diff --git a/.dockerignore b/.dockerignore index d8d3a3ebe..f7accabfd 100644 --- a/.dockerignore +++ b/.dockerignore @@ -24,3 +24,4 @@ Dockerfile settings.json src/node_modules +admin/node_modules diff --git a/.github/workflows/backend-tests.yml b/.github/workflows/backend-tests.yml index f41f5ac5e..0dd1000d8 100644 --- a/.github/workflows/backend-tests.yml +++ b/.github/workflows/backend-tests.yml @@ -54,6 +54,12 @@ jobs: - name: Install all dependencies and symlink for ep_etherpad-lite run: bin/installDeps.sh + - name: Install admin ui + working-directory: admin + run: pnpm install + - name: Build admin ui + working-directory: admin + run: pnpm build - name: Run the backend tests run: pnpm test @@ -105,6 +111,12 @@ jobs: - name: Install all dependencies and symlink for ep_etherpad-lite run: bin/installDeps.sh + - name: Install admin ui + working-directory: admin + run: pnpm install + - name: Build admin ui + working-directory: admin + run: pnpm build - name: Install Etherpad plugins run: > @@ -163,6 +175,12 @@ jobs: - name: Install all dependencies and symlink for ep_etherpad-lite run: bin/installOnWindows.bat + - name: Install admin ui + working-directory: admin + run: pnpm install + - name: Build admin ui + working-directory: admin + run: pnpm build - name: Fix up the settings.json run: | @@ -207,6 +225,12 @@ jobs: ${{ runner.os }}-pnpm-store- - name: Only install direct dependencies run: pnpm config set auto-install-peers false + - name: Install admin ui + working-directory: admin + run: pnpm install + - name: Build admin ui + working-directory: admin + run: pnpm build - name: Install Etherpad plugins # The --legacy-peer-deps flag is required to work around a bug in npm diff --git a/.github/workflows/frontend-admin-tests.yml b/.github/workflows/frontend-admin-tests.yml index aa1c4e70a..37c8ede08 100644 --- a/.github/workflows/frontend-admin-tests.yml +++ b/.github/workflows/frontend-admin-tests.yml @@ -12,7 +12,6 @@ jobs: name: with plugins runs-on: ubuntu-latest -# node: [16, 19, 20] >> Disabled node 16 and 18 because they do not work strategy: fail-fast: false matrix: @@ -83,11 +82,11 @@ jobs: run: "sed -i 's/\"enableAdminUITests\": false/\"enableAdminUITests\": true,\\n\"users\":{\"admin\":{\"password\":\"changeme\",\"is_admin\":true}}/' settings.json" - name: increase maxHttpBufferSize - run: "sed -i 's/\"maxHttpBufferSize\": 10000/\"maxHttpBufferSize\": 100000/' settings.json" + run: "sed -i 's/\"maxHttpBufferSize\": 10000/\"maxHttpBufferSize\": 10000000/' settings.json" - name: Disable import/export rate limiting run: | - sed -e '/^ *"importExportRateLimiting":/,/^ *\}/ s/"max":.*/"max": 1000000/' -i settings.json + sed -e '/^ *"importExportRateLimiting":/,/^ *\}/ s/"max":.*/"max": 100000000/' -i settings.json - name: Remove standard frontend test files, so only admin tests are run run: mv src/tests/frontend/specs/* /tmp && mv /tmp/admin*.js src/tests/frontend/specs diff --git a/.gitignore b/.gitignore index 38e2889d9..2a8335497 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,5 @@ out/ /src/bin/etherpad-1.deb /src/bin/node.exe plugin_packages -pnpm-lock.yaml \ No newline at end of file +pnpm-lock.yaml +/src/templates/admin diff --git a/Dockerfile b/Dockerfile index 7cd8105d9..35e4665b2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,13 @@ # # Author: muxator +FROM node:alpine as adminBuild + +WORKDIR /opt/etherpad-lite +COPY ./admin ./admin +RUN cd ./admin && npm install -g pnpm && pnpm install && pnpm run build --outDir ./dist + + FROM node:alpine as build LABEL maintainer="Etherpad team, https://github.com/ether/etherpad-lite" @@ -99,16 +106,18 @@ COPY --chown=etherpad:etherpad ./pnpm-workspace.yaml ./package.json ./ FROM build as development COPY --chown=etherpad:etherpad ./src/package.json .npmrc ./src/pnpm-lock.yaml ./src/ +COPY --chown=etherpad:etherpad --from=adminBuild /opt/etherpad-lite/admin/dist ./src/templates/admin RUN bin/installDeps.sh && { [ -z "${ETHERPAD_PLUGINS}" ] || \ pnpm install --workspace-root ${ETHERPAD_PLUGINS}; } - + FROM build as production ENV NODE_ENV=production ENV ETHERPAD_PRODUCTION=true COPY --chown=etherpad:etherpad ./src ./src +COPY --chown=etherpad:etherpad --from=adminBuild /opt/etherpad-lite/admin/dist ./src/templates/admin RUN bin/installDeps.sh && { [ -z "${ETHERPAD_PLUGINS}" ] || \ pnpm install --workspace-root ${ETHERPAD_PLUGINS}; } && \ diff --git a/admin/.eslintrc.cjs b/admin/.eslintrc.cjs new file mode 100644 index 000000000..d6c953795 --- /dev/null +++ b/admin/.eslintrc.cjs @@ -0,0 +1,18 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, +} diff --git a/admin/.gitignore b/admin/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/admin/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/admin/README.md b/admin/README.md new file mode 100644 index 000000000..0d6babedd --- /dev/null +++ b/admin/README.md @@ -0,0 +1,30 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js +export default { + // other rules... + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: ['./tsconfig.json', './tsconfig.node.json'], + tsconfigRootDir: __dirname, + }, +} +``` + +- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` +- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list diff --git a/admin/index.html b/admin/index.html new file mode 100644 index 000000000..8863894ed --- /dev/null +++ b/admin/index.html @@ -0,0 +1,14 @@ + + + + + + Etherpad Admin Dashboard + + + +
+
+ + + diff --git a/admin/package.json b/admin/package.json new file mode 100644 index 000000000..d74fedc12 --- /dev/null +++ b/admin/package.json @@ -0,0 +1,38 @@ +{ + "name": "admin", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "@radix-ui/react-dialog": "^1.0.5", + "@radix-ui/react-toast": "^1.1.5", + "i18next": "^23.10.1", + "i18next-browser-languagedetector": "^7.2.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-i18next": "^14.1.0", + "react-router-dom": "^6.22.3", + "zustand": "^4.5.2" + }, + "devDependencies": { + "@types/react": "^18.2.56", + "@types/react-dom": "^18.2.19", + "@typescript-eslint/eslint-plugin": "^7.0.2", + "@typescript-eslint/parser": "^7.0.2", + "@vitejs/plugin-react-swc": "^3.5.0", + "eslint": "^8.56.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.5", + "socket.io-client": "^4.7.4", + "typescript": "^5.2.2", + "vite": "^5.1.4", + "vite-plugin-static-copy": "^1.0.1", + "vite-plugin-svgr": "^4.2.0" + } +} diff --git a/admin/pnpm-workspace.yaml b/admin/pnpm-workspace.yaml new file mode 100644 index 000000000..e69de29bb diff --git a/admin/public/ep_admin_pads/ar.json b/admin/public/ep_admin_pads/ar.json new file mode 100644 index 000000000..746946edf --- /dev/null +++ b/admin/public/ep_admin_pads/ar.json @@ -0,0 +1,28 @@ +{ + "@metadata": { + "authors": [ + "Meno25", + "محمد أحمد عبد الفتاح" + ] + }, + "ep_adminpads2_action": "فعل", + "ep_adminpads2_autoupdate-label": "التحديث التلقائي على تغييرات الوسادة", + "ep_adminpads2_autoupdate.title": "لتمكين أو تعطيل التحديثات التلقائية للاستعلام الحالي.", + "ep_adminpads2_confirm": "هل تريد حقًا حذف الوسادة {{padID}}؟", + "ep_adminpads2_delete.value": "حذف", + "ep_adminpads2_last-edited": "آخر تعديل", + "ep_adminpads2_loading": "جارٍ التحميل...", + "ep_adminpads2_manage-pads": "إدارة الفوط", + "ep_adminpads2_no-results": "لا توجد نتائج.", + "ep_adminpads2_pad-user-count": "عدد المستخدمين الوسادة", + "ep_adminpads2_padname": "بادنام", + "ep_adminpads2_search-box.placeholder": "مصطلح البحث", + "ep_adminpads2_search-button.value": "بحث", + "ep_adminpads2_search-done": "اكتمل البحث", + "ep_adminpads2_search-error-explanation": "واجه الخادم خطأً أثناء البحث عن منصات:", + "ep_adminpads2_search-error-title": "فشل في الحصول على قائمة الوسادة", + "ep_adminpads2_search-heading": "ابحث عن الفوط", + "ep_adminpads2_title": "إدارة الوسادة", + "ep_adminpads2_unknown-error": "خطأ غير معروف", + "ep_adminpads2_unknown-status": "حالة غير معروفة" +} diff --git a/admin/public/ep_admin_pads/bn.json b/admin/public/ep_admin_pads/bn.json new file mode 100644 index 000000000..0048b52bb --- /dev/null +++ b/admin/public/ep_admin_pads/bn.json @@ -0,0 +1,23 @@ +{ + "@metadata": { + "authors": [ + "আজিজ", + "আফতাবুজ্জামান" + ] + }, + "ep_adminpads2_action": "কার্য", + "ep_adminpads2_delete.value": "মুছে ফেলুন", + "ep_adminpads2_last-edited": "সর্বশেষ সম্পাদিত", + "ep_adminpads2_loading": "লোড হচ্ছে...", + "ep_adminpads2_manage-pads": "প্যাড পরিচালনা করুন", + "ep_adminpads2_no-results": "ফলাফল নেই", + "ep_adminpads2_padname": "প্যাডের নাম", + "ep_adminpads2_search-button.value": "অনুসন্ধান", + "ep_adminpads2_search-done": "অনুসন্ধান সম্পূর্ণ", + "ep_adminpads2_search-error-explanation": "প্যাড অনুসন্ধান করার সময় সার্ভার একটি ত্রুটির সম্মুখীন হয়েছে:", + "ep_adminpads2_search-error-title": "প্যাডের তালিকা পেতে ব্যর্থ", + "ep_adminpads2_search-heading": "প্যাড অনুসন্ধান করুন", + "ep_adminpads2_title": "প্যাড প্রশাসন", + "ep_adminpads2_unknown-error": "অজানা ত্রুটি", + "ep_adminpads2_unknown-status": "অজানা অবস্থা" +} diff --git a/admin/public/ep_admin_pads/ca.json b/admin/public/ep_admin_pads/ca.json new file mode 100644 index 000000000..1d4e34216 --- /dev/null +++ b/admin/public/ep_admin_pads/ca.json @@ -0,0 +1,27 @@ +{ + "@metadata": { + "authors": [ + "Mguix" + ] + }, + "ep_adminpads2_action": "Acció", + "ep_adminpads2_autoupdate-label": "Actualització automàtica en cas de canvis de pad", + "ep_adminpads2_autoupdate.title": "Activa o desactiva les actualitzacions automàtiques per a la consulta actual.", + "ep_adminpads2_confirm": "Esteu segur que voleu suprimir el pad {{padID}}?", + "ep_adminpads2_delete.value": "Esborrar", + "ep_adminpads2_last-edited": "Darrera modificació", + "ep_adminpads2_loading": "S’està carregant…", + "ep_adminpads2_manage-pads": "Gestiona els pads", + "ep_adminpads2_no-results": "No hi ha cap resultat", + "ep_adminpads2_pad-user-count": "Nombre d'usuaris de pads", + "ep_adminpads2_padname": "Nom del pad", + "ep_adminpads2_search-box.placeholder": "Terme de cerca", + "ep_adminpads2_search-button.value": "Cercar", + "ep_adminpads2_search-done": "Cerca completa", + "ep_adminpads2_search-error-explanation": "El servidor ha trobat un error mentre buscava pads:", + "ep_adminpads2_search-error-title": "No s'ha pogut obtenir la llista del pad", + "ep_adminpads2_search-heading": "Cerca pads", + "ep_adminpads2_title": "Administració del pad", + "ep_adminpads2_unknown-error": "Error desconegut", + "ep_adminpads2_unknown-status": "Estat desconegut" +} diff --git a/admin/public/ep_admin_pads/cs.json b/admin/public/ep_admin_pads/cs.json new file mode 100644 index 000000000..19e92894d --- /dev/null +++ b/admin/public/ep_admin_pads/cs.json @@ -0,0 +1,27 @@ +{ + "@metadata": { + "authors": [ + "Spotter" + ] + }, + "ep_adminpads2_action": "Akce", + "ep_adminpads2_autoupdate-label": "Automatická aktualizace změn Padu", + "ep_adminpads2_autoupdate.title": "Povolí nebo zakáže automatické aktualizace pro aktuální dotaz.", + "ep_adminpads2_confirm": "Opravdu chcete odstranit pad {{padID}}?", + "ep_adminpads2_delete.value": "Smazat", + "ep_adminpads2_last-edited": "Naposledy upraveno", + "ep_adminpads2_loading": "Načítání…", + "ep_adminpads2_manage-pads": "Spravovat pady", + "ep_adminpads2_no-results": "Žádné výsledky", + "ep_adminpads2_pad-user-count": "Počet uživatelů padu", + "ep_adminpads2_padname": "Název padu", + "ep_adminpads2_search-box.placeholder": "Hledaný výraz", + "ep_adminpads2_search-button.value": "Hledat", + "ep_adminpads2_search-done": "Hledání dokončeno", + "ep_adminpads2_search-error-explanation": "Při hledání padů došlo k chybě serveru:", + "ep_adminpads2_search-error-title": "Seznam padů se nepodařilo získat", + "ep_adminpads2_search-heading": "Hledat pady", + "ep_adminpads2_title": "Správa Padu", + "ep_adminpads2_unknown-error": "Neznámá chyba", + "ep_adminpads2_unknown-status": "Neznámý stav" +} diff --git a/admin/public/ep_admin_pads/cy.json b/admin/public/ep_admin_pads/cy.json new file mode 100644 index 000000000..02546da90 --- /dev/null +++ b/admin/public/ep_admin_pads/cy.json @@ -0,0 +1,27 @@ +{ + "@metadata": { + "authors": [ + "Robin Owain" + ] + }, + "ep_adminpads2_action": "Gweithred", + "ep_adminpads2_autoupdate-label": "Diweddaru newidiadau pad yn otomatig", + "ep_adminpads2_autoupdate.title": "Galluogi neu analluogi diweddaru'r ymholiad cyfredol.", + "ep_adminpads2_confirm": "Siwr eich bod am ddileu'r pad {{padID}}?", + "ep_adminpads2_delete.value": "Dileu", + "ep_adminpads2_last-edited": "Golygwyd ddiwethaf", + "ep_adminpads2_loading": "Wrthi'n llwytho...", + "ep_adminpads2_manage-pads": "Rheoli'r padiau", + "ep_adminpads2_no-results": "Dim canlyniad", + "ep_adminpads2_pad-user-count": "Cyfri defnyddiwr pad", + "ep_adminpads2_padname": "Enwpad", + "ep_adminpads2_search-box.placeholder": "Term chwilio", + "ep_adminpads2_search-button.value": "Chwilio", + "ep_adminpads2_search-done": "Wedi gorffen", + "ep_adminpads2_search-error-explanation": "Nam ar y gweinydd wrth chwilio'r padiau:", + "ep_adminpads2_search-error-title": "Methwyd a chael y rhestr pad", + "ep_adminpads2_search-heading": "Chwilio am badiau", + "ep_adminpads2_title": "Gweinyddiaeth y pad", + "ep_adminpads2_unknown-error": "Nam o ryw fath", + "ep_adminpads2_unknown-status": "Statws anhysbys" +} diff --git a/admin/public/ep_admin_pads/da.json b/admin/public/ep_admin_pads/da.json new file mode 100644 index 000000000..a5303b9cb --- /dev/null +++ b/admin/public/ep_admin_pads/da.json @@ -0,0 +1,14 @@ +{ + "@metadata": { + "authors": [ + "Saederup92" + ] + }, + "ep_adminpads2_action": "Handling", + "ep_adminpads2_delete.value": "Slet", + "ep_adminpads2_last-edited": "Sidst redigeret", + "ep_adminpads2_loading": "Indlæser...", + "ep_adminpads2_no-results": "Ingen resultater", + "ep_adminpads2_unknown-error": "Ukendt fejl", + "ep_adminpads2_unknown-status": "Ukendt status" +} diff --git a/admin/public/ep_admin_pads/de.json b/admin/public/ep_admin_pads/de.json new file mode 100644 index 000000000..afb553caf --- /dev/null +++ b/admin/public/ep_admin_pads/de.json @@ -0,0 +1,32 @@ +{ + "@metadata": { + "authors": [ + "Brettchenweber", + "Justman10000", + "Lorisobi", + "SamTV", + "Umlaut", + "Zunkelty" + ] + }, + "ep_adminpads2_action": "Aktion", + "ep_adminpads2_autoupdate-label": "Automatisch bei Pad-Änderungen updaten", + "ep_adminpads2_autoupdate.title": "Aktiviert oder deaktiviert automatische Aktualisierungen für die aktuelle Abfrage.", + "ep_adminpads2_confirm": "Willst du das Pad {{padID}} wirklich löschen?", + "ep_adminpads2_delete.value": "Löschen", + "ep_adminpads2_last-edited": "Zuletzt bearbeitet", + "ep_adminpads2_loading": "Lädt...", + "ep_adminpads2_manage-pads": "Pads verwalten", + "ep_adminpads2_no-results": "Keine Ergebnisse", + "ep_adminpads2_pad-user-count": "Nutzerzahl des Pads", + "ep_adminpads2_padname": "Padname", + "ep_adminpads2_search-box.placeholder": "Suchbegriff", + "ep_adminpads2_search-button.value": "Suche", + "ep_adminpads2_search-done": "Suche vollendet", + "ep_adminpads2_search-error-explanation": "Der Server ist bei der Suche nach Pads auf einen Fehler gestoßen:", + "ep_adminpads2_search-error-title": "Pad-Liste konnte nicht abgerufen werden", + "ep_adminpads2_search-heading": "Nach Pads suchen", + "ep_adminpads2_title": "Pad-Verwaltung", + "ep_adminpads2_unknown-error": "Unbekannter Fehler", + "ep_adminpads2_unknown-status": "Unbekannter Status" +} diff --git a/admin/public/ep_admin_pads/diq.json b/admin/public/ep_admin_pads/diq.json new file mode 100644 index 000000000..983680965 --- /dev/null +++ b/admin/public/ep_admin_pads/diq.json @@ -0,0 +1,28 @@ +{ + "@metadata": { + "authors": [ + "1917 Ekim Devrimi", + "Mirzali" + ] + }, + "ep_adminpads2_action": "Hereketi", + "ep_adminpads2_autoupdate-label": "Vurnayışanê pedi otomatik rocane kerê", + "ep_adminpads2_autoupdate.title": "Persê mewcudi rê rocaneyışanê otomatika aktiv ke ya zi dewrê ra vecê", + "ep_adminpads2_confirm": "Şıma qayılê pedê {{padID}} bıesternê?", + "ep_adminpads2_delete.value": "Bestere", + "ep_adminpads2_last-edited": "Vurnayışo peyên", + "ep_adminpads2_loading": "Bar beno...", + "ep_adminpads2_manage-pads": "Pedan idare kerê", + "ep_adminpads2_no-results": "Netice çıniyo", + "ep_adminpads2_pad-user-count": "Amarê karberanê pedi", + "ep_adminpads2_padname": "Padname", + "ep_adminpads2_search-box.placeholder": "termê cıgêrayış", + "ep_adminpads2_search-button.value": "Cı geyre", + "ep_adminpads2_search-done": "Cıgeyrayışi temam", + "ep_adminpads2_search-error-explanation": "Server cıgeyrayışê pedan de yew xetaya raşt ame", + "ep_adminpads2_search-error-title": "Lista pedi nêgêriye", + "ep_adminpads2_search-heading": "Pedan cıgeyrayış", + "ep_adminpads2_title": "İdarey pedi", + "ep_adminpads2_unknown-error": "Xetaya nêzanıtiye", + "ep_adminpads2_unknown-status": "Weziyeto nêzanaye" +} diff --git a/admin/public/ep_admin_pads/dsb.json b/admin/public/ep_admin_pads/dsb.json new file mode 100644 index 000000000..363732a20 --- /dev/null +++ b/admin/public/ep_admin_pads/dsb.json @@ -0,0 +1,27 @@ +{ + "@metadata": { + "authors": [ + "Michawiki" + ] + }, + "ep_adminpads2_action": "Akcija", + "ep_adminpads2_autoupdate-label": "Pśi změnach na zapisniku awtomatiski aktualizěrowaś", + "ep_adminpads2_autoupdate.title": "Zmóžnja abo znjemóžnja awtomatiske aktualizacije za aktualne wótpšašowanje.", + "ep_adminpads2_confirm": "Cośo napšawdu zapisnik {{padID}} lašowaś?", + "ep_adminpads2_delete.value": "Lašowaś", + "ep_adminpads2_last-edited": "Slědna změna", + "ep_adminpads2_loading": "Zacytujo se...", + "ep_adminpads2_manage-pads": "Zapisniki zastojaś", + "ep_adminpads2_no-results": "Žedne wuslědki", + "ep_adminpads2_pad-user-count": "Licba wužywarjow zapisnika", + "ep_adminpads2_padname": "Mě zapisnika", + "ep_adminpads2_search-box.placeholder": "Pytańske zapśimjeśe", + "ep_adminpads2_search-button.value": "Pytaś", + "ep_adminpads2_search-done": "Pytanje dokóńcone", + "ep_adminpads2_search-error-explanation": "Serwer jo starcył na zmólku, mjaztym až jo pytał za zapisnikami:", + "ep_adminpads2_search-error-title": "Lisćina zapisnikow njedajo se wobstaraś", + "ep_adminpads2_search-heading": "Za zapisnikami pytaś", + "ep_adminpads2_title": "Zapisnikowa administracija", + "ep_adminpads2_unknown-error": "Njeznata zmólka", + "ep_adminpads2_unknown-status": "Njeznaty status" +} diff --git a/admin/public/ep_admin_pads/el.json b/admin/public/ep_admin_pads/el.json new file mode 100644 index 000000000..77b6af3dd --- /dev/null +++ b/admin/public/ep_admin_pads/el.json @@ -0,0 +1,16 @@ +{ + "@metadata": { + "authors": [ + "Norhorn" + ] + }, + "ep_adminpads2_delete.value": "Διαγραφή", + "ep_adminpads2_last-edited": "Τελευταία απεξεργασία", + "ep_adminpads2_loading": "Φόρτωση…", + "ep_adminpads2_no-results": "Κανένα αποτέλεσμα", + "ep_adminpads2_search-box.placeholder": "Αναζήτηση όρων", + "ep_adminpads2_search-button.value": "Αναζήτηση", + "ep_adminpads2_search-done": "Ολοκλήρωση αναζήτησης", + "ep_adminpads2_unknown-error": "Άγνωστο σφάλμα", + "ep_adminpads2_unknown-status": "Άγνωστη κατάσταση" +} diff --git a/admin/public/ep_admin_pads/en.json b/admin/public/ep_admin_pads/en.json new file mode 100644 index 000000000..8a9044b1b --- /dev/null +++ b/admin/public/ep_admin_pads/en.json @@ -0,0 +1,22 @@ +{ + "ep_adminpads2_action": "Action", + "ep_adminpads2_autoupdate-label": "Auto-update on pad changes", + "ep_adminpads2_autoupdate.title": "Enables or disables automatic updates for the current query.", + "ep_adminpads2_confirm": "Do you really want to delete the pad {{padID}}?", + "ep_adminpads2_delete.value": "Delete", + "ep_adminpads2_last-edited": "Last edited", + "ep_adminpads2_loading": "Loading…", + "ep_adminpads2_manage-pads": "Manage pads", + "ep_adminpads2_no-results": "No results", + "ep_adminpads2_pad-user-count": "Pad user count", + "ep_adminpads2_padname": "Padname", + "ep_adminpads2_search-box.placeholder": "Search term", + "ep_adminpads2_search-button.value": "Search", + "ep_adminpads2_search-done": "Search complete", + "ep_adminpads2_search-error-explanation": "The server encountered an error while searching for pads:", + "ep_adminpads2_search-error-title": "Failed to get pad list", + "ep_adminpads2_search-heading": "Search for pads", + "ep_adminpads2_title": "Pad administration", + "ep_adminpads2_unknown-error": "Unknown error", + "ep_adminpads2_unknown-status": "Unknown status" +} diff --git a/admin/public/ep_admin_pads/eu.json b/admin/public/ep_admin_pads/eu.json new file mode 100644 index 000000000..71d9dfe79 --- /dev/null +++ b/admin/public/ep_admin_pads/eu.json @@ -0,0 +1,27 @@ +{ + "@metadata": { + "authors": [ + "Izendegi" + ] + }, + "ep_adminpads2_action": "Ekintza", + "ep_adminpads2_autoupdate-label": "Automatikoki eguneratu pad-aren aldaketak daudenean", + "ep_adminpads2_autoupdate.title": "Oraingo kontsultarako eguneratze automatikoak gaitu edo desgaitzen du.", + "ep_adminpads2_confirm": "Ziur zaude {{padID}} pad-a ezabatu nahi duzula?", + "ep_adminpads2_delete.value": "Ezabatu", + "ep_adminpads2_last-edited": "Azkenengoz editatua", + "ep_adminpads2_loading": "Kargatzen...", + "ep_adminpads2_manage-pads": "Kudeatu pad-ak", + "ep_adminpads2_no-results": "Emaitzarik ez", + "ep_adminpads2_pad-user-count": "Pad-erabiltzaile kopurua", + "ep_adminpads2_padname": "Pad-izena", + "ep_adminpads2_search-box.placeholder": "Bilaketa testua", + "ep_adminpads2_search-button.value": "Bilatu", + "ep_adminpads2_search-done": "Bilaketa osatu da", + "ep_adminpads2_search-error-explanation": "Zerbitzariak errore bat izan du pad-ak bilatzean:", + "ep_adminpads2_search-error-title": "Pad-zerrenda eskuratzeak huts egin du", + "ep_adminpads2_search-heading": "Bilatu pad-ak", + "ep_adminpads2_title": "Pad-en kudeaketa", + "ep_adminpads2_unknown-error": "Errore ezezaguna", + "ep_adminpads2_unknown-status": "Egoera ezezaguna" +} diff --git a/admin/public/ep_admin_pads/ff.json b/admin/public/ep_admin_pads/ff.json new file mode 100644 index 000000000..8cb5aea99 --- /dev/null +++ b/admin/public/ep_admin_pads/ff.json @@ -0,0 +1,27 @@ +{ + "@metadata": { + "authors": [ + "Ibrahima Malal Sarr" + ] + }, + "ep_adminpads2_action": "Baɗal", + "ep_adminpads2_autoupdate-label": "Hesɗitin e jaajol tuma baylagol faɗo", + "ep_adminpads2_autoupdate.title": "Hurminat walla daaƴa kesɗitine jaaje wonannde ɗaɓɓitannde wonaande.", + "ep_adminpads2_confirm": "Aɗa yiɗi e jaati momtude faɗo {{padID}}?", + "ep_adminpads2_delete.value": "Momtu", + "ep_adminpads2_last-edited": "Taƴtaa sakket", + "ep_adminpads2_loading": "Nana loowa…", + "ep_adminpads2_manage-pads": "Toppito paɗe", + "ep_adminpads2_no-results": "Alaa njaltudi", + "ep_adminpads2_pad-user-count": "Limoore huutorɓe faɗo", + "ep_adminpads2_padname": "Innde faɗo", + "ep_adminpads2_search-box.placeholder": "Helmere njiilaw", + "ep_adminpads2_search-button.value": "Yiylo", + "ep_adminpads2_search-done": "Njiylaw timmii", + "ep_adminpads2_search-error-explanation": "Sarworde ndee hawrii e juumre tuma nde yiylotoo faɗo:", + "ep_adminpads2_search-error-title": "Horiima heɓde doggol paɗe", + "ep_adminpads2_search-heading": "Yiylo paɗe", + "ep_adminpads2_title": "Yiylorde paɗe", + "ep_adminpads2_unknown-error": "Juumre nde anndaaka", + "ep_adminpads2_unknown-status": "Ngonka ka anndaaka" +} diff --git a/admin/public/ep_admin_pads/fi.json b/admin/public/ep_admin_pads/fi.json new file mode 100644 index 000000000..708b2bef8 --- /dev/null +++ b/admin/public/ep_admin_pads/fi.json @@ -0,0 +1,27 @@ +{ + "@metadata": { + "authors": [ + "Artnay", + "Kyykaarme", + "MITO", + "Maantietäjä", + "Yupik" + ] + }, + "ep_adminpads2_action": "Toiminto", + "ep_adminpads2_delete.value": "Poista", + "ep_adminpads2_last-edited": "Viimeksi muokattu", + "ep_adminpads2_loading": "Ladataan...", + "ep_adminpads2_manage-pads": "Hallitse muistioita", + "ep_adminpads2_no-results": "Ei tuloksia", + "ep_adminpads2_pad-user-count": "Pad-käyttäjien määrä", + "ep_adminpads2_padname": "Muistion nimi", + "ep_adminpads2_search-box.placeholder": "Haettava teksti", + "ep_adminpads2_search-button.value": "Etsi", + "ep_adminpads2_search-done": "Haku valmis", + "ep_adminpads2_search-error-explanation": "Palvelimessa tapahtui virhe etsiessään muistioita:", + "ep_adminpads2_search-error-title": "Pad-luettelon hakeminen epäonnistui", + "ep_adminpads2_search-heading": "Etsi sisältöä", + "ep_adminpads2_unknown-error": "Tuntematon virhe", + "ep_adminpads2_unknown-status": "Tuntematon tila" +} diff --git a/admin/public/ep_admin_pads/fr.json b/admin/public/ep_admin_pads/fr.json new file mode 100644 index 000000000..e6c8a8703 --- /dev/null +++ b/admin/public/ep_admin_pads/fr.json @@ -0,0 +1,27 @@ +{ + "@metadata": { + "authors": [ + "Verdy p" + ] + }, + "ep_adminpads2_action": "Action", + "ep_adminpads2_autoupdate-label": "Mise à jour automatique en cas de changements du bloc-notes", + "ep_adminpads2_autoupdate.title": "Active ou désactive les mises à jour automatiques pour la requête actuelle.", + "ep_adminpads2_confirm": "Voulez-vous vraiment supprimer le bloc-notes {{padID}} ?", + "ep_adminpads2_delete.value": "Supprimer", + "ep_adminpads2_last-edited": "Dernière modification", + "ep_adminpads2_loading": "Chargement en cours...", + "ep_adminpads2_manage-pads": "Gérer les bloc-notes", + "ep_adminpads2_no-results": "Aucun résultat", + "ep_adminpads2_pad-user-count": "Nombre d’utilisateurs du bloc-notes", + "ep_adminpads2_padname": "Nom du bloc-notes", + "ep_adminpads2_search-box.placeholder": "Terme de recherche", + "ep_adminpads2_search-button.value": "Rechercher", + "ep_adminpads2_search-done": "Recherche terminée", + "ep_adminpads2_search-error-explanation": "Le serveur a rencontré une erreur en cherchant des blocs-notes :", + "ep_adminpads2_search-error-title": "Échec d’obtention de la liste de blocs-notes", + "ep_adminpads2_search-heading": "Rechercher des blocs-notes", + "ep_adminpads2_title": "Administration du bloc-notes", + "ep_adminpads2_unknown-error": "Erreur inconnue", + "ep_adminpads2_unknown-status": "État inconnu" +} diff --git a/admin/public/ep_admin_pads/gl.json b/admin/public/ep_admin_pads/gl.json new file mode 100644 index 000000000..5e6b66549 --- /dev/null +++ b/admin/public/ep_admin_pads/gl.json @@ -0,0 +1,27 @@ +{ + "@metadata": { + "authors": [ + "Ghose" + ] + }, + "ep_adminpads2_action": "Accións", + "ep_adminpads2_autoupdate-label": "Actualización automática dos cambios", + "ep_adminpads2_autoupdate.title": "Activa ou desactiva as actualizacións automáticas para a consulta actual.", + "ep_adminpads2_confirm": "Tes a certeza de querer eliminar o pad {{padID}}?", + "ep_adminpads2_delete.value": "Eliminar", + "ep_adminpads2_last-edited": "Última edición", + "ep_adminpads2_loading": "Cargando…", + "ep_adminpads2_manage-pads": "Xestionar pads", + "ep_adminpads2_no-results": "Sen resultados", + "ep_adminpads2_pad-user-count": "Usuarias neste pad", + "ep_adminpads2_padname": "Nome do pad", + "ep_adminpads2_search-box.placeholder": "Buscar termo", + "ep_adminpads2_search-button.value": "Buscar", + "ep_adminpads2_search-done": "Busca completa", + "ep_adminpads2_search-error-explanation": "O servidor atopou un fallo cando buscaba pads:", + "ep_adminpads2_search-error-title": "Non se obtivo a lista de pads", + "ep_adminpads2_search-heading": "Buscar pads", + "ep_adminpads2_title": "Administración do pad", + "ep_adminpads2_unknown-error": "Erro descoñecido", + "ep_adminpads2_unknown-status": "Estado descoñecido" +} diff --git a/admin/public/ep_admin_pads/he.json b/admin/public/ep_admin_pads/he.json new file mode 100644 index 000000000..8b506946b --- /dev/null +++ b/admin/public/ep_admin_pads/he.json @@ -0,0 +1,27 @@ +{ + "@metadata": { + "authors": [ + "YaronSh" + ] + }, + "ep_adminpads2_action": "פעולה", + "ep_adminpads2_autoupdate-label": "לעדכן אוטומטית כשהמחברת נערכת", + "ep_adminpads2_autoupdate.title": "הפעלה או השבתה של עדכונים אוטומטיים לשאילתה הנוכחית.", + "ep_adminpads2_confirm": "למחוק את המחברת {{padID}}?", + "ep_adminpads2_delete.value": "מחיקה", + "ep_adminpads2_last-edited": "עריכה אחרונה", + "ep_adminpads2_loading": "בטעינה…", + "ep_adminpads2_manage-pads": "ניהול מחברות", + "ep_adminpads2_no-results": "אין תוצאות", + "ep_adminpads2_pad-user-count": "ספירת משתמשים במחברת", + "ep_adminpads2_padname": "שם המחברת", + "ep_adminpads2_search-box.placeholder": "הביטוי לחיפוש", + "ep_adminpads2_search-button.value": "חיפוש", + "ep_adminpads2_search-done": "החיפוש הושלם", + "ep_adminpads2_search-error-explanation": "השרת נתקל בשגיאה בעת חיפוש מחברות:", + "ep_adminpads2_search-error-title": "קבלת רשימת המחברות נכשלה", + "ep_adminpads2_search-heading": "חיפוש אחר מחברות", + "ep_adminpads2_title": "ניהול מחברות", + "ep_adminpads2_unknown-error": "שגיאה בלתי־ידועה", + "ep_adminpads2_unknown-status": "מצב לא ידוע" +} diff --git a/admin/public/ep_admin_pads/hsb.json b/admin/public/ep_admin_pads/hsb.json new file mode 100644 index 000000000..a6c29611f --- /dev/null +++ b/admin/public/ep_admin_pads/hsb.json @@ -0,0 +1,27 @@ +{ + "@metadata": { + "authors": [ + "Michawiki" + ] + }, + "ep_adminpads2_action": "Akcija", + "ep_adminpads2_autoupdate-label": "Při změnach na zapisniku awtomatisce aktualizować", + "ep_adminpads2_autoupdate.title": "Zmóžnja abo znjemóžnja awtomatiske aktualizacije za aktualne wotprašowanje.", + "ep_adminpads2_confirm": "Chceće woprawdźe zapisnik {{padID}} zhašeć?", + "ep_adminpads2_delete.value": "Zhašeć", + "ep_adminpads2_last-edited": "Poslednja změna", + "ep_adminpads2_loading": "Začituje so...", + "ep_adminpads2_manage-pads": "Zapisniki rjadować", + "ep_adminpads2_no-results": "Žane wuslědki.", + "ep_adminpads2_pad-user-count": "Ličba wužiwarjow zapisnika", + "ep_adminpads2_padname": "Mjeno zapisnika", + "ep_adminpads2_search-box.placeholder": "Pytanske zapřijeće", + "ep_adminpads2_search-button.value": "Pytać", + "ep_adminpads2_search-done": "Pytanje dokónčene", + "ep_adminpads2_search-error-explanation": "Serwer je na zmylk storčił, mjeztym zo je za zapisnikami pytał:", + "ep_adminpads2_search-error-title": "Lisćina zapisnikow njeda so wobstarać", + "ep_adminpads2_search-heading": "Za zapisnikami pytać", + "ep_adminpads2_title": "Zapisnikowa administracija", + "ep_adminpads2_unknown-error": "Njeznaty zmylk", + "ep_adminpads2_unknown-status": "Njeznaty status" +} diff --git a/admin/public/ep_admin_pads/hu.json b/admin/public/ep_admin_pads/hu.json new file mode 100644 index 000000000..9210761bc --- /dev/null +++ b/admin/public/ep_admin_pads/hu.json @@ -0,0 +1,25 @@ +{ + "@metadata": { + "authors": [] + }, + "ep_adminpads2_action": "Művelet", + "ep_adminpads2_autoupdate-label": "Változáskor jegyzetfüzet önműködő frissítése", + "ep_adminpads2_autoupdate.title": "Önműködő frissítése az jelenlegi lekérdezéshez be- vagy kikapcsolása.", + "ep_adminpads2_confirm": "Biztosan törölni szeretné a(z) {{padID}} jegyzetfüzetet?", + "ep_adminpads2_delete.value": "Törlés", + "ep_adminpads2_last-edited": "Utoljára szerkesztve", + "ep_adminpads2_loading": "Betöltés folyamatban…", + "ep_adminpads2_manage-pads": "Jegyzetfüzetek kezelése", + "ep_adminpads2_no-results": "Nincs találat", + "ep_adminpads2_pad-user-count": "Jegyzetfüzet felhasználók száma", + "ep_adminpads2_padname": "Jegyzetfüzet név", + "ep_adminpads2_search-box.placeholder": "Keresési kifejezés", + "ep_adminpads2_search-button.value": "Keresés", + "ep_adminpads2_search-done": "Keresés befejezve", + "ep_adminpads2_search-error-explanation": "A kiszolgáló hibát észlelt a jegyzetfüzetek keresésekor:", + "ep_adminpads2_search-error-title": "Nem sikerült lekérni a jegyzetfüzet listát", + "ep_adminpads2_search-heading": "Jegyzetfüzetek keresése", + "ep_adminpads2_title": "Jegyzetfüzet felügyelete", + "ep_adminpads2_unknown-error": "Ismeretlen hiba", + "ep_adminpads2_unknown-status": "Ismeretlen állapot" +} diff --git a/admin/public/ep_admin_pads/ia.json b/admin/public/ep_admin_pads/ia.json new file mode 100644 index 000000000..f0e00e5ca --- /dev/null +++ b/admin/public/ep_admin_pads/ia.json @@ -0,0 +1,27 @@ +{ + "@metadata": { + "authors": [ + "McDutchie" + ] + }, + "ep_adminpads2_action": "Action", + "ep_adminpads2_autoupdate-label": "Actualisar automaticamente le pad in caso de cambiamentos", + "ep_adminpads2_autoupdate.title": "Activa o disactiva le actualisationes automatic pro le consulta actual.", + "ep_adminpads2_confirm": "Es tu secur de voler deler le pad {{padID}}?", + "ep_adminpads2_delete.value": "Deler", + "ep_adminpads2_last-edited": "Ultime modification", + "ep_adminpads2_loading": "Cargamento in curso…", + "ep_adminpads2_manage-pads": "Gerer pads", + "ep_adminpads2_no-results": "Nulle resultato", + "ep_adminpads2_pad-user-count": "Numero de usatores del pad", + "ep_adminpads2_padname": "Nomine del pad", + "ep_adminpads2_search-box.placeholder": "Termino de recerca", + "ep_adminpads2_search-button.value": "Cercar", + "ep_adminpads2_search-done": "Recerca terminate", + "ep_adminpads2_search-error-explanation": "Le servitor ha incontrate un error durante le recerca de pads:", + "ep_adminpads2_search-error-title": "Non poteva obtener le lista de pads", + "ep_adminpads2_search-heading": "Cercar pads", + "ep_adminpads2_title": "Administration de pads", + "ep_adminpads2_unknown-error": "Error incognite", + "ep_adminpads2_unknown-status": "Stato incognite" +} diff --git a/admin/public/ep_admin_pads/it.json b/admin/public/ep_admin_pads/it.json new file mode 100644 index 000000000..493cbb4d5 --- /dev/null +++ b/admin/public/ep_admin_pads/it.json @@ -0,0 +1,16 @@ +{ + "@metadata": { + "authors": [ + "Beta16", + "Luca.favorido" + ] + }, + "ep_adminpads2_action": "Azione", + "ep_adminpads2_delete.value": "Cancella", + "ep_adminpads2_last-edited": "Ultima modifica", + "ep_adminpads2_loading": "Caricamento…", + "ep_adminpads2_no-results": "Nessun risultato", + "ep_adminpads2_search-button.value": "Cerca", + "ep_adminpads2_unknown-error": "Errore sconosciuto", + "ep_adminpads2_unknown-status": "Stato sconosciuto" +} diff --git a/admin/public/ep_admin_pads/kn.json b/admin/public/ep_admin_pads/kn.json new file mode 100644 index 000000000..1e9019611 --- /dev/null +++ b/admin/public/ep_admin_pads/kn.json @@ -0,0 +1,13 @@ +{ + "@metadata": { + "authors": [ + "ಮಲ್ನಾಡಾಚ್ ಕೊಂಕ್ಣೊ" + ] + }, + "ep_adminpads2_action": "ಕ್ರಿಯೆ", + "ep_adminpads2_delete.value": "ಅಳಿಸು", + "ep_adminpads2_loading": "ತುಂಬಿಸಲಾಗುತ್ತಿದೆ…", + "ep_adminpads2_no-results": "ಯಾವ ಫಲಿತಾಂಶಗಳೂ ಇಲ್ಲ", + "ep_adminpads2_search-button.value": "ಹುಡುಕು", + "ep_adminpads2_unknown-error": "ಅಪರಿಚಿತ ದೋಷ" +} diff --git a/admin/public/ep_admin_pads/ko.json b/admin/public/ep_admin_pads/ko.json new file mode 100644 index 000000000..9ab8feed3 --- /dev/null +++ b/admin/public/ep_admin_pads/ko.json @@ -0,0 +1,28 @@ +{ + "@metadata": { + "authors": [ + "Ykhwong", + "그냥기여자" + ] + }, + "ep_adminpads2_action": "동작", + "ep_adminpads2_autoupdate-label": "패드 변경 시 자동 업데이트", + "ep_adminpads2_autoupdate.title": "현재 쿼리의 자동 업데이트를 활성화하거나 비활성화합니다.", + "ep_adminpads2_confirm": "{{padID}} 패드를 삭제하시겠습니까?", + "ep_adminpads2_delete.value": "삭제", + "ep_adminpads2_last-edited": "최근 편집", + "ep_adminpads2_loading": "불러오는 중...", + "ep_adminpads2_manage-pads": "패드 관리", + "ep_adminpads2_no-results": "결과 없음", + "ep_adminpads2_pad-user-count": "패드 사용자 수", + "ep_adminpads2_padname": "패드 이름", + "ep_adminpads2_search-box.placeholder": "검색어", + "ep_adminpads2_search-button.value": "검색", + "ep_adminpads2_search-done": "검색 완료", + "ep_adminpads2_search-error-explanation": "패드 검색 중 서버에 오류가 발생했습니다:", + "ep_adminpads2_search-error-title": "패드 목록 가져오기 실패", + "ep_adminpads2_search-heading": "패드 검색", + "ep_adminpads2_title": "패드 관리", + "ep_adminpads2_unknown-error": "알 수 없는 오류", + "ep_adminpads2_unknown-status": "알 수 없는 상태" +} diff --git a/admin/public/ep_admin_pads/krc.json b/admin/public/ep_admin_pads/krc.json new file mode 100644 index 000000000..2caf4f099 --- /dev/null +++ b/admin/public/ep_admin_pads/krc.json @@ -0,0 +1,27 @@ +{ + "@metadata": { + "authors": [ + "Къарачайлы" + ] + }, + "ep_adminpads2_action": "Этиу", + "ep_adminpads2_autoupdate-label": "Блокнот тюрлендириулеринде автомат халда джангыртыу", + "ep_adminpads2_autoupdate.title": "Баргъан излем ючюн автомат халда джангыртыуланы джандын неда джукълат.", + "ep_adminpads2_confirm": "{{padID}} блокнотну керти да кетерирге излеймисиз?", + "ep_adminpads2_delete.value": "Кетер", + "ep_adminpads2_last-edited": "Ахыр тюзетиу", + "ep_adminpads2_loading": "Джюклениу…", + "ep_adminpads2_manage-pads": "Блокнотланы оноуун эт", + "ep_adminpads2_no-results": "Эсебле джокъдула", + "ep_adminpads2_pad-user-count": "Блокнот хайырланыучуланы саны", + "ep_adminpads2_padname": "Блокнот ат", + "ep_adminpads2_search-box.placeholder": "Терминни изле", + "ep_adminpads2_search-button.value": "Изле", + "ep_adminpads2_search-done": "Излеу тамамланды", + "ep_adminpads2_search-error-explanation": "Сервер, блокнотланы излеген заманда халат табды:", + "ep_adminpads2_search-error-title": "Блокнот тизмеси алынамады", + "ep_adminpads2_search-heading": "Блокнотла ючюн излеу", + "ep_adminpads2_title": "Блокнот башчылыкъ", + "ep_adminpads2_unknown-error": "Билинмеген халат", + "ep_adminpads2_unknown-status": "Билинмеген турум" +} diff --git a/admin/public/ep_admin_pads/lb.json b/admin/public/ep_admin_pads/lb.json new file mode 100644 index 000000000..61aa2588d --- /dev/null +++ b/admin/public/ep_admin_pads/lb.json @@ -0,0 +1,16 @@ +{ + "@metadata": { + "authors": [ + "Robby", + "Volvox" + ] + }, + "ep_adminpads2_confirm": "Wëllt Dir de Pad {{padID}} wierklech läschen?", + "ep_adminpads2_delete.value": "Läschen", + "ep_adminpads2_loading": "Lueden...", + "ep_adminpads2_no-results": "Keng Resultater", + "ep_adminpads2_padname": "Padnumm", + "ep_adminpads2_search-box.placeholder": "Sichbegrëff", + "ep_adminpads2_search-button.value": "Sichen", + "ep_adminpads2_unknown-error": "Onbekannte Feeler" +} diff --git a/admin/public/ep_admin_pads/lt.json b/admin/public/ep_admin_pads/lt.json new file mode 100644 index 000000000..59b2a13b3 --- /dev/null +++ b/admin/public/ep_admin_pads/lt.json @@ -0,0 +1,27 @@ +{ + "@metadata": { + "authors": [ + "Nokeoo" + ] + }, + "ep_adminpads2_action": "Veiksmas", + "ep_adminpads2_autoupdate-label": "Automatinis bloknoto keitimų naujinimas", + "ep_adminpads2_autoupdate.title": "Įjungia arba išjungia automatinius dabartinės užklausos atnaujinimus.", + "ep_adminpads2_confirm": "Ar tikrai norite ištrinti bloknotą {{padID}}?", + "ep_adminpads2_delete.value": "Ištrinti", + "ep_adminpads2_last-edited": "Paskutinis pakeitimas", + "ep_adminpads2_loading": "Įkeliama…", + "ep_adminpads2_manage-pads": "Tvarkyti bloknotą", + "ep_adminpads2_no-results": "Nėra rezultatų", + "ep_adminpads2_pad-user-count": "Bloknoto naudotojų skaičius", + "ep_adminpads2_padname": "Bloknoto pavadinimas", + "ep_adminpads2_search-box.placeholder": "Paieškos terminas", + "ep_adminpads2_search-button.value": "Paieška", + "ep_adminpads2_search-done": "Paieška baigta", + "ep_adminpads2_search-error-explanation": "Serveris susidūrė su klaida ieškant bloknotų:", + "ep_adminpads2_search-error-title": "Nepavyko gauti bloknotų sąrašo", + "ep_adminpads2_search-heading": "Ieškokite bloknotų", + "ep_adminpads2_title": "Bloknotų administravimas", + "ep_adminpads2_unknown-error": "Nežinoma klaida", + "ep_adminpads2_unknown-status": "Nežinoma būsena" +} diff --git a/admin/public/ep_admin_pads/mk.json b/admin/public/ep_admin_pads/mk.json new file mode 100644 index 000000000..72affd86c --- /dev/null +++ b/admin/public/ep_admin_pads/mk.json @@ -0,0 +1,27 @@ +{ + "@metadata": { + "authors": [ + "Bjankuloski06" + ] + }, + "ep_adminpads2_action": "Дејство", + "ep_adminpads2_autoupdate-label": "Самоподнова при измени во тетратката", + "ep_adminpads2_autoupdate.title": "Овозможува или оневозможува самоподнова на тековното барање.", + "ep_adminpads2_confirm": "Дали навистина сакате да ја избришете тетратката {{padID}}?", + "ep_adminpads2_delete.value": "Избриши", + "ep_adminpads2_last-edited": "Последно уредување", + "ep_adminpads2_loading": "Вчитувам…", + "ep_adminpads2_manage-pads": "Раководење со тетратки", + "ep_adminpads2_no-results": "Нема исход", + "ep_adminpads2_pad-user-count": "Корисници на тетратката", + "ep_adminpads2_padname": "Назив на тетратката", + "ep_adminpads2_search-box.placeholder": "Пребаран поим", + "ep_adminpads2_search-button.value": "Пребарај", + "ep_adminpads2_search-done": "Пребарувањето заврши", + "ep_adminpads2_search-error-explanation": "Опслужувачот наиде на грешка при пребарувањето на тетратки:", + "ep_adminpads2_search-error-title": "Не можев да го добијам списокот на тетратки", + "ep_adminpads2_search-heading": "Пребарај по тетратките", + "ep_adminpads2_title": "Администрација на тетратки", + "ep_adminpads2_unknown-error": "Непозната грешка", + "ep_adminpads2_unknown-status": "Непозната состојба" +} diff --git a/admin/public/ep_admin_pads/my.json b/admin/public/ep_admin_pads/my.json new file mode 100644 index 000000000..6b94ba702 --- /dev/null +++ b/admin/public/ep_admin_pads/my.json @@ -0,0 +1,27 @@ +{ + "@metadata": { + "authors": [ + "Andibecker" + ] + }, + "ep_adminpads2_action": "လုပ်ဆောင်ချက်", + "ep_adminpads2_autoupdate-label": "pad အပြောင်းအလဲများတွင်အလိုအလျောက်အပ်ဒိတ်လုပ်ပါ", + "ep_adminpads2_autoupdate.title": "လက်ရှိမေးမြန်းမှုအတွက်အလိုအလျောက်အပ်ဒိတ်များကိုဖွင့်ပါသို့မဟုတ်ပိတ်ပါ။", + "ep_adminpads2_confirm": "pad {{padID}} ကိုသင်တကယ်ဖျက်ချင်လား။", + "ep_adminpads2_delete.value": "ဖျက်ပါ", + "ep_adminpads2_last-edited": "နောက်ဆုံးတည်းဖြတ်သည်", + "ep_adminpads2_loading": "ဖွင့်နေသည်…", + "ep_adminpads2_manage-pads": "pads များကိုစီမံပါ", + "ep_adminpads2_no-results": "ရလဒ်မရှိပါ", + "ep_adminpads2_pad-user-count": "Pad အသုံးပြုသူအရေအတွက်", + "ep_adminpads2_padname": "Padname", + "ep_adminpads2_search-box.placeholder": "ဝေါဟာရရှာဖွေပါ", + "ep_adminpads2_search-button.value": "ရှာဖွေပါ", + "ep_adminpads2_search-done": "ရှာဖွေမှုပြီးပါပြီ", + "ep_adminpads2_search-error-explanation": "pads များကိုရှာဖွေစဉ်ဆာဗာသည်အမှားတစ်ခုကြုံခဲ့သည်။", + "ep_adminpads2_search-error-title": "pad စာရင်းရယူရန်မအောင်မြင်ပါ", + "ep_adminpads2_search-heading": "pads များကိုရှာဖွေပါ", + "ep_adminpads2_title": "Pad စီမံခန့်ခွဲမှု", + "ep_adminpads2_unknown-error": "အမည်မသိအမှား", + "ep_adminpads2_unknown-status": "အခြေအနေမသိ" +} diff --git a/admin/public/ep_admin_pads/nb.json b/admin/public/ep_admin_pads/nb.json new file mode 100644 index 000000000..acd194397 --- /dev/null +++ b/admin/public/ep_admin_pads/nb.json @@ -0,0 +1,13 @@ +{ + "@metadata": { + "authors": [ + "EdoAug" + ] + }, + "ep_adminpads2_action": "Handling", + "ep_adminpads2_last-edited": "Sist redigert", + "ep_adminpads2_loading": "Laster …", + "ep_adminpads2_no-results": "Ingen resultater", + "ep_adminpads2_search-button.value": "Søk", + "ep_adminpads2_search-done": "Søk fullført" +} diff --git a/admin/public/ep_admin_pads/nl.json b/admin/public/ep_admin_pads/nl.json new file mode 100644 index 000000000..f4d97b351 --- /dev/null +++ b/admin/public/ep_admin_pads/nl.json @@ -0,0 +1,29 @@ +{ + "@metadata": { + "authors": [ + "Aranka", + "McDutchie", + "Spinster" + ] + }, + "ep_adminpads2_action": "Handeling", + "ep_adminpads2_autoupdate-label": "Automatisch bijwerken bij aanpassingen aan de pad", + "ep_adminpads2_autoupdate.title": "Schakelt automatische updates voor de huidige query in of uit.", + "ep_adminpads2_confirm": "Wil je de pad {{padID}} echt verwijderen?", + "ep_adminpads2_delete.value": "Verwijderen", + "ep_adminpads2_last-edited": "Laatst bewerkt", + "ep_adminpads2_loading": "Bezig met laden...", + "ep_adminpads2_manage-pads": "Pads beheren", + "ep_adminpads2_no-results": "Geen resultaten", + "ep_adminpads2_pad-user-count": "Aantal gebruikers van de pad", + "ep_adminpads2_padname": "Naam van de pad", + "ep_adminpads2_search-box.placeholder": "Zoekterm", + "ep_adminpads2_search-button.value": "Zoeken", + "ep_adminpads2_search-done": "Zoekopdracht voltooid", + "ep_adminpads2_search-error-explanation": "De server heeft een fout aangetroffen tijdens het zoeken naar pads:", + "ep_adminpads2_search-error-title": "Kan lijst met pads niet ophalen", + "ep_adminpads2_search-heading": "Pads zoeken", + "ep_adminpads2_title": "Administratie van pad", + "ep_adminpads2_unknown-error": "Onbekende fout", + "ep_adminpads2_unknown-status": "Onbekende status" +} diff --git a/admin/public/ep_admin_pads/oc.json b/admin/public/ep_admin_pads/oc.json new file mode 100644 index 000000000..ae0169faf --- /dev/null +++ b/admin/public/ep_admin_pads/oc.json @@ -0,0 +1,21 @@ +{ + "@metadata": { + "authors": [ + "Quentí" + ] + }, + "ep_adminpads2_action": "Accion", + "ep_adminpads2_delete.value": "Suprimir", + "ep_adminpads2_last-edited": "Darrièra edicion", + "ep_adminpads2_loading": "Cargament…", + "ep_adminpads2_manage-pads": "Gerir los pads", + "ep_adminpads2_no-results": "Pas cap de resultat", + "ep_adminpads2_padname": "Nom del pad", + "ep_adminpads2_search-box.placeholder": "Tèrme de recèrca", + "ep_adminpads2_search-button.value": "Recercar", + "ep_adminpads2_search-done": "Recèrca acabada", + "ep_adminpads2_search-heading": "Cercar de pads", + "ep_adminpads2_title": "Administracion de pad", + "ep_adminpads2_unknown-error": "Error desconeguda", + "ep_adminpads2_unknown-status": "Estat desconegut" +} diff --git a/admin/public/ep_admin_pads/pms.json b/admin/public/ep_admin_pads/pms.json new file mode 100644 index 000000000..ac0542b85 --- /dev/null +++ b/admin/public/ep_admin_pads/pms.json @@ -0,0 +1,27 @@ +{ + "@metadata": { + "authors": [ + "Borichèt" + ] + }, + "ep_adminpads2_action": "Assion", + "ep_adminpads2_autoupdate-label": "Agiornament automàtich an sle modìfiche ëd plancia", + "ep_adminpads2_autoupdate.title": "Abilité o disabilité j'agiornament automàtich për l'arcesta atual.", + "ep_adminpads2_confirm": "Veul-lo për da bon dëscancelé la plancia {{padID}}?", + "ep_adminpads2_delete.value": "Dëscancelé", + "ep_adminpads2_last-edited": "Modificà l'ùltima vira", + "ep_adminpads2_loading": "Cariament…", + "ep_adminpads2_manage-pads": "Gestì le plance", + "ep_adminpads2_no-results": "Gnun arzultà", + "ep_adminpads2_pad-user-count": "Conteur ëd plancia dl'utent", + "ep_adminpads2_padname": "Nòm ëd plancia", + "ep_adminpads2_search-box.placeholder": "Tèrmin d'arserca", + "ep_adminpads2_search-button.value": "Arserca", + "ep_adminpads2_search-done": "Arserca completà", + "ep_adminpads2_search-error-explanation": "Ël servent a l'ha rancontrà n'eror an sërcand dle plance:", + "ep_adminpads2_search-error-title": "Falì a oten-e la lista ëd plance", + "ep_adminpads2_search-heading": "Arserca ëd plance", + "ep_adminpads2_title": "Aministrassion ëd plance", + "ep_adminpads2_unknown-error": "Eror nen conossù", + "ep_adminpads2_unknown-status": "Statù nen conossù" +} diff --git a/admin/public/ep_admin_pads/pt-br.json b/admin/public/ep_admin_pads/pt-br.json new file mode 100644 index 000000000..28a7874ee --- /dev/null +++ b/admin/public/ep_admin_pads/pt-br.json @@ -0,0 +1,30 @@ +{ + "@metadata": { + "authors": [ + "Duke of Wikipädia", + "Eduardo Addad de Oliveira", + "Eduardoaddad", + "YuriNikolai" + ] + }, + "ep_adminpads2_action": "Ação", + "ep_adminpads2_autoupdate-label": "Atualizar notas automaticamente", + "ep_adminpads2_autoupdate.title": "Habilita ou desabilita atualizações automáticas para a consulta atual.", + "ep_adminpads2_confirm": "Você realmente deseja excluir a nota {{padID}}?", + "ep_adminpads2_delete.value": "Excluir", + "ep_adminpads2_last-edited": "Última edição", + "ep_adminpads2_loading": "Carregando…", + "ep_adminpads2_manage-pads": "Gerenciar notas", + "ep_adminpads2_no-results": "Sem resultados", + "ep_adminpads2_pad-user-count": "Número de utilizadores na nota", + "ep_adminpads2_padname": "Nome da nota", + "ep_adminpads2_search-box.placeholder": "Termo de pesquisa", + "ep_adminpads2_search-button.value": "Pesquisar", + "ep_adminpads2_search-done": "Busca completa", + "ep_adminpads2_search-error-explanation": "O servidor encontrou um erro enquanto procurava por notas:", + "ep_adminpads2_search-error-title": "Falha ao buscar lista de notas", + "ep_adminpads2_search-heading": "Pesquisar por notas", + "ep_adminpads2_title": "Administração de notas", + "ep_adminpads2_unknown-error": "Erro desconhecido", + "ep_adminpads2_unknown-status": "Status desconhecido" +} diff --git a/admin/public/ep_admin_pads/pt.json b/admin/public/ep_admin_pads/pt.json new file mode 100644 index 000000000..b7abf2f3f --- /dev/null +++ b/admin/public/ep_admin_pads/pt.json @@ -0,0 +1,27 @@ +{ + "@metadata": { + "authors": [ + "Guilha" + ] + }, + "ep_adminpads2_action": "Ação", + "ep_adminpads2_autoupdate-label": "Atualizar automaticamente as notas", + "ep_adminpads2_autoupdate.title": "Ativa ou desativa atualizações automáticas na consulta atual.", + "ep_adminpads2_confirm": "Tencionas mesmo eliminar a nota {{padID}}?", + "ep_adminpads2_delete.value": "Eliminar", + "ep_adminpads2_last-edited": "Última edição", + "ep_adminpads2_loading": "A carregar...", + "ep_adminpads2_manage-pads": "Gerir notas", + "ep_adminpads2_no-results": "Sem resultados", + "ep_adminpads2_pad-user-count": "Número de utilizadores na nota", + "ep_adminpads2_padname": "Nome da nota", + "ep_adminpads2_search-box.placeholder": "Procurar termo", + "ep_adminpads2_search-button.value": "Procurar", + "ep_adminpads2_search-done": "Procura completa", + "ep_adminpads2_search-error-explanation": "O servidor encontrou um erro enquanto procurava por notas:", + "ep_adminpads2_search-error-title": "Falha ao obter lista de notas", + "ep_adminpads2_search-heading": "Procurar por notas", + "ep_adminpads2_title": "Administração da nota", + "ep_adminpads2_unknown-error": "Erro desconhecido", + "ep_adminpads2_unknown-status": "Estado desconhecido" +} diff --git a/admin/public/ep_admin_pads/qqq.json b/admin/public/ep_admin_pads/qqq.json new file mode 100644 index 000000000..de36e2ae6 --- /dev/null +++ b/admin/public/ep_admin_pads/qqq.json @@ -0,0 +1,10 @@ +{ + "@metadata": { + "authors": [ + "BryanDavis" + ] + }, + "ep_adminpads2_action": "{{Identical|Action}}", + "ep_adminpads2_delete.value": "{{Identical|Delete}}", + "ep_adminpads2_search-button.value": "{{Identical|Search}}" +} diff --git a/admin/public/ep_admin_pads/ru.json b/admin/public/ep_admin_pads/ru.json new file mode 100644 index 000000000..6d0d163d0 --- /dev/null +++ b/admin/public/ep_admin_pads/ru.json @@ -0,0 +1,31 @@ +{ + "@metadata": { + "authors": [ + "DDPAT", + "Ice bulldog", + "Megakott", + "Okras", + "Pacha Tchernof" + ] + }, + "ep_adminpads2_action": "Действие", + "ep_adminpads2_autoupdate-label": "Автообновление при изменении документа", + "ep_adminpads2_autoupdate.title": "Включает или отключает автоматические обновления для текущего запроса.", + "ep_adminpads2_confirm": "Вы действительно хотите удалить документ {{padID}}?", + "ep_adminpads2_delete.value": "Удалить", + "ep_adminpads2_last-edited": "Последнее изменение", + "ep_adminpads2_loading": "Загружается…", + "ep_adminpads2_manage-pads": "Управление документами", + "ep_adminpads2_no-results": "Нет результатов", + "ep_adminpads2_pad-user-count": "Количество пользователей документа", + "ep_adminpads2_padname": "Название документа", + "ep_adminpads2_search-box.placeholder": "Искать термин", + "ep_adminpads2_search-button.value": "Найти", + "ep_adminpads2_search-done": "Поиск завершён", + "ep_adminpads2_search-error-explanation": "Сервер обнаружил ошибку при поиске документов:", + "ep_adminpads2_search-error-title": "Не удалось получить список документов", + "ep_adminpads2_search-heading": "Поиск документов", + "ep_adminpads2_title": "Администрирование документов", + "ep_adminpads2_unknown-error": "Неизвестная ошибка", + "ep_adminpads2_unknown-status": "Неизвестный статус" +} diff --git a/admin/public/ep_admin_pads/sc.json b/admin/public/ep_admin_pads/sc.json new file mode 100644 index 000000000..a37bba5a2 --- /dev/null +++ b/admin/public/ep_admin_pads/sc.json @@ -0,0 +1,27 @@ +{ + "@metadata": { + "authors": [ + "Adr mm" + ] + }, + "ep_adminpads2_action": "Atzione", + "ep_adminpads2_autoupdate-label": "Atualizatzione automàtica de is modìficas de su pad", + "ep_adminpads2_autoupdate.title": "Ativat o disativat is atualizatziones automàticas pro sa chirca atuale.", + "ep_adminpads2_confirm": "Seguru chi boles cantzellare su pad {{padID}}?", + "ep_adminpads2_delete.value": "Cantzella", + "ep_adminpads2_last-edited": "Ùrtima modìfica", + "ep_adminpads2_loading": "Carrighende...", + "ep_adminpads2_manage-pads": "Gesti is pads", + "ep_adminpads2_no-results": "Nissunu resurtadu", + "ep_adminpads2_pad-user-count": "Nùmeru de utentes de pads", + "ep_adminpads2_padname": "Nòmine de su pad", + "ep_adminpads2_search-box.placeholder": "Tèrmine de chirca", + "ep_adminpads2_search-button.value": "Chirca", + "ep_adminpads2_search-done": "Chirca cumpleta", + "ep_adminpads2_search-error-explanation": "Su serbidore at agatadu un'errore chirchende pads:", + "ep_adminpads2_search-error-title": "Impossìbile otènnere sa lista de pads", + "ep_adminpads2_search-heading": "Chirca pads", + "ep_adminpads2_title": "Amministratzione de su pad", + "ep_adminpads2_unknown-error": "Errore disconnotu", + "ep_adminpads2_unknown-status": "Istadu disconnotu" +} diff --git a/admin/public/ep_admin_pads/sdc.json b/admin/public/ep_admin_pads/sdc.json new file mode 100644 index 000000000..c4672fd7f --- /dev/null +++ b/admin/public/ep_admin_pads/sdc.json @@ -0,0 +1,14 @@ +{ + "@metadata": { + "authors": [ + "F Samaritani" + ] + }, + "ep_adminpads2_action": "Azioni", + "ep_adminpads2_delete.value": "Canzella", + "ep_adminpads2_loading": "carrigghendi...", + "ep_adminpads2_no-results": "Nisciun risulthaddu", + "ep_adminpads2_search-button.value": "Zercha", + "ep_adminpads2_search-heading": "Zirchà dati", + "ep_adminpads2_unknown-error": "Errori ischunisciddu" +} diff --git a/admin/public/ep_admin_pads/sk.json b/admin/public/ep_admin_pads/sk.json new file mode 100644 index 000000000..ab0392d4e --- /dev/null +++ b/admin/public/ep_admin_pads/sk.json @@ -0,0 +1,27 @@ +{ + "@metadata": { + "authors": [ + "Yardom78" + ] + }, + "ep_adminpads2_action": "Akcia", + "ep_adminpads2_autoupdate-label": "Automatická aktualizácia zmien na poznámkovom bloku", + "ep_adminpads2_autoupdate.title": "Zapne alebo vypne automatickú aktualizáciu.", + "ep_adminpads2_confirm": "Skutočne chcete vymazať poznámkový blok {{padID}}?", + "ep_adminpads2_delete.value": "Vymazať", + "ep_adminpads2_last-edited": "Posledná úprava", + "ep_adminpads2_loading": "Načítavanie...", + "ep_adminpads2_manage-pads": "Spravovať poznámkové bloky", + "ep_adminpads2_no-results": "Žiadne výsledky", + "ep_adminpads2_pad-user-count": "Počet používateľov poznámkového bloku", + "ep_adminpads2_padname": "Názov poznámkového bloku", + "ep_adminpads2_search-box.placeholder": "Hľadať výraz", + "ep_adminpads2_search-button.value": "Hľadať", + "ep_adminpads2_search-done": "Hľadanie dokončené", + "ep_adminpads2_search-error-explanation": "Pri hľadaní poznámkového bloku došlo k chybe:", + "ep_adminpads2_search-error-title": "Nepodarilo sa získať zoznam poznámkových blokov", + "ep_adminpads2_search-heading": "Hľadať poznámkový blok", + "ep_adminpads2_title": "Správa poznámkového bloku", + "ep_adminpads2_unknown-error": "Neznáma chyba", + "ep_adminpads2_unknown-status": "Neznámy stav" +} diff --git a/admin/public/ep_admin_pads/skr-arab.json b/admin/public/ep_admin_pads/skr-arab.json new file mode 100644 index 000000000..08162f849 --- /dev/null +++ b/admin/public/ep_admin_pads/skr-arab.json @@ -0,0 +1,20 @@ +{ + "@metadata": { + "authors": [ + "Saraiki" + ] + }, + "ep_adminpads2_action": "عمل", + "ep_adminpads2_delete.value": "مٹاؤ", + "ep_adminpads2_last-edited": "چھیکڑی تبدیلی", + "ep_adminpads2_loading": "لوڈ تھین٘دا پئے۔۔۔", + "ep_adminpads2_manage-pads": "پیڈ منیج کرو", + "ep_adminpads2_no-results": "کوئی نتیجہ کائنی", + "ep_adminpads2_padname": "پیڈ ناں", + "ep_adminpads2_search-box.placeholder": "ٹرم ڳولو", + "ep_adminpads2_search-button.value": "ڳولو", + "ep_adminpads2_search-done": "ڳولݨ پورا تھیا", + "ep_adminpads2_search-heading": "پیڈاں دی ڳول", + "ep_adminpads2_unknown-error": "نامعلوم غلطی", + "ep_adminpads2_unknown-status": "نامعلوم حالت" +} diff --git a/admin/public/ep_admin_pads/sl.json b/admin/public/ep_admin_pads/sl.json new file mode 100644 index 000000000..3bebe1972 --- /dev/null +++ b/admin/public/ep_admin_pads/sl.json @@ -0,0 +1,28 @@ +{ + "@metadata": { + "authors": [ + "Eleassar", + "HairyFotr" + ] + }, + "ep_adminpads2_action": "Dejanje", + "ep_adminpads2_autoupdate-label": "Samodejno posodabljanje ob spremembah blokcev", + "ep_adminpads2_autoupdate.title": "Omogoči ali onemogoči samodejne posodobitve za trenutno poizvedbo.", + "ep_adminpads2_confirm": "Ali res želite izbrisati blokec {{padID}}?", + "ep_adminpads2_delete.value": "Izbriši", + "ep_adminpads2_last-edited": "Zadnje urejanje", + "ep_adminpads2_loading": "Nalaganje ...", + "ep_adminpads2_manage-pads": "Upravljanje blokcev", + "ep_adminpads2_no-results": "Ni zadetkov", + "ep_adminpads2_pad-user-count": "Število urejevalcev blokca", + "ep_adminpads2_padname": "Ime blokca", + "ep_adminpads2_search-box.placeholder": "Iskalni izraz", + "ep_adminpads2_search-button.value": "Išči", + "ep_adminpads2_search-done": "Iskanje končano", + "ep_adminpads2_search-error-explanation": "Strežnik je med iskanjem blokcev naletel na napako:", + "ep_adminpads2_search-error-title": "Ni bilo mogoče pridobiti seznama blokcev", + "ep_adminpads2_search-heading": "Iskanje blokcev", + "ep_adminpads2_title": "Upravljanje blokcev", + "ep_adminpads2_unknown-error": "Neznana napaka", + "ep_adminpads2_unknown-status": "Neznano stanje" +} diff --git a/admin/public/ep_admin_pads/smn.json b/admin/public/ep_admin_pads/smn.json new file mode 100644 index 000000000..9d57cc73c --- /dev/null +++ b/admin/public/ep_admin_pads/smn.json @@ -0,0 +1,13 @@ +{ + "@metadata": { + "authors": [ + "Yupik" + ] + }, + "ep_adminpads2_delete.value": "Siho", + "ep_adminpads2_last-edited": "Majemustáá nubástittum", + "ep_adminpads2_search-box.placeholder": "Uuccâmsääni", + "ep_adminpads2_search-button.value": "Uusâ", + "ep_adminpads2_unknown-error": "Tubdâmettum feilâ", + "ep_adminpads2_unknown-status": "Tubdâmettum tile" +} diff --git a/admin/public/ep_admin_pads/sms.json b/admin/public/ep_admin_pads/sms.json new file mode 100644 index 000000000..8d3cf5797 --- /dev/null +++ b/admin/public/ep_admin_pads/sms.json @@ -0,0 +1,16 @@ +{ + "@metadata": { + "authors": [ + "Yupik" + ] + }, + "ep_adminpads2_delete.value": "Jaukkâd", + "ep_adminpads2_last-edited": "Mââimõssân muttum", + "ep_adminpads2_no-results": "Ij käunnʼjam ni mii", + "ep_adminpads2_padname": "Mošttʼtõspõʹmmai nõmm", + "ep_adminpads2_search-box.placeholder": "Ooccâmsääʹnn", + "ep_adminpads2_search-button.value": "Ooʒʒ", + "ep_adminpads2_search-heading": "Ooʒʒ mošttʼtõspõʹmmjid", + "ep_adminpads2_unknown-error": "Toobdteʹmes vââʹǩǩ", + "ep_adminpads2_unknown-status": "Toobdteʹmes status" +} diff --git a/admin/public/ep_admin_pads/sq.json b/admin/public/ep_admin_pads/sq.json new file mode 100644 index 000000000..cc4740763 --- /dev/null +++ b/admin/public/ep_admin_pads/sq.json @@ -0,0 +1,27 @@ +{ + "@metadata": { + "authors": [ + "Besnik b" + ] + }, + "ep_adminpads2_action": "Veprim", + "ep_adminpads2_autoupdate-label": "Vetëpërditësohu, kur nga ndryshime blloku", + "ep_adminpads2_autoupdate.title": "Aktivizon ose çaktivizon përditësim të automatizuara për kërkesën e tanishme.", + "ep_adminpads2_confirm": "Doni vërtet të fshihet blloku {{padID}}?", + "ep_adminpads2_delete.value": "Fshije", + "ep_adminpads2_last-edited": "Përpunuar së fundi më", + "ep_adminpads2_loading": "Po ngarkohet…", + "ep_adminpads2_manage-pads": "Administroni blloqe", + "ep_adminpads2_no-results": "S’ka përfundime", + "ep_adminpads2_pad-user-count": "Numër përdoruesish blloku", + "ep_adminpads2_padname": "Emër blloku", + "ep_adminpads2_search-box.placeholder": "Term kërkimi", + "ep_adminpads2_search-button.value": "Kërko", + "ep_adminpads2_search-done": "Kërkim i plotë", + "ep_adminpads2_search-error-explanation": "Shërbyesi hasi një gabim teksa kërkohej për blloqe:", + "ep_adminpads2_search-error-title": "S’u arrit të merrej listë blloqesh", + "ep_adminpads2_search-heading": "Kërkoni për blloqe", + "ep_adminpads2_title": "Administrim blloku", + "ep_adminpads2_unknown-error": "Gabim i panjohur", + "ep_adminpads2_unknown-status": "Gjendje e panjohur" +} diff --git a/admin/public/ep_admin_pads/sv.json b/admin/public/ep_admin_pads/sv.json new file mode 100644 index 000000000..e77aaf2c4 --- /dev/null +++ b/admin/public/ep_admin_pads/sv.json @@ -0,0 +1,28 @@ +{ + "@metadata": { + "authors": [ + "Bengtsson96", + "WikiPhoenix" + ] + }, + "ep_adminpads2_action": "Åtgärd", + "ep_adminpads2_autoupdate-label": "Uppdatera automatiskt när blocket ändras", + "ep_adminpads2_autoupdate.title": "Aktivera eller inaktivera automatiska uppdatering för nuvarande förfrågan.", + "ep_adminpads2_confirm": "Vill du verkligen radera blocket {{padID}}?", + "ep_adminpads2_delete.value": "Radera", + "ep_adminpads2_last-edited": "Senast redigerad", + "ep_adminpads2_loading": "Läser in …", + "ep_adminpads2_manage-pads": "Hantera block", + "ep_adminpads2_no-results": "Inga resultat", + "ep_adminpads2_pad-user-count": "Antal blockanvändare", + "ep_adminpads2_padname": "Blocknamn", + "ep_adminpads2_search-box.placeholder": "Sökord", + "ep_adminpads2_search-button.value": "Sök", + "ep_adminpads2_search-done": "Sökning slutförd", + "ep_adminpads2_search-error-explanation": "Servern stötte på ett fel vid sökning efter block:", + "ep_adminpads2_search-error-title": "Misslyckades att hämta blocklista", + "ep_adminpads2_search-heading": "Sök efter block", + "ep_adminpads2_title": "Blockadministration", + "ep_adminpads2_unknown-error": "Okänt fel", + "ep_adminpads2_unknown-status": "Okänd status" +} diff --git a/admin/public/ep_admin_pads/sw.json b/admin/public/ep_admin_pads/sw.json new file mode 100644 index 000000000..f1beeecbb --- /dev/null +++ b/admin/public/ep_admin_pads/sw.json @@ -0,0 +1,27 @@ +{ + "@metadata": { + "authors": [ + "Andibecker" + ] + }, + "ep_adminpads2_action": "Hatua", + "ep_adminpads2_autoupdate-label": "Sasisha kiotomatiki kwenye mabadiliko ya pedi", + "ep_adminpads2_autoupdate.title": "Huwasha au kulemaza sasisho otomatiki kwa hoja ya sasa.", + "ep_adminpads2_confirm": "Je! Kweli unataka kufuta pedi {{padID}}?", + "ep_adminpads2_delete.value": "Futa", + "ep_adminpads2_last-edited": "Ilihaririwa mwisho", + "ep_adminpads2_loading": "Inapakia...", + "ep_adminpads2_manage-pads": "Dhibiti pedi", + "ep_adminpads2_no-results": "Hakuna matokeo", + "ep_adminpads2_pad-user-count": "Hesabu ya mtumiaji wa pedi", + "ep_adminpads2_padname": "Jina la utani", + "ep_adminpads2_search-box.placeholder": "Neno la utaftaji", + "ep_adminpads2_search-button.value": "Tafuta", + "ep_adminpads2_search-done": "Utafutaji umekamilika", + "ep_adminpads2_search-error-explanation": "Seva ilipata hitilafu wakati wa kutafuta pedi:", + "ep_adminpads2_search-error-title": "Imeshindwa kupata orodha ya pedi", + "ep_adminpads2_search-heading": "Tafuta pedi", + "ep_adminpads2_title": "Usimamizi wa pedi", + "ep_adminpads2_unknown-error": "Hitilafu isiyojulikana", + "ep_adminpads2_unknown-status": "Hali isiyojulikana" +} diff --git a/admin/public/ep_admin_pads/th.json b/admin/public/ep_admin_pads/th.json new file mode 100644 index 000000000..693e3f797 --- /dev/null +++ b/admin/public/ep_admin_pads/th.json @@ -0,0 +1,27 @@ +{ + "@metadata": { + "authors": [ + "Andibecker" + ] + }, + "ep_adminpads2_action": "การกระทำ", + "ep_adminpads2_autoupdate-label": "อัปเดตอัตโนมัติเมื่อเปลี่ยนแผ่น", + "ep_adminpads2_autoupdate.title": "เปิดหรือปิดการอัปเดตอัตโนมัติสำหรับคิวรีปัจจุบัน", + "ep_adminpads2_confirm": "คุณต้องการลบแพด {{padID}} จริงหรือไม่", + "ep_adminpads2_delete.value": "ลบ", + "ep_adminpads2_last-edited": "แก้ไขล่าสุด", + "ep_adminpads2_loading": "กำลังโหลด…", + "ep_adminpads2_manage-pads": "จัดการแผ่นรอง", + "ep_adminpads2_no-results": "ไม่มีผลลัพธ์", + "ep_adminpads2_pad-user-count": "จำนวนผู้ใช้แพด", + "ep_adminpads2_padname": "นามแฝง", + "ep_adminpads2_search-box.placeholder": "คำที่ต้องการค้นหา", + "ep_adminpads2_search-button.value": "ค้นหา", + "ep_adminpads2_search-done": "ค้นหาเสร็จสมบูรณ์", + "ep_adminpads2_search-error-explanation": "เซิร์ฟเวอร์พบข้อผิดพลาดขณะค้นหาแผ่นอิเล็กโทรด:", + "ep_adminpads2_search-error-title": "ไม่สามารถรับรายการแผ่นรอง", + "ep_adminpads2_search-heading": "ค้นหาแผ่นรอง", + "ep_adminpads2_title": "การบริหารแผ่น", + "ep_adminpads2_unknown-error": "ข้อผิดพลาดที่ไม่รู้จัก", + "ep_adminpads2_unknown-status": "ไม่ทราบสถานะ" +} diff --git a/admin/public/ep_admin_pads/tl.json b/admin/public/ep_admin_pads/tl.json new file mode 100644 index 000000000..238e01236 --- /dev/null +++ b/admin/public/ep_admin_pads/tl.json @@ -0,0 +1,17 @@ +{ + "@metadata": { + "authors": [ + "Mrkczr" + ] + }, + "ep_adminpads2_action": "Kilos", + "ep_adminpads2_delete.value": "Burahin", + "ep_adminpads2_last-edited": "Huling binago", + "ep_adminpads2_loading": "Naglo-load...", + "ep_adminpads2_no-results": "Walang mga resulta", + "ep_adminpads2_search-box.placeholder": "Mga katagang hahanapin:", + "ep_adminpads2_search-button.value": "Hanapin", + "ep_adminpads2_search-done": "Natapos na ang paghahanap", + "ep_adminpads2_unknown-error": "Hindi nalalamang kamalian", + "ep_adminpads2_unknown-status": "Hindi alam na katayuan" +} diff --git a/admin/public/ep_admin_pads/tr.json b/admin/public/ep_admin_pads/tr.json new file mode 100644 index 000000000..7e2e9d402 --- /dev/null +++ b/admin/public/ep_admin_pads/tr.json @@ -0,0 +1,28 @@ +{ + "@metadata": { + "authors": [ + "Hedda", + "MuratTheTurkish" + ] + }, + "ep_adminpads2_action": "Eylem", + "ep_adminpads2_autoupdate-label": "Bloknot değişikliklerinde otomatik güncelleme", + "ep_adminpads2_autoupdate.title": "Mevcut sorgu için otomatik güncellemeleri etkinleştirir veya devre dışı bırakır.", + "ep_adminpads2_confirm": "{{padID}} bloknotunu gerçekten silmek istiyor musunuz?", + "ep_adminpads2_delete.value": "Sil", + "ep_adminpads2_last-edited": "Son düzenleme", + "ep_adminpads2_loading": "Yükleniyor...", + "ep_adminpads2_manage-pads": "Bloknotları yönet", + "ep_adminpads2_no-results": "Sonuç yok", + "ep_adminpads2_pad-user-count": "Bloknot kullanıcı sayısı", + "ep_adminpads2_padname": "Bloknot adı", + "ep_adminpads2_search-box.placeholder": "Terimi ara", + "ep_adminpads2_search-button.value": "Ara", + "ep_adminpads2_search-done": "Arama tamamlandı", + "ep_adminpads2_search-error-explanation": "Sunucu, bloknotları ararken bir hatayla karşılaştı:", + "ep_adminpads2_search-error-title": "Bloknot listesi alınamadı", + "ep_adminpads2_search-heading": "Bloknotları ara", + "ep_adminpads2_title": "Bloknot yönetimi", + "ep_adminpads2_unknown-error": "Bilinmeyen hata", + "ep_adminpads2_unknown-status": "Bilinmeyen durum" +} diff --git a/admin/public/ep_admin_pads/uk.json b/admin/public/ep_admin_pads/uk.json new file mode 100644 index 000000000..c5c95f722 --- /dev/null +++ b/admin/public/ep_admin_pads/uk.json @@ -0,0 +1,28 @@ +{ + "@metadata": { + "authors": [ + "DDPAT", + "Ice bulldog" + ] + }, + "ep_adminpads2_action": "Дія", + "ep_adminpads2_autoupdate-label": "Автоматичне оновлення при зміні майданчика", + "ep_adminpads2_autoupdate.title": "Вмикає або вимикає автоматичне оновлення поточного запиту.", + "ep_adminpads2_confirm": "Ви дійсно хочете видалити панель {{padID}}?", + "ep_adminpads2_delete.value": "Видалити", + "ep_adminpads2_last-edited": "Останнє редагування", + "ep_adminpads2_loading": "Завантаження…", + "ep_adminpads2_manage-pads": "Управління майданчиками", + "ep_adminpads2_no-results": "Немає результатів", + "ep_adminpads2_pad-user-count": "Кількість майданчиків користувача", + "ep_adminpads2_padname": "Назва майданчика", + "ep_adminpads2_search-box.placeholder": "Пошуковий термін", + "ep_adminpads2_search-button.value": "Пошук", + "ep_adminpads2_search-done": "Пошук завершено", + "ep_adminpads2_search-error-explanation": "Під час пошуку педів сервер виявив помилку:", + "ep_adminpads2_search-error-title": "Не вдалося отримати список панелей", + "ep_adminpads2_search-heading": "Пошук майданчиків", + "ep_adminpads2_title": "Введення майданчиків", + "ep_adminpads2_unknown-error": "Невідома помилка", + "ep_adminpads2_unknown-status": "Невідомий статус" +} diff --git a/admin/public/ep_admin_pads/zh-hans.json b/admin/public/ep_admin_pads/zh-hans.json new file mode 100644 index 000000000..cdf0d945f --- /dev/null +++ b/admin/public/ep_admin_pads/zh-hans.json @@ -0,0 +1,29 @@ +{ + "@metadata": { + "authors": [ + "GuoPC", + "Lakejason0", + "沈澄心" + ] + }, + "ep_adminpads2_action": "操作", + "ep_adminpads2_autoupdate-label": "在记事本更改时自动更新", + "ep_adminpads2_autoupdate.title": "启用或禁用目前查询的自动更新", + "ep_adminpads2_confirm": "您确定要删除记事本 {{padID}}?", + "ep_adminpads2_delete.value": "删除", + "ep_adminpads2_last-edited": "上次编辑于", + "ep_adminpads2_loading": "正在加载…", + "ep_adminpads2_manage-pads": "管理记事本", + "ep_adminpads2_no-results": "没有结果", + "ep_adminpads2_pad-user-count": "记事本用户数", + "ep_adminpads2_padname": "记事本名称", + "ep_adminpads2_search-box.placeholder": "搜索关键词", + "ep_adminpads2_search-button.value": "搜索", + "ep_adminpads2_search-done": "搜索完成", + "ep_adminpads2_search-error-explanation": "搜索记事本时服务器发生错误:", + "ep_adminpads2_search-error-title": "获取记事本列表失败", + "ep_adminpads2_search-heading": "搜索记事本", + "ep_adminpads2_title": "记事本管理", + "ep_adminpads2_unknown-error": "未知错误", + "ep_adminpads2_unknown-status": "未知状态" +} diff --git a/admin/public/ep_admin_pads/zh-hant.json b/admin/public/ep_admin_pads/zh-hant.json new file mode 100644 index 000000000..daeed55f5 --- /dev/null +++ b/admin/public/ep_admin_pads/zh-hant.json @@ -0,0 +1,28 @@ +{ + "@metadata": { + "authors": [ + "HellojoeAoPS", + "Kly" + ] + }, + "ep_adminpads2_action": "操作", + "ep_adminpads2_autoupdate-label": "在記事本更改時自動更新", + "ep_adminpads2_autoupdate.title": "啟用或停用目前查詢的自動更新。", + "ep_adminpads2_confirm": "您確定要刪除記事本 {{padID}}?", + "ep_adminpads2_delete.value": "刪除", + "ep_adminpads2_last-edited": "上一次編輯", + "ep_adminpads2_loading": "載入中…", + "ep_adminpads2_manage-pads": "管理記事本", + "ep_adminpads2_no-results": "沒有結果", + "ep_adminpads2_pad-user-count": "記事本使用者數", + "ep_adminpads2_padname": "記事本名稱", + "ep_adminpads2_search-box.placeholder": "搜尋關鍵字", + "ep_adminpads2_search-button.value": "搜尋", + "ep_adminpads2_search-done": "搜尋完成", + "ep_adminpads2_search-error-explanation": "當搜尋記事本時伺服器發生錯誤:", + "ep_adminpads2_search-error-title": "取得記事本清單失敗", + "ep_adminpads2_search-heading": "搜尋記事本", + "ep_adminpads2_title": "記事本管理", + "ep_adminpads2_unknown-error": "不明錯誤", + "ep_adminpads2_unknown-status": "不明狀態" +} diff --git a/admin/public/fond.jpg b/admin/public/fond.jpg new file mode 100644 index 000000000..81357c7bb Binary files /dev/null and b/admin/public/fond.jpg differ diff --git a/admin/src/App.css b/admin/src/App.css new file mode 100644 index 000000000..e69de29bb diff --git a/admin/src/App.tsx b/admin/src/App.tsx new file mode 100644 index 000000000..3db45f0e8 --- /dev/null +++ b/admin/src/App.tsx @@ -0,0 +1,104 @@ +import {useEffect} from 'react' +import './App.css' +import {connect} from 'socket.io-client' +import {isJSONClean} from './utils/utils.ts' +import {NavLink, Outlet, useNavigate} from "react-router-dom"; +import {useStore} from "./store/store.ts"; +import {LoadingScreen} from "./utils/LoadingScreen.tsx"; +import {Trans, useTranslation} from "react-i18next"; + +const WS_URL = import.meta.env.DEV? 'http://localhost:9001' : '' +export const App = ()=> { + const setSettings = useStore(state => state.setSettings); + const {t} = useTranslation() + const navigate = useNavigate() + + useEffect(() => { + fetch('/admin-auth/', { + method: 'POST' + }).then((value)=>{ + if(!value.ok){ + navigate('/login') + } + }).catch(()=>{ + navigate('/login') + }) + }, []); + + useEffect(() => { + document.title = t('admin.page-title') + + useStore.getState().setShowLoading(true); + const settingSocket = connect(`${WS_URL}/settings`, { + transports: ['websocket'], + }); + + const pluginsSocket = connect(`${WS_URL}/pluginfw/installer`, { + transports: ['websocket'], + }) + + pluginsSocket.on('connect', () => { + useStore.getState().setPluginsSocket(pluginsSocket); + }); + + + settingSocket.on('connect', () => { + useStore.getState().setSettingsSocket(settingSocket); + useStore.getState().setShowLoading(false) + settingSocket.emit('load'); + console.log('connected'); + }); + + settingSocket.on('disconnect', (reason) => { + // The settingSocket.io client will automatically try to reconnect for all reasons other than "io + // server disconnect". + useStore.getState().setShowLoading(true) + if (reason === 'io server disconnect') { + settingSocket.connect(); + } + }); + + settingSocket.on('settings', (settings) => { + /* Check whether the settings.json is authorized to be viewed */ + if (settings.results === 'NOT_ALLOWED') { + console.log('Not allowed to view settings.json') + return; + } + + /* Check to make sure the JSON is clean before proceeding */ + if (isJSONClean(settings.results)) { + setSettings(settings.results); + } else { + alert('Invalid JSON'); + } + useStore.getState().setShowLoading(false); + }); + + settingSocket.on('saveprogress', (status)=>{ + console.log(status) + }) + + return () => { + settingSocket.disconnect(); + pluginsSocket.disconnect() + } + }, []); + + return
+ +
+

Etherpad

+
    +
  • +
  • +
  • +
  • +
+
+
+ +
+
+} + +export default App diff --git a/admin/src/index.css b/admin/src/index.css new file mode 100644 index 000000000..e9683befa --- /dev/null +++ b/admin/src/index.css @@ -0,0 +1,489 @@ +:root { + --etherpad-color: #0f775b; +} + + + +html, body, #root { + box-sizing: border-box; + height: 100%; + +} + +*, *:before, *:after { + box-sizing: inherit; +} + +body { + margin: 0; + color: #333; + font: 14px helvetica, sans-serif; + background: #eee; +} + +div.menu { + height: 100%; + padding: 15px; + width: 220px; + border-right: 1px solid #ccc; + position: fixed; +} + +div.menu ul { + padding: 0; +} + +div.menu li { + list-style: none; + margin-left: 3px; + line-height: 3; + border-top: 1px solid #ccc; +} + +div.menu li:last-child { + border-bottom: 1px solid #ccc; +} + +div.innerwrapper { + padding: 15px; + padding-left: 265px; +} + +div.innerwrapper-err { + padding: 15px; + padding-left: 265px; + display: none; +} + +#wrapper { + background: none repeat scroll 0px 0px #FFFFFF; + box-shadow: 0px 1px 10px rgba(0, 0, 0, 0.2); + margin: auto; + max-width: 1150px; + min-height: 100%;/*always display a scrollbar*/ + +} + +h1 { + font-size: 29px; +} + +h2 { + font-size: 24px; +} + +.separator { + margin: 10px 0; + height: 1px; + background: #aaa; + background: -webkit-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff); + background: -moz-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff); + background: -ms-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff); + background: -o-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff); +} + +form { + margin-bottom: 0; +} + +#inner { + width: 300px; + margin: 0 auto; +} + +input { + font-weight: bold; + font-size: 15px; +} + + +.sort { + cursor: pointer; +} +.sort:after { + content: '▲▼' +} +.sort.up:after { + content:'▲' +} +.sort.down:after { + content:'▼' +} + +table { + border: 1px solid #ddd; + border-radius: 3px; + border-spacing: 0; + width: 100%; + margin: 20px 0; + position:relative; /* Allows us to position the loading indicator relative to the table */ +} + +table thead tr { + background: #eee; +} + +td, th { + padding: 5px; +} + +.template { + display: none; +} + +#installed-plugins td>div { + position: relative;/* Allows us to position the loading indicator relative to this row */ + display: inline-block; /*make this fill the whole cell*/ + width:100%; +} + +.messages { + height: 5em; +} +.messages * { + display: none; + text-align: center; +} +.messages .fetching { + display: block; +} + +.progress { + position: absolute; + top: 0; left: 0; bottom:0; right:0; + padding: auto; + + background: rgb(255,255,255); + display: none; +} + +#search-progress.progress { + padding-top: 20%; + background: rgba(255,255,255,0.3); +} + +.progress * { + display: block; + margin: 0 auto; + text-align: center; + color: #666; +} + +.settings { + outline: none; + width: 100%; + min-height: 80vh; + resize: none; +} + +#response { + display: inline; +} + +a:link, a:visited, a:hover, a:focus { + color: #333333; + text-decoration: none; +} + +a:focus, a:hover { + text-decoration: underline; +} + +.installed-results a:link, +.search-results a:link, +.installed-results a:visited, +.search-results a:visited, +.installed-results a:hover, +.search-results a:hover, +.installed-results a:focus, +.search-results a:focus { + text-decoration: underline; +} + +.installed-results a:focus, +.search-results a:focus, +.installed-results a:hover, +.search-results a:hover { + text-decoration: none; +} + +pre { + white-space: pre-wrap; + word-wrap: break-word; +} + +@media (max-width: 800px) { + div.innerwrapper { + padding: 0 15px 15px 15px; + } + + div.menu { + padding: 1px 15px 0 15px; + position: static; + height: auto; + border-right: none; + width: auto; + } + + table { + border: none; + } + + table, thead, tbody, td, tr { + display: block; + } + + thead tr { + display: none; + } + + tr { + border: 1px solid #ccc; + margin-bottom: 5px; + border-radius: 3px; + } + + td { + border: none; + border-bottom: 1px solid #eee; + position: relative; + padding-left: 50%; + white-space: normal; + text-align: left; + } + + td.name { + word-wrap: break-word; + } + + td:before { + position: absolute; + top: 6px; + left: 6px; + text-align: left; + padding-right: 10px; + white-space: nowrap; + font-weight: bold; + content: attr(data-label); + } + + td:last-child { + border-bottom: none; + } + + table input[type="button"] { + float: none; + } +} + + +.settings-button-bar { + margin-top: 10px; + display: flex; + gap: 10px; +} + +.login-background { + background-image: url("/fond.jpg"); + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + background-color: #f0f0f0; +} + + +.login-textinput { + width: 100%; + padding: 10px; + background-color: #fffacc; + border-radius: 5px; + border: 1px solid #ccc; + margin-bottom: 10px; +} + +.login-box { + width: 20%; + padding: 20px; + border-radius: 5px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + background-color: #fff; +} + +.login-inner-box{ + position: relative; + padding: 20px; +} + +.login-title { + color: var(--etherpad-color); + font-size: 2em; +} + +.login-button { + padding: 10px; + background-color: var(--etherpad-color); + color: white; + border: none; + border-radius: 5px; + cursor: pointer; + width: 100%; + height: 40px; +} + +.dialog-overlay { + position: fixed; + inset: 0; + background-color: white; + z-index: 100; +} + + +.dialog-confirm-overlay { + position: fixed; + inset: 0; + background-color: rgba(0, 0, 0, 0.5); + z-index: 100; +} + + +.dialog-confirm-content { + position: fixed; + top: 50%; + left: 50%; + background-color: white; + transform: translate(-50%, -50%); + padding: 20px; + z-index: 101; +} + + +.dialog-content { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + padding: 20px; + z-index: 101; +} + +.dialog-title { + color: var(--etherpad-color); + font-size: 2em; + margin-bottom: 20px; +} + + + +.ToastViewport { + position: fixed; + top: 10px; + right: 20px; + display: flex; + flex-direction: column; + gap: 10px; + width: 390px; + max-width: 100vw; + margin: 0; + list-style: none; + z-index: 2147483647; + outline: none; +} + +.ToastRootSuccess { + background-color: lawngreen; +} + +.ToastRootFailure { + background-color: red; +} + +.ToastRootFailure > .ToastTitle { + color: white; +} + +.ToastRoot { + border-radius: 20px; + box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px; + padding: 15px; + display: grid; + grid-template-areas: 'title action' 'description action'; + grid-template-columns: auto max-content; + column-gap: 15px; + align-items: center; +} +.ToastRoot[data-state='open'] { + animation: slideIn 150ms cubic-bezier(0.16, 1, 0.3, 1); +} +.ToastRoot[data-state='closed'] { + animation: hide 100ms ease-in; +} +.ToastRoot[data-swipe='move'] { + transform: translateX(var(--radix-toast-swipe-move-x)); +} +.ToastRoot[data-swipe='cancel'] { + transform: translateX(0); + transition: transform 200ms ease-out; +} +.ToastRoot[data-swipe='end'] { + animation: swipeOut 100ms ease-out; +} + +@keyframes hide { + from { + opacity: 1; + } + to { + opacity: 0; + } +} + +@keyframes slideIn { + from { + transform: translateX(calc(100% + var(--viewport-padding))); + } + to { + transform: translateX(0); + } +} + +@keyframes swipeOut { + from { + transform: translateX(var(--radix-toast-swipe-end-x)); + } + to { + transform: translateX(calc(100% + var(--viewport-padding))); + } +} + +.ToastTitle { + grid-area: title; + margin-bottom: 5px; + font-weight: 500; + color: var(--slate-12); + padding: 10px; + font-size: 15px; +} + +.ToastDescription { + grid-area: description; + margin: 0; + color: var(--slate-11); + font-size: 13px; + line-height: 1.3; +} + +.ToastAction { + grid-area: action; +} + +.help-block { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 20px +} + +.search-field { + width: 50%; + padding: 5px; +} diff --git a/admin/src/localization/i18n.ts b/admin/src/localization/i18n.ts new file mode 100644 index 000000000..67ae140e7 --- /dev/null +++ b/admin/src/localization/i18n.ts @@ -0,0 +1,57 @@ +import i18n from 'i18next' +import {initReactI18next} from "react-i18next"; +import LanguageDetector from 'i18next-browser-languagedetector' + + +import { BackendModule } from 'i18next'; + +const LazyImportPlugin: BackendModule = { + type: 'backend', + init: function () { + }, + read: async function (language, namespace, callback) { + + let baseURL = import.meta.env.BASE_URL + if(namespace === "translation") { + // If default we load the translation file + baseURL+=`/locales/${language}.json` + } else { + // Else we load the former plugin translation file + baseURL+=`/${namespace}/${language}.json` + } + + const localeJSON = await fetch(baseURL, { + cache: "force-cache" + }) + let json; + + try { + json = JSON.parse(await localeJSON.text()) + } catch(e) { + callback(new Error("Error loading"), null); + } + + + callback(null, json); + }, + + save: function () { + }, + + create: function () { + /* save the missing translation */ + }, +}; + +i18n + .use(LanguageDetector) + .use(LazyImportPlugin) + .use(initReactI18next) + .init( + { + ns: ['translation','ep_admin_pads'], + fallbackLng: 'en' + } + ) + +export default i18n diff --git a/admin/src/main.tsx b/admin/src/main.tsx new file mode 100644 index 000000000..03ec73104 --- /dev/null +++ b/admin/src/main.tsx @@ -0,0 +1,40 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App.tsx' +import './index.css' +import {createBrowserRouter, createRoutesFromElements, Route, RouterProvider} from "react-router-dom"; +import {HomePage} from "./pages/HomePage.tsx"; +import {SettingsPage} from "./pages/SettingsPage.tsx"; +import {LoginScreen} from "./pages/LoginScreen.tsx"; +import {HelpPage} from "./pages/HelpPage.tsx"; +import * as Toast from '@radix-ui/react-toast' +import {I18nextProvider} from "react-i18next"; +import i18n from "./localization/i18n.ts"; +import {PadPage} from "./pages/PadPage.tsx"; +import {ToastDialog} from "./utils/Toast.tsx"; + +const router = createBrowserRouter(createRoutesFromElements( + <>}> + }/> + }/> + }/> + }/> + }/> + + }/> + +), { + basename: import.meta.env.BASE_URL +}) + + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + + + + , +) diff --git a/admin/src/pages/HelpPage.tsx b/admin/src/pages/HelpPage.tsx new file mode 100644 index 000000000..6f06907e1 --- /dev/null +++ b/admin/src/pages/HelpPage.tsx @@ -0,0 +1,70 @@ +import {Trans} from "react-i18next"; +import {useStore} from "../store/store.ts"; +import {useEffect, useState} from "react"; +import {HelpObj} from "./Plugin.ts"; + +export const HelpPage = () => { + const settingsSocket = useStore(state=>state.settingsSocket) + const [helpData, setHelpData] = useState(); + + useEffect(() => { + if(!settingsSocket) return; + settingsSocket?.on('reply:help', (data) => { + setHelpData(data) + }); + + settingsSocket?.emit('help'); + }, [settingsSocket]); + + const renderHooks = (hooks:Record>) => { + return Object.keys(hooks).map((hookName, i) => { + return
+

{hookName}

+
    + {Object.keys(hooks[hookName]).map((hook, i) =>
  • {hook} +
      + {Object.keys(hooks[hookName][hook]).map((subHook, i) =>
    • {subHook}
    • )} +
    +
  • )} +
+
+ }) + } + + + if (!helpData) return
+ + return
+

+
+
+
{helpData?.epVersion}
+
+
{helpData.latestVersion}
+
Git sha
+
{helpData.gitCommit}
+
+

+
    + {helpData.installedPlugins.map((plugin, i) =>
  • {plugin}
  • )} +
+ +

+
    + {helpData.installedParts.map((part, i) =>
  • {part}
  • )} +
+ +

+ { + renderHooks(helpData.installedServerHooks) + } + +

+ + { + renderHooks(helpData.installedClientHooks) + } +

+ +
+} diff --git a/admin/src/pages/HomePage.tsx b/admin/src/pages/HomePage.tsx new file mode 100644 index 000000000..fd29e390e --- /dev/null +++ b/admin/src/pages/HomePage.tsx @@ -0,0 +1,179 @@ +import {useStore} from "../store/store.ts"; +import {useEffect, useState} from "react"; +import {InstalledPlugin, PluginDef, SearchParams} from "./Plugin.ts"; +import {useDebounce} from "../utils/useDebounce.ts"; +import {Trans, useTranslation} from "react-i18next"; + + +export const HomePage = () => { + const pluginsSocket = useStore(state=>state.pluginsSocket) + const [plugins,setPlugins] = useState([]) + const [installedPlugins, setInstalledPlugins] = useState([]) + const [searchParams, setSearchParams] = useState({ + offset: 0, + limit: 99999, + sortBy: 'name', + sortDir: 'asc', + searchTerm: '' + }) + const [searchTerm, setSearchTerm] = useState('') + const {t} = useTranslation() + + + useEffect(() => { + if(!pluginsSocket){ + return + } + + pluginsSocket.on('results:installed', (data:{ + installed: InstalledPlugin[] + })=>{ + setInstalledPlugins(data.installed) + }) + + pluginsSocket.on('results:updatable', (data) => { + data.updatable.forEach((pluginName: string) => { + setInstalledPlugins(installedPlugins.map(plugin => { + if (plugin.name === pluginName) { + return { + ...plugin, + updatable: true + } + } + return plugin + })) + }) + }) + + pluginsSocket.on('finished:install', () => { + pluginsSocket!.emit('getInstalled'); + }) + + pluginsSocket.on('finished:uninstall', () => { + console.log("Finished uninstall") + }) + + + // Reload on reconnect + pluginsSocket.on('connect', ()=>{ + // Initial retrieval of installed plugins + pluginsSocket.emit('getInstalled'); + pluginsSocket.emit('search', searchParams) + }) + + pluginsSocket.emit('getInstalled'); + + // check for updates every 5mins + const interval = setInterval(() => { + pluginsSocket.emit('checkUpdates'); + }, 1000 * 60 * 5); + + return ()=>{ + clearInterval(interval) + } + }, [pluginsSocket]); + + + useEffect(() => { + if (!pluginsSocket) { + return + } + + pluginsSocket?.emit('search', searchParams) + + + pluginsSocket!.on('results:search', (data: { + results: PluginDef[] + }) => { + setPlugins(data.results) + }) + + + }, [searchParams, pluginsSocket]); + + const uninstallPlugin = (pluginName: string)=>{ + pluginsSocket!.emit('uninstall', pluginName); + // Remove plugin + setInstalledPlugins(installedPlugins.filter(i=>i.name !== pluginName)) + } + + const installPlugin = (pluginName: string)=>{ + pluginsSocket!.emit('install', pluginName); + setPlugins(plugins.filter(plugin=>plugin.name !== pluginName)) + } + + + useDebounce(()=>{ + setSearchParams({ + ...searchParams, + offset: 0, + searchTerm: searchTerm + }) + }, 500, [searchTerm]) + + return
+

+ +

+ + + + + + + + + + + {installedPlugins.map((plugin, index) => { + return + + + + + })} + +
{plugin.name}{plugin.version} + { + plugin.updatable ? + + : + + } +
+ + +

+ + { + setSearchTerm(v.target.value) + }}/> + + + + + + + + + + + + + {plugins.map((plugin) => { + return + + + + + + + })} + +
{plugin.name}{plugin.description}{plugin.version}{plugin.time} + +
+
+} diff --git a/admin/src/pages/LoginScreen.tsx b/admin/src/pages/LoginScreen.tsx new file mode 100644 index 000000000..7860368f8 --- /dev/null +++ b/admin/src/pages/LoginScreen.tsx @@ -0,0 +1,44 @@ +import {useState} from "react"; +import {useStore} from "../store/store.ts"; +import {useNavigate} from "react-router-dom"; + +export const LoginScreen = ()=>{ + const navigate = useNavigate() + const [username, setUsername] = useState('') + const [password, setPassword] = useState('') + + const login = ()=>{ + fetch('/admin-auth/', { + method: 'POST', + headers:{ + Authorization: `Basic ${btoa(`${username}:${password}`)}` + } + }).then(r=>{ + if(!r.ok) { + useStore.getState().setToastState({ + open: true, + title: "Login failed", + success: false + }) + } else { + navigate('/') + } + }).catch(e=>{ + console.error(e) + }) + } + + return
+
+

Login Etherpad

+
+
Username
+ setUsername(v.target.value)} placeholder="Username"/> +
Passwort
+ setPassword(v.target.value)} placeholder="Password"/> + +
+
+
+} diff --git a/admin/src/pages/PadPage.tsx b/admin/src/pages/PadPage.tsx new file mode 100644 index 000000000..5c11755d6 --- /dev/null +++ b/admin/src/pages/PadPage.tsx @@ -0,0 +1,172 @@ +import {Trans, useTranslation} from "react-i18next"; +import {useEffect, useMemo, useState} from "react"; +import {useStore} from "../store/store.ts"; +import {PadSearchQuery, PadSearchResult} from "../utils/PadSearch.ts"; +import {useDebounce} from "../utils/useDebounce.ts"; +import {determineSorting} from "../utils/sorting.ts"; +import * as Dialog from "@radix-ui/react-dialog"; + +export const PadPage = ()=>{ + const settingsSocket = useStore(state=>state.settingsSocket) + const [searchParams, setSearchParams] = useState({ + offset: 0, + limit: 12, + pattern: '', + sortBy: 'padName', + ascending: true + }) + const {t} = useTranslation() + const [searchTerm, setSearchTerm] = useState('') + const pads = useStore(state=>state.pads) + const pages = useMemo(()=>{ + if(!pads){ + return [0] + } + + const totalPages = Math.ceil(pads!.total / searchParams.limit) + return Array.from({length: totalPages}, (_, i) => i+1) + },[pads, searchParams.limit]) + const [deleteDialog, setDeleteDialog] = useState(false) + const [padToDelete, setPadToDelete] = useState('') + + useDebounce(()=>{ + setSearchParams({ + ...searchParams, + pattern: searchTerm + }) + + }, 500, [searchTerm]) + + useEffect(() => { + if(!settingsSocket){ + return + } + + settingsSocket.emit('padLoad', searchParams) + + }, [settingsSocket, searchParams]); + + useEffect(() => { + if(!settingsSocket){ + return + } + + settingsSocket.on('results:padLoad', (data: PadSearchResult)=>{ + useStore.getState().setPads(data); + }) + + + settingsSocket.on('results:deletePad', (padID: string)=>{ + const newPads = useStore.getState().pads?.results?.filter((pad)=>{ + return pad.padName !== padID + }) + useStore.getState().setPads({ + total: useStore.getState().pads!.total-1, + results: newPads + }) + }) + }, [settingsSocket, pads]); + + const deletePad = (padID: string)=>{ + settingsSocket?.emit('deletePad', padID) + } + + + + return
+ + + +
+
+
+ {t("ep_admin_pads:ep_adminpads2_confirm", { + padID: padToDelete, + })} +
+
+ + +
+
+
+
+
+

+ setSearchTerm(v.target.value)} + placeholder={t('ep_admin_pads:ep_adminpads2_search-heading')}/> + + + + + + + + + + + + { + pads?.results?.map((pad)=>{ + return + + + + + + + }) + } + +
{ + setSearchParams({ + ...searchParams, + sortBy: 'padName', + ascending: !searchParams.ascending + }) + }}>{ + setSearchParams({ + ...searchParams, + sortBy: 'lastEdited', + ascending: !searchParams.ascending + }) + }}>{ + setSearchParams({ + ...searchParams, + sortBy: 'userCount', + ascending: !searchParams.ascending + }) + }}>{ + setSearchParams({ + ...searchParams, + sortBy: 'revisionNumber', + ascending: !searchParams.ascending + }) + }}>Revision number
{pad.padName}{pad.userCount}{new Date(pad.lastEdited).toLocaleString()}{pad.revisionNumber} +
+ + +
+
+
+ {pages.map((page)=>{ + return + })} +
+
+} diff --git a/admin/src/pages/Plugin.ts b/admin/src/pages/Plugin.ts new file mode 100644 index 000000000..3188c247f --- /dev/null +++ b/admin/src/pages/Plugin.ts @@ -0,0 +1,36 @@ +export type PluginDef = { + name: string, + description: string, + version: string, + time: string, + official: boolean, +} + + +export type InstalledPlugin = { + name: string, + path: string, + realPath: string, + version:string, + updatable?: boolean +} + + +export type SearchParams = { + searchTerm: string, + offset: number, + limit: number, + sortBy: 'name'|'version', + sortDir: 'asc'|'desc' +} + + +export type HelpObj = { + epVersion: string + gitCommit: string + installedClientHooks: Record>, + installedParts: string[], + installedPlugins: string[], + installedServerHooks: Record, + latestVersion: string +} diff --git a/admin/src/pages/SettingsPage.tsx b/admin/src/pages/SettingsPage.tsx new file mode 100644 index 000000000..b72507c3a --- /dev/null +++ b/admin/src/pages/SettingsPage.tsx @@ -0,0 +1,45 @@ +import {useStore} from "../store/store.ts"; +import {isJSONClean} from "../utils/utils.ts"; +import {Trans} from "react-i18next"; + +export const SettingsPage = ()=>{ + const settingsSocket = useStore(state=>state.settingsSocket) + + const settings = useStore(state=>state.settings) + + return
+

+ - - -
-
- Example production settings template - Example development settings template -
- -
-

-
- - - - - diff --git a/src/templates/javascript.html b/src/templates/javascript.html index 42482f69d..c501af65c 100644 --- a/src/templates/javascript.html +++ b/src/templates/javascript.html @@ -34,24 +34,16 @@ require-kernel.js - plugins.js Apache-2.0-only - plugins.js - minify.json.js Expat - minify.json.js - settings.js Apache-2.0-only - settings.js - jquery.autosize.js Expat - jquery.autosize.js diff --git a/src/tests/backend/specs/webaccess.ts b/src/tests/backend/specs/webaccess.ts index 009737c46..96c2265fc 100644 --- a/src/tests/backend/specs/webaccess.ts +++ b/src/tests/backend/specs/webaccess.ts @@ -54,10 +54,10 @@ describe(__filename, function () { await agent.get('/').expect(200); }); - it('!authn !authz anonymous /admin/ -> 401', async function () { + it('!authn !authz anonymous /admin-auth// -> 401', async function () { settings.requireAuthentication = false; settings.requireAuthorization = false; - await agent.get('/admin/').expect(401); + await agent.get('/admin-auth/').expect(401); }); it('authn !authz anonymous / -> 401', async function () { @@ -72,10 +72,10 @@ describe(__filename, function () { await agent.get('/').auth('user', 'user-password').expect(200); }); - it('authn !authz user /admin/ -> 403', async function () { + it('authn !authz user //admin-auth// -> 403', async function () { settings.requireAuthentication = true; settings.requireAuthorization = false; - await agent.get('/admin/').auth('user', 'user-password').expect(403); + await agent.get('/admin-auth//').auth('user', 'user-password').expect(403); }); it('authn !authz admin / -> 200', async function () { @@ -84,10 +84,10 @@ describe(__filename, function () { await agent.get('/').auth('admin', 'admin-password').expect(200); }); - it('authn !authz admin /admin/ -> 200', async function () { + it('authn !authz admin /admin-auth/ -> 200', async function () { settings.requireAuthentication = true; settings.requireAuthorization = false; - await agent.get('/admin/').auth('admin', 'admin-password').expect(200); + await agent.get('/admin-auth/').auth('admin', 'admin-password').expect(200); }); it('authn authz anonymous /robots.txt -> 200', async function () { @@ -102,10 +102,10 @@ describe(__filename, function () { await agent.get('/').auth('user', 'user-password').expect(403); }); - it('authn authz user /admin/ -> 403', async function () { + it('authn authz user //admin-auth// -> 403', async function () { settings.requireAuthentication = true; settings.requireAuthorization = true; - await agent.get('/admin/').auth('user', 'user-password').expect(403); + await agent.get('/admin-auth//').auth('user', 'user-password').expect(403); }); it('authn authz admin / -> 200', async function () { @@ -114,10 +114,10 @@ describe(__filename, function () { await agent.get('/').auth('admin', 'admin-password').expect(200); }); - it('authn authz admin /admin/ -> 200', async function () { + it('authn authz admin /admin-auth/ -> 200', async function () { settings.requireAuthentication = true; settings.requireAuthorization = true; - await agent.get('/admin/').auth('admin', 'admin-password').expect(200); + await agent.get('/admin-auth/').auth('admin', 'admin-password').expect(200); }); describe('login fails if password is nullish', function () { @@ -130,7 +130,7 @@ describe(__filename, function () { it(`admin password: ${adminPassword} credentials: ${creds}`, async function () { settings.users.admin.password = adminPassword; const encCreds = Buffer.from(creds).toString('base64'); - await agent.get('/admin/').set('Authorization', `Basic ${encCreds}`).expect(401); + await agent.get('/admin-auth/').set('Authorization', `Basic ${encCreds}`).expect(401); }); } } @@ -228,11 +228,11 @@ describe(__filename, function () { it('cannot grant access to /admin', async function () { handlers.preAuthorize[0].innerHandle = () => [true]; - await agent.get('/admin/').expect(401); + await agent.get('/admin-auth/').expect(401); // Notes: // * preAuthorize[1] is called despite preAuthorize[0] returning a non-empty list because - // 'true' entries are ignored for /admin/* requests. - // * The authenticate hook always runs for /admin/* requests even if + // 'true' entries are ignored for /admin-auth//* requests. + // * The authenticate hook always runs for /admin-auth//* requests even if // settings.requireAuthentication is false. assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1', @@ -240,9 +240,9 @@ describe(__filename, function () { 'authenticate_1']); }); - it('can deny access to /admin', async function () { + it('can deny access to /admin-auth/', async function () { handlers.preAuthorize[0].innerHandle = () => [false]; - await agent.get('/admin/').auth('admin', 'admin-password').expect(403); + await agent.get('/admin-auth/').auth('admin', 'admin-password').expect(403); assert.deepEqual(callOrder, ['preAuthorize_0']); }); @@ -258,7 +258,7 @@ describe(__filename, function () { res.status(200).send('injected'); return cb([true]); })]; - await agent.get('/admin/').auth('admin', 'admin-password').expect(200, 'injected'); + await agent.get('/admin-auth//').auth('admin', 'admin-password').expect(200, 'injected'); assert(called); }); @@ -274,15 +274,15 @@ describe(__filename, function () { settings.requireAuthorization = false; }); - it('is not called if !requireAuthentication and not /admin/*', async function () { + it('is not called if !requireAuthentication and not /admin-auth/*', async function () { settings.requireAuthentication = false; await agent.get('/').expect(200); assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1']); }); - it('is called if !requireAuthentication and /admin/*', async function () { + it('is called if !requireAuthentication and /admin-auth//*', async function () { settings.requireAuthentication = false; - await agent.get('/admin/').expect(401); + await agent.get('/admin-auth/').expect(401); assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1', 'authenticate_0', @@ -393,7 +393,7 @@ describe(__filename, function () { it('is not called if !requireAuthorization (/admin)', async function () { settings.requireAuthorization = false; - await agent.get('/admin/').auth('admin', 'admin-password').expect(200); + await agent.get('/admin-auth/').auth('admin', 'admin-password').expect(200); assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1', 'authenticate_0',