Compare commits

..

557 Commits

Author SHA1 Message Date
hank
87329a68bd New translations en.po (Spanish) 2026-01-31 16:16:15 -05:00
hank
1c7caaf59a New translations en.po (Norwegian) 2026-01-31 16:16:14 -05:00
hank
4c14877f33 New translations en.po (Hungarian) 2026-01-31 16:16:13 -05:00
hank
5941c5e449 New translations en.po (Chinese Traditional) 2026-01-31 16:16:12 -05:00
hank
d2991bdc88 New translations en.po (Dutch) 2026-01-31 16:16:10 -05:00
hank
b58d0dd16a New translations en.po (Spanish) 2026-01-29 14:38:57 -05:00
hank
01e75c19ba New translations en.po (Chinese Traditional) 2026-01-23 22:27:32 -05:00
hank
3aea19d66e New translations en.po (Chinese Traditional) 2026-01-23 21:14:18 -05:00
hank
2ba3c87b07 New translations en.po (Dutch) 2026-01-20 17:34:00 -05:00
hank
1565812bd7 New translations en.po (Hungarian) 2026-01-18 18:33:26 -05:00
hank
406b74d70f New translations en.po (Norwegian) 2026-01-17 07:51:47 -05:00
hank
35e254f84b New translations en.po (Russian) 2026-01-17 02:47:22 -05:00
hank
c96d9d9cdf New translations en.po (Polish) 2026-01-15 10:56:08 -05:00
hank
4acb763ac5 New translations en.po (Polish) 2026-01-15 09:49:55 -05:00
hank
ed027c9c37 New translations en.po (French) 2026-01-13 17:24:56 -05:00
hank
c4e847fd1a New translations en.po (Indonesian) 2026-01-12 16:37:34 -05:00
hank
6731b3b9f1 New translations en.po (Chinese Traditional, Hong Kong) 2026-01-11 17:21:15 -05:00
hank
2229ff8a72 New translations en.po (Croatian) 2026-01-11 17:21:14 -05:00
hank
f2579602b0 New translations en.po (Thai) 2026-01-11 17:21:13 -05:00
hank
f3b8be37fc New translations en.po (Persian) 2026-01-11 17:21:12 -05:00
hank
87a802e85f New translations en.po (Vietnamese) 2026-01-11 17:21:11 -05:00
hank
088dbb9933 New translations en.po (Chinese Simplified) 2026-01-11 17:21:10 -05:00
hank
442c1b2922 New translations en.po (Ukrainian) 2026-01-11 17:21:09 -05:00
hank
b443c8d680 New translations en.po (Turkish) 2026-01-11 17:21:08 -05:00
hank
552eaf4240 New translations en.po (Swedish) 2026-01-11 17:21:07 -05:00
hank
125f63603d New translations en.po (Serbian (Cyrillic)) 2026-01-11 17:21:06 -05:00
hank
69e1c3608c New translations en.po (Slovenian) 2026-01-11 17:21:05 -05:00
hank
e4c90c7855 New translations en.po (Russian) 2026-01-11 17:21:04 -05:00
hank
03e2de874a New translations en.po (Portuguese) 2026-01-11 17:21:03 -05:00
hank
9a0b666acc New translations en.po (Norwegian) 2026-01-11 17:21:02 -05:00
hank
de2f96ef2e New translations en.po (Korean) 2026-01-11 17:21:01 -05:00
hank
931eec842f New translations en.po (Japanese) 2026-01-11 17:21:00 -05:00
hank
f52cd6514e New translations en.po (Italian) 2026-01-11 17:20:59 -05:00
hank
b38d424770 New translations en.po (Hungarian) 2026-01-11 17:20:58 -05:00
hank
bd31c8f232 New translations en.po (Hebrew) 2026-01-11 17:20:57 -05:00
hank
254e210d97 New translations en.po (German) 2026-01-11 17:20:56 -05:00
hank
80cb847771 New translations en.po (Czech) 2026-01-11 17:20:55 -05:00
hank
e09e906248 New translations en.po (Bulgarian) 2026-01-11 17:20:54 -05:00
hank
7845d90ce7 New translations en.po (Chinese Traditional) 2026-01-11 17:20:53 -05:00
hank
5989f18cfb New translations en.po (Indonesian) 2026-01-11 17:20:52 -05:00
hank
5b675bddf8 New translations en.po (Polish) 2026-01-11 17:20:52 -05:00
hank
ad97cce189 New translations en.po (Dutch) 2026-01-11 17:20:51 -05:00
hank
789c9f166b New translations en.po (Danish) 2026-01-11 17:20:50 -05:00
hank
40c8696c0f New translations en.po (Arabic) 2026-01-11 17:20:48 -05:00
hank
59cd27a9ec New translations en.po (Spanish) 2026-01-11 17:20:48 -05:00
hank
a59a2bb408 New translations en.po (French) 2026-01-11 17:20:47 -05:00
hank
1cabcc9ecb New translations en.po (Romanian) 2026-01-11 17:20:46 -05:00
hank
7f5a2be7fa New translations en.po (Chinese Traditional, Hong Kong) 2026-01-11 13:53:42 -05:00
hank
ee34919525 New translations en.po (Croatian) 2026-01-11 13:53:41 -05:00
hank
d58c4bfbf3 New translations en.po (Thai) 2026-01-11 13:53:40 -05:00
hank
c811cd2fd6 New translations en.po (Persian) 2026-01-11 13:53:39 -05:00
hank
8cf2678c9a New translations en.po (Vietnamese) 2026-01-11 13:53:38 -05:00
hank
5775e2a7e0 New translations en.po (Chinese Simplified) 2026-01-11 13:53:37 -05:00
hank
cfc9ac437f New translations en.po (Ukrainian) 2026-01-11 13:53:36 -05:00
hank
813c622ebf New translations en.po (Turkish) 2026-01-11 13:53:35 -05:00
hank
9048d645d0 New translations en.po (Swedish) 2026-01-11 13:53:34 -05:00
hank
2e247312aa New translations en.po (Serbian (Cyrillic)) 2026-01-11 13:53:33 -05:00
hank
9e1932394b New translations en.po (Slovenian) 2026-01-11 13:53:32 -05:00
hank
99f156d1d7 New translations en.po (Russian) 2026-01-11 13:53:31 -05:00
hank
10bda5d42d New translations en.po (Portuguese) 2026-01-11 13:53:30 -05:00
hank
5ec86f57b8 New translations en.po (Norwegian) 2026-01-11 13:53:29 -05:00
hank
51fa959ccf New translations en.po (Korean) 2026-01-11 13:53:28 -05:00
hank
9e50162063 New translations en.po (Japanese) 2026-01-11 13:53:27 -05:00
hank
6d13acd294 New translations en.po (Italian) 2026-01-11 13:53:26 -05:00
hank
3570ee8313 New translations en.po (Hungarian) 2026-01-11 13:53:25 -05:00
hank
c9e67ae7b8 New translations en.po (Hebrew) 2026-01-11 13:53:24 -05:00
hank
832e08ac3b New translations en.po (German) 2026-01-11 13:53:23 -05:00
hank
934e877616 New translations en.po (Czech) 2026-01-11 13:53:22 -05:00
hank
e8a8bab2db New translations en.po (Bulgarian) 2026-01-11 13:53:21 -05:00
hank
69645f33d5 New translations en.po (Chinese Traditional) 2026-01-11 13:53:20 -05:00
hank
8af100796a New translations en.po (Indonesian) 2026-01-11 13:53:19 -05:00
hank
eee6698e60 New translations en.po (Polish) 2026-01-11 13:53:18 -05:00
hank
fed9084e07 New translations en.po (Dutch) 2026-01-11 13:53:16 -05:00
hank
56ce1309c9 New translations en.po (Danish) 2026-01-11 13:53:15 -05:00
hank
ce7f096789 New translations en.po (Arabic) 2026-01-11 13:53:14 -05:00
hank
2656f6581a New translations en.po (Spanish) 2026-01-11 13:53:13 -05:00
hank
23055c2255 New translations en.po (French) 2026-01-11 13:53:12 -05:00
hank
e493b72c0f New translations en.po (Romanian) 2026-01-11 13:53:11 -05:00
hank
4b1c20820e New translations en.po (Chinese Traditional) 2026-01-10 12:34:34 -05:00
hank
7c74922760 New translations en.po (French) 2026-01-09 16:08:47 -05:00
hank
19fcce8952 New translations en.po (French) 2025-12-25 17:44:44 -05:00
hank
3d74c23098 New translations en.po (Arabic) 2025-12-25 14:15:27 -05:00
hank
d433962a41 New translations en.po (Indonesian) 2025-12-25 05:45:43 -05:00
hank
c1d94ac29f New translations en.po (Indonesian) 2025-12-25 04:22:51 -05:00
hank
9f4e56c9cf New translations en.po (Romanian) 2025-12-19 06:58:15 -05:00
hank
a3ebfbf37f New translations en.po (Romanian) 2025-12-19 05:55:07 -05:00
hank
cb90e96ae5 New translations en.po (Danish) 2025-12-19 05:55:06 -05:00
hank
25ed5d0d66 New translations en.po (Danish) 2025-12-19 03:46:19 -05:00
hank
3e5b91056e New translations en.po (Polish) 2025-12-18 14:21:36 -05:00
hank
dc79f24c06 New translations en.po (Thai) 2025-12-17 13:16:58 -05:00
hank
f9f5258b22 New translations en.po (Dutch) 2025-12-17 07:02:37 -05:00
hank
0b611cda57 New translations en.po (Dutch) 2025-12-17 05:18:18 -05:00
hank
74635e5763 New translations en.po (Spanish) 2025-12-14 04:39:12 -05:00
hank
8ce9088d9d New translations en.po (Serbian (Cyrillic)) 2025-12-08 13:22:54 -05:00
hank
b3331c00f8 New translations en.po (Chinese Traditional) 2025-12-08 10:28:57 -05:00
hank
d0559065c1 New translations en.po (Chinese Traditional) 2025-12-08 08:54:57 -05:00
hank
78f6006bdc New translations en.po (Serbian (Cyrillic)) 2025-12-05 15:24:47 -05:00
hank
436b42f4d1 New translations en.po (Swedish) 2025-12-05 15:24:42 -05:00
hank
16f7b30624 New translations en.po (Slovenian) 2025-12-05 15:24:41 -05:00
hank
9249256c9f New translations en.po (Portuguese) 2025-12-05 15:24:40 -05:00
hank
34163b8595 New translations en.po (Norwegian) 2025-12-05 15:24:38 -05:00
hank
a4731f9179 New translations en.po (German) 2025-12-05 15:24:34 -05:00
hank
9c74eccaf0 New translations en.po (Danish) 2025-12-05 15:24:33 -05:00
hank
4768adf440 New translations en.po (Czech) 2025-12-05 15:24:32 -05:00
hank
11ffb422e8 New translations en.po (French) 2025-12-05 15:24:29 -05:00
hank
bab02ad738 New translations en.po (Spanish) 2025-12-05 15:24:28 -05:00
hank
61faee2450 New translations en.po (Serbian (Cyrillic)) 2025-12-04 15:03:35 -05:00
hank
d0be54f47c New translations en.po (Chinese Traditional, Hong Kong) 2025-12-02 18:18:21 -05:00
hank
cc2be97055 New translations en.po (Croatian) 2025-12-02 18:18:20 -05:00
hank
2fc1565b75 New translations en.po (Persian) 2025-12-02 18:18:19 -05:00
hank
f5421eff3c New translations en.po (Indonesian) 2025-12-02 18:18:18 -05:00
hank
fc01ca1cad New translations en.po (Vietnamese) 2025-12-02 18:18:17 -05:00
hank
e2923126d2 New translations en.po (Chinese Simplified) 2025-12-02 18:18:16 -05:00
hank
972ce62ff5 New translations en.po (Ukrainian) 2025-12-02 18:18:15 -05:00
hank
05d9297ca3 New translations en.po (Turkish) 2025-12-02 18:18:14 -05:00
hank
ffc35b3c51 New translations en.po (Swedish) 2025-12-02 18:18:13 -05:00
hank
a547de8bf0 New translations en.po (Slovenian) 2025-12-02 18:18:12 -05:00
hank
d8d1a89256 New translations en.po (Portuguese) 2025-12-02 18:18:11 -05:00
hank
c29ba1c353 New translations en.po (Polish) 2025-12-02 18:18:10 -05:00
hank
474d860929 New translations en.po (Norwegian) 2025-12-02 18:18:09 -05:00
hank
fb57a57e77 New translations en.po (Dutch) 2025-12-02 18:18:08 -05:00
hank
02e5a8e9fc New translations en.po (Korean) 2025-12-02 18:18:07 -05:00
hank
c0f6f64aa7 New translations en.po (Japanese) 2025-12-02 18:18:06 -05:00
hank
3077ed045d New translations en.po (Hungarian) 2025-12-02 18:18:05 -05:00
hank
f7b62a2868 New translations en.po (Hebrew) 2025-12-02 18:18:04 -05:00
hank
5daa0d3576 New translations en.po (German) 2025-12-02 18:18:03 -05:00
hank
a6b9fa2aa9 New translations en.po (Danish) 2025-12-02 18:18:02 -05:00
hank
5195e6d675 New translations en.po (Czech) 2025-12-02 18:18:01 -05:00
hank
93e71dcf30 New translations en.po (Bulgarian) 2025-12-02 18:18:00 -05:00
hank
826227f3af New translations en.po (Arabic) 2025-12-02 18:17:59 -05:00
hank
cfe8645c18 New translations en.po (French) 2025-12-02 18:17:58 -05:00
hank
681184b444 New translations en.po (Romanian) 2025-12-02 18:17:56 -05:00
hank
6d759cbe9f New translations en.po (Spanish) 2025-12-02 18:17:55 -05:00
hank
75480f66fa New translations en.po (Russian) 2025-12-02 18:17:55 -05:00
hank
416c237ef3 New translations en.po (Italian) 2025-12-02 18:17:53 -05:00
hank
ff64ac7a37 New translations en.po (Chinese Traditional) 2025-12-02 18:17:52 -05:00
hank
555f668b54 New translations en.po (Spanish) 2025-12-01 18:32:58 -05:00
hank
0dedc634a7 New translations en.po (Russian) 2025-12-01 18:32:57 -05:00
hank
5500e45951 New translations en.po (Chinese Traditional) 2025-12-01 18:32:56 -05:00
hank
e7574a927f New translations en.po (Chinese Traditional) 2025-11-22 06:14:37 -05:00
hank
83fbaa7a3f New translations en.po (Chinese Traditional) 2025-11-22 00:56:30 -05:00
hank
cf3efa1f9f New translations en.po (Chinese Traditional) 2025-11-21 22:50:27 -05:00
hank
24093e33a9 New translations en.po (Russian) 2025-11-21 18:15:30 -05:00
hank
075fad1da4 New translations en.po (Italian) 2025-11-20 11:58:14 -05:00
hank
a35631415a New translations en.po (Polish) 2025-11-17 11:53:50 -05:00
hank
8e99d67174 New translations en.po (Spanish) 2025-11-15 03:26:28 -05:00
hank
cf37c9a93c New translations en.po (Indonesian) 2025-11-14 17:51:43 -05:00
hank
402d1d9fec New translations en.po (Hebrew) 2025-11-14 17:51:41 -05:00
hank
b4f2afa4b6 New translations en.po (Chinese Traditional, Hong Kong) 2025-11-14 17:51:40 -05:00
hank
beff2eb43f New translations en.po (Persian) 2025-11-14 17:51:39 -05:00
hank
2e0d12a02d New translations en.po (Vietnamese) 2025-11-14 17:51:38 -05:00
hank
bc2fd34ac5 New translations en.po (Chinese Simplified) 2025-11-14 17:51:37 -05:00
hank
333cfae109 New translations en.po (Ukrainian) 2025-11-14 17:51:36 -05:00
hank
8cf8dd492d New translations en.po (Turkish) 2025-11-14 17:51:35 -05:00
hank
9b664b6400 New translations en.po (Swedish) 2025-11-14 17:51:34 -05:00
hank
c30d2fbe39 New translations en.po (Slovenian) 2025-11-14 17:51:33 -05:00
hank
f1a4fae659 New translations en.po (Russian) 2025-11-14 17:51:32 -05:00
hank
32675e403f New translations en.po (Polish) 2025-11-14 17:51:31 -05:00
hank
d92d67eece New translations en.po (Korean) 2025-11-14 17:51:30 -05:00
hank
ed1fc63ce2 New translations en.po (Japanese) 2025-11-14 17:51:29 -05:00
hank
0e2b1675fa New translations en.po (Italian) 2025-11-14 17:51:28 -05:00
hank
ee8f901918 New translations en.po (Danish) 2025-11-14 17:51:27 -05:00
hank
7dc2a86b1e New translations en.po (Czech) 2025-11-14 17:51:26 -05:00
hank
51699ddc12 New translations en.po (Bulgarian) 2025-11-14 17:51:25 -05:00
hank
e0675567b8 New translations en.po (Arabic) 2025-11-14 17:51:24 -05:00
hank
61b3102eda New translations en.po (Spanish) 2025-11-14 17:51:23 -05:00
hank
7cc8f2b933 New translations en.po (Romanian) 2025-11-14 17:51:22 -05:00
hank
d38e3eab9c New translations en.po (Croatian) 2025-11-14 17:51:21 -05:00
hank
4ceb06b0c5 New translations en.po (Chinese Traditional) 2025-11-14 17:51:19 -05:00
hank
b1a468a0ab New translations en.po (Portuguese) 2025-11-14 17:51:18 -05:00
hank
c6755183a8 New translations en.po (Norwegian) 2025-11-14 17:51:17 -05:00
hank
cdc9f11ac0 New translations en.po (Dutch) 2025-11-14 17:51:16 -05:00
hank
6ccaaee57e New translations en.po (Hungarian) 2025-11-14 17:51:15 -05:00
hank
214ee4a75a New translations en.po (German) 2025-11-14 17:51:14 -05:00
hank
a506d3c84a New translations en.po (French) 2025-11-14 17:51:13 -05:00
hank
812e849769 New translations en.po (Spanish) 2025-11-14 12:09:18 -05:00
hank
774eef1f3f New translations en.po (Polish) 2025-11-13 09:52:30 -05:00
hank
fc85c50f2f New translations en.po (Indonesian) 2025-11-12 15:36:57 -05:00
hank
fad22eee61 New translations en.po (Hebrew) 2025-11-12 15:36:56 -05:00
hank
e84dbd639b New translations en.po (Chinese Traditional, Hong Kong) 2025-11-12 15:36:55 -05:00
hank
2dd59b4e11 New translations en.po (Persian) 2025-11-12 15:36:54 -05:00
hank
bb649971dc New translations en.po (Vietnamese) 2025-11-12 15:36:53 -05:00
hank
3f82ee0330 New translations en.po (Chinese Simplified) 2025-11-12 15:36:52 -05:00
hank
a26fde66e6 New translations en.po (Ukrainian) 2025-11-12 15:36:51 -05:00
hank
ed9b576bde New translations en.po (Turkish) 2025-11-12 15:36:49 -05:00
hank
aea463f5da New translations en.po (Swedish) 2025-11-12 15:36:48 -05:00
hank
45e00c70ab New translations en.po (Slovenian) 2025-11-12 15:36:47 -05:00
hank
7fc4655f13 New translations en.po (Russian) 2025-11-12 15:36:46 -05:00
hank
7dcaeaa2b4 New translations en.po (Polish) 2025-11-12 15:36:45 -05:00
hank
d07293bf1a New translations en.po (Korean) 2025-11-12 15:36:44 -05:00
hank
38298bbeab New translations en.po (Japanese) 2025-11-12 15:36:43 -05:00
hank
0124ccfac1 New translations en.po (Italian) 2025-11-12 15:36:42 -05:00
hank
5f62ebcd7b New translations en.po (Danish) 2025-11-12 15:36:41 -05:00
hank
ac354e9d6b New translations en.po (Czech) 2025-11-12 15:36:40 -05:00
hank
5572cad7f6 New translations en.po (Bulgarian) 2025-11-12 15:36:39 -05:00
hank
5fa6a7c4e4 New translations en.po (Arabic) 2025-11-12 15:36:37 -05:00
hank
0f9442eaf8 New translations en.po (Spanish) 2025-11-12 15:36:36 -05:00
hank
6094d8d92d New translations en.po (Romanian) 2025-11-12 15:36:35 -05:00
hank
1469710166 New translations en.po (Croatian) 2025-11-12 15:36:34 -05:00
hank
c1df7edddc New translations en.po (Chinese Traditional) 2025-11-12 15:36:33 -05:00
hank
e70f06285c New translations en.po (Portuguese) 2025-11-12 15:36:32 -05:00
hank
c5efa9b20c New translations en.po (Norwegian) 2025-11-12 15:36:31 -05:00
hank
29a948ece4 New translations en.po (Dutch) 2025-11-12 15:36:30 -05:00
hank
d9b587c67b New translations en.po (Hungarian) 2025-11-12 15:36:28 -05:00
hank
f806ae58b6 New translations en.po (German) 2025-11-12 15:36:27 -05:00
hank
8b5dd8dedd New translations en.po (French) 2025-11-12 15:36:26 -05:00
hank
222e5addef New translations en.po (French) 2025-11-11 14:25:02 -05:00
hank
21e7bae720 New translations en.po (Romanian) 2025-11-10 20:21:58 -05:00
hank
a13d90d794 New translations en.po (Swedish) 2025-11-09 20:57:18 -05:00
hank
6e900d0f26 New translations en.po (Portuguese) 2025-11-08 18:43:12 -05:00
hank
112f1853ee New translations en.po (Swedish) 2025-11-06 10:06:27 -05:00
hank
7aa9e3a6d3 New translations en.po (Spanish) 2025-11-05 13:06:57 -05:00
hank
6e9bc6a53b New translations en.po (French) 2025-11-05 10:16:19 -05:00
hank
12eb884b1e New translations en.po (Indonesian) 2025-11-04 18:47:36 -05:00
hank
270afa1c00 New translations en.po (Hebrew) 2025-11-04 18:47:35 -05:00
hank
35505f2d50 New translations en.po (Chinese Traditional, Hong Kong) 2025-11-04 18:47:34 -05:00
hank
5a28ba3a74 New translations en.po (Persian) 2025-11-04 18:47:33 -05:00
hank
08d9126883 New translations en.po (Vietnamese) 2025-11-04 18:47:32 -05:00
hank
e9829229b6 New translations en.po (Chinese Simplified) 2025-11-04 18:47:31 -05:00
hank
6d7bb7ceee New translations en.po (Ukrainian) 2025-11-04 18:47:30 -05:00
hank
60a8b06b72 New translations en.po (Turkish) 2025-11-04 18:47:29 -05:00
hank
c4a145836b New translations en.po (Swedish) 2025-11-04 18:47:28 -05:00
hank
ea37451c98 New translations en.po (Slovenian) 2025-11-04 18:47:27 -05:00
hank
1faad84cc2 New translations en.po (Russian) 2025-11-04 18:47:26 -05:00
hank
d0b6200c5b New translations en.po (Polish) 2025-11-04 18:47:25 -05:00
hank
fb66760665 New translations en.po (Korean) 2025-11-04 18:47:24 -05:00
hank
480f1596bb New translations en.po (Japanese) 2025-11-04 18:47:23 -05:00
hank
6c11e2954e New translations en.po (Italian) 2025-11-04 18:47:22 -05:00
hank
6a5f6530ef New translations en.po (Danish) 2025-11-04 18:47:21 -05:00
hank
a4ee88bf7f New translations en.po (Czech) 2025-11-04 18:47:20 -05:00
hank
d9f1f06b15 New translations en.po (Bulgarian) 2025-11-04 18:47:19 -05:00
hank
4c00988a37 New translations en.po (Arabic) 2025-11-04 18:47:18 -05:00
hank
193fbe9d31 New translations en.po (Spanish) 2025-11-04 18:47:17 -05:00
hank
ca6b6394ef New translations en.po (Romanian) 2025-11-04 18:47:16 -05:00
hank
1600b94846 New translations en.po (Croatian) 2025-11-04 18:47:15 -05:00
hank
b5aa66224a New translations en.po (Chinese Traditional) 2025-11-04 18:47:15 -05:00
hank
5a14eafae5 New translations en.po (Portuguese) 2025-11-04 18:47:14 -05:00
hank
0b11dcdb1b New translations en.po (Norwegian) 2025-11-04 18:47:13 -05:00
hank
fc1c135e71 New translations en.po (Dutch) 2025-11-04 18:47:12 -05:00
hank
7cb6966335 New translations en.po (Hungarian) 2025-11-04 18:47:11 -05:00
hank
c090cf9e3e New translations en.po (German) 2025-11-04 18:47:10 -05:00
hank
224bce7616 New translations en.po (French) 2025-11-04 18:47:09 -05:00
hank
7fc3afb82b New translations en.po (Spanish) 2025-11-04 17:13:09 -05:00
hank
affdd66065 New translations en.po (Portuguese) 2025-11-04 17:13:06 -05:00
hank
466c5a237b New translations en.po (Portuguese) 2025-11-02 10:12:47 -05:00
hank
827758c97f New translations en.po (Spanish) 2025-11-02 01:32:54 -05:00
hank
fb4a35b054 New translations en.po (Indonesian) 2025-11-01 15:01:34 -04:00
hank
ba561ec34c New translations en.po (Hebrew) 2025-11-01 15:01:33 -04:00
hank
a94f85794c New translations en.po (Chinese Traditional, Hong Kong) 2025-11-01 15:01:32 -04:00
hank
aa408f82c6 New translations en.po (Persian) 2025-11-01 15:01:31 -04:00
hank
e8045a3438 New translations en.po (Vietnamese) 2025-11-01 15:01:30 -04:00
hank
7afd678f54 New translations en.po (Chinese Simplified) 2025-11-01 15:01:28 -04:00
hank
77e2b98470 New translations en.po (Ukrainian) 2025-11-01 15:01:27 -04:00
hank
70e894caf4 New translations en.po (Turkish) 2025-11-01 15:01:26 -04:00
hank
433bd6dde1 New translations en.po (Swedish) 2025-11-01 15:01:25 -04:00
hank
f60ee6a839 New translations en.po (Slovenian) 2025-11-01 15:01:24 -04:00
hank
56e6dbf0a8 New translations en.po (Russian) 2025-11-01 15:01:23 -04:00
hank
7ae179764d New translations en.po (Polish) 2025-11-01 15:01:22 -04:00
hank
e54cac2c7b New translations en.po (Korean) 2025-11-01 15:01:21 -04:00
hank
6bc8878408 New translations en.po (Japanese) 2025-11-01 15:01:20 -04:00
hank
0f6d85f124 New translations en.po (Italian) 2025-11-01 15:01:19 -04:00
hank
b6717c11ae New translations en.po (Danish) 2025-11-01 15:01:18 -04:00
hank
3cff9ccff8 New translations en.po (Czech) 2025-11-01 15:01:17 -04:00
hank
3fb5f065b8 New translations en.po (Bulgarian) 2025-11-01 15:01:16 -04:00
hank
73e397abbf New translations en.po (Arabic) 2025-11-01 15:01:15 -04:00
hank
38f63b02bd New translations en.po (Spanish) 2025-11-01 15:01:14 -04:00
hank
879e84bb34 New translations en.po (Romanian) 2025-11-01 15:01:13 -04:00
hank
79b709d53c New translations en.po (Croatian) 2025-11-01 15:01:12 -04:00
hank
97e188a619 New translations en.po (Chinese Traditional) 2025-11-01 15:01:11 -04:00
hank
9459b59b14 New translations en.po (Portuguese) 2025-11-01 15:01:10 -04:00
hank
c281a0717f New translations en.po (Norwegian) 2025-11-01 15:01:09 -04:00
hank
b274261d3e New translations en.po (Dutch) 2025-11-01 15:01:08 -04:00
hank
63a78f2829 New translations en.po (Hungarian) 2025-11-01 15:01:07 -04:00
hank
31aa6df5b2 New translations en.po (German) 2025-11-01 15:01:06 -04:00
hank
74d8f685bd New translations en.po (French) 2025-11-01 15:01:05 -04:00
hank
43a32ef0ce New translations en.po (Spanish) 2025-11-01 13:41:18 -04:00
hank
4426b53f47 New translations en.po (Chinese Traditional) 2025-11-01 03:52:31 -04:00
hank
6902e90ca7 New translations en.po (Hebrew) 2025-10-30 17:53:10 -04:00
hank
9aeb80ce88 New translations en.po (Chinese Simplified) 2025-10-30 17:53:08 -04:00
hank
60c557055f New translations en.po (Italian) 2025-10-30 17:53:02 -04:00
hank
0a6020b6b8 New translations en.po (Arabic) 2025-10-30 17:52:59 -04:00
hank
7f85b8f2a9 New translations en.po (Spanish) 2025-10-30 17:52:58 -04:00
hank
5ca388d855 New translations en.po (Portuguese) 2025-10-30 17:52:56 -04:00
hank
34d74b3bcd New translations en.po (Hebrew) 2025-10-30 16:47:52 -04:00
hank
86ff86aa99 New translations en.po (Indonesian) 2025-10-29 17:56:50 -04:00
hank
d9a463e465 New translations en.po (Hebrew) 2025-10-29 17:56:49 -04:00
hank
a5d0690a81 New translations en.po (Chinese Traditional, Hong Kong) 2025-10-28 19:00:28 -04:00
hank
eef4092b16 New translations en.po (Persian) 2025-10-28 19:00:27 -04:00
hank
a4e89676df New translations en.po (Vietnamese) 2025-10-28 19:00:26 -04:00
hank
3f083686fe New translations en.po (Chinese Simplified) 2025-10-28 19:00:25 -04:00
hank
cf2ccc1eb4 New translations en.po (Ukrainian) 2025-10-28 19:00:24 -04:00
hank
43c44a085b New translations en.po (Turkish) 2025-10-28 19:00:23 -04:00
hank
e90dfde12e New translations en.po (Swedish) 2025-10-28 19:00:22 -04:00
hank
a1bb77f4f2 New translations en.po (Slovenian) 2025-10-28 19:00:21 -04:00
hank
ef582ec1ef New translations en.po (Russian) 2025-10-28 19:00:20 -04:00
hank
2c30bdb2e4 New translations en.po (Polish) 2025-10-28 19:00:19 -04:00
hank
5bfbc0420a New translations en.po (Korean) 2025-10-28 19:00:18 -04:00
hank
b7a3222d31 New translations en.po (Japanese) 2025-10-28 19:00:17 -04:00
hank
d4955af3ba New translations en.po (Italian) 2025-10-28 19:00:16 -04:00
hank
bde88dda26 New translations en.po (Danish) 2025-10-28 19:00:14 -04:00
hank
3f51bd5ec6 New translations en.po (Czech) 2025-10-28 19:00:13 -04:00
hank
092e8c9948 New translations en.po (Bulgarian) 2025-10-28 19:00:11 -04:00
hank
c35762de98 New translations en.po (Arabic) 2025-10-28 19:00:10 -04:00
hank
7c43e9e27c New translations en.po (Spanish) 2025-10-28 19:00:09 -04:00
hank
ee07a0d181 New translations en.po (Romanian) 2025-10-28 19:00:06 -04:00
hank
3178587f20 New translations en.po (Croatian) 2025-10-28 19:00:05 -04:00
hank
b07a791d6a New translations en.po (Chinese Traditional) 2025-10-28 19:00:03 -04:00
hank
8b6918b4a5 New translations en.po (Portuguese) 2025-10-28 19:00:02 -04:00
hank
c7733146b7 New translations en.po (Norwegian) 2025-10-28 19:00:01 -04:00
hank
8f23bbd436 New translations en.po (Dutch) 2025-10-28 18:59:59 -04:00
hank
7ca3cca15d New translations en.po (Hungarian) 2025-10-28 18:59:58 -04:00
hank
dd553ec7b6 New translations en.po (German) 2025-10-28 18:59:57 -04:00
hank
1a8dd0ab32 New translations en.po (French) 2025-10-28 18:59:56 -04:00
hank
48e0c1efbf New translations en.po (German) 2025-10-27 19:49:39 -04:00
hank
8cdddc9f5e New translations en.po (Spanish) 2025-10-27 14:01:40 -04:00
hank
756c5eab3e New translations en.po (Italian) 2025-10-26 22:07:18 -04:00
hank
cf8102d547 New translations en.po (Danish) 2025-10-26 15:21:12 -04:00
hank
156a54f26c New translations en.po (Polish) 2025-10-26 05:04:19 -04:00
hank
ad1e7772af New translations en.po (Chinese Traditional, Hong Kong) 2025-10-25 19:16:37 -04:00
hank
62edf55a37 New translations en.po (Persian) 2025-10-25 19:16:36 -04:00
hank
223b627619 New translations en.po (Vietnamese) 2025-10-25 19:16:35 -04:00
hank
d3bc1a6764 New translations en.po (Chinese Simplified) 2025-10-25 19:16:34 -04:00
hank
5157144504 New translations en.po (Ukrainian) 2025-10-25 19:16:33 -04:00
hank
cdb396408f New translations en.po (Turkish) 2025-10-25 19:16:32 -04:00
hank
ca7c68140a New translations en.po (Swedish) 2025-10-25 19:16:31 -04:00
hank
5af7afb970 New translations en.po (Slovenian) 2025-10-25 19:16:30 -04:00
hank
fd053fc8e5 New translations en.po (Russian) 2025-10-25 19:16:29 -04:00
hank
f1f01657c0 New translations en.po (Polish) 2025-10-25 19:16:28 -04:00
hank
1c9f03c848 New translations en.po (Korean) 2025-10-25 19:16:27 -04:00
hank
82b5ee0424 New translations en.po (Japanese) 2025-10-25 19:16:26 -04:00
hank
0885bf2ba4 New translations en.po (Italian) 2025-10-25 19:16:25 -04:00
hank
d541b42bef New translations en.po (Danish) 2025-10-25 19:16:24 -04:00
hank
29599cd59c New translations en.po (Czech) 2025-10-25 19:16:23 -04:00
hank
5fff9bd3ac New translations en.po (Bulgarian) 2025-10-25 19:16:22 -04:00
hank
81b6198ee7 New translations en.po (Arabic) 2025-10-25 19:16:21 -04:00
hank
01f58a328e New translations en.po (Spanish) 2025-10-25 19:16:20 -04:00
hank
6ffef3c33b New translations en.po (Romanian) 2025-10-25 19:16:19 -04:00
hank
3d50d0cbba New translations en.po (Croatian) 2025-10-25 19:16:18 -04:00
hank
a8ec54f5a5 New translations en.po (Chinese Traditional) 2025-10-25 19:16:17 -04:00
hank
3c44f51671 New translations en.po (Portuguese) 2025-10-25 19:16:16 -04:00
hank
449642fdd2 New translations en.po (Norwegian) 2025-10-25 19:16:15 -04:00
hank
bb324258d6 New translations en.po (Dutch) 2025-10-25 19:16:14 -04:00
hank
693117724a New translations en.po (Hungarian) 2025-10-25 19:16:13 -04:00
hank
008dd9d184 New translations en.po (German) 2025-10-25 19:16:12 -04:00
hank
f171ec9932 New translations en.po (French) 2025-10-25 19:16:11 -04:00
hank
9e6e1771d1 New translations en.po (Chinese Simplified) 2025-10-25 17:09:25 -04:00
hank
bde6264d11 New translations en.po (Spanish) 2025-10-25 17:09:17 -04:00
hank
ae61acbedd New translations en.po (German) 2025-10-25 17:09:12 -04:00
hank
af392c8084 New translations en.po (French) 2025-10-25 16:53:16 -04:00
hank
ce8d206004 New translations en.po (Danish) 2025-10-25 06:58:38 -04:00
hank
5a15e7c048 New translations en.po (Danish) 2025-10-25 05:58:43 -04:00
hank
3a360f3ede New translations en.po (Spanish) 2025-10-24 13:47:34 -04:00
hank
7cf2493af7 New translations en.po (Spanish) 2025-10-24 12:17:55 -04:00
hank
d71a0083bb New translations en.po (Chinese Simplified) 2025-10-23 15:40:46 -04:00
hank
6f6aeeb315 New translations en.po (Danish) 2025-10-22 19:14:03 -04:00
hank
7845d25c83 New translations en.po (Danish) 2025-10-22 17:51:32 -04:00
hank
3320707567 New translations en.po (Danish) 2025-10-22 14:17:32 -04:00
hank
e0df2a1e60 New translations en.po (Norwegian) 2025-10-22 06:37:12 -04:00
hank
881c0cd137 New translations en.po (German) 2025-10-22 06:37:11 -04:00
hank
5bde9500b6 New translations en.po (French) 2025-10-21 06:28:47 -04:00
hank
b43541ea60 New translations en.po (German) 2025-10-21 04:08:22 -04:00
hank
339e443bca New translations en.po (Chinese Traditional, Hong Kong) 2025-10-20 17:37:43 -04:00
hank
8643fb2fd5 New translations en.po (Persian) 2025-10-20 17:37:42 -04:00
hank
8dcf03fb15 New translations en.po (Vietnamese) 2025-10-20 17:37:41 -04:00
hank
53c3b0c359 New translations en.po (Chinese Simplified) 2025-10-20 17:37:40 -04:00
hank
1701947b26 New translations en.po (Ukrainian) 2025-10-20 17:37:39 -04:00
hank
c8cb041855 New translations en.po (Turkish) 2025-10-20 17:37:38 -04:00
hank
032d06601e New translations en.po (Swedish) 2025-10-20 17:37:37 -04:00
hank
1507825c16 New translations en.po (Slovenian) 2025-10-20 17:37:36 -04:00
hank
073fc308bb New translations en.po (Russian) 2025-10-20 17:37:35 -04:00
hank
9d5aaaf989 New translations en.po (Polish) 2025-10-20 17:37:34 -04:00
hank
0f6063ebe5 New translations en.po (Korean) 2025-10-20 17:37:33 -04:00
hank
742c217b5f New translations en.po (Japanese) 2025-10-20 17:37:32 -04:00
hank
85589e1e07 New translations en.po (Italian) 2025-10-20 17:37:31 -04:00
hank
6ceb58254b New translations en.po (Greek) 2025-10-20 17:37:30 -04:00
hank
10e21993ce New translations en.po (Danish) 2025-10-20 17:37:29 -04:00
hank
ccff653ef1 New translations en.po (Czech) 2025-10-20 17:37:28 -04:00
hank
323705aced New translations en.po (Bulgarian) 2025-10-20 17:37:27 -04:00
hank
774ddaa726 New translations en.po (Arabic) 2025-10-20 17:37:26 -04:00
hank
e75ada4483 New translations en.po (Spanish) 2025-10-20 17:37:24 -04:00
hank
14e8b28b85 New translations en.po (Romanian) 2025-10-20 17:37:24 -04:00
hank
1f7f764fca New translations en.po (Croatian) 2025-10-20 17:37:23 -04:00
hank
2757e51040 New translations en.po (Chinese Traditional) 2025-10-20 17:37:22 -04:00
hank
1233e6bee6 New translations en.po (Portuguese) 2025-10-20 17:37:21 -04:00
hank
e4619b303e New translations en.po (Norwegian) 2025-10-20 17:37:20 -04:00
hank
3abb7a2a29 New translations en.po (Dutch) 2025-10-20 17:37:19 -04:00
hank
045c3cfdf8 New translations en.po (Hungarian) 2025-10-20 17:37:18 -04:00
hank
4b5e1cc5fa New translations en.po (German) 2025-10-20 17:37:17 -04:00
hank
7600a47d08 New translations en.po (French) 2025-10-20 17:37:16 -04:00
hank
47827c09f6 New translations en.po (French) 2025-10-20 12:38:50 -04:00
hank
c8c84ca0ad New translations en.po (French) 2025-10-20 11:14:32 -04:00
hank
309860f9d0 New translations en.po (Chinese Traditional, Hong Kong) 2025-10-18 19:59:24 -04:00
hank
2a76cf4a1f New translations en.po (Persian) 2025-10-18 19:59:23 -04:00
hank
6d6b6891e1 New translations en.po (Vietnamese) 2025-10-18 19:59:22 -04:00
hank
bdd24b95d2 New translations en.po (Chinese Simplified) 2025-10-18 19:59:21 -04:00
hank
9ae2bee9e3 New translations en.po (Ukrainian) 2025-10-18 19:59:20 -04:00
hank
b2396de0d9 New translations en.po (Turkish) 2025-10-18 19:59:19 -04:00
hank
d85e3bc26f New translations en.po (Swedish) 2025-10-18 19:59:18 -04:00
hank
2a3220be5a New translations en.po (Slovenian) 2025-10-18 19:59:17 -04:00
hank
92910faca0 New translations en.po (Russian) 2025-10-18 19:59:16 -04:00
hank
d596474426 New translations en.po (Polish) 2025-10-18 19:59:15 -04:00
hank
db471ea619 New translations en.po (Korean) 2025-10-18 19:59:13 -04:00
hank
f6e30b1c9f New translations en.po (Japanese) 2025-10-18 19:59:13 -04:00
hank
dcc013330e New translations en.po (Italian) 2025-10-18 19:59:12 -04:00
hank
d6feda8a91 New translations en.po (Greek) 2025-10-18 19:59:11 -04:00
hank
310892d401 New translations en.po (Danish) 2025-10-18 19:59:09 -04:00
hank
302e951bb9 New translations en.po (Czech) 2025-10-18 19:59:08 -04:00
hank
6810270f51 New translations en.po (Bulgarian) 2025-10-18 19:59:07 -04:00
hank
1403f75781 New translations en.po (Arabic) 2025-10-18 19:59:06 -04:00
hank
660a7967f8 New translations en.po (Spanish) 2025-10-18 19:59:05 -04:00
hank
5ad420a6bc New translations en.po (Romanian) 2025-10-18 19:59:04 -04:00
hank
7403f67109 New translations en.po (Croatian) 2025-10-18 19:59:03 -04:00
hank
626b865c3b New translations en.po (Chinese Traditional) 2025-10-18 19:59:02 -04:00
hank
ebbddef0d9 New translations en.po (Portuguese) 2025-10-18 19:59:01 -04:00
hank
a528ddfea3 New translations en.po (Norwegian) 2025-10-18 19:59:00 -04:00
hank
9c6a4873b2 New translations en.po (Dutch) 2025-10-18 19:58:59 -04:00
hank
82e976ff0b New translations en.po (Hungarian) 2025-10-18 19:58:58 -04:00
hank
5342f2cbbc New translations en.po (German) 2025-10-18 19:58:57 -04:00
hank
c7838f744f New translations en.po (French) 2025-10-18 19:58:56 -04:00
hank
74e41851cf New translations en.po (Croatian) 2025-10-18 18:52:43 -04:00
hank
f1342a305c New translations en.po (Hungarian) 2025-10-14 19:40:36 -04:00
hank
7f926c687b New translations en.po (Hungarian) 2025-10-14 16:31:25 -04:00
hank
496cc67390 New translations en.po (Hungarian) 2025-10-14 15:16:43 -04:00
hank
e4b300bc71 New translations en.po (Hungarian) 2025-10-14 14:15:05 -04:00
hank
cee20d701a New translations en.po (Norwegian) 2025-10-13 13:31:12 -04:00
hank
0cd5f3696d New translations en.po (Norwegian) 2025-10-13 11:54:15 -04:00
hank
3686df0f9d New translations en.po (Chinese Traditional) 2025-10-11 23:15:08 -04:00
hank
f58f555367 New translations en.po (French) 2025-10-09 18:25:14 -04:00
hank
adfa14ccbe New translations en.po (Portuguese) 2025-10-09 08:03:07 -04:00
hank
26a147e2e5 New translations en.po (Norwegian) 2025-10-06 03:37:07 -04:00
hank
b9a74e1284 New translations en.po (German) 2025-10-05 12:13:24 -04:00
hank
21d2b3ec7b New translations en.po (Romanian) 2025-10-03 14:42:32 -04:00
hank
69d94b0bf9 New translations en.po (Chinese Traditional, Hong Kong) 2025-10-03 14:42:31 -04:00
hank
5e49fca60e New translations en.po (Croatian) 2025-10-03 14:42:30 -04:00
hank
9babff17d1 New translations en.po (Persian) 2025-10-03 14:42:29 -04:00
hank
be86983f00 New translations en.po (Vietnamese) 2025-10-03 14:42:28 -04:00
hank
907bb4dc52 New translations en.po (Chinese Traditional) 2025-10-03 14:42:27 -04:00
hank
9a34a3700d New translations en.po (Chinese Simplified) 2025-10-03 14:42:26 -04:00
hank
fdb468abf4 New translations en.po (Ukrainian) 2025-10-03 14:42:25 -04:00
hank
0de0326778 New translations en.po (Turkish) 2025-10-03 14:42:24 -04:00
hank
30db58b94f New translations en.po (Swedish) 2025-10-03 14:42:23 -04:00
hank
8e40b1013b New translations en.po (Slovenian) 2025-10-03 14:42:22 -04:00
hank
aa96521696 New translations en.po (Russian) 2025-10-03 14:42:21 -04:00
hank
17f40d58ac New translations en.po (Portuguese) 2025-10-03 14:42:20 -04:00
hank
bdcdda4e9c New translations en.po (Polish) 2025-10-03 14:42:18 -04:00
hank
c36d57f962 New translations en.po (Norwegian) 2025-10-03 14:42:17 -04:00
hank
542ac4bfc0 New translations en.po (Dutch) 2025-10-03 14:42:16 -04:00
hank
68a684f3d6 New translations en.po (Korean) 2025-10-03 14:42:15 -04:00
hank
b9bcb372f7 New translations en.po (Japanese) 2025-10-03 14:42:14 -04:00
hank
3d94451124 New translations en.po (Italian) 2025-10-03 14:42:13 -04:00
hank
0af952d66c New translations en.po (Hungarian) 2025-10-03 14:42:12 -04:00
hank
e46bc1ee36 New translations en.po (Greek) 2025-10-03 14:42:11 -04:00
hank
5d297be871 New translations en.po (German) 2025-10-03 14:42:10 -04:00
hank
6c0bc90f96 New translations en.po (Danish) 2025-10-03 14:42:09 -04:00
hank
db0b6f77e3 New translations en.po (Czech) 2025-10-03 14:42:08 -04:00
hank
7f42ab68d2 New translations en.po (Bulgarian) 2025-10-03 14:42:07 -04:00
hank
c4f6e81c56 New translations en.po (Arabic) 2025-10-03 14:42:06 -04:00
hank
3bf595959b New translations en.po (Spanish) 2025-10-03 14:42:05 -04:00
hank
5af1e058b0 New translations en.po (French) 2025-10-03 14:42:03 -04:00
hank
ec62d1597b New translations en.po (Romanian) 2025-10-03 13:07:14 -04:00
hank
fa06a2935b New translations en.po (Russian) 2025-09-28 03:31:06 -04:00
hank
2cdd521a10 New translations en.po (Portuguese) 2025-09-26 15:48:54 -04:00
hank
90ac853e4f New translations en.po (Slovenian) 2025-09-25 13:11:54 -04:00
hank
007fe0c0af New translations en.po (Croatian) 2025-09-24 18:31:13 -04:00
hank
f3afcd351a New translations en.po (Chinese Traditional) 2025-09-24 18:31:12 -04:00
hank
8eb161171e New translations en.po (Chinese Simplified) 2025-09-24 18:31:11 -04:00
hank
413f829107 New translations en.po (Croatian) 2025-09-23 08:43:25 -04:00
hank
7f09474f33 New translations en.po (Korean) 2025-09-22 22:45:02 -04:00
hank
28386c58db New translations en.po (Chinese Traditional, Hong Kong) 2025-09-22 19:10:36 -04:00
hank
e79aae7925 New translations en.po (Croatian) 2025-09-22 19:10:35 -04:00
hank
fabadf998b New translations en.po (Persian) 2025-09-22 19:10:34 -04:00
hank
2f0d158ed8 New translations en.po (Icelandic) 2025-09-22 19:10:33 -04:00
hank
b5f08d4e4c New translations en.po (Vietnamese) 2025-09-22 19:10:32 -04:00
hank
fce10da7f6 New translations en.po (Chinese Traditional) 2025-09-22 19:10:31 -04:00
hank
d59937dba7 New translations en.po (Chinese Simplified) 2025-09-22 19:10:30 -04:00
hank
0eb2bfab12 New translations en.po (Ukrainian) 2025-09-22 19:10:29 -04:00
hank
bb4111671f New translations en.po (Turkish) 2025-09-22 19:10:28 -04:00
hank
5e50d791fd New translations en.po (Swedish) 2025-09-22 19:10:27 -04:00
hank
cfaf0712d6 New translations en.po (Slovenian) 2025-09-22 19:10:26 -04:00
hank
9cb2d694fe New translations en.po (Russian) 2025-09-22 19:10:25 -04:00
hank
9f4c6b30d8 New translations en.po (Portuguese) 2025-09-22 19:10:24 -04:00
hank
b9055a5d22 New translations en.po (Polish) 2025-09-22 19:10:23 -04:00
hank
aa0d1e7f61 New translations en.po (Norwegian) 2025-09-22 19:10:22 -04:00
hank
35a8cb1d36 New translations en.po (Dutch) 2025-09-22 19:10:21 -04:00
hank
92ba8a0ca3 New translations en.po (Korean) 2025-09-22 19:10:20 -04:00
hank
acbc02162f New translations en.po (Japanese) 2025-09-22 19:10:19 -04:00
hank
8da777b6f4 New translations en.po (Italian) 2025-09-22 19:10:18 -04:00
hank
e62ec1d993 New translations en.po (Hungarian) 2025-09-22 19:10:16 -04:00
hank
4fa11b4c79 New translations en.po (Greek) 2025-09-22 19:10:15 -04:00
hank
c9dba873ee New translations en.po (German) 2025-09-22 19:10:14 -04:00
hank
3e2e897f34 New translations en.po (Danish) 2025-09-22 19:10:13 -04:00
hank
9658fba5aa New translations en.po (Czech) 2025-09-22 19:10:12 -04:00
hank
1c00c39eac New translations en.po (Bulgarian) 2025-09-22 19:10:11 -04:00
hank
2d3f186c18 New translations en.po (Arabic) 2025-09-22 19:10:10 -04:00
hank
983a471e6f New translations en.po (Spanish) 2025-09-22 19:10:08 -04:00
hank
7a228f553c New translations en.po (French) 2025-09-22 19:10:08 -04:00
hank
007dfa9519 New translations en.po (Dutch) 2025-09-21 14:30:42 -04:00
hank
f3a74b1f46 New translations en.po (Polish) 2025-09-18 11:36:03 -04:00
hank
4a76b620e7 New translations en.po (Polish) 2025-09-18 08:40:40 -04:00
hank
25210d031d New translations en.po (Chinese Traditional, Hong Kong) 2025-09-17 14:45:45 -04:00
hank
87b55fd4cf New translations en.po (Croatian) 2025-09-17 14:45:44 -04:00
hank
b4430ac76f New translations en.po (Persian) 2025-09-17 14:45:43 -04:00
hank
5f9aa78b72 New translations en.po (Icelandic) 2025-09-17 14:45:42 -04:00
hank
7537f6bd5c New translations en.po (Vietnamese) 2025-09-17 14:45:41 -04:00
hank
83dee5e554 New translations en.po (Chinese Traditional) 2025-09-17 14:45:40 -04:00
hank
036b4495e6 New translations en.po (Chinese Simplified) 2025-09-17 14:45:39 -04:00
hank
31cd36fcc1 New translations en.po (Ukrainian) 2025-09-17 14:45:38 -04:00
hank
0a2aaf3260 New translations en.po (Turkish) 2025-09-17 14:45:36 -04:00
hank
6e2e90120a New translations en.po (Swedish) 2025-09-17 14:45:35 -04:00
hank
23f95d6ebd New translations en.po (Slovenian) 2025-09-17 14:45:35 -04:00
hank
56dc1096d9 New translations en.po (Russian) 2025-09-17 14:45:34 -04:00
hank
9dd203f85a New translations en.po (Portuguese) 2025-09-17 14:45:32 -04:00
hank
adac9cf79d New translations en.po (Polish) 2025-09-17 14:45:31 -04:00
hank
efd2ba04c5 New translations en.po (Norwegian) 2025-09-17 14:45:30 -04:00
hank
a9055e216d New translations en.po (Dutch) 2025-09-17 14:45:29 -04:00
hank
f64130029b New translations en.po (Korean) 2025-09-17 14:45:28 -04:00
hank
6d172bac82 New translations en.po (Japanese) 2025-09-17 14:45:27 -04:00
hank
354cba5690 New translations en.po (Italian) 2025-09-17 14:45:26 -04:00
hank
e65cde9675 New translations en.po (Hungarian) 2025-09-17 14:45:25 -04:00
hank
fbd2fbb6a6 New translations en.po (Greek) 2025-09-17 14:45:24 -04:00
hank
01bf64083a New translations en.po (German) 2025-09-17 14:45:23 -04:00
hank
103856121d New translations en.po (Danish) 2025-09-17 14:45:21 -04:00
hank
8a798c7e3f New translations en.po (Czech) 2025-09-17 14:45:20 -04:00
hank
beeec5c39e New translations en.po (Bulgarian) 2025-09-17 14:45:19 -04:00
hank
6d43045d79 New translations en.po (Arabic) 2025-09-17 14:45:18 -04:00
hank
88ea94f5b0 New translations en.po (Spanish) 2025-09-17 14:45:17 -04:00
hank
3b8d333f8e New translations en.po (French) 2025-09-17 14:45:16 -04:00
hank
3a06982502 New translations en.po (Chinese Simplified) 2025-09-17 01:21:38 -04:00
hank
b820b46042 New translations en.po (Polish) 2025-09-14 04:46:36 -04:00
hank
fab799f177 New translations en.po (Chinese Traditional, Hong Kong) 2025-09-11 12:53:01 -04:00
hank
5eaf9b9157 New translations en.po (Croatian) 2025-09-11 12:53:00 -04:00
hank
1eed3c53c8 New translations en.po (Persian) 2025-09-11 12:52:59 -04:00
hank
90729a7a95 New translations en.po (Icelandic) 2025-09-11 12:52:58 -04:00
hank
d450f6df10 New translations en.po (Vietnamese) 2025-09-11 12:52:57 -04:00
hank
7aa2bcf761 New translations en.po (Chinese Traditional) 2025-09-11 12:52:55 -04:00
hank
a7a86f46c3 New translations en.po (Chinese Simplified) 2025-09-11 12:52:54 -04:00
hank
17e30aff60 New translations en.po (Ukrainian) 2025-09-11 12:52:53 -04:00
hank
66008e47f3 New translations en.po (Turkish) 2025-09-11 12:52:52 -04:00
hank
56788b1e5b New translations en.po (Swedish) 2025-09-11 12:52:51 -04:00
hank
b72371487a New translations en.po (Slovenian) 2025-09-11 12:52:50 -04:00
hank
7656b4189e New translations en.po (Russian) 2025-09-11 12:52:49 -04:00
hank
8e6731c102 New translations en.po (Portuguese) 2025-09-11 12:52:48 -04:00
hank
e86fa40fe4 New translations en.po (Polish) 2025-09-11 12:52:46 -04:00
hank
f0e728a1ed New translations en.po (Norwegian) 2025-09-11 12:52:45 -04:00
hank
bb076eb439 New translations en.po (Dutch) 2025-09-11 12:52:44 -04:00
hank
2f0b16367a New translations en.po (Korean) 2025-09-11 12:52:43 -04:00
hank
aa33124e18 New translations en.po (Japanese) 2025-09-11 12:52:42 -04:00
hank
42f404c80a New translations en.po (Italian) 2025-09-11 12:52:41 -04:00
hank
5056fddd40 New translations en.po (Hungarian) 2025-09-11 12:52:40 -04:00
hank
2296202ea1 New translations en.po (Greek) 2025-09-11 12:52:39 -04:00
hank
c034e9b0fa New translations en.po (German) 2025-09-11 12:52:38 -04:00
hank
2d45119a98 New translations en.po (Danish) 2025-09-11 12:52:36 -04:00
hank
63be4f1ab5 New translations en.po (Czech) 2025-09-11 12:52:35 -04:00
hank
6d2259100e New translations en.po (Bulgarian) 2025-09-11 12:52:34 -04:00
hank
142af6e7b6 New translations en.po (Arabic) 2025-09-11 12:52:33 -04:00
hank
5c1e009188 New translations en.po (Spanish) 2025-09-11 12:52:32 -04:00
hank
27b2cb84d6 New translations en.po (French) 2025-09-11 12:52:31 -04:00
61 changed files with 27141 additions and 3080 deletions

