Browse Source

Merge branch 'main' of github.com:chazzbg/homepage into truenas-widget

Dimitar Ilkov 2 years ago
parent
commit
30c35f99d2
44 changed files with 784 additions and 25 deletions
  1. 1 1
      .github/workflows/docker-publish.yml
  2. 302 0
      public/locales/ar/common.json
  3. 11 0
      public/locales/bg/common.json
  4. 11 0
      public/locales/ca/common.json
  5. 11 0
      public/locales/de/common.json
  6. 11 0
      public/locales/en/common.json
  7. 15 4
      public/locales/es/common.json
  8. 11 0
      public/locales/fi/common.json
  9. 15 4
      public/locales/fr/common.json
  10. 11 0
      public/locales/he/common.json
  11. 11 0
      public/locales/hr/common.json
  12. 11 0
      public/locales/hu/common.json
  13. 11 0
      public/locales/it/common.json
  14. 11 0
      public/locales/nb-NO/common.json
  15. 11 0
      public/locales/nl/common.json
  16. 11 0
      public/locales/pl/common.json
  17. 11 0
      public/locales/pt-BR/common.json
  18. 11 0
      public/locales/pt/common.json
  19. 11 0
      public/locales/ro/common.json
  20. 11 0
      public/locales/ru/common.json
  21. 11 0
      public/locales/sr/common.json
  22. 11 0
      public/locales/sv/common.json
  23. 15 4
      public/locales/te/common.json
  24. 11 0
      public/locales/tr/common.json
  25. 11 0
      public/locales/vi/common.json
  26. 11 0
      public/locales/yue/common.json
  27. 11 0
      public/locales/zh-CN/common.json
  28. 11 0
      public/locales/zh-Hant/common.json
  29. 6 4
      src/components/widgets/datetime/datetime.jsx
  30. 2 2
      src/pages/index.jsx
  31. 1 1
      src/skeleton/bookmarks.yaml
  32. 1 1
      src/skeleton/docker.yaml
  33. 1 1
      src/skeleton/services.yaml
  34. 1 1
      src/skeleton/settings.yaml
  35. 1 1
      src/skeleton/widgets.yaml
  36. 2 0
      src/utils/proxy/handlers/credentialed.js
  37. 2 0
      src/widgets/components.js
  38. 1 1
      src/widgets/emby/widget.js
  39. 40 0
      src/widgets/tubearchivist/component.jsx
  40. 23 0
      src/widgets/tubearchivist/widget.js
  41. 36 0
      src/widgets/watchtower/component.jsx
  42. 48 0
      src/widgets/watchtower/proxy.js
  43. 14 0
      src/widgets/watchtower/widget.js
  44. 4 0
      src/widgets/widgets.js

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

@@ -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
           cache-from: type=local,src=/tmp/.buildx-cache
           cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
 

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

@@ -0,0 +1,302 @@
+{
+    "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"
+    }
+}

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

@@ -287,5 +287,16 @@
         "filters": "Filters",
         "indexers": "Indexers",
         "approvedPushes": "Approved"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
     }
 }

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

@@ -287,5 +287,16 @@
         "rejectedPushes": "Rejected",
         "filters": "Filters",
         "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
     }
 }

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

@@ -287,5 +287,16 @@
         "rejectedPushes": "Rejected",
         "filters": "Filters",
         "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
     }
 }

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

@@ -293,12 +293,23 @@
         "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",

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

@@ -283,9 +283,20 @@
         "child_bridges_status": "{{ok}}/{{total}}"
     },
     "autobrr": {
-        "approvedPushes": "Approved",
-        "rejectedPushes": "Rejected",
-        "filters": "Filters",
-        "indexers": "Indexers"
+        "approvedPushes": "Aprobado",
+        "rejectedPushes": "Rechazado",
+        "filters": "Filtros",
+        "indexers": "Indexadores"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
     }
 }

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

