Merge branch 'gethomepage:dev' into integration

This commit is contained in:
djeinstine 2024-12-22 08:50:54 +01:00 committed by GitHub
commit 797cc52359
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
70 changed files with 337 additions and 242 deletions

View File

@ -1,3 +1,14 @@
<!--
==== STOP ====================
======== STOP ================
============ STOP ============
================ STOP ========
==================== STOP ====
⚠️ Before opening this pull request please review the guidelines in the checklist below.
If this PR does not meet those guidelines it will not be accepted, and everyone will be sad.
-->
## Proposed change
<!--

View File

@ -118,7 +118,7 @@ jobs:
uses: docker/build-push-action@v6
with:
context: .
push: ${{ github.event_name != 'pull_request' && !(github.event_name == 'push' && startsWith(github.ref, 'refs/heads/feature')) }}
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |

View File

@ -212,9 +212,9 @@ jobs:
}
const CUTOFF_1_DAYS = 180;
const CUTOFF_1_COUNT = 5;
const CUTOFF_1_COUNT = 10;
const CUTOFF_2_DAYS = 365;
const CUTOFF_2_COUNT = 10;
const CUTOFF_2_COUNT = 20;
const cutoff1Date = new Date();
cutoff1Date.setDate(cutoff1Date.getDate() - CUTOFF_1_DAYS);

View File

@ -210,7 +210,7 @@ rules:
- get
- list
- apiGroups:
- traefik.containo.us
- traefik.io
resources:
- ingressroutes
verbs:

View File

@ -55,7 +55,7 @@ self-hosted / open-source alternative, we ask that any widgets, etc. are develop
To ensure cohesiveness of various widgets, the following should be used as a guide for developing new widgets:
- Please only submit widgets that target a feature request discussion with at least 10 'up-votes'. The purpose of this requirement is to avoid the addition (and maintenance) of service widgets that might only benefit a small number of users.
- Please only submit widgets that target a feature request discussion with at least 20 'up-votes'. The purpose of this requirement is to avoid the addition (and maintenance) of service widgets that might only benefit a small number of users.
- Note that we reserve the right to decline widgets for projects that are very young (eg < ~1y) or those with a small reach (eg low GitHub stars). Again, this is in an effort to keep overall widget maintenance under control.
- Widgets should be only one row of blocks
- Widgets should be no more than 4 blocks wide and generally conform to the styling / design choices of other widgets

View File

@ -12,6 +12,11 @@ The `systemID` in the `id` field on the collections page of Beszel.
Allowed fields for 'overview' mode: `["systems", "up"]`
Allowed fields for a single system: `["name", "status", "updated", "cpu", "memory", "disk", "network"]`
| Beszel Version | Homepage Widget Version |
| -------------- | ----------------------- |
| < 0.9.0 | 1 (default) |
| >= 0.9.0 | 2 |
```yaml
widget:
type: beszel
@ -19,4 +24,5 @@ widget:
username: username # email
password: password
systemId: systemId # optional
version: 2 # optional, default is 1
```

View File

@ -14,4 +14,5 @@ widget:
type: deluge
url: http://deluge.host.or.ip
password: password # webui password
enableLeechProgress: true # optional, defaults to false
```

View File

@ -16,5 +16,6 @@ To group both `offline` and `unknown` devices together, users should use the `of
widget:
type: esphome
url: http://esphome.host.or.ip:port
key: myesphomecookie # only if auth enabled, get the value from a request from the frontend e.g. `authenticated=myesphomecookie`
username: myesphomeuser # only if auth enabled
password: myesphomepass # only if auth enabled
```

View File

@ -14,6 +14,7 @@ Allowed fields: `["name", "address", "last_seen", "status"]`.
```yaml
widget:
type: headscale
url: http://headscale.host.or.ip:port
nodeId: nodeid
key: headscaleapiaccesstoken
```

View File

@ -15,4 +15,5 @@ widget:
url: http://qbittorrent.host.or.ip
username: username
password: password
enableLeechProgress: true # optional, defaults to false
```

21
package-lock.json generated
View File

