@qwickapps/server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/LICENSE +45 -0
  2. package/README.md +321 -0
  3. package/dist/core/control-panel.d.ts +21 -0
  4. package/dist/core/control-panel.d.ts.map +1 -0
  5. package/dist/core/control-panel.js +416 -0
  6. package/dist/core/control-panel.js.map +1 -0
  7. package/dist/core/gateway.d.ts +133 -0
  8. package/dist/core/gateway.d.ts.map +1 -0
  9. package/dist/core/gateway.js +270 -0
  10. package/dist/core/gateway.js.map +1 -0
  11. package/dist/core/health-manager.d.ts +52 -0
  12. package/dist/core/health-manager.d.ts.map +1 -0
  13. package/dist/core/health-manager.js +192 -0
  14. package/dist/core/health-manager.js.map +1 -0
  15. package/dist/core/index.d.ts +10 -0
  16. package/dist/core/index.d.ts.map +1 -0
  17. package/dist/core/index.js +8 -0
  18. package/dist/core/index.js.map +1 -0
  19. package/dist/core/logging.d.ts +83 -0
  20. package/dist/core/logging.d.ts.map +1 -0
  21. package/dist/core/logging.js +191 -0
  22. package/dist/core/logging.js.map +1 -0
  23. package/dist/core/types.d.ts +195 -0
  24. package/dist/core/types.d.ts.map +1 -0
  25. package/dist/core/types.js +7 -0
  26. package/dist/core/types.js.map +1 -0
  27. package/dist/index.d.ts +18 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +17 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/plugins/config-plugin.d.ts +15 -0
  32. package/dist/plugins/config-plugin.d.ts.map +1 -0
  33. package/dist/plugins/config-plugin.js +96 -0
  34. package/dist/plugins/config-plugin.js.map +1 -0
  35. package/dist/plugins/diagnostics-plugin.d.ts +29 -0
  36. package/dist/plugins/diagnostics-plugin.d.ts.map +1 -0
  37. package/dist/plugins/diagnostics-plugin.js +142 -0
  38. package/dist/plugins/diagnostics-plugin.js.map +1 -0
  39. package/dist/plugins/health-plugin.d.ts +17 -0
  40. package/dist/plugins/health-plugin.d.ts.map +1 -0
  41. package/dist/plugins/health-plugin.js +25 -0
  42. package/dist/plugins/health-plugin.js.map +1 -0
  43. package/dist/plugins/index.d.ts +14 -0
  44. package/dist/plugins/index.d.ts.map +1 -0
  45. package/dist/plugins/index.js +10 -0
  46. package/dist/plugins/index.js.map +1 -0
  47. package/dist/plugins/logs-plugin.d.ts +22 -0
  48. package/dist/plugins/logs-plugin.d.ts.map +1 -0
  49. package/dist/plugins/logs-plugin.js +242 -0
  50. package/dist/plugins/logs-plugin.js.map +1 -0
  51. package/dist-ui/assets/index-Bk7ypbI4.js +465 -0
  52. package/dist-ui/assets/index-Bk7ypbI4.js.map +1 -0
  53. package/dist-ui/assets/index-CiizQQnb.css +1 -0
  54. package/dist-ui/index.html +13 -0
  55. package/package.json +98 -0
  56. package/src/core/control-panel.ts +493 -0
  57. package/src/core/gateway.ts +421 -0
  58. package/src/core/health-manager.ts +227 -0
  59. package/src/core/index.ts +25 -0
  60. package/src/core/logging.ts +234 -0
  61. package/src/core/types.ts +218 -0
  62. package/src/index.ts +55 -0
  63. package/src/plugins/config-plugin.ts +117 -0
  64. package/src/plugins/diagnostics-plugin.ts +178 -0
  65. package/src/plugins/health-plugin.ts +35 -0
  66. package/src/plugins/index.ts +17 -0
  67. package/src/plugins/logs-plugin.ts +314 -0
  68. package/ui/index.html +12 -0
  69. package/ui/src/App.tsx +65 -0
  70. package/ui/src/api/controlPanelApi.ts +148 -0
  71. package/ui/src/config/AppConfig.ts +18 -0
  72. package/ui/src/index.css +29 -0
  73. package/ui/src/index.tsx +11 -0
  74. package/ui/src/pages/ConfigPage.tsx +199 -0
  75. package/ui/src/pages/DashboardPage.tsx +264 -0
  76. package/ui/src/pages/DiagnosticsPage.tsx +315 -0
  77. package/ui/src/pages/HealthPage.tsx +204 -0
  78. package/ui/src/pages/LogsPage.tsx +267 -0
  79. package/ui/src/pages/NotFoundPage.tsx +41 -0
  80. package/ui/tsconfig.json +19 -0
  81. package/ui/vite.config.ts +21 -0
