@reddoorla/maintenance 0.69.0 → 0.72.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/README.md +15 -14
- package/dist/airtable-U2GTDTK5.js +9 -0
- package/dist/{announce-72R3LRNT.js → announce-HJCGEVJA.js} +6 -6
- package/dist/blux-FKZYMKLF.js +2205 -0
- package/dist/blux-FKZYMKLF.js.map +1 -0
- package/dist/{bump-deps-QNM3H4GI.js → bump-deps-GZEJAUKI.js} +3 -2
- package/dist/bump-deps-GZEJAUKI.js.map +1 -0
- package/dist/chunk-7MJBTV2R.js +145 -0
- package/dist/chunk-7MJBTV2R.js.map +1 -0
- package/dist/{chunk-2DCA7FHZ.js → chunk-7P7YETHG.js} +34 -12
- package/dist/chunk-7P7YETHG.js.map +1 -0
- package/dist/{chunk-42KKVLYN.js → chunk-APTAITXZ.js} +10 -2
- package/dist/chunk-APTAITXZ.js.map +1 -0
- package/dist/{chunk-6G7BC5SN.js → chunk-BHE5CUWI.js} +392 -7
- package/dist/chunk-BHE5CUWI.js.map +1 -0
- package/dist/{chunk-XWPZ46IN.js → chunk-BJCA7O4A.js} +2 -2
- package/dist/{chunk-C3PDS623.js → chunk-BWKK2ZXS.js} +67 -8
- package/dist/chunk-BWKK2ZXS.js.map +1 -0
- package/dist/{chunk-45AUGDFR.js → chunk-D4FVAIEJ.js} +6 -20
- package/dist/chunk-D4FVAIEJ.js.map +1 -0
- package/dist/{chunk-YQMYQAJX.js → chunk-GI4M2AMO.js} +2 -2
- package/dist/{chunk-OTBA7DON.js → chunk-GYNRZCM6.js} +7 -2
- package/dist/chunk-GYNRZCM6.js.map +1 -0
- package/dist/{chunk-6MT5W6UF.js → chunk-HR5SONJC.js} +6 -3
- package/dist/chunk-HR5SONJC.js.map +1 -0
- package/dist/{chunk-6JFOAPW4.js → chunk-JCITWZQR.js} +51 -19
- package/dist/chunk-JCITWZQR.js.map +1 -0
- package/dist/{chunk-GKSXZIMX.js → chunk-JGLAWXUC.js} +77 -3
- package/dist/chunk-JGLAWXUC.js.map +1 -0
- package/dist/{chunk-E2GKKUEX.js → chunk-KLB2OLPW.js} +26 -6
- package/dist/chunk-KLB2OLPW.js.map +1 -0
- package/dist/{chunk-RINXPU3H.js → chunk-KLVMJYZZ.js} +2 -2
- package/dist/chunk-KRDLHAKK.js +17 -0
- package/dist/chunk-KRDLHAKK.js.map +1 -0
- package/dist/{chunk-AESJBEM7.js → chunk-KRHPS3R4.js} +3 -3
- package/dist/{chunk-LYCKN7ZR.js → chunk-LJPCMXR7.js} +8 -5
- package/dist/chunk-LJPCMXR7.js.map +1 -0
- package/dist/{chunk-O6G4HHMF.js → chunk-NRISG4XD.js} +308 -104
- package/dist/chunk-NRISG4XD.js.map +1 -0
- package/dist/chunk-PKVZMFEB.js +315 -0
- package/dist/chunk-PKVZMFEB.js.map +1 -0
- package/dist/chunk-SL24DCGC.js +41 -0
- package/dist/chunk-SL24DCGC.js.map +1 -0
- package/dist/{chunk-A5KYRGQA.js → chunk-SQJ53Z7R.js} +4 -4
- package/dist/cli/bin.js +63 -19
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.d.ts +1 -1
- package/dist/cli/commands/audit.js +11 -9
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/configs/playwright-a11y.d.ts +1 -1
- package/dist/configs/playwright-a11y.js +1 -3
- package/dist/{convert-to-pnpm-XH7NYRVK.js → convert-to-pnpm-SVFDLXZS.js} +3 -2
- package/dist/convert-to-pnpm-SVFDLXZS.js.map +1 -0
- package/dist/{digest-Q2TAQHXF.js → digest-VX7Q4MNF.js} +8 -8
- package/dist/{ensure-site-2PF522VL.js → ensure-site-O7G5ZJS7.js} +2 -2
- package/dist/forms/index.d.ts +5 -2
- package/dist/forms/index.js +22 -9
- package/dist/forms/index.js.map +1 -1
- package/dist/{github-signals-CLQC2IPY.js → github-signals-QVW2LB7N.js} +5 -5
- package/dist/health-endpoint-IYR5VBUT.js +54 -0
- package/dist/health-endpoint-IYR5VBUT.js.map +1 -0
- package/dist/index.d.ts +136 -20
- package/dist/index.js +207 -64
- package/dist/index.js.map +1 -1
- package/dist/{init-UA6D7GKW.js → init-CU6PVZRG.js} +9 -5
- package/dist/init-CU6PVZRG.js.map +1 -0
- package/dist/{launch-AENAS2O2.js → launch-JWXCTJRG.js} +9 -9
- package/dist/{lighthouse-airtable-UBSXKHAQ.js → lighthouse-airtable-J5TLASJC.js} +3 -3
- package/dist/{onboard-X6KJ5Z2V.js → onboard-6UQE5QIO.js} +2 -2
- package/dist/{orchestrate-GADNFGDH.js → orchestrate-ZDH24Z6Y.js} +5 -5
- package/dist/{preflight-D5RQGX4V.js → preflight-BNTMWTPG.js} +7 -7
- package/dist/probe-DJAMLVTT.js +46 -0
- package/dist/probe-DJAMLVTT.js.map +1 -0
- package/dist/recipes/sync-configs.d.ts +1 -1
- package/dist/{renovate-dispatch-LG4RJGDY.js → renovate-dispatch-C3ALLACX.js} +2 -2
- package/dist/{report-3FJ224H5.js → report-YLFSGNWR.js} +12 -12
- package/dist/run-migration-LJNNZWIV.js +205 -0
- package/dist/run-migration-LJNNZWIV.js.map +1 -0
- package/dist/{self-updating-JTTFOJ7Q.js → self-updating-64FNRQB5.js} +2 -2
- package/dist/{selftest-55OJIWEB.js → selftest-7DPNGL6R.js} +12 -9
- package/dist/selftest-7DPNGL6R.js.map +1 -0
- package/dist/smoke-suite-TWILWDSF.js +54 -0
- package/dist/smoke-suite-TWILWDSF.js.map +1 -0
- package/dist/{svelte-codemods-KJLNH6FQ.js → svelte-codemods-RVDTTXFD.js} +3 -2
- package/dist/svelte-codemods-RVDTTXFD.js.map +1 -0
- package/dist/{sync-configs-TIBO6DUJ.js → sync-configs-BUUD6L5Q.js} +3 -2
- package/dist/sync-configs-BUUD6L5Q.js.map +1 -0
- package/dist/{types-Dg4mHvyf.d.ts → types-0jreMCF5.d.ts} +2 -2
- package/dist/{upgrade-TPTE26FG.js → upgrade-HHKWYJRN.js} +3 -2
- package/dist/upgrade-HHKWYJRN.js.map +1 -0
- package/dist/{websites-6YTT5ZTO.js → websites-EH2ULELZ.js} +8 -2
- package/dist/{write-audits-to-airtable-IWOO4FFH.js → write-audits-to-airtable-LZDYTNX5.js} +4 -4
- package/package.json +4 -1
- package/dist/airtable-WGQQ3KAD.js +0 -9
- package/dist/bump-deps-QNM3H4GI.js.map +0 -1
- package/dist/chunk-2DCA7FHZ.js.map +0 -1
- package/dist/chunk-42KKVLYN.js.map +0 -1
- package/dist/chunk-45AUGDFR.js.map +0 -1
- package/dist/chunk-6G7BC5SN.js.map +0 -1
- package/dist/chunk-6JFOAPW4.js.map +0 -1
- package/dist/chunk-6MT5W6UF.js.map +0 -1
- package/dist/chunk-C3PDS623.js.map +0 -1
- package/dist/chunk-E2GKKUEX.js.map +0 -1
- package/dist/chunk-GKSXZIMX.js.map +0 -1
- package/dist/chunk-LYCKN7ZR.js.map +0 -1
- package/dist/chunk-O6G4HHMF.js.map +0 -1
- package/dist/chunk-OTBA7DON.js.map +0 -1
- package/dist/convert-to-pnpm-XH7NYRVK.js.map +0 -1
- package/dist/init-UA6D7GKW.js.map +0 -1
- package/dist/selftest-55OJIWEB.js.map +0 -1
- package/dist/svelte-codemods-KJLNH6FQ.js.map +0 -1
- package/dist/sync-configs-TIBO6DUJ.js.map +0 -1
- package/dist/upgrade-TPTE26FG.js.map +0 -1
- /package/dist/{airtable-WGQQ3KAD.js.map → airtable-U2GTDTK5.js.map} +0 -0
- /package/dist/{announce-72R3LRNT.js.map → announce-HJCGEVJA.js.map} +0 -0
- /package/dist/{chunk-XWPZ46IN.js.map → chunk-BJCA7O4A.js.map} +0 -0
- /package/dist/{chunk-YQMYQAJX.js.map → chunk-GI4M2AMO.js.map} +0 -0
- /package/dist/{chunk-RINXPU3H.js.map → chunk-KLVMJYZZ.js.map} +0 -0
- /package/dist/{chunk-AESJBEM7.js.map → chunk-KRHPS3R4.js.map} +0 -0
- /package/dist/{chunk-A5KYRGQA.js.map → chunk-SQJ53Z7R.js.map} +0 -0
- /package/dist/{digest-Q2TAQHXF.js.map → digest-VX7Q4MNF.js.map} +0 -0
- /package/dist/{ensure-site-2PF522VL.js.map → ensure-site-O7G5ZJS7.js.map} +0 -0
- /package/dist/{github-signals-CLQC2IPY.js.map → github-signals-QVW2LB7N.js.map} +0 -0
- /package/dist/{launch-AENAS2O2.js.map → launch-JWXCTJRG.js.map} +0 -0
- /package/dist/{lighthouse-airtable-UBSXKHAQ.js.map → lighthouse-airtable-J5TLASJC.js.map} +0 -0
- /package/dist/{onboard-X6KJ5Z2V.js.map → onboard-6UQE5QIO.js.map} +0 -0
- /package/dist/{orchestrate-GADNFGDH.js.map → orchestrate-ZDH24Z6Y.js.map} +0 -0
- /package/dist/{preflight-D5RQGX4V.js.map → preflight-BNTMWTPG.js.map} +0 -0
- /package/dist/{renovate-dispatch-LG4RJGDY.js.map → renovate-dispatch-C3ALLACX.js.map} +0 -0
- /package/dist/{report-3FJ224H5.js.map → report-YLFSGNWR.js.map} +0 -0
- /package/dist/{self-updating-JTTFOJ7Q.js.map → self-updating-64FNRQB5.js.map} +0 -0
- /package/dist/{websites-6YTT5ZTO.js.map → websites-EH2ULELZ.js.map} +0 -0
- /package/dist/{write-audits-to-airtable-IWOO4FFH.js.map → write-audits-to-airtable-LZDYTNX5.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import "./chunk-
|
|
1
|
+
import "./chunk-KRHPS3R4.js";
|
|
2
2
|
import {
|
|
3
3
|
approveBlockers
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-KLB2OLPW.js";
|
|
5
|
+
import {
|
|
6
|
+
draftReportForSite
|
|
7
|
+
} from "./chunk-NRISG4XD.js";
|
|
8
|
+
import "./chunk-KLVMJYZZ.js";
|
|
5
9
|
import {
|
|
6
10
|
findDueReports
|
|
7
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-D4FVAIEJ.js";
|
|
8
12
|
import {
|
|
9
13
|
upgradeSvelte4to5
|
|
10
14
|
} from "./chunk-ZO7WNY57.js";
|
|
@@ -12,7 +16,7 @@ import {
|
|
|
12
16
|
DEFAULT_INIT_STEPS,
|
|
13
17
|
a11yFixturesPage,
|
|
14
18
|
init
|
|
15
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-APTAITXZ.js";
|
|
16
20
|
import {
|
|
17
21
|
convertToPnpm
|
|
18
22
|
} from "./chunk-2X322ZES.js";
|
|
@@ -20,32 +24,32 @@ import {
|
|
|
20
24
|
svelteCodemods
|
|
21
25
|
} from "./chunk-BTEBRTW4.js";
|
|
22
26
|
import "./chunk-J7SZQUCW.js";
|
|
27
|
+
import "./chunk-7MJBTV2R.js";
|
|
28
|
+
import "./chunk-PKVZMFEB.js";
|
|
29
|
+
import "./chunk-KRDLHAKK.js";
|
|
23
30
|
import {
|
|
24
31
|
onboard,
|
|
25
32
|
selfCaretRange,
|
|
26
33
|
selfPackageVersion
|
|
27
34
|
} from "./chunk-ZYBOT5PM.js";
|
|
28
|
-
import {
|
|
29
|
-
draftReportForSite
|
|
30
|
-
} from "./chunk-O6G4HHMF.js";
|
|
31
|
-
import "./chunk-RINXPU3H.js";
|
|
32
35
|
import {
|
|
33
36
|
parseAddresses,
|
|
34
37
|
sendApprovedReports,
|
|
35
38
|
withGlobalCc
|
|
36
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-7P7YETHG.js";
|
|
37
40
|
import "./chunk-GECESMVS.js";
|
|
38
41
|
import {
|
|
39
42
|
BLURRED_CID,
|
|
40
43
|
CHECK_CID,
|
|
41
44
|
checklistFor,
|
|
42
45
|
escapeHtml,
|
|
43
|
-
|
|
46
|
+
gatingFields,
|
|
47
|
+
isHealthGateClear,
|
|
44
48
|
isPendingApproval,
|
|
45
49
|
loadBundledImages,
|
|
46
50
|
renderReportHtml,
|
|
47
51
|
safeUrl
|
|
48
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-JCITWZQR.js";
|
|
49
53
|
import "./chunk-WEKL2HYJ.js";
|
|
50
54
|
import {
|
|
51
55
|
bumpDeps
|
|
@@ -66,11 +70,11 @@ import {
|
|
|
66
70
|
runAudits,
|
|
67
71
|
runAuditsAcross,
|
|
68
72
|
securityAudit
|
|
69
|
-
} from "./chunk-
|
|
73
|
+
} from "./chunk-BHE5CUWI.js";
|
|
70
74
|
import "./chunk-GASBX52O.js";
|
|
71
75
|
import "./chunk-YEGHUODU.js";
|
|
72
76
|
import "./chunk-LKGVSM2O.js";
|
|
73
|
-
import "./chunk-
|
|
77
|
+
import "./chunk-GYNRZCM6.js";
|
|
74
78
|
import "./chunk-PH3AGF4Z.js";
|
|
75
79
|
import "./chunk-HIN3P4ZN.js";
|
|
76
80
|
import {
|
|
@@ -82,16 +86,16 @@ import "./chunk-XXTZBPUY.js";
|
|
|
82
86
|
import "./chunk-XTK5VIQB.js";
|
|
83
87
|
import {
|
|
84
88
|
fromAirtableBase
|
|
85
|
-
} from "./chunk-
|
|
89
|
+
} from "./chunk-HR5SONJC.js";
|
|
86
90
|
import "./chunk-U5SZ4FOP.js";
|
|
87
91
|
import {
|
|
88
92
|
SEVERITY_RANK,
|
|
89
93
|
siteSlug
|
|
90
|
-
} from "./chunk-
|
|
94
|
+
} from "./chunk-BWKK2ZXS.js";
|
|
91
95
|
import "./chunk-HN6I2XWI.js";
|
|
92
96
|
import "./chunk-6VAL7XJT.js";
|
|
93
97
|
import "./chunk-EYODPUHV.js";
|
|
94
|
-
import "./chunk-
|
|
98
|
+
import "./chunk-LJPCMXR7.js";
|
|
95
99
|
|
|
96
100
|
// src/recipes/index.ts
|
|
97
101
|
var ALL_RECIPE_NAMES = [
|
|
@@ -102,6 +106,8 @@ var ALL_RECIPE_NAMES = [
|
|
|
102
106
|
"convert-to-pnpm",
|
|
103
107
|
"onboard",
|
|
104
108
|
"a11y-fixtures-page",
|
|
109
|
+
"health-endpoint",
|
|
110
|
+
"smoke-suite",
|
|
105
111
|
"self-updating",
|
|
106
112
|
"init"
|
|
107
113
|
];
|
|
@@ -316,24 +322,59 @@ function securitySection(site) {
|
|
|
316
322
|
<ul class="vuln-list">${sorted.map(advisoryRow).join("")}</ul>
|
|
317
323
|
</div>`;
|
|
318
324
|
}
|
|
325
|
+
function healthPresentation(status) {
|
|
326
|
+
switch (status) {
|
|
327
|
+
case "pass":
|
|
328
|
+
return { cls: "healthy", word: "clear" };
|
|
329
|
+
case "fail":
|
|
330
|
+
return { cls: "attention", word: "blocks" };
|
|
331
|
+
case "n/a":
|
|
332
|
+
return { cls: "na", word: "n/a" };
|
|
333
|
+
default:
|
|
334
|
+
return { cls: "watch", word: "needs you" };
|
|
335
|
+
}
|
|
336
|
+
}
|
|
319
337
|
function checklistBlock(r) {
|
|
320
338
|
const items = checklistFor(r.reportType);
|
|
321
339
|
if (items.length === 0) return "";
|
|
340
|
+
const gating = new Set(gatingFields(r.reportType));
|
|
322
341
|
const rid = escapeHtml(r.id);
|
|
323
|
-
const
|
|
324
|
-
const boxes = items.map((item) => {
|
|
325
|
-
const checked = r.checklist[item.field] === true ? " checked" : "";
|
|
342
|
+
const rows = items.map((item) => {
|
|
326
343
|
const ev = r.autoEvidence?.[item.field];
|
|
327
|
-
const
|
|
328
|
-
|
|
344
|
+
const status = ev?.result ?? "unknown";
|
|
345
|
+
const { cls, word } = healthPresentation(status);
|
|
346
|
+
const advisory = gating.has(item.field) ? "" : ` <span class="advisory">advisory \u2014 never blocks</span>`;
|
|
347
|
+
const note = ev?.note ? ` <span class="check-note">${escapeHtml(ev.note)}</span>` : "";
|
|
348
|
+
return `<li class="check-item"><span class="pill ${cls}" title="${escapeHtml(ev?.note ?? "not yet measured")}">${word}</span> ${escapeHtml(item.label)}${advisory}${note}</li>`;
|
|
329
349
|
}).join("");
|
|
330
|
-
return `<
|
|
350
|
+
return `<ul class="checklist" data-checklist-for="${rid}">${rows}</ul>`;
|
|
331
351
|
}
|
|
332
352
|
function approveButton(r, blocked) {
|
|
333
|
-
const
|
|
353
|
+
const gateClear = isHealthGateClear({
|
|
354
|
+
reportType: r.reportType,
|
|
355
|
+
autoEvidence: r.autoEvidence ?? {}
|
|
356
|
+
});
|
|
357
|
+
const disabled = gateClear && !blocked ? "" : " disabled";
|
|
334
358
|
const blockedAttr = blocked ? ` data-send-blocked="1"` : "";
|
|
335
359
|
return `<button class="approve" data-report-id="${escapeHtml(r.id)}" data-approve-url="${escapeHtml(`/api/reports/${encodeURIComponent(r.id)}/approve`)}"${blockedAttr}${disabled}>Approve</button>`;
|
|
336
360
|
}
|
|
361
|
+
function overrideControl(r) {
|
|
362
|
+
const gateClear = isHealthGateClear({
|
|
363
|
+
reportType: r.reportType,
|
|
364
|
+
autoEvidence: r.autoEvidence ?? {}
|
|
365
|
+
});
|
|
366
|
+
if (gateClear) return "";
|
|
367
|
+
const rid = escapeHtml(r.id);
|
|
368
|
+
const overrideUrl = escapeHtml(`/api/reports/${encodeURIComponent(r.id)}/approve?override=1`);
|
|
369
|
+
return `<div class="override" data-override-for="${rid}">
|
|
370
|
+
<button type="button" class="override-toggle" data-report-id="${rid}">Send anyway\u2026</button>
|
|
371
|
+
<div class="override-form" hidden>
|
|
372
|
+
<input type="text" class="override-reason" placeholder="Reason for overriding the health gate (required)" />
|
|
373
|
+
<button type="button" class="override-submit" data-report-id="${rid}" data-override-url="${overrideUrl}">Confirm override</button>
|
|
374
|
+
<span class="override-status"></span>
|
|
375
|
+
</div>
|
|
376
|
+
</div>`;
|
|
377
|
+
}
|
|
337
378
|
function preflightChip(findings) {
|
|
338
379
|
const fails = findings.filter((f) => f.level === "fail");
|
|
339
380
|
const warns = findings.filter((f) => f.level === "warn");
|
|
@@ -381,7 +422,7 @@ function pendingRow(r, site, now) {
|
|
|
381
422
|
const blocked = findings.some((f) => f.level === "fail");
|
|
382
423
|
const preview = r.renderedHtmlAttachment ? `<a href="${escapeHtml(safeUrl(r.renderedHtmlAttachment.url))}" rel="noopener noreferrer" title="rendered at draft time \u2014 Commentary/subject edits after drafting are not reflected">draft preview \u25B8</a>` : `<span class="muted">no preview yet</span>`;
|
|
383
424
|
const sendLine = sendTimingLine(now);
|
|
384
|
-
return `<li><div class="pending-head"><strong>${type}</strong> <span class="muted">${period}</span> ${preflightChip(findings)} ${preview} ${approveButton(r, blocked)}</div><div class="pending-info">${recipientsLine(site)} ${sendLine}</div>${checklistBlock(r)}</li>`;
|
|
425
|
+
return `<li><div class="pending-head"><strong>${type}</strong> <span class="muted">${period}</span> ${preflightChip(findings)} ${preview} ${approveButton(r, blocked)}</div><div class="pending-info">${recipientsLine(site)} ${sendLine}</div>${checklistBlock(r)}${overrideControl(r)}</li>`;
|
|
385
426
|
}
|
|
386
427
|
function pendingSection(reports, site, now) {
|
|
387
428
|
const pending = reports.filter(isPendingApproval);
|
|
@@ -531,13 +572,73 @@ button.approve:disabled { opacity: 0.6; cursor: default; }
|
|
|
531
572
|
.pending-list li { padding: 0.5rem; border-bottom: 1px solid #eee; }
|
|
532
573
|
@media (prefers-color-scheme: dark) { .pending-list li { border-color: #2a2a2a; } }
|
|
533
574
|
.pending-head { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; row-gap: 0.25rem; }
|
|
534
|
-
.checklist {
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
575
|
+
.checklist {
|
|
576
|
+
list-style: none;
|
|
577
|
+
padding: 0;
|
|
578
|
+
margin: 0.5rem 0 0.25rem 0.25rem;
|
|
579
|
+
display: flex;
|
|
580
|
+
flex-direction: column;
|
|
581
|
+
gap: 0.3rem;
|
|
582
|
+
}
|
|
583
|
+
.check-item {
|
|
584
|
+
display: flex;
|
|
585
|
+
align-items: center;
|
|
586
|
+
gap: 0.5rem;
|
|
587
|
+
font-size: 0.9rem;
|
|
588
|
+
}
|
|
589
|
+
.pill.healthy {
|
|
590
|
+
background: #e8f5e9;
|
|
591
|
+
color: #1b7a2f;
|
|
592
|
+
}
|
|
593
|
+
.pill.watch {
|
|
594
|
+
background: #fff4e5;
|
|
595
|
+
color: #a65a00;
|
|
596
|
+
}
|
|
597
|
+
.pill.attention {
|
|
598
|
+
background: #fdecea;
|
|
599
|
+
color: #b00;
|
|
600
|
+
}
|
|
601
|
+
.pill.na {
|
|
602
|
+
background: #f0f0f0;
|
|
603
|
+
color: #666;
|
|
604
|
+
}
|
|
605
|
+
.advisory {
|
|
606
|
+
font-size: 0.72rem;
|
|
607
|
+
color: #999;
|
|
608
|
+
font-style: italic;
|
|
609
|
+
}
|
|
610
|
+
.check-note {
|
|
611
|
+
font-size: 0.75rem;
|
|
612
|
+
color: #999;
|
|
613
|
+
}
|
|
614
|
+
@media (prefers-color-scheme: dark) {
|
|
615
|
+
.pill.healthy {
|
|
616
|
+
background: #10240f;
|
|
617
|
+
color: #7fce85;
|
|
618
|
+
}
|
|
619
|
+
.pill.watch {
|
|
620
|
+
background: #2a2410;
|
|
621
|
+
color: #ffd454;
|
|
622
|
+
}
|
|
623
|
+
.pill.attention {
|
|
624
|
+
background: #2a0f0d;
|
|
625
|
+
color: #ff8a80;
|
|
626
|
+
}
|
|
627
|
+
.pill.na {
|
|
628
|
+
background: #222;
|
|
629
|
+
color: #999;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
540
632
|
.pill { font-size: 0.75rem; padding: 0.1rem 0.5rem; border-radius: 999px; font-weight: 700; }
|
|
633
|
+
.override { margin: 0.4rem 0 0.15rem 0.25rem; }
|
|
634
|
+
button.override-toggle { font: inherit; font-size: 0.78rem; padding: 0.15rem 0.6rem; border: 1px solid #b00; border-radius: 6px; background: transparent; color: #b00; cursor: pointer; }
|
|
635
|
+
@media (prefers-color-scheme: dark) { button.override-toggle { border-color: #ff8a80; color: #ff8a80; } }
|
|
636
|
+
.override-form { margin-top: 0.35rem; display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; }
|
|
637
|
+
.override-reason { font: inherit; font-size: 0.85rem; padding: 0.25rem 0.45rem; border: 1px solid #ccc; border-radius: 4px; background: transparent; color: inherit; min-width: 220px; flex: 1 1 220px; }
|
|
638
|
+
@media (prefers-color-scheme: dark) { .override-reason { border-color: #444; } }
|
|
639
|
+
button.override-submit { font: inherit; font-size: 0.8rem; padding: 0.25rem 0.7rem; border: 1px solid #b00; border-radius: 6px; background: #b00; color: #fff; cursor: pointer; }
|
|
640
|
+
button.override-submit:disabled { opacity: 0.6; cursor: default; }
|
|
641
|
+
.override-status { font-size: 0.78rem; color: #999; }
|
|
541
642
|
.vuln-list { list-style: none; padding: 0; margin: 0; }
|
|
542
643
|
.vuln-item { padding: 0.45rem 0; border-bottom: 1px solid #eee; display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: baseline; }
|
|
543
644
|
@media (prefers-color-scheme: dark) { .vuln-item { border-color: #2a2a2a; } }
|
|
@@ -645,6 +746,64 @@ function renderSiteDashboardHtml(site, reports, submissions = [], spamTotals = n
|
|
|
645
746
|
}
|
|
646
747
|
});
|
|
647
748
|
});
|
|
749
|
+
// Health-gate override affordance: the toggle reveals a required-reason field; the
|
|
750
|
+
// submit POSTs the server-provided override URL with { reason } as the body \u2014 a
|
|
751
|
+
// distinct, deliberate action, never a silent default (an empty reason is
|
|
752
|
+
// refused client-side AND server-side).
|
|
753
|
+
document.querySelectorAll("button.override-toggle").forEach((b) => {
|
|
754
|
+
b.addEventListener("click", () => {
|
|
755
|
+
const form = b.nextElementSibling;
|
|
756
|
+
if (form) form.hidden = !form.hidden;
|
|
757
|
+
});
|
|
758
|
+
});
|
|
759
|
+
document.querySelectorAll("button.override-submit").forEach((b) => {
|
|
760
|
+
b.addEventListener("click", async () => {
|
|
761
|
+
const wrap = b.closest(".override-form");
|
|
762
|
+
const input = wrap ? wrap.querySelector(".override-reason") : null;
|
|
763
|
+
const status = wrap ? wrap.querySelector(".override-status") : null;
|
|
764
|
+
const reason = input ? input.value.trim() : "";
|
|
765
|
+
if (!reason) {
|
|
766
|
+
if (status) status.textContent = "Reason required";
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
b.disabled = true;
|
|
770
|
+
if (status) status.textContent = "Sending\u2026";
|
|
771
|
+
try {
|
|
772
|
+
const res = await fetch(b.dataset.overrideUrl, {
|
|
773
|
+
method: "POST",
|
|
774
|
+
headers: { "content-type": "application/json" },
|
|
775
|
+
body: JSON.stringify({ reason }),
|
|
776
|
+
});
|
|
777
|
+
if (res.ok) {
|
|
778
|
+
if (status) status.textContent = "Overridden \u2713";
|
|
779
|
+
if (input) input.disabled = true;
|
|
780
|
+
// Reflect the send-anyway state on the row's own Approve button too \u2014
|
|
781
|
+
// it stays server-gated disabled, but the label should stop implying
|
|
782
|
+
// "waiting on you" once an override has gone through.
|
|
783
|
+
const approveBtn = document.querySelector(
|
|
784
|
+
'button.approve[data-report-id="' + b.dataset.reportId + '"]',
|
|
785
|
+
);
|
|
786
|
+
if (approveBtn) approveBtn.textContent = "Overridden";
|
|
787
|
+
} else {
|
|
788
|
+
// A 409 carries { reason, blockers } \u2014 surface WHY (textContent only,
|
|
789
|
+
// never innerHTML, so server strings stay inert).
|
|
790
|
+
const data = await res.json().catch(() => null);
|
|
791
|
+
if (status) {
|
|
792
|
+
status.textContent =
|
|
793
|
+
data && Array.isArray(data.blockers) && data.blockers.length > 0
|
|
794
|
+
? data.blockers.join("; ")
|
|
795
|
+
: data && data.reason === "override-reason-required"
|
|
796
|
+
? "Reason required"
|
|
797
|
+
: "Failed";
|
|
798
|
+
}
|
|
799
|
+
b.disabled = false;
|
|
800
|
+
}
|
|
801
|
+
} catch {
|
|
802
|
+
if (status) status.textContent = "Failed";
|
|
803
|
+
b.disabled = false;
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
});
|
|
648
807
|
// Trigger-renovate button: async on-demand dispatch (mirrors the cockpit).
|
|
649
808
|
document.querySelectorAll("button.trigger-renovate").forEach((b) => {
|
|
650
809
|
b.addEventListener("click", async () => {
|
|
@@ -685,34 +844,6 @@ function renderSiteDashboardHtml(site, reports, submissions = [], spamTotals = n
|
|
|
685
844
|
});
|
|
686
845
|
});
|
|
687
846
|
${SUBMISSION_STATUS_SCRIPT}
|
|
688
|
-
// Checklist gate: ticking a box POSTs the one field; the response { complete }
|
|
689
|
-
// decides whether THIS report's Approve button is enabled. Scoped per report by
|
|
690
|
-
// matching the checkbox's report id to the Approve button's id, so multiple
|
|
691
|
-
// pending reports on one page never cross-toggle. On failure the checkbox reverts.
|
|
692
|
-
document.querySelectorAll("input.checklist-checkbox").forEach((cb) => {
|
|
693
|
-
cb.addEventListener("change", async () => {
|
|
694
|
-
const reportId = cb.dataset.checklistReportId;
|
|
695
|
-
const approveBtn = document.querySelector(
|
|
696
|
-
'button.approve[data-report-id="' + (window.CSS && CSS.escape ? CSS.escape(reportId) : reportId) + '"]',
|
|
697
|
-
);
|
|
698
|
-
cb.disabled = true;
|
|
699
|
-
try {
|
|
700
|
-
const res = await fetch(cb.dataset.checklistUrl, {
|
|
701
|
-
method: "POST",
|
|
702
|
-
headers: { "content-type": "application/json" },
|
|
703
|
-
body: JSON.stringify({ reportId, field: cb.dataset.field, value: cb.checked }),
|
|
704
|
-
});
|
|
705
|
-
if (!res.ok) throw new Error("bad status");
|
|
706
|
-
const data = await res.json();
|
|
707
|
-
if (approveBtn) approveBtn.disabled = !data.complete || approveBtn.dataset.sendBlocked === "1";
|
|
708
|
-
} catch {
|
|
709
|
-
// Revert the optimistic flip so the box reflects the (unchanged) server state.
|
|
710
|
-
cb.checked = !cb.checked;
|
|
711
|
-
} finally {
|
|
712
|
-
cb.disabled = false;
|
|
713
|
-
}
|
|
714
|
-
});
|
|
715
|
-
});
|
|
716
847
|
</script>
|
|
717
848
|
</body>
|
|
718
849
|
</html>`;
|
|
@@ -896,7 +1027,12 @@ function submBadge(c) {
|
|
|
896
1027
|
const n = c.newSubmissions ?? 0;
|
|
897
1028
|
return n > 0 ? `<span class="chip">\u{1F4E5} ${n} new</span>` : "";
|
|
898
1029
|
}
|
|
899
|
-
var PILL_LABEL = {
|
|
1030
|
+
var PILL_LABEL = {
|
|
1031
|
+
attention: "failing",
|
|
1032
|
+
watch: "watch",
|
|
1033
|
+
healthy: "ok",
|
|
1034
|
+
"pre-launch": "pre-launch"
|
|
1035
|
+
};
|
|
900
1036
|
function attentionBadge(status) {
|
|
901
1037
|
if (status === "new") return `<span class="badge">NEW</span>`;
|
|
902
1038
|
if (status === "worse") return `<span class="badge">WORSE</span>`;
|
|
@@ -1037,6 +1173,8 @@ details.fleet-browse > summary, details.inbox > summary { cursor:pointer; font-w
|
|
|
1037
1173
|
.pill.attention { background:#fdecea; color:#b00; }
|
|
1038
1174
|
.pill.watch { background:#fff4e5; color:#a65a00; }
|
|
1039
1175
|
.pill.healthy { background:#e8f5e9; color:#1b7a2f; }
|
|
1176
|
+
.pill.pre-launch { background:#eef1f5; color:#5b6570; }
|
|
1177
|
+
@media (prefers-color-scheme: dark) { .pill.pre-launch { background:#1c2530; color:#9aa7b4; } }
|
|
1040
1178
|
.chips { display:flex; flex-wrap:wrap; gap:0.4rem; margin-top:0.5rem; }
|
|
1041
1179
|
.chip { font-size:0.8rem; padding:0.1rem 0.5rem; border-radius:6px; background:#f0f0f0; }
|
|
1042
1180
|
@media (prefers-color-scheme: dark) { .chip { background:#222; } }
|
|
@@ -1088,7 +1226,9 @@ function verdictBar(model, feed) {
|
|
|
1088
1226
|
const auditedTerm = auditedIso ? `fleet last audited ${escapeHtml(relativeTimeFromNow(auditedIso))}` : null;
|
|
1089
1227
|
const total = model.cards.length;
|
|
1090
1228
|
const { broken, watch, approval } = needsYouCounts(feed);
|
|
1091
|
-
const
|
|
1229
|
+
const preLaunch = model.summary.preLaunch;
|
|
1230
|
+
const preLaunchTerm = preLaunch > 0 ? `${preLaunch} pre-launch` : null;
|
|
1231
|
+
const healthy = Math.max(0, total - (broken + watch + approval + preLaunch));
|
|
1092
1232
|
const actions = `<div class="fleet-actions">
|
|
1093
1233
|
<button type="button" class="refresh-fleet" data-refresh-url="/api/fleet/refresh">\u21BB Audit fleet</button>
|
|
1094
1234
|
<div id="rf-status" class="rf-status" aria-live="polite"></div>
|
|
@@ -1099,8 +1239,8 @@ function verdictBar(model, feed) {
|
|
|
1099
1239
|
${actions}
|
|
1100
1240
|
</div>`;
|
|
1101
1241
|
if (broken === 0 && watch === 0 && approval === 0) {
|
|
1102
|
-
const
|
|
1103
|
-
return render("ok", "\u2713 All clear", [
|
|
1242
|
+
const healthyWord = healthy > 0 ? `${healthy} site${healthy === 1 ? "" : "s"} healthy` : null;
|
|
1243
|
+
return render("ok", "\u2713 All clear", [healthyWord, preLaunchTerm, auditedTerm]);
|
|
1104
1244
|
}
|
|
1105
1245
|
const watchTerm = watch > 0 ? `${watch} watching` : null;
|
|
1106
1246
|
const approvalTerm = approval > 0 ? `${approval} waiting on you` : null;
|
|
@@ -1110,6 +1250,7 @@ function verdictBar(model, feed) {
|
|
|
1110
1250
|
watchTerm,
|
|
1111
1251
|
approvalTerm,
|
|
1112
1252
|
healthyTerm,
|
|
1253
|
+
preLaunchTerm,
|
|
1113
1254
|
auditedTerm
|
|
1114
1255
|
]);
|
|
1115
1256
|
}
|
|
@@ -1117,10 +1258,11 @@ function verdictBar(model, feed) {
|
|
|
1117
1258
|
return render("watch", `${watch} site${watch === 1 ? "" : "s"} to watch`, [
|
|
1118
1259
|
approvalTerm,
|
|
1119
1260
|
healthyTerm,
|
|
1261
|
+
preLaunchTerm,
|
|
1120
1262
|
auditedTerm
|
|
1121
1263
|
]);
|
|
1122
1264
|
}
|
|
1123
|
-
return render("soft", `${approval} waiting on you`, [healthyTerm, auditedTerm]);
|
|
1265
|
+
return render("soft", `${approval} waiting on you`, [healthyTerm, preLaunchTerm, auditedTerm]);
|
|
1124
1266
|
}
|
|
1125
1267
|
var NEEDS_YOU_GROUP_LABEL = {
|
|
1126
1268
|
broken: "Broken",
|
|
@@ -1151,7 +1293,8 @@ var RECENT_ICON = {
|
|
|
1151
1293
|
ci_recovered: "\u2705",
|
|
1152
1294
|
site_launched: "\u{1F680}",
|
|
1153
1295
|
cert_renewed: "\u{1F512}",
|
|
1154
|
-
fleet_swept: "\u{1F504}"
|
|
1296
|
+
fleet_swept: "\u{1F504}",
|
|
1297
|
+
report_sent_with_override: "\u2733\uFE0F"
|
|
1155
1298
|
};
|
|
1156
1299
|
function renderRecentlyLane(model) {
|
|
1157
1300
|
const events = model.recent ?? [];
|