@absolutejs/voice 0.0.22-beta.433 → 0.0.22-beta.435
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/incidentTimeline.d.ts +7 -0
- package/dist/index.js +32 -4
- package/package.json +1 -1
|
@@ -2,6 +2,8 @@ import { Elysia } from 'elysia';
|
|
|
2
2
|
import type { VoiceFailureReplayReport, VoiceOperationsRecord } from './operationsRecord';
|
|
3
3
|
import type { VoiceOperationalStatusReport } from './operationalStatus';
|
|
4
4
|
import type { VoiceOpsRecoveryReport } from './opsRecovery';
|
|
5
|
+
import type { VoiceAuditEventStore } from './audit';
|
|
6
|
+
import type { VoiceTraceEventStore } from './trace';
|
|
5
7
|
import type { VoiceMonitorIssue } from './voiceMonitoring';
|
|
6
8
|
export type VoiceIncidentTimelineStatus = 'fail' | 'pass' | 'warn';
|
|
7
9
|
export type VoiceIncidentTimelineSeverity = 'critical' | 'info' | 'warn';
|
|
@@ -22,6 +24,8 @@ export type VoiceIncidentRecoveryAction = {
|
|
|
22
24
|
};
|
|
23
25
|
export type VoiceIncidentRecoveryActionResult = {
|
|
24
26
|
actionId: string;
|
|
27
|
+
afterStatus?: VoiceIncidentTimelineStatus;
|
|
28
|
+
beforeStatus?: VoiceIncidentTimelineStatus;
|
|
25
29
|
detail?: string;
|
|
26
30
|
href?: string;
|
|
27
31
|
ok: boolean;
|
|
@@ -91,6 +95,7 @@ export type VoiceIncidentTimelineOptions = {
|
|
|
91
95
|
export type VoiceIncidentTimelineRoutesOptions = VoiceIncidentTimelineOptions & {
|
|
92
96
|
actionHandlers?: Record<string, VoiceIncidentRecoveryActionHandler>;
|
|
93
97
|
actionPath?: false | string;
|
|
98
|
+
audit?: VoiceAuditEventStore;
|
|
94
99
|
headers?: HeadersInit;
|
|
95
100
|
htmlPath?: false | string;
|
|
96
101
|
markdownPath?: false | string;
|
|
@@ -98,12 +103,14 @@ export type VoiceIncidentTimelineRoutesOptions = VoiceIncidentTimelineOptions &
|
|
|
98
103
|
path?: string;
|
|
99
104
|
render?: (report: VoiceIncidentTimelineReport) => string | Promise<string>;
|
|
100
105
|
title?: string;
|
|
106
|
+
trace?: VoiceTraceEventStore;
|
|
101
107
|
};
|
|
102
108
|
export declare const buildVoiceIncidentTimelineReport: (options: VoiceIncidentTimelineOptions) => Promise<VoiceIncidentTimelineReport>;
|
|
103
109
|
export declare const renderVoiceIncidentTimelineMarkdown: (report: VoiceIncidentTimelineReport, options?: {
|
|
104
110
|
title?: string;
|
|
105
111
|
}) => string;
|
|
106
112
|
export declare const renderVoiceIncidentTimelineHTML: (report: VoiceIncidentTimelineReport, options?: {
|
|
113
|
+
actionPath?: string;
|
|
107
114
|
title?: string;
|
|
108
115
|
}) => string;
|
|
109
116
|
export declare const createVoiceIncidentTimelineRoutes: (options: VoiceIncidentTimelineRoutesOptions) => Elysia<"", {
|
package/dist/index.js
CHANGED
|
@@ -30406,6 +30406,7 @@ ${report.actions.map((action) => `- ${action.method ?? "GET"} ${action.id}: ${ac
|
|
|
30406
30406
|
};
|
|
30407
30407
|
var renderVoiceIncidentTimelineHTML = (report, options = {}) => {
|
|
30408
30408
|
const title = options.title ?? "AbsoluteJS Voice Incident Timeline";
|
|
30409
|
+
const actionPath = options.actionPath ?? "/api/voice/incident-timeline/actions";
|
|
30409
30410
|
const events = report.events.map((event) => `<article class="${escapeHtml43(event.severity)}">
|
|
30410
30411
|
<span>${escapeHtml43(event.severity.toUpperCase())} / ${escapeHtml43(event.category)}</span>
|
|
30411
30412
|
<h2>${escapeHtml43(event.label)}</h2>
|
|
@@ -30421,7 +30422,7 @@ var renderVoiceIncidentTimelineHTML = (report, options = {}) => {
|
|
|
30421
30422
|
const control = action.method === "POST" ? `<button type="button" data-voice-incident-action="${escapeHtml43(action.id)}" ${action.disabled ? "disabled" : ""}>${label}</button>` : href;
|
|
30422
30423
|
return `<article class="action"><span>${escapeHtml43(action.method ?? "GET")}</span><h2>${label}</h2>${detail}<div>${control}${href && action.method === "POST" ? href : ""}</div></article>`;
|
|
30423
30424
|
}).join("");
|
|
30424
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml43(title)}</title><style>body{background:#11110d;color:#faf4df;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1100px;padding:32px}.hero{background:linear-gradient(135deg,rgba(248,113,113,.2),rgba(245,158,11,.13),rgba(34,197,94,.12));border:1px solid #39301d;border-radius:30px;margin-bottom:18px;padding:28px}.eyebrow{color:#fcd34d;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}.status{border:1px solid #575030;border-radius:999px;display:inline-flex;font-weight:900;padding:8px 12px}.status.pass{border-color:rgba(34,197,94,.65)}.status.warn{border-color:rgba(245,158,11,.75)}.status.fail{border-color:rgba(239,68,68,.85)}.grid{display:grid;gap:14px}.actions{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));margin:0 0 18px}.summary{display:flex;flex-wrap:wrap;gap:10px}.summary span{background:#181711;border:1px solid #39301d;border-radius:999px;padding:8px 12px}article{background:#181711;border:1px solid #39301d;border-radius:22px;padding:18px}article.critical{border-color:rgba(239,68,68,.85)}article.warn{border-color:rgba(245,158,11,.75)}article.info{border-color:rgba(34,197,94,.55)}article.action{border-color:#5b4a22}article span{color:#fcd34d;font-size:.78rem;font-weight:900;letter-spacing:.08em}article h2{margin:.35rem 0}.muted,article p{color:#cfc5a8}article strong{display:block;font-size:1.3rem;margin:.5rem 0}a{color:#fde68a;margin-right:12px}button{background:#fcd34d;border:0;border-radius:999px;color:#171307;cursor:pointer;font-weight:900;padding:10px 14px}button:disabled{cursor:not-allowed;opacity:.55}</style></head><body><main><section class="hero"><p class="eyebrow">Operational triage</p><h1>${escapeHtml43(title)}</h1><p class="status ${escapeHtml43(report.status)}">Overall: ${escapeHtml43(report.status.toUpperCase())}</p><p class="muted">Generated ${escapeHtml43(new Date(report.generatedAt).toLocaleString())}</p><div class="summary"><span>${String(report.summary.critical)} critical</span><span>${String(report.summary.warn)} warn</span><span>${String(report.summary.info)} info</span><span>${String(report.summary.total)} total</span></div></section><h2>Recovery actions</h2><section class="actions">${actions || '<article class="action"><span>NONE</span><h2>No recovery actions</h2><p>No executable actions are available for this report.</p></article>'}</section><h2>Timeline</h2><section class="grid">${events || '<article class="info"><span>INFO</span><h2>No incident events</h2><p>No non-pass operational events were found in this window.</p></article>'}</section></main><script>document.querySelectorAll("[data-voice-incident-action]").forEach((button)=>{button.addEventListener("click",async()=>{const id=button.getAttribute("data-voice-incident-action");if(!id)return;button.disabled=true;const original=button.textContent;button.textContent="Running...";try{const response=await fetch("/
|
|
30425
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml43(title)}</title><style>body{background:#11110d;color:#faf4df;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1100px;padding:32px}.hero{background:linear-gradient(135deg,rgba(248,113,113,.2),rgba(245,158,11,.13),rgba(34,197,94,.12));border:1px solid #39301d;border-radius:30px;margin-bottom:18px;padding:28px}.eyebrow{color:#fcd34d;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}.status{border:1px solid #575030;border-radius:999px;display:inline-flex;font-weight:900;padding:8px 12px}.status.pass{border-color:rgba(34,197,94,.65)}.status.warn{border-color:rgba(245,158,11,.75)}.status.fail{border-color:rgba(239,68,68,.85)}.grid{display:grid;gap:14px}.actions{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));margin:0 0 18px}.summary{display:flex;flex-wrap:wrap;gap:10px}.summary span{background:#181711;border:1px solid #39301d;border-radius:999px;padding:8px 12px}article{background:#181711;border:1px solid #39301d;border-radius:22px;padding:18px}article.critical{border-color:rgba(239,68,68,.85)}article.warn{border-color:rgba(245,158,11,.75)}article.info{border-color:rgba(34,197,94,.55)}article.action{border-color:#5b4a22}article span{color:#fcd34d;font-size:.78rem;font-weight:900;letter-spacing:.08em}article h2{margin:.35rem 0}.muted,article p{color:#cfc5a8}article strong{display:block;font-size:1.3rem;margin:.5rem 0}a{color:#fde68a;margin-right:12px}button{background:#fcd34d;border:0;border-radius:999px;color:#171307;cursor:pointer;font-weight:900;padding:10px 14px}button:disabled{cursor:not-allowed;opacity:.55}</style></head><body><main><section class="hero"><p class="eyebrow">Operational triage</p><h1>${escapeHtml43(title)}</h1><p class="status ${escapeHtml43(report.status)}">Overall: ${escapeHtml43(report.status.toUpperCase())}</p><p class="muted">Generated ${escapeHtml43(new Date(report.generatedAt).toLocaleString())}</p><div class="summary"><span>${String(report.summary.critical)} critical</span><span>${String(report.summary.warn)} warn</span><span>${String(report.summary.info)} info</span><span>${String(report.summary.total)} total</span></div></section><h2>Recovery actions</h2><section class="actions">${actions || '<article class="action"><span>NONE</span><h2>No recovery actions</h2><p>No executable actions are available for this report.</p></article>'}</section><h2>Timeline</h2><section class="grid">${events || '<article class="info"><span>INFO</span><h2>No incident events</h2><p>No non-pass operational events were found in this window.</p></article>'}</section></main><script>const voiceIncidentActionPath=${JSON.stringify(actionPath)};document.querySelectorAll("[data-voice-incident-action]").forEach((button)=>{button.addEventListener("click",async()=>{const id=button.getAttribute("data-voice-incident-action");if(!id)return;button.disabled=true;const original=button.textContent;button.textContent="Running...";try{const response=await fetch(voiceIncidentActionPath+"/"+encodeURIComponent(id),{method:"POST"});button.textContent=response.ok?"Done":"Failed";if(response.ok)setTimeout(()=>location.reload(),700)}catch{button.textContent="Failed"}finally{setTimeout(()=>{button.disabled=false;button.textContent=original},1600)}})});</script></body></html>`;
|
|
30425
30426
|
};
|
|
30426
30427
|
var createVoiceIncidentTimelineRoutes = (options) => {
|
|
30427
30428
|
const path = options.path ?? "/api/voice/incident-timeline";
|
|
@@ -30443,7 +30444,10 @@ var createVoiceIncidentTimelineRoutes = (options) => {
|
|
|
30443
30444
|
if (htmlPath !== false) {
|
|
30444
30445
|
routes.get(htmlPath, async () => {
|
|
30445
30446
|
const report = await buildVoiceIncidentTimelineReport(options);
|
|
30446
|
-
const body = await (options.render ?? ((input) => renderVoiceIncidentTimelineHTML(input, {
|
|
30447
|
+
const body = await (options.render ?? ((input) => renderVoiceIncidentTimelineHTML(input, {
|
|
30448
|
+
actionPath: actionPath === false ? undefined : actionPath,
|
|
30449
|
+
title: options.title
|
|
30450
|
+
})))(report);
|
|
30447
30451
|
return new Response(body, {
|
|
30448
30452
|
headers: {
|
|
30449
30453
|
"Content-Type": "text/html; charset=utf-8",
|
|
@@ -30515,12 +30519,36 @@ var createVoiceIncidentTimelineRoutes = (options) => {
|
|
|
30515
30519
|
report,
|
|
30516
30520
|
request
|
|
30517
30521
|
});
|
|
30518
|
-
|
|
30522
|
+
const status = result.ok ? 200 : 500;
|
|
30523
|
+
const afterReport = await buildVoiceIncidentTimelineReport(options);
|
|
30524
|
+
const resultWithStatus = {
|
|
30525
|
+
...result,
|
|
30526
|
+
afterStatus: result.afterStatus ?? afterReport.status,
|
|
30527
|
+
beforeStatus: result.beforeStatus ?? report.status
|
|
30528
|
+
};
|
|
30529
|
+
await recordVoiceOpsActionAudit({
|
|
30530
|
+
actionId: `incident.${actionId}`,
|
|
30531
|
+
body: {
|
|
30532
|
+
action,
|
|
30533
|
+
afterStatus: resultWithStatus.afterStatus,
|
|
30534
|
+
beforeStatus: resultWithStatus.beforeStatus,
|
|
30535
|
+
eventIds: report.events.map((event) => event.id),
|
|
30536
|
+
result
|
|
30537
|
+
},
|
|
30538
|
+
error: result.ok ? undefined : result.detail ?? result.status,
|
|
30539
|
+
ok: result.ok,
|
|
30540
|
+
ranAt: Date.now(),
|
|
30541
|
+
status
|
|
30542
|
+
}, {
|
|
30543
|
+
audit: options.audit,
|
|
30544
|
+
trace: options.trace
|
|
30545
|
+
});
|
|
30546
|
+
return new Response(JSON.stringify(resultWithStatus), {
|
|
30519
30547
|
headers: {
|
|
30520
30548
|
"Content-Type": "application/json; charset=utf-8",
|
|
30521
30549
|
...options.headers
|
|
30522
30550
|
},
|
|
30523
|
-
status
|
|
30551
|
+
status
|
|
30524
30552
|
});
|
|
30525
30553
|
});
|
|
30526
30554
|
}
|