diff --git a/frontend/src/components/config/PoolPanel.tsx b/frontend/src/components/config/PoolPanel.tsx index 49fd9a0..4fb674d 100644 --- a/frontend/src/components/config/PoolPanel.tsx +++ b/frontend/src/components/config/PoolPanel.tsx @@ -170,7 +170,6 @@ export function PoolPanel({ tenantId, deviceId, active }: ConfigPanelProps) { entries={typedEntries} panel={panel} poolUsedBy={poolUsedBy} - existingPools={typedEntries.map((e) => e.name).filter(Boolean)} /> {/* Change Preview Modal */} @@ -197,13 +196,10 @@ function PoolTable({ entries, panel, poolUsedBy, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - existingPools: _existingPools, }: { entries: PoolEntry[] panel: PanelHook poolUsedBy: Record - existingPools: string[] }) { const [dialogOpen, setDialogOpen] = useState(false) const [editing, setEditing] = useState(null) diff --git a/frontend/src/components/config/WifiPanel.tsx b/frontend/src/components/config/WifiPanel.tsx index 3cc922a..8de778b 100644 --- a/frontend/src/components/config/WifiPanel.tsx +++ b/frontend/src/components/config/WifiPanel.tsx @@ -9,7 +9,7 @@ * 2. Security Profiles (RouterOS 6 only) -- authentication, passphrases */ -import { useState, useMemo, useCallback } from 'react' +import { useState, useMemo, useCallback, useEffect } from 'react' import { Wifi, Plus, @@ -629,9 +629,7 @@ function WirelessEditDialog({ }) // Reset form when entry changes - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const _entryId = entry?.['.id'] || '' - useState(() => { + useEffect(() => { if (entry) { setFormData({ ssid: entry.ssid || entry['configuration.ssid'] || '', @@ -643,7 +641,7 @@ function WirelessEditDialog({ 'security.passphrase': entry['security.passphrase'] || '', }) } - }) + }, [entry]) // Use effect-like pattern to reset form on dialog open const handleOpenChange = useCallback((nextOpen: boolean) => { diff --git a/frontend/src/routes/_authenticated/tenants/$tenantId/devices/$deviceId.tsx b/frontend/src/routes/_authenticated/tenants/$tenantId/devices/$deviceId.tsx index 4b2ee2e..9c1bf74 100644 --- a/frontend/src/routes/_authenticated/tenants/$tenantId/devices/$deviceId.tsx +++ b/frontend/src/routes/_authenticated/tenants/$tenantId/devices/$deviceId.tsx @@ -18,7 +18,7 @@ import { ShieldOff, Shield, } from 'lucide-react' -import { devicesApi, deviceGroupsApi, deviceTagsApi, tenantsApi, type DeviceResponse, type DeviceUpdate } from '@/lib/api' +import { devicesApi, deviceGroupsApi, deviceTagsApi, tenantsApi, configApi, type DeviceResponse, type DeviceUpdate } from '@/lib/api' import { alertsApi } from '@/lib/alertsApi' import { useAuth, canWrite, canDelete } from '@/lib/auth' import { toast } from '@/components/ui/toast' @@ -59,6 +59,7 @@ import { SimpleConfigView } from '@/components/simple-config/SimpleConfigView' import { WinBoxButton } from '@/components/fleet/WinBoxButton' import { RemoteWinBoxButton } from '@/components/fleet/RemoteWinBoxButton' import { SSHTerminal } from '@/components/fleet/SSHTerminal' +import { RollbackAlert } from '@/components/config/RollbackAlert' export const Route = createFileRoute( '/_authenticated/tenants/$tenantId/devices/$deviceId', @@ -348,6 +349,21 @@ function DeviceDetailPage() { queryFn: () => tenantsApi.get(tenantId), }) + const { data: backups } = useQuery({ + queryKey: ['config-backups', tenantId, deviceId], + queryFn: () => configApi.listBackups(tenantId, deviceId), + }) + + // True if a pre-restore backup was created within the last 30 minutes, + // indicating a config push just happened before the device went offline. + const hasRecentPushAlert = backups + ? backups.some((b) => { + if (b.trigger_type !== 'pre-restore') return false + const age = Date.now() - new Date(b.created_at).getTime() + return age < 30 * 60 * 1000 + }) + : false + const { data: groups } = useQuery({ queryKey: ['device-groups', tenantId], queryFn: () => deviceGroupsApi.list(tenantId), @@ -482,6 +498,14 @@ function DeviceDetailPage() { + {/* Emergency rollback banner */} + + {/* Config View (Simple or Standard) */}