feat(ui): Monitoring renders from cached resources (B4)
Some checks failed
Demo images / Build & push demo images (push) Failing after 2m7s

monitoring.current/history/alerts/alert-rules become useCachedResource
entries: revisiting the page paints the last snapshot, chart, and alert
list instantly and the 5s poll revalidates behind them (refreshes dedup
in the store; errors keep last-known values instead of blanking).
Alert-rule toggles and acknowledgements refresh their entries after the
mutation as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago 2026-07-28 05:22:06 -04:00
parent 529c7fe25d
commit 43e50e669e

View File

@ -218,6 +218,7 @@ import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { rpcClient } from '@/api/rpc-client'
import { useCachedResource } from '@/composables/useCachedResource'
import { useHomeStatusStore } from '@/stores/homeStatus'
import BackButton from '@/components/BackButton.vue'
import LineChart from '@/components/LineChart.vue'
@ -291,11 +292,51 @@ const backTarget = computed(() => (cameFromHome.value ? '/dashboard' : '/dashboa
const backLabel = computed(() => (cameFromHome.value ? t('common.back') : 'Web5'))
const homeStatus = useHomeStatusStore()
const current = ref<MetricSnapshot | null>(null)
const history = ref<MetricSnapshot[]>([])
const containers = ref<ContainerMetrics[]>([])
const alerts = ref<FiredAlert[]>([])
const alertRules = ref<AlertRule[]>([])
// Cached: revisits paint the last snapshot/chart/alerts instantly and the 5s
// poll revalidates behind them; errors keep the last-known values.
const currentRes = useCachedResource<MetricSnapshot>({
key: 'monitoring.current',
fetcher: async (signal) => {
const data = await rpcClient.call<MetricSnapshot | { status: string }>({
method: 'monitoring.current', signal, dedup: true, maxRetries: 1,
})
if (!data || !('system' in data)) throw new Error('metrics not ready')
return data
},
})
const historyRes = useCachedResource<MetricSnapshot[]>({
key: 'monitoring.history.minute60',
fetcher: async (signal) => {
const data = await rpcClient.call<HistoryResponse>({
method: 'monitoring.history', params: { resolution: 'minute', count: 60 },
signal, dedup: true, maxRetries: 1,
})
return data?.data ?? []
},
})
const alertsRes = useCachedResource<FiredAlert[]>({
key: 'monitoring.alerts',
fetcher: async (signal) => {
const data = await rpcClient.call<{ alerts: FiredAlert[] }>({
method: 'monitoring.alerts', params: { count: 50 }, signal, dedup: true, maxRetries: 1,
})
return (data?.alerts ?? []).reverse()
},
})
const alertRulesRes = useCachedResource<AlertRule[]>({
key: 'monitoring.alert-rules',
fetcher: async (signal) => {
const data = await rpcClient.call<{ rules: AlertRule[] }>({
method: 'monitoring.alert-rules', signal, dedup: true, maxRetries: 1,
})
return data?.rules ?? []
},
})
const current = computed(() => currentRes.data.value)
const history = computed(() => historyRes.data.value ?? [])
const containers = computed<ContainerMetrics[]>(() => currentRes.data.value?.containers ?? [])
const alerts = computed(() => alertsRes.data.value ?? [])
const alertRules = computed(() => alertRulesRes.data.value ?? [])
const showAlertConfig = ref(false)
const chartWidth = ref(380)
let pollTimer: ReturnType<typeof setInterval> | null = null
@ -464,66 +505,10 @@ async function exportMetrics(format: 'csv' | 'json') {
}
}
async function fetchCurrent() {
try {
await homeStatus.refreshSystemStats()
const data = await rpcClient.call<MetricSnapshot | { status: string }>({
method: 'monitoring.current',
})
if (data && 'system' in data) {
current.value = data
containers.value = data.containers ?? []
}
} catch {
// Silently retry on next poll
}
}
async function fetchHistory() {
try {
const data = await rpcClient.call<HistoryResponse>({
method: 'monitoring.history',
params: { resolution: 'minute', count: 60 },
})
if (data?.data) {
history.value = data.data
}
} catch {
// Silently retry on next poll
}
}
async function fetchAlerts() {
try {
const data = await rpcClient.call<{ alerts: FiredAlert[] }>({
method: 'monitoring.alerts',
params: { count: 50 },
})
if (data?.alerts) {
alerts.value = data.alerts.reverse()
}
} catch {
// Silently retry on next poll
}
}
async function fetchAlertRules() {
try {
const data = await rpcClient.call<{ rules: AlertRule[] }>({
method: 'monitoring.alert-rules',
})
if (data?.rules) {
alertRules.value = data.rules
}
} catch {
// Non-critical
}
}
async function toggleAlertRule(kind: string, enabled: boolean) {
try {
await rpcClient.call({ method: 'monitoring.configure-alert', params: { kind, enabled } })
await fetchAlertRules()
await alertRulesRes.refresh()
} catch {
// Non-critical
}
@ -534,7 +519,7 @@ async function updateThreshold(kind: string, value: string) {
if (isNaN(threshold) || threshold <= 0) return
try {
await rpcClient.call({ method: 'monitoring.configure-alert', params: { kind, threshold } })
await fetchAlertRules()
await alertRulesRes.refresh()
} catch {
// Non-critical
}
@ -543,7 +528,7 @@ async function updateThreshold(kind: string, value: string) {
async function acknowledgeAlert(id: string) {
try {
await rpcClient.call({ method: 'monitoring.acknowledge-alert', params: { id } })
await fetchAlerts()
await alertsRes.refresh()
} catch {
// Non-critical
}
@ -556,18 +541,18 @@ function updateChartWidth() {
}
}
onMounted(async () => {
onMounted(() => {
updateChartWidth()
window.addEventListener('resize', updateChartWidth)
await Promise.all([fetchCurrent(), fetchHistory(), fetchAlerts(), fetchAlertRules()])
pollTimer = setInterval(async () => {
try {
await Promise.all([fetchCurrent(), fetchHistory(), fetchAlerts()])
} catch {
// Background poll ignore transient errors
}
// The cached resources fetch themselves on first use; the poll keeps the
// live view fresh (refreshes dedup in the store).
void homeStatus.refreshSystemStats()
pollTimer = setInterval(() => {
void homeStatus.refreshSystemStats()
void currentRes.refresh()
void historyRes.refresh()
void alertsRes.refresh()
}, 5000)
})