2872 Commits

Author SHA1 Message Date
ProxMenuxBot
f11aa77e3a Update helpers_cache.json 2026-05-07 18:32:01 +00:00
MacRimi
1bbcf60e58 Update attribution clause in LICENSE 2026-05-07 17:16:56 +02:00
ProxMenuxBot
c90ee58bc0 Update helpers_cache.json 2026-05-07 12:34:40 +00:00
ProxMenuxBot
2983e20695 Update helpers_cache.json 2026-05-06 17:56:15 +00:00
MacRimi
4e7570cb26 Refactor install_methods variable and split_notes function 2026-05-06 19:55:33 +02:00
ProxMenuxBot
7987b61345 Update helpers_cache.json 2026-05-05 12:22:52 +00:00
ProxMenuxBot
6fbd47ed8a Update helpers_cache.json 2026-05-05 06:43:37 +00:00
ProxMenuxBot
bada4ef524 Update helpers_cache.json 2026-05-05 00:27:53 +00:00
ProxMenuxBot
8918d37bb6 Update helpers_cache.json 2026-05-04 18:29:56 +00:00
ProxMenuxBot
b0d94a3594 Update helpers_cache.json 2026-05-04 06:59:01 +00:00
ProxMenuxBot
1291204d2b Update helpers_cache.json 2026-05-04 00:27:30 +00:00
ProxMenuxBot
804f201589 Update helpers_cache.json 2026-05-03 18:16:18 +00:00
ProxMenuxBot
65779b1eb6 Update helpers_cache.json 2026-05-03 00:27:17 +00:00
ProxMenuxBot
7eac978950 Update helpers_cache.json 2026-05-02 18:16:00 +00:00
ProxMenuxBot
b1d0cfa2e7 Update helpers_cache.json 2026-05-02 12:16:12 +00:00
ProxMenuxBot
282b1c2b0a Update helpers_cache.json 2026-05-02 00:27:19 +00:00
ProxMenuxBot
b4b0d4cd7e Update helpers_cache.json 2026-04-30 18:27:38 +00:00
ProxMenuxBot
6a66ee75ac Update helpers_cache.json 2026-04-30 12:29:27 +00:00
ProxMenuxBot
45dcdcccbb Update helpers_cache.json 2026-04-30 06:50:16 +00:00
ProxMenuxBot
5d47f1dfca Update helpers_cache.json 2026-04-29 12:29:45 +00:00
ProxMenuxBot
1e07e5f17b Update helpers_cache.json 2026-04-28 12:33:16 +00:00
ProxMenuxBot
8adb1b9730 Update helpers_cache.json 2026-04-28 06:52:18 +00:00
ProxMenuxBot
9d3720b6a1 Update helpers_cache.json 2026-04-26 18:12:46 +00:00
ProxMenuxBot
6496601e4e Update helpers_cache.json 2026-04-26 12:12:36 +00:00
ProxMenuxBot
b96df3b501 Update helpers_cache.json 2026-04-26 00:23:40 +00:00
ProxMenuxBot
489460f509 Update helpers_cache.json 2026-04-24 18:13:16 +00:00
ProxMenuxBot
192870d658 Update helpers_cache.json 2026-04-24 12:18:50 +00:00
ProxMenuxBot
bed4b5ee49 Update helpers_cache.json 2026-04-22 12:19:08 +00:00
ProxMenuxBot
f0eb402100 Update helpers_cache.json 2026-04-22 06:35:40 +00:00
MacRimi
e73486904b Revise changelog for ProxMenux v1.2.1 release
Updated the changelog for ProxMenux v1.2.1 to clarify community-reported fixes and improvements.
2026-04-21 22:27:33 +02:00
github-actions[bot]
1119a45e93 Update AppImage release build (2026-04-21 19:28:24) 2026-04-21 19:28:24 +00:00
MacRimi
85860cdddd Merge pull request #187 from MacRimi/develop
Create CHANGELOG.md
2026-04-21 21:23:08 +02:00
MacRimi
da2e89bc94 Create CHANGELOG.md 2026-04-21 21:22:34 +02:00
MacRimi
01ed2da10e Merge pull request #186 from MacRimi/develop
Develop
2026-04-21 21:22:13 +02:00
MacRimi
5fea839e34 Delete CHANGELOG.md 2026-04-21 21:22:01 +02:00
MacRimi
d725910e7a Delete CHANGELOG.md 2026-04-21 21:20:37 +02:00
MacRimi
99f73ad745 Fix image link in CHANGELOG for SR-IOV state
Updated image link for SR-IOV active state in the Monitor UI.
2026-04-21 21:19:16 +02:00
MacRimi
b599a990f6 Rename riov-indicator.png to sriov-indicator.png 2026-04-21 21:17:33 +02:00
MacRimi
c742393efc Update CHANGELOG.md 2026-04-21 21:15:31 +02:00
MacRimi
c403300cd2 Merge pull request #185 from MacRimi/develop
new version v1.2.1
2026-04-21 21:14:12 +02:00
MacRimi
c3a5d6201e Delete AppImage/ProxMenux-Monitor.AppImage.sha256 2026-04-21 21:12:02 +02:00
MacRimi
9e7350c3bb Delete AppImage/ProxMenux-1.2.0.AppImage 2026-04-21 21:09:21 +02:00
MacRimi
5bee471884 Update version.txt 2026-04-21 21:07:15 +02:00
MacRimi
77eb8c7b78 update pci_passthrough_helpers.sh 2026-04-21 21:06:22 +02:00
ProxMenuxBot
acf2302755 Update helpers_cache.json 2026-04-21 18:20:49 +00:00
ProxMenuxBot
8fedd3defe Update helpers_cache.json 2026-04-21 12:19:33 +00:00
ProxMenuxBot
adda8181a6 Update helpers_cache.json 2026-04-21 06:35:35 +00:00
ProxMenuxBot
789494cc89 Update helpers_cache.json 2026-04-21 00:23:27 +00:00
ProxMenuxBot
561086e940 Update helpers_cache.json 2026-04-19 18:10:57 +00:00
MacRimi
20c1140676 Create build-appimage-manual.yml 2026-04-19 19:33:58 +02:00
github-actions[bot]
9bf99c0fdd Update AppImage beta build (2026-04-19 11:49:17) 2026-04-19 11:49:17 +00:00
MacRimi
899eb61dcf update verified_ai_models.json 2026-04-19 13:47:12 +02:00
github-actions[bot]
a5e6e112a5 Update AppImage beta build (2026-04-19 10:29:06) 2026-04-19 10:29:06 +00:00
MacRimi
834795d6d9 update gpu-switch-mode-indicator.tsx 2026-04-19 12:26:52 +02:00
ProxMenuxBot
8442cbca77 Update helpers_cache.json 2026-04-19 00:21:12 +00:00
github-actions[bot]
bcca760403 Update AppImage beta build (2026-04-19 00:10:21) 2026-04-19 00:10:21 +00:00
github-actions[bot]
102a58a068 Update AppImage release build (2026-04-19 00:07:37) 2026-04-19 00:07:37 +00:00
MacRimi
4e849d5309 Merge pull request #178 from MacRimi/develop
update notification_events.py
2026-04-19 02:03:53 +02:00
MacRimi
44e92c8bf0 Delete AppImage/ProxMenux-Monitor.AppImage.sha256 2026-04-19 02:03:35 +02:00
MacRimi
873f5ae51e Delete AppImage/ProxMenux-1.2.0.AppImage 2026-04-19 02:03:24 +02:00
MacRimi
3e0b907138 update notification_events.py 2026-04-19 02:01:04 +02:00
MacRimi
7ee6b6a96b Merge pull request #177 from MacRimi/develop
Update nvidia_installer.sh
2026-04-18 19:21:21 +02:00
MacRimi
14adb673f6 Update nvidia_installer.sh 2026-04-18 19:20:36 +02:00
MacRimi
91381f0850 Merge pull request #176 from MacRimi/develop
Develop
2026-04-18 19:01:18 +02:00
MacRimi
d3e91b5d06 Update menu 2026-04-18 19:00:08 +02:00
MacRimi
74fcd7d569 Update beta_version.txt 2026-04-18 18:53:31 +02:00
MacRimi
0843cd8363 Create beta_version.txt 2026-04-18 18:52:07 +02:00
MacRimi
8c3d022506 Merge pull request #175 from MacRimi/develop
Develop
2026-04-18 18:51:06 +02:00
MacRimi
a5a55f3c7d Delete beta_version.txt 2026-04-18 18:50:29 +02:00
MacRimi
2fb9e74a13 Update menu 2026-04-18 18:48:33 +02:00
MacRimi
f950882ffd Update beta_version.txt 2026-04-18 18:20:57 +02:00
MacRimi
18b3b572f0 Delete beta_version.txt 2026-04-18 18:16:31 +02:00
MacRimi
023e3ff59b Merge pull request #174 from MacRimi/develop
Create beta_version.txt
2026-04-18 18:02:59 +02:00
MacRimi
7318c81fe0 Create beta_version.txt 2026-04-18 18:01:40 +02:00
MacRimi
f516a1cf4c Merge pull request #173 from MacRimi/develop
Develop
2026-04-18 17:58:51 +02:00
MacRimi
43959fc758 Delete beta_version.txt 2026-04-18 17:58:02 +02:00
MacRimi
ff7b1e10a4 Create version.txt 2026-04-18 17:55:35 +02:00
MacRimi
b049712cd6 Update CHANGELOG for ProxMenux v1.2.0 release
Document the release of ProxMenux v1.2.0 with AI enhancements and performance improvements.
2026-04-18 14:21:20 +02:00
MacRimi
2cdfc60fe1 Update CHANGELOG for ProxMenux v1.2.0 release
This release introduces AI-enhanced notifications, a redesigned multi-channel notification system, and performance improvements. It consolidates recent work on Storage, Hardware, and GPU/TPU scripts.
2026-04-18 14:20:31 +02:00
MacRimi
2739cb0894 Revise CHANGELOG for version v1.1.9
Updated changelog for version v1.1.9 with new entries.
2026-04-18 14:15:15 +02:00
MacRimi
3e5ef4fa08 Fix menu option display height in hw_grafics_menu.sh 2026-04-18 09:15:37 +02:00
MacRimi
a7a010d660 Fix spinner stop call and add newline at end of file 2026-04-18 09:14:30 +02:00
MacRimi
67000f5ff1 Update nvidia_installer.sh 2026-04-18 09:13:52 +02:00
MacRimi
efa111e2dd Refactor message building in menu_Helper_Scripts.sh 2026-04-18 09:06:01 +02:00
MacRimi
813798ec2b Add message for completed NVIDIA uninstallation
Add confirmation message after NVIDIA uninstallation steps.
2026-04-18 09:05:18 +02:00
MacRimi
c8b1cd0fab Update nvidia_installer.sh 2026-04-18 08:58:50 +02:00
MacRimi
9220dfb7a3 Update version.txt 2026-04-18 01:24:52 +02:00
MacRimi
f6e9497f1e Delete version.txt 2026-04-18 01:23:47 +02:00
MacRimi
45e7713638 Update menu 2026-04-18 01:21:11 +02:00
MacRimi
802dc491f8 Update menu 2026-04-18 01:20:39 +02:00
MacRimi
3ca5a36240 Update config_menu.sh 2026-04-18 01:13:13 +02:00
MacRimi
3046299414 Update hw_grafics_menu.sh 2026-04-18 01:03:16 +02:00
MacRimi
46edd4e3e4 Remove safe self-update and beta check functions 2026-04-18 00:59:54 +02:00
MacRimi
800a18ac60 Eliminate syntax check for installed launcher
Removed defensive checks for launcher script syntax during installation.
2026-04-18 00:59:23 +02:00
MacRimi
37c60cb82a Upate menu 2026-04-18 00:58:25 +02:00
MacRimi
9446112081 Update beta_version.txt 2026-04-18 00:38:32 +02:00
MacRimi
07384e4d7c Implement safe self-update mechanism
Add safe self-update functionality to prevent launcher corruption.
2026-04-18 00:36:56 +02:00
MacRimi
1f7bf74970 Add syntax check for installed launcher script
Added defensive checks to ensure the installed launcher script passes syntax validation before proceeding with installation.
2026-04-18 00:36:30 +02:00
MacRimi
4b72490486 update menu 2026-04-18 00:33:40 +02:00
MacRimi
9e4e0bc24a Update version.txt 2026-04-18 00:11:49 +02:00
MacRimi
18687666a6 Update README with security note on VirusTotal
Added a security note regarding VirusTotal false positives.
2026-04-18 00:08:58 +02:00
MacRimi
ccb924f8f6 Bump version from 1.1.9 to 1.2.0 2026-04-18 00:03:40 +02:00
MacRimi
58ac4ee743 Update CHANGELOG to remove UI flow consistency details
Removed UI flow consistency section from CHANGELOG.
2026-04-18 00:02:25 +02:00
MacRimi
d515448113 Release v1.2.0 with AI enhancements and optimizations
This release introduces AI-enhanced notifications, a redesigned multi-channel notification system, expanded hardware detection, and significant performance optimizations.
2026-04-18 00:00:52 +02:00
github-actions[bot]
ec3de30b14 Update AppImage release build (2026-04-17 22:00:49) 2026-04-17 22:00:49 +00:00
MacRimi
09ff203662 Update README with security note on VirusTotal
Added a security note regarding VirusTotal false positives for the installation script.
2026-04-17 23:58:31 +02:00
MacRimi
4837b66089 Update README.md 2026-04-17 23:57:15 +02:00
MacRimi
c312cf3d38 Delete beta_version.txt 2026-04-17 23:56:38 +02:00
MacRimi
46f5af1966 Delete install_proxmenux_beta.sh 2026-04-17 23:56:25 +02:00
MacRimi
85f98dff9a Delete AppImage/ProxMenux-Monitor.AppImage.sha256 2026-04-17 23:54:41 +02:00
MacRimi
bd56964860 Delete AppImage/ProxMenux-1.2.0.AppImage 2026-04-17 23:54:30 +02:00
MacRimi
481a653b75 Delete AppImage/ProxMenux-1.0.2.AppImage 2026-04-17 23:54:20 +02:00
MacRimi
f94b59954f Merge pull request #169 from MacRimi/develop
ProxMenux v1.2.0
2026-04-17 23:51:51 +02:00
MacRimi
4f7977b5ca Merge branch 'main' into develop 2026-04-17 23:49:29 +02:00
MacRimi
512cc11894 Delete AppImage/ProxMenux-1.0.1.AppImage 2026-04-17 23:39:24 +02:00
github-actions[bot]
51be0bd3bd Update AppImage release build (2026-04-17 21:38:53) 2026-04-17 21:38:53 +00:00
MacRimi
0f6095f8c3 Create ProxMenux_ai.png 2026-04-17 23:28:16 +02:00
MacRimi
831bf67ee4 update install 2026-04-17 22:26:25 +02:00
MacRimi
e7cca5e532 Delete AppImage/ProxMenux-1.0.2-beta.AppImage 2026-04-17 22:02:18 +02:00
MacRimi
75b08de934 update hw_grafics_menu.sh 2026-04-17 21:55:53 +02:00
github-actions[bot]
987b665115 Update AppImage beta build (2026-04-17 18:58:06) 2026-04-17 18:58:06 +00:00
MacRimi
68ca68c6f1 update hardware.tsx 2026-04-17 20:56:07 +02:00
ProxMenuxBot
5c4ca290fb Update helpers_cache.json 2026-04-17 18:16:35 +00:00
github-actions[bot]
1d61442a49 Update AppImage beta build (2026-04-17 18:03:26) 2026-04-17 18:03:26 +00:00
MacRimi
0e2ede5e66 update v1.2.0 2026-04-17 20:01:30 +02:00
github-actions[bot]
0db74814be Update AppImage beta build (2026-04-17 17:56:07) 2026-04-17 17:56:07 +00:00
MacRimi
75c6f74fc4 update nstall_coral_pve9.sh 2026-04-17 19:53:17 +02:00
MacRimi
e6faec24fa update nvidia_installer.sh 2026-04-17 19:02:46 +02:00
MacRimi
35b7d01d7e update nvidia_installer.sh 2026-04-17 18:35:42 +02:00
MacRimi
415bc439bb Update nvidia_update.sh 2026-04-17 18:24:07 +02:00
MacRimi
fe47522275 Update nvidia_update.sh 2026-04-17 18:07:43 +02:00
MacRimi
24b97831a4 Update nvidia_update.sh 2026-04-17 18:06:21 +02:00
github-actions[bot]
ac95a5afba Update AppImage beta build (2026-04-17 15:39:07) 2026-04-17 15:39:07 +00:00
MacRimi
03850d2958 update virtual-machines.tsx 2026-04-17 17:36:57 +02:00
github-actions[bot]
c7b49cfc4a Update AppImage beta build (2026-04-17 15:03:32) 2026-04-17 15:03:32 +00:00
MacRimi
5398211ab5 update virtual-machines.tsx 2026-04-17 17:01:24 +02:00
github-actions[bot]
dc8ebb651a Update AppImage beta build (2026-04-17 14:41:07) 2026-04-17 14:41:07 +00:00
MacRimi
039e35f3c5 update health_monitor.py 2026-04-17 16:39:08 +02:00
github-actions[bot]
ffadb2c508 Update AppImage beta build (2026-04-17 13:42:17) 2026-04-17 13:42:17 +00:00
MacRimi
019e98e6b6 update flask_proxmenux_routes.py 2026-04-17 15:33:50 +02:00
github-actions[bot]
c998e39038 Update AppImage beta build (2026-04-17 08:42:52) 2026-04-17 08:42:52 +00:00
MacRimi
baa2ff4fa9 update health_persistence.py 2026-04-17 10:38:39 +02:00
github-actions[bot]
4b6a91e74c Update AppImage beta build (2026-04-16 18:54:07) 2026-04-16 18:54:07 +00:00
MacRimi
37f56c8a16 Update settings.tsx 2026-04-16 20:51:52 +02:00
github-actions[bot]
09bb47f408 Update AppImage beta build (2026-04-16 18:42:29) 2026-04-16 18:42:29 +00:00
MacRimi
5db6762690 upddate flask_proxmenux_routes.py 2026-04-16 20:40:34 +02:00
github-actions[bot]
b1cc880253 Update AppImage beta build (2026-04-16 18:15:34) 2026-04-16 18:15:34 +00:00
MacRimi
7d4ea806a2 Update flask_proxmenux_routes.py 2026-04-16 20:13:13 +02:00
github-actions[bot]
2b306c9033 Update AppImage beta build (2026-04-16 18:05:44) 2026-04-16 18:05:44 +00:00
MacRimi
a776d6b746 Update health_persistence.py 2026-04-16 20:01:56 +02:00
github-actions[bot]
07f87de742 Update AppImage beta build (2026-04-16 17:36:53) 2026-04-16 17:36:53 +00:00
MacRimi
cf871da880 update health_persistence.py 2026-04-16 19:33:47 +02:00
github-actions[bot]
774d42d5be Update AppImage beta build (2026-04-16 17:20:45) 2026-04-16 17:20:46 +00:00
MacRimi
6660122e69 Update health_persistence.py 2026-04-16 19:18:42 +02:00
github-actions[bot]
1ef4bc4fed Update AppImage beta build (2026-04-16 17:13:00) 2026-04-16 17:13:00 +00:00
MacRimi
ee1204c566 update health_monitor.py 2026-04-16 19:10:47 +02:00
github-actions[bot]
7f2b0c5de1 Update AppImage beta build (2026-04-16 16:12:35) 2026-04-16 16:12:35 +00:00
MacRimi
9737ffd996 Update storage-overview.tsx 2026-04-16 18:10:27 +02:00
github-actions[bot]
be60b7e17c Update AppImage beta build (2026-04-16 16:02:46) 2026-04-16 16:02:46 +00:00
MacRimi
97368a6f44 update storage-overview.tsx 2026-04-16 17:58:27 +02:00
github-actions[bot]
c3d7f01b40 Update AppImage beta build (2026-04-16 15:41:42) 2026-04-16 15:41:42 +00:00
MacRimi
cbebd5147c update storage-overview.tsx 2026-04-16 17:36:23 +02:00
github-actions[bot]
4611be734f Update AppImage beta build (2026-04-16 15:00:04) 2026-04-16 15:00:04 +00:00
MacRimi
528b57664f Update storage-overview.tsx 2026-04-16 16:57:40 +02:00
github-actions[bot]
cc86d68507 Update AppImage beta build (2026-04-16 14:44:10) 2026-04-16 14:44:10 +00:00
MacRimi
7c8da462db update storage-overview.tsx 2026-04-16 16:42:11 +02:00
github-actions[bot]
b7963c3b70 Update AppImage beta build (2026-04-16 14:11:03) 2026-04-16 14:11:03 +00:00
MacRimi
d51dd35376 Update storage-overview.tsx 2026-04-16 16:08:58 +02:00
github-actions[bot]
196086498e Update AppImage beta build (2026-04-16 13:54:35) 2026-04-16 13:54:35 +00:00
MacRimi
f6ff76f9ce Update storage-overview.tsx 2026-04-16 15:52:26 +02:00
github-actions[bot]
20a2db6739 Update AppImage beta build (2026-04-16 13:38:55) 2026-04-16 13:38:55 +00:00
MacRimi
aa3b8ebe82 Update storage-overview.tsx 2026-04-16 15:28:48 +02:00
github-actions[bot]
d03afa1793 Update AppImage beta build (2026-04-16 13:11:30) 2026-04-16 13:11:30 +00:00
MacRimi
056cee2f94 Update storage-overview.tsx 2026-04-16 15:09:16 +02:00
github-actions[bot]
f80e087429 Update AppImage beta build (2026-04-16 12:47:49) 2026-04-16 12:47:49 +00:00
MacRimi
eea765300e Update flask_server.py 2026-04-16 14:45:47 +02:00
ProxMenuxBot
c2396d7e81 Update helpers_cache.json 2026-04-16 12:19:51 +00:00
github-actions[bot]
0b94acf7f6 Update AppImage beta build (2026-04-16 10:10:25) 2026-04-16 10:10:25 +00:00
MacRimi
324cb23f75 Update storage-overview.tsx 2026-04-16 12:08:36 +02:00
github-actions[bot]
b341ba8297 Update AppImage beta build (2026-04-16 09:46:02) 2026-04-16 09:46:02 +00:00
MacRimi
f5b9da0908 update storage-overview.tsx 2026-04-16 11:43:42 +02:00
ProxMenuxBot
c83672a4bc Update helpers_cache.json 2026-04-16 00:24:34 +00:00
ProxMenuxBot
af8e3f6a71 Update helpers_cache.json 2026-04-15 12:18:08 +00:00
ProxMenuxBot
5025d38a76 Update helpers_cache.json 2026-04-14 12:18:35 +00:00
ProxMenuxBot
f3c7fb97fb Update helpers_cache.json 2026-04-13 18:23:34 +00:00
github-actions[bot]
cb2ab5f67b Update AppImage beta build (2026-04-13 17:32:33) 2026-04-13 17:32:33 +00:00
MacRimi
003c8850b7 Update storage-overview.tsx 2026-04-13 19:30:19 +02:00
github-actions[bot]
d9461c170d Update AppImage beta build (2026-04-13 17:25:04) 2026-04-13 17:25:04 +00:00
MacRimi
57b5de4a4a Update storage-overview.tsx 2026-04-13 19:22:59 +02:00
github-actions[bot]
c406c52086 Update AppImage beta build (2026-04-13 17:16:44) 2026-04-13 17:16:44 +00:00
MacRimi
df4855ec47 Update storage-overview.tsx 2026-04-13 19:11:57 +02:00
github-actions[bot]
8c7f4a4c20 Update AppImage beta build (2026-04-13 16:53:47) 2026-04-13 16:53:47 +00:00
MacRimi
3ec733d9c6 update storage-overview.tsx 2026-04-13 18:49:18 +02:00
github-actions[bot]
71c64d1ae5 Update AppImage beta build (2026-04-13 13:31:25) 2026-04-13 13:31:25 +00:00
MacRimi
98becfd368 update storage-overview.tsx 2026-04-13 15:29:22 +02:00
github-actions[bot]
4eea90bd97 Update AppImage beta build (2026-04-13 12:51:54) 2026-04-13 12:51:54 +00:00
MacRimi
2344935357 update storage-overview.tsx 2026-04-13 14:49:48 +02:00
ProxMenuxBot
550279ec68 Update helpers_cache.json 2026-04-13 12:20:26 +00:00
github-actions[bot]
44aefb5d3b Update AppImage beta build (2026-04-13 09:13:46) 2026-04-13 09:13:46 +00:00
MacRimi
7d3cf4d364 Update flask_server.py 2026-04-13 11:11:42 +02:00
github-actions[bot]
f6ef383598 Update AppImage beta build (2026-04-13 08:09:07) 2026-04-13 08:09:07 +00:00
MacRimi
a6149e3cd8 update storage-overview.tsx 2026-04-13 10:07:09 +02:00
github-actions[bot]
07f1098418 Update AppImage beta build (2026-04-13 07:50:38) 2026-04-13 07:50:38 +00:00
MacRimi
3d00f33dbf Update flask_server.py 2026-04-13 09:48:48 +02:00
MacRimi
98c859fbf8 Update storage-overview.tsx 2026-04-13 09:35:23 +02:00
github-actions[bot]
9460bee72f Update AppImage beta build (2026-04-13 07:20:17) 2026-04-13 07:20:17 +00:00
MacRimi
5a957fe904 Update storage-overview.tsx 2026-04-13 09:18:09 +02:00
github-actions[bot]
59a4b6e4ca Update AppImage beta build (2026-04-13 06:42:00) 2026-04-13 06:42:00 +00:00
MacRimi
9000316224 Update storage-overview.tsx 2026-04-13 08:38:32 +02:00
github-actions[bot]
da96c57819 Update AppImage beta build (2026-04-12 21:56:49) 2026-04-12 21:56:49 +00:00
MacRimi
b3cef0b009 Update storage-overview.tsx 2026-04-12 23:54:52 +02:00
MacRimi
78ace237dd Update storage-overview.tsx 2026-04-12 23:48:49 +02:00
MacRimi
e94e065eca update storage-overview.tsx 2026-04-12 23:45:23 +02:00
github-actions[bot]
4ffe0f3f46 Update AppImage beta build (2026-04-12 21:13:40) 2026-04-12 21:13:40 +00:00
MacRimi
7af4150e44 update storage-overview.tsx 2026-04-12 23:11:31 +02:00
github-actions[bot]
71950369e1 Update AppImage beta build (2026-04-12 21:01:05) 2026-04-12 21:01:05 +00:00
MacRimi
adb4815c9b Update storage-overview.tsx 2026-04-12 22:58:49 +02:00
github-actions[bot]
1841feb643 Update AppImage beta build (2026-04-12 20:52:32) 2026-04-12 20:52:32 +00:00
MacRimi
f14a0393b7 update storage-overview.tsx 2026-04-12 22:50:30 +02:00
github-actions[bot]
710f77764b Update AppImage beta build (2026-04-12 20:37:41) 2026-04-12 20:37:41 +00:00
MacRimi
ae2e86d1d1 Update storage-overview.tsx 2026-04-12 22:35:12 +02:00
github-actions[bot]
167fcb2921 Update AppImage beta build (2026-04-12 20:30:17) 2026-04-12 20:30:17 +00:00
MacRimi
47145ab9d1 update storage-overview.tsx 2026-04-12 22:28:15 +02:00
github-actions[bot]
441ee8e948 Update AppImage beta build (2026-04-12 19:58:21) 2026-04-12 19:58:21 +00:00
github-actions[bot]
9c4528dfcb Update AppImage beta build (2026-04-12 19:39:05) 2026-04-12 19:39:05 +00:00
MacRimi
d7c60631b4 Update storage-overview.tsx 2026-04-12 21:37:02 +02:00
github-actions[bot]
530f5c2dbc Update AppImage beta build (2026-04-12 19:30:36) 2026-04-12 19:30:36 +00:00
MacRimi
03dc2afe8d Update storage-overview.tsx 2026-04-12 21:28:36 +02:00
MacRimi
7d35d91415 Update storage-overview.tsx 2026-04-12 21:06:01 +02:00
MacRimi
4843fae0eb Update scripts 2026-04-12 20:32:34 +02:00
ProxMenuxBot
3dfbeac541 Update helpers_cache.json 2026-04-11 00:18:18 +00:00
MacRimi
4fa4bbb08b update switch_gpu_mode.sh 2026-04-10 09:51:03 +02:00
github-actions[bot]
4204e619db Update AppImage beta build (2026-04-10 07:10:14) 2026-04-10 07:10:14 +00:00
MacRimi
a663b83daa Update script-terminal-modal.tsx 2026-04-10 09:07:57 +02:00
github-actions[bot]
fb218a9331 Update AppImage beta build (2026-04-09 20:08:09) 2026-04-09 20:08:09 +00:00
MacRimi
75b677576e update switch_gpu_mode_direct.sh 2026-04-09 22:05:23 +02:00
MacRimi
04bda0bf10 update switch_gpu_mode_direct.sh 2026-04-09 21:42:44 +02:00
MacRimi
8ca33dec6f update switch_gpu_mode_direct.sh 2026-04-09 21:03:06 +02:00
MacRimi
eed9303e41 Update script-terminal-modal.tsx 2026-04-09 20:50:56 +02:00
MacRimi
5277e7b47d Update script-terminal-modal.tsx 2026-04-09 20:41:12 +02:00
github-actions[bot]
727c86a804 Update AppImage beta build (2026-04-09 18:32:02) 2026-04-09 18:32:02 +00:00
MacRimi
21edae5944 Update script-terminal-modal.tsx 2026-04-09 20:29:50 +02:00
github-actions[bot]
a0d48a1191 Update AppImage beta build (2026-04-09 18:22:51) 2026-04-09 18:22:51 +00:00
MacRimi
086ba9e577 Update switch_gpu_mode_direct.sh 2026-04-09 20:20:46 +02:00
github-actions[bot]
bda5fdbecd Update AppImage beta build (2026-04-09 18:14:32) 2026-04-09 18:14:32 +00:00
MacRimi
13d2eeb9b2 update gpu-switch-mode-indicator.tsx 2026-04-09 20:12:21 +02:00
github-actions[bot]
1bbf814ea3 Update AppImage beta build (2026-04-09 18:01:03) 2026-04-09 18:01:03 +00:00
MacRimi
6d0e5add0b create switch_gpu_mode_direct.sh 2026-04-09 19:58:54 +02:00
github-actions[bot]
06849ca666 Update AppImage beta build (2026-04-09 17:42:58) 2026-04-09 17:42:58 +00:00
MacRimi
4346a5554f Update hardware.tsx 2026-04-09 19:40:49 +02:00
github-actions[bot]
2a174df697 Update AppImage beta build (2026-04-09 17:30:29) 2026-04-09 17:30:29 +00:00
MacRimi
28df51092c update gpu-switch-mode-indicator.tsx 2026-04-09 19:27:45 +02:00
github-actions[bot]
ca70852060 Update AppImage beta build (2026-04-09 13:44:32) 2026-04-09 13:44:32 +00:00
MacRimi
86df5cda6e Update hardware.tsx 2026-04-09 15:42:05 +02:00
github-actions[bot]
7db7b7d98f Update AppImage beta build (2026-04-09 13:31:57) 2026-04-09 13:31:57 +00:00
MacRimi
3cede88a3d update gpu-switch-mode-indicator.tsx 2026-04-09 15:29:41 +02:00
github-actions[bot]
af63b71ab8 Update AppImage beta build (2026-04-09 13:04:37) 2026-04-09 13:04:38 +00:00
MacRimi
2805c46a22 update gpu-switch-mode-indicator.tsx 2026-04-09 15:02:25 +02:00
github-actions[bot]
104353f013 Update AppImage beta build (2026-04-09 12:22:57) 2026-04-09 12:22:57 +00:00
MacRimi
435f346d98 update notification_events.py 2026-04-09 14:08:56 +02:00
MacRimi
2b8caa924f update notification_events.py 2026-04-09 12:34:03 +02:00
ProxMenuxBot
dd22f303ac Update helpers_cache.json 2026-04-08 18:23:52 +00:00
ProxMenuxBot
02141ae16f Update helpers_cache.json 2026-04-08 12:16:35 +00:00
ProxMenuxBot
b9b10a69d5 Update helpers_cache.json 2026-04-07 18:17:36 +00:00
github-actions[bot]
d8631a8594 Update AppImage beta build (2026-04-07 13:37:06) 2026-04-07 13:37:06 +00:00
MacRimi
463769aba9 Update health_persistence.py 2026-04-07 15:34:48 +02:00
ProxMenuxBot
a9fbaf15b2 Update helpers_cache.json 2026-04-07 12:16:27 +00:00
ProxMenuxBot
b04c3b9f78 Update helpers_cache.json 2026-04-07 00:19:15 +00:00
MacRimi
f2229c2393 Update add_gpu_vm.sh 2026-04-06 23:10:02 +02:00
MacRimi
b4aadfee3e Update add_gpu_vm.sh 2026-04-06 22:58:17 +02:00
MacRimi
840e8a774e Update add_gpu_vm.sh 2026-04-06 22:51:30 +02:00
MacRimi
c429d391f5 Update add_gpu_vm.sh 2026-04-06 22:34:37 +02:00
MacRimi
2fda3dd1d5 Update add_gpu_vm.sh 2026-04-06 22:26:54 +02:00
MacRimi
4018093c9d Update add_gpu_vm.sh 2026-04-06 22:21:38 +02:00
MacRimi
2f5b8ce4ed update add_gpu_vm.sh 2026-04-06 22:04:38 +02:00
MacRimi
2174c04e4f update add_gpu_vm.sh 2026-04-06 21:50:46 +02:00
MacRimi
109a4d7f54 update switch_gpu_mode.sh 2026-04-06 21:12:13 +02:00
ProxMenuxBot
080ee5cff0 Update helpers_cache.json 2026-04-06 18:16:41 +00:00
MacRimi
257668ab96 Update gpu_hook_guard_helpers.sh 2026-04-06 19:36:35 +02:00
MacRimi
d747ac7659 Update gpu_hook_guard_helpers.sh 2026-04-06 19:25:47 +02:00
MacRimi
d7c04ebbc7 update vm_storage_helpers.sh 2026-04-06 19:13:31 +02:00
MacRimi
c5b01b4bb7 update switch_gpu_mode.sh 2026-04-06 18:40:57 +02:00
MacRimi
e8cc90b83d update m_storage_helpers.sh 2026-04-06 17:26:01 +02:00
MacRimi
d3974018d8 add_controller_nvme_vm.sh 2026-04-06 17:16:26 +02:00
MacRimi
10ce9dbcde add_controller_nvme_vm.sh 2026-04-06 13:39:07 +02:00
github-actions[bot]
5be4bd2ae9 Update AppImage beta build (2026-04-06 10:16:08) 2026-04-06 10:16:08 +00:00
MacRimi
ea1d8ab037 Update system-overview.tsx 2026-04-06 12:06:08 +02:00
MacRimi
adde2ce5b9 update health_persistence.py 2026-04-06 12:02:05 +02:00
ProxMenuxBot
9d37a7293b Update helpers_cache.json 2026-04-06 00:19:03 +00:00
ProxMenuxBot
975d4aab60 Update helpers_cache.json 2026-04-05 12:07:31 +00:00
github-actions[bot]
5ead9ee661 Update AppImage beta build (2026-04-05 10:19:43) 2026-04-05 10:19:43 +00:00
MacRimi
95e876b37f update health_monitor.py 2026-04-05 12:17:42 +02:00
github-actions[bot]
4c72d0b3ef Update AppImage beta build (2026-04-05 10:05:01) 2026-04-05 10:05:01 +00:00
MacRimi
e7dc030304 Update health_monitor.py 2026-04-05 12:02:59 +02:00
github-actions[bot]
c9d5c84d35 Update AppImage beta build (2026-04-05 10:00:14) 2026-04-05 10:00:14 +00:00
MacRimi
4b01ba1d2f update health_monitor.py 2026-04-05 11:58:14 +02:00
github-actions[bot]
723e56ada2 Update AppImage beta build (2026-04-05 09:53:20) 2026-04-05 09:53:20 +00:00
MacRimi
e9851da12f update virtual-machines.tsx 2026-04-05 11:51:26 +02:00
MacRimi
5826b0419b update pci_passthrough_helpers.sh 2026-04-05 11:24:08 +02:00
ProxMenuxBot
77124c4549 Update helpers_cache.json 2026-04-05 00:19:09 +00:00
github-actions[bot]
00dbd1c87e Update AppImage beta build (2026-04-03 23:33:41) 2026-04-03 23:33:41 +00:00
MacRimi
e0e732dd2c update health_persistence.py 2026-04-04 01:31:37 +02:00
MacRimi
ce69c0ba1f Merge branch 'develop' of https://github.com/MacRimi/ProxMenux into develop 2026-04-04 00:55:09 +02:00
MacRimi
58c4e115ba update add_gpu_vm.sh 2026-04-04 00:54:57 +02:00
github-actions[bot]
44478057dd Update AppImage beta build (2026-04-03 22:53:29) 2026-04-03 22:53:29 +00:00
MacRimi
d1efae37a4 update health_persistence.py 2026-04-04 00:51:20 +02:00
ProxMenuxBot
8d8e4bab26 Update helpers_cache.json 2026-04-03 12:10:34 +00:00
MacRimi
db113f0433 Update menu_post_install.sh 2026-04-03 10:44:10 +02:00
MacRimi
5a66167709 Merge branch 'develop' of https://github.com/MacRimi/ProxMenux into develop 2026-04-03 10:34:56 +02:00
MacRimi
7326201b0a update system_utils.sh 2026-04-03 10:34:46 +02:00
github-actions[bot]
f116a6b0f2 Update AppImage beta build (2026-04-03 08:07:42) 2026-04-03 08:07:42 +00:00
MacRimi
3087ab36da Update flask_server.py 2026-04-03 10:05:44 +02:00
ProxMenuxBot
28a7189905 Update helpers_cache.json 2026-04-03 00:19:43 +00:00
ProxMenuxBot
0f8e215706 Update helpers_cache.json 2026-04-02 18:15:08 +00:00
github-actions[bot]
fd92bed465 Update AppImage beta build (2026-04-02 18:01:23) 2026-04-02 18:01:23 +00:00
MacRimi
281f6975ec Update notification_templates.py 2026-04-02 19:59:27 +02:00
github-actions[bot]
e2c40eae48 Update AppImage beta build (2026-04-02 17:26:45) 2026-04-02 17:26:45 +00:00
MacRimi
26968b02a1 update post_install.sh 2026-04-02 19:23:55 +02:00
MacRimi
0d8070455d Update notification_templates.py 2026-04-02 18:29:35 +02:00
MacRimi
2537e964a5 Update notification_templates.py 2026-04-02 17:38:47 +02:00
MacRimi
ca7134e610 Update notification_templates.py 2026-04-02 17:20:02 +02:00
MacRimi
a8c591affd Merge branch 'develop' of https://github.com/MacRimi/ProxMenux into develop 2026-04-02 17:05:10 +02:00
MacRimi
e3842f200d Update add_gpu_lxc.sh 2026-04-02 17:05:00 +02:00
github-actions[bot]
cc952d8c79 Update AppImage beta build (2026-04-02 15:01:15) 2026-04-02 15:01:15 +00:00
MacRimi
e11daa0b36 update ai_context_enrichment.py 2026-04-02 16:59:09 +02:00
github-actions[bot]
873c77d659 Update AppImage beta build (2026-04-02 09:40:56) 2026-04-02 09:40:56 +00:00
MacRimi
1756a6eb28 Merge branch 'develop' of https://github.com/MacRimi/ProxMenux into develop 2026-04-02 11:38:51 +02:00
MacRimi
9636d3671c Update notification_events.py 2026-04-02 11:38:36 +02:00
github-actions[bot]
22e2ebb96c Update AppImage beta build (2026-04-02 09:12:04) 2026-04-02 09:12:04 +00:00
MacRimi
c53d3807e7 Merge branch 'develop' of https://github.com/MacRimi/ProxMenux into develop 2026-04-02 11:10:03 +02:00
MacRimi
23a6392979 Update notification_events.py 2026-04-02 11:09:50 +02:00
github-actions[bot]
fa599ad183 Update AppImage beta build (2026-04-02 07:52:09) 2026-04-02 07:52:09 +00:00
MacRimi
e79fdcfe58 Merge branch 'develop' of https://github.com/MacRimi/ProxMenux into develop 2026-04-02 09:50:10 +02:00
MacRimi
3746f356b9 Update notification_events.py 2026-04-02 09:49:54 +02:00
github-actions[bot]
a5f3362d6e Update AppImage beta build (2026-04-02 07:31:50) 2026-04-02 07:31:50 +00:00
MacRimi
2072264918 update notification_manager.py 2026-04-02 09:29:29 +02:00
github-actions[bot]
a5cb01133e Update AppImage beta build (2026-04-02 07:01:18) 2026-04-02 07:01:18 +00:00
MacRimi
62b55cbf16 Merge branch 'develop' of https://github.com/MacRimi/ProxMenux into develop 2026-04-02 08:58:02 +02:00
MacRimi
7ea0c4d36c Update notification_events.py 2026-04-02 08:57:37 +02:00
github-actions[bot]
546200844e Update AppImage beta build (2026-04-02 06:40:10) 2026-04-02 06:40:10 +00:00
MacRimi
007e3d1c0e update notification_templates.py 2026-04-02 08:38:01 +02:00
ProxMenuxBot
74a508e3a8 Update helpers_cache.json 2026-04-02 00:17:06 +00:00
MacRimi
5f5dc171be update scripts gpu 2026-04-01 23:09:51 +02:00
github-actions[bot]
bccba6e9b9 Update AppImage beta build (2026-04-01 13:27:08) 2026-04-01 13:27:08 +00:00
MacRimi
c2073a5db5 Update health_monitor.py 2026-04-01 15:24:47 +02:00
ProxMenuxBot
52ad229d93 Update helpers_cache.json 2026-04-01 12:16:07 +00:00
github-actions[bot]
f6a7352672 Update AppImage beta build (2026-04-01 11:24:43) 2026-04-01 11:24:43 +00:00
MacRimi
46e0322e6f update health_persistence.py 2026-04-01 13:01:48 +02:00
MacRimi
618538a854 update health_persistence.py 2026-04-01 12:30:19 +02:00
MacRimi
d62396717a update health_persistence.py 2026-04-01 12:03:54 +02:00
MacRimi
a734fa5566 Update health_monitor.py 2026-04-01 00:01:12 +02:00
MacRimi
f98b302b94 Update health_persistence.py 2026-03-31 23:47:38 +02:00
MacRimi
215d36900a Update health_persistence.py 2026-03-31 23:20:33 +02:00
MacRimi
2df55d2839 Update health_monitor.py 2026-03-31 23:14:48 +02:00
MacRimi
e00051caa7 update health_monitor.py 2026-03-31 23:00:00 +02:00
MacRimi
5138b2f1d5 update health_persistence.py 2026-03-31 20:55:17 +02:00
MacRimi
65dfb9103f update /system-logs.tsx 2026-03-31 20:10:58 +02:00
MacRimi
39bbc036cd update system-logs.tsx 2026-03-31 19:38:23 +02:00
MacRimi
aaf6dd36f0 update system-logs.tsx 2026-03-31 19:30:10 +02:00
MacRimi
e7519e68a3 update system-logs.tsx 2026-03-31 19:09:41 +02:00
MacRimi
ad5803ef9c update proxmox-dashboard.tsx 2026-03-31 18:51:38 +02:00
MacRimi
c04b514a2a update proxmox-dashboard.tsx 2026-03-31 18:42:35 +02:00
MacRimi
cb9f567154 Fix link in security note in README.md
Updated the link in the security note to point to the correct GitHub issue.
2026-03-30 23:40:06 +02:00
MacRimi
80afa789e7 Update notification service 2026-03-30 22:26:20 +02:00
MacRimi
43f2ce52a5 Update notification service 2026-03-30 22:10:40 +02:00
MacRimi
cf2e24269e Update notification_templates.py 2026-03-30 21:34:17 +02:00
MacRimi
6899650bf8 Update health_persistence.py 2026-03-30 21:19:08 +02:00
MacRimi
c16df51892 Update notification_templates.py 2026-03-30 21:10:54 +02:00
MacRimi
c549737ad0 Update HealthMonitor 2026-03-30 20:52:25 +02:00
MacRimi
a85b51843a Update switch.tsx 2026-03-30 20:27:40 +02:00
MacRimi
60d401f5ea Update settings.tsx 2026-03-30 20:19:00 +02:00
ProxMenuxBot
ca02b9001f Update helpers_cache.json 2026-03-30 18:17:00 +00:00
MacRimi
2fc5e2865d Update notification service 2026-03-30 19:55:19 +02:00
MacRimi
261b2bfb3c Update notification_manager.py 2026-03-30 19:16:31 +02:00
MacRimi
30e32e89b2 Update notification_manager.py 2026-03-30 19:11:19 +02:00
MacRimi
8b1a2b9bff Update security note in README.md
Clarified security note regarding VirusTotal false positives.
2026-03-30 19:04:53 +02:00
MacRimi
f71289b248 Update README with security note and support request
Added a security note regarding VirusTotal false positives and encouraged support for the project.
2026-03-30 19:04:02 +02:00
MacRimi
54eab9af49 Update notification service 2026-03-30 18:53:03 +02:00
ProxMenuxBot
a05546e811 Update helpers_cache.json 2026-03-30 12:16:12 +00:00
ProxMenuxBot
276c648f29 Update helpers_cache.json 2026-03-29 18:07:42 +00:00
ProxMenuxBot
8c389f4790 Update helpers_cache.json 2026-03-29 12:07:15 +00:00
ProxMenuxBot
a09144d21a Update helpers_cache.json 2026-03-29 00:18:49 +00:00
MacRimi
cb9a43f496 Merge branch 'develop' of https://github.com/MacRimi/ProxMenux into develop 2026-03-28 23:18:42 +01:00
MacRimi
30606e4743 Update beta_version.txt 2026-03-28 23:18:03 +01:00
github-actions[bot]
d05913fbdf Update AppImage beta build (2026-03-28 22:15:36) 2026-03-28 22:15:36 +00:00
MacRimi
a34efb50e0 Update notification service 2026-03-28 23:13:35 +01:00
MacRimi
a26c69fc8d Update beta_version.txt 2026-03-28 23:09:39 +01:00
MacRimi
107803705c Update beta_version.txt 2026-03-28 23:02:01 +01:00
MacRimi
858b1689bf Update security_menu.sh 2026-03-28 22:56:07 +01:00
MacRimi
641acbd1f4 Update security_menu.sh 2026-03-28 22:53:56 +01:00
MacRimi
1de76ae6c1 Create security_menu.sh 2026-03-28 22:50:44 +01:00
github-actions[bot]
94f535b8ec Update AppImage beta build (2026-03-28 21:48:28) 2026-03-28 21:48:28 +00:00
MacRimi
f072e285fc Update startup_grace.py 2026-03-28 22:44:58 +01:00
MacRimi
2c363bbb8e Update health_persistence.py 2026-03-28 22:44:52 +01:00
MacRimi
1c2b87d584 Update notification_templates.py 2026-03-28 22:37:21 +01:00
MacRimi
e55154bd5e Update notification service 2026-03-28 22:19:33 +01:00
MacRimi
71098abb65 Update terminal-panel.tsx 2026-03-28 22:13:20 +01:00
MacRimi
7a3a4d1413 Update terminal-panel.tsx 2026-03-28 22:04:47 +01:00
MacRimi
7858fb0283 Update terminal-panel.tsx 2026-03-28 21:50:04 +01:00
MacRimi
2f9959c009 Update virtual-machines.tsx 2026-03-28 21:32:59 +01:00
MacRimi
e7d3b20295 Update menus 2026-03-28 19:32:15 +01:00
MacRimi
264fa4982f Update notification service 2026-03-28 19:29:16 +01:00
MacRimi
9636614761 Update notification service 2026-03-28 19:25:05 +01:00
MacRimi
ac6561ca52 Update menus 2026-03-28 18:29:58 +01:00
MacRimi
923172d39b udate lynis install 2026-03-28 17:27:29 +01:00
MacRimi
d46c42d26b Unistall Fail2ban 2026-03-28 17:01:08 +01:00
MacRimi
d628233982 Update notification service 2026-03-28 15:50:30 +01:00
ProxMenuxBot
e9e1d471ec Update helpers_cache.json 2026-03-28 12:07:42 +00:00
MacRimi
f4740916f5 Update install_coral_lxc.sh 2026-03-28 12:49:52 +01:00
ProxMenuxBot
3a2c9b1b05 Update helpers_cache.json 2026-03-28 00:17:05 +00:00
MacRimi
4cc1147579 Update notification service 2026-03-27 20:42:03 +01:00
MacRimi
976f23a90e update notification service 2026-03-27 20:31:21 +01:00
MacRimi
8ed500adf7 Update notification service 2026-03-27 20:17:59 +01:00
MacRimi
8658044c0c Update notification service 2026-03-27 19:55:15 +01:00
MacRimi
0edc2cc3af Update notification service 2026-03-27 19:40:17 +01:00
ProxMenuxBot
f4db4cde13 Update helpers_cache.json 2026-03-27 18:16:33 +00:00
MacRimi
6bb9313b95 Update notification service 2026-03-27 19:15:11 +01:00
ProxMenuxBot
6447dfef50 Update helpers_cache.json 2026-03-27 12:12:51 +00:00
ProxMenuxBot
55cb3a1267 Update helpers_cache.json 2026-03-27 00:18:11 +00:00
MacRimi
7c5e7208b9 Update notification service 2026-03-26 20:04:53 +01:00
ProxMenuxBot
aad4b13fda Update helpers_cache.json 2026-03-26 18:19:00 +00:00
MacRimi
839a20df97 Update notification service 2026-03-26 19:05:11 +01:00
MacRimi
d497763e38 Update repository and clean up 2026-03-26 19:03:01 +01:00
MacRimi
12c088c10b Update nvidia_installer.sh 2026-03-26 18:18:26 +01:00
MacRimi
8c6a6bece6 Update menu_Helper_Scripts.sh 2026-03-26 17:43:34 +01:00
ProxMenuxBot
819ca8a212 Update helpers_cache.json 2026-03-26 12:16:15 +00:00
MacRimi
8d1becbd8c Update shutdown-notify.sh 2026-03-26 01:11:12 +01:00
MacRimi
ebb7491c58 Create install_proxmenux_beta.sh 2026-03-26 00:56:20 +01:00
MacRimi
4f1278c37a Delete install_proxmenux_beta.sh 2026-03-26 00:54:28 +01:00
MacRimi
8ddee9013b Update install_proxmenux_beta.sh 2026-03-26 00:53:14 +01:00
MacRimi
7fe233ae2e Update install_proxmenux_beta.sh 2026-03-26 00:51:14 +01:00
MacRimi
f7b37b1559 Update install_proxmenux_beta.sh 2026-03-26 00:49:06 +01:00
MacRimi
0b3624dbd5 Update notification service 2026-03-26 00:32:52 +01:00
MacRimi
fb066eb2e9 Update install_proxmenux_beta.sh 2026-03-25 23:57:33 +01:00
MacRimi
22fadbb87b Update install_proxmenux_beta.sh 2026-03-25 23:56:27 +01:00
MacRimi
3b119d528c Merge branch 'develop' of https://github.com/MacRimi/ProxMenux into develop 2026-03-25 23:50:59 +01:00
MacRimi
bb3d3d759e Update install_proxmenux_beta.sh 2026-03-25 23:50:39 +01:00
github-actions[bot]
3c08ae9399 Update AppImage beta build (2026-03-25 22:48:45) 2026-03-25 22:48:45 +00:00
MacRimi
7a6fa2afa5 Upgrade GitHub Actions to use latest versions 2026-03-25 23:47:00 +01:00
MacRimi
ba4e3c3adb Update notification service 2026-03-25 23:45:34 +01:00
MacRimi
66892f69ce Update notification service 2026-03-25 23:30:00 +01:00
MacRimi
e37469ac2b Update install_proxmenux_beta.sh 2026-03-25 23:17:29 +01:00
MacRimi
cdc2d7bbcb update notification service 2026-03-25 23:13:11 +01:00
MacRimi
92c0e6ff09 Update install_proxmenux_beta.sh 2026-03-25 22:58:51 +01:00
MacRimi
9b3fd324c3 Update install_proxmenux_beta.sh 2026-03-25 22:56:55 +01:00
MacRimi
23bd692f8e Update install_proxmenux_beta.sh 2026-03-25 22:53:54 +01:00
MacRimi
29b4573ca9 Update install_proxmenux_beta.sh 2026-03-25 22:50:32 +01:00
MacRimi
8b6755d866 Update notification service 2026-03-25 22:43:42 +01:00
MacRimi
6da20aab05 update notification service 2026-03-25 22:14:38 +01:00
MacRimi
5aa5942bcd Upgrade GitHub Actions to latest versions 2026-03-25 20:16:09 +01:00
MacRimi
68872d0e06 Update notification service 2026-03-25 20:12:08 +01:00
MacRimi
d53c1dc402 Utdate notification service 2026-03-25 19:47:47 +01:00
MacRimi
6c2b03ae76 Update notification service 2026-03-25 19:21:37 +01:00
ProxMenuxBot
9f79d2b737 Update helpers_cache.json 2026-03-25 18:17:47 +00:00
MacRimi
2241b125d6 Update notification service 2026-03-25 18:28:54 +01:00
github-actions[bot]
152624302c Update AppImage beta build (2026-03-25 12:13:46) 2026-03-25 12:13:46 +00:00
ProxMenuxBot
6a703ee6a4 Update helpers_cache.json 2026-03-25 12:13:03 +00:00
MacRimi
0c0caa422d Update notification service 2026-03-25 13:10:36 +01:00
ProxMenuxBot
6fa7c1d4eb Update helpers_cache.json 2026-03-25 00:16:22 +00:00
MacRimi
509fff3972 Refactor discussion template for AI prompts
Removed prompt name and output language fields from the template. Updated description field to include output language information.
2026-03-24 18:20:58 +01:00
MacRimi
bcacd8b98e Update notification service 2026-03-24 17:48:52 +01:00
MacRimi
d2c8178772 Update notification service 2026-03-24 17:34:05 +01:00
MacRimi
365a246461 Update health-status-modal.tsx 2026-03-24 17:18:38 +01:00
MacRimi
098ae13f94 Update install_proxmenux_beta.sh 2026-03-24 15:14:36 +01:00
ProxMenuxBot
6a92225630 Update helpers_cache.json 2026-03-24 12:14:00 +00:00
github-actions[bot]
c98044be9a Update AppImage beta build (2026-03-24 12:02:15) 2026-03-24 12:02:15 +00:00
MacRimi
e6eb81cc61 Update notification_events.py 2026-03-24 12:48:26 +01:00
github-actions[bot]
0e50caadec Update AppImage beta build (2026-03-24 11:06:32) 2026-03-24 11:06:32 +00:00
MacRimi
aad44ad42f Merge branch 'develop' of https://github.com/MacRimi/ProxMenux into develop 2026-03-24 12:04:31 +01:00
MacRimi
59fd055526 Update beta_version.txt 2026-03-24 12:04:18 +01:00
github-actions[bot]
5fce75a60d Update AppImage beta build (2026-03-24 11:01:15) 2026-03-24 11:01:15 +00:00
MacRimi
74390726b4 Update flask_server.py 2026-03-24 11:41:49 +01:00
MacRimi
10f37b88c3 Upgrade GitHub Actions to v6 for build workflow 2026-03-24 10:58:16 +01:00
MacRimi
9bfacd9da9 Upgrade GitHub Actions to version 6 2026-03-24 10:57:45 +01:00
MacRimi
a286770fd2 Upgrade GitHub Actions to version 6 2026-03-24 10:56:50 +01:00
github-actions[bot]
3101e84830 Update AppImage beta build (2026-03-24 09:41:18) 2026-03-24 09:41:18 +00:00
MacRimi
815b3bebda Update notification_templates.py 2026-03-24 10:38:06 +01:00
MacRimi
c71eda1229 Update notification service 2026-03-24 10:27:52 +01:00
MacRimi
60518be5bd Update notification service 2026-03-23 23:05:27 +01:00
MacRimi
83254d9d70 Update notification_events.py 2026-03-23 21:04:01 +01:00
MacRimi
d34cebc90d Update health_monitor.py 2026-03-23 20:25:27 +01:00
MacRimi
c7ef51a73c Update notification service 2026-03-23 20:14:25 +01:00
MacRimi
ab34fb08c1 Update health_monitor.py 2026-03-23 19:31:21 +01:00
ProxMenuxBot
6f99e1e8c1 Update helpers_cache.json 2026-03-23 18:14:42 +00:00
MacRimi
5fe87a04f0 Merge branch 'develop' of https://github.com/MacRimi/ProxMenux into develop 2026-03-23 18:08:35 +01:00
MacRimi
4ac71381da Update health_monitor.py 2026-03-23 18:08:22 +01:00
github-actions[bot]
f95e1ad4b8 Update AppImage beta build (2026-03-23 16:56:41) 2026-03-23 16:56:41 +00:00
MacRimi
168726c131 Update notification service 2026-03-23 17:49:39 +01:00
ProxMenuxBot
4545aeb9c6 Update helpers_cache.json 2026-03-23 12:13:22 +00:00
ProxMenuxBot
84cf3e6a15 Update helpers_cache.json 2026-03-23 00:17:27 +00:00
MacRimi
cd123b3479 Update notification_channels.py 2026-03-22 22:32:28 +01:00
MacRimi
4b99de8841 Update notification service 2026-03-22 22:24:17 +01:00
MacRimi
147ca0a41a Update gemini_provider.py 2026-03-22 21:35:25 +01:00
MacRimi
3f24d55945 update notification service 2026-03-22 21:14:19 +01:00
MacRimi
70871330d3 Update notification service 2026-03-22 20:58:57 +01:00
MacRimi
08bc354fa5 Update notification service 2026-03-22 20:47:51 +01:00
MacRimi
54c7322b23 Update flask_server.py 2026-03-22 20:20:59 +01:00
github-actions[bot]
4d2ceee26b Update AppImage beta build (2026-03-22 18:56:48) 2026-03-22 18:56:48 +00:00
MacRimi
b6321e9698 Update notification_templates.py 2026-03-22 19:42:12 +01:00
MacRimi
dd197c9826 Update auto_post_install.sh 2026-03-22 19:32:08 +01:00
MacRimi
01427f4926 Update notification service 2026-03-22 18:43:33 +01:00
MacRimi
c47a7ba2a5 Update notification_events.py 2026-03-22 15:02:29 +01:00
MacRimi
04564bc9cf Update health_monitor.py 2026-03-22 14:57:46 +01:00
MacRimi
04661ce340 Update flask_server.py 2026-03-22 14:53:34 +01:00
MacRimi
fcc54e2d6a Update flask_server.py 2026-03-22 14:48:30 +01:00
MacRimi
52fc3b03b7 Update flask_server.py 2026-03-22 14:39:09 +01:00
MacRimi
70c29ed7b6 Update flask_server.py 2026-03-22 14:29:46 +01:00
MacRimi
d33741a90d Update notification service 2026-03-22 14:20:47 +01:00
ProxMenuxBot
484f117b8e Update helpers_cache.json 2026-03-22 12:05:28 +00:00
MacRimi
317739b508 Update beta_version.txt 2026-03-22 11:16:44 +01:00
github-actions[bot]
d8062b4859 Update AppImage beta build (2026-03-22 10:15:19) 2026-03-22 10:15:19 +00:00
MacRimi
452eb70faf Update notification-settings.tsx 2026-03-22 10:22:17 +01:00
MacRimi
83889d7e3c Add discussion template for AI notifications prompts 2026-03-22 00:28:09 +01:00
MacRimi
2eb970a6a2 Create discussion template for custom prompts
Added a discussion template for sharing custom prompts, including fields for prompt name, AI provider, model, output language, description, prompt content, example output, additional notes, and confirmation checkboxes.
2026-03-22 00:18:20 +01:00
MacRimi
7838762a4e Update health_monitor.py 2026-03-21 23:19:41 +01:00
MacRimi
f370a670ad Update flask_server.py 2026-03-21 23:03:30 +01:00
MacRimi
41fa6f4b10 Update notification service 2026-03-21 22:27:54 +01:00
MacRimi
18aa9a77dd Update notification service 2026-03-21 21:53:46 +01:00
MacRimi
e53f6c0c52 Update notification-settings.tsx 2026-03-21 21:09:06 +01:00
MacRimi
642539cdfc Update notification-settings.tsx 2026-03-21 20:55:34 +01:00
MacRimi
6534fa7171 Update notification-settings.tsx 2026-03-21 20:41:02 +01:00
MacRimi
ff08f4c0b5 Update notification-settings.tsx 2026-03-21 20:29:30 +01:00
MacRimi
134c62d543 Update notification-settings.tsx 2026-03-21 20:16:06 +01:00
MacRimi
1243842c68 Update notification-settings.tsx 2026-03-21 20:11:52 +01:00
MacRimi
c1093be548 Update notification service 2026-03-21 19:57:28 +01:00
MacRimi
b6f58758f2 Update notification-settings.tsx 2026-03-21 19:17:02 +01:00
MacRimi
1dbb59bc3f Update notification service 2026-03-21 18:53:35 +01:00
MacRimi
5e32857729 Update flask_server.py 2026-03-21 17:22:31 +01:00
MacRimi
e3a611f33d Remove star history chart from README
Removed the star history chart HTML section from README.
2026-03-21 11:08:33 +01:00
MacRimi
8fb2a9094e Enhance README with star history chart
Added responsive star history chart with dark and light themes.
2026-03-21 11:07:56 +01:00
github-actions[bot]
46b9309336 Update AppImage beta build (2026-03-20 22:56:56) 2026-03-20 22:56:56 +00:00
MacRimi
e60d2db8cb Update verified_ai_models.json 2026-03-20 23:48:07 +01:00
MacRimi
e5e6c00100 Update notification service 2026-03-20 23:40:17 +01:00
MacRimi
40709b7480 Update flask_notification_routes.py 2026-03-20 23:29:25 +01:00
MacRimi
900c7154b6 Update notification service 2026-03-20 23:21:00 +01:00
MacRimi
2f4ea02544 Update notification service 2026-03-20 22:18:56 +01:00
MacRimi
c24c10a13a Update notification service 2026-03-20 21:45:19 +01:00
MacRimi
22cd2e4bb3 Update notification service 2026-03-20 20:33:41 +01:00
github-actions[bot]
53ac43eb49 Update AppImage beta build (2026-03-20 19:13:03) 2026-03-20 19:13:03 +00:00
MacRimi
fa29c46a95 Update notification service 2026-03-20 20:11:08 +01:00
MacRimi
d871b4c78e Update notification_templates.py 2026-03-20 20:04:38 +01:00
MacRimi
03acdce2c8 Update beta_version.txt 2026-03-20 19:57:58 +01:00
github-actions[bot]
9aa3b61efc Update AppImage beta build (2026-03-20 18:56:21) 2026-03-20 18:56:21 +00:00
MacRimi
0a62e3deca Update notification_templates.py 2026-03-20 19:48:14 +01:00
MacRimi
f454d5f045 Update notification_templates.py 2026-03-20 19:38:45 +01:00
MacRimi
a5dca65e57 Update notification_templates.py 2026-03-20 19:20:51 +01:00
MacRimi
979a7e5d18 Update notification_templates.py 2026-03-20 19:16:36 +01:00
ProxMenuxBot
d1e7154040 Update helpers_cache.json 2026-03-20 18:10:20 +00:00
MacRimi
4750ff8cd5 Update notification_templates.py 2026-03-20 19:03:04 +01:00
MacRimi
72d02010c7 Update notification service 2026-03-20 18:45:35 +01:00
MacRimi
b49be42f2d Update notification_templates.py 2026-03-20 18:37:31 +01:00
MacRimi
eddc183b85 Update notification_templates.py 2026-03-20 18:07:54 +01:00
MacRimi
812cf83de4 Update notification_templates.py 2026-03-20 17:46:02 +01:00
MacRimi
502cb8403f Update notification_templates.py 2026-03-20 17:09:32 +01:00
github-actions[bot]
cf78bff21d Update AppImage beta build (2026-03-20 15:28:37) 2026-03-20 15:28:37 +00:00
MacRimi
1e352f4a7e Update notification service 2026-03-20 16:26:30 +01:00
MacRimi
47be85fdc0 Update notification_templates.py 2026-03-20 14:13:22 +01:00
MacRimi
5c9849e729 Update notification_templates.py 2026-03-20 13:55:47 +01:00
ProxMenuxBot
e695b4e764 Update helpers_cache.json 2026-03-20 12:08:24 +00:00
github-actions[bot]
d4d2e33619 Update AppImage beta build (2026-03-20 11:08:38) 2026-03-20 11:08:38 +00:00
MacRimi
1603f1ae66 Update notification service 2026-03-20 11:44:46 +01:00
MacRimi
88da476249 Update notification service 2026-03-20 11:26:26 +01:00
github-actions[bot]
1218fde6ea Update AppImage beta build (2026-03-19 20:30:24) 2026-03-19 20:30:24 +00:00
MacRimi
5046398e80 Update notification_templates.py 2026-03-19 21:28:27 +01:00
github-actions[bot]
20e5b6cc5a Update AppImage beta build (2026-03-19 19:47:33) 2026-03-19 19:47:33 +00:00
MacRimi
c867c4ef51 Update ollama_provider.py 2026-03-19 20:45:35 +01:00
github-actions[bot]
f2e804783b Update AppImage beta build (2026-03-19 19:22:23) 2026-03-19 19:22:23 +00:00
MacRimi
817e18ded2 Update ollama_provider.py 2026-03-19 20:20:11 +01:00
github-actions[bot]
f99b498608 Update AppImage beta build (2026-03-19 19:11:56) 2026-03-19 19:11:56 +00:00
MacRimi
d6cd4763f5 Merge branch 'develop' of https://github.com/MacRimi/ProxMenux into develop 2026-03-19 20:09:39 +01:00
MacRimi
fdac846ede update ollama modal 2026-03-19 20:09:26 +01:00
github-actions[bot]
d75c73df30 Update AppImage beta build (2026-03-19 19:00:26) 2026-03-19 19:00:27 +00:00
MacRimi
55c8dffe37 Update notification-settings.tsx 2026-03-19 19:58:17 +01:00
github-actions[bot]
8b35861602 Update AppImage beta build (2026-03-19 18:48:01) 2026-03-19 18:48:01 +00:00
MacRimi
6db7e64ca9 Update notification-settings.tsx 2026-03-19 19:42:42 +01:00
MacRimi
9484f78fb6 Update notification-settings.tsx 2026-03-19 19:36:39 +01:00
github-actions[bot]
1949aeb10f Update AppImage beta build (2026-03-19 18:20:31) 2026-03-19 18:20:31 +00:00
MacRimi
65fad4cc37 Create switch.tsx 2026-03-19 19:11:32 +01:00
MacRimi
876194cdc8 update storage settings 2026-03-19 19:07:26 +01:00
MacRimi
cefeac72fc Merge branch 'develop' of https://github.com/MacRimi/ProxMenux into develop 2026-03-19 17:56:57 +01:00
MacRimi
71708c3874 Update notification-settings.tsx 2026-03-19 17:56:51 +01:00
ProxMenuxBot
b1eae7b768 Update helpers_cache.json 2026-03-19 12:09:20 +00:00
github-actions[bot]
f02985e367 Update AppImage beta build (2026-03-19 09:11:42) 2026-03-19 09:11:42 +00:00
MacRimi
2848f672c1 Merge branch 'develop' of https://github.com/MacRimi/ProxMenux into develop 2026-03-19 10:08:11 +01:00
MacRimi
f2210946c2 update ollama model 2026-03-19 10:08:08 +01:00
github-actions[bot]
4b79b9a417 Update AppImage beta build (2026-03-19 08:20:27) 2026-03-19 08:20:27 +00:00
MacRimi
10f8735f55 Update notification service 2026-03-19 09:18:14 +01:00
MacRimi
19a3a14417 Delete AppImage/ProxMenux-1.0.2.AppImage 2026-03-18 23:07:40 +01:00
github-actions[bot]
aeaea1289c Update AppImage beta build (2026-03-18 22:05:40) 2026-03-18 22:05:40 +00:00
MacRimi
014ffa9b74 Update release-notes-modal.tsx 2026-03-18 23:02:18 +01:00
github-actions[bot]
0a9efe0122 Update AppImage beta build (2026-03-18 21:43:41) 2026-03-18 21:43:41 +00:00
MacRimi
82082a4b89 Update v1.0.2-beta 2026-03-18 22:41:39 +01:00
MacRimi
55bb5b5a1c Add beta program details to README
Added a beta program section with installation instructions and feedback guidelines.
2026-03-18 22:16:28 +01:00
MacRimi
fd399edce7 Update README.md 2026-03-18 22:13:03 +01:00
MacRimi
5611b69ad2 Update README.md 2026-03-18 22:06:24 +01:00
MacRimi
7b181046d3 Update beta_version.txt 2026-03-18 21:56:26 +01:00
MacRimi
b593d50b9a Update beta_version.txt 2026-03-18 21:55:49 +01:00
github-actions[bot]
8fe9426f5c Update AppImage beta build (2026-03-18 20:53:59) 2026-03-18 20:53:59 +00:00
MacRimi
0ce5f72df4 Update install_proxmenux_beta.sh 2026-03-18 21:47:24 +01:00
MacRimi
d88c6570d0 Merge branch 'develop' of https://github.com/MacRimi/ProxMenux into develop 2026-03-18 21:24:33 +01:00
MacRimi
2980f7c9b8 Update menu 2026-03-18 21:24:18 +01:00
github-actions[bot]
7b2825e5ce Update AppImage beta build (2026-03-18 20:18:13) 2026-03-18 20:18:13 +00:00
MacRimi
e47f79bcd4 Merge branch 'develop' of https://github.com/MacRimi/ProxMenux into develop 2026-03-18 21:15:49 +01:00
MacRimi
415020bf5d Beta Program Installer 2026-03-18 21:15:34 +01:00
github-actions[bot]
6ab1582db8 Update AppImage beta build (2026-03-18 20:05:10) 2026-03-18 20:05:10 +00:00
MacRimi
751a02aae8 Merge branch 'develop' of https://github.com/MacRimi/ProxMenux into develop 2026-03-18 21:02:46 +01:00
MacRimi
bee637aa48 Beta Program Installer 2026-03-18 21:02:42 +01:00
github-actions[bot]
ad6c7ffda8 Update AppImage beta build (2026-03-18 19:12:58) 2026-03-18 19:12:58 +00:00
MacRimi
7f59ca37f2 Merge branch 'develop' of https://github.com/MacRimi/ProxMenux into develop 2026-03-18 20:10:50 +01:00
MacRimi
9056964bb9 Update notification_templates.py 2026-03-18 20:10:48 +01:00
github-actions[bot]
154441bc1b Update AppImage beta build (2026-03-18 18:34:56) 2026-03-18 18:34:56 +00:00
MacRimi
873ec75586 update notification service 2026-03-18 19:32:38 +01:00
ProxMenuxBot
e8232a9ea0 Update helpers_cache.json 2026-03-18 18:18:46 +00:00
MacRimi
751b361528 Update notification_templates.py 2026-03-18 19:18:27 +01:00
MacRimi
8990a3e243 Update notification_templates.py 2026-03-18 19:08:48 +01:00
MacRimi
bc44490e96 Update notification_templates.py 2026-03-18 18:59:46 +01:00
MacRimi
fe2d0b1d2a Update notification_templates.py 2026-03-18 18:43:05 +01:00
MacRimi
bf3c5c1602 Update notification_templates.py 2026-03-18 18:37:55 +01:00
github-actions[bot]
6364931322 Update AppImage beta build (2026-03-18 16:52:23) 2026-03-18 16:52:23 +00:00
MacRimi
536d7141d9 Add files via upload 2026-03-18 17:50:13 +01:00
MacRimi
69e0bfe89a update notification service 2026-03-18 17:48:02 +01:00
ProxMenuxBot
aeabb99be6 Update helpers_cache.json 2026-03-18 12:14:02 +00:00
MacRimi
38ee6d836d Add GitHub Actions workflow for AppImage beta build 2026-03-18 11:22:29 +01:00
MacRimi
7bb4bd3da5 Rename workflow for building AppImage release 2026-03-18 11:21:26 +01:00
MacRimi
7524615671 Save compiled AppImage files before git operations
Added steps to save compiled AppImage files before cleaning local changes.
2026-03-18 11:15:14 +01:00
MacRimi
f5fe883d49 Enhance AppImage workflow to include checksum upload
Updated the workflow to upload both AppImage and checksum artifacts, and modified the commit process to use the current branch instead of main.
2026-03-18 11:11:05 +01:00
MacRimi
bec6406216 Update GitHub Actions workflow for AppImage build 2026-03-18 10:48:41 +01:00
MacRimi
c13c7ba626 Update notification service 2026-03-18 09:36:05 +01:00
ProxMenuxBot
ef041f2702 Update helpers_cache.json 2026-03-18 00:17:00 +00:00
MacRimi
46b222180a Update notification-settings.tsx 2026-03-17 20:43:33 +01:00
MacRimi
cd69b317c0 Update notification_manager.py 2026-03-17 20:41:09 +01:00
MacRimi
05fa751137 Update notification_templates.py 2026-03-17 20:34:55 +01:00
MacRimi
820317b9bd Update notification_manager.py 2026-03-17 20:25:19 +01:00
MacRimi
bce01ad7a1 Update notification_templates.py 2026-03-17 20:05:23 +01:00
MacRimi
bbe014798e Update notification_templates.py 2026-03-17 19:52:25 +01:00
MacRimi
beea4dea04 Update notification_templates.py 2026-03-17 19:43:26 +01:00
MacRimi
71505362b4 Update notification service 2026-03-17 19:22:12 +01:00
MacRimi
ff6904d436 Update notification-settings.tsx 2026-03-17 18:49:22 +01:00
MacRimi
1915bb3a9b Update notification service 2026-03-17 18:19:34 +01:00
MacRimi
df0f15419e Add files via upload 2026-03-17 18:07:18 +01:00
MacRimi
04474d2e07 Create telegram.png 2026-03-17 17:35:27 +01:00
MacRimi
518bf0f217 Update notification service 2026-03-17 15:23:44 +01:00
MacRimi
ac8f06c3a2 Update notification_manager.py 2026-03-17 15:04:29 +01:00
MacRimi
feaf7b8abd Update notification-settings.tsx 2026-03-17 14:56:23 +01:00
MacRimi
ac71057a3d Update notification-settings.tsx 2026-03-17 14:13:39 +01:00
MacRimi
0cb8900374 Update notification service 2026-03-17 14:07:47 +01:00
ProxMenuxBot
dc531eaa37 Update helpers_cache.json 2026-03-17 12:13:15 +00:00
MacRimi
9a51a9e635 Update terminal-panel.tsx 2026-03-16 23:59:49 +01:00
MacRimi
754a0988ee Update terminal-panel.tsx 2026-03-16 23:48:35 +01:00
MacRimi
1985c0f815 Update terminal-panel.tsx 2026-03-16 23:35:31 +01:00
MacRimi
889b778d43 Update terminal-panel.tsx 2026-03-16 23:22:42 +01:00
MacRimi
26e90aa39e Update terminal panel 2026-03-16 23:13:13 +01:00
ProxMenuxBot
1eaabd14bd Update helpers_cache.json 2026-03-16 18:18:06 +00:00
MacRimi
f406342b53 update latency modal 2026-03-16 18:19:19 +01:00
MacRimi
b7c800b550 Update oci_manager.py 2026-03-16 17:58:04 +01:00
MacRimi
b6780ba876 Update oci manager 2026-03-16 17:46:34 +01:00
ProxMenuxBot
c9c8987cca Update helpers_cache.json 2026-03-16 12:14:04 +00:00
MacRimi
f74d336072 Update health_persistence.py 2026-03-16 09:48:58 +01:00
MacRimi
6aaaa910af Update health_monitor.py 2026-03-16 09:36:40 +01:00
MacRimi
35eee03aa5 Update setupWebSocket 2026-03-16 09:20:45 +01:00
ProxMenuxBot
06dc6ea23f Update helpers_cache.json 2026-03-16 00:17:59 +00:00
MacRimi
c1f8e7f511 Update lxc-terminal-modal.tsx 2026-03-15 21:34:45 +01:00
MacRimi
6d39acc627 Update terminal modal 2026-03-15 21:24:04 +01:00
MacRimi
11f768d26c Update oci_manager.py 2026-03-15 21:06:39 +01:00
MacRimi
602afc2954 Update jwt 2026-03-15 20:43:05 +01:00
MacRimi
b7203b8219 update simple_jwt 2026-03-15 20:00:10 +01:00
MacRimi
513774bb7b Update SSL WebSocket 2026-03-15 19:04:35 +01:00
MacRimi
7375e306fb Update health_persistence.py 2026-03-15 18:27:55 +01:00
MacRimi
785d58cb59 Update health monitor 2026-03-15 18:12:42 +01:00
MacRimi
af61d145da Update oci manager 2026-03-15 17:59:47 +01:00
MacRimi
0b75e967f3 Update oci manager 2026-03-15 17:19:35 +01:00
MacRimi
cfa4210b0a Update terminal panel 2026-03-15 16:40:28 +01:00
MacRimi
0d6d570ae8 Update terminal panel 2026-03-15 16:27:37 +01:00
MacRimi
65add36b2f Update terminal panel 2026-03-15 16:16:03 +01:00
MacRimi
793b3dde12 Upgrade GitHub Actions to latest versions 2026-03-15 14:27:07 +01:00
MacRimi
8b3a76dfc5 Upgrade GitHub Actions to latest versions 2026-03-15 14:14:00 +01:00
ProxMenuxBot
60398210c7 Update helpers_cache.json 2026-03-15 12:06:54 +00:00
MacRimi
9112bcc52f Update health_monitor.py 2026-03-15 10:54:37 +01:00
MacRimi
e534cffcf7 Update health_monitor.py 2026-03-15 10:41:34 +01:00
MacRimi
a184dcc38f Update health_monitor.py 2026-03-15 10:36:19 +01:00
MacRimi
2c80223fc4 Update health_persistence.py 2026-03-15 10:28:53 +01:00
MacRimi
59a578fb2d Update health_persistence.py 2026-03-15 10:23:38 +01:00
MacRimi
91c3f3520b Update health_persistence.py 2026-03-15 10:13:44 +01:00
MacRimi
e169200f40 Update health monitor 2026-03-15 10:03:35 +01:00
ProxMenuxBot
486c7ef530 Update helpers_cache.json 2026-03-15 00:18:01 +00:00
MacRimi
26c75e8309 Update health_persistence.py 2026-03-15 00:00:24 +01:00
MacRimi
bc6eb0b5a0 Update secure-gateway-setup.tsx 2026-03-14 23:35:47 +01:00
MacRimi
9a057ef646 Update secure-gateway-setup.tsx 2026-03-14 23:29:17 +01:00
MacRimi
a7b06bd5fc Update secure-gateway-setup.tsx 2026-03-14 23:09:46 +01:00
MacRimi
9d706d3aa3 Update secure-gateway-setup.tsx 2026-03-14 22:56:55 +01:00
MacRimi
fdc4253117 Update secure-gateway-setup.tsx 2026-03-14 22:50:07 +01:00
MacRimi
ff168937aa Update vpn seervice 2026-03-14 22:38:48 +01:00
MacRimi
b7d060a1f3 Update vpn service 2026-03-14 22:10:17 +01:00
MacRimi
c37466e948 Update secure-gateway-setup.tsx 2026-03-14 21:45:00 +01:00
MacRimi
09513c0beb Update vpn service 2026-03-14 21:29:16 +01:00
MacRimi
a3f4277bdc Update secure-gateway-setup.tsx 2026-03-14 20:53:42 +01:00
MacRimi
b43d8918bd update vpn service 2026-03-14 20:43:56 +01:00
MacRimi
4546adb894 Update vpn service 2026-03-14 20:30:53 +01:00
MacRimi
db5ac37ad3 Update secure-gateway-setup.tsx 2026-03-14 20:07:25 +01:00
MacRimi
098c14f9e0 Update secure-gateway-setup.tsx 2026-03-14 19:55:54 +01:00
ProxMenuxBot
94131097a5 Update helpers_cache.json 2026-03-14 18:06:03 +00:00
MacRimi
6d69e009dc Rebuild Helper Scripts catalog for new data architecture
Rebuilt the Helper Scripts catalog to connect directly to the PocketBase API, enhancing data structure and script options. Acknowledged contributions from Community Scripts maintainers for their support in the integration.
2026-03-14 18:29:27 +01:00
MacRimi
6d9b132ab8 Bump version from 1.1.8 to 1.1.9 2026-03-14 18:18:44 +01:00
MacRimi
461a353e92 Update menu_Helper_Scripts.sh 2026-03-14 18:18:05 +01:00
MacRimi
efec1aff18 Update script version and improve loading logic
Updated version to 1.3 and last updated date to 14/03/2025. Removed dependency on metadata.json and improved script loading and error handling.
2026-03-14 18:16:55 +01:00
MacRimi
258d6d9a49 Update menu_Helper_Scripts.sh 2026-03-14 18:15:15 +01:00
MacRimi
1c4b7c7b97 Update menu_Helper_Scripts.sh 2026-03-14 18:13:34 +01:00
ProxMenuxBot
8bc6306813 Update helpers_cache.json 2026-03-14 17:04:43 +00:00
MacRimi
2923c00738 Add files via upload 2026-03-14 18:04:09 +01:00
MacRimi
b30b6a062a Delete .github/scripts/generate_helpers_cache.py 2026-03-14 18:03:51 +01:00
MacRimi
8f5df889ab Rename helpers_cache__.json to helpers_cache.json 2026-03-14 17:50:43 +01:00
MacRimi
4ec8b19251 Add backup JSON cache file 2026-03-14 17:50:25 +01:00
MacRimi
1035a94775 Rename helpers_cache_back.json to helpers_cache.json 2026-03-14 17:32:12 +01:00
MacRimi
3ca2ae7175 Add helpers_cache__.json file 2026-03-14 17:31:56 +01:00
ProxMenuxBot
4ba1ca890c Update helpers_cache.json 2026-03-14 15:44:57 +00:00
MacRimi
cba012bd15 Add backup JSON helpers cache file 2026-03-14 16:44:28 +01:00
MacRimi
9515ccd816 Add files via upload 2026-03-14 16:43:44 +01:00
MacRimi
46622f5028 Add generate_helpers_cache_back.py script 2026-03-14 16:40:25 +01:00
MacRimi
1fd896fb72 Update oci_manager.py 2026-03-13 20:12:35 +01:00
MacRimi
d081cc6c21 Update oci_manager.py 2026-03-13 19:19:32 +01:00
MacRimi
9190c8e5bf Update API_URL for JSON content retrieval 2026-03-13 17:15:01 +01:00
MacRimi
109498e2df Update API_URL to point to Frontend-Archive 2026-03-13 17:08:57 +01:00
MacRimi
21cfc63fc0 Update oci_manager.py 2026-03-13 00:13:52 +01:00
MacRimi
a5f14146b9 Update oci_manager.py 2026-03-12 23:47:37 +01:00
MacRimi
2c18f6d975 update oci manager 2026-03-12 23:37:21 +01:00
MacRimi
84d9146c04 update oci manager 2026-03-12 22:50:20 +01:00
MacRimi
6d4006fd93 update oci manager 2026-03-12 22:13:56 +01:00
MacRimi
b4a2e5ee11 Create oci manager 2026-03-12 21:30:44 +01:00
MacRimi
304b814bb1 Merge branch 'develop' of https://github.com/MacRimi/ProxMenux into develop 2026-03-12 19:40:31 +01:00
MacRimi
c5e4774b29 Update notification service 2026-03-12 19:40:13 +01:00
MacRimi
e90651b55b Update Node.js version and add environment variable 2026-03-12 19:37:25 +01:00
MacRimi
60d7c395bc Update Node.js version and add environment variable 2026-03-12 19:35:23 +01:00
MacRimi
2b5c9c2d61 Update health_persistence.py 2026-03-12 19:22:06 +01:00
MacRimi
a703f1db73 Update health_persistence.py 2026-03-12 18:55:15 +01:00
MacRimi
4aaba7619e Update notification service 2026-03-12 18:12:04 +01:00
MacRimi
1a88dd801d Update proxmox-dashboard.tsx 2026-03-10 19:31:07 +01:00
MacRimi
83352ab9fe Update proxmox-dashboard.tsx 2026-03-10 19:25:03 +01:00
MacRimi
6e268a1bf4 Update proxmox-dashboard.tsx 2026-03-10 19:15:31 +01:00
MacRimi
4d65e54576 Update proxmox-dashboard.tsx 2026-03-10 19:06:28 +01:00
MacRimi
c131ec722e Update proxmox-dashboard.tsx 2026-03-10 18:54:03 +01:00
MacRimi
574e12f336 Update proxmox-dashboard.tsx 2026-03-10 18:37:37 +01:00
MacRimi
1705868457 Update proxmox-dashboard.tsx 2026-03-10 18:22:59 +01:00
MacRimi
8392d111dc Update health-status-modal.tsx 2026-03-10 18:06:40 +01:00
MacRimi
8c5ccbadac Update notification service 2026-03-10 18:00:56 +01:00
MacRimi
e4aa081e64 Update health-status-modal.tsx 2026-03-10 17:32:18 +01:00
MacRimi
8cc74eceb6 Update notification service 2026-03-10 17:22:27 +01:00
MacRimi
45365e3860 Update storage-overview.tsx 2026-03-08 23:22:18 +01:00
MacRimi
3739560956 Update notification service 2026-03-08 22:47:04 +01:00
MacRimi
b8cff3e699 Update notification service 2026-03-08 20:01:02 +01:00
MacRimi
d1d44afc9d Update notification service 2026-03-08 19:37:04 +01:00
ProxMenuxBot
782d847e54 Update helpers_cache.json 2026-03-08 18:04:29 +00:00
MacRimi
be2bfa0087 Update health_monitor.py 2026-03-08 18:21:34 +01:00
MacRimi
1ea28d66df Update notification service 2026-03-08 18:15:36 +01:00
MacRimi
8c51957bfa Update notification service 2026-03-08 16:33:52 +01:00
MacRimi
858a1bba4f Update health_monitor.py 2026-03-08 10:23:53 +01:00
MacRimi
17e4227978 Update notification service 2026-03-08 10:15:55 +01:00
MacRimi
f8b5e07518 Update notification service 2026-03-08 10:00:17 +01:00
ProxMenuxBot
d96e4019aa Update helpers_cache.json 2026-03-08 00:15:29 +00:00
MacRimi
acc9760690 Update health_monitor.py 2026-03-08 00:58:37 +01:00
MacRimi
56dab535c3 Update notification service 2026-03-08 00:53:35 +01:00
MacRimi
94670711e7 Update flask_server.py 2026-03-08 00:30:05 +01:00
MacRimi
673c206e02 Update latency-detail-modal.tsx 2026-03-07 23:47:24 +01:00
MacRimi
decd3bd134 Update latency-detail-modal.tsx 2026-03-07 23:40:19 +01:00
MacRimi
6e5c7aeab5 Update notification service 2026-03-07 23:33:13 +01:00
MacRimi
2647550324 Update notification service 2026-03-07 23:18:25 +01:00
MacRimi
424a63011b Update notification service 2026-03-07 23:05:51 +01:00
MacRimi
0e6a125c60 update notification service 2026-03-07 22:55:42 +01:00
MacRimi
758cae4f86 Update latency-detail-modal.tsx 2026-03-07 22:36:20 +01:00
MacRimi
6e8368c62a Update latency-detail-modal.tsx 2026-03-07 22:14:47 +01:00
MacRimi
a14e554323 Update latency-detail-modal.tsx 2026-03-07 21:58:57 +01:00
MacRimi
6435202fa1 Update latency-detail-modal.tsx 2026-03-07 21:37:24 +01:00
MacRimi
cf8425ff14 update notification service 2026-03-07 21:17:13 +01:00
MacRimi
9bb1c1b233 Update notification service 2026-03-07 20:38:18 +01:00
MacRimi
c9ccc5e27e update notification service 2026-03-07 20:17:40 +01:00
MacRimi
7115b2ff54 Update notification service 2026-03-07 20:06:59 +01:00
MacRimi
b89e234ba4 Update latency-detail-modal.tsx 2026-03-07 19:45:03 +01:00
MacRimi
70fbaa0bfd Update latency-detail-modal.tsx 2026-03-07 19:27:31 +01:00
MacRimi
f6cdd4ff36 Update latency-detail-modal.tsx 2026-03-07 19:02:14 +01:00
MacRimi
1857f46452 Update notification service 2026-03-07 18:44:51 +01:00
MacRimi
f95a6f4fd7 Update latency-detail-modal.tsx 2026-03-07 17:57:57 +01:00
MacRimi
b0bc66f548 Update latency-detail-modal.tsx 2026-03-07 17:21:44 +01:00
MacRimi
34b4a6c3d8 Update latency-detail-modal.tsx 2026-03-07 17:06:47 +01:00
MacRimi
b4c7463226 Update latency-detail-modal.tsx 2026-03-07 16:59:13 +01:00
MacRimi
ca5b33ef69 Update latency-detail-modal.tsx 2026-03-07 16:42:01 +01:00
MacRimi
acd980091d Update latency-detail-modal.tsx 2026-03-07 16:23:11 +01:00
MacRimi
de5317987e Update latency-detail-modal.tsx 2026-03-07 12:02:20 +01:00
ProxMenuxBot
6b438bc4aa Update helpers_cache.json 2026-03-07 00:14:59 +00:00
MacRimi
9b1495a490 Update notification service 2026-03-06 21:25:14 +01:00
MacRimi
f638011d63 Update latency-detail-modal.tsx 2026-03-06 20:54:40 +01:00
MacRimi
8447a95c8a Update latency-detail-modal.tsx 2026-03-06 20:34:59 +01:00
MacRimi
4feceaa1d1 Update latency-detail-modal.tsx 2026-03-06 20:13:31 +01:00
MacRimi
8383e381d1 Update latency-detail-modal.tsx 2026-03-06 20:02:49 +01:00
MacRimi
a064a7471e Update notification service 2026-03-06 19:32:10 +01:00
MacRimi
f0e3d7d09a Update notification_events.py 2026-03-06 19:05:08 +01:00
MacRimi
2b7f4ccd6c Update notification_manager.py 2026-03-06 18:57:43 +01:00
MacRimi
46fa89233b Update notification service 2026-03-06 18:44:27 +01:00
MacRimi
591099e42b Update storage-overview.tsx 2026-03-06 15:24:18 +01:00
MacRimi
d08398ea57 Update flask_server.py 2026-03-06 14:43:18 +01:00
MacRimi
83f49742b6 Update notification service 2026-03-06 14:32:23 +01:00
ProxMenuxBot
50d07f81fd Update helpers_cache.json 2026-03-06 12:08:42 +00:00
MacRimi
594ee21fcd Update notification_events.py 2026-03-06 12:16:06 +01:00
MacRimi
ea2763c48c Update notification service 2026-03-06 12:06:53 +01:00
MacRimi
925fe1cce0 Update notification service 2026-03-05 21:28:48 +01:00
MacRimi
d927b462b6 Update notification service 2026-03-05 20:44:51 +01:00
MacRimi
5a79556ab2 Update flask_server.py 2026-03-05 19:59:17 +01:00
MacRimi
260870ad8a Update notification service 2026-03-05 19:49:35 +01:00
ProxMenuxBot
7d69e64adc Update helpers_cache.json 2026-03-05 18:34:30 +00:00
MacRimi
5af51096d8 Update notification service 2026-03-05 19:25:05 +01:00
MacRimi
898392725a Update notification service 2026-03-05 17:29:07 +01:00
MacRimi
9089035f18 Update notification service 2026-03-04 19:11:38 +01:00
MacRimi
66d2a68167 Update notification service 2026-03-03 21:12:19 +01:00
MacRimi
4a41e40592 Update notification_channels.py 2026-03-03 20:59:43 +01:00
MacRimi
2a75b920a0 Update notification_channels.py 2026-03-03 20:49:36 +01:00
MacRimi
2851eae423 Update notification_channels.py 2026-03-03 20:21:43 +01:00
MacRimi
efc2295b8d Update notification service 2026-03-03 20:09:38 +01:00
MacRimi
2bee28a1d8 Update notification service 2026-03-03 19:45:54 +01:00
MacRimi
a8cc995558 Update notification service 2026-03-03 19:36:33 +01:00
MacRimi
9a11c41424 Update notification service 2026-03-03 19:19:56 +01:00
MacRimi
2a4d056b59 Update notification service 2026-03-03 18:48:54 +01:00
MacRimi
5a77a398bd Update notification-settings.tsx 2026-03-03 17:50:56 +01:00
MacRimi
4cf2238c99 Update notification-settings.tsx 2026-03-03 17:23:38 +01:00
MacRimi
58df4f1481 update notification service 2026-03-03 16:42:12 +01:00
MacRimi
da3f99a254 Update notification service 2026-03-03 13:40:46 +01:00
ProxMenuxBot
c2fa6095cc Update helpers_cache.json 2026-03-03 12:08:48 +00:00
MacRimi
f0b8ed20a2 update notification service 2026-03-02 23:21:40 +01:00
ProxMenuxBot
0b8b72be5c Update helpers_cache.json 2026-03-02 18:12:26 +00:00
MacRimi
18c6455837 Update notification service 2026-03-02 18:55:02 +01:00
MacRimi
e0477015c4 Update notification-settings.tsx 2026-03-02 18:42:58 +01:00
MacRimi
e99a4e2b08 Update notification-settings.tsx 2026-03-02 18:36:50 +01:00
MacRimi
c44d06b0dc Update notification-settings.tsx 2026-03-02 18:26:23 +01:00
MacRimi
0e8327c085 Update notification-settings.tsx 2026-03-02 18:12:47 +01:00
MacRimi
5c5a86c7fc Update notification-settings.tsx 2026-03-02 18:06:27 +01:00
MacRimi
a785213cb2 Update notification service 2026-03-02 18:01:34 +01:00
MacRimi
e041440c97 Update notification_events.py 2026-03-02 17:27:45 +01:00
MacRimi
688ca8a604 Update notification service 2026-03-02 17:16:22 +01:00
ProxMenuxBot
fd6f0967b0 Update helpers_cache.json 2026-03-02 12:08:55 +00:00
MacRimi
9fe58935c4 Update notification service 2026-03-01 22:56:25 +01:00
MacRimi
0dfb35730f Update notification service 2026-03-01 22:29:58 +01:00
MacRimi
dc52f4c692 Update notification service 2026-03-01 18:44:11 +01:00
MacRimi
bcf5395868 update notification service 2026-03-01 17:24:13 +01:00
MacRimi
3e96a89adf update notification service 2026-02-28 20:32:58 +01:00
MacRimi
c0a882251d Update notification service 2026-02-28 20:22:24 +01:00
MacRimi
6a53b895e5 Update health-status-modal.tsx 2026-02-28 19:40:14 +01:00
MacRimi
c5354d014c Update notification service 2026-02-28 19:18:13 +01:00
MacRimi
5e9ef37646 Update notification service 2026-02-28 18:07:00 +01:00
MacRimi
cb96bea73d Update health_monitor.py 2026-02-28 17:31:03 +01:00
MacRimi
95fa2440ce Update notification service 2026-02-28 17:18:03 +01:00
ProxMenuxBot
ca9698f75d Update helpers_cache.json 2026-02-28 12:05:10 +00:00
MacRimi
0f1413f130 Update notification service 2026-02-28 00:01:01 +01:00
MacRimi
52a4b604dd Update notification service 2026-02-27 23:49:26 +01:00
MacRimi
3c64ee7af2 Update notification service 2026-02-27 23:45:18 +01:00
MacRimi
026719cd88 Update health_monitor.py 2026-02-27 23:28:27 +01:00
MacRimi
9bac00ee29 Update health-status-modal.tsx 2026-02-27 23:11:57 +01:00
MacRimi
828c0f66a6 Update health_monitor.py 2026-02-27 20:07:01 +01:00
MacRimi
9841e92634 Update health_monitor.py 2026-02-27 19:55:02 +01:00
MacRimi
171e7ddcae Update notification service 2026-02-27 19:47:36 +01:00
ProxMenuxBot
968a5bd789 Update helpers_cache.json 2026-02-27 18:10:15 +00:00
MacRimi
be119a69af Update health_monitor.py 2026-02-27 18:42:44 +01:00
MacRimi
800c3c11be Update health_monitor.py 2026-02-27 18:36:51 +01:00
MacRimi
1242da5ed1 Update notification service 2026-02-27 18:27:24 +01:00
MacRimi
8bf4fa0cf1 Update notification service 2026-02-26 21:28:14 +01:00
MacRimi
0693acc07b Update notification service 2026-02-26 20:58:40 +01:00
MacRimi
17eecfca9d Update notification service 2026-02-26 20:31:44 +01:00
MacRimi
4d24d6d17b Update notification service 2026-02-26 18:21:01 +01:00
ProxMenuxBot
1fe4ee5b81 Update helpers_cache.json 2026-02-26 12:12:55 +00:00
ProxMenuxBot
137aeac91a Update helpers_cache.json 2026-02-25 18:21:39 +00:00
ProxMenuxBot
ccb0b58a2d Update helpers_cache.json 2026-02-25 12:11:18 +00:00
MacRimi
ffc202f6a3 Update notification_events.py 2026-02-24 20:11:29 +01:00
MacRimi
f7fd728683 Update notification service 2026-02-24 19:52:27 +01:00
MacRimi
46c04e5a81 Update notification service 2026-02-24 19:27:43 +01:00
MacRimi
f43feb825f Update notification service 2026-02-24 18:20:43 +01:00
MacRimi
05cd21d44e Update notification service 2026-02-24 18:10:12 +01:00
MacRimi
4182af75ff Update notification service 2026-02-24 17:55:03 +01:00
ProxMenuxBot
680123eb64 Update helpers_cache.json 2026-02-24 12:11:48 +00:00
ProxMenuxBot
aec04f0b8c Update helpers_cache.json 2026-02-24 00:15:52 +00:00
ProxMenuxBot
e75bbc0a22 Update helpers_cache.json 2026-02-23 18:20:58 +00:00
ProxMenuxBot
81fc625c5d Update helpers_cache.json 2026-02-23 12:10:39 +00:00
ProxMenuxBot
f85683239f Update helpers_cache.json 2026-02-22 12:05:24 +00:00
MacRimi
507f769357 Update notification service 2026-02-21 22:36:58 +01:00
MacRimi
e3f7e8c97a Update notification service 2026-02-21 22:19:45 +01:00
MacRimi
49e9e26bff Update flask_notification_routes.py 2026-02-21 21:55:37 +01:00
MacRimi
fccd4c12ca Update notification service 2026-02-21 21:36:27 +01:00
MacRimi
06c9ff481e Update flask_notification_routes.py 2026-02-21 21:11:57 +01:00
MacRimi
50e5775062 Update flask_notification_routes.py 2026-02-21 20:55:18 +01:00
MacRimi
91da8db589 Update flask_notification_routes.py 2026-02-21 20:49:59 +01:00
MacRimi
0d854ae42b Update notification service 2026-02-21 20:33:22 +01:00
MacRimi
ec21050fad Update notification service 2026-02-21 19:56:50 +01:00
MacRimi
67c61a5829 Update notification service 2026-02-21 18:47:15 +01:00
MacRimi
e685668959 update notificacion service 2026-02-21 18:13:38 +01:00
MacRimi
de13eb5b96 Update notification service 2026-02-21 17:23:03 +01:00
ProxMenuxBot
c0f54c334e Update helpers_cache.json 2026-02-21 00:16:10 +00:00
MacRimi
f134fcb528 Update notification service 2026-02-20 17:55:05 +01:00
MacRimi
d5954a3a32 Update notification service 2026-02-19 20:51:54 +01:00
MacRimi
bd28e312fc Update notification-settings.tsx 2026-02-19 20:30:11 +01:00
MacRimi
7208d5b2bf Update notification service 2026-02-19 19:56:20 +01:00
ProxMenuxBot
5c2d4e4718 Update helpers_cache.json 2026-02-19 18:16:51 +00:00
MacRimi
8cdeae6c3f Update notification service 2026-02-19 18:37:42 +01:00
MacRimi
e7bc6d09f2 Update flask_notification_routes.py 2026-02-19 17:46:50 +01:00
MacRimi
4ce2699a48 Update notication service 2026-02-19 17:26:36 +01:00
MacRimi
7c5cdb9161 Update notification service 2026-02-19 17:02:02 +01:00
ProxMenuxBot
64a0aa6157 Update helpers_cache.json 2026-02-19 12:11:34 +00:00
MacRimi
34d04e57dd Update notification service 2026-02-18 17:24:26 +01:00
MacRimi
1317c5bddc Update health monitor 2026-02-17 20:01:45 +01:00
MacRimi
74b6f565e9 Update health monitor 2026-02-17 17:57:49 +01:00
MacRimi
08f49d4d0b Update health-status-modal.tsx 2026-02-17 17:24:47 +01:00
MacRimi
99605b6a55 Update health monitor 2026-02-17 17:09:00 +01:00
MacRimi
beeeabc377 Update health monitor 2026-02-17 16:49:26 +01:00
ProxMenuxBot
ff2e40d49a Update helpers_cache.json 2026-02-17 12:10:47 +00:00
MacRimi
31c5eeb6c3 Update health monitor 2026-02-17 11:35:11 +01:00
MacRimi
8004ee48c9 Update health monitor 2026-02-16 22:53:16 +01:00
MacRimi
a1d48a28e9 Update health monitor 2026-02-16 22:26:43 +01:00
MacRimi
0f81f45c5f update health monitor 2026-02-16 22:07:10 +01:00
MacRimi
05f7957557 Update health_monitor.py 2026-02-16 18:39:36 +01:00
MacRimi
1ed8f5d124 Update health monitor 2026-02-16 18:19:29 +01:00
MacRimi
2ee5be7402 Update health monitor 2026-02-16 15:48:41 +01:00
ProxMenuxBot
1226e7bee1 Update helpers_cache.json 2026-02-16 12:10:34 +00:00
MacRimi
dcbc52efc6 Update spinner 2026-02-16 12:11:37 +01:00
MacRimi
92b0a1478a Update log 2026-02-16 11:43:12 +01:00
MacRimi
f27c7fdf31 Update system-logs.tsx 2026-02-16 10:55:26 +01:00
MacRimi
18a427b501 Update logs 2026-02-16 09:52:33 +01:00
MacRimi
b7951b730d Update spinner 2026-02-16 09:48:03 +01:00
ProxMenuxBot
342203bb81 Update helpers_cache.json 2026-02-16 00:16:44 +00:00
ProxMenuxBot
e4bc526a09 Update helpers_cache.json 2026-02-15 18:05:49 +00:00
MacRimi
f75e30afd0 Update security.tsx 2026-02-14 18:34:36 +01:00
MacRimi
9f11238d43 Update security.tsx 2026-02-14 18:21:53 +01:00
MacRimi
070a1b47e5 Update security.tsx 2026-02-14 17:59:44 +01:00
MacRimi
3e8661f5ca Update temperature-detail-modal.tsx 2026-02-14 17:40:09 +01:00
MacRimi
9f8c27ddc1 Update modal 2026-02-14 17:28:35 +01:00
MacRimi
bafaaf9c47 Update security.tsx 2026-02-14 17:01:14 +01:00
MacRimi
1ee5863da7 Update firewall 2026-02-14 16:49:08 +01:00
MacRimi
ace4d83789 Update security.tsx 2026-02-14 16:39:24 +01:00
MacRimi
1da1c178d0 Update security.tsx 2026-02-14 16:08:11 +01:00
MacRimi
c429cb2ed1 Update flask_server.py 2026-02-14 14:32:30 +01:00
MacRimi
40c40f81fc Update flask_auth_routes.py 2026-02-14 12:23:08 +01:00
MacRimi
6647a3b083 Update security.tsx 2026-02-14 12:07:51 +01:00
MacRimi
782eaef440 Update flask_server.py 2026-02-14 11:12:54 +01:00
MacRimi
6003310a39 Update system-overview.tsx 2026-02-13 20:34:09 +01:00
MacRimi
229ac5006b Update temperature-detail-modal.tsx 2026-02-13 20:23:56 +01:00
MacRimi
322687c658 Update modal temperature 2026-02-13 20:09:50 +01:00
MacRimi
fe3963dfe2 Update temperature-detail-modal.tsx 2026-02-13 19:58:57 +01:00
MacRimi
e4a57b97b7 Update modal temperature 2026-02-13 19:40:22 +01:00
MacRimi
9b48c498f5 new modal temperature 2026-02-13 19:07:24 +01:00
MacRimi
4228177920 Update auto_post_install.sh 2026-02-13 18:21:28 +01:00
MacRimi
e98637321d Update auto_post_install.sh 2026-02-13 18:17:32 +01:00
ProxMenuxBot
5941bd4b68 Update helpers_cache.json 2026-02-13 12:08:41 +00:00
MacRimi
a686360c1f Update memory speed 2026-02-13 12:30:27 +01:00
MacRimi
20ee9da1ec Update flask_auth_routes.py 2026-02-13 11:02:53 +01:00
MacRimi
c89baf34a8 Update 2FA 2026-02-13 10:51:27 +01:00
MacRimi
00230d1b8f Update security_manager.py 2026-02-12 20:13:53 +01:00
MacRimi
4396d57e3d Update security_manager.py 2026-02-12 19:43:52 +01:00
MacRimi
86789f677a Update security_manager.py 2026-02-12 19:23:55 +01:00
MacRimi
8fb2deeab0 Update security_manager.py 2026-02-12 19:15:08 +01:00
MacRimi
2099bbe58f Update security_manager.py 2026-02-12 18:58:39 +01:00
ProxMenuxBot
1c95319608 Update helpers_cache.json 2026-02-11 12:14:33 +00:00
ProxMenuxBot
eeea948844 Update helpers_cache.json 2026-02-11 00:20:59 +00:00
MacRimi
c4b1820d08 Update security_manager.py 2026-02-10 19:32:49 +01:00
MacRimi
59cc2741b8 Update security_manager.py 2026-02-10 19:04:04 +01:00
MacRimi
cc34d33090 Update security 2026-02-10 18:28:43 +01:00
ProxMenuxBot
59bb0070e9 Update helpers_cache.json 2026-02-10 12:15:22 +00:00
ProxMenuxBot
ec2206ade0 Update helpers_cache.json 2026-02-09 18:16:40 +00:00
MacRimi
06a3e6b472 Update root access 2026-02-09 18:20:09 +01:00
MacRimi
9108882921 Update security.tsx 2026-02-09 17:18:42 +01:00
ProxMenuxBot
7796f7d3bc Update helpers_cache.json 2026-02-09 12:14:11 +00:00
MacRimi
00a0ae6561 Update security.tsx 2026-02-09 10:55:42 +01:00
MacRimi
6310293190 Update security.tsx 2026-02-08 20:54:04 +01:00
MacRimi
809930df9a Update security.tsx 2026-02-08 20:38:27 +01:00
MacRimi
f1874d4ab1 Update security.tsx 2026-02-08 20:25:20 +01:00
MacRimi
cc0f401855 Update Lynis 2026-02-08 20:00:23 +01:00
MacRimi
42626f3bce Update security_manager.py 2026-02-08 19:37:44 +01:00
MacRimi
22d570b024 Update lynis 2026-02-08 19:24:40 +01:00
MacRimi
6d0a07f212 Update security_manager.py 2026-02-08 18:52:18 +01:00
MacRimi
a512b5a110 Update security 2026-02-08 18:30:18 +01:00
MacRimi
bde3dade14 Update lynis 2026-02-08 17:51:20 +01:00
MacRimi
de2058d966 Update fail2ban 2026-02-08 17:23:53 +01:00
MacRimi
7f191764be Update security 2026-02-08 17:01:49 +01:00
MacRimi
7f9da757aa update firewall 2026-02-08 16:19:48 +01:00
MacRimi
f07e8cfe14 Update security 2026-02-08 13:19:24 +01:00
ProxMenuxBot
b806bf80b1 Update helpers_cache.json 2026-02-08 12:05:47 +00:00
ProxMenuxBot
173ea58701 Update helpers_cache.json 2026-02-08 00:20:03 +00:00
MacRimi
3ad5b72ebf Update flask_server.py 2026-02-07 19:02:08 +01:00
MacRimi
567e2e5d6d Update flask_server.py 2026-02-07 18:53:21 +01:00
MacRimi
616bd0ac91 Backend SSL config manager and API endpoints 2026-02-07 18:36:14 +01:00
MacRimi
108a169e7c Update 2FA 2026-02-07 18:03:46 +01:00
MacRimi
eab902d68e Update Log 2026-02-07 17:37:55 +01:00
MacRimi
985f6e89ec Update modal LXC 2026-02-07 11:45:28 +01:00
MacRimi
0480989fd2 Update virtual-machines.tsx 2026-02-07 11:25:33 +01:00
MacRimi
72ffe420b7 Update virtual-machines.tsx 2026-02-07 10:55:06 +01:00
ProxMenuxBot
775b6ff4fd Update helpers_cache.json 2026-02-07 00:15:38 +00:00
ProxMenuxBot
b0f18461b3 Update helpers_cache.json 2026-02-06 18:13:29 +00:00
ProxMenuxBot
b8ccbfd222 Update helpers_cache.json 2026-02-06 12:09:32 +00:00
ProxMenuxBot
c2fa497137 Update helpers_cache.json 2026-02-05 18:16:47 +00:00
ProxMenuxBot
bdcfa6929c Update helpers_cache.json 2026-02-05 12:09:37 +00:00
ProxMenuxBot
8470b58b60 Update helpers_cache.json 2026-02-04 18:14:29 +00:00
ProxMenuxBot
002413c067 Update helpers_cache.json 2026-02-04 12:08:26 +00:00
ProxMenuxBot
ecce59e734 Update helpers_cache.json 2026-02-04 00:14:14 +00:00
MacRimi
f5d169eaa2 Update flask_server.py 2026-02-03 23:17:44 +01:00
MacRimi
b3b921e1ae Update flask_server.py 2026-02-03 23:02:19 +01:00
MacRimi
91f15b723e Update modal lxc 2026-02-03 22:53:37 +01:00
MacRimi
303dcb1eb6 Update flask_server.py 2026-02-03 22:30:55 +01:00
MacRimi
4eaeb1b020 Create textarea.tsx 2026-02-03 22:18:00 +01:00
MacRimi
f13427ca27 Update modal lxc 2026-02-03 22:10:53 +01:00
MacRimi
458f2cdf16 Update virtual-machines.tsx 2026-02-03 18:29:00 +01:00
MacRimi
df588f25bf Update virtual-machines.tsx 2026-02-03 18:14:40 +01:00
MacRimi
bd0fdff29c Update virtual-machines.tsx 2026-02-03 18:12:37 +01:00
MacRimi
774da61da1 Update virtual-machines.tsx 2026-02-03 18:07:55 +01:00
MacRimi
42e67e01aa Update virtual-machines.tsx 2026-02-03 17:41:34 +01:00
MacRimi
36e201e824 Update virtual-machines.tsx 2026-02-03 17:33:18 +01:00
MacRimi
497233c9f1 Update virtual-machines.tsx 2026-02-03 17:10:19 +01:00
MacRimi
4b7c9a1bd3 Update virtual-machines.tsx 2026-02-03 17:00:43 +01:00
MacRimi
7c2d6d6618 Update virtual-machines.tsx 2026-02-03 16:57:50 +01:00
MacRimi
c44e0afb81 Update virtual-machines.tsx 2026-02-03 16:50:29 +01:00
MacRimi
d3ef3c7452 Update virtual-machines.tsx 2026-02-03 16:45:01 +01:00
ProxMenuxBot
1935c76f30 Update helpers_cache.json 2026-02-02 18:11:22 +00:00
MacRimi
71056d8f15 Update virtual-machines.tsx 2026-02-02 19:01:07 +01:00
MacRimi
8d34119e7a Update modal lxc 2026-02-02 18:49:18 +01:00
MacRimi
f159ee77cd Update modal vm 2026-02-02 17:46:23 +01:00
MacRimi
d336c4f5b7 Update modal vm 2026-02-02 17:29:14 +01:00
ProxMenuxBot
81b7a3e665 Update helpers_cache.json 2026-02-02 12:08:58 +00:00
MacRimi
1870f74f0c Update terminal-panel.tsx 2026-02-01 01:32:20 +01:00
MacRimi
d19f9c6888 Create dropdown-menu.tsx 2026-02-01 01:18:20 +01:00
ProxMenuxBot
a68bf6fc8f Update helpers_cache.json 2026-02-01 00:16:53 +00:00
MacRimi
c2d2745777 Update terminal modal 2026-02-01 01:13:13 +01:00
MacRimi
fc8bf841bf Update lxc-terminal-modal.tsx 2026-02-01 00:50:53 +01:00
MacRimi
08f435597a Update lxc-terminal-modal.tsx 2026-02-01 00:42:51 +01:00
MacRimi
58a4e475ad Update lxc-terminal-modal.tsx 2026-01-31 18:29:19 +01:00
MacRimi
5bfc911e1b Update lxc-terminal-modal.tsx 2026-01-31 18:17:50 +01:00
MacRimi
d2c7362736 Update terminal modal 2026-01-31 18:10:55 +01:00
MacRimi
82cac690fa Update terminal panel 2026-01-31 17:40:08 +01:00
MacRimi
3c3c902087 Update terminal modal 2026-01-31 17:19:50 +01:00
MacRimi
964538eb43 Update lxc-terminal-modal.tsx 2026-01-31 17:02:11 +01:00
MacRimi
5e8b2bdb50 Update terminal panel 2026-01-31 16:48:22 +01:00
MacRimi
e3d10495f3 Update terminal modal 2026-01-31 16:17:36 +01:00
MacRimi
6c3886ad24 Update virtual-machines.tsx 2026-01-31 15:57:59 +01:00
MacRimi
ba727f53c4 Update flask_server.py 2026-01-31 15:53:56 +01:00
MacRimi
35a4737e43 Update node-metrics-charts.tsx 2026-01-31 15:34:11 +01:00
MacRimi
abde8652b2 Update system-overview.tsx 2026-01-31 15:25:15 +01:00
MacRimi
cadef0bf81 Update terminal-panel.tsx 2026-01-31 14:34:26 +01:00
MacRimi
caac696244 Update terminal panel 2026-01-31 12:25:23 +01:00
MacRimi
6910a0b4bd Update intel_gpu_tools.sh 2026-01-31 11:52:34 +01:00
MacRimi
74e2584e4d Update flask_server.py 2026-01-31 11:44:32 +01:00
ProxMenuxBot
459dd2d9c7 Update helpers_cache.json 2026-01-31 00:14:20 +00:00
MacRimi
0f5c83c1c2 Update intel_gpu_tools.sh 2026-01-29 21:26:11 +01:00
ProxMenuxBot
fed4cc2a97 Update helpers_cache.json 2026-01-29 19:56:29 +00:00
MacRimi
c238711b3e Update hardware.tsx 2026-01-29 20:03:39 +01:00
MacRimi
f42334917e Create intel_gpu_tools.sh 2026-01-29 19:59:12 +01:00
MacRimi
09004d4c09 Update share-common.func 2026-01-29 19:54:54 +01:00
MacRimi
68da9b2f69 Create amd_gpu_tools.sh 2026-01-29 19:41:48 +01:00
MacRimi
454ff37a72 Update gpu monitor 2026-01-29 19:04:52 +01:00
MacRimi
ca13d18d7d Update backend monitor 2026-01-29 18:27:36 +01:00
MacRimi
1657a7dbe3 Update hardware_monitor.py 2026-01-29 18:21:23 +01:00
MacRimi
61e925eaab Update hardware_monitor.py 2026-01-29 18:07:49 +01:00
MacRimi
09d3313e15 Update flask_server.py 2026-01-29 17:59:04 +01:00
MacRimi
a20d61037e Update backend monitor 2026-01-29 17:47:10 +01:00
ProxMenuxBot
7eaa692712 Update helpers_cache.json 2026-01-28 18:06:50 +00:00
ProxMenuxBot
691bae9a96 Update helpers_cache.json 2026-01-28 12:05:34 +00:00
ProxMenuxBot
d5a8c9b7d1 Update helpers_cache.json 2026-01-26 18:05:37 +00:00
ProxMenuxBot
8c20e7c661 Update helpers_cache.json 2026-01-25 00:14:01 +00:00
ProxMenuxBot
47a2d28c6a Update helpers_cache.json 2026-01-24 00:12:27 +00:00
ProxMenuxBot
31f8961e27 Update helpers_cache.json 2026-01-23 18:04:28 +00:00
ProxMenuxBot
424bd0bc28 Update helpers_cache.json 2026-01-23 12:05:47 +00:00
ProxMenuxBot
9c078583dd Update helpers_cache.json 2026-01-22 12:05:55 +00:00
ProxMenuxBot
ca27048679 Update helpers_cache.json 2026-01-22 00:13:22 +00:00
MacRimi
4e65663748 Update coral lxc 2026-01-20 20:17:53 +01:00
ProxMenuxBot
c7c5cbde83 Update helpers_cache.json 2026-01-20 00:12:48 +00:00
ProxMenuxBot
a4905ad207 Update helpers_cache.json 2026-01-19 18:04:06 +00:00
MacRimi
bebf0e692a Update License 2026-01-19 17:15:00 +01:00
ProxMenuxBot
8ff9a87dfe Update helpers_cache.json 2026-01-19 12:06:36 +00:00
ProxMenuxBot
62f2d8ac16 Update helpers_cache.json 2026-01-19 00:12:53 +00:00
ProxMenuxBot
8fef2a6232 Update helpers_cache.json 2026-01-18 18:04:16 +00:00
ProxMenuxBot
94064fe78c Update helpers_cache.json 2026-01-17 12:05:12 +00:00
ProxMenuxBot
2ffcc43adc Update helpers_cache.json 2026-01-16 18:05:23 +00:00
ProxMenuxBot
3846fce73a Update helpers_cache.json 2026-01-16 12:05:44 +00:00
ProxMenuxBot
ea950e9dbc Update helpers_cache.json 2026-01-15 18:07:27 +00:00
ProxMenuxBot
f2639c4ff1 Update helpers_cache.json 2026-01-14 18:05:47 +00:00
ProxMenuxBot
32c1798eb8 Update helpers_cache.json 2026-01-13 12:05:29 +00:00
ProxMenuxBot
75e3167b65 Update helpers_cache.json 2026-01-12 18:04:41 +00:00
ProxMenuxBot
ad07a61aa7 Update helpers_cache.json 2026-01-08 18:04:25 +00:00
ProxMenuxBot
c91b6329f3 Update helpers_cache.json 2026-01-07 00:11:18 +00:00
ProxMenuxBot
9cc60efd5a Update helpers_cache.json 2026-01-03 12:04:28 +00:00
ProxMenuxBot
08eeea6b9c Update helpers_cache.json 2026-01-02 12:05:29 +00:00
ProxMenuxBot
8dea7335de Update helpers_cache.json 2025-12-30 18:04:18 +00:00
ProxMenuxBot
2ad6d43422 Update helpers_cache.json 2025-12-29 00:12:44 +00:00
ProxMenuxBot
12c2e7aefb Update helpers_cache.json 2025-12-28 12:04:17 +00:00
ProxMenuxBot
6b62e46950 Update helpers_cache.json 2025-12-28 00:13:42 +00:00
ProxMenuxBot
853c58e0a0 Update helpers_cache.json 2025-12-26 18:03:55 +00:00
ProxMenuxBot
eb0abc425a Update helpers_cache.json 2025-12-26 12:05:50 +00:00
ProxMenuxBot
c808e40bf6 Update helpers_cache.json 2025-12-25 12:29:08 +00:00
ProxMenuxBot
f0bbb14f3f Update helpers_cache.json 2025-12-24 18:20:56 +00:00
ProxMenuxBot
95dd0ea6fb Update helpers_cache.json 2025-12-24 01:06:53 +00:00
ProxMenuxBot
7f34102ae6 Update helpers_cache.json 2025-12-23 01:07:03 +00:00
ProxMenuxBot
7623962da5 Update helpers_cache.json 2025-12-22 01:10:38 +00:00
ProxMenuxBot
cfb34b59df Update helpers_cache.json 2025-12-21 12:25:59 +00:00
ProxMenuxBot
e5004bb55e Update helpers_cache.json 2025-12-20 01:03:45 +00:00
ProxMenuxBot
c0193fdf73 Update helpers_cache.json 2025-12-19 01:08:01 +00:00
ProxMenuxBot
6cbafd557c Update helpers_cache.json 2025-12-16 12:30:30 +00:00
ProxMenuxBot
ee8ab75907 Update helpers_cache.json 2025-12-15 12:32:47 +00:00
MacRimi
f2e93ad69e Update route.ts 2025-12-14 02:37:02 +01:00
MacRimi
5faf3fd61c Update route.ts 2025-12-14 02:20:07 +01:00
MacRimi
956a8f4864 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-12-14 02:12:40 +01:00
MacRimi
d26bc56b5c Update route.ts 2025-12-14 02:12:26 +01:00
ProxMenuxBot
7457770ef8 Update helpers_cache.json 2025-12-14 01:11:59 +00:00
MacRimi
54af9073cb Update web 2025-12-14 01:57:36 +01:00
MacRimi
a8dcf5e8f5 Update web 2025-12-14 01:38:07 +01:00
MacRimi
9e3334d75f Update web 2025-12-13 21:19:08 +01:00
MacRimi
cca6e71911 Update web 2025-12-13 20:20:21 +01:00
ProxMenuxBot
7fbd377ab2 Update helpers_cache.json 2025-12-13 18:18:09 +00:00
MacRimi
24417feba3 Update next.config.mjs 2025-12-13 19:09:26 +01:00
ProxMenuxBot
f8c24964e3 Update helpers_cache.json 2025-12-13 01:02:58 +00:00
MacRimi
1ae2ebfaf0 Update nvidia_installer.sh 2025-12-11 22:07:01 +01:00
MacRimi
4feea6d153 Update script-terminal-modal.tsx 2025-12-11 21:30:30 +01:00
MacRimi
ec6b658685 Update script-terminal-modal.tsx 2025-12-11 21:22:15 +01:00
MacRimi
fb0f05a08d Update script-terminal-modal.tsx 2025-12-11 19:56:20 +01:00
MacRimi
11bc477f1f Update script-terminal-modal.tsx 2025-12-11 19:06:53 +01:00
MacRimi
9760375855 Update script-terminal-modal.tsx 2025-12-11 18:44:56 +01:00
MacRimi
a6e20bd9f0 Update script-terminal-modal.tsx 2025-12-11 18:27:11 +01:00
MacRimi
90fedbf9a2 Update script-terminal-modal.tsx 2025-12-11 18:19:38 +01:00
MacRimi
eb03262abc Update script-terminal-modal.tsx 2025-12-11 18:13:37 +01:00
MacRimi
59eb6e5f1b Update script-terminal-modal.tsx 2025-12-11 18:06:24 +01:00
MacRimi
edf513aca9 Update script-terminal-modal.tsx 2025-12-11 17:55:19 +01:00
MacRimi
efed63519a Update script-terminal-modal.tsx 2025-12-11 17:44:23 +01:00
MacRimi
d78f781506 Update script-terminal-modal.tsx 2025-12-11 17:30:12 +01:00
ProxMenuxBot
93fe269b09 Update helpers_cache.json 2025-12-11 12:31:23 +00:00
ProxMenuxBot
8cad6c4e56 Update helpers_cache.json 2025-12-11 01:07:29 +00:00
MacRimi
f92049dc71 Update script-terminal-modal.tsx 2025-12-10 23:48:43 +01:00
MacRimi
a3497a9d39 Update script-terminal-modal.tsx 2025-12-10 23:43:05 +01:00
MacRimi
bfc0a2ed57 Update script-terminal-modal.tsx 2025-12-10 23:34:30 +01:00
MacRimi
c49b45d262 Update script-terminal-modal.tsx 2025-12-10 23:26:11 +01:00
MacRimi
15678cf96a Update script-terminal-modal.tsx 2025-12-10 23:15:50 +01:00
MacRimi
feeaaa7f2b Update script-terminal-modal.tsx 2025-12-10 23:06:03 +01:00
MacRimi
50df1a2212 Update script-terminal-modal.tsx 2025-12-10 22:57:19 +01:00
MacRimi
ac9254d049 Update script-terminal-modal.tsx 2025-12-10 22:48:07 +01:00
MacRimi
e15eeb36a5 Update script-terminal-modal.tsx 2025-12-10 22:30:11 +01:00
MacRimi
e275e03d4e Update script-terminal-modal.tsx 2025-12-10 22:19:41 +01:00
MacRimi
41c8826ca8 Update script-terminal-modal.tsx 2025-12-10 22:10:41 +01:00
MacRimi
d8af31ba5b Update script-terminal-modal.tsx 2025-12-10 21:57:16 +01:00
MacRimi
2eb7cb1687 Update AppImage 2025-12-10 21:47:52 +01:00
MacRimi
207e75f5b9 Update script-terminal-modal.tsx 2025-12-10 20:17:13 +01:00
MacRimi
7b9e1a71a3 Update script-terminal-modal.tsx 2025-12-10 20:12:03 +01:00
MacRimi
345838c6ce Update script-terminal-modal.tsx 2025-12-10 19:54:39 +01:00
MacRimi
b02a60f4b3 Update script-terminal-modal.tsx 2025-12-10 19:44:39 +01:00
MacRimi
ecd3a4e490 Update script-terminal-modal.tsx 2025-12-10 19:26:19 +01:00
MacRimi
8c0c9bd60a Update script-terminal-modal.tsx 2025-12-10 19:14:57 +01:00
MacRimi
943a8bf02d Update script-terminal-modal.tsx 2025-12-10 18:54:35 +01:00
MacRimi
d3beb72652 Update script-terminal-modal.tsx 2025-12-10 18:36:31 +01:00
MacRimi
c62dd2014e Update script-terminal-modal.tsx 2025-12-10 18:22:30 +01:00
MacRimi
62fee7827b Update script-terminal-modal.tsx 2025-12-10 18:15:45 +01:00
MacRimi
80b9d16494 Update AppImage 2025-12-10 18:09:21 +01:00
MacRimi
cb5581c49f Update AppImage 2025-12-10 17:56:11 +01:00
MacRimi
0098000ae0 Update terminal-panel.tsx 2025-12-10 17:45:38 +01:00
MacRimi
ddc8429499 Update AppImage 2025-12-10 17:35:43 +01:00
MacRimi
0424961d46 Update AppImage 2025-12-10 17:08:41 +01:00
MacRimi
cbf510cfd1 Update script-terminal-modal.tsx 2025-12-10 17:01:17 +01:00
MacRimi
cbb44ae253 Update script-terminal-modal.tsx 2025-12-10 16:56:56 +01:00
MacRimi
4dd4f045aa Update AppImage 2025-12-10 16:50:52 +01:00
ProxMenuxBot
ab0d7f8dc6 Update helpers_cache.json 2025-12-09 12:29:33 +00:00
MacRimi
69f93fcb59 Actualizar select_nas_iso.sh 2025-12-08 22:52:34 +01:00
ProxMenuxBot
de68e0d7c2 Update helpers_cache.json 2025-12-08 01:05:59 +00:00
MacRimi
cdbcb451e1 Update script-terminal-modal.tsx 2025-12-06 23:41:34 +01:00
MacRimi
105c543a98 Update script-terminal-modal.tsx 2025-12-06 23:25:35 +01:00
MacRimi
ab421e3184 Update AppImage 2025-12-06 23:06:18 +01:00
MacRimi
d76b7a99b8 Apdate AppImage 2025-12-06 22:40:24 +01:00
MacRimi
e8dae63e05 Update script-terminal-modal.tsx 2025-12-06 22:33:17 +01:00
MacRimi
ea58b70435 Update script-terminal-modal.tsx 2025-12-06 22:26:42 +01:00
MacRimi
f90f6f364a Update script-terminal-modal.tsx 2025-12-06 22:20:34 +01:00
MacRimi
7fc967c64c Update script-terminal-modal.tsx 2025-12-06 22:09:54 +01:00
MacRimi
8969a229d1 Update script-terminal-modal.tsx 2025-12-06 22:03:12 +01:00
MacRimi
9601e0428e Update script-terminal-modal.tsx 2025-12-06 21:55:31 +01:00
MacRimi
94fd91ce4a Update script-terminal-modal.tsx 2025-12-06 21:50:15 +01:00
MacRimi
310f972c7f Update script-terminal-modal.tsx 2025-12-06 21:37:43 +01:00
MacRimi
4378a5843c Update AppImage 2025-12-06 21:30:17 +01:00
MacRimi
9bd403ec51 Update script-terminal-modal.tsx 2025-12-06 21:20:23 +01:00
MacRimi
2f53786ca9 Update terminal-panel.tsx 2025-12-06 20:56:36 +01:00
MacRimi
07ed213c94 Update script-terminal-modal.tsx 2025-12-06 20:46:13 +01:00
MacRimi
05a2eca9a7 Update AppImage 2025-12-06 20:27:00 +01:00
MacRimi
d30c836d04 Update AppImage 2025-12-06 20:10:24 +01:00
MacRimi
8c623adad8 Update AppImage 2025-12-06 19:43:41 +01:00
MacRimi
5191edfc0c Update AppImage 2025-12-06 19:31:07 +01:00
MacRimi
ff99663d5c Update AppImage 2025-12-06 19:19:54 +01:00
MacRimi
360335a608 Update AppImage 2025-12-06 19:03:19 +01:00
MacRimi
1c83e5eeab Update AppImage 2025-12-06 18:36:34 +01:00
MacRimi
122ebb12f4 Update AppImage 2025-12-06 13:54:37 +01:00
MacRimi
fed242315d Update AppImage 2025-12-06 13:48:46 +01:00
MacRimi
84e8e18ef8 Update script-terminal-modal.tsx 2025-12-06 13:38:19 +01:00
MacRimi
36a1916b5f Update script-terminal-modal.tsx 2025-12-06 13:28:56 +01:00
MacRimi
a1089460d7 Update AppImage 2025-12-06 13:11:04 +01:00
MacRimi
c62f0dea6f Update script-terminal-modal.tsx 2025-12-06 13:00:29 +01:00
MacRimi
a6c121dc33 Update AppImage 2025-12-06 12:46:41 +01:00
MacRimi
c627c65a7d Update AppImage 2025-12-06 12:25:57 +01:00
MacRimi
72006aff21 Update hardware.tsx 2025-12-06 12:14:08 +01:00
MacRimi
68338ebeff Update flask_terminal_routes.py 2025-12-06 12:06:12 +01:00
MacRimi
49b8503b64 Update AppImage 2025-12-06 11:54:36 +01:00
MacRimi
0fc41df7e7 Update flask_terminal_routes.py 2025-12-06 11:37:16 +01:00
MacRimi
bb82c52747 Update AppImage 2025-12-06 11:30:49 +01:00
MacRimi
a79367fb1c Update script-terminal-modal.tsx 2025-12-06 11:25:27 +01:00
MacRimi
4dbc6db6f0 Update script-terminal-modal.tsx 2025-12-06 11:19:26 +01:00
ProxMenuxBot
a50cee62be Update helpers_cache.json 2025-12-05 18:18:53 +00:00
ProxMenuxBot
382aa5cb16 Update helpers_cache.json 2025-12-05 01:05:55 +00:00
ProxMenuxBot
d1c2ff277b Update helpers_cache.json 2025-12-03 01:05:21 +00:00
ProxMenuxBot
92b08b5550 Update helpers_cache.json 2025-12-02 12:30:00 +00:00
ProxMenuxBot
da85470fef Update helpers_cache.json 2025-12-02 01:04:45 +00:00
MacRimi
9d1e7d94cc Update script-terminal-modal.tsx 2025-12-01 01:40:04 +01:00
MacRimi
65438286ec Update script-terminal-modal.tsx 2025-12-01 01:26:29 +01:00
MacRimi
ffa7d27148 Update script-terminal-modal.tsx 2025-12-01 01:22:04 +01:00
MacRimi
4a7d951d0d Update script-terminal-modal.tsx 2025-12-01 01:15:19 +01:00
MacRimi
89f1911a6e Update package.json 2025-12-01 01:07:41 +01:00
MacRimi
b990bd1792 Update AppImage 2025-12-01 01:04:31 +01:00
MacRimi
88667416d8 Update hybrid-script-monitor.tsx 2025-12-01 00:38:42 +01:00
MacRimi
216491012e Update hybrid-script-monitor.tsx 2025-12-01 00:31:23 +01:00
MacRimi
c88f3dcf75 Update flask_script_runner.py 2025-12-01 00:17:04 +01:00
MacRimi
6c3e21339d Update hybrid-script-monitor.tsx 2025-11-30 23:57:08 +01:00
MacRimi
e7f9f9f13d Update hybrid-script-monitor.tsx 2025-11-30 23:47:33 +01:00
MacRimi
6b8d6da5be Update AppImage 2025-11-30 23:40:13 +01:00
MacRimi
8c73c5d662 Update hybrid-script-monitor.tsx 2025-11-30 23:27:32 +01:00
MacRimi
f7dc2c9a9e Update hybrid-script-monitor.tsx 2025-11-30 23:20:34 +01:00
MacRimi
eadf825b67 Update hybrid-script-monitor.tsx 2025-11-30 23:13:37 +01:00
MacRimi
150999d71b Update AppImage 2025-11-30 23:00:02 +01:00
MacRimi
7cd89a594e Update AppImage 2025-11-30 22:50:40 +01:00
MacRimi
b67f1cb4b8 Update hardware.tsx 2025-11-30 22:12:51 +01:00
MacRimi
4678f8c7da Update hardware.tsx 2025-11-30 21:55:37 +01:00
MacRimi
0577f48437 Update hardware.tsx 2025-11-30 21:49:13 +01:00
MacRimi
0c079482f0 Update hardware.tsx 2025-11-30 21:38:49 +01:00
MacRimi
684fe3945d Update AppImage 2025-11-30 21:19:38 +01:00
MacRimi
d91d325744 Update route.ts 2025-11-30 20:36:25 +01:00
MacRimi
040d7564ed Update AppImage 2025-11-30 20:25:44 +01:00
MacRimi
d1db34445e Update AppImage 2025-11-30 19:40:42 +01:00
MacRimi
9639dd422a Update AppImage 2025-11-30 19:15:07 +01:00
MacRimi
f60bfe8c54 Update nvidia installer 2025-11-30 18:29:05 +01:00
MacRimi
fe53c11447 Update utils.sh 2025-11-30 18:05:56 +01:00
MacRimi
9bd17bdf6f Update utils.sh 2025-11-30 17:47:30 +01:00
MacRimi
4b64308951 Update utils.sh 2025-11-30 17:12:11 +01:00
MacRimi
bb7dacea91 Update AppImage 2025-11-30 16:02:44 +01:00
MacRimi
0a369621a3 Update nvidia_installer.sh 2025-11-30 15:41:40 +01:00
MacRimi
e0ee1a50ae new script nvidia 2025-11-30 15:31:19 +01:00
MacRimi
6b49fc4294 Update utils.sh 2025-11-30 12:57:29 +01:00
ProxMenuxBot
ed20ea6af4 Update helpers_cache.json 2025-11-30 01:11:38 +00:00
ProxMenuxBot
73fe4dc7a0 Update helpers_cache.json 2025-11-29 18:18:19 +00:00
MacRimi
c4967de530 Update utils.sh 2025-11-29 11:04:28 +01:00
ProxMenuxBot
bcf3d36ba1 Update helpers_cache.json 2025-11-28 18:19:11 +00:00
MacRimi
d52bd7f012 Update update-pve9_2.sh 2025-11-28 19:10:26 +01:00
MacRimi
e6232be244 Update update-pve9_2.sh 2025-11-28 17:31:32 +01:00
MacRimi
b33f313e2e Update update-pve9_2.sh 2025-11-28 17:06:59 +01:00
ProxMenuxBot
0b4372fe88 Update helpers_cache.json 2025-11-27 18:19:16 +00:00
MacRimi
4e07c7f2dc Update system-overview.tsx 2025-11-27 18:11:56 +01:00
MacRimi
941e194df3 Update system-overview.tsx 2025-11-27 17:50:26 +01:00
MacRimi
2b8f94f457 Update health_monitor.py 2025-11-27 17:30:19 +01:00
MacRimi
7ec8c0cea5 Update health_monitor.py 2025-11-27 14:56:12 +01:00
MacRimi
c69384dabd Update health_monitor.py 2025-11-27 13:39:02 +01:00
MacRimi
8c92216a1d Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-11-27 13:29:27 +01:00
MacRimi
41537c0bad Update health_monitor.py 2025-11-27 13:29:15 +01:00
ProxMenuxBot
c112f56b37 Update helpers_cache.json 2025-11-27 12:28:29 +00:00
MacRimi
f22de50527 Update flask_server.py 2025-11-27 12:45:57 +01:00
MacRimi
a22e08f39d Update AppImage 2025-11-27 12:34:51 +01:00
MacRimi
210d470473 Update AppImage 2025-11-27 12:17:52 +01:00
MacRimi
0eebb77438 Update AppImage 2025-11-27 11:58:20 +01:00
MacRimi
f819cb9c5f Update hardware.tsx 2025-11-27 09:32:41 +01:00
MacRimi
240963f1f3 Update hardware.tsx 2025-11-27 09:29:12 +01:00
MacRimi
16819d98fa Update AppImage 2025-11-27 09:19:45 +01:00
MacRimi
8be7e0f0cb Update hardware.tsx 2025-11-26 21:33:25 +01:00
MacRimi
3a51daf51b Update hardware.tsx 2025-11-26 21:27:56 +01:00
MacRimi
7622e72b70 Update flask_server.py 2025-11-26 21:15:35 +01:00
MacRimi
b59173cac4 Update flask_server.py 2025-11-26 20:46:56 +01:00
MacRimi
18411ee5bd Update AppImage 2025-11-26 20:31:09 +01:00
MacRimi
6e1c6fab2d Update flask_server.py 2025-11-26 20:22:10 +01:00
MacRimi
98eb2d8836 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-11-26 19:38:28 +01:00
MacRimi
504e32f922 Update flask_server.py 2025-11-26 19:38:24 +01:00
ProxMenuxBot
c096054b1f Update helpers_cache.json 2025-11-26 18:17:30 +00:00
MacRimi
ac2f198851 Update flask_server.py 2025-11-26 18:57:01 +01:00
MacRimi
9aed659f17 Update AppImage 2025-11-26 18:44:37 +01:00
MacRimi
0b8f5d3b22 Update AppImage 2025-11-26 18:00:01 +01:00
MacRimi
55c74e8891 Update AppImage 2025-11-26 17:36:23 +01:00
MacRimi
3a49aa6a67 Update hardware_monitor.py 2025-11-26 16:48:24 +01:00
MacRimi
10770b6fe1 Update AppImage 2025-11-26 12:27:25 +01:00
MacRimi
c81ea08f42 Update terminal-panel.tsx 2025-11-25 22:54:27 +01:00
MacRimi
73b6ab4a18 Update terminal-panel.tsx 2025-11-25 22:42:14 +01:00
MacRimi
7497235d7b Update terminal-panel.tsx 2025-11-25 22:35:23 +01:00
MacRimi
27191e4234 Update terminal-panel.tsx 2025-11-25 22:23:02 +01:00
MacRimi
7b0110ce42 Update AppImage 2025-11-25 19:44:40 +01:00
MacRimi
117a635a1e Update sidebar.tsx 2025-11-25 19:31:22 +01:00
MacRimi
98c922fb3e Update AppImage 2025-11-25 19:26:50 +01:00
MacRimi
bf84d04f1f Update AppImage 2025-11-25 19:08:00 +01:00
MacRimi
f4e358b509 Update AppImge 2025-11-25 19:04:54 +01:00
MacRimi
060ad7966e Update proxmox-dashboard.tsx 2025-11-25 17:21:29 +01:00
MacRimi
f0301fd1a4 Update AppImage 2025-11-25 17:21:20 +01:00
MacRimi
ae8212a51d Update terminal-panel.tsx 2025-11-24 23:54:46 +01:00
MacRimi
393a0d5cdc Update terminal-panel.tsx 2025-11-24 23:26:36 +01:00
MacRimi
4cf43a8d74 Update terminal-panel.tsx 2025-11-24 23:10:50 +01:00
MacRimi
74b2f47e3a Update terminal-panel.tsx 2025-11-24 22:57:09 +01:00
ProxMenuxBot
1e727db09a Update helpers_cache.json 2025-11-24 18:21:47 +00:00
MacRimi
1daa120d06 Update terminal-panel.tsx 2025-11-24 19:11:11 +01:00
MacRimi
a1d2445ae6 Update terminal-panel.tsx 2025-11-24 19:01:15 +01:00
MacRimi
4d4e35e24b Update terminal-panel.tsx 2025-11-24 18:41:42 +01:00
MacRimi
400cc599e3 Update terminal-panel.tsx 2025-11-24 18:28:06 +01:00
MacRimi
e55352346b Update terminal-panel.tsx 2025-11-24 18:18:10 +01:00
MacRimi
cca226dec0 Update terminal-panel.tsx 2025-11-24 17:50:41 +01:00
MacRimi
fec95c91f8 Update terminal-panel.tsx 2025-11-24 17:29:38 +01:00
MacRimi
9955418a8e Remove 'Contributing' section from README 2025-11-24 15:43:10 +01:00
MacRimi
90c7539956 Remove contributing and development setup sections
Removed the contributing section and development setup instructions from the README.
2025-11-24 15:41:52 +01:00
MacRimi
a751e45602 Update README.md 2025-11-24 15:40:49 +01:00
MacRimi
b50d388f9e Update terminal-panel.tsx 2025-11-24 13:37:17 +01:00
MacRimi
fd60292b5d Update terminal-panel.tsx 2025-11-24 13:25:20 +01:00
MacRimi
4ebb0c432e Update terminal-panel.tsx 2025-11-24 13:16:35 +01:00
MacRimi
897b2478e8 Update terminal-panel.tsx 2025-11-24 13:02:04 +01:00
MacRimi
b8ebb7f6c4 Update terminal-panel.tsx 2025-11-24 12:24:16 +01:00
MacRimi
f32dba72b4 Update terminal-panel.tsx 2025-11-24 12:10:07 +01:00
MacRimi
498ad280e0 Update terminal-panel.tsx 2025-11-24 11:49:20 +01:00
MacRimi
32358de718 Update AppImage 2025-11-24 11:37:00 +01:00
MacRimi
2474a6ce01 Update terminal-panel.tsx 2025-11-24 11:21:50 +01:00
MacRimi
1ba45200ee Update AppImage 2025-11-24 11:01:48 +01:00
MacRimi
da793856ce Update terminal-panel.tsx 2025-11-23 23:46:34 +01:00
MacRimi
d950588c36 Update terminal-panel.tsx 2025-11-23 23:42:19 +01:00
MacRimi
2b4a5d2ce7 Update terminal-panel.tsx 2025-11-23 23:29:49 +01:00
MacRimi
86daedc802 Update AppImage 2025-11-23 23:23:59 +01:00
MacRimi
3788487196 Update terminal-panel.tsx 2025-11-23 23:10:23 +01:00
MacRimi
25559b7e3e Update terminal-panel.tsx 2025-11-23 22:51:37 +01:00
MacRimi
246db33ee6 Update terminal-panel.tsx 2025-11-23 22:47:30 +01:00
MacRimi
d435e9b58b Update terminal-panel.tsx 2025-11-23 22:45:09 +01:00
MacRimi
09ecc79050 Update terminal-panel.tsx 2025-11-23 22:39:46 +01:00
MacRimi
1914435707 Update terminal-panel.tsx 2025-11-23 22:34:50 +01:00
MacRimi
f6c237afc5 Update terminal-panel.tsx 2025-11-23 22:30:14 +01:00
MacRimi
a1f2579047 Update terminal-panel.tsx 2025-11-23 22:25:21 +01:00
MacRimi
1ea6617a5d Update terminal-panel.tsx 2025-11-23 22:21:24 +01:00
MacRimi
489175aa45 Update AppImage 2025-11-23 22:17:45 +01:00
MacRimi
cb72f43b03 Update globals.css 2025-11-23 22:01:50 +01:00
MacRimi
4bbbcc7c39 Update terminal-panel.tsx 2025-11-23 21:43:17 +01:00
MacRimi
af1e4884b7 Update AppImage 2025-11-23 21:39:45 +01:00
MacRimi
5213d6255a Update globals.css 2025-11-23 21:32:46 +01:00
MacRimi
a9af689aa5 Update terminal-panel.tsx 2025-11-23 21:22:52 +01:00
MacRimi
407a9f7780 Update terminal-panel.tsx 2025-11-23 21:15:54 +01:00
MacRimi
a0ca667ca7 Update terminal-panel.tsx 2025-11-23 21:08:14 +01:00
MacRimi
c2f6f97c34 Update terminal-panel.tsx 2025-11-23 20:59:50 +01:00
MacRimi
2daefbe2f4 Update terminal-panel.tsx 2025-11-23 20:47:42 +01:00
MacRimi
84b0c9d4b7 Update terminal-panel.tsx 2025-11-23 20:26:26 +01:00
MacRimi
0d848569f0 Update terminal-panel.tsx 2025-11-23 20:19:07 +01:00
MacRimi
611f8397ca Update terminal-panel.tsx 2025-11-23 20:16:05 +01:00
MacRimi
11ed0a1367 Update terminal-panel.tsx 2025-11-23 20:06:33 +01:00
MacRimi
ff51966fbb Update terminal-panel.tsx 2025-11-23 19:58:31 +01:00
MacRimi
5491d51eba Update terminal-panel.tsx 2025-11-23 19:53:39 +01:00
MacRimi
61a5a7e929 Update terminal-panel.tsx 2025-11-23 19:46:57 +01:00
MacRimi
3de000bc94 Update terminal-panel.tsx 2025-11-23 19:41:36 +01:00
MacRimi
ef456e6ea0 Update terminal-panel.tsx 2025-11-23 19:30:01 +01:00
MacRimi
2a8b67e22a Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-11-23 19:25:58 +01:00
MacRimi
c35b66f6e1 Update terminal-panel.tsx 2025-11-23 19:25:49 +01:00
ProxMenuxBot
c8348dcaaa Update helpers_cache.json 2025-11-23 18:18:06 +00:00
MacRimi
e38174110e Update terminal-panel.tsx 2025-11-23 19:14:18 +01:00
MacRimi
a95130c01f Update terminal-panel.tsx 2025-11-23 19:10:22 +01:00
MacRimi
0e93417090 Update terminal-panel.tsx 2025-11-23 18:56:14 +01:00
MacRimi
07054bf55a Update terminal-panel.tsx 2025-11-23 18:51:46 +01:00
MacRimi
368eab476a Update AppImage 2025-11-23 18:41:58 +01:00
MacRimi
996679a2d2 Update AppImage 2025-11-23 18:34:47 +01:00
MacRimi
85a6943cd5 Update terminal-panel.tsx 2025-11-23 18:17:00 +01:00
MacRimi
0b96893f3b Update terminal-panel.tsx 2025-11-23 18:12:09 +01:00
MacRimi
846e2e27ba Update terminal-panel.tsx 2025-11-23 17:57:46 +01:00
MacRimi
43ea9b7696 Update globals.css 2025-11-23 17:42:00 +01:00
MacRimi
9dd4df2ca9 Update terminal-panel.tsx 2025-11-23 17:29:11 +01:00
MacRimi
2b4fb55526 Update globals.css 2025-11-23 17:17:26 +01:00
MacRimi
72cf16301f Update AppImage 2025-11-23 17:09:25 +01:00
MacRimi
c512dde028 Update terminal-panel.tsx 2025-11-23 16:56:41 +01:00
MacRimi
1e13c7ab31 Update globals.css 2025-11-23 16:48:32 +01:00
MacRimi
cdbab86dee Update AppImage 2025-11-23 16:32:01 +01:00
MacRimi
fec03d1fd4 Update AppImage 2025-11-23 16:10:41 +01:00
MacRimi
6aa24e23c0 Update terminal-panel.tsx 2025-11-23 14:16:21 +01:00
MacRimi
78770d1da5 Update terminal-panel.tsx 2025-11-23 14:04:43 +01:00
MacRimi
6f72447e2e Update terminal-panel.tsx 2025-11-23 13:52:11 +01:00
MacRimi
cb75a15a6f Update terminal-panel.tsx 2025-11-23 13:47:40 +01:00
MacRimi
c3555237b3 Update terminal-panel.tsx 2025-11-23 13:39:54 +01:00
MacRimi
e4a2cc7ac8 Update terminal-panel.tsx 2025-11-23 13:28:30 +01:00
MacRimi
3900d305b9 Update terminal-panel.tsx 2025-11-23 12:15:55 +01:00
MacRimi
cb3d501649 Update terminal-panel.tsx 2025-11-23 12:02:34 +01:00
MacRimi
28323a486a Update terminal-panel.tsx 2025-11-23 11:56:38 +01:00
MacRimi
dfcad4b9fd Update terminal-panel.tsx 2025-11-23 11:51:51 +01:00
MacRimi
6fb2869cd8 Update terminal-panel.tsx 2025-11-23 11:42:30 +01:00
MacRimi
e764e39ba9 Update terminal-panel.tsx 2025-11-23 11:21:17 +01:00
MacRimi
128077dcbc Update AppImage 2025-11-23 10:57:28 +01:00
MacRimi
1c51107f1e Update AppImage 2025-11-22 23:59:55 +01:00
MacRimi
d154cab054 Update proxmox-dashboard.tsx 2025-11-22 23:55:10 +01:00
MacRimi
7ed4368d5b Update appImage 2025-11-22 23:46:43 +01:00
MacRimi
ee64df2376 Update AppImage 2025-11-22 23:34:09 +01:00
MacRimi
b13f03eb97 Update terminal-panel.tsx 2025-11-22 23:19:10 +01:00
MacRimi
8d20829428 Update AppImage 2025-11-22 23:08:11 +01:00
MacRimi
97401f609e Update terminal-panel.tsx 2025-11-22 22:57:49 +01:00
MacRimi
fe074729ea Update AppImage 2025-11-22 22:50:23 +01:00
MacRimi
db5141e010 Update AppImage 2025-11-22 22:28:34 +01:00
MacRimi
4564fdc6aa Update AppImage 2025-11-22 22:15:39 +01:00
MacRimi
a477b36a57 Update terminal-panel.tsx 2025-11-22 22:06:07 +01:00
MacRimi
3b8ae2c879 Update terminal-panel.tsx 2025-11-22 21:58:29 +01:00
MacRimi
ebe3a51398 Update appImage 2025-11-22 21:43:14 +01:00
MacRimi
76d22f0cb5 Update AppImage 2025-11-22 21:35:22 +01:00
MacRimi
c61d676dfb Update terminal-panel.tsx 2025-11-22 21:26:35 +01:00
MacRimi
b1913e7204 Update terminal-panel.tsx 2025-11-22 21:12:05 +01:00
MacRimi
b6609e0a14 Update AppImage 2025-11-22 21:06:44 +01:00
MacRimi
55fa759344 Update AppImage 2025-11-22 20:58:06 +01:00
MacRimi
8992a713cc Update AppImage 2025-11-22 20:50:05 +01:00
MacRimi
c55dcec252 Update AppImage 2025-11-22 20:41:36 +01:00
MacRimi
e3dd6cbef5 Update AppImage 2025-11-22 20:27:41 +01:00
MacRimi
dd3e5ea368 Update globals.css 2025-11-22 20:20:57 +01:00
MacRimi
ac2e77e0d6 Update globals.css 2025-11-22 20:17:10 +01:00
MacRimi
9f57622f54 Update globals.css 2025-11-22 20:13:02 +01:00
MacRimi
cfed460eba Update globals.css 2025-11-22 20:09:36 +01:00
MacRimi
06f97b671f Update globals.css 2025-11-22 20:06:43 +01:00
MacRimi
aebf83d735 Update AppImage 2025-11-22 20:02:42 +01:00
MacRimi
31894dd117 Update globals.css 2025-11-22 19:44:00 +01:00
MacRimi
e041d802ec Update globals.css 2025-11-22 19:37:31 +01:00
MacRimi
82ea15388c Update globals.css 2025-11-22 19:33:58 +01:00
MacRimi
bf9ed8ff00 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-11-22 19:27:57 +01:00
MacRimi
c02606df6a Update Appimage 2025-11-22 19:27:45 +01:00
ProxMenuxBot
7372e2e385 Update helpers_cache.json 2025-11-22 18:17:41 +00:00
MacRimi
ba86fa6d3e Update terminal-panel.tsx 2025-11-22 19:07:27 +01:00
MacRimi
0e434cbd1c Update AppImage 2025-11-22 18:15:12 +01:00
MacRimi
c89300022a Update AppImage 2025-11-22 17:54:30 +01:00
MacRimi
1300756d6f Update AppImage 2025-11-22 17:40:47 +01:00
MacRimi
c4ad02ff92 Update terminal-panel.tsx 2025-11-22 17:32:47 +01:00
MacRimi
b3f47f140a Update AppImage 2025-11-22 17:16:18 +01:00
MacRimi
2206b3d5b5 Update terminal-panel.tsx 2025-11-22 11:56:29 +01:00
MacRimi
b08f8a450d Update terminal-panel.tsx 2025-11-22 11:43:17 +01:00
MacRimi
37c8be8a6e Update terminal-panel.tsx 2025-11-22 11:21:11 +01:00
MacRimi
ae58c265a0 Update AppImage 2025-11-22 11:04:21 +01:00
MacRimi
54e6d1aa16 Update AppImage 2025-11-22 10:33:35 +01:00
MacRimi
4ddb5f14d9 Update flask_terminal_routes.py 2025-11-21 20:12:07 +01:00
MacRimi
623aec495b Update proxmox-dashboard.tsx 2025-11-21 19:56:22 +01:00
MacRimi
f6d2b9bad0 Update AppImage 2025-11-21 19:49:42 +01:00
MacRimi
08b5a278f3 Update proxmox-dashboard.tsx 2025-11-21 19:44:15 +01:00
MacRimi
f62b30b50d Update AppImage 2025-11-21 19:32:27 +01:00
MacRimi
50e3b8e7d4 Update terminal-panel.tsx 2025-11-21 19:25:23 +01:00
MacRimi
e26956dbe8 Update terminal-panel.tsx 2025-11-21 19:15:35 +01:00
MacRimi
cff2c12d70 Update build_appimage.sh 2025-11-21 18:53:30 +01:00
MacRimi
5781d532a4 Update build_appimage.sh 2025-11-21 18:47:56 +01:00
MacRimi
f161a593f8 Update page.tsx 2025-11-21 18:40:19 +01:00
MacRimi
5725d5a2fe Update AppImage 2025-11-21 18:36:09 +01:00
MacRimi
23280fd97b Update AppImage 2025-11-21 18:32:10 +01:00
MacRimi
fe6679f16a Update update-pve9_2.sh 2025-11-21 18:01:34 +01:00
MacRimi
19a95a3670 Update network-traffic-chart.tsx 2025-11-19 22:54:41 +01:00
MacRimi
90cffb3791 Update format-network.ts 2025-11-19 22:47:24 +01:00
MacRimi
31168fbeca Update network-metrics.tsx 2025-11-19 22:33:28 +01:00
MacRimi
c4cce5d184 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-11-19 22:05:49 +01:00
MacRimi
08b59dd082 Update network-metrics.tsx 2025-11-19 22:05:45 +01:00
ProxMenuxBot
4aaf1a5868 Update helpers_cache.json 2025-11-19 18:20:10 +00:00
MacRimi
6e78fa0b1f Update AppImage 2025-11-19 18:43:08 +01:00
MacRimi
e1a42189a6 Update system-overview.tsx 2025-11-19 18:18:47 +01:00
MacRimi
386e0c9b6b Update format-network.ts 2025-11-19 18:09:52 +01:00
MacRimi
3b1b423936 Update AppImage 2025-11-19 17:58:03 +01:00
MacRimi
8e8e8161bb Update AppImage 2025-11-19 17:30:01 +01:00
MacRimi
b368fde82d Update appImage 2025-11-19 17:15:32 +01:00
ProxMenuxBot
7267111083 Update helpers_cache.json 2025-11-19 12:28:30 +00:00
MacRimi
d05dab6633 Update AppImage 2025-11-18 22:05:54 +01:00
MacRimi
e1409a8045 Update AppImage 2025-11-18 21:27:24 +01:00
MacRimi
ae69fec7ce Update AppImage 2025-11-18 21:11:56 +01:00
MacRimi
a2862f22f6 Update AppImage 2025-11-18 20:56:15 +01:00
MacRimi
7db8e18bcc Update AppImage 2025-11-18 19:43:18 +01:00
MacRimi
0ffe1272fe Update virtual-machines.tsx 2025-11-18 19:24:23 +01:00
MacRimi
92b54075c4 Update AppImage 2025-11-18 19:11:14 +01:00
MacRimi
ce5c679d6b Update AppImage 2025-11-18 19:00:51 +01:00
MacRimi
4f61386b21 Update flask_server.py 2025-11-18 18:33:57 +01:00
MacRimi
2738ae1abc Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-11-18 17:14:46 +01:00
MacRimi
f5e43ff7b4 Update flask_server.py 2025-11-18 17:14:30 +01:00
MacRimi
63c499bf2c Merge pull request #93 from riri-314/networkGraph
Add option to change network unit
2025-11-18 17:00:23 +01:00
riri-314
9e72720bda revert code formating 2025-11-18 13:30:37 +01:00
ProxMenuxBot
bbe10b2dab Update helpers_cache.json 2025-11-18 12:29:38 +00:00
riri-314
f3b0784651 Network metrics take network unit into acount 2025-11-18 13:19:23 +01:00
riri-314
9c0ea9b1c7 System oberview take network unit into account 2025-11-18 13:08:39 +01:00
riri-314
620a088c6c Removed debug code 2025-11-18 11:32:32 +01:00
riri-314
867a74cffb Added optional prop to display network traffic in bits or bytes 2025-11-18 11:26:49 +01:00
riri-314
f2316fdd3a Add setting to change network unit 2025-11-18 10:58:06 +01:00
MacRimi
7d49d4f948 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-11-17 19:26:44 +01:00
MacRimi
f85b2b889c Update flask_server.py 2025-11-17 19:26:30 +01:00
ProxMenuxBot
9471ac4a52 Update helpers_cache.json 2025-11-17 18:20:20 +00:00
MacRimi
db520c39e3 Update flask_server.py 2025-11-17 18:48:20 +01:00
MacRimi
cc59fbe2ba Delete ProxMenux-1.0.2-deb.AppImage 2025-11-17 18:22:15 +01:00
MacRimi
e260af58f2 Create ProxMenux-1.0.2-deb.AppImage 2025-11-17 17:53:50 +01:00
MacRimi
166fc6dad9 Update AppImage 2025-11-17 17:47:58 +01:00
MacRimi
959433d737 Update install_proxmenux.sh 2025-11-17 17:11:41 +01:00
MacRimi
f9fa9ce6d8 Update install_proxmenux.sh 2025-11-17 16:58:45 +01:00
MacRimi
6b3a41dfe0 Update install_proxmenux.sh 2025-11-17 16:40:56 +01:00
ProxMenuxBot
37428ecca4 Update helpers_cache.json 2025-11-16 18:18:01 +00:00
MacRimi
6934df253f update menu 2025-11-16 10:19:50 +01:00
MacRimi
00782598a4 Update menu 2025-11-16 01:21:07 +01:00
MacRimi
565c500810 Update menu 2025-11-16 01:17:54 +01:00
MacRimi
e3c16166e6 Update menu 2025-11-16 01:15:48 +01:00
MacRimi
cfa8d1b689 Update menu 2025-11-16 01:13:24 +01:00
MacRimi
a19397f9b5 Update menu 2025-11-16 01:11:49 +01:00
MacRimi
ddfc80b45f Update menu 2025-11-16 01:07:25 +01:00
MacRimi
8591f9b2a1 Update menu 2025-11-16 01:04:16 +01:00
MacRimi
cb26a55e65 Update menu 2025-11-16 00:59:54 +01:00
MacRimi
ef92394685 Update menu 2025-11-16 00:56:04 +01:00
MacRimi
d588ef438e Update menu 2025-11-16 00:53:40 +01:00
MacRimi
09cd363b11 Update menu 2025-11-16 00:51:12 +01:00
MacRimi
2d5c7fdbb5 Update menu 2025-11-16 00:47:26 +01:00
MacRimi
2f0e28368d Update menu 2025-11-16 00:44:00 +01:00
MacRimi
f7f1a2a3b3 Update menu 2025-11-16 00:33:26 +01:00
MacRimi
30afb85260 Update menu 2025-11-16 00:23:14 +01:00
MacRimi
78d883a1b4 Update menu 2025-11-16 00:17:22 +01:00
MacRimi
7913b673a3 Update menu 2025-11-16 00:13:52 +01:00
MacRimi
5edc27297f Update menu 2025-11-16 00:02:52 +01:00
MacRimi
ebc24c2476 Update menu 2025-11-15 23:58:26 +01:00
MacRimi
ed7dd037e5 Update menu 2025-11-15 23:52:39 +01:00
MacRimi
277924c04d Update menu 2025-11-15 23:48:50 +01:00
MacRimi
26ea0feddb Update menu 2025-11-15 23:46:02 +01:00
MacRimi
63c1eab930 Update menu 2025-11-15 23:42:17 +01:00
MacRimi
813e7711df Update menu 2025-11-15 23:40:46 +01:00
MacRimi
6c1f50a230 Remove Nginx configuration example from README
Removed example Nginx configuration from README.
2025-11-15 16:17:31 +01:00
MacRimi
470b6359ba Remove onboarding image sections from README
Removed multiple image sections related to storage management, network monitoring, virtual machines, hardware information, and system logs from the README.
2025-11-15 16:16:30 +01:00
MacRimi
2f45233748 Fix formatting issues in menu file 2025-11-15 15:20:12 +01:00
MacRimi
82fd52f572 Add LOCAL_SCRIPTS variable to menu configuration 2025-11-15 14:30:34 +01:00
MacRimi
ed6331e6a4 Update config_menu.sh 2025-11-14 22:17:11 +01:00
MacRimi
2ae9188535 Update version.txt 2025-11-14 21:44:50 +01:00
MacRimi
1a55a5394a Update menu 2025-11-14 21:43:38 +01:00
MacRimi
99d2f37cfc Update version.txt 2025-11-14 21:42:15 +01:00
MacRimi
09b531e0c1 Update version.txt 2025-11-14 21:40:38 +01:00
MacRimi
232e872c0d Update menu 2025-11-14 21:37:55 +01:00
MacRimi
acdb0d2838 Update menu 2025-11-14 21:30:58 +01:00
MacRimi
bd0ea1379f Update menu 2025-11-14 21:02:43 +01:00
MacRimi
5461ea1a3a Update menu 2025-11-14 20:48:38 +01:00
MacRimi
200ee075b5 Update menu 2025-11-14 20:38:07 +01:00
MacRimi
79e9e5fcf1 Update version.txt 2025-11-14 20:33:07 +01:00
MacRimi
a2df23d562 Update version.txt 2025-11-14 20:23:30 +01:00
MacRimi
55af3d7f65 Update version.txt 2025-11-14 20:21:12 +01:00
MacRimi
ef54f3fe59 Update ChangeLog 2025-11-14 20:14:03 +01:00
MacRimi
9d84ff6aa7 Update version.txt 2025-11-14 20:10:57 +01:00
MacRimi
ee26006f3c Update CHANGELOG.md 2025-11-14 20:08:07 +01:00
MacRimi
be03035574 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-11-14 19:35:39 +01:00
MacRimi
619f3ca700 Create ProxMenux_offline.png 2025-11-14 19:35:37 +01:00
ProxMenuxBot
8553e63338 Update helpers_cache.json 2025-11-14 18:19:26 +00:00
MacRimi
be4d9fe24b Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-11-14 18:54:33 +01:00
MacRimi
497f727b08 Update menu_Helper_Scripts.sh 2025-11-14 18:54:32 +01:00
ProxMenuxBot
74a7569f4c Update helpers_cache.json 2025-11-14 17:18:37 +00:00
MacRimi
66185e3b91 Update cache 2025-11-14 18:17:23 +01:00
MacRimi
1b2beda695 Update workflow 2025-11-14 18:15:56 +01:00
MacRimi
feb3b5ef5f Update config_menu.sh 2025-11-14 17:44:19 +01:00
MacRimi
b2439331b3 Update install_proxmenux.sh 2025-11-14 17:26:08 +01:00
MacRimi
f1000afc27 Delete ProxMenux-1.0.0.AppImage 2025-11-14 17:06:58 +01:00
github-actions[bot]
5fc2a82423 Update AppImage build (2025-11-14 16:02:55) 2025-11-14 16:02:55 +00:00
MacRimi
a27f884418 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-11-14 16:59:35 +01:00
MacRimi
cae4b73226 Update AppImage 2025-11-14 16:59:18 +01:00
MacRimi
50ed293de2 Merge pull request #76 from c78-contrib/main
Proxmenux offline mode
2025-11-14 16:56:04 +01:00
cod378
616b772a45 chore: remove test installer script
- Delete install_proxmenux_test.sh (1048 lines)
- Test installer no longer needed after validation
2025-11-14 02:40:48 +00:00
cod378
c1d00e21db fix: suppress systemctl output in ProxMenux Monitor uninstaller
- Redirect stdout and stderr to /dev/null for stop, disable, daemon-reload, and reset-failed commands
- Maintain clean console output during uninstallation process
2025-11-14 02:37:43 +00:00
cod378
2e8e2b61d3 fix: use MONITOR_SERVICE constant instead of MONITOR_SERVICE_NAME in uninstall function 2025-11-14 02:30:19 +00:00
cod378
ba595c9719 feat: add test installer script with offline support and ProxMenux Monitor uninstaller 2025-11-14 02:21:00 +00:00
cod378
e392f6a2b7 feat: add cod378 to contributors list in config_menu.sh 2025-11-14 02:20:07 +00:00
cod378
7457e71776 Merge branch 'MacRimi:main' into main 2025-11-13 23:08:13 -03:00
cod378
982d0dd72e Merge branch 'main' of github.com:c78-contrib/ProxMenuxOffline 2025-11-14 01:49:58 +00:00
cod378
d345f96518 feat: add ProxMenux Monitor uninstallation to config menu
- Add uninstall_proxmenux_monitor() function with systemd service cleanup
- Stop and disable monitor service if active
- Remove systemd unit file and reload daemon
- Integrate monitor uninstallation into main uninstall_proxmenu() workflow
- Define MONITOR_UNIT_FILE constant for service file path
2025-11-14 01:49:39 +00:00
ProxMenuxBot
469874e975 Update helpers_cache.json 2025-11-14 01:37:35 +00:00
ProxMenuxBot
6ba817cd43 Update helpers_cache.json 2025-11-14 01:03:21 +00:00
MacRimi
42f2e69e3a Update AppImagen 2025-11-13 21:12:32 +01:00
MacRimi
12442b4bd3 Update settings.tsx 2025-11-13 20:59:36 +01:00
MacRimi
305d37a13b Update settings.tsx 2025-11-13 20:43:13 +01:00
MacRimi
4baf60174f Update settings.tsx 2025-11-13 20:36:35 +01:00
MacRimi
8cd1ac6a4b Update settings.tsx 2025-11-13 20:29:22 +01:00
MacRimi
c65fef638e Update settings.tsx 2025-11-13 20:23:08 +01:00
MacRimi
a030cd7e28 Update flask_auth_routes.py 2025-11-13 20:16:39 +01:00
MacRimi
59a3b7eac5 Update settings.tsx 2025-11-13 20:10:00 +01:00
MacRimi
2faac48adf Update settings.tsx 2025-11-13 20:01:08 +01:00
MacRimi
3883039764 Update AppImage 2025-11-13 19:51:42 +01:00
MacRimi
307ed0c637 Update AppImage 2025-11-13 19:43:17 +01:00
MacRimi
96ffdb65d0 Update README.md 2025-11-13 19:36:45 +01:00
MacRimi
5ca55798b2 Update README.md 2025-11-13 19:20:06 +01:00
MacRimi
cd32e11c6d Update AppImage 2025-11-13 19:11:56 +01:00
MacRimi
774cbe4c9d Update AppImage 2025-11-13 18:32:44 +01:00
MacRimi
1d0bb20506 Update AppImage 2025-11-13 18:21:37 +01:00
MacRimi
8064e107f4 Update AppImage 2025-11-13 17:56:42 +01:00
MacRimi
c1d1121ed1 Update AppImage 2025-11-13 17:46:07 +01:00
MacRimi
07603f11db Update api-config.ts 2025-11-13 17:25:51 +01:00
MacRimi
ec22c857d5 Update api-config.ts 2025-11-13 17:20:31 +01:00
MacRimi
364e808261 Update api-config.ts 2025-11-13 17:14:47 +01:00
MacRimi
1d47ad0c4b Update AppImage 2025-11-13 16:58:45 +01:00
cod378
c9d0eac6cc Merge branch 'main' of github.com:c78-contrib/ProxMenuxOffline 2025-11-13 03:23:32 +00:00
cod378
97fc72b78a fix: add validation for missing ProxMenux Monitor AppImage
- Check if AppImage exists before attempting installation
- Display clear error message when AppImage is not found
- Update config to track installation failure state
2025-11-13 03:23:11 +00:00
cod378
7b1111430b chore: remove unused offline installer script 2025-11-13 03:22:52 +00:00
cod378
4f3306cd0f Merge branch 'MacRimi:main' into main 2025-11-12 23:56:22 -03:00
cod378
d3f7056ece Merge branch 'main' of github.com:c78-contrib/ProxMenuxOffline 2025-11-13 02:52:26 +00:00
cod378
9f3286c570 feat: migrate to offline installer with enhanced monitor deployment
- Restructured installer to use local repository files instead of remote downloads for improved reliability
- Added comprehensive logging functions (spinner, type_text, msg_* helpers) and dual logo support for SSH/noVNC terminals
- Implemented AppImage version detection, SHA256 verification, and systemd service management for ProxMenux Monitor
- Updated metadata to reflect toolkit positioning and added contributor attribution
2025-11-13 02:50:41 +00:00
ProxMenuxBot
16fc737b2d Update helpers_cache.json 2025-11-12 18:29:07 +00:00
ProxMenuxBot
3e0ae709d9 Update helpers_cache.json 2025-11-12 18:19:51 +00:00
ProxMenuxBot
39ddb7c8f9 Update helpers_cache.json 2025-11-12 12:41:44 +00:00
ProxMenuxBot
3c509ce0e4 Update helpers_cache.json 2025-11-12 12:38:40 +00:00
cod378
048cf2fb8f docs: update project name references from ProxMenuxDotDeb to ProxMenuxOffline 2025-11-12 05:21:33 +00:00
cod378
0a20821c41 refactor: remove verbose cleanup messages from temporary file removal 2025-11-12 05:00:06 +00:00
cod378
e0eaf6267f fix: suppress git clone output to reduce installation noise 2025-11-12 04:53:37 +00:00
cod378
3ddf98277f refactor: update utils script source URL to offline repository 2025-11-12 04:48:29 +00:00
cod378
85294bcd33 fix: correct utils.sh download URL format 2025-11-12 04:40:10 +00:00
cod378
acff4523f3 refactor: simplify utils.sh loading with inline sourcing
- Replaced conditional file check with direct curl sourcing using process substitution
- Streamlined error handling to single-line check
2025-11-12 04:29:40 +00:00
cod378
bf71e1f9b8 refactor: update comment for utils.sh loading 2025-11-12 04:23:43 +00:00
cod378
f0bcdc1c25 refactor: move utils.sh loading to script initialization because this is an installer dependency 2025-11-12 04:22:11 +00:00
cod378
43526c58bd refactor: reorganize installer to use git-based offline installation
- Changed from local script loading to cloning repository into temporary directory
- Added cleanup function with trap to ensure temporary files are removed on exit
- Added git as a required dependency for the installation process
2025-11-12 04:11:41 +00:00
cod378
ce3c7a545e feat: add GitHub authentication script to gitignore 2025-11-12 04:04:10 +00:00
cod378
9498e4e7eb Merge branch 'MacRimi:main' into main 2025-11-11 23:39:28 -03:00
ProxMenuxBot
4ec7c207f4 Update helpers_cache.json 2025-11-12 01:29:08 +00:00
ProxMenuxBot
000479463f Update helpers_cache.json 2025-11-12 01:04:12 +00:00
MacRimi
6b2065e43c Update AppImage 2025-11-11 22:00:44 +01:00
MacRimi
e97e1363ae Update release-notes-modal.tsx 2025-11-11 21:37:39 +01:00
MacRimi
697a1f8e31 Update release-notes-modal.tsx 2025-11-11 21:30:57 +01:00
MacRimi
035f43311a Update release-notes-modal.tsx 2025-11-11 21:25:10 +01:00
MacRimi
c597f1252e Update release-notes-modal.tsx 2025-11-11 21:12:09 +01:00
MacRimi
cc1e7a715c Update settings.tsx 2025-11-11 19:48:02 +01:00
MacRimi
80057e3014 Update AppImage 2025-11-11 19:36:37 +01:00
MacRimi
79ffba873f Update AppImage 2025-11-11 19:20:59 +01:00
MacRimi
673e1cf212 Update virtual-machines.tsx 2025-11-11 18:21:30 +01:00
MacRimi
7e878ecff2 Update virtual-machines.tsx 2025-11-11 18:07:03 +01:00
MacRimi
88cf51a602 Update AppImage 2025-11-11 17:59:36 +01:00
MacRimi
1860fffe07 Update system-logs.tsx 2025-11-11 17:26:47 +01:00
MacRimi
fa925543db Update AppImage 2025-11-11 17:12:56 +01:00
MacRimi
825e99c59b Update api-config.ts 2025-11-11 17:04:26 +01:00
MacRimi
955bed80fb Update AppImage 2025-11-11 17:01:25 +01:00
ProxMenuxBot
03b9ac3ec4 Update helpers_cache.json 2025-11-11 12:40:51 +00:00
ProxMenuxBot
c255d9a5d8 Update helpers_cache.json 2025-11-11 12:27:59 +00:00
ProxMenuxBot
401d973a51 Update helpers_cache.json 2025-11-11 06:31:46 +00:00
ProxMenuxBot
a507d559e1 Update helpers_cache.json 2025-11-11 01:29:31 +00:00
MacRimi
9225982ca5 Create ProxMenux-1.0.1-beta2.AppImage 2025-11-10 19:03:30 +01:00
MacRimi
6f831530cc Update system-logs.tsx 2025-11-10 18:38:33 +01:00
MacRimi
e6b4443074 Update virtual-machines.tsx 2025-11-10 18:22:44 +01:00
MacRimi
1c800cbd8f Update storage-overview.tsx 2025-11-10 17:45:40 +01:00
MacRimi
a65924799e Update storage-overview.tsx 2025-11-10 17:38:46 +01:00
MacRimi
adbfa1e73e Update AppImge 2025-11-10 17:25:22 +01:00
cod378
44a4226ad2 Merge branch 'MacRimi:main' into main 2025-11-10 11:04:47 -03:00
ProxMenuxBot
07ca3f13a0 Update helpers_cache.json 2025-11-10 12:41:30 +00:00
ProxMenuxBot
87a052b89c Update helpers_cache.json 2025-11-10 12:27:40 +00:00
MacRimi
2216543ac3 Update storage-overview.tsx 2025-11-09 23:59:21 +01:00
MacRimi
4254d57d12 Update storage-overview.tsx 2025-11-09 23:46:32 +01:00
MacRimi
30d93898d8 Update storage-overview.tsx 2025-11-09 23:36:50 +01:00
MacRimi
4c7ed2c2c5 Update health_monitor.py 2025-11-09 21:50:10 +01:00
MacRimi
4fb327cef8 Create ProxMenux-1.0.1-beat1.AppImage 2025-11-09 21:37:01 +01:00
MacRimi
588af3613b Update AppImage 2025-11-09 21:20:39 +01:00
MacRimi
5b5f325a4e Update health_monitor.py 2025-11-09 21:03:00 +01:00
MacRimi
ae62196dff Update AppImage 2025-11-09 20:52:39 +01:00
MacRimi
27e66ee770 Update health_monitor.py 2025-11-09 20:02:38 +01:00
MacRimi
8fb8134898 Update AppImage 2025-11-09 18:23:27 +01:00
MacRimi
a59489f804 Update health_persistence.py 2025-11-09 18:11:55 +01:00
MacRimi
cbf3938784 Update AppImage 2025-11-09 18:05:35 +01:00
MacRimi
c45ebfe598 Update AppImage 2025-11-09 17:56:37 +01:00
MacRimi
a75aad1fdc Update build_appimage.sh 2025-11-09 17:34:11 +01:00
MacRimi
a0635a1026 Update AppImage 2025-11-09 17:28:20 +01:00
MacRimi
27353e160f Update AppImage 2025-11-09 16:43:45 +01:00
MacRimi
b9619efbbf Update AppImage 2025-11-09 16:30:29 +01:00
MacRimi
1712d32ef7 Update AppImage 2025-11-09 15:44:35 +01:00
MacRimi
014deb2118 Update flask_server.py 2025-11-09 15:35:01 +01:00
MacRimi
6077cf81f2 Update system-overview.tsx 2025-11-09 15:12:32 +01:00
MacRimi
0422c38096 Update system-overview.tsx 2025-11-09 14:48:51 +01:00
MacRimi
8c902ae04d Update system-overview.tsx 2025-11-09 14:39:06 +01:00
MacRimi
0a0b916067 Update system-overview.tsx 2025-11-09 14:34:32 +01:00
MacRimi
6822635a0b Update flask_server.py 2025-11-09 13:55:09 +01:00
MacRimi
f9b15fd110 Update node-metrics-charts.tsx 2025-11-09 13:31:16 +01:00
MacRimi
131a458e69 Update node-metrics-charts.tsx 2025-11-09 13:20:30 +01:00
MacRimi
7260807d78 Create use-mobile.tsx 2025-11-09 13:12:51 +01:00
MacRimi
df83d8a3e5 Update node-metrics-charts.tsx 2025-11-09 13:07:42 +01:00
MacRimi
0f45424458 Update virtual-machines.tsx 2025-11-09 12:52:10 +01:00
MacRimi
60f92d019b Update virtual-machines.tsx 2025-11-09 12:26:55 +01:00
ProxMenuxBot
2189487982 Update helpers_cache.json 2025-11-08 06:27:04 +00:00
cod378
3a44997795 Merge branch 'MacRimi:main' into main 2025-11-07 21:53:57 -03:00
MacRimi
1f04134aac Update Appimagen 2025-11-07 21:14:56 +01:00
MacRimi
ce44538240 Update AppImage 2025-11-07 21:07:33 +01:00
MacRimi
5fd53883be Update two-factor-setup.tsx 2025-11-07 21:02:20 +01:00
MacRimi
f064cc89ba Update AppImage 2025-11-07 20:55:00 +01:00
MacRimi
5dd8b3ee36 Update AppImage 2025-11-07 20:36:46 +01:00
MacRimi
f2f9c37ee2 Update ppImage 2025-11-07 20:19:55 +01:00
MacRimi
6836777629 Update AppImage 2025-11-07 20:05:29 +01:00
MacRimi
beefdd280f Update proxmox-dashboard.tsx 2025-11-07 19:52:45 +01:00
MacRimi
4b9ad0da7a Update AppImage 2025-11-07 19:43:55 +01:00
ProxMenuxBot
f9fdd1686c Update helpers_cache.json 2025-11-07 18:28:17 +00:00
MacRimi
25fc3d931e Update AppImage 2025-11-07 19:25:36 +01:00
MacRimi
fc7d0f2cd5 Update AppImage 2025-11-07 18:49:37 +01:00
MacRimi
60c91d9fe4 Update AppImage 2025-11-07 17:35:45 +01:00
MacRimi
cc2d6849a8 Update AppImage 2025-11-07 17:00:32 +01:00
MacRimi
4a5379ea42 Update flask_server.py 2025-11-07 15:36:51 +01:00
MacRimi
ba84c644df Update flask_server.py 2025-11-07 14:33:27 +01:00
MacRimi
37217b4219 Update AppImage 2025-11-07 14:14:43 +01:00
MacRimi
41dab03a5f Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-11-07 13:42:03 +01:00
MacRimi
6e48bf2a71 Update AppImage 2025-11-07 13:41:39 +01:00
ProxMenuxBot
1c1c6f513c Update helpers_cache.json 2025-11-07 12:40:39 +00:00
ProxMenuxBot
49c54f5593 Update helpers_cache.json 2025-11-07 12:27:33 +00:00
MacRimi
d083e49d0b Update proxmox-dashboard.tsx 2025-11-07 13:06:47 +01:00
MacRimi
8dc2b833f4 Update AppImage 2025-11-07 12:54:10 +01:00
MacRimi
7d5726be50 Update proxmox-dashboard.tsx 2025-11-07 12:43:31 +01:00
MacRimi
246c1674d1 Uppdate AppImage 2025-11-07 12:37:11 +01:00
MacRimi
06b81f2b64 Update AppImage 2025-11-07 12:21:37 +01:00
MacRimi
ee57797890 Updete AppImage 2025-11-07 12:17:10 +01:00
MacRimi
a94000e114 Update AppImage 2025-11-07 11:05:57 +01:00
MacRimi
e6655b35f3 Update AppImage 2025-11-07 10:58:50 +01:00
MacRimi
696ffde184 Update issue template contact link description to English 2025-11-07 10:16:17 +01:00
MacRimi
9e74e99923 Update feature_request.md 2025-11-07 10:15:09 +01:00
MacRimi
bc5c6dadfb Translate bug report template to English 2025-11-07 10:14:37 +01:00
MacRimi
0d173a0bfe Update ISO file name for XigmaNAS version 14.3.0.5 2025-11-06 12:10:12 +01:00
MacRimi
cd78920edd Fix ISO URL for XigmaNAS version 14.3.0.5 2025-11-06 09:08:09 +01:00
MacRimi
9a7ec62cf9 Update select_nas_iso.sh 2025-11-06 09:06:52 +01:00
cod378
2b4580cfe8 Merge branch 'MacRimi:main' into main 2025-11-05 23:24:21 -03:00
MacRimi
b790c06294 Merge pull request #62 from MrCaringi/main
Add Issue Templates and Configuration for Categorization
2025-11-05 21:21:33 +01:00
JFC
61d87b46d9 Create feature request issue template
Adds a feature request template for GitHub issues.
2025-11-05 12:37:45 -06:00
JFC
143cb4cbab Modify bug report template and assign to MacRimi
Updated bug report template to include mandatory screenshots and assigned to 'MacRimi'.
2025-11-05 12:36:50 -06:00
JFC
22709dac36 Add issue template configuration for GitHub 2025-11-05 12:35:11 -06:00
ProxMenuxBot
d97be93449 Update helpers_cache.json 2025-11-05 18:29:25 +00:00
ProxMenuxBot
5864de7dea Update helpers_cache.json 2025-11-05 18:19:51 +00:00
MacRimi
4ea5890e92 Update health-status-modal.tsx 2025-11-05 18:46:19 +01:00
MacRimi
876d51b009 Update health-status-modal.tsx 2025-11-05 18:38:29 +01:00
MacRimi
5b0d55c1a2 Update health_monitor.py 2025-11-05 18:30:31 +01:00
cod378
3ddb1421c3 feat: add offline installer script for ProxMenux
- Clones ProxMenux repository to temporary location and executes installation
- Includes automatic cleanup of temporary files and git dependency check
- Adds colored output and error handling for better user experience
2025-11-04 22:47:52 +00:00
cod378
58f9a7bc02 refactor: simplify utils.sh loading error handling 2025-11-04 22:47:00 +00:00
MacRimi
e8e4b728ce Update proxmox-dashboard.tsx 2025-11-04 23:00:37 +01:00
MacRimi
0a4868192d Update proxmox-dashboard.tsx 2025-11-04 22:55:41 +01:00
MacRimi
9d81ffffe8 Update proxmox-dashboard.tsx 2025-11-04 22:47:11 +01:00
MacRimi
e6fe4a09e5 Update AppImage 2025-11-04 22:28:42 +01:00
MacRimi
77c5ad7b09 Update AppImage 2025-11-04 21:59:28 +01:00
MacRimi
b850e9615a Update proxmox-dashboard.tsx 2025-11-04 21:48:54 +01:00
MacRimi
c2ea307821 Update AppImage 2025-11-04 21:42:38 +01:00
MacRimi
fb588c0d60 Update flask_auth_routes.py 2025-11-04 21:36:31 +01:00
MacRimi
fecbdf6190 Update build_appimage.sh 2025-11-04 21:32:14 +01:00
MacRimi
bbbbf6892f Update flask_server.py 2025-11-04 21:27:29 +01:00
MacRimi
e1a11053a6 Update flask_server.py 2025-11-04 21:16:16 +01:00
MacRimi
f0a62191ea Updae AppImage 2025-11-04 21:02:56 +01:00
MacRimi
a8311923fb Update AppImage 2025-11-04 19:58:09 +01:00
MacRimi
cd1d88760d Update proxmox-dashboard.tsx 2025-11-04 19:39:50 +01:00
MacRimi
004949d3a0 Update proxmox-dashboard.tsx 2025-11-04 19:21:31 +01:00
MacRimi
f6d26042da Update AppImage 2025-11-04 19:13:47 +01:00
MacRimi
270a73a470 Update auth-setup.tsx 2025-11-04 18:48:27 +01:00
MacRimi
018e80e59d Update AppImage 2025-11-04 18:45:54 +01:00
MacRimi
cb5cb1e594 Create checkbox.tsx 2025-11-04 18:11:42 +01:00
MacRimi
6c5eb156a1 Update AppImage 2025-11-04 18:07:13 +01:00
MacRimi
8abef33840 Update build_appimage.sh 2025-11-04 17:37:32 +01:00
MacRimi
1d6b8951e8 Update hardware.tsx 2025-11-04 15:28:27 +01:00
MacRimi
711d57d91f Update flask_server.py 2025-11-04 15:09:23 +01:00
MacRimi
65fd847251 Update flask_server.py 2025-11-04 14:24:34 +01:00
MacRimi
73a170a5f1 Update hardware.tsx 2025-11-04 14:00:01 +01:00
MacRimi
9a32d1c0f7 Update flask_server.py 2025-11-04 13:47:00 +01:00
MacRimi
59918032c6 Update hardware.tsx 2025-11-04 13:18:39 +01:00
MacRimi
55394cbf09 Update AppImage 2025-11-04 12:47:26 +01:00
MacRimi
83dcc0c4f2 Update storage-overview.tsx 2025-11-04 12:17:32 +01:00
MacRimi
b4b93f0572 Update AppImage 2025-11-04 12:11:08 +01:00
MacRimi
ab0e59215c Aupdate version ProxMenux Monitor 2025-11-04 11:34:46 +01:00
MacRimi
5669ce207c Update flask_server.py 2025-11-04 11:03:09 +01:00
MacRimi
37f6cd96a4 Update flask_server.py 2025-11-04 09:46:11 +01:00
MacRimi
c0ec74fb12 Update AppImage 2025-11-04 09:14:29 +01:00
cod378
226dc45190 Merge branch 'MacRimi:main' into main 2025-11-03 21:24:30 -03:00
MacRimi
11e3f53a2f Update AppImage 2025-11-03 23:26:04 +01:00
MacRimi
31d7f7e3e9 Update appImage 2025-11-03 23:17:27 +01:00
MacRimi
128edc08e2 Update flask_server.py 2025-11-03 23:13:24 +01:00
MacRimi
5158c5f359 Update flask_server.py 2025-11-03 19:12:07 +01:00
MacRimi
a70b33ce13 Update AppImage 2025-11-03 19:02:41 +01:00
MacRimi
d787c3caa0 Update AppImage 2025-11-03 18:35:16 +01:00
MacRimi
a554af939e Create build-appimage.yml 2025-11-03 18:24:43 +01:00
MacRimi
06604ff0d1 Add manual build workflow for AppImage 2025-11-03 18:15:17 +01:00
MacRimi
9490f79c6d Update build-appimage.yml 2025-11-03 18:14:13 +01:00
MacRimi
311a624698 Update customizable_post_install.sh 2025-11-03 18:08:28 +01:00
cod378
3e2e77f9fb Merge branch 'MacRimi:main' into main 2025-11-03 10:15:41 -03:00
cod378
b2e02cd0e7 refactor: switch from remote URL to local script execution 2025-11-03 12:32:19 +00:00
ProxMenuxBot
87ead71766 Update helpers_cache.json 2025-11-03 12:28:15 +00:00
cod378
b8517a5b3e Merge branch 'ProxMenux-Offline' 2025-11-03 03:54:10 +00:00
cod378
c29cdf44fb refactor: switch from remote URLs to local script execution (unused path) 2025-11-03 02:12:25 +00:00
cod378
4b2ab2894a refactor: switch from remote URLs to local script execution 2025-11-03 02:11:15 +00:00
cod378
c9a01ab5ad refactor: switch from remote URLs to local script execution (unused path) 2025-11-03 02:10:46 +00:00
cod378
90d1046312 refactor: switch from remote URLs to local script execution (unused path) 2025-11-03 02:10:18 +00:00
cod378
14e749a18d refactor: switch from remote URLs to local script execution 2025-11-03 02:09:56 +00:00
cod378
a4be1af0ef refactor: switch from remote URLs to local script execution 2025-11-03 02:08:36 +00:00
cod378
f4185d0a2a refactor: switch from remote URLs to local script execution 2025-11-03 02:07:20 +00:00
cod378
ffb8324b5a refactor: switch from remote URLs to local script execution 2025-11-03 02:06:43 +00:00
cod378
6df44f1632 refactor: switch from remote URLs to local script execution 2025-11-03 02:03:26 +00:00
cod378
9570819f59 refactor: switch from remote URLs to local script execution (unused path) 2025-11-03 01:56:34 +00:00
cod378
f2afc94ed2 refactor: switch from remote URLs to local script execution (unused path) 2025-11-03 01:56:23 +00:00
cod378
050b95946c refactor: switch from remote URLs to local script execution (unused path) 2025-11-03 01:55:45 +00:00
cod378
e33ef92334 refactor: switch from remote URLs to local script execution 2025-11-03 01:54:52 +00:00
cod378
0d7ff46aec refactor: switch from remote URLs to local script execution 2025-11-03 01:53:10 +00:00
cod378
042913e080 refactor: switch from remote URLs to local script execution (unused path) 2025-11-03 01:51:57 +00:00
cod378
98bc8be642 refactor: switch from remote URLs to local script execution (unused path) 2025-11-03 01:51:43 +00:00
cod378
2c6d2f4255 refactor: switch from remote URLs to local script execution (unused path) 2025-11-03 01:51:24 +00:00
cod378
6293556837 refactor: switch from remote URLs to local script execution (unused path) 2025-11-03 01:51:06 +00:00
cod378
641721d199 refactor: switch from remote URLs to local script execution (unused path) 2025-11-03 01:48:35 +00:00
cod378
036a2b9014 refactor: switch from remote URLs to local script execution (unused path) 2025-11-03 01:47:52 +00:00
cod378
9ad092d340 refactor: switch from remote URLs to local script execution (unused path) 2025-11-03 01:47:17 +00:00
cod378
d24884f651 refactor: switch from remote URLs to local script execution (unused path) 2025-11-03 01:46:50 +00:00
cod378
fa1c498716 refactor: switch from remote URLs to local script execution (unused path) 2025-11-03 01:46:19 +00:00
cod378
25635239d4 refactor: switch from remote URLs to local script execution (unused path) 2025-11-03 01:45:43 +00:00
cod378
c816688de3 refactor: switch from remote URLs to local script execution (unused path) 2025-11-03 01:44:48 +00:00
cod378
43d79bd1e9 refactor: switch from remote URLs to local script execution (unused path) 2025-11-03 01:44:06 +00:00
cod378
ba88c7b0f6 refactor: switch from remote URLs to local script execution 2025-11-03 01:41:39 +00:00
cod378
4359d92ffe refactor: switch from remote URLs to local script execution 2025-11-03 01:40:38 +00:00
cod378
16c7513e82 refactor: switch from remote URLs to local script execution 2025-11-03 01:39:15 +00:00
cod378
4572478ad8 refactor: switch from remote URLs to local script execution 2025-11-03 01:38:05 +00:00
cod378
02b5cd61bd refactor: switch from remote URLs to local script execution 2025-11-03 01:36:57 +00:00
cod378
bff07311b2 refactor: switch from remote URLs to local script execution 2025-11-03 01:34:44 +00:00
cod378
44cc89b9d5 refactor: switch from remote URLs to local file paths 2025-11-03 01:33:04 +00:00
cod378
fa1e6c6c64 refactor: switch from remote URLs to local script execution 2025-11-03 01:27:37 +00:00
cod378
4ebbdb284b refactor: switch from remote URLs to local script execution 2025-11-03 01:26:28 +00:00
cod378
51302a7c5a refactor: switch from remote URLs to local script execution 2025-11-03 01:25:19 +00:00
cod378
ba984592ed refactor: replace remote script fetching with local file execution 2025-11-03 01:22:24 +00:00
cod378
60a97e5815 feat: replace remote script fetching with local file execution 2025-11-03 01:20:11 +00:00
cod378
3275a1ecb4 refactor: replace remote script loading with local paths 2025-11-03 01:17:21 +00:00
cod378
af72c7a2d3 refactor: switch from remote URLs to local script paths 2025-11-03 01:16:32 +00:00
cod378
c07ada1fc4 refactor: switch from remote to local script execution 2025-11-03 01:14:54 +00:00
cod378
c19c8f9c5d refactor: switch network menu from remote URL to local scripts 2025-11-03 01:10:44 +00:00
cod378
43fe7ae7db refactor: replace remote script fetching with local file execution 2025-11-03 01:09:12 +00:00
cod378
22916868df feat: switch script paths from remote repo to local directory 2025-11-03 01:06:04 +00:00
cod378
7d00ff8869 feat: switch menu scripts from remote URLs to local paths 2025-11-03 01:03:39 +00:00
cod378
4ea2088485 refactor: replace remote script loading with local file execution
- Changed script loading from curl-based remote fetching to local file execution for improved security and reliability
- Removed dependency on external repository access for core menu functionality
- Fixed missing semicolon in case statement default branch
2025-11-03 00:59:48 +00:00
cod378
e421b40093 refactor: switch from remote scripts to local execution 2025-11-03 00:50:18 +00:00
cod378
a9dd7562ac feat: switch from remote URLs to local script paths
- Changed script sourcing from GitHub URLs to local filesystem paths for improved reliability
- Added error handling for missing script files with descriptive messages
- Removed redundant utility file sourcing and consolidated into single conditional block
- Updated script execution to use direct paths instead of curl commands
- Removed unused start_vm_configuration function that was duplicated elsewhere
2025-11-03 00:46:23 +00:00
cod378
8f62ed67d3 refactor: switch from remote URL to local script paths 2025-11-03 00:22:59 +00:00
cod378
cfd89a14f7 refactor: update script paths to use local resources 2025-11-03 00:16:28 +00:00
cod378
55011842f5 refactor: update script paths to use local resources 2025-11-03 00:15:42 +00:00
cod378
3079a3f51c refactor: update script paths to use local resources 2025-11-03 00:15:08 +00:00
cod378
c4ec390ca0 refactor: update scripts paths to use local references 2025-11-03 00:13:19 +00:00
cod378
f99b7f3589 refactor: switch script to use local paths instead of remote URLs 2025-11-03 00:07:34 +00:00
cod378
887b170c0e refactor: switch script to use local paths instead of remote URLs 2025-11-03 00:06:12 +00:00
cod378
c696cfd8d8 refactor: switch update script to use local file paths 2025-11-03 00:04:47 +00:00
cod378
25966973a2 refactor: update script paths to use local resources 2025-11-03 00:03:21 +00:00
cod378
d0a57d4b7c refactor: update script paths to use local resources 2025-11-03 00:01:36 +00:00
cod378
9341b49fd1 refactor: update script paths to use local references 2025-11-03 00:00:52 +00:00
cod378
bbc3c922a6 refactor: switch from remote repo to local script paths 2025-11-02 23:58:48 +00:00
cod378
17b8d63e6c refactor: switch from remote URLs to local script paths 2025-11-02 23:55:37 +00:00
cod378
c751a8168a refactor: switch backup script from remote URL to local file paths 2025-11-02 23:36:13 +00:00
cod378
f2509dbe5d refactor: switch from remote to local script loading. 2025-11-02 23:26:45 +00:00
cod378
6d44c22982 refactor: switch from remote to local script loading. 2025-11-02 23:25:04 +00:00
cod378
4bed489610 refactor: switch backup scripts from remote URL to local paths 2025-11-02 23:08:34 +00:00
cod378
8edf488636 refactor: update script paths to use local references 2025-11-02 03:32:41 +00:00
cod378
8fe7d249f8 refactor: update script paths to use local resources 2025-11-02 03:32:02 +00:00
cod378
6ed14e1d3c feat: switch from remote to local script loading 2025-11-02 03:29:56 +00:00
cod378
a5459acdaf feat: switch help menu from remote to local script loading 2025-11-02 03:28:14 +00:00
cod378
61cd198d35 feat: switch disk passthrough script to use local scripts 2025-11-02 03:27:18 +00:00
cod378
49ea2b304d feat: switch disk passthrough script to use local scripts 2025-11-02 03:26:27 +00:00
cod378
27231d1764 feat: switch iGPU configuration to use local scripts 2025-11-02 03:25:41 +00:00
cod378
8744620220 feat: improve log2ram installation and system checks
- Enhanced log2ram size calculation to support both MB and GB configurations
- Updated RAM detection to use MB-level precision before converting to GB
- Fixed typos in status messages ("successfull" → "successful")
- Switched from remote repo URL to local scripts directory for better reliability
- Added registration of LVM repair tool after successful header checks
- Improved log2ram monitoring script to properly handle different size units (M
2025-11-02 03:24:26 +00:00
cod378
4590be6d42 fix: replace remote script loading with local file execution 2025-11-02 03:22:54 +00:00
cod378
fa93b43c32 feat: switch telegram notifier to use local scripts
- Changed script source from GitHub repository to local directory (/usr/local/share/proxmenux/scripts)
- Updated path configuration to ensure consistent local file access
- Removed dependency on external repository for improved reliability and security
2025-11-02 02:59:33 +00:00
cod378
3c47f84a24 refactor: switch from remote repo to local scripts path
- Changed repository URL reference from GitHub to local scripts directory (/usr/local/share/proxmenux/scripts)
- Fixed spacing in info2 message formatting by adding space after HOLD variable
- Simplified script dependencies to use local installation instead of remote fetching
2025-11-02 02:55:19 +00:00
cod378
8a371c26de refactor: switch from remote to local script execution
- Changed script loading from remote URL to local directory path for offline usage
- Updated REPO_URL to LOCAL_SCRIPTS path (/usr/local/share/proxmenux/scripts)
- Disabled check_updates function since it's not applicable for local version
- Added comments explaining update functionality will be handled via .deb package in future
2025-11-02 02:51:24 +00:00
ProxMenuxBot
088a594468 Update helpers_cache.json 2025-11-02 01:06:57 +00:00
MacRimi
c551913551 Update uninstall-tools.sh 2025-11-02 00:53:04 +01:00
code78
05e81053e0 feat: switch to local file installation and improve monitor setup
- Replaced remote file downloads with local file copying for more reliable installation
- Added proper cleanup of existing monitor service before reinstallation
- Enhanced error handling and logging for monitor service startup
- Improved SHA256 verification for monitor AppImage
- Added copying of install script and all utility scripts to base directory
- Updated progress messages to be more descriptive and accurate
- Increased monitor
2025-11-01 23:47:45 +00:00
MacRimi
981c0ab980 Update remove-banner-pve-v3.sh 2025-11-02 00:42:22 +01:00
MacRimi
1f083b335f Update remove banner v3 2025-11-02 00:37:59 +01:00
code78
10603900df update: Progress status 2025-11-01 23:33:25 +00:00
MacRimi
c22e36d219 Update script PVE 9 2025-11-01 17:22:51 +01:00
MacRimi
26fc2ae9db Update install_proxmenux.sh 2025-11-01 17:09:38 +01:00
code78
2a0b298ae5 feat: add comprehensive project documentation and analysis
- Created detailed documentation covering ProxMenux project structure, installation flow, and core components
- Added in-depth analysis of script architecture, execution patterns, and key functionalities
- Documented system configuration, translation mechanism, and component interactions
- Included detailed breakdown of file organization, menu system, and installation processes
- Added technical specifications for ProxMenux Monitor web dashboar
2025-11-01 03:01:51 +00:00
ProxMenuxBot
96f0a9bc5d Update helpers_cache.json 2025-11-01 01:06:00 +00:00
MacRimi
5054e78864 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-11-01 00:11:05 +01:00
MacRimi
f1fa6b03d5 change license CC-BY-NC-4.0 2025-11-01 00:10:46 +01:00
MacRimi
8f15bf9668 change license to CC-BY-NC-4.0 2025-10-31 23:48:46 +01:00
MacRimi
4b8e7b19a3 change licente to CC-BY-NC-4.0 2025-10-31 23:47:17 +01:00
MacRimi
67bba1dd09 Update version.txt 2025-10-31 23:38:45 +01:00
MacRimi
b826dec79d Update CHANGELOG.md 2025-10-31 23:34:21 +01:00
MacRimi
62b200c5d9 Update CHANGELOG.md 2025-10-31 23:27:11 +01:00
MacRimi
c2ed772f34 Update coral TPU pve9 2025-10-31 22:55:13 +01:00
MacRimi
bbce6d4ad0 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-10-31 22:45:01 +01:00
MacRimi
45f6a0ec02 Update Install ProxMenux 2025-10-31 22:44:59 +01:00
github-actions[bot]
6b681278e0 Update AppImage build (2025-10-31 20:13:11) 2025-10-31 20:13:11 +00:00
MacRimi
2281ff06c7 Update GitHub Actions workflow permissions
Added permissions for write access to contents.
2025-10-31 21:10:47 +01:00
MacRimi
572a81fd4e Enhance workflow to include SHA256 checksum generation
Added steps to generate SHA256 checksum and upload AppImage.
2025-10-31 21:04:28 +01:00
MacRimi
5fd7df69fd Update update-pve9_2.sh 2025-10-31 20:47:46 +01:00
MacRimi
3401c6305e Update remove-banner-pve-v3.sh 2025-10-31 20:25:55 +01:00
MacRimi
269f9ac52c Update update-pve9_2.sh 2025-10-31 20:20:24 +01:00
MacRimi
4712171d43 Updsate Post Install 2025-10-31 20:16:42 +01:00
MacRimi
632c7b91f4 Update proxmox_update.sh 2025-10-31 20:09:52 +01:00
MacRimi
f72dd79dff Update remove-banner-pve-v3.sh 2025-10-31 20:07:57 +01:00
MacRimi
4d109c0481 Update Post Install 2025-10-31 20:03:34 +01:00
MacRimi
c046b77223 Update auto_post_install.sh 2025-10-31 18:44:30 +01:00
MacRimi
56ba3b5e5f Update auto_post_install.sh 2025-10-31 18:39:11 +01:00
MacRimi
fa99247cb7 Update update-pve.sh 2025-10-31 18:35:49 +01:00
MacRimi
653fd37c08 Update update-pve.sh 2025-10-31 18:31:02 +01:00
MacRimi
9d053beafb Update Post Install menu 2025-10-31 18:28:11 +01:00
MacRimi
f33c451a19 Update Post Install 2025-10-31 17:57:09 +01:00
MacRimi
9543148887 remove subscription banner V3 2025-10-31 17:49:28 +01:00
MacRimi
029ee4ed2f Update onboarding-carousel.tsx 2025-10-31 17:02:09 +01:00
MacRimi
688e826e9d Update onboarding-carousel.tsx 2025-10-30 23:30:58 +01:00
MacRimi
deea0c54d4 Updarte AppImage 2025-10-30 23:14:00 +01:00
MacRimi
028f62aa9c Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-10-30 21:08:31 +01:00
MacRimi
d3aef9c1d1 Update AppImage 2025-10-30 21:08:08 +01:00
ProxMenuxBot
72edce511a Update helpers_cache.json 2025-10-30 12:27:00 +00:00
MacRimi
37fade8f7a Update AppImage 2025-10-29 22:16:40 +01:00
MacRimi
0d2aa6738c Create ProxMenux-rc2.AppImage 2025-10-29 21:39:35 +01:00
MacRimi
e3e0e5cba8 Create Heriberto.AppImage 2025-10-29 21:33:09 +01:00
MacRimi
e40de189c3 Update flask_server.py 2025-10-29 21:28:23 +01:00
MacRimi
c9c99f7b2a Create ProxMenux-rc.AppImage 2025-10-29 21:07:28 +01:00
MacRimi
e6400918c8 Update AppImage 2025-10-29 20:57:06 +01:00
MacRimi
d08557ad0e Update flask_server.py 2025-10-29 20:42:11 +01:00
MacRimi
ba84dce7a7 Update build_appimage.sh 2025-10-29 20:29:43 +01:00
MacRimi
7044725bf1 Update AppImage 2025-10-29 20:27:39 +01:00
MacRimi
1812966fe6 Update AppImage 2025-10-29 20:22:55 +01:00
MacRimi
a9bac25c9b Update proxmox-dashboard.tsx 2025-10-29 20:14:30 +01:00
MacRimi
5c967f11f0 Update AppImage 2025-10-29 20:03:17 +01:00
MacRimi
1b5f080495 Update system-logs.tsx 2025-10-29 19:45:32 +01:00
MacRimi
50a27fa3f6 Update virtual-machines.tsx 2025-10-29 19:22:53 +01:00
MacRimi
f8ed53c1b9 Update virtual-machines.tsx 2025-10-29 19:04:01 +01:00
MacRimi
2163830a54 Update virtual-machines.tsx 2025-10-29 18:41:01 +01:00
MacRimi
cae5e3b99f Update virtual-machines.tsx 2025-10-29 18:27:00 +01:00
MacRimi
cc0a7941ea Update AppImage 2025-10-29 18:14:09 +01:00
MacRimi
18901c0e2d Update flask_server.py 2025-10-29 17:47:53 +01:00
ProxMenuxBot
d4ea239185 Update helpers_cache.json 2025-10-29 12:29:04 +00:00
MacRimi
813c6aab13 Update AppImage 2025-10-28 23:18:32 +01:00
MacRimi
f606e131a7 Update AppImage 2025-10-28 23:07:22 +01:00
MacRimi
ccefa61b3d Update virtual-machines.tsx 2025-10-28 23:01:51 +01:00
MacRimi
606cae411f Update virtual-machines.tsx 2025-10-28 22:51:54 +01:00
MacRimi
901e4012cc Update AppImage 2025-10-28 22:45:15 +01:00
MacRimi
d03b667194 Update AppImage 2025-10-28 22:12:57 +01:00
MacRimi
d30954167e Update AppImage 2025-10-28 21:44:39 +01:00
MacRimi
0ee514ea15 Update flask_server.py 2025-10-28 19:59:37 +01:00
MacRimi
7e60792be8 Update flask_server.py 2025-10-28 19:38:56 +01:00
MacRimi
244a325394 Update flask_server.py 2025-10-28 19:07:08 +01:00
MacRimi
53df16a7ca Update flask_server.py 2025-10-28 18:48:33 +01:00
MacRimi
420576da09 Update flask_server.py 2025-10-28 18:45:31 +01:00
MacRimi
d5a9d8ffdb Update flask_server.py 2025-10-28 18:40:11 +01:00
MacRimi
1873ad1a02 Update flask_server.py 2025-10-28 18:28:37 +01:00
MacRimi
9dec238f41 Update flask_server.py 2025-10-28 17:53:06 +01:00
MacRimi
b93a018dc1 Update flask_server.py 2025-10-28 17:47:37 +01:00
ProxMenuxBot
6b1d5bf7db Update helpers_cache.json 2025-10-28 12:26:47 +00:00
ProxMenuxBot
4580866281 Update helpers_cache.json 2025-10-28 01:00:36 +00:00
ProxMenuxBot
5b743772ac Update helpers_cache.json 2025-10-27 18:19:45 +00:00
MacRimi
0ecf08e8e6 Update flask_server.py 2025-10-27 00:18:23 +01:00
MacRimi
7ca53d30b2 Update flask_server.py 2025-10-27 00:15:26 +01:00
MacRimi
18a0ba1981 Update flask_server.py 2025-10-27 00:03:26 +01:00
MacRimi
11a35ed589 Update flask_server.py 2025-10-26 23:58:02 +01:00
MacRimi
eb5aa12d7f Update flask_server.py 2025-10-26 23:33:17 +01:00
MacRimi
1065b67073 Update flask_server.py 2025-10-26 23:17:18 +01:00
MacRimi
0d7b278003 Update flask_server.py 2025-10-26 22:58:56 +01:00
MacRimi
05093a9d49 Update AppImage 2025-10-26 22:53:13 +01:00
MacRimi
87f9b2b72c Update AppImage 2025-10-26 22:30:14 +01:00
MacRimi
2d4833d199 Update AppImage 2025-10-26 22:11:02 +01:00
MacRimi
610e08e690 Update AppImage 2025-10-26 21:42:12 +01:00
MacRimi
1192224c15 Update proxmox-dashboard.tsx 2025-10-26 21:30:48 +01:00
MacRimi
4c3a9928e7 Update metrics-dialog.tsx 2025-10-26 21:11:55 +01:00
MacRimi
433a4359e6 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-10-26 21:00:02 +01:00
MacRimi
f1854b5120 Update AppImage 2025-10-26 20:59:59 +01:00
ProxMenuxBot
6961b0c2f5 Update helpers_cache.json 2025-10-26 18:17:36 +00:00
MacRimi
8b26f30e37 Create ProxMenux-beta7.AppImage 2025-10-26 18:28:54 +01:00
MacRimi
071724949f Update AppImage 2025-10-26 18:03:09 +01:00
MacRimi
28898aa1db Update network-metrics.tsx 2025-10-26 17:39:19 +01:00
MacRimi
11fae19e33 Update AppImage 2025-10-26 16:23:46 +01:00
MacRimi
13b9dd0262 Update network-metrics.tsx 2025-10-26 16:17:23 +01:00
MacRimi
b47520c938 Update flask_server.py 2025-10-26 15:39:52 +01:00
MacRimi
f6869b9e1c Update AppImage 2025-10-26 15:36:53 +01:00
MacRimi
96046a5d1f Update AppImage 2025-10-26 15:18:54 +01:00
MacRimi
524d0b278b Update network-metrics.tsx 2025-10-26 14:44:41 +01:00
MacRimi
6e2348eb06 Update AppImage 2025-10-26 14:31:10 +01:00
MacRimi
e4b57e6ca3 Updae AppImage 2025-10-26 14:28:35 +01:00
MacRimi
9640e558cd Update AppImage 2025-10-26 14:25:23 +01:00
MacRimi
07b13d1374 Update network-traffic-chart.tsx 2025-10-26 14:17:22 +01:00
MacRimi
7e4389abd9 Update network-traffic-chart.tsx 2025-10-26 14:08:42 +01:00
MacRimi
0f424e7f0d Update network-traffic-chart.tsx 2025-10-26 13:31:14 +01:00
MacRimi
455e5735ff Update network-traffic-chart.tsx 2025-10-26 12:44:18 +01:00
MacRimi
6577d2ae3c Update AppImage 2025-10-26 12:32:40 +01:00
MacRimi
56ed543dfb Update AppImage 2025-10-26 12:17:22 +01:00
MacRimi
5549e3a398 Update network-metrics.tsx 2025-10-26 12:00:42 +01:00
MacRimi
2ff7c111af Update network-metrics.tsx 2025-10-26 11:49:13 +01:00
MacRimi
a7af072ca7 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-10-26 11:41:20 +01:00
MacRimi
a4a455f31e Update network-metrics.tsx 2025-10-26 11:41:18 +01:00
ProxMenuxBot
99d55b4314 Update helpers_cache.json 2025-10-26 01:05:33 +00:00
MacRimi
811e2155a6 Update flask_server.py 2025-10-26 01:46:12 +02:00
MacRimi
ab7a49351d Update network-metrics.tsx 2025-10-26 00:34:05 +02:00
MacRimi
efdfba0575 Update AppImage 2025-10-26 00:21:33 +02:00
MacRimi
af9b5f6ca4 Update network-metrics.tsx 2025-10-25 23:44:54 +02:00
MacRimi
65144b9a3d Update AppImage 2025-10-25 23:39:23 +02:00
MacRimi
621f57d702 Update AppImage 2025-10-25 23:33:18 +02:00
MacRimi
a0444fbeee Update node-metrics-charts.tsx 2025-10-25 22:57:21 +02:00
MacRimi
cff81eea14 Update AppImage 2025-10-25 22:43:17 +02:00
MacRimi
5738c90721 Update network-metrics.tsx 2025-10-25 22:31:13 +02:00
MacRimi
a229231c0c Update AppImage 2025-10-25 22:10:08 +02:00
MacRimi
6bf5bd97b5 Update network-metrics.tsx 2025-10-25 21:47:04 +02:00
MacRimi
35c50a7c60 Update network-metrics.tsx 2025-10-25 21:36:46 +02:00
MacRimi
042e6584eb Update network-metrics.tsx 2025-10-25 21:19:43 +02:00
MacRimi
bcce1b7ea8 Update network-traffic-chart.tsx 2025-10-25 21:04:21 +02:00
MacRimi
73181f9e33 Update network-metrics.tsx 2025-10-25 20:55:24 +02:00
MacRimi
b0a7b6c7cd Update AppImage 2025-10-25 18:47:24 +02:00
MacRimi
09744818dc Update AppImage 2025-10-25 18:09:48 +02:00
MacRimi
f93b3109b9 Update uninstall-tools.sh 2025-10-25 17:45:57 +02:00
MacRimi
48d4836f0a Update auto_post_install.sh 2025-10-25 17:33:05 +02:00
MacRimi
5d4f70e943 Update auto_post_install.sh 2025-10-25 17:22:52 +02:00
ProxMenuxBot
9e05197a9a Update helpers_cache.json 2025-10-25 12:22:51 +00:00
MacRimi
11671e884d Update auto_post_install.sh 2025-10-25 11:44:00 +02:00
MacRimi
dcce818678 Update post Install 2025-10-25 11:28:07 +02:00
MacRimi
f6c23bc9a0 Update virtual-machines.tsx 2025-10-24 23:35:08 +02:00
MacRimi
15eca895fb Update virtual-machines.tsx 2025-10-24 23:28:10 +02:00
MacRimi
d5d5dd7855 Update hardware.tsx 2025-10-24 23:15:53 +02:00
MacRimi
c79f5fd8a5 Update hardware.tsx 2025-10-24 23:11:24 +02:00
MacRimi
409e40f3b7 Update hardware.tsx 2025-10-24 23:04:03 +02:00
MacRimi
67a83cb164 Update hardware.tsx 2025-10-24 22:57:06 +02:00
MacRimi
908bdc7c86 Update hardware.tsx 2025-10-24 22:50:03 +02:00
MacRimi
b1c2bd3d64 Update hardware.tsx 2025-10-24 22:42:08 +02:00
MacRimi
ffd317aff0 Update hardware.tsx 2025-10-24 22:34:29 +02:00
MacRimi
84a10afea1 Update network-metrics.tsx 2025-10-24 22:00:16 +02:00
MacRimi
32036ef64d Update AppImage 2025-10-24 21:54:34 +02:00
MacRimi
a8b8036311 Update network-metrics.tsx 2025-10-24 21:40:19 +02:00
MacRimi
b813716f7c Update network-metrics.tsx 2025-10-24 21:24:57 +02:00
MacRimi
7bd6061a59 Update network-metrics.tsx 2025-10-24 21:06:45 +02:00
MacRimi
7682a6e708 Update AppImage 2025-10-24 20:55:45 +02:00
MacRimi
fe9c592107 Update network-metrics.tsx 2025-10-24 20:31:56 +02:00
MacRimi
9ff24dc446 Update network-metrics.tsx 2025-10-24 20:22:24 +02:00
MacRimi
3036711fb4 Update network-metrics.tsx 2025-10-24 20:08:33 +02:00
MacRimi
53363f293b Update network-metrics.tsx 2025-10-24 19:58:04 +02:00
MacRimi
e9791984ee Update AppImage 2025-10-24 19:30:10 +02:00
MacRimi
ddca96a60e Update AppImage 2025-10-24 19:20:37 +02:00
MacRimi
be3607dd4d Update AppImage 2025-10-24 18:56:39 +02:00
MacRimi
6000a7a60f Update proxmox-dashboard.tsx 2025-10-24 18:39:00 +02:00
MacRimi
cc64c9f9d8 Update install_coral_pve9.sh 2025-10-24 18:23:09 +02:00
MacRimi
5c699d956c Delete 0001-fix-apex-group-and-udev-rules.patch 2025-10-24 18:17:46 +02:00
MacRimi
e1757e5ac5 Update 0001-fix-apex-group-and-udev-rules.patch 2025-10-24 18:11:33 +02:00
MacRimi
31167234be Update 0001-fix-apex-group-and-udev-rules.patch 2025-10-24 18:05:27 +02:00
MacRimi
807638ca04 Update coral TPU 2025-10-24 18:00:26 +02:00
MacRimi
ff6b78252c Create 0001-fix-apex-group-and-udev-rules.patch .sh 2025-10-24 17:58:37 +02:00
MacRimi
93bdcaab7f Update auto_post_install.sh 2025-10-24 17:26:32 +02:00
MacRimi
d06c580bbc Update AppImage 2025-10-24 17:17:14 +02:00
MacRimi
a5b32b356c Create ProxMenux-beta6.AppImage 2025-10-23 22:44:55 +02:00
MacRimi
d117e666fd Update proxmox-dashboard.tsx 2025-10-23 22:40:00 +02:00
MacRimi
09da94b2ab Update proxmox-dashboard.tsx 2025-10-23 22:32:43 +02:00
MacRimi
9ebf5919a2 Update proxmox-dashboard.tsx 2025-10-23 22:25:59 +02:00
MacRimi
9c46452a4d Update proxmox-dashboard.tsx 2025-10-23 22:16:02 +02:00
MacRimi
b34536491b Update proxmox-dashboard.tsx 2025-10-23 22:10:21 +02:00
MacRimi
a7c1e240c1 Update proxmox-dashboard.tsx 2025-10-23 22:00:44 +02:00
MacRimi
44618d3d73 Update proxmox-dashboard.tsx 2025-10-23 21:54:22 +02:00
MacRimi
1f92af64f0 Update proxmox-dashboard.tsx 2025-10-23 21:41:55 +02:00
MacRimi
5bcd081e88 Update proxmox-dashboard.tsx 2025-10-23 21:36:02 +02:00
MacRimi
b141622e75 Update proxmox-dashboard.tsx 2025-10-23 21:31:21 +02:00
MacRimi
f96bdee71d Update proxmox-dashboard.tsx 2025-10-23 21:28:00 +02:00
MacRimi
0af70d3298 Update proxmox-dashboard.tsx 2025-10-23 21:20:56 +02:00
MacRimi
e044c59627 Update proxmox-dashboard.tsx 2025-10-23 21:13:17 +02:00
MacRimi
cce1c902e5 Update proxmox-dashboard.tsx 2025-10-23 21:09:01 +02:00
MacRimi
d845474644 Update proxmox-dashboard.tsx 2025-10-23 21:02:56 +02:00
MacRimi
cd67aba2ad Update proxmox-dashboard.tsx 2025-10-23 20:56:11 +02:00
MacRimi
1e47162357 Update proxmox-dashboard.tsx 2025-10-23 20:48:52 +02:00
MacRimi
230400846f Update proxmox-dashboard.tsx 2025-10-23 20:35:58 +02:00
MacRimi
ebb29ad04b Update proxmox-dashboard.tsx 2025-10-23 20:29:39 +02:00
MacRimi
2cd603357d Update proxmox-dashboard.tsx 2025-10-23 20:22:12 +02:00
MacRimi
bee26838e1 Update proxmox-dashboard.tsx 2025-10-23 20:12:56 +02:00
MacRimi
5b0572879d Update proxmox-dashboard.tsx 2025-10-23 20:04:56 +02:00
MacRimi
6e86275dce Update proxmox-dashboard.tsx 2025-10-23 19:57:11 +02:00
MacRimi
03a007b9b6 Update AppImage 2025-10-23 19:47:26 +02:00
MacRimi
dadb215ce0 Update AppImage 2025-10-23 19:39:24 +02:00
MacRimi
a1e3e12c6b Update globals.css 2025-10-23 19:34:30 +02:00
MacRimi
4274c817d3 Update AppImage 2025-10-23 19:29:19 +02:00
MacRimi
5abedc15dc Update proxmox-dashboard.tsx 2025-10-23 18:41:37 +02:00
MacRimi
947c9639e8 Update proxmox-dashboard.tsx 2025-10-23 18:31:44 +02:00
MacRimi
c4cdf4a834 Update AppImage 2025-10-23 18:22:00 +02:00
MacRimi
44b9bfee68 Update system-overview.tsx 2025-10-23 17:56:56 +02:00
MacRimi
ac9d43892b Update system-overview.tsx 2025-10-23 17:33:48 +02:00
MacRimi
9f62f8eff9 Update AppImage 2025-10-23 17:21:48 +02:00
MacRimi
13dd400795 Update hardware.tsx 2025-10-23 15:31:25 +02:00
MacRimi
37343a4114 Update hardware.tsx 2025-10-23 14:58:46 +02:00
MacRimi
3df6a4048a Update hardware.tsx 2025-10-23 14:38:17 +02:00
MacRimi
c9fb87b571 Update ProxMenux-beta5.AppImage 2025-10-23 13:27:36 +02:00
MacRimi
44ca1d507d Update metrics-dialog.tsx 2025-10-23 12:55:31 +02:00
MacRimi
30b236548a Update metrics-dialog.tsx 2025-10-23 12:47:28 +02:00
MacRimi
21b7d1c3fb Update virtual-machines.tsx 2025-10-23 12:36:48 +02:00
MacRimi
8d5ea66ecc Update network-metrics.tsx 2025-10-23 12:25:17 +02:00
MacRimi
b5ed10689d Update node-metrics-charts.tsx 2025-10-23 12:13:22 +02:00
MacRimi
a55cdfd7fa Update node-metrics-charts.tsx 2025-10-23 11:56:51 +02:00
MacRimi
39a4c10ac9 Update node-metrics-charts.tsx 2025-10-23 11:38:03 +02:00
MacRimi
c542cd4d7d Update node-metrics-charts.tsx 2025-10-23 11:00:36 +02:00
MacRimi
01de338a65 Update AppImage 2025-10-23 09:59:27 +02:00
MacRimi
f23f7b1983 Updata AppImage 2025-10-23 09:32:46 +02:00
MacRimi
a349ab62ec Update node-metrics-charts.tsx 2025-10-22 20:02:32 +02:00
MacRimi
e620010f10 Update AppImage 2025-10-22 19:50:26 +02:00
MacRimi
70509355de Uodate AppImage 2025-10-22 19:38:04 +02:00
MacRimi
f25654ead7 Updte AppImage 2025-10-22 19:25:04 +02:00
MacRimi
f1741d4dac Update AppImage 2025-10-22 19:07:18 +02:00
MacRimi
f3245d092b Update virtual-machines.tsx 2025-10-22 18:49:41 +02:00
MacRimi
a039a8600e Update virtual-machines.tsx 2025-10-22 18:38:04 +02:00
MacRimi
ee56f4a7a2 Update virtual-machines.tsx 2025-10-22 18:27:27 +02:00
MacRimi
c40b6ca7f4 Update AppImage 2025-10-22 18:17:57 +02:00
MacRimi
2c0e1e498b Update globals.css 2025-10-22 18:10:05 +02:00
MacRimi
0262ea31eb Update AppImage 2025-10-22 18:05:33 +02:00
MacRimi
4b671d7fb0 Update virtual-machines.tsx 2025-10-22 17:50:36 +02:00
MacRimi
d8f9419eb9 Update virtual-machines.tsx 2025-10-22 17:34:46 +02:00
MacRimi
aa65bab486 Update flask_server.py 2025-10-22 17:19:42 +02:00
MacRimi
849c3967fd Update virtual-machines.tsx 2025-10-22 17:03:27 +02:00
MacRimi
83562cf7d8 Update virtual-machines.tsx 2025-10-22 16:49:15 +02:00
MacRimi
2631a44410 Update AppImage 2025-10-22 16:15:49 +02:00
MacRimi
ddfea43b79 Update virtual-machines.tsx 2025-10-22 12:50:53 +02:00
MacRimi
68f19ffa5f Update virtual-machines.tsx 2025-10-22 12:37:52 +02:00
MacRimi
8800d42c32 Update virtual-machines.tsx 2025-10-22 12:19:03 +02:00
MacRimi
416a8a7cb2 Update virtual-machines.tsx 2025-10-22 12:03:51 +02:00
MacRimi
7088f249a5 Update virtual-machines.tsx 2025-10-22 11:45:06 +02:00
MacRimi
a2301cc980 Update virtual-machines.tsx 2025-10-22 11:24:56 +02:00
MacRimi
af545404e8 Update virtual-machines.tsx 2025-10-22 11:09:46 +02:00
MacRimi
2e96f19476 Update virtual-machines.tsx 2025-10-22 10:59:20 +02:00
MacRimi
e3f26b7f75 Update virtual-machines.tsx 2025-10-22 10:27:14 +02:00
MacRimi
f45c98a6a7 Update AppImage 2025-10-22 09:28:46 +02:00
ProxMenuxBot
a565a3c909 Update helpers_cache.json 2025-10-22 01:02:42 +00:00
MacRimi
b5e3dd6c06 Update AppImage 2025-10-21 20:47:04 +02:00
MacRimi
928f592d9c Update virtual-machines.tsx 2025-10-21 20:31:15 +02:00
MacRimi
8ee8edcd36 Update AppImage 2025-10-21 20:12:00 +02:00
MacRimi
1e128348e5 Update AppImage 2025-10-21 19:50:17 +02:00
MacRimi
6ef5655b7d Update virtual-machines.tsx 2025-10-21 19:16:44 +02:00
MacRimi
f6ba5329ce Update AppImage 2025-10-21 19:05:38 +02:00
MacRimi
93cef0d580 Update update-pve.sh 2025-10-21 18:56:33 +02:00
MacRimi
797b088cc8 aupdate AppImage 2025-10-21 18:43:56 +02:00
MacRimi
6d23d3510f Update AppImage 2025-10-21 18:04:35 +02:00
MacRimi
f2d7d0af43 Update AppImage 2025-10-21 17:57:05 +02:00
MacRimi
e55c0461db Update virtual-machines.tsx 2025-10-21 17:45:12 +02:00
MacRimi
8eca511a53 Uodate AppImage 2025-10-21 17:33:53 +02:00
MacRimi
f20e46dee0 Update AppImage 2025-10-21 17:20:16 +02:00
MacRimi
b79f22f4fe Add log directories for pveproxy with permissions
Create directories for pveproxy logs and set permissions
2025-10-21 14:26:37 +02:00
MacRimi
3287dc77e2 Update auto_post_install.sh 2025-10-21 14:24:41 +02:00
MacRimi
78a08b35e7 Update auto_post_install.sh 2025-10-21 14:06:10 +02:00
MacRimi
e86196999a Update auto_post_install.sh 2025-10-21 13:57:32 +02:00
MacRimi
4d50339041 Update journald configuration in auto_post_install.sh 2025-10-21 13:56:37 +02:00
MacRimi
edc5a2c0f2 Enhance Log2RAM installation script
Refactor Log2RAM installation and configuration script to improve error handling, cleanup previous installations, and adjust systemd-journald limits based on Log2RAM size.
2025-10-21 09:29:58 +02:00
MacRimi
598b88b1f0 Update virtual-machines.tsx 2025-10-20 23:48:07 +02:00
MacRimi
c22b9f8ff5 Update AppImage 2025-10-20 23:30:18 +02:00
MacRimi
3c654ab495 Update virtual-machines.tsx 2025-10-20 23:24:38 +02:00
MacRimi
2f0fabea7a Update virtual-machines.tsx 2025-10-20 23:16:56 +02:00
MacRimi
099b14efc3 Update virtual-machines.tsx 2025-10-20 23:02:52 +02:00
MacRimi
ee42dee366 Update virtual-machines.tsx 2025-10-20 22:40:37 +02:00
MacRimi
deae081cb3 Update AppImage 2025-10-20 22:15:08 +02:00
MacRimi
6479f14d3d Update AppImage 2025-10-20 20:56:54 +02:00
MacRimi
60707c3868 Update create VM 2025-10-20 20:22:31 +02:00
MacRimi
e78d8e1ae6 Update metrics-dialog.tsx 2025-10-20 20:17:46 +02:00
MacRimi
fee0d0aed9 Update metrics-dialog.tsx 2025-10-20 20:00:29 +02:00
MacRimi
178abc77ce Update metrics-dialog.tsx 2025-10-20 19:40:59 +02:00
MacRimi
7001f97d96 Update vm_creator.sh 2025-10-20 19:20:50 +02:00
MacRimi
55432e61ff Update metrics-dialog.tsx 2025-10-20 19:00:00 +02:00
MacRimi
4ee993ef3b Update metrics-dialog.tsx 2025-10-20 18:39:12 +02:00
MacRimi
64d471bb9b Update metrics-dialog.tsx 2025-10-20 17:30:39 +02:00
MacRimi
4dfcdcb0b2 Update metrics-dialog.tsx 2025-10-20 17:11:30 +02:00
MacRimi
8e69a84e7a Update metrics-dialog.tsx 2025-10-20 16:52:16 +02:00
MacRimi
8ec643d882 Update metrics-dialog.tsx 2025-10-20 16:43:07 +02:00
ProxMenuxBot
f4d6192c80 Update helpers_cache.json 2025-10-20 12:28:47 +00:00
MacRimi
c0aa2b85fc Update metrics-dialog.tsx 2025-10-19 17:53:39 +02:00
MacRimi
61a376fb6d Update virtual-machines.tsx 2025-10-19 17:46:47 +02:00
MacRimi
8786cb5180 Update AppImage 2025-10-19 17:40:13 +02:00
MacRimi
c305ef1360 Update AppImage 2025-10-19 17:29:23 +02:00
MacRimi
f662ce0b7a Update AppImage 2025-10-19 17:16:35 +02:00
MacRimi
71af9345a5 Update AppImage 2025-10-19 16:51:52 +02:00
MacRimi
a819a19c77 Update virtual-machines.tsx 2025-10-19 16:19:47 +02:00
MacRimi
c65fad06b7 Update virtual-machines.tsx 2025-10-19 16:06:19 +02:00
MacRimi
9e6e1931b1 Update customizable_post_install.sh 2025-10-19 09:48:38 +02:00
MacRimi
3b22273f5a Update virtual-machines.tsx 2025-10-18 18:57:14 +02:00
MacRimi
fc5ff1782b Update virtual-machines.tsx 2025-10-18 18:48:01 +02:00
MacRimi
4d3b3d984d Update virtual-machines.tsx 2025-10-18 18:37:22 +02:00
MacRimi
514976561f Update hardware.tsx 2025-10-18 18:32:13 +02:00
MacRimi
fb4998d21b Update auto_post_install.sh 2025-10-18 18:24:05 +02:00
MacRimi
0feec978d3 Update system-logs.tsx 2025-10-18 18:18:57 +02:00
MacRimi
5f1c39aba5 Update system-logs.tsx 2025-10-18 18:04:05 +02:00
MacRimi
17973619de Update system-logs.tsx 2025-10-18 17:54:06 +02:00
MacRimi
478d7a2d2d Update system-logs.tsx 2025-10-18 17:45:13 +02:00
MacRimi
f2af0be1e1 Update system-logs.tsx 2025-10-18 17:36:08 +02:00
MacRimi
d0725f5098 Update system-logs.tsx 2025-10-18 17:18:41 +02:00
MacRimi
646d614d94 Update update-pve.sh 2025-10-18 17:12:11 +02:00
MacRimi
4c337ef5e9 Update system-logs.tsx 2025-10-18 17:02:16 +02:00
MacRimi
2b633b8566 Update system-logs.tsx 2025-10-18 16:47:04 +02:00
MacRimi
6b16454217 Update system-logs.tsx 2025-10-18 16:36:31 +02:00
MacRimi
b7086deeac Update zimaos.sh 2025-10-18 16:16:48 +02:00
MacRimi
f021afb6a4 Update AppImage and ZimaOS 2025-10-18 16:11:28 +02:00
ProxMenuxBot
99622bd3d6 Update helpers_cache.json 2025-10-18 12:23:38 +00:00
MacRimi
50a76519ea Update system-logs.tsx 2025-10-18 12:39:26 +02:00
MacRimi
1e806054ab Update system-logs.tsx 2025-10-18 12:26:27 +02:00
MacRimi
89d7f335fc Update system-logs.tsx 2025-10-18 11:58:01 +02:00
MacRimi
7a664ec4ec Update system-logs.tsx 2025-10-18 11:41:49 +02:00
MacRimi
a8a4d029f8 Update system-logs.tsx 2025-10-18 11:35:14 +02:00
MacRimi
501b5dce76 Update system-logs.tsx 2025-10-18 11:25:16 +02:00
MacRimi
e9b3504370 Update system-logs.tsx 2025-10-18 11:13:01 +02:00
MacRimi
7b20c78e73 Update system-logs.tsx 2025-10-18 11:05:27 +02:00
MacRimi
a343ce69aa Update system-logs.tsx 2025-10-18 10:58:08 +02:00
MacRimi
d52ce400fb Update AppImage 2025-10-18 10:43:58 +02:00
MacRimi
0ee574eaaa Update system-logs.tsx 2025-10-18 10:34:34 +02:00
MacRimi
74c4392b6d Update AppImage 2025-10-18 10:28:04 +02:00
MacRimi
d844c330e9 Update system-logs.tsx 2025-10-18 10:11:43 +02:00
MacRimi
b50cb78fa6 Update system-logs.tsx 2025-10-18 09:56:53 +02:00
MacRimi
26c138f42c Update AppImage 2025-10-17 20:32:17 +02:00
MacRimi
0ec7e65926 Update proxmox-dashboard.tsx 2025-10-17 20:27:02 +02:00
MacRimi
3588cc4c03 Update proxmox-dashboard.tsx 2025-10-17 20:19:37 +02:00
MacRimi
da8c7749c8 Update proxmox-dashboard.tsx 2025-10-17 20:09:26 +02:00
MacRimi
79fe999e77 Update proxmox-dashboard.tsx 2025-10-17 20:01:10 +02:00
MacRimi
439c65ad6d Update AppImage 2025-10-17 19:51:41 +02:00
MacRimi
81b3aa5ac1 Update system-logs.tsx 2025-10-17 19:44:19 +02:00
MacRimi
c4beb9ae4d Update hardware.tsx 2025-10-17 19:34:35 +02:00
MacRimi
9d286d8378 Update network-metrics.tsx 2025-10-17 19:24:14 +02:00
MacRimi
19e7a43fe3 Update network-metrics.tsx 2025-10-17 19:16:55 +02:00
MacRimi
ab59e2deac Update AppImage 2025-10-17 19:06:15 +02:00
MacRimi
043f22e6ec Update AppImage 2025-10-17 18:52:18 +02:00
MacRimi
4abb6af31e Update AppImagen 2025-10-17 18:30:18 +02:00
MacRimi
a17ba4a81f Update virtual-machines.tsx 2025-10-17 18:15:46 +02:00
MacRimi
bc8a6847e3 Update virtual-machines.tsx 2025-10-17 18:10:12 +02:00
MacRimi
18f97f9df2 Update AppImage 2025-10-17 18:03:01 +02:00
MacRimi
4a204d8d89 Update AppImage 2025-10-17 17:53:06 +02:00
MacRimi
062c6c2364 Update AppImage 2025-10-17 17:38:13 +02:00
MacRimi
477716ef67 Update virtual-machines.tsx 2025-10-17 17:22:10 +02:00
MacRimi
ef973df7c9 Update flask_server.py 2025-10-17 17:04:55 +02:00
MacRimi
c40bb6a4d5 Create rafa.AppImage 2025-10-16 21:29:45 +02:00
MacRimi
20e942dccd Update storage-overview.tsx 2025-10-16 21:19:03 +02:00
MacRimi
598cbc4d11 Update AppImage 2025-10-16 19:57:55 +02:00
MacRimi
70a3d5af07 Update flask_server.py 2025-10-16 19:34:45 +02:00
MacRimi
9cabb1afbd Update hardware.tsx 2025-10-16 19:23:41 +02:00
MacRimi
4267224f59 Create ProxMenux-beta5.AppImage 2025-10-16 09:41:04 +02:00
MacRimi
02d910f53c Update flask_server.py 2025-10-16 09:31:35 +02:00
MacRimi
e81b7b5b9f Update AppImage 2025-10-16 09:07:39 +02:00
ProxMenuxBot
17e0a8eec1 Update helpers_cache.json 2025-10-16 01:00:35 +00:00
MacRimi
eb322c9d41 Create ProxMenux-beta4.AppImage 2025-10-15 20:51:42 +02:00
MacRimi
751af92c21 Update proxmox-dashboard.tsx 2025-10-15 20:42:51 +02:00
MacRimi
fb4962a41a Update proxmox-dashboard.tsx 2025-10-15 20:35:10 +02:00
MacRimi
daf6598599 Update AppImage 2025-10-15 20:27:30 +02:00
MacRimi
e1e4f71f3a Update virtual-machines.tsx 2025-10-15 20:13:01 +02:00
MacRimi
a2fa7ec9c4 Update virtual-machines.tsx 2025-10-15 19:51:24 +02:00
MacRimi
5cd37b74b4 Update flask_server.py 2025-10-15 19:22:16 +02:00
MacRimi
beed7e83f2 Update storage-overview.tsx 2025-10-15 19:06:33 +02:00
MacRimi
a7726edca6 Update storage-overview.tsx 2025-10-15 18:56:02 +02:00
MacRimi
eed0c21c41 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-10-15 18:41:21 +02:00
MacRimi
094a43157e Update storage-overview.tsx 2025-10-15 18:41:03 +02:00
MacRimi
4033958bf5 Merge pull request #37 from MrCaringi/main
🌶️ Contributors rocks! 🌶️
2025-10-15 18:23:37 +02:00
MacRimi
f18784ecc1 Update flask_server.py 2025-10-15 18:22:04 +02:00
JFC
418494b7f3 Merge pull request #1 from MrCaringi/MrCaringi-contributors
Update README.md
2025-10-15 10:20:16 -06:00
JFC
343753ee2a Update README.md 2025-10-15 10:18:43 -06:00
MacRimi
1ceda066c4 Update hardware.tsx 2025-10-15 18:08:49 +02:00
MacRimi
3c13dea55f Update hardware.tsx 2025-10-15 17:54:04 +02:00
MacRimi
6ced2cbf7c Update flask_server.py 2025-10-15 17:24:06 +02:00
MacRimi
b41f16a736 Update storage-overview.tsx 2025-10-15 17:04:10 +02:00
MacRimi
42fa02a887 Create ProxMenux-beta3.AppImage 2025-10-15 00:09:51 +02:00
MacRimi
2ce87cdac0 Update AppImage 2025-10-15 00:03:56 +02:00
MacRimi
e4864d3871 Update AppImage 2025-10-14 23:57:46 +02:00
MacRimi
b6e0052013 Update storage-overview.tsx 2025-10-14 23:42:00 +02:00
MacRimi
9ef83a59c7 Update flask_server.py 2025-10-14 23:21:09 +02:00
MacRimi
325724ff85 Update flask_server.py 2025-10-14 23:16:56 +02:00
MacRimi
998bfa0656 Update flask_server.py 2025-10-14 22:47:13 +02:00
MacRimi
e476df5e7d Update flask_server.py 2025-10-14 22:36:50 +02:00
MacRimi
83a3601cdb Update flask_server.py 2025-10-14 22:28:28 +02:00
MacRimi
996dcc4b23 Update AppImage 2025-10-14 22:14:48 +02:00
MacRimi
04304f8283 Update storage-overview.tsx 2025-10-14 21:31:15 +02:00
MacRimi
3418f73390 Update storage-overview.tsx 2025-10-14 21:22:21 +02:00
MacRimi
b07b6c8960 Update storage-overview.tsx 2025-10-14 21:11:34 +02:00
MacRimi
4f2cf37d73 Update storage-overview.tsx 2025-10-14 20:54:41 +02:00
MacRimi
408e017f2f Update virtual-machines.tsx 2025-10-14 20:31:53 +02:00
MacRimi
62b42266e8 Update virtual-machines.tsx 2025-10-14 20:22:55 +02:00
MacRimi
80e9e23965 Update virtual-machines.tsx 2025-10-14 20:04:18 +02:00
MacRimi
c73154aeb1 Update virtual-machines.tsx 2025-10-14 19:48:57 +02:00
MacRimi
66c4786ec2 Update virtual-machines.tsx 2025-10-14 19:35:25 +02:00
MacRimi
143f5a2085 Update hardware.tsx 2025-10-14 19:00:24 +02:00
MacRimi
699c7df798 Update AppImage 2025-10-14 18:51:39 +02:00
MacRimi
d3de7b95aa Update flask_server.py 2025-10-14 18:09:48 +02:00
MacRimi
2dc6f76da9 Update hardware.tsx 2025-10-14 17:38:26 +02:00
MacRimi
63ccf6b553 Update hardware.tsx 2025-10-14 17:23:59 +02:00
MacRimi
f49ffe3cb0 Update AppImage 2025-10-14 15:34:19 +02:00
MacRimi
c1b578350d Update flask_server.py 2025-10-14 15:19:18 +02:00
MacRimi
48e4af41ae Update flask_server.py 2025-10-14 14:28:08 +02:00
MacRimi
5e915f9c40 Update flask_server.py 2025-10-14 13:56:37 +02:00
MacRimi
70b5f91f82 Update AppImage 2025-10-14 13:37:48 +02:00
MacRimi
792df08c78 Update hardware.tsx 2025-10-14 12:58:53 +02:00
MacRimi
032b5d3580 Update AppImage 2025-10-14 12:40:27 +02:00
MacRimi
8f93e43bb3 Update AppImage 2025-10-14 11:37:30 +02:00
MacRimi
04f95e648b Update AppImage 2025-10-14 11:08:47 +02:00
MacRimi
511b8eb407 Update storage-metrics.tsx 2025-10-14 10:47:30 +02:00
MacRimi
a6e6dd255d Update storage-metrics.tsx 2025-10-14 10:25:18 +02:00
MacRimi
c3c53d4056 Update storage-metrics.tsx 2025-10-14 09:33:03 +02:00
MacRimi
2f700d9a4c Update AppImage 2025-10-14 09:15:44 +02:00
MacRimi
e4da9f5afe Update storage-metrics.tsx 2025-10-14 09:06:17 +02:00
MacRimi
3f919813f2 Update AppImage 2025-10-14 08:54:56 +02:00
MacRimi
4063ffc163 Update storage-metrics.tsx 2025-10-14 00:20:15 +02:00
MacRimi
0d08b89853 Update storage-metrics.tsx 2025-10-14 00:13:10 +02:00
MacRimi
6c9da364d0 Update storage-metrics.tsx 2025-10-14 00:03:22 +02:00
MacRimi
c1614e8241 Update AppImage 2025-10-13 23:50:31 +02:00
MacRimi
75a458f2be Update AppImage 2025-10-13 19:04:38 +02:00
MacRimi
602291736f Update flask_server.py 2025-10-13 17:53:34 +02:00
MacRimi
9f2d15e590 Update AppImage 2025-10-13 17:40:08 +02:00
MacRimi
5e88201d47 Update flask_server.py 2025-10-13 17:03:39 +02:00
MacRimi
598b8bd1cd Update AppImage 2025-10-13 15:22:19 +02:00
MacRimi
9186a44860 Update AppImage 2025-10-13 15:06:03 +02:00
MacRimi
61e3dae708 Update AppImage 2025-10-12 21:19:01 +02:00
MacRimi
94d46299d0 Update proxmox-dashboard.tsx 2025-10-12 21:10:24 +02:00
MacRimi
7070c05f2f Update proxmox-dashboard.tsx 2025-10-12 21:00:42 +02:00
MacRimi
0b7038cc65 Update proxmox-dashboard.tsx 2025-10-12 20:47:58 +02:00
MacRimi
4882d04ece Update system-logs.tsx 2025-10-12 20:36:12 +02:00
MacRimi
4b2d34491e Update system-logs.tsx 2025-10-12 20:28:10 +02:00
MacRimi
79d8230821 Update system-logs.tsx 2025-10-12 20:20:32 +02:00
MacRimi
c7e3305a76 Update system-logs.tsx 2025-10-12 20:12:55 +02:00
MacRimi
81feccf0d2 Update AppImage 2025-10-12 20:03:40 +02:00
MacRimi
9666bee006 Update AppImage 2025-10-12 19:51:03 +02:00
MacRimi
44d54057d0 Update hardware.tsx 2025-10-12 19:40:35 +02:00
MacRimi
beb4251688 Update network-metrics.tsx 2025-10-12 19:32:17 +02:00
MacRimi
598395cd38 Update system-overview.tsx 2025-10-12 19:20:05 +02:00
MacRimi
0a6913f5d0 Update system-overview.tsx 2025-10-12 19:11:39 +02:00
MacRimi
fa36458303 Update AppImage 2025-10-12 18:51:38 +02:00
MacRimi
3f96f88027 Update system-overview.tsx 2025-10-12 18:19:50 +02:00
MacRimi
131ab714ba Update system-overview.tsx 2025-10-12 18:05:35 +02:00
MacRimi
333d0c933a Update system-overview.tsx 2025-10-12 17:50:15 +02:00
MacRimi
2a5c0e05cc Update flask_server.py 2025-10-12 17:37:51 +02:00
MacRimi
4bda9da860 Update AppImage 2025-10-12 17:24:13 +02:00
MacRimi
4c579cf862 Update flask_server.py 2025-10-12 17:09:01 +02:00
MacRimi
1b74ce7ac0 Update flask_server.py 2025-10-12 16:32:29 +02:00
MacRimi
29e3625c7b Update flask_server.py 2025-10-12 16:09:38 +02:00
MacRimi
a41b9381a1 Update system-logs.tsx 2025-10-12 16:00:52 +02:00
MacRimi
b4980a968c Update system-logs.tsx 2025-10-12 15:39:31 +02:00
MacRimi
ea91751217 Update system-logs.tsx 2025-10-12 14:51:07 +02:00
MacRimi
1ceffc3391 Update system-logs.tsx 2025-10-12 03:12:15 +02:00
MacRimi
3de31427a3 Update system-logs.tsx 2025-10-12 02:58:11 +02:00
MacRimi
4abb9c2ea6 Update system-logs.tsx 2025-10-12 02:44:49 +02:00
MacRimi
e7bfbe77c2 Update system-logs.tsx 2025-10-12 02:04:42 +02:00
MacRimi
776282ed6b Update system-logs.tsx 2025-10-12 01:46:48 +02:00
MacRimi
d1621684df Update flask_server.py 2025-10-12 01:26:58 +02:00
MacRimi
ba183e71e1 Update AppImage 2025-10-12 01:09:33 +02:00
MacRimi
aac34d4fad Update system-logs.tsx 2025-10-12 00:54:23 +02:00
MacRimi
8e28e4ecbf Update AppImage 2025-10-12 00:45:38 +02:00
MacRimi
48665aa1ad Update AppImage 2025-10-12 00:41:15 +02:00
MacRimi
f34968bcf5 Update calendar.tsx 2025-10-12 00:35:47 +02:00
MacRimi
4a5c1ed582 Update AppImage 2025-10-11 19:43:15 +02:00
MacRimi
6d87ab08e2 Update AppImage 2025-10-11 19:35:04 +02:00
MacRimi
5ae18bf4f9 Update calendar.tsx 2025-10-11 19:24:20 +02:00
MacRimi
1bac12259d Update system-logs.tsx 2025-10-11 19:16:44 +02:00
MacRimi
434dc408c3 Update calendar.tsx 2025-10-11 19:09:24 +02:00
MacRimi
6601ee3b12 Update AppImage 2025-10-11 19:04:02 +02:00
MacRimi
fde1731365 Update AppImage 2025-10-11 18:51:50 +02:00
MacRimi
7725952776 Update AppImage 2025-10-11 18:37:26 +02:00
MacRimi
e18ee08b70 Update AppImge 2025-10-11 18:13:35 +02:00
MacRimi
5aaaeb426c Update AppImage 2025-10-11 17:55:25 +02:00
MacRimi
1f55a0cbd8 Update AppImage 2025-10-11 17:34:10 +02:00
MacRimi
4ad026b398 Update flask_server.py 2025-10-11 17:29:17 +02:00
MacRimi
d36825da52 Update AppImage 2025-10-11 17:18:52 +02:00
MacRimi
bf2715c2be Update AppImage 2025-10-11 16:51:27 +02:00
MacRimi
80953a0148 Update AppImage 2025-10-11 16:25:22 +02:00
MacRimi
bb9a08d00d Update system-logs.tsx 2025-10-11 16:04:42 +02:00
MacRimi
3e8fa7cba7 Update system-logs.tsx 2025-10-11 12:06:59 +02:00
MacRimi
da8b88b6b2 Update flask_server.py 2025-10-11 11:42:02 +02:00
MacRimi
4bd21a1ccb Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-10-11 11:30:47 +02:00
MacRimi
29f7586b93 Update AppImage 2025-10-11 11:30:45 +02:00
ProxMenuxBot
39b6b725f5 Update helpers_cache.json 2025-10-11 00:56:17 +00:00
MacRimi
631c68029a Update AppImage 2025-10-11 01:36:42 +02:00
MacRimi
93aefc127f Update storage-overview.tsx 2025-10-11 00:21:22 +02:00
MacRimi
19ac88b560 Update flask_server.py 2025-10-11 00:11:12 +02:00
MacRimi
99e775a283 Update hardware.tsx 2025-10-11 00:00:21 +02:00
MacRimi
7fc05b96a2 Update flask_server.py 2025-10-10 23:52:06 +02:00
MacRimi
9b811da43d Update hardware.tsx 2025-10-10 23:39:31 +02:00
MacRimi
4199b609b4 Updata AppImage 2025-10-10 23:19:29 +02:00
MacRimi
958e6d8519 Update flask_server.py 2025-10-10 23:06:04 +02:00
MacRimi
9dea22ab05 Update hardware.tsx 2025-10-10 22:52:22 +02:00
MacRimi
3dc7c6b36f Update flask_server.py 2025-10-10 22:43:02 +02:00
MacRimi
44e76f36b4 Update flask_server.py 2025-10-10 22:33:45 +02:00
MacRimi
010333a190 Update flask_server.py 2025-10-10 21:43:43 +02:00
MacRimi
ba833a265a Update AppImage 2025-10-10 21:38:19 +02:00
MacRimi
e0bf156272 Update hardware.tsx 2025-10-10 21:18:49 +02:00
MacRimi
a654e21b27 Update flask_server.py 2025-10-10 21:06:00 +02:00
MacRimi
de6f149e3b Update flask_server.py 2025-10-10 17:37:30 +02:00
MacRimi
29893b89b3 Update flask_server.py 2025-10-10 17:09:23 +02:00
MacRimi
7783e9ed20 Update flask_server.py 2025-10-10 16:52:43 +02:00
MacRimi
d93d1ed48a Update AppImage 2025-10-10 16:17:55 +02:00
MacRimi
32c461e93b Update AppImage 2025-10-10 15:40:41 +02:00
MacRimi
d88e6153c1 Update flask_server.py 2025-10-10 12:45:08 +02:00
MacRimi
7b980ae4d4 Update flask_server.py 2025-10-10 12:40:58 +02:00
MacRimi
b249d37bab Update flask_server.py 2025-10-10 12:29:47 +02:00
MacRimi
fa34e081cc Update hardware.tsx 2025-10-10 01:06:17 +02:00
MacRimi
9f795d7256 Update flask_server.py 2025-10-10 00:48:56 +02:00
MacRimi
c8d7d6be43 Update hardware.tsx 2025-10-10 00:38:57 +02:00
MacRimi
c31124eb14 Update hardware.tsx 2025-10-10 00:27:22 +02:00
MacRimi
e999b7a8f8 Update hardware.tsx 2025-10-10 00:13:54 +02:00
MacRimi
49353a5ec5 Update hardware.tsx 2025-10-09 23:56:43 +02:00
MacRimi
229fbdd306 Update hardware.tsx 2025-10-09 23:46:26 +02:00
MacRimi
4562dd08dc Update hardware.tsx 2025-10-09 23:34:44 +02:00
MacRimi
f24f4ea8f9 Update hardware.tsx 2025-10-09 23:14:47 +02:00
MacRimi
6338d38ab6 Update flask_server.py 2025-10-09 23:00:25 +02:00
MacRimi
527d93c6b4 Update flask_server.py 2025-10-09 22:48:33 +02:00
MacRimi
3f5f8d9f57 Update flask_server.py 2025-10-09 22:37:02 +02:00
MacRimi
245c913ba1 Update flask_server.py 2025-10-09 22:17:09 +02:00
MacRimi
7d3ef52f03 Update flask_server.py 2025-10-09 21:16:55 +02:00
MacRimi
6cd7556bc5 Update flask_server.py 2025-10-09 20:58:43 +02:00
MacRimi
123f0594a3 Update flask_server.py 2025-10-09 20:40:29 +02:00
MacRimi
652cebc7d0 Update flask_server.py 2025-10-09 20:26:21 +02:00
MacRimi
a4cb9a8923 Update flask_server.py 2025-10-09 20:18:49 +02:00
MacRimi
eb954fb10d Update flask_server.py 2025-10-09 19:52:44 +02:00
MacRimi
845eab6f53 Update flask_server.py 2025-10-09 19:38:54 +02:00
MacRimi
4fe20db497 Update AppImage 2025-10-09 19:30:12 +02:00
MacRimi
c40d503f6e Update flask_server.py 2025-10-09 19:24:39 +02:00
MacRimi
1ea843bde4 Update flask_server.py 2025-10-09 19:21:32 +02:00
MacRimi
9ed5d70250 Update flask_server.py 2025-10-09 19:08:43 +02:00
MacRimi
f6209b97e2 Update AppImage 2025-10-09 19:00:58 +02:00
MacRimi
5221ad6da7 Update flask_server.py 2025-10-09 18:46:16 +02:00
MacRimi
cd0bded428 Update flask_server.py 2025-10-09 18:09:59 +02:00
MacRimi
ff5fddf353 Update flask_server.py 2025-10-09 18:01:46 +02:00
MacRimi
28b29ed086 Update flask_server.py 2025-10-09 17:03:01 +02:00
MacRimi
765b2b1d69 Update flask_server.py 2025-10-09 16:46:07 +02:00
MacRimi
599a434faa Update flask_server.py 2025-10-09 16:23:19 +02:00
MacRimi
a2abee986d Update flask_server.py 2025-10-09 16:06:42 +02:00
MacRimi
d57c0712b0 Update flask_server.py 2025-10-09 15:44:17 +02:00
MacRimi
4166d78e87 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-10-09 15:35:43 +02:00
MacRimi
5322291402 Update flask_server.py 2025-10-09 15:35:40 +02:00
ProxMenuxBot
dc2ffd758d Update helpers_cache.json 2025-10-09 12:26:29 +00:00
MacRimi
b73cdb2e7d Update flask_server.py 2025-10-09 10:29:21 +02:00
MacRimi
3a83e5d519 Update flask_server.py 2025-10-09 10:00:06 +02:00
MacRimi
3c0cdcadc0 Update flask_server.py 2025-10-09 09:48:18 +02:00
MacRimi
0a23ef8b5d Update flask_server.py 2025-10-08 18:26:28 +02:00
MacRimi
c8a38ac709 Update flask_server.py 2025-10-08 18:19:19 +02:00
MacRimi
31d15fadbe Update flask_server.py 2025-10-08 18:15:38 +02:00
MacRimi
3fc481e302 Update hardware.tsx 2025-10-08 12:06:24 +02:00
MacRimi
803605c318 Update AppImage 2025-10-08 11:43:45 +02:00
MacRimi
1d138e3b4b Update flask_server.py 2025-10-07 23:42:15 +02:00
MacRimi
5b6c5326b6 Update AppImage 2025-10-07 23:36:13 +02:00
MacRimi
60a1c303da Update hardware.tsx 2025-10-07 23:04:44 +02:00
MacRimi
b9f32da7b8 Update flask_server.py 2025-10-07 22:38:49 +02:00
MacRimi
fde0b1d8bf Update AppImage 2025-10-07 22:26:49 +02:00
MacRimi
cf07004fcd Update flask_server.py 2025-10-07 21:59:57 +02:00
MacRimi
b41b52df84 Update flask_server.py 2025-10-07 21:49:29 +02:00
MacRimi
9632dd170a Update hardware.tsx 2025-10-07 21:32:54 +02:00
MacRimi
9dc334dea9 Update flask_server.py 2025-10-07 21:15:01 +02:00
MacRimi
0741079450 Update flask_server.py 2025-10-07 21:00:03 +02:00
MacRimi
562df0f48f Update flask_server.py 2025-10-07 20:26:05 +02:00
MacRimi
3b87c078f4 Update flask_server.py 2025-10-07 20:15:42 +02:00
MacRimi
fc091665ff Update flask_server.py 2025-10-07 19:59:18 +02:00
MacRimi
3f2842a9a3 Update flask_server.py 2025-10-07 18:26:37 +02:00
MacRimi
f2418c81d7 Update flask_server.py 2025-10-07 18:22:15 +02:00
MacRimi
3c85797cc9 Update flask_server.py 2025-10-07 18:15:33 +02:00
MacRimi
9187bf0b83 Update flask_server.py 2025-10-07 17:53:57 +02:00
MacRimi
5a2381d9dd Update flask_server.py 2025-10-07 17:43:12 +02:00
MacRimi
5fb8bb0dac Update flask_server.py 2025-10-07 17:22:14 +02:00
MacRimi
0d55da18d4 Update hardware.tsx 2025-10-07 12:08:49 +02:00
MacRimi
73148d65bb Update hardware.tsx 2025-10-07 11:22:51 +02:00
MacRimi
e81438e49f Update AppImage 2025-10-07 11:10:20 +02:00
MacRimi
e247d8095e Update AppImage 2025-10-07 10:49:43 +02:00
MacRimi
46ddb36c79 Update hardware.tsx 2025-10-07 03:03:45 +02:00
MacRimi
a87fee906f Update flask_server.py 2025-10-07 02:51:10 +02:00
MacRimi
888d94131e Update AppImage 2025-10-07 02:46:35 +02:00
MacRimi
63dd018756 Update flask_server.py 2025-10-07 02:29:07 +02:00
MacRimi
db38571646 Update flask_server.py 2025-10-07 02:23:55 +02:00
MacRimi
8111d96a20 Update flask_server.py 2025-10-07 02:02:14 +02:00
MacRimi
a1b5b7c03c Update hardware.tsx 2025-10-07 01:37:43 +02:00
MacRimi
91e95b1ef2 Update flask_server.py 2025-10-07 01:31:29 +02:00
MacRimi
1491f35f5e Update AppImage 2025-10-07 01:24:02 +02:00
MacRimi
9bb127dda7 Update flask_server.py 2025-10-07 01:10:54 +02:00
MacRimi
c4348b0cb2 Update flask_server.py 2025-10-07 00:56:41 +02:00
MacRimi
a3fc0c7f96 Update hardware.tsx 2025-10-07 00:50:17 +02:00
MacRimi
c7387068cc Update hardware.tsx 2025-10-07 00:44:26 +02:00
MacRimi
658ce390e2 Update AppImage 2025-10-07 00:35:23 +02:00
MacRimi
f0b6f66be6 Update AppImage 2025-10-07 00:24:35 +02:00
MacRimi
304812e14f Update flask_server.py 2025-10-06 23:57:02 +02:00
MacRimi
92d8a05393 Update AppImage 2025-10-06 23:51:35 +02:00
MacRimi
d96d98b8f4 Update AppImage 2025-10-06 23:40:54 +02:00
MacRimi
0d059187ec Update hardware.tsx 2025-10-06 23:26:26 +02:00
MacRimi
1b73b0b861 Update hardware.tsx 2025-10-06 23:16:31 +02:00
MacRimi
29f8d6b981 Update AppImage 2025-10-06 22:58:54 +02:00
MacRimi
7826de9d29 Update flask_server.py 2025-10-06 22:44:24 +02:00
MacRimi
78c56e4f28 Update AppImage 2025-10-06 22:39:37 +02:00
MacRimi
3ef7736e85 Update hardware.tsx 2025-10-06 22:23:56 +02:00
MacRimi
73e6194551 Update AppImage 2025-10-06 22:19:42 +02:00
MacRimi
7ceed3dfbc Update flask_server.py 2025-10-06 19:15:17 +02:00
MacRimi
7a0c2dc261 Update AppImage 2025-10-06 19:08:21 +02:00
MacRimi
5807c4d97f Update flask_server.py 2025-10-06 19:00:36 +02:00
MacRimi
a689607e98 Update AppImage 2025-10-06 18:52:58 +02:00
MacRimi
b6b3e27408 Update AppImage 2025-10-06 18:29:00 +02:00
MacRimi
ac30bd6e51 Update hardware.ts 2025-10-06 18:15:56 +02:00
MacRimi
174fc4f72b Update flask_server.py 2025-10-06 18:09:07 +02:00
MacRimi
047ec982f4 Update AppImage 2025-10-06 18:02:40 +02:00
MacRimi
e427f37f0e Update AppImage 2025-10-06 17:40:42 +02:00
MacRimi
810ac1fcfa Update AppImage 2025-10-06 17:25:08 +02:00
MacRimi
5ee3cc6712 Update build_appimage.sh 2025-10-06 17:00:52 +02:00
MacRimi
5ad3d5697e Update build_appimage.sh 2025-10-06 16:54:12 +02:00
MacRimi
874ab093d5 Update AppImage 2025-10-06 16:40:14 +02:00
MacRimi
fb668859b0 Update build_appimage.sh 2025-10-06 14:53:10 +02:00
MacRimi
be7a2d7f41 Update AppImage 2025-10-06 14:17:14 +02:00
MacRimi
154b6b9f74 Update AppImage 2025-10-06 14:12:28 +02:00
MacRimi
23c91386dc Update AppImage 2025-10-06 13:48:02 +02:00
MacRimi
741b6ce0d9 Update AppImage 2025-10-06 13:06:27 +02:00
MacRimi
600c2f6061 Update AppImagen 2025-10-06 12:09:43 +02:00
MacRimi
359de2dbe0 Update build_appimage.sh 2025-10-06 11:06:19 +02:00
MacRimi
84eec4655a Update AppImage 2025-10-06 11:02:00 +02:00
MacRimi
7e8c69a02d Update hardware.tsx 2025-10-05 22:46:14 +02:00
MacRimi
730d47f2f7 Update AppImage 2025-10-05 22:40:38 +02:00
MacRimi
5afb74e606 Update hardware.tsx 2025-10-05 22:34:30 +02:00
MacRimi
8a21547668 Update hardware.tsx 2025-10-05 22:27:20 +02:00
MacRimi
3ee3044270 Update flask_server.py 2025-10-05 22:15:32 +02:00
MacRimi
782dc24eba Update AppImage 2025-10-05 22:10:24 +02:00
MacRimi
475b96178e Update AppImage 2025-10-05 21:59:44 +02:00
MacRimi
4beba53675 Update AppImage 2025-10-05 21:44:22 +02:00
MacRimi
efb7cad993 Update flask_server.py 2025-10-05 21:24:15 +02:00
MacRimi
7b7705866d Update proxmox-dashboard.tsx 2025-10-05 21:15:51 +02:00
MacRimi
b7e06d51ea Create sheet.tsx 2025-10-05 21:02:20 +02:00
MacRimi
95476276ac Update proxmox-dashboard.tsx 2025-10-05 20:58:15 +02:00
MacRimi
2347e10458 Update AppImage 2025-10-05 20:45:54 +02:00
MacRimi
85051f1340 Update virtual-machines.tsx 2025-10-05 20:30:47 +02:00
MacRimi
42c6e70ebe Update virtual-machines.tsx 2025-10-05 20:24:30 +02:00
MacRimi
b9fe83e7a8 Update virtual-machines.tsx 2025-10-05 20:12:33 +02:00
MacRimi
841108623f Update virtual-machines.tsx 2025-10-05 17:27:10 +02:00
MacRimi
8ce221e41b Update virtual-machines.tsx 2025-10-05 17:18:26 +02:00
MacRimi
f8c41ab39f Update virtual-machines.tsx 2025-10-05 17:10:28 +02:00
MacRimi
79e7fd175e Update virtual-machines.tsx 2025-10-05 17:01:50 +02:00
MacRimi
fbcf755591 Update virtual-machines.tsx 2025-10-05 16:28:12 +02:00
MacRimi
6168a47e24 Update network-metrics.tsx 2025-10-05 16:15:45 +02:00
MacRimi
d788114be3 Update AppImage 2025-10-05 16:05:54 +02:00
MacRimi
497814f80c Update AppImage 2025-10-05 15:54:24 +02:00
MacRimi
7297edf16f Update AppImage 2025-10-05 15:44:19 +02:00
MacRimi
714407eb46 Update virtual-machines.tsx 2025-10-05 15:38:29 +02:00
MacRimi
dd3523ddd7 Update virtual-machines.tsx 2025-10-05 15:18:50 +02:00
MacRimi
7739de5db9 Update AppImage 2025-10-05 15:00:42 +02:00
MacRimi
99c08026ee Update network-metrics.tsx 2025-10-05 14:33:47 +02:00
MacRimi
19f7ea70f0 Update AppImage 2025-10-05 14:16:21 +02:00
MacRimi
49050c042d Update AppImage 2025-10-05 13:50:29 +02:00
MacRimi
18ccff5759 Update AppImage 2025-10-05 13:15:44 +02:00
MacRimi
9f6f646e77 Update virtual-machines.tsx 2025-10-05 12:56:06 +02:00
MacRimi
b8c0d8ef79 Update AppImage 2025-10-05 12:48:34 +02:00
MacRimi
2ccd41bfb9 Update AppImage 2025-10-05 12:32:09 +02:00
MacRimi
fa64b51d4a Update AppImage 2025-10-05 12:03:47 +02:00
MacRimi
f5ac194008 Update AppImage 2025-10-05 11:48:32 +02:00
MacRimi
816cf0141b Update AppImage 2025-10-04 20:23:42 +02:00
ProxMenuxBot
7baabc6d2c Update helpers_cache.json 2025-10-04 18:16:43 +00:00
MacRimi
37d1c7338b Update flask_server.py 2025-10-04 20:14:57 +02:00
MacRimi
404ea9d838 Update AppImage 2025-10-04 20:06:47 +02:00
MacRimi
98c5c5827c Update AppImage 2025-10-04 19:58:12 +02:00
MacRimi
79525284b1 Update network-metrics.tsx 2025-10-04 19:53:12 +02:00
MacRimi
c14ea7afdf Update AppImage 2025-10-04 19:45:37 +02:00
MacRimi
c437753d64 Update package.json 2025-10-04 19:28:13 +02:00
MacRimi
441cc35e5a Update AppImage 2025-10-04 19:25:28 +02:00
MacRimi
dc03144773 Update AppImage 2025-10-04 19:05:39 +02:00
MacRimi
992921b24c Update storage-overview.tsx 2025-10-04 18:53:31 +02:00
MacRimi
28f38dca46 Update storage-overview.tsx 2025-10-04 18:46:12 +02:00
MacRimi
53155ccef0 Update AppImage 2025-10-04 18:36:15 +02:00
MacRimi
ba6f0a1aab Update AppImage 2025-10-04 18:23:45 +02:00
MacRimi
2d89d06bcb Update AppImage 2025-10-04 17:48:10 +02:00
MacRimi
54ff50ce68 Update AppImage 2025-10-04 17:34:07 +02:00
MacRimi
22aa8cdd6c Update flask_server.py 2025-10-04 17:03:39 +02:00
MacRimi
06b0195d74 Update customizable_post_install.sh 2025-10-04 16:31:38 +02:00
MacRimi
a99b4ded7f Update uninstall-tools.sh 2025-10-04 16:22:17 +02:00
MacRimi
2405a0e778 Update uninstall-tools.sh 2025-10-04 16:20:30 +02:00
MacRimi
84544b1e84 Update auto_post_install.sh 2025-10-04 16:11:45 +02:00
MacRimi
95fce39502 Update auto_post_install.sh 2025-10-04 16:04:09 +02:00
MacRimi
99c5b26241 Update uninstall-tools.sh 2025-10-04 10:26:06 +02:00
ProxMenuxBot
6e07e49c84 Update helpers_cache.json 2025-10-03 18:18:02 +00:00
ProxMenuxBot
0bcfea9d20 Update helpers_cache.json 2025-10-03 12:25:37 +00:00
MacRimi
2658331fd2 Update AppImage 2025-10-02 23:41:31 +02:00
MacRimi
2ab49cc545 Update AppImage 2025-10-02 23:20:59 +02:00
MacRimi
a39fe5ff3b Update flask_server.py 2025-10-02 23:02:17 +02:00
MacRimi
01578b4e34 Update flask_server.py 2025-10-02 22:38:06 +02:00
MacRimi
95718c889d Update AppImage 2025-10-02 22:29:24 +02:00
MacRimi
6279cc9ec1 Update AppImage 2025-10-02 19:51:53 +02:00
MacRimi
f7fb9034ef Update system-overview.tsx 2025-10-02 19:34:53 +02:00
MacRimi
15f3af2020 Update AppImage 2025-10-02 18:28:36 +02:00
MacRimi
97288ed6ce Update system-overview.tsx 2025-10-02 18:11:00 +02:00
MacRimi
5e168c2561 Update system-overview.tsx 2025-10-02 17:49:40 +02:00
MacRimi
358b3f96ae Update globals.css 2025-10-02 17:41:23 +02:00
MacRimi
c0d9c3808a Update globals.css 2025-10-02 17:28:23 +02:00
MacRimi
7404bb8e64 Update globals.css 2025-10-02 17:21:04 +02:00
MacRimi
93eccd7dcf Update globals.css 2025-10-02 17:17:21 +02:00
MacRimi
05d9d41860 Update AppImage 2025-10-02 17:10:27 +02:00
MacRimi
c47c41548f Update globals.css 2025-10-02 16:57:12 +02:00
ProxMenuxBot
013d1980a3 Update helpers_cache.json 2025-10-01 18:18:31 +00:00
MacRimi
df9f4a23b4 Update AppImage 2025-10-01 18:14:58 +02:00
MacRimi
c41da47a48 Update AppImage 2025-10-01 18:08:31 +02:00
MacRimi
e7214ad8df Update globals.css 2025-10-01 18:04:38 +02:00
MacRimi
d6671de842 Update AppImage 2025-10-01 18:01:54 +02:00
MacRimi
aad218db5d Update globals.css 2025-10-01 17:44:00 +02:00
MacRimi
724ba1e271 Update AppImge 2025-10-01 17:39:43 +02:00
MacRimi
97d554f638 update AppImage 2025-10-01 17:27:05 +02:00
MacRimi
c5a7655d26 Update AppImage 2025-10-01 17:10:37 +02:00
MacRimi
403e896e3e Update proxmox-dashboard.tsx 2025-10-01 17:03:56 +02:00
MacRimi
1a15f43cad Update proxmox-dashboard.tsx 2025-10-01 16:53:37 +02:00
MacRimi
399b460c53 Update globals.css 2025-09-30 00:11:24 +02:00
MacRimi
acc0362180 Update AppImage 2025-09-30 00:09:11 +02:00
MacRimi
00db93e03f Update AppImage 2025-09-29 23:56:33 +02:00
MacRimi
d1997794c8 Update globals.css 2025-09-29 23:40:45 +02:00
MacRimi
aa1ebe69f2 Update globals.css 2025-09-29 23:11:40 +02:00
MacRimi
4e7f5f56f1 Update AppImage 2025-09-29 22:59:10 +02:00
MacRimi
28cb7359ce Update system-overview.tsx 2025-09-29 22:38:25 +02:00
ProxMenuxBot
91c272d21c Update helpers_cache.json 2025-09-29 18:19:17 +00:00
MacRimi
3c00125e83 Update flask_server.py 2025-09-29 19:19:35 +02:00
MacRimi
f359848a2f Update AppRun 2025-09-29 19:12:56 +02:00
MacRimi
989769e5e8 Update AppRun 2025-09-29 19:07:35 +02:00
MacRimi
0f2f1b6211 Update system-overview.tsx 2025-09-29 19:02:59 +02:00
MacRimi
ffe8f4acc6 Update AppImage 2025-09-29 18:58:53 +02:00
MacRimi
edb09777de Update package.json 2025-09-29 18:47:18 +02:00
MacRimi
5262c7863e Update AppImage 2025-09-29 18:43:14 +02:00
MacRimi
54256826fe Update AppImage 2025-09-29 18:37:32 +02:00
MacRimi
3d3c224b3a Update flask_server.py 2025-09-29 18:31:09 +02:00
MacRimi
049eccb872 Update AppImage 2025-09-29 18:27:09 +02:00
MacRimi
269828c79e Update AppImage 2025-09-29 18:16:01 +02:00
MacRimi
b4e25ae66d Update AppImage 2025-09-29 18:07:30 +02:00
MacRimi
b20dd74d23 Update AppImage 2025-09-29 17:57:00 +02:00
MacRimi
bc3e2ec358 Update AppImage 2025-09-29 17:46:37 +02:00
MacRimi
6133a6d6d8 Update build_appimage.sh 2025-09-29 17:32:24 +02:00
MacRimi
46a16c04e6 Update AppImage 2025-09-29 17:21:59 +02:00
MacRimi
8469b3b26f Update AppImage 2025-09-29 17:05:42 +02:00
ProxMenuxBot
2ed04f57fe Update helpers_cache.json 2025-09-29 12:26:48 +00:00
MacRimi
b19bac679a Update flask_server.py 2025-09-29 00:00:01 +02:00
MacRimi
3c33d5982c Update AppImage 2025-09-28 23:51:06 +02:00
MacRimi
5b934eeb87 Update AppImage 2025-09-28 23:25:58 +02:00
MacRimi
795d96f8d5 Update AppImage 2025-09-28 23:09:31 +02:00
MacRimi
a8e7119b4a Update AppImage 2025-09-28 23:05:59 +02:00
MacRimi
38569ff7fc Update AppImage 2025-09-28 22:57:15 +02:00
MacRimi
e404557d62 Update AppImage 2025-09-28 22:53:42 +02:00
MacRimi
96cbc75a5e Update build_appimage.sh 2025-09-28 21:26:25 +02:00
MacRimi
c989af6cf0 Update build_appimage.sh 2025-09-28 21:18:52 +02:00
MacRimi
4eac9d03ea Update build_appimage.sh 2025-09-28 21:15:18 +02:00
MacRimi
6292009b0b Update AppImage 2025-09-28 21:08:58 +02:00
MacRimi
3272be967d Update build_appimage.sh 2025-09-28 21:01:48 +02:00
MacRimi
1c015da440 Update build_appimage.sh 2025-09-28 20:57:23 +02:00
MacRimi
0d047cc956 Update build_appimage.sh 2025-09-28 20:46:55 +02:00
MacRimi
e682070b85 update AppImage 2025-09-28 20:28:35 +02:00
MacRimi
9f08694d9b Update Appimage 2025-09-28 20:25:55 +02:00
MacRimi
70f0db73e5 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-09-28 20:20:46 +02:00
MacRimi
9dc8f44379 Create tsconfig.json 2025-09-28 20:20:31 +02:00
MacRimi
59f7ccd723 Update build-appimage.yml 2025-09-28 20:15:56 +02:00
MacRimi
0710e95a6d Update package.json 2025-09-28 20:15:33 +02:00
MacRimi
4d1b5e3919 Update build-appimage.yml 2025-09-28 20:12:21 +02:00
MacRimi
0cc2cb92dd Update build-appimage.yml 2025-09-28 20:10:08 +02:00
MacRimi
dba4d168f7 Update build-appimage.yml 2025-09-28 20:08:02 +02:00
MacRimi
d87ac7843c Update build-appimage.yml 2025-09-28 20:07:12 +02:00
MacRimi
040535b004 Update build-appimage.yml 2025-09-28 20:04:39 +02:00
MacRimi
c8acd2c0b1 Update build-appimage.yml 2025-09-28 19:56:03 +02:00
MacRimi
d67fecea6e Update build-appimage.yml 2025-09-28 19:46:03 +02:00
MacRimi
61f80f9ee6 Update build-appimage.yml 2025-09-28 19:44:51 +02:00
MacRimi
9da8f9a5d1 Update build-appimage.yml 2025-09-28 19:43:05 +02:00
MacRimi
f381468d5a Create build-appimage.yml 2025-09-28 19:41:13 +02:00
MacRimi
6ae97266e4 Create AppImage 2025-09-28 19:40:23 +02:00
MacRimi
66060f345c Create jd2_2.sh 2025-09-27 20:21:15 +02:00
MacRimi
c61f568170 Update nfs_lxc_server.sh 2025-09-27 18:50:50 +02:00
MacRimi
dcd108bda3 Update update-pve.sh 2025-09-27 18:28:56 +02:00
MacRimi
9d89f98987 Update customizable_post_install.sh 2025-09-27 18:26:23 +02:00
MacRimi
ca7b959fce Update auto_post_install.sh 2025-09-27 18:25:25 +02:00
MacRimi
4a30793595 Update uninstall-tools.sh 2025-09-27 18:24:02 +02:00
MacRimi
35e2d53f0f update remove subscription banner PVE 9 2025-09-27 18:16:12 +02:00
MacRimi
503efa4572 Create remove-banner-pve9_2.sh 2025-09-27 17:42:18 +02:00
ProxMenuxBot
b0c33d9dff Update helpers_cache.json 2025-09-27 00:56:57 +00:00
MacRimi
012b156b46 Update install_coral_pve9.sh 2025-09-26 00:17:32 +02:00
MacRimi
25d0d3bf59 Create install_coral_pve9.sh 2025-09-25 23:45:50 +02:00
ProxMenuxBot
0f1babc82b Update helpers_cache.json 2025-09-25 18:20:06 +00:00
ProxMenuxBot
e2b93ea785 Update helpers_cache.json 2025-09-24 18:19:23 +00:00
ProxMenuxBot
b1cedfa81e Update helpers_cache.json 2025-09-24 12:27:12 +00:00
ProxMenuxBot
701ee36f6a Update helpers_cache.json 2025-09-23 12:25:52 +00:00
ProxMenuxBot
4e5db86434 Update helpers_cache.json 2025-09-21 12:23:25 +00:00
ProxMenuxBot
f45e9e657c Update helpers_cache.json 2025-09-19 18:18:42 +00:00
ProxMenuxBot
4936fcdb1e Update helpers_cache.json 2025-09-18 18:18:59 +00:00
ProxMenuxBot
374e05c422 Update helpers_cache.json 2025-09-18 12:25:39 +00:00
ProxMenuxBot
9c00798373 Update helpers_cache.json 2025-09-17 18:18:09 +00:00
ProxMenuxBot
db82fce925 Update helpers_cache.json 2025-09-15 12:26:37 +00:00
ProxMenuxBot
acaa28e476 Update helpers_cache.json 2025-09-13 00:54:55 +00:00
ProxMenuxBot
f297ce5809 Update helpers_cache.json 2025-09-12 12:24:43 +00:00
ProxMenuxBot
3dc3fc5f67 Update helpers_cache.json 2025-09-12 00:57:45 +00:00
ProxMenuxBot
4884fc4418 Update helpers_cache.json 2025-09-11 18:15:27 +00:00
MacRimi
adc17842ec Update README.md 2025-09-11 18:18:42 +02:00
MacRimi
daa48b0b7c Update README.md 2025-09-11 18:17:16 +02:00
MacRimi
17c0362df3 Update cache.json 2025-09-10 20:29:19 +02:00
MacRimi
29b9a63fc9 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-09-10 20:28:00 +02:00
MacRimi
2a9fae160e Update cache.json 2025-09-10 20:27:58 +02:00
ProxMenuxBot
0c49a1e3bd Update helpers_cache.json 2025-09-10 18:18:53 +00:00
MacRimi
e896c41be1 Update main_menu.sh 2025-09-10 19:14:42 +02:00
MacRimi
187250fa24 update text ProxMenux 2025-09-10 19:06:04 +02:00
MacRimi
9035b18584 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-09-10 18:58:12 +02:00
MacRimi
4534d78978 update text ProxMenux 2025-09-10 18:57:49 +02:00
MacRimi
f4ab0e982c Update README.md 2025-09-10 18:53:41 +02:00
MacRimi
3e7c6629a6 update text ProxMenux 2025-09-10 18:47:55 +02:00
MacRimi
3ea17331fe Update install_proxmenux.sh 2025-09-10 18:24:14 +02:00
MacRimi
1057fcc271 Update install_proxmenux.sh 2025-09-10 18:19:13 +02:00
MacRimi
5a31c36097 update menu share 2025-09-10 18:05:06 +02:00
MacRimi
1677a69bba Update version.txt 2025-09-10 17:52:17 +02:00
MacRimi
315c49165d Update CHANGELOG.md 2025-09-10 17:50:53 +02:00
MacRimi
aae70e7ec0 Update CHANGELOG.md 2025-09-10 17:47:37 +02:00
MacRimi
5cb9e13ca7 Update CHANGELOG.md 2025-09-10 17:37:51 +02:00
MacRimi
0187010f94 Create main-menu.png 2025-09-10 16:28:47 +02:00
ProxMenuxBot
2c2ed21e59 Update helpers_cache.json 2025-09-10 12:24:52 +00:00
MacRimi
f8b2ccec40 Update commands_share.sh 2025-09-10 13:48:17 +02:00
MacRimi
e858dc582d Update commands_share.sh 2025-09-10 13:47:13 +02:00
MacRimi
dd737f4b46 Update commands_share.sh 2025-09-10 13:46:02 +02:00
MacRimi
f0bc238b6d Update commands_share.sh 2025-09-10 13:35:16 +02:00
MacRimi
af55424850 Update lxc-mount-manager_minimal.sh 2025-09-10 13:16:54 +02:00
MacRimi
902534baff update menu shared 2025-09-10 12:57:17 +02:00
MacRimi
6daa630040 Update nfs_host.sh 2025-09-10 12:53:09 +02:00
MacRimi
0b2b86673b Update lxc-mount-manager_minimal.sh 2025-09-10 12:31:49 +02:00
MacRimi
6aa5b58208 Update share_menu.sh 2025-09-10 12:17:03 +02:00
MacRimi
4430201cd2 Update lxc-mount-manager_minimal.sh 2025-09-10 12:16:25 +02:00
MacRimi
7c7963a83e Create lxc-mount-manager_minimal.sh 2025-09-10 11:10:02 +02:00
ProxMenuxBot
e2202cd2d8 Update helpers_cache.json 2025-09-09 18:17:13 +00:00
MacRimi
a931be83bc Update share-common.func 2025-09-09 19:20:15 +02:00
MacRimi
7350bea345 Update auto_post_install.sh 2025-09-09 19:16:13 +02:00
MacRimi
9b1e39dbb4 Update update-pve.sh 2025-09-09 19:14:27 +02:00
MacRimi
15cd118845 Update auto_post_install.sh 2025-09-09 19:06:33 +02:00
MacRimi
d58dff047c Update auto_post_install.sh 2025-09-08 19:24:49 +02:00
MacRimi
a2f83c896c Update common-functions.sh 2025-09-08 19:14:41 +02:00
MacRimi
6ef77c731c Update guia.md 2025-09-08 17:44:17 +02:00
MacRimi
29b0f61958 Update guia.md 2025-09-08 17:43:39 +02:00
MacRimi
e944b2ecdd Update guia.md 2025-09-08 17:24:23 +02:00
MacRimi
41819c46a3 Update guia.md 2025-09-08 17:21:58 +02:00
MacRimi
13f391a6f0 Update guia.md 2025-09-08 17:14:52 +02:00
MacRimi
85a3d44f2c Update guia.md 2025-09-08 17:05:26 +02:00
MacRimi
0792392058 Update guia.md 2025-09-08 16:42:46 +02:00
MacRimi
ff5083ada0 Update guia.md 2025-09-08 16:20:10 +02:00
MacRimi
62841677bc Update guia.md 2025-09-08 16:06:50 +02:00
MacRimi
1761cf53a2 Update guia.md 2025-09-08 15:27:32 +02:00
MacRimi
a771efc5fa Update guia.md 2025-09-08 15:08:02 +02:00
MacRimi
ed049da76a Update guia.md 2025-09-08 15:03:10 +02:00
MacRimi
5d1d357a2e Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-09-08 15:01:02 +02:00
MacRimi
30d0706a1c Create guia.md 2025-09-08 15:01:00 +02:00
ProxMenuxBot
e9667e1266 Update helpers_cache.json 2025-09-08 12:27:18 +00:00
MacRimi
73109483e7 Update share-common.func 2025-09-08 11:29:02 +02:00
MacRimi
a9c1acf204 Update lxc-mount-manager.sh 2025-09-08 11:15:52 +02:00
MacRimi
81c4f5814c Update samba_host.sh 2025-09-08 10:40:59 +02:00
MacRimi
c595f6d781 Update nfs_host.sh 2025-09-08 10:36:57 +02:00
MacRimi
24bb6b1d3d Update lxc-mount-manager.sh 2025-09-07 19:10:23 +02:00
MacRimi
49eeb6020d Update install_proxmenux.sh 2025-09-07 17:24:29 +02:00
MacRimi
7c272bd2a2 Update install_proxmenux.sh 2025-09-07 17:22:47 +02:00
MacRimi
cfbd865937 Update share-common.func 2025-09-07 09:29:43 +02:00
MacRimi
fe472f33ef Update lxc-mount-manager.sh 2025-09-07 09:28:09 +02:00
MacRimi
8d6b3d650f update menu share 2025-09-07 09:19:53 +02:00
MacRimi
3b0d5b5eb7 Update nfs_lxc_server.sh 2025-09-07 09:16:22 +02:00
MacRimi
875e8a99bd Update samba_client.sh 2025-09-07 09:06:47 +02:00
MacRimi
6c19d81844 Update samba_client.sh 2025-09-07 09:05:23 +02:00
MacRimi
ba535a931f Update nfs_client.sh 2025-09-07 09:04:13 +02:00
MacRimi
45dca5218d Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-09-07 09:00:39 +02:00
MacRimi
da3cb9971b Update share-common.func 2025-09-07 09:00:37 +02:00
ProxMenuxBot
b39270dc1e Update helpers_cache.json 2025-09-07 01:03:30 +00:00
MacRimi
ae8a7d0de9 Update commands_share.sh 2025-09-06 23:59:23 +02:00
MacRimi
2d501415bf Update commands_share.sh 2025-09-06 23:50:57 +02:00
MacRimi
da639ccaac Update commands_share.sh 2025-09-06 23:35:46 +02:00
MacRimi
a352770e2d Update share_menu.sh 2025-09-06 23:20:59 +02:00
MacRimi
e3e1899466 update menu share 2025-09-06 23:18:11 +02:00
MacRimi
e67288e623 Update lxc-mount-manager.sh 2025-09-06 22:55:28 +02:00
MacRimi
4019e49b07 Update share-common.func 2025-09-06 22:51:57 +02:00
MacRimi
cd8711f3bc Update lxc-mount-manager.sh 2025-09-06 22:50:30 +02:00
MacRimi
0d119379de Update lxc-mount-manager.sh 2025-09-06 22:46:52 +02:00
MacRimi
aa2b6ff112 Update lxc-mount-manager.sh 2025-09-06 22:44:09 +02:00
MacRimi
3482f7dc98 Update share-common.func 2025-09-06 22:42:17 +02:00
MacRimi
16c321f114 Update lxc-mount-manager.sh 2025-09-06 22:33:19 +02:00
MacRimi
a81e7f3c44 Update lxc-mount-manager.sh 2025-09-06 22:25:50 +02:00
MacRimi
d7cc001521 Update lxc-mount-manager.sh 2025-09-06 22:20:34 +02:00
MacRimi
eb11962231 Update share-common.func 2025-09-06 22:19:52 +02:00
MacRimi
9f73b8f159 Update lxc-mount-manager.sh 2025-09-06 22:17:08 +02:00
MacRimi
873a4abe24 Update share-common.func 2025-09-06 22:16:40 +02:00
MacRimi
56bc584f5e Update lxc-mount-manager.sh 2025-09-06 22:10:01 +02:00
MacRimi
2a9f2f3c2e Update lxc-mount-manager.sh 2025-09-06 22:09:11 +02:00
MacRimi
ee719cdd39 Update share-common.func 2025-09-06 22:06:02 +02:00
MacRimi
a571b57b30 Update share-common.func 2025-09-06 21:48:39 +02:00
MacRimi
5ee7a23bea Update share-common.func 2025-09-06 21:47:20 +02:00
MacRimi
fe159ea195 Update share-common.func 2025-09-06 21:42:30 +02:00
MacRimi
8fcdf6176b Update share-common.func 2025-09-06 21:31:03 +02:00
MacRimi
715166bbca Update share-common.func 2025-09-06 21:22:40 +02:00
MacRimi
1d58072c70 Update share-common.func 2025-09-06 21:21:39 +02:00
MacRimi
d667cde699 Update lxc-mount-manager.sh 2025-09-06 21:19:55 +02:00
MacRimi
4cd8889c38 Update share-common.func 2025-09-06 21:14:54 +02:00
MacRimi
93896f6fb7 Update share-common.func 2025-09-06 21:07:12 +02:00
MacRimi
3b3f0387bb Update share-common.func 2025-09-06 21:05:32 +02:00
MacRimi
2875c9af95 Update lxc-mount-manager.sh 2025-09-06 20:59:03 +02:00
MacRimi
93ef1bfccc update share menu 2025-09-06 20:57:04 +02:00
MacRimi
a886af1d87 Update samba_client.sh 2025-09-06 20:48:37 +02:00
MacRimi
d731ff3ae6 Update samba_host.sh 2025-09-06 20:40:45 +02:00
MacRimi
d44864637d Update nfs_host_auto.sh 2025-09-06 20:37:23 +02:00
MacRimi
674ee34ec6 Update nfs_host_auto.sh 2025-09-06 20:30:04 +02:00
MacRimi
a93eeda243 Update share-common.func 2025-09-06 19:56:24 +02:00
MacRimi
80fd92e2a1 Update share-common.func 2025-09-06 19:55:01 +02:00
MacRimi
d4ff2da473 Update share-common.func 2025-09-06 19:16:15 +02:00
MacRimi
9b7b271580 update menu shared 2025-09-06 19:13:52 +02:00
MacRimi
e1b340966a Update share-common.func 2025-09-06 18:56:10 +02:00
MacRimi
7ec4c331af Update nfs_client.sh 2025-09-06 18:39:35 +02:00
MacRimi
3102d596ee Update nfs_lxc_server.sh 2025-09-06 18:37:28 +02:00
MacRimi
af56dc546e Update nfs_host_auto.sh 2025-09-06 18:26:13 +02:00
MacRimi
15d47499fa Update nfs_host_auto.sh 2025-09-06 18:22:02 +02:00
MacRimi
53a34d0470 update nfs menu 2025-09-06 18:20:20 +02:00
MacRimi
3ee675cefe Update nfs_lxc_server.sh 2025-09-06 18:08:48 +02:00
MacRimi
d98c7bdc03 Update share_menu.sh 2025-09-06 16:48:07 +02:00
MacRimi
bb4f1ebed6 Update share_menu.sh 2025-09-06 16:44:58 +02:00
MacRimi
c8f73ea23b Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-09-06 16:41:55 +02:00
MacRimi
8292b12787 Create nfs_lxc_server.sh 2025-09-06 16:41:54 +02:00
ProxMenuxBot
0f518e3c35 Update helpers_cache.json 2025-09-06 12:22:12 +00:00
MacRimi
1c2f67d43d Update lxc-mount-manager.sh 2025-09-06 11:55:23 +02:00
MacRimi
a5560a3123 Update share-common.func 2025-09-06 11:50:48 +02:00
MacRimi
1332096360 Update share-common.func 2025-09-06 11:39:17 +02:00
MacRimi
80381a6375 Update share-common.func 2025-09-06 11:32:07 +02:00
MacRimi
acf92bd005 Update share-common.func 2025-09-06 11:30:23 +02:00
MacRimi
da4f8a3a19 Update share-common.func 2025-09-06 11:11:19 +02:00
MacRimi
3a332192e3 Update share-common.func 2025-09-06 11:08:23 +02:00
MacRimi
1fdb1d87cc Update share-common.func 2025-09-06 11:01:35 +02:00
MacRimi
b99aa55d7a Update share-common.func 2025-09-06 10:46:22 +02:00
MacRimi
de20da2dad Update lxc-mount-manager.sh 2025-09-06 10:10:37 +02:00
MacRimi
9444f0a68b Update nfs_host_auto.sh 2025-09-06 08:51:41 +02:00
MacRimi
48fd223a28 Update samba.sh 2025-09-04 20:38:11 +02:00
MacRimi
0845efe419 Update samba_client.sh 2025-09-04 20:37:18 +02:00
MacRimi
57b7ba91bc Update share_menu.sh 2025-09-04 20:34:43 +02:00
MacRimi
97af8a4892 Update share_menu.sh 2025-09-04 20:31:33 +02:00
MacRimi
d6f237e289 Update share_menu.sh 2025-09-04 20:30:36 +02:00
MacRimi
aba7109b35 Update samba_host.sh 2025-09-04 20:28:16 +02:00
MacRimi
d3ec71052e Update samba_client.sh 2025-09-04 20:26:51 +02:00
MacRimi
1be63f396b Update nfs_client.sh 2025-09-04 20:26:26 +02:00
MacRimi
9308742146 Update samba_client.sh 2025-09-04 20:23:00 +02:00
MacRimi
b32241082d Update share_menu.sh 2025-09-04 20:12:24 +02:00
MacRimi
1f8504d685 update shared 2025-09-04 20:04:08 +02:00
MacRimi
97c5c48150 Update nfs_host.sh 2025-09-03 23:13:21 +02:00
MacRimi
afe84dc46a Update nfs_client.sh 2025-09-03 23:10:01 +02:00
MacRimi
ffafd42f03 Update nfs.sh 2025-09-03 23:06:47 +02:00
MacRimi
7dca715c91 Update nfs.sh 2025-09-03 23:04:46 +02:00
MacRimi
7695e1d8dd Update nfs.sh 2025-09-03 22:55:45 +02:00
MacRimi
84b86d1db7 Update nfs.sh 2025-09-03 22:14:29 +02:00
MacRimi
bae3ef6460 Update nfs.sh 2025-09-03 22:06:34 +02:00
MacRimi
97c6ec8875 Update share-common.func 2025-09-03 16:47:03 +02:00
MacRimi
d33128dc26 Update share_menu.sh 2025-09-03 12:27:59 +02:00
MacRimi
10bdecabb6 Update share_menu.sh 2025-09-03 12:25:35 +02:00
MacRimi
de88f530c8 Update share_menu.sh 2025-09-03 12:23:54 +02:00
MacRimi
fb511b7596 Update share_menu.sh 2025-09-03 12:22:49 +02:00
MacRimi
322665ce91 Update share_menu.sh 2025-09-03 12:21:21 +02:00
MacRimi
baeca1fcfb Update share-common.func 2025-09-03 11:35:38 +02:00
MacRimi
095b98c36a Update share-common.func 2025-09-03 11:28:37 +02:00
MacRimi
29bb7e7608 Update share-common.func 2025-09-03 11:16:34 +02:00
MacRimi
e3d137efba Update share-common.func 2025-09-02 22:56:46 +02:00
MacRimi
207e915393 Update share-common.func 2025-09-02 22:45:33 +02:00
MacRimi
614e629a2b Update share-common.func 2025-09-02 22:44:42 +02:00
MacRimi
f35de5c749 Update share-common.func 2025-09-02 21:34:13 +02:00
MacRimi
c1623bd4df Update share-common.func 2025-09-02 21:23:57 +02:00
ProxMenuxBot
8690da5017 Update helpers_cache.json 2025-09-02 18:17:01 +00:00
MacRimi
696adcdc24 Update share-common.func 2025-09-02 18:48:57 +02:00
MacRimi
2756bd06c1 Update share-common.func 2025-09-02 18:48:20 +02:00
MacRimi
4893f6ea00 Update lxc-mount-manager.sh 2025-09-02 18:45:53 +02:00
MacRimi
35a7348197 Update lxc-mount-manager.sh 2025-09-02 18:45:04 +02:00
MacRimi
cdd6333d0a Update share_menu.sh 2025-09-02 18:43:27 +02:00
MacRimi
54399b5b5d Update share-common.func 2025-09-02 18:25:39 +02:00
MacRimi
f6b192cc1e Update lxc-mount-manager.sh 2025-09-02 18:22:34 +02:00
MacRimi
cd231b90d8 Update share-common.func 2025-09-02 17:21:09 +02:00
ProxMenuxBot
87fe788358 Update helpers_cache.json 2025-09-02 12:26:27 +00:00
MacRimi
3e9bd21ea8 update share menu 2025-09-02 00:02:16 +02:00
MacRimi
b6d4029797 Update local-shared-manager.sh 2025-09-01 19:10:46 +02:00
MacRimi
ec65e96148 Update share_menu.sh 2025-09-01 19:08:48 +02:00
MacRimi
926f1f971f update share menu 2025-09-01 19:06:52 +02:00
MacRimi
5d69fad73f Update share_menu.sh 2025-09-01 18:45:14 +02:00
MacRimi
a796761023 Update share-common.func 2025-09-01 17:39:45 +02:00
MacRimi
5d1338e485 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-09-01 17:21:41 +02:00
MacRimi
ce25a167f1 Update auto_post_install.sh 2025-09-01 17:21:39 +02:00
MacRimi
1c44969580 Update share_menu.sh 2025-09-01 14:43:46 +02:00
MacRimi
b6e04e3ede Update share-common.func 2025-09-01 14:30:16 +02:00
ProxMenuxBot
84c26be703 Update helpers_cache.json 2025-09-01 12:26:47 +00:00
MacRimi
d201160722 Update share-common.func 2025-09-01 14:08:10 +02:00
MacRimi
e112361b43 Update share-common.func 2025-09-01 13:53:54 +02:00
MacRimi
3e69795c9d Update lxc-mount-manager.sh 2025-09-01 13:33:48 +02:00
MacRimi
b11baf2e5d Update auto_post_install.sh 2025-09-01 12:42:46 +02:00
MacRimi
233770b553 Update auto_post_install.sh 2025-09-01 12:41:26 +02:00
MacRimi
187db73798 Update zimaos.sh 2025-09-01 12:20:45 +02:00
MacRimi
0e3fc6f682 Update zimaos.sh 2025-09-01 12:15:30 +02:00
MacRimi
d11e3a4ac4 Update auto_post_install.sh 2025-08-31 23:45:37 +02:00
MacRimi
d3b4ca3e66 Update customizable_post_install.sh 2025-08-31 21:46:28 +02:00
MacRimi
f37fbbfb8b Update menu share 2025-08-30 18:56:49 +02:00
MacRimi
52b7aac424 Update share-common.func 2025-08-30 18:05:02 +02:00
MacRimi
d42f3f8f0c Update share-common.func 2025-08-30 18:02:20 +02:00
MacRimi
91b5c7c9bc Update share-common.func 2025-08-30 17:51:45 +02:00
MacRimi
48feebc092 Update share-common.func 2025-08-30 16:59:46 +02:00
MacRimi
14e2d66d96 Update share-common.func 2025-08-30 11:43:02 +02:00
MacRimi
10d844a195 Update share-common.func 2025-08-30 11:36:52 +02:00
MacRimi
bbf91ae5d6 Update main_menu.sh 2025-08-30 09:40:42 +02:00
ProxMenuxBot
cb82eda49a Update helpers_cache.json 2025-08-30 00:57:37 +00:00
MacRimi
bc1dbb1c27 Update help_info_menu.sh 2025-08-30 00:17:51 +02:00
ProxMenuxBot
9496a7f1ce Update helpers_cache.json 2025-08-28 18:19:20 +00:00
ProxMenuxBot
7241fa31b4 Update helpers_cache.json 2025-08-28 12:26:37 +00:00
MacRimi
fed7216436 Update share-common.func 2025-08-27 18:15:26 +02:00
MacRimi
ffe7d7c4c6 Create group_manager.sh 2025-08-27 10:54:03 +02:00
MacRimi
f430ac8d6c Update upgrade_pve8_to_pve9.sh 2025-08-26 19:40:38 +02:00
MacRimi
70dfd7c9a3 Update upgrade_pve8_to_pve9.sh 2025-08-26 19:25:59 +02:00
MacRimi
ed3140932b Update upgrade_pve8_to_pve9.sh 2025-08-26 19:24:41 +02:00
MacRimi
3cd2bd6ce8 Update share-common.func 2025-08-26 17:46:48 +02:00
MacRimi
982bf45fc4 Update share-common.func 2025-08-26 17:37:09 +02:00
MacRimi
aaba8569fc Update share-common.func 2025-08-26 17:19:11 +02:00
MacRimi
4111e15eb9 Update share-common.func 2025-08-26 17:15:47 +02:00
MacRimi
2012478f26 Update share-common.func 2025-08-26 17:05:17 +02:00
MacRimi
88869d3239 Update share-common.func 2025-08-26 17:02:24 +02:00
ProxMenuxBot
f3c2549b18 Update helpers_cache.json 2025-08-26 12:28:24 +00:00
MacRimi
57e3b839d0 Update share-common.func 2025-08-26 13:56:00 +02:00
MacRimi
faf3f43413 Create share-common.func 2025-08-26 13:26:22 +02:00
ProxMenuxBot
52e5bb3386 Update helpers_cache.json 2025-08-26 01:02:34 +00:00
ProxMenuxBot
89405f6670 Update helpers_cache.json 2025-08-25 18:20:02 +00:00
ProxMenuxBot
73111c4139 Update helpers_cache.json 2025-08-25 12:27:14 +00:00
ProxMenuxBot
04e9c5db8c Update helpers_cache.json 2025-08-24 12:24:09 +00:00
MacRimi
69278902de Update customizable_post_install.sh 2025-08-24 10:55:25 +02:00
MacRimi
efa95b0858 Update customizable_post_install.sh 2025-08-24 10:29:00 +02:00
MacRimi
660128cd5c Update customizable_post_install.sh 2025-08-24 10:28:14 +02:00
MacRimi
ef1e052e47 Update customizable_post_install.sh 2025-08-24 10:06:20 +02:00
ProxMenuxBot
0b346bc343 Update helpers_cache.json 2025-08-24 06:19:28 +00:00
MacRimi
2272eaf833 Update lxc-unprivileged-to-privileged.sh 2025-08-22 19:08:03 +02:00
MacRimi
4adee98bce new menu lxc 2025-08-22 19:05:36 +02:00
MacRimi
cbdb2c0705 Rename lxc-manual-guide.sh to lxc-manual-guide.sh 2025-08-22 19:04:10 +02:00
MacRimi
4f438aabbf update manual lxc guide 2025-08-22 19:03:08 +02:00
MacRimi
b6ccc06963 Update lxc_menu.sh 2025-08-22 18:58:10 +02:00
MacRimi
5b89a15bfc menu lxc 2025-08-22 18:57:00 +02:00
MacRimi
5596ae551d Update storage_menu.sh 2025-08-22 18:34:12 +02:00
MacRimi
1360df592a Create backup_host4.sh 2025-08-22 18:17:01 +02:00
MacRimi
13684ff83c Update share_menu.sh 2025-08-22 18:10:34 +02:00
MacRimi
ae88f7870e Update share_menu.sh 2025-08-22 18:08:20 +02:00
MacRimi
810b6da60c Share menu 2025-08-22 18:05:14 +02:00
ProxMenuxBot
7bdf3e08f9 Update helpers_cache.json 2025-08-21 18:19:22 +00:00
MacRimi
fdad2a087f Update zimaos.sh 2025-08-21 19:39:50 +02:00
MacRimi
c437a8c426 Update zimaos.sh 2025-08-21 19:36:28 +02:00
MacRimi
ef861e6d1d Update zimaos.sh 2025-08-21 19:02:58 +02:00
MacRimi
928a008688 Update zimaos.sh 2025-08-21 18:59:54 +02:00
MacRimi
638a124adb Update zimaos.sh 2025-08-21 18:31:32 +02:00
MacRimi
c2a63ae9bb Update zimaos.sh 2025-08-21 18:30:43 +02:00
MacRimi
28cf31e6e7 Update zimaos.sh 2025-08-21 18:27:30 +02:00
MacRimi
3cf416167d Update select_nas_iso.sh 2025-08-21 18:22:57 +02:00
MacRimi
ebf03923a0 Update select_nas_iso.sh 2025-08-21 18:14:58 +02:00
MacRimi
82797d2421 Update select_nas_iso.sh 2025-08-21 18:05:45 +02:00
MacRimi
52b6be946c Create zimaos.sh 2025-08-21 18:00:58 +02:00
MacRimi
dc46724d7b Update select_linux_iso.sh 2025-08-20 23:02:38 +02:00
MacRimi
ed7d43b6a9 Update select_linux_iso.sh 2025-08-20 22:58:14 +02:00
MacRimi
6f3fc51278 Update system_utils.sh 2025-08-20 22:40:42 +02:00
MacRimi
a446acc282 Update customizable_post_install.sh 2025-08-20 22:39:28 +02:00
306 changed files with 147064 additions and 21263 deletions

View File

@@ -0,0 +1,117 @@
title: "[Prompt] "
labels:
- custom-prompt
- community
body:
- type: markdown
attributes:
value: |
## Share Your Custom Prompt
Thank you for sharing your custom prompt with the community!
**Title format suggestion:** Include the provider in the title for easy filtering.
Example: `[Gemini] Clean Spanish - Structured, no emojis`
This helps others find prompts for their specific AI provider.
- type: dropdown
id: provider
attributes:
label: AI Provider
description: Which AI provider did you test this prompt with?
options:
- OpenAI
- Gemini
- Groq
- Ollama
- Anthropic
- OpenRouter
- DeepSeek
- Other
validations:
required: true
- type: input
id: model
attributes:
label: Model
description: The specific model you tested with
placeholder: "e.g., gpt-4o-mini, gemini-2.0-flash, llama3.2:3b"
validations:
required: true
- type: textarea
id: description
attributes:
label: Description
description: Describe what your prompt does, main features, and output language
placeholder: |
This prompt generates concise notifications in Spanish.
Features:
- Brief format (2-3 lines)
- Includes severity indicators
- Uses emojis for visual clarity
validations:
required: true
- type: textarea
id: prompt-content
attributes:
label: Prompt Content
description: Paste your complete custom prompt here
render: text
placeholder: |
You are a notification formatter for ProxMenux Monitor.
Your task is to...
RULES:
1. ...
2. ...
OUTPUT FORMAT:
[TITLE]
...
[BODY]
...
validations:
required: true
- type: textarea
id: example-output
attributes:
label: Example Output
description: Show an example of how a notification looks with your prompt
placeholder: |
**Input notification:**
CPU usage high on node pve01
**Output with this prompt:**
pve01: High CPU Usage
CPU at 95% for 5 minutes. Check running processes.
validations:
required: false
- type: textarea
id: additional-notes
attributes:
label: Additional Notes
description: Any tips, variations, or known limitations
placeholder: |
- Works best with models that support system prompts
- May need adjustment for very long notifications
- Tested with Proxmox VE 8.x
validations:
required: false
- type: checkboxes
id: confirmation
attributes:
label: Confirmation
options:
- label: I have tested this prompt and it works correctly
required: true
- label: I am sharing this prompt for the community to use freely
required: true

29
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,29 @@
---
name: Bug Report
about: Report a problem in the project
title: "[BUG] Describe the issue"
labels: bug
assignees: 'MacRimi'
---
## Description
Describe the bug clearly and concisely.
## Steps to Reproduce
1. ...
2. ...
3. ...
## Expected Behavior
What should happen?
## Screenshots (Required)
Add images to help illustrate the issue.
## Environment
- Operating system:
- Software version:
- Other relevant details:
## Additional Information
Add any other context about the problem here.

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Soporte General
url: https://github.com/MacRimi/ProxMenux/discussions
about: If your request is neither a bug nor a feature, please use Discussions.

View File

@@ -0,0 +1,19 @@
---
name: Feature Request
about: Suggest a new feature or improvement
title: "[FEATURE] Describe your proposal"
labels: enhancement
assignees: 'MacRimi'
---
## Description
Explain the feature you are proposing.
## Motivation
Why is this improvement important? What problem does it solve?
## Alternatives Considered
Are there other solutions you have thought about?
## Additional Information
Add any extra details that help understand your proposal.

View File

@@ -1,76 +1,265 @@
import requests, json
#!/usr/bin/env python3
import json
import re
import sys
from pathlib import Path
from typing import Any
# GitHub API URL to fetch all .json files describing scripts
API_URL = "https://api.github.com/repos/community-scripts/ProxmoxVE/contents/frontend/public/json"
import requests
# Base path to build the full URL for the installable scripts
SCRIPT_BASE = "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
POCKETBASE_BASE = "https://db.community-scripts.org/api/collections"
SCRIPT_COLLECTION_URL = f"{POCKETBASE_BASE}/script_scripts/records"
CATEGORY_COLLECTION_URL = f"{POCKETBASE_BASE}/script_categories/records"
# Output file where the consolidated helper scripts cache will be stored
OUTPUT_FILE = Path("json/helpers_cache.json")
REPO_ROOT = Path(__file__).resolve().parents[2]
OUTPUT_FILE = REPO_ROOT / "json" / "helpers_cache.json"
OUTPUT_FILE.parent.mkdir(parents=True, exist_ok=True)
res = requests.get(API_URL)
data = res.json()
cache = []
TYPE_TO_PATH_PREFIX = {
"lxc": "ct",
"vm": "vm",
"addon": "tools/addon",
"pve": "tools/pve",
}
# Loop over each file in the JSON directory
for item in data:
url = item.get("download_url")
if not url or not url.endswith(".json"):
continue
def to_mirror_url(raw_url: str) -> str:
m = re.match(r"^https://raw\.githubusercontent\.com/([^/]+)/([^/]+)/([^/]+)/(.+)$", raw_url or "")
if not m:
return ""
org, repo, branch, path = m.groups()
if org.lower() != "community-scripts" or repo != "ProxmoxVE":
return ""
return f"https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/{branch}/{path}"
def fetch_json(url: str, *, params: dict[str, Any] | None = None) -> dict[str, Any]:
r = requests.get(url, params=params, timeout=60)
r.raise_for_status()
data = r.json()
if not isinstance(data, dict):
raise RuntimeError(f"Unexpected response from {url}: expected object")
return data
def fetch_all_records(url: str, *, expand: str | None = None, per_page: int = 500) -> list[dict[str, Any]]:
page = 1
items: list[dict[str, Any]] = []
while True:
params: dict[str, Any] = {"page": page, "perPage": per_page}
if expand:
params["expand"] = expand
data = fetch_json(url, params=params)
page_items = data.get("items", [])
if not isinstance(page_items, list):
raise RuntimeError(f"Unexpected items list from {url}")
items.extend(page_items)
total_pages = data.get("totalPages", page)
if not isinstance(total_pages, int) or page >= total_pages:
break
page += 1
return items
def normalize_os_variants(install_methods: list[dict[str, Any]]) -> list[str]:
os_values: list[str] = []
for item in install_methods:
if not isinstance(item, dict):
continue
resources = item.get("resources", {})
if not isinstance(resources, dict):
continue
os_name = resources.get("os")
if isinstance(os_name, str) and os_name.strip():
normalized = os_name.strip().lower()
if normalized not in os_values:
os_values.append(normalized)
return os_values
def split_notes(notes_raw: list[dict[str, Any]]) -> tuple[list[str], list[str]]:
"""Split PocketBase notes into (info_notes, warnings).
Each entry has shape ``{"text": str, "type": "warning"|...}``. Anything
flagged ``type == "warning"`` lands in the warnings list so the bash
menu can render those in red with a dedicated WARNINGS header. Other
notes go to the regular notes list.
"""
info: list[str] = []
warns: list[str] = []
for note in notes_raw or []:
if not isinstance(note, dict):
continue
text = note.get("text")
if not isinstance(text, str) or not text.strip():
continue
text = text.strip()
ntype = (note.get("type") or "").strip().lower()
if ntype == "warning":
warns.append(text)
else:
info.append(text)
return info, warns
def build_script_path(type_name: str, slug: str) -> str:
type_name = (type_name or "").strip().lower()
slug = (slug or "").strip()
if type_name == "turnkey":
return "turnkey/turnkey.sh"
prefix = TYPE_TO_PATH_PREFIX.get(type_name)
if not prefix or not slug:
return ""
return f"{prefix}/{slug}.sh"
def main() -> int:
try:
raw = requests.get(url).json()
scripts = fetch_all_records(SCRIPT_COLLECTION_URL, expand="type,categories")
categories = fetch_all_records(CATEGORY_COLLECTION_URL)
except Exception as e:
print(f"ERROR: Unable to fetch PocketBase data: {e}", file=sys.stderr)
return 1
category_map: dict[str, dict[str, Any]] = {}
for category in categories:
category_id = category.get("id")
if isinstance(category_id, str) and category_id:
category_map[category_id] = category
cache: list[dict[str, Any]] = []
print(f"Fetched {len(scripts)} scripts and {len(category_map)} categories")
for idx, raw in enumerate(scripts, start=1):
if not isinstance(raw, dict):
continue
except:
continue
# Extract fields required to identify a valid helper script
name = raw.get("name", "")
slug = raw.get("slug")
type_ = raw.get("type", "")
script = raw.get("install_methods", [{}])[0].get("script", "")
if not slug or not script:
continue # Skip if it's not a valid script
slug = raw.get("slug")
name = raw.get("name", "")
desc = raw.get("description", "")
desc = raw.get("description", "")
categories = raw.get("categories", [])
notes = [note.get("text", "") for note in raw.get("notes", []) if isinstance(note, dict)]
full_script_url = f"{SCRIPT_BASE}/{script}"
if not isinstance(slug, str) or not slug.strip():
continue
expand = raw.get("expand", {}) if isinstance(raw.get("expand"), dict) else {}
type_expanded = expand.get("type", {}) if isinstance(expand.get("type"), dict) else {}
type_name = type_expanded.get("type", "") if isinstance(type_expanded.get("type"), str) else ""
credentials = raw.get("default_credentials", {})
cred_username = credentials.get("username")
cred_password = credentials.get("password")
add_credentials = (
(cred_username is not None and str(cred_username).strip() != "") or
(cred_password is not None and str(cred_password).strip() != "")
)
script_path = build_script_path(type_name, slug)
if not script_path:
print(f"[{idx:03d}] WARNING: Unable to build script path for slug={slug} type={type_name!r}", file=sys.stderr)
continue
entry = {
"name": name,
"slug": slug,
"desc": desc,
"script": script,
"script_url": full_script_url,
"categories": categories,
"notes": notes,
"type": type_
}
if add_credentials:
entry["default_credentials"] = {
"username": cred_username,
"password": cred_password
full_script_url = f"{SCRIPT_BASE}/{script_path}"
script_url_mirror = to_mirror_url(full_script_url)
# Sprint 11.7: PocketBase exposes these as `install_methods` and
# `notes`, not `install_methods_json` / `notes_json`. The legacy field
# names silently returned [] for every entry, which is why the cache
# had empty notes and missing OS variants for every script.
install_methods = raw.get("install_methods", [])
if not isinstance(install_methods, list):
install_methods = []
notes_raw = raw.get("notes", [])
if not isinstance(notes_raw, list):
notes_raw = []
notes, warnings = split_notes(notes_raw)
category_ids = raw.get("categories", [])
if not isinstance(category_ids, list):
category_ids = []
expanded_categories = expand.get("categories", []) if isinstance(expand.get("categories"), list) else []
category_names: list[str] = []
for cat in expanded_categories:
if isinstance(cat, dict):
cat_name = cat.get("name")
if isinstance(cat_name, str) and cat_name.strip():
category_names.append(cat_name.strip())
if not category_names:
for cat_id in category_ids:
cat = category_map.get(cat_id, {})
cat_name = cat.get("name")
if isinstance(cat_name, str) and cat_name.strip():
category_names.append(cat_name.strip())
# Shared fields across all install method entries
default_user = raw.get("default_user")
default_passwd = raw.get("default_passwd")
default_credentials: dict[str, str] | None = None
if (isinstance(default_user, str) and default_user.strip()) or (isinstance(default_passwd, str) and default_passwd.strip()):
default_credentials = {
"username": default_user if isinstance(default_user, str) else "",
"password": default_passwd if isinstance(default_passwd, str) else "",
}
base_entry: dict[str, Any] = {
"name": name,
"slug": slug,
"desc": desc,
"script": script_path,
"script_url": full_script_url,
"script_url_mirror": script_url_mirror,
"type": type_name,
"type_id": raw.get("type", ""),
"categories": category_ids,
"category_names": category_names,
"notes": notes,
"warnings": warnings,
"port": raw.get("port", 0),
"website": raw.get("website", ""),
"documentation": raw.get("documentation", ""),
"logo": raw.get("logo", ""),
"updateable": bool(raw.get("updateable", False)),
"privileged": bool(raw.get("privileged", False)),
"has_arm": bool(raw.get("has_arm", False)),
"is_dev": bool(raw.get("is_dev", False)),
"execute_in": raw.get("execute_in", []),
"config_path": raw.get("config_path", ""),
}
if default_credentials:
base_entry["default_credentials"] = default_credentials
cache.append(entry)
# Emit one entry per install method so the menu shell can offer an
# explicit OS choice. When there is only one method (or none), a
# single entry is emitted with os="" (script decides at runtime).
os_variants = normalize_os_variants(install_methods)
if len(os_variants) > 1:
for os_name in os_variants:
entry = {**base_entry, "os": os_name}
cache.append(entry)
print(f"[{len(cache):03d}] {slug:<24}{script_path:<28} type={type_name:<7} os={os_name}")
else:
os_name = os_variants[0] if os_variants else ""
entry = {**base_entry, "os": os_name}
cache.append(entry)
print(f"[{len(cache):03d}] {slug:<24}{script_path:<28} type={type_name:<7} os={os_name or 'n/a'}")
# Write the JSON cache to disk
with open(OUTPUT_FILE, "w", encoding="utf-8") as f:
json.dump(cache, f, indent=2)
cache.sort(key=lambda x: (x.get("slug") or "", x.get("script") or ""))
print(f"✅ helpers_cache.json created at {OUTPUT_FILE} with {len(cache)} valid scripts.")
with OUTPUT_FILE.open("w", encoding="utf-8") as f:
json.dump(cache, f, ensure_ascii=False, indent=2)
total_notes = sum(len(e.get("notes", [])) for e in cache)
total_warns = sum(len(e.get("warnings", [])) for e in cache)
print(f"\n✅ helpers_cache.json → {OUTPUT_FILE}")
print(f" Guardados: {len(cache)} entries, {total_notes} notes, {total_warns} warnings")
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,76 @@
import requests, json
from pathlib import Path
# GitHub API URL to fetch all .json files describing scripts
API_URL = "https://api.github.com/repos/community-scripts/ProxmoxVE/contents/frontend/public/json"
# Base path to build the full URL for the installable scripts
SCRIPT_BASE = "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
# Output file where the consolidated helper scripts cache will be stored
OUTPUT_FILE = Path("json/helpers_cache.json")
OUTPUT_FILE.parent.mkdir(parents=True, exist_ok=True)
res = requests.get(API_URL)
data = res.json()
cache = []
# Loop over each file in the JSON directory
for item in data:
url = item.get("download_url")
if not url or not url.endswith(".json"):
continue
try:
raw = requests.get(url).json()
if not isinstance(raw, dict):
continue
except:
continue
# Extract fields required to identify a valid helper script
name = raw.get("name", "")
slug = raw.get("slug")
type_ = raw.get("type", "")
script = raw.get("install_methods", [{}])[0].get("script", "")
if not slug or not script:
continue # Skip if it's not a valid script
desc = raw.get("description", "")
categories = raw.get("categories", [])
notes = [note.get("text", "") for note in raw.get("notes", []) if isinstance(note, dict)]
full_script_url = f"{SCRIPT_BASE}/{script}"
credentials = raw.get("default_credentials", {})
cred_username = credentials.get("username")
cred_password = credentials.get("password")
add_credentials = (
(cred_username is not None and str(cred_username).strip() != "") or
(cred_password is not None and str(cred_password).strip() != "")
)
entry = {
"name": name,
"slug": slug,
"desc": desc,
"script": script,
"script_url": full_script_url,
"categories": categories,
"notes": notes,
"type": type_
}
if add_credentials:
entry["default_credentials"] = {
"username": cred_username,
"password": cred_password
}
cache.append(entry)
# Write the JSON cache to disk
with open(OUTPUT_FILE, "w", encoding="utf-8") as f:
json.dump(cache, f, indent=2)
print(f"✅ helpers_cache.json created at {OUTPUT_FILE} with {len(cache)} valid scripts.")

View File

@@ -0,0 +1,178 @@
#!/usr/bin/env python3
import json
import re
import sys
from pathlib import Path
import requests
# ---------- Config ----------
# API_URL = "https://api.github.com/repos/community-scripts/ProxmoxVE/contents/frontend/public/json"
API_URL = "https://api.github.com/repos/community-scripts/ProxmoxVE-Frontend-Archive/contents/public/json"
SCRIPT_BASE = "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
# Escribimos siempre en <raiz_repo>/json/helpers_cache.json, independientemente del cwd
REPO_ROOT = Path(__file__).resolve().parents[2]
OUTPUT_FILE = REPO_ROOT / "json" / "helpers_cache.json"
OUTPUT_FILE.parent.mkdir(parents=True, exist_ok=True)
# ----------------------------
def to_mirror_url(raw_url: str) -> str:
"""
Convierte una URL raw de GitHub al raw del mirror.
GH : https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/docker.sh
MIR: https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/ct/docker.sh
"""
m = re.match(r"^https://raw\.githubusercontent\.com/([^/]+)/([^/]+)/([^/]+)/(.+)$", raw_url or "")
if not m:
return ""
org, repo, branch, path = m.groups()
if org.lower() != "community-scripts" or repo != "ProxmoxVE":
return ""
return f"https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/{branch}/{path}"
def guess_os_from_script_path(script_path: str) -> str | None:
"""
Heurística suave cuando el JSON no publica resources.os:
- tools/pve/* -> proxmox
- ct/alpine-* -> alpine
- tools/addon/* -> generic (suele ejecutarse sobre LXC existente)
- ct/* -> debian (por defecto para CTs)
"""
if not script_path:
return None
if script_path.startswith("tools/pve/") or script_path == "tools/pve/host-backup.sh" or script_path.startswith("vm/"):
return "proxmox"
if "/alpine-" in script_path or script_path.startswith("ct/alpine-"):
return "alpine"
if script_path.startswith("tools/addon/"):
return "generic"
if script_path.startswith("ct/"):
return "debian"
return None
def fetch_directory_json(api_url: str) -> list[dict]:
r = requests.get(api_url, timeout=30)
r.raise_for_status()
data = r.json()
if not isinstance(data, list):
raise RuntimeError("GitHub API no devolvió una lista.")
return data
def main() -> int:
try:
directory = fetch_directory_json(API_URL)
except Exception as e:
print(f"ERROR: No se pudo leer el índice de JSONs: {e}", file=sys.stderr)
return 1
cache: list[dict] = []
seen: set[tuple[str, str]] = set() # (slug, script) para evitar duplicados
total_items = len(directory)
processed = 0
kept = 0
for item in directory:
url = item.get("download_url")
name_in_dir = item.get("name", "")
if not url or not url.endswith(".json"):
continue
try:
raw = requests.get(url, timeout=30).json()
if not isinstance(raw, dict):
continue
except Exception:
print(f"❌ Error al obtener/parsing {name_in_dir}", file=sys.stderr)
continue
processed += 1
name = raw.get("name", "")
slug = raw.get("slug")
type_ = raw.get("type", "")
desc = raw.get("description", "")
categories = raw.get("categories", [])
notes = [n.get("text", "") for n in raw.get("notes", []) if isinstance(n, dict)]
# Credenciales (si existen, se copian tal cual)
credentials = raw.get("default_credentials", {})
cred_username = credentials.get("username") if isinstance(credentials, dict) else None
cred_password = credentials.get("password") if isinstance(credentials, dict) else None
add_credentials = any([
cred_username not in (None, ""),
cred_password not in (None, "")
])
install_methods = raw.get("install_methods", [])
if not isinstance(install_methods, list) or not install_methods:
# Sin install_methods válidos -> continuamos
continue
for im in install_methods:
if not isinstance(im, dict):
continue
script = im.get("script", "")
if not script:
continue
# OS desde resources u heurística
resources = im.get("resources", {}) if isinstance(im, dict) else {}
os_name = resources.get("os") if isinstance(resources, dict) else None
if not os_name:
os_name = guess_os_from_script_path(script)
if isinstance(os_name, str):
os_name = os_name.strip().lower()
full_script_url = f"{SCRIPT_BASE}/{script}"
script_url_mirror = to_mirror_url(full_script_url)
key = (slug or "", script)
if key in seen:
continue
seen.add(key)
entry = {
"name": name,
"slug": slug,
"desc": desc,
"script": script,
"script_url": full_script_url,
"script_url_mirror": script_url_mirror, # nuevo
"os": os_name, # nuevo
"categories": categories,
"notes": notes,
"type": type_,
}
if add_credentials:
entry["default_credentials"] = {
"username": cred_username,
"password": cred_password,
}
cache.append(entry)
kept += 1
# Progreso ligero
print(f"[{kept:03d}] {slug or name:<24}{script:<28} os={os_name or 'n/a'} src={'GH+MR' if script_url_mirror else 'GH'}")
# Orden estable para commits reproducibles
cache.sort(key=lambda x: (x.get("slug") or "", x.get("script") or ""))
with OUTPUT_FILE.open("w", encoding="utf-8") as f:
json.dump(cache, f, ensure_ascii=False, indent=2)
print(f"\n✅ helpers_cache.json → {OUTPUT_FILE}")
print(f" Total JSON en índice: {total_items}")
print(f" Procesados: {processed} | Guardados: {kept} | Únicos (slug,script): {len(seen)}")
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,83 @@
name: Build AppImage Release
on:
workflow_dispatch:
permissions:
contents: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
build:
runs-on: ubuntu-22.04
steps:
- name: Checkout main
uses: actions/checkout@v6
with:
ref: main
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
- name: Install dependencies
working-directory: AppImage
run: npm install --legacy-peer-deps
- name: Build Next.js app
working-directory: AppImage
run: npm run build
- name: Install Python dependencies
run: |
sudo apt-get update
sudo apt-get install -y python3 python3-pip python3-venv
- name: Make build script executable
working-directory: AppImage
run: chmod +x scripts/build_appimage.sh
- name: Build AppImage
working-directory: AppImage
run: ./scripts/build_appimage.sh
- name: Get version from package.json
id: version
working-directory: AppImage
run: echo "VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
- name: Generate SHA256 checksum
run: |
cd AppImage/dist
sha256sum *.AppImage > ProxMenux-Monitor.AppImage.sha256
echo "Generated SHA256:"
cat ProxMenux-Monitor.AppImage.sha256
- name: Upload AppImage artifact
uses: actions/upload-artifact@v6
with:
name: ProxMenux-${{ steps.version.outputs.VERSION }}-AppImage
path: |
AppImage/dist/*.AppImage
AppImage/dist/*.sha256
retention-days: 30
- name: Commit AppImage to main
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
rm -f AppImage/*.AppImage AppImage/*.sha256 || true
cp AppImage/dist/*.AppImage AppImage/
cp AppImage/dist/ProxMenux-Monitor.AppImage.sha256 AppImage/
git add AppImage/*.AppImage AppImage/*.sha256
git commit -m "Update AppImage release build ($(date +'%Y-%m-%d %H:%M:%S'))" || echo "No changes to commit"
git push origin main

View File

@@ -0,0 +1,83 @@
name: Build AppImage Beta
on:
workflow_dispatch:
permissions:
contents: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
build:
runs-on: ubuntu-22.04
steps:
- name: Checkout develop
uses: actions/checkout@v6
with:
ref: develop
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
- name: Install dependencies
working-directory: AppImage
run: npm install --legacy-peer-deps
- name: Build Next.js app
working-directory: AppImage
run: npm run build
- name: Install Python dependencies
run: |
sudo apt-get update
sudo apt-get install -y python3 python3-pip python3-venv
- name: Make build script executable
working-directory: AppImage
run: chmod +x scripts/build_appimage.sh
- name: Build AppImage
working-directory: AppImage
run: ./scripts/build_appimage.sh
- name: Get version from package.json
id: version
working-directory: AppImage
run: echo "VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
- name: Generate SHA256 checksum
run: |
cd AppImage/dist
sha256sum *.AppImage > ProxMenux-Monitor.AppImage.sha256
echo "Generated SHA256:"
cat ProxMenux-Monitor.AppImage.sha256
- name: Upload AppImage artifact
uses: actions/upload-artifact@v6
with:
name: ProxMenux-${{ steps.version.outputs.VERSION }}-beta-AppImage
path: |
AppImage/dist/*.AppImage
AppImage/dist/*.sha256
retention-days: 30
- name: Commit AppImage to develop
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
rm -f AppImage/*.AppImage AppImage/*.sha256 || true
cp AppImage/dist/*.AppImage AppImage/
cp AppImage/dist/ProxMenux-Monitor.AppImage.sha256 AppImage/
git add AppImage/*.AppImage AppImage/*.sha256
git commit -m "Update AppImage beta build ($(date +'%Y-%m-%d %H:%M:%S'))" || echo "No changes to commit"
git push origin develop

View File

@@ -0,0 +1,81 @@
name: Build ProxMenux Monitor AppImage
on:
workflow_dispatch:
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
working-directory: AppImage
run: npm install --legacy-peer-deps
- name: Build Next.js app
working-directory: AppImage
run: npm run build
- name: Install Python dependencies
run: |
sudo apt-get update
sudo apt-get install -y python3 python3-pip python3-venv
- name: Make build script executable
working-directory: AppImage
run: chmod +x scripts/build_appimage.sh
- name: Build AppImage
working-directory: AppImage
run: ./scripts/build_appimage.sh
- name: Get version from package.json
id: version
working-directory: AppImage
run: echo "VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
- name: Upload AppImage artifact
uses: actions/upload-artifact@v4
with:
name: ProxMenux-${{ steps.version.outputs.VERSION }}-AppImage
path: AppImage/dist/*.AppImage
retention-days: 30
- name: Generate SHA256 checksum
run: |
cd AppImage/dist
sha256sum *.AppImage > ProxMenux-Monitor.AppImage.sha256
echo "Generated SHA256:"
cat ProxMenux-Monitor.AppImage.sha256
- name: Upload AppImage and checksum to /AppImage folder in main
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git fetch origin main
git checkout main
rm -f AppImage/*.AppImage AppImage/*.sha256 || true
# Copy new files
cp AppImage/dist/*.AppImage AppImage/
cp AppImage/dist/ProxMenux-Monitor.AppImage.sha256 AppImage/
git add AppImage/*.AppImage AppImage/*.sha256
git commit -m "Update AppImage build ($(date +'%Y-%m-%d %H:%M:%S'))" || echo "No changes to commit"
git push origin main

59
.github/workflows/build-appimage.yml vendored Normal file
View File

@@ -0,0 +1,59 @@
name: Build ProxMenux Monitor AppImage
on:
push:
branches: [ main ]
paths: [ 'AppImage/**' ]
pull_request:
branches: [ main ]
paths: [ 'AppImage/**' ]
workflow_dispatch:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
build:
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
- name: Install dependencies
working-directory: AppImage
run: npm install --legacy-peer-deps
- name: Build Next.js app
working-directory: AppImage
run: npm run build
- name: Install Python dependencies
run: |
sudo apt-get update
sudo apt-get install -y python3 python3-pip python3-venv
- name: Make build script executable
working-directory: AppImage
run: chmod +x scripts/build_appimage.sh
- name: Build AppImage
working-directory: AppImage
run: ./scripts/build_appimage.sh
- name: Get version from package.json
id: version
working-directory: AppImage
run: echo "VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
- name: Upload AppImage artifact
uses: actions/upload-artifact@v6
with:
name: ProxMenux-${{ steps.version.outputs.VERSION }}-AppImage
path: AppImage/dist/*.AppImage
retention-days: 30

2
.gitignore vendored
View File

@@ -51,3 +51,5 @@ Thumbs.db
!guides/
!web/
# GitHub authentication
.github/auth.sh

BIN
AppImage/ProxMenux-1.2.0.AppImage Executable file

Binary file not shown.

View File

@@ -0,0 +1 @@
db5bc199adba9c231f344428ac902a0cbf7473778e8a79a4535263599d975449 ProxMenux-1.2.0.AppImage

753
AppImage/README.md Normal file
View File

@@ -0,0 +1,753 @@
# ProxMenux Monitor
A modern, responsive dashboard for monitoring Proxmox VE systems built with Next.js and React.
---
## Table of Contents
- [Overview](#overview)
- [Features](#features)
- [Technology Stack](#technology-stack)
- [Installation](#installation)
- [Authentication & Security](#authentication--security)
- [Setup Authentication](#setup-authentication)
- [Two-Factor Authentication (2FA)](#two-factor-authentication-2fa)
- [Security Best Practices for API Tokens](#security-best-practices-for-api-tokens)
- [API Documentation](#api-documentation)
- [API Authentication](#api-authentication)
- [Generating API Tokens](#generating-api-tokens)
- [Available Endpoints](#available-endpoints)
- [Integration Examples](#integration-examples)
- [Homepage Integration](#homepage-integration)
- [Home Assistant Integration](#home-assistant-integration)
- [License](#license)
---
## Overview
**ProxMenux Monitor** is a comprehensive, real-time monitoring dashboard for Proxmox VE environments. Built with modern web technologies, it provides an intuitive interface to monitor system resources, virtual machines, containers, storage, network traffic, and system logs.
The application runs as a standalone AppImage on your Proxmox server and serves a web interface accessible from any device on your network.
## Screenshots
Get a quick overview of ProxMenux Monitor's main features:
<p align="center">
<img src="public/images/onboarding/imagen1.png" alt="Overview Dashboard" width="800"/>
<br/>
<em>System Overview - Monitor CPU, memory, temperature, and uptime in real-time</em>
</p>
---
## Features
- **System Overview**: Real-time monitoring of CPU, memory, temperature, and system uptime
- **Storage Management**: Visual representation of storage distribution, disk health, and SMART data
- **Network Monitoring**: Network interface statistics, real-time traffic graphs, and bandwidth usage
- **Virtual Machines & LXC**: Comprehensive view of all VMs and containers with resource usage and controls
- **Hardware Information**: Detailed hardware specifications including CPU, GPU, PCIe devices, and disks
- **System Logs**: Real-time system log monitoring with filtering and search capabilities
- **Health Monitoring**: Proactive system health checks with persistent error tracking
- **Authentication & 2FA**: Optional password protection with TOTP-based two-factor authentication
- **RESTful API**: Complete API access for integrations with Homepage, Home Assistant, and custom dashboards
- **Dark/Light Theme**: Toggle between themes with Proxmox-inspired design
- **Responsive Design**: Works seamlessly on desktop, tablet, and mobile devices
- **Release Notes**: Automatic notifications of new features and improvements
## Technology Stack
- **Frontend**: Next.js 15, React 19, TypeScript
- **Styling**: Tailwind CSS v4 with custom Proxmox-inspired theme
- **Charts**: Recharts for data visualization
- **UI Components**: Radix UI primitives with shadcn/ui
- **Backend**: Flask (Python) server for system data collection
- **Packaging**: AppImage for easy distribution and deployment
## Installation
**ProxMenux Monitor is integrated into [ProxMenux](https://proxmenux.com) and comes enabled by default.** No manual installation is required if you're using ProxMenux.
The monitor automatically starts when ProxMenux is installed and runs as a systemd service on your Proxmox server.
### Accessing the Dashboard
You can access ProxMenux Monitor in two ways:
1. **Direct Access**: `http://your-proxmox-ip:8008`
2. **Via Proxy** (Recommended): `https://your-domain.com/proxmenux-monitor/`
**Note**: All API endpoints work seamlessly with both direct access and proxy configurations. When using a reverse proxy, the application automatically detects and adapts to the proxied environment.
### Proxy Configuration
ProxMenux Monitor includes built-in support for reverse proxy configurations. If you're using Nginx, Caddy, or Traefik, the application will automatically:
- Detect the proxy headers (`X-Forwarded-For`, `X-Forwarded-Proto`, `X-Forwarded-Host`)
- Adjust API endpoints to work correctly through the proxy
- Maintain full functionality for all features including authentication and API access
## Authentication & Security
ProxMenux Monitor includes an optional authentication system to protect your dashboard with a password and two-factor authentication.
### Setup Authentication
On first launch, you'll be presented with three options:
1. **Set up authentication** - Create a username and password to protect your dashboard
2. **Enable 2FA** - Add TOTP-based two-factor authentication for enhanced security
3. **Skip** - Continue without authentication (not recommended for production environments)
![Authentication Setup](AppImage/public/images/docs/auth-setup.png)
### Two-Factor Authentication (2FA)
After setting up your password, you can enable 2FA using any TOTP authenticator app (Google Authenticator, Authy, 1Password, etc.):
1. Navigate to **Settings > Authentication**
2. Click **Enable 2FA**
3. Scan the QR code with your authenticator app
4. Enter the 6-digit code to verify
5. Save your backup codes in a secure location
![2FA Setup](AppImage/public/images/docs/2fa-setup.png)
### Security Best Practices for API Tokens
**IMPORTANT**: Never hardcode your API tokens directly in configuration files or scripts. Instead, use environment variables or secrets management.
**Option 1: Environment Variables**
Store your token in an environment variable:
```bash
# Linux/macOS - Add to ~/.bashrc or ~/.zshrc
export PROXMENUX_API_TOKEN="your_actual_token_here"
# Windows PowerShell - Add to profile
$env:PROXMENUX_API_TOKEN = "your_actual_token_here"
```
Then reference it in your scripts:
```bash
# Linux/macOS
curl -H "Authorization: Bearer $PROXMENUX_API_TOKEN" \
http://your-proxmox-ip:8008/api/system
# Windows PowerShell
curl -H "Authorization: Bearer $env:PROXMENUX_API_TOKEN" `
http://your-proxmox-ip:8008/api/system
```
**Option 2: Secrets File**
Create a dedicated secrets file (make sure to add it to `.gitignore`):
```bash
# Create secrets file
echo "PROXMENUX_API_TOKEN=your_actual_token_here" > ~/.proxmenux_secrets
# Secure the file (Linux/macOS only)
chmod 600 ~/.proxmenux_secrets
# Load in your script
source ~/.proxmenux_secrets
```
**Option 3: Homepage Secrets (Recommended)**
Homepage supports secrets management. Create a `secrets.yaml` file:
```yaml
# secrets.yaml (add to .gitignore!)
proxmenux_token: "your_actual_token_here"
```
Then reference it in your `services.yaml`:
```yaml
- ProxMenux Monitor:
widget:
type: customapi
url: http://proxmox.example.tld:8008/api/system
headers:
Authorization: Bearer {{HOMEPAGE_VAR_PROXMENUX_TOKEN}}
```
**Option 4: Home Assistant Secrets**
Home Assistant has built-in secrets support. Edit `secrets.yaml`:
```yaml
# secrets.yaml
proxmenux_api_token: "your_actual_token_here"
```
Then reference it in `configuration.yaml`:
```yaml
sensor:
- platform: rest
name: ProxMenux CPU
resource: http://proxmox.example.tld:8008/api/system
headers:
Authorization: !secret proxmenux_api_token
```
**Token Security Checklist:**
- ✅ Store tokens in environment variables or secrets files
- ✅ Add secrets files to `.gitignore`
- ✅ Set proper file permissions (chmod 600 on Linux/macOS)
- ✅ Rotate tokens periodically (every 3-6 months)
- ✅ Use different tokens for different integrations
- ✅ Delete tokens you no longer use
- ❌ Never commit tokens to version control
- ❌ Never share tokens in screenshots or logs
- ❌ Never hardcode tokens in configuration files
---
## API Documentation
ProxMenux Monitor provides a comprehensive RESTful API for integrating with external services like Homepage, Home Assistant, or custom dashboards.
### API Authentication
When authentication is enabled on ProxMenux Monitor, all API endpoints (except `/api/health` and `/api/auth/*`) require a valid JWT token in the `Authorization` header.
### API Endpoint Base URL
**Direct Access:**
```
http://your-proxmox-ip:8008/api/
```
**Via Proxy:**
```
https://your-domain.com/proxmenux-monitor/api/
```
**Note**: All API examples in this documentation work with both direct and proxied URLs. Simply replace the base URL with your preferred access method.
### Generating API Tokens
To use the API with authentication enabled, you need to generate a long-lived API token.
#### Option 1: Generate via Web Panel (Recommended)
The easiest way to generate an API token is through the ProxMenux Monitor web interface:
1. Navigate to **Settings** tab in the dashboard
2. Scroll to the **API Access Tokens** section
3. Enter your password
4. If 2FA is enabled, enter your 6-digit code
5. Provide a name for the token (e.g., "Homepage Integration")
6. Click **Generate Token**
7. Copy the token immediately - it will not be shown again
![Generate API Token](AppImage/public/images/docs/generate-api-token.png)
The token will be valid for **365 days** (1 year) and can be used for integrations with Homepage, Home Assistant, or any custom application.
#### Option 2: Generate via API Call
For advanced users or automation, you can generate tokens programmatically:
```bash
curl -X POST http://your-proxmox-ip:8008/api/auth/generate-api-token \
-H "Content-Type: application/json" \
-d '{
"username": "your-username",
"password": "your-password",
"totp_token": "123456",
"token_name": "Homepage Integration"
}'
```
**Response:**
```json
{
"success": true,
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_name": "Homepage Integration",
"expires_in": "365 days",
"message": "API token generated successfully. Store this token securely, it will not be shown again."
}
```
**Notes:**
- If 2FA is enabled, include the `totp_token` field with your 6-digit code
- If 2FA is not enabled, omit the `totp_token` field
- The token is valid for **365 days** (1 year)
- Store the token securely - it cannot be retrieved again
#### Option 3: Generate via cURL (without 2FA)
```bash
# Without 2FA
curl -X POST http://your-proxmox-ip:8008/api/auth/generate-api-token \
-H "Content-Type: application/json" \
-d '{"username":"pedro","password":"your-password","token_name":"Homepage"}'
```
### Using API Tokens
Once you have your API token, include it in the `Authorization` header of all API requests:
```bash
curl -H "Authorization: Bearer YOUR_API_TOKEN_HERE" \
http://your-proxmox-ip:8008/api/system
```
---
### Available Endpoints
Below is a complete list of all API endpoints with descriptions and example responses.
#### System & Metrics
| Endpoint | Method | Auth Required | Description |
|----------|--------|---------------|-------------|
| `/api/system` | GET | Yes | Complete system information (CPU, memory, temperature, uptime) |
| `/api/system-info` | GET | No | Lightweight system info for header (hostname, uptime, health) |
| `/api/node/metrics` | GET | Yes | Historical metrics data (RRD) for CPU, memory, disk I/O |
| `/api/prometheus` | GET | Yes | Export metrics in Prometheus format |
**Example `/api/system` Response:**
```json
{
"hostname": "pve",
"cpu_usage": 15.2,
"memory_usage": 45.8,
"temperature": 42.5,
"uptime": 345600,
"kernel": "6.2.16-3-pve",
"pve_version": "8.0.3"
}
```
#### Storage
| Endpoint | Method | Auth Required | Description |
|----------|--------|---------------|-------------|
| `/api/storage` | GET | Yes | Complete storage information with SMART data |
| `/api/storage/summary` | GET | Yes | Optimized storage summary (without SMART) |
| `/api/proxmox-storage` | GET | Yes | Proxmox storage pools information |
| `/api/backups` | GET | Yes | List of all backup files |
**Example `/api/storage/summary` Response:**
```json
{
"total_capacity": 1431894917120,
"used_space": 197414092800,
"free_space": 1234480824320,
"usage_percentage": 13.8,
"disks": [
{
"device": "/dev/sda",
"model": "Samsung SSD 970",
"size": "476.94 GB",
"type": "SSD"
}
]
}
```
#### Network
| Endpoint | Method | Auth Required | Description |
|----------|--------|---------------|-------------|
| `/api/network` | GET | Yes | Complete network information for all interfaces |
| `/api/network/summary` | GET | Yes | Optimized network summary |
| `/api/network/<interface>/metrics` | GET | Yes | Historical metrics (RRD) for specific interface |
**Example `/api/network/summary` Response:**
```json
{
"interfaces": [
{
"name": "vmbr0",
"ip": "192.168.1.100",
"state": "up",
"rx_bytes": 1234567890,
"tx_bytes": 987654321
}
]
}
```
#### Virtual Machines & Containers
| Endpoint | Method | Auth Required | Description |
|----------|--------|---------------|-------------|
| `/api/vms` | GET | Yes | List of all VMs and LXC containers |
| `/api/vms/<vmid>` | GET | Yes | Detailed configuration for specific VM/LXC |
| `/api/vms/<vmid>/metrics` | GET | Yes | Historical metrics (RRD) for specific VM/LXC |
| `/api/vms/<vmid>/logs` | GET | Yes | Download real logs for specific VM/LXC |
| `/api/vms/<vmid>/control` | POST | Yes | Control VM/LXC (start, stop, shutdown, reboot) |
| `/api/vms/<vmid>/config` | PUT | Yes | Update VM/LXC configuration (description/notes) |
**Example `/api/vms` Response:**
```json
{
"vms": [
{
"vmid": "100",
"name": "ubuntu-server",
"type": "qemu",
"status": "running",
"cpu": 2,
"maxcpu": 4,
"mem": 2147483648,
"maxmem": 4294967296,
"uptime": 86400
}
]
}
```
#### Hardware
| Endpoint | Method | Auth Required | Description |
|----------|--------|---------------|-------------|
| `/api/hardware` | GET | Yes | Complete hardware information (CPU, GPU, PCIe, disks) |
| `/api/gpu/<slot>/realtime` | GET | Yes | Real-time monitoring for specific GPU |
**Example `/api/hardware` Response:**
```json
{
"cpu": {
"model": "AMD Ryzen 9 5950X",
"cores": 16,
"threads": 32,
"frequency": "3.4 GHz"
},
"gpus": [
{
"slot": "0000:01:00.0",
"vendor": "NVIDIA",
"model": "GeForce RTX 3080",
"driver": "nvidia"
}
]
}
```
#### Logs, Events & Notifications
| Endpoint | Method | Auth Required | Description |
|----------|--------|---------------|-------------|
| `/api/logs` | GET | Yes | System logs (journalctl) with filters |
| `/api/logs/download` | GET | Yes | Download logs as text file |
| `/api/notifications` | GET | Yes | Proxmox notification history |
| `/api/notifications/download` | GET | Yes | Download full notification log |
| `/api/events` | GET | Yes | Recent Proxmox tasks and events |
| `/api/task-log/<upid>` | GET | Yes | Full log for specific task using UPID |
**Example `/api/logs` Query Parameters:**
```
/api/logs?severity=error&since=1h&search=failed
```
#### Health Monitoring
| Endpoint | Method | Auth Required | Description |
|----------|--------|---------------|-------------|
| `/api/health` | GET | No | Basic health check (for external monitoring) |
| `/api/health/status` | GET | Yes | Summary of system health status |
| `/api/health/details` | GET | Yes | Detailed health check results |
| `/api/health/acknowledge` | POST | Yes | Dismiss/acknowledge health warnings |
| `/api/health/active-errors` | GET | Yes | Get active persistent errors |
#### ProxMenux Optimizations
| Endpoint | Method | Auth Required | Description |
|----------|--------|---------------|-------------|
| `/api/proxmenux/installed-tools` | GET | Yes | List of installed ProxMenux optimizations |
#### Authentication
| Endpoint | Method | Auth Required | Description |
|----------|--------|---------------|-------------|
| `/api/auth/status` | GET | No | Current authentication status |
| `/api/auth/login` | POST | No | Authenticate and receive JWT token |
| `/api/auth/generate-api-token` | POST | No | Generate long-lived API token (365 days) |
| `/api/auth/setup` | POST | No | Initial setup of username/password |
| `/api/auth/enable` | POST | No | Enable authentication |
| `/api/auth/disable` | POST | Yes | Disable authentication |
| `/api/auth/change-password` | POST | No | Change password |
| `/api/auth/totp/setup` | POST | Yes | Initialize 2FA setup |
| `/api/auth/totp/enable` | POST | Yes | Enable 2FA after verification |
| `/api/auth/totp/disable` | POST | Yes | Disable 2FA |
---
## Integration Examples
### Homepage Integration
[Homepage](https://gethomepage.dev/) is a modern, fully static, fast, secure fully proxied, highly customizable application dashboard.
#### Basic Configuration (No Authentication)
```yaml
- ProxMenux Monitor:
href: http://proxmox.example.tld:8008/
icon: lucide:flask-round
widget:
type: customapi
url: http://proxmox.example.tld:8008/api/system
refreshInterval: 10000
mappings:
- field: uptime
label: Uptime
icon: lucide:clock-4
format: text
- field: cpu_usage
label: CPU
icon: lucide:cpu
format: percent
- field: memory_usage
label: RAM
icon: lucide:memory-stick
format: percent
- field: temperature
label: Temp
icon: lucide:thermometer-sun
format: number
suffix: °C
```
#### With Authentication Enabled (Using Secrets)
First, generate an API token via the web interface (Settings > API Access Tokens) or via API.
Then, store your token securely in Homepage's `secrets.yaml`:
```yaml
# secrets.yaml (add to .gitignore!)
proxmenux_token: "your_actual_api_token_here"
```
Finally, reference the secret in your `services.yaml`:
```yaml
- ProxMenux Monitor:
href: http://proxmox.example.tld:8008/
icon: lucide:flask-round
widget:
type: customapi
url: http://proxmox.example.tld:8008/api/system
headers:
Authorization: Bearer {{HOMEPAGE_VAR_PROXMENUX_TOKEN}}
refreshInterval: 10000
mappings:
- field: uptime
label: Uptime
icon: lucide:clock-4
format: text
- field: cpu_usage
label: CPU
icon: lucide:cpu
format: percent
- field: memory_usage
label: RAM
icon: lucide:memory-stick
format: percent
- field: temperature
label: Temp
icon: lucide:thermometer-sun
format: number
suffix: °C
```
#### Advanced Multi-Widget Configuration
```yaml
# Store token in secrets.yaml
# proxmenux_token: "your_actual_api_token_here"
- ProxMenux System:
href: http://proxmox.example.tld:8008/
icon: lucide:server
description: Proxmox VE Host
widget:
type: customapi
url: http://proxmox.example.tld:8008/api/system
headers:
Authorization: Bearer {{HOMEPAGE_VAR_PROXMENUX_TOKEN}}
refreshInterval: 5000
mappings:
- field: cpu_usage
label: CPU
icon: lucide:cpu
format: percent
- field: memory_usage
label: RAM
icon: lucide:memory-stick
format: percent
- field: temperature
label: Temp
icon: lucide:thermometer-sun
format: number
suffix: °C
- ProxMenux Storage:
href: http://proxmox.example.tld:8008/#/storage
icon: lucide:hard-drive
description: Storage Overview
widget:
type: customapi
url: http://proxmox.example.tld:8008/api/storage/summary
headers:
Authorization: Bearer {{HOMEPAGE_VAR_PROXMENUX_TOKEN}}
refreshInterval: 30000
mappings:
- field: usage_percentage
label: Used
icon: lucide:database
format: percent
- field: used_space
label: Space
icon: lucide:folder
format: bytes
- ProxMenux Network:
href: http://proxmox.example.tld:8008/#/network
icon: lucide:network
description: Network Stats
widget:
type: customapi
url: http://proxmox.example.tld:8008/api/network/summary
headers:
Authorization: Bearer {{HOMEPAGE_VAR_PROXMENUX_TOKEN}}
refreshInterval: 5000
mappings:
- field: interfaces[0].rx_bytes
label: Received
icon: lucide:download
format: bytes
- field: interfaces[0].tx_bytes
label: Sent
icon: lucide:upload
format: bytes
```
![Homepage Integration Example](AppImage/public/images/docs/homepage-integration.png)
### Home Assistant Integration
[Home Assistant](https://www.home-assistant.io/) is an open-source home automation platform.
#### Store Token Securely
First, add your API token to Home Assistant's `secrets.yaml`:
```yaml
# secrets.yaml
proxmenux_api_token: "Bearer your_actual_api_token_here"
```
**Note**: Include "Bearer " prefix in the secrets file for Home Assistant.
#### Configuration.yaml
```yaml
# ProxMenux Monitor Sensors
sensor:
- platform: rest
name: ProxMenux CPU
resource: http://proxmox.example.tld:8008/api/system
headers:
Authorization: !secret proxmenux_api_token
value_template: "{{ value_json.cpu_usage }}"
unit_of_measurement: "%"
scan_interval: 30
- platform: rest
name: ProxMenux Memory
resource: http://proxmox.example.tld:8008/api/system
headers:
Authorization: !secret proxmenux_api_token
value_template: "{{ value_json.memory_usage }}"
unit_of_measurement: "%"
scan_interval: 30
- platform: rest
name: ProxMenux Temperature
resource: http://proxmox.example.tld:8008/api/system
headers:
Authorization: !secret proxmenux_api_token
value_template: "{{ value_json.temperature }}"
unit_of_measurement: "°C"
device_class: temperature
scan_interval: 30
- platform: rest
name: ProxMenux Uptime
resource: http://proxmox.example.tld:8008/api/system
headers:
Authorization: !secret proxmenux_api_token
value_template: >
{% set uptime_seconds = value_json.uptime | int %}
{% set days = (uptime_seconds / 86400) | int %}
{% set hours = ((uptime_seconds % 86400) / 3600) | int %}
{% set minutes = ((uptime_seconds % 3600) / 60) | int %}
{{ days }}d {{ hours }}h {{ minutes }}m
scan_interval: 60
```
#### Lovelace Card Example
```yaml
type: entities
title: Proxmox Monitor
entities:
- entity: sensor.proxmenux_cpu
name: CPU Usage
icon: mdi:cpu-64-bit
- entity: sensor.proxmenux_memory
name: Memory Usage
icon: mdi:memory
- entity: sensor.proxmenux_temperature
name: Temperature
icon: mdi:thermometer
- entity: sensor.proxmenux_uptime
name: Uptime
icon: mdi:clock-outline
```
![Home Assistant Integration Example](AppImage/public/images/docs/homeassistant-integration.png)
---
## License
This project is licensed under the **Creative Commons Attribution-NonCommercial 4.0 International License (CC BY-NC 4.0)**.
You are free to:
- Share — copy and redistribute the material in any medium or format
- Adapt — remix, transform, and build upon the material
Under the following terms:
- Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made
- NonCommercial — You may not use the material for commercial purposes
For more details, see the [full license](https://creativecommons.org/licenses/by-nc/4.0/).
---
**ProxMenux Monitor** - Made with ❤️ for the Proxmox community

View File

@@ -0,0 +1,9 @@
import { ProxmoxDashboard } from "../../components/proxmox-dashboard"
export default function DashboardPage() {
return (
<main className="min-h-screen bg-background">
<ProxmoxDashboard />
</main>
)
}

177
AppImage/app/globals.css Normal file
View File

@@ -0,0 +1,177 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
/* ===================== */
/* Light Mode (default) */
/* ===================== */
:root {
--background: oklch(1 0 0); /* blanco */
--foreground: oklch(0.145 0 0); /* casi negro */
--card: oklch(1 0 0);
--card-foreground: var(--foreground);
--popover: var(--card);
--popover-foreground: var(--foreground);
--primary: oklch(0.205 0 0); /* gris oscuro */
--primary-foreground: oklch(0.985 0 0); /* blanco */
--secondary: oklch(0.97 0 0);
--secondary-foreground: var(--primary);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0); /* gris medio */
--accent: oklch(0.97 0 0);
--accent-foreground: var(--primary);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.145 0 0);
--border: oklch(0.922 0 0);
--input: var(--border);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--radius: 0.625rem;
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: var(--foreground);
--sidebar-primary: var(--primary);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: var(--primary);
--sidebar-border: var(--border);
--sidebar-ring: var(--ring);
}
/* ===================== */
/* Dark Mode (gris) */
/* ===================== */
.dark {
--background: oklch(0.22 0 0); /* gris oscuro */
--foreground: oklch(0.97 0 0); /* blanco/gris claro */
--card: oklch(0.24 0 0);
--card-foreground: var(--foreground);
--popover: var(--card);
--popover-foreground: var(--foreground);
--primary: oklch(0.83 0 0); /* casi blanco */
--primary-foreground: var(--background);
--secondary: oklch(0.28 0 0);
--secondary-foreground: oklch(0.92 0 0);
--muted: oklch(0.26 0 0);
--muted-foreground: oklch(0.72 0 0);
--accent: oklch(0.28 0 0);
--accent-foreground: var(--primary);
--destructive: oklch(0.53 0.25 27);
--destructive-foreground: oklch(0.9 0 0);
--border: oklch(0.34 0 0);
--input: var(--border);
--ring: oklch(0.55 0 0);
--chart-1: oklch(0.60 0.20 255);
--chart-2: oklch(0.70 0.16 165);
--chart-3: oklch(0.76 0.19 70);
--chart-4: oklch(0.63 0.25 305);
--chart-5: oklch(0.66 0.24 20);
--sidebar: oklch(0.24 0 0);
--sidebar-foreground: var(--foreground);
--sidebar-primary: var(--chart-1);
--sidebar-primary-foreground: var(--foreground);
--sidebar-accent: oklch(0.28 0 0);
--sidebar-accent-foreground: var(--foreground);
--sidebar-border: var(--border);
--sidebar-ring: var(--ring);
}
/* ===================== */
/* Base layer */
/* ===================== */
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
/* Foco accesible */
:is(button,[role="button"],a,input,select,textarea,[tabindex]:not([tabindex="-1"])):focus {
@apply outline-none;
}
:is(button,[role="button"],a,input,select,textarea,[tabindex]:not([tabindex="-1"])):focus-visible {
@apply ring-2;
--tw-ring-color: var(--ring);
--tw-ring-opacity: 0.5; /* equivalente al /50 */
}
}
/* ===================== */
/* Ajustes para Charts */
/* ===================== */
@layer components {
/* Recharts axis */
.recharts-cartesian-axis-tick tspan {
fill: var(--muted-foreground);
}
.recharts-cartesian-axis-line,
.recharts-cartesian-grid line {
stroke: var(--border);
}
/* Chart.js axis */
.chartjs-render-monitor text {
fill: var(--muted-foreground);
}
.chartjs-render-monitor line {
stroke: var(--border);
}
}
/* ===================== */
/* Ajustes para xterm.js */
/* ===================== */
/* Quitar padding para que la terminal ocupe el 100% del ancho */
.xterm {
padding: 0 !important;
}
/* Por si acaso el viewport añade padding extra */
.xterm .xterm-viewport {
padding: 0 !important;
}
/* Opcional: asegurar que no haya margen raro */
.xterm-rows {
margin: 0 !important;
}
/* ===================== */
/* Progress Animations */
/* ===================== */
@keyframes indeterminate {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(400%);
}
}

View File

@@ -0,0 +1,5 @@
import Hardware from "@/components/hardware"
export default function HardwarePage() {
return <Hardware />
}

52
AppImage/app/layout.tsx Normal file
View File

@@ -0,0 +1,52 @@
import type React from "react"
import type { Metadata, Viewport } from "next"
import { GeistSans } from "geist/font/sans"
import { GeistMono } from "geist/font/mono"
import { ThemeProvider } from "../components/theme-provider"
import { Suspense } from "react"
import "./globals.css"
export const metadata: Metadata = {
title: "ProxMenux Monitor",
description: "Proxmox System Dashboard and Monitor",
generator: "v0.app",
manifest: "/manifest.json",
icons: {
icon: [
{ url: "/favicon.ico", sizes: "any" },
{ url: "/icon.svg", type: "image/svg+xml" },
{ url: "/icon.png", type: "image/png", sizes: "32x32" },
],
shortcut: "/favicon.ico",
apple: [{ url: "/apple-touch-icon.png", sizes: "180x180", type: "image/png" }],
},
}
export const viewport: Viewport = {
width: "device-width",
initialScale: 1,
maximumScale: 1,
userScalable: false,
themeColor: [
{ media: "(prefers-color-scheme: light)", color: "#ffffff" },
{ media: "(prefers-color-scheme: dark)", color: "#2b2f36" },
],
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en" suppressHydrationWarning>
<body className={`${GeistSans.variable} ${GeistMono.variable} antialiased bg-background text-foreground`}>
<Suspense fallback={<div>Loading...</div>}>
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem disableTransitionOnChange>
{children}
</ThemeProvider>
</Suspense>
</body>
</html>
)
}

98
AppImage/app/page.tsx Normal file
View File

@@ -0,0 +1,98 @@
"use client"
import { useState, useEffect } from "react"
import { ProxmoxDashboard } from "../components/proxmox-dashboard"
import { Login } from "../components/login"
import { AuthSetup } from "../components/auth-setup"
import { getApiUrl } from "../lib/api-config"
export default function Home() {
const [authStatus, setAuthStatus] = useState<{
loading: boolean
authEnabled: boolean
authConfigured: boolean
authenticated: boolean
}>({
loading: true,
authEnabled: false,
authConfigured: false,
authenticated: false,
})
useEffect(() => {
checkAuthStatus()
}, [])
const checkAuthStatus = async () => {
try {
const token = localStorage.getItem("proxmenux-auth-token")
const response = await fetch(getApiUrl("/api/auth/status"), {
headers: token ? { Authorization: `Bearer ${token}` } : {},
})
// Check if response is valid JSON before parsing
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
const contentType = response.headers.get("content-type")
if (!contentType || !contentType.includes("application/json")) {
throw new Error("Response is not JSON")
}
const data = await response.json()
const authenticated = data.auth_enabled ? data.authenticated : true
setAuthStatus({
loading: false,
authEnabled: data.auth_enabled,
authConfigured: data.auth_configured,
authenticated,
})
} catch {
// API not available - assume no auth configured (silent fail, no console error)
setAuthStatus({
loading: false,
authEnabled: false,
authConfigured: false,
authenticated: true,
})
}
}
const handleAuthComplete = () => {
checkAuthStatus()
}
const handleLoginSuccess = () => {
checkAuthStatus()
}
if (authStatus.loading) {
return (
<div className="min-h-screen bg-background flex items-center justify-center">
<div className="flex flex-col items-center gap-4">
<div className="relative">
<div className="h-12 w-12 rounded-full border-2 border-muted"></div>
<div className="absolute inset-0 h-12 w-12 rounded-full border-2 border-transparent border-t-primary animate-spin"></div>
</div>
<div className="text-sm font-medium text-foreground">Loading...</div>
<p className="text-xs text-muted-foreground">Connecting to ProxMenux Monitor</p>
</div>
</div>
)
}
if (authStatus.authEnabled && !authStatus.authenticated) {
return <Login onLogin={handleLoginSuccess} />
}
// Show dashboard in all other cases
return (
<>
{!authStatus.authConfigured && <AuthSetup onComplete={handleAuthComplete} />}
<ProxmoxDashboard />
</>
)
}

View File

@@ -0,0 +1,286 @@
"use client"
import { useState, useEffect } from "react"
import { Button } from "./ui/button"
import { Dialog, DialogContent, DialogTitle } from "./ui/dialog"
import { Input } from "./ui/input"
import { Label } from "./ui/label"
import { Shield, Lock, User, AlertCircle, Eye, EyeOff } from "lucide-react"
import { getApiUrl } from "../lib/api-config"
interface AuthSetupProps {
onComplete: () => void
}
export function AuthSetup({ onComplete }: AuthSetupProps) {
const [open, setOpen] = useState(false)
const [step, setStep] = useState<"choice" | "setup">("choice")
const [username, setUsername] = useState("")
const [password, setPassword] = useState("")
const [confirmPassword, setConfirmPassword] = useState("")
const [error, setError] = useState("")
const [loading, setLoading] = useState(false)
const [showPassword, setShowPassword] = useState(false)
const [showConfirmPassword, setShowConfirmPassword] = useState(false)
useEffect(() => {
const checkOnboardingStatus = async () => {
try {
const response = await fetch(getApiUrl("/api/auth/status"))
// Check if response is valid JSON before parsing
if (!response.ok) {
// API not available - don't show modal in preview
return
}
const contentType = response.headers.get("content-type")
if (!contentType || !contentType.includes("application/json")) {
return
}
const data = await response.json()
// Show modal if auth is not configured and not declined
if (!data.auth_configured) {
setTimeout(() => setOpen(true), 500)
}
} catch {
// API not available (preview environment) - don't show modal
}
}
checkOnboardingStatus()
}, [])
const handleSkipAuth = async () => {
setLoading(true)
setError("")
try {
console.log("[v0] Skipping authentication setup...")
const response = await fetch(getApiUrl("/api/auth/skip"), {
method: "POST",
headers: { "Content-Type": "application/json" },
})
const data = await response.json()
console.log("[v0] Auth skip response:", data)
if (!response.ok) {
throw new Error(data.error || "Failed to skip authentication")
}
if (data.auth_declined) {
console.log("[v0] Authentication skipped successfully - APIs should be accessible without token")
}
console.log("[v0] Authentication skipped successfully")
localStorage.setItem("proxmenux-auth-declined", "true")
localStorage.removeItem("proxmenux-auth-token") // Remove any old token
setOpen(false)
onComplete()
} catch (err) {
console.error("[v0] Auth skip error:", err)
setError(err instanceof Error ? err.message : "Failed to save preference")
} finally {
setLoading(false)
}
}
const handleSetupAuth = async () => {
setError("")
if (!username || !password) {
setError("Please fill in all fields")
return
}
if (password !== confirmPassword) {
setError("Passwords do not match")
return
}
if (password.length < 6) {
setError("Password must be at least 6 characters")
return
}
setLoading(true)
try {
console.log("[v0] Setting up authentication...")
const response = await fetch(getApiUrl("/api/auth/setup"), {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
username,
password,
}),
})
const data = await response.json()
console.log("[v0] Auth setup response:", data)
if (!response.ok) {
throw new Error(data.error || "Failed to setup authentication")
}
if (data.token) {
localStorage.setItem("proxmenux-auth-token", data.token)
localStorage.removeItem("proxmenux-auth-declined")
console.log("[v0] Authentication setup successful")
}
setOpen(false)
onComplete()
} catch (err) {
console.error("[v0] Auth setup error:", err)
setError(err instanceof Error ? err.message : "Failed to setup authentication")
} finally {
setLoading(false)
}
}
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="max-w-md max-h-[90vh] overflow-y-auto">
<DialogTitle className="sr-only">
{step === "choice" ? "Setup Dashboard Protection" : "Create Password"}
</DialogTitle>
{step === "choice" ? (
<div className="space-y-6 py-2">
<div className="text-center space-y-2">
<div className="mx-auto w-16 h-16 bg-blue-500/10 rounded-full flex items-center justify-center">
<Shield className="h-8 w-8 text-blue-500" />
</div>
<h2 className="text-2xl font-bold">Protect Your Dashboard?</h2>
<p className="text-muted-foreground text-sm">
Add an extra layer of security to protect your Proxmox data when accessing from non-private networks.
</p>
</div>
<div className="space-y-3">
<Button onClick={() => setStep("setup")} className="w-full bg-blue-500 hover:bg-blue-600" size="lg">
<Lock className="h-4 w-4 mr-2" />
Yes, Setup Password
</Button>
<Button
onClick={handleSkipAuth}
variant="outline"
className="w-full bg-transparent"
size="lg"
disabled={loading}
>
No, Continue Without Protection
</Button>
</div>
<p className="text-xs text-center text-muted-foreground">You can always enable this later in Settings</p>
</div>
) : (
<div className="space-y-6 py-2">
<div className="text-center space-y-2">
<div className="mx-auto w-16 h-16 bg-blue-500/10 rounded-full flex items-center justify-center">
<Lock className="h-8 w-8 text-blue-500" />
</div>
<h2 className="text-2xl font-bold">Setup Authentication</h2>
<p className="text-muted-foreground text-sm">Create a username and password to protect your dashboard</p>
</div>
{error && (
<div className="bg-red-500/10 border border-red-500/20 rounded-lg p-3 flex items-start gap-2">
<AlertCircle className="h-5 w-5 text-red-500 flex-shrink-0 mt-0.5" />
<p className="text-sm text-red-500">{error}</p>
</div>
)}
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="username" className="text-sm">
Username
</Label>
<div className="relative">
<User className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
id="username"
type="text"
placeholder="Enter username"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="pl-10 text-base"
disabled={loading}
autoComplete="username"
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="password" className="text-sm">
Password
</Label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
id="password"
type={showPassword ? "text" : "password"}
placeholder="Enter password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="pl-10 text-base"
disabled={loading}
autoComplete="new-password"
/>
<Button
variant="ghost"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2"
disabled={loading}
>
{showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</Button>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="confirm-password" className="text-sm">
Confirm Password
</Label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
id="confirm-password"
type={showConfirmPassword ? "text" : "password"}
placeholder="Confirm password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="pl-10 text-base"
disabled={loading}
autoComplete="new-password"
/>
<Button
variant="ghost"
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2"
disabled={loading}
>
{showConfirmPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</Button>
</div>
</div>
</div>
<div className="space-y-2">
<Button onClick={handleSetupAuth} className="w-full bg-blue-500 hover:bg-blue-600" disabled={loading}>
{loading ? "Setting up..." : "Setup Authentication"}
</Button>
<Button onClick={() => setStep("choice")} variant="ghost" className="w-full" disabled={loading}>
Back
</Button>
</div>
</div>
)}
</DialogContent>
</Dialog>
)
}

View File

@@ -0,0 +1,395 @@
"use client"
import { cn } from "@/lib/utils"
interface SriovInfo {
role: "vf" | "pf-active" | "pf-idle"
physfn?: string // VF only: parent PF BDF
vfCount?: number // PF only: active VF count
totalvfs?: number // PF only: maximum VFs
}
interface GpuSwitchModeIndicatorProps {
mode: "lxc" | "vm" | "sriov" | "unknown"
isEditing?: boolean
pendingMode?: "lxc" | "vm" | null
onToggle?: (e: React.MouseEvent) => void
className?: string
sriovInfo?: SriovInfo
}
export function GpuSwitchModeIndicator({
mode,
isEditing = false,
pendingMode = null,
onToggle,
className,
sriovInfo,
}: GpuSwitchModeIndicatorProps) {
// SR-IOV is a non-editable hardware state. Pending toggles don't apply here.
const displayMode = mode === "sriov" ? "sriov" : (pendingMode ?? mode)
const isLxcActive = displayMode === "lxc"
const isVmActive = displayMode === "vm"
const isSriovActive = displayMode === "sriov"
const hasChanged =
mode !== "sriov" && pendingMode !== null && pendingMode !== mode
// Colors
const sriovColor = "#14b8a6" // teal-500
const activeColor = isSriovActive
? sriovColor
: isLxcActive
? "#3b82f6"
: isVmActive
? "#a855f7"
: "#6b7280"
const inactiveColor = "#374151" // gray-700 for dark theme
const dimmedColor = "#4b5563" // gray-600 for dashed SR-IOV branches
const lxcColor = isLxcActive ? "#3b82f6" : inactiveColor
const vmColor = isVmActive ? "#a855f7" : inactiveColor
const handleClick = (e: React.MouseEvent) => {
// SR-IOV state can't be toggled — swallow the click so it doesn't reach
// the card (which would open the detail modal unexpectedly from this
// area). For lxc/vm, preserve the original behavior.
if (isSriovActive) {
e.stopPropagation()
return
}
if (isEditing) {
e.stopPropagation()
if (onToggle) {
onToggle(e)
}
}
// When not editing, let the click propagate to the card to open the modal
}
// Build the VF count label shown in the SR-IOV badge. For PFs we know
// exactly how many VFs are active; for a VF we show its parent PF.
const sriovBadgeText = (() => {
if (!isSriovActive) return ""
if (sriovInfo?.role === "vf") return "SR-IOV VF"
if (sriovInfo?.vfCount && sriovInfo.vfCount > 0) return `SR-IOV ×${sriovInfo.vfCount}`
return "SR-IOV"
})()
return (
<div
className={cn(
"flex items-center gap-6",
isEditing && !isSriovActive && "cursor-pointer",
className
)}
onClick={handleClick}
>
{/* Large SVG Diagram */}
<svg
viewBox="0 0 220 100"
className="h-24 w-56 flex-shrink-0"
xmlns="http://www.w3.org/2000/svg"
>
{/* GPU Chip - Large with "GPU" text */}
<g transform="translate(0, 22)">
{/* Main chip body */}
<rect
x="4"
y="8"
width="44"
height="36"
rx="6"
fill={`${activeColor}20`}
stroke={activeColor}
strokeWidth="2.5"
className="transition-all duration-300"
/>
{/* Chip pins - top */}
<line x1="14" y1="2" x2="14" y2="8" stroke={activeColor} strokeWidth="2.5" strokeLinecap="round" className="transition-all duration-300" />
<line x1="26" y1="2" x2="26" y2="8" stroke={activeColor} strokeWidth="2.5" strokeLinecap="round" className="transition-all duration-300" />
<line x1="38" y1="2" x2="38" y2="8" stroke={activeColor} strokeWidth="2.5" strokeLinecap="round" className="transition-all duration-300" />
{/* Chip pins - bottom */}
<line x1="14" y1="44" x2="14" y2="50" stroke={activeColor} strokeWidth="2.5" strokeLinecap="round" className="transition-all duration-300" />
<line x1="26" y1="44" x2="26" y2="50" stroke={activeColor} strokeWidth="2.5" strokeLinecap="round" className="transition-all duration-300" />
<line x1="38" y1="44" x2="38" y2="50" stroke={activeColor} strokeWidth="2.5" strokeLinecap="round" className="transition-all duration-300" />
{/* GPU text */}
<text
x="26"
y="32"
textAnchor="middle"
fill={activeColor}
className="text-[14px] font-bold transition-all duration-300"
style={{ fontFamily: 'system-ui, sans-serif' }}
>
GPU
</text>
</g>
{/* Connection line from GPU to switch */}
<line
x1="52"
y1="50"
x2="78"
y2="50"
stroke={activeColor}
strokeWidth="3"
strokeLinecap="round"
className="transition-all duration-300"
/>
{/* Central Switch Node - Large circle with inner dot */}
<circle
cx="95"
cy="50"
r="14"
fill={isEditing && !isSriovActive ? "#f59e0b20" : `${activeColor}20`}
stroke={isEditing && !isSriovActive ? "#f59e0b" : activeColor}
strokeWidth="3"
className="transition-all duration-300"
/>
<circle
cx="95"
cy="50"
r="6"
fill={isEditing && !isSriovActive ? "#f59e0b" : activeColor}
className="transition-all duration-300"
/>
{/* LXC Branch Line - going up-right.
In SR-IOV mode the branch is dashed + dimmed to show that the
target is theoretically reachable via a VF but not controlled
by ProxMenux. */}
<path
d="M 109 42 L 135 20"
fill="none"
stroke={isSriovActive ? dimmedColor : lxcColor}
strokeWidth={isLxcActive ? "3.5" : "2"}
strokeLinecap="round"
strokeDasharray={isSriovActive ? "3 3" : undefined}
className="transition-all duration-300"
/>
{/* VM Branch Line - going down-right (dashed/dimmed in SR-IOV). */}
<path
d="M 109 58 L 135 80"
fill="none"
stroke={isSriovActive ? dimmedColor : vmColor}
strokeWidth={isVmActive ? "3.5" : "2"}
strokeLinecap="round"
strokeDasharray={isSriovActive ? "3 3" : undefined}
className="transition-all duration-300"
/>
{/* SR-IOV in-line connector + badge (only when mode === 'sriov').
A horizontal line from the switch node leads to a pill-shaped
badge carrying the "SR-IOV ×N" label. Placed on the GPU's
baseline to visually read as an in-line extension, not as a
third branch. */}
{isSriovActive && (
<>
<line
x1="109"
y1="50"
x2="130"
y2="50"
stroke={sriovColor}
strokeWidth="3"
strokeLinecap="round"
className="transition-all duration-300"
/>
<rect
x="132"
y="40"
width="60"
height="20"
rx="10"
fill={`${sriovColor}25`}
stroke={sriovColor}
strokeWidth="2"
className="transition-all duration-300"
/>
<text
x="162"
y="54"
textAnchor="middle"
fill={sriovColor}
className="text-[11px] font-bold transition-all duration-300"
style={{ fontFamily: 'system-ui, sans-serif' }}
>
{sriovBadgeText}
</text>
</>
)}
{/* LXC Container Icon - dimmed/smaller in SR-IOV mode. */}
{!isSriovActive && (
<g transform="translate(138, 2)">
<rect
x="0"
y="0"
width="32"
height="28"
rx="4"
fill={isLxcActive ? `${lxcColor}25` : "transparent"}
stroke={lxcColor}
strokeWidth={isLxcActive ? "2.5" : "1.5"}
className="transition-all duration-300"
/>
<line x1="0" y1="10" x2="32" y2="10" stroke={lxcColor} strokeWidth={isLxcActive ? "1.5" : "1"} className="transition-all duration-300" />
<line x1="0" y1="19" x2="32" y2="19" stroke={lxcColor} strokeWidth={isLxcActive ? "1.5" : "1"} className="transition-all duration-300" />
<circle cx="7" cy="5" r="2" fill={lxcColor} className="transition-all duration-300" />
<circle cx="7" cy="14.5" r="2" fill={lxcColor} className="transition-all duration-300" />
<circle cx="7" cy="23.5" r="2" fill={lxcColor} className="transition-all duration-300" />
</g>
)}
{/* SR-IOV: compact dimmed LXC glyph so the geometry stays recognizable
but it's clearly not the active target. */}
{isSriovActive && (
<g transform="translate(138, 6)" opacity="0.35">
<rect x="0" y="0" width="20" height="18" rx="3" fill="transparent" stroke={dimmedColor} strokeWidth="1.5" />
<line x1="0" y1="6" x2="20" y2="6" stroke={dimmedColor} strokeWidth="1" />
<line x1="0" y1="12" x2="20" y2="12" stroke={dimmedColor} strokeWidth="1" />
</g>
)}
{/* LXC Label */}
{!isSriovActive && (
<text
x="188"
y="22"
textAnchor="start"
fill={lxcColor}
className={cn(
"transition-all duration-300",
isLxcActive ? "text-[14px] font-bold" : "text-[12px] font-medium"
)}
style={{ fontFamily: 'system-ui, sans-serif' }}
>
LXC
</text>
)}
{isSriovActive && (
<text
x="162"
y="16"
fill={dimmedColor}
className="text-[9px] font-medium"
style={{ fontFamily: 'system-ui, sans-serif' }}
>
LXC
</text>
)}
{/* VM Monitor Icon - active view */}
{!isSriovActive && (
<g transform="translate(138, 65)">
<rect
x="2"
y="0"
width="28"
height="18"
rx="3"
fill={isVmActive ? `${vmColor}25` : "transparent"}
stroke={vmColor}
strokeWidth={isVmActive ? "2.5" : "1.5"}
className="transition-all duration-300"
/>
<rect
x="5"
y="3"
width="22"
height="12"
rx="1"
fill={isVmActive ? `${vmColor}30` : `${vmColor}10`}
className="transition-all duration-300"
/>
<line x1="16" y1="18" x2="16" y2="24" stroke={vmColor} strokeWidth={isVmActive ? "2.5" : "1.5"} strokeLinecap="round" className="transition-all duration-300" />
<line x1="8" y1="24" x2="24" y2="24" stroke={vmColor} strokeWidth={isVmActive ? "2.5" : "1.5"} strokeLinecap="round" className="transition-all duration-300" />
</g>
)}
{/* SR-IOV: compact dimmed VM monitor glyph, mirror of the LXC glyph. */}
{isSriovActive && (
<g transform="translate(138, 72)" opacity="0.35">
<rect x="0" y="0" width="20" height="13" rx="2" fill="transparent" stroke={dimmedColor} strokeWidth="1.5" />
<line x1="10" y1="13" x2="10" y2="17" stroke={dimmedColor} strokeWidth="1.5" strokeLinecap="round" />
<line x1="5" y1="17" x2="15" y2="17" stroke={dimmedColor} strokeWidth="1.5" strokeLinecap="round" />
</g>
)}
{/* VM Label */}
{!isSriovActive && (
<text
x="188"
y="84"
textAnchor="start"
fill={vmColor}
className={cn(
"transition-all duration-300",
isVmActive ? "text-[14px] font-bold" : "text-[12px] font-medium"
)}
style={{ fontFamily: 'system-ui, sans-serif' }}
>
VM
</text>
)}
{isSriovActive && (
<text
x="162"
y="82"
fill={dimmedColor}
className="text-[9px] font-medium"
style={{ fontFamily: 'system-ui, sans-serif' }}
>
VM
</text>
)}
</svg>
{/* Status Text - Large like GPU name */}
<div className="flex flex-col gap-1 min-w-0 flex-1">
<span
className={cn(
"text-base font-semibold transition-all duration-300",
isSriovActive
? "text-teal-500"
: isLxcActive
? "text-blue-500"
: isVmActive
? "text-purple-500"
: "text-muted-foreground"
)}
>
{isSriovActive
? "SR-IOV active"
: isLxcActive
? "Ready for LXC containers"
: isVmActive
? "Ready for VM passthrough"
: "Mode unknown"}
</span>
<span className="text-sm text-muted-foreground">
{isSriovActive
? "Virtual Functions managed externally"
: isLxcActive
? "Native driver active"
: isVmActive
? "VFIO-PCI driver active"
: "No driver detected"}
</span>
{isSriovActive && sriovInfo && (
<span className="text-xs font-mono text-teal-600/80 dark:text-teal-400/80">
{sriovInfo.role === "vf"
? `Virtual Function${sriovInfo.physfn ? ` · parent PF ${sriovInfo.physfn}` : ""}`
: sriovInfo.vfCount !== undefined
? `1 PF + ${sriovInfo.vfCount} VF${sriovInfo.vfCount === 1 ? "" : "s"}${sriovInfo.totalvfs ? ` / ${sriovInfo.totalvfs} max` : ""}`
: null}
</span>
)}
{hasChanged && (
<span className="text-sm text-amber-500 font-medium animate-pulse">
Change pending...
</span>
)}
</div>
</div>
)
}

View File

@@ -0,0 +1,114 @@
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card"
import { Cpu } from "@/components/icons/cpu" // Added import for Cpu
import type { PCIDevice } from "../types/hardware" // Fixed import to use relative path instead of alias
import { Progress } from "@/components/ui/progress"
function GPUCard({ device }: { device: PCIDevice }) {
const hasMonitoring = device.gpu_temperature !== undefined || device.gpu_utilization !== undefined
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Cpu className="h-5 w-5" />
{device.device}
</CardTitle>
<CardDescription>{device.vendor}</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<div className="text-muted-foreground">Slot</div>
<div className="font-medium">{device.slot}</div>
</div>
{device.driver && (
<div>
<div className="text-muted-foreground">Driver</div>
<div className="font-medium">{device.driver}</div>
</div>
)}
{device.gpu_driver_version && (
<div>
<div className="text-muted-foreground">Driver Version</div>
<div className="font-medium">{device.gpu_driver_version}</div>
</div>
)}
{device.gpu_memory && (
<div>
<div className="text-muted-foreground">Memory</div>
<div className="font-medium">{device.gpu_memory}</div>
</div>
)}
{device.gpu_compute_capability && (
<div>
<div className="text-muted-foreground">Compute Capability</div>
<div className="font-medium">{device.gpu_compute_capability}</div>
</div>
)}
</div>
{hasMonitoring && (
<div className="space-y-3 pt-4 border-t">
<h4 className="text-sm font-semibold">Real-time Monitoring</h4>
{device.gpu_temperature !== undefined && (
<div className="space-y-1">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Temperature</span>
<span className="font-medium">{device.gpu_temperature}°C</span>
</div>
<Progress value={(device.gpu_temperature / 100) * 100} className="h-2" />
</div>
)}
{device.gpu_utilization !== undefined && (
<div className="space-y-1">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">GPU Utilization</span>
<span className="font-medium">{device.gpu_utilization}%</span>
</div>
<Progress value={device.gpu_utilization} className="h-2" />
</div>
)}
{device.gpu_memory_used && device.gpu_memory_total && (
<div className="space-y-1">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Memory Usage</span>
<span className="font-medium">
{device.gpu_memory_used} / {device.gpu_memory_total}
</span>
</div>
<Progress
value={(Number.parseInt(device.gpu_memory_used) / Number.parseInt(device.gpu_memory_total)) * 100}
className="h-2"
/>
</div>
)}
{device.gpu_power_draw && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Power Draw</span>
<span className="font-medium">{device.gpu_power_draw}</span>
</div>
)}
{device.gpu_clock_speed && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">GPU Clock</span>
<span className="font-medium">{device.gpu_clock_speed}</span>
</div>
)}
{device.gpu_memory_clock && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Memory Clock</span>
<span className="font-medium">{device.gpu_memory_clock}</span>
</div>
)}
</div>
)}
</CardContent>
</Card>
)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,842 @@
"use client"
import type React from "react"
import { useState, useEffect, useCallback } from "react"
import { getAuthToken } from "@/lib/api-config"
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {
Loader2,
CheckCircle2,
AlertTriangle,
XCircle,
Info,
Activity,
Cpu,
MemoryStick,
HardDrive,
Disc,
Network,
Box,
Settings,
FileText,
RefreshCw,
Shield,
X,
Clock,
BellOff,
ChevronRight,
Settings2,
HelpCircle,
} from "lucide-react"
interface CategoryCheck {
status: string
reason?: string
details?: any
checks?: Record<string, { status: string; detail: string; [key: string]: any }>
dismissable?: boolean
[key: string]: any
}
interface DismissedError {
error_key: string
category: string
severity: string
reason: string
dismissed: boolean
permanent?: boolean
suppression_remaining_hours: number
suppression_hours?: number
resolved_at: string
}
interface CustomSuppression {
key: string
label: string
category: string
icon: string
hours: number
}
interface HealthDetails {
overall: string
summary: string
details: {
cpu: CategoryCheck
memory: CategoryCheck
storage: CategoryCheck
disks: CategoryCheck
network: CategoryCheck
vms: CategoryCheck
services: CategoryCheck
logs: CategoryCheck
updates: CategoryCheck
security: CategoryCheck
}
timestamp: string
}
interface FullHealthData {
health: HealthDetails
active_errors: any[]
dismissed: DismissedError[]
custom_suppressions: CustomSuppression[]
timestamp: string
}
interface HealthStatusModalProps {
open: boolean
onOpenChange: (open: boolean) => void
getApiUrl: (path: string) => string
}
const CATEGORIES = [
{ key: "cpu", category: "temperature", label: "CPU Usage & Temperature", Icon: Cpu },
{ key: "memory", category: "memory", label: "Memory & Swap", Icon: MemoryStick },
{ key: "storage", category: "storage", label: "Storage Mounts & Space", Icon: HardDrive },
{ key: "disks", category: "disks", label: "Disk I/O & Errors", Icon: Disc },
{ key: "network", category: "network", label: "Network Interfaces", Icon: Network },
{ key: "vms", category: "vms", label: "VMs & Containers", Icon: Box },
{ key: "services", category: "pve_services", label: "PVE Services", Icon: Settings },
{ key: "logs", category: "logs", label: "System Logs", Icon: FileText },
{ key: "updates", category: "updates", label: "System Updates", Icon: RefreshCw },
{ key: "security", category: "security", label: "Security & Certificates", Icon: Shield },
]
export function HealthStatusModal({ open, onOpenChange, getApiUrl }: HealthStatusModalProps) {
const [loading, setLoading] = useState(true)
const [healthData, setHealthData] = useState<HealthDetails | null>(null)
const [dismissedItems, setDismissedItems] = useState<DismissedError[]>([])
const [customSuppressions, setCustomSuppressions] = useState<CustomSuppression[]>([])
const [error, setError] = useState<string | null>(null)
const [dismissingKey, setDismissingKey] = useState<string | null>(null)
const [expandedCategories, setExpandedCategories] = useState<Set<string>>(new Set())
const fetchHealthDetails = useCallback(async () => {
setLoading(true)
setError(null)
try {
let newOverallStatus = "OK"
// Use the new combined endpoint for fewer round-trips
const token = getAuthToken()
const authHeaders: Record<string, string> = {}
if (token) {
authHeaders["Authorization"] = `Bearer ${token}`
}
const response = await fetch(getApiUrl("/api/health/full"), { headers: authHeaders })
let infoCount = 0
if (!response.ok) {
// Fallback to legacy endpoint
const legacyResponse = await fetch(getApiUrl("/api/health/details"), { headers: authHeaders })
if (!legacyResponse.ok) throw new Error("Failed to fetch health details")
const data = await legacyResponse.json()
setHealthData(data)
setDismissedItems([])
setCustomSuppressions([])
newOverallStatus = data?.overall || "OK"
// Count INFO categories from legacy data
if (data?.details) {
CATEGORIES.forEach(({ key }) => {
const cat = data.details[key as keyof typeof data.details]
if (cat && cat.status?.toUpperCase() === "INFO") {
infoCount++
}
})
}
} else {
const fullData: FullHealthData = await response.json()
setHealthData(fullData.health)
setDismissedItems(fullData.dismissed || [])
setCustomSuppressions(fullData.custom_suppressions || [])
newOverallStatus = fullData.health?.overall || "OK"
// Get categories that have dismissed items (these become INFO)
const customCats = new Set((fullData.custom_suppressions || []).map((cs: { category: string }) => cs.category))
const filteredDismissed = (fullData.dismissed || []).filter((item: { category: string }) => !customCats.has(item.category))
const categoriesWithDismissed = new Set<string>()
filteredDismissed.forEach((item: { category: string }) => {
const catMeta = CATEGORIES.find(c => c.category === item.category || c.key === item.category)
if (catMeta) {
categoriesWithDismissed.add(catMeta.key)
}
})
// Count effective INFO categories (original INFO + OK categories with dismissed)
if (fullData.health?.details) {
CATEGORIES.forEach(({ key }) => {
const cat = fullData.health.details[key as keyof typeof fullData.health.details]
if (cat) {
const originalStatus = cat.status?.toUpperCase()
// Count as INFO if: originally INFO OR (originally OK and has dismissed items)
if (originalStatus === "INFO" || (originalStatus === "OK" && categoriesWithDismissed.has(key))) {
infoCount++
}
}
})
}
}
const totalInfoCount = infoCount
// Emit event with the FRESH data from the response, not the stale state
const event = new CustomEvent("healthStatusUpdated", {
detail: { status: newOverallStatus, infoCount: totalInfoCount },
})
window.dispatchEvent(event)
} catch (err) {
setError(err instanceof Error ? err.message : "Unknown error")
} finally {
setLoading(false)
}
}, [getApiUrl])
// Tick counter to force re-render every 30s so "X minutes ago" stays current
const [, setTick] = useState(0)
useEffect(() => {
if (!open) return
const tickInterval = setInterval(() => setTick(t => t + 1), 30000)
return () => clearInterval(tickInterval)
}, [open])
useEffect(() => {
if (open) {
fetchHealthDetails()
// Auto-refresh every 5 minutes while modal is open
const refreshInterval = setInterval(fetchHealthDetails, 300000)
return () => clearInterval(refreshInterval)
}
}, [open, fetchHealthDetails])
// Auto-expand non-OK categories when data loads
useEffect(() => {
if (healthData?.details) {
const nonOkCategories = new Set<string>()
CATEGORIES.forEach(({ key }) => {
const cat = healthData.details[key as keyof typeof healthData.details]
if (cat && cat.status?.toUpperCase() !== "OK") {
// Updates section: only auto-expand on WARNING+, not INFO
if (key === "updates" && cat.status?.toUpperCase() === "INFO") {
return
}
nonOkCategories.add(key)
}
})
setExpandedCategories(nonOkCategories)
}
}, [healthData])
const toggleCategory = (key: string) => {
setExpandedCategories(prev => {
const next = new Set(prev)
if (next.has(key)) {
next.delete(key)
} else {
next.add(key)
}
return next
})
}
const getStatusIcon = (status: string, size: "sm" | "md" = "md") => {
const statusUpper = status?.toUpperCase()
const cls = size === "sm" ? "h-4 w-4" : "h-5 w-5"
switch (statusUpper) {
case "OK":
return <CheckCircle2 className={`${cls} text-green-500`} />
case "INFO":
return <Info className={`${cls} text-blue-500`} />
case "WARNING":
return <AlertTriangle className={`${cls} text-yellow-500`} />
case "CRITICAL":
return <XCircle className={`${cls} text-red-500`} />
case "UNKNOWN":
return <HelpCircle className={`${cls} text-amber-400`} />
default:
return <Activity className={`${cls} text-muted-foreground`} />
}
}
const getStatusBadge = (status: string) => {
const statusUpper = status?.toUpperCase()
switch (statusUpper) {
case "OK":
return <Badge className="bg-green-500 text-white hover:bg-green-500">OK</Badge>
case "INFO":
return <Badge className="bg-blue-500 text-white hover:bg-blue-500">Info</Badge>
case "WARNING":
return <Badge className="bg-yellow-500 text-white hover:bg-yellow-500">Warning</Badge>
case "CRITICAL":
return <Badge className="bg-red-500 text-white hover:bg-red-500">Critical</Badge>
case "UNKNOWN":
return <Badge className="bg-amber-500 text-white hover:bg-amber-500">UNKNOWN</Badge>
default:
return <Badge>Unknown</Badge>
}
}
// Get categories that have dismissed items (to show as INFO)
const getCategoriesWithDismissed = () => {
const customCats = new Set(customSuppressions.map(cs => cs.category))
const filteredDismissed = dismissedItems.filter(item => !customCats.has(item.category))
const categoriesWithDismissed = new Set<string>()
filteredDismissed.forEach(item => {
// Map dismissed category to our CATEGORIES keys
const catMeta = CATEGORIES.find(c => c.category === item.category || c.key === item.category)
if (catMeta) {
categoriesWithDismissed.add(catMeta.key)
}
})
return categoriesWithDismissed
}
const categoriesWithDismissed = getCategoriesWithDismissed()
// Get effective status for a category (considers dismissed items)
const getEffectiveStatus = (key: string, originalStatus: string) => {
// If category has dismissed items and original status is OK, show as INFO
if (categoriesWithDismissed.has(key) && originalStatus?.toUpperCase() === "OK") {
return "INFO"
}
return originalStatus?.toUpperCase() || "UNKNOWN"
}
const getHealthStats = () => {
if (!healthData?.details) return { total: 0, healthy: 0, info: 0, warnings: 0, critical: 0, unknown: 0 }
let healthy = 0
let info = 0
let warnings = 0
let critical = 0
let unknown = 0
CATEGORIES.forEach(({ key }) => {
const categoryData = healthData.details[key as keyof typeof healthData.details]
if (categoryData) {
const effectiveStatus = getEffectiveStatus(key, categoryData.status)
if (effectiveStatus === "OK") healthy++
else if (effectiveStatus === "INFO") info++
else if (effectiveStatus === "WARNING") warnings++
else if (effectiveStatus === "CRITICAL") critical++
else if (effectiveStatus === "UNKNOWN") unknown++
}
})
return { total: CATEGORIES.length, healthy, info, warnings, critical, unknown }
}
const stats = getHealthStats()
const handleCategoryClick = (categoryKey: string, status: string) => {
if (status === "OK" || status === "INFO") return
onOpenChange(false)
const categoryToTab: Record<string, string> = {
storage: "storage",
disks: "storage",
network: "network",
vms: "vms",
logs: "logs",
hardware: "hardware",
services: "hardware",
}
const targetTab = categoryToTab[categoryKey]
if (targetTab) {
const event = new CustomEvent("changeTab", { detail: { tab: targetTab } })
window.dispatchEvent(event)
}
}
const handleAcknowledge = async (errorKey: string, e: React.MouseEvent) => {
e.stopPropagation()
setDismissingKey(errorKey)
try {
const url = getApiUrl("/api/health/acknowledge")
const token = getAuthToken()
const headers: Record<string, string> = { "Content-Type": "application/json" }
if (token) {
headers["Authorization"] = `Bearer ${token}`
}
const response = await fetch(url, {
method: "POST",
headers,
body: JSON.stringify({ error_key: errorKey }),
})
const responseData = await response.json().catch(() => ({}))
if (!response.ok) {
throw new Error(responseData.error || `Failed to dismiss error (${response.status})`)
}
// Optimistically update local state to avoid slow re-fetch
// Add the dismissed item to the local list immediately
if (responseData.result || responseData.success) {
const dismissedItem = {
error_key: errorKey,
category: responseData.result?.category || responseData.category || '',
severity: responseData.result?.original_severity || 'WARNING',
reason: 'Dismissed by user',
dismissed: true,
acknowledged_at: new Date().toISOString()
}
setDismissedItems(prev => [...prev, dismissedItem])
}
// Fetch fresh data in background (non-blocking)
fetchHealthDetails().catch(() => {})
} catch (err) {
console.error("Error dismissing:", err)
} finally {
setDismissingKey(null)
}
}
const getTimeSinceCheck = () => {
if (!healthData?.timestamp) return null
const checkTime = new Date(healthData.timestamp)
const now = new Date()
const diffMs = now.getTime() - checkTime.getTime()
const diffMin = Math.floor(diffMs / 60000)
if (diffMin < 1) return "just now"
if (diffMin === 1) return "1 minute ago"
if (diffMin < 60) return `${diffMin} minutes ago`
const diffHours = Math.floor(diffMin / 60)
return `${diffHours}h ${diffMin % 60}m ago`
}
const getCategoryRowStyle = (status: string) => {
const s = status?.toUpperCase()
if (s === "CRITICAL") return "bg-red-500/5 border-red-500/20 hover:bg-red-500/10 cursor-pointer"
if (s === "WARNING") return "bg-yellow-500/5 border-yellow-500/20 hover:bg-yellow-500/10 cursor-pointer"
if (s === "UNKNOWN") return "bg-amber-500/5 border-amber-500/20 hover:bg-amber-500/10 cursor-pointer"
if (s === "INFO") return "bg-blue-500/5 border-blue-500/20 hover:bg-blue-500/10"
return "bg-card border-border hover:bg-muted/30"
}
const getOutlineBadgeStyle = (status: string) => {
const s = status?.toUpperCase()
if (s === "OK") return "border-green-500 text-green-500 bg-transparent"
if (s === "INFO") return "border-blue-500 text-blue-500 bg-blue-500/5"
if (s === "WARNING") return "border-yellow-500 text-yellow-500 bg-yellow-500/5"
if (s === "CRITICAL") return "border-red-500 text-red-500 bg-red-500/5"
if (s === "UNKNOWN") return "border-amber-400 text-amber-400 bg-amber-500/5"
return ""
}
const formatCheckLabel = (key: string): string => {
const labels: Record<string, string> = {
// CPU
cpu_usage: "CPU Usage",
cpu_temperature: "Temperature",
// Memory
ram_usage: "RAM Usage",
swap_usage: "Swap Usage",
// Disk I/O
root_filesystem: "Root Filesystem",
smart_health: "SMART Health",
io_errors: "I/O Errors",
zfs_pools: "ZFS Pools",
lvm_volumes: "LVM Volumes",
lvm_check: "LVM Status",
// Network
connectivity: "Connectivity",
// VMs & CTs
qmp_communication: "QMP Communication",
container_startup: "Container Startup",
vm_startup: "VM Startup",
oom_killer: "OOM Killer",
// Services
cluster_mode: "Cluster Mode",
// Logs (prefixed with log_)
log_error_cascade: "Error Cascade",
log_error_spike: "Error Spike",
log_persistent_errors: "Persistent Errors",
log_critical_errors: "Critical Errors",
// Updates
pve_version: "Proxmox VE Version",
security_updates: "Security Updates",
system_age: "System Age",
pending_updates: "Pending Updates",
kernel_pve: "Kernel / PVE",
// Security
uptime: "Uptime",
certificates: "Certificates",
login_attempts: "Login Attempts",
fail2ban: "Fail2Ban",
// Storage (Proxmox)
proxmox_storages: "Proxmox Storages",
}
if (labels[key]) return labels[key]
// Convert snake_case or camelCase to Title Case
return key
.replace(/_/g, " ")
.replace(/([a-z])([A-Z])/g, "$1 $2")
.replace(/\b\w/g, (c) => c.toUpperCase())
}
const renderChecks = (
checks: Record<string, { status: string; detail: string; dismissable?: boolean; [key: string]: any }>,
categoryKey: string
) => {
if (!checks || Object.keys(checks).length === 0) return null
return (
<div className="mt-2 space-y-0.5">
{Object.entries(checks)
.filter(([, checkData]) => checkData.installed !== false)
.map(([checkKey, checkData]) => {
const isDismissable = checkData.dismissable === true
const checkStatus = checkData.status?.toUpperCase() || "OK"
return (
<div
key={checkKey}
className="flex items-center justify-between gap-1.5 sm:gap-2 text-[10px] sm:text-xs py-1.5 px-2 sm:px-3 rounded-md hover:bg-muted/40 transition-colors"
>
<div className="flex items-start gap-1.5 sm:gap-2 min-w-0 flex-1">
<span className="mt-0.5 shrink-0">{getStatusIcon(checkData.dismissed ? "INFO" : checkData.status, "sm")}</span>
<span className="font-medium shrink-0">{formatCheckLabel(checkKey)}</span>
<span className="text-muted-foreground break-words whitespace-pre-wrap min-w-0">{checkData.detail}</span>
{checkData.dismissed && (
<Badge variant="outline" className="text-[9px] px-1 py-0 h-4 shrink-0 text-blue-400 border-blue-400/30">
Dismissed
</Badge>
)}
</div>
<div className="flex items-center gap-1 sm:gap-1.5 shrink-0">
{(checkStatus === "WARNING" || checkStatus === "CRITICAL" || checkStatus === "UNKNOWN") && isDismissable && !checkData.dismissed && (
<Button
size="sm"
variant="outline"
className="h-5 px-1 sm:px-1.5 shrink-0 hover:bg-red-500/10 hover:border-red-500/50 bg-transparent text-[10px]"
disabled={dismissingKey === (checkData.error_key || checkKey)}
onClick={(e) => {
e.stopPropagation()
handleAcknowledge(checkData.error_key || checkKey, e)
}}
>
{dismissingKey === (checkData.error_key || checkKey) ? (
<Loader2 className="h-3 w-3 animate-spin" />
) : (
<>
<X className="h-3 w-3 sm:mr-0.5" />
<span className="hidden sm:inline">Dismiss</span>
</>
)}
</Button>
)}
</div>
</div>
)
})}
</div>
)
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-3xl w-[calc(100vw-2rem)] sm:w-[95vw] max-h-[85vh] overflow-y-auto overflow-x-hidden p-4 sm:p-6">
<DialogHeader>
<div className="flex items-center justify-between gap-3">
<DialogTitle className="flex items-center gap-2 flex-1 min-w-0">
<Activity className="h-5 w-5 sm:h-6 sm:w-6 shrink-0" />
<span className="truncate text-base sm:text-lg">System Health Status</span>
{healthData && <div className="shrink-0">{getStatusBadge(healthData.overall)}</div>}
</DialogTitle>
</div>
<DialogDescription className="flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs sm:text-sm">
<span>Detailed health checks for all system components</span>
{getTimeSinceCheck() && (
<span className="inline-flex items-center gap-1 text-xs text-muted-foreground">
<Clock className="h-3 w-3" />
{getTimeSinceCheck()}
</span>
)}
</DialogDescription>
</DialogHeader>
{loading && (
<div className="flex items-center justify-center py-8">
<Loader2 className="h-8 w-8 animate-spin text-primary" />
</div>
)}
{error && (
<div className="rounded-lg border border-red-200 bg-red-50 p-4 text-red-800 dark:bg-red-950 dark:border-red-800 dark:text-red-200">
<p className="font-medium">Error loading health status</p>
<p className="text-sm">{error}</p>
</div>
)}
{healthData && !loading && (
<div className="space-y-4">
{/* Overall Stats Summary */}
<div className={`grid gap-2 sm:gap-3 p-3 sm:p-4 rounded-lg bg-muted/30 border ${stats.info > 0 ? "grid-cols-5" : "grid-cols-4"}`}>
<div className="text-center">
<div className="text-lg sm:text-2xl font-bold">{stats.total}</div>
<div className="text-[10px] sm:text-xs text-muted-foreground">Total</div>
</div>
<div className="text-center">
<div className="text-lg sm:text-2xl font-bold text-green-500">{stats.healthy}</div>
<div className="text-[10px] sm:text-xs text-muted-foreground">Healthy</div>
</div>
{stats.info > 0 && (
<div className="text-center">
<div className="text-lg sm:text-2xl font-bold text-blue-500">{stats.info}</div>
<div className="text-[10px] sm:text-xs text-muted-foreground">Info</div>
</div>
)}
<div className="text-center">
<div className="text-lg sm:text-2xl font-bold text-yellow-500">{stats.warnings}</div>
<div className="text-[10px] sm:text-xs text-muted-foreground">Warn</div>
</div>
<div className="text-center">
<div className="text-lg sm:text-2xl font-bold text-red-500">{stats.critical}</div>
<div className="text-[10px] sm:text-xs text-muted-foreground">Critical</div>
</div>
{stats.unknown > 0 && (
<div className="text-center">
<div className="text-lg sm:text-2xl font-bold text-amber-400">{stats.unknown}</div>
<div className="text-[10px] sm:text-xs text-muted-foreground">Unknown</div>
</div>
)}
</div>
{healthData.summary && healthData.summary !== "All systems operational" && (
<div className="text-xs sm:text-sm p-3 rounded-lg bg-muted/20 border overflow-hidden max-w-full">
<p className="font-medium text-foreground break-words whitespace-pre-wrap">{healthData.summary}</p>
</div>
)}
{/* Category List */}
<div className="space-y-2">
{CATEGORIES.map(({ key, label, Icon }) => {
const categoryData = healthData.details[key as keyof typeof healthData.details]
const originalStatus = categoryData?.status || "UNKNOWN"
const status = getEffectiveStatus(key, originalStatus)
const reason = categoryData?.reason
const checks = categoryData?.checks
const isExpanded = expandedCategories.has(key)
const hasChecks = checks && Object.keys(checks).length > 0
return (
<div
key={key}
className={`rounded-lg border transition-colors overflow-hidden ${getCategoryRowStyle(status)}`}
>
{/* Clickable header row */}
<div
className="flex items-center gap-2 sm:gap-3 p-2 sm:p-3 cursor-pointer select-none overflow-hidden"
onClick={() => toggleCategory(key)}
>
<div className="shrink-0 flex items-center gap-1.5 sm:gap-2">
<Icon className="h-4 w-4 text-blue-500 hidden sm:block" />
{getStatusIcon(status)}
</div>
<div className="flex-1 min-w-0 overflow-hidden">
<div className="flex items-center gap-1.5 sm:gap-2">
<p className="font-medium text-xs sm:text-sm truncate">{label}</p>
{hasChecks && (
<span className="text-[10px] text-muted-foreground shrink-0">
({Object.values(checks).filter(c => c.installed !== false).length})
</span>
)}
</div>
{reason && !isExpanded && (
<p className="text-[10px] sm:text-xs text-muted-foreground mt-0.5 line-clamp-2 break-words">{reason}</p>
)}
</div>
<div className="flex items-center gap-1 sm:gap-2 shrink-0">
<Badge variant="outline" className={`text-[10px] sm:text-xs px-1.5 sm:px-2.5 ${getOutlineBadgeStyle(status)}`}>
{status}
</Badge>
<ChevronRight
className={`h-3.5 w-3.5 sm:h-4 sm:w-4 text-muted-foreground transition-transform duration-200 ${
isExpanded ? "rotate-90" : ""
}`}
/>
</div>
</div>
{/* Expandable checks section */}
{isExpanded && (
<div className="border-t border-border/50 bg-muted/5 px-1.5 sm:px-2 py-1.5 overflow-hidden">
{reason && (
<div className="flex items-center justify-between gap-2 px-3 py-1.5 mb-1">
<p className="text-xs text-muted-foreground break-words whitespace-pre-wrap flex-1">{reason}</p>
{/* Show dismiss button for UNKNOWN status at category level when dismissable */}
{status === "UNKNOWN" && categoryData?.dismissable && !hasChecks && (
<Button
size="sm"
variant="outline"
className="h-5 px-1.5 shrink-0 hover:bg-red-500/10 hover:border-red-500/50 bg-transparent text-[10px]"
disabled={dismissingKey === `category_${key}`}
onClick={(e) => {
e.stopPropagation()
handleAcknowledge(`category_${key}_unknown`, e)
}}
>
{dismissingKey === `category_${key}` ? (
<Loader2 className="h-3 w-3 animate-spin" />
) : (
<>
<X className="h-3 w-3 sm:mr-0.5" />
<span className="hidden sm:inline">Dismiss</span>
</>
)}
</Button>
)}
</div>
)}
{hasChecks ? (
renderChecks(checks, key)
) : (
<div className="flex items-center gap-2 text-xs text-muted-foreground px-3 py-2">
<CheckCircle2 className="h-3.5 w-3.5 text-green-500" />
No issues detected
</div>
)}
</div>
)}
</div>
)
})}
</div>
{/* Dismissed Items Section -- hide items whose category has custom suppression */}
{(() => {
const customCats = new Set(customSuppressions.map(cs => cs.category))
const filteredDismissed = dismissedItems.filter(item => !customCats.has(item.category))
if (filteredDismissed.length === 0) return null
return (
<div className="space-y-2">
<div className="flex items-center gap-2 text-xs sm:text-sm font-medium text-muted-foreground pt-2">
<BellOff className="h-3.5 w-3.5 sm:h-4 sm:w-4" />
Dismissed Items ({filteredDismissed.length})
</div>
{filteredDismissed.map((item) => {
const catMeta = CATEGORIES.find(c => c.category === item.category || c.key === item.category)
const CatIcon = catMeta?.Icon || BellOff
const catLabel = catMeta?.label || item.category
const isPermanent = item.permanent || item.suppression_remaining_hours === -1
return (
<div
key={item.error_key}
className="flex items-start gap-2 sm:gap-3 p-2 sm:p-3 rounded-lg border bg-muted/10 border-muted opacity-75"
>
<div className="mt-0.5 shrink-0 flex items-center gap-1.5 sm:gap-2">
<CatIcon className="h-3.5 w-3.5 sm:h-4 sm:w-4 text-muted-foreground" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-start justify-between gap-2 mb-1">
<div className="min-w-0 flex-1 overflow-hidden">
<p className="font-medium text-xs sm:text-sm text-muted-foreground truncate">{catLabel}</p>
<p className="text-[10px] sm:text-xs text-muted-foreground/70 break-words line-clamp-2">{item.reason}</p>
</div>
<div className="flex items-center gap-1.5 shrink-0">
{isPermanent ? (
<Badge variant="outline" className="text-[9px] sm:text-xs border-amber-500/50 text-amber-500/70 bg-transparent whitespace-nowrap">
Permanent
</Badge>
) : (
<Badge variant="outline" className="text-[9px] sm:text-xs border-blue-500/50 text-blue-500/70 bg-transparent whitespace-nowrap">
Dismissed
</Badge>
)}
<Badge variant="outline" className={`text-[9px] sm:text-xs whitespace-nowrap ${getOutlineBadgeStyle(item.severity)}`}>
was {item.severity}
</Badge>
</div>
</div>
<p className="text-[10px] sm:text-xs text-muted-foreground flex items-center gap-1">
<Clock className="h-3 w-3" />
{isPermanent
? "Permanently suppressed"
: `Suppressed for ${
item.suppression_remaining_hours < 24
? `${Math.round(item.suppression_remaining_hours)}h`
: item.suppression_remaining_hours < 720
? `${Math.round(item.suppression_remaining_hours / 24)} days`
: `${Math.round(item.suppression_remaining_hours / 720)} month(s)`
} more`
}
</p>
</div>
</div>
)
})}
</div>
)
})()}
{/* Custom Suppression Settings Summary */}
{customSuppressions.length > 0 && (
<div className="space-y-2 pt-2">
<div className="flex items-center gap-2 text-xs sm:text-sm font-medium text-muted-foreground">
<Settings2 className="h-3.5 w-3.5 sm:h-4 sm:w-4" />
Custom Suppression Settings
</div>
<div className="rounded-lg border border-blue-500/20 bg-blue-500/5 p-2.5 sm:p-3">
<div className="space-y-1.5">
{customSuppressions.map((cs) => {
const catMeta = CATEGORIES.find(c => c.category === cs.category || c.key === cs.category || c.label === cs.label)
const CatIcon = catMeta?.Icon || Settings2
const durationLabel = cs.hours === -1
? "Permanent"
: cs.hours >= 8760
? `${Math.floor(cs.hours / 8760)} year(s)`
: cs.hours >= 720
? `${Math.floor(cs.hours / 720)} month(s)`
: cs.hours >= 168
? `${Math.floor(cs.hours / 168)} week(s)`
: cs.hours >= 72
? `${Math.floor(cs.hours / 24)} days`
: `${cs.hours}h`
return (
<div key={cs.key} className="flex items-center justify-between gap-2">
<div className="flex items-center gap-2 min-w-0">
<CatIcon className="h-3 w-3 sm:h-3.5 sm:w-3.5 text-blue-400/70 shrink-0" />
<span className="text-[11px] sm:text-xs text-blue-400/80 truncate">{cs.label}</span>
</div>
<Badge variant="outline" className="text-[9px] sm:text-[10px] border-blue-500/30 text-blue-400/80 bg-transparent shrink-0">
{durationLabel}
</Badge>
</div>
)
})}
</div>
<p className="text-[10px] text-muted-foreground/60 mt-2 pt-1.5 border-t border-blue-500/10">
Alerts in these categories are auto-suppressed when detected.
</p>
</div>
</div>
)}
{healthData.timestamp && (
<div className="text-xs text-muted-foreground text-center pt-2">
Last updated: {new Date(healthData.timestamp).toLocaleString()}
</div>
)}
</div>
)}
</DialogContent>
</Dialog>
)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,258 @@
"use client"
import type React from "react"
import { useState, useEffect } from "react"
import { Button } from "./ui/button"
import { Input } from "./ui/input"
import { Label } from "./ui/label"
import { Checkbox } from "./ui/checkbox"
import { Lock, User, AlertCircle, Server, Shield, Eye, EyeOff } from "lucide-react"
import { getApiUrl } from "../lib/api-config"
import Image from "next/image"
interface LoginProps {
onLogin: () => void
}
export function Login({ onLogin }: LoginProps) {
const [username, setUsername] = useState("")
const [password, setPassword] = useState("")
const [totpCode, setTotpCode] = useState("")
const [requiresTotp, setRequiresTotp] = useState(false)
const [rememberMe, setRememberMe] = useState(false)
const [showPassword, setShowPassword] = useState(false)
const [error, setError] = useState("")
const [loading, setLoading] = useState(false)
useEffect(() => {
const savedUsername = localStorage.getItem("proxmenux-saved-username")
const savedPassword = localStorage.getItem("proxmenux-saved-password")
if (savedUsername && savedPassword) {
setUsername(savedUsername)
setPassword(savedPassword)
setRememberMe(true)
}
}, [])
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault()
setError("")
if (!username || !password) {
setError("Please enter username and password")
return
}
if (requiresTotp && !totpCode) {
setError("Please enter your 2FA code")
return
}
setLoading(true)
try {
const response = await fetch(getApiUrl("/api/auth/login"), {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
username,
password,
totp_token: totpCode || undefined, // Include 2FA code if provided
}),
})
const data = await response.json()
if (data.requires_totp) {
setRequiresTotp(true)
setLoading(false)
return
}
if (!response.ok) {
throw new Error(data.message || "Login failed")
}
localStorage.setItem("proxmenux-auth-token", data.token)
if (rememberMe) {
localStorage.setItem("proxmenux-saved-username", username)
localStorage.setItem("proxmenux-saved-password", password)
} else {
localStorage.removeItem("proxmenux-saved-username")
localStorage.removeItem("proxmenux-saved-password")
}
onLogin()
} catch (err) {
setError(err instanceof Error ? err.message : "Login failed")
} finally {
setLoading(false)
}
}
return (
<div className="min-h-screen bg-background flex items-center justify-center p-4">
<div className="w-full max-w-md space-y-8">
<div className="text-center space-y-4">
<div className="flex justify-center">
<div className="w-20 h-20 relative flex items-center justify-center bg-primary/10 rounded-lg">
<Image
src="/images/proxmenux-logo.png"
alt="ProxMenux Logo"
width={80}
height={80}
className="object-contain"
priority
onError={(e) => {
const target = e.target as HTMLImageElement
target.style.display = "none"
const fallback = target.parentElement?.querySelector(".fallback-icon")
if (fallback) {
fallback.classList.remove("hidden")
}
}}
/>
<Server className="h-12 w-12 text-primary absolute fallback-icon hidden" />
</div>
</div>
<div>
<h1 className="text-3xl font-bold">ProxMenux Monitor</h1>
<p className="text-muted-foreground mt-2">Sign in to access your dashboard</p>
</div>
</div>
<div className="bg-card border border-border rounded-lg p-6 shadow-lg">
<form onSubmit={handleLogin} className="space-y-4">
{error && (
<div className="bg-red-500/10 border border-red-500/20 rounded-lg p-3 flex items-start gap-2">
<AlertCircle className="h-5 w-5 text-red-500 flex-shrink-0 mt-0.5" />
<p className="text-sm text-red-500">{error}</p>
</div>
)}
{!requiresTotp ? (
<>
<div className="space-y-2">
<Label htmlFor="login-username" className="text-sm">
Username
</Label>
<div className="relative">
<User className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
id="login-username"
type="text"
placeholder="Enter your username"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="pl-10 text-base"
disabled={loading}
autoComplete="username"
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="login-password" className="text-sm">
Password
</Label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
id="login-password"
type={showPassword ? "text" : "password"}
placeholder="Enter your password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="pl-10 pr-10 text-base"
disabled={loading}
autoComplete="current-password"
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors"
disabled={loading}
tabIndex={-1}
>
{showPassword ? (
<EyeOff className="h-4 w-4" />
) : (
<Eye className="h-4 w-4" />
)}
</button>
</div>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="remember-me"
checked={rememberMe}
onCheckedChange={(checked) => setRememberMe(checked as boolean)}
disabled={loading}
/>
<Label htmlFor="remember-me" className="text-sm font-normal cursor-pointer select-none">
Remember me
</Label>
</div>
</>
) : (
<div className="space-y-4">
<div className="bg-blue-500/10 border border-blue-500/20 rounded-lg p-3 flex items-start gap-2">
<Shield className="h-5 w-5 text-blue-500 flex-shrink-0 mt-0.5" />
<div>
<p className="text-sm font-medium text-blue-500">Two-Factor Authentication</p>
<p className="text-xs text-blue-500 mt-1">Enter the 6-digit code from your authentication app</p>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="totp-code" className="text-sm">
Authentication Code
</Label>
<Input
id="totp-code"
type="text"
placeholder="000000"
value={totpCode}
onChange={(e) => setTotpCode(e.target.value.replace(/\D/g, "").slice(0, 6))}
className="text-center text-lg tracking-widest font-mono text-base"
maxLength={6}
disabled={loading}
autoComplete="one-time-code"
autoFocus
/>
<p className="text-xs text-muted-foreground text-center">
You can also use a backup code (format: XXXX-XXXX)
</p>
</div>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => {
setRequiresTotp(false)
setTotpCode("")
setError("")
}}
className="w-full"
>
Back to login
</Button>
</div>
)}
<Button type="submit" className="w-full bg-blue-500 hover:bg-blue-600" disabled={loading}>
{loading ? "Signing in..." : requiresTotp ? "Verify Code" : "Sign In"}
</Button>
</form>
</div>
<p className="text-center text-sm text-muted-foreground">ProxMenux Monitor v1.2.0</p>
</div>
</div>
)
}

View File

@@ -0,0 +1,857 @@
"use client"
import type React from "react"
import { useState, useEffect, useRef, useCallback } from "react"
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
import {
Activity,
ArrowUp,
ArrowDown,
ArrowLeft,
ArrowRight,
CornerDownLeft,
GripHorizontal,
ChevronDown,
Search,
Send,
Lightbulb,
Terminal,
Trash2,
X,
} from "lucide-react"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
DropdownMenuSeparator,
DropdownMenuLabel,
} from "@/components/ui/dropdown-menu"
import { DialogHeader, DialogDescription } from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Dialog as SearchDialog, DialogContent as SearchDialogContent, DialogTitle as SearchDialogTitle } from "@/components/ui/dialog"
import "xterm/css/xterm.css"
import { API_PORT, fetchApi } from "@/lib/api-config"
interface LxcTerminalModalProps {
open: boolean
onClose: () => void
vmid: number
vmName: string
}
interface CheatSheetResult {
command: string
description: string
examples: string[]
}
const proxmoxCommands = [
{ cmd: "ls -la", desc: "List all files with details" },
{ cmd: "cd /path/to/dir", desc: "Change directory" },
{ cmd: "cat filename", desc: "Display file contents" },
{ cmd: "grep 'pattern' file", desc: "Search for pattern in file" },
{ cmd: "find . -name 'file'", desc: "Find files by name" },
{ cmd: "df -h", desc: "Show disk usage" },
{ cmd: "du -sh *", desc: "Show directory sizes" },
{ cmd: "free -h", desc: "Show memory usage" },
{ cmd: "top", desc: "Show running processes" },
{ cmd: "ps aux | grep process", desc: "Find running process" },
{ cmd: "systemctl status service", desc: "Check service status" },
{ cmd: "systemctl restart service", desc: "Restart a service" },
{ cmd: "apt update && apt upgrade", desc: "Update packages" },
{ cmd: "apt install package", desc: "Install package" },
{ cmd: "tail -f /var/log/syslog", desc: "Follow log file" },
{ cmd: "chmod 755 file", desc: "Change file permissions" },
{ cmd: "chown user:group file", desc: "Change file owner" },
{ cmd: "tar -xzf file.tar.gz", desc: "Extract tar.gz archive" },
{ cmd: "docker ps", desc: "List running containers" },
{ cmd: "docker images", desc: "List Docker images" },
{ cmd: "ip addr show", desc: "Show IP addresses" },
{ cmd: "ping host", desc: "Test network connectivity" },
{ cmd: "curl -I url", desc: "Get HTTP headers" },
{ cmd: "history", desc: "Show command history" },
{ cmd: "clear", desc: "Clear terminal screen" },
]
function getWebSocketUrl(): string {
if (typeof window === "undefined") {
return "ws://localhost:8008/ws/terminal"
}
const { protocol, hostname, port } = window.location
const isStandardPort = port === "" || port === "80" || port === "443"
const wsProtocol = protocol === "https:" ? "wss:" : "ws:"
if (isStandardPort) {
return `${wsProtocol}//${hostname}/ws/terminal`
} else {
return `${wsProtocol}//${hostname}:${API_PORT}/ws/terminal`
}
}
export function LxcTerminalModal({
open: isOpen,
onClose,
vmid,
vmName,
}: LxcTerminalModalProps) {
const termRef = useRef<any>(null)
const wsRef = useRef<WebSocket | null>(null)
const fitAddonRef = useRef<any>(null)
const terminalContainerRef = useRef<HTMLDivElement>(null)
const pingIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
const [connectionStatus, setConnectionStatus] = useState<"connecting" | "online" | "offline">("connecting")
const [isMobile, setIsMobile] = useState(false)
const [isTablet, setIsTablet] = useState(false)
const isInsideLxcRef = useRef(false)
const outputBufferRef = useRef<string>("")
const [modalHeight, setModalHeight] = useState(500)
const [isResizing, setIsResizing] = useState(false)
const resizeBarRef = useRef<HTMLDivElement>(null)
const modalHeightRef = useRef(500)
// Search state
const [searchModalOpen, setSearchModalOpen] = useState(false)
const [searchQuery, setSearchQuery] = useState("")
const [filteredCommands, setFilteredCommands] = useState<Array<{ cmd: string; desc: string }>>(proxmoxCommands)
const [isSearching, setIsSearching] = useState(false)
const [searchResults, setSearchResults] = useState<CheatSheetResult[]>([])
const [useOnline, setUseOnline] = useState(true)
// Detect mobile/tablet
useEffect(() => {
const checkDevice = () => {
const width = window.innerWidth
setIsMobile(width < 640)
setIsTablet(width >= 640 && width < 1024)
}
checkDevice()
window.addEventListener("resize", checkDevice)
return () => window.removeEventListener("resize", checkDevice)
}, [])
// Cleanup on close
useEffect(() => {
if (!isOpen) {
if (pingIntervalRef.current) {
clearInterval(pingIntervalRef.current)
pingIntervalRef.current = null
}
if (wsRef.current) {
wsRef.current.close()
wsRef.current = null
}
if (termRef.current) {
termRef.current.dispose()
termRef.current = null
}
setConnectionStatus("connecting")
isInsideLxcRef.current = false
outputBufferRef.current = ""
}
}, [isOpen])
// Initialize terminal
useEffect(() => {
if (!isOpen) return
// Small delay to ensure Dialog content is rendered
const initTimeout = setTimeout(() => {
if (!terminalContainerRef.current) return
initTerminal()
}, 100)
const initTerminal = async () => {
const [TerminalClass, FitAddonClass] = await Promise.all([
import("xterm").then((mod) => mod.Terminal),
import("xterm-addon-fit").then((mod) => mod.FitAddon),
])
const fontSize = window.innerWidth < 768 ? 12 : 16
const term = new TerminalClass({
rendererType: "dom",
fontFamily: '"Courier", "Courier New", "Liberation Mono", "DejaVu Sans Mono", monospace',
fontSize: fontSize,
lineHeight: 1,
cursorBlink: true,
scrollback: 2000,
disableStdin: false,
customGlyphs: true,
fontWeight: "500",
fontWeightBold: "700",
theme: {
background: "#000000",
foreground: "#ffffff",
cursor: "#ffffff",
cursorAccent: "#000000",
black: "#2e3436",
red: "#cc0000",
green: "#4e9a06",
yellow: "#c4a000",
blue: "#3465a4",
magenta: "#75507b",
cyan: "#06989a",
white: "#d3d7cf",
brightBlack: "#555753",
brightRed: "#ef2929",
brightGreen: "#8ae234",
brightYellow: "#fce94f",
brightBlue: "#729fcf",
brightMagenta: "#ad7fa8",
brightCyan: "#34e2e2",
brightWhite: "#eeeeec",
},
})
const fitAddon = new FitAddonClass()
term.loadAddon(fitAddon)
if (terminalContainerRef.current) {
term.open(terminalContainerRef.current)
fitAddon.fit()
}
termRef.current = term
fitAddonRef.current = fitAddon
// Connect WebSocket to host terminal
const wsUrl = getWebSocketUrl()
const ws = new WebSocket(wsUrl)
wsRef.current = ws
// Reset state for new connection
isInsideLxcRef.current = false
outputBufferRef.current = ""
ws.onopen = () => {
setConnectionStatus("online")
// Start heartbeat ping
pingIntervalRef.current = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'ping' }))
} else {
if (pingIntervalRef.current) {
clearInterval(pingIntervalRef.current)
}
}
}, 25000)
// Sync terminal size
fitAddon.fit()
ws.send(JSON.stringify({
type: "resize",
cols: term.cols,
rows: term.rows,
}))
// Auto-execute pct enter after connection is ready
setTimeout(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(`pct enter ${vmid}\r`)
}
}, 300)
}
ws.onerror = () => {
setConnectionStatus("offline")
term.writeln("\r\n\x1b[31m[ERROR] WebSocket connection error\x1b[0m")
}
ws.onclose = () => {
setConnectionStatus("offline")
if (pingIntervalRef.current) {
clearInterval(pingIntervalRef.current)
}
term.writeln("\r\n\x1b[33m[INFO] Connection closed\x1b[0m")
}
term.onData((data) => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(data)
}
})
ws.onmessage = (event) => {
// Filter out pong responses
if (event.data === '{"type": "pong"}' || event.data === '{"type":"pong"}') {
return
}
// Helper to strip ANSI escape codes for pattern matching
const stripAnsi = (str: string) => str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '')
// Buffer output until we detect we're inside the LXC
// pct enter always enters directly without login prompt when run as root
if (!isInsideLxcRef.current) {
outputBufferRef.current += event.data
const buffer = outputBufferRef.current
const cleanBuffer = stripAnsi(buffer)
// Look for pct enter command followed by a new prompt
const pctEnterMatch = cleanBuffer.match(/pct enter (\d+)\r?\n/)
if (pctEnterMatch) {
const afterPctEnter = cleanBuffer.substring(cleanBuffer.indexOf(pctEnterMatch[0]) + pctEnterMatch[0].length)
// Extract the host name from the prompt BEFORE pct enter (e.g., "root@amd")
const hostPromptMatch = cleanBuffer.match(/@([a-zA-Z0-9_-]+).*pct enter/)
const hostName = hostPromptMatch ? hostPromptMatch[1] : null
// Look for a new prompt after pct enter that ends with # or $
// This works for both bash (user@host:~#) and ash/Alpine ([user@host /]#)
const promptMatch = afterPctEnter.match(/[@\[]([a-zA-Z0-9_-]+)[^\r\n]*[#$]\s*$/)
if (promptMatch) {
const lxcHostname = promptMatch[1]
// If we found a prompt with a DIFFERENT hostname than the Proxmox host,
// we're inside the LXC container
if (!hostName || lxcHostname !== hostName) {
isInsideLxcRef.current = true
// Find the original prompt with ANSI codes to display it properly
const afterPctEnterWithAnsi = buffer.substring(buffer.indexOf('pct enter') + pctEnterMatch[0].length)
// Write the LXC prompt (last line with # or $)
const lastPromptMatch = afterPctEnterWithAnsi.match(/[^\r\n]*[#$]\s*$/)
if (lastPromptMatch) {
term.write(lastPromptMatch[0])
}
// Detect if this is Alpine/ash shell by checking prompt format
// Alpine uses: [root@hostname ~]# or [root@hostname /]#
// Other distros use: root@hostname:/# or root@hostname:~#
const isAlpine = afterPctEnter.match(/\[[^\]]+@[^\]]+\s+[^\]]*\][#$]/)
if (isAlpine) {
// Send an extra Enter ONLY for Alpine containers (ash shell)
// This forces the prompt to refresh properly
setTimeout(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send('\r')
}
}, 100)
}
return
}
}
}
} else {
// Already inside LXC, write directly
term.write(event.data)
}
}
}
return () => {
clearTimeout(initTimeout)
if (pingIntervalRef.current) {
clearInterval(pingIntervalRef.current)
}
if (wsRef.current) {
wsRef.current.close()
}
if (termRef.current) {
termRef.current.dispose()
}
}
}, [isOpen, vmid])
// Resize handling
useEffect(() => {
if (termRef.current && fitAddonRef.current && isOpen) {
setTimeout(() => {
fitAddonRef.current?.fit()
if (wsRef.current?.readyState === WebSocket.OPEN) {
wsRef.current.send(JSON.stringify({
type: "resize",
cols: termRef.current.cols,
rows: termRef.current.rows,
}))
}
}, 100)
}
}, [modalHeight, isOpen])
// Resize bar handlers
const handleResizeStart = useCallback((e: React.MouseEvent | React.TouchEvent) => {
e.preventDefault()
setIsResizing(true)
modalHeightRef.current = modalHeight
}, [modalHeight])
useEffect(() => {
if (!isResizing) return
const handleMove = (e: MouseEvent | TouchEvent) => {
const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY
const windowHeight = window.innerHeight
const newHeight = windowHeight - clientY - 20
const clampedHeight = Math.max(300, Math.min(windowHeight - 100, newHeight))
modalHeightRef.current = clampedHeight
setModalHeight(clampedHeight)
}
const handleEnd = () => {
setIsResizing(false)
}
document.addEventListener("mousemove", handleMove)
document.addEventListener("mouseup", handleEnd)
document.addEventListener("touchmove", handleMove)
document.addEventListener("touchend", handleEnd)
return () => {
document.removeEventListener("mousemove", handleMove)
document.removeEventListener("mouseup", handleEnd)
document.removeEventListener("touchmove", handleMove)
document.removeEventListener("touchend", handleEnd)
}
}, [isResizing])
// Send key helpers for mobile/tablet
const sendKey = useCallback((key: string) => {
if (wsRef.current?.readyState === WebSocket.OPEN) {
wsRef.current.send(key)
}
}, [])
const sendEsc = useCallback(() => sendKey("\x1b"), [sendKey])
const sendTab = useCallback(() => sendKey("\t"), [sendKey])
const sendArrowUp = useCallback(() => sendKey("\x1b[A"), [sendKey])
const sendArrowDown = useCallback(() => sendKey("\x1b[B"), [sendKey])
const sendArrowLeft = useCallback(() => sendKey("\x1b[D"), [sendKey])
const sendArrowRight = useCallback(() => sendKey("\x1b[C"), [sendKey])
const sendEnter = useCallback(() => sendKey("\r"), [sendKey])
const sendCtrlC = useCallback(() => sendKey("\x03"), [sendKey]) // Ctrl+C
// Search effect - debounced search with cheat.sh
useEffect(() => {
const searchCheatSh = async (query: string) => {
if (!query.trim()) {
setSearchResults([])
setFilteredCommands(proxmoxCommands)
return
}
try {
setIsSearching(true)
const searchEndpoint = `/api/terminal/search-command?q=${encodeURIComponent(query)}`
const data = await fetchApi<{ success: boolean; examples: any[] }>(searchEndpoint, {
method: "GET",
signal: AbortSignal.timeout(10000),
})
if (!data.success || !data.examples || data.examples.length === 0) {
throw new Error("No examples found")
}
const formattedResults: CheatSheetResult[] = data.examples.map((example: any) => ({
command: example.command,
description: example.description || "",
examples: [example.command],
}))
setUseOnline(true)
setSearchResults(formattedResults)
} catch (error) {
const filtered = proxmoxCommands.filter(
(item) =>
item.cmd.toLowerCase().includes(query.toLowerCase()) ||
item.desc.toLowerCase().includes(query.toLowerCase()),
)
setFilteredCommands(filtered)
setSearchResults([])
setUseOnline(false)
} finally {
setIsSearching(false)
}
}
const debounce = setTimeout(() => {
if (searchQuery && searchQuery.length >= 2) {
searchCheatSh(searchQuery)
} else {
setSearchResults([])
setFilteredCommands(proxmoxCommands)
}
}, 800)
return () => clearTimeout(debounce)
}, [searchQuery])
const handleClear = useCallback(() => {
if (termRef.current) {
termRef.current.clear()
}
}, [])
const sendToTerminal = useCallback((command: string) => {
if (wsRef.current?.readyState === WebSocket.OPEN) {
wsRef.current.send(command)
setTimeout(() => {
setSearchModalOpen(false)
}, 100)
}
}, [])
const showMobileControls = isMobile || isTablet
return (
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
<DialogContent
className="max-w-4xl w-[95vw] p-0 gap-0 bg-black border-border overflow-hidden flex flex-col"
style={{ height: `${modalHeight}px` }}
hideClose
>
{/* Resize bar */}
<div
ref={resizeBarRef}
className="h-3 w-full cursor-ns-resize flex items-center justify-center bg-zinc-900 hover:bg-zinc-800 transition-colors touch-none"
onMouseDown={handleResizeStart}
onTouchStart={handleResizeStart}
>
<GripHorizontal className="h-4 w-4 text-zinc-500" />
</div>
{/* Header */}
<div className="flex items-center justify-between px-4 py-2 bg-zinc-900 border-b border-zinc-800">
<DialogTitle className="text-sm font-medium text-white">
Terminal: {vmName} (ID: {vmid})
</DialogTitle>
<div className="flex gap-2">
<Button
onClick={() => setSearchModalOpen(true)}
variant="outline"
size="sm"
disabled={connectionStatus !== "online"}
className="h-8 gap-2 bg-blue-600/20 hover:bg-blue-600/30 border-blue-600/50 text-blue-400 disabled:opacity-50"
>
<Search className="h-4 w-4" />
<span className="hidden sm:inline">Search</span>
</Button>
<Button
onClick={handleClear}
variant="outline"
size="sm"
disabled={connectionStatus !== "online"}
className="h-8 gap-2 bg-yellow-600/20 hover:bg-yellow-600/30 border-yellow-600/50 text-yellow-400 disabled:opacity-50"
>
<Trash2 className="h-4 w-4" />
<span className="hidden sm:inline">Clear</span>
</Button>
</div>
</div>
{/* Terminal container */}
<div className="flex-1 overflow-hidden bg-black p-1">
<div
ref={terminalContainerRef}
className="w-full h-full"
style={{ minHeight: "200px" }}
/>
</div>
{/* Mobile/Tablet control buttons */}
{showMobileControls && (
<div className="px-2 py-2 bg-zinc-900 border-t border-zinc-800">
<div className="flex items-center justify-center gap-1.5">
<Button
variant="outline"
size="sm"
onClick={sendEsc}
className="h-8 px-2 text-xs bg-zinc-800 border-zinc-700 text-zinc-300"
>
ESC
</Button>
<Button
variant="outline"
size="sm"
onClick={sendTab}
className="h-8 px-2 text-xs bg-zinc-800 border-zinc-700 text-zinc-300"
>
TAB
</Button>
<Button
variant="outline"
size="sm"
onClick={sendArrowUp}
className="h-8 w-8 p-0 bg-zinc-800 border-zinc-700"
>
<ArrowUp className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={sendArrowDown}
className="h-8 w-8 p-0 bg-zinc-800 border-zinc-700"
>
<ArrowDown className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={sendArrowLeft}
className="h-8 w-8 p-0 bg-zinc-800 border-zinc-700"
>
<ArrowLeft className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={sendArrowRight}
className="h-8 w-8 p-0 bg-zinc-800 border-zinc-700"
>
<ArrowRight className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={sendEnter}
className="h-8 px-2 text-xs bg-blue-600/20 border-blue-600/50 text-blue-400 hover:bg-blue-600/30"
>
<CornerDownLeft className="h-4 w-4 mr-1" />
Enter
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
size="sm"
className="h-8 px-2 text-xs bg-zinc-800 border-zinc-700 text-zinc-300 gap-1"
>
Ctrl
<ChevronDown className="h-3 w-3" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-48">
<DropdownMenuLabel className="text-xs text-muted-foreground">Control Sequences</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem onSelect={() => sendKey("\x03")}>
<span className="font-mono text-xs mr-2">Ctrl+C</span>
<span className="text-muted-foreground text-xs">Cancel/Interrupt</span>
</DropdownMenuItem>
<DropdownMenuItem onSelect={() => sendKey("\x18")}>
<span className="font-mono text-xs mr-2">Ctrl+X</span>
<span className="text-muted-foreground text-xs">Exit (nano)</span>
</DropdownMenuItem>
<DropdownMenuItem onSelect={() => sendKey("\x12")}>
<span className="font-mono text-xs mr-2">Ctrl+R</span>
<span className="text-muted-foreground text-xs">Search history</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
)}
{/* Status bar at bottom */}
<div className="flex items-center justify-between px-4 py-2 bg-zinc-900 border-t border-zinc-800">
<div className="flex items-center gap-3">
<Activity className="h-5 w-5 text-blue-500" />
<div
className={`w-2 h-2 rounded-full ${
connectionStatus === "online"
? "bg-green-500"
: connectionStatus === "connecting"
? "bg-yellow-500 animate-pulse"
: "bg-red-500"
}`}
/>
<span className="text-xs text-zinc-400 capitalize">{connectionStatus}</span>
</div>
<Button
onClick={onClose}
variant="outline"
size="sm"
className="h-8 gap-2 bg-red-600/20 hover:bg-red-600/30 border-red-600/50 text-red-400"
>
<X className="h-4 w-4" />
<span className="hidden sm:inline">Close</span>
</Button>
</div>
</DialogContent>
{/* Search Commands Modal */}
<SearchDialog open={searchModalOpen} onOpenChange={setSearchModalOpen}>
<SearchDialogContent className="max-w-3xl max-h-[85vh] overflow-hidden flex flex-col">
<DialogHeader className="flex flex-row items-center justify-between space-y-0 pb-4 border-b border-zinc-800">
<SearchDialogTitle className="text-xl font-semibold">Search Commands</SearchDialogTitle>
<div className="flex items-center gap-2">
<div
className={`w-2 h-2 rounded-full ${useOnline ? "bg-green-500" : "bg-red-500"}`}
title={useOnline ? "Online - Using cheat.sh API" : "Offline - Using local commands"}
/>
</div>
</DialogHeader>
<DialogDescription className="sr-only">Search for Linux commands</DialogDescription>
<div className="space-y-4">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-zinc-500" />
<Input
placeholder="Search commands... (e.g., tar, docker, systemctl)"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10 bg-zinc-900 border-zinc-700 focus:border-blue-500 focus:ring-1 focus:ring-blue-500 text-base"
autoCapitalize="none"
autoComplete="off"
autoCorrect="off"
spellCheck={false}
/>
</div>
{isSearching && (
<div className="text-center py-4 text-zinc-400">
<div className="animate-spin inline-block w-6 h-6 border-2 border-current border-t-transparent rounded-full mb-2" />
<p className="text-sm">Searching cheat.sh...</p>
</div>
)}
<div className="flex-1 overflow-y-auto space-y-2 pr-2 max-h-[50vh]">
{searchResults.length > 0 ? (
<>
{searchResults.map((result, index) => (
<div
key={index}
className="p-4 rounded-lg border border-zinc-700 bg-zinc-800/50 hover:border-zinc-600 transition-colors"
>
{result.description && (
<p className="text-xs text-zinc-400 mb-2 leading-relaxed"># {result.description}</p>
)}
<div
onClick={() => sendToTerminal(result.command)}
className="flex items-start justify-between gap-2 cursor-pointer group hover:bg-zinc-800/50 rounded p-2 -m-2"
>
<code className="text-sm text-blue-400 font-mono break-all flex-1">{result.command}</code>
<Send className="h-4 w-4 text-zinc-600 group-hover:text-blue-400 flex-shrink-0 mt-0.5 transition-colors" />
</div>
</div>
))}
<div className="text-center py-2">
<p className="text-xs text-zinc-500">
<Lightbulb className="inline-block w-3 h-3 mr-1" />
Powered by cheat.sh
</p>
</div>
</>
) : filteredCommands.length > 0 && !useOnline ? (
filteredCommands.map((item, index) => (
<div
key={index}
onClick={() => sendToTerminal(item.cmd)}
className="p-3 rounded-lg border border-zinc-700 bg-zinc-800/50 hover:bg-zinc-800 hover:border-blue-500 cursor-pointer transition-colors"
>
<div className="flex items-start justify-between gap-2">
<div className="flex-1 min-w-0">
<code className="text-sm text-blue-400 font-mono break-all">{item.cmd}</code>
<p className="text-xs text-zinc-400 mt-1">{item.desc}</p>
</div>
<Button
onClick={(e) => {
e.stopPropagation()
sendToTerminal(item.cmd)
}}
size="sm"
variant="ghost"
className="shrink-0 h-7 px-2 text-xs"
>
<Send className="h-3 w-3 mr-1" />
Send
</Button>
</div>
</div>
))
) : !isSearching && !searchQuery && !useOnline ? (
proxmoxCommands.map((item, index) => (
<div
key={index}
onClick={() => sendToTerminal(item.cmd)}
className="p-3 rounded-lg border border-zinc-700 bg-zinc-800/50 hover:bg-zinc-800 hover:border-blue-500 cursor-pointer transition-colors"
>
<div className="flex items-start justify-between gap-2">
<div className="flex-1 min-w-0">
<code className="text-sm text-blue-400 font-mono break-all">{item.cmd}</code>
<p className="text-xs text-zinc-400 mt-1">{item.desc}</p>
</div>
<Button
onClick={(e) => {
e.stopPropagation()
sendToTerminal(item.cmd)
}}
size="sm"
variant="ghost"
className="shrink-0 h-7 px-2 text-xs"
>
<Send className="h-3 w-3 mr-1" />
Send
</Button>
</div>
</div>
))
) : !isSearching ? (
<div className="text-center py-12 space-y-4">
{searchQuery ? (
<>
<Search className="w-12 h-12 text-zinc-600 mx-auto" />
<div>
<p className="text-zinc-400 font-medium">{"No results found for \""}{searchQuery}{"\""}</p>
<p className="text-xs text-zinc-500 mt-1">Try a different command or check your spelling</p>
</div>
</>
) : (
<>
<Terminal className="w-12 h-12 text-zinc-600 mx-auto" />
<div>
<p className="text-zinc-400 font-medium mb-2">Search for any command</p>
<div className="text-sm text-zinc-500 space-y-1">
<p>Try searching for:</p>
<div className="flex flex-wrap justify-center gap-2 mt-2">
{["tar", "grep", "docker", "systemctl", "curl"].map((cmd) => (
<code
key={cmd}
onClick={() => setSearchQuery(cmd)}
className="px-2 py-1 bg-zinc-800 rounded text-blue-400 cursor-pointer hover:bg-zinc-700"
>
{cmd}
</code>
))}
</div>
</div>
</div>
{useOnline && (
<div className="flex items-center justify-center gap-2 text-xs text-zinc-600 mt-4">
<Lightbulb className="w-3 h-3" />
<span>Powered by cheat.sh</span>
</div>
)}
</>
)}
</div>
) : null}
</div>
<div className="pt-2 border-t border-zinc-800 flex items-center justify-between text-xs text-zinc-500">
<div className="flex items-center gap-2">
<Lightbulb className="w-3 h-3" />
<span>Tip: Search for any Linux command</span>
</div>
{useOnline && searchResults.length > 0 && <span className="text-zinc-600">Powered by cheat.sh</span>}
</div>
</div>
</SearchDialogContent>
</SearchDialog>
</Dialog>
)
}

View File

@@ -0,0 +1,489 @@
"use client"
import { useState, useEffect } from "react"
import { Button } from "@/components/ui/button"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { ArrowLeft, Loader2 } from "lucide-react"
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from "recharts"
import { fetchApi } from "@/lib/api-config"
interface MetricsViewProps {
vmid: number
vmName: string
vmType: "qemu" | "lxc"
onBack: () => void
}
const TIMEFRAME_OPTIONS = [
{ value: "hour", label: "1 Hour" },
{ value: "day", label: "24 Hours" },
{ value: "week", label: "7 Days" },
{ value: "month", label: "30 Days" },
{ value: "year", label: "1 Year" },
]
const CustomCPUTooltip = ({ active, payload, label }: any) => {
if (active && payload && payload.length) {
return (
<div className="bg-gray-900/95 backdrop-blur-sm border border-gray-700 rounded-lg p-3 shadow-xl">
<p className="text-sm font-semibold text-white mb-2">{label}</p>
<div className="space-y-1.5">
{payload.map((entry: any, index: number) => (
<div key={index} className="flex items-center gap-2">
<div className="w-2.5 h-2.5 rounded-full flex-shrink-0" style={{ backgroundColor: entry.color }} />
<span className="text-xs text-gray-300 min-w-[60px]">{entry.name}:</span>
<span className="text-sm font-semibold text-white">{entry.value}</span>
</div>
))}
</div>
</div>
)
}
return null
}
const CustomMemoryTooltip = ({ active, payload, label }: any) => {
if (active && payload && payload.length) {
return (
<div className="bg-gray-900/95 backdrop-blur-sm border border-gray-700 rounded-lg p-3 shadow-xl">
<p className="text-sm font-semibold text-white mb-2">{label}</p>
<div className="space-y-1.5">
{payload.map((entry: any, index: number) => (
<div key={index} className="flex items-center gap-2">
<div className="w-2.5 h-2.5 rounded-full flex-shrink-0" style={{ backgroundColor: entry.color }} />
<span className="text-xs text-gray-300 min-w-[60px]">{entry.name}:</span>
<span className="text-sm font-semibold text-white">{entry.value} GB</span>
</div>
))}
</div>
</div>
)
}
return null
}
const CustomDiskTooltip = ({ active, payload, label }: any) => {
if (active && payload && payload.length) {
return (
<div className="bg-gray-900/95 backdrop-blur-sm border border-gray-700 rounded-lg p-3 shadow-xl">
<p className="text-sm font-semibold text-white mb-2">{label}</p>
<div className="space-y-1.5">
{payload.map((entry: any, index: number) => (
<div key={index} className="flex items-center gap-2">
<div className="w-2.5 h-2.5 rounded-full flex-shrink-0" style={{ backgroundColor: entry.color }} />
<span className="text-xs text-gray-300 min-w-[60px]">{entry.name}:</span>
<span className="text-sm font-semibold text-white">{entry.value} MB</span>
</div>
))}
</div>
</div>
)
}
return null
}
const CustomNetworkTooltip = ({ active, payload, label }: any) => {
if (active && payload && payload.length) {
return (
<div className="bg-gray-900/95 backdrop-blur-sm border border-gray-700 rounded-lg p-3 shadow-xl">
<p className="text-sm font-semibold text-white mb-2">{label}</p>
<div className="space-y-1.5">
{payload.map((entry: any, index: number) => (
<div key={index} className="flex items-center gap-2">
<div className="w-2.5 h-2.5 rounded-full flex-shrink-0" style={{ backgroundColor: entry.color }} />
<span className="text-xs text-gray-300 min-w-[60px]">{entry.name}:</span>
<span className="text-sm font-semibold text-white">{entry.value} MB</span>
</div>
))}
</div>
</div>
)
}
return null
}
export function MetricsView({ vmid, vmName, vmType, onBack }: MetricsViewProps) {
const [timeframe, setTimeframe] = useState("week")
const [data, setData] = useState<any[]>([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [hiddenDiskLines, setHiddenDiskLines] = useState<string[]>([])
const [hiddenNetworkLines, setHiddenNetworkLines] = useState<string[]>([])
useEffect(() => {
fetchMetrics()
}, [vmid, timeframe])
const fetchMetrics = async () => {
setLoading(true)
setError(null)
try {
const result = await fetchApi<any>(`/api/vms/${vmid}/metrics?timeframe=${timeframe}`)
const transformedData = result.data.map((item: any) => {
const date = new Date(item.time * 1000)
let timeLabel = ""
if (timeframe === "hour") {
timeLabel = date.toLocaleString("en-US", {
hour: "2-digit",
minute: "2-digit",
hour12: false,
})
} else if (timeframe === "day") {
timeLabel = date.toLocaleString("en-US", {
hour: "2-digit",
minute: "2-digit",
hour12: false,
})
} else if (timeframe === "week") {
timeLabel = date.toLocaleString("en-US", {
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
hour12: false,
})
} else if (timeframe === "month") {
timeLabel = date.toLocaleString("en-US", {
month: "short",
day: "numeric",
})
} else {
timeLabel = date.toLocaleString("en-US", {
month: "short",
year: "numeric",
})
}
return {
time: timeLabel,
timestamp: item.time,
cpu: item.cpu ? Number((item.cpu * 100).toFixed(2)) : 0,
memory: item.mem ? Number(((item.mem / item.maxmem) * 100).toFixed(2)) : 0,
memoryGB: item.mem ? Number((item.mem / 1024 / 1024 / 1024).toFixed(2)) : 0,
maxMemoryGB: item.maxmem ? Number((item.maxmem / 1024 / 1024 / 1024).toFixed(2)) : 0,
netin: item.netin ? Number((item.netin / 1024 / 1024).toFixed(2)) : 0,
netout: item.netout ? Number((item.netout / 1024 / 1024).toFixed(2)) : 0,
diskread: item.diskread ? Number((item.diskread / 1024 / 1024).toFixed(2)) : 0,
diskwrite: item.diskwrite ? Number((item.diskwrite / 1024 / 1024).toFixed(2)) : 0,
}
})
setData(transformedData)
} catch (err: any) {
setError(err.message || "Error loading metrics")
} finally {
setLoading(false)
}
}
const formatXAxisTick = (tick: any) => {
return tick
}
const renderAllCharts = () => {
if (loading) {
return (
<div className="flex items-center justify-center h-[400px]">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
)
}
if (error) {
return (
<div className="flex items-center justify-center h-[400px]">
<p className="text-red-500">{error}</p>
</div>
)
}
if (data.length === 0) {
return (
<div className="flex items-center justify-center h-[400px]">
<p className="text-muted-foreground">No data available</p>
</div>
)
}
const tickInterval = Math.ceil(data.length / 8)
return (
<div className="space-y-8">
{/* CPU Chart */}
<div>
<h3 className="text-lg font-semibold mb-4">CPU Usage</h3>
<ResponsiveContainer width="100%" height={300}>
<AreaChart data={data} margin={{ bottom: 80 }}>
<CartesianGrid strokeDasharray="3 3" stroke="currentColor" className="text-border" />
<XAxis
dataKey="time"
stroke="currentColor"
className="text-foreground"
tick={{ fill: "currentColor" }}
angle={-45}
textAnchor="end"
height={60}
interval={tickInterval}
tickFormatter={formatXAxisTick}
/>
<YAxis
stroke="currentColor"
className="text-foreground"
tick={{ fill: "currentColor" }}
label={{ value: "%", angle: -90, position: "insideLeft", fill: "currentColor" }}
domain={[0, "dataMax"]}
/>
<Tooltip content={<CustomCPUTooltip />} />
<Area
type="monotone"
dataKey="cpu"
stroke="#3b82f6"
strokeWidth={2}
fill="#3b82f6"
fillOpacity={0.3}
name="CPU %"
/>
</AreaChart>
</ResponsiveContainer>
</div>
{/* Memory Chart */}
<div>
<h3 className="text-lg font-semibold mb-4">Memory Usage</h3>
<ResponsiveContainer width="100%" height={300}>
<AreaChart data={data} margin={{ bottom: 80 }}>
<CartesianGrid strokeDasharray="3 3" stroke="currentColor" className="text-border" />
<XAxis
dataKey="time"
stroke="currentColor"
className="text-foreground"
tick={{ fill: "currentColor" }}
angle={-45}
textAnchor="end"
height={60}
interval={tickInterval}
tickFormatter={formatXAxisTick}
/>
<YAxis
stroke="currentColor"
className="text-foreground"
tick={{ fill: "currentColor" }}
label={{ value: "GB", angle: -90, position: "insideLeft", fill: "currentColor" }}
domain={[0, "dataMax"]}
/>
<Tooltip content={<CustomMemoryTooltip />} />
<Area
type="monotone"
dataKey="memoryGB"
stroke="#10b981"
fill="#10b981"
fillOpacity={0.3}
strokeWidth={2}
name="Memory GB"
/>
</AreaChart>
</ResponsiveContainer>
</div>
{/* Disk I/O Chart */}
<div>
<h3 className="text-lg font-semibold mb-4">Disk I/O</h3>
<ResponsiveContainer width="100%" height={300}>
<AreaChart data={data} margin={{ bottom: 80 }}>
<CartesianGrid strokeDasharray="3 3" stroke="currentColor" className="text-border" />
<XAxis
dataKey="time"
stroke="currentColor"
className="text-foreground"
tick={{ fill: "currentColor" }}
angle={-45}
textAnchor="end"
height={60}
interval={tickInterval}
tickFormatter={formatXAxisTick}
/>
<YAxis
stroke="currentColor"
className="text-foreground"
tick={{ fill: "currentColor" }}
label={{ value: "MB", angle: -90, position: "insideLeft", fill: "currentColor" }}
domain={[0, "dataMax"]}
/>
<Tooltip content={<CustomDiskTooltip />} />
<Legend content={renderDiskLegend} verticalAlign="top" />
<Area
type="monotone"
dataKey="diskread"
stroke="#10b981"
fill="#10b981"
fillOpacity={0.3}
strokeWidth={2}
name="Read"
hide={hiddenDiskLines.includes("diskread")}
/>
<Area
type="monotone"
dataKey="diskwrite"
stroke="#3b82f6"
fill="#3b82f6"
fillOpacity={0.3}
strokeWidth={2}
name="Write"
hide={hiddenDiskLines.includes("diskwrite")}
/>
</AreaChart>
</ResponsiveContainer>
</div>
{/* Network I/O Chart */}
<div>
<h3 className="text-lg font-semibold mb-4">Network I/O</h3>
<ResponsiveContainer width="100%" height={300}>
<AreaChart data={data} margin={{ bottom: 80 }}>
<CartesianGrid strokeDasharray="3 3" stroke="currentColor" className="text-border" />
<XAxis
dataKey="time"
stroke="currentColor"
className="text-foreground"
tick={{ fill: "currentColor" }}
angle={-45}
textAnchor="end"
height={60}
interval={tickInterval}
tickFormatter={formatXAxisTick}
/>
<YAxis
stroke="currentColor"
className="text-foreground"
tick={{ fill: "currentColor" }}
label={{ value: "MB", angle: -90, position: "insideLeft", fill: "currentColor" }}
domain={[0, "dataMax"]}
/>
<Tooltip content={<CustomNetworkTooltip />} />
<Legend content={renderNetworkLegend} verticalAlign="top" />
<Area
type="monotone"
dataKey="netin"
stroke="#10b981"
fill="#10b981"
fillOpacity={0.3}
strokeWidth={2}
name="Download"
hide={hiddenNetworkLines.includes("netin")}
/>
<Area
type="monotone"
dataKey="netout"
stroke="#3b82f6"
fill="#3b82f6"
fillOpacity={0.3}
strokeWidth={2}
name="Upload"
hide={hiddenNetworkLines.includes("netout")}
/>
</AreaChart>
</ResponsiveContainer>
</div>
</div>
)
}
const handleDiskLegendClick = (dataKey: string) => {
setHiddenDiskLines((prev) => {
if (prev.includes(dataKey)) {
return prev.filter((key) => key !== dataKey)
} else {
return [...prev, dataKey]
}
})
}
const handleNetworkLegendClick = (dataKey: string) => {
setHiddenNetworkLines((prev) => {
if (prev.includes(dataKey)) {
return prev.filter((key) => key !== dataKey)
} else {
return [...prev, dataKey]
}
})
}
const renderDiskLegend = (props: any) => {
const { payload } = props
return (
<div className="flex justify-center gap-6 pb-2">
{payload.map((entry: any) => (
<button
key={entry.dataKey}
onClick={() => handleDiskLegendClick(entry.dataKey)}
className={`flex items-center gap-2 cursor-pointer transition-opacity hover:opacity-100 ${
hiddenDiskLines.includes(entry.dataKey) ? "opacity-40" : "opacity-100"
}`}
>
<span className="w-3 h-3 rounded-full" style={{ backgroundColor: entry.color }} />
<span className="text-sm">{entry.value}</span>
</button>
))}
</div>
)
}
const renderNetworkLegend = (props: any) => {
const { payload } = props
return (
<div className="flex justify-center gap-6 pb-2">
{payload.map((entry: any) => (
<button
key={entry.dataKey}
onClick={() => handleNetworkLegendClick(entry.dataKey)}
className={`flex items-center gap-2 cursor-pointer transition-opacity hover:opacity-100 ${
hiddenNetworkLines.includes(entry.dataKey) ? "opacity-40" : "opacity-100"
}`}
>
<span className="w-3 h-3 rounded-full" style={{ backgroundColor: entry.color }} />
<span className="text-sm">{entry.value}</span>
</button>
))}
</div>
)
}
return (
<div className="flex flex-col h-full max-h-[90vh]">
{/* Fixed Header */}
<div className="p-6 pb-4 border-b shrink-0">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Button variant="ghost" size="icon" onClick={onBack}>
<ArrowLeft className="h-5 w-5" />
</Button>
<div>
<h2 className="text-xl font-semibold">Metrics - {vmName}</h2>
<p className="text-sm text-muted-foreground mt-1">
VMID: {vmid} Type: {vmType.toUpperCase()}
</p>
</div>
</div>
<Select value={timeframe} onValueChange={setTimeframe}>
<SelectTrigger className="w-32">
<SelectValue />
</SelectTrigger>
<SelectContent>
{TIMEFRAME_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
{/* Scrollable Content with all charts */}
<div className="flex-1 overflow-y-auto p-6 min-h-0">{renderAllCharts()}</div>
</div>
)
}

View File

@@ -0,0 +1,234 @@
"use client"
import { Card, CardContent } from "./ui/card"
import { Badge } from "./ui/badge"
import { Wifi, Zap } from 'lucide-react'
import { useState, useEffect } from "react"
import { fetchApi } from "../lib/api-config"
import { formatNetworkTraffic, getNetworkUnit } from "../lib/format-network"
interface NetworkCardProps {
interface_: {
name: string
type: string
status: string
speed: number
duplex?: string
mtu?: number
mac_address: string | null
addresses: Array<{
ip: string
netmask: string
}>
bytes_sent?: number
bytes_recv?: number
bridge_physical_interface?: string
bridge_bond_slaves?: string[]
vmid?: number
vm_name?: string
vm_type?: string
}
timeframe: "hour" | "day" | "week" | "month" | "year"
onClick?: () => void
}
const getInterfaceTypeBadge = (type: string) => {
switch (type) {
case "physical":
return { color: "bg-blue-500/10 text-blue-500 border-blue-500/20", label: "Physical" }
case "bridge":
return { color: "bg-green-500/10 text-green-500 border-green-500/20", label: "Bridge" }
case "bond":
return { color: "bg-purple-500/10 text-purple-500 border-purple-500/20", label: "Bond" }
case "vlan":
return { color: "bg-cyan-500/10 text-cyan-500 border-cyan-500/20", label: "VLAN" }
case "vm_lxc":
return { color: "bg-orange-500/10 text-orange-500 border-orange-500/20", label: "Virtual" }
case "virtual":
return { color: "bg-orange-500/10 text-orange-500 border-orange-500/20", label: "Virtual" }
default:
return { color: "bg-gray-500/10 text-gray-500 border-gray-500/20", label: "Unknown" }
}
}
const getVMTypeBadge = (vmType: string | undefined) => {
if (vmType === "lxc") {
return { color: "bg-cyan-500/10 text-cyan-500 border-cyan-500/20", label: "LXC" }
} else if (vmType === "vm") {
return { color: "bg-purple-500/10 text-purple-500 border-purple-500/20", label: "VM" }
}
return { color: "bg-gray-500/10 text-gray-500 border-gray-500/20", label: "Unknown" }
}
const formatSpeed = (speed: number): string => {
if (speed === 0) return "N/A"
if (speed >= 1000) return `${(speed / 1000).toFixed(1)} Gbps`
return `${speed} Mbps`
}
export function NetworkCard({ interface_, timeframe, onClick }: NetworkCardProps) {
const typeBadge = getInterfaceTypeBadge(interface_.type)
const vmTypeBadge = interface_.vm_type ? getVMTypeBadge(interface_.vm_type) : null
const [networkUnit, setNetworkUnit] = useState<"Bytes" | "Bits">(getNetworkUnit())
const [trafficData, setTrafficData] = useState<{ received: number; sent: number }>({
received: 0,
sent: 0,
})
useEffect(() => {
const handleUnitChange = () => {
setNetworkUnit(getNetworkUnit())
}
window.addEventListener("networkUnitChanged", handleUnitChange)
window.addEventListener("storage", handleUnitChange)
return () => {
window.removeEventListener("networkUnitChanged", handleUnitChange)
window.removeEventListener("storage", handleUnitChange)
}
}, [])
useEffect(() => {
const fetchTrafficData = async () => {
try {
const data = await fetchApi(`/api/network/${interface_.name}/metrics?timeframe=${timeframe}`)
if (data.data && data.data.length > 0) {
const lastPoint = data.data[data.data.length - 1]
const firstPoint = data.data[0]
const receivedGB = Math.max(0, (lastPoint.netin || 0) - (firstPoint.netin || 0))
const sentGB = Math.max(0, (lastPoint.netout || 0) - (firstPoint.netout || 0))
setTrafficData({
received: receivedGB,
sent: sentGB,
})
}
} catch (error) {
console.error("[v0] Failed to fetch traffic data for card:", error)
setTrafficData({ received: 0, sent: 0 })
}
}
if (interface_.status.toLowerCase() === "up" && interface_.vm_type !== "vm") {
fetchTrafficData()
const interval = setInterval(fetchTrafficData, 60000)
return () => clearInterval(interval)
}
}, [interface_.name, interface_.status, interface_.vm_type, timeframe])
const getTimeframeLabel = () => {
switch (timeframe) {
case "hour":
return "Last Hour"
case "day":
return "Last 24 Hours"
case "week":
return "Last 7 Days"
case "month":
return "Last 30 Days"
case "year":
return "Last Year"
default:
return "Last 24 Hours"
}
}
return (
<Card className="bg-card border-border hover:bg-white/5 transition-colors cursor-pointer" onClick={onClick}>
<CardContent className="p-4">
<div className="flex flex-col gap-3">
{/* First row: Icon, Name, Type Badge, Status */}
<div className="flex items-center gap-3 flex-wrap">
<Wifi className="h-5 w-5 text-muted-foreground flex-shrink-0" />
<div className="flex items-center gap-2 min-w-0 flex-1 flex-wrap">
<div className="font-medium text-foreground">{interface_.name}</div>
{vmTypeBadge ? (
<Badge variant="outline" className={vmTypeBadge.color}>
{vmTypeBadge.label}
</Badge>
) : (
<Badge variant="outline" className={typeBadge.color}>
{typeBadge.label}
</Badge>
)}
{interface_.vm_name && (
<div className="text-sm text-muted-foreground truncate"> {interface_.vm_name}</div>
)}
{interface_.type === "bridge" && interface_.bridge_physical_interface && (
<div className="text-sm text-blue-500 font-medium flex items-center gap-1 flex-wrap break-all">
{interface_.bridge_physical_interface}
</div>
)}
</div>
<Badge
variant="outline"
className={
interface_.status === "up"
? "bg-green-500/10 text-green-500 border-green-500/20"
: "bg-red-500/10 text-red-500 border-red-500/20"
}
>
{interface_.status.toUpperCase()}
</Badge>
</div>
{/* Second row: Details - Responsive layout */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
<div>
<div className="text-muted-foreground text-xs">
{interface_.type === "vm_lxc" ? "VMID" : "IP Address"}
</div>
<div className="font-medium text-foreground font-mono text-sm truncate">
{interface_.type === "vm_lxc"
? (interface_.vmid ?? "N/A")
: interface_.addresses.length > 0
? interface_.addresses[0].ip
: "N/A"}
</div>
</div>
<div>
<div className="text-muted-foreground text-xs">Speed</div>
<div className="font-medium text-foreground flex items-center gap-1 text-xs">
<Zap className="h-3 w-3" />
{formatSpeed(interface_.speed)}
</div>
</div>
<div className="col-span-2 md:col-span-1">
<div className="text-muted-foreground text-xs">{getTimeframeLabel()}</div>
<div className="font-medium text-foreground text-xs">
{interface_.status.toLowerCase() === "up" && interface_.vm_type !== "vm" ? (
<>
<span className="text-green-500"> {formatNetworkTraffic(trafficData.received * 1024 * 1024 * 1024, networkUnit)}</span>
{" / "}
<span className="text-blue-500"> {formatNetworkTraffic(trafficData.sent * 1024 * 1024 * 1024, networkUnit)}</span>
</>
) : (
<>
<span className="text-green-500"> {formatNetworkTraffic(interface_.bytes_recv || 0, networkUnit)}</span>
{" / "}
<span className="text-blue-500"> {formatNetworkTraffic(interface_.bytes_sent || 0, networkUnit)}</span>
</>
)}
</div>
</div>
{interface_.mac_address && (
<div className="col-span-2 md:col-span-1">
<div className="text-muted-foreground text-xs">MAC</div>
<div className="font-medium text-foreground font-mono text-xs truncate">{interface_.mac_address}</div>
</div>
)}
</div>
</div>
</CardContent>
</Card>
)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,330 @@
"use client"
import { useState, useEffect } from "react"
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from "recharts"
import { Loader2 } from 'lucide-react'
import { fetchApi } from "../lib/api-config"
import { getNetworkUnit } from "../lib/format-network"
interface NetworkMetricsData {
time: string
timestamp: number
netIn: number
netOut: number
}
interface NetworkTrafficChartProps {
timeframe: string
interfaceName?: string
onTotalsCalculated?: (totals: { received: number; sent: number }) => void
refreshInterval?: number // En milisegundos, por defecto 60000 (60 segundos)
networkUnit?: "Bytes" | "Bits" // Added networkUnit prop
}
const CustomNetworkTooltip = ({ active, payload, label, networkUnit }: any) => {
if (active && payload && payload.length) {
return (
<div className="bg-gray-900/95 backdrop-blur-sm border border-gray-700 rounded-lg p-3 shadow-xl">
<p className="text-sm font-semibold text-white mb-2">{label}</p>
<div className="space-y-1.5">
{payload.map((entry: any, index: number) => (
<div key={index} className="flex items-center gap-2">
<div className="w-2.5 h-2.5 rounded-full flex-shrink-0" style={{ backgroundColor: entry.color }} />
<span className="text-xs text-gray-300 min-w-[60px]">{entry.name}:</span>
<span className="text-sm font-semibold text-white">
{entry.value.toFixed(3)} {networkUnit === "Bits" ? "Gb" : "GB"}
</span>
</div>
))}
</div>
</div>
)
}
return null
}
export function NetworkTrafficChart({
timeframe,
interfaceName,
onTotalsCalculated,
refreshInterval = 60000,
networkUnit: networkUnitProp, // Rename prop to avoid conflict
}: NetworkTrafficChartProps) {
const [data, setData] = useState<NetworkMetricsData[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [isInitialLoad, setIsInitialLoad] = useState(true)
const [visibleLines, setVisibleLines] = useState({
netIn: true,
netOut: true,
})
const [networkUnit, setNetworkUnit] = useState<"Bytes" | "Bits">(
networkUnitProp || getNetworkUnit()
)
useEffect(() => {
const handleUnitChange = () => {
const newUnit = getNetworkUnit()
setNetworkUnit(newUnit)
}
window.addEventListener("networkUnitChanged", handleUnitChange)
window.addEventListener("storage", handleUnitChange)
return () => {
window.removeEventListener("networkUnitChanged", handleUnitChange)
window.removeEventListener("storage", handleUnitChange)
}
}, [])
useEffect(() => {
if (networkUnitProp) {
setNetworkUnit(networkUnitProp)
}
}, [networkUnitProp])
useEffect(() => {
setIsInitialLoad(true)
fetchMetrics()
}, [timeframe, interfaceName, networkUnit])
useEffect(() => {
if (refreshInterval > 0) {
const interval = setInterval(() => {
fetchMetrics()
}, refreshInterval)
return () => clearInterval(interval)
}
}, [timeframe, interfaceName, refreshInterval, networkUnit]) // Added networkUnit to dependencies
const fetchMetrics = async () => {
if (isInitialLoad) {
setLoading(true)
}
setError(null)
try {
const apiPath = interfaceName
? `/api/network/${interfaceName}/metrics?timeframe=${timeframe}`
: `/api/node/metrics?timeframe=${timeframe}`
console.log("[v0] Fetching network metrics from:", apiPath)
const result = await fetchApi<any>(apiPath)
if (!result.data || !Array.isArray(result.data)) {
throw new Error("Invalid data format received from server")
}
if (result.data.length === 0) {
setData([])
setLoading(false)
return
}
const transformedData = result.data.map((item: any, index: number) => {
const date = new Date(item.time * 1000)
let timeLabel = ""
if (timeframe === "hour") {
timeLabel = date.toLocaleString("en-US", {
hour: "2-digit",
minute: "2-digit",
hour12: false,
})
} else if (timeframe === "day") {
timeLabel = date.toLocaleString("en-US", {
hour: "2-digit",
minute: "2-digit",
hour12: false,
})
} else if (timeframe === "week") {
timeLabel = date.toLocaleString("en-US", {
month: "short",
day: "numeric",
hour: "2-digit",
hour12: false,
})
} else if (timeframe === "year") {
timeLabel = date.toLocaleString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
})
} else {
timeLabel = date.toLocaleString("en-US", {
month: "short",
day: "numeric",
})
}
let intervalSeconds = 60
if (index > 0) {
intervalSeconds = item.time - result.data[index - 1].time
}
const netInBytes = (item.netin || 0) * intervalSeconds
const netOutBytes = (item.netout || 0) * intervalSeconds
if (networkUnit === "Bits") {
return {
time: timeLabel,
timestamp: item.time,
netIn: Number(((netInBytes * 8) / 1024 / 1024 / 1024).toFixed(4)),
netOut: Number(((netOutBytes * 8) / 1024 / 1024 / 1024).toFixed(4)),
}
}
return {
time: timeLabel,
timestamp: item.time,
netIn: Number((netInBytes / 1024 / 1024 / 1024).toFixed(4)),
netOut: Number((netOutBytes / 1024 / 1024 / 1024).toFixed(4)),
}
})
setData(transformedData)
const totalReceivedGB = result.data.reduce((sum: number, item: any, index: number) => {
const intervalSeconds = index > 0 ? item.time - result.data[index - 1].time : 60
const netInBytes = (item.netin || 0) * intervalSeconds
return sum + (netInBytes / 1024 / 1024 / 1024)
}, 0)
const totalSentGB = result.data.reduce((sum: number, item: any, index: number) => {
const intervalSeconds = index > 0 ? item.time - result.data[index - 1].time : 60
const netOutBytes = (item.netout || 0) * intervalSeconds
return sum + (netOutBytes / 1024 / 1024 / 1024)
}, 0)
if (onTotalsCalculated) {
onTotalsCalculated({ received: totalReceivedGB, sent: totalSentGB })
}
if (isInitialLoad) {
setIsInitialLoad(false)
}
} catch (err: any) {
console.error("[v0] Error fetching network metrics:", err)
setError(err.message || "Error loading metrics")
} finally {
setLoading(false)
}
}
const tickInterval = Math.ceil(data.length / 8)
const handleLegendClick = (dataKey: string) => {
setVisibleLines((prev) => ({
...prev,
[dataKey as keyof typeof prev]: !prev[dataKey as keyof typeof prev],
}))
}
const renderLegend = (props: any) => {
const { payload } = props
return (
<div className="flex justify-center gap-4 pb-2 flex-wrap">
{payload.map((entry: any, index: number) => {
const isVisible = visibleLines[entry.dataKey as keyof typeof visibleLines]
return (
<div
key={`legend-${index}`}
className="flex items-center gap-2 cursor-pointer hover:opacity-80 transition-opacity"
onClick={() => handleLegendClick(entry.dataKey)}
style={{ opacity: isVisible ? 1 : 0.4 }}
>
<div className="w-3 h-3 rounded-sm" style={{ backgroundColor: entry.color }} />
<span className="text-sm text-foreground">{entry.value}</span>
</div>
)
})}
</div>
)
}
if (loading && isInitialLoad) {
return (
<div className="flex items-center justify-center h-[300px]">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
)
}
if (error) {
return (
<div className="flex flex-col items-center justify-center h-[300px] gap-2">
<p className="text-muted-foreground text-sm">Network metrics not available yet</p>
<p className="text-xs text-red-500">{error}</p>
</div>
)
}
if (data.length === 0) {
return (
<div className="flex items-center justify-center h-[300px]">
<p className="text-muted-foreground text-sm">No network metrics available</p>
</div>
)
}
return (
<ResponsiveContainer width="100%" height={300}>
<AreaChart data={data} margin={{ bottom: 80 }}>
<CartesianGrid strokeDasharray="3 3" stroke="currentColor" className="text-border" />
<XAxis
dataKey="time"
stroke="currentColor"
className="text-foreground"
tick={{ fill: "currentColor", fontSize: 12 }}
angle={-45}
textAnchor="end"
height={60}
interval={tickInterval}
/>
<YAxis
stroke="currentColor"
className="text-foreground"
tick={{ fill: "currentColor", fontSize: 12 }}
label={{
value: networkUnit === "Bits" ? "Gb" : "GB", // Dynamic label based on unit
angle: -90,
position: "insideLeft",
fill: "currentColor",
}}
domain={[0, "auto"]}
/>
<Tooltip content={<CustomNetworkTooltip networkUnit={networkUnit} />} /> // Pass networkUnit to tooltip
<Legend verticalAlign="top" height={36} content={renderLegend} />
<Area
type="monotone"
dataKey="netIn"
stroke="#10b981"
strokeWidth={2}
fill="#10b981"
fillOpacity={0.3}
name="Received"
hide={!visibleLines.netIn}
isAnimationActive={true}
animationDuration={300}
animationEasing="ease-in-out"
/>
<Area
type="monotone"
dataKey="netOut"
stroke="#3b82f6"
strokeWidth={2}
fill="#3b82f6"
fillOpacity={0.3}
name="Sent"
hide={!visibleLines.netOut}
isAnimationActive={true}
animationDuration={300}
animationEasing="ease-in-out"
/>
</AreaChart>
</ResponsiveContainer>
)
}

View File

@@ -0,0 +1,473 @@
"use client"
import { useState, useEffect } from "react"
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from "recharts"
import { Loader2, TrendingUp, MemoryStick } from "lucide-react"
import { useIsMobile } from "../hooks/use-mobile"
import { fetchApi } from "@/lib/api-config"
const TIMEFRAME_OPTIONS = [
{ value: "hour", label: "1 Hour" },
{ value: "day", label: "24 Hours" },
{ value: "week", label: "7 Days" },
{ value: "month", label: "30 Days" },
]
interface NodeMetricsData {
time: string
timestamp: number
cpu: number
load: number
memoryTotal: number
memoryUsed: number
memoryFree: number
memoryZfsArc: number
}
const CustomCpuTooltip = ({ active, payload, label }: any) => {
if (active && payload && payload.length) {
return (
<div className="bg-gray-900/95 backdrop-blur-sm border border-gray-700 rounded-lg p-3 shadow-xl">
<p className="text-sm font-semibold text-white mb-2">{label}</p>
<div className="space-y-1.5">
{payload.map((entry: any, index: number) => (
<div key={index} className="flex items-center gap-2">
<div className="w-2.5 h-2.5 rounded-full flex-shrink-0" style={{ backgroundColor: entry.color }} />
<span className="text-xs text-gray-300 min-w-[60px]">{entry.name}:</span>
<span className="text-sm font-semibold text-white">{entry.value}</span>
</div>
))}
</div>
</div>
)
}
return null
}
const CustomMemoryTooltip = ({ active, payload, label }: any) => {
if (active && payload && payload.length) {
return (
<div className="bg-gray-900/95 backdrop-blur-sm border border-gray-700 rounded-lg p-3 shadow-xl">
<p className="text-sm font-semibold text-white mb-2">{label}</p>
<div className="space-y-1.5">
{payload.map((entry: any, index: number) => (
<div key={index} className="flex items-center gap-2">
<div className="w-2.5 h-2.5 rounded-full flex-shrink-0" style={{ backgroundColor: entry.color }} />
<span className="text-xs text-gray-300 min-w-[60px]">{entry.name}:</span>
<span className="text-sm font-semibold text-white">{entry.value} GB</span>
</div>
))}
</div>
</div>
)
}
return null
}
export function NodeMetricsCharts() {
const [timeframe, setTimeframe] = useState("day")
const [data, setData] = useState<NodeMetricsData[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const isMobile = useIsMobile()
const [visibleLines, setVisibleLines] = useState({
cpu: { cpu: true, load: true },
memory: { memoryTotal: true, memoryUsed: true, memoryZfsArc: true, memoryFree: true },
})
// Check if ZFS ARC or Free memory have any non-zero values to decide if we should show them
const hasZfsArc = data.some(d => d.memoryZfsArc > 0)
const hasMemoryFree = data.some(d => d.memoryFree > 0)
useEffect(() => {
console.log("[v0] NodeMetricsCharts component mounted")
fetchMetrics()
}, [timeframe])
const fetchMetrics = async () => {
console.log("[v0] fetchMetrics called with timeframe:", timeframe)
setLoading(true)
setError(null)
try {
const result = await fetchApi<any>(`/api/node/metrics?timeframe=${timeframe}`)
console.log("[v0] Node metrics result:", result)
console.log("[v0] Result keys:", Object.keys(result))
console.log("[v0] Data array length:", result.data?.length || 0)
if (!result.data || !Array.isArray(result.data)) {
console.error("[v0] Invalid data format - data is not an array:", result)
throw new Error("Invalid data format received from server")
}
if (result.data.length === 0) {
console.warn("[v0] No data points received")
setData([])
setLoading(false)
return
}
console.log("[v0] First data point sample:", result.data[0])
console.log("[v0] First data point loadavg field:", result.data[0]?.loadavg)
console.log("[v0] loadavg type:", typeof result.data[0]?.loadavg)
console.log("[v0] loadavg is array:", Array.isArray(result.data[0]?.loadavg))
if (result.data[0]?.loadavg) {
console.log("[v0] loadavg length:", result.data[0].loadavg.length)
console.log("[v0] loadavg[0]:", result.data[0].loadavg[0])
}
const transformedData = result.data.map((item: any) => {
const date = new Date(item.time * 1000)
let timeLabel = ""
if (timeframe === "hour") {
timeLabel = date.toLocaleString("en-US", {
hour: "2-digit",
minute: "2-digit",
hour12: false,
})
} else if (timeframe === "day") {
timeLabel = date.toLocaleString("en-US", {
hour: "2-digit",
minute: "2-digit",
hour12: false,
})
} else if (timeframe === "week") {
timeLabel = date.toLocaleString("en-US", {
month: "short",
day: "numeric",
hour: "2-digit",
hour12: false,
})
} else {
timeLabel = date.toLocaleString("en-US", {
month: "short",
day: "numeric",
})
}
return {
time: timeLabel,
timestamp: item.time,
cpu: item.cpu ? Number((item.cpu * 100).toFixed(2)) : 0,
load: item.loadavg
? typeof item.loadavg === "number"
? Number(item.loadavg.toFixed(2))
: Array.isArray(item.loadavg) && item.loadavg.length > 0
? Number(item.loadavg[0].toFixed(2))
: 0
: 0,
memoryTotal: item.memtotal ? Number((item.memtotal / 1024 / 1024 / 1024).toFixed(2)) : 0,
memoryUsed: item.memused ? Number((item.memused / 1024 / 1024 / 1024).toFixed(2)) : 0,
memoryFree: item.memfree ? Number((item.memfree / 1024 / 1024 / 1024).toFixed(2)) : 0,
memoryZfsArc: item.zfsarc ? Number((item.zfsarc / 1024 / 1024 / 1024).toFixed(2)) : 0,
}
})
setData(transformedData)
} catch (err: any) {
console.error("[v0] Error fetching node metrics:", err)
console.error("[v0] Error message:", err.message)
console.error("[v0] Error stack:", err.stack)
setError(err.message || "Error loading metrics")
} finally {
console.log("[v0] fetchMetrics finally block - setting loading to false")
setLoading(false)
}
}
const tickInterval = Math.ceil(data.length / 8)
const handleLegendClick = (chartType: "cpu" | "memory", dataKey: string) => {
setVisibleLines((prev) => ({
...prev,
[chartType]: {
...prev[chartType],
[dataKey as keyof (typeof prev)[typeof chartType]]:
!prev[chartType][dataKey as keyof (typeof prev)[typeof chartType]],
},
}))
}
const renderLegend = (chartType: "cpu" | "memory") => (props: any) => {
const { payload } = props
return (
<div className="flex justify-center gap-4 pb-2 flex-wrap">
{payload.map((entry: any, index: number) => {
// For memory chart, hide ZFS ARC and Free from legend if they have no data
if (chartType === "memory") {
if (entry.dataKey === "memoryZfsArc" && !hasZfsArc) return null
if (entry.dataKey === "memoryFree" && !hasMemoryFree) return null
}
const isVisible = visibleLines[chartType][entry.dataKey as keyof (typeof visibleLines)[typeof chartType]]
return (
<div
key={`legend-${index}`}
className="flex items-center gap-2 cursor-pointer hover:opacity-80 transition-opacity"
onClick={() => handleLegendClick(chartType, entry.dataKey)}
style={{ opacity: isVisible ? 1 : 0.4 }}
>
<div className="w-3 h-3 rounded-sm" style={{ backgroundColor: entry.color }} />
<span className="text-sm text-foreground">{entry.value}</span>
</div>
)
})}
</div>
)
}
console.log("[v0] Render state - loading:", loading, "error:", error, "data length:", data.length)
if (loading) {
console.log("[v0] Rendering loading state")
return (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card className="bg-card border-border">
<CardContent className="p-6">
<div className="flex items-center justify-center h-[300px]">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
</CardContent>
</Card>
<Card className="bg-card border-border">
<CardContent className="p-6">
<div className="flex items-center justify-center h-[300px]">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
</CardContent>
</Card>
</div>
)
}
if (error) {
console.log("[v0] Rendering error state:", error)
return (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card className="bg-card border-border">
<CardContent className="p-6">
<div className="flex flex-col items-center justify-center h-[300px] gap-2">
<p className="text-muted-foreground text-sm">Metrics data not available yet</p>
<p className="text-xs text-red-500">{error}</p>
</div>
</CardContent>
</Card>
<Card className="bg-card border-border">
<CardContent className="p-6">
<div className="flex flex-col items-center justify-center h-[300px] gap-2">
<p className="text-muted-foreground text-sm">Metrics data not available yet</p>
<p className="text-xs text-red-500">{error}</p>
</div>
</CardContent>
</Card>
</div>
)
}
if (data.length === 0) {
console.log("[v0] Rendering no data state")
return (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card className="bg-card border-border">
<CardContent className="p-6">
<div className="flex items-center justify-center h-[300px]">
<p className="text-muted-foreground text-sm">No metrics data available</p>
</div>
</CardContent>
</Card>
<Card className="bg-card border-border">
<CardContent className="p-6">
<div className="flex items-center justify-center h-[300px]">
<p className="text-muted-foreground text-sm">No metrics data available</p>
</div>
</CardContent>
</Card>
</div>
)
}
console.log("[v0] Rendering charts with", data.length, "data points")
return (
<div className="space-y-6">
{/* Timeframe Selector */}
<div className="flex justify-end">
<Select value={timeframe} onValueChange={setTimeframe}>
<SelectTrigger className="w-32">
<SelectValue />
</SelectTrigger>
<SelectContent>
{TIMEFRAME_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* Charts Grid */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* CPU Usage + Load Average Chart */}
<Card className="bg-card border-border">
<CardHeader className="px-4 md:px-6">
<CardTitle className="text-foreground flex items-center">
<TrendingUp className="h-5 w-5 mr-2" />
CPU Usage & Load Average
</CardTitle>
</CardHeader>
<CardContent className="px-0 md:px-6">
<ResponsiveContainer width="100%" height={300}>
<AreaChart data={data} margin={{ bottom: 60, left: 0, right: 0 }}>
<CartesianGrid strokeDasharray="3 3" stroke="currentColor" className="text-border" />
<XAxis
dataKey="time"
stroke="currentColor"
className="text-foreground"
tick={{ fill: "currentColor", fontSize: 12 }}
angle={-45}
textAnchor="end"
height={60}
interval={tickInterval}
/>
<YAxis
yAxisId="left"
stroke="currentColor"
className="text-foreground"
tick={{ fill: "currentColor", fontSize: 12 }}
label={
isMobile ? undefined : { value: "CPU %", angle: -90, position: "insideLeft", fill: "currentColor" }
}
domain={[0, "dataMax"]}
/>
<YAxis
yAxisId="right"
orientation="right"
stroke="currentColor"
className="text-foreground"
tick={{ fill: "currentColor", fontSize: 12 }}
label={
isMobile ? undefined : { value: "Load", angle: 90, position: "insideRight", fill: "currentColor" }
}
domain={[0, "dataMax"]}
/>
<Tooltip content={<CustomCpuTooltip />} />
<Legend verticalAlign="top" height={36} content={renderLegend("cpu")} />
<Area
yAxisId="left"
type="monotone"
dataKey="cpu"
stroke="#3b82f6"
strokeWidth={2}
fill="#3b82f6"
fillOpacity={0.3}
name="CPU %"
hide={!visibleLines.cpu.cpu}
/>
<Area
yAxisId="right"
type="monotone"
dataKey="load"
stroke="#10b981"
strokeWidth={2}
fill="#10b981"
fillOpacity={0.3}
name="Load Avg"
hide={!visibleLines.cpu.load}
/>
</AreaChart>
</ResponsiveContainer>
</CardContent>
</Card>
{/* Memory Usage Chart */}
<Card className="bg-card border-border">
<CardHeader className="px-4 md:px-6">
<CardTitle className="text-foreground flex items-center">
<MemoryStick className="h-5 w-5 mr-2" />
Memory Usage
</CardTitle>
</CardHeader>
<CardContent className="px-0 pr-2 md:px-6">
<ResponsiveContainer width="100%" height={300}>
<AreaChart data={data} margin={{ bottom: 60, left: 0, right: 0 }}>
<CartesianGrid strokeDasharray="3 3" stroke="currentColor" className="text-border" />
<XAxis
dataKey="time"
stroke="currentColor"
className="text-foreground"
tick={{ fill: "currentColor", fontSize: 12 }}
angle={-45}
textAnchor="end"
height={60}
interval={tickInterval}
/>
<YAxis
stroke="currentColor"
className="text-foreground"
tick={{ fill: "currentColor", fontSize: 12 }}
label={
isMobile ? undefined : { value: "GB", angle: -90, position: "insideLeft", fill: "currentColor" }
}
domain={[0, "dataMax"]}
/>
<Tooltip content={<CustomMemoryTooltip />} />
<Legend verticalAlign="top" height={36} content={renderLegend("memory")} />
<Area
type="monotone"
dataKey="memoryTotal"
stroke="#3b82f6"
strokeWidth={2}
fill="#3b82f6"
fillOpacity={0.1}
name="Total"
hide={!visibleLines.memory.memoryTotal}
/>
<Area
type="monotone"
dataKey="memoryUsed"
stroke="#10b981"
strokeWidth={2}
fill="#10b981"
fillOpacity={0.3}
name="Used"
hide={!visibleLines.memory.memoryUsed}
/>
{/* Only show ZFS ARC if there's data */}
{hasZfsArc && (
<Area
type="monotone"
dataKey="memoryZfsArc"
stroke="#f59e0b"
strokeWidth={2}
fill="#f59e0b"
fillOpacity={0.3}
name="ZFS ARC"
hide={!visibleLines.memory.memoryZfsArc}
/>
)}
{/* Only show Free memory if there's data */}
{hasMemoryFree && (
<Area
type="monotone"
dataKey="memoryFree"
stroke="#06b6d4"
strokeWidth={2}
fill="#06b6d4"
fillOpacity={0.3}
name="Free"
hide={!visibleLines.memory.memoryFree}
/>
)}
</AreaChart>
</ResponsiveContainer>
</CardContent>
</Card>
</div>
</div>
)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,290 @@
"use client"
import type React from "react"
import { useState, useEffect } from "react"
import { Button } from "./ui/button"
import { Dialog, DialogContent, DialogTitle } from "./ui/dialog"
import {
ChevronLeft,
ChevronRight,
X,
Sparkles,
LayoutDashboard,
HardDrive,
Network,
Box,
Cpu,
FileText,
Rocket,
} from "lucide-react"
import Image from "next/image"
import { Checkbox } from "./ui/checkbox"
interface OnboardingSlide {
id: number
title: string
description: string
image?: string
icon: React.ReactNode
gradient: string
}
const slides: OnboardingSlide[] = [
{
id: 0,
title: "Welcome to ProxMenux Monitor!",
description:
"Your new monitoring tool for Proxmox. Discover all the features that will help you manage and supervise your infrastructure efficiently.",
icon: <Sparkles className="h-16 w-16" />,
gradient: "from-blue-500 via-purple-500 to-pink-500",
},
{
id: 1,
title: "System Overview",
description:
"Monitor your server's status in real-time: CPU, memory, temperature, system load and more. Everything in an intuitive and easy-to-understand dashboard.",
image: "/images/onboarding/imagen1.png",
icon: <LayoutDashboard className="h-12 w-12" />,
gradient: "from-blue-500 to-cyan-500",
},
{
id: 2,
title: "Storage Management",
description:
"Visualize the status of all your disks and volumes. Detailed information on capacity, usage, SMART health, temperature and performance of each storage device.",
image: "/images/onboarding/imagen2.png",
icon: <HardDrive className="h-12 w-12" />,
gradient: "from-cyan-500 to-teal-500",
},
{
id: 3,
title: "Network Metrics",
description:
"Monitor network traffic in real-time. Bandwidth statistics, active interfaces, transfer speeds and historical usage graphs.",
image: "/images/onboarding/imagen3.png",
icon: <Network className="h-12 w-12" />,
gradient: "from-teal-500 to-green-500",
},
{
id: 4,
title: "Virtual Machines & Containers",
description:
"Manage all your VMs and LXC containers from one place. Status, allocated resources, current usage and quick controls for each virtual machine.",
image: "/images/onboarding/imagen4.png",
icon: <Box className="h-12 w-12" />,
gradient: "from-green-500 to-emerald-500",
},
{
id: 5,
title: "Hardware Information",
description:
"Complete details of your server hardware: CPU, RAM, GPU, disks, network, UPS and more. Technical specifications, models, serial numbers and status of each component.",
image: "/images/onboarding/imagen5.png",
icon: <Cpu className="h-12 w-12" />,
gradient: "from-emerald-500 to-blue-500",
},
{
id: 6,
title: "System Logs",
description:
"Access system logs in real-time. Filter by event type, search for specific errors and keep complete track of your server activity. Download the displayed logs for further analysis.",
image: "/images/onboarding/imagen6.png",
icon: <FileText className="h-12 w-12" />,
gradient: "from-blue-500 to-indigo-500",
},
{
id: 7,
title: "Ready for the Future!",
description:
"ProxMenux Monitor is prepared to receive updates and improvements that will be added gradually, improving the user experience and being able to execute ProxMenux functions from the web panel.",
icon: <Rocket className="h-16 w-16" />,
gradient: "from-indigo-500 via-purple-500 to-pink-500",
},
]
export function OnboardingCarousel() {
const [open, setOpen] = useState(false)
const [currentSlide, setCurrentSlide] = useState(0)
const [direction, setDirection] = useState<"next" | "prev">("next")
const [dontShowAgain, setDontShowAgain] = useState(false)
useEffect(() => {
const hasSeenOnboarding = localStorage.getItem("proxmenux-onboarding-seen")
if (!hasSeenOnboarding) {
setOpen(true)
}
}, [])
const handleNext = () => {
if (currentSlide < slides.length - 1) {
setDirection("next")
setCurrentSlide(currentSlide + 1)
} else {
if (dontShowAgain) {
localStorage.setItem("proxmenux-onboarding-seen", "true")
}
setOpen(false)
}
}
const handlePrev = () => {
if (currentSlide > 0) {
setDirection("prev")
setCurrentSlide(currentSlide - 1)
}
}
const handleSkip = () => {
if (dontShowAgain) {
localStorage.setItem("proxmenux-onboarding-seen", "true")
}
setOpen(false)
}
const handleClose = () => {
if (dontShowAgain) {
localStorage.setItem("proxmenux-onboarding-seen", "true")
}
setOpen(false)
}
const handleDotClick = (index: number) => {
setDirection(index > currentSlide ? "next" : "prev")
setCurrentSlide(index)
}
const slide = slides[currentSlide]
return (
<Dialog open={open} onOpenChange={handleClose}>
<DialogContent className="max-w-4xl p-0 gap-0 overflow-hidden border-0 bg-transparent">
<DialogTitle className="sr-only">ProxMenux Onboarding</DialogTitle>
<div className="relative bg-card rounded-lg overflow-hidden shadow-2xl">
<Button
variant="ghost"
size="icon"
className="absolute top-4 right-4 z-50 h-8 w-8 rounded-full bg-background/80 backdrop-blur-sm hover:bg-background"
onClick={handleClose}
>
<X className="h-4 w-4" />
</Button>
<div
className={`relative h-48 md:h-64 bg-gradient-to-br ${slide.gradient} flex items-center justify-center overflow-hidden`}
>
<div className="absolute inset-0 bg-black/10" />
<div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_120%,rgba(255,255,255,0.1),transparent)]" />
<div className="relative z-10 text-white">
{slide.image ? (
<div className="relative w-full h-36 md:h-48 flex items-center justify-center px-4">
<Image
src={slide.image || "/placeholder.svg"}
alt={slide.title}
width={600}
height={400}
className="rounded-lg shadow-2xl object-cover max-h-36 md:max-h-48"
onError={(e) => {
const target = e.target as HTMLImageElement
target.style.display = "none"
const fallback = target.parentElement?.querySelector(".fallback-icon")
if (fallback) {
fallback.classList.remove("hidden")
}
}}
/>
<div className="fallback-icon hidden">{slide.icon}</div>
</div>
) : (
<div className="animate-pulse">{slide.icon}</div>
)}
</div>
<div className="absolute top-10 left-10 w-20 h-20 bg-white/10 rounded-full blur-2xl" />
<div className="absolute bottom-10 right-10 w-32 h-32 bg-white/10 rounded-full blur-3xl" />
</div>
<div className="p-4 md:p-8 space-y-3 md:space-y-6 max-h-[60vh] md:max-h-none overflow-y-auto">
<div className="space-y-2 md:space-y-3">
<h2 className="text-xl md:text-3xl font-bold text-foreground text-balance">{slide.title}</h2>
<p className="text-sm md:text-lg text-muted-foreground leading-relaxed text-pretty">
{slide.description}
</p>
</div>
<div className="flex items-center justify-center gap-2 py-2 md:py-4">
{slides.map((_, index) => (
<button
key={index}
onClick={() => handleDotClick(index)}
className={`transition-all duration-300 rounded-full ${
index === currentSlide
? "w-8 h-2.5 bg-blue-500 shadow-lg shadow-blue-500/50"
: "w-2.5 h-2.5 bg-muted-foreground/60 hover:bg-muted-foreground/80 border border-muted-foreground/40"
}`}
aria-label={`Go to slide ${index + 1}`}
/>
))}
</div>
<div className="flex flex-col sm:flex-row items-center justify-between gap-2 md:gap-4">
<Button
variant="ghost"
onClick={handlePrev}
disabled={currentSlide === 0}
className="gap-2 w-full sm:w-auto text-sm"
>
<ChevronLeft className="h-4 w-4" />
Previous
</Button>
<div className="flex gap-2 w-full sm:w-auto">
{currentSlide < slides.length - 1 ? (
<>
<Button
variant="outline"
onClick={handleSkip}
className="flex-1 sm:flex-none bg-transparent text-sm"
>
Skip
</Button>
<Button
onClick={handleNext}
className="gap-2 bg-blue-500 hover:bg-blue-600 flex-1 sm:flex-none text-sm"
>
Next
<ChevronRight className="h-4 w-4" />
</Button>
</>
) : (
<Button
onClick={handleNext}
className="gap-2 bg-gradient-to-r from-blue-500 to-purple-500 hover:from-blue-600 hover:to-purple-600 w-full sm:w-auto text-sm"
>
Get Started!
<Sparkles className="h-4 w-4" />
</Button>
)}
</div>
</div>
<div className="flex items-center justify-center gap-2 pt-2 pb-1">
<Checkbox
id="dont-show-again"
checked={dontShowAgain}
onCheckedChange={(checked) => setDontShowAgain(checked as boolean)}
/>
<label
htmlFor="dont-show-again"
className="text-xs md:text-sm text-muted-foreground hover:text-foreground transition-colors cursor-pointer select-none"
>
Don't show this again
</label>
</div>
</div>
</div>
</DialogContent>
</Dialog>
)
}

View File

@@ -0,0 +1,805 @@
"use client"
import { useState, useEffect, useMemo, useCallback } from "react"
import { Badge } from "./ui/badge"
import { Button } from "./ui/button"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs"
import { SystemOverview } from "./system-overview"
import { StorageOverview } from "./storage-overview"
import { NetworkMetrics } from "./network-metrics"
import { VirtualMachines } from "./virtual-machines"
import Hardware from "./hardware"
import { SystemLogs } from "./system-logs"
import { Settings } from "./settings"
import { Security } from "./security"
import { OnboardingCarousel } from "./onboarding-carousel"
import { HealthStatusModal } from "./health-status-modal"
import { ReleaseNotesModal, useVersionCheck } from "./release-notes-modal"
import { getApiUrl, fetchApi } from "../lib/api-config"
import { TerminalPanel } from "./terminal-panel"
import {
RefreshCw,
AlertTriangle,
CheckCircle,
XCircle,
Server,
Menu,
LayoutDashboard,
HardDrive,
NetworkIcon,
Box,
Cpu,
FileText,
SettingsIcon,
Terminal,
ShieldCheck,
Info,
} from "lucide-react"
import Image from "next/image"
import { ThemeToggle } from "./theme-toggle"
import { Sheet, SheetContent, SheetTrigger } from "./ui/sheet"
interface SystemStatus {
status: "healthy" | "warning" | "critical"
uptime: string
lastUpdate: string
serverName: string
nodeId: string
}
interface FlaskSystemData {
hostname: string
node_id: string
uptime: string
cpu_usage: number
memory_usage: number
temperature: number
load_average: number[]
}
interface FlaskSystemInfo {
hostname: string
node_id: string
uptime: string
health: {
status: "healthy" | "warning" | "critical"
}
}
export function ProxmoxDashboard() {
const [systemStatus, setSystemStatus] = useState<SystemStatus>({
status: "healthy",
uptime: "Loading...",
lastUpdate: new Date().toLocaleTimeString("en-US", { hour12: false }),
serverName: "Loading...",
nodeId: "Loading...",
})
const [isRefreshing, setIsRefreshing] = useState(false)
const [isServerConnected, setIsServerConnected] = useState(true)
const [componentKey, setComponentKey] = useState(0)
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
const [activeTab, setActiveTab] = useState("overview")
const [infoCount, setInfoCount] = useState(0)
const [updateAvailable, setUpdateAvailable] = useState(false)
const [showNavigation, setShowNavigation] = useState(true)
const [lastScrollY, setLastScrollY] = useState(0)
const [showHealthModal, setShowHealthModal] = useState(false)
const { showReleaseNotes, setShowReleaseNotes } = useVersionCheck()
// Category keys for health info count calculation
const HEALTH_CATEGORY_KEYS = [
{ key: "cpu", category: "temperature" },
{ key: "memory", category: "memory" },
{ key: "storage", category: "storage" },
{ key: "disks", category: "disks" },
{ key: "network", category: "network" },
{ key: "vms", category: "vms" },
{ key: "services", category: "pve_services" },
{ key: "logs", category: "logs" },
{ key: "updates", category: "updates" },
{ key: "security", category: "security" },
]
// Fetch ProxMenux update status
const fetchUpdateStatus = useCallback(async () => {
try {
const response = await fetchApi("/api/proxmenux/update-status")
if (response?.success && response?.update_available) {
const { stable, beta } = response.update_available
setUpdateAvailable(stable || beta)
}
} catch (error) {
// Silently fail - updateAvailable will remain false
}
}, [])
// Fetch health info count independently (for initial load and refresh)
const fetchHealthInfoCount = useCallback(async () => {
try {
const response = await fetchApi("/api/health/full")
let calculatedInfoCount = 0
if (response && response.health?.details) {
// Get categories that have dismissed items (these become INFO)
const customCats = new Set((response.custom_suppressions || []).map((cs: { category: string }) => cs.category))
const filteredDismissed = (response.dismissed || []).filter((item: { category: string }) => !customCats.has(item.category))
const categoriesWithDismissed = new Set<string>()
filteredDismissed.forEach((item: { category: string }) => {
const catMeta = HEALTH_CATEGORY_KEYS.find(c => c.category === item.category || c.key === item.category)
if (catMeta) {
categoriesWithDismissed.add(catMeta.key)
}
})
// Count effective INFO categories (original INFO + OK categories with dismissed)
HEALTH_CATEGORY_KEYS.forEach(({ key }) => {
const cat = response.health.details[key as keyof typeof response.health.details]
if (cat) {
const originalStatus = cat.status?.toUpperCase()
// Count as INFO if: originally INFO OR (originally OK and has dismissed items)
if (originalStatus === "INFO" || (originalStatus === "OK" && categoriesWithDismissed.has(key))) {
calculatedInfoCount++
}
}
})
}
setInfoCount(calculatedInfoCount)
} catch (error) {
// Silently fail - infoCount will remain at 0
}
}, [])
const fetchSystemData = useCallback(async () => {
try {
const data: FlaskSystemInfo = await fetchApi("/api/system-info")
const uptimeValue =
data.uptime && typeof data.uptime === "string" && data.uptime.trim() !== "" ? data.uptime : "N/A"
const backendStatus = data.health?.status?.toUpperCase() || "OK"
let healthStatus: "healthy" | "warning" | "critical"
if (backendStatus === "CRITICAL") {
healthStatus = "critical"
} else if (backendStatus === "WARNING") {
healthStatus = "warning"
} else {
healthStatus = "healthy"
}
setSystemStatus({
status: healthStatus,
uptime: uptimeValue,
lastUpdate: new Date().toLocaleTimeString("en-US", { hour12: false }),
serverName: data.hostname || "Unknown",
nodeId: data.node_id || "Unknown",
})
setIsServerConnected(true)
} catch (error) {
// Expected to fail in v0 preview (no Flask server)
setIsServerConnected(false)
setSystemStatus((prev) => ({
...prev,
status: "critical",
serverName: "Server Offline",
nodeId: "Server Offline",
uptime: "N/A",
lastUpdate: new Date().toLocaleTimeString("en-US", { hour12: false }),
}))
}
}, [])
useEffect(() => {
// Siempre fetch inicial
fetchSystemData()
fetchHealthInfoCount()
fetchUpdateStatus()
// En overview: cada 30 segundos para actualización frecuente del estado de salud
// En otras tabs: cada 60 segundos para reducir carga
let interval: ReturnType<typeof setInterval> | null = null
let healthInterval: ReturnType<typeof setInterval> | null = null
if (activeTab === "overview") {
interval = setInterval(fetchSystemData, 30000) // 30 segundos
healthInterval = setInterval(fetchHealthInfoCount, 30000) // Also refresh info count
} else {
interval = setInterval(fetchSystemData, 60000) // 60 segundos
healthInterval = setInterval(fetchHealthInfoCount, 60000) // Also refresh info count
}
return () => {
if (interval) clearInterval(interval)
if (healthInterval) clearInterval(healthInterval)
}
}, [fetchSystemData, fetchHealthInfoCount, fetchUpdateStatus, activeTab])
useEffect(() => {
const handleChangeTab = (event: CustomEvent) => {
const { tab } = event.detail
if (tab) {
setActiveTab(tab)
}
}
window.addEventListener("changeTab", handleChangeTab as EventListener)
return () => {
window.removeEventListener("changeTab", handleChangeTab as EventListener)
}
}, [])
// Auto-refresh terminal on mobile devices
// This fixes the issue where terminal doesn't connect properly on mobile/VPN
useEffect(() => {
if (activeTab === "terminal") {
const isMobileDevice = window.innerWidth < 768 ||
('ontouchstart' in window && navigator.maxTouchPoints > 0)
if (isMobileDevice) {
// Delay to allow initial connection attempt, then refresh to ensure proper connection
const timeoutId = setTimeout(() => {
setComponentKey(prev => prev + 1)
}, 500)
return () => clearTimeout(timeoutId)
}
}
}, [activeTab])
useEffect(() => {
const handleHealthStatusUpdate = (event: CustomEvent) => {
const { status, infoCount: newInfoCount } = event.detail
let healthStatus: "healthy" | "warning" | "critical"
if (status === "CRITICAL") {
healthStatus = "critical"
} else if (status === "WARNING") {
healthStatus = "warning"
} else {
healthStatus = "healthy"
}
setSystemStatus((prev) => ({
...prev,
status: healthStatus,
}))
// Update info count (INFO categories + dismissed items)
if (typeof newInfoCount === "number") {
setInfoCount(newInfoCount)
}
}
window.addEventListener("healthStatusUpdated", handleHealthStatusUpdate as EventListener)
return () => {
window.removeEventListener("healthStatusUpdated", handleHealthStatusUpdate as EventListener)
}
}, [])
useEffect(() => {
if (
systemStatus.serverName &&
systemStatus.serverName !== "Loading..." &&
systemStatus.serverName !== "Server Offline"
) {
document.title = `${systemStatus.serverName} - ProxMenux Monitor`
} else {
document.title = "ProxMenux Monitor"
}
}, [systemStatus.serverName])
useEffect(() => {
let hideTimeout: ReturnType<typeof setTimeout> | null = null
let lastPosition = window.scrollY
const handleScroll = () => {
const currentScrollY = window.scrollY
const delta = currentScrollY - lastPosition
if (currentScrollY < 50) {
setShowNavigation(true)
} else if (delta > 2) {
if (hideTimeout) clearTimeout(hideTimeout)
hideTimeout = setTimeout(() => setShowNavigation(false), 20)
} else if (delta < -2) {
if (hideTimeout) clearTimeout(hideTimeout)
setShowNavigation(true)
}
lastPosition = currentScrollY
}
window.addEventListener("scroll", handleScroll, { passive: true })
return () => {
window.removeEventListener("scroll", handleScroll)
if (hideTimeout) clearTimeout(hideTimeout)
}
}, [])
const refreshData = async () => {
setIsRefreshing(true)
await fetchSystemData()
setComponentKey((prev) => prev + 1)
await new Promise((resolve) => setTimeout(resolve, 500))
setIsRefreshing(false)
}
const statusIcon = useMemo(() => {
switch (systemStatus.status) {
case "healthy":
return <CheckCircle className="h-4 w-4 text-green-500" />
case "warning":
return <AlertTriangle className="h-4 w-4 text-yellow-500" />
case "critical":
return <XCircle className="h-4 w-4 text-red-500" />
}
}, [systemStatus.status])
const statusColor = useMemo(() => {
switch (systemStatus.status) {
case "healthy":
return "bg-green-500/10 text-green-500 border-green-500/20"
case "warning":
return "bg-yellow-500/10 text-yellow-500 border-yellow-500/20"
case "critical":
return "bg-red-500/10 text-red-500 border-red-500/20"
}
}, [systemStatus.status])
const getActiveTabLabel = () => {
switch (activeTab) {
case "overview":
return "Overview"
case "storage":
return "Storage"
case "network":
return "Network"
case "vms":
return "VMs & LXCs"
case "hardware":
return "Hardware"
case "terminal":
return "Terminal"
case "logs":
return "System Logs"
case "security":
return "Security"
case "settings":
return "Settings"
default:
return "Navigation Menu"
}
}
return (
<div className="min-h-screen bg-background">
<OnboardingCarousel />
<ReleaseNotesModal open={showReleaseNotes} onClose={() => setShowReleaseNotes(false)} />
{!isServerConnected && (
<div className="bg-red-500/10 border-b border-red-500/20 px-6 py-3">
<div className="container mx-auto">
<div className="flex items-center space-x-2 text-red-500 mb-2">
<XCircle className="h-5 w-5" />
<span className="font-medium">ProxMenux Server Connection Failed</span>
</div>
<div className="text-sm text-red-500/80 space-y-1 ml-7">
<p> Check that the monitor.service is running correctly.</p>
<p> The ProxMenux server should start automatically on port 8008</p>
<p>
Try accessing:{" "}
<a href={getApiUrl("/api/health")} target="_blank" rel="noopener noreferrer" className="underline">
{getApiUrl("/api/health")}
</a>
</p>
</div>
</div>
</div>
)}
<header
className="border-b border-border bg-card sticky top-0 z-50 shadow-sm cursor-pointer hover:bg-accent/5 transition-colors"
onClick={() => setShowHealthModal(true)}
>
<div className="container mx-auto px-4 md:px-6 py-4 md:py-4">
{/* Logo and Title */}
<div className="flex items-start justify-between gap-3">
{/* Logo and Title */}
<div className="flex items-center space-x-2 md:space-x-3 min-w-0">
<div className="w-16 h-16 md:w-10 md:h-10 relative flex items-center justify-center bg-primary/10 flex-shrink-0">
<Image
src={updateAvailable ? "/images/proxmenux_update-logo.png" : "/images/proxmenux-logo.png"}
alt="ProxMenux Logo"
width={64}
height={64}
className="object-contain md:w-10 md:h-10"
priority
onError={(e) => {
const target = e.target as HTMLImageElement
target.style.display = "none"
const fallback = target.parentElement?.querySelector(".fallback-icon")
if (fallback) {
fallback.classList.remove("hidden")
}
}}
/>
<Server className="h-8 w-8 md:h-6 md:w-6 text-primary absolute fallback-icon hidden" />
</div>
<div className="min-w-0">
<h1 className="text-lg md:text-xl font-semibold text-foreground truncate">ProxMenux Monitor</h1>
<p className="text-xs md:text-sm text-muted-foreground">Proxmox System Dashboard</p>
<div className="lg:hidden flex items-center gap-1 text-xs text-muted-foreground mt-0.5">
<Server className="h-3 w-3" />
<span className="truncate">Node: {systemStatus.serverName}</span>
</div>
</div>
</div>
{/* Desktop Actions */}
<div className="hidden lg:flex items-center space-x-4">
<div className="flex items-center space-x-2">
<Server className="h-4 w-4 text-muted-foreground" />
<div className="text-sm">
<div className="font-medium text-foreground">Node: {systemStatus.serverName}</div>
</div>
</div>
<div className="flex items-center gap-2">
<Badge variant="outline" className={statusColor}>
{statusIcon}
<span className="ml-1 capitalize">{systemStatus.status}</span>
</Badge>
{systemStatus.status === "healthy" && infoCount > 0 && (
<Badge variant="outline" className="bg-blue-500/10 text-blue-500 border-blue-500/20">
<Info className="h-4 w-4" />
<span className="ml-1">{infoCount} info</span>
</Badge>
)}
</div>
<div className="text-sm text-muted-foreground whitespace-nowrap">
Uptime: {systemStatus.uptime || "N/A"}
</div>
<Button
variant="outline"
size="sm"
onClick={(e) => {
e.stopPropagation()
refreshData()
}}
disabled={isRefreshing}
className="border-border/50 bg-transparent hover:bg-secondary"
>
<RefreshCw className={`h-4 w-4 mr-2 ${isRefreshing ? "animate-spin" : ""}`} />
Refresh
</Button>
<div onClick={(e) => e.stopPropagation()}>
<ThemeToggle />
</div>
</div>
{/* Mobile Actions */}
<div className="flex lg:hidden items-start gap-2 pt-2">
<div className="flex flex-col items-end gap-1">
<Badge variant="outline" className={`${statusColor} text-xs px-2`}>
{statusIcon}
</Badge>
{systemStatus.status === "healthy" && infoCount > 0 && (
<Badge variant="outline" className="bg-blue-500/10 text-blue-500 border-blue-500/20 text-xs px-2">
<Info className="h-4 w-4" />
<span className="ml-1">{infoCount}</span>
</Badge>
)}
</div>
<Button
variant="ghost"
size="sm"
onClick={(e) => {
e.stopPropagation()
refreshData()
}}
disabled={isRefreshing}
className="h-8 w-8 p-0 -mt-1"
>
<RefreshCw className={`h-4 w-4 ${isRefreshing ? "animate-spin" : ""}`} />
</Button>
<div onClick={(e) => e.stopPropagation()} className="-mt-1">
<ThemeToggle />
</div>
</div>
</div>
{/* Mobile Server Info */}
<div className="lg:hidden mt-2 flex items-center justify-end text-xs text-muted-foreground">
<span className="whitespace-nowrap">Uptime: {systemStatus.uptime || "N/A"}</span>
</div>
</div>
</header>
<div
className={`sticky z-40 bg-background
top-[120px] lg:top-[76px]
transition-all duration-700 ease-in-out
${showNavigation ? "translate-y-0 opacity-100" : "-translate-y-[120%] opacity-0 pointer-events-none"}
`}
>
<div className="container mx-auto px-4 lg:px-6 pt-4 lg:pt-6">
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-0">
<TabsList className="hidden lg:grid w-full grid-cols-9 bg-card border border-border">
<TabsTrigger
value="overview"
className="data-[state=active]:bg-blue-500 data-[state=active]:text-white data-[state=active]:rounded-md"
>
Overview
</TabsTrigger>
<TabsTrigger
value="storage"
className="data-[state=active]:bg-blue-500 data-[state=active]:text-white data-[state=active]:rounded-md"
>
Storage
</TabsTrigger>
<TabsTrigger
value="network"
className="data-[state=active]:bg-blue-500 data-[state=active]:text-white data-[state=active]:rounded-md"
>
Network
</TabsTrigger>
<TabsTrigger
value="vms"
className="data-[state=active]:bg-blue-500 data-[state=active]:text-white data-[state=active]:rounded-md"
>
VMs & LXCs
</TabsTrigger>
<TabsTrigger
value="hardware"
className="data-[state=active]:bg-blue-500 data-[state=active]:text-white data-[state=active]:rounded-md"
>
Hardware
</TabsTrigger>
<TabsTrigger
value="logs"
className="data-[state=active]:bg-blue-500 data-[state=active]:text-white data-[state=active]:rounded-md"
>
System Logs
</TabsTrigger>
<TabsTrigger
value="terminal"
className="data-[state=active]:bg-blue-500 data-[state=active]:text-white data-[state=active]:rounded-md"
>
Terminal
</TabsTrigger>
<TabsTrigger
value="security"
className="data-[state=active]:bg-blue-500 data-[state=active]:text-white data-[state=active]:rounded-md"
>
Security
</TabsTrigger>
<TabsTrigger
value="settings"
className="data-[state=active]:bg-blue-500 data-[state=active]:text-white data-[state=active]:rounded-md"
>
Settings
</TabsTrigger>
</TabsList>
<Sheet open={mobileMenuOpen} onOpenChange={setMobileMenuOpen}>
<div className="lg:hidden">
<SheetTrigger asChild>
<Button
variant="outline"
className={`w-full justify-between border-border ${
activeTab ? "bg-blue-500/10 text-blue-500" : "bg-card"
}`}
>
<span>{getActiveTabLabel()}</span>
<Menu className="h-4 w-4" />
</Button>
</SheetTrigger>
</div>
<SheetContent side="top" className="bg-card border-border">
<div className="flex flex-col gap-2 mt-4">
<Button
variant="ghost"
onClick={() => {
setActiveTab("overview")
setMobileMenuOpen(false)
}}
className={`w-full justify-start gap-3 ${
activeTab === "overview"
? "bg-blue-500/10 text-blue-500 border-l-4 border-blue-500 rounded-l-none"
: ""
}`}
>
<LayoutDashboard className="h-5 w-5" />
<span>Overview</span>
</Button>
<Button
variant="ghost"
onClick={() => {
setActiveTab("storage")
setMobileMenuOpen(false)
}}
className={`w-full justify-start gap-3 ${
activeTab === "storage"
? "bg-blue-500/10 text-blue-500 border-l-4 border-blue-500 rounded-l-none"
: ""
}`}
>
<HardDrive className="h-5 w-5" />
<span>Storage</span>
</Button>
<Button
variant="ghost"
onClick={() => {
setActiveTab("network")
setMobileMenuOpen(false)
}}
className={`w-full justify-start gap-3 ${
activeTab === "network"
? "bg-blue-500/10 text-blue-500 border-l-4 border-blue-500 rounded-l-none"
: ""
}`}
>
<NetworkIcon className="h-5 w-5" />
<span>Network</span>
</Button>
<Button
variant="ghost"
onClick={() => {
setActiveTab("vms")
setMobileMenuOpen(false)
}}
className={`w-full justify-start gap-3 ${
activeTab === "vms"
? "bg-blue-500/10 text-blue-500 border-l-4 border-blue-500 rounded-l-none"
: ""
}`}
>
<Box className="h-5 w-5" />
<span>VMs & LXCs</span>
</Button>
<Button
variant="ghost"
onClick={() => {
setActiveTab("hardware")
setMobileMenuOpen(false)
}}
className={`w-full justify-start gap-3 ${
activeTab === "hardware"
? "bg-blue-500/10 text-blue-500 border-l-4 border-blue-500 rounded-l-none"
: ""
}`}
>
<Cpu className="h-5 w-5" />
<span>Hardware</span>
</Button>
<Button
variant="ghost"
onClick={() => {
setActiveTab("logs")
setMobileMenuOpen(false)
}}
className={`w-full justify-start gap-3 ${
activeTab === "logs"
? "bg-blue-500/10 text-blue-500 border-l-4 border-blue-500 rounded-l-none"
: ""
}`}
>
<FileText className="h-5 w-5" />
<span>System Logs</span>
</Button>
<Button
variant="ghost"
onClick={() => {
setActiveTab("terminal")
setMobileMenuOpen(false)
}}
className={`w-full justify-start gap-3 ${
activeTab === "terminal"
? "bg-blue-500/10 text-blue-500 border-l-4 border-blue-500 rounded-l-none"
: ""
}`}
>
<Terminal className="h-5 w-5" />
<span>Terminal</span>
</Button>
<Button
variant="ghost"
onClick={() => {
setActiveTab("security")
setMobileMenuOpen(false)
}}
className={`w-full justify-start gap-3 ${
activeTab === "security"
? "bg-blue-500/10 text-blue-500 border-l-4 border-blue-500 rounded-l-none"
: ""
}`}
>
<ShieldCheck className="h-5 w-5" />
<span>Security</span>
</Button>
<Button
variant="ghost"
onClick={() => {
setActiveTab("settings")
setMobileMenuOpen(false)
}}
className={`w-full justify-start gap-3 ${
activeTab === "settings"
? "bg-blue-500/10 text-blue-500 border-l-4 border-blue-500 rounded-l-none"
: ""
}`}
>
<SettingsIcon className="h-5 w-5" />
<span>Settings</span>
</Button>
</div>
</SheetContent>
</Sheet>
</Tabs>
</div>
</div>
<div className="container mx-auto px-4 md:px-6 py-4 md:py-6">
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-4 md:space-y-6">
<TabsContent value="overview" className="space-y-4 md:space-y-6 mt-0">
<SystemOverview key={`overview-${componentKey}`} />
</TabsContent>
<TabsContent value="storage" className="space-y-4 md:space-y-6 mt-0">
<StorageOverview key={`storage-${componentKey}`} />
</TabsContent>
<TabsContent value="network" className="space-y-4 md:space-y-6 mt-0">
<NetworkMetrics key={`network-${componentKey}`} />
</TabsContent>
<TabsContent value="vms" className="space-y-4 md:space-y-6 mt-0">
<VirtualMachines key={`vms-${componentKey}`} />
</TabsContent>
<TabsContent value="hardware" className="space-y-4 md:space-y-6 mt-0">
<Hardware key={`hardware-${componentKey}`} />
</TabsContent>
<TabsContent value="logs" className="space-y-4 md:space-y-6 mt-0">
<SystemLogs key={`logs-${componentKey}`} />
</TabsContent>
<TabsContent value="terminal" className="mt-0">
<TerminalPanel key={`terminal-${componentKey}`} />
</TabsContent>
<TabsContent value="security" className="space-y-4 md:space-y-6 mt-0">
<Security key={`security-${componentKey}`} />
</TabsContent>
<TabsContent value="settings" className="space-y-4 md:space-y-6 mt-0">
<Settings />
</TabsContent>
</Tabs>
<footer className="mt-8 md:mt-12 pt-4 md:pt-6 border-t border-border text-center text-xs md:text-sm text-muted-foreground">
<p className="font-medium mb-2">ProxMenux Monitor v1.2.0</p>
<p>
<a
href="https://ko-fi.com/macrimi"
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:text-blue-600 hover:underline transition-colors"
>
Support and contribute to the project
</a>
</p>
</footer>
</div>
<HealthStatusModal open={showHealthModal} onOpenChange={setShowHealthModal} getApiUrl={getApiUrl} />
</div>
)
}

View File

@@ -0,0 +1,227 @@
"use client"
import { useState, useEffect } from "react"
import { Button } from "./ui/button"
import { Dialog, DialogContent, DialogTitle } from "./ui/dialog"
import { X, Sparkles, Thermometer, Terminal, Activity, HardDrive, Bell, Shield, Globe, Cpu, Zap } from "lucide-react"
import { Checkbox } from "./ui/checkbox"
const APP_VERSION = "1.2.0" // Sync with AppImage/package.json
interface ReleaseNote {
date: string
changes: {
added?: string[]
changed?: string[]
fixed?: string[]
}
}
export const CHANGELOG: Record<string, ReleaseNote> = {
"1.1.2-beta": {
date: "March 18, 2026",
changes: {
added: [
"Temperature & Latency Charts - Real-time visual monitoring with interactive graphs",
"WebSocket Terminal - Direct access to Proxmox host and LXC containers terminal",
"AI-Enhanced Notifications - Intelligent message formatting with multi-provider support (OpenAI, Groq, Anthropic, Ollama)",
"Security Section - Comprehensive security settings for ProxMenux and Proxmox",
"VPN Integration - Easy Tailscale VPN installation and configuration",
"GPU Scripts - Installation utilities for Intel, AMD and NVIDIA drivers",
"Disk Observations System - Track and document disk health observations over time",
"Enhanced Health Monitor - Configurable monitoring with advanced settings panel",
],
changed: [
"Improved overall performance with optimized data fetching",
"Notifications now support rich formatting with contextual emojis",
"Health monitor now configurable from Settings section",
"Better Proxmox service name translation for non-expert users",
],
fixed: [
"Fixed notification message truncation for large backup reports",
"Improved disk error deduplication to prevent repeated alerts",
"Corrected AI provider base URL handling for OpenAI-compatible APIs",
],
},
},
"1.0.1": {
date: "November 11, 2025",
changes: {
added: [
"Proxy Support - Access ProxMenux through reverse proxies with full functionality",
"Authentication System - Secure your dashboard with password protection",
"PCIe Link Speed Detection - View NVMe drive connection speeds and detect performance issues",
"Two-Factor Authentication (2FA) - Enhanced security with TOTP support",
"Health Monitoring System - Comprehensive system health checks with dismissible warnings",
],
changed: [
"Optimized VM & LXC page - Reduced CPU usage by 85% through intelligent caching",
"Storage metrics now separate local and remote storage for clarity",
],
fixed: [
"Fixed dark mode text contrast issues in various components",
"Corrected storage calculation discrepancies between Overview and Storage pages",
],
},
},
"1.0.0": {
date: "October 15, 2025",
changes: {
added: [
"Initial release of ProxMenux Monitor",
"Real-time system monitoring dashboard",
"Storage management with SMART health monitoring",
"Network metrics and bandwidth tracking",
"VM & LXC container management",
"Hardware information display",
"System logs viewer with filtering",
],
},
},
}
const CURRENT_VERSION_FEATURES = [
{
icon: <Thermometer className="h-5 w-5" />,
text: "Temperature & Latency Charts - Real-time visual monitoring with interactive historical graphs",
},
{
icon: <Terminal className="h-5 w-5" />,
text: "WebSocket Terminal - Direct terminal access to Proxmox host and LXC containers from the browser",
},
{
icon: <Activity className="h-5 w-5" />,
text: "Enhanced Health Monitor - Configurable health monitoring with advanced settings and disk observations",
},
{
icon: <Bell className="h-5 w-5" />,
text: "AI-Enhanced Notifications - Intelligent message formatting with support for OpenAI, Groq, Anthropic and Ollama",
},
{
icon: <Shield className="h-5 w-5" />,
text: "Security Section - Comprehensive security configuration for both ProxMenux and Proxmox systems",
},
{
icon: <Globe className="h-5 w-5" />,
text: "VPN Integration - Easy Tailscale VPN installation and configuration for secure remote access",
},
{
icon: <Cpu className="h-5 w-5" />,
text: "GPU Drivers - Installation scripts for Intel, AMD and NVIDIA graphics drivers and utilities",
},
{
icon: <Zap className="h-5 w-5" />,
text: "Performance Improvements - Optimized data fetching and reduced resource consumption",
},
]
interface ReleaseNotesModalProps {
open: boolean
onClose: () => void
}
export function ReleaseNotesModal({ open, onClose }: ReleaseNotesModalProps) {
const [dontShowAgain, setDontShowAgain] = useState(false)
const handleClose = () => {
if (dontShowAgain) {
localStorage.setItem("proxmenux-last-seen-version", APP_VERSION)
}
onClose()
}
return (
<Dialog open={open} onOpenChange={handleClose}>
<DialogContent className="max-w-2xl max-h-[85vh] p-0 gap-0 border-0 bg-transparent">
<DialogTitle className="sr-only">Release Notes - Version {APP_VERSION}</DialogTitle>
<div className="relative bg-card rounded-lg shadow-2xl h-full flex flex-col max-h-[85vh]">
<Button
variant="ghost"
size="icon"
className="absolute top-4 right-4 z-50 h-8 w-8 rounded-full bg-background/80 backdrop-blur-sm hover:bg-background"
onClick={handleClose}
>
<X className="h-4 w-4" />
</Button>
<div className="relative h-32 md:h-40 bg-gradient-to-br from-amber-500 via-orange-500 to-red-500 flex items-center justify-center overflow-hidden flex-shrink-0">
<div className="absolute inset-0 bg-black/10" />
<div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_120%,rgba(255,255,255,0.1),transparent)]" />
<div className="relative z-10 text-white animate-pulse">
<Sparkles className="h-12 w-12 md:h-14 md:w-14" />
</div>
<div className="absolute top-10 left-10 w-20 h-20 bg-white/10 rounded-full blur-2xl" />
<div className="absolute bottom-10 right-10 w-32 h-32 bg-white/10 rounded-full blur-3xl" />
</div>
<div className="flex-1 overflow-y-auto p-6 md:p-8 space-y-4 md:space-y-6 min-h-0">
<div className="space-y-2">
<h2 className="text-xl md:text-2xl font-bold text-foreground text-balance">
What's New in Version {APP_VERSION}
</h2>
<p className="text-sm text-muted-foreground leading-relaxed">
We've added exciting new features and improvements to make ProxMenux Monitor even better!
</p>
</div>
<div className="space-y-2">
{CURRENT_VERSION_FEATURES.map((feature, index) => (
<div
key={index}
className="flex items-start gap-2 md:gap-3 p-3 rounded-lg bg-muted/50 border border-border/50 hover:bg-muted/70 transition-colors"
>
<div className="text-orange-500 mt-0.5 flex-shrink-0">{feature.icon}</div>
<p className="text-xs md:text-sm text-foreground leading-relaxed">{feature.text}</p>
</div>
))}
</div>
</div>
<div className="flex-shrink-0 p-6 md:p-8 pt-4 border-t border-border/50 bg-card">
<div className="flex flex-col gap-3">
<Button
onClick={handleClose}
className="w-full bg-gradient-to-r from-amber-500 to-orange-500 hover:from-amber-600 hover:to-orange-600"
>
<Sparkles className="h-4 w-4 mr-2" />
Got it!
</Button>
<div className="flex items-center justify-center gap-2">
<Checkbox
id="dont-show-version-again"
checked={dontShowAgain}
onCheckedChange={(checked) => setDontShowAgain(checked as boolean)}
/>
<label
htmlFor="dont-show-version-again"
className="text-xs md:text-sm text-muted-foreground hover:text-foreground transition-colors cursor-pointer select-none"
>
Don't show again for this version
</label>
</div>
</div>
</div>
</div>
</DialogContent>
</Dialog>
)
}
export function useVersionCheck() {
const [showReleaseNotes, setShowReleaseNotes] = useState(false)
useEffect(() => {
const lastSeenVersion = localStorage.getItem("proxmenux-last-seen-version")
if (lastSeenVersion !== APP_VERSION) {
setShowReleaseNotes(true)
}
}, [])
return { showReleaseNotes, setShowReleaseNotes }
}
export { APP_VERSION }

View File

@@ -0,0 +1,950 @@
"use client"
import type React from "react"
import { useState, useEffect, useRef, useCallback } from "react"
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import {
Loader2,
Activity,
ArrowUp,
ArrowDown,
ArrowLeft,
ArrowRight,
CornerDownLeft,
GripHorizontal,
ChevronDown,
} from "lucide-react"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
DropdownMenuSeparator,
DropdownMenuLabel,
} from "@/components/ui/dropdown-menu"
import "xterm/css/xterm.css"
import { API_PORT } from "@/lib/api-config"
interface WebInteraction {
type: "yesno" | "menu" | "msgbox" | "input" | "inputbox"
id: string
title: string
message: string
options?: Array<{ label: string; value: string }>
default?: string
}
interface ScriptTerminalModalProps {
open: boolean
onClose: () => void
scriptPath: string
title: string
description: string
scriptName?: string
params?: Record<string, string>
}
export function ScriptTerminalModal({
open: isOpen,
onClose,
scriptPath,
title,
description,
params = { EXECUTION_MODE: "web" },
}: ScriptTerminalModalProps) {
const termRef = useRef<any>(null)
const wsRef = useRef<WebSocket | null>(null)
const fitAddonRef = useRef<any>(null)
const sessionIdRef = useRef<string>(Math.random().toString(36).substring(2, 8))
const [connectionStatus, setConnectionStatus] = useState<"connecting" | "online" | "offline">("connecting")
const [isComplete, setIsComplete] = useState(false)
const [currentInteraction, setCurrentInteraction] = useState<WebInteraction | null>(null)
const [interactionInput, setInteractionInput] = useState("")
const checkConnectionInterval = useRef<NodeJS.Timeout | null>(null)
const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null)
const reconnectAttemptsRef = useRef(0)
const keepAliveIntervalRef = useRef<NodeJS.Timeout | null>(null)
const [isMobile, setIsMobile] = useState(false)
const [isTablet, setIsTablet] = useState(false)
const [isWaitingNextInteraction, setIsWaitingNextInteraction] = useState(false)
const waitingTimeoutRef = useRef<NodeJS.Timeout | null>(null)
const [modalHeight, setModalHeight] = useState(600)
const [isResizing, setIsResizing] = useState(false)
const resizeBarRef = useRef<HTMLDivElement>(null)
const modalHeightRef = useRef(600)
const terminalContainerRef = useRef<HTMLDivElement>(null)
const paramsRef = useRef(params)
// Keep paramsRef updated with latest params
useEffect(() => {
paramsRef.current = params
}, [params])
const attemptReconnect = useCallback(() => {
if (!isOpen || isComplete || reconnectAttemptsRef.current >= 3) {
return
}
reconnectAttemptsRef.current++
setConnectionStatus("connecting")
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current)
}
reconnectTimeoutRef.current = setTimeout(() => {
if (wsRef.current?.readyState !== WebSocket.OPEN && termRef.current) {
if (wsRef.current) {
wsRef.current.close()
}
const wsUrl = getScriptWebSocketUrl(sessionIdRef.current)
const ws = new WebSocket(wsUrl)
wsRef.current = ws
ws.onopen = () => {
setConnectionStatus("online")
reconnectAttemptsRef.current = 0
if (keepAliveIntervalRef.current) {
clearInterval(keepAliveIntervalRef.current)
}
keepAliveIntervalRef.current = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: "ping" }))
}
}, 30000)
const initMessage = {
script_path: scriptPath,
params: paramsRef.current,
}
ws.send(JSON.stringify(initMessage))
setTimeout(() => {
if (fitAddonRef.current && termRef.current && ws.readyState === WebSocket.OPEN) {
const cols = termRef.current.cols
const rows = termRef.current.rows
ws.send(JSON.stringify({ type: "resize", cols, rows }))
}
}, 100)
}
ws.onmessage = (event) => {
// Filter out pong responses from heartbeat
if (event.data === '{"type": "pong"}' || event.data === '{"type":"pong"}') {
return
}
try {
const msg = JSON.parse(event.data)
if (msg.type === "web_interaction" && msg.interaction) {
setIsWaitingNextInteraction(false)
if (waitingTimeoutRef.current) {
clearTimeout(waitingTimeoutRef.current)
}
setCurrentInteraction({
type: msg.interaction.type,
id: msg.interaction.id,
title: msg.interaction.title || "",
message: msg.interaction.message || "",
options: msg.interaction.options,
default: msg.interaction.default,
})
return
}
if (msg.type === "error") {
termRef.current?.writeln(`\x1b[31m${msg.message}\x1b[0m`)
return
}
} catch {}
termRef.current?.write(event.data)
setIsWaitingNextInteraction(false)
if (waitingTimeoutRef.current) {
clearTimeout(waitingTimeoutRef.current)
}
}
ws.onerror = () => {
setConnectionStatus("offline")
}
ws.onclose = (event) => {
setConnectionStatus("offline")
if (keepAliveIntervalRef.current) {
clearInterval(keepAliveIntervalRef.current)
keepAliveIntervalRef.current = null
}
if (!isComplete && reconnectAttemptsRef.current < 3) {
reconnectTimeoutRef.current = setTimeout(attemptReconnect, 2000)
} else {
setIsComplete(true)
}
}
}
}, 1000)
}, [isOpen, isComplete, scriptPath])
const sendKey = useCallback((key: string) => {
if (!termRef.current) return
const keyMap: Record<string, string> = {
escape: "\x1b",
tab: "\t",
up: "\x1bOA",
down: "\x1bOB",
left: "\x1bOD",
right: "\x1bOC",
enter: "\r",
ctrlc: "\x03",
}
const sequence = keyMap[key]
if (sequence && wsRef.current?.readyState === WebSocket.OPEN) {
wsRef.current.send(sequence)
}
}, [])
const initializeTerminal = async () => {
const [TerminalClass, FitAddonClass] = await Promise.all([
import("xterm").then((mod) => mod.Terminal),
import("xterm-addon-fit").then((mod) => mod.FitAddon),
import("xterm/css/xterm.css"),
])
const fontSize = window.innerWidth < 768 ? 12 : 16
const term = new TerminalClass({
rendererType: "dom",
fontFamily: '"Courier", "Courier New", "Liberation Mono", "DejaVu Sans Mono", monospace',
fontSize: fontSize,
lineHeight: 1,
cursorBlink: true,
scrollback: 2000,
disableStdin: false,
customGlyphs: true,
fontWeight: "500",
fontWeightBold: "700",
theme: {
background: "#000000",
foreground: "#ffffff",
cursor: "#ffffff",
cursorAccent: "#000000",
black: "#2e3436",
red: "#cc0000",
green: "#4e9a06",
yellow: "#c4a000",
blue: "#3465a4",
magenta: "#75507b",
cyan: "#06989a",
white: "#d3d7cf",
brightBlack: "#555753",
brightRed: "#ef2929",
brightGreen: "#8ae234",
brightYellow: "#fce94f",
brightBlue: "#729fcf",
brightMagenta: "#ad7fa8",
brightCyan: "#34e2e2",
brightWhite: "#eeeeec",
},
})
const fitAddon = new FitAddonClass()
term.loadAddon(fitAddon)
if (terminalContainerRef.current) {
term.open(terminalContainerRef.current)
}
termRef.current = term
fitAddonRef.current = fitAddon
setTimeout(() => {
if (fitAddonRef.current && termRef.current) {
fitAddonRef.current.fit()
}
}, 100)
const wsUrl = getScriptWebSocketUrl(sessionIdRef.current)
const ws = new WebSocket(wsUrl)
wsRef.current = ws
ws.onopen = () => {
setConnectionStatus("online")
if (keepAliveIntervalRef.current) {
clearInterval(keepAliveIntervalRef.current)
}
keepAliveIntervalRef.current = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: "ping" }))
}
}, 30000)
const initMessage = {
script_path: scriptPath,
params: paramsRef.current,
}
ws.send(JSON.stringify(initMessage))
setTimeout(() => {
if (fitAddonRef.current && termRef.current && ws.readyState === WebSocket.OPEN) {
const cols = termRef.current.cols
const rows = termRef.current.rows
ws.send(
JSON.stringify({
type: "resize",
cols: cols,
rows: rows,
}),
)
}
}, 100)
}
ws.onmessage = (event) => {
// Filter out pong responses from heartbeat - don't display in terminal
if (event.data === '{"type": "pong"}' || event.data === '{"type":"pong"}') {
return
}
try {
const msg = JSON.parse(event.data)
if (msg.type === "web_interaction" && msg.interaction) {
setIsWaitingNextInteraction(false)
if (waitingTimeoutRef.current) {
clearTimeout(waitingTimeoutRef.current)
}
setCurrentInteraction({
type: msg.interaction.type,
id: msg.interaction.id,
title: msg.interaction.title || "",
message: msg.interaction.message || "",
options: msg.interaction.options,
default: msg.interaction.default,
})
return
}
if (msg.type === "error") {
term.writeln(`\x1b[31m${msg.message}\x1b[0m`)
return
}
} catch {
// Not JSON, es output normal de terminal
}
term.write(event.data)
setIsWaitingNextInteraction(false)
if (waitingTimeoutRef.current) {
clearTimeout(waitingTimeoutRef.current)
}
}
ws.onerror = (error) => {
setConnectionStatus("offline")
term.writeln("\x1b[31mWebSocket error occurred\x1b[0m")
}
ws.onclose = (event) => {
setConnectionStatus("offline")
term.writeln("\x1b[33mConnection closed\x1b[0m")
if (keepAliveIntervalRef.current) {
clearInterval(keepAliveIntervalRef.current)
keepAliveIntervalRef.current = null
}
if (!isComplete) {
setIsComplete(true)
}
}
term.onData((data) => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(data)
}
})
checkConnectionInterval.current = setInterval(() => {
if (wsRef.current) {
setConnectionStatus(
wsRef.current.readyState === WebSocket.OPEN
? "online"
: wsRef.current.readyState === WebSocket.CONNECTING
? "connecting"
: "offline",
)
}
}, 500)
let resizeTimeout: NodeJS.Timeout | null = null
const resizeObserver = new ResizeObserver(() => {
if (resizeTimeout) clearTimeout(resizeTimeout)
resizeTimeout = setTimeout(() => {
if (fitAddonRef.current && termRef.current && wsRef.current?.readyState === WebSocket.OPEN) {
fitAddonRef.current.fit()
wsRef.current.send(
JSON.stringify({
type: "resize",
cols: termRef.current.cols,
rows: termRef.current.rows,
}),
)
}
}, 100)
})
if (terminalContainerRef.current) {
resizeObserver.observe(terminalContainerRef.current)
}
}
useEffect(() => {
const savedHeight = localStorage.getItem("scriptModalHeight")
if (savedHeight) {
const height = Number.parseInt(savedHeight, 10)
setModalHeight(height)
modalHeightRef.current = height
}
if (isOpen) {
initializeTerminal()
} else {
if (checkConnectionInterval.current) {
clearInterval(checkConnectionInterval.current)
}
if (waitingTimeoutRef.current) {
clearTimeout(waitingTimeoutRef.current)
}
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current)
}
if (wsRef.current) {
wsRef.current.close()
wsRef.current = null
}
if (termRef.current) {
termRef.current.dispose()
termRef.current = null
}
if (keepAliveIntervalRef.current) {
clearInterval(keepAliveIntervalRef.current)
keepAliveIntervalRef.current = null
}
sessionIdRef.current = Math.random().toString(36).substring(2, 8)
reconnectAttemptsRef.current = 0
setIsComplete(false)
setInteractionInput("")
setCurrentInteraction(null)
setIsWaitingNextInteraction(false)
setConnectionStatus("connecting")
}
}, [isOpen])
useEffect(() => {
const updateDeviceType = () => {
const width = window.innerWidth
const isTouchDevice = "ontouchstart" in window || navigator.maxTouchPoints > 0
const isTabletSize = width >= 768 && width <= 1366
setIsMobile(width < 768)
setIsTablet(isTouchDevice && isTabletSize)
}
updateDeviceType()
const handleResize = () => updateDeviceType()
window.addEventListener("resize", handleResize)
const handleVisibilityChange = () => {
if (!document.hidden && isOpen) {
if (wsRef.current?.readyState !== WebSocket.OPEN && !isComplete) {
attemptReconnect()
}
}
}
const handleFocus = () => {
if (isOpen && wsRef.current?.readyState !== WebSocket.OPEN && !isComplete) {
attemptReconnect()
}
}
let wakeLock: any = null
const requestWakeLock = async () => {
if ("wakeLock" in navigator && isOpen) {
try {
wakeLock = await (navigator as any).wakeLock.request("screen")
} catch (err) {
// Wake Lock no soportado o denegado, continuar sin él
}
}
}
requestWakeLock()
document.addEventListener("visibilitychange", handleVisibilityChange)
window.addEventListener("focus", handleFocus)
return () => {
window.removeEventListener("resize", handleResize)
document.removeEventListener("visibilitychange", handleVisibilityChange)
window.removeEventListener("focus", handleFocus)
if (wakeLock) {
wakeLock.release().catch(() => {})
}
}
}, [isOpen, isComplete, attemptReconnect])
const getScriptWebSocketUrl = (sid: string): string => {
if (typeof window === "undefined") {
return `ws://localhost:${API_PORT}/ws/script/${sid}`
}
const { protocol, hostname, port } = window.location
const isStandardPort = port === "" || port === "80" || port === "443"
const wsProtocol = protocol === "https:" ? "wss:" : "ws:"
if (isStandardPort) {
return `${wsProtocol}//${hostname}/ws/script/${sid}`
} else {
return `${wsProtocol}//${hostname}:${API_PORT}/ws/script/${sid}`
}
}
const handleInteractionResponse = (value: string) => {
if (!wsRef.current || !currentInteraction) {
return
}
if (value === "cancel" || value === "") {
setCurrentInteraction(null)
setInteractionInput("")
handleCloseModal()
return
}
const response = JSON.stringify({
type: "interaction_response",
id: currentInteraction.id,
value: value,
})
if (wsRef.current.readyState === WebSocket.OPEN) {
wsRef.current.send(response)
}
setCurrentInteraction(null)
setInteractionInput("")
waitingTimeoutRef.current = setTimeout(() => {
setIsWaitingNextInteraction(true)
}, 50)
}
const handleCloseModal = () => {
if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
wsRef.current.close()
}
if (checkConnectionInterval.current) {
clearInterval(checkConnectionInterval.current)
}
if (termRef.current) {
termRef.current.dispose()
}
onClose()
}
const handleResizeStart = (e: React.MouseEvent | React.TouchEvent) => {
e.preventDefault()
e.stopPropagation()
setIsResizing(true)
const clientY = "touches" in e ? e.touches[0].clientY : e.clientY
const startY = clientY
const startHeight = modalHeight
const handleMove = (moveEvent: MouseEvent | TouchEvent) => {
const currentY = "touches" in moveEvent ? moveEvent.touches[0].clientY : moveEvent.clientY
const deltaY = currentY - startY
const newHeight = Math.max(300, Math.min(window.innerHeight - 50, startHeight + deltaY))
modalHeightRef.current = newHeight
setModalHeight(newHeight)
}
const handleEnd = () => {
const finalHeight = modalHeightRef.current
setIsResizing(false)
document.removeEventListener("mousemove", handleMove as any)
document.removeEventListener("mouseup", handleEnd)
document.removeEventListener("touchmove", handleMove as any)
document.removeEventListener("touchend", handleEnd)
document.removeEventListener("touchcancel", handleEnd)
localStorage.setItem("scriptModalHeight", finalHeight.toString())
if (fitAddonRef.current && termRef.current && wsRef.current?.readyState === WebSocket.OPEN) {
setTimeout(() => {
fitAddonRef.current?.fit()
wsRef.current?.send(
JSON.stringify({
type: "resize",
cols: termRef.current.cols,
rows: termRef.current.rows,
}),
)
}, 100)
}
}
document.addEventListener("mousemove", handleMove as any)
document.addEventListener("mouseup", handleEnd)
document.addEventListener("touchmove", handleMove as any, { passive: false })
document.addEventListener("touchend", handleEnd)
document.addEventListener("touchcancel", handleEnd)
}
const sendCommand = (command: string) => {
if (wsRef.current?.readyState === WebSocket.OPEN) {
wsRef.current.send(command)
}
}
return (
<>
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent
className="max-w-7xl p-0 flex flex-col gap-0 overflow-hidden"
style={{
height: isMobile ? "80vh" : `${modalHeight}px`,
maxHeight: "none",
}}
onInteractOutside={(e) => e.preventDefault()}
onEscapeKeyDown={(e) => e.preventDefault()}
hideClose
>
<DialogTitle className="sr-only">{title}</DialogTitle>
<div className="flex items-center gap-2 p-4 border-b">
<div>
<h2 className="text-lg font-semibold">{title}</h2>
{description && <p className="text-sm text-muted-foreground">{description}</p>}
</div>
</div>
<div className="overflow-hidden relative flex-1">
<div ref={terminalContainerRef} className="w-full h-full" />
{isWaitingNextInteraction && !currentInteraction && (
<div className="absolute inset-0 flex items-center justify-center bg-black/50 backdrop-blur-sm">
<div className="flex flex-col items-center gap-3">
<Loader2 className="h-8 w-8 animate-spin text-blue-500" />
<p className="text-sm text-muted-foreground">Processing...</p>
</div>
</div>
)}
</div>
{!isMobile && (
<div
ref={resizeBarRef}
onMouseDown={handleResizeStart}
onTouchStart={handleResizeStart}
className={`h-4 w-full cursor-row-resize transition-colors flex items-center justify-center group relative ${
isResizing ? "bg-blue-500" : "bg-zinc-800 hover:bg-blue-600"
}`}
style={{ touchAction: "none" }}
>
<GripHorizontal
className={`h-5 w-5 transition-colors pointer-events-none ${
isResizing ? "text-white" : "text-zinc-600 group-hover:text-white"
}`}
/>
</div>
)}
{(isMobile || isTablet) && (
<div className="flex items-center justify-center gap-1.5 px-1 py-2 border-t bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<Button
onPointerDown={(e) => {
e.preventDefault()
e.stopPropagation()
sendCommand("\x1b")
}}
variant="outline"
size="sm"
className="h-8 px-2 text-xs bg-zinc-800 hover:bg-zinc-700 border-zinc-700 text-white min-w-[50px]"
>
ESC
</Button>
<Button
onPointerDown={(e) => {
e.preventDefault()
e.stopPropagation()
sendCommand("\t")
}}
variant="outline"
size="sm"
className="h-8 px-2 text-xs bg-zinc-800 hover:bg-zinc-700 border-zinc-700 text-white min-w-[50px]"
>
TAB
</Button>
<Button
onPointerDown={(e) => {
e.preventDefault()
e.stopPropagation()
sendCommand("\x1bOA")
}}
variant="outline"
size="sm"
className="h-8 px-2.5 text-xs bg-zinc-800 hover:bg-zinc-700 border-zinc-700 text-white"
>
<ArrowUp className="h-4 w-4" />
</Button>
<Button
onPointerDown={(e) => {
e.preventDefault()
e.stopPropagation()
sendCommand("\x1bOB")
}}
variant="outline"
size="sm"
className="h-8 px-2.5 text-xs bg-zinc-800 hover:bg-zinc-700 border-zinc-700 text-white"
>
<ArrowDown className="h-4 w-4" />
</Button>
<Button
onPointerDown={(e) => {
e.preventDefault()
e.stopPropagation()
sendCommand("\x1bOD")
}}
variant="outline"
size="sm"
className="h-8 px-2.5 text-xs bg-zinc-800 hover:bg-zinc-700 border-zinc-700 text-white"
>
<ArrowLeft className="h-4 w-4" />
</Button>
<Button
onPointerDown={(e) => {
e.preventDefault()
e.stopPropagation()
sendCommand("\x1bOC")
}}
variant="outline"
size="sm"
className="h-8 px-2.5 text-xs bg-zinc-800 hover:bg-zinc-700 border-zinc-700 text-white"
>
<ArrowRight className="h-4 w-4" />
</Button>
<Button
onPointerDown={(e) => {
e.preventDefault()
e.stopPropagation()
sendCommand("\r")
}}
variant="outline"
size="sm"
className="h-8 px-2.5 text-xs bg-blue-600/20 hover:bg-blue-600/30 border-blue-600/50 text-blue-400"
>
<CornerDownLeft className="h-4 w-4 mr-1" />
Enter
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
size="sm"
className="h-8 px-2 text-xs bg-zinc-800 hover:bg-zinc-700 border-zinc-700 text-white min-w-[65px] gap-1"
>
Ctrl
<ChevronDown className="h-3 w-3" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-48">
<DropdownMenuLabel className="text-xs text-muted-foreground">Control Sequences</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem onSelect={() => sendCommand("\x03")}>
<span className="font-mono text-xs mr-2">Ctrl+C</span>
<span className="text-muted-foreground text-xs">Cancel/Interrupt</span>
</DropdownMenuItem>
<DropdownMenuItem onSelect={() => sendCommand("\x18")}>
<span className="font-mono text-xs mr-2">Ctrl+X</span>
<span className="text-muted-foreground text-xs">Exit (nano)</span>
</DropdownMenuItem>
<DropdownMenuItem onSelect={() => sendCommand("\x12")}>
<span className="font-mono text-xs mr-2">Ctrl+R</span>
<span className="text-muted-foreground text-xs">Search history</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
)}
<div className="flex items-center justify-between px-4 py-3 border-t bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div className="flex items-center gap-3">
<Activity className="h-5 w-5 text-blue-500" />
<div
className={`w-2 h-2 rounded-full ${
connectionStatus === "online"
? "bg-green-500"
: connectionStatus === "connecting"
? "bg-blue-500"
: "bg-red-500"
}`}
title={
connectionStatus === "online"
? "Connected"
: connectionStatus === "connecting"
? "Connecting"
: "Disconnected"
}
></div>
<span className="text-xs text-muted-foreground">
{connectionStatus === "online"
? "Online"
: connectionStatus === "connecting"
? "Connecting..."
: "Offline"}
</span>
</div>
<Button
onClick={handleCloseModal}
variant="outline"
className="bg-red-600/20 hover:bg-red-600/30 border-red-600/50 text-red-400"
>
Close
</Button>
</div>
</DialogContent>
</Dialog>
{currentInteraction && (
<Dialog open={true}>
<DialogContent
className="max-w-4xl max-h-[80vh] overflow-y-auto animate-in fade-in-0 zoom-in-95 duration-100"
onInteractOutside={(e) => e.preventDefault()}
onEscapeKeyDown={(e) => e.preventDefault()}
hideClose
>
<DialogTitle>{currentInteraction.title}</DialogTitle>
<div className="space-y-4">
<p
className="whitespace-pre-wrap"
dangerouslySetInnerHTML={{
__html: currentInteraction.message.replace(/\\n/g, "<br/>").replace(/\n/g, "<br/>"),
}}
/>
{currentInteraction.type === "yesno" && (
<div className="flex gap-2">
<Button
onClick={() => handleInteractionResponse("yes")}
className="flex-1 bg-blue-600 hover:bg-blue-700 text-white transition-all duration-150"
>
Yes
</Button>
<Button
onClick={() => handleInteractionResponse("cancel")}
variant="outline"
className="flex-1 hover:bg-red-600 hover:text-white hover:border-red-600 transition-all duration-150"
>
Cancel
</Button>
</div>
)}
{currentInteraction.type === "menu" && currentInteraction.options && (
<div className="space-y-2">
{currentInteraction.options.map((option, index) => (
<Button
key={option.value}
onClick={() => handleInteractionResponse(option.value)}
variant="outline"
className="w-full justify-start hover:bg-blue-600 hover:text-white transition-all duration-100 animate-in fade-in-0 slide-in-from-left-2"
style={{ animationDelay: `${index * 30}ms` }}
>
{option.label}
</Button>
))}
<Button
onClick={() => handleInteractionResponse("cancel")}
variant="outline"
className="w-full hover:bg-red-600 hover:text-white hover:border-red-600 transition-all duration-150"
>
Cancel
</Button>
</div>
)}
{(currentInteraction.type === "input" || currentInteraction.type === "inputbox") && (
<div className="space-y-2">
<Label>Your input:</Label>
<Input
value={interactionInput}
onChange={(e) => setInteractionInput(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
handleInteractionResponse(interactionInput)
}
}}
placeholder={currentInteraction.default || ""}
className="transition-all duration-150"
/>
<div className="flex gap-2">
<Button
onClick={() => handleInteractionResponse(interactionInput)}
className="flex-1 bg-blue-600 hover:bg-blue-700 transition-all duration-150"
>
Submit
</Button>
<Button
onClick={() => handleInteractionResponse("cancel")}
variant="outline"
className="flex-1 hover:bg-red-600 hover:text-white hover:border-red-600 transition-all duration-150"
>
Cancel
</Button>
</div>
</div>
)}
{currentInteraction.type === "msgbox" && (
<div className="flex gap-2">
<Button
onClick={() => handleInteractionResponse("ok")}
className="flex-1 bg-blue-600 hover:bg-blue-700 transition-all duration-150"
>
OK
</Button>
<Button
onClick={() => handleInteractionResponse("cancel")}
variant="outline"
className="flex-1 hover:bg-red-600 hover:text-white hover:border-red-600 transition-all duration-150"
>
Cancel
</Button>
</div>
)}
</div>
</DialogContent>
</Dialog>
)}
</>
)
}

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

@@ -0,0 +1,125 @@
"use client"
import { LayoutDashboard, HardDrive, Network, Server, Cpu, FileText, SettingsIcon, Terminal } from "lucide-react"
const menuItems = [
{ name: "Overview", href: "/", icon: LayoutDashboard },
{ name: "Storage", href: "/storage", icon: HardDrive },
{ name: "Network", href: "/network", icon: Network },
{ name: "Virtual Machines", href: "/virtual-machines", icon: Server },
{ name: "Hardware", href: "/hardware", icon: Cpu },
{ name: "System Logs", href: "/logs", icon: FileText },
{ name: "Terminal", href: "/terminal", icon: Terminal },
{ name: "Settings", href: "/settings", icon: SettingsIcon },
]
const Sidebar = ({ currentPath, setOpen }) => {
const handleNavigation = (tabName: string) => {
// Dispatch custom event to change tab in dashboard
const event = new CustomEvent("changeTab", { detail: { tab: tabName } })
window.dispatchEvent(event)
setOpen(false)
}
return (
<div>
<button
onClick={() => handleNavigation("overview")}
className={`flex items-center gap-3 px-3 py-2 rounded-lg transition-colors ${
currentPath === "/" || currentPath === "/overview"
? "bg-blue-500/10 text-blue-500"
: "text-muted-foreground hover:text-foreground hover:bg-accent"
}`}
>
<LayoutDashboard className="h-5 w-5" />
<span>Overview</span>
</button>
<button
onClick={() => handleNavigation("storage")}
className={`flex items-center gap-3 px-3 py-2 rounded-lg transition-colors ${
currentPath === "/storage"
? "bg-blue-500/10 text-blue-500"
: "text-muted-foreground hover:text-foreground hover:bg-accent"
}`}
>
<HardDrive className="h-5 w-5" />
<span>Storage</span>
</button>
<button
onClick={() => handleNavigation("network")}
className={`flex items-center gap-3 px-3 py-2 rounded-lg transition-colors ${
currentPath === "/network"
? "bg-blue-500/10 text-blue-500"
: "text-muted-foreground hover:text-foreground hover:bg-accent"
}`}
>
<Network className="h-5 w-5" />
<span>Network</span>
</button>
<button
onClick={() => handleNavigation("vms")}
className={`flex items-center gap-3 px-3 py-2 rounded-lg transition-colors ${
currentPath === "/virtual-machines"
? "bg-blue-500/10 text-blue-500"
: "text-muted-foreground hover:text-foreground hover:bg-accent"
}`}
>
<Server className="h-5 w-5" />
<span>VMs & LXCs</span>
</button>
<button
onClick={() => handleNavigation("hardware")}
className={`flex items-center gap-3 px-3 py-2 rounded-lg transition-colors ${
currentPath === "/hardware"
? "bg-blue-500/10 text-blue-500"
: "text-muted-foreground hover:text-foreground hover:bg-accent"
}`}
>
<Cpu className="h-5 w-5" />
<span>Hardware</span>
</button>
<button
onClick={() => handleNavigation("logs")}
className={`flex items-center gap-3 px-3 py-2 rounded-lg transition-colors ${
currentPath === "/logs"
? "bg-blue-500/10 text-blue-500"
: "text-muted-foreground hover:text-foreground hover:bg-accent"
}`}
>
<FileText className="h-5 w-5" />
<span>System Logs</span>
</button>
<button
onClick={() => handleNavigation("terminal")}
className={`flex items-center gap-3 px-3 py-2 rounded-lg transition-colors ${
currentPath === "/terminal"
? "bg-blue-500/10 text-blue-500"
: "text-muted-foreground hover:text-foreground hover:bg-accent"
}`}
>
<Terminal className="h-5 w-5" />
<span>Terminal</span>
</button>
<button
onClick={() => handleNavigation("settings")}
className={`flex items-center gap-3 px-3 py-2 rounded-lg transition-colors ${
currentPath === "/settings"
? "bg-blue-500/10 text-blue-500"
: "text-muted-foreground hover:text-foreground hover:bg-accent"
}`}
>
<SettingsIcon className="h-5 w-5" />
<span>Settings</span>
</button>
</div>
)
}
export default Sidebar

View File

@@ -0,0 +1,238 @@
"use client"
import { useState, useEffect } from "react"
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card"
import { Progress } from "./ui/progress"
import { Badge } from "./ui/badge"
import { HardDrive, Database, Archive, AlertTriangle, CheckCircle, Activity, AlertCircle } from "lucide-react"
import { formatStorage } from "@/lib/utils"
interface StorageData {
total: number
used: number
available: number
disks: DiskInfo[]
}
interface DiskInfo {
name: string
mountpoint: string
fstype: string
total: number
used: number
available: number
usage_percent: number
health: string
temperature: number
}
const fetchStorageData = async (): Promise<StorageData | null> => {
try {
console.log("[v0] Fetching storage data from Flask server...")
const response = await fetch("/api/storage", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
signal: AbortSignal.timeout(5000),
})
if (!response.ok) {
throw new Error(`Flask server responded with status: ${response.status}`)
}
const data = await response.json()
console.log("[v0] Successfully fetched storage data from Flask:", data)
return data
} catch (error) {
console.error("[v0] Failed to fetch storage data from Flask server:", error)
return null
}
}
export function StorageMetrics() {
const [storageData, setStorageData] = useState<StorageData | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
const fetchData = async () => {
setLoading(true)
setError(null)
const result = await fetchStorageData()
if (!result) {
setError("Flask server not available. Please ensure the server is running.")
} else {
setStorageData(result)
}
setLoading(false)
}
fetchData()
const interval = setInterval(fetchData, 60000)
return () => clearInterval(interval)
}, [])
if (loading) {
return (
<div className="space-y-6">
<div className="text-center py-8">
<div className="text-lg font-medium text-foreground mb-2">Loading storage data...</div>
</div>
</div>
)
}
if (error || !storageData) {
return (
<div className="space-y-6">
<Card className="bg-red-500/10 border-red-500/20">
<CardContent className="p-6">
<div className="flex items-center gap-3 text-red-600">
<AlertCircle className="h-6 w-6" />
<div>
<div className="font-semibold text-lg mb-1">Flask Server Not Available</div>
<div className="text-sm">
{error || "Unable to connect to the Flask server. Please ensure the server is running and try again."}
</div>
</div>
</div>
</CardContent>
</Card>
</div>
)
}
const usagePercent = storageData.total > 0 ? (storageData.used / storageData.total) * 100 : 0
return (
<div className="space-y-6">
{/* Storage Overview Cards */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3 lg:gap-6">
<Card className="bg-card border-border">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">Total Storage</CardTitle>
<HardDrive className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-xl lg:text-2xl font-bold text-foreground">{formatStorage(storageData.total)}</div>
<Progress value={usagePercent} className="mt-2" />
<p className="text-xs text-muted-foreground mt-2">
{formatStorage(storageData.used)} used {formatStorage(storageData.available)} available
</p>
</CardContent>
</Card>
<Card className="bg-card border-border">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">Used Storage</CardTitle>
<Database className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-xl lg:text-2xl font-bold text-foreground">{formatStorage(storageData.used)}</div>
<Progress value={usagePercent} className="mt-2" />
<p className="text-xs text-muted-foreground mt-2">{usagePercent.toFixed(1)}% of total space</p>
</CardContent>
</Card>
<Card className="bg-card border-border">
<CardHeader>
<CardTitle className="text-foreground flex items-center">
<Archive className="h-5 w-5 mr-2" />
Available
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-xl lg:text-2xl font-bold text-foreground">{formatStorage(storageData.available)}</div>
<div className="flex items-center mt-2">
<Badge variant="outline" className="bg-green-500/10 text-green-500 border-green-500/20">
{((storageData.available / storageData.total) * 100).toFixed(1)}% Free
</Badge>
</div>
<p className="text-xs text-muted-foreground mt-2">Available space</p>
</CardContent>
</Card>
<Card className="bg-card border-border">
<CardHeader>
<CardTitle className="text-foreground flex items-center">
<Activity className="h-5 w-5 mr-2" />
Disks
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-xl lg:text-2xl font-bold text-foreground">{storageData.disks.length}</div>
<div className="flex items-center space-x-2 mt-2">
<Badge variant="outline" className="bg-green-500/10 text-green-500 border-green-500/20">
{storageData.disks.filter((d) => d.health === "healthy").length} Healthy
</Badge>
</div>
<p className="text-xs text-muted-foreground mt-2">Storage devices</p>
</CardContent>
</Card>
</div>
{/* Disk Details */}
<Card className="bg-card border-border">
<CardHeader>
<CardTitle className="text-foreground flex items-center">
<Database className="h-5 w-5 mr-2" />
Storage Devices
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
{storageData.disks.map((disk, index) => (
<div
key={index}
className="flex items-center justify-between p-4 rounded-lg border border-border bg-card/50"
>
<div className="flex items-center space-x-4">
<HardDrive className="h-5 w-5 text-muted-foreground" />
<div>
<div className="font-medium text-foreground">{disk.name}</div>
<div className="text-sm text-muted-foreground">
{disk.fstype} {disk.mountpoint}
</div>
</div>
</div>
<div className="flex items-center space-x-6">
<div className="text-right">
<div className="text-sm font-medium text-foreground">
{formatStorage(disk.used)} / {formatStorage(disk.total)}
</div>
<Progress value={disk.usage_percent} className="w-24 mt-1" />
</div>
<div className="text-center">
<div className="text-sm text-muted-foreground">Temp</div>
<div className="text-sm font-medium text-foreground">{disk.temperature}°C</div>
</div>
<Badge
variant="outline"
className={
disk.health === "healthy"
? "bg-green-500/10 text-green-500 border-green-500/20"
: "bg-yellow-500/10 text-yellow-500 border-yellow-500/20"
}
>
{disk.health === "healthy" ? (
<CheckCircle className="h-3 w-3 mr-1" />
) : (
<AlertTriangle className="h-3 w-3 mr-1" />
)}
{disk.health}
</Badge>
</div>
</div>
))}
</div>
</CardContent>
</Card>
</div>
)
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,838 @@
"use client"
import { useState, useEffect } from "react"
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card"
import { Progress } from "./ui/progress"
import { Badge } from "./ui/badge"
import { Cpu, MemoryStick, Thermometer, Server, Zap, AlertCircle, HardDrive, Network } from "lucide-react"
import { NodeMetricsCharts } from "./node-metrics-charts"
import { NetworkTrafficChart } from "./network-traffic-chart"
import { TemperatureDetailModal } from "./temperature-detail-modal"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"
import { fetchApi } from "../lib/api-config"
import { formatNetworkTraffic, getNetworkUnit } from "../lib/format-network"
import { formatStorage } from "../lib/utils"
import { Area, AreaChart, ResponsiveContainer } from "recharts"
interface TempDataPoint {
timestamp: number
value: number
}
interface SystemData {
cpu_usage: number
memory_usage: number
memory_total: number
memory_used: number
temperature: number
temperature_sparkline?: TempDataPoint[]
uptime: string
load_average: number[]
hostname: string
node_id: string
timestamp: string
cpu_cores?: number
cpu_threads?: number
proxmox_version?: string
kernel_version?: string
available_updates?: number
}
interface VMData {
vmid: number
name: string
status: string
cpu: number
mem: number
maxmem: number
disk: number
maxdisk: number
uptime: number
type?: string
}
interface StorageData {
total: number
used: number
available: number
disk_count: number
disks: Array<{
name: string
mountpoint: string
total: number
used: number
available: number
usage_percent: number
}>
}
interface NetworkData {
interfaces: Array<{
name: string
status: string
addresses: Array<{ ip: string; netmask: string }>
}>
traffic: {
bytes_sent: number
bytes_recv: number
packets_sent: number
packets_recv: number
}
physical_active_count?: number
physical_total_count?: number
bridge_active_count?: number
bridge_total_count?: number
physical_interfaces?: Array<{
name: string
status: string
addresses: Array<{ ip: string; netmask: string }>
}>
bridge_interfaces?: Array<{
name: string
status: string
addresses: Array<{ ip: string; netmask: string }>
}>
}
interface ProxmoxStorageData {
storage: Array<{
name: string
type: string
status: string
total: number
used: number
available: number
percent: number
}>
}
const fetchSystemData = async (retries = 3, delayMs = 500): Promise<SystemData | null> => {
for (let attempt = 0; attempt < retries; attempt++) {
try {
const data = await fetchApi<SystemData>("/api/system")
return data
} catch {
if (attempt === retries - 1) {
// Silent fail - API not available (expected in preview environment)
return null
}
// Wait before retry
await new Promise((resolve) => setTimeout(resolve, delayMs))
}
}
return null
}
const fetchVMData = async (): Promise<VMData[]> => {
try {
const data = await fetchApi<any>("/api/vms")
return Array.isArray(data) ? data : data.vms || []
} catch {
// Silent fail - API not available
return []
}
}
const fetchStorageData = async (): Promise<StorageData | null> => {
try {
const data = await fetchApi<StorageData>("/api/storage/summary")
return data
} catch {
return null
}
}
const fetchNetworkData = async (): Promise<NetworkData | null> => {
try {
const data = await fetchApi<NetworkData>("/api/network/summary")
return data
} catch {
return null
}
}
const fetchProxmoxStorageData = async (): Promise<ProxmoxStorage[] | null> => {
try {
const data = await fetchApi<ProxmoxStorage[]>("/api/proxmox-storage")
return data
} catch {
return null
}
}
const getUnitsSettings = (): "Bytes" | "Bits" => {
if (typeof window === "undefined") return "Bytes"
const raw = window.localStorage.getItem("proxmenux-network-unit")
return raw && raw.toLowerCase() === "bits" ? "Bits" : "Bytes"
}
export function SystemOverview() {
const [systemData, setSystemData] = useState<SystemData | null>(null)
const [vmData, setVmData] = useState<VMData[]>([])
const [storageData, setStorageData] = useState<StorageData | null>(null)
const [proxmoxStorageData, setProxmoxStorageData] = useState<ProxmoxStorageData | null>(null)
const [networkData, setNetworkData] = useState<NetworkData | null>(null)
const [loadingStates, setLoadingStates] = useState({
system: true,
vms: true,
storage: true,
network: true,
})
const [error, setError] = useState<string | null>(null)
const [hasAttemptedLoad, setHasAttemptedLoad] = useState(false) // Added hasAttemptedLoad state
const [networkTimeframe, setNetworkTimeframe] = useState("day")
const [networkTotals, setNetworkTotals] = useState<{ received: number; sent: number }>({ received: 0, sent: 0 })
const [networkUnit, setNetworkUnit] = useState<"Bytes" | "Bits">("Bytes") // Added networkUnit state
const [tempModalOpen, setTempModalOpen] = useState(false)
useEffect(() => {
const fetchAllData = async () => {
const [systemResult, vmResult, storageResults, networkResult] = await Promise.all([
fetchSystemData().finally(() => setLoadingStates((prev) => ({ ...prev, system: false }))),
fetchVMData().finally(() => setLoadingStates((prev) => ({ ...prev, vms: false }))),
Promise.all([fetchStorageData(), fetchProxmoxStorageData()]).finally(() =>
setLoadingStates((prev) => ({ ...prev, storage: false })),
),
fetchNetworkData().finally(() => setLoadingStates((prev) => ({ ...prev, network: false }))),
])
setHasAttemptedLoad(true)
if (!systemResult) {
setError("Flask server not available. Please ensure the server is running.")
return
}
setSystemData(systemResult)
setVmData(vmResult)
setStorageData(storageResults[0])
setProxmoxStorageData(storageResults[1])
setNetworkData(networkResult)
setTimeout(async () => {
const refreshedSystemData = await fetchSystemData()
if (refreshedSystemData) {
setSystemData(refreshedSystemData)
}
}, 2000)
}
fetchAllData()
const systemInterval = setInterval(async () => {
const data = await fetchSystemData()
if (data) setSystemData(data)
}, 5000)
const vmInterval = setInterval(async () => {
const data = await fetchVMData()
setVmData(data)
}, 59000)
const storageInterval = setInterval(async () => {
const [storage, proxmoxStorage] = await Promise.all([fetchStorageData(), fetchProxmoxStorageData()])
if (storage) setStorageData(storage)
if (proxmoxStorage) setProxmoxStorageData(proxmoxStorage)
}, 59000)
const networkInterval = setInterval(async () => {
const data = await fetchNetworkData()
if (data) setNetworkData(data)
}, 59000)
setNetworkUnit(getNetworkUnit()) // Load initial setting
const handleUnitChange = (e: CustomEvent) => {
setNetworkUnit(e.detail === "Bits" ? "Bits" : "Bytes")
}
window.addEventListener("networkUnitChanged" as any, handleUnitChange)
return () => {
clearInterval(systemInterval)
clearInterval(vmInterval)
clearInterval(storageInterval)
clearInterval(networkInterval)
window.removeEventListener("networkUnitChanged" as any, handleUnitChange)
}
}, [])
if (!hasAttemptedLoad || loadingStates.system) {
return (
<div className="flex flex-col items-center justify-center min-h-[400px] gap-4">
<div className="relative">
<div className="h-12 w-12 rounded-full border-2 border-muted"></div>
<div className="absolute inset-0 h-12 w-12 rounded-full border-2 border-transparent border-t-primary animate-spin"></div>
</div>
<div className="text-sm font-medium text-foreground">Loading system overview...</div>
<p className="text-xs text-muted-foreground">Fetching system status and metrics</p>
</div>
)
}
if (error || !systemData) {
return (
<div className="space-y-6">
<Card className="bg-red-500/10 border-red-500/20">
<CardContent className="p-6">
<div className="flex items-center gap-3 text-red-600">
<AlertCircle className="h-6 w-6" />
<div>
<div className="font-semibold text-lg mb-1">Flask Server Not Available</div>
<div className="text-sm">
{error || "Unable to connect to the Flask server. Please ensure the server is running and try again."}
</div>
</div>
</div>
</CardContent>
</Card>
</div>
)
}
const vmStats = {
total: vmData.length,
running: vmData.filter((vm) => vm.status === "running").length,
stopped: vmData.filter((vm) => vm.status === "stopped").length,
lxc: vmData.filter((vm) => vm.type === "lxc").length,
vms: vmData.filter((vm) => vm.type === "qemu" || !vm.type).length,
}
const getTemperatureStatus = (temp: number) => {
if (temp === 0) return { status: "N/A", color: "bg-gray-500/10 text-gray-500 border-gray-500/20" }
if (temp < 60) return { status: "Normal", color: "bg-green-500/10 text-green-500 border-green-500/20" }
if (temp < 75) return { status: "Warm", color: "bg-yellow-500/10 text-yellow-500 border-yellow-500/20" }
return { status: "Hot", color: "bg-red-500/10 text-red-500 border-red-500/20" }
}
const formatUptime = (seconds: number) => {
if (!seconds || seconds === 0) return "Stopped"
const days = Math.floor(seconds / 86400)
const hours = Math.floor((seconds % 86400) / 3600)
const minutes = Math.floor((seconds % 3600) / 60)
if (days > 0) return `${days}d ${hours}h`
if (hours > 0) return `${hours}h ${minutes}m`
return `${minutes}m`
}
const formatBytes = (bytes: number) => {
return (bytes / 1024 ** 3).toFixed(2)
}
const tempStatus = getTemperatureStatus(systemData.temperature)
const localStorage = proxmoxStorageData?.storage.find((s) => s.name === "local")
const vmLxcStorages = proxmoxStorageData?.storage.filter(
(s) =>
(s.type === "lvm" || s.type === "lvmthin" || s.type === "zfspool" || s.type === "btrfs" || s.type === "dir") &&
s.type !== "nfs" &&
s.type !== "cifs" &&
s.type !== "iscsi" &&
s.name !== "local",
)
const vmLxcStorageTotal = vmLxcStorages?.reduce((acc, s) => acc + s.total, 0) || 0
const vmLxcStorageUsed = vmLxcStorages?.reduce((acc, s) => acc + s.used, 0) || 0
const vmLxcStorageAvailable = vmLxcStorages?.reduce((acc, s) => acc + s.available, 0) || 0
const vmLxcStoragePercent = vmLxcStorageTotal > 0 ? (vmLxcStorageUsed / vmLxcStorageTotal) * 100 : 0
const getLoadStatus = (load: number, cores: number) => {
if (load < cores) {
return { status: "Normal", color: "bg-green-500/10 text-green-500 border-green-500/20" }
} else if (load < cores * 1.5) {
return { status: "Moderate", color: "bg-yellow-500/10 text-yellow-500 border-yellow-500/20" }
} else {
return { status: "High", color: "bg-red-500/10 text-red-500 border-red-500/20" }
}
}
const systemAlerts = []
if (systemData.available_updates && systemData.available_updates > 0) {
systemAlerts.push({
type: "warning",
message: `${systemData.available_updates} updates available`,
})
}
if (vmStats.stopped > 0) {
systemAlerts.push({
type: "info",
message: `${vmStats.stopped} VM${vmStats.stopped > 1 ? "s" : ""} stopped`,
})
}
if (systemData.temperature > 75) {
systemAlerts.push({
type: "warning",
message: "High temperature detected",
})
}
if (localStorage && localStorage.percent > 90) {
systemAlerts.push({
type: "warning",
message: "System storage almost full",
})
}
const loadStatus = getLoadStatus(systemData.load_average[0], systemData.cpu_cores || 8)
const getTimeframeLabel = (timeframe: string): string => {
switch (timeframe) {
case "hour":
return "1h"
case "day":
return "24h"
case "week":
return "7d"
case "month":
return "30d"
case "year":
return "1y"
default:
return timeframe
}
}
return (
<div className="space-y-6">
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3 lg:gap-6">
<Card className="bg-card border-border">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">CPU Usage</CardTitle>
<Cpu className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-xl lg:text-2xl font-bold text-foreground">{systemData.cpu_usage}%</div>
<Progress value={systemData.cpu_usage} className="mt-2 [&>div]:bg-blue-500" />
<p className="text-xs text-muted-foreground mt-2">Real-time usage</p>
</CardContent>
</Card>
<Card className="bg-card border-border">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">Memory Usage</CardTitle>
<MemoryStick className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-xl lg:text-2xl font-bold text-foreground">{systemData.memory_used.toFixed(1)} GB</div>
<Progress value={systemData.memory_usage} className="mt-2 [&>div]:bg-blue-500" />
<p className="text-xs text-muted-foreground mt-2">
<span className="text-green-500 font-medium">{systemData.memory_usage.toFixed(1)}%</span> of{" "}
{systemData.memory_total} GB
</p>
</CardContent>
</Card>
<Card className="bg-card border-border">
<CardHeader>
<CardTitle className="text-foreground flex items-center">
<Server className="h-5 w-5 mr-2" />
Active VM & LXC
</CardTitle>
</CardHeader>
<CardContent>
{loadingStates.vms ? (
<div className="space-y-2 animate-pulse">
<div className="h-8 bg-muted rounded w-12"></div>
<div className="h-5 bg-muted rounded w-24"></div>
<div className="h-4 bg-muted rounded w-32"></div>
</div>
) : (
<>
<div className="text-xl lg:text-2xl font-bold text-foreground">{vmStats.running}</div>
<div className="mt-2 flex flex-wrap gap-1">
<Badge variant="outline" className="bg-green-500/10 text-green-500 border-green-500/20">
{vmStats.running} Running
</Badge>
{vmStats.stopped > 0 && (
<Badge variant="outline" className="bg-red-500/10 text-red-500 border-red-500/20">
{vmStats.stopped} Stopped
</Badge>
)}
</div>
<p className="text-xs text-muted-foreground mt-2">
Total: {vmStats.vms} VMs, {vmStats.lxc} LXC
</p>
</>
)}
</CardContent>
</Card>
<Card
className={`bg-card border-border ${systemData.temperature > 0 ? "cursor-pointer hover:bg-white/5 transition-colors" : ""}`}
onClick={() => systemData.temperature > 0 && setTempModalOpen(true)}
>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">Temperature</CardTitle>
<Thermometer className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="flex items-center justify-between">
<span className="text-xl lg:text-2xl font-bold text-foreground">
{systemData.temperature === 0 ? "N/A" : `${Math.round(systemData.temperature * 10) / 10}°C`}
</span>
<Badge variant="outline" className={tempStatus.color}>
{tempStatus.status}
</Badge>
</div>
{systemData.temperature > 0 && systemData.temperature_sparkline && systemData.temperature_sparkline.length > 1 ? (
<div className="mt-2 h-10">
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={systemData.temperature_sparkline} margin={{ top: 0, right: 0, left: 0, bottom: 0 }}>
<defs>
<linearGradient id="tempSparkGradient" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor={systemData.temperature >= 75 ? "#ef4444" : systemData.temperature >= 60 ? "#f59e0b" : "#22c55e"} stopOpacity={0.3} />
<stop offset="100%" stopColor={systemData.temperature >= 75 ? "#ef4444" : systemData.temperature >= 60 ? "#f59e0b" : "#22c55e"} stopOpacity={0} />
</linearGradient>
</defs>
<Area
type="monotone"
dataKey="value"
stroke={systemData.temperature >= 75 ? "#ef4444" : systemData.temperature >= 60 ? "#f59e0b" : "#22c55e"}
strokeWidth={1.5}
fill="url(#tempSparkGradient)"
dot={false}
isAnimationActive={false}
/>
</AreaChart>
</ResponsiveContainer>
</div>
) : (
<p className="text-xs text-muted-foreground mt-2">
{systemData.temperature === 0 ? "No sensor available" : "Collecting data..."}
</p>
)}
</CardContent>
</Card>
</div>
<TemperatureDetailModal
open={tempModalOpen}
onOpenChange={setTempModalOpen}
liveTemperature={systemData.temperature}
/>
<NodeMetricsCharts />
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card className="bg-card border-border">
<CardHeader>
<CardTitle className="text-foreground flex items-center">
<HardDrive className="h-5 w-5 mr-2" />
Storage Overview
</CardTitle>
</CardHeader>
<CardContent>
{loadingStates.storage ? (
<div className="space-y-4 animate-pulse">
<div className="h-6 bg-muted rounded w-full"></div>
<div className="h-4 bg-muted rounded w-3/4"></div>
<div className="h-4 bg-muted rounded w-2/3"></div>
</div>
) : storageData ? (
<div className="space-y-4">
{(() => {
const totalCapacity = (vmLxcStorageTotal || 0) + (localStorage?.total || 0)
const totalUsed = (vmLxcStorageUsed || 0) + (localStorage?.used || 0)
const totalAvailable = (vmLxcStorageAvailable || 0) + (localStorage?.available || 0)
const totalPercent = totalCapacity > 0 ? (totalUsed / totalCapacity) * 100 : 0
return totalCapacity > 0 ? (
<div className="space-y-2 pb-4 border-b-2 border-border">
<div className="flex justify-between items-center">
<span className="text-sm font-medium text-foreground">Total Node Capacity:</span>
<span className="text-lg font-bold text-foreground">
{formatStorage(totalCapacity)}
</span>
</div>
<Progress
value={totalPercent}
className="mt-2 h-3 [&>div]:bg-gradient-to-r [&>div]:from-blue-500 [&>div]:to-purple-500"
/>
<div className="flex justify-between items-center mt-1">
<div className="flex items-center gap-3">
<span className="text-xs text-muted-foreground">
Used:{" "}
<span className="font-semibold text-foreground">
{formatStorage(totalUsed)}
</span>
</span>
<span className="text-xs text-muted-foreground">
Free:{" "}
<span className="font-semibold text-green-500">
{formatStorage(totalAvailable)}
</span>
</span>
</div>
<span className="text-xs font-semibold text-muted-foreground">{totalPercent.toFixed(1)}%</span>
</div>
</div>
) : null
})()}
<div className="space-y-2 pb-3 border-b border-border">
<div className="flex justify-between items-center">
<span className="text-sm text-muted-foreground">Total Capacity:</span>
<span className="text-lg font-semibold text-foreground">{storageData.total} TB</span>
</div>
<div className="flex justify-between items-center">
<span className="text-sm text-muted-foreground">Physical Disks:</span>
<span className="text-sm font-semibold text-foreground">
{storageData.disk_count} disk{storageData.disk_count !== 1 ? "s" : ""}
</span>
</div>
</div>
{vmLxcStorages && vmLxcStorages.length > 0 ? (
<div className="space-y-2 pb-3 border-b border-border">
<div className="text-xs font-medium text-muted-foreground mb-2">VM/LXC Storage</div>
<div className="flex justify-between items-center">
<span className="text-xs text-muted-foreground">Used:</span>
<span className="text-sm font-semibold text-foreground">
{formatStorage(vmLxcStorageUsed)}
</span>
</div>
<div className="flex justify-between items-center">
<span className="text-xs text-muted-foreground">Available:</span>
<span className="text-sm font-semibold text-green-500">
{formatStorage(vmLxcStorageAvailable)}
</span>
</div>
<Progress value={vmLxcStoragePercent} className="mt-2 [&>div]:bg-blue-500" />
<div className="flex justify-between items-center mt-1">
<span className="text-xs text-muted-foreground">
{formatStorage(vmLxcStorageUsed)} /{" "}
{formatStorage(vmLxcStorageTotal)}
</span>
<span className="text-xs text-muted-foreground">{vmLxcStoragePercent.toFixed(1)}%</span>
</div>
{vmLxcStorages.length > 1 && (
<div className="text-xs text-muted-foreground mt-1">
{vmLxcStorages.length} storage volume{vmLxcStorages.length > 1 ? "s" : ""}
</div>
)}
</div>
) : (
<div className="space-y-2 pb-3 border-b border-border">
<div className="text-xs font-medium text-muted-foreground mb-2">VM/LXC Storage</div>
<div className="text-center py-4 text-muted-foreground text-sm">No VM/LXC storage configured</div>
</div>
)}
{localStorage && (
<div className="space-y-2">
<div className="text-xs font-medium text-muted-foreground mb-2">Local Storage (System)</div>
<div className="flex justify-between items-center">
<span className="text-xs text-muted-foreground">Used:</span>
<span className="text-sm font-semibold text-foreground">
{formatStorage(localStorage.used)}
</span>
</div>
<div className="flex justify-between items-center">
<span className="text-xs text-muted-foreground">Available:</span>
<span className="text-sm font-semibold text-green-500">
{formatStorage(localStorage.available)}
</span>
</div>
<Progress value={localStorage.percent} className="mt-2 [&>div]:bg-purple-500" />
<div className="flex justify-between items-center mt-1">
<span className="text-xs text-muted-foreground">
{formatStorage(localStorage.used)} /{" "}
{formatStorage(localStorage.total)}
</span>
<span className="text-xs text-muted-foreground">{localStorage.percent.toFixed(1)}%</span>
</div>
</div>
)}
</div>
) : (
<div className="text-center py-8 text-muted-foreground">Storage data not available</div>
)}
</CardContent>
</Card>
<Card className="bg-card border-border">
<CardHeader>
<CardTitle className="text-foreground flex items-center justify-between">
<div className="flex items-center">
<Network className="h-5 w-5 mr-2" />
Network Overview
</div>
<Select value={networkTimeframe} onValueChange={setNetworkTimeframe}>
<SelectTrigger className="w-28 h-8 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="hour">1 Hour</SelectItem>
<SelectItem value="day">24 Hours</SelectItem>
<SelectItem value="week">7 Days</SelectItem>
<SelectItem value="month">30 Days</SelectItem>
<SelectItem value="year">1 Year</SelectItem>
</SelectContent>
</Select>
</CardTitle>
</CardHeader>
<CardContent>
{loadingStates.network ? (
<div className="space-y-4 animate-pulse">
<div className="h-6 bg-muted rounded w-full"></div>
<div className="h-4 bg-muted rounded w-3/4"></div>
<div className="h-4 bg-muted rounded w-2/3"></div>
</div>
) : networkData ? (
<div className="space-y-4">
<div className="flex justify-between items-center pb-3 border-b border-border">
<span className="text-sm text-muted-foreground">Active Interfaces:</span>
<span className="text-lg font-semibold text-foreground">
{(networkData.physical_active_count || 0) + (networkData.bridge_active_count || 0)}
</span>
</div>
<div className="space-y-2">
{networkData.physical_interfaces && networkData.physical_interfaces.length > 0 && (
<div className="flex flex-wrap gap-2">
{networkData.physical_interfaces
.filter((iface) => iface.status === "up")
.map((iface) => (
<Badge
key={iface.name}
variant="outline"
className="bg-blue-500/10 text-blue-500 border-blue-500/20"
>
{iface.name}
</Badge>
))}
</div>
)}
{networkData.bridge_interfaces && networkData.bridge_interfaces.length > 0 && (
<div className="flex flex-wrap gap-2">
{networkData.bridge_interfaces
.filter((iface) => iface.status === "up")
.map((iface) => (
<Badge
key={iface.name}
variant="outline"
className="bg-green-500/10 text-green-500 border-green-500/20"
>
{iface.name}
</Badge>
))}
</div>
)}
</div>
<div className="pt-2 border-t border-border space-y-2">
<div className="flex justify-between items-center">
<span className="text-sm text-muted-foreground">Received:</span>
<span className="text-lg font-semibold text-green-500 flex items-center gap-1">
{" "}
{networkUnit === "Bytes"
? `${networkTotals.received.toFixed(2)} GB`
: formatNetworkTraffic(networkTotals.received * 1024 * 1024 * 1024, "Bits")}
<span className="text-xs text-muted-foreground">({getTimeframeLabel(networkTimeframe)})</span>
</span>
</div>
<div className="flex justify-between items-center">
<span className="text-sm text-muted-foreground">Sent:</span>
<span className="text-lg font-semibold text-blue-500 flex items-center gap-1">
{" "}
{networkUnit === "Bytes"
? `${networkTotals.sent.toFixed(2)} GB`
: formatNetworkTraffic(networkTotals.sent * 1024 * 1024 * 1024, "Bits")}
<span className="text-xs text-muted-foreground">({getTimeframeLabel(networkTimeframe)})</span>
</span>
</div>
</div>
<div className="pt-3 border-t border-border">
<NetworkTrafficChart
timeframe={networkTimeframe}
onTotalsCalculated={setNetworkTotals}
networkUnit={networkUnit}
/>
</div>
</div>
) : (
<div className="text-center py-8 text-muted-foreground">Network data not available</div>
)}
</CardContent>
</Card>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card className="bg-card border-border">
<CardHeader>
<CardTitle className="text-foreground flex items-center">
<Server className="h-5 w-5 mr-2" />
System Information
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<div className="flex justify-between">
<span className="text-muted-foreground">Uptime:</span>
<span className="text-foreground">{systemData.uptime}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Proxmox Version:</span>
<span className="text-foreground">{systemData.proxmox_version || "N/A"}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">Kernel:</span>
<span className="text-foreground font-mono text-sm">{systemData.kernel_version || "Linux"}</span>
</div>
{systemData.available_updates !== undefined && systemData.available_updates > 0 && (
<div className="flex justify-between">
<span className="text-muted-foreground">Available Updates:</span>
<Badge variant="outline" className="bg-yellow-500/10 text-yellow-500 border-yellow-500/20">
{systemData.available_updates} packages
</Badge>
</div>
)}
</CardContent>
</Card>
<Card className="bg-card border-border">
<CardHeader>
<CardTitle className="text-foreground flex items-center">
<Zap className="h-5 w-5 mr-2" />
System Overview
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex justify-between items-center pb-3 border-b border-border">
<div className="flex flex-col">
<span className="text-sm text-muted-foreground">Load Average (1m):</span>
</div>
<div className="flex items-center gap-2">
<span className="text-lg font-semibold text-foreground font-mono">
{systemData.load_average[0].toFixed(2)}
</span>
<Badge variant="outline" className={loadStatus.color}>
{loadStatus.status}
</Badge>
</div>
</div>
<div className="flex justify-between items-center pb-3 border-b border-border">
<span className="text-sm text-muted-foreground">CPU Threads:</span>
<span className="text-lg font-semibold text-foreground">{systemData.cpu_threads || "N/A"}</span>
</div>
<div className="flex justify-between items-center pb-3 border-b border-border">
<span className="text-sm text-muted-foreground">Physical Disks:</span>
<span className="text-lg font-semibold text-foreground">{storageData?.disk_count || "N/A"}</span>
</div>
<div className="flex justify-between items-center">
<span className="text-sm text-muted-foreground">Network Interfaces:</span>
<span className="text-lg font-semibold text-foreground">
{networkData?.physical_total_count || networkData?.physical_interfaces?.length || "N/A"}
</span>
</div>
</CardContent>
</Card>
</div>
</div>
)
}

View File

@@ -0,0 +1,242 @@
"use client"
import { useState, useEffect } from "react"
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "./ui/dialog"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"
import { Badge } from "./ui/badge"
import { Thermometer, TrendingDown, TrendingUp, Minus } from "lucide-react"
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from "recharts"
import { useIsMobile } from "../hooks/use-mobile"
import { fetchApi } from "@/lib/api-config"
const TIMEFRAME_OPTIONS = [
{ value: "hour", label: "1 Hour" },
{ value: "day", label: "24 Hours" },
{ value: "week", label: "7 Days" },
{ value: "month", label: "30 Days" },
]
interface TempHistoryPoint {
timestamp: number
value: number
min?: number
max?: number
}
interface TempStats {
min: number
max: number
avg: number
current: number
}
interface TemperatureDetailModalProps {
open: boolean
onOpenChange: (open: boolean) => void
liveTemperature?: number
}
const CustomTooltip = ({ active, payload, label }: any) => {
if (active && payload && payload.length) {
return (
<div className="bg-gray-900/95 backdrop-blur-sm border border-gray-700 rounded-lg p-3 shadow-xl">
<p className="text-sm font-semibold text-white mb-2">{label}</p>
<div className="space-y-1.5">
{payload.map((entry: any, index: number) => (
<div key={index} className="flex items-center gap-2">
<div className="w-2.5 h-2.5 rounded-full flex-shrink-0" style={{ backgroundColor: entry.color }} />
<span className="text-xs text-gray-300 min-w-[60px]">{entry.name}:</span>
<span className="text-sm font-semibold text-white">{entry.value}°C</span>
</div>
))}
</div>
</div>
)
}
return null
}
const getStatusColor = (temp: number) => {
if (temp >= 75) return "#ef4444"
if (temp >= 60) return "#f59e0b"
return "#22c55e"
}
const getStatusInfo = (temp: number) => {
if (temp === 0) return { status: "N/A", color: "bg-gray-500/10 text-gray-500 border-gray-500/20" }
if (temp < 60) return { status: "Normal", color: "bg-green-500/10 text-green-500 border-green-500/20" }
if (temp < 75) return { status: "Warm", color: "bg-yellow-500/10 text-yellow-500 border-yellow-500/20" }
return { status: "Hot", color: "bg-red-500/10 text-red-500 border-red-500/20" }
}
export function TemperatureDetailModal({ open, onOpenChange, liveTemperature }: TemperatureDetailModalProps) {
const [timeframe, setTimeframe] = useState("hour")
const [data, setData] = useState<TempHistoryPoint[]>([])
const [stats, setStats] = useState<TempStats>({ min: 0, max: 0, avg: 0, current: 0 })
const [loading, setLoading] = useState(true)
const isMobile = useIsMobile()
useEffect(() => {
if (open) {
fetchHistory()
}
}, [open, timeframe])
const fetchHistory = async () => {
setLoading(true)
try {
const result = await fetchApi<{ data: TempHistoryPoint[]; stats: TempStats }>(
`/api/temperature/history?timeframe=${timeframe}`
)
if (result && result.data) {
setData(result.data)
setStats(result.stats)
}
} catch (err) {
console.error("[v0] Failed to fetch temperature history:", err)
} finally {
setLoading(false)
}
}
const formatTime = (timestamp: number) => {
const date = new Date(timestamp * 1000)
if (timeframe === "hour") {
return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
} else if (timeframe === "day") {
return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
} else {
return date.toLocaleDateString([], { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" })
}
}
const chartData = data.map((d) => ({
...d,
time: formatTime(d.timestamp),
}))
// Use live temperature from the overview card (real-time) instead of last DB record
const currentTemp = liveTemperature && liveTemperature > 0 ? Math.round(liveTemperature * 10) / 10 : stats.current
const currentStatus = getStatusInfo(currentTemp)
const chartColor = getStatusColor(currentTemp)
// Calculate Y axis domain based on plotted data values only.
// Stats cards already show the real historical min/max separately.
// Using only graphed values keeps the chart readable and avoids
// large empty gaps caused by momentary spikes that get averaged out.
const values = data.map((d) => d.value)
const yMin = values.length > 0 ? Math.max(0, Math.floor(Math.min(...values) - 3)) : 0
const yMax = values.length > 0 ? Math.ceil(Math.max(...values) + 3) : 100
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-3xl bg-card border-border px-3 sm:px-6">
<DialogHeader>
<div className="flex items-center justify-between pr-6">
<DialogTitle className="text-foreground flex items-center gap-2">
<Thermometer className="h-5 w-5" />
CPU Temperature
</DialogTitle>
<Select value={timeframe} onValueChange={setTimeframe}>
<SelectTrigger className="w-[130px] bg-card border-border">
<SelectValue />
</SelectTrigger>
<SelectContent>
{TIMEFRAME_OPTIONS.map((opt) => (
<SelectItem key={opt.value} value={opt.value}>
{opt.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</DialogHeader>
{/* Stats bar */}
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2 sm:gap-3">
<div className={`rounded-lg p-3 text-center ${currentStatus.color}`}>
<div className="text-xs opacity-80 mb-1">Current</div>
<div className="text-lg font-bold">{currentTemp}°C</div>
</div>
<div className="bg-muted/50 rounded-lg p-3 text-center">
<div className="text-xs text-muted-foreground mb-1 flex items-center justify-center gap-1">
<TrendingDown className="h-3 w-3" /> Min
</div>
<div className="text-lg font-bold text-green-500">{stats.min}°C</div>
</div>
<div className="bg-muted/50 rounded-lg p-3 text-center">
<div className="text-xs text-muted-foreground mb-1 flex items-center justify-center gap-1">
<Minus className="h-3 w-3" /> Avg
</div>
<div className="text-lg font-bold text-foreground">{stats.avg}°C</div>
</div>
<div className="bg-muted/50 rounded-lg p-3 text-center">
<div className="text-xs text-muted-foreground mb-1 flex items-center justify-center gap-1">
<TrendingUp className="h-3 w-3" /> Max
</div>
<div className="text-lg font-bold text-red-500">{stats.max}°C</div>
</div>
</div>
{/* Chart */}
<div className="h-[300px] lg:h-[350px]">
{loading ? (
<div className="h-full flex items-center justify-center">
<div className="space-y-3 w-full animate-pulse">
<div className="h-4 bg-muted rounded w-1/4 mx-auto" />
<div className="h-[250px] bg-muted/50 rounded" />
</div>
</div>
) : chartData.length === 0 ? (
<div className="h-full flex items-center justify-center text-muted-foreground">
<div className="text-center">
<Thermometer className="h-8 w-8 mx-auto mb-2 opacity-50" />
<p>No temperature data available for this period</p>
<p className="text-sm mt-1">Data is collected every 60 seconds</p>
</div>
</div>
) : (
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={chartData} margin={{ top: 10, right: 10, left: 0, bottom: 0 }}>
<defs>
<linearGradient id="tempGradient" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor={chartColor} stopOpacity={0.3} />
<stop offset="100%" stopColor={chartColor} stopOpacity={0.02} />
</linearGradient>
</defs>
<CartesianGrid strokeDasharray="3 3" stroke="currentColor" className="text-border" />
<XAxis
dataKey="time"
stroke="currentColor"
className="text-foreground"
tick={{ fill: "currentColor", fontSize: isMobile ? 10 : 12 }}
interval="preserveStartEnd"
minTickGap={isMobile ? 40 : 60}
/>
<YAxis
domain={[yMin, yMax]}
stroke="currentColor"
className="text-foreground"
tick={{ fill: "currentColor", fontSize: isMobile ? 10 : 12 }}
tickFormatter={(v) => `${v}°`}
width={isMobile ? 40 : 45}
/>
<Tooltip content={<CustomTooltip />} />
<Area
type="monotone"
dataKey="value"
name="Temperature"
stroke={chartColor}
strokeWidth={2}
fill="url(#tempGradient)"
dot={false}
activeDot={{ r: 4, fill: chartColor, stroke: "#fff", strokeWidth: 2 }}
/>
</AreaChart>
</ResponsiveContainer>
)}
</div>
</DialogContent>
</Dialog>
)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
"use client"
import { ThemeProvider as NextThemesProvider } from "next-themes"
import type { ThemeProviderProps } from "next-themes"
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}

View File

@@ -0,0 +1,39 @@
"use client"
import { Moon, Sun } from "lucide-react"
import { useTheme } from "next-themes"
import { useEffect, useState } from "react"
import { Button } from "./ui/button"
export function ThemeToggle() {
const { theme, setTheme } = useTheme()
const [mounted, setMounted] = useState(false)
useEffect(() => {
setMounted(true)
}, [])
const handleThemeToggle = () => {
console.log("[v0] Current theme:", theme)
const newTheme = theme === "light" ? "dark" : "light"
console.log("[v0] Switching to theme:", newTheme)
setTheme(newTheme)
}
if (!mounted) {
return (
<Button variant="outline" size="sm" className="border-border bg-transparent w-9 h-9">
<Sun className="h-4 w-4" />
<span className="sr-only">Toggle theme</span>
</Button>
)
}
return (
<Button variant="outline" size="sm" onClick={handleThemeToggle} className="border-border bg-transparent w-9 h-9">
<Sun className="h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
)
}

View File

@@ -0,0 +1,297 @@
"use client"
import { useState } from "react"
import { Button } from "./ui/button"
import { Input } from "./ui/input"
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "./ui/dialog"
import { AlertCircle, CheckCircle, Copy, Shield, Check } from "lucide-react"
import { getApiUrl } from "../lib/api-config"
interface TwoFactorSetupProps {
open: boolean
onClose: () => void
onSuccess: () => void
}
export function TwoFactorSetup({ open, onClose, onSuccess }: TwoFactorSetupProps) {
const [step, setStep] = useState(1)
const [qrCode, setQrCode] = useState("")
const [secret, setSecret] = useState("")
const [backupCodes, setBackupCodes] = useState<string[]>([])
const [verificationCode, setVerificationCode] = useState("")
const [error, setError] = useState("")
const [loading, setLoading] = useState(false)
const [copiedSecret, setCopiedSecret] = useState(false)
const [copiedCodes, setCopiedCodes] = useState(false)
const handleSetupStart = async () => {
setError("")
setLoading(true)
try {
const token = localStorage.getItem("proxmenux-auth-token")
const response = await fetch(getApiUrl("/api/auth/totp/setup"), {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
})
const data = await response.json()
if (!response.ok) {
throw new Error(data.message || "Failed to setup 2FA")
}
setQrCode(data.qr_code)
setSecret(data.secret)
setBackupCodes(data.backup_codes)
setStep(2)
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to setup 2FA")
} finally {
setLoading(false)
}
}
const handleVerify = async () => {
if (!verificationCode || verificationCode.length !== 6) {
setError("Please enter a 6-digit code")
return
}
setError("")
setLoading(true)
try {
const token = localStorage.getItem("proxmenux-auth-token")
const response = await fetch(getApiUrl("/api/auth/totp/enable"), {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ token: verificationCode }),
})
const data = await response.json()
if (!response.ok) {
throw new Error(data.message || "Invalid verification code")
}
setStep(3)
} catch (err) {
setError(err instanceof Error ? err.message : "Verification failed")
} finally {
setLoading(false)
}
}
const copyToClipboard = async (text: string, type: "secret" | "codes") => {
let ok = false
// Preferred path (HTTPS / localhost). On plain HTTP the Promise rejects,
// so we catch and fall through to the textarea fallback.
try {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(text)
ok = true
}
} catch {
// fall through to execCommand fallback
}
if (!ok) {
try {
const textarea = document.createElement("textarea")
textarea.value = text
textarea.style.position = "fixed"
textarea.style.left = "-9999px"
textarea.style.top = "-9999px"
textarea.style.opacity = "0"
textarea.readOnly = true
document.body.appendChild(textarea)
textarea.focus()
textarea.select()
ok = document.execCommand("copy")
document.body.removeChild(textarea)
} catch {
ok = false
}
}
if (!ok) {
console.error("Failed to copy to clipboard")
return
}
if (type === "secret") {
setCopiedSecret(true)
setTimeout(() => setCopiedSecret(false), 2000)
} else {
setCopiedCodes(true)
setTimeout(() => setCopiedCodes(false), 2000)
}
}
const handleClose = () => {
setStep(1)
setQrCode("")
setSecret("")
setBackupCodes([])
setVerificationCode("")
setError("")
onClose()
}
const handleFinish = () => {
handleClose()
onSuccess()
}
return (
<Dialog open={open} onOpenChange={handleClose}>
<DialogContent className="max-w-md max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Shield className="h-5 w-5 text-blue-500" />
Setup Two-Factor Authentication
</DialogTitle>
<DialogDescription>Add an extra layer of security to your account</DialogDescription>
</DialogHeader>
{error && (
<div className="bg-red-500/10 border border-red-500/20 rounded-lg p-3 flex items-start gap-2">
<AlertCircle className="h-5 w-5 text-red-500 flex-shrink-0 mt-0.5" />
<p className="text-sm text-red-500">{error}</p>
</div>
)}
{step === 1 && (
<div className="space-y-4">
<div className="bg-blue-500/10 border border-blue-500/20 rounded-lg p-4">
<p className="text-sm text-blue-500">
Two-factor authentication (2FA) adds an extra layer of security by requiring a code from your
authentication app in addition to your password.
</p>
</div>
<div className="space-y-2">
<h4 className="font-medium">You will need:</h4>
<ul className="text-sm text-muted-foreground space-y-1 list-disc list-inside">
<li>An authentication app (Google Authenticator, Authy, etc.)</li>
<li>Scan a QR code or enter a key manually</li>
<li>Store backup codes securely</li>
</ul>
</div>
<Button onClick={handleSetupStart} className="w-full bg-blue-500 hover:bg-blue-600" disabled={loading}>
{loading ? "Starting..." : "Start Setup"}
</Button>
</div>
)}
{step === 2 && (
<div className="space-y-4">
<div className="space-y-2">
<h4 className="font-medium">1. Scan the QR code</h4>
<p className="text-sm text-muted-foreground">Open your authentication app and scan this QR code</p>
{qrCode && (
<div className="flex justify-center p-4 bg-white rounded-lg">
<img src={qrCode || "/placeholder.svg"} alt="QR Code" width={200} height={200} className="rounded" />
</div>
)}
</div>
<div className="space-y-2">
<h4 className="font-medium">Or enter the key manually:</h4>
<div className="flex gap-2">
<Input value={secret} readOnly className="font-mono text-sm" />
<Button
variant="outline"
size="icon"
onClick={() => copyToClipboard(secret, "secret")}
title="Copy key"
>
{copiedSecret ? <Check className="h-4 w-4 text-green-500" /> : <Copy className="h-4 w-4" />}
</Button>
</div>
</div>
<div className="space-y-2">
<h4 className="font-medium">2. Enter the verification code</h4>
<p className="text-sm text-muted-foreground">Enter the 6-digit code that appears in your app</p>
<Input
type="text"
placeholder="000000"
value={verificationCode}
onChange={(e) => setVerificationCode(e.target.value.replace(/\D/g, "").slice(0, 6))}
className="text-center text-lg tracking-widest font-mono text-base"
maxLength={6}
disabled={loading}
/>
</div>
<div className="flex gap-2">
<Button onClick={handleVerify} className="flex-1 bg-blue-500 hover:bg-blue-600" disabled={loading}>
{loading ? "Verifying..." : "Verify and Enable"}
</Button>
<Button onClick={handleClose} variant="outline" className="flex-1 bg-transparent" disabled={loading}>
Cancel
</Button>
</div>
</div>
)}
{step === 3 && (
<div className="space-y-4">
<div className="bg-green-500/10 border border-green-500/20 rounded-lg p-4 flex items-start gap-2">
<CheckCircle className="h-5 w-5 text-green-500 flex-shrink-0 mt-0.5" />
<div>
<p className="font-medium text-green-500">2FA Enabled Successfully</p>
<p className="text-sm text-green-500 mt-1">
Your account is now protected with two-factor authentication
</p>
</div>
</div>
<div className="space-y-2">
<h4 className="font-medium text-orange-500">Important: Save your backup codes</h4>
<p className="text-sm text-muted-foreground">
These codes will allow you to access your account if you lose access to your authentication app. Store
them in a safe place.
</p>
<div className="bg-muted/50 rounded-lg p-4 space-y-2">
<div className="flex justify-between items-center mb-2">
<span className="text-sm font-medium">Backup Codes</span>
<Button variant="outline" size="sm" onClick={() => copyToClipboard(backupCodes.join("\n"), "codes")}>
{copiedCodes ? (
<Check className="h-4 w-4 text-green-500 mr-2" />
) : (
<Copy className="h-4 w-4 mr-2" />
)}
Copy All
</Button>
</div>
<div className="grid grid-cols-2 gap-2">
{backupCodes.map((code, index) => (
<div key={index} className="bg-background rounded px-3 py-2 font-mono text-sm text-center">
{code}
</div>
))}
</div>
</div>
</div>
<Button onClick={handleFinish} className="w-full bg-blue-500 hover:bg-blue-600">
Finish
</Button>
</div>
)}
</DialogContent>
</Dialog>
)
}

View File

@@ -0,0 +1,28 @@
import type * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
},
)
export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return <div className={cn(badgeVariants({ variant }), className)} {...props} />
}
export { Badge, badgeVariants }

View File

@@ -0,0 +1,46 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
},
)
Button.displayName = "Button"
export { Button, buttonVariants }

View File

@@ -0,0 +1,42 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)} {...props} />
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />
),
)
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
({ className, ...props }, ref) => (
<h3 ref={ref} className={cn("text-2xl font-semibold leading-none tracking-tight", className)} {...props} />
),
)
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
({ className, ...props }, ref) => (
<p ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
),
)
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />,
)
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn("flex items-center p-6 pt-0", className)} {...props} />
),
)
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }

View File

@@ -0,0 +1,27 @@
"use client"
import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { Check } from "lucide-react"
import { cn } from "@/lib/utils"
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className,
)}
{...props}
>
<CheckboxPrimitive.Indicator className={cn("flex items-center justify-center text-current")}>
<Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
))
Checkbox.displayName = CheckboxPrimitive.Root.displayName
export { Checkbox }

View File

@@ -0,0 +1,102 @@
"use client"
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = DialogPrimitive.Portal
const DialogClose = DialogPrimitive.Close
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className,
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
hideClose?: boolean
}
>(({ className, children, hideClose, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] rounded-lg",
className,
)}
aria-describedby={props["aria-describedby"] || undefined}
{...props}
>
{children}
{!hideClose && (
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)} {...props} />
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)} {...props} />
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold leading-none tracking-tight", className)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogClose,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}

View File

@@ -0,0 +1,257 @@
'use client'
import * as React from 'react'
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
function DropdownMenu({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
}
function DropdownMenuPortal({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
return (
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
)
}
function DropdownMenuTrigger({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
return (
<DropdownMenuPrimitive.Trigger
data-slot="dropdown-menu-trigger"
{...props}
/>
)
}
function DropdownMenuContent({
className,
sideOffset = 4,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
return (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
data-slot="dropdown-menu-content"
sideOffset={sideOffset}
className={cn(
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] origin-[var(--radix-dropdown-menu-content-transform-origin)] overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md',
className,
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
)
}
function DropdownMenuGroup({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
return (
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
)
}
function DropdownMenuItem({
className,
inset,
variant = 'default',
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
variant?: 'default' | 'destructive'
}) {
return (
<DropdownMenuPrimitive.Item
data-slot="dropdown-menu-item"
data-inset={inset}
data-variant={variant}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
/>
)
}
function DropdownMenuCheckboxItem({
className,
children,
checked,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
return (
<DropdownMenuPrimitive.CheckboxItem
data-slot="dropdown-menu-checkbox-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
checked={checked}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
)
}
function DropdownMenuRadioGroup({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
return (
<DropdownMenuPrimitive.RadioGroup
data-slot="dropdown-menu-radio-group"
{...props}
/>
)
}
function DropdownMenuRadioItem({
className,
children,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
return (
<DropdownMenuPrimitive.RadioItem
data-slot="dropdown-menu-radio-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<CircleIcon className="size-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
)
}
function DropdownMenuLabel({
className,
inset,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}) {
return (
<DropdownMenuPrimitive.Label
data-slot="dropdown-menu-label"
data-inset={inset}
className={cn(
'px-2 py-1.5 text-sm font-medium data-[inset]:pl-8',
className,
)}
{...props}
/>
)
}
function DropdownMenuSeparator({
className,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
return (
<DropdownMenuPrimitive.Separator
data-slot="dropdown-menu-separator"
className={cn('bg-border -mx-1 my-1 h-px', className)}
{...props}
/>
)
}
function DropdownMenuShortcut({
className,
...props
}: React.ComponentProps<'span'>) {
return (
<span
data-slot="dropdown-menu-shortcut"
className={cn(
'text-muted-foreground ml-auto text-xs tracking-widest',
className,
)}
{...props}
/>
)
}
function DropdownMenuSub({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
}
function DropdownMenuSubTrigger({
className,
inset,
children,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}) {
return (
<DropdownMenuPrimitive.SubTrigger
data-slot="dropdown-menu-sub-trigger"
data-inset={inset}
className={cn(
'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-none select-none data-[inset]:pl-8',
className,
)}
{...props}
>
{children}
<ChevronRightIcon className="ml-auto size-4" />
</DropdownMenuPrimitive.SubTrigger>
)
}
function DropdownMenuSubContent({
className,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
return (
<DropdownMenuPrimitive.SubContent
data-slot="dropdown-menu-sub-content"
className={cn(
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-[var(--radix-dropdown-menu-content-transform-origin)] overflow-hidden rounded-md border p-1 shadow-lg',
className,
)}
{...props}
/>
)
}
export {
DropdownMenu,
DropdownMenuPortal,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
}

View File

@@ -0,0 +1,22 @@
import * as React from "react"
import { cn } from "@/lib/utils"
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-lg border border-input bg-background px-4 py-2 text-sm shadow-sm transition-all file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 hover:border-ring/50",
className,
)}
ref={ref}
{...props}
/>
)
})
Input.displayName = "Input"
export { Input }

View File

@@ -0,0 +1,17 @@
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "../../lib/utils"
const labelVariants = cva("text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70")
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
))
Label.displayName = LabelPrimitive.Root.displayName
export { Label }

View File

@@ -0,0 +1,25 @@
"use client"
import * as React from "react"
import * as ProgressPrimitive from "@radix-ui/react-progress"
import { cn } from "@/lib/utils"
const Progress = React.forwardRef<
React.ElementRef<typeof ProgressPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
>(({ className, value, ...props }, ref) => (
<ProgressPrimitive.Root
ref={ref}
className={cn("relative h-2 w-full overflow-hidden rounded-full bg-secondary", className)}
{...props}
>
<ProgressPrimitive.Indicator
className="h-full w-full flex-1 bg-primary transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
))
Progress.displayName = ProgressPrimitive.Root.displayName
export { Progress }

View File

@@ -0,0 +1,40 @@
"use client"
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import { cn } from "@/lib/utils"
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root ref={ref} className={cn("relative overflow-hidden", className)} {...props}>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">{children}</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
))
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
"flex touch-none select-none transition-colors",
orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent p-[1px]",
className,
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
))
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
export { ScrollArea, ScrollBar }

View File

@@ -0,0 +1,144 @@
"use client"
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { Check, ChevronDown, ChevronUp } from "lucide-react"
import { cn } from "@/lib/utils"
const Select = SelectPrimitive.Root
const SelectGroup = SelectPrimitive.Group
const SelectValue = SelectPrimitive.Value
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-9 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className,
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
))
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn("flex cursor-default items-center justify-center py-1", className)}
{...props}
>
<ChevronUp className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton>
))
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn("flex cursor-default items-center justify-center py-1", className)}
{...props}
>
<ChevronDown className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton>
))
SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className,
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
))
SelectContent.displayName = SelectPrimitive.Content.displayName
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label ref={ref} className={cn("px-2 py-1.5 text-sm font-semibold", className)} {...props} />
))
SelectLabel.displayName = SelectPrimitive.Label.displayName
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className,
)}
{...props}
>
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
))
SelectItem.displayName = SelectPrimitive.Item.displayName
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-muted", className)} {...props} />
))
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
}

View File

@@ -0,0 +1,109 @@
"use client"
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { cva, type VariantProps } from "class-variance-authority"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
const Sheet = DialogPrimitive.Root
const SheetTrigger = DialogPrimitive.Trigger
const SheetClose = DialogPrimitive.Close
const SheetPortal = DialogPrimitive.Portal
const SheetOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className,
)}
{...props}
ref={ref}
/>
))
SheetOverlay.displayName = DialogPrimitive.Overlay.displayName
const sheetVariants = cva(
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
{
variants: {
side: {
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
bottom:
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
right:
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
},
},
defaultVariants: {
side: "right",
},
},
)
interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>,
VariantProps<typeof sheetVariants> {}
const SheetContent = React.forwardRef<React.ElementRef<typeof DialogPrimitive.Content>, SheetContentProps>(
({ side = "right", className, children, ...props }, ref) => (
<SheetPortal>
<SheetOverlay />
<DialogPrimitive.Content ref={ref} className={cn(sheetVariants({ side }), className)} {...props}>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</SheetPortal>
),
)
SheetContent.displayName = DialogPrimitive.Content.displayName
const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...props} />
)
SheetHeader.displayName = "SheetHeader"
const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)} {...props} />
)
SheetFooter.displayName = "SheetFooter"
const SheetTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title ref={ref} className={cn("text-lg font-semibold text-foreground", className)} {...props} />
))
SheetTitle.displayName = DialogPrimitive.Title.displayName
const SheetDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
))
SheetDescription.displayName = DialogPrimitive.Description.displayName
export {
Sheet,
SheetPortal,
SheetOverlay,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
}

View File

@@ -0,0 +1,29 @@
"use client"
import * as React from "react"
import * as SwitchPrimitives from "@radix-ui/react-switch"
import { cn } from "@/lib/utils"
const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
className
)}
{...props}
ref={ref}
>
<SwitchPrimitives.Thumb
className={cn(
"pointer-events-none block h-4 w-4 rounded-full bg-white shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
)}
/>
</SwitchPrimitives.Root>
))
Switch.displayName = SwitchPrimitives.Root.displayName
export { Switch }

View File

@@ -0,0 +1,52 @@
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"
const Tabs = TabsPrimitive.Root
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
className,
)}
{...props}
/>
))
TabsList.displayName = TabsPrimitive.List.displayName
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
className,
)}
{...props}
/>
))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className,
)}
{...props}
/>
))
TabsContent.displayName = TabsPrimitive.Content.displayName
export { Tabs, TabsList, TabsTrigger, TabsContent }

View File

@@ -0,0 +1,24 @@
import * as React from "react"
import { cn } from "@/lib/utils"
export interface TextareaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, ...props }, ref) => {
return (
<textarea
className={cn(
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
}
)
Textarea.displayName = "Textarea"
export { Textarea }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,77 @@
{
"_description": "Verified AI models for ProxMenux notifications. Only models listed here will be shown to users. Models are tested to work with the chat/completions API format.",
"_updated": "2026-04-19",
"_verifier": "Refreshed with tools/ai-models-verifier (private). Re-run before each ProxMenux release to keep the list current. The verifier and ProxMenux share the same reasoning/thinking-model handlers so their verdicts stay aligned with runtime behaviour.",
"groq": {
"models": [
"llama-3.3-70b-versatile",
"llama-3.1-70b-versatile",
"llama-3.1-8b-instant",
"llama3-70b-8192",
"llama3-8b-8192",
"mixtral-8x7b-32768",
"gemma2-9b-it"
],
"recommended": "llama-3.3-70b-versatile",
"_note": "Not yet re-verified in 2026-04 refresh — kept from previous curation. Run the verifier with a Groq key to prune deprecated entries."
},
"gemini": {
"models": [
"gemini-2.5-flash-lite",
"gemini-2.5-flash",
"gemini-3-flash-preview"
],
"recommended": "gemini-2.5-flash-lite",
"_note": "flash-lite / flash pass the verifier consistently; pro variants reject thinkingBudget=0 and are overkill for notification translation anyway. 'latest' aliases (gemini-flash-latest, gemini-flash-lite-latest) are intentionally omitted because they resolved to different models across runs and produced timeouts in some regions.",
"_deprecated": ["gemini-2.0-flash", "gemini-2.0-flash-lite", "gemini-1.5-flash", "gemini-1.0-pro", "gemini-pro"]
},
"openai": {
"models": [
"gpt-4.1-nano",
"gpt-4.1-mini",
"gpt-4o-mini",
"gpt-4.1",
"gpt-4o",
"gpt-5-chat-latest",
"gpt-5.4-nano",
"gpt-5.4-mini"
],
"recommended": "gpt-4.1-nano",
"_note": "Reasoning models (o-series, gpt-5/5.1/5.2 non-chat variants) are supported by openai_provider.py via max_completion_tokens + reasoning_effort=minimal, but not listed here by default: their latency is higher than the chat models and they do not improve translation quality for notifications. Add specific reasoning IDs to this list only if a user explicitly wants them."
},
"anthropic": {
"models": [
"claude-3-5-haiku-latest",
"claude-3-5-sonnet-latest",
"claude-3-opus-latest"
],
"recommended": "claude-3-5-haiku-latest",
"_note": "Not re-verified in 2026-04 refresh — kept from previous curation. Add claude-4.x / claude-4.5 / claude-4.6 / claude-4.7 variants after running the verifier with an Anthropic key."
},
"openrouter": {
"models": [
"meta-llama/llama-3.3-70b-instruct",
"meta-llama/llama-3.1-70b-instruct",
"meta-llama/llama-3.1-8b-instruct",
"anthropic/claude-3.5-haiku",
"anthropic/claude-3.5-sonnet",
"google/gemini-flash-1.5",
"openai/gpt-4o-mini",
"mistralai/mistral-7b-instruct",
"mistralai/mixtral-8x7b-instruct"
],
"recommended": "meta-llama/llama-3.3-70b-instruct",
"_note": "Not re-verified in 2026-04 refresh. google/gemini-flash-2.5-flash-lite was malformed in the previous entry and has been replaced with google/gemini-flash-1.5."
},
"ollama": {
"_note": "Ollama models are local, we don't filter them. User manages their own models.",
"models": [],
"recommended": ""
}
}

View File

@@ -0,0 +1,23 @@
"use client"
import { useEffect, useState } from "react"
export function useIsMobile() {
const [isMobile, setIsMobile] = useState(false)
useEffect(() => {
const checkMobile = () => {
setIsMobile(window.innerWidth < 768)
}
// Check on mount
checkMobile()
// Listen for resize
window.addEventListener("resize", checkMobile)
return () => window.removeEventListener("resize", checkMobile)
}, [])
return isMobile
}

114
AppImage/lib/api-config.ts Normal file
View File

@@ -0,0 +1,114 @@
/**
* API Configuration for ProxMenux Monitor
* Handles API URL generation with automatic proxy detection
*/
/**
* API Server Port Configuration
* Default: 8008 (production)
* Can be changed to 8009 for beta testing
* This can also be set via NEXT_PUBLIC_API_PORT environment variable
*/
export const API_PORT = process.env.NEXT_PUBLIC_API_PORT || "8008"
/**
* Gets the base URL for API calls
* Automatically detects if running behind a proxy by checking if we're on a standard port
*
* @returns Base URL for API endpoints
*/
export function getApiBaseUrl(): string {
if (typeof window === "undefined") {
return ""
}
const { protocol, hostname, port } = window.location
// If accessing via standard ports (80/443) or no port, assume we're behind a proxy
// In this case, use relative URLs so the proxy handles routing
const isStandardPort = port === "" || port === "80" || port === "443"
if (isStandardPort) {
return ""
} else {
return `${protocol}//${hostname}:${API_PORT}`
}
}
/**
* Constructs a full API URL
*
* @param endpoint - API endpoint path (e.g., '/api/system')
* @returns Full API URL
*/
export function getApiUrl(endpoint: string): string {
const baseUrl = getApiBaseUrl()
// Ensure endpoint starts with /
const normalizedEndpoint = endpoint.startsWith("/") ? endpoint : `/${endpoint}`
return `${baseUrl}${normalizedEndpoint}`
}
/**
* Gets the JWT token from localStorage
*
* @returns JWT token or null if not authenticated
*/
export function getAuthToken(): string | null {
if (typeof window === "undefined") {
return null
}
return localStorage.getItem("proxmenux-auth-token")
}
/**
* Fetches data from an API endpoint with error handling
*
* @param endpoint - API endpoint path
* @param options - Fetch options
* @returns Promise with the response data
*/
export async function fetchApi<T>(endpoint: string, options?: RequestInit): Promise<T> {
const url = getApiUrl(endpoint)
const token = getAuthToken()
const headers: Record<string, string> = {
"Content-Type": "application/json",
...(options?.headers as Record<string, string>),
}
if (token) {
headers["Authorization"] = `Bearer ${token}`
}
const response = await fetch(url, {
...options,
headers,
cache: "no-store",
})
if (!response.ok) {
if (response.status === 401) {
console.error("[v0] fetchApi: 401 UNAUTHORIZED -", endpoint, "- Token present:", !!token)
throw new Error(`Unauthorized: ${endpoint}`)
}
throw new Error(`API request failed: ${response.status} ${response.statusText}`)
}
// Check content type to ensure we're getting JSON
const contentType = response.headers.get("content-type")
if (!contentType || !contentType.includes("application/json")) {
const text = await response.text()
console.error("[v0] fetchApi: Expected JSON but got:", contentType, "- Body preview:", text.substring(0, 200))
throw new Error(`Expected JSON response but got ${contentType || "unknown content type"}`)
}
try {
return await response.json()
} catch (jsonError) {
console.error("[v0] fetchApi: JSON parse error for", endpoint, "-", jsonError)
throw new Error(`Invalid JSON response from ${endpoint}`)
}
}

View File

@@ -0,0 +1,68 @@
/**
* Utility functions for formatting network traffic data
* Supports conversion between Bytes and Bits based on user preferences
*/
export type NetworkUnit = 'Bytes' | 'Bits';
/**
* Format network traffic value with appropriate unit
* @param bytes - Value in bytes
* @param unit - Target unit ('Bytes' or 'Bits')
* @param decimals - Number of decimal places (default: 2)
* @returns Formatted string with value and unit
*/
export function formatNetworkTraffic(
bytes: number,
unit: NetworkUnit = 'Bytes',
decimals: number = 2
): string {
if (bytes === 0) return unit === 'Bits' ? '0 b' : '0 B';
const k = unit === 'Bits' ? 1000 : 1024;
const dm = decimals < 0 ? 0 : Math.min(decimals, 2);
// For Bits: convert bytes to bits first (multiply by 8)
const value = unit === 'Bits' ? bytes * 8 : bytes;
const sizes = unit === 'Bits'
? ['b', 'Kb', 'Mb', 'Gb', 'Tb', 'Pb']
: ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
const i = Math.floor(Math.log(value) / Math.log(k));
const finalDecimals = 2; // Always use 2 decimals for consistency
const formattedValue = parseFloat((value / Math.pow(k, i)).toFixed(finalDecimals));
return `${formattedValue} ${sizes[i]}`;
}
/**
* Get the current network unit preference from localStorage
* @returns 'Bytes' or 'Bits'
*/
export function getNetworkUnit(): NetworkUnit {
if (typeof window === 'undefined') return 'Bytes';
const stored = localStorage.getItem('proxmenux-network-unit');
return stored === 'Bits' ? 'Bits' : 'Bytes';
}
/**
* Get the label for network traffic based on current unit
* @param direction - 'received' or 'sent'
* @returns Label string
*/
export function getNetworkLabel(direction: 'received' | 'sent'): string {
const unit = getNetworkUnit();
const prefix = direction === 'received' ? 'Received' : 'Sent';
return unit === 'Bits' ? `${prefix}` : `${prefix}`;
}
/**
* Get the unit suffix for displaying in charts
* @returns Unit suffix string (e.g., 'GB' or 'Gb')
*/
export function getNetworkUnitSuffix(): string {
const unit = getNetworkUnit();
return unit === 'Bits' ? 'b' : 'B';
}

View File

@@ -0,0 +1,39 @@
import { exec } from "child_process"
import { promisify } from "util"
const execAsync = promisify(exec)
interface ScriptExecutorOptions {
env?: Record<string, string>
timeout?: number
}
interface ScriptResult {
stdout: string
stderr: string
exitCode: number
}
export async function executeScript(scriptPath: string, options: ScriptExecutorOptions = {}): Promise<ScriptResult> {
const { env = {}, timeout = 300000 } = options // 5 minutes default timeout
try {
const { stdout, stderr } = await execAsync(`bash ${scriptPath}`, {
env: { ...process.env, ...env },
timeout,
maxBuffer: 1024 * 1024 * 10, // 10MB buffer
})
return {
stdout,
stderr,
exitCode: 0,
}
} catch (error: any) {
return {
stdout: error.stdout || "",
stderr: error.stderr || error.message || "Unknown error",
exitCode: error.code || 1,
}
}
}

21
AppImage/lib/utils.ts Normal file
View File

@@ -0,0 +1,21 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
export function formatStorage(sizeInGB: number): string {
if (sizeInGB < 1) {
// Less than 1 GB, show in MB
const mb = sizeInGB * 1024
return `${mb % 1 === 0 ? mb.toFixed(0) : mb.toFixed(1)} MB`
} else if (sizeInGB < 1024) {
// Less than 1024 GB, show in GB
return `${sizeInGB % 1 === 0 ? sizeInGB.toFixed(0) : sizeInGB.toFixed(1)} GB`
} else {
// 1024 GB or more, show in TB
const tb = sizeInGB / 1024
return `${tb % 1 === 0 ? tb.toFixed(0) : tb.toFixed(1)} TB`
}
}

30
AppImage/next.config.mjs Normal file
View File

@@ -0,0 +1,30 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
trailingSlash: true,
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
ignoreBuildErrors: true,
},
images: {
unoptimized: true,
},
experimental: {
esmExternals: 'loose',
},
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.fallback = {
...config.resolve.fallback,
fs: false,
net: false,
tls: false,
};
}
return config;
},
};
export default nextConfig;

77
AppImage/package.json Normal file
View File

@@ -0,0 +1,77 @@
{
"name": "ProxMenux-Monitor",
"version": "1.2.0",
"description": "Proxmox System Monitoring Dashboard",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"export": "next build"
},
"dependencies": {
"@hookform/resolvers": "^3.10.0",
"@radix-ui/react-accordion": "1.2.2",
"@radix-ui/react-alert-dialog": "1.1.4",
"@radix-ui/react-aspect-ratio": "1.1.1",
"@radix-ui/react-avatar": "1.1.2",
"@radix-ui/react-checkbox": "1.1.3",
"@radix-ui/react-collapsible": "1.1.2",
"@radix-ui/react-context-menu": "2.2.4",
"@radix-ui/react-dialog": "1.1.4",
"@radix-ui/react-dropdown-menu": "2.1.4",
"@radix-ui/react-hover-card": "1.1.4",
"@radix-ui/react-label": "2.1.1",
"@radix-ui/react-menubar": "1.1.4",
"@radix-ui/react-navigation-menu": "1.2.3",
"@radix-ui/react-popover": "1.1.4",
"@radix-ui/react-progress": "1.1.1",
"@radix-ui/react-radio-group": "1.2.2",
"@radix-ui/react-scroll-area": "1.2.2",
"@radix-ui/react-select": "2.1.4",
"@radix-ui/react-separator": "1.1.1",
"@radix-ui/react-slider": "1.2.2",
"@radix-ui/react-slot": "1.1.1",
"@radix-ui/react-switch": "1.1.2",
"@radix-ui/react-tabs": "1.1.2",
"@radix-ui/react-toast": "1.2.4",
"@radix-ui/react-toggle": "1.1.1",
"@radix-ui/react-toggle-group": "1.1.1",
"@radix-ui/react-tooltip": "1.1.6",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "1.0.4",
"date-fns": "4.1.0",
"embla-carousel-react": "8.5.1",
"geist": "^1.3.1",
"input-otp": "1.4.1",
"lucide-react": "^0.454.0",
"next": "15.1.6",
"next-themes": "^0.4.6",
"react": "^19",
"react-day-picker": "9.8.0",
"react-dom": "^19",
"react-hook-form": "^7.60.0",
"react-resizable-panels": "^2.1.7",
"recharts": "2.15.4",
"socket.io-client": "^4.8.1",
"sonner": "^1.7.4",
"swr": "^2.2.5",
"tailwind-merge": "^3.3.1",
"tailwindcss-animate": "^1.0.7",
"vaul": "^0.9.9",
"xterm": "^5.3.0",
"xterm-addon-fit": "^0.8.0",
"zod": "3.25.67"
},
"devDependencies": {
"@types/node": "^22",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.4.20",
"postcss": "^8.5",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
}

View File

@@ -0,0 +1,9 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
export default config;

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

BIN
AppImage/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
AppImage/public/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

3
AppImage/public/icon.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 63 KiB

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200.18 69.76">
<g id="Layer_1-2" data-name="Layer 1">
<path d="M114.26.13c-13.19,0-23.88,10.68-23.88,23.88s10.68,23.9,23.88,23.9,23.88-10.68,23.88-23.88h0c-.02-13.19-10.71-23.88-23.88-23.9ZM114.26,38.94c-8.24,0-14.93-6.69-14.93-14.93s6.69-14.93,14.93-14.93,14.93,6.69,14.93,14.93c-.02,8.24-6.71,14.93-14.93,14.93h0Z"/>
<path d="M24.11,0C10.92-.11.13,10.47,0,23.66c-.13,13.19,10.47,23.98,23.66,24.11h8.31v-8.94h-7.86c-8.24.11-15-6.5-15.1-14.74-.11-8.24,6.5-15,14.74-15.1h.34c8.22,0,14.95,6.69,14.95,14.93h0v21.98h0c0,8.18-6.65,14.83-14.81,14.93-3.91-.04-7.63-1.59-10.39-4.38l-6.33,6.31c4.4,4.42,10.34,6.92,16.57,6.99h.32c13.02-.19,23.49-10.75,23.56-23.77v-22.69C47.65,10.35,37.05.02,24.11,0Z"/>
<path d="M191.28,68.74V23.43c-.32-12.96-10.92-23.28-23.88-23.3-13.19-.13-23.98,10.47-24.11,23.66-.13,13.19,10.49,23.98,23.68,24.11h8.31v-8.94h-7.86c-8.24.11-15-6.5-15.1-14.74s6.5-15,14.74-15.1h.34c8.22,0,14.95,6.69,14.95,14.93h0v44.63h0l8.92.06Z"/>
<path d="M54.8,47.9h8.92v-23.88c0-8.24,6.69-14.93,14.93-14.93,2.72,0,5.25.72,7.46,2l4.48-7.75c-3.5-2.02-7.58-3.19-11.92-3.19-13.19,0-23.88,10.68-23.88,23.88v23.88Z"/>
<path d="M198.01.74c.68.38,1.21.91,1.59,1.59.38.68.57,1.42.57,2.25s-.19,1.57-.59,2.27c-.4.68-.93,1.23-1.61,1.61-.68.4-1.44.59-2.25.59s-1.57-.19-2.25-.59c-.68-.4-1.21-.93-1.59-1.61-.38-.68-.59-1.42-.59-2.25s.19-1.57.59-2.25c.38-.68.93-1.21,1.61-1.61s1.44-.59,2.27-.59c.83,0,1.57.19,2.25.59ZM197.57,7.75c.55-.32.98-.76,1.3-1.32.32-.55.47-1.17.47-1.85s-.15-1.3-.47-1.85-.74-.98-1.27-1.3c-.55-.32-1.17-.47-1.85-.47s-1.3.17-1.85.49c-.55.32-.98.76-1.3,1.32s-.47,1.17-.47,1.85.15,1.3.47,1.85c.32.55.74,1,1.27,1.32.55.32,1.15.49,1.83.49.7-.04,1.32-.21,1.87-.53ZM197.84,4.82c-.15.25-.38.45-.68.59l1.06,1.64h-1.32l-.91-1.42h-.87v1.42h-1.32V2.17h2.12c.66,0,1.19.15,1.57.47.38.32.57.74.57,1.27,0,.34-.08.66-.23.91ZM195.85,4.65c.3,0,.53-.06.68-.19.17-.13.25-.32.25-.55s-.08-.42-.25-.57-.4-.19-.68-.19h-.74v1.53h.74v-.02Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200.18 69.76">
<defs>
<style>
.cls-1 {
fill: #fff;
}
</style>
</defs>
<g id="Layer_1-2" data-name="Layer 1">
<path class="cls-1" d="M114.26.13c-13.19,0-23.88,10.68-23.88,23.88s10.68,23.9,23.88,23.9,23.88-10.68,23.88-23.88h0c-.02-13.19-10.71-23.88-23.88-23.9ZM114.26,38.94c-8.24,0-14.93-6.69-14.93-14.93s6.69-14.93,14.93-14.93,14.93,6.69,14.93,14.93c-.02,8.24-6.71,14.93-14.93,14.93h0Z"/>
<path class="cls-1" d="M24.11,0C10.92-.11.13,10.47,0,23.66c-.13,13.19,10.47,23.98,23.66,24.11h8.31v-8.94h-7.86c-8.24.11-15-6.5-15.1-14.74-.11-8.24,6.5-15,14.74-15.1h.34c8.22,0,14.95,6.69,14.95,14.93h0v21.98h0c0,8.18-6.65,14.83-14.81,14.93-3.91-.04-7.63-1.59-10.39-4.38l-6.33,6.31c4.4,4.42,10.34,6.92,16.57,6.99h.32c13.02-.19,23.49-10.75,23.56-23.77v-22.69C47.65,10.35,37.05.02,24.11,0Z"/>
<path class="cls-1" d="M191.28,68.74V23.43c-.32-12.96-10.92-23.28-23.88-23.3-13.19-.13-23.98,10.47-24.11,23.66-.13,13.19,10.49,23.98,23.68,24.11h8.31v-8.94h-7.86c-8.24.11-15-6.5-15.1-14.74s6.5-15,14.74-15.1h.34c8.22,0,14.95,6.69,14.95,14.93h0v44.63h0l8.92.06Z"/>
<path class="cls-1" d="M54.8,47.9h8.92v-23.88c0-8.24,6.69-14.93,14.93-14.93,2.72,0,5.25.72,7.46,2l4.48-7.75c-3.5-2.02-7.58-3.19-11.92-3.19-13.19,0-23.88,10.68-23.88,23.88v23.88Z"/>
<path class="cls-1" d="M198.01.74c.68.38,1.21.91,1.59,1.59.38.68.57,1.42.57,2.25s-.19,1.57-.59,2.27c-.4.68-.93,1.23-1.61,1.61-.68.4-1.44.59-2.25.59s-1.57-.19-2.25-.59c-.68-.4-1.21-.93-1.59-1.61-.38-.68-.59-1.42-.59-2.25s.19-1.57.59-2.25c.38-.68.93-1.21,1.61-1.61s1.44-.59,2.27-.59c.83,0,1.57.19,2.25.59ZM197.57,7.75c.55-.32.98-.76,1.3-1.32.32-.55.47-1.17.47-1.85s-.15-1.3-.47-1.85-.74-.98-1.27-1.3c-.55-.32-1.17-.47-1.85-.47s-1.3.17-1.85.49c-.55.32-.98.76-1.3,1.32s-.47,1.17-.47,1.85.15,1.3.47,1.85c.32.55.74,1,1.27,1.32.55.32,1.15.49,1.83.49.7-.04,1.32-.21,1.87-.53ZM197.84,4.82c-.15.25-.38.45-.68.59l1.06,1.64h-1.32l-.91-1.42h-.87v1.42h-1.32V2.17h2.12c.66,0,1.19.15,1.57.47.38.32.57.74.57,1.27,0,.34-.08.66-.23.91ZM195.85,4.65c.3,0,.53-.06.68-.19.17-.13.25-.32.25-.55s-.08-.42-.25-.57-.4-.19-.68-.19h-.74v1.53h.74v-.02Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="#0d597f"><path d="M23.25 38.81v-6.745l-4.855 4.864c.474.333.968.635 1.48.906.463.243.87.434 1.303.58s.782.24 1.13.304.66.093.95.096m24.822-.562c.045.037.092.07.142.1a2.77 2.77 0 0 0 .385.203 2.93 2.93 0 0 0 .637.194c.296.06.598.088.9.087.3 0 .608-.03.955-.087a7.24 7.24 0 0 0 1.138-.301 9.96 9.96 0 0 0 1.32-.579c.52-.274 1.02-.58 1.503-.918l-3.685-3.6-12.21-12.258-5.356 5.356-7.23-7.455-18.14 17.935a13.82 13.82 0 0 0 1.5.918c.47.246.91.434 1.317.58a7.18 7.18 0 0 0 1.135.301 5.53 5.53 0 0 0 .955.087c.302.001.604-.028.9-.087a3.29 3.29 0 0 0 .637-.194 2.49 2.49 0 0 0 .385-.197l.145-.104 8.193-8.193 2.924-2.808 8.106 8.106 2.837 2.912a1.29 1.29 0 0 0 .145.101 2.52 2.52 0 0 0 .385.2c.206.085.42.15.637.194.255.052.556.087.903.087.3 0 .608-.03.955-.087a6.89 6.89 0 0 0 1.138-.301 9.95 9.95 0 0 0 1.32-.579c.52-.274 1.02-.58 1.503-.918l-6.508-6.37 1.2-1.2 5.63 5.63 3.283 3.254m-.07-33.96l15.998 27.714L48.003 59.71H15.996L-.002 31.997 15.996 4.283z"/><path d="M38.02 30.65l-4.262-4.256.304-.304 4.3 4.244z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256" version="1.0">
<defs>
<linearGradient xlink:href="#a" id="d" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-.39377 0 0 .39375 978.34969 416.9815)" x1="541.33502" y1="104.50665" x2="606.91248" y2="303.14029"/>
<linearGradient gradientUnits="userSpaceOnUse" id="a" y2="129.3468" x2="112.49853" y1="6.1372099" x1="112.49854" gradientTransform="translate(287 -83)">
<stop offset="0" style="stop-color:#fff;stop-opacity:0"/>
<stop offset="1" style="stop-color:#fff;stop-opacity:.27450982"/>
</linearGradient>
<linearGradient id="b">
<stop style="stop-color:#00bdec" offset="0"/>
<stop style="stop-color:#40bfde" offset="1"/>
</linearGradient>
<linearGradient id="c">
<stop style="stop-color:#6e6e6e" offset="0"/>
<stop style="stop-color:#4d4d4d" offset="1"/>
</linearGradient>
</defs>
<path style="fill:#1793d1" d="M128 0c-11.39482 27.937051-18.31337 46.237163-31 73.34375 7.7785 8.245207 17.33826 17.811753 32.84375 28.65625-16.66992-6.859577-28.03357-13.728504-36.53125-20.875C77.076039 115.00489 51.621645 163.24639 0 256c40.562707-23.41756 72.007597-37.86167 101.3125-43.375-1.25376-5.40435-1.923505-11.27752-1.875-17.375l.03125-1.28125c.64379-25.99398 14.16934-45.98224 30.1875-44.625 16.01815 1.35723 28.48754 23.53727 27.84375 49.53125-.12127 4.89622-.6905 9.60082-1.65625 13.96875C184.83328 218.51691 215.98162 232.89667 256 256c-7.89193-14.52962-14.96051-27.61983-21.6875-40.09375-10.59609-8.21269-21.64301-18.89743-44.1875-30.46875 15.4958 4.02645 26.60184 8.6825 35.25 13.875C156.97985 71.972668 151.45422 55.040376 128 0z" transform="matrix(1 0 0 1 -.000002 4e-8)"/>
<path style="fill:#fff;fill-opacity:.16568047" d="M818.22607 548.55277c-41.18143-55.89508-50.72685-100.94481-53.14467-111.70015 21.96737 50.6686 21.81733 51.28995 53.14467 111.70015z" transform="matrix(1.34737 0 0 1.34737 -902.40019 -586.944907)"/>
<path style="fill:url(#d);fill-opacity:1" d="M765.09805 436.43495c-1.05641 2.59705-2.08559 5.1172-3.06152 7.51465-1.08115 2.65585-2.10928 5.19128-3.13111 7.677-1.02174 2.48575-2.03439 4.91156-3.03833 7.30591-1.00398 2.39446-2.01068 4.76169-3.03833 7.14355-1.02758 2.38177-2.06156 4.78845-3.15429 7.23633-1.09273 2.44796-2.23335 4.94504-3.43262 7.53784-1.19937 2.59282-2.45641 5.27815-3.80371 8.09448-.18662.39008-.41312.83402-.60303 1.22925 5.75521 6.09563 12.84133 13.14976 24.28345 21.15234-12.34021-5.07792-20.76511-10.15751-27.06665-15.44677-.32717.66791-.61387 1.26431-.95093 1.94824-.44365.90024-.97632 1.92315-1.43799 2.85278-.80967 1.66032-1.65574 3.36576-2.52807 5.12574-.33524.66652-.62948 1.24283-.97413 1.92504-5.50733 11.05265-12.33962 24.28304-21.12915 40.72754 24.09557-13.57581 50.08533-33.16242 97.29615-16.30493-2.36708-4.48319-4.54319-8.68756-6.58692-12.64038-2.0437-3.95294-3.94246-7.6555-5.70556-11.15601-1.76297-3.50043-3.39212-6.80069-4.917-9.92675-1.52486-3.12599-2.93832-6.0765-4.26757-8.90625-1.32934-2.8297-2.58106-5.55264-3.75733-8.16407-1.17634-2.6114-2.29708-5.11315-3.36304-7.58422-1.06607-2.4712-2.08657-4.89718-3.08471-7.30591-.99823-2.4088-1.97267-4.81178-2.94556-7.23633-.34772-.86638-.69553-1.7689-1.0437-2.64404-2.66339-6.25269-5.3982-12.73163-8.55835-20.15503z" transform="matrix(1.34737 0 0 1.34737 -902.40019 -586.944907)"/>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 10.0, SVG Export Plug-In . SVG Version: 3.0.0 Build 77) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
<!ENTITY ns_extend "http://ns.adobe.com/Extensibility/1.0/">
<!ENTITY ns_ai "http://ns.adobe.com/AdobeIllustrator/10.0/">
<!ENTITY ns_graphs "http://ns.adobe.com/Graphs/1.0/">
<!ENTITY ns_vars "http://ns.adobe.com/Variables/1.0/">
<!ENTITY ns_imrep "http://ns.adobe.com/ImageReplacement/1.0/">
<!ENTITY ns_sfw "http://ns.adobe.com/SaveForWeb/1.0/">
<!ENTITY ns_custom "http://ns.adobe.com/GenericCustomNamespace/1.0/">
<!ENTITY ns_adobe_xpath "http://ns.adobe.com/XPath/1.0/">
<!ENTITY ns_svg "http://www.w3.org/2000/svg">
<!ENTITY ns_xlink "http://www.w3.org/1999/xlink">
]>
<svg
xmlns:x="&ns_extend;" xmlns:i="&ns_ai;" xmlns:graph="&ns_graphs;" i:viewOrigin="262 450" i:rulerOrigin="0 0" i:pageBounds="0 792 612 0"
xmlns="&ns_svg;" xmlns:xlink="&ns_xlink;" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
width="87.041" height="108.445" viewBox="0 0 87.041 108.445" overflow="visible" enable-background="new 0 0 87.041 108.445"
xml:space="preserve">
<metadata>
<variableSets xmlns="&ns_vars;">
<variableSet varSetName="binding1" locked="none">
<variables></variables>
<v:sampleDataSets xmlns="&ns_custom;" xmlns:v="&ns_vars;"></v:sampleDataSets>
</variableSet>
</variableSets>
<sfw xmlns="&ns_sfw;">
<slices></slices>
<sliceSourceBounds y="341.555" x="262" width="87.041" height="108.445" bottomLeftOrigin="true"></sliceSourceBounds>
</sfw>
</metadata>
<g id="Layer_1" i:layer="yes" i:dimmedPercent="50" i:rgbTrio="#4F008000FFFF">
<g>
<path i:knockout="Off" fill="#A80030" d="M51.986,57.297c-1.797,0.025,0.34,0.926,2.686,1.287
c0.648-0.506,1.236-1.018,1.76-1.516C54.971,57.426,53.484,57.434,51.986,57.297"/>
<path i:knockout="Off" fill="#A80030" d="M61.631,54.893c1.07-1.477,1.85-3.094,2.125-4.766c-0.24,1.192-0.887,2.221-1.496,3.307
c-3.359,2.115-0.316-1.256-0.002-2.537C58.646,55.443,61.762,53.623,61.631,54.893"/>
<path i:knockout="Off" fill="#A80030" d="M65.191,45.629c0.217-3.236-0.637-2.213-0.924-0.978
C64.602,44.825,64.867,46.932,65.191,45.629"/>
<path i:knockout="Off" fill="#A80030" d="M45.172,1.399c0.959,0.172,2.072,0.304,1.916,0.533
C48.137,1.702,48.375,1.49,45.172,1.399"/>
<path i:knockout="Off" fill="#A80030" d="M47.088,1.932l-0.678,0.14l0.631-0.056L47.088,1.932"/>
<path i:knockout="Off" fill="#A80030" d="M76.992,46.856c0.107,2.906-0.85,4.316-1.713,6.812l-1.553,0.776
c-1.271,2.468,0.123,1.567-0.787,3.53c-1.984,1.764-6.021,5.52-7.313,5.863c-0.943-0.021,0.639-1.113,0.846-1.541
c-2.656,1.824-2.131,2.738-6.193,3.846l-0.119-0.264c-10.018,4.713-23.934-4.627-23.751-17.371
c-0.107,0.809-0.304,0.607-0.526,0.934c-0.517-6.557,3.028-13.143,9.007-15.832c5.848-2.895,12.704-1.707,16.893,2.197
c-2.301-3.014-6.881-6.209-12.309-5.91c-5.317,0.084-10.291,3.463-11.951,7.131c-2.724,1.715-3.04,6.611-4.227,7.507
C31.699,56.271,36.3,61.342,44.083,67.307c1.225,0.826,0.345,0.951,0.511,1.58c-2.586-1.211-4.954-3.039-6.901-5.277
c1.033,1.512,2.148,2.982,3.589,4.137c-2.438-0.826-5.695-5.908-6.646-6.115c4.203,7.525,17.052,13.197,23.78,10.383
c-3.113,0.115-7.068,0.064-10.566-1.229c-1.469-0.756-3.467-2.322-3.11-2.615c9.182,3.43,18.667,2.598,26.612-3.771
c2.021-1.574,4.229-4.252,4.867-4.289c-0.961,1.445,0.164,0.695-0.574,1.971c2.014-3.248-0.875-1.322,2.082-5.609l1.092,1.504
c-0.406-2.696,3.348-5.97,2.967-10.234c0.861-1.304,0.961,1.403,0.047,4.403c1.268-3.328,0.334-3.863,0.66-6.609
c0.352,0.923,0.814,1.904,1.051,2.878c-0.826-3.216,0.848-5.416,1.262-7.285c-0.408-0.181-1.275,1.422-1.473-2.377
c0.029-1.65,0.459-0.865,0.625-1.271c-0.324-0.186-1.174-1.451-1.691-3.877c0.375-0.57,1.002,1.478,1.512,1.562
c-0.328-1.929-0.893-3.4-0.916-4.88c-1.49-3.114-0.527,0.415-1.736-1.337c-1.586-4.947,1.316-1.148,1.512-3.396
c2.404,3.483,3.775,8.881,4.404,11.117c-0.48-2.726-1.256-5.367-2.203-7.922c0.73,0.307-1.176-5.609,0.949-1.691
c-2.27-8.352-9.715-16.156-16.564-19.818c0.838,0.767,1.896,1.73,1.516,1.881c-3.406-2.028-2.807-2.186-3.295-3.043
c-2.775-1.129-2.957,0.091-4.795,0.002c-5.23-2.774-6.238-2.479-11.051-4.217l0.219,1.023c-3.465-1.154-4.037,0.438-7.782,0.004
c-0.228-0.178,1.2-0.644,2.375-0.815c-3.35,0.442-3.193-0.66-6.471,0.122c0.808-0.567,1.662-0.942,2.524-1.424
c-2.732,0.166-6.522,1.59-5.352,0.295c-4.456,1.988-12.37,4.779-16.811,8.943l-0.14-0.933c-2.035,2.443-8.874,7.296-9.419,10.46
l-0.544,0.127c-1.059,1.793-1.744,3.825-2.584,5.67c-1.385,2.36-2.03,0.908-1.833,1.278c-2.724,5.523-4.077,10.164-5.246,13.97
c0.833,1.245,0.02,7.495,0.335,12.497c-1.368,24.704,17.338,48.69,37.785,54.228c2.997,1.072,7.454,1.031,11.245,1.141
c-4.473-1.279-5.051-0.678-9.408-2.197c-3.143-1.48-3.832-3.17-6.058-5.102l0.881,1.557c-4.366-1.545-2.539-1.912-6.091-3.037
l0.941-1.229c-1.415-0.107-3.748-2.385-4.386-3.646l-1.548,0.061c-1.86-2.295-2.851-3.949-2.779-5.23l-0.5,0.891
c-0.567-0.973-6.843-8.607-3.587-6.83c-0.605-0.553-1.409-0.9-2.281-2.484l0.663-0.758c-1.567-2.016-2.884-4.6-2.784-5.461
c0.836,1.129,1.416,1.34,1.99,1.533c-3.957-9.818-4.179-0.541-7.176-9.994l0.634-0.051c-0.486-0.732-0.781-1.527-1.172-2.307
l0.276-2.75C4.667,58.121,6.719,47.409,7.13,41.534c0.285-2.389,2.378-4.932,3.97-8.92l-0.97-0.167
c1.854-3.234,10.586-12.988,14.63-12.486c1.959-2.461-0.389-0.009-0.772-0.629c4.303-4.453,5.656-3.146,8.56-3.947
c3.132-1.859-2.688,0.725-1.203-0.709c5.414-1.383,3.837-3.144,10.9-3.846c0.745,0.424-1.729,0.655-2.35,1.205
c4.511-2.207,14.275-1.705,20.617,1.225c7.359,3.439,15.627,13.605,15.953,23.17l0.371,0.1
c-0.188,3.802,0.582,8.199-0.752,12.238L76.992,46.856"/>
<path i:knockout="Off" fill="#A80030" d="M32.372,59.764l-0.252,1.26c1.181,1.604,2.118,3.342,3.626,4.596
C34.661,63.502,33.855,62.627,32.372,59.764"/>
<path i:knockout="Off" fill="#A80030" d="M35.164,59.654c-0.625-0.691-0.995-1.523-1.409-2.352
c0.396,1.457,1.207,2.709,1.962,3.982L35.164,59.654"/>
<path i:knockout="Off" fill="#A80030" d="M84.568,48.916l-0.264,0.662c-0.484,3.438-1.529,6.84-3.131,9.994
C82.943,56.244,84.088,52.604,84.568,48.916"/>
<path i:knockout="Off" fill="#A80030" d="M45.527,0.537C46.742,0.092,48.514,0.293,49.803,0c-1.68,0.141-3.352,0.225-5.003,0.438
L45.527,0.537"/>
<path i:knockout="Off" fill="#A80030" d="M2.872,23.219c0.28,2.592-1.95,3.598,0.494,1.889
C4.676,22.157,2.854,24.293,2.872,23.219"/>
<path i:knockout="Off" fill="#A80030" d="M0,35.215c0.563-1.728,0.665-2.766,0.88-3.766C-0.676,33.438,0.164,33.862,0,35.215"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 32 32" preserveAspectRatio="xMidYMid"><path d="M32 16c0 8.836-7.164 16-16 16S0 24.836 0 16 7.164 0 16 0s16 7.164 16 16z" fill="#dd4814"/><path d="M5.12 13.864c-1.18 0-2.137.956-2.137 2.137s.956 2.136 2.137 2.136S7.257 17.18 7.257 16 6.3 13.864 5.12 13.864zm15.252 9.71c-1.022.6-1.372 1.896-.782 2.917s1.895 1.372 2.917.782 1.372-1.895.782-2.917-1.896-1.37-2.917-.782zM9.76 16a6.23 6.23 0 0 1 2.653-5.105L10.852 8.28a9.3 9.3 0 0 0-3.838 5.394C7.69 14.224 8.12 15.06 8.12 16s-.432 1.776-1.106 2.326c.577 2.237 1.968 4.146 3.838 5.395l1.562-2.616A6.23 6.23 0 0 1 9.761 16zM16 9.76a6.24 6.24 0 0 1 6.215 5.687l3.044-.045a9.25 9.25 0 0 0-2.757-6.019c-.812.307-1.75.26-2.56-.208a2.99 2.99 0 0 1-1.461-2.118C17.7 6.84 16.86 6.72 16 6.72c-1.477 0-2.873.347-4.113.96l1.484 2.66c.8-.372 1.69-.58 2.628-.58zm0 12.48c-.94 0-1.83-.21-2.628-.58l-1.484 2.66c1.24.614 2.636.96 4.113.96a9.28 9.28 0 0 0 2.479-.338c.14-.858.65-1.648 1.46-2.118s1.75-.514 2.56-.207a9.25 9.25 0 0 0 2.757-6.019l-3.045-.045A6.24 6.24 0 0 1 16 22.24zm4.372-13.813c1.022.6 2.328.24 2.917-.78s.24-2.328-.78-2.918-2.328-.24-2.918.783-.24 2.327.782 2.917z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,23 @@
# Onboarding Images
Place your screenshot images here with the following names:
- `imagen1.png` - Overview section screenshot
- `imagen2.png` - Storage section screenshot
- `imagen3.png` - Network section screenshot
- `imagen4.png` - VMs & LXCs section screenshot
- `imagen5.png` - Hardware section screenshot
- `imagen6.png` - System Logs section screenshot
## Image Guidelines
- **Format**: PNG or JPG
- **Recommended size**: 1200x800px or similar aspect ratio
- **Quality**: High quality screenshots showing the main features of each section
- **Content**: Capture the full section with representative data
## Notes
- The last slide (Future Updates) doesn't need an image as it uses an icon
- If an image fails to load, the component will show a fallback icon
- Images should be optimized for web (compressed but still high quality)

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -0,0 +1,16 @@
{
"name": "ProxMenux Monitor",
"short_name": "ProxMenux",
"description": "Proxmox System Dashboard and Monitor",
"start_url": "/",
"display": "standalone",
"background_color": "#2b2f36",
"theme_color": "#2b2f36",
"icons": [
{
"src": "/images/proxmenux-logo.png",
"sizes": "256x256",
"type": "image/png"
}
]
}

41
AppImage/scripts/AppRun Normal file
View File

@@ -0,0 +1,41 @@
#!/bin/bash
# ProxMenux Monitor AppImage Entry Point
# This script is executed when the AppImage is run
# Get the directory where this AppImage is mounted
APPDIR="$(dirname "$(readlink -f "${0}")")"
export PATH="${APPDIR}/usr/bin:${PATH}"
export LD_LIBRARY_PATH="${APPDIR}/usr/lib/x86_64-linux-gnu:${APPDIR}/usr/lib:${APPDIR}/lib/x86_64-linux-gnu:${APPDIR}/lib:${LD_LIBRARY_PATH}"
export PYTHONPATH="${APPDIR}/usr/lib/python3/dist-packages:${APPDIR}/usr/lib/python3/site-packages:${PYTHONPATH}"
# Change to the AppImage directory
cd "${APPDIR}"
# Check for translation argument
if [[ "$1" == "--translate" ]]; then
echo "🌐 Starting ProxMenux Translation Service..."
exec python3 "${APPDIR}/usr/bin/translate_cli.py" "${@:2}"
else
echo "🚀 Starting ProxMenux Monitor Dashboard..."
echo ""
echo "🔧 Hardware monitoring tools:"
[ -x "${APPDIR}/usr/bin/ipmitool" ] && echo " ✅ ipmitool available" || echo " ⚠️ ipmitool not available"
[ -x "${APPDIR}/usr/bin/sensors" ] && echo " ✅ sensors available" || echo " ⚠️ sensors not available"
[ -x "${APPDIR}/usr/bin/upsc" ] && echo " ✅ upsc available" || echo " ⚠️ upsc not available"
if [ -x "${APPDIR}/usr/bin/ipmitool" ]; then
if ldd "${APPDIR}/usr/bin/ipmitool" 2>/dev/null | grep -q "libfreeipmi.so.17 => not found"; then
echo " ⚠️ libfreeipmi.so.17 not found - ipmitool may not work"
elif ldd "${APPDIR}/usr/bin/ipmitool" 2>/dev/null | grep -q "libfreeipmi.so.17"; then
echo " ✅ libfreeipmi.so.17 loaded successfully"
fi
fi
echo ""
# Start the Flask server
exec python3 "${APPDIR}/usr/bin/flask_server.py"
fi

View File

@@ -0,0 +1,390 @@
#!/usr/bin/env python3
"""
AI Context Enrichment Module
Enriches notification context with additional information to help AI provide
more accurate and helpful responses:
1. Event frequency - how often this error has occurred
2. System uptime - helps distinguish startup issues from runtime failures
3. SMART disk data - for disk-related errors
4. Known error matching - from proxmox_known_errors database
Author: MacRimi
"""
import os
import re
import subprocess
from datetime import datetime, timedelta
from typing import Optional, Dict, Any
import sqlite3
from pathlib import Path
# Import known errors database
try:
from proxmox_known_errors import get_error_context, find_matching_error
except ImportError:
def get_error_context(*args, **kwargs):
return None
def find_matching_error(*args, **kwargs):
return None
DB_PATH = Path('/usr/local/share/proxmenux/health_monitor.db')
def get_system_uptime() -> str:
"""Get system uptime in human-readable format.
Returns:
String like "2 minutes (recently booted)" or "89 days, 4 hours (stable system)"
"""
try:
with open('/proc/uptime', 'r') as f:
uptime_seconds = float(f.readline().split()[0])
days = int(uptime_seconds // 86400)
hours = int((uptime_seconds % 86400) // 3600)
minutes = int((uptime_seconds % 3600) // 60)
# Build human-readable string
parts = []
if days > 0:
parts.append(f"{days} day{'s' if days != 1 else ''}")
if hours > 0:
parts.append(f"{hours} hour{'s' if hours != 1 else ''}")
if not parts: # Less than an hour
parts.append(f"{minutes} minute{'s' if minutes != 1 else ''}")
uptime_str = ", ".join(parts)
# Add context hint
if uptime_seconds < 600: # Less than 10 minutes
return f"{uptime_str} (just booted - likely startup issue)"
elif uptime_seconds < 3600: # Less than 1 hour
return f"{uptime_str} (recently booted)"
elif days >= 30:
return f"{uptime_str} (stable system)"
else:
return uptime_str
except Exception:
return "unknown"
def get_event_frequency(error_id: str = None, error_key: str = None,
category: str = None, hours: int = 24) -> Optional[Dict[str, Any]]:
"""Get frequency information for an error from the database.
Args:
error_id: Specific error ID to look up
error_key: Alternative error key
category: Error category
hours: Time window to check (default 24h)
Returns:
Dict with frequency info or None
"""
if not DB_PATH.exists():
return None
try:
conn = sqlite3.connect(str(DB_PATH), timeout=5)
cursor = conn.cursor()
# Try to find the error
if error_id:
cursor.execute('''
SELECT first_seen, last_seen, occurrences, category
FROM errors WHERE error_key = ? OR error_id = ?
ORDER BY last_seen DESC LIMIT 1
''', (error_id, error_id))
elif error_key:
cursor.execute('''
SELECT first_seen, last_seen, occurrences, category
FROM errors WHERE error_key = ?
ORDER BY last_seen DESC LIMIT 1
''', (error_key,))
elif category:
cursor.execute('''
SELECT first_seen, last_seen, occurrences, category
FROM errors WHERE category = ? AND resolved_at IS NULL
ORDER BY last_seen DESC LIMIT 1
''', (category,))
else:
conn.close()
return None
row = cursor.fetchone()
conn.close()
if not row:
return None
first_seen, last_seen, occurrences, cat = row
# Calculate age
try:
first_dt = datetime.fromisoformat(first_seen) if first_seen else None
last_dt = datetime.fromisoformat(last_seen) if last_seen else None
now = datetime.now()
result = {
'occurrences': occurrences or 1,
'category': cat
}
if first_dt:
age = now - first_dt
if age.total_seconds() < 3600:
result['first_seen_ago'] = f"{int(age.total_seconds() / 60)} minutes ago"
elif age.total_seconds() < 86400:
result['first_seen_ago'] = f"{int(age.total_seconds() / 3600)} hours ago"
else:
result['first_seen_ago'] = f"{age.days} days ago"
if last_dt and first_dt and occurrences and occurrences > 1:
# Calculate average interval
span = (last_dt - first_dt).total_seconds()
if span > 0 and occurrences > 1:
avg_interval = span / (occurrences - 1)
if avg_interval < 60:
result['pattern'] = f"recurring every ~{int(avg_interval)} seconds"
elif avg_interval < 3600:
result['pattern'] = f"recurring every ~{int(avg_interval / 60)} minutes"
else:
result['pattern'] = f"recurring every ~{int(avg_interval / 3600)} hours"
return result
except (ValueError, TypeError):
return {'occurrences': occurrences or 1, 'category': cat}
except Exception as e:
print(f"[AIContext] Error getting frequency: {e}")
return None
def get_smart_data(disk_device: str) -> Optional[str]:
"""Get SMART health data for a disk.
Args:
disk_device: Device path like /dev/sda or just sda
Returns:
Formatted SMART summary or None
"""
if not disk_device:
return None
# Normalize device path
if not disk_device.startswith('/dev/'):
disk_device = f'/dev/{disk_device}'
# Check device exists
if not os.path.exists(disk_device):
return None
try:
# Get health status
result = subprocess.run(
['smartctl', '-H', disk_device],
capture_output=True, text=True, timeout=10
)
health_status = "UNKNOWN"
if "PASSED" in result.stdout:
health_status = "PASSED"
elif "FAILED" in result.stdout:
health_status = "FAILED"
# Get key attributes
result = subprocess.run(
['smartctl', '-A', disk_device],
capture_output=True, text=True, timeout=10
)
attributes = {}
critical_attrs = [
'Reallocated_Sector_Ct', 'Current_Pending_Sector',
'Offline_Uncorrectable', 'UDMA_CRC_Error_Count',
'Reallocated_Event_Count', 'Reported_Uncorrect'
]
for line in result.stdout.split('\n'):
for attr in critical_attrs:
if attr in line:
parts = line.split()
# Typical format: ID ATTRIBUTE_NAME FLAGS VALUE WORST THRESH TYPE UPDATED RAW_VALUE
if len(parts) >= 10:
raw_value = parts[-1]
attributes[attr] = raw_value
# Build summary
lines = [f"SMART Health: {health_status}"]
# Add critical attributes if non-zero
for attr, value in attributes.items():
try:
if int(value) > 0:
lines.append(f" {attr}: {value}")
except ValueError:
pass
return "\n".join(lines) if len(lines) > 1 or health_status == "FAILED" else f"SMART Health: {health_status}"
except subprocess.TimeoutExpired:
return None
except FileNotFoundError:
# smartctl not installed
return None
except Exception:
return None
def extract_disk_device(text: str) -> Optional[str]:
"""Extract disk device name from error text.
Args:
text: Error message or log content
Returns:
Device name like 'sda' or None
"""
if not text:
return None
# Common patterns for disk devices in errors
patterns = [
r'/dev/(sd[a-z]\d*)',
r'/dev/(nvme\d+n\d+(?:p\d+)?)',
r'/dev/(hd[a-z]\d*)',
r'/dev/(vd[a-z]\d*)',
r'\b(sd[a-z])\b',
r'disk[_\s]+(sd[a-z])',
r'ata\d+\.\d+: (sd[a-z])',
]
for pattern in patterns:
match = re.search(pattern, text, re.IGNORECASE)
if match:
return match.group(1)
return None
def enrich_context_for_ai(
title: str,
body: str,
event_type: str,
data: Dict[str, Any],
journal_context: str = '',
detail_level: str = 'standard'
) -> str:
"""Build enriched context string for AI processing.
Combines:
- Original journal context
- Event frequency information
- System uptime
- SMART data (for disk errors)
- Known error matching
Args:
title: Notification title
body: Notification body
event_type: Type of event
data: Event data dict
journal_context: Original journal log context
detail_level: Level of detail (minimal, standard, detailed)
Returns:
Enriched context string
"""
context_parts = []
combined_text = f"{title} {body} {journal_context}"
# 1. System uptime - ONLY for critical system-level failures
# Uptime helps distinguish startup issues from runtime failures
# BUT it's noise for disk errors, warnings, or routine operations
# Only include for: system crash, kernel panic, OOM, cluster failures
uptime_critical_types = [
'crash', 'panic', 'oom', 'kernel',
'split_brain', 'quorum_lost', 'node_offline', 'node_fail',
'system_fail', 'boot_fail'
]
# Check if this is a critical system-level event (not disk/service/hardware)
event_lower = event_type.lower()
is_critical_system_event = any(t in event_lower for t in uptime_critical_types)
# Only add uptime for critical system failures, nothing else
if is_critical_system_event:
uptime = get_system_uptime()
if uptime and uptime != "unknown":
context_parts.append(f"System uptime: {uptime}")
# 2. Event frequency
error_key = data.get('error_key') or data.get('error_id')
category = data.get('category')
freq = get_event_frequency(error_id=error_key, category=category)
if freq:
freq_line = f"Event frequency: {freq.get('occurrences', 1)} occurrence(s)"
if freq.get('first_seen_ago'):
freq_line += f", first seen {freq['first_seen_ago']}"
if freq.get('pattern'):
freq_line += f", {freq['pattern']}"
context_parts.append(freq_line)
# 3. SMART data for disk-related events
disk_related = any(x in event_type.lower() for x in ['disk', 'smart', 'storage', 'io_error'])
if not disk_related:
disk_related = any(x in combined_text.lower() for x in ['disk', 'smart', '/dev/sd', 'ata', 'i/o error'])
if disk_related:
disk_device = extract_disk_device(combined_text)
if disk_device:
smart_data = get_smart_data(disk_device)
if smart_data:
context_parts.append(smart_data)
# 4. Known error matching
known_error_ctx = get_error_context(combined_text, category=category, detail_level=detail_level)
if known_error_ctx:
context_parts.append(known_error_ctx)
# 5. Add original journal context
if journal_context:
context_parts.append(f"Journal logs:\n{journal_context}")
# Combine all parts
if context_parts:
return "\n\n".join(context_parts)
return journal_context or ""
def get_enriched_context(
event: 'NotificationEvent',
detail_level: str = 'standard'
) -> str:
"""Convenience function to enrich context from a NotificationEvent.
Args:
event: NotificationEvent object
detail_level: Level of detail
Returns:
Enriched context string
"""
journal_context = event.data.get('_journal_context', '')
return enrich_context_for_ai(
title=event.data.get('title', ''),
body=event.data.get('body', event.data.get('message', '')),
event_type=event.event_type,
data=event.data,
journal_context=journal_context,
detail_level=detail_level
)

View File

@@ -0,0 +1,106 @@
"""AI Providers for ProxMenux notification enhancement.
This module provides a pluggable architecture for different AI providers
to enhance and translate notification messages.
Supported providers:
- Groq: Fast inference, generous free tier (30 req/min)
- OpenAI: Industry standard, widely used
- Anthropic: Excellent for text generation, Claude Haiku is fast and affordable
- Gemini: Google's model, free tier available, good quality/price ratio
- Ollama: 100% local execution, no costs, complete privacy
- OpenRouter: Aggregator with access to 100+ models using a single API key
"""
from .base import AIProvider, AIProviderError
from .groq_provider import GroqProvider
from .openai_provider import OpenAIProvider
from .anthropic_provider import AnthropicProvider
from .gemini_provider import GeminiProvider
from .ollama_provider import OllamaProvider
from .openrouter_provider import OpenRouterProvider
PROVIDERS = {
'groq': GroqProvider,
'openai': OpenAIProvider,
'anthropic': AnthropicProvider,
'gemini': GeminiProvider,
'ollama': OllamaProvider,
'openrouter': OpenRouterProvider,
}
# Provider metadata for UI display
# Note: No hardcoded models - users load models dynamically from each provider
PROVIDER_INFO = {
'groq': {
'name': 'Groq',
'description': 'Fast inference, generous free tier (30 req/min). Ideal to get started.',
'requires_api_key': True,
},
'openai': {
'name': 'OpenAI',
'description': 'Industry standard. Very accurate and widely used.',
'requires_api_key': True,
},
'anthropic': {
'name': 'Anthropic (Claude)',
'description': 'Excellent for writing and translation. Fast and affordable.',
'requires_api_key': True,
},
'gemini': {
'name': 'Google Gemini',
'description': 'Free tier available, very good quality/price ratio.',
'requires_api_key': True,
},
'ollama': {
'name': 'Ollama (Local)',
'description': '100% local execution. No costs, complete privacy, no internet required.',
'requires_api_key': False,
},
'openrouter': {
'name': 'OpenRouter',
'description': 'Aggregator with access to 100+ models using a single API key. Maximum flexibility.',
'requires_api_key': True,
},
}
def get_provider(name: str, **kwargs) -> AIProvider:
"""Factory function to get provider instance.
Args:
name: Provider name (groq, openai, anthropic, gemini, ollama, openrouter)
**kwargs: Provider-specific arguments (api_key, model, base_url)
Returns:
AIProvider instance
Raises:
AIProviderError: If provider name is unknown
"""
if name not in PROVIDERS:
raise AIProviderError(f"Unknown provider: {name}. Available: {list(PROVIDERS.keys())}")
return PROVIDERS[name](**kwargs)
def get_provider_info(name: str = None) -> dict:
"""Get provider metadata for UI display.
Args:
name: Optional provider name. If None, returns all providers info.
Returns:
Provider info dict or dict of all providers
"""
if name:
return PROVIDER_INFO.get(name, {})
return PROVIDER_INFO
__all__ = [
'AIProvider',
'AIProviderError',
'PROVIDERS',
'PROVIDER_INFO',
'get_provider',
'get_provider_info',
]

View File

@@ -0,0 +1,80 @@
"""Anthropic (Claude) provider implementation.
Anthropic's Claude models are excellent for text generation and translation.
Models use "-latest" aliases that auto-update to newest versions.
"""
from typing import Optional, List
from .base import AIProvider, AIProviderError
class AnthropicProvider(AIProvider):
"""Anthropic provider using their Messages API."""
NAME = "anthropic"
REQUIRES_API_KEY = True
API_URL = "https://api.anthropic.com/v1/messages"
API_VERSION = "2023-06-01"
# Known stable model aliases (Anthropic doesn't have a public models list API)
# These use "-latest" which auto-updates to the newest version
KNOWN_MODELS = [
"claude-3-5-haiku-latest",
"claude-3-5-sonnet-latest",
"claude-3-opus-latest",
]
def list_models(self) -> List[str]:
"""Return known Anthropic model aliases.
Anthropic doesn't have a public models list API, but their "-latest"
aliases auto-update to the newest versions, making them reliable choices.
"""
return self.KNOWN_MODELS
def generate(self, system_prompt: str, user_message: str,
max_tokens: int = 200) -> Optional[str]:
"""Generate a response using Anthropic's API.
Note: Anthropic uses a different API format than OpenAI.
The system prompt goes in a separate field, not in messages.
Args:
system_prompt: System instructions
user_message: User message to process
max_tokens: Maximum response length
Returns:
Generated text or None if failed
Raises:
AIProviderError: If API key is missing or request fails
"""
if not self.api_key:
raise AIProviderError("API key required for Anthropic")
# Anthropic uses a different format - system is a top-level field
payload = {
'model': self.model,
'system': system_prompt,
'messages': [
{'role': 'user', 'content': user_message},
],
'max_tokens': max_tokens,
}
headers = {
'Content-Type': 'application/json',
'x-api-key': self.api_key,
'anthropic-version': self.API_VERSION,
}
result = self._make_request(self.API_URL, payload, headers)
try:
# Anthropic returns content as array of content blocks
content = result['content']
if isinstance(content, list) and len(content) > 0:
return content[0].get('text', '').strip()
return str(content).strip()
except (KeyError, IndexError) as e:
raise AIProviderError(f"Unexpected response format: {e}")

Some files were not shown because too many files have changed in this diff Show More