Browse Source

Merge remote-tracking branch 'origin/main' into pyload_widget

stuffinator 2 years ago
parent
commit
d6f53ab1e9
46 changed files with 1591 additions and 205 deletions
  1. 10 0
      .all-contributorsrc
  2. 9 9
      .github/workflows/docker-publish.yml
  3. 3 3
      Dockerfile
  4. 8 44
      README.md
  5. 308 0
      public/locales/ar/common.json
  6. 12 0
      public/locales/bg/common.json
  7. 12 0
      public/locales/ca/common.json
  8. 308 0
      public/locales/cs/common.json
  9. 308 0
      public/locales/da/common.json
  10. 12 0
      public/locales/de/common.json
  11. 12 0
      public/locales/en/common.json
  12. 15 3
      public/locales/es/common.json
  13. 12 0
      public/locales/fi/common.json
  14. 12 0
      public/locales/fr/common.json
  15. 12 0
      public/locales/he/common.json
  16. 116 104
      public/locales/hr/common.json
  17. 12 0
      public/locales/hu/common.json
  18. 12 0
      public/locales/it/common.json
  19. 12 0
      public/locales/nb-NO/common.json
  20. 12 0
      public/locales/nl/common.json
  21. 12 0
      public/locales/pl/common.json
  22. 12 0
      public/locales/pt-BR/common.json
  23. 12 0
      public/locales/pt/common.json
  24. 12 0
      public/locales/ro/common.json
  25. 12 0
      public/locales/ru/common.json
  26. 12 0
      public/locales/sr/common.json
  27. 12 0
      public/locales/sv/common.json
  28. 12 0
      public/locales/te/common.json
  29. 12 0
      public/locales/tr/common.json
  30. 12 0
      public/locales/vi/common.json
  31. 12 0
      public/locales/yue/common.json
  32. 12 0
      public/locales/zh-CN/common.json
  33. 12 0
      public/locales/zh-Hant/common.json
  34. 7 1
      src/components/bookmarks/item.jsx
  35. 2 2
      src/components/quicklaunch.jsx
  36. 37 0
      src/components/resolvedicon.jsx
  37. 9 1
      src/components/services/group.jsx
  38. 5 37
      src/components/services/item.jsx
  39. 2 0
      src/utils/proxy/handlers/credentialed.js
  40. 2 0
      src/widgets/components.js
  41. 1 1
      src/widgets/emby/component.jsx
  42. 65 0
      src/widgets/truenas/component.jsx
  43. 21 0
      src/widgets/truenas/widget.js
  44. 40 0
      src/widgets/tubearchivist/component.jsx
  45. 23 0
      src/widgets/tubearchivist/widget.js
  46. 4 0
      src/widgets/widgets.js

+ 10 - 0
.all-contributorsrc

@@ -0,0 +1,10 @@
+{
+  "projectName": "homepage",
+  "projectOwner": "benphelps",
+  "repoType": "github",
+  "repoHost": "https://github.com",
+  "files": ["README.md"],
+  "imageSize": 50,
+  "contributorsSortAlphabetically": true,
+  "contributors": []
+}

+ 9 - 9
.github/workflows/docker-publish.yml

@@ -43,7 +43,7 @@ jobs:
         if: github.event_name != 'pull_request'
         uses: sigstore/cosign-installer@main
         with:
-          cosign-release: 'v1.11.0' # optional
+          cosign-release: 'v1.13.1' # optional
 
       # Setup QEMU
       # https://github.com/marketplace/actions/docker-setup-buildx#with-qemu
@@ -100,7 +100,7 @@ jobs:
             REVISION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }}
           # https://github.com/docker/setup-qemu-action#about
           # platforms: linux/amd64,linux/arm64,linux/riscv64,linux/ppc64le,linux/s390x,linux/386,linux/mips64le,linux/mips64,linux/arm/v7,linux/arm/v6
-          platforms: linux/amd64,linux/arm64,linux/arm/v7
+          platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6
           cache-from: type=local,src=/tmp/.buildx-cache
           cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
 
@@ -109,13 +109,13 @@ jobs:
       # repository is public to avoid leaking data.  If you would like to publish
       # transparency data even for private images, pass --force to cosign below.
       # https://github.com/sigstore/cosign
-      - name: Sign the published Docker image
-        if: ${{ github.event_name != 'pull_request' }}
-        env:
-          COSIGN_EXPERIMENTAL: "true"
-        # This step uses the identity token to provision an ephemeral certificate
-        # against the sigstore community Fulcio instance.
-        run: echo "${{ steps.meta.outputs.tags }}" | xargs -I {} cosign sign {}@${{ steps.build-and-push.outputs.digest }}
+#       - name: Sign the published Docker image
+#         if: ${{ github.event_name != 'pull_request' }}
+#         env:
+#           COSIGN_EXPERIMENTAL: "true"
+#         # This step uses the identity token to provision an ephemeral certificate
+#         # against the sigstore community Fulcio instance.
+#         run: echo "${{ steps.meta.outputs.tags }}" | xargs -I {} cosign sign {}@${{ steps.build-and-push.outputs.digest }}
 
       # Temp fix
       # https://github.com/docker/build-push-action/issues/252

+ 3 - 3
Dockerfile

@@ -1,7 +1,7 @@
 # syntax = docker/dockerfile:latest
 
 # Install dependencies only when needed
-FROM node:current-alpine AS deps
+FROM docker.io/node:18-alpine AS deps
 
 WORKDIR /app
 
@@ -19,7 +19,7 @@ RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store pnpm f
 RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store pnpm install -r --offline
 
 # Rebuild the source code only when needed
-FROM node:current-alpine AS builder
+FROM docker.io/node:18-alpine AS builder
 WORKDIR /app
 
 ARG BUILDTIME
@@ -37,7 +37,7 @@ RUN <<EOF
 EOF
 
 # Production image, copy all the files and run next
-FROM node:current-alpine AS runner
+FROM docker.io/node:18-alpine AS runner
 LABEL org.opencontainers.image.title "Homepage"
 LABEL org.opencontainers.image.description "A self-hosted services landing page, with docker and service integrations."
 LABEL org.opencontainers.image.url="https://github.com/benphelps/homepage"

+ 8 - 44
README.md

