mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-10-19 22:27:20 +02:00
fix: bring the admin pages in line with the rest of the app
- Overview: error-volume bars render (the fills were inline spans), the cumulative users chart is scaled to its range and labels it, latencies over a second read in seconds, and charts and daily numbers use the accent instead of four unrelated colours. - Labels, table headers and state chips drop the mono caps; queue stat values drop the serif. - Error rows keep their content aligned; row metadata wraps instead of truncating every item; "1 term" is singular. - Errors filter uses the shared segmented control; breadcrumbs include Admin; user detail spaces its status and actions, uses the accent for Generate and says when no access token is stored. - The jobs table scrolls inside its card on phones. - Mock server: admin fixtures for every admin endpoint (ADMIN=1). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
cfd7b73de4
commit
80944954d6
11 files changed
+556
-61
No files matched your search
@@ -128,7 +128,7 @@
|
||||
</div>
|
||||
<div class="col-sev">
|
||||
<span class="sev-dot" :class="'sev-' + row?._bucket"></span>
|
||||
<span class="sev-label">{{ fmt?.uppercase(row?._bucket) }}</span>
|
||||
<span class="sev-label">{{ fmt?.title(row?._bucket) }}</span>
|
||||
</div>
|
||||
<div class="col-mod"><span class="pill pill-module">{{ row?.module }}</span></div>
|
||||
<div class="col-msg">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="container paper-page admin-page overview-page">
|
||||
<div class="paper-crumbs">Admin · System Health</div>
|
||||
<div class="paper-crumbs">Admin / <span class="here">Overview</span></div>
|
||||
|
||||
<div class="ov-header">
|
||||
<h1 class="paper-page-title">Overview</h1>
|
||||
@@ -105,11 +105,11 @@
|
||||
<div class="ov-chart-card">
|
||||
<div class="ov-chart-head">
|
||||
<span class="ov-chart-title">Users · 30d</span>
|
||||
<span class="ov-chart-legend"><span class="ov-dot-legend ov-dot-user-fill"></span>total users</span>
|
||||
<span class="ov-chart-legend"><span class="ov-dot-legend ov-dot-user-fill"></span>total users · {{ fmt?.number(historyRange('nbUsers').min) }}–{{ fmt?.number(historyRange('nbUsers').max) }}</span>
|
||||
</div>
|
||||
<div class="ov-spark-bars" v-if="data?.history?.length">
|
||||
<div class="ov-spark-col has-tip" v-for="(d, index) in data?.history" :key="index" :data-tip="(historyLabel(d)) + ": " + (fmt?.number(d?.nbUsers)) + " users"" :aria-label="(historyLabel(d)) + ": " + (fmt?.number(d?.nbUsers)) + " users"">
|
||||
<span class="ov-spark-fill ov-spark-fill-user" :style="{height: historyBarH(d, 'nbUsers') + 'px'}"></span>
|
||||
<span class="ov-spark-fill ov-spark-fill-user" :style="{height: historyRangeH(d, 'nbUsers') + 'px'}"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ov-spark-x" v-if="data?.history?.length">
|
||||
@@ -308,7 +308,7 @@
|
||||
<thead><tr><th scope="col">Route</th><th scope="col" class="num">Requests</th><th scope="col" class="num">Average</th><th scope="col" class="num">First byte p95</th><th scope="col" class="num">p95</th><th scope="col" class="num">p99</th><th scope="col" class="num">Interrupted</th><th scope="col" class="num">5xx</th><th scope="col" class="num">Slow</th></tr></thead>
|
||||
<tbody><tr v-for="row in performance.routes" :key="row.service + row.method + row.route">
|
||||
<th scope="row" class="ov-metric-name"><span>{{ row.route }}</span><small>{{ row.service }} · {{ row.method }}</small></th>
|
||||
<td class="num">{{ fmt.number(row.count) }}</td><td class="num">{{ row.avgMs }} ms</td>
|
||||
<td class="num">{{ fmt.number(row.count) }}</td><td class="num">{{ formatMs(row.avgMs) }}</td>
|
||||
<td class="num">{{ firstByteBound(row) }}</td><td class="num">{{ latencyBound(row, 'p95UpperMs') }}</td><td class="num">{{ latencyBound(row, 'p99UpperMs') }}</td>
|
||||
<td class="num" :class="{'ov-metric-warn': row.aborted > 0}">{{ fmt.number(row.aborted) }}</td><td class="num" :class="{'ov-metric-warn': row.errors > 0}">{{ fmt.number(row.errors) }}</td><td class="num" :class="{'ov-metric-warn': row.slow > 0}">{{ fmt.number(row.slow) }}</td>
|
||||
</tr></tbody>
|
||||
@@ -340,7 +340,7 @@
|
||||
<caption class="sr-only">Work stages and cache results</caption>
|
||||
<thead><tr><th scope="col">Work stage</th><th scope="col" class="num">Samples</th><th scope="col" class="num">Average</th><th scope="col" class="num">p95</th></tr></thead>
|
||||
<tbody><tr v-for="row in performance.stages.filter(row => row.metric !== 'first_byte')" :key="row.service + row.metric">
|
||||
<th scope="row" class="ov-metric-name"><span>{{ row.metric }}</span><small>{{ row.service }}</small></th><td class="num">{{ fmt.number(row.count) }}</td><td class="num">{{ row.avgMs }} ms</td><td class="num">{{ latencyBound(row, 'p95UpperMs') }}</td>
|
||||
<th scope="row" class="ov-metric-name"><span>{{ row.metric }}</span><small>{{ row.service }}</small></th><td class="num">{{ fmt.number(row.count) }}</td><td class="num">{{ formatMs(row.avgMs) }}</td><td class="num">{{ latencyBound(row, 'p95UpperMs') }}</td>
|
||||
</tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<!-- Detail: throughput chart + stats panel -->
|
||||
<div class="q-detail" v-if="selectedStats">
|
||||
<div class="q-throughput">
|
||||
<div class="q-section-label">{{ selectedQueue }}·throughput <span class="q-section-right"><span class="q-legend-completed">●</span> completed <span class="q-legend-failed">●</span> failed <span class="q-legend-exec">- -</span> avg time · {{ fmt?.uppercase(range) }}</span></div>
|
||||
<div class="q-section-label">{{ selectedQueue }} · throughput <span class="q-section-right"><span class="q-legend-completed">●</span> completed <span class="q-legend-failed">●</span> failed <span class="q-legend-exec">- -</span> avg time · {{ range }}</span></div>
|
||||
<div class="q-chart-wrap">
|
||||
<canvas id="q-throughput-chart" height="180"></canvas>
|
||||
<div id="q-chart-tooltip" class="q-chart-tooltip" style="display:none;"></div>
|
||||
@@ -52,30 +52,30 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="q-stats-panel">
|
||||
<div class="q-section-label">{{ selectedQueue }}·stats</div>
|
||||
<div class="q-section-label">{{ selectedQueue }} · stats</div>
|
||||
<div class="q-stats-grid">
|
||||
<div class="q-stat">
|
||||
<div class="q-stat-label">WAITING</div>
|
||||
<div class="q-stat-label">Waiting</div>
|
||||
<div class="q-stat-value" v-text="selectedStats?.counts?.waiting || 0"></div>
|
||||
</div>
|
||||
<div class="q-stat">
|
||||
<div class="q-stat-label">ACTIVE</div>
|
||||
<div class="q-stat-label">Active</div>
|
||||
<div class="q-stat-value" v-text="selectedStats?.counts?.active || 0"></div>
|
||||
</div>
|
||||
<div class="q-stat">
|
||||
<div class="q-stat-label">COMPLETED (24H)</div>
|
||||
<div class="q-stat-label">Completed (24h)</div>
|
||||
<div class="q-stat-value" v-text="fmt?.number(selectedStats?.completed24h)"></div>
|
||||
</div>
|
||||
<div class="q-stat">
|
||||
<div class="q-stat-label">FAILED (24H)</div>
|
||||
<div class="q-stat-label">Failed (24h)</div>
|
||||
<div class="q-stat-value" v-text="selectedStats?.failed24h || 0"></div>
|
||||
</div>
|
||||
<div class="q-stat">
|
||||
<div class="q-stat-label">DELAYED</div>
|
||||
<div class="q-stat-label">Delayed</div>
|
||||
<div class="q-stat-value" v-text="selectedStats?.counts?.delayed || 0"></div>
|
||||
</div>
|
||||
<div class="q-stat">
|
||||
<div class="q-stat-label">WORKERS</div>
|
||||
<div class="q-stat-label">Workers</div>
|
||||
<div class="q-stat-value" v-text="selectedStats?.workers || 0"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -115,12 +115,12 @@
|
||||
<table class="q-table" v-if="filteredJobs().length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>STATE</th>
|
||||
<th>JOB ID</th>
|
||||
<th>PAYLOAD</th>
|
||||
<th>ATTEMPTS</th>
|
||||
<th>DURATION</th>
|
||||
<th>PROGRESS</th>
|
||||
<th>State</th>
|
||||
<th>Job id</th>
|
||||
<th>Payload</th>
|
||||
<th>Attempts</th>
|
||||
<th>Duration</th>
|
||||
<th>Progress</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -156,52 +156,52 @@
|
||||
<div class="q-job-detail">
|
||||
<div class="q-job-detail-grid">
|
||||
<div class="q-job-detail-item">
|
||||
<span class="q-job-detail-label">JOB ID</span>
|
||||
<span class="q-job-detail-label">Job id</span>
|
||||
<span class="q-job-detail-value"><a target="_blank" v-text="job?.id" :href="safeUrl("/r/" + (job?.id))"></a></span>
|
||||
</div>
|
||||
<div class="q-job-detail-item">
|
||||
<span class="q-job-detail-label">STATE</span>
|
||||
<span class="q-job-detail-label">State</span>
|
||||
<span class="q-job-detail-value"><span v-text="job?._state" :class=""q-state-badge q-state-" + (job?._state)"></span></span>
|
||||
</div>
|
||||
<div class="q-job-detail-item" v-if="job?.data?.repoId">
|
||||
<span class="q-job-detail-label">REPO ID</span>
|
||||
<span class="q-job-detail-label">Repo ID</span>
|
||||
<span class="q-job-detail-value" v-text="job?.data?.repoId"></span>
|
||||
</div>
|
||||
<div class="q-job-detail-item" v-if="job?.timestamp">
|
||||
<span class="q-job-detail-label">CREATED</span>
|
||||
<span class="q-job-detail-label">Created</span>
|
||||
<span class="q-job-detail-value" v-text="humanTime(job?.timestamp)"></span>
|
||||
</div>
|
||||
<div class="q-job-detail-item" v-if="job?._state == 'delayed' && job?.delayUntil">
|
||||
<span class="q-job-detail-label">RETRY AT</span>
|
||||
<span class="q-job-detail-label">Retry at</span>
|
||||
<span class="q-job-detail-value">{{ humanTime(job?.delayUntil) }} ({{ delayCountdown(job?.delayUntil) }})</span>
|
||||
</div>
|
||||
<div class="q-job-detail-item" v-if="job?.processedOn">
|
||||
<span class="q-job-detail-label">PROCESSED</span>
|
||||
<span class="q-job-detail-label">Processed</span>
|
||||
<span class="q-job-detail-value" v-text="humanTime(job?.processedOn)"></span>
|
||||
</div>
|
||||
<div class="q-job-detail-item" v-if="job?.finishedOn">
|
||||
<span class="q-job-detail-label">FINISHED</span>
|
||||
<span class="q-job-detail-label">Finished</span>
|
||||
<span class="q-job-detail-value" v-text="humanTime(job?.finishedOn)"></span>
|
||||
</div>
|
||||
<div class="q-job-detail-item" v-if="job?.attemptsMade">
|
||||
<span class="q-job-detail-label">ATTEMPTS</span>
|
||||
<span class="q-job-detail-label">Attempts</span>
|
||||
<span class="q-job-detail-value" v-text="job?.attemptsMade"></span>
|
||||
</div>
|
||||
<div class="q-job-detail-item" v-if="job?.progress && job?.progress?.status">
|
||||
<span class="q-job-detail-label">STATUS</span>
|
||||
<span class="q-job-detail-label">Status</span>
|
||||
<span class="q-job-detail-value" v-text="job?.progress?.status"></span>
|
||||
</div>
|
||||
<div class="q-job-detail-item" v-if="jobProgressPct(job) !== null">
|
||||
<span class="q-job-detail-label">PROGRESS</span>
|
||||
<span class="q-job-detail-label">Progress</span>
|
||||
<span class="q-job-detail-value" v-text="jobProgressPct(job) + '%'"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="q-job-detail-error" v-if="job?.failedReason">
|
||||
<span class="q-job-detail-label">ERROR</span>
|
||||
<span class="q-job-detail-label">Error</span>
|
||||
<div class="q-error-reason" v-text="job?.failedReason"></div>
|
||||
</div>
|
||||
<div v-if="job?.stacktrace?.length">
|
||||
<span class="q-job-detail-label">STACKTRACE</span>
|
||||
<span class="q-job-detail-label">Stacktrace</span>
|
||||
<pre class="q-error-stack" v-for="(stack, index) in job?.stacktrace" :key="index"><code v-text="stack"></code></pre>
|
||||
</div>
|
||||
<div class="q-job-detail-actions">
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
<div class="anon-text">
|
||||
<a class="repo-name" target="_blank" v-text="repo?.repoId" :href="safeUrl("/r/" + (repo?.repoId))"></a>
|
||||
<div class="anon-sub">
|
||||
<a v-text="repo?.source?.repositoryName" :href="safeUrl("https://github.com/" + (repo?.source?.repositoryName) + "/")"></a><span v-if="repo?.options?.update"> · <a v-text="repo?.source?.branch" :href="safeUrl("https://github.com/" + (repo?.source?.fullName) + "/tree/" + (repo?.source?.branch))"></a></span><span v-if="!repo?.options?.update"> · @<a v-text="repo?.source?.commit?.substring(0, 8)" :href="safeUrl("https://github.com/" + (repo?.source?.fullName) + "/tree/" + (repo?.source?.commit))"></a></span><span v-if="repo?.conference"> · <svg class="ui-icon" aria-hidden="true"><use href="#project-icon-conference"></use></svg> {{ repo?.conference }}</span><span> · {{ fmt?.humanFileSize(repo?.size?.storage) }}</span><span> · {{ fmt?.number(repo?.options?.terms?.length) }} terms</span>
|
||||
<a v-text="repo?.source?.repositoryName" :href="safeUrl("https://github.com/" + (repo?.source?.repositoryName) + "/")"></a><span v-if="repo?.options?.update"> · <a v-text="repo?.source?.branch" :href="safeUrl("https://github.com/" + (repo?.source?.fullName) + "/tree/" + (repo?.source?.branch))"></a></span><span v-if="!repo?.options?.update"> · @<a v-text="repo?.source?.commit?.substring(0, 8)" :href="safeUrl("https://github.com/" + (repo?.source?.fullName) + "/tree/" + (repo?.source?.commit))"></a></span><span v-if="repo?.conference"> · <svg class="ui-icon" aria-hidden="true"><use href="#project-icon-conference"></use></svg> {{ repo?.conference }}</span><span> · {{ fmt?.humanFileSize(repo?.size?.storage) }}</span><span> · {{ fmt?.number(repo?.options?.terms?.length) }} {{ repo?.options?.terms?.length === 1 ? 'term' : 'terms' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="container paper-page admin-page">
|
||||
<div class="paper-crumbs"><a href="/admin/users">Users</a> / <span class="here">{{ userInfo?.username || 'Profile' }}</span></div>
|
||||
<div class="paper-crumbs"><a href="/admin/">Admin</a> / <a href="/admin/users">Users</a> / <span class="here">{{ userInfo?.username || 'Profile' }}</span></div>
|
||||
<h1 class="paper-page-title">{{ userInfo?.username || 'User' }}</h1>
|
||||
|
||||
<nav class="admin-nav">
|
||||
@@ -40,7 +40,8 @@
|
||||
<div class="detail-value">{{ userInfo?.emails?.[0]?.email }}</div>
|
||||
|
||||
<div class="detail-label">Access token</div>
|
||||
<div class="detail-value" style="font-family: var(--font-mono); font-size: 0.85rem;">{{ userInfo?.accessTokens?.github }}</div>
|
||||
<div class="detail-value" style="font-family: var(--font-mono); font-size: 0.85rem;" v-if="userInfo?.accessTokens?.github">{{ userInfo?.accessTokens?.github }}</div>
|
||||
<div class="detail-value text-muted" v-if="!userInfo?.accessTokens?.github">None stored</div>
|
||||
|
||||
<div class="detail-label">GitHub</div>
|
||||
<div class="detail-value">
|
||||
@@ -98,7 +99,7 @@
|
||||
|
||||
<form class="d-flex" style="gap: 8px; margin-bottom: 12px;" v-form="viewState" @submit.prevent="submitForm($event, () => { createToken() })">
|
||||
<input type="text" class="form-control" placeholder="Token name (e.g. dev-laptop)" required v-field="{ state: viewState, set: value => { tokenForm.name = value }, value: tokenForm?.name, form: null, options: {} }">
|
||||
<button type="submit" class="btn btn-primary"><svg class="ui-icon" aria-hidden="true"><use href="#project-icon-plus"></use></svg> Generate</button>
|
||||
<button type="submit" class="btn btn-ink"><svg class="ui-icon" aria-hidden="true"><use href="#project-icon-plus"></use></svg> Generate</button>
|
||||
</form>
|
||||
|
||||
<div class="alert alert-warning" role="alert" v-if="tokenForm?.plaintext">
|
||||
@@ -180,7 +181,7 @@
|
||||
<div class="anon-text">
|
||||
<a class="repo-name" target="_blank" v-text="repo?.repoId" :href="safeUrl("/r/" + (repo?.repoId))"></a>
|
||||
<div class="anon-sub">
|
||||
<a v-text="repo?.source?.fullName" :href="safeUrl("https://github.com/" + (repo?.source?.fullName) + "/")"></a><span v-if="repo?.options?.update"> · <a v-text="repo?.source?.branch" :href="safeUrl("https://github.com/" + (repo?.source?.fullName) + "/tree/" + (repo?.source?.branch))"></a></span><span v-if="!repo?.options?.update"> · @<a v-text="repo?.source?.commit?.substring(0, 8)" :href="safeUrl("https://github.com/" + (repo?.source?.fullName) + "/tree/" + (repo?.source?.commit))"></a></span><span v-if="repo?.conference"> · <svg class="ui-icon" aria-hidden="true"><use href="#project-icon-conference"></use></svg> {{ repo?.conference }}</span><span> · {{ fmt?.humanFileSize(repo?.size?.storage) }}</span><span> · {{ fmt?.number(repo?.options?.terms?.length) }} terms</span>
|
||||
<a v-text="repo?.source?.fullName" :href="safeUrl("https://github.com/" + (repo?.source?.fullName) + "/")"></a><span v-if="repo?.options?.update"> · <a v-text="repo?.source?.branch" :href="safeUrl("https://github.com/" + (repo?.source?.fullName) + "/tree/" + (repo?.source?.branch))"></a></span><span v-if="!repo?.options?.update"> · @<a v-text="repo?.source?.commit?.substring(0, 8)" :href="safeUrl("https://github.com/" + (repo?.source?.fullName) + "/tree/" + (repo?.source?.commit))"></a></span><span v-if="repo?.conference"> · <svg class="ui-icon" aria-hidden="true"><use href="#project-icon-conference"></use></svg> {{ repo?.conference }}</span><span> · {{ fmt?.humanFileSize(repo?.size?.storage) }}</span><span> · {{ fmt?.number(repo?.options?.terms?.length) }} {{ repo?.options?.terms?.length === 1 ? 'term' : 'terms' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user