Add Command Center v2: DQ/RAG integration, S3 browser, Jupyter, GPU matrix.
Mirror mo/atc-GPU layout with config/, docs/, scripts/ for Gitea deploy.
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
import { useState } from 'react'
|
||||
import { Check, ShieldAlert, X } from 'lucide-react'
|
||||
import type { Agent, Approval } from '../../types'
|
||||
import { getAgentMeta } from '../../lib/agentMeta'
|
||||
import { Button } from '../ui/Button'
|
||||
import { cn } from '../../lib/utils'
|
||||
|
||||
const ACTION_LABELS: Record<string, string> = {
|
||||
'docker.restart': 'Container restart',
|
||||
'docker.update': 'Image update',
|
||||
'generic.mutate': 'Infrastructure change',
|
||||
}
|
||||
|
||||
type Props = {
|
||||
approvals: Approval[]
|
||||
agents: Agent[]
|
||||
highlighted: boolean
|
||||
onDecide: (id: string, approved: boolean, decidedBy: string, note: string) => Promise<void>
|
||||
onDismissHighlight?: () => void
|
||||
}
|
||||
|
||||
export function ApprovalCards({ approvals, agents, highlighted, onDecide, onDismissHighlight }: Props) {
|
||||
const [busyId, setBusyId] = useState<string | null>(null)
|
||||
const [decider, setDecider] = useState<'mo-commander' | 'bart-commander'>('mo-commander')
|
||||
|
||||
if (!approvals.length) return null
|
||||
|
||||
const agentOf = (id: string) => agents.find((a) => a.id === id)
|
||||
|
||||
const handle = async (id: string, approved: boolean) => {
|
||||
setBusyId(id)
|
||||
try {
|
||||
await onDecide(id, approved, decider, '')
|
||||
} finally {
|
||||
setBusyId(null)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn('mx-2 mb-2 rounded-lg border bg-surface-overlay p-2.5', highlighted ? 'border-warning/50 ring-1 ring-warning/20' : 'border-border')}>
|
||||
<div className="mb-2 flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-1.5 text-[11px] font-semibold text-warning">
|
||||
<ShieldAlert className="h-3.5 w-3.5" />
|
||||
Pending approvals ({approvals.length})
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<label className="flex items-center gap-1 text-[9px] text-foreground-muted">
|
||||
As
|
||||
<select
|
||||
value={decider}
|
||||
onChange={(e) => setDecider(e.target.value as typeof decider)}
|
||||
className="rounded border border-border bg-surface px-1 py-0.5 text-[10px] text-foreground-muted"
|
||||
>
|
||||
<option value="mo-commander">Mo</option>
|
||||
<option value="bart-commander">Bart</option>
|
||||
</select>
|
||||
</label>
|
||||
{highlighted && onDismissHighlight && (
|
||||
<button type="button" onClick={onDismissHighlight} className="text-[9px] text-foreground-muted hover:text-foreground-muted">Dismiss</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-2 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{approvals.map((a) => {
|
||||
const ag = agentOf(a.agent_id)
|
||||
const meta = ag ? getAgentMeta(ag.id) : null
|
||||
const Icon = meta?.icon
|
||||
return (
|
||||
<div key={a.id} className="rounded-lg border border-border bg-surface p-2">
|
||||
<div className="mb-1.5 flex items-start gap-1.5">
|
||||
{Icon && (
|
||||
<span className="flex h-6 w-6 items-center justify-center rounded bg-surface-overlay text-docker">
|
||||
<Icon className="h-3.5 w-3.5" />
|
||||
</span>
|
||||
)}
|
||||
<div className="min-w-0 flex-1">
|
||||
<span className="text-[8px] uppercase tracking-wide text-warning">{ACTION_LABELS[a.action_type] || a.action_type}</span>
|
||||
<p className="truncate text-[11px] font-medium text-foreground">{ag?.name || a.agent_id}</p>
|
||||
</div>
|
||||
<span className="font-mono text-[8px] text-foreground-faint">#{a.id.slice(0, 6)}</span>
|
||||
</div>
|
||||
<p className="mb-1 text-[10px] text-foreground-muted line-clamp-2">{a.action}</p>
|
||||
{a.target && <p className="text-[9px] text-foreground-muted">Target: {a.target}</p>}
|
||||
<div className="mt-2 flex gap-1">
|
||||
<Button size="sm" variant="success" className="flex-1 text-[10px]" disabled={busyId === a.id} onClick={() => handle(a.id, true)}>
|
||||
<Check className="h-3 w-3" /> Approve
|
||||
</Button>
|
||||
<Button size="sm" variant="danger" className="flex-1 text-[10px]" disabled={busyId === a.id} onClick={() => handle(a.id, false)}>
|
||||
<X className="h-3 w-3" /> Deny
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user