@@ -144,47 +144,11 @@ This is a [Next.js](https://nextjs.org/) application, see their doucmentation fo
 
 ## Contributors
 
-Huge thanks to the all the contributors who have helped make this project what it is today! In alphabetical order:
-
-- [aidenpwnz](https://github.com/benphelps/homepage/commits?author=aidenpwnz) - Nginx Proxy Manager, Search Bar Widget
-- [AlexFullmoon](https://github.com/benphelps/homepage/commits?author=AlexFullmoon) - OpenWeatherMap Widget
-- [andrii-kryvoviaz](https://github.com/benphelps/homepage/commits?author=andrii-kryvoviaz) - Background opacity option
-- [DevPGSV](https://github.com/benphelps/homepage/commits?author=DevPGSV) - Syncthing Relay Server & Mastodon widgets
-- [ilusi0n](https://github.com/benphelps/homepage/commits?author=ilusi0n) - Jellyseerr Integration
-- [ItsJustMeChris](https://github.com/benphelps/homepage/commits?author=ItsJustMeChris) - Coin Market Cap Widget
-- [JazzFisch](https://github.com/benphelps/homepage/commits?author=JazzFisch) - Readarr, Bazarr, Lidarr, SABnzbd, Transmission, qBittorrent, Proxmox Integrations & countless more improvements
-- [josways](https://github.com/benphelps/homepage/commits?author=josways) - Baidu search provider
-- [mauricio-kalil](https://github.com/benphelps/homepage/commits?author=mauricio-kalil) - Portuguese (Brazil)
-- [modem7](https://github.com/benphelps/homepage/commits?author=modem7) - Impvoed Docker Image
-- [MountainGod2](https://github.com/benphelps/homepage/discussions/243) - Homepage Logo
-- [quod](https://github.com/benphelps/homepage/commits?author=quod) - Fixed Typos
-- [schklom](https://github.com/benphelps/homepage/commits?author=schklom) - ARM64, ARMv7 and ARMv6
-- [xicopitz](https://github.com/benphelps/homepage/commits?author=xicopitz) - Gotify & Prowlarr Integration
-
-### Translators
-
-- [3vilson](https://github.com/benphelps/homepage/commits?author=3vilson) - German
-- [4lenz1](https://github.com/benphelps/homepage/commits?author=4lenz1) - Chinese
-- [AmadeusGraves](https://github.com/benphelps/homepage/commits?author=AmadeusGraves) - Spanish
-- [boerniee](https://github.com/benphelps/homepage/commits?author=boerniee) - German
-- [brunoccr](https://github.com/benphelps/homepage/commits?author=brunoccr) - Portuguese (Brazil)
-- [C8opmBM](https://github.com/benphelps/homepage/commits?author=C8opmBM) - Romainian
-- [comradekingu](https://github.com/benphelps/homepage/commits?author=comradekingu) - Norwegian Bokmål
-- Daniel Varga - German & Hungarian
-- [deffcolony](https://github.com/benphelps/homepage/commits?author=deffcolony) - Dutch
-- [desolaris](https://github.com/benphelps/homepage/commits?author=desolaris) - Russian
-- [ericlokz](https://github.com/benphelps/homepage/commits?author=ericlokz) - Yue
-- [FunsKiTo](https://github.com/benphelps/homepage/commits?author=FunsKiTo) - Spanish
-- [jackblk](https://github.com/benphelps/homepage/commits?author=jackblk) - Vietnamese
-- [juanmanuelbc](https://github.com/benphelps/homepage/commits?author=juanmanuelbc) - Spanish and Catalan
-- [ling0412](https://github.com/benphelps/homepage/commits?author=ling0412) - Chinese
-- [milotype](https://github.com/benphelps/homepage/commits?author=milotype) - Croatian
-- [nicedc](https://github.com/benphelps/homepage/commits?author=nicedc) - Chinese
-- [Nonoss117](https://github.com/benphelps/homepage/commits?author=Nonoss117) - French
-- [pacoculebras](https://github.com/benphelps/homepage/commits?author=pacoculebras) - Catalan
-- [Prilann](https://github.com/benphelps/homepage/commits?author=Prilann) - German
-- [psychodracon](https://github.com/benphelps/homepage/commits?author=psychodracon) - Polish
-- Sascha Jelinek - German
-- [ShlomiPorush](https://github.com/benphelps/homepage/commits?author=ShlomiPorush) - Hebrew
-- [SuperDOS](https://github.com/benphelps/homepage/commits?author=SuperDOS) - Swedish
-- [kaihu](https://github.com/benphelps/homepage/commits?author=kaihu) - Finnish
+<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
+<!-- prettier-ignore-start -->
+<!-- markdownlint-disable -->
+
+<!-- markdownlint-restore -->
+<!-- prettier-ignore-end -->
+
+<!-- ALL-CONTRIBUTORS-LIST:END -->

+ 308 - 0
public/locales/ar/common.json

@@ -0,0 +1,308 @@
+{
+    "widget": {
+        "missing_type": "نوع القطعة مفقود: {{type}}",
+        "api_error": "API خطأ",
+        "status": "الحالة"
+    },
+    "weather": {
+        "current": "الموقع الحالي",
+        "allow": "اضغط للسماح",
+        "updating": "جاري التحديث",
+        "wait": "الرجاء الانتظار"
+    },
+    "search": {
+        "placeholder": "بحث …"
+    },
+    "resources": {
+        "cpu": "المعالج",
+        "total": "المجموع",
+        "free": "متاح",
+        "used": "مستخدم",
+        "load": "الضغط"
+    },
+    "unifi": {
+        "users": "المستخدمون",
+        "uptime": "مدة تشغيل النظام",
+        "days": "ايام",
+        "wan": "WAN",
+        "lan": "LAN",
+        "wlan": "WLAN",
+        "devices": "الاجهزة",
+        "lan_devices": "LAN اجهزة",
+        "wlan_devices": "WLAN احهزة",
+        "lan_users": "LAN مستخدمين",
+        "wlan_users": "WLAN مستخدمين",
+        "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"
+    },
+    "docker": {
+        "rx": "RX",
+        "tx": "TX",
+        "mem": "الرام",
+        "cpu": "المعالج",
+        "offline": "غير متصل"
+    },
+    "emby": {
+        "playing": "يعمل الان",
+        "transcoding": "التحويل",
+        "bitrate": "معدل البت",
+        "no_active": "No Active Streams"
+    },
+    "changedetectionio": {
+        "totalObserved": "Total Observed",
+        "diffsDetected": "Diffs Detected"
+    },
+    "tautulli": {
+        "playing": "Playing",
+        "transcoding": "Transcoding",
+        "bitrate": "Bitrate",
+        "no_active": "No Active Streams"
+    },
+    "nzbget": {
+        "rate": "Rate",
+        "remaining": "Remaining",
+        "downloaded": "Downloaded"
+    },
+    "plex": {
+        "streams": "Active Streams",
+        "movies": "Movies",
+        "tv": "TV Shows"
+    },
+    "sabnzbd": {
+        "rate": "Rate",
+        "queue": "Queue",
+        "timeleft": "Time Left"
+    },
+    "rutorrent": {
+        "active": "Active",
+        "upload": "Upload",
+        "download": "Download"
+    },
+    "transmission": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "qbittorrent": {
+        "download": "Download",
+        "upload": "Upload",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "sonarr": {
+        "wanted": "Wanted",
+        "queued": "Queued",
+        "series": "Series"
+    },
+    "radarr": {
+        "wanted": "Wanted",
+        "missing": "Missing",
+        "queued": "Queued",
+        "movies": "Movies"
+    },
+    "lidarr": {
+        "wanted": "Wanted",
+        "queued": "Queued",
+        "albums": "Albums"
+    },
+    "readarr": {
+        "wanted": "Wanted",
+        "queued": "Queued",
+        "books": "Books"
+    },
+    "bazarr": {
+        "missingEpisodes": "Missing Episodes",
+        "missingMovies": "Missing Movies"
+    },
+    "ombi": {
+        "pending": "Pending",
+        "approved": "Approved",
+        "available": "Available"
+    },
+    "jellyseerr": {
+        "pending": "Pending",
+        "approved": "Approved",
+        "available": "Available"
+    },
+    "overseerr": {
+        "pending": "Pending",
+        "approved": "Approved",
+        "available": "Available"
+    },
+    "pihole": {
+        "queries": "Queries",
+        "blocked": "Blocked",
+        "gravity": "Gravity"
+    },
+    "adguard": {
+        "queries": "Queries",
+        "blocked": "Blocked",
+        "filtered": "Filtered",
+        "latency": "Latency"
+    },
+    "speedtest": {
+        "upload": "Upload",
+        "download": "Download",
+        "ping": "Ping"
+    },
+    "portainer": {
+        "running": "Running",
+        "stopped": "Stopped",
+        "total": "Total"
+    },
+    "traefik": {
+        "routers": "Routers",
+        "services": "Services",
+        "middleware": "Middleware"
+    },
+    "npm": {
+        "enabled": "Enabled",
+        "disabled": "Disabled",
+        "total": "Total"
+    },
+    "coinmarketcap": {
+        "configure": "Configure one or more crypto currencies to track",
+        "1hour": "1 Hour",
+        "1day": "1 Day",
+        "7days": "7 Days",
+        "30days": "30 Days"
+    },
+    "gotify": {
+        "apps": "Applications",
+        "clients": "Clients",
+        "messages": "Messages"
+    },
+    "prowlarr": {
+        "enableIndexers": "Indexers",
+        "numberOfGrabs": "Grabs",
+        "numberOfQueries": "Queries",
+        "numberOfFailGrabs": "Fail Grabs",
+        "numberOfFailQueries": "Fail Queries"
+    },
+    "jackett": {
+        "configured": "Configured",
+        "errored": "Errored"
+    },
+    "strelaysrv": {
+        "numActiveSessions": "Sessions",
+        "numConnections": "Connections",
+        "dataRelayed": "Relayed",
+        "transferRate": "Rate"
+    },
+    "mastodon": {
+        "user_count": "Users",
+        "status_count": "Posts",
+        "domain_count": "Domains"
+    },
+    "authentik": {
+        "users": "Users",
+        "loginsLast24H": "Logins (24h)",
+        "failedLoginsLast24H": "Failed Logins (24h)"
+    },
+    "proxmox": {
+        "mem": "MEM",
+        "cpu": "CPU",
+        "lxc": "LXC",
+        "vms": "VMs"
+    },
+    "glances": {
+        "cpu": "CPU",
+        "mem": "MEM",
+        "wait": "Please wait"
+    },
+    "quicklaunch": {
+        "bookmark": "Bookmark",
+        "service": "Service"
+    },
+    "homebridge": {
+        "available_update": "System",
+        "updates": "Updates",
+        "update_available": "Update Available",
+        "up_to_date": "Up to Date",
+        "child_bridges": "Child Bridges",
+        "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "autobrr": {
+        "approvedPushes": "Approved",
+        "rejectedPushes": "Rejected",
+        "filters": "Filters",
+        "indexers": "Indexers"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    }
+}

+ 12 - 0
public/locales/bg/common.json

@@ -292,5 +292,17 @@
         "containers_scanned": "Scanned",
         "containers_updated": "Updated",
         "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
     }
 }

+ 12 - 0
public/locales/ca/common.json

@@ -292,5 +292,17 @@
         "containers_scanned": "Scanned",
         "containers_updated": "Updated",
         "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
     }
 }

+ 308 - 0
public/locales/cs/common.json

@@ -0,0 +1,308 @@
+{
+    "tubearchivist": {
+        "videos": "Videa",
+        "channels": "Kanály",
+        "playlists": "Playlisty",
+        "downloads": "Fronta"
+    },
+    "truenas": {
+        "load": "Vytížení systému",
+        "uptime": "Doba spuštění",
+        "alerts": "Upozornění",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
+    "widget": {
+        "missing_type": "Chybí typ widgetu: {{type}}",
+        "api_error": "Chyba API",
+        "status": "Status"
+    },
+    "weather": {
+        "current": "Aktuální poloha",
+        "allow": "Klikni pro povolení",
+        "updating": "Probíhá aktualizace",
+        "wait": "Počkejte prosím"
+    },
+    "search": {
+        "placeholder": "Hledat…"
+    },
+    "resources": {
+        "cpu": "CPU",
+        "total": "Celkem",
+        "free": "Volné",
+        "used": "Využité",
+        "load": "Vytížení"
+    },
+    "unifi": {
+        "users": "Uživatelé",
+        "uptime": "Čas od startu systému",
+        "days": "Dnů",
+        "wan": "WAN",
+        "lan": "LAN",
+        "wlan": "WLAN",
+        "devices": "Zařízení",
+        "lan_devices": "LAN Zařízení",
+        "wlan_devices": "WLAN Zařízení",
+        "lan_users": "LAN Uživatelé",
+        "wlan_users": "WLAN Uživatelé",
+        "up": "BĚŽÍ",
+        "down": "NEBĚŽÍ",
+        "wait": "Počkejte prosím"
+    },
+    "docker": {
+        "rx": "RX",
+        "tx": "TX",
+        "mem": "RAM",
+        "cpu": "CPU",
+        "offline": "Offline"
+    },
+    "emby": {
+        "playing": "Přehrává",
+        "transcoding": "Transkódování",
+        "bitrate": "Bitrate",
+        "no_active": "Žádný aktivní stream"
+    },
+    "changedetectionio": {
+        "totalObserved": "Celkem zjištěno",
+        "diffsDetected": "Rozdíly detekovány"
+    },
+    "tautulli": {
+        "playing": "Přehrává",
+        "transcoding": "Transkódování",
+        "bitrate": "Bitrate",
+        "no_active": "Žádný aktivní stream"
+    },
+    "nzbget": {
+        "rate": "Rychlost",
+        "remaining": "Zbývá",
+        "downloaded": "Staženo"
+    },
+    "plex": {
+        "streams": "Aktivní streamy",
+        "movies": "Filmy",
+        "tv": "Seriály"
+    },
+    "sabnzbd": {
+        "rate": "Rychlost",
+        "queue": "Fronta",
+        "timeleft": "Zbývající čas"
+    },
+    "rutorrent": {
+        "active": "Aktivní",
+        "upload": "Nahrávání",
+        "download": "Stahování"
+    },
+    "transmission": {
+        "download": "Stahování",
+        "upload": "Nahrávání",
+        "leech": "Leecher",
+        "seed": "Seeder"
+    },
+    "qbittorrent": {
+        "download": "Stahování",
+        "upload": "Nahrávání",
+        "leech": "Leecher",
+        "seed": "Seeder"
+    },
+    "sonarr": {
+        "wanted": "Hledaný",
+        "queued": "Ve frontě",
+        "series": "Seriály"
+    },
+    "radarr": {
+        "wanted": "Hledaný",
+        "missing": "Chybějící",
+        "queued": "Ve frontě",
+        "movies": "Filmy"
+    },
+    "lidarr": {
+        "wanted": "Hledaný",
+        "queued": "Ve frontě",
+        "albums": "Alba"
+    },
+    "readarr": {
+        "wanted": "Hledaný",
+        "queued": "Ve frontě",
+        "books": "Knihy"
+    },
+    "bazarr": {
+        "missingEpisodes": "Chybějící epizody",
+        "missingMovies": "Chybějící filmy"
+    },
+    "ombi": {
+        "pending": "Čeká",
+        "approved": "Schváleno",
+        "available": "Dostupný"
+    },
+    "jellyseerr": {
+        "pending": "Čeká",
+        "approved": "Schváleno",
+        "available": "Dostupný"
+    },
+    "overseerr": {
+        "pending": "Čeká",
+        "approved": "Schváleno",
+        "available": "Dostupný"
+    },
+    "pihole": {
+        "queries": "Dotazy",
+        "blocked": "Blokováno",
+        "gravity": "Gravitace"
+    },
+    "adguard": {
+        "queries": "Dotazy",
+        "blocked": "Blokováno",
+        "filtered": "Filtrováno",
+        "latency": "Odezva"
+    },
+    "speedtest": {
+        "upload": "Nahrávání",
+        "download": "Stahování",
+        "ping": "Ping"
+    },
+    "portainer": {
+        "running": "Běží",
+        "stopped": "Zastaveno",
+        "total": "Celkově"
+    },
+    "traefik": {
+        "routers": "Routery",
+        "services": "Služby",
+        "middleware": "Prostředník"
+    },
+    "npm": {
+        "enabled": "Povoleno",
+        "disabled": "Zakázáno",
+        "total": "Celkově"
+    },
+    "coinmarketcap": {
+        "configure": "Nakonfigurujte alespoň jednu crypto měnu ke sledování",
+        "1hour": "1 Hodina",
+        "1day": "1 Den",
+        "7days": "7 Dní",
+        "30days": "30 Dní"
+    },
+    "wmo": {
+        "1-night": "Převážně jasno",
+        "2-day": "Polojasno",
+        "0-day": "Slunečno",
+        "0-night": "Jasno",
+        "1-day": "Převážně slunečno",
+        "2-night": "Polojasno",
+        "3-day": "Oblačno",
+        "3-night": "Oblačno",
+        "45-day": "Mlha",
+        "45-night": "Mlha",
+        "48-day": "Mlha",
+        "48-night": "Mlha",
+        "51-day": "Lehké mrholení",
+        "53-day": "Mrholení",
+        "53-night": "Mrholení",
+        "55-day": "Silné mrholení",
+        "55-night": "Silné mrholení",
+        "56-day": "Mírné mrznoucí mrholení",
+        "56-night": "Mírné mrznoucí mrholení",
+        "57-day": "Mrznoucí mrholení",
+        "57-night": "Mrznoucí mrholení",
+        "61-day": "Slabý déšť",
+        "61-night": "Slabý déšť",
+        "51-night": "Lehké mrholení",
+        "63-day": "Déšť",
+        "63-night": "Déšť",
+        "65-day": "Silný déšť",
+        "65-night": "Silný déšť",
+        "66-day": "Mrznoucí déšť",
+        "66-night": "Mrznoucí déšť",
+        "67-day": "Mrznoucí déšť",
+        "67-night": "Mrznoucí déšť",
+        "71-day": "Slabé sněžení",
+        "73-night": "Sněžení",
+        "75-day": "Silné sněžení",
+        "75-night": "Silné sněžení",
+        "77-day": "Sněhová zrna",
+        "71-night": "Slabé sněžení",
+        "73-day": "Sněžení",
+        "77-night": "Sněhová zrna",
+        "80-day": "Lehké přeháňky",
+        "80-night": "Lehké přeháňky",
+        "81-day": "Přeháňky",
+        "81-night": "Přeháňky",
+        "82-day": "Silné přeháňky",
+        "82-night": "Silné přeháňky",
+        "85-day": "Déšť se sněhem",
+        "85-night": "Déšť se sněhem",
+        "86-day": "Déšť se sněhem",
+        "86-night": "Déšť se sněhem",
+        "95-day": "Bouřka",
+        "95-night": "Bouřka",
+        "96-day": "Bouřka s krupobitím",
+        "96-night": "Bouřka s krupobitím",
+        "99-day": "Bouřka s krupobitím",
+        "99-night": "Bouřka s krupobitím"
+    },
+    "gotify": {
+        "apps": "Aplikace",
+        "clients": "Klienti",
+        "messages": "Zprávy"
+    },
+    "prowlarr": {
+        "enableIndexers": "Indexery",
+        "numberOfGrabs": "Uchopení",
+        "numberOfQueries": "Dotazy",
+        "numberOfFailGrabs": "Neúspěšné uchopení",
+        "numberOfFailQueries": "Neúspěšné dotazy"
+    },
+    "jackett": {
+        "configured": "Konfigurováno",
+        "errored": "Chybné"
+    },
+    "strelaysrv": {
+        "numActiveSessions": "Sezení",
+        "numConnections": "Připojení",
+        "dataRelayed": "Přenášení",
+        "transferRate": "Tempo"
+    },
+    "mastodon": {
+        "user_count": "Uživatelé",
+        "status_count": "Příspěvky",
+        "domain_count": "Domény"
+    },
+    "authentik": {
+        "users": "Uživatelé",
+        "loginsLast24H": "Příhlášení (24h)",
+        "failedLoginsLast24H": "Neúspěšná přihlášení (24h)"
+    },
+    "proxmox": {
+        "mem": "RAM",
+        "cpu": "CPU",
+        "lxc": "LXC",
+        "vms": "Virtuální Stroje"
+    },
+    "glances": {
+        "cpu": "CPU",
+        "mem": "RAM",
+        "wait": "Prosím počkejte"
+    },
+    "quicklaunch": {
+        "bookmark": "Záložka",
+        "service": "Služba"
+    },
+    "homebridge": {
+        "update_available": "Dostupná aktualizace",
+        "up_to_date": "Aktuální",
+        "available_update": "Systém",
+        "updates": "Aktualizace",
+        "child_bridges": "Podřadné můstky",
+        "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "watchtower": {
+        "containers_scanned": "Naskenováno",
+        "containers_updated": "Aktualizováno",
+        "containers_failed": "Chyba"
+    },
+    "autobrr": {
+        "approvedPushes": "Schváleno",
+        "rejectedPushes": "Zamítnuto",
+        "filters": "Filtry",
+        "indexers": "Indexery"
+    }
+}

+ 308 - 0
public/locales/da/common.json

@@ -0,0 +1,308 @@
+{
+    "plex": {
+        "movies": "Movies",
+        "tv": "TV Shows",
+        "streams": "Active Streams"
+    },
+    "radarr": {
+        "queued": "Queued",
+        "movies": "Movies",
+        "wanted": "Wanted",
+        "missing": "Missing"
+    },
+    "lidarr": {
+        "wanted": "Wanted",
+        "queued": "Queued",
+        "albums": "Albums"
+    },
+    "jellyseerr": {
+        "available": "Available",
+        "pending": "Pending",
+        "approved": "Approved"
+    },
+    "overseerr": {
+        "pending": "Pending",
+        "approved": "Approved",
+        "available": "Available"
+    },
+    "adguard": {
+        "queries": "Queries",
+        "blocked": "Blocked",
+        "filtered": "Filtered",
+        "latency": "Latency"
+    },
+    "speedtest": {
+        "upload": "Upload",
+        "download": "Download",
+        "ping": "Ping"
+    },
+    "npm": {
+        "total": "Total",
+        "enabled": "Enabled",
+        "disabled": "Disabled"
+    },
+    "coinmarketcap": {
+        "30days": "30 Days",
+        "1day": "1 Day",
+        "configure": "Configure one or more crypto currencies to track",
+        "7days": "7 Days",
+        "1hour": "1 Hour"
+    },
+    "strelaysrv": {
+        "numActiveSessions": "Sessions",
+        "dataRelayed": "Relayed",
+        "numConnections": "Connections",
+        "transferRate": "Rate"
+    },
+    "mastodon": {
+        "domain_count": "Domains",
+        "status_count": "Posts",
+        "user_count": "Users"
+    },
+    "authentik": {
+        "users": "Users",
+        "loginsLast24H": "Logins (24h)",
+        "failedLoginsLast24H": "Failed Logins (24h)"
+    },
+    "glances": {
+        "cpu": "CPU",
+        "mem": "MEM",
+        "wait": "Please wait"
+    },
+    "wmo": {
+        "1-day": "Mainly Sunny",
+        "48-day": "Foggy",
+        "48-night": "Foggy",
+        "51-day": "Light Drizzle",
+        "51-night": "Light Drizzle",
+        "66-night": "Freezing Rain",
+        "67-day": "Freezing Rain",
+        "67-night": "Freezing Rain",
+        "71-day": "Light Snow",
+        "75-night": "Heavy Snow",
+        "86-day": "Snow Showers",
+        "86-night": "Snow Showers",
+        "95-day": "Thunderstorm",
+        "99-day": "Thunderstorm With Hail",
+        "99-night": "Thunderstorm With Hail",
+        "0-day": "Sunny",
+        "0-night": "Clear",
+        "1-night": "Mainly Clear",
+        "2-day": "Partly Cloudy",
+        "2-night": "Partly Cloudy",
+        "3-day": "Cloudy",
+        "3-night": "Cloudy",
+        "45-day": "Foggy",
+        "65-day": "Heavy Rain",
+        "65-night": "Heavy Rain",
+        "45-night": "Foggy",
+        "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",
+        "66-day": "Freezing Rain",
+        "71-night": "Light Snow",
+        "73-day": "Snow",
+        "73-night": "Snow",
+        "75-day": "Heavy Snow",
+        "77-day": "Snow Grains",
+        "80-day": "Light Showers",
+        "80-night": "Light Showers",
+        "81-day": "Showers",
+        "77-night": "Snow Grains",
+        "81-night": "Showers",
+        "82-day": "Heavy Showers",
+        "82-night": "Heavy Showers",
+        "85-day": "Snow Showers",
+        "85-night": "Snow Showers",
+        "95-night": "Thunderstorm",
+        "96-day": "Thunderstorm With Hail",
+        "96-night": "Thunderstorm With Hail"
+    },
+    "homebridge": {
+        "available_update": "System",
+        "updates": "Updates",
+        "update_available": "Update Available",
+        "up_to_date": "Up to Date",
+        "child_bridges": "Child Bridges",
+        "child_bridges_status": "{{ok}}/{{total}}"
+    },
+    "widget": {
+        "missing_type": "Missing Widget Type: {{type}}",
+        "api_error": "API Error",
+        "status": "Status"
+    },
+    "weather": {
+        "current": "Current Location",
+        "allow": "Click to allow",
+        "updating": "Updating",
+        "wait": "Please wait"
+    },
+    "search": {
+        "placeholder": "Search…"
+    },
+    "resources": {
+        "cpu": "CPU",
+        "total": "Total",
+        "free": "Free",
+        "used": "Used",
+        "load": "Load"
+    },
+    "unifi": {
+        "users": "Users",
+        "uptime": "System Uptime",
+        "days": "Days",
+        "wan": "WAN",
+        "lan": "LAN",
+        "wlan": "WLAN",
+        "devices": "Devices",
+        "lan_devices": "LAN Devices",
+        "wlan_devices": "WLAN Devices",
+        "lan_users": "LAN Users",
+        "wlan_users": "WLAN Users",
+        "up": "UP",
+        "down": "DOWN",
+        "wait": "Please wait"
+    },
+    "docker": {
+        "cpu": "CPU",
+        "rx": "RX",
+        "tx": "TX",
+        "mem": "MEM",
+        "offline": "Offline"
+    },
+    "emby": {
+        "playing": "Playing",
+        "transcoding": "Transcoding",
+        "bitrate": "Bitrate",
+        "no_active": "No Active Streams"
+    },
+    "changedetectionio": {
+        "totalObserved": "Total Observed",
+        "diffsDetected": "Diffs Detected"
+    },
+    "tautulli": {
+        "playing": "Playing",
+        "transcoding": "Transcoding",
+        "bitrate": "Bitrate",
+        "no_active": "No Active Streams"
+    },
+    "nzbget": {
+        "rate": "Rate",
+        "remaining": "Remaining",
+        "downloaded": "Downloaded"
+    },
+    "sabnzbd": {
+        "rate": "Rate",
+        "queue": "Queue",
+        "timeleft": "Time Left"
+    },
+    "rutorrent": {
+        "active": "Active",
+        "upload": "Upload",
+        "download": "Download"
+    },
+    "transmission": {
+        "upload": "Upload",
+        "download": "Download",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "qbittorrent": {
+        "upload": "Upload",
+        "download": "Download",
+        "leech": "Leech",
+        "seed": "Seed"
+    },
+    "sonarr": {
+        "wanted": "Wanted",
+        "queued": "Queued",
+        "series": "Series"
+    },
+    "readarr": {
+        "wanted": "Wanted",
+        "queued": "Queued",
+        "books": "Books"
+    },
+    "bazarr": {
+        "missingEpisodes": "Missing Episodes",
+        "missingMovies": "Missing Movies"
+    },
+    "ombi": {
+        "pending": "Pending",
+        "approved": "Approved",
+        "available": "Available"
+    },
+    "pihole": {
+        "blocked": "Blocked",
+        "gravity": "Gravity",
+        "queries": "Queries"
+    },
+    "portainer": {
+        "running": "Running",
+        "stopped": "Stopped",
+        "total": "Total"
+    },
+    "traefik": {
+        "routers": "Routers",
+        "services": "Services",
+        "middleware": "Middleware"
+    },
+    "gotify": {
+        "apps": "Applications",
+        "clients": "Clients",
+        "messages": "Messages"
+    },
+    "prowlarr": {
+        "enableIndexers": "Indexers",
+        "numberOfGrabs": "Grabs",
+        "numberOfQueries": "Queries",
+        "numberOfFailGrabs": "Fail Grabs",
+        "numberOfFailQueries": "Fail Queries"
+    },
+    "jackett": {
+        "configured": "Configured",
+        "errored": "Errored"
+    },
+    "proxmox": {
+        "mem": "MEM",
+        "cpu": "CPU",
+        "lxc": "LXC",
+        "vms": "VMs"
+    },
+    "quicklaunch": {
+        "bookmark": "Bookmark",
+        "service": "Service"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "autobrr": {
+        "indexers": "Indexers",
+        "approvedPushes": "Approved",
+        "rejectedPushes": "Rejected",
+        "filters": "Filters"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    }
+}

+ 12 - 0
public/locales/de/common.json

@@ -293,6 +293,18 @@
         "containers_updated": "Updated",
         "containers_failed": "Failed"
     },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
     "pyload": {
         "speed": "Geschwindigkeit",
         "active": "Aktiv",

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

@@ -304,6 +304,18 @@
         "filters": "Filters",
         "indexers": "Indexers"
     },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
+    },
     "pyload": {
         "speed": "Speed",
         "active": "Active",

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

@@ -289,8 +289,20 @@
         "indexers": "Indexadores"
     },
     "watchtower": {
-        "containers_scanned": "Scanned",
-        "containers_updated": "Updated",
-        "containers_failed": "Failed"
+        "containers_scanned": "Escaneado",
+        "containers_updated": "Actualizado",
+        "containers_failed": "Fallido"
+    },
+    "tubearchivist": {
+        "downloads": "Cola",
+        "videos": "Vídeos",
+        "channels": "Canales",
+        "playlists": "Listas de reproducción"
+    },
+    "truenas": {
+        "load": "Carga del sistema",
+        "uptime": "Tiempo de la actividad",
+        "alerts": "Alertas",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
     }
 }

+ 12 - 0
public/locales/fi/common.json

@@ -292,5 +292,17 @@
         "containers_scanned": "Scanned",
         "containers_updated": "Updated",
         "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
     }
 }

+ 12 - 0
public/locales/fr/common.json

@@ -292,5 +292,17 @@
         "containers_scanned": "Scanné",
         "containers_updated": "Mis à jour",
         "containers_failed": "Échoué"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Vidéos",
+        "channels": "Chaînes",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "Charge Système",
+        "uptime": "Démarré depuis",
+        "alerts": "Alertes",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
     }
 }

+ 12 - 0
public/locales/he/common.json

@@ -292,5 +292,17 @@
         "containers_scanned": "Scanned",
         "containers_updated": "Updated",
         "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
     }
 }

+ 116 - 104
public/locales/hr/common.json

@@ -1,12 +1,12 @@
 {
     "weather": {
-        "current": "Trenutna lokacija",
+        "current": "Trenutna lokacija",
         "allow": "Pritisni za dozvoljavanje",
         "updating": "Aktualiziranje",
-        "wait": "Molimo pričekajte"
+        "wait": "Pričekaj"
     },
     "search": {
-        "placeholder": "Traži…"
+        "placeholder": "Traži …"
     },
     "resources": {
         "total": "Ukupno",
@@ -17,7 +17,7 @@
     },
     "sabnzbd": {
         "rate": "Stopa",
-        "queue": "Red",
+        "queue": "Red čekanja",
         "timeleft": "Preostalo vrijeme"
     },
     "overseerr": {
@@ -28,7 +28,7 @@
     "pihole": {
         "queries": "Upiti",
         "blocked": "Blokirano",
-        "gravity": "Ozbiljnost"
+        "gravity": "Čuvanje podataka"
     },
     "adguard": {
         "latency": "Kašnjenje",
@@ -65,7 +65,7 @@
         "tx": "TX",
         "mem": "MEM",
         "cpu": "CPU",
-        "offline": "Izvan mreže"
+        "offline": "Nepovezan"
     },
     "emby": {
         "playing": "Reprodukcija",
@@ -92,27 +92,27 @@
     "transmission": {
         "download": "Preuzimanje",
         "upload": "Prijenos",
-        "leech": "Krvopija",
-        "seed": "Prijenos preuzetog sadržaja"
+        "leech": "Leecher",
+        "seed": "Seeder"
     },
     "sonarr": {
-        "wanted": "Željeno",
+        "wanted": "Zatraženo",
         "queued": "U redu čekanja",
         "series": "Serije"
     },
     "radarr": {
-        "wanted": "Željeno",
+        "wanted": "Zatraženo",
         "queued": "U redu čekanja",
         "movies": "Filmovi",
-        "missing": "Missing"
+        "missing": "Nedostaje"
     },
     "lidarr": {
-        "wanted": "Željeno",
+        "wanted": "Zatraženo",
         "queued": "U redu čekanja",
         "albums": "Albumi"
     },
     "readarr": {
-        "wanted": "Željeno",
+        "wanted": "Zatraženo",
         "queued": "U redu čekanja",
         "books": "Knjige"
     },
@@ -143,7 +143,7 @@
     "traefik": {
         "routers": "Ruteri",
         "services": "Usluge",
-        "middleware": "Middleware"
+        "middleware": "Posrednički softver"
     },
     "gotify": {
         "clients": "Klijenti",
@@ -157,8 +157,8 @@
     "qbittorrent": {
         "download": "Preuzimanje",
         "upload": "Prijenos",
-        "leech": "Krvopija",
-        "seed": "Prijenos preuzetog sadržaja"
+        "leech": "Leecher",
+        "seed": "Seeder"
     },
     "mastodon": {
         "user_count": "Korisnici",
@@ -172,125 +172,137 @@
         "transferRate": "Stopa"
     },
     "authentik": {
-        "users": "Users",
-        "loginsLast24H": "Logins (24h)",
-        "failedLoginsLast24H": "Failed Logins (24h)"
+        "users": "Korisnici",
+        "loginsLast24H": "Prijave (24 h)",
+        "failedLoginsLast24H": "Neuspjele prijave (24 h)"
     },
     "proxmox": {
         "mem": "MEM",
         "cpu": "CPU",
-        "lxc": "LXC",
-        "vms": "VMs"
+        "lxc": "Linux kontejner",
+        "vms": "Virtualni uređaji"
     },
     "unifi": {
         "users": "Korisnici",
-        "uptime": "Vrijeme rada sustava",
+        "uptime": "Radno vrijeme sustava",
         "days": "Dani",
         "wan": "WAN",
-        "lan_users": "LAN Korisnici",
-        "wlan_users": "WLAN Korisnici",
+        "lan_users": "LAN korisnici",
+        "wlan_users": "WLAN korisnici",
         "up": "Upaljen",
         "down": "Ugašen",
-        "wait": "Molimo pričekajte",
+        "wait": "Pričekaj",
         "lan": "LAN",
         "wlan": "WLAN",
-        "devices": "Devices",
-        "lan_devices": "LAN Devices",
-        "wlan_devices": "WLAN Devices"
+        "devices": "Uređaji",
+        "lan_devices": "LAN uređaji",
+        "wlan_devices": "WLAN uređaji"
     },
     "plex": {
-        "streams": "Active Streams",
-        "movies": "Movies",
-        "tv": "TV Shows"
+        "streams": "Aktivni prijenosi",
+        "movies": "Filmovi",
+        "tv": "TV emisije"
     },
     "glances": {
         "cpu": "CPU",
         "mem": "MEM",
-        "wait": "Please wait"
+        "wait": "Pričekaj"
     },
     "changedetectionio": {
-        "totalObserved": "Total Observed",
-        "diffsDetected": "Diffs Detected"
+        "totalObserved": "Ukupno promatrano",
+        "diffsDetected": "Otkrivene razlike"
     },
     "wmo": {
-        "0-day": "Sunny",
-        "0-night": "Clear",
-        "1-day": "Mainly Sunny",
-        "1-night": "Mainly Clear",
-        "2-day": "Partly Cloudy",
-        "45-day": "Foggy",
-        "45-night": "Foggy",
-        "48-day": "Foggy",
-        "2-night": "Partly Cloudy",
-        "3-day": "Cloudy",
-        "3-night": "Cloudy",
-        "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",
-        "75-night": "Heavy Snow",
-        "77-day": "Snow Grains",
-        "71-day": "Light Snow",
-        "71-night": "Light Snow",
-        "73-day": "Snow",
-        "73-night": "Snow",
-        "75-day": "Heavy Snow",
-        "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"
+        "0-day": "Sunčano",
+        "0-night": "Vedro",
+        "1-day": "Pretežno sunčano",
+        "1-night": "Pretežno verdo",
+        "2-day": "Djelimično oblačno",
+        "45-day": "Maglovito",
+        "45-night": "Maglovito",
+        "48-day": "Maglovito",
+        "2-night": "Djelimično oblačno",
+        "3-day": "Oblačno",
+        "3-night": "Oblačno",
+        "48-night": "Maglovito",
+        "51-day": "Laka rosulja",
+        "51-night": "Laka rosulja",
+        "53-day": "Rosulja",
+        "53-night": "Rosulja",
+        "55-day": "Jaka rosulja",
+        "55-night": "Jaka rosulja",
+        "56-day": "Laka ledena rosulja",
+        "56-night": "Laka ledena rosulja",
+        "57-day": "Ledena rosulja",
+        "57-night": "Ledena rosulja",
+        "61-day": "Laka kiša",
+        "61-night": "Laka kiša",
+        "63-day": "Kiša",
+        "63-night": "Kiša",
+        "65-day": "Jaka kiša",
+        "65-night": "Jaka kiša",
+        "66-day": "Ledena kiša",
+        "66-night": "Ledena kiša",
+        "67-day": "Ledena kiša",
+        "67-night": "Ledena kiša",
+        "75-night": "Jaki snijeg",
+        "77-day": "Zrnati snijeg",
+        "71-day": "Laki snijeg",
+        "71-night": "Laki snijeg",
+        "73-day": "Snijeg",
+        "73-night": "Snijeg",
+        "75-day": "Jaki snijeg",
+        "77-night": "Zrnati snijeg",
+        "80-day": "Laki pljuskovi",
+        "80-night": "Laki pljuskovi",
+        "81-day": "Pljuskovi",
+        "81-night": "Pljuskovi",
+        "82-day": "Jaki pljuskovi",
+        "82-night": "Jaki pljuskovi",
+        "85-day": "Snježni pljuskovi",
+        "85-night": "Snježni pljuskovi",
+        "86-day": "Snježni pljuskovi",
+        "86-night": "Snježni pljuskovi",
+        "95-day": "Oluja",
+        "95-night": "Oluja",
+        "96-day": "Oluja s grmljavinom",
+        "96-night": "Oluja s grmljavinom",
+        "99-day": "Oluja s grmljavinom",
+        "99-night": "Oluja s grmljavinom"
     },
     "quicklaunch": {
-        "bookmark": "Bookmark",
-        "service": "Service"
+        "bookmark": "Straničnik",
+        "service": "Usluga"
     },
     "homebridge": {
-        "available_update": "System",
-        "updates": "Updates",
-        "update_available": "Update Available",
-        "up_to_date": "Up to Date",
-        "child_bridges": "Child Bridges",
+        "available_update": "Sustav",
+        "updates": "Aktualiziranja",
+        "update_available": "Dostupna je nova verzija",
+        "up_to_date": "Aktualno",
+        "child_bridges": "Podređeni mosotvi",
         "child_bridges_status": "{{ok}}/{{total}}"
     },
     "autobrr": {
-        "rejectedPushes": "Rejected",
-        "approvedPushes": "Approved",
-        "filters": "Filters",
-        "indexers": "Indexers"
+        "rejectedPushes": "Odbijeno",
+        "approvedPushes": "Odobreno",
+        "filters": "Filtri",
+        "indexers": "Indeksatori"
     },
     "watchtower": {
-        "containers_scanned": "Scanned",
-        "containers_updated": "Updated",
-        "containers_failed": "Failed"
+        "containers_scanned": "Skenirano",
+        "containers_updated": "Aktualizirano",
+        "containers_failed": "Neuspjelo"
+    },
+    "tubearchivist": {
+        "downloads": "Red čekanja",
+        "videos": "Videa",
+        "channels": "Kanali",
+        "playlists": "Playliste"
+    },
+    "truenas": {
+        "load": "Opterećenje sustava",
+        "uptime": "Radno vrijeme",
+        "alerts": "Upozorenja",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
     }
 }

+ 12 - 0
public/locales/hu/common.json

@@ -292,5 +292,17 @@
         "containers_scanned": "Scanned",
         "containers_updated": "Updated",
         "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
     }
 }

+ 12 - 0
public/locales/it/common.json

@@ -292,5 +292,17 @@
         "containers_scanned": "Scanned",
         "containers_updated": "Updated",
         "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
     }
 }

+ 12 - 0
public/locales/nb-NO/common.json

@@ -292,5 +292,17 @@
         "containers_scanned": "Scanned",
         "containers_updated": "Updated",
         "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
     }
 }

+ 12 - 0
public/locales/nl/common.json

@@ -292,5 +292,17 @@
         "containers_scanned": "Scanned",
         "containers_updated": "Updated",
         "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
     }
 }

+ 12 - 0
public/locales/pl/common.json

@@ -292,5 +292,17 @@
         "containers_scanned": "Scanned",
         "containers_updated": "Updated",
         "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
     }
 }

+ 12 - 0
public/locales/pt-BR/common.json

@@ -292,5 +292,17 @@
         "containers_scanned": "Scanned",
         "containers_updated": "Updated",
         "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
     }
 }

+ 12 - 0
public/locales/pt/common.json

@@ -303,5 +303,17 @@
         "containers_scanned": "Scanned",
         "containers_updated": "Updated",
         "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
     }
 }

+ 12 - 0
public/locales/ro/common.json

@@ -292,5 +292,17 @@
         "containers_scanned": "Scanned",
         "containers_updated": "Updated",
         "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
     }
 }

+ 12 - 0
public/locales/ru/common.json

@@ -292,5 +292,17 @@
         "containers_scanned": "Scanned",
         "containers_updated": "Updated",
         "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
     }
 }

+ 12 - 0
public/locales/sr/common.json

@@ -292,5 +292,17 @@
         "containers_scanned": "Scanned",
         "containers_updated": "Updated",
         "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
     }
 }

+ 12 - 0
public/locales/sv/common.json

@@ -292,5 +292,17 @@
         "containers_scanned": "Scanned",
         "containers_updated": "Updated",
         "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
     }
 }

+ 12 - 0
public/locales/te/common.json

@@ -292,5 +292,17 @@
         "containers_scanned": "స్కాన్ చేశారు",
         "containers_updated": "నవీకరించబడింది",
         "containers_failed": "విఫలమయ్యారు"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
     }
 }

+ 12 - 0
public/locales/tr/common.json

@@ -292,5 +292,17 @@
         "containers_scanned": "Scanned",
         "containers_updated": "Updated",
         "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
     }
 }

+ 12 - 0
public/locales/vi/common.json

@@ -292,5 +292,17 @@
         "containers_scanned": "Scanned",
         "containers_updated": "Updated",
         "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
     }
 }

+ 12 - 0
public/locales/yue/common.json

@@ -292,5 +292,17 @@
         "containers_scanned": "Scanned",
         "containers_updated": "Updated",
         "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
     }
 }

+ 12 - 0
public/locales/zh-CN/common.json

@@ -292,5 +292,17 @@
         "containers_scanned": "Scanned",
         "containers_updated": "Updated",
         "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
     }
 }

