146 lines
5.2 KiB
TypeScript
146 lines
5.2 KiB
TypeScript
import { html, nothing } from "lit";
|
|
|
|
import { formatEventPayload } from "../presenter";
|
|
import type { EventLogEntry } from "../app-events";
|
|
|
|
export type DebugProps = {
|
|
loading: boolean;
|
|
status: Record<string, unknown> | null;
|
|
health: Record<string, unknown> | null;
|
|
models: unknown[];
|
|
heartbeat: unknown;
|
|
eventLog: EventLogEntry[];
|
|
callMethod: string;
|
|
callParams: string;
|
|
callResult: string | null;
|
|
callError: string | null;
|
|
onCallMethodChange: (next: string) => void;
|
|
onCallParamsChange: (next: string) => void;
|
|
onRefresh: () => void;
|
|
onCall: () => void;
|
|
};
|
|
|
|
export function renderDebug(props: DebugProps) {
|
|
const securityAudit =
|
|
props.status && typeof props.status === "object"
|
|
? (props.status as { securityAudit?: { summary?: Record<string, number> } }).securityAudit
|
|
: null;
|
|
const securitySummary = securityAudit?.summary ?? null;
|
|
const critical = securitySummary?.critical ?? 0;
|
|
const warn = securitySummary?.warn ?? 0;
|
|
const info = securitySummary?.info ?? 0;
|
|
const securityTone = critical > 0 ? "danger" : warn > 0 ? "warn" : "success";
|
|
const securityLabel =
|
|
critical > 0
|
|
? `${critical} critical`
|
|
: warn > 0
|
|
? `${warn} warnings`
|
|
: "No critical issues";
|
|
|
|
return html`
|
|
<section class="grid grid-cols-2">
|
|
<div class="card">
|
|
<div class="row" style="justify-content: space-between;">
|
|
<div>
|
|
<div class="card-title">Snapshots</div>
|
|
<div class="card-sub">Status, health, and heartbeat data.</div>
|
|
</div>
|
|
<button class="btn" ?disabled=${props.loading} @click=${props.onRefresh}>
|
|
${props.loading ? "Refreshing…" : "Refresh"}
|
|
</button>
|
|
</div>
|
|
<div class="stack" style="margin-top: 12px;">
|
|
<div>
|
|
<div class="muted">Status</div>
|
|
${securitySummary
|
|
? html`<div class="callout ${securityTone}" style="margin-top: 8px;">
|
|
Security audit: ${securityLabel}${info > 0 ? ` · ${info} info` : ""}. Run
|
|
<span class="mono">moltbot security audit --deep</span> for details.
|
|
</div>`
|
|
: nothing}
|
|
<pre class="code-block">${JSON.stringify(props.status ?? {}, null, 2)}</pre>
|
|
</div>
|
|
<div>
|
|
<div class="muted">Health</div>
|
|
<pre class="code-block">${JSON.stringify(props.health ?? {}, null, 2)}</pre>
|
|
</div>
|
|
<div>
|
|
<div class="muted">Last heartbeat</div>
|
|
<pre class="code-block">${JSON.stringify(props.heartbeat ?? {}, null, 2)}</pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-title">Manual RPC</div>
|
|
<div class="card-sub">Send a raw gateway method with JSON params.</div>
|
|
<div class="form-grid" style="margin-top: 16px;">
|
|
<label class="field">
|
|
<span>Method</span>
|
|
<input
|
|
.value=${props.callMethod}
|
|
@input=${(e: Event) =>
|
|
props.onCallMethodChange((e.target as HTMLInputElement).value)}
|
|
placeholder="system-presence"
|
|
/>
|
|
</label>
|
|
<label class="field">
|
|
<span>Params (JSON)</span>
|
|
<textarea
|
|
.value=${props.callParams}
|
|
@input=${(e: Event) =>
|
|
props.onCallParamsChange((e.target as HTMLTextAreaElement).value)}
|
|
rows="6"
|
|
></textarea>
|
|
</label>
|
|
</div>
|
|
<div class="row" style="margin-top: 12px;">
|
|
<button class="btn primary" @click=${props.onCall}>Call</button>
|
|
</div>
|
|
${props.callError
|
|
? html`<div class="callout danger" style="margin-top: 12px;">
|
|
${props.callError}
|
|
</div>`
|
|
: nothing}
|
|
${props.callResult
|
|
? html`<pre class="code-block" style="margin-top: 12px;">${props.callResult}</pre>`
|
|
: nothing}
|
|
</div>
|
|
</section>
|
|
|
|
<section class="card" style="margin-top: 18px;">
|
|
<div class="card-title">Models</div>
|
|
<div class="card-sub">Catalog from models.list.</div>
|
|
<pre class="code-block" style="margin-top: 12px;">${JSON.stringify(
|
|
props.models ?? [],
|
|
null,
|
|
2,
|
|
)}</pre>
|
|
</section>
|
|
|
|
<section class="card" style="margin-top: 18px;">
|
|
<div class="card-title">Event Log</div>
|
|
<div class="card-sub">Latest gateway events.</div>
|
|
${props.eventLog.length === 0
|
|
? html`<div class="muted" style="margin-top: 12px;">No events yet.</div>`
|
|
: html`
|
|
<div class="list" style="margin-top: 12px;">
|
|
${props.eventLog.map(
|
|
(evt) => html`
|
|
<div class="list-item">
|
|
<div class="list-main">
|
|
<div class="list-title">${evt.event}</div>
|
|
<div class="list-sub">${new Date(evt.ts).toLocaleTimeString()}</div>
|
|
</div>
|
|
<div class="list-meta">
|
|
<pre class="code-block">${formatEventPayload(evt.payload)}</pre>
|
|
</div>
|
|
</div>
|
|
`,
|
|
)}
|
|
</div>
|
|
`}
|
|
</section>
|
|
`;
|
|
}
|