Files
goldenChart/frontend_golden/src/components/dashboard/RealtimeWebSocketGauges.tsx
T
2026-05-23 15:11:48 +09:00

479 lines
16 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { Box, Typography, Grid, Card, CardContent, Chip, Alert } from '@mui/material';
import GaugeComponent from 'react-gauge-component';
import { Cable, CheckCircle, Error as ErrorIcon } from '@mui/icons-material';
import { useRealtimeWebSocketMetrics } from '../../hooks/useRealtimeWebSocketMetrics';
interface RealtimeWebSocketGaugesProps {
websocketStats: {
totalConnections: number;
activeConnections: number;
serviceStats: Record<string, {
serviceName: string;
connections: number;
messagesReceived: number;
messagesSent: number;
messagesPerSecond: number;
lastActivity: string | null;
}>;
};
}
const RealtimeWebSocketGauges: React.FC<RealtimeWebSocketGaugesProps> = ({ websocketStats }) => {
// 실시간 WebSocket 메트릭 Hook 사용
const { metrics, isConnected } = useRealtimeWebSocketMetrics();
const [animatedValues, setAnimatedValues] = useState({
frontend: 0,
backend: 0,
dataService: 0,
});
// 실시간 메트릭으로 값 업데이트
useEffect(() => {
setAnimatedValues({
frontend: metrics.frontend.messagesPerSecond,
backend: metrics.backend.messagesPerSecond,
dataService: metrics.dataService.messagesPerSecond,
});
}, [metrics]);
return (
<Box>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 3 }}>
<Typography variant="h6" sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Cable color="info" />
실시간 WebSocket 데이터 수신 현황
</Typography>
<Chip
icon={isConnected ? <CheckCircle /> : <ErrorIcon />}
label={isConnected ? 'WebSocket 연결됨' : 'WebSocket 연결 끊김'}
color={isConnected ? 'success' : 'error'}
size="small"
sx={{
boxShadow: isConnected
? '0 0 10px rgba(74, 222, 128, 0.4)'
: '0 0 10px rgba(248, 113, 113, 0.4)',
}}
/>
</Box>
{!isConnected && (
<Alert severity="warning" sx={{ mb: 2 }}>
WebSocket 연결이 끊어졌습니다. 재연결을 시도 중입니다...
</Alert>
)}
<Card
sx={{
mb: 4,
p: 3,
bgcolor: 'rgba(10, 25, 41, 0.9)',
border: '1px solid rgba(100, 149, 237, 0.3)',
}}
>
{/* 상단 통계 - 실시간 메트릭 사용 */}
<Grid container spacing={2} sx={{ mb: 3 }}>
<Grid item xs={12} sm={6}>
<Box
sx={{
p: 2,
bgcolor: 'rgba(30, 41, 59, 0.6)',
borderRadius: 2,
border: '1px solid rgba(255,255,255,0.1)',
}}
>
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
메시지 (Frontend)
</Typography>
<Typography variant="h4" sx={{ color: '#4ade80', fontWeight: 700, mt: 1 }}>
{metrics.frontend.totalMessages.toLocaleString()}
</Typography>
</Box>
</Grid>
<Grid item xs={12} sm={6}>
<Box
sx={{
p: 2,
bgcolor: 'rgba(30, 41, 59, 0.6)',
borderRadius: 2,
border: '1px solid rgba(255,255,255,0.1)',
}}
>
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
메시지 (Backend)
</Typography>
<Typography variant="h4" sx={{ color: '#fbbf24', fontWeight: 700, mt: 1 }}>
{metrics.backend.totalMessages.toLocaleString()}
</Typography>
</Box>
</Grid>
</Grid>
{/* 게이지 차트들 */}
<Grid container spacing={4}>
{/* Frontend WebSocket */}
<Grid item xs={12} md={4}>
<Box
sx={{
p: 3,
bgcolor: 'rgba(30, 41, 59, 0.6)',
borderRadius: 3,
border: '1px solid rgba(74, 222, 128, 0.3)',
boxShadow: '0 0 20px rgba(74, 222, 128, 0.1)',
}}
>
<Typography
variant="subtitle1"
sx={{
color: '#fff',
fontWeight: 600,
mb: 2,
textAlign: 'center',
}}
>
Frontend
</Typography>
<GaugeComponent
type="semicircle"
arc={{
width: 0.2,
padding: 0.005,
cornerRadius: 7,
subArcs: [
{
limit: 20,
color: '#4ade80',
showTick: true,
},
{
limit: 40,
color: '#a3e635',
showTick: true,
},
{
limit: 60,
color: '#fbbf24',
showTick: true,
},
{
limit: 80,
color: '#fb923c',
showTick: true,
},
{
limit: 100,
color: '#f87171',
showTick: true,
},
],
}}
pointer={{
color: '#fff',
length: 0.80,
width: 15,
elastic: true,
}}
labels={{
valueLabel: {
formatTextValue: (value) => `${value.toFixed(1)} msg/s`,
style: {
fontSize: '28px',
fill: '#fff',
textShadow: '0 0 10px rgba(74, 222, 128, 0.8)',
},
},
tickLabels: {
type: 'outer',
ticks: [
{ value: 0 },
{ value: 20 },
{ value: 40 },
{ value: 60 },
{ value: 80 },
{ value: 100 },
],
defaultTickValueConfig: {
formatTextValue: (value) => `${value}`,
style: {
fontSize: '10px',
fill: 'rgba(255,255,255,0.6)',
},
},
},
}}
value={Math.min(animatedValues.frontend * 10, 100)}
minValue={0}
maxValue={100}
/>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mt: 2 }}>
<Chip
label={`${animatedValues.frontend.toFixed(1)} msg/s`}
size="small"
sx={{ bgcolor: 'rgba(74, 222, 128, 0.2)', color: '#4ade80', fontWeight: 700 }}
/>
<Chip
label={`총: ${metrics.frontend.totalMessages.toLocaleString()}`}
size="small"
variant="outlined"
sx={{ color: 'rgba(255,255,255,0.7)' }}
/>
</Box>
</Box>
</Grid>
{/* Backend WebSocket */}
<Grid item xs={12} md={4}>
<Box
sx={{
p: 3,
bgcolor: 'rgba(30, 41, 59, 0.6)',
borderRadius: 3,
border: '1px solid rgba(251, 191, 36, 0.3)',
boxShadow: '0 0 20px rgba(251, 191, 36, 0.1)',
}}
>
<Typography
variant="subtitle1"
sx={{
color: '#fff',
fontWeight: 600,
mb: 2,
textAlign: 'center',
}}
>
Backend (Upbit)
</Typography>
<GaugeComponent
type="semicircle"
arc={{
width: 0.2,
padding: 0.005,
cornerRadius: 7,
subArcs: [
{
limit: 20,
color: '#fbbf24',
showTick: true,
},
{
limit: 40,
color: '#fb923c',
showTick: true,
},
{
limit: 60,
color: '#f87171',
showTick: true,
},
{
limit: 80,
color: '#ef4444',
showTick: true,
},
{
limit: 100,
color: '#dc2626',
showTick: true,
},
],
}}
pointer={{
color: '#fff',
length: 0.80,
width: 15,
elastic: true,
}}
labels={{
valueLabel: {
formatTextValue: (value) => `${value.toFixed(1)} msg/s`,
style: {
fontSize: '28px',
fill: '#fff',
textShadow: '0 0 10px rgba(251, 191, 36, 0.8)',
},
},
tickLabels: {
type: 'outer',
ticks: [
{ value: 0 },
{ value: 20 },
{ value: 40 },
{ value: 60 },
{ value: 80 },
{ value: 100 },
],
defaultTickValueConfig: {
formatTextValue: (value) => `${value}`,
style: {
fontSize: '10px',
fill: 'rgba(255,255,255,0.6)',
},
},
},
}}
value={Math.min(animatedValues.backend * 10, 100)}
minValue={0}
maxValue={100}
/>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mt: 2 }}>
<Chip
label={`${animatedValues.backend.toFixed(1)} msg/s`}
size="small"
sx={{ bgcolor: 'rgba(251, 191, 36, 0.2)', color: '#fbbf24', fontWeight: 700 }}
/>
<Chip
label={`총: ${metrics.backend.totalMessages.toLocaleString()}`}
size="small"
variant="outlined"
sx={{ color: 'rgba(255,255,255,0.7)' }}
/>
</Box>
</Box>
</Grid>
{/* Data Service WebSocket */}
<Grid item xs={12} md={4}>
<Box
sx={{
p: 3,
bgcolor: 'rgba(30, 41, 59, 0.6)',
borderRadius: 3,
border: '1px solid rgba(100, 149, 237, 0.3)',
boxShadow: '0 0 20px rgba(100, 149, 237, 0.1)',
}}
>
<Typography
variant="subtitle1"
sx={{
color: '#fff',
fontWeight: 600,
mb: 2,
textAlign: 'center',
}}
>
Data Service
</Typography>
<GaugeComponent
type="semicircle"
arc={{
width: 0.2,
padding: 0.005,
cornerRadius: 7,
subArcs: [
{
limit: 20,
color: '#6495ed',
showTick: true,
},
{
limit: 40,
color: '#60a5fa',
showTick: true,
},
{
limit: 60,
color: '#93c5fd',
showTick: true,
},
{
limit: 80,
color: '#dbeafe',
showTick: true,
},
{
limit: 100,
color: '#fff',
showTick: true,
},
],
}}
pointer={{
color: '#fff',
length: 0.80,
width: 15,
elastic: true,
}}
labels={{
valueLabel: {
formatTextValue: (value) => `${value.toFixed(1)} msg/s`,
style: {
fontSize: '28px',
fill: '#fff',
textShadow: '0 0 10px rgba(100, 149, 237, 0.8)',
},
},
tickLabels: {
type: 'outer',
ticks: [
{ value: 0 },
{ value: 20 },
{ value: 40 },
{ value: 60 },
{ value: 80 },
{ value: 100 },
],
defaultTickValueConfig: {
formatTextValue: (value) => `${value}`,
style: {
fontSize: '10px',
fill: 'rgba(255,255,255,0.6)',
},
},
},
}}
value={Math.min(animatedValues.dataService * 10, 100)}
minValue={0}
maxValue={100}
/>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mt: 2 }}>
<Chip
label={`${animatedValues.dataService.toFixed(1)} msg/s`}
size="small"
sx={{ bgcolor: 'rgba(100, 149, 237, 0.2)', color: '#6495ed', fontWeight: 700 }}
/>
<Chip
label={`총: ${metrics.dataService.totalMessages.toLocaleString()}`}
size="small"
variant="outlined"
sx={{ color: 'rgba(255,255,255,0.7)' }}
/>
</Box>
</Box>
</Grid>
</Grid>
{/* 하단 상세 정보 */}
<Box sx={{ mt: 3, p: 2, bgcolor: 'rgba(15, 23, 42, 0.6)', borderRadius: 2 }}>
<Grid container spacing={2}>
<Grid item xs={12} md={8}>
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.5)' }}>
💡 게이지는 실제 WebSocket 메시지 수신을 실시간으로 모니터링합니다. 100ms마다 업데이트되며,
1 슬라이딩 윈도우로 초당 메시지 수를 계산합니다.
</Typography>
</Grid>
<Grid item xs={12} md={4}>
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
<Chip
label={`업데이트: ${isConnected ? '실시간' : '중단됨'}`}
size="small"
color={isConnected ? 'success' : 'default'}
variant="outlined"
/>
<Chip
label="100ms 갱신"
size="small"
variant="outlined"
sx={{ color: 'rgba(255,255,255,0.7)' }}
/>
</Box>
</Grid>
</Grid>
</Box>
</Card>
</Box>
);
};
export default RealtimeWebSocketGauges;