feat: update agent button on server page
This commit is contained in:
@@ -188,6 +188,7 @@ export default function ServerDetailPage() {
|
||||
const [confirmDelete, setConfirmDelete] = useState(false);
|
||||
const [showGenerateModal, setShowGenerateModal] = useState(false);
|
||||
const [copiedUpdate, setCopiedUpdate] = useState(false);
|
||||
const [updateSuccess, setUpdateSuccess] = useState(false);
|
||||
|
||||
const { data: server, isLoading, error } = useQuery({
|
||||
queryKey: ["servers", serverId],
|
||||
@@ -204,6 +205,20 @@ export default function ServerDetailPage() {
|
||||
},
|
||||
});
|
||||
|
||||
const { data: latestVersion } = useQuery({
|
||||
queryKey: ["agent-latest-version"],
|
||||
queryFn: () => api.getLatestAgentVersion(),
|
||||
staleTime: 5 * 60_000,
|
||||
});
|
||||
|
||||
const { mutate: triggerUpdate, isPending: isUpdating } = useMutation({
|
||||
mutationFn: () => api.updateAgent(serverId),
|
||||
onSuccess: () => {
|
||||
setUpdateSuccess(true);
|
||||
setTimeout(() => setUpdateSuccess(false), 4000);
|
||||
},
|
||||
});
|
||||
|
||||
const { mutate: deleteServer, isPending: isDeleting } = useMutation({
|
||||
mutationFn: () => api.deleteServer(serverId),
|
||||
onSuccess: () => {
|
||||
@@ -290,24 +305,50 @@ export default function ServerDetailPage() {
|
||||
<CardHeader>
|
||||
<CardTitle>Update Agent</CardTitle>
|
||||
</CardHeader>
|
||||
<p className="mb-4 text-sm text-text-secondary">
|
||||
Run this command on the server as <code className="rounded bg-surface-2 px-1 py-0.5 text-xs font-mono text-text-primary">root</code> to update the agent to the latest version:
|
||||
</p>
|
||||
<div className="relative rounded-lg border border-border bg-[#0a0c14] p-4 font-mono text-sm">
|
||||
<pre className="overflow-x-auto whitespace-pre-wrap break-all text-text-secondary leading-relaxed">
|
||||
<div className="mb-4 flex flex-wrap items-center gap-4 text-sm">
|
||||
<div>
|
||||
<span className="text-text-secondary">Installed: </span>
|
||||
<span className="font-mono font-medium text-text-primary">
|
||||
{server.agent_version ? `v${server.agent_version}` : "unknown"}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-text-secondary">Latest: </span>
|
||||
<span className="font-mono font-medium text-text-primary">
|
||||
{latestVersion ? `v${latestVersion.version}` : "—"}
|
||||
</span>
|
||||
</div>
|
||||
{latestVersion && server.agent_version && server.agent_version !== latestVersion.version && (
|
||||
<Badge variant="warning">update available</Badge>
|
||||
)}
|
||||
{latestVersion && server.agent_version && server.agent_version === latestVersion.version && (
|
||||
<Badge variant="success">up to date</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<Button
|
||||
variant="primary"
|
||||
loading={isUpdating}
|
||||
onClick={() => triggerUpdate()}
|
||||
disabled={server.status !== "active"}
|
||||
title={server.status !== "active" ? "Agent must be online to update" : undefined}
|
||||
>
|
||||
{updateSuccess ? "Update Sent!" : "Update Agent"}
|
||||
</Button>
|
||||
<div className="relative flex-1 min-w-64 rounded-lg border border-border bg-[#0a0c14] px-4 py-2.5 font-mono text-sm">
|
||||
<span className="text-accent">$</span>{" "}
|
||||
<span className="text-text-primary">{api.getUpdateCommand()}</span>
|
||||
</pre>
|
||||
<button
|
||||
onClick={async () => {
|
||||
await navigator.clipboard.writeText(api.getUpdateCommand());
|
||||
setCopiedUpdate(true);
|
||||
setTimeout(() => setCopiedUpdate(false), 2000);
|
||||
}}
|
||||
className="absolute right-3 top-3 rounded-md border border-border bg-surface-2 px-2.5 py-1 text-xs font-medium text-text-secondary transition-colors hover:border-accent/50 hover:text-text-primary"
|
||||
>
|
||||
{copiedUpdate ? <span className="text-success">Copied!</span> : "Copy"}
|
||||
</button>
|
||||
<button
|
||||
onClick={async () => {
|
||||
await navigator.clipboard.writeText(api.getUpdateCommand());
|
||||
setCopiedUpdate(true);
|
||||
setTimeout(() => setCopiedUpdate(false), 2000);
|
||||
}}
|
||||
className="absolute right-2 top-1.5 rounded-md border border-border bg-surface-2 px-2 py-0.5 text-xs font-medium text-text-secondary transition-colors hover:border-accent/50 hover:text-text-primary"
|
||||
>
|
||||
{copiedUpdate ? <span className="text-success">Copied!</span> : "Copy"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
@@ -326,6 +367,12 @@ export default function ServerDetailPage() {
|
||||
<dt className="text-text-secondary">OS</dt>
|
||||
<dd className="mt-0.5 text-text-primary">{server.os_info}</dd>
|
||||
</div>
|
||||
<div className="border-t border-border pt-3">
|
||||
<dt className="text-text-secondary">Agent Version</dt>
|
||||
<dd className="mt-0.5 font-mono text-text-primary">
|
||||
{server.agent_version ? `v${server.agent_version}` : "unknown"}
|
||||
</dd>
|
||||
</div>
|
||||
<div className="border-t border-border pt-3">
|
||||
<dt className="text-text-secondary">Last Seen</dt>
|
||||
<dd className="mt-0.5 text-text-primary">{server.last_seen ? formatDate(server.last_seen) : "Never"}</dd>
|
||||
|
||||
@@ -8,6 +8,7 @@ export interface Server {
|
||||
ip_address: string;
|
||||
os_info: string;
|
||||
status: ServerStatus;
|
||||
agent_version?: string;
|
||||
last_seen: string;
|
||||
created_at: string;
|
||||
}
|
||||
@@ -116,6 +117,16 @@ export const api = {
|
||||
return `curl -fsSL "${window.location.origin}/update" | bash`;
|
||||
},
|
||||
|
||||
getLatestAgentVersion(): Promise<{ version: string }> {
|
||||
return request<{ version: string }>("/agent/latest-version");
|
||||
},
|
||||
|
||||
updateAgent(serverId: string): Promise<{ message: string; version: string }> {
|
||||
return request<{ message: string; version: string }>(`/servers/${serverId}/update-agent`, {
|
||||
method: "POST",
|
||||
});
|
||||
},
|
||||
|
||||
// Keys
|
||||
listKeys(): Promise<Key[]> {
|
||||
return request<Key[]>("/keys");
|
||||
|
||||
Reference in New Issue
Block a user