Skip to content

Commit 3183fe7

Browse files
committed
feat: add repositories list page
- New docs/repositories.html + repositories.js: paginated table of tracked repos with mode badge, attestation count, and expiry info - Make Repositories stat card on dashboard a clickable link - Add badge-danger / badge-warn CSS classes for mode indicators - Update CI sed commands to stamp __GIT_SHA__ and __GITHUB_REPO__ into the new files
1 parent 62e8d01 commit 3183fe7

5 files changed

Lines changed: 253 additions & 4 deletions

File tree

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,8 @@ jobs:
162162

163163
- name: Stamp git SHA and repo into dashboard
164164
run: |
165-
sed -i "s/__GIT_SHA__/${GITHUB_SHA::8}/g" docs/index.html docs/my-attestations.html
166-
sed -i "s|__GITHUB_REPO__|${GITHUB_REPOSITORY}|g" docs/index.html docs/my-attestations.html docs/app.js docs/my-attestations.js
165+
sed -i "s/__GIT_SHA__/${GITHUB_SHA::8}/g" docs/index.html docs/my-attestations.html docs/repositories.html
166+
sed -i "s|__GITHUB_REPO__|${GITHUB_REPOSITORY}|g" docs/index.html docs/my-attestations.html docs/repositories.html docs/app.js docs/my-attestations.js docs/repositories.js
167167
168168
- name: Deploy dashboard
169169
run: npx wrangler pages deploy docs --project-name action-gate-dashboard --commit-dirty=true

docs/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@
5555
<main class="container">
5656
<!-- ── Summary cards ──────────────────────────────────────── -->
5757
<section class="stats-grid" id="stats-grid" aria-label="Summary statistics">
58-
<div class="stat-card" id="stat-repos">
58+
<a class="stat-card" id="stat-repos" href="repositories.html">
5959
<div class="stat-value"></div>
6060
<div class="stat-label">Repositories</div>
61-
</div>
61+
</a>
6262
<div class="stat-card" id="stat-active">
6363
<div class="stat-value"></div>
6464
<div class="stat-label">Active Attestations</div>

docs/repositories.html

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<!--
2+
SPDX-FileCopyrightText: 2026 The Linux Foundation
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
-->
6+
7+
<!DOCTYPE html>
8+
<html lang="en">
9+
<head>
10+
<meta charset="UTF-8" />
11+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
12+
<title>Repositories — Action Gate</title>
13+
<link rel="stylesheet" href="styles.css" />
14+
<script>
15+
window.ACTION_GATE_API_URL = "https://github-action-gate.pytorch-foundation.workers.dev";
16+
</script>
17+
</head>
18+
<body>
19+
<!-- ── Header ─────────────────────────────────────────────── -->
20+
<header class="site-header">
21+
<div class="container header-inner">
22+
<a href="index.html" class="logo">
23+
<span class="logo-icon">🔒</span>
24+
<span class="logo-text">Action Gate</span>
25+
</a>
26+
27+
<div class="header-actions">
28+
<div id="user-info" class="user-info" hidden>
29+
<img id="user-avatar" class="user-avatar" src="" alt="" />
30+
<span id="user-login" class="user-login"></span>
31+
<button id="btn-logout" class="btn btn-secondary btn-sm">Logout</button>
32+
</div>
33+
</div>
34+
</div>
35+
</header>
36+
37+
<main class="container">
38+
<!-- ── Page header ──────────────────────────────────────── -->
39+
<section class="page-header">
40+
<div class="page-header-inner">
41+
<div>
42+
<h1 class="page-title">Repositories</h1>
43+
<p class="page-subtitle">All repositories tracked by Action Gate.</p>
44+
</div>
45+
</div>
46+
</section>
47+
48+
<!-- ── Table ────────────────────────────────────────────── -->
49+
<section class="table-section">
50+
<div id="loading" class="loading-state" aria-live="polite">Loading…</div>
51+
<div id="error-msg" class="error-state" hidden></div>
52+
53+
<div class="table-wrapper" id="table-wrapper" hidden>
54+
<table>
55+
<thead>
56+
<tr>
57+
<th>Repository</th>
58+
<th>Mode</th>
59+
<th>Active Attestations</th>
60+
<th>Expiry (days)</th>
61+
<th>Added</th>
62+
</tr>
63+
</thead>
64+
<tbody id="repos-body"></tbody>
65+
</table>
66+
</div>
67+
68+
<!-- Pagination -->
69+
<div class="pagination" id="pagination" hidden>
70+
<button id="btn-prev" class="btn btn-secondary" disabled>← Previous</button>
71+
<span id="page-info"></span>
72+
<button id="btn-next" class="btn btn-secondary">Next →</button>
73+
</div>
74+
</section>
75+
</main>
76+
77+
<footer class="site-footer">
78+
<div class="container">
79+
<p>
80+
Action Gate
81+
<!-- __GIT_SHA__ is replaced at deploy time by CI -->
82+
<span id="deploy-sha" class="deploy-sha">__GIT_SHA__</span>
83+
&mdash;
84+
<a
85+
href="https://github.com/__GITHUB_REPO__"
86+
target="_blank"
87+
rel="noopener noreferrer"
88+
>GitHub</a
89+
>
90+
</p>
91+
</div>
92+
</footer>
93+
94+
<script src="repositories.js"></script>
95+
</body>
96+
</html>

