openclaw-vainplex/ui/src/ui/views/exec-approval.ts
2026-02-02 15:23:36 +09:00

89 lines
2.9 KiB
TypeScript

import { html, nothing } from "lit";
import type { AppViewState } from "../app-view-state";
function formatRemaining(ms: number): string {
const remaining = Math.max(0, ms);
const totalSeconds = Math.floor(remaining / 1000);
if (totalSeconds < 60) {
return `${totalSeconds}s`;
}
const minutes = Math.floor(totalSeconds / 60);
if (minutes < 60) {
return `${minutes}m`;
}
const hours = Math.floor(minutes / 60);
return `${hours}h`;
}
function renderMetaRow(label: string, value?: string | null) {
if (!value) {
return nothing;
}
return html`<div class="exec-approval-meta-row"><span>${label}</span><span>${value}</span></div>`;
}
export function renderExecApprovalPrompt(state: AppViewState) {
const active = state.execApprovalQueue[0];
if (!active) {
return nothing;
}
const request = active.request;
const remainingMs = active.expiresAtMs - Date.now();
const remaining = remainingMs > 0 ? `expires in ${formatRemaining(remainingMs)}` : "expired";
const queueCount = state.execApprovalQueue.length;
return html`
<div class="exec-approval-overlay" role="dialog" aria-live="polite">
<div class="exec-approval-card">
<div class="exec-approval-header">
<div>
<div class="exec-approval-title">Exec approval needed</div>
<div class="exec-approval-sub">${remaining}</div>
</div>
${
queueCount > 1
? html`<div class="exec-approval-queue">${queueCount} pending</div>`
: nothing
}
</div>
<div class="exec-approval-command mono">${request.command}</div>
<div class="exec-approval-meta">
${renderMetaRow("Host", request.host)}
${renderMetaRow("Agent", request.agentId)}
${renderMetaRow("Session", request.sessionKey)}
${renderMetaRow("CWD", request.cwd)}
${renderMetaRow("Resolved", request.resolvedPath)}
${renderMetaRow("Security", request.security)}
${renderMetaRow("Ask", request.ask)}
</div>
${
state.execApprovalError
? html`<div class="exec-approval-error">${state.execApprovalError}</div>`
: nothing
}
<div class="exec-approval-actions">
<button
class="btn primary"
?disabled=${state.execApprovalBusy}
@click=${() => state.handleExecApprovalDecision("allow-once")}
>
Allow once
</button>
<button
class="btn"
?disabled=${state.execApprovalBusy}
@click=${() => state.handleExecApprovalDecision("allow-always")}
>
Always allow
</button>
<button
class="btn danger"
?disabled=${state.execApprovalBusy}
@click=${() => state.handleExecApprovalDecision("deny")}
>
Deny
</button>
</div>
</div>
</div>
`;
}