Jason Fischer 2 vuotta sitten
vanhempi
commit
893b3f0986
53 muutettua tiedostoa jossa 1068 lisäystä ja 383 poistoa
  1. 16 16
      Dockerfile
  2. 15 1
      docker-entrypoint.sh
  3. 242 229
      public/locales/ar/common.json
  4. 16 3
      public/locales/bg/common.json
  5. 17 4
      public/locales/ca/common.json
  6. 16 3
      public/locales/cs/common.json
  7. 16 3
      public/locales/da/common.json
  8. 16 3
      public/locales/de/common.json
  9. 16 0
      public/locales/en/common.json
  10. 16 3
      public/locales/eo/common.json
  11. 16 3
      public/locales/es/common.json
  12. 21 8
      public/locales/fi/common.json
  13. 17 4
      public/locales/fr/common.json
  14. 16 3
      public/locales/he/common.json
  15. 16 3
      public/locales/hi/common.json
  16. 16 3
      public/locales/hr/common.json
  17. 16 3
      public/locales/hu/common.json
  18. 16 3
      public/locales/it/common.json
  19. 16 3
      public/locales/ja/common.json
  20. 16 3
      public/locales/lv/common.json
  21. 16 3
      public/locales/ms/common.json
  22. 16 3
      public/locales/nb-NO/common.json
  23. 16 3
      public/locales/nl/common.json
  24. 16 3
      public/locales/pl/common.json
  25. 16 3
      public/locales/pt-BR/common.json
  26. 16 3
      public/locales/pt/common.json
  27. 16 3
      public/locales/ro/common.json
  28. 16 3
      public/locales/ru/common.json
  29. 16 3
      public/locales/sr/common.json
  30. 16 3
      public/locales/sv/common.json
  31. 16 3
      public/locales/te/common.json
  32. 16 3
      public/locales/tr/common.json
  33. 19 6
      public/locales/uk/common.json
  34. 16 3
      public/locales/vi/common.json
  35. 16 3
      public/locales/yue/common.json
  36. 16 3
      public/locales/zh-CN/common.json
  37. 19 6
      public/locales/zh-Hant/common.json
  38. 12 12
      src/pages/api/docker/stats/[...service].js
  39. 6 2
      src/pages/api/docker/status/[...service].js
  40. 13 1
      src/utils/config/api-response.js
  41. 29 2
      src/utils/config/service-helpers.js
  42. 6 0
      src/utils/proxy/handlers/credentialed.js
  43. 31 0
      src/widgets/cloudflared/component.jsx
  44. 18 0
      src/widgets/cloudflared/widget.js
  45. 3 0
      src/widgets/components.js
  46. 33 0
      src/widgets/immich/component.jsx
  47. 8 0
      src/widgets/immich/widget.js
  48. 1 1
      src/widgets/nzbget/component.jsx
  49. 3 5
      src/widgets/opnsense/component.jsx
  50. 2 2
      src/widgets/proxmox/component.jsx
  51. 45 0
      src/widgets/proxmoxbackupserver/component.jsx
  52. 22 0
      src/widgets/proxmoxbackupserver/widget.js
  53. 6 0
      src/widgets/widgets.js

+ 16 - 16
Dockerfile

@@ -7,12 +7,10 @@ WORKDIR /app
 
 COPY --link package.json pnpm-lock.yaml* ./
 
-RUN <<EOF
-    set -xe
-    apk add libc6-compat
-    apk add --virtual .gyp python3 make g++
-    npm install -g pnpm
-EOF
+SHELL ["/bin/ash", "-xeo", "pipefail", "-c"]
+RUN apk add --no-cache libc6-compat \
+ && apk add --no-cache --virtual .gyp python3 make g++ \
+ && npm install -g pnpm
 
 RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store pnpm fetch | grep -v "cross-device link not permitted\|Falling back to copying packages from store"
 
@@ -29,12 +27,10 @@ ARG REVISION
 COPY --link --from=deps /app/node_modules ./node_modules/
 COPY . .
 
-RUN <<EOF
-    set -xe
-    npm run telemetry
-    mkdir config && echo '-' > config/settings.yaml
-    NEXT_PUBLIC_BUILDTIME=$BUILDTIME NEXT_PUBLIC_VERSION=$VERSION NEXT_PUBLIC_REVISION=$REVISION npm run build
-EOF
+SHELL ["/bin/ash", "-xeo", "pipefail", "-c"]
+RUN npm run telemetry \
+ && mkdir config && echo '---' > config/settings.yaml \
+ && NEXT_PUBLIC_BUILDTIME=$BUILDTIME NEXT_PUBLIC_VERSION=$VERSION NEXT_PUBLIC_REVISION=$REVISION npm run build
 
 # Production image, copy all the files and run next
 FROM docker.io/node:18-alpine AS runner
@@ -50,12 +46,15 @@ ENV NODE_ENV production
 WORKDIR /app
 
 # Copy files from context (this allows the files to copy before the builder stage is done).
-COPY --link package.json next.config.js ./
-COPY --link /public ./public
+COPY --link --chown=1000:1000 package.json next.config.js ./
+COPY --link --chown=1000:1000 /public ./public/
 
 # Copy files from builder
-COPY --link --from=builder /app/.next/standalone ./
-COPY --link --from=builder /app/.next/static/ ./.next/static/
+COPY --link --from=builder --chown=1000:1000 /app/.next/standalone ./
+COPY --link --from=builder --chown=1000:1000 /app/.next/static/ ./.next/static/
+COPY --link --chmod=755 docker-entrypoint.sh /usr/local/bin/
+
+RUN apk add --no-cache su-exec
 
 ENV PORT 3000
 EXPOSE $PORT
@@ -63,4 +62,5 @@ EXPOSE $PORT
 HEALTHCHECK --interval=10s --timeout=3s --start-period=20s \
   CMD wget --no-verbose --tries=1 --spider --no-check-certificate http://localhost:$PORT/api/healthcheck || exit 1
 
+ENTRYPOINT ["docker-entrypoint.sh"]
 CMD ["node", "server.js"]

+ 15 - 1
docker-entrypoint.sh

@@ -2,8 +2,22 @@
 
 set -e
 
+# Default to root, so old installations won't break
+export PUID=${PUID:-0}
+export PGID=${PGID:-0}
+
 # This is in attempt to preserve the original behavior of the Dockerfile,
 # while also supporting the lscr.io /config directory
 [ ! -d "/app/config" ] && ln -s /config /app/config
 
-node server.js
+# Set privileges for /app but only if pid 1 user is root and we are dropping privileges.
+# If container is run as an unprivileged user, it means owner already handled ownership setup on their own.
+# Running chown in that case (as non-root) will cause error
+[ "$(id -u)" == "0" ] && [ "${PUID}" != "0" ] && chown -R ${PUID}:${PGID} /app
+
+# Drop privileges (when asked to) if root, otherwise run as current user
+if [ "$(id -u)" == "0" ] && [ "${PUID}" != "0" ]; then
+  su-exec ${PUID}:${PGID} "$@"
+else
+  exec "$@"
+fi

+ 242 - 229
public/locales/ar/common.json

@@ -3,16 +3,16 @@
         "missing_type": "نوع القطعة مفقود: {{type}}",
         "api_error": "API خطأ",
         "status": "الحالة",
-        "information": "Information",
+        "information": "معلومات",
         "url": "URL",
         "raw_error": "Raw Error",
