feat: add admin panel with project and tag management

- Full CRUD interface for projects with GitHub integration and tagging
- Real-time event log with expandable metadata viewer
- Reusable component library (Badge, Button, Input, Modal, Table,
TagPicker)
- Server-side API client with Unix socket and HTTP support
- JWT-based authentication with Svelte 5 reactive stores
- Settings management for social links and site identity
- Remove /admin path from tarpit to allow legitimate access
This commit is contained in:
2026-01-06 10:07:30 -06:00
parent 045781f7a5
commit 16bf2b76f3
32 changed files with 3260 additions and 60 deletions
+64
View File
@@ -0,0 +1,64 @@
import { getLogger } from "@logtape/logtape";
import { env } from "$env/dynamic/private";
const logger = getLogger(["ssr", "lib", "api"]);
const upstreamUrl = env.UPSTREAM_URL;
const isUnixSocket =
upstreamUrl?.startsWith("/") || upstreamUrl?.startsWith("./");
const baseUrl = isUnixSocket ? "http://localhost" : upstreamUrl;
export async function apiFetch<T>(
path: string,
init?: RequestInit,
): Promise<T> {
if (!upstreamUrl) {
logger.error("UPSTREAM_URL environment variable not set");
throw new Error("UPSTREAM_URL environment variable not set");
}
const url = `${baseUrl}${path}`;
const method = init?.method ?? "GET";
const fetchOptions: RequestInit & { unix?: string } = {
...init,
signal: init?.signal ?? AbortSignal.timeout(30_000),
};
if (isUnixSocket) {
fetchOptions.unix = upstreamUrl;
}
logger.debug("API request", {
method,
url,
path,
isUnixSocket,
upstreamUrl,
});
try {
const response = await fetch(url, fetchOptions);
if (!response.ok) {
logger.error("API request failed", {
method,
url,
status: response.status,
statusText: response.statusText,
});
throw new Error(`API error: ${response.status} ${response.statusText}`);
}
const data = await response.json();
logger.debug("API response", { method, url, status: response.status });
return data;
} catch (error) {
logger.error("API request exception", {
method,
url,
error: error instanceof Error ? error.message : String(error),
});
throw error;
}
}