mirror of
https://github.com/9technologygroup/patchmon.net.git
synced 2025-11-01 20:44:09 +00:00
313 lines
7.8 KiB
JavaScript
313 lines
7.8 KiB
JavaScript
const express = require('express');
|
|
const { PrismaClient } = require('@prisma/client');
|
|
const moment = require('moment');
|
|
const { authenticateToken } = require('../middleware/auth');
|
|
const {
|
|
requireViewDashboard,
|
|
requireViewHosts,
|
|
requireViewPackages
|
|
} = require('../middleware/permissions');
|
|
|
|
const router = express.Router();
|
|
const prisma = new PrismaClient();
|
|
|
|
// Get dashboard statistics
|
|
router.get('/stats', authenticateToken, requireViewDashboard, async (req, res) => {
|
|
try {
|
|
const now = new Date();
|
|
const twentyFourHoursAgo = moment(now).subtract(24, 'hours').toDate();
|
|
|
|
// Get all statistics in parallel for better performance
|
|
const [
|
|
totalHosts,
|
|
hostsNeedingUpdates,
|
|
totalOutdatedPackages,
|
|
erroredHosts,
|
|
securityUpdates,
|
|
osDistribution,
|
|
updateTrends
|
|
] = await Promise.all([
|
|
// Total hosts count
|
|
prisma.host.count({
|
|
where: { status: 'active' }
|
|
}),
|
|
|
|
// Hosts needing updates (distinct hosts with packages needing updates)
|
|
prisma.host.count({
|
|
where: {
|
|
status: 'active',
|
|
hostPackages: {
|
|
some: {
|
|
needsUpdate: true
|
|
}
|
|
}
|
|
}
|
|
}),
|
|
|
|
// Total outdated packages across all hosts
|
|
prisma.hostPackage.count({
|
|
where: { needsUpdate: true }
|
|
}),
|
|
|
|
// Errored hosts (not updated in 24 hours)
|
|
prisma.host.count({
|
|
where: {
|
|
status: 'active',
|
|
lastUpdate: {
|
|
lt: twentyFourHoursAgo
|
|
}
|
|
}
|
|
}),
|
|
|
|
// Security updates count
|
|
prisma.hostPackage.count({
|
|
where: {
|
|
needsUpdate: true,
|
|
isSecurityUpdate: true
|
|
}
|
|
}),
|
|
|
|
// OS distribution for pie chart
|
|
prisma.host.groupBy({
|
|
by: ['osType'],
|
|
where: { status: 'active' },
|
|
_count: {
|
|
osType: true
|
|
}
|
|
}),
|
|
|
|
// Update trends for the last 7 days
|
|
prisma.updateHistory.groupBy({
|
|
by: ['timestamp'],
|
|
where: {
|
|
timestamp: {
|
|
gte: moment(now).subtract(7, 'days').toDate()
|
|
}
|
|
},
|
|
_count: {
|
|
id: true
|
|
},
|
|
_sum: {
|
|
packagesCount: true,
|
|
securityCount: true
|
|
}
|
|
})
|
|
]);
|
|
|
|
// Format OS distribution for pie chart
|
|
const osDistributionFormatted = osDistribution.map(item => ({
|
|
name: item.osType,
|
|
count: item._count.osType
|
|
}));
|
|
|
|
// Calculate update status distribution
|
|
const updateStatusDistribution = [
|
|
{ name: 'Up to date', count: totalHosts - hostsNeedingUpdates },
|
|
{ name: 'Needs updates', count: hostsNeedingUpdates },
|
|
{ name: 'Errored', count: erroredHosts }
|
|
];
|
|
|
|
// Package update priority distribution
|
|
const packageUpdateDistribution = [
|
|
{ name: 'Security', count: securityUpdates },
|
|
{ name: 'Regular', count: totalOutdatedPackages - securityUpdates }
|
|
];
|
|
|
|
res.json({
|
|
cards: {
|
|
totalHosts,
|
|
hostsNeedingUpdates,
|
|
totalOutdatedPackages,
|
|
erroredHosts,
|
|
securityUpdates
|
|
},
|
|
charts: {
|
|
osDistribution: osDistributionFormatted,
|
|
updateStatusDistribution,
|
|
packageUpdateDistribution
|
|
},
|
|
trends: updateTrends,
|
|
lastUpdated: now.toISOString()
|
|
});
|
|
} catch (error) {
|
|
console.error('Error fetching dashboard stats:', error);
|
|
res.status(500).json({ error: 'Failed to fetch dashboard statistics' });
|
|
}
|
|
});
|
|
|
|
// Get hosts with their update status
|
|
router.get('/hosts', authenticateToken, requireViewHosts, async (req, res) => {
|
|
try {
|
|
const hosts = await prisma.host.findMany({
|
|
// Show all hosts regardless of status
|
|
select: {
|
|
id: true,
|
|
hostname: true,
|
|
ip: true,
|
|
osType: true,
|
|
osVersion: true,
|
|
lastUpdate: true,
|
|
status: true,
|
|
agentVersion: true,
|
|
autoUpdate: true,
|
|
hostGroup: {
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
color: true
|
|
}
|
|
},
|
|
_count: {
|
|
select: {
|
|
hostPackages: {
|
|
where: {
|
|
needsUpdate: true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
orderBy: { lastUpdate: 'desc' }
|
|
});
|
|
|
|
// Get update counts for each host separately
|
|
const hostsWithUpdateInfo = await Promise.all(
|
|
hosts.map(async (host) => {
|
|
const updatesCount = await prisma.hostPackage.count({
|
|
where: {
|
|
hostId: host.id,
|
|
needsUpdate: true
|
|
}
|
|
});
|
|
|
|
return {
|
|
...host,
|
|
updatesCount,
|
|
isStale: moment(host.lastUpdate).isBefore(moment().subtract(24, 'hours'))
|
|
};
|
|
})
|
|
);
|
|
|
|
res.json(hostsWithUpdateInfo);
|
|
} catch (error) {
|
|
console.error('Error fetching hosts:', error);
|
|
res.status(500).json({ error: 'Failed to fetch hosts' });
|
|
}
|
|
});
|
|
|
|
// Get packages that need updates across all hosts
|
|
router.get('/packages', authenticateToken, requireViewPackages, async (req, res) => {
|
|
try {
|
|
const packages = await prisma.package.findMany({
|
|
where: {
|
|
hostPackages: {
|
|
some: {
|
|
needsUpdate: true
|
|
}
|
|
}
|
|
},
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
description: true,
|
|
category: true,
|
|
latestVersion: true,
|
|
hostPackages: {
|
|
where: { needsUpdate: true },
|
|
select: {
|
|
currentVersion: true,
|
|
availableVersion: true,
|
|
isSecurityUpdate: true,
|
|
host: {
|
|
select: {
|
|
id: true,
|
|
hostname: true,
|
|
osType: true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
orderBy: {
|
|
name: 'asc'
|
|
}
|
|
});
|
|
|
|
const packagesWithHostInfo = packages.map(pkg => ({
|
|
id: pkg.id,
|
|
name: pkg.name,
|
|
description: pkg.description,
|
|
category: pkg.category,
|
|
latestVersion: pkg.latestVersion,
|
|
affectedHostsCount: pkg.hostPackages.length,
|
|
isSecurityUpdate: pkg.hostPackages.some(hp => hp.isSecurityUpdate),
|
|
affectedHosts: pkg.hostPackages.map(hp => ({
|
|
hostId: hp.host.id,
|
|
hostname: hp.host.hostname,
|
|
osType: hp.host.osType,
|
|
currentVersion: hp.currentVersion,
|
|
availableVersion: hp.availableVersion,
|
|
isSecurityUpdate: hp.isSecurityUpdate
|
|
}))
|
|
}));
|
|
|
|
res.json(packagesWithHostInfo);
|
|
} catch (error) {
|
|
console.error('Error fetching packages:', error);
|
|
res.status(500).json({ error: 'Failed to fetch packages' });
|
|
}
|
|
});
|
|
|
|
// Get detailed host information
|
|
router.get('/hosts/:hostId', authenticateToken, requireViewHosts, async (req, res) => {
|
|
try {
|
|
const { hostId } = req.params;
|
|
|
|
const host = await prisma.host.findUnique({
|
|
where: { id: hostId },
|
|
include: {
|
|
hostGroup: {
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
color: true
|
|
}
|
|
},
|
|
hostPackages: {
|
|
include: {
|
|
package: true
|
|
},
|
|
orderBy: {
|
|
needsUpdate: 'desc'
|
|
}
|
|
},
|
|
updateHistory: {
|
|
orderBy: {
|
|
timestamp: 'desc'
|
|
},
|
|
take: 10
|
|
}
|
|
}
|
|
});
|
|
|
|
if (!host) {
|
|
return res.status(404).json({ error: 'Host not found' });
|
|
}
|
|
|
|
const hostWithStats = {
|
|
...host,
|
|
stats: {
|
|
totalPackages: host.hostPackages.length,
|
|
outdatedPackages: host.hostPackages.filter(hp => hp.needsUpdate).length,
|
|
securityUpdates: host.hostPackages.filter(hp => hp.needsUpdate && hp.isSecurityUpdate).length
|
|
}
|
|
};
|
|
|
|
res.json(hostWithStats);
|
|
} catch (error) {
|
|
console.error('Error fetching host details:', error);
|
|
res.status(500).json({ error: 'Failed to fetch host details' });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|