-        "response_data": "Response Data"
+        "response_data": "بيانات الاستجابة"
     },
     "weather": {
         "current": "الموقع الحالي",
-        "allow": "اضغط للسماح",
+        "allow": "أنقر للسماح",
         "updating": "جاري التحديث",
-        "wait": "الرجاء الانتظار"
+        "wait": "الرجاء الإنتظار"
     },
     "search": {
         "placeholder": "بحث …"
@@ -27,88 +27,88 @@
     "unifi": {
         "users": "المستخدمون",
         "uptime": "مدة تشغيل النظام",
-        "days": "ايام",
+        "days": "أيام",
         "wan": "WAN",
         "lan": "LAN",
         "wlan": "WLAN",
-        "devices": "الاجهزة",
-        "lan_devices": "LAN اجهزة",
-        "wlan_devices": "WLAN احهزة",
+        "devices": "الأجهزة",
+        "lan_devices": "LAN أجهزة",
+        "wlan_devices": "WLAN أجهزة",
         "lan_users": "LAN مستخدمين",
         "wlan_users": "WLAN مستخدمين",
-        "up": "اعلي",
-        "down": "اسفل",
-        "wait": "الرجاء الانتظار"
+        "up": "يعمل",
+        "down": "لا يعمل",
+        "wait": "الرجاء الإنتظار"
     },
     "wmo": {
-        "73-day": "Snow",
-        "0-day": "Sunny",
-        "0-night": "Clear",
-        "1-day": "Mainly Sunny",
-        "1-night": "Mainly Clear",
-        "2-day": "Partly Cloudy",
-        "2-night": "Partly Cloudy",
-        "3-day": "Cloudy",
-        "3-night": "Cloudy",
-        "45-day": "Foggy",
-        "45-night": "Foggy",
-        "48-day": "Foggy",
-        "48-night": "Foggy",
-        "51-day": "Light Drizzle",
-        "51-night": "Light Drizzle",
-        "53-day": "Drizzle",
-        "53-night": "Drizzle",
-        "55-day": "Heavy Drizzle",
-        "55-night": "Heavy Drizzle",
-        "56-day": "Light Freezing Drizzle",
-        "56-night": "Light Freezing Drizzle",
-        "57-day": "Freezing Drizzle",
-        "57-night": "Freezing Drizzle",
-        "61-day": "Light Rain",
-        "61-night": "Light Rain",
-        "63-day": "Rain",
-        "63-night": "Rain",
-        "65-day": "Heavy Rain",
-        "65-night": "Heavy Rain",
-        "66-day": "Freezing Rain",
-        "66-night": "Freezing Rain",
-        "67-day": "Freezing Rain",
-        "67-night": "Freezing Rain",
-        "71-day": "Light Snow",
-        "71-night": "Light Snow",
-        "73-night": "Snow",
-        "75-day": "Heavy Snow",
-        "75-night": "Heavy Snow",
-        "77-day": "Snow Grains",
-        "77-night": "Snow Grains",
-        "80-day": "Light Showers",
-        "80-night": "Light Showers",
-        "81-day": "Showers",
-        "81-night": "Showers",
-        "82-day": "Heavy Showers",
-        "82-night": "Heavy Showers",
-        "85-day": "Snow Showers",
-        "85-night": "Snow Showers",
-        "86-day": "Snow Showers",
-        "86-night": "Snow Showers",
-        "95-day": "Thunderstorm",
-        "95-night": "Thunderstorm",
-        "96-day": "Thunderstorm With Hail",
-        "96-night": "Thunderstorm With Hail",
-        "99-day": "Thunderstorm With Hail",
-        "99-night": "Thunderstorm With Hail"
+        "73-day": "ثلج",
+        "0-day": "مشمس",
+        "0-night": "صافي",
+        "1-day": "مشمس غالباً",
+        "1-night": "صافي غالباً",
+        "2-day": "غائم جزئياً",
+        "2-night": "غائم جزئياً",
+        "3-day": "غائم",
+        "3-night": "غائم",
+        "45-day": "ضبابي",
+        "45-night": "ضبابي",
+        "48-day": "ضبابي",
+        "48-night": "ضبابي",
+        "51-day": "رذاذ خفيف",
+        "51-night": "رذاذ خفيف",
+        "53-day": "رذاذ",
+        "53-night": "رذاذ",
+        "55-day": "رذاذ كثيف",
+        "55-night": "رذاذ كثيف",
+        "56-day": "رذاذ متجمد خفيف",
+        "56-night": "رذاذ متجمد خفيف",
+        "57-day": "رذاذ متجمد",
+        "57-night": "رذاذ متجمد",
+        "61-day": "مطر خفيف",
+        "61-night": "مطر خفيف",
+        "63-day": "مطر",
+        "63-night": "مطر",
+        "65-day": "مطر شديد",
+        "65-night": "مطر شديد",
+        "66-day": "مطر متجمد",
+        "66-night": "مطر متجمد",
+        "67-day": "مطر متجمد",
+        "67-night": "مطر متجمد",
+        "71-day": "ثلج خفيف",
+        "71-night": "ثلج خفيف",
+        "73-night": "ثلج",
+        "75-day": "ثلج شديد",
+        "75-night": "ثلج شديد",
+        "77-day": "حبيبات الثلج",
+        "77-night": "حبيبات الثلج",
+        "80-day": "أمطار خفيفة",
+        "80-night": "أمطار خفيفة",
+        "81-day": "أمطار",
+        "81-night": "أمطار",
+        "82-day": "أمطار شديدة",
+        "82-night": "أمطار شديدة",
+        "85-day": "زخات الثلوج",
+        "85-night": "زخات الثلوج",
+        "86-day": "زخات الثلوج",
+        "86-night": "زخات الثلوج",
+        "95-day": "عاصفة رعدية",
+        "95-night": "‬عاصفة رعدية",
+        "96-day": "عاصفة رعدية مع مطر",
+        "96-night": "عاصفة رعدية مع مطر",
+        "99-day": "عاصفة رعدية مع مطر",
+        "99-night": "عاصفة رعدية مع مطر"
     },
     "docker": {
         "rx": "RX",
         "tx": "TX",
-        "mem": "الرام",
+        "mem": "الذاكرة",
         "cpu": "المعالج",
         "offline": "غير متصل",
-        "error": "Error",
-        "unknown": "Unknown"
+        "error": "خطأ",
+        "unknown": "مجهول"
     },
     "emby": {
-        "playing": "يعمل الان",
+        "playing": "يعمل الآن",
         "transcoding": "التحويل",
         "bitrate": "معدل البت",
         "no_active": "No Active Streams"
@@ -118,310 +118,323 @@
         "diffsDetected": "Diffs Detected"
     },
     "tautulli": {
-        "playing": "Playing",
-        "transcoding": "Transcoding",
-        "bitrate": "Bitrate",
+        "playing": "يشتغل",
+        "transcoding": "التحويل",
+        "bitrate": "معدل البت",
         "no_active": "No Active Streams"
     },
     "nzbget": {
-        "rate": "Rate",
-        "remaining": "Remaining",
-        "downloaded": "Downloaded"
+        "rate": "معدل",
+        "remaining": "متبقي",
+        "downloaded": "مُنزل"
     },
     "plex": {
         "streams": "Active Streams",
-        "movies": "Movies",
-        "tv": "TV Shows"
+        "movies": "أفلام",
+        "tv": "مسلسلات"
     },
     "sabnzbd": {
-        "rate": "Rate",
-        "queue": "Queue",
-        "timeleft": "Time Left"
+        "rate": "معدل",
+        "queue": "إنتظار",
+        "timeleft": "الوقت المتبقي"
     },
     "rutorrent": {
-        "active": "Active",
-        "upload": "Upload",
-        "download": "Download"
+        "active": "نشط",
+        "upload": "تحميل",
+        "download": "تنزيل"
     },
     "transmission": {
-        "download": "Download",
-        "upload": "Upload",
+        "download": "تنزيل",
+        "upload": "تحميل",
         "leech": "Leech",
         "seed": "Seed"
     },
     "qbittorrent": {
-        "download": "Download",
-        "upload": "Upload",
+        "download": "تنزيل",
+        "upload": "تحميل",
         "leech": "Leech",
         "seed": "Seed"
     },
     "sonarr": {
-        "wanted": "Wanted",
-        "queued": "Queued",
-        "series": "Series"
+        "wanted": "مطلوب",
+        "queued": "في الإنتظار",
+        "series": "سلسلة"
     },
     "radarr": {
-        "wanted": "Wanted",
-        "missing": "Missing",
-        "queued": "Queued",
-        "movies": "Movies"
+        "wanted": "مطلوب",
+        "missing": "مفقود",
+        "queued": "في الإنتظار",
+        "movies": "أفلام"
     },
     "lidarr": {
-        "wanted": "Wanted",
-        "queued": "Queued",
-        "albums": "Albums"
+        "wanted": "مطلوب",
+        "queued": "في الإنتظار",
+        "albums": "ألبومات"
     },
     "readarr": {
-        "wanted": "Wanted",
-        "queued": "Queued",
-        "books": "Books"
+        "wanted": "مطلوب",
+        "queued": "في الإنتظار",
+        "books": "كتب"
     },
     "bazarr": {
-        "missingEpisodes": "Missing Episodes",
-        "missingMovies": "Missing Movies"
+        "missingEpisodes": "حلقات مفقودة",
+        "missingMovies": "أفلام مفقودة"
     },
     "ombi": {
-        "pending": "Pending",
-        "approved": "Approved",
-        "available": "Available"
+        "pending": "معلق",
+        "approved": "مصدق",
+        "available": "متاح"
     },
     "jellyseerr": {
-        "pending": "Pending",
-        "approved": "Approved",
-        "available": "Available"
+        "pending": "معلق",
+        "approved": "مصدق",
+        "available": "متاح"
     },
     "overseerr": {
-        "pending": "Pending",
-        "approved": "Approved",
-        "available": "Available",
-        "processing": "Processing"
+        "pending": "معلق",
+        "approved": "مصدق",
+        "available": "متاح",
+        "processing": "معالجة"
     },
     "pihole": {
-        "queries": "Queries",
-        "blocked": "Blocked",
-        "gravity": "Gravity"
+        "queries": "الاستعلامات",
+        "blocked": "محظور",
+        "gravity": "الجاذبية"
     },
     "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"
+        "running": "يعمل",
+        "stopped": "متوقف",
+        "total": "مجموع"
     },
     "traefik": {
-        "routers": "Routers",
-        "services": "Services",
-        "middleware": "Middleware"
+        "routers": "راوتر",
+        "services": "خدمات",
+        "middleware": "الوسيطة"
     },
     "npm": {
-        "enabled": "Enabled",
-        "disabled": "Disabled",
-        "total": "Total"
+        "enabled": "مفعل",
+        "disabled": "معطل",
+        "total": "مجموع"
     },
     "coinmarketcap": {
-        "configure": "Configure one or more crypto currencies to track",
-        "1hour": "1 Hour",
-        "1day": "1 Day",
-        "7days": "7 Days",
-        "30days": "30 Days"
+        "configure": "قم بأنشاء عملة تشفير واحدة أو أكثر للتتبع",
+        "1hour": "١ ساعة",
+        "1day": "١ يوم",
+        "7days": "٧ أيام",
+        "30days": "٣٠ يوم"
     },
     "gotify": {
-        "apps": "Applications",
-        "clients": "Clients",
-        "messages": "Messages"
+        "apps": "التطبيقات",
+        "clients": "العملاء",
+        "messages": "الرسائل"
     },
     "prowlarr": {
-        "enableIndexers": "Indexers",
+        "enableIndexers": "مفهرسات",
         "numberOfGrabs": "Grabs",
-        "numberOfQueries": "Queries",
+        "numberOfQueries": "الاستعلامات",
         "numberOfFailGrabs": "Fail Grabs",
-        "numberOfFailQueries": "Fail Queries"
+        "numberOfFailQueries": "فشل الاستعلامات"
     },
     "jackett": {
         "configured": "Configured",
-        "errored": "Errored"
+        "errored": "خطأ"
     },
     "strelaysrv": {
-        "numActiveSessions": "Sessions",
-        "numConnections": "Connections",
+        "numActiveSessions": "الجلسات",
+        "numConnections": "التوصيلات",
         "dataRelayed": "Relayed",
-        "transferRate": "Rate"
+        "transferRate": "معدل"
     },
     "mastodon": {
-        "user_count": "Users",
+        "user_count": "المستخدمين",
         "status_count": "Posts",
         "domain_count": "Domains"
     },
     "authentik": {
-        "users": "Users",
-        "loginsLast24H": "Logins (24h)",
-        "failedLoginsLast24H": "Failed Logins (24h)"
+        "users": "المستخدمين",
+        "loginsLast24H": "تسجيلات الدخول (٢٤س)",
+        "failedLoginsLast24H": "فشل تسجيلات الدخول (٢٤س)"
     },
     "proxmox": {
-        "mem": "MEM",
-        "cpu": "CPU",
+        "mem": "الذاكرة",
+        "cpu": "المعالج",
         "lxc": "LXC",
         "vms": "VMs"
     },
     "glances": {
-        "cpu": "CPU",
-        "mem": "MEM",
-        "wait": "Please wait"
+        "cpu": "معالج",
+        "mem": "الذاكرة",
+        "wait": "الرجاء الإنتظار"
     },
     "quicklaunch": {
-        "bookmark": "Bookmark",
-        "service": "Service"
+        "bookmark": "مفضلة",
+        "service": "خدمة"
     },
     "homebridge": {
-        "available_update": "System",
-        "updates": "Updates",
-        "update_available": "Update Available",
-        "up_to_date": "Up to Date",
+        "available_update": "نظام",
+        "updates": "تحديثات",
+        "update_available": "تحديث متاح",
+        "up_to_date": "حتى الآن",
         "child_bridges": "Child Bridges",
         "child_bridges_status": "{{ok}}/{{total}}"
     },
     "watchtower": {
         "containers_scanned": "Scanned",
-        "containers_updated": "Updated",
-        "containers_failed": "Failed"
+        "containers_updated": "محدث",
+        "containers_failed": "فشل"
     },
     "autobrr": {
-        "approvedPushes": "Approved",
-        "rejectedPushes": "Rejected",
-        "filters": "Filters",
-        "indexers": "Indexers"
+        "approvedPushes": "مصدق",
+        "rejectedPushes": "مرفوض",
+        "filters": "المرشحات",
+        "indexers": "مفهرسات"
     },
     "tubearchivist": {
-        "downloads": "Queue",
-        "videos": "Videos",
-        "channels": "Channels",
-        "playlists": "Playlists"
+        "downloads": "إنتظار",
+        "videos": "الفيديوهات",
+        "channels": "القنوات",
+        "playlists": "قوائم التشغيل"
     },
     "truenas": {
-        "load": "System Load",
-        "uptime": "Uptime",
-        "alerts": "Alerts",
+        "load": "حمل النظام",
+        "uptime": "مدة التشغيل",
+        "alerts": "تنبيهات",
         "time": "{{value, number(style: unit; unitDisplay: long;)}}"
     },
     "navidrome": {
         "nothing_streaming": "No Active Streams",
-        "please_wait": "Please Wait"
+        "please_wait": "الرجاء الإنتظار"
     },
     "pyload": {
-        "speed": "Speed",
-        "active": "Active",
-        "queue": "Queue",
-        "total": "Total"
+        "speed": "السرعة",
+        "active": "نشط",
+        "queue": "إنتظار",
+        "total": "مجموع"
     },
     "gluetun": {
-        "public_ip": "Public IP",
-        "region": "Region",
-        "country": "Country"
+        "public_ip": "العام IP",
+        "region": "منطقة",
+        "country": "الدولة"
     },
     "hdhomerun": {
-        "channels": "Channels",
+        "channels": "قنوات",
         "hd": "HD"
     },
     "ping": {
-        "error": "Error",
+        "error": "خطأ",
         "ping": "Ping"
     },
     "scrutiny": {
-        "passed": "Passed",
-        "failed": "Failed",
-        "unknown": "Unknown"
+        "passed": "إجتاز",
+        "failed": "فشل",
+        "unknown": "مجهول"
     },
     "paperlessngx": {
-        "inbox": "Inbox",
-        "total": "Total"
+        "inbox": "صندوق الوارد",
+        "total": "المجموع"
     },
     "deluge": {
-        "download": "Download",
-        "upload": "Upload",
+        "download": "تنزيل",
+        "upload": "تحميل",
         "leech": "Leech",
         "seed": "Seed"
     },
     "flood": {
-        "download": "Download",
-        "upload": "Upload",
+        "download": "التنزيل",
+        "upload": "التحميل",
         "leech": "Leech",
         "seed": "Seed"
     },
     "tdarr": {
-        "queue": "Queue",
-        "processed": "Processed",
-        "errored": "Errored",
-        "saved": "Saved"
+        "queue": "إنتظار",
+        "processed": "معالجة",
+        "errored": "خطأ",
+        "saved": "حفظ"
     },
     "miniflux": {
-        "read": "Read",
-        "unread": "Unread"
+        "read": "قراءة",
+        "unread": "غير مقروء"
     },
     "nextdns": {
-        "wait": "Please Wait",
-        "no_devices": "No Device Data Received"
+        "wait": "الرجاء الإنتظار",
+        "no_devices": "لم يتم استلام بيانات الجهاز"
     },
     "common": {
         "bibyterate": "{{value, rate(bits: false; binary: true)}}",
         "bibitrate": "{{value, rate(bits: true; binary: true)}}"
     },
     "omada": {
-        "connectedAp": "Connected APs",
-        "activeUser": "Active devices",
-        "alerts": "Alerts",
+        "connectedAp": "المتصلة APs",
+        "activeUser": "الأجهزة النشطة",
+        "alerts": "تنبيهات",
         "connectedGateway": "Connected gateways",
         "connectedSwitches": "Connected switches"
     },
     "downloadstation": {
-        "download": "Download",
-        "upload": "Upload",
+        "download": "تنزيل",
+        "upload": "تحميل",
         "leech": "Leech",
         "seed": "Seed"
     },
     "mikrotik": {
-        "cpuLoad": "CPU Load",
-        "memoryUsed": "Memory Used",
-        "uptime": "Uptime",
+        "cpuLoad": "حمل المعالج",
+        "memoryUsed": "الذاكرة الستخدمة",
+        "uptime": "مدة التشغيل",
         "numberOfLeases": "Leases"
     },
     "xteve": {
         "streams_all": "All Streams",
         "streams_active": "Active Streams",
-        "streams_xepg": "XEPG Channels"
+        "streams_xepg": "XEPG قنوات"
     },
     "opnsense": {
-        "cpu": "CPU Load",
-        "memory": "Active Memory",
-        "wanUpload": "WAN Upload",
-        "wanDownload": "WAN Download"
+        "cpu": "حمل المعالج",
+        "memory": "الذاكرة النشطة",
+        "wanUpload": "WAN التحميل",
+        "wanDownload": "WAN التنزيل"
     },
     "moonraker": {
-        "printer_state": "Printer State",
-        "print_status": "Print Status",
-        "print_progress": "Progress",
-        "layers": "Layers"
+        "printer_state": "حالة الطابعة",
+        "print_status": "حالة الطابعة",
+        "print_progress": "تقدم",
+        "layers": "طبقات"
     },
     "medusa": {
-        "wanted": "Wanted",
-        "queued": "Queued",
-        "series": "Series"
-    },
-    "octoPrint": {
-        "job_completion": "Completion"
+        "wanted": "مطلوب",
+        "queued": "في الإنتظار",
+        "series": "سلسلة"
     },
     "octoprint": {
-        "printer_state": "Status",
-        "temp_tool": "Tool temp",
-        "temp_bed": "Bed temp",
-        "job_completion": "Completion"
+        "printer_state": "حالة",
+        "temp_tool": "أداة درجة الحرارة",
+        "temp_bed": "درجة حرارة السرير",
+        "job_completion": "إتمام"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "حالة"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 16 - 3
public/locales/bg/common.json

@@ -415,13 +415,26 @@
         "queued": "Queued",
         "series": "Series"
     },
