254 lines
8.2 KiB
TypeScript
254 lines
8.2 KiB
TypeScript
|
|
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;
|