@@ -287,5 +287,16 @@
         "rejectedPushes": "Rejected",
         "filters": "Filters",
         "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
     }
 }

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

@@ -283,9 +283,20 @@
         "child_bridges_status": "{{ok}}/{{total}}"
     },
     "autobrr": {
-        "approvedPushes": "Approved",
-        "rejectedPushes": "Rejected",
-        "filters": "Filters",
-        "indexers": "Indexers"
+        "approvedPushes": "Approuvé",
+        "rejectedPushes": "Rejeté",
+        "filters": "Filtres",
+        "indexers": "Indexeur"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanné",
+        "containers_updated": "Mis à jour",
+        "containers_failed": "Échoué"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
     }
 }

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

@@ -287,5 +287,16 @@
         "rejectedPushes": "Rejected",
         "filters": "Filters",
         "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
     }
 }

+ 11 - 0
public/locales/hr/common.json

@@ -287,5 +287,16 @@
         "approvedPushes": "Approved",
         "filters": "Filters",
         "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
     }
 }

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

@@ -287,5 +287,16 @@
         "rejectedPushes": "Rejected",
         "filters": "Filters",
         "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
     }
 }

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

@@ -287,5 +287,16 @@
         "rejectedPushes": "Rejected",
         "filters": "Filters",
         "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
     }
 }

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

@@ -287,5 +287,16 @@
         "rejectedPushes": "Rejected",
         "filters": "Filters",
         "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
     }
 }

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

@@ -287,5 +287,16 @@
         "rejectedPushes": "Rejected",
         "filters": "Filters",
         "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
     }
 }

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

@@ -287,5 +287,16 @@
         "rejectedPushes": "Rejected",
         "filters": "Filters",
         "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
     }
 }

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

@@ -287,5 +287,16 @@
         "rejectedPushes": "Rejected",
         "filters": "Filters",
         "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
     }
 }

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

@@ -298,5 +298,16 @@
         "rejectedPushes": "Rejected",
         "filters": "Filters",
         "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
     }
 }

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

@@ -287,5 +287,16 @@
         "rejectedPushes": "Rejected",
         "filters": "Filters",
         "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
     }
 }

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

@@ -287,5 +287,16 @@
         "rejectedPushes": "Rejected",
         "filters": "Filters",
         "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
     }
 }

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

@@ -287,5 +287,16 @@
         "rejectedPushes": "Rejected",
         "filters": "Filters",
         "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
     }
 }

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

@@ -287,5 +287,16 @@
         "rejectedPushes": "Rejected",
         "filters": "Filters",
         "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
     }
 }

+ 15 - 4
public/locales/te/common.json

@@ -283,9 +283,20 @@
         "child_bridges_status": "{{ok}}/{{total}}"
     },
     "autobrr": {
-        "rejectedPushes": "Rejected",
-        "approvedPushes": "Approved",
-        "filters": "Filters",
-        "indexers": "Indexers"
+        "rejectedPushes": "తిరస్కరించారు",
+        "approvedPushes": "ఆమోదించబడింది",
+        "filters": "ఫిల్టర్లు",
+        "indexers": "సూచికలు"
+    },
+    "watchtower": {
+        "containers_scanned": "స్కాన్ చేశారు",
+        "containers_updated": "నవీకరించబడింది",
+        "containers_failed": "విఫలమయ్యారు"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
     }
 }

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

@@ -287,5 +287,16 @@
         "rejectedPushes": "Rejected",
         "filters": "Filters",
         "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
     }
 }

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

@@ -287,5 +287,16 @@
         "rejectedPushes": "Rejected",
         "filters": "Filters",
         "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
     }
 }

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

@@ -287,5 +287,16 @@
         "rejectedPushes": "Rejected",
         "filters": "Filters",
         "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
     }
 }

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

@@ -287,5 +287,16 @@
         "rejectedPushes": "Rejected",
         "filters": "Filters",
         "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
     }
 }

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