-    "octoPrint": {
-        "job_completion": "Completion"
-    },
     "octoprint": {
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "temp_bed": "Bed temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 17 - 4
public/locales/ca/common.json

@@ -184,7 +184,7 @@
         "failedLoginsLast24H": "Errors d'inici de sessió (24h)"
     },
     "proxmox": {
-        "vms": "Màquines Virtuals",
+        "vms": "VMs",
         "mem": "Memòria",
         "cpu": "Processador",
         "lxc": "LXC"
@@ -415,13 +415,26 @@
         "queued": "Queued",
         "series": "Series"
     },
-    "octoPrint": {
-        "job_completion": "Completion"
-    },
     "octoprint": {
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "temp_bed": "Bed temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 16 - 3
public/locales/cs/common.json

@@ -415,13 +415,26 @@
         "queued": "Queued",
         "series": "Series"
     },
-    "octoPrint": {
-        "job_completion": "Completion"
-    },
     "octoprint": {
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "temp_bed": "Bed temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 16 - 3
public/locales/da/common.json

@@ -415,13 +415,26 @@
         "queued": "Queued",
         "series": "Series"
     },
-    "octoPrint": {
-        "job_completion": "Completion"
-    },
     "octoprint": {
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "temp_bed": "Bed temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 16 - 3
public/locales/de/common.json

@@ -415,13 +415,26 @@
         "queued": "Queued",
         "series": "Series"
     },
