Compare commits

..
537 Commits
Author SHA1 Message Date
Ben Phelps ea6a668a84 add version information 2022-09-24 01:18:37 +03:00
Ben Phelps 08615fe9f6 Update docker-publish.yml 2022-09-24 00:17:23 +03:00
SuperDOS f8ef5ddf5a Translated using Weblate (Swedish)
Currently translated at 93.0% (107 of 115 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/sv/
2022-09-23 13:44:01 +02:00
XNRavenZen 595d81dd2f Translated using Weblate (Chinese (Simplified))
Currently translated at 94.7% (109 of 115 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-09-23 13:44:01 +02:00
nicedc 00c654953d Translated using Weblate (Chinese (Simplified))
Currently translated at 94.7% (109 of 115 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-09-23 13:44:01 +02:00
Bruno Rossetto bf2f3a7d17 Translated using Weblate (Portuguese)
Currently translated at 100.0% (115 of 115 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pt/
2022-09-23 13:44:01 +02:00
Nonoss117 612f0fde2d Translated using Weblate (French)
Currently translated at 100.0% (115 of 115 strings)

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

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-23 13:44:00 +02:00
Juan Manuel Bennàssar Carretero d51854e663 Translated using Weblate (Spanish)
Currently translated at 100.0% (115 of 115 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-23 13:44:00 +02:00
Moritz Neumann cb9a18fd40 Translated using Weblate (German)
Currently translated at 100.0% (115 of 115 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/de/
2022-09-23 13:44:00 +02:00
Ben Phelps ed28d69d76 Merge pull request #251 from JazzFisch/patch-nextjs-logging
Patch console object to instead use homepage's logger for logging
2022-09-23 11:51:15 +03:00
Jason Fischer 5667cedafc Merge branch 'main' into patch-nextjs-logging 2022-09-21 23:38:53 -07:00
Jason Fischer 42fe535df7 Patch console object to instead use winston for logging
- Allow setting labels via new createLogger method
- Make logger initialization more robust
2022-09-21 23:37:17 -07:00
Ben Phelps 0e1e2bde22 Update FUNDING.yml 2022-09-22 03:01:00 +03:00
Ben Phelps 1a5e2f3cda Update FUNDING.yml 2022-09-22 03:00:39 +03:00
Ben Phelps 32cb113014 Create FUNDING.yml 2022-09-22 02:57:58 +03:00
Ben Phelps 559af0cd56 Update README.md 2022-09-21 22:53:19 +03:00
Ben Phelps 0e5477eecf experiment with lazily loaded widgets 2022-09-21 22:26:58 +03:00
Jason Fischer ab631fa26e Merge pull request #246 from JazzFisch/fix-log-folder
Write homepage.log into config folder
2022-09-21 08:46:21 -07:00
Jason Fischer 5776544c20 Write homepage.log into config folder
This will make it easier for people to find their log file(s)
2022-09-21 08:32:37 -07:00
Ben Phelps 666e2a42cf fix context state race 2022-09-21 17:39:46 +03:00
Ben Phelps 2fc1dda122 add tailwind-scollbars 2022-09-21 16:11:48 +03:00
Juan Manuel Bennàssar Carretero 0115b594d6 Translated using Weblate (Catalan)
Currently translated at 100.0% (115 of 115 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ca/
2022-09-21 11:32:07 +02:00
Juan Manuel Bennàssar Carretero c5828978b2 Translated using Weblate (Spanish)
Currently translated at 100.0% (115 of 115 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-21 11:32:07 +02:00
Ben Phelps 3ff756e057 update readme 2022-09-21 10:09:24 +03:00
Anonymous 1c405ff4ec Translated using Weblate (Hebrew)
Currently translated at 87.8% (101 of 115 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/he/
2022-09-21 08:06:43 +02:00
Anonymous 5d1041d564 Translated using Weblate (Hungarian)
Currently translated at 93.9% (108 of 115 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/hu/
2022-09-21 08:06:42 +02:00
Anonymous 9e8942398c Translated using Weblate (Croatian)
Currently translated at 7.8% (9 of 115 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/hr/
2022-09-21 08:06:42 +02:00
Anonymous a5b7c8439d Translated using Weblate (Swedish)
Currently translated at 83.4% (96 of 115 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/sv/
2022-09-21 08:06:42 +02:00
Anonymous 9131a8f118 Translated using Weblate (Polish)
Currently translated at 85.2% (98 of 115 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pl/
2022-09-21 08:06:42 +02:00
Anonymous 8687fe6b26 Translated using Weblate (Catalan)
Currently translated at 93.9% (108 of 115 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ca/
2022-09-21 08:06:41 +02:00
Anonymous 1d38fd8dea Translated using Weblate (Chinese (Traditional))
Currently translated at 7.8% (9 of 115 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hant/
2022-09-21 08:06:41 +02:00
Anonymous 4186bbb3c3 Translated using Weblate (Dutch)
Currently translated at 54.7% (63 of 115 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nl/
2022-09-21 08:06:41 +02:00
Anonymous 897c71f36e Translated using Weblate (Vietnamese)
Currently translated at 38.2% (44 of 115 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/vi/
2022-09-21 08:06:41 +02:00
Anonymous f9ce9b7716 Translated using Weblate (Norwegian Bokmål)
Currently translated at 68.6% (79 of 115 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nb_NO/
2022-09-21 08:06:40 +02:00
Anonymous ec230ba249 Translated using Weblate (Italian)
Currently translated at 66.9% (77 of 115 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/it/
2022-09-21 08:06:40 +02:00
Anonymous 6bfa49689e Translated using Weblate (Chinese (Simplified))
Currently translated at 93.9% (108 of 115 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-09-21 08:06:39 +02:00
Anonymous 177acf86d7 Translated using Weblate (Russian)
Currently translated at 20.0% (23 of 115 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ru/
2022-09-21 08:06:39 +02:00
Anonymous c89ed904cc Translated using Weblate (Portuguese)
Currently translated at 76.5% (88 of 115 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pt/
2022-09-21 08:06:39 +02:00
Anonymous 6add7c3d82 Translated using Weblate (French)
Currently translated at 96.5% (111 of 115 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-21 08:06:39 +02:00
Anonymous c562035776 Translated using Weblate (Spanish)
Currently translated at 93.9% (108 of 115 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-21 08:06:38 +02:00
Anonymous 4757e25fdc Translated using Weblate (German)
Currently translated at 90.4% (104 of 115 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/de/
2022-09-21 08:06:38 +02:00
Nonoss117 077bc356b8 Translated using Weblate (French)
Currently translated at 100.0% (111 of 111 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-21 08:06:30 +02:00
Ben Phelps 1dff880a93 Merge pull request #230 from DevPGSV/feature/widget_strelaysrv
Add Syncthing Relay Server widget
2022-09-21 09:06:26 +03:00
Ben Phelps 122b987fa3 Merge branch 'main' into feature/widget_strelaysrv 2022-09-21 09:05:42 +03:00
Ben Phelps c024c4f01c Merge pull request #241 from JazzFisch/add-logger
Add Winston for log handling
2022-09-21 09:03:52 +03:00
Ben Phelps a677fbefbf add global settings context
will be useful going forward, and simplify widget props being passed around all over the place
2022-09-21 09:00:57 +03:00
Ben Phelps 244a76de0b fix bookmark styling 2022-09-21 08:58:24 +03:00
Ben Phelps 20ac15b18c allow setting a global link target 2022-09-21 08:40:19 +03:00
Anonymous 75244cc40e Translated using Weblate (Hebrew)
Currently translated at 90.9% (101 of 111 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/he/
2022-09-21 07:39:32 +02:00
Anonymous 3e731298a5 Translated using Weblate (Hungarian)
Currently translated at 97.2% (108 of 111 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/hu/
2022-09-21 07:39:32 +02:00
Anonymous ed0bf027fc Translated using Weblate (Croatian)
Currently translated at 8.1% (9 of 111 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/hr/
2022-09-21 07:39:31 +02:00
Anonymous 7a10131768 Translated using Weblate (Swedish)
Currently translated at 86.4% (96 of 111 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/sv/
2022-09-21 07:39:31 +02:00
Anonymous 19522b8712 Translated using Weblate (Polish)
Currently translated at 88.2% (98 of 111 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pl/
2022-09-21 07:39:31 +02:00
Anonymous af79061a45 Translated using Weblate (Catalan)
Currently translated at 97.2% (108 of 111 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ca/
2022-09-21 07:39:31 +02:00
Anonymous d56d9f7a50 Translated using Weblate (Chinese (Traditional))
Currently translated at 8.1% (9 of 111 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hant/
2022-09-21 07:39:30 +02:00
Anonymous 45a1a9ed5a Translated using Weblate (Dutch)
Currently translated at 56.7% (63 of 111 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nl/
2022-09-21 07:39:30 +02:00
Anonymous 50954cf3d4 Translated using Weblate (Vietnamese)
Currently translated at 39.6% (44 of 111 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/vi/
2022-09-21 07:39:30 +02:00
Anonymous 6beefbf39a Translated using Weblate (Norwegian Bokmål)
Currently translated at 71.1% (79 of 111 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nb_NO/
2022-09-21 07:39:30 +02:00
Anonymous 8f21d1ae31 Translated using Weblate (Italian)
Currently translated at 69.3% (77 of 111 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/it/
2022-09-21 07:39:29 +02:00
Anonymous e4825531c4 Translated using Weblate (Chinese (Simplified))
Currently translated at 97.2% (108 of 111 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-09-21 07:39:29 +02:00
Anonymous 6174f53f37 Translated using Weblate (Russian)
Currently translated at 20.7% (23 of 111 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ru/
2022-09-21 07:39:29 +02:00
Anonymous 42baa4b188 Translated using Weblate (Portuguese)
Currently translated at 79.2% (88 of 111 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pt/
2022-09-21 07:39:28 +02:00
Anonymous 52eaddae37 Translated using Weblate (French)
Currently translated at 97.2% (108 of 111 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-21 07:39:28 +02:00
Anonymous f43ce0db44 Translated using Weblate (Spanish)
Currently translated at 97.2% (108 of 111 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-21 07:39:28 +02:00
Anonymous b46cb0a1f7 Translated using Weblate (German)
Currently translated at 93.6% (104 of 111 strings)

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

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ca/
2022-09-21 07:39:20 +02:00
Ben Phelps 0e6ea57023 Merge pull request #229 from DevPGSV/feature/widget_mastodon
Add Mastodon widget
2022-09-21 08:39:16 +03:00
Jason Fischer 280bb5fc81 Add Winston for log handling
- write log data to 'homepage.log'
2022-09-20 20:16:04 -07:00
Pablo Garcia de los Salmones Valencia 077d21eb7e Updated stat literals to single words 2022-09-21 02:15:25 +02:00
Pablo Garcia de los Salmones Valencia f281d86e8a Fixed missing text 2022-09-20 05:29:24 +02:00
Pablo Garcia de los Salmones Valencia f7000a280e Add Syncthing Relay Server widget 2022-09-20 04:42:04 +02:00
Pablo Garcia de los Salmones Valencia 586ded6b3f Add Mastodon widget 2022-09-20 03:41:10 +02:00
3vilson 539e0f005a Translated using Weblate (German)
Currently translated at 96.2% (104 of 108 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/de/
2022-09-19 19:16:41 +02:00
Ben Phelps 330575bab3 fix links 2022-09-18 18:49:50 +03:00
Ben Phelps 30ec4aed28 Translated using Weblate (Dutch)
Currently translated at 58.3% (63 of 108 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nl/
2022-09-18 16:53:21 +02:00
Ben Phelps ee456fd8e5 Translated using Weblate (Italian)
Currently translated at 71.2% (77 of 108 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/it/
2022-09-18 16:53:21 +02:00
Ben Phelps ed25c8a84b Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (108 of 108 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-09-18 16:53:21 +02:00
Ben Phelps b0a45fe09c Translated using Weblate (Russian)
Currently translated at 21.2% (23 of 108 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ru/
2022-09-18 16:53:20 +02:00
Nonoss117 50f0f46ad9 Translated using Weblate (French)
Currently translated at 100.0% (108 of 108 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-18 16:53:20 +02:00
Ben Phelps bd61d459ad Translated using Weblate (Spanish)
Currently translated at 100.0% (108 of 108 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-18 16:53:20 +02:00
Ben Phelps 4f73c60d37 Translated using Weblate (German)
Currently translated at 62.0% (67 of 108 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/de/
2022-09-18 16:53:20 +02:00
Ben Phelps 7c536f0cb0 update attribution sorting 2022-09-18 17:05:31 +03:00
Ben Phelps d5a489198a update readme 2022-09-18 17:00:40 +03:00
Ben Phelps 17f54da524 yaml validation 2022-09-18 16:41:01 +03:00
ShlomiPorush b5065673ab Translated using Weblate (Hebrew)
Currently translated at 93.5% (101 of 108 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/he/
2022-09-18 15:13:10 +02:00
Pacux 610b0f63e0 Translated using Weblate (Catalan)
Currently translated at 100.0% (108 of 108 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ca/
2022-09-18 15:13:09 +02:00
yahoo~~ 73317bda67 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (108 of 108 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-09-18 15:13:09 +02:00
Nonoss117 f690f3acba Translated using Weblate (French)
Currently translated at 100.0% (108 of 108 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-18 15:13:09 +02:00
Anonymous eea9f1f6cb Translated using Weblate (Hebrew)
Currently translated at 100.0% (0 of 0 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/he/
2022-09-18 08:37:27 +02:00
Daniel Varga d9089e8d1c Translated using Weblate (Hungarian)
Currently translated at 100.0% (108 of 108 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/hu/
2022-09-18 08:37:26 +02:00
Nonoss117 bed5acc9d5 Translated using Weblate (French)
Currently translated at 100.0% (108 of 108 strings)

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

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-18 08:37:25 +02:00
Daniel Varga d46a98c7d5 Translated using Weblate (German)
Currently translated at 62.0% (67 of 108 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/de/
2022-09-18 08:37:25 +02:00
ShlomiPorush 05af60df4f Added translation using Weblate (Hebrew) 2022-09-18 08:37:17 +02:00
Ben Phelps 5fc266ed81 better status icon logic for tautulli 2022-09-18 02:12:24 +03:00
Ben Phelps 38356c31b0 handle when structure differs from expectation 2022-09-18 01:38:41 +03:00
Ben Phelps 2703cfb81e update attributions and features 2022-09-17 22:03:21 +03:00
Ben Phelps 8a226ca473 Merge pull request #198 from JazzFisch/fix-incorrect-widget-values
Fix issues with incorrect values in widgets
2022-09-17 19:21:26 +03:00
Jason Fischer 33e6d54fd2 Fix issues with incorrect values in widgets
associated: #180
associated: #194
2022-09-17 09:17:03 -07:00
Ben Phelps d36f37a4ed remove as it’s causing troubles 2022-09-17 17:23:45 +03:00
Ben Phelps f3ebbb6547 pass errors 2022-09-17 16:55:18 +03:00
Ben Phelps 28b2f79e5b use aggregate mapped data
to reduce the size of the API responses
2022-09-17 13:05:44 +03:00
Anonymous 9a77115a30 Translated using Weblate (Hungarian)
Currently translated at 100.0% (0 of 0 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/hu/
2022-09-17 09:25:24 +02:00
Daniel Varga 2d899e364d Added translation using Weblate (Hungarian) 2022-09-17 09:25:17 +02:00
Anonymous 32b881891c Translated using Weblate (Croatian)
Currently translated at 8.3% (9 of 108 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/hr/
2022-09-17 09:06:55 +02:00
Anonymous 9eefc07c7c Translated using Weblate (Swedish)
Currently translated at 88.8% (96 of 108 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/sv/
2022-09-17 09:06:55 +02:00
Anonymous 792accffb6 Translated using Weblate (Polish)
Currently translated at 82.4% (89 of 108 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pl/
2022-09-17 09:06:54 +02:00
Anonymous 03af88aba5 Translated using Weblate (Catalan)
Currently translated at 92.5% (100 of 108 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ca/
2022-09-17 09:06:54 +02:00
Anonymous f56b6b4ad0 Translated using Weblate (Chinese (Traditional))
Currently translated at 8.3% (9 of 108 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hant/
2022-09-17 09:06:54 +02:00
Anonymous 4ce1681e79 Translated using Weblate (Dutch)
Currently translated at 57.4% (62 of 108 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nl/
2022-09-17 09:06:54 +02:00
Anonymous 7570fa71f0 Translated using Weblate (Vietnamese)
Currently translated at 40.7% (44 of 108 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/vi/
2022-09-17 09:06:53 +02:00
Anonymous 8a61c76cd9 Translated using Weblate (Norwegian Bokmål)
Currently translated at 73.1% (79 of 108 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nb_NO/
2022-09-17 09:06:53 +02:00
Anonymous fbf5381699 Translated using Weblate (Italian)
Currently translated at 63.8% (69 of 108 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/it/
2022-09-17 09:06:53 +02:00
Anonymous ff77f0db4f Translated using Weblate (Chinese (Simplified))
Currently translated at 76.8% (83 of 108 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-09-17 09:06:52 +02:00
Anonymous 2e30abedc9 Translated using Weblate (Russian)
Currently translated at 19.4% (21 of 108 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ru/
2022-09-17 09:06:52 +02:00
Anonymous c4cb4f7475 Translated using Weblate (Portuguese)
Currently translated at 81.4% (88 of 108 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pt/
2022-09-17 09:06:52 +02:00
Anonymous 7432bb813e Translated using Weblate (French)
Currently translated at 96.2% (104 of 108 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-17 09:06:52 +02:00
Anonymous 572a104779 Translated using Weblate (Spanish)
Currently translated at 96.2% (104 of 108 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-17 09:06:51 +02:00
Anonymous f77dc23d92 Translated using Weblate (German)
Currently translated at 59.2% (64 of 108 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/de/
2022-09-17 09:06:51 +02:00
nicedc e92fc74dd3 Translated using Weblate (Chinese (Simplified))
Currently translated at 79.8% (83 of 104 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-09-17 09:06:42 +02:00
Nonoss117 9479c3d5c3 Translated using Weblate (French)
Currently translated at 100.0% (104 of 104 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-17 09:06:42 +02:00
Ángel Fernández Sánchez cfc37a64e1 Translated using Weblate (Spanish)
Currently translated at 100.0% (104 of 104 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-17 09:06:42 +02:00
Ben Phelps 2d5294804c Merge pull request #192 from JazzFisch/add-qbittorrent
Add qBittorrent Widget
2022-09-17 10:06:38 +03:00
Jason Fischer 6c01a85077 Merge branch 'main' into add-qbittorrent 2022-09-16 23:19:24 -07:00
Ben Phelps cf41e988eb fix error with no map 2022-09-17 08:38:53 +03:00
Ben Phelps d7a161c088 remove map for now 2022-09-17 08:34:32 +03:00
Ben Phelps 379c4040fe Merge branch 'JazzFisch-proxy-with-mapping' 2022-09-17 08:32:48 +03:00
Ben Phelps 3f17618ad5 allow endpoint specific maps 2022-09-17 08:32:40 +03:00
Andy d7be64c3d9 add backgroundOpacity option 2022-09-17 08:24:12 +03:00
Juan Manuel Bennàssar Carretero ef7737e9be Translated using Weblate (Spanish)
Currently translated at 100.0% (104 of 104 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-17 08:24:12 +03:00
Ben Phelps 51ad3184b6 Merge pull request #183 from andrii-kryvoviaz/add-background-image-opacity
Add backgroundOpacity option
2022-09-17 07:33:35 +03:00
Jason Fischer efc8fd878a Merge branch 'main' into add-qbittorrent 2022-09-16 19:12:41 -07:00
Jason Fischer 6da1e98c83 Add qBittorrent Widget
- extract cookie jar functionality into its own file
- use i18n for more strings in existing widgets

completes: #152
associated: #123
2022-09-16 19:11:57 -07:00
Juan Manuel Bennàssar Carretero 513a06740c Translated using Weblate (Spanish)
Currently translated at 100.0% (104 of 104 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-16 21:23:35 +02:00
Jason Fischer 743a070724 Proposal to add ability to map data in a proxy 2022-09-16 11:33:11 -07:00
Andy 5fb0e76669 add backgroundOpacity option 2022-09-16 15:31:13 +03:00
Anonymous bedeab686e Translated using Weblate (Croatian)
Currently translated at 100.0% (0 of 0 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/hr/
2022-09-16 14:31:03 +02:00
Nonoss117 9d9fa352ce Translated using Weblate (French)
Currently translated at 100.0% (104 of 104 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-16 14:31:02 +02:00
sheep 1bfa6ce862 Added translation using Weblate (Croatian) 2022-09-16 14:30:39 +02:00
Ben Phelps 755b29c859 update readme 2022-09-16 14:18:27 +03:00
Anonymous aab5b0247a Translated using Weblate (Swedish)
Currently translated at 92.3% (96 of 104 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/sv/
2022-09-16 13:06:38 +02:00
Anonymous d7e4b0bd17 Translated using Weblate (Polish)
Currently translated at 85.5% (89 of 104 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pl/
2022-09-16 13:06:38 +02:00
Anonymous 3bacdadb80 Translated using Weblate (Catalan)
Currently translated at 96.1% (100 of 104 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ca/
2022-09-16 13:06:38 +02:00
Anonymous 1d75ee44ed Translated using Weblate (Chinese (Traditional))
Currently translated at 8.6% (9 of 104 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hant/
2022-09-16 13:06:37 +02:00
Anonymous 230cc343af Translated using Weblate (Dutch)
Currently translated at 59.6% (62 of 104 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nl/
2022-09-16 13:06:37 +02:00
Anonymous b318ee165c Translated using Weblate (Vietnamese)
Currently translated at 42.3% (44 of 104 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/vi/
2022-09-16 13:06:37 +02:00
Anonymous f677646365 Translated using Weblate (Norwegian Bokmål)
Currently translated at 75.9% (79 of 104 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nb_NO/
2022-09-16 13:06:36 +02:00
Anonymous 8db7d820d7 Translated using Weblate (Italian)
Currently translated at 66.3% (69 of 104 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/it/
2022-09-16 13:06:36 +02:00
Anonymous adb0632566 Translated using Weblate (Chinese (Simplified))
Currently translated at 79.8% (83 of 104 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-09-16 13:06:36 +02:00
Anonymous 4d5c8db333 Translated using Weblate (Russian)
Currently translated at 20.1% (21 of 104 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ru/
2022-09-16 13:06:35 +02:00
Anonymous 01d6a3d5f8 Translated using Weblate (Portuguese)
Currently translated at 84.6% (88 of 104 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pt/
2022-09-16 13:06:35 +02:00
Anonymous fbeadbc32f Translated using Weblate (French)
Currently translated at 96.1% (100 of 104 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-16 13:06:35 +02:00
Anonymous 1289be888f Translated using Weblate (Spanish)
Currently translated at 96.1% (100 of 104 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-16 13:06:35 +02:00
Anonymous aa7e3a955c Translated using Weblate (German)
Currently translated at 61.5% (64 of 104 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/de/
2022-09-16 13:06:34 +02:00
SuperDOS 2823f3b921 Translated using Weblate (Swedish)
Currently translated at 96.0% (96 of 100 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/sv/
2022-09-16 13:06:26 +02:00
Ben Phelps ddb2a74540 add AdGuard widget 2022-09-16 14:05:56 +03:00
Ben Phelps 37d8d7a2f8 fix indentation 2022-09-16 14:05:43 +03:00
Ben Phelps 578b715a1f allow HTTP basic auth on generic proxy 2022-09-16 14:05:27 +03:00
Anonymous f14a811ce9 Translated using Weblate (Swedish)
Currently translated at 100.0% (0 of 0 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/sv/
2022-09-16 11:02:49 +02:00
SuperDOS 06dd6d2213 Added translation using Weblate (Swedish) 2022-09-16 11:02:42 +02:00
Pacux 72471c47f4 Translated using Weblate (Catalan)
Currently translated at 100.0% (100 of 100 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ca/
2022-09-16 10:56:30 +02:00
Nonoss117 aec5f7173c Translated using Weblate (French)
Currently translated at 100.0% (100 of 100 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-16 10:56:29 +02:00
Ángel Fernández Sánchez f7b68789ac Translated using Weblate (Spanish)
Currently translated at 100.0% (100 of 100 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-16 10:56:29 +02:00
Ángel Fernández Sánchez 0672da621e Translated using Weblate (Spanish)
Currently translated at 100.0% (100 of 100 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-16 10:40:37 +02:00
Juan Manuel Bennàssar Carretero a7f9b78533 Translated using Weblate (Spanish)
Currently translated at 100.0% (100 of 100 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-16 10:40:36 +02:00
Ben Phelps 0075429e08 add greeting and datetime info widgets 2022-09-16 10:53:12 +03:00
Ben Phelps 43f7ccd166 update readme attributions 2022-09-16 10:49:20 +03:00
Ángel Fernández Sánchez 8c64e0f288 Translated using Weblate (Spanish)
Currently translated at 100.0% (100 of 100 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-16 09:10:38 +02:00
Juan Manuel Bennàssar Carretero c91a387833 Translated using Weblate (Spanish)
Currently translated at 100.0% (100 of 100 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-16 09:10:38 +02:00
Ben Phelps 93d5dd88ba add options for layout, theme and color settings 2022-09-15 19:58:41 +03:00
Ben Phelps 05427253b9 tweak streaming widget spacings 2022-09-15 19:53:48 +03:00
Ben Phelps e2bc541089 show transcoding info on streaming widgets 2022-09-15 19:48:23 +03:00
Kamil Ganczarek 9a959bab16 Translated using Weblate (Polish)
Currently translated at 89.0% (89 of 100 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pl/
2022-09-15 11:30:42 +02:00
Anonymous 45ca4a15f7 Translated using Weblate (Polish)
Currently translated at 100.0% (0 of 0 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pl/
2022-09-15 11:01:12 +02:00
Kamil Ganczarek ddd2ff53ff Added translation using Weblate (Polish) 2022-09-15 11:01:05 +02:00
Nonoss117 5c3266b48f Translated using Weblate (French)
Currently translated at 100.0% (100 of 100 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-15 10:58:49 +02:00
Ben Phelps 0da6db9d9f update readme with supported integrations 2022-09-15 09:00:40 +03:00
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
Ben Phelps e3237b9022 fix text alignment 2022-09-10 21:43:14 +03:00
Ben Phelps 3882dd4f5a fix cases where configurations are empty 2022-09-09 22:01:01 +03:00
Ben Phelps d66326b41d implement docker service discovery via labels 2022-09-09 21:53:05 +03:00
Anonymous ef1c5dbcc9 Translated using Weblate (Dutch)
Currently translated at 100.0% (0 of 0 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nl/
2022-09-09 14:59:59 +02:00
Allan Nordhøy c45c8e93de Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (64 of 64 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nb_NO/
2022-09-09 14:59:58 +02:00
Francisco Coelho 1e93bf3ec4 Translated using Weblate (Portuguese)
Currently translated at 32.8% (21 of 64 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pt/
2022-09-09 14:59:58 +02:00
Bernhard Großer 4bff209bd7 Translated using Weblate (German)
Currently translated at 100.0% (64 of 64 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/de/
2022-09-09 14:59:58 +02:00
Ben Phelps e5ee937c38 Added translation using Weblate (Dutch) 2022-09-09 14:59:54 +02:00
Ben Phelps c418efe007 fix fallback to / in disk resource widget 2022-09-09 15:27:42 +03:00
Ben Phelps 5677254b46 add new weather feature to readme 2022-09-09 13:07:20 +03:00
Ben Phelps cb76a8165d pre-create settings.yaml for build process 2022-09-09 13:07:09 +03:00
Ben Phelps a7a1eca0cd attempt to fix weird race condition in builds? 2022-09-09 12:59:43 +03:00
Ben Phelps 85bc078c46 always attempt location fetch
if it fails, then we just fallback to user interaction
2022-09-09 12:57:15 +03:00
Anonymous 5c347d9427 Translated using Weblate (Vietnamese)
Currently translated at 50.0% (32 of 64 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/vi/
2022-09-09 11:44:50 +02:00
Anonymous 6c17efc2ab Translated using Weblate (Norwegian Bokmål)
Currently translated at 93.7% (60 of 64 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nb_NO/
2022-09-09 11:44:50 +02:00
Anonymous a6e929ba86 Translated using Weblate (Italian)
Currently translated at 14.0% (9 of 64 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/it/
2022-09-09 11:44:50 +02:00
Anonymous f9886f7c63 Translated using Weblate (Chinese (Simplified))
Currently translated at 93.7% (60 of 64 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-09-09 11:44:49 +02:00
Anonymous ec937f6212 Translated using Weblate (Russian)
Currently translated at 14.0% (9 of 64 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ru/
2022-09-09 11:44:49 +02:00
Anonymous b7427c3409 Translated using Weblate (Portuguese)
Currently translated at 14.0% (9 of 64 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pt/
2022-09-09 11:44:49 +02:00
Anonymous 5bd9cf46ea Translated using Weblate (French)
Currently translated at 14.0% (9 of 64 strings)

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

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-09 11:44:48 +02:00
Anonymous 27c5b4227d Translated using Weblate (German)
Currently translated at 93.7% (60 of 64 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/de/
2022-09-09 11:44:48 +02:00
Trung Le 914e869778 Translated using Weblate (Vietnamese)
Currently translated at 53.3% (32 of 60 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/vi/
2022-09-09 11:44:42 +02:00
Ben Phelps e4ea30becc implement weather geolocation 2022-09-09 12:44:34 +03:00
Ben Phelps 61f91f0e45 remove logging 2022-09-09 11:51:36 +03:00
Ben Phelps c6d8668e69 fix jellyfin integration 2022-09-09 11:42:08 +03:00
Anonymous 3c73b000df Translated using Weblate (Vietnamese)
Currently translated at 100.0% (0 of 0 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/vi/
2022-09-09 08:11:36 +02:00
Trung Le ed5a5ae86f Added translation using Weblate (Vietnamese) 2022-09-09 08:11:33 +02:00
Ben Phelps 036fbb0f49 fix build 2022-09-09 08:27:25 +03:00
Ben Phelps 13779c5618 update skeleton files 2022-09-09 07:10:45 +03:00
Ben Phelps 6802fd0c1d update readme 2022-09-09 07:10:39 +03:00
Ben Phelps 7b523501ad fix linting 2022-09-09 07:10:33 +03:00
Ben Phelps 0c8bbdf02b background images, document title 2022-09-09 06:45:43 +03:00
Ben Phelps 0b43f83daa cleanup bitrate/byterate i18n formatter 2022-09-08 20:17:58 +03:00
Ben Phelps 0f2f552e87 normalize resource widget styling 2022-09-08 20:17:39 +03:00
Ben Phelps b3bedc7c31 update readme, contributors and formatting 2022-09-08 18:57:27 +03:00
Ben Phelps 32a2a3f484 Translated using Weblate (French)
Currently translated at 0.0% (0 of 60 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-09-08 17:29:15 +02:00
Ángel Fernández Sánchez 27de7d1c84 Translated using Weblate (Spanish)
Currently translated at 100.0% (60 of 60 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-09-08 17:28:22 +02:00
Ben Phelps c31c2a4c84 Translated using Weblate (Portuguese)
Currently translated at 0.0% (0 of 60 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pt/
2022-09-08 13:41:33 +02:00
Ben Phelps 9fb88eb325 fix to BCP style language folder 2022-09-08 14:41:26 +03:00
Ben Phelps 7cdc2fa89a add Norwegian attribution to readme 2022-09-08 14:19:22 +03:00
Allan Nordhøy c06dbddcea Translated using Weblate (Norwegian Bokmål)
Currently translated at 91.6% (55 of 60 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nb_NO/
2022-09-08 13:13:17 +02:00
Ben Phelps a4b17d9a8f remove readonly i18n keys from other languages 2022-09-08 14:00:53 +03:00
Anonymous 7ffea76b9e Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (0 of 0 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nb_NO/
2022-09-08 12:55:38 +02:00
Allan Nordhøy 6a6db91dc9 Added translation using Weblate (Norwegian Bokmål) 2022-09-08 12:55:24 +02:00
Anonymous 9078fd2302 Translated using Weblate (Italian)
Currently translated at 15.0% (9 of 60 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/it/
2022-09-08 12:36:08 +02:00
Ben Phelps 39786c5dd4 Translated using Weblate (Russian)
Currently translated at 0.0% (0 of 60 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ru/
2022-09-08 12:36:07 +02:00
Ben Phelps 7c35a88483 Merge branch 'main' of github.com:benphelps/homepage 2022-09-08 13:20:38 +03:00
Ben Phelps 1b885cb189 fix incorrect i18n keys 2022-09-08 13:19:49 +03:00
Ben Phelps 183bbbe6e7 Added translation using Weblate (Italian) 2022-09-08 12:13:31 +02:00
Ben Phelps 562318f2d3 Deleted translation using Weblate (Italian) 2022-09-08 12:13:09 +02:00
Ben Phelps 797401a7de Added translation using Weblate (Italian) 2022-09-08 12:08:29 +02:00
Ben Phelps d4dbfebe72 fix english 2022-09-08 12:54:05 +03:00
Ben Phelps 575d233078 add translation info to readme 2022-09-08 12:52:28 +03:00
Ben Phelps 04a78c07a7 use ellipsis 2022-09-08 12:52:04 +03:00
Ben Phelps 59856bc753 Translated using Weblate (English)
Currently translated at 100.0% (60 of 60 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/en/
2022-09-08 11:48:12 +02:00
Ben Phelps a8e7a5f912 add machine translated locales for
de, es, fr, pt, ru and zh-CN
2022-09-08 11:48:59 +03:00
Ben Phelps c08d4b7b9c implement i18n 2022-09-08 11:48:16 +03:00
Ben Phelps d25148c8ae revert to array key indexes, it’s the only option 2022-09-08 11:47:21 +03:00
Ben Phelps b0e640fd03 remove unused component 2022-09-08 11:45:59 +03:00
Ben Phelps 7ae07cb1ee add Tautulli to list of services 2022-09-08 08:29:08 +03:00
Ben Phelps c0b34faa79 Delete linter.yml 2022-09-07 17:47:19 +03:00
Ben Phelps 81e3b0bd1e cleanup resource widget design 2022-09-07 17:17:01 +03:00
Ben Phelps f74e8b9d32 linting and cleanup 2022-09-07 16:53:24 +03:00
Ben Phelps 7f041e8303 Update linter.yml 2022-09-07 16:23:18 +03:00
Ben Phelps db05a66f3a Create linter.yml 2022-09-07 16:18:52 +03:00
Ben Phelps efc6e86991 add build attribution to readme 2022-09-07 12:29:02 +03:00
Ben Phelps 1af867d4b3 forcefully disable next-swc 2022-09-07 12:14:06 +03:00
Ben Phelps 43da60595c add @swc/core & cli packages
continued attempts to solve the SWC build step on arm platforms
2022-09-07 12:02:34 +03:00
Ben Phelps c2849744b8 revert back to alpine 2022-09-07 12:01:53 +03:00
Ben Phelps 9bef2a8fcb trial run on non-alpine images
this is to test and see the size differences and if it’ll fix the SWC errors given by Next
2022-09-07 11:52:17 +03:00
Ben Phelps 6ec73362f2 fix docker, s/latest/current/ 2022-09-07 11:32:54 +03:00
Ben Phelps f5bef651d8 bump node:alpine to latest 2022-09-07 11:31:31 +03:00
Ben Phelps aa77a274c3 attempt to skip cache restore step 2022-09-07 11:22:12 +03:00
Ben Phelps 4e90bd8e46 remove apk cache as it seems to have issues 2022-09-07 11:10:56 +03:00
Ben Phelps 6a238948c2 Merge pull request #62 from modem7/dockerfile-optimise
Update Dockerfile
2022-09-07 11:09:56 +03:00
Alex 8e2bebcfd9 Updated EXPOSE to variable 2022-09-06 21:41:53 +01:00
Alex b023801fe4 Update Dockerfile to include cache mounts + update healthcheck to new endpoint 2022-09-06 21:39:31 +01:00
modem7 516b812b2b Merge branch 'benphelps:main' into dockerfile-optimise 2022-09-06 21:25:19 +01:00
Ben Phelps 8373057758 add simple healthcheck endpoint 2022-09-06 09:13:48 +03:00
Ben Phelps 939f5d7c20 fix path.join import 2022-09-06 08:39:25 +03:00
Alex 36d1a9c738 Update healthcheck to work with PORT variable 2022-09-05 22:55:14 +01:00
Alex fb845c3e03 Update Dockerfile and add healthcheck 2022-09-05 22:17:58 +01:00
Alex 78c52861c7 Update Dockerfile for --link 2022-09-05 19:44:58 +01:00
Ben Phelps cf39395924 add documentation link 2022-09-05 20:20:34 +03:00
Ben Phelps 6061d9ec65 cleanup imports 2022-09-05 20:19:08 +03:00
Ben Phelps 5a8defb478 allow weather apis to use hidden api keys 2022-09-05 20:14:14 +03:00
Alex a265038bf6 Update Dockerfile 2022-09-05 16:42:10 +01:00
Ben Phelps 08afa0b747 check that content type exists before setting it 2022-09-05 10:08:02 +03:00
140 changed files with 7223 additions and 1048 deletions

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
{
"presets": ["next/babel"]
}
+17 -1
View File
@@ -1,3 +1,19 @@
{
"extends": "next/core-web-vitals"
"extends": ["airbnb", "next/core-web-vitals", "prettier"],
"plugins": ["prettier"],
"rules": {
"import/order": [
"error",
{
"newlines-between": "always"
}
]
},
"settings": {
"import/resolver": {
"node": {
"paths": ["src"]
}
}
}
}
+3
View File
@@ -0,0 +1,3 @@
github: benphelps
ko_fi: benphelps
custom: ["https://paypal.me/phelpsben"]
+30
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
+17
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.
+14 -8
View File
@@ -54,13 +54,15 @@ jobs:
- name: Setup Docker buildx
uses: docker/setup-buildx-action@v2
- name: Cache Docker layers
uses: actions/cache@v3
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
# This step is being disabled because the runner is on a self-hosted machine
# where the cache will stick between runs.
# - name: Cache Docker layers
# uses: actions/cache@v3
# with:
# path: /tmp/.buildx-cache
# key: ${{ runner.os }}-buildx-${{ github.sha }}
# restore-keys: |
# ${{ runner.os }}-buildx-
# Login against a Docker registry except on PR
# https://github.com/docker/login-action
@@ -92,9 +94,13 @@ jobs:
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
BUILDTIME=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}
VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}
REVISION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }}
# https://github.com/docker/setup-qemu-action#about
# platforms: linux/amd64,linux/arm64,linux/riscv64,linux/ppc64le,linux/s390x,linux/386,linux/mips64le,linux/mips64,linux/arm/v7,linux/arm/v6
platforms: linux/amd64,linux/arm64,linux/arm/v6,linux/arm/v7
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
+4
View File
@@ -19,6 +19,10 @@
.DS_Store
*.pem
# log files
error.log
homepage.log
# debug
npm-debug.log*
yarn-debug.log*
+19
View File
@@ -0,0 +1,19 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Next.js: debug full stack",
"type": "node",
"request": "launch",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/next",
"serverReadyAction": {
"pattern": "started server on .+, url: (https?://.+)",
"uriFormat": "%s",
"action": "debugWithChrome"
}
}
]
}
+128
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
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)
+55 -27
View File
@@ -1,4 +1,43 @@
FROM node:16-alpine AS base
# syntax = docker/dockerfile:latest
# Install dependencies only when needed
FROM node:current-alpine AS deps
WORKDIR /app
COPY --link package.json pnpm-lock.yaml* ./
RUN <<EOF
set -xe
apk add libc6-compat
apk add --virtual .gyp python3 make g++
yarn global add pnpm
EOF
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store pnpm fetch | grep -v "cross-device link not permitted\|Falling back to copying packages from store"
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store pnpm install -r --offline
# Rebuild the source code only when needed
FROM node:current-alpine AS builder
WORKDIR /app
ARG BUILDTIME
ARG VERSION
ARG REVISION
COPY --link --from=deps /app/node_modules ./node_modules/
COPY . .
RUN <<EOF
set -xe
yarn next telemetry disable
mkdir config && echo '-' > config/settings.yaml
NEXT_PUBLIC_BUILDTIME=$BUILDTIME NEXT_PUBLIC_VERSION=$VERSION NEXT_PUBLIC_REVISION=$REVISION npm run build
EOF
# Production image, copy all the files and run next
FROM node:current-alpine AS runner
LABEL org.opencontainers.image.title "Homepage"
LABEL org.opencontainers.image.description "A self-hosted services landing page, with docker and service integrations."
LABEL org.opencontainers.image.url="https://github.com/benphelps/homepage"
@@ -6,33 +45,22 @@ LABEL org.opencontainers.image.documentation='https://github.com/benphelps/homep
LABEL org.opencontainers.image.source='https://github.com/benphelps/homepage'
LABEL org.opencontainers.image.licenses='Apache-2.0'
# Install dependencies only when needed
FROM node:16-alpine AS deps
RUN apk add --no-cache libc6-compat
RUN apk add --no-cache --virtual .gyp python3 make g++
WORKDIR /app
COPY package.json pnpm-lock.yaml* ./
RUN yarn global add pnpm
RUN pnpm install
RUN apk del .gyp
# Rebuild the source code only when needed
FROM node:16-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
# Production image, copy all the files and run next
FROM node:16-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
EXPOSE 3000
WORKDIR /app
# Copy files from context (this allows the files to copy before the builder stage is done).
COPY --link package.json next.config.js ./
COPY --link /public ./public
# Copy files from builder
COPY --link --from=builder /app/.next/standalone ./
COPY --link --from=builder /app/.next/static/ ./.next/static/
ENV PORT 3000
EXPOSE $PORT
HEALTHCHECK --interval=10s --timeout=3s --start-period=20s \
CMD wget --no-verbose --tries=1 --spider --no-check-certificate http://localhost:$PORT/api/healthcheck || exit 1
CMD ["node", "server.js"]
+78 -24
View File
@@ -1,22 +1,42 @@
![Homepage Preview](/images/preview.png)
[![Docker](https://github.com/benphelps/homepage/actions/workflows/docker-publish.yml/badge.svg)](https://github.com/benphelps/homepage/actions/workflows/docker-publish.yml)
<p align="center">
<a href="https://discord.gg/k4ruYNrudu"><img src="https://img.shields.io/badge/Discord - Chat-blue?logo=discord&logoColor=white" /></a>
<a href="https://paypal.me/phelpsben" title="Donate"><img src="https://img.shields.io/badge/PayPal - Donate-blue?logo=paypal&logoColor=white" alt="Linkedin - phelpsben"></a>
</p>
<p align="center">
<a href="https://github.com/benphelps/homepage/actions/workflows/docker-publish.yml"><img src="https://github.com/benphelps/homepage/actions/workflows/docker-publish.yml/badge.svg" alt="Docker"></a>
<a href="https://hosted.weblate.org/engage/homepage/"><img src="https://hosted.weblate.org/widgets/homepage/-/homepage/svg-badge.svg" alt="Weblate"></a>
</p>
## Features
* Web Bookmarks
* Service Bookmarks
- Docker Integration
- Status light + CPU, Memory & Network Reporting *(click on the status light)*
- Service Integration
- Currently supports Sonarr, Radarr, Ombi, Emby, Jellyfin, Jellyseerr ([by ilusi0n](https://github.com/benphelps/homepage/pull/34)), NZBGet, ruTorrent
- Portainer, Traefik, Speedtest Tracker, PiHole, Nginx Proxy Manager ([by aidenpwnz](https://github.com/benphelps/homepage/pull/45))
* Homepage Widgets
- System Stats (Disk, CPU, Memory)
- Weather via WeatherAPI.com or OpenWeatherMap ([by AlexFullmoon](https://github.com/benphelps/homepage/pull/25))
- Search Bar ([by aidenpwnz](https://github.com/benphelps/homepage/pull/45))
* Customizable
- 21 theme colors with light and dark mode support
- Fast! The entire site is statically generated at build time, so you can expect instant load times
- Images built for AMD64 (x86_64), ARM64, ARMv7 and ARMv6
- Supports all Raspberry Pi's, most SBCs & Apple Silicon
- Full i18n support with automatic language detection
- Translations for Chinese, Dutch, French, German, Hebrew, Hungarian, Norwegian Bokmål, Polish, Portuguese, Russian, Spanish and Swedish
- Want to help translate? [Join the Weblate project](https://hosted.weblate.org/engage/homepage/)
- Service & Web Bookmarks
- Docker Integration
- Container status (Running / Stopped) & statistics (CPU, Memory, Network)
- Automatic service discovery (via labels)
- Service Integration
- Sonarr, Radarr, Readarr, Prowlarr, Bazarr, Lidarr, Emby, Jellyfin, Tautulli (Plex)
- Ombi, Overseerr, Jellyseerr, Jackett, NZBGet, SABnzbd, ruTorrent, Transmission, qBittorrent
- Portainer, Traefik, Speedtest Tracker, PiHole, AdGuard Home, Nginx Proxy Manager, Gotify, Syncthing Relay Server
- Information Providers
- Coin Market Cap, Mastodon
- Information & Utility Widgets
- System Stats (Disk, CPU, Memory)
- Weather via WeatherAPI.com or OpenWeatherMap
- 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
@@ -33,16 +53,16 @@ For configuration options, examples and more, [please check out the Wiki](https:
Using docker compose:
```yaml
version: '3.3'
version: "3.3"
services:
homepage:
image: ghcr.io/benphelps/homepage:latest
container_name: homepage
ports:
- 3000:3000
volumes:
- /path/to/config:/app/config
- /var/run/docker.sock:/var/run/docker.sock # (optional) For docker integrations
homepage:
image: ghcr.io/benphelps/homepage:latest
container_name: homepage
ports:
- 3000:3000
volumes:
- /path/to/config:/app/config # Make sure your local config directory exists
- /var/run/docker.sock:/var/run/docker.sock # (optional) For docker integrations
```
or docker run:
@@ -66,6 +86,8 @@ pnpm install
pnpm build
```
If this is your first time starting, copy the `src/skeleton` directory to `config/` to populate initial example config files.
Finally, run the server:
```bash
@@ -76,9 +98,11 @@ pnpm start
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.
You may also check [the wiki](https://github.com/benphelps/homepage/wiki) for detailed configuration instructions, examples and more.
## Development
Install NPM packages, this project uses [pnpm](https://pnpm.io/) (and so should you!):
@@ -96,3 +120,33 @@ pnpm dev
Open [http://localhost:3000](http://localhost:3000) to start.
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
- [andrii-kryvoviaz](https://github.com/benphelps/homepage/commits?author=andrii-kryvoviaz) - Background opacity option
- [boerniee](https://github.com/benphelps/homepage/commits?author=boerniee) - German Translation
- [comradekingu](https://github.com/benphelps/homepage/commits?author=comradekingu) - Norwegian Bokmål Translation
- Daniel Varga - German & Hungarian Translation
- [deffcolony](https://github.com/benphelps/homepage/commits?author=deffcolony) - Dutch Translation
- [desolaris](https://github.com/benphelps/homepage/commits?author=desolaris) - Russian Translation
- [DevPGSV](https://github.com/benphelps/homepage/commits?author=DevPGSV) - Syncthing Relay Server & Mastodon widgets
- [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 & qBittorrent Integrations
- [juanmanuelbc](https://github.com/benphelps/homepage/commits?author=juanmanuelbc) - Spanish and Catalan Translations
- [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
- [pacoculebras](https://github.com/benphelps/homepage/commits?author=pacoculebras) - Catalan Translation
- [psychodracon](https://github.com/benphelps/homepage/commits?author=psychodracon) - Polish 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
- [ShlomiPorush](https://github.com/benphelps/homepage/commits?author=ShlomiPorush) - Hebrew Translation
- [SuperDOS](https://github.com/benphelps/homepage/commits?author=SuperDOS) - Swedish Translation
- [xicopitz](https://github.com/benphelps/homepage/commits?author=xicopitz) - Gotify & Prowlarr Integration
+9
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

+1 -1
View File
@@ -3,9 +3,9 @@ const nextConfig = {
reactStrictMode: true,
output: "standalone",
swcMinify: false,
experimental: { images: { allowFutureImage: true, unoptimized: true } },
images: {
domains: ["cdn.jsdelivr.net"],
unoptimized: true,
},
};
+31 -10
View File
@@ -6,30 +6,51 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"telemetry": "next telemetry disable"
},
"dependencies": {
"@headlessui/react": "^1.6.6",
"@headlessui/react": "^1.7.0",
"@tailwindcss/forms": "^0.5.3",
"classnames": "^2.3.1",
"compare-versions": "^5.0.1",
"dockerode": "^3.3.4",
"follow-redirects": "^1.15.2",
"i18next": "^21.9.1",
"i18next-browser-languagedetector": "^6.1.5",
"i18next-http-backend": "^1.4.1",
"js-yaml": "^4.1.0",
"json-rpc-2.0": "^1.4.1",
"memory-cache": "^0.2.0",
"next": "12.2.5",
"next": "^12.3.0",
"node-os-utils": "^1.3.7",
"pretty-bytes": "^6.0.0",
"raw-body": "^2.5.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-i18next": "^11.18.6",
"react-icons": "^4.4.0",
"rutorrent-promise": "^2.0.0",
"swr": "^1.3.0"
"shvl": "^3.0.0",
"swr": "^1.3.0",
"tailwind-scrollbar": "^2.0.1",
"tough-cookie": "^4.1.2",
"winston": "^3.8.2"
},
"devDependencies": {
"autoprefixer": "^10.4.8",
"eslint": "8.22.0",
"eslint-config-next": "12.2.5",
"autoprefixer": "^10.4.9",
"eslint": "^8.23.1",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-next": "^12.3.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.6.1",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.31.8",
"eslint-plugin-react-hooks": "^4.6.0",
"postcss": "^8.4.16",
"prettier": "^2.7.1",
"tailwindcss": "^3.1.8",
"typescript": "^4.8.2"
"typescript": "^4.8.3"
}
}
+616 -172
View File
File diff suppressed because it is too large. Load diff
+172
View File
@@ -0,0 +1,172 @@
{
"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": "Gravity"
},
"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": "Configurat",
"errored": "Amb errors"
},
"bazarr": {
"missingEpisodes": "Episodis que falten",
"missingMovies": "Pel·lícules que falten"
},
"lidarr": {
"wanted": "Volgut",
"queued": "En cua",
"albums": "Àlbums"
},
"adguard": {
"queries": "Consultes",
"blocked": "Bloquejat",
"filtered": "Filtrat",
"latency": "Latència"
},
"qbittorrent": {
"download": "Descàrrega",
"upload": "Càrrega",
"leech": "Companys",
"seed": "Llavors"
},
"mastodon": {
"user_count": "Usuaris",
"status_count": "Publicacions",
"domain_count": "Dominis"
},
"strelaysrv": {
"numActiveSessions": "Sessions",
"numConnections": "Connexions",
"dataRelayed": "Transmès",
"transferRate": "Velocitat"
}
}
+172
View File
@@ -0,0 +1,172 @@
{
"widget": {
"missing_type": "Fehlender Widget-Typ: {{type}}",
"api_error": "API-Fehler",
"status": "Status"
},
"search": {
"placeholder": "Suche…"
},
"resources": {
"total": "Gesamt",
"free": "Frei",
"used": "Gebraucht",
"load": "Belastung"
},
"docker": {
"rx": "Rx",
"tx": "Tx",
"mem": "Mem",
"cpu": "Prozessor",
"offline": "Offline"
},
"emby": {
"playing": "Spielen",
"transcoding": "Transcodierung",
"bitrate": "Bitrate",
"no_active": "Keine aktive Streams"
},
"tautulli": {
"playing": "Spielen",
"transcoding": "Transcodierung",
"bitrate": "Bitrate",
"no_active": "Keine aktiven streamen"
},
"rutorrent": {
"active": "Aktiv",
"upload": "Hochladen",
"download": "Download"
},
"sonarr": {
"wanted": "Gesucht",
"queued": "In Warteschlange",
"series": "Serie"
},
"radarr": {
"wanted": "Gesucht",
"queued": "In Warteschlange",
"movies": "Filme"
},
"readarr": {
"wanted": "Gesucht",
"queued": "In Warteschlange",
"books": "Bücher"
},
"ombi": {
"pending": "Ausstehend",
"approved": "Genehmigt",
"available": "Verfügbar"
},
"jellyseerr": {
"pending": "Ausstehend",
"approved": "Genehmigt",
"available": "Verfügbar"
},
"pihole": {
"queries": "Abfragen",
"blocked": "Blockiert",
"gravity": "Gravity"
},
"speedtest": {
"upload": "Upload",
"download": "Download",
"ping": "Ping"
},
"portainer": {
"running": "Betrieb",
"stopped": "Gestoppt",
"total": "Gesamt"
},
"traefik": {
"routers": "Router",
"services": "Dienste",
"middleware": "Zwischenanwendung"
},
"npm": {
"enabled": "Aktiviert",
"disabled": "Deaktiviert",
"total": "Gesamt"
},
"weather": {
"current": "Aktueller Standort",
"allow": "Zum Zulassen anklicken",
"updating": "Aktualisieren",
"wait": "Bitte warten"
},
"overseerr": {
"pending": "Ausstehend",
"approved": "Genehmigt",
"available": "Verfügbar"
},
"sabnzbd": {
"rate": "Geschwindigkeit",
"queue": "Warteschlange",
"timeleft": "Verbleibende Zeit"
},
"nzbget": {
"rate": "Geschwindigkeit",
"remaining": "Verbleibend",
"downloaded": "Heruntergeladen"
},
"coinmarketcap": {
"configure": "Konfiguriere eine oder mehrere Kryptowährungen zur Verfolgung",
"1hour": "1 Stunde",
"1day": "1 Tag",
"7days": "7 Tage",
"30days": "30 Tage"
},
"gotify": {
"apps": "Programme",
"clients": "Benutzer",
"messages": "Nachrichten"
},
"prowlarr": {
"enableIndexers": "Indexer",
"numberOfGrabs": "Abrufungen",
"numberOfQueries": "Anfragen",
"numberOfFailGrabs": "Fehlgeschlagene Abrufungen",
"numberOfFailQueries": "Fehlgeschlagene Anfragen"
},
"transmission": {
"download": "Herunterladen",
"upload": "Hochladen",
"leech": "Leech",
"seed": "Seed"
},
"jackett": {
"configured": "Konfiguriert",
"errored": "Fehlerhaft"
},
"bazarr": {
"missingEpisodes": "Fehlende Episoden",
"missingMovies": "Fehlende Filme"
},
"lidarr": {
"wanted": "Gesucht",
"queued": "In Warteschlange",
"albums": "Alben"
},
"adguard": {
"queries": "Anfragen",
"blocked": "Blockiert",
"filtered": "Gefiltert",
"latency": "Latenz"
},
"qbittorrent": {
"download": "Herunterladen",
"upload": "Hochladen",
"leech": "Leech",
"seed": "Seed"
},
"mastodon": {
"user_count": "Nutzer",
"status_count": "Beiträge",
"domain_count": "Domänen"
},
"strelaysrv": {
"numActiveSessions": "Sitzungen",
"numConnections": "Verbindungen",
"dataRelayed": "Weitergeleitet",
"transferRate": "Bewerten"
}
}
+183
View File
@@ -0,0 +1,183 @@
{
"common": {
"bytes": "{{value, bytes}}",
"bits": "{{value, bytes(bits: true)}}",
"bbytes": "{{value, bytes(binary: true)}}",
"bbits": "{{value, bytes(bits: true, binary: true)}}",
"byterate": "{{value, rate}}",
"bitrate": "{{value, rate(bits: true)}}",
"percent": "{{value, percent}}",
"number": "{{value, number}}",
"ms": "{{value, number}}"
},
"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"
},
"search": {
"placeholder": "Search…"
},
"resources": {
"total": "Total",
"free": "Free",
"used": "Used",
"load": "Load"
},
"docker": {
"rx": "RX",
"tx": "TX",
"mem": "MEM",
"cpu": "CPU",
"offline": "Offline"
},
"emby": {
"playing": "Playing",
"transcoding": "Transcoding",
"bitrate": "Bitrate",
"no_active": "No Active Streams"
},
"tautulli": {
"playing": "Playing",
"transcoding": "Transcoding",
"bitrate": "Bitrate",
"no_active": "No Active Streams"
},
"nzbget": {
"rate": "Rate",
"remaining": "Remaining",
"downloaded": "Downloaded"
},
"sabnzbd": {
"rate": "Rate",
"queue": "Queue",
"timeleft": "Time Left"
},
"rutorrent": {
"active": "Active",
"upload": "Upload",
"download": "Download"
},
"transmission": {
"download": "Download",
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
},
"qbittorrent": {
"download": "Download",
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
},
"sonarr": {
"wanted": "Wanted",
"queued": "Queued",
"series": "Series"
},
"radarr": {
"wanted": "Wanted",
"queued": "Queued",
"movies": "Movies"
},
"lidarr": {
"wanted": "Wanted",
"queued": "Queued",
"albums": "Albums"
},
"readarr": {
"wanted": "Wanted",
"queued": "Queued",
"books": "Books"
},
"bazarr": {
"missingEpisodes": "Missing Episodes",
"missingMovies": "Missing Movies"
},
"ombi": {
"pending": "Pending",
"approved": "Approved",
"available": "Available"
},
"jellyseerr": {
"pending": "Pending",
"approved": "Approved",
"available": "Available"
},
"overseerr": {
"pending": "Pending",
"approved": "Approved",
"available": "Available"
},
"pihole": {
"queries": "Queries",
"blocked": "Blocked",
"gravity": "Gravity"
},
"adguard": {
"queries": "Queries",
"blocked": "Blocked",
"filtered": "Filtered",
"latency": "Latency"
},
"speedtest": {
"upload": "Upload",
"download": "Download",
"ping": "Ping"
},
"portainer": {
"running": "Running",
"stopped": "Stopped",
"total": "Total"
},
"traefik": {
"routers": "Routers",
"services": "Services",
"middleware": "Middleware"
},
"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"
},
"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"
},
"strelaysrv": {
"numActiveSessions": "Sessions",
"numConnections": "Connections",
"dataRelayed": "Relayed",
"transferRate": "Rate"
},
"mastodon": {
"user_count": "Users",
"status_count": "Posts",
"domain_count": "Domains"
}
}
+172
View File
@@ -0,0 +1,172 @@
{
"widget": {
"missing_type": "Falta el tipo de widget: {{type}}",
"api_error": "Error de API",
"status": "Estado"
},
"search": {
"placeholder": "Buscar…"
},
"resources": {
"total": "Total",
"free": "Libre",
"used": "Usado",
"load": "Carga"
},
"docker": {
"rx": "Recibido",
"tx": "Transmitido",
"mem": "Memoria",
"cpu": "Procesador",
"offline": "Desconectado"
},
"emby": {
"playing": "Reproduciendo",
"transcoding": "Transcodificando",
"bitrate": "Tasa de bits",
"no_active": "Sin transmisiones activas"
},
"tautulli": {
"playing": "Reproduciendo",
"transcoding": "Transcodificando",
"bitrate": "Tasa de bits",
"no_active": "Sin transmisiones activas"
},
"rutorrent": {
"active": "Activo",
"upload": "Subida",
"download": "Bajada"
},
"sonarr": {
"wanted": "Más deseado",
"queued": "En cola",
"series": "Series"
},
"radarr": {
"wanted": "Más deseado",
"queued": "En cola",
"movies": "Películas"
},
"readarr": {
"wanted": "Más deseado",
"queued": "En cola",
"books": "Libros"
},
"ombi": {
"pending": "Pendiente",
"approved": "Aprobado",
"available": "Disponible"
},
"jellyseerr": {
"pending": "Pendiente",
"approved": "Aprobado",
"available": "Disponible"
},
"pihole": {
"queries": "Consultas",
"blocked": "Bloqueado",
"gravity": "Gravedad"
},
"speedtest": {
"upload": "Subida",
"download": "Bajada",
"ping": "Ping"
},
"portainer": {
"running": "En ejecución",
"stopped": "Detenido",
"total": "Total"
},
"traefik": {
"routers": "Enrutadores",
"services": "Servicios",
"middleware": "Middleware"
},
"npm": {
"enabled": "Activado",
"disabled": "Desactivado",
"total": "Total"
},
"weather": {
"current": "Ubicación actual",
"allow": "Clic para permitir",
"updating": "Actualizando",
"wait": "Espere, por favor"
},
"overseerr": {
"pending": "Pendiente",
"approved": "Aprobado",
"available": "Disponible"
},
"sabnzbd": {
"rate": "Tasa",
"queue": "En cola",
"timeleft": "Tiempo restante"
},
"nzbget": {
"rate": "Tasa",
"remaining": "Restante",
"downloaded": "Descargado"
},
"coinmarketcap": {
"configure": "Configurar una o más criptomonedas para rastrear",
"1hour": "1 Hora",
"1day": "1 Día",
"7days": "7 Días",
"30days": "30 Días"
},
"gotify": {
"apps": "Aplicaciones",
"clients": "Clientes",
"messages": "Mensajes"
},
"prowlarr": {
"enableIndexers": "Indexadores",
"numberOfGrabs": "Capturas",
"numberOfQueries": "Consultas",
"numberOfFailGrabs": "Capturas fallidas",
"numberOfFailQueries": "Consultas fallidas"
},
"transmission": {
"download": "Bajada",
"upload": "Subida",
"leech": "Compañeros",
"seed": "Semillas"
},
"jackett": {
"configured": "Configurado",
"errored": "Con errores"
},
"bazarr": {
"missingEpisodes": "Episodios perdidos",
"missingMovies": "Películas perdidas"
},
"lidarr": {
"queued": "En cola",
"wanted": "Más deseado",
"albums": "Álbumes"
},
"adguard": {
"queries": "Consultas",
"blocked": "Bloqueado",
"filtered": "Filtrado",
"latency": "Latencia"
},
"qbittorrent": {
"download": "Bajada",
"upload": "Subida",
"leech": "Compañeros",
"seed": "Semillas"
},
"mastodon": {
"user_count": "Usuarios",
"status_count": "Publicaciones",
"domain_count": "Dominios"
},
"strelaysrv": {
"numActiveSessions": "Sesiones",
"numConnections": "Conexiones",
"dataRelayed": "Retransmitido",
"transferRate": "Velocidad"
}
}
+183
View File
@@ -0,0 +1,183 @@
{
"widget": {
"missing_type": "Type de widget manquant: {{type}}",
"api_error": "Erreur de l'API",
"status": "Statut"
},
"search": {
"placeholder": "Recherche…"
},
"resources": {
"total": "Total",
"free": "Libre",
"used": "Utilisé",
"load": "Charge"
},
"docker": {
"rx": "Rx",
"tx": "Tx",
"mem": "Mém",
"cpu": "Cpu",
"offline": "Hors ligne"
},
"emby": {
"playing": "En lecture",
"transcoding": "Transcodage",
"bitrate": "Débit",
"no_active": "Aucun flux actif"
},
"tautulli": {
"playing": "En lecture",
"transcoding": "Transcodage",
"bitrate": "Débit",
"no_active": "Aucun flux actif"
},
"rutorrent": {
"active": "Actif",
"upload": "Envoi",
"download": "Réception"
},
"sonarr": {
"wanted": "Demande",
"queued": "En attente",
"series": "Séries"
},
"radarr": {
"wanted": "Demande",
"queued": "En attente",
"movies": "Films"
},
"readarr": {
"wanted": "Demande",
"queued": "Attente",
"books": "Livres"
},
"ombi": {
"pending": "En attente",
"approved": "Validé",
"available": "Disponible"
},
"jellyseerr": {
"pending": "En attente",
"approved": "Validé",
"available": "Disponible"
},
"pihole": {
"queries": "Requêtes",
"blocked": "Bloqué",
"gravity": "Listes dom. bloqués"
},
"speedtest": {
"upload": "Envoi",
"download": "Récep.",
"ping": "Ping"
},
"portainer": {
"running": "Démarré",
"stopped": "Arrêté",
"total": "Total"
},
"traefik": {
"routers": "Routeurs",
"services": "Services",
"middleware": "Middleware"
},
"npm": {
"enabled": "Activé",
"disabled": "Désactivé",
"total": "Total"
},
"common": {
"bbytes": "{{value, bytes(binary: true)}}",
"bytes": "{{value, bytes}}",
"bits": "{{value, bytes(bits: true)}}",
"bbits": "{{value, bytes(bits: true, binary: true)}}",
"number": "{{value, number}}",
"byterate": "{{value, bytes}}",
"bitrate": "{{value, bytes(bits: true)}}",
"percent": "{{value, percent}}",
"ms": "{{value, number}}"
},
"weather": {
"current": "Localisation actuelle",
"allow": "Cliquez pour autoriser",
"updating": "Mise à jour",
"wait": "Veuillez patienter"
},
"overseerr": {
"pending": "En attente",
"approved": "Demande",
"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": "Applis",
"clients": "Clients",
"messages": "Msg"
},
"prowlarr": {
"enableIndexers": "Indexeur",
"numberOfGrabs": "Capture",
"numberOfQueries": "Demande",
"numberOfFailGrabs": "Capt. échouée",
"numberOfFailQueries": "Dem. échouée"
},
"transmission": {
"download": "Réception",
"upload": "Envoi",
"leech": "Leech",
"seed": "Seed"
},
"jackett": {
"configured": "Configuré",
"errored": "En erreur"
},
"bazarr": {
"missingEpisodes": "Épisodes manquants",
"missingMovies": "Films manquants"
},
"lidarr": {
"wanted": "Demandé",
"queued": "En queue",
"albums": "Albums"
},
"adguard": {
"queries": "Requêtes",
"blocked": "Bloquées",
"filtered": "Filtrées",
"latency": "Latence"
},
"qbittorrent": {
"download": "Récep.",
"upload": "Envoi",
"leech": "Leech",
"seed": "Seed"
},
"mastodon": {
"user_count": "Utilisateurs",
"status_count": "Messages",
"domain_count": "Domaines"
},
"strelaysrv": {
"numActiveSessions": "Sessions",
"numConnections": "Cnx",
"dataRelayed": "Relayé",
"transferRate": "Débit"
}
}
+172
View File
@@ -0,0 +1,172 @@
{
"widget": {
"missing_type": "סוג ווידג'ט חסר: {{type}}",
"api_error": "שגיאת API",
"status": "סטטוס"
},
"weather": {
"current": "מיקום נוכחי",
"allow": "יש ללחוץ כדי לאשר",
"updating": "מעדכן",
"wait": "המתן בבקשה"
},
"search": {
"placeholder": "חיפוש…"
},
"resources": {
"total": "סה\"כ",
"free": "פנוי",
"used": "בשימוש",
"load": "עומס"
},
"docker": {
"rx": "RX",
"tx": "TX",
"mem": "זיכרון",
"cpu": "מעבד",
"offline": "כבוי"
},
"emby": {
"playing": "מנגן",
"transcoding": "מקודד",
"bitrate": "סיביות",
"no_active": "אין הזרמות פעילות"
},
"tautulli": {
"playing": "מנגן",
"transcoding": "מקודד",
"bitrate": "סיביות",
"no_active": "אין הזרמות פעילות"
},
"nzbget": {
"rate": "יחס",
"remaining": "נותר",
"downloaded": "הורד"
},
"sabnzbd": {
"rate": "יחס",
"queue": "תור",
"timeleft": "זמן שנותר"
},
"rutorrent": {
"active": "פעיל",
"upload": "העלאה",
"download": "הורדה"
},
"transmission": {
"download": "הורדה",
"upload": "העלאה",
"leech": "בהורדה",
"seed": "בשיתוף"
},
"qbittorrent": {
"download": "הורדה",
"upload": "העלאה",
"leech": "בהורדה",
"seed": "בשיתוף"
},
"sonarr": {
"wanted": "מבוקש",
"queued": "בתור",
"series": "סדרות"
},
"radarr": {
"wanted": "מבוקש",
"queued": "בתור",
"movies": "סרטים"
},
"lidarr": {
"wanted": "מבוקש",
"queued": "בתור",
"albums": "אלבומים"
},
"readarr": {
"wanted": "מבוקש",
"queued": "בתור",
"books": "ספרים"
},
"bazarr": {
"missingEpisodes": "פרקים חסרים",
"missingMovies": "סרטים חסרים"
},
"ombi": {
"pending": "ממתין",
"approved": "מאושר",
"available": "זמין"
},
"jellyseerr": {
"pending": "ממתין",
"approved": "מאושר",
"available": "זמין"
},
"overseerr": {
"pending": "ממתין",
"approved": "מאושר",
"available": "זמין"
},
"pihole": {
"queries": "שאילתות",
"blocked": "נחסם",
"gravity": "Gravity"
},
"adguard": {
"queries": "שאילתות",
"blocked": "נחסם",
"filtered": "מסונן",
"latency": "השהיה"
},
"speedtest": {
"upload": "העלאה",
"download": "הורדה",
"ping": "פינג"
},
"portainer": {
"running": "פעיל",
"stopped": "נעצר",
"total": "סה\"כ"
},
"traefik": {
"routers": "ניתובים",
"services": "שירותים",
"middleware": "מתווך"
},
"npm": {
"enabled": "מופעל",
"disabled": "מבוטל",
"total": "סה\"כ"
},
"coinmarketcap": {
"configure": "קבע את התצורה של מטבע קריפטו אחד או יותר למעקב",
"1hour": "שעה אחת",
"1day": "יום 1",
"7days": "7 יום",
"30days": "30 יום"
},
"gotify": {
"apps": "אפליקציות",
"clients": "לקוחות",
"messages": "הודעות"
},
"prowlarr": {
"enableIndexers": "אינדקסים",
"numberOfGrabs": "Grabs",
"numberOfQueries": "שאילתות",
"numberOfFailGrabs": "Fail Grabs",
"numberOfFailQueries": "Fail Queries"
},
"jackett": {
"configured": "מוגדר",
"errored": "שגיאה"
},
"mastodon": {
"user_count": "Users",
"status_count": "Posts",
"domain_count": "Domains"
},
"strelaysrv": {
"numActiveSessions": "Sessions",
"numConnections": "Connections",
"dataRelayed": "Relayed",
"transferRate": "Rate"
}
}
+172
View File
@@ -0,0 +1,172 @@
{
"weather": {
"current": "Current Location",
"allow": "Click to allow",
"updating": "Updating",
"wait": "Please wait"
},
"search": {
"placeholder": "Search…"
},
"resources": {
"total": "Total",
"free": "Free",
"used": "Used",
"load": "Load"
},
"sabnzbd": {
"rate": "Rate",
"queue": "Queue",
"timeleft": "Time Left"
},
"overseerr": {
"available": "Available",
"pending": "Pending",
"approved": "Approved"
},
"pihole": {
"queries": "Queries",
"blocked": "Blocked",
"gravity": "Gravity"
},
"adguard": {
"latency": "Latency",
"queries": "Queries",
"blocked": "Blocked",
"filtered": "Filtered"
},
"npm": {
"total": "Total",
"enabled": "Enabled",
"disabled": "Disabled"
},
"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"
},
"widget": {
"missing_type": "Missing Widget Type: {{type}}",
"api_error": "API Error",
"status": "Status"
},
"docker": {
"rx": "RX",
"tx": "TX",
"mem": "MEM",
"cpu": "CPU",
"offline": "Offline"
},
"emby": {
"playing": "Playing",
"transcoding": "Transcoding",
"bitrate": "Bitrate",
"no_active": "No Active Streams"
},
"tautulli": {
"playing": "Playing",
"transcoding": "Transcoding",
"bitrate": "Bitrate",
"no_active": "No Active Streams"
},
"nzbget": {
"rate": "Rate",
"remaining": "Remaining",
"downloaded": "Downloaded"
},
"rutorrent": {
"upload": "Upload",
"download": "Download",
"active": "Active"
},
"transmission": {
"download": "Download",
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
},
"sonarr": {
"wanted": "Wanted",
"queued": "Queued",
"series": "Series"
},
"radarr": {
"wanted": "Wanted",
"queued": "Queued",
"movies": "Movies"
},
"lidarr": {
"wanted": "Wanted",
"queued": "Queued",
"albums": "Albums"
},
"readarr": {
"wanted": "Wanted",
"queued": "Queued",
"books": "Books"
},
"bazarr": {
"missingEpisodes": "Missing Episodes",
"missingMovies": "Missing Movies"
},
"ombi": {
"pending": "Pending",
"approved": "Approved",
"available": "Available"
},
"jellyseerr": {
"pending": "Pending",
"approved": "Approved",
"available": "Available"
},
"speedtest": {
"upload": "Upload",
"download": "Download",
"ping": "Ping"
},
"portainer": {
"running": "Running",
"stopped": "Stopped",
"total": "Total"
},
"traefik": {
"routers": "Routers",
"services": "Services",
"middleware": "Middleware"
},
"gotify": {
"clients": "Clients",
"messages": "Messages",
"apps": "Applications"
},
"jackett": {
"configured": "Configured",
"errored": "Errored"
},
"qbittorrent": {
"download": "Download",
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
},
"mastodon": {
"user_count": "Users",
"status_count": "Posts",
"domain_count": "Domains"
},
"strelaysrv": {
"numActiveSessions": "Sessions",
"numConnections": "Connections",
"dataRelayed": "Relayed",
"transferRate": "Rate"
}
}
+172
View File
@@ -0,0 +1,172 @@
{
"resources": {
"total": "Összes",
"free": "Szabad",
"used": "Használt",
"load": "Terhelés"
},
"docker": {
"rx": "RX",
"tx": "TX",
"mem": "MEM",
"cpu": "CPU",
"offline": "Offline"
},
"lidarr": {
"albums": "Albumok",
"wanted": "Keresett",
"queued": "Sorban áll"
},
"readarr": {
"wanted": "Keresett",
"queued": "Sorban áll",
"books": "Könyvek"
},
"bazarr": {
"missingEpisodes": "Hiányzó epizódok",
"missingMovies": "Hiányzó filmek"
},
"widget": {
"missing_type": "Hiányzó Widget Típus: {{type}}",
"api_error": "API Hiba",
"status": "Státusz"
},
"weather": {
"current": "Aktuális hely",
"allow": "Kattints az engedélyezéshez",
"updating": "Frissítés",
"wait": "Kérlek várj"
},
"search": {
"placeholder": "Keresés…"
},
"emby": {
"playing": "Lejátszás",
"transcoding": "Átkódolás",
"bitrate": "Bitráta",
"no_active": "Nincs aktív lejátszás"
},
"tautulli": {
"playing": "Lejátszás folyamatban",
"transcoding": "Átkódolás",
"bitrate": "Bitráta",
"no_active": "Nincs aktív lejátszás"
},
"nzbget": {
"rate": "Ráta",
"remaining": "Hátralévő",
"downloaded": "Letöltött"
},
"sabnzbd": {
"rate": "Ráta",
"queue": "Sor",
"timeleft": "Hátralévő idő"
},
"rutorrent": {
"active": "Aktív",
"upload": "Feltöltés",
"download": "Letöltés"
},
"transmission": {
"leech": "Leechelés",
"seed": "Seedelés",
"download": "Letöltés",
"upload": "Feltöltés"
},
"qbittorrent": {
"download": "Letöltés",
"upload": "Feltöltés",
"leech": "Leechelés",
"seed": "Seedelés"
},
"sonarr": {
"wanted": "Keresett",
"queued": "Sorban áll",
"series": "Sorozat"
},
"radarr": {
"wanted": "Keresett",
"queued": "Sorban áll",
"movies": "Filmek"
},
"ombi": {
"pending": "Függőben",
"approved": "Engedélyezett",
"available": "Elérhető"
},
"jellyseerr": {
"pending": "Függőben",
"approved": "Engedélyezett",
"available": "Elérhető"
},
"overseerr": {
"pending": "Függőben",
"approved": "Engedélyezett",
"available": "Elérhető"
},
"pihole": {
"queries": "Lekérdezések",
"blocked": "Blokkolt",
"gravity": "Gravitáció"
},
"adguard": {
"queries": "Lekérdezések",
"blocked": "Blokkolt",
"filtered": "Szűrt",
"latency": "Késleltetés"
},
"speedtest": {
"upload": "Feltöltés",
"download": "Letöltés",
"ping": "Ping"
},
"portainer": {
"running": "Futó",
"stopped": "Megállított",
"total": "Összes"
},
"traefik": {
"routers": "Routerek",
"services": "Folyamatok",
"middleware": "Közvetítő"
},
"npm": {
"enabled": "Bekapcsolva",
"disabled": "Kikapcsolva",
"total": "Összes"
},
"coinmarketcap": {
"configure": "Állíts be egy vagy több Cryptovalutát a követéshez",
"1hour": "1 Óra",
"1day": "1 Nap",
"7days": "7 Nap",
"30days": "30 Nap"
},
"gotify": {
"apps": "Applikációk",
"clients": "Kliensek",
"messages": "Üzenetek"
},
"prowlarr": {
"enableIndexers": "Indexerek",
"numberOfGrabs": "Fogott",
"numberOfFailGrabs": "Hibás fogások",
"numberOfQueries": "Lekérdezések",
"numberOfFailQueries": "Hibás lekérdezések"
},
"jackett": {
"configured": "Beállított",
"errored": "Hibás"
},
"mastodon": {
"user_count": "Users",
"status_count": "Posts",
"domain_count": "Domains"
},
"strelaysrv": {
"numActiveSessions": "Sessions",
"numConnections": "Connections",
"dataRelayed": "Relayed",
"transferRate": "Rate"
}
}
+172
View File
@@ -0,0 +1,172 @@
{
"docker": {
"tx": "TX",
"mem": "MEM",
"cpu": "CPU",
"offline": "Offline",
"rx": "RX"
},
"emby": {
"playing": "In riproduzione",
"transcoding": "Transcoding",
"bitrate": "Bitrate",
"no_active": "Nessuno Stream Attivo"
},
"tautulli": {
"playing": "In riproduzione",
"transcoding": "Transcoding",
"bitrate": "Bitrate",
"no_active": "Nessuno Stream Attivo"
},
"speedtest": {
"upload": "Upload",
"download": "Download",
"ping": "Ping"
},
"portainer": {
"running": "In esecuzione",
"stopped": "Fermati",
"total": "Totali"
},
"traefik": {
"routers": "Routers",
"services": "Servizi",
"middleware": "Middleware"
},
"widget": {
"missing_type": "Missing Widget Type: {{type}}",
"api_error": "Errore API",
"status": "Stato"
},
"search": {
"placeholder": "Cerca…"
},
"resources": {
"total": "Totale",
"free": "Libero",
"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": "Rapporto",
"queue": "Coda",
"timeleft": "Tempo Rimanente"
},
"nzbget": {
"rate": "Rapporto",
"remaining": "Rimanente",
"downloaded": "Scaricato"
},
"coinmarketcap": {
"configure": "Configure one or more crypto currencies to track",
"1day": "1 Day",
"7days": "7 Days",
"1hour": "1 Hour",
"30days": "30 Days"
},
"gotify": {
"apps": "Applicazioni",
"clients": "Clients",
"messages": "Messaggi"
},
"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"
},
"adguard": {
"queries": "Queries",
"blocked": "Blocked",
"filtered": "Filtered",
"latency": "Latency"
},
"qbittorrent": {
"download": "Download",
"leech": "Leech",
"upload": "Upload",
"seed": "Seed"
},
"mastodon": {
"user_count": "Users",
"status_count": "Posts",
"domain_count": "Domains"
},
"strelaysrv": {
"numActiveSessions": "Sessions",
"numConnections": "Connections",
"dataRelayed": "Relayed",
"transferRate": "Rate"
}
}
+172
View File
@@ -0,0 +1,172 @@
{
"widget": {
"missing_type": "Manglende miniprogramstype: {{type}}",
"api_error": "API-feil",
"status": "Status"
},
"search": {
"placeholder": "Søk …"
},
"resources": {
"total": "Totalt",
"free": "Ledig",
"used": "Brukt",
"load": "Last inn"
},
"docker": {
"rx": "Mottatt",
"tx": "Sendt",
"mem": "Minne",
"cpu": "Prosessor",
"offline": "Frakoblet"
},
"emby": {
"playing": "Spiller",
"transcoding": "Transkoding",
"bitrate": "Bitrate",
"no_active": "Ingen aktive strømmer"
},
"tautulli": {
"playing": "Spiller",
"transcoding": "Transkoding",
"bitrate": "Bitrate",
"no_active": "Ingen aktive strømmer"
},
"rutorrent": {
"active": "Aktiv",
"upload": "Opplasting",
"download": "Nedlasting"
},
"sonarr": {
"wanted": "Ønsket",
"queued": "I kø",
"series": "Serie"
},
"radarr": {
"wanted": "Ønsket",
"queued": "I kø",
"movies": "Filmer"
},
"readarr": {
"wanted": "Wanted",
"queued": "Queued",
"books": "Books"
},
"ombi": {
"pending": "Venter",
"approved": "Godkjent",
"available": "Tilgjengelig"
},
"jellyseerr": {
"pending": "Venter",
"approved": "Godkjent",
"available": "Tilgjengelig"
},
"pihole": {
"queries": "Spørringer",
"blocked": "Blokkert",
"gravity": "Gravitet"
},
"speedtest": {
"upload": "Opplasting",
"download": "Nedlasting",
"ping": "Ekkoforespørsel"
},
"portainer": {
"running": "Kjører",
"stopped": "Stoppet",
"total": "Totalt"
},
"traefik": {
"routers": "Rutere",
"services": "Tjenester",
"middleware": "Midtvare"
},
"npm": {
"enabled": "Påskrudd",
"disabled": "Avskrudd",
"total": "Totalt"
},
"weather": {
"allow": "Klikk for å tillate",
"updating": "Oppdaterer …",
"wait": "Vent litt …",
"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"
},
"adguard": {
"queries": "Queries",
"blocked": "Blocked",
"filtered": "Filtered",
"latency": "Latency"
},
"qbittorrent": {
"download": "Download",
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
},
"mastodon": {
"user_count": "Users",
"status_count": "Posts",
"domain_count": "Domains"
},
"strelaysrv": {
"numActiveSessions": "Sessions",
"numConnections": "Connections",
"dataRelayed": "Relayed",
"transferRate": "Rate"
}
}
+172
View File
@@ -0,0 +1,172 @@
{
"widget": {
"missing_type": "Missing Widget Type: {{type}}",
"api_error": "API Error",
"status": "Status"
},
"resources": {
"total": "Totaal",
"free": "Vrij",
"used": "Gebruikt",
"load": "Load"
},
"docker": {
"rx": "RX",
"tx": "TX",
"mem": "MEM",
"cpu": "CPU",
"offline": "Offline"
},
"speedtest": {
"upload": "Upload",
"download": "Download",
"ping": "Ping"
},
"portainer": {
"running": "Draaiend",
"stopped": "Gestopt",
"total": "Totaal"
},
"weather": {
"updating": "Updaten",
"wait": "Even geduld",
"current": "Huidige Locatie",
"allow": "Klik om toe te staan"
},
"search": {
"placeholder": "Zoeken…"
},
"emby": {
"playing": "Afspelen",
"transcoding": "Transcodering",
"bitrate": "Bitsnelheid",
"no_active": "Geen Actieve Steams"
},
"tautulli": {
"playing": "Afspelen",
"transcoding": "Transcodering",
"bitrate": "Bitsnelheid",
"no_active": "No Active Streams"
},
"rutorrent": {
"active": "Actief",
"upload": "Upload",
"download": "Download"
},
"sonarr": {
"wanted": "Gezocht",
"queued": "In de wachtrij",
"series": "Series"
},
"radarr": {
"movies": "Films",
"wanted": "Gezocht",
"queued": "In de wachtrij"
},
"readarr": {
"wanted": "Wanted",
"queued": "Queued",
"books": "Books"
},
"ombi": {
"pending": "In afwachting",
"approved": "Goedgekeurd",
"available": "Beschikbaar"
},
"jellyseerr": {
"pending": "In afwachting",
"approved": "Goedgekeurd",
"available": "Beschikbaar"
},
"pihole": {
"queries": "Queries",
"blocked": "Geblokkeerd",
"gravity": "Gravity"
},
"traefik": {
"routers": "Routers",
"services": "Services",
"middleware": "Middleware"
},
"npm": {
"enabled": "Ingeschakeld",
"disabled": "Uitgeschakeld",
"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"
},
"adguard": {
"queries": "Queries",
"blocked": "Blocked",
"filtered": "Filtered",
"latency": "Latency"
},
"qbittorrent": {
"download": "Download",
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
},
"mastodon": {
"user_count": "Users",
"status_count": "Posts",
"domain_count": "Domains"
},
"strelaysrv": {
"numActiveSessions": "Sessions",
"numConnections": "Connections",
"dataRelayed": "Relayed",
"transferRate": "Rate"
}
}
+172
View File
@@ -0,0 +1,172 @@
{
"weather": {
"allow": "Kliknij, aby zezwolić",
"updating": "Aktualizacja",
"wait": "Proszę czekać",
"current": "Aktualna lokalizacja"
},
"search": {
"placeholder": "Szukaj…"
},
"resources": {
"used": "Użyte",
"load": "Obciążenie",
"total": "Całkowite",
"free": "Wolne"
},
"emby": {
"no_active": "Brak aktywnych strumieni",
"playing": "Odtwarzanie",
"transcoding": "Transkodowanie",
"bitrate": "Bitrate"
},
"tautulli": {
"playing": "Odtwarzanie",
"transcoding": "Transkodowanie",
"bitrate": "Bitrate",
"no_active": "Brak aktywnych strumieni"
},
"speedtest": {
"download": "Pobieranie",
"ping": "Ping",
"upload": "Wysyłanie"
},
"portainer": {
"running": "Działające",
"stopped": "Zatrzymane",
"total": "Ogólnie"
},
"coinmarketcap": {
"1day": "1 dzień",
"7days": "7 dni",
"30days": "30 dni",
"1hour": "1 godzina",
"configure": "Wybierz jedną lub więcej kryptowalut do śledzenia"
},
"gotify": {
"apps": "Aplikacje",
"clients": "Klienci",
"messages": "Wiadomości"
},
"widget": {
"missing_type": "Brakujący typ widżetu: {{type}}",
"api_error": "Błąd API",
"status": "Stan"
},
"docker": {
"rx": "RX",
"tx": "TX",
"mem": "MEM",
"cpu": "CPU",
"offline": "Offline"
},
"nzbget": {
"rate": "Szybkość",
"remaining": "Pozostało",
"downloaded": "Pobrano"
},
"sabnzbd": {
"rate": "Szybkość",
"queue": "Kolejka",
"timeleft": "Pozostało"
},
"rutorrent": {
"active": "Aktywny",
"upload": "Wysyłanie",
"download": "Pobieranie"
},
"transmission": {
"download": "Pobieranie",
"upload": "Wysyłanie",
"leech": "Leech",
"seed": "Seed"
},
"sonarr": {
"wanted": "Poszukiwane",
"queued": "W kolejce",
"series": "Seriale"
},
"radarr": {
"wanted": "Poszukiwane",
"queued": "W kolejce",
"movies": "Filmy"
},
"lidarr": {
"wanted": "Poszukiwane",
"queued": "W kolejce",
"albums": "Albumy"
},
"readarr": {
"wanted": "Poszukiwane",
"queued": "W kolejce",
"books": "Książki"
},
"bazarr": {
"missingEpisodes": "Brakujące odcinki",
"missingMovies": "Brakujące filmy"
},
"ombi": {
"pending": "Oczekiwane",
"approved": "Zaakceptowane",
"available": "Dostępne"
},
"jellyseerr": {
"pending": "Oczekiwane",
"approved": "Zaakceptowane",
"available": "Dostępne"
},
"overseerr": {
"pending": "Oczekiwane",
"approved": "Zaakceptowane",
"available": "Dostępne"
},
"pihole": {
"queries": "Zapytania",
"blocked": "Zablokowane",
"gravity": "Gravity"
},
"traefik": {
"routers": "Routery",
"services": "Serwisy",
"middleware": "Pośrednicy"
},
"npm": {
"enabled": "Włączone",
"disabled": "Wyłączone",
"total": "Ogólnie"
},
"prowlarr": {
"enableIndexers": "Indeksery",
"numberOfGrabs": "Pochwycenia",
"numberOfQueries": "Zapytania",
"numberOfFailGrabs": "Nieudane pochwycenia",
"numberOfFailQueries": "Nieudane zapytania"
},
"jackett": {
"configured": "Skonfigurowane",
"errored": "Błędne"
},
"adguard": {
"queries": "Queries",
"blocked": "Blocked",
"filtered": "Filtered",
"latency": "Latency"
},
"qbittorrent": {
"download": "Download",
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
},
"mastodon": {
"user_count": "Users",
"status_count": "Posts",
"domain_count": "Domains"
},
"strelaysrv": {
"numActiveSessions": "Sessions",
"numConnections": "Connections",
"dataRelayed": "Relayed",
"transferRate": "Rate"
}
}
+183
View File
@@ -0,0 +1,183 @@
{
"widget": {
"missing_type": "Widget ausente: {{type}}",
"api_error": "Erro da API",
"status": "Status"
},
"search": {
"placeholder": "Pesquisar…"
},
"resources": {
"total": "Total",
"free": "Livre",
"used": "Usado",
"load": "Carregar"
},
"docker": {
"rx": "Rx",
"tx": "Tx",
"mem": "Mem",
"cpu": "CPU",
"offline": "Desligado"
},
"emby": {
"playing": "A reproduzir",
"transcoding": "Transcodificação",
"bitrate": "Taxa de bits",
"no_active": "Sem streams ativas"
},
"tautulli": {
"playing": "Reproduzindo",
"transcoding": "Transcodificação",
"bitrate": "Taxa de bits",
"no_active": "Sem streams ativas"
},
"rutorrent": {
"active": "Ativo",
"upload": "Enviando",
"download": "Baixando"
},
"sonarr": {
"wanted": "Desejada",
"queued": "Em fila",
"series": "Séries"
},
"radarr": {
"wanted": "Desejado",
"queued": "Fila",
"movies": "Filmes"
},
"readarr": {
"wanted": "Desejados",
"queued": "Em fila",
"books": "Livros"
},
"ombi": {
"pending": "Pendente",
"approved": "Aprovada",
"available": "Disponível"
},
"jellyseerr": {
"pending": "Pendente",
"approved": "Aprovada",
"available": "Disponível"
},
"pihole": {
"queries": "Consultas",
"blocked": "Bloqueado",
"gravity": "Gravidade"
},
"speedtest": {
"upload": "Envio",
"download": "Download",
"ping": "Ping"
},
"portainer": {
"running": "A correr",
"stopped": "Parado",
"total": "Total"
},
"traefik": {
"routers": "Roteadores",
"services": "Serviços",
"middleware": "Middleware"
},
"npm": {
"enabled": "Ativo",
"disabled": "Desabilitado",
"total": "Total"
},
"common": {
"bytes": "{{value, bytes}}",
"bbytes": "{{value, bytes(binary: true)}}",
"bits": "{{value, bytes(bits: true)}}",
"bbits": "{{value, bytes(bits: true, binary: true)}}",
"number": "{{value, number}}",
"byterate": "{{value, bytes}}",
"ms": "{{value, number}}",
"bitrate": "{{value, bytes(bits: true)}}",
"percent": "{{value, percent}}"
},
"weather": {
"current": "Localização atual",
"allow": "Clicar para permitir",
"updating": "A atualizar",
"wait": "Por favor aguarde"
},
"overseerr": {
"pending": "Pendente",
"approved": "Aprovado",
"available": "Disponível"
},
"sabnzbd": {
"rate": "Taxa",
"queue": "Fila",
"timeleft": "Tempo restante"
},
"nzbget": {
"rate": "Taxa",
"remaining": "Restante",
"downloaded": "Baixado"
},
"coinmarketcap": {
"configure": "Configurar uma ou mais moedas",
"1hour": "1 Hora",
"1day": "1 Dia",
"7days": "7 Dias",
"30days": "30 Dias"
},
"gotify": {
"apps": "Aplicações",
"clients": "Clientes",
"messages": "Mensagens"
},
"prowlarr": {
"enableIndexers": "Indexadores",
"numberOfGrabs": "Agarrados",
"numberOfQueries": "Consultas",
"numberOfFailGrabs": "Falhados",
"numberOfFailQueries": "Pesquisas falhadas"
},
"transmission": {
"download": "Baixando",
"upload": "Enviando",
"leech": "Sanguessugas",
"seed": "Semeadores"
},
"jackett": {
"configured": "Configurado",
"errored": "Errado"
},
"bazarr": {
"missingEpisodes": "Episódios Faltantes",
"missingMovies": "Filmes Faltantes"
},
"lidarr": {
"queued": "Enfileirado",
"wanted": "Desejado",
"albums": "Álbuns"
},
"adguard": {
"queries": "Consultas",
"blocked": "Bloqueado",
"filtered": "Filtrado",
"latency": "Latência"
},
"qbittorrent": {
"download": "Baixando",
"upload": "Enviando",
"leech": "Sanguessugas",
"seed": "Semeadores"
},
"mastodon": {
"user_count": "Usuários",
"status_count": "Postagens",
"domain_count": "Domínios"
},
"strelaysrv": {
"numActiveSessions": "Sessões",
"numConnections": "Conexões",
"dataRelayed": "Retransmitido",
"transferRate": "Taxa"
}
}
+172
View File
@@ -0,0 +1,172 @@
{
"widget": {
"missing_type": "Отсутствует тип виджета: {{type}}",
"api_error": "Ошибка API",
"status": "Статус"
},
"search": {
"placeholder": "Поиск…"
},
"resources": {
"total": "Всего",
"free": "Свободно",
"used": "Использовано",
"load": "Load"
},
"docker": {
"rx": "Rx",
"tx": "Тx",
"mem": "Память",
"cpu": "Процессор",
"offline": "Не в сети"
},
"emby": {
"playing": "Воспроизведение",
"transcoding": "Транскодирование",
"bitrate": "Битрейт",
"no_active": "No Active Streams"
},
"tautulli": {
"playing": "Воспроизведение",
"transcoding": "Транскодирование",
"bitrate": "Битрейт",
"no_active": "No Active Streams"
},
"rutorrent": {
"active": "Активный",
"upload": "Загрузить",
"download": "Скачать"
},
"sonarr": {
"wanted": "Хотел",
"queued": "В очереди",
"series": "Серии"
},
"radarr": {
"wanted": "Хотел",
"queued": "В очереди",
"movies": "Фильмы"
},
"readarr": {
"wanted": "Wanted",
"queued": "Queued",
"books": "Books"
},
"ombi": {
"pending": "Ожидание",
"approved": "Одобрено",
"available": "Доступно"
},
"jellyseerr": {
"pending": "Ожидание",
"approved": "Одобрено",
"available": "Доступно"
},
"pihole": {
"queries": "Запросы",
"blocked": "Заблокировано",
"gravity": "Сила тяжести"
},
"speedtest": {
"upload": "Загрузка",
"download": "Скачать",
"ping": "пинг"
},
"portainer": {
"running": "Запущено",
"stopped": "Остановлено",
"total": "Всего"
},
"traefik": {
"routers": "Маршрутизаторы",
"services": "Сервисы",
"middleware": "Промежуточное программное обеспечение"
},
"npm": {
"enabled": "Включено",
"disabled": "Отключено",
"total": "Всего"
},
"weather": {
"wait": "Пожалуйста подождите",
"current": "Текущая локация",
"allow": "Нажмите, чтобы разрешить",
"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 Дней"
},
"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"
},
"adguard": {
"queries": "Queries",
"blocked": "Blocked",
"filtered": "Filtered",
"latency": "Latency"
},
"qbittorrent": {
"download": "Download",
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
},
"mastodon": {
"user_count": "Users",
"status_count": "Posts",
"domain_count": "Domains"
},
"strelaysrv": {
"numConnections": "Connections",
"dataRelayed": "Relayed",
"transferRate": "Rate",
"numActiveSessions": "Sessions"
}
}
+172
View File
@@ -0,0 +1,172 @@
{
"widget": {
"missing_type": "Saknar Widget-typ: {{type}}",
"api_error": "API-fel",
"status": "Status"
},
"weather": {
"current": "Nuvarande plats",
"allow": "Klicka för att tillåta",
"updating": "Uppdaterar",
"wait": "Vänligen vänta"
},
"resources": {
"load": "Laddar",
"total": "Total",
"free": "Ledigt",
"used": "Använt"
},
"docker": {
"rx": "RX",
"tx": "TX",
"mem": "MEM",
"cpu": "CPU",
"offline": "Offline"
},
"search": {
"placeholder": "Sök…"
},
"emby": {
"playing": "Spelar",
"transcoding": "Omkodning",
"bitrate": "Bitrate",
"no_active": "Inga aktiva strömmar"
},
"tautulli": {
"playing": "Spelar",
"transcoding": "Omkodning",
"bitrate": "Bitrate",
"no_active": "Inga aktiva strömmar"
},
"nzbget": {
"rate": "Hastighet",
"remaining": "Återstående",
"downloaded": "Nedladdat"
},
"sabnzbd": {
"rate": "Hastighet",
"queue": "Kö",
"timeleft": "Tid kvar"
},
"rutorrent": {
"active": "Aktiva",
"upload": "Uppladdning",
"download": "Nedladdning"
},
"transmission": {
"download": "Nedladdning",
"upload": "Uppladdning",
"leech": "Leech",
"seed": "Seed"
},
"sonarr": {
"wanted": "Eftersöker",
"queued": "I kö",
"series": "Serier"
},
"radarr": {
"wanted": "Eftersöker",
"queued": "I kö",
"movies": "Filmer"
},
"lidarr": {
"wanted": "Eftersöker",
"queued": "I kö",
"albums": "Album"
},
"readarr": {
"wanted": "Eftersökt",
"queued": "I kö",
"books": "Böcker"
},
"bazarr": {
"missingEpisodes": "Saknade program",
"missingMovies": "Saknade filmer"
},
"ombi": {
"pending": "Avvaktar",
"approved": "Godkända",
"available": "Tillgänglig"
},
"jellyseerr": {
"pending": "Avvaktar",
"approved": "Godkända",
"available": "Tillgänglig"
},
"overseerr": {
"pending": "Avvaktar",
"approved": "Godkända",
"available": "Tillgänglig"
},
"pihole": {
"blocked": "Blockerad",
"queries": "Förfrågningar",
"gravity": "Gravity"
},
"speedtest": {
"upload": "Uppladdning",
"download": "Nedladdning",
"ping": "Svarstid"
},
"portainer": {
"running": "Körs",
"stopped": "Stoppade",
"total": "Totalt"
},
"traefik": {
"routers": "Routers",
"services": "Tjänster",
"middleware": "Middleware"
},
"npm": {
"enabled": "Aktiverad",
"disabled": "Inaktiverad",
"total": "Totalt"
},
"coinmarketcap": {
"configure": "Konfigurera en eller flera kryptovalutor att följa",
"1hour": "1 timme",
"1day": "1 dag",
"7days": "7 dagar",
"30days": "30 dagar"
},
"gotify": {
"apps": "Program",
"clients": "Klienter",
"messages": "Meddelande"
},
"prowlarr": {
"enableIndexers": "Indexerare",
"numberOfGrabs": "Hämtningar",
"numberOfQueries": "Hämtningar",
"numberOfFailGrabs": "Misslyckade hämtningar",
"numberOfFailQueries": "Misslyckade hämtningar"
},
"jackett": {
"configured": "Konfigurerade",
"errored": "Felaktiga"
},
"adguard": {
"queries": "Förfrågningar",
"blocked": "Blockerad",
"filtered": "Filtrerad",
"latency": "Svarstid"
},
"qbittorrent": {
"download": "Nedladdning",
"upload": "Uppladdning",
"leech": "Leech",
"seed": "Seed"
},
"mastodon": {
"user_count": "Användare",
"status_count": "Posts",
"domain_count": "Domains"
},
"strelaysrv": {
"numActiveSessions": "Sessioner",
"numConnections": "Anslutningar",
"dataRelayed": "Relayed",
"transferRate": "Rate"
}
}
+172
View File
@@ -0,0 +1,172 @@
{
"widget": {
"missing_type": "Thiếu loại Widget: {{type}}",
"api_error": "Lỗi API",
"status": "Trạng thái"
},
"search": {
"placeholder": "Tìm kiếm…"
},
"resources": {
"total": "Tổng",
"free": "Dư",
"used": "Đã dùng",
"load": "Load"
},
"docker": {
"rx": "RX",
"tx": "TX",
"mem": "BỘ NHỚ",
"cpu": "CPU",
"offline": "Ngoại tuyến"
},
"emby": {
"playing": "Đang chơi",
"transcoding": "Chuyển định dạng",
"bitrate": "Bitrate",
"no_active": "No Active Streams"
},
"tautulli": {
"playing": "Đang chơi",
"transcoding": "Chuyển định dạng",
"bitrate": "Bitrate",
"no_active": "No Active Streams"
},
"rutorrent": {
"active": "Hoạt động",
"upload": "Tải lên",
"download": "Tải xuống"
},
"sonarr": {
"wanted": "Wanted",
"queued": "Queued",
"series": "Series"
},
"radarr": {
"wanted": "Wanted",
"queued": "Queued",
"movies": "Phim"
},
"readarr": {
"wanted": "Đang tìm",
"queued": "Đang chờ",
"books": "Sách"
},
"ombi": {
"pending": "Đang xử lý",
"approved": "Đã duyệt",
"available": "Available"
},
"jellyseerr": {
"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"
},
"npm": {
"enabled": "Enabled",
"disabled": "Disabled",
"total": "Total"
},
"weather": {
"current": "Vị trí hiện tại",
"allow": "Bấm để đồng ý",
"updating": "Đang cập nhật",
"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"
},
"adguard": {
"queries": "Queries",
"blocked": "Blocked",
"filtered": "Filtered",
"latency": "Latency"
},
"qbittorrent": {
"download": "Download",
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
},
"mastodon": {
"user_count": "Users",
"status_count": "Posts",
"domain_count": "Domains"
},
"strelaysrv": {
"numActiveSessions": "Sessions",
"numConnections": "Connections",
"dataRelayed": "Relayed",
"transferRate": "Rate"
}
}
+172
View File
@@ -0,0 +1,172 @@
{
"widget": {
"missing_type": "缺少小部件类型:{{type}}",
"api_error": "API错误",
"status": "状态"
},
"search": {
"placeholder": "搜索…"
},
"resources": {
"total": "共",
"free": "空闲",
"used": "已用",
"load": "负载"
},
"docker": {
"rx": "接收",
"tx": "发送",
"mem": "内存",
"cpu": "处理器",
"offline": "离线"
},
"emby": {
"playing": "播放中",
"transcoding": "转码",
"bitrate": "比特率",
"no_active": "暂无播放"
},
"tautulli": {
"playing": "播放中",
"transcoding": "转码",
"bitrate": "比特率",
"no_active": "暂无播放"
},
"rutorrent": {
"active": "活动中",
"upload": "上传",
"download": "下载"
},
"sonarr": {
"wanted": "想看",
"queued": "排队",
"series": "系列"
},
"radarr": {
"wanted": "想看",
"queued": "队列",
"movies": "电影"
},
"readarr": {
"wanted": "订阅",
"queued": "队列",
"books": "书籍"
},
"ombi": {
"pending": "待办的",
"approved": "已批准",
"available": "可用的"
},
"jellyseerr": {
"pending": "待办的",
"approved": "得到正式认可的",
"available": "可用的"
},
"pihole": {
"queries": "查询",
"blocked": "阻止",
"gravity": "重力"
},
"speedtest": {
"upload": "上传",
"download": "下载",
"ping": "ping"
},
"portainer": {
"running": "运行中",
"stopped": "停止",
"total": "总计"
},
"traefik": {
"routers": "路由器",
"services": "服务",
"middleware": "中间件"
},
"npm": {
"enabled": "已启用",
"disabled": "禁用",
"total": "全部的"
},
"weather": {
"current": "当前定位",
"allow": "点击并允许",
"updating": "更新中",
"wait": "请稍后"
},
"overseerr": {
"pending": "待办",
"approved": "已批准",
"available": "可用"
},
"sabnzbd": {
"rate": "速率",
"queue": "队列",
"timeleft": "剩余时间"
},
"nzbget": {
"rate": "速率",
"remaining": "剩余",
"downloaded": "下载"
},
"coinmarketcap": {
"configure": "配置一个或多个需要追踪的加密",
"1hour": "1小时",
"1day": "1天",
"7days": "7天",
"30days": "30天"
},
"gotify": {
"apps": "应用",
"clients": "客户端",
"messages": "信息"
},
"prowlarr": {
"enableIndexers": "索引器",
"numberOfGrabs": "抓取",
"numberOfQueries": "查询",
"numberOfFailGrabs": "抓取失败",
"numberOfFailQueries": "查询失败"
},
"transmission": {
"download": "下载",
"upload": "上传",
"leech": "下载中",
"seed": "做种"
},
"jackett": {
"configured": "已配置",
"errored": "出错了"
},
"bazarr": {
"missingEpisodes": "缺少的剧集",
"missingMovies": "缺少的电影"
},
"lidarr": {
"wanted": "订阅",
"queued": "队列",
"albums": "相册"
},
"adguard": {
"queries": "查询",
"blocked": "阻止",
"filtered": "过滤",
"latency": "延迟"
},
"qbittorrent": {
"download": "下载",
"upload": "上传",
"leech": "下载中",
"seed": "做种"
},
"mastodon": {
"user_count": "用户",
"status_count": "Posts",
"domain_count": "Domains"
},
"strelaysrv": {
"numActiveSessions": "Sessions",
"dataRelayed": "Relayed",
"numConnections": "Connections",
"transferRate": "Rate"
}
}
+172
View File
@@ -0,0 +1,172 @@
{
"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"
},
"adguard": {
"queries": "Queries",
"blocked": "Blocked",
"filtered": "Filtered",
"latency": "Latency"
},
"qbittorrent": {
"download": "Download",
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
},
"mastodon": {
"user_count": "Users",
"status_count": "Posts",
"domain_count": "Domains"
},
"strelaysrv": {
"numActiveSessions": "Sessions",
"numConnections": "Connections",
"dataRelayed": "Relayed",
"transferRate": "Rate"
}
}
+2 -7
View File
@@ -2,13 +2,8 @@ import List from "components/bookmarks/list";
export default function BookmarksGroup({ group }) {
return (
<div
key={group.name}
className="basis-full md:basis-1/2 lg:basis-1/3 xl:basis-1/4 flex-1 p-1"
>
<h2 className="text-theme-800 dark:text-theme-300 text-xl font-medium">
{group.name}
</h2>
<div key={group.name} className="basis-full md:basis-1/2 lg:basis-1/3 xl:basis-1/4 flex-1 p-1">
<h2 className="text-theme-800 dark:text-theme-300 text-xl font-medium">{group.name}</h2>
<List bookmarks={group.bookmarks} />
</div>
);
+22 -14
View File
@@ -1,21 +1,29 @@
import { useContext } from "react";
import { SettingsContext } from "utils/settings-context";
export default function Item({ bookmark }) {
const { hostname } = new URL(bookmark.href);
const { settings } = useContext(SettingsContext);
return (
<li
onClick={() => {
window.open(bookmark.href, "_blank").focus();
}}
key={bookmark.name}
className="mb-3 cursor-pointer flex rounded-md font-medium text-theme-700 hover:text-theme-800 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-theme-900/10 dark:shadow-theme-900/50 bg-white/50 hover:bg-theme-300/10 dark:bg-white/5 dark:hover:bg-white/10"
>
<div className="flex-shrink-0 flex items-center justify-center w-11 bg-theme-500/10 dark:bg-theme-900/50 text-theme-700 dark:text-theme-200 text-sm font-medium rounded-l-md">
{bookmark.abbr}
</div>
<div className="flex-1 flex items-center justify-between rounded-r-md ">
<div className="flex-1 grow pl-3 py-2 text-xs">{bookmark.name}</div>
<div className="px-2 py-2 truncate text-theme-500 dark:text-theme-400 opacity-50 text-xs">{hostname}</div>
</div>
<li key={bookmark.name}>
<a
href={bookmark.href}
title={bookmark.name}
target={settings.target ?? "_blank"}
className="block w-full text-left mb-3 cursor-pointer rounded-md font-medium text-theme-700 hover:text-theme-700 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-black/10 dark:shadow-black/20 bg-white/50 hover:bg-theme-300/10 dark:bg-white/10 dark:hover:bg-white/20"
>
<div className="flex">
<div className="flex-shrink-0 flex items-center justify-center w-11 bg-theme-500/10 dark:bg-theme-900/50 text-theme-700 hover:text-theme-700 dark:text-theme-200 text-sm font-medium rounded-l-md">
{bookmark.abbr}
</div>
<div className="flex-1 flex items-center justify-between rounded-r-md ">
<div className="flex-1 grow pl-3 py-2 text-xs">{bookmark.name}</div>
<div className="px-2 py-2 truncate text-theme-500 dark:text-theme-400 opacity-50 text-xs">{hostname}</div>
</div>
</div>
</a>
</li>
);
}
+1 -1
View File
@@ -2,7 +2,7 @@ import Item from "components/bookmarks/item";
export default function List({ bookmarks }) {
return (
<ul role="list" className="mt-3 flex flex-col">
<ul className="mt-3 flex flex-col">
{bookmarks.map((bookmark) => (
<Item key={bookmark.name} bookmark={bookmark} />
))}
+36 -38
View File
@@ -1,7 +1,7 @@
import { useContext } from "react";
import { useContext, Fragment } from "react";
import { IoColorPalette } from "react-icons/io5";
import { Popover, Transition } from "@headlessui/react";
import { Fragment } from "react";
import classNames from "classnames";
import { ColorContext } from "utils/color-context";
@@ -27,6 +27,7 @@ const colors = [
"pink",
"rose",
"red",
"white",
];
export default function ColorToggle() {
@@ -39,42 +40,39 @@ export default function ColorToggle() {
return (
<div className="w-full self-center">
<Popover className="relative flex items-center">
{({ open }) => (
<>
<Popover.Button className="outline-none">
<IoColorPalette
className="h-5 w-5 text-theme-800 dark:text-theme-200 transition duration-150 ease-in-out"
aria-hidden="true"
/>
</Popover.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel className="absolute -top-[75px] left-0">
<div className="rounded-md shadow-lg ring-1 ring-black ring-opacity-5">
<div className="relative grid gap-2 p-2 grid-cols-11 shadow-theme-900/10 dark:shadow-theme-900 rounded-md shadow-md">
{colors.map((color) => (
<button role="button" onClick={() => setColor(color)} key={color}>
<div
className={
(active == color ? "border-2" : "border-0") +
` rounded-md w-5 h-5 border-black/50 dark:border-white/50 theme-${color} bg-theme-500`
}
/>
</button>
))}
</div>
</div>
</Popover.Panel>
</Transition>
</>
)}
<Popover.Button className="outline-none">
<IoColorPalette
className="h-5 w-5 text-theme-800 dark:text-theme-200 transition duration-150 ease-in-out"
aria-hidden="true"
/>
</Popover.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel className="absolute -top-[75px] left-0">
<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">
{colors.map((color) => (
<button type="button" onClick={() => setColor(color)} key={color}>
<div
title={color}
className={classNames(
active === color ? "border-2" : "border-0",
`rounded-md w-5 h-5 border-black/50 dark:border-white/50 theme-${color} bg-theme-400`
)}
/>
</button>
))}
</div>
</div>
</Popover.Panel>
</Transition>
</Popover>
</div>
);
-20
View File
@@ -1,20 +0,0 @@
export default function Greeting() {
const name = process.env.NEXT_PUBLIC_DISPLAY_NAME;
const hour = new Date().getHours();
let day = "day";
if (hour < 12) {
day = "morning";
} else if (hour < 17) {
day = "afternoon";
} else {
day = "evening";
}
return (
<div className="self-end grow text-2xl font-thin text-theme-800 dark:text-theme-200">
Good {day}
</div>
);
}
-68
View File
@@ -1,68 +0,0 @@
import { Fragment, useRef, useState, Children } from "react";
import { Dialog, Transition } from "@headlessui/react";
function classNames(...classes) {
return classes.filter(Boolean).join(" ");
}
const Modal = ({ Toggle, Content }) => {
const [open, setOpen] = useState(false);
const cancelButtonRef = useRef(null);
return (
<>
<Toggle open={open} setOpen={setOpen} />
<Transition.Root show={open} as={Fragment}>
<Dialog
as="div"
className="relative z-10"
initialFocus={cancelButtonRef}
onClose={setOpen}
>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-theme-900/90 transition-opacity" />
</Transition.Child>
<div className="fixed z-10 inset-0 overflow-y-auto">
<div className="flex items-center justify-center min-h-full">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative rounded-lg shadow-xl transform transition-all my-8 max-w-lg w-full">
<Content open={open} setOpen={setOpen} />
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
</>
);
};
const ModalToggle = ({ open, setOpen, children }) => (
<div onClick={() => setOpen(!open)}>{children}</div>
);
const ModalContent = ({ open, setOpen, children }) => (
<div className="body">{children}</div>
);
Modal.Toggle = ModalToggle;
Modal.Content = ModalContent;
export default Modal;
+17
View File
@@ -0,0 +1,17 @@
import { MdRefresh } from "react-icons/md";
export default function Revalidate() {
const revalidate = () => {
fetch("/api/revalidate").then((res) => {
if (res.ok) {
window.location.reload();
}
});
};
return (
<div className="rounded-full flex align-middle self-center mr-3">
<MdRefresh onClick={() => revalidate()} className="text-theme-800 dark:text-theme-200 w-6 h-6 cursor-pointer" />
</div>
);
}
+48
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>
);
}
+9 -6
View File
@@ -1,15 +1,18 @@
import classNames from "classnames";
import List from "components/services/list";
export default function ServicesGroup({ services }) {
export default function ServicesGroup({ services, layout }) {
return (
<div
key={services.name}
className="basis-full md:basis-1/2 lg:basis-1/3 xl:basis-1/4 flex-1 p-1"
className={classNames(
layout?.style === "row" ? "basis-full" : "basis-full md:basis-1/2 lg:basis-1/3 xl:basis-1/4",
"flex-1 p-1"
)}
>
<h2 className="text-theme-800 dark:text-theme-300 text-xl font-medium">
{services.name}
</h2>
<List services={services.services} />
<h2 className="text-theme-800 dark:text-theme-300 text-xl font-medium">{services.name}</h2>
<List services={services.services} layout={layout} />
</div>
);
}
+59 -36
View File
@@ -1,61 +1,84 @@
import Image from "next/future/image";
import { useContext } from "react";
import { Disclosure } from "@headlessui/react";
import Status from "./status";
import Widget from "./widget";
import Docker from "./widgets/service/docker";
import { SettingsContext } from "utils/settings-context";
function resolveIcon(icon) {
if (icon.startsWith("http")) {
return `/api/proxy?url=${encodeURIComponent(icon)}`;
} else if (icon.startsWith("/")) {
return icon;
} else {
if (icon.endsWith(".png")) {
return `https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${icon}`;
} else {
return `https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${icon}.png`;
}
}
if (icon.startsWith("/")) {
return icon;
}
if (icon.endsWith(".png")) {
return `https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${icon}`;
}
return `https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${icon}.png`;
}
export default function Item({ service }) {
const hasLink = service.href && service.href !== "#";
const { settings } = useContext(SettingsContext);
return (
<li key={service.name}>
<Disclosure>
<div className={
(service.href && service.href !== "#" ? 'cursor-pointer ' : 'cursor-default ') +
'transition-all h-15 overflow-hidden mb-3 p-1 rounded-md font-medium text-theme-700 hover:text-theme-800 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-theme-900/10 dark:shadow-theme-900/40 bg-white/50 hover:bg-theme-300/10 dark:bg-white/5 dark:hover:bg-white/10'
}>
<div className="flex">
{service.icon && (
<div
onClick={() => {
if (service.href && service.href !== "#") {
window.open(service.href, "_blank").focus();
}
}}
className="flex-shrink-0 flex items-center justify-center w-12 "
<div
className={`${
hasLink ? "cursor-pointer " : " "
}transition-all h-15 mb-3 p-1 rounded-md font-medium text-theme-700 hover:text-theme-700/70 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-black/10 dark:shadow-black/20 bg-white/50 hover:bg-theme-300/20 dark:bg-white/10 dark:hover:bg-white/20`}
>
<div className="flex select-none">
{service.icon &&
(hasLink ? (
<a
href={service.href}
target={settings.target ?? "_blank"}
rel="noreferrer"
className="flex-shrink-0 flex items-center justify-center w-12 "
>
<Image src={resolveIcon(service.icon)} width={32} height={32} alt="logo" />
</a>
) : (
<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 ? (
<a
href={service.href}
target={settings.target ?? "_blank"}
rel="noreferrer"
className="flex-1 flex items-center justify-between rounded-r-md "
>
<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>
</a>
) : (
<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>
)}
<div
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">
{service.name}
<p className="text-theme-500 dark:text-theme-400 text-xs font-extralight">{service.description}</p>
</div>
</div>
{service.container && (
<Disclosure.Button as="div" className="flex-shrink-0 flex items-center justify-center w-12 cursor-pointer">
<Disclosure.Button
as="div"
className="flex-shrink-0 flex items-center justify-center w-12 cursor-pointer"
>
<Status service={service} />
</Disclosure.Button>
)}
+21 -2
View File
@@ -1,8 +1,27 @@
import classNames from "classnames";
import Item from "components/services/item";
export default function List({ services }) {
const columnMap = [
"grid-cols-1 md:grid-cols-1 lg:grid-cols-1",
"grid-cols-1 md:grid-cols-1 lg:grid-cols-1",
"grid-cols-1 md:grid-cols-2 lg:grid-cols-2",
"grid-cols-1 md:grid-cols-2 lg:grid-cols-3",
"grid-cols-1 md:grid-cols-2 lg:grid-cols-4",
"grid-cols-1 md:grid-cols-2 lg:grid-cols-5",
"grid-cols-1 md:grid-cols-2 lg:grid-cols-6",
"grid-cols-1 md:grid-cols-2 lg:grid-cols-7",
"grid-cols-1 md:grid-cols-2 lg:grid-cols-8",
];
export default function List({ services, layout }) {
return (
<ul role="list" className="mt-3 flex flex-col">
<ul
className={classNames(
layout?.style === "row" ? `grid ${columnMap[layout?.columns]} gap-x-2` : "flex flex-col",
"mt-3"
)}
>
{services.map((service) => (
<Item key={service.name} service={service} />
))}
+4 -14
View File
@@ -1,28 +1,18 @@
import useSWR from "swr";
export default function Status({ service }) {
const { data, error } = useSWR(
`/api/docker/status/${service.container}/${service.server || ""}`
);
const { data, error } = useSWR(`/api/docker/status/${service.container}/${service.server || ""}`);
if (error) {
return (
<div className="w-3 h-3 bg-rose-300 dark:bg-rose-500 rounded-full" />
);
return <div className="w-3 h-3 bg-rose-300 dark:bg-rose-500 rounded-full" />;
}
if (data && data.status === "running") {
return (
<div className="w-3 h-3 bg-emerald-300 dark:bg-emerald-500 rounded-full" />
);
return <div className="w-3 h-3 bg-emerald-300 dark:bg-emerald-500 rounded-full" />;
}
if (data && data.status === "not found") {
return (
<>
<div className="h-2.5 w-2.5 bg-orange-400/50 dark:bg-yellow-200/40 -rotate-45"></div>
</>
);
return <div className="h-2.5 w-2.5 bg-orange-400/50 dark:bg-yellow-200/40 -rotate-45" />;
}
return <div className="w-3 h-3 bg-black/20 dark:bg-white/40 rounded-full" />;
+49 -18
View File
@@ -1,38 +1,71 @@
import Sonarr from "./widgets/service/sonarr";
import Radarr from "./widgets/service/radarr";
import Ombi from "./widgets/service/ombi";
import Portainer from "./widgets/service/portainer";
import Emby from "./widgets/service/emby";
import Nzbget from "./widgets/service/nzbget";
import Docker from "./widgets/service/docker";
import Pihole from "./widgets/service/pihole";
import Rutorrent from "./widgets/service/rutorrent";
import Jellyfin from "./widgets/service/jellyfin";
import Speedtest from "./widgets/service/speedtest";
import Traefik from "./widgets/service/traefik";
import Jellyseerr from "./widgets/service/jellyseerr";
import Npm from "./widgets/service/npm";
import Tautulli from "./widgets/service/tautulli";
import { useTranslation } from "react-i18next";
import dynamic from "next/dynamic";
const Sonarr = dynamic(() => import("./widgets/service/sonarr"));
const Radarr = dynamic(() => import("./widgets/service/radarr"));
const Lidarr = dynamic(() => import("./widgets/service/lidarr"));
const Readarr = dynamic(() => import("./widgets/service/readarr"));
const Bazarr = dynamic(() => import("./widgets/service/bazarr"));
const Ombi = dynamic(() => import("./widgets/service/ombi"));
const Portainer = dynamic(() => import("./widgets/service/portainer"));
const Emby = dynamic(() => import("./widgets/service/emby"));
const Nzbget = dynamic(() => import("./widgets/service/nzbget"));
const SABnzbd = dynamic(() => import("./widgets/service/sabnzbd"));
const Transmission = dynamic(() => import("./widgets/service/transmission"));
const QBittorrent = dynamic(() => import("./widgets/service/qbittorrent"));
const Docker = dynamic(() => import("./widgets/service/docker"));
const Pihole = dynamic(() => import("./widgets/service/pihole"));
const Rutorrent = dynamic(() => import("./widgets/service/rutorrent"));
const Jellyfin = dynamic(() => import("./widgets/service/jellyfin"));
const Speedtest = dynamic(() => import("./widgets/service/speedtest"));
const Traefik = dynamic(() => import("./widgets/service/traefik"));
const Jellyseerr = dynamic(() => import("./widgets/service/jellyseerr"));
const Overseerr = dynamic(() => import("./widgets/service/overseerr"));
const Npm = dynamic(() => import("./widgets/service/npm"));
const Tautulli = dynamic(() => import("./widgets/service/tautulli"));
const CoinMarketCap = dynamic(() => import("./widgets/service/coinmarketcap"));
const Gotify = dynamic(() => import("./widgets/service/gotify"));
const Prowlarr = dynamic(() => import("./widgets/service/prowlarr"));
const Jackett = dynamic(() => import("./widgets/service/jackett"));
const AdGuard = dynamic(() => import("./widgets/service/adguard"));
const StRelaySrv = dynamic(() => import("./widgets/service/strelaysrv"));
const Mastodon = dynamic(() => import("./widgets/service/mastodon"));
const widgetMappings = {
docker: Docker,
sonarr: Sonarr,
radarr: Radarr,
lidarr: Lidarr,
readarr: Readarr,
bazarr: Bazarr,
ombi: Ombi,
portainer: Portainer,
emby: Emby,
jellyfin: Jellyfin,
nzbget: Nzbget,
sabnzbd: SABnzbd,
transmission: Transmission,
qbittorrent: QBittorrent,
pihole: Pihole,
rutorrent: Rutorrent,
speedtest: Speedtest,
traefik: Traefik,
jellyseerr: Jellyseerr,
overseerr: Overseerr,
coinmarketcap: CoinMarketCap,
npm: Npm,
tautulli: Tautulli,
gotify: Gotify,
prowlarr: Prowlarr,
jackett: Jackett,
adguard: AdGuard,
strelaysrv: StRelaySrv,
mastodon: Mastodon,
};
export default function Widget({ service }) {
const { t } = useTranslation("common");
const ServiceWidget = widgetMappings[service.widget.type];
if (ServiceWidget) {
@@ -41,9 +74,7 @@ export default function Widget({ service }) {
return (
<div className="bg-theme-200/50 dark:bg-theme-900/20 rounded m-1 flex-1 flex flex-col items-center justify-center p-1">
<div className="font-thin text-sm">
Missing Widget Type: <strong>{service.widget.type}</strong>
</div>
<div className="font-thin text-sm">{t("widget.missing_type", { type: service.widget.type })}</div>
</div>
);
}
@@ -0,0 +1,45 @@
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 AdGuard({ service }) {
const { t } = useTranslation();
const config = service.widget;
const { data: adguardData, error: adguardError } = useSWR(formatApiUrl(config, "stats"));
if (adguardError) {
return <Widget error={t("widget.api_error")} />;
}
if (!adguardData) {
return (
<Widget>
<Block label={t("adguard.queries")} />
<Block label={t("adguard.blocked")} />
<Block label={t("adguard.filtered")} />
<Block label={t("adguard.latency")} />
</Widget>
);
}
const filtered =
adguardData.num_replaced_safebrowsing + adguardData.num_replaced_safesearch + adguardData.num_replaced_parental;
return (
<Widget>
<Block label={t("adguard.queries")} value={t("common.number", { value: adguardData.num_dns_queries })} />
<Block label={t("adguard.blocked")} value={t("common.number", { value: adguardData.num_blocked_filtering })} />
<Block label={t("adguard.filtered")} value={t("common.number", { value: filtered })} />
<Block
label={t("adguard.latency")}
value={t("common.ms", { value: adguardData.avg_processing_time * 1000, style: "unit", unit: "millisecond" })}
/>
</Widget>
);
}
@@ -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={t("common.number", { value: episodesData.total })} />
<Block label={t("bazarr.missingMovies")} value={t("common.number", { value: moviesData.total })} />
</Widget>
);
}
@@ -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>
);
}
@@ -1,11 +1,14 @@
import useSWR from "swr";
import { useTranslation } from "react-i18next";
import Widget from "../widget";
import Block from "../block";
import { calculateCPUPercent, formatBytes } from "utils/stats-helpers";
import calculateCPUPercent from "utils/stats-helpers";
export default function Docker({ service }) {
const { t } = useTranslation();
const config = service.widget;
const { data: statusData, error: statusError } = useSWR(
@@ -23,13 +26,13 @@ export default function Docker({ service }) {
);
if (statsError || statusError) {
return <Widget error="Error Fetching Data" />;
return <Widget error={t("widget.api_error")} />;
}
if (statusData && statusData.status !== "running") {
return (
<Widget>
<Block label="Status" value="Offline" />
<Block label={t("widget.status")} value={t("docker.offline")} />
</Widget>
);
}
@@ -37,22 +40,22 @@ export default function Docker({ service }) {
if (!statsData || !statusData) {
return (
<Widget>
<Block label="CPU" />
<Block label="MEM" />
<Block label="RX" />
<Block label="TX" />
<Block label={t("docker.cpu")} />
<Block label={t("docker.mem")} />
<Block label={t("docker.rx")} />
<Block label={t("docker.tx")} />
</Widget>
);
}
return (
<Widget>
<Block label="CPU" value={`${calculateCPUPercent(statsData.stats)}%`} />
<Block label="MEM" value={formatBytes(statsData.stats.memory_stats.usage, 0)} />
<Block label={t("docker.cpu")} value={t("common.percent", { value: calculateCPUPercent(statsData.stats) })} />
<Block label={t("docker.mem")} value={t("common.bytes", { value: statsData.stats.memory_stats.usage })} />
{statsData.stats.networks && (
<>
<Block label="RX" value={formatBytes(statsData.stats.networks.eth0.rx_bytes, 0)} />
<Block label="TX" value={formatBytes(statsData.stats.networks.eth0.tx_bytes, 0)} />
<Block label={t("docker.rx")} value={t("common.bytes", { value: statsData.stats.networks.eth0.rx_bytes })} />
<Block label={t("docker.tx")} value={t("common.bytes", { value: statsData.stats.networks.eth0.tx_bytes })} />
</>
)}
</Widget>
+218 -19
View File
@@ -1,40 +1,239 @@
import useSWR from "swr";
import { useTranslation } from "react-i18next";
import { BsVolumeMuteFill, BsFillPlayFill, BsPauseFill, BsCpu, BsFillCpuFill } from "react-icons/bs";
import { MdOutlineSmartDisplay } from "react-icons/md";
import Widget from "../widget";
import Block from "../block";
import { formatApiUrl } from "utils/api-helpers";
export default function Emby({ service, title = "Emby" }) {
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 }) {
const {
NowPlayingItem: { Name, SeriesName, RunTimeTicks },
PlayState: { PositionTicks, IsPaused, IsMuted },
} = session;
const { IsVideoDirect, VideoDecoderIsHardware, VideoEncoderIsHardware } = session?.TranscodingInfo || {
IsVideoDirect: true,
VideoDecoderIsHardware: true,
VideoEncoderIsHardware: true,
};
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="grow text-xs z-10 self-center ml-2 relative w-full h-4 mr-2">
<div className="absolute w-full whitespace-nowrap text-ellipsis overflow-hidden">
{Name}
{SeriesName && ` - ${SeriesName}`}
</div>
</div>
<div className="self-center text-xs flex justify-end mr-1.5 pl-1">
{IsVideoDirect && <MdOutlineSmartDisplay className="opacity-50" />}
{!IsVideoDirect && (!VideoDecoderIsHardware || !VideoEncoderIsHardware) && <BsCpu className="opacity-50" />}
{!IsVideoDirect && VideoDecoderIsHardware && VideoEncoderIsHardware && (
<BsFillCpuFill className="opacity-50" />
)}
</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-1 z-10">{IsMuted && <BsVolumeMuteFill />}</div>
<div className="self-center text-xs flex justify-end mr-2 z-10">
{ticksToString(PositionTicks)}
<span className="mx-0.5 text-[8px]">/</span>
{ticksToString(RunTimeTicks)}
</div>
</div>
</>
);
}
function SessionEntry({ playCommand, session }) {
const {
NowPlayingItem: { Name, SeriesName, RunTimeTicks },
PlayState: { PositionTicks, IsPaused, IsMuted },
} = session;
const { IsVideoDirect, VideoDecoderIsHardware, VideoEncoderIsHardware } = session?.TranscodingInfo || {};
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"
/>
)}
</div>
<div className="grow text-xs z-10 self-center relative w-full h-4">
<div className="absolute w-full whitespace-nowrap text-ellipsis overflow-hidden">
{Name}
{SeriesName && ` - ${SeriesName}`}
</div>
</div>
<div className="self-center text-xs flex justify-end mr-1 z-10">{IsMuted && <BsVolumeMuteFill />}</div>
<div className="self-center text-xs flex justify-end mr-1 z-10">{ticksToString(PositionTicks)}</div>
<div className="self-center items-center text-xs flex justify-end mr-1.5 pl-1 z-10">
{IsVideoDirect && <MdOutlineSmartDisplay className="opacity-50" />}
{!IsVideoDirect && (!VideoDecoderIsHardware || !VideoEncoderIsHardware) && <BsCpu className="opacity-50" />}
{!IsVideoDirect && VideoDecoderIsHardware && VideoEncoderIsHardware && <BsFillCpuFill className="opacity-50" />}
</div>
</div>
);
}
export default function Emby({ service }) {
const { t } = useTranslation();
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) {
return <Widget error={`${title} API Error`} />;
return <Widget error={t("widget.api_error")} />;
}
if (!sessionsData) {
return (
<Widget>
<Block label="Playing" />
<Block label="Transcoding" />
<Block label="Bitrate" />
</Widget>
<div className="flex flex-col pb-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]">-</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>
);
}
const playing = sessionsData.filter((session) => session.hasOwnProperty("NowPlayingItem"));
const transcoding = sessionsData.filter(
(session) => session.hasOwnProperty("PlayState") && session.PlayState.PlayMethod === "Transcode"
);
const bitrate = playing.reduce((acc, session) => acc + session.NowPlayingItem.Bitrate, 0);
const playing = sessionsData
.filter((session) => session?.NowPlayingItem)
.sort((a, b) => {
if (a.PlayState.PositionTicks > b.PlayState.PositionTicks) {
return 1;
}
if (a.PlayState.PositionTicks < b.PlayState.PositionTicks) {
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("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 (
<Widget>
<Block label="Playing" value={playing.length} />
<Block label="Transcoding" value={transcoding.length} />
<Block label="Bitrate" value={`${Math.round((bitrate / 1024 / 1024) * 100) / 100} Mbps`} />
</Widget>
<div className="flex flex-col pb-1 mx-1">
{playing.map((session) => (
<SessionEntry
key={session.Id}
playCommand={(currentSession, command) => handlePlayCommand(currentSession, command)}
session={session}
/>
))}
</div>
);
}
@@ -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>
);
}
@@ -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={t("common.number", { value: indexersData.length })} />
<Block label={t("jackett.errored")} value={t("common.number", { value: errored.length })} />
</Widget>
);
}
@@ -1,6 +1,5 @@
import Emby from "./emby";
// Jellyfin and Emby share the same API, so proxy the Emby widget to Jellyfin.
export default function Jellyfin({ service }) {
return <Emby service={service} title="Jellyfin" />;
return <Emby service={service} />;
}
@@ -1,4 +1,5 @@
import useSWR from "swr";
import { useTranslation } from "react-i18next";
import Widget from "../widget";
import Block from "../block";
@@ -6,29 +7,31 @@ import Block from "../block";
import { formatApiUrl } from "utils/api-helpers";
export default function Jellyseerr({ service }) {
const { t } = useTranslation();
const config = service.widget;
const { data: statsData, error: statsError } = useSWR(formatApiUrl(config, `request/count`));
if (statsError) {
return <Widget error="Jellyseerr API Error" />;
return <Widget error={t("widget.api_error")} />;
}
if (!statsData) {
return (
<Widget>
<Block label="Pending" />
<Block label="Approved" />
<Block label="Available" />
<Block label={t("jellyseerr.pending")} />
<Block label={t("jellyseerr.approved")} />
<Block label={t("jellyseerr.available")} />
</Widget>
);
}
return (
<Widget>
<Block label="Pending" value={statsData.pending} />
<Block label="Approved" value={statsData.approved} />
<Block label="Available" value={statsData.available} />
<Block label={t("jellyseerr.pending")} value={statsData.pending} />
<Block label={t("jellyseerr.approved")} value={statsData.approved} />
<Block label={t("jellyseerr.available")} value={statsData.available} />
</Widget>
);
}
@@ -0,0 +1,39 @@
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>
);
}
return (
<Widget>
<Block label={t("lidarr.wanted")} value={t("common.number", { value: wantedData.totalRecords })} />
<Block label={t("lidarr.queued")} value={t("common.number", { value: queueData.totalCount })} />
<Block label={t("lidarr.albums")} value={t("common.number", { value: albumsData.have })} />
</Widget>
);
}
@@ -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 Mastodon({ service }) {
const { t } = useTranslation();
const config = service.widget;
const { data: statsData, error: statsError } = useSWR(formatApiUrl(config, `instance`));
if (statsError) {
return <Widget error={t("widget.api_error")} />;
}
if (!statsData) {
return (
<Widget>
<Block label={t("mastodon.user_count")} />
<Block label={t("mastodon.status_count")} />
<Block label={t("mastodon.domain_count")} />
</Widget>
);
}
return (
<Widget>
<Block label={t("mastodon.user_count")} value={t("common.number", { value: statsData.stats.user_count })} />
<Block label={t("mastodon.status_count")} value={t("common.number", { value: statsData.stats.status_count })} />
<Block label={t("mastodon.domain_count")} value={t("common.number", { value: statsData.stats.domain_count })} />
</Widget>
);
}
@@ -1,4 +1,5 @@
import useSWR from "swr";
import { useTranslation } from "react-i18next";
import Widget from "../widget";
import Block from "../block";
@@ -6,20 +7,22 @@ import Block from "../block";
import { formatApiUrl } from "utils/api-helpers";
export default function Npm({ service }) {
const { t } = useTranslation();
const config = service.widget;
const { data: infoData, error: infoError } = useSWR(formatApiUrl(config, "nginx/proxy-hosts"));
if (infoError) {
return <Widget error="NGINX Proxy Manager API Error" />;
return <Widget error={t("widget.api_error")} />;
}
if (!infoData) {
return (
<Widget>
<Block label="Enabled" />
<Block label="Disabled" />
<Block label="Total" />
<Block label={t("npm.enabled")} />
<Block label={t("npm.disabled")} />
<Block label={t("npm.total")} />
</Widget>
);
}
@@ -30,9 +33,9 @@ export default function Npm({ service }) {
return (
<Widget>
<Block label="Enabled" value={enabled} />
<Block label="Disabled" value={disabled} />
<Block label="Total" value={total} />
<Block label={t("npm.enabled")} value={enabled} />
<Block label={t("npm.disabled")} value={disabled} />
<Block label={t("npm.total")} value={total} />
</Widget>
);
}
@@ -1,35 +1,43 @@
import useSWR from "swr";
import { useTranslation } from "react-i18next";
import Widget from "../widget";
import Block from "../block";
import { formatApiUrl } from "utils/api-helpers";
import { formatBytes } from "utils/stats-helpers";
export default function Nzbget({ service }) {
const { t } = useTranslation("common");
const config = service.widget;
const { data: statusData, error: statusError } = useSWR(formatApiUrl(config, "status"));
if (statusError) {
return <Widget error="Nzbget API Error" />;
return <Widget error={t("widget.api_error")} />;
}
if (!statusData) {
return (
<Widget>
<Block label="Rate" />
<Block label="Remaining" />
<Block label="Downloaded" />
<Block label={t("nzbget.rate")} />
<Block label={t("nzbget.remaining")} />
<Block label={t("nzbget.downloaded")} />
</Widget>
);
}
return (
<Widget>
<Block label="Rate" value={`${formatBytes(statusData.DownloadRate)}/s`} />
<Block label="Remaining" value={`${Math.round((statusData.RemainingSizeMB / 1024) * 100) / 100} GB`} />
<Block label="Downloaded" value={`${Math.round((statusData.DownloadedSizeMB / 1024) * 100) / 100} GB`} />
<Block label={t("nzbget.rate")} value={t("common.bitrate", { value: statusData.DownloadRate })} />
<Block
label={t("nzbget.remaining")}
value={t("common.bytes", { value: statusData.RemainingSizeMB * 1024 * 1024 })}
/>
<Block
label={t("nzbget.downloaded")}
value={t("common.bytes", { value: statusData.DownloadedSizeMB * 1024 * 1024 })}
/>
</Widget>
);
}
@@ -1,4 +1,5 @@
import useSWR from "swr";
import { useTranslation } from "react-i18next";
import Widget from "../widget";
import Block from "../block";
@@ -6,29 +7,31 @@ import Block from "../block";
import { formatApiUrl } from "utils/api-helpers";
export default function Ombi({ service }) {
const { t } = useTranslation();
const config = service.widget;
const { data: statsData, error: statsError } = useSWR(formatApiUrl(config, `Request/count`));
if (statsError) {
return <Widget error="Ombi API Error" />;
return <Widget error={t("widget.api_error")} />;
}
if (!statsData) {
return (
<Widget>
<Block label="Pending" />
<Block label="Approved" />
<Block label="Available" />
<Block label={t("ombi.pending")} />
<Block label={t("ombi.approved")} />
<Block label={t("ombi.available")} />
</Widget>
);
}
return (
<Widget>
<Block label="Pending" value={statsData.pending} />
<Block label="Approved" value={statsData.approved} />
<Block label="Available" value={statsData.available} />
<Block label={t("ombi.pending")} value={statsData.pending} />
<Block label={t("ombi.approved")} value={statsData.approved} />
<Block label={t("ombi.available")} value={statsData.available} />
</Widget>
);
}
@@ -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>
);
}
@@ -1,4 +1,5 @@
import useSWR from "swr";
import { useTranslation } from "react-i18next";
import Widget from "../widget";
import Block from "../block";
@@ -6,29 +7,31 @@ import Block from "../block";
import { formatApiUrl } from "utils/api-helpers";
export default function Pihole({ service }) {
const { t } = useTranslation();
const config = service.widget;
const { data: piholeData, error: piholeError } = useSWR(formatApiUrl(config, "api.php"));
if (piholeError) {
return <Widget error="PiHole API Error" />;
return <Widget error={t("widget.api_error")} />;
}
if (!piholeData) {
return (
<Widget>
<Block label="Queries" />
<Block label="Blocked" />
<Block label="Gravity" />
<Block label={t("pihole.queries")} />
<Block label={t("pihole.blocked")} />
<Block label={t("pihole.gravity")} />
</Widget>
);
}
return (
<Widget>
<Block label="Queries" value={piholeData.dns_queries_today.toLocaleString()} />
<Block label="Blocked" value={piholeData.ads_blocked_today.toLocaleString()} />
<Block label="Gravity" value={piholeData.domains_being_blocked.toLocaleString()} />
<Block label={t("pihole.queries")} value={t("common.number", { value: piholeData.dns_queries_today })} />
<Block label={t("pihole.blocked")} value={t("common.number", { value: piholeData.ads_blocked_today })} />
<Block label={t("pihole.gravity")} value={t("common.number", { value: piholeData.domains_being_blocked })} />
</Widget>
);
}
@@ -1,4 +1,5 @@
import useSWR from "swr";
import { useTranslation } from "react-i18next";
import Widget from "../widget";
import Block from "../block";
@@ -6,26 +7,28 @@ import Block from "../block";
import { formatApiUrl } from "utils/api-helpers";
export default function Portainer({ service }) {
const { t } = useTranslation();
const config = service.widget;
const { data: containersData, error: containersError } = useSWR(formatApiUrl(config, `docker/containers/json?all=1`));
if (containersError) {
return <Widget error="Portainer API Error" />;
return <Widget error={t("widget.api_error")} />;
}
if (!containersData) {
return (
<Widget>
<Block label="Running" />
<Block label="Stopped" />
<Block label="Total" />
<Block label={t("portainer.running")} />
<Block label={t("portainer.stopped")} />
<Block label={t("portainer.total")} />
</Widget>
);
}
if (containersData.error) {
return <Widget error="Portainer API Error" />;
return <Widget error={t("widget.api_error")} />;
}
const running = containersData.filter((c) => c.State === "running").length;
@@ -34,9 +37,9 @@ export default function Portainer({ service }) {
return (
<Widget>
<Block label="Running" value={running} />
<Block label="Stopped" value={stopped} />
<Block label="Total" value={total} />
<Block label={t("portainer.running")} value={running} />
<Block label={t("portainer.stopped")} value={stopped} />
<Block label={t("portainer.total")} value={total} />
</Widget>
);
}
@@ -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>
);
}
@@ -0,0 +1,69 @@
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 QBittorrent ({ service }) {
const { t } = useTranslation();
const config = service.widget;
const { data: torrentData, error: torrentError } = useSWR(formatApiUrl(config, "torrents/info"));
if (torrentError) {
return <Widget error={t("widget.api_error")} />;
}
if (!torrentData) {
return (
<Widget>
<Block label={t("qbittorrent.leech")} />
<Block label={t("qbittorrent.download")} />
<Block label={t("qbittorrent.seed")} />
<Block label={t("qbittorrent.upload")} />
</Widget>
);
}
let rateDl = 0;
let rateUl = 0;
let completed = 0;
for (let i = 0; i < torrentData.length; i += 1) {
const torrent = torrentData[i];
rateDl += torrent.dlspeed;
rateUl += torrent.upspeed;
if (torrent.progress === 1) {
completed += 1;
}
}
const leech = torrentData.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("qbittorrent.leech")} value={t("common.number", { value: leech })} />
<Block label={t("qbittorrent.download")} value={`${rateDl.toFixed(2)} ${unitsDl}`} />
<Block label={t("qbittorrent.seed")} value={t("common.number", { value: completed })} />
<Block label={t("qbittorrent.upload")} value={`${rateUl.toFixed(2)} ${unitsUl}`} />
</Widget>
);
}
@@ -1,4 +1,5 @@
import useSWR from "swr";
import { useTranslation } from "react-i18next";
import Widget from "../widget";
import Block from "../block";
@@ -6,33 +7,32 @@ import Block from "../block";
import { formatApiUrl } from "utils/api-helpers";
export default function Radarr({ service }) {
const { t } = useTranslation();
const config = service.widget;
const { data: moviesData, error: moviesError } = useSWR(formatApiUrl(config, "movie"));
const { data: queuedData, error: queuedError } = useSWR(formatApiUrl(config, "queue/status"));
if (moviesError || queuedError) {
return <Widget error="Radarr API Error" />;
return <Widget error={t("widget.api_error")} />;
}
if (!moviesData || !queuedData) {
return (
<Widget>
<Block label="Wanted" />
<Block label="Queued" />
<Block label="Movies" />
<Block label={t("radarr.wanted")} />
<Block label={t("radarr.queued")} />
<Block label={t("radarr.movies")} />
</Widget>
);
}
const wanted = moviesData.filter((movie) => movie.isAvailable === false);
const have = moviesData.filter((movie) => movie.isAvailable === true);
return (
<Widget>
<Block label="Wanted" value={wanted.length} />
<Block label="Queued" value={queuedData.totalCount} />
<Block label="Movies" value={have.length} />
<Block label={t("radarr.wanted")} value={moviesData.wanted} />
<Block label={t("radarr.queued")} value={queuedData.totalCount} />
<Block label={t("radarr.movies")} value={moviesData.have} />
</Widget>
);
}
@@ -0,0 +1,39 @@
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>
);
}
return (
<Widget>
<Block label={t("readarr.wanted")} value={t("common.number", { value: wantedData.totalRecords })} />
<Block label={t("readarr.queued")} value={t("common.number", { value: queueData.totalCount })} />
<Block label={t("readarr.books")} value={t("common.number", { value: booksData.have })} />
</Widget>
);
}
@@ -1,45 +1,43 @@
import useSWR from "swr";
import { useTranslation } from "react-i18next";
import Widget from "../widget";
import Block from "../block";
import { formatApiUrl } from "utils/api-helpers";
import { formatBytes } from "utils/stats-helpers";
export default function Rutorrent({ service }) {
const { t } = useTranslation();
const config = service.widget;
const { data: statusData, error: statusError } = useSWR(formatApiUrl(config));
if (statusError) {
return <Widget error="Nzbget API Error" />;
return <Widget error={t("widget.api_error")} />;
}
if (!statusData) {
return (
<Widget>
<Block label="Active" />
<Block label="Upload" />
<Block label="Download" />
<Block label={t("rutorrent.active")} />
<Block label={t("rutorrent.upload")} />
<Block label={t("rutorrent.download")} />
</Widget>
);
}
const upload = statusData.reduce((acc, torrent) => {
return acc + parseInt(torrent["d.get_up_rate"]);
}, 0);
const upload = statusData.reduce((acc, torrent) => acc + parseInt(torrent["d.get_up_rate"], 10), 0);
const download = statusData.reduce((acc, torrent) => {
return acc + parseInt(torrent["d.get_down_rate"]);
}, 0);
const download = statusData.reduce((acc, torrent) => acc + parseInt(torrent["d.get_down_rate"], 10), 0);
const active = statusData.filter((torrent) => torrent["d.get_state"] === "1");
return (
<Widget>
<Block label="Active" value={active.length} />
<Block label="Upload" value={`${formatBytes(upload)}/s`} />
<Block label="Download" value={`${formatBytes(download)}/s`} />
<Block label={t("rutorrent.active")} value={active.length} />
<Block label={t("rutorrent.upload")} value={t("common.bitrate", { value: upload })} />
<Block label={t("rutorrent.download")} value={t("common.bitrate", { value: download })} />
</Widget>
);
}
@@ -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={t("common.number", { value: queueData.queue.noofslots })} />
<Block label={t("sabnzbd.timeleft")} value={queueData.queue.timeleft} />
</Widget>
);
}
@@ -1,4 +1,5 @@
import useSWR from "swr";
import { useTranslation } from "react-i18next";
import Widget from "../widget";
import Block from "../block";
@@ -6,6 +7,8 @@ import Block from "../block";
import { formatApiUrl } from "utils/api-helpers";
export default function Sonarr({ service }) {
const { t } = useTranslation();
const config = service.widget;
const { data: wantedData, error: wantedError } = useSWR(formatApiUrl(config, "wanted/missing"));
@@ -13,24 +16,24 @@ export default function Sonarr({ service }) {
const { data: seriesData, error: seriesError } = useSWR(formatApiUrl(config, "series"));
if (wantedError || queuedError || seriesError) {
return <Widget error="Sonar API Error" />;
return <Widget error={t("widget.api_error")} />;
}
if (!wantedData || !queuedData || !seriesData) {
return (
<Widget>
<Block label="Wanted" />
<Block label="Queued" />
<Block label="Series" />
<Block label={t("sonarr.wanted")} />
<Block label={t("sonarr.queued")} />
<Block label={t("sonarr.series")} />
</Widget>
);
}
return (
<Widget>
<Block label="Wanted" value={wantedData.totalRecords} />
<Block label="Queued" value={queuedData.totalRecords} />
<Block label="Series" value={seriesData.length} />
<Block label={t("sonarr.wanted")} value={wantedData.totalRecords} />
<Block label={t("sonarr.queued")} value={queuedData.totalRecords} />
<Block label={t("sonarr.series")} value={seriesData.total} />
</Widget>
);
}
@@ -1,35 +1,46 @@
import useSWR from "swr";
import { useTranslation } from "react-i18next";
import Widget from "../widget";
import Block from "../block";
import { formatBits } from "utils/stats-helpers";
import { formatApiUrl } from "utils/api-helpers";
export default function Speedtest({ service }) {
const { t } = useTranslation();
const config = service.widget;
const { data: speedtestData, error: speedtestError } = useSWR(formatApiUrl(config, "speedtest/latest"));
if (speedtestError) {
return <Widget error="Speedtest API Error" />;
return <Widget error={t("widget.api_error")} />;
}
if (!speedtestData) {
return (
<Widget>
<Block label="Download" />
<Block label="Upload" />
<Block label="Ping" />
<Block label={t("speedtest.download")} />
<Block label={t("speedtest.upload")} />
<Block label={t("speedtest.ping")} />
</Widget>
);
}
return (
<Widget>
<Block label="Download" value={`${formatBits(speedtestData.data.download * 1024 * 1024, 0)}ps`} />
<Block label="Upload" value={`${formatBits(speedtestData.data.upload * 1024 * 1024, 0)}ps`} />
<Block label="Ping" value={`${speedtestData.data.ping} ms`} />
<Block
label={t("speedtest.download")}
value={t("common.bitrate", { value: speedtestData.data.download * 1024 * 1024 })}
/>
<Block
label={t("speedtest.upload")}
value={t("common.bitrate", { value: speedtestData.data.upload * 1024 * 1024 })}
/>
<Block
label={t("speedtest.ping")}
value={t("common.ms", { value: speedtestData.data.ping, style: "unit", unit: "millisecond" })}
/>
</Widget>
);
}
@@ -0,0 +1,38 @@
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 StRelaySrv({ service }) {
const { t } = useTranslation();
const config = service.widget;
const { data: statsData, error: statsError } = useSWR(formatApiUrl(config, `status`));
if (statsError) {
return <Widget error={t("widget.api_error")} />;
}
if (!statsData) {
return (
<Widget>
<Block label={t("strelaysrv.numActiveSessions")} />
<Block label={t("strelaysrv.numConnections")} />
<Block label={t("strelaysrv.bytesProxied")} />
</Widget>
);
}
return (
<Widget>
<Block label={t("strelaysrv.numActiveSessions")} value={t("common.number", { value: statsData.numActiveSessions })} />
<Block label={t("strelaysrv.numConnections")} value={t("common.number", { value: statsData.numConnections })} />
<Block label={t("strelaysrv.dataRelayed")} value={t("common.bytes", { value: statsData.bytesProxied })} />
<Block label={t("strelaysrv.transferRate")} value={t("common.bitrate",{ value: statsData.kbps10s1m5m15m30m60m[5] })} />
</Widget>
);
}
@@ -1,37 +1,183 @@
/* eslint-disable camelcase */
import useSWR from "swr";
import { useTranslation } from "react-i18next";
import { BsFillPlayFill, BsPauseFill, BsCpu, BsFillCpuFill } from "react-icons/bs";
import { MdOutlineSmartDisplay, MdSmartDisplay } from "react-icons/md";
import Widget from "../widget";
import Block from "../block";
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, video_decision, audio_decision } = 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 relative w-full h-4 grow mr-2">
<div className="absolute w-full whitespace-nowrap text-ellipsis overflow-hidden">{full_title}</div>
</div>
<div className="self-center text-xs flex justify-end mr-1.5 pl-1">
{video_decision === "direct play" && audio_decision === "direct play" && (
<MdSmartDisplay className="opacity-50" />
)}
{video_decision === "copy" && audio_decision === "copy" && <MdOutlineSmartDisplay className="opacity-50" />}
{video_decision !== "copy" &&
video_decision !== "direct play" &&
(audio_decision !== "copy" || audio_decision !== "direct play") && <BsFillCpuFill className="opacity-50" />}
{(video_decision === "copy" || video_decision === "direct play") &&
audio_decision !== "copy" &&
audio_decision !== "direct play" && <BsCpu className="opacity-50" />}
</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 z-10">
{millisecondsToString(view_offset)}
<span className="mx-0.5 text-[8px]">/</span>
{millisecondsToString(duration)}
</div>
</div>
</>
);
}
function SessionEntry({ session }) {
const { full_title, view_offset, progress_percent, state, video_decision, audio_decision } = 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" />
)}
</div>
<div className="text-xs z-10 self-center ml-2 relative w-full h-4 grow mr-2">
<div className="absolute w-full whitespace-nowrap text-ellipsis overflow-hidden">{full_title}</div>
</div>
<div className="self-center text-xs flex justify-end mr-1.5 pl-1 z-10">
{video_decision === "direct play" && audio_decision === "direct play" && (
<MdSmartDisplay className="opacity-50" />
)}
{video_decision === "copy" && audio_decision === "copy" && <MdOutlineSmartDisplay className="opacity-50" />}
{video_decision !== "copy" &&
video_decision !== "direct play" &&
(audio_decision !== "copy" || audio_decision !== "direct play") && <BsFillCpuFill className="opacity-50" />}
{(video_decision === "copy" || video_decision === "direct play") &&
audio_decision !== "copy" &&
audio_decision !== "direct play" && <BsCpu className="opacity-50" />}
</div>
<div className="self-center text-xs flex justify-end mr-2 z-10">{millisecondsToString(view_offset)}</div>
</div>
);
}
export default function Tautulli({ service }) {
const { t } = useTranslation();
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) {
return <Widget error="Tautulli API Error" />;
if (activityError) {
return <Widget error={t("widget.api_error")} />;
}
if (!statsData) {
if (!activityData) {
return (
<Widget>
<Block label="Playing" />
<Block label="Transcoding" />
<Block label="Bitrate" />
</Widget>
<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]">-</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>
);
}
const data = statsData.response.data;
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 (
<Widget>
<Block label="Playing" value={data.stream_count} />
<Block label="Transcoding" value={data.stream_count_transcode} />
{/* We divide by 1000 here because thats how Tautulli reports it on its own dashboard */}
<Block label="Bitrate" value={`${Math.round((data.total_bandwidth / 1000) * 100) / 100} Mbps`} />
</Widget>
<div className="flex flex-col pb-1 mx-1">
{playing.map((session) => (
<SessionEntry key={session.Id} session={session} />
))}
</div>
);
}
@@ -1,4 +1,5 @@
import useSWR from "swr";
import { useTranslation } from "react-i18next";
import Widget from "../widget";
import Block from "../block";
@@ -6,29 +7,31 @@ import Block from "../block";
import { formatApiUrl } from "utils/api-helpers";
export default function Traefik({ service }) {
const { t } = useTranslation();
const config = service.widget;
const { data: traefikData, error: traefikError } = useSWR(formatApiUrl(config, "overview"));
if (traefikError) {
return <Widget error="Traefik API Error" />;
return <Widget error={t("widget.api_error")} />;
}
if (!traefikData) {
return (
<Widget>
<Block label="Routers" />
<Block label="Services" />
<Block label="Middleware" />
<Block label={t("traefik.routers")} />
<Block label={t("traefik.services")} />
<Block label={t("traefik.middleware")} />
</Widget>
);
}
return (
<Widget>
<Block label="Routers" value={traefikData.http.routers.total} />
<Block label="Services" value={traefikData.http.services.total} />
<Block label="Middleware" value={traefikData.http.middlewares.total} />
<Block label={t("traefik.routers")} value={traefikData.http.routers.total} />
<Block label={t("traefik.services")} value={traefikData.http.services.total} />
<Block label={t("traefik.middleware")} value={traefikData.http.middlewares.total} />
</Widget>
);
}
@@ -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={t("common.number", { value: leech })} />
<Block label={t("transmission.download")} value={`${rateDl.toFixed(2)} ${unitsDl}`} />
<Block label={t("transmission.seed")} value={t("common.number", { value: completed })} />
<Block label={t("transmission.upload")} value={`${rateUl.toFixed(2)} ${unitsUl}`} />
</Widget>
);
}
+1 -1
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>;
}
+1 -6
View File
@@ -1,10 +1,5 @@
import { useContext } from "react";
import {
MdDarkMode,
MdLightMode,
MdToggleOff,
MdToggleOn,
} from "react-icons/md";
import { MdDarkMode, MdLightMode, MdToggleOff, MdToggleOn } from "react-icons/md";
import { ThemeContext } from "utils/theme-context";
+47
View File
@@ -0,0 +1,47 @@
import { useTranslation } from "react-i18next";
import useSWR from "swr";
import { compareVersions } from "compare-versions";
import { MdNewReleases } from "react-icons/md";
export default function Version() {
const { t, i18n } = useTranslation();
const buildTime = process.env.NEXT_PUBLIC_BUILDTIME ?? new Date().toISOString();
const revision = process.env.NEXT_PUBLIC_REVISION ?? "dev";
const version = process.env.NEXT_PUBLIC_VERSION ?? "dev";
const { data: releaseData } = useSWR("https://api.github.com/repos/benphelps/homepage/releases");
// use Intl.DateTimeFormat to format the date
const formatDate = (date) => {
const options = {
year: "numeric",
month: "short",
day: "numeric",
};
return new Intl.DateTimeFormat(i18n.language, options).format(new Date(date));
};
const latestRelease = releaseData?.[0];
return (
<div className="flex flex-row items-center">
<span className="text-xs text-theme-500 opacity-50">
{version} ({revision.substring(0, 7)}, {formatDate(buildTime)})
</span>
{version === "main" || version === "dev"
? null
: releaseData &&
compareVersions(latestRelease.tag_name, version) > 0 && (
<a
href={latestRelease.html_url}
target="_blank"
rel="noopener noreferrer"
className="ml-2 text-xs text-theme-500 opacity-50 flex flex-row items-center"
>
<MdNewReleases className="mr-1" /> {t("Update Available")}
</a>
)}
</div>
);
}
+7 -3
View File
@@ -2,6 +2,8 @@ import WeatherApi from "components/widgets/weather/weather";
import OpenWeatherMap from "components/widgets/openweathermap/weather";
import Resources from "components/widgets/resources/resources";
import Search from "components/widgets/search/search";
import Greeting from "components/widgets/greeting/greeting";
import DateTime from "components/widgets/datetime/datetime";
const widgetMappings = {
weather: WeatherApi, // This key will be deprecated in the future
@@ -9,13 +11,15 @@ const widgetMappings = {
openweathermap: OpenWeatherMap,
resources: Resources,
search: Search,
greeting: Greeting,
datetime: DateTime,
};
export default function Widget({ widget }) {
const ServiceWidget = widgetMappings[widget.type];
const InfoWidget = widgetMappings[widget.type];
if (ServiceWidget) {
return <ServiceWidget options={widget.options} />;
if (InfoWidget) {
return <InfoWidget options={widget.options} />;
}
return (
@@ -0,0 +1,36 @@
import { useState, useEffect } from "react";
import { useTranslation } from "react-i18next";
const textSizes = {
"4xl": "text-4xl",
"3xl": "text-3xl",
"2xl": "text-2xl",
xl: "text-xl",
lg: "text-lg",
md: "text-md",
sm: "text-sm",
xs: "text-xs",
};
export default function DateTime({ options }) {
const { text_size: textSize, format } = options;
const { i18n } = useTranslation();
const [date, setDate] = useState(new Date());
useEffect(() => {
const interval = setInterval(() => {
setDate(new Date());
}, 1000);
return () => clearInterval(interval);
}, [setDate]);
const dateFormat = new Intl.DateTimeFormat(i18n.language, { ...format });
return (
<div className="flex flex-row items-center grow justify-end">
<span className={`text-theme-800 dark:text-theme-200 ${textSizes[textSize || "lg"]}`}>
{dateFormat.format(date)}
</span>
</div>
);
}
@@ -0,0 +1,22 @@
const textSizes = {
"4xl": "text-4xl",
"3xl": "text-3xl",
"2xl": "text-2xl",
xl: "text-xl",
lg: "text-lg",
md: "text-md",
sm: "text-sm",
xs: "text-xs",
};
export default function Greeting({ options }) {
if (options.text) {
return (
<div className="flex flex-row items-center justify-start">
<span className={`text-theme-800 dark:text-theme-200 ${textSizes[options.text_size || "xl"]}`}>
{options.text}
</span>
</div>
);
}
}
@@ -1,7 +1,7 @@
import mapIcon from "utils/owm-condition-map";
export default function Icon({ condition, timeOfDay }) {
const Icon = mapIcon(condition, timeOfDay);
const IconComponent = mapIcon(condition, timeOfDay);
return <Icon className="w-10 h-10 text-theme-800 dark:text-theme-200"></Icon>;
return <IconComponent className="w-10 h-10 text-theme-800 dark:text-theme-200" />;
}
@@ -1,22 +1,28 @@
import useSWR from "swr";
import { useState } from "react";
import { BiError } from "react-icons/bi";
import { WiCloudDown } from "react-icons/wi";
import { MdLocationDisabled, MdLocationSearching } from "react-icons/md";
import { useTranslation } from "react-i18next";
import Icon from "./icon";
export default function OpenWeatherMap({ options }) {
function Widget({ options }) {
const { t, i18n } = useTranslation();
const { data, error } = useSWR(
`/api/widgets/openweathermap?lat=${options.latitude}&lon=${options.longitude}&apiKey=${options.apiKey}&duration=${options.cache}&units=${options.units}`
`/api/widgets/openweathermap?${new URLSearchParams({ lang: i18n.language, ...options }).toString()}`
);
if (error || data?.cod == 401) {
if (error || data?.cod === 401 || data?.error) {
return (
<div className="flex flex-col">
<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-col items-center">
<BiError className="w-8 h-8 text-theme-800 dark:text-theme-200" />
<div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-sm">API</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">Error</span>
<span className="text-theme-800 dark:text-theme-200 text-sm">{t("widget.api_error")}</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">-</span>
</div>
</div>
</div>
@@ -25,15 +31,25 @@ export default function OpenWeatherMap({ options }) {
}
if (!data) {
return <div className="flex flex-row items-center"></div>;
return (
<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-col items-center">
<WiCloudDown className="w-8 h-8 text-theme-800 dark:text-theme-200" />
</div>
<div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-sm">{t("weather.updating")}</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">{t("weather.wait")}</span>
</div>
</div>
</div>
);
}
if (data.error) {
return <div className="flex flex-row items-center"></div>;
}
const unit = options.units === "metric" ? "celsius" : "fahrenheit";
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-col items-center">
<Icon
@@ -44,13 +60,63 @@ export default function OpenWeatherMap({ options }) {
<div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-sm">
{options.label && `${options.label}, `}
{data.main.temp.toFixed(1)}&deg;
</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">
{data.weather[0].description.charAt(0).toUpperCase() + data.weather[0].description.slice(1)}
{t("common.number", { value: data.main.temp, style: "unit", unit })}
</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">{data.weather[0].description}</span>
</div>
</div>
</div>
);
}
export default function OpenWeatherMap({ options }) {
const { t } = useTranslation();
const [location, setLocation] = useState(false);
const [requesting, setRequesting] = useState(false);
if (!location && options.latitude && options.longitude) {
setLocation({ latitude: options.latitude, longitude: options.longitude });
}
const requestLocation = () => {
setRequesting(true);
navigator.geolocation.getCurrentPosition(
(position) => {
setLocation({ latitude: position.coords.latitude, longitude: position.coords.longitude });
setRequesting(false);
},
() => {
setRequesting(false);
},
{
enableHighAccuracy: true,
maximumAge: 1000 * 60 * 60 * 3,
timeout: 1000 * 30,
}
);
};
if (!requesting && !location) requestLocation();
if (!location) {
return (
<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-col items-center">
{requesting ? (
<MdLocationSearching className="w-6 h-6 text-theme-800 dark:text-theme-200 animate-pulse" />
) : (
<MdLocationDisabled className="w-6 h-6 text-theme-800 dark:text-theme-200" />
)}
</div>
<div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-sm">{t("weather.current")}</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">{t("weather.allow")}</span>
</div>
</div>
</button>
);
}
return <Widget options={{ ...location, ...options }} />;
}
+39 -17
View File
@@ -1,19 +1,23 @@
import useSWR from "swr";
import { FiCpu } from "react-icons/fi";
import { BiError } from "react-icons/bi";
import { useTranslation } from "react-i18next";
import UsageBar from "./usage-bar";
export default function Cpu({ expanded }) {
const { t } = useTranslation();
export default function Cpu() {
const { data, error } = useSWR(`/api/widgets/resources?type=cpu`, {
refreshInterval: 1500,
});
if (error || data?.error) {
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" />
<div className="flex flex-col ml-3 text-left font-mono">
<span className="text-theme-800 dark:text-theme-200 text-xs">Resources</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">Error</span>
<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>
</div>
</div>
);
@@ -21,26 +25,44 @@ export default function Cpu() {
if (!data) {
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" />
<div className="flex flex-col ml-3 text-left font-mono">
<span className="text-theme-800 dark:text-theme-200 text-xs">- Usage</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">- Load</span>
<div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-xs">-</span>
</div>
</div>
);
}
const percent = data.cpu.usage;
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" />
<div className="flex flex-col ml-3 text-left font-mono">
<span className="text-theme-800 dark:text-theme-200 text-xs">
<span className="whitespace-pre">{`${Math.round(data.cpu.usage)}%`.padEnd(3, " ")} Usage</span>
</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">
{`${(Math.round(data.cpu.load * 100) / 100).toFixed(1)}`.padEnd(3, " ")} Load
</span>
<div className="flex flex-col ml-3 text-left min-w-[85px]">
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
<div className="pl-0.5">
{t("common.number", {
value: data.cpu.usage,
style: "unit",
unit: "percent",
maximumFractionDigits: 0,
})}
</div>
<div className="pr-1">{t("docker.cpu")}</div>
</div>
{expanded && (
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
<div className="pl-0.5">
{t("common.number", {
value: data.cpu.load,
maximumFractionDigits: 2,
})}
</div>
<div className="pr-1">{t("resources.load")}</div>
</div>
)}
<UsageBar percent={percent} />
</div>
</div>
);
+26 -17
View File
@@ -1,20 +1,23 @@
import useSWR from "swr";
import { FiHardDrive } from "react-icons/fi";
import { BiError } from "react-icons/bi";
import { formatBytes } from "utils/stats-helpers";
import { useTranslation } from "react-i18next";
import UsageBar from "./usage-bar";
export default function Disk({ options, expanded }) {
const { t } = useTranslation();
export default function Disk({ options }) {
const { data, error } = useSWR(`/api/widgets/resources?type=disk&target=${options.disk}`, {
refreshInterval: 1500,
});
if (error || data?.error) {
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" />
<div className="flex flex-col ml-3 text-left font-mono">
<span className="text-theme-800 dark:text-theme-200 text-xs">Resources</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">Error</span>
<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>
</div>
</div>
);
@@ -22,26 +25,32 @@ export default function Disk({ options }) {
if (!data) {
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" />
<div className="flex flex-col ml-3 text-left font-mono">
<span className="text-theme-800 dark:text-theme-200 text-xs">- Free</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">- Used</span>
<div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-xs">-</span>
</div>
</div>
);
}
const percent = Math.round((data.drive.usedGb / data.drive.totalGb) * 100);
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" />
<div className="flex flex-col ml-3 text-left font-mono">
<span className="text-theme-800 dark:text-theme-200 text-xs">
{formatBytes(data.drive.freeGb * 1024 * 1024 * 1024)} Free
</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">
{formatBytes(data.drive.usedGb * 1024 * 1024 * 1024)} Used
<div className="flex flex-col ml-3 text-left min-w-[85px]">
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
<div className="pl-0.5">{t("common.bytes", { value: data.drive.freeGb * 1024 * 1024 * 1024 })}</div>
<div className="pr-1">{t("resources.free")}</div>
</span>
{expanded && (
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
<div className="pl-0.5">{t("common.bytes", { value: data.drive.totalGb * 1024 * 1024 * 1024 })}</div>
<div className="pr-1">{t("resources.total")}</div>
</span>
)}
<UsageBar percent={percent} />
</div>
</div>
);
+34 -17
View File
@@ -1,20 +1,23 @@
import useSWR from "swr";
import { FaMemory } from "react-icons/fa";
import { BiError } from "react-icons/bi";
import { formatBytes } from "utils/stats-helpers";
import { useTranslation } from "react-i18next";
import UsageBar from "./usage-bar";
export default function Memory({ expanded }) {
const { t } = useTranslation();
export default function Memory() {
const { data, error } = useSWR(`/api/widgets/resources?type=memory`, {
refreshInterval: 1500,
});
if (error || data?.error) {
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" />
<div className="flex flex-col ml-3 text-left font-mono">
<span className="text-theme-800 dark:text-theme-200 text-xs">Resources</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">Error</span>
<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>
</div>
</div>
);
@@ -22,26 +25,40 @@ export default function Memory() {
if (!data) {
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" />
<div className="flex flex-col ml-3 text-left font-mono">
<span className="text-theme-800 dark:text-theme-200 text-xs">- GB Used</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">- GB Free</span>
<div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-xs">-</span>
</div>
</div>
);
}
const percent = Math.round((data.memory.usedMemMb / data.memory.totalMemMb) * 100);
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" />
<div className="flex flex-col ml-3 text-left font-mono">
<span className="text-theme-800 dark:text-theme-200 text-xs">
{formatBytes(data.memory.usedMemMb * 1024 * 1024)} Used
</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">
{formatBytes(data.memory.freeMemMb * 1024 * 1024)} Free
<div className="flex flex-col ml-3 text-left min-w-[85px]">
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
<div className="pl-0.5">
{t("common.bytes", { value: data.memory.freeMemMb * 1024 * 1024, maximumFractionDigits: 0, binary: true })}
</div>
<div className="pr-1">{t("resources.free")}</div>
</span>
{expanded && (
<span className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
<div className="pl-0.5">
{t("common.bytes", {
value: data.memory.totalMemMb * 1024 * 1024,
maximumFractionDigits: 0,
binary: true,
})}
</div>
<div className="pr-1">{t("resources.total")}</div>
</span>
)}
<UsageBar percent={percent} />
</div>
</div>
);
+12 -13
View File
@@ -3,20 +3,19 @@ import Cpu from "./cpu";
import Memory from "./memory";
export default function Resources({ options }) {
const { expanded } = options;
return (
<>
<div className="flex flex-col max-w:full basis-1/2 sm:basis-auto self-center">
<div className="flex flex-row space-x-4 self-center">
{options.cpu && <Cpu />}
{options.memory && <Memory />}
{options.disk && <Disk options={options} />}
</div>
{options.label && (
<div className="border-t-2 border-theme-800 dark:border-theme-200 mt-1 pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">
{options.label}
</div>
)}
<div className="flex flex-col max-w:full sm:basis-auto self-center m-auto flex-wrap">
<div className="flex flex-row self-center flex-wrap justify-between">
{options.cpu && <Cpu expanded={expanded} />}
{options.memory && <Memory expanded={expanded} />}
{Array.isArray(options.disk)
? options.disk.map((disk) => <Disk key={disk} options={{ disk }} expanded={expanded} />)
: options.disk && <Disk options={options} expanded={expanded} />}
</div>
</>
{options.label && (
<div className="ml-6 pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">{options.label}</div>
)}
</div>
);
}
@@ -0,0 +1,12 @@
export default function UsageBar({ percent }) {
return (
<div className="mt-0.5 w-full bg-theme-800/30 rounded-full h-1 dark:bg-white/20">
<div
className="bg-theme-800/70 h-1 rounded-full dark:bg-white/50"
style={{
width: `${percent}%`,
}}
/>
</div>
);
}
+27 -9
View File
@@ -1,4 +1,5 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { FiSearch } from "react-icons/fi";
import { SiDuckduckgo, SiMicrosoftbing, SiGoogle } from "react-icons/si";
@@ -26,11 +27,13 @@ const providers = {
};
export default function Search({ options }) {
const { t } = useTranslation();
const provider = providers[options.provider];
const [query, setQuery] = useState("");
if (!provider) {
return <></>;
return null;
}
function handleSubmit(event) {
@@ -48,21 +51,36 @@ export default function Search({ options }) {
}
return (
<form className="flex-col relative h-8 my-4 min-w-full md:min-w-fit grow" onSubmit={handleSubmit}>
<div className="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none w-full text-theme-800 dark:text-theme-200"></div>
<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" />
<input
type="search"
autoFocus
className={`overflow-hidden w-full placeholder-theme-900 text-xs text-theme-900 bg-theme-50 rounded-md border border-theme-300 focus:ring-theme-500 focus:border-theme-500 dark:bg-theme-800 dark:border-theme-600 dark:placeholder-theme-400 dark:text-white dark:focus:ring-theme-500 dark:focus:border-theme-500 h-full`}
placeholder="Search..."
type="text"
className="
overflow-hidden w-full h-full rounded-md
text-xs text-theme-900 dark:text-white
placeholder-theme-900 dark:placeholder-white/80
bg-white/50 dark:bg-white/10
focus:ring-theme-500 dark:focus:ring-white/50
focus:border-theme-500 dark:focus:border-white/50
border border-theme-300 dark:border-theme-200/50"
placeholder={t("search.placeholder")}
onChange={(s) => setQuery(s.currentTarget.value)}
required
autoCapitalize="off"
autoCorrect="off"
autoComplete="off"
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={options.focus}
/>
<button
type="submit"
className="text-white absolute right-0.5 bottom-0.5 bg-theme-700 hover:bg-theme-800 border-1 focus:ring-2 focus:ring-theme-300 font-medium rounded-r-md text-sm px-4 py-2 dark:bg-theme-600 dark:hover:bg-theme-700 dark:focus:ring-theme-500"
className="
absolute right-0.5 bottom-0.5 rounded-r-md px-4 py-2 border-1
text-white font-medium text-sm
bg-theme-600/40 dark:bg-white/10
focus:ring-theme-500 dark:focus:ring-white/50"
>
<provider.icon className="text-theme-800 dark:text-theme-200 w-3 h-3" />
<provider.icon className="text-white w-3 h-3" />
</button>
</form>
);
+2 -2
View File
@@ -1,7 +1,7 @@
import mapIcon from "utils/condition-map";
export default function Icon({ condition, timeOfDay }) {
const Icon = mapIcon(condition, timeOfDay);
const IconComponent = mapIcon(condition, timeOfDay);
return <Icon className="w-10 h-10 text-theme-800 dark:text-theme-200"></Icon>;
return <IconComponent className="w-10 h-10 text-theme-800 dark:text-theme-200" />;
}
+84 -12
View File
@@ -1,22 +1,28 @@
import useSWR from "swr";
import { useState } from "react";
import { BiError } from "react-icons/bi";
import { WiCloudDown } from "react-icons/wi";
import { MdLocationDisabled, MdLocationSearching } from "react-icons/md";
import { useTranslation } from "react-i18next";
import Icon from "./icon";
export default function WeatherApi({ options }) {
function Widget({ options }) {
const { t, i18n } = useTranslation();
const { data, error } = useSWR(
`/api/widgets/weather?lat=${options.latitude}&lon=${options.longitude}&apiKey=${options.apiKey}&duration=${options.cache}`
`/api/widgets/weather?${new URLSearchParams({ lang: i18n.language, ...options }).toString()}`
);
if (error) {
if (error || data?.error) {
return (
<div className="flex flex-col">
<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-col items-center">
<BiError className="w-8 h-8 text-theme-800 dark:text-theme-200" />
<div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-sm">API</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">Error</span>
<span className="text-theme-800 dark:text-theme-200 text-sm">{t("widget.api_error")}</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">-</span>
</div>
</div>
</div>
@@ -25,15 +31,25 @@ export default function WeatherApi({ options }) {
}
if (!data) {
return <div className="flex flex-row items-center justify-end"></div>;
return (
<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-col items-center">
<WiCloudDown className="w-8 h-8 text-theme-800 dark:text-theme-200" />
</div>
<div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-sm">{t("weather.updating")}</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">{t("weather.wait")}</span>
</div>
</div>
</div>
);
}
if (data.error) {
return <div className="flex flex-row items-center justify-end"></div>;
}
const unit = options.units === "metric" ? "celsius" : "fahrenheit";
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-col items-center">
<Icon condition={data.current.condition.code} timeOfDay={data.current.is_day ? "day" : "night"} />
@@ -41,7 +57,11 @@ export default function WeatherApi({ options }) {
<div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-sm">
{options.label && `${options.label}, `}
{options.units === "metric" ? data.current.temp_c : data.current.temp_f}&deg;
{t("common.number", {
value: options.units === "metric" ? data.current.temp_c : data.current.temp_f,
style: "unit",
unit,
})}
</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">{data.current.condition.text}</span>
</div>
@@ -49,3 +69,55 @@ export default function WeatherApi({ options }) {
</div>
);
}
export default function WeatherApi({ options }) {
const { t } = useTranslation();
const [location, setLocation] = useState(false);
const [requesting, setRequesting] = useState(false);
if (!location && options.latitude && options.longitude) {
setLocation({ latitude: options.latitude, longitude: options.longitude });
}
const requestLocation = () => {
setRequesting(true);
navigator.geolocation.getCurrentPosition(
(position) => {
setLocation({ latitude: position.coords.latitude, longitude: position.coords.longitude });
setRequesting(false);
},
() => {
setRequesting(false);
},
{
enableHighAccuracy: true,
maximumAge: 1000 * 60 * 60 * 3,
timeout: 1000 * 30,
}
);
};
if (!requesting && !location) requestLocation();
if (!location) {
return (
<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-col items-center">
{requesting ? (
<MdLocationSearching className="w-6 h-6 text-theme-800 dark:text-theme-200 animate-pulse" />
) : (
<MdLocationDisabled className="w-6 h-6 text-theme-800 dark:text-theme-200" />
)}
</div>
<div className="flex flex-col ml-3 text-left">
<span className="text-theme-800 dark:text-theme-200 text-sm">{t("weather.current")}</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">{t("weather.allow")}</span>
</div>
</div>
</button>
);
}
return <Widget options={{ ...location, ...options }} />;
}
-18
View File
@@ -1,18 +0,0 @@
import { SWRConfig } from "swr";
import "styles/globals.css";
import "styles/weather-icons.css";
import "styles/theme.css";
function MyApp({ Component, pageProps }) {
return (
<SWRConfig
value={{
fetcher: (resource, init) => fetch(resource, init).then((res) => res.json()),
}}
>
<Component {...pageProps} />
</SWRConfig>
);
}
export default MyApp;
+31
View File
@@ -0,0 +1,31 @@
/* eslint-disable react/jsx-props-no-spreading */
import { SWRConfig } from "swr";
import "styles/globals.css";
import "styles/weather-icons.css";
import "styles/theme.css";
import "utils/i18n";
import { ColorProvider } from "utils/color-context";
import { ThemeProvider } from "utils/theme-context";
import { SettingsProvider } from "utils/settings-context";
function MyApp({ Component, pageProps }) {
return (
<SWRConfig
value={{
fetcher: (resource, init) => fetch(resource, init).then((res) => res.json()),
}}
>
<ColorProvider>
<ThemeProvider>
<SettingsProvider>
<Component {...pageProps} />
</SettingsProvider>
</ThemeProvider>
</ColorProvider>
</SWRConfig>
);
}
export default MyApp;
@@ -11,7 +11,7 @@ export default function Document() {
rel="stylesheet"
/>
</Head>
<body className="w-full h-full bg-theme-50 dark:bg-theme-800 transition duration-150 ease-in-out">
<body className="relative w-full h-full bg-theme-50 dark:bg-theme-800 transition duration-150 ease-in-out">
<Main />
<NextScript />
</body>
+9 -11
View File
@@ -1,6 +1,8 @@
import { promises as fs } from "fs";
import path from "path";
import yaml from "js-yaml";
import checkAndCopyConfig from "utils/config";
export default async function handler(req, res) {
@@ -11,17 +13,13 @@ export default async function handler(req, res) {
const bookmarks = yaml.load(fileContents);
// map easy to write YAML objects into easy to consume JS arrays
const bookmarksArray = bookmarks.map((group) => {
return {
name: Object.keys(group)[0],
bookmarks: group[Object.keys(group)[0]].map((entries) => {
return {
name: Object.keys(entries)[0],
...entries[Object.keys(entries)[0]][0],
};
}),
};
});
const bookmarksArray = bookmarks.map((group) => ({
name: Object.keys(group)[0],
bookmarks: group[Object.keys(group)[0]].map((entries) => ({
name: Object.keys(entries)[0],
...entries[Object.keys(entries)[0]][0],
})),
}));
res.send(bookmarksArray);
}
+10 -9
View File
@@ -1,4 +1,5 @@
import Docker from "dockerode";
import getDockerArguments from "utils/docker";
export default async function handler(req, res) {
@@ -13,7 +14,7 @@ export default async function handler(req, res) {
}
try {
const docker = new Docker(await getDockerArguments(containerServer));
const docker = new Docker(getDockerArguments(containerServer));
const containers = await docker.listContainers({
all: true,
});
@@ -21,30 +22,30 @@ export default async function handler(req, res) {
// bad docker connections can result in a <Buffer ...> object?
// in any case, this ensures the result is the expected array
if (!Array.isArray(containers)) {
return res.status(500).send({
res.status(500).send({
error: "query failed",
});
return;
}
const containerNames = containers.map((container) => {
return container.Names[0].replace(/^\//, "");
});
const containerNames = containers.map((container) => container.Names[0].replace(/^\//, ""));
const containerExists = containerNames.includes(containerName);
if (!containerExists) {
return res.status(200).send({
res.status(200).send({
error: "not found",
});
return;
}
const container = docker.getContainer(containerName);
const stats = await container.stats({ stream: false });
return res.status(200).json({
stats: stats,
res.status(200).json({
stats,
});
} catch {
return res.status(500).send({
res.status(500).send({
error: "unknown error",
});
}
+3 -4
View File
@@ -1,4 +1,5 @@
import Docker from "dockerode";
import getDockerArguments from "utils/docker";
export default async function handler(req, res) {
@@ -12,7 +13,7 @@ export default async function handler(req, res) {
}
try {
const docker = new Docker(await getDockerArguments(containerServer));
const docker = new Docker(getDockerArguments(containerServer));
const containers = await docker.listContainers({
all: true,
});
@@ -25,9 +26,7 @@ export default async function handler(req, res) {
});
}
const containerNames = containers.map((container) => {
return container.Names[0].replace(/^\//, "");
});
const containerNames = containers.map((container) => container.Names[0].replace(/^\//, ""));
const containerExists = containerNames.includes(containerName);
if (!containerExists) {
+3
View File
@@ -0,0 +1,3 @@
export default function handler(req, res) {
res.send("up");
}
Loaded 100 of 140 files, more files were not shown because too many files have changed in this diff. Show more