feat(dashboard): scaffold SvelteKit SPA for control plane
SvelteKit 2 + Svelte 5 (runes) + TS, built with `adapter-static`
into a single SPA bundle that Caddy serves verbatim in production.
The dashboard targets only `/api/admin/*` (manager); data-plane
invocations go through the orchestrator, not through here.
* Vite dev server proxies `/api` and `/healthz` to PICLOUD_API
(default `http://127.0.0.1:18080` to match the picloud bind
override). Port configurable via PICLOUD_DASHBOARD_PORT.
* `src/lib/api.ts` is a thin typed client over the control-plane
paths; the scripts placeholder route shows the "load → error →
list" shape so the missing-API state is informative, not blank.
* SSR disabled at the layout level: the build is a pure SPA, no
Node runtime is required at serve time.
* `npm run check` and `npm run build` both green; `npm audit`
clean (cookie override pins past the SvelteKit transitive
advisory that doesn't apply in SPA mode).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
45
dashboard/src/lib/api.ts
Normal file
45
dashboard/src/lib/api.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
// Thin client for the PiCloud control-plane API.
|
||||
//
|
||||
// All admin/CRUD calls hit `/api/admin/*` (manager). Data-plane calls
|
||||
// (script invocations) go to `/api/execute/*` (orchestrator). The
|
||||
// dashboard only talks to the control plane — data-plane invocations
|
||||
// from the dashboard go through the same path as any external caller.
|
||||
|
||||
export interface Script {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
version: number;
|
||||
source: string;
|
||||
timeout_seconds: number;
|
||||
memory_limit_mb: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
async function request<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
const res = await fetch(path, {
|
||||
...init,
|
||||
headers: { 'content-type': 'application/json', ...(init?.headers ?? {}) }
|
||||
});
|
||||
if (!res.ok) {
|
||||
const body = await res.text().catch(() => '');
|
||||
throw new Error(`${res.status} ${res.statusText}: ${body}`);
|
||||
}
|
||||
return res.json() as Promise<T>;
|
||||
}
|
||||
|
||||
export const api = {
|
||||
health: () => fetch('/healthz').then((r) => r.text()),
|
||||
|
||||
scripts: {
|
||||
list: () => request<Script[]>('/api/admin/scripts'),
|
||||
get: (id: string) => request<Script>(`/api/admin/scripts/${id}`),
|
||||
create: (input: Partial<Script>) =>
|
||||
request<Script>('/api/admin/scripts', { method: 'POST', body: JSON.stringify(input) }),
|
||||
update: (id: string, input: Partial<Script>) =>
|
||||
request<Script>(`/api/admin/scripts/${id}`, { method: 'PUT', body: JSON.stringify(input) }),
|
||||
remove: (id: string) =>
|
||||
request<void>(`/api/admin/scripts/${id}`, { method: 'DELETE' })
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user