downloads.ts

2.6 KB · raw

1import type { Hono } from "hono";2import { html } from "../pages/html.ts";3import { plural } from "../pages/layout.ts";4import { downloadsByDay, repoDownloads, topFiles, utcDay } from "../releases/releases.ts";5import { type AdminContext, adminPage } from "./console.ts";6import { allRepos } from "./store.ts";78const DAYS_SHOWN = 30;9const TOP_FILES = 10;10const DAY_MS = 24 * 60 * 60 * 1000;1112/** §24's Downloads section: the last 30 days, each repo's totals and the top files. */13export function registerDownloads(app: Hono, ctx: AdminContext): void {14	app.get("/admin/downloads", (c) => {15		// An unknown repo narrows nothing, as the issues list treats one.16		const repos = allRepos(ctx.kepr).map((repo) => repo.name);17		const repo = repos.find((name) => name === c.req.query("repo")) ?? "";18		const now = Date.now();19		const days = Array.from({ length: DAYS_SHOWN }, (_, i) =>20			utcDay(now - (DAYS_SHOWN - 1 - i) * DAY_MS),21		);22		const since = days[0] as string;23		const counts = downloadsByDay(ctx.kepr, since, repo);24		const peak = Math.max(1, ...counts.values());25		const option = (name: string) =>26			html`<option value="${name}"${name === repo ? html` selected` : ""}>${name}</option>`;27		const body = html`<form class="issues-filter" method="get" action="/admin/downloads">28<select class="issues-input" name="repo" aria-label="repository">29<option value="">all repos</option>30${repos.map(option)}31</select>32<button class="issues-submit" type="submit">show</button>33</form>34<section class="repo-section"><h2 class="section-label mono">last ${String(DAYS_SHOWN)} days</h2>35<ol class="admin-days mono">36${days.map((day) => {37	const n = counts.get(day) ?? 0;38	return html`<li><span>${day}</span>39<meter min="0" max="${String(peak)}" value="${String(n)}" aria-label="${plural(n, "download")} on ${day}"></meter>40<span>${String(n)}</span></li>`;41})}42</ol></section>43<section class="repo-section"><h2 class="section-label mono">repositories</h2>44${repoDownloads(ctx.kepr, since).map(45	(row) => html`<p class="issue-row"><a href="/admin/downloads?repo=${row.repo}">${row.repo}</a>46<span class="issue-meta mono">${plural(row.total, "download")} · ${String(row.recent)} in ${String(DAYS_SHOWN)} days</span></p>`,47)}48</section>49<section class="repo-section"><h2 class="section-label mono">most downloaded files</h2>50${topFiles(ctx.kepr, TOP_FILES).map(51	(52		row,53	) => html`<p class="issue-row"><span class="mono">${row.repo} ${row.version} ${row.platform}</span>54<span class="issue-meta mono">${plural(row.count, "download")}</span></p>`,55)}56</section>`;57		return c.html(adminPage(c, ctx, "downloads", body));58	});59}