Files
Catalyst/js/db.js
josh cb01573cdf
All checks were successful
CI / test (pull_request) Successful in 13s
CI / build-dev (pull_request) Has been skipped
feat: audit log / history timeline on instance detail page
Adds an instance_history table that records every field change:
- createInstance logs a 'created' event
- updateInstance diffs old vs new and logs one row per changed field
  (name, state, stack, vmid, tailscale_ip, all service flags)
- History is stored under the new vmid when vmid changes

New endpoint: GET /api/instances/:vmid/history

The 'timestamps' section on the detail page is replaced with a
grid timeline showing timestamp | field | old → new for each event.
State changes are colour-coded (deployed=green, testing=amber,
degraded=red). Boolean service flags display as on/off.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 14:35:35 -04:00

63 lines
2.1 KiB
JavaScript

// API client — replaces the sql.js database layer.
// Swap these fetch() calls for any other transport when needed.
const BASE = '/api';
async function api(path, options = {}) {
const res = await fetch(BASE + path, options);
if (res.status === 204) return null;
return res.json().then(data => ({ ok: res.ok, status: res.status, data }));
}
// ── Queries ───────────────────────────────────────────────────────────────────
async function getInstances(filters = {}) {
const params = new URLSearchParams(
Object.entries(filters).filter(([, v]) => v)
);
const res = await fetch(`${BASE}/instances?${params}`);
return res.json();
}
async function getInstance(vmid) {
const res = await fetch(`${BASE}/instances/${vmid}`);
if (res.status === 404) return null;
return res.json();
}
async function getDistinctStacks() {
const res = await fetch(`${BASE}/instances/stacks`);
return res.json();
}
// ── Mutations ─────────────────────────────────────────────────────────────────
async function createInstance(data) {
const { ok, data: body } = await api('/instances', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!ok) return { ok: false, error: body.error ?? body.errors?.[0] ?? 'error creating instance' };
return { ok: true };
}
async function updateInstance(vmid, data) {
const { ok, data: body } = await api(`/instances/${vmid}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!ok) return { ok: false, error: body.error ?? body.errors?.[0] ?? 'error updating instance' };
return { ok: true };
}
async function deleteInstance(vmid) {
await api(`/instances/${vmid}`, { method: 'DELETE' });
}
async function getInstanceHistory(vmid) {
const res = await fetch(`${BASE}/instances/${vmid}/history`);
return res.json();
}