22
33import { useCallback , useMemo , useState } from 'react' ;
44import { useRouter , useSearchParams } from 'next/navigation' ;
5- import { useQuery } from '@tanstack/react-query' ;
5+ import { useMutation , useQuery , useQueryClient } from '@tanstack/react-query' ;
66import { useTRPC } from '@/lib/trpc/utils' ;
77import {
88 Table ,
@@ -23,7 +23,17 @@ import {
2323 SelectValue ,
2424} from '@/components/ui/select' ;
2525import { Badge } from '@/components/ui/badge' ;
26- import { ChevronLeft , ChevronRight , X } from 'lucide-react' ;
26+ import {
27+ AlertDialog ,
28+ AlertDialogAction ,
29+ AlertDialogCancel ,
30+ AlertDialogContent ,
31+ AlertDialogDescription ,
32+ AlertDialogFooter ,
33+ AlertDialogHeader ,
34+ AlertDialogTitle ,
35+ } from '@/components/ui/alert-dialog' ;
36+ import { ChevronLeft , ChevronRight , X , Bomb } from 'lucide-react' ;
2737import Link from 'next/link' ;
2838import { formatDistanceToNow , format , parseISO } from 'date-fns' ;
2939import {
@@ -219,6 +229,66 @@ function DailyChart({ data }: { data: DailyChartData[] }) {
219229 ) ;
220230}
221231
232+ // --- Dev Nuke All Button ---
233+
234+ function DevNukeAllButton ( ) {
235+ if ( process . env . NODE_ENV !== 'development' ) return null ;
236+
237+ const trpc = useTRPC ( ) ;
238+ const queryClient = useQueryClient ( ) ;
239+ const [ open , setOpen ] = useState ( false ) ;
240+
241+ const nukeAll = useMutation (
242+ trpc . admin . kiloclawInstances . devNukeAll . mutationOptions ( {
243+ onSuccess ( data ) {
244+ void queryClient . invalidateQueries ( {
245+ queryKey : trpc . admin . kiloclawInstances . list . queryKey ( ) ,
246+ } ) ;
247+ void queryClient . invalidateQueries ( {
248+ queryKey : trpc . admin . kiloclawInstances . stats . queryKey ( ) ,
249+ } ) ;
250+ const errorSuffix =
251+ data . errors . length > 0
252+ ? `\n${ data . errors . length } failed:\n${ data . errors . map ( e => ` ${ e . userId } : ${ e . error } ` ) . join ( '\n' ) } `
253+ : '' ;
254+ alert ( `Destroyed ${ data . destroyed } /${ data . total } instances${ errorSuffix } ` ) ;
255+ } ,
256+ } )
257+ ) ;
258+
259+ return (
260+ < >
261+ < Button variant = "destructive" onClick = { ( ) => setOpen ( true ) } disabled = { nukeAll . isPending } >
262+ < Bomb className = "mr-2 h-4 w-4" />
263+ { nukeAll . isPending ? 'Nuking...' : 'Nuke All' }
264+ </ Button >
265+ < AlertDialog open = { open } onOpenChange = { setOpen } >
266+ < AlertDialogContent >
267+ < AlertDialogHeader >
268+ < AlertDialogTitle > Nuke all KiloClaw instances?</ AlertDialogTitle >
269+ < AlertDialogDescription >
270+ This will destroy every active KiloClaw instance. This action cannot be undone. Only
271+ available in development mode.
272+ </ AlertDialogDescription >
273+ </ AlertDialogHeader >
274+ < AlertDialogFooter >
275+ < AlertDialogCancel > Cancel</ AlertDialogCancel >
276+ < AlertDialogAction
277+ onClick = { ( ) => {
278+ nukeAll . mutate ( ) ;
279+ setOpen ( false ) ;
280+ } }
281+ className = "bg-destructive text-destructive-foreground hover:bg-destructive/90"
282+ >
283+ Nuke All
284+ </ AlertDialogAction >
285+ </ AlertDialogFooter >
286+ </ AlertDialogContent >
287+ </ AlertDialog >
288+ </ >
289+ ) ;
290+ }
291+
222292// --- Main Page ---
223293
224294export function KiloclawInstancesPage ( ) {
@@ -378,6 +448,8 @@ export function KiloclawInstancesPage() {
378448 < SelectItem value = "destroyed" > Destroyed Only</ SelectItem >
379449 </ SelectContent >
380450 </ Select >
451+
452+ < DevNukeAllButton />
381453 </ div >
382454
383455 { /* Table */ }
0 commit comments