refactor(routes): Phase 3.5 - standardize logs.js

This commit is contained in:
Krystie
2026-03-29 20:14:33 -07:00
parent 8b1492142f
commit cabcbcf98a
2 changed files with 50 additions and 35 deletions

View File

@@ -4,13 +4,23 @@ const fsp = require('fs').promises;
const path = require('path');
const { exists } = require('../fs-helpers');
const { paginate, parsePaginationParams } = require('../pagination');
const { NotFoundError } = require('../errors');
module.exports = function(ctx) {
/**
* Logs route factory
* @param {Object} deps - Explicit dependencies
* @param {Function} deps.asyncHandler - Async route handler wrapper
* @param {Object} deps.docker - Docker client
* @param {Object} deps.logDigest - Log digest manager (optional)
* @param {Object} deps.dockerMaintenance - Docker maintenance module (optional)
* @returns {express.Router}
*/
module.exports = function({ asyncHandler, docker, logDigest, dockerMaintenance }) {
const router = express.Router();
// List containers with logs
router.get('/logs/containers', ctx.asyncHandler(async (req, res) => {
const containers = await ctx.docker.client.listContainers({ all: true });
router.get('/logs/containers', asyncHandler(async (req, res) => {
const containers = await docker.client.listContainers({ all: true });
const containerList = containers.map(c => ({
id: c.Id.slice(0, 12),
name: c.Names[0]?.replace(/^\//, '') || 'unknown',
@@ -25,13 +35,13 @@ module.exports = function(ctx) {
}, 'logs-containers'));
// Get logs for a specific container
router.get('/logs/container/:id', ctx.asyncHandler(async (req, res) => {
router.get('/logs/container/:id', asyncHandler(async (req, res) => {
const containerId = req.params.id;
const tail = parseInt(req.query.tail) || 100;
const since = req.query.since || 0;
const timestamps = req.query.timestamps !== 'false';
const container = ctx.docker.client.getContainer(containerId);
const container = docker.client.getContainer(containerId);
let info;
try {
info = await container.inspect();
@@ -80,9 +90,9 @@ module.exports = function(ctx) {
}, 'logs-container'));
// Stream logs (SSE)
router.get('/logs/stream/:id', ctx.asyncHandler(async (req, res) => {
router.get('/logs/stream/:id', asyncHandler(async (req, res) => {
const containerId = req.params.id;
const container = ctx.docker.client.getContainer(containerId);
const container = docker.client.getContainer(containerId);
try {
await container.inspect();
} catch (err) {
@@ -129,7 +139,7 @@ module.exports = function(ctx) {
});
logStream.on('error', (err) => {
res.write(`data: ${JSON.stringify({ error: ctx.safeErrorMessage(err) })}\n\n`);
res.write(`data: ${JSON.stringify({ error: err.message || String(err) })}\n\n`);
res.end();
});
@@ -139,9 +149,9 @@ module.exports = function(ctx) {
}, 'logs-stream'));
// Get latest daily digest
router.get('/logs/digest/latest', ctx.asyncHandler(async (req, res) => {
if (!ctx.logDigest) return ctx.errorResponse(res, 503, 'Log digest not available');
const digest = await ctx.logDigest.getLatestDigest();
router.get('/logs/digest/latest', asyncHandler(async (req, res) => {
if (!logDigest) throw new Error('Log digest not available');
const digest = await logDigest.getLatestDigest();
if (!digest) {
return res.json({ success: true, digest: null, message: 'No digest available yet. First digest is generated at midnight.' });
}
@@ -149,63 +159,63 @@ module.exports = function(ctx) {
}, 'logs-digest-latest'));
// Get live digest data (today's accumulated stats)
router.get('/logs/digest/live', ctx.asyncHandler(async (req, res) => {
if (!ctx.logDigest) return ctx.errorResponse(res, 503, 'Log digest not available');
const live = ctx.logDigest.getLiveData();
router.get('/logs/digest/live', asyncHandler(async (req, res) => {
if (!logDigest) throw new Error('Log digest not available');
const live = logDigest.getLiveData();
res.json({ success: true, ...live });
}, 'logs-digest-live'));
// List available digest dates
router.get('/logs/digest/history', ctx.asyncHandler(async (req, res) => {
if (!ctx.logDigest) return ctx.errorResponse(res, 503, 'Log digest not available');
const dates = await ctx.logDigest.listDigests();
router.get('/logs/digest/history', asyncHandler(async (req, res) => {
if (!logDigest) throw new Error('Log digest not available');
const dates = await logDigest.listDigests();
res.json({ success: true, dates });
}, 'logs-digest-history'));
// Generate digest on demand (for today or a specific date)
router.post('/logs/digest/generate', ctx.asyncHandler(async (req, res) => {
if (!ctx.logDigest) return ctx.errorResponse(res, 503, 'Log digest not available');
router.post('/logs/digest/generate', asyncHandler(async (req, res) => {
if (!logDigest) throw new Error('Log digest not available');
const date = req.body.date || new Date().toISOString().slice(0, 10);
const digest = await ctx.logDigest.generateDailyDigest(date);
const digest = await logDigest.generateDailyDigest(date);
res.json({ success: true, digest });
}, 'logs-digest-generate'));
// Get digest for a specific date (JSON)
router.get('/logs/digest/:date', ctx.asyncHandler(async (req, res) => {
if (!ctx.logDigest) return ctx.errorResponse(res, 503, 'Log digest not available');
router.get('/logs/digest/:date', asyncHandler(async (req, res) => {
if (!logDigest) throw new Error('Log digest not available');
const { date } = req.params;
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
throw new ValidationError('Invalid date format. Use YYYY-MM-DD.');
}
const format = req.query.format || 'json';
if (format === 'text') {
const text = await ctx.logDigest.getDigestText(date);
if (!text) return ctx.errorResponse(res, 404, `No digest found for ${date}`);
const text = await logDigest.getDigestText(date);
if (!text) throw new NotFoundError(`Digest for ${date}`);
res.setHeader('Content-Type', 'text/plain');
return res.send(text);
}
const digest = await ctx.logDigest.getDigestByDate(date);
if (!digest) return ctx.errorResponse(res, 404, `No digest found for ${date}`);
const digest = await logDigest.getDigestByDate(date);
if (!digest) throw new NotFoundError(`Digest for ${date}`);
res.json({ success: true, digest });
}, 'logs-digest-date'));
// Get Docker disk usage snapshot
router.get('/logs/docker-disk', ctx.asyncHandler(async (req, res) => {
if (!ctx.dockerMaintenance) return ctx.errorResponse(res, 503, 'Docker maintenance not available');
const diskUsage = await ctx.dockerMaintenance.getDiskUsage();
const status = ctx.dockerMaintenance.getStatus();
router.get('/logs/docker-disk', asyncHandler(async (req, res) => {
if (!dockerMaintenance) throw new Error('Docker maintenance not available');
const diskUsage = await dockerMaintenance.getDiskUsage();
const status = dockerMaintenance.getStatus();
res.json({ success: true, diskUsage, maintenance: status });
}, 'logs-docker-disk'));
// Trigger Docker maintenance manually
router.post('/logs/docker-maintenance', ctx.asyncHandler(async (req, res) => {
if (!ctx.dockerMaintenance) return ctx.errorResponse(res, 503, 'Docker maintenance not available');
const result = await ctx.dockerMaintenance.runMaintenance();
router.post('/logs/docker-maintenance', asyncHandler(async (req, res) => {
if (!dockerMaintenance) throw new Error('Docker maintenance not available');
const result = await dockerMaintenance.runMaintenance();
res.json({ success: true, result });
}, 'logs-docker-maintenance'));
// Get logs from a file path (for native applications)
router.get('/logs/file', ctx.asyncHandler(async (req, res) => {
router.get('/logs/file', asyncHandler(async (req, res) => {
const { path: logPath, tail = 100 } = req.query;
if (!logPath) {

View File

@@ -365,7 +365,12 @@ async function createApp() {
}));
apiRouter.use(arrRoutes(ctx));
apiRouter.use(appsRoutes(ctx));
apiRouter.use(logsRoutes(ctx));
apiRouter.use(logsRoutes({
asyncHandler: ctx.asyncHandler,
docker: ctx.docker,
logDigest: ctx.logDigest,
dockerMaintenance: ctx.dockerMaintenance
}));
apiRouter.use(backupsRoutes({
backupManager: ctx.backupManager,
asyncHandler: ctx.asyncHandler