diff --git a/public/locales/ar/common.json b/public/locales/ar/common.json
index 39b77411..a1bf5578 100644
--- a/public/locales/ar/common.json
+++ b/public/locales/ar/common.json
@@ -23,7 +23,7 @@
"free": "متاح",
"used": "مستخدم",
"load": "الضغط",
- "mem": "MEM",
+ "mem": "الذاكرة",
"temp": "TEMP",
"max": "Max",
"uptime": "UP",
@@ -134,7 +134,7 @@
"episodes": "Episodes"
},
"changedetectionio": {
- "totalObserved": "Total Observed",
+ "totalObserved": "مجموع الملاحظات",
"diffsDetected": "Diffs Detected"
},
"tautulli": {
@@ -179,18 +179,22 @@
"sonarr": {
"wanted": "مطلوب",
"queued": "في الإنتظار",
- "series": "سلسلة"
+ "series": "سلسلة",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"wanted": "مطلوب",
"missing": "مفقود",
"queued": "في الإنتظار",
- "movies": "أفلام"
+ "movies": "أفلام",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"lidarr": {
"wanted": "مطلوب",
"queued": "في الإنتظار",
- "albums": "ألبومات"
+ "artists": "Artists"
},
"readarr": {
"wanted": "مطلوب",
@@ -640,5 +644,11 @@
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/bg/common.json b/public/locales/bg/common.json
index 3fec6dbd..35dd13f4 100644
--- a/public/locales/bg/common.json
+++ b/public/locales/bg/common.json
@@ -117,18 +117,22 @@
"sonarr": {
"wanted": "Wanted",
"queued": "Queued",
- "series": "Series"
+ "series": "Series",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"wanted": "Wanted",
"queued": "Queued",
"movies": "Movies",
- "missing": "Missing"
+ "missing": "Missing",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"lidarr": {
"wanted": "Wanted",
"queued": "Queued",
- "albums": "Albums"
+ "artists": "Artists"
},
"readarr": {
"wanted": "Wanted",
@@ -640,5 +644,11 @@
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/ca/common.json b/public/locales/ca/common.json
index 33fe5338..3842feec 100644
--- a/public/locales/ca/common.json
+++ b/public/locales/ca/common.json
@@ -26,7 +26,9 @@
"sonarr": {
"wanted": "Volgut",
"queued": "En cua",
- "series": "Sèries"
+ "series": "Sèries",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"speedtest": {
"ping": "Ping",
@@ -99,7 +101,9 @@
"wanted": "Volgut",
"queued": "En cua",
"movies": "Pel·lícules",
- "missing": "Faltant"
+ "missing": "Faltant",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"readarr": {
"wanted": "Volgut",
@@ -173,7 +177,7 @@
"lidarr": {
"wanted": "Volgut",
"queued": "En cua",
- "albums": "Àlbums"
+ "artists": "Artists"
},
"adguard": {
"queries": "Consultes",
@@ -640,5 +644,11 @@
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/cs/common.json b/public/locales/cs/common.json
index 6088851b..4e16a37a 100644
--- a/public/locales/cs/common.json
+++ b/public/locales/cs/common.json
@@ -133,18 +133,22 @@
"sonarr": {
"wanted": "Hledané",
"queued": "Ve frontě",
- "series": "Seriály"
+ "series": "Seriály",
+ "unknown": "Unknown",
+ "queue": "Queue"
},
"radarr": {
"wanted": "Hledané",
"missing": "Chybějící",
"queued": "Ve frontě",
- "movies": "Filmy"
+ "movies": "Filmy",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"lidarr": {
"wanted": "Hledané",
"queued": "Ve frontě",
- "albums": "Alba"
+ "artists": "Artists"
},
"readarr": {
"wanted": "Hledané",
@@ -640,5 +644,11 @@
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadSpeed": "Download Speed",
+ "downloadCount": "Queue Count",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/da/common.json b/public/locales/da/common.json
index 3769da6f..76ff86dd 100644
--- a/public/locales/da/common.json
+++ b/public/locales/da/common.json
@@ -9,12 +9,14 @@
"queued": "I Kø",
"movies": "Film",
"wanted": "Ønskede",
- "missing": "Mangler"
+ "missing": "Mangler",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"lidarr": {
"wanted": "Ønsket",
"queued": "I Kø",
- "albums": "Albums"
+ "artists": "Artists"
},
"jellyseerr": {
"available": "Tilgængelig",
@@ -264,7 +266,9 @@
"sonarr": {
"wanted": "Ønsket",
"queued": "I Kø",
- "series": "Serier"
+ "series": "Serier",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"readarr": {
"wanted": "Ønskede",
@@ -640,5 +644,11 @@
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadSpeed": "Download Speed",
+ "downloadCount": "Queue Count",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/de/common.json b/public/locales/de/common.json
index b62c5e6a..521e2cec 100644
--- a/public/locales/de/common.json
+++ b/public/locales/de/common.json
@@ -66,13 +66,17 @@
"sonarr": {
"wanted": "Gesucht",
"queued": "In Warteschlange",
- "series": "Serien"
+ "series": "Serien",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"wanted": "Gesucht",
"queued": "In Warteschlange",
"movies": "Filme",
- "missing": "Fehlt"
+ "missing": "Fehlt",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"readarr": {
"wanted": "Gesucht",
@@ -173,7 +177,7 @@
"lidarr": {
"wanted": "Gesucht",
"queued": "In Warteschlange",
- "albums": "Alben"
+ "artists": "Artists"
},
"adguard": {
"queries": "Anfragen",
@@ -640,5 +644,11 @@
"connected": "Verbunden",
"new_devices": "Neue Geräte",
"down_alerts": "Down Alarme"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/el/common.json b/public/locales/el/common.json
index d42fdfe5..b36b43f6 100644
--- a/public/locales/el/common.json
+++ b/public/locales/el/common.json
@@ -206,7 +206,9 @@
"sonarr": {
"series": "Σειρές",
"wanted": "Επιθυμούντε",
- "queued": "Σε σειρά"
+ "queued": "Σε σειρά",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"downloadstation": {
"download": "Μεταφόρτωση",
@@ -218,12 +220,14 @@
"wanted": "Επιθυμούντε",
"missing": "Απουσιάζει",
"queued": "Σε σειρά",
- "movies": "Ταινίες"
+ "movies": "Ταινίες",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"lidarr": {
"wanted": "Θέλετε",
"queued": "Στη σειρά",
- "albums": "Δίσκοι"
+ "artists": "Artists"
},
"readarr": {
"wanted": "Θέλετε",
@@ -640,5 +644,11 @@
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index 1de843cc..e26f4a0b 100755
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -194,18 +194,22 @@
"sonarr": {
"wanted": "Wanted",
"queued": "Queued",
- "series": "Series"
+ "series": "Series",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"wanted": "Wanted",
"missing": "Missing",
"queued": "Queued",
- "movies": "Movies"
+ "movies": "Movies",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"lidarr": {
"wanted": "Wanted",
"queued": "Queued",
- "albums": "Albums"
+ "artists": "Artists"
},
"readarr": {
"wanted": "Wanted",
@@ -650,11 +654,13 @@
"monitoring": "Monitoring",
"updates": "Updates"
},
- "nextpvr": {
- "upcoming": "Upcoming",
- "ready": "Recent"
- },
"wgeasy": {
"clients": "Total Clients"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size",
+ "downloadSpeed": "Speed"
}
}
\ No newline at end of file
diff --git a/public/locales/eo/common.json b/public/locales/eo/common.json
index 50d305ac..908169cb 100644
--- a/public/locales/eo/common.json
+++ b/public/locales/eo/common.json
@@ -131,18 +131,22 @@
"sonarr": {
"wanted": "Wanted",
"queued": "Queued",
- "series": "Serio"
+ "series": "Serio",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"wanted": "Wanted",
"missing": "Missing",
"queued": "Queued",
- "movies": "Filmoj"
+ "movies": "Filmoj",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"lidarr": {
"wanted": "Wanted",
"queued": "Queued",
- "albums": "Albumoj"
+ "artists": "Artists"
},
"readarr": {
"wanted": "Wanted",
@@ -640,5 +644,11 @@
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/es/common.json b/public/locales/es/common.json
index 1ca5a62f..91c48ce1 100644
--- a/public/locales/es/common.json
+++ b/public/locales/es/common.json
@@ -66,13 +66,17 @@
"sonarr": {
"wanted": "Buscando",
"queued": "En cola",
- "series": "Series"
+ "series": "Series",
+ "queue": "Poner a la cola",
+ "unknown": "Desconocido"
},
"radarr": {
"wanted": "Buscando",
"queued": "En cola",
"movies": "Películas",
- "missing": "Faltan"
+ "missing": "Faltan",
+ "queue": "Poner a la cola",
+ "unknown": "Desconocido"
},
"readarr": {
"wanted": "Buscando",
@@ -173,7 +177,7 @@
"lidarr": {
"queued": "En cola",
"wanted": "Buscando",
- "albums": "Álbumes"
+ "artists": "Artistas"
},
"adguard": {
"queries": "Consultas",
@@ -640,5 +644,11 @@
"connected": "Conectado",
"new_devices": "Nuevos dispositivos",
"down_alerts": "Alertas"
+ },
+ "jdownloader": {
+ "downloadCount": "Recuento de las colas",
+ "downloadSpeed": "Velocidad de Descarga",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/fi/common.json b/public/locales/fi/common.json
index aad674a1..c3875b21 100644
--- a/public/locales/fi/common.json
+++ b/public/locales/fi/common.json
@@ -94,18 +94,22 @@
"sonarr": {
"wanted": "Haluttu",
"queued": "Jonossa",
- "series": "Sarja"
+ "series": "Sarja",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"wanted": "Haluttu",
"queued": "Jonossa",
"movies": "Elokuvia",
- "missing": "Missing"
+ "missing": "Missing",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"lidarr": {
"wanted": "Haluttu",
"queued": "Jonossa",
- "albums": "Albumeja"
+ "artists": "Artists"
},
"readarr": {
"wanted": "Haluttu",
@@ -640,5 +644,11 @@
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/fr/common.json b/public/locales/fr/common.json
index 2e9e0e0d..43b44d45 100644
--- a/public/locales/fr/common.json
+++ b/public/locales/fr/common.json
@@ -66,13 +66,17 @@
"sonarr": {
"wanted": "Demande",
"queued": "Attente",
- "series": "Séries"
+ "series": "Séries",
+ "queue": "Attente",
+ "unknown": "Inconnu"
},
"radarr": {
"wanted": "Demande",
"queued": "Attente",
"movies": "Films",
- "missing": "Manquant"
+ "missing": "Manquant",
+ "queue": "Attente",
+ "unknown": "Inconnu"
},
"readarr": {
"wanted": "Demande",
@@ -173,7 +177,7 @@
"lidarr": {
"wanted": "Demandé",
"queued": "En queue",
- "albums": "Albums"
+ "artists": "Artistes"
},
"adguard": {
"queries": "Requêtes",
@@ -397,7 +401,7 @@
"queue": "À traiter",
"processed": "Traité",
"errored": "En erreur",
- "saved": "Economisé"
+ "saved": "Libéré"
},
"miniflux": {
"read": "Lu",
@@ -640,5 +644,11 @@
"connected": "Connecté",
"new_devices": "Nouvel Appareil",
"down_alerts": "Alertes"
+ },
+ "jdownloader": {
+ "downloadCount": "Total en attente",
+ "downloadSpeed": "Vitesse de téléchargement",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/he/common.json b/public/locales/he/common.json
index 50f61ae5..41e5746b 100644
--- a/public/locales/he/common.json
+++ b/public/locales/he/common.json
@@ -94,18 +94,22 @@
"sonarr": {
"wanted": "מבוקש",
"queued": "בתור",
- "series": "סדרות"
+ "series": "סדרות",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"wanted": "מבוקש",
"queued": "בתור",
"movies": "סרטים",
- "missing": "Missing"
+ "missing": "Missing",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"lidarr": {
"wanted": "מבוקש",
"queued": "בתור",
- "albums": "אלבומים"
+ "artists": "Artists"
},
"readarr": {
"wanted": "מבוקש",
@@ -640,5 +644,11 @@
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/hi/common.json b/public/locales/hi/common.json
index 6736caf0..155235a9 100644
--- a/public/locales/hi/common.json
+++ b/public/locales/hi/common.json
@@ -155,18 +155,22 @@
"sonarr": {
"wanted": "Wanted",
"queued": "Queued",
- "series": "Series"
+ "series": "Series",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"wanted": "Wanted",
"missing": "Missing",
"queued": "Queued",
- "movies": "Movies"
+ "movies": "Movies",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"lidarr": {
"wanted": "Wanted",
"queued": "Queued",
- "albums": "Albums"
+ "artists": "Artists"
},
"overseerr": {
"pending": "Pending",
@@ -640,5 +644,11 @@
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/hr/common.json b/public/locales/hr/common.json
index db20a999..94dfae26 100644
--- a/public/locales/hr/common.json
+++ b/public/locales/hr/common.json
@@ -125,18 +125,22 @@
"sonarr": {
"wanted": "Zatraženo",
"queued": "U redu čekanja",
- "series": "Serije"
+ "series": "Serije",
+ "unknown": "Unknown",
+ "queue": "Queue"
},
"radarr": {
"wanted": "Zatraženo",
"queued": "U redu čekanja",
"movies": "Filmovi",
- "missing": "Nedostaje"
+ "missing": "Nedostaje",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"lidarr": {
"wanted": "Zatraženo",
"queued": "U redu čekanja",
- "albums": "Albumi"
+ "artists": "Artists"
},
"readarr": {
"wanted": "Zatraženo",
@@ -239,11 +243,11 @@
"uptime": "UP",
"days": "d",
"hours": "h",
- "used": "Used",
- "load": "Load",
- "warn": "Warn",
- "total": "Total",
- "free": "Free"
+ "used": "Korišteno",
+ "load": "Opterećenje",
+ "warn": "Upozori",
+ "total": "Ukupno",
+ "free": "Slobodno"
},
"changedetectionio": {
"totalObserved": "Ukupno promatrano",
@@ -478,7 +482,7 @@
"up": "Aktivne stranice",
"down": "Neaktivne stranice",
"uptime": "Radno vrijeme",
- "incident": "Incident",
+ "incident": "Slučaj",
"m": "min"
},
"komga": {
@@ -609,36 +613,42 @@
"poolUsage": "Korištenje memorijskog skupa",
"cpuUsage": "Korištenje procesora",
"memUsage": "Korištenje memorije",
- "volumeUsage": "Volume Usage",
- "invalid": "Invalid"
+ "volumeUsage": "Korištenje jedinice memorije",
+ "invalid": "Neispravno"
},
"pfsense": {
- "load": "Load Avg",
- "memory": "Mem Usage",
- "wanStatus": "WAN Status",
+ "load": "Prosječno opterećenje",
+ "memory": "Korištenje memorije",
+ "wanStatus": "Stanje WAN-a",
"up": "Up",
"down": "Down",
- "temp": "Temp",
- "disk": "Disk Usage",
+ "temp": "Temperatura",
+ "disk": "Korištenje diska",
"wanIP": "WAN IP"
},
"caddy": {
- "upstreams": "Upstreams",
- "requests": "Current requests",
- "requests_failed": "Failed requests"
+ "upstreams": "Glavne grane",
+ "requests": "Aktualni zahtjevi",
+ "requests_failed": "Neuspjeli zahtjevi"
},
"evcc": {
- "pv_power": "Production",
- "battery_soc": "Battery",
- "grid_power": "Grid",
- "home_power": "Consumption",
- "charge_power": "Charger",
+ "pv_power": "Proizvodnja",
+ "battery_soc": "Baterija",
+ "grid_power": "Raspored",
+ "home_power": "Potrošnja",
+ "charge_power": "Punjač",
"watt_hour": "Wh"
},
"pialert": {
- "total": "Total",
- "connected": "Connected",
- "new_devices": "New Devices",
- "down_alerts": "Down Alerts"
+ "total": "Ukupno",
+ "connected": "Povezano",
+ "new_devices": "Novi uređaji",
+ "down_alerts": "Obavijest o rušenju"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/hu/common.json b/public/locales/hu/common.json
index b417692d..016a54d8 100644
--- a/public/locales/hu/common.json
+++ b/public/locales/hu/common.json
@@ -31,9 +31,9 @@
"healthy": "Healthy"
},
"lidarr": {
- "albums": "Albumok",
"wanted": "Keresett",
- "queued": "Sorban áll"
+ "queued": "Sorban áll",
+ "artists": "Artists"
},
"readarr": {
"wanted": "Keresett",
@@ -108,13 +108,17 @@
"sonarr": {
"wanted": "Keresett",
"queued": "Sorban áll",
- "series": "Sorozat"
+ "series": "Sorozat",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"wanted": "Keresett",
"queued": "Sorban áll",
"movies": "Filmek",
- "missing": "Missing"
+ "missing": "Missing",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"ombi": {
"pending": "Függőben",
@@ -640,5 +644,11 @@
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadSpeed": "Download Speed",
+ "downloadCount": "Queue Count",
+ "downloadTotalBytes": "Size",
+ "downloadBytesRemaining": "Remaining"
}
}
diff --git a/public/locales/id/common.json b/public/locales/id/common.json
index e9169cbd..0be279a5 100644
--- a/public/locales/id/common.json
+++ b/public/locales/id/common.json
@@ -55,18 +55,22 @@
"sonarr": {
"wanted": "Wanted",
"queued": "Queued",
- "series": "Series"
+ "series": "Series",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"wanted": "Wanted",
"missing": "Missing",
"queued": "Queued",
- "movies": "Movies"
+ "movies": "Movies",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"lidarr": {
"wanted": "Wanted",
"queued": "Queued",
- "albums": "Albums"
+ "artists": "Artists"
},
"readarr": {
"wanted": "Wanted",
@@ -640,5 +644,11 @@
"transcoding": "Transcoding",
"bitrate": "Bitrate",
"no_active": "No Active Streams"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/it/common.json b/public/locales/it/common.json
index 8dc065d4..68aaa2f5 100644
--- a/public/locales/it/common.json
+++ b/public/locales/it/common.json
@@ -81,13 +81,17 @@
"sonarr": {
"series": "Serie",
"wanted": "Richiesti",
- "queued": "In coda"
+ "queued": "In coda",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"wanted": "Richiesti",
"queued": "In coda",
"movies": "Film",
- "missing": "Mancanti"
+ "missing": "Mancanti",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"readarr": {
"wanted": "Richiesti",
@@ -173,7 +177,7 @@
"lidarr": {
"wanted": "Mancanti",
"queued": "In coda",
- "albums": "Album"
+ "artists": "Artists"
},
"adguard": {
"queries": "Interrogazioni",
@@ -640,5 +644,11 @@
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/ja/common.json b/public/locales/ja/common.json
index bcca3d2a..1bc37e96 100644
--- a/public/locales/ja/common.json
+++ b/public/locales/ja/common.json
@@ -63,7 +63,7 @@
"resources": {
"cpu": "CPU",
"total": "合計",
- "free": "フリー",
+ "free": "Free",
"used": "使用",
"load": "ロード",
"mem": "MEM",
@@ -193,18 +193,22 @@
"sonarr": {
"wanted": "募集中",
"queued": "待機中",
- "series": "シリーズ"
+ "series": "シリーズ",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"wanted": "募集中",
"missing": "不明",
"queued": "キュー",
- "movies": "映画"
+ "movies": "映画",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"lidarr": {
"wanted": "募集中",
"queued": "キュー",
- "albums": "アルバム"
+ "artists": "Artists"
},
"readarr": {
"wanted": "募集中",
@@ -605,11 +609,11 @@
"ago": "{{value}} 前"
},
"qnap": {
- "cpuUsage": "CPU Usage",
- "memUsage": "MEM Usage",
- "systemTempC": "System Temp",
- "poolUsage": "Pool Usage",
- "volumeUsage": "Volume Usage",
+ "cpuUsage": "CPU使用量",
+ "memUsage": "MEM使用量",
+ "systemTempC": "システム温度",
+ "poolUsage": "プール使用量",
+ "volumeUsage": "ボリューム使用量",
"invalid": "Invalid"
},
"pfsense": {
@@ -629,16 +633,22 @@
},
"evcc": {
"watt_hour": "Wh",
- "pv_power": "Production",
- "battery_soc": "Battery",
- "grid_power": "Grid",
- "home_power": "Consumption",
- "charge_power": "Charger"
+ "pv_power": "発電量",
+ "battery_soc": "バッテリー",
+ "grid_power": "グリッド",
+ "home_power": "消費",
+ "charge_power": "チャージャー"
},
"pialert": {
"total": "Total",
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/ko/common.json b/public/locales/ko/common.json
index 4e5e6298..f9037f52 100644
--- a/public/locales/ko/common.json
+++ b/public/locales/ko/common.json
@@ -163,18 +163,22 @@
"sonarr": {
"wanted": "요청",
"queued": "대기 중",
- "series": "시리즈"
+ "series": "시리즈",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"wanted": "요청",
"missing": "빠짐",
"queued": "대기 중",
- "movies": "영화"
+ "movies": "영화",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"lidarr": {
"wanted": "요청",
"queued": "대기 중",
- "albums": "앨범"
+ "artists": "Artists"
},
"readarr": {
"wanted": "요청",
@@ -640,5 +644,11 @@
"connected": "Connected",
"down_alerts": "Down Alerts",
"new_devices": "New Devices"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/lv/common.json b/public/locales/lv/common.json
index fa73f2c7..70361ee2 100644
--- a/public/locales/lv/common.json
+++ b/public/locales/lv/common.json
@@ -154,18 +154,22 @@
"sonarr": {
"wanted": "Wanted",
"queued": "Queued",
- "series": "Series"
+ "series": "Series",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"wanted": "Wanted",
"missing": "Missing",
"queued": "Queued",
- "movies": "Filmas"
+ "movies": "Filmas",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"lidarr": {
"wanted": "Wanted",
"queued": "Queued",
- "albums": "Albumi"
+ "artists": "Artists"
},
"readarr": {
"wanted": "Wanted",
@@ -640,5 +644,11 @@
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/ms/common.json b/public/locales/ms/common.json
index da42ff44..70fd66e3 100644
--- a/public/locales/ms/common.json
+++ b/public/locales/ms/common.json
@@ -33,8 +33,8 @@
},
"lidarr": {
"queued": "Dibaris Gilir",
- "albums": "Album",
- "wanted": "Mahu"
+ "wanted": "Mahu",
+ "artists": "Artists"
},
"readarr": {
"wanted": "Mahu",
@@ -233,13 +233,17 @@
"sonarr": {
"wanted": "Mahu",
"queued": "Dibaris Gilir",
- "series": "Bersiri"
+ "series": "Bersiri",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"wanted": "Mahu",
"missing": "Hilang",
"queued": "Dibaris Gilir",
- "movies": "Filem"
+ "movies": "Filem",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"bazarr": {
"missingEpisodes": "Episod Yang Hilang",
@@ -640,5 +644,11 @@
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/nb-NO/common.json b/public/locales/nb-NO/common.json
index e7a2dd5c..79124218 100644
--- a/public/locales/nb-NO/common.json
+++ b/public/locales/nb-NO/common.json
@@ -66,13 +66,17 @@
"sonarr": {
"wanted": "Ønsket",
"queued": "I kø",
- "series": "Serie"
+ "series": "Serie",
+ "unknown": "Unknown",
+ "queue": "Queue"
},
"radarr": {
"wanted": "Ønsket",
"queued": "I kø",
"movies": "Filmer",
- "missing": "Missing"
+ "missing": "Missing",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"readarr": {
"wanted": "Wanted",
@@ -173,7 +177,7 @@
"lidarr": {
"wanted": "Wanted",
"queued": "Queued",
- "albums": "Albums"
+ "artists": "Artists"
},
"adguard": {
"queries": "Queries",
@@ -640,5 +644,11 @@
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/nl/common.json b/public/locales/nl/common.json
index 0be39980..139e9051 100644
--- a/public/locales/nl/common.json
+++ b/public/locales/nl/common.json
@@ -82,13 +82,17 @@
"sonarr": {
"wanted": "Gezocht",
"queued": "In de wachtrij",
- "series": "Series"
+ "series": "Series",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"movies": "Films",
"wanted": "Gezocht",
"queued": "In de wachtrij",
- "missing": "Missend"
+ "missing": "Missend",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"readarr": {
"wanted": "Gezocht",
@@ -173,7 +177,7 @@
"lidarr": {
"wanted": "Gezocht",
"queued": "In de wachtrij",
- "albums": "Albums"
+ "artists": "Artists"
},
"adguard": {
"queries": "Queries",
@@ -640,5 +644,11 @@
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/pl/common.json b/public/locales/pl/common.json
index a900de72..6af51828 100644
--- a/public/locales/pl/common.json
+++ b/public/locales/pl/common.json
@@ -110,18 +110,22 @@
"sonarr": {
"wanted": "Poszukiwane",
"queued": "W kolejce",
- "series": "Seriale"
+ "series": "Seriale",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"wanted": "Poszukiwane",
"queued": "W kolejce",
"movies": "Filmy",
- "missing": "Brakujące"
+ "missing": "Brakujące",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"lidarr": {
"wanted": "Poszukiwane",
"queued": "W kolejce",
- "albums": "Albumy"
+ "artists": "Artists"
},
"readarr": {
"wanted": "Poszukiwane",
@@ -640,5 +644,11 @@
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/pt-BR/common.json b/public/locales/pt-BR/common.json
index bc405db0..d2146d2c 100644
--- a/public/locales/pt-BR/common.json
+++ b/public/locales/pt-BR/common.json
@@ -112,18 +112,22 @@
"sonarr": {
"wanted": "Desejado",
"queued": "Na fila",
- "series": "Séries"
+ "series": "Séries",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"wanted": "Desejado",
"queued": "Na fila",
"movies": "Filmes",
- "missing": "Faltando"
+ "missing": "Faltando",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"lidarr": {
"wanted": "Desejado",
"queued": "Na fila",
- "albums": "Álbuns"
+ "artists": "Artists"
},
"readarr": {
"wanted": "Desejado",
@@ -640,5 +644,11 @@
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/pt/common.json b/public/locales/pt/common.json
index b0df5367..5c84e6c5 100644
--- a/public/locales/pt/common.json
+++ b/public/locales/pt/common.json
@@ -66,13 +66,17 @@
"sonarr": {
"wanted": "Desejada",
"queued": "Em fila",
- "series": "Séries"
+ "series": "Séries",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"wanted": "Desejado",
"queued": "Fila",
"movies": "Filmes",
- "missing": "Faltando"
+ "missing": "Faltando",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"readarr": {
"wanted": "Desejados",
@@ -186,7 +190,7 @@
"lidarr": {
"queued": "Enfileirado",
"wanted": "Desejado",
- "albums": "Álbuns"
+ "artists": "Artists"
},
"adguard": {
"queries": "Consultas",
@@ -586,12 +590,12 @@
"switches_on": "Interruptores Ligados"
},
"freshrss": {
- "subscriptions": "Subscriptions",
- "unread": "Unread"
+ "subscriptions": "Assinaturas",
+ "unread": "Não lida"
},
"channelsdvrserver": {
"shows": "Shows",
- "recordings": "Recordings",
+ "recordings": "Gravações",
"scheduled": "Scheduled",
"passes": "Passes"
},
@@ -633,21 +637,27 @@
},
"caddy": {
"upstreams": "Upstreams",
- "requests": "Current requests",
- "requests_failed": "Failed requests"
+ "requests": "Solicitações atuais",
+ "requests_failed": "Solicitações com falha"
},
"evcc": {
- "pv_power": "Production",
- "battery_soc": "Battery",
- "grid_power": "Grid",
- "home_power": "Consumption",
- "charge_power": "Charger",
- "watt_hour": "Wh"
+ "pv_power": "Produção",
+ "battery_soc": "Bateria",
+ "grid_power": "Grade",
+ "home_power": "Consumo",
+ "charge_power": "Carregador",
+ "watt_hour": "Kw"
},
"pialert": {
"total": "Total",
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/ro/common.json b/public/locales/ro/common.json
index a1f62db2..e8bf9ac7 100644
--- a/public/locales/ro/common.json
+++ b/public/locales/ro/common.json
@@ -134,18 +134,22 @@
"sonarr": {
"wanted": "Dorite",
"queued": "În coadă",
- "series": "Seriale"
+ "series": "Seriale",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"queued": "În coadă",
"wanted": "Dorite",
"movies": "Filme",
- "missing": "Missing"
+ "missing": "Missing",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"lidarr": {
"wanted": "Dorite",
"queued": "În coadă",
- "albums": "Albume"
+ "artists": "Artists"
},
"readarr": {
"wanted": "Dorite",
@@ -640,5 +644,11 @@
"down_alerts": "Down Alerts",
"total": "Total",
"connected": "Connected"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/ru/common.json b/public/locales/ru/common.json
index 454bfdfe..d3cd6d48 100644
--- a/public/locales/ru/common.json
+++ b/public/locales/ru/common.json
@@ -66,13 +66,17 @@
"sonarr": {
"wanted": "Хотел",
"queued": "В очереди",
- "series": "Серии"
+ "series": "Серии",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"wanted": "Хотел",
"queued": "В очереди",
"movies": "Фильмы",
- "missing": "Пропущено"
+ "missing": "Пропущено",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"readarr": {
"wanted": "Хотел",
@@ -173,7 +177,7 @@
"lidarr": {
"wanted": "Хотел",
"queued": "В очереди",
- "albums": "Альбомы"
+ "artists": "Artists"
},
"adguard": {
"queries": "Запросы",
@@ -640,5 +644,11 @@
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/sk/common.json b/public/locales/sk/common.json
index a66ef5e6..5f95889c 100644
--- a/public/locales/sk/common.json
+++ b/public/locales/sk/common.json
@@ -273,18 +273,22 @@
"sonarr": {
"wanted": "Wanted",
"queued": "Queued",
- "series": "Series"
+ "series": "Series",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"wanted": "Wanted",
"missing": "Missing",
"queued": "Queued",
- "movies": "Movies"
+ "movies": "Movies",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"lidarr": {
"wanted": "Wanted",
"queued": "Queued",
- "albums": "Albums"
+ "artists": "Artists"
},
"readarr": {
"wanted": "Wanted",
@@ -640,5 +644,11 @@
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/sl/common.json b/public/locales/sl/common.json
index afca8429..24d4b8f7 100644
--- a/public/locales/sl/common.json
+++ b/public/locales/sl/common.json
@@ -235,18 +235,22 @@
"sonarr": {
"wanted": "Iskano",
"queued": "V vrsti",
- "series": "Serije"
+ "series": "Serije",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"wanted": "Iskano",
"missing": "Manjka",
"queued": "V vrsti",
- "movies": "Filmi"
+ "movies": "Filmi",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"lidarr": {
"wanted": "Iskano",
"queued": "V vrsti",
- "albums": "Albumi"
+ "artists": "Artists"
},
"readarr": {
"wanted": "Iskano",
@@ -640,5 +644,11 @@
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/sr/common.json b/public/locales/sr/common.json
index b30db3fe..9ef56193 100644
--- a/public/locales/sr/common.json
+++ b/public/locales/sr/common.json
@@ -117,18 +117,22 @@
"sonarr": {
"wanted": "Wanted",
"queued": "Queued",
- "series": "Series"
+ "series": "Series",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"wanted": "Wanted",
"queued": "Queued",
"movies": "Movies",
- "missing": "Missing"
+ "missing": "Missing",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"lidarr": {
"wanted": "Wanted",
"queued": "Queued",
- "albums": "Albums"
+ "artists": "Artists"
},
"readarr": {
"wanted": "Wanted",
@@ -640,5 +644,11 @@
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/sv/common.json b/public/locales/sv/common.json
index 37b38a71..c3325844 100644
--- a/public/locales/sv/common.json
+++ b/public/locales/sv/common.json
@@ -88,18 +88,22 @@
"sonarr": {
"wanted": "Eftersöker",
"queued": "I kö",
- "series": "Serier"
+ "series": "Serier",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"wanted": "Eftersöker",
"queued": "I kö",
"movies": "Filmer",
- "missing": "Missing"
+ "missing": "Missing",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"lidarr": {
"wanted": "Eftersöker",
"queued": "I kö",
- "albums": "Album"
+ "artists": "Artists"
},
"readarr": {
"wanted": "Eftersökt",
@@ -640,5 +644,11 @@
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/te/common.json b/public/locales/te/common.json
index e357b266..b7d11223 100644
--- a/public/locales/te/common.json
+++ b/public/locales/te/common.json
@@ -111,18 +111,22 @@
"sonarr": {
"wanted": "కావలెను",
"queued": "క్యూయూఎడ్",
- "series": "సిరీస్"
+ "series": "సిరీస్",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"wanted": "కావలెను",
"queued": "క్యూయూఎడ్",
"movies": "సినిమాలు",
- "missing": "మిస్సింగ్"
+ "missing": "మిస్సింగ్",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"lidarr": {
"wanted": "కావలెను",
"queued": "క్యూయూఎడ్",
- "albums": "ఆల్బములు"
+ "artists": "Artists"
},
"bazarr": {
"missingEpisodes": "ఎపిసోడ్లు లేవు",
@@ -640,5 +644,11 @@
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/th/common.json b/public/locales/th/common.json
index 65c1b90a..2afb51f2 100644
--- a/public/locales/th/common.json
+++ b/public/locales/th/common.json
@@ -190,7 +190,9 @@
"sonarr": {
"wanted": "Wanted",
"queued": "Queued",
- "series": "Series"
+ "series": "Series",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"readarr": {
"queued": "Queued",
@@ -216,12 +218,14 @@
"wanted": "Wanted",
"missing": "Missing",
"queued": "Queued",
- "movies": "Movies"
+ "movies": "Movies",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"lidarr": {
"wanted": "Wanted",
"queued": "Queued",
- "albums": "Albums"
+ "artists": "Artists"
},
"ombi": {
"pending": "Pending",
@@ -640,5 +644,11 @@
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/tr/common.json b/public/locales/tr/common.json
index d5be780e..b152e0f7 100644
--- a/public/locales/tr/common.json
+++ b/public/locales/tr/common.json
@@ -24,13 +24,13 @@
"used": "Kullanımda",
"load": "Yük",
"mem": "MEM",
- "temp": "TEMP",
- "max": "Max",
- "uptime": "UP",
- "months": "mo",
- "days": "d",
- "hours": "h",
- "minutes": "m"
+ "temp": "Geçici",
+ "max": "En Yüksek",
+ "uptime": "Çalışma Süresi",
+ "months": "Ay",
+ "days": "Gün",
+ "hours": "Saat",
+ "minutes": "Dakika"
},
"unifi": {
"users": "Kullanıcılar",
@@ -57,23 +57,23 @@
"offline": "Çevrimdışı",
"error": "Hata",
"unknown": "Bilinmiyor",
- "running": "Running",
- "starting": "Starting",
- "unhealthy": "Unhealthy",
- "not_found": "Not Found",
- "exited": "Exited",
- "partial": "Partial",
- "healthy": "Healthy"
+ "running": "Çalışan",
+ "starting": "Başlatılıyor",
+ "unhealthy": "Sağlıksız",
+ "not_found": "Bulunamadı",
+ "exited": "Durduruldu",
+ "partial": "Parçalı",
+ "healthy": "Sağlık"
},
"emby": {
"playing": "Oynatılıyor",
"transcoding": "Dönüştürülüyor",
"bitrate": "Bit Oranı",
"no_active": "Aktif akış yok",
- "movies": "Movies",
- "series": "Series",
- "episodes": "Episodes",
- "songs": "Songs"
+ "movies": "Filmler",
+ "series": "Diziler",
+ "episodes": "Bölümler",
+ "songs": "Şarkılar"
},
"tautulli": {
"playing": "Oynatılıyor",
@@ -90,7 +90,7 @@
"streams": "Aktif Akış",
"movies": "Filmler",
"tv": "TV Showları",
- "albums": "Albums"
+ "albums": "Albümler"
},
"sabnzbd": {
"rate": "Oran",
@@ -117,18 +117,22 @@
"sonarr": {
"wanted": "Aranan",
"queued": "Kuyrukta",
- "series": "Seriler"
+ "series": "Seriler",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"wanted": "Aranan",
"queued": "Kuyrukta",
"movies": "Filmler",
- "missing": "Kayıp"
+ "missing": "Kayıp",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"lidarr": {
"wanted": "Aranan",
"queued": "Kuyrukta",
- "albums": "Albümler"
+ "artists": "Artists"
},
"readarr": {
"wanted": "Aranan",
@@ -159,7 +163,7 @@
"queries": "Sorgular",
"blocked": "Engellenen",
"gravity": "Yer Çekimi",
- "blocked_percent": "Blocked %"
+ "blocked_percent": "Engellenen %"
},
"adguard": {
"queries": "Sorgular",
@@ -235,15 +239,15 @@
"glances": {
"cpu": "İşlemci",
"wait": "Lütfen bekleyiniz",
- "temp": "TEMP",
- "uptime": "UP",
- "days": "d",
- "hours": "h",
- "load": "Load",
- "warn": "Warn",
- "total": "Total",
- "free": "Free",
- "used": "Used"
+ "temp": "Sıcaklık",
+ "uptime": "Çalışma Süresi",
+ "days": "Gün",
+ "hours": "Saat",
+ "load": "Yük",
+ "warn": "Uyarı",
+ "total": "Toplam",
+ "free": "Boş",
+ "used": "Kullanım"
},
"changedetectionio": {
"totalObserved": "Toplam Gözlenen",
@@ -311,9 +315,9 @@
"bookmark": "Yer İmi",
"service": "Hizmet",
"search": "Ara",
- "custom": "Custom",
- "visit": "Visit",
- "url": "URL"
+ "custom": "Özel",
+ "visit": "Ziyaret",
+ "url": "Link"
},
"homebridge": {
"available_update": "Sistem",
@@ -384,14 +388,14 @@
"deluge": {
"download": "İndir",
"upload": "Yükle",
- "leech": "Leech",
+ "leech": "Tüketici",
"seed": "Tohum"
},
"flood": {
"download": "İndir",
"upload": "Yükle",
- "leech": "Leech",
- "seed": "Tohum"
+ "leech": "Tüketici",
+ "seed": "Sağlayıcı"
},
"tdarr": {
"queue": "Sıra",
@@ -421,7 +425,7 @@
"downloadstation": {
"download": "İndir",
"upload": "Yükle",
- "leech": "Leech",
+ "leech": "Tüketici",
"seed": "Tohum"
},
"mikrotik": {
@@ -448,7 +452,7 @@
"layers": "Katmanlar"
},
"medusa": {
- "wanted": "Wanted",
+ "wanted": "Aranan",
"queued": "Kuyrukta",
"series": "Seri"
},
@@ -554,11 +558,11 @@
"targets_total": "Total Targets"
},
"minecraft": {
- "players": "Players",
- "version": "Version",
- "status": "Status",
- "up": "Online",
- "down": "Offline"
+ "players": "Oyuncular",
+ "version": "Versiyon",
+ "status": "Durum",
+ "up": "Çevrimiçi",
+ "down": "Çevrimdışı"
},
"ghostfolio": {
"gross_percent_today": "Today",
@@ -577,40 +581,40 @@
"switches_on": "Switches On"
},
"freshrss": {
- "subscriptions": "Subscriptions",
- "unread": "Unread"
+ "subscriptions": "Abonelikler",
+ "unread": "Okunmamış"
},
"channelsdvrserver": {
- "shows": "Shows",
- "recordings": "Recordings",
- "scheduled": "Scheduled",
- "passes": "Passes"
+ "shows": "Diziler",
+ "recordings": "Kayıtlar",
+ "scheduled": "Planlanmış",
+ "passes": "Geçilenler"
},
"whatsupdocker": {
"monitoring": "Monitoring",
"updates": "Updates"
},
"tailscale": {
- "never": "Never",
- "last_seen": "Last Seen",
- "now": "Now",
- "years": "{{number}}y",
- "weeks": "{{number}}w",
- "days": "{{number}}d",
- "hours": "{{number}}h",
- "minutes": "{{number}}m",
- "seconds": "{{number}}s",
- "ago": "{{value}} Ago",
- "address": "Address",
- "expires": "Expires"
+ "never": "Asla",
+ "last_seen": "Son Görülme",
+ "now": "Şimdi",
+ "years": "{{number}} Yıl",
+ "weeks": "{{number}} Hafta",
+ "days": "{{number}} Gün",
+ "hours": "{{number}} Saat",
+ "minutes": "{{number}} Dakika",
+ "seconds": "{{number}} Saniye",
+ "ago": "{{value}} Önce",
+ "address": "Adres",
+ "expires": "Geciken"
},
"qnap": {
- "cpuUsage": "CPU Usage",
- "memUsage": "MEM Usage",
- "systemTempC": "System Temp",
- "poolUsage": "Pool Usage",
- "volumeUsage": "Volume Usage",
- "invalid": "Invalid"
+ "cpuUsage": "İşlemci Kullanımı",
+ "memUsage": "Bellek Kullanımı",
+ "systemTempC": "Sistem Sıcaklığı",
+ "poolUsage": "Havuz Kullanımı",
+ "volumeUsage": "Alan Kullanımı",
+ "invalid": "Geçersiz"
},
"pfsense": {
"load": "Load Avg",
@@ -623,22 +627,28 @@
"wanIP": "WAN IP"
},
"caddy": {
- "upstreams": "Upstreams",
- "requests": "Current requests",
- "requests_failed": "Failed requests"
+ "upstreams": "Akış",
+ "requests": "Anlık İstekler",
+ "requests_failed": "Başarısız İstekler"
},
"evcc": {
- "pv_power": "Production",
- "battery_soc": "Battery",
- "grid_power": "Grid",
- "home_power": "Consumption",
- "charge_power": "Charger",
- "watt_hour": "Wh"
+ "pv_power": "Üretim",
+ "battery_soc": "Batarya",
+ "grid_power": "Güç",
+ "home_power": "Tüketim",
+ "charge_power": "Şarj",
+ "watt_hour": "Watt/Saat"
},
"pialert": {
- "total": "Total",
- "connected": "Connected",
- "new_devices": "New Devices",
- "down_alerts": "Down Alerts"
+ "total": "Toplam",
+ "connected": "Bağlandı",
+ "new_devices": "Yeni Cihazlar",
+ "down_alerts": "Düşme Uyarıları"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/uk/common.json b/public/locales/uk/common.json
index 37320dfd..1abd398d 100644
--- a/public/locales/uk/common.json
+++ b/public/locales/uk/common.json
@@ -232,18 +232,22 @@
"sonarr": {
"wanted": "Розшукується",
"queued": "У черзі",
- "series": "Серії"
+ "series": "Серії",
+ "queue": "Черга",
+ "unknown": "Невідомо"
},
"radarr": {
"wanted": "Розшукується",
"missing": "Відсутній",
"queued": "У черзі",
- "movies": "Фільми"
+ "movies": "Фільми",
+ "queue": "Черга",
+ "unknown": "Невідомо"
},
"lidarr": {
"wanted": "Розшукується",
"queued": "У черзі",
- "albums": "Альбоми"
+ "artists": "Виконавці"
},
"traefik": {
"middleware": "Проміжне програмне забезпечення",
@@ -640,5 +644,11 @@
"connected": "Підключено",
"new_devices": "Нові пристрої",
"down_alerts": "Сповіщення про збій"
+ },
+ "jdownloader": {
+ "downloadCount": "Всього в черзі",
+ "downloadSpeed": "Швидкість завантаження",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/vi/common.json b/public/locales/vi/common.json
index 54431b3c..069eaff2 100644
--- a/public/locales/vi/common.json
+++ b/public/locales/vi/common.json
@@ -66,13 +66,17 @@
"sonarr": {
"wanted": "Wanted",
"queued": "Queued",
- "series": "Series"
+ "series": "Series",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"wanted": "Wanted",
"queued": "Queued",
"movies": "Phim",
- "missing": "Missing"
+ "missing": "Missing",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"readarr": {
"wanted": "Đang tìm",
@@ -173,7 +177,7 @@
"lidarr": {
"wanted": "Wanted",
"queued": "Queued",
- "albums": "Albums"
+ "artists": "Artists"
},
"adguard": {
"queries": "Queries",
@@ -640,5 +644,11 @@
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/yue/common.json b/public/locales/yue/common.json
index 5ab69f88..4e360b26 100644
--- a/public/locales/yue/common.json
+++ b/public/locales/yue/common.json
@@ -94,18 +94,22 @@
"sonarr": {
"wanted": "想睇",
"queued": "排緊隊",
- "series": "電視劇"
+ "series": "電視劇",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"wanted": "想睇",
"queued": "排緊隊",
"movies": "電影",
- "missing": "Missing"
+ "missing": "Missing",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"lidarr": {
"wanted": "想睇",
"queued": "排緊隊",
- "albums": "專輯"
+ "artists": "Artists"
},
"readarr": {
"wanted": "想睇",
@@ -640,5 +644,11 @@
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/zh-CN/common.json b/public/locales/zh-CN/common.json
index 0d6f427e..a1f0dfda 100644
--- a/public/locales/zh-CN/common.json
+++ b/public/locales/zh-CN/common.json
@@ -66,13 +66,17 @@
"sonarr": {
"wanted": "想看",
"queued": "排队",
- "series": "系列"
+ "series": "系列",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"radarr": {
"wanted": "想看",
"queued": "队列",
"movies": "电影",
- "missing": "丢失"
+ "missing": "丢失",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"readarr": {
"wanted": "订阅",
@@ -173,7 +177,7 @@
"lidarr": {
"wanted": "订阅",
"queued": "队列",
- "albums": "相册"
+ "artists": "Artists"
},
"adguard": {
"queries": "查询",
@@ -640,5 +644,11 @@
"connected": "Connected",
"new_devices": "New Devices",
"down_alerts": "Down Alerts"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/public/locales/zh-Hant/common.json b/public/locales/zh-Hant/common.json
index 75674709..7c0cb9fe 100644
--- a/public/locales/zh-Hant/common.json
+++ b/public/locales/zh-Hant/common.json
@@ -88,12 +88,16 @@
"movies": "電影",
"wanted": "關注中",
"queued": "已加入佇列",
- "missing": "缺少"
+ "missing": "缺少",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"sonarr": {
"wanted": "關注中",
"queued": "已加入佇列",
- "series": "影集"
+ "series": "影集",
+ "queue": "Queue",
+ "unknown": "Unknown"
},
"readarr": {
"wanted": "關注中",
@@ -173,7 +177,7 @@
"lidarr": {
"wanted": "關注中",
"queued": "已加入佇列",
- "albums": "專輯"
+ "artists": "Artists"
},
"adguard": {
"queries": "查詢",
@@ -640,5 +644,11 @@
"connected": "已連線",
"new_devices": "新裝置",
"down_alerts": "離線警告"
+ },
+ "jdownloader": {
+ "downloadCount": "Queue Count",
+ "downloadSpeed": "Download Speed",
+ "downloadBytesRemaining": "Remaining",
+ "downloadTotalBytes": "Size"
}
}
diff --git a/src/components/services/group.jsx b/src/components/services/group.jsx
index 5f1c5446..94557064 100644
--- a/src/components/services/group.jsx
+++ b/src/components/services/group.jsx
@@ -3,7 +3,7 @@ import classNames from "classnames";
import List from "components/services/list";
import ResolvedIcon from "components/resolvedicon";
-export default function ServicesGroup({ services, layout, fiveColumns }) {
+export default function ServicesGroup({ group, services, layout, fiveColumns }) {
return (
{services.name}
-
+
);
}
diff --git a/src/components/services/item.jsx b/src/components/services/item.jsx
index 08b8d1f4..36e454ce 100644
--- a/src/components/services/item.jsx
+++ b/src/components/services/item.jsx
@@ -11,7 +11,7 @@ import Kubernetes from "widgets/kubernetes/component";
import { SettingsContext } from "utils/contexts/settings";
import ResolvedIcon from "components/resolvedicon";
-export default function Item({ service }) {
+export default function Item({ service, group }) {
const hasLink = service.href && service.href !== "#";
const { settings } = useContext(SettingsContext);
const showStats = (service.showStats === false) ? false : settings.showStats;
@@ -77,7 +77,7 @@ export default function Item({ service }) {
{service.ping && (
)}
diff --git a/src/components/services/list.jsx b/src/components/services/list.jsx
index c8028df5..85083af3 100644
--- a/src/components/services/list.jsx
+++ b/src/components/services/list.jsx
@@ -14,7 +14,7 @@ const columnMap = [
"grid-cols-1 md:grid-cols-2 lg:grid-cols-8",
];
-export default function List({ services, layout }) {
+export default function List({ group, services, layout }) {
return (
{services.map((service) => (
-
+
))}
);
diff --git a/src/components/services/ping.jsx b/src/components/services/ping.jsx
index a54b1b55..291bc9e0 100644
--- a/src/components/services/ping.jsx
+++ b/src/components/services/ping.jsx
@@ -1,9 +1,9 @@
import { useTranslation } from "react-i18next";
import useSWR from "swr";
-export default function Ping({ service }) {
+export default function Ping({ group, service }) {
const { t } = useTranslation();
- const { data, error } = useSWR(`/api/ping?${new URLSearchParams({ping: service.ping}).toString()}`, {
+ const { data, error } = useSWR(`/api/ping?${new URLSearchParams({ group, service }).toString()}`, {
refreshInterval: 30000
});
@@ -23,7 +23,7 @@ export default function Ping({ service }) {
);
}
- const statusText = `${service.ping}: HTTP status ${data.status}`;
+ const statusText = `${service}: HTTP status ${data.status}`;
if (data.status > 403) {
return (
diff --git a/src/components/services/widget/container.jsx b/src/components/services/widget/container.jsx
index f4d8c13e..4b8a06ca 100644
--- a/src/components/services/widget/container.jsx
+++ b/src/components/services/widget/container.jsx
@@ -15,7 +15,9 @@ export default function Container({ error = false, children, service }) {
return
}
- let visibleChildren = children;
+ const childrenArray = Array.isArray(children) ? children : [children];
+
+ let visibleChildren = childrenArray;
const fields = service?.widget?.fields;
const type = service?.widget?.type;
if (fields && type) {
@@ -24,7 +26,7 @@ export default function Container({ error = false, children, service }) {
// fields: [ "resources.cpu", "resources.mem", "field"]
// or even
// fields: [ "resources.cpu", "widget_type.field" ]
- visibleChildren = children?.filter(child => fields.some(field => {
+ visibleChildren = childrenArray?.filter(child => fields.some(field => {
let fullField = field;
if (!field.includes(".")) {
fullField = `${type}.${field}`;
diff --git a/src/components/services/widget/error.jsx b/src/components/services/widget/error.jsx
index 587c572f..cf5e1366 100644
--- a/src/components/services/widget/error.jsx
+++ b/src/components/services/widget/error.jsx
@@ -9,10 +9,12 @@ function displayData(data) {
return (data.type === 'Buffer') ? Buffer.from(data).toString() : JSON.stringify(data, 4);
}
-export default function Error({ error: err }) {
+export default function Error({ error }) {
const { t } = useTranslation();
- const { error } = err?.data ?? { error: err };
+ if (error?.data?.error) {
+ error = error.data.error; // eslint-disable-line no-param-reassign
+ }
return (
diff --git a/src/components/widgets/datetime/datetime.jsx b/src/components/widgets/datetime/datetime.jsx
index 86983473..454d004d 100644
--- a/src/components/widgets/datetime/datetime.jsx
+++ b/src/components/widgets/datetime/datetime.jsx
@@ -1,6 +1,9 @@
import { useState, useEffect } from "react";
import { useTranslation } from "next-i18next";
+import Container from "../widget/container";
+import Raw from "../widget/raw";
+
const textSizes = {
"4xl": "text-4xl",
"3xl": "text-3xl",
@@ -17,7 +20,7 @@ export default function DateTime({ options }) {
const { i18n } = useTranslation();
const [date, setDate] = useState("");
const dateLocale = locale ?? i18n.language;
-
+
useEffect(() => {
const dateFormat = new Intl.DateTimeFormat(dateLocale, { ...format });
const interval = setInterval(() => {
@@ -27,12 +30,14 @@ export default function DateTime({ options }) {
}, [date, setDate, dateLocale, format]);
return (
-
+
+
+
+
+ {date}
+
+
+
+
);
}
diff --git a/src/components/widgets/glances/glances.jsx b/src/components/widgets/glances/glances.jsx
index 85dd44c0..e5cf3fbd 100644
--- a/src/components/widgets/glances/glances.jsx
+++ b/src/components/widgets/glances/glances.jsx
@@ -1,11 +1,13 @@
import useSWR from "swr";
import { useContext } from "react";
-import { BiError } from "react-icons/bi";
import { FaMemory, FaRegClock, FaThermometerHalf } from "react-icons/fa";
import { FiCpu, FiHardDrive } from "react-icons/fi";
import { useTranslation } from "next-i18next";
-import UsageBar from "../resources/usage-bar";
+import Error from "../widget/error";
+import Resource from "../widget/resource";
+import Resources from "../widget/resources";
+import WidgetLabel from "../widget/widget_label";
import { SettingsContext } from "utils/contexts/settings";
@@ -26,52 +28,19 @@ export default function Widget({ options }) {
);
if (error || data?.error) {
- return (
-
-
-
-
-
- {t("widget.api_error")}
-
-
-
-
- );
+ return
}
if (!data) {
- return (
-
-
-
-
-
-
-
- {t("glances.wait")}
-
-
-
-
-
-
-
-
-
-
- {t("glances.wait")}
-
-
-
-
-
-
- {options.label && (
-
{options.label}
- )}
-
- );
+ return
+
+
+ { options.cputemp && }
+ { options.disk && !Array.isArray(options.disk) && }
+ { options.disk && Array.isArray(options.disk) && options.disk.map((disk) => )}
+ { options.uptime && }
+ { options.label && }
+ ;
}
const unit = options.units === "imperial" ? "fahrenheit" : "celsius";
@@ -101,131 +70,84 @@ export default function Widget({ options }) {
}
return (
-
-
-
-
-
-
-
- {t("common.number", {
- value: data.cpu.total,
- style: "unit",
- unit: "percent",
- maximumFractionDigits: 0,
- })}
-
-
{t("glances.cpu")}
-
- {options.expanded && (
-
-
- {t("common.number", {
- value: data.load.min15,
- style: "unit",
- unit: "percent",
- maximumFractionDigits: 0,
- })}
-
- {t("glances.load")}
-
- )}
-
-
-
-
-
-
-
-
- {t("common.bytes", {
- value: data.mem.free,
- maximumFractionDigits: 1,
- binary: true,
- })}
-
-
{t("glances.free")}
-
- {options.expanded && (
-
-
- {t("common.bytes", {
- value: data.mem.total,
- maximumFractionDigits: 1,
- binary: true,
- })}
-
- {t("glances.total")}
-
- )}
-
-
-
- {disks.map((disk) => (
-
-
-
-
- {t("common.bytes", { value: disk.free })}
- {t("glances.free")}
-
- {options.expanded && (
-
- {t("common.bytes", { value: disk.size })}
- {t("glances.total")}
-
- )}
-
-
-
))}
- {options.cputemp && mainTemp > 0 &&
- (
-
-
-
-
- {t("common.number", {
- value: mainTemp,
- maximumFractionDigits: 1,
- style: "unit",
- unit
- })}
-
- {t("glances.temp")}
-
- {options.expanded && (
-
-
- {t("common.number", {
- value: maxTemp,
- maximumFractionDigits: 1,
- style: "unit",
- unit
- })}
-
- {t("glances.warn")}
-
- )}
-
-
-
)}
- {options.uptime && data.uptime &&
- (
-
-
-
-
- {data.uptime.replace(" days,", t("glances.days")).replace(/:\d\d:\d\d$/g, t("glances.hours"))}
-
- {t("glances.uptime")}
-
-
-
-
)}
-
- {options.label && (
- {options.label}
- )}
-
+
+
+
+ {disks.map((disk) => (
+
+ ))}
+ {options.cputemp && mainTemp > 0 &&
+
+ }
+ {options.uptime && data.uptime &&
+
+ }
+ {options.label && }
+
);
}
diff --git a/src/components/widgets/greeting/greeting.jsx b/src/components/widgets/greeting/greeting.jsx
index da0f063d..11de571c 100644
--- a/src/components/widgets/greeting/greeting.jsx
+++ b/src/components/widgets/greeting/greeting.jsx
@@ -1,3 +1,6 @@
+import Container from "../widget/container";
+import Raw from "../widget/raw";
+
const textSizes = {
"4xl": "text-4xl",
"3xl": "text-3xl",
@@ -11,12 +14,12 @@ const textSizes = {
export default function Greeting({ options }) {
if (options.text) {
- return (
-
+ return
+
{options.text}
-
- );
+
+ ;
}
}
diff --git a/src/components/widgets/kubernetes/kubernetes.jsx b/src/components/widgets/kubernetes/kubernetes.jsx
index 78c4caaf..2d1f55e4 100644
--- a/src/components/widgets/kubernetes/kubernetes.jsx
+++ b/src/components/widgets/kubernetes/kubernetes.jsx
@@ -1,12 +1,15 @@
import useSWR from "swr";
-import { BiError } from "react-icons/bi";
import { useTranslation } from "next-i18next";
+import Error from "../widget/error";
+import Container from "../widget/container";
+import Raw from "../widget/raw";
+
import Node from "./node";
export default function Widget({ options }) {
const { cluster, nodes } = options;
- const { t, i18n } = useTranslation();
+ const { i18n } = useTranslation();
const defaultData = {
cpu: {
@@ -18,7 +21,7 @@ export default function Widget({ options }) {
used: 0,
total: 0,
free: 0,
- precent: 0
+ percent: 0
}
};
@@ -29,23 +32,12 @@ export default function Widget({ options }) {
);
if (error || data?.error) {
- return (
-
-
-
-
-
- {t("widget.api_error")}
-
-
-
-
- );
+ return
}
if (!data) {
- return (
-
+ return
+
{cluster.show &&
@@ -54,12 +46,12 @@ export default function Widget({ options }) {
}
-
- );
+
+ ;
}
- return (
-
+ return
+
{cluster.show &&
@@ -69,6 +61,6 @@ export default function Widget({ options }) {
)
}
-
- );
+
+ ;
}
diff --git a/src/components/widgets/kubernetes/node.jsx b/src/components/widgets/kubernetes/node.jsx
index 7a7c322d..cc864be6 100644
--- a/src/components/widgets/kubernetes/node.jsx
+++ b/src/components/widgets/kubernetes/node.jsx
@@ -3,8 +3,7 @@ import { FiAlertTriangle, FiCpu, FiServer } from "react-icons/fi";
import { SiKubernetes } from "react-icons/si";
import { useTranslation } from "next-i18next";
-import UsageBar from "./usage-bar";
-
+import UsageBar from "../resources/usage-bar";
export default function Node({ type, options, data }) {
const { t } = useTranslation();
@@ -29,7 +28,7 @@ export default function Node({ type, options, data }) {
{t("common.number", {
- value: data.cpu.percent,
+ value: data?.cpu?.percent ?? 0,
style: "unit",
unit: "percent",
maximumFractionDigits: 0
@@ -37,18 +36,18 @@ export default function Node({ type, options, data }) {
-
+
{t("common.bytes", {
- value: data.memory.free,
+ value: data?.memory?.free ?? 0,
maximumFractionDigits: 0,
binary: true
})}
-
+
{options.showLabel && (
{type === "cluster" ? options.label : data.name}
)}
diff --git a/src/components/widgets/kubernetes/usage-bar.jsx b/src/components/widgets/kubernetes/usage-bar.jsx
deleted file mode 100644
index c817db4c..00000000
--- a/src/components/widgets/kubernetes/usage-bar.jsx
+++ /dev/null
@@ -1,12 +0,0 @@
-export default function UsageBar({ percent }) {
- return (
-
- );
-}
diff --git a/src/components/widgets/logo/logo.jsx b/src/components/widgets/logo/logo.jsx
index 96e8569f..3a4a2565 100644
--- a/src/components/widgets/logo/logo.jsx
+++ b/src/components/widgets/logo/logo.jsx
@@ -1,9 +1,13 @@
+import Container from "../widget/container";
+import Raw from "../widget/raw";
+
import ResolvedIcon from "components/resolvedicon"
export default function Logo({ options }) {
return (
-
- {options.icon ?
+
+
+ {options.icon ?
:
// fallback to homepage logo
}
-
+
+
)
}
diff --git a/src/components/widgets/longhorn/longhorn.jsx b/src/components/widgets/longhorn/longhorn.jsx
index 9fcb21b4..c0169ceb 100644
--- a/src/components/widgets/longhorn/longhorn.jsx
+++ b/src/components/widgets/longhorn/longhorn.jsx
@@ -1,37 +1,31 @@
import useSWR from "swr";
-import { BiError } from "react-icons/bi";
-import { useTranslation } from "next-i18next";
+
+import Error from "../widget/error";
+import Container from "../widget/container";
+import Raw from "../widget/raw";
import Node from "./node";
export default function Longhorn({ options }) {
const { expanded, total, labels, include, nodes } = options;
- const { t } = useTranslation();
const { data, error } = useSWR(`/api/widgets/longhorn`, {
refreshInterval: 1500
});
if (error || data?.error) {
- return (
-
-
-
- {t("widget.api_error")}
-
-
- );
+ return
}
if (!data) {
- return (
-
- );
+
+ ;
}
- return (
-
+ return
+
{data.nodes
.filter((node) => {
@@ -52,6 +46,6 @@ export default function Longhorn({ options }) {
)}
-
- );
+
+ ;
}
diff --git a/src/components/widgets/longhorn/node.jsx b/src/components/widgets/longhorn/node.jsx
index e0ee69af..5235698a 100644
--- a/src/components/widgets/longhorn/node.jsx
+++ b/src/components/widgets/longhorn/node.jsx
@@ -1,32 +1,20 @@
-import { FiHardDrive } from "react-icons/fi";
import { useTranslation } from "next-i18next";
+import { FaThermometerHalf } from "react-icons/fa";
-import UsageBar from "../resources/usage-bar";
+import Resource from "../widget/resource";
+import WidgetLabel from "../widget/widget_label";
export default function Node({ data, expanded, labels }) {
const { t } = useTranslation();
- return (
- <>
-
-
-
-
- {t("common.bytes", { value: data.node.available })}
- {t("resources.free")}
-
- {expanded && (
-
- {t("common.bytes", { value: data.node.maximum })}
- {t("resources.total")}
-
- )}
-
-
-
- {labels && (
- {data.node.id}
- )}
- >
- );
+ return { labels && }
+
}
diff --git a/src/components/widgets/openmeteo/openmeteo.jsx b/src/components/widgets/openmeteo/openmeteo.jsx
index 0d29aef5..040a3b6b 100644
--- a/src/components/widgets/openmeteo/openmeteo.jsx
+++ b/src/components/widgets/openmeteo/openmeteo.jsx
@@ -1,10 +1,16 @@
import useSWR from "swr";
import { useState } from "react";
-import { BiError } from "react-icons/bi";
import { WiCloudDown } from "react-icons/wi";
import { MdLocationDisabled, MdLocationSearching } from "react-icons/md";
import { useTranslation } from "next-i18next";
+import Error from "../widget/error";
+import Container from "../widget/container";
+import ContainerButton from "../widget/container_button";
+import WidgetIcon from "../widget/widget_icon";
+import PrimaryText from "../widget/primary_text";
+import SecondaryText from "../widget/secondary_text";
+
import Icon from "./icon";
function Widget({ options }) {
@@ -15,60 +21,35 @@ function Widget({ options }) {
);
if (error || data?.error) {
- return (
-
-
-
-
-
- {t("widget.api_error")}
- -
-
-
-
-
- );
+ return
}
if (!data) {
- return (
-
-
-
-
-
-
- {t("weather.updating")}
- {t("weather.wait")}
-
-
-
- );
+ return
+ {t("weather.updating")}
+ {t("weather.wait")}
+
+ ;
}
const unit = options.units === "metric" ? "celsius" : "fahrenheit";
- const timeOfDay = data.current_weather.time > data.daily.sunrise[0] && data.current_weather.time < data.daily.sunset[0] ? "day" : "night";
+ const weatherInfo = {
+ condition: data.current_weather.weathercode,
+ timeOfDay: data.current_weather.time > data.daily.sunrise[0] && data.current_weather.time < data.daily.sunset[0] ? "day" : "night"
+ };
- return (
-
-
-
-
-
-
-
- {options.label && `${options.label}, `}
- {t("common.number", {
- value: data.current_weather.temperature,
- style: "unit",
- unit,
- })}
-
- {t(`wmo.${data.current_weather.weathercode}-${timeOfDay}`)}
-
-
-
- );
+ return
+
+ {options.label && `${options.label}, `}
+ {t("common.number", {
+ value: data.current_weather.temperature,
+ style: "unit",
+ unit,
+ })}
+
+ {t(`wmo.${data.current_weather.weathercode}-${weatherInfo.timeOfDay}`)}
+
+ ;
}
export default function OpenMeteo({ options }) {
@@ -103,27 +84,11 @@ export default function OpenMeteo({ options }) {
// if (!requesting && !location) requestLocation();
if (!location) {
- return (
-
- );
+ return
+ {t("weather.current")}
+ {t("weather.allow")}
+
+ ;
}
return ;
diff --git a/src/components/widgets/openweathermap/weather.jsx b/src/components/widgets/openweathermap/weather.jsx
index 49f428a0..a857f13a 100644
--- a/src/components/widgets/openweathermap/weather.jsx
+++ b/src/components/widgets/openweathermap/weather.jsx
@@ -1,12 +1,19 @@
import useSWR from "swr";
import { useState } from "react";
-import { BiError } from "react-icons/bi";
import { WiCloudDown } from "react-icons/wi";
import { MdLocationDisabled, MdLocationSearching } from "react-icons/md";
import { useTranslation } from "next-i18next";
+import Error from "../widget/error";
+import Container from "../widget/container";
+import ContainerButton from "../widget/container_button";
+import PrimaryText from "../widget/primary_text";
+import SecondaryText from "../widget/secondary_text";
+import WidgetIcon from "../widget/widget_icon";
+
import Icon from "./icon";
+
function Widget({ options }) {
const { t, i18n } = useTranslation();
@@ -15,58 +22,29 @@ function Widget({ options }) {
);
if (error || data?.cod === 401 || data?.error) {
- return (
-
-
-
-
-
- {t("widget.api_error")}
- -
-
-
-
-
- );
+ return
}
if (!data) {
- return (
-
-
-
-
-
-
- {t("weather.updating")}
- {t("weather.wait")}
-
-
-
- );
+ return
+ {t("weather.updating")}
+ {t("weather.wait")}
+
+ ;
}
const unit = options.units === "metric" ? "celsius" : "fahrenheit";
- return (
-
-
-
- data.sys.sunrise && data.dt < data.sys.sunset ? "day" : "night"}
- />
-
-
-
- {options.label && `${options.label}, `}
- {t("common.number", { value: data.main.temp, style: "unit", unit })}
-
- {data.weather[0].description}
-
-
-
- );
+ const weatherInfo = {
+ condition: data.weather[0].id,
+ timeOfDay: data.dt > data.sys.sunrise && data.dt < data.sys.sunset ? "day" : "night"
+ };
+
+ return
+ {options.label && `${options.label}, ` }{t("common.number", { value: data.main.temp, style: "unit", unit })}
+ {data.weather[0].description}
+
+ ;
}
export default function OpenWeatherMap({ options }) {
@@ -98,30 +76,12 @@ export default function OpenWeatherMap({ options }) {
}
};
- // if (!requesting && !location) requestLocation();
-
if (!location) {
- return (
-
- );
+ return
+ {t("weather.current")}
+ {t("weather.allow")}
+
+ ;
}
return ;
diff --git a/src/components/widgets/queue/queueEntry.jsx b/src/components/widgets/queue/queueEntry.jsx
new file mode 100644
index 00000000..adea45ad
--- /dev/null
+++ b/src/components/widgets/queue/queueEntry.jsx
@@ -0,0 +1,18 @@
+export default function QueueEntry({ title, activity, timeLeft, progress}) {
+ return (
+
+
+
+
+ {timeLeft ? `${activity} - ${timeLeft}` : activity}
+
+
+ );
+}
diff --git a/src/components/widgets/resources/cpu.jsx b/src/components/widgets/resources/cpu.jsx
index 7069e3c4..12972fe8 100644
--- a/src/components/widgets/resources/cpu.jsx
+++ b/src/components/widgets/resources/cpu.jsx
@@ -1,9 +1,9 @@
import useSWR from "swr";
import { FiCpu } from "react-icons/fi";
-import { BiError } from "react-icons/bi";
import { useTranslation } from "next-i18next";
-import UsageBar from "./usage-bar";
+import Resource from "../widget/resource";
+import Error from "../widget/error";
export default function Cpu({ expanded }) {
const { t } = useTranslation();
@@ -13,67 +13,29 @@ export default function Cpu({ expanded }) {
});
if (error || data?.error) {
- return (
-
-
-
- {t("widget.api_error")}
-
-
- );
+ return
}
if (!data) {
- return (
-
-
-
-
-
-
-
{t("resources.cpu")}
-
- {expanded && (
-
-
-
-
{t("resources.load")}
-
- )}
-
-
-
- );
+ return
}
- const percent = data.cpu.usage;
-
- return (
-
-
-
-
-
- {t("common.number", {
- value: data.cpu.usage,
- style: "unit",
- unit: "percent",
- maximumFractionDigits: 0,
- })}
-
-
{t("resources.cpu")}
-
- {expanded && (
-
-
- {t("common.number", {
- value: data.cpu.load,
- maximumFractionDigits: 2,
- })}
-
-
{t("resources.load")}
-
- )}
-
-
-
- );
+ return
}
diff --git a/src/components/widgets/resources/cputemp.jsx b/src/components/widgets/resources/cputemp.jsx
index 571e6c8a..ba6d9b73 100644
--- a/src/components/widgets/resources/cputemp.jsx
+++ b/src/components/widgets/resources/cputemp.jsx
@@ -1,9 +1,9 @@
import useSWR from "swr";
import { FaThermometerHalf } from "react-icons/fa";
-import { BiError } from "react-icons/bi";
import { useTranslation } from "next-i18next";
-import UsageBar from "./usage-bar";
+import Resource from "../widget/resource";
+import Error from "../widget/error";
function convertToFahrenheit(t) {
return t * 9/5 + 32
@@ -17,34 +17,18 @@ export default function CpuTemp({ expanded, units }) {
});
if (error || data?.error) {
- return (
-
-
-
- {t("widget.api_error")}
-
-
- );
+ return
}
if (!data || !data.cputemp) {
- return (
-
-
-
-
- -
- {t("resources.temp")}
-
- {expanded && (
-
- -
- {t("resources.max")}
-
- )}
-
-
- );
+ return ;
}
let mainTemp = data.cputemp.main;
@@ -54,38 +38,24 @@ export default function CpuTemp({ expanded, units }) {
const unit = units === "imperial" ? "fahrenheit" : "celsius";
mainTemp = (unit === "celsius") ? mainTemp : convertToFahrenheit(mainTemp);
const maxTemp = (unit === "celsius") ? data.cputemp.max : convertToFahrenheit(data.cputemp.max);
- const percent = Math.round((mainTemp / maxTemp) * 100);
- return (
-
-
-
-
-
- {t("common.number", {
- value: mainTemp,
- maximumFractionDigits: 1,
- style: "unit",
- unit
- })}
-
- {t("resources.temp")}
-
- {expanded && (
-
-
- {t("common.number", {
- value: maxTemp,
- maximumFractionDigits: 1,
- style: "unit",
- unit
- })}
-
- {t("resources.max")}
-
- )}
-
-
-
- );
+ return ;
}
diff --git a/src/components/widgets/resources/disk.jsx b/src/components/widgets/resources/disk.jsx
index ca09c095..ab56624d 100644
--- a/src/components/widgets/resources/disk.jsx
+++ b/src/components/widgets/resources/disk.jsx
@@ -1,9 +1,9 @@
import useSWR from "swr";
import { FiHardDrive } from "react-icons/fi";
-import { BiError } from "react-icons/bi";
import { useTranslation } from "next-i18next";
-import UsageBar from "./usage-bar";
+import Resource from "../widget/resource";
+import Error from "../widget/error";
export default function Disk({ options, expanded }) {
const { t } = useTranslation();
@@ -13,56 +13,31 @@ export default function Disk({ options, expanded }) {
});
if (error || data?.error) {
- return (
-
-
-
- {t("widget.api_error")}
-
-
- );
+ return
}
if (!data) {
- return (
-
-
-
-
- -
- {t("resources.free")}
-
- {expanded && (
-
- -
- {t("resources.total")}
-
- )}
-
-
-
- );
+ return ;
}
// data.drive.used not accurate?
const percent = Math.round(((data.drive.size - data.drive.available) / data.drive.size) * 100);
- return (
-
-
-
-
- {t("common.bytes", { value: data.drive.available })}
- {t("resources.free")}
-
- {expanded && (
-
- {t("common.bytes", { value: data.drive.size })}
- {t("resources.total")}
-
- )}
-
-
-
- );
+ return ;
}
diff --git a/src/components/widgets/resources/memory.jsx b/src/components/widgets/resources/memory.jsx
index 30b7c8eb..19ae8687 100644
--- a/src/components/widgets/resources/memory.jsx
+++ b/src/components/widgets/resources/memory.jsx
@@ -1,9 +1,9 @@
import useSWR from "swr";
import { FaMemory } from "react-icons/fa";
-import { BiError } from "react-icons/bi";
import { useTranslation } from "next-i18next";
-import UsageBar from "./usage-bar";
+import Resource from "../widget/resource";
+import Error from "../widget/error";
export default function Memory({ expanded }) {
const { t } = useTranslation();
@@ -13,63 +13,30 @@ export default function Memory({ expanded }) {
});
if (error || data?.error) {
- return (
-
-
-
- {t("widget.api_error")}
-
-
- );
+ return
}
if (!data) {
- return (
-
-
-
-
- -
- {t("resources.free")}
-
- {expanded && (
-
- -
- {t("resources.total")}
-
- )}
-
-
-
- );
+ return ;
}
const percent = Math.round((data.memory.active / data.memory.total) * 100);
- return (
-
-
-
-
-
- {t("common.bytes", { value: data.memory.available, maximumFractionDigits: 1, binary: true })}
-
- {t("resources.free")}
-
- {expanded && (
-
-
- {t("common.bytes", {
- value: data.memory.total,
- maximumFractionDigits: 1,
- binary: true,
- })}
-
- {t("resources.total")}
-
- )}
-
-
-
- );
+ return ;
}
diff --git a/src/components/widgets/resources/resources.jsx b/src/components/widgets/resources/resources.jsx
index 4ff0c81c..0cc2c301 100644
--- a/src/components/widgets/resources/resources.jsx
+++ b/src/components/widgets/resources/resources.jsx
@@ -1,3 +1,6 @@
+import Container from "../widget/container";
+import Raw from "../widget/raw";
+
import Disk from "./disk";
import Cpu from "./cpu";
import Memory from "./memory";
@@ -6,8 +9,8 @@ import Uptime from "./uptime";
export default function Resources({ options }) {
const { expanded, units } = options;
- return (
-
+ return
+
{options.cpu &&
}
{options.memory &&
}
@@ -20,6 +23,6 @@ export default function Resources({ options }) {
{options.label && (
{options.label}
)}
-
- );
+
+ ;
}
diff --git a/src/components/widgets/resources/uptime.jsx b/src/components/widgets/resources/uptime.jsx
index 3bf785b1..3984975f 100644
--- a/src/components/widgets/resources/uptime.jsx
+++ b/src/components/widgets/resources/uptime.jsx
@@ -1,9 +1,9 @@
import useSWR from "swr";
import { FaRegClock } from "react-icons/fa";
-import { BiError } from "react-icons/bi";
import { useTranslation } from "next-i18next";
-import UsageBar from "./usage-bar";
+import Resource from "../widget/resource";
+import Error from "../widget/error";
export default function Uptime() {
const { t } = useTranslation();
@@ -13,54 +13,24 @@ export default function Uptime() {
});
if (error || data?.error) {
- return (
-
-
-
- {t("widget.api_error")}
-
-
- );
+ return
}
if (!data) {
- return (
-
-
-
-
- -
- {t("resources.temp")}
-
-
-
- );
+ return
;
}
const mo = Math.floor(data.uptime / (3600 * 24 * 31));
const d = Math.floor(data.uptime % (3600 * 24 * 31) / (3600 * 24));
const h = Math.floor(data.uptime % (3600 * 24) / 3600);
const m = Math.floor(data.uptime % 3600 / 60);
-
+
let uptime;
if (mo > 0) uptime = `${mo}${t("resources.months")} ${d}${t("resources.days")}`;
else if (d > 0) uptime = `${d}${t("resources.days")} ${h}${t("resources.hours")}`;
else uptime = `${h}${t("resources.hours")} ${m}${t("resources.minutes")}`;
- const percent = Math.round((new Date().getSeconds() / 60) * 100);
+ const percent = Math.round((new Date().getSeconds() / 60) * 100).toString();
- return (
-
-
-
-
-
- {uptime}
-
- {t("resources.uptime")}
-
-
-
-
- );
+ return
;
}
diff --git a/src/components/widgets/search/search.jsx b/src/components/widgets/search/search.jsx
index 4689567f..1bac4a61 100644
--- a/src/components/widgets/search/search.jsx
+++ b/src/components/widgets/search/search.jsx
@@ -1,10 +1,13 @@
-import { useState, useEffect, Fragment } from "react";
+import { useState, useEffect, useCallback, Fragment } from "react";
import { useTranslation } from "next-i18next";
import { FiSearch } from "react-icons/fi";
import { SiDuckduckgo, SiMicrosoftbing, SiGoogle, SiBaidu, SiBrave } from "react-icons/si";
import { Listbox, Transition } from "@headlessui/react";
import classNames from "classnames";
+import ContainerForm from "../widget/container_form";
+import Raw from "../widget/raw";
+
export const searchProviders = {
google: {
name: "Google",
@@ -76,14 +79,9 @@ export default function Search({ options }) {
setSelectedProvider(storedProvider);
}
}, [availableProviderIds]);
-
- if (!availableProviderIds) {
- return null;
- }
- function handleSubmit(event) {
+ const submitCallback = useCallback(event => {
const q = encodeURIComponent(query);
-
const { url } = selectedProvider;
if (url) {
window.open(`${url}${q}`, options.target || "_blank");
@@ -94,6 +92,10 @@ export default function Search({ options }) {
event.preventDefault();
event.target.reset();
setQuery("");
+ }, [options.target, options.url, query, selectedProvider]);
+
+ if (!availableProviderIds) {
+ return null;
}
const onChangeProvider = (provider) => {
@@ -101,77 +103,79 @@ export default function Search({ options }) {
localStorage.setItem(localStorageKey, provider.name);
}
- return (
-
+
+
}
diff --git a/src/components/widgets/weather/weather.jsx b/src/components/widgets/weather/weather.jsx
index 20bf3dec..702ea669 100644
--- a/src/components/widgets/weather/weather.jsx
+++ b/src/components/widgets/weather/weather.jsx
@@ -1,10 +1,16 @@
import useSWR from "swr";
import { useState } from "react";
-import { BiError } from "react-icons/bi";
import { WiCloudDown } from "react-icons/wi";
import { MdLocationDisabled, MdLocationSearching } from "react-icons/md";
import { useTranslation } from "next-i18next";
+import Error from "../widget/error";
+import Container from "../widget/container";
+import PrimaryText from "../widget/primary_text";
+import SecondaryText from "../widget/secondary_text";
+import WidgetIcon from "../widget/widget_icon";
+import ContainerButton from "../widget/container_button";
+
import Icon from "./icon";
function Widget({ options }) {
@@ -15,59 +21,35 @@ function Widget({ options }) {
);
if (error || data?.error) {
- return (
-
-
-
-
-
- {t("widget.api_error")}
- -
-
-
-
-
- );
+ return
}
if (!data) {
- return (
-
-
-
-
-
-
- {t("weather.updating")}
- {t("weather.wait")}
-
-
-
- );
+ return
+ {t("weather.updating")}
+ {t("weather.wait")}
+
+ ;
}
const unit = options.units === "metric" ? "celsius" : "fahrenheit";
+ const weatherInfo = {
+ condition: data.current.condition.code,
+ timeOfDay: data.current.is_day ? "day" : "night",
+ };
- return (
-
-
-
-
-
-
-
- {options.label && `${options.label}, `}
- {t("common.number", {
- value: options.units === "metric" ? data.current.temp_c : data.current.temp_f,
- style: "unit",
- unit,
- })}
-
- {data.current.condition.text}
-
-
-
- );
+ return
+
+ {options.label && `${options.label}, `}
+ {t("common.number", {
+ value: options.units === "metric" ? data.current.temp_c : data.current.temp_f,
+ style: "unit",
+ unit,
+ })}
+
+ {data.current.condition.text}
+
+ ;
}
export default function WeatherApi({ options }) {
@@ -99,30 +81,12 @@ export default function WeatherApi({ options }) {
}
};
- // if (!requesting && !location) requestLocation();
-
if (!location) {
- return (
-
- );
+ return
+ {t("weather.current")}
+ {t("weather.allow")}
+
+ ;
}
return ;
diff --git a/src/components/widgets/widget.jsx b/src/components/widgets/widget.jsx
index 47141887..b4fdb143 100644
--- a/src/components/widgets/widget.jsx
+++ b/src/components/widgets/widget.jsx
@@ -17,13 +17,13 @@ const widgetMappings = {
kubernetes: dynamic(() => import("components/widgets/kubernetes/kubernetes")),
};
-export default function Widget({ widget }) {
+export default function Widget({ widget, style }) {
const InfoWidget = widgetMappings[widget.type];
if (InfoWidget) {
return (
-
+
);
}
diff --git a/src/components/widgets/widget/container.jsx b/src/components/widgets/widget/container.jsx
new file mode 100644
index 00000000..59ea5684
--- /dev/null
+++ b/src/components/widgets/widget/container.jsx
@@ -0,0 +1,54 @@
+import classNames from "classnames";
+
+import WidgetIcon from "./widget_icon";
+import PrimaryText from "./primary_text";
+import SecondaryText from "./secondary_text";
+import Raw from "./raw";
+
+export function getAllClasses(options, additionalClassNames = '') {
+ if (options?.style?.header === "boxedWidgets") {
+ return classNames(
+ "flex flex-col justify-center first:ml-0 ml-2 mr-2",
+ "mt-2 m:mb-0 rounded-md shadow-md shadow-theme-900/10 dark:shadow-theme-900/20 bg-theme-100/20 dark:bg-white/5 p-2 pl-3 pr-3",
+ additionalClassNames
+ );
+ }
+
+ let widgetAlignedClasses = "flex flex-col max-w:full sm:basis-auto self-center grow-0 flex-wrap";
+ if (options?.style?.isRightAligned) {
+ widgetAlignedClasses = "flex flex-col justify-center first:ml-auto ml-2 mr-2 ";
+ }
+
+ return classNames(
+ widgetAlignedClasses,
+ additionalClassNames
+ );
+}
+
+export function getInnerBlock(children) {
+ // children won't be an array if it's Raw component
+ return Array.isArray(children) &&
+
{children.find(child => child.type === WidgetIcon)}
+
+ {children.find(child => child.type === PrimaryText)}
+ {children.find(child => child.type === SecondaryText)}
+
+
;
+}
+
+export function getBottomBlock(children) {
+ if (children.type !== Raw) {
+ return children.find(child => child.type === Raw) || [];
+ }
+
+ return [children];
+}
+
+export default function Container({ children = [], options, additionalClassNames = '' }) {
+ return (
+
+ {getInnerBlock(children)}
+ {getBottomBlock(children)}
+
+ );
+}
diff --git a/src/components/widgets/widget/container_button.jsx b/src/components/widgets/widget/container_button.jsx
new file mode 100644
index 00000000..92d8a416
--- /dev/null
+++ b/src/components/widgets/widget/container_button.jsx
@@ -0,0 +1,10 @@
+import { getAllClasses, getInnerBlock, getBottomBlock } from "./container";
+
+export default function ContainerButton ({ children = [], options, additionalClassNames = '', callback }) {
+ return (
+
+ );
+}
diff --git a/src/components/widgets/widget/container_form.jsx b/src/components/widgets/widget/container_form.jsx
new file mode 100644
index 00000000..7d28a1bb
--- /dev/null
+++ b/src/components/widgets/widget/container_form.jsx
@@ -0,0 +1,10 @@
+import { getAllClasses, getInnerBlock, getBottomBlock } from "./container";
+
+export default function ContainerForm ({ children = [], options, additionalClassNames = '', callback }) {
+ return (
+
+ );
+}
diff --git a/src/components/widgets/widget/container_link.jsx b/src/components/widgets/widget/container_link.jsx
new file mode 100644
index 00000000..8ef0e80a
--- /dev/null
+++ b/src/components/widgets/widget/container_link.jsx
@@ -0,0 +1,10 @@
+import { getAllClasses, getInnerBlock, getBottomBlock } from "./container";
+
+export default function ContainerLink ({ children = [], options, additionalClassNames = '', target }) {
+ return (
+
+ {getInnerBlock(children)}
+ {getBottomBlock(children)}
+
+ );
+}
diff --git a/src/components/widgets/widget/error.jsx b/src/components/widgets/widget/error.jsx
new file mode 100644
index 00000000..a3dbab85
--- /dev/null
+++ b/src/components/widgets/widget/error.jsx
@@ -0,0 +1,15 @@
+import { useTranslation } from "react-i18next";
+import { BiError } from "react-icons/bi";
+
+import Container from "./container";
+import PrimaryText from "./primary_text";
+import WidgetIcon from "./widget_icon";
+
+export default function Error({ options }) {
+ const { t } = useTranslation();
+
+ return
+ {t("widget.api_error")}
+
+ ;
+}
diff --git a/src/components/widgets/widget/primary_text.jsx b/src/components/widgets/widget/primary_text.jsx
new file mode 100644
index 00000000..3418b92c
--- /dev/null
+++ b/src/components/widgets/widget/primary_text.jsx
@@ -0,0 +1,5 @@
+export default function PrimaryText({ children }) {
+ return (
+ {children}
+ );
+}
diff --git a/src/components/widgets/widget/raw.jsx b/src/components/widgets/widget/raw.jsx
new file mode 100644
index 00000000..44e3dddc
--- /dev/null
+++ b/src/components/widgets/widget/raw.jsx
@@ -0,0 +1,7 @@
+export default function Raw({ children }) {
+ if (children.type === Raw) {
+ return [children];
+ }
+
+ return children;
+}
diff --git a/src/components/widgets/widget/resource.jsx b/src/components/widgets/widget/resource.jsx
new file mode 100644
index 00000000..18e7c800
--- /dev/null
+++ b/src/components/widgets/widget/resource.jsx
@@ -0,0 +1,22 @@
+import UsageBar from "../resources/usage-bar";
+
+export default function Resource({ children, icon, value, label, expandedValue = "", expandedLabel = "", percentage, expanded = false }) {
+ const Icon = icon;
+
+ return
+
+
+
+ { expanded &&
+
{expandedValue}
+
{expandedLabel}
+
+ }
+ { percentage &&
}
+ { children }
+
+
;
+}
diff --git a/src/components/widgets/widget/resources.jsx b/src/components/widgets/widget/resources.jsx
new file mode 100644
index 00000000..394a3058
--- /dev/null
+++ b/src/components/widgets/widget/resources.jsx
@@ -0,0 +1,17 @@
+import ContainerLink from "./container_link";
+import Resource from "./resource";
+import Raw from "./raw";
+import WidgetLabel from "./widget_label";
+
+export default function Resources({ options, children, target }) {
+ const widgetParts = [].concat(...children);
+
+ return
+
+
+ { widgetParts.filter(child => child && child.type === Resource) }
+
+ { widgetParts.filter(child => child && child.type === WidgetLabel) }
+
+ ;
+}
diff --git a/src/components/widgets/widget/secondary_text.jsx b/src/components/widgets/widget/secondary_text.jsx
new file mode 100644
index 00000000..363d1bd0
--- /dev/null
+++ b/src/components/widgets/widget/secondary_text.jsx
@@ -0,0 +1,5 @@
+export default function SecondaryText({ children }) {
+ return (
+ {children}
+ );
+}
diff --git a/src/components/widgets/widget/widget_icon.jsx b/src/components/widgets/widget/widget_icon.jsx
new file mode 100644
index 00000000..9766a879
--- /dev/null
+++ b/src/components/widgets/widget/widget_icon.jsx
@@ -0,0 +1,18 @@
+export default function WidgetIcon({ icon, size = "s", pulse = false, weatherInfo = {} }) {
+ const Icon = icon;
+ const { condition, timeOfDay } = weatherInfo;
+ let additionalClasses = "text-theme-800 dark:text-theme-200 ";
+
+ switch (size) {
+ case "m": additionalClasses += "w-6 h-6 "; break;
+ case "l": additionalClasses += "w-8 h-8 "; break;
+ case "xl": additionalClasses += "w-10 h-10 "; break;
+ default: additionalClasses += "w-5 h-5 ";
+ }
+
+ if (pulse) {
+ additionalClasses += "animate-pulse ";
+ }
+
+ return ;
+}
diff --git a/src/components/widgets/widget/widget_label.jsx b/src/components/widgets/widget/widget_label.jsx
new file mode 100644
index 00000000..5fc6ced0
--- /dev/null
+++ b/src/components/widgets/widget/widget_label.jsx
@@ -0,0 +1,3 @@
+export default function WidgetLabel({ label = "" }) {
+ return {label}
+}
diff --git a/src/pages/api/ping.js b/src/pages/api/ping.js
index 96c1b12c..cfc2aafa 100644
--- a/src/pages/api/ping.js
+++ b/src/pages/api/ping.js
@@ -1,12 +1,22 @@
import { performance } from "perf_hooks";
+import { getServiceItem } from "utils/config/service-helpers";
import createLogger from "utils/logger";
import { httpProxy } from "utils/proxy/http";
const logger = createLogger("ping");
export default async function handler(req, res) {
- const { ping: pingURL } = req.query;
+ const { group, service } = req.query;
+ const serviceItem = await getServiceItem(group, service);
+ if (!serviceItem) {
+ logger.debug(`No service item found for group ${group} named ${service}`);
+ return res.status(400).send({
+ error: "Unable to find service, see log for details.",
+ });
+ }
+
+ const { ping: pingURL } = serviceItem;
if (!pingURL) {
logger.debug("No ping URL specified");
diff --git a/src/pages/api/widgets/longhorn.js b/src/pages/api/widgets/longhorn.js
index a6b6781c..d23a7f61 100644
--- a/src/pages/api/widgets/longhorn.js
+++ b/src/pages/api/widgets/longhorn.js
@@ -46,7 +46,7 @@ function parseLonghornData(data) {
export default async function handler(req, res) {
const settings = getSettings();
- const longhornSettings = settings?.providers?.longhorn;
+ const longhornSettings = settings?.providers?.longhorn || {};
const {url, username, password} = longhornSettings;
if (!url) {
diff --git a/src/pages/index.jsx b/src/pages/index.jsx
index 06e55010..d91a8339 100644
--- a/src/pages/index.jsx
+++ b/src/pages/index.jsx
@@ -160,6 +160,7 @@ const headerStyles = {
"m-4 mb-0 sm:m-8 sm:mb-0 rounded-md shadow-md shadow-theme-900/10 dark:shadow-theme-900/20 bg-theme-100/20 dark:bg-white/5 p-3",
underlined: "m-4 mb-0 sm:m-8 sm:mb-1 border-b-2 pb-4 border-theme-800 dark:border-theme-200/50",
clean: "m-4 mb-0 sm:m-8 sm:mb-0",
+ boxedWidgets: "m-4 mb-0 sm:m-8 sm:mb-0 sm:mt-1",
};
function Home({ initialSettings }) {
@@ -208,6 +209,7 @@ function Home({ initialSettings }) {
searchProvider = searchProviders[searchWidget.options?.provider];
}
}
+ const headerStyle = initialSettings?.headerStyle || "underlined";
useEffect(() => {
function handleKeyDown(e) {
@@ -252,11 +254,11 @@ function Home({ initialSettings }) {
/>
-
+
!rightAlignedWidgets.includes(widget.type))
.map((widget, i) => (
-
+
))}
-
+
{widgets
.filter((widget) => rightAlignedWidgets.includes(widget.type))
.map((widget, i) => (
-
+
))}
>
@@ -289,7 +294,7 @@ function Home({ initialSettings }) {
{services?.length > 0 && (
{services.map((group) => (
-
+
))}
)}
@@ -302,14 +307,16 @@ function Home({ initialSettings }) {
)}
-
- {!initialSettings?.color && }
-
- {!initialSettings?.theme && }
-
+
+
+ {!initialSettings?.color && }
+
+ {!initialSettings?.theme && }
+
-
- {!initialSettings?.hideVersion &&
}
+
+ {!initialSettings?.hideVersion && }
+
>
@@ -357,7 +364,7 @@ export default function Wrapper({ initialSettings, fallback }) {
style={wrappedStyle}
>
{
try {
+ const isSwarm = !!servers[serverName].swarm;
const docker = new Docker(getDockerArguments(serverName).conn);
- const containers = await docker.listContainers({
- all: true,
- });
+ const listProperties = { all: true };
+ const containers = await ((isSwarm) ? docker.listServices(listProperties) : docker.listContainers(listProperties));
// bad docker connections can result in a object?
// in any case, this ensures the result is the expected array
@@ -76,17 +76,19 @@ export async function servicesFromDocker() {
const discovered = containers.map((container) => {
let constructedService = null;
+ const containerLabels = isSwarm ? shvl.get(container, 'Spec.Labels') : container.Labels;
+ const containerName = isSwarm ? shvl.get(container, 'Spec.Name') : container.Names[0];
- Object.keys(container.Labels).forEach((label) => {
+ Object.keys(containerLabels).forEach((label) => {
if (label.startsWith("homepage.")) {
if (!constructedService) {
constructedService = {
- container: container.Names[0].replace(/^\//, ""),
+ container: containerName.replace(/^\//, ""),
server: serverName,
type: 'service'
};
}
- shvl.set(constructedService, label.replace("homepage.", ""), substituteEnvironmentVars(container.Labels[label]));
+ shvl.set(constructedService, label.replace("homepage.", ""), substituteEnvironmentVars(containerLabels[label]));
}
});
@@ -156,11 +158,20 @@ export async function servicesFromKubernetes() {
return null;
});
- const traefikIngressList = await crd.listClusterCustomObject("traefik.containo.us", "v1alpha1", "ingressroutes")
+ const traefikIngressList = await crd.listClusterCustomObject("traefik.io", "v1alpha1", "ingressroutes")
.then((response) => response.body)
- .catch((error) => {
- logger.error("Error getting traefik ingresses: %d %s %s", error.statusCode, error.body, error.response);
- return null;
+ .catch(async (error) => {
+ logger.error("Error getting traefik ingresses from traefik.io: %d %s %s", error.statusCode, error.body, error.response);
+
+ // Fallback to the old traefik CRD group
+ const fallbackIngressList = await crd.listClusterCustomObject("traefik.containo.us", "v1alpha1", "ingressroutes")
+ .then((response) => response.body)
+ .catch((fallbackError) => {
+ logger.error("Error getting traefik ingresses from traefik.containo.us: %d %s %s", fallbackError.statusCode, fallbackError.body, fallbackError.response);
+ return null;
+ });
+
+ return fallbackIngressList;
});
if (traefikIngressList && traefikIngressList.items.length > 0) {
@@ -168,7 +179,7 @@ export async function servicesFromKubernetes() {
.filter((ingress) => ingress.metadata.annotations && ingress.metadata.annotations[`${ANNOTATION_BASE}/href`])
ingressList.items.push(...traefikServices);
}
-
+
if (!ingressList) {
return [];
}
@@ -276,7 +287,8 @@ export function cleanServiceGroups(groups) {
wan, // opnsense widget, pfsense widget
enableBlocks, // emby/jellyfin
enableNowPlaying,
- volume, // diskstation widget
+ volume, // diskstation widget,
+ enableQueue, // sonarr/radarr
} = cleanedService.widget;
const fieldsList = typeof fields === 'string' ? JSON.parse(fields) : fields;
@@ -312,6 +324,9 @@ export function cleanServiceGroups(groups) {
if (enableBlocks !== undefined) cleanedService.widget.enableBlocks = JSON.parse(enableBlocks);
if (enableNowPlaying !== undefined) cleanedService.widget.enableNowPlaying = JSON.parse(enableNowPlaying);
}
+ if (["sonarr", "radarr"].includes(type)) {
+ if (enableQueue !== undefined) cleanedService.widget.enableQueue = JSON.parse(enableQueue);
+ }
if (["diskstation", "qnap"].includes(type)) {
if (volume) cleanedService.widget.volume = volume;
}
@@ -322,16 +337,13 @@ export function cleanServiceGroups(groups) {
}));
}
-export default async function getServiceWidget(group, service) {
+export async function getServiceItem(group, service) {
const configuredServices = await servicesFromConfig();
const serviceGroup = configuredServices.find((g) => g.name === group);
if (serviceGroup) {
const serviceEntry = serviceGroup.services.find((s) => s.name === service);
- if (serviceEntry) {
- const { widget } = serviceEntry;
- return widget;
- }
+ if (serviceEntry) return serviceEntry;
}
const discoveredServices = await servicesFromDocker();
@@ -339,20 +351,24 @@ export default async function getServiceWidget(group, service) {
const dockerServiceGroup = discoveredServices.find((g) => g.name === group);
if (dockerServiceGroup) {
const dockerServiceEntry = dockerServiceGroup.services.find((s) => s.name === service);
- if (dockerServiceEntry) {
- const { widget } = dockerServiceEntry;
- return widget;
- }
+ if (dockerServiceEntry) return dockerServiceEntry;
}
const kubernetesServices = await servicesFromKubernetes();
const kubernetesServiceGroup = kubernetesServices.find((g) => g.name === group);
if (kubernetesServiceGroup) {
const kubernetesServiceEntry = kubernetesServiceGroup.services.find((s) => s.name === service);
- if (kubernetesServiceEntry) {
- const { widget } = kubernetesServiceEntry;
- return widget;
- }
+ if (kubernetesServiceEntry) return kubernetesServiceEntry;
+ }
+
+ return false;
+}
+
+export default async function getServiceWidget(group, service) {
+ const serviceItem = await getServiceItem(group, service);
+ if (serviceItem) {
+ const { widget } = serviceItem;
+ return widget;
}
return false;
diff --git a/src/utils/proxy/handlers/credentialed.js b/src/utils/proxy/handlers/credentialed.js
index 5d4b7e3b..8fa975d0 100644
--- a/src/utils/proxy/handlers/credentialed.js
+++ b/src/utils/proxy/handlers/credentialed.js
@@ -55,6 +55,12 @@ export default async function credentialedProxyHandler(req, res, map) {
} else {
headers.Authorization = `Basic ${Buffer.from(`${widget.username}:${widget.password}`).toString("base64")}`;
}
+ } else if (widget.type === "paperlessngx") {
+ if (widget.key) {
+ headers.Authorization = `Token ${widget.key}`;
+ } else {
+ headers.Authorization = `Basic ${Buffer.from(`${widget.username}:${widget.password}`).toString("base64")}`;
+ }
} else {
headers["X-API-Key"] = `${widget.key}`;
}
diff --git a/src/utils/proxy/http.js b/src/utils/proxy/http.js
index e022fb46..72f65be3 100644
--- a/src/utils/proxy/http.js
+++ b/src/utils/proxy/http.js
@@ -1,5 +1,7 @@
/* eslint-disable prefer-promise-reject-errors */
/* eslint-disable no-param-reassign */
+import { createUnzip } from "node:zlib";
+
import { http, https } from "follow-redirects";
import { addCookieToJar, setCookieHeader } from "./cookie-jar";
@@ -28,12 +30,24 @@ function handleRequest(requestor, url, params) {
const request = requestor.request(url, params, (response) => {
const data = [];
+ const contentEncoding = response.headers['content-encoding']?.trim().toLowerCase();
- response.on("data", (chunk) => {
+ let responseContent = response;
+ if (contentEncoding === 'gzip' || contentEncoding === 'deflate') {
+ responseContent = createUnzip();
+ // zlib errors
+ responseContent.on("error", (e) => {
+ logger.error(e);
+ responseContent = response; // fallback
+ });
+ response.pipe(responseContent);
+ }
+
+ responseContent.on("data", (chunk) => {
data.push(chunk);
});
- response.on("end", () => {
+ responseContent.on("end", () => {
addCookieToJar(url, response.headers);
resolve([response.statusCode, response.headers["content-type"], Buffer.concat(data), response.headers]);
});
diff --git a/src/utils/proxy/use-widget-api.js b/src/utils/proxy/use-widget-api.js
index 52e986e1..e4744038 100644
--- a/src/utils/proxy/use-widget-api.js
+++ b/src/utils/proxy/use-widget-api.js
@@ -7,7 +7,11 @@ export default function useWidgetAPI(widget, ...options) {
if (options && options[1]?.refreshInterval) {
config.refreshInterval = options[1].refreshInterval;
}
- const { data, error, mutate } = useSWR(formatProxyUrl(widget, ...options), config);
+ let url = formatProxyUrl(widget, ...options)
+ if (options[0] === "") {
+ url = null
+ }
+ const { data, error, mutate } = useSWR(url, config);
// make the data error the top-level error
return { data, error: data?.error ?? error, mutate }
}
diff --git a/src/widgets/components.js b/src/widgets/components.js
index 3a918e62..5d2dc7e6 100644
--- a/src/widgets/components.js
+++ b/src/widgets/components.js
@@ -31,6 +31,7 @@ const components = {
healthchecks: dynamic(() => import("./healthchecks/component")),
immich: dynamic(() => import("./immich/component")),
jackett: dynamic(() => import("./jackett/component")),
+ jdownloader: dynamic(() => import("./jdownloader/component")),
jellyfin: dynamic(() => import("./emby/component")),
jellyseerr: dynamic(() => import("./jellyseerr/component")),
komga: dynamic(() => import("./komga/component")),
@@ -93,4 +94,4 @@ const components = {
xteve: dynamic(() => import("./xteve/component")),
};
-export default components;
+export default components;
\ No newline at end of file
diff --git a/src/widgets/homebridge/proxy.js b/src/widgets/homebridge/proxy.js
index b0a75a03..a9e1ac97 100644
--- a/src/widgets/homebridge/proxy.js
+++ b/src/widgets/homebridge/proxy.js
@@ -63,7 +63,7 @@ async function apiCall(widget, endpoint, service) {
}
if (status !== 200) {
- logger.error("Error getting data from Homebridge: %s status %d. Data: %s", url, status, data);
+ logger.error("Error getting data from Homebridge: %s status %d. Data: %s", url, status, JSON.stringify(data));
return { status, contentType, data: null, responseHeaders };
}
diff --git a/src/widgets/jdownloader/component.jsx b/src/widgets/jdownloader/component.jsx
new file mode 100644
index 00000000..8f271935
--- /dev/null
+++ b/src/widgets/jdownloader/component.jsx
@@ -0,0 +1,39 @@
+import { useTranslation } from "next-i18next";
+
+import Block from "components/services/widget/block";
+import Container from "components/services/widget/container";
+import useWidgetAPI from "utils/proxy/use-widget-api";
+
+export default function Component({ service }) {
+ const { t } = useTranslation();
+
+ const { widget } = service;
+
+ const { data: jdownloaderData, error: jdownloaderAPIError } = useWidgetAPI(widget, "unified", {
+ refreshInterval: 30000,
+ });
+
+ if (jdownloaderAPIError) {
+ return ;
+ }
+
+ if (!jdownloaderData) {
+ return (
+
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/widgets/jdownloader/proxy.js b/src/widgets/jdownloader/proxy.js
new file mode 100644
index 00000000..be858d51
--- /dev/null
+++ b/src/widgets/jdownloader/proxy.js
@@ -0,0 +1,196 @@
+/* eslint-disable no-underscore-dangle */
+import crypto from 'crypto';
+import querystring from 'querystring';
+
+import { sha256, uniqueRid, validateRid, createEncryptionToken, decrypt, encrypt } from "./tools"
+
+import getServiceWidget from "utils/config/service-helpers";
+import { httpProxy } from "utils/proxy/http";
+import createLogger from "utils/logger";
+
+const proxyName = "jdownloaderProxyHandler";
+const logger = createLogger(proxyName);
+
+async function getWidget(req) {
+ const { group, service } = req.query;
+ if (!group || !service) {
+ logger.debug("Invalid or missing service '%s' or group '%s'", service, group);
+ return null;
+ }
+ const widget = await getServiceWidget(group, service);
+ if (!widget) {
+ logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group);
+ return null;
+ }
+
+ return widget;
+}
+
+async function login(loginSecret, deviceSecret, params) {
+ const rid = uniqueRid();
+ const path = `/my/connect?${querystring.stringify({ ...params, rid })}`;
+
+ const signature = crypto
+ .createHmac('sha256', loginSecret)
+ .update(path)
+ .digest('hex');
+ const url = `${new URL(`https://api.jdownloader.org${path}&signature=${signature}`)}`
+
+ const [status, contentType, data] = await httpProxy(url, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+
+ if (status !== 200) {
+ logger.error("HTTP %d communicating with jdownloader. Data: %s", status, data.toString());
+ return [status, data];
+ }
+
+ try {
+ const decryptedData = JSON.parse(decrypt(data.toString(), loginSecret))
+ const sessionToken = decryptedData.sessiontoken;
+ validateRid(decryptedData, rid);
+ const serverEncryptionToken = createEncryptionToken(loginSecret, sessionToken);
+ const deviceEncryptionToken = createEncryptionToken(deviceSecret, sessionToken);
+ return [status, decryptedData, contentType, serverEncryptionToken, deviceEncryptionToken, sessionToken];
+ } catch (e) {
+ logger.error("Error decoding jdownloader API data. Data: %s", data.toString());
+ return [status, null];
+ }
+}
+
+
+async function getDevice(serverEncryptionToken, deviceName, params) {
+ const rid = uniqueRid();
+ const path = `/my/listdevices?${querystring.stringify({ ...params, rid })}`;
+ const signature = crypto
+ .createHmac('sha256', serverEncryptionToken)
+ .update(path)
+ .digest('hex');
+ const url = `${new URL(`https://api.jdownloader.org${path}&signature=${signature}`)}`
+
+ const [status, , data] = await httpProxy(url, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+
+ if (status !== 200) {
+ logger.error("HTTP %d communicating with jdownloader. Data: %s", status, data.toString());
+ return [status, data];
+ }
+
+ try {
+ const decryptedData = JSON.parse(decrypt(data.toString(), serverEncryptionToken))
+ const filteredDevice = decryptedData.list.filter(device => device.name === deviceName);
+ return [status, filteredDevice[0].id];
+ } catch (e) {
+ logger.error("Error decoding jdownloader API data. Data: %s", data.toString());
+ return [status, null];
+ }
+
+}
+
+function createBody(rid, query, params) {
+ const baseBody = {
+ apiVer: 1,
+ rid,
+ url: query
+ };
+ return params ? { ...baseBody, params: [JSON.stringify(params)] } : baseBody;
+}
+
+async function queryPackages(deviceEncryptionToken, deviceId, sessionToken, params) {
+ const rid = uniqueRid();
+ const body = encrypt(JSON.stringify(createBody(rid, '/downloadsV2/queryPackages', params)), deviceEncryptionToken);
+ const url = `${new URL(`https://api.jdownloader.org/t_${encodeURI(sessionToken)}_${encodeURI(deviceId)}/downloadsV2/queryPackages`)}`
+ const [status, , data] = await httpProxy(url, {
+ method: 'POST',
+ body,
+ });
+
+ if (status !== 200) {
+ logger.error("HTTP %d communicating with jdownloader. Data: %s", status, data.toString());
+ return [status, data];
+ }
+
+ try {
+ const decryptedData = JSON.parse(decrypt(data.toString(), deviceEncryptionToken))
+ return decryptedData.data;
+ } catch (e) {
+ logger.error("Error decoding JDRss jdownloader data. Data: %s", data.toString());
+ return [status, null];
+ }
+
+}
+
+
+export default async function jdownloaderProxyHandler(req, res) {
+ const widget = await getWidget(req);
+
+ if (!widget) {
+ return res.status(400).json({ error: "Invalid proxy service type" });
+ }
+ logger.debug("Getting data from JDRss API");
+ const { username } = widget
+ const { password } = widget
+
+ const appKey = "homepage"
+ const loginSecret = sha256(`${username}${password}server`)
+ const deviceSecret = sha256(`${username}${password}device`)
+ const email = username;
+
+ const loginData = await login(loginSecret, deviceSecret, {
+ appKey,
+ email
+ })
+
+ const deviceData = await getDevice(loginData[3], widget.client, {
+ sessiontoken: loginData[5]
+ })
+
+ const packageStatus = await queryPackages(loginData[4], deviceData[1], loginData[5], {
+ "bytesLoaded": false,
+ "bytesTotal": true,
+ "comment": false,
+ "enabled": true,
+ "eta": false,
+ "priority": false,
+ "finished": true,
+ "running": true,
+ "speed": true,
+ "status": true,
+ "childCount": false,
+ "hosts": false,
+ "saveTo": false,
+ "maxResults": -1,
+ "startAt": 0,
+ }
+ )
+
+ let bytesRemaining = 0;
+ let totalBytes = 0;
+ let totalSpeed = 0;
+ packageStatus.forEach(file => {
+ totalBytes += file.bytesTotal;
+ if (file.finished !== true) {
+ bytesRemaining += file.bytesTotal;
+ if (file.speed) {
+ totalSpeed += file.speed;
+ }
+ }
+ });
+
+ const data = {
+ downloadCount: packageStatus.length,
+ bytesRemaining,
+ totalBytes,
+ totalSpeed
+ };
+
+ return res.send(data);
+
+}
\ No newline at end of file
diff --git a/src/widgets/jdownloader/tools.js b/src/widgets/jdownloader/tools.js
new file mode 100644
index 00000000..d678b072
--- /dev/null
+++ b/src/widgets/jdownloader/tools.js
@@ -0,0 +1,55 @@
+import crypto from 'crypto';
+
+export function sha256(data) {
+ return crypto
+ .createHash('sha256')
+ .update(data)
+ .digest();
+}
+
+export function uniqueRid() {
+ return Math.floor(Math.random() * 10e12);
+}
+
+export function validateRid(decryptedData, rid) {
+ if (decryptedData.rid !== rid) {
+ throw new Error('RequestID mismatch');
+ }
+ return decryptedData;
+
+}
+
+export function decrypt(data, ivKey) {
+ const iv = ivKey.slice(0, ivKey.length / 2);
+ const key = ivKey.slice(ivKey.length / 2, ivKey.length);
+ const cipher = crypto.createDecipheriv('aes-128-cbc', key, iv);
+ return Buffer.concat([
+ cipher.update(data, 'base64'),
+ cipher.final()
+ ]).toString();
+}
+
+export function createEncryptionToken(oldTokenBuff, updateToken) {
+ const updateTokenBuff = Buffer.from(updateToken, 'hex');
+ const mergedBuffer = Buffer.concat([oldTokenBuff, updateTokenBuff], oldTokenBuff.length + updateTokenBuff.length);
+ return sha256(mergedBuffer);
+}
+
+export function encrypt(data, ivKey) {
+ if (typeof data !== 'string') {
+ throw new Error('data no es un string');
+ }
+ if (!(ivKey instanceof Buffer)) {
+ throw new Error('ivKey no es un buffer');
+ }
+ if (ivKey.length !== 32) {
+ throw new Error('ivKey tiene que tener tamaño 32');
+ }
+ const stringIVKey = ivKey.toString('hex');
+ const stringIV = stringIVKey.substring(0, stringIVKey.length / 2);
+ const stringKey = stringIVKey.substring(stringIVKey.length / 2, stringIVKey.length);
+ const iv = Buffer.from(stringIV, 'hex');
+ const key = Buffer.from(stringKey, 'hex');
+ const cipher = crypto.createCipheriv('aes-128-cbc', key, iv);
+ return cipher.update(data, 'utf8', 'base64') + cipher.final('base64');
+}
\ No newline at end of file
diff --git a/src/widgets/jdownloader/widget.js b/src/widgets/jdownloader/widget.js
new file mode 100644
index 00000000..acd25d74
--- /dev/null
+++ b/src/widgets/jdownloader/widget.js
@@ -0,0 +1,15 @@
+import jdownloaderProxyHandler from "./proxy";
+
+const widget = {
+ api: "https://api.jdownloader.org/{endpoint}/&signature={signature}",
+ proxyHandler: jdownloaderProxyHandler,
+
+ mappings: {
+ unified: {
+ endpoint: "/",
+ signature: "",
+ },
+ },
+};
+
+export default widget;
\ No newline at end of file
diff --git a/src/widgets/kubernetes/component.jsx b/src/widgets/kubernetes/component.jsx
index c4d67553..e756e4be 100644
--- a/src/widgets/kubernetes/component.jsx
+++ b/src/widgets/kubernetes/component.jsx
@@ -16,7 +16,7 @@ export default function Component({ service }) {
`/api/kubernetes/stats/${widget.namespace}/${widget.app}?${podSelectorString}`);
if (statsError || statusError) {
- return ;
+ return ;
}
if (statusData && statusData.status !== "running") {
diff --git a/src/widgets/lidarr/component.jsx b/src/widgets/lidarr/component.jsx
index a6aa82c7..68360d82 100644
--- a/src/widgets/lidarr/component.jsx
+++ b/src/widgets/lidarr/component.jsx
@@ -9,21 +9,21 @@ export default function Component({ service }) {
const { widget } = service;
- const { data: albumsData, error: albumsError } = useWidgetAPI(widget, "album");
+ const { data: artistsData, error: artistsError } = useWidgetAPI(widget, "artist");
const { data: wantedData, error: wantedError } = useWidgetAPI(widget, "wanted/missing");
const { data: queueData, error: queueError } = useWidgetAPI(widget, "queue/status");
- if (albumsError || wantedError || queueError) {
- const finalError = albumsError ?? wantedError ?? queueError;
+ if (artistsError || wantedError || queueError) {
+ const finalError = artistsError ?? wantedError ?? queueError;
return ;
}
- if (!albumsData || !wantedData || !queueData) {
+ if (!artistsData || !wantedData || !queueData) {
return (
-
+
);
}
@@ -32,7 +32,7 @@ export default function Component({ service }) {
-
+
);
}
diff --git a/src/widgets/lidarr/widget.js b/src/widgets/lidarr/widget.js
index 6ff93254..2f036726 100644
--- a/src/widgets/lidarr/widget.js
+++ b/src/widgets/lidarr/widget.js
@@ -1,16 +1,12 @@
import genericProxyHandler from "utils/proxy/handlers/generic";
-import { jsonArrayFilter } from "utils/proxy/api-helpers";
const widget = {
api: "{url}/api/v1/{endpoint}?apikey={key}",
proxyHandler: genericProxyHandler,
mappings: {
- album: {
- endpoint: "album",
- map: (data) => ({
- have: jsonArrayFilter(data, (item) => item?.statistics?.percentOfTracks === 100).length,
- }),
+ artist: {
+ endpoint: "artist",
},
"wanted/missing": {
endpoint: "wanted/missing",
diff --git a/src/widgets/paperlessngx/widget.js b/src/widgets/paperlessngx/widget.js
index 8af8079d..de37f595 100644
--- a/src/widgets/paperlessngx/widget.js
+++ b/src/widgets/paperlessngx/widget.js
@@ -1,8 +1,8 @@
-import genericProxyHandler from "utils/proxy/handlers/generic";
+import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
const widget = {
api: "{url}/api/{endpoint}",
- proxyHandler: genericProxyHandler,
+ proxyHandler: credentialedProxyHandler,
mappings: {
"statistics": {
diff --git a/src/widgets/portainer/component.jsx b/src/widgets/portainer/component.jsx
index aab9eba1..53a6fd26 100644
--- a/src/widgets/portainer/component.jsx
+++ b/src/widgets/portainer/component.jsx
@@ -1,12 +1,8 @@
-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: containersData, error: containersError } = useWidgetAPI(widget, "docker/containers/json", {
@@ -27,8 +23,9 @@ export default function Component({ service }) {
);
}
- if (containersData.error) {
- return ;
+ if (containersData.error || containersData.message) {
+ // containersData can be itself an error object e.g. if environment fails
+ return ;
}
const running = containersData.filter((c) => c.State === "running").length;
diff --git a/src/widgets/radarr/component.jsx b/src/widgets/radarr/component.jsx
index f8a932ea..6ce2f599 100644
--- a/src/widgets/radarr/component.jsx
+++ b/src/widgets/radarr/component.jsx
@@ -1,22 +1,41 @@
import { useTranslation } from "next-i18next";
+import { useCallback } from 'react';
+
+import QueueEntry from "../../components/widgets/queue/queueEntry";
import Container from "components/services/widget/container";
import Block from "components/services/widget/block";
import useWidgetAPI from "utils/proxy/use-widget-api";
+function getProgress(sizeLeft, size) {
+ return sizeLeft === 0 ? 100 : (1 - sizeLeft / size) * 100
+}
+
export default function Component({ service }) {
const { t } = useTranslation();
const { widget } = service;
const { data: moviesData, error: moviesError } = useWidgetAPI(widget, "movie");
const { data: queuedData, error: queuedError } = useWidgetAPI(widget, "queue/status");
+ const { data: queueDetailsData, error: queueDetailsError } = useWidgetAPI(widget, "queue/details");
- if (moviesError || queuedError) {
- const finalError = moviesError ?? queuedError;
+ const formatDownloadState = useCallback((downloadState) => {
+ switch (downloadState) {
+ case "importPending":
+ return "import pending";
+ case "failedPending":
+ return "failed pending";
+ default:
+ return downloadState;
+ }
+ }, []);
+
+ if (moviesError || queuedError || queueDetailsError) {
+ const finalError = moviesError ?? queuedError ?? queueDetailsError;
return ;
}
- if (!moviesData || !queuedData) {
+ if (!moviesData || !queuedData || !queueDetailsData) {
return (
@@ -27,12 +46,27 @@ export default function Component({ service }) {
);
}
+ const enableQueue = widget?.enableQueue && Array.isArray(queueDetailsData) && queueDetailsData.length > 0;
+
return (
-
-
-
-
-
-
+ <>
+
+
+
+
+
+
+ {enableQueue &&
+ queueDetailsData.map((queueEntry) => (
+ entry.id === queueEntry.movieId)?.title ?? t("radarr.unknown")}
+ activity={formatDownloadState(queueEntry.trackedDownloadState)}
+ key={`${queueEntry.movieId}-${queueEntry.sizeLeft}`}
+ />
+ ))
+ }
+ >
);
}
diff --git a/src/widgets/radarr/widget.js b/src/widgets/radarr/widget.js
index 78054219..3373975e 100644
--- a/src/widgets/radarr/widget.js
+++ b/src/widgets/radarr/widget.js
@@ -1,5 +1,5 @@
import genericProxyHandler from "utils/proxy/handlers/generic";
-import { jsonArrayFilter } from "utils/proxy/api-helpers";
+import { asJson, jsonArrayFilter } from "utils/proxy/api-helpers";
const widget = {
api: "{url}/api/v3/{endpoint}?apikey={key}",
@@ -12,6 +12,7 @@ const widget = {
wanted: jsonArrayFilter(data, (item) => item.monitored && !item.hasFile && item.isAvailable).length,
have: jsonArrayFilter(data, (item) => item.hasFile).length,
missing: jsonArrayFilter(data, (item) => item.monitored && !item.hasFile).length,
+ all: asJson(data),
}),
},
"queue/status": {
@@ -20,6 +21,37 @@ const widget = {
"totalCount"
]
},
+ "queue/details": {
+ endpoint: "queue/details",
+ map: (data) => asJson(data).map((entry) => ({
+ trackedDownloadState: entry.trackedDownloadState,
+ trackedDownloadStatus: entry.trackedDownloadStatus,
+ timeLeft: entry.timeleft,
+ size: entry.size,
+ sizeLeft: entry.sizeleft,
+ movieId: entry.movieId ?? entry.id,
+ status: entry.status
+ })).sort((a, b) => {
+ const downloadingA = a.trackedDownloadState === "downloading"
+ const downloadingB = b.trackedDownloadState === "downloading"
+ if (downloadingA && !downloadingB) {
+ return -1;
+ }
+ if (downloadingB && !downloadingA) {
+ return 1;
+ }
+
+ const percentA = a.sizeLeft / a.size;
+ const percentB = b.sizeLeft / b.size;
+ if (percentA < percentB) {
+ return -1;
+ }
+ if (percentA > percentB) {
+ return 1;
+ }
+ return 0;
+ })
+ },
},
};
diff --git a/src/widgets/sonarr/component.jsx b/src/widgets/sonarr/component.jsx
index adbb8c30..27b1ab03 100644
--- a/src/widgets/sonarr/component.jsx
+++ b/src/widgets/sonarr/component.jsx
@@ -1,9 +1,26 @@
import { useTranslation } from "next-i18next";
+import { useCallback } from 'react';
+
+import QueueEntry from "../../components/widgets/queue/queueEntry";
import Container from "components/services/widget/container";
import Block from "components/services/widget/block";
import useWidgetAPI from "utils/proxy/use-widget-api";
+function getProgress(sizeLeft, size) {
+ return sizeLeft === 0 ? 100 : (1 - sizeLeft / size) * 100
+}
+
+function getTitle(queueEntry, seriesData) {
+ let title = ''
+ const seriesTitle = seriesData.find((entry) => entry.id === queueEntry.seriesId)?.title;
+ if (seriesTitle) title += `${seriesTitle}: `;
+ const { episodeTitle } = queueEntry;
+ if (episodeTitle) title += episodeTitle;
+ if (title === '') return null;
+ return title;
+}
+
export default function Component({ service }) {
const { t } = useTranslation();
const { widget } = service;
@@ -11,13 +28,25 @@ export default function Component({ service }) {
const { data: wantedData, error: wantedError } = useWidgetAPI(widget, "wanted/missing");
const { data: queuedData, error: queuedError } = useWidgetAPI(widget, "queue");
const { data: seriesData, error: seriesError } = useWidgetAPI(widget, "series");
+ const { data: queueDetailsData, error: queueDetailsError } = useWidgetAPI(widget, "queue/details");
- if (wantedError || queuedError || seriesError) {
- const finalError = wantedError ?? queuedError ?? seriesError;
+ const formatDownloadState = useCallback((downloadState) => {
+ switch (downloadState) {
+ case "importPending":
+ return "import pending";
+ case "failedPending":
+ return "failed pending";
+ default:
+ return downloadState;
+ }
+ }, []);
+
+ if (wantedError || queuedError || seriesError || queueDetailsError) {
+ const finalError = wantedError ?? queuedError ?? seriesError ?? queueDetailsError;
return ;
}
- if (!wantedData || !queuedData || !seriesData) {
+ if (!wantedData || !queuedData || !seriesData || !queueDetailsData) {
return (
@@ -27,11 +56,26 @@ export default function Component({ service }) {
);
}
+ const enableQueue = widget?.enableQueue && Array.isArray(queueDetailsData) && queueDetailsData.length > 0;
+
return (
-
-
-
-
-
+ <>
+
+
+
+
+
+ {enableQueue &&
+ queueDetailsData.map((queueEntry) => (
+
+ ))
+ }
+ >
);
}
diff --git a/src/widgets/sonarr/widget.js b/src/widgets/sonarr/widget.js
index c1413975..7f658eb1 100644
--- a/src/widgets/sonarr/widget.js
+++ b/src/widgets/sonarr/widget.js
@@ -8,9 +8,10 @@ const widget = {
mappings: {
series: {
endpoint: "series",
- map: (data) => ({
- total: asJson(data).length,
- })
+ map: (data) => asJson(data).map((entry) => ({
+ title: entry.title,
+ id: entry.id
+ }))
},
queue: {
endpoint: "queue",
@@ -24,6 +25,39 @@ const widget = {
"totalRecords"
]
},
+ "queue/details": {
+ endpoint: "queue/details",
+ map: (data) => asJson(data).map((entry) => ({
+ trackedDownloadState: entry.trackedDownloadState,
+ trackedDownloadStatus: entry.trackedDownloadStatus,
+ timeLeft: entry.timeleft,
+ size: entry.size,
+ sizeLeft: entry.sizeleft,
+ seriesId: entry.seriesId,
+ episodeTitle: entry.episode?.title ?? entry.title,
+ episodeId: entry.episodeId ?? entry.id,
+ status: entry.status,
+ })).sort((a, b) => {
+ const downloadingA = a.trackedDownloadState === "downloading"
+ const downloadingB = b.trackedDownloadState === "downloading"
+ if (downloadingA && !downloadingB) {
+ return -1;
+ }
+ if (downloadingB && !downloadingA) {
+ return 1;
+ }
+
+ const percentA = a.sizeLeft / a.size;
+ const percentB = b.sizeLeft / b.size;
+ if (percentA < percentB) {
+ return -1;
+ }
+ if (percentA > percentB) {
+ return 1;
+ }
+ return 0;
+ })
+ }
},
};
diff --git a/src/widgets/widgets.js b/src/widgets/widgets.js
index dc675bb8..ab8beb56 100644
--- a/src/widgets/widgets.js
+++ b/src/widgets/widgets.js
@@ -27,6 +27,7 @@ import healthchecks from "./healthchecks/widget";
import immich from "./immich/widget";
import jackett from "./jackett/widget";
import jellyseerr from "./jellyseerr/widget";
+import jdownloader from "./jdownloader/widget";
import komga from "./komga/widget";
import kopia from "./kopia/widget";
import lidarr from "./lidarr/widget";
@@ -115,6 +116,7 @@ const widgets = {
healthchecks,
immich,
jackett,
+ jdownloader,
jellyfin: emby,
jellyseerr,
komga,