@reddoorla/maintenance 0.66.0 → 0.68.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/dist/airtable-WGQQ3KAD.js +9 -0
- package/dist/{announce-YPXVHNNP.js → announce-72R3LRNT.js} +9 -11
- package/dist/{announce-YPXVHNNP.js.map → announce-72R3LRNT.js.map} +1 -1
- package/dist/{bump-deps-INDDTDSG.js → bump-deps-QNM3H4GI.js} +2 -2
- package/dist/{chunk-CH6K2VMM.js → chunk-2E2X5BCG.js} +43 -3
- package/dist/chunk-2E2X5BCG.js.map +1 -0
- package/dist/{chunk-VKAYZVMI.js → chunk-45AUGDFR.js} +2 -1
- package/dist/chunk-45AUGDFR.js.map +1 -0
- package/dist/{chunk-SYTPVYIK.js → chunk-6JFOAPW4.js} +241 -4
- package/dist/chunk-6JFOAPW4.js.map +1 -0
- package/dist/{chunk-NMGGP5WN.js → chunk-6MT5W6UF.js} +2 -2
- package/dist/{chunk-XKSKHHSX.js → chunk-A5KYRGQA.js} +4 -4
- package/dist/{chunk-4P6UQG5L.js → chunk-C3PDS623.js} +6 -1
- package/dist/chunk-C3PDS623.js.map +1 -0
- package/dist/{chunk-JD3X3QMP.js → chunk-GKSXZIMX.js} +3 -3
- package/dist/{chunk-CWSQZ27M.js → chunk-H6YZBXBQ.js} +19 -13
- package/dist/chunk-H6YZBXBQ.js.map +1 -0
- package/dist/{chunk-XKKTK6WA.js → chunk-HIN3P4ZN.js} +23 -2
- package/dist/chunk-HIN3P4ZN.js.map +1 -0
- package/dist/chunk-MAYHLXCS.js +322 -0
- package/dist/chunk-MAYHLXCS.js.map +1 -0
- package/dist/{chunk-G35B5U6F.js → chunk-O6G4HHMF.js} +7 -9
- package/dist/{chunk-G35B5U6F.js.map → chunk-O6G4HHMF.js.map} +1 -1
- package/dist/{chunk-G2YRXV26.js → chunk-OTBA7DON.js} +2 -2
- package/dist/{chunk-PTPTVVY7.js → chunk-PH3AGF4Z.js} +2 -2
- package/dist/{chunk-6LIWDIXU.js → chunk-RINXPU3H.js} +2 -2
- package/dist/{chunk-YBYWNUT3.js → chunk-XWPZ46IN.js} +2 -2
- package/dist/{chunk-LISJ3K3R.js → chunk-YQMYQAJX.js} +2 -2
- package/dist/cli/bin.js +33 -15
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.js +8 -8
- package/dist/client-NVVQK3HH.js +11 -0
- package/dist/{convert-to-pnpm-4GD4667L.js → convert-to-pnpm-XH7NYRVK.js} +2 -2
- package/dist/{db-YBLQR2YY.js → db-3QW2XEXA.js} +3 -3
- package/dist/{digest-SIZUXT67.js → digest-J7TDG62Q.js} +14 -5
- package/dist/digest-J7TDG62Q.js.map +1 -0
- package/dist/ensure-site-2PF522VL.js +87 -0
- package/dist/ensure-site-2PF522VL.js.map +1 -0
- package/dist/forms/index.d.ts +13 -0
- package/dist/forms/index.js +28 -2
- package/dist/forms/index.js.map +1 -1
- package/dist/{github-signals-3O64PFWP.js → github-signals-PP7GWYIY.js} +7 -7
- package/dist/index.d.ts +19 -2
- package/dist/index.js +117 -39
- package/dist/index.js.map +1 -1
- package/dist/{init-KMWXJZEX.js → init-WLR35YGN.js} +2 -2
- package/dist/{launch-5KRCKFYM.js → launch-2HPQXWII.js} +10 -12
- package/dist/{launch-5KRCKFYM.js.map → launch-2HPQXWII.js.map} +1 -1
- package/dist/{lighthouse-airtable-5JICCZND.js → lighthouse-airtable-UBSXKHAQ.js} +3 -3
- package/dist/migrate-35DNNF3H.js +7 -0
- package/dist/{onboard-RCBXS5B3.js → onboard-X6KJ5Z2V.js} +2 -2
- package/dist/{orchestrate-O4KR6JME.js → orchestrate-PVCGBH4K.js} +7 -9
- package/dist/preflight-KWVYJ2DU.js +64 -0
- package/dist/preflight-KWVYJ2DU.js.map +1 -0
- package/dist/{renovate-dispatch-ABRSIT77.js → renovate-dispatch-LG4RJGDY.js} +2 -2
- package/dist/{report-LSGTMXQJ.js → report-ZBFMTT76.js} +8 -9
- package/dist/{report-LSGTMXQJ.js.map → report-ZBFMTT76.js.map} +1 -1
- package/dist/{self-updating-CPMY3HFY.js → self-updating-JTTFOJ7Q.js} +2 -2
- package/dist/{selftest-7Y5K4PYL.js → selftest-LFZPZRJP.js} +10 -12
- package/dist/{selftest-7Y5K4PYL.js.map → selftest-LFZPZRJP.js.map} +1 -1
- package/dist/{svelte-codemods-GJRTBLJE.js → svelte-codemods-KJLNH6FQ.js} +2 -2
- package/dist/{sync-configs-NYJNSIRN.js → sync-configs-6GBZLGRQ.js} +2 -2
- package/dist/{upgrade-LVYRDKDL.js → upgrade-TPTE26FG.js} +2 -2
- package/dist/{websites-5ETIXB7F.js → websites-6YTT5ZTO.js} +2 -2
- package/dist/{write-audits-to-airtable-GHR337N7.js → write-audits-to-airtable-IWOO4FFH.js} +4 -4
- package/package.json +1 -1
- package/dist/airtable-M4AQHRNV.js +0 -9
- package/dist/chunk-4P6UQG5L.js.map +0 -1
- package/dist/chunk-CH6K2VMM.js.map +0 -1
- package/dist/chunk-CWSQZ27M.js.map +0 -1
- package/dist/chunk-OCS7SZUZ.js +0 -243
- package/dist/chunk-OCS7SZUZ.js.map +0 -1
- package/dist/chunk-OGGRFBL4.js +0 -14
- package/dist/chunk-OGGRFBL4.js.map +0 -1
- package/dist/chunk-SYTPVYIK.js.map +0 -1
- package/dist/chunk-VKAYZVMI.js.map +0 -1
- package/dist/chunk-XKKTK6WA.js.map +0 -1
- package/dist/client-C2GOZD2D.js +0 -11
- package/dist/digest-SIZUXT67.js.map +0 -1
- package/dist/migrate-BFOG2OCP.js +0 -7
- /package/dist/{airtable-M4AQHRNV.js.map → airtable-WGQQ3KAD.js.map} +0 -0
- /package/dist/{bump-deps-INDDTDSG.js.map → bump-deps-QNM3H4GI.js.map} +0 -0
- /package/dist/{chunk-NMGGP5WN.js.map → chunk-6MT5W6UF.js.map} +0 -0
- /package/dist/{chunk-XKSKHHSX.js.map → chunk-A5KYRGQA.js.map} +0 -0
- /package/dist/{chunk-JD3X3QMP.js.map → chunk-GKSXZIMX.js.map} +0 -0
- /package/dist/{chunk-G2YRXV26.js.map → chunk-OTBA7DON.js.map} +0 -0
- /package/dist/{chunk-PTPTVVY7.js.map → chunk-PH3AGF4Z.js.map} +0 -0
- /package/dist/{chunk-6LIWDIXU.js.map → chunk-RINXPU3H.js.map} +0 -0
- /package/dist/{chunk-YBYWNUT3.js.map → chunk-XWPZ46IN.js.map} +0 -0
- /package/dist/{chunk-LISJ3K3R.js.map → chunk-YQMYQAJX.js.map} +0 -0
- /package/dist/{client-C2GOZD2D.js.map → client-NVVQK3HH.js.map} +0 -0
- /package/dist/{convert-to-pnpm-4GD4667L.js.map → convert-to-pnpm-XH7NYRVK.js.map} +0 -0
- /package/dist/{db-YBLQR2YY.js.map → db-3QW2XEXA.js.map} +0 -0
- /package/dist/{github-signals-3O64PFWP.js.map → github-signals-PP7GWYIY.js.map} +0 -0
- /package/dist/{init-KMWXJZEX.js.map → init-WLR35YGN.js.map} +0 -0
- /package/dist/{lighthouse-airtable-5JICCZND.js.map → lighthouse-airtable-UBSXKHAQ.js.map} +0 -0
- /package/dist/{migrate-BFOG2OCP.js.map → migrate-35DNNF3H.js.map} +0 -0
- /package/dist/{onboard-RCBXS5B3.js.map → onboard-X6KJ5Z2V.js.map} +0 -0
- /package/dist/{orchestrate-O4KR6JME.js.map → orchestrate-PVCGBH4K.js.map} +0 -0
- /package/dist/{renovate-dispatch-ABRSIT77.js.map → renovate-dispatch-LG4RJGDY.js.map} +0 -0
- /package/dist/{self-updating-CPMY3HFY.js.map → self-updating-JTTFOJ7Q.js.map} +0 -0
- /package/dist/{svelte-codemods-GJRTBLJE.js.map → svelte-codemods-KJLNH6FQ.js.map} +0 -0
- /package/dist/{sync-configs-NYJNSIRN.js.map → sync-configs-6GBZLGRQ.js.map} +0 -0
- /package/dist/{upgrade-LVYRDKDL.js.map → upgrade-TPTE26FG.js.map} +0 -0
- /package/dist/{websites-5ETIXB7F.js.map → websites-6YTT5ZTO.js.map} +0 -0
- /package/dist/{write-audits-to-airtable-GHR337N7.js.map → write-audits-to-airtable-IWOO4FFH.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import "./chunk-
|
|
1
|
+
import "./chunk-2E2X5BCG.js";
|
|
2
|
+
import {
|
|
3
|
+
approveBlockers
|
|
4
|
+
} from "./chunk-MAYHLXCS.js";
|
|
2
5
|
import {
|
|
3
6
|
findDueReports
|
|
4
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-45AUGDFR.js";
|
|
5
8
|
import {
|
|
6
9
|
upgradeSvelte4to5
|
|
7
10
|
} from "./chunk-ZO7WNY57.js";
|
|
@@ -24,26 +27,25 @@ import {
|
|
|
24
27
|
} from "./chunk-ZYBOT5PM.js";
|
|
25
28
|
import {
|
|
26
29
|
draftReportForSite
|
|
27
|
-
} from "./chunk-
|
|
28
|
-
import "./chunk-
|
|
30
|
+
} from "./chunk-O6G4HHMF.js";
|
|
31
|
+
import "./chunk-RINXPU3H.js";
|
|
29
32
|
import {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
parseAddresses,
|
|
34
|
+
sendApprovedReports,
|
|
35
|
+
withGlobalCc
|
|
36
|
+
} from "./chunk-H6YZBXBQ.js";
|
|
33
37
|
import "./chunk-GECESMVS.js";
|
|
34
38
|
import {
|
|
35
39
|
BLURRED_CID,
|
|
36
40
|
CHECK_CID,
|
|
37
|
-
loadBundledImages,
|
|
38
|
-
renderReportHtml
|
|
39
|
-
} from "./chunk-SYTPVYIK.js";
|
|
40
|
-
import {
|
|
41
41
|
checklistFor,
|
|
42
42
|
escapeHtml,
|
|
43
43
|
isChecklistComplete,
|
|
44
44
|
isPendingApproval,
|
|
45
|
+
loadBundledImages,
|
|
46
|
+
renderReportHtml,
|
|
45
47
|
safeUrl
|
|
46
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-6JFOAPW4.js";
|
|
47
49
|
import {
|
|
48
50
|
bumpDeps
|
|
49
51
|
} from "./chunk-D3DQFC32.js";
|
|
@@ -64,9 +66,9 @@ import {
|
|
|
64
66
|
securityAudit
|
|
65
67
|
} from "./chunk-WCN54744.js";
|
|
66
68
|
import "./chunk-LKGVSM2O.js";
|
|
67
|
-
import "./chunk-
|
|
68
|
-
import "./chunk-
|
|
69
|
-
import "./chunk-
|
|
69
|
+
import "./chunk-OTBA7DON.js";
|
|
70
|
+
import "./chunk-PH3AGF4Z.js";
|
|
71
|
+
import "./chunk-HIN3P4ZN.js";
|
|
70
72
|
import "./chunk-GASBX52O.js";
|
|
71
73
|
import "./chunk-YEGHUODU.js";
|
|
72
74
|
import {
|
|
@@ -78,12 +80,12 @@ import "./chunk-XXTZBPUY.js";
|
|
|
78
80
|
import "./chunk-XTK5VIQB.js";
|
|
79
81
|
import {
|
|
80
82
|
fromAirtableBase
|
|
81
|
-
} from "./chunk-
|
|
83
|
+
} from "./chunk-6MT5W6UF.js";
|
|
82
84
|
import "./chunk-U5SZ4FOP.js";
|
|
83
85
|
import {
|
|
84
86
|
SEVERITY_RANK,
|
|
85
87
|
siteSlug
|
|
86
|
-
} from "./chunk-
|
|
88
|
+
} from "./chunk-C3PDS623.js";
|
|
87
89
|
import "./chunk-HN6I2XWI.js";
|
|
88
90
|
import "./chunk-WEKL2HYJ.js";
|
|
89
91
|
import "./chunk-6VAL7XJT.js";
|
|
@@ -172,6 +174,9 @@ function extraFieldsList(raw) {
|
|
|
172
174
|
).join("");
|
|
173
175
|
return rows;
|
|
174
176
|
}
|
|
177
|
+
function isVisibleInStrip(s) {
|
|
178
|
+
return s.status !== "spam_auto";
|
|
179
|
+
}
|
|
175
180
|
function renderSubmissionRow(s) {
|
|
176
181
|
const when = s.submittedAt ? escapeHtml(relativeTimeFromNow(s.submittedAt)) : "\u2014";
|
|
177
182
|
const type = escapeHtml(s.formType);
|
|
@@ -181,6 +186,8 @@ function renderSubmissionRow(s) {
|
|
|
181
186
|
const id = escapeHtml(s.id);
|
|
182
187
|
const url = `/api/submissions/${encodeURIComponent(s.id)}/status`;
|
|
183
188
|
const btn = (label, action) => `<button class="subm-status" data-id="${id}" data-status="${action}" data-url="${url}">${label}</button>`;
|
|
189
|
+
const provenance = s.status === "spam_auto" && s.spamScore !== null && s.spamScore !== void 0 ? ` <span class="subm-provenance" title="${escapeHtml(s.spamReason ?? "")}">auto-spam \xB7 ${escapeHtml(String(s.spamScore))}</span>` : "";
|
|
190
|
+
const recover = s.status === "spam_auto" ? btn("Not spam \u2192 new", "new") : "";
|
|
184
191
|
const kv = (label, value) => value === null || value === "" ? "" : `<div class="subm-kv"><span class="k">${label}</span> ${escapeHtml(String(value))}</div>`;
|
|
185
192
|
const sourceLink = s.sourceUrl ? `<div class="subm-kv"><span class="k">Source</span> <a href="${escapeHtml(safeUrl(s.sourceUrl))}" rel="noopener noreferrer">${escapeHtml(s.sourceUrl)}</a></div>` : "";
|
|
186
193
|
const messageBlock = s.message ? `<div class="subm-kv"><span class="k">Message</span></div><div class="subm-msg">${escapeHtml(s.message)}</div>` : "";
|
|
@@ -196,10 +203,10 @@ function renderSubmissionRow(s) {
|
|
|
196
203
|
].join("");
|
|
197
204
|
return `<li class="subm-item">
|
|
198
205
|
<details>
|
|
199
|
-
<summary class="subm-head"><strong>${type}</strong> \xB7 ${who} <span class="muted">${email}</span> <span class="pill subm-${status}">${status}</span
|
|
206
|
+
<summary class="subm-head"><strong>${type}</strong> \xB7 ${who} <span class="muted">${email}</span> <span class="pill subm-${status}">${status}</span>${provenance} <span class="muted">${when}</span></summary>
|
|
200
207
|
<div class="subm-detail">${details}</div>
|
|
201
208
|
</details>
|
|
202
|
-
<div class="subm-actions">${btn("Read", "read")}${btn("Archive", "archived")}${btn("Spam", "spam")}</div>
|
|
209
|
+
<div class="subm-actions">${recover}${btn("Read", "read")}${btn("Archive", "archived")}${btn("Spam", "spam")}</div>
|
|
203
210
|
</li>`;
|
|
204
211
|
}
|
|
205
212
|
var SUBMISSION_STYLES = `.subm-list { list-style: none; padding: 0; margin: 0; }
|
|
@@ -220,6 +227,8 @@ button.subm-status:disabled { opacity: 0.6; cursor: default; }
|
|
|
220
227
|
.pill.subm-read { background: #f0f0f0; color: #555; }
|
|
221
228
|
.pill.subm-archived { background: #eee; color: #888; }
|
|
222
229
|
.pill.subm-spam { background: #fdecea; color: #b00; }
|
|
230
|
+
.pill.subm-spam_auto { background: #fff4e5; color: #a65a00; }
|
|
231
|
+
.subm-provenance { font-size: 0.72rem; border-radius: 0.25rem; padding: 0 0.35rem; white-space: nowrap; background: #fff4e5; color: #a65a00; }
|
|
223
232
|
.subm-viewall { font-size: 0.8rem; font-weight: normal; margin-left: 0.4rem; white-space: nowrap; }`;
|
|
224
233
|
var SUBMISSION_STATUS_SCRIPT = `document.querySelectorAll("button.subm-status").forEach((b) => {
|
|
225
234
|
b.addEventListener("click", async () => {
|
|
@@ -320,21 +329,66 @@ function checklistBlock(r) {
|
|
|
320
329
|
}).join("");
|
|
321
330
|
return `<div class="checklist" data-checklist-for="${rid}">${boxes}</div>`;
|
|
322
331
|
}
|
|
323
|
-
function approveButton(r) {
|
|
324
|
-
const disabled = isChecklistComplete(r) ? "" : " disabled";
|
|
325
|
-
|
|
332
|
+
function approveButton(r, blocked) {
|
|
333
|
+
const disabled = isChecklistComplete(r) && !blocked ? "" : " disabled";
|
|
334
|
+
const blockedAttr = blocked ? ` data-send-blocked="1"` : "";
|
|
335
|
+
return `<button class="approve" data-report-id="${escapeHtml(r.id)}" data-approve-url="${escapeHtml(`/api/reports/${encodeURIComponent(r.id)}/approve`)}"${blockedAttr}${disabled}>Approve</button>`;
|
|
326
336
|
}
|
|
327
|
-
function
|
|
337
|
+
function preflightChip(findings) {
|
|
338
|
+
const fails = findings.filter((f) => f.level === "fail");
|
|
339
|
+
const warns = findings.filter((f) => f.level === "warn");
|
|
340
|
+
if (fails.length > 0) {
|
|
341
|
+
const title = escapeHtml(fails.map((f) => `${f.check}: ${f.message}`).join("\n"));
|
|
342
|
+
return `<span class="preflight preflight-fail" title="${title}">preflight \u2717 ${fails.length}</span>`;
|
|
343
|
+
}
|
|
344
|
+
if (warns.length > 0) {
|
|
345
|
+
const title = escapeHtml(warns.map((f) => `${f.check}: ${f.message}`).join("\n"));
|
|
346
|
+
return `<span class="preflight preflight-warn" title="${title}">preflight \u26A0 ${warns.length}</span>`;
|
|
347
|
+
}
|
|
348
|
+
return `<span class="preflight preflight-ok" title="recipients, header image and report scores all present">preflight \u2713</span>`;
|
|
349
|
+
}
|
|
350
|
+
function nextDailyRun(now) {
|
|
351
|
+
const run = new Date(now);
|
|
352
|
+
run.setUTCHours(9, 23, 0, 0);
|
|
353
|
+
if (run.getTime() <= now.getTime()) run.setUTCDate(run.getUTCDate() + 1);
|
|
354
|
+
return run;
|
|
355
|
+
}
|
|
356
|
+
function sendTimingLine(now) {
|
|
357
|
+
const minuteOfDay = now.getUTCHours() * 60 + now.getUTCMinutes();
|
|
358
|
+
const RUN_START = 9 * 60 + 23;
|
|
359
|
+
const GRACE_END = 10 * 60 + 30;
|
|
360
|
+
if (minuteOfDay >= RUN_START && minuteOfDay < GRACE_END) {
|
|
361
|
+
return `<span class="muted">today's 09:23 UTC run may still be in flight \u2014 approving now can send within minutes</span>`;
|
|
362
|
+
}
|
|
363
|
+
const run = nextDailyRun(now);
|
|
364
|
+
const leadMin = Math.floor((run.getTime() - now.getTime()) / 6e4);
|
|
365
|
+
const lead = leadMin >= 60 ? `~${Math.floor(leadMin / 60)}h` : leadMin >= 1 ? `~${leadMin} min` : "under 1 min";
|
|
366
|
+
return `<span class="muted">approve \u2260 send: goes out at the next daily run, 09:23 UTC (${lead})</span>`;
|
|
367
|
+
}
|
|
368
|
+
function recipientsLine(site) {
|
|
369
|
+
const to = parseAddresses(site.reportRecipientsTo) ?? parseAddresses(site.pointOfContact) ?? [];
|
|
370
|
+
if (to.length === 0) {
|
|
371
|
+
return `<span class="recipients recipients-missing">recipients: none resolve \u2014 set point of contact in Airtable</span>`;
|
|
372
|
+
}
|
|
373
|
+
const cc = withGlobalCc(parseAddresses(site.reportRecipientsCc), to);
|
|
374
|
+
const ccPart = cc.length > 0 ? ` \xB7 CC ${cc.map(escapeHtml).join(", ")}` : "";
|
|
375
|
+
return `<span class="recipients">To ${to.map(escapeHtml).join(", ")}${ccPart}</span>`;
|
|
376
|
+
}
|
|
377
|
+
function pendingRow(r, site, now) {
|
|
328
378
|
const type = escapeHtml(r.reportType);
|
|
329
379
|
const period = r.period ? escapeHtml(r.period) : "\u2014";
|
|
330
|
-
|
|
380
|
+
const findings = approveBlockers(site, r);
|
|
381
|
+
const blocked = findings.some((f) => f.level === "fail");
|
|
382
|
+
const preview = r.renderedHtmlAttachment ? `<a href="${escapeHtml(safeUrl(r.renderedHtmlAttachment.url))}" rel="noopener noreferrer" title="rendered at draft time \u2014 Commentary/subject edits after drafting are not reflected">draft preview \u25B8</a>` : `<span class="muted">no preview yet</span>`;
|
|
383
|
+
const sendLine = sendTimingLine(now);
|
|
384
|
+
return `<li><div class="pending-head"><strong>${type}</strong> <span class="muted">${period}</span> ${preflightChip(findings)} ${preview} ${approveButton(r, blocked)}</div><div class="pending-info">${recipientsLine(site)} ${sendLine}</div>${checklistBlock(r)}</li>`;
|
|
331
385
|
}
|
|
332
|
-
function pendingSection(reports) {
|
|
386
|
+
function pendingSection(reports, site, now) {
|
|
333
387
|
const pending = reports.filter(isPendingApproval);
|
|
334
388
|
if (pending.length === 0) return "";
|
|
335
389
|
return `<div class="section pending">
|
|
336
390
|
<h2>Pending your yes (${pending.length})</h2>
|
|
337
|
-
<ul class="pending-list">${pending.map(pendingRow).join("")}</ul>
|
|
391
|
+
<ul class="pending-list">${pending.map((r) => pendingRow(r, site, now)).join("")}</ul>
|
|
338
392
|
</div>`;
|
|
339
393
|
}
|
|
340
394
|
function gaUsersCell(r) {
|
|
@@ -351,24 +405,28 @@ function searchCell(r) {
|
|
|
351
405
|
}
|
|
352
406
|
return DASH;
|
|
353
407
|
}
|
|
354
|
-
function reportRow(r) {
|
|
408
|
+
function reportRow(r, site) {
|
|
355
409
|
const date = r.completedOn ? escapeHtml(r.completedOn) : DASH;
|
|
356
410
|
const type = escapeHtml(r.reportType);
|
|
357
411
|
const id = escapeHtml(r.reportId);
|
|
358
412
|
const ga = gaUsersCell(r);
|
|
359
413
|
const search = searchCell(r);
|
|
360
414
|
const link = r.renderedHtmlAttachment ? `<a href="${escapeHtml(safeUrl(r.renderedHtmlAttachment.url))}">view</a>` : `<span class="muted">no attachment</span>`;
|
|
361
|
-
const action = isPendingApproval(r) ? approveButton(
|
|
415
|
+
const action = isPendingApproval(r) ? approveButton(
|
|
416
|
+
r,
|
|
417
|
+
approveBlockers(site, r).some((f) => f.level === "fail")
|
|
418
|
+
) : "";
|
|
362
419
|
return `<tr><td>${date}</td><td>${type}</td><td><code>${id}</code></td><td>${ga}</td><td>${search}</td><td>${link}</td><td>${action}</td></tr>`;
|
|
363
420
|
}
|
|
364
421
|
var SUBMISSIONS_PER_SITE_CAP = 25;
|
|
365
422
|
function submissionsSection(submissions, site) {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
const
|
|
423
|
+
const visible = submissions.filter(isVisibleInStrip);
|
|
424
|
+
if (visible.length === 0) return "";
|
|
425
|
+
const recent = [...visible].sort((a, b) => (b.submittedAt ?? "").localeCompare(a.submittedAt ?? "")).slice(0, SUBMISSIONS_PER_SITE_CAP);
|
|
426
|
+
const note = visible.length > recent.length ? `<span class="muted"> \u2014 showing ${recent.length} of ${visible.length}</span>` : "";
|
|
369
427
|
const viewAll = `<a class="subm-viewall" href="/submissions?site=${escapeHtml(siteSlug(site.name))}">View all for this site \u2192</a>`;
|
|
370
428
|
return `<div class="section submissions">
|
|
371
|
-
<h2>Form submissions (${
|
|
429
|
+
<h2>Form submissions (${visible.length})${note} ${viewAll}</h2>
|
|
372
430
|
<ul class="subm-list">${recent.map(renderSubmissionRow).join("")}</ul>
|
|
373
431
|
</div>`;
|
|
374
432
|
}
|
|
@@ -463,10 +521,16 @@ th, td { text-align: left; padding: 0.5rem; border-bottom: 1px solid #eee; }
|
|
|
463
521
|
.empty { color: #999; padding: 1rem; border: 1px dashed #ccc; border-radius: 6px; text-align: center; }
|
|
464
522
|
button.approve { font: inherit; padding: 0.35rem 0.85rem; border: 1px solid #2c7; border-radius: 6px; background: #2c7; color: #fff; cursor: pointer; }
|
|
465
523
|
button.approve:disabled { opacity: 0.6; cursor: default; }
|
|
524
|
+
.pending-info { display: flex; flex-wrap: wrap; gap: 0.35rem 1rem; font-size: 0.82rem; margin: 0.3rem 0 0.15rem; }
|
|
525
|
+
.recipients-missing { color: #e57373; }
|
|
526
|
+
.preflight { font-size: 0.78rem; padding: 0.1rem 0.45rem; border-radius: 999px; white-space: nowrap; }
|
|
527
|
+
.preflight-ok { background: #1b5e2033; color: #7bc67e; }
|
|
528
|
+
.preflight-warn { background: #f9a82533; color: #d79921; }
|
|
529
|
+
.preflight-fail { background: #b71c1c33; color: #e57373; }
|
|
466
530
|
.pending-list { list-style: none; padding: 0; margin: 0; }
|
|
467
531
|
.pending-list li { padding: 0.5rem; border-bottom: 1px solid #eee; }
|
|
468
532
|
@media (prefers-color-scheme: dark) { .pending-list li { border-color: #2a2a2a; } }
|
|
469
|
-
.pending-head { display: flex; align-items: center; gap: 0.5rem; }
|
|
533
|
+
.pending-head { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; row-gap: 0.25rem; }
|
|
470
534
|
.checklist { display: flex; flex-wrap: wrap; gap: 0.25rem 1.25rem; margin: 0.5rem 0 0.25rem 0.25rem; }
|
|
471
535
|
.check-item { display: flex; align-items: center; gap: 0.4rem; font-size: 0.9rem; }
|
|
472
536
|
.check-item input { margin: 0; }
|
|
@@ -516,7 +580,7 @@ function renderSiteDashboardHtml(site, reports, submissions = [], spamTotals = n
|
|
|
516
580
|
const recentReports = [...reports].sort((a, b) => (b.completedOn ?? "").localeCompare(a.completedOn ?? "")).slice(0, 6);
|
|
517
581
|
const reportsSection = recentReports.length === 0 ? `<div class="empty">No reports yet.</div>` : `<table>
|
|
518
582
|
<thead><tr><th>Completed</th><th>Type</th><th>ID</th><th>GA users</th><th>Search</th><th>Report</th><th></th></tr></thead>
|
|
519
|
-
<tbody>${recentReports.map(reportRow).join("")}</tbody>
|
|
583
|
+
<tbody>${recentReports.map((r) => reportRow(r, site)).join("")}</tbody>
|
|
520
584
|
</table>`;
|
|
521
585
|
return `<!doctype html>
|
|
522
586
|
<html lang="en">
|
|
@@ -533,7 +597,7 @@ function renderSiteDashboardHtml(site, reports, submissions = [], spamTotals = n
|
|
|
533
597
|
<div class="meta"><a href="${escapeHtml(urlSafe)}">${escapeHtml(site.url)}</a></div>
|
|
534
598
|
${auditedLine}
|
|
535
599
|
${setupSection(site)}
|
|
536
|
-
${pendingSection(reports)}
|
|
600
|
+
${pendingSection(reports, site, now)}
|
|
537
601
|
|
|
538
602
|
<div class="section">
|
|
539
603
|
<h2>Lighthouse</h2>
|
|
@@ -561,8 +625,18 @@ function renderSiteDashboardHtml(site, reports, submissions = [], spamTotals = n
|
|
|
561
625
|
b.disabled = true;
|
|
562
626
|
try {
|
|
563
627
|
const res = await fetch(b.dataset.approveUrl, { method: "POST" });
|
|
564
|
-
|
|
565
|
-
|
|
628
|
+
if (res.ok) {
|
|
629
|
+
b.textContent = "Approved";
|
|
630
|
+
} else {
|
|
631
|
+
// A 409 carries { reason, blockers } \u2014 surface WHY instead of a bare
|
|
632
|
+
// "Failed" next to a possibly-stale green chip. textContent/title
|
|
633
|
+
// assignment only (no innerHTML), so server strings stay inert.
|
|
634
|
+
const data = await res.json().catch(() => null);
|
|
635
|
+
b.textContent = data && data.reason === "send-blocked" ? "Blocked" : "Failed";
|
|
636
|
+
if (data && Array.isArray(data.blockers)) b.title = data.blockers.join("
|
|
637
|
+
");
|
|
638
|
+
b.disabled = false;
|
|
639
|
+
}
|
|
566
640
|
} catch {
|
|
567
641
|
// Network rejection (offline, DNS, abort): mirror the !res.ok path so
|
|
568
642
|
// the button doesn't sit permanently disabled reading "Approve".
|
|
@@ -630,7 +704,7 @@ function renderSiteDashboardHtml(site, reports, submissions = [], spamTotals = n
|
|
|
630
704
|
});
|
|
631
705
|
if (!res.ok) throw new Error("bad status");
|
|
632
706
|
const data = await res.json();
|
|
633
|
-
if (approveBtn) approveBtn.disabled = !data.complete;
|
|
707
|
+
if (approveBtn) approveBtn.disabled = !data.complete || approveBtn.dataset.sendBlocked === "1";
|
|
634
708
|
} catch {
|
|
635
709
|
// Revert the optimistic flip so the box reflects the (unchanged) server state.
|
|
636
710
|
cb.checked = !cb.checked;
|
|
@@ -1103,7 +1177,9 @@ function renderInboxLane(model) {
|
|
|
1103
1177
|
const subs = model.submissions ?? [];
|
|
1104
1178
|
const spam = model.spam;
|
|
1105
1179
|
const hasSpam = !!spam && (spam.caught > 0 || spam.through > 0);
|
|
1106
|
-
|
|
1180
|
+
const autoFiltered = model.autoFiltered ?? 0;
|
|
1181
|
+
const hasAutoFiltered = autoFiltered > 0;
|
|
1182
|
+
if (subs.length === 0 && !hasSpam && !hasAutoFiltered) return "";
|
|
1107
1183
|
const shown = [...subs].sort((a, b) => (b.submittedAt ?? "").localeCompare(a.submittedAt ?? "")).slice(0, SUBMISSIONS_STRIP_CAP);
|
|
1108
1184
|
const rows = shown.map((sub) => {
|
|
1109
1185
|
const href = `/s/${escapeHtml(sub.slug)}`;
|
|
@@ -1120,10 +1196,12 @@ function renderInboxLane(model) {
|
|
|
1120
1196
|
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>`;
|
|
1121
1197
|
const spamLine = hasSpam ? `<div class="spam-rollup muted">\u{1F6E1} Spam (30d) \u2014 caught ${spam.caught} \xB7 through ${spam.through}</div>` : "";
|
|
1122
1198
|
const spamInSummary = hasSpam ? " \xB7 \u{1F6E1} Spam (30d)" : "";
|
|
1199
|
+
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>` : "";
|
|
1123
1200
|
return `<details class="inbox">
|
|
1124
1201
|
<summary>\u{1F4E5} Submissions (${subs.length} new)${spamInSummary}</summary>
|
|
1125
1202
|
${rows}${subs.length > 0 ? more : ""}
|
|
1126
1203
|
${spamLine}
|
|
1204
|
+
${autoFilteredLine}
|
|
1127
1205
|
</details>`;
|
|
1128
1206
|
}
|
|
1129
1207
|
var AUDIT_SCRIPT = `<script>
|