Files
Backup-Dashboard/public/js/api.js
2026-10-05 04:00:29 +00:00

61 lines
2.1 KiB
JavaScript

import { CONFIG } from './config.js';
export class ApiError extends Error {
constructor(message, status = null) {
super(message);
this.name = 'ApiError';
this.status = status;
}
}
async function request(path) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), CONFIG.requestTimeoutMs);
try {
const res = await fetch(`${CONFIG.apiBaseUrl}${path}`, {
headers: { Accept: 'application/json' },
credentials: 'same-origin', // ready for cookie/session auth behind nginx
signal: controller.signal,
});
// 401 means the nginx/SSO session lapsed; a reload sends the user back through sign-in
if (res.status === 401) throw new ApiError('Your session has expired. Reload the page to sign in again', 401);
if (!res.ok) throw new ApiError(`Server returned HTTP ${res.status}`, res.status);
return await res.json();
} catch (err) {
if (err.name === 'AbortError') throw new ApiError('Request timed out');
if (err instanceof ApiError) throw err;
throw new ApiError(`Network error: ${err.message}`);
} finally {
clearTimeout(timer);
}
}
const httpApi = {
/** GET /jobs → { generated_at, jobs: Job[] } */
async listJobs() {
const data = await request('/jobs');
if (!Array.isArray(data?.jobs)) throw new ApiError('Unexpected response shape from /jobs');
return data;
},
/** GET /jobs/:id → JobDetail */
getJob(id) {
return request(`/jobs/${encodeURIComponent(id)}`);
},
/** GET /jobs/:id/runs?days=N → { job_id, since, runs: RunSummary[] } (newest first, no messages) */
listRuns(id, days) {
return request(`/jobs/${encodeURIComponent(id)}/runs?days=${encodeURIComponent(days)}`);
},
/** GET /runs/:id → Run (with message and details) */
getRun(id) {
return request(`/runs/${encodeURIComponent(id)}`);
},
};
// Mock is loaded lazily so it never ships in the production request path.
export async function getApi() {
if (CONFIG.useMock) {
const { mockApi } = await import('./mock.js');
return mockApi;
}
return httpApi;
}