feat(ops): operations insights engine + working quick actions; fix global search (v0.16.2-beta)

Global search:
- Clicking/Enter on a client result now opens that client's detail page
  (/clients/<mac>) instead of dumping to the clients list.

Operations dashboard (new backend route /api/operations):
- GET /insights: server-side attention engine (offline, updates, CPU/memory
  pressure, missing backups, recent outages, weak WiFi clients, co-channel
  overlap, high TX retries, error events) + per-device capacity + merged
  activity feed (config changes, audit actions, alerts; login noise filtered).
- POST /backup-all and /sync-all: fleet-wide actions over online devices.
- Quick Actions now execute: Run discovery, Backup all, Sync config, and
  Open terminal (device picker -> live SSH TerminalModal); inline status.
- New cards: Capacity & health meters, Security posture rollup (reuses
  per-device posture, cached), Recent activity feed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2GT-Rich
2026-06-25 23:12:16 -07:00
co-authored by Claude Opus 4.8
parent 4e6c9f481e
commit 6cfca387d8
11 changed files with 659 additions and 68 deletions
+30
View File
@@ -413,6 +413,36 @@ export const eventsApi = {
};
// ─── Backups ──────────────────────────────────────────────────────────────────
// ─── Operations ────────────────────────────────────────────────────────────────
export interface OpsAttentionItem {
sev: 'error' | 'warn' | 'info';
category: string;
title: string;
body: string;
action: string;
path: string;
}
export interface OpsCapacityRow { id: number; name: string; cpu: number; mem_pct: number }
export interface OpsActivityItem {
at: string;
kind: 'config' | 'audit' | 'alert';
sev?: string;
title: string;
sub: string;
}
export interface OpsInsights {
attention: OpsAttentionItem[];
capacity: OpsCapacityRow[];
activity: OpsActivityItem[];
}
export interface OpsActionResult { total: number; results: { name: string; ok: boolean; error?: string }[] }
export const operationsApi = {
insights: () => api.get<OpsInsights>('/operations/insights'),
backupAll: () => api.post<OpsActionResult>('/operations/backup-all', undefined, { timeout: 180_000 }),
syncAll: () => api.post<OpsActionResult>('/operations/sync-all', undefined, { timeout: 180_000 }),
};
export const backupsApi = {
list: (deviceId?: number) =>
api.get<Backup[]>('/backups', { params: deviceId ? { deviceId } : {} }),