@ -34,7 +34,7 @@
"recharts": "^2.12.6",
"rrule": "^2.8.1",
"swr": "^1.3.0",
"systeminformation": "^5.23.2",
"systeminformation": "^5.23.8",
"tough-cookie": "^4.1.3",
"urbackup-server-api": "^0.52.1",
"winston": "^3.11.0",
@ -2115,9 +2115,9 @@
}
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
@ -5510,9 +5510,9 @@
"optional": true
},
"node_modules/nanoid": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
"funding": [
{
"type": "github",
@ -7680,10 +7680,9 @@
"license": "0BSD"
},
"node_modules/systeminformation": {
"version": "5.23.5",
"resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.23.5.tgz",
"integrity": "sha512-PEpJwhRYxZgBCAlWZhWIgfMTjXLqfcaZ1pJsJn9snWNfBW/Z1YQg1mbIUSWrEV3ErAHF7l/OoVLQeaZDlPzkpA==",
"license": "MIT",
"version": "5.23.8",
"resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.23.8.tgz",
"integrity": "sha512-Osd24mNKe6jr/YoXLLK3k8TMdzaxDffhpCxgkfgBHcapykIkd50HXThM3TCEuHO2pPuCsSx2ms/SunqhU5MmsQ==",
"os": [
"darwin",
"linux",

View File

@ -36,7 +36,7 @@
"recharts": "^2.12.6",
"rrule": "^2.8.1",
"swr": "^1.3.0",
"systeminformation": "^5.23.2",
"systeminformation": "^5.23.8",
"tough-cookie": "^4.1.3",
"urbackup-server-api": "^0.52.1",
"winston": "^3.11.0",

View File

@ -87,8 +87,8 @@ importers:
specifier: ^1.3.0
version: 1.3.0(react@18.3.1)
systeminformation:
specifier: ^5.23.2
version: 5.23.5
specifier: ^5.23.8
version: 5.23.8
tough-cookie:
specifier: ^4.1.3
version: 4.1.4
@ -783,8 +783,8 @@ packages:
resolution: {integrity: sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==}
engines: {node: '>=10.0.0'}
cross-spawn@7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
css-select@5.1.0:
@ -2528,8 +2528,8 @@ packages:
resolution: {integrity: sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==}
engines: {node: ^14.18.0 || >=16.0.0}
systeminformation@5.23.5:
resolution: {integrity: sha512-PEpJwhRYxZgBCAlWZhWIgfMTjXLqfcaZ1pJsJn9snWNfBW/Z1YQg1mbIUSWrEV3ErAHF7l/OoVLQeaZDlPzkpA==}
systeminformation@5.23.8:
resolution: {integrity: sha512-Osd24mNKe6jr/YoXLLK3k8TMdzaxDffhpCxgkfgBHcapykIkd50HXThM3TCEuHO2pPuCsSx2ms/SunqhU5MmsQ==}
engines: {node: '>=8.0.0'}
os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android]
hasBin: true
@ -3462,7 +3462,7 @@ snapshots:
nan: 2.20.0
optional: true
cross-spawn@7.0.3:
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
shebang-command: 2.0.0
@ -4003,7 +4003,7 @@ snapshots:
'@ungap/structured-clone': 1.2.0
ajv: 6.12.6
chalk: 4.1.2
cross-spawn: 7.0.3
cross-spawn: 7.0.6
debug: 4.3.6
doctrine: 3.0.0
escape-string-regexp: 4.0.0
@ -4058,7 +4058,7 @@ snapshots:
execa@5.0.0:
dependencies:
cross-spawn: 7.0.3
cross-spawn: 7.0.6
get-stream: 6.0.1
human-signals: 2.1.0
is-stream: 2.0.1
@ -4127,7 +4127,7 @@ snapshots:
foreground-child@3.3.0:
dependencies:
cross-spawn: 7.0.3
cross-spawn: 7.0.6
signal-exit: 4.1.0
forever-agent@0.6.1: {}
@ -5420,7 +5420,7 @@ snapshots:
'@pkgr/core': 0.1.1
tslib: 2.7.0
systeminformation@5.23.5: {}
systeminformation@5.23.8: {}
tailwind-scrollbar@3.1.0(tailwindcss@3.4.14):
dependencies:

View File

@ -120,7 +120,7 @@
"grid_power": "Rooster",
"home_power": "Verbruik",
"charge_power": "Laaier",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "Aflaai",
@ -990,22 +990,22 @@
"network": "NET"
},
"argocd": {
"apps": "Apps",
"synced": "Synced",
"outOfSync": "Out Of Sync",
"apps": "Programme",
"synced": "Gesinkroniseer",
"outOfSync": "Nie Gesinchroniseer Nie",
"healthy": "Gesond",
"degraded": "Degraded",
"progressing": "Progressing",
"degraded": "Gedegradeer",
"progressing": "Vorderend",
"missing": "Vermis",
"suspended": "Suspended"
"suspended": "Geskors"
},
"spoolman": {
"loading": "Laai"
},
"gitlab": {
"groups": "Groups",
"groups": "Groepe",
"issues": "Kwessies",
"merges": "Merge Requests",
"projects": "Projects"
"merges": "Saamvleg Versoeke",
"projects": "Projekte"
}
}

View File

@ -120,7 +120,7 @@
"grid_power": "شبكة",
"home_power": "الاستهلاك",
"charge_power": "شاحن",
"watt_hour": "واط ساعة"
"kilowatt": "kW"
},
"flood": {
"download": "التنزيل",

View File

@ -120,7 +120,7 @@
"grid_power": "Grid",
"home_power": "Consumption",
"charge_power": "Charger",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "Download",

View File

@ -120,7 +120,7 @@
"grid_power": "Xarxa",
"home_power": "Consum",
"charge_power": "Carregador",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "Descarregar",
@ -350,7 +350,7 @@
"queue": "Cua",
"processed": "Processat",
"errored": "Error",
"saved": "Desat"
"saved": "Estalviat"
},
"traefik": {
"routers": "Encaminadors",
@ -581,7 +581,7 @@
"clientIP": "Client"
},
"scrutiny": {
"passed": "Aprobat",
"passed": "Aprovat",
"failed": "Error",
"unknown": "Desconegut"
},
@ -820,7 +820,7 @@
"total": "Total",
"running": "En execució",
"stopped": "Aturat",
"passed": "Aprobat",
"passed": "Aprovat",
"failed": "Error"
},
"openwrt": {
@ -991,21 +991,21 @@
},
"argocd": {
"apps": "Apps",
"synced": "Synced",
"outOfSync": "Out Of Sync",
"synced": "Sincronitzats",
"outOfSync": "Dessincronitzats",
"healthy": "Saludable",
"degraded": "Degraded",
"progressing": "Progressing",
"degraded": "Degradats",
"progressing": "Progressant",
"missing": "Falten",
"suspended": "Suspended"
"suspended": "Suspesos"
},
"spoolman": {
"loading": "Carregant"
},
"gitlab": {
"groups": "Groups",
"groups": "Grups",
"issues": "Problemes",
"merges": "Merge Requests",
"projects": "Projects"
"projects": "Projectes"
}
}

View File

@ -120,7 +120,7 @@
"grid_power": "Mřížka",
"home_power": "Spotřeba",
"charge_power": "Nabíječka",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "Stahování",

View File

