Merge branch 'benphelps:main' into main

This commit is contained in:
Chris 2022-09-14 18:27:36 -05:00 committed by GitHub
commit 90995d40d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 482 additions and 110 deletions

View file

@ -96,7 +96,7 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
# https://github.com/docker/setup-qemu-action#about
# platforms: linux/amd64,linux/arm64,linux/riscv64,linux/ppc64le,linux/s390x,linux/386,linux/mips64le,linux/mips64,linux/arm/v7,linux/arm/v6
platforms: linux/amd64,linux/arm64,linux/arm/v6,linux/arm/v7
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max

View file

@ -17,7 +17,7 @@
- Automatic service discovery (via labels)
* Service Integration
- Sonarr, Radarr, Readarr, Prowlarr, Emby, Jellyfin, Tautulli (Plex)
- Ombi, Overseerr, Jellyseerr, NZBGet, SABnzbd, ruTorrent
- Ombi, Overseerr, Jellyseerr, NZBGet, SABnzbd, ruTorrent, Transmission
- Portainer, Traefik, Speedtest Tracker, PiHole, Nginx Proxy Manager, Gotify
* Information Providers
- Coin Market Cap
@ -127,7 +127,7 @@ Huge thanks to the all the contributors who have helped make this project what i
* [ilusi0n](https://github.com/benphelps/homepage/commits?author=ilusi0n) - Jellyseerr Integration
* [ItsJustMeChris](https://github.com/benphelps/homepage/commits?author=ItsJustMeChris) - Coin Market Cap Widget
* [jackblk](https://github.com/benphelps/homepage/commits?author=jackblk) - Vietnamese Translation
* [JazzFisch](https://github.com/benphelps/homepage/commits?author=JazzFisch) - Readarr, SABnzbd Integrations
* [JazzFisch](https://github.com/benphelps/homepage/commits?author=JazzFisch) - Readarr, SABnzbd & Transmission Integrations
* [modem7](https://github.com/benphelps/homepage/commits?author=modem7) - Impvoed Docker Image
* [nicedc](https://github.com/benphelps/homepage/commits?author=nicedc) - Chinese Translation
* [Nonoss117](https://github.com/benphelps/homepage/commits?author=Nonoss117) - French Translation

9
docker-entrypoint.sh Executable file
View file

@ -0,0 +1,9 @@
#!/bin/sh
set -e
# This is in attempt to preserve the original behavior of the Dockerfile,
# while also supporting the lscr.io /config directory
[ ! -d "/app/config" ] && ln -s /config /app/config
node server.js

View file

@ -6,13 +6,15 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"telemetry": "next telemetry disable"
},
"dependencies": {
"@headlessui/react": "^1.7.0",
"@tailwindcss/forms": "^0.5.3",
"classnames": "^2.3.1",
"dockerode": "^3.3.4",
"follow-redirects": "^1.15.2",
"i18next": "^21.9.1",
"i18next-browser-languagedetector": "^6.1.5",
"i18next-http-backend": "^1.4.1",
@ -29,7 +31,8 @@
"react-icons": "^4.4.0",
"rutorrent-promise": "^2.0.0",
"shvl": "^3.0.0",
"swr": "^1.3.0"
"swr": "^1.3.0",
"tough-cookie": "^4.1.2"
},
"devDependencies": {
"autoprefixer": "^10.4.9",

49
pnpm-lock.yaml generated
View file

@ -15,6 +15,7 @@ specifiers:
eslint-plugin-prettier: ^4.2.1
eslint-plugin-react: ^7.31.8
eslint-plugin-react-hooks: ^4.6.0
follow-redirects: ^1.15.2
i18next: ^21.9.1
i18next-browser-languagedetector: ^6.1.5
i18next-http-backend: ^1.4.1
@ -35,6 +36,7 @@ specifiers:
shvl: ^3.0.0
swr: ^1.3.0
tailwindcss: ^3.1.8
tough-cookie: ^4.1.2
typescript: ^4.8.3
dependencies:
@ -42,6 +44,7 @@ dependencies:
'@tailwindcss/forms': 0.5.3_tailwindcss@3.1.8
classnames: 2.3.1
dockerode: 3.3.4
follow-redirects: 1.15.2
i18next: 21.9.1
i18next-browser-languagedetector: 6.1.5
i18next-http-backend: 1.4.1
@ -59,6 +62,7 @@ dependencies:
rutorrent-promise: 2.0.0
shvl: 3.0.0
swr: 1.3.0_react@18.2.0
tough-cookie: 4.1.2
devDependencies:
autoprefixer: 10.4.9_postcss@8.4.16
@ -1352,6 +1356,16 @@ packages:
resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==}
dev: true
/follow-redirects/1.15.2:
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
dev: false
/form-data/3.0.1:
resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==}
engines: {node: '>= 6'}
@ -2156,6 +2170,10 @@ packages:
react-is: 16.13.1
dev: true
/psl/1.9.0:
resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==}
dev: false
/pump/3.0.0:
resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==}
dependencies:
@ -2166,7 +2184,10 @@ packages:
/punycode/2.1.1:
resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==}
engines: {node: '>=6'}
dev: true
/querystringify/2.2.0:
resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
dev: false
/queue-microtask/1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
@ -2271,6 +2292,10 @@ packages:
engines: {node: '>=8'}
dev: true
/requires-port/1.0.0:
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
dev: false
/resolve-from/4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
@ -2557,6 +2582,16 @@ packages:
engines: {node: '>=0.6'}
dev: false
/tough-cookie/4.1.2:
resolution: {integrity: sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==}
engines: {node: '>=6'}
dependencies:
psl: 1.9.0
punycode: 2.1.1
universalify: 0.2.0
url-parse: 1.5.10
dev: false
/tr46/0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
dev: false
@ -2619,6 +2654,11 @@ packages:
which-boxed-primitive: 1.0.2
dev: true
/universalify/0.2.0:
resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
engines: {node: '>= 4.0.0'}
dev: false
/unpipe/1.0.0:
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
engines: {node: '>= 0.8'}
@ -2641,6 +2681,13 @@ packages:
punycode: 2.1.1
dev: true
/url-parse/1.5.10:
resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
dependencies:
querystringify: 2.2.0
requires-port: 1.0.0
dev: false
/use-sync-external-store/1.2.0_react@18.2.0:
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
peerDependencies:

View file

@ -0,0 +1,140 @@
{
"widget": {
"missing_type": "Falta el tipus de widget: {{type}}",
"api_error": "Error d'API",
"status": "Estat"
},
"weather": {
"allow": "Feu clic per permetre",
"updating": "Actualitzant",
"wait": "Si us plau, espereu",
"current": "Localització actual"
},
"search": {
"placeholder": "Cercar…"
},
"transmission": {
"seed": "Llavors",
"download": "Descàrrega",
"upload": "Càrrega",
"leech": "Companys"
},
"sonarr": {
"wanted": "Volgut",
"queued": "En cua",
"series": "Sèries"
},
"speedtest": {
"ping": "Ping",
"upload": "Càrrega",
"download": "Descàrrega"
},
"resources": {
"total": "Total",
"free": "Lliure",
"used": "Usat",
"load": "Càrrega"
},
"docker": {
"rx": "RX",
"tx": "TX",
"mem": "MEM",
"cpu": "CPU",
"offline": "Fora de línia"
},
"emby": {
"playing": "Reproduint",
"transcoding": "Transcodificant",
"bitrate": "Taxa de bits",
"no_active": "Sense transmissions actives"
},
"tautulli": {
"playing": "Reproduint",
"transcoding": "Transcodificant",
"bitrate": "Taxa de bits",
"no_active": "Sense transmissions actives"
},
"nzbget": {
"rate": "Taxa",
"remaining": "Restant",
"downloaded": "Descarregat"
},
"sabnzbd": {
"rate": "Taxa",
"queue": "Cua",
"timeleft": "Temps restant"
},
"rutorrent": {
"active": "Actiu",
"upload": "Càrrega",
"download": "Descàrrega"
},
"radarr": {
"wanted": "Volgut",
"queued": "En cua",
"movies": "Pel·lícules"
},
"readarr": {
"wanted": "Volgut",
"queued": "En cua",
"books": "Llibres"
},
"ombi": {
"pending": "Pendent",
"approved": "Aprovat",
"available": "Disponible"
},
"jellyseerr": {
"pending": "Pendent",
"approved": "Aprovat",
"available": "Disponible"
},
"overseerr": {
"pending": "Pendent",
"approved": "Aprovat",
"available": "Disponible"
},
"pihole": {
"queries": "Consultes",
"blocked": "Bloquejat",
"gravity": "Gravetat"
},
"portainer": {
"running": "Executant",
"stopped": "Aturat",
"total": "Total"
},
"traefik": {
"routers": "Encaminadors",
"services": "Serveis",
"middleware": "Middleware"
},
"npm": {
"total": "Total",
"enabled": "Activat",
"disabled": "Desactivat"
},
"coinmarketcap": {
"configure": "Configura una o més criptomonedes per fer el seguiment",
"1hour": "1 Hora",
"1day": "1 Dia",
"7days": "7 Dies",
"30days": "30 Dies"
},
"gotify": {
"apps": "Aplicacions",
"clients": "Clients",
"messages": "Missatges"
},
"prowlarr": {
"enableIndexers": "Indexadors",
"numberOfGrabs": "Captures",
"numberOfQueries": "Consultes",
"numberOfFailGrabs": "Captures fallides",
"numberOfFailQueries": "Consultes fallides"
},
"jackett": {
"configured": "Configured",
"errored": "Errored"
}
}

View file

@ -109,7 +109,11 @@
"downloaded": "Downloaded"
},
"coinmarketcap": {
"configure": "Configure one or more crypto currencies to track"
"configure": "Configure one or more crypto currencies to track",
"1hour": "1 Hour",
"1day": "1 Day",
"7days": "7 Days",
"30days": "30 Days"
},
"gotify": {
"apps": "Applications",
@ -128,5 +132,9 @@
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
},
"jackett": {
"configured": "Configured",
"errored": "Errored"
}
}

View file

@ -143,5 +143,9 @@
"numberOfQueries": "Queries",
"numberOfFailGrabs": "Fail Grabs",
"numberOfFailQueries": "Fail Queries"
},
"jackett": {
"configured": "Configured",
"errored": "Errored"
}
}

