Private
Public Access
1
0

M5: Patch Deployment & Job Management

Backend:
- migrations/003_jobs_scheduling.sql: retry_next_at/last_error columns,
  pg_notify trigger for immediate job dispatch, retry index
- pm-agent-client: ApplyPatchesRequest/Response, AgentJobStatus,
  RollbackResponse types; apply_patches/job_status/rollback_job
  client methods + generic POST helper
- pm-core/models: JobStatus, JobKind, PatchJob, PatchJobHost,
  CreateJobRequest, PatchJobSummary
- pm-web/routes/jobs.rs: POST/GET /api/v1/jobs, GET /jobs/:id,
  POST /jobs/:id/cancel, POST /jobs/:id/rollback
- pm-worker/job_executor.rs: NOTIFY listener, periodic scanner,
  execute_host_job, poll_running_jobs, handle_host_failure (3-retry
  exponential backoff 1m/5m/30m), sync_job_status, retry_pending_jobs
- pm-worker/main.rs: spawn job_executor

Frontend:
- types/index.ts: PatchInfo, PatchJobHost, PatchJob, PatchJobSummary,
  CreateJobRequest interfaces
- api/client.ts: jobsApi (list/get/create/cancel/rollback),
  patchesApi (getHostPatches)
- pages/PatchDeploymentPage.tsx: 3-step MUI Stepper
  (host select → configure → result)
- pages/JobsPage.tsx: job list table, expandable per-host detail,
  cancel/rollback actions with confirm dialog, load-more pagination
- App.tsx: /jobs and /deployment routes wired to real pages

cargo check: 0 errors | vite build: 0 errors
This commit is contained in:
2026-04-23 17:08:43 +00:00
parent a6eb762962
commit 6f9c6dc881
30 changed files with 8465 additions and 44 deletions

View File

@ -1,6 +1,7 @@
import axios, { type AxiosError } from 'axios'
import type { InternalAxiosRequestConfig } from 'axios'
import { useAuthStore } from '../store/authStore'
import type { FleetStatus, CreateJobRequest } from '../types'
const BASE_URL = '/api/v1'
@ -93,3 +94,32 @@ export const authApi = {
verifyMfa: (secretBase32: string, code: string) =>
apiClient.post('/auth/mfa/verify', { secret_base32: secretBase32, code }),
}
// ── Fleet API functions ──────────────────────────────────────────────────────
export const fleetApi = {
getStatus: () => apiClient.get<FleetStatus>('/status/fleet'),
}
// ── Hosts API functions ──────────────────────────────────────────────────────
export const hostsApi = {
list: (params?: Record<string, unknown>) => apiClient.get('/hosts', { params }),
get: (id: string) => apiClient.get(`/hosts/${id}`),
delete: (id: string) => apiClient.delete(`/hosts/${id}`),
refresh: (id: string) => apiClient.post(`/hosts/${id}/refresh`),
}
// ── Jobs API ─────────────────────────────────────────────────────────────────
export const jobsApi = {
list: (params?: Record<string, unknown>) => apiClient.get('/jobs', { params }),
get: (id: string) => apiClient.get(`/jobs/${id}`),
create: (body: CreateJobRequest) => apiClient.post('/jobs', body),
cancel: (id: string) => apiClient.post(`/jobs/${id}/cancel`),
rollback: (id: string) => apiClient.post(`/jobs/${id}/rollback`),
}
// ── Patches API (per-host patch listing) ──────────────────────────────────────
export const patchesApi = {
// Returns patches available on a specific host via the manager's proxy
// The backend reads from host_patch_data table (cached from agent poll)
getHostPatches: (hostId: string) => apiClient.get(`/hosts/${hostId}/patches`),
}