mirror of
https://github.com/gethomepage/homepage.git
synced 2025-12-06 21:57:48 +01:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bad436b858 | ||
|
|
7f0345a56a | ||
|
|
97bf174b78 | ||
|
|
975f79f6cc | ||
|
|
e72efe7fd0 | ||
|
|
d94b3e829d | ||
|
|
10c63939dc | ||
|
|
972ede9249 | ||
|
|
8f001ad88a | ||
|
|
229c5dac59 | ||
|
|
0622395ec7 | ||
|
|
66073ed460 | ||
|
|
533f40b536 | ||
|
|
13afe82fa5 | ||
|
|
10c27dfd84 | ||
|
|
057d5eca8f | ||
|
|
e89f3668a9 | ||
|
|
c46306fc1d | ||
|
|
76d534583b | ||
|
|
7b4f360a5e | ||
|
|
992b18c9de | ||
|
|
6291a5422a | ||
|
|
4581c4eeb0 | ||
|
|
d6d93e3c03 | ||
|
|
f40ca1e25c | ||
|
|
2d764ce59b | ||
|
|
a1841f26bb | ||
|
|
c4ab3eb992 | ||
|
|
617cbcaee1 | ||
|
|
a9a28e14df | ||
|
|
169c64f687 |
2
.github/workflows/docker-publish.yml
vendored
2
.github/workflows/docker-publish.yml
vendored
@@ -79,6 +79,8 @@ jobs:
|
|||||||
uses: docker/metadata-action@v4
|
uses: docker/metadata-action@v4
|
||||||
with:
|
with:
|
||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
flavor: |
|
||||||
|
latest=auto
|
||||||
|
|
||||||
# Build and push Docker image with Buildx (don't push on PR)
|
# Build and push Docker image with Buildx (don't push on PR)
|
||||||
# https://github.com/docker/build-push-action
|
# https://github.com/docker/build-push-action
|
||||||
|
|||||||
11
README.md
11
README.md
@@ -9,11 +9,12 @@
|
|||||||
- Docker Integration
|
- Docker Integration
|
||||||
- Status light + CPU, Memory & Network Reporting *(click on the status light)*
|
- Status light + CPU, Memory & Network Reporting *(click on the status light)*
|
||||||
- Service Integration
|
- Service Integration
|
||||||
- Currently supports Sonarr, Radarr, Ombi, Emby, Jellyfin, NZBGet, ruTorrent
|
- Currently supports Sonarr, Radarr, Ombi, Emby, Jellyfin, Jellyseerr ([by ilusi0n](https://github.com/benphelps/homepage/pull/34)), NZBGet, ruTorrent
|
||||||
- Portainer, Traefik, Speedtest Tracker, PiHole
|
- Portainer, Traefik, Speedtest Tracker, PiHole, Nginx Proxy Manager ([by aidenpwnz](https://github.com/benphelps/homepage/pull/45))
|
||||||
* Homepage Widgets
|
* Homepage Widgets
|
||||||
- System Stats (Disk, CPU, Memory)
|
- System Stats (Disk, CPU, Memory)
|
||||||
- Weather via WeatherAPI.com or OpenWeatherMap ([thanks to AlexFullmoon](https://github.com/benphelps/homepage/pull/25))
|
- Weather via WeatherAPI.com or OpenWeatherMap ([by AlexFullmoon](https://github.com/benphelps/homepage/pull/25))
|
||||||
|
- Search Bar ([by aidenpwnz](https://github.com/benphelps/homepage/pull/45))
|
||||||
* Customizable
|
* Customizable
|
||||||
- 21 theme colors with light and dark mode support
|
- 21 theme colors with light and dark mode support
|
||||||
|
|
||||||
@@ -35,7 +36,7 @@ Using docker compose:
|
|||||||
version: '3.3'
|
version: '3.3'
|
||||||
services:
|
services:
|
||||||
homepage:
|
homepage:
|
||||||
image: ghcr.io/benphelps/homepage:main
|
image: ghcr.io/benphelps/homepage:latest
|
||||||
container_name: homepage
|
container_name: homepage
|
||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
@@ -47,7 +48,7 @@ services:
|
|||||||
or docker run:
|
or docker run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -p 3000:3000 -v /path/to/config:/app/config -v /var/run/docker.sock:/var/run/docker.sock ghcr.io/benphelps/homepage:main
|
docker run -p 3000:3000 -v /path/to/config:/app/config -v /var/run/docker.sock:/var/run/docker.sock ghcr.io/benphelps/homepage:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
### With Node
|
### With Node
|
||||||
|
|||||||
@@ -10,10 +10,10 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^1.6.6",
|
"@headlessui/react": "^1.6.6",
|
||||||
"@tailwindcss/forms": "^0.5.2",
|
"@tailwindcss/forms": "^0.5.3",
|
||||||
"dockerode": "^3.3.3",
|
"dockerode": "^3.3.4",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"json-rpc-2.0": "^1.3.0",
|
"json-rpc-2.0": "^1.4.1",
|
||||||
"memory-cache": "^0.2.0",
|
"memory-cache": "^0.2.0",
|
||||||
"next": "12.2.5",
|
"next": "12.2.5",
|
||||||
"node-os-utils": "^1.3.7",
|
"node-os-utils": "^1.3.7",
|
||||||
@@ -30,6 +30,6 @@
|
|||||||
"eslint-config-next": "12.2.5",
|
"eslint-config-next": "12.2.5",
|
||||||
"postcss": "^8.4.16",
|
"postcss": "^8.4.16",
|
||||||
"tailwindcss": "^3.1.8",
|
"tailwindcss": "^3.1.8",
|
||||||
"typescript": "^4.7.4"
|
"typescript": "^4.8.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
207
pnpm-lock.yaml
generated
207
pnpm-lock.yaml
generated
@@ -2,13 +2,13 @@ lockfileVersion: 5.4
|
|||||||
|
|
||||||
specifiers:
|
specifiers:
|
||||||
'@headlessui/react': ^1.6.6
|
'@headlessui/react': ^1.6.6
|
||||||
'@tailwindcss/forms': ^0.5.2
|
'@tailwindcss/forms': ^0.5.3
|
||||||
autoprefixer: ^10.4.8
|
autoprefixer: ^10.4.8
|
||||||
dockerode: ^3.3.3
|
dockerode: ^3.3.4
|
||||||
eslint: 8.22.0
|
eslint: 8.22.0
|
||||||
eslint-config-next: 12.2.5
|
eslint-config-next: 12.2.5
|
||||||
js-yaml: ^4.1.0
|
js-yaml: ^4.1.0
|
||||||
json-rpc-2.0: ^1.3.0
|
json-rpc-2.0: ^1.4.1
|
||||||
memory-cache: ^0.2.0
|
memory-cache: ^0.2.0
|
||||||
next: 12.2.5
|
next: 12.2.5
|
||||||
node-os-utils: ^1.3.7
|
node-os-utils: ^1.3.7
|
||||||
@@ -20,14 +20,14 @@ specifiers:
|
|||||||
rutorrent-promise: ^2.0.0
|
rutorrent-promise: ^2.0.0
|
||||||
swr: ^1.3.0
|
swr: ^1.3.0
|
||||||
tailwindcss: ^3.1.8
|
tailwindcss: ^3.1.8
|
||||||
typescript: ^4.7.4
|
typescript: ^4.8.2
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@headlessui/react': 1.6.6_biqbaboplfbrettd7655fr4n2y
|
'@headlessui/react': 1.6.6_biqbaboplfbrettd7655fr4n2y
|
||||||
'@tailwindcss/forms': 0.5.2_tailwindcss@3.1.8
|
'@tailwindcss/forms': 0.5.3_tailwindcss@3.1.8
|
||||||
dockerode: 3.3.3
|
dockerode: 3.3.4
|
||||||
js-yaml: 4.1.0
|
js-yaml: 4.1.0
|
||||||
json-rpc-2.0: 1.3.0
|
json-rpc-2.0: 1.4.1
|
||||||
memory-cache: 0.2.0
|
memory-cache: 0.2.0
|
||||||
next: 12.2.5_biqbaboplfbrettd7655fr4n2y
|
next: 12.2.5_biqbaboplfbrettd7655fr4n2y
|
||||||
node-os-utils: 1.3.7
|
node-os-utils: 1.3.7
|
||||||
@@ -41,10 +41,10 @@ dependencies:
|
|||||||
devDependencies:
|
devDependencies:
|
||||||
autoprefixer: 10.4.8_postcss@8.4.16
|
autoprefixer: 10.4.8_postcss@8.4.16
|
||||||
eslint: 8.22.0
|
eslint: 8.22.0
|
||||||
eslint-config-next: 12.2.5_4rv7y5c6xz3vfxwhbrcxxi73bq
|
eslint-config-next: 12.2.5_shit3uhl6a7megkzgoz6xssnfa
|
||||||
postcss: 8.4.16
|
postcss: 8.4.16
|
||||||
tailwindcss: 3.1.8
|
tailwindcss: 3.1.8_postcss@8.4.16
|
||||||
typescript: 4.7.4
|
typescript: 4.8.2
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-qZEWeccZCrHA2Au4/X05QW5CMdm4VjUDCrGq5gf1ZDcM4hRqreKrtwAn7yci9zfgAS9apvnsFXiGBHBAxZdK9A==}
|
resolution: {integrity: sha512-qZEWeccZCrHA2Au4/X05QW5CMdm4VjUDCrGq5gf1ZDcM4hRqreKrtwAn7yci9zfgAS9apvnsFXiGBHBAxZdK9A==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
core-js-pure: 3.24.1
|
core-js-pure: 3.25.0
|
||||||
regenerator-runtime: 0.13.9
|
regenerator-runtime: 0.13.9
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
@@ -63,13 +63,17 @@ packages:
|
|||||||
regenerator-runtime: 0.13.9
|
regenerator-runtime: 0.13.9
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@eslint/eslintrc/1.3.0:
|
/@balena/dockerignore/1.0.2:
|
||||||
resolution: {integrity: sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==}
|
resolution: {integrity: sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@eslint/eslintrc/1.3.1:
|
||||||
|
resolution: {integrity: sha512-OhSY22oQQdw3zgPOOwdoj01l/Dzl1Z+xyUP33tkSN+aqyEhymJCcPHyXt+ylW8FSe0TfRC2VG+ROQOapD0aZSQ==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
dependencies:
|
dependencies:
|
||||||
ajv: 6.12.6
|
ajv: 6.12.6
|
||||||
debug: 4.3.4
|
debug: 4.3.4
|
||||||
espree: 9.3.3
|
espree: 9.4.0
|
||||||
globals: 13.17.0
|
globals: 13.17.0
|
||||||
ignore: 5.2.0
|
ignore: 5.2.0
|
||||||
import-fresh: 3.3.0
|
import-fresh: 3.3.0
|
||||||
@@ -265,21 +269,21 @@ packages:
|
|||||||
tslib: 2.4.0
|
tslib: 2.4.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@tailwindcss/forms/0.5.2_tailwindcss@3.1.8:
|
/@tailwindcss/forms/0.5.3_tailwindcss@3.1.8:
|
||||||
resolution: {integrity: sha512-pSrFeJB6Bg1Mrg9CdQW3+hqZXAKsBrSG9MAfFLKy1pVA4Mb4W7C0k7mEhlmS2Dfo/otxrQOET7NJiJ9RrS563w==}
|
resolution: {integrity: sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1'
|
tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1'
|
||||||
dependencies:
|
dependencies:
|
||||||
mini-svg-data-uri: 1.4.4
|
mini-svg-data-uri: 1.4.4
|
||||||
tailwindcss: 3.1.8
|
tailwindcss: 3.1.8_postcss@8.4.16
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@types/json5/0.0.29:
|
/@types/json5/0.0.29:
|
||||||
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@typescript-eslint/parser/5.33.0_4rv7y5c6xz3vfxwhbrcxxi73bq:
|
/@typescript-eslint/parser/5.36.1_shit3uhl6a7megkzgoz6xssnfa:
|
||||||
resolution: {integrity: sha512-cgM5cJrWmrDV2KpvlcSkelTBASAs1mgqq+IUGKJvFxWrapHpaRy5EXPQz9YaKF3nZ8KY18ILTiVpUtbIac86/w==}
|
resolution: {integrity: sha512-/IsgNGOkBi7CuDfUbwt1eOqUXF9WGVBW9dwEe1pi+L32XrTsZIgmDFIi2RxjzsvB/8i+MIf5JIoTEH8LOZ368A==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
|
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||||
@@ -288,31 +292,31 @@ packages:
|
|||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/scope-manager': 5.33.0
|
'@typescript-eslint/scope-manager': 5.36.1
|
||||||
'@typescript-eslint/types': 5.33.0
|
'@typescript-eslint/types': 5.36.1
|
||||||
'@typescript-eslint/typescript-estree': 5.33.0_typescript@4.7.4
|
'@typescript-eslint/typescript-estree': 5.36.1_typescript@4.8.2
|
||||||
debug: 4.3.4
|
debug: 4.3.4
|
||||||
eslint: 8.22.0
|
eslint: 8.22.0
|
||||||
typescript: 4.7.4
|
typescript: 4.8.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@typescript-eslint/scope-manager/5.33.0:
|
/@typescript-eslint/scope-manager/5.36.1:
|
||||||
resolution: {integrity: sha512-/Jta8yMNpXYpRDl8EwF/M8It2A9sFJTubDo0ATZefGXmOqlaBffEw0ZbkbQ7TNDK6q55NPHFshGBPAZvZkE8Pw==}
|
resolution: {integrity: sha512-pGC2SH3/tXdu9IH3ItoqciD3f3RRGCh7hb9zPdN2Drsr341zgd6VbhP5OHQO/reUqihNltfPpMpTNihFMarP2w==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 5.33.0
|
'@typescript-eslint/types': 5.36.1
|
||||||
'@typescript-eslint/visitor-keys': 5.33.0
|
'@typescript-eslint/visitor-keys': 5.36.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@typescript-eslint/types/5.33.0:
|
/@typescript-eslint/types/5.36.1:
|
||||||
resolution: {integrity: sha512-nIMt96JngB4MYFYXpZ/3ZNU4GWPNdBbcB5w2rDOCpXOVUkhtNlG2mmm8uXhubhidRZdwMaMBap7Uk8SZMU/ppw==}
|
resolution: {integrity: sha512-jd93ShpsIk1KgBTx9E+hCSEuLCUFwi9V/urhjOWnOaksGZFbTOxAT47OH2d4NLJnLhkVD+wDbB48BuaycZPLBg==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@typescript-eslint/typescript-estree/5.33.0_typescript@4.7.4:
|
/@typescript-eslint/typescript-estree/5.36.1_typescript@4.8.2:
|
||||||
resolution: {integrity: sha512-tqq3MRLlggkJKJUrzM6wltk8NckKyyorCSGMq4eVkyL5sDYzJJcMgZATqmF8fLdsWrW7OjjIZ1m9v81vKcaqwQ==}
|
resolution: {integrity: sha512-ih7V52zvHdiX6WcPjsOdmADhYMDN15SylWRZrT2OMy80wzKbc79n8wFW0xpWpU0x3VpBz/oDgTm2xwDAnFTl+g==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '*'
|
typescript: '*'
|
||||||
@@ -320,23 +324,23 @@ packages:
|
|||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 5.33.0
|
'@typescript-eslint/types': 5.36.1
|
||||||
'@typescript-eslint/visitor-keys': 5.33.0
|
'@typescript-eslint/visitor-keys': 5.36.1
|
||||||
debug: 4.3.4
|
debug: 4.3.4
|
||||||
globby: 11.1.0
|
globby: 11.1.0
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
semver: 7.3.7
|
semver: 7.3.7
|
||||||
tsutils: 3.21.0_typescript@4.7.4
|
tsutils: 3.21.0_typescript@4.8.2
|
||||||
typescript: 4.7.4
|
typescript: 4.8.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@typescript-eslint/visitor-keys/5.33.0:
|
/@typescript-eslint/visitor-keys/5.36.1:
|
||||||
resolution: {integrity: sha512-/XsqCzD4t+Y9p5wd9HZiptuGKBlaZO5showwqODii5C0nZawxWLF+Q6k5wYHBrQv96h6GYKyqqMHCSTqta8Kiw==}
|
resolution: {integrity: sha512-ojB9aRyRFzVMN3b5joSYni6FAS10BBSCAfKJhjJAV08t/a95aM6tAhz+O1jF+EtgxktuSO3wJysp2R+Def/IWQ==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 5.33.0
|
'@typescript-eslint/types': 5.36.1
|
||||||
eslint-visitor-keys: 3.3.0
|
eslint-visitor-keys: 3.3.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
@@ -418,7 +422,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.2
|
call-bind: 1.0.2
|
||||||
define-properties: 1.1.4
|
define-properties: 1.1.4
|
||||||
es-abstract: 1.20.1
|
es-abstract: 1.20.2
|
||||||
get-intrinsic: 1.1.2
|
get-intrinsic: 1.1.2
|
||||||
is-string: 1.0.7
|
is-string: 1.0.7
|
||||||
dev: true
|
dev: true
|
||||||
@@ -434,7 +438,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.2
|
call-bind: 1.0.2
|
||||||
define-properties: 1.1.4
|
define-properties: 1.1.4
|
||||||
es-abstract: 1.20.1
|
es-abstract: 1.20.2
|
||||||
es-shim-unscopables: 1.0.0
|
es-shim-unscopables: 1.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
@@ -444,7 +448,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.2
|
call-bind: 1.0.2
|
||||||
define-properties: 1.1.4
|
define-properties: 1.1.4
|
||||||
es-abstract: 1.20.1
|
es-abstract: 1.20.2
|
||||||
es-shim-unscopables: 1.0.0
|
es-shim-unscopables: 1.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
@@ -470,7 +474,7 @@ packages:
|
|||||||
postcss: ^8.1.0
|
postcss: ^8.1.0
|
||||||
dependencies:
|
dependencies:
|
||||||
browserslist: 4.21.3
|
browserslist: 4.21.3
|
||||||
caniuse-lite: 1.0.30001375
|
caniuse-lite: 1.0.30001388
|
||||||
fraction.js: 4.2.0
|
fraction.js: 4.2.0
|
||||||
normalize-range: 0.1.2
|
normalize-range: 0.1.2
|
||||||
picocolors: 1.0.0
|
picocolors: 1.0.0
|
||||||
@@ -531,10 +535,10 @@ packages:
|
|||||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
caniuse-lite: 1.0.30001375
|
caniuse-lite: 1.0.30001388
|
||||||
electron-to-chromium: 1.4.219
|
electron-to-chromium: 1.4.241
|
||||||
node-releases: 2.0.6
|
node-releases: 2.0.6
|
||||||
update-browserslist-db: 1.0.5_browserslist@4.21.3
|
update-browserslist-db: 1.0.7_browserslist@4.21.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/buffer/5.7.1:
|
/buffer/5.7.1:
|
||||||
@@ -571,8 +575,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
|
resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
/caniuse-lite/1.0.30001375:
|
/caniuse-lite/1.0.30001388:
|
||||||
resolution: {integrity: sha512-kWIMkNzLYxSvnjy0hL8w1NOaWNr2rn39RTAVyIwcw8juu60bZDWiF1/loOYANzjtJmy6qPgNmn38ro5Pygagdw==}
|
resolution: {integrity: sha512-znVbq4OUjqgLxMxoNX2ZeeLR0d7lcDiE5uJ4eUiWdml1J1EkxbnQq6opT9jb9SMfJxB0XA16/ziHwni4u1I3GQ==}
|
||||||
|
|
||||||
/chalk/4.1.2:
|
/chalk/4.1.2:
|
||||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||||
@@ -621,8 +625,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/core-js-pure/3.24.1:
|
/core-js-pure/3.25.0:
|
||||||
resolution: {integrity: sha512-r1nJk41QLLPyozHUUPmILCEMtMw24NG4oWK6RbsDdjzQgg9ZvrUsPBj1MnG0wXXp1DCDU6j+wUvEmBSrtRbLXg==}
|
resolution: {integrity: sha512-IeHpLwk3uoci37yoI2Laty59+YqH9x5uR65/yiA0ARAJrTrN4YU0rmauLWfvqOuk77SlNJXj2rM6oT/dBD87+A==}
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
@@ -734,8 +738,8 @@ packages:
|
|||||||
/dlv/1.1.3:
|
/dlv/1.1.3:
|
||||||
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
|
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
|
||||||
|
|
||||||
/docker-modem/3.0.5:
|
/docker-modem/3.0.6:
|
||||||
resolution: {integrity: sha512-x1E6jxWdtoK3+ifAUWj4w5egPdTDGBpesSCErm+aKET5BnnEOvDtTP6GxcnMB1zZiv2iQ0qJZvJie+1wfIRg6Q==}
|
resolution: {integrity: sha512-h0Ow21gclbYsZ3mkHDfsYNDqtRhXS8fXr51bU0qr1dxgTMJj0XufbzX+jhNOvA8KuEEzn6JbvLVhXyv+fny9Uw==}
|
||||||
engines: {node: '>= 8.0'}
|
engines: {node: '>= 8.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.3.4
|
debug: 4.3.4
|
||||||
@@ -746,11 +750,12 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/dockerode/3.3.3:
|
/dockerode/3.3.4:
|
||||||
resolution: {integrity: sha512-lvKV6/NGf2/CYLt5V4c0fd6Fl9XZSCo1Z2HBT9ioKrKLMB2o+gA62Uza8RROpzGvYv57KJx2dKu+ZwSpB//OIA==}
|
resolution: {integrity: sha512-3EUwuXnCU+RUlQEheDjmBE0B7q66PV9Rw5NiH1sXwINq0M9c5ERP9fxgkw36ZHOtzf4AGEEYySnkx/sACC9EgQ==}
|
||||||
engines: {node: '>= 8.0'}
|
engines: {node: '>= 8.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
docker-modem: 3.0.5
|
'@balena/dockerignore': 1.0.2
|
||||||
|
docker-modem: 3.0.6
|
||||||
tar-fs: 2.0.1
|
tar-fs: 2.0.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -770,8 +775,8 @@ packages:
|
|||||||
esutils: 2.0.3
|
esutils: 2.0.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/electron-to-chromium/1.4.219:
|
/electron-to-chromium/1.4.241:
|
||||||
resolution: {integrity: sha512-zoQJsXOUw0ZA0YxbjkmzBumAJRtr6je5JySuL/bAoFs0DuLiLJ+5FzRF7/ZayihxR2QcewlRZVm5QZdUhwjOgA==}
|
resolution: {integrity: sha512-e7Wsh4ilaioBZ5bMm6+F4V5c11dh56/5Jwz7Hl5Tu1J7cnB+Pqx5qIF2iC7HPpfyQMqGSvvLP5bBAIDd2gAtGw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/emoji-regex/9.2.2:
|
/emoji-regex/9.2.2:
|
||||||
@@ -784,8 +789,8 @@ packages:
|
|||||||
once: 1.4.0
|
once: 1.4.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/es-abstract/1.20.1:
|
/es-abstract/1.20.2:
|
||||||
resolution: {integrity: sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==}
|
resolution: {integrity: sha512-XxXQuVNrySBNlEkTYJoDNFe5+s2yIOpzq80sUHEdPdQr0S5nTLz4ZPPPswNIpKseDDUS5yghX1gfLIHQZ1iNuQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.2
|
call-bind: 1.0.2
|
||||||
@@ -806,7 +811,7 @@ packages:
|
|||||||
is-weakref: 1.0.2
|
is-weakref: 1.0.2
|
||||||
object-inspect: 1.12.2
|
object-inspect: 1.12.2
|
||||||
object-keys: 1.1.1
|
object-keys: 1.1.1
|
||||||
object.assign: 4.1.3
|
object.assign: 4.1.4
|
||||||
regexp.prototype.flags: 1.4.3
|
regexp.prototype.flags: 1.4.3
|
||||||
string.prototype.trimend: 1.0.5
|
string.prototype.trimend: 1.0.5
|
||||||
string.prototype.trimstart: 1.0.5
|
string.prototype.trimstart: 1.0.5
|
||||||
@@ -838,7 +843,7 @@ packages:
|
|||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/eslint-config-next/12.2.5_4rv7y5c6xz3vfxwhbrcxxi73bq:
|
/eslint-config-next/12.2.5_shit3uhl6a7megkzgoz6xssnfa:
|
||||||
resolution: {integrity: sha512-SOowilkqPzW6DxKp3a3SYlrfPi5Ajs9MIzp9gVfUDxxH9QFM5ElkR1hX5m/iICJuvCbWgQqFBiA3mCMozluniw==}
|
resolution: {integrity: sha512-SOowilkqPzW6DxKp3a3SYlrfPi5Ajs9MIzp9gVfUDxxH9QFM5ElkR1hX5m/iICJuvCbWgQqFBiA3mCMozluniw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^7.23.0 || ^8.0.0
|
eslint: ^7.23.0 || ^8.0.0
|
||||||
@@ -849,15 +854,15 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@next/eslint-plugin-next': 12.2.5
|
'@next/eslint-plugin-next': 12.2.5
|
||||||
'@rushstack/eslint-patch': 1.1.4
|
'@rushstack/eslint-patch': 1.1.4
|
||||||
'@typescript-eslint/parser': 5.33.0_4rv7y5c6xz3vfxwhbrcxxi73bq
|
'@typescript-eslint/parser': 5.36.1_shit3uhl6a7megkzgoz6xssnfa
|
||||||
eslint: 8.22.0
|
eslint: 8.22.0
|
||||||
eslint-import-resolver-node: 0.3.6
|
eslint-import-resolver-node: 0.3.6
|
||||||
eslint-import-resolver-typescript: 2.7.1_2iahngt3u2tkbdlu6s4gkur3pu
|
eslint-import-resolver-typescript: 2.7.1_2iahngt3u2tkbdlu6s4gkur3pu
|
||||||
eslint-plugin-import: 2.26.0_42jdfezp7lcuhr3fexihng3k3a
|
eslint-plugin-import: 2.26.0_oqagwj4pgbbpoeodu52b4a34xi
|
||||||
eslint-plugin-jsx-a11y: 6.6.1_eslint@8.22.0
|
eslint-plugin-jsx-a11y: 6.6.1_eslint@8.22.0
|
||||||
eslint-plugin-react: 7.30.1_eslint@8.22.0
|
eslint-plugin-react: 7.31.5_eslint@8.22.0
|
||||||
eslint-plugin-react-hooks: 4.6.0_eslint@8.22.0
|
eslint-plugin-react-hooks: 4.6.0_eslint@8.22.0
|
||||||
typescript: 4.7.4
|
typescript: 4.8.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- eslint-import-resolver-webpack
|
- eslint-import-resolver-webpack
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -881,7 +886,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.3.4
|
debug: 4.3.4
|
||||||
eslint: 8.22.0
|
eslint: 8.22.0
|
||||||
eslint-plugin-import: 2.26.0_42jdfezp7lcuhr3fexihng3k3a
|
eslint-plugin-import: 2.26.0_oqagwj4pgbbpoeodu52b4a34xi
|
||||||
glob: 7.2.3
|
glob: 7.2.3
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
resolve: 1.22.1
|
resolve: 1.22.1
|
||||||
@@ -890,7 +895,7 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/eslint-module-utils/2.7.4_2znkvn5grarov6xvamxzzjpwtu:
|
/eslint-module-utils/2.7.4_56k6siz3uc4k5fooi777xiw6ie:
|
||||||
resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==}
|
resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -911,7 +916,7 @@ packages:
|
|||||||
eslint-import-resolver-webpack:
|
eslint-import-resolver-webpack:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/parser': 5.33.0_4rv7y5c6xz3vfxwhbrcxxi73bq
|
'@typescript-eslint/parser': 5.36.1_shit3uhl6a7megkzgoz6xssnfa
|
||||||
debug: 3.2.7
|
debug: 3.2.7
|
||||||
eslint: 8.22.0
|
eslint: 8.22.0
|
||||||
eslint-import-resolver-node: 0.3.6
|
eslint-import-resolver-node: 0.3.6
|
||||||
@@ -920,7 +925,7 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/eslint-plugin-import/2.26.0_42jdfezp7lcuhr3fexihng3k3a:
|
/eslint-plugin-import/2.26.0_oqagwj4pgbbpoeodu52b4a34xi:
|
||||||
resolution: {integrity: sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==}
|
resolution: {integrity: sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -930,14 +935,14 @@ packages:
|
|||||||
'@typescript-eslint/parser':
|
'@typescript-eslint/parser':
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/parser': 5.33.0_4rv7y5c6xz3vfxwhbrcxxi73bq
|
'@typescript-eslint/parser': 5.36.1_shit3uhl6a7megkzgoz6xssnfa
|
||||||
array-includes: 3.1.5
|
array-includes: 3.1.5
|
||||||
array.prototype.flat: 1.3.0
|
array.prototype.flat: 1.3.0
|
||||||
debug: 2.6.9
|
debug: 2.6.9
|
||||||
doctrine: 2.1.0
|
doctrine: 2.1.0
|
||||||
eslint: 8.22.0
|
eslint: 8.22.0
|
||||||
eslint-import-resolver-node: 0.3.6
|
eslint-import-resolver-node: 0.3.6
|
||||||
eslint-module-utils: 2.7.4_2znkvn5grarov6xvamxzzjpwtu
|
eslint-module-utils: 2.7.4_56k6siz3uc4k5fooi777xiw6ie
|
||||||
has: 1.0.3
|
has: 1.0.3
|
||||||
is-core-module: 2.10.0
|
is-core-module: 2.10.0
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
@@ -982,8 +987,8 @@ packages:
|
|||||||
eslint: 8.22.0
|
eslint: 8.22.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/eslint-plugin-react/7.30.1_eslint@8.22.0:
|
/eslint-plugin-react/7.31.5_eslint@8.22.0:
|
||||||
resolution: {integrity: sha512-NbEvI9jtqO46yJA3wcRF9Mo0lF9T/jhdHqhCHXiXtD+Zcb98812wvokjWpU7Q4QH5edo6dmqrukxVvWWXHlsUg==}
|
resolution: {integrity: sha512-y7472VAcqns17rsQUk6tQCnqBi+boYjGdYarX022719+wGd1T4U1fOYJ2T2Trd3Od2q5M92e42zJ2uZOGmWamA==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8
|
eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8
|
||||||
@@ -1038,7 +1043,7 @@ packages:
|
|||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint/eslintrc': 1.3.0
|
'@eslint/eslintrc': 1.3.1
|
||||||
'@humanwhocodes/config-array': 0.10.4
|
'@humanwhocodes/config-array': 0.10.4
|
||||||
'@humanwhocodes/gitignore-to-minimatch': 1.0.2
|
'@humanwhocodes/gitignore-to-minimatch': 1.0.2
|
||||||
ajv: 6.12.6
|
ajv: 6.12.6
|
||||||
@@ -1050,7 +1055,7 @@ packages:
|
|||||||
eslint-scope: 7.1.1
|
eslint-scope: 7.1.1
|
||||||
eslint-utils: 3.0.0_eslint@8.22.0
|
eslint-utils: 3.0.0_eslint@8.22.0
|
||||||
eslint-visitor-keys: 3.3.0
|
eslint-visitor-keys: 3.3.0
|
||||||
espree: 9.3.3
|
espree: 9.4.0
|
||||||
esquery: 1.4.0
|
esquery: 1.4.0
|
||||||
esutils: 2.0.3
|
esutils: 2.0.3
|
||||||
fast-deep-equal: 3.1.3
|
fast-deep-equal: 3.1.3
|
||||||
@@ -1081,8 +1086,8 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/espree/9.3.3:
|
/espree/9.4.0:
|
||||||
resolution: {integrity: sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==}
|
resolution: {integrity: sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.8.0
|
acorn: 8.8.0
|
||||||
@@ -1166,12 +1171,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==}
|
resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==}
|
||||||
engines: {node: ^10.12.0 || >=12.0.0}
|
engines: {node: ^10.12.0 || >=12.0.0}
|
||||||
dependencies:
|
dependencies:
|
||||||
flatted: 3.2.6
|
flatted: 3.2.7
|
||||||
rimraf: 3.0.2
|
rimraf: 3.0.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/flatted/3.2.6:
|
/flatted/3.2.7:
|
||||||
resolution: {integrity: sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==}
|
resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/form-data/3.0.1:
|
/form-data/3.0.1:
|
||||||
@@ -1211,7 +1216,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.2
|
call-bind: 1.0.2
|
||||||
define-properties: 1.1.4
|
define-properties: 1.1.4
|
||||||
es-abstract: 1.20.1
|
es-abstract: 1.20.2
|
||||||
functions-have-names: 1.2.3
|
functions-have-names: 1.2.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
@@ -1498,8 +1503,8 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
argparse: 2.0.1
|
argparse: 2.0.1
|
||||||
|
|
||||||
/json-rpc-2.0/1.3.0:
|
/json-rpc-2.0/1.4.1:
|
||||||
resolution: {integrity: sha512-pA85D9LG4B+b8njdb8DzktltNfGPcxGJJydEg6jRqePfwhK008lRdbX7bco6aokfw9oDFw7fEe+LRxxXwP11HA==}
|
resolution: {integrity: sha512-OX1NJhpIfuK4GjDnJ/gKtZy1HOYo0l4eL0a4rb0rNeQheX1xlyQ9+JMmPzs/sFNthpS/TXKPWlGo09X7B5l81A==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/json-schema-traverse/0.4.1:
|
/json-schema-traverse/0.4.1:
|
||||||
@@ -1522,7 +1527,7 @@ packages:
|
|||||||
engines: {node: '>=4.0'}
|
engines: {node: '>=4.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
array-includes: 3.1.5
|
array-includes: 3.1.5
|
||||||
object.assign: 4.1.3
|
object.assign: 4.1.4
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/language-subtag-registry/0.3.22:
|
/language-subtag-registry/0.3.22:
|
||||||
@@ -1661,7 +1666,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@next/env': 12.2.5
|
'@next/env': 12.2.5
|
||||||
'@swc/helpers': 0.4.3
|
'@swc/helpers': 0.4.3
|
||||||
caniuse-lite: 1.0.30001375
|
caniuse-lite: 1.0.30001388
|
||||||
postcss: 8.4.14
|
postcss: 8.4.14
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0_react@18.2.0
|
react-dom: 18.2.0_react@18.2.0
|
||||||
@@ -1733,8 +1738,8 @@ packages:
|
|||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/object.assign/4.1.3:
|
/object.assign/4.1.4:
|
||||||
resolution: {integrity: sha512-ZFJnX3zltyjcYJL0RoCJuzb+11zWGyaDbjgxZbdV7rFEcHQuYxrZqhow67aA7xpes6LhojyFDaBKAFfogQrikA==}
|
resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.2
|
call-bind: 1.0.2
|
||||||
@@ -1749,7 +1754,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.2
|
call-bind: 1.0.2
|
||||||
define-properties: 1.1.4
|
define-properties: 1.1.4
|
||||||
es-abstract: 1.20.1
|
es-abstract: 1.20.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/object.fromentries/2.0.5:
|
/object.fromentries/2.0.5:
|
||||||
@@ -1758,14 +1763,14 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.2
|
call-bind: 1.0.2
|
||||||
define-properties: 1.1.4
|
define-properties: 1.1.4
|
||||||
es-abstract: 1.20.1
|
es-abstract: 1.20.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/object.hasown/1.1.1:
|
/object.hasown/1.1.1:
|
||||||
resolution: {integrity: sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==}
|
resolution: {integrity: sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==}
|
||||||
dependencies:
|
dependencies:
|
||||||
define-properties: 1.1.4
|
define-properties: 1.1.4
|
||||||
es-abstract: 1.20.1
|
es-abstract: 1.20.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/object.values/1.1.5:
|
/object.values/1.1.5:
|
||||||
@@ -1774,7 +1779,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.2
|
call-bind: 1.0.2
|
||||||
define-properties: 1.1.4
|
define-properties: 1.1.4
|
||||||
es-abstract: 1.20.1
|
es-abstract: 1.20.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/once/1.4.0:
|
/once/1.4.0:
|
||||||
@@ -2163,7 +2168,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.2
|
call-bind: 1.0.2
|
||||||
define-properties: 1.1.4
|
define-properties: 1.1.4
|
||||||
es-abstract: 1.20.1
|
es-abstract: 1.20.2
|
||||||
get-intrinsic: 1.1.2
|
get-intrinsic: 1.1.2
|
||||||
has-symbols: 1.0.3
|
has-symbols: 1.0.3
|
||||||
internal-slot: 1.0.3
|
internal-slot: 1.0.3
|
||||||
@@ -2176,7 +2181,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.2
|
call-bind: 1.0.2
|
||||||
define-properties: 1.1.4
|
define-properties: 1.1.4
|
||||||
es-abstract: 1.20.1
|
es-abstract: 1.20.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/string.prototype.trimstart/1.0.5:
|
/string.prototype.trimstart/1.0.5:
|
||||||
@@ -2184,7 +2189,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.2
|
call-bind: 1.0.2
|
||||||
define-properties: 1.1.4
|
define-properties: 1.1.4
|
||||||
es-abstract: 1.20.1
|
es-abstract: 1.20.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/string_decoder/1.3.0:
|
/string_decoder/1.3.0:
|
||||||
@@ -2245,10 +2250,12 @@ packages:
|
|||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/tailwindcss/3.1.8:
|
/tailwindcss/3.1.8_postcss@8.4.16:
|
||||||
resolution: {integrity: sha512-YSneUCZSFDYMwk+TGq8qYFdCA3yfBRdBlS7txSq0LUmzyeqRe3a8fBQzbz9M3WS/iFT4BNf/nmw9mEzrnSaC0g==}
|
resolution: {integrity: sha512-YSneUCZSFDYMwk+TGq8qYFdCA3yfBRdBlS7txSq0LUmzyeqRe3a8fBQzbz9M3WS/iFT4BNf/nmw9mEzrnSaC0g==}
|
||||||
engines: {node: '>=12.13.0'}
|
engines: {node: '>=12.13.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
peerDependencies:
|
||||||
|
postcss: ^8.0.9
|
||||||
dependencies:
|
dependencies:
|
||||||
arg: 5.0.2
|
arg: 5.0.2
|
||||||
chokidar: 3.5.3
|
chokidar: 3.5.3
|
||||||
@@ -2331,14 +2338,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==}
|
resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/tsutils/3.21.0_typescript@4.7.4:
|
/tsutils/3.21.0_typescript@4.8.2:
|
||||||
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
|
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta'
|
typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta'
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 1.14.1
|
tslib: 1.14.1
|
||||||
typescript: 4.7.4
|
typescript: 4.8.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/tweetnacl/0.14.5:
|
/tweetnacl/0.14.5:
|
||||||
@@ -2357,8 +2364,8 @@ packages:
|
|||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/typescript/4.7.4:
|
/typescript/4.8.2:
|
||||||
resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==}
|
resolution: {integrity: sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==}
|
||||||
engines: {node: '>=4.2.0'}
|
engines: {node: '>=4.2.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
@@ -2377,8 +2384,8 @@ packages:
|
|||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/update-browserslist-db/1.0.5_browserslist@4.21.3:
|
/update-browserslist-db/1.0.7_browserslist@4.21.3:
|
||||||
resolution: {integrity: sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q==}
|
resolution: {integrity: sha512-iN/XYesmZ2RmmWAiI4Z5rq0YqSiv0brj9Ce9CfhNE4xIW2h+MFxcgkxIzZ+ShkFPUkjU3gQ+3oypadD3RAMtrg==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
browserslist: '>= 4.21.0'
|
browserslist: '>= 4.21.0'
|
||||||
|
|||||||
@@ -21,14 +21,19 @@ function resolveIcon(icon) {
|
|||||||
|
|
||||||
export default function Item({ service }) {
|
export default function Item({ service }) {
|
||||||
return (
|
return (
|
||||||
<li key={service.name} className="">
|
<li key={service.name}>
|
||||||
<Disclosure>
|
<Disclosure>
|
||||||
<div className="transition-all h-15 overflow-hidden mb-3 cursor-pointer p-1 rounded-md font-medium text-theme-700 hover:text-theme-800 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-theme-900/10 dark:shadow-theme-900/40 bg-white/50 hover:bg-theme-300/10 dark:bg-white/5 dark:hover:bg-white/10">
|
<div className={
|
||||||
|
(service.href && service.href !== "#" ? 'cursor-pointer ' : 'cursor-default ') +
|
||||||
|
'transition-all h-15 overflow-hidden mb-3 p-1 rounded-md font-medium text-theme-700 hover:text-theme-800 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-theme-900/10 dark:shadow-theme-900/40 bg-white/50 hover:bg-theme-300/10 dark:bg-white/5 dark:hover:bg-white/10'
|
||||||
|
}>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
{service.icon && (
|
{service.icon && (
|
||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.open(service.href, "_blank").focus();
|
if (service.href && service.href !== "#") {
|
||||||
|
window.open(service.href, "_blank").focus();
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
className="flex-shrink-0 flex items-center justify-center w-12 "
|
className="flex-shrink-0 flex items-center justify-center w-12 "
|
||||||
>
|
>
|
||||||
@@ -38,7 +43,9 @@ export default function Item({ service }) {
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.open(service.href, "_blank").focus();
|
if (service.href && service.href !== "#") {
|
||||||
|
window.open(service.href, "_blank").focus();
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
className="flex-1 flex items-center justify-between rounded-r-md "
|
className="flex-1 flex items-center justify-between rounded-r-md "
|
||||||
>
|
>
|
||||||
@@ -48,7 +55,7 @@ export default function Item({ service }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{service.container && (
|
{service.container && (
|
||||||
<Disclosure.Button as="div" className="flex-shrink-0 flex items-center justify-center w-12 ">
|
<Disclosure.Button as="div" className="flex-shrink-0 flex items-center justify-center w-12 cursor-pointer">
|
||||||
<Status service={service} />
|
<Status service={service} />
|
||||||
</Disclosure.Button>
|
</Disclosure.Button>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import Rutorrent from "./widgets/service/rutorrent";
|
|||||||
import Jellyfin from "./widgets/service/jellyfin";
|
import Jellyfin from "./widgets/service/jellyfin";
|
||||||
import Speedtest from "./widgets/service/speedtest";
|
import Speedtest from "./widgets/service/speedtest";
|
||||||
import Traefik from "./widgets/service/traefik";
|
import Traefik from "./widgets/service/traefik";
|
||||||
|
import Jellyseerr from "./widgets/service/jellyseerr";
|
||||||
|
import Npm from "./widgets/service/npm";
|
||||||
|
import Tautulli from "./widgets/service/tautulli";
|
||||||
|
|
||||||
const widgetMappings = {
|
const widgetMappings = {
|
||||||
docker: Docker,
|
docker: Docker,
|
||||||
@@ -24,6 +27,9 @@ const widgetMappings = {
|
|||||||
rutorrent: Rutorrent,
|
rutorrent: Rutorrent,
|
||||||
speedtest: Speedtest,
|
speedtest: Speedtest,
|
||||||
traefik: Traefik,
|
traefik: Traefik,
|
||||||
|
jellyseerr: Jellyseerr,
|
||||||
|
npm: Npm,
|
||||||
|
tautulli: Tautulli,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Widget({ service }) {
|
export default function Widget({ service }) {
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
import { calculateCPUPercent, formatBytes } from "utils/stats-helpers";
|
|
||||||
|
|
||||||
import Widget from "../widget";
|
import Widget from "../widget";
|
||||||
import Block from "../block";
|
import Block from "../block";
|
||||||
|
|
||||||
|
import { calculateCPUPercent, formatBytes } from "utils/stats-helpers";
|
||||||
|
|
||||||
export default function Docker({ service }) {
|
export default function Docker({ service }) {
|
||||||
const config = service.widget;
|
const config = service.widget;
|
||||||
|
|
||||||
const { data: statusData, error: statusError } = useSWR(
|
const { data: statusData, error: statusError } = useSWR(
|
||||||
`/api/docker/status/${config.container}/${config.server || ""}`,
|
`/api/docker/status/${config.container}/${config.server || ""}`,
|
||||||
{
|
{
|
||||||
refreshInterval: 1500,
|
refreshInterval: 5000,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data: statsData, error: statsError } = useSWR(
|
const { data: statsData, error: statsError } = useSWR(
|
||||||
`/api/docker/stats/${config.container}/${config.server || ""}`,
|
`/api/docker/stats/${config.container}/${config.server || ""}`,
|
||||||
{
|
{
|
||||||
refreshInterval: 1500,
|
refreshInterval: 5000,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -49,8 +49,12 @@ export default function Docker({ service }) {
|
|||||||
<Widget>
|
<Widget>
|
||||||
<Block label="CPU" value={`${calculateCPUPercent(statsData.stats)}%`} />
|
<Block label="CPU" value={`${calculateCPUPercent(statsData.stats)}%`} />
|
||||||
<Block label="MEM" value={formatBytes(statsData.stats.memory_stats.usage, 0)} />
|
<Block label="MEM" value={formatBytes(statsData.stats.memory_stats.usage, 0)} />
|
||||||
<Block label="RX" value={formatBytes(statsData.stats.networks.eth0.rx_bytes, 0)} />
|
{statsData.stats.networks && (
|
||||||
<Block label="TX" value={formatBytes(statsData.stats.networks.eth0.tx_bytes, 0)} />
|
<>
|
||||||
|
<Block label="RX" value={formatBytes(statsData.stats.networks.eth0.rx_bytes, 0)} />
|
||||||
|
<Block label="TX" value={formatBytes(statsData.stats.networks.eth0.tx_bytes, 0)} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Widget>
|
</Widget>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,17 +3,12 @@ import useSWR from "swr";
|
|||||||
import Widget from "../widget";
|
import Widget from "../widget";
|
||||||
import Block from "../block";
|
import Block from "../block";
|
||||||
|
|
||||||
|
import { formatApiUrl } from "utils/api-helpers";
|
||||||
|
|
||||||
export default function Emby({ service, title = "Emby" }) {
|
export default function Emby({ service, title = "Emby" }) {
|
||||||
const config = service.widget;
|
const config = service.widget;
|
||||||
|
|
||||||
function buildApiUrl(endpoint) {
|
const { data: sessionsData, error: sessionsError } = useSWR(formatApiUrl(config, "Sessions"));
|
||||||
const { url, key } = config;
|
|
||||||
return `${url}/emby/${endpoint}?api_key=${key}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data: sessionsData, error: sessionsError } = useSWR(buildApiUrl(`Sessions`), {
|
|
||||||
refreshInterval: 1000,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (sessionsError) {
|
if (sessionsError) {
|
||||||
return <Widget error={`${title} API Error`} />;
|
return <Widget error={`${title} API Error`} />;
|
||||||
|
|||||||
34
src/components/services/widgets/service/jellyseerr.jsx
Normal file
34
src/components/services/widgets/service/jellyseerr.jsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import useSWR from "swr";
|
||||||
|
|
||||||
|
import Widget from "../widget";
|
||||||
|
import Block from "../block";
|
||||||
|
|
||||||
|
import { formatApiUrl } from "utils/api-helpers";
|
||||||
|
|
||||||
|
export default function Jellyseerr({ service }) {
|
||||||
|
const config = service.widget;
|
||||||
|
|
||||||
|
const { data: statsData, error: statsError } = useSWR(formatApiUrl(config, `request/count`));
|
||||||
|
|
||||||
|
if (statsError) {
|
||||||
|
return <Widget error="Jellyseerr API Error" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!statsData) {
|
||||||
|
return (
|
||||||
|
<Widget>
|
||||||
|
<Block label="Pending" />
|
||||||
|
<Block label="Approved" />
|
||||||
|
<Block label="Available" />
|
||||||
|
</Widget>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Widget>
|
||||||
|
<Block label="Pending" value={statsData.pending} />
|
||||||
|
<Block label="Approved" value={statsData.approved} />
|
||||||
|
<Block label="Available" value={statsData.available} />
|
||||||
|
</Widget>
|
||||||
|
);
|
||||||
|
}
|
||||||
38
src/components/services/widgets/service/npm.jsx
Normal file
38
src/components/services/widgets/service/npm.jsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import useSWR from "swr";
|
||||||
|
|
||||||
|
import Widget from "../widget";
|
||||||
|
import Block from "../block";
|
||||||
|
|
||||||
|
import { formatApiUrl } from "utils/api-helpers";
|
||||||
|
|
||||||
|
export default function Npm({ service }) {
|
||||||
|
const config = service.widget;
|
||||||
|
|
||||||
|
const { data: infoData, error: infoError } = useSWR(formatApiUrl(config, "nginx/proxy-hosts"));
|
||||||
|
|
||||||
|
if (infoError) {
|
||||||
|
return <Widget error="NGINX Proxy Manager API Error" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!infoData) {
|
||||||
|
return (
|
||||||
|
<Widget>
|
||||||
|
<Block label="Enabled" />
|
||||||
|
<Block label="Disabled" />
|
||||||
|
<Block label="Total" />
|
||||||
|
</Widget>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const enabled = infoData.filter((c) => c.enabled === 1).length;
|
||||||
|
const disabled = infoData.filter((c) => c.enabled === 0).length;
|
||||||
|
const total = infoData.length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Widget>
|
||||||
|
<Block label="Enabled" value={enabled} />
|
||||||
|
<Block label="Disabled" value={disabled} />
|
||||||
|
<Block label="Total" value={total} />
|
||||||
|
</Widget>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,44 +1,15 @@
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { JSONRPCClient } from "json-rpc-2.0";
|
|
||||||
|
|
||||||
import { formatBytes } from "utils/stats-helpers";
|
|
||||||
|
|
||||||
import Widget from "../widget";
|
import Widget from "../widget";
|
||||||
import Block from "../block";
|
import Block from "../block";
|
||||||
|
|
||||||
|
import { formatApiUrl } from "utils/api-helpers";
|
||||||
|
import { formatBytes } from "utils/stats-helpers";
|
||||||
|
|
||||||
export default function Nzbget({ service }) {
|
export default function Nzbget({ service }) {
|
||||||
const config = service.widget;
|
const config = service.widget;
|
||||||
|
|
||||||
const constructedUrl = new URL(config.url);
|
const { data: statusData, error: statusError } = useSWR(formatApiUrl(config, "status"));
|
||||||
constructedUrl.pathname = "jsonrpc";
|
|
||||||
|
|
||||||
const client = new JSONRPCClient((jsonRPCRequest) =>
|
|
||||||
fetch(constructedUrl.toString(), {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"content-type": "application/json",
|
|
||||||
authorization: `Basic ${btoa(`${config.username}:${config.password}`)}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify(jsonRPCRequest),
|
|
||||||
}).then(async (response) => {
|
|
||||||
if (response.status === 200) {
|
|
||||||
const jsonRPCResponse = await response.json();
|
|
||||||
return client.receive(jsonRPCResponse);
|
|
||||||
} else if (jsonRPCRequest.id !== undefined) {
|
|
||||||
return Promise.reject(new Error(response.statusText));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: statusData, error: statusError } = useSWR(
|
|
||||||
"status",
|
|
||||||
(resource) => {
|
|
||||||
return client.request(resource).then((response) => response);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
refreshInterval: 1000,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (statusError) {
|
if (statusError) {
|
||||||
return <Widget error="Nzbget API Error" />;
|
return <Widget error="Nzbget API Error" />;
|
||||||
|
|||||||
@@ -3,27 +3,12 @@ import useSWR from "swr";
|
|||||||
import Widget from "../widget";
|
import Widget from "../widget";
|
||||||
import Block from "../block";
|
import Block from "../block";
|
||||||
|
|
||||||
|
import { formatApiUrl } from "utils/api-helpers";
|
||||||
|
|
||||||
export default function Ombi({ service }) {
|
export default function Ombi({ service }) {
|
||||||
const config = service.widget;
|
const config = service.widget;
|
||||||
|
|
||||||
function buildApiUrl(endpoint) {
|
const { data: statsData, error: statsError } = useSWR(formatApiUrl(config, `Request/count`));
|
||||||
const { url } = config;
|
|
||||||
return `${url}/api/v1/${endpoint}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetcher = (url) => {
|
|
||||||
return fetch(url, {
|
|
||||||
method: "GET",
|
|
||||||
withCredentials: true,
|
|
||||||
credentials: "include",
|
|
||||||
headers: {
|
|
||||||
ApiKey: `${config.key}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
}).then((res) => res.json());
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data: statsData, error: statsError } = useSWR(buildApiUrl(`Request/count`), fetcher);
|
|
||||||
|
|
||||||
if (statsError) {
|
if (statsError) {
|
||||||
return <Widget error="Ombi API Error" />;
|
return <Widget error="Ombi API Error" />;
|
||||||
|
|||||||
@@ -3,21 +3,12 @@ import useSWR from "swr";
|
|||||||
import Widget from "../widget";
|
import Widget from "../widget";
|
||||||
import Block from "../block";
|
import Block from "../block";
|
||||||
|
|
||||||
|
import { formatApiUrl } from "utils/api-helpers";
|
||||||
|
|
||||||
export default function Pihole({ service }) {
|
export default function Pihole({ service }) {
|
||||||
const config = service.widget;
|
const config = service.widget;
|
||||||
|
|
||||||
function buildApiUrl(endpoint) {
|
const { data: piholeData, error: piholeError } = useSWR(formatApiUrl(config, "api.php"));
|
||||||
const { url, proxy } = config;
|
|
||||||
|
|
||||||
if (proxy) {
|
|
||||||
const fullUrl = `${url}/admin/${endpoint}`;
|
|
||||||
return "/api/proxy?url=" + encodeURIComponent(fullUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${url}/admin/${endpoint}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data: piholeData, error: piholeError } = useSWR(buildApiUrl("api.php"));
|
|
||||||
|
|
||||||
if (piholeError) {
|
if (piholeError) {
|
||||||
return <Widget error="PiHole API Error" />;
|
return <Widget error="PiHole API Error" />;
|
||||||
|
|||||||
@@ -3,29 +3,12 @@ import useSWR from "swr";
|
|||||||
import Widget from "../widget";
|
import Widget from "../widget";
|
||||||
import Block from "../block";
|
import Block from "../block";
|
||||||
|
|
||||||
|
import { formatApiUrl } from "utils/api-helpers";
|
||||||
|
|
||||||
export default function Portainer({ service }) {
|
export default function Portainer({ service }) {
|
||||||
const config = service.widget;
|
const config = service.widget;
|
||||||
|
|
||||||
function buildApiUrl(endpoint) {
|
const { data: containersData, error: containersError } = useSWR(formatApiUrl(config, `docker/containers/json?all=1`));
|
||||||
const { url, env } = config;
|
|
||||||
const reqUrl = new URL(`/api/endpoints/${env}/${endpoint}`, url);
|
|
||||||
return `/api/proxy?url=${encodeURIComponent(reqUrl)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetcher = async (url) => {
|
|
||||||
const res = await fetch(url, {
|
|
||||||
method: "GET",
|
|
||||||
withCredentials: true,
|
|
||||||
credentials: "include",
|
|
||||||
headers: {
|
|
||||||
"X-API-Key": `${config.key}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return await res.json();
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data: containersData, error: containersError } = useSWR(buildApiUrl(`docker/containers/json?all=1`), fetcher);
|
|
||||||
|
|
||||||
if (containersError) {
|
if (containersError) {
|
||||||
return <Widget error="Portainer API Error" />;
|
return <Widget error="Portainer API Error" />;
|
||||||
|
|||||||
@@ -3,16 +3,13 @@ import useSWR from "swr";
|
|||||||
import Widget from "../widget";
|
import Widget from "../widget";
|
||||||
import Block from "../block";
|
import Block from "../block";
|
||||||
|
|
||||||
|
import { formatApiUrl } from "utils/api-helpers";
|
||||||
|
|
||||||
export default function Radarr({ service }) {
|
export default function Radarr({ service }) {
|
||||||
const config = service.widget;
|
const config = service.widget;
|
||||||
|
|
||||||
function buildApiUrl(endpoint) {
|
const { data: moviesData, error: moviesError } = useSWR(formatApiUrl(config, "movie"));
|
||||||
const { url, key } = config;
|
const { data: queuedData, error: queuedError } = useSWR(formatApiUrl(config, "queue/status"));
|
||||||
return `${url}/api/v3/${endpoint}?apikey=${key}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data: moviesData, error: moviesError } = useSWR(buildApiUrl("movie"));
|
|
||||||
const { data: queuedData, error: queuedError } = useSWR(buildApiUrl("queue/status"));
|
|
||||||
|
|
||||||
if (moviesError || queuedError) {
|
if (moviesError || queuedError) {
|
||||||
return <Widget error="Radarr API Error" />;
|
return <Widget error="Radarr API Error" />;
|
||||||
@@ -35,7 +32,7 @@ export default function Radarr({ service }) {
|
|||||||
<Widget>
|
<Widget>
|
||||||
<Block label="Wanted" value={wanted.length} />
|
<Block label="Wanted" value={wanted.length} />
|
||||||
<Block label="Queued" value={queuedData.totalCount} />
|
<Block label="Queued" value={queuedData.totalCount} />
|
||||||
<Block label="Movies" value={moviesData.length} />
|
<Block label="Movies" value={have.length} />
|
||||||
</Widget>
|
</Widget>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,15 @@
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import RuTorrent from "rutorrent-promise";
|
|
||||||
|
|
||||||
import { formatBytes } from "utils/stats-helpers";
|
|
||||||
|
|
||||||
import Widget from "../widget";
|
import Widget from "../widget";
|
||||||
import Block from "../block";
|
import Block from "../block";
|
||||||
|
|
||||||
|
import { formatApiUrl } from "utils/api-helpers";
|
||||||
|
import { formatBytes } from "utils/stats-helpers";
|
||||||
|
|
||||||
export default function Rutorrent({ service }) {
|
export default function Rutorrent({ service }) {
|
||||||
const config = service.widget;
|
const config = service.widget;
|
||||||
|
|
||||||
function buildApiUrl() {
|
const { data: statusData, error: statusError } = useSWR(formatApiUrl(config));
|
||||||
const { url, username, password } = config;
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
url: `${url}/plugins/httprpc/action.php`,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (username && password) {
|
|
||||||
options.username = username;
|
|
||||||
options.password = password;
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = new URLSearchParams(options);
|
|
||||||
|
|
||||||
return `/api/widgets/rutorrent?${params.toString()}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data: statusData, error: statusError } = useSWR(buildApiUrl());
|
|
||||||
|
|
||||||
if (statusError) {
|
if (statusError) {
|
||||||
return <Widget error="Nzbget API Error" />;
|
return <Widget error="Nzbget API Error" />;
|
||||||
|
|||||||
@@ -3,17 +3,14 @@ import useSWR from "swr";
|
|||||||
import Widget from "../widget";
|
import Widget from "../widget";
|
||||||
import Block from "../block";
|
import Block from "../block";
|
||||||
|
|
||||||
|
import { formatApiUrl } from "utils/api-helpers";
|
||||||
|
|
||||||
export default function Sonarr({ service }) {
|
export default function Sonarr({ service }) {
|
||||||
const config = service.widget;
|
const config = service.widget;
|
||||||
|
|
||||||
function buildApiUrl(endpoint) {
|
const { data: wantedData, error: wantedError } = useSWR(formatApiUrl(config, "wanted/missing"));
|
||||||
const { url, key } = config;
|
const { data: queuedData, error: queuedError } = useSWR(formatApiUrl(config, "queue"));
|
||||||
return `${url}/api/v3/${endpoint}?apikey=${key}`;
|
const { data: seriesData, error: seriesError } = useSWR(formatApiUrl(config, "series"));
|
||||||
}
|
|
||||||
|
|
||||||
const { data: wantedData, error: wantedError } = useSWR(buildApiUrl("wanted/missing"));
|
|
||||||
const { data: queuedData, error: queuedError } = useSWR(buildApiUrl("queue"));
|
|
||||||
const { data: seriesData, error: seriesError } = useSWR(buildApiUrl("series"));
|
|
||||||
|
|
||||||
if (wantedError || queuedError || seriesError) {
|
if (wantedError || queuedError || seriesError) {
|
||||||
return <Widget error="Sonar API Error" />;
|
return <Widget error="Sonar API Error" />;
|
||||||
|
|||||||
@@ -4,16 +4,12 @@ import Widget from "../widget";
|
|||||||
import Block from "../block";
|
import Block from "../block";
|
||||||
|
|
||||||
import { formatBits } from "utils/stats-helpers";
|
import { formatBits } from "utils/stats-helpers";
|
||||||
|
import { formatApiUrl } from "utils/api-helpers";
|
||||||
|
|
||||||
export default function Speedtest({ service }) {
|
export default function Speedtest({ service }) {
|
||||||
const config = service.widget;
|
const config = service.widget;
|
||||||
|
|
||||||
function buildApiUrl(endpoint) {
|
const { data: speedtestData, error: speedtestError } = useSWR(formatApiUrl(config, "speedtest/latest"));
|
||||||
const { url } = config;
|
|
||||||
return `${url}/api/${endpoint}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data: speedtestData, error: speedtestError } = useSWR(buildApiUrl("speedtest/latest"));
|
|
||||||
|
|
||||||
if (speedtestError) {
|
if (speedtestError) {
|
||||||
return <Widget error="Speedtest API Error" />;
|
return <Widget error="Speedtest API Error" />;
|
||||||
@@ -31,8 +27,8 @@ export default function Speedtest({ service }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Widget>
|
<Widget>
|
||||||
<Block label="Download" value={`${formatBits(speedtestData.data.download * 1024 * 1024)}ps`} />
|
<Block label="Download" value={`${formatBits(speedtestData.data.download * 1024 * 1024, 0)}ps`} />
|
||||||
<Block label="Upload" value={`${formatBits(speedtestData.data.upload * 1024 * 1024)}ps`} />
|
<Block label="Upload" value={`${formatBits(speedtestData.data.upload * 1024 * 1024, 0)}ps`} />
|
||||||
<Block label="Ping" value={`${speedtestData.data.ping} ms`} />
|
<Block label="Ping" value={`${speedtestData.data.ping} ms`} />
|
||||||
</Widget>
|
</Widget>
|
||||||
);
|
);
|
||||||
|
|||||||
37
src/components/services/widgets/service/tautulli.jsx
Normal file
37
src/components/services/widgets/service/tautulli.jsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import useSWR from "swr";
|
||||||
|
|
||||||
|
import Widget from "../widget";
|
||||||
|
import Block from "../block";
|
||||||
|
|
||||||
|
import { formatApiUrl } from "utils/api-helpers";
|
||||||
|
|
||||||
|
export default function Tautulli({ service }) {
|
||||||
|
const config = service.widget;
|
||||||
|
|
||||||
|
const { data: statsData, error: statsError } = useSWR(formatApiUrl(config, "get_activity"));
|
||||||
|
|
||||||
|
if (statsError) {
|
||||||
|
return <Widget error="Tautulli API Error" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!statsData) {
|
||||||
|
return (
|
||||||
|
<Widget>
|
||||||
|
<Block label="Playing" />
|
||||||
|
<Block label="Transcoding" />
|
||||||
|
<Block label="Bitrate" />
|
||||||
|
</Widget>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = statsData.response.data;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Widget>
|
||||||
|
<Block label="Playing" value={data.stream_count} />
|
||||||
|
<Block label="Transcoding" value={data.stream_count_transcode} />
|
||||||
|
{/* We divide by 1000 here because thats how Tautulli reports it on its own dashboard */}
|
||||||
|
<Block label="Bitrate" value={`${Math.round((data.total_bandwidth / 1000) * 100) / 100} Mbps`} />
|
||||||
|
</Widget>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,16 +3,12 @@ import useSWR from "swr";
|
|||||||
import Widget from "../widget";
|
import Widget from "../widget";
|
||||||
import Block from "../block";
|
import Block from "../block";
|
||||||
|
|
||||||
|
import { formatApiUrl } from "utils/api-helpers";
|
||||||
|
|
||||||
export default function Traefik({ service }) {
|
export default function Traefik({ service }) {
|
||||||
const config = service.widget;
|
const config = service.widget;
|
||||||
|
|
||||||
function buildApiUrl(endpoint) {
|
const { data: traefikData, error: traefikError } = useSWR(formatApiUrl(config, "overview"));
|
||||||
const { url } = config;
|
|
||||||
const fullUrl = `${url}/api/${endpoint}`;
|
|
||||||
return `/api/proxy?url=${encodeURIComponent(fullUrl)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data: traefikData, error: traefikError } = useSWR(buildApiUrl("overview"));
|
|
||||||
|
|
||||||
if (traefikError) {
|
if (traefikError) {
|
||||||
return <Widget error="Traefik API Error" />;
|
return <Widget error="Traefik API Error" />;
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import WeatherApi from "components/widgets/weather/weather";
|
import WeatherApi from "components/widgets/weather/weather";
|
||||||
import OpenWeatherMap from "components/widgets/openweathermap/weather";
|
import OpenWeatherMap from "components/widgets/openweathermap/weather";
|
||||||
import Resources from "components/widgets/resources/resources";
|
import Resources from "components/widgets/resources/resources";
|
||||||
|
import Search from "components/widgets/search/search";
|
||||||
|
|
||||||
const widgetMappings = {
|
const widgetMappings = {
|
||||||
weather: WeatherApi, // This key will be deprecated in the future
|
weather: WeatherApi, // This key will be deprecated in the future
|
||||||
weatherapi: WeatherApi,
|
weatherapi: WeatherApi,
|
||||||
openweathermap: OpenWeatherMap,
|
openweathermap: OpenWeatherMap,
|
||||||
resources: Resources,
|
resources: Resources,
|
||||||
|
search: Search,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Widget({ widget }) {
|
export default function Widget({ widget }) {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export default function OpenWeatherMap({ options }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col justify-center">
|
||||||
<div className="flex flex-row items-center justify-end">
|
<div className="flex flex-row items-center justify-end">
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<Icon
|
<Icon
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import Memory from "./memory";
|
|||||||
export default function Resources({ options }) {
|
export default function Resources({ options }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="pr-2 flex flex-col">
|
<div className="flex flex-col max-w:full basis-1/2 sm:basis-auto self-center">
|
||||||
<div className="flex flex-row space-x-4">
|
<div className="flex flex-row space-x-4 self-center">
|
||||||
{options.disk && <Disk options={options} />}
|
|
||||||
{options.cpu && <Cpu />}
|
{options.cpu && <Cpu />}
|
||||||
{options.memory && <Memory />}
|
{options.memory && <Memory />}
|
||||||
|
{options.disk && <Disk options={options} />}
|
||||||
</div>
|
</div>
|
||||||
{options.label && (
|
{options.label && (
|
||||||
<div className="border-t-2 border-theme-800 dark:border-theme-200 mt-1 pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">
|
<div className="border-t-2 border-theme-800 dark:border-theme-200 mt-1 pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">
|
||||||
|
|||||||
69
src/components/widgets/search/search.jsx
Normal file
69
src/components/widgets/search/search.jsx
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { FiSearch } from "react-icons/fi";
|
||||||
|
import { SiDuckduckgo, SiMicrosoftbing, SiGoogle } from "react-icons/si";
|
||||||
|
|
||||||
|
const providers = {
|
||||||
|
google: {
|
||||||
|
name: "Google",
|
||||||
|
url: "https://www.google.com/search?q=",
|
||||||
|
icon: SiGoogle,
|
||||||
|
},
|
||||||
|
duckduckgo: {
|
||||||
|
name: "DuckDuckGo",
|
||||||
|
url: "https://duckduckgo.com/?q=",
|
||||||
|
icon: SiDuckduckgo,
|
||||||
|
},
|
||||||
|
bing: {
|
||||||
|
name: "Bing",
|
||||||
|
url: "https://www.bing.com/search?q=",
|
||||||
|
icon: SiMicrosoftbing,
|
||||||
|
},
|
||||||
|
custom: {
|
||||||
|
name: "Custom",
|
||||||
|
url: false,
|
||||||
|
icon: FiSearch,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Search({ options }) {
|
||||||
|
const provider = providers[options.provider];
|
||||||
|
const [query, setQuery] = useState("");
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSubmit(event) {
|
||||||
|
const q = encodeURIComponent(query);
|
||||||
|
|
||||||
|
if (provider.url) {
|
||||||
|
window.open(`${provider.url}${q}`, options.target || "_blank");
|
||||||
|
} else {
|
||||||
|
window.open(`${options.url}${q}`, options.target || "_blank");
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.target.reset();
|
||||||
|
setQuery("");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form className="flex-col relative h-8 my-4 min-w-full md:min-w-fit grow" onSubmit={handleSubmit}>
|
||||||
|
<div className="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none w-full text-theme-800 dark:text-theme-200"></div>
|
||||||
|
<input
|
||||||
|
type="search"
|
||||||
|
autoFocus
|
||||||
|
className={`overflow-hidden w-full placeholder-theme-900 text-xs text-theme-900 bg-theme-50 rounded-md border border-theme-300 focus:ring-theme-500 focus:border-theme-500 dark:bg-theme-800 dark:border-theme-600 dark:placeholder-theme-400 dark:text-white dark:focus:ring-theme-500 dark:focus:border-theme-500 h-full`}
|
||||||
|
placeholder="Search..."
|
||||||
|
onChange={(s) => setQuery(s.currentTarget.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="text-white absolute right-0.5 bottom-0.5 bg-theme-700 hover:bg-theme-800 border-1 focus:ring-2 focus:ring-theme-300 font-medium rounded-r-md text-sm px-4 py-2 dark:bg-theme-600 dark:hover:bg-theme-700 dark:focus:ring-theme-500"
|
||||||
|
>
|
||||||
|
<provider.icon className="text-theme-800 dark:text-theme-200 w-3 h-3" />
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -33,7 +33,7 @@ export default function WeatherApi({ options }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col justify-center">
|
||||||
<div className="flex flex-row items-center justify-end">
|
<div className="flex flex-row items-center justify-end">
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<Icon condition={data.current.condition.code} timeOfDay={data.current.is_day ? "day" : "night"} />
|
<Icon condition={data.current.condition.code} timeOfDay={data.current.is_day ? "day" : "night"} />
|
||||||
|
|||||||
@@ -15,10 +15,23 @@ export default async function handler(req, res) {
|
|||||||
return {
|
return {
|
||||||
name: Object.keys(group)[0],
|
name: Object.keys(group)[0],
|
||||||
services: group[Object.keys(group)[0]].map((entries) => {
|
services: group[Object.keys(group)[0]].map((entries) => {
|
||||||
return {
|
const { widget, ...service } = entries[Object.keys(entries)[0]];
|
||||||
|
let res = {
|
||||||
name: Object.keys(entries)[0],
|
name: Object.keys(entries)[0],
|
||||||
...entries[Object.keys(entries)[0]],
|
...service,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (widget) {
|
||||||
|
const { type } = widget;
|
||||||
|
|
||||||
|
res.widget = {
|
||||||
|
type: type,
|
||||||
|
service_group: Object.keys(group)[0],
|
||||||
|
service_name: Object.keys(entries)[0],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
37
src/pages/api/services/proxy.js
Normal file
37
src/pages/api/services/proxy.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import genericProxyHandler from "utils/proxies/generic";
|
||||||
|
import credentialedProxyHandler from "utils/proxies/credentialed";
|
||||||
|
import rutorrentProxyHandler from "utils/proxies/rutorrent";
|
||||||
|
import nzbgetProxyHandler from "utils/proxies/nzbget";
|
||||||
|
import npmProxyHandler from "utils/proxies/npm";
|
||||||
|
|
||||||
|
const serviceProxyHandlers = {
|
||||||
|
// uses query param auth
|
||||||
|
emby: genericProxyHandler,
|
||||||
|
jellyfin: genericProxyHandler,
|
||||||
|
pihole: genericProxyHandler,
|
||||||
|
radarr: genericProxyHandler,
|
||||||
|
sonarr: genericProxyHandler,
|
||||||
|
speedtest: genericProxyHandler,
|
||||||
|
tautulli: genericProxyHandler,
|
||||||
|
traefik: genericProxyHandler,
|
||||||
|
// uses X-API-Key header auth
|
||||||
|
portainer: credentialedProxyHandler,
|
||||||
|
jellyseerr: credentialedProxyHandler,
|
||||||
|
ombi: credentialedProxyHandler,
|
||||||
|
// super specific handlers
|
||||||
|
rutorrent: rutorrentProxyHandler,
|
||||||
|
nzbget: nzbgetProxyHandler,
|
||||||
|
npm: npmProxyHandler,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function handler(req, res) {
|
||||||
|
const { type } = req.query;
|
||||||
|
|
||||||
|
const serviceProxyHandler = serviceProxyHandlers[type];
|
||||||
|
|
||||||
|
if (serviceProxyHandler) {
|
||||||
|
return serviceProxyHandler(req, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(403).json({ error: "Unkown proxy service type" });
|
||||||
|
}
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import RuTorrent from "rutorrent-promise";
|
|
||||||
|
|
||||||
// TODO: Remove the 3rd party dependency once I figure out how to
|
|
||||||
// call this myself with fetch. Just need to destruct the package.
|
|
||||||
|
|
||||||
export default async function handler(req, res) {
|
|
||||||
const { url, username, password } = req.query;
|
|
||||||
|
|
||||||
const constructedUrl = new URL(url);
|
|
||||||
|
|
||||||
const rutorrent = new RuTorrent({
|
|
||||||
host: constructedUrl.hostname, // default: localhost
|
|
||||||
port: constructedUrl.port, // default: 80
|
|
||||||
path: constructedUrl.pathname, // default: /rutorrent
|
|
||||||
ssl: constructedUrl.protocol === "https:", // default: false
|
|
||||||
username: username, // default: none
|
|
||||||
password: password, // default: none
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await rutorrent.get(["d.get_down_rate", "d.get_up_rate", "d.get_state"]);
|
|
||||||
|
|
||||||
res.status(200).send(data);
|
|
||||||
}
|
|
||||||
@@ -8,6 +8,7 @@ import ServicesGroup from "components/services/group";
|
|||||||
import BookmarksGroup from "components/bookmarks/group";
|
import BookmarksGroup from "components/bookmarks/group";
|
||||||
import Widget from "components/widget";
|
import Widget from "components/widget";
|
||||||
import { ColorProvider } from "utils/color-context";
|
import { ColorProvider } from "utils/color-context";
|
||||||
|
import Search from "components/widgets/search/search";
|
||||||
|
|
||||||
const ThemeToggle = dynamic(() => import("components/theme-toggle"), {
|
const ThemeToggle = dynamic(() => import("components/theme-toggle"), {
|
||||||
ssr: false,
|
ssr: false,
|
||||||
@@ -17,7 +18,8 @@ const ColorToggle = dynamic(() => import("components/color-toggle"), {
|
|||||||
ssr: false,
|
ssr: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const rightAlignedWidgets = ["weatherapi", "openweathermap", "weather"];
|
const rightAlignedWidgets = ["weatherapi", "openweathermap", "weather", "search"];
|
||||||
|
const expandedWidgets = ["search"];
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const { data: services, error: servicesError } = useSWR("/api/services");
|
const { data: services, error: servicesError } = useSWR("/api/services");
|
||||||
@@ -31,7 +33,7 @@ export default function Home() {
|
|||||||
<title>Welcome</title>
|
<title>Welcome</title>
|
||||||
</Head>
|
</Head>
|
||||||
<div className="w-full container m-auto flex flex-col h-screen justify-between">
|
<div className="w-full container m-auto flex flex-col h-screen justify-between">
|
||||||
<div className="flex flex-wrap space-x-4 m-8 pb-4 mt-10 border-b-2 border-theme-800 dark:border-theme-200">
|
<div className="flex flex-row flex-wrap space-x-0 sm:space-x-4 m-8 pb-4 mt-10 border-b-2 border-theme-800 dark:border-theme-200 justify-between md:justify-start">
|
||||||
{widgets && (
|
{widgets && (
|
||||||
<>
|
<>
|
||||||
{widgets
|
{widgets
|
||||||
@@ -39,12 +41,14 @@ export default function Home() {
|
|||||||
.map((widget, i) => (
|
.map((widget, i) => (
|
||||||
<Widget key={i} widget={widget} />
|
<Widget key={i} widget={widget} />
|
||||||
))}
|
))}
|
||||||
<div className="grow"></div>
|
|
||||||
{widgets
|
<div className="flex flex-wrap basis-full space-x-0 sm:space-x-4 grow sm:basis-auto justify-between md:justify-end mt-2 md:mt-0">
|
||||||
.filter((widget) => rightAlignedWidgets.includes(widget.type))
|
{widgets
|
||||||
.map((widget, i) => (
|
.filter((widget) => rightAlignedWidgets.includes(widget.type))
|
||||||
<Widget key={i} widget={widget} />
|
.map((widget, i) => (
|
||||||
))}
|
<Widget key={i} widget={widget} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,15 +3,20 @@
|
|||||||
|
|
||||||
- My First Group:
|
- My First Group:
|
||||||
- My First Service:
|
- My First Service:
|
||||||
href: http://localhosdt/
|
href: http://localhost/
|
||||||
description: Homepage is awesome
|
description: Homepage is awesome
|
||||||
|
# widget:
|
||||||
|
# type: npm # npm for NGINX Proxy Manager
|
||||||
|
# url: http://localhost # no slash at the end
|
||||||
|
# username: email@example.com # your email
|
||||||
|
# password: secretpassword # your password
|
||||||
|
|
||||||
- My Second Group:
|
- My Second Group:
|
||||||
- My Second Service:
|
- My Second Service:
|
||||||
href: http://localhosdt/
|
href: http://localhost/
|
||||||
description: Homepage is the best
|
description: Homepage is the best
|
||||||
|
|
||||||
- My Third Group:
|
- My Third Group:
|
||||||
- My Third Service:
|
- My Third Service:
|
||||||
href: http://localhosdt/
|
href: http://localhost/
|
||||||
description: Homepage is 😎
|
description: Homepage is 😎
|
||||||
|
|||||||
@@ -5,3 +5,9 @@
|
|||||||
cpu: true
|
cpu: true
|
||||||
memory: true
|
memory: true
|
||||||
disk: /
|
disk: /
|
||||||
|
# - search: # Searchbar in widgets area
|
||||||
|
# provider: custom # Can be google, duckduckgo, bing or custom.
|
||||||
|
# target: _blank # Can be _blank, _top, _self or _parent.
|
||||||
|
# customdata:
|
||||||
|
# url: https://startpage.com/search?q= # Required for custom provider. Remember to add the q param as per your provider.
|
||||||
|
# abbr: G # Can be omitted. Only the first 2 characters will be considered.
|
||||||
|
|||||||
35
src/utils/api-helpers.js
Normal file
35
src/utils/api-helpers.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
const formats = {
|
||||||
|
emby: `{url}/emby/{endpoint}?api_key={key}`,
|
||||||
|
jellyfin: `{url}/emby/{endpoint}?api_key={key}`,
|
||||||
|
pihole: `{url}/admin/{endpoint}`,
|
||||||
|
radarr: `{url}/api/v3/{endpoint}?apikey={key}`,
|
||||||
|
sonarr: `{url}/api/v3/{endpoint}?apikey={key}`,
|
||||||
|
speedtest: `{url}/api/{endpoint}`,
|
||||||
|
tautulli: `{url}/api/v2?apikey={key}&cmd={endpoint}`,
|
||||||
|
traefik: `{url}/api/{endpoint}`,
|
||||||
|
portainer: `{url}/api/endpoints/{env}/{endpoint}`,
|
||||||
|
rutorrent: `{url}/plugins/httprpc/action.php`,
|
||||||
|
jellyseerr: `{url}/api/v1/{endpoint}`,
|
||||||
|
ombi: `{url}/api/v1/{endpoint}`,
|
||||||
|
npm: `{url}/api/{endpoint}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function formatApiCall(api, args) {
|
||||||
|
const match = /\{.*?\}/g;
|
||||||
|
const replace = (match) => {
|
||||||
|
const key = match.replace(/\{|\}/g, "");
|
||||||
|
return args[key];
|
||||||
|
};
|
||||||
|
|
||||||
|
return formats[api].replace(match, replace);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatApiUrl(widget, endpoint) {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
type: widget.type,
|
||||||
|
group: widget.service_group,
|
||||||
|
service: widget.service_name,
|
||||||
|
endpoint,
|
||||||
|
});
|
||||||
|
return `/api/services/proxy?${params.toString()}`;
|
||||||
|
}
|
||||||
@@ -44,3 +44,20 @@ export function httpRequest(url, params) {
|
|||||||
request.end();
|
request.end();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function httpProxy(url, params = {}) {
|
||||||
|
const constructedUrl = new URL(url);
|
||||||
|
|
||||||
|
if (constructedUrl.protocol === "https:") {
|
||||||
|
const httpsAgent = new https.Agent({
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return httpsRequest(constructedUrl, {
|
||||||
|
agent: httpsAgent,
|
||||||
|
...params,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return httpRequest(constructedUrl, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
28
src/utils/proxies/credentialed.js
Normal file
28
src/utils/proxies/credentialed.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { getServiceWidget } from "utils/service-helpers";
|
||||||
|
import { formatApiCall } from "utils/api-helpers";
|
||||||
|
import { httpProxy } from "utils/http";
|
||||||
|
|
||||||
|
export default async function credentialedProxyHandler(req, res) {
|
||||||
|
const { group, service, endpoint } = req.query;
|
||||||
|
|
||||||
|
if (group && service) {
|
||||||
|
const widget = await getServiceWidget(group, service);
|
||||||
|
|
||||||
|
if (widget) {
|
||||||
|
const url = new URL(formatApiCall(widget.type, { endpoint, ...widget }));
|
||||||
|
const [status, contentType, data] = await httpProxy(url, {
|
||||||
|
withCredentials: true,
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"X-API-Key": `${widget.key}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
res.setHeader("Content-Type", contentType);
|
||||||
|
return res.status(status).send(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(400).json({ error: "Invalid proxy service type" });
|
||||||
|
}
|
||||||
21
src/utils/proxies/generic.js
Normal file
21
src/utils/proxies/generic.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { getServiceWidget } from "utils/service-helpers";
|
||||||
|
import { formatApiCall } from "utils/api-helpers";
|
||||||
|
import { httpProxy } from "utils/http";
|
||||||
|
|
||||||
|
export default async function genericProxyHandler(req, res) {
|
||||||
|
const { group, service, endpoint } = req.query;
|
||||||
|
|
||||||
|
if (group && service) {
|
||||||
|
const widget = await getServiceWidget(group, service);
|
||||||
|
|
||||||
|
if (widget) {
|
||||||
|
const url = new URL(formatApiCall(widget.type, { endpoint, ...widget }));
|
||||||
|
const [status, contentType, data] = await httpProxy(url);
|
||||||
|
|
||||||
|
res.setHeader("Content-Type", contentType);
|
||||||
|
return res.status(status).send(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(400).json({ error: "Invalid proxy service type" });
|
||||||
|
}
|
||||||
37
src/utils/proxies/npm.js
Normal file
37
src/utils/proxies/npm.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { getServiceWidget } from "utils/service-helpers";
|
||||||
|
import { formatApiCall } from "utils/api-helpers";
|
||||||
|
|
||||||
|
export default async function npmProxyHandler(req, res) {
|
||||||
|
const { group, service, endpoint } = req.query;
|
||||||
|
|
||||||
|
if (group && service) {
|
||||||
|
const widget = await getServiceWidget(group, service);
|
||||||
|
|
||||||
|
if (widget) {
|
||||||
|
const url = new URL(formatApiCall(widget.type, { endpoint, ...widget }));
|
||||||
|
|
||||||
|
const loginUrl = `${widget.url}/api/tokens`;
|
||||||
|
const body = { identity: widget.username, secret: widget.password };
|
||||||
|
|
||||||
|
const authResponse = await fetch(loginUrl, {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}).then((response) => response.json());
|
||||||
|
|
||||||
|
const apiResponse = await fetch(url, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: "Bearer " + authResponse.token,
|
||||||
|
},
|
||||||
|
}).then((response) => response.json());
|
||||||
|
|
||||||
|
return res.send(apiResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(400).json({ error: "Invalid proxy service type" });
|
||||||
|
}
|
||||||
39
src/utils/proxies/nzbget.js
Normal file
39
src/utils/proxies/nzbget.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { JSONRPCClient } from "json-rpc-2.0";
|
||||||
|
import { getServiceWidget } from "utils/service-helpers";
|
||||||
|
|
||||||
|
export default async function nzbgetProxyHandler(req, res) {
|
||||||
|
const { group, service, endpoint } = req.query;
|
||||||
|
|
||||||
|
if (group && service) {
|
||||||
|
const widget = await getServiceWidget(group, service);
|
||||||
|
|
||||||
|
if (widget) {
|
||||||
|
const constructedUrl = new URL(widget.url);
|
||||||
|
constructedUrl.pathname = "jsonrpc";
|
||||||
|
|
||||||
|
const authorization = Buffer.from(`${widget.username}:${widget.password}`).toString("base64");
|
||||||
|
|
||||||
|
const client = new JSONRPCClient((jsonRPCRequest) =>
|
||||||
|
fetch(constructedUrl.toString(), {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json",
|
||||||
|
authorization: `Basic ${authorization}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify(jsonRPCRequest),
|
||||||
|
}).then(async (response) => {
|
||||||
|
if (response.status === 200) {
|
||||||
|
const jsonRPCResponse = await response.json();
|
||||||
|
return client.receive(jsonRPCResponse);
|
||||||
|
} else if (jsonRPCRequest.id !== undefined) {
|
||||||
|
return Promise.reject(new Error(response.statusText));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.send(await client.request(endpoint));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(400).json({ error: "Invalid proxy service type" });
|
||||||
|
}
|
||||||
30
src/utils/proxies/rutorrent.js
Normal file
30
src/utils/proxies/rutorrent.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import RuTorrent from "rutorrent-promise";
|
||||||
|
|
||||||
|
import { getServiceWidget } from "utils/service-helpers";
|
||||||
|
|
||||||
|
export default async function rutorrentProxyHandler(req, res) {
|
||||||
|
const { group, service } = req.query;
|
||||||
|
|
||||||
|
if (group && service) {
|
||||||
|
const widget = await getServiceWidget(group, service);
|
||||||
|
|
||||||
|
if (widget) {
|
||||||
|
const constructedUrl = new URL(widget.url);
|
||||||
|
|
||||||
|
const rutorrent = new RuTorrent({
|
||||||
|
host: constructedUrl.hostname,
|
||||||
|
port: constructedUrl.port,
|
||||||
|
path: constructedUrl.pathname,
|
||||||
|
ssl: constructedUrl.protocol === "https:",
|
||||||
|
username: widget.username,
|
||||||
|
password: widget.password,
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await rutorrent.get(["d.get_down_rate", "d.get_up_rate", "d.get_state"]);
|
||||||
|
|
||||||
|
return res.status(200).send(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(400).json({ error: "Invalid proxy service type" });
|
||||||
|
}
|
||||||
33
src/utils/service-helpers.js
Normal file
33
src/utils/service-helpers.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { promises as fs } from "fs";
|
||||||
|
import path from "path";
|
||||||
|
import yaml from "js-yaml";
|
||||||
|
|
||||||
|
export async function getServiceWidget(group, service) {
|
||||||
|
const servicesYaml = path.join(process.cwd(), "config", "services.yaml");
|
||||||
|
const fileContents = await fs.readFile(servicesYaml, "utf8");
|
||||||
|
const services = yaml.load(fileContents);
|
||||||
|
|
||||||
|
// map easy to write YAML objects into easy to consume JS arrays
|
||||||
|
const servicesArray = services.map((group) => {
|
||||||
|
return {
|
||||||
|
name: Object.keys(group)[0],
|
||||||
|
services: group[Object.keys(group)[0]].map((entries) => {
|
||||||
|
return {
|
||||||
|
name: Object.keys(entries)[0],
|
||||||
|
...entries[Object.keys(entries)[0]],
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const serviceGroup = servicesArray.find((g) => g.name === group);
|
||||||
|
if (serviceGroup) {
|
||||||
|
const serviceEntry = serviceGroup.services.find((s) => s.name === service);
|
||||||
|
if (serviceEntry) {
|
||||||
|
const { widget } = serviceEntry;
|
||||||
|
return widget;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user