mirror of
https://github.com/gethomepage/homepage.git
synced 2025-12-06 21:57:48 +01:00
Compare commits
96 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79e5ff2fea | ||
|
|
7f91fe59e2 | ||
|
|
b40dad3d3e | ||
|
|
f9f816845f | ||
|
|
193b58d0fc | ||
|
|
b7f490544a | ||
|
|
5d85e3c0e2 | ||
|
|
75214c345a | ||
|
|
6d55e74ae4 | ||
|
|
710f979f94 | ||
|
|
c2a036c526 | ||
|
|
a6c52df4cb | ||
|
|
4d1ad16ea2 | ||
|
|
94c093ea57 | ||
|
|
acd421c617 | ||
|
|
4d2004c8c9 | ||
|
|
6c5bfa466f | ||
|
|
794938c525 | ||
|
|
62188ffdc7 | ||
|
|
34f7bd4341 | ||
|
|
6b45825472 | ||
|
|
b81a5d1e51 | ||
|
|
5d9e90f033 | ||
|
|
55a3e6880b | ||
|
|
beee9ecd84 | ||
|
|
b94d7a4ae8 | ||
|
|
331999c1a4 | ||
|
|
e5db1ec848 | ||
|
|
17d7161374 | ||
|
|
06d4f2b9f3 | ||
|
|
4b69fdefef | ||
|
|
13db31ede0 | ||
|
|
22a073ba1a | ||
|
|
ce9c115f3d | ||
|
|
767aa9b3e1 | ||
|
|
16ddb2461b | ||
|
|
f75827c4c6 | ||
|
|
cf03e60186 | ||
|
|
8ee071769a | ||
|
|
b312183a7b | ||
|
|
5baaf5faec | ||
|
|
d685bfd11d | ||
|
|
cf4b230b7a | ||
|
|
d46f5f4613 | ||
|
|
945ed854a4 | ||
|
|
25f0672c18 | ||
|
|
6f6c8b2ae0 | ||
|
|
7e62410f98 | ||
|
|
f49e8486c7 | ||
|
|
844bc23f8c | ||
|
|
850226b260 | ||
|
|
c5100567d6 | ||
|
|
5344854199 | ||
|
|
d790b17507 | ||
|
|
667d3851ce | ||
|
|
ba4d345f4f | ||
|
|
52816426fc | ||
|
|
38a423cf2a | ||
|
|
75ad7eb7e4 | ||
|
|
533c3b7b1b | ||
|
|
1c98999994 | ||
|
|
b19b4f047e | ||
|
|
95b6ea0e23 | ||
|
|
b3db549a65 | ||
|
|
cd768000e9 | ||
|
|
da6099c29d | ||
|
|
d36e569ede | ||
|
|
eca3757af5 | ||
|
|
7d8634ce5e | ||
|
|
d4f6785946 | ||
|
|
b468045039 | ||
|
|
f3b4f21c2e | ||
|
|
a50ae64397 | ||
|
|
2a5c58e138 | ||
|
|
d21945a6e6 | ||
|
|
0c572cb029 | ||
|
|
9d30b952ee | ||
|
|
e32876d08d | ||
|
|
340b138962 | ||
|
|
7ae0ba31cb | ||
|
|
f566671975 | ||
|
|
b7ff123e44 | ||
|
|
e1c34bc489 | ||
|
|
dedd341e02 | ||
|
|
dc8fc04b57 | ||
|
|
6a85859a35 | ||
|
|
ff1e8d9e8c | ||
|
|
16da998452 | ||
|
|
2fc7c6ab99 | ||
|
|
834f33e5a5 | ||
|
|
90a13a4e83 | ||
|
|
e4343a4f2f | ||
|
|
7852797bab | ||
|
|
4a93d2ba1e | ||
|
|
9287d711dc | ||
|
|
b5538655e0 |
30
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
30
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[Bug] "
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Configuration**
|
||||
If applicable,
|
||||
```yaml
|
||||
# Please provide your service, widget or otherwise related configuration here
|
||||
```
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here. This includes things like:
|
||||
- Service version or API version
|
||||
- Docker version
|
||||
- Deployment method
|
||||
- Sample YAML configurations
|
||||
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[Feature Request] "
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a service? Please describe.**
|
||||
A clear and concise description of what you would like to see from this service.
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I would like it if [...]
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
128
CODE_OF_CONDUCT.md
Normal file
128
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
ben@phelps.io.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
41
CONTRIBUTING.md
Normal file
41
CONTRIBUTING.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Contributing to Homepage
|
||||
We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's:
|
||||
|
||||
- Reporting a bug
|
||||
- Discussing the current state of the project
|
||||
- Submitting a fix
|
||||
- Proposing new features
|
||||
- Becoming a maintainer
|
||||
|
||||
## We Develop with Github
|
||||
We use github to host code, to track issues and feature requests, as well as accept pull requests.
|
||||
|
||||
## Any contributions you make will be under the GNU General Public License v3.0
|
||||
In short, when you submit code changes, your submissions are understood to be under the same [GNU General Public License v3.0](https://choosealicense.com/licenses/gpl-3.0/) that covers the project. Feel free to contact the maintainers if that's a concern.
|
||||
|
||||
## Report bugs using Github's [issues](https://github.com/benphelps/homepage/issues)
|
||||
We use GitHub issues to track public bugs. Report a bug by [opening a new issue](https://github.com/benphelps/homepage/issues/new); it's that easy!
|
||||
|
||||
## Write bug reports with detail, background, and sample configurations
|
||||
Homepage includes a lot of configuration options and is often deploying in larger systems. Please include as much information (configurations, deployment method, Docker & API versions, etc) as you can when reporting an issue.
|
||||
|
||||
**Great Bug Reports** tend to have:
|
||||
|
||||
- A quick summary and/or background
|
||||
- Steps to reproduce
|
||||
- Be specific!
|
||||
- Give example configurations if you can.
|
||||
- What you expected would happen
|
||||
- What actually happens
|
||||
- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work)
|
||||
|
||||
People *love* thorough bug reports. I'm not even kidding.
|
||||
|
||||
## Use a Consistent Coding Style
|
||||
This project follows the [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript), please follow it when submitting pull requests.
|
||||
|
||||
## License
|
||||
By contributing, you agree that your contributions will be licensed under its GNU General Public License.
|
||||
|
||||
## References
|
||||
This document was adapted from the open-source contribution guidelines for [Facebook's Draft](https://github.com/facebook/draft-js/blob/main/CONTRIBUTING.md)
|
||||
@@ -16,7 +16,7 @@
|
||||
- Container status (Running / Stopped) & statistics (CPU, Memory, Network)
|
||||
- Automatic service discovery (via labels)
|
||||
* Service Integration
|
||||
- Sonarr, Radarr, Readarr, Emby, Jellyfin, Tautulli (Plex)
|
||||
- Sonarr, Radarr, Readarr, Prowlarr, Emby, Jellyfin, Tautulli (Plex)
|
||||
- Ombi, Overseerr, Jellyseerr, NZBGet, SABnzbd, ruTorrent
|
||||
- Portainer, Traefik, Speedtest Tracker, PiHole, Nginx Proxy Manager, Gotify
|
||||
* Information Providers
|
||||
@@ -78,6 +78,8 @@ pnpm install
|
||||
pnpm build
|
||||
```
|
||||
|
||||
If this is your first time starting, copy the `src/skeleton` directory to `config/` to populate initial example config files.
|
||||
|
||||
Finally, run the server:
|
||||
|
||||
```bash
|
||||
@@ -131,4 +133,4 @@ Huge thanks to the all the contributors who have helped make this project what i
|
||||
* [Nonoss117](https://github.com/benphelps/homepage/commits?author=Nonoss117) - French Translation
|
||||
* [quod](https://github.com/benphelps/homepage/commits?author=quod) - Fixed Typos
|
||||
* [schklom](https://github.com/benphelps/homepage/commits?author=schklom) - ARM64, ARMv7 and ARMv6
|
||||
* [xicopitz](https://github.com/benphelps/homepage/commits?author=xicopitz) - Gotify Integration
|
||||
* [xicopitz](https://github.com/benphelps/homepage/commits?author=xicopitz) - Gotify & Prowlarr Integration
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
"@headlessui/react": "^1.7.0",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"classnames": "^2.3.1",
|
||||
"currency-symbol-map": "^5.1.0",
|
||||
"dockerode": "^3.3.4",
|
||||
"i18next": "^21.9.1",
|
||||
"i18next-browser-languagedetector": "^6.1.5",
|
||||
|
||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@@ -5,7 +5,6 @@ specifiers:
|
||||
'@tailwindcss/forms': ^0.5.3
|
||||
autoprefixer: ^10.4.9
|
||||
classnames: ^2.3.1
|
||||
currency-symbol-map: ^5.1.0
|
||||
dockerode: ^3.3.4
|
||||
eslint: ^8.23.1
|
||||
eslint-config-airbnb: ^19.0.4
|
||||
@@ -42,7 +41,6 @@ dependencies:
|
||||
'@headlessui/react': 1.7.0_biqbaboplfbrettd7655fr4n2y
|
||||
'@tailwindcss/forms': 0.5.3_tailwindcss@3.1.8
|
||||
classnames: 2.3.1
|
||||
currency-symbol-map: 5.1.0
|
||||
dockerode: 3.3.4
|
||||
i18next: 21.9.1
|
||||
i18next-browser-languagedetector: 6.1.5
|
||||
@@ -706,10 +704,6 @@ packages:
|
||||
engines: {node: '>=4'}
|
||||
hasBin: true
|
||||
|
||||
/currency-symbol-map/5.1.0:
|
||||
resolution: {integrity: sha512-LO/lzYRw134LMDVnLyAf1dHE5tyO6axEFkR3TXjQIOmMkAM9YL6QsiUwuXzZAmFnuDJcs4hayOgyIYtViXFrLw==}
|
||||
dev: false
|
||||
|
||||
/damerau-levenshtein/1.0.8:
|
||||
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
|
||||
dev: true
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
"resources": {
|
||||
"total": "Gesamt",
|
||||
"free": "Frei",
|
||||
"used": "Gebraucht"
|
||||
"used": "Gebraucht",
|
||||
"load": "Load"
|
||||
},
|
||||
"docker": {
|
||||
"rx": "Rx",
|
||||
@@ -108,11 +109,28 @@
|
||||
"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",
|
||||
"clients": "Clients",
|
||||
"messages": "Messages"
|
||||
},
|
||||
"prowlarr": {
|
||||
"enableIndexers": "Indexers",
|
||||
"numberOfGrabs": "Grabs",
|
||||
"numberOfQueries": "Queries",
|
||||
"numberOfFailGrabs": "Fail Grabs",
|
||||
"numberOfFailQueries": "Fail Queries"
|
||||
},
|
||||
"transmission": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,8 @@
|
||||
"resources": {
|
||||
"total": "Total",
|
||||
"free": "Free",
|
||||
"used": "Used"
|
||||
"used": "Used",
|
||||
"load": "Load"
|
||||
},
|
||||
"docker": {
|
||||
"rx": "RX",
|
||||
@@ -63,6 +64,12 @@
|
||||
"upload": "Upload",
|
||||
"download": "Download"
|
||||
},
|
||||
"transmission": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
},
|
||||
"sonarr": {
|
||||
"wanted": "Wanted",
|
||||
"queued": "Queued",
|
||||
@@ -119,11 +126,22 @@
|
||||
"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"
|
||||
},
|
||||
"gotify": {
|
||||
"apps": "Applications",
|
||||
"clients": "Clients",
|
||||
"messages": "Messages"
|
||||
},
|
||||
"prowlarr":{
|
||||
"enableIndexers": "Indexers",
|
||||
"numberOfGrabs": "Grabs",
|
||||
"numberOfQueries": "Queries",
|
||||
"numberOfFailGrabs": "Fail Grabs",
|
||||
"numberOfFailQueries": "Fail Queries"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
"resources": {
|
||||
"total": "Total",
|
||||
"free": "Libre",
|
||||
"used": "Usado"
|
||||
"used": "Usado",
|
||||
"load": "Carga"
|
||||
},
|
||||
"docker": {
|
||||
"rx": "Recibido",
|
||||
@@ -33,8 +34,8 @@
|
||||
},
|
||||
"rutorrent": {
|
||||
"active": "Activo",
|
||||
"upload": "Subir",
|
||||
"download": "Descargar"
|
||||
"upload": "Subida",
|
||||
"download": "Descarga"
|
||||
},
|
||||
"sonarr": {
|
||||
"wanted": "Más deseado",
|
||||
@@ -47,9 +48,9 @@
|
||||
"movies": "Películas"
|
||||
},
|
||||
"readarr": {
|
||||
"wanted": "Wanted",
|
||||
"queued": "Queued",
|
||||
"books": "Books"
|
||||
"wanted": "Más deseado",
|
||||
"queued": "Puesto en cola",
|
||||
"books": "Libros"
|
||||
},
|
||||
"ombi": {
|
||||
"pending": "Pendiente",
|
||||
@@ -68,7 +69,7 @@
|
||||
},
|
||||
"speedtest": {
|
||||
"upload": "Subir",
|
||||
"download": "Descargar",
|
||||
"download": "Descarga",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"portainer": {
|
||||
@@ -98,21 +99,38 @@
|
||||
"available": "Disponible"
|
||||
},
|
||||
"sabnzbd": {
|
||||
"rate": "Rate",
|
||||
"queue": "Queue",
|
||||
"timeleft": "Time Left"
|
||||
"rate": "Tasa de descarga",
|
||||
"queue": "Puesto en cola",
|
||||
"timeleft": "Tiempo Restante"
|
||||
},
|
||||
"nzbget": {
|
||||
"rate": "Rate",
|
||||
"remaining": "Remaining",
|
||||
"downloaded": "Downloaded"
|
||||
"rate": "Tasa de descarga",
|
||||
"remaining": "Restante",
|
||||
"downloaded": "Descargado"
|
||||
},
|
||||
"coinmarketcap": {
|
||||
"configure": "Configure one or more crypto currencies to track"
|
||||
"configure": "Configurar una o varias criptomonedas para su seguimiento",
|
||||
"1hour": "1 Hour",
|
||||
"1day": "1 Day",
|
||||
"7days": "7 Days",
|
||||
"30days": "30 Days"
|
||||
},
|
||||
"gotify": {
|
||||
"apps": "Applications",
|
||||
"clients": "Clients",
|
||||
"messages": "Messages"
|
||||
"apps": "Aplicaciones",
|
||||
"clients": "Clientes",
|
||||
"messages": "Mensajes"
|
||||
},
|
||||
"prowlarr": {
|
||||
"enableIndexers": "Indexadores",
|
||||
"numberOfGrabs": "Capturas",
|
||||
"numberOfQueries": "Consultas",
|
||||
"numberOfFailGrabs": "Capturas Fallidas",
|
||||
"numberOfFailQueries": "Consultas Fallidas"
|
||||
},
|
||||
"transmission": {
|
||||
"download": "Descarga",
|
||||
"upload": "Subida",
|
||||
"leech": "Egoístas (Leech)",
|
||||
"seed": "Semillas"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
"resources": {
|
||||
"total": "Total",
|
||||
"free": "Libre",
|
||||
"used": "Utilisée"
|
||||
"used": "Utilisé",
|
||||
"load": "Charge"
|
||||
},
|
||||
"docker": {
|
||||
"rx": "Rx",
|
||||
@@ -32,53 +33,53 @@
|
||||
"no_active": "Aucun flux actif"
|
||||
},
|
||||
"rutorrent": {
|
||||
"active": "Active",
|
||||
"active": "Actif",
|
||||
"upload": "Téléverser",
|
||||
"download": "Télécharger"
|
||||
},
|
||||
"sonarr": {
|
||||
"wanted": "Recherchée",
|
||||
"wanted": "Demandé",
|
||||
"queued": "En queue",
|
||||
"series": "Séries"
|
||||
},
|
||||
"radarr": {
|
||||
"wanted": "Recherchée",
|
||||
"wanted": "Demandé",
|
||||
"queued": "En queue",
|
||||
"movies": "Films"
|
||||
},
|
||||
"readarr": {
|
||||
"wanted": "Wanted",
|
||||
"queued": "Queued",
|
||||
"books": "Books"
|
||||
"wanted": "Demandé",
|
||||
"queued": "En Queue",
|
||||
"books": "Livres"
|
||||
},
|
||||
"ombi": {
|
||||
"pending": "En attente",
|
||||
"approved": "Approuvée",
|
||||
"approved": "Validé",
|
||||
"available": "Disponible"
|
||||
},
|
||||
"jellyseerr": {
|
||||
"pending": "En attente",
|
||||
"approved": "Approuvée",
|
||||
"approved": "Validé",
|
||||
"available": "Disponible"
|
||||
},
|
||||
"pihole": {
|
||||
"queries": "Requêtes",
|
||||
"blocked": "Bloquée",
|
||||
"gravity": "La gravité"
|
||||
"blocked": "Bloqué",
|
||||
"gravity": "Listes dom. bloqués"
|
||||
},
|
||||
"speedtest": {
|
||||
"upload": "Téléversement",
|
||||
"download": "Téléchargement",
|
||||
"ping": "Ping-ping"
|
||||
"ping": "Ping"
|
||||
},
|
||||
"portainer": {
|
||||
"running": "Fonctionnement",
|
||||
"running": "Démarré",
|
||||
"stopped": "Arrêté",
|
||||
"total": "Total"
|
||||
},
|
||||
"traefik": {
|
||||
"routers": "Routeurs",
|
||||
"services": "Prestations de service",
|
||||
"services": "Services",
|
||||
"middleware": "Middleware"
|
||||
},
|
||||
"npm": {
|
||||
@@ -105,25 +106,42 @@
|
||||
},
|
||||
"overseerr": {
|
||||
"pending": "En attente",
|
||||
"approved": "Approuvée",
|
||||
"approved": "Validé",
|
||||
"available": "Disponible"
|
||||
},
|
||||
"sabnzbd": {
|
||||
"rate": "Rate",
|
||||
"rate": "Taux",
|
||||
"queue": "Queue",
|
||||
"timeleft": "Time Left"
|
||||
"timeleft": "Temps restant"
|
||||
},
|
||||
"nzbget": {
|
||||
"remaining": "Remaining",
|
||||
"downloaded": "Downloaded",
|
||||
"rate": "Rate"
|
||||
"remaining": "Restant",
|
||||
"downloaded": "Téléchargé",
|
||||
"rate": "Évaluer"
|
||||
},
|
||||
"coinmarketcap": {
|
||||
"configure": "Configure one or more crypto currencies to track"
|
||||
"configure": "Configurer une ou plusieurs crypto-monnaies à suivre",
|
||||
"1hour": "1 Hour",
|
||||
"1day": "1 Day",
|
||||
"7days": "7 Days",
|
||||
"30days": "30 Days"
|
||||
},
|
||||
"gotify": {
|
||||
"apps": "Applications",
|
||||
"clients": "Clients",
|
||||
"messages": "Messages"
|
||||
},
|
||||
"prowlarr": {
|
||||
"enableIndexers": "Indexeurs",
|
||||
"numberOfGrabs": "Capture",
|
||||
"numberOfQueries": "Demandes",
|
||||
"numberOfFailGrabs": "Capture échouée",
|
||||
"numberOfFailQueries": "Demande échouée"
|
||||
},
|
||||
"transmission": {
|
||||
"download": "Réception",
|
||||
"upload": "Envoi",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,8 @@
|
||||
"resources": {
|
||||
"total": "Totale",
|
||||
"free": "Libero",
|
||||
"used": "In utilizzo"
|
||||
"used": "In utilizzo",
|
||||
"load": "Load"
|
||||
},
|
||||
"rutorrent": {
|
||||
"active": "Attivo",
|
||||
@@ -108,11 +109,28 @@
|
||||
"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",
|
||||
"clients": "Clients",
|
||||
"messages": "Messages"
|
||||
},
|
||||
"prowlarr": {
|
||||
"enableIndexers": "Indexers",
|
||||
"numberOfGrabs": "Grabs",
|
||||
"numberOfQueries": "Queries",
|
||||
"numberOfFailGrabs": "Fail Grabs",
|
||||
"numberOfFailQueries": "Fail Queries"
|
||||
},
|
||||
"transmission": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
"resources": {
|
||||
"total": "Totalt",
|
||||
"free": "Ledig",
|
||||
"used": "Brukt"
|
||||
"used": "Brukt",
|
||||
"load": "Last inn"
|
||||
},
|
||||
"docker": {
|
||||
"rx": "Mottatt",
|
||||
@@ -23,13 +24,13 @@
|
||||
"playing": "Spiller",
|
||||
"transcoding": "Transkoding",
|
||||
"bitrate": "Bitrate",
|
||||
"no_active": "No Active Streams"
|
||||
"no_active": "Ingen aktive strømmer"
|
||||
},
|
||||
"tautulli": {
|
||||
"playing": "Spiller",
|
||||
"transcoding": "Transkoding",
|
||||
"bitrate": "Bitrate",
|
||||
"no_active": "No Active Streams"
|
||||
"no_active": "Ingen aktive strømmer"
|
||||
},
|
||||
"rutorrent": {
|
||||
"active": "Aktiv",
|
||||
@@ -93,26 +94,43 @@
|
||||
"current": "Nåværende posisjon"
|
||||
},
|
||||
"overseerr": {
|
||||
"pending": "Pending",
|
||||
"approved": "Approved",
|
||||
"available": "Available"
|
||||
"pending": "Venter",
|
||||
"approved": "Godkjent",
|
||||
"available": "Tilgjengelig"
|
||||
},
|
||||
"sabnzbd": {
|
||||
"rate": "Rate",
|
||||
"queue": "Queue",
|
||||
"timeleft": "Time Left"
|
||||
"rate": "Takt",
|
||||
"queue": "Kø",
|
||||
"timeleft": "Gjenstående tid"
|
||||
},
|
||||
"nzbget": {
|
||||
"rate": "Rate",
|
||||
"downloaded": "Downloaded",
|
||||
"remaining": "Remaining"
|
||||
"rate": "Takt",
|
||||
"downloaded": "Nedlastet",
|
||||
"remaining": "Gjenstående"
|
||||
},
|
||||
"coinmarketcap": {
|
||||
"configure": "Configure one or more crypto currencies to track"
|
||||
"configure": "Sett opp én eller flere kryptovalutaer å holde øye med",
|
||||
"1hour": "1 Hour",
|
||||
"1day": "1 Day",
|
||||
"7days": "7 Days",
|
||||
"30days": "30 Days"
|
||||
},
|
||||
"gotify": {
|
||||
"apps": "Applications",
|
||||
"clients": "Clients",
|
||||
"messages": "Messages"
|
||||
"apps": "Programmer",
|
||||
"clients": "Klienter",
|
||||
"messages": "Meldinger"
|
||||
},
|
||||
"prowlarr": {
|
||||
"enableIndexers": "Indekserere",
|
||||
"numberOfGrabs": "Hentninger",
|
||||
"numberOfQueries": "Spørringer",
|
||||
"numberOfFailGrabs": "Mislykkede hentinger",
|
||||
"numberOfFailQueries": "Mislykkede spørringer"
|
||||
},
|
||||
"transmission": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
"resources": {
|
||||
"total": "Totaal",
|
||||
"free": "Vrij",
|
||||
"used": "Gebruikt"
|
||||
"used": "Gebruikt",
|
||||
"load": "Load"
|
||||
},
|
||||
"docker": {
|
||||
"rx": "RX",
|
||||
@@ -108,11 +109,28 @@
|
||||
"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",
|
||||
"clients": "Clients",
|
||||
"messages": "Messages"
|
||||
},
|
||||
"prowlarr": {
|
||||
"enableIndexers": "Indexers",
|
||||
"numberOfGrabs": "Grabs",
|
||||
"numberOfQueries": "Queries",
|
||||
"numberOfFailGrabs": "Fail Grabs",
|
||||
"numberOfFailQueries": "Fail Queries"
|
||||
},
|
||||
"transmission": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,29 +10,30 @@
|
||||
"resources": {
|
||||
"total": "Total",
|
||||
"free": "Livre",
|
||||
"used": "Usada"
|
||||
"used": "Usado",
|
||||
"load": "Load"
|
||||
},
|
||||
"docker": {
|
||||
"rx": "Rx",
|
||||
"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"
|
||||
},
|
||||
@@ -43,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",
|
||||
@@ -64,7 +65,7 @@
|
||||
"pihole": {
|
||||
"queries": "Consultas",
|
||||
"blocked": "Bloqueado",
|
||||
"gravity": "Gravidade"
|
||||
"gravity": "Gravity"
|
||||
},
|
||||
"speedtest": {
|
||||
"upload": "Envio",
|
||||
@@ -72,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": {
|
||||
@@ -104,26 +105,43 @@
|
||||
"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",
|
||||
"clients": "Clientes",
|
||||
"messages": "Mensagens"
|
||||
},
|
||||
"prowlarr": {
|
||||
"enableIndexers": "Indexers",
|
||||
"numberOfGrabs": "Grabs",
|
||||
"numberOfQueries": "Queries",
|
||||
"numberOfFailGrabs": "Falhados",
|
||||
"numberOfFailQueries": "Pesquisas falhadas"
|
||||
},
|
||||
"transmission": {
|
||||
"download": "Download",
|
||||
"upload": "Envio",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
"resources": {
|
||||
"total": "Общий",
|
||||
"free": "Свободно",
|
||||
"used": "Использовано"
|
||||
"used": "Использовано",
|
||||
"load": "Load"
|
||||
},
|
||||
"docker": {
|
||||
"rx": "Rx",
|
||||
@@ -108,11 +109,28 @@
|
||||
"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",
|
||||
"clients": "Clients",
|
||||
"messages": "Messages"
|
||||
},
|
||||
"prowlarr": {
|
||||
"enableIndexers": "Indexers",
|
||||
"numberOfGrabs": "Grabs",
|
||||
"numberOfQueries": "Queries",
|
||||
"numberOfFailGrabs": "Fail Grabs",
|
||||
"numberOfFailQueries": "Fail Queries"
|
||||
},
|
||||
"transmission": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
"resources": {
|
||||
"total": "Tổng",
|
||||
"free": "Dư",
|
||||
"used": "Đã dùng"
|
||||
"used": "Đã dùng",
|
||||
"load": "Load"
|
||||
},
|
||||
"docker": {
|
||||
"rx": "RX",
|
||||
@@ -108,11 +109,28 @@
|
||||
"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",
|
||||
"clients": "Clients",
|
||||
"messages": "Messages"
|
||||
},
|
||||
"prowlarr": {
|
||||
"numberOfFailGrabs": "Fail Grabs",
|
||||
"enableIndexers": "Indexers",
|
||||
"numberOfGrabs": "Grabs",
|
||||
"numberOfQueries": "Queries",
|
||||
"numberOfFailQueries": "Fail Queries"
|
||||
},
|
||||
"transmission": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,31 +8,32 @@
|
||||
"placeholder": "搜索…"
|
||||
},
|
||||
"resources": {
|
||||
"total": "全部的",
|
||||
"free": "自由的",
|
||||
"used": "用过的"
|
||||
"total": "共",
|
||||
"free": "空闲",
|
||||
"used": "已用",
|
||||
"load": "负载"
|
||||
},
|
||||
"docker": {
|
||||
"rx": "rx",
|
||||
"tx": "TX",
|
||||
"mem": "mem",
|
||||
"cpu": "中央处理器",
|
||||
"rx": "接收",
|
||||
"tx": "发送",
|
||||
"mem": "内存",
|
||||
"cpu": "处理器",
|
||||
"offline": "离线"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "玩",
|
||||
"playing": "正在播放",
|
||||
"transcoding": "转码",
|
||||
"bitrate": "比特率",
|
||||
"no_active": "No Active Streams"
|
||||
"no_active": "暂无播放"
|
||||
},
|
||||
"tautulli": {
|
||||
"playing": "玩",
|
||||
"playing": "正在播放",
|
||||
"transcoding": "转码",
|
||||
"bitrate": "比特率",
|
||||
"no_active": "No Active Streams"
|
||||
"no_active": "暂无播放"
|
||||
},
|
||||
"rutorrent": {
|
||||
"active": "积极的",
|
||||
"active": "活动中",
|
||||
"upload": "上传",
|
||||
"download": "下载"
|
||||
},
|
||||
@@ -42,18 +43,18 @@
|
||||
"series": "系列"
|
||||
},
|
||||
"radarr": {
|
||||
"wanted": "通缉",
|
||||
"queued": "排队",
|
||||
"wanted": "订阅",
|
||||
"queued": "队列",
|
||||
"movies": "电影"
|
||||
},
|
||||
"readarr": {
|
||||
"wanted": "Wanted",
|
||||
"queued": "Queued",
|
||||
"books": "Books"
|
||||
"wanted": "订阅",
|
||||
"queued": "队列",
|
||||
"books": "书籍"
|
||||
},
|
||||
"ombi": {
|
||||
"pending": "待办的",
|
||||
"approved": "得到正式认可的",
|
||||
"approved": "已批准",
|
||||
"available": "可用的"
|
||||
},
|
||||
"jellyseerr": {
|
||||
@@ -72,9 +73,9 @@
|
||||
"ping": "ping"
|
||||
},
|
||||
"portainer": {
|
||||
"running": "跑步",
|
||||
"stopped": "停了下来",
|
||||
"total": "全部的"
|
||||
"running": "运行中",
|
||||
"stopped": "已停止",
|
||||
"total": "总计"
|
||||
},
|
||||
"traefik": {
|
||||
"routers": "路由器",
|
||||
@@ -87,32 +88,49 @@
|
||||
"total": "全部的"
|
||||
},
|
||||
"weather": {
|
||||
"current": "Current Location",
|
||||
"allow": "Click to allow",
|
||||
"updating": "Updating",
|
||||
"wait": "Please wait"
|
||||
"current": "当前位置",
|
||||
"allow": "点击并允许",
|
||||
"updating": "更新中",
|
||||
"wait": "请等待"
|
||||
},
|
||||
"overseerr": {
|
||||
"pending": "Pending",
|
||||
"approved": "Approved",
|
||||
"available": "Available"
|
||||
"pending": "待办",
|
||||
"approved": "已批准",
|
||||
"available": "可用"
|
||||
},
|
||||
"sabnzbd": {
|
||||
"rate": "Rate",
|
||||
"queue": "Queue",
|
||||
"timeleft": "Time Left"
|
||||
"rate": "速率",
|
||||
"queue": "队列",
|
||||
"timeleft": "剩余时间"
|
||||
},
|
||||
"nzbget": {
|
||||
"rate": "Rate",
|
||||
"remaining": "Remaining",
|
||||
"downloaded": "Downloaded"
|
||||
"rate": "速率",
|
||||
"remaining": "剩余",
|
||||
"downloaded": "下载"
|
||||
},
|
||||
"coinmarketcap": {
|
||||
"configure": "Configure one or more crypto currencies to track"
|
||||
"configure": "配置一个或多个需要追踪的加密",
|
||||
"1hour": "1 Hour",
|
||||
"1day": "1 Day",
|
||||
"7days": "7 Days",
|
||||
"30days": "30 Days"
|
||||
},
|
||||
"gotify": {
|
||||
"apps": "Applications",
|
||||
"clients": "Clients",
|
||||
"messages": "Messages"
|
||||
"apps": "应用",
|
||||
"clients": "客户端",
|
||||
"messages": "信息"
|
||||
},
|
||||
"prowlarr": {
|
||||
"enableIndexers": "Indexers",
|
||||
"numberOfGrabs": "Grabs",
|
||||
"numberOfQueries": "Queries",
|
||||
"numberOfFailGrabs": "Fail Grabs",
|
||||
"numberOfFailQueries": "Fail Queries"
|
||||
},
|
||||
"transmission": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
|
||||
136
public/locales/zh-Hant/common.json
Normal file
136
public/locales/zh-Hant/common.json
Normal file
@@ -0,0 +1,136 @@
|
||||
{
|
||||
"widget": {
|
||||
"missing_type": "Missing Widget Type: {{type}}",
|
||||
"api_error": "API Error",
|
||||
"status": "Status"
|
||||
},
|
||||
"weather": {
|
||||
"current": "Current Location",
|
||||
"allow": "Click to allow",
|
||||
"updating": "Updating",
|
||||
"wait": "Please wait"
|
||||
},
|
||||
"docker": {
|
||||
"rx": "RX",
|
||||
"offline": "Offline",
|
||||
"tx": "TX",
|
||||
"mem": "MEM",
|
||||
"cpu": "CPU"
|
||||
},
|
||||
"emby": {
|
||||
"playing": "Playing",
|
||||
"transcoding": "Transcoding",
|
||||
"bitrate": "Bitrate",
|
||||
"no_active": "No Active Streams"
|
||||
},
|
||||
"tautulli": {
|
||||
"playing": "Playing",
|
||||
"transcoding": "Transcoding",
|
||||
"bitrate": "Bitrate",
|
||||
"no_active": "No Active Streams"
|
||||
},
|
||||
"jellyseerr": {
|
||||
"pending": "Pending",
|
||||
"approved": "Approved",
|
||||
"available": "Available"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Search…"
|
||||
},
|
||||
"resources": {
|
||||
"total": "Total",
|
||||
"free": "Free",
|
||||
"used": "Used",
|
||||
"load": "Load"
|
||||
},
|
||||
"nzbget": {
|
||||
"rate": "Rate",
|
||||
"remaining": "Remaining",
|
||||
"downloaded": "Downloaded"
|
||||
},
|
||||
"sabnzbd": {
|
||||
"rate": "Rate",
|
||||
"queue": "Queue",
|
||||
"timeleft": "Time Left"
|
||||
},
|
||||
"rutorrent": {
|
||||
"active": "Active",
|
||||
"upload": "Upload",
|
||||
"download": "Download"
|
||||
},
|
||||
"radarr": {
|
||||
"movies": "Movies",
|
||||
"wanted": "Wanted",
|
||||
"queued": "Queued"
|
||||
},
|
||||
"sonarr": {
|
||||
"wanted": "Wanted",
|
||||
"queued": "Queued",
|
||||
"series": "Series"
|
||||
},
|
||||
"readarr": {
|
||||
"wanted": "Wanted",
|
||||
"queued": "Queued",
|
||||
"books": "Books"
|
||||
},
|
||||
"ombi": {
|
||||
"pending": "Pending",
|
||||
"approved": "Approved",
|
||||
"available": "Available"
|
||||
},
|
||||
"overseerr": {
|
||||
"pending": "Pending",
|
||||
"approved": "Approved",
|
||||
"available": "Available"
|
||||
},
|
||||
"pihole": {
|
||||
"queries": "Queries",
|
||||
"blocked": "Blocked",
|
||||
"gravity": "Gravity"
|
||||
},
|
||||
"speedtest": {
|
||||
"upload": "Upload",
|
||||
"download": "Download",
|
||||
"ping": "Ping"
|
||||
},
|
||||
"portainer": {
|
||||
"running": "Running",
|
||||
"stopped": "Stopped",
|
||||
"total": "Total"
|
||||
},
|
||||
"traefik": {
|
||||
"routers": "Routers",
|
||||
"services": "Services",
|
||||
"middleware": "Middleware"
|
||||
},
|
||||
"gotify": {
|
||||
"clients": "Clients",
|
||||
"apps": "Applications",
|
||||
"messages": "Messages"
|
||||
},
|
||||
"npm": {
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
"total": "Total"
|
||||
},
|
||||
"coinmarketcap": {
|
||||
"configure": "Configure one or more crypto currencies to track",
|
||||
"1hour": "1 Hour",
|
||||
"1day": "1 Day",
|
||||
"7days": "7 Days",
|
||||
"30days": "30 Days"
|
||||
},
|
||||
"prowlarr": {
|
||||
"enableIndexers": "Indexers",
|
||||
"numberOfGrabs": "Grabs",
|
||||
"numberOfQueries": "Queries",
|
||||
"numberOfFailGrabs": "Fail Grabs",
|
||||
"numberOfFailQueries": "Fail Queries"
|
||||
},
|
||||
"transmission": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"leech": "Leech",
|
||||
"seed": "Seed"
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ export default function Item({ bookmark }) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => window.open(bookmark.href, "_blank").focus()}
|
||||
className="w-full text-left mb-3 cursor-pointer rounded-md font-medium text-theme-700 hover:text-theme-700 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-black/10 dark:shadow-black/20 bg-white/50 hover:bg-theme-300/10 dark:bg-white/10 dark:hover:bg-white/20 backdrop-blur-md"
|
||||
className="w-full text-left mb-3 cursor-pointer rounded-md font-medium text-theme-700 hover:text-theme-700 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-black/10 dark:shadow-black/20 bg-white/50 hover:bg-theme-300/10 dark:bg-white/10 dark:hover:bg-white/20"
|
||||
>
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0 flex items-center justify-center w-11 bg-theme-500/10 dark:bg-theme-900/50 text-theme-700 hover:text-theme-700 dark:text-theme-200 text-sm font-medium rounded-l-md">
|
||||
|
||||
48
src/components/services/dropdown.jsx
Normal file
48
src/components/services/dropdown.jsx
Normal file
@@ -0,0 +1,48 @@
|
||||
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, value, setValue }) {
|
||||
return (
|
||||
<Menu as="div" className="relative inline-block text-left">
|
||||
<div>
|
||||
<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>
|
||||
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
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-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((option) => (
|
||||
<Menu.Item key={option.value} as={Fragment}>
|
||||
<button
|
||||
onClick={() => {
|
||||
setValue(option.value);
|
||||
}}
|
||||
type="button"
|
||||
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"
|
||||
)}
|
||||
>
|
||||
{option.label}
|
||||
</button>
|
||||
</Menu.Item>
|
||||
))}
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
@@ -36,7 +36,7 @@ export default function Item({ service }) {
|
||||
<div
|
||||
className={`${
|
||||
hasLink ? "cursor-pointer " : " "
|
||||
}transition-all h-15 mb-3 p-1 rounded-md font-medium text-theme-700 hover:text-theme-700/70 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-black/10 dark:shadow-black/20 bg-white/50 hover:bg-theme-300/20 dark:bg-white/10 dark:hover:bg-white/20 backdrop-blur-md`}
|
||||
}transition-all h-15 mb-3 p-1 rounded-md font-medium text-theme-700 hover:text-theme-700/70 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-black/10 dark:shadow-black/20 bg-white/50 hover:bg-theme-300/20 dark:bg-white/10 dark:hover:bg-white/20`}
|
||||
>
|
||||
<div className="flex select-none">
|
||||
{service.icon &&
|
||||
|
||||
@@ -8,6 +8,7 @@ import Portainer from "./widgets/service/portainer";
|
||||
import Emby from "./widgets/service/emby";
|
||||
import Nzbget from "./widgets/service/nzbget";
|
||||
import SABnzbd from "./widgets/service/sabnzbd";
|
||||
import Transmission from "./widgets/service/transmission";
|
||||
import Docker from "./widgets/service/docker";
|
||||
import Pihole from "./widgets/service/pihole";
|
||||
import Rutorrent from "./widgets/service/rutorrent";
|
||||
@@ -20,6 +21,7 @@ import Npm from "./widgets/service/npm";
|
||||
import Tautulli from "./widgets/service/tautulli";
|
||||
import CoinMarketCap from "./widgets/service/coinmarketcap";
|
||||
import Gotify from "./widgets/service/gotify";
|
||||
import Prowlarr from "./widgets/service/prowlarr";
|
||||
|
||||
const widgetMappings = {
|
||||
docker: Docker,
|
||||
@@ -31,6 +33,8 @@ const widgetMappings = {
|
||||
emby: Emby,
|
||||
jellyfin: Jellyfin,
|
||||
nzbget: Nzbget,
|
||||
sabnzbd: SABnzbd,
|
||||
transmission: Transmission,
|
||||
pihole: Pihole,
|
||||
rutorrent: Rutorrent,
|
||||
speedtest: Speedtest,
|
||||
@@ -41,7 +45,7 @@ const widgetMappings = {
|
||||
npm: Npm,
|
||||
tautulli: Tautulli,
|
||||
gotify: Gotify,
|
||||
sabnzbd: SABnzbd
|
||||
prowlarr: Prowlarr,
|
||||
};
|
||||
|
||||
export default function Widget({ service }) {
|
||||
|
||||
@@ -1,15 +1,26 @@
|
||||
import useSWR from "swr";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import getSymbolFromCurrency from "currency-symbol-map";
|
||||
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 }) {
|
||||
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;
|
||||
@@ -30,7 +41,7 @@ export default function CoinMarketCap({ service }) {
|
||||
return <Widget error={t("widget.api_error")} />;
|
||||
}
|
||||
|
||||
if (!statsData) {
|
||||
if (!statsData || !dateRange) {
|
||||
return (
|
||||
<Widget>
|
||||
<Block value={t("coinmarketcap.configure")} />
|
||||
@@ -39,28 +50,36 @@ export default function CoinMarketCap({ service }) {
|
||||
}
|
||||
|
||||
const { data } = statsData;
|
||||
const currencySymbol = getSymbolFromCurrency(currencyCode);
|
||||
|
||||
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">
|
||||
{currencySymbol}
|
||||
{data[key].quote[currencyCode].price.toFixed(2)}
|
||||
{t("common.number", {
|
||||
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_1h > 0 ? "text-emerald-300" : "text-rose-300"
|
||||
data[symbol].quote[currencyCode][`percent_change_${dateRange}`] > 0
|
||||
? "text-emerald-300"
|
||||
: "text-rose-300"
|
||||
}`}
|
||||
>
|
||||
{data[key].quote[currencyCode].percent_change_1h.toFixed(2)}%
|
||||
{data[symbol].quote[currencyCode][`percent_change_${dateRange}`].toFixed(2)}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
55
src/components/services/widgets/service/prowlarr.jsx
Normal file
55
src/components/services/widgets/service/prowlarr.jsx
Normal file
@@ -0,0 +1,55 @@
|
||||
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 Prowlarr({ service }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const config = service.widget;
|
||||
|
||||
const { data: indexersData, error: indexersError } = useSWR(formatApiUrl(config, "indexer"));
|
||||
const { data: grabsData, error: grabsError } = useSWR(formatApiUrl(config, "indexerstats"));
|
||||
|
||||
if (indexersError || grabsError) {
|
||||
return <Widget error={t("widget.api_error")} />;
|
||||
}
|
||||
|
||||
if (!indexersData || !grabsData) {
|
||||
return (
|
||||
<Widget>
|
||||
<Block label={t("prowlarr.enableIndexers")} />
|
||||
<Block label={t("prowlarr.numberOfGrabs")} />
|
||||
<Block label={t("prowlarr.numberOfQueries")} />
|
||||
<Block label={t("prowlarr.numberOfFailGrabs")} />
|
||||
<Block label={t("prowlarr.numberOfFailQueries")} />
|
||||
</Widget>
|
||||
);
|
||||
}
|
||||
|
||||
const indexers = indexersData?.filter((indexer) => indexer.enable === true);
|
||||
|
||||
let numberOfGrabs = 0
|
||||
let numberOfQueries = 0
|
||||
let numberOfFailedGrabs = 0
|
||||
let numberOfFailedQueries = 0
|
||||
grabsData?.indexers?.forEach(element => {
|
||||
numberOfGrabs += element.numberOfGrabs;
|
||||
numberOfQueries += element.numberOfQueries;
|
||||
numberOfFailedGrabs += numberOfFailedGrabs + element.numberOfFailedGrabs;
|
||||
numberOfFailedQueries += numberOfFailedQueries + element.numberOfFailedQueries;
|
||||
});
|
||||
|
||||
return (
|
||||
<Widget>
|
||||
<Block label={t("prowlarr.enableIndexers")} value={indexers.length} />
|
||||
<Block label={t("prowlarr.numberOfGrabs")} value={numberOfGrabs} />
|
||||
<Block label={t("prowlarr.numberOfQueries")} value={numberOfQueries} />
|
||||
<Block label={t("prowlarr.numberOfFailGrabs")} value={numberOfFailedGrabs} />
|
||||
<Block label={t("prowlarr.numberOfFailQueries")} value={numberOfFailedQueries} />
|
||||
</Widget>
|
||||
);
|
||||
}
|
||||
@@ -29,7 +29,7 @@ export default function SABnzbd({ service }) {
|
||||
|
||||
return (
|
||||
<Widget>
|
||||
<Block label={t("sabnzbd.rate")} value={`${queueData.queue.speed}bps`} />
|
||||
<Block label={t("sabnzbd.rate")} value={`${queueData.queue.speed}B/s`} />
|
||||
<Block label={t("sabnzbd.queue")} value={queueData.queue.noofslots} />
|
||||
<Block label={t("sabnzbd.timeleft")} value={queueData.queue.timeleft} />
|
||||
</Widget>
|
||||
|
||||
@@ -48,10 +48,10 @@ function SingleSessionEntry({ session }) {
|
||||
/>
|
||||
<div className="text-xs z-10 self-center ml-1">
|
||||
{state === "paused" && (
|
||||
<BsFillPlayFill className="inline-block w-4 h-4 cursor-pointer -mt-[1px] mr-1 opacity-80" />
|
||||
<BsPauseFill className="inline-block w-4 h-4 cursor-pointer -mt-[1px] mr-1 opacity-80" />
|
||||
)}
|
||||
{state !== "paused" && (
|
||||
<BsPauseFill className="inline-block w-4 h-4 cursor-pointer -mt-[1px] mr-1 opacity-80" />
|
||||
<BsFillPlayFill className="inline-block w-4 h-4 cursor-pointer -mt-[1px] mr-1 opacity-80" />
|
||||
)}
|
||||
</div>
|
||||
<div className="grow " />
|
||||
@@ -76,10 +76,10 @@ function SessionEntry({ session }) {
|
||||
/>
|
||||
<div className="text-xs z-10 self-center ml-1">
|
||||
{state === "paused" && (
|
||||
<BsFillPlayFill className="inline-block w-4 h-4 cursor-pointer -mt-[1px] mr-1 opacity-80" />
|
||||
<BsPauseFill className="inline-block w-4 h-4 cursor-pointer -mt-[1px] mr-1 opacity-80" />
|
||||
)}
|
||||
{state !== "paused" && (
|
||||
<BsPauseFill className="inline-block w-4 h-4 cursor-pointer -mt-[1px] mr-1 opacity-80" />
|
||||
<BsFillPlayFill className="inline-block w-4 h-4 cursor-pointer -mt-[1px] mr-1 opacity-80" />
|
||||
)}
|
||||
<span>{full_title}</span>
|
||||
</div>
|
||||
|
||||
70
src/components/services/widgets/service/transmission.jsx
Normal file
70
src/components/services/widgets/service/transmission.jsx
Normal file
@@ -0,0 +1,70 @@
|
||||
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 Transmission({ service }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const config = service.widget;
|
||||
|
||||
const { data: torrentData, error: torrentError } = useSWR(formatApiUrl(config));
|
||||
|
||||
if (torrentError) {
|
||||
return <Widget error={t("widget.api_error")} />;
|
||||
}
|
||||
|
||||
if (!torrentData) {
|
||||
return (
|
||||
<Widget>
|
||||
<Block label={t("transmission.leech")} />
|
||||
<Block label={t("transmission.download")} />
|
||||
<Block label={t("transmission.seed")} />
|
||||
<Block label={t("transmission.upload")} />
|
||||
</Widget>
|
||||
);
|
||||
}
|
||||
|
||||
const { torrents } = torrentData.arguments;
|
||||
let rateDl = 0;
|
||||
let rateUl = 0;
|
||||
let completed = 0;
|
||||
|
||||
for (let i = 0; i < torrents.length; i += 1) {
|
||||
const torrent = torrents[i];
|
||||
rateDl += torrent.rateDownload;
|
||||
rateUl += torrent.rateUpload;
|
||||
if (torrent.percentDone === 1) {
|
||||
completed += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const leech = torrents.length - completed;
|
||||
|
||||
let unitsDl = "KB/s";
|
||||
let unitsUl = "KB/s";
|
||||
rateDl /= 1024;
|
||||
rateUl /= 1024;
|
||||
|
||||
if (rateDl > 1024) {
|
||||
rateDl /= 1024;
|
||||
unitsDl = "MB/s";
|
||||
}
|
||||
|
||||
if (rateUl > 1024) {
|
||||
rateUl /= 1024;
|
||||
unitsUl = "MB/s";
|
||||
}
|
||||
|
||||
return (
|
||||
<Widget>
|
||||
<Block label={t("transmission.leech")} value={leech} />
|
||||
<Block label={t("transmission.download")} value={`${rateDl.toFixed(2)} ${unitsDl}`} />
|
||||
<Block label={t("transmission.seed")} value={completed} />
|
||||
<Block label={t("transmission.upload")} value={`${rateUl.toFixed(2)} ${unitsUl}`} />
|
||||
</Widget>
|
||||
);
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import UsageBar from "./usage-bar";
|
||||
|
||||
export default function Cpu() {
|
||||
export default function Cpu({ expanded }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { data, error } = useSWR(`/api/widgets/resources?type=cpu`, {
|
||||
@@ -39,11 +39,29 @@ export default function Cpu() {
|
||||
return (
|
||||
<div className="flex-none flex flex-row items-center mr-3 py-1.5">
|
||||
<FiCpu className="text-theme-800 dark:text-theme-200 w-5 h-5" />
|
||||
<div className="flex flex-col ml-3 text-left font-mono min-w-[80px]">
|
||||
<div className="text-theme-800 dark:text-theme-200 text-xs">
|
||||
{t("common.number", { value: data.cpu.usage, style: "unit", unit: "percent", maximumFractionDigits: 0 })}{" "}
|
||||
{t("docker.cpu")}
|
||||
<div className="flex flex-col ml-3 text-left min-w-[85px]">
|
||||
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
||||
<div className="pl-0.5">
|
||||
{t("common.number", {
|
||||
value: data.cpu.usage,
|
||||
style: "unit",
|
||||
unit: "percent",
|
||||
maximumFractionDigits: 0,
|
||||
})}
|
||||
</div>
|
||||
<div className="pr-1">{t("docker.cpu")}</div>
|
||||
</div>
|
||||
{expanded && (
|
||||
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
||||
<div className="pl-0.5">
|
||||
{t("common.number", {
|
||||
value: data.cpu.load,
|
||||
maximumFractionDigits: 2,
|
||||
})}
|
||||
</div>
|
||||
<div className="pr-1">{t("resources.load")}</div>
|
||||
</div>
|
||||
)}
|
||||
<UsageBar percent={percent} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import UsageBar from "./usage-bar";
|
||||
|
||||
export default function Disk({ options }) {
|
||||
export default function Disk({ options, expanded }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { data, error } = useSWR(`/api/widgets/resources?type=disk&target=${options.disk}`, {
|
||||
@@ -37,15 +37,19 @@ export default function Disk({ options }) {
|
||||
const percent = Math.round((data.drive.usedGb / data.drive.totalGb) * 100);
|
||||
|
||||
return (
|
||||
<div className="flex-none flex flex-row items-center mr-3 py-1.5 group">
|
||||
<div className="flex-none flex flex-row items-center mr-3 py-1.5">
|
||||
<FiHardDrive className="text-theme-800 dark:text-theme-200 w-5 h-5" />
|
||||
<div className="flex flex-col ml-3 text-left min-w-[80px]">
|
||||
<span className="text-theme-800 dark:text-theme-200 text-xs group-hover:hidden">
|
||||
{t("common.bytes", { value: data.drive.freeGb * 1024 * 1024 * 1024 })} {t("resources.free")}
|
||||
</span>
|
||||
<span className="text-theme-800 dark:text-theme-200 text-xs hidden group-hover:block">
|
||||
{t("common.bytes", { value: data.drive.totalGb * 1024 * 1024 * 1024 })} {t("resources.total")}
|
||||
<div className="flex flex-col ml-3 text-left min-w-[85px]">
|
||||
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
||||
<div className="pl-0.5">{t("common.bytes", { value: data.drive.freeGb * 1024 * 1024 * 1024 })}</div>
|
||||
<div className="pr-1">{t("resources.free")}</div>
|
||||
</span>
|
||||
{expanded && (
|
||||
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
||||
<div className="pl-0.5">{t("common.bytes", { value: data.drive.totalGb * 1024 * 1024 * 1024 })}</div>
|
||||
<div className="pr-1">{t("resources.total")}</div>
|
||||
</span>
|
||||
)}
|
||||
<UsageBar percent={percent} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import UsageBar from "./usage-bar";
|
||||
|
||||
export default function Memory() {
|
||||
export default function Memory({ expanded }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { data, error } = useSWR(`/api/widgets/resources?type=memory`, {
|
||||
@@ -37,15 +37,27 @@ export default function Memory() {
|
||||
const percent = Math.round((data.memory.usedMemMb / data.memory.totalMemMb) * 100);
|
||||
|
||||
return (
|
||||
<div className="flex-none flex flex-row items-center mr-3 py-1.5 group">
|
||||
<div className="flex-none flex flex-row items-center mr-3 py-1.5">
|
||||
<FaMemory className="text-theme-800 dark:text-theme-200 w-5 h-5" />
|
||||
<div className="flex flex-col ml-3 text-left min-w-[80px]">
|
||||
<span className="text-theme-800 dark:text-theme-200 text-xs group-hover:hidden">
|
||||
{t("common.bytes", { value: data.memory.freeMemMb * 1024 * 1024 })} {t("resources.free")}
|
||||
</span>
|
||||
<span className="text-theme-800 dark:text-theme-200 text-xs hidden group-hover:block">
|
||||
{t("common.bytes", { value: data.memory.usedMemMb * 1024 * 1024 })} {t("resources.used")}
|
||||
<div className="flex flex-col ml-3 text-left min-w-[85px]">
|
||||
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
||||
<div className="pl-0.5">
|
||||
{t("common.bytes", { value: data.memory.freeMemMb * 1024 * 1024, maximumFractionDigits: 0, binary: true })}
|
||||
</div>
|
||||
<div className="pr-1">{t("resources.free")}</div>
|
||||
</span>
|
||||
{expanded && (
|
||||
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
|
||||
<div className="pl-0.5">
|
||||
{t("common.bytes", {
|
||||
value: data.memory.totalMemMb * 1024 * 1024,
|
||||
maximumFractionDigits: 0,
|
||||
binary: true,
|
||||
})}
|
||||
</div>
|
||||
<div className="pr-1">{t("resources.total")}</div>
|
||||
</span>
|
||||
)}
|
||||
<UsageBar percent={percent} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,14 +3,15 @@ import Cpu from "./cpu";
|
||||
import Memory from "./memory";
|
||||
|
||||
export default function Resources({ options }) {
|
||||
const { expanded } = options;
|
||||
return (
|
||||
<div className="flex flex-col max-w:full sm:basis-auto self-center m-auto flex-wrap">
|
||||
<div className="flex flex-row self-center flex-wrap justify-between">
|
||||
{options.cpu && <Cpu />}
|
||||
{options.memory && <Memory />}
|
||||
{options.cpu && <Cpu expanded={expanded} />}
|
||||
{options.memory && <Memory expanded={expanded} />}
|
||||
{Array.isArray(options.disk)
|
||||
? options.disk.map((disk) => <Disk key={disk} options={{ disk }} />)
|
||||
: options.disk && <Disk options={options} />}
|
||||
? options.disk.map((disk) => <Disk key={disk} options={{ disk }} expanded={expanded} />)
|
||||
: options.disk && <Disk options={options} expanded={expanded} />}
|
||||
</div>
|
||||
{options.label && (
|
||||
<div className="ml-6 pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">{options.label}</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export default function UsageBar({ percent }) {
|
||||
return (
|
||||
<div className="mt-0.5 w-full bg-theme-800/30 rounded-full h-1 dark:bg-white/20 backdrop-blur-md">
|
||||
<div className="mt-0.5 w-full bg-theme-800/30 rounded-full h-1 dark:bg-white/20">
|
||||
<div
|
||||
className="bg-theme-800/70 h-1 rounded-full dark:bg-white/50"
|
||||
style={{
|
||||
|
||||
@@ -62,8 +62,7 @@ export default function Search({ options }) {
|
||||
bg-white/50 dark:bg-white/10
|
||||
focus:ring-theme-500 dark:focus:ring-white/50
|
||||
focus:border-theme-500 dark:focus:border-white/50
|
||||
border border-theme-300 dark:border-theme-200/50
|
||||
backdrop-blur-md"
|
||||
border border-theme-300 dark:border-theme-200/50"
|
||||
placeholder={t("search.placeholder")}
|
||||
onChange={(s) => setQuery(s.currentTarget.value)}
|
||||
required
|
||||
|
||||
@@ -3,6 +3,7 @@ import credentialedProxyHandler from "utils/proxies/credentialed";
|
||||
import rutorrentProxyHandler from "utils/proxies/rutorrent";
|
||||
import nzbgetProxyHandler from "utils/proxies/nzbget";
|
||||
import npmProxyHandler from "utils/proxies/npm";
|
||||
import transmissionProxyHandler from "utils/proxies/transmission";
|
||||
|
||||
const serviceProxyHandlers = {
|
||||
// uses query param auth
|
||||
@@ -23,10 +24,12 @@ const serviceProxyHandlers = {
|
||||
overseerr: credentialedProxyHandler,
|
||||
ombi: credentialedProxyHandler,
|
||||
coinmarketcap: credentialedProxyHandler,
|
||||
prowlarr: credentialedProxyHandler,
|
||||
// super specific handlers
|
||||
rutorrent: rutorrentProxyHandler,
|
||||
nzbget: nzbgetProxyHandler,
|
||||
npm: npmProxyHandler,
|
||||
transmission: transmissionProxyHandler,
|
||||
};
|
||||
|
||||
export default async function handler(req, res) {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -9,6 +9,7 @@ const formats = {
|
||||
traefik: `{url}/api/{endpoint}`,
|
||||
portainer: `{url}/api/endpoints/{env}/{endpoint}`,
|
||||
rutorrent: `{url}/plugins/httprpc/action.php`,
|
||||
transmission: `{url}/transmission/rpc`,
|
||||
jellyseerr: `{url}/api/v1/{endpoint}`,
|
||||
overseerr: `{url}/api/v1/{endpoint}`,
|
||||
ombi: `{url}/api/v1/{endpoint}`,
|
||||
@@ -17,6 +18,7 @@ const formats = {
|
||||
sabnzbd: `{url}/api/?apikey={key}&output=json&mode={endpoint}`,
|
||||
coinmarketcap: `https://pro-api.coinmarketcap.com/{endpoint}`,
|
||||
gotify: `{url}/{endpoint}`,
|
||||
prowlarr: `{url}/api/v1/{endpoint}`,
|
||||
};
|
||||
|
||||
export function formatApiCall(api, args) {
|
||||
|
||||
@@ -12,7 +12,7 @@ export function httpsRequest(url, params) {
|
||||
});
|
||||
|
||||
response.on("end", () => {
|
||||
resolve([response.statusCode, response.headers["content-type"], Buffer.concat(data)]);
|
||||
resolve([response.statusCode, response.headers["content-type"], Buffer.concat(data), response.headers]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,6 +20,10 @@ export function httpsRequest(url, params) {
|
||||
reject([500, error]);
|
||||
});
|
||||
|
||||
if (params.body) {
|
||||
request.write(params.body);
|
||||
}
|
||||
|
||||
request.end();
|
||||
});
|
||||
}
|
||||
@@ -34,7 +38,7 @@ export function httpRequest(url, params) {
|
||||
});
|
||||
|
||||
response.on("end", () => {
|
||||
resolve([response.statusCode, response.headers["content-type"], Buffer.concat(data)]);
|
||||
resolve([response.statusCode, response.headers["content-type"], Buffer.concat(data), response.headers]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -42,6 +46,10 @@ export function httpRequest(url, params) {
|
||||
reject([500, error]);
|
||||
});
|
||||
|
||||
if (params.body) {
|
||||
request.write(params.body);
|
||||
}
|
||||
|
||||
request.end();
|
||||
});
|
||||
}
|
||||
|
||||
56
src/utils/proxies/transmission.js
Normal file
56
src/utils/proxies/transmission.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import { httpProxy } from "utils/http";
|
||||
import { formatApiCall } from "utils/api-helpers";
|
||||
import getServiceWidget from "utils/service-helpers";
|
||||
|
||||
export default async function transmissionProxyHandler(req, res) {
|
||||
const { group, service, endpoint } = req.query;
|
||||
|
||||
if (!group || !service) {
|
||||
return res.status(400).json({ error: "Invalid proxy service type" });
|
||||
}
|
||||
|
||||
const widget = await getServiceWidget(group, service);
|
||||
|
||||
if (!widget) {
|
||||
return res.status(400).json({ error: "Invalid proxy service type" });
|
||||
}
|
||||
|
||||
const url = new URL(formatApiCall(widget.type, { endpoint, ...widget }));
|
||||
const csrfHeaderName = "x-transmission-session-id";
|
||||
|
||||
const method = "POST";
|
||||
const auth = `${widget.username}:${widget.password}`;
|
||||
const body = JSON.stringify({
|
||||
method: "torrent-get",
|
||||
arguments: {
|
||||
fields: ["percentDone", "status", "rateDownload", "rateUpload"]
|
||||
}
|
||||
});
|
||||
|
||||
const headers = {
|
||||
"content-type": "application/json",
|
||||
};
|
||||
|
||||
let [status, contentType, data, responseHeaders] = await httpProxy(url, {
|
||||
method,
|
||||
auth,
|
||||
body,
|
||||
headers,
|
||||
});
|
||||
|
||||
if (status === 409) {
|
||||
// Transmission is rejecting the request, but returning a CSRF token
|
||||
headers[csrfHeaderName] = responseHeaders[csrfHeaderName];
|
||||
|
||||
// retry the request, now with the CSRF token
|
||||
[status, contentType, data, responseHeaders] = await httpProxy(url, {
|
||||
method,
|
||||
auth,
|
||||
body,
|
||||
headers,
|
||||
});
|
||||
}
|
||||
|
||||
if (contentType) res.setHeader("Content-Type", contentType);
|
||||
return res.status(status).send(data);
|
||||
}
|
||||
Reference in New Issue
Block a user