View file

@ -1,6 +1,6 @@
{
"widget": {
"missing_type": "Tipo de widget faltante: {{type}}",
"missing_type": "Falta el tipo de widget: {{type}}",
"api_error": "Error de API",
"status": "Estado"
},
@ -11,7 +11,7 @@
"total": "Total",
"free": "Libre",
"used": "Usado",
"load": "Load"
"load": "Carga"
},
"docker": {
"rx": "Recibido",
@ -34,8 +34,8 @@
},
"rutorrent": {
"active": "Activo",
"upload": "Subir",
"download": "Descargar"
"upload": "Subida",
"download": "Descarga"
},
"sonarr": {
"wanted": "Más deseado",
@ -69,7 +69,7 @@
},
"speedtest": {
"upload": "Subir",
"download": "Descargar",
"download": "Descarga",
"ping": "Ping"
},
"portainer": {
@ -109,7 +109,11 @@
"downloaded": "Descargado"
},
"coinmarketcap": {
"configure": "Configurar una o varias criptomonedas para su seguimiento"
"configure": "Configurar una o varias criptomonedas para su seguimiento",
"1hour": "1 Hora",
"1day": "1 Día",
"7days": "7 Dias",
"30days": "30 Dias"
},
"gotify": {
"apps": "Aplicaciones",
@ -117,16 +121,20 @@
"messages": "Mensajes"
},
"prowlarr": {
"enableIndexers": "Indexers",
"numberOfGrabs": "Grabs",
"numberOfQueries": "Queries",
"numberOfFailGrabs": "Fail Grabs",
"numberOfFailQueries": "Fail Queries"
"enableIndexers": "Indexadores",
"numberOfGrabs": "Capturas",
"numberOfQueries": "Consultas",
"numberOfFailGrabs": "Capturas Fallidas",
"numberOfFailQueries": "Consultas Fallidas"
},
"transmission": {
"download": "Download",
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
"download": "Descarga",
"upload": "Subida",
"leech": "Egoístas (Leech)",
"seed": "Semillas"
},
"jackett": {
"configured": "Configured",
"errored": "Errored"
}
}

