Fix jwt-token
This commit is contained in:
@@ -98,35 +98,90 @@ const System = () => {
|
||||
)
|
||||
}
|
||||
|
||||
const ContainerCard = ({ name, metrics }) => (
|
||||
<div className="bg-gray-50 rounded-lg p-4 border">
|
||||
<h4 className="font-medium text-gray-900 mb-3 truncate" title={name}>
|
||||
const ContainerCard = ({ name, metrics }) => {
|
||||
const getTypeIcon = (type) => {
|
||||
switch (type) {
|
||||
case 'database': return '🗄️';
|
||||
case 'cache': return '⚡';
|
||||
case 'proxy': return '🌐';
|
||||
case 'application': return '📱';
|
||||
case 'logging': return '📋';
|
||||
case 'monitoring': return '📊';
|
||||
default: return '📦';
|
||||
}
|
||||
};
|
||||
|
||||
const getTypeColor = (type) => {
|
||||
switch (type) {
|
||||
case 'database': return 'border-l-blue-500';
|
||||
case 'cache': return 'border-l-yellow-500';
|
||||
case 'proxy': return 'border-l-green-500';
|
||||
case 'application': return 'border-l-purple-500';
|
||||
case 'logging': return 'border-l-orange-500';
|
||||
case 'monitoring': return 'border-l-red-500';
|
||||
default: return 'border-l-gray-500';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`bg-gray-50 rounded-lg p-4 border-l-4 ${getTypeColor(metrics.type)} border border-gray-200`}>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h4 className="font-medium text-gray-900 truncate" title={name}>
|
||||
<span className="mr-2">{getTypeIcon(metrics.type)}</span>
|
||||
{name.replace('drone-detection-', '').replace('uamils-', '')}
|
||||
</h4>
|
||||
{metrics.error || metrics.status === 'health_check_failed' ? (
|
||||
{metrics.type && (
|
||||
<span className="text-xs bg-gray-200 text-gray-600 px-2 py-1 rounded-full">
|
||||
{metrics.type}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{metrics.error || metrics.status === 'health_check_failed' || metrics.status === 'unreachable' ? (
|
||||
<div className="text-sm text-red-600">
|
||||
<div className="font-medium">
|
||||
{metrics.status === 'health_check_failed' ? 'Health Check Failed' : 'Not Available'}
|
||||
{metrics.status === 'health_check_failed' ? 'Health Check Failed' :
|
||||
metrics.status === 'unreachable' ? 'Unreachable' :
|
||||
metrics.status === 'timeout' ? 'Timeout' : 'Not Available'}
|
||||
</div>
|
||||
<div className="text-xs mt-1">{metrics.error || metrics.message}</div>
|
||||
</div>
|
||||
) : metrics.source === 'docker_compose' ? (
|
||||
) : metrics.status === 'responding' ? (
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-500">Status</span>
|
||||
<span className={`font-medium ${metrics.status === 'running' ? 'text-green-600' : 'text-red-600'}`}>
|
||||
<span className="font-medium text-green-600">Responding</span>
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
Basic connectivity confirmed
|
||||
</div>
|
||||
</div>
|
||||
) : metrics.source === 'docker_compose' || metrics.source === 'process_list' ? (
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-500">Status</span>
|
||||
<span className={`font-medium ${
|
||||
metrics.status === 'running' ? 'text-green-600' :
|
||||
metrics.status === 'Up' ? 'text-green-600' : 'text-red-600'
|
||||
}`}>
|
||||
{metrics.status}
|
||||
</span>
|
||||
</div>
|
||||
{metrics.health && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-500">Health</span>
|
||||
<span className="font-medium">{metrics.health}</span>
|
||||
</div>
|
||||
)}
|
||||
{metrics.ports && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-500">Ports</span>
|
||||
<span className="font-medium text-xs">{metrics.ports || 'N/A'}</span>
|
||||
<span className="font-medium text-xs">{metrics.ports}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="text-xs text-blue-600 mt-2">
|
||||
Source: {metrics.source.replace('_', ' ').toUpperCase()}
|
||||
</div>
|
||||
<div className="text-xs text-blue-600 mt-2">Source: Docker Compose</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2 text-sm">
|
||||
@@ -154,7 +209,8 @@ const System = () => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const SSLCard = ({ domain, ssl }) => (
|
||||
<div className="bg-gray-50 rounded-lg p-4 border">
|
||||
@@ -336,11 +392,39 @@ const System = () => {
|
||||
<div className="text-sm mt-1">{systemInfo.containers.message}</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
{/* Group containers by type */}
|
||||
{['application', 'database', 'cache', 'proxy', 'logging', 'monitoring', 'unknown'].map(type => {
|
||||
const containersOfType = Object.entries(systemInfo.containers).filter(([name, metrics]) =>
|
||||
metrics.type === type || (type === 'unknown' && !metrics.type)
|
||||
);
|
||||
|
||||
if (containersOfType.length === 0) return null;
|
||||
|
||||
const typeLabels = {
|
||||
application: '📱 Application Services',
|
||||
database: '🗄️ Database Services',
|
||||
cache: '⚡ Cache Services',
|
||||
proxy: '🌐 Proxy & Load Balancers',
|
||||
logging: '📋 Logging Services',
|
||||
monitoring: '📊 Monitoring Services',
|
||||
unknown: '📦 Other Services'
|
||||
};
|
||||
|
||||
return (
|
||||
<div key={type}>
|
||||
<h4 className="text-sm font-medium text-gray-700 mb-3 border-b pb-2">
|
||||
{typeLabels[type]} ({containersOfType.length})
|
||||
</h4>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{Object.entries(systemInfo.containers).map(([name, metrics]) => (
|
||||
{containersOfType.map(([name, metrics]) => (
|
||||
<ContainerCard key={name} name={name} metrics={metrics} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</StatusCard>
|
||||
</div>
|
||||
|
||||
@@ -132,78 +132,170 @@ router.get('/system-info', async (req, res) => {
|
||||
let containerMetrics = {};
|
||||
|
||||
const containerEndpoints = [
|
||||
{ name: 'drone-detection-backend', url: 'http://drone-detection-backend:3000/health/metrics' },
|
||||
{ name: 'drone-detection-frontend', url: 'http://drone-detection-frontend:80/health/metrics' },
|
||||
{ name: 'drone-detection-management', url: 'http://drone-detection-management:3001/health/metrics' }
|
||||
// Application containers with custom health endpoints
|
||||
{ name: 'drone-detection-backend', url: 'http://drone-detection-backend:3000/health/metrics', type: 'app' },
|
||||
{ name: 'drone-detection-frontend', url: 'http://drone-detection-frontend:80/health/metrics', type: 'app' },
|
||||
{ name: 'drone-detection-management', url: 'http://drone-detection-management:3001/health/metrics', type: 'app' },
|
||||
|
||||
// Database containers - try standard health endpoints
|
||||
{ name: 'postgres', url: 'http://postgres:5432', type: 'database' },
|
||||
{ name: 'redis', url: 'http://redis:6379', type: 'cache' },
|
||||
|
||||
// Infrastructure containers
|
||||
{ name: 'nginx', url: 'http://nginx:80/nginx_status', type: 'proxy' },
|
||||
{ name: 'nginx-proxy-manager', url: 'http://nginx-proxy-manager:81/api/health', type: 'proxy' }
|
||||
];
|
||||
|
||||
// Try internal container health endpoints first
|
||||
try {
|
||||
const fetch = require('node-fetch');
|
||||
const https = require('https');
|
||||
const http = require('http');
|
||||
|
||||
const healthChecks = await Promise.allSettled(
|
||||
containerEndpoints.map(async ({ name, url }) => {
|
||||
const response = await fetch(url, { timeout: 3000 });
|
||||
const metrics = await response.json();
|
||||
return { name, metrics };
|
||||
containerEndpoints.map(async ({ name, url, type }) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const urlObj = new URL(url);
|
||||
const client = urlObj.protocol === 'https:' ? https : http;
|
||||
|
||||
const req = client.request({
|
||||
hostname: urlObj.hostname,
|
||||
port: urlObj.port,
|
||||
path: urlObj.pathname,
|
||||
method: 'GET',
|
||||
timeout: 3000
|
||||
}, (res) => {
|
||||
let data = '';
|
||||
res.on('data', chunk => data += chunk);
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const metrics = res.headers['content-type']?.includes('application/json')
|
||||
? JSON.parse(data)
|
||||
: { status: 'healthy', raw: data };
|
||||
resolve({ name, metrics: { ...metrics, type, source: 'health_endpoint' } });
|
||||
} catch (e) {
|
||||
resolve({ name, metrics: { status: 'responding', type, source: 'basic_check', data: data.substring(0, 100) } });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (error) => {
|
||||
resolve({ name, metrics: { status: 'unreachable', type, error: error.message, source: 'health_check_failed' } });
|
||||
});
|
||||
|
||||
req.on('timeout', () => {
|
||||
req.destroy();
|
||||
resolve({ name, metrics: { status: 'timeout', type, source: 'health_check_failed' } });
|
||||
});
|
||||
|
||||
req.end();
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
healthChecks.forEach((result, index) => {
|
||||
const containerName = containerEndpoints[index].name;
|
||||
healthChecks.forEach((result) => {
|
||||
if (result.status === 'fulfilled') {
|
||||
containerMetrics[containerName] = result.value.metrics;
|
||||
} else {
|
||||
containerMetrics[containerName] = {
|
||||
status: 'health_check_failed',
|
||||
error: result.reason?.message || 'Health endpoint unavailable'
|
||||
};
|
||||
containerMetrics[result.value.name] = result.value.metrics;
|
||||
}
|
||||
});
|
||||
|
||||
} catch (healthError) {
|
||||
console.log('Container health checks failed, trying Docker stats...');
|
||||
}
|
||||
|
||||
// Fallback to Docker stats if health endpoints fail
|
||||
// Fallback to Docker stats for ALL containers (not just our apps)
|
||||
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);
|
||||
|
||||
containerMetrics = lines.reduce((acc, line) => {
|
||||
lines.forEach(line => {
|
||||
const [container, cpu, memUsage, memPerc, netIO, blockIO] = line.split('\t');
|
||||
if (container.includes('drone-detection') || container.includes('uamils')) {
|
||||
acc[container] = {
|
||||
if (container && cpu) {
|
||||
// Determine container type
|
||||
let type = 'unknown';
|
||||
if (container.includes('postgres') || container.includes('mysql') || container.includes('mongo')) type = 'database';
|
||||
else if (container.includes('redis') || container.includes('memcached')) type = 'cache';
|
||||
else if (container.includes('nginx') || container.includes('proxy') || container.includes('traefik')) type = 'proxy';
|
||||
else if (container.includes('drone-detection') || container.includes('uamils')) type = 'application';
|
||||
else if (container.includes('elasticsearch') || container.includes('kibana') || container.includes('logstash')) type = 'logging';
|
||||
else if (container.includes('prometheus') || container.includes('grafana')) type = 'monitoring';
|
||||
|
||||
// If we don't have health endpoint data, use docker stats
|
||||
if (!containerMetrics[container]) {
|
||||
containerMetrics[container] = {
|
||||
cpu: cpu,
|
||||
memory: { usage: memUsage, percentage: memPerc },
|
||||
network: netIO,
|
||||
disk: blockIO,
|
||||
type: type,
|
||||
source: 'docker_stats'
|
||||
};
|
||||
} else {
|
||||
// Enhance existing health data with docker stats
|
||||
containerMetrics[container] = {
|
||||
...containerMetrics[container],
|
||||
cpu: cpu,
|
||||
memory: { usage: memUsage, percentage: memPerc },
|
||||
network: netIO,
|
||||
disk: blockIO
|
||||
};
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
});
|
||||
} catch (dockerError) {
|
||||
console.log('Docker stats failed, trying docker compose...');
|
||||
|
||||
// Try container inspection via docker compose
|
||||
try {
|
||||
const { stdout: composeStatus } = await execAsync('docker-compose ps --format json');
|
||||
const containers = JSON.parse(`[${composeStatus.split('\n').filter(line => line.trim()).join(',')}]`);
|
||||
|
||||
containerMetrics = containers.reduce((acc, container) => {
|
||||
if (container.Name && (container.Name.includes('drone-detection') || container.Name.includes('uamils'))) {
|
||||
acc[container.Name] = {
|
||||
containers.forEach(container => {
|
||||
if (container.Name && !containerMetrics[container.Name]) {
|
||||
let type = 'unknown';
|
||||
const name = container.Name.toLowerCase();
|
||||
if (name.includes('postgres') || name.includes('mysql') || name.includes('mongo')) type = 'database';
|
||||
else if (name.includes('redis') || name.includes('memcached')) type = 'cache';
|
||||
else if (name.includes('nginx') || name.includes('proxy')) type = 'proxy';
|
||||
else if (name.includes('drone-detection') || name.includes('uamils')) type = 'application';
|
||||
|
||||
containerMetrics[container.Name] = {
|
||||
status: container.State,
|
||||
health: container.Health || 'unknown',
|
||||
ports: container.Ports,
|
||||
type: type,
|
||||
source: 'docker_compose'
|
||||
};
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
});
|
||||
} catch (composeError) {
|
||||
// Final fallback - try to detect containers via process list
|
||||
try {
|
||||
const { stdout: processes } = await execAsync('ps aux | grep -E "(postgres|redis|nginx|docker)" | grep -v grep');
|
||||
const processLines = processes.split('\n').filter(line => line.trim());
|
||||
|
||||
const detectedServices = {};
|
||||
processLines.forEach(line => {
|
||||
if (line.includes('postgres')) detectedServices['postgres-process'] = { status: 'running', type: 'database', source: 'process_list' };
|
||||
if (line.includes('redis')) detectedServices['redis-process'] = { status: 'running', type: 'cache', source: 'process_list' };
|
||||
if (line.includes('nginx')) detectedServices['nginx-process'] = { status: 'running', type: 'proxy', source: 'process_list' };
|
||||
});
|
||||
|
||||
if (Object.keys(detectedServices).length > 0) {
|
||||
containerMetrics = { ...containerMetrics, ...detectedServices };
|
||||
} else {
|
||||
containerMetrics = {
|
||||
error: 'All container monitoring methods failed',
|
||||
attempts: ['health_endpoints', 'docker_stats', 'docker_compose'],
|
||||
attempts: ['health_endpoints', 'docker_stats', 'docker_compose', 'process_list'],
|
||||
lastError: composeError.message
|
||||
};
|
||||
}
|
||||
} catch (processError) {
|
||||
containerMetrics = {
|
||||
error: 'All container monitoring methods failed',
|
||||
attempts: ['health_endpoints', 'docker_stats', 'docker_compose', 'process_list'],
|
||||
lastError: processError.message
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user