@prism-d1/cli 1.4.3 → 1.6.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/aws/check.js +1 -1
- package/dist/src/aws/team.d.ts +39 -0
- package/dist/src/aws/team.d.ts.map +1 -0
- package/dist/src/aws/team.js +172 -0
- package/dist/src/aws/team.js.map +1 -0
- package/dist/src/commands/enable/codeburn.d.ts +27 -3
- package/dist/src/commands/enable/codeburn.d.ts.map +1 -1
- package/dist/src/commands/enable/codeburn.js +317 -40
- package/dist/src/commands/enable/codeburn.js.map +1 -1
- package/dist/src/commands/team/add-developer.d.ts +17 -0
- package/dist/src/commands/team/add-developer.d.ts.map +1 -0
- package/dist/src/commands/team/add-developer.js +22 -0
- package/dist/src/commands/team/add-developer.js.map +1 -0
- package/dist/src/commands/team/info.d.ts +16 -0
- package/dist/src/commands/team/info.d.ts.map +1 -0
- package/dist/src/commands/team/info.js +30 -0
- package/dist/src/commands/team/info.js.map +1 -0
- 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 +203 -55
- package/dist/src/dashboard/page.js.map +1 -1
- package/dist/src/dashboard/payload.d.ts +31 -4
- package/dist/src/dashboard/payload.d.ts.map +1 -1
- package/dist/src/dashboard/payload.js +43 -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 +21 -5
- package/dist/src/dashboard/setup.d.ts.map +1 -1
- package/dist/src/dashboard/setup.js +47 -17
- 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 +20 -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/index.d.ts.map +1 -1
- package/dist/src/index.js +8 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/utils/otel-sync-schedule.d.ts +18 -1
- package/dist/src/utils/otel-sync-schedule.d.ts.map +1 -1
- package/dist/src/utils/otel-sync-schedule.js +44 -3
- package/dist/src/utils/otel-sync-schedule.js.map +1 -1
- package/dist/src/utils/sync-mode.d.ts +43 -0
- package/dist/src/utils/sync-mode.d.ts.map +1 -0
- package/dist/src/utils/sync-mode.js +84 -0
- package/dist/src/utils/sync-mode.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add-developer.js","sourceRoot":"","sources":["../../../../src/commands/team/add-developer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,YAAY,EAA4B,MAAM,mBAAmB,CAAC;AAC5F,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAkB,MAAM,WAAW,CAAC;AAErD,eAAe;IACb,WAAW,EAAE,kIAAkI,eAAe,GAAG;IACjK,QAAQ,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,gDAAgD,EAAE;IAC3G,OAAO,EAAE;QACP,GAAG,kBAAkB;QACrB,EAAE,KAAK,EAAE,qBAAqB,EAAE,WAAW,EAAE,sEAAsE,EAAE;QACrH,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,sDAAsD,EAAE;QACvF,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,8DAA8D,EAAE;KACpG;IACD,KAAK,CAAC,MAAM,CAAC,IAAyB,EAAE,QAAmB,EAAE;QAC3D,MAAM,UAAU,CAAC,KAAK,IAAI,EAAE;YAC1B,MAAM,CAAC,GAAG,kBAAkB,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,SAAS,EAAE,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;YAC3H,OAAO,YAAY,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,IAAI,QAAQ,CAAC,EAAE,CAAC,CAAC;QACzD,CAAC,EAAE,KAAK,CAAC,CAAC;IACZ,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type TeamDeps, type TeamOptions } from '../../aws/team.js';
|
|
2
|
+
import { type RunHooks } from '../enable/security.js';
|
|
3
|
+
export declare function teamDeps(): TeamDeps;
|
|
4
|
+
export interface TeamHooks extends RunHooks {
|
|
5
|
+
teamDeps?: () => TeamDeps;
|
|
6
|
+
}
|
|
7
|
+
declare const _default: {
|
|
8
|
+
description: string;
|
|
9
|
+
options: {
|
|
10
|
+
flags: string;
|
|
11
|
+
description: string;
|
|
12
|
+
}[];
|
|
13
|
+
action(opts: TeamOptions, hooks?: TeamHooks): Promise<void>;
|
|
14
|
+
};
|
|
15
|
+
export default _default;
|
|
16
|
+
//# sourceMappingURL=info.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"info.d.ts","sourceRoot":"","sources":["../../../../src/commands/team/info.ts"],"names":[],"mappings":"AAGA,OAAO,EAA8B,KAAK,QAAQ,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhG,OAAO,EAAc,KAAK,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAElE,wBAAgB,QAAQ,IAAI,QAAQ,CAYnC;AAED,MAAM,WAAW,SAAU,SAAQ,QAAQ;IAAG,QAAQ,CAAC,EAAE,MAAM,QAAQ,CAAA;CAAE;;;;;;;iBAKpD,WAAW,UAAS,SAAS;;AAHlD,wBASE"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline';
|
|
2
|
+
import { JSON_OPTION } from '../../utils/audit.js';
|
|
3
|
+
import { configuredRegion, connectionDefaults } from '../../aws/connection.js';
|
|
4
|
+
import { INFO_PERMISSIONS, teamInfo } from '../../aws/team.js';
|
|
5
|
+
import { AWS_TARGET_OPTIONS } from '../aws/check.js';
|
|
6
|
+
import { runGuarded } from '../enable/security.js';
|
|
7
|
+
export function teamDeps() {
|
|
8
|
+
return {
|
|
9
|
+
env: process.env,
|
|
10
|
+
configRegion: configuredRegion,
|
|
11
|
+
isTTY: !!(process.stdin.isTTY && process.stdout.isTTY),
|
|
12
|
+
ask: (q) => new Promise((done) => {
|
|
13
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
14
|
+
rl.question(q, (a) => { rl.close(); done(a.trim()); });
|
|
15
|
+
}),
|
|
16
|
+
log: (line = '') => console.log(line),
|
|
17
|
+
err: (line = '') => console.error(line),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export default {
|
|
21
|
+
description: `Read-only: print the PRISM stack's OtelCollectorUrl and OtelUserPoolId, and the line to send developers (needs ${INFO_PERMISSIONS})`,
|
|
22
|
+
options: [...AWS_TARGET_OPTIONS, JSON_OPTION],
|
|
23
|
+
async action(opts, hooks = {}) {
|
|
24
|
+
await runGuarded(async () => {
|
|
25
|
+
const o = connectionDefaults({ ...opts, profile: opts.profile || undefined, region: opts.region || undefined }, hooks.env);
|
|
26
|
+
return teamInfo(o, (hooks.teamDeps ?? teamDeps)());
|
|
27
|
+
}, hooks);
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=info.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"info.js","sourceRoot":"","sources":["../../../../src/commands/team/info.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC/E,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAmC,MAAM,mBAAmB,CAAC;AAChG,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAiB,MAAM,uBAAuB,CAAC;AAElE,MAAM,UAAU,QAAQ;IACtB,OAAO;QACL,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,YAAY,EAAE,gBAAgB;QAC9B,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;QACtD,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;YAC/B,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;YAC7E,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACzD,CAAC,CAAC;QACF,GAAG,EAAE,CAAC,IAAI,GAAG,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;QACrC,GAAG,EAAE,CAAC,IAAI,GAAG,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC;KACxC,CAAC;AACJ,CAAC;AAID,eAAe;IACb,WAAW,EAAE,kHAAkH,gBAAgB,GAAG;IAClJ,OAAO,EAAE,CAAC,GAAG,kBAAkB,EAAE,WAAW,CAAC;IAC7C,KAAK,CAAC,MAAM,CAAC,IAAiB,EAAE,QAAmB,EAAE;QACnD,MAAM,UAAU,CAAC,KAAK,IAAI,EAAE;YAC1B,MAAM,CAAC,GAAG,kBAAkB,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,SAAS,EAAE,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;YAC3H,OAAO,QAAQ,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,IAAI,QAAQ,CAAC,EAAE,CAAC,CAAC;QACrD,CAAC,EAAE,KAAK,CAAC,CAAC;IACZ,CAAC;CACF,CAAC"}
|
|
@@ -73,8 +73,10 @@ export interface CodewatchPanel {
|
|
|
73
73
|
gitleaksBlocks: RunSummary[];
|
|
74
74
|
/** Commits pushed without a pre-commit scan (`--no-verify`, rebase, ...). */
|
|
75
75
|
skipped: RunSummary[];
|
|
76
|
-
/** Last N runs, oldest first. */
|
|
76
|
+
/** Last N runs that were not skipped, oldest first. A skipped run (nothing to review, no gitleaks) has no findings to chart. */
|
|
77
77
|
trend: RunSummary[];
|
|
78
|
+
/** Runs left out of the trend because they were skipped. */
|
|
79
|
+
skippedRuns: number;
|
|
78
80
|
secretScanning: SecretScanning;
|
|
79
81
|
/** Results mapped onto commits, newest first. */
|
|
80
82
|
commits: CommitsPanel;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codewatch.d.ts","sourceRoot":"","sources":["../../../src/dashboard/codewatch.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAA8C,KAAK,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAmB,MAAM,yBAAyB,CAAC;AAGzJ,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjE,eAAO,MAAM,kBAAkB,KAAK,CAAC;AACrC,kFAAkF;AAClF,eAAO,MAAM,YAAY,MAAM,CAAC;AAEhC,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1B,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1B,MAAM,EAAE,YAAY,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IAC1B,4DAA4D;IAC5D,OAAO,EAAE,UAAU,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,UAAW,SAAQ,OAAO;IACzC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,kDAAkD;IAClD,iBAAiB,EAAE,OAAO,CAAC;IAC3B;;;;OAIG;IACH,gBAAgB,EAAE,MAAM,CAAC;IACzB,2EAA2E;IAC3E,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,8FAA8F;IAC9F,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,+DAA+D;IAC/D,UAAU,EAAE,OAAO,CAAC;IACpB,4DAA4D;IAC5D,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gEAAgE;IAChE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,YAAY,EAAE,CAAC;IAC/B,sDAAsD;IACtD,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,UAAU,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1D,UAAU,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAClD,qFAAqF;IACrF,cAAc,EAAE,UAAU,EAAE,CAAC;IAC7B,6EAA6E;IAC7E,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,
|
|
1
|
+
{"version":3,"file":"codewatch.d.ts","sourceRoot":"","sources":["../../../src/dashboard/codewatch.ts"],"names":[],"mappings":"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"}
|
|
@@ -113,6 +113,7 @@ export function collectCodewatch(repoRoot, trendRuns = DEFAULT_TREND_RUNS, gitle
|
|
|
113
113
|
// --no-verify and other unhooked commits; gitleaks-missing ones are counted in secretScanning.
|
|
114
114
|
skipped: all.filter((r) => r.kind === 'secrets' && r.status === 'skipped' && !isMissingSkip(r)).map(summarize).reverse(),
|
|
115
115
|
trend: all.filter((r) => r.status !== 'skipped').slice(-trendRuns).map(summarize),
|
|
116
|
+
skippedRuns: all.filter((r) => r.status === 'skipped').length,
|
|
116
117
|
secretScanning: secretScanning(all, gitleaksAvailable),
|
|
117
118
|
commits: all.length ? collectCommits(repoRoot, all, { idOf: (r) => idOf.get(r) ?? null }) : { rows: [], total: 0, truncated: false, timing: { logMs: 0, blameMs: 0, blameCalls: 0 } },
|
|
118
119
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codewatch.js","sourceRoot":"","sources":["../../../src/dashboard/codewatch.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAqD,eAAe,EAAE,MAAM,yBAAyB,CAAC;AACzJ,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,cAAc,EAAqB,MAAM,cAAc,CAAC;AAEjE,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AACrC,kFAAkF;AAClF,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,CAAC;
|
|
1
|
+
{"version":3,"file":"codewatch.js","sourceRoot":"","sources":["../../../src/dashboard/codewatch.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../../src/dashboard/page.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../../src/dashboard/page.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA6nBH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQhD"}
|
|
@@ -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; }
|
|
@@ -28,7 +29,7 @@ table { width:100%; border-collapse:collapse; font-size:13px; } th, td { text-al
|
|
|
28
29
|
th { white-space:nowrap; } td.msg { min-width:280px; } td.count { font-variant-numeric:tabular-nums; font-weight:600; } td code { white-space:normal; overflow-wrap:anywhere; }
|
|
29
30
|
th { color:var(--muted); font-weight:600; } td.num { text-align:right; font-variant-numeric: tabular-nums; }
|
|
30
31
|
.pill { white-space:nowrap; display:inline-block; padding:0 7px; border-radius:10px; font-size:12px; font-weight:600; border:1px solid currentColor; }
|
|
31
|
-
.s-ok { color:var(--ok); } .s-warn, .s-findings, .s-skipped { color:var(--warn); } .s-missing, .s-error { color:var(--bad); }
|
|
32
|
+
.s-ok { color:var(--ok); } .s-info { color:var(--accent); } .s-warn, .s-findings, .s-skipped { color:var(--warn); } .s-missing, .s-error { color:var(--bad); }
|
|
32
33
|
.sev-high { color:var(--high); } .sev-medium { color:var(--medium); } .sev-low { color:var(--low); }
|
|
33
34
|
.stats { display:flex; gap:18px; flex-wrap:wrap; margin:6px 0; } .stat b { display:block; font-size:20px; } .stat span { color:var(--muted); font-size:12px; }
|
|
34
35
|
.fix { color:var(--muted); font-size:12px; overflow-wrap:anywhere; }
|
|
@@ -41,11 +42,25 @@ details.repo { border-top:1px solid var(--line); padding:8px 0; } details.repo >
|
|
|
41
42
|
.chead { color:var(--muted); font-weight:600; } summary.crow { cursor:pointer; list-style:none; } summary.crow::-webkit-details-marker { display:none; } details.cdetails[open] > summary.crow { background:var(--bg); } details.cdetails > .tablewrap { padding:4px 0 8px 18px; }
|
|
42
43
|
@media (max-width: 760px) { .crow { grid-template-columns: 1fr 1fr; } .chead { display:none; } }
|
|
43
44
|
a.rlink { color:var(--accent); } a.rlink:hover { text-decoration:underline; }
|
|
45
|
+
.help { font-size:12px; margin-top:4px; overflow-wrap:anywhere; } .help-links { margin-top:2px; } .help-steps { margin:4px 0; padding-left:18px; } .help code { white-space:normal; overflow-wrap:anywhere; }
|
|
44
46
|
.banner { border:1px solid var(--warn); border-left-width:4px; border-radius:6px; padding:10px 12px; margin:4px 0 12px; background:color-mix(in srgb, var(--warn) 10%, var(--card)); }
|
|
45
47
|
.banner strong { color:var(--text); display:block; margin-bottom:4px; } .banner .fix code { white-space:normal; overflow-wrap:anywhere; } .note { color:var(--muted); font-size:12px; margin-top:10px; }
|
|
46
48
|
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
49
|
svg.trend line { stroke:var(--line); }
|
|
48
50
|
.repo-title { font-size:12px; color:var(--muted); margin-bottom:6px; word-break:break-all; }
|
|
51
|
+
.summary { display:grid; gap:14px; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
|
|
52
|
+
@media (min-width: 641px) and (max-width: 1040px) { .summary { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
|
53
|
+
@media (max-width: 640px) { .summary { grid-template-columns: 1fr; gap:10px; } }
|
|
54
|
+
.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); }
|
|
55
|
+
.scard .k { color:var(--muted); font-size:12px; font-weight:600; text-transform:uppercase; letter-spacing:.04em; }
|
|
56
|
+
.scard .v { font-size:28px; font-weight:700; line-height:1.15; font-variant-numeric:tabular-nums; overflow-wrap:anywhere; }
|
|
57
|
+
.scard .v .unit { font-size:13px; font-weight:600; color:var(--muted); margin:0 10px 0 4px; }
|
|
58
|
+
.scard .d { color:var(--muted); font-size:13px; overflow-wrap:anywhere; display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden; }
|
|
59
|
+
.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; }
|
|
60
|
+
.empty-line { color:var(--muted); margin:10px 0 4px; font-size:13px; overflow-wrap:anywhere; } .empty-line b { color:var(--text); }
|
|
61
|
+
.changed { outline:2px solid var(--accent); outline-offset:1px; border-radius:4px; }
|
|
62
|
+
@keyframes prism-flash { from { background-color:color-mix(in srgb, var(--accent) 35%, transparent); } to { background-color:transparent; } }
|
|
63
|
+
@media (prefers-reduced-motion: no-preference) { .changed { animation:prism-flash 2.5s ease-out 1; } }
|
|
49
64
|
`;
|
|
50
65
|
const SCRIPT = `
|
|
51
66
|
'use strict';
|
|
@@ -68,6 +83,21 @@ function table(head, rows) {
|
|
|
68
83
|
}
|
|
69
84
|
const num = (v) => el('td', { class: 'num' }, txt(v));
|
|
70
85
|
|
|
86
|
+
/** Repo labels (a parent path when two repos share a name), set on each load from data.repos. */
|
|
87
|
+
let LABELS = new Map();
|
|
88
|
+
const labelOf = (r) => LABELS.get(r.path) || txt(r.name);
|
|
89
|
+
|
|
90
|
+
/** Sections with no data, each collected as one line into the "Nothing recorded yet" card. */
|
|
91
|
+
let EMPTY = [];
|
|
92
|
+
function emptyLine(title, ...parts) { EMPTY.push(el('div', { class: 'slim-line' }, el('b', {}, title), ': ', ...parts)); return null; }
|
|
93
|
+
/** The same one line, inside a card that has other content. */
|
|
94
|
+
const inlineEmpty = (title, ...parts) => el('p', { class: 'empty-line' }, el('b', {}, title), ': ', ...parts);
|
|
95
|
+
|
|
96
|
+
/** Values compared between loads: key -> text. A changed value is outlined. */
|
|
97
|
+
let PREV = null, CUR = {};
|
|
98
|
+
function track(key, value, node) { CUR[key] = txt(value); node.setAttribute('data-track', key); return node; }
|
|
99
|
+
function changedKeys(prev, cur) { return prev ? Object.keys(cur).filter((k) => k in prev && prev[k] !== cur[k]) : []; }
|
|
100
|
+
|
|
71
101
|
function trendSvg(trend) {
|
|
72
102
|
const w = Math.max(trend.length * 14, 140), h = 48, svg = document.createElementNS(SVG, 'svg');
|
|
73
103
|
svg.setAttribute('class', 'trend'); svg.setAttribute('width', String(w)); svg.setAttribute('height', String(h + 2));
|
|
@@ -98,7 +128,54 @@ function scanBanner(ss) {
|
|
|
98
128
|
}
|
|
99
129
|
|
|
100
130
|
function repoName(r) {
|
|
101
|
-
return el('span', { title: r.path }, r
|
|
131
|
+
return el('span', { title: r.path }, labelOf(r), r.current ? el('span', { class: 'tag' }, 'this repo') : null);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const REVIEW_STATUS = { ok: 'clean', findings: 'findings', error: 'failed', skipped: 'skipped' };
|
|
135
|
+
|
|
136
|
+
/** The summary strip: latest review, spend, setup warnings. Same object as summary in --json. */
|
|
137
|
+
/** Long detail text is clamped to three lines by CSS; the full text stays in the title. */
|
|
138
|
+
const DETAIL_TITLE_MIN = 80;
|
|
139
|
+
const detail = (...parts) => {
|
|
140
|
+
const d = el('div', { class: 'd' }, ...parts);
|
|
141
|
+
const full = d.textContent;
|
|
142
|
+
if (full.length > DETAIL_TITLE_MIN) d.setAttribute('title', full);
|
|
143
|
+
return d;
|
|
144
|
+
};
|
|
145
|
+
/** One summary card: label, large value, one detail line. */
|
|
146
|
+
const scard = (label, value, det) => el('div', { class: 'scard' }, el('div', { class: 'k' }, label), value, det);
|
|
147
|
+
|
|
148
|
+
/** The summary cards: latest review, spend today, spend this month, setup warnings. Same object as summary in --json. */
|
|
149
|
+
function summaryPanel(sum) {
|
|
150
|
+
const s = el('section', { class: 'card wide' }, el('h2', {}, 'Summary'));
|
|
151
|
+
const box = el('div', { class: 'summary' });
|
|
152
|
+
const rv = sum && sum.review;
|
|
153
|
+
if (rv) {
|
|
154
|
+
const ran = rv.status !== 'skipped' && rv.status !== 'error';
|
|
155
|
+
const counts = ran ? rv.high + ' high · ' + rv.medium + ' medium · ' + rv.low + ' low' : txt(REVIEW_STATUS[rv.status] || rv.status);
|
|
156
|
+
const value = ran
|
|
157
|
+
? el('div', { class: 'v' }, el('span', { class: rv.high ? 'sev-high' : '' }, String(rv.high)), el('span', { class: 'unit' }, 'high'),
|
|
158
|
+
el('span', { class: rv.medium ? 'sev-medium' : '' }, String(rv.medium)), el('span', { class: 'unit' }, 'med'),
|
|
159
|
+
el('span', { class: rv.low ? 'sev-low' : '' }, String(rv.low)), el('span', { class: 'unit' }, 'low'))
|
|
160
|
+
: el('div', { class: 'v' }, counts.charAt(0).toUpperCase() + counts.slice(1));
|
|
161
|
+
box.append(scard('Latest codewatch review', track('sum:review', rv.id + ' ' + counts, value),
|
|
162
|
+
detail(el('span', { title: rv.path }, txt(rv.repo)), ' · ', reportLink(rv.id, when(rv.at)), ' · ' + txt(rv.reviewer) + (rv.reason ? ' · ' + txt(rv.reason) : ''))));
|
|
163
|
+
} else {
|
|
164
|
+
box.append(scard('Latest codewatch review', el('div', { class: 'v' }, 'none'), detail('No pre-push review recorded yet.')));
|
|
165
|
+
}
|
|
166
|
+
const sp = sum && sum.spend;
|
|
167
|
+
const ok = sp && sp.status === 'ok';
|
|
168
|
+
const spendCard = (label, key, n, det) => scard(label,
|
|
169
|
+
ok ? track(key, n, el('div', { class: 'v' }, money(n, sp.currency))) : el('div', { class: 'v' }, '-'),
|
|
170
|
+
detail(ok ? det : txt((sp && sp.message) || (sp && sp.status) || 'no codeburn data')));
|
|
171
|
+
box.append(spendCard('Spend today', 'sum:today', sp && sp.today, 'codeburn, all projects'));
|
|
172
|
+
box.append(spendCard('Spend this month', 'sum:month', sp && sp.month, 'codeburn, this calendar month'));
|
|
173
|
+
const w = (sum && sum.warnings) || { count: 0, first: null };
|
|
174
|
+
box.append(scard('Setup warnings',
|
|
175
|
+
track('sum:warnings', w.count, el('div', { class: 'v' + (w.count ? ' s-warn' : ' s-ok') }, String(w.count))),
|
|
176
|
+
detail(w.first ? txt(w.first) : 'Nothing needs attention.')));
|
|
177
|
+
s.append(box);
|
|
178
|
+
return s;
|
|
102
179
|
}
|
|
103
180
|
|
|
104
181
|
const SCAN_LABEL = { ok: ['ok', 'ok'], 'not-scanned': ['warn', 'not scanned'], 'no-verify': ['warn', 'no-verify'], flagged: ['missing', 'secrets found'], none: ['skipped', '-'] };
|
|
@@ -170,68 +247,96 @@ function codewatchDetail(repo) {
|
|
|
170
247
|
el('div', { class: 'stat' }, el('b', {}, cw.gitleaksBlocks.length), el('span', {}, 'gitleaks blocks')),
|
|
171
248
|
el('div', { class: 'stat' }, el('b', {}, cw.skipped.length), el('span', {}, 'pushed without a scan'))));
|
|
172
249
|
|
|
173
|
-
s.append(
|
|
174
|
-
s.append(commitsList(cw.commits));
|
|
250
|
+
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.'));
|
|
251
|
+
else s.append(el('h3', {}, 'Commits'), commitsList(cw.commits));
|
|
175
252
|
|
|
176
|
-
s.append(
|
|
177
|
-
s.append(scansList(cw.scans));
|
|
253
|
+
if (!cw.scans.length) s.append(inlineEmpty('Scans', 'no pre-push reviews yet.'));
|
|
254
|
+
else s.append(el('h3', {}, 'Scans (last ' + cw.scans.length + ')'), scansList(cw.scans));
|
|
178
255
|
|
|
179
|
-
|
|
180
|
-
|
|
256
|
+
const skipped = cw.skippedRuns || 0;
|
|
257
|
+
if (!cw.trend.length) {
|
|
258
|
+
// By design: a skipped run (nothing new to review, or no gitleaks) has no findings, so it is not charted.
|
|
259
|
+
s.append(inlineEmpty('Trend', 'nothing to chart. ' + (skipped
|
|
260
|
+
? '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.'
|
|
261
|
+
: 'No run has finished yet.')));
|
|
262
|
+
} else {
|
|
263
|
+
s.append(el('h3', {}, 'Trend (last ' + cw.trend.length + ' runs)'), trendSvg(cw.trend));
|
|
264
|
+
if (skipped) s.append(el('p', { class: 'note' }, skipped + ' skipped run' + (skipped === 1 ? ' is' : 's are') + ' not charted: they have no findings.'));
|
|
265
|
+
}
|
|
181
266
|
|
|
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)])));
|
|
267
|
+
if (!cw.gitleaksBlocks.length) s.append(inlineEmpty('Gitleaks blocks', 'no commits blocked.'));
|
|
268
|
+
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
269
|
|
|
186
|
-
s.append(
|
|
187
|
-
if (!cw.skipped.length) s.append(el('p', { class: 'empty' }, 'None.'));
|
|
270
|
+
if (!cw.skipped.length) s.append(inlineEmpty('Commits pushed without a pre-commit scan', 'none.'));
|
|
188
271
|
else {
|
|
272
|
+
s.append(el('h3', {}, 'Commits pushed without a pre-commit scan'));
|
|
189
273
|
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
274
|
s.append(el('p', { class: 'note' }, 'Usually git commit --no-verify; rebases and cherry-picks also show up here.'));
|
|
191
275
|
}
|
|
192
276
|
return s;
|
|
193
277
|
}
|
|
194
278
|
|
|
279
|
+
/** "29 other repos: not set up", expanding to one line per repo. key keeps it open across refreshes. */
|
|
280
|
+
function otherRepos(key, list, what, line) {
|
|
281
|
+
if (!list.length) return null;
|
|
282
|
+
return el('details', { class: 'missing-group', 'data-key': key },
|
|
283
|
+
el('summary', {}, list.length + ' other repo' + (list.length === 1 ? '' : 's') + ': ' + what),
|
|
284
|
+
el('ul', { class: 'sec-findings' }, list.map((r) => el('li', {}, repoName(r), line(r)))));
|
|
285
|
+
}
|
|
286
|
+
|
|
195
287
|
function codewatchPanel(data) {
|
|
196
|
-
const
|
|
288
|
+
const rows = data.overview.codewatch;
|
|
197
289
|
const banner = scanBanner(data.machine.secretScanning);
|
|
290
|
+
if (!rows.length && !banner) {
|
|
291
|
+
const sg = (data.suggestions || []).find((x) => x.id === 'codewatch-global');
|
|
292
|
+
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.');
|
|
293
|
+
return emptyLine('Codewatch', "isn't enabled in any repo yet. Run ", el('code', {}, 'prism-cli enable codewatch'), ' inside a repo.');
|
|
294
|
+
}
|
|
295
|
+
const s = el('section', { class: 'card wide' }, el('h2', {}, 'Codewatch'));
|
|
198
296
|
if (banner) s.append(banner);
|
|
199
|
-
const rows = data.overview.codewatch;
|
|
200
297
|
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.'));
|
|
298
|
+
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
299
|
return s;
|
|
205
300
|
}
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
r
|
|
301
|
+
const cur = data.usage && data.usage.currency;
|
|
302
|
+
const count = (key, n, cls) => track(key, n, el('td', { class: 'count' + (n ? ' ' + cls : '') }, n));
|
|
303
|
+
s.append(table(['Repo', 'High', 'Medium', 'Low', 'Last run', 'Reviewer', 'Unscanned', 'Gitleaks', 'Spend (month)'], rows.map((r) => [
|
|
304
|
+
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'),
|
|
305
|
+
track('cw:last:' + r.path, r.lastRunAt, el('td', {}, r.lastRunAt ? (r.lastReviewId ? reportLink(r.lastReviewId, when(r.lastRunAt)) : when(r.lastRunAt)) : 'never')),
|
|
306
|
+
txt(r.reviewer || '-'), count('cw:u:' + r.path, r.unscannedCommits, 'sev-medium'),
|
|
210
307
|
r.gitleaksWarning ? pill('warn', 'not scanned') : '-',
|
|
308
|
+
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
309
|
])));
|
|
212
310
|
const byPath = Object.fromEntries(data.repos.map((r) => [r.path, r]));
|
|
213
311
|
s.append(el('h3', {}, 'Per repo'));
|
|
214
312
|
for (const row of rows) {
|
|
215
313
|
const repo = byPath[row.path]; if (!repo) continue;
|
|
216
314
|
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
|
|
315
|
+
const d = el('details', repo.current ? { class: 'repo', open: '', 'data-key': 'cw:' + repo.path } : { class: 'repo', 'data-key': 'cw:' + repo.path },
|
|
316
|
+
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
317
|
repo.current ? el('span', { class: 'tag' }, 'this repo') : null),
|
|
220
318
|
el('div', { class: 'repo-title' }, repo.path));
|
|
221
319
|
d.append(codewatchDetail(repo));
|
|
222
320
|
s.append(d);
|
|
223
321
|
}
|
|
322
|
+
const shown = new Set(rows.map((r) => r.path));
|
|
323
|
+
const rest = data.overview.setup.filter((r) => !shown.has(r.path));
|
|
324
|
+
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)));
|
|
325
|
+
if (other) s.append(other);
|
|
224
326
|
return s;
|
|
225
327
|
}
|
|
226
328
|
|
|
227
329
|
function usagePanel(u) {
|
|
330
|
+
if (u.status === 'not-installed' || u.status === 'no-data') {
|
|
331
|
+
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)), '.'));
|
|
332
|
+
}
|
|
228
333
|
const s = el('section', { class: 'card' }, el('h2', {}, 'Usage (codeburn)'));
|
|
229
334
|
if (u.status !== 'ok') {
|
|
230
|
-
s.append(el('p', { class: 'empty' }, pill(
|
|
335
|
+
s.append(el('p', { class: 'empty' }, pill('missing', u.status), ' ', txt(u.message)));
|
|
231
336
|
} else {
|
|
232
337
|
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'))));
|
|
338
|
+
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')),
|
|
339
|
+
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
340
|
s.append(el('h3', {}, 'Top models this month'));
|
|
236
341
|
s.append(table(['Model', 'Cost', 'Calls'], u.topModels.map((m) => [m.name, num(money(m.cost, u.currency)), num(m.calls)])));
|
|
237
342
|
s.append(el('h3', {}, 'Top projects this month'));
|
|
@@ -254,9 +359,19 @@ function suggestionCallout(sg) {
|
|
|
254
359
|
el('div', { class: 'fix' }, 'Applies to every repo on this machine, including new clones. Repos can opt out with ', el('code', {}, 'git config prism.codewatch false'), '.'));
|
|
255
360
|
}
|
|
256
361
|
|
|
362
|
+
/** A row's help: the text, any steps and commands, then its doc links (https only), each opening in a new tab. */
|
|
363
|
+
function helpBlock(h) {
|
|
364
|
+
const links = (h.links || []).filter((l) => /^https:\\/\\//.test(txt(l.url)))
|
|
365
|
+
.map((l) => el('a', { href: txt(l.url), class: 'rlink', target: '_blank', rel: 'noopener noreferrer' }, txt(l.label)));
|
|
366
|
+
const steps = (h.steps || []).length ? el('ol', { class: 'help-steps' }, h.steps.map((x) => el('li', {}, txt(x)))) : null;
|
|
367
|
+
const cmds = (h.commands || []).length ? el('div', { class: 'help-links' }, 'Commands: ', h.commands.flatMap((c, n) => n ? [', ', el('code', {}, txt(c))] : [el('code', {}, txt(c))])) : null;
|
|
368
|
+
return el('div', { class: 'help' }, txt(h.text), steps, cmds, links.length ? el('div', { class: 'help-links' }, links.flatMap((a, n) => n ? [' · ', a] : [a])) : null);
|
|
369
|
+
}
|
|
370
|
+
|
|
257
371
|
function setupPanel(data) {
|
|
258
372
|
const s = el('section', { class: 'card' }, el('h2', {}, 'Setup checklist'));
|
|
259
|
-
const rows = (items) => items.map((i) => [pill(i.status), i.label, el('div', {}, i.detail,
|
|
373
|
+
const rows = (items) => items.map((i) => [pill(i.status), i.label, el('div', {}, i.detail,
|
|
374
|
+
i.help ? helpBlock(i.help) : i.fix ? el('div', { class: 'fix' }, 'Fix: ' + i.fix) : null)]);
|
|
260
375
|
s.append(el('h3', {}, 'This machine'), table(['', 'Check', 'Detail'], rows(data.machine.setup)));
|
|
261
376
|
const repos = data.overview.setup, c = data.overview.counts;
|
|
262
377
|
s.append(el('h3', {}, 'Per repo: codewatch (' + c.codewatch + ' of ' + c.repos + ' installed)'));
|
|
@@ -271,23 +386,25 @@ function setupPanel(data) {
|
|
|
271
386
|
];
|
|
272
387
|
const sg = (data.suggestions || []).find((x) => x.id === 'codewatch-global');
|
|
273
388
|
if (sg) s.append(suggestionCallout(sg));
|
|
274
|
-
//
|
|
389
|
+
// Shown: the current repo, repos with activity (listed first), and repos with something set up.
|
|
275
390
|
const shown = repos.filter((r) => !r.collapsed);
|
|
276
391
|
const hidden = repos.filter((r) => r.collapsed);
|
|
277
392
|
if (shown.length) s.append(table(['Repo', 'Codewatch', 'Security Agent'], shown.map(rowOf)));
|
|
278
393
|
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
|
-
|
|
394
|
+
const optedOut = hidden.filter((r) => r.status === 'opted-out').length;
|
|
395
|
+
const group = el('details', { class: 'missing-group', 'data-key': 'setup:other' },
|
|
396
|
+
el('summary', {}, hidden.length + ' other repo' + (hidden.length === 1 ? '' : 's') + ': not set up' + (optedOut ? ' (' + optedOut + ' opted out of codewatch)' : '')),
|
|
397
|
+
table(['Repo', 'Codewatch', 'Security Agent'], hidden.map(rowOf)));
|
|
398
|
+
if (hidden.length) s.append(group);
|
|
284
399
|
return s;
|
|
285
400
|
}
|
|
286
401
|
|
|
287
|
-
|
|
402
|
+
const SEC_LABEL = { ok: 'ok', 'in-use': 'in use', missing: 'missing' };
|
|
403
|
+
|
|
404
|
+
/** Security Agent per repo: the enable security marker, else saved results (local evidence; the page makes no AWS calls). */
|
|
288
405
|
function securityCell(sec) {
|
|
289
406
|
if (!sec) return el('span', { class: 'fix' }, '-');
|
|
290
|
-
return el('div', {}, pill(sec.status === '
|
|
407
|
+
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
408
|
sec.fix ? el('div', { class: 'fix' }, 'Fix: ', el('code', {}, sec.fix)) : null);
|
|
292
409
|
}
|
|
293
410
|
|
|
@@ -304,13 +421,13 @@ function securityPanel(sec) {
|
|
|
304
421
|
const tms = (sec && sec.threat_models) || [];
|
|
305
422
|
const pts = (sec && sec.pentests) || [];
|
|
306
423
|
if (!rows.length && !tms.length && !pts.length) {
|
|
307
|
-
|
|
308
|
-
return s;
|
|
424
|
+
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
425
|
}
|
|
426
|
+
if (!rows.length) s.append(inlineEmpty('Code reviews', 'none yet. Run ', el('code', {}, (sec && sec.hint) || 'prism-cli security code-review'), '.'));
|
|
310
427
|
for (const r of rows) {
|
|
311
428
|
const counts = r.counts || {};
|
|
312
429
|
const b = el('div', { class: 'sec-repo' });
|
|
313
|
-
b.append(el('div', { class: 'sec-head' }, el('b', {},
|
|
430
|
+
b.append(el('div', { class: 'sec-head' }, el('b', {}, labelOf(r)), r.current ? el('span', { class: 'tag' }, 'this repo') : null));
|
|
314
431
|
b.append(el('div', { class: 'fix' },
|
|
315
432
|
(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
433
|
const pills = el('div', { class: 'sec-pills' });
|
|
@@ -348,7 +465,7 @@ function securityPanel(sec) {
|
|
|
348
465
|
if (tms.length) {
|
|
349
466
|
s.append(el('h3', {}, 'Threat models'));
|
|
350
467
|
for (const t of tms) s.append(threatModelBlock(t, (sec && sec.threats_hint) || 'prism-cli security threats'));
|
|
351
|
-
}
|
|
468
|
+
} else s.append(inlineEmpty('Threat models', 'none yet. Run ', el('code', {}, 'prism-cli security threat-model'), '.'));
|
|
352
469
|
return s;
|
|
353
470
|
}
|
|
354
471
|
|
|
@@ -359,7 +476,7 @@ const strideText = (list) => (list || []).map((x) => txt(x).toLowerCase().replac
|
|
|
359
476
|
/** One cached threat model per repo, in the code review blocks' compact style. */
|
|
360
477
|
function threatModelBlock(t, hint) {
|
|
361
478
|
const b = el('div', { class: 'sec-repo' });
|
|
362
|
-
b.append(el('div', { class: 'sec-head' }, el('b', {},
|
|
479
|
+
b.append(el('div', { class: 'sec-head' }, el('b', {}, labelOf(t)), t.current ? el('span', { class: 'tag' }, 'this repo') : null));
|
|
363
480
|
const docs = (t.scope_docs || []).length;
|
|
364
481
|
b.append(el('div', { class: 'fix' },
|
|
365
482
|
(t.pending ? 'started ' + when(t.started_at) : when(t.finished_at)) + ' · commit ' + txt(t.commit).slice(0, 12)
|
|
@@ -399,12 +516,12 @@ function threatModelBlock(t, hint) {
|
|
|
399
516
|
/** Penetration-test rows, in the same Security card below the code-review rows. */
|
|
400
517
|
function securityPentestRows(s, sec) {
|
|
401
518
|
const rows = (sec && sec.pentests) || [];
|
|
402
|
-
if (!rows.length) return;
|
|
519
|
+
if (!rows.length) { s.append(inlineEmpty('Penetration tests', 'none yet. Run ', el('code', {}, 'prism-cli security pentest'), '.')); return; }
|
|
403
520
|
s.append(el('h3', {}, 'Penetration tests'));
|
|
404
521
|
for (const r of rows) {
|
|
405
522
|
const counts = r.counts || {};
|
|
406
523
|
const b = el('div', { class: 'sec-repo' });
|
|
407
|
-
b.append(el('div', { class: 'sec-head' }, el('b', {},
|
|
524
|
+
b.append(el('div', { class: 'sec-head' }, el('b', {}, labelOf(r)), r.current ? el('span', { class: 'tag' }, 'this repo') : null));
|
|
408
525
|
b.append(el('div', { class: 'fix' },
|
|
409
526
|
(r.pending ? 'started ' + when(r.started_at) : when(r.finished_at)) + ' · ' + txt(r.target_host) + ' · job ' + txt(r.job_id).slice(0, 12)));
|
|
410
527
|
const pills = el('div', { class: 'sec-pills' });
|
|
@@ -444,12 +561,9 @@ const AUDIT_SEVERITIES = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'];
|
|
|
444
561
|
|
|
445
562
|
/** The latest aws check per account, in the Security panel's compact style. Local results only. */
|
|
446
563
|
function awsPanel(aws) {
|
|
447
|
-
const s = el('section', { class: 'card' }, el('h2', {}, 'AWS account'));
|
|
448
564
|
const rows = (aws && aws.accounts) || [];
|
|
449
|
-
if (!rows.length) {
|
|
450
|
-
|
|
451
|
-
return s;
|
|
452
|
-
}
|
|
565
|
+
if (!rows.length) return emptyLine('AWS account', 'no checks yet. Run ', el('code', {}, (aws && aws.hint) || 'prism-cli aws check'), ' (read-only).');
|
|
566
|
+
const s = el('section', { class: 'card' }, el('h2', {}, 'AWS account'));
|
|
453
567
|
for (const a of rows) {
|
|
454
568
|
const b = el('div', { class: 'sec-repo' });
|
|
455
569
|
b.append(el('div', { class: 'sec-head' }, el('b', {}, txt(a.account)), a.alias ? el('span', { class: 'tag' }, txt(a.alias)) : null));
|
|
@@ -473,22 +587,56 @@ function awsPanel(aws) {
|
|
|
473
587
|
return s;
|
|
474
588
|
}
|
|
475
589
|
|
|
590
|
+
/** Auto-refresh period while the tab is visible; paused while hidden. */
|
|
591
|
+
const AUTO_REFRESH_MS = 60000;
|
|
592
|
+
let lastLoad = 0, loading = false;
|
|
593
|
+
|
|
476
594
|
async function load() {
|
|
595
|
+
if (loading) return;
|
|
596
|
+
loading = true;
|
|
597
|
+
try { await render(); } finally { loading = false; lastLoad = Date.now(); }
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
async function render() {
|
|
477
601
|
const main = document.getElementById('main'), meta = document.getElementById('meta');
|
|
602
|
+
const before = meta.textContent;
|
|
478
603
|
meta.textContent = 'loading...';
|
|
479
604
|
let data;
|
|
480
605
|
try {
|
|
481
606
|
const res = await fetch('/api/data', { credentials: 'same-origin', cache: 'no-store' });
|
|
482
607
|
if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
483
608
|
data = await res.json();
|
|
484
|
-
} catch (e) {
|
|
609
|
+
} catch (e) {
|
|
610
|
+
// Keep the last good view; say the refresh failed.
|
|
611
|
+
meta.textContent = 'failed to load: ' + e.message + (before && before !== 'loading...' ? ' · showing ' + before : '');
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
// Open/closed state of keyed <details> survives the redraw.
|
|
615
|
+
const openState = new Map([...main.querySelectorAll('details[data-key]')].map((d) => [d.getAttribute('data-key'), d.open]));
|
|
616
|
+
LABELS = new Map((data.repos || []).map((r) => [r.path, r.label || r.name]));
|
|
617
|
+
EMPTY = []; CUR = {};
|
|
485
618
|
main.replaceChildren();
|
|
486
619
|
if (data.scope.error) main.append(el('section', { class: 'card wide' }, pill('error'), ' ', data.scope.error));
|
|
487
|
-
|
|
620
|
+
const panels = [summaryPanel(data.summary), codewatchPanel(data), usagePanel(data.usage), setupPanel(data), securityPanel(data.security), awsPanel(data.aws)];
|
|
621
|
+
main.append(...panels.filter(Boolean));
|
|
622
|
+
if (EMPTY.length) main.append(el('section', { class: 'card wide' }, el('h2', {}, 'Nothing recorded yet'), ...EMPTY));
|
|
623
|
+
for (const d of main.querySelectorAll('details[data-key]')) {
|
|
624
|
+
const k = d.getAttribute('data-key');
|
|
625
|
+
if (openState.has(k)) d.open = openState.get(k);
|
|
626
|
+
}
|
|
627
|
+
const changed = new Set(changedKeys(PREV, CUR));
|
|
628
|
+
for (const n of main.querySelectorAll('[data-track]')) {
|
|
629
|
+
const k = n.getAttribute('data-track');
|
|
630
|
+
if (changed.has(k)) { n.classList.add('changed'); n.setAttribute('title', 'changed since the previous load (was ' + PREV[k] + ')'); }
|
|
631
|
+
}
|
|
632
|
+
PREV = CUR;
|
|
488
633
|
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)' : '')
|
|
634
|
+
meta.textContent = c.repos + ' repos under ' + txt(data.scope.root_display) + ' · ' + c.codewatch + ' with codewatch' + (data.scope.truncated ? ' (scan capped)' : '')
|
|
635
|
+
+ ' · updated ' + when(data.generated_at) + (changed.size ? ' · ' + changed.size + ' value' + (changed.size === 1 ? '' : 's') + ' changed (outlined)' : '') + ' · refreshes every minute while visible';
|
|
490
636
|
}
|
|
491
637
|
document.getElementById('refresh').addEventListener('click', load);
|
|
638
|
+
setInterval(() => { if (document.visibilityState === 'visible' && Date.now() - lastLoad >= AUTO_REFRESH_MS - 1000) load(); }, AUTO_REFRESH_MS / 4);
|
|
639
|
+
document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible' && Date.now() - lastLoad >= AUTO_REFRESH_MS) load(); });
|
|
492
640
|
load();
|
|
493
641
|
`;
|
|
494
642
|
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuDb,CAAC;AAEF,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgkBd,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,wFAAwF;IACxF,OAAO;;8CAEqC,KAAK,KAAK,KAAK;;;iBAG5C,KAAK,KAAK,MAAM,yBAAyB,CAAC;AAC3D,CAAC"}
|