goldenChat base source add
This commit is contained in:
@@ -0,0 +1,478 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user