mirror of
https://github.com/gethomepage/homepage.git
synced 2025-12-05 21:47:48 +01:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
939f5d7c20 | ||
|
|
cf39395924 | ||
|
|
6061d9ec65 | ||
|
|
5a8defb478 | ||
|
|
08afa0b747 | ||
|
|
bad436b858 | ||
|
|
7f0345a56a | ||
|
|
97bf174b78 | ||
|
|
975f79f6cc | ||
|
|
e72efe7fd0 |
@@ -10,10 +10,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^1.6.6",
|
||||
"@tailwindcss/forms": "^0.5.2",
|
||||
"dockerode": "^3.3.3",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"dockerode": "^3.3.4",
|
||||
"js-yaml": "^4.1.0",
|
||||
"json-rpc-2.0": "^1.3.0",
|
||||
"json-rpc-2.0": "^1.4.1",
|
||||
"memory-cache": "^0.2.0",
|
||||
"next": "12.2.5",
|
||||
"node-os-utils": "^1.3.7",
|
||||
@@ -30,6 +30,6 @@
|
||||
"eslint-config-next": "12.2.5",
|
||||
"postcss": "^8.4.16",
|
||||
"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:
|
||||
'@headlessui/react': ^1.6.6
|
||||
'@tailwindcss/forms': ^0.5.2
|
||||
'@tailwindcss/forms': ^0.5.3
|
||||
autoprefixer: ^10.4.8
|
||||
dockerode: ^3.3.3
|
||||
dockerode: ^3.3.4
|
||||
eslint: 8.22.0
|
||||
eslint-config-next: 12.2.5
|
||||
js-yaml: ^4.1.0
|
||||
json-rpc-2.0: ^1.3.0
|
||||
json-rpc-2.0: ^1.4.1
|
||||
memory-cache: ^0.2.0
|
||||
next: 12.2.5
|
||||
node-os-utils: ^1.3.7
|
||||
@@ -20,14 +20,14 @@ specifiers:
|
||||
rutorrent-promise: ^2.0.0
|
||||
swr: ^1.3.0
|
||||
tailwindcss: ^3.1.8
|
||||
typescript: ^4.7.4
|
||||
typescript: ^4.8.2
|
||||
|
||||
dependencies:
|
||||
'@headlessui/react': 1.6.6_biqbaboplfbrettd7655fr4n2y
|
||||
'@tailwindcss/forms': 0.5.2_tailwindcss@3.1.8
|
||||
dockerode: 3.3.3
|
||||
'@tailwindcss/forms': 0.5.3_tailwindcss@3.1.8
|
||||
dockerode: 3.3.4
|
||||
js-yaml: 4.1.0
|
||||
json-rpc-2.0: 1.3.0
|
||||
json-rpc-2.0: 1.4.1
|
||||
memory-cache: 0.2.0
|
||||
next: 12.2.5_biqbaboplfbrettd7655fr4n2y
|
||||
node-os-utils: 1.3.7
|
||||
@@ -41,10 +41,10 @@ dependencies:
|
||||
devDependencies:
|
||||
autoprefixer: 10.4.8_postcss@8.4.16
|
||||
eslint: 8.22.0
|
||||
eslint-config-next: 12.2.5_4rv7y5c6xz3vfxwhbrcxxi73bq
|
||||
eslint-config-next: 12.2.5_shit3uhl6a7megkzgoz6xssnfa
|
||||
postcss: 8.4.16
|
||||
tailwindcss: 3.1.8
|
||||
typescript: 4.7.4
|
||||
tailwindcss: 3.1.8_postcss@8.4.16
|
||||
typescript: 4.8.2
|
||||
|
||||
packages:
|
||||
|
||||
@@ -52,7 +52,7 @@ packages:
|
||||
resolution: {integrity: sha512-qZEWeccZCrHA2Au4/X05QW5CMdm4VjUDCrGq5gf1ZDcM4hRqreKrtwAn7yci9zfgAS9apvnsFXiGBHBAxZdK9A==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
core-js-pure: 3.24.1
|
||||
core-js-pure: 3.25.0
|
||||
regenerator-runtime: 0.13.9
|
||||
dev: true
|
||||
|
||||
@@ -63,13 +63,17 @@ packages:
|
||||
regenerator-runtime: 0.13.9
|
||||
dev: true
|
||||
|
||||
/@eslint/eslintrc/1.3.0:
|
||||
resolution: {integrity: sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==}
|
||||
/@balena/dockerignore/1.0.2:
|
||||
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}
|
||||
dependencies:
|
||||
ajv: 6.12.6
|
||||
debug: 4.3.4
|
||||
espree: 9.3.3
|
||||
espree: 9.4.0
|
||||
globals: 13.17.0
|
||||
ignore: 5.2.0
|
||||
import-fresh: 3.3.0
|
||||
@@ -265,21 +269,21 @@ packages:
|
||||
tslib: 2.4.0
|
||||
dev: false
|
||||
|
||||
/@tailwindcss/forms/0.5.2_tailwindcss@3.1.8:
|
||||
resolution: {integrity: sha512-pSrFeJB6Bg1Mrg9CdQW3+hqZXAKsBrSG9MAfFLKy1pVA4Mb4W7C0k7mEhlmS2Dfo/otxrQOET7NJiJ9RrS563w==}
|
||||
/@tailwindcss/forms/0.5.3_tailwindcss@3.1.8:
|
||||
resolution: {integrity: sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q==}
|
||||
peerDependencies:
|
||||
tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1'
|
||||
dependencies:
|
||||
mini-svg-data-uri: 1.4.4
|
||||
tailwindcss: 3.1.8
|
||||
tailwindcss: 3.1.8_postcss@8.4.16
|
||||
dev: false
|
||||
|
||||
/@types/json5/0.0.29:
|
||||
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
||||
dev: true
|
||||
|
||||
/@typescript-eslint/parser/5.33.0_4rv7y5c6xz3vfxwhbrcxxi73bq:
|
||||
resolution: {integrity: sha512-cgM5cJrWmrDV2KpvlcSkelTBASAs1mgqq+IUGKJvFxWrapHpaRy5EXPQz9YaKF3nZ8KY18ILTiVpUtbIac86/w==}
|
||||
/@typescript-eslint/parser/5.36.1_shit3uhl6a7megkzgoz6xssnfa:
|
||||
resolution: {integrity: sha512-/IsgNGOkBi7CuDfUbwt1eOqUXF9WGVBW9dwEe1pi+L32XrTsZIgmDFIi2RxjzsvB/8i+MIf5JIoTEH8LOZ368A==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
peerDependencies:
|
||||
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
@@ -288,31 +292,31 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@typescript-eslint/scope-manager': 5.33.0
|
||||
'@typescript-eslint/types': 5.33.0
|
||||
'@typescript-eslint/typescript-estree': 5.33.0_typescript@4.7.4
|
||||
'@typescript-eslint/scope-manager': 5.36.1
|
||||
'@typescript-eslint/types': 5.36.1
|
||||
'@typescript-eslint/typescript-estree': 5.36.1_typescript@4.8.2
|
||||
debug: 4.3.4
|
||||
eslint: 8.22.0
|
||||
typescript: 4.7.4
|
||||
typescript: 4.8.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@typescript-eslint/scope-manager/5.33.0:
|
||||
resolution: {integrity: sha512-/Jta8yMNpXYpRDl8EwF/M8It2A9sFJTubDo0ATZefGXmOqlaBffEw0ZbkbQ7TNDK6q55NPHFshGBPAZvZkE8Pw==}
|
||||
/@typescript-eslint/scope-manager/5.36.1:
|
||||
resolution: {integrity: sha512-pGC2SH3/tXdu9IH3ItoqciD3f3RRGCh7hb9zPdN2Drsr341zgd6VbhP5OHQO/reUqihNltfPpMpTNihFMarP2w==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 5.33.0
|
||||
'@typescript-eslint/visitor-keys': 5.33.0
|
||||
'@typescript-eslint/types': 5.36.1
|
||||
'@typescript-eslint/visitor-keys': 5.36.1
|
||||
dev: true
|
||||
|
||||
/@typescript-eslint/types/5.33.0:
|
||||
resolution: {integrity: sha512-nIMt96JngB4MYFYXpZ/3ZNU4GWPNdBbcB5w2rDOCpXOVUkhtNlG2mmm8uXhubhidRZdwMaMBap7Uk8SZMU/ppw==}
|
||||
/@typescript-eslint/types/5.36.1:
|
||||
resolution: {integrity: sha512-jd93ShpsIk1KgBTx9E+hCSEuLCUFwi9V/urhjOWnOaksGZFbTOxAT47OH2d4NLJnLhkVD+wDbB48BuaycZPLBg==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
dev: true
|
||||
|
||||
/@typescript-eslint/typescript-estree/5.33.0_typescript@4.7.4:
|
||||
resolution: {integrity: sha512-tqq3MRLlggkJKJUrzM6wltk8NckKyyorCSGMq4eVkyL5sDYzJJcMgZATqmF8fLdsWrW7OjjIZ1m9v81vKcaqwQ==}
|
||||
/@typescript-eslint/typescript-estree/5.36.1_typescript@4.8.2:
|
||||
resolution: {integrity: sha512-ih7V52zvHdiX6WcPjsOdmADhYMDN15SylWRZrT2OMy80wzKbc79n8wFW0xpWpU0x3VpBz/oDgTm2xwDAnFTl+g==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
peerDependencies:
|
||||
typescript: '*'
|
||||
@@ -320,23 +324,23 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 5.33.0
|
||||
'@typescript-eslint/visitor-keys': 5.33.0
|
||||
'@typescript-eslint/types': 5.36.1
|
||||
'@typescript-eslint/visitor-keys': 5.36.1
|
||||
debug: 4.3.4
|
||||
globby: 11.1.0
|
||||
is-glob: 4.0.3
|
||||
semver: 7.3.7
|
||||
tsutils: 3.21.0_typescript@4.7.4
|
||||
typescript: 4.7.4
|
||||
tsutils: 3.21.0_typescript@4.8.2
|
||||
typescript: 4.8.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@typescript-eslint/visitor-keys/5.33.0:
|
||||
resolution: {integrity: sha512-/XsqCzD4t+Y9p5wd9HZiptuGKBlaZO5showwqODii5C0nZawxWLF+Q6k5wYHBrQv96h6GYKyqqMHCSTqta8Kiw==}
|
||||
/@typescript-eslint/visitor-keys/5.36.1:
|
||||
resolution: {integrity: sha512-ojB9aRyRFzVMN3b5joSYni6FAS10BBSCAfKJhjJAV08t/a95aM6tAhz+O1jF+EtgxktuSO3wJysp2R+Def/IWQ==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 5.33.0
|
||||
'@typescript-eslint/types': 5.36.1
|
||||
eslint-visitor-keys: 3.3.0
|
||||
dev: true
|
||||
|
||||
@@ -418,7 +422,7 @@ packages:
|
||||
dependencies:
|
||||
call-bind: 1.0.2
|
||||
define-properties: 1.1.4
|
||||
es-abstract: 1.20.1
|
||||
es-abstract: 1.20.2
|
||||
get-intrinsic: 1.1.2
|
||||
is-string: 1.0.7
|
||||
dev: true
|
||||
@@ -434,7 +438,7 @@ packages:
|
||||
dependencies:
|
||||
call-bind: 1.0.2
|
||||
define-properties: 1.1.4
|
||||
es-abstract: 1.20.1
|
||||
es-abstract: 1.20.2
|
||||
es-shim-unscopables: 1.0.0
|
||||
dev: true
|
||||
|
||||
@@ -444,7 +448,7 @@ packages:
|
||||
dependencies:
|
||||
call-bind: 1.0.2
|
||||
define-properties: 1.1.4
|
||||
es-abstract: 1.20.1
|
||||
es-abstract: 1.20.2
|
||||
es-shim-unscopables: 1.0.0
|
||||
dev: true
|
||||
|
||||
@@ -470,7 +474,7 @@ packages:
|
||||
postcss: ^8.1.0
|
||||
dependencies:
|
||||
browserslist: 4.21.3
|
||||
caniuse-lite: 1.0.30001375
|
||||
caniuse-lite: 1.0.30001388
|
||||
fraction.js: 4.2.0
|
||||
normalize-range: 0.1.2
|
||||
picocolors: 1.0.0
|
||||
@@ -531,10 +535,10 @@ packages:
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
caniuse-lite: 1.0.30001375
|
||||
electron-to-chromium: 1.4.219
|
||||
caniuse-lite: 1.0.30001388
|
||||
electron-to-chromium: 1.4.241
|
||||
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
|
||||
|
||||
/buffer/5.7.1:
|
||||
@@ -571,8 +575,8 @@ packages:
|
||||
resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
/caniuse-lite/1.0.30001375:
|
||||
resolution: {integrity: sha512-kWIMkNzLYxSvnjy0hL8w1NOaWNr2rn39RTAVyIwcw8juu60bZDWiF1/loOYANzjtJmy6qPgNmn38ro5Pygagdw==}
|
||||
/caniuse-lite/1.0.30001388:
|
||||
resolution: {integrity: sha512-znVbq4OUjqgLxMxoNX2ZeeLR0d7lcDiE5uJ4eUiWdml1J1EkxbnQq6opT9jb9SMfJxB0XA16/ziHwni4u1I3GQ==}
|
||||
|
||||
/chalk/4.1.2:
|
||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||
@@ -621,8 +625,8 @@ packages:
|
||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||
dev: true
|
||||
|
||||
/core-js-pure/3.24.1:
|
||||
resolution: {integrity: sha512-r1nJk41QLLPyozHUUPmILCEMtMw24NG4oWK6RbsDdjzQgg9ZvrUsPBj1MnG0wXXp1DCDU6j+wUvEmBSrtRbLXg==}
|
||||
/core-js-pure/3.25.0:
|
||||
resolution: {integrity: sha512-IeHpLwk3uoci37yoI2Laty59+YqH9x5uR65/yiA0ARAJrTrN4YU0rmauLWfvqOuk77SlNJXj2rM6oT/dBD87+A==}
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
|
||||
@@ -734,8 +738,8 @@ packages:
|
||||
/dlv/1.1.3:
|
||||
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
|
||||
|
||||
/docker-modem/3.0.5:
|
||||
resolution: {integrity: sha512-x1E6jxWdtoK3+ifAUWj4w5egPdTDGBpesSCErm+aKET5BnnEOvDtTP6GxcnMB1zZiv2iQ0qJZvJie+1wfIRg6Q==}
|
||||
/docker-modem/3.0.6:
|
||||
resolution: {integrity: sha512-h0Ow21gclbYsZ3mkHDfsYNDqtRhXS8fXr51bU0qr1dxgTMJj0XufbzX+jhNOvA8KuEEzn6JbvLVhXyv+fny9Uw==}
|
||||
engines: {node: '>= 8.0'}
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
@@ -746,11 +750,12 @@ packages:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/dockerode/3.3.3:
|
||||
resolution: {integrity: sha512-lvKV6/NGf2/CYLt5V4c0fd6Fl9XZSCo1Z2HBT9ioKrKLMB2o+gA62Uza8RROpzGvYv57KJx2dKu+ZwSpB//OIA==}
|
||||
/dockerode/3.3.4:
|
||||
resolution: {integrity: sha512-3EUwuXnCU+RUlQEheDjmBE0B7q66PV9Rw5NiH1sXwINq0M9c5ERP9fxgkw36ZHOtzf4AGEEYySnkx/sACC9EgQ==}
|
||||
engines: {node: '>= 8.0'}
|
||||
dependencies:
|
||||
docker-modem: 3.0.5
|
||||
'@balena/dockerignore': 1.0.2
|
||||
docker-modem: 3.0.6
|
||||
tar-fs: 2.0.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -770,8 +775,8 @@ packages:
|
||||
esutils: 2.0.3
|
||||
dev: true
|
||||
|
||||
/electron-to-chromium/1.4.219:
|
||||
resolution: {integrity: sha512-zoQJsXOUw0ZA0YxbjkmzBumAJRtr6je5JySuL/bAoFs0DuLiLJ+5FzRF7/ZayihxR2QcewlRZVm5QZdUhwjOgA==}
|
||||
/electron-to-chromium/1.4.241:
|
||||
resolution: {integrity: sha512-e7Wsh4ilaioBZ5bMm6+F4V5c11dh56/5Jwz7Hl5Tu1J7cnB+Pqx5qIF2iC7HPpfyQMqGSvvLP5bBAIDd2gAtGw==}
|
||||
dev: true
|
||||
|
||||
/emoji-regex/9.2.2:
|
||||
@@ -784,8 +789,8 @@ packages:
|
||||
once: 1.4.0
|
||||
dev: false
|
||||
|
||||
/es-abstract/1.20.1:
|
||||
resolution: {integrity: sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==}
|
||||
/es-abstract/1.20.2:
|
||||
resolution: {integrity: sha512-XxXQuVNrySBNlEkTYJoDNFe5+s2yIOpzq80sUHEdPdQr0S5nTLz4ZPPPswNIpKseDDUS5yghX1gfLIHQZ1iNuQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
dependencies:
|
||||
call-bind: 1.0.2
|
||||
@@ -806,7 +811,7 @@ packages:
|
||||
is-weakref: 1.0.2
|
||||
object-inspect: 1.12.2
|
||||
object-keys: 1.1.1
|
||||
object.assign: 4.1.3
|
||||
object.assign: 4.1.4
|
||||
regexp.prototype.flags: 1.4.3
|
||||
string.prototype.trimend: 1.0.5
|
||||
string.prototype.trimstart: 1.0.5
|
||||
@@ -838,7 +843,7 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/eslint-config-next/12.2.5_4rv7y5c6xz3vfxwhbrcxxi73bq:
|
||||
/eslint-config-next/12.2.5_shit3uhl6a7megkzgoz6xssnfa:
|
||||
resolution: {integrity: sha512-SOowilkqPzW6DxKp3a3SYlrfPi5Ajs9MIzp9gVfUDxxH9QFM5ElkR1hX5m/iICJuvCbWgQqFBiA3mCMozluniw==}
|
||||
peerDependencies:
|
||||
eslint: ^7.23.0 || ^8.0.0
|
||||
@@ -849,15 +854,15 @@ packages:
|
||||
dependencies:
|
||||
'@next/eslint-plugin-next': 12.2.5
|
||||
'@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-import-resolver-node: 0.3.6
|
||||
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-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
|
||||
typescript: 4.7.4
|
||||
typescript: 4.8.2
|
||||
transitivePeerDependencies:
|
||||
- eslint-import-resolver-webpack
|
||||
- supports-color
|
||||
@@ -881,7 +886,7 @@ packages:
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
eslint: 8.22.0
|
||||
eslint-plugin-import: 2.26.0_42jdfezp7lcuhr3fexihng3k3a
|
||||
eslint-plugin-import: 2.26.0_oqagwj4pgbbpoeodu52b4a34xi
|
||||
glob: 7.2.3
|
||||
is-glob: 4.0.3
|
||||
resolve: 1.22.1
|
||||
@@ -890,7 +895,7 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/eslint-module-utils/2.7.4_2znkvn5grarov6xvamxzzjpwtu:
|
||||
/eslint-module-utils/2.7.4_56k6siz3uc4k5fooi777xiw6ie:
|
||||
resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==}
|
||||
engines: {node: '>=4'}
|
||||
peerDependencies:
|
||||
@@ -911,7 +916,7 @@ packages:
|
||||
eslint-import-resolver-webpack:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@typescript-eslint/parser': 5.33.0_4rv7y5c6xz3vfxwhbrcxxi73bq
|
||||
'@typescript-eslint/parser': 5.36.1_shit3uhl6a7megkzgoz6xssnfa
|
||||
debug: 3.2.7
|
||||
eslint: 8.22.0
|
||||
eslint-import-resolver-node: 0.3.6
|
||||
@@ -920,7 +925,7 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/eslint-plugin-import/2.26.0_42jdfezp7lcuhr3fexihng3k3a:
|
||||
/eslint-plugin-import/2.26.0_oqagwj4pgbbpoeodu52b4a34xi:
|
||||
resolution: {integrity: sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==}
|
||||
engines: {node: '>=4'}
|
||||
peerDependencies:
|
||||
@@ -930,14 +935,14 @@ packages:
|
||||
'@typescript-eslint/parser':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@typescript-eslint/parser': 5.33.0_4rv7y5c6xz3vfxwhbrcxxi73bq
|
||||
'@typescript-eslint/parser': 5.36.1_shit3uhl6a7megkzgoz6xssnfa
|
||||
array-includes: 3.1.5
|
||||
array.prototype.flat: 1.3.0
|
||||
debug: 2.6.9
|
||||
doctrine: 2.1.0
|
||||
eslint: 8.22.0
|
||||
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
|
||||
is-core-module: 2.10.0
|
||||
is-glob: 4.0.3
|
||||
@@ -982,8 +987,8 @@ packages:
|
||||
eslint: 8.22.0
|
||||
dev: true
|
||||
|
||||
/eslint-plugin-react/7.30.1_eslint@8.22.0:
|
||||
resolution: {integrity: sha512-NbEvI9jtqO46yJA3wcRF9Mo0lF9T/jhdHqhCHXiXtD+Zcb98812wvokjWpU7Q4QH5edo6dmqrukxVvWWXHlsUg==}
|
||||
/eslint-plugin-react/7.31.5_eslint@8.22.0:
|
||||
resolution: {integrity: sha512-y7472VAcqns17rsQUk6tQCnqBi+boYjGdYarX022719+wGd1T4U1fOYJ2T2Trd3Od2q5M92e42zJ2uZOGmWamA==}
|
||||
engines: {node: '>=4'}
|
||||
peerDependencies:
|
||||
eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8
|
||||
@@ -1038,7 +1043,7 @@ packages:
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@eslint/eslintrc': 1.3.0
|
||||
'@eslint/eslintrc': 1.3.1
|
||||
'@humanwhocodes/config-array': 0.10.4
|
||||
'@humanwhocodes/gitignore-to-minimatch': 1.0.2
|
||||
ajv: 6.12.6
|
||||
@@ -1050,7 +1055,7 @@ packages:
|
||||
eslint-scope: 7.1.1
|
||||
eslint-utils: 3.0.0_eslint@8.22.0
|
||||
eslint-visitor-keys: 3.3.0
|
||||
espree: 9.3.3
|
||||
espree: 9.4.0
|
||||
esquery: 1.4.0
|
||||
esutils: 2.0.3
|
||||
fast-deep-equal: 3.1.3
|
||||
@@ -1081,8 +1086,8 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/espree/9.3.3:
|
||||
resolution: {integrity: sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==}
|
||||
/espree/9.4.0:
|
||||
resolution: {integrity: sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
dependencies:
|
||||
acorn: 8.8.0
|
||||
@@ -1166,12 +1171,12 @@ packages:
|
||||
resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==}
|
||||
engines: {node: ^10.12.0 || >=12.0.0}
|
||||
dependencies:
|
||||
flatted: 3.2.6
|
||||
flatted: 3.2.7
|
||||
rimraf: 3.0.2
|
||||
dev: true
|
||||
|
||||
/flatted/3.2.6:
|
||||
resolution: {integrity: sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==}
|
||||
/flatted/3.2.7:
|
||||
resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==}
|
||||
dev: true
|
||||
|
||||
/form-data/3.0.1:
|
||||
@@ -1211,7 +1216,7 @@ packages:
|
||||
dependencies:
|
||||
call-bind: 1.0.2
|
||||
define-properties: 1.1.4
|
||||
es-abstract: 1.20.1
|
||||
es-abstract: 1.20.2
|
||||
functions-have-names: 1.2.3
|
||||
dev: true
|
||||
|
||||
@@ -1498,8 +1503,8 @@ packages:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
|
||||
/json-rpc-2.0/1.3.0:
|
||||
resolution: {integrity: sha512-pA85D9LG4B+b8njdb8DzktltNfGPcxGJJydEg6jRqePfwhK008lRdbX7bco6aokfw9oDFw7fEe+LRxxXwP11HA==}
|
||||
/json-rpc-2.0/1.4.1:
|
||||
resolution: {integrity: sha512-OX1NJhpIfuK4GjDnJ/gKtZy1HOYo0l4eL0a4rb0rNeQheX1xlyQ9+JMmPzs/sFNthpS/TXKPWlGo09X7B5l81A==}
|
||||
dev: false
|
||||
|
||||
/json-schema-traverse/0.4.1:
|
||||
@@ -1522,7 +1527,7 @@ packages:
|
||||
engines: {node: '>=4.0'}
|
||||
dependencies:
|
||||
array-includes: 3.1.5
|
||||
object.assign: 4.1.3
|
||||
object.assign: 4.1.4
|
||||
dev: true
|
||||
|
||||
/language-subtag-registry/0.3.22:
|
||||
@@ -1661,7 +1666,7 @@ packages:
|
||||
dependencies:
|
||||
'@next/env': 12.2.5
|
||||
'@swc/helpers': 0.4.3
|
||||
caniuse-lite: 1.0.30001375
|
||||
caniuse-lite: 1.0.30001388
|
||||
postcss: 8.4.14
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
@@ -1733,8 +1738,8 @@ packages:
|
||||
engines: {node: '>= 0.4'}
|
||||
dev: true
|
||||
|
||||
/object.assign/4.1.3:
|
||||
resolution: {integrity: sha512-ZFJnX3zltyjcYJL0RoCJuzb+11zWGyaDbjgxZbdV7rFEcHQuYxrZqhow67aA7xpes6LhojyFDaBKAFfogQrikA==}
|
||||
/object.assign/4.1.4:
|
||||
resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
dependencies:
|
||||
call-bind: 1.0.2
|
||||
@@ -1749,7 +1754,7 @@ packages:
|
||||
dependencies:
|
||||
call-bind: 1.0.2
|
||||
define-properties: 1.1.4
|
||||
es-abstract: 1.20.1
|
||||
es-abstract: 1.20.2
|
||||
dev: true
|
||||
|
||||
/object.fromentries/2.0.5:
|
||||
@@ -1758,14 +1763,14 @@ packages:
|
||||
dependencies:
|
||||
call-bind: 1.0.2
|
||||
define-properties: 1.1.4
|
||||
es-abstract: 1.20.1
|
||||
es-abstract: 1.20.2
|
||||
dev: true
|
||||
|
||||
/object.hasown/1.1.1:
|
||||
resolution: {integrity: sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==}
|
||||
dependencies:
|
||||
define-properties: 1.1.4
|
||||
es-abstract: 1.20.1
|
||||
es-abstract: 1.20.2
|
||||
dev: true
|
||||
|
||||
/object.values/1.1.5:
|
||||
@@ -1774,7 +1779,7 @@ packages:
|
||||
dependencies:
|
||||
call-bind: 1.0.2
|
||||
define-properties: 1.1.4
|
||||
es-abstract: 1.20.1
|
||||
es-abstract: 1.20.2
|
||||
dev: true
|
||||
|
||||
/once/1.4.0:
|
||||
@@ -2163,7 +2168,7 @@ packages:
|
||||
dependencies:
|
||||
call-bind: 1.0.2
|
||||
define-properties: 1.1.4
|
||||
es-abstract: 1.20.1
|
||||
es-abstract: 1.20.2
|
||||
get-intrinsic: 1.1.2
|
||||
has-symbols: 1.0.3
|
||||
internal-slot: 1.0.3
|
||||
@@ -2176,7 +2181,7 @@ packages:
|
||||
dependencies:
|
||||
call-bind: 1.0.2
|
||||
define-properties: 1.1.4
|
||||
es-abstract: 1.20.1
|
||||
es-abstract: 1.20.2
|
||||
dev: true
|
||||
|
||||
/string.prototype.trimstart/1.0.5:
|
||||
@@ -2184,7 +2189,7 @@ packages:
|
||||
dependencies:
|
||||
call-bind: 1.0.2
|
||||
define-properties: 1.1.4
|
||||
es-abstract: 1.20.1
|
||||
es-abstract: 1.20.2
|
||||
dev: true
|
||||
|
||||
/string_decoder/1.3.0:
|
||||
@@ -2245,10 +2250,12 @@ packages:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/tailwindcss/3.1.8:
|
||||
/tailwindcss/3.1.8_postcss@8.4.16:
|
||||
resolution: {integrity: sha512-YSneUCZSFDYMwk+TGq8qYFdCA3yfBRdBlS7txSq0LUmzyeqRe3a8fBQzbz9M3WS/iFT4BNf/nmw9mEzrnSaC0g==}
|
||||
engines: {node: '>=12.13.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
postcss: ^8.0.9
|
||||
dependencies:
|
||||
arg: 5.0.2
|
||||
chokidar: 3.5.3
|
||||
@@ -2331,14 +2338,14 @@ packages:
|
||||
resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==}
|
||||
dev: false
|
||||
|
||||
/tsutils/3.21.0_typescript@4.7.4:
|
||||
/tsutils/3.21.0_typescript@4.8.2:
|
||||
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
|
||||
engines: {node: '>= 6'}
|
||||
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'
|
||||
dependencies:
|
||||
tslib: 1.14.1
|
||||
typescript: 4.7.4
|
||||
typescript: 4.8.2
|
||||
dev: true
|
||||
|
||||
/tweetnacl/0.14.5:
|
||||
@@ -2357,8 +2364,8 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/typescript/4.7.4:
|
||||
resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==}
|
||||
/typescript/4.8.2:
|
||||
resolution: {integrity: sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==}
|
||||
engines: {node: '>=4.2.0'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
@@ -2377,8 +2384,8 @@ packages:
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/update-browserslist-db/1.0.5_browserslist@4.21.3:
|
||||
resolution: {integrity: sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q==}
|
||||
/update-browserslist-db/1.0.7_browserslist@4.21.3:
|
||||
resolution: {integrity: sha512-iN/XYesmZ2RmmWAiI4Z5rq0YqSiv0brj9Ce9CfhNE4xIW2h+MFxcgkxIzZ+ShkFPUkjU3gQ+3oypadD3RAMtrg==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
browserslist: '>= 4.21.0'
|
||||
|
||||
@@ -12,6 +12,7 @@ import Speedtest from "./widgets/service/speedtest";
|
||||
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 = {
|
||||
docker: Docker,
|
||||
@@ -28,6 +29,7 @@ const widgetMappings = {
|
||||
traefik: Traefik,
|
||||
jellyseerr: Jellyseerr,
|
||||
npm: Npm,
|
||||
tautulli: Tautulli,
|
||||
};
|
||||
|
||||
export default function Widget({ service }) {
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import useSWR from "swr";
|
||||
|
||||
import { calculateCPUPercent, formatBytes } from "utils/stats-helpers";
|
||||
|
||||
import Widget from "../widget";
|
||||
import Block from "../block";
|
||||
|
||||
import { calculateCPUPercent, formatBytes } from "utils/stats-helpers";
|
||||
|
||||
export default function Docker({ service }) {
|
||||
const config = service.widget;
|
||||
|
||||
const { data: statusData, error: statusError } = useSWR(
|
||||
`/api/docker/status/${config.container}/${config.server || ""}`,
|
||||
{
|
||||
refreshInterval: 1500,
|
||||
refreshInterval: 5000,
|
||||
}
|
||||
);
|
||||
|
||||
const { data: statsData, error: statsError } = useSWR(
|
||||
`/api/docker/stats/${config.container}/${config.server || ""}`,
|
||||
{
|
||||
refreshInterval: 1500,
|
||||
refreshInterval: 5000,
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -3,17 +3,12 @@ import useSWR from "swr";
|
||||
import Widget from "../widget";
|
||||
import Block from "../block";
|
||||
|
||||
import { formatApiUrl } from "utils/api-helpers";
|
||||
|
||||
export default function Emby({ service, title = "Emby" }) {
|
||||
const config = service.widget;
|
||||
|
||||
function buildApiUrl(endpoint) {
|
||||
const { url, key } = config;
|
||||
return `${url}/emby/${endpoint}?api_key=${key}`;
|
||||
}
|
||||
|
||||
const { data: sessionsData, error: sessionsError } = useSWR(buildApiUrl(`Sessions`), {
|
||||
refreshInterval: 1000,
|
||||
});
|
||||
const { data: sessionsData, error: sessionsError } = useSWR(formatApiUrl(config, "Sessions"));
|
||||
|
||||
if (sessionsError) {
|
||||
return <Widget error={`${title} API Error`} />;
|
||||
|
||||
@@ -3,49 +3,32 @@ 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 config = service.widget;
|
||||
|
||||
function buildApiUrl(endpoint) {
|
||||
const { url } = config;
|
||||
const reqUrl = new URL(`/api/v1/${endpoint}`, url);
|
||||
return `/api/proxy?url=${encodeURIComponent(reqUrl)}`;
|
||||
}
|
||||
const { data: statsData, error: statsError } = useSWR(formatApiUrl(config, `request/count`));
|
||||
|
||||
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: statsData, error: statsError } = useSWR(buildApiUrl(`request/count`), fetcher);
|
||||
|
||||
if (statsError) {
|
||||
return <Widget error="Jellyseerr API Error" />;
|
||||
}
|
||||
|
||||
if (!statsData) {
|
||||
return (
|
||||
<Widget>
|
||||
<Block label="Pending" />
|
||||
<Block label="Approved" />
|
||||
<Block label="Available" />
|
||||
</Widget>
|
||||
);
|
||||
}
|
||||
if (statsError) {
|
||||
return <Widget error="Jellyseerr API Error" />;
|
||||
}
|
||||
|
||||
if (!statsData) {
|
||||
return (
|
||||
<Widget>
|
||||
<Block label="Pending" value={statsData.pending} />
|
||||
<Block label="Approved" value={statsData.approved} />
|
||||
<Block label="Available" value={statsData.available} />
|
||||
</Widget>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,39 +3,12 @@ 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 { url } = config;
|
||||
|
||||
const fetcher = async (reqUrl) => {
|
||||
const { url, username, password } = config;
|
||||
const loginUrl = `${url}/api/tokens`;
|
||||
const body = { identity: username, secret: password };
|
||||
|
||||
const res = await fetch(loginUrl, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(body),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(
|
||||
async (data) =>
|
||||
await fetch(reqUrl, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + data.token,
|
||||
},
|
||||
})
|
||||
);
|
||||
return res.json();
|
||||
};
|
||||
|
||||
const { data: infoData, error: infoError } = useSWR(`${url}/api/nginx/proxy-hosts`, fetcher);
|
||||
|
||||
console.log(infoData);
|
||||
const { data: infoData, error: infoError } = useSWR(formatApiUrl(config, "nginx/proxy-hosts"));
|
||||
|
||||
if (infoError) {
|
||||
return <Widget error="NGINX Proxy Manager API Error" />;
|
||||
|
||||
@@ -1,44 +1,15 @@
|
||||
import useSWR from "swr";
|
||||
import { JSONRPCClient } from "json-rpc-2.0";
|
||||
|
||||
import { formatBytes } from "utils/stats-helpers";
|
||||
|
||||
import Widget from "../widget";
|
||||
import Block from "../block";
|
||||
|
||||
import { formatApiUrl } from "utils/api-helpers";
|
||||
import { formatBytes } from "utils/stats-helpers";
|
||||
|
||||
export default function Nzbget({ service }) {
|
||||
const config = service.widget;
|
||||
|
||||
const constructedUrl = new URL(config.url);
|
||||
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,
|
||||
}
|
||||
);
|
||||
const { data: statusData, error: statusError } = useSWR(formatApiUrl(config, "status"));
|
||||
|
||||
if (statusError) {
|
||||
return <Widget error="Nzbget API Error" />;
|
||||
|
||||
@@ -3,27 +3,12 @@ import useSWR from "swr";
|
||||
import Widget from "../widget";
|
||||
import Block from "../block";
|
||||
|
||||
import { formatApiUrl } from "utils/api-helpers";
|
||||
|
||||
export default function Ombi({ service }) {
|
||||
const config = service.widget;
|
||||
|
||||
function buildApiUrl(endpoint) {
|
||||
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);
|
||||
const { data: statsData, error: statsError } = useSWR(formatApiUrl(config, `Request/count`));
|
||||
|
||||
if (statsError) {
|
||||
return <Widget error="Ombi API Error" />;
|
||||
|
||||
@@ -3,21 +3,12 @@ import useSWR from "swr";
|
||||
import Widget from "../widget";
|
||||
import Block from "../block";
|
||||
|
||||
import { formatApiUrl } from "utils/api-helpers";
|
||||
|
||||
export default function Pihole({ service }) {
|
||||
const config = service.widget;
|
||||
|
||||
function buildApiUrl(endpoint) {
|
||||
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"));
|
||||
const { data: piholeData, error: piholeError } = useSWR(formatApiUrl(config, "api.php"));
|
||||
|
||||
if (piholeError) {
|
||||
return <Widget error="PiHole API Error" />;
|
||||
|
||||
@@ -3,29 +3,12 @@ import useSWR from "swr";
|
||||
import Widget from "../widget";
|
||||
import Block from "../block";
|
||||
|
||||
import { formatApiUrl } from "utils/api-helpers";
|
||||
|
||||
export default function Portainer({ service }) {
|
||||
const config = service.widget;
|
||||
|
||||
function buildApiUrl(endpoint) {
|
||||
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);
|
||||
const { data: containersData, error: containersError } = useSWR(formatApiUrl(config, `docker/containers/json?all=1`));
|
||||
|
||||
if (containersError) {
|
||||
return <Widget error="Portainer API Error" />;
|
||||
|
||||
@@ -3,16 +3,13 @@ import useSWR from "swr";
|
||||
import Widget from "../widget";
|
||||
import Block from "../block";
|
||||
|
||||
import { formatApiUrl } from "utils/api-helpers";
|
||||
|
||||
export default function Radarr({ service }) {
|
||||
const config = service.widget;
|
||||
|
||||
function buildApiUrl(endpoint) {
|
||||
const { url, key } = config;
|
||||
return `${url}/api/v3/${endpoint}?apikey=${key}`;
|
||||
}
|
||||
|
||||
const { data: moviesData, error: moviesError } = useSWR(buildApiUrl("movie"));
|
||||
const { data: queuedData, error: queuedError } = useSWR(buildApiUrl("queue/status"));
|
||||
const { data: moviesData, error: moviesError } = useSWR(formatApiUrl(config, "movie"));
|
||||
const { data: queuedData, error: queuedError } = useSWR(formatApiUrl(config, "queue/status"));
|
||||
|
||||
if (moviesError || queuedError) {
|
||||
return <Widget error="Radarr API Error" />;
|
||||
@@ -35,7 +32,7 @@ export default function Radarr({ service }) {
|
||||
<Widget>
|
||||
<Block label="Wanted" value={wanted.length} />
|
||||
<Block label="Queued" value={queuedData.totalCount} />
|
||||
<Block label="Movies" value={moviesData.length} />
|
||||
<Block label="Movies" value={have.length} />
|
||||
</Widget>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,32 +1,15 @@
|
||||
import useSWR from "swr";
|
||||
import RuTorrent from "rutorrent-promise";
|
||||
|
||||
import { formatBytes } from "utils/stats-helpers";
|
||||
|
||||
import Widget from "../widget";
|
||||
import Block from "../block";
|
||||
|
||||
import { formatApiUrl } from "utils/api-helpers";
|
||||
import { formatBytes } from "utils/stats-helpers";
|
||||
|
||||
export default function Rutorrent({ service }) {
|
||||
const config = service.widget;
|
||||
|
||||
function buildApiUrl() {
|
||||
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());
|
||||
const { data: statusData, error: statusError } = useSWR(formatApiUrl(config));
|
||||
|
||||
if (statusError) {
|
||||
return <Widget error="Nzbget API Error" />;
|
||||
|
||||
@@ -3,17 +3,14 @@ import useSWR from "swr";
|
||||
import Widget from "../widget";
|
||||
import Block from "../block";
|
||||
|
||||
import { formatApiUrl } from "utils/api-helpers";
|
||||
|
||||
export default function Sonarr({ service }) {
|
||||
const config = service.widget;
|
||||
|
||||
function buildApiUrl(endpoint) {
|
||||
const { url, key } = config;
|
||||
return `${url}/api/v3/${endpoint}?apikey=${key}`;
|
||||
}
|
||||
|
||||
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"));
|
||||
const { data: wantedData, error: wantedError } = useSWR(formatApiUrl(config, "wanted/missing"));
|
||||
const { data: queuedData, error: queuedError } = useSWR(formatApiUrl(config, "queue"));
|
||||
const { data: seriesData, error: seriesError } = useSWR(formatApiUrl(config, "series"));
|
||||
|
||||
if (wantedError || queuedError || seriesError) {
|
||||
return <Widget error="Sonar API Error" />;
|
||||
|
||||
@@ -4,16 +4,12 @@ import Widget from "../widget";
|
||||
import Block from "../block";
|
||||
|
||||
import { formatBits } from "utils/stats-helpers";
|
||||
import { formatApiUrl } from "utils/api-helpers";
|
||||
|
||||
export default function Speedtest({ service }) {
|
||||
const config = service.widget;
|
||||
|
||||
function buildApiUrl(endpoint) {
|
||||
const { url } = config;
|
||||
return `${url}/api/${endpoint}`;
|
||||
}
|
||||
|
||||
const { data: speedtestData, error: speedtestError } = useSWR(buildApiUrl("speedtest/latest"));
|
||||
const { data: speedtestData, error: speedtestError } = useSWR(formatApiUrl(config, "speedtest/latest"));
|
||||
|
||||
if (speedtestError) {
|
||||
return <Widget error="Speedtest API Error" />;
|
||||
@@ -31,8 +27,8 @@ export default function Speedtest({ service }) {
|
||||
|
||||
return (
|
||||
<Widget>
|
||||
<Block label="Download" value={`${formatBits(speedtestData.data.download * 1024 * 1024)}ps`} />
|
||||
<Block label="Upload" value={`${formatBits(speedtestData.data.upload * 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, 0)}ps`} />
|
||||
<Block label="Ping" value={`${speedtestData.data.ping} ms`} />
|
||||
</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 Block from "../block";
|
||||
|
||||
import { formatApiUrl } from "utils/api-helpers";
|
||||
|
||||
export default function Traefik({ service }) {
|
||||
const config = service.widget;
|
||||
|
||||
function buildApiUrl(endpoint) {
|
||||
const { url } = config;
|
||||
const fullUrl = `${url}/api/${endpoint}`;
|
||||
return `/api/proxy?url=${encodeURIComponent(fullUrl)}`;
|
||||
}
|
||||
|
||||
const { data: traefikData, error: traefikError } = useSWR(buildApiUrl("overview"));
|
||||
const { data: traefikData, error: traefikError } = useSWR(formatApiUrl(config, "overview"));
|
||||
|
||||
if (traefikError) {
|
||||
return <Widget error="Traefik API Error" />;
|
||||
|
||||
@@ -4,9 +4,7 @@ import { BiError } from "react-icons/bi";
|
||||
import Icon from "./icon";
|
||||
|
||||
export default function OpenWeatherMap({ options }) {
|
||||
const { data, error } = useSWR(
|
||||
`/api/widgets/openweathermap?lat=${options.latitude}&lon=${options.longitude}&apiKey=${options.apiKey}&duration=${options.cache}&units=${options.units}`
|
||||
);
|
||||
const { data, error } = useSWR(`/api/widgets/openweathermap?${new URLSearchParams(options).toString()}`);
|
||||
|
||||
if (error || data?.cod == 401) {
|
||||
return (
|
||||
|
||||
@@ -4,9 +4,8 @@ import { BiError } from "react-icons/bi";
|
||||
import Icon from "./icon";
|
||||
|
||||
export default function WeatherApi({ options }) {
|
||||
const { data, error } = useSWR(
|
||||
`/api/widgets/weather?lat=${options.latitude}&lon=${options.longitude}&apiKey=${options.apiKey}&duration=${options.cache}`
|
||||
);
|
||||
console.log(options);
|
||||
const { data, error } = useSWR(`/api/widgets/weather?${new URLSearchParams(options).toString()}`);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
|
||||
@@ -15,10 +15,23 @@ export default async function handler(req, res) {
|
||||
return {
|
||||
name: Object.keys(group)[0],
|
||||
services: group[Object.keys(group)[0]].map((entries) => {
|
||||
return {
|
||||
const { widget, ...service } = entries[Object.keys(entries)[0]];
|
||||
let res = {
|
||||
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,9 +1,28 @@
|
||||
import cachedFetch from "utils/cached-fetch";
|
||||
import { getSettings } from "utils/config";
|
||||
|
||||
export default async function handler(req, res) {
|
||||
const { lat, lon, apiKey, duration, units } = req.query;
|
||||
const { latitude, longitude, units, provider, cache } = req.query;
|
||||
let { apiKey } = req.query;
|
||||
|
||||
const api_url = `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${apiKey}&units=${units}`;
|
||||
if (!apiKey && !provider) {
|
||||
return res.status(400).json({ error: "Missing API key or provider" });
|
||||
}
|
||||
|
||||
res.send(await cachedFetch(api_url, duration));
|
||||
if (!apiKey && provider !== "openweathermap") {
|
||||
return res.status(400).json({ error: "Invalid provider for endpoint" });
|
||||
}
|
||||
|
||||
if (!apiKey && provider) {
|
||||
const settings = await getSettings();
|
||||
apiKey = settings?.providers?.openweathermap;
|
||||
}
|
||||
|
||||
if (!apiKey) {
|
||||
return res.status(400).json({ error: "Missing API key" });
|
||||
}
|
||||
|
||||
const api_url = `https://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid=${apiKey}&units=${units}`;
|
||||
|
||||
res.send(await cachedFetch(api_url, cache));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -1,9 +1,28 @@
|
||||
import cachedFetch from "utils/cached-fetch";
|
||||
import { getSettings } from "utils/config";
|
||||
|
||||
export default async function handler(req, res) {
|
||||
const { lat, lon, apiKey, duration } = req.query;
|
||||
const { latitude, longitude, provider, cache } = req.query;
|
||||
let { apiKey } = req.query;
|
||||
|
||||
const api_url = `http://api.weatherapi.com/v1/current.json?q=${lat},${lon}&key=${apiKey}`;
|
||||
if (!apiKey && !provider) {
|
||||
return res.status(400).json({ error: "Missing API key or provider" });
|
||||
}
|
||||
|
||||
res.send(await cachedFetch(api_url, duration));
|
||||
if (!apiKey && provider !== "weatherapi") {
|
||||
return res.status(400).json({ error: "Invalid provider for endpoint" });
|
||||
}
|
||||
|
||||
if (!apiKey && provider) {
|
||||
const settings = await getSettings();
|
||||
apiKey = settings?.providers?.weatherapi;
|
||||
}
|
||||
|
||||
if (!apiKey) {
|
||||
return res.status(400).json({ error: "Missing API key" });
|
||||
}
|
||||
|
||||
const api_url = `http://api.weatherapi.com/v1/current.json?q=${latitude},${longitude}&key=${apiKey}`;
|
||||
|
||||
res.send(await cachedFetch(api_url, cache));
|
||||
}
|
||||
|
||||
6
src/skeleton/settings.yaml
Normal file
6
src/skeleton/settings.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
# For configuration options and examples, please see:
|
||||
# https://github.com/benphelps/homepage/wiki/Settings
|
||||
|
||||
providers:
|
||||
openweathermap: openweathermapapikey
|
||||
weatherapi: weatherapiapikey
|
||||
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()}`;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { join } from "path";
|
||||
import { existsSync, copyFile } from "fs";
|
||||
import { existsSync, copyFile, promises as fs } from "fs";
|
||||
import yaml from "js-yaml";
|
||||
|
||||
export default function checkAndCopyConfig(config) {
|
||||
const configYaml = join(process.cwd(), "config", config);
|
||||
@@ -14,3 +15,11 @@ export default function checkAndCopyConfig(config) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSettings() {
|
||||
checkAndCopyConfig("settings.yaml");
|
||||
|
||||
const settingsYaml = join(process.cwd(), "config", "settings.yaml");
|
||||
const fileContents = await fs.readFile(settingsYaml, "utf8");
|
||||
return yaml.load(fileContents);
|
||||
}
|
||||
|
||||
@@ -44,3 +44,20 @@ export function httpRequest(url, params) {
|
||||
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",
|
||||
},
|
||||
});
|
||||
|
||||
if (contentType) 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);
|
||||
|
||||
if (contentType) 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