+ 12 - 0
public/locales/zh-Hant/common.json

@@ -292,5 +292,17 @@
         "containers_scanned": "Scanned",
         "containers_updated": "Updated",
         "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
+    },
+    "truenas": {
+        "load": "System Load",
+        "uptime": "Uptime",
+        "alerts": "Alerts",
+        "time": "{{value, number(style: unit; unitDisplay: long;)}}"
     }
 }

+ 7 - 1
src/components/bookmarks/item.jsx

@@ -1,6 +1,7 @@
 import { useContext } from "react";
 
 import { SettingsContext } from "utils/contexts/settings";
+import ResolvedIcon from "components/resolvedicon";
 
 export default function Item({ bookmark }) {
   const { hostname } = new URL(bookmark.href);
@@ -16,7 +17,12 @@ export default function Item({ bookmark }) {
       >
         <div className="flex">
           <div className="flex-shrink-0 flex items-center justify-center w-11 bg-theme-500/10 dark:bg-theme-900/50 text-theme-700 hover:text-theme-700 dark:text-theme-200 text-sm font-medium rounded-l-md">
-            {bookmark.abbr}
+            {bookmark.icon && 
+              <div className="flex-shrink-0 w-5 h-5">
+                <ResolvedIcon icon={bookmark.icon} />
+              </div>
+            }
+            {!bookmark.icon && bookmark.abbr}
           </div>
           <div className="flex-1 flex items-center justify-between rounded-r-md ">
             <div className="flex-1 grow pl-3 py-2 text-xs">{bookmark.name}</div>

+ 2 - 2
src/components/quicklaunch.jsx

@@ -2,7 +2,7 @@ import { useTranslation } from "react-i18next";
 import { useEffect, useState, useRef, useCallback, useContext } from "react";
 import classNames from "classnames";
 
-import { resolveIcon } from "./services/item";
+import ResolvedIcon from "./resolvedicon";
 
 import { SettingsContext } from "utils/contexts/settings";
 
@@ -135,7 +135,7 @@ export default function QuickLaunch({servicesAndBookmarks, searchString, setSear
                     )} onClick={handleItemClick}>
                     <div className="flex flex-row items-center mr-4 pointer-events-none">
                       <div className="w-5 text-xs mr-4">
-                        {r.icon && resolveIcon(r.icon)}
+                        {r.icon && <ResolvedIcon icon={r.icon} />}
                         {r.abbr && r.abbr}
                       </div>
                       <div className="flex flex-col md:flex-row text-left items-baseline mr-4 pointer-events-none">

+ 37 - 0
src/components/resolvedicon.jsx

@@ -0,0 +1,37 @@
+import Image from "next/future/image";
+
+export default function ResolvedIcon({ icon }) {
+  // direct or relative URLs
+  if (icon.startsWith("http") || icon.startsWith("/")) {
+    return <Image src={`${icon}`} width={32} height={32} alt="logo" />;
+  }
+
+  // mdi- prefixed, material design icons
+  if (icon.startsWith("mdi-")) {
+    const iconName = icon.replace("mdi-", "").replace(".svg", "");
+    return (
+      <div
+        style={{
+          width: 32,
+          height: 32,
+          'max-width': '100%',
+          'max-height': '100%',
+          background: "linear-gradient(180deg, rgb(var(--color-logo-start)), rgb(var(--color-logo-stop)))",
+          mask: `url(https://cdn.jsdelivr.net/npm/@mdi/svg@latest/svg/${iconName}.svg) no-repeat center / contain`,
+          WebkitMask: `url(https://cdn.jsdelivr.net/npm/@mdi/svg@latest/svg/${iconName}.svg) no-repeat center / contain`,
+        }}
+      />
+    );
+  }
+
+  // fallback to dashboard-icons
+  const iconName = icon.replace(".png", "");
+  return (
+    <Image
+      src={`https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${iconName}.png`}
+      width={32}
+      height={32}
+      alt="logo"
+    />
+  );
+}

+ 9 - 1
src/components/services/group.jsx

@@ -1,6 +1,7 @@
 import classNames from "classnames";
 
 import List from "components/services/list";
+import ResolvedIcon from "components/resolvedicon";
 
 export default function ServicesGroup({ services, layout }) {
   return (
@@ -11,7 +12,14 @@ export default function ServicesGroup({ services, layout }) {
         "flex-1 p-1"
       )}
     >
-      <h2 className="text-theme-800 dark:text-theme-300 text-xl font-medium">{services.name}</h2>
+      <div className="flex select-none items-center">
+        {layout?.icon &&
+          <div className="flex-shrink-0 mr-2 w-7 h-7">
+            <ResolvedIcon icon={layout.icon} />
+          </div>
+        }
+        <h2 className="text-theme-800 dark:text-theme-300 text-xl font-medium">{services.name}</h2>
+      </div>
       <List services={services.services} layout={layout} />
     </div>
   );

+ 5 - 37
src/components/services/item.jsx

@@ -1,4 +1,3 @@
-import Image from "next/future/image";
 import classNames from "classnames";
 import { useContext, useState } from "react";
 
@@ -7,40 +6,7 @@ import Widget from "./widget";
 
 import Docker from "widgets/docker/component";
 import { SettingsContext } from "utils/contexts/settings";
-
-export function resolveIcon(icon) {
-  // direct or relative URLs
-  if (icon.startsWith("http") || icon.startsWith("/")) {
-    return <Image src={`${icon}`} width={32} height={32} alt="logo" />;
-  }
-
-  // mdi- prefixed, material design icons
-  if (icon.startsWith("mdi-")) {
-    const iconName = icon.replace("mdi-", "").replace(".svg", "");
-    return (
-      <div
-        style={{
-          width: 32,
-          height: 32,
-          background: "linear-gradient(180deg, rgb(var(--color-logo-start)), rgb(var(--color-logo-stop)))",
-          mask: `url(https://cdn.jsdelivr.net/npm/@mdi/svg@latest/svg/${iconName}.svg) no-repeat center / contain`,
-          WebkitMask: `url(https://cdn.jsdelivr.net/npm/@mdi/svg@latest/svg/${iconName}.svg) no-repeat center / contain`,
-        }}
-      />
-    );
-  }
-
-  // fallback to dashboard-icons
-  const iconName = icon.replace(".png", "");
-  return (
-    <Image
-      src={`https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${iconName}.png`}
-      width={32}
-      height={32}
-      alt="logo"
-    />
-  );
-}
+import ResolvedIcon from "components/resolvedicon";
 
 export default function Item({ service }) {
   const hasLink = service.href && service.href !== "#";
@@ -75,10 +41,12 @@ export default function Item({ service }) {
                 rel="noreferrer"
                 className="flex-shrink-0 flex items-center justify-center w-12 "
               >
-                {resolveIcon(service.icon)}
+                <ResolvedIcon icon={service.icon} />
               </a>
             ) : (
-              <div className="flex-shrink-0 flex items-center justify-center w-12 ">{resolveIcon(service.icon)}</div>
+              <div className="flex-shrink-0 flex items-center justify-center w-12 ">
+                <ResolvedIcon icon={service.icon} />
+              </div>
             ))}
 
           {hasLink ? (

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

@@ -33,6 +33,8 @@ export default async function credentialedProxyHandler(req, res) {
         headers.Authorization = `PVEAPIToken=${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 {
         headers["X-API-Key"] = `${widget.key}`;
       }

+ 2 - 0
src/widgets/components.js

@@ -37,6 +37,8 @@ const components = {
   tautulli: dynamic(() => import("./tautulli/component")),
   traefik: dynamic(() => import("./traefik/component")),
   transmission: dynamic(() => import("./transmission/component")),
+  tubearchivist: dynamic(() => import("./tubearchivist/component")),
+  truenas: dynamic(() => import("./truenas/component")),
   unifi: dynamic(() => import("./unifi/component")),
   watchtower: dynamic(() => import("./watchtower/component")),
 };

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

@@ -171,7 +171,7 @@ export default function Component({ service }) {
     });
   }
 
-  if (sessionsError) {
+  if (sessionsError || sessionsData?.error) {
     return <Container error={t("widget.api_error")} />;
   }
 

+ 65 - 0
src/widgets/truenas/component.jsx

@@ -0,0 +1,65 @@
+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";
+
+const processUptime = uptime => {
+
+  const seconds = uptime.toFixed(0);
+
+  const levels = [
+    [Math.floor(seconds / 31536000), 'year'],
+    [Math.floor((seconds % 31536000) / 2592000), 'month'],
+    [Math.floor(((seconds % 31536000) % 2592000) / 86400), 'day'],
+    [Math.floor(((seconds % 31536000) % 86400) / 3600), 'hour'],
+    [Math.floor((((seconds % 31536000) % 86400) % 3600) / 60), 'minute'],
+    [(((seconds % 31536000) % 86400) % 3600) % 60, 'second'],
+  ];
+  
+  for (let i = 0; i< levels.length; i += 1) {
+    const level = levels[i];
+    if (level[0] > 0){
+      return {
+          value: level[0],
+          unit: level[1]
+        }
+      } 
+  }
+
+  return {
+    value: 0,
+    unit: 'second'
+  };
+}
+
+export default function Component({ service }) {
+  const { t } = useTranslation();
+
+  const { widget } = service;
+
+  const { data: alertData, error: alertError } = useWidgetAPI(widget, "alerts");
+  const { data: statusData, error: statusError } = useWidgetAPI(widget, "status");
+
+  if (alertError || statusError) {
+    return <Container error={t("widget.api_error")} />;
+  }
+
+  if (!alertData || !statusData) {
+    return (
+      <Container service={service}>
+        <Block label="truenas.load" />
+        <Block label="truenas.uptime" />
+        <Block label="truenas.alerts" />
+      </Container>
+    );
+  }
+  
+  return (
+    <Container service={service}>
+      <Block label="truenas.load" value={t("common.number", { value: statusData.loadavg[0] })} />
+      <Block label="truenas.uptime" value={t('truenas.time', processUptime(statusData.uptime_seconds))} />
+      <Block label="truenas.alerts" value={t("common.number", { value: alertData.pending })} />
+    </Container>
+  );
+}

+ 21 - 0
src/widgets/truenas/widget.js

@@ -0,0 +1,21 @@
+import { jsonArrayFilter } from "utils/proxy/api-helpers";
+import genericProxyHandler from "utils/proxy/handlers/generic";
+
+const widget = {
+  api: "{url}/api/v2.0/{endpoint}",
+  proxyHandler: genericProxyHandler,
+
+  mappings: {
+    alerts: {
+      endpoint: "alert/list",
+      map: (data) => ({
+        pending: jsonArrayFilter(data, (item) => item?.dismissed === false).length,
+      }),
+    },
+    status: {
+      endpoint: "system/info",
+    },
+  },
+};
+
+export default widget;

+ 40 - 0
src/widgets/tubearchivist/component.jsx

@@ -0,0 +1,40 @@
+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: downloadsData, error: downloadsError } = useWidgetAPI(widget, "downloads");
+  const { data: videosData, error: videosError } = useWidgetAPI(widget, "videos");
+  const { data: channelsData, error: channelsError } = useWidgetAPI(widget, "channels");
+  const { data: playlistsData, error: playlistsError } = useWidgetAPI(widget, "playlists");
+
+  if (downloadsError || videosError || channelsError || playlistsError) {
+    return <Container error={t("widget.api_error")} />;
+  }
+
+  if (!downloadsData || !videosData || !channelsData || !playlistsData) {
+    return (
+      <Container service={service}>
+        <Block label="tubearchivist.downloads" />
+        <Block label="tubearchivist.videos" />
+        <Block label="tubearchivist.channels" />
+        <Block label="tubearchivist.playlists" />
+      </Container>
+    );
+  }
+
+  return (
+    <Container service={service}>
+      <Block label="tubearchivist.downloads" value={t("common.number", { value: downloadsData?.paginate?.total_hits })} />
+      <Block label="tubearchivist.videos" value={t("common.number", { value: videosData?.paginate?.total_hits })} />
+      <Block label="tubearchivist.channels" value={t("common.number", { value: channelsData?.paginate?.total_hits })} />
+      <Block label="tubearchivist.playlists" value={t("common.number", { value: playlistsData?.paginate?.total_hits })} />
+    </Container>
+  );
+}

+ 23 - 0
src/widgets/tubearchivist/widget.js

@@ -0,0 +1,23 @@
+import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
+
+const widget = {
+  api: "{url}/api/{endpoint}",
+  proxyHandler: credentialedProxyHandler,
+
+  mappings: {
+    downloads: {
+      endpoint: "download",
+    },
+    videos: {
+      endpoint: "video",
+    },
+    channels: {
+      endpoint: "channel",
+    },
+    playlists: {
+      endpoint: "playlist",
+    },
+  },
+};
+
+export default widget;

+ 4 - 0
src/widgets/widgets.js

@@ -32,6 +32,8 @@ import strelaysrv from "./strelaysrv/widget";
 import tautulli from "./tautulli/widget";
 import traefik from "./traefik/widget";
 import transmission from "./transmission/widget";
+import tubearchivist from "./tubearchivist/widget";
+import truenas from "./truenas/widget";
 import unifi from "./unifi/widget";
 import watchtower from './watchtower/widget'
 
@@ -71,6 +73,8 @@ const widgets = {
   tautulli,
   traefik,
   transmission,
+  tubearchivist,
+  truenas,
   unifi,
   unifi_console: unifi,
   watchtower,