@ -120,7 +120,7 @@
"grid_power": "Gitter",
"home_power": "Forbrug",
"charge_power": "Oplader",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "Download",

View File

@ -120,7 +120,7 @@
"grid_power": "Netz",
"home_power": "verbauch",
"charge_power": "Ladegerät",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "Download",
@ -1003,9 +1003,9 @@
"loading": "Wird geladen"
},
"gitlab": {
"groups": "Groups",
"groups": "Gruppen",
"issues": "Probleme",
"merges": "Merge Requests",
"projects": "Projects"
"projects": "Projekte"
}
}

View File

@ -120,7 +120,7 @@
"grid_power": "Πλέγμα",
"home_power": "Κατανάλωση",
"charge_power": "Φορτιστής",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "Λήξη",

View File

@ -120,7 +120,7 @@
"grid_power": "Grid",
"home_power": "Consumption",
"charge_power": "Charger",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "Elŝuti",

View File

@ -120,7 +120,7 @@
"grid_power": "Red",
"home_power": "Consumo",
"charge_power": "Cargador",
"watt_hour": "vatio-hora (Wh)"
"kilowatt": "kW"
},
"flood": {
"download": "Descarga",

View File

@ -120,7 +120,7 @@
"grid_power": "Grid",
"home_power": "Consumption",
"charge_power": "Charger",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "Download",

View File

@ -120,7 +120,7 @@
"grid_power": "Grid",
"home_power": "Consumption",
"charge_power": "Charger",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "Download",

View File

@ -120,7 +120,7 @@
"grid_power": "Grille",
"home_power": "Consommation",
"charge_power": "Chargeur",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "Récep.",

View File

@ -120,7 +120,7 @@
"grid_power": "Grid",
"home_power": "Consumption",
"charge_power": "Charger",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "Download",

View File

@ -120,7 +120,7 @@
"grid_power": "Grid",
"home_power": "Consumption",
"charge_power": "Charger",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "Download",

View File

@ -120,7 +120,7 @@
"grid_power": "Raspored",
"home_power": "Potrošnja",
"charge_power": "Punjač",
"watt_hour": "Kilovat-sat"
"kilowatt": "kW"
},
"flood": {
"download": "Preuzimanje",

View File

@ -25,7 +25,7 @@
"api_error": "API Hiba",
"information": "Információ",
"status": "Státusz",
"url": "LINK",
"url": "URL",
"raw_error": "Nyers hiba",
"response_data": "Válaszadatok"
},
@ -120,7 +120,7 @@
"grid_power": "Rács",
"home_power": "Fogyasztás",
"charge_power": "Töltő",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "Letöltés",
@ -227,8 +227,8 @@
"seed": "Seed"
},
"develancacheui": {
"cachehitbytes": "Cache Hit Bytes",
"cachemissbytes": "Cache Miss Bytes"
"cachehitbytes": "Gyorsítótárban Sikeres Bitek",
"cachemissbytes": "Gyorsítótárban Hibás Bitek"
},
"downloadstation": {
"download": "Letöltés",
@ -311,13 +311,13 @@
},
"suwayomi": {
"download": "Letöltött",
"nondownload": "Non-Downloaded",
"nondownload": "Nem Letöltött",
"read": "Olvasott",
"unread": "Olvasatlan",
"downloadedread": "Downloaded & Read",
"downloadedunread": "Downloaded & Unread",
"nondownloadedread": "Non-Downloaded & Read",
"nondownloadedunread": "Non-Downloaded & Unread"
"downloadedread": "Letöltött & Olvasott",
"downloadedunread": "Letöltött & Olvasatlan",
"nondownloadedread": "Nem Letöltött & Olvasatlan",
"nondownloadedunread": "Nem Letöltött & Olvasatlan"
},
"tailscale": {
"address": "Cím",
@ -335,15 +335,15 @@
},
"technitium": {
"totalQueries": "Lekérdezések",
"totalNoError": "Success",
"totalServerFailure": "Failures",
"totalNxDomain": "NX Domains",
"totalRefused": "Refused",
"totalAuthoritative": "Authoritative",
"totalRecursive": "Recursive",
"totalCached": "Cached",
"totalNoError": "Sikerek",
"totalServerFailure": "Hibák",
"totalNxDomain": "NX Domainek",
"totalRefused": "Elutasított",
"totalAuthoritative": "Irányadó",
"totalRecursive": "Rekurzív",
"totalCached": "Gyorsítótárazott",
"totalBlocked": "Blokkolt",
"totalDropped": "Dropped",
"totalDropped": "Eldobott",
"totalClients": "Kliensek"
},
"tdarr": {
@ -453,7 +453,7 @@
"search": "Keresés",
"custom": "Egyedi",
"visit": "Megnéz",
"url": "LINK",
"url": "URL",
"searchsuggestion": "Javaslat"
},
"wmo": {
@ -854,16 +854,16 @@
},
"romm": {
"platforms": "Felület",
"totalRoms": "Games",
"saves": "Saves",
"states": "States",
"screenshots": "Screenshots",
"totalfilesize": "Total Size"
"totalRoms": "Játékok",
"saves": "Mentések",
"states": "Állapotok",
"screenshots": "Képernyőképek",
"totalfilesize": "Teljes méret"
},
"mailcow": {
"domains": "Domainek",
"mailboxes": "Mailboxes",
"mails": "Mails",
"mailboxes": "E-mail fiókok",
"mails": "Mailek",
"storage": "Tárhely"
},
"netdata": {
@ -912,7 +912,7 @@
},
"crowdsec": {
"alerts": "Riasztások",
"bans": "Bans"
"bans": "Kitiltások"
},
"wgeasy": {
"connected": "Csatlakozva",
@ -921,10 +921,10 @@
"total": "Összes"
},
"swagdashboard": {
"proxied": "Proxied",
"auth": "With Auth",
"outdated": "Outdated",
"banned": "Banned"
"proxied": "Proxyzott",
"auth": "Hitelesítéssel",
"outdated": "Elavult",
"banned": "Kitiltott"
},
"myspeed": {
"ping": "Ping",
@ -932,29 +932,29 @@
"upload": "Feltöltés"
},
"stocks": {
"stocks": "Stocks",
"loading": "Loading",
"open": "Open - US Market",
"closed": "Closed - US Market",
"invalidConfiguration": "Invalid Configuration"
"stocks": "Tőzsde",
"loading": "Betöltés",
"open": "Nyitva - US Piac",
"closed": "Zárva - US Piac",
"invalidConfiguration": "Érvénytelen konfiguráció"
},
"frigate": {
"cameras": "Cameras",
"cameras": "Kamerák",
"uptime": "Üzemidő",
"version": "Verzió"
},
"linkwarden": {
"links": "Links",
"collections": "Collections",
"links": "Linkek",
"collections": "Gyűjtemény",
"tags": "Címkék"
},
"zabbix": {
"unclassified": "Not classified",
"unclassified": "Nem titkosított",
"information": "Információ",
"warning": "Warning",
"average": "Average",
"high": "High",
"disaster": "Disaster"
"warning": "Figyelmeztetés",
"average": "Átlag",
"high": "Magas",
"disaster": "Katasztrófa"
},
"lubelogger": {
"vehicle": "Jármű",
@ -962,13 +962,13 @@
"serviceRecords": "Szolgáltatások nyílvántartása",
"reminders": "Emlékeztetők",
"nextReminder": "Következő emlékeztető",
"none": "None"
"none": "Semmi"
},
"vikunja": {
"projects": "Active Projects",
"tasks7d": "Tasks Due This Week",
"tasksOverdue": "Overdue Tasks",
"tasksInProgress": "Tasks In Progress"
"projects": "Aktív Projektek",
"tasks7d": "Hátralévő feladatok a héten",
"tasksOverdue": "Lejárt feladatok",
"tasksInProgress": "Folyamatban levő Feladatok"
},
"headscale": {
"name": "Név",
@ -980,32 +980,32 @@
},
"beszel": {
"name": "Név",
"systems": "Systems",
"systems": "Rendszerek",
"up": "Fel",
"status": "Státusz",
"updated": "Frissített",
"cpu": "Processzor",
"memory": "RAM",
"disk": "Disk",
"network": "NET"
"disk": "Lemez",
"network": "Hálózat"
},
"argocd": {
"apps": "Apps",
"synced": "Synced",
"outOfSync": "Out Of Sync",
"apps": "Alkalmazások",
"synced": "Szinkronizált",
"outOfSync": "Nincs szinkronban",
"healthy": "Egészséges",
"degraded": "Degraded",
"progressing": "Progressing",
"degraded": "Leépült",
"progressing": "Halad",
"missing": "Hiányzik",
"suspended": "Suspended"
"suspended": "Felfüggesztett"
},
"spoolman": {
"loading": "Loading"
"loading": "Betöltés"
},
"gitlab": {
"groups": "Groups",
"groups": "Csoportok",
"issues": "Problémák",
"merges": "Merge Requests",
"projects": "Projects"
"merges": "Merge kérések",
"projects": "Projektek"
}
}

