c1adb94abb
CI has been running `prettier --check` against a tree that was never formatted, so the check reported 63 files and failed every run. Nothing here is a behaviour change: `pnpm lint` and the four typecheck builds pass exactly as before. conduit-bringup-artifacts is added to .prettierignore instead. Those files are captured bring-up reports, not maintained sources; reflowing them would only churn a record of what happened.
123 lines
3.5 KiB
TypeScript
123 lines
3.5 KiB
TypeScript
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
import { Play, Square, RotateCcw, Skull } from 'lucide-react';
|
|
import { api } from '@/lib/api';
|
|
import { Button } from '@/components/ui/button';
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
DialogClose,
|
|
} from '@/components/ui/dialog';
|
|
|
|
interface PowerControlsProps {
|
|
serverId: string;
|
|
orgId: string;
|
|
status: string;
|
|
}
|
|
|
|
type PowerAction = 'start' | 'stop' | 'restart' | 'kill';
|
|
|
|
interface CachedServerDetail {
|
|
status: string;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
export function PowerControls({ serverId, orgId, status }: PowerControlsProps) {
|
|
const queryClient = useQueryClient();
|
|
const serverQueryKey = ['server', orgId, serverId] as const;
|
|
|
|
const powerMutation = useMutation({
|
|
mutationFn: (action: PowerAction) =>
|
|
api.post(`/organizations/${orgId}/servers/${serverId}/power`, { action }),
|
|
onMutate: (action) => {
|
|
const nextStatusByAction: Record<PowerAction, string> = {
|
|
start: 'starting',
|
|
stop: 'stopping',
|
|
restart: 'stopping',
|
|
kill: 'stopped',
|
|
};
|
|
|
|
queryClient.setQueryData<CachedServerDetail | undefined>(serverQueryKey, (current) => {
|
|
if (!current) return current;
|
|
return {
|
|
...current,
|
|
status: nextStatusByAction[action],
|
|
};
|
|
});
|
|
},
|
|
onSettled: () => {
|
|
queryClient.invalidateQueries({ queryKey: serverQueryKey });
|
|
queryClient.invalidateQueries({ queryKey: ['servers', orgId] });
|
|
},
|
|
});
|
|
|
|
const isRunning = status === 'running';
|
|
const isStopped = status === 'stopped' || status === 'error';
|
|
const isTransitioning = status === 'starting' || status === 'stopping' || status === 'installing';
|
|
|
|
return (
|
|
<div className="flex items-center gap-2">
|
|
<Button
|
|
size="sm"
|
|
onClick={() => powerMutation.mutate('start')}
|
|
disabled={!isStopped || powerMutation.isPending}
|
|
className="bg-green-600 hover:bg-green-700"
|
|
>
|
|
<Play className="h-4 w-4" />
|
|
Start
|
|
</Button>
|
|
|
|
<Button
|
|
size="sm"
|
|
variant="secondary"
|
|
onClick={() => powerMutation.mutate('restart')}
|
|
disabled={!isRunning || powerMutation.isPending}
|
|
>
|
|
<RotateCcw className="h-4 w-4" />
|
|
Restart
|
|
</Button>
|
|
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
onClick={() => powerMutation.mutate('stop')}
|
|
disabled={!isRunning || powerMutation.isPending}
|
|
>
|
|
<Square className="h-4 w-4" />
|
|
Stop
|
|
</Button>
|
|
|
|
<Dialog>
|
|
<DialogTrigger asChild>
|
|
<Button size="sm" variant="destructive" disabled={isTransitioning && !isRunning}>
|
|
<Skull className="h-4 w-4" />
|
|
Kill
|
|
</Button>
|
|
</DialogTrigger>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>Kill Server</DialogTitle>
|
|
<DialogDescription>
|
|
This will forcefully terminate the server process. Any unsaved data may be lost.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<DialogFooter>
|
|
<DialogClose asChild>
|
|
<Button variant="outline">Cancel</Button>
|
|
</DialogClose>
|
|
<DialogClose asChild>
|
|
<Button variant="destructive" onClick={() => powerMutation.mutate('kill')}>
|
|
Kill Server
|
|
</Button>
|
|
</DialogClose>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
);
|
|
}
|