All checks were successful
continuous-integration/drone/push Build is passing
- Backend: rewrote containers/services/logs/metrics routes to use dockerode - docker-compose: mount /var/run/docker.sock, run backend as root - .drone.yml: sync docker-compose.yml from Gitea on deploy - Frontend: Containers page shows real data with wired start/stop/restart - Frontend: Services page shows Docker Compose stacks with health status - Frontend: Metrics page adds disk (docker df) and containers cards + chart legend - Frontend: Logs page replaces text input with container dropdown + auto-refresh
67 lines
2.0 KiB
TypeScript
67 lines
2.0 KiB
TypeScript
import { Router, Request, Response } from 'express';
|
|
import Docker from 'dockerode';
|
|
|
|
const router = Router();
|
|
const docker = new Docker({ socketPath: '/var/run/docker.sock' });
|
|
|
|
router.get('/', async (req: Request, res: Response) => {
|
|
try {
|
|
const list = await docker.listContainers({ all: true });
|
|
|
|
const projects = new Map<string, any[]>();
|
|
const standalone: any[] = [];
|
|
|
|
for (const c of list) {
|
|
const project = c.Labels?.['com.docker.compose.project'];
|
|
const service = c.Labels?.['com.docker.compose.service'];
|
|
if (project && service) {
|
|
if (!projects.has(project)) projects.set(project, []);
|
|
projects.get(project)!.push({
|
|
name: service,
|
|
container: c.Names[0]?.replace(/^\//, ''),
|
|
state: c.State,
|
|
status: c.Status,
|
|
image: c.Image,
|
|
});
|
|
} else {
|
|
standalone.push({
|
|
name: c.Names[0]?.replace(/^\//, '') ?? c.Id.slice(0, 12),
|
|
container: c.Names[0]?.replace(/^\//, ''),
|
|
state: c.State,
|
|
status: c.Status,
|
|
image: c.Image,
|
|
});
|
|
}
|
|
}
|
|
|
|
const stacks = [
|
|
...Array.from(projects.entries()).map(([project, svcs]) => ({
|
|
id: project,
|
|
name: project,
|
|
type: 'compose',
|
|
services: svcs,
|
|
running: svcs.filter(s => s.state === 'running').length,
|
|
total: svcs.length,
|
|
status: svcs.every(s => s.state === 'running') ? 'healthy'
|
|
: svcs.some(s => s.state === 'running') ? 'degraded' : 'stopped',
|
|
})),
|
|
...(standalone.length > 0 ? [{
|
|
id: 'standalone',
|
|
name: 'standalone',
|
|
type: 'standalone',
|
|
services: standalone,
|
|
running: standalone.filter(s => s.state === 'running').length,
|
|
total: standalone.length,
|
|
status: 'unknown',
|
|
}] : []),
|
|
];
|
|
|
|
res.json({ services: stacks });
|
|
} catch (error) {
|
|
console.error('Services error:', error);
|
|
res.status(500).json({ error: 'Failed to fetch services' });
|
|
}
|
|
});
|
|
|
|
export default router;
|