View File

@ -120,7 +120,7 @@
"grid_power": "Grid",
"home_power": "Konsumsi",
"charge_power": "Charger",
"watt_hour": "Watt/jam"
"kilowatt": "kW"
},
"flood": {
"download": "Unduh",

View File

@ -120,7 +120,7 @@
"grid_power": "Griglia",
"home_power": "Consumo",
"charge_power": "Caricatore",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "Download",

View File

@ -120,7 +120,7 @@
"grid_power": "グリッド",
"home_power": "消費",
"charge_power": "チャージャー",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "ダウンロード",
@ -804,7 +804,7 @@
"ping": "Ping"
},
"urbackup": {
"ok": "はい",
"ok": "正常",
"errored": "エラー",
"noRecent": "期限切れ",
"totalUsed": "使用済みストレージ"
@ -957,7 +957,7 @@
"disaster": "災害"
},
"lubelogger": {
"vehicle": "Vehicle",
"vehicle": "車両",
"vehicles": "Vehicles",
"serviceRecords": "Service Records",
"reminders": "Reminders",

View File

@ -120,7 +120,7 @@
"grid_power": "눈금",
"home_power": "Consumption",
"charge_power": "Charger",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "다운로드",

View File

@ -120,7 +120,7 @@
"grid_power": "Grid",
"home_power": "Consumption",
"charge_power": "Charger",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "Lejupielāde",

View File

@ -120,7 +120,7 @@
"grid_power": "Grid",
"home_power": "Penggunaan",
"charge_power": "Pengecas",
"watt_hour": "Wj"
"kilowatt": "kW"
},
"flood": {
"download": "Muat turun",

View File

@ -120,7 +120,7 @@
"grid_power": "Netstroom",
"home_power": "Consumptie",
"charge_power": "Oplader",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "Download",

View File

@ -120,7 +120,7 @@
"grid_power": "Nett",
"home_power": "Forbruk",
"charge_power": "Lader",
"watt_hour": "W/t"
"kilowatt": "kW"
},
"flood": {
"download": "Last ned",

View File

@ -13,7 +13,7 @@
"ms": "{{value, number}}",
"date": "{{value, date}}",
"relativeDate": "{{value, relativeDate}}",
"duration": "{{value, duration}}",
"duration": "{value, duration}",
"months": "mc",
"days": "d",
"hours": "g",
@ -120,7 +120,7 @@
"grid_power": "Siatka",
"home_power": "Zużycie",
"charge_power": "Ładowarka",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "Pobieranie",
@ -311,13 +311,13 @@
},
"suwayomi": {
"download": "Pobrano",
"nondownload": "Non-Downloaded",
"nondownload": "Niepobrane",
"read": "Przeczytane",
"unread": "Nieprzeczytane",
"downloadedread": "Downloaded & Read",
"downloadedunread": "Downloaded & Unread",
"nondownloadedread": "Non-Downloaded & Read",
"nondownloadedunread": "Non-Downloaded & Unread"
"downloadedread": "Pobrane i przeczytane",
"downloadedunread": "Pobrane i nieprzeczytane",
"nondownloadedread": "Niepobrane i przeczytane",
"nondownloadedunread": "Niepobrane i nieprzeczytane"
},
"tailscale": {
"address": "Adres",
@ -957,18 +957,18 @@
"disaster": "Katastrofa"
},
"lubelogger": {
"vehicle": "Vehicle",
"vehicles": "Vehicles",
"serviceRecords": "Service Records",
"reminders": "Reminders",
"nextReminder": "Next Reminder",
"none": "None"
"vehicle": "Pojazd",
"vehicles": "Pojazdy",
"serviceRecords": "Wpisy serwisowe",
"reminders": "Przypomnienia",
"nextReminder": "Następne przypomnienie",
"none": "Brak"
},
"vikunja": {
"projects": "Active Projects",
"tasks7d": "Tasks Due This Week",
"tasksOverdue": "Overdue Tasks",
"tasksInProgress": "Tasks In Progress"
"projects": "Aktywne Projekty",
"tasks7d": "Zadania w tym tygodniu",
"tasksOverdue": "Zaległe zadania",
"tasksInProgress": "Zadania w toku"
},
"headscale": {
"name": "Nazwa",
@ -980,32 +980,32 @@
},
"beszel": {
"name": "Nazwa",
"systems": "Systems",
"systems": "Systemy",
"up": "Dostępny",
"status": "Stan",
"updated": "Zaktualizowane",
"cpu": "Procesor",
"memory": "RAM",
"disk": "Disk",
"disk": "Dysk",
"network": "NET"
},
"argocd": {
"apps": "Apps",
"synced": "Synced",
"outOfSync": "Out Of Sync",
"apps": "Aplikacje",
"synced": "Synchronizowane",
"outOfSync": "Bez synchronizacji",
"healthy": "Zdrowy",
"degraded": "Degraded",
"progressing": "Progressing",
"degraded": "Zdegradowane",
"progressing": "Postępujące",
"missing": "Brakujące",
"suspended": "Suspended"
"suspended": "Zawieszone"
},
"spoolman": {
"loading": "Wczytywanie"
},
"gitlab": {
"groups": "Groups",
"groups": "Grupy",
"issues": "Zgłoszenia",
"merges": "Merge Requests",
"projects": "Projects"
"merges": "Żądania scaleń",
"projects": "Projekty"
}
}

