nickname
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
||||
fetchAlerts,
|
||||
fetchAudit,
|
||||
revokeAgent,
|
||||
setAgentNickname,
|
||||
} from "@/api";
|
||||
import { AppLayout } from "@/layout/AppLayout";
|
||||
import { AgentsPage } from "@/pages/AgentsPage";
|
||||
@@ -73,6 +74,15 @@ export function App() {
|
||||
return result.revoked;
|
||||
}
|
||||
|
||||
async function onSetAgentNickname(
|
||||
agentId: string,
|
||||
nickname: string | null,
|
||||
): Promise<boolean> {
|
||||
const result = await setAgentNickname(agentId, nickname);
|
||||
await loadAll();
|
||||
return result.updated;
|
||||
}
|
||||
|
||||
async function refreshAlertsAndHistory() {
|
||||
const [nextAlerts, nextHistory] = await Promise.all([
|
||||
fetchAlerts(),
|
||||
@@ -149,6 +159,7 @@ export function App() {
|
||||
selectedAgentId={selectedAgentId}
|
||||
onSelectAgent={setSelectedAgentId}
|
||||
onRevokeAgent={onRevokeAgent}
|
||||
onSetAgentNickname={onSetAgentNickname}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
+25
-1
@@ -1,4 +1,8 @@
|
||||
export type Agent = { agent_id: string; connected: boolean };
|
||||
export type Agent = {
|
||||
agent_id: string;
|
||||
connected: boolean;
|
||||
nickname?: string | null;
|
||||
};
|
||||
|
||||
export type Alert = {
|
||||
alert_id: string;
|
||||
@@ -62,6 +66,12 @@ export type RevokeAgentResponse = {
|
||||
revoked: boolean;
|
||||
};
|
||||
|
||||
export type SetAgentNicknameResponse = {
|
||||
agent_id: string;
|
||||
nickname: string | null;
|
||||
updated: boolean;
|
||||
};
|
||||
|
||||
async function request<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
const res = await fetch(path, {
|
||||
...init,
|
||||
@@ -134,6 +144,20 @@ export function revokeAgent(agentId: string): Promise<RevokeAgentResponse> {
|
||||
);
|
||||
}
|
||||
|
||||
export function setAgentNickname(
|
||||
agentId: string,
|
||||
nickname: string | null,
|
||||
): Promise<SetAgentNicknameResponse> {
|
||||
const normalized = nickname?.trim() ?? "";
|
||||
return request<SetAgentNicknameResponse>(
|
||||
`/api/v1/control/agents/${encodeURIComponent(agentId)}/nickname`,
|
||||
{
|
||||
method: "PATCH",
|
||||
body: JSON.stringify({ nickname: normalized ? normalized : null }),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function runCommand(
|
||||
agentId: string,
|
||||
kind: CommandKind,
|
||||
|
||||
+65
-11
@@ -1,6 +1,7 @@
|
||||
import type { Agent } from "@/api";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { useState } from "react";
|
||||
|
||||
type Props = {
|
||||
@@ -8,6 +9,10 @@ type Props = {
|
||||
selectedAgentId: string;
|
||||
onSelectAgent: (agentId: string) => void;
|
||||
onRevokeAgent: (agentId: string) => Promise<boolean>;
|
||||
onSetAgentNickname: (
|
||||
agentId: string,
|
||||
nickname: string | null,
|
||||
) => Promise<boolean>;
|
||||
};
|
||||
|
||||
export function AgentsPage({
|
||||
@@ -15,10 +20,17 @@ export function AgentsPage({
|
||||
selectedAgentId,
|
||||
onSelectAgent,
|
||||
onRevokeAgent,
|
||||
onSetAgentNickname,
|
||||
}: Props) {
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [status, setStatus] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
const [drafts, setDrafts] = useState<Record<string, string>>({});
|
||||
|
||||
function displayLabel(agent: Agent): string {
|
||||
const nickname = agent.nickname?.trim();
|
||||
return nickname ? nickname : agent.agent_id;
|
||||
}
|
||||
|
||||
async function onRevoke(agentId: string) {
|
||||
if (!window.confirm(`Revoke agent credentials for ${agentId}?`)) {
|
||||
@@ -39,6 +51,25 @@ export function AgentsPage({
|
||||
}
|
||||
}
|
||||
|
||||
async function onSaveNickname(agent: Agent) {
|
||||
const draft = drafts[agent.agent_id] ?? agent.nickname ?? "";
|
||||
setBusy(true);
|
||||
setStatus("");
|
||||
setError("");
|
||||
try {
|
||||
const updated = await onSetAgentNickname(agent.agent_id, draft);
|
||||
setStatus(
|
||||
updated
|
||||
? `Updated nickname for ${agent.agent_id}`
|
||||
: `${agent.agent_id} not found`,
|
||||
);
|
||||
} catch (err) {
|
||||
setError(String(err));
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
@@ -55,24 +86,47 @@ export function AgentsPage({
|
||||
variant={
|
||||
selectedAgentId === agent.agent_id ? "secondary" : "outline"
|
||||
}
|
||||
className="flex h-auto flex-1 items-center justify-between px-3 py-2 text-left"
|
||||
className="flex h-auto flex-1 items-center justify-between gap-3 px-3 py-2 text-left"
|
||||
onClick={() => onSelectAgent(agent.agent_id)}
|
||||
disabled={busy}
|
||||
>
|
||||
<span>{agent.agent_id}</span>
|
||||
<span className="min-w-0 truncate">{displayLabel(agent)}</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{agent.connected ? "connected" : "offline"}
|
||||
</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="shrink-0 border-destructive/50 text-destructive hover:bg-destructive/10"
|
||||
onClick={() => void onRevoke(agent.agent_id)}
|
||||
disabled={busy}
|
||||
>
|
||||
Revoke
|
||||
</Button>
|
||||
<div className="grid min-w-52 gap-2">
|
||||
<Input
|
||||
value={drafts[agent.agent_id] ?? agent.nickname ?? ""}
|
||||
onChange={(e) =>
|
||||
setDrafts((prev) => ({
|
||||
...prev,
|
||||
[agent.agent_id]: e.target.value,
|
||||
}))
|
||||
}
|
||||
placeholder="nickname (optional)"
|
||||
disabled={busy}
|
||||
/>
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => void onSaveNickname(agent)}
|
||||
disabled={busy}
|
||||
>
|
||||
Save Name
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="shrink-0 border-destructive/50 text-destructive hover:bg-destructive/10"
|
||||
onClick={() => void onRevoke(agent.agent_id)}
|
||||
disabled={busy}
|
||||
>
|
||||
Revoke
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{!agents.length && (
|
||||
|
||||
@@ -26,6 +26,11 @@ export function CommandsPage({
|
||||
onSelectAgent,
|
||||
onAfterCommand,
|
||||
}: Props) {
|
||||
function displayAgentLabel(agent: Agent): string {
|
||||
const nickname = agent.nickname?.trim();
|
||||
return nickname ? nickname : agent.agent_id;
|
||||
}
|
||||
|
||||
const [kind, setKind] = useState<CommandKind>("devs");
|
||||
const [query, setQuery] = useState("");
|
||||
const [output, setOutput] = useState("Select agent and run a command");
|
||||
@@ -77,7 +82,9 @@ export function CommandsPage({
|
||||
className={`size-2 shrink-0 rounded-full ${agent.connected ? "bg-emerald-500" : "bg-zinc-400"}`}
|
||||
aria-hidden
|
||||
/>
|
||||
<span className="min-w-0 truncate">{agent.agent_id}</span>
|
||||
<span className="min-w-0 truncate">
|
||||
{displayAgentLabel(agent)}
|
||||
</span>
|
||||
<span className="shrink-0 text-xs text-muted-foreground">
|
||||
{agent.connected ? "connected" : "offline"}
|
||||
</span>
|
||||
|
||||
@@ -44,6 +44,11 @@ export function DevicesPage({
|
||||
onSelectAgent,
|
||||
onAfterWake,
|
||||
}: Props) {
|
||||
function displayAgentLabel(agent: Agent): string {
|
||||
const nickname = agent.nickname?.trim();
|
||||
return nickname ? nickname : agent.agent_id;
|
||||
}
|
||||
|
||||
const selectedAgent = agents.find(
|
||||
(agent) => agent.agent_id === selectedAgentId,
|
||||
);
|
||||
@@ -351,7 +356,9 @@ export function DevicesPage({
|
||||
) : null}
|
||||
<span className="min-w-0 truncate">
|
||||
{selectedAgentId
|
||||
? selectedAgentId
|
||||
? selectedAgent
|
||||
? displayAgentLabel(selectedAgent)
|
||||
: selectedAgentId
|
||||
: "Select connected agent"}
|
||||
</span>
|
||||
{selectedAgent ? (
|
||||
@@ -378,7 +385,7 @@ export function DevicesPage({
|
||||
aria-hidden
|
||||
/>
|
||||
<span className="min-w-0 truncate">
|
||||
{agent.agent_id}
|
||||
{displayAgentLabel(agent)}
|
||||
</span>
|
||||
<span className="shrink-0 text-xs text-muted-foreground">
|
||||
{agent.connected ? "connected" : "offline"}
|
||||
|
||||
Reference in New Issue
Block a user