pages
This commit is contained in:
1 parent
6e91d0b599
commit
34e40e8c0d
13 files changed
+399
-132
No files matched your search
@@ -0,0 +1,28 @@
|
||||
import type { Agent } from "@/api";
|
||||
|
||||
type Props = {
|
||||
agents: Agent[];
|
||||
selectedAgentId: string;
|
||||
onSelectAgent: (agentId: string) => void;
|
||||
};
|
||||
|
||||
export function AgentsPage({ agents, selectedAgentId, onSelectAgent }: Props) {
|
||||
return (
|
||||
<section className="card">
|
||||
<h2>Agents</h2>
|
||||
<div className="list">
|
||||
{agents.map((agent) => (
|
||||
<button
|
||||
key={agent.agent_id}
|
||||
className={`row ${selectedAgentId === agent.agent_id ? "selected" : ""}`}
|
||||
onClick={() => onSelectAgent(agent.agent_id)}
|
||||
>
|
||||
<span>{agent.agent_id}</span>
|
||||
<span>{agent.connected ? "connected" : "offline"}</span>
|
||||
</button>
|
||||
))}
|
||||
{!agents.length && <div className="empty">No agents enrolled</div>}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { Alert, AlertTransition } from "@/api";
|
||||
|
||||
type Props = {
|
||||
alerts: Alert[];
|
||||
transitions: AlertTransition[];
|
||||
onRefresh: () => Promise<void>;
|
||||
};
|
||||
|
||||
export function AlertsPage({ alerts, transitions, onRefresh }: Props) {
|
||||
return (
|
||||
<section className="two-col">
|
||||
<div className="card">
|
||||
<div className="row-head">
|
||||
<h2>Active Alerts</h2>
|
||||
<button onClick={() => void onRefresh()}>Refresh</button>
|
||||
</div>
|
||||
<div className="list">
|
||||
{alerts.map((alert) => (
|
||||
<div className="row plain" key={alert.alert_id}>
|
||||
<span>{alert.kind}{alert.agent_id ? ` (${alert.agent_id})` : ""}</span>
|
||||
<span>{alert.severity}</span>
|
||||
</div>
|
||||
))}
|
||||
{!alerts.length && <div className="empty">No active alerts</div>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card">
|
||||
<h2>Transition History</h2>
|
||||
<pre className="output small">{JSON.stringify(transitions, null, 2)}</pre>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { AuditEvent } from "@/api";
|
||||
|
||||
type Props = {
|
||||
events: AuditEvent[];
|
||||
onRefresh: () => Promise<void>;
|
||||
};
|
||||
|
||||
export function AuditPage({ events, onRefresh }: Props) {
|
||||
return (
|
||||
<section className="card span-full">
|
||||
<div className="row-head">
|
||||
<h2>Recent Audit Events</h2>
|
||||
<button onClick={() => void onRefresh()}>Refresh</button>
|
||||
</div>
|
||||
<pre className="output">{JSON.stringify(events, null, 2)}</pre>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { runCommand, type Agent, type CommandKind } from "@/api";
|
||||
|
||||
type Props = {
|
||||
agents: Agent[];
|
||||
selectedAgentId: string;
|
||||
onSelectAgent: (agentId: string) => void;
|
||||
onAfterCommand?: () => Promise<void>;
|
||||
};
|
||||
|
||||
export function CommandsPage({ agents, selectedAgentId, onSelectAgent, onAfterCommand }: Props) {
|
||||
const [kind, setKind] = useState<CommandKind>("devs");
|
||||
const [query, setQuery] = useState("");
|
||||
const [output, setOutput] = useState("Select agent and run a command");
|
||||
const [running, setRunning] = useState(false);
|
||||
|
||||
async function submit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
if (!selectedAgentId) return;
|
||||
setRunning(true);
|
||||
setOutput("Running...");
|
||||
try {
|
||||
const out = await runCommand(selectedAgentId, kind, query.trim());
|
||||
setOutput(JSON.stringify(out, null, 2));
|
||||
if (onAfterCommand) await onAfterCommand();
|
||||
} catch (err) {
|
||||
setOutput(String(err));
|
||||
} finally {
|
||||
setRunning(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="card">
|
||||
<h2>Command Runner</h2>
|
||||
<form className="form" onSubmit={submit}>
|
||||
<label>
|
||||
Agent
|
||||
<select value={selectedAgentId} onChange={(e) => onSelectAgent(e.target.value)} required>
|
||||
<option value="" disabled>Select agent</option>
|
||||
{agents.map((agent) => (
|
||||
<option key={agent.agent_id} value={agent.agent_id}>
|
||||
{agent.agent_id} ({agent.connected ? "connected" : "offline"})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Command
|
||||
<select value={kind} onChange={(e) => setKind(e.target.value as CommandKind)}>
|
||||
<option value="devs">devs</option>
|
||||
<option value="status">status</option>
|
||||
<option value="leases">leases</option>
|
||||
<option value="inventory">inventory</option>
|
||||
<option value="wake">wake</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Query
|
||||
<input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="optional" />
|
||||
</label>
|
||||
<button type="submit" disabled={running || !selectedAgentId}>{running ? "Running..." : "Run command"}</button>
|
||||
</form>
|
||||
<pre className="output">{output}</pre>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import type { Agent, Alert, AlertTransition } from "@/api";
|
||||
|
||||
type Props = {
|
||||
agents: Agent[];
|
||||
alerts: Alert[];
|
||||
transitions: AlertTransition[];
|
||||
loading: boolean;
|
||||
onRefresh: () => void;
|
||||
};
|
||||
|
||||
export function DashboardPage({ agents, alerts, transitions, loading, onRefresh }: Props) {
|
||||
const connected = agents.filter((a) => a.connected).length;
|
||||
return (
|
||||
<section className="card-grid">
|
||||
<div className="card stat"><h3>Agents</h3><strong>{agents.length}</strong></div>
|
||||
<div className="card stat"><h3>Connected</h3><strong>{connected}</strong></div>
|
||||
<div className="card stat"><h3>Active Alerts</h3><strong>{alerts.length}</strong></div>
|
||||
<div className="card stat"><h3>Transitions</h3><strong>{transitions.length}</strong></div>
|
||||
<div className="card span-full">
|
||||
<div className="row-head">
|
||||
<h2>Overview</h2>
|
||||
<button onClick={onRefresh} disabled={loading}>{loading ? "Refreshing..." : "Refresh"}</button>
|
||||
</div>
|
||||
<p className="muted">
|
||||
Use tabs to run commands, inspect audits, and monitor alerts in real time.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export function TokensPage() {
|
||||
return (
|
||||
<section className="card">
|
||||
<h2>Tokens</h2>
|
||||
<p className="muted">
|
||||
Token operations UI is the next increment. Backend endpoints are already available for issue/list/revoke.
|
||||
</p>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user