View File

@ -120,7 +120,7 @@
"grid_power": "Grelha",
"home_power": "Consumo",
"charge_power": "Carregador",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "Descarregar",

View File

@ -120,7 +120,7 @@
"grid_power": "Grade",
"home_power": "Consumo",
"charge_power": "Carregador",
"watt_hour": "Kw"
"kilowatt": "kW"
},
"flood": {
"download": "Descarregar",

View File

@ -120,7 +120,7 @@
"grid_power": "Grilă",
"home_power": "Consum",
"charge_power": "Încărcător",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "Descarcă",

View File

@ -120,7 +120,7 @@
"grid_power": "Сетка",
"home_power": "Потребление",
"charge_power": "Зарядка",
"watt_hour": "Вт"
"kilowatt": "кВт"
},
"flood": {
"download": "Скачивание",
@ -990,22 +990,22 @@
"network": "Сеть"
},
"argocd": {
"apps": "Apps",
"synced": "Synced",
"outOfSync": "Out Of Sync",
"apps": "Приложения",
"synced": "Синхронизированные",
"outOfSync": "Не синхронизированные",
"healthy": "Здоровый",
"degraded": "Degraded",
"progressing": "Progressing",
"degraded": "Деградированные",
"progressing": "Выполняются",
"missing": "Отсутствует",
"suspended": "Suspended"
"suspended": "Приостановленные"
},
"spoolman": {
"loading": "Загрузка"
},
"gitlab": {
"groups": "Groups",
"groups": "Группы",
"issues": "Вопросы",
"merges": "Merge Requests",
"projects": "Projects"
"merges": "Мердж-реквесты",
"projects": "Проекты"
}
}

View File

@ -120,7 +120,7 @@
"grid_power": "Mriežka",
"home_power": "Spotreba",
"charge_power": "Nabíjačka",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "Sťahovanie",

View File

@ -120,7 +120,7 @@
"grid_power": "Omrežje",
"home_power": "Poraba",
"charge_power": "Polnilec",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "Prenos",
@ -1003,9 +1003,9 @@
"loading": "Nalaganje"
},
"gitlab": {
"groups": "Groups",
"groups": "Skupine",
"issues": "Težave",
"merges": "Merge Requests",
"projects": "Projects"
"merges": "Združi zahtevke",
"projects": "Projekti"
}
}

View File

@ -120,7 +120,7 @@
"grid_power": "Grid",
"home_power": "Consumption",
"charge_power": "Charger",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "Download",

View File

@ -120,7 +120,7 @@
"grid_power": "Grid",
"home_power": "Consumption",
"charge_power": "Charger",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "Download",

View File

@ -120,7 +120,7 @@
"grid_power": "Grid",
"home_power": "Consumption",
"charge_power": "Charger",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "Download",

