@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.
@@ -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("/api/voice/incident-timeline/actions/"+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
+ 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, { title: options.title })))(report);
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
- return new Response(JSON.stringify(result), {
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: result.ok ? 200 : 500
30551
+ status
30524
30552
  });
30525
30553
  });
30526
30554
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.433",
3
+ "version": "0.0.22-beta.435",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",