@@ -0,0 +1,315 @@
1
+ import { useState, useEffect } from 'react';
2
+ import {
3
+ Box,
4
+ Card,
5
+ CardContent,
6
+ Typography,
7
+ Grid,
8
+ CircularProgress,
9
+ Chip,
10
+ IconButton,
11
+ Tooltip,
12
+ Snackbar,
13
+ Alert,
14
+ LinearProgress,
15
+ } from '@mui/material';
16
+ import ContentCopyIcon from '@mui/icons-material/ContentCopy';
17
+ import RefreshIcon from '@mui/icons-material/Refresh';
18
+ import MemoryIcon from '@mui/icons-material/Memory';
19
+ import ComputerIcon from '@mui/icons-material/Computer';
20
+ import StorageIcon from '@mui/icons-material/Storage';
21
+ import AccessTimeIcon from '@mui/icons-material/AccessTime';
22
+ import { api, DiagnosticsResponse } from '../api/controlPanelApi';
23
+
24
+ function formatBytes(bytes: number): string {
25
+ if (bytes === 0) return '0 B';
26
+ const k = 1024;
27
+ const sizes = ['B', 'KB', 'MB', 'GB'];
28
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
29
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
30
+ }
31
+
32
+ function formatUptime(ms: number): string {
33
+ const seconds = Math.floor(ms / 1000);
34
+ const minutes = Math.floor(seconds / 60);
35
+ const hours = Math.floor(minutes / 60);
36
+ const days = Math.floor(hours / 24);
37
+
38
+ if (days > 0) {
39
+ return `${days}d ${hours % 24}h ${minutes % 60}m`;
40
+ }
41
+ if (hours > 0) {
42
+ return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
43
+ }
44
+ if (minutes > 0) {
45
+ return `${minutes}m ${seconds % 60}s`;
46
+ }
47
+ return `${seconds}s`;
48
+ }
49
+
50
+ export function DiagnosticsPage() {
51
+ const [diagnostics, setDiagnostics] = useState<DiagnosticsResponse | null>(null);
52
+ const [loading, setLoading] = useState(true);
53
+ const [error, setError] = useState<string | null>(null);
54
+ const [snackbar, setSnackbar] = useState<{ open: boolean; message: string }>({
55
+ open: false,
56
+ message: '',
57
+ });
58
+
59
+ const fetchDiagnostics = async () => {
60
+ setLoading(true);
61
+ try {
62
+ const data = await api.getDiagnostics();
63
+ setDiagnostics(data);
64
+ setError(null);
65
+ } catch (err) {
66
+ setError(err instanceof Error ? err.message : 'Failed to fetch diagnostics');
67
+ } finally {
68
+ setLoading(false);
69
+ }
70
+ };
71
+
72
+ useEffect(() => {
73
+ fetchDiagnostics();
74
+ const interval = setInterval(fetchDiagnostics, 30000);
75
+ return () => clearInterval(interval);
76
+ }, []);
77
+
78
+ const handleCopyAll = () => {
79
+ navigator.clipboard.writeText(JSON.stringify(diagnostics, null, 2));
80
+ setSnackbar({ open: true, message: 'Diagnostics copied to clipboard' });
81
+ };
82
+
83
+ if (loading && !diagnostics) {
84
+ return (
85
+ <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '50vh' }}>
86
+ <CircularProgress />
87
+ </Box>
88
+ );
89
+ }
90
+
91
+ if (error) {
92
+ return (
93
+ <Card sx={{ bgcolor: 'var(--theme-surface)', border: '1px solid var(--theme-error)' }}>
94
+ <CardContent>
95
+ <Typography color="error">{error}</Typography>
96
+ </CardContent>
97
+ </Card>
98
+ );
99
+ }
100
+
101
+ const memoryUsedPercent = diagnostics
102
+ ? (diagnostics.system.memory.used / diagnostics.system.memory.total) * 100
103
+ : 0;
104
+
105
+ return (
106
+ <Box>
107
+ <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1 }}>
108
+ <Typography variant="h4" sx={{ color: 'var(--theme-text-primary)' }}>
109
+ Diagnostics
110
+ </Typography>
111
+ <Box sx={{ display: 'flex', gap: 1 }}>
112
+ <Tooltip title="Copy diagnostics JSON">
113
+ <IconButton onClick={handleCopyAll} sx={{ color: 'var(--theme-primary)' }}>
114
+ <ContentCopyIcon />
115
+ </IconButton>
116
+ </Tooltip>
117
+ <Tooltip title="Refresh">
118
+ <IconButton onClick={fetchDiagnostics} sx={{ color: 'var(--theme-primary)' }}>
119
+ <RefreshIcon />
120
+ </IconButton>
121
+ </Tooltip>
122
+ </Box>
123
+ </Box>
124
+ <Typography variant="body2" sx={{ mb: 4, color: 'var(--theme-text-secondary)' }}>
125
+ System information and health diagnostics for AI agents and troubleshooting
126
+ </Typography>
127
+
128
+ <Grid container spacing={3}>
129
+ {/* System Info */}
130
+ <Grid size={{ xs: 12, md: 6 }}>
131
+ <Card sx={{ bgcolor: 'var(--theme-surface)', height: '100%' }}>
132
+ <CardContent>
133
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
134
+ <ComputerIcon sx={{ color: 'var(--theme-primary)' }} />
135
+ <Typography variant="h6" sx={{ color: 'var(--theme-text-primary)' }}>
136
+ System Information
137
+ </Typography>
138
+ </Box>
139
+ <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
140
+ <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
141
+ <Typography sx={{ color: 'var(--theme-text-secondary)' }}>Node.js</Typography>
142
+ <Chip
143
+ label={diagnostics?.system.nodeVersion}
144
+ size="small"
145
+ sx={{ bgcolor: 'var(--theme-background)', color: 'var(--theme-text-primary)' }}
146
+ />
147
+ </Box>
148
+ <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
149
+ <Typography sx={{ color: 'var(--theme-text-secondary)' }}>Platform</Typography>
150
+ <Chip
151
+ label={diagnostics?.system.platform}
152
+ size="small"
153
+ sx={{ bgcolor: 'var(--theme-background)', color: 'var(--theme-text-primary)' }}
154
+ />
155
+ </Box>
156
+ <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
157
+ <Typography sx={{ color: 'var(--theme-text-secondary)' }}>Architecture</Typography>
158
+ <Chip
159
+ label={diagnostics?.system.arch}
160
+ size="small"
161
+ sx={{ bgcolor: 'var(--theme-background)', color: 'var(--theme-text-primary)' }}
162
+ />
163
+ </Box>
164
+ </Box>
165
+ </CardContent>
166
+ </Card>
167
+ </Grid>
168
+
169
+ {/* Memory Usage */}
170
+ <Grid size={{ xs: 12, md: 6 }}>
171
+ <Card sx={{ bgcolor: 'var(--theme-surface)', height: '100%' }}>
172
+ <CardContent>
173
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
174
+ <MemoryIcon sx={{ color: 'var(--theme-warning)' }} />
175
+ <Typography variant="h6" sx={{ color: 'var(--theme-text-primary)' }}>
176
+ Memory Usage
177
+ </Typography>
178
+ </Box>
179
+ <Box sx={{ mb: 2 }}>
180
+ <Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 1 }}>
181
+ <Typography sx={{ color: 'var(--theme-text-secondary)' }}>
182
+ Heap Used
183
+ </Typography>
184
+ <Typography sx={{ color: 'var(--theme-text-primary)' }}>
185
+ {formatBytes(diagnostics?.system.memory.used || 0)}
186
+ </Typography>
187
+ </Box>
188
+ <LinearProgress
189
+ variant="determinate"
190
+ value={memoryUsedPercent}
191
+ sx={{
192
+ height: 8,
193
+ borderRadius: 4,
194
+ bgcolor: 'var(--theme-background)',
195
+ '& .MuiLinearProgress-bar': {
196
+ bgcolor: memoryUsedPercent > 80 ? 'var(--theme-error)' : 'var(--theme-warning)',
197
+ borderRadius: 4,
198
+ },
199
+ }}
200
+ />
201
+ </Box>
202
+ <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
203
+ <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
204
+ <Typography sx={{ color: 'var(--theme-text-secondary)' }}>Heap Total</Typography>
205
+ <Typography sx={{ color: 'var(--theme-text-primary)' }}>
206
+ {formatBytes(diagnostics?.system.memory.total || 0)}
207
+ </Typography>
208
+ </Box>
209
+ <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
210
+ <Typography sx={{ color: 'var(--theme-text-secondary)' }}>Heap Free</Typography>
211
+ <Typography sx={{ color: 'var(--theme-text-primary)' }}>
212
+ {formatBytes(diagnostics?.system.memory.free || 0)}
213
+ </Typography>
214
+ </Box>
215
+ </Box>
216
+ </CardContent>
217
+ </Card>
218
+ </Grid>
219
+
220
+ {/* Service Info */}
221
+ <Grid size={{ xs: 12, md: 6 }}>
222
+ <Card sx={{ bgcolor: 'var(--theme-surface)', height: '100%' }}>
223
+ <CardContent>
224
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
225
+ <StorageIcon sx={{ color: 'var(--theme-info)' }} />
226
+ <Typography variant="h6" sx={{ color: 'var(--theme-text-primary)' }}>
227
+ Service Info
228
+ </Typography>
229
+ </Box>
230
+ <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
231
+ <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
232
+ <Typography sx={{ color: 'var(--theme-text-secondary)' }}>Product</Typography>
233
+ <Typography sx={{ color: 'var(--theme-text-primary)' }}>
234
+ {diagnostics?.product}
235
+ </Typography>
236
+ </Box>
237
+ <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
238
+ <Typography sx={{ color: 'var(--theme-text-secondary)' }}>Version</Typography>
239
+ <Chip
240
+ label={diagnostics?.version || 'N/A'}
241
+ size="small"
242
+ sx={{ bgcolor: 'var(--theme-primary)20', color: 'var(--theme-primary)' }}
243
+ />
244
+ </Box>
245
+ <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
246
+ <Typography sx={{ color: 'var(--theme-text-secondary)' }}>Timestamp</Typography>
247
+ <Typography sx={{ color: 'var(--theme-text-primary)', fontSize: '0.875rem' }}>
248
+ {diagnostics?.timestamp ? new Date(diagnostics.timestamp).toLocaleString() : 'N/A'}
249
+ </Typography>
250
+ </Box>
251
+ </Box>
252
+ </CardContent>
253
+ </Card>
254
+ </Grid>
255
+
256
+ {/* Uptime */}
257
+ <Grid size={{ xs: 12, md: 6 }}>
258
+ <Card sx={{ bgcolor: 'var(--theme-surface)', height: '100%' }}>
259
+ <CardContent>
260
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}>
261
+ <AccessTimeIcon sx={{ color: 'var(--theme-success)' }} />
262
+ <Typography variant="h6" sx={{ color: 'var(--theme-text-primary)' }}>
263
+ Uptime
264
+ </Typography>
265
+ </Box>
266
+ <Typography variant="h3" sx={{ color: 'var(--theme-success)', mb: 1 }}>
267
+ {formatUptime(diagnostics?.uptime || 0)}
268
+ </Typography>
269
+ <Typography sx={{ color: 'var(--theme-text-secondary)' }}>
270
+ Service has been running without interruption
271
+ </Typography>
272
+ </CardContent>
273
+ </Card>
274
+ </Grid>
275
+
276
+ {/* Raw JSON */}
277
+ <Grid size={{ xs: 12 }}>
278
+ <Card sx={{ bgcolor: 'var(--theme-surface)' }}>
279
+ <CardContent>
280
+ <Typography variant="h6" sx={{ color: 'var(--theme-text-primary)', mb: 2 }}>
281
+ Raw Diagnostics JSON (for AI agents)
282
+ </Typography>
283
+ <Box
284
+ component="pre"
285
+ sx={{
286
+ bgcolor: 'var(--theme-background)',
287
+ p: 2,
288
+ borderRadius: 1,
289
+ overflow: 'auto',
290
+ maxHeight: 300,
291
+ color: 'var(--theme-text-primary)',
292
+ fontFamily: 'monospace',
293
+ fontSize: '0.75rem',
294
+ }}
295
+ >
296
+ {JSON.stringify(diagnostics, null, 2)}
297
+ </Box>
298
+ </CardContent>
299
+ </Card>
300
+ </Grid>
301
+ </Grid>
302
+
303
+ <Snackbar
304
+ open={snackbar.open}
305
+ autoHideDuration={2000}
306
+ onClose={() => setSnackbar({ ...snackbar, open: false })}
307
+ anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
308
+ >
309
+ <Alert severity="success" variant="filled">
310
+ {snackbar.message}
311
+ </Alert>
312
+ </Snackbar>
313
+ </Box>
314
+ );
315
+ }
@@ -0,0 +1,204 @@
1
+ import { useState, useEffect } from 'react';
2
+ import {
3
+ Box,
4
+ Card,
5
+ CardContent,
6
+ Typography,
7
+ Table,
8
+ TableBody,
9
+ TableCell,
10
+ TableContainer,
11
+ TableHead,
12
+ TableRow,
13
+ Chip,
14
+ CircularProgress,
15
+ LinearProgress,
16
+ } from '@mui/material';
17
+ import CheckCircleIcon from '@mui/icons-material/CheckCircle';
18
+ import ErrorIcon from '@mui/icons-material/Error';
19
+ import WarningIcon from '@mui/icons-material/Warning';
20
+ import { api, HealthResponse } from '../api/controlPanelApi';
21
+
22
+ function getStatusIcon(status: string, size = 20) {
23
+ switch (status) {
24
+ case 'healthy':
25
+ return <CheckCircleIcon sx={{ color: 'var(--theme-success)', fontSize: size }} />;
26
+ case 'degraded':
27
+ return <WarningIcon sx={{ color: 'var(--theme-warning)', fontSize: size }} />;
28
+ case 'unhealthy':
29
+ return <ErrorIcon sx={{ color: 'var(--theme-error)', fontSize: size }} />;
30
+ default:
31
+ return <CircularProgress size={size} />;
32
+ }
33
+ }
34
+
35
+ function getStatusColor(status: string): string {
36
+ switch (status) {
37
+ case 'healthy':
38
+ return 'var(--theme-success)';
39
+ case 'degraded':
40
+ return 'var(--theme-warning)';
41
+ case 'unhealthy':
42
+ return 'var(--theme-error)';
43
+ default:
44
+ return 'var(--theme-text-secondary)';
45
+ }
46
+ }
47
+
48
+ function formatLatency(latency?: number): string {
49
+ if (latency === undefined) return '-';
50
+ if (latency < 1000) return `${latency}ms`;
51
+ return `${(latency / 1000).toFixed(2)}s`;
52
+ }
53
+
54
+ export function HealthPage() {
55
+ const [health, setHealth] = useState<HealthResponse | null>(null);
56
+ const [loading, setLoading] = useState(true);
57
+ const [error, setError] = useState<string | null>(null);
58
+
59
+ useEffect(() => {
60
+ const fetchHealth = async () => {
61
+ try {
62
+ const data = await api.getHealth();
63
+ setHealth(data);
64
+ setError(null);
65
+ } catch (err) {
66
+ setError(err instanceof Error ? err.message : 'Failed to fetch health');
67
+ } finally {
68
+ setLoading(false);
69
+ }
70
+ };
71
+
72
+ fetchHealth();
73
+ const interval = setInterval(fetchHealth, 5000);
74
+ return () => clearInterval(interval);
75
+ }, []);
76
+
77
+ if (loading) {
78
+ return (
79
+ <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '50vh' }}>
80
+ <CircularProgress />
81
+ </Box>
82
+ );
83
+ }
84
+
85
+ if (error) {
86
+ return (
87
+ <Card sx={{ bgcolor: 'var(--theme-surface)', border: '1px solid var(--theme-error)' }}>
88
+ <CardContent>
89
+ <Typography color="error">{error}</Typography>
90
+ </CardContent>
91
+ </Card>
92
+ );
93
+ }
94
+
95
+ const healthChecks = health ? Object.entries(health.checks) : [];
96
+ const healthyCount = healthChecks.filter(([, c]) => c.status === 'healthy').length;
97
+ const totalCount = healthChecks.length;
98
+ const healthPercentage = totalCount > 0 ? (healthyCount / totalCount) * 100 : 0;
99
+
100
+ return (
101
+ <Box>
102
+ <Typography variant="h4" sx={{ mb: 1, color: 'var(--theme-text-primary)' }}>
103
+ Health Checks
104
+ </Typography>
105
+ <Typography variant="body2" sx={{ mb: 4, color: 'var(--theme-text-secondary)' }}>
106
+ Detailed view of all service health checks
107
+ </Typography>
108
+
109
+ {/* Summary Card */}
110
+ <Card sx={{ mb: 4, bgcolor: 'var(--theme-surface)' }}>
111
+ <CardContent>
112
+ <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
113
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
114
+ {getStatusIcon(health?.status || 'unknown', 32)}
115
+ <Box>
116
+ <Typography variant="h5" sx={{ color: 'var(--theme-text-primary)' }}>
117
+ Overall Status: {health?.status?.charAt(0).toUpperCase()}{health?.status?.slice(1)}
118
+ </Typography>
119
+ <Typography variant="body2" sx={{ color: 'var(--theme-text-secondary)' }}>
120
+ {healthyCount} of {totalCount} checks passing
121
+ </Typography>
122
+ </Box>
123
+ </Box>
124
+ <Typography variant="h3" sx={{ color: getStatusColor(health?.status || 'unknown') }}>
125
+ {healthPercentage.toFixed(0)}%
126
+ </Typography>
127
+ </Box>
128
+ <LinearProgress
129
+ variant="determinate"
130
+ value={healthPercentage}
131
+ sx={{
132
+ height: 8,
133
+ borderRadius: 4,
134
+ bgcolor: 'var(--theme-background)',
135
+ '& .MuiLinearProgress-bar': {
136
+ bgcolor: getStatusColor(health?.status || 'unknown'),
137
+ borderRadius: 4,
138
+ },
139
+ }}
140
+ />
141
+ </CardContent>
142
+ </Card>
143
+
144
+ {/* Health Checks Table */}
145
+ <Card sx={{ bgcolor: 'var(--theme-surface)' }}>
146
+ <TableContainer>
147
+ <Table>
148
+ <TableHead>
149
+ <TableRow>
150
+ <TableCell sx={{ color: 'var(--theme-text-secondary)', borderColor: 'var(--theme-border)' }}>
151
+ Check
152
+ </TableCell>
153
+ <TableCell sx={{ color: 'var(--theme-text-secondary)', borderColor: 'var(--theme-border)' }}>
154
+ Status
155
+ </TableCell>
156
+ <TableCell sx={{ color: 'var(--theme-text-secondary)', borderColor: 'var(--theme-border)' }}>
157
+ Latency
158
+ </TableCell>
159
+ <TableCell sx={{ color: 'var(--theme-text-secondary)', borderColor: 'var(--theme-border)' }}>
160
+ Last Checked
161
+ </TableCell>
162
+ <TableCell sx={{ color: 'var(--theme-text-secondary)', borderColor: 'var(--theme-border)' }}>
163
+ Error
164
+ </TableCell>
165
+ </TableRow>
166
+ </TableHead>
167
+ <TableBody>
168
+ {healthChecks.map(([name, check]) => (
169
+ <TableRow key={name}>
170
+ <TableCell sx={{ color: 'var(--theme-text-primary)', borderColor: 'var(--theme-border)' }}>
171
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
172
+ {getStatusIcon(check.status)}
173
+ <Typography fontWeight={500}>{name}</Typography>
174
+ </Box>
175
+ </TableCell>
176
+ <TableCell sx={{ borderColor: 'var(--theme-border)' }}>
177
+ <Chip
178
+ label={check.status}
179
+ size="small"
180
+ sx={{
181
+ bgcolor: getStatusColor(check.status) + '20',
182
+ color: getStatusColor(check.status),
183
+ textTransform: 'capitalize',
184
+ }}
185
+ />
186
+ </TableCell>
187
+ <TableCell sx={{ color: 'var(--theme-text-primary)', borderColor: 'var(--theme-border)' }}>
188
+ {formatLatency(check.latency)}
189
+ </TableCell>
190
+ <TableCell sx={{ color: 'var(--theme-text-secondary)', borderColor: 'var(--theme-border)' }}>
191
+ {new Date(check.lastChecked).toLocaleTimeString()}
192
+ </TableCell>
193
+ <TableCell sx={{ color: 'var(--theme-error)', borderColor: 'var(--theme-border)' }}>
194
+ {check.error || '-'}
195
+ </TableCell>
196
+ </TableRow>
197
+ ))}
198
+ </TableBody>
199
+ </Table>
200
+ </TableContainer>
201
+ </Card>
202
+ </Box>
203
+ );
204
+ }