View File

@ -120,7 +120,7 @@
"grid_power": "Grid",
"home_power": "Consumption",
"charge_power": "Charger",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "ดาวน์โหลด",

View File

@ -120,7 +120,7 @@
"grid_power": "Güç",
"home_power": "Tüketim",
"charge_power": "Şarj",
"watt_hour": "Watt/Saat"
"kilowatt": "kW"
},
"flood": {
"download": "İndirme",

View File

@ -120,7 +120,7 @@
"grid_power": "Сітка",
"home_power": "Споживання",
"charge_power": "Зарядний пристрій",
"watt_hour": "Вт/год"
"kilowatt": "kW"
},
"flood": {
"download": "Завантажено",
@ -311,13 +311,13 @@
},
"suwayomi": {
"download": "Завантажено",
"nondownload": "Non-Downloaded",
"nondownload": "Не завантажено",
"read": "Прочитано",
"unread": "Не прочитано",
"downloadedread": "Downloaded & Read",
"downloadedunread": "Downloaded & Unread",
"nondownloadedread": "Non-Downloaded & Read",
"nondownloadedunread": "Non-Downloaded & Unread"
"downloadedread": "Завантажено та Прочитано",
"downloadedunread": "Завантажено та Непрочитано",
"nondownloadedread": "Не завантажено та Прочитано",
"nondownloadedunread": "Не завантажено та Не прочитано"
},
"tailscale": {
"address": "Адреса",
@ -965,10 +965,10 @@
"none": "Жодного"
},
"vikunja": {
"projects": "Active Projects",
"tasks7d": "Tasks Due This Week",
"tasksOverdue": "Overdue Tasks",
"tasksInProgress": "Tasks In Progress"
"projects": "Активні проекти",
"tasks7d": "Завдання цього тижня",
"tasksOverdue": "Прострочені завдання",
"tasksInProgress": "Завдання в процесі"
},
"headscale": {
"name": "Назва",
@ -980,32 +980,32 @@
},
"beszel": {
"name": "Назва",
"systems": "Systems",
"systems": "Системи",
"up": "Онлайн",
"status": "Стан",
"updated": "Оновлено",
"cpu": "ЦП",
"memory": "ОЗП",
"disk": "Disk",
"network": "NET"
"disk": "Диск",
"network": "МЕРЕЖА"
},
"argocd": {
"apps": "Apps",
"synced": "Synced",
"outOfSync": "Out Of Sync",
"apps": "Додатки",
"synced": "Синхронізовано",
"outOfSync": "Не синхронізовано",
"healthy": "Здоровий",
"degraded": "Degraded",
"progressing": "Progressing",
"degraded": "Деградує",
"progressing": "Прогрес",
"missing": "Відсутній",
"suspended": "Suspended"
"suspended": "Призупинено"
},
"spoolman": {
"loading": "Завантажую"
},
"gitlab": {
"groups": "Groups",
"groups": "Групи",
"issues": "Питання",
"merges": "Merge Requests",
"projects": "Projects"
"merges": "Запити на злиття",
"projects": "Проєкти"
}
}

View File

@ -120,7 +120,7 @@
"grid_power": "Grid",
"home_power": "Consumption",
"charge_power": "Charger",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "Download",

View File

