mirror of
https://github.com/gethomepage/homepage.git
synced 2025-12-05 21:47:48 +01:00
Compare commits
18 Commits
7a7d93519d
...
v1.6.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
438543d8cd | ||
|
|
5a350cc9ce | ||
|
|
9b5275a854 | ||
|
|
e623196ac0 | ||
|
|
973b1f7aaf | ||
|
|
81a322cc99 | ||
|
|
36e82a8b90 | ||
|
|
1383e22acd | ||
|
|
a756a01d63 | ||
|
|
937efc9f1b | ||
|
|
fe6f32f072 | ||
|
|
226603770c | ||
|
|
2f48d21bfd | ||
|
|
4457baffa5 | ||
|
|
91d12c401c | ||
|
|
3f8da51aeb | ||
|
|
837717461f | ||
|
|
f7a6b7dbf4 |
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@v5
|
uses: actions/setup-node@v6
|
||||||
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@v5
|
uses: actions/setup-node@v6
|
||||||
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-docker-stats).
|
Also see the settings for [show docker stats](settings.md#show-container-stats).
|
||||||
|
|||||||
@@ -118,6 +118,47 @@ 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,6 +109,20 @@ 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:
|
||||||
@@ -486,9 +500,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 Docker Stats
|
## Show Container Stats
|
||||||
|
|
||||||
You can show all docker stats expanded in `settings.yaml`:
|
You can show all docker or proxmox stats expanded in `settings.yaml`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
showStats: true
|
showStats: true
|
||||||
|
|||||||
@@ -62,3 +62,4 @@ 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.
|
||||||
|
|||||||
@@ -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 **GUEST** (read only) role: [Managing API Keys](https://docs.unraid.net/go/managing-api-keys)
|
- API key with the **ADMIN** 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","arrayFreeSpace","arrayUsedSpace","arrayUsedPercent","status","pool1UsedSpace","pool1FreeSpace","pool1UsedPercent","pool2UsedSpace","pool2FreeSpace","pool2UsedPercent","pool3UsedSpace","pool3FreeSpace","pool3UsedPercent","pool4UsedSpace","pool4FreeSpace","pool4UsedPercent"]`
|
**Allowed fields:** `["cpu","memoryPercent","memoryAvailable","memoryUsed","notifications","arrayFree","arrayUsedSpace","arrayUsedPercent","status","pool1UsedSpace","pool1FreeSpace","pool1UsedPercent","pool2UsedSpace","pool2FreeSpace","pool2UsedPercent","pool3UsedSpace","pool3FreeSpace","pool3UsedPercent","pool4UsedSpace","pool4FreeSpace","pool4UsedPercent"]`
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
widget:
|
widget:
|
||||||
|
|||||||
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "homepage",
|
"name": "homepage",
|
||||||
"version": "1.5.0",
|
"version": "1.6.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
"telemetry": "next telemetry disable"
|
"telemetry": "next telemetry disable"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^2.2.7",
|
"@headlessui/react": "^2.2.9",
|
||||||
"@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",
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
"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": "^6.1.1",
|
"pretty-bytes": "^7.1.0",
|
||||||
"raw-body": "^3.0.1",
|
"raw-body": "^3.0.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
"@tailwindcss/postcss": "^4.1.14",
|
"@tailwindcss/postcss": "^4.1.14",
|
||||||
"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.1",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"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.4",
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
"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.1.0",
|
"prettier-plugin-organize-imports": "^4.3.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"
|
||||||
|
|||||||
111
pnpm-lock.yaml
generated
111
pnpm-lock.yaml
generated
@@ -9,8 +9,8 @@ importers:
|
|||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@headlessui/react':
|
'@headlessui/react':
|
||||||
specifier: ^2.2.7
|
specifier: ^2.2.9
|
||||||
version: 2.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 2.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@kubernetes/client-node':
|
'@kubernetes/client-node':
|
||||||
specifier: ^1.0.0
|
specifier: ^1.0.0
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
@@ -60,8 +60,8 @@ importers:
|
|||||||
specifier: ^0.4.4
|
specifier: ^0.4.4
|
||||||
version: 0.4.4
|
version: 0.4.4
|
||||||
pretty-bytes:
|
pretty-bytes:
|
||||||
specifier: ^6.1.1
|
specifier: ^7.1.0
|
||||||
version: 6.1.1
|
version: 7.1.0
|
||||||
raw-body:
|
raw-body:
|
||||||
specifier: ^3.0.1
|
specifier: ^3.0.1
|
||||||
version: 3.0.1
|
version: 3.0.1
|
||||||
@@ -112,8 +112,8 @@ importers:
|
|||||||
specifier: ^15.2.4
|
specifier: ^15.2.4
|
||||||
version: 15.2.4(eslint@9.25.1(jiti@2.6.1))(typescript@5.7.3)
|
version: 15.2.4(eslint@9.25.1(jiti@2.6.1))(typescript@5.7.3)
|
||||||
eslint-config-prettier:
|
eslint-config-prettier:
|
||||||
specifier: ^10.1.1
|
specifier: ^10.1.8
|
||||||
version: 10.1.1(eslint@9.25.1(jiti@2.6.1))
|
version: 10.1.8(eslint@9.25.1(jiti@2.6.1))
|
||||||
eslint-plugin-import:
|
eslint-plugin-import:
|
||||||
specifier: ^2.32.0
|
specifier: ^2.32.0
|
||||||
version: 2.32.0(@typescript-eslint/parser@8.29.0(eslint@9.25.1(jiti@2.6.1))(typescript@5.7.3))(eslint-import-resolver-typescript@3.10.0)(eslint@9.25.1(jiti@2.6.1))
|
version: 2.32.0(@typescript-eslint/parser@8.29.0(eslint@9.25.1(jiti@2.6.1))(typescript@5.7.3))(eslint-import-resolver-typescript@3.10.0)(eslint@9.25.1(jiti@2.6.1))
|
||||||
@@ -122,7 +122,7 @@ importers:
|
|||||||
version: 6.10.2(eslint@9.25.1(jiti@2.6.1))
|
version: 6.10.2(eslint@9.25.1(jiti@2.6.1))
|
||||||
eslint-plugin-prettier:
|
eslint-plugin-prettier:
|
||||||
specifier: ^5.5.4
|
specifier: ^5.5.4
|
||||||
version: 5.5.4(eslint-config-prettier@10.1.1(eslint@9.25.1(jiti@2.6.1)))(eslint@9.25.1(jiti@2.6.1))(prettier@3.6.2)
|
version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.25.1(jiti@2.6.1)))(eslint@9.25.1(jiti@2.6.1))(prettier@3.6.2)
|
||||||
eslint-plugin-react:
|
eslint-plugin-react:
|
||||||
specifier: ^7.37.4
|
specifier: ^7.37.4
|
||||||
version: 7.37.4(eslint@9.25.1(jiti@2.6.1))
|
version: 7.37.4(eslint@9.25.1(jiti@2.6.1))
|
||||||
@@ -136,8 +136,8 @@ importers:
|
|||||||
specifier: ^3.6.2
|
specifier: ^3.6.2
|
||||||
version: 3.6.2
|
version: 3.6.2
|
||||||
prettier-plugin-organize-imports:
|
prettier-plugin-organize-imports:
|
||||||
specifier: ^4.1.0
|
specifier: ^4.3.0
|
||||||
version: 4.1.0(prettier@3.6.2)(typescript@5.7.3)
|
version: 4.3.0(prettier@3.6.2)(typescript@5.7.3)
|
||||||
tailwind-scrollbar:
|
tailwind-scrollbar:
|
||||||
specifier: ^4.0.2
|
specifier: ^4.0.2
|
||||||
version: 4.0.2(react@18.3.1)(tailwindcss@4.0.9)
|
version: 4.0.2(react@18.3.1)(tailwindcss@4.0.9)
|
||||||
@@ -257,8 +257,8 @@ packages:
|
|||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@headlessui/react@2.2.7':
|
'@headlessui/react@2.2.9':
|
||||||
resolution: {integrity: sha512-WKdTymY8Y49H8/gUc/lIyYK1M+/6dq0Iywh4zTZVAaiTDprRfioxSgD0wnXTQTBpjpGJuTL1NO/mqEvc//5SSg==}
|
resolution: {integrity: sha512-Mb+Un58gwBn0/yWZfyrCh0TJyurtT+dETj7YHleylHk5od3dv2XqETPGWMyQ5/7sYN7oWdyM1u9MvC0OC8UmzQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^18 || ^19 || ^19.0.0-rc
|
react: ^18 || ^19 || ^19.0.0-rc
|
||||||
@@ -559,14 +559,14 @@ packages:
|
|||||||
'@protobufjs/utf8@1.1.0':
|
'@protobufjs/utf8@1.1.0':
|
||||||
resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==}
|
resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==}
|
||||||
|
|
||||||
'@react-aria/focus@3.21.1':
|
'@react-aria/focus@3.21.2':
|
||||||
resolution: {integrity: sha512-hmH1IhHlcQ2lSIxmki1biWzMbGgnhdxJUM0MFfzc71Rv6YAzhlx4kX3GYn4VNcjCeb6cdPv4RZ5vunV4kgMZYQ==}
|
resolution: {integrity: sha512-JWaCR7wJVggj+ldmM/cb/DXFg47CXR55lznJhZBh4XVqJjMKwaOOqpT5vNN7kpC1wUpXicGNuDnJDN1S/+6dhQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
||||||
react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
||||||
|
|
||||||
'@react-aria/interactions@3.25.5':
|
'@react-aria/interactions@3.25.6':
|
||||||
resolution: {integrity: sha512-EweYHOEvMwef/wsiEqV73KurX/OqnmbzKQa2fLxdULbec5+yDj6wVGaRHIzM4NiijIDe+bldEl5DG05CAKOAHA==}
|
resolution: {integrity: sha512-5UgwZmohpixwNMVkMvn9K1ceJe6TzlRlAfuYoQDUuOkk62/JVJNDLAPKIf5YMRc7d2B0rmfgaZLMtbREb0Zvkw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
||||||
react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
||||||
@@ -577,8 +577,8 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
||||||
|
|
||||||
'@react-aria/utils@3.30.1':
|
'@react-aria/utils@3.31.0':
|
||||||
resolution: {integrity: sha512-zETcbDd6Vf9GbLndO6RiWJadIZsBU2MMm23rBACXLmpRztkrIqPEb2RVdlLaq1+GklDx0Ii6PfveVjx+8S5U6A==}
|
resolution: {integrity: sha512-ABOzCsZrWzf78ysswmguJbx3McQUja7yeGj6/vZo4JVsZNlxAN+E9rs381ExBRI0KzVo6iBTeX5De8eMZPJXig==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
||||||
react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
||||||
@@ -591,8 +591,8 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
||||||
|
|
||||||
'@react-types/shared@3.32.0':
|
'@react-types/shared@3.32.1':
|
||||||
resolution: {integrity: sha512-t+cligIJsZYFMSPFMvsJMjzlzde06tZMOIOFa1OV5Z0BcMowrb2g4mB57j/9nP28iJIRYn10xCniQts+qadrqQ==}
|
resolution: {integrity: sha512-famxyD5emrGGpFuUlgOP6fVW2h/ZaF405G5KDi3zPHzyjAWys/8W6NAVJtNbkCkhedmvL0xOhvt8feGXyXaw5w==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
||||||
|
|
||||||
@@ -1411,8 +1411,8 @@ packages:
|
|||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
eslint-config-prettier@10.1.1:
|
eslint-config-prettier@10.1.8:
|
||||||
resolution: {integrity: sha512-4EQQr6wXwS+ZJSzaR5ZCrYgLxqvUjdXctaEtBqHcbkW944B1NQyO4qpdHQbXBONfwxXdkAY81HH4+LUfrg+zPw==}
|
resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: '>=7.0.0'
|
eslint: '>=7.0.0'
|
||||||
@@ -2361,12 +2361,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
|
resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
|
|
||||||
prettier-plugin-organize-imports@4.1.0:
|
prettier-plugin-organize-imports@4.3.0:
|
||||||
resolution: {integrity: sha512-5aWRdCgv645xaa58X8lOxzZoiHAldAPChljr/MT0crXVOWTZ+Svl4hIWlz+niYSlO6ikE5UXkN1JrRvIP2ut0A==}
|
resolution: {integrity: sha512-FxFz0qFhyBsGdIsb697f/EkvHzi5SZOhWAjxcx2dLt+Q532bAlhswcXGYB1yzjZ69kW8UoadFBw7TyNwlq96Iw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
prettier: '>=2.0'
|
prettier: '>=2.0'
|
||||||
typescript: '>=2.9'
|
typescript: '>=2.9'
|
||||||
vue-tsc: ^2.1.0
|
vue-tsc: ^2.1.0 || 3
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
vue-tsc:
|
vue-tsc:
|
||||||
optional: true
|
optional: true
|
||||||
@@ -2376,9 +2376,9 @@ packages:
|
|||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
pretty-bytes@6.1.1:
|
pretty-bytes@7.1.0:
|
||||||
resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==}
|
resolution: {integrity: sha512-nODzvTiYVRGRqAOvE84Vk5JDPyyxsVk0/fbA/bq7RqlnhksGpset09XTxbpvLTIjoaF7K8Z8DG8yHtKGTPSYRw==}
|
||||||
engines: {node: ^14.13.1 || >=16.0.0}
|
engines: {node: '>=20'}
|
||||||
|
|
||||||
prism-react-renderer@2.4.1:
|
prism-react-renderer@2.4.1:
|
||||||
resolution: {integrity: sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==}
|
resolution: {integrity: sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==}
|
||||||
@@ -2782,8 +2782,8 @@ packages:
|
|||||||
os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android]
|
os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android]
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
tabbable@6.2.0:
|
tabbable@6.3.0:
|
||||||
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
|
resolution: {integrity: sha512-EIHvdY5bPLuWForiR/AN2Bxngzpuwn1is4asboytXtpTgsArc+WmSJKVLlhdh71u7jFcryDqB2A8lQvj78MkyQ==}
|
||||||
|
|
||||||
tailwind-scrollbar@4.0.2:
|
tailwind-scrollbar@4.0.2:
|
||||||
resolution: {integrity: sha512-wAQiIxAPqk0MNTPptVe/xoyWi27y+NRGnTwvn4PQnbvB9kp8QUBiGl/wsfoVBHnQxTmhXJSNt9NHTmcz9EivFA==}
|
resolution: {integrity: sha512-wAQiIxAPqk0MNTPptVe/xoyWi27y+NRGnTwvn4PQnbvB9kp8QUBiGl/wsfoVBHnQxTmhXJSNt9NHTmcz9EivFA==}
|
||||||
@@ -2930,6 +2930,11 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
|
||||||
|
use-sync-external-store@1.6.0:
|
||||||
|
resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
|
||||||
util-deprecate@1.0.2:
|
util-deprecate@1.0.2:
|
||||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||||
|
|
||||||
@@ -3148,7 +3153,7 @@ snapshots:
|
|||||||
'@floating-ui/utils': 0.2.10
|
'@floating-ui/utils': 0.2.10
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
tabbable: 6.2.0
|
tabbable: 6.3.0
|
||||||
|
|
||||||
'@floating-ui/utils@0.2.10': {}
|
'@floating-ui/utils@0.2.10': {}
|
||||||
|
|
||||||
@@ -3164,15 +3169,15 @@ snapshots:
|
|||||||
protobufjs: 7.5.3
|
protobufjs: 7.5.3
|
||||||
yargs: 17.7.2
|
yargs: 17.7.2
|
||||||
|
|
||||||
'@headlessui/react@2.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@headlessui/react@2.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@floating-ui/react': 0.26.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@floating-ui/react': 0.26.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@react-aria/focus': 3.21.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@react-aria/focus': 3.21.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@react-aria/interactions': 3.25.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@react-aria/interactions': 3.25.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@tanstack/react-virtual': 3.13.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@tanstack/react-virtual': 3.13.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
use-sync-external-store: 1.5.0(react@18.3.1)
|
use-sync-external-store: 1.6.0(react@18.3.1)
|
||||||
|
|
||||||
'@humanfs/core@0.19.1': {}
|
'@humanfs/core@0.19.1': {}
|
||||||
|
|
||||||
@@ -3419,22 +3424,22 @@ snapshots:
|
|||||||
|
|
||||||
'@protobufjs/utf8@1.1.0': {}
|
'@protobufjs/utf8@1.1.0': {}
|
||||||
|
|
||||||
'@react-aria/focus@3.21.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@react-aria/focus@3.21.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@react-aria/interactions': 3.25.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@react-aria/interactions': 3.25.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@react-aria/utils': 3.30.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@react-aria/utils': 3.31.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@react-types/shared': 3.32.0(react@18.3.1)
|
'@react-types/shared': 3.32.1(react@18.3.1)
|
||||||
'@swc/helpers': 0.5.17
|
'@swc/helpers': 0.5.17
|
||||||
clsx: 2.1.1
|
clsx: 2.1.1
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
|
||||||
'@react-aria/interactions@3.25.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@react-aria/interactions@3.25.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@react-aria/ssr': 3.9.10(react@18.3.1)
|
'@react-aria/ssr': 3.9.10(react@18.3.1)
|
||||||
'@react-aria/utils': 3.30.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@react-aria/utils': 3.31.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@react-stately/flags': 3.1.2
|
'@react-stately/flags': 3.1.2
|
||||||
'@react-types/shared': 3.32.0(react@18.3.1)
|
'@react-types/shared': 3.32.1(react@18.3.1)
|
||||||
'@swc/helpers': 0.5.17
|
'@swc/helpers': 0.5.17
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
@@ -3444,12 +3449,12 @@ snapshots:
|
|||||||
'@swc/helpers': 0.5.17
|
'@swc/helpers': 0.5.17
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
|
|
||||||
'@react-aria/utils@3.30.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@react-aria/utils@3.31.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@react-aria/ssr': 3.9.10(react@18.3.1)
|
'@react-aria/ssr': 3.9.10(react@18.3.1)
|
||||||
'@react-stately/flags': 3.1.2
|
'@react-stately/flags': 3.1.2
|
||||||
'@react-stately/utils': 3.10.8(react@18.3.1)
|
'@react-stately/utils': 3.10.8(react@18.3.1)
|
||||||
'@react-types/shared': 3.32.0(react@18.3.1)
|
'@react-types/shared': 3.32.1(react@18.3.1)
|
||||||
'@swc/helpers': 0.5.17
|
'@swc/helpers': 0.5.17
|
||||||
clsx: 2.1.1
|
clsx: 2.1.1
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
@@ -3464,7 +3469,7 @@ snapshots:
|
|||||||
'@swc/helpers': 0.5.17
|
'@swc/helpers': 0.5.17
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
|
|
||||||
'@react-types/shared@3.32.0(react@18.3.1)':
|
'@react-types/shared@3.32.1(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
|
|
||||||
@@ -4415,7 +4420,7 @@ snapshots:
|
|||||||
- eslint-plugin-import-x
|
- eslint-plugin-import-x
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
eslint-config-prettier@10.1.1(eslint@9.25.1(jiti@2.6.1)):
|
eslint-config-prettier@10.1.8(eslint@9.25.1(jiti@2.6.1)):
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint: 9.25.1(jiti@2.6.1)
|
eslint: 9.25.1(jiti@2.6.1)
|
||||||
|
|
||||||
@@ -4501,14 +4506,14 @@ snapshots:
|
|||||||
safe-regex-test: 1.1.0
|
safe-regex-test: 1.1.0
|
||||||
string.prototype.includes: 2.0.1
|
string.prototype.includes: 2.0.1
|
||||||
|
|
||||||
eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.1(eslint@9.25.1(jiti@2.6.1)))(eslint@9.25.1(jiti@2.6.1))(prettier@3.6.2):
|
eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8(eslint@9.25.1(jiti@2.6.1)))(eslint@9.25.1(jiti@2.6.1))(prettier@3.6.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint: 9.25.1(jiti@2.6.1)
|
eslint: 9.25.1(jiti@2.6.1)
|
||||||
prettier: 3.6.2
|
prettier: 3.6.2
|
||||||
prettier-linter-helpers: 1.0.0
|
prettier-linter-helpers: 1.0.0
|
||||||
synckit: 0.11.11
|
synckit: 0.11.11
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
eslint-config-prettier: 10.1.1(eslint@9.25.1(jiti@2.6.1))
|
eslint-config-prettier: 10.1.8(eslint@9.25.1(jiti@2.6.1))
|
||||||
|
|
||||||
eslint-plugin-react-hooks@5.2.0(eslint@9.25.1(jiti@2.6.1)):
|
eslint-plugin-react-hooks@5.2.0(eslint@9.25.1(jiti@2.6.1)):
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5411,14 +5416,14 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
fast-diff: 1.3.0
|
fast-diff: 1.3.0
|
||||||
|
|
||||||
prettier-plugin-organize-imports@4.1.0(prettier@3.6.2)(typescript@5.7.3):
|
prettier-plugin-organize-imports@4.3.0(prettier@3.6.2)(typescript@5.7.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
prettier: 3.6.2
|
prettier: 3.6.2
|
||||||
typescript: 5.7.3
|
typescript: 5.7.3
|
||||||
|
|
||||||
prettier@3.6.2: {}
|
prettier@3.6.2: {}
|
||||||
|
|
||||||
pretty-bytes@6.1.1: {}
|
pretty-bytes@7.1.0: {}
|
||||||
|
|
||||||
prism-react-renderer@2.4.1(react@18.3.1):
|
prism-react-renderer@2.4.1(react@18.3.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5504,7 +5509,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/use-sync-external-store': 0.0.6
|
'@types/use-sync-external-store': 0.0.6
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
use-sync-external-store: 1.5.0(react@18.3.1)
|
use-sync-external-store: 1.6.0(react@18.3.1)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.0.10
|
'@types/react': 19.0.10
|
||||||
redux: 5.0.1
|
redux: 5.0.1
|
||||||
@@ -5902,7 +5907,7 @@ snapshots:
|
|||||||
|
|
||||||
systeminformation@5.27.7: {}
|
systeminformation@5.27.7: {}
|
||||||
|
|
||||||
tabbable@6.2.0: {}
|
tabbable@6.3.0: {}
|
||||||
|
|
||||||
tailwind-scrollbar@4.0.2(react@18.3.1)(tailwindcss@4.0.9):
|
tailwind-scrollbar@4.0.2(react@18.3.1)(tailwindcss@4.0.9):
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6089,6 +6094,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
|
|
||||||
|
use-sync-external-store@1.6.0(react@18.3.1):
|
||||||
|
dependencies:
|
||||||
|
react: 18.3.1
|
||||||
|
|
||||||
util-deprecate@1.0.2: {}
|
util-deprecate@1.0.2: {}
|
||||||
|
|
||||||
uuid@10.0.0: {}
|
uuid@10.0.0: {}
|
||||||
|
|||||||
@@ -1,16 +1,47 @@
|
|||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { useTranslation } from "next-i18next";
|
import { useTranslation } from "next-i18next";
|
||||||
|
import { useContext, useMemo } from "react";
|
||||||
|
|
||||||
export default function Block({ value, label }) {
|
import { BlockHighlightContext } from "./highlight-context";
|
||||||
|
|
||||||
|
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,7 +1,10 @@
|
|||||||
import { useContext } from "react";
|
import { useContext, useMemo } 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",
|
||||||
@@ -11,6 +14,11 @@ 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;
|
||||||
@@ -51,6 +59,11 @@ export default function Container({ error = false, children, service }) {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const content = <div className="relative flex flex-row w-full service-container">{visibleChildren}</div>;
|
||||||
|
|
||||||
return <div className="relative flex flex-row w-full service-container">{visibleChildren}</div>;
|
if (!highlightConfig) {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <BlockHighlightContext.Provider value={highlightConfig}>{content}</BlockHighlightContext.Provider>;
|
||||||
}
|
}
|
||||||
|
|||||||
3
src/components/services/widget/highlight-context.jsx
Normal file
3
src/components/services/widget/highlight-context.jsx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
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.free,
|
value: data.mem.available,
|
||||||
maximumFractionDigits: 1,
|
maximumFractionDigits: 1,
|
||||||
binary: true,
|
binary: true,
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -254,6 +254,7 @@ export function cleanServiceGroups(groups) {
|
|||||||
// all widgets
|
// all widgets
|
||||||
fields,
|
fields,
|
||||||
hideErrors,
|
hideErrors,
|
||||||
|
highlight,
|
||||||
type,
|
type,
|
||||||
|
|
||||||
// azuredevops
|
// azuredevops
|
||||||
@@ -441,6 +442,21 @@ 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;
|
||||||
|
|||||||
257
src/utils/highlights.js
Normal file
257
src/utils/highlights.js
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
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,10 +24,11 @@ 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 {
|
} else if (statuses[0] === "STATUS_ERROR") {
|
||||||
numFailureLatest++;
|
numFailureLatest++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,13 +205,14 @@ 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, "Sessions", {
|
} = useWidgetAPI(widget, enableNowPlaying ? "Sessions" : "", {
|
||||||
refreshInterval: 5000,
|
refreshInterval: enableNowPlaying ? 5000 : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: countData, error: countError } = useWidgetAPI(widget, "Count", {
|
const { data: countData, error: countError } = useWidgetAPI(widget, "Count", {
|
||||||
@@ -239,13 +240,12 @@ 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 (!sessionsData || !countData) {
|
if ((enableNowPlaying && !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.free }];
|
const newDataPoints = [...prevDataPoints, { a: data.used, b: data.available }];
|
||||||
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.free && chart && (
|
{data.available && chart && (
|
||||||
<div className="text-xs opacity-50">
|
<div className="text-xs opacity-50">
|
||||||
{t("common.bytes", {
|
{t("common.bytes", {
|
||||||
value: data.free,
|
value: data.available,
|
||||||
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.free && (
|
{data.available && (
|
||||||
<div className="text-xs opacity-50">
|
<div className="text-xs opacity-50">
|
||||||
{t("common.bytes", {
|
{t("common.bytes", {
|
||||||
value: data.free,
|
value: data.available,
|
||||||
maximumFractionDigits: 1,
|
maximumFractionDigits: 1,
|
||||||
binary: true,
|
binary: true,
|
||||||
})}{" "}
|
})}{" "}
|
||||||
|
|||||||
@@ -33,14 +33,6 @@ 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", {
|
||||||
@@ -55,6 +47,14 @@ 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].includes(controllerVersionMajor)) {
|
if (![3, 4, 5, 6].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,6 +80,7 @@ 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:
|
||||||
@@ -122,6 +123,7 @@ 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:
|
||||||
@@ -207,8 +209,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 (controllerVersionMajor === 4 || controllerVersionMajor === 5) {
|
} else if ([4, 5, 6].includes(controllerVersionMajor)) {
|
||||||
const siteName = controllerVersionMajor === 5 ? site.id : site.key;
|
const siteName = controllerVersionMajor > 4 ? 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`
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ 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:
|
||||||
@@ -62,7 +63,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: monitor.logs[0].duration });
|
uptime = t("common.duration", { value: hasLogs ? monitor.logs[0].duration : 0 });
|
||||||
logIndex = 1;
|
logIndex = 1;
|
||||||
break;
|
break;
|
||||||
case 8:
|
case 8:
|
||||||
@@ -76,14 +77,14 @@ export default function Component({ service }) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastDown = new Date(monitor.logs[logIndex].datetime * 1000).toLocaleString();
|
const lastDown = hasLogs ? new Date(monitor.logs[logIndex].datetime * 1000).toLocaleString() : "";
|
||||||
const downDuration = t("common.duration", { value: monitor.logs[logIndex].duration });
|
const downDuration = t("common.duration", { value: hasLogs ? monitor.logs[logIndex].duration : 0 });
|
||||||
const hideDown = logIndex === 1 && monitor.logs[logIndex].type !== 1;
|
const hideDown = !hasLogs || (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} />
|
||||||
<Block label="uptimerobot.uptime" value={uptime} />
|
{hasLogs && <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>
|
||||||
|
|||||||
Reference in New Issue
Block a user