Compare commits

..

270 Commits

Author SHA1 Message Date
Ben Phelps
adeffbcf71 update readme 2022-09-15 08:44:52 +03:00
Anonymous
f0ca7b753f Translated using Weblate (Catalan)
Currently translated at 93.0% (93 of 100 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ca/
2022-09-15 07:21:24 +02:00
Anonymous
1bbde65121 Translated using Weblate (Chinese (Traditional))
Currently translated at 9.0% (9 of 100 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hant/
2022-09-15 07:21:24 +02:00
Anonymous
3acdf041e9 Translated using Weblate (Dutch)
Currently translated at 62.0% (62 of 100 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nl/
2022-09-15 07:21:23 +02:00
Anonymous
fce755f0c4 Translated using Weblate (Vietnamese)
Currently translated at 44.0% (44 of 100 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/vi/
2022-09-15 07:21:23 +02:00
Anonymous
b1cdccc020 Translated using Weblate (Norwegian Bokmål)
Currently translated at 79.0% (79 of 100 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nb_NO/
2022-09-15 07:21:23 +02:00
Anonymous
eee070f1cd Translated using Weblate (Italian)
Currently translated at 69.0% (69 of 100 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/it/
2022-09-15 07:21:23 +02:00
Anonymous
e36dd56e3b Translated using Weblate (Chinese (Simplified))
Currently translated at 83.0% (83 of 100 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-09-15 07:21:22 +02:00
Anonymous
aa7d08e93f Translated using Weblate (Russian)
Currently translated at 21.0% (21 of 100 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ru/
2022-09-15 07:21:22 +02:00
Anonymous
df67896a55 Translated using Weblate (Portuguese)
Currently translated at 88.0% (88 of 100 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pt/
2022-09-15 07:21:22 +02:00
Anonymous
3fb790c33c Translated using Weblate (French)
Currently translated at 95.0% (95 of 100 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-15 07:21:21 +02:00
Anonymous
850a1a39fe Translated using Weblate (Spanish)
Currently translated at 93.0% (93 of 100 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-15 07:21:21 +02:00
Anonymous
942b575c18 Translated using Weblate (German)
Currently translated at 64.0% (64 of 100 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/de/
2022-09-15 07:21:21 +02:00
Ben Phelps
6dc53052b6 Merge pull request #163 from JazzFisch/add-lidarr
Add Lidarr widget
2022-09-15 08:21:10 +03:00
Jason Fischer
7e99b3e505 Merge branch 'main' into add-lidarr 2022-09-14 19:41:43 -07:00
Anonymous
0c474e6b74 Translated using Weblate (Catalan)
Currently translated at 95.8% (93 of 97 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ca/
2022-09-15 04:36:31 +02:00
Anonymous
d5b92478ba Translated using Weblate (Chinese (Traditional))
Currently translated at 9.2% (9 of 97 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hant/
2022-09-15 04:36:31 +02:00
Anonymous
3b45699e58 Translated using Weblate (Dutch)
Currently translated at 63.9% (62 of 97 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nl/
2022-09-15 04:36:31 +02:00
Anonymous
587a317e91 Translated using Weblate (Vietnamese)
Currently translated at 45.3% (44 of 97 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/vi/
2022-09-15 04:36:31 +02:00
Anonymous
62db38b0d1 Translated using Weblate (Norwegian Bokmål)
Currently translated at 81.4% (79 of 97 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nb_NO/
2022-09-15 04:36:31 +02:00
Anonymous
34ccca6a91 Translated using Weblate (Italian)
Currently translated at 71.1% (69 of 97 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/it/
2022-09-15 04:36:30 +02:00
Anonymous
bf94d6bf5b Translated using Weblate (Chinese (Simplified))
Currently translated at 85.5% (83 of 97 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-09-15 04:36:30 +02:00
Anonymous
fc0658574c Translated using Weblate (Russian)
Currently translated at 21.6% (21 of 97 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ru/
2022-09-15 04:36:30 +02:00
Anonymous
b9e8ee4d0e Translated using Weblate (Portuguese)
Currently translated at 90.7% (88 of 97 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pt/
2022-09-15 04:36:30 +02:00
Anonymous
47dc1a3960 Translated using Weblate (French)
Currently translated at 97.9% (95 of 97 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-15 04:36:29 +02:00
Anonymous
6796f5cb28 Translated using Weblate (Spanish)
Currently translated at 95.8% (93 of 97 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-15 04:36:29 +02:00
Anonymous
f5fb5b32e4 Translated using Weblate (German)
Currently translated at 65.9% (64 of 97 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/de/
2022-09-15 04:36:29 +02:00
Trung Le
3941e7fb1c Translated using Weblate (Vietnamese)
Currently translated at 46.3% (44 of 95 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/vi/
2022-09-15 04:36:23 +02:00
Nonoss117
a28051fa16 Translated using Weblate (French)
Currently translated at 100.0% (95 of 95 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-15 04:36:23 +02:00
Ben Phelps
ace1610dfc Merge pull request #160 from JazzFisch/add-bazarr
Add Bazarr widget
2022-09-15 05:36:19 +03:00
Jason Fischer
cf2f987fd4 Update completed album logic 2022-09-14 19:36:15 -07:00
Jason Fischer
1f2639fbb5 Add Lidarr widget 2022-09-14 19:30:51 -07:00
Ben Phelps
3c2880e4ba allow search to be auto-focused 2022-09-15 05:28:40 +03:00
Ben Phelps
db18519c16 allow changing language from settings.yaml 2022-09-15 05:17:30 +03:00
Jason Fischer
b520713dc3 Add Bazarr widget
associated: #110
2022-09-14 16:15:26 -07:00
Anonymous
15a8c4f0d7 Translated using Weblate (Catalan)
Currently translated at 97.8% (93 of 95 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ca/
2022-09-14 21:29:51 +02:00
Anonymous
a7d80fec89 Translated using Weblate (Chinese (Traditional))
Currently translated at 9.4% (9 of 95 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hant/
2022-09-14 21:29:51 +02:00
Anonymous
d32ecc9080 Translated using Weblate (Dutch)
Currently translated at 65.2% (62 of 95 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nl/
2022-09-14 21:29:51 +02:00
Anonymous
370f156ae0 Translated using Weblate (Vietnamese)
Currently translated at 34.7% (33 of 95 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/vi/
2022-09-14 21:29:50 +02:00
Anonymous
97736d4163 Translated using Weblate (Norwegian Bokmål)
Currently translated at 83.1% (79 of 95 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nb_NO/
2022-09-14 21:29:50 +02:00
Anonymous
a0338beaae Translated using Weblate (Italian)
Currently translated at 72.6% (69 of 95 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/it/
2022-09-14 21:29:50 +02:00
Anonymous
8bb850d96b Translated using Weblate (Chinese (Simplified))
Currently translated at 87.3% (83 of 95 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-09-14 21:29:50 +02:00
Anonymous
999e55c7af Translated using Weblate (Russian)
Currently translated at 22.1% (21 of 95 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ru/
2022-09-14 21:29:49 +02:00
Anonymous
c3533de7fa Translated using Weblate (Portuguese)
Currently translated at 92.6% (88 of 95 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pt/
2022-09-14 21:29:49 +02:00
Anonymous
0543f28fd4 Translated using Weblate (French)
Currently translated at 97.8% (93 of 95 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-14 21:29:49 +02:00
Anonymous
48f73eab06 Translated using Weblate (Spanish)
Currently translated at 97.8% (93 of 95 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-14 21:29:49 +02:00
Anonymous
14c572102e Translated using Weblate (German)
Currently translated at 67.3% (64 of 95 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/de/
2022-09-14 21:29:48 +02:00
Juan Manuel Bennàssar Carretero
c58a52c797 Translated using Weblate (Catalan)
Currently translated at 100.0% (93 of 93 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ca/
2022-09-14 21:29:44 +02:00
nicedc
8c0c0f1617 Translated using Weblate (Chinese (Simplified))
Currently translated at 89.2% (83 of 93 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-09-14 21:29:44 +02:00
Ben Phelps
b154314b79 Translated using Weblate (Russian)
Currently translated at 22.5% (21 of 93 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ru/
2022-09-14 21:29:44 +02:00
Nonoss117
70010d09d6 Translated using Weblate (French)
Currently translated at 100.0% (93 of 93 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-14 21:29:44 +02:00
Juan Manuel Bennàssar Carretero
0b24533a13 Translated using Weblate (Spanish)
Currently translated at 100.0% (93 of 93 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-14 21:29:44 +02:00
Ángel Fernández Sánchez
be78d063a4 Translated using Weblate (Spanish)
Currently translated at 100.0% (93 of 93 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-14 21:29:44 +02:00
Ben Phelps
4cee24bd96 Merge pull request #156 from JazzFisch/add-jackett-widget
Add the Jackett widget
2022-09-14 22:29:39 +03:00
Jason Fischer
0a5cdfc57a Refactor setting cookie header into own method 2022-09-14 11:08:36 -07:00
Jason Fischer
5009f9d3f2 Merge branch 'main' into add-jackett-widget 2022-09-14 10:50:53 -07:00
Jason Fischer
f750876425 Add the Jackett widget
- add the follow-redirect package
- add the tough-cookie package

Jackett API uses a redirect mechanism to set a CSRF token.
This CSRF token is stored in a cookie that is required to
be present or the API won't work.
2022-09-14 10:46:52 -07:00
Ben Phelps
680d488647 Update docker-publish.yml 2022-09-14 19:19:22 +03:00
Ben Phelps
81af23ecb5 revert to previous Dockerfile 2022-09-14 19:19:02 +03:00
Ben Phelps
d4b05b2612 experiment with entrypoint for backwards compat 2022-09-14 19:04:19 +03:00
Ben Phelps
5a284bff26 Update docker-publish.yml 2022-09-14 16:08:32 +03:00
Ben Phelps
f1a9191e84 use linuxserver.io base image 2022-09-14 16:04:00 +03:00
Ben Phelps
d876454638 experimental docker user support 2022-09-14 15:04:40 +03:00
Ben Phelps
06de8dd532 update contributions 2022-09-14 14:43:51 +03:00
Anonymous
70592c2438 Translated using Weblate (Catalan)
Currently translated at 100.0% (0 of 0 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ca/
2022-09-14 09:14:51 +02:00
Juan Manuel Bennàssar Carretero
5acaa31a1f Added translation using Weblate (Catalan) 2022-09-14 09:14:45 +02:00
Ben Phelps
79e5ff2fea fix linting 2022-09-14 09:23:21 +03:00
Ben Phelps
7f91fe59e2 allow setting base and favicon 2022-09-14 09:11:55 +03:00
Ben Phelps
b40dad3d3e remove unused package 2022-09-14 09:11:44 +03:00
Anonymous
f9f816845f Translated using Weblate (Chinese (Traditional))
Currently translated at 9.6% (9 of 93 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hant/
2022-09-14 08:10:06 +02:00
Anonymous
193b58d0fc Translated using Weblate (Dutch)
Currently translated at 66.6% (62 of 93 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nl/
2022-09-14 08:10:05 +02:00
Anonymous
b7f490544a Translated using Weblate (Vietnamese)
Currently translated at 35.4% (33 of 93 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/vi/
2022-09-14 08:10:05 +02:00
Anonymous
5d85e3c0e2 Translated using Weblate (Norwegian Bokmål)
Currently translated at 84.9% (79 of 93 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nb_NO/
2022-09-14 08:10:05 +02:00
Anonymous
75214c345a Translated using Weblate (Italian)
Currently translated at 74.1% (69 of 93 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/it/
2022-09-14 08:10:04 +02:00
Anonymous
6d55e74ae4 Translated using Weblate (Chinese (Simplified))
Currently translated at 86.0% (80 of 93 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-09-14 08:10:04 +02:00
Anonymous
710f979f94 Translated using Weblate (Russian)
Currently translated at 13.9% (13 of 93 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ru/
2022-09-14 08:10:04 +02:00
Anonymous
c2a036c526 Translated using Weblate (Portuguese)
Currently translated at 94.6% (88 of 93 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pt/
2022-09-14 08:10:04 +02:00
Anonymous
a6c52df4cb Translated using Weblate (French)
Currently translated at 95.6% (89 of 93 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-14 08:10:03 +02:00
Anonymous
4d1ad16ea2 Translated using Weblate (Spanish)
Currently translated at 95.6% (89 of 93 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-14 08:10:03 +02:00
Anonymous
94c093ea57 Translated using Weblate (German)
Currently translated at 68.8% (64 of 93 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/de/
2022-09-14 08:10:03 +02:00
Francisco Coelho
acd421c617 Translated using Weblate (Portuguese)
Currently translated at 98.8% (88 of 89 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pt/
2022-09-14 08:09:57 +02:00
Nonoss117
4d2004c8c9 Translated using Weblate (French)
Currently translated at 100.0% (89 of 89 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-14 08:09:57 +02:00
Ángel Fernández Sánchez
6c5bfa466f Translated using Weblate (Spanish)
Currently translated at 100.0% (89 of 89 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-14 08:09:57 +02:00
Ben Phelps
794938c525 Merge branch 'ItsJustMeChris-main' 2022-09-14 09:09:39 +03:00
Ben Phelps
62188ffdc7 cleanup 2022-09-14 09:09:11 +03:00
Chris McGravey
34f7bd4341 Merge remote-tracking branch 'origin/main' 2022-09-13 17:29:35 -05:00
Chris McGravey
6b45825472 translate 2022-09-13 17:29:02 -05:00
Ben Phelps
b81a5d1e51 Update common.json 2022-09-14 01:15:06 +03:00
Ben Phelps
5d9e90f033 Update common.json 2022-09-14 01:14:42 +03:00
Ben Phelps
55a3e6880b Update common.json 2022-09-14 01:14:20 +03:00
Ben Phelps
beee9ecd84 Update common.json 2022-09-14 01:14:00 +03:00
Ben Phelps
b94d7a4ae8 Update common.json 2022-09-14 01:13:46 +03:00
Ben Phelps
331999c1a4 Update common.json 2022-09-14 01:13:08 +03:00
Ben Phelps
e5db1ec848 Update common.json 2022-09-14 01:12:50 +03:00
Ben Phelps
17d7161374 Update common.json 2022-09-14 01:12:13 +03:00
Ben Phelps
06d4f2b9f3 Update common.json 2022-09-14 01:11:55 +03:00
Ben Phelps
4b69fdefef Update common.json 2022-09-14 01:11:30 +03:00
Ben Phelps
13db31ede0 Update common.json 2022-09-14 01:11:00 +03:00
Ben Phelps
22a073ba1a Update common.json 2022-09-14 01:10:38 +03:00
Ben Phelps
ce9c115f3d Update common.json 2022-09-14 01:10:11 +03:00
Chris McGravey
767aa9b3e1 Update CoinMarketCap widget to have time selector 2022-09-13 15:35:53 -05:00
Ben Phelps
16ddb2461b Update feature_request.md 2022-09-13 21:27:43 +03:00
Ben Phelps
f75827c4c6 Update bug_report.md 2022-09-13 21:27:05 +03:00
Ben Phelps
cf03e60186 Update bug_report.md 2022-09-13 21:26:20 +03:00
Ben Phelps
8ee071769a Update bug_report.md 2022-09-13 21:25:53 +03:00
Ben Phelps
b312183a7b Update issue templates 2022-09-13 21:22:14 +03:00
Ben Phelps
5baaf5faec Create CONTRIBUTING.md 2022-09-13 21:15:55 +03:00
Ben Phelps
d685bfd11d Merge pull request #148 from benphelps/add-code-of-conduct-1
Create CODE_OF_CONDUCT.md
2022-09-13 20:56:17 +03:00
Ben Phelps
cf4b230b7a Create CODE_OF_CONDUCT.md 2022-09-13 20:56:08 +03:00
Ben Phelps
d46f5f4613 reverse status icons for Tautulli 2022-09-13 20:48:08 +03:00
Ben Phelps
945ed854a4 remove experimental tag again 2022-09-13 12:07:20 +03:00
Anonymous
25f0672c18 Translated using Weblate (Chinese (Traditional))
Currently translated at 10.1% (9 of 89 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hant/
2022-09-13 07:08:13 +02:00
Anonymous
6f6c8b2ae0 Translated using Weblate (Dutch)
Currently translated at 69.6% (62 of 89 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nl/
2022-09-13 07:08:13 +02:00
Anonymous
7e62410f98 Translated using Weblate (Vietnamese)
Currently translated at 37.0% (33 of 89 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/vi/
2022-09-13 07:08:13 +02:00
Anonymous
f49e8486c7 Translated using Weblate (Norwegian Bokmål)
Currently translated at 88.7% (79 of 89 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nb_NO/
2022-09-13 07:08:12 +02:00
Anonymous
844bc23f8c Translated using Weblate (Italian)
Currently translated at 77.5% (69 of 89 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/it/
2022-09-13 07:08:12 +02:00
Anonymous
850226b260 Translated using Weblate (Chinese (Simplified))
Currently translated at 89.8% (80 of 89 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-09-13 07:08:12 +02:00
Anonymous
c5100567d6 Translated using Weblate (Russian)
Currently translated at 14.6% (13 of 89 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ru/
2022-09-13 07:08:12 +02:00
Anonymous
5344854199 Translated using Weblate (Portuguese)
Currently translated at 29.2% (26 of 89 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pt/
2022-09-13 07:08:11 +02:00
Anonymous
d790b17507 Translated using Weblate (French)
Currently translated at 95.5% (85 of 89 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-13 07:08:11 +02:00
Anonymous
667d3851ce Translated using Weblate (Spanish)
Currently translated at 88.7% (79 of 89 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-13 07:08:11 +02:00
Anonymous
ba4d345f4f Translated using Weblate (German)
Currently translated at 71.9% (64 of 89 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/de/
2022-09-13 07:08:10 +02:00
Ben Phelps
52816426fc Merge pull request #141 from JazzFisch/add-transmission-widget
Add transmission widget
2022-09-13 08:08:00 +03:00
nicedc
38a423cf2a Translated using Weblate (Chinese (Simplified))
Currently translated at 94.1% (80 of 85 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-09-13 06:46:00 +02:00
Anonymous
75ad7eb7e4 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (0 of 0 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hant/
2022-09-13 05:41:06 +02:00
Nonoss117
533c3b7b1b Translated using Weblate (French)
Currently translated at 100.0% (85 of 85 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-13 05:41:05 +02:00
Fallenzone
1c98999994 Added translation using Weblate (Chinese (Traditional)) 2022-09-13 05:41:00 +02:00
Jason Fischer
b19b4f047e Fix linting errors 2022-09-12 20:06:00 -07:00
Jason Fischer
95b6ea0e23 Merge main 2022-09-12 19:38:43 -07:00
Jason Fischer
b3db549a65 Add Transmission widget
- Update http.js to support writing request bodies
- Update http.js to support returning all response headers

resolves: #104
2022-09-12 19:35:47 -07:00
Allan Nordhøy
cd768000e9 Translated using Weblate (Norwegian Bokmål)
Currently translated at 92.9% (79 of 85 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nb_NO/
2022-09-13 01:11:43 +02:00
Nonoss117
da6099c29d Translated using Weblate (French)
Currently translated at 98.8% (84 of 85 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-13 01:11:43 +02:00
Ben Phelps
d36e569ede Update README.md 2022-09-12 21:29:19 +03:00
Ben Phelps
eca3757af5 update features and attribution 2022-09-12 21:16:42 +03:00
Anonymous
7d8634ce5e Translated using Weblate (Dutch)
Currently translated at 72.9% (62 of 85 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nl/
2022-09-12 20:14:12 +02:00
Anonymous
d4f6785946 Translated using Weblate (Vietnamese)
Currently translated at 38.8% (33 of 85 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/vi/
2022-09-12 20:14:12 +02:00
Anonymous
b468045039 Translated using Weblate (Norwegian Bokmål)
Currently translated at 75.2% (64 of 85 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nb_NO/
2022-09-12 20:14:12 +02:00
Anonymous
f3b4f21c2e Translated using Weblate (Italian)
Currently translated at 81.1% (69 of 85 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/it/
2022-09-12 20:14:12 +02:00
Anonymous
a50ae64397 Translated using Weblate (Chinese (Simplified))
Currently translated at 92.9% (79 of 85 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-09-12 20:14:11 +02:00
Anonymous
2a5c58e138 Translated using Weblate (Russian)
Currently translated at 15.2% (13 of 85 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ru/
2022-09-12 20:14:11 +02:00
Anonymous
d21945a6e6 Translated using Weblate (Portuguese)
Currently translated at 30.5% (26 of 85 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pt/
2022-09-12 20:14:11 +02:00
Anonymous
0c572cb029 Translated using Weblate (French)
Currently translated at 47.0% (40 of 85 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-12 20:14:11 +02:00
Anonymous
9d30b952ee Translated using Weblate (Spanish)
Currently translated at 92.9% (79 of 85 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-12 20:14:11 +02:00
Anonymous
e32876d08d Translated using Weblate (German)
Currently translated at 75.2% (64 of 85 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/de/
2022-09-12 20:14:10 +02:00
Ben Phelps
340b138962 Add expanded view for resource widgets 2022-09-12 21:13:57 +03:00
Ben Phelps
7ae0ba31cb remove backdrop-blur until it can be made opt-in 2022-09-12 21:13:37 +03:00
Anonymous
f566671975 Translated using Weblate (Dutch)
Currently translated at 73.8% (62 of 84 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nl/
2022-09-12 20:11:38 +02:00
Anonymous
b7ff123e44 Translated using Weblate (Vietnamese)
Currently translated at 39.2% (33 of 84 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/vi/
2022-09-12 20:11:38 +02:00
Anonymous
e1c34bc489 Translated using Weblate (Norwegian Bokmål)
Currently translated at 76.1% (64 of 84 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nb_NO/
2022-09-12 20:11:38 +02:00
Anonymous
dedd341e02 Translated using Weblate (Italian)
Currently translated at 82.1% (69 of 84 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/it/
2022-09-12 20:11:37 +02:00
Anonymous
dc8fc04b57 Translated using Weblate (Chinese (Simplified))
Currently translated at 94.0% (79 of 84 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-09-12 20:11:37 +02:00
Anonymous
6a85859a35 Translated using Weblate (Russian)
Currently translated at 15.4% (13 of 84 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ru/
2022-09-12 20:11:37 +02:00
Anonymous
ff1e8d9e8c Translated using Weblate (Portuguese)
Currently translated at 30.9% (26 of 84 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pt/
2022-09-12 20:11:36 +02:00
Anonymous
16da998452 Translated using Weblate (French)
Currently translated at 47.6% (40 of 84 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-12 20:11:36 +02:00
Anonymous
2fc7c6ab99 Translated using Weblate (Spanish)
Currently translated at 94.0% (79 of 84 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-12 20:11:36 +02:00
Anonymous
834f33e5a5 Translated using Weblate (German)
Currently translated at 76.1% (64 of 84 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/de/
2022-09-12 20:11:36 +02:00
nicedc
90a13a4e83 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (79 of 79 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-09-12 20:11:29 +02:00
Ángel Fernández Sánchez
e4343a4f2f Translated using Weblate (Spanish)
Currently translated at 100.0% (79 of 79 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-12 20:11:29 +02:00
Ben Phelps
7852797bab Merge pull request #134 from xicopitz/main
Add Prowlarr widget
2022-09-12 21:11:25 +03:00
Francisco Coelho
4a93d2ba1e remove prowlarr locales 2022-09-12 18:27:34 +01:00
Francisco Coelho
9287d711dc Update prowlarr.jsx 2022-09-12 18:00:53 +01:00
Francisco Coelho
b5538655e0 Add Prowlarr widget 2022-09-12 17:56:04 +01:00
Ben Phelps
406358aae9 package coinmarketcap logo 2022-09-12 18:19:07 +03:00
Ben Phelps
a5d59e7e45 images/future is no longer experimental 2022-09-12 14:30:57 +03:00
Ben Phelps
92a4ad0c5e update packages 2022-09-12 14:29:55 +03:00
Ben Phelps
d963bcd0c4 Update README.md 2022-09-12 13:45:46 +03:00
Ben Phelps
2e4125c81c Update README.md 2022-09-12 13:34:27 +03:00
Ben Phelps
5293ff3580 reorganize contributions in readme 2022-09-12 13:33:55 +03:00
Anonymous
7a1349df83 Translated using Weblate (Dutch)
Currently translated at 78.4% (62 of 79 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nl/
2022-09-12 11:41:23 +02:00
Anonymous
de8de8f731 Translated using Weblate (Vietnamese)
Currently translated at 41.7% (33 of 79 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/vi/
2022-09-12 11:41:23 +02:00
Anonymous
6d36382436 Translated using Weblate (Norwegian Bokmål)
Currently translated at 81.0% (64 of 79 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nb_NO/
2022-09-12 11:41:23 +02:00
Anonymous
e31833b649 Translated using Weblate (Italian)
Currently translated at 87.3% (69 of 79 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/it/
2022-09-12 11:41:23 +02:00
Anonymous
2dce18563d Translated using Weblate (Chinese (Simplified))
Currently translated at 75.9% (60 of 79 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-09-12 11:41:22 +02:00
Anonymous
aa55c27ab0 Translated using Weblate (Russian)
Currently translated at 16.4% (13 of 79 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ru/
2022-09-12 11:41:22 +02:00
Anonymous
16e321af54 Translated using Weblate (French)
Currently translated at 50.6% (40 of 79 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-12 11:41:22 +02:00
Anonymous
c4edb29ff3 Translated using Weblate (Spanish)
Currently translated at 87.3% (69 of 79 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-12 11:41:21 +02:00
Anonymous
1d5cc05941 Translated using Weblate (German)
Currently translated at 81.0% (64 of 79 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/de/
2022-09-12 11:41:21 +02:00
Ben Phelps
9faae7cb67 allow Weblate to handle missing translations 2022-09-12 12:41:03 +03:00
Ben Phelps
ea06fbe666 js linting 2022-09-12 12:39:04 +03:00
Ben Phelps
cc0b4be50c cleanup coinmarketcap widget 2022-09-12 12:38:50 +03:00
Ben Phelps
ea55cde043 Merge pull request #121 from xicopitz/main
Gotify
2022-09-12 12:36:38 +03:00
Francisco Coelho
840c88db89 Update widget.jsx
duplicated widget name
2022-09-12 10:23:44 +01:00
Francisco Coelho
8e8c9755a3 Merge branch 'benphelps:main' into main 2022-09-12 10:07:06 +01:00
Francisco Coelho
ba3b48e8ce Gotify 2022-09-12 10:06:47 +01:00
Ben Phelps
d3806f7d5b better handle non-clickable service tiles 2022-09-12 11:55:01 +03:00
Francisco Coelho
0c9c1c599f Merge branch 'main' of https://github.com/xicopitz/homepage 2022-09-12 09:37:42 +01:00
Francisco Coelho
af02440c40 Revert "Update credentialed.js"
This reverts commit eeac1200e7.
2022-09-12 09:31:44 +01:00
Ben Phelps
cd53440eff fix search and weather widget spacing 2022-09-12 11:00:15 +03:00
Ben Phelps
3660140539 consolidate api handlers 2022-09-12 10:59:56 +03:00
Anonymous
7bf1bf5369 Translated using Weblate (Dutch)
Currently translated at 81.5% (62 of 76 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nl/
2022-09-12 09:41:25 +02:00
Anonymous
898e30d6de Translated using Weblate (Vietnamese)
Currently translated at 43.4% (33 of 76 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/vi/
2022-09-12 09:41:25 +02:00
Anonymous
a792d213e9 Translated using Weblate (Norwegian Bokmål)
Currently translated at 84.2% (64 of 76 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nb_NO/
2022-09-12 09:41:25 +02:00
Anonymous
ebee953ebc Translated using Weblate (Italian)
Currently translated at 90.7% (69 of 76 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/it/
2022-09-12 09:41:25 +02:00
Anonymous
200ab220e8 Translated using Weblate (Chinese (Simplified))
Currently translated at 78.9% (60 of 76 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-09-12 09:41:24 +02:00
Anonymous
2499d25ce6 Translated using Weblate (Russian)
Currently translated at 17.1% (13 of 76 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ru/
2022-09-12 09:41:24 +02:00
Anonymous
42356166c0 Translated using Weblate (Portuguese)
Currently translated at 30.2% (23 of 76 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pt/
2022-09-12 09:41:24 +02:00
Anonymous
80a31c8427 Translated using Weblate (French)
Currently translated at 52.6% (40 of 76 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-12 09:41:24 +02:00
Anonymous
867c6f9e97 Translated using Weblate (Spanish)
Currently translated at 90.7% (69 of 76 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-12 09:41:24 +02:00
Anonymous
ee9194fce1 Translated using Weblate (German)
Currently translated at 84.2% (64 of 76 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/de/
2022-09-12 09:41:23 +02:00
Ben Phelps
f6322077a4 Merge pull request #118 from ItsJustMeChris/main
Add coin market cap module
2022-09-12 10:41:17 +03:00
Ben Phelps
15a0e6cc54 Merge branch 'main' into main 2022-09-12 10:40:56 +03:00
Anonymous
5ee5adbb1e Translated using Weblate (Dutch)
Currently translated at 82.6% (62 of 75 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nl/
2022-09-12 09:33:05 +02:00
Anonymous
1d4b3eee9b Translated using Weblate (Vietnamese)
Currently translated at 44.0% (33 of 75 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/vi/
2022-09-12 09:33:05 +02:00
Anonymous
fe971d23f8 Translated using Weblate (Norwegian Bokmål)
Currently translated at 85.3% (64 of 75 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nb_NO/
2022-09-12 09:33:04 +02:00
Anonymous
34bf49845f Translated using Weblate (Italian)
Currently translated at 92.0% (69 of 75 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/it/
2022-09-12 09:33:04 +02:00
Anonymous
34468e5bb0 Translated using Weblate (Chinese (Simplified))
Currently translated at 80.0% (60 of 75 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-09-12 09:33:04 +02:00
Anonymous
d0dd52c5c2 Translated using Weblate (Russian)
Currently translated at 17.3% (13 of 75 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ru/
2022-09-12 09:33:04 +02:00
Anonymous
98cca4ca8b Translated using Weblate (Portuguese)
Currently translated at 30.6% (23 of 75 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pt/
2022-09-12 09:33:03 +02:00
Anonymous
b88463a785 Translated using Weblate (French)
Currently translated at 53.3% (40 of 75 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-12 09:33:03 +02:00
Anonymous
6409188de8 Translated using Weblate (Spanish)
Currently translated at 92.0% (69 of 75 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-12 09:33:03 +02:00
Anonymous
510973c761 Translated using Weblate (German)
Currently translated at 85.3% (64 of 75 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/de/
2022-09-12 09:33:02 +02:00
Ben Phelps
4480c26910 allow weblate to apply new strings 2022-09-12 10:32:40 +03:00
Ben Phelps
e778595296 Merge branch 'main' of github.com:benphelps/homepage
# Conflicts:
#	public/locales/it/common.json
2022-09-12 10:32:22 +03:00
Ben Phelps
f84ff7cedc allow weblate to apply new strings 2022-09-12 10:26:03 +03:00
Luca Pellegrino
04f98ae7a9 Translated using Weblate (Italian)
Currently translated at 100.0% (69 of 69 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/it/
2022-09-12 09:23:53 +02:00
Nonoss117
8ef3d7c20e Translated using Weblate (French)
Currently translated at 53.6% (37 of 69 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-12 09:23:53 +02:00
Ángel Fernández Sánchez
428fd6cbba Translated using Weblate (Spanish)
Currently translated at 100.0% (69 of 69 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-12 09:23:53 +02:00
Ben Phelps
ee79335eff Merge pull request #116 from JazzFisch/additional-widgets
Add Readarr and SABnzbd widgets
2022-09-12 10:23:49 +03:00
Ben Phelps
83d7100dd1 normalize media streaming widget padding 2022-09-12 09:41:44 +03:00
Chris McGravey
ccd9049806 Merge branch 'main' of https://github.com/ItsJustMeChris/homepage 2022-09-12 01:38:43 -05:00
Chris McGravey
769f36fa8e - Change block to return configure translation text 2022-09-12 01:38:29 -05:00
Chris
ffe89b02e9 Merge branch 'benphelps:main' into main 2022-09-12 01:32:09 -05:00
Chris McGravey
1c158f743c - Add CoinMarketCap widget 2022-09-12 01:30:42 -05:00
Ben Phelps
4531985032 fix standalone docker widget 2022-09-12 06:18:51 +03:00
Jason Fischer
f8aa1ba391 Add Readarr and SABnzbd widgets 2022-09-11 19:49:18 -07:00
Francisco Coelho
9d790894d5 Sabnzbd Support 2022-09-12 02:22:39 +01:00
Francisco Coelho
eeac1200e7 Update credentialed.js 2022-09-11 22:11:14 +01:00
Francisco Coelho
a304d87b8a Merge branch 'main' of https://github.com/xicopitz/homepage 2022-09-11 22:08:11 +01:00
Ben Phelps
ffbb1f5f0b tweak widget layouts for mobile 2022-09-11 21:02:33 +03:00
Ben Phelps
ad53119088 fix theme selector on mobile 2022-09-11 19:11:58 +03:00
Anonymous
fe1c525fb7 Translated using Weblate (Dutch)
Currently translated at 88.4% (61 of 69 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nl/
2022-09-11 16:24:48 +02:00
Anonymous
323375e8e4 Translated using Weblate (Vietnamese)
Currently translated at 46.3% (32 of 69 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/vi/
2022-09-11 16:24:48 +02:00
Anonymous
8af27ea86d Translated using Weblate (Norwegian Bokmål)
Currently translated at 92.7% (64 of 69 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nb_NO/
2022-09-11 16:24:48 +02:00
Anonymous
9335be0049 Translated using Weblate (Italian)
Currently translated at 13.0% (9 of 69 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/it/
2022-09-11 16:24:47 +02:00
Anonymous
8e0f265080 Translated using Weblate (Chinese (Simplified))
Currently translated at 86.9% (60 of 69 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-09-11 16:24:47 +02:00
Anonymous
ae357159ef Translated using Weblate (Russian)
Currently translated at 14.4% (10 of 69 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ru/
2022-09-11 16:24:47 +02:00
Anonymous
387d40910f Translated using Weblate (Portuguese)
Currently translated at 30.4% (21 of 69 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pt/
2022-09-11 16:24:47 +02:00
Anonymous
5344485a4f Translated using Weblate (French)
Currently translated at 26.0% (18 of 69 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-11 16:24:46 +02:00
Anonymous
21f2f2a215 Translated using Weblate (Spanish)
Currently translated at 92.7% (64 of 69 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-11 16:24:46 +02:00
Anonymous
9586fac665 Translated using Weblate (German)
Currently translated at 92.7% (64 of 69 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/de/
2022-09-11 16:24:46 +02:00
Ben Phelps
4aedda7ba2 add Overseerr widget 2022-09-11 17:24:33 +03:00
Ben Phelps
f79213c9d3 update language attributions 2022-09-11 17:24:12 +03:00
Anonymous
eeddcb26a0 Translated using Weblate (Dutch)
Currently translated at 92.4% (61 of 66 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nl/
2022-09-11 16:02:46 +02:00
Anonymous
725922db78 Translated using Weblate (Vietnamese)
Currently translated at 48.4% (32 of 66 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/vi/
2022-09-11 16:02:46 +02:00
Anonymous
1846dcaba9 Translated using Weblate (Norwegian Bokmål)
Currently translated at 96.9% (64 of 66 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nb_NO/
2022-09-11 16:02:45 +02:00
Anonymous
ab96d88ebe Translated using Weblate (Italian)
Currently translated at 13.6% (9 of 66 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/it/
2022-09-11 16:02:45 +02:00
Anonymous
a3bf28915d Translated using Weblate (Chinese (Simplified))
Currently translated at 90.9% (60 of 66 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-09-11 16:02:45 +02:00
Anonymous
47920a5f7a Translated using Weblate (Russian)
Currently translated at 15.1% (10 of 66 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ru/
2022-09-11 16:02:45 +02:00
Anonymous
1c10903823 Translated using Weblate (Portuguese)
Currently translated at 31.8% (21 of 66 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pt/
2022-09-11 16:02:45 +02:00
Anonymous
96e4133517 Translated using Weblate (French)
Currently translated at 27.2% (18 of 66 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-11 16:02:44 +02:00
Anonymous
8b5167a911 Translated using Weblate (Spanish)
Currently translated at 96.9% (64 of 66 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-11 16:02:44 +02:00
Anonymous
45edab5d88 Translated using Weblate (German)
Currently translated at 96.9% (64 of 66 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/de/
2022-09-11 16:02:44 +02:00
desolaris
b4375fb6fc Translated using Weblate (Russian)
Currently translated at 15.6% (10 of 64 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ru/
2022-09-11 16:02:39 +02:00
Ben Phelps
bd2b28a7ac redesigned media streaming widgets 2022-09-11 17:01:51 +03:00
Ben Phelps
53149df5f1 handle proxy methods other than GET 2022-09-11 14:30:28 +03:00
Ben Phelps
bc2025b3ba handle 204 and 304 proxy responses 2022-09-11 14:30:14 +03:00
Ben Phelps
236450f6f1 add error logging to services fetching 2022-09-11 14:28:29 +03:00
Ben Phelps
fb9e03b31d attempt to fix layout shift on resource widgets 2022-09-11 14:28:12 +03:00
Ben Phelps
31ccb9c933 fix no disk case 2022-09-11 14:21:16 +03:00
Ben Phelps
6e01a743df support array of disks, for disk resource widget 2022-09-11 14:13:58 +03:00
Ben Phelps
ed65c89516 blur backdrops for better background image support 2022-09-11 13:46:01 +03:00
Ben Phelps
b8b8dad9fb Update README.md 2022-09-11 11:35:00 +03:00
Ben Phelps
690d17e132 Merge branch 'main' of github.com:benphelps/homepage 2022-09-11 11:34:00 +03:00
Ben Phelps
a1e9912b36 update readme 2022-09-11 11:33:36 +03:00
deffcolony
26914bfb09 Translated using Weblate (Dutch)
Currently translated at 95.3% (61 of 64 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nl/
2022-09-11 10:23:22 +02:00
J. Lavoie
079fdb3011 Translated using Weblate (French)
Currently translated at 28.1% (18 of 64 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-11 10:23:22 +02:00
Ángel Fernández Sánchez
102cdbd53a Translated using Weblate (Spanish)
Currently translated at 100.0% (64 of 64 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-11 10:23:22 +02:00
Ben Phelps
d861264ecf fix error case cause failure to load anything 2022-09-11 11:13:54 +03:00
Francisco Coelho
9831df1427 Update proxy.js 2022-09-11 04:39:40 +01:00
Francisco Coelho
5e6312fe93 Add Gotify Service 2022-09-11 04:11:02 +01:00
58 changed files with 2933 additions and 687 deletions

30
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View 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

View 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.

View File

@@ -96,7 +96,7 @@ jobs:
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
# https://github.com/docker/setup-qemu-action#about # 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/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-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max

128
CODE_OF_CONDUCT.md Normal file
View 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
View 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)

View File

@@ -5,26 +5,30 @@
## Features ## Features
* Fast! The entire site is statically generated at build time, so you can expect instant load times. - Fast! The entire site is statically generated at build time, so you can expect instant load times
* Full i18n support with automatic language detection. - Images built for AMD64 (x86_64), ARM64, ARMv7 and ARMv6
- Human translations for English, Norwegian Bokmål ([comradekingu](https://github.com/benphelps/homepage/commits?author=comradekingu)) and Spanish ([AmadeusGraves](https://github.com/benphelps/homepage/commits?author=AmadeusGraves)). - Supports all Raspberry Pi's, most SBCs & Apple Silicon
- Machine translations for Portuguese, French, German, Russian and Chinese (simplified). - Full i18n support with automatic language detection
- Want to help translate? [Join the Weblate project](https://hosted.weblate.org/engage/homepage/). - Translations for Chinese, Dutch, French, German, Norwegian Bokmål, Portuguese, Russian and Spanish
* Complete Docker support, including AMD64, ARM64, ARMv7 and ARMv6 support ([schklom](https://github.com/benphelps/homepage/pull/3) and [modem7](https://github.com/benphelps/homepage/pull/62)) - Want to help translate? [Join the Weblate project](https://hosted.weblate.org/engage/homepage/)
* Service & Web Bookmarks - Service & Web Bookmarks
* Docker Integration - Docker Integration
- Status light + CPU, Memory & Network Reporting *(click on the status light)* - Container status (Running / Stopped) & statistics (CPU, Memory, Network)
* Service Integration - Automatic service discovery (via labels)
- Currently supports Sonarr, Radarr, Ombi, Emby, Jellyfin, Tautulli (Plex), Jellyseerr ([ilusi0n](https://github.com/benphelps/homepage/pull/34)), NZBGet, ruTorrent - Service Integration
- Portainer, Traefik, Speedtest Tracker, PiHole, Nginx Proxy Manager ([aidenpwnz](https://github.com/benphelps/homepage/pull/45)) - Sonarr, Radarr, Readarr, Prowlarr, Emby, Jellyfin, Tautulli (Plex)
* Information & Utility Widgets - Ombi, Overseerr, Jellyseerr, NZBGet, SABnzbd, ruTorrent, Transmission
- System Stats (Disk, CPU, Memory) - Portainer, Traefik, Speedtest Tracker, PiHole, Nginx Proxy Manager, Gotify
- Weather via WeatherAPI.com or OpenWeatherMap ([AlexFullmoon](https://github.com/benphelps/homepage/pull/25)) - Information Providers
- Automatic location detection (with HTTPS), or manual location selection - Coin Market Cap
- Search Bar ([aidenpwnz](https://github.com/benphelps/homepage/pull/45)) - Information & Utility Widgets
* Customizable - System Stats (Disk, CPU, Memory)
- 21 theme colors with light and dark mode support - Weather via WeatherAPI.com or OpenWeatherMap
- Background image support - Automatic location detection (with HTTPS), or manual location selection
- Search Bar
- Customizable
- 21 theme colors with light and dark mode support
- Background image support
## Support & Suggestions ## Support & Suggestions
@@ -41,16 +45,17 @@ For configuration options, examples and more, [please check out the Wiki](https:
Using docker compose: Using docker compose:
```yaml ```yaml
version: '3.3' version: "3.3"
services: services:
homepage: homepage:
image: ghcr.io/benphelps/homepage:latest image: ghcr.io/benphelps/homepage:latest
container_name: homepage container_name: homepage
ports: user: 1000:1000 # Optional, change to your user and group IDs for permissions
- 3000:3000 ports:
volumes: - 3000:3000
- /path/to/config:/app/config volumes:
- /var/run/docker.sock:/var/run/docker.sock # (optional) For docker integrations - /path/to/config:/app/config # Make sure your local config directory is exists
- /var/run/docker.sock:/var/run/docker.sock # (optional) For docker integrations
``` ```
or docker run: or docker run:
@@ -74,6 +79,8 @@ pnpm install
pnpm build 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: Finally, run the server:
```bash ```bash
@@ -84,7 +91,7 @@ pnpm start
Configuration files will be genereted and placed on the first request. Configuration files will be genereted and placed on the first request.
Configuration is done in the /config directory using .yaml files. Refer to each config for Configuration is done in the /config directory using .yaml files. Refer to each config for
the specific configuration options. the specific configuration options.
You may also check [the wiki](https://github.com/benphelps/homepage/wiki) for detailed configuration instructions, examples and more. You may also check [the wiki](https://github.com/benphelps/homepage/wiki) for detailed configuration instructions, examples and more.
@@ -106,3 +113,25 @@ pnpm dev
Open [http://localhost:3000](http://localhost:3000) to start. Open [http://localhost:3000](http://localhost:3000) to start.
This is a [Next.js](https://nextjs.org/) application, see their doucmentation for more information: This is a [Next.js](https://nextjs.org/) application, see their doucmentation for more information:
## Contributors
Huge thanks to the all the contributors who have helped make this project what it is today! In alphabetical order:
- [aidenpwnz](https://github.com/benphelps/homepage/commits?author=aidenpwnz) - Nginx Proxy Manager, Search Bar Widget
- [AlexFullmoon](https://github.com/benphelps/homepage/commits?author=AlexFullmoon) - OpenWeatherMap Widget
- [AmadeusGraves](https://github.com/benphelps/homepage/commits?author=AmadeusGraves) - Spanish Translation
- [boerniee](https://github.com/benphelps/homepage/commits?author=boerniee) - German Translation
- [comradekingu](https://github.com/benphelps/homepage/commits?author=comradekingu) - Norwegian Bokmål Translation
- [deffcolony](https://github.com/benphelps/homepage/commits?author=deffcolony) - Dutch Translation
- [desolaris](https://github.com/benphelps/homepage/commits?author=desolaris) - Russian Translation
- [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, Bazarr, Lidarr, 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
- [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 & Prowlarr Integration

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -3,9 +3,9 @@ const nextConfig = {
reactStrictMode: true, reactStrictMode: true,
output: "standalone", output: "standalone",
swcMinify: false, swcMinify: false,
experimental: { images: { allowFutureImage: true, unoptimized: true } },
images: { images: {
domains: ["cdn.jsdelivr.net"], domains: ["cdn.jsdelivr.net"],
unoptimized: true,
}, },
}; };

View File

@@ -6,45 +6,48 @@
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint" "lint": "next lint",
"telemetry": "next telemetry disable"
}, },
"dependencies": { "dependencies": {
"@headlessui/react": "^1.6.6", "@headlessui/react": "^1.7.0",
"@tailwindcss/forms": "^0.5.3", "@tailwindcss/forms": "^0.5.3",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"dockerode": "^3.3.4", "dockerode": "^3.3.4",
"follow-redirects": "^1.15.2",
"i18next": "^21.9.1", "i18next": "^21.9.1",
"i18next-browser-languagedetector": "^6.1.5", "i18next-browser-languagedetector": "^6.1.5",
"i18next-http-backend": "^1.4.1", "i18next-http-backend": "^1.4.1",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"json-rpc-2.0": "^1.4.1", "json-rpc-2.0": "^1.4.1",
"memory-cache": "^0.2.0", "memory-cache": "^0.2.0",
"next": "12.2.5", "next": "^12.3.0",
"node-os-utils": "^1.3.7", "node-os-utils": "^1.3.7",
"pretty-bytes": "^6.0.0", "pretty-bytes": "^6.0.0",
"raw-body": "^2.5.1", "raw-body": "^2.5.1",
"react": "18.2.0", "react": "^18.2.0",
"react-dom": "18.2.0", "react-dom": "^18.2.0",
"react-i18next": "^11.18.5", "react-i18next": "^11.18.6",
"react-icons": "^4.4.0", "react-icons": "^4.4.0",
"rutorrent-promise": "^2.0.0", "rutorrent-promise": "^2.0.0",
"shvl": "^3.0.0", "shvl": "^3.0.0",
"swr": "^1.3.0" "swr": "^1.3.0",
"tough-cookie": "^4.1.2"
}, },
"devDependencies": { "devDependencies": {
"autoprefixer": "^10.4.8", "autoprefixer": "^10.4.9",
"eslint": "8.22.0", "eslint": "^8.23.1",
"eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb": "^19.0.4",
"eslint-config-next": "12.2.5", "eslint-config-next": "^12.3.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.6.1", "eslint-plugin-jsx-a11y": "^6.6.1",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.30.1", "eslint-plugin-react": "^7.31.8",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"postcss": "^8.4.16", "postcss": "^8.4.16",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"tailwindcss": "^3.1.8", "tailwindcss": "^3.1.8",
"typescript": "^4.8.2" "typescript": "^4.8.3"
} }
} }

510
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,149 @@
{
"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"
},
"bazarr": {
"missingEpisodes": "Missing Episodes",
"missingMovies": "Missing Movies"
},
"lidarr": {
"wanted": "Wanted",
"queued": "Queued",
"albums": "Albums"
}
}

View File

@@ -10,7 +10,8 @@
"resources": { "resources": {
"total": "Gesamt", "total": "Gesamt",
"free": "Frei", "free": "Frei",
"used": "Gebraucht" "used": "Gebraucht",
"load": "Load"
}, },
"docker": { "docker": {
"rx": "Rx", "rx": "Rx",
@@ -22,17 +23,14 @@
"emby": { "emby": {
"playing": "Spielen", "playing": "Spielen",
"transcoding": "Transcodierung", "transcoding": "Transcodierung",
"bitrate": "Bitrate" "bitrate": "Bitrate",
"no_active": "No Active Streams"
}, },
"tautulli": { "tautulli": {
"playing": "Spielen", "playing": "Spielen",
"transcoding": "Transcodierung", "transcoding": "Transcodierung",
"bitrate": "Bitrate" "bitrate": "Bitrate",
}, "no_active": "No Active Streams"
"nzbget": {
"rate": "Rate",
"remaining": "Verblieben",
"downloaded": "Heruntergeladen"
}, },
"rutorrent": { "rutorrent": {
"active": "Aktiv", "active": "Aktiv",
@@ -49,6 +47,11 @@
"queued": "In Warteschlange", "queued": "In Warteschlange",
"movies": "Filme" "movies": "Filme"
}, },
"readarr": {
"wanted": "Wanted",
"queued": "Queued",
"books": "Books"
},
"ombi": { "ombi": {
"pending": "Ausstehend", "pending": "Ausstehend",
"approved": "Genehmigt", "approved": "Genehmigt",
@@ -89,5 +92,58 @@
"allow": "Zum Zulassen anklicken", "allow": "Zum Zulassen anklicken",
"updating": "Aktualisieren", "updating": "Aktualisieren",
"wait": "Bitte warten" "wait": "Bitte warten"
},
"overseerr": {
"pending": "Pending",
"approved": "Approved",
"available": "Available"
},
"sabnzbd": {
"rate": "Rate",
"queue": "Queue",
"timeleft": "Time Left"
},
"nzbget": {
"rate": "Rate",
"remaining": "Remaining",
"downloaded": "Downloaded"
},
"coinmarketcap": {
"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"
},
"jackett": {
"configured": "Configured",
"errored": "Errored"
},
"bazarr": {
"missingEpisodes": "Missing Episodes",
"missingMovies": "Missing Movies"
},
"lidarr": {
"wanted": "Wanted",
"queued": "Queued",
"albums": "Albums"
} }
} }

View File

@@ -27,7 +27,8 @@
"resources": { "resources": {
"total": "Total", "total": "Total",
"free": "Free", "free": "Free",
"used": "Used" "used": "Used",
"load": "Load"
}, },
"docker": { "docker": {
"rx": "RX", "rx": "RX",
@@ -39,23 +40,36 @@
"emby": { "emby": {
"playing": "Playing", "playing": "Playing",
"transcoding": "Transcoding", "transcoding": "Transcoding",
"bitrate": "Bitrate" "bitrate": "Bitrate",
"no_active": "No Active Streams"
}, },
"tautulli": { "tautulli": {
"playing": "Playing", "playing": "Playing",
"transcoding": "Transcoding", "transcoding": "Transcoding",
"bitrate": "Bitrate" "bitrate": "Bitrate",
"no_active": "No Active Streams"
}, },
"nzbget": { "nzbget": {
"rate": "Rate", "rate": "Rate",
"remaining": "Remaining", "remaining": "Remaining",
"downloaded": "Downloaded" "downloaded": "Downloaded"
}, },
"sabnzbd": {
"rate": "Rate",
"queue": "Queue",
"timeleft": "Time Left"
},
"rutorrent": { "rutorrent": {
"active": "Active", "active": "Active",
"upload": "Upload", "upload": "Upload",
"download": "Download" "download": "Download"
}, },
"transmission": {
"download": "Download",
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
},
"sonarr": { "sonarr": {
"wanted": "Wanted", "wanted": "Wanted",
"queued": "Queued", "queued": "Queued",
@@ -66,6 +80,20 @@
"queued": "Queued", "queued": "Queued",
"movies": "Movies" "movies": "Movies"
}, },
"lidarr": {
"wanted": "Wanted",
"queued": "Queued",
"albums": "Albums"
},
"readarr": {
"wanted": "Wanted",
"queued": "Queued",
"books": "Books"
},
"bazarr": {
"missingEpisodes": "Missing Episodes",
"missingMovies": "Missing Movies"
},
"ombi": { "ombi": {
"pending": "Pending", "pending": "Pending",
"approved": "Approved", "approved": "Approved",
@@ -76,6 +104,11 @@
"approved": "Approved", "approved": "Approved",
"available": "Available" "available": "Available"
}, },
"overseerr": {
"pending": "Pending",
"approved": "Approved",
"available": "Available"
},
"pihole": { "pihole": {
"queries": "Queries", "queries": "Queries",
"blocked": "Blocked", "blocked": "Blocked",
@@ -100,5 +133,28 @@
"enabled": "Enabled", "enabled": "Enabled",
"disabled": "Disabled", "disabled": "Disabled",
"total": "Total" "total": "Total"
},
"coinmarketcap": {
"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"
},
"jackett": {
"configured": "Configured",
"errored": "Errored"
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"widget": { "widget": {
"missing_type": "Tipo de widget faltante: {{type}}", "missing_type": "Falta el tipo de widget: {{type}}",
"api_error": "Error de API", "api_error": "Error de API",
"status": "Estado" "status": "Estado"
}, },
@@ -10,11 +10,12 @@
"resources": { "resources": {
"total": "Total", "total": "Total",
"free": "Libre", "free": "Libre",
"used": "Usado" "used": "Usado",
"load": "Carga"
}, },
"docker": { "docker": {
"rx": "RX", "rx": "Recibido",
"tx": "TX", "tx": "Transmitido",
"mem": "Memoria", "mem": "Memoria",
"cpu": "Procesador", "cpu": "Procesador",
"offline": "Desconectado" "offline": "Desconectado"
@@ -22,22 +23,19 @@
"emby": { "emby": {
"playing": "En ejecución", "playing": "En ejecución",
"transcoding": "Transcodificando", "transcoding": "Transcodificando",
"bitrate": "Tasa de Bits" "bitrate": "Tasa de Bits",
"no_active": "No hay streams activos"
}, },
"tautulli": { "tautulli": {
"playing": "En ejecución", "playing": "En ejecución",
"transcoding": "Transcodificación", "transcoding": "Transcodificación",
"bitrate": "Tasa de bits" "bitrate": "Tasa de bits",
}, "no_active": "No hay streams activos"
"nzbget": {
"rate": "Velocidad",
"remaining": "Restante",
"downloaded": "Descargado"
}, },
"rutorrent": { "rutorrent": {
"active": "Activo", "active": "Activo",
"upload": "Subir", "upload": "Subida",
"download": "Descargar" "download": "Descarga"
}, },
"sonarr": { "sonarr": {
"wanted": "Más deseado", "wanted": "Más deseado",
@@ -49,6 +47,11 @@
"queued": "Puesto en cola", "queued": "Puesto en cola",
"movies": "Películas" "movies": "Películas"
}, },
"readarr": {
"wanted": "Más deseado",
"queued": "Puesto en cola",
"books": "Libros"
},
"ombi": { "ombi": {
"pending": "Pendiente", "pending": "Pendiente",
"approved": "Aprobado", "approved": "Aprobado",
@@ -66,7 +69,7 @@
}, },
"speedtest": { "speedtest": {
"upload": "Subir", "upload": "Subir",
"download": "Descargar", "download": "Descarga",
"ping": "Ping" "ping": "Ping"
}, },
"portainer": { "portainer": {
@@ -85,9 +88,62 @@
"total": "Total" "total": "Total"
}, },
"weather": { "weather": {
"current": "Current Location", "current": "Ubicación Actual",
"allow": "Click to allow", "allow": "Haga clic para permitir",
"updating": "Updating", "updating": "Actualizando",
"wait": "Please wait" "wait": "Espere, por favor"
},
"overseerr": {
"pending": "Pendiente",
"approved": "Aprobado",
"available": "Disponible"
},
"sabnzbd": {
"rate": "Tasa de descarga",
"queue": "Puesto en cola",
"timeleft": "Tiempo Restante"
},
"nzbget": {
"rate": "Tasa de descarga",
"remaining": "Restante",
"downloaded": "Descargado"
},
"coinmarketcap": {
"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",
"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"
},
"jackett": {
"configured": "Configured",
"errored": "Errored"
},
"bazarr": {
"missingEpisodes": "Missing Episodes",
"missingMovies": "Missing Movies"
},
"lidarr": {
"queued": "Queued",
"wanted": "Wanted",
"albums": "Albums"
} }
} }

View File

@@ -5,84 +5,87 @@
"status": "Statut" "status": "Statut"
}, },
"search": { "search": {
"placeholder": "Chercher…" "placeholder": "Recherche…"
}, },
"resources": { "resources": {
"total": "Totale", "total": "Total",
"free": "Libre", "free": "Libre",
"used": "Utilisée" "used": "Utilisé",
"load": "Charge"
}, },
"docker": { "docker": {
"rx": "Rx", "rx": "Rx",
"tx": "TX", "tx": "Tx",
"mem": "Mem", "mem": "Mém",
"cpu": "CPU", "cpu": "Cpu",
"offline": "Hors ligne" "offline": "Hors ligne"
}, },
"emby": { "emby": {
"playing": "En jouant", "playing": "En lecture",
"transcoding": "Transcoding", "transcoding": "Transcodage",
"bitrate": "Débiter" "bitrate": "Débit",
"no_active": "Aucun flux actif"
}, },
"tautulli": { "tautulli": {
"playing": "En jouant", "playing": "En lecture",
"transcoding": "Transcoding", "transcoding": "Transcodage",
"bitrate": "Débiter" "bitrate": "Débit",
}, "no_active": "Aucun flux actif"
"nzbget": {
"rate": "Évaluer",
"remaining": "Restante",
"downloaded": "Téléchargé"
}, },
"rutorrent": { "rutorrent": {
"active": "Active", "active": "Actif",
"upload": "Télécharger", "upload": "Téléverser",
"download": "Télécharger" "download": "Télécharger"
}, },
"sonarr": { "sonarr": {
"wanted": "Recherchée", "wanted": "Demandé",
"queued": "En queue", "queued": "En queue",
"series": "Série" "series": "Séries"
}, },
"radarr": { "radarr": {
"wanted": "Recherchée", "wanted": "Demandé",
"queued": "En queue", "queued": "En queue",
"movies": "Films" "movies": "Films"
}, },
"readarr": {
"wanted": "Demandé",
"queued": "En Queue",
"books": "Livres"
},
"ombi": { "ombi": {
"pending": "En attente", "pending": "En attente",
"approved": "Approuvée", "approved": "Validé",
"available": "Disponible" "available": "Disponible"
}, },
"jellyseerr": { "jellyseerr": {
"pending": "En attente", "pending": "En attente",
"approved": "Approuvée", "approved": "Validé",
"available": "Disponible" "available": "Disponible"
}, },
"pihole": { "pihole": {
"queries": "Requêtes", "queries": "Requêtes",
"blocked": "Bloquée", "blocked": "Bloqué",
"gravity": "La gravité" "gravity": "Listes dom. bloqués"
}, },
"speedtest": { "speedtest": {
"upload": "Télécharger", "upload": "Téléversement",
"download": "Télécharger", "download": "Téléchargement",
"ping": "Ping-ping" "ping": "Ping"
}, },
"portainer": { "portainer": {
"running": "Fonctionnement", "running": "Démarré",
"stopped": "Arrêté", "stopped": "Arrêté",
"total": "Totale" "total": "Total"
}, },
"traefik": { "traefik": {
"routers": "Routeurs", "routers": "Routeurs",
"services": "Prestations de service", "services": "Services",
"middleware": "Middleware" "middleware": "Middleware"
}, },
"npm": { "npm": {
"enabled": "Activé", "enabled": "Activé",
"disabled": "Handicapée", "disabled": "Désactivé",
"total": "Totale" "total": "Total"
}, },
"common": { "common": {
"bbytes": "{{value, bytes(binary: true)}}", "bbytes": "{{value, bytes(binary: true)}}",
@@ -96,9 +99,62 @@
"ms": "{{value, number}}" "ms": "{{value, number}}"
}, },
"weather": { "weather": {
"current": "Current Location", "current": "Localisation actuelle",
"allow": "Click to allow", "allow": "Cliquez pour autoriser",
"updating": "Updating", "updating": "Mise à jour",
"wait": "Please wait" "wait": "Veuillez patienter"
},
"overseerr": {
"pending": "En attente",
"approved": "Validé",
"available": "Disponible"
},
"sabnzbd": {
"rate": "Débit",
"queue": "Queue",
"timeleft": "Temps restant"
},
"nzbget": {
"remaining": "Restant",
"downloaded": "Téléchargé",
"rate": "Débit"
},
"coinmarketcap": {
"configure": "Configurer une ou plusieurs crypto-monnaies à suivre",
"1hour": "1 Heure",
"1day": "1 Jour",
"7days": "7 Jours",
"30days": "30 Jours"
},
"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"
},
"jackett": {
"configured": "Configuré",
"errored": "En erreur"
},
"bazarr": {
"missingEpisodes": "Missing Episodes",
"missingMovies": "Missing Movies"
},
"lidarr": {
"wanted": "Wanted",
"queued": "Queued",
"albums": "Albums"
} }
} }

View File

@@ -7,14 +7,16 @@
"rx": "RX" "rx": "RX"
}, },
"emby": { "emby": {
"playing": "Playing", "playing": "In riproduzione",
"transcoding": "Transcoding", "transcoding": "Transcoding",
"bitrate": "Bitrate" "bitrate": "Bitrate",
"no_active": "Nessuno Stream Attivo"
}, },
"tautulli": { "tautulli": {
"playing": "Playing", "playing": "In riproduzione",
"transcoding": "Transcoding", "transcoding": "Transcoding",
"bitrate": "Bitrate" "bitrate": "Bitrate",
"no_active": "Nessuno Stream Attivo"
}, },
"speedtest": { "speedtest": {
"upload": "Upload", "upload": "Upload",
@@ -22,72 +24,126 @@
"ping": "Ping" "ping": "Ping"
}, },
"portainer": { "portainer": {
"running": "Running", "running": "In esecuzione",
"stopped": "Stopped", "stopped": "Fermati",
"total": "Total" "total": "Totali"
}, },
"traefik": { "traefik": {
"routers": "Routers", "routers": "Routers",
"services": "Services", "services": "Servizi",
"middleware": "Middleware" "middleware": "Middleware"
}, },
"widget": { "widget": {
"missing_type": "Missing Widget Type: {{type}}", "missing_type": "Missing Widget Type: {{type}}",
"api_error": "API Error", "api_error": "Errore API",
"status": "Status" "status": "Stato"
}, },
"search": { "search": {
"placeholder": "Search…" "placeholder": "Cerca…"
}, },
"resources": { "resources": {
"total": "Total", "total": "Totale",
"free": "Free", "free": "Libero",
"used": "Used" "used": "In utilizzo",
"load": "Load"
},
"rutorrent": {
"active": "Attivo",
"upload": "Upload",
"download": "Download"
},
"sonarr": {
"series": "Serie",
"wanted": "Rchiesti",
"queued": "In coda"
},
"radarr": {
"wanted": "Richiesti",
"queued": "In coda",
"movies": "Film"
},
"readarr": {
"wanted": "Wanted",
"queued": "Queued",
"books": "Books"
},
"ombi": {
"pending": "In attesa",
"approved": "Approvati",
"available": "Disponibili"
},
"jellyseerr": {
"pending": "In attesa",
"approved": "Approvati",
"available": "Disponibili"
},
"pihole": {
"queries": "Richieste",
"blocked": "Bloccati",
"gravity": "Severità"
},
"npm": {
"enabled": "Attivi",
"disabled": "Disabilitati",
"total": "Totali"
},
"weather": {
"current": "Posizione Attuale",
"allow": "Clicca per consentire",
"updating": "Aggiornamento in corso",
"wait": "Attendi per favore"
},
"overseerr": {
"pending": "In attesa",
"approved": "Approvati",
"available": "Disponibili"
},
"sabnzbd": {
"rate": "Rate",
"queue": "Queue",
"timeleft": "Time Left"
}, },
"nzbget": { "nzbget": {
"rate": "Rate", "rate": "Rate",
"remaining": "Remaining", "remaining": "Remaining",
"downloaded": "Downloaded" "downloaded": "Downloaded"
}, },
"rutorrent": { "coinmarketcap": {
"active": "Active", "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", "upload": "Upload",
"download": "Download" "leech": "Leech",
"seed": "Seed"
}, },
"sonarr": { "jackett": {
"series": "Series", "configured": "Configured",
"wanted": "Wanted", "errored": "Errored"
"queued": "Queued"
}, },
"radarr": { "bazarr": {
"missingEpisodes": "Missing Episodes",
"missingMovies": "Missing Movies"
},
"lidarr": {
"wanted": "Wanted", "wanted": "Wanted",
"queued": "Queued", "queued": "Queued",
"movies": "Movies" "albums": "Albums"
},
"ombi": {
"pending": "Pending",
"approved": "Approved",
"available": "Available"
},
"jellyseerr": {
"pending": "Pending",
"approved": "Approved",
"available": "Available"
},
"pihole": {
"queries": "Queries",
"blocked": "Blocked",
"gravity": "Gravity"
},
"npm": {
"enabled": "Enabled",
"disabled": "Disabled",
"total": "Total"
},
"weather": {
"current": "Current Location",
"allow": "Click to allow",
"updating": "Updating",
"wait": "Please wait"
} }
} }

View File

@@ -10,7 +10,8 @@
"resources": { "resources": {
"total": "Totalt", "total": "Totalt",
"free": "Ledig", "free": "Ledig",
"used": "Brukt" "used": "Brukt",
"load": "Last inn"
}, },
"docker": { "docker": {
"rx": "Mottatt", "rx": "Mottatt",
@@ -22,17 +23,14 @@
"emby": { "emby": {
"playing": "Spiller", "playing": "Spiller",
"transcoding": "Transkoding", "transcoding": "Transkoding",
"bitrate": "Bitrate" "bitrate": "Bitrate",
"no_active": "Ingen aktive strømmer"
}, },
"tautulli": { "tautulli": {
"playing": "Spiller", "playing": "Spiller",
"transcoding": "Transkoding", "transcoding": "Transkoding",
"bitrate": "Bitrate" "bitrate": "Bitrate",
}, "no_active": "Ingen aktive strømmer"
"nzbget": {
"rate": "Takt",
"remaining": "Gjenstående",
"downloaded": "Nedlastet"
}, },
"rutorrent": { "rutorrent": {
"active": "Aktiv", "active": "Aktiv",
@@ -49,6 +47,11 @@
"queued": "I kø", "queued": "I kø",
"movies": "Filmer" "movies": "Filmer"
}, },
"readarr": {
"wanted": "Wanted",
"queued": "Queued",
"books": "Books"
},
"ombi": { "ombi": {
"pending": "Venter", "pending": "Venter",
"approved": "Godkjent", "approved": "Godkjent",
@@ -89,5 +92,58 @@
"updating": "Oppdaterer …", "updating": "Oppdaterer …",
"wait": "Vent litt …", "wait": "Vent litt …",
"current": "Nåværende posisjon" "current": "Nåværende posisjon"
},
"overseerr": {
"pending": "Venter",
"approved": "Godkjent",
"available": "Tilgjengelig"
},
"sabnzbd": {
"rate": "Takt",
"queue": "Kø",
"timeleft": "Gjenstående tid"
},
"nzbget": {
"rate": "Takt",
"downloaded": "Nedlastet",
"remaining": "Gjenstående"
},
"coinmarketcap": {
"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",
"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"
},
"jackett": {
"configured": "Configured",
"errored": "Errored"
},
"bazarr": {
"missingEpisodes": "Missing Episodes",
"missingMovies": "Missing Movies"
},
"lidarr": {
"wanted": "Wanted",
"queued": "Queued",
"albums": "Albums"
} }
} }

View File

@@ -5,9 +5,10 @@
"status": "Status" "status": "Status"
}, },
"resources": { "resources": {
"total": "Total", "total": "Totaal",
"free": "Free", "free": "Vrij",
"used": "Used" "used": "Gebruikt",
"load": "Load"
}, },
"docker": { "docker": {
"rx": "RX", "rx": "RX",
@@ -16,68 +17,70 @@
"cpu": "CPU", "cpu": "CPU",
"offline": "Offline" "offline": "Offline"
}, },
"nzbget": {
"rate": "Rate",
"remaining": "Remaining",
"downloaded": "Downloaded"
},
"speedtest": { "speedtest": {
"upload": "Upload", "upload": "Upload",
"download": "Download", "download": "Download",
"ping": "Ping" "ping": "Ping"
}, },
"portainer": { "portainer": {
"running": "Running", "running": "Draaiend",
"stopped": "Stopped", "stopped": "Gestopt",
"total": "Total" "total": "Totaal"
}, },
"weather": { "weather": {
"updating": "Updating", "updating": "Updaten",
"wait": "Please wait", "wait": "Even geduld",
"current": "Current Location", "current": "Huidige Locatie",
"allow": "Click to allow" "allow": "Klik om toe te staan"
}, },
"search": { "search": {
"placeholder": "Search…" "placeholder": "Zoeken…"
}, },
"emby": { "emby": {
"playing": "Playing", "playing": "Afspelen",
"transcoding": "Transcoding", "transcoding": "Transcodering",
"bitrate": "Bitrate" "bitrate": "Bitsnelheid",
"no_active": "No Active Streams"
}, },
"tautulli": { "tautulli": {
"playing": "Playing", "playing": "Afspelen",
"transcoding": "Transcoding", "transcoding": "Transcodering",
"bitrate": "Bitrate" "bitrate": "Bitsnelheid",
"no_active": "No Active Streams"
}, },
"rutorrent": { "rutorrent": {
"active": "Active", "active": "Actief",
"upload": "Upload", "upload": "Upload",
"download": "Download" "download": "Download"
}, },
"sonarr": { "sonarr": {
"wanted": "Wanted", "wanted": "Gezocht",
"queued": "Queued", "queued": "In de wachtrij",
"series": "Series" "series": "Series"
}, },
"radarr": { "radarr": {
"movies": "Movies", "movies": "Films",
"wanted": "Gezocht",
"queued": "In de wachtrij"
},
"readarr": {
"wanted": "Wanted", "wanted": "Wanted",
"queued": "Queued" "queued": "Queued",
"books": "Books"
}, },
"ombi": { "ombi": {
"pending": "Pending", "pending": "In afwachting",
"approved": "Approved", "approved": "Goedgekeurd",
"available": "Available" "available": "Beschikbaar"
}, },
"jellyseerr": { "jellyseerr": {
"pending": "Pending", "pending": "In afwachting",
"approved": "Approved", "approved": "Goedgekeurd",
"available": "Available" "available": "Beschikbaar"
}, },
"pihole": { "pihole": {
"queries": "Queries", "queries": "Queries",
"blocked": "Blocked", "blocked": "Geblokkeerd",
"gravity": "Gravity" "gravity": "Gravity"
}, },
"traefik": { "traefik": {
@@ -86,8 +89,61 @@
"middleware": "Middleware" "middleware": "Middleware"
}, },
"npm": { "npm": {
"enabled": "Enabled", "enabled": "Ingeschakeld",
"disabled": "Disabled", "disabled": "Uitgeschakeld",
"total": "Total" "total": "Totaal"
},
"overseerr": {
"pending": "Pending",
"approved": "Approved",
"available": "Available"
},
"sabnzbd": {
"rate": "Rate",
"queue": "Queue",
"timeleft": "Time Left"
},
"nzbget": {
"rate": "Rate",
"remaining": "Remaining",
"downloaded": "Downloaded"
},
"coinmarketcap": {
"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"
},
"jackett": {
"configured": "Configured",
"errored": "Errored"
},
"bazarr": {
"missingEpisodes": "Missing Episodes",
"missingMovies": "Missing Movies"
},
"lidarr": {
"wanted": "Wanted",
"queued": "Queued",
"albums": "Albums"
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"widget": { "widget": {
"missing_type": "Tipo de widget ausente: {{type}}", "missing_type": "Widget ausente: {{type}}",
"api_error": "Erro da API", "api_error": "Erro da API",
"status": "Status" "status": "Status"
}, },
@@ -10,32 +10,30 @@
"resources": { "resources": {
"total": "Total", "total": "Total",
"free": "Livre", "free": "Livre",
"used": "Usada" "used": "Usado",
"load": "Load"
}, },
"docker": { "docker": {
"rx": "Rx", "rx": "Rx",
"tx": "Tx", "tx": "Tx",
"mem": "Mem", "mem": "Mem",
"cpu": "CPU", "cpu": "CPU",
"offline": "Desligada" "offline": "Desligado"
}, },
"emby": { "emby": {
"playing": "A reproduzir", "playing": "A reproduzir",
"transcoding": "Transcodificação", "transcoding": "Transcodificação",
"bitrate": "Taxa de bits" "bitrate": "Bitrate",
"no_active": "Sem streams ativas"
}, },
"tautulli": { "tautulli": {
"playing": "Reproduzindo", "playing": "Reproduzindo",
"transcoding": "Transcodificação", "transcoding": "Transcodificação",
"bitrate": "Taxa de bits" "bitrate": "Taxa de bits",
}, "no_active": "Sem streams ativas"
"nzbget": {
"rate": "Avaliar",
"remaining": "Em falta",
"downloaded": "Baixada"
}, },
"rutorrent": { "rutorrent": {
"active": "Ativa", "active": "Ativo",
"upload": "Envio", "upload": "Envio",
"download": "ReceçãoDownload" "download": "ReceçãoDownload"
}, },
@@ -46,9 +44,14 @@
}, },
"radarr": { "radarr": {
"wanted": "Desejado", "wanted": "Desejado",
"queued": "Enfileiradas", "queued": "Fila",
"movies": "Filmes" "movies": "Filmes"
}, },
"readarr": {
"wanted": "Wanted",
"queued": "Em fila",
"books": "Livros"
},
"ombi": { "ombi": {
"pending": "Pendente", "pending": "Pendente",
"approved": "Aprovada", "approved": "Aprovada",
@@ -62,7 +65,7 @@
"pihole": { "pihole": {
"queries": "Consultas", "queries": "Consultas",
"blocked": "Bloqueado", "blocked": "Bloqueado",
"gravity": "Gravidade" "gravity": "Gravity"
}, },
"speedtest": { "speedtest": {
"upload": "Envio", "upload": "Envio",
@@ -70,18 +73,18 @@
"ping": "Ping" "ping": "Ping"
}, },
"portainer": { "portainer": {
"running": "Corrida", "running": "A correr",
"stopped": "Parou", "stopped": "Parado",
"total": "Total" "total": "Total"
}, },
"traefik": { "traefik": {
"routers": "Roteadores", "routers": "Routers",
"services": "Serviços", "services": "Serviços",
"middleware": "Middleware" "middleware": "Middleware"
}, },
"npm": { "npm": {
"enabled": "Habilitada", "enabled": "Ativo",
"disabled": "Desabilitada", "disabled": "Desabilitado",
"total": "Total" "total": "Total"
}, },
"common": { "common": {
@@ -100,5 +103,58 @@
"allow": "Clicar para permitir", "allow": "Clicar para permitir",
"updating": "A atualizar", "updating": "A atualizar",
"wait": "Por favor aguarde" "wait": "Por favor aguarde"
},
"overseerr": {
"pending": "Pendente",
"approved": "Aprovado",
"available": "Disponível"
},
"sabnzbd": {
"rate": "Rate",
"queue": "Fila",
"timeleft": "Tempo restante"
},
"nzbget": {
"rate": "Rate",
"remaining": "Restante",
"downloaded": "Downloaded"
},
"coinmarketcap": {
"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"
},
"jackett": {
"configured": "Configured",
"errored": "Errored"
},
"bazarr": {
"missingEpisodes": "Missing Episodes",
"missingMovies": "Missing Movies"
},
"lidarr": {
"queued": "Queued",
"wanted": "Wanted",
"albums": "Albums"
} }
} }

View File

@@ -8,31 +8,29 @@
"placeholder": "Поиск…" "placeholder": "Поиск…"
}, },
"resources": { "resources": {
"total": "Общий", "total": "Всего",
"free": "Свободно", "free": "Свободно",
"used": "Использовал" "used": "Использовано",
"load": "Load"
}, },
"docker": { "docker": {
"rx": "Rx", "rx": "Rx",
"tx": "Техас", "tx": "Тx",
"mem": "Мем", "mem": "Память",
"cpu": "Процессор", "cpu": "Процессор",
"offline": "Не в сети" "offline": "Не в сети"
}, },
"emby": { "emby": {
"playing": "Игра", "playing": "Воспроизведение",
"transcoding": "Транскодирование", "transcoding": "Транскодирование",
"bitrate": "Битрейт" "bitrate": "Битрейт",
"no_active": "No Active Streams"
}, },
"tautulli": { "tautulli": {
"playing": "Игра", "playing": "Воспроизведение",
"transcoding": "Транскодирование", "transcoding": "Транскодирование",
"bitrate": "Битрейт" "bitrate": "Битрейт",
}, "no_active": "No Active Streams"
"nzbget": {
"rate": "Оценивать",
"remaining": "Оставшийся",
"downloaded": "Загружен"
}, },
"rutorrent": { "rutorrent": {
"active": "Активный", "active": "Активный",
@@ -49,45 +47,103 @@
"queued": "В очереди", "queued": "В очереди",
"movies": "Фильмы" "movies": "Фильмы"
}, },
"readarr": {
"wanted": "Wanted",
"queued": "Queued",
"books": "Books"
},
"ombi": { "ombi": {
"pending": "В ожидании", "pending": "Ожидание",
"approved": "Одобренный", "approved": "Одобрено",
"available": "Доступный" "available": "Доступно"
}, },
"jellyseerr": { "jellyseerr": {
"pending": "В ожидании", "pending": "Ожидание",
"approved": "Одобренный", "approved": "Одобрено",
"available": "Доступный" "available": "Доступно"
}, },
"pihole": { "pihole": {
"queries": "Запросы", "queries": "Запросы",
"blocked": "Заблокированный", "blocked": "Заблокировано",
"gravity": "Сила тяжести" "gravity": "Сила тяжести"
}, },
"speedtest": { "speedtest": {
"upload": "Загрузить", "upload": "Загрузка",
"download": "Скачать", "download": "Скачать",
"ping": "пинг" "ping": "пинг"
}, },
"portainer": { "portainer": {
"running": "Бег", "running": "Запущено",
"stopped": "Остановился", "stopped": "Остановлено",
"total": "Общий" "total": "Всего"
}, },
"traefik": { "traefik": {
"routers": "Маршрутизаторы", "routers": "Маршрутизаторы",
"services": "Услуги", "services": "Сервисы",
"middleware": "Промежуточное программное обеспечение" "middleware": "Промежуточное программное обеспечение"
}, },
"npm": { "npm": {
"enabled": "Включено", "enabled": "Включено",
"disabled": "Неполноценный", "disabled": "Отключено",
"total": "Общий" "total": "Всего"
}, },
"weather": { "weather": {
"wait": "Please wait", "wait": "Пожалуйста подождите",
"current": "Current Location", "current": "Текущая локация",
"allow": "Click to allow", "allow": "Нажмите, чтобы разрешить",
"updating": "Updating" "updating": "Обновление"
},
"overseerr": {
"pending": "Pending",
"approved": "Approved",
"available": "Available"
},
"sabnzbd": {
"rate": "Rate",
"queue": "Queue",
"timeleft": "Time Left"
},
"nzbget": {
"rate": "Rate",
"remaining": "Remaining",
"downloaded": "Downloaded"
},
"coinmarketcap": {
"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"
},
"jackett": {
"configured": "Configured",
"errored": "Errored"
},
"bazarr": {
"missingEpisodes": "Missing Episodes",
"missingMovies": "Missing Movies"
},
"lidarr": {
"wanted": "Wanted",
"queued": "Queued",
"albums": "Albums"
} }
} }

View File

@@ -10,7 +10,8 @@
"resources": { "resources": {
"total": "Tổng", "total": "Tổng",
"free": "Dư", "free": "Dư",
"used": "Đã dùng" "used": "Đã dùng",
"load": "Load"
}, },
"docker": { "docker": {
"rx": "RX", "rx": "RX",
@@ -22,17 +23,14 @@
"emby": { "emby": {
"playing": "Đang chơi", "playing": "Đang chơi",
"transcoding": "Chuyển định dạng", "transcoding": "Chuyển định dạng",
"bitrate": "Bitrate" "bitrate": "Bitrate",
"no_active": "No Active Streams"
}, },
"tautulli": { "tautulli": {
"playing": "Đang chơi", "playing": "Đang chơi",
"transcoding": "Chuyển định dạng", "transcoding": "Chuyển định dạng",
"bitrate": "Bitrate" "bitrate": "Bitrate",
}, "no_active": "No Active Streams"
"nzbget": {
"rate": "Rate",
"remaining": "Còn lại",
"downloaded": "Đã tải"
}, },
"rutorrent": { "rutorrent": {
"active": "Hoạt động", "active": "Hoạt động",
@@ -47,11 +45,16 @@
"radarr": { "radarr": {
"wanted": "Wanted", "wanted": "Wanted",
"queued": "Queued", "queued": "Queued",
"movies": "Movies" "movies": "Phim"
},
"readarr": {
"wanted": "Đang tìm",
"queued": "Đang chờ",
"books": "Sách"
}, },
"ombi": { "ombi": {
"pending": "Pending", "pending": "Đang xử lý",
"approved": "Approved", "approved": "Đã duyệt",
"available": "Available" "available": "Available"
}, },
"jellyseerr": { "jellyseerr": {
@@ -85,9 +88,62 @@
"total": "Total" "total": "Total"
}, },
"weather": { "weather": {
"current": "Current Location", "current": "Vị trí hiện tại",
"allow": "Click to allow", "allow": "Bấm để đồng ý",
"updating": "Updating", "updating": "Đang cập nhật",
"wait": "Please wait" "wait": "Vui lòng chờ"
},
"overseerr": {
"pending": "Pending",
"approved": "Đã duyệt",
"available": "Available"
},
"sabnzbd": {
"rate": "Rate",
"queue": "Hàng chờ",
"timeleft": "Thời gian còn lại"
},
"nzbget": {
"rate": "Rate",
"remaining": "Remaining",
"downloaded": "Đã tải"
},
"coinmarketcap": {
"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"
},
"jackett": {
"configured": "Configured",
"errored": "Errored"
},
"bazarr": {
"missingEpisodes": "Missing Episodes",
"missingMovies": "Missing Movies"
},
"lidarr": {
"wanted": "Wanted",
"queued": "Queued",
"albums": "Albums"
} }
} }

View File

@@ -8,34 +8,32 @@
"placeholder": "搜索…" "placeholder": "搜索…"
}, },
"resources": { "resources": {
"total": "全部的", "total": "",
"free": "自由的", "free": "空闲",
"used": "用过的" "used": "已用",
"load": "负载"
}, },
"docker": { "docker": {
"rx": "rx", "rx": "接收",
"tx": "TX", "tx": "发送",
"mem": "mem", "mem": "内存",
"cpu": "中央处理器", "cpu": "处理器",
"offline": "离线" "offline": "离线"
}, },
"emby": { "emby": {
"playing": "", "playing": "正在播放",
"transcoding": "转码", "transcoding": "转码",
"bitrate": "比特率" "bitrate": "比特率",
"no_active": "暂无播放"
}, },
"tautulli": { "tautulli": {
"playing": "", "playing": "正在播放",
"transcoding": "转码", "transcoding": "转码",
"bitrate": "比特率" "bitrate": "比特率",
}, "no_active": "暂无播放"
"nzbget": {
"rate": "速度",
"remaining": "其余的",
"downloaded": "下载"
}, },
"rutorrent": { "rutorrent": {
"active": "积极的", "active": "活动中",
"upload": "上传", "upload": "上传",
"download": "下载" "download": "下载"
}, },
@@ -45,13 +43,18 @@
"series": "系列" "series": "系列"
}, },
"radarr": { "radarr": {
"wanted": "通缉", "wanted": "订阅",
"queued": "队", "queued": "队",
"movies": "电影" "movies": "电影"
}, },
"readarr": {
"wanted": "订阅",
"queued": "队列",
"books": "书籍"
},
"ombi": { "ombi": {
"pending": "待办的", "pending": "待办的",
"approved": "得到正式认可的", "approved": "已批准",
"available": "可用的" "available": "可用的"
}, },
"jellyseerr": { "jellyseerr": {
@@ -70,9 +73,9 @@
"ping": "ping" "ping": "ping"
}, },
"portainer": { "portainer": {
"running": "跑步", "running": "运行中",
"stopped": "停了下来", "stopped": "已停止",
"total": "全部的" "total": "总计"
}, },
"traefik": { "traefik": {
"routers": "路由器", "routers": "路由器",
@@ -85,9 +88,62 @@
"total": "全部的" "total": "全部的"
}, },
"weather": { "weather": {
"current": "Current Location", "current": "当前位置",
"allow": "Click to allow", "allow": "点击并允许",
"updating": "Updating", "updating": "更新中",
"wait": "Please wait" "wait": "请等待"
},
"overseerr": {
"pending": "待办",
"approved": "已批准",
"available": "可用"
},
"sabnzbd": {
"rate": "速率",
"queue": "队列",
"timeleft": "剩余时间"
},
"nzbget": {
"rate": "速率",
"remaining": "剩余",
"downloaded": "下载"
},
"coinmarketcap": {
"configure": "配置一个或多个需要追踪的加密",
"1hour": "1 Hour",
"1day": "1 Day",
"7days": "7 Days",
"30days": "30 Days"
},
"gotify": {
"apps": "应用",
"clients": "客户端",
"messages": "信息"
},
"prowlarr": {
"enableIndexers": "Indexers",
"numberOfGrabs": "Grabs",
"numberOfQueries": "Queries",
"numberOfFailGrabs": "Fail Grabs",
"numberOfFailQueries": "Fail Queries"
},
"transmission": {
"download": "下载",
"upload": "上传",
"leech": "Leech",
"seed": "做种"
},
"jackett": {
"configured": "Configured",
"errored": "Errored"
},
"bazarr": {
"missingEpisodes": "Missing Episodes",
"missingMovies": "Missing Movies"
},
"lidarr": {
"wanted": "Wanted",
"queued": "Queued",
"albums": "Albums"
} }
} }

View File

@@ -0,0 +1,149 @@
{
"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"
},
"jackett": {
"configured": "Configured",
"errored": "Errored"
},
"bazarr": {
"missingEpisodes": "Missing Episodes",
"missingMovies": "Missing Movies"
},
"lidarr": {
"wanted": "Wanted",
"queued": "Queued",
"albums": "Albums"
}
}

View File

@@ -56,7 +56,7 @@ export default function ColorToggle() {
leaveTo="opacity-0 translate-y-1" leaveTo="opacity-0 translate-y-1"
> >
<Popover.Panel className="absolute -top-[75px] left-0"> <Popover.Panel className="absolute -top-[75px] left-0">
<div className="rounded-md shadow-lg ring-1 ring-black ring-opacity-5"> <div className="rounded-md shadow-lg ring-1 ring-black ring-opacity-5 w-[85vw] sm:w-full">
<div className="relative grid gap-2 p-2 grid-cols-11 bg-white/50 dark:bg-white/10 shadow-black/10 dark:shadow-black/20 rounded-md shadow-md"> <div className="relative grid gap-2 p-2 grid-cols-11 bg-white/50 dark:bg-white/10 shadow-black/10 dark:shadow-black/20 rounded-md shadow-md">
{colors.map((color) => ( {colors.map((color) => (
<button type="button" onClick={() => setColor(color)} key={color}> <button type="button" onClick={() => setColor(color)} key={color}>

View 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>
);
}

View File

@@ -22,43 +22,58 @@ function resolveIcon(icon) {
} }
export default function Item({ service }) { export default function Item({ service }) {
const handleOnClick = () => {
if (service.href && service.href !== "#") {
window.open(service.href, "_blank").focus();
}
};
const hasLink = service.href && service.href !== "#";
return ( return (
<li key={service.name}> <li key={service.name}>
<Disclosure> <Disclosure>
<div <div
className={`${ className={`${
service.href && service.href !== "#" ? "cursor-pointer " : "cursor-default " hasLink ? "cursor-pointer " : " "
}transition-all h-15 overflow-hidden 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`} }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"> <div className="flex select-none">
{service.icon && ( {service.icon &&
(hasLink ? (
<button
type="button"
onClick={handleOnClick}
className="flex-shrink-0 flex items-center justify-center w-12 "
>
<Image src={resolveIcon(service.icon)} width={32} height={32} alt="logo" />
</button>
) : (
<div className="flex-shrink-0 flex items-center justify-center w-12 ">
<Image src={resolveIcon(service.icon)} width={32} height={32} alt="logo" />
</div>
))}
{hasLink ? (
<button <button
type="button" type="button"
onClick={() => { onClick={handleOnClick}
if (service.href && service.href !== "#") { className="flex-1 flex items-center justify-between rounded-r-md "
window.open(service.href, "_blank").focus();
}
}}
className="flex-shrink-0 flex items-center justify-center w-12 "
> >
<Image src={resolveIcon(service.icon)} width={32} height={32} alt="logo" /> <div className="flex-1 px-2 py-2 text-sm text-left">
{service.name}
<p className="text-theme-500 dark:text-theme-400 text-xs font-extralight">{service.description}</p>
</div>
</button> </button>
) : (
<div className="flex-1 flex items-center justify-between rounded-r-md ">
<div className="flex-1 px-2 py-2 text-sm text-left">
{service.name}
<p className="text-theme-500 dark:text-theme-400 text-xs font-extralight">{service.description}</p>
</div>
</div>
)} )}
<button
type="button"
onClick={() => {
if (service.href && service.href !== "#") {
window.open(service.href, "_blank").focus();
}
}}
className="flex-1 flex items-center justify-between rounded-r-md "
>
<div className="flex-1 px-2 py-2 text-sm text-left">
{service.name}
<p className="text-theme-500 dark:text-theme-400 text-xs font-extralight">{service.description}</p>
</div>
</button>
{service.container && ( {service.container && (
<Disclosure.Button <Disclosure.Button
as="div" as="div"

View File

@@ -2,10 +2,15 @@ import { useTranslation } from "react-i18next";
import Sonarr from "./widgets/service/sonarr"; import Sonarr from "./widgets/service/sonarr";
import Radarr from "./widgets/service/radarr"; import Radarr from "./widgets/service/radarr";
import Lidarr from "./widgets/service/lidarr";
import Readarr from "./widgets/service/readarr";
import Bazarr from "./widgets/service/bazarr";
import Ombi from "./widgets/service/ombi"; import Ombi from "./widgets/service/ombi";
import Portainer from "./widgets/service/portainer"; import Portainer from "./widgets/service/portainer";
import Emby from "./widgets/service/emby"; import Emby from "./widgets/service/emby";
import Nzbget from "./widgets/service/nzbget"; 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 Docker from "./widgets/service/docker";
import Pihole from "./widgets/service/pihole"; import Pihole from "./widgets/service/pihole";
import Rutorrent from "./widgets/service/rutorrent"; import Rutorrent from "./widgets/service/rutorrent";
@@ -13,25 +18,40 @@ import Jellyfin from "./widgets/service/jellyfin";
import Speedtest from "./widgets/service/speedtest"; import Speedtest from "./widgets/service/speedtest";
import Traefik from "./widgets/service/traefik"; import Traefik from "./widgets/service/traefik";
import Jellyseerr from "./widgets/service/jellyseerr"; import Jellyseerr from "./widgets/service/jellyseerr";
import Overseerr from "./widgets/service/overseerr";
import Npm from "./widgets/service/npm"; import Npm from "./widgets/service/npm";
import Tautulli from "./widgets/service/tautulli"; 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 = { const widgetMappings = {
docker: Docker, docker: Docker,
sonarr: Sonarr, sonarr: Sonarr,
radarr: Radarr, radarr: Radarr,
lidarr: Lidarr,
readarr: Readarr,
bazarr: Bazarr,
ombi: Ombi, ombi: Ombi,
portainer: Portainer, portainer: Portainer,
emby: Emby, emby: Emby,
jellyfin: Jellyfin, jellyfin: Jellyfin,
nzbget: Nzbget, nzbget: Nzbget,
sabnzbd: SABnzbd,
transmission: Transmission,
pihole: Pihole, pihole: Pihole,
rutorrent: Rutorrent, rutorrent: Rutorrent,
speedtest: Speedtest, speedtest: Speedtest,
traefik: Traefik, traefik: Traefik,
jellyseerr: Jellyseerr, jellyseerr: Jellyseerr,
overseerr: Overseerr,
coinmarketcap: CoinMarketCap,
npm: Npm, npm: Npm,
tautulli: Tautulli, tautulli: Tautulli,
gotify: Gotify,
prowlarr: Prowlarr,
jackett: Jackett,
}; };
export default function Widget({ service }) { export default function Widget({ service }) {

View File

@@ -0,0 +1,36 @@
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 Bazarr({ service }) {
const { t } = useTranslation();
const config = service.widget;
const { data: episodesData, error: episodesError } = useSWR(formatApiUrl(config, "episodes"));
const { data: moviesData, error: moviesError } = useSWR(formatApiUrl(config, "movies"));
if (episodesError || moviesError) {
return <Widget error={t("widget.api_error")} />;
}
if (!episodesData || !moviesData) {
return (
<Widget>
<Block label={t("bazarr.missingEpisodes")} />
<Block label={t("bazarr.missingMovies")} />
</Widget>
);
}
return (
<Widget>
<Block label={t("bazarr.missingEpisodes")} value={episodesData.total} />
<Block label={t("bazarr.missingMovies")} value={moviesData.total} />
</Widget>
);
}

View File

@@ -0,0 +1,90 @@
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 }) {
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;
const { data: statsData, error: statsError } = useSWR(
formatApiUrl(config, `v1/cryptocurrency/quotes/latest?symbol=${symbols.join(",")}&convert=${currencyCode}`)
);
if (!symbols || symbols.length === 0) {
return (
<Widget>
<Block value={t("coinmarketcap.configure")} />
</Widget>
);
}
if (statsError) {
return <Widget error={t("widget.api_error")} />;
}
if (!statsData || !dateRange) {
return (
<Widget>
<Block value={t("coinmarketcap.configure")} />
</Widget>
);
}
const { data } = statsData;
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((symbol) => (
<div
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[symbol].name}</div>
<div className="flex flex-row text-right">
<div className="font-bold mr-2">
{t("common.number", {
value: data[symbol].quote[currencyCode].price,
style: "currency",
currency: currencyCode,
})}
</div>
<div
className={`font-bold w-10 mr-2 ${
data[symbol].quote[currencyCode][`percent_change_${dateRange}`] > 0
? "text-emerald-300"
: "text-rose-300"
}`}
>
{data[symbol].quote[currencyCode][`percent_change_${dateRange}`].toFixed(2)}%
</div>
</div>
</div>
))}
</div>
</Widget>
);
}

View File

@@ -1,17 +1,149 @@
import useSWR from "swr"; import useSWR from "swr";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { BsVolumeMuteFill, BsFillPlayFill, BsPauseFill } from "react-icons/bs";
import Widget from "../widget"; import Widget from "../widget";
import Block from "../block";
import { formatApiUrl } from "utils/api-helpers"; import { formatApiUrl } from "utils/api-helpers";
function ticksToTime(ticks) {
const milliseconds = ticks / 10000;
const seconds = Math.floor((milliseconds / 1000) % 60);
const minutes = Math.floor((milliseconds / (1000 * 60)) % 60);
const hours = Math.floor((milliseconds / (1000 * 60 * 60)) % 24);
return { hours, minutes, seconds };
}
function ticksToString(ticks) {
const { hours, minutes, seconds } = ticksToTime(ticks);
const parts = [];
if (hours > 0) {
parts.push(hours);
}
parts.push(minutes);
parts.push(seconds);
return parts.map((part) => part.toString().padStart(2, "0")).join(":");
}
function SingleSessionEntry({ playCommand, session }) {
console.log(session);
const {
NowPlayingItem: { Name, SeriesName, RunTimeTicks },
PlayState: { PositionTicks, IsPaused, IsMuted },
} = session;
const percent = (PositionTicks / RunTimeTicks) * 100;
return (
<>
<div className="text-theme-700 dark:text-theme-200 relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1 flex">
<div className="text-xs z-10 self-center ml-2">
<span>
{Name}
{SeriesName && ` - ${SeriesName}`}
</span>
</div>
<div className="grow" />
<div className="self-center text-xs flex justify-end mr-1">{IsMuted && <BsVolumeMuteFill />}</div>
</div>
<div className="text-theme-700 dark:text-theme-200 relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1 flex">
<div
className="absolute h-5 rounded-md bg-theme-200 dark:bg-theme-900/40 z-0"
style={{
width: `${percent}%`,
}}
/>
<div className="text-xs z-10 self-center ml-1">
{IsPaused && (
<BsFillPlayFill
onClick={() => {
playCommand(session, "Unpause");
}}
className="inline-block w-4 h-4 cursor-pointer -mt-[1px] mr-1 opacity-80"
/>
)}
{!IsPaused && (
<BsPauseFill
onClick={() => {
playCommand(session, "Pause");
}}
className="inline-block w-4 h-4 cursor-pointer -mt-[1px] mr-1 opacity-80"
/>
)}
</div>
<div className="grow " />
<div className="self-center text-xs flex justify-end mr-2">{ticksToString(PositionTicks)}</div>
</div>
</>
);
}
function SessionEntry({ playCommand, session }) {
const {
NowPlayingItem: { Name, SeriesName, RunTimeTicks },
PlayState: { PositionTicks, IsPaused, IsMuted },
} = session;
const percent = (PositionTicks / RunTimeTicks) * 100;
return (
<div className="text-theme-700 dark:text-theme-200 relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1 flex">
<div
className="absolute h-5 rounded-md bg-theme-200 dark:bg-theme-900/40 z-0"
style={{
width: `${percent}%`,
}}
/>
<div className="text-xs z-10 self-center ml-1">
{IsPaused && (
<BsFillPlayFill
onClick={() => {
playCommand(session, "Unpause");
}}
className="inline-block w-4 h-4 cursor-pointer -mt-[1px] mr-1 opacity-80"
/>
)}
{!IsPaused && (
<BsPauseFill
onClick={() => {
playCommand(session, "Pause");
}}
className="inline-block w-4 h-4 cursor-pointer -mt-[1px] mr-1 opacity-80"
/>
)}
<span>
{Name}
{SeriesName && ` - ${SeriesName}`}
</span>
</div>
<div className="grow " />
<div className="self-center text-xs flex justify-end mr-1">{IsMuted && <BsVolumeMuteFill />}</div>
<div className="self-center text-xs flex justify-end mr-2">{ticksToString(PositionTicks)}</div>
</div>
);
}
export default function Emby({ service }) { export default function Emby({ service }) {
const { t } = useTranslation(); const { t } = useTranslation();
const config = service.widget; const config = service.widget;
const { data: sessionsData, error: sessionsError } = useSWR(formatApiUrl(config, "Sessions")); const {
data: sessionsData,
error: sessionsError,
mutate: sessionMutate,
} = useSWR(formatApiUrl(config, "Sessions"), {
refreshInterval: 5000,
});
async function handlePlayCommand(session, command) {
const url = formatApiUrl(config, `Sessions/${session.Id}/Playing/${command}`);
await fetch(url, {
method: "POST",
}).then(() => {
sessionMutate();
});
}
if (sessionsError) { if (sessionsError) {
return <Widget error={t("widget.api_error")} />; return <Widget error={t("widget.api_error")} />;
@@ -19,26 +151,63 @@ export default function Emby({ service }) {
if (!sessionsData) { if (!sessionsData) {
return ( return (
<Widget> <div className="flex flex-col pb-1">
<Block label={t("emby.playing")} /> <div className="text-theme-700 dark:text-theme-200 text-xs relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1">
<Block label={t("emby.transcoding")} /> <span className="absolute left-2 text-xs mt-[2px]">-</span>
<Block label={t("emby.bitrate")} /> </div>
</Widget> <div className="text-theme-700 dark:text-theme-200 text-xs relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1">
<span className="absolute left-2 text-xs mt-[2px]">-</span>
</div>
</div>
); );
} }
const playing = sessionsData.filter((session) => session?.NowPlayingItem); const playing = sessionsData
const transcoding = sessionsData.filter( .filter((session) => session?.NowPlayingItem)
(session) => session?.PlayState && session.PlayState.PlayMethod === "Transcode" .sort((a, b) => {
); if (a.PlayState.PositionTicks > b.PlayState.PositionTicks) {
return 1;
}
if (a.PlayState.PositionTicks < b.PlayState.PositionTicks) {
return -1;
}
return 0;
});
const bitrate = playing.reduce((acc, session) => acc + session.NowPlayingItem.Bitrate, 0); if (playing.length === 0) {
return (
<div className="flex flex-col pb-1 mx-1">
<div className="text-theme-700 dark:text-theme-200 text-xs relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1">
<span className="absolute left-2 text-xs mt-[2px]">{t("emby.no_active")}</span>
</div>
<div className="text-theme-700 dark:text-theme-200 text-xs relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1">
<span className="absolute left-2 text-xs mt-[2px]">-</span>
</div>
</div>
);
}
if (playing.length === 1) {
const session = playing[0];
return (
<div className="flex flex-col pb-1 mx-1">
<SingleSessionEntry
playCommand={(currentSession, command) => handlePlayCommand(currentSession, command)}
session={session}
/>
</div>
);
}
return ( return (
<Widget> <div className="flex flex-col pb-1 mx-1">
<Block label={t("emby.playing")} value={playing.length} /> {playing.map((session) => (
<Block label={t("emby.transcoding")} value={transcoding.length} /> <SessionEntry
<Block label={t("emby.bitrate")} value={t("common.bitrate", { value: bitrate })} /> key={session.Id}
</Widget> playCommand={(currentSession, command) => handlePlayCommand(currentSession, command)}
session={session}
/>
))}
</div>
); );
} }

View File

@@ -0,0 +1,29 @@
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 Gotify({ service }) {
const { t } = useTranslation();
const config = service.widget;
const { data: appsData, error: appsError } = useSWR(formatApiUrl(config, `application`));
const { data: messagesData, error: messagesError } = useSWR(formatApiUrl(config, `message`));
const { data: clientsData, error: clientsError } = useSWR(formatApiUrl(config, `client`));
if (appsError || messagesError || clientsError) {
return <Widget error={t("widget.api_error")} />;
}
return (
<Widget>
<Block label={t("gotify.apps")} value={appsData?.length} />
<Block label={t("gotify.clients")} value={clientsData?.length} />
<Block label={t("gotify.messages")} value={messagesData?.messages?.length} />
</Widget>
);
}

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

@@ -1,48 +1,5 @@
import useSWR from "swr"; import Emby from "./emby";
import { useTranslation } from "react-i18next";
import Widget from "../widget";
import Block from "../block";
import { formatApiUrl } from "utils/api-helpers";
export default function Jellyfin({ service }) { export default function Jellyfin({ service }) {
const { t } = useTranslation(); return <Emby service={service} />;
const config = service.widget;
const { data: sessionsData, error: sessionsError } = useSWR(formatApiUrl(config, "Sessions"));
if (sessionsError) {
return <Widget error={t("widget.api_error")} />;
}
if (!sessionsData) {
return (
<Widget>
<Block label={t("emby.playing")} />
<Block label={t("emby.transcoding")} />
<Block label={t("emby.bitrate")} />
</Widget>
);
}
const playing = sessionsData.filter((session) => session?.NowPlayingItem);
const transcoding = sessionsData.filter(
(session) => session?.PlayState && session.PlayState.PlayMethod === "Transcode"
);
const bitrate = playing.reduce(
(acc, session) =>
acc + session.NowPlayingQueueFullItems[0].MediaSources.reduce((acb, source) => acb + source.Bitrate, 0),
0
);
return (
<Widget>
<Block label={t("emby.playing")} value={playing.length} />
<Block label={t("emby.transcoding")} value={transcoding.length} />
<Block label={t("emby.bitrate")} value={t("common.bitrate", { value: bitrate })} />
</Widget>
);
} }

View File

@@ -0,0 +1,41 @@
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 Lidarr({ service }) {
const { t } = useTranslation();
const config = service.widget;
const { data: albumsData, error: albumsError } = useSWR(formatApiUrl(config, "album"));
const { data: wantedData, error: wantedError } = useSWR(formatApiUrl(config, "wanted/missing"));
const { data: queueData, error: queueError } = useSWR(formatApiUrl(config, "queue/status"));
if (albumsError || wantedError || queueError) {
return <Widget error={t("widget.api_error")} />;
}
if (!albumsData || !wantedData || !queueData) {
return (
<Widget>
<Block label={t("lidarr.wanted")} />
<Block label={t("lidarr.queued")} />
<Block label={t("lidarr.albums")} />
</Widget>
);
}
const have = albumsData.filter((album) => album.statistics.percentOfTracks === 100);
return (
<Widget>
<Block label={t("lidarr.wanted")} value={wantedData.totalRecords} />
<Block label={t("lidarr.queued")} value={queueData.totalCount} />
<Block label={t("lidarr.albums")} value={have.length} />
</Widget>
);
}

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 Overseerr({ service }) {
const { t } = useTranslation();
const config = service.widget;
const { data: statsData, error: statsError } = useSWR(formatApiUrl(config, `request/count`));
if (statsError) {
return <Widget error={t("widget.api_error")} />;
}
if (!statsData) {
return (
<Widget>
<Block label={t("overseerr.pending")} />
<Block label={t("overseerr.approved")} />
<Block label={t("overseerr.available")} />
</Widget>
);
}
return (
<Widget>
<Block label={t("overseerr.pending")} value={statsData.pending} />
<Block label={t("overseerr.approved")} value={statsData.approved} />
<Block label={t("overseerr.available")} value={statsData.available} />
</Widget>
);
}

View 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>
);
}

View File

@@ -0,0 +1,41 @@
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 Readarr({ service }) {
const { t } = useTranslation();
const config = service.widget;
const { data: booksData, error: booksError } = useSWR(formatApiUrl(config, "book"));
const { data: wantedData, error: wantedError } = useSWR(formatApiUrl(config, "wanted/missing"));
const { data: queueData, error: queueError } = useSWR(formatApiUrl(config, "queue/status"));
if (booksError || wantedError || queueError) {
return <Widget error={t("widget.api_error")} />;
}
if (!booksData || !wantedData || !queueData) {
return (
<Widget>
<Block label={t("readarr.wanted")} />
<Block label={t("readarr.queued")} />
<Block label={t("readarr.books")} />
</Widget>
);
}
const have = booksData.filter((book) => book.statistics.bookFileCount > 0);
return (
<Widget>
<Block label={t("readarr.wanted")} value={wantedData.totalRecords} />
<Block label={t("readarr.queued")} value={queueData.totalCount} />
<Block label={t("readarr.books")} value={have.length} />
</Widget>
);
}

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 SABnzbd({ service }) {
const { t } = useTranslation();
const config = service.widget;
const { data: queueData, error: queueError } = useSWR(formatApiUrl(config, "queue"));
if (queueError) {
return <Widget error={t("widget.api_error")} />;
}
if (!queueData) {
return (
<Widget>
<Block label={t("sabnzbd.rate")} />
<Block label={t("sabnzbd.queue")} />
<Block label={t("sabnzbd.timeleft")} />
</Widget>
);
}
return (
<Widget>
<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>
);
}

View File

@@ -1,39 +1,157 @@
/* eslint-disable camelcase */
import useSWR from "swr"; import useSWR from "swr";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { BsFillPlayFill, BsPauseFill } from "react-icons/bs";
import Widget from "../widget"; import Widget from "../widget";
import Block from "../block";
import { formatApiUrl } from "utils/api-helpers"; import { formatApiUrl } from "utils/api-helpers";
function millisecondsToTime(milliseconds) {
const seconds = Math.floor((milliseconds / 1000) % 60);
const minutes = Math.floor((milliseconds / (1000 * 60)) % 60);
const hours = Math.floor((milliseconds / (1000 * 60 * 60)) % 24);
return { hours, minutes, seconds };
}
function millisecondsToString(milliseconds) {
const { hours, minutes, seconds } = millisecondsToTime(milliseconds);
const parts = [];
if (hours > 0) {
parts.push(hours);
}
parts.push(minutes);
parts.push(seconds);
return parts.map((part) => part.toString().padStart(2, "0")).join(":");
}
function SingleSessionEntry({ session }) {
const { full_title, duration, view_offset, progress_percent, state, year, grandparent_year } = session;
return (
<>
<div className="text-theme-700 dark:text-theme-200 relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1 flex">
<div className="text-xs z-10 self-center ml-2">
<span>{full_title}</span>
</div>
<div className="grow" />
<div className="self-center text-xs flex justify-end mr-2">{year || grandparent_year}</div>
</div>
<div className="text-theme-700 dark:text-theme-200 relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1 flex">
<div
className="absolute h-5 rounded-md bg-theme-200 dark:bg-theme-900/40 z-0"
style={{
width: `${progress_percent}%`,
}}
/>
<div className="text-xs z-10 self-center ml-1">
{state === "paused" && (
<BsPauseFill className="inline-block w-4 h-4 cursor-pointer -mt-[1px] mr-1 opacity-80" />
)}
{state !== "paused" && (
<BsFillPlayFill className="inline-block w-4 h-4 cursor-pointer -mt-[1px] mr-1 opacity-80" />
)}
</div>
<div className="grow " />
<div className="self-center text-xs flex justify-end mr-2">
{millisecondsToString(view_offset)} / {millisecondsToString(duration)}
</div>
</div>
</>
);
}
function SessionEntry({ session }) {
const { full_title, view_offset, progress_percent, state } = session;
return (
<div className="text-theme-700 dark:text-theme-200 relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1 flex">
<div
className="absolute h-5 rounded-md bg-theme-200 dark:bg-theme-900/40 z-0"
style={{
width: `${progress_percent}%`,
}}
/>
<div className="text-xs z-10 self-center ml-1">
{state === "paused" && (
<BsPauseFill className="inline-block w-4 h-4 cursor-pointer -mt-[1px] mr-1 opacity-80" />
)}
{state !== "paused" && (
<BsFillPlayFill className="inline-block w-4 h-4 cursor-pointer -mt-[1px] mr-1 opacity-80" />
)}
<span>{full_title}</span>
</div>
<div className="grow " />
<div className="self-center text-xs flex justify-end mr-2">{millisecondsToString(view_offset)}</div>
</div>
);
}
export default function Tautulli({ service }) { export default function Tautulli({ service }) {
const { t } = useTranslation(); const { t } = useTranslation();
const config = service.widget; const config = service.widget;
const { data: statsData, error: statsError } = useSWR(formatApiUrl(config, "get_activity")); const { data: activityData, error: activityError } = useSWR(formatApiUrl(config, "get_activity"), {
refreshInterval: 5000,
});
if (statsError) { if (activityError) {
return <Widget error={t("widget.api_error")} />; return <Widget error={t("widget.api_error")} />;
} }
if (!statsData) { if (!activityData) {
return ( return (
<Widget> <div className="flex flex-col pb-1 mx-1">
<Block label={t("tautulli.playing")} /> <div className="text-theme-700 dark:text-theme-200 text-xs relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1">
<Block label={t("tautulli.transcoding")} /> <span className="absolute left-2 text-xs mt-[2px]">-</span>
<Block label={t("tautulli.bitrate")} /> </div>
</Widget> <div className="text-theme-700 dark:text-theme-200 text-xs relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1">
<span className="absolute left-2 text-xs mt-[2px]">-</span>
</div>
</div>
); );
} }
const { data } = statsData.response; const playing = activityData.response.data.sessions.sort((a, b) => {
if (a.view_offset > b.view_offset) {
return 1;
}
if (a.view_offset < b.view_offset) {
return -1;
}
return 0;
});
if (playing.length === 0) {
return (
<div className="flex flex-col pb-1 mx-1">
<div className="text-theme-700 dark:text-theme-200 text-xs relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1">
<span className="absolute left-2 text-xs mt-[2px]">{t("tautulli.no_active")}</span>
</div>
<div className="text-theme-700 dark:text-theme-200 text-xs relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1">
<span className="absolute left-2 text-xs mt-[2px]">-</span>
</div>
</div>
);
}
if (playing.length === 1) {
const session = playing[0];
return (
<div className="flex flex-col pb-1 mx-1">
<SingleSessionEntry session={session} />
</div>
);
}
return ( return (
<Widget> <div className="flex flex-col pb-1 mx-1">
<Block label={t("tautulli.playing")} value={data.stream_count} /> {playing.map((session) => (
<Block label={t("tautulli.transcoding")} value={data.stream_count_transcode} /> <SessionEntry key={session.Id} session={session} />
<Block label={t("tautulli.bitrate")} value={t("common.bitrate", { value: data.total_bandwidth })} /> ))}
</Widget> </div>
); );
} }

View 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>
);
}

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

@@ -16,7 +16,7 @@ function Widget({ options }) {
if (error || data?.cod === 401 || data?.error) { if (error || data?.cod === 401 || data?.error) {
return ( return (
<div className="flex flex-col justify-center"> <div className="flex flex-col justify-center first:ml-0 ml-4">
<div className="flex flex-row items-center justify-end"> <div className="flex flex-row items-center justify-end">
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<BiError className="w-8 h-8 text-theme-800 dark:text-theme-200" /> <BiError className="w-8 h-8 text-theme-800 dark:text-theme-200" />
@@ -32,7 +32,7 @@ function Widget({ options }) {
if (!data) { if (!data) {
return ( return (
<div className="flex flex-col justify-center"> <div className="flex flex-col justify-center first:ml-0 ml-4">
<div className="flex flex-row items-center justify-end"> <div className="flex flex-row items-center justify-end">
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<WiCloudDown className="w-8 h-8 text-theme-800 dark:text-theme-200" /> <WiCloudDown className="w-8 h-8 text-theme-800 dark:text-theme-200" />
@@ -49,7 +49,7 @@ function Widget({ options }) {
const unit = options.units === "metric" ? "celsius" : "fahrenheit"; const unit = options.units === "metric" ? "celsius" : "fahrenheit";
return ( return (
<div className="flex flex-col justify-center"> <div className="flex flex-col justify-center first:ml-0 ml-4">
<div className="flex flex-row items-center justify-end"> <div className="flex flex-row items-center justify-end">
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<Icon <Icon
@@ -100,7 +100,7 @@ export default function OpenWeatherMap({ options }) {
if (!location) { if (!location) {
return ( return (
<button type="button" onClick={() => requestLocation()} className="flex flex-col justify-center"> <button type="button" onClick={() => requestLocation()} className="flex flex-col justify-center first:ml-0 ml-4">
<div className="flex flex-row items-center justify-end"> <div className="flex flex-row items-center justify-end">
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
{requesting ? ( {requesting ? (

View File

@@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
import UsageBar from "./usage-bar"; import UsageBar from "./usage-bar";
export default function Cpu() { export default function Cpu({ expanded }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { data, error } = useSWR(`/api/widgets/resources?type=cpu`, { const { data, error } = useSWR(`/api/widgets/resources?type=cpu`, {
@@ -14,7 +14,7 @@ export default function Cpu() {
if (error || data?.error) { if (error || data?.error) {
return ( return (
<div className="flex-none flex flex-row items-center justify-center"> <div className="flex-none flex flex-row items-center mr-3 py-1.5">
<BiError className="text-theme-800 dark:text-theme-200 w-5 h-5" /> <BiError className="text-theme-800 dark:text-theme-200 w-5 h-5" />
<div className="flex flex-col ml-3 text-left"> <div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-xs">{t("widget.api_error")}</span> <span className="text-theme-800 dark:text-theme-200 text-xs">{t("widget.api_error")}</span>
@@ -25,7 +25,7 @@ export default function Cpu() {
if (!data) { if (!data) {
return ( return (
<div className="flex-none flex flex-row items-center justify-center"> <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" /> <FiCpu className="text-theme-800 dark:text-theme-200 w-5 h-5" />
<div className="flex flex-col ml-3 text-left"> <div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-xs">-</span> <span className="text-theme-800 dark:text-theme-200 text-xs">-</span>
@@ -37,12 +37,31 @@ export default function Cpu() {
const percent = data.cpu.usage; const percent = data.cpu.usage;
return ( return (
<div className="flex-none flex flex-row items-center justify-center"> <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" /> <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-[50px]"> <div className="flex flex-col ml-3 text-left min-w-[85px]">
<div className="text-theme-800 dark:text-theme-200 text-xs"> <div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
{t("common.number", { value: data.cpu.usage, style: "unit", unit: "percent", maximumFractionDigits: 0 })} <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> </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} /> <UsageBar percent={percent} />
</div> </div>
</div> </div>

View File

@@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
import UsageBar from "./usage-bar"; import UsageBar from "./usage-bar";
export default function Disk({ options }) { export default function Disk({ options, expanded }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { data, error } = useSWR(`/api/widgets/resources?type=disk&target=${options.disk}`, { const { data, error } = useSWR(`/api/widgets/resources?type=disk&target=${options.disk}`, {
@@ -14,7 +14,7 @@ export default function Disk({ options }) {
if (error || data?.error) { if (error || data?.error) {
return ( return (
<div className="flex-none flex flex-row items-center justify-center"> <div className="flex-none flex flex-row items-center mr-3 py-1.5">
<BiError className="text-theme-800 dark:text-theme-200 w-5 h-5" /> <BiError className="text-theme-800 dark:text-theme-200 w-5 h-5" />
<div className="flex flex-col ml-3 text-left"> <div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-xs">{t("widget.api_error")}</span> <span className="text-theme-800 dark:text-theme-200 text-xs">{t("widget.api_error")}</span>
@@ -25,7 +25,7 @@ export default function Disk({ options }) {
if (!data) { if (!data) {
return ( return (
<div className="flex-none flex flex-row items-center justify-center"> <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" /> <FiHardDrive className="text-theme-800 dark:text-theme-200 w-5 h-5" />
<div className="flex flex-col ml-3 text-left"> <div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-xs">-</span> <span className="text-theme-800 dark:text-theme-200 text-xs">-</span>
@@ -37,15 +37,19 @@ export default function Disk({ options }) {
const percent = Math.round((data.drive.usedGb / data.drive.totalGb) * 100); const percent = Math.round((data.drive.usedGb / data.drive.totalGb) * 100);
return ( return (
<div className="flex-none flex flex-row items-center justify-center 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" /> <FiHardDrive className="text-theme-800 dark:text-theme-200 w-5 h-5" />
<div className="flex flex-col ml-3 text-left "> <div className="flex flex-col ml-3 text-left min-w-[85px]">
<span className="text-theme-800 dark:text-theme-200 text-xs group-hover:hidden"> <span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
{t("common.bytes", { value: data.drive.freeGb * 1024 * 1024 * 1024 })} {t("resources.free")} <div className="pl-0.5">{t("common.bytes", { value: data.drive.freeGb * 1024 * 1024 * 1024 })}</div>
</span> <div className="pr-1">{t("resources.free")}</div>
<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")}
</span> </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} /> <UsageBar percent={percent} />
</div> </div>
</div> </div>

View File

@@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
import UsageBar from "./usage-bar"; import UsageBar from "./usage-bar";
export default function Memory() { export default function Memory({ expanded }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { data, error } = useSWR(`/api/widgets/resources?type=memory`, { const { data, error } = useSWR(`/api/widgets/resources?type=memory`, {
@@ -14,7 +14,7 @@ export default function Memory() {
if (error || data?.error) { if (error || data?.error) {
return ( return (
<div className="flex-none flex flex-row items-center justify-center"> <div className="flex-none flex flex-row items-center mr-3 py-1.5">
<BiError className="text-theme-800 dark:text-theme-200 w-5 h-5" /> <BiError className="text-theme-800 dark:text-theme-200 w-5 h-5" />
<div className="flex flex-col ml-3 text-left"> <div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-xs">{t("widget.api_error")}</span> <span className="text-theme-800 dark:text-theme-200 text-xs">{t("widget.api_error")}</span>
@@ -25,7 +25,7 @@ export default function Memory() {
if (!data) { if (!data) {
return ( return (
<div className="flex-none flex flex-row items-center justify-center"> <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" /> <FaMemory className="text-theme-800 dark:text-theme-200 w-5 h-5" />
<div className="flex flex-col ml-3 text-left"> <div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-xs">-</span> <span className="text-theme-800 dark:text-theme-200 text-xs">-</span>
@@ -37,15 +37,27 @@ export default function Memory() {
const percent = Math.round((data.memory.usedMemMb / data.memory.totalMemMb) * 100); const percent = Math.round((data.memory.usedMemMb / data.memory.totalMemMb) * 100);
return ( return (
<div className="flex-none flex flex-row items-center justify-center 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" /> <FaMemory className="text-theme-800 dark:text-theme-200 w-5 h-5" />
<div className="flex flex-col ml-3 text-left"> <div className="flex flex-col ml-3 text-left min-w-[85px]">
<span className="text-theme-800 dark:text-theme-200 text-xs group-hover:hidden"> <span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
{t("common.bytes", { value: data.memory.freeMemMb * 1024 * 1024 })} {t("resources.free")} <div className="pl-0.5">
</span> {t("common.bytes", { value: data.memory.freeMemMb * 1024 * 1024, maximumFractionDigits: 0, binary: true })}
<span className="text-theme-800 dark:text-theme-200 text-xs hidden group-hover:block"> </div>
{t("common.bytes", { value: data.memory.usedMemMb * 1024 * 1024 })} {t("resources.used")} <div className="pr-1">{t("resources.free")}</div>
</span> </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} /> <UsageBar percent={percent} />
</div> </div>
</div> </div>

View File

@@ -3,12 +3,15 @@ import Cpu from "./cpu";
import Memory from "./memory"; import Memory from "./memory";
export default function Resources({ options }) { export default function Resources({ options }) {
const { expanded } = options;
return ( return (
<div className="flex flex-col max-w:full basis-1/2 sm:basis-auto self-center"> <div className="flex flex-col max-w:full sm:basis-auto self-center m-auto flex-wrap">
<div className="flex flex-row space-x-4 self-center"> <div className="flex flex-row self-center flex-wrap justify-between">
{options.cpu && <Cpu />} {options.cpu && <Cpu expanded={expanded} />}
{options.memory && <Memory />} {options.memory && <Memory expanded={expanded} />}
{options.disk && <Disk options={options} />} {Array.isArray(options.disk)
? options.disk.map((disk) => <Disk key={disk} options={{ disk }} expanded={expanded} />)
: options.disk && <Disk options={options} expanded={expanded} />}
</div> </div>
{options.label && ( {options.label && (
<div className="ml-6 pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">{options.label}</div> <div className="ml-6 pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">{options.label}</div>

View File

@@ -51,7 +51,7 @@ export default function Search({ options }) {
} }
return ( return (
<form className="flex-col relative h-8 my-4 min-w-full md:min-w-fit grow" onSubmit={handleSubmit}> <form className="flex-col relative h-8 my-4 min-w-full md:min-w-fit grow first:ml-0 ml-4" onSubmit={handleSubmit}>
<div className="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none w-full text-theme-800 dark:text-white" /> <div className="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none w-full text-theme-800 dark:text-white" />
<input <input
type="text" type="text"
@@ -69,6 +69,8 @@ export default function Search({ options }) {
autoCapitalize="off" autoCapitalize="off"
autoCorrect="off" autoCorrect="off"
autoComplete="off" autoComplete="off"
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={options.focus}
/> />
<button <button
type="submit" type="submit"

View File

@@ -16,7 +16,7 @@ function Widget({ options }) {
if (error || data?.error) { if (error || data?.error) {
return ( return (
<div className="flex flex-col justify-center"> <div className="flex flex-col justify-center first:ml-0 ml-4">
<div className="flex flex-row items-center justify-end"> <div className="flex flex-row items-center justify-end">
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<BiError className="w-8 h-8 text-theme-800 dark:text-theme-200" /> <BiError className="w-8 h-8 text-theme-800 dark:text-theme-200" />
@@ -32,7 +32,7 @@ function Widget({ options }) {
if (!data) { if (!data) {
return ( return (
<div className="flex flex-col justify-center"> <div className="flex flex-col justify-center first:ml-0 ml-4">
<div className="flex flex-row items-center justify-end"> <div className="flex flex-row items-center justify-end">
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<WiCloudDown className="w-8 h-8 text-theme-800 dark:text-theme-200" /> <WiCloudDown className="w-8 h-8 text-theme-800 dark:text-theme-200" />
@@ -49,7 +49,7 @@ function Widget({ options }) {
const unit = options.units === "metric" ? "celsius" : "fahrenheit"; const unit = options.units === "metric" ? "celsius" : "fahrenheit";
return ( return (
<div className="flex flex-col justify-center"> <div className="flex flex-col justify-center first:ml-0 ml-4">
<div className="flex flex-row items-center justify-end"> <div className="flex flex-row items-center justify-end">
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<Icon condition={data.current.condition.code} timeOfDay={data.current.is_day ? "day" : "night"} /> <Icon condition={data.current.condition.code} timeOfDay={data.current.is_day ? "day" : "night"} />
@@ -101,7 +101,7 @@ export default function WeatherApi({ options }) {
if (!location) { if (!location) {
return ( return (
<button type="button" onClick={() => requestLocation()} className="flex flex-col justify-center"> <button type="button" onClick={() => requestLocation()} className="flex flex-col justify-center first:ml-0 ml-4">
<div className="flex flex-row items-center justify-end"> <div className="flex flex-row items-center justify-end">
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
{requesting ? ( {requesting ? (

View File

@@ -1,8 +1,23 @@
/* eslint-disable no-console */
import { servicesFromConfig, servicesFromDocker, cleanServiceGroups } from "utils/service-helpers"; import { servicesFromConfig, servicesFromDocker, cleanServiceGroups } from "utils/service-helpers";
export default async function handler(req, res) { export default async function handler(req, res) {
const discoveredServices = cleanServiceGroups(await servicesFromDocker()); let discoveredServices;
const configuredServices = cleanServiceGroups(await servicesFromConfig()); let configuredServices;
try {
discoveredServices = cleanServiceGroups(await servicesFromDocker());
} catch {
console.error("Failed to discover services, please check docker.yaml for errors");
discoveredServices = [];
}
try {
configuredServices = cleanServiceGroups(await servicesFromConfig());
} catch {
console.error("Failed to load services.yaml, please check for errors");
configuredServices = [];
}
const mergedGroupsNames = [ const mergedGroupsNames = [
...new Set([discoveredServices.map((group) => group.name), configuredServices.map((group) => group.name)].flat()), ...new Set([discoveredServices.map((group) => group.name), configuredServices.map((group) => group.name)].flat()),

View File

@@ -3,6 +3,7 @@ import credentialedProxyHandler from "utils/proxies/credentialed";
import rutorrentProxyHandler from "utils/proxies/rutorrent"; import rutorrentProxyHandler from "utils/proxies/rutorrent";
import nzbgetProxyHandler from "utils/proxies/nzbget"; import nzbgetProxyHandler from "utils/proxies/nzbget";
import npmProxyHandler from "utils/proxies/npm"; import npmProxyHandler from "utils/proxies/npm";
import transmissionProxyHandler from "utils/proxies/transmission";
const serviceProxyHandlers = { const serviceProxyHandlers = {
// uses query param auth // uses query param auth
@@ -11,17 +12,27 @@ const serviceProxyHandlers = {
pihole: genericProxyHandler, pihole: genericProxyHandler,
radarr: genericProxyHandler, radarr: genericProxyHandler,
sonarr: genericProxyHandler, sonarr: genericProxyHandler,
lidarr: genericProxyHandler,
readarr: genericProxyHandler,
bazarr: genericProxyHandler,
speedtest: genericProxyHandler, speedtest: genericProxyHandler,
tautulli: genericProxyHandler, tautulli: genericProxyHandler,
traefik: genericProxyHandler, traefik: genericProxyHandler,
// uses X-API-Key header auth sabnzbd: genericProxyHandler,
jackett: genericProxyHandler,
// uses X-API-Key (or similar) header auth
gotify: credentialedProxyHandler,
portainer: credentialedProxyHandler, portainer: credentialedProxyHandler,
jellyseerr: credentialedProxyHandler, jellyseerr: credentialedProxyHandler,
overseerr: credentialedProxyHandler,
ombi: credentialedProxyHandler, ombi: credentialedProxyHandler,
coinmarketcap: credentialedProxyHandler,
prowlarr: credentialedProxyHandler,
// super specific handlers // super specific handlers
rutorrent: rutorrentProxyHandler, rutorrent: rutorrentProxyHandler,
nzbget: nzbgetProxyHandler, nzbget: nzbgetProxyHandler,
npm: npmProxyHandler, npm: npmProxyHandler,
transmission: transmissionProxyHandler,
}; };
export default async function handler(req, res) { export default async function handler(req, res) {

View File

@@ -2,6 +2,8 @@
import useSWR from "swr"; import useSWR from "swr";
import Head from "next/head"; import Head from "next/head";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import { useTranslation } from "react-i18next";
import { useEffect } from "react";
import ServicesGroup from "components/services/group"; import ServicesGroup from "components/services/group";
import BookmarksGroup from "components/bookmarks/group"; import BookmarksGroup from "components/bookmarks/group";
@@ -30,7 +32,10 @@ export async function getStaticProps() {
}, },
}; };
} }
export default function Home({ settings }) { export default function Home({ settings }) {
const { i18n } = useTranslation();
const { data: services } = useSWR("/api/services"); const { data: services } = useSWR("/api/services");
const { data: bookmarks } = useSWR("/api/bookmarks"); const { data: bookmarks } = useSWR("/api/bookmarks");
const { data: widgets } = useSWR("/api/widgets"); const { data: widgets } = useSWR("/api/widgets");
@@ -41,15 +46,23 @@ export default function Home({ settings }) {
wrappedStyle.backgroundSize = "cover"; wrappedStyle.backgroundSize = "cover";
} }
useEffect(() => {
if (settings.language) {
i18n.changeLanguage(settings.language);
}
}, [i18n, settings.language]);
return ( return (
<ColorProvider> <ColorProvider>
<ThemeProvider> <ThemeProvider>
<Head> <Head>
<title>{settings.title || "Homepage"}</title> <title>{settings.title || "Homepage"}</title>
{settings.base && <base href={settings.base} />}
{settings.favicon && <link rel="icon" href={settings.favicon} />}
</Head> </Head>
<div className="fixed w-full h-full m-0 p-0" style={wrappedStyle} /> <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"> <div className="relative w-full container m-auto flex flex-col h-screen justify-between">
<div className="flex flex-row flex-wrap space-x-0 sm:space-x-4 m-8 pb-4 mt-10 border-b-2 border-theme-800 dark:border-theme-200 justify-between md:justify-start"> <div className="flex flex-row flex-wrap m-8 pb-4 mt-10 border-b-2 border-theme-800 dark:border-theme-200 justify-between">
{widgets && ( {widgets && (
<> <>
{widgets {widgets
@@ -58,7 +71,7 @@ export default function Home({ settings }) {
<Widget key={i} widget={widget} /> <Widget key={i} widget={widget} />
))} ))}
<div className="flex flex-wrap basis-full space-x-0 sm:space-x-4 grow sm:basis-auto justify-between md:justify-end mt-2 md:mt-0"> <div className="ml-4 flex flex-wrap basis-full grow sm:basis-auto justify-between md:justify-end mt-2 md:mt-0">
{widgets {widgets
.filter((widget) => rightAlignedWidgets.includes(widget.type)) .filter((widget) => rightAlignedWidgets.includes(widget.type))
.map((widget, i) => ( .map((widget, i) => (

View File

@@ -9,9 +9,19 @@ const formats = {
traefik: `{url}/api/{endpoint}`, traefik: `{url}/api/{endpoint}`,
portainer: `{url}/api/endpoints/{env}/{endpoint}`, portainer: `{url}/api/endpoints/{env}/{endpoint}`,
rutorrent: `{url}/plugins/httprpc/action.php`, rutorrent: `{url}/plugins/httprpc/action.php`,
transmission: `{url}/transmission/rpc`,
jellyseerr: `{url}/api/v1/{endpoint}`, jellyseerr: `{url}/api/v1/{endpoint}`,
overseerr: `{url}/api/v1/{endpoint}`,
ombi: `{url}/api/v1/{endpoint}`, ombi: `{url}/api/v1/{endpoint}`,
npm: `{url}/api/{endpoint}`, npm: `{url}/api/{endpoint}`,
lidarr: `{url}/api/v1/{endpoint}?apikey={key}`,
readarr: `{url}/api/v1/{endpoint}?apikey={key}`,
bazarr: `{url}/api/{endpoint}/wanted?apikey={key}`,
sabnzbd: `{url}/api/?apikey={key}&output=json&mode={endpoint}`,
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) { export function formatApiCall(api, args) {

View File

@@ -1,9 +1,46 @@
/* eslint-disable prefer-promise-reject-errors */ /* eslint-disable prefer-promise-reject-errors */
import https from "https"; /* eslint-disable no-param-reassign */
import http from "http"; 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) { export function httpsRequest(url, params) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
addCookieHandler(url, params);
const request = https.request(url, params, (response) => { const request = https.request(url, params, (response) => {
const data = []; const data = [];
@@ -12,7 +49,7 @@ export function httpsRequest(url, params) {
}); });
response.on("end", () => { 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,12 +57,17 @@ export function httpsRequest(url, params) {
reject([500, error]); reject([500, error]);
}); });
if (params.body) {
request.write(params.body);
}
request.end(); request.end();
}); });
} }
export function httpRequest(url, params) { export function httpRequest(url, params) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
addCookieHandler(url, params);
const request = http.request(url, params, (response) => { const request = http.request(url, params, (response) => {
const data = []; const data = [];
@@ -34,7 +76,7 @@ export function httpRequest(url, params) {
}); });
response.on("end", () => { 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 +84,10 @@ export function httpRequest(url, params) {
reject([500, error]); reject([500, error]);
}); });
if (params.body) {
request.write(params.body);
}
request.end(); request.end();
}); });
} }

View File

@@ -10,15 +10,30 @@ export default async function credentialedProxyHandler(req, res) {
if (widget) { if (widget) {
const url = new URL(formatApiCall(widget.type, { endpoint, ...widget })); const url = new URL(formatApiCall(widget.type, { endpoint, ...widget }));
const headers = {
"Content-Type": "application/json",
};
if (widget.type === "coinmarketcap") {
headers["X-CMC_PRO_API_KEY"] = `${widget.key}`;
} else if (widget.type === "gotify") {
headers["X-gotify-Key"] = `${widget.key}`;
} else {
headers["X-API-Key"] = `${widget.key}`;
}
const [status, contentType, data] = await httpProxy(url, { const [status, contentType, data] = await httpProxy(url, {
method: req.method,
withCredentials: true, withCredentials: true,
credentials: "include", credentials: "include",
headers: { headers,
"X-API-Key": `${widget.key}`,
"Content-Type": "application/json",
},
}); });
if (status === 204 || status === 304) {
return res.status(status).end();
}
if (contentType) res.setHeader("Content-Type", contentType); if (contentType) res.setHeader("Content-Type", contentType);
return res.status(status).send(data); return res.status(status).send(data);
} }

View File

@@ -10,9 +10,16 @@ export default async function genericProxyHandler(req, res) {
if (widget) { if (widget) {
const url = new URL(formatApiCall(widget.type, { endpoint, ...widget })); const url = new URL(formatApiCall(widget.type, { endpoint, ...widget }));
const [status, contentType, data] = await httpProxy(url); const [status, contentType, data] = await httpProxy(url, {
method: req.method,
});
if (contentType) res.setHeader("Content-Type", contentType); if (contentType) res.setHeader("Content-Type", contentType);
if (status === 204 || status === 304) {
return res.status(status).end();
}
return res.status(status).send(data); return res.status(status).send(data);
} }
} }

View 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);
}

View File

@@ -110,13 +110,27 @@ export function cleanServiceGroups(groups) {
const cleanedService = { ...service }; const cleanedService = { ...service };
if (cleanedService.widget) { if (cleanedService.widget) {
const { type } = cleanedService.widget; // whitelisted set of keys to pass to the frontend
const {
type, // all widgets
server, // docker widget
container,
currency, // coinmarketcap widget
symbols,
} = cleanedService.widget;
cleanedService.widget = { cleanedService.widget = {
type, type,
currency,
symbols,
service_name: service.name, service_name: service.name,
service_group: serviceGroup.name, service_group: serviceGroup.name,
}; };
if (type === "docker") {
cleanedService.widget.server = server;
cleanedService.widget.container = container;
}
} }
return cleanedService; return cleanedService;