From 60518be5bd7e7a5ab843e23b388fb99c4f899158 Mon Sep 17 00:00:00 2001 From: MacRimi Date: Mon, 23 Mar 2026 23:05:27 +0100 Subject: [PATCH] Update notification service --- AppImage/components/proxmox-dashboard.tsx | 26 +++++++++--- .../public/images/proxmenux_update-logo.png | Bin 0 -> 31076 bytes AppImage/scripts/flask_proxmenux_routes.py | 39 ++++++++++++++++++ AppImage/scripts/notification_events.py | 32 ++++++++++++++ 4 files changed, 91 insertions(+), 6 deletions(-) create mode 100644 AppImage/public/images/proxmenux_update-logo.png diff --git a/AppImage/components/proxmox-dashboard.tsx b/AppImage/components/proxmox-dashboard.tsx index 19f3c0dc..823fd9e9 100644 --- a/AppImage/components/proxmox-dashboard.tsx +++ b/AppImage/components/proxmox-dashboard.tsx @@ -80,6 +80,7 @@ export function ProxmoxDashboard() { 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) @@ -99,6 +100,19 @@ export function ProxmoxDashboard() { { 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 { @@ -178,9 +192,10 @@ export function ProxmoxDashboard() { }, []) useEffect(() => { - // Siempre fetch inicial - fetchSystemData() - fetchHealthInfoCount() // Fetch info count on initial load + // Siempre fetch inicial + fetchSystemData() + fetchHealthInfoCount() // Fetch info count on initial load + fetchUpdateStatus() // Fetch ProxMenux update status on initial load // En overview: cada 30 segundos para actualización frecuente del estado de salud // En otras tabs: cada 60 segundos para reducir carga @@ -198,7 +213,7 @@ export function ProxmoxDashboard() { if (interval) clearInterval(interval) if (healthInterval) clearInterval(healthInterval) } - }, [fetchSystemData, fetchHealthInfoCount, activeTab]) + }, [fetchSystemData, fetchHealthInfoCount, fetchUpdateStatus, activeTab]) useEffect(() => { const handleChangeTab = (event: CustomEvent) => { @@ -376,14 +391,13 @@ export function ProxmoxDashboard() {
ProxMenux Logo { - console.log("[v0] Logo failed to load, using fallback icon") const target = e.target as HTMLImageElement target.style.display = "none" const fallback = target.parentElement?.querySelector(".fallback-icon") diff --git a/AppImage/public/images/proxmenux_update-logo.png b/AppImage/public/images/proxmenux_update-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..6ca7f9d20380e60b6ccdf10c92224e41f6dde871 GIT binary patch literal 31076 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 literal 0 HcmV?d00001 diff --git a/AppImage/scripts/flask_proxmenux_routes.py b/AppImage/scripts/flask_proxmenux_routes.py index 6e48e3fe..d8725b43 100644 --- a/AppImage/scripts/flask_proxmenux_routes.py +++ b/AppImage/scripts/flask_proxmenux_routes.py @@ -28,6 +28,45 @@ TOOL_DESCRIPTIONS = { 'persistent_network': 'Setting persistent network interfaces' } +@proxmenux_bp.route('/api/proxmenux/update-status', methods=['GET']) +def get_update_status(): + """Get ProxMenux update availability status from config.json""" + config_path = '/usr/local/share/proxmenux/config.json' + + try: + if not os.path.exists(config_path): + return jsonify({ + 'success': True, + 'update_available': { + 'stable': False, + 'stable_version': '', + 'beta': False, + 'beta_version': '' + } + }) + + with open(config_path, 'r') as f: + config = json.load(f) + + update_status = config.get('update_available', { + 'stable': False, + 'stable_version': '', + 'beta': False, + 'beta_version': '' + }) + + return jsonify({ + 'success': True, + 'update_available': update_status + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + + @proxmenux_bp.route('/api/proxmenux/installed-tools', methods=['GET']) def get_installed_tools(): """Get list of installed ProxMenux tools/optimizations""" diff --git a/AppImage/scripts/notification_events.py b/AppImage/scripts/notification_events.py index affec3d0..310032a9 100644 --- a/AppImage/scripts/notification_events.py +++ b/AppImage/scripts/notification_events.py @@ -2231,12 +2231,42 @@ class PollingCollector: except Exception: return (0,) + def update_config_json(stable: bool = None, stable_version: str = None, + beta: bool = None, beta_version: str = None): + """Update update_available status in config.json.""" + config_path = Path('/usr/local/share/proxmenux/config.json') + try: + config = {} + if config_path.exists(): + with open(config_path, 'r') as f: + config = json.load(f) + + if 'update_available' not in config: + config['update_available'] = { + 'stable': False, 'stable_version': '', + 'beta': False, 'beta_version': '' + } + + if stable is not None: + config['update_available']['stable'] = stable + config['update_available']['stable_version'] = stable_version or '' + if beta is not None: + config['update_available']['beta'] = beta + config['update_available']['beta_version'] = beta_version or '' + + with open(config_path, 'w') as f: + json.dump(config, f, indent=2) + except Exception as e: + print(f"[PollingCollector] Failed to update config.json: {e}") + try: # Check main version local_main = read_local_version(self.PROXMENUX_VERSION_FILE) if local_main: remote_main = read_remote_version(self.REPO_MAIN_VERSION_URL) if remote_main and version_tuple(remote_main) > version_tuple(local_main): + # Update config.json with stable update status + update_config_json(stable=True, stable_version=remote_main) # Only notify if we haven't already notified for this version if self._notified_proxmenux_version != remote_main: self._notified_proxmenux_version = remote_main @@ -2255,6 +2285,8 @@ class PollingCollector: if local_beta: remote_beta = read_remote_version(self.REPO_DEVELOP_VERSION_URL) if remote_beta and version_tuple(remote_beta) > version_tuple(local_beta): + # Update config.json with beta update status + update_config_json(beta=True, beta_version=remote_beta) # Only notify if we haven't already notified for this version if self._notified_proxmenux_beta_version != remote_beta: self._notified_proxmenux_beta_version = remote_beta