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>
63 lines
2.1 KiB
JavaScript
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();
|
|
}
|