import type { Context, Hono } from "hono"; import { checkFormat, listRepos } from "../bucket/repo.ts"; import type { SyncTarget } from "../bucket/sync.ts"; import { type Html, html } from "../pages/html.ts"; import { type PageContext, page } from "../pages/layout.ts"; import { formToken } from "../server/login.ts"; export type AdminContext = PageContext & { sync: SyncTarget; dataDir: string }; const SECTIONS = [ ["/admin", "status"], ["/admin/repos", "repos"], ["/admin/issues", "issues"], ["/admin/releases", "releases"], ["/admin/site", "site"], ] as const; /** The hidden same-site token (§25) that every admin form posts back. */ export function formField(c: Context): Html { return html``; } /** An admin page: the site's own look, with the console's sections and sign-out on top. */ export function adminPage(c: Context, ctx: AdminContext, title: string, body: Html): string { const tab = ([href, name]: (typeof SECTIONS)[number]) => html`${name}`; return page(ctx, { title: `${title} · admin`, description: "", path: c.req.path, body: html`
${SECTIONS.map(tab)}${formField(c)}

${title}

${body}`, }); } // ponytail: counted once per process by walking the source, since there is no build step. let runningLines: number | null = null; async function sourceLines(): Promise { if (runningLines !== null) return runningLines; let total = 0; const root = `${import.meta.dir}/../..`; for await (const path of new Bun.Glob("{src,scripts}/**/*.ts").scan(root)) { if (path.endsWith(".test.ts")) continue; const text = await Bun.file(`${root}/${path}`).text(); total += text.split("\n").length - (text.endsWith("\n") ? 1 : 0); } runningLines = total; return total; } /** §24's Status section at `/admin`: the bucket, each repo's format, and the running version. */ export function registerStatus(app: Hono, ctx: AdminContext): void { app.get("/admin", async (c) => { let bucket: Html; try { const repos = await listRepos(ctx.sync.bucket); const formats = await Promise.all( repos.map(async (repo) => { try { await checkFormat(ctx.sync.bucket, repo); return html`
  • ${repo} format ok
  • `; } catch (error) { return html`
  • ${repo} ${String(error)}
  • `; } }), ); bucket = html`

    Bucket reachable: ${repos.length} repos.

    `; } catch (error) { bucket = html``; } const { version } = (await Bun.file(`${import.meta.dir}/../../package.json`).json()) as { version: string; }; const lines = (await sourceLines()).toLocaleString("en-US"); return c.html( adminPage( c, ctx, "status", html`
    ${bucket}

    Running kepr ${version}: ${lines} lines of TypeScript.

    `, ), ); }); }