Files
source-gamepanel/apps/web/src/components/server/power-controls.tsx
T
hibna c1adb94abb Format the repository with Prettier
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.
2026-08-02 21:08:12 +03:00

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>
);
}