Files
server-manager/backend/src/routes/services.ts
Ernie Butcher 4233734759
All checks were successful
continuous-integration/drone/push Build is passing
feat: live Docker data — containers, services, logs, metrics via dockerode
- 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
2026-03-18 18:33:25 -04:00

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;