mirror of
https://github.com/gethomepage/homepage.git
synced 2025-12-06 21:57:48 +01:00
Compare commits
1 Commits
v1.6.0
...
7ac9c2db87
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ac9c2db87 |
4
.github/workflows/docker-publish.yml
vendored
4
.github/workflows/docker-publish.yml
vendored
@@ -38,7 +38,7 @@ jobs:
|
|||||||
run_install: false
|
run_install: false
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -96,7 +96,7 @@ jobs:
|
|||||||
run_install: false
|
run_install: false
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|||||||
@@ -271,4 +271,4 @@ You can show the docker stats by clicking the status indicator but this can also
|
|||||||
showStats: true
|
showStats: true
|
||||||
```
|
```
|
||||||
|
|
||||||
Also see the settings for [show docker stats](settings.md#show-container-stats).
|
Also see the settings for [show docker stats](settings.md#show-docker-stats).
|
||||||
|
|||||||
@@ -178,32 +178,3 @@ See [ClusterRole and ClusterRoleBinding](../installation/k8s.md#clusterrole-and-
|
|||||||
## Caveats
|
## Caveats
|
||||||
|
|
||||||
Similarly to Docker service discovery, there currently is no rigid ordering to discovered services and discovered services will be displayed above those specified in the `services.yaml`.
|
Similarly to Docker service discovery, there currently is no rigid ordering to discovered services and discovered services will be displayed above those specified in the `services.yaml`.
|
||||||
|
|
||||||
## Adding extra configuration files
|
|
||||||
|
|
||||||
Some Homepage features (for example, [Proxmox](../configs/proxmox.md)) require additional configuration files such as `proxmox.yaml`.
|
|
||||||
When running Homepage on Kubernetes, these files must be provided via a `ConfigMap` and mounted into the container at `/app/config`.
|
|
||||||
|
|
||||||
### ConfigMap example
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: homepage
|
|
||||||
data:
|
|
||||||
proxmox.yaml: |
|
|
||||||
pve:
|
|
||||||
url: https://proxmox.host.or.ip:8006
|
|
||||||
token: username@pam!Token ID
|
|
||||||
secret: secret
|
|
||||||
```
|
|
||||||
|
|
||||||
Mount the file into `/app/config` by updating the `Deployment`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
volumeMounts:
|
|
||||||
- mountPath: /app/config/proxmox.yaml
|
|
||||||
name: homepage-config
|
|
||||||
subPath: proxmox.yaml
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ description: Proxmox Configuration
|
|||||||
---
|
---
|
||||||
|
|
||||||
The Proxmox connection is configured in the `proxmox.yaml` file. See [Create token](#create-token) section below for details on how to generate the required API token.
|
The Proxmox connection is configured in the `proxmox.yaml` file. See [Create token](#create-token) section below for details on how to generate the required API token.
|
||||||
To configure multiple nodes, ensure the key name in the `proxmox.yaml` matches the `proxmoxNode` field used in your service configuration.
|
You can configure multiple nodes - be sure to use the exact `proxmoxNode` identifier!
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
pve: # must match your actual Proxmox node name
|
pve:
|
||||||
url: https://proxmox.host.or.ip:8006
|
url: https://proxmox.host.or.ip:8006
|
||||||
token: username@pam!Token ID
|
token: username@pam!Token ID
|
||||||
secret: secret
|
secret: secret
|
||||||
|
|||||||
@@ -118,47 +118,6 @@ Each widget can optionally provide a list of which fields should be visible via
|
|||||||
key: apikeyapikeyapikeyapikeyapikey
|
key: apikeyapikeyapikeyapikeyapikey
|
||||||
```
|
```
|
||||||
|
|
||||||
### Block Highlighting
|
|
||||||
|
|
||||||
Widgets can tint their metric block text automatically based on rules defined alongside the service. Attach a `highlight` section to the widget configuration and map each block to one or more numeric or string rules using the field key (for example, `queued`, `lan_users`).
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
- Sonarr:
|
|
||||||
icon: sonarr.png
|
|
||||||
href: http://sonarr.host.or.ip
|
|
||||||
widget:
|
|
||||||
type: sonarr
|
|
||||||
url: http://sonarr.host.or.ip
|
|
||||||
key: ${SONARR_API_KEY}
|
|
||||||
highlight:
|
|
||||||
queued:
|
|
||||||
numeric:
|
|
||||||
- level: danger
|
|
||||||
when: gte
|
|
||||||
value: 20
|
|
||||||
- level: warn
|
|
||||||
when: gte
|
|
||||||
value: 5
|
|
||||||
- level: good
|
|
||||||
when: eq
|
|
||||||
value: 0
|
|
||||||
status:
|
|
||||||
string:
|
|
||||||
- level: danger
|
|
||||||
when: regex
|
|
||||||
value: "(failed|import) pending"
|
|
||||||
- level: good
|
|
||||||
when: equals
|
|
||||||
value: "All good"
|
|
||||||
status_code:
|
|
||||||
string:
|
|
||||||
- level: warn
|
|
||||||
when: regex
|
|
||||||
value: "^5\\d{2}$"
|
|
||||||
```
|
|
||||||
|
|
||||||
Supported numeric operators for the `when` property are `gt`, `gte`, `lt`, `lte`, `eq`, `ne`, `between`, and `outside`. String rules support `equals`, `includes`, `startsWith`, `endsWith`, and `regex`. Each rule can be inverted with `negate: true`, and string rules may pass `caseSensitive: true` or custom regex `flags`. The highlight engine does its best to coerce formatted values, but you will get the most reliable results when you pass plain numbers or strings into `<Block>`.
|
|
||||||
|
|
||||||
## Descriptions
|
## Descriptions
|
||||||
|
|
||||||
Services may have descriptions,
|
Services may have descriptions,
|
||||||
|
|||||||
@@ -109,20 +109,6 @@ color: slate
|
|||||||
|
|
||||||
Supported colors are: `slate`, `gray`, `zinc`, `neutral`, `stone`, `amber`, `yellow`, `lime`, `green`, `emerald`, `teal`, `cyan`, `sky`, `blue`, `indigo`, `violet`, `purple`, `fuchsia`, `pink`, `rose`, `red`, `white`
|
Supported colors are: `slate`, `gray`, `zinc`, `neutral`, `stone`, `amber`, `yellow`, `lime`, `green`, `emerald`, `teal`, `cyan`, `sky`, `blue`, `indigo`, `violet`, `purple`, `fuchsia`, `pink`, `rose`, `red`, `white`
|
||||||
|
|
||||||
## Block Highlight Levels
|
|
||||||
|
|
||||||
You can override the default Tailwind classes applied when a widget highlight rule resolves to the `good`, `warn`, or `danger` level.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
blockHighlights:
|
|
||||||
levels:
|
|
||||||
good: "bg-emerald-500/40 text-emerald-950 dark:bg-emerald-900/60 dark:text-emerald-400"
|
|
||||||
warn: "bg-amber-300/30 text-amber-900 dark:bg-amber-900/30 dark:text-amber-200"
|
|
||||||
danger: "bg-rose-700/45 text-rose-200 dark:bg-rose-950/70 dark:text-rose-400"
|
|
||||||
```
|
|
||||||
|
|
||||||
Any unspecified level falls back to the built-in defaults.
|
|
||||||
|
|
||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
You can configure service and bookmarks sections to be either "column" or "row" based layouts, like so:
|
You can configure service and bookmarks sections to be either "column" or "row" based layouts, like so:
|
||||||
@@ -500,9 +486,9 @@ logpath: /logfile/path
|
|||||||
|
|
||||||
By default, logs are sent both to `stdout` and to a file at the path specified. This can be changed by setting the `LOG_TARGETS` environment variable to one of `both` (default), `stdout` or `file`.
|
By default, logs are sent both to `stdout` and to a file at the path specified. This can be changed by setting the `LOG_TARGETS` environment variable to one of `both` (default), `stdout` or `file`.
|
||||||
|
|
||||||
## Show Container Stats
|
## Show Docker Stats
|
||||||
|
|
||||||
You can show all docker or proxmox stats expanded in `settings.yaml`:
|
You can show all docker stats expanded in `settings.yaml`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
showStats: true
|
showStats: true
|
||||||
|
|||||||
@@ -62,4 +62,3 @@ To ensure cohesiveness of various widgets, the following should be used as a gui
|
|||||||
- Minimize the number of API calls
|
- Minimize the number of API calls
|
||||||
- Avoid the use of custom proxy unless absolutely necessary
|
- Avoid the use of custom proxy unless absolutely necessary
|
||||||
- Widgets should be 'read-only', as in they should not make write changes using the relevant tool's API. Homepage widgets are designed to surface information, not to be a (usually worse) replacement for the tool itself.
|
- Widgets should be 'read-only', as in they should not make write changes using the relevant tool's API. Homepage widgets are designed to surface information, not to be a (usually worse) replacement for the tool itself.
|
||||||
- Widgets should not allow manually overriding the "refresh interval" setting, as misconfigured refresh intervals can easily lead to performance issues for users.
|
|
||||||
|
|||||||
@@ -16,5 +16,4 @@ widget:
|
|||||||
username: username
|
username: username
|
||||||
password: password
|
password: password
|
||||||
enableLeechProgress: true # optional, defaults to false
|
enableLeechProgress: true # optional, defaults to false
|
||||||
enableLeechSize: true # optional, defaults to false
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ The Unraid widget allows you to monitor the resources of an Unraid server.
|
|||||||
**Minimum Requirements:**
|
**Minimum Requirements:**
|
||||||
|
|
||||||
- Unraid 7.2 -or- Unraid Connect plugin 2025.08.19.1850
|
- Unraid 7.2 -or- Unraid Connect plugin 2025.08.19.1850
|
||||||
- API key with the **ADMIN** role: [Managing API Keys](https://docs.unraid.net/go/managing-api-keys)
|
- API key with the **GUEST** (read only) role: [Managing API Keys](https://docs.unraid.net/go/managing-api-keys)
|
||||||
|
|
||||||
The widget can display metrics for selected Unraid pools. If using one of the "pool" fields, you must also add the pool name to the settings.
|
The widget can display metrics for selected Unraid pools. If using one of the "pool" fields, you must also add the pool name to the settings.
|
||||||
|
|
||||||
**Allowed fields:** `["cpu","memoryPercent","memoryAvailable","memoryUsed","notifications","arrayFree","arrayUsedSpace","arrayUsedPercent","status","pool1UsedSpace","pool1FreeSpace","pool1UsedPercent","pool2UsedSpace","pool2FreeSpace","pool2UsedPercent","pool3UsedSpace","pool3FreeSpace","pool3UsedPercent","pool4UsedSpace","pool4FreeSpace","pool4UsedPercent"]`
|
**Allowed fields:** `["cpu","memoryPercent","memoryAvailable","memoryUsed","notifications","arrayFreeSpace","arrayUsedSpace","arrayUsedPercent","status","pool1UsedSpace","pool1FreeSpace","pool1UsedPercent","pool2UsedSpace","pool2FreeSpace","pool2UsedPercent","pool3UsedSpace","pool3FreeSpace","pool3UsedPercent","pool4UsedSpace","pool4FreeSpace","pool4UsedPercent"]`
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
widget:
|
widget:
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
---
|
|
||||||
title: Your Spotify
|
|
||||||
description: Your Spotify Widget Configuration
|
|
||||||
---
|
|
||||||
|
|
||||||
Learn more about [Your Spotify](https://github.com/Yooooomi/your_spotify).
|
|
||||||
|
|
||||||
Find your API key under `Settings > Account > Public token`, click `Generate` if not yet generated, copy key after
|
|
||||||
`?token=`.
|
|
||||||
|
|
||||||
Allowed fields: `["songs", "time", "artists"]`.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
widget:
|
|
||||||
type: yourspotify
|
|
||||||
url: http://your-spotify-server.host.or.ip # if using lsio image, add /api/
|
|
||||||
key: apikeyapikeyapikeyapikeyapikey
|
|
||||||
interval: month # optional, defaults to week
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Interval
|
|
||||||
|
|
||||||
Allowed values for `interval`: `day`, `week`, `month`, `year`, `all`.
|
|
||||||
|
|
||||||
!!! note
|
|
||||||
|
|
||||||
`interval` is different from predefined intervals you see in `Your Spotify`'s UI.
|
|
||||||
For example, `This week` in UI means _from the start of this week_, here `week` means _past 7 days_.
|
|
||||||
@@ -176,7 +176,6 @@ nav:
|
|||||||
- widgets/services/wgeasy.md
|
- widgets/services/wgeasy.md
|
||||||
- widgets/services/whatsupdocker.md
|
- widgets/services/whatsupdocker.md
|
||||||
- widgets/services/xteve.md
|
- widgets/services/xteve.md
|
||||||
- widgets/services/yourspotify.md
|
|
||||||
- widgets/services/zabbix.md
|
- widgets/services/zabbix.md
|
||||||
- "Information Widgets":
|
- "Information Widgets":
|
||||||
- widgets/info/index.md
|
- widgets/info/index.md
|
||||||
|
|||||||
20
package.json
20
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "homepage",
|
"name": "homepage",
|
||||||
"version": "1.6.0",
|
"version": "1.5.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
@@ -11,14 +11,14 @@
|
|||||||
"telemetry": "next telemetry disable"
|
"telemetry": "next telemetry disable"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^2.2.9",
|
"@headlessui/react": "^2.2.7",
|
||||||
"@kubernetes/client-node": "^1.0.0",
|
"@kubernetes/client-node": "^1.0.0",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"compare-versions": "^6.1.1",
|
"compare-versions": "^6.1.1",
|
||||||
"dockerode": "^4.0.7",
|
"dockerode": "^4.0.7",
|
||||||
"follow-redirects": "^1.15.11",
|
"follow-redirects": "^1.15.11",
|
||||||
"gamedig": "^5.3.2",
|
"gamedig": "^5.3.1",
|
||||||
"i18next": "^25.5.3",
|
"i18next": "^24.2.3",
|
||||||
"ical.js": "^2.1.0",
|
"ical.js": "^2.1.0",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"json-rpc-2.0": "^1.7.0",
|
"json-rpc-2.0": "^1.7.0",
|
||||||
@@ -28,8 +28,8 @@
|
|||||||
"next": "^15.5.2",
|
"next": "^15.5.2",
|
||||||
"next-i18next": "^12.1.0",
|
"next-i18next": "^12.1.0",
|
||||||
"ping": "^0.4.4",
|
"ping": "^0.4.4",
|
||||||
"pretty-bytes": "^7.1.0",
|
"pretty-bytes": "^6.1.1",
|
||||||
"raw-body": "^3.0.1",
|
"raw-body": "^3.0.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-i18next": "^15.5.3",
|
"react-i18next": "^15.5.3",
|
||||||
@@ -44,18 +44,18 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/forms": "^0.5.10",
|
"@tailwindcss/forms": "^0.5.10",
|
||||||
"@tailwindcss/postcss": "^4.1.14",
|
"@tailwindcss/postcss": "^4.0.9",
|
||||||
"eslint": "^9.25.1",
|
"eslint": "^9.25.1",
|
||||||
"eslint-config-next": "^15.2.4",
|
"eslint-config-next": "^15.2.4",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.1",
|
||||||
"eslint-plugin-import": "^2.32.0",
|
"eslint-plugin-import": "^2.32.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||||
"eslint-plugin-prettier": "^5.5.4",
|
"eslint-plugin-prettier": "^5.5.1",
|
||||||
"eslint-plugin-react": "^7.37.4",
|
"eslint-plugin-react": "^7.37.4",
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"prettier-plugin-organize-imports": "^4.3.0",
|
"prettier-plugin-organize-imports": "^4.1.0",
|
||||||
"tailwind-scrollbar": "^4.0.2",
|
"tailwind-scrollbar": "^4.0.2",
|
||||||
"tailwindcss": "^4.0.9",
|
"tailwindcss": "^4.0.9",
|
||||||
"typescript": "^5.7.3"
|
"typescript": "^5.7.3"
|
||||||
|
|||||||
663
pnpm-lock.yaml
generated
663
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1114,10 +1114,5 @@
|
|||||||
"num_success_latest": "Succeeding",
|
"num_success_latest": "Succeeding",
|
||||||
"num_failure_latest": "Failing",
|
"num_failure_latest": "Failing",
|
||||||
"bytes_added_30": "Bytes Added"
|
"bytes_added_30": "Bytes Added"
|
||||||
},
|
|
||||||
"yourspotify": {
|
|
||||||
"songs": "Songs",
|
|
||||||
"time": "Time",
|
|
||||||
"artists": "Artists"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1098,10 +1098,10 @@
|
|||||||
"NEW_DISK_TOO_SMALL": "New Disk Too Small",
|
"NEW_DISK_TOO_SMALL": "New Disk Too Small",
|
||||||
"NO_DATA_DISKS": "No Data Disks",
|
"NO_DATA_DISKS": "No Data Disks",
|
||||||
"notifications": "Notifications",
|
"notifications": "Notifications",
|
||||||
"status": "Status",
|
"status": "État",
|
||||||
"cpu": "CPU",
|
"cpu": "UCT",
|
||||||
"memoryUsed": "Memory Used",
|
"memoryUsed": "Mémoire Utilisé",
|
||||||
"memoryAvailable": "Memory Available",
|
"memoryAvailable": "Mémoire Disponible",
|
||||||
"arrayUsed": "Array Used",
|
"arrayUsed": "Array Used",
|
||||||
"arrayFree": "Array Free",
|
"arrayFree": "Array Free",
|
||||||
"poolUsed": "{{pool}} Used",
|
"poolUsed": "{{pool}} Used",
|
||||||
|
|||||||
@@ -61,16 +61,16 @@
|
|||||||
"wlan_devices": "WLAN Eszközök",
|
"wlan_devices": "WLAN Eszközök",
|
||||||
"lan_users": "LAN Felhasználók",
|
"lan_users": "LAN Felhasználók",
|
||||||
"wlan_users": "WLAN Felhasználók",
|
"wlan_users": "WLAN Felhasználók",
|
||||||
"up": "UP",
|
"up": "FUT",
|
||||||
"down": "ÁLL",
|
"down": "ÁLL",
|
||||||
"wait": "Please wait",
|
"wait": "Kérjük várjon",
|
||||||
"empty_data": "Az alrendszer állapota ismeretlen"
|
"empty_data": "Az alrendszer állapota ismeretlen"
|
||||||
},
|
},
|
||||||
"docker": {
|
"docker": {
|
||||||
"rx": "RX",
|
"rx": "RX",
|
||||||
"tx": "TX",
|
"tx": "TX",
|
||||||
"mem": "MEM",
|
"mem": "MEM",
|
||||||
"cpu": "CPU",
|
"cpu": "Processzor",
|
||||||
"running": "Futó",
|
"running": "Futó",
|
||||||
"offline": "Nem elérhető",
|
"offline": "Nem elérhető",
|
||||||
"error": "Hiba",
|
"error": "Hiba",
|
||||||
@@ -93,8 +93,8 @@
|
|||||||
"http_status": "HTTP állapot",
|
"http_status": "HTTP állapot",
|
||||||
"error": "Hiba",
|
"error": "Hiba",
|
||||||
"response": "Válasz",
|
"response": "Válasz",
|
||||||
"down": "Down",
|
"down": "Leállt",
|
||||||
"up": "Up",
|
"up": "Fut",
|
||||||
"not_available": "Nem elérhető"
|
"not_available": "Nem elérhető"
|
||||||
},
|
},
|
||||||
"emby": {
|
"emby": {
|
||||||
@@ -108,10 +108,10 @@
|
|||||||
"songs": "Zeneszám"
|
"songs": "Zeneszám"
|
||||||
},
|
},
|
||||||
"esphome": {
|
"esphome": {
|
||||||
"offline": "Offline",
|
"offline": "Nem elérhető",
|
||||||
"offline_alt": "Offline",
|
"offline_alt": "Nem elérhető",
|
||||||
"online": "Csatlakozva",
|
"online": "Csatlakozva",
|
||||||
"total": "Total",
|
"total": "Összes",
|
||||||
"unknown": "Ismeretlen"
|
"unknown": "Ismeretlen"
|
||||||
},
|
},
|
||||||
"evcc": {
|
"evcc": {
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
"unread": "Olvasatlan"
|
"unread": "Olvasatlan"
|
||||||
},
|
},
|
||||||
"fritzbox": {
|
"fritzbox": {
|
||||||
"connectionStatus": "Status",
|
"connectionStatus": "Státusz",
|
||||||
"connectionStatusUnconfigured": "Nem beállított",
|
"connectionStatusUnconfigured": "Nem beállított",
|
||||||
"connectionStatusConnecting": "Kapcsolódás",
|
"connectionStatusConnecting": "Kapcsolódás",
|
||||||
"connectionStatusAuthenticating": "Hitelesítés",
|
"connectionStatusAuthenticating": "Hitelesítés",
|
||||||
@@ -141,16 +141,16 @@
|
|||||||
"connectionStatusDisconnecting": "Kapcsolat bontása",
|
"connectionStatusDisconnecting": "Kapcsolat bontása",
|
||||||
"connectionStatusDisconnected": "Kapcsolat bontva",
|
"connectionStatusDisconnected": "Kapcsolat bontva",
|
||||||
"connectionStatusConnected": "Csatlakozva",
|
"connectionStatusConnected": "Csatlakozva",
|
||||||
"uptime": "Uptime",
|
"uptime": "Működési idő",
|
||||||
"maxDown": "Max let.",
|
"maxDown": "Max let.",
|
||||||
"maxUp": "Max felt.",
|
"maxUp": "Max felt.",
|
||||||
"down": "Down",
|
"down": "Leállt",
|
||||||
"up": "Up",
|
"up": "Fut",
|
||||||
"received": "Fogadott",
|
"received": "Fogadott",
|
||||||
"sent": "Küldött",
|
"sent": "Küldött",
|
||||||
"externalIPAddress": "Külső IP cím",
|
"externalIPAddress": "Külső IP cím",
|
||||||
"externalIPv6Address": "Ext. IPv6",
|
"externalIPv6Address": "Küls. IPv6",
|
||||||
"externalIPv6Prefix": "Ext. IPv6-Prefix"
|
"externalIPv6Prefix": "Küls. IPv6-Prefix"
|
||||||
},
|
},
|
||||||
"caddy": {
|
"caddy": {
|
||||||
"upstreams": "Upstreamek",
|
"upstreams": "Upstreamek",
|
||||||
@@ -168,17 +168,17 @@
|
|||||||
"passes": "Engedélyek"
|
"passes": "Engedélyek"
|
||||||
},
|
},
|
||||||
"tautulli": {
|
"tautulli": {
|
||||||
"playing": "Playing",
|
"playing": "Lejátszás",
|
||||||
"transcoding": "Transcoding",
|
"transcoding": "Transzkódolás",
|
||||||
"bitrate": "Bitrate",
|
"bitrate": "Bitráta",
|
||||||
"no_active": "No Active Streams",
|
"no_active": "Nincs aktív lejátszás",
|
||||||
"plex_connection_error": "Plex kapcsolat ellenőrzése"
|
"plex_connection_error": "Plex kapcsolat ellenőrzése"
|
||||||
},
|
},
|
||||||
"omada": {
|
"omada": {
|
||||||
"connectedAp": "Csatlakoztatott AP-k",
|
"connectedAp": "Csatlakoztatott AP-k",
|
||||||
"activeUser": "Aktív eszközök",
|
"activeUser": "Aktív eszközök",
|
||||||
"alerts": "Riasztások",
|
"alerts": "Riasztások",
|
||||||
"connectedGateways": "Connected gateways",
|
"connectedGateways": "Csatlakoztatott gateway-ek",
|
||||||
"connectedSwitches": "Csatlakoztatott switch-ek"
|
"connectedSwitches": "Csatlakoztatott switch-ek"
|
||||||
},
|
},
|
||||||
"nzbget": {
|
"nzbget": {
|
||||||
@@ -189,11 +189,11 @@
|
|||||||
"plex": {
|
"plex": {
|
||||||
"streams": "Aktív Stream-ek",
|
"streams": "Aktív Stream-ek",
|
||||||
"albums": "Albumok",
|
"albums": "Albumok",
|
||||||
"movies": "Movies",
|
"movies": "Filmek",
|
||||||
"tv": "TV műsorok"
|
"tv": "TV műsorok"
|
||||||
},
|
},
|
||||||
"sabnzbd": {
|
"sabnzbd": {
|
||||||
"rate": "Rate",
|
"rate": "Ráta",
|
||||||
"queue": "Sor",
|
"queue": "Sor",
|
||||||
"timeleft": "Hátralévő idő"
|
"timeleft": "Hátralévő idő"
|
||||||
},
|
},
|
||||||
@@ -233,34 +233,34 @@
|
|||||||
"cachemissbytes": "Gyorsítótárban Hibás Bitek"
|
"cachemissbytes": "Gyorsítótárban Hibás Bitek"
|
||||||
},
|
},
|
||||||
"downloadstation": {
|
"downloadstation": {
|
||||||
"download": "Download",
|
"download": "Letöltés",
|
||||||
"upload": "Upload",
|
"upload": "Feltöltés",
|
||||||
"leech": "Leech",
|
"leech": "Leech",
|
||||||
"seed": "Seed"
|
"seed": "Seed"
|
||||||
},
|
},
|
||||||
"sonarr": {
|
"sonarr": {
|
||||||
"wanted": "Keresett",
|
"wanted": "Keresett",
|
||||||
"queued": "Sorban áll",
|
"queued": "Sorban áll",
|
||||||
"series": "Series",
|
"series": "Sorozatok",
|
||||||
"queue": "Queue",
|
"queue": "Várólista",
|
||||||
"unknown": "Unknown"
|
"unknown": "Ismeretlen"
|
||||||
},
|
},
|
||||||
"radarr": {
|
"radarr": {
|
||||||
"wanted": "Wanted",
|
"wanted": "Keresett",
|
||||||
"missing": "Hiányzik",
|
"missing": "Hiányzik",
|
||||||
"queued": "Queued",
|
"queued": "Sorban áll",
|
||||||
"movies": "Movies",
|
"movies": "Filmek",
|
||||||
"queue": "Queue",
|
"queue": "Várólista",
|
||||||
"unknown": "Unknown"
|
"unknown": "Ismeretlen"
|
||||||
},
|
},
|
||||||
"lidarr": {
|
"lidarr": {
|
||||||
"wanted": "Wanted",
|
"wanted": "Keresett",
|
||||||
"queued": "Queued",
|
"queued": "Sorban áll",
|
||||||
"artists": "Előadók"
|
"artists": "Előadók"
|
||||||
},
|
},
|
||||||
"readarr": {
|
"readarr": {
|
||||||
"wanted": "Wanted",
|
"wanted": "Keresett",
|
||||||
"queued": "Queued",
|
"queued": "Sorban áll",
|
||||||
"books": "Könyvek"
|
"books": "Könyvek"
|
||||||
},
|
},
|
||||||
"bazarr": {
|
"bazarr": {
|
||||||
@@ -273,20 +273,20 @@
|
|||||||
"available": "Elérhető"
|
"available": "Elérhető"
|
||||||
},
|
},
|
||||||
"jellyseerr": {
|
"jellyseerr": {
|
||||||
"pending": "Pending",
|
"pending": "Függőben lévő",
|
||||||
"approved": "Approved",
|
"approved": "Jóváhagyott",
|
||||||
"available": "Available",
|
"available": "Elérhető",
|
||||||
"issues": "Open Issues"
|
"issues": "Nyitott problémák"
|
||||||
},
|
},
|
||||||
"overseerr": {
|
"overseerr": {
|
||||||
"pending": "Pending",
|
"pending": "Függőben lévő",
|
||||||
"processing": "Feldolgozás",
|
"processing": "Feldolgozás",
|
||||||
"approved": "Approved",
|
"approved": "Jóváhagyott",
|
||||||
"available": "Available"
|
"available": "Elérhető"
|
||||||
},
|
},
|
||||||
"netalertx": {
|
"netalertx": {
|
||||||
"total": "Total",
|
"total": "Összes",
|
||||||
"connected": "Connected",
|
"connected": "Csatlakoztatott",
|
||||||
"new_devices": "Új eszközök",
|
"new_devices": "Új eszközök",
|
||||||
"down_alerts": "Leállási riasztások"
|
"down_alerts": "Leállási riasztások"
|
||||||
},
|
},
|
||||||
@@ -297,26 +297,26 @@
|
|||||||
"gravity": "Gravitáció"
|
"gravity": "Gravitáció"
|
||||||
},
|
},
|
||||||
"adguard": {
|
"adguard": {
|
||||||
"queries": "Queries",
|
"queries": "Lekérdezések",
|
||||||
"blocked": "Blocked",
|
"blocked": "Blokkolt",
|
||||||
"filtered": "Szűrt",
|
"filtered": "Szűrt",
|
||||||
"latency": "Késleltetés"
|
"latency": "Késleltetés"
|
||||||
},
|
},
|
||||||
"speedtest": {
|
"speedtest": {
|
||||||
"upload": "Upload",
|
"upload": "Feltöltés",
|
||||||
"download": "Download",
|
"download": "Letöltés",
|
||||||
"ping": "Ping"
|
"ping": "Ping"
|
||||||
},
|
},
|
||||||
"portainer": {
|
"portainer": {
|
||||||
"running": "Running",
|
"running": "Folyamatban",
|
||||||
"stopped": "Megállított",
|
"stopped": "Megállított",
|
||||||
"total": "Total"
|
"total": "Összes"
|
||||||
},
|
},
|
||||||
"suwayomi": {
|
"suwayomi": {
|
||||||
"download": "Downloaded",
|
"download": "Letöltött",
|
||||||
"nondownload": "Nem Letöltött",
|
"nondownload": "Nem Letöltött",
|
||||||
"read": "Read",
|
"read": "Olvasott",
|
||||||
"unread": "Unread",
|
"unread": "Olvasatlan",
|
||||||
"downloadedread": "Letöltött & Olvasott",
|
"downloadedread": "Letöltött & Olvasott",
|
||||||
"downloadedunread": "Letöltött & Olvasatlan",
|
"downloadedunread": "Letöltött & Olvasatlan",
|
||||||
"nondownloadedread": "Nem Letöltött & Olvasatlan",
|
"nondownloadedread": "Nem Letöltött & Olvasatlan",
|
||||||
@@ -337,7 +337,7 @@
|
|||||||
"ago": "{{value}} Ezelőtt"
|
"ago": "{{value}} Ezelőtt"
|
||||||
},
|
},
|
||||||
"technitium": {
|
"technitium": {
|
||||||
"totalQueries": "Queries",
|
"totalQueries": "Lekérdezések",
|
||||||
"totalNoError": "Sikerek",
|
"totalNoError": "Sikerek",
|
||||||
"totalServerFailure": "Hibák",
|
"totalServerFailure": "Hibák",
|
||||||
"totalNxDomain": "NX Domainek",
|
"totalNxDomain": "NX Domainek",
|
||||||
@@ -345,12 +345,12 @@
|
|||||||
"totalAuthoritative": "Irányadó",
|
"totalAuthoritative": "Irányadó",
|
||||||
"totalRecursive": "Rekurzív",
|
"totalRecursive": "Rekurzív",
|
||||||
"totalCached": "Gyorsítótárazott",
|
"totalCached": "Gyorsítótárazott",
|
||||||
"totalBlocked": "Blocked",
|
"totalBlocked": "Blokkolt",
|
||||||
"totalDropped": "Eldobott",
|
"totalDropped": "Eldobott",
|
||||||
"totalClients": "Kliensek"
|
"totalClients": "Kliensek"
|
||||||
},
|
},
|
||||||
"tdarr": {
|
"tdarr": {
|
||||||
"queue": "Queue",
|
"queue": "Várólista",
|
||||||
"processed": "Feldolgozott",
|
"processed": "Feldolgozott",
|
||||||
"errored": "Hibás",
|
"errored": "Hibás",
|
||||||
"saved": "Mentett"
|
"saved": "Mentett"
|
||||||
@@ -361,19 +361,19 @@
|
|||||||
"middleware": "Közvetítő"
|
"middleware": "Közvetítő"
|
||||||
},
|
},
|
||||||
"trilium": {
|
"trilium": {
|
||||||
"version": "Version",
|
"version": "Verzió",
|
||||||
"notesCount": "Notes",
|
"notesCount": "Jegyzetek",
|
||||||
"dbSize": "Database Size",
|
"dbSize": "Adatbázis mérete",
|
||||||
"unknown": "Unknown"
|
"unknown": "Ismeretlen"
|
||||||
},
|
},
|
||||||
"navidrome": {
|
"navidrome": {
|
||||||
"nothing_streaming": "No Active Streams",
|
"nothing_streaming": "Nincs aktív lejátszás",
|
||||||
"please_wait": "Kérjük Várjon"
|
"please_wait": "Kérjük Várjon"
|
||||||
},
|
},
|
||||||
"npm": {
|
"npm": {
|
||||||
"enabled": "Bekapcsolva",
|
"enabled": "Bekapcsolva",
|
||||||
"disabled": "Kikapcsolva",
|
"disabled": "Kikapcsolva",
|
||||||
"total": "Total"
|
"total": "Összes"
|
||||||
},
|
},
|
||||||
"coinmarketcap": {
|
"coinmarketcap": {
|
||||||
"configure": "Állíts be egy vagy több Cryptovalutát a követéshez",
|
"configure": "Állíts be egy vagy több Cryptovalutát a követéshez",
|
||||||
@@ -384,73 +384,73 @@
|
|||||||
},
|
},
|
||||||
"gotify": {
|
"gotify": {
|
||||||
"apps": "Applikációk",
|
"apps": "Applikációk",
|
||||||
"clients": "Clients",
|
"clients": "Kliensek",
|
||||||
"messages": "Üzenetek"
|
"messages": "Üzenetek"
|
||||||
},
|
},
|
||||||
"prowlarr": {
|
"prowlarr": {
|
||||||
"enableIndexers": "Indexerek",
|
"enableIndexers": "Indexerek",
|
||||||
"numberOfGrabs": "Fogott",
|
"numberOfGrabs": "Fogott",
|
||||||
"numberOfQueries": "Queries",
|
"numberOfQueries": "Lekérdezések",
|
||||||
"numberOfFailGrabs": "Hibás fogások",
|
"numberOfFailGrabs": "Hibás fogások",
|
||||||
"numberOfFailQueries": "Hibás lekérdezések"
|
"numberOfFailQueries": "Hibás lekérdezések"
|
||||||
},
|
},
|
||||||
"jackett": {
|
"jackett": {
|
||||||
"configured": "Beállított",
|
"configured": "Beállított",
|
||||||
"errored": "Errored"
|
"errored": "Hibák"
|
||||||
},
|
},
|
||||||
"strelaysrv": {
|
"strelaysrv": {
|
||||||
"numActiveSessions": "Munkamenetek",
|
"numActiveSessions": "Munkamenetek",
|
||||||
"numConnections": "Csatlakozások",
|
"numConnections": "Csatlakozások",
|
||||||
"dataRelayed": "Átirányított",
|
"dataRelayed": "Átirányított",
|
||||||
"transferRate": "Rate"
|
"transferRate": "Ráta"
|
||||||
},
|
},
|
||||||
"mastodon": {
|
"mastodon": {
|
||||||
"user_count": "Users",
|
"user_count": "Felhasználók",
|
||||||
"status_count": "Posztok",
|
"status_count": "Posztok",
|
||||||
"domain_count": "Domainek"
|
"domain_count": "Domainek"
|
||||||
},
|
},
|
||||||
"medusa": {
|
"medusa": {
|
||||||
"wanted": "Wanted",
|
"wanted": "Keresett",
|
||||||
"queued": "Queued",
|
"queued": "Sorban áll",
|
||||||
"series": "Series"
|
"series": "Sorozatok"
|
||||||
},
|
},
|
||||||
"minecraft": {
|
"minecraft": {
|
||||||
"players": "Lejátszók",
|
"players": "Lejátszók",
|
||||||
"version": "Verzió",
|
"version": "Verzió",
|
||||||
"status": "Status",
|
"status": "Státusz",
|
||||||
"up": "Online",
|
"up": "Kapcsolódva",
|
||||||
"down": "Offline"
|
"down": "Nem elérhető"
|
||||||
},
|
},
|
||||||
"miniflux": {
|
"miniflux": {
|
||||||
"read": "Olvasott",
|
"read": "Olvasott",
|
||||||
"unread": "Unread"
|
"unread": "Olvasatlan"
|
||||||
},
|
},
|
||||||
"authentik": {
|
"authentik": {
|
||||||
"users": "Users",
|
"users": "Felhasználók",
|
||||||
"loginsLast24H": "Bejelentkezések (24 óra)",
|
"loginsLast24H": "Bejelentkezések (24 óra)",
|
||||||
"failedLoginsLast24H": "Sikertelen bejelentkezések (24h)"
|
"failedLoginsLast24H": "Sikertelen bejelentkezések (24h)"
|
||||||
},
|
},
|
||||||
"proxmox": {
|
"proxmox": {
|
||||||
"mem": "MEM",
|
"mem": "MEM",
|
||||||
"cpu": "CPU",
|
"cpu": "Processzor",
|
||||||
"lxc": "LXC-k",
|
"lxc": "LXC-k",
|
||||||
"vms": "VM-ek"
|
"vms": "VM-ek"
|
||||||
},
|
},
|
||||||
"glances": {
|
"glances": {
|
||||||
"cpu": "CPU",
|
"cpu": "Processzor",
|
||||||
"load": "Load",
|
"load": "Terhelés",
|
||||||
"wait": "Please wait",
|
"wait": "Kérem várjon",
|
||||||
"temp": "TEMP",
|
"temp": "HŐM",
|
||||||
"_temp": "Hőmérséklet",
|
"_temp": "Hőmérséklet",
|
||||||
"warn": "Figyelmeztet",
|
"warn": "Figyelmeztet",
|
||||||
"uptime": "UP",
|
"uptime": "FUT",
|
||||||
"total": "Total",
|
"total": "Összes",
|
||||||
"free": "Free",
|
"free": "Szabad",
|
||||||
"used": "Used",
|
"used": "Felhasznált",
|
||||||
"days": "d",
|
"days": "n",
|
||||||
"hours": "h",
|
"hours": "ó",
|
||||||
"crit": "Kritikus",
|
"crit": "Kritikus",
|
||||||
"read": "Read",
|
"read": "Olvasott",
|
||||||
"write": "Írás",
|
"write": "Írás",
|
||||||
"gpu": "GPU",
|
"gpu": "GPU",
|
||||||
"mem": "Memória",
|
"mem": "Memória",
|
||||||
@@ -471,57 +471,57 @@
|
|||||||
"1-day": "Többnyire napos",
|
"1-day": "Többnyire napos",
|
||||||
"1-night": "Többnyire derült",
|
"1-night": "Többnyire derült",
|
||||||
"2-day": "Részben felhős",
|
"2-day": "Részben felhős",
|
||||||
"2-night": "Partly Cloudy",
|
"2-night": "Részben felhős",
|
||||||
"3-day": "Felhős",
|
"3-day": "Felhős",
|
||||||
"3-night": "Cloudy",
|
"3-night": "Felhős",
|
||||||
"45-day": "Ködös",
|
"45-day": "Ködös",
|
||||||
"45-night": "Foggy",
|
"45-night": "Ködös",
|
||||||
"48-day": "Foggy",
|
"48-day": "Ködös",
|
||||||
"48-night": "Foggy",
|
"48-night": "Ködös",
|
||||||
"51-day": "Enyhe szitálás",
|
"51-day": "Enyhe szitálás",
|
||||||
"51-night": "Light Drizzle",
|
"51-night": "Enyhe szitálás",
|
||||||
"53-day": "Szitálás",
|
"53-day": "Szitálás",
|
||||||
"53-night": "Drizzle",
|
"53-night": "Szitálás",
|
||||||
"55-day": "Erős szitálás",
|
"55-day": "Erős szitálás",
|
||||||
"55-night": "Heavy Drizzle",
|
"55-night": "Erős szitálás",
|
||||||
"56-day": "Enyhe fagyos szitálás",
|
"56-day": "Enyhe fagyos szitálás",
|
||||||
"56-night": "Light Freezing Drizzle",
|
"56-night": "Enyhe fagyos szitálás",
|
||||||
"57-day": "Fagyos szitálás",
|
"57-day": "Fagyos szitálás",
|
||||||
"57-night": "Freezing Drizzle",
|
"57-night": "Fagyos szitálás",
|
||||||
"61-day": "Enyhe eső",
|
"61-day": "Enyhe eső",
|
||||||
"61-night": "Light Rain",
|
"61-night": "Enyhe eső",
|
||||||
"63-day": "Eső",
|
"63-day": "Eső",
|
||||||
"63-night": "Rain",
|
"63-night": "Eső",
|
||||||
"65-day": "Heves eső",
|
"65-day": "Heves eső",
|
||||||
"65-night": "Heavy Rain",
|
"65-night": "Heves eső",
|
||||||
"66-day": "Ónos eső",
|
"66-day": "Ónos eső",
|
||||||
"66-night": "Freezing Rain",
|
"66-night": "Ónos eső",
|
||||||
"67-day": "Freezing Rain",
|
"67-day": "Ónos eső",
|
||||||
"67-night": "Freezing Rain",
|
"67-night": "Ónos eső",
|
||||||
"71-day": "Enyhe havazás",
|
"71-day": "Enyhe havazás",
|
||||||
"71-night": "Light Snow",
|
"71-night": "Enyhe havazás",
|
||||||
"73-day": "Hó",
|
"73-day": "Hó",
|
||||||
"73-night": "Snow",
|
"73-night": "Havazás",
|
||||||
"75-day": "Erős havazás",
|
"75-day": "Erős havazás",
|
||||||
"75-night": "Heavy Snow",
|
"75-night": "Erős havazás",
|
||||||
"77-day": "Hódara",
|
"77-day": "Hódara",
|
||||||
"77-night": "Snow Grains",
|
"77-night": "Hódara",
|
||||||
"80-day": "Enyhe záporok",
|
"80-day": "Enyhe záporok",
|
||||||
"80-night": "Light Showers",
|
"80-night": "Enyhe záporok",
|
||||||
"81-day": "Záporok",
|
"81-day": "Záporok",
|
||||||
"81-night": "Showers",
|
"81-night": "Záporok",
|
||||||
"82-day": "Heves záporok",
|
"82-day": "Heves záporok",
|
||||||
"82-night": "Heavy Showers",
|
"82-night": "Heves záporok",
|
||||||
"85-day": "Hózáporok",
|
"85-day": "Hózáporok",
|
||||||
"85-night": "Snow Showers",
|
"85-night": "Hózáporok",
|
||||||
"86-day": "Snow Showers",
|
"86-day": "Hózáporok",
|
||||||
"86-night": "Snow Showers",
|
"86-night": "Hózáporok",
|
||||||
"95-day": "Zivatar",
|
"95-day": "Zivatar",
|
||||||
"95-night": "Thunderstorm",
|
"95-night": "Vihar",
|
||||||
"96-day": "Zivatar jégesővel",
|
"96-day": "Zivatar jégesővel",
|
||||||
"96-night": "Thunderstorm With Hail",
|
"96-night": "Zivatar jégesővel",
|
||||||
"99-day": "Thunderstorm With Hail",
|
"99-day": "Zivatar jégesővel",
|
||||||
"99-night": "Thunderstorm With Hail"
|
"99-night": "Zivatar jégesővel"
|
||||||
},
|
},
|
||||||
"homebridge": {
|
"homebridge": {
|
||||||
"available_update": "Rendszer",
|
"available_update": "Rendszer",
|
||||||
@@ -530,17 +530,17 @@
|
|||||||
"up_to_date": "Naprakész",
|
"up_to_date": "Naprakész",
|
||||||
"child_bridges": "Gyerek Hidak",
|
"child_bridges": "Gyerek Hidak",
|
||||||
"child_bridges_status": "{{ok}}/{{total}}",
|
"child_bridges_status": "{{ok}}/{{total}}",
|
||||||
"up": "Up",
|
"up": "Fut",
|
||||||
"pending": "Pending",
|
"pending": "Függőben lévő",
|
||||||
"down": "Down"
|
"down": "Leállt"
|
||||||
},
|
},
|
||||||
"healthchecks": {
|
"healthchecks": {
|
||||||
"new": "Új",
|
"new": "Új",
|
||||||
"up": "Up",
|
"up": "Fut",
|
||||||
"grace": "Türelmi idő alatt",
|
"grace": "Türelmi idő alatt",
|
||||||
"down": "Down",
|
"down": "Leállt",
|
||||||
"paused": "Szünetel",
|
"paused": "Szünetel",
|
||||||
"status": "Status",
|
"status": "Státusz",
|
||||||
"last_ping": "Legutóbbi Ping",
|
"last_ping": "Legutóbbi Ping",
|
||||||
"never": "Még nincsenek ping-ek"
|
"never": "Még nincsenek ping-ek"
|
||||||
},
|
},
|
||||||
@@ -550,21 +550,21 @@
|
|||||||
"containers_failed": "Sikertelen"
|
"containers_failed": "Sikertelen"
|
||||||
},
|
},
|
||||||
"autobrr": {
|
"autobrr": {
|
||||||
"approvedPushes": "Approved",
|
"approvedPushes": "Jóváhagyott",
|
||||||
"rejectedPushes": "Elutasított",
|
"rejectedPushes": "Elutasított",
|
||||||
"filters": "Szűrők",
|
"filters": "Szűrők",
|
||||||
"indexers": "Indexers"
|
"indexers": "Indexerek"
|
||||||
},
|
},
|
||||||
"tubearchivist": {
|
"tubearchivist": {
|
||||||
"downloads": "Queue",
|
"downloads": "Várólista",
|
||||||
"videos": "Videók",
|
"videos": "Videók",
|
||||||
"channels": "Csatornák",
|
"channels": "Csatornák",
|
||||||
"playlists": "Lejátszási listák"
|
"playlists": "Lejátszási listák"
|
||||||
},
|
},
|
||||||
"truenas": {
|
"truenas": {
|
||||||
"load": "Rendszerterhelés",
|
"load": "Rendszerterhelés",
|
||||||
"uptime": "Uptime",
|
"uptime": "Működési idő",
|
||||||
"alerts": "Alerts"
|
"alerts": "Figyelmeztetések"
|
||||||
},
|
},
|
||||||
"pyload": {
|
"pyload": {
|
||||||
"speed": "Sebesség",
|
"speed": "Sebesség",
|
||||||
|
|||||||
@@ -955,7 +955,7 @@
|
|||||||
"invalidConfiguration": "Invalid Configuration"
|
"invalidConfiguration": "Invalid Configuration"
|
||||||
},
|
},
|
||||||
"frigate": {
|
"frigate": {
|
||||||
"cameras": "Cameras",
|
"cameras": "Kamery",
|
||||||
"uptime": "Dostupnosť",
|
"uptime": "Dostupnosť",
|
||||||
"version": "Verzia"
|
"version": "Verzia"
|
||||||
},
|
},
|
||||||
@@ -966,7 +966,7 @@
|
|||||||
},
|
},
|
||||||
"zabbix": {
|
"zabbix": {
|
||||||
"unclassified": "Not classified",
|
"unclassified": "Not classified",
|
||||||
"information": "Information",
|
"information": "Informácie",
|
||||||
"warning": "Warning",
|
"warning": "Warning",
|
||||||
"average": "Average",
|
"average": "Average",
|
||||||
"high": "High",
|
"high": "High",
|
||||||
|
|||||||
@@ -1,47 +1,16 @@
|
|||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { useTranslation } from "next-i18next";
|
import { useTranslation } from "next-i18next";
|
||||||
import { useContext, useMemo } from "react";
|
|
||||||
|
|
||||||
import { BlockHighlightContext } from "./highlight-context";
|
export default function Block({ value, label }) {
|
||||||
|
|
||||||
import { evaluateHighlight, getHighlightClass } from "utils/highlights";
|
|
||||||
|
|
||||||
export default function Block({ value, label, field }) {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const highlightConfig = useContext(BlockHighlightContext);
|
|
||||||
|
|
||||||
const highlight = useMemo(() => {
|
|
||||||
if (!highlightConfig) return null;
|
|
||||||
const labels = Array.isArray(label) ? label : [label];
|
|
||||||
const candidates = [];
|
|
||||||
if (typeof field === "string") candidates.push(field);
|
|
||||||
for (const candidateLabel of labels) {
|
|
||||||
if (typeof candidateLabel === "string") candidates.push(candidateLabel);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const candidate of candidates) {
|
|
||||||
const result = evaluateHighlight(candidate, value, highlightConfig);
|
|
||||||
if (result) return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}, [field, label, value, highlightConfig]);
|
|
||||||
|
|
||||||
const highlightClass = useMemo(() => {
|
|
||||||
if (!highlight?.level) return undefined;
|
|
||||||
return getHighlightClass(highlight.level, highlightConfig);
|
|
||||||
}, [highlight, highlightConfig]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"bg-theme-200/50 dark:bg-theme-900/20 rounded-sm m-1 flex-1 flex flex-col items-center justify-center text-center p-1",
|
"bg-theme-200/50 dark:bg-theme-900/20 rounded-sm m-1 flex-1 flex flex-col items-center justify-center text-center p-1",
|
||||||
value === undefined ? "animate-pulse" : "",
|
value === undefined ? "animate-pulse" : "",
|
||||||
highlightClass,
|
|
||||||
"service-block",
|
"service-block",
|
||||||
)}
|
)}
|
||||||
data-highlight-level={highlight?.level}
|
|
||||||
data-highlight-source={highlight?.source}
|
|
||||||
>
|
>
|
||||||
<div className="font-thin text-sm">{value === undefined || value === null ? "-" : value}</div>
|
<div className="font-thin text-sm">{value === undefined || value === null ? "-" : value}</div>
|
||||||
<div className="font-bold text-xs uppercase">{t(label)}</div>
|
<div className="font-bold text-xs uppercase">{t(label)}</div>
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import { useContext, useMemo } from "react";
|
import { useContext } from "react";
|
||||||
import { SettingsContext } from "utils/contexts/settings";
|
import { SettingsContext } from "utils/contexts/settings";
|
||||||
|
|
||||||
import Error from "./error";
|
import Error from "./error";
|
||||||
import { BlockHighlightContext } from "./highlight-context";
|
|
||||||
|
|
||||||
import { buildHighlightConfig } from "utils/highlights";
|
|
||||||
|
|
||||||
const ALIASED_WIDGETS = {
|
const ALIASED_WIDGETS = {
|
||||||
pialert: "netalertx",
|
pialert: "netalertx",
|
||||||
@@ -14,11 +11,6 @@ const ALIASED_WIDGETS = {
|
|||||||
export default function Container({ error = false, children, service }) {
|
export default function Container({ error = false, children, service }) {
|
||||||
const { settings } = useContext(SettingsContext);
|
const { settings } = useContext(SettingsContext);
|
||||||
|
|
||||||
const highlightConfig = useMemo(
|
|
||||||
() => buildHighlightConfig(settings?.blockHighlights, service?.widget?.highlight, service?.widget?.type),
|
|
||||||
[settings?.blockHighlights, service?.widget?.highlight, service?.widget?.type],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
if (settings.hideErrors || service.widget.hide_errors) {
|
if (settings.hideErrors || service.widget.hide_errors) {
|
||||||
return null;
|
return null;
|
||||||
@@ -59,11 +51,6 @@ export default function Container({ error = false, children, service }) {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const content = <div className="relative flex flex-row w-full service-container">{visibleChildren}</div>;
|
|
||||||
|
|
||||||
if (!highlightConfig) {
|
return <div className="relative flex flex-row w-full service-container">{visibleChildren}</div>;
|
||||||
return content;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <BlockHighlightContext.Provider value={highlightConfig}>{content}</BlockHighlightContext.Provider>;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
import { createContext } from "react";
|
|
||||||
|
|
||||||
export const BlockHighlightContext = createContext(null);
|
|
||||||
@@ -113,7 +113,7 @@ export default function Widget({ options }) {
|
|||||||
<Resource
|
<Resource
|
||||||
icon={FaMemory}
|
icon={FaMemory}
|
||||||
value={t("common.bytes", {
|
value={t("common.bytes", {
|
||||||
value: data.mem.available,
|
value: data.mem.free,
|
||||||
maximumFractionDigits: 1,
|
maximumFractionDigits: 1,
|
||||||
binary: true,
|
binary: true,
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export default function QueueEntry({ title, activity, timeLeft, progress, size }) {
|
export default function QueueEntry({ title, activity, timeLeft, progress }) {
|
||||||
return (
|
return (
|
||||||
<div className="text-theme-700 dark:text-theme-200 relative h-5 rounded-md bg-theme-200/50 dark:bg-theme-900/20 m-1 px-1 flex">
|
<div className="text-theme-700 dark:text-theme-200 relative h-5 rounded-md bg-theme-200/50 dark:bg-theme-900/20 m-1 px-1 flex">
|
||||||
<div
|
<div
|
||||||
@@ -11,7 +11,6 @@ export default function QueueEntry({ title, activity, timeLeft, progress, size }
|
|||||||
<div className="absolute w-full whitespace-nowrap text-ellipsis overflow-hidden text-left">{title}</div>
|
<div className="absolute w-full whitespace-nowrap text-ellipsis overflow-hidden text-left">{title}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="self-center text-xs flex justify-end mr-1.5 pl-1 z-10 text-ellipsis overflow-hidden whitespace-nowrap">
|
<div className="self-center text-xs flex justify-end mr-1.5 pl-1 z-10 text-ellipsis overflow-hidden whitespace-nowrap">
|
||||||
{size && `${size} - `}
|
|
||||||
{timeLeft ? `${activity} - ${timeLeft}` : activity}
|
{timeLeft ? `${activity} - ${timeLeft}` : activity}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -417,7 +417,6 @@ function Home({ initialSettings }) {
|
|||||||
)}
|
)}
|
||||||
<meta name="msapplication-TileColor" content={themes[settings.color || "slate"][settings.theme || "dark"]} />
|
<meta name="msapplication-TileColor" content={themes[settings.color || "slate"][settings.theme || "dark"]} />
|
||||||
<meta name="theme-color" content={themes[settings.color || "slate"][settings.theme || "dark"]} />
|
<meta name="theme-color" content={themes[settings.color || "slate"][settings.theme || "dark"]} />
|
||||||
<meta name="color-scheme" content="dark light"></meta>
|
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<Script src="/api/config/custom.js" />
|
<Script src="/api/config/custom.js" />
|
||||||
@@ -540,48 +539,38 @@ export default function Wrapper({ initialSettings, fallback }) {
|
|||||||
html.classList.add(desiredThemeClass);
|
html.classList.add(desiredThemeClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (backgroundImage) {
|
// Remove any previously applied inline styles
|
||||||
const safeBackgroundImage = backgroundImage.replace(/'/g, "\\'");
|
body.style.backgroundImage = "";
|
||||||
body.style.backgroundImage = `linear-gradient(rgb(var(--bg-color) / ${opacity}), rgb(var(--bg-color) / ${opacity})), url('${safeBackgroundImage}')`;
|
body.style.backgroundColor = "";
|
||||||
body.style.backgroundSize = "cover";
|
body.style.backgroundAttachment = "";
|
||||||
body.style.backgroundPosition = "center";
|
|
||||||
body.style.backgroundAttachment = "fixed";
|
|
||||||
body.style.backgroundRepeat = "no-repeat";
|
|
||||||
body.style.backgroundColor = "";
|
|
||||||
} else {
|
|
||||||
body.style.backgroundImage = "none";
|
|
||||||
body.style.backgroundColor = "rgb(var(--bg-color))";
|
|
||||||
body.style.backgroundSize = "";
|
|
||||||
body.style.backgroundPosition = "";
|
|
||||||
body.style.backgroundAttachment = "";
|
|
||||||
body.style.backgroundRepeat = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
body.style.backgroundImage = "";
|
|
||||||
body.style.backgroundColor = "";
|
|
||||||
body.style.backgroundSize = "";
|
|
||||||
body.style.backgroundPosition = "";
|
|
||||||
body.style.backgroundAttachment = "";
|
|
||||||
body.style.backgroundRepeat = "";
|
|
||||||
};
|
|
||||||
}, [backgroundImage, opacity, theme, color, initialSettings.color]);
|
}, [backgroundImage, opacity, theme, color, initialSettings.color]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="page_wrapper" className="relative min-h-screen">
|
<>
|
||||||
<div
|
{backgroundImage && (
|
||||||
id="inner_wrapper"
|
<div
|
||||||
tabIndex="-1"
|
id="background"
|
||||||
className={classNames(
|
aria-hidden="true"
|
||||||
"w-full min-h-screen overflow-auto",
|
style={{
|
||||||
backgroundBlur &&
|
backgroundImage: `linear-gradient(rgb(var(--bg-color) / ${opacity}), rgb(var(--bg-color) / ${opacity})), url('${backgroundImage}')`,
|
||||||
`backdrop-blur${initialSettings.background.blur?.length ? `-${initialSettings.background.blur}` : ""}`,
|
}}
|
||||||
backgroundSaturate && `backdrop-saturate-${initialSettings.background.saturate}`,
|
/>
|
||||||
backgroundBrightness && `backdrop-brightness-${initialSettings.background.brightness}`,
|
)}
|
||||||
)}
|
<div id="page_wrapper" className="relative h-full">
|
||||||
>
|
<div
|
||||||
<Index initialSettings={initialSettings} fallback={fallback} />
|
id="inner_wrapper"
|
||||||
|
tabIndex="-1"
|
||||||
|
className={classNames(
|
||||||
|
"w-full h-full overflow-auto",
|
||||||
|
backgroundBlur &&
|
||||||
|
`backdrop-blur${initialSettings.background.blur?.length ? `-${initialSettings.background.blur}` : ""}`,
|
||||||
|
backgroundSaturate && `backdrop-saturate-${initialSettings.background.saturate}`,
|
||||||
|
backgroundBrightness && `backdrop-brightness-${initialSettings.background.brightness}`,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Index initialSettings={initialSettings} fallback={fallback} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,18 @@ body,
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
background-color: rgb(var(--bg-color));
|
||||||
|
}
|
||||||
|
|
||||||
|
#background {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 0;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-attachment: scroll;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
|
|||||||
@@ -254,7 +254,6 @@ export function cleanServiceGroups(groups) {
|
|||||||
// all widgets
|
// all widgets
|
||||||
fields,
|
fields,
|
||||||
hideErrors,
|
hideErrors,
|
||||||
highlight,
|
|
||||||
type,
|
type,
|
||||||
|
|
||||||
// azuredevops
|
// azuredevops
|
||||||
@@ -285,7 +284,6 @@ export function cleanServiceGroups(groups) {
|
|||||||
|
|
||||||
// deluge, qbittorrent
|
// deluge, qbittorrent
|
||||||
enableLeechProgress,
|
enableLeechProgress,
|
||||||
enableLeechSize,
|
|
||||||
|
|
||||||
// diskstation
|
// diskstation
|
||||||
volume,
|
volume,
|
||||||
@@ -410,9 +408,6 @@ export function cleanServiceGroups(groups) {
|
|||||||
// wgeasy
|
// wgeasy
|
||||||
threshold,
|
threshold,
|
||||||
|
|
||||||
// yourspotify
|
|
||||||
interval,
|
|
||||||
|
|
||||||
// technitium
|
// technitium
|
||||||
range,
|
range,
|
||||||
|
|
||||||
@@ -442,21 +437,6 @@ export function cleanServiceGroups(groups) {
|
|||||||
index,
|
index,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (highlight) {
|
|
||||||
let parsedHighlight = highlight;
|
|
||||||
if (typeof highlight === "string") {
|
|
||||||
try {
|
|
||||||
parsedHighlight = JSON.parse(highlight);
|
|
||||||
} catch (e) {
|
|
||||||
logger.error("Invalid highlight configuration detected in config for service '%s'", service.name);
|
|
||||||
parsedHighlight = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (parsedHighlight && typeof parsedHighlight === "object") {
|
|
||||||
widget.highlight = parsedHighlight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === "azuredevops") {
|
if (type === "azuredevops") {
|
||||||
if (userEmail) widget.userEmail = userEmail;
|
if (userEmail) widget.userEmail = userEmail;
|
||||||
if (repositoryId) widget.repositoryId = repositoryId;
|
if (repositoryId) widget.repositoryId = repositoryId;
|
||||||
@@ -510,7 +490,6 @@ export function cleanServiceGroups(groups) {
|
|||||||
}
|
}
|
||||||
if (["deluge", "qbittorrent"].includes(type)) {
|
if (["deluge", "qbittorrent"].includes(type)) {
|
||||||
if (enableLeechProgress !== undefined) widget.enableLeechProgress = JSON.parse(enableLeechProgress);
|
if (enableLeechProgress !== undefined) widget.enableLeechProgress = JSON.parse(enableLeechProgress);
|
||||||
if (enableLeechSize !== undefined) widget.enableLeechSize = JSON.parse(enableLeechSize);
|
|
||||||
}
|
}
|
||||||
if (["opnsense", "pfsense"].includes(type)) {
|
if (["opnsense", "pfsense"].includes(type)) {
|
||||||
if (wan) widget.wan = wan;
|
if (wan) widget.wan = wan;
|
||||||
@@ -644,11 +623,6 @@ export function cleanServiceGroups(groups) {
|
|||||||
if (pool3) widget.pool3 = pool3;
|
if (pool3) widget.pool3 = pool3;
|
||||||
if (pool4) widget.pool4 = pool4;
|
if (pool4) widget.pool4 = pool4;
|
||||||
}
|
}
|
||||||
if (type === "yourspotify") {
|
|
||||||
if (interval !== undefined) {
|
|
||||||
widget.interval = interval;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return widget;
|
return widget;
|
||||||
});
|
});
|
||||||
return cleanedService;
|
return cleanedService;
|
||||||
|
|||||||
@@ -1,257 +0,0 @@
|
|||||||
const DEFAULT_LEVEL_CLASSES = {
|
|
||||||
good: "bg-emerald-500/40 text-emerald-950 dark:bg-emerald-900/60 dark:text-emerald-400",
|
|
||||||
warn: "bg-amber-300/30 text-amber-900 dark:bg-amber-900/30 dark:text-amber-200",
|
|
||||||
danger: "bg-rose-700/45 text-rose-200 dark:bg-rose-950/70 dark:text-rose-400",
|
|
||||||
};
|
|
||||||
|
|
||||||
const normalizeFieldKeys = (fields, widgetType) => {
|
|
||||||
if (!fields || typeof fields !== "object") return {};
|
|
||||||
|
|
||||||
return Object.entries(fields).reduce((acc, [key, value]) => {
|
|
||||||
if (value === null || value === undefined) return acc;
|
|
||||||
if (typeof key !== "string") return acc;
|
|
||||||
const trimmedKey = key.trim();
|
|
||||||
if (trimmedKey === "") return acc;
|
|
||||||
|
|
||||||
acc[trimmedKey] = value;
|
|
||||||
|
|
||||||
if (widgetType && !trimmedKey.includes(".")) {
|
|
||||||
const namespacedKey = `${widgetType}.${trimmedKey}`;
|
|
||||||
if (!(namespacedKey in acc)) {
|
|
||||||
acc[namespacedKey] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const buildHighlightConfig = (globalConfig, widgetConfig, widgetType) => {
|
|
||||||
const levels = {
|
|
||||||
...DEFAULT_LEVEL_CLASSES,
|
|
||||||
...(globalConfig?.levels || {}),
|
|
||||||
...(widgetConfig?.levels || {}),
|
|
||||||
};
|
|
||||||
|
|
||||||
const { levels: _levels, ...fields } = widgetConfig || {};
|
|
||||||
const normalizedFields = normalizeFieldKeys(fields, widgetType);
|
|
||||||
|
|
||||||
const hasLevels = Object.values(levels).some(Boolean);
|
|
||||||
const hasFields = Object.keys(normalizedFields).length > 0;
|
|
||||||
|
|
||||||
if (!hasLevels && !hasFields) return null;
|
|
||||||
|
|
||||||
return { levels, fields: normalizedFields };
|
|
||||||
};
|
|
||||||
|
|
||||||
const NUMERIC_OPERATORS = {
|
|
||||||
gt: (value, target) => value > target,
|
|
||||||
gte: (value, target) => value >= target,
|
|
||||||
lt: (value, target) => value < target,
|
|
||||||
lte: (value, target) => value <= target,
|
|
||||||
eq: (value, target) => value === target,
|
|
||||||
ne: (value, target) => value !== target,
|
|
||||||
};
|
|
||||||
|
|
||||||
const STRING_OPERATORS = {
|
|
||||||
equals: (value, target, caseSensitive) =>
|
|
||||||
caseSensitive ? value === target : value.toLowerCase() === target.toLowerCase(),
|
|
||||||
includes: (value, target, caseSensitive) =>
|
|
||||||
caseSensitive ? value.includes(target) : value.toLowerCase().includes(target.toLowerCase()),
|
|
||||||
startsWith: (value, target, caseSensitive) =>
|
|
||||||
caseSensitive ? value.startsWith(target) : value.toLowerCase().startsWith(target.toLowerCase()),
|
|
||||||
endsWith: (value, target, caseSensitive) =>
|
|
||||||
caseSensitive ? value.endsWith(target) : value.toLowerCase().endsWith(target.toLowerCase()),
|
|
||||||
};
|
|
||||||
|
|
||||||
const toNumber = (value) => {
|
|
||||||
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
||||||
if (typeof value === "string" && value.trim()) {
|
|
||||||
const trimmed = value.trim();
|
|
||||||
const candidate = Number(trimmed);
|
|
||||||
if (!Number.isNaN(candidate)) return candidate;
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const parseNumericValue = (value) => {
|
|
||||||
if (value === null || value === undefined) return undefined;
|
|
||||||
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
||||||
|
|
||||||
if (typeof value === "string") {
|
|
||||||
const trimmed = value.trim();
|
|
||||||
if (!trimmed) return undefined;
|
|
||||||
|
|
||||||
const direct = Number(trimmed);
|
|
||||||
if (!Number.isNaN(direct)) return direct;
|
|
||||||
|
|
||||||
const compact = trimmed.replace(/\s+/g, "");
|
|
||||||
if (!compact || !/^[-+]?[0-9.,]+$/.test(compact)) return undefined;
|
|
||||||
|
|
||||||
const commaCount = (compact.match(/,/g) || []).length;
|
|
||||||
const dotCount = (compact.match(/\./g) || []).length;
|
|
||||||
|
|
||||||
if (commaCount && dotCount) {
|
|
||||||
const lastComma = compact.lastIndexOf(",");
|
|
||||||
const lastDot = compact.lastIndexOf(".");
|
|
||||||
if (lastComma > lastDot) {
|
|
||||||
const asDecimal = compact.replace(/\./g, "").replace(/,/g, ".");
|
|
||||||
const parsed = Number(asDecimal);
|
|
||||||
return Number.isNaN(parsed) ? undefined : parsed;
|
|
||||||
}
|
|
||||||
const asThousands = compact.replace(/,/g, "");
|
|
||||||
const parsed = Number(asThousands);
|
|
||||||
return Number.isNaN(parsed) ? undefined : parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (commaCount) {
|
|
||||||
const parts = compact.split(",");
|
|
||||||
if (commaCount === 1 && parts[1]?.length <= 2) {
|
|
||||||
const parsed = Number(compact.replace(",", "."));
|
|
||||||
if (!Number.isNaN(parsed)) return parsed;
|
|
||||||
}
|
|
||||||
const isGrouped = parts.length > 1 && parts.slice(1).every((part) => part.length === 3);
|
|
||||||
if (isGrouped) {
|
|
||||||
const parsed = Number(compact.replace(/,/g, ""));
|
|
||||||
if (!Number.isNaN(parsed)) return parsed;
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dotCount) {
|
|
||||||
const parts = compact.split(".");
|
|
||||||
if (dotCount === 1 && parts[1]?.length <= 2) {
|
|
||||||
const parsed = Number(compact);
|
|
||||||
if (!Number.isNaN(parsed)) return parsed;
|
|
||||||
}
|
|
||||||
const isGrouped = parts.length > 1 && parts.slice(1).every((part) => part.length === 3);
|
|
||||||
if (isGrouped) {
|
|
||||||
const parsed = Number(compact.replace(/\./g, ""));
|
|
||||||
if (!Number.isNaN(parsed)) return parsed;
|
|
||||||
}
|
|
||||||
const parsed = Number(compact);
|
|
||||||
return Number.isNaN(parsed) ? undefined : parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsed = Number(compact);
|
|
||||||
return Number.isNaN(parsed) ? undefined : parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value === "object" && value !== null && "props" in value) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const evaluateNumericRule = (value, rule) => {
|
|
||||||
if (!rule || typeof rule !== "object") return false;
|
|
||||||
const operator = rule.when && NUMERIC_OPERATORS[rule.when];
|
|
||||||
const numericValue = toNumber(rule.value);
|
|
||||||
if (operator && numericValue !== undefined) {
|
|
||||||
const passes = operator(value, numericValue);
|
|
||||||
return rule.negate ? !passes : passes;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rule.when === "between") {
|
|
||||||
const min = toNumber(rule.min ?? rule.value?.min);
|
|
||||||
const max = toNumber(rule.max ?? rule.value?.max);
|
|
||||||
if (min === undefined && max === undefined) return false;
|
|
||||||
const lowerBound = min ?? Number.NEGATIVE_INFINITY;
|
|
||||||
const upperBound = max ?? Number.POSITIVE_INFINITY;
|
|
||||||
const passes = value >= lowerBound && value <= upperBound;
|
|
||||||
return rule.negate ? !passes : passes;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rule.when === "outside") {
|
|
||||||
const min = toNumber(rule.min ?? rule.value?.min);
|
|
||||||
const max = toNumber(rule.max ?? rule.value?.max);
|
|
||||||
if (min === undefined && max === undefined) return false;
|
|
||||||
const passes = value < (min ?? Number.NEGATIVE_INFINITY) || value > (max ?? Number.POSITIVE_INFINITY);
|
|
||||||
return rule.negate ? !passes : passes;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const evaluateStringRule = (value, rule) => {
|
|
||||||
if (!rule || typeof rule !== "object") return false;
|
|
||||||
if (rule.when === "regex" && typeof rule.value === "string") {
|
|
||||||
try {
|
|
||||||
const flags = rule.flags || (rule.caseSensitive ? "" : "i");
|
|
||||||
const regex = new RegExp(rule.value, flags);
|
|
||||||
const passes = regex.test(value);
|
|
||||||
return rule.negate ? !passes : passes;
|
|
||||||
} catch (error) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const operator = rule.when && STRING_OPERATORS[rule.when];
|
|
||||||
if (!operator || typeof rule.value !== "string") return false;
|
|
||||||
const passes = operator(value, rule.value, Boolean(rule.caseSensitive));
|
|
||||||
return rule.negate ? !passes : passes;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ensureArray = (value) => {
|
|
||||||
if (Array.isArray(value)) return value;
|
|
||||||
if (value === undefined || value === null) return [];
|
|
||||||
return [value];
|
|
||||||
};
|
|
||||||
|
|
||||||
const findHighlightLevel = (ruleSet, numericValue, stringValue) => {
|
|
||||||
const { numeric, string } = ruleSet;
|
|
||||||
|
|
||||||
if (numeric && numericValue !== undefined) {
|
|
||||||
const numericRules = ensureArray(numeric);
|
|
||||||
const numericCandidates = Array.isArray(numericValue) ? numericValue : [numericValue];
|
|
||||||
for (const candidate of numericCandidates) {
|
|
||||||
for (const rule of numericRules) {
|
|
||||||
if (rule?.level && evaluateNumericRule(candidate, rule)) {
|
|
||||||
return { level: rule.level, source: "numeric", rule };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string && stringValue !== undefined) {
|
|
||||||
const stringRules = ensureArray(string);
|
|
||||||
for (const rule of stringRules) {
|
|
||||||
if (rule?.level && evaluateStringRule(stringValue, rule)) {
|
|
||||||
return { level: rule.level, source: "string", rule };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const evaluateHighlight = (fieldKey, value, highlightConfig) => {
|
|
||||||
if (!highlightConfig || !fieldKey) return null;
|
|
||||||
const { fields } = highlightConfig;
|
|
||||||
if (!fields || typeof fields !== "object") return null;
|
|
||||||
|
|
||||||
const ruleSet = fields[fieldKey];
|
|
||||||
if (!ruleSet) return null;
|
|
||||||
|
|
||||||
const numericValue = parseNumericValue(value);
|
|
||||||
let stringValue;
|
|
||||||
if (typeof value === "string") {
|
|
||||||
stringValue = value;
|
|
||||||
} else if (typeof value === "number" || typeof value === "bigint") {
|
|
||||||
stringValue = String(value);
|
|
||||||
} else if (typeof value === "boolean") {
|
|
||||||
stringValue = value ? "true" : "false";
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalizedString = typeof stringValue === "string" ? stringValue.trim() : stringValue;
|
|
||||||
|
|
||||||
return findHighlightLevel(ruleSet, numericValue, normalizedString);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getHighlightClass = (level, highlightConfig) => {
|
|
||||||
if (!level || !highlightConfig) return undefined;
|
|
||||||
return highlightConfig.levels?.[level];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getDefaultHighlightLevels = () => DEFAULT_LEVEL_CLASSES;
|
|
||||||
@@ -24,11 +24,10 @@ function buildResponse(plans) {
|
|||||||
|
|
||||||
plans.forEach((plan) => {
|
plans.forEach((plan) => {
|
||||||
const statuses = plan?.recentBackups?.status;
|
const statuses = plan?.recentBackups?.status;
|
||||||
// See https://github.com/garethgeorge/backrest/blob/4357295a17cb2e71639473c9929a060c4dd1b624/proto/v1/operations.proto#L78-L87
|
|
||||||
if (Array.isArray(statuses) && statuses.length > 0) {
|
if (Array.isArray(statuses) && statuses.length > 0) {
|
||||||
if (statuses[0] === "STATUS_SUCCESS") {
|
if (statuses[0] === "STATUS_SUCCESS") {
|
||||||
numSuccessLatest++;
|
numSuccessLatest++;
|
||||||
} else if (statuses[0] === "STATUS_ERROR") {
|
} else {
|
||||||
numFailureLatest++;
|
numFailureLatest++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,7 +150,6 @@ const components = {
|
|||||||
wgeasy: dynamic(() => import("./wgeasy/component")),
|
wgeasy: dynamic(() => import("./wgeasy/component")),
|
||||||
whatsupdocker: dynamic(() => import("./whatsupdocker/component")),
|
whatsupdocker: dynamic(() => import("./whatsupdocker/component")),
|
||||||
xteve: dynamic(() => import("./xteve/component")),
|
xteve: dynamic(() => import("./xteve/component")),
|
||||||
yourspotify: dynamic(() => import("./yourspotify/component")),
|
|
||||||
zabbix: dynamic(() => import("./zabbix/component")),
|
zabbix: dynamic(() => import("./zabbix/component")),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -205,14 +205,13 @@ export default function Component({ service }) {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { widget } = service;
|
const { widget } = service;
|
||||||
const enableNowPlaying = service.widget?.enableNowPlaying ?? true;
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: sessionsData,
|
data: sessionsData,
|
||||||
error: sessionsError,
|
error: sessionsError,
|
||||||
mutate: sessionMutate,
|
mutate: sessionMutate,
|
||||||
} = useWidgetAPI(widget, enableNowPlaying ? "Sessions" : "", {
|
} = useWidgetAPI(widget, "Sessions", {
|
||||||
refreshInterval: enableNowPlaying ? 5000 : undefined,
|
refreshInterval: 5000,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: countData, error: countError } = useWidgetAPI(widget, "Count", {
|
const { data: countData, error: countError } = useWidgetAPI(widget, "Count", {
|
||||||
@@ -240,12 +239,13 @@ export default function Component({ service }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const enableBlocks = service.widget?.enableBlocks;
|
const enableBlocks = service.widget?.enableBlocks;
|
||||||
|
const enableNowPlaying = service.widget?.enableNowPlaying ?? true;
|
||||||
const enableMediaControl = service.widget?.enableMediaControl !== false; // default is true
|
const enableMediaControl = service.widget?.enableMediaControl !== false; // default is true
|
||||||
const enableUser = !!service.widget?.enableUser; // default is false
|
const enableUser = !!service.widget?.enableUser; // default is false
|
||||||
const expandOneStreamToTwoRows = service.widget?.expandOneStreamToTwoRows !== false; // default is true
|
const expandOneStreamToTwoRows = service.widget?.expandOneStreamToTwoRows !== false; // default is true
|
||||||
const showEpisodeNumber = !!service.widget?.showEpisodeNumber; // default is false
|
const showEpisodeNumber = !!service.widget?.showEpisodeNumber; // default is false
|
||||||
|
|
||||||
if ((enableNowPlaying && !sessionsData) || !countData) {
|
if (!sessionsData || !countData) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{enableBlocks && <CountBlocks service={service} countData={null} />}
|
{enableBlocks && <CountBlocks service={service} countData={null} />}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export default function Component({ service }) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
setDataPoints((prevDataPoints) => {
|
setDataPoints((prevDataPoints) => {
|
||||||
const newDataPoints = [...prevDataPoints, { a: data.used, b: data.available }];
|
const newDataPoints = [...prevDataPoints, { a: data.used, b: data.free }];
|
||||||
if (newDataPoints.length > pointsLimit) {
|
if (newDataPoints.length > pointsLimit) {
|
||||||
newDataPoints.shift();
|
newDataPoints.shift();
|
||||||
}
|
}
|
||||||
@@ -67,10 +67,10 @@ export default function Component({ service }) {
|
|||||||
|
|
||||||
{data && !error && (
|
{data && !error && (
|
||||||
<Block position="bottom-3 left-3">
|
<Block position="bottom-3 left-3">
|
||||||
{data.available && chart && (
|
{data.free && chart && (
|
||||||
<div className="text-xs opacity-50">
|
<div className="text-xs opacity-50">
|
||||||
{t("common.bytes", {
|
{t("common.bytes", {
|
||||||
value: data.available,
|
value: data.free,
|
||||||
maximumFractionDigits: 1,
|
maximumFractionDigits: 1,
|
||||||
binary: true,
|
binary: true,
|
||||||
})}{" "}
|
})}{" "}
|
||||||
@@ -93,10 +93,10 @@ export default function Component({ service }) {
|
|||||||
|
|
||||||
{!chart && (
|
{!chart && (
|
||||||
<Block position="top-3 right-3">
|
<Block position="top-3 right-3">
|
||||||
{data.available && (
|
{data.free && (
|
||||||
<div className="text-xs opacity-50">
|
<div className="text-xs opacity-50">
|
||||||
{t("common.bytes", {
|
{t("common.bytes", {
|
||||||
value: data.available,
|
value: data.free,
|
||||||
maximumFractionDigits: 1,
|
maximumFractionDigits: 1,
|
||||||
binary: true,
|
binary: true,
|
||||||
})}{" "}
|
})}{" "}
|
||||||
|
|||||||
@@ -33,6 +33,14 @@ export default function Component({ service }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Container service={service}>
|
<Container service={service}>
|
||||||
|
<Block
|
||||||
|
label="myspeed.ping"
|
||||||
|
value={t("common.ms", {
|
||||||
|
value: data[0].ping,
|
||||||
|
style: "unit",
|
||||||
|
unit: "millisecond",
|
||||||
|
})}
|
||||||
|
/>
|
||||||
<Block
|
<Block
|
||||||
label="myspeed.download"
|
label="myspeed.download"
|
||||||
value={t("common.bitrate", {
|
value={t("common.bitrate", {
|
||||||
@@ -47,14 +55,6 @@ export default function Component({ service }) {
|
|||||||
decimals: 2,
|
decimals: 2,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
<Block
|
|
||||||
label="myspeed.ping"
|
|
||||||
value={t("common.ms", {
|
|
||||||
value: data[0].ping,
|
|
||||||
style: "unit",
|
|
||||||
unit: "millisecond",
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export default async function omadaProxyHandler(req, res) {
|
|||||||
|
|
||||||
const controllerVersionMajor = parseInt(controllerVersion.split(".")[0], 10);
|
const controllerVersionMajor = parseInt(controllerVersion.split(".")[0], 10);
|
||||||
|
|
||||||
if (![3, 4, 5, 6].includes(controllerVersionMajor)) {
|
if (![3, 4, 5].includes(controllerVersionMajor)) {
|
||||||
return res.status(500).json({ error: { message: "Error determining controller version", data } });
|
return res.status(500).json({ error: { message: "Error determining controller version", data } });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +80,6 @@ export default async function omadaProxyHandler(req, res) {
|
|||||||
loginUrl = `${url}/api/v2/login`;
|
loginUrl = `${url}/api/v2/login`;
|
||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
case 6:
|
|
||||||
loginUrl = `${url}/${cId}/api/v2/login`;
|
loginUrl = `${url}/${cId}/api/v2/login`;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -123,7 +122,6 @@ export default async function omadaProxyHandler(req, res) {
|
|||||||
sitesUrl = `${url}/api/v2/sites?token=${token}¤tPage=1¤tPageSize=1000`;
|
sitesUrl = `${url}/api/v2/sites?token=${token}¤tPage=1¤tPageSize=1000`;
|
||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
case 6:
|
|
||||||
sitesUrl = `${url}/${cId}/api/v2/sites?token=${token}¤tPage=1¤tPageSize=1000`;
|
sitesUrl = `${url}/${cId}/api/v2/sites?token=${token}¤tPage=1¤tPageSize=1000`;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -209,8 +207,8 @@ export default async function omadaProxyHandler(req, res) {
|
|||||||
connectedAp = siteResponseData.result.connectedAp;
|
connectedAp = siteResponseData.result.connectedAp;
|
||||||
activeUser = siteResponseData.result.activeUser;
|
activeUser = siteResponseData.result.activeUser;
|
||||||
alerts = siteResponseData.result.alerts;
|
alerts = siteResponseData.result.alerts;
|
||||||
} else if ([4, 5, 6].includes(controllerVersionMajor)) {
|
} else if (controllerVersionMajor === 4 || controllerVersionMajor === 5) {
|
||||||
const siteName = controllerVersionMajor > 4 ? site.id : site.key;
|
const siteName = controllerVersionMajor === 5 ? site.id : site.key;
|
||||||
const siteStatsUrl =
|
const siteStatsUrl =
|
||||||
controllerVersionMajor === 4
|
controllerVersionMajor === 4
|
||||||
? `${url}/api/v2/sites/${siteName}/dashboard/overviewDiagram?token=${token}¤tPage=1¤tPageSize=1000`
|
? `${url}/api/v2/sites/${siteName}/dashboard/overviewDiagram?token=${token}¤tPage=1¤tPageSize=1000`
|
||||||
|
|||||||
@@ -80,11 +80,6 @@ export default function Component({ service }) {
|
|||||||
timeLeft={t("common.duration", { value: queueEntry.eta })}
|
timeLeft={t("common.duration", { value: queueEntry.eta })}
|
||||||
title={queueEntry.name}
|
title={queueEntry.name}
|
||||||
activity={queueEntry.state}
|
activity={queueEntry.state}
|
||||||
size={
|
|
||||||
widget?.enableLeechSize
|
|
||||||
? t("common.bbytes", { value: queueEntry.size, maximumFractionDigits: 1 })
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
key={`${queueEntry.name}-${queueEntry.amount_left}`}
|
key={`${queueEntry.name}-${queueEntry.amount_left}`}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ export default function Component({ service }) {
|
|||||||
let status;
|
let status;
|
||||||
let uptime = 0;
|
let uptime = 0;
|
||||||
let logIndex = 0;
|
let logIndex = 0;
|
||||||
const hasLogs = Array.isArray(monitor.logs) && monitor.logs.length > 0;
|
|
||||||
|
|
||||||
switch (monitor.status) {
|
switch (monitor.status) {
|
||||||
case 0:
|
case 0:
|
||||||
@@ -63,7 +62,7 @@ export default function Component({ service }) {
|
|||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
status = t("uptimerobot.up");
|
status = t("uptimerobot.up");
|
||||||
uptime = t("common.duration", { value: hasLogs ? monitor.logs[0].duration : 0 });
|
uptime = t("common.duration", { value: monitor.logs[0].duration });
|
||||||
logIndex = 1;
|
logIndex = 1;
|
||||||
break;
|
break;
|
||||||
case 8:
|
case 8:
|
||||||
@@ -77,14 +76,14 @@ export default function Component({ service }) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastDown = hasLogs ? new Date(monitor.logs[logIndex].datetime * 1000).toLocaleString() : "";
|
const lastDown = new Date(monitor.logs[logIndex].datetime * 1000).toLocaleString();
|
||||||
const downDuration = t("common.duration", { value: hasLogs ? monitor.logs[logIndex].duration : 0 });
|
const downDuration = t("common.duration", { value: monitor.logs[logIndex].duration });
|
||||||
const hideDown = !hasLogs || (logIndex === 1 && monitor.logs[logIndex].type !== 1);
|
const hideDown = logIndex === 1 && monitor.logs[logIndex].type !== 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container service={service}>
|
<Container service={service}>
|
||||||
<Block label="uptimerobot.status" value={status} />
|
<Block label="uptimerobot.status" value={status} />
|
||||||
{hasLogs && <Block label="uptimerobot.uptime" value={uptime} />}
|
<Block label="uptimerobot.uptime" value={uptime} />
|
||||||
{!hideDown && <Block label="uptimerobot.lastDown" value={lastDown} />}
|
{!hideDown && <Block label="uptimerobot.lastDown" value={lastDown} />}
|
||||||
{!hideDown && <Block label="uptimerobot.downDuration" value={downDuration} />}
|
{!hideDown && <Block label="uptimerobot.downDuration" value={downDuration} />}
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@@ -141,7 +141,6 @@ import watchtower from "./watchtower/widget";
|
|||||||
import wgeasy from "./wgeasy/widget";
|
import wgeasy from "./wgeasy/widget";
|
||||||
import whatsupdocker from "./whatsupdocker/widget";
|
import whatsupdocker from "./whatsupdocker/widget";
|
||||||
import xteve from "./xteve/widget";
|
import xteve from "./xteve/widget";
|
||||||
import yourspotify from "./yourspotify/widget";
|
|
||||||
import zabbix from "./zabbix/widget";
|
import zabbix from "./zabbix/widget";
|
||||||
|
|
||||||
const widgets = {
|
const widgets = {
|
||||||
@@ -292,7 +291,6 @@ const widgets = {
|
|||||||
wgeasy,
|
wgeasy,
|
||||||
whatsupdocker,
|
whatsupdocker,
|
||||||
xteve,
|
xteve,
|
||||||
yourspotify,
|
|
||||||
zabbix,
|
zabbix,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
import Block from "components/services/widget/block";
|
|
||||||
import Container from "components/services/widget/container";
|
|
||||||
import { useTranslation } from "next-i18next";
|
|
||||||
import { useMemo } from "react";
|
|
||||||
|
|
||||||
import useWidgetAPI from "utils/proxy/use-widget-api";
|
|
||||||
|
|
||||||
function getStartDate(interval) {
|
|
||||||
const d = new Date();
|
|
||||||
switch (interval) {
|
|
||||||
case "day":
|
|
||||||
d.setDate(d.getDate() - 1);
|
|
||||||
break;
|
|
||||||
case "week":
|
|
||||||
d.setDate(d.getDate() - 7);
|
|
||||||
break;
|
|
||||||
case "month":
|
|
||||||
d.setMonth(d.getMonth() - 1);
|
|
||||||
break;
|
|
||||||
case "year":
|
|
||||||
d.setFullYear(d.getFullYear() - 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return d.toISOString();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Component({ service }) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { widget } = service;
|
|
||||||
|
|
||||||
const interval = widget?.interval || "week";
|
|
||||||
|
|
||||||
const date = useMemo(() => {
|
|
||||||
return interval === "all" ? "2006-04-23T00:00:00.000Z" : getStartDate(interval);
|
|
||||||
}, [interval]);
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
timeSplit: "all",
|
|
||||||
start: date,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data: songsListened, error: songsError } = useWidgetAPI(widget, "songs", params);
|
|
||||||
const { data: timeListened, error: timeError } = useWidgetAPI(widget, "time", params);
|
|
||||||
const { data: artistsListened, error: artistsError } = useWidgetAPI(widget, "artists", params);
|
|
||||||
|
|
||||||
if (songsError || timeError || artistsError) {
|
|
||||||
return <Container service={service} error={songsError ?? timeError ?? artistsError} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNaN(songsListened) || isNaN(timeListened) || isNaN(artistsListened)) {
|
|
||||||
return (
|
|
||||||
<Container service={service}>
|
|
||||||
<Block label="yourspotify.songs" />
|
|
||||||
<Block label="yourspotify.time" />
|
|
||||||
<Block label="yourspotify.artists" />
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Container service={service}>
|
|
||||||
<Block label="yourspotify.songs" value={t("common.number", { value: songsListened })} />
|
|
||||||
|
|
||||||
<Block
|
|
||||||
label="yourspotify.time"
|
|
||||||
value={t(
|
|
||||||
timeListened > 0 ? "common.duration" : "common.number", // Display 0 if duration is 0
|
|
||||||
{
|
|
||||||
value: timeListened / 1000,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Block label="yourspotify.artists" value={t("common.number", { value: artistsListened })} />
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import { asJson } from "utils/proxy/api-helpers";
|
|
||||||
import genericProxyHandler from "utils/proxy/handlers/generic";
|
|
||||||
|
|
||||||
const widget = {
|
|
||||||
api: "{url}/spotify/{endpoint}?token={key}",
|
|
||||||
proxyHandler: genericProxyHandler,
|
|
||||||
|
|
||||||
mappings: {
|
|
||||||
songs: {
|
|
||||||
endpoint: "songs_per",
|
|
||||||
params: ["start", "timeSplit"],
|
|
||||||
map: (data) => asJson(data)[0]?.count || 0,
|
|
||||||
},
|
|
||||||
time: {
|
|
||||||
endpoint: "time_per",
|
|
||||||
params: ["start", "timeSplit"],
|
|
||||||
map: (data) => asJson(data)[0]?.count || 0,
|
|
||||||
},
|
|
||||||
artists: {
|
|
||||||
endpoint: "different_artists_per",
|
|
||||||
params: ["start", "timeSplit"],
|
|
||||||
map: (data) => asJson(data)[0]?.artists?.length || 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default widget;
|
|
||||||
Reference in New Issue
Block a user