// Copyright (c) 2026 Gordon Bolton. MIT License. const express = require('express'); const router = express.Router(); const requireAuth = require('../middleware/requireAuth'); const cache = require('../utils/cache'); const { getLastPollTimings, POLLING_ENABLED } = require('../utils/poller'); const { getSonarrInstances, getRadarrInstances, getOmbiInstances } = require('../utils/config'); const { getGlobalWebhookMetrics } = require('../utils/cache'); const { checkWebhookConfigured, checkOmbiWebhookConfigured, aggregateMetrics } = require('../services/WebhookStatus'); /** * @openapi * /api/status/status: * get: * tags: [Status] * summary: Get server status (admin-only) * description: | * Admin-only endpoint returning server metrics, cache statistics, polling information, * and webhook metrics. Used by the admin status panel to monitor sofarr health. * * **Authentication:** Requires valid `emby_user` cookie (admin only). * * **Response Structure:** * - `server`: Uptime, Node version, memory usage * - `polling`: Polling enabled status, interval, last poll timings * - `cache`: Cache statistics (item count, sizes, TTLs) * - `webhooks`: Webhook configuration and metrics for Sonarr/Radarr * * **Webhook Metrics:** * - `configured`: Whether webhook is configured in Sonarr/Radarr * - `eventsReceived`: Total webhook events received * - `lastWebhookTimestamp`: Last webhook event time * - `pollsSkipped`: Number of poll cycles skipped due to recent webhook activity * * **x-integration-notes:** This endpoint is used by the admin status panel to display: * - Server health and resource usage * - Polling performance and timing * - Cache hit rates and sizes * - Webhook activity and smart polling effectiveness * security: * - CookieAuth: [] * responses: * '200': * description: Status data * content: * application/json: * schema: * $ref: '#/components/schemas/StatusResponse' * example: * server: * uptimeSeconds: 3600 * nodeVersion: "v22.0.0" * memoryUsageMB: 128.5 * heapUsedMB: 64.2 * heapTotalMB: 128.0 * polling: * enabled: true * intervalMs: 5000 * lastPoll: * sabnzbdQueue: 150 * sonarrQueue: 200 * cache: * "poll:sab-queue": * size: 2456 * items: 1 * ttlRemaining: 12000 * webhooks: * sonarr: * configured: true * eventsReceived: 42 * lastWebhookTimestamp: "2026-05-21T10:00:00.000Z" * pollsSkipped: 15 * radarr: * configured: true * eventsReceived: 38 * lastWebhookTimestamp: "2026-05-21T09:55:00.000Z" * pollsSkipped: 12 * '403': * description: Admin access required * content: * application/json: * schema: * $ref: '#/components/schemas/ErrorResponse' * example: * error: "Admin access required" * '500': * description: Server error * content: * application/json: * schema: * $ref: '#/components/schemas/ErrorResponse' * x-code-samples: * - lang: curl * label: cURL * source: | * curl -X GET http://localhost:3001/api/status/status \ * -b cookies.txt * - lang: JavaScript * label: JavaScript (fetch) * source: | * const response = await fetch('http://localhost:3001/api/status/status', { * method: 'GET', * credentials: 'include' * }); * const data = await response.json(); * console.log('Uptime:', data.server.uptimeSeconds); */ router.get('/', requireAuth, async (req, res) => { try { const user = req.user; if (!user.isAdmin) { return res.status(403).json({ error: 'Admin access required' }); } const cacheStats = cache.getStats(); const uptime = process.uptime(); // Get webhook metrics const webhookMetrics = getGlobalWebhookMetrics(); // Check webhook configuration for each service const sonarrInstances = getSonarrInstances(); const radarrInstances = getRadarrInstances(); const ombiInstances = getOmbiInstances(); const sonarrWebhookConfigured = sonarrInstances.length > 0 ? await checkWebhookConfigured(sonarrInstances[0], 'Sonarr') : false; const radarrWebhookConfigured = radarrInstances.length > 0 ? await checkWebhookConfigured(radarrInstances[0], 'Radarr') : false; const ombiWebhookConfigured = ombiInstances.length > 0 ? await checkOmbiWebhookConfigured(ombiInstances[0]) : false; // Find Sonarr, Radarr, and Ombi metrics from instances const sonarrMetrics = {}; const radarrMetrics = {}; const ombiMetrics = {}; for (const [url, metrics] of Object.entries(webhookMetrics.instances || {})) { if (url.includes('sonarr')) { sonarrMetrics[url] = metrics; } else if (url.includes('radarr')) { radarrMetrics[url] = metrics; } else if (url.includes('ombi') || (ombiInstances.length > 0 && url === ombiInstances[0].url)) { ombiMetrics[url] = metrics; } } res.json({ server: { uptimeSeconds: Math.floor(uptime), nodeVersion: process.version, memoryUsageMB: Math.round(process.memoryUsage().rss / 1024 / 1024 * 10) / 10, heapUsedMB: Math.round(process.memoryUsage().heapUsed / 1024 / 1024 * 10) / 10, heapTotalMB: Math.round(process.memoryUsage().heapTotal / 1024 / 1024 * 10) / 10 }, polling: { enabled: POLLING_ENABLED, intervalMs: POLLING_ENABLED ? require('../utils/poller').POLL_INTERVAL : 0, lastPoll: getLastPollTimings() }, cache: cacheStats, webhooks: { sonarr: aggregateMetrics(sonarrMetrics, sonarrWebhookConfigured), radarr: aggregateMetrics(radarrMetrics, radarrWebhookConfigured), ombi: aggregateMetrics(ombiMetrics, ombiWebhookConfigured) } }); } catch (err) { res.status(500).json({ error: 'Failed to get status', details: err.message }); } }); module.exports = router;