1
Fork 0
mirror of https://github.com/thegeneralist01/archivr synced 2026-07-22 03:05:32 +02:00
archivr/frontend/src/api.js
TheGeneralist 4458f17b13
feat(ui): rewrite frontend in React with Vite
- Scaffold Vite+React project in frontend/ (bun, react 18, @vitejs/plugin-react)
- vite.config.js outputs to crates/archivr-server/static/ directly
- src/utils.js: formatBytes, valueText, formatTimestamp, SOURCE_ICONS, sourceIconSvg
- src/api.js: typed fetch wrappers for all API endpoints
- App.jsx: full state management, archive switching, debounced search,
  tag filter, view routing, capture dialog orchestration
- components/Topbar.jsx: archive switcher, nav, capture button
- components/CaptureDialog.jsx: native <dialog> ref with showModal/close,
  Escape key support, locator validation
- components/EntriesView.jsx + EntryRow.jsx: flex table with source icons,
  type pills, keyboard selection
- components/ContextRail.jsx: parallel detail+tags fetch, stale-race guard
  via selectSeqRef, inline tag assign/remove
- components/RunsView.jsx: runs kept as <table> (matches original)
- components/AdminView.jsx: mounted archives list
- components/TagsView.jsx: recursive tag tree with active state
- Remove old app.js; styles.css moved to frontend/src/ (Vite bundles it)
2026-06-24 12:24:34 +02:00

70 lines
2 KiB
JavaScript

async function getJson(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`${response.status} ${response.statusText}`);
}
return response.json();
}
export async function fetchArchives() {
return getJson("/api/archives");
}
export async function fetchEntries(archiveId) {
return getJson(`/api/archives/${archiveId}/entries`);
}
export async function searchEntries(archiveId, q, tag) {
const params = new URLSearchParams();
if (q) params.set("q", q);
if (tag) params.set("tag", tag);
return getJson(`/api/archives/${archiveId}/entries/search?${params}`);
}
export async function fetchEntryDetail(archiveId, entryUid) {
return getJson(`/api/archives/${archiveId}/entries/${entryUid}`);
}
export async function fetchEntryTags(archiveId, entryUid) {
return getJson(`/api/archives/${archiveId}/entries/${entryUid}/tags`);
}
export async function assignTag(archiveId, entryUid, tagPath) {
const resp = await fetch(
`/api/archives/${archiveId}/entries/${entryUid}/tags`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ tag_path: tagPath }),
}
);
if (!resp.ok) throw new Error(`Failed to add tag (${resp.status})`);
}
export async function removeTag(archiveId, entryUid, tagUid) {
const resp = await fetch(
`/api/archives/${archiveId}/entries/${entryUid}/tags/${tagUid}`,
{ method: "DELETE" }
);
if (!resp.ok) throw new Error(`Remove failed (${resp.status})`);
}
export async function fetchRuns(archiveId) {
return getJson(`/api/archives/${archiveId}/runs`);
}
export async function fetchTags(archiveId) {
return getJson(`/api/archives/${archiveId}/tags`);
}
export async function submitCapture(archiveId, locator) {
const res = await fetch(`/api/archives/${archiveId}/captures`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ locator }),
});
if (!res.ok) {
const msg = await res.text();
throw new Error(msg || `HTTP ${res.status}`);
}
}