@reddoorla/maintenance 0.60.1 → 0.62.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/airtable-HHAWHBNM.js +9 -0
- package/dist/{announce-Q2DXP5OO.js → announce-54EBITHX.js} +16 -6
- package/dist/announce-54EBITHX.js.map +1 -0
- package/dist/{bump-deps-RYHQIKW4.js → bump-deps-2U6GIHM5.js} +2 -2
- package/dist/{chunk-BWLUNY7L.js → chunk-2T3HZ3DB.js} +24 -14
- package/dist/chunk-2T3HZ3DB.js.map +1 -0
- package/dist/{chunk-D3LFQNJK.js → chunk-3HM4S422.js} +2 -2
- package/dist/{chunk-LMXIZITU.js → chunk-4REUHZAW.js} +40 -4
- package/dist/chunk-4REUHZAW.js.map +1 -0
- package/dist/chunk-5L3FPQIJ.js +92 -0
- package/dist/chunk-5L3FPQIJ.js.map +1 -0
- package/dist/{client-JA6QLEDW.js → chunk-6ZKM7OIJ.js} +5 -4
- package/dist/{chunk-KC4HU7BC.js → chunk-76VIUWR3.js} +31 -6
- package/dist/chunk-76VIUWR3.js.map +1 -0
- package/dist/{chunk-L4ULXHIN.js → chunk-BLD6AYJO.js} +15 -1
- package/dist/chunk-BLD6AYJO.js.map +1 -0
- package/dist/chunk-BMDBOPN3.js +211 -0
- package/dist/chunk-BMDBOPN3.js.map +1 -0
- package/dist/{chunk-HB7QPECR.js → chunk-KTPPKO2C.js} +6 -6
- package/dist/chunk-L5F664ZA.js +45 -0
- package/dist/chunk-L5F664ZA.js.map +1 -0
- package/dist/{chunk-XB6T4ETK.js → chunk-OSRFDGQM.js} +21 -1
- package/dist/chunk-OSRFDGQM.js.map +1 -0
- package/dist/{chunk-6CSCLBAA.js → chunk-SAOCVNX7.js} +2 -2
- package/dist/{chunk-5PHCZ6L5.js → chunk-SS7BKOIX.js} +89 -2
- package/dist/chunk-SS7BKOIX.js.map +1 -0
- package/dist/{chunk-TZN2G6FD.js → chunk-TYRCYHRA.js} +3 -3
- package/dist/{chunk-HG5DTFNF.js → chunk-XKKTK6WA.js} +17 -1
- package/dist/chunk-XKKTK6WA.js.map +1 -0
- package/dist/{chunk-JNXCDUYB.js → chunk-XVR7UM6Z.js} +4 -4
- package/dist/{chunk-HPSGCDWY.js → chunk-YYUY2OVP.js} +2 -2
- package/dist/{chunk-TLZ4V2BE.js → chunk-ZEX2T6JH.js} +3 -2
- package/dist/{chunk-TLZ4V2BE.js.map → chunk-ZEX2T6JH.js.map} +1 -1
- package/dist/cli/bin.js +16 -15
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.d.ts +3 -2
- package/dist/cli/commands/audit.js +29 -8
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/client-GTMJTXBK.js +11 -0
- package/dist/{convert-to-pnpm-KNCXU6FM.js → convert-to-pnpm-JG3767ZQ.js} +2 -2
- package/dist/{db-AXKMRTVA.js → db-Y6BVTNH7.js} +3 -3
- package/dist/{digest-RVIOHUXR.js → digest-ABJROO4H.js} +13 -197
- package/dist/digest-ABJROO4H.js.map +1 -0
- package/dist/{github-signals-UL2BBS3Q.js → github-signals-XTJLKOXJ.js} +26 -5
- package/dist/github-signals-XTJLKOXJ.js.map +1 -0
- package/dist/index.d.ts +62 -5
- package/dist/index.js +345 -206
- package/dist/index.js.map +1 -1
- package/dist/{init-KD56UENX.js → init-DMAIKUQE.js} +8 -8
- package/dist/{launch-UWCI3JRM.js → launch-UZPPB4IE.js} +10 -9
- package/dist/{launch-UWCI3JRM.js.map → launch-UZPPB4IE.js.map} +1 -1
- package/dist/{lighthouse-airtable-N3KCBPVA.js → lighthouse-airtable-HTEW6BBT.js} +3 -3
- package/dist/migrate-BFOG2OCP.js +7 -0
- package/dist/{onboard-SCZ3UKZ2.js → onboard-TTIC3YZI.js} +2 -2
- package/dist/{orchestrate-D72PB6NG.js → orchestrate-5GGOL5EX.js} +7 -4
- package/dist/recipes/sync-configs.d.ts +1 -1
- package/dist/recipes/sync-configs.js +2 -2
- package/dist/{renovate-dispatch-NG5KUQKY.js → renovate-dispatch-QDDMTATD.js} +3 -3
- package/dist/{report-CN56TGMK.js → report-SZGZIDDG.js} +6 -6
- package/dist/{self-updating-CFHXJSAX.js → self-updating-2RLUP4AA.js} +4 -4
- package/dist/{svelte-codemods-HNITLWQM.js → svelte-codemods-BPLKSVIA.js} +2 -2
- package/dist/{sync-configs-BZXBYKTI.js → sync-configs-VFRV3TJ4.js} +4 -4
- package/dist/{types-QG-QhCYh.d.ts → types-Dg4mHvyf.d.ts} +6 -1
- package/dist/{upgrade-6YDUELFA.js → upgrade-WCFD2TRN.js} +2 -2
- package/dist/{websites-IYP7IGLU.js → websites-HM2NMZMR.js} +2 -2
- package/dist/{write-audits-to-airtable-VDGHCZGQ.js → write-audits-to-airtable-QF26PCAR.js} +5 -4
- package/dist/write-audits-to-airtable-QF26PCAR.js.map +1 -0
- package/package.json +1 -1
- package/dist/airtable-YZ6VMSQZ.js +0 -9
- package/dist/announce-Q2DXP5OO.js.map +0 -1
- package/dist/chunk-5PHCZ6L5.js.map +0 -1
- package/dist/chunk-BWLUNY7L.js.map +0 -1
- package/dist/chunk-HG5DTFNF.js.map +0 -1
- package/dist/chunk-KC4HU7BC.js.map +0 -1
- package/dist/chunk-L4ULXHIN.js.map +0 -1
- package/dist/chunk-LMXIZITU.js.map +0 -1
- package/dist/chunk-XB6T4ETK.js.map +0 -1
- package/dist/digest-RVIOHUXR.js.map +0 -1
- package/dist/github-signals-UL2BBS3Q.js.map +0 -1
- package/dist/migrate-P2LS4WXI.js +0 -7
- /package/dist/{airtable-YZ6VMSQZ.js.map → airtable-HHAWHBNM.js.map} +0 -0
- /package/dist/{bump-deps-RYHQIKW4.js.map → bump-deps-2U6GIHM5.js.map} +0 -0
- /package/dist/{chunk-D3LFQNJK.js.map → chunk-3HM4S422.js.map} +0 -0
- /package/dist/{client-JA6QLEDW.js.map → chunk-6ZKM7OIJ.js.map} +0 -0
- /package/dist/{chunk-HB7QPECR.js.map → chunk-KTPPKO2C.js.map} +0 -0
- /package/dist/{chunk-6CSCLBAA.js.map → chunk-SAOCVNX7.js.map} +0 -0
- /package/dist/{chunk-TZN2G6FD.js.map → chunk-TYRCYHRA.js.map} +0 -0
- /package/dist/{chunk-JNXCDUYB.js.map → chunk-XVR7UM6Z.js.map} +0 -0
- /package/dist/{chunk-HPSGCDWY.js.map → chunk-YYUY2OVP.js.map} +0 -0
- /package/dist/{lighthouse-airtable-N3KCBPVA.js.map → client-GTMJTXBK.js.map} +0 -0
- /package/dist/{convert-to-pnpm-KNCXU6FM.js.map → convert-to-pnpm-JG3767ZQ.js.map} +0 -0
- /package/dist/{db-AXKMRTVA.js.map → db-Y6BVTNH7.js.map} +0 -0
- /package/dist/{init-KD56UENX.js.map → init-DMAIKUQE.js.map} +0 -0
- /package/dist/{migrate-P2LS4WXI.js.map → lighthouse-airtable-HTEW6BBT.js.map} +0 -0
- /package/dist/{orchestrate-D72PB6NG.js.map → migrate-BFOG2OCP.js.map} +0 -0
- /package/dist/{onboard-SCZ3UKZ2.js.map → onboard-TTIC3YZI.js.map} +0 -0
- /package/dist/{websites-IYP7IGLU.js.map → orchestrate-5GGOL5EX.js.map} +0 -0
- /package/dist/{renovate-dispatch-NG5KUQKY.js.map → renovate-dispatch-QDDMTATD.js.map} +0 -0
- /package/dist/{report-CN56TGMK.js.map → report-SZGZIDDG.js.map} +0 -0
- /package/dist/{self-updating-CFHXJSAX.js.map → self-updating-2RLUP4AA.js.map} +0 -0
- /package/dist/{svelte-codemods-HNITLWQM.js.map → svelte-codemods-BPLKSVIA.js.map} +0 -0
- /package/dist/{sync-configs-BZXBYKTI.js.map → sync-configs-VFRV3TJ4.js.map} +0 -0
- /package/dist/{upgrade-6YDUELFA.js.map → upgrade-WCFD2TRN.js.map} +0 -0
- /package/dist/{write-audits-to-airtable-VDGHCZGQ.js.map → websites-HM2NMZMR.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,33 +1,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
findDueReports
|
|
3
|
+
} from "./chunk-WVRPOXKH.js";
|
|
4
|
+
import {
|
|
5
|
+
upgradeSvelte4to5
|
|
6
|
+
} from "./chunk-O65I7G7G.js";
|
|
1
7
|
import {
|
|
2
8
|
DEFAULT_INIT_STEPS,
|
|
3
9
|
a11yFixturesPage,
|
|
4
10
|
init
|
|
5
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-KTPPKO2C.js";
|
|
12
|
+
import {
|
|
13
|
+
convertToPnpm
|
|
14
|
+
} from "./chunk-2X322ZES.js";
|
|
6
15
|
import {
|
|
7
16
|
svelteCodemods
|
|
8
17
|
} from "./chunk-FUD4I2ZW.js";
|
|
18
|
+
import "./chunk-OQPMZ6BM.js";
|
|
9
19
|
import {
|
|
10
20
|
onboard,
|
|
11
21
|
selfCaretRange,
|
|
12
22
|
selfPackageVersion
|
|
13
23
|
} from "./chunk-ZYBOT5PM.js";
|
|
14
|
-
import {
|
|
15
|
-
findDueReports
|
|
16
|
-
} from "./chunk-WVRPOXKH.js";
|
|
17
24
|
import {
|
|
18
25
|
draftReportForSite
|
|
19
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-TYRCYHRA.js";
|
|
20
27
|
import "./chunk-6LIWDIXU.js";
|
|
28
|
+
import "./chunk-BMDBOPN3.js";
|
|
21
29
|
import {
|
|
22
30
|
sendApprovedReports
|
|
23
|
-
} from "./chunk-
|
|
24
|
-
import "./chunk-OGGRFBL4.js";
|
|
31
|
+
} from "./chunk-76VIUWR3.js";
|
|
25
32
|
import {
|
|
26
33
|
BLURRED_CID,
|
|
27
34
|
CHECK_CID,
|
|
28
35
|
loadBundledImages,
|
|
29
36
|
renderReportHtml
|
|
30
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-2T3HZ3DB.js";
|
|
38
|
+
import "./chunk-OGGRFBL4.js";
|
|
31
39
|
import {
|
|
32
40
|
checklistFor,
|
|
33
41
|
escapeHtml,
|
|
@@ -38,18 +46,11 @@ import {
|
|
|
38
46
|
import {
|
|
39
47
|
bumpDeps
|
|
40
48
|
} from "./chunk-D3DQFC32.js";
|
|
41
|
-
import {
|
|
42
|
-
upgradeSvelte4to5
|
|
43
|
-
} from "./chunk-O65I7G7G.js";
|
|
44
|
-
import "./chunk-OQPMZ6BM.js";
|
|
45
|
-
import {
|
|
46
|
-
convertToPnpm
|
|
47
|
-
} from "./chunk-2X322ZES.js";
|
|
48
49
|
import {
|
|
49
50
|
syncConfigs
|
|
50
|
-
} from "./chunk-
|
|
51
|
-
import "./chunk-CASCWQQX.js";
|
|
51
|
+
} from "./chunk-XVR7UM6Z.js";
|
|
52
52
|
import "./chunk-XKOZUVK2.js";
|
|
53
|
+
import "./chunk-CASCWQQX.js";
|
|
53
54
|
import "./chunk-OTGB6YQO.js";
|
|
54
55
|
import {
|
|
55
56
|
ALL_AUDIT_NAMES,
|
|
@@ -60,8 +61,11 @@ import {
|
|
|
60
61
|
runAudits,
|
|
61
62
|
runAuditsAcross,
|
|
62
63
|
securityAudit
|
|
63
|
-
} from "./chunk-
|
|
64
|
+
} from "./chunk-SS7BKOIX.js";
|
|
64
65
|
import "./chunk-LKGVSM2O.js";
|
|
66
|
+
import "./chunk-L5F664ZA.js";
|
|
67
|
+
import "./chunk-6ZKM7OIJ.js";
|
|
68
|
+
import "./chunk-XKKTK6WA.js";
|
|
65
69
|
import {
|
|
66
70
|
fromJsonFile,
|
|
67
71
|
localPath
|
|
@@ -71,12 +75,12 @@ import "./chunk-XXTZBPUY.js";
|
|
|
71
75
|
import "./chunk-XTK5VIQB.js";
|
|
72
76
|
import {
|
|
73
77
|
fromAirtableBase
|
|
74
|
-
} from "./chunk-
|
|
78
|
+
} from "./chunk-ZEX2T6JH.js";
|
|
75
79
|
import "./chunk-U5SZ4FOP.js";
|
|
76
80
|
import {
|
|
77
81
|
SEVERITY_RANK,
|
|
78
82
|
siteSlug
|
|
79
|
-
} from "./chunk-
|
|
83
|
+
} from "./chunk-BLD6AYJO.js";
|
|
80
84
|
import "./chunk-HN6I2XWI.js";
|
|
81
85
|
import "./chunk-WEKL2HYJ.js";
|
|
82
86
|
import "./chunk-6VAL7XJT.js";
|
|
@@ -636,7 +640,94 @@ function renderSiteDashboardHtml(site, reports, submissions = [], spamTotals = n
|
|
|
636
640
|
</html>`;
|
|
637
641
|
}
|
|
638
642
|
|
|
639
|
-
// src/dashboard/fleet-
|
|
643
|
+
// src/dashboard/fleet-cockpit.ts
|
|
644
|
+
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
645
|
+
var READY_DEPLOY_STATES = /* @__PURE__ */ new Set(["ready"]);
|
|
646
|
+
var FAILED_DEPLOY_STATES = /* @__PURE__ */ new Set(["error", "failed", "rejected"]);
|
|
647
|
+
function isReadyDeployStatus(status) {
|
|
648
|
+
return status !== null && READY_DEPLOY_STATES.has(status.toLowerCase());
|
|
649
|
+
}
|
|
650
|
+
function isFailedDeployStatus(status) {
|
|
651
|
+
return status !== null && FAILED_DEPLOY_STATES.has(status.toLowerCase());
|
|
652
|
+
}
|
|
653
|
+
var NEEDS_YOU_GROUP_RANK = { broken: 0, approval: 1, slipping: 2 };
|
|
654
|
+
function buildNeedsYouFeed(model) {
|
|
655
|
+
const bySlug = /* @__PURE__ */ new Map();
|
|
656
|
+
const get = (siteName) => {
|
|
657
|
+
const slug = siteSlug(siteName);
|
|
658
|
+
let a = bySlug.get(slug);
|
|
659
|
+
if (!a) {
|
|
660
|
+
a = {
|
|
661
|
+
slug,
|
|
662
|
+
siteName,
|
|
663
|
+
reasons: [],
|
|
664
|
+
hasCritical: false,
|
|
665
|
+
broken: false,
|
|
666
|
+
approval: false,
|
|
667
|
+
slipping: false
|
|
668
|
+
};
|
|
669
|
+
bySlug.set(slug, a);
|
|
670
|
+
}
|
|
671
|
+
return a;
|
|
672
|
+
};
|
|
673
|
+
for (const card2 of model.cards) {
|
|
674
|
+
if (card2.tier === "attention") {
|
|
675
|
+
for (const item of card2.items) {
|
|
676
|
+
if (item.kind === "vuln" && item.autoFixExhausted !== true) continue;
|
|
677
|
+
const a = get(card2.site.name);
|
|
678
|
+
a.reasons.push(item.title);
|
|
679
|
+
a.broken = true;
|
|
680
|
+
if (item.severity === "critical") a.hasCritical = true;
|
|
681
|
+
}
|
|
682
|
+
} else if (card2.tier === "watch" && card2.watchReasons.length > 0) {
|
|
683
|
+
const a = get(card2.site.name);
|
|
684
|
+
for (const r of card2.watchReasons) a.reasons.push(r);
|
|
685
|
+
a.slipping = true;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
for (const p of model.pending) {
|
|
689
|
+
const a = get(p.siteName);
|
|
690
|
+
a.reasons.push(`${p.reportType} ${p.period} ready`);
|
|
691
|
+
a.approval = true;
|
|
692
|
+
}
|
|
693
|
+
const items = [];
|
|
694
|
+
for (const a of bySlug.values()) {
|
|
695
|
+
if (a.reasons.length === 0) continue;
|
|
696
|
+
const group = a.broken ? "broken" : a.approval ? "approval" : "slipping";
|
|
697
|
+
items.push({
|
|
698
|
+
group,
|
|
699
|
+
hasCritical: a.hasCritical,
|
|
700
|
+
slug: a.slug,
|
|
701
|
+
siteName: a.siteName,
|
|
702
|
+
reasons: a.reasons,
|
|
703
|
+
url: `/s/${a.slug}`
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
items.sort((x, y) => {
|
|
707
|
+
if (NEEDS_YOU_GROUP_RANK[x.group] !== NEEDS_YOU_GROUP_RANK[y.group])
|
|
708
|
+
return NEEDS_YOU_GROUP_RANK[x.group] - NEEDS_YOU_GROUP_RANK[y.group];
|
|
709
|
+
if (x.group === "broken" && x.hasCritical !== y.hasCritical) return x.hasCritical ? -1 : 1;
|
|
710
|
+
return x.siteName.toLowerCase().localeCompare(y.siteName.toLowerCase());
|
|
711
|
+
});
|
|
712
|
+
return items;
|
|
713
|
+
}
|
|
714
|
+
function fleetLastAuditedAt(cards) {
|
|
715
|
+
let latestIso = null;
|
|
716
|
+
let latestMs = -Infinity;
|
|
717
|
+
for (const c of cards) {
|
|
718
|
+
const iso = c.site.lastLighthouseAuditAt;
|
|
719
|
+
if (!iso) continue;
|
|
720
|
+
const ms = Date.parse(iso);
|
|
721
|
+
if (!Number.isFinite(ms)) continue;
|
|
722
|
+
if (ms > latestMs) {
|
|
723
|
+
latestMs = ms;
|
|
724
|
+
latestIso = iso;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
return latestIso;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// src/dashboard/fleet-browse-render.ts
|
|
640
731
|
var DASH2 = "\u2014";
|
|
641
732
|
function scoreSpan(category, value) {
|
|
642
733
|
const display = value === null ? DASH2 : String(value);
|
|
@@ -696,10 +787,115 @@ function card(site) {
|
|
|
696
787
|
site.securityVulnsModerate,
|
|
697
788
|
site.securityVulnsLow
|
|
698
789
|
)}
|
|
790
|
+
<span class="metric-label">deploy</span> ${deployBadge(site)}
|
|
699
791
|
</span>
|
|
700
792
|
</div>
|
|
701
793
|
</article>`;
|
|
702
794
|
}
|
|
795
|
+
function deployBadge(site) {
|
|
796
|
+
const state = site.deployStatus;
|
|
797
|
+
if (state === null) {
|
|
798
|
+
return `<span class="metric deploy unknown" title="No deploy data">${DASH2}</span>`;
|
|
799
|
+
}
|
|
800
|
+
const cls = isReadyDeployStatus(state) ? "deploy ready" : isFailedDeployStatus(state) ? "deploy failed" : "deploy building";
|
|
801
|
+
const when = relativeTimeFromNow(site.lastDeployAt);
|
|
802
|
+
const label = escapeHtml(`${state}${when !== DASH2 ? ` \xB7 ${when}` : ""}`);
|
|
803
|
+
const url = site.deployLogUrl ? safeUrl(site.deployLogUrl) : "#";
|
|
804
|
+
if (url !== "#") {
|
|
805
|
+
return `<a class="metric ${cls}" href="${escapeHtml(url)}" target="_blank" rel="noopener">${label}</a>`;
|
|
806
|
+
}
|
|
807
|
+
return `<span class="metric ${cls}">${label}</span>`;
|
|
808
|
+
}
|
|
809
|
+
function submBadge(c) {
|
|
810
|
+
const n = c.newSubmissions ?? 0;
|
|
811
|
+
return n > 0 ? `<span class="chip">\u{1F4E5} ${n} new</span>` : "";
|
|
812
|
+
}
|
|
813
|
+
var PILL_LABEL = { attention: "failing", watch: "watch", healthy: "ok" };
|
|
814
|
+
function attentionBadge(status) {
|
|
815
|
+
if (status === "new") return `<span class="badge">NEW</span>`;
|
|
816
|
+
if (status === "worse") return `<span class="badge">WORSE</span>`;
|
|
817
|
+
return "";
|
|
818
|
+
}
|
|
819
|
+
function chips(c) {
|
|
820
|
+
const items = c.items.map((it) => {
|
|
821
|
+
const cls = it.autoFixExhausted ? "chip critical stuck" : it.severity === "critical" ? "chip critical" : "chip";
|
|
822
|
+
return `<span class="${cls}">${attentionBadge(it.status)}${escapeHtml(it.title)}</span>`;
|
|
823
|
+
});
|
|
824
|
+
for (const reason of c.watchReasons)
|
|
825
|
+
items.push(`<span class="chip">${escapeHtml(reason)}</span>`);
|
|
826
|
+
return items.length ? `<div class="chips">${items.join("")}</div>` : "";
|
|
827
|
+
}
|
|
828
|
+
function signalsAttr(c) {
|
|
829
|
+
const kinds = /* @__PURE__ */ new Set();
|
|
830
|
+
for (const it of c.items) {
|
|
831
|
+
kinds.add(it.kind === "vuln" ? "vulns" : it.kind === "renovate" ? "prs" : it.kind);
|
|
832
|
+
}
|
|
833
|
+
if (c.items.some((it) => it.autoFixExhausted)) kinds.add("auto-fix-failed");
|
|
834
|
+
for (const sig of c.watchSignals) kinds.add(sig);
|
|
835
|
+
return [...kinds].join(" ");
|
|
836
|
+
}
|
|
837
|
+
function triggerRenovateBtn(c) {
|
|
838
|
+
if (!c.site.gitRepo?.trim()) return "";
|
|
839
|
+
const url = `/api/sites/${escapeHtml(siteSlug(c.site.name))}/trigger-renovate`;
|
|
840
|
+
return `<button class="trigger-renovate" data-trigger-url="${url}">Trigger Renovate</button>`;
|
|
841
|
+
}
|
|
842
|
+
function cockpitCard(c) {
|
|
843
|
+
const base = card(c.site);
|
|
844
|
+
const pill = `<span class="pill ${c.tier}">${PILL_LABEL[c.tier]}</span>`;
|
|
845
|
+
const extra = `${pill}${chips(c)}${submBadge(c)}${triggerRenovateBtn(c)}`;
|
|
846
|
+
const opening = `<article class="card" data-signals="${signalsAttr(c)}">`;
|
|
847
|
+
return base.replace('<article class="card">', () => opening).replace("</article>", () => `${extra}</article>`);
|
|
848
|
+
}
|
|
849
|
+
var FLEET_FILTERS = [
|
|
850
|
+
"all",
|
|
851
|
+
"vulns",
|
|
852
|
+
"lighthouse",
|
|
853
|
+
"delivery",
|
|
854
|
+
"prs",
|
|
855
|
+
"ci",
|
|
856
|
+
"auto-fix-failed",
|
|
857
|
+
"stale",
|
|
858
|
+
"no-domain"
|
|
859
|
+
];
|
|
860
|
+
function renderFleetBrowsePanel(model) {
|
|
861
|
+
const total = model.cards.length;
|
|
862
|
+
const chips2 = FLEET_FILTERS.map(
|
|
863
|
+
(f) => `<button type="button" data-filter="${f}" aria-pressed="${f === "all" ? "true" : "false"}">${f}</button>`
|
|
864
|
+
).join("");
|
|
865
|
+
const body = total === 0 ? `<div class="empty">No sites on the fleet view yet.</div>` : `<div class="cards">${model.cards.map(cockpitCard).join("")}</div>`;
|
|
866
|
+
return `<details class="fleet-browse">
|
|
867
|
+
<summary>Fleet (${total})</summary>
|
|
868
|
+
<div class="filters">${chips2}</div>
|
|
869
|
+
${body}
|
|
870
|
+
</details>`;
|
|
871
|
+
}
|
|
872
|
+
var FLEET_BROWSE_SCRIPT = `<script>
|
|
873
|
+
(function(){
|
|
874
|
+
var btns = document.querySelectorAll('.fleet-browse .filters button');
|
|
875
|
+
var cards = document.querySelectorAll('.fleet-browse .cards .card');
|
|
876
|
+
btns.forEach(function(b){
|
|
877
|
+
b.addEventListener('click', function(){
|
|
878
|
+
var f = b.getAttribute('data-filter');
|
|
879
|
+
btns.forEach(function(x){ x.setAttribute('aria-pressed', x===b ? 'true':'false'); });
|
|
880
|
+
cards.forEach(function(c){
|
|
881
|
+
var sig = (c.getAttribute('data-signals')||'').split(' ');
|
|
882
|
+
c.style.display = (f==='all' || sig.indexOf(f)!==-1) ? '' : 'none';
|
|
883
|
+
});
|
|
884
|
+
});
|
|
885
|
+
});
|
|
886
|
+
document.querySelectorAll('.fleet-browse button.trigger-renovate').forEach(function(b){
|
|
887
|
+
b.addEventListener('click', async function(){
|
|
888
|
+
b.disabled = true; b.textContent = 'Dispatching\u2026';
|
|
889
|
+
try { var res = await fetch(b.dataset.triggerUrl, { method: 'POST' });
|
|
890
|
+
b.textContent = res.ok ? 'Dispatched \u2713' : 'Failed';
|
|
891
|
+
if (!res.ok) b.disabled = false; }
|
|
892
|
+
catch(e){ b.textContent = 'Failed'; b.disabled = false; }
|
|
893
|
+
});
|
|
894
|
+
});
|
|
895
|
+
})();
|
|
896
|
+
</script>`;
|
|
897
|
+
|
|
898
|
+
// src/dashboard/fleet-render.ts
|
|
703
899
|
var STYLES2 = `
|
|
704
900
|
:root { color-scheme: light dark; }
|
|
705
901
|
body { font: 16px/1.5 system-ui, -apple-system, sans-serif; max-width: 1100px; margin: 2rem auto; padding: 0 1rem; color: #1a1a1a; }
|
|
@@ -720,12 +916,18 @@ h1 { margin: 0 0 0.25rem; font-size: 1.75rem; }
|
|
|
720
916
|
.cluster.lighthouse .score { display: inline-block; min-width: 2.25rem; text-align: right; }
|
|
721
917
|
.metric-label { color: #999; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.04em; }
|
|
722
918
|
.metric { font-feature-settings: "tnum"; }
|
|
723
|
-
.
|
|
724
|
-
.
|
|
725
|
-
.
|
|
919
|
+
.metric.deploy { font-size:0.78rem; padding:0.05rem 0.4rem; border-radius:6px; text-decoration:none; }
|
|
920
|
+
.metric.deploy.ready { background:#e6f4ea; color:#1a7f37; }
|
|
921
|
+
.metric.deploy.failed { background:#fdecea; color:#b00; font-weight:600; }
|
|
922
|
+
.metric.deploy.building { background:#fff4e5; color:#9a6700; }
|
|
923
|
+
.metric.deploy.unknown { color:#999; }
|
|
924
|
+
@media (prefers-color-scheme: dark) {
|
|
925
|
+
.metric.deploy.ready { background:#0f2417; color:#4ac26b; }
|
|
926
|
+
.metric.deploy.failed { background:#3a1412; color:#ff6b6b; }
|
|
927
|
+
.metric.deploy.building { background:#2e2410; color:#e3b341; }
|
|
928
|
+
}
|
|
726
929
|
.spam-rollup { font-size:0.9rem; margin-bottom:1rem; }
|
|
727
930
|
.muted { color:#999; }
|
|
728
|
-
.subm-viewall { font-size:0.8rem; font-weight:normal; margin-left:0.4rem; white-space:nowrap; }
|
|
729
931
|
.filters { display:flex; flex-wrap:wrap; gap:0.4rem; margin-bottom:1.25rem; }
|
|
730
932
|
.filters button { font:inherit; font-size:0.85rem; padding:0.25rem 0.7rem; border:1px solid #ccc; border-radius:999px; background:transparent; color:inherit; cursor:pointer; }
|
|
731
933
|
.filters button[aria-pressed="true"] { background:#1a1a1a; color:#fff; border-color:#1a1a1a; }
|
|
@@ -740,11 +942,8 @@ h1 { margin: 0 0 0.25rem; font-size: 1.75rem; }
|
|
|
740
942
|
.rf-sub { color:#999; font-size:0.8rem; margin-left:1.1rem; }
|
|
741
943
|
.rf-spin { display:inline-block; width:0.8em; height:0.8em; border:2px solid #999; border-top-color:transparent; border-radius:50%; animation:rf-spin 0.8s linear infinite; vertical-align:-0.1em; }
|
|
742
944
|
@keyframes rf-spin { to { transform:rotate(360deg); } }
|
|
743
|
-
details.
|
|
744
|
-
details.
|
|
745
|
-
.approve-strip { border:1px solid #ffe08a; background:#fff8e1; border-radius:8px; padding:0.75rem 1rem; margin-bottom:1.25rem; }
|
|
746
|
-
@media (prefers-color-scheme: dark) { .approve-strip { background:#241f00; border-color:#5a4d00; } }
|
|
747
|
-
.approve-strip h2 { font-size:1rem; margin:0 0 0.5rem; }
|
|
945
|
+
details.fleet-browse, details.inbox { margin:0.75rem 0; }
|
|
946
|
+
details.fleet-browse > summary, details.inbox > summary { cursor:pointer; font-weight:700; font-size:1.05rem; padding:0.35rem 0; list-style:none; }
|
|
748
947
|
.approve-row { display:flex; flex-wrap:wrap; gap:0.5rem 1rem; align-items:center; padding:0.25rem 0; }
|
|
749
948
|
.pill { font-size:0.75rem; padding:0.1rem 0.5rem; border-radius:999px; font-weight:700; }
|
|
750
949
|
.pill.attention { background:#fdecea; color:#b00; }
|
|
@@ -756,85 +955,108 @@ details.tier > summary { cursor:pointer; font-weight:700; font-size:1.05rem; pad
|
|
|
756
955
|
.chip.critical { background:#fdecea; color:#b00; }
|
|
757
956
|
.chip.stuck { border:1px solid #b00; font-weight:600; }
|
|
758
957
|
.badge { font-weight:700; color:#C00; font-size:0.72rem; margin-right:0.25rem; }
|
|
759
|
-
.
|
|
760
|
-
|
|
958
|
+
.verdict { border-radius:8px; padding:0.9rem 1.1rem; margin-bottom:1.25rem; }
|
|
959
|
+
.verdict .verdict-line { font-weight:800; font-size:1.4rem; }
|
|
960
|
+
.verdict .verdict-meta { color:#666; font-size:0.9rem; margin-top:0.2rem; }
|
|
961
|
+
.verdict.ok { background:#e8f5e9; color:#1b7a2f; }
|
|
962
|
+
.verdict.ok .verdict-meta { color:#2e7d32; }
|
|
963
|
+
.verdict.warn { background:#fdecea; color:#b00; }
|
|
964
|
+
.verdict.warn .verdict-meta { color:#b00; opacity:0.85; }
|
|
965
|
+
@media (prefers-color-scheme: dark) { .verdict.ok { background:#10240f; color:#7fce85; } .verdict.warn { background:#2a0f0d; color:#ff8a80; } }
|
|
966
|
+
.verdict .fleet-actions { margin:0.6rem 0 0; }
|
|
967
|
+
.needs-you { border:1px solid #e5e5e5; border-radius:8px; padding:0.75rem 1rem; margin-bottom:1.25rem; }
|
|
968
|
+
@media (prefers-color-scheme: dark) { .needs-you { border-color:#2a2a2a; background:#181818; } }
|
|
969
|
+
.needs-you h2 { font-size:1.05rem; margin:0 0 0.5rem; }
|
|
970
|
+
.feed-group-label { text-transform:uppercase; letter-spacing:0.04em; font-size:0.72rem; color:#999; margin:0.6rem 0 0.2rem; }
|
|
971
|
+
.feed-row { display:flex; gap:0.5rem; align-items:center; padding:0.3rem 0; border-bottom:1px dashed #eee; }
|
|
972
|
+
.feed-row:last-child { border-bottom:0; }
|
|
973
|
+
@media (prefers-color-scheme: dark) { .feed-row { border-bottom-color:#262626; } }
|
|
974
|
+
.feed-what { flex:1; }
|
|
975
|
+
.feed-open { white-space:nowrap; }
|
|
976
|
+
.dot { width:0.55rem; height:0.55rem; border-radius:50%; display:inline-block; flex:0 0 auto; }
|
|
977
|
+
.dot.broken { background:#dc2626; }
|
|
978
|
+
.dot.approval { background:#2563eb; }
|
|
979
|
+
.dot.slipping { background:#f59e0b; }
|
|
761
980
|
`;
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
"vulns",
|
|
770
|
-
"lighthouse",
|
|
771
|
-
"delivery",
|
|
772
|
-
"prs",
|
|
773
|
-
"ci",
|
|
774
|
-
"auto-fix-failed",
|
|
775
|
-
"stale",
|
|
776
|
-
"no-domain",
|
|
777
|
-
"pending",
|
|
778
|
-
"submissions"
|
|
779
|
-
];
|
|
780
|
-
function summaryBar(model) {
|
|
781
|
-
const s = model.summary;
|
|
782
|
-
const heads = [
|
|
783
|
-
`${s.criticalHighVulns} critical/high vuln${s.criticalHighVulns === 1 ? "" : "s"}`,
|
|
784
|
-
`${s.lighthouseBelowFloor} Lighthouse<75`,
|
|
785
|
-
`${s.deliveryFailures} delivery`,
|
|
786
|
-
`${s.renovateFailing} PRs failing`,
|
|
787
|
-
`${s.ciRed} CI red`,
|
|
788
|
-
`${s.autoFixStuck} auto-fix stuck`,
|
|
789
|
-
`${s.pending} pending`,
|
|
790
|
-
`${s.newSubmissions ?? 0} new`
|
|
791
|
-
].join(" \xB7 ");
|
|
792
|
-
const chips2 = FILTERS.map(
|
|
793
|
-
(f) => `<button type="button" data-filter="${f}" aria-pressed="${f === "all" ? "true" : "false"}">${f}</button>`
|
|
794
|
-
).join("");
|
|
795
|
-
return `<div class="summary">
|
|
796
|
-
<span class="tier">\u{1F534} ${s.attention} needs attention</span>
|
|
797
|
-
<span class="tier">\u{1F7E1} ${s.watch} watch</span>
|
|
798
|
-
<span class="tier">\u{1F7E2} ${s.healthy} healthy</span>
|
|
799
|
-
</div>
|
|
800
|
-
<div class="summary heads">${escapeHtml(heads)}</div>
|
|
801
|
-
<div class="filters">${chips2}</div>
|
|
802
|
-
<div class="fleet-actions">
|
|
803
|
-
<button type="button" class="refresh-fleet" data-refresh-url="/api/fleet/refresh">\u21BB Refresh fleet state</button>
|
|
981
|
+
function verdictBar(model, feedCount) {
|
|
982
|
+
const auditedIso = fleetLastAuditedAt(model.cards);
|
|
983
|
+
const audited = auditedIso ? ` \xB7 fleet last audited ${escapeHtml(relativeTimeFromNow(auditedIso))}` : "";
|
|
984
|
+
const sites = model.cards.length;
|
|
985
|
+
const sitesWord = `${sites} site${sites === 1 ? "" : "s"}`;
|
|
986
|
+
const actions = `<div class="fleet-actions">
|
|
987
|
+
<button type="button" class="refresh-fleet" data-refresh-url="/api/fleet/refresh">\u21BB Audit fleet</button>
|
|
804
988
|
<div id="rf-status" class="rf-status" aria-live="polite"></div>
|
|
805
989
|
</div>`;
|
|
990
|
+
if (feedCount === 0) {
|
|
991
|
+
return `<div class="verdict ok">
|
|
992
|
+
<div class="verdict-line">\u2713 All clear</div>
|
|
993
|
+
<div class="verdict-meta">${sitesWord} healthy${audited}</div>
|
|
994
|
+
${actions}
|
|
995
|
+
</div>`;
|
|
996
|
+
}
|
|
997
|
+
const noun = feedCount === 1 ? "site needs" : "sites need";
|
|
998
|
+
return `<div class="verdict warn">
|
|
999
|
+
<div class="verdict-line">\u26A0 ${feedCount} ${noun} you</div>
|
|
1000
|
+
<div class="verdict-meta">${sitesWord}${audited}</div>
|
|
1001
|
+
${actions}
|
|
1002
|
+
</div>`;
|
|
806
1003
|
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
}
|
|
812
|
-
function
|
|
813
|
-
if (
|
|
814
|
-
const
|
|
815
|
-
|
|
1004
|
+
var NEEDS_YOU_GROUP_LABEL = {
|
|
1005
|
+
broken: "Broken",
|
|
1006
|
+
approval: "Waiting on your yes",
|
|
1007
|
+
slipping: "Slipping"
|
|
1008
|
+
};
|
|
1009
|
+
function renderNeedsYouFeed(feed) {
|
|
1010
|
+
if (feed.length === 0) return "";
|
|
1011
|
+
const groups = ["broken", "approval", "slipping"];
|
|
1012
|
+
const blocks = groups.map((g) => {
|
|
1013
|
+
const rows = feed.filter((i) => i.group === g);
|
|
1014
|
+
if (rows.length === 0) return "";
|
|
1015
|
+
const lis = rows.map(
|
|
1016
|
+
(i) => `<div class="feed-row" data-group="${g}">
|
|
1017
|
+
<span class="dot ${g}"></span>
|
|
1018
|
+
<span class="feed-what"><strong>${escapeHtml(i.siteName)}</strong> \u2014 ${escapeHtml(i.reasons.join(" \xB7 "))}</span>
|
|
1019
|
+
<a class="feed-open" href="${escapeHtml(i.url)}">Open \u25B8</a>
|
|
1020
|
+
</div>`
|
|
1021
|
+
).join("");
|
|
1022
|
+
return `<div class="feed-group"><div class="feed-group-label">${NEEDS_YOU_GROUP_LABEL[g]}</div>${lis}</div>`;
|
|
1023
|
+
}).join("");
|
|
1024
|
+
return `<section class="needs-you"><h2>Needs you (${feed.length})</h2>${blocks}</section>`;
|
|
816
1025
|
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
1026
|
+
var SUBMISSIONS_STRIP_CAP = 10;
|
|
1027
|
+
var RECENT_ICON = {
|
|
1028
|
+
pr_automerged: "\u{1F527}",
|
|
1029
|
+
vuln_cleared: "\u{1F6E1}",
|
|
1030
|
+
ci_recovered: "\u2705",
|
|
1031
|
+
site_launched: "\u{1F680}",
|
|
1032
|
+
cert_renewed: "\u{1F512}",
|
|
1033
|
+
fleet_swept: "\u{1F504}"
|
|
1034
|
+
};
|
|
1035
|
+
function renderRecentlyLane(model) {
|
|
1036
|
+
const events = model.recent ?? [];
|
|
1037
|
+
if (events.length === 0) return "";
|
|
1038
|
+
const rows = events.map((e) => {
|
|
1039
|
+
const icon = RECENT_ICON[e.type] ?? "\u2022";
|
|
1040
|
+
const when = escapeHtml(relativeTimeFromNow(e.ts));
|
|
1041
|
+
const site = e.siteName ? `<strong>${escapeHtml(e.siteName)}</strong> \u2014 ` : "";
|
|
1042
|
+
const link = e.url ? `<a href="${escapeHtml(e.url)}">view \u25B8</a>` : e.slug ? `<a href="/s/${escapeHtml(e.slug)}">open \u25B8</a>` : "";
|
|
1043
|
+
return `<div class="recent-row" data-type="${e.type}">
|
|
1044
|
+
<span class="recent-icon">${icon}</span>
|
|
1045
|
+
<span class="recent-what">${site}${escapeHtml(e.summary)}</span>
|
|
1046
|
+
<span class="muted">${when}</span>
|
|
1047
|
+
${link}
|
|
827
1048
|
</div>`;
|
|
828
1049
|
}).join("");
|
|
829
|
-
return `<
|
|
830
|
-
<
|
|
1050
|
+
return `<details class="recently">
|
|
1051
|
+
<summary>\u{1F527} Recently (${events.length})</summary>
|
|
831
1052
|
${rows}
|
|
832
|
-
</
|
|
1053
|
+
</details>`;
|
|
833
1054
|
}
|
|
834
|
-
|
|
835
|
-
function submissionsStrip(model) {
|
|
1055
|
+
function renderInboxLane(model) {
|
|
836
1056
|
const subs = model.submissions ?? [];
|
|
837
|
-
|
|
1057
|
+
const spam = model.spam;
|
|
1058
|
+
const hasSpam = !!spam && (spam.caught > 0 || spam.through > 0);
|
|
1059
|
+
if (subs.length === 0 && !hasSpam) return "";
|
|
838
1060
|
const shown = [...subs].sort((a, b) => (b.submittedAt ?? "").localeCompare(a.submittedAt ?? "")).slice(0, SUBMISSIONS_STRIP_CAP);
|
|
839
1061
|
const rows = shown.map((sub) => {
|
|
840
1062
|
const href = `/s/${escapeHtml(sub.slug)}`;
|
|
@@ -848,91 +1070,17 @@ function submissionsStrip(model) {
|
|
|
848
1070
|
</div>`;
|
|
849
1071
|
}).join("");
|
|
850
1072
|
const overflow = subs.length - shown.length;
|
|
851
|
-
const more = overflow > 0 ? `<div class="approve-row subm-more muted"><a href="/submissions">+${overflow} more \u2014 view all submissions</a></div>` : ""
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
1073
|
+
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>`;
|
|
1074
|
+
const spamLine = hasSpam ? `<div class="spam-rollup muted">\u{1F6E1} Spam (30d) \u2014 caught ${spam.caught} \xB7 through ${spam.through}</div>` : "";
|
|
1075
|
+
const spamInSummary = hasSpam ? " \xB7 \u{1F6E1} Spam (30d)" : "";
|
|
1076
|
+
return `<details class="inbox">
|
|
1077
|
+
<summary>\u{1F4E5} Submissions (${subs.length} new)${spamInSummary}</summary>
|
|
1078
|
+
${rows}${subs.length > 0 ? more : ""}
|
|
1079
|
+
${spamLine}
|
|
1080
|
+
</details>`;
|
|
856
1081
|
}
|
|
857
|
-
|
|
858
|
-
const n = c.newSubmissions ?? 0;
|
|
859
|
-
return n > 0 ? `<span class="chip">\u{1F4E5} ${n} new</span>` : "";
|
|
860
|
-
}
|
|
861
|
-
var PILL_LABEL = { attention: "failing", watch: "watch", healthy: "ok" };
|
|
862
|
-
function attentionBadge(status) {
|
|
863
|
-
if (status === "new") return `<span class="badge">NEW</span>`;
|
|
864
|
-
if (status === "worse") return `<span class="badge">WORSE</span>`;
|
|
865
|
-
return "";
|
|
866
|
-
}
|
|
867
|
-
function chips(c) {
|
|
868
|
-
const items = c.items.map((it) => {
|
|
869
|
-
const cls = it.autoFixExhausted ? "chip critical stuck" : it.severity === "critical" ? "chip critical" : "chip";
|
|
870
|
-
return `<span class="${cls}">${attentionBadge(it.status)}${escapeHtml(it.title)}</span>`;
|
|
871
|
-
});
|
|
872
|
-
for (const reason of c.watchReasons)
|
|
873
|
-
items.push(`<span class="chip">${escapeHtml(reason)}</span>`);
|
|
874
|
-
return items.length ? `<div class="chips">${items.join("")}</div>` : "";
|
|
875
|
-
}
|
|
876
|
-
function signalsAttr(c) {
|
|
877
|
-
const kinds = /* @__PURE__ */ new Set();
|
|
878
|
-
for (const it of c.items) {
|
|
879
|
-
kinds.add(it.kind === "vuln" ? "vulns" : it.kind === "renovate" ? "prs" : it.kind);
|
|
880
|
-
}
|
|
881
|
-
if (c.items.some((it) => it.autoFixExhausted)) kinds.add("auto-fix-failed");
|
|
882
|
-
for (const sig of c.watchSignals) kinds.add(sig);
|
|
883
|
-
return [...kinds].join(" ");
|
|
884
|
-
}
|
|
885
|
-
function triggerRenovateBtn(c) {
|
|
886
|
-
if (!c.site.gitRepo?.trim()) return "";
|
|
887
|
-
const url = `/api/sites/${escapeHtml(siteSlug(c.site.name))}/trigger-renovate`;
|
|
888
|
-
return `<button class="trigger-renovate" data-trigger-url="${url}">Trigger Renovate</button>`;
|
|
889
|
-
}
|
|
890
|
-
function cockpitCard(c) {
|
|
891
|
-
const base = card(c.site);
|
|
892
|
-
const pill = `<span class="pill ${c.tier}">${PILL_LABEL[c.tier]}</span>`;
|
|
893
|
-
const extra = `${pill}${chips(c)}${submBadge(c)}${triggerRenovateBtn(c)}`;
|
|
894
|
-
const opening = `<article class="card" data-signals="${signalsAttr(c)}">`;
|
|
895
|
-
return base.replace('<article class="card">', () => opening).replace("</article>", () => `${extra}</article>`);
|
|
896
|
-
}
|
|
897
|
-
var FILTER_SCRIPT = `<script>
|
|
1082
|
+
var AUDIT_SCRIPT = `<script>
|
|
898
1083
|
(function(){
|
|
899
|
-
var btns = document.querySelectorAll('.filters button');
|
|
900
|
-
var cards = document.querySelectorAll('.cards .card');
|
|
901
|
-
var details = document.querySelectorAll('details.tier');
|
|
902
|
-
btns.forEach(function(b){
|
|
903
|
-
b.addEventListener('click', function(){
|
|
904
|
-
var f = b.getAttribute('data-filter');
|
|
905
|
-
btns.forEach(function(x){ x.setAttribute('aria-pressed', x===b ? 'true':'false'); });
|
|
906
|
-
// "pending" lives on the approve strip, not on tier cards \u2014 just jump to it,
|
|
907
|
-
// never hide the triage cards (else the whole board blanks).
|
|
908
|
-
if (f === 'pending') { var s = document.querySelector('.approve-strip'); if (s) s.scrollIntoView({behavior:'smooth'}); return; }
|
|
909
|
-
if (f === 'submissions') { var ss = document.querySelector('[data-tier="submissions"]'); if (ss) ss.scrollIntoView({behavior:'smooth'}); return; }
|
|
910
|
-
if (f !== 'all') details.forEach(function(d){ d.open = true; });
|
|
911
|
-
cards.forEach(function(c){
|
|
912
|
-
var sig = (c.getAttribute('data-signals')||'').split(' ');
|
|
913
|
-
c.style.display = (f==='all' || sig.indexOf(f)!==-1) ? '' : 'none';
|
|
914
|
-
});
|
|
915
|
-
});
|
|
916
|
-
});
|
|
917
|
-
// approve buttons: mirror the per-site dashboard's inline POST.
|
|
918
|
-
document.querySelectorAll('button.approve').forEach(function(b){
|
|
919
|
-
b.addEventListener('click', async function(){
|
|
920
|
-
b.disabled = true; b.textContent = 'Approving\u2026';
|
|
921
|
-
try { var res = await fetch(b.dataset.approveUrl, { method: 'POST' });
|
|
922
|
-
b.textContent = res.ok ? 'Approved \u2713' : 'Failed'; }
|
|
923
|
-
catch(e){ b.textContent = 'Failed'; b.disabled = false; }
|
|
924
|
-
});
|
|
925
|
-
});
|
|
926
|
-
// trigger-renovate buttons: fire the on-demand dispatch (async, fire-and-forget).
|
|
927
|
-
document.querySelectorAll('button.trigger-renovate').forEach(function(b){
|
|
928
|
-
b.addEventListener('click', async function(){
|
|
929
|
-
b.disabled = true; b.textContent = 'Dispatching\u2026';
|
|
930
|
-
try { var res = await fetch(b.dataset.triggerUrl, { method: 'POST' });
|
|
931
|
-
b.textContent = res.ok ? 'Dispatched \u2713' : 'Failed';
|
|
932
|
-
if (!res.ok) b.disabled = false; }
|
|
933
|
-
catch(e){ b.textContent = 'Failed'; b.disabled = false; }
|
|
934
|
-
});
|
|
935
|
-
});
|
|
936
1084
|
// fleet-refresh live status: dispatch, then poll the actual runs and follow them.
|
|
937
1085
|
// Vanilla JS, string-concat only (no template literals) \u2014 this lives inside a TS
|
|
938
1086
|
// template string, so backticks or interpolation syntax would break the server render.
|
|
@@ -990,7 +1138,7 @@ var FILTER_SCRIPT = `<script>
|
|
|
990
1138
|
var p = rfPanel();
|
|
991
1139
|
if (data && data.authFail){
|
|
992
1140
|
if (p) p.innerHTML += '<div class="rf-row">Session expired \u2014 reload to sign in.</div>';
|
|
993
|
-
if (rf){ rf.disabled = false; rf.textContent = '\u21BB
|
|
1141
|
+
if (rf){ rf.disabled = false; rf.textContent = '\u21BB Audit fleet'; }
|
|
994
1142
|
rfStop(); return;
|
|
995
1143
|
}
|
|
996
1144
|
if (data && data.status){
|
|
@@ -1001,13 +1149,13 @@ var FILTER_SCRIPT = `<script>
|
|
|
1001
1149
|
rfStop(); setTimeout(function(){ location.reload(); }, 2000); return;
|
|
1002
1150
|
}
|
|
1003
1151
|
if (p) p.innerHTML += '<div class="rf-row"><button type="button" onclick="location.reload()">Reload</button></div>';
|
|
1004
|
-
if (rf){ rf.disabled = false; rf.textContent = '\u21BB
|
|
1152
|
+
if (rf){ rf.disabled = false; rf.textContent = '\u21BB Audit fleet'; }
|
|
1005
1153
|
rfStop(); return;
|
|
1006
1154
|
}
|
|
1007
1155
|
}
|
|
1008
1156
|
if (Date.now() - startedAt > RF_MAX_MS){
|
|
1009
1157
|
if (p) p.innerHTML += '<div class="rf-row">Still running \u2014 reload later.</div>';
|
|
1010
|
-
if (rf){ rf.disabled = false; rf.textContent = '\u21BB
|
|
1158
|
+
if (rf){ rf.disabled = false; rf.textContent = '\u21BB Audit fleet'; }
|
|
1011
1159
|
rfStop(); return;
|
|
1012
1160
|
}
|
|
1013
1161
|
setTimeout(function(){ rfPoll(since, startedAt); }, RF_POLL_MS);
|
|
@@ -1015,7 +1163,7 @@ var FILTER_SCRIPT = `<script>
|
|
|
1015
1163
|
var p = rfPanel();
|
|
1016
1164
|
if (Date.now() - startedAt > RF_MAX_MS){
|
|
1017
1165
|
if (p) p.innerHTML += '<div class="rf-row">Still running \u2014 reload later.</div>';
|
|
1018
|
-
if (rf){ rf.disabled = false; rf.textContent = '\u21BB
|
|
1166
|
+
if (rf){ rf.disabled = false; rf.textContent = '\u21BB Audit fleet'; }
|
|
1019
1167
|
rfStop(); return;
|
|
1020
1168
|
}
|
|
1021
1169
|
setTimeout(function(){ rfPoll(since, startedAt); }, RF_POLL_MS);
|
|
@@ -1029,12 +1177,12 @@ var FILTER_SCRIPT = `<script>
|
|
|
1029
1177
|
var rf = document.querySelector('button.refresh-fleet');
|
|
1030
1178
|
if (rf) rf.addEventListener('click', async function(){
|
|
1031
1179
|
if (!confirm('Kick off the security + Lighthouse sweeps for the whole fleet? They take a few minutes.')) return;
|
|
1032
|
-
rf.disabled = true; rf.textContent = '
|
|
1180
|
+
rf.disabled = true; rf.textContent = 'Auditing\u2026';
|
|
1033
1181
|
try {
|
|
1034
1182
|
var res = await fetch(rf.dataset.refreshUrl, { method: 'POST' });
|
|
1035
1183
|
if (res.ok){
|
|
1036
1184
|
var data = await res.json();
|
|
1037
|
-
rf.textContent = '\u21BB
|
|
1185
|
+
rf.textContent = '\u21BB Audit running\u2026';
|
|
1038
1186
|
if (data && data.since) rfBegin(data.since, Date.now());
|
|
1039
1187
|
} else { rf.textContent = 'Failed to start'; rf.disabled = false; }
|
|
1040
1188
|
} catch(e){ rf.textContent = 'Failed to start'; rf.disabled = false; }
|
|
@@ -1043,7 +1191,7 @@ var FILTER_SCRIPT = `<script>
|
|
|
1043
1191
|
try {
|
|
1044
1192
|
var rfSaved = JSON.parse(localStorage.getItem(RF_KEY) || 'null');
|
|
1045
1193
|
if (rfSaved && rfSaved.since && rfSaved.startedAt && (Date.now() - rfSaved.startedAt) < RF_MAX_MS){
|
|
1046
|
-
if (rf){ rf.disabled = true; rf.textContent = '\u21BB
|
|
1194
|
+
if (rf){ rf.disabled = true; rf.textContent = '\u21BB Audit running\u2026'; }
|
|
1047
1195
|
rfBegin(rfSaved.since, rfSaved.startedAt);
|
|
1048
1196
|
} else if (rfSaved) { rfStop(); }
|
|
1049
1197
|
} catch(e){}
|
|
@@ -1051,16 +1199,7 @@ var FILTER_SCRIPT = `<script>
|
|
|
1051
1199
|
</script>`;
|
|
1052
1200
|
function renderCockpitHtml(model) {
|
|
1053
1201
|
const total = model.cards.length;
|
|
1054
|
-
const
|
|
1055
|
-
const sections = tiers.map((tier) => {
|
|
1056
|
-
const cards = model.cards.filter((c) => c.tier === tier);
|
|
1057
|
-
const meta = TIER_META[tier];
|
|
1058
|
-
const body = cards.length === 0 ? `<div class="empty">None.</div>` : `<div class="cards">${cards.map(cockpitCard).join("")}</div>`;
|
|
1059
|
-
return `<details class="tier" data-tier="${tier}"${meta.open ? " open" : ""}>
|
|
1060
|
-
<summary>${meta.emoji} ${meta.label} (${cards.length})</summary>
|
|
1061
|
-
${body}
|
|
1062
|
-
</details>`;
|
|
1063
|
-
}).join("");
|
|
1202
|
+
const feed = buildNeedsYouFeed(model);
|
|
1064
1203
|
return `<!doctype html>
|
|
1065
1204
|
<html lang="en">
|
|
1066
1205
|
<head>
|
|
@@ -1073,13 +1212,13 @@ function renderCockpitHtml(model) {
|
|
|
1073
1212
|
<body>
|
|
1074
1213
|
<h1>Reddoor fleet cockpit</h1>
|
|
1075
1214
|
<div class="meta">${total} site${total === 1 ? "" : "s"} on the Reddoor stack.</div>
|
|
1076
|
-
${
|
|
1077
|
-
${
|
|
1078
|
-
${
|
|
1079
|
-
${
|
|
1080
|
-
${
|
|
1081
|
-
${
|
|
1082
|
-
${
|
|
1215
|
+
${verdictBar(model, feed.length)}
|
|
1216
|
+
${renderNeedsYouFeed(feed)}
|
|
1217
|
+
${renderFleetBrowsePanel(model)}
|
|
1218
|
+
${renderRecentlyLane(model)}
|
|
1219
|
+
${renderInboxLane(model)}
|
|
1220
|
+
${AUDIT_SCRIPT}
|
|
1221
|
+
${FLEET_BROWSE_SCRIPT}
|
|
1083
1222
|
</body>
|
|
1084
1223
|
</html>`;
|
|
1085
1224
|
}
|