Files
awesome-copilot/skills/pr-dashboard/assets/dashboard.html
James 4beca2f03b Add pr-dashboard skill and plugin (#1444)
* Add pr-dashboard skill and plugin

Adds a self-contained PR dashboard skill that generates and opens a
rich HTML dashboard in the browser showing GitHub pull requests for a
given date range and role filter.

- Skill: skills/pr-dashboard/ — bundles pr-dashboard-cli.mjs,
  dashboard.html, and lib/utils.mjs
- Plugin: plugins/pr-dashboard/ — makes it installable via
  `copilot skill install pr-dashboard@awesome-copilot`

Requires GitHub CLI (gh) installed and authenticated.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Restore README.instructions.md to upstream sort order

macOS locale sorts Japanese/Korean C# entries differently than Linux CI.
Restore to the upstream/staged version since we don't add any instructions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address PR review comments

- Fix regex character class bug: [-to]+ → (?:-|to) alternation in utils.mjs
- Fix 'last week' to return previous calendar week (Mon–Sun) not last 7 days
- Remove unused formatHumanDate and buildMarkdown exports from utils.mjs
- Fix ghApi error handling: rethrow with helpful message instead of silently
  returning parsed JSON on failure (prevents silent auth errors)
- Add pagination to searchIssues (up to 1000 results across pages)
- Add rel="noopener noreferrer" to target=_blank links in generated rows
- HTML-escape fallback template content in renderHtml to prevent injection
- Move escapeHtml to module level so it's available before renderHtml body
- Neutralise dashboard.html template: placeholder title/h1/meta/stats/tbody
- Empty __md and filename in template (CLI populates at runtime)
- Add aria-label to search input and status/review selects

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* remove newline

* Regenerate docs/README.instructions.md

* refactor(pr-dashboard): move scripts/assets per skills spec, remove plugin

- Move pr-dashboard-cli.mjs and lib/utils.mjs into scripts/ per skills spec
- Move dashboard.html into assets/ per skills spec
- Update CLI template path and SKILL.md script path reference
- Remove plugins/pr-dashboard (redundant now that gh skills install works)
- Clean up marketplace.json and docs/README.plugins.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-30 09:59:44 +10:00

132 lines
7.0 KiB
HTML

<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>PR Dashboard</title>
<style>
:root {
--bg:#ffffff; --bg2:#f6f8fa; --bg3:#eaeef2; --border:#d0d7de;
--text:#1f2328; --muted:#636c76; --link:#0969da;
--hover:#f3f4f6; --thead:#f6f8fa;
}
[data-theme="dark"] {
--bg:#0d1117; --bg2:#161b22; --bg3:#21262d; --border:#30363d;
--text:#e6edf3; --muted:#8b949e; --link:#58a6ff;
--hover:#1c2128; --thead:#21262d;
}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:1rem;background:var(--bg);color:var(--text);padding:24px 32px;transition:background .2s,color .2s}
h1{font-size:2rem;font-weight:800;margin-bottom:8px;letter-spacing:-.5px}
.meta{color:var(--muted);font-size:.875rem;margin-bottom:32px}
.stats{display:flex;gap:16px;margin-bottom:32px;flex-wrap:wrap}
.stat{background:var(--bg2);border:1px solid var(--border);border-radius:12px;padding:20px 28px;text-align:center;transition:all .2s;cursor:default}
.stat:hover{border-color:var(--link);transform:translateY(-2px);box-shadow:0 4px 12px rgba(0,0,0,.08)}
.stat .n{font-size:2.5rem;font-weight:800;line-height:1}.stat .l{font-size:.85rem;color:var(--muted);margin-top:6px;font-weight:500}
.stat.open .n{color:#1a7f37}.stat.merged .n{color:#8250df}.stat.closed .n{color:#cf222e}.stat.draft .n{color:#636c76}
[data-theme="dark"] .stat.open .n{color:#2ea043}[data-theme="dark"] .stat.merged .n{color:#8957e5}
[data-theme="dark"] .stat.closed .n{color:#da3633}[data-theme="dark"] .stat.draft .n{color:#848d97}
.toolbar{display:flex;gap:12px;margin-bottom:20px;align-items:center;flex-wrap:wrap;padding:12px;background:var(--bg2);border-radius:8px}
.toolbar input,.toolbar select{background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:8px 12px;color:var(--text);font-size:.95rem;outline:none;transition:border .2s}
.toolbar input{flex:1;min-width:200px}.toolbar input:focus,.toolbar select:focus{border-color:var(--link);box-shadow:0 0 0 3px rgba(9,105,218,.1)}
.visible-count{margin-left:auto;font-size:.875rem;color:var(--muted);font-weight:500}
.theme-toggle{background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:8px 14px;color:var(--text);font-size:.875rem;cursor:pointer;white-space:nowrap;transition:all .2s;font-weight:500}
.theme-toggle:hover{background:var(--bg3);border-color:var(--link)}
table{width:100%;border-collapse:collapse;background:var(--bg);border:1px solid var(--border);border-radius:10px;overflow:hidden;font-size:.95rem;box-shadow:0 1px 3px rgba(0,0,0,.05)}
thead{background:var(--thead)}
th{padding:14px 16px;text-align:left;font-size:.8rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px}
td{padding:16px;border-top:1px solid var(--border);vertical-align:top}
tr:last-child td{border-bottom:none}
tr:hover td{background:var(--hover)}
a{color:var(--link);text-decoration:none;font-weight:500}a:hover{text-decoration:underline}
.title-line{font-weight:600;font-size:1rem;margin-bottom:6px}
.badge{display:inline-block;padding:4px 10px;border-radius:14px;font-size:.75rem;font-weight:700;color:#fff;letter-spacing:.3px;text-transform:uppercase}
.empty{text-align:center;padding:60px 40px;color:var(--muted);font-size:1.1rem}
.controls{display:inline-flex;gap:6px;margin-left:8px}
.mini-btn{background:var(--bg2);border:1px solid var(--border);padding:4px 8px;border-radius:6px;font-size:.75rem;cursor:pointer;transition:all .2s}
.mini-btn:hover{background:var(--bg3);border-color:var(--link)}
</style>
</head>
<body>
<h1>🔀 PR Dashboard</h1>
<div class="meta">PR Dashboard</div>
<div class="stats">
<div class="stat open"><div class="n">0</div><div class="l">Open</div></div>
<div class="stat merged"><div class="n">0</div><div class="l">Merged</div></div>
<div class="stat closed"><div class="n">0</div><div class="l">Closed</div></div>
<div class="stat draft"><div class="n">0</div><div class="l">Draft</div></div>
</div>
<div class="toolbar">
<input type="search" id="q" placeholder="Search title or repo…" aria-label="Search pull requests" oninput="filter()">
<select id="sf" aria-label="Filter by status" onchange="filter()">
<option value="">All statuses</option>
<option>OPEN</option><option>MERGED</option><option>CLOSED</option><option>DRAFT</option>
</select>
<select id="rf" aria-label="Filter by review status" onchange="filter()">
<option value="">All reviews</option>
<option>APPROVED</option><option>CHANGES_REQUESTED</option><option>REVIEW_REQUIRED</option>
</select>
<span class="visible-count" id="vc">0 PRs</span>
<button id="exportMdBtn" class="theme-toggle" onclick="downloadMarkdown()">📄 Export MD</button>
<button class="theme-toggle" onclick="toggleTheme()" id="themeBtn">🌙 Dark</button>
</div>
<table>
<thead><tr><th>Repository</th><th>Title</th><th>Status</th><th>Review</th><th>CI</th><th>Created</th><th>Updated</th></tr></thead>
<tbody id="tb">
<tr>
<td colspan="7" style="text-align:center;color:var(--muted);">No pull request data loaded.</td>
</tr>
</tbody>
</table>
<script>const __md = "";
function filter(){
const q=document.getElementById("q").value.toLowerCase();
const sf=document.getElementById("sf").value;
const rf=document.getElementById("rf").value;
let n=0;
document.querySelectorAll("#tb tr").forEach(r=>{
const t=r.textContent.toLowerCase();
const badges=[...r.querySelectorAll(".badge")].map(b=>b.textContent);
const show=(!q||t.includes(q))&&(!sf||badges[0]===sf)&&(!rf||badges[1]===rf);
r.style.display=show?"":"none";
if(show)n++;
});
document.getElementById("vc").textContent=n+" PR"+(n!==1?"s":"");
}
function toggleTheme(){
const html=document.documentElement;
const isDark=html.getAttribute("data-theme")==="dark";
html.setAttribute("data-theme",isDark?"light":"dark");
document.getElementById("themeBtn").textContent=isDark?"🌙 Dark":"☀️ Light";
localStorage.setItem("pr-dashboard-theme",isDark?"light":"dark");
}
function downloadMarkdown(){
try{
const md = __md || '';
const blob = new Blob([md], { type: 'text/markdown;charset=utf-8' });
const filename = 'pr-dashboard.md';
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url; a.download = filename; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url);
} catch (e) { console.error(e); }
}
// Restore saved theme preference
const saved=localStorage.getItem("pr-dashboard-theme");
if(saved==="dark"){
document.documentElement.setAttribute("data-theme","dark");
document.getElementById("themeBtn").textContent="☀️ Light";
}
</script>
</body>
</html>