First
Some checks failed
Build All Docker Images / changes (push) Has been cancelled
Build and Push App Docker Image / build (push) Has been cancelled
Build and Push Node Docker Image / build (push) Has been cancelled
Test and Lint / test-app (push) Has been cancelled
Test and Lint / test-node (push) Has been cancelled
Test and Lint / lint-dockerfiles (push) Has been cancelled
Test and Lint / security-scan (push) Has been cancelled
Build All Docker Images / build-app (push) Has been cancelled
Build All Docker Images / build-node (push) Has been cancelled
Build All Docker Images / summary (push) Has been cancelled
Some checks failed
Build All Docker Images / changes (push) Has been cancelled
Build and Push App Docker Image / build (push) Has been cancelled
Build and Push Node Docker Image / build (push) Has been cancelled
Test and Lint / test-app (push) Has been cancelled
Test and Lint / test-node (push) Has been cancelled
Test and Lint / lint-dockerfiles (push) Has been cancelled
Test and Lint / security-scan (push) Has been cancelled
Build All Docker Images / build-app (push) Has been cancelled
Build All Docker Images / build-node (push) Has been cancelled
Build All Docker Images / summary (push) Has been cancelled
This commit is contained in:
commit
4169337dd0
68 changed files with 8726 additions and 0 deletions
253
app/src/client/pages/ServerStatus.tsx
Normal file
253
app/src/client/pages/ServerStatus.tsx
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
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;
|
||||
Loading…
Add table
Add a link
Reference in a new issue