Merge branch 'main' into kubernetes
This commit is contained in:
commit
174cb651b4
81 changed files with 2717 additions and 342 deletions
6
.devcontainer/Dockerfile
Normal file
6
.devcontainer/Dockerfile
Normal file
|
@ -0,0 +1,6 @@
|
|||
ARG VARIANT="16-buster"
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:${VARIANT}
|
||||
|
||||
RUN npm install -g pnpm
|
||||
|
||||
ENV PATH="${PATH}:./node_modules/.bin"
|
27
.devcontainer/devcontainer.json
Normal file
27
.devcontainer/devcontainer.json
Normal file
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"name": "homepage",
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile",
|
||||
"args": {
|
||||
"VARIANT": "18-buster"
|
||||
}
|
||||
},
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"mhutchie.git-graph",
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
],
|
||||
"settings": {
|
||||
"eslint.format.enable": true,
|
||||
"eslint.lintTask.enable": true,
|
||||
"eslint.packageManager": "pnpm"
|
||||
}
|
||||
}
|
||||
},
|
||||
"postCreateCommand": ".devcontainer/setup.sh",
|
||||
"forwardPorts": [
|
||||
3000
|
||||
]
|
||||
}
|
11
.devcontainer/setup.sh
Executable file
11
.devcontainer/setup.sh
Executable file
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Install Node packages
|
||||
pnpm install
|
||||
|
||||
# Copy in skeleton configuration if there is no existing configuration
|
||||
if [ ! -d "config/" ]; then
|
||||
echo "Adding skeleton config"
|
||||
mkdir config/
|
||||
cp -r src/skeleton/* config
|
||||
fi
|
10
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
10
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
@ -59,6 +59,16 @@ body:
|
|||
label: Configuration
|
||||
description: Please provide any relevant service, widget or otherwise related configuration here
|
||||
render: yaml
|
||||
- type: textarea
|
||||
id: container-logs
|
||||
attributes:
|
||||
label: Container Logs
|
||||
description: Please review and provide any logs from the container, if relevant
|
||||
- type: textarea
|
||||
id: browser-logs
|
||||
attributes:
|
||||
label: Browser Logs
|
||||
description: Please review and provide any relevant logs from the browser, if relevant
|
||||
- type: textarea
|
||||
id: other
|
||||
attributes:
|
||||
|
|
|
@ -47,12 +47,12 @@
|
|||
- Service Integration
|
||||
- Sonarr, Radarr, Readarr, Prowlarr, Bazarr, Lidarr, Emby, Jellyfin, Tautulli (Plex)
|
||||
- Ombi, Overseerr, Jellyseerr, Jackett, NZBGet, SABnzbd, ruTorrent, Transmission, qBittorrent
|
||||
- Portainer, Traefik, Speedtest Tracker, PiHole, AdGuard Home, Nginx Proxy Manager, Gotify, Syncthing Relay Server, Authentic, Proxmox
|
||||
- Portainer, Traefik, Speedtest Tracker, PiHole, AdGuard Home, Nginx Proxy Manager, Gotify, Syncthing Relay Server, Authentik, Proxmox
|
||||
- Information Providers
|
||||
- Coin Market Cap, Mastodon
|
||||
- Information & Utility Widgets
|
||||
- System Stats (Disk, CPU, Memory)
|
||||
- Weather via WeatherAPI.com or OpenWeatherMap
|
||||
- Weather via [OpenWeatherMap](https://openweathermap.org/) or [Open-Meteo](https://open-meteo.com/)
|
||||
- Search Bar
|
||||
- Customizable
|
||||
- 21 theme colors with light and dark mode support
|
||||
|
|
|
@ -103,7 +103,7 @@ module.exports = {
|
|||
const bits = options.bits ? value : value / 8;
|
||||
const k = 1024;
|
||||
const dm = options.decimals ? options.decimals : 0;
|
||||
const sizes = ["Bps", "Kbps", "Mbps", "Gbps", "Tbps", "Pbps", "Ebps", "Zbps", "Ybps"];
|
||||
const sizes = ["Bps", "KiBps", "MiBps", "GiBps", "TiBps", "PiBps", "EiBps", "ZiBps", "YiBps"];
|
||||
|
||||
const i = Math.floor(Math.log(bits) / Math.log(k));
|
||||
|
||||
|
|
|
@ -103,7 +103,9 @@
|
|||
"tx": "TX",
|
||||
"mem": "الرام",
|
||||
"cpu": "المعالج",
|
||||
"offline": "غير متصل"
|
||||
"offline": "غير متصل",
|
||||
"error": "Error",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "يعمل الان",
|
||||
|
@ -191,7 +193,8 @@
|
|||
"overseerr": {
|
||||
"pending": "Pending",
|
||||
"approved": "Approved",
|
||||
"available": "Available"
|
||||
"available": "Available",
|
||||
"processing": "Processing"
|
||||
},
|
||||
"pihole": {
|
||||
"queries": "Queries",
|
||||
|
@ -327,5 +330,36 @@
|
|||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"hd": "HD"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Error",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Passed",
|
||||
"failed": "Failed",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Inbox",
|
||||
"total": "Total"
|
||||
},
|
||||
"deluge": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"diskstation": {
|
||||
"leech": "Leech",
|
||||
"seed": "Seed",
|
||||
"download": "Download",
|
||||
"upload": "Upload"
|
||||
},
|
||||
"flood": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,9 @@
|
|||
"rx": "RX",
|
||||
"tx": "TX",
|
||||
"mem": "MEM",
|
||||
"cpu": "CPU"
|
||||
"cpu": "CPU",
|
||||
"error": "Error",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "Възпроизвежда",
|
||||
|
@ -129,7 +131,8 @@
|
|||
"overseerr": {
|
||||
"pending": "Pending",
|
||||
"approved": "Approved",
|
||||
"available": "Available"
|
||||
"available": "Available",
|
||||
"processing": "Processing"
|
||||
},
|
||||
"pihole": {
|
||||
"queries": "Queries",
|
||||
|
@ -327,5 +330,36 @@
|
|||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"hd": "HD"
|
||||
},
|
||||
"ping": {
|
||||
"ping": "Ping",
|
||||
"error": "Error"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Passed",
|
||||
"failed": "Failed",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Inbox",
|
||||
"total": "Total"
|
||||
},
|
||||
"deluge": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"diskstation": {
|
||||
"seed": "Seed",
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech"
|
||||
},
|
||||
"flood": {
|
||||
"leech": "Leech",
|
||||
"seed": "Seed",
|
||||
"download": "Download",
|
||||
"upload": "Upload"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,9 @@
|
|||
"tx": "Transmès",
|
||||
"mem": "Memòria",
|
||||
"cpu": "Processador",
|
||||
"offline": "Fora de línia"
|
||||
"offline": "Fora de línia",
|
||||
"error": "Error",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "Reproduint",
|
||||
|
@ -98,7 +100,8 @@
|
|||
"overseerr": {
|
||||
"pending": "Pendent",
|
||||
"approved": "Aprovat",
|
||||
"available": "Disponible"
|
||||
"available": "Disponible",
|
||||
"processing": "Processing"
|
||||
},
|
||||
"pihole": {
|
||||
"queries": "Consultes",
|
||||
|
@ -327,5 +330,36 @@
|
|||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"hd": "HD"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Error",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Passed",
|
||||
"failed": "Failed",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Inbox",
|
||||
"total": "Total"
|
||||
},
|
||||
"deluge": {
|
||||
"seed": "Seed",
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech"
|
||||
},
|
||||
"diskstation": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"flood": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,7 +57,9 @@
|
|||
"tx": "TX",
|
||||
"mem": "RAM",
|
||||
"cpu": "CPU",
|
||||
"offline": "Offline"
|
||||
"offline": "Offline",
|
||||
"error": "Error",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "Přehrává",
|
||||
|
@ -145,7 +147,8 @@
|
|||
"overseerr": {
|
||||
"pending": "Čeká",
|
||||
"approved": "Schváleno",
|
||||
"available": "Dostupný"
|
||||
"available": "Dostupný",
|
||||
"processing": "Processing"
|
||||
},
|
||||
"pihole": {
|
||||
"queries": "Dotazy",
|
||||
|
@ -327,5 +330,36 @@
|
|||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"hd": "HD"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Error",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Passed",
|
||||
"failed": "Failed",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Inbox",
|
||||
"total": "Total"
|
||||
},
|
||||
"deluge": {
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed",
|
||||
"download": "Download"
|
||||
},
|
||||
"diskstation": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"flood": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,8 @@
|
|||
"overseerr": {
|
||||
"pending": "Afventer",
|
||||
"approved": "Godkendt",
|
||||
"available": "Tilgængelig"
|
||||
"available": "Tilgængelig",
|
||||
"processing": "Processing"
|
||||
},
|
||||
"adguard": {
|
||||
"queries": "Forespørgsler",
|
||||
|
@ -181,7 +182,9 @@
|
|||
"rx": "RX",
|
||||
"tx": "TX",
|
||||
"mem": "RAM",
|
||||
"offline": "Offline"
|
||||
"offline": "Offline",
|
||||
"error": "Error",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "Afspiller",
|
||||
|
@ -327,5 +330,36 @@
|
|||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"hd": "HD"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Error",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Passed",
|
||||
"failed": "Failed",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Inbox",
|
||||
"total": "Total"
|
||||
},
|
||||
"deluge": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"diskstation": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"flood": {
|
||||
"leech": "Leech",
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,9 @@
|
|||
"tx": "Tx",
|
||||
"mem": "Mem",
|
||||
"cpu": "Prozessor",
|
||||
"offline": "Offline"
|
||||
"offline": "Offline",
|
||||
"error": "Error",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "Spielen",
|
||||
|
@ -102,7 +104,8 @@
|
|||
"overseerr": {
|
||||
"pending": "Ausstehend",
|
||||
"approved": "Genehmigt",
|
||||
"available": "Verfügbar"
|
||||
"available": "Verfügbar",
|
||||
"processing": "Processing"
|
||||
},
|
||||
"sabnzbd": {
|
||||
"rate": "Geschwindigkeit",
|
||||
|
@ -327,5 +330,36 @@
|
|||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"hd": "HD"
|
||||
},
|
||||
"ping": {
|
||||
"ping": "Ping",
|
||||
"error": "Error"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Passed",
|
||||
"failed": "Failed",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Inbox",
|
||||
"total": "Total"
|
||||
},
|
||||
"deluge": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"diskstation": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"flood": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,7 +56,13 @@
|
|||
"tx": "TX",
|
||||
"mem": "MEM",
|
||||
"cpu": "CPU",
|
||||
"offline": "Offline"
|
||||
"offline": "Offline",
|
||||
"error": "Error",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Error",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "Playing",
|
||||
|
@ -64,6 +70,12 @@
|
|||
"bitrate": "Bitrate",
|
||||
"no_active": "No Active Streams"
|
||||
},
|
||||
"flood": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"changedetectionio": {
|
||||
"totalObserved": "Total Observed",
|
||||
"diffsDetected": "Diffs Detected"
|
||||
|
@ -106,6 +118,18 @@
|
|||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"deluge": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"diskstation": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"sonarr": {
|
||||
"wanted": "Wanted",
|
||||
"queued": "Queued",
|
||||
|
@ -143,6 +167,7 @@
|
|||
},
|
||||
"overseerr": {
|
||||
"pending": "Pending",
|
||||
"processing": "Processing",
|
||||
"approved": "Approved",
|
||||
"available": "Available"
|
||||
},
|
||||
|
@ -301,7 +326,7 @@
|
|||
"child_bridges": "Child Bridges",
|
||||
"child_bridges_status": "{{ok}}/{{total}}"
|
||||
},
|
||||
"watchtower":{
|
||||
"watchtower": {
|
||||
"containers_scanned": "Scanned",
|
||||
"containers_updated": "Updated",
|
||||
"containers_failed": "Failed"
|
||||
|
@ -338,5 +363,14 @@
|
|||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"hd": "HD"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Passed",
|
||||
"failed": "Failed",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Inbox",
|
||||
"total": "Total"
|
||||
}
|
||||
}
|
||||
}
|
365
public/locales/eo/common.json
Normal file
365
public/locales/eo/common.json
Normal file
|
@ -0,0 +1,365 @@
|
|||
{
|
||||
"widget": {
|
||||
"missing_type": "Missing Widget Type: {{type}}",
|
||||
"api_error": "API Error",
|
||||
"information": "Informo",
|
||||
"status": "Stato",
|
||||
"url": "URL",
|
||||
"raw_error": "Raw Error",
|
||||
"response_data": "Response Data"
|
||||
},
|
||||
"weather": {
|
||||
"current": "Aktuala loko",
|
||||
"allow": "Click to allow",
|
||||
"updating": "Updating",
|
||||
"wait": "Please wait"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Serĉi…"
|
||||
},
|
||||
"resources": {
|
||||
"cpu": "Ĉefprocesoro",
|
||||
"total": "Totalo",
|
||||
"free": "Libera",
|
||||
"used": "Uzata",
|
||||
"load": "Ŝarĝo"
|
||||
},
|
||||
"unifi": {
|
||||
"users": "Uzantoj",
|
||||
"uptime": "System Uptime",
|
||||
"days": "Tagoj",
|
||||
"wan": "WAN",
|
||||
"lan": "LAN",
|
||||
"wlan": "WLAN",
|
||||
"devices": "Aparatoj",
|
||||
"lan_devices": "LAN Devices",
|
||||
"wlan_devices": "WLAN Devices",
|
||||
"lan_users": "LAN Users",
|
||||
"wlan_users": "WLAN Users",
|
||||
"up": "UP",
|
||||
"down": "DOWN",
|
||||
"wait": "Please wait"
|
||||
},
|
||||
"docker": {
|
||||
"rx": "RX",
|
||||
"tx": "TX",
|
||||
"mem": "Memoro",
|
||||
"cpu": "Ĉefprocesoro",
|
||||
"offline": "Offline",
|
||||
"error": "Eraro",
|
||||
"unknown": "Nekonata"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Eraro",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "Ludante",
|
||||
"transcoding": "Transcoding",
|
||||
"bitrate": "Bitrate",
|
||||
"no_active": "No Active Streams"
|
||||
},
|
||||
"changedetectionio": {
|
||||
"totalObserved": "Total Observed",
|
||||
"diffsDetected": "Diffs Detected"
|
||||
},
|
||||
"tautulli": {
|
||||
"playing": "Playing",
|
||||
"transcoding": "Transcoding",
|
||||
"bitrate": "Bitrate",
|
||||
"no_active": "No Active Streams"
|
||||
},
|
||||
"nzbget": {
|
||||
"rate": "Rate",
|
||||
"remaining": "Remaining",
|
||||
"downloaded": "Downloaded"
|
||||
},
|
||||
"plex": {
|
||||
"streams": "Active Streams",
|
||||
"movies": "Filmoj",
|
||||
"tv": "Televidprogramoj"
|
||||
},
|
||||
"sabnzbd": {
|
||||
"rate": "Rate",
|
||||
"queue": "Queue",
|
||||
"timeleft": "Time Left"
|
||||
},
|
||||
"rutorrent": {
|
||||
"active": "Active",
|
||||
"upload": "Alŝuto",
|
||||
"download": "Elŝuto"
|
||||
},
|
||||
"transmission": {
|
||||
"download": "Elŝuto",
|
||||
"upload": "Alŝuto",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"qbittorrent": {
|
||||
"download": "Elŝuto",
|
||||
"upload": "Alŝuto",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"deluge": {
|
||||
"download": "Elŝuto",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"sonarr": {
|
||||
"wanted": "Wanted",
|
||||
"queued": "Queued",
|
||||
"series": "Serio"
|
||||
},
|
||||
"radarr": {
|
||||
"wanted": "Wanted",
|
||||
"missing": "Missing",
|
||||
"queued": "Queued",
|
||||
"movies": "Filmoj"
|
||||
},
|
||||
"lidarr": {
|
||||
"wanted": "Wanted",
|
||||
"queued": "Queued",
|
||||
"albums": "Albumoj"
|
||||
},
|
||||
"readarr": {
|
||||
"wanted": "Wanted",
|
||||
"queued": "Queued",
|
||||
"books": "Libroj"
|
||||
},
|
||||
"bazarr": {
|
||||
"missingEpisodes": "Missing Episodes",
|
||||
"missingMovies": "Missing Movies"
|
||||
},
|
||||
"ombi": {
|
||||
"pending": "Pending",
|
||||
"approved": "Aprobita",
|
||||
"available": "Havebla"
|
||||
},
|
||||
"jellyseerr": {
|
||||
"pending": "Pending",
|
||||
"approved": "Aprobita",
|
||||
"available": "Havebla"
|
||||
},
|
||||
"overseerr": {
|
||||
"pending": "Pending",
|
||||
"processing": "Processing",
|
||||
"approved": "Aprobita",
|
||||
"available": "Havebla"
|
||||
},
|
||||
"pihole": {
|
||||
"queries": "Queries",
|
||||
"blocked": "Blocked",
|
||||
"gravity": "Gravity"
|
||||
},
|
||||
"adguard": {
|
||||
"queries": "Queries",
|
||||
"blocked": "Blokitaj",
|
||||
"filtered": "Filtritaj",
|
||||
"latency": "Latency"
|
||||
},
|
||||
"speedtest": {
|
||||
"upload": "Upload",
|
||||
"download": "Download",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"portainer": {
|
||||
"running": "Running",
|
||||
"stopped": "Stopped",
|
||||
"total": "Totalo"
|
||||
},
|
||||
"traefik": {
|
||||
"routers": "Routers",
|
||||
"services": "Servoj",
|
||||
"middleware": "Middleware"
|
||||
},
|
||||
"navidrome": {
|
||||
"nothing_streaming": "No Active Streams",
|
||||
"please_wait": "Please Wait"
|
||||
},
|
||||
"npm": {
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
"total": "Total"
|
||||
},
|
||||
"coinmarketcap": {
|
||||
"configure": "Configure one or more crypto currencies to track",
|
||||
"1hour": "1 horo",
|
||||
"1day": "1 tago",
|
||||
"7days": "7 tagoj",
|
||||
"30days": "30 tagoj"
|
||||
},
|
||||
"gotify": {
|
||||
"apps": "Applications",
|
||||
"clients": "Klientoj",
|
||||
"messages": "Mesaĝoj"
|
||||
},
|
||||
"prowlarr": {
|
||||
"enableIndexers": "Indexers",
|
||||
"numberOfGrabs": "Grabs",
|
||||
"numberOfQueries": "Queries",
|
||||
"numberOfFailGrabs": "Fail Grabs",
|
||||
"numberOfFailQueries": "Fail Queries"
|
||||
},
|
||||
"jackett": {
|
||||
"configured": "Configured",
|
||||
"errored": "Errored"
|
||||
},
|
||||
"strelaysrv": {
|
||||
"numActiveSessions": "Seancoj",
|
||||
"numConnections": "Konektoj",
|
||||
"dataRelayed": "Relayed",
|
||||
"transferRate": "Rate"
|
||||
},
|
||||
"mastodon": {
|
||||
"user_count": "Uzantoj",
|
||||
"status_count": "Afiŝoj",
|
||||
"domain_count": "Domains"
|
||||
},
|
||||
"authentik": {
|
||||
"users": "Users",
|
||||
"loginsLast24H": "Logins (24h)",
|
||||
"failedLoginsLast24H": "Failed Logins (24h)"
|
||||
},
|
||||
"proxmox": {
|
||||
"mem": "Memoro",
|
||||
"cpu": "Ĉefprocesoro",
|
||||
"lxc": "LXC",
|
||||
"vms": "VMs"
|
||||
},
|
||||
"glances": {
|
||||
"cpu": "Ĉefprocesoro",
|
||||
"mem": "Memoro",
|
||||
"wait": "Bonvolu atendi"
|
||||
},
|
||||
"quicklaunch": {
|
||||
"bookmark": "Bookmark",
|
||||
"service": "Servo"
|
||||
},
|
||||
"wmo": {
|
||||
"0-day": "Suna",
|
||||
"0-night": "Sennuba",
|
||||
"1-day": "Mainly Sunny",
|
||||
"1-night": "Mainly Clear",
|
||||
"2-day": "Nubeta",
|
||||
"2-night": "Nubeta",
|
||||
"3-day": "Nuba",
|
||||
"3-night": "Nuba",
|
||||
"45-day": "Nebula",
|
||||
"45-night": "Nebula",
|
||||
"48-day": "Nebula",
|
||||
"48-night": "Nebula",
|
||||
"51-day": "Light Drizzle",
|
||||
"51-night": "Light Drizzle",
|
||||
"53-day": "Drizzle",
|
||||
"53-night": "Drizzle",
|
||||
"55-day": "Heavy Drizzle",
|
||||
"55-night": "Heavy Drizzle",
|
||||
"56-day": "Light Freezing Drizzle",
|
||||
"56-night": "Light Freezing Drizzle",
|
||||
"57-day": "Freezing Drizzle",
|
||||
"57-night": "Freezing Drizzle",
|
||||
"61-day": "Light Rain",
|
||||
"61-night": "Light Rain",
|
||||
"63-day": "Pluvo",
|
||||
"63-night": "Pluvo",
|
||||
"65-day": "Pluvego",
|
||||
"65-night": "Pluvego",
|
||||
"66-day": "Frosta pluvo",
|
||||
"66-night": "Frosta pluvo",
|
||||
"67-day": "Frosta pluvo",
|
||||
"67-night": "Frosta pluvo",
|
||||
"71-day": "Light Snow",
|
||||
"71-night": "Light Snow",
|
||||
"73-day": "Neĝo",
|
||||
"73-night": "Neĝo",
|
||||
"75-day": "Neĝego",
|
||||
"75-night": "Neĝego",
|
||||
"77-day": "Snow Grains",
|
||||
"77-night": "Snow Grains",
|
||||
"80-day": "Light Showers",
|
||||
"80-night": "Light Showers",
|
||||
"81-day": "Showers",
|
||||
"81-night": "Showers",
|
||||
"82-day": "Heavy Showers",
|
||||
"82-night": "Heavy Showers",
|
||||
"85-day": "Snow Showers",
|
||||
"85-night": "Snow Showers",
|
||||
"86-day": "Snow Showers",
|
||||
"86-night": "Snow Showers",
|
||||
"95-day": "Fulmotondro",
|
||||
"95-night": "Fulmotondro",
|
||||
"96-day": "Fulmotondro kun hajlo",
|
||||
"96-night": "Fulmotondro kun hajlo",
|
||||
"99-day": "Fulmotondro kun hajlo",
|
||||
"99-night": "Fulmotondro kun hajlo"
|
||||
},
|
||||
"homebridge": {
|
||||
"available_update": "Sistemo",
|
||||
"updates": "Updates",
|
||||
"update_available": "Update Available",
|
||||
"up_to_date": "Up to Date",
|
||||
"child_bridges": "Child Bridges",
|
||||
"child_bridges_status": "{{ok}}/{{total}}"
|
||||
},
|
||||
"watchtower": {
|
||||
"containers_scanned": "Scanned",
|
||||
"containers_updated": "Updated",
|
||||
"containers_failed": "Failed"
|
||||
},
|
||||
"autobrr": {
|
||||
"approvedPushes": "Approved",
|
||||
"rejectedPushes": "Rejected",
|
||||
"filters": "Filtriloj",
|
||||
"indexers": "Indexers"
|
||||
},
|
||||
"tubearchivist": {
|
||||
"downloads": "Queue",
|
||||
"videos": "Videos",
|
||||
"channels": "Kanaloj",
|
||||
"playlists": "Playlists"
|
||||
},
|
||||
"truenas": {
|
||||
"load": "System Load",
|
||||
"uptime": "Uptime",
|
||||
"alerts": "Alerts",
|
||||
"time": "{{value, number(style: unit; unitDisplay: long;)}}"
|
||||
},
|
||||
"pyload": {
|
||||
"speed": "Speed",
|
||||
"active": "Aktiva",
|
||||
"queue": "Queue",
|
||||
"total": "Total"
|
||||
},
|
||||
"gluetun": {
|
||||
"public_ip": "Public IP",
|
||||
"region": "Regiono",
|
||||
"country": "Lando"
|
||||
},
|
||||
"hdhomerun": {
|
||||
"channels": "Kanaloj",
|
||||
"hd": "HD"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Passed",
|
||||
"failed": "Failed",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Inbox",
|
||||
"total": "Totalo"
|
||||
},
|
||||
"diskstation": {
|
||||
"download": "Download",
|
||||
"leech": "Leech",
|
||||
"upload": "Upload",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"flood": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
|
@ -3,10 +3,10 @@
|
|||
"missing_type": "Falta el tipo de widget: {{type}}",
|
||||
"api_error": "Error de API",
|
||||
"status": "Estado",
|
||||
"information": "Information",
|
||||
"information": "Información",
|
||||
"url": "URL",
|
||||
"raw_error": "Raw Error",
|
||||
"response_data": "Response Data"
|
||||
"raw_error": "Error sin procesar",
|
||||
"response_data": "Datos de respuesta"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Buscar…"
|
||||
|
@ -23,7 +23,9 @@
|
|||
"tx": "Transmitido",
|
||||
"mem": "Memoria",
|
||||
"cpu": "Procesador",
|
||||
"offline": "Desconectado"
|
||||
"offline": "Desconectado",
|
||||
"error": "Fallo",
|
||||
"unknown": "Desconocido"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "Reproduciendo",
|
||||
|
@ -51,7 +53,7 @@
|
|||
"wanted": "Buscando",
|
||||
"queued": "En cola",
|
||||
"movies": "Películas",
|
||||
"missing": "No Encontrado"
|
||||
"missing": "Faltan"
|
||||
},
|
||||
"readarr": {
|
||||
"wanted": "Buscando",
|
||||
|
@ -102,7 +104,8 @@
|
|||
"overseerr": {
|
||||
"pending": "Pendiente",
|
||||
"approved": "Aprobado",
|
||||
"available": "Disponible"
|
||||
"available": "Disponible",
|
||||
"processing": "Procesando"
|
||||
},
|
||||
"sabnzbd": {
|
||||
"rate": "Tasa",
|
||||
|
@ -136,7 +139,7 @@
|
|||
"transmission": {
|
||||
"download": "Bajada",
|
||||
"upload": "Subida",
|
||||
"leech": "Compañeros",
|
||||
"leech": "Leech",
|
||||
"seed": "Semillas"
|
||||
},
|
||||
"jackett": {
|
||||
|
@ -161,7 +164,7 @@
|
|||
"qbittorrent": {
|
||||
"download": "Bajada",
|
||||
"upload": "Subida",
|
||||
"leech": "Compañeros",
|
||||
"leech": "Leech",
|
||||
"seed": "Semillas"
|
||||
},
|
||||
"mastodon": {
|
||||
|
@ -325,7 +328,38 @@
|
|||
"country": "País"
|
||||
},
|
||||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"hd": "HD"
|
||||
"channels": "Canales",
|
||||
"hd": "Alta definición"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Error",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Aprobado",
|
||||
"failed": "Fallido",
|
||||
"unknown": "Desconocido"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Bandeja de entrada",
|
||||
"total": "Total"
|
||||
},
|
||||
"deluge": {
|
||||
"download": "Descarga",
|
||||
"upload": "Subida",
|
||||
"leech": "Leech",
|
||||
"seed": "Semilla"
|
||||
},
|
||||
"diskstation": {
|
||||
"download": "Descargar",
|
||||
"upload": "Cargar",
|
||||
"leech": "Leech",
|
||||
"seed": "Semilla"
|
||||
},
|
||||
"flood": {
|
||||
"download": "Descargar",
|
||||
"upload": "Subir",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,9 @@
|
|||
"tx": "TX",
|
||||
"mem": "RAM",
|
||||
"cpu": "CPU",
|
||||
"offline": "Offline"
|
||||
"offline": "Offline",
|
||||
"error": "Error",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "Toistaa",
|
||||
|
@ -108,7 +110,8 @@
|
|||
"overseerr": {
|
||||
"pending": "Vireillä",
|
||||
"approved": "Hyväksytty",
|
||||
"available": "Saatavilla"
|
||||
"available": "Saatavilla",
|
||||
"processing": "Processing"
|
||||
},
|
||||
"pihole": {
|
||||
"queries": "Kyselyjä",
|
||||
|
@ -327,5 +330,36 @@
|
|||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"hd": "HD"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Error",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Passed",
|
||||
"failed": "Failed",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Inbox",
|
||||
"total": "Total"
|
||||
},
|
||||
"deluge": {
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed",
|
||||
"download": "Download"
|
||||
},
|
||||
"diskstation": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"flood": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,9 @@
|
|||
"tx": "Tx",
|
||||
"mem": "Mém",
|
||||
"cpu": "Cpu",
|
||||
"offline": "Hors ligne"
|
||||
"offline": "Hors ligne",
|
||||
"error": "Erreur",
|
||||
"unknown": "Inconnu"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "En lecture",
|
||||
|
@ -102,7 +104,8 @@
|
|||
"overseerr": {
|
||||
"pending": "En attente",
|
||||
"approved": "Demande",
|
||||
"available": "Disponible"
|
||||
"available": "Disponible",
|
||||
"processing": "En traitement"
|
||||
},
|
||||
"sabnzbd": {
|
||||
"rate": "Débit",
|
||||
|
@ -325,7 +328,38 @@
|
|||
"country": "Pays"
|
||||
},
|
||||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"channels": "Canaux",
|
||||
"hd": "HD"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Erreur",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Réussi",
|
||||
"failed": "Échoué",
|
||||
"unknown": "Inconnu"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Boîte de réception",
|
||||
"total": "Total"
|
||||
},
|
||||
"deluge": {
|
||||
"download": "Récep.",
|
||||
"upload": "Envoi",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"diskstation": {
|
||||
"download": "Réception",
|
||||
"upload": "Envoi",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"flood": {
|
||||
"download": "Récep.",
|
||||
"upload": "Envoi",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,9 @@
|
|||
"tx": "TX",
|
||||
"mem": "זיכרון",
|
||||
"cpu": "מעבד",
|
||||
"offline": "כבוי"
|
||||
"offline": "כבוי",
|
||||
"error": "Error",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "מנגן",
|
||||
|
@ -108,7 +110,8 @@
|
|||
"overseerr": {
|
||||
"pending": "ממתין",
|
||||
"approved": "מאושר",
|
||||
"available": "זמין"
|
||||
"available": "זמין",
|
||||
"processing": "Processing"
|
||||
},
|
||||
"pihole": {
|
||||
"queries": "שאילתות",
|
||||
|
@ -327,5 +330,36 @@
|
|||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"hd": "HD"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Error",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Passed",
|
||||
"failed": "Failed",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Inbox",
|
||||
"total": "Total"
|
||||
},
|
||||
"deluge": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"diskstation": {
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"download": "Download",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"flood": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,7 +79,9 @@
|
|||
"tx": "TX",
|
||||
"mem": "MEM",
|
||||
"cpu": "CPU",
|
||||
"offline": "Offline"
|
||||
"offline": "Offline",
|
||||
"error": "Error",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "Playing",
|
||||
|
@ -148,7 +150,8 @@
|
|||
"overseerr": {
|
||||
"pending": "Pending",
|
||||
"approved": "Approved",
|
||||
"available": "Available"
|
||||
"available": "Available",
|
||||
"processing": "Processing"
|
||||
},
|
||||
"pihole": {
|
||||
"queries": "Queries",
|
||||
|
@ -327,5 +330,36 @@
|
|||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"hd": "HD"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Error",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Passed",
|
||||
"failed": "Failed",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Inbox",
|
||||
"total": "Total"
|
||||
},
|
||||
"deluge": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"diskstation": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"flood": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,8 @@
|
|||
"overseerr": {
|
||||
"available": "Dostupno",
|
||||
"pending": "Predstoji",
|
||||
"approved": "Odobreno"
|
||||
"approved": "Odobreno",
|
||||
"processing": "Obrada"
|
||||
},
|
||||
"pihole": {
|
||||
"queries": "Upiti",
|
||||
|
@ -59,17 +60,19 @@
|
|||
"missing_type": "Nedostajuća vrsta widgeta: {{type}}",
|
||||
"api_error": "API greška",
|
||||
"status": "Stanje",
|
||||
"information": "Information",
|
||||
"information": "Informacije",
|
||||
"url": "URL",
|
||||
"raw_error": "Raw Error",
|
||||
"response_data": "Response Data"
|
||||
"raw_error": "Raw greška",
|
||||
"response_data": "Podaci odgovora"
|
||||
},
|
||||
"docker": {
|
||||
"rx": "RX",
|
||||
"tx": "TX",
|
||||
"mem": "MEM",
|
||||
"cpu": "CPU",
|
||||
"offline": "Nepovezan"
|
||||
"offline": "Nepovezan",
|
||||
"error": "Greška",
|
||||
"unknown": "Nepoznato"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "Reprodukcija",
|
||||
|
@ -320,12 +323,43 @@
|
|||
"total": "Ukupno"
|
||||
},
|
||||
"gluetun": {
|
||||
"public_ip": "Public IP",
|
||||
"region": "Region",
|
||||
"country": "Country"
|
||||
"public_ip": "Javni IP",
|
||||
"region": "Regija",
|
||||
"country": "Zemlja"
|
||||
},
|
||||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"channels": "Kanali",
|
||||
"hd": "HD"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Greška",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Prošlo",
|
||||
"failed": "Neuspjelo",
|
||||
"unknown": "Nepoznato"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Ulazni sandučić",
|
||||
"total": "Ukupno"
|
||||
},
|
||||
"deluge": {
|
||||
"download": "Preuzimanje",
|
||||
"upload": "Prijenos",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"diskstation": {
|
||||
"download": "Preuzimanje",
|
||||
"upload": "Prijenos",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"flood": {
|
||||
"download": "Preuzimanje",
|
||||
"upload": "Prijenos",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,9 @@
|
|||
"tx": "TX",
|
||||
"mem": "MEM",
|
||||
"cpu": "CPU",
|
||||
"offline": "Offline"
|
||||
"offline": "Offline",
|
||||
"error": "Error",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"lidarr": {
|
||||
"albums": "Albumok",
|
||||
|
@ -108,7 +110,8 @@
|
|||
"overseerr": {
|
||||
"pending": "Függőben",
|
||||
"approved": "Engedélyezett",
|
||||
"available": "Elérhető"
|
||||
"available": "Elérhető",
|
||||
"processing": "Processing"
|
||||
},
|
||||
"pihole": {
|
||||
"queries": "Lekérdezések",
|
||||
|
@ -327,5 +330,36 @@
|
|||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"hd": "HD"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Error",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Passed",
|
||||
"failed": "Failed",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Inbox",
|
||||
"total": "Total"
|
||||
},
|
||||
"deluge": {
|
||||
"download": "Download",
|
||||
"seed": "Seed",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech"
|
||||
},
|
||||
"diskstation": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"flood": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
"mem": "MEM",
|
||||
"cpu": "CPU",
|
||||
"offline": "Offline",
|
||||
"rx": "RX"
|
||||
"rx": "RX",
|
||||
"error": "Error",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "In riproduzione",
|
||||
|
@ -102,7 +104,8 @@
|
|||
"overseerr": {
|
||||
"pending": "In attesa",
|
||||
"approved": "Approvati",
|
||||
"available": "Disponibili"
|
||||
"available": "Disponibili",
|
||||
"processing": "Processing"
|
||||
},
|
||||
"sabnzbd": {
|
||||
"rate": "Rapporto",
|
||||
|
@ -327,5 +330,36 @@
|
|||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"hd": "HD"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Error",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Passed",
|
||||
"failed": "Failed",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Inbox",
|
||||
"total": "Total"
|
||||
},
|
||||
"deluge": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"diskstation": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"flood": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -126,10 +126,10 @@
|
|||
"missing_type": "Jenis Widget Hilang: {{type}}",
|
||||
"api_error": "Masalah API",
|
||||
"status": "Status",
|
||||
"information": "Information",
|
||||
"information": "Informasi",
|
||||
"url": "URL",
|
||||
"raw_error": "Raw Error",
|
||||
"response_data": "Response Data"
|
||||
"raw_error": "Ralat Mentah",
|
||||
"response_data": "Data Respon"
|
||||
},
|
||||
"weather": {
|
||||
"current": "Lokasi Sekarang",
|
||||
|
@ -150,7 +150,9 @@
|
|||
"tx": "TX",
|
||||
"mem": "MEM",
|
||||
"cpu": "CPU",
|
||||
"offline": "Luar talian"
|
||||
"offline": "Luar talian",
|
||||
"error": "Ralat",
|
||||
"unknown": "Tidak Diketahui"
|
||||
},
|
||||
"changedetectionio": {
|
||||
"totalObserved": "Jumlah Diperhatikan",
|
||||
|
@ -218,7 +220,8 @@
|
|||
"overseerr": {
|
||||
"pending": "Tertangguh",
|
||||
"approved": "Lulus",
|
||||
"available": "Sudah Ada"
|
||||
"available": "Sudah Ada",
|
||||
"processing": "Processing"
|
||||
},
|
||||
"pihole": {
|
||||
"queries": "Permintaan",
|
||||
|
@ -320,12 +323,43 @@
|
|||
"total": "Jumlah"
|
||||
},
|
||||
"gluetun": {
|
||||
"public_ip": "Public IP",
|
||||
"region": "Region",
|
||||
"country": "Country"
|
||||
"public_ip": "IP Awam",
|
||||
"region": "Rantau",
|
||||
"country": "Negara"
|
||||
},
|
||||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"channels": "Saluran",
|
||||
"hd": "HD"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Ralat",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Lulus",
|
||||
"failed": "Gagal",
|
||||
"unknown": "Tidak Diketahui"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Peti Masuk",
|
||||
"total": "Jumlah"
|
||||
},
|
||||
"deluge": {
|
||||
"download": "Muat Turun",
|
||||
"upload": "Muat Naik",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"diskstation": {
|
||||
"upload": "Upload",
|
||||
"download": "Download",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"flood": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,9 @@
|
|||
"tx": "Sendt",
|
||||
"mem": "Minne",
|
||||
"cpu": "Prosessor",
|
||||
"offline": "Frakoblet"
|
||||
"offline": "Frakoblet",
|
||||
"error": "Error",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "Spiller",
|
||||
|
@ -102,7 +104,8 @@
|
|||
"overseerr": {
|
||||
"pending": "Venter",
|
||||
"approved": "Godkjent",
|
||||
"available": "Tilgjengelig"
|
||||
"available": "Tilgjengelig",
|
||||
"processing": "Processing"
|
||||
},
|
||||
"sabnzbd": {
|
||||
"rate": "Takt",
|
||||
|
@ -327,5 +330,36 @@
|
|||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"hd": "HD"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Error",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Passed",
|
||||
"failed": "Failed",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Inbox",
|
||||
"total": "Total"
|
||||
},
|
||||
"deluge": {
|
||||
"leech": "Leech",
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"diskstation": {
|
||||
"leech": "Leech",
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"flood": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,9 @@
|
|||
"tx": "TX",
|
||||
"mem": "MEM",
|
||||
"cpu": "CPU",
|
||||
"offline": "Offline"
|
||||
"offline": "Offline",
|
||||
"error": "Error",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"speedtest": {
|
||||
"upload": "Upload",
|
||||
|
@ -102,7 +104,8 @@
|
|||
"overseerr": {
|
||||
"pending": "Pending",
|
||||
"approved": "Approved",
|
||||
"available": "Available"
|
||||
"available": "Available",
|
||||
"processing": "Processing"
|
||||
},
|
||||
"sabnzbd": {
|
||||
"rate": "Rate",
|
||||
|
@ -327,5 +330,36 @@
|
|||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"hd": "HD"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Error",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Passed",
|
||||
"failed": "Failed",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Inbox",
|
||||
"total": "Total"
|
||||
},
|
||||
"deluge": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"diskstation": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"flood": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,17 +53,19 @@
|
|||
"missing_type": "Brakujący typ widżetu: {{type}}",
|
||||
"api_error": "Błąd API",
|
||||
"status": "Stan",
|
||||
"url": "URL",
|
||||
"information": "Information",
|
||||
"raw_error": "Raw Error",
|
||||
"response_data": "Response Data"
|
||||
"url": "Adres URL",
|
||||
"information": "Informacje",
|
||||
"raw_error": "Niesformatowany błąd",
|
||||
"response_data": "Dane odpowiedzi"
|
||||
},
|
||||
"docker": {
|
||||
"rx": "RX",
|
||||
"tx": "TX",
|
||||
"mem": "MEM",
|
||||
"cpu": "CPU",
|
||||
"offline": "Offline"
|
||||
"rx": "Rx",
|
||||
"tx": "Tx",
|
||||
"mem": "Pamięć",
|
||||
"cpu": "Procesor",
|
||||
"offline": "Offline",
|
||||
"error": "Błąd",
|
||||
"unknown": "Nieznany"
|
||||
},
|
||||
"nzbget": {
|
||||
"rate": "Szybkość",
|
||||
|
@ -95,7 +97,7 @@
|
|||
"wanted": "Poszukiwane",
|
||||
"queued": "W kolejce",
|
||||
"movies": "Filmy",
|
||||
"missing": "Missing"
|
||||
"missing": "Brakujące"
|
||||
},
|
||||
"lidarr": {
|
||||
"wanted": "Poszukiwane",
|
||||
|
@ -124,7 +126,8 @@
|
|||
"overseerr": {
|
||||
"pending": "Oczekiwane",
|
||||
"approved": "Zaakceptowane",
|
||||
"available": "Dostępne"
|
||||
"available": "Dostępne",
|
||||
"processing": "Przetwarzane"
|
||||
},
|
||||
"pihole": {
|
||||
"queries": "Zapytania",
|
||||
|
@ -166,14 +169,14 @@
|
|||
},
|
||||
"mastodon": {
|
||||
"user_count": "Użytkownicy",
|
||||
"status_count": "Posts",
|
||||
"status_count": "Posty",
|
||||
"domain_count": "Domeny"
|
||||
},
|
||||
"strelaysrv": {
|
||||
"numActiveSessions": "Sesje",
|
||||
"numConnections": "Połączenia",
|
||||
"dataRelayed": "Relayed",
|
||||
"transferRate": "Rate"
|
||||
"dataRelayed": "Przekazane",
|
||||
"transferRate": "Przesył"
|
||||
},
|
||||
"authentik": {
|
||||
"users": "Użytkownicy",
|
||||
|
@ -181,10 +184,10 @@
|
|||
"failedLoginsLast24H": "Nieudane logowania (24h)"
|
||||
},
|
||||
"proxmox": {
|
||||
"mem": "MEM",
|
||||
"cpu": "CPU",
|
||||
"lxc": "LXC",
|
||||
"vms": "VMs"
|
||||
"mem": "Pamięć",
|
||||
"cpu": "Procesor",
|
||||
"lxc": "Kontenery LXC",
|
||||
"vms": "Maszyn wirtualnych"
|
||||
},
|
||||
"unifi": {
|
||||
"users": "Użytkownicy",
|
||||
|
@ -203,25 +206,25 @@
|
|||
"wlan_devices": "Urządzenia WLAN"
|
||||
},
|
||||
"plex": {
|
||||
"streams": "Active Streams",
|
||||
"streams": "Aktywne strumienie",
|
||||
"movies": "Filmy",
|
||||
"tv": "Seriale"
|
||||
},
|
||||
"glances": {
|
||||
"cpu": "CPU",
|
||||
"mem": "MEM",
|
||||
"cpu": "Procesor",
|
||||
"mem": "Pamięć",
|
||||
"wait": "Proszę czekać"
|
||||
},
|
||||
"changedetectionio": {
|
||||
"diffsDetected": "Diffs Detected",
|
||||
"totalObserved": "Total Observed"
|
||||
"diffsDetected": "Wykryto różnic",
|
||||
"totalObserved": "Obserwowanych ogółem"
|
||||
},
|
||||
"wmo": {
|
||||
"77-day": "Snow Grains",
|
||||
"77-day": "Ziarnisty śnieg",
|
||||
"0-day": "Słoneczny",
|
||||
"0-night": "Clear",
|
||||
"1-day": "Mainly Sunny",
|
||||
"1-night": "Mainly Clear",
|
||||
"0-night": "Bezchmurny",
|
||||
"1-day": "Głównie słoneczny",
|
||||
"1-night": "Głównie bezchmurny",
|
||||
"2-day": "Częściowo pochmurnie",
|
||||
"2-night": "Częściowo pochmurnie",
|
||||
"3-day": "Pochmurnie",
|
||||
|
@ -236,10 +239,10 @@
|
|||
"53-night": "Mżawka",
|
||||
"55-day": "Ciężka mżawka",
|
||||
"55-night": "Ciężka mżawka",
|
||||
"56-day": "Light Freezing Drizzle",
|
||||
"56-night": "Light Freezing Drizzle",
|
||||
"57-day": "Freezing Drizzle",
|
||||
"57-night": "Freezing Drizzle",
|
||||
"56-day": "Lekko chłodna mżawka",
|
||||
"56-night": "Lekko chłodna mżawka",
|
||||
"57-day": "Chłodna mżawka",
|
||||
"57-night": "Chłodna mżawka",
|
||||
"61-day": "Lekki deszcz",
|
||||
"61-night": "Lekki deszcz",
|
||||
"63-day": "Deszcz",
|
||||
|
@ -256,7 +259,7 @@
|
|||
"73-night": "Śnieg",
|
||||
"75-day": "Ciężki śnieg",
|
||||
"75-night": "Ciężki śnieg",
|
||||
"77-night": "Snow Grains",
|
||||
"77-night": "Ziarnisty śnieg",
|
||||
"80-day": "Lekkie opady",
|
||||
"80-night": "Lekkie opady",
|
||||
"81-day": "Opady",
|
||||
|
@ -275,7 +278,7 @@
|
|||
"99-night": "Burza z gradobiciem"
|
||||
},
|
||||
"quicklaunch": {
|
||||
"bookmark": "Bookmark",
|
||||
"bookmark": "Zakładka",
|
||||
"service": "Usługi"
|
||||
},
|
||||
"homebridge": {
|
||||
|
@ -287,45 +290,76 @@
|
|||
"child_bridges_status": "{{ok}}/{{total}}"
|
||||
},
|
||||
"autobrr": {
|
||||
"approvedPushes": "Approved",
|
||||
"rejectedPushes": "Rejected",
|
||||
"filters": "Filters",
|
||||
"indexers": "Indexers"
|
||||
"approvedPushes": "Zaakceptowane",
|
||||
"rejectedPushes": "Odrzucone",
|
||||
"filters": "Filtry",
|
||||
"indexers": "Indeksery"
|
||||
},
|
||||
"watchtower": {
|
||||
"containers_scanned": "Scanned",
|
||||
"containers_updated": "Updated",
|
||||
"containers_failed": "Failed"
|
||||
"containers_scanned": "Zeskanowane",
|
||||
"containers_updated": "Zaktualizowane",
|
||||
"containers_failed": "Niepowodzenie"
|
||||
},
|
||||
"tubearchivist": {
|
||||
"downloads": "Queue",
|
||||
"videos": "Videos",
|
||||
"channels": "Channels",
|
||||
"playlists": "Playlists"
|
||||
"downloads": "Kolejka",
|
||||
"videos": "Pliki wideo",
|
||||
"channels": "Kanały",
|
||||
"playlists": "Playlisty"
|
||||
},
|
||||
"truenas": {
|
||||
"load": "System Load",
|
||||
"uptime": "Uptime",
|
||||
"alerts": "Alerts",
|
||||
"load": "Obciążenie systemu",
|
||||
"uptime": "Czas działania",
|
||||
"alerts": "Ostrzeżenia",
|
||||
"time": "{{value, number(style: unit; unitDisplay: long;)}}"
|
||||
},
|
||||
"navidrome": {
|
||||
"please_wait": "Please Wait",
|
||||
"nothing_streaming": "No Active Streams"
|
||||
"please_wait": "Proszę czekać",
|
||||
"nothing_streaming": "Brak aktywnych strumieni"
|
||||
},
|
||||
"pyload": {
|
||||
"speed": "Speed",
|
||||
"active": "Active",
|
||||
"queue": "Queue",
|
||||
"total": "Total"
|
||||
"speed": "Prędkość",
|
||||
"active": "Aktywne",
|
||||
"queue": "Kolejka",
|
||||
"total": "Razem"
|
||||
},
|
||||
"gluetun": {
|
||||
"public_ip": "Public IP",
|
||||
"public_ip": "Adres publiczny",
|
||||
"region": "Region",
|
||||
"country": "Country"
|
||||
"country": "Państwo"
|
||||
},
|
||||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"channels": "Kanały",
|
||||
"hd": "HD"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Błąd",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Powodzenie",
|
||||
"failed": "Niepowodzenie",
|
||||
"unknown": "Nieznane"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Skrzynka odbiorcza",
|
||||
"total": "W sumie"
|
||||
},
|
||||
"deluge": {
|
||||
"download": "Pobieranie",
|
||||
"upload": "Wysyłanie",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"diskstation": {
|
||||
"download": "Pobieranie",
|
||||
"upload": "Wysyłanie",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"flood": {
|
||||
"download": "Pobieranie",
|
||||
"upload": "Wysyłanie",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,7 +57,9 @@
|
|||
"tx": "Tx",
|
||||
"mem": "Mem",
|
||||
"cpu": "CPU",
|
||||
"offline": "Desligado"
|
||||
"offline": "Desligado",
|
||||
"error": "Error",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "Reproduzindo",
|
||||
|
@ -126,7 +128,8 @@
|
|||
"overseerr": {
|
||||
"pending": "Pendente",
|
||||
"approved": "Aprovado",
|
||||
"available": "Disponível"
|
||||
"available": "Disponível",
|
||||
"processing": "Processing"
|
||||
},
|
||||
"pihole": {
|
||||
"queries": "Consultas",
|
||||
|
@ -327,5 +330,36 @@
|
|||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"hd": "HD"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Error",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Passed",
|
||||
"failed": "Failed",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Inbox",
|
||||
"total": "Total"
|
||||
},
|
||||
"deluge": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"diskstation": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"flood": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
"missing_type": "Widget ausente: {{type}}",
|
||||
"api_error": "Erro da API",
|
||||
"status": "Status",
|
||||
"information": "Information",
|
||||
"url": "URL",
|
||||
"information": "Informação",
|
||||
"url": "Endereço URL",
|
||||
"raw_error": "Raw Error",
|
||||
"response_data": "Response Data"
|
||||
"response_data": "Dados da Resposta"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Pesquisar…"
|
||||
|
@ -23,7 +23,9 @@
|
|||
"tx": "Tx",
|
||||
"mem": "Mem",
|
||||
"cpu": "CPU",
|
||||
"offline": "Desligado"
|
||||
"offline": "Desligado",
|
||||
"error": "Erro",
|
||||
"unknown": "Desconhecido"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "A reproduzir",
|
||||
|
@ -113,7 +115,8 @@
|
|||
"overseerr": {
|
||||
"pending": "Pendente",
|
||||
"approved": "Aprovado",
|
||||
"available": "Disponível"
|
||||
"available": "Disponível",
|
||||
"processing": "Processing"
|
||||
},
|
||||
"sabnzbd": {
|
||||
"rate": "Taxa",
|
||||
|
@ -147,7 +150,7 @@
|
|||
"transmission": {
|
||||
"download": "Baixando",
|
||||
"upload": "Enviando",
|
||||
"leech": "Sanguessugas",
|
||||
"leech": "Leech",
|
||||
"seed": "Semeadores"
|
||||
},
|
||||
"jackett": {
|
||||
|
@ -198,25 +201,25 @@
|
|||
"vms": "VMs"
|
||||
},
|
||||
"unifi": {
|
||||
"users": "Users",
|
||||
"uptime": "System Uptime",
|
||||
"days": "Days",
|
||||
"users": "Utilizadores",
|
||||
"uptime": "Tempo de Atividade do Sistema",
|
||||
"days": "Dias",
|
||||
"wan": "WAN",
|
||||
"lan_users": "LAN Users",
|
||||
"wlan_users": "WLAN Users",
|
||||
"lan_users": "Utilizadores LAN",
|
||||
"wlan_users": "Utilizadores WLAN",
|
||||
"up": "UP",
|
||||
"down": "DOWN",
|
||||
"wait": "Please wait",
|
||||
"wait": "Por favor aguarde",
|
||||
"lan": "LAN",
|
||||
"wlan": "WLAN",
|
||||
"devices": "Devices",
|
||||
"lan_devices": "LAN Devices",
|
||||
"wlan_devices": "WLAN Devices"
|
||||
"devices": "Dispositivos",
|
||||
"lan_devices": "Dispositivos LAN",
|
||||
"wlan_devices": "Dispositivos WLAN"
|
||||
},
|
||||
"plex": {
|
||||
"streams": "Active Streams",
|
||||
"movies": "Movies",
|
||||
"tv": "TV Shows"
|
||||
"streams": "Streams Ativas",
|
||||
"movies": "Filmes",
|
||||
"tv": "Series de TV"
|
||||
},
|
||||
"glances": {
|
||||
"cpu": "CPU",
|
||||
|
@ -224,8 +227,8 @@
|
|||
"wait": "Please wait"
|
||||
},
|
||||
"changedetectionio": {
|
||||
"totalObserved": "Total Observed",
|
||||
"diffsDetected": "Diffs Detected"
|
||||
"totalObserved": "Total Observado",
|
||||
"diffsDetected": "Diferenças Detetadas"
|
||||
},
|
||||
"wmo": {
|
||||
"0-day": "Sunny",
|
||||
|
@ -338,5 +341,36 @@
|
|||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"hd": "HD"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Erro",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Passed",
|
||||
"failed": "Failed",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Inbox",
|
||||
"total": "Total"
|
||||
},
|
||||
"deluge": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"diskstation": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"flood": {
|
||||
"download": "Descarregar",
|
||||
"upload": "Carregar",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,9 @@
|
|||
"tx": "TX",
|
||||
"mem": "MEM",
|
||||
"cpu": "CPU",
|
||||
"offline": "Offline"
|
||||
"offline": "Offline",
|
||||
"error": "Error",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"jellyseerr": {
|
||||
"approved": "Aprobate",
|
||||
|
@ -21,7 +23,8 @@
|
|||
"overseerr": {
|
||||
"pending": "În așteptare",
|
||||
"approved": "Aprobate",
|
||||
"available": "Disponibile"
|
||||
"available": "Disponibile",
|
||||
"processing": "Processing"
|
||||
},
|
||||
"pihole": {
|
||||
"queries": "Cereri",
|
||||
|
@ -327,5 +330,36 @@
|
|||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"hd": "HD"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Error",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Passed",
|
||||
"failed": "Failed",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Inbox",
|
||||
"total": "Total"
|
||||
},
|
||||
"deluge": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"diskstation": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"flood": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
"missing_type": "Отсутствует тип виджета: {{type}}",
|
||||
"api_error": "Ошибка API",
|
||||
"status": "Статус",
|
||||
"information": "Information",
|
||||
"information": "Информация",
|
||||
"url": "URL",
|
||||
"raw_error": "Raw Error",
|
||||
"response_data": "Response Data"
|
||||
"response_data": "Данные ответа"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Поиск…"
|
||||
|
@ -15,21 +15,23 @@
|
|||
"total": "Всего",
|
||||
"free": "Свободно",
|
||||
"used": "Использовано",
|
||||
"load": "Load",
|
||||
"cpu": "CPU"
|
||||
"load": "Загрузка",
|
||||
"cpu": "Процессор"
|
||||
},
|
||||
"docker": {
|
||||
"rx": "Rx",
|
||||
"tx": "Тx",
|
||||
"mem": "Память",
|
||||
"cpu": "Процессор",
|
||||
"offline": "Не в сети"
|
||||
"offline": "Не в сети",
|
||||
"error": "Ошибка",
|
||||
"unknown": "Неизвестный"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "Воспроизведение",
|
||||
"transcoding": "Транскодирование",
|
||||
"bitrate": "Битрейт",
|
||||
"no_active": "No Active Streams"
|
||||
"no_active": "Нет активных потоков"
|
||||
},
|
||||
"tautulli": {
|
||||
"playing": "Воспроизведение",
|
||||
|
@ -102,7 +104,8 @@
|
|||
"overseerr": {
|
||||
"pending": "Pending",
|
||||
"approved": "Approved",
|
||||
"available": "Available"
|
||||
"available": "Available",
|
||||
"processing": "Processing"
|
||||
},
|
||||
"sabnzbd": {
|
||||
"rate": "Rate",
|
||||
|
@ -187,19 +190,19 @@
|
|||
"vms": "VMs"
|
||||
},
|
||||
"unifi": {
|
||||
"users": "Users",
|
||||
"uptime": "System Uptime",
|
||||
"days": "Days",
|
||||
"users": "Пользователи",
|
||||
"uptime": "Время работы системы",
|
||||
"days": "Дней",
|
||||
"wan": "WAN",
|
||||
"lan_users": "LAN Users",
|
||||
"wlan_users": "WLAN Users",
|
||||
"lan_users": "Пользователи LAN",
|
||||
"wlan_users": "Пользователи WLAN",
|
||||
"up": "UP",
|
||||
"down": "DOWN",
|
||||
"wait": "Please wait",
|
||||
"wait": "Подождите",
|
||||
"lan": "LAN",
|
||||
"wlan": "WLAN",
|
||||
"devices": "Devices",
|
||||
"lan_devices": "LAN Devices",
|
||||
"devices": "Устройства",
|
||||
"lan_devices": "Устройства подключённые по LAN",
|
||||
"wlan_devices": "WLAN Devices"
|
||||
},
|
||||
"plex": {
|
||||
|
@ -213,8 +216,8 @@
|
|||
"wait": "Please wait"
|
||||
},
|
||||
"changedetectionio": {
|
||||
"totalObserved": "Total Observed",
|
||||
"diffsDetected": "Diffs Detected"
|
||||
"totalObserved": "Всего наблюдаемых",
|
||||
"diffsDetected": "Обнаружены различия"
|
||||
},
|
||||
"wmo": {
|
||||
"0-day": "Sunny",
|
||||
|
@ -327,5 +330,36 @@
|
|||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"hd": "HD"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Ошибка",
|
||||
"ping": "Пинг"
|
||||
},
|
||||
"scrutiny": {
|
||||
"failed": "Failed",
|
||||
"unknown": "Unknown",
|
||||
"passed": "Passed"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Inbox",
|
||||
"total": "Total"
|
||||
},
|
||||
"deluge": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"diskstation": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"seed": "Seed",
|
||||
"leech": "Leech"
|
||||
},
|
||||
"flood": {
|
||||
"upload": "Upload",
|
||||
"download": "Download",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,9 @@
|
|||
"tx": "TX",
|
||||
"mem": "MEM",
|
||||
"cpu": "CPU",
|
||||
"offline": "Offline"
|
||||
"offline": "Offline",
|
||||
"error": "Error",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "Playing",
|
||||
|
@ -129,7 +131,8 @@
|
|||
"overseerr": {
|
||||
"pending": "Pending",
|
||||
"approved": "Approved",
|
||||
"available": "Available"
|
||||
"available": "Available",
|
||||
"processing": "Processing"
|
||||
},
|
||||
"pihole": {
|
||||
"queries": "Queries",
|
||||
|
@ -327,5 +330,36 @@
|
|||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"hd": "HD"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Error",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Passed",
|
||||
"failed": "Failed",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Inbox",
|
||||
"total": "Total"
|
||||
},
|
||||
"deluge": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"diskstation": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"flood": {
|
||||
"download": "Download",
|
||||
"seed": "Seed",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,9 @@
|
|||
"tx": "TX",
|
||||
"mem": "MEM",
|
||||
"cpu": "CPU",
|
||||
"offline": "Offline"
|
||||
"offline": "Offline",
|
||||
"error": "Error",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Sök…"
|
||||
|
@ -102,7 +104,8 @@
|
|||
"overseerr": {
|
||||
"pending": "Avvaktar",
|
||||
"approved": "Godkända",
|
||||
"available": "Tillgänglig"
|
||||
"available": "Tillgänglig",
|
||||
"processing": "Processing"
|
||||
},
|
||||
"pihole": {
|
||||
"blocked": "Blockerad",
|
||||
|
@ -327,5 +330,36 @@
|
|||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"hd": "HD"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Error",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Passed",
|
||||
"failed": "Failed",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Inbox",
|
||||
"total": "Total"
|
||||
},
|
||||
"deluge": {
|
||||
"download": "Download",
|
||||
"leech": "Leech",
|
||||
"upload": "Upload",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"diskstation": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"flood": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,7 +46,9 @@
|
|||
"tx": "TX",
|
||||
"mem": "MEM",
|
||||
"cpu": "సీపియూ",
|
||||
"offline": "ఆఫ్లైన్"
|
||||
"offline": "ఆఫ్లైన్",
|
||||
"error": "Error",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "ఆడుతున్నారు",
|
||||
|
@ -120,7 +122,8 @@
|
|||
"overseerr": {
|
||||
"pending": "పెండింగ్",
|
||||
"approved": "ఆమోదించబడింది",
|
||||
"available": "అందుబాటులో"
|
||||
"available": "అందుబాటులో",
|
||||
"processing": "Processing"
|
||||
},
|
||||
"pihole": {
|
||||
"queries": "ప్రశ్నలు",
|
||||
|
@ -327,5 +330,36 @@
|
|||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"hd": "HD"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Error",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Passed",
|
||||
"failed": "Failed",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Inbox",
|
||||
"total": "Total"
|
||||
},
|
||||
"deluge": {
|
||||
"seed": "Seed",
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech"
|
||||
},
|
||||
"diskstation": {
|
||||
"leech": "Leech",
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"flood": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,9 @@
|
|||
"tx": "Giden Veri",
|
||||
"mem": "Bellek",
|
||||
"cpu": "İşlemci",
|
||||
"offline": "Çevrimdışı"
|
||||
"offline": "Çevrimdışı",
|
||||
"error": "Error",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "Oynatılıyor",
|
||||
|
@ -129,7 +131,8 @@
|
|||
"overseerr": {
|
||||
"pending": "Bekliyor",
|
||||
"approved": "Onaylı",
|
||||
"available": "Kullanılabilir"
|
||||
"available": "Kullanılabilir",
|
||||
"processing": "Processing"
|
||||
},
|
||||
"pihole": {
|
||||
"queries": "Sorgular",
|
||||
|
@ -327,5 +330,36 @@
|
|||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"hd": "HD"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Error",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Passed",
|
||||
"failed": "Failed",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Inbox",
|
||||
"total": "Total"
|
||||
},
|
||||
"deluge": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"diskstation": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"flood": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,9 @@
|
|||
"tx": "TX",
|
||||
"mem": "BỘ NHỚ",
|
||||
"cpu": "CPU",
|
||||
"offline": "Ngoại tuyến"
|
||||
"offline": "Ngoại tuyến",
|
||||
"error": "Error",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "Đang chơi",
|
||||
|
@ -102,7 +104,8 @@
|
|||
"overseerr": {
|
||||
"pending": "Pending",
|
||||
"approved": "Đã duyệt",
|
||||
"available": "Available"
|
||||
"available": "Available",
|
||||
"processing": "Processing"
|
||||
},
|
||||
"sabnzbd": {
|
||||
"rate": "Rate",
|
||||
|
@ -327,5 +330,36 @@
|
|||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"hd": "HD"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Error",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Passed",
|
||||
"failed": "Failed",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Inbox",
|
||||
"total": "Total"
|
||||
},
|
||||
"deluge": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"diskstation": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"flood": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"seed": "Seed",
|
||||
"leech": "Leech"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,9 @@
|
|||
"tx": "發送",
|
||||
"mem": "內存",
|
||||
"cpu": "處理器",
|
||||
"offline": "離線"
|
||||
"offline": "離線",
|
||||
"error": "Error",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"nzbget": {
|
||||
"rate": "速度",
|
||||
|
@ -108,7 +110,8 @@
|
|||
"overseerr": {
|
||||
"pending": "待定",
|
||||
"approved": "批准",
|
||||
"available": "可用"
|
||||
"available": "可用",
|
||||
"processing": "Processing"
|
||||
},
|
||||
"pihole": {
|
||||
"queries": "查詢",
|
||||
|
@ -327,5 +330,36 @@
|
|||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"hd": "HD"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Error",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Passed",
|
||||
"failed": "Failed",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Inbox",
|
||||
"total": "Total"
|
||||
},
|
||||
"deluge": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"diskstation": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"flood": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,9 @@
|
|||
"tx": "发送",
|
||||
"mem": "内存",
|
||||
"cpu": "处理器",
|
||||
"offline": "离线"
|
||||
"offline": "离线",
|
||||
"error": "Error",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "播放中",
|
||||
|
@ -102,7 +104,8 @@
|
|||
"overseerr": {
|
||||
"pending": "待办",
|
||||
"approved": "已批准",
|
||||
"available": "可用"
|
||||
"available": "可用",
|
||||
"processing": "Processing"
|
||||
},
|
||||
"sabnzbd": {
|
||||
"rate": "速率",
|
||||
|
@ -293,9 +296,9 @@
|
|||
"indexers": "Indexers"
|
||||
},
|
||||
"watchtower": {
|
||||
"containers_scanned": "Scanned",
|
||||
"containers_updated": "Updated",
|
||||
"containers_failed": "Failed"
|
||||
"containers_scanned": "以扫描",
|
||||
"containers_updated": "以升级",
|
||||
"containers_failed": "失败"
|
||||
},
|
||||
"tubearchivist": {
|
||||
"downloads": "Queue",
|
||||
|
@ -327,5 +330,36 @@
|
|||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"hd": "HD"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Error",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Passed",
|
||||
"failed": "Failed",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Inbox",
|
||||
"total": "Total"
|
||||
},
|
||||
"deluge": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"diskstation": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"flood": {
|
||||
"leech": "Leech",
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,9 @@
|
|||
"offline": "Offline",
|
||||
"tx": "TX",
|
||||
"mem": "MEM",
|
||||
"cpu": "CPU"
|
||||
"cpu": "CPU",
|
||||
"error": "Error",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "Playing",
|
||||
|
@ -87,7 +89,8 @@
|
|||
"overseerr": {
|
||||
"pending": "Pending",
|
||||
"approved": "Approved",
|
||||
"available": "Available"
|
||||
"available": "Available",
|
||||
"processing": "Processing"
|
||||
},
|
||||
"pihole": {
|
||||
"queries": "Queries",
|
||||
|
@ -327,5 +330,36 @@
|
|||
"hdhomerun": {
|
||||
"channels": "Channels",
|
||||
"hd": "HD"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Error",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"scrutiny": {
|
||||
"passed": "Passed",
|
||||
"failed": "Failed",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"paperlessngx": {
|
||||
"inbox": "Inbox",
|
||||
"total": "Total"
|
||||
},
|
||||
"deluge": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"diskstation": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"flood": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,18 +107,19 @@ export default function QuickLaunch({servicesAndBookmarks, searchString, setSear
|
|||
|
||||
function highlightText(text) {
|
||||
const parts = text.split(new RegExp(`(${searchString})`, 'gi'));
|
||||
return <span>{parts.map(part => part.toLowerCase() === searchString.toLowerCase() ? <span className="bg-theme-300/10">{part}</span> : part)}</span>;
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
return <span>{parts.map((part, i) => part.toLowerCase() === searchString.toLowerCase() ? <span key={`${searchString}_${i}`} className="bg-theme-300/10">{part}</span> : part)}</span>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames(
|
||||
"relative z-10 ease-in-out duration-300 transition-opacity",
|
||||
"relative z-20 ease-in-out duration-300 transition-opacity",
|
||||
hidden && !isOpen && "hidden",
|
||||
!hidden && isOpen && "opacity-100",
|
||||
!isOpen && "opacity-0",
|
||||
)} role="dialog" aria-modal="true">
|
||||
<div className="fixed inset-0 bg-gray-500 bg-opacity-50" />
|
||||
<div className="fixed inset-0 z-10 overflow-y-auto">
|
||||
<div className="fixed inset-0 z-20 overflow-y-auto">
|
||||
<div className="flex min-h-full min-w-full items-start justify-center text-center">
|
||||
<dialog className="mt-[10%] min-w-[80%] max-w-[90%] md:min-w-[40%] rounded-md p-0 block font-medium text-theme-700 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-theme-900/10 dark:shadow-theme-900/20 bg-theme-50 dark:bg-theme-800">
|
||||
<input placeholder="Search" className={classNames(
|
||||
|
@ -147,7 +148,7 @@ export default function QuickLaunch({servicesAndBookmarks, searchString, setSear
|
|||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs text-theme-600 font-bold pointer-events-none">{r.abbr ? t("quicklaunch.bookmark") : t("quicklaunch.service")}</div>
|
||||
<div className="text-xs text-theme-600 font-bold pointer-events-none">{r.type === 'service' ? t("quicklaunch.service") : t("quicklaunch.bookmark")}</div>
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
|
|
|
@ -3,6 +3,7 @@ import { useContext, useState } from "react";
|
|||
|
||||
import Status from "./status";
|
||||
import Widget from "./widget";
|
||||
import Ping from "./ping";
|
||||
import KubernetesStatus from "./kubernetes-status";
|
||||
|
||||
import Docker from "widgets/docker/component";
|
||||
|
@ -32,7 +33,7 @@ export default function Item({ service }) {
|
|||
<div
|
||||
className={`${
|
||||
hasLink ? "cursor-pointer " : " "
|
||||
}transition-all h-15 mb-3 p-1 rounded-md font-medium text-theme-700 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-theme-900/10 dark:shadow-theme-900/20 bg-theme-100/20 hover:bg-theme-300/20 dark:bg-white/5 dark:hover:bg-white/10`}
|
||||
}transition-all h-15 mb-3 p-1 rounded-md font-medium text-theme-700 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-theme-900/10 dark:shadow-theme-900/20 bg-theme-100/20 hover:bg-theme-300/20 dark:bg-white/5 dark:hover:bg-white/10 relative`}
|
||||
>
|
||||
<div className="flex select-none">
|
||||
{service.icon &&
|
||||
|
@ -72,26 +73,35 @@ export default function Item({ service }) {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{service.container && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => (statsOpen ? closeStats() : setStatsOpen(true))}
|
||||
className="flex-shrink-0 flex items-center justify-center w-12 cursor-pointer"
|
||||
>
|
||||
<Status service={service} />
|
||||
<span className="sr-only">View container stats</span>
|
||||
</button>
|
||||
)}
|
||||
{service.app && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => (statsOpen ? closeStats() : setStatsOpen(true))}
|
||||
className="flex-shrink-0 flex items-center justify-center w-12 cursor-pointer"
|
||||
>
|
||||
<KubernetesStatus service={service} />
|
||||
<span className="sr-only">View container stats</span>
|
||||
</button>
|
||||
)}
|
||||
<div className="absolute top-0 right-0 w-1/2 flex flex-row justify-end gap-2 mr-2">
|
||||
{service.ping && (
|
||||
<div className="flex-shrink-0 flex items-center justify-center cursor-pointer">
|
||||
<Ping service={service} />
|
||||
<span className="sr-only">Ping status</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{service.container && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => (statsOpen ? closeStats() : setStatsOpen(true))}
|
||||
className="flex-shrink-0 flex items-center justify-center cursor-pointer"
|
||||
>
|
||||
<Status service={service} />
|
||||
<span className="sr-only">View container stats</span>
|
||||
</button>
|
||||
)}
|
||||
{service.app && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => (statsOpen ? closeStats() : setStatsOpen(true))}
|
||||
className="flex-shrink-0 flex items-center justify-center w-12 cursor-pointer"
|
||||
>
|
||||
<KubernetesStatus service={service} />
|
||||
<span className="sr-only">View container stats</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{service.container && service.server && (
|
||||
|
|
44
src/components/services/ping.jsx
Normal file
44
src/components/services/ping.jsx
Normal file
|
@ -0,0 +1,44 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import useSWR from "swr";
|
||||
|
||||
export default function Ping({ service }) {
|
||||
const { t } = useTranslation();
|
||||
const { data, error } = useSWR(`/api/ping?${new URLSearchParams({ping: service.ping}).toString()}`, {
|
||||
refreshInterval: 30000
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="w-auto px-1.5 py-0.5 text-center bg-theme-500/10 dark:bg-theme-900/50 rounded-b-[3px] overflow-hidden">
|
||||
<div className="text-[8px] font-bold text-rose-500 uppercase">{t("ping.error")}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return (
|
||||
<div className="w-auto px-1.5 py-0.5 text-center bg-theme-500/10 dark:bg-theme-900/50 rounded-b-[3px] overflow-hidden">
|
||||
<div className="text-[8px] font-bold text-black/20 dark:text-white/40 uppercase">{t("ping.ping")}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const statusText = `${service.ping}: HTTP status ${data.status}`;
|
||||
|
||||
if (data && data.status !== 200) {
|
||||
return (
|
||||
<div className="w-auto px-1.5 py-0.5 text-center bg-theme-500/10 dark:bg-theme-900/50 rounded-b-[3px] overflow-hidden" title={statusText}>
|
||||
<div className="text-[8px] font-bold text-rose-500/80">{data.status}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (data && data.status === 200) {
|
||||
return (
|
||||
<div className="w-auto px-1.5 py-0.5 text-center bg-theme-500/10 dark:bg-theme-900/50 rounded-b-[3px] overflow-hidden" title={statusText}>
|
||||
<div className="text-[8px] font-bold text-emerald-500/80">{t("common.ms", { value: data.latency, style: "unit", unit: "millisecond", unitDisplay: "narrow", maximumFractionDigits: 0 })}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,19 +1,52 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import useSWR from "swr";
|
||||
|
||||
export default function Status({ service }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { data, error } = useSWR(`/api/docker/status/${service.container}/${service.server || ""}`);
|
||||
|
||||
if (error) {
|
||||
return <div className="w-3 h-3 bg-rose-300 dark:bg-rose-500 rounded-full" />;
|
||||
<div className="w-auto px-1.5 py-0.5 text-center bg-theme-500/10 dark:bg-theme-900/50 rounded-b-[3px] overflow-hidden" title={data.status}>
|
||||
<div className="text-[8px] font-bold text-rose-500/80 uppercase">{t("docker.error")}</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
if (data && data.status === "running") {
|
||||
return <div className="w-3 h-3 bg-emerald-300 dark:bg-emerald-500 rounded-full" />;
|
||||
if (data.health === "starting") {
|
||||
return (
|
||||
<div className="w-auto px-1.5 py-0.5 text-center bg-theme-500/10 dark:bg-theme-900/50 rounded-b-[3px] overflow-hidden" title={data.health}>
|
||||
<div className="text-[8px] font-bold text-blue-500/80 uppercase">{data.health}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (data.health === "unhealthy") {
|
||||
return (
|
||||
<div className="w-auto px-1.5 py-0.5 text-center bg-theme-500/10 dark:bg-theme-900/50 rounded-b-[3px] overflow-hidden" title={data.health}>
|
||||
<div className="text-[8px] font-bold text-orange-400/50 dark:text-orange-400/80 uppercase">{data.health}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-auto px-1.5 py-0.5 text-center bg-theme-500/10 dark:bg-theme-900/50 rounded-b-[3px] overflow-hidden" title={data.health ?? data.status}>
|
||||
<div className="text-[8px] font-bold text-emerald-500/80 uppercase">{data.health ?? data.status}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (data && data.status === "not found") {
|
||||
return <div className="h-2.5 w-2.5 bg-orange-400/50 dark:bg-yellow-200/40 -rotate-45" />;
|
||||
if (data && (data.status === "not found" || data.status === "exited")) {
|
||||
return (
|
||||
<div className="w-auto px-1.5 py-0.5 text-center bg-theme-500/10 dark:bg-theme-900/50 rounded-b-[3px] overflow-hidden" title={data.status}>
|
||||
<div className="text-[8px] font-bold text-orange-400/50 dark:text-orange-400/80 uppercase">{data.status}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <div className="w-3 h-3 bg-black/20 dark:bg-white/40 rounded-full" />;
|
||||
return (
|
||||
<div className="w-auto px-1.5 py-0.5 text-center bg-theme-500/10 dark:bg-theme-900/50 rounded-b-[3px] overflow-hidden">
|
||||
<div className="text-[8px] font-bold text-black/20 dark:text-white/40 uppercase">{t("docker.unknown")}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,9 +8,9 @@ import cachedFetch from "utils/proxy/cached-fetch";
|
|||
export default function Version() {
|
||||
const { t, i18n } = useTranslation();
|
||||
|
||||
const buildTime = process.env.NEXT_PUBLIC_BUILDTIME ?? new Date().toISOString();
|
||||
const revision = process.env.NEXT_PUBLIC_REVISION ?? "dev";
|
||||
const version = process.env.NEXT_PUBLIC_VERSION ?? "dev";
|
||||
const buildTime = process.env.NEXT_PUBLIC_BUILDTIME?.length ? process.env.NEXT_PUBLIC_BUILDTIME : new Date().toISOString();
|
||||
const revision = process.env.NEXT_PUBLIC_REVISION?.length ? process.env.NEXT_PUBLIC_REVISION : "dev";
|
||||
const version = process.env.NEXT_PUBLIC_VERSION?.length ? process.env.NEXT_PUBLIC_VERSION : "dev";
|
||||
|
||||
const cachedFetcher = (resource) => cachedFetch(resource, 5).then((res) => res.json());
|
||||
|
||||
|
@ -36,17 +36,14 @@ export default function Version() {
|
|||
{version} ({revision.substring(0, 7)}, {formatDate(buildTime)})
|
||||
</>
|
||||
) : (
|
||||
releaseData &&
|
||||
compareVersions(latestRelease.tag_name, version) > 0 && (
|
||||
<a
|
||||
href={`https://github.com/benphelps/homepage/releases/tag/${version}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="ml-2 text-xs text-theme-500 dark:text-theme-400 flex flex-row items-center"
|
||||
>
|
||||
{version} ({revision.substring(0, 7)}, {formatDate(buildTime)})
|
||||
</a>
|
||||
)
|
||||
<a
|
||||
href={`https://github.com/benphelps/homepage/releases/tag/${version}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="ml-2 text-xs text-theme-500 dark:text-theme-400 flex flex-row items-center"
|
||||
>
|
||||
{version} ({revision.substring(0, 7)}, {formatDate(buildTime)})
|
||||
</a>
|
||||
)}
|
||||
</span>
|
||||
{version === "main" || version === "dev" || version === "nightly"
|
||||
|
|
|
@ -15,22 +15,21 @@ const textSizes = {
|
|||
export default function DateTime({ options }) {
|
||||
const { text_size: textSize, format } = options;
|
||||
const { i18n } = useTranslation();
|
||||
const [date, setDate] = useState(new Date());
|
||||
|
||||
const [date, setDate] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const dateFormat = new Intl.DateTimeFormat(i18n.language, { ...format });
|
||||
const interval = setInterval(() => {
|
||||
setDate(new Date());
|
||||
setDate(dateFormat.format(new Date()));
|
||||
}, 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, [setDate]);
|
||||
|
||||
const dateFormat = new Intl.DateTimeFormat(i18n.language, { ...format });
|
||||
}, [date, setDate, i18n.language, format]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col justify-center first:ml-0 ml-4">
|
||||
<div className="flex flex-row items-center grow justify-end">
|
||||
<span className={`text-theme-800 dark:text-theme-200 ${textSizes[textSize || "lg"]}`}>
|
||||
{dateFormat.format(date)}
|
||||
{date}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import mapIcon from "utils/weather/owm-condition-map";
|
||||
import mapIcon from "utils/weather/openmeteo-condition-map";
|
||||
|
||||
export default function Icon({ condition, timeOfDay }) {
|
||||
const IconComponent = mapIcon(condition, timeOfDay);
|
||||
|
|
|
@ -52,7 +52,7 @@ export default function Memory({ expanded }) {
|
|||
<div className="flex flex-col ml-3 text-left min-w-[85px]">
|
||||
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
||||
<div className="pl-0.5">
|
||||
{t("common.bytes", { value: data.memory.freeMemMb * 1024 * 1024, maximumFractionDigits: 0, binary: true })}
|
||||
{t("common.bytes", { value: data.memory.freeMemMb * 1024 * 1024, maximumFractionDigits: 1, binary: true })}
|
||||
</div>
|
||||
<div className="pr-1">{t("resources.free")}</div>
|
||||
</span>
|
||||
|
@ -61,7 +61,7 @@ export default function Memory({ expanded }) {
|
|||
<div className="pl-0.5">
|
||||
{t("common.bytes", {
|
||||
value: data.memory.totalMemMb * 1024 * 1024,
|
||||
maximumFractionDigits: 0,
|
||||
maximumFractionDigits: 1,
|
||||
binary: true,
|
||||
})}
|
||||
</div>
|
||||
|
|
|
@ -40,6 +40,7 @@ export default async function handler(req, res) {
|
|||
|
||||
return res.status(200).json({
|
||||
status: info.State.Status,
|
||||
health: info.State.Health?.Status
|
||||
});
|
||||
} catch {
|
||||
return res.status(500).send({
|
||||
|
|
35
src/pages/api/ping.js
Normal file
35
src/pages/api/ping.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { performance } from "perf_hooks";
|
||||
|
||||
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;
|
||||
|
||||
if (!pingURL) {
|
||||
logger.debug("No ping URL specified");
|
||||
return res.status(400).send({
|
||||
error: "No ping URL given",
|
||||
});
|
||||
}
|
||||
|
||||
let startTime = performance.now();
|
||||
let [status] = await httpProxy(pingURL, {
|
||||
method: "HEAD"
|
||||
});
|
||||
let endTime = performance.now();
|
||||
|
||||
if (status >= 400) {
|
||||
// try one more time as a GET in case HEAD is rejected for whatever reason
|
||||
startTime = performance.now();
|
||||
[status] = await httpProxy(pingURL);
|
||||
endTime = performance.now();
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
status,
|
||||
latency: endTime - startTime
|
||||
});
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
import cachedFetch from "utils/proxy/cached-fetch";
|
||||
|
||||
export default async function handler(req, res) {
|
||||
const { latitude, longitude, units, cache } = req.query;
|
||||
const { latitude, longitude, units, cache, timezone } = req.query;
|
||||
const degrees = units === "imperial" ? "fahrenheit" : "celsius";
|
||||
const apiUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&daily=sunrise,sunset¤t_weather=true&temperature_unit=${degrees}&timezone=auto`;
|
||||
const timezeone = timezone ?? 'auto'
|
||||
const apiUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&daily=sunrise,sunset¤t_weather=true&temperature_unit=${degrees}&timezone=${timezeone}`;
|
||||
return res.send(await cachedFetch(apiUrl, cache));
|
||||
}
|
|
@ -94,7 +94,8 @@ export async function servicesResponse() {
|
|||
].flat()),
|
||||
];
|
||||
|
||||
const mergedGroups = [];
|
||||
const sortedGroups = [];
|
||||
const unsortedGroups = [];
|
||||
const definedLayouts = initialSettings.layout ? Object.keys(initialSettings.layout) : null;
|
||||
|
||||
mergedGroupsNames.forEach((groupName) => {
|
||||
|
@ -113,12 +114,12 @@ export async function servicesResponse() {
|
|||
|
||||
if (definedLayouts) {
|
||||
const layoutIndex = definedLayouts.findIndex(layout => layout === mergedGroup.name);
|
||||
if (layoutIndex > -1) mergedGroups.splice(layoutIndex, 0, mergedGroup);
|
||||
else mergedGroups.push(mergedGroup);
|
||||
if (layoutIndex > -1) sortedGroups[layoutIndex] = mergedGroup;
|
||||
else unsortedGroups.push(mergedGroup);
|
||||
} else {
|
||||
mergedGroups.push(mergedGroup);
|
||||
unsortedGroups.push(mergedGroup);
|
||||
}
|
||||
});
|
||||
|
||||
return mergedGroups;
|
||||
return [...sortedGroups.filter(g => g), ...unsortedGroups];
|
||||
}
|
||||
|
|
|
@ -32,5 +32,5 @@ export function getSettings() {
|
|||
|
||||
const settingsYaml = join(process.cwd(), "config", "settings.yaml");
|
||||
const fileContents = readFileSync(settingsYaml, "utf8");
|
||||
return yaml.load(fileContents);
|
||||
return yaml.load(fileContents) ?? {};
|
||||
}
|
|
@ -202,6 +202,7 @@ export function cleanServiceGroups(groups) {
|
|||
container,
|
||||
currency, // coinmarketcap widget
|
||||
symbols,
|
||||
defaultinterval
|
||||
namespace, // kubernetes widget
|
||||
app
|
||||
} = cleanedService.widget;
|
||||
|
@ -215,6 +216,7 @@ export function cleanServiceGroups(groups) {
|
|||
|
||||
if (currency) cleanedService.widget.currency = currency;
|
||||
if (symbols) cleanedService.widget.symbols = symbols;
|
||||
if (defaultinterval) cleanedService.widget.defaultinterval = defaultinterval;
|
||||
|
||||
if (type === "docker") {
|
||||
if (server) cleanedService.widget.server = server;
|
||||
|
@ -265,4 +267,4 @@ export default async function getServiceWidget(group, service) {
|
|||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -4,10 +4,15 @@ import { format as utilFormat } from "node:util";
|
|||
|
||||
import winston from "winston";
|
||||
|
||||
import checkAndCopyConfig, { getSettings } from "utils/config/config";
|
||||
|
||||
let winstonLogger;
|
||||
|
||||
function init() {
|
||||
const configPath = join(process.cwd(), "config");
|
||||
checkAndCopyConfig("settings.yaml");
|
||||
const settings = getSettings();
|
||||
const logpath = settings.logpath || configPath;
|
||||
|
||||
function combineMessageAndSplat() {
|
||||
return {
|
||||
|
@ -57,7 +62,7 @@ function init() {
|
|||
winston.format.timestamp(),
|
||||
winston.format.printf(messageFormatter)
|
||||
),
|
||||
filename: `${configPath}/logs/homepage.log`,
|
||||
filename: `${logpath}/logs/homepage.log`,
|
||||
handleExceptions: true,
|
||||
handleRejections: true,
|
||||
}),
|
||||
|
|
82
src/utils/proxy/handlers/jsonrpc.js
Normal file
82
src/utils/proxy/handlers/jsonrpc.js
Normal file
|
@ -0,0 +1,82 @@
|
|||
import { JSONRPCClient, JSONRPCErrorException } from "json-rpc-2.0";
|
||||
|
||||
import { formatApiCall } from "utils/proxy/api-helpers";
|
||||
import { httpProxy } from "utils/proxy/http";
|
||||
import getServiceWidget from "utils/config/service-helpers";
|
||||
import createLogger from "utils/logger";
|
||||
import widgets from "widgets/widgets";
|
||||
|
||||
const logger = createLogger("jsonrpcProxyHandler");
|
||||
|
||||
export async function sendJsonRpcRequest(url, method, params, username, password) {
|
||||
const headers = {
|
||||
"content-type": "application/json",
|
||||
"accept": "application/json"
|
||||
}
|
||||
|
||||
if (username && password) {
|
||||
headers.authorization = `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`;
|
||||
}
|
||||
|
||||
const client = new JSONRPCClient(async (rpcRequest) => {
|
||||
const body = JSON.stringify(rpcRequest);
|
||||
const httpRequestParams = {
|
||||
method: "POST",
|
||||
headers,
|
||||
body
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [status, contentType, data] = await httpProxy(url, httpRequestParams);
|
||||
if (status === 200) {
|
||||
const json = JSON.parse(data.toString());
|
||||
|
||||
// in order to get access to the underlying error object in the JSON response
|
||||
// you must set `result` equal to undefined
|
||||
if (json.error && (json.result === null)) {
|
||||
json.result = undefined;
|
||||
}
|
||||
return client.receive(json);
|
||||
}
|
||||
|
||||
return Promise.reject(data?.error ? data : new Error(data.toString()));
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await client.request(method, params);
|
||||
return [200, "application/json", JSON.stringify(response)];
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof JSONRPCErrorException) {
|
||||
logger.debug("Error calling JSONPRC endpoint: %s. %s", url, e.message);
|
||||
return [200, "application/json", JSON.stringify({result: null, error: {code: e.code, message: e.message}})];
|
||||
}
|
||||
|
||||
logger.warn("Error calling JSONPRC endpoint: %s. %s", url, e);
|
||||
return [500, "application/json", JSON.stringify({result: null, error: {code: 2, message: e.toString()}})];
|
||||
}
|
||||
}
|
||||
|
||||
export default async function jsonrpcProxyHandler(req, res) {
|
||||
const { group, service, endpoint: method } = req.query;
|
||||
|
||||
if (group && service) {
|
||||
const widget = await getServiceWidget(group, service);
|
||||
const api = widgets?.[widget.type]?.api;
|
||||
|
||||
if (!api) {
|
||||
return res.status(403).json({ error: "Service does not support API calls" });
|
||||
}
|
||||
|
||||
if (widget) {
|
||||
const url = formatApiCall(api, { ...widget });
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [status, contentType, data] = await sendJsonRpcRequest(url, method, null, widget.username, widget.password);
|
||||
return res.status(status).end(data);
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("Invalid or missing proxy service type '%s' in group '%s'", service, group);
|
||||
return res.status(400).json({ error: "Invalid proxy service type" });
|
||||
}
|
|
@ -18,10 +18,15 @@ function addCookieHandler(url, params) {
|
|||
};
|
||||
}
|
||||
|
||||
export function httpsRequest(url, params) {
|
||||
function handleRequest(requestor, url, params) {
|
||||
return new Promise((resolve, reject) => {
|
||||
addCookieHandler(url, params);
|
||||
const request = https.request(url, params, (response) => {
|
||||
if (params?.body) {
|
||||
params.headers = params.headers ?? {};
|
||||
params.headers['content-length'] = Buffer.byteLength(params.body);
|
||||
}
|
||||
|
||||
const request = requestor.request(url, params, (response) => {
|
||||
const data = [];
|
||||
|
||||
response.on("data", (chunk) => {
|
||||
|
@ -38,7 +43,7 @@ export function httpsRequest(url, params) {
|
|||
reject([500, error]);
|
||||
});
|
||||
|
||||
if (params.body) {
|
||||
if (params?.body) {
|
||||
request.write(params.body);
|
||||
}
|
||||
|
||||
|
@ -46,32 +51,12 @@ export function httpsRequest(url, params) {
|
|||
});
|
||||
}
|
||||
|
||||
export function httpsRequest(url, params) {
|
||||
return handleRequest(https, url, params);
|
||||
}
|
||||
|
||||
export function httpRequest(url, params) {
|
||||
return new Promise((resolve, reject) => {
|
||||
addCookieHandler(url, params);
|
||||
const request = http.request(url, params, (response) => {
|
||||
const data = [];
|
||||
|
||||
response.on("data", (chunk) => {
|
||||
data.push(chunk);
|
||||
});
|
||||
|
||||
response.on("end", () => {
|
||||
addCookieToJar(url, response.headers);
|
||||
resolve([response.statusCode, response.headers["content-type"], Buffer.concat(data), response.headers]);
|
||||
});
|
||||
});
|
||||
|
||||
request.on("error", (error) => {
|
||||
reject([500, error]);
|
||||
});
|
||||
|
||||
if (params.body) {
|
||||
request.write(params.body);
|
||||
}
|
||||
|
||||
request.end();
|
||||
});
|
||||
return handleRequest(http, url, params);
|
||||
}
|
||||
|
||||
export async function httpProxy(url, params = {}) {
|
||||
|
@ -96,7 +81,7 @@ export async function httpProxy(url, params = {}) {
|
|||
return [status, contentType, data, responseHeaders];
|
||||
}
|
||||
catch (err) {
|
||||
logger.error("Error calling %s//%s%s...", url.protocol, url.hostname, url.pathname);
|
||||
logger.error("Error calling %s//%s%s...", constructedUrl.protocol, constructedUrl.hostname, constructedUrl.pathname);
|
||||
logger.error(err);
|
||||
return [500, "application/json", { error: {message: err?.message ?? "Unknown error", url, rawError: err} }, null];
|
||||
}
|
||||
|
|
211
src/utils/weather/openmeteo-condition-map.js
Normal file
211
src/utils/weather/openmeteo-condition-map.js
Normal file
|
@ -0,0 +1,211 @@
|
|||
import * as Icons from "react-icons/wi";
|
||||
|
||||
// see https://open-meteo.com/en/docs
|
||||
|
||||
const conditions = [
|
||||
{
|
||||
code: 1,
|
||||
icon: {
|
||||
day: Icons.WiDayCloudy,
|
||||
night: Icons.WiNightAltCloudy,
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 2,
|
||||
icon: {
|
||||
day: Icons.WiDayCloudy,
|
||||
night: Icons.WiNightAltCloudy,
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 3,
|
||||
icon: {
|
||||
day: Icons.WiDayCloudy,
|
||||
night: Icons.WiNightAltCloudy,
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 45,
|
||||
icon: {
|
||||
day: Icons.WiDayFog,
|
||||
night: Icons.WiNightFog,
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 48,
|
||||
icon: {
|
||||
day: Icons.WiDayFog,
|
||||
night: Icons.WiNightFog,
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 51,
|
||||
icon: {
|
||||
day: Icons.WiDaySprinkle,
|
||||
night: Icons.WiNightAltSprinkle,
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 53,
|
||||
icon: {
|
||||
day: Icons.WiDaySprinkle,
|
||||
night: Icons.WiNightAltSprinkle,
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 55,
|
||||
icon: {
|
||||
day: Icons.WiDaySprinkle,
|
||||
night: Icons.WiNightAltSprinkle,
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 56,
|
||||
icon: {
|
||||
day: Icons.WiDaySleet,
|
||||
night: Icons.WiNightAltSleet,
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 57,
|
||||
icon: {
|
||||
day: Icons.WiDaySleet,
|
||||
night: Icons.WiNightAltSleet,
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 61,
|
||||
icon: {
|
||||
day: Icons.WiDayShowers,
|
||||
night: Icons.WiNightAltShowers,
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 63,
|
||||
icon: {
|
||||
day: Icons.WiDayShowers,
|
||||
night: Icons.WiNightAltShowers,
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 65,
|
||||
icon: {
|
||||
day: Icons.WiDayShowers,
|
||||
night: Icons.WiNightAltShowers,
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 66,
|
||||
icon: {
|
||||
day: Icons.WiDaySleet,
|
||||
night: Icons.WiNightAltSleet,
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 67,
|
||||
icon: {
|
||||
day: Icons.WiDaySleet,
|
||||
night: Icons.WiNightAltSleet,
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 71,
|
||||
icon: {
|
||||
day: Icons.WiDaySnow,
|
||||
night: Icons.WiNightAltSnow,
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 73,
|
||||
icon: {
|
||||
day: Icons.WiDaySnow,
|
||||
night: Icons.WiNightAltSnow,
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 75,
|
||||
icon: {
|
||||
day: Icons.WiDaySnow,
|
||||
night: Icons.WiNightAltSnow,
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 77,
|
||||
icon: {
|
||||
day: Icons.WiDaySnow,
|
||||
night: Icons.WiNightAltSnow,
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 80,
|
||||
icon: {
|
||||
day: Icons.WiDaySnow,
|
||||
night: Icons.WiNightAltSnow,
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 81,
|
||||
icon: {
|
||||
day: Icons.WiDaySnow,
|
||||
night: Icons.WiNightAltSnow,
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 82,
|
||||
icon: {
|
||||
day: Icons.WiDaySnow,
|
||||
night: Icons.WiNightAltSnow,
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 85,
|
||||
icon: {
|
||||
day: Icons.WiDaySnow,
|
||||
night: Icons.WiNightAltSnow,
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 86,
|
||||
icon: {
|
||||
day: Icons.WiDaySnow,
|
||||
night: Icons.WiNightAltSnow,
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 95,
|
||||
icon: {
|
||||
day: Icons.WiDayThunderstorm,
|
||||
night: Icons.WiNightAltThunderstorm,
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 96,
|
||||
icon: {
|
||||
day: Icons.WiDayThunderstorm,
|
||||
night: Icons.WiNightAltThunderstorm,
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 99,
|
||||
icon: {
|
||||
day: Icons.WiDayThunderstorm,
|
||||
night: Icons.WiNightAltThunderstorm,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export default function mapIcon(weatherStatusCode, timeOfDay) {
|
||||
const mapping = conditions.find((condition) => condition.code === weatherStatusCode);
|
||||
|
||||
if (mapping) {
|
||||
if (timeOfDay === "day") {
|
||||
return mapping.icon.day;
|
||||
}
|
||||
|
||||
if (timeOfDay === "night") {
|
||||
return mapping.icon.night;
|
||||
}
|
||||
}
|
||||
|
||||
return Icons.WiDaySunny;
|
||||
}
|
|
@ -14,6 +14,11 @@ export default function Component({ service }) {
|
|||
if (error) {
|
||||
return <Container error={error} />;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return <Container service={service} />;
|
||||
}
|
||||
|
||||
const totalObserved = Object.keys(data).length;
|
||||
let diffsDetected = 0;
|
||||
|
||||
|
|
|
@ -17,11 +17,12 @@ export default function Component({ service }) {
|
|||
{ label: t("coinmarketcap.30days"), value: "30d" },
|
||||
];
|
||||
|
||||
const [dateRange, setDateRange] = useState(dateRangeOptions[0].value);
|
||||
|
||||
const { widget } = service;
|
||||
const { symbols } = widget;
|
||||
const currencyCode = widget.currency ?? "USD";
|
||||
const interval = widget.defaultinterval ?? dateRangeOptions[0].value;
|
||||
|
||||
const [dateRange, setDateRange] = useState(interval);
|
||||
|
||||
const { data: statsData, error: statsError } = useWidgetAPI(widget, "v1/cryptocurrency/quotes/latest", {
|
||||
symbol: `${symbols.join(",")}`,
|
||||
|
|
|
@ -7,9 +7,12 @@ const components = {
|
|||
bazarr: dynamic(() => import("./bazarr/component")),
|
||||
changedetectionio: dynamic(() => import("./changedetectionio/component")),
|
||||
coinmarketcap: dynamic(() => import("./coinmarketcap/component")),
|
||||
deluge: dynamic(() => import("./deluge/component")),
|
||||
diskstation: dynamic(() => import("./diskstation/component")),
|
||||
docker: dynamic(() => import("./docker/component")),
|
||||
kubernetes: dynamic(() => import("./kubernetes/component")),
|
||||
emby: dynamic(() => import("./emby/component")),
|
||||
flood: dynamic(() => import("./flood/component")),
|
||||
gluetun: dynamic(() => import("./gluetun/component")),
|
||||
gotify: dynamic(() => import("./gotify/component")),
|
||||
hdhomerun: dynamic(() => import("./hdhomerun/component")),
|
||||
|
@ -24,6 +27,7 @@ const components = {
|
|||
nzbget: dynamic(() => import("./nzbget/component")),
|
||||
ombi: dynamic(() => import("./ombi/component")),
|
||||
overseerr: dynamic(() => import("./overseerr/component")),
|
||||
paperlessngx: dynamic(() => import("./paperlessngx/component")),
|
||||
pihole: dynamic(() => import("./pihole/component")),
|
||||
plex: dynamic(() => import("./plex/component")),
|
||||
portainer: dynamic(() => import("./portainer/component")),
|
||||
|
@ -35,6 +39,7 @@ const components = {
|
|||
readarr: dynamic(() => import("./readarr/component")),
|
||||
rutorrent: dynamic(() => import("./rutorrent/component")),
|
||||
sabnzbd: dynamic(() => import("./sabnzbd/component")),
|
||||
scrutiny: dynamic(() => import("./scrutiny/component")),
|
||||
sonarr: dynamic(() => import("./sonarr/component")),
|
||||
speedtest: dynamic(() => import("./speedtest/component")),
|
||||
strelaysrv: dynamic(() => import("./strelaysrv/component")),
|
||||
|
|
52
src/widgets/deluge/component.jsx
Normal file
52
src/widgets/deluge/component.jsx
Normal file
|
@ -0,0 +1,52 @@
|
|||
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: torrentData, error: torrentError } = useWidgetAPI(widget);
|
||||
|
||||
if (torrentError) {
|
||||
return <Container error={torrentError} />;
|
||||
}
|
||||
|
||||
if (!torrentData) {
|
||||
return (
|
||||
<Container service={service}>
|
||||
<Block label="deluge.leech" />
|
||||
<Block label="deluge.download" />
|
||||
<Block label="deluge.seed" />
|
||||
<Block label="deluge.upload" />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
const { torrents } = torrentData;
|
||||
const keys = torrents ? Object.keys(torrents) : [];
|
||||
|
||||
let rateDl = 0;
|
||||
let rateUl = 0;
|
||||
let completed = 0;
|
||||
for (let i = 0; i < keys.length; i += 1) {
|
||||
const torrent = torrents[keys[i]];
|
||||
rateDl += torrent.download_payload_rate;
|
||||
rateUl += torrent.upload_payload_rate;
|
||||
completed += torrent.total_remaining === 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
const leech = keys.length - completed || 0;
|
||||
|
||||
return (
|
||||
<Container service={service}>
|
||||
<Block label="deluge.leech" value={t("common.number", { value: leech })} />
|
||||
<Block label="deluge.download" value={t("common.bitrate", { value: rateDl })} />
|
||||
<Block label="deluge.seed" value={t("common.number", { value: completed })} />
|
||||
<Block label="deluge.upload" value={t("common.bitrate", { value: rateUl })} />
|
||||
</Container>
|
||||
);
|
||||
}
|
63
src/widgets/deluge/proxy.js
Normal file
63
src/widgets/deluge/proxy.js
Normal file
|
@ -0,0 +1,63 @@
|
|||
import { formatApiCall } from "utils/proxy/api-helpers";
|
||||
import { sendJsonRpcRequest } from "utils/proxy/handlers/jsonrpc";
|
||||
import getServiceWidget from "utils/config/service-helpers";
|
||||
import createLogger from "utils/logger";
|
||||
import widgets from "widgets/widgets";
|
||||
|
||||
const logger = createLogger("delugeProxyHandler");
|
||||
|
||||
const dataMethod = "web.update_ui";
|
||||
const dataParams = [
|
||||
["queue", "name", "total_wanted", "state", "progress", "download_payload_rate", "upload_payload_rate", "total_remaining"],
|
||||
{}
|
||||
];
|
||||
const loginMethod = "auth.login";
|
||||
|
||||
async function sendRpc(url, method, params) {
|
||||
const [status, contentType, data] = await sendJsonRpcRequest(url, method, params);
|
||||
const json = JSON.parse(data.toString());
|
||||
if (json?.error) {
|
||||
if (json.error.code === 1) {
|
||||
return [403, contentType, data];
|
||||
}
|
||||
return [500, contentType, data];
|
||||
}
|
||||
|
||||
return [status, contentType, data];
|
||||
}
|
||||
|
||||
function login(url, password) {
|
||||
return sendRpc(url, loginMethod, [password]);
|
||||
}
|
||||
|
||||
export default async function delugeProxyHandler(req, res) {
|
||||
const { group, service } = req.query;
|
||||
|
||||
if (!group || !service) {
|
||||
logger.debug("Invalid or missing service '%s' or group '%s'", service, group);
|
||||
return res.status(400).json({ error: "Invalid proxy service type" });
|
||||
}
|
||||
|
||||
const widget = await getServiceWidget(group, service);
|
||||
|
||||
if (!widget) {
|
||||
logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group);
|
||||
return res.status(400).json({ error: "Invalid proxy service type" });
|
||||
}
|
||||
|
||||
const api = widgets?.[widget.type]?.api
|
||||
const url = new URL(formatApiCall(api, { ...widget }));
|
||||
|
||||
let [status, contentType, data] = await sendRpc(url, dataMethod, dataParams);
|
||||
if (status === 403) {
|
||||
[status, contentType, data] = await login(url, widget.password);
|
||||
if (status !== 200) {
|
||||
return res.status(status).end(data);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
[status, contentType, data] = await sendRpc(url, dataMethod, dataParams);
|
||||
}
|
||||
|
||||
return res.status(status).end(data);
|
||||
}
|
8
src/widgets/deluge/widget.js
Normal file
8
src/widgets/deluge/widget.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
import delugeProxyHandler from "./proxy";
|
||||
|
||||
const widget = {
|
||||
api: "{url}/json",
|
||||
proxyHandler: delugeProxyHandler,
|
||||
};
|
||||
|
||||
export default widget;
|
41
src/widgets/diskstation/component.jsx
Normal file
41
src/widgets/diskstation/component.jsx
Normal file
|
@ -0,0 +1,41 @@
|
|||
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: listData, error: listError } = useWidgetAPI(widget, "list");
|
||||
|
||||
if (listError) {
|
||||
return <Container error={listError} />;
|
||||
}
|
||||
|
||||
const tasks = listData?.data?.tasks;
|
||||
if (!tasks) {
|
||||
return (
|
||||
<Container service={service}>
|
||||
<Block label="diskstation.leech" />
|
||||
<Block label="diskstation.download" />
|
||||
<Block label="diskstation.seed" />
|
||||
<Block label="diskstation.upload" />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
const rateDl = tasks.reduce((acc, task) => acc + (task?.additional?.transfer?.speed_download ?? 0), 0);
|
||||
const rateUl = tasks.reduce((acc, task) => acc + (task?.additional?.transfer?.speed_upload ?? 0), 0);
|
||||
const completed = tasks.filter((task) => task?.additional?.transfer?.size_downloaded === task?.size)?.length || 0;
|
||||
const leech = tasks.length - completed || 0;
|
||||
|
||||
return (
|
||||
<Container service={service}>
|
||||
<Block label="diskstation.leech" value={t("common.number", { value: leech })} />
|
||||
<Block label="diskstation.download" value={t("common.bitrate", { value: rateDl })} />
|
||||
<Block label="diskstation.seed" value={t("common.number", { value: completed })} />
|
||||
<Block label="diskstation.upload" value={t("common.bitrate", { value: rateUl })} />
|
||||
</Container>
|
||||
);
|
||||
}
|
70
src/widgets/diskstation/proxy.js
Normal file
70
src/widgets/diskstation/proxy.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
import { formatApiCall } from "utils/proxy/api-helpers";
|
||||
import { httpProxy } from "utils/proxy/http";
|
||||
import createLogger from "utils/logger";
|
||||
import widgets from "widgets/widgets";
|
||||
import getServiceWidget from "utils/config/service-helpers";
|
||||
|
||||
const logger = createLogger("diskstationProxyHandler");
|
||||
const authApi = "{url}/webapi/auth.cgi?api=SYNO.API.Auth&version=2&method=login&account={username}&passwd={password}&session=DownloadStation&format=cookie"
|
||||
|
||||
async function login(widget) {
|
||||
const loginUrl = formatApiCall(authApi, widget);
|
||||
const [status, contentType, data] = await httpProxy(loginUrl);
|
||||
if (status !== 200) {
|
||||
return [status, contentType, data];
|
||||
}
|
||||
|
||||
const json = JSON.parse(data.toString());
|
||||
if (json?.success !== true) {
|
||||
// from https://global.download.synology.com/download/Document/Software/DeveloperGuide/Package/DownloadStation/All/enu/Synology_Download_Station_Web_API.pdf
|
||||
/*
|
||||
Code Description
|
||||
400 No such account or incorrect password
|
||||
401 Account disabled
|
||||
402 Permission denied
|
||||
403 2-step verification code required
|
||||
404 Failed to authenticate 2-step verification code
|
||||
*/
|
||||
let message = "Authentication failed.";
|
||||
if (json?.error?.code >= 403) message += " 2FA enabled.";
|
||||
logger.warn("Unable to login. Code: %d", json?.error?.code);
|
||||
return [401, "application/json", JSON.stringify({ code: json?.error?.code, message })];
|
||||
}
|
||||
|
||||
return [status, contentType, data];
|
||||
}
|
||||
|
||||
export default async function diskstationProxyHandler(req, res) {
|
||||
const { group, service, endpoint } = req.query;
|
||||
|
||||
if (!group || !service) {
|
||||
return res.status(400).json({ error: "Invalid proxy service type" });
|
||||
}
|
||||
|
||||
const widget = await getServiceWidget(group, service);
|
||||
const api = widgets?.[widget.type]?.api;
|
||||
if (!api) {
|
||||
return res.status(403).json({ error: "Service does not support API calls" });
|
||||
}
|
||||
|
||||
const url = formatApiCall(api, { endpoint, ...widget });
|
||||
let [status, contentType, data] = await httpProxy(url);
|
||||
if (status !== 200) {
|
||||
logger.debug("Error %d calling endpoint %s", status, url);
|
||||
return res.status(status, data);
|
||||
}
|
||||
|
||||
const json = JSON.parse(data.toString());
|
||||
if (json?.success !== true) {
|
||||
logger.debug("Logging in to DiskStation");
|
||||
[status, contentType, data] = await login(widget);
|
||||
if (status !== 200) {
|
||||
return res.status(status).end(data)
|
||||
}
|
||||
|
||||
[status, contentType, data] = await httpProxy(url);
|
||||
}
|
||||
|
||||
if (contentType) res.setHeader("Content-Type", contentType);
|
||||
return res.status(status).send(data);
|
||||
}
|
14
src/widgets/diskstation/widget.js
Normal file
14
src/widgets/diskstation/widget.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
import diskstationProxyHandler from "./proxy";
|
||||
|
||||
const widget = {
|
||||
api: "{url}/webapi/DownloadStation/task.cgi?api=SYNO.DownloadStation.Task&version=1&method={endpoint}",
|
||||
proxyHandler: diskstationProxyHandler,
|
||||
|
||||
mappings: {
|
||||
"list": {
|
||||
endpoint: "list&additional=transfer",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default widget;
|
|
@ -46,7 +46,9 @@ export default function Component({ service }) {
|
|||
return (
|
||||
<Container service={service}>
|
||||
<Block label="docker.cpu" value={t("common.percent", { value: calculateCPUPercent(statsData.stats) })} />
|
||||
<Block label="docker.mem" value={t("common.bytes", { value: statsData.stats.memory_stats.usage })} />
|
||||
{statsData.stats.memory_stats.usage &&
|
||||
<Block label="docker.mem" value={t("common.bytes", { value: statsData.stats.memory_stats.usage })} />
|
||||
}
|
||||
{network && (
|
||||
<>
|
||||
<Block label="docker.rx" value={t("common.bytes", { value: network.rx_bytes })} />
|
||||
|
|
53
src/widgets/flood/component.jsx
Normal file
53
src/widgets/flood/component.jsx
Normal file
|
@ -0,0 +1,53 @@
|
|||
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: torrentData, error: torrentError } = useWidgetAPI(widget, "torrents");
|
||||
|
||||
if (torrentError || !torrentData?.torrents) {
|
||||
return <Container error={torrentError ?? {message: "No torrent data returned"}} />;
|
||||
}
|
||||
|
||||
if (!torrentData || !torrentData.torrents) {
|
||||
return (
|
||||
<Container service={service}>
|
||||
<Block label="flood.leech" />
|
||||
<Block label="flood.download" />
|
||||
<Block label="flood.seed" />
|
||||
<Block label="flood.upload" />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
let rateDl = 0;
|
||||
let rateUl = 0;
|
||||
let completed = 0;
|
||||
let leech = 0;
|
||||
|
||||
Object.values(torrentData.torrents).forEach(torrent => {
|
||||
rateDl += torrent.downRate;
|
||||
rateUl += torrent.upRate;
|
||||
if(torrent.status.includes('complete')){
|
||||
completed += 1;
|
||||
}
|
||||
if(torrent.status.includes('downloading')){
|
||||
leech += 1;
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<Container service={service}>
|
||||
<Block label="flood.leech" value={t("common.number", { value: leech })} />
|
||||
<Block label="flood.download" value={t("common.bitrate", { value: rateDl })} />
|
||||
<Block label="flood.seed" value={t("common.number", { value: completed })} />
|
||||
<Block label="flood.upload" value={t("common.bitrate", { value: rateUl })} />
|
||||
</Container>
|
||||
);
|
||||
}
|
66
src/widgets/flood/proxy.js
Normal file
66
src/widgets/flood/proxy.js
Normal file
|
@ -0,0 +1,66 @@
|
|||
import { formatApiCall } from "utils/proxy/api-helpers";
|
||||
import { httpProxy } from "utils/proxy/http";
|
||||
import getServiceWidget from "utils/config/service-helpers";
|
||||
import createLogger from "utils/logger";
|
||||
|
||||
const logger = createLogger("floodProxyHandler");
|
||||
|
||||
async function login(widget) {
|
||||
logger.debug("flood is rejecting the request, logging in.");
|
||||
const loginUrl = new URL(`${widget.url}/api/auth/authenticate`).toString();
|
||||
|
||||
const loginParams = {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: null
|
||||
};
|
||||
|
||||
if (widget.username && widget.password) {
|
||||
loginParams.body = JSON.stringify({
|
||||
"username": widget.username,
|
||||
"password": widget.password
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [status, contentType, data] = await httpProxy(loginUrl, loginParams);
|
||||
return [status, data];
|
||||
}
|
||||
|
||||
export default async function floodProxyHandler(req, res) {
|
||||
const { group, service, endpoint } = req.query;
|
||||
|
||||
if (!group || !service) {
|
||||
logger.debug("Invalid or missing service '%s' or group '%s'", service, group);
|
||||
return res.status(400).json({ error: "Invalid proxy service type" });
|
||||
}
|
||||
|
||||
const widget = await getServiceWidget(group, service);
|
||||
|
||||
if (!widget) {
|
||||
logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group);
|
||||
return res.status(400).json({ error: "Invalid proxy service type" });
|
||||
}
|
||||
|
||||
const url = new URL(formatApiCall("{url}/api/{endpoint}", { endpoint, ...widget }));
|
||||
const params = { method: "GET", headers: {} };
|
||||
|
||||
let [status, contentType, data] = await httpProxy(url, params);
|
||||
if (status === 401) {
|
||||
[status, data] = await login(widget);
|
||||
|
||||
if (status !== 200) {
|
||||
logger.error("HTTP %d logging in to flood. Data: %s", status, data);
|
||||
return res.status(status).end(data);
|
||||
}
|
||||
|
||||
[status, contentType, data] = await httpProxy(url, params);
|
||||
}
|
||||
|
||||
if (status !== 200) {
|
||||
logger.error("HTTP %d getting data from flood. Data: %s", status, data);
|
||||
}
|
||||
|
||||
if (contentType) res.setHeader("Content-Type", contentType);
|
||||
return res.status(status).send(data);
|
||||
}
|
7
src/widgets/flood/widget.js
Normal file
7
src/widgets/flood/widget.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import floodProxyHandler from "./proxy";
|
||||
|
||||
const widget = {
|
||||
proxyHandler: floodProxyHandler,
|
||||
};
|
||||
|
||||
export default widget;
|
|
@ -20,12 +20,18 @@ async function login(loginUrl, username, password) {
|
|||
});
|
||||
|
||||
const status = authResponse[0];
|
||||
const data = JSON.parse(Buffer.from(authResponse[2]).toString());
|
||||
let data = authResponse[2];
|
||||
|
||||
if (status === 200) {
|
||||
cache.put(tokenCacheKey, data.token);
|
||||
try {
|
||||
data = JSON.parse(Buffer.from(authResponse[2]).toString());
|
||||
|
||||
if (status === 200) {
|
||||
const expiration = new Date(data.expires) - Date.now();
|
||||
cache.put(tokenCacheKey, data.token, expiration - (5 * 60 * 1000)); // expiration -5 minutes
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error(`Error ${status} logging into npm`, authResponse[2]);
|
||||
}
|
||||
|
||||
return [status, data.token ?? data];
|
||||
}
|
||||
|
||||
|
@ -51,8 +57,8 @@ export default async function npmProxyHandler(req, res) {
|
|||
if (!token) {
|
||||
[status, token] = await login(loginUrl, widget.username, widget.password);
|
||||
if (status !== 200) {
|
||||
logger.debug(`HTTTP ${status} logging into npm api: ${data}`);
|
||||
return res.status(status).send(data);
|
||||
logger.debug(`HTTTP ${status} logging into npm api: ${token}`);
|
||||
return res.status(status).send(token);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
import { JSONRPCClient } from "json-rpc-2.0";
|
||||
|
||||
import getServiceWidget from "utils/config/service-helpers";
|
||||
|
||||
export default async function nzbgetProxyHandler(req, res) {
|
||||
const { group, service, endpoint } = req.query;
|
||||
|
||||
if (group && service) {
|
||||
const widget = await getServiceWidget(group, service);
|
||||
|
||||
if (widget) {
|
||||
const constructedUrl = new URL(widget.url);
|
||||
constructedUrl.pathname = "jsonrpc";
|
||||
|
||||
const authorization = Buffer.from(`${widget.username}:${widget.password}`).toString("base64");
|
||||
|
||||
const client = new JSONRPCClient((jsonRPCRequest) =>
|
||||
fetch(constructedUrl.toString(), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
authorization: `Basic ${authorization}`,
|
||||
},
|
||||
body: JSON.stringify(jsonRPCRequest),
|
||||
}).then(async (response) => {
|
||||
if (response.status === 200) {
|
||||
const jsonRPCResponse = await response.json();
|
||||
return client.receive(jsonRPCResponse);
|
||||
}
|
||||
|
||||
return Promise.reject(new Error(response.statusText));
|
||||
})
|
||||
);
|
||||
|
||||
return res.send(await client.request(endpoint));
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(400).json({ error: "Invalid proxy service type" });
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
import nzbgetProxyHandler from "./proxy";
|
||||
import jsonrpcProxyHandler from "utils/proxy/handlers/jsonrpc";
|
||||
|
||||
const widget = {
|
||||
proxyHandler: nzbgetProxyHandler,
|
||||
api: "{url}/jsonrpc",
|
||||
proxyHandler: jsonrpcProxyHandler,
|
||||
};
|
||||
|
||||
export default widget;
|
||||
|
|
|
@ -15,6 +15,7 @@ export default function Component({ service }) {
|
|||
return (
|
||||
<Container service={service}>
|
||||
<Block label="overseerr.pending" />
|
||||
<Block label="overseerr.processing" />
|
||||
<Block label="overseerr.approved" />
|
||||
<Block label="overseerr.available" />
|
||||
</Container>
|
||||
|
@ -24,6 +25,7 @@ export default function Component({ service }) {
|
|||
return (
|
||||
<Container service={service}>
|
||||
<Block label="overseerr.pending" value={statsData.pending} />
|
||||
<Block label="overseerr.processing" value={statsData.processing} />
|
||||
<Block label="overseerr.approved" value={statsData.approved} />
|
||||
<Block label="overseerr.available" value={statsData.available} />
|
||||
</Container>
|
||||
|
|
|
@ -9,6 +9,7 @@ const widget = {
|
|||
endpoint: "request/count",
|
||||
validate: [
|
||||
"pending",
|
||||
"processing",
|
||||
"approved",
|
||||
"available",
|
||||
],
|
||||
|
|
29
src/widgets/paperlessngx/component.jsx
Normal file
29
src/widgets/paperlessngx/component.jsx
Normal file
|
@ -0,0 +1,29 @@
|
|||
import Container from "components/services/widget/container";
|
||||
import Block from "components/services/widget/block";
|
||||
import useWidgetAPI from "utils/proxy/use-widget-api";
|
||||
|
||||
export default function Component({ service }) {
|
||||
const { widget } = service;
|
||||
|
||||
const { data: statisticsData, error: statisticsError } = useWidgetAPI(widget, "statistics");
|
||||
|
||||
if (statisticsError) {
|
||||
return <Container error={statisticsError} />;
|
||||
}
|
||||
|
||||
if (!statisticsData) {
|
||||
return (
|
||||
<Container service={service}>
|
||||
<Block label="paperlessngx.inbox" />
|
||||
<Block label="paperlessngx.total" />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Container service={service}>
|
||||
{statisticsData.documents_inbox !== undefined && <Block label="paperlessngx.inbox" value={statisticsData.documents_inbox} />}
|
||||
<Block label="paperlessngx.total" value={statisticsData.documents_total} />
|
||||
</Container>
|
||||
);
|
||||
}
|
17
src/widgets/paperlessngx/widget.js
Normal file
17
src/widgets/paperlessngx/widget.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import genericProxyHandler from "utils/proxy/handlers/generic";
|
||||
|
||||
const widget = {
|
||||
api: "{url}/api/{endpoint}",
|
||||
proxyHandler: genericProxyHandler,
|
||||
|
||||
mappings: {
|
||||
"statistics": {
|
||||
endpoint: "statistics/?format=json",
|
||||
validate: [
|
||||
"documents_total"
|
||||
]
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default widget;
|
|
@ -1,30 +1,23 @@
|
|||
import { formatApiCall } from "utils/proxy/api-helpers";
|
||||
import { addCookieToJar, setCookieHeader } from "utils/proxy/cookie-jar";
|
||||
import { httpProxy } from "utils/proxy/http";
|
||||
import getServiceWidget from "utils/config/service-helpers";
|
||||
import createLogger from "utils/logger";
|
||||
|
||||
const logger = createLogger("qbittorrentProxyHandler");
|
||||
|
||||
async function login(widget, params) {
|
||||
async function login(widget) {
|
||||
logger.debug("qBittorrent is rejecting the request, logging in.");
|
||||
const loginUrl = new URL(`${widget.url}/api/v2/auth/login`).toString();
|
||||
const loginBody = `username=${encodeURI(widget.username)}&password=${encodeURI(widget.password)}`;
|
||||
|
||||
// using fetch intentionally, for login only, as the httpProxy method causes qBittorrent to
|
||||
// complain about header encoding
|
||||
return fetch(loginUrl, {
|
||||
const loginParams = {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: loginBody,
|
||||
})
|
||||
.then(async (response) => {
|
||||
addCookieToJar(loginUrl, response.headers);
|
||||
setCookieHeader(loginUrl, params);
|
||||
const data = await response.text();
|
||||
return [response.status, data];
|
||||
})
|
||||
.catch((err) => [500, err]);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [status, contentType, data] = await httpProxy(loginUrl, loginParams);
|
||||
return [status, data];
|
||||
}
|
||||
|
||||
export default async function qbittorrentProxyHandler(req, res) {
|
||||
|
@ -44,11 +37,10 @@ export default async function qbittorrentProxyHandler(req, res) {
|
|||
|
||||
const url = new URL(formatApiCall("{url}/api/v2/{endpoint}", { endpoint, ...widget }));
|
||||
const params = { method: "GET", headers: {} };
|
||||
setCookieHeader(url, params);
|
||||
|
||||
let [status, contentType, data] = await httpProxy(url, params);
|
||||
if (status === 403) {
|
||||
[status, data] = await login(widget, params);
|
||||
[status, data] = await login(widget);
|
||||
|
||||
if (status !== 200) {
|
||||
logger.error("HTTP %d logging in to qBittorrent. Data: %s", status, data);
|
||||
|
@ -59,9 +51,9 @@ export default async function qbittorrentProxyHandler(req, res) {
|
|||
logger.error("Error logging in to qBittorrent: Data: %s", data);
|
||||
return res.status(401).end(data);
|
||||
}
|
||||
}
|
||||
|
||||
[status, contentType, data] = await httpProxy(url, params);
|
||||
[status, contentType, data] = await httpProxy(url, params);
|
||||
}
|
||||
|
||||
if (status !== 200) {
|
||||
logger.error("HTTP %d getting data from qBittorrent. Data: %s", status, data);
|
||||
|
|
|
@ -11,9 +11,14 @@ export default async function rutorrentProxyHandler(req, res) {
|
|||
if (widget) {
|
||||
const constructedUrl = new URL(widget.url);
|
||||
|
||||
let rtPort = constructedUrl.port;
|
||||
if (rtPort === '') {
|
||||
rtPort = constructedUrl.protocol === "https:" ? 443 : 80;
|
||||
}
|
||||
|
||||
const rutorrent = new RuTorrent({
|
||||
host: constructedUrl.hostname,
|
||||
port: constructedUrl.port,
|
||||
port: rtPort,
|
||||
path: constructedUrl.pathname,
|
||||
ssl: constructedUrl.protocol === "https:",
|
||||
username: widget.username,
|
||||
|
|
62
src/widgets/scrutiny/component.jsx
Normal file
62
src/widgets/scrutiny/component.jsx
Normal file
|
@ -0,0 +1,62 @@
|
|||
import Container from "components/services/widget/container";
|
||||
import Block from "components/services/widget/block";
|
||||
import useWidgetAPI from "utils/proxy/use-widget-api";
|
||||
|
||||
|
||||
// @see https://github.com/AnalogJ/scrutiny/blob/d8d56f77f9e868127c4849dac74d65512db658e8/webapp/frontend/src/app/shared/device-status.pipe.ts
|
||||
const DeviceStatus = {
|
||||
passed: 0,
|
||||
failed_smart: 1,
|
||||
failed_scrutiny: 2,
|
||||
failed_both: 3,
|
||||
|
||||
isFailed(s){ return s > this.passed && s <= this.failed_both},
|
||||
isUnknown(s){ return s < this.passed || s > this.failed_both}
|
||||
}
|
||||
|
||||
// @see https://github.com/AnalogJ/scrutiny/blob/d8d56f77f9e868127c4849dac74d65512db658e8/webapp/frontend/src/app/core/config/app.config.ts
|
||||
const DeviceStatusThreshold = {
|
||||
smart: 1,
|
||||
scrutiny: 2,
|
||||
both: 3
|
||||
}
|
||||
|
||||
export default function Component({ service }) {
|
||||
const { widget } = service;
|
||||
|
||||
const { data: scrutinySettings, error: scrutinySettingsError } = useWidgetAPI(widget, "settings");
|
||||
const { data: scrutinyData, error: scrutinyError } = useWidgetAPI(widget, "summary");
|
||||
|
||||
if (scrutinyError || scrutinySettingsError) {
|
||||
const finalError = scrutinyError ?? scrutinySettingsError;
|
||||
return <Container error={finalError} />;
|
||||
}
|
||||
|
||||
if (!scrutinyData || !scrutinySettings) {
|
||||
return (
|
||||
<Container service={service}>
|
||||
<Block label="scrutiny.passed" />
|
||||
<Block label="scrutiny.failed" />
|
||||
<Block label="scrutiny.unknown" />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
const deviceIds = Object.values(scrutinyData.data.summary);
|
||||
const statusThreshold = scrutinySettings.settings.metrics.status_threshold;
|
||||
|
||||
const failed = deviceIds.filter(deviceId => (DeviceStatus.isFailed(deviceId.device.device_status) && statusThreshold === DeviceStatusThreshold.both) || [statusThreshold, DeviceStatus.failed_both].includes(deviceId.device.device_status))?.length || 0;
|
||||
const unknown = deviceIds.filter(deviceId => DeviceStatus.isUnknown(deviceId.device.device_status))?.length || 0;
|
||||
const passed = deviceIds.length - (failed + unknown);
|
||||
|
||||
return (
|
||||
<Container service={service}>
|
||||
<Block label="scrutiny.passed" value={passed} />
|
||||
<Block label="scrutiny.failed" value={failed} />
|
||||
<Block label="scrutiny.unknown" value={unknown} />
|
||||
</Container>
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
23
src/widgets/scrutiny/widget.js
Normal file
23
src/widgets/scrutiny/widget.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import genericProxyHandler from "utils/proxy/handlers/generic";
|
||||
|
||||
const widget = {
|
||||
api: "{url}/api/{endpoint}",
|
||||
proxyHandler: genericProxyHandler,
|
||||
|
||||
mappings: {
|
||||
summary: {
|
||||
endpoint: "summary",
|
||||
validate: [
|
||||
"data",
|
||||
]
|
||||
},
|
||||
settings: {
|
||||
endpoint: "settings",
|
||||
validate: [
|
||||
"settings",
|
||||
]
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default widget;
|
|
@ -4,7 +4,10 @@ import autobrr from "./autobrr/widget";
|
|||
import bazarr from "./bazarr/widget";
|
||||
import changedetectionio from "./changedetectionio/widget";
|
||||
import coinmarketcap from "./coinmarketcap/widget";
|
||||
import deluge from "./deluge/widget";
|
||||
import diskstation from "./diskstation/widget";
|
||||
import emby from "./emby/widget";
|
||||
import flood from "./flood/widget";
|
||||
import gluetun from "./gluetun/widget";
|
||||
import gotify from "./gotify/widget";
|
||||
import hdhomerun from "./hdhomerun/widget";
|
||||
|
@ -18,6 +21,7 @@ import npm from "./npm/widget";
|
|||
import nzbget from "./nzbget/widget";
|
||||
import ombi from "./ombi/widget";
|
||||
import overseerr from "./overseerr/widget";
|
||||
import paperlessngx from "./paperlessngx/widget";
|
||||
import pihole from "./pihole/widget";
|
||||
import plex from "./plex/widget";
|
||||
import portainer from "./portainer/widget";
|
||||
|
@ -29,6 +33,7 @@ import radarr from "./radarr/widget";
|
|||
import readarr from "./readarr/widget";
|
||||
import rutorrent from "./rutorrent/widget";
|
||||
import sabnzbd from "./sabnzbd/widget";
|
||||
import scrutiny from "./scrutiny/widget";
|
||||
import sonarr from "./sonarr/widget";
|
||||
import speedtest from "./speedtest/widget";
|
||||
import strelaysrv from "./strelaysrv/widget";
|
||||
|
@ -47,7 +52,10 @@ const widgets = {
|
|||
bazarr,
|
||||
changedetectionio,
|
||||
coinmarketcap,
|
||||
deluge,
|
||||
diskstation,
|
||||
emby,
|
||||
flood,
|
||||
gluetun,
|
||||
gotify,
|
||||
hdhomerun,
|
||||
|
@ -62,6 +70,7 @@ const widgets = {
|
|||
nzbget,
|
||||
ombi,
|
||||
overseerr,
|
||||
paperlessngx,
|
||||
pihole,
|
||||
plex,
|
||||
portainer,
|
||||
|
@ -73,6 +82,7 @@ const widgets = {
|
|||
readarr,
|
||||
rutorrent,
|
||||
sabnzbd,
|
||||
scrutiny,
|
||||
sonarr,
|
||||
speedtest,
|
||||
strelaysrv,
|
||||
|
|
Loading…
Add table
Reference in a new issue