-    "octoPrint": {
-        "job_completion": "Completion"
-    },
     "octoprint": {
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "temp_bed": "Bed temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "cpu_usage": "CPU",
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 16 - 0
public/locales/en/common.json

@@ -431,6 +431,22 @@
         "temp_bed": "Bed temp",
         "job_completion": "Completion"
     },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status" 
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
+    },
     "diskstation": {
         "days": "Days",
         "uptime": "Uptime",

+ 16 - 3
public/locales/eo/common.json

@@ -415,13 +415,26 @@
         "queued": "Queued",
         "series": "Series"
     },
-    "octoPrint": {
-        "job_completion": "Completion"
-    },
     "octoprint": {
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "temp_bed": "Bed temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 16 - 3
public/locales/es/common.json

@@ -415,13 +415,26 @@
         "queued": "A la espera",
         "series": "Serie"
     },
-    "octoPrint": {
-        "job_completion": "Conclusión"
-    },
     "octoprint": {
         "temp_bed": "Bed temp",
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "IP de origen",
+        "status": "Estado"
+    },
+    "proxmoxbackupserver": {
+        "cpu_usage": "CPU",
+        "datastore_usage": "Almacén de datos",
+        "failed_tasks_24h": "Tareas fallidas en 24h",
+        "memory_usage": "Memoria"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 21 - 8
public/locales/fi/common.json

@@ -194,7 +194,7 @@
         "uptime": "System Uptime",
         "lan_users": "LAN Users",
         "wlan_users": "WLAN Users",
-        "wait": "Please wait",
+        "wait": "Odota, ole hyvä",
         "days": "Days",
         "wan": "WAN",
         "up": "UP",
@@ -314,7 +314,7 @@
     },
     "navidrome": {
         "nothing_streaming": "No Active Streams",
-        "please_wait": "Please Wait"
+        "please_wait": "Odota, ole hyvä"
     },
     "pyload": {
         "speed": "Speed",
@@ -411,17 +411,30 @@
         "layers": "Layers"
     },
     "medusa": {
-        "wanted": "Wanted",
-        "queued": "Queued",
-        "series": "Series"
-    },
-    "octoPrint": {
-        "job_completion": "Completion"
+        "wanted": "Haluttu",
+        "queued": "Jonossa",
+        "series": "Sarja"
     },
     "octoprint": {
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "temp_bed": "Bed temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 17 - 4
public/locales/fr/common.json

@@ -415,13 +415,26 @@
         "queued": "En attente",
         "series": "Séries"
     },
