syco.me Homelab Dashboard

This commit is contained in:
2026-05-10 21:23:42 +02:00
parent 933e492d15
commit 90de2c1674
45 changed files with 6666 additions and 0 deletions
+133
View File
@@ -0,0 +1,133 @@
import { Router } from 'express'
import axios from 'axios'
const router = Router()
router.get('/calendar', async (req, res) => {
const today = new Date()
const defaultStart = new Date(today.getFullYear(), today.getMonth(), 1).toISOString().slice(0, 10)
const defaultEnd = new Date(today.getFullYear(), today.getMonth() + 1, 0).toISOString().slice(0, 10)
const start = (req.query.start as string) || defaultStart
const end = (req.query.end as string) || defaultEnd
const calParams = { start, end, unmonitored: false }
const sonarrHeaders = process.env.SONARR_API_KEY ? { 'X-Api-Key': process.env.SONARR_API_KEY } : null
const results = await Promise.allSettled([
process.env.RADARR_HOST && process.env.RADARR_API_KEY
? axios.get(`${process.env.RADARR_HOST}/api/v3/calendar`, {
headers: { 'X-Api-Key': process.env.RADARR_API_KEY },
params: calParams,
})
: Promise.resolve(null),
process.env.SONARR_HOST && sonarrHeaders
? axios.get(`${process.env.SONARR_HOST}/api/v3/calendar`, {
headers: sonarrHeaders,
params: calParams,
})
: Promise.resolve(null),
process.env.SONARR_HOST && sonarrHeaders
? axios.get(`${process.env.SONARR_HOST}/api/v3/series`, {
headers: sonarrHeaders,
})
: Promise.resolve(null),
process.env.LIDARR_HOST && process.env.LIDARR_API_KEY
? axios.get(`${process.env.LIDARR_HOST}/api/v1/calendar`, {
headers: { 'X-Api-Key': process.env.LIDARR_API_KEY },
params: calParams,
})
: Promise.resolve(null),
])
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const radarrData: any[] = results[0].status === 'fulfilled' && results[0].value?.data ? results[0].value.data : []
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const sonarrData: any[] = results[1].status === 'fulfilled' && results[1].value?.data ? results[1].value.data : []
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const seriesList: any[] = results[2].status === 'fulfilled' && results[2].value?.data ? results[2].value.data : []
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const lidarrData: any[] = results[3].status === 'fulfilled' && results[3].value?.data ? results[3].value.data : []
const seriesById: Record<number, string> = {}
for (const s of seriesList) seriesById[s.id] = s.title
const items = [
...radarrData.map(m => ({
date: (m.digitalRelease ?? m.physicalRelease ?? m.inCinemas ?? '').slice(0, 10),
title: m.title,
subtitle: m.year ? String(m.year) : '',
type: 'movie' as const,
downloaded: m.hasFile ?? false,
})).filter(m => m.date),
...sonarrData.map(e => ({
date: (e.airDate ?? '').slice(0, 10),
title: seriesById[e.seriesId] ?? e.title,
subtitle: `S${String(e.seasonNumber).padStart(2,'0')}E${String(e.episodeNumber).padStart(2,'0')}${e.title && e.title !== 'TBA' ? ` · ${e.title}` : ''}`,
type: 'episode' as const,
downloaded: e.hasFile ?? false,
})).filter(e => e.date),
...lidarrData.map(a => ({
date: (a.releaseDate ?? '').slice(0, 10),
title: a.artist?.artistName ?? '',
subtitle: a.title,
type: 'album' as const,
downloaded: a.statistics?.trackFileCount > 0,
})).filter(a => a.date),
].sort((a, b) => a.date.localeCompare(b.date))
res.json({ items })
})
router.get('/stats', async (_req, res) => {
const results = await Promise.allSettled([
process.env.RADARR_HOST && process.env.RADARR_API_KEY ? Promise.all([
axios.get(`${process.env.RADARR_HOST}/api/v3/movie`, { headers: { 'X-Api-Key': process.env.RADARR_API_KEY } }),
axios.get(`${process.env.RADARR_HOST}/api/v3/queue/status`, { headers: { 'X-Api-Key': process.env.RADARR_API_KEY } }),
]) : Promise.resolve(null),
process.env.SONARR_HOST && process.env.SONARR_API_KEY ? Promise.all([
axios.get(`${process.env.SONARR_HOST}/api/v3/series`, { headers: { 'X-Api-Key': process.env.SONARR_API_KEY } }),
axios.get(`${process.env.SONARR_HOST}/api/v3/wanted/missing?pageSize=1`, { headers: { 'X-Api-Key': process.env.SONARR_API_KEY } }),
axios.get(`${process.env.SONARR_HOST}/api/v3/queue/status`, { headers: { 'X-Api-Key': process.env.SONARR_API_KEY } }),
]) : Promise.resolve(null),
process.env.LIDARR_HOST && process.env.LIDARR_API_KEY ? Promise.all([
axios.get(`${process.env.LIDARR_HOST}/api/v1/artist`, { headers: { 'X-Api-Key': process.env.LIDARR_API_KEY } }),
axios.get(`${process.env.LIDARR_HOST}/api/v1/wanted/missing?pageSize=1`, { headers: { 'X-Api-Key': process.env.LIDARR_API_KEY } }),
axios.get(`${process.env.LIDARR_HOST}/api/v1/queue/status`, { headers: { 'X-Api-Key': process.env.LIDARR_API_KEY } }),
]) : Promise.resolve(null),
])
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const radarr = results[0].status === 'fulfilled' && results[0].value ? results[0].value as any[] : null
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const sonarr = results[1].status === 'fulfilled' && results[1].value ? results[1].value as any[] : null
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const lidarr = results[2].status === 'fulfilled' && results[2].value ? results[2].value as any[] : null
res.json({
radarr: radarr ? {
movies: radarr[0].data.length,
missing: radarr[0].data.filter((m: { hasFile: boolean; monitored: boolean }) => !m.hasFile && m.monitored).length,
queue: radarr[1].data.totalCount ?? 0,
} : null,
sonarr: sonarr ? {
series: sonarr[0].data.length,
missing: sonarr[1].data.totalRecords ?? 0,
queue: sonarr[2].data.totalCount ?? 0,
} : null,
lidarr: lidarr ? {
artists: lidarr[0].data.length,
missing: lidarr[1].data.totalRecords ?? 0,
queue: lidarr[2].data.totalCount ?? 0,
} : null,
})
})
export default router