1
.gitignore vendored
View File

@@ -20,4 +20,3 @@ __debug_*
agent/lhm/obj
agent/lhm/bin
dockerfile_agent_dev
.vite

View File

@@ -77,7 +77,7 @@ dev-hub: export ENV=dev
dev-hub:
mkdir -p ./internal/site/dist && touch ./internal/site/dist/index.html
@if command -v entr >/dev/null 2>&1; then \
find ./internal -type f -name '*.go' | entr -r -s "cd ./internal/cmd/hub && go run -tags development . serve --http 0.0.0.0:8090"; \
find ./internal/cmd/hub/*.go ./internal/{alerts,hub,records,users}/*.go | entr -r -s "cd ./internal/cmd/hub && go run -tags development . serve --http 0.0.0.0:8090"; \
else \
cd ./internal/cmd/hub && go run -tags development . serve --http 0.0.0.0:8090; \
fi

View File

@@ -22,23 +22,23 @@ import (
)
type Agent struct {
sync.Mutex // Used to lock agent while collecting data
debug bool // true if LOG_LEVEL is set to debug
zfs bool // true if system has arcstats
memCalc string // Memory calculation formula
fsNames []string // List of filesystem device names being monitored
fsStats map[string]*system.FsStats // Keeps track of disk stats for each filesystem
netInterfaces map[string]struct{} // Stores all valid network interfaces
netIoStats map[string]system.NetIoStats // Keeps track of per-interface bandwidth usage
dockerManager *dockerManager // Manages Docker API requests
sensorConfig *SensorConfig // Sensors config
systemInfo system.Info // Host system info
gpuManager *GPUManager // Manages GPU data
cache *SessionCache // Cache for system stats based on primary session ID
connectionManager *ConnectionManager // Channel to signal connection events
server *ssh.Server // SSH server
dataDir string // Directory for persisting data
keys []gossh.PublicKey // SSH public keys
sync.Mutex // Used to lock agent while collecting data
debug bool // true if LOG_LEVEL is set to debug
zfs bool // true if system has arcstats
memCalc string // Memory calculation formula
fsNames []string // List of filesystem device names being monitored
fsStats map[string]*system.FsStats // Keeps track of disk stats for each filesystem
netInterfaces map[string]struct{} // Stores all valid network interfaces
netIoStats system.NetIoStats // Keeps track of bandwidth usage
dockerManager *dockerManager // Manages Docker API requests
sensorConfig *SensorConfig // Sensors config
systemInfo system.Info // Host system info
gpuManager *GPUManager // Manages GPU data
cache *SessionCache // Cache for system stats based on primary session ID
connectionManager *ConnectionManager // Channel to signal connection events
server *ssh.Server // SSH server
dataDir string // Directory for persisting data
keys []gossh.PublicKey // SSH public keys
}
// NewAgent creates a new agent with the given data directory for persisting data.

View File

@@ -85,7 +85,7 @@ func getToken() (string, error) {
if err != nil {
return "", err
}
return strings.TrimSpace(string(tokenBytes)), nil
return string(tokenBytes), nil
}
// getOptions returns the WebSocket client options, creating them if necessary.

View File

@@ -537,25 +537,4 @@ func TestGetToken(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "", token, "Empty file should return empty string")
})
t.Run("strips whitespace from TOKEN_FILE", func(t *testing.T) {
unsetEnvVars()
tokenWithWhitespace := " test-token-with-whitespace \n\t"
expectedToken := "test-token-with-whitespace"
tokenFile, err := os.CreateTemp("", "token-test-*.txt")
require.NoError(t, err)
defer os.Remove(tokenFile.Name())
_, err = tokenFile.WriteString(tokenWithWhitespace)
require.NoError(t, err)
tokenFile.Close()
os.Setenv("TOKEN_FILE", tokenFile.Name())
defer os.Unsetenv("TOKEN_FILE")
token, err := getToken()
assert.NoError(t, err)
assert.Equal(t, expectedToken, token, "Whitespace should be stripped from token file content")
})
}

View File

@@ -5,16 +5,12 @@ import (
"strings"
"time"
"github.com/henrygd/beszel/internal/entities/system"
psutilNet "github.com/shirou/gopsutil/v4/net"
)
func (a *Agent) initializeNetIoStats() {
// reset valid network interfaces
a.netInterfaces = make(map[string]struct{}, 0)
// reset network I/O stats per interface
a.netIoStats = make(map[string]system.NetIoStats, 0)
// map of network interface names passed in via NICS env var
var nicsMap map[string]struct{}
@@ -26,10 +22,13 @@ func (a *Agent) initializeNetIoStats() {
}
}
// reset network I/O stats
a.netIoStats.BytesSent = 0
a.netIoStats.BytesRecv = 0
// get intial network I/O stats
if netIO, err := psutilNet.IOCounters(true); err == nil {
now := time.Now()
a.netIoStats.Time = time.Now()
for _, v := range netIO {
switch {
// skip if nics exists and the interface is not in the list
@@ -44,15 +43,10 @@ func (a *Agent) initializeNetIoStats() {
}
}
slog.Info("Detected network interface", "name", v.Name, "sent", v.BytesSent, "recv", v.BytesRecv)
a.netIoStats.BytesSent += v.BytesSent
a.netIoStats.BytesRecv += v.BytesRecv
// store as a valid network interface
a.netInterfaces[v.Name] = struct{}{}
// initialize per-interface stats
a.netIoStats[v.Name] = system.NetIoStats{
BytesRecv: v.BytesRecv,
BytesSent: v.BytesSent,
Time: now,
Name: v.Name,
}
}
}
}

View File

@@ -176,85 +176,53 @@ func (a *Agent) getSystemStats() system.Stats {
if len(a.netInterfaces) == 0 {
// if no network interfaces, initialize again
// this is a fix if agent started before network is online (#466)
// maybe refactor this in the future to not cache interface names at all so we
// don't miss an interface that's been added after agent started in any circumstance
a.initializeNetIoStats()
}
if netIO, err := psutilNet.IOCounters(true); err == nil {
now := time.Now()
// pre-allocate maps with known capacity
interfaceCount := len(a.netInterfaces)
if systemStats.NetworkInterfaces == nil || len(systemStats.NetworkInterfaces) != interfaceCount {
systemStats.NetworkInterfaces = make(map[string]system.NetworkInterfaceStats, interfaceCount)
}
var totalSent, totalRecv float64
// single pass through interfaces
msElapsed := uint64(time.Since(a.netIoStats.Time).Milliseconds())
a.netIoStats.Time = time.Now()
totalBytesSent := uint64(0)
totalBytesRecv := uint64(0)
// sum all bytes sent and received
for _, v := range netIO {
// skip if not in valid network interfaces list
if _, exists := a.netInterfaces[v.Name]; !exists {
continue
}
// get previous stats for this interface
prevStats, exists := a.netIoStats[v.Name]
var networkSentPs, networkRecvPs float64
if exists {
secondsElapsed := time.Since(prevStats.Time).Seconds()
if secondsElapsed > 0 {
// direct calculation to MB/s, avoiding intermediate bytes/sec
networkSentPs = bytesToMegabytes(float64(v.BytesSent-prevStats.BytesSent) / secondsElapsed)
networkRecvPs = bytesToMegabytes(float64(v.BytesRecv-prevStats.BytesRecv) / secondsElapsed)
}
}
// accumulate totals
totalSent += networkSentPs
totalRecv += networkRecvPs
// store per-interface stats
systemStats.NetworkInterfaces[v.Name] = system.NetworkInterfaceStats{
NetworkSent: networkSentPs,
NetworkRecv: networkRecvPs,
TotalBytesSent: v.BytesSent,
TotalBytesRecv: v.BytesRecv,
}
// update previous stats (reuse existing struct if possible)
if prevStats.Name == v.Name {
prevStats.BytesRecv = v.BytesRecv
prevStats.BytesSent = v.BytesSent
prevStats.PacketsSent = v.PacketsSent
prevStats.PacketsRecv = v.PacketsRecv
prevStats.Time = now
a.netIoStats[v.Name] = prevStats
} else {
a.netIoStats[v.Name] = system.NetIoStats{
BytesRecv: v.BytesRecv,
BytesSent: v.BytesSent,
PacketsSent: v.PacketsSent,
PacketsRecv: v.PacketsRecv,
Time: now,
Name: v.Name,
}
}
totalBytesSent += v.BytesSent
totalBytesRecv += v.BytesRecv
}
// add to systemStats
var bytesSentPerSecond, bytesRecvPerSecond uint64
if msElapsed > 0 {
bytesSentPerSecond = (totalBytesSent - a.netIoStats.BytesSent) * 1000 / msElapsed
bytesRecvPerSecond = (totalBytesRecv - a.netIoStats.BytesRecv) * 1000 / msElapsed
}
networkSentPs := bytesToMegabytes(float64(bytesSentPerSecond))
networkRecvPs := bytesToMegabytes(float64(bytesRecvPerSecond))
// add check for issue (#150) where sent is a massive number
if totalSent > 10_000 || totalRecv > 10_000 {
slog.Warn("Invalid net stats. Resetting.", "sent", totalSent, "recv", totalRecv)
if networkSentPs > 10_000 || networkRecvPs > 10_000 {
slog.Warn("Invalid net stats. Resetting.", "sent", networkSentPs, "recv", networkRecvPs)
for _, v := range netIO {
if _, exists := a.netInterfaces[v.Name]; !exists {
continue
}
slog.Info(v.Name, "recv", v.BytesRecv, "sent", v.BytesSent)
}
// reset network I/O stats
a.initializeNetIoStats()
} else {
systemStats.NetworkSent = totalSent
systemStats.NetworkRecv = totalRecv
systemStats.NetworkSent = networkSentPs
systemStats.NetworkRecv = networkRecvPs
systemStats.Bandwidth[0], systemStats.Bandwidth[1] = bytesSentPerSecond, bytesRecvPerSecond
// update netIoStats
a.netIoStats.BytesSent = totalBytesSent
a.netIoStats.BytesRecv = totalBytesRecv
}
}
// connection counts
a.updateConnectionCounts(&systemStats)
// temperatures
// TODO: maybe refactor to methods on systemStats
a.updateTemperatures(&systemStats)
@@ -302,109 +270,14 @@ func (a *Agent) getSystemStats() system.Stats {
a.systemInfo.MemPct = systemStats.MemPct
a.systemInfo.DiskPct = systemStats.DiskPct
a.systemInfo.Uptime, _ = host.Uptime()
// Sum all per-interface network sent/recv and assign to systemInfo
var totalSent, totalRecv float64
for _, iface := range systemStats.NetworkInterfaces {
totalSent += iface.NetworkSent
totalRecv += iface.NetworkRecv
}
a.systemInfo.NetworkSent = twoDecimals(totalSent)
a.systemInfo.NetworkRecv = twoDecimals(totalRecv)
// TODO: in future release, remove MB bandwidth values in favor of bytes
a.systemInfo.Bandwidth = twoDecimals(systemStats.NetworkSent + systemStats.NetworkRecv)
a.systemInfo.BandwidthBytes = systemStats.Bandwidth[0] + systemStats.Bandwidth[1]
slog.Debug("sysinfo", "data", a.systemInfo)
return systemStats
}
func (a *Agent) updateConnectionCounts(systemStats *system.Stats) {
// Get IPv4 connections
connectionsIPv4, err := psutilNet.Connections("inet")
if err != nil {
slog.Debug("Failed to get IPv4 connection stats", "err", err)
return
}
// Get IPv6 connections
connectionsIPv6, err := psutilNet.Connections("inet6")
if err != nil {
slog.Debug("Failed to get IPv6 connection stats", "err", err)
// Continue with IPv4 only if IPv6 fails
}
// Initialize Nets map if needed
if systemStats.Nets == nil {
systemStats.Nets = make(map[string]float64)
}
// Count IPv4 connection states
connStatsIPv4 := map[string]int{
"established": 0,
"listen": 0,
"time_wait": 0,
"close_wait": 0,
"syn_recv": 0,
}
for _, conn := range connectionsIPv4 {
// Only count TCP connections (Type 1 = SOCK_STREAM)
if conn.Type == 1 {
switch strings.ToUpper(conn.Status) {
case "ESTABLISHED":
connStatsIPv4["established"]++
case "LISTEN":
connStatsIPv4["listen"]++
case "TIME_WAIT":
connStatsIPv4["time_wait"]++
case "CLOSE_WAIT":
connStatsIPv4["close_wait"]++
case "SYN_RECV":
connStatsIPv4["syn_recv"]++
}
}
}
// Count IPv6 connection states
connStatsIPv6 := map[string]int{
"established": 0,
"listen": 0,
"time_wait": 0,
"close_wait": 0,
"syn_recv": 0,
}
for _, conn := range connectionsIPv6 {
// Only count TCP connections (Type 1 = SOCK_STREAM)
if conn.Type == 1 {
switch strings.ToUpper(conn.Status) {
case "ESTABLISHED":
connStatsIPv6["established"]++
case "LISTEN":
connStatsIPv6["listen"]++
case "TIME_WAIT":
connStatsIPv6["time_wait"]++
case "CLOSE_WAIT":
connStatsIPv6["close_wait"]++
case "SYN_RECV":
connStatsIPv6["syn_recv"]++
}
}
}
// Add IPv4 connection counts to Nets
systemStats.Nets["conn_established"] = float64(connStatsIPv4["established"])
systemStats.Nets["conn_listen"] = float64(connStatsIPv4["listen"])
systemStats.Nets["conn_timewait"] = float64(connStatsIPv4["time_wait"])
systemStats.Nets["conn_closewait"] = float64(connStatsIPv4["close_wait"])
systemStats.Nets["conn_synrecv"] = float64(connStatsIPv4["syn_recv"])
// Add IPv6 connection counts to Nets
systemStats.Nets["conn6_established"] = float64(connStatsIPv6["established"])
systemStats.Nets["conn6_listen"] = float64(connStatsIPv6["listen"])
systemStats.Nets["conn6_timewait"] = float64(connStatsIPv6["time_wait"])
systemStats.Nets["conn6_closewait"] = float64(connStatsIPv6["close_wait"])
systemStats.Nets["conn6_synrecv"] = float64(connStatsIPv6["syn_recv"])
}
// Returns the size of the ZFS ARC memory cache in bytes
func getARCSize() (uint64, error) {
file, err := os.Open("/proc/spl/kstat/zfs/arcstats")

View File

@@ -38,7 +38,7 @@ func (am *AlertManager) HandleSystemAlerts(systemRecord *core.Record, data *syst
case "Memory":
val = data.Info.MemPct
case "Bandwidth":
val = data.Info.NetworkSent + data.Info.NetworkRecv
val = data.Info.Bandwidth
unit = " MB/s"
case "Disk":
maxUsedPct := data.Info.DiskPct

View File

@@ -8,47 +8,39 @@ import (
"github.com/henrygd/beszel/internal/entities/container"
)
type NetworkInterfaceStats struct {
NetworkSent float64 `json:"ns"`
NetworkRecv float64 `json:"nr"`
MaxNetworkSent float64 `json:"nsm,omitempty"`
MaxNetworkRecv float64 `json:"nrm,omitempty"`
TotalBytesSent uint64 `json:"tbs,omitempty"` // Total bytes sent since boot
TotalBytesRecv uint64 `json:"tbr,omitempty"` // Total bytes received since boot
}
type Stats struct {
Cpu float64 `json:"cpu" cbor:"0,keyasint"`
MaxCpu float64 `json:"cpum,omitempty" cbor:"1,keyasint,omitempty"`
Mem float64 `json:"m" cbor:"2,keyasint"`
MemUsed float64 `json:"mu" cbor:"3,keyasint"`
MemPct float64 `json:"mp" cbor:"4,keyasint"`
MemBuffCache float64 `json:"mb" cbor:"5,keyasint"`
MemZfsArc float64 `json:"mz,omitempty" cbor:"6,keyasint,omitempty"` // ZFS ARC memory
Swap float64 `json:"s,omitempty" cbor:"7,keyasint,omitempty"`
SwapUsed float64 `json:"su,omitempty" cbor:"8,keyasint,omitempty"`
DiskTotal float64 `json:"d" cbor:"9,keyasint"`
DiskUsed float64 `json:"du" cbor:"10,keyasint"`
DiskPct float64 `json:"dp" cbor:"11,keyasint"`
DiskReadPs float64 `json:"dr" cbor:"12,keyasint"`
DiskWritePs float64 `json:"dw" cbor:"13,keyasint"`
MaxDiskReadPs float64 `json:"drm,omitempty" cbor:"14,keyasint,omitempty"`
MaxDiskWritePs float64 `json:"dwm,omitempty" cbor:"15,keyasint,omitempty"`
NetworkInterfaces map[string]NetworkInterfaceStats `json:"ni" cbor:"16,omitempty"` // Per-interface network stats
NetworkSent float64 `json:"ns" cbor:"17,keyasint"` // Total network sent (MB/s)
NetworkRecv float64 `json:"nr" cbor:"18,keyasint"` // Total network recv (MB/s)
MaxNetworkSent float64 `json:"nsm,omitempty" cbor:"19,keyasint,omitempty"`
MaxNetworkRecv float64 `json:"nrm,omitempty" cbor:"20,keyasint,omitempty"`
Temperatures map[string]float64 `json:"t,omitempty" cbor:"21,keyasint,omitempty"`
ExtraFs map[string]*FsStats `json:"efs,omitempty" cbor:"22,keyasint,omitempty"`
GPUData map[string]GPUData `json:"g,omitempty" cbor:"23,keyasint,omitempty"`
LoadAvg1 float64 `json:"l1,omitempty" cbor:"24,keyasint,omitempty"`
LoadAvg5 float64 `json:"l5,omitempty" cbor:"25,keyasint,omitempty"`
LoadAvg15 float64 `json:"l15,omitempty" cbor:"26,keyasint,omitempty"`
LoadAvg [3]float64 `json:"la,omitempty" cbor:"27,keyasint"` // [1min, 5min, 15min]
Battery [2]uint8 `json:"bat,omitzero" cbor:"28,keyasint,omitzero"` // [percent, charge state]
MaxMem float64 `json:"mm,omitempty" cbor:"29,keyasint,omitempty"`
Nets map[string]float64 `json:"nets,omitempty" cbor:"30,keyasint,omitempty"` // Network connection statistics
Cpu float64 `json:"cpu" cbor:"0,keyasint"`
MaxCpu float64 `json:"cpum,omitempty" cbor:"1,keyasint,omitempty"`
Mem float64 `json:"m" cbor:"2,keyasint"`
MemUsed float64 `json:"mu" cbor:"3,keyasint"`
MemPct float64 `json:"mp" cbor:"4,keyasint"`
MemBuffCache float64 `json:"mb" cbor:"5,keyasint"`
MemZfsArc float64 `json:"mz,omitempty" cbor:"6,keyasint,omitempty"` // ZFS ARC memory
Swap float64 `json:"s,omitempty" cbor:"7,keyasint,omitempty"`
SwapUsed float64 `json:"su,omitempty" cbor:"8,keyasint,omitempty"`
DiskTotal float64 `json:"d" cbor:"9,keyasint"`
DiskUsed float64 `json:"du" cbor:"10,keyasint"`
DiskPct float64 `json:"dp" cbor:"11,keyasint"`
DiskReadPs float64 `json:"dr" cbor:"12,keyasint"`
DiskWritePs float64 `json:"dw" cbor:"13,keyasint"`
MaxDiskReadPs float64 `json:"drm,omitempty" cbor:"14,keyasint,omitempty"`
MaxDiskWritePs float64 `json:"dwm,omitempty" cbor:"15,keyasint,omitempty"`
NetworkSent float64 `json:"ns" cbor:"16,keyasint"`
NetworkRecv float64 `json:"nr" cbor:"17,keyasint"`
MaxNetworkSent float64 `json:"nsm,omitempty" cbor:"18,keyasint,omitempty"`
MaxNetworkRecv float64 `json:"nrm,omitempty" cbor:"19,keyasint,omitempty"`
Temperatures map[string]float64 `json:"t,omitempty" cbor:"20,keyasint,omitempty"`
ExtraFs map[string]*FsStats `json:"efs,omitempty" cbor:"21,keyasint,omitempty"`
GPUData map[string]GPUData `json:"g,omitempty" cbor:"22,keyasint,omitempty"`
LoadAvg1 float64 `json:"l1,omitempty" cbor:"23,keyasint,omitempty"`
LoadAvg5 float64 `json:"l5,omitempty" cbor:"24,keyasint,omitempty"`
LoadAvg15 float64 `json:"l15,omitempty" cbor:"25,keyasint,omitempty"`
Bandwidth [2]uint64 `json:"b,omitzero" cbor:"26,keyasint,omitzero"` // [sent bytes, recv bytes]
MaxBandwidth [2]uint64 `json:"bm,omitzero" cbor:"27,keyasint,omitzero"` // [sent bytes, recv bytes]
// TODO: remove other load fields in future release in favor of load avg array
LoadAvg [3]float64 `json:"la,omitempty" cbor:"28,keyasint"`
Battery [2]uint8 `json:"bat,omitzero" cbor:"29,keyasint,omitzero"` // [percent, charge state, current]
MaxMem float64 `json:"mm,omitempty" cbor:"30,keyasint,omitempty"`
}
type GPUData struct {
@@ -76,12 +68,10 @@ type FsStats struct {
}
type NetIoStats struct {
BytesRecv uint64
BytesSent uint64
PacketsSent uint64
PacketsRecv uint64
Time time.Time
Name string
BytesRecv uint64
BytesSent uint64
Time time.Time
Name string
}
type Os = uint8
@@ -94,26 +84,27 @@ const (
)
type Info struct {
Hostname string `json:"h" cbor:"0,keyasint"`
KernelVersion string `json:"k,omitempty" cbor:"1,keyasint,omitempty"`
Cores int `json:"c" cbor:"2,keyasint"`
Threads int `json:"t,omitempty" cbor:"3,keyasint,omitempty"`
CpuModel string `json:"m" cbor:"4,keyasint"`
Uptime uint64 `json:"u" cbor:"5,keyasint"`
Cpu float64 `json:"cpu" cbor:"6,keyasint"`
MemPct float64 `json:"mp" cbor:"7,keyasint"`
DiskPct float64 `json:"dp" cbor:"8,keyasint"`
NetworkSent float64 `json:"ns" cbor:"9,keyasint"` // Per-interface total (MB/s)
NetworkRecv float64 `json:"nr" cbor:"10,keyasint"` // Per-interface total (MB/s)
AgentVersion string `json:"v" cbor:"11,keyasint"`
Podman bool `json:"p,omitempty" cbor:"12,keyasint,omitempty"`
GpuPct float64 `json:"g,omitempty" cbor:"13,keyasint,omitempty"`
DashboardTemp float64 `json:"dt,omitempty" cbor:"14,keyasint,omitempty"`
Os Os `json:"os" cbor:"15,keyasint"`
LoadAvg1 float64 `json:"l1,omitempty" cbor:"16,keyasint,omitempty"`
LoadAvg5 float64 `json:"l5,omitempty" cbor:"17,keyasint,omitempty"`
LoadAvg15 float64 `json:"l15,omitempty" cbor:"18,keyasint,omitempty"`
LoadAvg [3]float64 `json:"la,omitempty" cbor:"19,keyasint"` // [1min, 5min, 15min]
Hostname string `json:"h" cbor:"0,keyasint"`
KernelVersion string `json:"k,omitempty" cbor:"1,keyasint,omitempty"`
Cores int `json:"c" cbor:"2,keyasint"`
Threads int `json:"t,omitempty" cbor:"3,keyasint,omitempty"`
CpuModel string `json:"m" cbor:"4,keyasint"`
Uptime uint64 `json:"u" cbor:"5,keyasint"`
Cpu float64 `json:"cpu" cbor:"6,keyasint"`
MemPct float64 `json:"mp" cbor:"7,keyasint"`
DiskPct float64 `json:"dp" cbor:"8,keyasint"`
Bandwidth float64 `json:"b" cbor:"9,keyasint"`
AgentVersion string `json:"v" cbor:"10,keyasint"`
Podman bool `json:"p,omitempty" cbor:"11,keyasint,omitempty"`
GpuPct float64 `json:"g,omitempty" cbor:"12,keyasint,omitempty"`
DashboardTemp float64 `json:"dt,omitempty" cbor:"13,keyasint,omitempty"`
Os Os `json:"os" cbor:"14,keyasint"`
LoadAvg1 float64 `json:"l1,omitempty" cbor:"15,keyasint,omitempty"`
LoadAvg5 float64 `json:"l5,omitempty" cbor:"16,keyasint,omitempty"`
LoadAvg15 float64 `json:"l15,omitempty" cbor:"17,keyasint,omitempty"`
BandwidthBytes uint64 `json:"bb" cbor:"18,keyasint"`
// TODO: remove load fields in future release in favor of load avg array
LoadAvg [3]float64 `json:"la,omitempty" cbor:"19,keyasint"`
}
// Final data structure to return to the hub

View File

@@ -175,31 +175,35 @@ func (h *Hub) registerCronJobs(_ *core.ServeEvent) error {
// custom middlewares
func (h *Hub) registerMiddlewares(se *core.ServeEvent) {
// authorizes request with user matching the provided email
authorizeRequestWithEmail := func(e *core.RequestEvent, email string) (err error) {
if e.Auth != nil || email == "" {
return e.Next()
}
isAuthRefresh := e.Request.URL.Path == "/api/collections/users/auth-refresh" && e.Request.Method == http.MethodPost
e.Auth, err = e.App.FindFirstRecordByData("users", "email", email)
if err != nil || !isAuthRefresh {
return e.Next()
}
// auth refresh endpoint, make sure token is set in header
token, _ := e.Auth.NewAuthToken()
e.Request.Header.Set("Authorization", token)
return e.Next()
}
// authenticate with trusted header
if autoLogin, _ := GetEnv("AUTO_LOGIN"); autoLogin != "" {
se.Router.BindFunc(func(e *core.RequestEvent) error {
return authorizeRequestWithEmail(e, autoLogin)
})
}
// authenticate with trusted header
if trustedHeader, _ := GetEnv("TRUSTED_AUTH_HEADER"); trustedHeader != "" {
se.Router.BindFunc(func(e *core.RequestEvent) error {
return authorizeRequestWithEmail(e, e.Request.Header.Get(trustedHeader))
if e.Auth != nil {
return e.Next()
}
trustedEmail := e.Request.Header.Get(trustedHeader)
if trustedEmail == "" {
return e.Next()
}
isAuthRefresh := e.Request.URL.Path == "/api/collections/users/auth-refresh" && e.Request.Method == http.MethodPost
if !isAuthRefresh {
authRecord, err := e.App.FindAuthRecordByEmail("users", trustedEmail)
if err == nil {
e.Auth = authRecord
}
return e.Next()
}
// if auth refresh endpoint, find user record directly and generate token
user, err := e.App.FindFirstRecordByData("users", "email", trustedEmail)
if err != nil {
return e.Next()
}
e.Auth = user
// need to set the authorization header for the client sdk to pick up the token
if token, err := user.NewAuthToken(); err == nil {
e.Request.Header.Set("Authorization", token)
}
return e.Next()
})
}
}

View File

@@ -712,60 +712,6 @@ func TestCreateUserEndpointAvailability(t *testing.T) {
})
}
func TestAutoLoginMiddleware(t *testing.T) {
var hubs []*beszelTests.TestHub
defer func() {
defer os.Unsetenv("AUTO_LOGIN")
for _, hub := range hubs {
hub.Cleanup()
}
}()
os.Setenv("AUTO_LOGIN", "user@test.com")
testAppFactory := func(t testing.TB) *pbTests.TestApp {
hub, _ := beszelTests.NewTestHub(t.TempDir())
hubs = append(hubs, hub)
hub.StartHub()
return hub.TestApp
}
scenarios := []beszelTests.ApiScenario{
{
Name: "GET /getkey - without auto login should fail",
Method: http.MethodGet,
URL: "/api/beszel/getkey",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /getkey - with auto login should fail if no matching user",
Method: http.MethodGet,
URL: "/api/beszel/getkey",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /getkey - with auto login should succeed",
Method: http.MethodGet,
URL: "/api/beszel/getkey",
ExpectedStatus: 200,
ExpectedContent: []string{"\"key\":", "\"v\":"},
TestAppFactory: testAppFactory,
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
beszelTests.CreateUser(app, "user@test.com", "password123")
},
},
}
for _, scenario := range scenarios {
scenario.Test(t)
}
}
func TestTrustedHeaderMiddleware(t *testing.T) {
var hubs []*beszelTests.TestHub

View File

@@ -206,51 +206,15 @@ func (rm *RecordManager) AverageSystemStats(db dbx.Builder, records RecordIds) *
sum.DiskPct += stats.DiskPct
sum.DiskReadPs += stats.DiskReadPs
sum.DiskWritePs += stats.DiskWritePs
sum.LoadAvg1 += stats.LoadAvg1
sum.LoadAvg5 += stats.LoadAvg5
sum.LoadAvg15 += stats.LoadAvg15
sum.NetworkSent += stats.NetworkSent
sum.NetworkRecv += stats.NetworkRecv
sum.LoadAvg[0] += stats.LoadAvg[0]
sum.LoadAvg[1] += stats.LoadAvg[1]
sum.LoadAvg[2] += stats.LoadAvg[2]
sum.Bandwidth[0] += stats.Bandwidth[0]
sum.Bandwidth[1] += stats.Bandwidth[1]
batterySum += int(stats.Battery[0])
sum.Battery[1] = stats.Battery[1]
if stats.NetworkInterfaces != nil {
if sum.NetworkInterfaces == nil {
sum.NetworkInterfaces = make(map[string]system.NetworkInterfaceStats, len(stats.NetworkInterfaces))
}
for key, value := range stats.NetworkInterfaces {
if _, ok := sum.NetworkInterfaces[key]; !ok {
sum.NetworkInterfaces[key] = system.NetworkInterfaceStats{}
}
ni := sum.NetworkInterfaces[key]
ni.NetworkSent += value.NetworkSent
ni.NetworkRecv += value.NetworkRecv
ni.MaxNetworkSent += value.MaxNetworkSent
ni.MaxNetworkRecv += value.MaxNetworkRecv
// For cumulative totals, use the maximum value (most recent)
if value.TotalBytesSent > ni.TotalBytesSent {
ni.TotalBytesSent = value.TotalBytesSent
}
if value.TotalBytesRecv > ni.TotalBytesRecv {
ni.TotalBytesRecv = value.TotalBytesRecv
}
sum.NetworkInterfaces[key] = ni
}
}
// Handle network connection stats - use the latest values (most recent sample)
if stats.Nets != nil {
if sum.Nets == nil {
sum.Nets = make(map[string]float64)
}
for key, value := range stats.Nets {
sum.Nets[key] = value
}
}
// Set peak values
sum.MaxCpu = max(sum.MaxCpu, stats.MaxCpu, stats.Cpu)
sum.MaxMem = max(sum.MaxMem, stats.MaxMem, stats.MemUsed)
@@ -258,6 +222,8 @@ func (rm *RecordManager) AverageSystemStats(db dbx.Builder, records RecordIds) *
sum.MaxNetworkRecv = max(sum.MaxNetworkRecv, stats.MaxNetworkRecv, stats.NetworkRecv)
sum.MaxDiskReadPs = max(sum.MaxDiskReadPs, stats.MaxDiskReadPs, stats.DiskReadPs)
sum.MaxDiskWritePs = max(sum.MaxDiskWritePs, stats.MaxDiskWritePs, stats.DiskWritePs)
sum.MaxBandwidth[0] = max(sum.MaxBandwidth[0], stats.MaxBandwidth[0], stats.Bandwidth[0])
sum.MaxBandwidth[1] = max(sum.MaxBandwidth[1], stats.MaxBandwidth[1], stats.Bandwidth[1])
// Accumulate temperatures
if stats.Temperatures != nil {
@@ -325,26 +291,14 @@ func (rm *RecordManager) AverageSystemStats(db dbx.Builder, records RecordIds) *
sum.DiskPct = twoDecimals(sum.DiskPct / count)
sum.DiskReadPs = twoDecimals(sum.DiskReadPs / count)
sum.DiskWritePs = twoDecimals(sum.DiskWritePs / count)
sum.LoadAvg1 = twoDecimals(sum.LoadAvg1 / count)
sum.LoadAvg5 = twoDecimals(sum.LoadAvg5 / count)
sum.LoadAvg15 = twoDecimals(sum.LoadAvg15 / count)
sum.NetworkSent = twoDecimals(sum.NetworkSent / count)
sum.NetworkRecv = twoDecimals(sum.NetworkRecv / count)
sum.LoadAvg[0] = twoDecimals(sum.LoadAvg[0] / count)
sum.LoadAvg[1] = twoDecimals(sum.LoadAvg[1] / count)
sum.LoadAvg[2] = twoDecimals(sum.LoadAvg[2] / count)
sum.Bandwidth[0] = sum.Bandwidth[0] / uint64(count)
sum.Bandwidth[1] = sum.Bandwidth[1] / uint64(count)
sum.Battery[0] = uint8(batterySum / int(count))
if sum.NetworkInterfaces != nil {
for key := range sum.NetworkInterfaces {
ni := sum.NetworkInterfaces[key]
ni.NetworkSent = twoDecimals(ni.NetworkSent / count)
ni.NetworkRecv = twoDecimals(ni.NetworkRecv / count)
ni.MaxNetworkSent = twoDecimals(max(ni.MaxNetworkSent, ni.NetworkSent))
ni.MaxNetworkRecv = twoDecimals(max(ni.MaxNetworkRecv, ni.NetworkRecv))
sum.NetworkInterfaces[key] = ni
}
}
// Average temperatures
if sum.Temperatures != nil && tempCount > 0 {
for key := range sum.Temperatures {
@@ -409,15 +363,19 @@ func (rm *RecordManager) AverageContainerStats(db dbx.Builder, records RecordIds
}
sums[stat.Name].Cpu += stat.Cpu
sums[stat.Name].Mem += stat.Mem
sums[stat.Name].NetworkSent += stat.NetworkSent
sums[stat.Name].NetworkRecv += stat.NetworkRecv
}
}
result := make([]container.Stats, 0, len(sums))
for _, value := range sums {
result = append(result, container.Stats{
Name: value.Name,
Cpu: twoDecimals(value.Cpu / count),
Mem: twoDecimals(value.Mem / count),
Name: value.Name,
Cpu: twoDecimals(value.Cpu / count),
Mem: twoDecimals(value.Mem / count),
NetworkSent: twoDecimals(value.NetworkSent / count),
NetworkRecv: twoDecimals(value.NetworkRecv / count),
})
}
return result

View File

@@ -17,10 +17,7 @@
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"correctness": {
"useUniqueElementIds": "off"
}
"recommended": true
}
},
"javascript": {
@@ -38,4 +35,4 @@
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -22,7 +22,7 @@ import { memo, useEffect, useRef, useState } from "react"
import { $router, basePath, Link, navigate } from "./router"
import { SystemRecord } from "@/types"
import { SystemStatus } from "@/lib/enums"
import { AppleIcon, DockerIcon, FreeBsdIcon, TuxIcon, WindowsIcon } from "./ui/icons"
import { AppleIcon, DockerIcon, TuxIcon, WindowsIcon } from "./ui/icons"
import { InputCopy } from "./ui/input-copy"
import { getPagePath } from "@nanostores/router"
import {
@@ -253,12 +253,6 @@ export const SystemDialog = ({ setOpen, system }: { setOpen: (open: boolean) =>
copyWindowsCommand(isUnixSocket ? hostValue : port.current?.value, publicKey, token),
icons: [WindowsIcon],
},
{
text: t({ message: "FreeBSD command", context: "Button to copy install command" }),
onClick: async () =>
copyLinuxCommand(isUnixSocket ? hostValue : port.current?.value, publicKey, token),
icons: [FreeBsdIcon],
},
{
text: t`Manual setup instructions`,
url: "https://beszel.dev/guide/agent-installation#binary",

View File

@@ -1,124 +0,0 @@
import { memo } from "react"
import { useLingui } from "@lingui/react/macro"
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
ChartLegend,
ChartLegendContent,
xAxis,
} from "@/components/ui/chart"
import { cn, formatShortDate, chartMargin } from "@/lib/utils"
import { ChartData } from "@/types"
import { useYAxisWidth } from "./hooks"
export default memo(function ConnectionChart({ chartData }: { chartData: ChartData }) {
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
const { t } = useLingui()
if (chartData.systemStats.length === 0) {
return null
}
const dataKeys = [
{
name: t`IPv4 Established`,
dataKey: "stats.nets.conn_established",
color: "hsl(220, 70%, 50%)", // Blue
},
{
name: t`IPv4 Listen`,
dataKey: "stats.nets.conn_listen",
color: "hsl(142, 70%, 45%)", // Green
},
{
name: t`IPv4 Time Wait`,
dataKey: "stats.nets.conn_timewait",
color: "hsl(48, 96%, 53%)", // Yellow
},
{
name: t`IPv4 Close Wait`,
dataKey: "stats.nets.conn_closewait",
color: "hsl(271, 81%, 56%)", // Purple
},
{
name: t`IPv4 Syn Recv`,
dataKey: "stats.nets.conn_synrecv",
color: "hsl(9, 78%, 56%)", // Red
},
{
name: t`IPv6 Established`,
dataKey: "stats.nets.conn6_established",
color: "hsl(220, 70%, 65%)", // Light Blue
},
{
name: t`IPv6 Listen`,
dataKey: "stats.nets.conn6_listen",
color: "hsl(142, 70%, 60%)", // Light Green
},
{
name: t`IPv6 Time Wait`,
dataKey: "stats.nets.conn6_timewait",
color: "hsl(48, 96%, 68%)", // Light Yellow
},
{
name: t`IPv6 Close Wait`,
dataKey: "stats.nets.conn6_closewait",
color: "hsl(271, 81%, 71%)", // Light Purple
},
{
name: t`IPv6 Syn Recv`,
dataKey: "stats.nets.conn6_synrecv",
color: "hsl(9, 78%, 71%)", // Light Red
},
]
return (
<div>
<ChartContainer
className={cn("h-full w-full absolute aspect-auto bg-card opacity-0 transition-opacity", {
"opacity-100": yAxisWidth,
})}
>
<AreaChart accessibilityLayer data={chartData.systemStats} margin={chartMargin}>
<CartesianGrid vertical={false} />
<YAxis
direction="ltr"
orientation={chartData.orientation}
className="tracking-tighter"
width={yAxisWidth}
tickFormatter={(value) => updateYAxisWidth(value.toString())}
tickLine={false}
axisLine={false}
/>
{xAxis(chartData)}
<ChartTooltip
animationEasing="ease-out"
animationDuration={150}
content={
<ChartTooltipContent
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
contentFormatter={({ value }) => value.toString()}
/>
}
/>
<ChartLegend content={<ChartLegendContent />} />
{dataKeys.map((key, i) => (
<Area
key={i}
dataKey={key.dataKey}
name={key.name}
type="monotoneX"
fill={key.color}
fillOpacity={0.3}
stroke={key.color}
strokeOpacity={1}
isAnimationActive={false}
/>
))}
</AreaChart>
</ChartContainer>
</div>
)
})

View File

@@ -1,164 +0,0 @@
import { memo, useMemo } from "react"
import { useLingui } from "@lingui/react/macro"
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
xAxis,
ChartLegend,
ChartLegendContent,
} from "@/components/ui/chart"
import { cn, formatShortDate, chartMargin, formatBytes, toFixedFloat, decimalString } from "@/lib/utils"
import { ChartData } from "@/types"
import { useStore } from "@nanostores/react"
import { $networkInterfaceFilter, $userSettings } from "@/lib/stores"
import { Unit } from "@/lib/enums"
import { useYAxisWidth } from "./hooks"
const getNestedValue = (path: string, max = false, data: any): number | null => {
// path format is like "eth0.ns" or "eth0.nr"
// need to access data.stats.ni[interface][property]
const parts = path.split(".")
if (parts.length !== 2) return null
const [interfaceName, property] = parts
const propertyKey = property + (max ? "m" : "")
return data?.stats?.ni?.[interfaceName]?.[propertyKey] ?? null
}
export default memo(function NetworkInterfaceChart({
chartData,
maxToggled = false,
max,
}: {
chartData: ChartData
maxToggled?: boolean
max?: number
}) {
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
const { i18n } = useLingui()
const networkInterfaceFilter = useStore($networkInterfaceFilter)
const userSettings = useStore($userSettings)
const { chartTime } = chartData
const showMax = chartTime !== "1h" && maxToggled
// Get network interface names from the latest stats
const networkInterfaces = useMemo(() => {
if (chartData.systemStats.length === 0) return []
const latestStats = chartData.systemStats[chartData.systemStats.length - 1]
const allInterfaces = Object.keys(latestStats.stats.ni || {})
// Filter interfaces based on filter value
if (networkInterfaceFilter) {
return allInterfaces.filter((iface) => iface.toLowerCase().includes(networkInterfaceFilter.toLowerCase()))
}
return allInterfaces
}, [chartData.systemStats, networkInterfaceFilter])
const dataKeys = useMemo(() => {
// Generate colors for each interface - each interface gets a unique hue
// and sent/received use different shades of that hue
const interfaceColors = networkInterfaces.map((iface, index) => {
const hue = ((index * 360) / Math.max(networkInterfaces.length, 1)) % 360
return {
interface: iface,
sentColor: `hsl(${hue}, 70%, 45%)`, // Darker shade for sent
receivedColor: `hsl(${hue}, 70%, 65%)`, // Lighter shade for received
}
})
return interfaceColors.flatMap(({ interface: iface, sentColor, receivedColor }) => [
{
name: `${iface} Sent`,
dataKey: `${iface}.ns`,
color: sentColor,
type: "sent" as const,
interface: iface,
},
{
name: `${iface} Received`,
dataKey: `${iface}.nr`,
color: receivedColor,
type: "received" as const,
interface: iface,
},
])
}, [networkInterfaces, i18n.locale])
const colors = dataKeys.map((key) => key.name)
return (
<div>
<ChartContainer
className={cn("h-full w-full absolute aspect-auto bg-card opacity-0 transition-opacity", {
"opacity-100": yAxisWidth,
})}
>
<AreaChart accessibilityLayer data={chartData.systemStats} margin={chartMargin}>
<CartesianGrid vertical={false} />
<YAxis
direction="ltr"
orientation={chartData.orientation}
className="tracking-tighter"
width={yAxisWidth}
tickFormatter={(value) => {
const { value: formattedValue, unit } = formatBytes(value, true, userSettings.unitNet ?? Unit.Bits, true)
const rounded = toFixedFloat(formattedValue, formattedValue >= 10 ? 1 : 2)
return updateYAxisWidth(`${rounded} ${unit}`)
}}
tickLine={false}
axisLine={false}
/>
{xAxis(chartData)}
<ChartTooltip
animationEasing="ease-out"
animationDuration={150}
content={
<ChartTooltipContent
labelFormatter={(_: any, data: any) => formatShortDate(data[0].payload.created)}
contentFormatter={({ value }: any) => {
const { value: formattedValue, unit } = formatBytes(
value,
true,
userSettings.unitNet ?? Unit.Bits,
true
)
return (
<span className="flex">
{decimalString(formattedValue, formattedValue >= 10 ? 1 : 2)} {unit}
</span>
)
}}
/>
}
/>
{dataKeys.map((key, i) => {
const filtered =
networkInterfaceFilter && !key.interface.toLowerCase().includes(networkInterfaceFilter.toLowerCase())
let fillOpacity = filtered ? 0.05 : 0.4
let strokeOpacity = filtered ? 0.1 : 1
return (
<Area
key={i}
dataKey={getNestedValue.bind(null, key.dataKey, showMax)}
name={key.name}
type="monotoneX"
fill={key.color}
fillOpacity={fillOpacity}
stroke={key.color}
strokeOpacity={strokeOpacity}
activeDot={{ opacity: filtered ? 0 : 1 }}
isAnimationActive={false}
/>
)
})}
{colors.length < 12 && <ChartLegend content={<ChartLegendContent />} />}
</AreaChart>
</ChartContainer>
</div>
)
})

View File

@@ -1,159 +0,0 @@
import { memo, useMemo } from "react"
import { useLingui } from "@lingui/react/macro"
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
ChartLegend,
ChartLegendContent,
xAxis,
} from "@/components/ui/chart"
import { cn, formatShortDate, chartMargin, formatBytes, toFixedFloat, decimalString } from "@/lib/utils"
import { ChartData } from "@/types"
import { useStore } from "@nanostores/react"
import { $userSettings } from "@/lib/stores"
import { Unit } from "@/lib/enums"
import { useYAxisWidth } from "./hooks"
const getPerInterfaceBandwidth = (data: any): Record<string, { sent: number; recv: number }> | null => {
const networkInterfaces = data?.stats?.ni
if (!networkInterfaces) {
return null
}
const interfaceData: Record<string, { sent: number; recv: number }> = {}
let hasData = false
Object.entries(networkInterfaces).forEach(([name, iface]: [string, any]) => {
if (iface.tbs || iface.tbr) {
interfaceData[name] = {
sent: iface.tbs || 0,
recv: iface.tbr || 0,
}
hasData = true
}
})
return hasData ? interfaceData : null
}
export default memo(function TotalBandwidthChart({ chartData }: { chartData: ChartData }) {
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
const { i18n } = useLingui()
const userSettings = useStore($userSettings)
// Transform data to include per-interface bandwidth
const { transformedData, interfaceNames } = useMemo(() => {
const allInterfaces = new Set<string>()
// First pass: collect all interface names
chartData.systemStats.forEach((dataPoint) => {
const interfaceData = getPerInterfaceBandwidth(dataPoint)
if (interfaceData) {
Object.keys(interfaceData).forEach((name) => allInterfaces.add(name))
}
})
const interfaceNames = Array.from(allInterfaces).sort()
// Second pass: transform data with per-interface values
const transformedData = chartData.systemStats.map((dataPoint) => {
const interfaceData = getPerInterfaceBandwidth(dataPoint)
const result: any = { ...dataPoint }
interfaceNames.forEach((interfaceName) => {
const data = interfaceData?.[interfaceName]
result[`${interfaceName}_sent`] = data?.sent || 0
result[`${interfaceName}_recv`] = data?.recv || 0
})
return result
})
return { transformedData, interfaceNames }
}, [chartData.systemStats])
// Generate dynamic data keys for each interface using same color scheme as NetworkInterfaceChart
const dataKeys = useMemo(() => {
const keys: Array<{ name: string; dataKey: string; color: string }> = []
interfaceNames.forEach((interfaceName, index) => {
// Use the same color calculation as NetworkInterfaceChart
const hue = ((index * 360) / Math.max(interfaceNames.length, 1)) % 360
keys.push({
name: `${interfaceName} Sent`,
dataKey: `${interfaceName}_sent`,
color: `hsl(${hue}, 70%, 45%)`, // Darker shade for sent (same as NetworkInterfaceChart)
})
keys.push({
name: `${interfaceName} Received`,
dataKey: `${interfaceName}_recv`,
color: `hsl(${hue}, 70%, 65%)`, // Lighter shade for received (same as NetworkInterfaceChart)
})
})
return keys
}, [interfaceNames])
return (
<div>
<ChartContainer
className={cn("h-full w-full absolute aspect-auto bg-card opacity-0 transition-opacity", {
"opacity-100": yAxisWidth,
})}
>
<AreaChart accessibilityLayer data={transformedData} margin={chartMargin}>
<CartesianGrid vertical={false} />
<YAxis
direction="ltr"
orientation={chartData.orientation}
className="tracking-tighter"
width={yAxisWidth}
tickFormatter={(value) => {
const { value: formattedValue, unit } = formatBytes(value, false, userSettings.unitNet ?? Unit.Bytes)
const rounded = toFixedFloat(formattedValue, formattedValue >= 10 ? 1 : 2)
return updateYAxisWidth(`${rounded} ${unit}`)
}}
tickLine={false}
axisLine={false}
/>
{xAxis(chartData)}
<ChartTooltip
animationEasing="ease-out"
animationDuration={150}
content={
<ChartTooltipContent
labelFormatter={(_: any, data: any) => formatShortDate(data[0].payload.created)}
contentFormatter={({ value }: any) => {
const { value: formattedValue, unit } = formatBytes(value, false, userSettings.unitNet ?? Unit.Bytes)
return (
<span className="flex">
{decimalString(formattedValue, formattedValue >= 10 ? 1 : 2)} {unit}
</span>
)
}}
/>
}
/>
<ChartLegend content={<ChartLegendContent />} />
{dataKeys.map((key, i) => (
<Area
key={i}
dataKey={key.dataKey}
name={key.name}
type="monotoneX"
fill={key.color}
fillOpacity={0.3}
stroke={key.color}
strokeOpacity={1}
isAnimationActive={false}
/>
))}
</AreaChart>
</ChartContainer>
</div>
)
})

View File

@@ -9,7 +9,6 @@ import {
RotateCwIcon,
ServerIcon,
Trash2Icon,
ExternalLinkIcon,
} from "lucide-react"
import { memo, useEffect, useMemo, useState } from "react"
import {
@@ -29,7 +28,7 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { AppleIcon, DockerIcon, FreeBsdIcon, TuxIcon, WindowsIcon } from "@/components/ui/icons"
import { AppleIcon, DockerIcon, TuxIcon, WindowsIcon } from "@/components/ui/icons"
import { Separator } from "@/components/ui/separator"
import { Switch } from "@/components/ui/switch"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
@@ -151,7 +150,6 @@ const SectionUniversalToken = memo(() => {
setIsLoading(false)
}
// biome-ignore lint/correctness/useExhaustiveDependencies: only on mount
useEffect(() => {
updateToken()
}, [])
@@ -223,16 +221,6 @@ const ActionsButtonUniversalToken = memo(({ token, checked }: { token: string; c
onClick: () => copyWindowsCommand(port, publicKey, token),
icons: [WindowsIcon],
},
{
text: t({ message: "FreeBSD command", context: "Button to copy install command" }),
onClick: () => copyLinuxCommand(port, publicKey, token),
icons: [FreeBsdIcon],
},
{
text: t`Manual setup instructions`,
url: "https://beszel.dev/guide/agent-installation#binary",
icons: [ExternalLinkIcon],
},
]
return (
<div className="flex items-center gap-2">
@@ -303,8 +291,8 @@ const SectionTable = memo(({ fingerprints = [] }: { fingerprints: FingerprintRec
</tr>
</TableHeader>
<TableBody className="whitespace-pre">
{fingerprints.map((fingerprint) => (
<TableRow key={fingerprint.id}>
{fingerprints.map((fingerprint, i) => (
<TableRow key={i}>
<TableCell className="font-medium ps-5 py-2 max-w-60 truncate">
{fingerprint.expand.system.name}
</TableCell>
@@ -329,10 +317,10 @@ async function updateFingerprint(fingerprint: FingerprintRecord, rotateToken = f
fingerprint: "",
token: rotateToken ? generateToken() : fingerprint.token,
})
} catch (error: unknown) {
} catch (error: any) {
toast({
title: t`Error`,
description: (error as Error).message,
description: error.message,
})
}
}

View File

@@ -24,7 +24,6 @@ import {
$containerFilter,
$direction,
$maxValues,
$networkInterfaceFilter,
$systems,
$temperatureFilter,
$userSettings,
@@ -53,9 +52,6 @@ import { Input } from "../ui/input"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select"
import { Separator } from "../ui/separator"
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip"
import ConnectionChart from "../charts/connection-chart"
import NetworkInterfaceChart from "../charts/network-interface-chart"
import TotalBandwidthChart from "../charts/total-bandwidth-chart"
type ChartTimeData = {
time: number
@@ -151,7 +147,6 @@ export default memo(function SystemDetail({ name }: { name: string }) {
const netCardRef = useRef<HTMLDivElement>(null)
const persistChartTime = useRef(false)
const [containerFilterBar, setContainerFilterBar] = useState(null as null | JSX.Element)
const [networkInterfaceFilterBar, setNetworkInterfaceFilterBar] = useState(null as null | JSX.Element)
const [bottomSpacing, setBottomSpacing] = useState(0)
const [chartLoading, setChartLoading] = useState(true)
const isLongerChart = chartTime !== "1h"
@@ -168,9 +163,7 @@ export default memo(function SystemDetail({ name }: { name: string }) {
setSystemStats([])
setContainerData([])
setContainerFilterBar(null)
setNetworkInterfaceFilterBar(null)
$containerFilter.set("")
$networkInterfaceFilter.set("")
}
}, [name])
@@ -267,19 +260,6 @@ export default memo(function SystemDetail({ name }: { name: string }) {
})
}, [system, chartTime])
// Set up network interface filter bar
useEffect(() => {
if (systemStats.length > 0) {
const latestStats = systemStats[systemStats.length - 1]
const networkInterfaces = Object.keys(latestStats.stats.ns || {})
if (networkInterfaces.length > 0) {
!networkInterfaceFilterBar && setNetworkInterfaceFilterBar(<FilterBar store={$networkInterfaceFilter} />)
} else if (networkInterfaceFilterBar) {
setNetworkInterfaceFilterBar(null)
}
}
}, [systemStats, networkInterfaceFilterBar])
// values for system info bar
const systemInfo = useMemo(() => {
if (!system.info) {
@@ -409,7 +389,6 @@ export default memo(function SystemDetail({ name }: { name: string }) {
const lastGpuVals = Object.values(systemStats.at(-1)?.stats.g ?? {})
const hasGpuData = lastGpuVals.length > 0
const hasGpuPowerData = lastGpuVals.some((gpu) => gpu.p !== undefined)
const latestNetworkStats = systemStats.at(-1)?.stats.ni
let translatedStatus: string = system.status
if (system.status === SystemStatus.Up) {
@@ -576,7 +555,7 @@ export default memo(function SystemDetail({ name }: { name: string }) {
empty={dataEmpty}
grid={grid}
title={t`Disk I/O`}
description={t`Disk read and write throughput`}
description={t`Throughput of root filesystem`}
cornerEl={maxValSelect}
>
<AreaChartDefault
@@ -607,44 +586,51 @@ export default memo(function SystemDetail({ name }: { name: string }) {
/>
</ChartCard>
{/* Network interface charts */}
{Object.keys(latestNetworkStats ?? {}).length > 0 && (
<ChartCard
empty={dataEmpty}
grid={grid}
title={t`Network Interfaces`}
description={t`Network traffic per interface`}
cornerEl={networkInterfaceFilterBar}
>
{/* @ts-ignore */}
<NetworkInterfaceChart chartData={chartData} />
</ChartCard>
)}
{/* Per-Interface Cumulative Bandwidth chart */}
{Object.keys(latestNetworkStats ?? {}).length > 0 && (
<ChartCard
empty={dataEmpty}
grid={grid}
title={t`Cumulative Bandwidth`}
description={t`Total bytes sent and received per network interface since boot`}
>
{/* @ts-ignore */}
<TotalBandwidthChart chartData={chartData} />
</ChartCard>
)}
{/* TCP Connection States chart */}
{systemStats.at(-1)?.stats.nets && Object.keys(systemStats.at(-1)?.stats.nets ?? {}).length > 0 && (
<ChartCard
empty={dataEmpty}
grid={grid}
title={t`TCP Connection States`}
description={t`TCP connection states for IPv4 and IPv6`}
>
<ConnectionChart chartData={chartData} />
</ChartCard>
)}
<ChartCard
empty={dataEmpty}
grid={grid}
title={t`Bandwidth`}
cornerEl={maxValSelect}
description={t`Network traffic of public interfaces`}
>
<AreaChartDefault
chartData={chartData}
maxToggled={maxValues}
dataPoints={[
{
label: t`Sent`,
// use bytes if available, otherwise multiply old MB (can remove in future)
dataKey(data) {
if (showMax) {
return data?.stats?.bm?.[0] ?? (data?.stats?.nsm ?? 0) * 1024 * 1024
}
return data?.stats?.b?.[0] ?? data?.stats?.ns * 1024 * 1024
},
color: 5,
opacity: 0.2,
},
{
label: t`Received`,
dataKey(data) {
if (showMax) {
return data?.stats?.bm?.[1] ?? (data?.stats?.nrm ?? 0) * 1024 * 1024
}
return data?.stats?.b?.[1] ?? data?.stats?.nr * 1024 * 1024
},
color: 2,
opacity: 0.2,
},
]}
tickFormatter={(val) => {
const { value, unit } = formatBytes(val, true, userSettings.unitNet, false)
return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}`
}}
contentFormatter={(data) => {
const { value, unit } = formatBytes(data.value, true, userSettings.unitNet, false)
return `${decimalString(value, value >= 100 ? 1 : 2)} ${unit}`
}}
/>
</ChartCard>
{containerFilterBar && containerData.length > 0 && (
<div

View File

@@ -216,32 +216,22 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
},
},
{
accessorFn: (row) => (row.info.ns || 0) + (row.info.nr || 0),
accessorFn: ({ info }) => info.bb || (info.b || 0) * 1024 * 1024,
id: "net",
name: () => t`Net`,
size: 0,
Icon: EthernetIcon,
header: sortableHeader,
sortDescFirst: true,
sortingFn: (rowA, rowB) => {
const a = (rowA.original.info.ns || 0) + (rowA.original.info.nr || 0)
const b = (rowB.original.info.ns || 0) + (rowB.original.info.nr || 0)
return a - b
},
cell(info) {
const system = info.row.original
const sent = system.info.ns || 0
const received = system.info.nr || 0
const sys = info.row.original
const userSettings = useStore($userSettings, { keys: ["unitNet"] })
if (system.status === SystemStatus.Paused) {
if (sys.status === SystemStatus.Paused) {
return null
}
const sentFmt = formatBytes(sent, true, userSettings.unitNet, true)
const receivedFmt = formatBytes(received, true, userSettings.unitNet, true)
const { value, unit } = formatBytes(info.getValue() as number, true, userSettings.unitNet, false)
return (
<span className={cn("tabular-nums whitespace-nowrap", { "ps-1": viewMode === "table" })}>
<span className="text-green-600"></span> {Math.round(sentFmt.value)} {sentFmt.unit}{" "}
<span className="text-blue-600"></span> {Math.round(receivedFmt.value)} {receivedFmt.unit}
<span className="tabular-nums whitespace-nowrap">
{decimalString(value, value >= 100 ? 1 : 2)} {unit}
</span>
)
},

View File

@@ -55,9 +55,6 @@ listenKeys($userSettings, ["chartTime"], ({ chartTime }) => $chartTime.set(chart
/** Container chart filter */
export const $containerFilter = atom("")
/** Network interface chart filter */
export const $networkInterfaceFilter = atom("")
/** Temperature chart filter */
export const $temperatureFilter = atom("")

View File

@@ -1,4 +1,3 @@
/** biome-ignore-all lint/suspicious/noAssignInExpressions: it's fine :) */
import type { PreinitializedMapStore } from "nanostores"
import { pb, verifyAuth } from "@/lib/api"
import {
@@ -17,10 +16,9 @@ const COLLECTION = pb.collection<SystemRecord>("systems")
const FIELDS_DEFAULT = "id,name,host,port,info,status"
/** Maximum system name length for display purposes */
const MAX_SYSTEM_NAME_LENGTH = 22
const MAX_SYSTEM_NAME_LENGTH = 20
let initialized = false
// biome-ignore lint/suspicious/noConfusingVoidType: typescript rocks
let unsub: (() => void) | undefined | void
/** Initialize the systems manager and set up listeners */
@@ -106,37 +104,20 @@ async function fetchSystems(): Promise<SystemRecord[]> {
}
}
/** Makes sure the system has valid info object and throws if not */
function validateSystemInfo(system: SystemRecord) {
if (!("cpu" in system.info)) {
throw new Error(`${system.name} has no CPU info`)
}
}
/** Add system to both name and ID stores */
export function add(system: SystemRecord) {
try {
validateSystemInfo(system)
$allSystemsByName.setKey(system.name, system)
$allSystemsById.setKey(system.id, system)
} catch (error) {
console.error(error)
}
$allSystemsByName.setKey(system.name, system)
$allSystemsById.setKey(system.id, system)
}
/** Update system in stores */
export function update(system: SystemRecord) {
try {
validateSystemInfo(system)
// if name changed, make sure old name is removed from the name store
const oldName = $allSystemsById.get()[system.id]?.name
if (oldName !== system.name) {
$allSystemsByName.setKey(oldName, undefined as unknown as SystemRecord)
}
add(system)
} catch (error) {
console.error(error)
// if name changed, make sure old name is removed from the name store
const oldName = $allSystemsById.get()[system.id]?.name
if (oldName !== system.name) {
$allSystemsByName.setKey(oldName, undefined as any)
}
add(system)
}
/** Remove system from stores */
@@ -151,7 +132,7 @@ export function remove(system: SystemRecord) {
/** Remove system from specific store */
function removeFromStore(system: SystemRecord, store: PreinitializedMapStore<Record<string, SystemRecord>>) {
const key = store === $allSystemsByName ? system.name : system.id
store.setKey(key, undefined as unknown as SystemRecord)
store.setKey(key, undefined as any)
}
/** Action functions for subscription */

View File

@@ -283,22 +283,6 @@ export const getHubURL = () => BESZEL?.HUB_URL || window.location.origin
/** Map of system IDs to their corresponding tokens (used to avoid fetching in add-system dialog) */
export const tokenMap = new Map<SystemRecord["id"], FingerprintRecord["token"]>()
/**
* Formats a network speed value (in MB/s) to the most readable unit (B/s, KB/s, MB/s, GB/s, TB/s).
* @param valueMBps The value in MB/s
* @returns A string with the value and the appropriate unit
*/
export function formatSpeed(valueMBps: number): string {
const bitsPerSec = valueMBps * 8_000_000
if (bitsPerSec >= 1_000_000_000) {
return (bitsPerSec / 1_000_000_000).toFixed(2) + ' Gbit/s'
} else if (bitsPerSec >= 1_000_000) {
return (bitsPerSec / 1_000_000).toFixed(2) + ' Mbit/s'
} else {
return (bitsPerSec / 1_000).toFixed(2) + ' kbit/s'
}
}
/** Calculate duration between two dates and format as human-readable string */
export function formatDuration(
createdDate: string | null | undefined,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -8,15 +8,15 @@ msgstr ""
"Language: is\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-08-28 23:21\n"
"PO-Revision-Date: 2025-09-22 23:10\n"
"Last-Translator: \n"
"Language-Team: Icelandic\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: beszel\n"
"X-Crowdin-Project-ID: 733311\n"
"X-Crowdin-Language: is\n"
"X-Crowdin-File: /main/beszel/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 16\n"
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
@@ -112,7 +112,7 @@ msgstr ""
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
msgid "Admin"
msgstr "Admin"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Agent"
@@ -173,6 +173,10 @@ msgstr "Meðal nýting örgjörva yfir allt kerfið"
msgid "Average utilization of {0}"
msgstr "Meðal notkun af {0}"
#: src/components/routes/system.tsx
msgid "Average utilization of GPU engines"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Backups"
@@ -197,7 +201,7 @@ msgstr "Beszel notar <0>Shoutrrr</0> til að tengjast vinsælum tilkynningaþjó
#: src/components/add-system.tsx
msgid "Binary"
msgstr "Binary"
msgstr ""
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx
@@ -363,6 +367,14 @@ msgstr ""
msgid "Critical (%)"
msgstr "Kritískt (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr ""
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
@@ -441,6 +453,10 @@ msgstr ""
msgid "Down ({downSystemsLength})"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr ""
#: src/components/alerts-history-columns.tsx
msgid "Duration"
msgstr ""
@@ -452,6 +468,7 @@ msgstr ""
#: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "Netfang"
@@ -472,6 +489,10 @@ msgstr "Settu netfang til að endursetja lykilorð"
msgid "Enter email address..."
msgstr "Settu inn Netfang..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr ""
#: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
@@ -542,10 +563,16 @@ msgstr ""
msgid "Forgot password?"
msgstr "Gleymt lykilorð?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr ""
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
msgstr "Full"
msgstr ""
#. Context: General settings
#: src/components/routes/settings/general.tsx
@@ -553,6 +580,10 @@ msgstr "Full"
msgid "General"
msgstr "Almennt"
#: src/components/routes/system.tsx
msgid "GPU Engines"
msgstr ""
#: src/components/routes/system.tsx
msgid "GPU Power Draw"
msgstr "Skjákorts rafmagnsnotkun"
@@ -569,7 +600,7 @@ msgstr "Homebrew skipun"
#: src/components/add-system.tsx
msgid "Host / IP"
msgstr "Host / IP"
msgstr ""
#. Context: Battery state
#: src/lib/i18n.ts
@@ -645,6 +676,7 @@ msgid "Manage display and notification preferences."
msgstr ""
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions"
msgstr ""
@@ -673,13 +705,15 @@ msgstr "Nafn"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Net"
msgstr "Net"
msgstr ""
#: src/components/routes/system.tsx
msgid "Network traffic of docker containers"
msgstr "Net traffík docker kerfa"
#: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces"
msgstr ""
@@ -715,6 +749,10 @@ msgstr "OAuth 2 / OIDC stuðningur"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr ""
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -807,7 +845,7 @@ msgstr "Vinsamlegast skráðu þig inn á aðganginn þinn"
#: src/components/add-system.tsx
msgid "Port"
msgstr "Port"
msgstr ""
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
@@ -833,6 +871,14 @@ msgstr "Lesa"
msgid "Received"
msgstr "Móttekið"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr ""
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr ""
#: src/components/login/forgot-pass-form.tsx
msgid "Reset Password"
msgstr "Endurstilla lykilorð"
@@ -882,16 +928,12 @@ msgstr ""
#: src/components/routes/system.tsx
msgid "Sent"
msgstr "Sent"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors."
msgstr "Stilltu prósentuþröskuld fyrir mælaliti."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr ""
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1002,6 +1044,10 @@ msgstr ""
msgid "Throughput of root filesystem"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr ""
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "Til tölvupósta"
@@ -1034,6 +1080,14 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr ""
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr ""
@@ -1095,6 +1149,10 @@ msgstr ""
msgid "Up ({upSystemsLength})"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr ""
#: src/components/routes/system.tsx
msgid "Uptime"
msgstr ""
@@ -1128,6 +1186,10 @@ msgstr ""
msgid "View"
msgstr "Skoða"
#: src/components/routes/system/network-sheet.tsx
msgid "View more"
msgstr ""
#: src/components/routes/settings/alerts-history-data-table.tsx
msgid "View your 200 most recent alerts."
msgstr ""
@@ -1183,3 +1245,4 @@ msgstr ""
#: src/components/routes/settings/layout.tsx
msgid "Your user settings have been updated."
msgstr "Notenda stillingar vistaðar."

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -75,25 +75,6 @@ export interface SystemInfo {
dt?: number
/** operating system */
os?: Os
/** network sent (mb) */
ns?: number
/** network received (mb) */
nr?: number
}
export interface NetworkInterfaceStats {
/** network sent (mb) */
ns: number
/** network received (mb) */
nr: number
/** max network sent (mb) */
nsm?: number
/** max network received (mb) */
nrm?: number
/** total bytes sent since boot */
tbs?: number
/** total bytes received since boot */
tbr?: number
}
export interface SystemStats {
@@ -150,9 +131,7 @@ export interface SystemStats {
nsm?: number
/** max network received (mb) */
nrm?: number
/** per-interface network stats */
ni?: Record<string, NetworkInterfaceStats>
/** max bandwidth (bytes) [sent, received] */
/** max network sent (bytes) */
bm?: [number, number]
/** temperatures */
t?: Record<string, number>
@@ -162,8 +141,6 @@ export interface SystemStats {
g?: Record<string, GPUData>
/** battery percent and state */
bat?: [number, BatteryState]
/** network connection statistics */
nets?: Record<string, number>
}
export interface GPUData {

View File

@@ -1,13 +1,5 @@
## 0.12.8
- Add setting for time format (12h / 24h). (#424)
- Add experimental one-time password (OTP) support.
- Add `TRUSTED_AUTH_HEADER` environment variable for authentication forwarding. (#399)
- Add `AUTO_LOGIN` environment variable for automatic login. (#399)
- Add FreeBSD support for agent install script and update command.
## 0.12.7

View File

@@ -1,7 +1,7 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "beszel.fullname" . }}
name: {{ include "beszel.fullname" . }}-web
labels:
{{- include "beszel.labels" . | nindent 4 }}
{{- if .Values.service.annotations }}

View File

@@ -30,10 +30,14 @@ securityContext: {}
service:
enabled: true
annotations: {}
type: ClusterIP
loadBalancerIP: ""
type: LoadBalancer
loadBalancerIP: "10.0.10.251"
port: 8090
# -- Annotations for the DHCP service
annotations:
metallb.universe.tf/address-pool: pool
metallb.universe.tf/allow-shared-ip: beszel-hub-web
# -- Labels for the DHCP service
ingress:
enabled: false
@@ -92,7 +96,7 @@ persistentVolumeClaim:
accessModes:
- ReadWriteOnce
storageClass: ""
storageClass: "retain-local-path"
# -- volume claim size
size: "500Mi"