@@ -287,5 +287,16 @@
         "rejectedPushes": "Rejected",
         "filters": "Filters",
         "indexers": "Indexers"
+    },
+    "watchtower": {
+        "containers_scanned": "Scanned",
+        "containers_updated": "Updated",
+        "containers_failed": "Failed"
+    },
+    "tubearchivist": {
+        "downloads": "Queue",
+        "videos": "Videos",
+        "channels": "Channels",
+        "playlists": "Playlists"
     }
 }

+ 6 - 4
src/components/widgets/datetime/datetime.jsx

@@ -27,10 +27,12 @@ export default function DateTime({ options }) {
   const dateFormat = new Intl.DateTimeFormat(i18n.language, { ...format });
 
   return (
-    <div className="flex flex-row items-center grow justify-end">
-      <span className={`text-theme-800 dark:text-theme-200 ${textSizes[textSize || "lg"]}`}>
-        {dateFormat.format(date)}
-      </span>
+    <div className="flex flex-col justify-center first:ml-0 ml-4">
+      <div className="flex flex-row items-center grow justify-end">
+        <span className={`text-theme-800 dark:text-theme-200 ${textSizes[textSize || "lg"]}`}>
+          {dateFormat.format(date)}
+        </span>
+      </div>
     </div>
   );
 }

+ 2 - 2
src/pages/index.jsx

@@ -35,7 +35,7 @@ const Version = dynamic(() => import("components/version"), {
   ssr: false,
 });
 
-const rightAlignedWidgets = ["weatherapi", "openweathermap", "weather", "search", "datetime"];
+const rightAlignedWidgets = ["weatherapi", "openweathermap", "weather", "openmeteo", "search", "datetime"];
 
 export async function getStaticProps() {
   let logger;
@@ -272,7 +272,7 @@ function Home({ initialSettings }) {
         )}
 
         {bookmarks && (
-          <div className="grow flex flex-wrap pt-0 p-4 sm:p-8">
+          <div className="grow flex flex-wrap pt-0 p-4 sm:p-8 gap-x-2">
             {bookmarks.map((group) => (
               <BookmarksGroup key={group.name} group={group} />
             ))}

+ 1 - 1
src/skeleton/bookmarks.yaml

@@ -1,6 +1,6 @@
 ---
 # For configuration options and examples, please see:
-# https://github.com/benphelps/homepage/wiki/Bookmarks
+# https://gethomepage.dev/en/configs/bookmarks
 
 - Developer:
     - Github:

+ 1 - 1
src/skeleton/docker.yaml

@@ -1,6 +1,6 @@
 ---
 # For configuration options and examples, please see:
-# https://github.com/benphelps/homepage/wiki/Docker-Integration
+# https://gethomepage.dev/en/configs/docker/
 
 # my-docker:
 #   host: 127.0.0.1

+ 1 - 1
src/skeleton/services.yaml

@@ -1,6 +1,6 @@
 ---
 # For configuration options and examples, please see:
-# https://github.com/benphelps/homepage/wiki/Services
+# https://gethomepage.dev/en/configs/services
 
 - My First Group:
     - My First Service:

+ 1 - 1
src/skeleton/settings.yaml

@@ -1,6 +1,6 @@
 ---
 # For configuration options and examples, please see:
-# https://github.com/benphelps/homepage/wiki/Settings
+# https://gethomepage.dev/en/configs/settings
 
 providers:
   openweathermap: openweathermapapikey

+ 1 - 1
src/skeleton/widgets.yaml

@@ -1,6 +1,6 @@
 ---
 # For configuration options and examples, please see:
-# https://github.com/benphelps/homepage/wiki/Information-Widgets
+# https://gethomepage.dev/en/configs/widgets
 
 - resources:
     cpu: true

+ 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

@@ -36,8 +36,10 @@ 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")),
 };
 
 export default components;

+ 1 - 1
src/widgets/emby/widget.js

@@ -10,7 +10,7 @@ const widget = {
     },
     PlayControl: {
       method: "POST",
-      enpoint: "Sessions/{sessionId}/Playing/{command}",
+      endpoint: "Sessions/{sessionId}/Playing/{command}",
       segments: ["sessionId", "command"],
     },
   },

