Compare commits

..

127 Commits

Author SHA1 Message Date
Ben Phelps
4ea279856f Merge pull request #377 from shamoon/unified-infowidget-settings
Feature: Unified info widget settings
2022-10-13 16:13:43 +03:00
GodD6366
c7d1c05e6e Fix the page auto zoom when ios input focus (#379) 2022-10-12 18:03:09 +03:00
Michael Shamoon
48a09e5a99 Refactor info widget sanitizing / privateOptions 2022-10-12 01:53:57 -07:00
Michael Shamoon
dced918804 Update unifi_console to use private widget options 2022-10-12 01:53:57 -07:00
Anonymous
9dd259654a Translated using Weblate (Bulgarian)
Currently translated at 33.3% (46 of 138 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/bg/
2022-10-12 10:20:27 +02:00
Anonymous
8f644ef7a4 Translated using Weblate (Telugu)
Currently translated at 97.8% (135 of 138 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/te/
2022-10-12 10:20:27 +02:00
Anonymous
61e82e2b2c Translated using Weblate (Finnish)
Currently translated at 89.1% (123 of 138 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fi/
2022-10-12 10:20:26 +02:00
Anonymous
17ec656b9b Translated using Weblate (Yue)
Currently translated at 85.5% (118 of 138 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/yue/
2022-10-12 10:20:26 +02:00
Anonymous
9669c463da Translated using Weblate (Portuguese (Brazil))
Currently translated at 83.3% (115 of 138 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pt_BR/
2022-10-12 10:20:26 +02:00
Anonymous
02dd6d08b4 Translated using Weblate (Romanian)
Currently translated at 85.5% (118 of 138 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ro/
2022-10-12 10:20:26 +02:00
Anonymous
a8bd84467c Translated using Weblate (Hebrew)
Currently translated at 73.1% (101 of 138 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/he/
2022-10-12 10:20:25 +02:00
Anonymous
770d52cbae Translated using Weblate (Hungarian)
Currently translated at 78.2% (108 of 138 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/hu/
2022-10-12 10:20:25 +02:00
Anonymous
73a200994c Translated using Weblate (Croatian)
Currently translated at 83.3% (115 of 138 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/hr/
2022-10-12 10:20:25 +02:00
Anonymous
c1d14e3b16 Translated using Weblate (Swedish)
Currently translated at 77.5% (107 of 138 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/sv/
2022-10-12 10:20:25 +02:00
Anonymous
e7215feee1 Translated using Weblate (Polish)
Currently translated at 71.0% (98 of 138 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pl/
2022-10-12 10:20:25 +02:00
Anonymous
be4d02f5e9 Translated using Weblate (Catalan)
Currently translated at 88.4% (122 of 138 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ca/
2022-10-12 10:20:24 +02:00
Anonymous
92e47c45f9 Translated using Weblate (Chinese (Traditional))
Currently translated at 6.5% (9 of 138 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hant/
2022-10-12 10:20:24 +02:00
Anonymous
73b44c3a2e Translated using Weblate (Dutch)
Currently translated at 45.6% (63 of 138 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nl/
2022-10-12 10:20:24 +02:00
Anonymous
bf666b3ec4 Translated using Weblate (Vietnamese)
Currently translated at 31.8% (44 of 138 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/vi/
2022-10-12 10:20:24 +02:00
Anonymous
69a67f450d Translated using Weblate (Norwegian Bokmål)
Currently translated at 57.2% (79 of 138 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nb_NO/
2022-10-12 10:20:23 +02:00
Anonymous
73e1d3dc20 Translated using Weblate (Italian)
Currently translated at 55.7% (77 of 138 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/it/
2022-10-12 10:20:23 +02:00
Anonymous
95f811e4c1 Translated using Weblate (Chinese (Simplified))
Currently translated at 89.1% (123 of 138 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-10-12 10:20:23 +02:00
Anonymous
65447f2b97 Translated using Weblate (Russian)
Currently translated at 16.6% (23 of 138 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ru/
2022-10-12 10:20:23 +02:00
Anonymous
b6dd2975e7 Translated using Weblate (Portuguese)
Currently translated at 83.3% (115 of 138 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pt/
2022-10-12 10:20:23 +02:00
Anonymous
911bd31c14 Translated using Weblate (French)
Currently translated at 97.8% (135 of 138 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-10-12 10:20:22 +02:00
Anonymous
072e29025e Translated using Weblate (Spanish)
Currently translated at 87.6% (121 of 138 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-10-12 10:20:22 +02:00
Anonymous
7bb8274b95 Translated using Weblate (German)
Currently translated at 85.5% (118 of 138 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/de/
2022-10-12 10:20:22 +02:00
hunkyn
280c8b0147 Translated using Weblate (Telugu)
Currently translated at 100.0% (135 of 135 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/te/
2022-10-12 10:20:13 +02:00
GM
46a29ea7d6 Translated using Weblate (Chinese (Simplified))
Currently translated at 91.1% (123 of 135 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-10-12 10:20:13 +02:00
Ben Phelps
b259ecf870 Merge pull request #369 from shamoon/glances-info-widget
Feature: Glances info widget
2022-10-12 11:17:25 +03:00
Michael Shamoon
c1a55ff6d3 Update glances to use private widget options API 2022-10-11 12:16:18 -07:00
Michael Shamoon
7c39cd8960 Private widget options API 2022-10-11 12:16:18 -07:00
Ben Phelps
2bdd9eaa88 remove shortcuts from manifest 2022-10-11 21:56:48 +03:00
Michael Shamoon
8e2ff61f1c Allow multiple glances widgets with optional id property 2022-10-11 10:21:57 -07:00
Michael Shamoon
99b70f96e4 Allow username + password for glances 2022-10-11 10:21:57 -07:00
Michael Shamoon
802fe0f721 Glances widget use settings for URL 2022-10-11 10:21:57 -07:00
Michael Shamoon
080bc44a6f Lint glances info widget 2022-10-11 10:21:57 -07:00
Michael Shamoon
321efd08cc Glances info widget 2022-10-11 10:21:57 -07:00
Jason Fischer
37fed0ce01 Merge pull request #372 from shamoon/plex-service-widget-key
Fix: change Plex service widget to use "key" not "token"
2022-10-11 08:12:16 -07:00
Michael Shamoon
778261f67e Change Plex service widget to use key not token 2022-10-11 07:08:29 -07:00
Ben Phelps
c95422b682 impl. mdi icons with the possibility for others 2022-10-11 15:02:38 +03:00
Lalyu Lalev
248c18d978 Translated using Weblate (Bulgarian)
Currently translated at 34.0% (46 of 135 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/bg/
2022-10-11 02:17:31 +02:00
hunkyn
add17be5ba Translated using Weblate (Telugu)
Currently translated at 100.0% (135 of 135 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/te/
2022-10-11 02:17:31 +02:00
Anonymous
c7b5ec33a8 Translated using Weblate (Bulgarian)
Currently translated at 100.0% (0 of 0 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/bg/
2022-10-10 16:55:08 +02:00
Nonoss117
0625ce2bb9 Translated using Weblate (French)
Currently translated at 100.0% (135 of 135 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-10-10 16:55:06 +02:00
Lalyu Lalev
860ae7f0f7 Added translation using Weblate (Bulgarian) 2022-10-10 16:54:57 +02:00
Anonymous
094b916c9e Translated using Weblate (Telugu)
Currently translated at 86.6% (117 of 135 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/te/
2022-10-10 15:33:21 +02:00
Anonymous
f50130971d Translated using Weblate (Finnish)
Currently translated at 91.1% (123 of 135 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fi/
2022-10-10 15:33:21 +02:00
Anonymous
05183a8861 Translated using Weblate (Yue)
Currently translated at 87.4% (118 of 135 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/yue/
2022-10-10 15:33:21 +02:00
Anonymous
92ffb8d082 Translated using Weblate (Portuguese (Brazil))
Currently translated at 85.1% (115 of 135 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pt_BR/
2022-10-10 15:33:21 +02:00
Anonymous
9d79e7e72d Translated using Weblate (Romanian)
Currently translated at 87.4% (118 of 135 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ro/
2022-10-10 15:33:20 +02:00
Anonymous
6b201028cd Translated using Weblate (Hebrew)
Currently translated at 74.8% (101 of 135 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/he/
2022-10-10 15:33:20 +02:00
Anonymous
46e0732bd1 Translated using Weblate (Hungarian)
Currently translated at 80.0% (108 of 135 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/hu/
2022-10-10 15:33:20 +02:00
Anonymous
375e513246 Translated using Weblate (Croatian)
Currently translated at 85.1% (115 of 135 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/hr/
2022-10-10 15:33:20 +02:00
Anonymous
2c80dc63d1 Translated using Weblate (Swedish)
Currently translated at 79.2% (107 of 135 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/sv/
2022-10-10 15:33:19 +02:00
Anonymous
d8a4d1ef5d Translated using Weblate (Polish)
Currently translated at 72.5% (98 of 135 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pl/
2022-10-10 15:33:19 +02:00
Anonymous
2e4e998654 Translated using Weblate (Catalan)
Currently translated at 90.3% (122 of 135 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ca/
2022-10-10 15:33:19 +02:00
Anonymous
5b66941774 Translated using Weblate (Chinese (Traditional))
Currently translated at 6.6% (9 of 135 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hant/
2022-10-10 15:33:18 +02:00
Anonymous
6f34ca50e0 Translated using Weblate (Dutch)
Currently translated at 46.6% (63 of 135 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nl/
2022-10-10 15:33:18 +02:00
Anonymous
59af5e1eb8 Translated using Weblate (Vietnamese)
Currently translated at 32.5% (44 of 135 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/vi/
2022-10-10 15:33:17 +02:00
Anonymous
b34a44cf9f Translated using Weblate (Norwegian Bokmål)
Currently translated at 58.5% (79 of 135 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nb_NO/
2022-10-10 15:33:17 +02:00
Anonymous
45c92ef2f7 Translated using Weblate (Italian)
Currently translated at 57.0% (77 of 135 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/it/
2022-10-10 15:33:17 +02:00
Anonymous
8854fcdb9b Translated using Weblate (Chinese (Simplified))
Currently translated at 90.3% (122 of 135 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-10-10 15:33:17 +02:00
Anonymous
8299c6ce8d Translated using Weblate (Russian)
Currently translated at 17.0% (23 of 135 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ru/
2022-10-10 15:33:16 +02:00
Anonymous
d5861ea52b Translated using Weblate (Portuguese)
Currently translated at 85.1% (115 of 135 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pt/
2022-10-10 15:33:16 +02:00
Anonymous
855665689c Translated using Weblate (French)
Currently translated at 91.1% (123 of 135 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-10-10 15:33:16 +02:00
Anonymous
a4795c21dc Translated using Weblate (Spanish)
Currently translated at 89.6% (121 of 135 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-10-10 15:33:16 +02:00
Anonymous
2e2aeef77b Translated using Weblate (German)
Currently translated at 87.4% (118 of 135 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/de/
2022-10-10 15:33:15 +02:00
Ben Phelps
5198b056cc Merge pull request #366 from shamoon/plex-service-widget
Feature: Plex service widget without Tautulli
2022-10-10 15:39:03 +03:00
Michael Shamoon
3c23e59a70 lint 2022-10-10 01:15:30 -07:00
Michael Shamoon
2440da8e08 Plex service widget without Tautulli 2022-10-10 00:31:50 -07:00
Anonymous
91795f2d07 Translated using Weblate (Telugu)
Currently translated at 88.6% (117 of 132 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/te/
2022-10-10 07:12:53 +02:00
Anonymous
9f55ec9a63 Translated using Weblate (Finnish)
Currently translated at 93.1% (123 of 132 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fi/
2022-10-10 07:12:53 +02:00
Anonymous
efeaa995e2 Translated using Weblate (Yue)
Currently translated at 89.3% (118 of 132 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/yue/
2022-10-10 07:12:53 +02:00
Anonymous
1524a6a6cf Translated using Weblate (Portuguese (Brazil))
Currently translated at 87.1% (115 of 132 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pt_BR/
2022-10-10 07:12:52 +02:00
Anonymous
702ede8b9b Translated using Weblate (Romanian)
Currently translated at 89.3% (118 of 132 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ro/
2022-10-10 07:12:52 +02:00
Anonymous
7e6fcd8418 Translated using Weblate (Hebrew)
Currently translated at 76.5% (101 of 132 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/he/
2022-10-10 07:12:52 +02:00
Anonymous
1f006a4093 Translated using Weblate (Hungarian)
Currently translated at 81.8% (108 of 132 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/hu/
2022-10-10 07:12:51 +02:00
Anonymous
d4a5245d1a Translated using Weblate (Croatian)
Currently translated at 87.1% (115 of 132 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/hr/
2022-10-10 07:12:51 +02:00
Anonymous
60519a2881 Translated using Weblate (Swedish)
Currently translated at 81.0% (107 of 132 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/sv/
2022-10-10 07:12:51 +02:00
Anonymous
182b66b53f Translated using Weblate (Polish)
Currently translated at 74.2% (98 of 132 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pl/
2022-10-10 07:12:50 +02:00
Anonymous
4b110cf7a8 Translated using Weblate (Catalan)
Currently translated at 92.4% (122 of 132 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ca/
2022-10-10 07:12:50 +02:00
Anonymous
dc0744f9d7 Translated using Weblate (Chinese (Traditional))
Currently translated at 6.8% (9 of 132 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hant/
2022-10-10 07:12:50 +02:00
Anonymous
90cd18735a Translated using Weblate (Dutch)
Currently translated at 47.7% (63 of 132 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nl/
2022-10-10 07:12:49 +02:00
Anonymous
494b2ebd08 Translated using Weblate (Vietnamese)
Currently translated at 33.3% (44 of 132 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/vi/
2022-10-10 07:12:49 +02:00
Anonymous
797b966267 Translated using Weblate (Norwegian Bokmål)
Currently translated at 59.8% (79 of 132 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/nb_NO/
2022-10-10 07:12:49 +02:00
Anonymous
b13754f174 Translated using Weblate (Italian)
Currently translated at 58.3% (77 of 132 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/it/
2022-10-10 07:12:48 +02:00
Anonymous
c9e7845154 Translated using Weblate (Chinese (Simplified))
Currently translated at 92.4% (122 of 132 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-10-10 07:12:48 +02:00
Anonymous
a6f7c48cc3 Translated using Weblate (Russian)
Currently translated at 17.4% (23 of 132 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ru/
2022-10-10 07:12:47 +02:00
Anonymous
7874eb1467 Translated using Weblate (Portuguese)
Currently translated at 87.1% (115 of 132 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/pt/
2022-10-10 07:12:47 +02:00
Anonymous
69626157ed Translated using Weblate (French)
Currently translated at 93.1% (123 of 132 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-10-10 07:12:47 +02:00
Anonymous
6dc0eac9f8 Translated using Weblate (Spanish)
Currently translated at 91.6% (121 of 132 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-10-10 07:12:46 +02:00
Anonymous
a6fc171539 Translated using Weblate (German)
Currently translated at 89.3% (118 of 132 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/de/
2022-10-10 07:12:46 +02:00
hunkyn
a8a715dd30 Translated using Weblate (Telugu)
Currently translated at 95.1% (117 of 123 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/te/
2022-10-10 07:11:36 +02:00
Anonymous
4ac6e43343 Translated using Weblate (Telugu)
Currently translated at 100.0% (0 of 0 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/te/
2022-10-10 07:11:36 +02:00
Kai Huuhko
13ee38bd12 Translated using Weblate (Finnish)
Currently translated at 100.0% (123 of 123 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fi/
2022-10-10 07:11:36 +02:00
Juan Manuel Bennàssar Carretero
d186ff16dd Translated using Weblate (Catalan)
Currently translated at 99.1% (122 of 123 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/ca/
2022-10-10 07:11:36 +02:00
yahoo~~
49a3f3e249 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.1% (122 of 123 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/zh_Hans/
2022-10-10 07:11:36 +02:00
Nonoss117
2d14230763 Translated using Weblate (French)
Currently translated at 100.0% (123 of 123 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/fr/
2022-10-10 07:11:36 +02:00
Juan Manuel Bennàssar Carretero
ce86d06006 Translated using Weblate (Spanish)
Currently translated at 98.3% (121 of 123 strings)

Translation: Homepage/Homepage
Translate-URL: https://hosted.weblate.org/projects/homepage/homepage/es/
2022-10-10 07:11:36 +02:00
hunkyn
c1d476997b Added translation using Weblate (Telugu) 2022-10-10 07:11:36 +02:00
Jason Fischer
a79e9cd342 Merge pull request #364 from JazzFisch/fix-unifi-field-visibility
Decompose i18n labels for Unifi widget
2022-10-09 20:21:08 -07:00
Jason Fischer
260201c2b4 Decompose i18n labels for Unifi widget
- Needed to decompose i18n labels for Unifi widget in order for field visibility setting to work correctly
- Fixed weird edge case where a call to cached-fetch would fail if no duration was passed
- Have VS Code hide the .next and node_modules folders from tree view
2022-10-09 20:05:28 -07:00
Jason Fischer
85df467fdb Merge pull request #341 from shamoon/main
Feature: UniFi Console Info & Service Widgets
2022-10-09 15:37:32 -07:00
Michael Shamoon
04da8f3925 lint 2022-10-09 15:13:27 -07:00
Michael Shamoon
86b12debc5 Revert "Infer unifi port" 2022-10-09 09:18:12 -07:00
Michael Shamoon
69c9a449b1 Revert "Rename unifi_console info widget to unifi" 2022-10-09 09:18:12 -07:00
Jason Fischer
0c20040f57 Merge pull request #360 from pacoculebras/patch-1
Update README.md
2022-10-09 08:57:00 -07:00
Paco Culebras
dedd725fae Update README.md
Add Catalan translation
2022-10-09 16:47:21 +02:00
Ben Phelps
fe7fa5c060 fix network stats when using podman #254 2022-10-09 13:34:08 +03:00
Ben Phelps
d2dc51d49c add services and bookmarks as manifest shortcuts 2022-10-09 13:31:00 +03:00
Ben Phelps
d26ae30fa6 remove old code 2022-10-09 13:30:37 +03:00
Michael Shamoon
fbac27f504 Fix unifi service lan/wlan labels 2022-10-08 17:47:52 -07:00
Michael Shamoon
d1b6dad14d Infer unifi port, use api widget property for login 2022-10-08 14:36:08 -07:00
Michael Shamoon
fe1064b173 Rename unifi_console info widget to unifi 2022-10-08 14:02:12 -07:00
Jason Fischer
42da3eca28 Standardize on double quoted strings 2022-10-08 13:59:09 -07:00
Jason Fischer
7ee3113d8a Rename unifi_console service widget to unifi
- This rename is necessary as the Unifi service widet gets its config data from a different location than the unifi_console information widget
2022-10-08 13:58:19 -07:00
Jason Fischer
2bd9c8eddc Cache console version check result 2022-10-08 13:52:22 -07:00
shamoon
1249ecaa68 Merge branch 'main' into main 2022-10-08 13:20:09 -07:00
Ben Phelps
ba428cf3ae fix docker service discovery #343 2022-10-08 23:10:41 +03:00
Ben Phelps
f06be8ca94 update readme 2022-10-08 23:02:09 +03:00
Michael Shamoon
e03822df6e Add UniFI console service widget 2022-10-08 00:45:00 -07:00
Michael Shamoon
ad1d1e751d Refactor unifi proxy for udm-pro compatibility 2022-10-07 23:26:16 -07:00
Jason Fischer
ac4dcd3222 Remove node-unifi package dependency
- Add custom Unifi proxy built on existing cookie jar and httpProxy
- Change formatApiCall to emit empty string instead of undefined on missing key
2022-10-07 17:12:29 -07:00
Michael Shamoon
952f0295cc uptime label 2022-10-05 21:44:15 -07:00
Michael Shamoon
5ba75bc62d support wan down too 2022-10-05 21:41:39 -07:00
Michael Shamoon
b9b9449cd3 Add unifi_console widget, API endpoint 2022-10-05 21:36:43 -07:00
51 changed files with 1678 additions and 62 deletions

6
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"files.exclude": {
"**/.next": true,
"**/node_modules": true
}
}

View File

@@ -6,7 +6,7 @@
</p>
<p align="center">
A modern <em>(fully static, fast)</em>, secure <em>(fully proxied)</em>, highly customizable application dashboard with integrations for more than 25 services and translations for 15 languages. Easily configured via YAML files (or discovery via docker labels).
A modern <em>(fully static, fast)</em>, secure <em>(fully proxied)</em>, highly customizable application dashboard with integrations for more than 25 services and translations for over 15 languages. Easily configured via YAML files (or discovery via docker labels).
</p>
<p align="center">
@@ -38,7 +38,7 @@
- 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
- Translantions for Chinese, Dutch, Finnish, French, German, Hebrew, Hungarian, Norwegian Bokmål, Polish, Portuguese, Portuguese (Brazil), Romainian, Russian, Spanish, Swedish and Yue
- Translantions for Catalan, Chinese, Dutch, Finnish, French, German, Hebrew, Hungarian, Norwegian Bokmål, Polish, Portuguese, Portuguese (Brazil), Romainian, Russian, Spanish, Swedish and Yue
- Want to help translate? [Join the Weblate project](https://hosted.weblate.org/engage/homepage/)
- Service & Web Bookmarks
- Docker Integration
@@ -47,7 +47,7 @@
- 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, Authentic
- Portainer, Traefik, Speedtest Tracker, PiHole, AdGuard Home, Nginx Proxy Manager, Gotify, Syncthing Relay Server, Authentic, Proxmox
- Information Providers
- Coin Market Cap, Mastodon
- Information & Utility Widgets
@@ -152,7 +152,7 @@ Huge thanks to the all the contributors who have helped make this project what i
- [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
- [JazzFisch](https://github.com/benphelps/homepage/commits?author=JazzFisch) - Readarr, Bazarr, Lidarr, SABnzbd, Transmission, qBittorrent Integrations & countless more improvements
- [JazzFisch](https://github.com/benphelps/homepage/commits?author=JazzFisch) - Readarr, Bazarr, Lidarr, SABnzbd, Transmission, qBittorrent, Proxmox Integrations & countless more improvements
- [josways](https://github.com/benphelps/homepage/commits?author=josways) - Baidu search provider
- [mauricio-kalil](https://github.com/benphelps/homepage/commits?author=mauricio-kalil) - Portuguese (Brazil)
- [modem7](https://github.com/benphelps/homepage/commits?author=modem7) - Impvoed Docker Image

View File

@@ -32,7 +32,8 @@
"shvl": "^3.0.0",
"swr": "^1.3.0",
"tough-cookie": "^4.1.2",
"winston": "^3.8.2"
"winston": "^3.8.2",
"xml-js": "^1.6.11"
},
"devDependencies": {
"@tailwindcss/forms": "^0.5.3",

13
pnpm-lock.yaml generated
View File

@@ -40,6 +40,7 @@ specifiers:
tough-cookie: ^4.1.2
typescript: ^4.8.3
winston: ^3.8.2
xml-js: ^1.6.11
dependencies:
'@headlessui/react': 1.7.2_biqbaboplfbrettd7655fr4n2y
@@ -65,6 +66,7 @@ dependencies:
swr: 1.3.0_react@18.2.0
tough-cookie: 4.1.2
winston: 3.8.2
xml-js: 1.6.11
devDependencies:
'@tailwindcss/forms': 0.5.3_tailwindcss@3.1.8
@@ -2554,6 +2556,10 @@ packages:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
dev: false
/sax/1.2.4:
resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==}
dev: false
/scheduler/0.23.0:
resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==}
dependencies:
@@ -3000,6 +3006,13 @@ packages:
/wrappy/1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
/xml-js/1.6.11:
resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==}
hasBin: true
dependencies:
sax: 1.2.4
dev: false
/xtend/4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}

View File

@@ -0,0 +1,205 @@
{
"sabnzbd": {
"queue": "Опашка",
"timeleft": "Оставащо Време",
"rate": "Rate"
},
"rutorrent": {
"active": "Акитивен",
"upload": "Споделяне",
"download": "Сваляне"
},
"widget": {
"missing_type": "Липсваща приставка: {{type}}",
"api_error": "API Грешка",
"status": "Статус"
},
"weather": {
"current": "Текущо местоположение",
"allow": "Разреши",
"updating": "Обновяване",
"wait": "Моля изчакайте"
},
"search": {
"placeholder": "Търсене…"
},
"resources": {
"cpu": "CPU",
"total": "Общо",
"free": "Свободни",
"used": "Заети",
"load": "Натоварване"
},
"unifi": {
"users": "Потребители",
"uptime": "Активен от",
"days": "Дни",
"wan": "WAN",
"lan_users": "LAN Потребители",
"wlan_users": "WLAN Потребители",
"up": "UP",
"down": "DOWN",
"wait": "Моля изчакайте"
},
"docker": {
"offline": "Изключен",
"rx": "RX",
"tx": "TX",
"mem": "MEM",
"cpu": "CPU"
},
"emby": {
"playing": "Възпроизвежда",
"transcoding": "Конвертира",
"bitrate": "Bitrate",
"no_active": "Няма активни потоци"
},
"tautulli": {
"playing": "Възпроизвежда",
"transcoding": "Конвертира",
"bitrate": "Честота",
"no_active": "Няма активни потоци"
},
"nzbget": {
"rate": "Rate",
"remaining": "Остава",
"downloaded": "Изтеглени"
},
"plex": {
"streams": "Активни Потоци",
"movies": "Филми",
"tv": "Сериали"
},
"transmission": {
"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"
},
"authentik": {
"loginsLast24H": "Logins (24h)",
"users": "Users",
"failedLoginsLast24H": "Failed Logins (24h)"
},
"jackett": {
"configured": "Configured",
"errored": "Errored"
},
"strelaysrv": {
"numActiveSessions": "Sessions",
"numConnections": "Connections",
"dataRelayed": "Relayed",
"transferRate": "Rate"
},
"mastodon": {
"user_count": "Users",
"status_count": "Posts",
"domain_count": "Domains"
},
"proxmox": {
"mem": "MEM",
"cpu": "CPU",
"lxc": "LXC",
"vms": "VMs"
},
"glances": {
"cpu": "CPU",
"mem": "MEM",
"wait": "Please wait"
}
}

View File

@@ -177,8 +177,29 @@
},
"proxmox": {
"vms": "VMs",
"mem": "MEM",
"cpu": "CPU",
"mem": "Memòria",
"cpu": "Processador",
"lxc": "LXC"
},
"unifi": {
"users": "Users",
"uptime": "System Uptime",
"days": "Days",
"wan": "WAN",
"lan_users": "LAN Users",
"wlan_users": "WLAN Users",
"up": "UP",
"down": "DOWN",
"wait": "Please wait"
},
"plex": {
"streams": "Active Streams",
"movies": "Movies",
"tv": "TV Shows"
},
"glances": {
"cpu": "CPU",
"mem": "MEM",
"wait": "Please wait"
}
}

View File

@@ -180,5 +180,26 @@
"cpu": "CPU",
"lxc": "LXC",
"vms": "VMs"
},
"unifi": {
"users": "Users",
"uptime": "System Uptime",
"days": "Days",
"wan": "WAN",
"lan_users": "LAN Users",
"wlan_users": "WLAN Users",
"up": "UP",
"down": "DOWN",
"wait": "Please wait"
},
"plex": {
"streams": "Active Streams",
"movies": "Movies",
"tv": "TV Shows"
},
"glances": {
"cpu": "CPU",
"mem": "MEM",
"wait": "Please wait"
}
}

View File

@@ -31,6 +31,17 @@
"used": "Used",
"load": "Load"
},
"unifi": {
"users": "Users",
"uptime": "System Uptime",
"days": "Days",
"wan": "WAN",
"lan_users": "LAN Users",
"wlan_users": "WLAN Users",
"up": "UP",
"down": "DOWN",
"wait": "Please wait"
},
"docker": {
"rx": "RX",
"tx": "TX",
@@ -55,6 +66,11 @@
"remaining": "Remaining",
"downloaded": "Downloaded"
},
"plex": {
"streams": "Active Streams",
"movies": "Movies",
"tv": "TV Shows"
},
"sabnzbd": {
"rate": "Rate",
"queue": "Queue",
@@ -191,5 +207,10 @@
"cpu": "CPU",
"lxc": "LXC",
"vms": "VMs"
},
"glances": {
"cpu": "CPU",
"mem": "MEM",
"wait": "Please wait"
}
}

View File

@@ -176,9 +176,30 @@
"failedLoginsLast24H": "Inicios de sesión fallidos (24h)"
},
"proxmox": {
"mem": "MEM",
"cpu": "CPU",
"mem": "Memoria",
"cpu": "Procesador",
"lxc": "LXC",
"vms": "VMs"
},
"unifi": {
"up": "UP",
"users": "Users",
"uptime": "System Uptime",
"days": "Days",
"wan": "WAN",
"lan_users": "LAN Users",
"wlan_users": "WLAN Users",
"down": "DOWN",
"wait": "Please wait"
},
"plex": {
"streams": "Active Streams",
"movies": "Movies",
"tv": "TV Shows"
},
"glances": {
"cpu": "CPU",
"mem": "MEM",
"wait": "Please wait"
}
}

View File

@@ -176,9 +176,30 @@
"failedLoginsLast24H": "Epäonnistuneita kirjautumisia (24h)"
},
"proxmox": {
"mem": "MEM",
"mem": "RAM",
"cpu": "CPU",
"lxc": "LXC",
"vms": "VMs"
"vms": "VKt"
},
"unifi": {
"users": "Users",
"uptime": "System Uptime",
"lan_users": "LAN Users",
"wlan_users": "WLAN Users",
"wait": "Please wait",
"days": "Days",
"wan": "WAN",
"up": "UP",
"down": "DOWN"
},
"plex": {
"streams": "Active Streams",
"movies": "Movies",
"tv": "TV Shows"
},
"glances": {
"wait": "Please wait",
"cpu": "CPU",
"mem": "MEM"
}
}

View File

@@ -176,9 +176,30 @@
"failedLoginsLast24H": "Cnx. échouées (24h)"
},
"proxmox": {
"mem": "MEM",
"cpu": "CPU",
"lxc": "LXC",
"mem": "Mém",
"cpu": "Cpu",
"lxc": "LxC",
"vms": "VMs"
},
"unifi": {
"users": "Utilisateurs",
"uptime": "Disponibilité du système",
"days": "Jours",
"wan": "WAN",
"lan_users": "Utilisateurs LAN",
"wlan_users": "Utilisateurs WLAN",
"up": "UP",
"down": "DOWN",
"wait": "Merci de patienter"
},
"plex": {
"streams": "Flux actif",
"movies": "Films",
"tv": "Séries TV"
},
"glances": {
"cpu": "CPU",
"mem": "MEM",
"wait": "Please wait"
}
}

View File

@@ -180,5 +180,26 @@
"cpu": "CPU",
"lxc": "LXC",
"vms": "VMs"
},
"unifi": {
"users": "Users",
"uptime": "System Uptime",
"days": "Days",
"wan": "WAN",
"lan_users": "LAN Users",
"wlan_users": "WLAN Users",
"up": "UP",
"down": "DOWN",
"wait": "Please wait"
},
"plex": {
"streams": "Active Streams",
"movies": "Movies",
"tv": "TV Shows"
},
"glances": {
"cpu": "CPU",
"mem": "MEM",
"wait": "Please wait"
}
}

View File

@@ -180,5 +180,26 @@
"cpu": "CPU",
"lxc": "LXC",
"vms": "VMs"
},
"unifi": {
"users": "Users",
"uptime": "System Uptime",
"days": "Days",
"wan": "WAN",
"lan_users": "LAN Users",
"wlan_users": "WLAN Users",
"up": "UP",
"down": "DOWN",
"wait": "Please wait"
},
"plex": {
"streams": "Active Streams",
"movies": "Movies",
"tv": "TV Shows"
},
"glances": {
"cpu": "CPU",
"mem": "MEM",
"wait": "Please wait"
}
}

View File

@@ -180,5 +180,26 @@
"cpu": "CPU",
"lxc": "LXC",
"vms": "VMs"
},
"unifi": {
"users": "Users",
"uptime": "System Uptime",
"days": "Days",
"wan": "WAN",
"lan_users": "LAN Users",
"wlan_users": "WLAN Users",
"up": "UP",
"down": "DOWN",
"wait": "Please wait"
},
"plex": {
"streams": "Active Streams",
"movies": "Movies",
"tv": "TV Shows"
},
"glances": {
"cpu": "CPU",
"mem": "MEM",
"wait": "Please wait"
}
}

View File

@@ -180,5 +180,26 @@
"cpu": "CPU",
"lxc": "LXC",
"vms": "VMs"
},
"unifi": {
"users": "Users",
"uptime": "System Uptime",
"days": "Days",
"wan": "WAN",
"lan_users": "LAN Users",
"wait": "Please wait",
"wlan_users": "WLAN Users",
"up": "UP",
"down": "DOWN"
},
"plex": {
"streams": "Active Streams",
"movies": "Movies",
"tv": "TV Shows"
},
"glances": {
"cpu": "CPU",
"mem": "MEM",
"wait": "Please wait"
}
}

View File

@@ -180,5 +180,26 @@
"cpu": "CPU",
"lxc": "LXC",
"vms": "VMs"
},
"unifi": {
"users": "Users",
"uptime": "System Uptime",
"days": "Days",
"wan": "WAN",
"lan_users": "LAN Users",
"wlan_users": "WLAN Users",
"up": "UP",
"down": "DOWN",
"wait": "Please wait"
},
"plex": {
"streams": "Active Streams",
"movies": "Movies",
"tv": "TV Shows"
},
"glances": {
"cpu": "CPU",
"mem": "MEM",
"wait": "Please wait"
}
}

View File

@@ -180,5 +180,26 @@
"cpu": "CPU",
"lxc": "LXC",
"vms": "VMs"
},
"unifi": {
"users": "Users",
"lan_users": "LAN Users",
"uptime": "System Uptime",
"days": "Days",
"wan": "WAN",
"wlan_users": "WLAN Users",
"up": "UP",
"down": "DOWN",
"wait": "Please wait"
},
"plex": {
"streams": "Active Streams",
"movies": "Movies",
"tv": "TV Shows"
},
"glances": {
"cpu": "CPU",
"mem": "MEM",
"wait": "Please wait"
}
}

View File

@@ -180,5 +180,26 @@
"cpu": "CPU",
"lxc": "LXC",
"vms": "VMs"
},
"unifi": {
"users": "Users",
"uptime": "System Uptime",
"days": "Days",
"wan": "WAN",
"lan_users": "LAN Users",
"wlan_users": "WLAN Users",
"up": "UP",
"down": "DOWN",
"wait": "Please wait"
},
"plex": {
"streams": "Active Streams",
"movies": "Movies",
"tv": "TV Shows"
},
"glances": {
"cpu": "CPU",
"mem": "MEM",
"wait": "Please wait"
}
}

View File

@@ -180,5 +180,26 @@
"cpu": "CPU",
"lxc": "LXC",
"vms": "VMs"
},
"unifi": {
"users": "Users",
"uptime": "System Uptime",
"days": "Days",
"wan": "WAN",
"lan_users": "LAN Users",
"wlan_users": "WLAN Users",
"up": "UP",
"down": "DOWN",
"wait": "Please wait"
},
"plex": {
"streams": "Active Streams",
"movies": "Movies",
"tv": "TV Shows"
},
"glances": {
"cpu": "CPU",
"mem": "MEM",
"wait": "Please wait"
}
}

View File

@@ -191,5 +191,26 @@
"cpu": "CPU",
"lxc": "LXC",
"vms": "VMs"
},
"unifi": {
"users": "Users",
"uptime": "System Uptime",
"days": "Days",
"wan": "WAN",
"lan_users": "LAN Users",
"wlan_users": "WLAN Users",
"up": "UP",
"down": "DOWN",
"wait": "Please wait"
},
"plex": {
"streams": "Active Streams",
"movies": "Movies",
"tv": "TV Shows"
},
"glances": {
"cpu": "CPU",
"mem": "MEM",
"wait": "Please wait"
}
}

View File

@@ -180,5 +180,26 @@
"mem": "MEM",
"cpu": "CPU",
"lxc": "LXC"
},
"unifi": {
"users": "Users",
"uptime": "System Uptime",
"days": "Days",
"wan": "WAN",
"lan_users": "LAN Users",
"wlan_users": "WLAN Users",
"up": "UP",
"down": "DOWN",
"wait": "Please wait"
},
"plex": {
"streams": "Active Streams",
"movies": "Movies",
"tv": "TV Shows"
},
"glances": {
"cpu": "CPU",
"mem": "MEM",
"wait": "Please wait"
}
}

View File

@@ -180,5 +180,26 @@
"cpu": "CPU",
"lxc": "LXC",
"vms": "VMs"
},
"unifi": {
"users": "Users",
"uptime": "System Uptime",
"days": "Days",
"wan": "WAN",
"lan_users": "LAN Users",
"wlan_users": "WLAN Users",
"up": "UP",
"down": "DOWN",
"wait": "Please wait"
},
"plex": {
"streams": "Active Streams",
"movies": "Movies",
"tv": "TV Shows"
},
"glances": {
"cpu": "CPU",
"mem": "MEM",
"wait": "Please wait"
}
}

View File

@@ -180,5 +180,26 @@
"cpu": "CPU",
"lxc": "LXC",
"vms": "VMs"
},
"unifi": {
"users": "Users",
"uptime": "System Uptime",
"days": "Days",
"wan": "WAN",
"lan_users": "LAN Users",
"wlan_users": "WLAN Users",
"up": "UP",
"down": "DOWN",
"wait": "Please wait"
},
"plex": {
"streams": "Active Streams",
"movies": "Movies",
"tv": "TV Shows"
},
"glances": {
"cpu": "CPU",
"mem": "MEM",
"wait": "Please wait"
}
}

View File

@@ -0,0 +1,205 @@
{
"readarr": {
"books": "పుస్తకాలు",
"wanted": "కావలెను",
"queued": "క్యూయూఎడ్"
},
"adguard": {
"blocked": "నిరోధించబడింది",
"filtered": "ఫిల్టర్ చేయబడింది",
"latency": "జాప్యం",
"queries": "ప్రశ్నలు"
},
"strelaysrv": {
"numActiveSessions": "సెషన్స్",
"numConnections": "కనెక్షన్లు",
"dataRelayed": "రెలయెడఁ",
"transferRate": "రేటు"
},
"widget": {
"missing_type": "విడ్జెట్ లేదు: {{type}}",
"api_error": "API లోపం",
"status": "హోదా"
},
"weather": {
"current": "ప్రస్తుత స్తలం",
"allow": "అనుమతించడానికి క్లిక్ చేయండి",
"updating": "నవీకరిస్తోంది",
"wait": "దయచేసి వేచి ఉండండి"
},
"search": {
"placeholder": "వెతకండి…"
},
"resources": {
"cpu": "సీపియూ",
"total": "మొత్తం",
"free": "మిగిలింది",
"used": "ఉపయోగించబడిన",
"load": "లోడ్"
},
"docker": {
"rx": "RX",
"tx": "TX",
"mem": "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": "ఆల్బములు"
},
"bazarr": {
"missingEpisodes": "ఎపిసోడ్‌లు లేవు",
"missingMovies": "సినిమాలు లేవు"
},
"ombi": {
"pending": "పెండింగ్",
"approved": "ఆమోదించబడింది",
"available": "అందుబాటులో వున్నవి"
},
"jellyseerr": {
"pending": "పెండింగ్",
"approved": "ఆమోదించబడింది",
"available": "అందుబాటులో"
},
"overseerr": {
"pending": "పెండింగ్",
"approved": "ఆమోదించబడింది",
"available": "అందుబాటులో"
},
"pihole": {
"queries": "ప్రశ్నలు",
"blocked": "నిరోధించబడింది",
"gravity": "గురుత్వాకర్షణ"
},
"speedtest": {
"upload": "అప్లోడ్",
"download": "డౌన్‌లోడ్",
"ping": "పింగ్"
},
"portainer": {
"running": "నడుస్తున్నవి",
"stopped": "ఆగిపోయినవి",
"total": "మొత్తం"
},
"traefik": {
"routers": "రౌటర్లు",
"services": "సేవలు",
"middleware": "మిడిల్వేర్"
},
"npm": {
"enabled": "ప్రారంభించబడింది",
"disabled": "డిసేబ్లెడ్",
"total": "మొత్తం"
},
"coinmarketcap": {
"configure": "ట్రాక్ చేయడానికి ఒకటి లేదా అంతకంటే ఎక్కువ క్రిప్టో కరెన్సీలను కాన్ఫిగర్ చేయండి",
"1hour": "1 గంట",
"1day": "1 రోజు",
"7days": "7 రోజులు",
"30days": "30 రోజులు"
},
"gotify": {
"apps": "అప్లికేషన్లు",
"clients": "ఖాతాదారులు",
"messages": "సందేశాలు"
},
"prowlarr": {
"enableIndexers": "సూచికలు",
"numberOfGrabs": "గ్రాబ్స్",
"numberOfQueries": "ప్రశ్నలు",
"numberOfFailGrabs": "ఫెయిల్ గ్రాబ్స్",
"numberOfFailQueries": "విఫలమైన ప్రశ్నలు"
},
"jackett": {
"configured": "కాన్ఫిగర్ చేయబడింది",
"errored": "పొరపాటు జరిగింది"
},
"mastodon": {
"user_count": "వినియోగదారులు",
"status_count": "పోస్ట్‌లు",
"domain_count": "డొమైన్‌లు"
},
"authentik": {
"users": "వినియోగదారులు",
"loginsLast24H": "లాగిన్లు (24గ)",
"failedLoginsLast24H": "విఫలమైన లాగిన్‌లు (24గ)"
},
"proxmox": {
"mem": "MEM",
"cpu": "సీపియూ",
"lxc": "LXC",
"vms": "విఎంలు"
},
"unifi": {
"users": "వినియోగదారులు",
"uptime": "సిస్టమ్ సమయము",
"days": "రోజులు",
"wan": "WAN",
"lan_users": "LAN వినియోగదారులు",
"wlan_users": "WLAN వినియోగదారులు",
"up": "అప్",
"down": "డౌన్",
"wait": "దయచేసి వేచి ఉండండి"
},
"plex": {
"streams": "యాక్టివ్ స్ట్రీమ్‌లు",
"movies": "సినిమాలు",
"tv": "దూరదర్శిని కార్యక్రమాలు"
},
"glances": {
"cpu": "CPU",
"mem": "MEM",
"wait": "Please wait"
}
}

View File

@@ -180,5 +180,26 @@
"cpu": "CPU",
"lxc": "LXC",
"vms": "VMs"
},
"unifi": {
"users": "Users",
"uptime": "System Uptime",
"days": "Days",
"wan": "WAN",
"lan_users": "LAN Users",
"wlan_users": "WLAN Users",
"up": "UP",
"down": "DOWN",
"wait": "Please wait"
},
"plex": {
"streams": "Active Streams",
"movies": "Movies",
"tv": "TV Shows"
},
"glances": {
"cpu": "CPU",
"mem": "MEM",
"wait": "Please wait"
}
}

View File

@@ -180,5 +180,26 @@
"cpu": "CPU",
"lxc": "LXC",
"vms": "VMs"
},
"unifi": {
"users": "Users",
"uptime": "System Uptime",
"days": "Days",
"wan": "WAN",
"lan_users": "LAN Users",
"wlan_users": "WLAN Users",
"up": "UP",
"down": "DOWN",
"wait": "Please wait"
},
"plex": {
"streams": "Active Streams",
"movies": "Movies",
"tv": "TV Shows"
},
"glances": {
"cpu": "CPU",
"mem": "MEM",
"wait": "Please wait"
}
}

View File

@@ -92,7 +92,7 @@
"current": "当前定位",
"allow": "点击并允许",
"updating": "更新中",
"wait": "请稍"
"wait": "请稍"
},
"overseerr": {
"pending": "待办",
@@ -162,23 +162,44 @@
"mastodon": {
"user_count": "用户",
"status_count": "Posts",
"domain_count": "Domains"
"domain_count": ""
},
"strelaysrv": {
"numActiveSessions": "Sessions",
"dataRelayed": "Relayed",
"numConnections": "Connections",
"transferRate": "Rate"
"numActiveSessions": "会话",
"dataRelayed": "中继",
"numConnections": "连接",
"transferRate": "速度"
},
"authentik": {
"users": "Users",
"loginsLast24H": "Logins (24h)",
"failedLoginsLast24H": "Failed Logins (24h)"
"users": "用户",
"loginsLast24H": "登录 (24h)",
"failedLoginsLast24H": "登录失败 (24h)"
},
"proxmox": {
"mem": "MEM",
"cpu": "CPU",
"mem": "内存",
"cpu": "处理器",
"lxc": "LXC",
"vms": "VMs"
},
"unifi": {
"users": "Users",
"uptime": "System Uptime",
"days": "Days",
"wan": "WAN",
"lan_users": "LAN Users",
"wlan_users": "WLAN Users",
"up": "UP",
"down": "DOWN",
"wait": "请稍候"
},
"plex": {
"streams": "Active Streams",
"movies": "Movies",
"tv": "TV Shows"
},
"glances": {
"cpu": "CPU",
"mem": "MEM",
"wait": "Please wait"
}
}

View File

@@ -180,5 +180,26 @@
"cpu": "CPU",
"lxc": "LXC",
"vms": "VMs"
},
"unifi": {
"users": "Users",
"uptime": "System Uptime",
"days": "Days",
"wan": "WAN",
"lan_users": "LAN Users",
"wlan_users": "WLAN Users",
"up": "UP",
"down": "DOWN",
"wait": "Please wait"
},
"plex": {
"streams": "Active Streams",
"movies": "Movies",
"tv": "TV Shows"
},
"glances": {
"cpu": "CPU",
"mem": "MEM",
"wait": "Please wait"
}
}

View File

@@ -9,19 +9,37 @@ import Docker from "widgets/docker/component";
import { SettingsContext } from "utils/contexts/settings";
function resolveIcon(icon) {
if (icon.startsWith("http")) {
return icon;
// direct or relative URLs
if (icon.startsWith("http") || icon.startsWith("/")) {
return <Image src={`${icon}`} width={32} height={32} alt="logo" />;
}
if (icon.startsWith("/")) {
return icon;
// mdi- prefixed, material design icons
if (icon.startsWith("mdi-")) {
const iconName = icon.replace("mdi-", "").replace(".svg", "");
return (
<div
style={{
width: 32,
height: 32,
background: "linear-gradient(180deg, rgb(var(--color-logo-start)), rgb(var(--color-logo-stop)))",
mask: `url(https://cdn.jsdelivr.net/npm/@mdi/svg@latest/svg/${iconName}.svg) no-repeat center / contain`,
WebkitMask: `url(https://cdn.jsdelivr.net/npm/@mdi/svg@latest/svg/${iconName}.svg) no-repeat center / contain`,
}}
/>
);
}
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`;
// fallback to dashboard-icons
const iconName = icon.replace(".png", "");
return (
<Image
src={`https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${iconName}.png`}
width={32}
height={32}
alt="logo"
/>
);
}
export default function Item({ service }) {
@@ -57,12 +75,10 @@ export default function Item({ service }) {
rel="noreferrer"
className="flex-shrink-0 flex items-center justify-center w-12 "
>
<Image src={resolveIcon(service.icon)} width={32} height={32} alt="logo" />
{resolveIcon(service.icon)}
</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>
<div className="flex-shrink-0 flex items-center justify-center w-12 ">{resolveIcon(service.icon)}</div>
))}
{hasLink ? (

View File

@@ -0,0 +1,110 @@
import useSWR from "swr";
import { BiError } from "react-icons/bi";
import { FaMemory } from "react-icons/fa";
import { FiCpu } from "react-icons/fi";
import { useTranslation } from "next-i18next";
import UsageBar from "../resources/usage-bar";
export default function Widget({ options }) {
const { t, i18n } = useTranslation();
const { data, error } = useSWR(
`/api/widgets/glances?${new URLSearchParams({ lang: i18n.language, ...options }).toString()}`, {
refreshInterval: 1500,
}
);
if (error || data?.error) {
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-row 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">{t("widget.api_error")}</span>
</div>
</div>
</div>
</div>
);
}
if (!data) {
return (
<div className="flex flex-col max-w:full sm:basis-auto self-center grow-0 flex-wrap ml-4">
<div className="flex flex-row self-center flex-wrap justify-between">
<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 min-w-[85px]">
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
<div className="pl-0.5 text-xs">
{t("glances.wait")}
</div>
</div>
<UsageBar percent="0" />
</div>
</div>
<div className="flex-none flex flex-row items-center mr-3 py-1.5">
<FaMemory className="text-theme-800 dark:text-theme-200 w-5 h-5" />
<div className="flex flex-col ml-3 text-left min-w-[85px]">
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between">
<div className="pl-0.5 text-xs">
{t("glances.wait")}
</div>
</div>
<UsageBar percent="0" />
</div>
</div>
</div>
{options.label && (
<div className="ml-6 pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">{options.label}</div>
)}
</div>
);
}
return (
<div className="flex flex-col max-w:full sm:basis-auto self-center grow-0 flex-wrap ml-4">
<div className="flex flex-row self-center flex-wrap justify-between">
<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 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,
style: "unit",
unit: "percent",
maximumFractionDigits: 0,
})}
</div>
<div className="pr-1">{t("glances.cpu")}</div>
</div>
<UsageBar percent={data.cpu} />
</div>
</div>
<div className="flex-none flex flex-row items-center mr-3 py-1.5">
<FaMemory className="text-theme-800 dark:text-theme-200 w-5 h-5" />
<div className="flex flex-col ml-3 text-left min-w-[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.mem,
style: "unit",
unit: "percent",
maximumFractionDigits: 0,
})}
</div>
<div className="pr-1">{t("glances.mem")}</div>
</div>
<UsageBar percent={data.mem} />
</div>
</div>
</div>
{options.label && (
<div className="pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">{options.label}</div>
)}
</div>
);
}

View File

@@ -0,0 +1,119 @@
import { BiError, BiWifi, BiCheckCircle, BiXCircle } from "react-icons/bi";
import { MdSettingsEthernet } from "react-icons/md";
import { useTranslation } from "next-i18next";
import { SiUbiquiti } from "react-icons/si";
import useWidgetAPI from "utils/proxy/use-widget-api";
export default function Widget({ options }) {
const { t } = useTranslation();
// eslint-disable-next-line no-param-reassign
options.type = "unifi_console";
const { data: statsData, error: statsError } = useWidgetAPI(options, "stat/sites", { index: options.index });
if (statsError || statsData?.error) {
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">
<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">{t("widget.api_error")}</span>
<span className="text-theme-800 dark:text-theme-200 text-xs">-</span>
</div>
</div>
</div>
</div>
);
}
const defaultSite = statsData?.data?.find(s => s.name === "default");
if (!defaultSite) {
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">
<SiUbiquiti className="w-5 h-5 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-xs">{t("unifi.wait")}</span>
</div>
</div>
</div>
);
}
const wan = defaultSite.health.find(h => h.subsystem === "wan");
const lan = defaultSite.health.find(h => h.subsystem === "lan");
const wlan = defaultSite.health.find(h => h.subsystem === "wlan");
const data = {
name: wan.gw_name,
uptime: wan["gw_system-stats"].uptime,
up: wan.status === 'ok',
wlan: {
users: wlan.num_user,
status: wlan.status
},
lan: {
users: lan.num_user,
status: lan.status
}
};
return (
<div className="flex-none flex flex-row items-center mr-3 py-1.5">
<div className="flex flex-col">
<div className="flex flex-row ml-3">
<SiUbiquiti className="text-theme-800 dark:text-theme-200 w-3 h-3 mr-1" />
<div className="text-theme-800 dark:text-theme-200 text-xs font-bold flex flex-row justify-between">
{data.name}
</div>
</div>
<div className="flex flex-row ml-3 text-[10px] justify-between">
<div className="flex flex-row" title={t("unifi.uptime")}>
<div className="pr-0.5 text-theme-800 dark:text-theme-200">
{t("common.number", {
value: data.uptime / 86400,
maximumFractionDigits: 1,
})}
</div>
<div className="pr-1 text-theme-800 dark:text-theme-200">{t("unifi.days")}</div>
</div>
<div className="flex flex-row">
<div className="pr-1 text-theme-800 dark:text-theme-200">{t("unifi.wan")}</div>
{ data.up
? <BiCheckCircle className="text-theme-800 dark:text-theme-200 h-4 w-3" />
: <BiXCircle className="text-theme-800 dark:text-theme-200 h-4 w-3" />
}
</div>
</div>
</div>
<div className="flex flex-col">
<div className="flex flex-row ml-3 py-0.5">
<BiWifi className="text-theme-800 dark:text-theme-200 w-4 h-4 mr-1" />
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between" title={t("unifi.users")}>
<div className="pr-0.5">
{t("common.number", {
value: data.wlan.users,
maximumFractionDigits: 0,
})}
</div>
</div>
</div>
<div className="flex flex-row ml-3 pb-0.5">
<MdSettingsEthernet className="text-theme-800 dark:text-theme-200 w-4 h-4 mr-1" />
<div className="text-theme-800 dark:text-theme-200 text-xs flex flex-row justify-between" title={t("unifi.users")}>
<div className="pr-0.5">
{t("common.number", {
value: data.lan.users,
maximumFractionDigits: 0,
})}
</div>
</div>
</div>
</div>
</div>
);
}

View File

@@ -10,6 +10,8 @@ const widgetMappings = {
greeting: dynamic(() => import("components/widgets/greeting/greeting")),
datetime: dynamic(() => import("components/widgets/datetime/datetime")),
logo: dynamic(() => import("components/widgets/logo/logo"), { ssr: false }),
unifi_console: dynamic(() => import("components/widgets/unifi_console/unifi_console")),
glances: dynamic(() => import("components/widgets/glances/glances")),
};
export default function Widget({ widget }) {

View File

@@ -8,6 +8,8 @@ export default function Document() {
name="description"
content="A highly customizable homepage (or startpage / application dashboard) with Docker and service API integrations."
/>
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png?v=4" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png?v=4" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png?v=4" />

View File

@@ -0,0 +1,40 @@
import { httpProxy } from "utils/proxy/http";
import createLogger from "utils/logger";
import { getPrivateWidgetOptions } from "utils/config/widget-helpers";
const logger = createLogger("glances");
export default async function handler(req, res) {
const { index } = req.query;
const privateWidgetOptions = await getPrivateWidgetOptions("glances", index);
const url = privateWidgetOptions?.url;
if (!url) {
const errorMessage = "Missing Glances URL";
logger.error(errorMessage);
return res.status(400).json({ error: errorMessage });
}
const apiUrl = `${url}/api/3/quicklook`;
const headers = {
"Accept-Encoding": "application/json"
};
if (privateWidgetOptions.username && privateWidgetOptions.password) {
headers.Authorization = `Basic ${Buffer.from(`${privateWidgetOptions.username}:${privateWidgetOptions.password}`).toString("base64")}`
}
const params = { method: "GET", headers };
const [status, contentType, data] = await httpProxy(apiUrl, params);
if (status === 401) {
logger.error("Authorization failure getting data from glances API. Data: %s", data);
}
if (status !== 200) {
logger.error("HTTP %d getting data from glances API. Data: %s", status, data);
}
if (contentType) res.setHeader("Content-Type", contentType);
return res.status(status).send(data);
}

View File

@@ -263,8 +263,6 @@ function Home({ initialSettings }) {
export default function Wrapper({ initialSettings, fallback }) {
const wrappedStyle = {};
if (initialSettings && initialSettings.background) {
// wrappedStyle.backgroundImage = `url(${initialSettings.background})`;
// wrappedStyle.backgroundSize = "cover";
const opacity = initialSettings.backgroundOpacity ?? 1;
const opacityValue = 1 - opacity;
wrappedStyle.backgroundImage = `

View File

@@ -9,8 +9,8 @@ export async function getServerSideProps({ res }) {
const theme = settings.theme || "dark";
const manifest = {
name: "Homepage",
short_name: "Homepage",
name: settings.title || "Homepage",
short_name: settings.title || "Homepage",
icons: [
{
src: "/android-chrome-192x192.png?v=2",

View File

@@ -6,6 +6,7 @@ import yaml from "js-yaml";
import checkAndCopyConfig from "utils/config/config";
import { servicesFromConfig, servicesFromDocker, cleanServiceGroups } from "utils/config/service-helpers";
import { cleanWidgetGroups, widgetsFromConfig } from "utils/config/widget-helpers";
export async function bookmarksResponse() {
checkAndCopyConfig("bookmarks.yaml");
@@ -29,21 +30,17 @@ export async function bookmarksResponse() {
}
export async function widgetsResponse() {
checkAndCopyConfig("widgets.yaml");
let configuredWidgets;
const widgetsYaml = path.join(process.cwd(), "config", "widgets.yaml");
const fileContents = await fs.readFile(widgetsYaml, "utf8");
const widgets = yaml.load(fileContents);
try {
configuredWidgets = cleanWidgetGroups(await widgetsFromConfig());
} catch (e) {
console.error("Failed to load widgets, please check widgets.yaml for errors or remove example entries.");
if (e) console.error(e);
configuredWidgets = [];
}
if (!widgets) return [];
// map easy to write YAML objects into easy to consume JS arrays
const widgetsArray = widgets.map((group) => ({
type: Object.keys(group)[0],
options: { ...group[Object.keys(group)[0]] },
}));
return widgetsArray;
return configuredWidgets;
}
export async function servicesResponse() {

View File

@@ -33,4 +33,4 @@ export function getSettings() {
const settingsYaml = join(process.cwd(), "config", "settings.yaml");
const fileContents = readFileSync(settingsYaml, "utf8");
return yaml.load(fileContents);
}
}

View File

@@ -59,7 +59,7 @@ export async function servicesFromDocker() {
let constructedService = null;
Object.keys(container.Labels).forEach((label) => {
if (label.startsWith("homepage")) {
if (label.startsWith("homepage.")) {
if (!constructedService) {
constructedService = {
container: container.Names[0].replace(/^\//, ""),
@@ -165,4 +165,4 @@ export default async function getServiceWidget(group, service) {
}
return false;
}
}

View File

@@ -0,0 +1,73 @@
import { promises as fs } from "fs";
import path from "path";
import yaml from "js-yaml";
import checkAndCopyConfig from "utils/config/config";
export async function widgetsFromConfig() {
checkAndCopyConfig("widgets.yaml");
const widgetsYaml = path.join(process.cwd(), "config", "widgets.yaml");
const fileContents = await fs.readFile(widgetsYaml, "utf8");
const widgets = yaml.load(fileContents);
if (!widgets) return [];
// map easy to write YAML objects into easy to consume JS arrays
const widgetsArray = widgets.map((group, index) => ({
type: Object.keys(group)[0],
options: {
index,
...group[Object.keys(group)[0]]
},
}));
return widgetsArray;
}
export async function cleanWidgetGroups(widgets) {
return widgets.map((widget, index) => {
const sanitizedOptions = widget.options;
const optionKeys = Object.keys(sanitizedOptions);
["url", "username", "password", "key"].forEach((pO) => {
if (optionKeys.includes(pO)) {
delete sanitizedOptions[pO];
}
});
return {
type: widget.type,
options: {
index,
...sanitizedOptions
},
}
});
}
export async function getPrivateWidgetOptions(type, widgetIndex) {
const widgets = await widgetsFromConfig();
const privateOptions = widgets.map((widget) => {
const {
index,
url,
username,
password,
key
} = widget.options;
return {
type: widget.type,
options: {
index,
url,
username,
password,
key
},
}
});
return (type !== undefined && widgetIndex !== undefined) ? privateOptions.find(o => o.type === type && o.options.index === parseInt(widgetIndex, 10))?.options : privateOptions;
}

View File

@@ -2,7 +2,7 @@ export function formatApiCall(url, args) {
const find = /\{.*?\}/g;
const replace = (match) => {
const key = match.replace(/\{|\}/g, "");
return args[key];
return args[key] || "";
};
return url.replace(/\/+$/, "").replace(find, replace);

View File

@@ -1,8 +1,13 @@
import cache from "memory-cache";
const defaultDuration = 5;
export default async function cachedFetch(url, duration) {
const cached = cache.get(url);
// eslint-disable-next-line no-param-reassign
duration = duration || defaultDuration;
if (cached) {
return cached;
}

View File

@@ -18,6 +18,7 @@ const components = {
ombi: dynamic(() => import("./ombi/component")),
overseerr: dynamic(() => import("./overseerr/component")),
pihole: dynamic(() => import("./pihole/component")),
plex: dynamic(() => import("./plex/component")),
portainer: dynamic(() => import("./portainer/component")),
prowlarr: dynamic(() => import("./prowlarr/component")),
proxmox: dynamic(() => import("./proxmox/component")),
@@ -32,6 +33,7 @@ const components = {
tautulli: dynamic(() => import("./tautulli/component")),
traefik: dynamic(() => import("./traefik/component")),
transmission: dynamic(() => import("./transmission/component")),
unifi: dynamic(() => import("./unifi/component")),
};
export default components;

View File

@@ -40,14 +40,16 @@ export default function Component({ service }) {
);
}
const network = statsData.stats.networks?.eth0 || statsData.stats.networks?.network;
return (
<Container service={service}>
<Block label="docker.cpu" value={t("common.percent", { value: calculateCPUPercent(statsData.stats) })} />
<Block label="docker.mem" value={t("common.bytes", { value: statsData.stats.memory_stats.usage })} />
{statsData.stats.networks && (
{network && (
<>
<Block label="docker.rx" value={t("common.bytes", { value: statsData.stats.networks.eth0.rx_bytes })} />
<Block label="docker.tx" value={t("common.bytes", { value: statsData.stats.networks.eth0.tx_bytes })} />
<Block label="docker.rx" value={t("common.bytes", { value: network.rx_bytes })} />
<Block label="docker.tx" value={t("common.bytes", { value: network.tx_bytes })} />
</>
)}
</Container>

View File

@@ -0,0 +1,38 @@
import useSWR from "swr";
import { useTranslation } from "next-i18next";
import Block from "components/services/widget/block";
import Container from "components/services/widget/container";
import { formatProxyUrl } from "utils/proxy/api-helpers";
export default function Component({ service }) {
const { t } = useTranslation();
const { widget } = service;
const { data: plexData, error: plexAPIError } = useSWR(formatProxyUrl(widget, "unified"), {
refreshInterval: 5000,
});
if (plexAPIError) {
return <Container error={t("widget.api_error")} />;
}
if (!plexData) {
return (
<Container service={service}>
<Block label="plex.streams" />
<Block label="plex.movies" />
<Block label="plex.tv" />
</Container>
);
}
return (
<Container service={service}>
<Block label="plex.streams" value={t("common.number", { value: plexData.streams })} />
<Block label="plex.movies" value={t("common.number", { value: plexData.movies })} />
<Block label="plex.tv" value={t("common.number", { value: plexData.tv })} />
</Container>
);
}

110
src/widgets/plex/proxy.js Normal file
View File

@@ -0,0 +1,110 @@
/* eslint-disable no-underscore-dangle */
import cache from "memory-cache";
import { xml2json } from "xml-js";
import { formatApiCall } from "utils/proxy/api-helpers";
import { httpProxy } from "utils/proxy/http";
import getServiceWidget from "utils/config/service-helpers";
import createLogger from "utils/logger";
import widgets from "widgets/widgets";
const proxyName = "plexProxyHandler";
const librariesCacheKey = `${proxyName}__libraries`;
const moviesCacheKey = `${proxyName}__movies`;
const tvCacheKey = `${proxyName}__tv`;
const logger = createLogger(proxyName);
async function getWidget(req) {
const { group, service } = req.query;
if (!group || !service) {
logger.debug("Invalid or missing service '%s' or group '%s'", service, group);
return null;
}
const widget = await getServiceWidget(group, service);
if (!widget) {
logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group);
return null;
}
return widget;
}
async function fetchFromPlexAPI(endpoint, widget) {
const api = widgets?.[widget.type]?.api;
if (!api) {
return [403, null];
}
const url = new URL(formatApiCall(api, { endpoint, ...widget }));
const [status, contentType, data] = await httpProxy(url);
if (status !== 200) {
logger.error("HTTP %d communicating with Plex. Data: %s", status, data.toString());
return [status, data.toString()];
}
try {
const dataDecoded = xml2json(data.toString(), { compact: true });
return [status, JSON.parse(dataDecoded), contentType];
} catch (e) {
logger.error("Error decoding Plex API data. Data: %s", data.toString());
return [status, null];
}
}
export default async function plexProxyHandler(req, res) {
const widget = await getWidget(req);
if (!widget) {
return res.status(400).json({ error: "Invalid proxy service type" });
}
logger.debug("Getting streams from Plex API");
let streams;
let [status, apiData] = await fetchFromPlexAPI("/status/sessions", widget);
if (apiData && apiData.MediaContainer) {
streams = apiData.MediaContainer._attributes.size;
}
let libraries = cache.get(librariesCacheKey);
if (libraries === null) {
logger.debug("Getting libraries from Plex API");
[status, apiData] = await fetchFromPlexAPI("/library/sections", widget);
if (apiData && apiData.MediaContainer) {
libraries = apiData.MediaContainer.Directory;
cache.put(librariesCacheKey, libraries, 1000 * 60 * 60 * 6);
}
}
let movies = cache.get(moviesCacheKey);
let tv = cache.get(tvCacheKey);
if (movies === null || tv === null) {
movies = 0;
tv = 0;
logger.debug("Getting movie + tv counts from Plex API");
libraries.filter(l => ["movie", "show"].includes(l._attributes.type)).forEach(async (library) => {
[status, apiData] = await fetchFromPlexAPI(`/library/sections/${library._attributes.key}/all`, widget);
if (apiData && apiData.MediaContainer) {
const size = parseInt(apiData.MediaContainer._attributes.size, 10);
if (library._attributes.type === "movie") {
movies += size;
} else if (library._attributes.type === "show") {
tv += size;
}
}
cache.put(tvCacheKey, tv, 1000 * 60 * 10);
cache.put(moviesCacheKey, movies, 1000 * 60 * 10);
});
}
const data = {
streams,
tv,
movies
};
return res.status(status).send(data);
}

View File

@@ -0,0 +1,14 @@
import plexProxyHandler from "./proxy";
const widget = {
api: "{url}{endpoint}?X-Plex-Token={key}",
proxyHandler: plexProxyHandler,
mappings: {
unified: {
endpoint: "/",
},
},
};
export default widget;

View File

@@ -0,0 +1,58 @@
import { useTranslation } from "next-i18next";
import Container from "components/services/widget/container";
import Block from "components/services/widget/block";
import useWidgetAPI from "utils/proxy/use-widget-api";
export default function Component({ service }) {
const { t } = useTranslation();
const { widget } = service;
const { data: statsData, error: statsError } = useWidgetAPI(widget, "stat/sites");
if (statsError || statsData?.error) {
return <Container error={t("widget.api_error")} />;
}
const defaultSite = statsData?.data?.find(s => s.name === "default");
if (!defaultSite) {
return (
<Container service={service}>
<Block label="unifi.uptime" />
<Block label="unifi.wan" />
<Block label="unifi.lan_users" />
<Block label="unifi.wlan_users" />
</Container>
);
}
const wan = defaultSite.health.find(h => h.subsystem === "wan");
const lan = defaultSite.health.find(h => h.subsystem === "lan");
const wlan = defaultSite.health.find(h => h.subsystem === "wlan");
const data = {
name: wan.gw_name,
uptime: wan["gw_system-stats"].uptime,
up: wan.status === 'ok',
wlan: {
users: wlan.num_user,
status: wlan.status
},
lan: {
users: lan.num_user,
status: lan.status
},
};
const uptime = `${t("common.number", { value: data.uptime / 86400, maximumFractionDigits: 1 })} ${t("unifi.days")}`;
return (
<Container service={service}>
<Block label="unifi.uptime" value={ uptime } />
<Block label="unifi.wan" value={ data.up ? t("unifi.up") : t("unifi.down") } />
<Block label="unifi.lan_users" value={ t("common.number", { value: data.lan.users }) } />
<Block label="unifi.wlan_users" value={ t("common.number", { value: data.wlan.users }) } />
</Container>
);
}

119
src/widgets/unifi/proxy.js Normal file
View File

@@ -0,0 +1,119 @@
import cache from "memory-cache";
import { formatApiCall } from "utils/proxy/api-helpers";
import { httpProxy } from "utils/proxy/http";
import { addCookieToJar, setCookieHeader } from "utils/proxy/cookie-jar";
import getServiceWidget from "utils/config/service-helpers";
import { getPrivateWidgetOptions } from "utils/config/widget-helpers";
import createLogger from "utils/logger";
import widgets from "widgets/widgets";
const udmpPrefix = "/proxy/network";
const proxyName = "unifiProxyHandler";
const prefixCacheKey = `${proxyName}__prefix`;
const logger = createLogger(proxyName);
async function getWidget(req) {
const { group, service, type } = req.query;
let widget = null;
if (type === "unifi_console") { // info widget
const index = req.query?.query ? JSON.parse(req.query.query).index : undefined;
widget = await getPrivateWidgetOptions(type, index);
if (!widget) {
logger.debug("Error retrieving settings for this Unifi widget");
return null;
}
widget.type = "unifi";
} else {
if (!group || !service) {
logger.debug("Invalid or missing service '%s' or group '%s'", service, group);
return null;
}
widget = await getServiceWidget(group, service);
if (!widget) {
logger.debug("Invalid or missing widget for service '%s' in group '%s'", service, group);
return null;
}
}
return widget;
}
async function login(widget) {
const endpoint = (widget.prefix === udmpPrefix) ? "auth/login" : "login";
const api = widgets?.[widget.type]?.api?.replace("{prefix}", ""); // no prefix for login url
const loginUrl = new URL(formatApiCall(api, { endpoint, ...widget }));
const loginBody = { username: widget.username, password: widget.password, remember: true };
const headers = { "Content-Type": "application/json" };
const [status, contentType, data, responseHeaders] = await httpProxy(loginUrl, {
method: "POST",
body: JSON.stringify(loginBody),
headers,
});
return [status, contentType, data, responseHeaders];
}
export default async function unifiProxyHandler(req, res) {
const widget = await getWidget(req);
if (!widget) {
return res.status(400).json({ error: "Invalid proxy service type" });
}
const api = widgets?.[widget.type]?.api;
if (!api) {
return res.status(403).json({ error: "Service does not support API calls" });
}
let [status, contentType, data, responseHeaders] = [];
let prefix = cache.get(prefixCacheKey);
if (prefix === null) {
// auto detect if we're talking to a UDM Pro, and cache the result so that we
// don't make two requests each time data from Unifi is required
[status, contentType, data, responseHeaders] = await httpProxy(widget.url);
prefix = "";
if (responseHeaders["x-csrf-token"]) {
prefix = udmpPrefix;
}
cache.put(prefixCacheKey, prefix);
}
widget.prefix = prefix;
const { endpoint } = req.query;
const url = new URL(formatApiCall(api, { endpoint, ...widget }));
const params = { method: "GET", headers: {} };
setCookieHeader(url, params);
[status, contentType, data, responseHeaders] = await httpProxy(url, params);
if (status === 401) {
logger.debug("Unifi isn't logged in or rejected the reqeust, attempting login.");
[status, contentType, data, responseHeaders] = await login(widget);
if (status !== 200) {
logger.error("HTTP %d logging in to Unifi. Data: %s", status, data);
return res.status(status).end(data);
}
const json = JSON.parse(data.toString());
if (!(json?.meta?.rc === "ok" || json.login_time)) {
logger.error("Error logging in to Unifi: Data: %s", data);
return res.status(401).end(data);
}
addCookieToJar(url, responseHeaders);
setCookieHeader(url, params);
logger.debug("Retrying Unifi request after login.");
[status, contentType, data, responseHeaders] = await httpProxy(url, params);
}
if (status !== 200) {
logger.error("HTTP %d getting data from Unifi endpoint %s. Data: %s", status, url.href, data);
}
if (contentType) res.setHeader("Content-Type", contentType);
return res.status(status).send(data);
}

View File

@@ -0,0 +1,14 @@
import unifiProxyHandler from "./proxy";
const widget = {
api: "{url}{prefix}/api/{endpoint}",
proxyHandler: unifiProxyHandler,
mappings: {
"stat/sites": {
endpoint: "stat/sites",
},
}
};
export default widget;

View File

@@ -13,6 +13,7 @@ import nzbget from "./nzbget/widget";
import ombi from "./ombi/widget";
import overseerr from "./overseerr/widget";
import pihole from "./pihole/widget";
import plex from "./plex/widget";
import portainer from "./portainer/widget";
import prowlarr from "./prowlarr/widget";
import proxmox from "./proxmox/widget";
@@ -27,6 +28,7 @@ import strelaysrv from "./strelaysrv/widget";
import tautulli from "./tautulli/widget";
import traefik from "./traefik/widget";
import transmission from "./transmission/widget";
import unifi from "./unifi/widget";
const widgets = {
adguard,
@@ -45,6 +47,7 @@ const widgets = {
ombi,
overseerr,
pihole,
plex,
portainer,
prowlarr,
proxmox,
@@ -59,6 +62,8 @@ const widgets = {
tautulli,
traefik,
transmission,
unifi,
unifi_console: unifi
};
export default widgets;