@ -120,7 +120,7 @@
"grid_power": "電網",
"home_power": "電源使用率",
"charge_power": "充電",
"watt_hour": "瓦時 (Wh)"
"kilowatt": "kW"
},
"flood": {
"download": "下載速率",

View File

@ -120,7 +120,7 @@
"grid_power": "Grid",
"home_power": "Consumption",
"charge_power": "Charger",
"watt_hour": "Wh"
"kilowatt": "kW"
},
"flood": {
"download": "下载",

View File

@ -120,7 +120,7 @@
"grid_power": "電網",
"home_power": "電源使用率",
"charge_power": "充電",
"watt_hour": "瓦時 (Wh)"
"kilowatt": "kW"
},
"flood": {
"download": "下載速率",

View File

@ -129,7 +129,7 @@ export default function QuickLaunch({ servicesAndBookmarks, searchString, setSea
useEffect(() => {
const abortController = new AbortController();
if (searchString.length === 0) setResults([]);
if (searchString.trim().length === 0) setResults([]);
else {
let newResults = servicesAndBookmarks.filter((r) => {
const nameMatch = r.name.toLowerCase().includes(searchString);

View File

@ -94,6 +94,7 @@ export default function Search({ options }) {
if (
options.showSearchSuggestions &&
(selectedProvider.suggestionUrl || options.suggestionUrl) && // custom providers pass url via options
query.trim().length > 0 &&
query.trim() !== searchSuggestions[0]
) {
fetch(`/api/search/searchSuggestion?query=${encodeURIComponent(query)}&providerName=${selectedProvider.name}`, {

View File

@ -51,7 +51,9 @@ export default function Widget({ options }) {
key={stock.ticker}
className="rounded h-full text-xs px-1 w-[4.75rem] flex flex-col items-center justify-center"
>
<span className="text-theme-800 dark:text-theme-200 text-xs">{stock.ticker}</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">
{stock.ticker.split(":").pop()}
</span>
{!viewingPercentChange ? (
<span
className={

View File

@ -1,9 +1,14 @@
import cachedFetch from "utils/proxy/cached-fetch";
import { getSettings } from "utils/config/config";
import createLogger from "utils/logger";
const logger = createLogger("stocks");
export default async function handler(req, res) {
const { watchlist, provider, cache } = req.query;
logger.debug("Stocks API request: %o", { watchlist, provider, cache });
if (!watchlist) {
return res.status(400).json({ error: "Missing watchlist" });
}
@ -56,6 +61,7 @@ export default async function handler(req, res) {
// Finnhub free accounts allow up to 60 calls/minute
// https://finnhub.io/pricing
const { c, dp } = await cachedFetch(apiUrl, cache || 1);
logger.debug("Finnhub API response for %s: %o", ticker, { c, dp });
// API sometimes returns 200, but values returned are `null`
if (c === null || dp === null) {

View File

@ -359,7 +359,7 @@ function Home({ initialSettings }) {
return (
<>
<Head>
<title>{settings.title || "Homepage"}</title>
<title>{initialSettings.title || "Homepage"}</title>
{settings.base && <base href={settings.base} />}
{settings.favicon ? (
<>

View File

@ -158,7 +158,7 @@ export async function servicesResponse() {
const discoveredKubernetesGroup = findGroupByName(discoveredKubernetesServices, groupName) || {
services: [],
};
const configuredGroup = findGroupByName(configuredServices, groupName) || { services: [] };
const configuredGroup = findGroupByName(configuredServices, groupName) || { services: [], groups: [] };
const mergedGroup = {
name: groupName,
@ -171,7 +171,7 @@ export async function servicesResponse() {
if (definedLayouts) {
const layoutIndex = definedLayouts.findIndex((layout) => layout === mergedGroup.name);
if (layoutIndex > -1) sortedGroups[layoutIndex] = mergedGroup;
else if (configuredGroup.name) {
else if (configuredGroup.parent) {
// this is a nested group, so find the parent group and merge the services
mergeSubgroups(configuredServices, mergedGroup);
} else unsortedGroups.push(mergedGroup);

View File

@ -24,6 +24,10 @@ function parseServicesToGroups(services) {
const serviceGroupServices = [];
serviceGroup[name].forEach((entries) => {
const entryName = Object.keys(entries)[0];
if (!entries[entryName]) {
logger.warn(`Error parsing service "${entryName}" from config. Ensure required fields are present.`);
return;
}
if (Array.isArray(entries[entryName])) {
groups = groups.concat(parseServicesToGroups([{ [entryName]: entries[entryName] }]));
} else {
@ -105,7 +109,11 @@ export async function servicesFromDocker() {
type: "service",
};
}
shvl.set(constructedService, value, substituteEnvironmentVars(containerLabels[label]));
let substitutedVal = substituteEnvironmentVars(containerLabels[label]);
if (value === "widget.version") {
substitutedVal = parseInt(substitutedVal, 10);
}
shvl.set(constructedService, value, substitutedVal);
}
});
@ -316,6 +324,9 @@ export function cleanServiceGroups(groups) {
mappings,
display,
// deluge, qbittorrent
enableLeechProgress,
// diskstation
volume,
@ -335,7 +346,7 @@ export function cleanServiceGroups(groups) {
// frigate
enableRecentEvents,
// glances, immich, mealie, pihole, pfsense
// beszel, glances, immich, mealie, pihole, pfsense
version,
// glances
@ -483,6 +494,9 @@ export function cleanServiceGroups(groups) {
if (allowScrolling) widget.allowScrolling = allowScrolling;
if (refreshInterval) widget.refreshInterval = refreshInterval;
}
if (["deluge", "qbittorrent"].includes(type)) {
if (enableLeechProgress !== undefined) widget.enableLeechProgress = JSON.parse(enableLeechProgress);
}
if (["opnsense", "pfsense"].includes(type)) {
if (wan) widget.wan = wan;
}
@ -510,7 +524,7 @@ export function cleanServiceGroups(groups) {
if (snapshotHost) widget.snapshotHost = snapshotHost;
if (snapshotPath) widget.snapshotPath = snapshotPath;
}
if (["glances", "immich", "mealie", "pfsense", "pihole"].includes(type)) {
if (["beszel", "glances", "immich", "mealie", "pfsense", "pihole"].includes(type)) {
if (version) widget.version = parseInt(version, 10);
}
if (type === "glances") {
@ -603,6 +617,7 @@ export function findGroupByName(groups, name) {
} else if (group.groups) {
const foundGroup = findGroupByName(group.groups, name);
if (foundGroup) {
foundGroup.parent = group;
return foundGroup;
}
}

View File

@ -89,7 +89,9 @@ export default async function credentialedProxyHandler(req, res, map) {
} else if (widget.type === "myspeed") {
headers.Password = `${widget.password}`;
} else if (widget.type === "esphome") {
if (widget.key) {
if (widget.username && widget.password) {
headers.Authorization = `Basic ${Buffer.from(`${widget.username}:${widget.password}`).toString("base64")}`;
} else if (widget.key) {
headers.Cookie = `authenticated=${widget.key}`;
}
} else if (widget.type === "wgeasy") {

View File

@ -45,7 +45,12 @@ export default async function beszelProxyHandler(req, res) {
if (widget) {
const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget }));
const loginUrl = formatApiCall(widgets[widget.type].api, { endpoint: "admins/auth-with-password", ...widget });
let authEndpointVersion = "authv1";
if (widget.version === 2) authEndpointVersion = "authv2";
const loginUrl = formatApiCall(widgets[widget.type].api, {
endpoint: widgets[widget.type].mappings[authEndpointVersion].endpoint,
...widget,
});
let status;
let data;
@ -54,7 +59,7 @@ export default async function beszelProxyHandler(req, res) {
if (!token) {
[status, token] = await login(loginUrl, widget.username, widget.password, service);
if (status !== 200) {
logger.debug(`HTTP ${status} logging into npm api: ${token}`);
logger.debug(`HTTP ${status} logging into Beszel: ${token}`);
return res.status(status).send(token);
}
}
@ -68,12 +73,12 @@ export default async function beszelProxyHandler(req, res) {
});
if (status === 403) {
logger.debug(`HTTP ${status} retrieving data from npm api, logging in and trying again.`);
logger.debug(`HTTP ${status} retrieving data from Beszel, logging in and trying again.`);
cache.del(`${tokenCacheKey}.${service}`);
[status, token] = await login(loginUrl, widget.username, widget.password, service);
if (status !== 200) {
logger.debug(`HTTP ${status} logging into npm api: ${data}`);
logger.debug(`HTTP ${status} logging into Beszel: ${data}`);
return res.status(status).send(data);
}

View File

@ -5,6 +5,12 @@ const widget = {
proxyHandler: beszelProxyHandler,
mappings: {
authv1: {
endpoint: "admins/auth-with-password",
},
authv2: {
endpoint: "collections/_superusers/auth-with-password",
},
systems: {
endpoint: "collections/systems/records?page=1&perPage=500&sort=%2Bcreated",
},

View File

@ -1,5 +1,7 @@
import { useTranslation } from "next-i18next";
import QueueEntry from "../../components/widgets/queue/queueEntry";
import Container from "components/services/widget/container";
import Block from "components/services/widget/block";
import useWidgetAPI from "utils/proxy/use-widget-api";
@ -32,21 +34,38 @@ export default function Component({ service }) {
let rateDl = 0;
let rateUl = 0;
let completed = 0;
const leechTorrents = [];
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;
if (torrent.state === "Downloading") {
leechTorrents.push(torrent);
}
}
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.byterate", { value: rateDl })} />
<Block label="deluge.seed" value={t("common.number", { value: completed })} />
<Block label="deluge.upload" value={t("common.byterate", { value: rateUl })} />
</Container>
<>
<Container service={service}>
<Block label="deluge.leech" value={t("common.number", { value: leech })} />
<Block label="deluge.download" value={t("common.byterate", { value: rateDl })} />
<Block label="deluge.seed" value={t("common.number", { value: completed })} />
<Block label="deluge.upload" value={t("common.byterate", { value: rateUl })} />
</Container>
{widget?.enableLeechProgress &&
leechTorrents.map((queueEntry) => (
<QueueEntry
progress={queueEntry.progress}
timeLeft={t("common.duration", { value: queueEntry.eta })}
title={queueEntry.name}
activity={queueEntry.state}
key={`${queueEntry.name}-${queueEntry.total_remaining}`}
/>
))}
</>
);
}

View File

@ -17,6 +17,7 @@ const dataParams = [
"download_payload_rate",
"upload_payload_rate",
"total_remaining",
"eta",
],
{},
];

View File

@ -1,4 +1,5 @@
import { useContext } from "react";
import classNames from "classnames";
import Error from "./error";
@ -17,7 +18,7 @@ export default function Container({ children, widget, error = null, chart = true
}
return (
<div>
<div className={classNames("service-container", chart ? "chart relative h-[120px]" : "")}>
{children}
<div className={`absolute top-0 right-0 bottom-0 left-0 overflow-clip pointer-events-none ${className}`} />
{chart && <div className="h-[68px] overflow-clip" />}

View File

@ -63,7 +63,7 @@ export default function Component({ service }) {
<div className="opacity-25 w-14 text-right">{item.cpu_percent.toFixed(1)}%</div>
<div className="opacity-25 w-14 text-right">
{t("common.bytes", {
value: item.memory_info[memoryInfoKey],
value: item.memory_info[memoryInfoKey] ?? item.memory_info.wset,
maximumFractionDigits: 0,
})}
</div>

View File

@ -1,12 +1,13 @@
import { useTranslation } from "next-i18next";
import QueueEntry from "../../components/widgets/queue/queueEntry";
import Container from "components/services/widget/container";
import Block from "components/services/widget/block";
import useWidgetAPI from "utils/proxy/use-widget-api";
export default function Component({ service }) {
const { t } = useTranslation();
const { widget } = service;
const { data: torrentData, error: torrentError } = useWidgetAPI(widget, "torrents");
@ -29,6 +30,7 @@ export default function Component({ service }) {
let rateDl = 0;
let rateUl = 0;
let completed = 0;
const leechTorrents = [];
for (let i = 0; i < torrentData.length; i += 1) {
const torrent = torrentData[i];
@ -37,16 +39,31 @@ export default function Component({ service }) {
if (torrent.progress === 1) {
completed += 1;
}
if (torrent.state.includes("DL") || torrent.state === "downloading") {
leechTorrents.push(torrent);
}
}
const leech = torrentData.length - completed;
return (
<Container service={service}>
<Block label="qbittorrent.leech" value={t("common.number", { value: leech })} />
<Block label="qbittorrent.download" value={t("common.bibyterate", { value: rateDl, decimals: 1 })} />
<Block label="qbittorrent.seed" value={t("common.number", { value: completed })} />
<Block label="qbittorrent.upload" value={t("common.bibyterate", { value: rateUl, decimals: 1 })} />
</Container>
<>
<Container service={service}>
<Block label="qbittorrent.leech" value={t("common.number", { value: leech })} />
<Block label="qbittorrent.download" value={t("common.bibyterate", { value: rateDl, decimals: 1 })} />
<Block label="qbittorrent.seed" value={t("common.number", { value: completed })} />
<Block label="qbittorrent.upload" value={t("common.bibyterate", { value: rateUl, decimals: 1 })} />
</Container>
{widget?.enableLeechProgress &&
leechTorrents.map((queueEntry) => (
<QueueEntry
progress={queueEntry.progress * 100}
timeLeft={t("common.duration", { value: queueEntry.eta })}
title={queueEntry.name}
activity={queueEntry.state}
key={`${queueEntry.name}-${queueEntry.amount_left}`}
/>
))}
</>
);
}