Files
sofarr/server/routes/status.js
T
gronod a21bafa041 docs(swagger): add JSDoc @openapi for status and history endpoints
- GET /api/status/status: admin-only, server/cache/polling/webhook metrics
- GET /api/history/recent: filtered by user tag, deduplication logic
- Document deduplication rules (imported suppresses failed)
- Document availableForUpgrade flag
- Include query parameters (days, showAll)
2026-05-21 12:36:07 +01:00

168 lines
6.1 KiB
JavaScript

// 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 } = require('../utils/config');
const { getGlobalWebhookMetrics } = require('../utils/cache');
const { checkWebhookConfigured, 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 sonarrWebhookConfigured = sonarrInstances.length > 0
? await checkWebhookConfigured(sonarrInstances[0], 'Sonarr')
: false;
const radarrWebhookConfigured = radarrInstances.length > 0
? await checkWebhookConfigured(radarrInstances[0], 'Radarr')
: false;
// Find Sonarr and Radarr metrics from instances
const sonarrMetrics = {};
const radarrMetrics = {};
for (const [url, metrics] of Object.entries(webhookMetrics.instances || {})) {
if (url.includes('sonarr')) {
sonarrMetrics[url] = metrics;
} else if (url.includes('radarr')) {
radarrMetrics[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)
}
});
} catch (err) {
res.status(500).json({ error: 'Failed to get status', details: err.message });
}
});
module.exports = router;