@prism-d1/cli 1.4.3 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/github-workflows/prism-eval-gate-kiro.yml +22 -2
- package/dist/assets/gitlab-workflows/prism-eval-gate.yml +22 -1
- package/dist/src/commands/enable/codeburn.d.ts +21 -1
- package/dist/src/commands/enable/codeburn.d.ts.map +1 -1
- package/dist/src/commands/enable/codeburn.js +199 -27
- package/dist/src/commands/enable/codeburn.js.map +1 -1
- package/dist/src/dashboard/codewatch.d.ts +3 -1
- package/dist/src/dashboard/codewatch.d.ts.map +1 -1
- package/dist/src/dashboard/codewatch.js +1 -0
- package/dist/src/dashboard/codewatch.js.map +1 -1
- package/dist/src/dashboard/page.d.ts.map +1 -1
- package/dist/src/dashboard/page.js +190 -53
- package/dist/src/dashboard/page.js.map +1 -1
- package/dist/src/dashboard/payload.d.ts +24 -4
- package/dist/src/dashboard/payload.d.ts.map +1 -1
- package/dist/src/dashboard/payload.js +41 -24
- package/dist/src/dashboard/payload.js.map +1 -1
- package/dist/src/dashboard/repos.d.ts +6 -0
- package/dist/src/dashboard/repos.d.ts.map +1 -1
- package/dist/src/dashboard/repos.js +24 -0
- package/dist/src/dashboard/repos.js.map +1 -1
- package/dist/src/dashboard/setup.d.ts +12 -3
- package/dist/src/dashboard/setup.d.ts.map +1 -1
- package/dist/src/dashboard/setup.js +15 -4
- package/dist/src/dashboard/setup.js.map +1 -1
- package/dist/src/dashboard/summary.d.ts +45 -0
- package/dist/src/dashboard/summary.d.ts.map +1 -0
- package/dist/src/dashboard/summary.js +19 -0
- package/dist/src/dashboard/summary.js.map +1 -0
- package/dist/src/dashboard/usage.d.ts +16 -0
- package/dist/src/dashboard/usage.d.ts.map +1 -1
- package/dist/src/dashboard/usage.js +31 -3
- package/dist/src/dashboard/usage.js.map +1 -1
- package/dist/src/utils/otel-sync-schedule.d.ts +16 -1
- package/dist/src/utils/otel-sync-schedule.d.ts.map +1 -1
- package/dist/src/utils/otel-sync-schedule.js +31 -3
- package/dist/src/utils/otel-sync-schedule.js.map +1 -1
- package/package.json +1 -1
|
@@ -8,15 +8,16 @@
|
|
|
8
8
|
*/
|
|
9
9
|
const STYLE = `
|
|
10
10
|
:root { color-scheme: light dark; --bg:#f7f7f8; --card:#fff; --text:#1d1d1f; --muted:#6b6b73; --line:#e3e3e8;
|
|
11
|
-
--ok:#1a7f37; --warn:#9a6700; --bad:#cf222e; --high:#cf222e; --medium:#bf8700; --low:#
|
|
11
|
+
--ok:#1a7f37; --warn:#9a6700; --bad:#cf222e; --high:#cf222e; --medium:#bf8700; --low:#3d6fa8; --accent:#0969da; --raised:var(--bg); }
|
|
12
12
|
@media (prefers-color-scheme: dark) { :root { --bg:#0d1117; --card:#161b22; --text:#e6edf3; --muted:#8b949e; --line:#30363d;
|
|
13
|
-
--ok:#3fb950; --warn:#d29922; --bad:#f85149; --high:#f85149; --medium:#d29922; --low:#
|
|
13
|
+
--ok:#3fb950; --warn:#d29922; --bad:#f85149; --high:#f85149; --medium:#d29922; --low:#a5d6ff; --accent:#58a6ff; --raised:#21262d; } }
|
|
14
14
|
* { box-sizing: border-box; }
|
|
15
15
|
body { margin:0; font:14px/1.45 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif; background:var(--bg); color:var(--text); }
|
|
16
|
-
header { display:flex; align-items:center; gap:12px; padding:14px 20px; border-bottom:1px solid var(--line); background:var(--card); }
|
|
16
|
+
header { display:flex; flex-wrap:wrap; align-items:center; gap:8px 12px; padding:14px 20px; border-bottom:1px solid var(--line); background:var(--card); }
|
|
17
17
|
header h1 { font-size:17px; margin:0; } header .meta { color:var(--muted); font-size:12px; margin-left:auto; }
|
|
18
18
|
button { font:inherit; padding:4px 10px; border:1px solid var(--line); border-radius:6px; background:var(--bg); color:var(--text); cursor:pointer; }
|
|
19
|
-
main { padding:16px 20px; display:grid; gap:16px; grid-template-columns: repeat(auto-fit, minmax(420px, 1fr)); }
|
|
19
|
+
main { padding:16px 20px; display:grid; gap:16px; grid-template-columns: repeat(auto-fit, minmax(min(420px, 100%), 1fr)); }
|
|
20
|
+
@media (max-width: 640px) { main { padding:12px; gap:12px; } header { padding:12px; } header .meta { margin-left:0; flex-basis:100%; } }
|
|
20
21
|
section.card { background:var(--card); border:1px solid var(--line); border-radius:10px; padding:14px 16px; min-width:0; }
|
|
21
22
|
section.wide { grid-column: 1 / -1; }
|
|
22
23
|
.sec-repo { border-top:1px solid var(--line); padding:10px 0; } .sec-repo:first-of-type { border-top:none; padding-top:0; } .sec-head { display:flex; gap:6px; align-items:baseline; flex-wrap:wrap; overflow-wrap:anywhere; }
|
|
@@ -46,6 +47,19 @@ a.rlink { color:var(--accent); } a.rlink:hover { text-decoration:underline; }
|
|
|
46
47
|
svg.trend rect.high { fill:var(--high); } svg.trend rect.medium { fill:var(--medium); } svg.trend rect.low { fill:var(--low); } svg.trend rect.zero { fill:var(--ok); }
|
|
47
48
|
svg.trend line { stroke:var(--line); }
|
|
48
49
|
.repo-title { font-size:12px; color:var(--muted); margin-bottom:6px; word-break:break-all; }
|
|
50
|
+
.summary { display:grid; gap:14px; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
|
|
51
|
+
@media (min-width: 641px) and (max-width: 1040px) { .summary { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
|
52
|
+
@media (max-width: 640px) { .summary { grid-template-columns: 1fr; gap:10px; } }
|
|
53
|
+
.scard { min-width:0; display:flex; flex-direction:column; gap:6px; padding:14px 16px; border:1px solid var(--line); border-radius:8px; background:var(--raised); }
|
|
54
|
+
.scard .k { color:var(--muted); font-size:12px; font-weight:600; text-transform:uppercase; letter-spacing:.04em; }
|
|
55
|
+
.scard .v { font-size:28px; font-weight:700; line-height:1.15; font-variant-numeric:tabular-nums; overflow-wrap:anywhere; }
|
|
56
|
+
.scard .v .unit { font-size:13px; font-weight:600; color:var(--muted); margin:0 10px 0 4px; }
|
|
57
|
+
.scard .d { color:var(--muted); font-size:13px; overflow-wrap:anywhere; display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden; }
|
|
58
|
+
.slim-line { padding:5px 0; border-top:1px solid var(--line); font-size:13px; overflow-wrap:anywhere; } .slim-line:first-of-type { border-top:none; } .slim-line code { white-space:normal; overflow-wrap:anywhere; }
|
|
59
|
+
.empty-line { color:var(--muted); margin:10px 0 4px; font-size:13px; overflow-wrap:anywhere; } .empty-line b { color:var(--text); }
|
|
60
|
+
.changed { outline:2px solid var(--accent); outline-offset:1px; border-radius:4px; }
|
|
61
|
+
@keyframes prism-flash { from { background-color:color-mix(in srgb, var(--accent) 35%, transparent); } to { background-color:transparent; } }
|
|
62
|
+
@media (prefers-reduced-motion: no-preference) { .changed { animation:prism-flash 2.5s ease-out 1; } }
|
|
49
63
|
`;
|
|
50
64
|
const SCRIPT = `
|
|
51
65
|
'use strict';
|
|
@@ -68,6 +82,21 @@ function table(head, rows) {
|
|
|
68
82
|
}
|
|
69
83
|
const num = (v) => el('td', { class: 'num' }, txt(v));
|
|
70
84
|
|
|
85
|
+
/** Repo labels (a parent path when two repos share a name), set on each load from data.repos. */
|
|
86
|
+
let LABELS = new Map();
|
|
87
|
+
const labelOf = (r) => LABELS.get(r.path) || txt(r.name);
|
|
88
|
+
|
|
89
|
+
/** Sections with no data, each collected as one line into the "Nothing recorded yet" card. */
|
|
90
|
+
let EMPTY = [];
|
|
91
|
+
function emptyLine(title, ...parts) { EMPTY.push(el('div', { class: 'slim-line' }, el('b', {}, title), ': ', ...parts)); return null; }
|
|
92
|
+
/** The same one line, inside a card that has other content. */
|
|
93
|
+
const inlineEmpty = (title, ...parts) => el('p', { class: 'empty-line' }, el('b', {}, title), ': ', ...parts);
|
|
94
|
+
|
|
95
|
+
/** Values compared between loads: key -> text. A changed value is outlined. */
|
|
96
|
+
let PREV = null, CUR = {};
|
|
97
|
+
function track(key, value, node) { CUR[key] = txt(value); node.setAttribute('data-track', key); return node; }
|
|
98
|
+
function changedKeys(prev, cur) { return prev ? Object.keys(cur).filter((k) => k in prev && prev[k] !== cur[k]) : []; }
|
|
99
|
+
|
|
71
100
|
function trendSvg(trend) {
|
|
72
101
|
const w = Math.max(trend.length * 14, 140), h = 48, svg = document.createElementNS(SVG, 'svg');
|
|
73
102
|
svg.setAttribute('class', 'trend'); svg.setAttribute('width', String(w)); svg.setAttribute('height', String(h + 2));
|
|
@@ -98,7 +127,54 @@ function scanBanner(ss) {
|
|
|
98
127
|
}
|
|
99
128
|
|
|
100
129
|
function repoName(r) {
|
|
101
|
-
return el('span', { title: r.path }, r
|
|
130
|
+
return el('span', { title: r.path }, labelOf(r), r.current ? el('span', { class: 'tag' }, 'this repo') : null);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const REVIEW_STATUS = { ok: 'clean', findings: 'findings', error: 'failed', skipped: 'skipped' };
|
|
134
|
+
|
|
135
|
+
/** The summary strip: latest review, spend, setup warnings. Same object as summary in --json. */
|
|
136
|
+
/** Long detail text is clamped to three lines by CSS; the full text stays in the title. */
|
|
137
|
+
const DETAIL_TITLE_MIN = 80;
|
|
138
|
+
const detail = (...parts) => {
|
|
139
|
+
const d = el('div', { class: 'd' }, ...parts);
|
|
140
|
+
const full = d.textContent;
|
|
141
|
+
if (full.length > DETAIL_TITLE_MIN) d.setAttribute('title', full);
|
|
142
|
+
return d;
|
|
143
|
+
};
|
|
144
|
+
/** One summary card: label, large value, one detail line. */
|
|
145
|
+
const scard = (label, value, det) => el('div', { class: 'scard' }, el('div', { class: 'k' }, label), value, det);
|
|
146
|
+
|
|
147
|
+
/** The summary cards: latest review, spend today, spend this month, setup warnings. Same object as summary in --json. */
|
|
148
|
+
function summaryPanel(sum) {
|
|
149
|
+
const s = el('section', { class: 'card wide' }, el('h2', {}, 'Summary'));
|
|
150
|
+
const box = el('div', { class: 'summary' });
|
|
151
|
+
const rv = sum && sum.review;
|
|
152
|
+
if (rv) {
|
|
153
|
+
const ran = rv.status !== 'skipped' && rv.status !== 'error';
|
|
154
|
+
const counts = ran ? rv.high + ' high · ' + rv.medium + ' medium · ' + rv.low + ' low' : txt(REVIEW_STATUS[rv.status] || rv.status);
|
|
155
|
+
const value = ran
|
|
156
|
+
? el('div', { class: 'v' }, el('span', { class: rv.high ? 'sev-high' : '' }, String(rv.high)), el('span', { class: 'unit' }, 'high'),
|
|
157
|
+
el('span', { class: rv.medium ? 'sev-medium' : '' }, String(rv.medium)), el('span', { class: 'unit' }, 'med'),
|
|
158
|
+
el('span', { class: rv.low ? 'sev-low' : '' }, String(rv.low)), el('span', { class: 'unit' }, 'low'))
|
|
159
|
+
: el('div', { class: 'v' }, counts.charAt(0).toUpperCase() + counts.slice(1));
|
|
160
|
+
box.append(scard('Latest codewatch review', track('sum:review', rv.id + ' ' + counts, value),
|
|
161
|
+
detail(el('span', { title: rv.path }, txt(rv.repo)), ' · ', reportLink(rv.id, when(rv.at)), ' · ' + txt(rv.reviewer) + (rv.reason ? ' · ' + txt(rv.reason) : ''))));
|
|
162
|
+
} else {
|
|
163
|
+
box.append(scard('Latest codewatch review', el('div', { class: 'v' }, 'none'), detail('No pre-push review recorded yet.')));
|
|
164
|
+
}
|
|
165
|
+
const sp = sum && sum.spend;
|
|
166
|
+
const ok = sp && sp.status === 'ok';
|
|
167
|
+
const spendCard = (label, key, n, det) => scard(label,
|
|
168
|
+
ok ? track(key, n, el('div', { class: 'v' }, money(n, sp.currency))) : el('div', { class: 'v' }, '-'),
|
|
169
|
+
detail(ok ? det : txt((sp && sp.message) || (sp && sp.status) || 'no codeburn data')));
|
|
170
|
+
box.append(spendCard('Spend today', 'sum:today', sp && sp.today, 'codeburn, all projects'));
|
|
171
|
+
box.append(spendCard('Spend this month', 'sum:month', sp && sp.month, 'codeburn, this calendar month'));
|
|
172
|
+
const w = (sum && sum.warnings) || { count: 0, first: null };
|
|
173
|
+
box.append(scard('Setup warnings',
|
|
174
|
+
track('sum:warnings', w.count, el('div', { class: 'v' + (w.count ? ' s-warn' : ' s-ok') }, String(w.count))),
|
|
175
|
+
detail(w.first ? txt(w.first) : 'Nothing needs attention.')));
|
|
176
|
+
s.append(box);
|
|
177
|
+
return s;
|
|
102
178
|
}
|
|
103
179
|
|
|
104
180
|
const SCAN_LABEL = { ok: ['ok', 'ok'], 'not-scanned': ['warn', 'not scanned'], 'no-verify': ['warn', 'no-verify'], flagged: ['missing', 'secrets found'], none: ['skipped', '-'] };
|
|
@@ -170,68 +246,96 @@ function codewatchDetail(repo) {
|
|
|
170
246
|
el('div', { class: 'stat' }, el('b', {}, cw.gitleaksBlocks.length), el('span', {}, 'gitleaks blocks')),
|
|
171
247
|
el('div', { class: 'stat' }, el('b', {}, cw.skipped.length), el('span', {}, 'pushed without a scan'))));
|
|
172
248
|
|
|
173
|
-
s.append(
|
|
174
|
-
s.append(commitsList(cw.commits));
|
|
249
|
+
if (!cw.commits || !cw.commits.rows.length) s.append(inlineEmpty('Commits', 'none recorded yet; commits appear after a pre-commit scan or a pre-push review that runs.'));
|
|
250
|
+
else s.append(el('h3', {}, 'Commits'), commitsList(cw.commits));
|
|
175
251
|
|
|
176
|
-
s.append(
|
|
177
|
-
s.append(scansList(cw.scans));
|
|
252
|
+
if (!cw.scans.length) s.append(inlineEmpty('Scans', 'no pre-push reviews yet.'));
|
|
253
|
+
else s.append(el('h3', {}, 'Scans (last ' + cw.scans.length + ')'), scansList(cw.scans));
|
|
178
254
|
|
|
179
|
-
|
|
180
|
-
|
|
255
|
+
const skipped = cw.skippedRuns || 0;
|
|
256
|
+
if (!cw.trend.length) {
|
|
257
|
+
// By design: a skipped run (nothing new to review, or no gitleaks) has no findings, so it is not charted.
|
|
258
|
+
s.append(inlineEmpty('Trend', 'nothing to chart. ' + (skipped
|
|
259
|
+
? 'All ' + skipped + ' run' + (skipped === 1 ? ' was' : 's were') + ' skipped (for example a push with no changes against upstream), and skipped runs have no findings to chart.'
|
|
260
|
+
: 'No run has finished yet.')));
|
|
261
|
+
} else {
|
|
262
|
+
s.append(el('h3', {}, 'Trend (last ' + cw.trend.length + ' runs)'), trendSvg(cw.trend));
|
|
263
|
+
if (skipped) s.append(el('p', { class: 'note' }, skipped + ' skipped run' + (skipped === 1 ? ' is' : 's are') + ' not charted: they have no findings.'));
|
|
264
|
+
}
|
|
181
265
|
|
|
182
|
-
s.append(
|
|
183
|
-
|
|
184
|
-
else s.append(table(['When', 'Branch', 'Result', 'Secrets'], cw.gitleaksBlocks.slice(0, 20).map((r) => [when(r.started_at), txt(r.branch), pill(r.status), r.status === 'findings' ? r.high : txt(r.reason)])));
|
|
266
|
+
if (!cw.gitleaksBlocks.length) s.append(inlineEmpty('Gitleaks blocks', 'no commits blocked.'));
|
|
267
|
+
else s.append(el('h3', {}, 'Gitleaks blocks'), table(['When', 'Branch', 'Result', 'Secrets'], cw.gitleaksBlocks.slice(0, 20).map((r) => [when(r.started_at), txt(r.branch), pill(r.status), r.status === 'findings' ? r.high : txt(r.reason)])));
|
|
185
268
|
|
|
186
|
-
s.append(
|
|
187
|
-
if (!cw.skipped.length) s.append(el('p', { class: 'empty' }, 'None.'));
|
|
269
|
+
if (!cw.skipped.length) s.append(inlineEmpty('Commits pushed without a pre-commit scan', 'none.'));
|
|
188
270
|
else {
|
|
271
|
+
s.append(el('h3', {}, 'Commits pushed without a pre-commit scan'));
|
|
189
272
|
s.append(table(['Commit', 'Branch', 'Recorded'], cw.skipped.slice(0, 20).map((r) => [el('code', {}, short(r.commit)), txt(r.branch), when(r.started_at)])));
|
|
190
273
|
s.append(el('p', { class: 'note' }, 'Usually git commit --no-verify; rebases and cherry-picks also show up here.'));
|
|
191
274
|
}
|
|
192
275
|
return s;
|
|
193
276
|
}
|
|
194
277
|
|
|
278
|
+
/** "29 other repos: not set up", expanding to one line per repo. key keeps it open across refreshes. */
|
|
279
|
+
function otherRepos(key, list, what, line) {
|
|
280
|
+
if (!list.length) return null;
|
|
281
|
+
return el('details', { class: 'missing-group', 'data-key': key },
|
|
282
|
+
el('summary', {}, list.length + ' other repo' + (list.length === 1 ? '' : 's') + ': ' + what),
|
|
283
|
+
el('ul', { class: 'sec-findings' }, list.map((r) => el('li', {}, repoName(r), line(r)))));
|
|
284
|
+
}
|
|
285
|
+
|
|
195
286
|
function codewatchPanel(data) {
|
|
196
|
-
const
|
|
287
|
+
const rows = data.overview.codewatch;
|
|
197
288
|
const banner = scanBanner(data.machine.secretScanning);
|
|
289
|
+
if (!rows.length && !banner) {
|
|
290
|
+
const sg = (data.suggestions || []).find((x) => x.id === 'codewatch-global');
|
|
291
|
+
if (sg) return emptyLine('Codewatch', "isn't enabled in any repo yet. Cover every repo at once: ", el('code', {}, sg.command), ', or run ', el('code', {}, 'prism-cli enable codewatch'), ' inside one repo.');
|
|
292
|
+
return emptyLine('Codewatch', "isn't enabled in any repo yet. Run ", el('code', {}, 'prism-cli enable codewatch'), ' inside a repo.');
|
|
293
|
+
}
|
|
294
|
+
const s = el('section', { class: 'card wide' }, el('h2', {}, 'Codewatch'));
|
|
198
295
|
if (banner) s.append(banner);
|
|
199
|
-
const rows = data.overview.codewatch;
|
|
200
296
|
if (!rows.length) {
|
|
201
|
-
|
|
202
|
-
if (sg) s.append(el('p', { class: 'empty' }, "Codewatch isn't enabled in any repo yet. Cover every repo at once: ", el('code', {}, sg.command), ', or run ', el('code', {}, 'prism-cli enable codewatch'), ' inside one repo.'));
|
|
203
|
-
else s.append(el('p', { class: 'empty' }, "Codewatch isn't enabled in any repo yet. Run ", el('code', {}, 'prism-cli enable codewatch'), ' inside a repo.'));
|
|
297
|
+
s.append(el('p', { class: 'empty' }, "Codewatch isn't enabled in any repo yet. Run ", el('code', {}, 'prism-cli enable codewatch'), ' inside a repo.'));
|
|
204
298
|
return s;
|
|
205
299
|
}
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
r
|
|
300
|
+
const cur = data.usage && data.usage.currency;
|
|
301
|
+
const count = (key, n, cls) => track(key, n, el('td', { class: 'count' + (n ? ' ' + cls : '') }, n));
|
|
302
|
+
s.append(table(['Repo', 'High', 'Medium', 'Low', 'Last run', 'Reviewer', 'Unscanned', 'Gitleaks', 'Spend (month)'], rows.map((r) => [
|
|
303
|
+
repoName(r), count('cw:h:' + r.path, r.high, 'sev-high'), count('cw:m:' + r.path, r.medium, 'sev-medium'), count('cw:l:' + r.path, r.low, 'sev-low'),
|
|
304
|
+
track('cw:last:' + r.path, r.lastRunAt, el('td', {}, r.lastRunAt ? (r.lastReviewId ? reportLink(r.lastReviewId, when(r.lastRunAt)) : when(r.lastRunAt)) : 'never')),
|
|
305
|
+
txt(r.reviewer || '-'), count('cw:u:' + r.path, r.unscannedCommits, 'sev-medium'),
|
|
210
306
|
r.gitleaksWarning ? pill('warn', 'not scanned') : '-',
|
|
307
|
+
track('cw:spend:' + r.path, r.spendMonth, el('td', { class: 'num', title: 'codeburn projects at or under this repo' }, r.spendMonth === null || r.spendMonth === undefined ? '-' : money(r.spendMonth, cur))),
|
|
211
308
|
])));
|
|
212
309
|
const byPath = Object.fromEntries(data.repos.map((r) => [r.path, r]));
|
|
213
310
|
s.append(el('h3', {}, 'Per repo'));
|
|
214
311
|
for (const row of rows) {
|
|
215
312
|
const repo = byPath[row.path]; if (!repo) continue;
|
|
216
313
|
const total = row.high + row.medium + row.low;
|
|
217
|
-
const d = el('details', repo.current ? { class: 'repo', open: '' } : { class: 'repo' },
|
|
218
|
-
el('summary', {}, el('b', {}, repo
|
|
314
|
+
const d = el('details', repo.current ? { class: 'repo', open: '', 'data-key': 'cw:' + repo.path } : { class: 'repo', 'data-key': 'cw:' + repo.path },
|
|
315
|
+
el('summary', {}, el('b', {}, labelOf(repo)), ' ', el('span', { class: 'fix' }, repo.codewatch.hasResults ? total + ' finding' + (total === 1 ? '' : 's') + ' in the latest reviews' : 'no results yet'),
|
|
219
316
|
repo.current ? el('span', { class: 'tag' }, 'this repo') : null),
|
|
220
317
|
el('div', { class: 'repo-title' }, repo.path));
|
|
221
318
|
d.append(codewatchDetail(repo));
|
|
222
319
|
s.append(d);
|
|
223
320
|
}
|
|
321
|
+
const shown = new Set(rows.map((r) => r.path));
|
|
322
|
+
const rest = data.overview.setup.filter((r) => !shown.has(r.path));
|
|
323
|
+
const other = otherRepos('cw:other', rest, 'no codewatch', (r) => r.fix ? el('div', { class: 'fix' }, 'Fix: ', el('code', {}, r.fix)) : el('div', { class: 'fix' }, txt(r.detail)));
|
|
324
|
+
if (other) s.append(other);
|
|
224
325
|
return s;
|
|
225
326
|
}
|
|
226
327
|
|
|
227
328
|
function usagePanel(u) {
|
|
329
|
+
if (u.status === 'not-installed' || u.status === 'no-data') {
|
|
330
|
+
return emptyLine('Usage (codeburn)', txt(u.message), u.status === 'not-installed' ? '' : el('span', {}, ' Usage appears once a coding tool records sessions; full charts: ', el('code', {}, txt(u.fullView)), '.'));
|
|
331
|
+
}
|
|
228
332
|
const s = el('section', { class: 'card' }, el('h2', {}, 'Usage (codeburn)'));
|
|
229
333
|
if (u.status !== 'ok') {
|
|
230
|
-
s.append(el('p', { class: 'empty' }, pill(
|
|
334
|
+
s.append(el('p', { class: 'empty' }, pill('missing', u.status), ' ', txt(u.message)));
|
|
231
335
|
} else {
|
|
232
336
|
s.append(el('div', { class: 'stats' },
|
|
233
|
-
el('div', { class: 'stat' }, el('b', {}, money(u.today.cost, u.currency)), el('span', {}, 'today, ' + u.today.calls + ' calls')),
|
|
234
|
-
el('div', { class: 'stat' }, el('b', {}, money(u.month.cost, u.currency)), el('span', {}, 'this month, ' + u.month.calls + ' calls, ' + u.month.sessions + ' sessions'))));
|
|
337
|
+
el('div', { class: 'stat' }, track('use:today', u.today.cost, el('b', {}, money(u.today.cost, u.currency))), el('span', {}, 'today, ' + u.today.calls + ' calls')),
|
|
338
|
+
el('div', { class: 'stat' }, track('use:month', u.month.cost, el('b', {}, money(u.month.cost, u.currency))), el('span', {}, 'this month, ' + u.month.calls + ' calls, ' + u.month.sessions + ' sessions'))));
|
|
235
339
|
s.append(el('h3', {}, 'Top models this month'));
|
|
236
340
|
s.append(table(['Model', 'Cost', 'Calls'], u.topModels.map((m) => [m.name, num(money(m.cost, u.currency)), num(m.calls)])));
|
|
237
341
|
s.append(el('h3', {}, 'Top projects this month'));
|
|
@@ -271,23 +375,25 @@ function setupPanel(data) {
|
|
|
271
375
|
];
|
|
272
376
|
const sg = (data.suggestions || []).find((x) => x.id === 'codewatch-global');
|
|
273
377
|
if (sg) s.append(suggestionCallout(sg));
|
|
274
|
-
//
|
|
378
|
+
// Shown: the current repo, repos with activity (listed first), and repos with something set up.
|
|
275
379
|
const shown = repos.filter((r) => !r.collapsed);
|
|
276
380
|
const hidden = repos.filter((r) => r.collapsed);
|
|
277
381
|
if (shown.length) s.append(table(['Repo', 'Codewatch', 'Security Agent'], shown.map(rowOf)));
|
|
278
382
|
else if (!hidden.length) s.append(el('p', { class: 'empty' }, 'No git repositories found under ' + txt(data.scope.root_display) + '.'));
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
383
|
+
const optedOut = hidden.filter((r) => r.status === 'opted-out').length;
|
|
384
|
+
const group = el('details', { class: 'missing-group', 'data-key': 'setup:other' },
|
|
385
|
+
el('summary', {}, hidden.length + ' other repo' + (hidden.length === 1 ? '' : 's') + ': not set up' + (optedOut ? ' (' + optedOut + ' opted out of codewatch)' : '')),
|
|
386
|
+
table(['Repo', 'Codewatch', 'Security Agent'], hidden.map(rowOf)));
|
|
387
|
+
if (hidden.length) s.append(group);
|
|
284
388
|
return s;
|
|
285
389
|
}
|
|
286
390
|
|
|
287
|
-
|
|
391
|
+
const SEC_LABEL = { ok: 'ok', 'in-use': 'in use', missing: 'missing' };
|
|
392
|
+
|
|
393
|
+
/** Security Agent per repo: the enable security marker, else saved results (local evidence; the page makes no AWS calls). */
|
|
288
394
|
function securityCell(sec) {
|
|
289
395
|
if (!sec) return el('span', { class: 'fix' }, '-');
|
|
290
|
-
return el('div', {}, pill(sec.status === '
|
|
396
|
+
return el('div', {}, pill(sec.status === 'missing' ? 'missing' : 'ok', SEC_LABEL[sec.status] || sec.status), ' ', sec.status === 'missing' ? '' : txt(sec.detail).replace(/^in use /, ''),
|
|
291
397
|
sec.fix ? el('div', { class: 'fix' }, 'Fix: ', el('code', {}, sec.fix)) : null);
|
|
292
398
|
}
|
|
293
399
|
|
|
@@ -304,13 +410,13 @@ function securityPanel(sec) {
|
|
|
304
410
|
const tms = (sec && sec.threat_models) || [];
|
|
305
411
|
const pts = (sec && sec.pentests) || [];
|
|
306
412
|
if (!rows.length && !tms.length && !pts.length) {
|
|
307
|
-
|
|
308
|
-
return s;
|
|
413
|
+
return emptyLine('Security findings', 'No Security Agent code reviews, threat models or penetration tests yet. Run ', el('code', {}, (sec && sec.hint) || 'prism-cli security code-review'), ', ', el('code', {}, 'prism-cli security threat-model'), ' or ', el('code', {}, 'prism-cli security pentest'), ' in a repo set up with ', el('code', {}, 'prism-cli enable security'), '.');
|
|
309
414
|
}
|
|
415
|
+
if (!rows.length) s.append(inlineEmpty('Code reviews', 'none yet. Run ', el('code', {}, (sec && sec.hint) || 'prism-cli security code-review'), '.'));
|
|
310
416
|
for (const r of rows) {
|
|
311
417
|
const counts = r.counts || {};
|
|
312
418
|
const b = el('div', { class: 'sec-repo' });
|
|
313
|
-
b.append(el('div', { class: 'sec-head' }, el('b', {},
|
|
419
|
+
b.append(el('div', { class: 'sec-head' }, el('b', {}, labelOf(r)), r.current ? el('span', { class: 'tag' }, 'this repo') : null));
|
|
314
420
|
b.append(el('div', { class: 'fix' },
|
|
315
421
|
(r.pending ? 'started ' + when(r.started_at) : when(r.finished_at)) + ' · ' + (r.scope === 'diff' ? 'branch diff' : r.scope === 'full' ? 'whole repo' : 'fetched') + ' · job ' + txt(r.job_id).slice(0, 12)));
|
|
316
422
|
const pills = el('div', { class: 'sec-pills' });
|
|
@@ -348,7 +454,7 @@ function securityPanel(sec) {
|
|
|
348
454
|
if (tms.length) {
|
|
349
455
|
s.append(el('h3', {}, 'Threat models'));
|
|
350
456
|
for (const t of tms) s.append(threatModelBlock(t, (sec && sec.threats_hint) || 'prism-cli security threats'));
|
|
351
|
-
}
|
|
457
|
+
} else s.append(inlineEmpty('Threat models', 'none yet. Run ', el('code', {}, 'prism-cli security threat-model'), '.'));
|
|
352
458
|
return s;
|
|
353
459
|
}
|
|
354
460
|
|
|
@@ -359,7 +465,7 @@ const strideText = (list) => (list || []).map((x) => txt(x).toLowerCase().replac
|
|
|
359
465
|
/** One cached threat model per repo, in the code review blocks' compact style. */
|
|
360
466
|
function threatModelBlock(t, hint) {
|
|
361
467
|
const b = el('div', { class: 'sec-repo' });
|
|
362
|
-
b.append(el('div', { class: 'sec-head' }, el('b', {},
|
|
468
|
+
b.append(el('div', { class: 'sec-head' }, el('b', {}, labelOf(t)), t.current ? el('span', { class: 'tag' }, 'this repo') : null));
|
|
363
469
|
const docs = (t.scope_docs || []).length;
|
|
364
470
|
b.append(el('div', { class: 'fix' },
|
|
365
471
|
(t.pending ? 'started ' + when(t.started_at) : when(t.finished_at)) + ' · commit ' + txt(t.commit).slice(0, 12)
|
|
@@ -399,12 +505,12 @@ function threatModelBlock(t, hint) {
|
|
|
399
505
|
/** Penetration-test rows, in the same Security card below the code-review rows. */
|
|
400
506
|
function securityPentestRows(s, sec) {
|
|
401
507
|
const rows = (sec && sec.pentests) || [];
|
|
402
|
-
if (!rows.length) return;
|
|
508
|
+
if (!rows.length) { s.append(inlineEmpty('Penetration tests', 'none yet. Run ', el('code', {}, 'prism-cli security pentest'), '.')); return; }
|
|
403
509
|
s.append(el('h3', {}, 'Penetration tests'));
|
|
404
510
|
for (const r of rows) {
|
|
405
511
|
const counts = r.counts || {};
|
|
406
512
|
const b = el('div', { class: 'sec-repo' });
|
|
407
|
-
b.append(el('div', { class: 'sec-head' }, el('b', {},
|
|
513
|
+
b.append(el('div', { class: 'sec-head' }, el('b', {}, labelOf(r)), r.current ? el('span', { class: 'tag' }, 'this repo') : null));
|
|
408
514
|
b.append(el('div', { class: 'fix' },
|
|
409
515
|
(r.pending ? 'started ' + when(r.started_at) : when(r.finished_at)) + ' · ' + txt(r.target_host) + ' · job ' + txt(r.job_id).slice(0, 12)));
|
|
410
516
|
const pills = el('div', { class: 'sec-pills' });
|
|
@@ -444,12 +550,9 @@ const AUDIT_SEVERITIES = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'];
|
|
|
444
550
|
|
|
445
551
|
/** The latest aws check per account, in the Security panel's compact style. Local results only. */
|
|
446
552
|
function awsPanel(aws) {
|
|
447
|
-
const s = el('section', { class: 'card' }, el('h2', {}, 'AWS account'));
|
|
448
553
|
const rows = (aws && aws.accounts) || [];
|
|
449
|
-
if (!rows.length) {
|
|
450
|
-
|
|
451
|
-
return s;
|
|
452
|
-
}
|
|
554
|
+
if (!rows.length) return emptyLine('AWS account', 'no checks yet. Run ', el('code', {}, (aws && aws.hint) || 'prism-cli aws check'), ' (read-only).');
|
|
555
|
+
const s = el('section', { class: 'card' }, el('h2', {}, 'AWS account'));
|
|
453
556
|
for (const a of rows) {
|
|
454
557
|
const b = el('div', { class: 'sec-repo' });
|
|
455
558
|
b.append(el('div', { class: 'sec-head' }, el('b', {}, txt(a.account)), a.alias ? el('span', { class: 'tag' }, txt(a.alias)) : null));
|
|
@@ -473,22 +576,56 @@ function awsPanel(aws) {
|
|
|
473
576
|
return s;
|
|
474
577
|
}
|
|
475
578
|
|
|
579
|
+
/** Auto-refresh period while the tab is visible; paused while hidden. */
|
|
580
|
+
const AUTO_REFRESH_MS = 60000;
|
|
581
|
+
let lastLoad = 0, loading = false;
|
|
582
|
+
|
|
476
583
|
async function load() {
|
|
584
|
+
if (loading) return;
|
|
585
|
+
loading = true;
|
|
586
|
+
try { await render(); } finally { loading = false; lastLoad = Date.now(); }
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
async function render() {
|
|
477
590
|
const main = document.getElementById('main'), meta = document.getElementById('meta');
|
|
591
|
+
const before = meta.textContent;
|
|
478
592
|
meta.textContent = 'loading...';
|
|
479
593
|
let data;
|
|
480
594
|
try {
|
|
481
595
|
const res = await fetch('/api/data', { credentials: 'same-origin', cache: 'no-store' });
|
|
482
596
|
if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
483
597
|
data = await res.json();
|
|
484
|
-
} catch (e) {
|
|
598
|
+
} catch (e) {
|
|
599
|
+
// Keep the last good view; say the refresh failed.
|
|
600
|
+
meta.textContent = 'failed to load: ' + e.message + (before && before !== 'loading...' ? ' · showing ' + before : '');
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
// Open/closed state of keyed <details> survives the redraw.
|
|
604
|
+
const openState = new Map([...main.querySelectorAll('details[data-key]')].map((d) => [d.getAttribute('data-key'), d.open]));
|
|
605
|
+
LABELS = new Map((data.repos || []).map((r) => [r.path, r.label || r.name]));
|
|
606
|
+
EMPTY = []; CUR = {};
|
|
485
607
|
main.replaceChildren();
|
|
486
608
|
if (data.scope.error) main.append(el('section', { class: 'card wide' }, pill('error'), ' ', data.scope.error));
|
|
487
|
-
|
|
609
|
+
const panels = [summaryPanel(data.summary), codewatchPanel(data), usagePanel(data.usage), setupPanel(data), securityPanel(data.security), awsPanel(data.aws)];
|
|
610
|
+
main.append(...panels.filter(Boolean));
|
|
611
|
+
if (EMPTY.length) main.append(el('section', { class: 'card wide' }, el('h2', {}, 'Nothing recorded yet'), ...EMPTY));
|
|
612
|
+
for (const d of main.querySelectorAll('details[data-key]')) {
|
|
613
|
+
const k = d.getAttribute('data-key');
|
|
614
|
+
if (openState.has(k)) d.open = openState.get(k);
|
|
615
|
+
}
|
|
616
|
+
const changed = new Set(changedKeys(PREV, CUR));
|
|
617
|
+
for (const n of main.querySelectorAll('[data-track]')) {
|
|
618
|
+
const k = n.getAttribute('data-track');
|
|
619
|
+
if (changed.has(k)) { n.classList.add('changed'); n.setAttribute('title', 'changed since the previous load (was ' + PREV[k] + ')'); }
|
|
620
|
+
}
|
|
621
|
+
PREV = CUR;
|
|
488
622
|
const c = data.overview.counts;
|
|
489
|
-
meta.textContent = c.repos + ' repos under ' + txt(data.scope.root_display) + ' · ' + c.codewatch + ' with codewatch' + (data.scope.truncated ? ' (scan capped)' : '')
|
|
623
|
+
meta.textContent = c.repos + ' repos under ' + txt(data.scope.root_display) + ' · ' + c.codewatch + ' with codewatch' + (data.scope.truncated ? ' (scan capped)' : '')
|
|
624
|
+
+ ' · updated ' + when(data.generated_at) + (changed.size ? ' · ' + changed.size + ' value' + (changed.size === 1 ? '' : 's') + ' changed (outlined)' : '') + ' · refreshes every minute while visible';
|
|
490
625
|
}
|
|
491
626
|
document.getElementById('refresh').addEventListener('click', load);
|
|
627
|
+
setInterval(() => { if (document.visibilityState === 'visible' && Date.now() - lastLoad >= AUTO_REFRESH_MS - 1000) load(); }, AUTO_REFRESH_MS / 4);
|
|
628
|
+
document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible' && Date.now() - lastLoad >= AUTO_REFRESH_MS) load(); });
|
|
492
629
|
load();
|
|
493
630
|
`;
|
|
494
631
|
export function renderPage(nonce) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page.js","sourceRoot":"","sources":["../../../src/dashboard/page.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,KAAK,GAAG
|
|
1
|
+
{"version":3,"file":"page.js","sourceRoot":"","sources":["../../../src/dashboard/page.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,KAAK,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsDb,CAAC;AAEF,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsjBd,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,wFAAwF;IACxF,OAAO;;8CAEqC,KAAK,KAAK,KAAK;;;iBAG5C,KAAK,KAAK,MAAM,yBAAyB,CAAC;AAC3D,CAAC"}
|
|
@@ -6,15 +6,22 @@
|
|
|
6
6
|
import { type CodewatchPanel, type SecretScanning } from './codewatch.js';
|
|
7
7
|
import { type DiscoverOptions } from './repos.js';
|
|
8
8
|
import { type CheckItem, type CodewatchSetup, type MachineDeps, type SecuritySetup } from './setup.js';
|
|
9
|
+
import { type DashboardSummary } from './summary.js';
|
|
9
10
|
import { type GlobalStatus } from '../codewatch/globalHooks.js';
|
|
10
11
|
export declare function tildeHome(p: string, home: string): string;
|
|
11
|
-
import { type CodeburnExport, type UsagePanel } from './usage.js';
|
|
12
|
+
import { type CodeburnExport, type RepoSpend, type UsagePanel } from './usage.js';
|
|
12
13
|
import { type SecurityPanel } from './security.js';
|
|
13
14
|
import { type AwsPanel } from './aws.js';
|
|
14
15
|
export declare const PAYLOAD_SCHEMA = "prism.dashboard.v2";
|
|
15
16
|
export interface RepoPayload {
|
|
16
17
|
name: string;
|
|
18
|
+
/** The name, or a short parent path when another repo has the same name. */
|
|
19
|
+
label: string;
|
|
17
20
|
path: string;
|
|
21
|
+
/** codeburn spend this month in projects at or under this repo; null when codeburn has no data. */
|
|
22
|
+
spend: RepoSpend | null;
|
|
23
|
+
/** Codewatch results, Security Agent results, or codeburn spend this month. */
|
|
24
|
+
active: boolean;
|
|
18
25
|
current: boolean;
|
|
19
26
|
hasPrism: boolean;
|
|
20
27
|
codewatch: CodewatchPanel;
|
|
@@ -25,7 +32,10 @@ export interface RepoPayload {
|
|
|
25
32
|
/** One row of the setup table. */
|
|
26
33
|
export interface SetupRow {
|
|
27
34
|
name: string;
|
|
35
|
+
label: string;
|
|
28
36
|
path: string;
|
|
37
|
+
/** See RepoPayload.active; active repos are listed first. */
|
|
38
|
+
active: boolean;
|
|
29
39
|
current: boolean;
|
|
30
40
|
status: CodewatchSetup['status'];
|
|
31
41
|
reviewer: string | null;
|
|
@@ -35,13 +45,21 @@ export interface SetupRow {
|
|
|
35
45
|
global: boolean;
|
|
36
46
|
/** Security Agent row for this repo. */
|
|
37
47
|
security: SecuritySetup;
|
|
38
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* Shown inside the one collapsed "other repos: not set up" line: not the
|
|
50
|
+
* current repo, no activity, codewatch missing or opted out, and no
|
|
51
|
+
* Security Agent marker.
|
|
52
|
+
*/
|
|
39
53
|
collapsed: boolean;
|
|
40
54
|
}
|
|
41
55
|
/** One row of the codewatch summary table. */
|
|
42
56
|
export interface CodewatchRow {
|
|
43
57
|
name: string;
|
|
58
|
+
label: string;
|
|
44
59
|
path: string;
|
|
60
|
+
active: boolean;
|
|
61
|
+
/** codeburn spend this month for this repo, or null when there is none to match. */
|
|
62
|
+
spendMonth: number | null;
|
|
45
63
|
current: boolean;
|
|
46
64
|
high: number;
|
|
47
65
|
medium: number;
|
|
@@ -102,6 +120,8 @@ export interface DashboardPayload {
|
|
|
102
120
|
security: SecurityPanel;
|
|
103
121
|
/** The latest cached aws check result per AWS account (repos and ~). */
|
|
104
122
|
aws: AwsPanel;
|
|
123
|
+
/** The one-glance summary shown at the top of the page (buildSummary). */
|
|
124
|
+
summary: DashboardSummary;
|
|
105
125
|
}
|
|
106
126
|
export interface PayloadOptions extends DiscoverOptions {
|
|
107
127
|
trendRuns?: number;
|
|
@@ -109,9 +129,9 @@ export interface PayloadOptions extends DiscoverOptions {
|
|
|
109
129
|
machineDeps?: MachineDeps;
|
|
110
130
|
pathEnv?: string;
|
|
111
131
|
}
|
|
112
|
-
/** The current repo first, then installed, bypassed (warn), missing and opted-out repos, each by name. */
|
|
132
|
+
/** The current repo first, then repos with activity, then installed, bypassed (warn), missing and opted-out repos, each by name. */
|
|
113
133
|
export declare function sortSetupRows(rows: SetupRow[]): SetupRow[];
|
|
114
|
-
/**
|
|
134
|
+
/** Repos with activity first, then most high findings, then medium, then low, then by name. */
|
|
115
135
|
export declare function sortCodewatchRows(rows: CodewatchRow[]): CodewatchRow[];
|
|
116
136
|
export declare function buildPayload(opts: PayloadOptions): Promise<DashboardPayload>;
|
|
117
137
|
//# sourceMappingURL=payload.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"payload.d.ts","sourceRoot":"","sources":["../../../src/dashboard/payload.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAA2C,KAAK,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACnH,OAAO,
|
|
1
|
+
{"version":3,"file":"payload.d.ts","sourceRoot":"","sources":["../../../src/dashboard/payload.ts"],"names":[],"mappings":"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"}
|