import os from 'node:os' import Site from '#models/site' import AuditLog from '#models/audit_log' import backupService from '#services/backup_service' import dockerService from '#services/docker_service' import metricsService from '#services/metrics_service' const CERT_WARNING_DAYS = 14 export default class OverviewController { async index() { const now = Math.floor(Date.now() / 1000) const [containers, sites, backups, recent, uptime] = await Promise.all([ dockerService.list().catch((error: Error) => error), Site.query().where('active', true).orderBy('domain'), backupService.list(), AuditLog.query().orderBy('id', 'desc').limit(8), metricsService.uptime(), ]) const docker = containers instanceof Error ? { ok: false as const, error: containers.message } : { ok: true as const, total: containers.length, running: containers.filter((c) => c.state === 'running').length, unhealthy: containers.filter((c) => c.health === 'unhealthy').map((c) => c.name), stopped: containers.filter((c) => c.state !== 'running').map((c) => c.name), } return { host: { latest: metricsService.latest, uptime, cpus: os.cpus().length, }, docker, sites: { total: sites.length, up: sites.filter((site) => site.lastOk === true).length, down: sites .filter((site) => site.lastOk === false) .map((site) => ({ id: site.id, domain: site.domain, error: site.lastError })), certSoon: sites .filter( (site) => site.certExpiresAt && site.certExpiresAt - now < CERT_WARNING_DAYS * 86_400 ) .map((site) => ({ id: site.id, domain: site.domain, days: Math.floor((site.certExpiresAt! - now) / 86_400), })), }, backups: { last: backups[0] ?? null, count: backups.length, schedule: backupService.schedule, }, recent: recent.map((entry) => ({ id: entry.id, action: entry.action, target: entry.target, status: entry.status, username: entry.username, createdAt: entry.createdAt.toISO(), })), } } }