@reddoorla/maintenance 0.87.0 → 0.88.3
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/airtable-QG5ICI3Y.js +9 -0
- package/dist/{announce-I2Z4JN3I.js → announce-GKD5YJNB.js} +9 -9
- package/dist/{blux-ZGDSHI3U.js → blux-EATHOHKK.js} +5 -5
- package/dist/{bump-deps-CGVELRLK.js → bump-deps-KGH5UYSF.js} +4 -4
- package/dist/{chunk-SDDPUAWH.js → chunk-6I6L3CMX.js} +2 -2
- package/dist/{chunk-NXNPDCF3.js → chunk-6UCVWYDA.js} +11 -1
- package/dist/chunk-6UCVWYDA.js.map +1 -0
- package/dist/{chunk-ZBDZTJUO.js → chunk-7FFUBF4I.js} +5 -2
- package/dist/chunk-7FFUBF4I.js.map +1 -0
- package/dist/{chunk-3JQ2FO3D.js → chunk-B23FFWU4.js} +2 -2
- package/dist/{chunk-DJZWYIPP.js → chunk-BGTYPVLT.js} +1 -1
- package/dist/chunk-BGTYPVLT.js.map +1 -0
- package/dist/chunk-BRYXFH77.js +70 -0
- package/dist/chunk-BRYXFH77.js.map +1 -0
- package/dist/{chunk-HSI6Q7MI.js → chunk-CEW5PEHZ.js} +168 -31
- package/dist/chunk-CEW5PEHZ.js.map +1 -0
- package/dist/{chunk-NDWYJLRF.js → chunk-DGZWFI2G.js} +3 -3
- package/dist/{chunk-HHMMUR4P.js → chunk-GQDDWYU2.js} +45 -8
- package/dist/chunk-GQDDWYU2.js.map +1 -0
- package/dist/{chunk-BBUSGEES.js → chunk-J3WEZGIN.js} +6 -6
- package/dist/{chunk-BNM5POSJ.js → chunk-JRHIFA5R.js} +2 -2
- package/dist/{chunk-MT2JINSS.js → chunk-KJGKZLCT.js} +3 -3
- package/dist/{chunk-3SF7D4DY.js → chunk-MFHS7IQ2.js} +36 -1
- package/dist/{chunk-3SF7D4DY.js.map → chunk-MFHS7IQ2.js.map} +1 -1
- package/dist/{chunk-SNSTJLQW.js → chunk-NO74DXDP.js} +48 -2
- package/dist/chunk-NO74DXDP.js.map +1 -0
- package/dist/{chunk-C6B4RHXR.js → chunk-O3GT46R2.js} +8 -2
- package/dist/chunk-O3GT46R2.js.map +1 -0
- package/dist/{chunk-C6CLRKPV.js → chunk-PR343K7F.js} +2 -2
- package/dist/{chunk-CROKUHJB.js → chunk-Q47DU255.js} +4 -4
- package/dist/{chunk-EVBCK2KC.js → chunk-RARREDQE.js} +2 -1
- package/dist/{chunk-EVBCK2KC.js.map → chunk-RARREDQE.js.map} +1 -1
- package/dist/chunk-RUPPWAD6.js +7 -0
- package/dist/chunk-RUPPWAD6.js.map +1 -0
- package/dist/{chunk-VSBFM7DS.js → chunk-SJOXWJ5Y.js} +3 -3
- package/dist/{chunk-HEWSRXYV.js → chunk-ULHPALI5.js} +3 -3
- package/dist/{chunk-XUGSZP7Z.js → chunk-W45FZEGE.js} +2 -2
- package/dist/{chunk-NCMZMLY7.js → chunk-WPL5GERE.js} +5 -69
- package/dist/chunk-WPL5GERE.js.map +1 -0
- package/dist/{chunk-JHGHLKFS.js → chunk-WS6NU475.js} +2 -2
- package/dist/{chunk-DEWJMMG3.js → chunk-XKDCR4UE.js} +19 -8
- package/dist/chunk-XKDCR4UE.js.map +1 -0
- package/dist/{chunk-ZGBEIWSC.js → chunk-XP5WM2LV.js} +5 -5
- package/dist/{chunk-TD2NNXLZ.js → chunk-XSOINOPY.js} +8 -8
- package/dist/{chunk-6TWWY7ZU.js → chunk-Z3JXLULD.js} +2 -2
- package/dist/cli/bin.js +31 -28
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.d.ts +4 -0
- package/dist/cli/commands/audit.js +16 -13
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/client-JYKPVCJX.js +11 -0
- package/dist/configs/playwright-a11y.js +1 -1
- package/dist/{convert-to-pnpm-QFJOEP5V.js → convert-to-pnpm-NWB5FCQ2.js} +4 -4
- package/dist/{db-UXNIZ2RG.js → db-DXUUAMXT.js} +121 -26
- package/dist/db-DXUUAMXT.js.map +1 -0
- package/dist/{digest-FW6QMZZJ.js → digest-NBLECFM7.js} +110 -29
- package/dist/digest-NBLECFM7.js.map +1 -0
- package/dist/digest-collectors-NM7WUWQP.js +50 -0
- package/dist/digest-state-3KHZF7GD.js +50 -0
- package/dist/digest-state-3KHZF7GD.js.map +1 -0
- package/dist/digest-state-F37XUCS5.js +13 -0
- package/dist/{dump-P6EBSIOH.js → dump-EPL7AZFR.js} +38 -13
- package/dist/dump-EPL7AZFR.js.map +1 -0
- package/dist/{email-KJXASVH5.js → email-FMMEUPX6.js} +16 -6
- package/dist/{email-KJXASVH5.js.map → email-FMMEUPX6.js.map} +1 -1
- package/dist/{ensure-site-4DCEBAPM.js → ensure-site-FU6527OU.js} +2 -2
- package/dist/{fleet-state-7TJ2GYT2.js → fleet-state-R5LU7IC3.js} +4 -2
- package/dist/{forms-notify-target-2EAWCWBW.js → forms-notify-target-SGDGXXK5.js} +6 -6
- package/dist/{github-signals-E2WPYTBK.js → github-signals-DB25UWY4.js} +7 -6
- package/dist/{github-signals-E2WPYTBK.js.map → github-signals-DB25UWY4.js.map} +1 -1
- package/dist/{header-image-6IC53W27.js → header-image-7A7XLVEU.js} +2 -2
- package/dist/{health-endpoint-RFXL2F4A.js → health-endpoint-UVX6R5N5.js} +4 -4
- package/dist/{health-mirror-L5JBEJVA.js → health-mirror-GCDN7BJL.js} +11 -6
- package/dist/health-mirror-GCDN7BJL.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +136 -95
- package/dist/index.js.map +1 -1
- package/dist/{init-XRAQZYID.js → init-32AWPS7A.js} +9 -9
- package/dist/{launch-IX4Y5JJC.js → launch-52WUE4TZ.js} +12 -11
- package/dist/{launch-IX4Y5JJC.js.map → launch-52WUE4TZ.js.map} +1 -1
- package/dist/migrate-YITCXBLS.js +7 -0
- package/dist/{notify-C645PGD5.js → notify-4HP4GZ2H.js} +5 -5
- package/dist/{onboard-O4IAHJMO.js → onboard-BKCYBAVW.js} +4 -4
- package/dist/{orchestrate-YLWQ6KK2.js → orchestrate-JNKCWU5K.js} +9 -9
- package/dist/{parity-56KA2DGR.js → parity-NBYZUBFU.js} +4 -2
- package/dist/pdf-N5QLQ5ZQ.js +38 -0
- package/dist/pdf-N5QLQ5ZQ.js.map +1 -0
- package/dist/{pipeline-3AANZFCR.js → pipeline-YIBIQ7XK.js} +3 -3
- package/dist/{preflight-TNORKGCX.js → preflight-IMTYDDD4.js} +10 -10
- package/dist/{prismic-ci-G2AK53VL.js → prismic-ci-MY2U6NN7.js} +4 -4
- package/dist/{prismic-models-O6Z3OK3A.js → prismic-models-IJWZW2EO.js} +6 -6
- package/dist/prospect/types.d.ts +33 -0
- package/dist/{prospect-audit-KSXUNQ5J.js → prospect-audit-WJQI7V5V.js} +32 -13
- package/dist/prospect-audit-WJQI7V5V.js.map +1 -0
- package/dist/render-CVKE7IWC.js +10 -0
- package/dist/{render-from-row-BWSOIYDS.js → render-from-row-DOYJDO2L.js} +5 -5
- package/dist/{replay-C2LXMJZP.js → replay-4S6ENYCH.js} +3 -3
- package/dist/{report-R3E3Y7HR.js → report-2LM2SX67.js} +20 -20
- package/dist/{report-mirror-4MNORYQ4.js → report-mirror-AAK3MXHY.js} +12 -6
- package/dist/report-mirror-AAK3MXHY.js.map +1 -0
- package/dist/schema-75PJECAE.js +18 -0
- package/dist/schema-75PJECAE.js.map +1 -0
- package/dist/{self-updating-UCIWESAX.js → self-updating-NTCCWJYZ.js} +4 -4
- package/dist/{selftest-VGHWPN72.js → selftest-DIZ5JD2X.js} +12 -12
- package/dist/{site-mirror-NIREX5ME.js → site-mirror-Q763PLGG.js} +17 -7
- package/dist/site-mirror-Q763PLGG.js.map +1 -0
- package/dist/{smoke-suite-U3UKD3NS.js → smoke-suite-4JFZZYVW.js} +4 -4
- package/dist/{spam-classifier-XLBFCO7G.js → spam-classifier-T5H4VTP6.js} +4 -2
- package/dist/spam-classifier-T5H4VTP6.js.map +1 -0
- package/dist/{submissions-IQEJX6FK.js → submissions-TEHPBSZJ.js} +4 -4
- package/dist/{svelte-codemods-GOKXTBHW.js → svelte-codemods-QAPDZC6F.js} +4 -4
- package/dist/{sync-CTUVXMCK.js → sync-GBFV45WX.js} +4 -2
- package/dist/{sync-CTUVXMCK.js.map → sync-GBFV45WX.js.map} +1 -1
- package/dist/{sync-configs-SPTLFJCC.js → sync-configs-34JIA6KZ.js} +4 -4
- package/dist/{upgrade-W6ZR3FJH.js → upgrade-IR2OO3KP.js} +4 -4
- package/dist/{webhook-VOGOW3W6.js → webhook-XM6EV4XT.js} +2 -2
- package/dist/{write-audits-to-airtable-CMKHGJNQ.js → write-audits-to-airtable-OPBJXGS4.js} +5 -2
- package/dist/write-audits-to-airtable-OPBJXGS4.js.map +1 -0
- package/package.json +1 -1
- package/dist/airtable-ZBUPQJSC.js +0 -9
- package/dist/chunk-C6B4RHXR.js.map +0 -1
- package/dist/chunk-DEWJMMG3.js.map +0 -1
- package/dist/chunk-DJZWYIPP.js.map +0 -1
- package/dist/chunk-HHMMUR4P.js.map +0 -1
- package/dist/chunk-HSI6Q7MI.js.map +0 -1
- package/dist/chunk-NCMZMLY7.js.map +0 -1
- package/dist/chunk-NXNPDCF3.js.map +0 -1
- package/dist/chunk-SNSTJLQW.js.map +0 -1
- package/dist/chunk-ZBDZTJUO.js.map +0 -1
- package/dist/client-AUE3XUPO.js +0 -11
- package/dist/db-UXNIZ2RG.js.map +0 -1
- package/dist/digest-FW6QMZZJ.js.map +0 -1
- package/dist/dump-P6EBSIOH.js.map +0 -1
- package/dist/health-mirror-L5JBEJVA.js.map +0 -1
- package/dist/migrate-GQH237A6.js +0 -7
- package/dist/prospect-audit-KSXUNQ5J.js.map +0 -1
- package/dist/render-IFOGFB5T.js +0 -10
- package/dist/report-mirror-4MNORYQ4.js.map +0 -1
- package/dist/site-mirror-NIREX5ME.js.map +0 -1
- /package/dist/{airtable-ZBUPQJSC.js.map → airtable-QG5ICI3Y.js.map} +0 -0
- /package/dist/{announce-I2Z4JN3I.js.map → announce-GKD5YJNB.js.map} +0 -0
- /package/dist/{blux-ZGDSHI3U.js.map → blux-EATHOHKK.js.map} +0 -0
- /package/dist/{bump-deps-CGVELRLK.js.map → bump-deps-KGH5UYSF.js.map} +0 -0
- /package/dist/{chunk-SDDPUAWH.js.map → chunk-6I6L3CMX.js.map} +0 -0
- /package/dist/{chunk-3JQ2FO3D.js.map → chunk-B23FFWU4.js.map} +0 -0
- /package/dist/{chunk-NDWYJLRF.js.map → chunk-DGZWFI2G.js.map} +0 -0
- /package/dist/{chunk-BBUSGEES.js.map → chunk-J3WEZGIN.js.map} +0 -0
- /package/dist/{chunk-BNM5POSJ.js.map → chunk-JRHIFA5R.js.map} +0 -0
- /package/dist/{chunk-MT2JINSS.js.map → chunk-KJGKZLCT.js.map} +0 -0
- /package/dist/{chunk-C6CLRKPV.js.map → chunk-PR343K7F.js.map} +0 -0
- /package/dist/{chunk-CROKUHJB.js.map → chunk-Q47DU255.js.map} +0 -0
- /package/dist/{chunk-VSBFM7DS.js.map → chunk-SJOXWJ5Y.js.map} +0 -0
- /package/dist/{chunk-HEWSRXYV.js.map → chunk-ULHPALI5.js.map} +0 -0
- /package/dist/{chunk-XUGSZP7Z.js.map → chunk-W45FZEGE.js.map} +0 -0
- /package/dist/{chunk-JHGHLKFS.js.map → chunk-WS6NU475.js.map} +0 -0
- /package/dist/{chunk-ZGBEIWSC.js.map → chunk-XP5WM2LV.js.map} +0 -0
- /package/dist/{chunk-TD2NNXLZ.js.map → chunk-XSOINOPY.js.map} +0 -0
- /package/dist/{chunk-6TWWY7ZU.js.map → chunk-Z3JXLULD.js.map} +0 -0
- /package/dist/{client-AUE3XUPO.js.map → client-JYKPVCJX.js.map} +0 -0
- /package/dist/{convert-to-pnpm-QFJOEP5V.js.map → convert-to-pnpm-NWB5FCQ2.js.map} +0 -0
- /package/dist/{fleet-state-7TJ2GYT2.js.map → digest-collectors-NM7WUWQP.js.map} +0 -0
- /package/dist/{migrate-GQH237A6.js.map → digest-state-F37XUCS5.js.map} +0 -0
- /package/dist/{ensure-site-4DCEBAPM.js.map → ensure-site-FU6527OU.js.map} +0 -0
- /package/dist/{notify-C645PGD5.js.map → fleet-state-R5LU7IC3.js.map} +0 -0
- /package/dist/{forms-notify-target-2EAWCWBW.js.map → forms-notify-target-SGDGXXK5.js.map} +0 -0
- /package/dist/{header-image-6IC53W27.js.map → header-image-7A7XLVEU.js.map} +0 -0
- /package/dist/{health-endpoint-RFXL2F4A.js.map → health-endpoint-UVX6R5N5.js.map} +0 -0
- /package/dist/{init-XRAQZYID.js.map → init-32AWPS7A.js.map} +0 -0
- /package/dist/{orchestrate-YLWQ6KK2.js.map → migrate-YITCXBLS.js.map} +0 -0
- /package/dist/{parity-56KA2DGR.js.map → notify-4HP4GZ2H.js.map} +0 -0
- /package/dist/{onboard-O4IAHJMO.js.map → onboard-BKCYBAVW.js.map} +0 -0
- /package/dist/{pipeline-3AANZFCR.js.map → orchestrate-JNKCWU5K.js.map} +0 -0
- /package/dist/{render-IFOGFB5T.js.map → parity-NBYZUBFU.js.map} +0 -0
- /package/dist/{render-from-row-BWSOIYDS.js.map → pipeline-YIBIQ7XK.js.map} +0 -0
- /package/dist/{preflight-TNORKGCX.js.map → preflight-IMTYDDD4.js.map} +0 -0
- /package/dist/{prismic-ci-G2AK53VL.js.map → prismic-ci-MY2U6NN7.js.map} +0 -0
- /package/dist/{prismic-models-O6Z3OK3A.js.map → prismic-models-IJWZW2EO.js.map} +0 -0
- /package/dist/{spam-classifier-XLBFCO7G.js.map → render-CVKE7IWC.js.map} +0 -0
- /package/dist/{write-audits-to-airtable-CMKHGJNQ.js.map → render-from-row-DOYJDO2L.js.map} +0 -0
- /package/dist/{replay-C2LXMJZP.js.map → replay-4S6ENYCH.js.map} +0 -0
- /package/dist/{report-R3E3Y7HR.js.map → report-2LM2SX67.js.map} +0 -0
- /package/dist/{self-updating-UCIWESAX.js.map → self-updating-NTCCWJYZ.js.map} +0 -0
- /package/dist/{selftest-VGHWPN72.js.map → selftest-DIZ5JD2X.js.map} +0 -0
- /package/dist/{smoke-suite-U3UKD3NS.js.map → smoke-suite-4JFZZYVW.js.map} +0 -0
- /package/dist/{submissions-IQEJX6FK.js.map → submissions-TEHPBSZJ.js.map} +0 -0
- /package/dist/{svelte-codemods-GOKXTBHW.js.map → svelte-codemods-QAPDZC6F.js.map} +0 -0
- /package/dist/{sync-configs-SPTLFJCC.js.map → sync-configs-34JIA6KZ.js.map} +0 -0
- /package/dist/{upgrade-W6ZR3FJH.js.map → upgrade-IR2OO3KP.js.map} +0 -0
- /package/dist/{webhook-VOGOW3W6.js.map → webhook-XM6EV4XT.js.map} +0 -0
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TURSO_IS_AUTHORITATIVE
|
|
3
|
+
} from "./chunk-RUPPWAD6.js";
|
|
1
4
|
import {
|
|
2
5
|
mirrorHealthFields,
|
|
3
6
|
mirrorScheduleFields
|
|
4
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-GQDDWYU2.js";
|
|
5
8
|
import "./chunk-P6AORG32.js";
|
|
6
9
|
import "./chunk-F3JWOPTG.js";
|
|
7
10
|
import "./chunk-VAD43MOR.js";
|
|
@@ -9,26 +12,28 @@ import "./chunk-7NYN2P6I.js";
|
|
|
9
12
|
import {
|
|
10
13
|
openDb,
|
|
11
14
|
readDbConfig
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
15
|
+
} from "./chunk-WS6NU475.js";
|
|
16
|
+
import "./chunk-MFHS7IQ2.js";
|
|
14
17
|
import "./chunk-6VAL7XJT.js";
|
|
15
18
|
|
|
16
19
|
// src/audits/health-mirror.ts
|
|
17
|
-
async function makeHealthMirrorBestEffort(open = () => openDb(readDbConfig())) {
|
|
20
|
+
async function makeHealthMirrorBestEffort(open = () => openDb(readDbConfig()), strict = TURSO_IS_AUTHORITATIVE) {
|
|
18
21
|
let db;
|
|
19
22
|
try {
|
|
20
23
|
db = await open();
|
|
21
24
|
} catch (e) {
|
|
25
|
+
if (strict) throw new Error(`health-mirror unavailable: ${String(e)}`, { cause: e });
|
|
22
26
|
console.error(`[health-mirror] mirroring disabled: no libSQL (${String(e)})`);
|
|
23
27
|
return null;
|
|
24
28
|
}
|
|
25
29
|
return (siteId, fields) => mirrorHealthFields(db, siteId, fields);
|
|
26
30
|
}
|
|
27
|
-
async function makeScheduleMirrorBestEffort(open = () => openDb(readDbConfig())) {
|
|
31
|
+
async function makeScheduleMirrorBestEffort(open = () => openDb(readDbConfig()), strict = TURSO_IS_AUTHORITATIVE) {
|
|
28
32
|
let db;
|
|
29
33
|
try {
|
|
30
34
|
db = await open();
|
|
31
35
|
} catch (e) {
|
|
36
|
+
if (strict) throw new Error(`schedule-mirror unavailable: ${String(e)}`, { cause: e });
|
|
32
37
|
console.error(`[schedule-mirror] mirroring disabled: no libSQL (${String(e)})`);
|
|
33
38
|
return null;
|
|
34
39
|
}
|
|
@@ -38,4 +43,4 @@ export {
|
|
|
38
43
|
makeHealthMirrorBestEffort,
|
|
39
44
|
makeScheduleMirrorBestEffort
|
|
40
45
|
};
|
|
41
|
-
//# sourceMappingURL=health-mirror-
|
|
46
|
+
//# sourceMappingURL=health-mirror-GCDN7BJL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/audits/health-mirror.ts"],"sourcesContent":["import { openDb, readDbConfig, type Db } from \"../db/client.js\";\nimport { mirrorHealthFields, mirrorScheduleFields } from \"../db/fleet-state.js\";\nimport { TURSO_IS_AUTHORITATIVE } from \"../db/freeze.js\";\n\n/** One site's just-written Airtable FieldSet, mirrored into site_health.\n * Resolves true when a site_health row matched; false when the UPDATE touched\n * 0 rows (site created in Airtable after the last hourly import) — callers\n * count that as mirror_missed, never as mirrored. */\nexport type HealthMirror = (siteId: string, fields: Record<string, unknown>) => Promise<boolean>;\n\n/** The site_schedule twin, for the nightly next-due write-back. Same\n * matched-row contract as {@link HealthMirror}: false = 0-row UPDATE →\n * mirror_missed. */\nexport type ScheduleMirror = (\n siteId: string,\n fields: Record<string, unknown>,\n computedAt: string,\n) => Promise<boolean>;\n\n/** Build the Turso write-through for the nightly writers (#539 Phase 3\n * dual-write), or null when libSQL creds are absent — mirroring NOT attempted\n * is a different outcome than mirroring failed, and the caller's summary line\n * reports mirror counts only when a mirror existed. Same contract as\n * recordFleetEventsBestEffort: the mirror must never fail the sweep that\n * produced the data (per-site failures are the caller's to count; Airtable\n * stays authoritative and the hourly sync converges whatever the mirror\n * missed). `open` is injectable for tests. */\nexport async function makeHealthMirrorBestEffort(\n open: () => Promise<Db> = () => openDb(readDbConfig()),\n /** #612. `true` = Turso is the only store, so \"mirroring disabled\" is no\n * longer a survivable state: returning null would discard every write in the\n * sweep with nothing to converge it. Throws instead. Injected by tests so\n * both sides stay proven. */\n strict: boolean = TURSO_IS_AUTHORITATIVE,\n): Promise<HealthMirror | null> {\n let db: Db;\n try {\n db = await open();\n } catch (e) {\n if (strict) throw new Error(`health-mirror unavailable: ${String(e)}`, { cause: e });\n console.error(`[health-mirror] mirroring disabled: no libSQL (${String(e)})`);\n return null;\n }\n return (siteId, fields) => mirrorHealthFields(db, siteId, fields);\n}\n\n/** {@link makeHealthMirrorBestEffort}'s schedule twin — same null-without-creds\n * contract, for `writeNextDueDates`' site_schedule write-through. */\nexport async function makeScheduleMirrorBestEffort(\n open: () => Promise<Db> = () => openDb(readDbConfig()),\n /** #612 — same contract as {@link makeHealthMirrorBestEffort}'s. */\n strict: boolean = TURSO_IS_AUTHORITATIVE,\n): Promise<ScheduleMirror | null> {\n let db: Db;\n try {\n db = await open();\n } catch (e) {\n if (strict) throw new Error(`schedule-mirror unavailable: ${String(e)}`, { cause: e });\n console.error(`[schedule-mirror] mirroring disabled: no libSQL (${String(e)})`);\n return null;\n }\n return (siteId, fields, computedAt) => mirrorScheduleFields(db, siteId, fields, computedAt);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA2BA,eAAsB,2BACpB,OAA0B,MAAM,OAAO,aAAa,CAAC,GAKrD,SAAkB,wBACY;AAC9B,MAAI;AACJ,MAAI;AACF,SAAK,MAAM,KAAK;AAAA,EAClB,SAAS,GAAG;AACV,QAAI,OAAQ,OAAM,IAAI,MAAM,8BAA8B,OAAO,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC;AACnF,YAAQ,MAAM,kDAAkD,OAAO,CAAC,CAAC,GAAG;AAC5E,WAAO;AAAA,EACT;AACA,SAAO,CAAC,QAAQ,WAAW,mBAAmB,IAAI,QAAQ,MAAM;AAClE;AAIA,eAAsB,6BACpB,OAA0B,MAAM,OAAO,aAAa,CAAC,GAErD,SAAkB,wBACc;AAChC,MAAI;AACJ,MAAI;AACF,SAAK,MAAM,KAAK;AAAA,EAClB,SAAS,GAAG;AACV,QAAI,OAAQ,OAAM,IAAI,MAAM,gCAAgC,OAAO,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC;AACrF,YAAQ,MAAM,oDAAoD,OAAO,CAAC,CAAC,GAAG;AAC9E,WAAO;AAAA,EACT;AACA,SAAO,CAAC,QAAQ,QAAQ,eAAe,qBAAqB,IAAI,QAAQ,QAAQ,UAAU;AAC5F;","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1500,9 +1500,12 @@ type CockpitModel = {
|
|
|
1500
1500
|
/** NEW submissions across the fleet, newest-first (optional for back-compat). */
|
|
1501
1501
|
submissions?: SubmissionEntry[];
|
|
1502
1502
|
/** Fleet spam totals over the window (optional; populated by buildCockpitModel). */
|
|
1503
|
+
/** `computedAt` present ⇒ these came from the nightly roll-up and are up to
|
|
1504
|
+
* 24h old; the strip labels them so a stale number is never read as live. */
|
|
1503
1505
|
spam?: {
|
|
1504
1506
|
caught: number;
|
|
1505
1507
|
through: number;
|
|
1508
|
+
computedAt?: string;
|
|
1506
1509
|
} | null;
|
|
1507
1510
|
/** Recent fleet-activity events for the "Recently" lane (optional for back-compat). */
|
|
1508
1511
|
recent?: RecentEntry[];
|
package/dist/index.js
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import "./chunk-2SLED3NB.js";
|
|
2
|
-
import "./chunk-NCMZMLY7.js";
|
|
3
2
|
import {
|
|
4
3
|
draftReportForSite
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import {
|
|
7
|
-
approveBlockers
|
|
8
|
-
} from "./chunk-SDDPUAWH.js";
|
|
9
|
-
import {
|
|
10
|
-
findDueReports
|
|
11
|
-
} from "./chunk-PZR4EBPY.js";
|
|
4
|
+
} from "./chunk-J3WEZGIN.js";
|
|
12
5
|
import {
|
|
13
6
|
DEFAULT_INIT_STEPS,
|
|
14
7
|
a11yFixturesPage,
|
|
15
8
|
init
|
|
16
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-XSOINOPY.js";
|
|
10
|
+
import {
|
|
11
|
+
convertToPnpm
|
|
12
|
+
} from "./chunk-XIDUYLSR.js";
|
|
13
|
+
import {
|
|
14
|
+
svelteCodemods
|
|
15
|
+
} from "./chunk-IYSEC543.js";
|
|
17
16
|
import "./chunk-BQRFQSGY.js";
|
|
18
17
|
import "./chunk-Y53WOQIJ.js";
|
|
19
18
|
import {
|
|
@@ -29,18 +28,20 @@ import "./chunk-R53VL3RI.js";
|
|
|
29
28
|
import {
|
|
30
29
|
upgradeSvelte4to5
|
|
31
30
|
} from "./chunk-Z3KYFUN6.js";
|
|
31
|
+
import "./chunk-J7SZQUCW.js";
|
|
32
|
+
import "./chunk-WPL5GERE.js";
|
|
32
33
|
import {
|
|
33
|
-
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
approveBlockers
|
|
35
|
+
} from "./chunk-6I6L3CMX.js";
|
|
35
36
|
import {
|
|
36
|
-
|
|
37
|
-
} from "./chunk-
|
|
38
|
-
import "./chunk-J7SZQUCW.js";
|
|
37
|
+
findDueReports
|
|
38
|
+
} from "./chunk-PZR4EBPY.js";
|
|
39
39
|
import {
|
|
40
40
|
parseAddresses,
|
|
41
41
|
sendApprovedReports,
|
|
42
42
|
withGlobalCc
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-DGZWFI2G.js";
|
|
44
|
+
import "./chunk-BRYXFH77.js";
|
|
44
45
|
import "./chunk-NYUYNYQL.js";
|
|
45
46
|
import {
|
|
46
47
|
isPendingApproval
|
|
@@ -52,28 +53,28 @@ import {
|
|
|
52
53
|
} from "./chunk-VAD43MOR.js";
|
|
53
54
|
import {
|
|
54
55
|
fromAirtableBase
|
|
55
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-PR343K7F.js";
|
|
56
57
|
import "./chunk-IY6256GK.js";
|
|
57
58
|
import "./chunk-HN6I2XWI.js";
|
|
58
|
-
import "./chunk-
|
|
59
|
+
import "./chunk-XP5WM2LV.js";
|
|
59
60
|
import "./chunk-GECESMVS.js";
|
|
61
|
+
import {
|
|
62
|
+
domainFromUrl,
|
|
63
|
+
generateHeaderImage,
|
|
64
|
+
loadHeadline,
|
|
65
|
+
loadPlate
|
|
66
|
+
} from "./chunk-5UINJNAJ.js";
|
|
60
67
|
import {
|
|
61
68
|
BLURRED_CID,
|
|
62
69
|
CHECK_CID,
|
|
63
70
|
loadBundledImages,
|
|
64
71
|
renderReportHtml,
|
|
65
72
|
rendersCommentary
|
|
66
|
-
} from "./chunk-
|
|
73
|
+
} from "./chunk-KJGKZLCT.js";
|
|
67
74
|
import {
|
|
68
75
|
escapeHtml,
|
|
69
76
|
safeUrl
|
|
70
|
-
} from "./chunk-
|
|
71
|
-
import {
|
|
72
|
-
domainFromUrl,
|
|
73
|
-
generateHeaderImage,
|
|
74
|
-
loadHeadline,
|
|
75
|
-
loadPlate
|
|
76
|
-
} from "./chunk-5UINJNAJ.js";
|
|
77
|
+
} from "./chunk-BGTYPVLT.js";
|
|
77
78
|
import {
|
|
78
79
|
CANONICAL_STATUSES,
|
|
79
80
|
SEVERITY_RANK,
|
|
@@ -95,18 +96,18 @@ import {
|
|
|
95
96
|
runAudits,
|
|
96
97
|
runAuditsAcross,
|
|
97
98
|
securityAudit
|
|
98
|
-
} from "./chunk-
|
|
99
|
+
} from "./chunk-B23FFWU4.js";
|
|
99
100
|
import "./chunk-LKGVSM2O.js";
|
|
100
101
|
import "./chunk-GASBX52O.js";
|
|
101
102
|
import "./chunk-XHHMM5HX.js";
|
|
102
103
|
import {
|
|
103
104
|
fromJsonFile,
|
|
104
105
|
localPath
|
|
105
|
-
} from "./chunk-
|
|
106
|
-
import "./chunk-
|
|
107
|
-
import "./chunk-
|
|
108
|
-
import "./chunk-
|
|
109
|
-
import "./chunk-
|
|
106
|
+
} from "./chunk-Z3JXLULD.js";
|
|
107
|
+
import "./chunk-W45FZEGE.js";
|
|
108
|
+
import "./chunk-RARREDQE.js";
|
|
109
|
+
import "./chunk-WS6NU475.js";
|
|
110
|
+
import "./chunk-MFHS7IQ2.js";
|
|
110
111
|
import "./chunk-A3WDWNN5.js";
|
|
111
112
|
import {
|
|
112
113
|
isLeadFormType
|
|
@@ -121,7 +122,7 @@ import "./chunk-LBYOLBW7.js";
|
|
|
121
122
|
import "./chunk-XXTZBPUY.js";
|
|
122
123
|
import "./chunk-6VAL7XJT.js";
|
|
123
124
|
import "./chunk-NMAWXBJA.js";
|
|
124
|
-
import "./chunk-
|
|
125
|
+
import "./chunk-O3GT46R2.js";
|
|
125
126
|
|
|
126
127
|
// src/recipes/index.ts
|
|
127
128
|
var ALL_RECIPE_NAMES = [
|
|
@@ -324,6 +325,7 @@ var WATCH_CONDITION_OPTIONS = [
|
|
|
324
325
|
"stale repo",
|
|
325
326
|
"no custom domain"
|
|
326
327
|
];
|
|
328
|
+
var CLEAR_SECRET = "__clear__";
|
|
327
329
|
|
|
328
330
|
// src/dashboard/render.ts
|
|
329
331
|
var DASH = "\u2014";
|
|
@@ -615,8 +617,26 @@ var DETAIL_VALUE_FN = `function detailValue(el) {
|
|
|
615
617
|
if (el.multiple) return Array.from(el.selectedOptions).map((o) => o.value).join(",");
|
|
616
618
|
return el.value;
|
|
617
619
|
}`;
|
|
620
|
+
var SAVE_DETAIL_FN = `function saveDetail(el, root) {
|
|
621
|
+
const span = root.querySelector('.detail-saved[data-for="' + el.dataset.detailField + '"]');
|
|
622
|
+
return fetch(el.dataset.detailsUrl, {
|
|
623
|
+
method: "POST",
|
|
624
|
+
headers: { "content-type": "application/json" },
|
|
625
|
+
body: JSON.stringify({ field: el.dataset.detailField, value: detailValue(el) }),
|
|
626
|
+
})
|
|
627
|
+
.then((r) => {
|
|
628
|
+
if (span) span.textContent = r.ok ? " \\u2713" : " \\u2717";
|
|
629
|
+
if (r.ok && "defaultValue" in el) el.defaultValue = el.value;
|
|
630
|
+
})
|
|
631
|
+
.catch(() => {
|
|
632
|
+
if (span) span.textContent = " \\u2717";
|
|
633
|
+
});
|
|
634
|
+
}`;
|
|
635
|
+
function toDateInputValue(value) {
|
|
636
|
+
return (value ?? "").slice(0, 10);
|
|
637
|
+
}
|
|
618
638
|
function dateRow(label, field, value, url) {
|
|
619
|
-
return `<div class="detail"><dt><label for="detail-${field}">${escapeHtml(label)}</label></dt><dd><input type="date" id="detail-${field}" data-detail-field="${field}" data-details-url="${url}" value="${escapeHtml(value
|
|
639
|
+
return `<div class="detail"><dt><label for="detail-${field}">${escapeHtml(label)}</label></dt><dd><input type="date" id="detail-${field}" data-detail-field="${field}" data-details-url="${url}" value="${escapeHtml(toDateInputValue(value))}" />${savedSpan(field)}</dd></div>`;
|
|
620
640
|
}
|
|
621
641
|
function checkboxRow(label, field, checked, url) {
|
|
622
642
|
return `<div class="detail"><dt><label for="detail-${field}">${escapeHtml(label)}</label></dt><dd><input type="checkbox" id="detail-${field}" data-detail-field="${field}" data-details-url="${url}"${checked ? " checked" : ""} />${savedSpan(field)}</dd></div>`;
|
|
@@ -629,7 +649,7 @@ function multiSelectRow(label, field, options, selected, url) {
|
|
|
629
649
|
return `<div class="detail wide"><dt><label for="detail-${field}">${escapeHtml(label)}</label></dt><dd><select multiple id="detail-${field}" data-detail-field="${field}" data-details-url="${url}">${opts}</select>${savedSpan(field)}</dd></div>`;
|
|
630
650
|
}
|
|
631
651
|
function secretRow(label, field, isSet, url) {
|
|
632
|
-
const ph = isSet ?
|
|
652
|
+
const ph = isSet ? `\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022 (type to replace, or ${CLEAR_SECRET} to erase)` : "not set";
|
|
633
653
|
return `<div class="detail"><dt><label for="detail-${field}">${escapeHtml(label)}</label></dt><dd><input type="password" autocomplete="off" id="detail-${field}" data-detail-field="${field}" data-details-url="${url}" placeholder="${escapeHtml(ph)}" />${savedSpan(field)}</dd></div>`;
|
|
634
654
|
}
|
|
635
655
|
function textareaRow(label, field, value, url) {
|
|
@@ -899,9 +919,18 @@ function renderSiteDashboardHtml(site, reports, submissions = [], spamTotals = n
|
|
|
899
919
|
${spamScreenSection(spamTotals, submissions, now)}
|
|
900
920
|
${submissionsSection(submissions, site)}
|
|
901
921
|
<script>
|
|
922
|
+
// The SAME pending report is rendered twice \u2014 once in the pending list, once
|
|
923
|
+
// in the reports history \u2014 so one report id owns two Approve buttons. Every
|
|
924
|
+
// state change has to reach both, or the two places you look disagree: one
|
|
925
|
+
// says "Approved", the other still says "Approve" and (with the gate clear)
|
|
926
|
+
// is still enabled.
|
|
927
|
+
function approveButtonsFor(id) {
|
|
928
|
+
return Array.from(document.querySelectorAll('button.approve[data-report-id="' + id + '"]'));
|
|
929
|
+
}
|
|
902
930
|
document.querySelectorAll("button.approve").forEach((b) => {
|
|
903
931
|
b.addEventListener("click", async () => {
|
|
904
|
-
|
|
932
|
+
const twins = approveButtonsFor(b.dataset.reportId);
|
|
933
|
+
twins.forEach((t) => { t.disabled = true; });
|
|
905
934
|
// is-loading swaps the label for a spinner; aria-busy is the same news for a
|
|
906
935
|
// screen reader, which cannot see it. Both are cleared in the finally below so
|
|
907
936
|
// no exit path can strand the button spinning forever.
|
|
@@ -910,30 +939,41 @@ function renderSiteDashboardHtml(site, reports, submissions = [], spamTotals = n
|
|
|
910
939
|
try {
|
|
911
940
|
const res = await fetch(b.dataset.approveUrl, { method: "POST" });
|
|
912
941
|
if (res.ok) {
|
|
913
|
-
|
|
914
|
-
//
|
|
915
|
-
//
|
|
916
|
-
|
|
942
|
+
// Terminal success: darker green, and they stay disabled (approving twice
|
|
943
|
+
// is a no-op server-side, but the button should not invite the second
|
|
944
|
+
// click). Applied to every twin, not just the one that was clicked.
|
|
945
|
+
twins.forEach((t) => {
|
|
946
|
+
t.textContent = "Approved";
|
|
947
|
+
t.classList.add("is-approved");
|
|
948
|
+
});
|
|
917
949
|
} else {
|
|
918
950
|
// A 409 carries { reason, blockers } \u2014 surface WHY instead of a bare
|
|
919
951
|
// "Failed" next to a possibly-stale green chip. textContent/title
|
|
920
952
|
// assignment only (no innerHTML), so server strings stay inert.
|
|
921
953
|
const data = await res.json().catch(() => null);
|
|
922
|
-
|
|
954
|
+
const label = data && data.reason === "send-blocked" ? "Blocked" : "Failed";
|
|
923
955
|
// Double-backslash, NOT single: this whole block lives inside the renderer's
|
|
924
956
|
// template literal, so a single-backslash escape is consumed HERE at build
|
|
925
957
|
// time and emits a real newline into the served HTML \u2014 an unterminated string
|
|
926
958
|
// literal that kills the parse of the entire script element, taking every
|
|
927
959
|
// handler on the page with it (exactly how approve/override/renovate went
|
|
928
960
|
// dead). Note this comment must also avoid backticks for the same reason.
|
|
929
|
-
|
|
930
|
-
|
|
961
|
+
const title = data && Array.isArray(data.blockers) ? data.blockers.join("\\n") : null;
|
|
962
|
+
// Re-enable EVERY twin, not just the clicked one \u2014 leaving the other
|
|
963
|
+
// disabled would strand it reading "Approve" and unusable.
|
|
964
|
+
twins.forEach((t) => {
|
|
965
|
+
t.textContent = label;
|
|
966
|
+
if (title !== null) t.title = title;
|
|
967
|
+
t.disabled = false;
|
|
968
|
+
});
|
|
931
969
|
}
|
|
932
970
|
} catch {
|
|
933
971
|
// Network rejection (offline, DNS, abort): mirror the !res.ok path so
|
|
934
|
-
// the
|
|
935
|
-
|
|
936
|
-
|
|
972
|
+
// the buttons don't sit permanently disabled reading "Approve".
|
|
973
|
+
twins.forEach((t) => {
|
|
974
|
+
t.textContent = "Failed";
|
|
975
|
+
t.disabled = false;
|
|
976
|
+
});
|
|
937
977
|
} finally {
|
|
938
978
|
b.classList.remove("is-loading");
|
|
939
979
|
b.removeAttribute("aria-busy");
|
|
@@ -946,7 +986,12 @@ function renderSiteDashboardHtml(site, reports, submissions = [], spamTotals = n
|
|
|
946
986
|
// refused client-side AND server-side).
|
|
947
987
|
document.querySelectorAll("button.override-toggle").forEach((b) => {
|
|
948
988
|
b.addEventListener("click", () => {
|
|
949
|
-
|
|
989
|
+
// Scoped lookup, not nextElementSibling: adjacency is a markup accident,
|
|
990
|
+
// and anything inserted between the toggle and its form would kill "Send
|
|
991
|
+
// anyway\u2026" silently. The submit handler six lines down already uses
|
|
992
|
+
// closest(); this is the same contract from the other end.
|
|
993
|
+
const wrap = b.closest(".override");
|
|
994
|
+
const form = wrap ? wrap.querySelector(".override-form") : null;
|
|
950
995
|
if (form) form.hidden = !form.hidden;
|
|
951
996
|
});
|
|
952
997
|
});
|
|
@@ -971,13 +1016,14 @@ function renderSiteDashboardHtml(site, reports, submissions = [], spamTotals = n
|
|
|
971
1016
|
if (res.ok) {
|
|
972
1017
|
if (status) status.textContent = "Overridden \u2713";
|
|
973
1018
|
if (input) input.disabled = true;
|
|
974
|
-
// Reflect the send-anyway state on the row's own Approve
|
|
975
|
-
//
|
|
976
|
-
// "waiting on you" once an override has gone through.
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
)
|
|
980
|
-
|
|
1019
|
+
// Reflect the send-anyway state on the row's own Approve buttons too \u2014
|
|
1020
|
+
// they stay server-gated disabled, but the label should stop implying
|
|
1021
|
+
// "waiting on you" once an override has gone through. ALL of them: the
|
|
1022
|
+
// same report is rendered in both the pending list and the history, so
|
|
1023
|
+
// a singular querySelector updated the first and left the second lying.
|
|
1024
|
+
approveButtonsFor(b.dataset.reportId).forEach((t) => {
|
|
1025
|
+
t.textContent = "Overridden";
|
|
1026
|
+
});
|
|
981
1027
|
} else {
|
|
982
1028
|
// A 409 carries { reason, blockers } \u2014 surface WHY (textContent only,
|
|
983
1029
|
// never innerHTML, so server strings stay inert).
|
|
@@ -1016,32 +1062,27 @@ function renderSiteDashboardHtml(site, reports, submissions = [], spamTotals = n
|
|
|
1016
1062
|
// Site-details editor: save on change (selects) / blur (inputs+textareas, only
|
|
1017
1063
|
// when the value actually changed). The per-field span shows \u2713 / \u2717.
|
|
1018
1064
|
${DETAIL_VALUE_FN}
|
|
1019
|
-
|
|
1020
|
-
const span = document.querySelector('.detail-saved[data-for="' + el.dataset.detailField + '"]');
|
|
1021
|
-
fetch(el.dataset.detailsUrl, {
|
|
1022
|
-
method: "POST",
|
|
1023
|
-
headers: { "content-type": "application/json" },
|
|
1024
|
-
body: JSON.stringify({ field: el.dataset.detailField, value: detailValue(el) }),
|
|
1025
|
-
})
|
|
1026
|
-
.then((r) => {
|
|
1027
|
-
if (span) span.textContent = r.ok ? " \u2713" : " \u2717";
|
|
1028
|
-
})
|
|
1029
|
-
.catch(() => {
|
|
1030
|
-
if (span) span.textContent = " \u2717";
|
|
1031
|
-
});
|
|
1032
|
-
}
|
|
1065
|
+
${SAVE_DETAIL_FN}
|
|
1033
1066
|
document.querySelectorAll("select[data-detail-field]").forEach((s) => {
|
|
1034
|
-
s.addEventListener("change", () => saveDetail(s));
|
|
1067
|
+
s.addEventListener("change", () => saveDetail(s, document));
|
|
1035
1068
|
});
|
|
1036
1069
|
// Checkboxes save on CHANGE. They cannot use the blur path below: that path
|
|
1037
1070
|
// only fires when \`value !== defaultValue\`, and for a checkbox both are the
|
|
1038
1071
|
// "on" content attribute, so it would never save at all.
|
|
1039
1072
|
document.querySelectorAll("input[type=checkbox][data-detail-field]").forEach((c) => {
|
|
1040
|
-
c.addEventListener("change", () => saveDetail(c));
|
|
1073
|
+
c.addEventListener("change", () => saveDetail(c, document));
|
|
1041
1074
|
});
|
|
1042
1075
|
document.querySelectorAll("input:not([type=checkbox])[data-detail-field], textarea[data-detail-field]").forEach((i) => {
|
|
1076
|
+
// A save needs BOTH a real edit gesture and a changed value. \`value !==
|
|
1077
|
+
// defaultValue\` alone is not evidence of an edit: the browser rewrites
|
|
1078
|
+
// \`.value\` on its own when a control is handed a value it cannot represent
|
|
1079
|
+
// (an ISO datetime in a date input sanitizes to ""), and the blur that
|
|
1080
|
+
// follows an untouched tab-through then posts that empty string as if the
|
|
1081
|
+
// operator had cleared the field. Requiring an \`input\` event first means
|
|
1082
|
+
// only a human keystroke or picker choice can arm the save.
|
|
1083
|
+
i.addEventListener("input", () => { i.dataset.edited = "1"; });
|
|
1043
1084
|
i.addEventListener("blur", () => {
|
|
1044
|
-
if (i.value !== i.defaultValue) saveDetail(i);
|
|
1085
|
+
if (i.dataset.edited === "1" && i.value !== i.defaultValue) saveDetail(i, document);
|
|
1045
1086
|
});
|
|
1046
1087
|
});
|
|
1047
1088
|
// Refresh preview: dispatch the re-render workflow, then tell the operator to
|
|
@@ -1609,7 +1650,8 @@ function renderInboxLane(model) {
|
|
|
1609
1650
|
}).join("");
|
|
1610
1651
|
const overflow = subs.length - shown.length;
|
|
1611
1652
|
const more = overflow > 0 ? `<div class="approve-row subm-more muted"><a href="/submissions">+${overflow} more \u2014 view all submissions</a></div>` : `<div class="approve-row subm-more muted"><a href="/submissions">View all submissions \u2192</a></div>`;
|
|
1612
|
-
const
|
|
1653
|
+
const spamAsOf = spam?.computedAt !== void 0 ? ` \xB7 as of ${escapeHtml(spam.computedAt.slice(0, 16).replace("T", " "))} UTC` : "";
|
|
1654
|
+
const spamLine = hasSpam ? `<div class="spam-rollup muted">\u{1F6E1} Spam (30d) \u2014 caught ${spam.caught} \xB7 through ${spam.through}${spamAsOf}</div>` : "";
|
|
1613
1655
|
const spamInSummary = hasSpam ? " \xB7 \u{1F6E1} Spam (30d)" : "";
|
|
1614
1656
|
const autoFilteredLine = hasAutoFiltered ? `<div class="spam-rollup muted">\u{1F6AB} ${autoFiltered} auto-filtered this week \u2014 <a href="/submissions?status=spam_auto">review</a></div>` : "";
|
|
1615
1657
|
const leads = subs.filter((s) => isLeadFormType(s.formType)).length;
|
|
@@ -1634,41 +1676,38 @@ var AUDIT_SCRIPT = `<script>
|
|
|
1634
1676
|
function rfStop(){ try { localStorage.removeItem(RF_KEY); } catch(e){} }
|
|
1635
1677
|
// Per-workflow ETA hint (the lighthouse fleet sweep runs ~48 min; security ~1-2 min).
|
|
1636
1678
|
function rfEta(label){ return label === 'lighthouse' ? '~48m' : label === 'security' ? '~2m' : ''; }
|
|
1637
|
-
//
|
|
1638
|
-
//
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1679
|
+
// Escape before interpolating. The values here are server-side enums and a
|
|
1680
|
+
// GitHub html_url today, but this builds HTML from a REMOTE API response, and
|
|
1681
|
+
// the house rule everywhere else on these pages is that no server string ever
|
|
1682
|
+
// reaches the browser as markup. Cheaper to escape than to keep re-deciding.
|
|
1683
|
+
function rfEsc(v){
|
|
1684
|
+
return String(v == null ? '' : v)
|
|
1685
|
+
.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')
|
|
1686
|
+
.replace(/"/g,'"').replace(/'/g,''');
|
|
1687
|
+
}
|
|
1688
|
+
// A run link must be a GitHub run URL. Anything else is dropped rather than
|
|
1689
|
+
// rendered, so a compromised or unexpected API response cannot place an
|
|
1690
|
+
// arbitrary href (javascript:, data:, an attacker's host) in the panel.
|
|
1691
|
+
function rfRunUrl(u){
|
|
1692
|
+
return typeof u === 'string' && u.indexOf('https://github.com/') === 0 ? rfEsc(u) : '';
|
|
1648
1693
|
}
|
|
1649
|
-
// Safe to build raw HTML: workflow/state/step are server-fixed (enums + GitHub step
|
|
1650
|
-
// names) and url is GitHub's own html_url for our central repo \u2014 none are
|
|
1651
|
-
// user-supplied. Don't interpolate untrusted fields here without escaping.
|
|
1652
1694
|
function rfRender(status, startedAt){
|
|
1653
1695
|
var failed = function(s){ return s === 'failure' || s === 'cancelled' || s === 'timed_out'; };
|
|
1654
1696
|
var mins = Math.floor((Date.now() - startedAt) / 60000);
|
|
1655
1697
|
var elapsed = mins < 1 ? '<1m' : mins + 'm';
|
|
1656
1698
|
return status.perWorkflow.map(function(w){
|
|
1657
|
-
var label = w.workflow.replace('.yml','').replace('fleet-','');
|
|
1699
|
+
var label = String(w.workflow || '').replace('.yml','').replace('fleet-','');
|
|
1658
1700
|
var done = w.state === 'success';
|
|
1659
1701
|
var isFailed = failed(w.state);
|
|
1660
1702
|
var icon = done ? '\u2713' : isFailed ? '\u2717' : '<span class="rf-spin"></span>';
|
|
1661
|
-
var line = icon + ' ' + label + ' \u2014 ' + w.state.replace('_',' ');
|
|
1703
|
+
var line = icon + ' ' + rfEsc(label) + ' \u2014 ' + rfEsc(String(w.state || '').replace('_',' '));
|
|
1704
|
+
var url = rfRunUrl(w.url);
|
|
1662
1705
|
if (!done && !isFailed){
|
|
1663
|
-
var phase = rfPhase(w.step);
|
|
1664
1706
|
var eta = rfEta(label);
|
|
1665
|
-
var
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
line += '<div class="rf-sub">' + detail.join(' \xB7 ') + link + '</div>';
|
|
1670
|
-
} else if (isFailed && w.url){
|
|
1671
|
-
line += ' <a href="'+w.url+'" target="_blank" rel="noopener">run</a>';
|
|
1707
|
+
var link = url ? ' \xB7 <a href="'+url+'" target="_blank" rel="noopener">view run \u2197</a>' : '';
|
|
1708
|
+
line += '<div class="rf-sub">' + elapsed + (eta ? ' / ' + eta : '') + link + '</div>';
|
|
1709
|
+
} else if (isFailed && url){
|
|
1710
|
+
line += ' <a href="'+url+'" target="_blank" rel="noopener">run</a>';
|
|
1672
1711
|
}
|
|
1673
1712
|
return '<div class="rf-row">' + line + '</div>';
|
|
1674
1713
|
}).join('');
|
|
@@ -1797,6 +1836,8 @@ var SESSION_TTL_SECONDS = 30 * 24 * 60 * 60;
|
|
|
1797
1836
|
|
|
1798
1837
|
// src/dashboard/prospect-audit-trigger.ts
|
|
1799
1838
|
var PROSPECT_AUDIT_DUPLICATE_WINDOW_MS = 10 * 60 * 1e3;
|
|
1839
|
+
var PROSPECT_AUDIT_DAILY_CAP = 25;
|
|
1840
|
+
var DAILY_CAP_LOOKBACK = PROSPECT_AUDIT_DAILY_CAP * 2;
|
|
1800
1841
|
export {
|
|
1801
1842
|
ALL_AUDIT_NAMES,
|
|
1802
1843
|
ALL_RECIPE_NAMES,
|