@reddoorla/maintenance 0.63.0 → 0.65.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-GGZJUGQB.js +9 -0
- package/dist/{announce-QEON4O7E.js → announce-GFLKAPTT.js} +4 -4
- package/dist/{bump-deps-2U6GIHM5.js → bump-deps-3QQHYDKJ.js} +2 -2
- package/dist/{chunk-BMDBOPN3.js → chunk-4A5PYCGB.js} +2 -2
- package/dist/{chunk-OSRFDGQM.js → chunk-4GACMVNE.js} +2 -1
- package/dist/{chunk-BLD6AYJO.js → chunk-7RGQ4KLE.js} +5 -2
- package/dist/chunk-7RGQ4KLE.js.map +1 -0
- package/dist/{chunk-YYUY2OVP.js → chunk-CSTLFSGM.js} +2 -2
- package/dist/{chunk-SAOCVNX7.js → chunk-FH7WQHD7.js} +2 -2
- package/dist/{chunk-KTPPKO2C.js → chunk-FX3QBSUF.js} +2 -2
- package/dist/{chunk-L5F664ZA.js → chunk-G2YRXV26.js} +2 -2
- package/dist/chunk-GASBX52O.js +12 -0
- package/dist/chunk-GASBX52O.js.map +1 -0
- package/dist/{chunk-4REUHZAW.js → chunk-LM42CATB.js} +3 -3
- package/dist/{chunk-NPJFHBAX.js → chunk-M5LX2VSR.js} +2 -2
- package/dist/{chunk-ZEX2T6JH.js → chunk-MQM5YP4J.js} +2 -2
- package/dist/{chunk-P7VJM46I.js → chunk-OYN6H4CX.js} +3 -3
- package/dist/{chunk-6ZKM7OIJ.js → chunk-PTPTVVY7.js} +6 -4
- package/dist/chunk-PTPTVVY7.js.map +1 -0
- package/dist/{chunk-J37CZORZ.js → chunk-QSL3EJGI.js} +3 -3
- package/dist/{chunk-SS7BKOIX.js → chunk-TC7Z5LG7.js} +237 -1
- package/dist/chunk-TC7Z5LG7.js.map +1 -0
- package/dist/{chunk-3HM4S422.js → chunk-UATHDNWP.js} +7 -14
- package/dist/chunk-UATHDNWP.js.map +1 -0
- package/dist/cli/bin.js +16 -16
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.js +10 -8
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/{client-GTMJTXBK.js → client-C2GOZD2D.js} +2 -2
- package/dist/{convert-to-pnpm-JG3767ZQ.js → convert-to-pnpm-ZO673UFB.js} +2 -2
- package/dist/{db-Y6BVTNH7.js → db-YBLQR2YY.js} +2 -2
- package/dist/{digest-ABJROO4H.js → digest-R3ROSGLP.js} +3 -3
- package/dist/{github-signals-XTJLKOXJ.js → github-signals-N5JGWHW2.js} +9 -9
- package/dist/index.d.ts +39 -3
- package/dist/index.js +86 -40
- package/dist/index.js.map +1 -1
- package/dist/{init-DMAIKUQE.js → init-FOHNISF2.js} +6 -4
- package/dist/{init-DMAIKUQE.js.map → init-FOHNISF2.js.map} +1 -1
- package/dist/{launch-IBP23PNY.js → launch-TM3IHSSU.js} +9 -8
- package/dist/{launch-IBP23PNY.js.map → launch-TM3IHSSU.js.map} +1 -1
- package/dist/{lighthouse-airtable-HTEW6BBT.js → lighthouse-airtable-5FHELRCA.js} +3 -3
- package/dist/{onboard-TTIC3YZI.js → onboard-K2EL4MSG.js} +2 -2
- package/dist/{orchestrate-CYYZ737H.js → orchestrate-OX3Z4DUI.js} +5 -5
- package/dist/{renovate-dispatch-QDDMTATD.js → renovate-dispatch-H25CQ36P.js} +3 -3
- package/dist/{report-QNE7HFOZ.js → report-UEYKO6WH.js} +5 -5
- package/dist/{self-updating-2RLUP4AA.js → self-updating-YXOGQWH5.js} +5 -4
- package/dist/{self-updating-2RLUP4AA.js.map → self-updating-YXOGQWH5.js.map} +1 -1
- package/dist/{selftest-5ELLRH6Q.js → selftest-VE7CSGWE.js} +7 -7
- package/dist/{svelte-codemods-BPLKSVIA.js → svelte-codemods-745QM25J.js} +2 -2
- package/dist/{sync-configs-VFRV3TJ4.js → sync-configs-NYC3Z625.js} +2 -2
- package/dist/{upgrade-WCFD2TRN.js → upgrade-UBPPJ6HN.js} +2 -2
- package/dist/{websites-HM2NMZMR.js → websites-CSVLSEVL.js} +2 -2
- package/dist/{write-audits-to-airtable-QF26PCAR.js → write-audits-to-airtable-JCV7FYAW.js} +4 -4
- package/package.json +1 -1
- package/dist/airtable-HHAWHBNM.js +0 -9
- package/dist/chunk-3HM4S422.js.map +0 -1
- package/dist/chunk-6ZKM7OIJ.js.map +0 -1
- package/dist/chunk-BLD6AYJO.js.map +0 -1
- package/dist/chunk-SS7BKOIX.js.map +0 -1
- /package/dist/{airtable-HHAWHBNM.js.map → airtable-GGZJUGQB.js.map} +0 -0
- /package/dist/{announce-QEON4O7E.js.map → announce-GFLKAPTT.js.map} +0 -0
- /package/dist/{bump-deps-2U6GIHM5.js.map → bump-deps-3QQHYDKJ.js.map} +0 -0
- /package/dist/{chunk-BMDBOPN3.js.map → chunk-4A5PYCGB.js.map} +0 -0
- /package/dist/{chunk-OSRFDGQM.js.map → chunk-4GACMVNE.js.map} +0 -0
- /package/dist/{chunk-YYUY2OVP.js.map → chunk-CSTLFSGM.js.map} +0 -0
- /package/dist/{chunk-SAOCVNX7.js.map → chunk-FH7WQHD7.js.map} +0 -0
- /package/dist/{chunk-KTPPKO2C.js.map → chunk-FX3QBSUF.js.map} +0 -0
- /package/dist/{chunk-L5F664ZA.js.map → chunk-G2YRXV26.js.map} +0 -0
- /package/dist/{chunk-4REUHZAW.js.map → chunk-LM42CATB.js.map} +0 -0
- /package/dist/{chunk-NPJFHBAX.js.map → chunk-M5LX2VSR.js.map} +0 -0
- /package/dist/{chunk-ZEX2T6JH.js.map → chunk-MQM5YP4J.js.map} +0 -0
- /package/dist/{chunk-P7VJM46I.js.map → chunk-OYN6H4CX.js.map} +0 -0
- /package/dist/{chunk-J37CZORZ.js.map → chunk-QSL3EJGI.js.map} +0 -0
- /package/dist/{client-GTMJTXBK.js.map → client-C2GOZD2D.js.map} +0 -0
- /package/dist/{convert-to-pnpm-JG3767ZQ.js.map → convert-to-pnpm-ZO673UFB.js.map} +0 -0
- /package/dist/{db-Y6BVTNH7.js.map → db-YBLQR2YY.js.map} +0 -0
- /package/dist/{digest-ABJROO4H.js.map → digest-R3ROSGLP.js.map} +0 -0
- /package/dist/{github-signals-XTJLKOXJ.js.map → github-signals-N5JGWHW2.js.map} +0 -0
- /package/dist/{lighthouse-airtable-HTEW6BBT.js.map → lighthouse-airtable-5FHELRCA.js.map} +0 -0
- /package/dist/{onboard-TTIC3YZI.js.map → onboard-K2EL4MSG.js.map} +0 -0
- /package/dist/{orchestrate-CYYZ737H.js.map → orchestrate-OX3Z4DUI.js.map} +0 -0
- /package/dist/{renovate-dispatch-QDDMTATD.js.map → renovate-dispatch-H25CQ36P.js.map} +0 -0
- /package/dist/{report-QNE7HFOZ.js.map → report-UEYKO6WH.js.map} +0 -0
- /package/dist/{selftest-5ELLRH6Q.js.map → selftest-VE7CSGWE.js.map} +0 -0
- /package/dist/{svelte-codemods-BPLKSVIA.js.map → svelte-codemods-745QM25J.js.map} +0 -0
- /package/dist/{sync-configs-VFRV3TJ4.js.map → sync-configs-NYC3Z625.js.map} +0 -0
- /package/dist/{upgrade-WCFD2TRN.js.map → upgrade-UBPPJ6HN.js.map} +0 -0
- /package/dist/{websites-HM2NMZMR.js.map → websites-CSVLSEVL.js.map} +0 -0
- /package/dist/{write-audits-to-airtable-QF26PCAR.js.map → write-audits-to-airtable-JCV7FYAW.js.map} +0 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as AuditResult, S as Site, A as AuditName, b as RecipeResult, R as RecipeName, I as InventoryProvider } from './types-Dg4mHvyf.js';
|
|
2
2
|
export { C as ConfigName } from './types-Dg4mHvyf.js';
|
|
3
3
|
import * as airtable_lib_airtable_base_js from 'airtable/lib/airtable_base.js';
|
|
4
4
|
import { F as FormType } from './types-RXY-vY-5.js';
|
|
@@ -91,6 +91,33 @@ type NetlifyDeployDeps = {
|
|
|
91
91
|
now: Date;
|
|
92
92
|
};
|
|
93
93
|
|
|
94
|
+
/** One GitHub Dependabot alert, mapped to the fields the security audit needs. */
|
|
95
|
+
type DependabotAlert = {
|
|
96
|
+
/** The vulnerable package name (`dependency.package.name`). */
|
|
97
|
+
package: string;
|
|
98
|
+
/** GitHub's raw severity vocabulary: "low" | "medium" | "high" | "critical". The audit layer
|
|
99
|
+
* maps "medium" → the app's "moderate"; the rest pass through. */
|
|
100
|
+
severity: string;
|
|
101
|
+
/** Human advisory summary (`security_advisory.summary`). */
|
|
102
|
+
summary: string;
|
|
103
|
+
/** CVE id from the advisory's `cve_id`, as a 0- or 1-element array. Empty for GHSA-only
|
|
104
|
+
* advisories (no CVE assigned) — those have no inline identifier here, but `url` still links
|
|
105
|
+
* to the GHSA advisory page. (The GitHub `identifiers[]` array is intentionally not surfaced.) */
|
|
106
|
+
cves: string[];
|
|
107
|
+
/** Link to the alert (`html_url`), or null. */
|
|
108
|
+
url: string | null;
|
|
109
|
+
/** Dependency graph scope, or null when GitHub doesn't classify it. "development" marks a
|
|
110
|
+
* build-time-only dependency (lower live-exploit surface for a static site). */
|
|
111
|
+
scope: "runtime" | "development" | null;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/** The Dependabot fetch the security audit needs, injectable for tests. The real default is
|
|
115
|
+
* built from GITHUB_TOKEN; absent token (or a site with no gitRepo) → pnpm/npm audit fallback. */
|
|
116
|
+
type DependabotDeps = {
|
|
117
|
+
listAlerts: (repo: string) => Promise<DependabotAlert[]>;
|
|
118
|
+
};
|
|
119
|
+
declare function securityAudit(ctx: AuditContext): Promise<AuditResult>;
|
|
120
|
+
|
|
94
121
|
type AuditContext = {
|
|
95
122
|
site: Site;
|
|
96
123
|
spawn?: SpawnFn;
|
|
@@ -104,14 +131,15 @@ type AuditContext = {
|
|
|
104
131
|
browserRunner?: BrowserRunner;
|
|
105
132
|
/** Netlify API injection for the netlify-deploy audit (tests). Defaults to a real API call. */
|
|
106
133
|
netlifyDeployDeps?: NetlifyDeployDeps;
|
|
134
|
+
/** GitHub Dependabot fetch injection for the security audit (tests). Defaults to a real client
|
|
135
|
+
* from GITHUB_TOKEN; absent token or no site.gitRepo → the pnpm/npm audit fallback. */
|
|
136
|
+
dependabotDeps?: DependabotDeps;
|
|
107
137
|
};
|
|
108
138
|
|
|
109
139
|
declare function depsAudit(ctx: AuditContext): Promise<AuditResult>;
|
|
110
140
|
|
|
111
141
|
declare function lintAudit(ctx: AuditContext): Promise<AuditResult>;
|
|
112
142
|
|
|
113
|
-
declare function securityAudit(ctx: AuditContext): Promise<AuditResult>;
|
|
114
|
-
|
|
115
143
|
declare function lighthouseAudit(ctx: AuditContext): Promise<AuditResult>;
|
|
116
144
|
|
|
117
145
|
declare function a11yAudit(ctx: AuditContext): Promise<AuditResult>;
|
|
@@ -307,6 +335,7 @@ type WebsiteRow = {
|
|
|
307
335
|
gitRepo: string | null;
|
|
308
336
|
reportRecipientsTo: string | null;
|
|
309
337
|
reportRecipientsCc: string | null;
|
|
338
|
+
acceptedWatchConditions: string[];
|
|
310
339
|
/** First attachment in the Header image field (Airtable's signed URL — fetch before expiry). */
|
|
311
340
|
headerImage: {
|
|
312
341
|
url: string;
|
|
@@ -396,6 +425,10 @@ type SecurityAdvisory = {
|
|
|
396
425
|
title: string;
|
|
397
426
|
cves: string[];
|
|
398
427
|
url: string | null;
|
|
428
|
+
/** GitHub Dependabot dependency scope ("runtime" | "development"), when known. Lets the
|
|
429
|
+
* dashboard flag build-time-only ("development") vulns. Omitted for advisories from the
|
|
430
|
+
* lockfile `pnpm audit` fallback, which carries no per-package graph scope. */
|
|
431
|
+
scope?: "runtime" | "development";
|
|
399
432
|
};
|
|
400
433
|
|
|
401
434
|
type ResolvedCopy = {
|
|
@@ -776,6 +809,9 @@ type SiteCard = {
|
|
|
776
809
|
watchReasons: string[];
|
|
777
810
|
/** Structured watch tags ("lighthouse" / "stale") for the client filter. */
|
|
778
811
|
watchSignals: string[];
|
|
812
|
+
/** Watch reasons the operator has accepted: suppressed from the band, shown as a
|
|
813
|
+
* muted chip. Populated whenever the underlying condition is currently active. */
|
|
814
|
+
acceptedReasons: string[];
|
|
779
815
|
/** Count of NEW submissions for this site (optional; populated by buildCockpitModel). */
|
|
780
816
|
newSubmissions?: number;
|
|
781
817
|
};
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import "./chunk-
|
|
1
|
+
import "./chunk-4A5PYCGB.js";
|
|
2
2
|
import {
|
|
3
3
|
findDueReports
|
|
4
4
|
} from "./chunk-WVRPOXKH.js";
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
DEFAULT_INIT_STEPS,
|
|
10
10
|
a11yFixturesPage,
|
|
11
11
|
init
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-FX3QBSUF.js";
|
|
13
13
|
import {
|
|
14
14
|
convertToPnpm
|
|
15
15
|
} from "./chunk-2X322ZES.js";
|
|
@@ -24,11 +24,11 @@ import {
|
|
|
24
24
|
} from "./chunk-ZYBOT5PM.js";
|
|
25
25
|
import {
|
|
26
26
|
draftReportForSite
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-M5LX2VSR.js";
|
|
28
28
|
import "./chunk-6LIWDIXU.js";
|
|
29
29
|
import {
|
|
30
30
|
sendApprovedReports
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-QSL3EJGI.js";
|
|
32
32
|
import "./chunk-OGGRFBL4.js";
|
|
33
33
|
import "./chunk-GECESMVS.js";
|
|
34
34
|
import {
|
|
@@ -62,11 +62,13 @@ import {
|
|
|
62
62
|
runAudits,
|
|
63
63
|
runAuditsAcross,
|
|
64
64
|
securityAudit
|
|
65
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-TC7Z5LG7.js";
|
|
66
66
|
import "./chunk-LKGVSM2O.js";
|
|
67
|
-
import "./chunk-
|
|
68
|
-
import "./chunk-
|
|
67
|
+
import "./chunk-G2YRXV26.js";
|
|
68
|
+
import "./chunk-PTPTVVY7.js";
|
|
69
69
|
import "./chunk-XKKTK6WA.js";
|
|
70
|
+
import "./chunk-GASBX52O.js";
|
|
71
|
+
import "./chunk-4GACMVNE.js";
|
|
70
72
|
import {
|
|
71
73
|
fromJsonFile,
|
|
72
74
|
localPath
|
|
@@ -76,12 +78,12 @@ import "./chunk-XXTZBPUY.js";
|
|
|
76
78
|
import "./chunk-XTK5VIQB.js";
|
|
77
79
|
import {
|
|
78
80
|
fromAirtableBase
|
|
79
|
-
} from "./chunk-
|
|
81
|
+
} from "./chunk-MQM5YP4J.js";
|
|
80
82
|
import "./chunk-U5SZ4FOP.js";
|
|
81
83
|
import {
|
|
82
84
|
SEVERITY_RANK,
|
|
83
85
|
siteSlug
|
|
84
|
-
} from "./chunk-
|
|
86
|
+
} from "./chunk-7RGQ4KLE.js";
|
|
85
87
|
import "./chunk-HN6I2XWI.js";
|
|
86
88
|
import "./chunk-WEKL2HYJ.js";
|
|
87
89
|
import "./chunk-6VAL7XJT.js";
|
|
@@ -285,12 +287,13 @@ function securitySub(site) {
|
|
|
285
287
|
function advisoryRow(a) {
|
|
286
288
|
const sev = escapeHtml(a.severity);
|
|
287
289
|
const module = escapeHtml(a.module);
|
|
290
|
+
const scope = a.scope === "development" ? ` <span class="muted">(dev)</span>` : "";
|
|
288
291
|
const title = a.title ? ` \u2014 ${escapeHtml(a.title)}` : "";
|
|
289
292
|
const cves = a.cves.length > 0 ? ` <span class="muted">(${escapeHtml(a.cves.join(", "))})</span>` : "";
|
|
290
293
|
const link = a.url ? ` <a href="${escapeHtml(safeUrl(a.url))}" rel="noopener noreferrer">advisory \u25B8</a>` : "";
|
|
291
294
|
return `<li class="vuln-item">
|
|
292
295
|
<span class="pill sev-${sev}">${sev}</span>
|
|
293
|
-
<strong>${module}</strong>${title}${cves}${link}
|
|
296
|
+
<strong>${module}</strong>${scope}${title}${cves}${link}
|
|
294
297
|
</li>`;
|
|
295
298
|
}
|
|
296
299
|
function securitySection(site) {
|
|
@@ -651,7 +654,7 @@ function isReadyDeployStatus(status) {
|
|
|
651
654
|
function isFailedDeployStatus(status) {
|
|
652
655
|
return status !== null && FAILED_DEPLOY_STATES.has(status.toLowerCase());
|
|
653
656
|
}
|
|
654
|
-
var NEEDS_YOU_GROUP_RANK = { broken: 0,
|
|
657
|
+
var NEEDS_YOU_GROUP_RANK = { broken: 0, watch: 1, approval: 2 };
|
|
655
658
|
function buildNeedsYouFeed(model) {
|
|
656
659
|
const bySlug = /* @__PURE__ */ new Map();
|
|
657
660
|
const get = (siteName) => {
|
|
@@ -664,8 +667,8 @@ function buildNeedsYouFeed(model) {
|
|
|
664
667
|
reasons: [],
|
|
665
668
|
hasCritical: false,
|
|
666
669
|
broken: false,
|
|
667
|
-
|
|
668
|
-
|
|
670
|
+
watch: false,
|
|
671
|
+
approval: false
|
|
669
672
|
};
|
|
670
673
|
bySlug.set(slug, a);
|
|
671
674
|
}
|
|
@@ -673,17 +676,25 @@ function buildNeedsYouFeed(model) {
|
|
|
673
676
|
};
|
|
674
677
|
for (const card2 of model.cards) {
|
|
675
678
|
if (card2.tier === "attention") {
|
|
676
|
-
|
|
677
|
-
|
|
679
|
+
const isSelfPatchingVuln = (it) => it.kind === "vuln" && it.autoFixExhausted !== true;
|
|
680
|
+
const hardBroken = card2.items.filter((it) => !isSelfPatchingVuln(it));
|
|
681
|
+
const selfPatchingVulns = card2.items.filter(isSelfPatchingVuln);
|
|
682
|
+
if (hardBroken.length > 0) {
|
|
678
683
|
const a = get(card2.site.name);
|
|
679
|
-
|
|
684
|
+
for (const it of hardBroken) {
|
|
685
|
+
a.reasons.push(it.title);
|
|
686
|
+
if (it.severity === "critical") a.hasCritical = true;
|
|
687
|
+
}
|
|
680
688
|
a.broken = true;
|
|
681
|
-
|
|
689
|
+
} else if (selfPatchingVulns.length > 0) {
|
|
690
|
+
const a = get(card2.site.name);
|
|
691
|
+
for (const it of selfPatchingVulns) a.reasons.push(it.title);
|
|
692
|
+
a.watch = true;
|
|
682
693
|
}
|
|
683
694
|
} else if (card2.tier === "watch" && card2.watchReasons.length > 0) {
|
|
684
695
|
const a = get(card2.site.name);
|
|
685
696
|
for (const r of card2.watchReasons) a.reasons.push(r);
|
|
686
|
-
a.
|
|
697
|
+
a.watch = true;
|
|
687
698
|
}
|
|
688
699
|
}
|
|
689
700
|
for (const p of model.pending) {
|
|
@@ -694,7 +705,7 @@ function buildNeedsYouFeed(model) {
|
|
|
694
705
|
const items = [];
|
|
695
706
|
for (const a of bySlug.values()) {
|
|
696
707
|
if (a.reasons.length === 0) continue;
|
|
697
|
-
const group = a.broken ? "broken" : a.
|
|
708
|
+
const group = a.broken ? "broken" : a.watch ? "watch" : "approval";
|
|
698
709
|
items.push({
|
|
699
710
|
group,
|
|
700
711
|
hasCritical: a.hasCritical,
|
|
@@ -824,6 +835,8 @@ function chips(c) {
|
|
|
824
835
|
});
|
|
825
836
|
for (const reason of c.watchReasons)
|
|
826
837
|
items.push(`<span class="chip">${escapeHtml(reason)}</span>`);
|
|
838
|
+
for (const reason of c.acceptedReasons)
|
|
839
|
+
items.push(`<span class="chip accepted">\u2713 accepted: ${escapeHtml(reason)}</span>`);
|
|
827
840
|
return items.length ? `<div class="chips">${items.join("")}</div>` : "";
|
|
828
841
|
}
|
|
829
842
|
function signalsAttr(c) {
|
|
@@ -955,6 +968,8 @@ details.fleet-browse > summary, details.inbox > summary { cursor:pointer; font-w
|
|
|
955
968
|
@media (prefers-color-scheme: dark) { .chip { background:#222; } }
|
|
956
969
|
.chip.critical { background:#fdecea; color:#b00; }
|
|
957
970
|
.chip.stuck { border:1px solid #b00; font-weight:600; }
|
|
971
|
+
.chip.accepted { background:transparent; border:1px dashed #bbb; color:#888; }
|
|
972
|
+
@media (prefers-color-scheme: dark) { .chip.accepted { border-color:#444; color:#888; } }
|
|
958
973
|
.badge { font-weight:700; color:#C00; font-size:0.72rem; margin-right:0.25rem; }
|
|
959
974
|
.verdict { border-radius:8px; padding:0.9rem 1.1rem; margin-bottom:1.25rem; }
|
|
960
975
|
.verdict .verdict-line { font-weight:800; font-size:1.4rem; }
|
|
@@ -963,7 +978,11 @@ details.fleet-browse > summary, details.inbox > summary { cursor:pointer; font-w
|
|
|
963
978
|
.verdict.ok .verdict-meta { color:#2e7d32; }
|
|
964
979
|
.verdict.warn { background:#fdecea; color:#b00; }
|
|
965
980
|
.verdict.warn .verdict-meta { color:#b00; opacity:0.85; }
|
|
966
|
-
|
|
981
|
+
.verdict.watch { background:#fff4e5; color:#a65a00; }
|
|
982
|
+
.verdict.watch .verdict-meta { color:#a65a00; opacity:0.85; }
|
|
983
|
+
.verdict.soft { background:#e7f1ff; color:#1c5d99; }
|
|
984
|
+
.verdict.soft .verdict-meta { color:#1c5d99; opacity:0.85; }
|
|
985
|
+
@media (prefers-color-scheme: dark) { .verdict.ok { background:#10240f; color:#7fce85; } .verdict.warn { background:#2a0f0d; color:#ff8a80; } .verdict.watch { background:#2a2410; color:#ffd454; } .verdict.soft { background:#0f1d2a; color:#7fb6e8; } }
|
|
967
986
|
.verdict .fleet-actions { margin:0.6rem 0 0; }
|
|
968
987
|
.needs-you { border:1px solid #e5e5e5; border-radius:8px; padding:0.75rem 1rem; margin-bottom:1.25rem; }
|
|
969
988
|
@media (prefers-color-scheme: dark) { .needs-you { border-color:#2a2a2a; background:#181818; } }
|
|
@@ -977,39 +996,66 @@ details.fleet-browse > summary, details.inbox > summary { cursor:pointer; font-w
|
|
|
977
996
|
.dot { width:0.55rem; height:0.55rem; border-radius:50%; display:inline-block; flex:0 0 auto; }
|
|
978
997
|
.dot.broken { background:#dc2626; }
|
|
979
998
|
.dot.approval { background:#2563eb; }
|
|
980
|
-
.dot.
|
|
999
|
+
.dot.watch { background:#f59e0b; }
|
|
981
1000
|
`;
|
|
982
|
-
function
|
|
1001
|
+
function needsYouCounts(feed) {
|
|
1002
|
+
let broken = 0;
|
|
1003
|
+
let watch = 0;
|
|
1004
|
+
let approval = 0;
|
|
1005
|
+
for (const i of feed) {
|
|
1006
|
+
if (i.group === "broken") broken++;
|
|
1007
|
+
else if (i.group === "watch") watch++;
|
|
1008
|
+
else approval++;
|
|
1009
|
+
}
|
|
1010
|
+
return { broken, watch, approval };
|
|
1011
|
+
}
|
|
1012
|
+
function verdictBar(model, feed) {
|
|
983
1013
|
const auditedIso = fleetLastAuditedAt(model.cards);
|
|
984
|
-
const
|
|
985
|
-
const
|
|
986
|
-
const
|
|
1014
|
+
const auditedTerm = auditedIso ? `fleet last audited ${escapeHtml(relativeTimeFromNow(auditedIso))}` : null;
|
|
1015
|
+
const total = model.cards.length;
|
|
1016
|
+
const { broken, watch, approval } = needsYouCounts(feed);
|
|
1017
|
+
const healthy = Math.max(0, total - (broken + watch + approval));
|
|
987
1018
|
const actions = `<div class="fleet-actions">
|
|
988
1019
|
<button type="button" class="refresh-fleet" data-refresh-url="/api/fleet/refresh">\u21BB Audit fleet</button>
|
|
989
1020
|
<div id="rf-status" class="rf-status" aria-live="polite"></div>
|
|
990
1021
|
</div>`;
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
<div class="verdict-meta">${sitesWord} healthy${audited}</div>
|
|
995
|
-
${actions}
|
|
996
|
-
</div>`;
|
|
997
|
-
}
|
|
998
|
-
const noun = feedCount === 1 ? "site needs" : "sites need";
|
|
999
|
-
return `<div class="verdict warn">
|
|
1000
|
-
<div class="verdict-line">\u26A0 ${feedCount} ${noun} you</div>
|
|
1001
|
-
<div class="verdict-meta">${sitesWord}${audited}</div>
|
|
1022
|
+
const render = (cls, line, terms) => `<div class="verdict ${cls}">
|
|
1023
|
+
<div class="verdict-line">${line}</div>
|
|
1024
|
+
<div class="verdict-meta">${terms.filter(Boolean).join(" \xB7 ")}</div>
|
|
1002
1025
|
${actions}
|
|
1003
1026
|
</div>`;
|
|
1027
|
+
if (broken === 0 && watch === 0 && approval === 0) {
|
|
1028
|
+
const sitesWord = `${total} site${total === 1 ? "" : "s"}`;
|
|
1029
|
+
return render("ok", "\u2713 All clear", [`${sitesWord} healthy`, auditedTerm]);
|
|
1030
|
+
}
|
|
1031
|
+
const watchTerm = watch > 0 ? `${watch} watching` : null;
|
|
1032
|
+
const approvalTerm = approval > 0 ? `${approval} waiting on you` : null;
|
|
1033
|
+
const healthyTerm = healthy > 0 ? `${healthy} healthy` : null;
|
|
1034
|
+
if (broken > 0) {
|
|
1035
|
+
return render("warn", `\u26A0 ${broken} site${broken === 1 ? "" : "s"} broken`, [
|
|
1036
|
+
watchTerm,
|
|
1037
|
+
approvalTerm,
|
|
1038
|
+
healthyTerm,
|
|
1039
|
+
auditedTerm
|
|
1040
|
+
]);
|
|
1041
|
+
}
|
|
1042
|
+
if (watch > 0) {
|
|
1043
|
+
return render("watch", `${watch} site${watch === 1 ? "" : "s"} to watch`, [
|
|
1044
|
+
approvalTerm,
|
|
1045
|
+
healthyTerm,
|
|
1046
|
+
auditedTerm
|
|
1047
|
+
]);
|
|
1048
|
+
}
|
|
1049
|
+
return render("soft", `${approval} waiting on you`, [healthyTerm, auditedTerm]);
|
|
1004
1050
|
}
|
|
1005
1051
|
var NEEDS_YOU_GROUP_LABEL = {
|
|
1006
1052
|
broken: "Broken",
|
|
1007
|
-
|
|
1008
|
-
|
|
1053
|
+
watch: "Watch",
|
|
1054
|
+
approval: "Waiting on your yes"
|
|
1009
1055
|
};
|
|
1010
1056
|
function renderNeedsYouFeed(feed) {
|
|
1011
1057
|
if (feed.length === 0) return "";
|
|
1012
|
-
const groups = ["broken", "
|
|
1058
|
+
const groups = ["broken", "watch", "approval"];
|
|
1013
1059
|
const blocks = groups.map((g) => {
|
|
1014
1060
|
const rows = feed.filter((i) => i.group === g);
|
|
1015
1061
|
if (rows.length === 0) return "";
|
|
@@ -1213,7 +1259,7 @@ function renderCockpitHtml(model) {
|
|
|
1213
1259
|
<body>
|
|
1214
1260
|
<h1>Reddoor fleet cockpit</h1>
|
|
1215
1261
|
<div class="meta">${total} site${total === 1 ? "" : "s"} on the Reddoor stack.</div>
|
|
1216
|
-
${verdictBar(model, feed
|
|
1262
|
+
${verdictBar(model, feed)}
|
|
1217
1263
|
${renderNeedsYouFeed(feed)}
|
|
1218
1264
|
${renderFleetBrowsePanel(model)}
|
|
1219
1265
|
${renderRecentlyLane(model)}
|