diff --git a/.dockerignore b/.dockerignore index edbf85256..90fbe1655 100644 --- a/.dockerignore +++ b/.dockerignore @@ -16,6 +16,7 @@ **/compose* **/Dockerfile* **/node_modules +!**/node_modules/.pnpm/compressjs@*/** **/npm-debug.log **/obj **/secrets.dev.yaml diff --git a/Dockerfile b/Dockerfile index 9fec35e70..f8bb15517 100644 --- a/Dockerfile +++ b/Dockerfile @@ -52,6 +52,8 @@ COPY --link --chown=1000:1000 /public ./public/ # Copy files from builder COPY --link --from=builder --chown=1000:1000 /app/.next/standalone ./ COPY --link --from=builder --chown=1000:1000 /app/.next/static/ ./.next/static/ +# see https://github.com/benphelps/homepage/issues/1795 +COPY --link --from=builder /app/node_modules/.pnpm/compressjs@1.0.3/node_modules/compressjs/lib/ ./node_modules/.pnpm/compressjs@1.0.3/node_modules/compressjs/lib/ COPY --link --chmod=755 docker-entrypoint.sh /usr/local/bin/ RUN apk add --no-cache su-exec diff --git a/public/locales/ar/common.json b/public/locales/ar/common.json index e4eb1d0c5..d48111dac 100644 --- a/public/locales/ar/common.json +++ b/public/locales/ar/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/bg/common.json b/public/locales/bg/common.json index dc3bbd036..cddb65636 100644 --- a/public/locales/bg/common.json +++ b/public/locales/bg/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/ca/common.json b/public/locales/ca/common.json index 5147a31dd..fdf0ead38 100644 --- a/public/locales/ca/common.json +++ b/public/locales/ca/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "totalUsed": "Used Storage", "noRecent": "Out of Date" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/cs/common.json b/public/locales/cs/common.json index 39d60f58d..2ead9c2fa 100644 --- a/public/locales/cs/common.json +++ b/public/locales/cs/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/da/common.json b/public/locales/da/common.json index 8b1ded637..5382ad049 100644 --- a/public/locales/da/common.json +++ b/public/locales/da/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/de/common.json b/public/locales/de/common.json index 9df3bfd2a..016b0289a 100644 --- a/public/locales/de/common.json +++ b/public/locales/de/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/el/common.json b/public/locales/el/common.json index 49b76d490..6cf37721c 100644 --- a/public/locales/el/common.json +++ b/public/locales/el/common.json @@ -237,17 +237,17 @@ }, "bazarr": { "missingEpisodes": "Επεισόδια που λείπουν", - "missingMovies": "Missing Movies" + "missingMovies": "Ταινίες που Λείπουν" }, "ombi": { - "pending": "Pending", - "approved": "Approved", - "available": "Available" + "pending": "Σε εκκρεμότητα", + "approved": "Εγκρίθηκε", + "available": "Διαθέσιμο" }, "jellyseerr": { - "pending": "Pending", - "approved": "Approved", - "available": "Available" + "pending": "Σε εκκρεμότητα", + "approved": "Εγκρίθηκε", + "available": "Διαθέσιμο" }, "overseerr": { "pending": "Pending", @@ -257,25 +257,25 @@ }, "pihole": { "queries": "Queries", - "blocked": "Blocked", + "blocked": "Αποκλεισμένο", "gravity": "Gravity", - "blocked_percent": "Blocked %" + "blocked_percent": "Αποκλεισμένο %" }, "adguard": { - "queries": "Queries", - "blocked": "Blocked", - "filtered": "Filtered", - "latency": "Latency" + "queries": "Αναζητήσεις", + "blocked": "Αποκλεισμένο", + "filtered": "Φιλτραρισμένα", + "latency": "Καθυστέρηση" }, "speedtest": { - "upload": "Upload", - "download": "Download", + "upload": "Ανέβασμα", + "download": "Λήψη", "ping": "Ping" }, "portainer": { "running": "Running", - "stopped": "Stopped", - "total": "Total" + "stopped": "Σταματημένο", + "total": "Σύνολο" }, "tdarr": { "queue": "Queue", @@ -563,7 +563,7 @@ "records_total": "Queue Length" }, "pterodactyl": { - "servers": "Servers", + "servers": "Διακομιστές", "nodes": "Nodes" }, "prometheus": { @@ -595,22 +595,22 @@ "shows": "Εκπομπές", "recordings": "Εγγραφές", "scheduled": "Προγραμματισμένα", - "passes": "Passes" + "passes": "Περάσματα" }, "whatsupdocker": { "monitoring": "Monitoring", "updates": "Updates" }, "tailscale": { - "address": "Address", - "never": "Never", + "address": "Διεύθυνση", + "never": "Ποτέ", "years": "{{number}}y", "weeks": "{{number}}w", "days": "{{number}}d", "hours": "{{number}}h", - "expires": "Expires", + "expires": "Λήγει", "last_seen": "Last Seen", - "now": "Now", + "now": "Τώρα", "minutes": "{{number}}m", "seconds": "{{number}}s", "ago": "{{value}} Ago" @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 7ff2973c0..2cf3f1bae 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -703,9 +703,17 @@ "totalUsed": "Used Storage" }, "mealie": { - "recipes": "Recipes", - "users": "Users", - "categories": "Categories", - "tags": "Tags" + "recipes": "Recipes", + "users": "Users", + "categories": "Categories", + "tags": "Tags" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/eo/common.json b/public/locales/eo/common.json index 019d05b73..5539ebc23 100644 --- a/public/locales/eo/common.json +++ b/public/locales/eo/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/es/common.json b/public/locales/es/common.json index 37d4b0e6b..f70bd011a 100644 --- a/public/locales/es/common.json +++ b/public/locales/es/common.json @@ -692,5 +692,13 @@ "errored": "Errores", "noRecent": "Caducado", "totalUsed": "Almacenamiento usado" + }, + "openmediavault": { + "running": "Ejecutando", + "downloading": "Descargando", + "total": "Total", + "stopped": "Detenido", + "passed": "Aprobado", + "failed": "Fallido" } } diff --git a/public/locales/eu/common.json b/public/locales/eu/common.json index 8ba75c93a..9737a2416 100644 --- a/public/locales/eu/common.json +++ b/public/locales/eu/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/fi/common.json b/public/locales/fi/common.json index 505c7a60c..6017b1794 100644 --- a/public/locales/fi/common.json +++ b/public/locales/fi/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/fr/common.json b/public/locales/fr/common.json index 8d76d83f4..8b0859469 100644 --- a/public/locales/fr/common.json +++ b/public/locales/fr/common.json @@ -692,5 +692,13 @@ "errored": "Erreur", "noRecent": "Obsolète", "totalUsed": "Esp. Utilisé" + }, + "openmediavault": { + "downloading": "Téléchargement", + "total": "Total", + "running": "Démarré", + "stopped": "Arrêté", + "passed": "Réussi", + "failed": "Échoué" } } diff --git a/public/locales/he/common.json b/public/locales/he/common.json index fb335e708..7af222f25 100644 --- a/public/locales/he/common.json +++ b/public/locales/he/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/hi/common.json b/public/locales/hi/common.json index bd2491e10..6754bad7e 100644 --- a/public/locales/hi/common.json +++ b/public/locales/hi/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/hr/common.json b/public/locales/hr/common.json index 26772c071..299c406c0 100644 --- a/public/locales/hr/common.json +++ b/public/locales/hr/common.json @@ -692,5 +692,13 @@ "totalUsed": "Used Storage", "ok": "Ok", "errored": "Errors" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/hu/common.json b/public/locales/hu/common.json index a8b2b9e8f..2a3f1a8a2 100644 --- a/public/locales/hu/common.json +++ b/public/locales/hu/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/id/common.json b/public/locales/id/common.json index e0a8e542b..f66f61112 100644 --- a/public/locales/id/common.json +++ b/public/locales/id/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/it/common.json b/public/locales/it/common.json index 502116983..d1a1b85ff 100644 --- a/public/locales/it/common.json +++ b/public/locales/it/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "total": "Total", + "running": "Running", + "downloading": "Downloading", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/ja/common.json b/public/locales/ja/common.json index 089ad81b6..34be82e38 100644 --- a/public/locales/ja/common.json +++ b/public/locales/ja/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/ko/common.json b/public/locales/ko/common.json index 31f4a6ff5..666fc35ec 100644 --- a/public/locales/ko/common.json +++ b/public/locales/ko/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/lv/common.json b/public/locales/lv/common.json index 2bdd7a4ce..9ddb8e4cf 100644 --- a/public/locales/lv/common.json +++ b/public/locales/lv/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/ms/common.json b/public/locales/ms/common.json index 5c5ecff98..c1f79f526 100644 --- a/public/locales/ms/common.json +++ b/public/locales/ms/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "downloading": "Downloading", + "total": "Total", + "failed": "Failed" } } diff --git a/public/locales/nb-NO/common.json b/public/locales/nb-NO/common.json index dea49c31c..738354e2e 100644 --- a/public/locales/nb-NO/common.json +++ b/public/locales/nb-NO/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/nl/common.json b/public/locales/nl/common.json index 39fe9fe5f..4521e9351 100644 --- a/public/locales/nl/common.json +++ b/public/locales/nl/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/pl/common.json b/public/locales/pl/common.json index 765b48f61..17320906e 100644 --- a/public/locales/pl/common.json +++ b/public/locales/pl/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/pt-BR/common.json b/public/locales/pt-BR/common.json index 13735c01c..00c51552d 100644 --- a/public/locales/pt-BR/common.json +++ b/public/locales/pt-BR/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/pt/common.json b/public/locales/pt/common.json index 4c7f42595..8429db914 100644 --- a/public/locales/pt/common.json +++ b/public/locales/pt/common.json @@ -701,5 +701,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/ro/common.json b/public/locales/ro/common.json index 567bfba5f..b72cf9ad7 100644 --- a/public/locales/ro/common.json +++ b/public/locales/ro/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/ru/common.json b/public/locales/ru/common.json index 5c65900ce..502adeaf8 100644 --- a/public/locales/ru/common.json +++ b/public/locales/ru/common.json @@ -16,11 +16,11 @@ "free": "Свободно", "used": "Использовано", "load": "Загрузка", - "cpu": "Процессор", - "mem": "Память", - "temp": "Температура", + "cpu": "ЦП", + "mem": "ОЗУ", + "temp": "Темпер.", "max": "Макс.", - "uptime": "UP", + "uptime": "Работает", "months": "мес", "days": "д", "hours": "ч", @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "passed": "Passed", + "failed": "Failed", + "running": "Running", + "stopped": "Stopped" } } diff --git a/public/locales/sk/common.json b/public/locales/sk/common.json index aba3bc6fa..c666bc7a4 100644 --- a/public/locales/sk/common.json +++ b/public/locales/sk/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "total": "Total", + "downloading": "Downloading", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/sl/common.json b/public/locales/sl/common.json index 51e78672a..12623c6f3 100644 --- a/public/locales/sl/common.json +++ b/public/locales/sl/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/sr/common.json b/public/locales/sr/common.json index 78075f4cb..437108fd5 100644 --- a/public/locales/sr/common.json +++ b/public/locales/sr/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed", + "downloading": "Downloading" } } diff --git a/public/locales/sv/common.json b/public/locales/sv/common.json index 1dd59ece7..8d24f5c96 100644 --- a/public/locales/sv/common.json +++ b/public/locales/sv/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/te/common.json b/public/locales/te/common.json index 77cc7df01..6c37a87c6 100644 --- a/public/locales/te/common.json +++ b/public/locales/te/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "failed": "Failed", + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed" } } diff --git a/public/locales/th/common.json b/public/locales/th/common.json index cf54e20ce..c3cbc7285 100644 --- a/public/locales/th/common.json +++ b/public/locales/th/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/tr/common.json b/public/locales/tr/common.json index 8dc340b25..7349981b4 100644 --- a/public/locales/tr/common.json +++ b/public/locales/tr/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/uk/common.json b/public/locales/uk/common.json index c94e7e5ba..71d754983 100644 --- a/public/locales/uk/common.json +++ b/public/locales/uk/common.json @@ -670,9 +670,9 @@ "maxPlayers": "Максимум гравців", "bots": "Ботів", "ping": "Пінг", - "status": "Status", - "online": "Online", - "offline": "Offline" + "status": "Статус", + "online": "В мережі", + "offline": "Не в мережі" }, "azuredevops": { "result": "Результат", @@ -692,5 +692,13 @@ "errored": "Помилки", "noRecent": "Застарілий", "totalUsed": "Використовувана пам'ять" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/vi/common.json b/public/locales/vi/common.json index cf2d03f8e..ebc0eae3e 100644 --- a/public/locales/vi/common.json +++ b/public/locales/vi/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/yue/common.json b/public/locales/yue/common.json index 8c6028534..0dc4ee45e 100644 --- a/public/locales/yue/common.json +++ b/public/locales/yue/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/zh-CN/common.json b/public/locales/zh-CN/common.json index 2512b2566..2d2a76cf8 100644 --- a/public/locales/zh-CN/common.json +++ b/public/locales/zh-CN/common.json @@ -692,5 +692,13 @@ "errored": "Errors", "noRecent": "Out of Date", "totalUsed": "Used Storage" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/public/locales/zh-Hant/common.json b/public/locales/zh-Hant/common.json index f8969c29c..c499b6351 100644 --- a/public/locales/zh-Hant/common.json +++ b/public/locales/zh-Hant/common.json @@ -692,5 +692,13 @@ "errored": "錯誤", "noRecent": "已過時", "totalUsed": "已使用空間" + }, + "openmediavault": { + "downloading": "Downloading", + "total": "Total", + "running": "Running", + "stopped": "Stopped", + "passed": "Passed", + "failed": "Failed" } } diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js index 83b4b07bc..4488277ad 100644 --- a/src/utils/config/service-helpers.js +++ b/src/utils/config/service-helpers.js @@ -298,6 +298,7 @@ export function cleanServiceGroups(groups) { metric, // glances stream, // mjpeg fit, + method, // openmediavault widget } = cleanedService.widget; let fieldsList = fields; @@ -368,6 +369,9 @@ export function cleanServiceGroups(groups) { if (stream) cleanedService.widget.stream = stream; if (fit) cleanedService.widget.fit = fit; } + if (type === "openmediavault") { + if (method) cleanedService.widget.method = method; + } } return cleanedService; diff --git a/src/widgets/components.js b/src/widgets/components.js index 5cb23bba3..f3242ce4b 100644 --- a/src/widgets/components.js +++ b/src/widgets/components.js @@ -60,6 +60,7 @@ const components = { ombi: dynamic(() => import("./ombi/component")), opnsense: dynamic(() => import("./opnsense/component")), overseerr: dynamic(() => import("./overseerr/component")), + openmediavault: dynamic(() => import("./openmediavault/component")), paperlessngx: dynamic(() => import("./paperlessngx/component")), pfsense: dynamic(() => import("./pfsense/component")), photoprism: dynamic(() => import("./photoprism/component")), diff --git a/src/widgets/openmediavault/component.jsx b/src/widgets/openmediavault/component.jsx new file mode 100644 index 000000000..bd34a7502 --- /dev/null +++ b/src/widgets/openmediavault/component.jsx @@ -0,0 +1,16 @@ +import ServicesGetStatus from "./methods/services_get_status"; +import SmartGetList from "./methods/smart_get_list"; +import DownloaderGetDownloadList from "./methods/downloader_get_downloadlist"; + +export default function Component({ service }) { + switch (service.widget.method) { + case "services.getStatus": + return ; + case "smart.getListBg": + return ; + case "downloader.getDownloadList": + return ; + default: + return null; + } +} diff --git a/src/widgets/openmediavault/methods/downloader_get_downloadlist.jsx b/src/widgets/openmediavault/methods/downloader_get_downloadlist.jsx new file mode 100644 index 000000000..ed776db0f --- /dev/null +++ b/src/widgets/openmediavault/methods/downloader_get_downloadlist.jsx @@ -0,0 +1,36 @@ +import useWidgetAPI from "utils/proxy/use-widget-api"; +import Container from "components/services/widget/container"; +import Block from "components/services/widget/block"; + +const downloadReduce = (acc, e) => { + if (e.downloading) { + return acc + 1; + } + return acc; +}; + +const items = [ + { label: "openmediavault.downloading", getNumber: (data) => (!data ? null : data.reduce(downloadReduce, 0)) }, + { label: "openmediavault.total", getNumber: (data) => (!data ? null : data?.length) }, +]; + +export default function Component({ service }) { + const { data, error } = useWidgetAPI(service.widget); + + if (error) { + return ; + } + + const itemsWithData = items.map((item) => ({ + ...item, + number: item.getNumber(data?.response?.data), + })); + + return ( + + {itemsWithData.map((e) => ( + + ))} + + ); +} diff --git a/src/widgets/openmediavault/methods/services_get_status.jsx b/src/widgets/openmediavault/methods/services_get_status.jsx new file mode 100644 index 000000000..3ec66a45c --- /dev/null +++ b/src/widgets/openmediavault/methods/services_get_status.jsx @@ -0,0 +1,43 @@ +import useWidgetAPI from "utils/proxy/use-widget-api"; +import Container from "components/services/widget/container"; +import Block from "components/services/widget/block"; + +const isRunningReduce = (acc, e) => { + if (e.running) { + return acc + 1; + } + return acc; +}; +const notRunningReduce = (acc, e) => { + if (!e.running) { + return acc + 1; + } + return acc; +}; + +const items = [ + { label: "openmediavault.running", getNumber: (data) => (!data ? null : data.reduce(isRunningReduce, 0)) }, + { label: "openmediavault.stopped", getNumber: (data) => (!data ? null : data.reduce(notRunningReduce, 0)) }, + { label: "openmediavault.total", getNumber: (data) => (!data ? null : data?.length) }, +]; + +export default function Component({ service }) { + const { data, error } = useWidgetAPI(service.widget); + + if (error) { + return ; + } + + const itemsWithData = items.map((item) => ({ + ...item, + number: item.getNumber(data?.response?.data), + })); + + return ( + + {itemsWithData.map((e) => ( + + ))} + + ); +} diff --git a/src/widgets/openmediavault/methods/smart_get_list.jsx b/src/widgets/openmediavault/methods/smart_get_list.jsx new file mode 100644 index 000000000..55a76db67 --- /dev/null +++ b/src/widgets/openmediavault/methods/smart_get_list.jsx @@ -0,0 +1,42 @@ +import useWidgetAPI from "utils/proxy/use-widget-api"; +import Container from "components/services/widget/container"; +import Block from "components/services/widget/block"; + +const passedReduce = (acc, e) => { + if (e.overallstatus === "GOOD") { + return acc + 1; + } + return acc; +}; +const failedReduce = (acc, e) => { + if (e.overallstatus !== "GOOD") { + return acc + 1; + } + return acc; +}; + +const items = [ + { label: "openmediavault.passed", getNumber: (data) => (!data ? null : data.reduce(passedReduce, 0)) }, + { label: "openmediavault.failed", getNumber: (data) => (!data ? null : data.reduce(failedReduce, 0)) }, +]; + +export default function Component({ service }) { + const { data, error } = useWidgetAPI(service.widget); + + if (error) { + return ; + } + + const itemsWithData = items.map((item) => ({ + ...item, + number: item.getNumber(JSON.parse(data?.response?.output || "{}")?.data), + })); + + return ( + + {itemsWithData.map((e) => ( + + ))} + + ); +} diff --git a/src/widgets/openmediavault/proxy.js b/src/widgets/openmediavault/proxy.js new file mode 100644 index 000000000..a9099d244 --- /dev/null +++ b/src/widgets/openmediavault/proxy.js @@ -0,0 +1,151 @@ +import { formatApiCall } from "utils/proxy/api-helpers"; +import { httpProxy } from "utils/proxy/http"; +import getServiceWidget from "utils/config/service-helpers"; +import { addCookieToJar, setCookieHeader } from "utils/proxy/cookie-jar"; +import createLogger from "utils/logger"; +import widgets from "widgets/widgets"; + +const PROXY_NAME = "OMVProxyHandler"; +const BG_MAX_RETRIES = 50; +const BG_POLL_PERIOD = 500; + +const logger = createLogger(PROXY_NAME); + +async function getWidget(req) { + const { group, service } = req.query; + + if (!group || !service) { + logger.debug("Invalid or missing service '%s' or group '%s'", service, group); + return null; + } + + const widget = await getServiceWidget(group, service); + + if (!widget) { + logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group); + return null; + } + + return widget; +} + +async function rpc(url, request) { + const params = { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(request), + }; + setCookieHeader(url, params); + const [status, contentType, data, headers] = await httpProxy(url, params); + + return { status, contentType, data, headers }; +} + +async function poll(attemptsLeft, makeReqByPos, pos = 0) { + if (attemptsLeft <= 0) { + return null; + } + + const resp = await makeReqByPos(pos); + + const data = JSON.parse(resp.data.toString()).response; + if (data.running === true || data.outputPending) { + await new Promise((resolve) => { + setTimeout(resolve, BG_POLL_PERIOD); + }); + return poll(attemptsLeft - 1, makeReqByPos, data.pos); + } + return resp; +} + +async function tryLogin(widget) { + const url = new URL(formatApiCall(widgets?.[widget.type]?.api, { ...widget })); + const { username, password } = widget; + const resp = await rpc(url, { + method: "login", + service: "session", + params: { username, password }, + }); + + if (resp.status !== 200) { + logger.error("HTTP %d logging in to OpenMediaVault. Data: %s", resp.status, resp.data); + return [false, resp]; + } + + const json = JSON.parse(resp.data.toString()); + if (json.response.authenticated !== true) { + logger.error("Login error in OpenMediaVault. Data: %s", resp.data); + resp.status = 401; + return [false, resp]; + } + + return [true, resp]; +} +async function processBg(url, filename) { + const resp = await poll(BG_MAX_RETRIES, (pos) => + rpc(url, { + service: "exec", + method: "getOutput", + params: { pos, filename }, + }) + ); + + if (resp == null) { + const errText = "The maximum number of attempts to receive a response from Bg data has been exceeded."; + logger.error(errText); + return errText; + } + if (resp.status !== 200) { + logger.error("HTTP %d getting Bg data from OpenMediaVault RPC. Data: %s", resp.status, resp.data); + } + return resp; +} + +export default async function proxyHandler(req, res) { + const widget = await getWidget(req); + if (!widget) { + return res.status(400).json({ error: "Invalid proxy service type" }); + } + + const api = widgets?.[widget.type]?.api; + if (!api) { + return res.status(403).json({ error: "Service does not support RPC calls" }); + } + + const url = new URL(formatApiCall(api, { ...widget })); + const [service, method] = widget.method.split("."); + const rpcReq = { params: { limit: -1, start: 0 }, service, method }; + + let resp = await rpc(url, rpcReq); + + if (resp.status === 401) { + logger.debug("Session not authenticated."); + const [success, lResp] = await tryLogin(widget); + + if (success) { + addCookieToJar(url, lResp.headers); + } else { + res.status(lResp.status).json({ error: { message: `HTTP Error ${lResp.status}`, url, data: lResp.data } }); + } + + logger.debug("Retrying OpenMediaVault request after login."); + resp = await rpc(url, rpcReq); + } + + if (resp.status !== 200) { + logger.error("HTTP %d getting data from OpenMediaVault RPC. Data: %s", resp.status, resp.data); + return res.status(resp.status).json({ error: { message: `HTTP Error ${resp.status}`, url, data: resp.data } }); + } + + if (method.endsWith("Bg")) { + const json = JSON.parse(resp.data.toString()); + const bgResp = await processBg(url, json.response); + + if (typeof bgResp === "string") { + return res.status(400).json({ error: bgResp }); + } + return res.status(bgResp.status).send(bgResp.data); + } + + return res.status(resp.status).send(resp.data); +} diff --git a/src/widgets/openmediavault/widget.js b/src/widgets/openmediavault/widget.js new file mode 100644 index 000000000..3678ebe8e --- /dev/null +++ b/src/widgets/openmediavault/widget.js @@ -0,0 +1,8 @@ +import proxyHandler from "./proxy"; + +const widget = { + api: "{url}/rpc.php", + proxyHandler, +}; + +export default widget; diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js index 11e1b9faa..1b7d9f1b6 100644 --- a/src/widgets/widgets.js +++ b/src/widgets/widgets.js @@ -54,6 +54,7 @@ import omada from "./omada/widget"; import ombi from "./ombi/widget"; import opnsense from "./opnsense/widget"; import overseerr from "./overseerr/widget"; +import openmediavault from "./openmediavault/widget"; import paperlessngx from "./paperlessngx/widget"; import pfsense from "./pfsense/widget"; import photoprism from "./photoprism/widget"; @@ -150,6 +151,7 @@ const widgets = { ombi, opnsense, overseerr, + openmediavault, paperlessngx, pfsense, photoprism,