- Dashboard Overview with real-time status display - Live Log Viewer (scrollable, filterable) - Config Editor with JSON syntax highlighting - Model Switcher for provider management - Provider Manager for API key configuration - Quick Actions for common tasks - API Routes: status, logs, config, actions, models, providers Tech Stack: - Next.js 14 (App Router) - TypeScript - Tailwind CSS - shadcn/ui components - CodeMirror for JSON editing - Docker support with docker-compose
163 lines
5.5 KiB
TypeScript
163 lines
5.5 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
|
import { Power, RefreshCw, Check, X, Play, Square, RotateCcw, AlertTriangle } from 'lucide-react';
|
|
|
|
export function QuickActions() {
|
|
const [loading, setLoading] = useState<string | null>(null);
|
|
const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null);
|
|
|
|
const handleAction = async (action: 'start' | 'stop' | 'restart') => {
|
|
setLoading(action);
|
|
setMessage(null);
|
|
|
|
try {
|
|
const response = await fetch('/api/actions', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ action }),
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
setMessage({ type: 'success', text: result.message });
|
|
} else {
|
|
setMessage({ type: 'error', text: result.message || 'Action failed' });
|
|
}
|
|
} catch (error) {
|
|
setMessage({ type: 'error', text: 'Failed to execute action' });
|
|
} finally {
|
|
setLoading(null);
|
|
}
|
|
};
|
|
|
|
const actions = [
|
|
{
|
|
id: 'start' as const,
|
|
title: 'Start Gateway',
|
|
description: 'Start the OpenClaw Gateway daemon',
|
|
icon: Play,
|
|
variant: 'default' as const,
|
|
color: 'bg-green-600 hover:bg-green-700',
|
|
},
|
|
{
|
|
id: 'stop' as const,
|
|
title: 'Stop Gateway',
|
|
description: 'Stop the OpenClaw Gateway daemon',
|
|
icon: Square,
|
|
variant: 'outline' as const,
|
|
color: 'border-red-600 text-red-400 hover:bg-red-600/10',
|
|
},
|
|
{
|
|
id: 'restart' as const,
|
|
title: 'Restart Gateway',
|
|
description: 'Restart the OpenClaw Gateway daemon',
|
|
icon: RotateCcw,
|
|
variant: 'outline' as const,
|
|
color: 'border-orange-600 text-orange-400 hover:bg-orange-600/10',
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Warning */}
|
|
<Alert className="bg-yellow-500/10 border-yellow-500/20 text-yellow-400">
|
|
<AlertTriangle className="h-4 w-4" />
|
|
<AlertDescription>
|
|
These actions affect the entire Gateway. Be careful when stopping or restarting.
|
|
</AlertDescription>
|
|
</Alert>
|
|
|
|
{/* Message */}
|
|
{message && (
|
|
<Alert variant={message.type === 'error' ? 'destructive' : 'default'} className={
|
|
message.type === 'success' ? 'bg-green-500/10 border-green-500/20 text-green-400' : ''
|
|
}>
|
|
{message.type === 'success' ? (
|
|
<Check className="h-4 w-4" />
|
|
) : (
|
|
<X className="h-4 w-4" />
|
|
)}
|
|
<AlertDescription>{message.text}</AlertDescription>
|
|
</Alert>
|
|
)}
|
|
|
|
{/* Action Cards */}
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
{actions.map((action) => {
|
|
const Icon = action.icon;
|
|
const isLoading = loading === action.id;
|
|
|
|
return (
|
|
<Card
|
|
key={action.id}
|
|
className="bg-zinc-900/50 border-zinc-800 hover:border-zinc-700 transition-all hover:scale-[1.02]"
|
|
>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Icon className="w-5 h-5" />
|
|
{action.title}
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p className="text-sm text-zinc-400 mb-4">
|
|
{action.description}
|
|
</p>
|
|
<Button
|
|
onClick={() => handleAction(action.id)}
|
|
disabled={isLoading}
|
|
className={`w-full ${action.id === 'start' ? action.color : ''} ${
|
|
action.id !== 'start'
|
|
? 'border-zinc-700 hover:bg-zinc-800'
|
|
: ''
|
|
}`}
|
|
variant={action.variant}
|
|
>
|
|
{isLoading ? (
|
|
<>
|
|
<RefreshCw className="w-4 h-4 mr-2 animate-spin" />
|
|
{action.id.charAt(0).toUpperCase() + action.id.slice(1)}ing...
|
|
</>
|
|
) : (
|
|
<>
|
|
{action.id === 'start' && <Power className="w-4 h-4 mr-2" />}
|
|
{action.id === 'stop' && <Square className="w-4 h-4 mr-2" />}
|
|
{action.id === 'restart' && <RotateCcw className="w-4 h-4 mr-2" />}
|
|
{action.title}
|
|
</>
|
|
)}
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
{/* Info */}
|
|
<Card className="bg-zinc-900/50 border-zinc-800">
|
|
<CardHeader>
|
|
<CardTitle className="text-lg">Quick Actions Info</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-3 text-sm text-zinc-400">
|
|
<div>
|
|
<strong className="text-zinc-300">Start Gateway:</strong> Launches the OpenClaw
|
|
Gateway daemon if it's not running.
|
|
</div>
|
|
<div>
|
|
<strong className="text-zinc-300">Stop Gateway:</strong> Gracefully stops the Gateway.
|
|
All active sessions will be terminated.
|
|
</div>
|
|
<div>
|
|
<strong className="text-zinc-300">Restart Gateway:</strong> Stops and immediately
|
|
starts the Gateway again. Useful for applying config changes.
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|