docs/repositories.js

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// SPDX-FileCopyrightText: 2026 The Linux Foundation
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
/* global ACTION_GATE_API_URL */
6+
"use strict";
7+
8+
const API_BASE =
9+
(typeof window !== "undefined" && window.ACTION_GATE_API_URL) || window.location.origin;
10+
11+
function $(id) { return document.getElementById(id); }
12+
13+
function escapeHtml(s) {
14+
const d = document.createElement("div");
15+
d.textContent = s;
16+
return d.innerHTML;
17+
}
18+
19+
// ── State ─────────────────────────────────────────────────────────────────────
20+
21+
const state = { page: 1, perPage: 30, total: 0, totalPages: 1 };
22+
23+
// ── API helper ────────────────────────────────────────────────────────────────
24+
25+
async function apiFetch(path) {
26+
const res = await fetch(`${API_BASE}${path}`);
27+
if (!res.ok) throw new Error(`API ${res.status}`);
28+
return res.json();
29+
}
30+
31+
// ── Rendering ─────────────────────────────────────────────────────────────────
32+
33+
function renderRepos(repos) {
34+
const tbody = $("repos-body");
35+
if (!repos.length) {
36+
tbody.innerHTML = `<tr><td colspan="5" class="empty-state">No repositories found.</td></tr>`;
37+
return;
38+
}
39+
40+
tbody.innerHTML = repos
41+
.map((r) => {
42+
const fullName = `${escapeHtml(r.owner)}/${escapeHtml(r.name)}`;
43+
const ghLink = `https://github.com/${encodeURIComponent(r.owner)}/${encodeURIComponent(r.name)}`;
44+
const activeCount = r._count?.attestations ?? 0;
45+
const mode = r.mode === "BLOCK" ? "Block" : r.mode === "WARN" ? "Warn" : escapeHtml(r.mode);
46+
const modeClass = r.mode === "BLOCK" ? "badge badge-danger" : "badge badge-warn";
47+
const added = new Date(r.createdAt).toLocaleDateString();
48+
return `<tr>
49+
<td><a href="${escapeHtml(ghLink)}" target="_blank" rel="noopener">${fullName}</a></td>
50+
<td><span class="${modeClass}">${mode}</span></td>
51+
<td>${activeCount}</td>
52+
<td>${r.expiryDays ?? "—"}</td>
53+
<td>${added}</td>
54+
</tr>`;
55+
})
56+
.join("");
57+
}
58+
59+
function updatePagination() {
60+
$("page-info").textContent = `Page ${state.page} of ${state.totalPages}`;
61+
$("btn-prev").disabled = state.page <= 1;
62+
$("btn-next").disabled = state.page >= state.totalPages;
63+
$("pagination").hidden = state.totalPages <= 1;
64+
}
65+
66+
// ── Data fetching ─────────────────────────────────────────────────────────────
67+
68+
async function loadRepos() {
69+
$("loading").hidden = false;
70+
$("error-msg").hidden = true;
71+
$("table-wrapper").hidden = true;
72+
73+
try {
74+
const data = await apiFetch(
75+
`/api/v1/repositories?page=${state.page}&per_page=${state.perPage}`
76+
);
77+
state.total = data.total ?? 0;
78+
state.totalPages = Math.max(1, Math.ceil(state.total / state.perPage));
79+
80+
renderRepos(data.repositories ?? []);
81+
updatePagination();
82+
83+
$("loading").hidden = true;
84+
$("table-wrapper").hidden = false;
85+
} catch (err) {
86+
$("loading").hidden = true;
87+
$("error-msg").textContent = `Failed to load repositories: ${err.message}`;
88+
$("error-msg").hidden = false;
89+
}
90+
}
91+
92+
// ── Auth (display only — no login required for this page) ─────────────────────
93+
94+
function getToken() {
95+
return localStorage.getItem("gh_token");
96+
}
97+
98+
async function checkAuth() {
99+
const token = getToken();
100+
if (!token) return;
101+
try {
102+
const res = await fetch("https://api.github.com/user", {
103+
headers: { Authorization: `Bearer ${token}` },
104+
});
105+
if (!res.ok) return;
106+
const user = await res.json();
107+
$("user-avatar").src = user.avatar_url;
108+
$("user-avatar").alt = `@${escapeHtml(user.login)}`;
109+
$("user-login").textContent = `@${user.login}`;
110+
$("user-info").hidden = false;
111+
} catch { /* ignore */ }
112+
}
113+
114+
// ── Events ────────────────────────────────────────────────────────────────────
115+
116+
$("btn-prev").addEventListener("click", () => {
117+
if (state.page > 1) { state.page--; loadRepos(); }
118+
});
119+
$("btn-next").addEventListener("click", () => {
120+
if (state.page < state.totalPages) { state.page++; loadRepos(); }
121+
});
122+
$("btn-logout")?.addEventListener("click", () => {
123+
localStorage.removeItem("gh_token");
124+
window.location.href = "index.html";
125+
});
126+
127+
// ── Init ──────────────────────────────────────────────────────────────────────
128+
129+
checkAuth();
130+
loadRepos();
131+
132+
// ── Deploy SHA link (same pattern as main dashboard) ──────────────────────────
133+
134+
(function deployShaLink() {
135+
const el = $("deploy-sha");
136+
if (!el) return;
137+
const sha = el.textContent.trim();
138+
if (!sha || sha === "__GIT_SHA__") {
139+
el.hidden = true;
140+
return;
141+
}
142+
const link = document.createElement("a");
143+
link.href = `https://github.com/__GITHUB_REPO__/commit/${encodeURIComponent(sha)}`;
144+
link.target = "_blank";
145+
link.rel = "noopener noreferrer";
146+
link.textContent = sha;
147+
link.className = "deploy-sha";
148+
el.replaceWith(link);
149+
})();

docs/styles.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ pre code { background: none; border: none; padding: 0; }
9494
text-align: center;
9595
}
9696
.stat-card.warning { border-color: var(--color-warning); }
97+
a.stat-card { text-decoration: none; color: inherit; display: block; }
98+
a.stat-card:hover { border-color: var(--color-primary); }
9799
.stat-value { font-size: 2rem; font-weight: 700; line-height: 1.2; }
98100
.stat-label { font-size: 0.8rem; color: var(--color-muted); margin-top: 4px; }
99101

@@ -253,6 +255,8 @@ tbody tr:hover { background: color-mix(in srgb, var(--color-surface) 80%, var(--
253255
.badge-expired { background: var(--color-warning-bg); color: var(--color-warning); }
254256
.badge-expiring { background: var(--color-warning-bg); color: var(--color-warning); }
255257
.badge-revoked { background: var(--color-danger-bg); color: var(--color-danger); }
258+
.badge-danger { background: var(--color-danger-bg); color: var(--color-danger); }
259+
.badge-warn { background: var(--color-warning-bg); color: var(--color-warning); }
256260

257261
/* ─── Loading / error ───────────────────────────────────────────────────── */
258262
.loading-state, .error-state {

0 commit comments

Comments
 (0)