View file

@ -110,17 +110,21 @@
"available": "Disponible"
},
"sabnzbd": {
"rate": "Taux",
"rate": "Débit",
"queue": "Queue",
"timeleft": "Temps restant"
},
"nzbget": {
"remaining": "Restant",
"downloaded": "Téléchargé",
"rate": "Évaluer"
"rate": "Débit"
},
"coinmarketcap": {
"configure": "Configurer une ou plusieurs crypto-monnaies à suivre"
"configure": "Configurer une ou plusieurs crypto-monnaies à suivre",
"1hour": "1 Heure",
"1day": "1 Jour",
"7days": "7 Jours",
"30days": "30 Jours"
},
"gotify": {
"apps": "Applications",
@ -135,9 +139,13 @@
"numberOfFailQueries": "Demande échouée"
},
"transmission": {
"download": "Download",
"upload": "Upload",
"download": "Réception",
"upload": "Envoi",
"leech": "Leech",
"seed": "Seed"
},
"jackett": {
"configured": "Configured",
"errored": "Errored"
}
}

View file

@ -109,7 +109,11 @@
"downloaded": "Downloaded"
},
"coinmarketcap": {
"configure": "Configure one or more crypto currencies to track"
"configure": "Configure one or more crypto currencies to track",
"1day": "1 Day",
"7days": "7 Days",
"1hour": "1 Hour",
"30days": "30 Days"
},
"gotify": {
"apps": "Applications",
@ -128,5 +132,9 @@
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
},
"jackett": {
"configured": "Configured",
"errored": "Errored"
}
}

