import type { EvidenceRow, Framework } from "./exporter"; export interface ExportListRow { id: string; framework: string; format: string; status: string; created_at: string; } export interface InstallationOption { installation_id: number; org_login: string; } // Shown only when the user can see more than one installation; a single-org // user gets the plain org name instead of a pointless dropdown. function installationSwitcher( installations: InstallationOption[], current: number, returnTo: "dashboard" | "access-review", ): string { if (installations.length < 2) return ""; const options = installations .map( (i) => ``, ) .join(""); return `
`; } export interface DashboardData { login: string; installationId: number; orgLogin: string; installations: InstallationOption[]; framework: Framework; rows: EvidenceRow[]; exports: ExportListRow[]; lastPolledAt: string | null; } function esc(value: unknown): string { return String(value ?? "").replace(/[&<>"']/g, (c) => { switch (c) { case "&": return "&"; case "<": return "<"; case ">": return ">"; case '"': return """; default: return "'"; } }); } const STYLE = ` :root { color-scheme: light; } * { box-sizing: border-box; } body { margin: 0; font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; color: #1a1a1a; background: #f6f7f9; } header { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; padding: 1rem 1.5rem; background: #fff; border-bottom: 1px solid #e2e5e9; flex-wrap: wrap; } header h1 { font-size: 1.05rem; margin: 0; } header .who { color: #666; font-size: 0.85rem; } header .who a { color: #0055dc; margin-left: 0.75rem; } main { max-width: 1100px; margin: 0 auto; padding: 1.5rem; } .cards { display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 1.5rem; } .card { flex: 1 1 120px; background: #fff; border: 1px solid #e2e5e9; border-radius: 8px; padding: 0.9rem 1rem; } .card .n { font-size: 1.6rem; font-weight: 600; } .card .l { color: #666; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.03em; } .n.positive { color: #1a8039; } .n.negative { color: #b32626; } .n.informational { color: #666; } .bar { display: flex; gap: 1rem; align-items: center; flex-wrap: wrap; margin-bottom: 1rem; } .bar .filters a { margin-right: 0.5rem; text-decoration: none; color: #0055dc; padding: 0.2rem 0.5rem; border-radius: 5px; } .bar .filters a.active { background: #0055dc; color: #fff; } form { display: inline-flex; gap: 0.4rem; align-items: center; margin: 0; } input, select, button { font: inherit; padding: 0.35rem 0.6rem; border: 1px solid #c9ced6; border-radius: 6px; background: #fff; } button { cursor: pointer; background: #0055dc; color: #fff; border-color: #0055dc; } button.secondary { background: #fff; color: #1a1a1a; } table { width: 100%; border-collapse: collapse; background: #fff; border: 1px solid #e2e5e9; border-radius: 8px; overflow: hidden; } th, td { text-align: left; padding: 0.5rem 0.75rem; border-bottom: 1px solid #eef0f3; font-size: 0.85rem; } th { background: #fafbfc; font-weight: 600; color: #444; } tr:last-child td { border-bottom: none; } .posture { font-weight: 600; } .posture.positive { color: #1a8039; } .posture.negative { color: #b32626; } .posture.informational { color: #888; } .section-title { font-size: 1rem; margin: 2rem 0 0.75rem; } .muted { color: #888; } code { background: #eef0f3; padding: 0.1rem 0.3rem; border-radius: 4px; font-size: 0.85em; } `; export function renderDashboard(data: DashboardData): string { const counts = { positive: 0, negative: 0, informational: 0 }; const repos = new Set(); for (const r of data.rows) { counts[r.posture as keyof typeof counts] = (counts[r.posture as keyof typeof counts] ?? 0) + 1; if (r.repo) repos.add(r.repo); } const frameworkTab = (value: Framework, label: string) => `${label}`; const evidenceRows = data.rows .map( (r) => ` ${esc(r.framework)} ${esc(r.control_id)} ${esc(r.posture)} ${esc(r.repo ?? r.subject ?? "—")} ${esc(r.resource)} ${esc(r.status)} `, ) .join(""); const exportRows = data.exports .map((e) => { const done = e.status === "done"; const cell = done ? `Download ${esc(e.format.toUpperCase())}` : `${esc(e.status)}…`; return ` ${esc(e.created_at)} ${esc(e.framework)} ${esc(e.format.toUpperCase())} ${cell} `; }) .join(""); return ` gh-attest — Compliance Evidence

gh-attest — Compliance Evidence

${esc(data.login)} · ${installationSwitcher(data.installations, data.installationId, "dashboard") || esc(data.orgLogin)} Access reviewLog out
${counts.negative}
Gaps
${counts.positive}
Satisfied
${counts.informational}
Informational
${repos.size}
Repositories
${frameworkTab("all", "All")} ${frameworkTab("soc2", "SOC 2")} ${frameworkTab("iso27001", "ISO 27001")}

${ data.lastPolledAt ? `Last synced ${esc(data.lastPolledAt)}` : "Not yet synced — click Re-sync now." }

${evidenceRows || ``}
FrameworkControlPostureRepo / SubjectResourceStatus
No evidence yet.

Recent exports

${exportRows || ``}
CreatedFrameworkFormatFile
No exports yet.
`; } export interface AccessReviewData { login: string; installationId: number; orgLogin: string; installations: InstallationOption[]; since: string; diff: import("./access-review").AccessDiff; } const CHANGE_CLASS: Record = { added: "negative", // new access is what an access review scrutinises removed: "positive", changed: "informational", }; export function renderAccessReview(data: AccessReviewData): string { const { diff } = data; const rows = diff.entries .map( (e) => ` ${esc(e.change)} ${esc(e.resource === "org_member" ? "org member" : "team member")} ${esc(e.subject)} ${esc(e.from ?? "—")} ${esc(e.to ?? "—")} `, ) .join(""); let banner: string; if (!diff.currentAt) { banner = `

No access data collected yet. Access review requires the App to be installed on an organization (personal accounts have no membership to review), and at least one sync to have run.

`; } else if (!diff.priorAt) { banner = `

Baseline captured ${esc(diff.currentAt)} (${diff.currentCount} access entries). No earlier snapshot before ${esc(data.since)} to compare against yet — the next sync after that date will produce a diff.

`; } else { banner = `

Comparing ${esc(diff.priorAt)} → ${esc(diff.currentAt)} · ${diff.currentCount} current access entries · ${diff.entries.length} change(s).

`; } return ` gh-attest — Access Review

gh-attest — Access Review

${esc(data.login)} · ${installationSwitcher(data.installations, data.installationId, "access-review") || esc(data.orgLogin)} DashboardLog out
${banner} ${rows || ``}
ChangeTypeSubjectWasNow
No membership changes in this window.
`; }