-    "octoPrint": {
-        "job_completion": "Achèvement"
-    },
     "octoprint": {
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "temp_bed": "Bed temp",
-        "job_completion": "Completion"
+        "job_completion": "Achèvement"
+    },
+    "cloudflared": {
+        "origin_ip": "IP Publique",
+        "status": "Statut"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Tâches échouées 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Mémoire"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 16 - 3
public/locales/he/common.json

@@ -415,13 +415,26 @@
         "queued": "Queued",
         "series": "Series"
     },
-    "octoPrint": {
-        "job_completion": "Completion"
-    },
     "octoprint": {
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "temp_bed": "Bed temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 16 - 3
public/locales/hi/common.json

@@ -415,13 +415,26 @@
         "queued": "Queued",
         "series": "Series"
     },
-    "octoPrint": {
-        "job_completion": "Completion"
-    },
     "octoprint": {
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "temp_bed": "Bed temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 16 - 3
public/locales/hr/common.json

@@ -415,13 +415,26 @@
         "queued": "Queued",
         "series": "Series"
     },
-    "octoPrint": {
-        "job_completion": "Completion"
-    },
     "octoprint": {
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "temp_bed": "Bed temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 16 - 3
public/locales/hu/common.json

@@ -415,13 +415,26 @@
         "queued": "Queued",
         "series": "Series"
     },
-    "octoPrint": {
-        "job_completion": "Completion"
-    },
     "octoprint": {
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "temp_bed": "Bed temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 16 - 3
public/locales/it/common.json

@@ -415,13 +415,26 @@
         "queued": "Queued",
         "series": "Series"
     },
-    "octoPrint": {
-        "job_completion": "Completion"
-    },
     "octoprint": {
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "temp_bed": "Bed temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 16 - 3
public/locales/ja/common.json

@@ -415,13 +415,26 @@
         "queued": "Queued",
         "series": "Series"
     },
-    "octoPrint": {
-        "job_completion": "Completion"
-    },
     "octoprint": {
         "temp_bed": "Bed temp",
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 16 - 3
public/locales/lv/common.json

@@ -415,13 +415,26 @@
         "print_progress": "Progress",
         "layers": "Layers"
     },
-    "octoPrint": {
-        "job_completion": "Completion"
-    },
     "octoprint": {
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "temp_bed": "Bed temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 16 - 3
public/locales/ms/common.json

@@ -415,13 +415,26 @@
         "queued": "Queued",
         "series": "Series"
     },
-    "octoPrint": {
-        "job_completion": "Completion"
-    },
     "octoprint": {
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "temp_bed": "Bed temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 16 - 3
public/locales/nb-NO/common.json

@@ -415,13 +415,26 @@
         "queued": "Queued",
         "series": "Series"
     },
-    "octoPrint": {
-        "job_completion": "Completion"
-    },
     "octoprint": {
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "temp_bed": "Bed temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 16 - 3
public/locales/nl/common.json

@@ -415,13 +415,26 @@
         "queued": "Queued",
         "series": "Series"
     },
-    "octoPrint": {
-        "job_completion": "Completion"
-    },
     "octoprint": {
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "temp_bed": "Bed temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 16 - 3
public/locales/pl/common.json

@@ -415,13 +415,26 @@
         "queued": "Zakolejkowane",
         "series": "Seria"
     },
-    "octoPrint": {
-        "job_completion": "Ukończenie"
-    },
     "octoprint": {
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "temp_bed": "Bed temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 16 - 3
public/locales/pt-BR/common.json

@@ -415,13 +415,26 @@
         "queued": "Queued",
         "series": "Series"
     },
-    "octoPrint": {
-        "job_completion": "Completion"
-    },
     "octoprint": {
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "temp_bed": "Bed temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 16 - 3
public/locales/pt/common.json

@@ -424,13 +424,26 @@
         "queued": "Queued",
         "series": "Series"
     },
-    "octoPrint": {
-        "job_completion": "Completion"
-    },
     "octoprint": {
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "temp_bed": "Bed temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 16 - 3
public/locales/ro/common.json

@@ -415,13 +415,26 @@
         "queued": "Queued",
         "series": "Series"
     },
-    "octoPrint": {
-        "job_completion": "Completion"
-    },
     "octoprint": {
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "temp_bed": "Bed temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 16 - 3
public/locales/ru/common.json

@@ -415,13 +415,26 @@
         "queued": "Queued",
         "series": "Series"
     },
-    "octoPrint": {
-        "job_completion": "Completion"
-    },
     "octoprint": {
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "temp_bed": "Bed temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 16 - 3
public/locales/sr/common.json

@@ -415,13 +415,26 @@
         "queued": "Queued",
         "series": "Series"
     },
-    "octoPrint": {
-        "job_completion": "Completion"
-    },
     "octoprint": {
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "temp_bed": "Bed temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 16 - 3
public/locales/sv/common.json

@@ -415,13 +415,26 @@
         "queued": "Queued",
         "series": "Series"
     },