View file

@ -109,7 +109,11 @@
"remaining": "Gjenstående"
},
"coinmarketcap": {
"configure": "Sett opp én eller flere kryptovalutaer å holde øye med"
"configure": "Sett opp én eller flere kryptovalutaer å holde øye med",
"1hour": "1 Hour",
"1day": "1 Day",
"7days": "7 Days",
"30days": "30 Days"
},
"gotify": {
"apps": "Programmer",
@ -128,5 +132,9 @@
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
},
"jackett": {
"configured": "Configured",
"errored": "Errored"
}
}

View file

@ -109,7 +109,11 @@
"downloaded": "Downloaded"
},
"coinmarketcap": {
"configure": "Configure one or more crypto currencies to track"
"configure": "Configure one or more crypto currencies to track",
"1hour": "1 Hour",
"7days": "7 Days",
"1day": "1 Day",
"30days": "30 Days"
},
"gotify": {
"apps": "Applications",
@ -128,5 +132,9 @@
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
},
"jackett": {
"configured": "Configured",
"errored": "Errored"
}
}

View file

@ -1,6 +1,6 @@
{
"widget": {
"missing_type": "Tipo de widget ausente: {{type}}",
"missing_type": "Widget ausente: {{type}}",
"api_error": "Erro da API",
"status": "Status"
},
@ -10,7 +10,7 @@
"resources": {
"total": "Total",
"free": "Livre",
"used": "Usada",
"used": "Usado",
"load": "Load"
},
"docker": {
@ -18,22 +18,22 @@
"tx": "Tx",
"mem": "Mem",
"cpu": "CPU",
"offline": "Desligada"
"offline": "Desligado"
},
"emby": {
"playing": "A reproduzir",
"transcoding": "Transcodificação",
"bitrate": "Taxa de bits",
"no_active": "No Active Streams"
"bitrate": "Bitrate",
"no_active": "Sem streams ativas"
},
"tautulli": {
"playing": "Reproduzindo",
"transcoding": "Transcodificação",
"bitrate": "Taxa de bits",
"no_active": "No Active Streams"
"no_active": "Sem streams ativas"
},
"rutorrent": {
"active": "Ativa",
"active": "Ativo",
"upload": "Envio",
"download": "ReceçãoDownload"
},
@ -44,13 +44,13 @@
},
"radarr": {
"wanted": "Desejado",
"queued": "Enfileiradas",
"queued": "Fila",
"movies": "Filmes"
},
"readarr": {
"wanted": "Wanted",
"queued": "Queued",
"books": "Books"
"queued": "Em fila",
"books": "Livros"
},
"ombi": {
"pending": "Pendente",
@ -65,7 +65,7 @@
"pihole": {
"queries": "Consultas",
"blocked": "Bloqueado",
"gravity": "Gravidade"
"gravity": "Gravity"
},
"speedtest": {
"upload": "Envio",
@ -73,18 +73,18 @@
"ping": "Ping"
},
"portainer": {
"running": "Corrida",
"stopped": "Parou",
"running": "A correr",
"stopped": "Parado",
"total": "Total"
},
"traefik": {
"routers": "Roteadores",
"routers": "Routers",
"services": "Serviços",
"middleware": "Middleware"
},
"npm": {
"enabled": "Habilitada",
"disabled": "Desabilitada",
"enabled": "Ativo",
"disabled": "Desabilitado",
"total": "Total"
},
"common": {
@ -105,22 +105,26 @@
"wait": "Por favor aguarde"
},
"overseerr": {
"pending": "Pending",
"approved": "Approved",
"available": "Available"
"pending": "Pendente",
"approved": "Aprovado",
"available": "Disponível"
},
"sabnzbd": {
"rate": "Rate",
"queue": "Queue",
"timeleft": "Time Left"
"queue": "Fila",
"timeleft": "Tempo restante"
},
"nzbget": {
"rate": "Rate",
"remaining": "Remaining",
"remaining": "Restante",
"downloaded": "Downloaded"
},
"coinmarketcap": {
"configure": "Configure one or more crypto currencies to track"
"configure": "Configurar uma ou mais moedas",
"1hour": "1 Hour",
"1day": "1 Day",
"7days": "7 Days",
"30days": "30 Days"
},
"gotify": {
"apps": "Aplicações",
@ -131,13 +135,17 @@
"enableIndexers": "Indexers",
"numberOfGrabs": "Grabs",
"numberOfQueries": "Queries",
"numberOfFailGrabs": "Fail Grabs",
"numberOfFailQueries": "Fail Queries"
"numberOfFailGrabs": "Falhados",
"numberOfFailQueries": "Pesquisas falhadas"
},
"transmission": {
"download": "Download",
"upload": "Upload",
"upload": "Envio",
"leech": "Leech",
"seed": "Seed"
},
"jackett": {
"configured": "Configured",
"errored": "Errored"
}
}

