feat: M6 maintenance windows + M7 WebSocket relay (real-time job status)
M6 - Maintenance Windows: - routes/maintenance_windows.rs: full CRUD API - migrations/004_maintenance_windows.sql - frontend/MaintenanceWindowsPage.tsx - HostDetailPage.tsx: maintenance window config panel M7 - WebSocket Relay: - pm-web: POST /api/v1/ws/ticket (JWT-auth, single-use, 60s TTL) - pm-web: WS /api/v1/ws/jobs?ticket=... (PgListener -> browser push) - pm-web: DashMap<String,WsTicket> in AppState, 30s cleanup task - pm-worker: ws_relay.rs subscribes to agent WS, updates patch_job_hosts, fires pg_notify(job_update) for real-time fan-out - frontend: useJobWebSocket hook with auto-reconnect + exponential backoff - frontend: JobsPage live updates with WS status indicator - types: JobWsEvent interface - api/client: wsApi.createTicket() All tasks marked complete in tasks/todo.md cargo build: zero errors, zero warnings
This commit is contained in:
@ -1,7 +1,12 @@
|
||||
import axios, { type AxiosError } from 'axios'
|
||||
import type { InternalAxiosRequestConfig } from 'axios'
|
||||
import { useAuthStore } from '../store/authStore'
|
||||
import type { FleetStatus, CreateJobRequest } from '../types'
|
||||
import type {
|
||||
FleetStatus,
|
||||
CreateJobRequest,
|
||||
CreateMaintenanceWindowRequest,
|
||||
UpdateMaintenanceWindowRequest,
|
||||
} from '../types'
|
||||
|
||||
const BASE_URL = '/api/v1'
|
||||
|
||||
@ -123,3 +128,22 @@ export const patchesApi = {
|
||||
// The backend reads from host_patch_data table (cached from agent poll)
|
||||
getHostPatches: (hostId: string) => apiClient.get(`/hosts/${hostId}/patches`),
|
||||
}
|
||||
|
||||
// ── Maintenance Windows API ───────────────────────────────────────────────────
|
||||
export const maintenanceWindowsApi = {
|
||||
list: (hostId: string) =>
|
||||
apiClient.get(`/hosts/${hostId}/maintenance-windows`),
|
||||
create: (hostId: string, body: CreateMaintenanceWindowRequest) =>
|
||||
apiClient.post(`/hosts/${hostId}/maintenance-windows`, body),
|
||||
update: (hostId: string, windowId: string, body: UpdateMaintenanceWindowRequest) =>
|
||||
apiClient.put(`/hosts/${hostId}/maintenance-windows/${windowId}`, body),
|
||||
remove: (hostId: string, windowId: string) =>
|
||||
apiClient.delete(`/hosts/${hostId}/maintenance-windows/${windowId}`),
|
||||
}
|
||||
|
||||
// ── WebSocket API (M7) ────────────────────────────────────────────────────────
|
||||
export const wsApi = {
|
||||
/** POST /api/v1/ws/ticket — obtain a single-use WS auth ticket (60 s expiry). */
|
||||
createTicket: (): Promise<{ ticket: string }> =>
|
||||
apiClient.post<{ ticket: string }>('/ws/ticket').then((r) => r.data),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user