+ 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;

+ 36 - 0
src/widgets/watchtower/component.jsx

@@ -0,0 +1,36 @@
+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: watchData, error: watchError } = useWidgetAPI(widget, "watchtower");
+
+  if (watchError || !watchData) {
+    return <Container error={t("widget.api_error")} />;
+  }
+
+  if (!watchData) {
+    return (
+      <Container service={service}>
+        <Block label="watchtower.containers_scanned " />
+        <Block label="watchtower.containers_updated" />
+        <Block label="watchtower.containers_failed" />
+      </Container>
+    );
+  }
+
+  return (
+    <Container service={service}>
+      <Block label="watchtower.containers_scanned" value={t("common.number", { value: watchData.watchtower_containers_scanned })} />
+      <Block label="watchtower.containers_updated" value={t("common.number", { value: watchData.watchtower_containers_updated })} />
+      <Block label="watchtower.containers_failed" value={t("common.number", { value: watchData.watchtower_containers_failed })} />
+    </Container>
+  );
+}

+ 48 - 0
src/widgets/watchtower/proxy.js

@@ -0,0 +1,48 @@
+import { httpProxy } from "utils/proxy/http";
+import { formatApiCall } from "utils/proxy/api-helpers";
+import getServiceWidget from "utils/config/service-helpers";
+import createLogger from "utils/logger";
+import widgets from "widgets/widgets";
+
+const proxyName = "watchtowerProxyHandler";
+const logger = createLogger(proxyName);
+
+export default async function watchtowerProxyHandler(req, res) {
+    const { group, service, endpoint } = req.query;
+
+  if (!group || !service) {
+    logger.debug("Invalid or missing service '%s' or group '%s'", service, group);
+    return res.status(400).json({ error: "Invalid proxy service type" });
+  }
+
+  const widget = await getServiceWidget(group, service);
+
+  if (!widget) {
+    logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group);
+    return res.status(400).json({ error: "Invalid proxy service type" });
+  }
+
+  const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget }));  
+  
+  const [status, contentType, data] = await httpProxy(url, {
+    method: "GET",
+    headers: {
+      "Authorization": `Bearer ${widget.key}`,
+    }
+  });
+
+  if (status !== 200 || !data) {
+    logger.error("Error getting data from WatchTower: %d.  Data: %s", status, data);
+  }
+
+  const cleanData = data.toString().split("\n").filter(s => s.startsWith("watchtower"))
+  const jsonRes = {}
+  
+  cleanData.map(e => e.split(" ")).forEach(strArray => { 
+    const [key, value] = strArray
+    jsonRes[key] = value
+  }) 
+
+  if (contentType) res.setHeader("Content-Type", contentType);
+  return res.status(status).send(jsonRes);
+}

+ 14 - 0
src/widgets/watchtower/widget.js

@@ -0,0 +1,14 @@
+import watchtowerProxyHandler from "./proxy";
+
+const widget = {
+  api: "{url}/{endpoint}",
+  proxyHandler: watchtowerProxyHandler,
+
+  mappings: {
+    "watchtower": {
+      endpoint: "v1/metrics",
+    },
+  },
+};
+
+export default widget;

+ 4 - 0
src/widgets/widgets.js

@@ -31,8 +31,10 @@ 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'
 
 const widgets = {
   adguard,
@@ -69,9 +71,11 @@ const widgets = {
   tautulli,
   traefik,
   transmission,
+  tubearchivist,
   truenas,
   unifi,
   unifi_console: unifi,
+  watchtower,
 };
 
 export default widgets;