arc-frp/app/src/client/pages/ServerStatus.tsx

254 lines
8.2 KiB
TypeScript
Raw Normal View History

2025-07-03 15:50:13 -04:00
import React, { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { frpcApi } from '../api/client';
import { Server, Play, Square, RotateCcw, RefreshCw, FileText, Activity } from 'lucide-react';
import { toast } from 'react-hot-toast';
const ServerStatus: React.FC = () => {
const [logsLines, setLogsLines] = useState(50);
const queryClient = useQueryClient();
const { data: frpcStatus, isLoading: statusLoading } = useQuery({
queryKey: ['frpc-status'],
queryFn: frpcApi.getStatus,
refetchInterval: 3000,
});
const { data: logs, isLoading: logsLoading } = useQuery({
queryKey: ['frpc-logs', logsLines],
queryFn: () => frpcApi.getLogs(logsLines),
refetchInterval: 5000,
});
const startMutation = useMutation({
mutationFn: frpcApi.start,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['frpc-status'] });
toast.success('FRPC service started');
},
onError: (error) => {
console.error('Start error:', error);
toast.error('Failed to start FRPC service');
},
});
const stopMutation = useMutation({
mutationFn: frpcApi.stop,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['frpc-status'] });
toast.success('FRPC service stopped');
},
onError: (error) => {
console.error('Stop error:', error);
toast.error('Failed to stop FRPC service');
},
});
const restartMutation = useMutation({
mutationFn: frpcApi.restart,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['frpc-status'] });
toast.success('FRPC service restarted');
},
onError: (error) => {
console.error('Restart error:', error);
toast.error('Failed to restart FRPC service');
},
});
const regenerateMutation = useMutation({
mutationFn: frpcApi.regenerate,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['frpc-status'] });
toast.success('FRPC configuration regenerated');
},
onError: (error) => {
console.error('Regenerate error:', error);
toast.error('Failed to regenerate FRPC configuration');
},
});
const handleStart = () => startMutation.mutate();
const handleStop = () => stopMutation.mutate();
const handleRestart = () => restartMutation.mutate();
const handleRegenerate = () => regenerateMutation.mutate();
const isLoading = statusLoading || logsLoading;
const isAnyMutationPending =
startMutation.isPending ||
stopMutation.isPending ||
restartMutation.isPending ||
regenerateMutation.isPending;
return (
<div className="server-status">
<div className="server-header">
<h1>Server Status</h1>
<div className="server-info">
<div className="status-indicator">
<Activity
size={24}
className={frpcStatus?.running ? 'text-green-500' : 'text-red-500'}
/>
<span className={`status-text ${frpcStatus?.running ? 'text-green-500' : 'text-red-500'}`}>
{frpcStatus?.running ? 'Running' : 'Stopped'}
</span>
</div>
</div>
</div>
<div className="server-controls">
<div className="control-section">
<h2>Service Controls</h2>
<div className="control-buttons">
<button
className="btn btn-success"
onClick={handleStart}
disabled={isAnyMutationPending || frpcStatus?.running}
>
<Play size={18} />
Start
</button>
<button
className="btn btn-danger"
onClick={handleStop}
disabled={isAnyMutationPending || !frpcStatus?.running}
>
<Square size={18} />
Stop
</button>
<button
className="btn btn-warning"
onClick={handleRestart}
disabled={isAnyMutationPending}
>
<RotateCcw size={18} />
Restart
</button>
<button
className="btn btn-secondary"
onClick={handleRegenerate}
disabled={isAnyMutationPending}
>
<RefreshCw size={18} />
Regenerate Config
</button>
</div>
</div>
<div className="status-section">
<h2>Service Information</h2>
<div className="info-grid">
<div className="info-item">
<span className="info-label">Status:</span>
<span className={`info-value ${frpcStatus?.running ? 'text-green-500' : 'text-red-500'}`}>
{frpcStatus?.running ? 'Running' : 'Stopped'}
</span>
</div>
<div className="info-item">
<span className="info-label">Container:</span>
<span className="info-value">frpc</span>
</div>
<div className="info-item">
<span className="info-label">Last Updated:</span>
<span className="info-value">
{new Date().toLocaleString()}
</span>
</div>
</div>
</div>
</div>
<div className="logs-section">
<div className="logs-header">
<h2>
<FileText size={20} />
Service Logs
</h2>
<div className="logs-controls">
<select
value={logsLines}
onChange={(e) => setLogsLines(parseInt(e.target.value))}
className="logs-select"
>
<option value={25}>Last 25 lines</option>
<option value={50}>Last 50 lines</option>
<option value={100}>Last 100 lines</option>
<option value={200}>Last 200 lines</option>
</select>
<button
className="btn btn-sm btn-secondary"
onClick={() => queryClient.invalidateQueries({ queryKey: ['frpc-logs'] })}
disabled={logsLoading}
>
<RefreshCw size={16} />
Refresh
</button>
</div>
</div>
<div className="logs-container">
{logsLoading ? (
<div className="logs-loading">
<RefreshCw className="animate-spin" size={20} />
<span>Loading logs...</span>
</div>
) : (
<pre className="logs-content">
{logs?.logs || 'No logs available'}
</pre>
)}
</div>
</div>
<div className="system-info">
<h2>System Information</h2>
<div className="info-cards">
<div className="info-card">
<div className="info-card-header">
<Server size={24} />
<h3>FRPC Service</h3>
</div>
<div className="info-card-content">
<p>Fast Reverse Proxy Client for tunneling services</p>
<p className="info-detail">
Status: <span className={frpcStatus?.running ? 'text-green-500' : 'text-red-500'}>
{frpcStatus?.running ? 'Active' : 'Inactive'}
</span>
</p>
</div>
</div>
<div className="info-card">
<div className="info-card-header">
<Activity size={24} />
<h3>API Server</h3>
</div>
<div className="info-card-content">
<p>RESTful API for managing tunnel configurations</p>
<p className="info-detail">
Status: <span className="text-green-500">Running</span>
</p>
</div>
</div>
<div className="info-card">
<div className="info-card-header">
<FileText size={24} />
<h3>Configuration</h3>
</div>
<div className="info-card-content">
<p>Tunnel configurations stored in SQLite database</p>
<p className="info-detail">
Auto-generated FRPC config from active tunnels
</p>
</div>
</div>
</div>
</div>
</div>
);
};
export default ServerStatus;