-    "octoPrint": {
-        "job_completion": "Completion"
-    },
     "octoprint": {
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "temp_bed": "Bed temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 16 - 3
public/locales/te/common.json

@@ -415,13 +415,26 @@
         "queued": "Queued",
         "series": "Series"
     },
-    "octoPrint": {
-        "job_completion": "Completion"
-    },
     "octoprint": {
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "temp_bed": "Bed temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 16 - 3
public/locales/tr/common.json

@@ -415,13 +415,26 @@
         "queued": "Queued",
         "series": "Series"
     },
-    "octoPrint": {
-        "job_completion": "Completion"
-    },
     "octoprint": {
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "temp_bed": "Bed temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 19 - 6
public/locales/uk/common.json

@@ -415,13 +415,26 @@
         "queued": "У черзі",
         "series": "Серії"
     },
-    "octoPrint": {
+    "octoprint": {
+        "printer_state": "Стан",
+        "temp_tool": "Температура інструменту",
+        "temp_bed": "Температура ліжка",
         "job_completion": "Завершення"
     },
-    "octoprint": {
-        "printer_state": "Status",
-        "temp_tool": "Tool temp",
-        "temp_bed": "Bed temp",
-        "job_completion": "Completion"
+    "cloudflared": {
+        "origin_ip": "Походження IP",
+        "status": "Стан"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 16 - 3
public/locales/vi/common.json

@@ -415,13 +415,26 @@
         "queued": "Queued",
         "series": "Series"
     },
-    "octoPrint": {
-        "job_completion": "Completion"
-    },
     "octoprint": {
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "temp_bed": "Bed temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 16 - 3
public/locales/yue/common.json

@@ -415,13 +415,26 @@
         "queued": "Queued",
         "series": "Series"
     },
-    "octoPrint": {
-        "job_completion": "Completion"
-    },
     "octoprint": {
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "temp_bed": "Bed temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 16 - 3
public/locales/zh-CN/common.json

@@ -415,13 +415,26 @@
         "queued": "Queued",
         "series": "Series"
     },
-    "octoPrint": {
-        "job_completion": "Completion"
-    },
     "octoprint": {
         "printer_state": "Status",
         "temp_tool": "Tool temp",
         "temp_bed": "Bed temp",
         "job_completion": "Completion"
+    },
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 19 - 6
public/locales/zh-Hant/common.json

@@ -415,13 +415,26 @@
         "queued": "已加入佇列",
         "series": "影集"
     },
-    "octoPrint": {
+    "octoprint": {
+        "printer_state": "狀態",
+        "temp_tool": "噴頭溫度",
+        "temp_bed": "平台溫度",
         "job_completion": "完成度"
     },
-    "octoprint": {
-        "printer_state": "Status",
-        "temp_tool": "Tool temp",
-        "temp_bed": "Bed temp",
-        "job_completion": "Completion"
+    "cloudflared": {
+        "origin_ip": "Origin IP",
+        "status": "Status"
+    },
+    "proxmoxbackupserver": {
+        "datastore_usage": "Datastore",
+        "failed_tasks_24h": "Failed Tasks 24h",
+        "cpu_usage": "CPU",
+        "memory_usage": "Memory"
+    },
+    "immich": {
+        "users": "Users",
+        "photos": "Photos",
+        "videos": "Videos",
+        "storage": "Storage"
     }
 }

+ 12 - 12
src/pages/api/docker/stats/[...service].js

@@ -1,16 +1,18 @@
 import Docker from "dockerode";
 
 import getDockerArguments from "utils/config/docker";
+import createLogger from "utils/logger";
+
+const logger = createLogger("dockerStatsService");
 
 export default async function handler(req, res) {
   const { service } = req.query;
   const [containerName, containerServer] = service;
 
   if (!containerName && !containerServer) {
-    res.status(400).send({
+    return res.status(400).send({
       error: "docker query parameters are required",
     });
-    return;
   }
 
   try {
@@ -23,10 +25,9 @@ export default async function handler(req, res) {
     // bad docker connections can result in a <Buffer ...> object?
     // in any case, this ensures the result is the expected array
     if (!Array.isArray(containers)) {
-      res.status(500).send({
+      return res.status(500).send({
         error: "query failed",
       });
-      return;
     }
 
     const containerNames = containers.map((container) => container.Names[0].replace(/^\//, ""));
@@ -36,10 +37,9 @@ export default async function handler(req, res) {
       const container = docker.getContainer(containerName);
       const stats = await container.stats({ stream: false });
 
-      res.status(200).json({
+      return res.status(200).json({
         stats,
       });
-      return;
     }
 
     // Try with a service deployed in Docker Swarm, if enabled
@@ -61,19 +61,19 @@ export default async function handler(req, res) {
         const container = docker.getContainer(taskContainerId);
         const stats = await container.stats({ stream: false });
 
-        res.status(200).json({
+        return res.status(200).json({
           stats,
         });
-        return;
       }
     }
 
-    res.status(200).send({
+    return res.status(200).send({
       error: "not found",
     });
-  } catch {
-    res.status(500).send({
-      error: {message: "Unknown error"},
+  } catch (e) {
+    logger.error(e);
+    return res.status(500).send({
+      error: {message: e?.message ?? "Unknown error"},
     });
   }
 }

+ 6 - 2
src/pages/api/docker/status/[...service].js

@@ -1,6 +1,9 @@
 import Docker from "dockerode";
 
 import getDockerArguments from "utils/config/docker";
+import createLogger from "utils/logger";
+
+const logger = createLogger("dockerStatusService");
 
 export default async function handler(req, res) {
   const { service } = req.query;
@@ -68,9 +71,10 @@ export default async function handler(req, res) {
     return res.status(200).send({
       error: "not found",
     });
-  } catch {
+  } catch (e) {
+    logger.error(e);
     return res.status(500).send({
-      error: "unknown error",
+      error: {message: e?.message ?? "Unknown error"},
     });
   }
 }

+ 13 - 1
src/utils/config/api-response.js

@@ -13,6 +13,17 @@ import {
 } from "utils/config/service-helpers";
 import { cleanWidgetGroups, widgetsFromConfig } from "utils/config/widget-helpers";
 
+/**
+ * Compares services by weight then by name.
+ */
+function compareServices(service1, service2) {
+  const comp = service1.weight - service2.weight;
+  if (comp !== 0) {
+    return comp;
+  }
+  return service1.name.localeCompare(service2.name);
+}
+
 export async function bookmarksResponse() {
   checkAndCopyConfig("bookmarks.yaml");
 
@@ -112,7 +123,8 @@ export async function servicesResponse() {
         ...discoveredDockerGroup.services,
         ...discoveredKubernetesGroup.services,
         ...configuredGroup.services
-      ].filter((service) => service),
+      ].filter((service) => service)
+        .sort(compareServices),
     };
 
     if (definedLayouts) {

+ 29 - 2
src/utils/config/service-helpers.js

@@ -33,6 +33,15 @@ export async function servicesFromConfig() {
     })),
   }));
 
+  // add default weight to services based on their position in the configuration
+  servicesArray.forEach((group, groupIndex) => {
+    group.services.forEach((service, serviceIndex) => {
+      if(!service.weight) {
+        servicesArray[groupIndex].services[serviceIndex].weight = (serviceIndex + 1) * 100;
+      }
+    });
+  });
+
   return servicesArray;
 }
 
@@ -152,6 +161,7 @@ export async function servicesFromKubernetes() {
         href: ingress.metadata.annotations[`${ANNOTATION_BASE}/href`] || getUrlFromIngress(ingress),
         name: ingress.metadata.annotations[`${ANNOTATION_BASE}/name`] || ingress.metadata.name,
         group: ingress.metadata.annotations[`${ANNOTATION_BASE}/group`] || "Kubernetes",
+        weight: ingress.metadata.annotations[`${ANNOTATION_BASE}/weight`] || '0',
         icon: ingress.metadata.annotations[`${ANNOTATION_BASE}/icon`] || '',
         description: ingress.metadata.annotations[`${ANNOTATION_BASE}/description`] || '',
       };
@@ -201,6 +211,17 @@ export function cleanServiceGroups(groups) {
     name: serviceGroup.name,
     services: serviceGroup.services.map((service) => {
       const cleanedService = { ...service };
+      if (typeof service.weight === 'string') {
+        const weight = parseInt(service.weight, 10);
+        if (Number.isNaN(weight)) {
+          cleanedService.weight = 0;
+        } else {
+          cleanedService.weight = weight;
+        }
+      }
+      if (typeof cleanedService.weight !== "number") {
+        cleanedService.weight = 0;
+      }
 
       if (cleanedService.widget) {
         // whitelisted set of keys to pass to the frontend
@@ -214,12 +235,15 @@ export function cleanServiceGroups(groups) {
           defaultinterval,
           namespace, // kubernetes widget
           app,
-          podSelector
+          podSelector,
+          wan // opnsense widget
         } = cleanedService.widget;
 
+        const fieldsList = typeof fields === 'string' ? JSON.parse(fields) : fields;
+
         cleanedService.widget = {
           type,
-          fields: fields || null,
+          fields: fieldsList || null,
           service_name: service.name,
           service_group: serviceGroup.name,
         };
@@ -237,6 +261,9 @@ export function cleanServiceGroups(groups) {
           if (app) cleanedService.widget.app = app;
           if (podSelector) cleanedService.widget.podSelector = podSelector;
         }
+        if (type === "opnsense") {
+          if (wan) cleanedService.widget.wan = wan;
+        }
       }
 
       return cleanedService;

+ 6 - 0
src/utils/proxy/handlers/credentialed.js

@@ -34,12 +34,18 @@ export default async function credentialedProxyHandler(req, res, map) {
         headers.Authorization = `Bearer ${widget.key}`;
       } else if (widget.type === "proxmox") {
         headers.Authorization = `PVEAPIToken=${widget.username}=${widget.password}`;
+      } else if (widget.type === "proxmoxbackupserver") {
+        delete headers["Content-Type"];
+        headers.Authorization = `PBSAPIToken=${widget.username}:${widget.password}`;
       } else if (widget.type === "autobrr") {
         headers["X-API-Token"] = `${widget.key}`;
       } else if (widget.type === "tubearchivist") {
         headers.Authorization = `Token ${widget.key}`;
       } else if (widget.type === "miniflux") {
         headers["X-Auth-Token"] = `${widget.key}`;
+      } else if (widget.type === "cloudflared") {
+        headers["X-Auth-Email"] = `${widget.email}`;
+        headers["X-Auth-Key"] = `${widget.key}`;
       } else {
         headers["X-API-Key"] = `${widget.key}`;
       }

+ 31 - 0
src/widgets/cloudflared/component.jsx

@@ -0,0 +1,31 @@
+import Container from "components/services/widget/container";
+import Block from "components/services/widget/block";
+import useWidgetAPI from "utils/proxy/use-widget-api";
+
+export default function Component({ service }) {
+  const { widget } = service;
+
+  const { data: statsData, error: statsError } = useWidgetAPI(widget, "cfd_tunnel");
+
+  if (statsError) {
+    return <Container error={statsError} />;
+  }
+
+  if (!statsData) {
+    return (
+      <Container service={service}>
+        <Block label="cloudflared.status" />
+        <Block label="cloudflared.origin_ip" />
+      </Container>
+    );
+  }
+
+  const originIP = statsData.result.connections?.origin_ip ?? statsData.result.connections[0]?.origin_ip;
+
+  return (
+    <Container service={service}>
+      <Block label="cloudflared.status" value={statsData.result.status.charAt(0).toUpperCase() + statsData.result.status.slice(1)} />
+      <Block label="cloudflared.origin_ip" value={originIP} />
+    </Container>
+  );
+}

+ 18 - 0
src/widgets/cloudflared/widget.js

@@ -0,0 +1,18 @@
+import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
+
+const widget = {
+  api: "https://api.cloudflare.com/client/v4/accounts/{accountid}/{endpoint}/{tunnelid}",
+  proxyHandler: credentialedProxyHandler,
+
+  mappings: {
+    "cfd_tunnel": {
+      endpoint: "cfd_tunnel",
+      validate: [
+        "success",
+        "result"
+      ]
+    },
+  },
+};
+
+export default widget;

+ 3 - 0
src/widgets/components.js

@@ -6,6 +6,7 @@ const components = {
   autobrr: dynamic(() => import("./autobrr/component")),
   bazarr: dynamic(() => import("./bazarr/component")),
   changedetectionio: dynamic(() => import("./changedetectionio/component")),
+  cloudflared: dynamic(() => import("./cloudflared/component")),
   coinmarketcap: dynamic(() => import("./coinmarketcap/component")),
   deluge: dynamic(() => import("./deluge/component")),
   diskstation: dynamic(() => import("./diskstation/component")),
@@ -37,6 +38,7 @@ const components = {
   opnsense: dynamic(() => import("./opnsense/component")),
   overseerr: dynamic(() => import("./overseerr/component")),
   paperlessngx: dynamic(() => import("./paperlessngx/component")),
+  proxmoxbackupserver: dynamic(() => import("./proxmoxbackupserver/component")),
   pihole: dynamic(() => import("./pihole/component")),
   plex: dynamic(() => import("./plex/component")),
   portainer: dynamic(() => import("./portainer/component")),
@@ -61,6 +63,7 @@ const components = {
   unifi: dynamic(() => import("./unifi/component")),
   watchtower: dynamic(() => import("./watchtower/component")),
   xteve: dynamic(() => import("./xteve/component")),
+  immich: dynamic(() => import("./immich/component")),
 };
 
 export default components;

+ 33 - 0
src/widgets/immich/component.jsx

@@ -0,0 +1,33 @@
+import Container from "components/services/widget/container";
+import Block from "components/services/widget/block";
+import useWidgetAPI from "utils/proxy/use-widget-api";
+
+export default function Component({ service }) {
+  const { widget } = service;
+
+  const { data: immichData, error: immichError } = useWidgetAPI(widget);
+
+  if (immichError || immichData?.statusCode === 401) {
+    return <Container error={immichError ?? immichData} />;
+  }
+
+  if (!immichData) {
+    return (
+      <Container service={service}>
+        <Block label="immich.users" />
+        <Block label="immich.photos" />
+        <Block label="immich.videos" />
+        <Block label="immich.storage" />
+      </Container>
+    );
+  }
+
+  return (
+    <Container service={service}>
+      <Block label="immich.users" value={immichData.usageByUser.length} />
+      <Block label="immich.photos" value={immichData.photos} />
+      <Block label="immich.videos" value={immichData.videos} />
+      <Block label="immich.storage" value={immichData.usage} />
+    </Container>
+  );
+}

+ 8 - 0
src/widgets/immich/widget.js

@@ -0,0 +1,8 @@
+import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
+
+const widget = {
+  api: "{url}/api/server-info/stats",
+  proxyHandler: credentialedProxyHandler,
+};
+
+export default widget;

+ 1 - 1
src/widgets/nzbget/component.jsx

@@ -27,7 +27,7 @@ export default function Component({ service }) {
 
   return (
     <Container service={service}>
-      <Block label="nzbget.rate" value={t("common.bitrate", { value: statusData.DownloadRate })} />
+      <Block label="nzbget.rate" value={t("common.byterate", { value: statusData.DownloadRate })} />
       <Block
         label="nzbget.remaining"
         value={t("common.bytes", { value: statusData.RemainingSizeMB * 1024 * 1024 })}

+ 3 - 5
src/widgets/opnsense/component.jsx

@@ -33,16 +33,14 @@ export default function Component({ service }) {
   const cpu = 100 - parseFloat(cpuIdle);
   const memory = activityData.headers[3].match(/Mem: (.+) Active,/)[1];
 
-  const wanUpload = interfaceData.interfaces.wan['bytes transmitted'];
-  const wanDownload = interfaceData.interfaces.wan['bytes received'];
+  const wan = widget.wan ? interfaceData.interfaces[widget.wan] : interfaceData.interfaces.wan;
 
   return (
     <Container service={service}>
       <Block label="opnsense.cpu" value={t("common.percent", { value: cpu.toFixed(2) })}  />
       <Block label="opnsense.memory" value={memory} />
-      <Block label="opnsense.wanUpload" value={t("common.bytes", { value: wanUpload })} />
-      <Block label="opnsense.wanDownload" value={t("common.bytes", { value: wanDownload })} />
-
+      {wan && <Block label="opnsense.wanUpload" value={t("common.bytes", { value: wan['bytes transmitted'] })} />}
+      {wan && <Block label="opnsense.wanDownload" value={t("common.bytes", { value: wan['bytes received'] })} />}
     </Container>
   );
 }

+ 2 - 2
src/widgets/proxmox/component.jsx

@@ -31,8 +31,8 @@ export default function Component({ service }) {
   }
 
   const { data } = clusterData ;
-  const vms = data.filter(item => item.type === "qemu") || [];
-  const lxc = data.filter(item => item.type === "lxc") || [];
+  const vms = data.filter(item => item.type === "qemu" && item.template === 0) || [];
+  const lxc = data.filter(item => item.type === "lxc" && item.template === 0) || [];
   const nodes = data.filter(item => item.type === "node") || [];
 
   const runningVMs = vms.reduce(calcRunning, 0);

+ 45 - 0
src/widgets/proxmoxbackupserver/component.jsx

@@ -0,0 +1,45 @@
+import { useTranslation } from "next-i18next";
+
+import Container from "components/services/widget/container";
+import Block from "components/services/widget/block";
+import useWidgetAPI from "utils/proxy/use-widget-api";
+
+export default function Component({ service }) {
+  const { t } = useTranslation();
+
+  const { widget } = service;
+
+  const { data: datastoreData, error: datastoreError } = useWidgetAPI(widget, "status/datastore-usage");
+  const { data: tasksData, error: tasksError } = useWidgetAPI(widget, "nodes/localhost/tasks");
+  const { data: hostData, error: hostError } = useWidgetAPI(widget, "nodes/localhost/status");
+
+  if (datastoreError || tasksError || hostError) {
+    const finalError = tasksError ?? datastoreError ?? hostError;
+    return <Container error={finalError} />;
+  }
+
+  if (!datastoreData || !tasksData || !hostData) {
+    return (
+      <Container service={service}>
+        <Block label="proxmoxbackupserver.datastore_usage" />
+        <Block label="proxmoxbackupserver.failed_tasks" />
+        <Block label="proxmoxbackupserver.cpu_usage" />
+        <Block label="proxmoxbackupserver.memory_usage" />
+      </Container>
+    );
+  }
+
+  const datastoreUsage = datastoreData.data[0].used / datastoreData.data[0].total * 100;
+  const cpuUsage = hostData.data.cpu * 100;
+  const memoryUsage = hostData.data.memory.used / hostData.data.memory.total * 100;
+  const failedTasks = tasksData.total >= 100 ? "99+" : tasksData.total;
+
+  return (
+    <Container service={service}>
+      <Block label="proxmoxbackupserver.datastore_usage" value={t("common.percent", { value: datastoreUsage })} />
+      <Block label="proxmoxbackupserver.failed_tasks_24h" value={failedTasks} />
+      <Block label="proxmoxbackupserver.cpu_usage" value={t("common.percent", { value: cpuUsage })} />
+      <Block label="proxmoxbackupserver.memory_usage" value={t("common.percent", { value: memoryUsage })} />
+    </Container>
+  );
+}

+ 22 - 0
src/widgets/proxmoxbackupserver/widget.js

@@ -0,0 +1,22 @@
+import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
+
+const since = Date.now() - (24 * 60 * 60 * 1000);
+
+const widget = {
+  api: "{url}/api2/json/{endpoint}",
+  proxyHandler: credentialedProxyHandler,
+
+  mappings: {
+    "status/datastore-usage": {
+      endpoint: "status/datastore-usage",
+    },
+    "nodes/localhost/tasks": {
+      endpoint: `nodes/localhost/tasks?errors=true&limit=100&since=${since}`,
+    },
+    "nodes/localhost/status": {
+      endpoint: "nodes/localhost/status",
+    },
+  },
+};
+
+export default widget;

+ 6 - 0
src/widgets/widgets.js

@@ -3,6 +3,7 @@ import authentik from "./authentik/widget";
 import autobrr from "./autobrr/widget";
 import bazarr from "./bazarr/widget";
 import changedetectionio from "./changedetectionio/widget";
+import cloudflared from "./cloudflared/widget";
 import coinmarketcap from "./coinmarketcap/widget";
 import deluge from "./deluge/widget";
 import diskstation from "./diskstation/widget";
@@ -31,6 +32,7 @@ import ombi from "./ombi/widget";
 import opnsense from "./opnsense/widget";
 import overseerr from "./overseerr/widget";
 import paperlessngx from "./paperlessngx/widget";
+import proxmoxbackupserver from "./proxmoxbackupserver/widget";
 import pihole from "./pihole/widget";
 import plex from "./plex/widget";
 import portainer from "./portainer/widget";
@@ -55,6 +57,7 @@ import truenas from "./truenas/widget";
 import unifi from "./unifi/widget";
 import watchtower from "./watchtower/widget";
 import xteve from "./xteve/widget";
+import immich from "./immich/widget";
 
 const widgets = {
   adguard,
@@ -62,6 +65,7 @@ const widgets = {
   autobrr,
   bazarr,
   changedetectionio,
+  cloudflared,
   coinmarketcap,
   deluge,
   diskstation,
@@ -91,6 +95,7 @@ const widgets = {
   opnsense,
   overseerr,
   paperlessngx,
+  proxmoxbackupserver,
   pihole,
   plex,
   portainer,
@@ -116,6 +121,7 @@ const widgets = {
   unifi_console: unifi,
   watchtower,
   xteve,
+  immich,
 };
 
 export default widgets;