View file

@ -8,7 +8,7 @@
"placeholder": "Поиск…"
},
"resources": {
"total": "Общий",
"total": "Всего",
"free": "Свободно",
"used": "Использовано",
"load": "Load"
@ -89,8 +89,8 @@
},
"weather": {
"wait": "Пожалуйста подождите",
"current": "Текущее местоположение",
"allow": "Click to allow",
"current": "Текущая локация",
"allow": "Нажмите, чтобы разрешить",
"updating": "Обновление"
},
"overseerr": {
@ -109,7 +109,11 @@
"downloaded": "Downloaded"
},
"coinmarketcap": {
"configure": "Configure one or more crypto currencies to track"
"configure": "Configure one or more crypto currencies to track",
"1hour": "1 Hour",
"1day": "1 Day",
"7days": "7 Days",
"30days": "30 Days"
},
"gotify": {
"apps": "Applications",
@ -128,5 +132,9 @@
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
},
"jackett": {
"configured": "Configured",
"errored": "Errored"
}
}

View file

@ -109,7 +109,11 @@
"downloaded": "Downloaded"
},
"coinmarketcap": {
"configure": "Configure one or more crypto currencies to track"
"configure": "Configure one or more crypto currencies to track",
"1hour": "1 Hour",
"1day": "1 Day",
"7days": "7 Days",
"30days": "30 Days"
},
"gotify": {
"apps": "Applications",
@ -128,5 +132,9 @@
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
},
"jackett": {
"configured": "Configured",
"errored": "Errored"
}
}

View file

