fix(api): attach Bearer token to services/widgets/backups requests

Under AUTH_ENABLED=true, api/services.ts, api/widgets.ts, and
api/backups.ts called fetch() directly without attaching the OIDC
access token, so every services/widgets/backups request 401'd while
api/client.ts requests succeeded. The token was only attached in
client.ts.

Extract the auth-attaching fetch helpers (buildUrl/buildHeaders/
readErrorDetail + get/post/put/del/postForm) into a new api/shared.ts
that consults getAccessToken(), rewrite services.ts/widgets.ts/
backups.ts to use them, and consolidate client.ts to import from
shared.ts (removing its duplicated copies). Now every backend request
goes through one auth-attaching path.

As a side benefit, error messages surface the HTTP status + backend
detail instead of a generic "Failed to ..." string.

Bug masked in dev because dev runs AUTH_ENABLED=false. npm run build
clean; 0 lint errors; 72 frontend tests pass.
This commit is contained in:
Developer
2026-06-26 08:18:39 +00:00
parent 8bc209b27e
commit 04319025de
5 changed files with 156 additions and 195 deletions
+26 -36
View File
@@ -1,3 +1,4 @@
import { get, post } from "./shared";
import type {
BackupAlert,
BackupDashboardSummary,
@@ -5,59 +6,48 @@ import type {
BackupRun,
} from "../types/backups";
const API_BASE = "/api";
export async function fetchBackupJobs(): Promise<BackupJob[]> {
const res = await fetch(`${API_BASE}/backups/jobs`);
if (!res.ok) throw new Error("Failed to fetch backup jobs");
return res.json();
return get<BackupJob[]>("/api/backups/jobs");
}
export async function fetchBackupJob(jobId: string): Promise<{ job: BackupJob; runs: BackupRun[] }> {
const res = await fetch(`${API_BASE}/backups/jobs/${jobId}`);
if (!res.ok) throw new Error("Failed to fetch backup job");
return res.json();
export async function fetchBackupJob(
jobId: string,
): Promise<{ job: BackupJob; runs: BackupRun[] }> {
return get<{ job: BackupJob; runs: BackupRun[] }>(`/api/backups/jobs/${jobId}`);
}
export async function fetchBackupRuns(jobId?: string, status?: string): Promise<BackupRun[]> {
const params = new URLSearchParams();
if (jobId) params.append("job_id", jobId);
if (status) params.append("status", status);
const res = await fetch(`${API_BASE}/backups/runs?${params}`);
if (!res.ok) throw new Error("Failed to fetch backup runs");
return res.json();
export async function fetchBackupRuns(
jobId?: string,
status?: string,
): Promise<BackupRun[]> {
return get<BackupRun[]>("/api/backups/runs", {
...(jobId ? { job_id: jobId } : {}),
...(status ? { status } : {}),
});
}
export async function fetchBackupRun(runId: string): Promise<BackupRun> {
const res = await fetch(`${API_BASE}/backups/runs/${runId}`);
if (!res.ok) throw new Error("Failed to fetch backup run");
return res.json();
return get<BackupRun>(`/api/backups/runs/${runId}`);
}
export async function fetchBackupAlerts(
jobId?: string,
acknowledged?: boolean,
severity?: string
severity?: string,
): Promise<BackupAlert[]> {
const params = new URLSearchParams();
if (jobId) params.append("job_id", jobId);
if (acknowledged !== undefined) params.append("acknowledged", String(acknowledged));
if (severity) params.append("severity", severity);
const res = await fetch(`${API_BASE}/backups/alerts?${params}`);
if (!res.ok) throw new Error("Failed to fetch backup alerts");
return res.json();
return get<BackupAlert[]>("/api/backups/alerts", {
...(jobId ? { job_id: jobId } : {}),
...(acknowledged !== undefined ? { acknowledged: String(acknowledged) } : {}),
...(severity ? { severity } : {}),
});
}
export async function acknowledgeBackupAlert(alertId: string): Promise<BackupAlert> {
const res = await fetch(`${API_BASE}/backups/alerts/${alertId}/acknowledge`, {
method: "POST",
});
if (!res.ok) throw new Error("Failed to acknowledge alert");
return res.json();
export async function acknowledgeBackupAlert(
alertId: string,
): Promise<BackupAlert> {
return post<BackupAlert>(`/api/backups/alerts/${alertId}/acknowledge`);
}
export async function fetchBackupDashboard(): Promise<BackupDashboardSummary> {
const res = await fetch(`${API_BASE}/dashboard/backups`);
if (!res.ok) throw new Error("Failed to fetch backup dashboard");
return res.json();
return get<BackupDashboardSummary>("/api/dashboard/backups");
}