import type { Express, RequestHandler } from "express"; import { statsLogger } from "../../../src/backend/utils/logger.js"; import { createCurrentSettingsRepository } from "../../../src/backend/database/repositories/factory.js"; type HostMetricsSettingsConfig = { statusCheckInterval: number; metricsInterval: number; }; type HostMetricsSettingsRoutesDeps = { requireAdmin: RequestHandler; defaultStatsConfig: HostMetricsSettingsConfig; refreshAllPolling: () => Promise; }; function isMissingSettingsTable(error: unknown): boolean { return ( error instanceof Error && error.message.includes("no such table: settings") ); } export function registerHostMetricsSettingsRoutes( app: Express, { requireAdmin, defaultStatsConfig: DEFAULT_STATS_CONFIG, refreshAllPolling, }: HostMetricsSettingsRoutesDeps, ): void { /** * @openapi * /global-settings: * get: * summary: Get global monitoring defaults * tags: * - Host Metrics * responses: * 200: * description: Global monitoring settings. * 403: * description: Requires admin privileges. */ app.get("/global-settings", requireAdmin, async (_req, res) => { try { const settings = createCurrentSettingsRepository(); const statusValue = await settings.get("global_status_check_interval"); const metricsValue = await settings.get("global_metrics_interval"); res.json({ statusCheckInterval: statusValue ? parseInt(statusValue, 10) : DEFAULT_STATS_CONFIG.statusCheckInterval, metricsInterval: metricsValue ? parseInt(metricsValue, 10) : DEFAULT_STATS_CONFIG.metricsInterval, }); } catch (error) { if (isMissingSettingsTable(error)) { statsLogger.warn("Settings table does not exist, using defaults", { operation: "global_settings_table_check", error: error.message, }); return res.json({ statusCheckInterval: DEFAULT_STATS_CONFIG.statusCheckInterval, metricsInterval: DEFAULT_STATS_CONFIG.metricsInterval, }); } statsLogger.error("Failed to fetch global settings", { operation: "global_settings_fetch_error", error: error instanceof Error ? error.message : String(error), }); res.status(500).json({ error: "Failed to fetch global settings" }); } }); /** * @openapi * /global-settings: * post: * summary: Update global monitoring defaults * tags: * - Host Metrics * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * statusCheckInterval: * type: integer * metricsInterval: * type: integer * responses: * 200: * description: Settings saved. * 400: * description: Invalid parameters. * 403: * description: Requires admin privileges. */ app.post("/global-settings", requireAdmin, async (req, res) => { const { statusCheckInterval, metricsInterval } = req.body; if ( statusCheckInterval !== undefined && (typeof statusCheckInterval !== "number" || statusCheckInterval < 5 || statusCheckInterval > 3600) ) { return res.status(400).json({ error: "statusCheckInterval must be between 5 and 3600 seconds", }); } if ( metricsInterval !== undefined && (typeof metricsInterval !== "number" || metricsInterval < 5 || metricsInterval > 3600) ) { return res .status(400) .json({ error: "metricsInterval must be between 5 and 3600 seconds" }); } try { const settings = createCurrentSettingsRepository(); if (statusCheckInterval !== undefined) { await settings.set( "global_status_check_interval", String(statusCheckInterval), ); } if (metricsInterval !== undefined) { await settings.set("global_metrics_interval", String(metricsInterval)); } await refreshAllPolling(); res.json({ success: true, message: "Settings updated and polling refreshed", }); } catch (error) { if (isMissingSettingsTable(error)) { statsLogger.error( "Settings table does not exist, cannot save settings", { operation: "global_settings_table_check", error: error.message, }, ); return res.status(500).json({ error: "Database settings table is missing. Please check database initialization.", }); } statsLogger.error("Failed to save global settings", { operation: "global_settings_save_error", error: error instanceof Error ? error.message : String(error), }); res.status(500).json({ error: "Failed to save global settings", details: error instanceof Error ? error.message : String(error), }); } }); /** * @openapi * /global-settings/history: * get: * summary: Get metrics history retention setting * tags: * - Host Metrics * responses: * 200: * description: Retention setting in days. * 403: * description: Requires admin privileges. */ app.get("/global-settings/history", requireAdmin, async (_req, res) => { try { const value = await createCurrentSettingsRepository().get( "metrics_history_retention_days", ); const days = value ? parseInt(value, 10) : 7; res.json({ metricsHistoryRetentionDays: isNaN(days) ? 7 : days }); } catch (error) { statsLogger.error("Failed to fetch history retention setting", { operation: "history_retention_fetch_error", error: error instanceof Error ? error.message : String(error), }); res .status(500) .json({ error: "Failed to fetch history retention setting" }); } }); /** * @openapi * /global-settings/history: * post: * summary: Update metrics history retention setting * tags: * - Host Metrics * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * metricsHistoryRetentionDays: * type: integer * minimum: 1 * maximum: 90 * responses: * 200: * description: Setting saved. * 400: * description: Invalid value. * 403: * description: Requires admin privileges. */ app.post("/global-settings/history", requireAdmin, async (req, res) => { const { metricsHistoryRetentionDays } = req.body; if ( typeof metricsHistoryRetentionDays !== "number" || metricsHistoryRetentionDays < 1 || metricsHistoryRetentionDays > 90 ) { return res.status(400).json({ error: "metricsHistoryRetentionDays must be between 1 and 90", }); } try { await createCurrentSettingsRepository().set( "metrics_history_retention_days", String(Math.floor(metricsHistoryRetentionDays)), ); res.json({ success: true }); } catch (error) { statsLogger.error("Failed to save history retention setting", { operation: "history_retention_save_error", error: error instanceof Error ? error.message : String(error), }); res .status(500) .json({ error: "Failed to save history retention setting" }); } }); }