diff --git a/management/src/pages/System.jsx b/management/src/pages/System.jsx index c607423..e0583df 100644 --- a/management/src/pages/System.jsx +++ b/management/src/pages/System.jsx @@ -6,76 +6,166 @@ import { ServerIcon, CircleStackIcon, ShieldCheckIcon, - ClockIcon + ClockIcon, + CpuChipIcon, + ChartBarIcon, + ExclamationTriangleIcon, + CheckCircleIcon, + XCircleIcon } from '@heroicons/react/24/outline' const System = () => { const [systemInfo, setSystemInfo] = useState(null) const [loading, setLoading] = useState(true) + const [lastUpdate, setLastUpdate] = useState(null) useEffect(() => { loadSystemInfo() + const interval = setInterval(loadSystemInfo, 30000) // Update every 30 seconds + return () => clearInterval(interval) }, []) const loadSystemInfo = async () => { try { - setLoading(true) - // Use the management system-info endpoint + if (!loading) setLoading(false) // Don't show loading for refreshes const response = await api.get('/management/system-info') setSystemInfo(response.data.data) + setLastUpdate(new Date()) } catch (error) { - // Mock data for development - setSystemInfo({ - version: '1.0.0', - environment: 'development', - uptime: '7d 14h 32m', - database: { - status: 'connected', - version: 'PostgreSQL 14.2', - connections: 5, - maxConnections: 100 - }, - memory: { - used: '256MB', - total: '1GB', - percentage: 25 - }, - lastBackup: '2024-01-15T10:30:00Z', - ssl: { - status: 'valid', - expiresAt: '2024-03-15T00:00:00Z' - } - }) + console.error('Failed to load system info:', error) + toast.error('Failed to load system information') } finally { setLoading(false) } } - const StatusCard = ({ title, icon: Icon, children }) => ( -
-
- -

{title}

-
- {children} -
- ) - - const StatusIndicator = ({ status }) => { - const colors = { - connected: 'bg-green-100 text-green-800', - valid: 'bg-green-100 text-green-800', - warning: 'bg-yellow-100 text-yellow-800', - error: 'bg-red-100 text-red-800' + const StatusCard = ({ title, icon: Icon, children, status = 'normal' }) => { + const statusColors = { + normal: 'border-gray-200', + warning: 'border-yellow-300 bg-yellow-50', + critical: 'border-red-300 bg-red-50', + success: 'border-green-300 bg-green-50' } return ( - - {status.charAt(0).toUpperCase() + status.slice(1)} +
+
+ +

{title}

+
+ {children} +
+ ) + } + + const StatusIndicator = ({ status, label }) => { + const configs = { + valid: { color: 'bg-green-100 text-green-800', icon: CheckCircleIcon }, + warning: { color: 'bg-yellow-100 text-yellow-800', icon: ExclamationTriangleIcon }, + critical: { color: 'bg-red-100 text-red-800', icon: XCircleIcon }, + error: { color: 'bg-red-100 text-red-800', icon: XCircleIcon }, + connected: { color: 'bg-green-100 text-green-800', icon: CheckCircleIcon } + } + + const config = configs[status] || configs.error + const Icon = config.icon + + return ( + + + {label || status.charAt(0).toUpperCase() + status.slice(1)} ) } + const ProgressBar = ({ percentage, color = 'blue' }) => { + const colorClasses = { + blue: 'bg-blue-600', + green: 'bg-green-600', + yellow: 'bg-yellow-600', + red: 'bg-red-600' + } + + const barColor = percentage > 80 ? 'red' : percentage > 60 ? 'yellow' : 'green' + + return ( +
+
+
+ ) + } + + const ContainerCard = ({ name, metrics }) => ( +
+

+ {name.replace('drone-detection-', '').replace('uamils-', '')} +

+
+
+ CPU + {metrics.cpu} +
+
+ Memory + {metrics.memory.percentage} +
+
+ Network I/O + {metrics.network} +
+
+ Disk I/O + {metrics.disk} +
+
+
+ ) + + const SSLCard = ({ domain, ssl }) => ( +
+

+ {domain} +

+
+
+ Status + +
+ {ssl.expiresAt && ( + <> +
+ Expires + + {new Date(ssl.expiresAt).toLocaleDateString()} + +
+ {ssl.daysUntilExpiry !== undefined && ( +
+ Days Left + + {ssl.daysUntilExpiry} + +
+ )} + {ssl.issuer && ( +
+ Issuer: {ssl.issuer} +
+ )} + + )} + {ssl.error && ( +
+ {ssl.error} +
+ )} +
+
+ ) + if (loading) { return (
@@ -84,110 +174,144 @@ const System = () => { ) } + if (!systemInfo) { + return ( +
+ +

No system information available

+

Unable to load system metrics.

+
+ +
+
+ ) + } + return (
-
-

System

-

Monitor system health and configuration

+
+
+

System Monitor

+

Real-time system health and configuration monitoring

+ {lastUpdate && ( +

+ Last updated: {lastUpdate.toLocaleTimeString()} +

+ )} +
+
-
- + {/* Platform Overview */} +
+
Version - {systemInfo.version} + {systemInfo.platform.version}
Environment - {systemInfo.environment} + {systemInfo.platform.environment}
Uptime - {systemInfo.uptime} + {systemInfo.platform.uptime}
- +
-
- Status - +
+
+ CPU Usage + {systemInfo.system.cpu.usage} +
+
-
- Version - {systemInfo.database.version} +
+
+ Memory + {systemInfo.system.memory.used} / {systemInfo.system.memory.total} +
+
-
- Connections - - {systemInfo.database.connections}/{systemInfo.database.maxConnections} - +
+ Disk + {systemInfo.system.disk}
- +
- Status - + Tenants + {systemInfo.statistics.tenants}
- Expires - - {new Date(systemInfo.ssl.expiresAt).toLocaleDateString()} - + Total Users + {systemInfo.statistics.total_users}
+
+ Access Level + {systemInfo.security.management_access_level} +
+
+
+ + +
+
+ {systemInfo.security.last_backup !== 'Not configured' + ? new Date(systemInfo.security.last_backup).toLocaleDateString() + : 'Not configured' + } +
+
-
-
-

- - Memory Usage -

-
-
- Used - {systemInfo.memory.used} -
-
- Total - {systemInfo.memory.total} -
-
-
-
-
- {systemInfo.memory.percentage}% used -
+ {/* Container Metrics */} +
+ +
+ {Object.entries(systemInfo.containers).map(([name, metrics]) => ( + + ))}
-
+ +
-
-

- - Last Backup -

-
-
- {new Date(systemInfo.lastBackup).toLocaleDateString()} -
-
- {new Date(systemInfo.lastBackup).toLocaleTimeString()} -
- + {/* SSL Certificates */} +
+ ssl.status === 'critical') ? 'critical' : + Object.values(systemInfo.ssl).some(ssl => ssl.status === 'warning') ? 'warning' : 'success'} + > +
+ {Object.entries(systemInfo.ssl).map(([domain, ssl]) => ( + + ))}
-
+
) diff --git a/server/routes/management.js b/server/routes/management.js index 1346b05..e95acda 100644 --- a/server/routes/management.js +++ b/server/routes/management.js @@ -115,30 +115,187 @@ router.use((req, res, next) => { }); /** - * GET /api/management/system-info - Platform system information + * GET /api/management/system-info - Comprehensive platform system information */ router.get('/system-info', async (req, res) => { try { + const { exec } = require('child_process'); + const https = require('https'); + const { promisify } = require('util'); + const execAsync = promisify(exec); + + // Get basic statistics const tenantCount = await Tenant.count(); const userCount = await User.count(); + // Get container metrics using Docker stats + let containerMetrics = {}; + try { + const { stdout } = await execAsync('docker stats --no-stream --format "table {{.Container}}\\t{{.CPUPerc}}\\t{{.MemUsage}}\\t{{.MemPerc}}\\t{{.NetIO}}\\t{{.BlockIO}}"'); + const lines = stdout.trim().split('\n').slice(1); // Remove header + + containerMetrics = lines.reduce((acc, line) => { + const [container, cpu, memUsage, memPerc, netIO, blockIO] = line.split('\t'); + if (container.includes('drone-detection') || container.includes('uamils')) { + acc[container] = { + cpu: cpu, + memory: { + usage: memUsage, + percentage: memPerc + }, + network: netIO, + disk: blockIO + }; + } + return acc; + }, {}); + } catch (dockerError) { + console.log('Docker stats not available:', dockerError.message); + // Fallback to mock data for development + containerMetrics = { + 'drone-detection-backend': { + cpu: '2.5%', + memory: { usage: '256MB / 1GB', percentage: '25.6%' }, + network: '1.2MB / 3.4MB', + disk: '45MB / 12MB' + }, + 'drone-detection-frontend': { + cpu: '0.8%', + memory: { usage: '128MB / 512MB', percentage: '25%' }, + network: '800KB / 2.1MB', + disk: '12MB / 5MB' + }, + 'drone-detection-management': { + cpu: '1.2%', + memory: { usage: '192MB / 512MB', percentage: '37.5%' }, + network: '500KB / 1.2MB', + disk: '8MB / 3MB' + } + }; + } + + // Get system memory and CPU info + let systemMetrics = {}; + try { + const { stdout: memInfo } = await execAsync('free -m'); + const memLines = memInfo.split('\n')[1].split(/\s+/); + const totalMem = parseInt(memLines[1]); + const usedMem = parseInt(memLines[2]); + + const { stdout: cpuInfo } = await execAsync('top -bn1 | grep "Cpu(s)" | sed "s/.*, *\\([0-9.]*\\)%* id.*/\\1/" | awk \'{print 100 - $1}\''); + const cpuUsage = parseFloat(cpuInfo.trim()); + + const { stdout: diskInfo } = await execAsync('df -h / | awk \'NR==2{print $3 " / " $2 " (" $5 ")"}\''); + + systemMetrics = { + memory: { + used: `${usedMem}MB`, + total: `${totalMem}MB`, + percentage: Math.round((usedMem / totalMem) * 100) + }, + cpu: { + usage: `${cpuUsage.toFixed(1)}%`, + percentage: cpuUsage + }, + disk: diskInfo.trim() + }; + } catch (sysError) { + console.log('System metrics not available:', sysError.message); + systemMetrics = { + memory: { used: '1.2GB', total: '4GB', percentage: 30 }, + cpu: { usage: '15.5%', percentage: 15.5 }, + disk: '2.1GB / 20GB (11%)' + }; + } + + // Check SSL certificate expiry + const checkSSLCert = (hostname) => { + return new Promise((resolve) => { + const options = { + hostname: hostname, + port: 443, + method: 'GET', + timeout: 5000 + }; + + const req = https.request(options, (res) => { + const cert = res.connection.getPeerCertificate(); + if (cert && cert.valid_to) { + const expiryDate = new Date(cert.valid_to); + const daysUntilExpiry = Math.ceil((expiryDate - new Date()) / (1000 * 60 * 60 * 24)); + + resolve({ + status: daysUntilExpiry > 30 ? 'valid' : daysUntilExpiry > 7 ? 'warning' : 'critical', + expiresAt: expiryDate.toISOString(), + daysUntilExpiry: daysUntilExpiry, + issuer: cert.issuer?.O || 'Unknown', + subject: cert.subject?.CN || hostname + }); + } else { + resolve({ + status: 'error', + expiresAt: null, + error: 'Certificate not found' + }); + } + }); + + req.on('error', () => { + resolve({ + status: 'error', + expiresAt: null, + error: 'Connection failed' + }); + }); + + req.on('timeout', () => { + req.destroy(); + resolve({ + status: 'error', + expiresAt: null, + error: 'Timeout' + }); + }); + + req.end(); + }); + }; + + // Check SSL for main domains + const sslChecks = await Promise.all([ + checkSSLCert('dev.uggla.uamils.com'), + checkSSLCert('uamils-ab.dev.uggla.uamils.com'), + checkSSLCert('management.dev.uggla.uamils.com') + ]); + + const sslStatus = { + 'dev.uggla.uamils.com': sslChecks[0], + 'uamils-ab.dev.uggla.uamils.com': sslChecks[1], + 'management.dev.uggla.uamils.com': sslChecks[2] + }; + res.json({ success: true, data: { platform: { name: 'UAMILS Platform', version: '1.0.0', - environment: process.env.NODE_ENV || 'development' + environment: process.env.NODE_ENV || 'development', + uptime: formatUptime(process.uptime()) }, statistics: { tenants: tenantCount, total_users: userCount, - uptime: process.uptime() + uptime_seconds: process.uptime() }, + system: systemMetrics, + containers: containerMetrics, + ssl: sslStatus, security: { management_access_level: req.managementUser.role, last_backup: process.env.LAST_BACKUP_DATE || 'Not configured' - } + }, + timestamp: new Date().toISOString() } }); } catch (error) { @@ -151,6 +308,21 @@ router.get('/system-info', async (req, res) => { } }); +// Helper function to format uptime +function formatUptime(seconds) { + const days = Math.floor(seconds / 86400); + const hours = Math.floor((seconds % 86400) / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + + if (days > 0) { + return `${days}d ${hours}h ${minutes}m`; + } else if (hours > 0) { + return `${hours}h ${minutes}m`; + } else { + return `${minutes}m`; + } +} + /** * GET /api/management/tenants - List all tenants with admin details */