mirror of
https://github.com/gethomepage/homepage.git
synced 2026-01-01 03:38:39 +08:00
Compare commits
1 Commits
l10n_dev
...
feature/je
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15fa053b4d |
2
.github/workflows/docker-publish.yml
vendored
2
.github/workflows/docker-publish.yml
vendored
@@ -84,7 +84,7 @@ jobs:
|
||||
latest=auto
|
||||
|
||||
- name: Next.js build cache
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .next/cache
|
||||
key: nextjs-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}-${{ hashFiles('**/*.js', '**/*.jsx') }}
|
||||
|
||||
4
.github/workflows/docs-publish.yml
vendored
4
.github/workflows/docs-publish.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
||||
with:
|
||||
python-version: 3.x
|
||||
- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
|
||||
- uses: actions/cache@v5
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
key: mkdocs-material-${{ env.cache_id }}
|
||||
path: .cache
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
with:
|
||||
python-version: 3.x
|
||||
- run: echo "cache_id=${{github.sha}}" >> $GITHUB_ENV
|
||||
- uses: actions/cache@v5
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
key: mkdocs-material-${{ env.cache_id }}
|
||||
path: .cache
|
||||
|
||||
2
.github/workflows/repo-maintenance.yml
vendored
2
.github/workflows/repo-maintenance.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
name: 'Lock Old Threads'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v6
|
||||
- uses: dessant/lock-threads@v5
|
||||
with:
|
||||
issue-inactive-days: '30'
|
||||
pr-inactive-days: '30'
|
||||
|
||||
@@ -189,8 +189,6 @@ labels: ...
|
||||
- homepage.widgets[1].slug=youreventslughere
|
||||
```
|
||||
|
||||
To pass custom HTTP headers with a widget request when using labels, use the same dot-notation: `homepage.widget.headers.X-Auth-Key=secret` (or `homepage.widgets[0].headers.X-Auth-Key=secret` when multiple widgets are present).
|
||||
|
||||
You can add specify fields for e.g. the [CustomAPI](../widgets/services/customapi.md) widget by using array-style dot notation:
|
||||
|
||||
```yaml
|
||||
|
||||
@@ -94,7 +94,6 @@ metadata:
|
||||
gethomepage.dev/name: Emby
|
||||
gethomepage.dev/widget.type: "emby"
|
||||
gethomepage.dev/widget.url: "https://emby.example.com"
|
||||
gethomepage.dev/widget.headers.X-Auth-Key: "your-secret-here"
|
||||
gethomepage.dev/pod-selector: ""
|
||||
gethomepage.dev/weight: 10 # optional
|
||||
gethomepage.dev/instance: "public" # optional
|
||||
|
||||
@@ -101,25 +101,6 @@ Each service can have multiple widgets attached to it, for example:
|
||||
|
||||
Multiple widgets per service are not yet supported with Kubernetes ingress annotations.
|
||||
|
||||
#### Custom HTTP headers
|
||||
|
||||
Widgets that make HTTP calls support extra request headers via `headers`. This is useful when a reverse proxy expects a secret header.
|
||||
|
||||
```yaml
|
||||
- UptimeRobot:
|
||||
icon: uptimekuma.png
|
||||
href: https://uptimerobot.com/
|
||||
widget:
|
||||
type: uptimerobot
|
||||
url: https://api.uptimerobot.com
|
||||
key: ${UPTIMEROBOT_API_KEY}
|
||||
headers:
|
||||
User-Agent: homepage
|
||||
X-Auth-Key: your-secret-here
|
||||
```
|
||||
|
||||
If you define services via Docker labels or Kubernetes annotations, use the same key with dot-notation (for example `homepage.widget.headers.X-Auth-Key=secret` or `gethomepage.dev/widget.headers.X-Auth-Key: "secret"`).
|
||||
|
||||
#### Field Visibility
|
||||
|
||||
Each widget can optionally provide a list of which fields should be visible via the `fields` widget property. If no fields are specified, then all fields will be displayed. The `fields` property must be a valid YAML array of strings. As an example, here is the entry for Sonarr showing only a couple of fields.
|
||||
|
||||
@@ -7,8 +7,8 @@ Learn more about [Jellyseerr](https://github.com/Fallenbagel/jellyseerr).
|
||||
|
||||
Find your API key under `Settings > General > API Key`.
|
||||
|
||||
Allowed fields: `["pending", "approved", "available", "issues"]`.
|
||||
Default fields: `["pending", "approved", "available"]`.
|
||||
Allowed fields: `["pending", "approved", "available", "completed", "issues"]`.
|
||||
Default fields: `["pending", "approved", "completed"]`.
|
||||
|
||||
```yaml
|
||||
widget:
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"luxon": "^3.6.1",
|
||||
"memory-cache": "^0.2.0",
|
||||
"minecraftstatuspinger": "^1.2.2",
|
||||
"next": "^15.5.9",
|
||||
"next": "^15.5.7",
|
||||
"next-i18next": "^12.1.0",
|
||||
"ping": "^0.4.4",
|
||||
"pretty-bytes": "^7.1.0",
|
||||
@@ -65,7 +65,6 @@
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@tailwindcss/oxide",
|
||||
"osx-temperature-sensor",
|
||||
"sharp"
|
||||
]
|
||||
|
||||
32
pnpm-lock.yaml
generated
32
pnpm-lock.yaml
generated
@@ -51,11 +51,11 @@ importers:
|
||||
specifier: ^1.2.2
|
||||
version: 1.2.2
|
||||
next:
|
||||
specifier: ^15.5.9
|
||||
version: 15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
specifier: ^15.5.7
|
||||
version: 15.5.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
next-i18next:
|
||||
specifier: ^12.1.0
|
||||
version: 12.1.0(next@15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
version: 12.1.0(next@15.5.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
ping:
|
||||
specifier: ^0.4.4
|
||||
version: 0.4.4
|
||||
@@ -466,8 +466,8 @@ packages:
|
||||
'@napi-rs/wasm-runtime@0.2.8':
|
||||
resolution: {integrity: sha512-OBlgKdX7gin7OIq4fadsjpg+cp2ZphvAIKucHsNfTdJiqdOmOEwQd/bHi0VwNrcw5xpBJyUw6cK/QilCqy1BSg==}
|
||||
|
||||
'@next/env@15.5.9':
|
||||
resolution: {integrity: sha512-4GlTZ+EJM7WaW2HEZcyU317tIQDjkQIyENDLxYJfSWlfqguN+dHkZgyQTV/7ykvobU7yEH5gKvreNrH4B6QgIg==}
|
||||
'@next/env@15.5.7':
|
||||
resolution: {integrity: sha512-4h6Y2NyEkIEN7Z8YxkA27pq6zTkS09bUSYC0xjd0NpwFxjnIKeZEeH591o5WECSmjpUhLn3H2QLJcDye3Uzcvg==}
|
||||
|
||||
'@next/eslint-plugin-next@15.2.4':
|
||||
resolution: {integrity: sha512-O8ScvKtnxkp8kL9TpJTTKnMqlkZnS+QxwoQnJwPGBxjBbzd6OVVPEJ5/pMNrktSyXQD/chEfzfFzYLM6JANOOQ==}
|
||||
@@ -1120,8 +1120,8 @@ packages:
|
||||
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
caniuse-lite@1.0.30001760:
|
||||
resolution: {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==}
|
||||
caniuse-lite@1.0.30001759:
|
||||
resolution: {integrity: sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==}
|
||||
|
||||
chalk@4.1.2:
|
||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||
@@ -2212,8 +2212,8 @@ packages:
|
||||
next: '>= 10.0.0'
|
||||
react: '>= 16.8.0'
|
||||
|
||||
next@15.5.9:
|
||||
resolution: {integrity: sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg==}
|
||||
next@15.5.7:
|
||||
resolution: {integrity: sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==}
|
||||
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -3374,7 +3374,7 @@ snapshots:
|
||||
'@tybys/wasm-util': 0.9.0
|
||||
optional: true
|
||||
|
||||
'@next/env@15.5.9': {}
|
||||
'@next/env@15.5.7': {}
|
||||
|
||||
'@next/eslint-plugin-next@15.2.4':
|
||||
dependencies:
|
||||
@@ -4024,7 +4024,7 @@ snapshots:
|
||||
|
||||
callsites@3.1.0: {}
|
||||
|
||||
caniuse-lite@1.0.30001760: {}
|
||||
caniuse-lite@1.0.30001759: {}
|
||||
|
||||
chalk@4.1.2:
|
||||
dependencies:
|
||||
@@ -5259,7 +5259,7 @@ snapshots:
|
||||
|
||||
net@1.0.2: {}
|
||||
|
||||
next-i18next@12.1.0(next@15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
next-i18next@12.1.0(next@15.5.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.26.9
|
||||
'@types/hoist-non-react-statics': 3.3.6
|
||||
@@ -5267,18 +5267,18 @@ snapshots:
|
||||
hoist-non-react-statics: 3.3.2
|
||||
i18next: 21.10.0
|
||||
i18next-fs-backend: 1.2.0
|
||||
next: 15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
next: 15.5.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react: 18.3.1
|
||||
react-i18next: 11.18.6(i18next@21.10.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
transitivePeerDependencies:
|
||||
- react-dom
|
||||
- react-native
|
||||
|
||||
next@15.5.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
next@15.5.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
'@next/env': 15.5.9
|
||||
'@next/env': 15.5.7
|
||||
'@swc/helpers': 0.5.15
|
||||
caniuse-lite: 1.0.30001760
|
||||
caniuse-lite: 1.0.30001759
|
||||
postcss: 8.4.31
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Op",
|
||||
"pending": "Afwagtend",
|
||||
"down": "Af",
|
||||
"ok": "Ok"
|
||||
"down": "Af"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "Nuut",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Up",
|
||||
"pending": "Pending",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "جديد(ة)",
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
"wlan_users": "WLAN Потребители",
|
||||
"up": "UP",
|
||||
"down": "DOWN",
|
||||
"wait": "Моля изчакайте",
|
||||
"wait": "Please wait",
|
||||
"empty_data": "Неизвестен статус на подсистема"
|
||||
},
|
||||
"docker": {
|
||||
@@ -83,7 +83,7 @@
|
||||
"partial": "Частично"
|
||||
},
|
||||
"ping": {
|
||||
"error": "Грешка",
|
||||
"error": "Error",
|
||||
"ping": "Пинг",
|
||||
"down": "Down",
|
||||
"up": "Up",
|
||||
@@ -91,11 +91,11 @@
|
||||
},
|
||||
"siteMonitor": {
|
||||
"http_status": "HTTP статус",
|
||||
"error": "Грешка",
|
||||
"error": "Error",
|
||||
"response": "Отговор",
|
||||
"down": "Down",
|
||||
"up": "Up",
|
||||
"not_available": "Не е налично"
|
||||
"not_available": "Not Available"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "Възпроизвежда",
|
||||
@@ -111,7 +111,7 @@
|
||||
"offline": "Offline",
|
||||
"offline_alt": "Offline",
|
||||
"online": "Онлайн",
|
||||
"total": "Общо",
|
||||
"total": "Total",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"evcc": {
|
||||
@@ -133,7 +133,7 @@
|
||||
"unread": "Непрочетени"
|
||||
},
|
||||
"fritzbox": {
|
||||
"connectionStatus": "Статус",
|
||||
"connectionStatus": "Status",
|
||||
"connectionStatusUnconfigured": "Неконфигуриран",
|
||||
"connectionStatusConnecting": "Свързване",
|
||||
"connectionStatusAuthenticating": "Удостоверяване",
|
||||
@@ -141,7 +141,7 @@
|
||||
"connectionStatusDisconnecting": "Прекъсване на връзката",
|
||||
"connectionStatusDisconnected": "Не е свързан",
|
||||
"connectionStatusConnected": "Свързан",
|
||||
"uptime": "Време на работа",
|
||||
"uptime": "Uptime",
|
||||
"maxDown": "Макс сваляне",
|
||||
"maxUp": "Макс качване",
|
||||
"down": "Down",
|
||||
@@ -170,8 +170,8 @@
|
||||
"tautulli": {
|
||||
"playing": "Playing",
|
||||
"transcoding": "Transcoding",
|
||||
"bitrate": "Битрейт",
|
||||
"no_active": "Няма активни потоци",
|
||||
"bitrate": "Bitrate",
|
||||
"no_active": "No Active Streams",
|
||||
"plex_connection_error": "Провери връзка с Plex"
|
||||
},
|
||||
"omada": {
|
||||
@@ -189,7 +189,7 @@
|
||||
"plex": {
|
||||
"streams": "Активни Потоци",
|
||||
"albums": "Албуми",
|
||||
"movies": "Филми",
|
||||
"movies": "Movies",
|
||||
"tv": "Сериали"
|
||||
},
|
||||
"sabnzbd": {
|
||||
@@ -362,8 +362,8 @@
|
||||
},
|
||||
"trilium": {
|
||||
"version": "Version",
|
||||
"notesCount": "Бележки",
|
||||
"dbSize": "Размер на базата данни",
|
||||
"notesCount": "Notes",
|
||||
"dbSize": "Database Size",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"navidrome": {
|
||||
@@ -373,7 +373,7 @@
|
||||
"npm": {
|
||||
"enabled": "Активирано",
|
||||
"disabled": "Деактивирано",
|
||||
"total": "Общо"
|
||||
"total": "Total"
|
||||
},
|
||||
"coinmarketcap": {
|
||||
"configure": "Настрой за следене една или повече крипто валути",
|
||||
@@ -384,7 +384,7 @@
|
||||
},
|
||||
"gotify": {
|
||||
"apps": "Приложения",
|
||||
"clients": "Клиенти",
|
||||
"clients": "Clients",
|
||||
"messages": "Съобщения"
|
||||
},
|
||||
"prowlarr": {
|
||||
@@ -405,7 +405,7 @@
|
||||
"transferRate": "Rate"
|
||||
},
|
||||
"mastodon": {
|
||||
"user_count": "Потребители",
|
||||
"user_count": "Users",
|
||||
"status_count": "Posts",
|
||||
"domain_count": "Domains"
|
||||
},
|
||||
@@ -416,17 +416,17 @@
|
||||
},
|
||||
"minecraft": {
|
||||
"players": "Играчи",
|
||||
"version": "Версия",
|
||||
"status": "Статус",
|
||||
"up": "Онлайн",
|
||||
"down": "Офлайн"
|
||||
"version": "Version",
|
||||
"status": "Status",
|
||||
"up": "Online",
|
||||
"down": "Offline"
|
||||
},
|
||||
"miniflux": {
|
||||
"read": "Read",
|
||||
"unread": "Unread"
|
||||
},
|
||||
"authentik": {
|
||||
"users": "Потребители",
|
||||
"users": "Users",
|
||||
"loginsLast24H": "Logins (24h)",
|
||||
"failedLoginsLast24H": "Failed Logins (24h)"
|
||||
},
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Up",
|
||||
"pending": "Pending",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "Нови",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Up",
|
||||
"pending": "Pending",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "Nou",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Up",
|
||||
"pending": "Pending",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "Nové",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Up",
|
||||
"pending": "Pending",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "Ny",
|
||||
|
||||
@@ -45,9 +45,9 @@
|
||||
"free": "Frei",
|
||||
"used": "In Benutzung",
|
||||
"load": "Last",
|
||||
"temp": "Temp",
|
||||
"temp": "TEMP",
|
||||
"max": "Max",
|
||||
"uptime": "Betriebszeit"
|
||||
"uptime": "BETRIEBSZEIT"
|
||||
},
|
||||
"unifi": {
|
||||
"users": "Benutzer",
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Online",
|
||||
"pending": "Wartend",
|
||||
"down": "Offline",
|
||||
"ok": "Ok"
|
||||
"down": "Offline"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "Neu",
|
||||
@@ -603,7 +602,7 @@
|
||||
"pangolin": {
|
||||
"orgs": "Orgs",
|
||||
"sites": "Sites",
|
||||
"resources": "Ressourcen",
|
||||
"resources": "Resources",
|
||||
"targets": "Targets",
|
||||
"traffic": "Traffic",
|
||||
"in": "In",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Up",
|
||||
"pending": "Pending",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "New",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Up",
|
||||
"pending": "Pending",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "New",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Up",
|
||||
"pending": "Pending",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "New",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Activo",
|
||||
"pending": "Pendiente",
|
||||
"down": "Inactivo",
|
||||
"ok": "Ok"
|
||||
"down": "Inactivo"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "Nuevo",
|
||||
@@ -602,12 +601,12 @@
|
||||
},
|
||||
"pangolin": {
|
||||
"orgs": "Orgs",
|
||||
"sites": "Sitios",
|
||||
"resources": "Recursos",
|
||||
"targets": "Destinos",
|
||||
"traffic": "Tráfico",
|
||||
"in": "Entrante",
|
||||
"out": "Saliente"
|
||||
"sites": "Sites",
|
||||
"resources": "Resources",
|
||||
"targets": "Targets",
|
||||
"traffic": "Traffic",
|
||||
"in": "In",
|
||||
"out": "Out"
|
||||
},
|
||||
"peanut": {
|
||||
"battery_charge": "Carga de la batería",
|
||||
@@ -770,7 +769,7 @@
|
||||
"gross_percent_today": "Hoy",
|
||||
"gross_percent_1y": "Un año",
|
||||
"gross_percent_max": "Todo el tiempo",
|
||||
"net_worth": "Patrimonio neto"
|
||||
"net_worth": "Net Worth"
|
||||
},
|
||||
"audiobookshelf": {
|
||||
"podcasts": "Podcasts",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Up",
|
||||
"pending": "Pending",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "New",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Up",
|
||||
"pending": "Pending",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "New",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "En ligne",
|
||||
"pending": "En attente",
|
||||
"down": "Hors ligne",
|
||||
"ok": "Ok"
|
||||
"down": "Hors ligne"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "Nouveau",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "למעלה",
|
||||
"pending": "ממתין",
|
||||
"down": "למטה",
|
||||
"ok": "Ok"
|
||||
"down": "למטה"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "חדש",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Up",
|
||||
"pending": "Pending",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "New",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Aktivno",
|
||||
"pending": "U tijeku",
|
||||
"down": "Neaktivno",
|
||||
"ok": "Ok"
|
||||
"down": "Neaktivno"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "Novo",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Fut",
|
||||
"pending": "Függőben lévő",
|
||||
"down": "Leállt",
|
||||
"ok": "Ok"
|
||||
"down": "Leállt"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "Új",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Up",
|
||||
"pending": "Pending",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "Baru",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Up",
|
||||
"pending": "Pending",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "Nuovo",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Up",
|
||||
"pending": "Pending",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "新着",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "업",
|
||||
"pending": "대기 중",
|
||||
"down": "다운",
|
||||
"ok": "Ok"
|
||||
"down": "다운"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "신규",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Up",
|
||||
"pending": "Pending",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "New",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Up",
|
||||
"pending": "Pending",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "Baharu",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Online",
|
||||
"pending": "In afwachting",
|
||||
"down": "Offline",
|
||||
"ok": "Ok"
|
||||
"down": "Offline"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "Nieuw",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Up",
|
||||
"pending": "Pending",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "Ny",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Up",
|
||||
"pending": "Pending",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "Nowy",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Up",
|
||||
"pending": "Pending",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "Novo",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Ativo",
|
||||
"pending": "Pendente",
|
||||
"down": "Inativo",
|
||||
"ok": "Ok"
|
||||
"down": "Inativo"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "Novo",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Up",
|
||||
"pending": "Pending",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "Nou",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "В сети",
|
||||
"pending": "Ожидают",
|
||||
"down": "Не в сети",
|
||||
"ok": "Ok"
|
||||
"down": "Не в сети"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "Новый",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Beží",
|
||||
"pending": "Čakajúce",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "Nový",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Up",
|
||||
"pending": "Pending",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "Nov",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Горе",
|
||||
"pending": "На чекању",
|
||||
"down": "Доле",
|
||||
"ok": "Ok"
|
||||
"down": "Доле"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "Сада",
|
||||
@@ -601,13 +600,13 @@
|
||||
"total": "Укупно"
|
||||
},
|
||||
"pangolin": {
|
||||
"orgs": "Организације",
|
||||
"sites": "Сајтови",
|
||||
"resources": "Ресурси",
|
||||
"targets": "Циљеви",
|
||||
"traffic": "Саобраћај",
|
||||
"in": "Улазак",
|
||||
"out": "Излазак"
|
||||
"orgs": "Orgs",
|
||||
"sites": "Sites",
|
||||
"resources": "Resources",
|
||||
"targets": "Targets",
|
||||
"traffic": "Traffic",
|
||||
"in": "In",
|
||||
"out": "Out"
|
||||
},
|
||||
"peanut": {
|
||||
"battery_charge": "Напуњеност батерије",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Up",
|
||||
"pending": "Pending",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "New",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Up",
|
||||
"pending": "Pending",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "New",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Up",
|
||||
"pending": "Pending",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "New",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Çalışıyor",
|
||||
"pending": "Bekleyen",
|
||||
"down": "Çalışmayan",
|
||||
"ok": "Ok"
|
||||
"down": "Çalışmayan"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "Yeni",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Up",
|
||||
"pending": "Pending",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "Новий",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Up",
|
||||
"pending": "Pending",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "New",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Up",
|
||||
"pending": "Pending",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "新建立",
|
||||
|
||||
@@ -364,7 +364,7 @@
|
||||
"version": "版本",
|
||||
"notesCount": "笔记",
|
||||
"dbSize": "数据库大小",
|
||||
"unknown": "未知"
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"navidrome": {
|
||||
"nothing_streaming": "",
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Up",
|
||||
"pending": "Pending",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "新建立",
|
||||
@@ -801,7 +800,7 @@
|
||||
},
|
||||
"kavita": {
|
||||
"seriesCount": "系列",
|
||||
"totalFiles": "文件"
|
||||
"totalFiles": "Files"
|
||||
},
|
||||
"azuredevops": {
|
||||
"result": "Result",
|
||||
@@ -1098,7 +1097,7 @@
|
||||
},
|
||||
"unraid": {
|
||||
"STARTED": "Started",
|
||||
"STOPPED": "已停止",
|
||||
"STOPPED": "Stopped",
|
||||
"NEW_ARRAY": "New Array",
|
||||
"RECON_DISK": "Reconstructing Disk",
|
||||
"DISABLE_DISK": "Disk Disabled",
|
||||
|
||||
@@ -532,8 +532,7 @@
|
||||
"child_bridges_status": "{{ok}}/{{total}}",
|
||||
"up": "Up",
|
||||
"pending": "Pending",
|
||||
"down": "Down",
|
||||
"ok": "Ok"
|
||||
"down": "Down"
|
||||
},
|
||||
"healthchecks": {
|
||||
"new": "新建",
|
||||
|
||||
@@ -24,12 +24,12 @@ export default function Resource({
|
||||
wide ? " min-w-[120px]" : "min-w-[85px]"
|
||||
}`}
|
||||
>
|
||||
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between gap-2">
|
||||
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
||||
<div className="pl-0.5">{value}</div>
|
||||
<div className="pr-1">{label}</div>
|
||||
</div>
|
||||
{expanded && (
|
||||
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between gap-2">
|
||||
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
||||
<div className="pl-0.5">{expandedValue}</div>
|
||||
<div className="pr-1">{expandedLabel}</div>
|
||||
</div>
|
||||
|
||||
@@ -59,7 +59,7 @@ export default async function handler(req, res) {
|
||||
}
|
||||
|
||||
if (type === "network") {
|
||||
let networkData = await si.networkStats("*");
|
||||
let networkData = await si.networkStats();
|
||||
let interfaceDefault;
|
||||
logger.debug("networkData:", JSON.stringify(networkData));
|
||||
if (interfaceName && interfaceName !== "default") {
|
||||
|
||||
@@ -554,38 +554,48 @@ export default function Wrapper({ initialSettings, fallback }) {
|
||||
html.classList.add(desiredThemeClass);
|
||||
}
|
||||
|
||||
// Remove any previously applied inline styles
|
||||
body.style.backgroundImage = "";
|
||||
body.style.backgroundColor = "";
|
||||
body.style.backgroundAttachment = "";
|
||||
if (backgroundImage) {
|
||||
const safeBackgroundImage = backgroundImage.replace(/'/g, "\\'");
|
||||
body.style.backgroundImage = `linear-gradient(rgb(var(--bg-color) / ${opacity}), rgb(var(--bg-color) / ${opacity})), url('${safeBackgroundImage}')`;
|
||||
body.style.backgroundSize = "cover";
|
||||
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]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{backgroundImage && (
|
||||
<div
|
||||
id="background"
|
||||
aria-hidden="true"
|
||||
style={{
|
||||
backgroundImage: `linear-gradient(rgb(var(--bg-color) / ${opacity}), rgb(var(--bg-color) / ${opacity})), url('${backgroundImage}')`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div id="page_wrapper" className="relative h-full">
|
||||
<div
|
||||
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 id="page_wrapper" className="relative min-h-screen">
|
||||
<div
|
||||
id="inner_wrapper"
|
||||
tabIndex="-1"
|
||||
className={classNames(
|
||||
"w-full min-h-screen 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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,18 +30,6 @@ body,
|
||||
height: 100%;
|
||||
margin: 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,
|
||||
|
||||
@@ -27,9 +27,6 @@ export default async function credentialedProxyHandler(req, res, map) {
|
||||
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
...(widgets[widget.type].headers ?? {}),
|
||||
...(widget.headers ?? {}),
|
||||
...(req.extraHeaders ?? {}),
|
||||
};
|
||||
|
||||
if (widget.type === "stocks") {
|
||||
|
||||
@@ -25,11 +25,7 @@ export default async function genericProxyHandler(req, res, map) {
|
||||
}
|
||||
const url = new URL(urlString);
|
||||
|
||||
const headers = {
|
||||
...(widgets[widget.type].headers ?? {}),
|
||||
...(widget.headers ?? {}),
|
||||
...(req.extraHeaders ?? {}),
|
||||
};
|
||||
const headers = req.extraHeaders ?? widget.headers ?? widgets[widget.type].headers ?? {};
|
||||
|
||||
if (widget.username && widget.password) {
|
||||
headers.Authorization = `Basic ${Buffer.from(`${widget.username}:${widget.password}`).toString("base64")}`;
|
||||
|
||||
@@ -85,9 +85,6 @@ export default async function fritzboxProxyHandler(req, res) {
|
||||
requestExternalIPv6Prefix ? requestEndpoint(apiBaseUrl, "WANIPConnection", "X_AVM_DE_GetIPv6Prefix") : null,
|
||||
])
|
||||
.then(([statusInfo, linkProperties, addonInfos, externalIPAddress, externalIPv6Address, externalIPv6Prefix]) => {
|
||||
const ipv6Prefix = externalIPv6Prefix?.NewIPv6Prefix;
|
||||
const ipv6Len = externalIPv6Prefix?.NewPrefixLength;
|
||||
|
||||
res.status(200).json({
|
||||
connectionStatus: statusInfo?.NewConnectionStatus || "Unconfigured",
|
||||
uptime: statusInfo?.NewUptime || 0,
|
||||
@@ -99,7 +96,7 @@ export default async function fritzboxProxyHandler(req, res) {
|
||||
sent: addonInfos?.NewX_AVM_DE_TotalBytesSent64 || 0,
|
||||
externalIPAddress: externalIPAddress?.NewExternalIPAddress || null,
|
||||
externalIPv6Address: externalIPv6Address?.NewExternalIPv6Address || null,
|
||||
externalIPv6Prefix: ipv6Prefix && ipv6Len != null ? `${ipv6Prefix}/${ipv6Len}` : (ipv6Prefix ?? null),
|
||||
externalIPv6Prefix: externalIPv6Prefix?.NewIPv6Prefix || null,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
@@ -3,12 +3,13 @@ import Container from "components/services/widget/container";
|
||||
|
||||
import useWidgetAPI from "utils/proxy/use-widget-api";
|
||||
|
||||
export const jellyseerrDefaultFields = ["pending", "approved", "available"];
|
||||
export const jellyseerrDefaultFields = ["pending", "approved", "completed"];
|
||||
const MAX_ALLOWED_FIELDS = 4;
|
||||
|
||||
export default function Component({ service }) {
|
||||
const { widget } = service;
|
||||
|
||||
widget.fields = widget?.fields?.length ? widget.fields : jellyseerrDefaultFields;
|
||||
widget.fields = widget?.fields?.length ? widget.fields.slice(0, MAX_ALLOWED_FIELDS) : jellyseerrDefaultFields;
|
||||
const isIssueEnabled = widget.fields.includes("issues");
|
||||
|
||||
const { data: statsData, error: statsError } = useWidgetAPI(widget, "request/count");
|
||||
@@ -23,16 +24,24 @@ export default function Component({ service }) {
|
||||
<Block label="jellyseerr.pending" />
|
||||
<Block label="jellyseerr.approved" />
|
||||
<Block label="jellyseerr.available" />
|
||||
<Block label="jellyseerr.completed" />
|
||||
<Block label="jellyseerr.issues" />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
if (statsData.completed === undefined) {
|
||||
// Newer versions added "completed", fallback to available
|
||||
widget.fields = widget.fields.filter((field) => field !== "completed");
|
||||
widget.fields.push("available");
|
||||
}
|
||||
|
||||
return (
|
||||
<Container service={service}>
|
||||
<Block label="jellyseerr.pending" value={statsData.pending} />
|
||||
<Block label="jellyseerr.approved" value={statsData.approved} />
|
||||
<Block label="jellyseerr.available" value={statsData.available} />
|
||||
<Block label="jellyseerr.completed" value={statsData.completed} />
|
||||
<Block label="jellyseerr.issues" value={`${issueData?.open} / ${issueData?.total}`} />
|
||||
</Container>
|
||||
);
|
||||
|
||||
@@ -65,7 +65,7 @@ async function fetchFromPlexAPI(endpoint, widget) {
|
||||
export default async function plexProxyHandler(req, res) {
|
||||
const widget = await getWidget(req);
|
||||
|
||||
const { service, index } = req.query;
|
||||
const { service } = req.query;
|
||||
|
||||
if (!widget) {
|
||||
return res.status(400).json({ error: "Invalid proxy service type" });
|
||||
@@ -85,19 +85,19 @@ export default async function plexProxyHandler(req, res) {
|
||||
streams = apiData.MediaContainer._attributes.size;
|
||||
}
|
||||
|
||||
let libraries = cache.get(`${librariesCacheKey}.${service}.${index}`);
|
||||
let libraries = cache.get(`${librariesCacheKey}.${service}`);
|
||||
if (libraries === null) {
|
||||
logger.debug("Getting libraries from Plex API");
|
||||
[status, apiData] = await fetchFromPlexAPI("/library/sections", widget);
|
||||
if (apiData && apiData.MediaContainer) {
|
||||
libraries = [].concat(apiData.MediaContainer.Directory);
|
||||
cache.put(`${librariesCacheKey}.${service}.${index}`, libraries, 1000 * 60 * 60 * 6);
|
||||
cache.put(`${librariesCacheKey}.${service}`, libraries, 1000 * 60 * 60 * 6);
|
||||
}
|
||||
}
|
||||
|
||||
let albums = cache.get(`${albumsCacheKey}.${service}.${index}`);
|
||||
let movies = cache.get(`${moviesCacheKey}.${service}.${index}`);
|
||||
let tv = cache.get(`${tvCacheKey}.${service}.${index}`);
|
||||
let albums = cache.get(`${albumsCacheKey}.${service}`);
|
||||
let movies = cache.get(`${moviesCacheKey}.${service}`);
|
||||
let tv = cache.get(`${tvCacheKey}.${service}`);
|
||||
if (albums === null || movies === null || tv === null) {
|
||||
albums = 0;
|
||||
movies = 0;
|
||||
@@ -123,9 +123,9 @@ export default async function plexProxyHandler(req, res) {
|
||||
}
|
||||
}),
|
||||
);
|
||||
cache.put(`${albumsCacheKey}.${service}.${index}`, albums, 1000 * 60 * 10);
|
||||
cache.put(`${tvCacheKey}.${service}.${index}`, tv, 1000 * 60 * 10);
|
||||
cache.put(`${moviesCacheKey}.${service}.${index}`, movies, 1000 * 60 * 10);
|
||||
cache.put(`${albumsCacheKey}.${service}`, albums, 1000 * 60 * 10);
|
||||
cache.put(`${tvCacheKey}.${service}`, tv, 1000 * 60 * 10);
|
||||
cache.put(`${moviesCacheKey}.${service}`, movies, 1000 * 60 * 10);
|
||||
}
|
||||
|
||||
const data = {
|
||||
|
||||
@@ -11,15 +11,6 @@ const logger = createLogger(proxyName);
|
||||
const sessionCacheKey = `${proxyName}__sessionId`;
|
||||
const isNgCacheKey = `${proxyName}__isNg`;
|
||||
|
||||
function parsePyloadResponse(url, data) {
|
||||
try {
|
||||
return JSON.parse(Buffer.from(data).toString());
|
||||
} catch (e) {
|
||||
logger.error(`Error communicating with pyload API at ${url}, returned: ${JSON.stringify(data)}`);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchFromPyloadAPI(url, sessionId, params, service) {
|
||||
const options = {
|
||||
body: params
|
||||
@@ -42,33 +33,13 @@ async function fetchFromPyloadAPI(url, sessionId, params, service) {
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [status, contentType, data, responseHeaders] = await httpProxy(url, options);
|
||||
const returnData = parsePyloadResponse(url, data);
|
||||
return [status, returnData, responseHeaders];
|
||||
}
|
||||
|
||||
async function fetchFromPyloadAPIBasic(url, params, username, password) {
|
||||
const parsedUrl = new URL(url);
|
||||
const isGetRequest = !params || Object.keys(params).length === 0;
|
||||
|
||||
const options = {
|
||||
method: isGetRequest ? "GET" : "POST",
|
||||
headers: {
|
||||
Authorization: `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`,
|
||||
},
|
||||
};
|
||||
|
||||
if (isGetRequest) {
|
||||
if (params) {
|
||||
Object.keys(params).forEach((key) => parsedUrl.searchParams.append(key, params[key]));
|
||||
}
|
||||
} else {
|
||||
options.headers["Content-Type"] = "application/json";
|
||||
options.body = JSON.stringify(params);
|
||||
let returnData;
|
||||
try {
|
||||
returnData = JSON.parse(Buffer.from(data).toString());
|
||||
} catch (e) {
|
||||
logger.error(`Error communicating with pyload API at ${url}, returned: ${JSON.stringify(data)}`);
|
||||
returnData = data;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [status, contentType, data, responseHeaders] = await httpProxy(parsedUrl, options);
|
||||
const returnData = parsePyloadResponse(parsedUrl, data);
|
||||
return [status, returnData, responseHeaders];
|
||||
}
|
||||
|
||||
@@ -95,43 +66,24 @@ async function login(loginUrl, service, username, password = "") {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
export default async function pyloadProxyHandler(req, res, map = {}) {
|
||||
export default async function pyloadProxyHandler(req, res) {
|
||||
const { group, service, endpoint, index } = req.query;
|
||||
const { ngEndpoint } = map;
|
||||
|
||||
try {
|
||||
if (group && service) {
|
||||
const widget = await getServiceWidget(group, service, index);
|
||||
|
||||
if (widget) {
|
||||
const apiTemplate = widgets[widget.type].api;
|
||||
const url = new URL(formatApiCall(apiTemplate, { endpoint, ...widget }));
|
||||
const ngUrl = ngEndpoint ? new URL(formatApiCall(apiTemplate, { endpoint: ngEndpoint, ...widget })) : url;
|
||||
const url = new URL(formatApiCall(widgets[widget.type].api, { endpoint, ...widget }));
|
||||
const loginUrl = `${widget.url}/api/login`;
|
||||
const hasCredentials = widget.username && widget.password;
|
||||
|
||||
if (hasCredentials) {
|
||||
const [status, data] = await fetchFromPyloadAPIBasic(ngUrl, null, widget.username, widget.password);
|
||||
|
||||
if (status === 200 && !data?.error) {
|
||||
cache.put(`${isNgCacheKey}.${service}`, true);
|
||||
return res.json(data);
|
||||
}
|
||||
|
||||
if (status === 401) {
|
||||
return res
|
||||
.status(status)
|
||||
.send({ error: { message: "Invalid credentials communicating with Pyload API", data } });
|
||||
}
|
||||
}
|
||||
|
||||
let sessionId =
|
||||
cache.get(`${sessionCacheKey}.${service}`) ??
|
||||
(await login(loginUrl, service, widget.username, widget.password));
|
||||
let [status, data] = await fetchFromPyloadAPI(url, sessionId, null, service);
|
||||
|
||||
if (status === 403 || status === 401 || (status === 400 && data?.error?.includes("CSRF token"))) {
|
||||
logger.info("Failed to retrieve data from Pyload API with session auth, trying to login again...");
|
||||
if (status === 403 || status === 401) {
|
||||
logger.info("Failed to retrieve data from Pyload API, trying to login again...");
|
||||
cache.del(`${sessionCacheKey}.${service}`);
|
||||
sessionId = await login(loginUrl, service, widget.username, widget.password);
|
||||
[status, data] = await fetchFromPyloadAPI(url, sessionId, null, service);
|
||||
|
||||
@@ -7,7 +7,6 @@ const widget = {
|
||||
mappings: {
|
||||
status: {
|
||||
endpoint: "statusServer",
|
||||
map: { ngEndpoint: "status_server" },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -49,12 +49,10 @@ export default function Component({ service }) {
|
||||
|
||||
// single monitor
|
||||
const monitor = uptimerobotData.monitors[0];
|
||||
const logs = Array.isArray(monitor.logs) ? monitor.logs : [];
|
||||
const lastUpLog = logs.find((log) => log.type === 2);
|
||||
const lastDownLog = logs.find((log) => log.type === 1);
|
||||
|
||||
let status;
|
||||
let uptime = 0;
|
||||
let logIndex = 0;
|
||||
const hasLogs = Array.isArray(monitor.logs) && monitor.logs.length > 0;
|
||||
|
||||
switch (monitor.status) {
|
||||
case 0:
|
||||
@@ -65,7 +63,8 @@ export default function Component({ service }) {
|
||||
break;
|
||||
case 2:
|
||||
status = t("uptimerobot.up");
|
||||
uptime = t("common.duration", { value: lastUpLog?.duration ?? 0 });
|
||||
uptime = t("common.duration", { value: hasLogs ? monitor.logs[0].duration : 0 });
|
||||
logIndex = 1;
|
||||
break;
|
||||
case 8:
|
||||
status = t("uptimerobot.seemsdown");
|
||||
@@ -78,14 +77,14 @@ export default function Component({ service }) {
|
||||
break;
|
||||
}
|
||||
|
||||
const lastDown = lastDownLog ? new Date(lastDownLog.datetime * 1000).toLocaleString() : "";
|
||||
const downDuration = t("common.duration", { value: lastDownLog?.duration ?? 0 });
|
||||
const hideDown = !lastDownLog;
|
||||
const lastDown = hasLogs ? new Date(monitor.logs[logIndex].datetime * 1000).toLocaleString() : "";
|
||||
const downDuration = t("common.duration", { value: hasLogs ? monitor.logs[logIndex].duration : 0 });
|
||||
const hideDown = !hasLogs || (logIndex === 1 && monitor.logs[logIndex].type !== 1);
|
||||
|
||||
return (
|
||||
<Container service={service}>
|
||||
<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.downDuration" value={downDuration} />}
|
||||
</Container>
|
||||
|
||||
Reference in New Issue
Block a user