From 79e97e14cc5ceb0501e891b217fe4a28a1989911 Mon Sep 17 00:00:00 2001 From: 2GT-Rich <107219997+2GT-Rich@users.noreply.github.com> Date: Sat, 11 Jul 2026 22:05:12 -0700 Subject: [PATCH] fix(ui): firmware state syncs across screens immediately (v0.16.7-beta) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Firmware actions on the device Config tab (check/install RouterOS, check/install RouterBOOT) and Firmware-page actions now invalidate the shared react-query caches on success — devices list, device header, firmware overview, dashboard "Things to handle", and Security Center findings. Previously they only updated local component state, so other screens showed stale "update available" data for up to 30s until their own background refetch fired, even though the backend had already persisted the new state. Browser-verified: after a firmware check on the Config tab, /api/devices refetches within the same second (sidebar observer) and the Devices page fetches fresh data on mount; both screens agree immediately. --- README.md | 2 +- backend/package.json | 2 +- frontend/package.json | 2 +- .../device-detail/SystemConfigTab.tsx | 19 +++++++++++++++++-- frontend/src/pages/FirmwarePage.tsx | 5 +++++ frontend/src/version.ts | 2 +- 6 files changed, 26 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1208e29..9361356 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A self-hosted, full-stack network management platform for MikroTik devices. Monitor, configure, and manage your entire MikroTik infrastructure — routers, switches, and wireless access points — from a single web interface. -![Version](https://img.shields.io/badge/version-0.16.6_Beta-blue) +![Version](https://img.shields.io/badge/version-0.16.7_Beta-blue) ![License](https://img.shields.io/badge/license-AGPLv3-blue) ![Docker](https://img.shields.io/badge/docker-compose-2496ED?logo=docker&logoColor=white) ![TypeScript](https://img.shields.io/badge/TypeScript-5.3-3178C6?logo=typescript&logoColor=white) diff --git a/backend/package.json b/backend/package.json index 6b78f73..93f0e9f 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "mikrotik-manager-backend", - "version": "0.16.6-beta", + "version": "0.16.7-beta", "description": "Mikrotik Network Management Platform - Backend API", "main": "dist/index.js", "scripts": { diff --git a/frontend/package.json b/frontend/package.json index d65c403..12aa617 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "mikrotik-manager-frontend", - "version": "0.16.6-beta", + "version": "0.16.7-beta", "private": true, "type": "module", "scripts": { diff --git a/frontend/src/components/device-detail/SystemConfigTab.tsx b/frontend/src/components/device-detail/SystemConfigTab.tsx index 9aa6559..3a4141d 100644 --- a/frontend/src/components/device-detail/SystemConfigTab.tsx +++ b/frontend/src/components/device-detail/SystemConfigTab.tsx @@ -135,10 +135,23 @@ export default function SystemConfigTab({ deviceId, device }: Props) { } }, [device.id]); + // Firmware state is rendered on many screens (Devices list, Firmware page, + // dashboard "Things to handle", Security Center). The backend persists the + // new state immediately, so refresh those caches whenever it changes here — + // otherwise other pages keep stale "update available" data until their own + // 30s refetch fires. + const invalidateFirmwareState = () => { + queryClient.invalidateQueries({ queryKey: ['devices'] }); + queryClient.invalidateQueries({ queryKey: ['device', deviceId] }); + queryClient.invalidateQueries({ queryKey: ['fw-overview'] }); + queryClient.invalidateQueries({ queryKey: ['ops-insights'] }); + queryClient.invalidateQueries({ queryKey: ['security-fleet'] }); + }; + const checkUpdateMutation = useMutation({ mutationFn: () => devicesApi.checkUpdate(deviceId), onMutate: () => { setUpdateChecking(true); setUpdateError(''); setInstallSuccess(''); }, - onSuccess: (res) => { setUpdateInfo(res.data); setUpdateChecking(false); }, + onSuccess: (res) => { setUpdateInfo(res.data); setUpdateChecking(false); invalidateFirmwareState(); }, onError: (err: unknown) => { const msg = (err as { response?: { data?: { error?: string } } })?.response?.data?.error; setUpdateError(msg || 'Failed to check for updates'); @@ -151,6 +164,7 @@ export default function SystemConfigTab({ deviceId, device }: Props) { onSuccess: () => { setUpdateInfo(null); setInstallSuccess('Update installation initiated. The device will reboot shortly.'); + invalidateFirmwareState(); }, onError: (err: unknown) => { const msg = (err as { response?: { data?: { error?: string } } })?.response?.data?.error; @@ -188,7 +202,7 @@ export default function SystemConfigTab({ deviceId, device }: Props) { const checkRouterboardMutation = useMutation({ mutationFn: () => devicesApi.checkRouterboard(deviceId), onMutate: () => { setRbChecking(true); setRbError(''); setRbInstallSuccess(''); }, - onSuccess: (res) => { setRbInfo(res.data); setRbChecking(false); }, + onSuccess: (res) => { setRbInfo(res.data); setRbChecking(false); invalidateFirmwareState(); }, onError: (err: unknown) => { const msg = (err as { response?: { data?: { error?: string } } })?.response?.data?.error; setRbError(msg || 'Failed to check RouterBOOT'); @@ -201,6 +215,7 @@ export default function SystemConfigTab({ deviceId, device }: Props) { onSuccess: () => { setRbInfo(null); setRbInstallSuccess('RouterBOOT upgrade initiated. The device will reboot shortly.'); + invalidateFirmwareState(); }, onError: (err: unknown) => { const msg = (err as { response?: { data?: { error?: string } } })?.response?.data?.error; diff --git a/frontend/src/pages/FirmwarePage.tsx b/frontend/src/pages/FirmwarePage.tsx index 70f4976..ce29c9b 100644 --- a/frontend/src/pages/FirmwarePage.tsx +++ b/frontend/src/pages/FirmwarePage.tsx @@ -147,6 +147,11 @@ export default function FirmwarePage() { const invalidate = () => { qc.invalidateQueries({ queryKey: ['fw-overview'] }); qc.invalidateQueries({ queryKey: ['fw-rollouts'] }); + // Firmware flags also drive the Devices page badges, dashboard insights, + // and Security Center findings — keep those screens in sync too. + qc.invalidateQueries({ queryKey: ['devices'] }); + qc.invalidateQueries({ queryKey: ['ops-insights'] }); + qc.invalidateQueries({ queryKey: ['security-fleet'] }); }; const checkAll = useMutation({ diff --git a/frontend/src/version.ts b/frontend/src/version.ts index 2621bf0..1a5afa9 100644 --- a/frontend/src/version.ts +++ b/frontend/src/version.ts @@ -1 +1 @@ -export const APP_VERSION = 'v0.16.6 Beta'; +export const APP_VERSION = 'v0.16.7 Beta';