@ -109,7 +109,11 @@
"downloaded": "下载"
},
"coinmarketcap": {
"configure": "配置一个或多个需要追踪的加密"
"configure": "配置一个或多个需要追踪的加密",
"1hour": "1 Hour",
"1day": "1 Day",
"7days": "7 Days",
"30days": "30 Days"
},
"gotify": {
"apps": "应用",
@ -124,9 +128,13 @@
"numberOfFailQueries": "Fail Queries"
},
"transmission": {
"download": "Download",
"upload": "Upload",
"download": "下载",
"upload": "上传",
"leech": "Leech",
"seed": "Seed"
"seed": "做种"
},
"jackett": {
"configured": "Configured",
"errored": "Errored"
}
}

View file

@ -114,7 +114,11 @@
"total": "Total"
},
"coinmarketcap": {
"configure": "Configure one or more crypto currencies to track"
"configure": "Configure one or more crypto currencies to track",
"1hour": "1 Hour",
"1day": "1 Day",
"7days": "7 Days",
"30days": "30 Days"
},
"prowlarr": {
"enableIndexers": "Indexers",
@ -128,5 +132,9 @@
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
},
"jackett": {
"configured": "Configured",
"errored": "Errored"
}
}

View file

@ -1,14 +1,15 @@
import { Fragment } from "react";
import { Menu, Transition } from "@headlessui/react";
import { BiCog } from "react-icons/bi";
import classNames from "classnames";
export default function Dropdown({ options, state }) {
export default function Dropdown({ options, value, setValue }) {
return (
<Menu as="div" className="relative inline-block text-left">
<div>
<Menu.Button className="inline-flex w-full justify-center rounded-md border border-gray-300 bg-theme-600/40 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-theme-600/40 focus:outline-none">
{state.value.label}
<BiCog className="-mr-1 ml-2 h-5 w-5" aria-hidden="true" />
<Menu.Button className="text-xs inline-flex w-full items-center rounded bg-theme-200/50 dark:bg-theme-900/20 px-3 py-1.5">
{options.find((option) => option.value === value).label}
<BiCog className="-mr-1 ml-2 h-4 w-4" aria-hidden="true" />
</Menu.Button>
</div>
@ -21,18 +22,21 @@ export default function Dropdown({ options, state }) {
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute right-0 z-10 mt-2 w-56 origin-top-right rounded-md bg-theme-600/40 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
<Menu.Items className="absolute right-0 z-10 mt-2 w-56 origin-top-right rounded-md bg-theme-200/50 dark:bg-theme-900/50 backdrop-blur shadow-md focus:outline-none text-theme-700 dark:text-theme-200">
<div className="py-1">
{options.map((i) => (
<Menu.Item key={i.value}>
{options.map((option) => (
<Menu.Item key={option.value} as={Fragment}>
<button
onClick={() => {
state.set(i);
setValue(option.value);
}}
type="button"
className="text-white block px-4 py-2 text-sm"
className={classNames(
value === option.value ? "bg-theme-300/40 dark:bg-theme-900/40" : "",
"w-full block px-3 py-1.5 text-sm hover:bg-theme-300/70 hover:dark:bg-theme-900/70 text-left"
)}
>
{i.label}
{option.label}
</button>
</Menu.Item>
))}

View file

@ -1,12 +1,9 @@
import Image from "next/future/image";
import { Disclosure } from "@headlessui/react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import Status from "./status";
import Widget from "./widget";
import Docker from "./widgets/service/docker";
import Dropdown from "./dropdown";
function resolveIcon(icon) {
if (icon.startsWith("http")) {
@ -25,27 +22,12 @@ function resolveIcon(icon) {
}
export default function Item({ service }) {
const { t } = useTranslation();
const handleOnClick = () => {
if (service.href && service.href !== "#") {
window.open(service.href, "_blank").focus();
}
};
const cmcValues = [
{ label: t("coinmarketcap.1hour"), value: "1h" },
{ label: t("coinmarketcap.1day"), value: "24h" },
{ label: t("coinmarketcap.7days"), value: "7d" },
{ label: t("coinmarketcap.30days"), value: "30d" },
];
const [cmcV, cmcSet] = useState(cmcValues[0]);
const states = {
coinmarketcap: { value: cmcV, set: cmcSet },
};
const hasLink = service.href && service.href !== "#";
return (
@ -100,7 +82,6 @@ export default function Item({ service }) {
<Status service={service} />
</Disclosure.Button>
)}
{service?.widget?.type === "coinmarketcap" && <Dropdown state={states.coinmarketcap} options={cmcValues} />}
</div>
<Disclosure.Panel>
@ -109,7 +90,7 @@ export default function Item({ service }) {
</div>
</Disclosure.Panel>
{service.widget && <Widget state={states[service?.widget?.type]} service={service} />}
{service.widget && <Widget service={service} />}
</div>
</Disclosure>
</li>

View file

@ -22,6 +22,7 @@ import Tautulli from "./widgets/service/tautulli";
import CoinMarketCap from "./widgets/service/coinmarketcap";
import Gotify from "./widgets/service/gotify";
import Prowlarr from "./widgets/service/prowlarr";
import Jackett from "./widgets/service/jackett";
const widgetMappings = {
docker: Docker,
@ -46,15 +47,16 @@ const widgetMappings = {
tautulli: Tautulli,
gotify: Gotify,
prowlarr: Prowlarr,
jackett: Jackett,
};
export default function Widget({ service, state }) {
export default function Widget({ service }) {
const { t } = useTranslation("common");
const ServiceWidget = widgetMappings[service.widget.type];
if (ServiceWidget) {
return <ServiceWidget state={state} service={service} />;
return <ServiceWidget service={service} />;
}
return (

View file

@ -1,14 +1,26 @@
import useSWR from "swr";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import classNames from "classnames";
import Widget from "../widget";
import Block from "../block";
import Dropdown from "components/services/dropdown";
import { formatApiUrl } from "utils/api-helpers";
export default function CoinMarketCap({ service, state }) {
export default function CoinMarketCap({ service }) {
const { t } = useTranslation();
const dateRangeOptions = [
{ label: t("coinmarketcap.1hour"), value: "1h" },
{ label: t("coinmarketcap.1day"), value: "24h" },
{ label: t("coinmarketcap.7days"), value: "7d" },
{ label: t("coinmarketcap.30days"), value: "30d" },
];
const [dateRange, setDateRange] = useState(dateRangeOptions[0].value);
const config = service.widget;
const currencyCode = config.currency ?? "USD";
const { symbols } = config;
@ -29,7 +41,7 @@ export default function CoinMarketCap({ service, state }) {
return <Widget error={t("widget.api_error")} />;
}
if (!statsData) {
if (!statsData || !dateRange) {
return (
<Widget>
<Block value={t("coinmarketcap.configure")} />
@ -41,29 +53,33 @@ export default function CoinMarketCap({ service, state }) {
return (
<Widget>
<div className={classNames(service.description ? "-top-10" : "-top-8", "absolute right-1")}>
<Dropdown options={dateRangeOptions} value={dateRange} setValue={setDateRange} />
</div>
<div className="flex flex-col w-full">
{symbols.map((key) => (
{symbols.map((symbol) => (
<div
key={data[key].symbol}
key={data[symbol].symbol}
className="bg-theme-200/50 dark:bg-theme-900/20 rounded m-1 flex-1 flex flex-row items-center justify-between p-1 text-xs"
>
<div className="font-thin pl-2">{data[key].name}</div>
<div className="font-thin pl-2">{data[symbol].name}</div>
<div className="flex flex-row text-right">
<div className="font-bold mr-2">
{t("common.number", {
value: data[key].quote[currencyCode].price,
value: data[symbol].quote[currencyCode].price,
style: "currency",
currency: currencyCode,
})}
</div>
<div
className={`font-bold w-10 mr-2 ${
data[key].quote[currencyCode][`percent_change_${state.value.value}`] > 0
data[symbol].quote[currencyCode][`percent_change_${dateRange}`] > 0
? "text-emerald-300"
: "text-rose-300"
}`}
>
{data[key].quote[currencyCode][`percent_change_${state.value.value}`].toFixed(2)}%
{data[symbol].quote[currencyCode][`percent_change_${dateRange}`].toFixed(2)}%
</div>
</div>
</div>

View file

@ -0,0 +1,37 @@
import useSWR from "swr";
import { useTranslation } from "react-i18next";
import Widget from "../widget";
import Block from "../block";
import { formatApiUrl } from "utils/api-helpers";
export default function Jackett({ service }) {
const { t } = useTranslation();
const config = service.widget;
const { data: indexersData, error: indexersError } = useSWR(formatApiUrl(config, "indexers"));
if (indexersError) {
return <Widget error={t("widget.api_error")} />;
}
if (!indexersData) {
return (
<Widget>
<Block label={t("jackett.configured")} />
<Block label={t("jackett.errored")} />
</Widget>
);
}
const errored = indexersData.filter((indexer) => indexer.last_error);
return (
<Widget>
<Block label={t("jackett.configured")} value={indexersData.length} />
<Block label={t("jackett.errored")} value={errored.length} />
</Widget>
);
}

View file

@ -7,5 +7,5 @@ export default function Widget({ error = false, children }) {
);
}
return <div className="flex flex-row w-full">{children}</div>;
return <div className="relative flex flex-row w-full">{children}</div>;
}

View file

@ -3,7 +3,6 @@ import Cpu from "./cpu";
import Memory from "./memory";
export default function Resources({ options }) {
console.log(options);
const { expanded } = options;
return (
<div className="flex flex-col max-w:full sm:basis-auto self-center m-auto flex-wrap">

View file

@ -17,6 +17,7 @@ const serviceProxyHandlers = {
tautulli: genericProxyHandler,
traefik: genericProxyHandler,
sabnzbd: genericProxyHandler,
jackett: genericProxyHandler,
// uses X-API-Key (or similar) header auth
gotify: credentialedProxyHandler,
portainer: credentialedProxyHandler,

View file

@ -46,6 +46,8 @@ export default function Home({ settings }) {
<ThemeProvider>
<Head>
<title>{settings.title || "Homepage"}</title>
{settings.base && <base href={settings.base} />}
{settings.favicon && <link rel="icon" href={settings.favicon} />}
</Head>
<div className="fixed w-full h-full m-0 p-0" style={wrappedStyle} />
<div className="relative w-full container m-auto flex flex-col h-screen justify-between">

View file

@ -19,6 +19,7 @@ const formats = {
coinmarketcap: `https://pro-api.coinmarketcap.com/{endpoint}`,
gotify: `{url}/{endpoint}`,
prowlarr: `{url}/api/v1/{endpoint}`,
jackett: `{url}/api/v2.0/{endpoint}?apikey={key}&configured=true`
};
export function formatApiCall(api, args) {

View file

@ -1,9 +1,46 @@
/* eslint-disable prefer-promise-reject-errors */
import https from "https";
import http from "http";
/* eslint-disable no-param-reassign */
import { http, https } from "follow-redirects";
import { Cookie, CookieJar } from 'tough-cookie';
const cookieJar = new CookieJar();
function setCookieHeader(url, params) {
// add cookie header, if we have one in the jar
const existingCookie = cookieJar.getCookieStringSync(url.toString());
if (existingCookie) {
params.headers = params.headers ?? {};
params.headers.Cookie = existingCookie;
}
}
function addCookieHandler(url, params) {
setCookieHeader(url, params);
// handle cookies during redirects
params.beforeRedirect = (options, responseInfo) => {
const cookieHeader = responseInfo.headers['set-cookie'];
if (!cookieHeader || cookieHeader.length === 0) return;
let cookies = null;
if (cookieHeader instanceof Array) {
cookies = cookieHeader.map(Cookie.parse);
}
else {
cookies = [Cookie.parse(cookieHeader)];
}
for (let i = 0; i < cookies.length; i += 1) {
cookieJar.setCookieSync(cookies[i], options.href);
}
setCookieHeader(options.href, options);
};
}
export function httpsRequest(url, params) {
return new Promise((resolve, reject) => {
addCookieHandler(url, params);
const request = https.request(url, params, (response) => {
const data = [];
@ -30,6 +67,7 @@ export function httpsRequest(url, params) {
export function httpRequest(url, params) {
return new Promise((resolve, reject) => {
addCookieHandler(url, params);
const request = http.request(url, params, (response) => {
const data = [];