mirror of
https://github.com/9technologygroup/patchmon.net.git
synced 2025-11-07 15:33:32 +00:00
Added more dashboard cards
Fixed permissions roles creation bug On initial deployment, made it so the agent being populated will be set as default and current Fixed host detail to include package numbers Added ability to add full name - fixed loads of other bugs caused by camelcase to snake_Case migration
This commit is contained in:
@@ -151,8 +151,13 @@ router.post('/admin/users', authenticateToken, requireManageUsers, [
|
||||
body('username').isLength({ min: 3 }).withMessage('Username must be at least 3 characters'),
|
||||
body('email').isEmail().withMessage('Valid email is required'),
|
||||
body('password').isLength({ min: 6 }).withMessage('Password must be at least 6 characters'),
|
||||
body('first_name').optional().isLength({ min: 1 }).withMessage('First name must be at least 1 character'),
|
||||
body('last_name').optional().isLength({ min: 1 }).withMessage('Last name must be at least 1 character'),
|
||||
body('role').optional().custom(async (value) => {
|
||||
if (!value) return true; // Optional field
|
||||
// Allow built-in roles even if not in role_permissions table yet
|
||||
const builtInRoles = ['admin', 'user'];
|
||||
if (builtInRoles.includes(value)) return true;
|
||||
const rolePermissions = await prisma.role_permissions.findUnique({
|
||||
where: { role: value }
|
||||
});
|
||||
@@ -168,7 +173,7 @@ router.post('/admin/users', authenticateToken, requireManageUsers, [
|
||||
return res.status(400).json({ errors: errors.array() });
|
||||
}
|
||||
|
||||
const { username, email, password, role = 'user' } = req.body;
|
||||
const { username, email, password, first_name, last_name, role = 'user' } = req.body;
|
||||
|
||||
// Check if user already exists
|
||||
const existingUser = await prisma.users.findFirst({
|
||||
@@ -190,15 +195,21 @@ router.post('/admin/users', authenticateToken, requireManageUsers, [
|
||||
// Create user
|
||||
const user = await prisma.users.create({
|
||||
data: {
|
||||
id: uuidv4(),
|
||||
username,
|
||||
email,
|
||||
password_hash: passwordHash,
|
||||
role
|
||||
first_name: first_name || null,
|
||||
last_name: last_name || null,
|
||||
role,
|
||||
updated_at: new Date()
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
email: true,
|
||||
first_name: true,
|
||||
last_name: true,
|
||||
role: true,
|
||||
is_active: true,
|
||||
created_at: true
|
||||
@@ -542,6 +553,8 @@ router.post('/login', [
|
||||
id: true,
|
||||
username: true,
|
||||
email: true,
|
||||
first_name: true,
|
||||
last_name: true,
|
||||
password_hash: true,
|
||||
role: true,
|
||||
is_active: true,
|
||||
@@ -690,6 +703,8 @@ router.post('/verify-tfa', [
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
first_name: user.first_name,
|
||||
last_name: user.last_name,
|
||||
role: user.role
|
||||
}
|
||||
});
|
||||
@@ -714,7 +729,9 @@ router.get('/profile', authenticateToken, async (req, res) => {
|
||||
// Update user profile
|
||||
router.put('/profile', authenticateToken, [
|
||||
body('username').optional().isLength({ min: 3 }).withMessage('Username must be at least 3 characters'),
|
||||
body('email').optional().isEmail().withMessage('Valid email is required')
|
||||
body('email').optional().isEmail().withMessage('Valid email is required'),
|
||||
body('first_name').optional().isLength({ min: 1 }).withMessage('First name must be at least 1 character'),
|
||||
body('last_name').optional().isLength({ min: 1 }).withMessage('Last name must be at least 1 character')
|
||||
], async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
@@ -722,11 +739,13 @@ router.put('/profile', authenticateToken, [
|
||||
return res.status(400).json({ errors: errors.array() });
|
||||
}
|
||||
|
||||
const { username, email } = req.body;
|
||||
const { username, email, first_name, last_name } = req.body;
|
||||
const updateData = {};
|
||||
|
||||
if (username) updateData.username = username;
|
||||
if (email) updateData.email = email;
|
||||
if (first_name !== undefined) updateData.first_name = first_name || null;
|
||||
if (last_name !== undefined) updateData.last_name = last_name || null;
|
||||
|
||||
// Check if username/email already exists (excluding current user)
|
||||
if (username || email) {
|
||||
@@ -756,6 +775,8 @@ router.put('/profile', authenticateToken, [
|
||||
id: true,
|
||||
username: true,
|
||||
email: true,
|
||||
first_name: true,
|
||||
last_name: true,
|
||||
role: true,
|
||||
is_active: true,
|
||||
last_login: true,
|
||||
|
||||
@@ -74,13 +74,17 @@ router.get('/defaults', authenticateToken, async (req, res) => {
|
||||
{ cardId: 'hostsNeedingUpdates', title: 'Needs Updating', icon: 'AlertTriangle', enabled: true, order: 1 },
|
||||
{ cardId: 'totalOutdatedPackages', title: 'Outdated Packages', icon: 'Package', enabled: true, order: 2 },
|
||||
{ cardId: 'securityUpdates', title: 'Security Updates', icon: 'Shield', enabled: true, order: 3 },
|
||||
{ cardId: 'erroredHosts', title: 'Errored Hosts', icon: 'AlertTriangle', enabled: true, order: 4 },
|
||||
{ cardId: 'offlineHosts', title: 'Offline/Stale Hosts', icon: 'WifiOff', enabled: false, order: 5 },
|
||||
{ cardId: 'osDistribution', title: 'OS Distribution', icon: 'BarChart3', enabled: true, order: 6 },
|
||||
{ cardId: 'osDistributionBar', title: 'OS Distribution (Bar)', icon: 'BarChart3', enabled: false, order: 7 },
|
||||
{ cardId: 'updateStatus', title: 'Update Status', icon: 'BarChart3', enabled: true, order: 8 },
|
||||
{ cardId: 'packagePriority', title: 'Package Priority', icon: 'BarChart3', enabled: true, order: 9 },
|
||||
{ cardId: 'quickStats', title: 'Quick Stats', icon: 'TrendingUp', enabled: true, order: 10 }
|
||||
{ cardId: 'upToDateHosts', title: 'Up to date', icon: 'CheckCircle', enabled: true, order: 4 },
|
||||
{ cardId: 'totalHostGroups', title: 'Host Groups', icon: 'Folder', enabled: false, order: 5 },
|
||||
{ cardId: 'totalUsers', title: 'Users', icon: 'Users', enabled: false, order: 6 },
|
||||
{ cardId: 'totalRepos', title: 'Repositories', icon: 'GitBranch', enabled: false, order: 7 },
|
||||
{ cardId: 'osDistribution', title: 'OS Distribution', icon: 'BarChart3', enabled: true, order: 8 },
|
||||
{ cardId: 'osDistributionBar', title: 'OS Distribution (Bar)', icon: 'BarChart3', enabled: false, order: 9 },
|
||||
{ cardId: 'updateStatus', title: 'Update Status', icon: 'BarChart3', enabled: true, order: 10 },
|
||||
{ cardId: 'packagePriority', title: 'Package Priority', icon: 'BarChart3', enabled: true, order: 11 },
|
||||
{ cardId: 'quickStats', title: 'Quick Stats', icon: 'TrendingUp', enabled: true, order: 12 },
|
||||
{ cardId: 'recentUsers', title: 'Recent Users Logged in', icon: 'Users', enabled: true, order: 13 },
|
||||
{ cardId: 'recentCollection', title: 'Recent Collection', icon: 'Server', enabled: true, order: 14 }
|
||||
];
|
||||
|
||||
res.json(defaultCards);
|
||||
|
||||
@@ -5,7 +5,8 @@ const { authenticateToken } = require('../middleware/auth');
|
||||
const {
|
||||
requireViewDashboard,
|
||||
requireViewHosts,
|
||||
requireViewPackages
|
||||
requireViewPackages,
|
||||
requireViewUsers
|
||||
} = require('../middleware/permissions');
|
||||
|
||||
const router = express.Router();
|
||||
@@ -33,6 +34,9 @@ router.get('/stats', authenticateToken, requireViewDashboard, async (req, res) =
|
||||
erroredHosts,
|
||||
securityUpdates,
|
||||
offlineHosts,
|
||||
totalHostGroups,
|
||||
totalUsers,
|
||||
totalRepos,
|
||||
osDistribution,
|
||||
updateTrends
|
||||
] = await Promise.all([
|
||||
@@ -83,6 +87,15 @@ router.get('/stats', authenticateToken, requireViewDashboard, async (req, res) =
|
||||
}
|
||||
}),
|
||||
|
||||
// Total host groups count
|
||||
prisma.host_groups.count(),
|
||||
|
||||
// Total users count
|
||||
prisma.users.count(),
|
||||
|
||||
// Total repositories count
|
||||
prisma.repositories.count(),
|
||||
|
||||
// OS distribution for pie chart
|
||||
prisma.hosts.groupBy({
|
||||
by: ['os_type'],
|
||||
@@ -133,10 +146,14 @@ router.get('/stats', authenticateToken, requireViewDashboard, async (req, res) =
|
||||
cards: {
|
||||
totalHosts,
|
||||
hostsNeedingUpdates,
|
||||
upToDateHosts: Math.max(totalHosts - hostsNeedingUpdates, 0),
|
||||
totalOutdatedPackages,
|
||||
erroredHosts,
|
||||
securityUpdates,
|
||||
offlineHosts
|
||||
offlineHosts,
|
||||
totalHostGroups,
|
||||
totalUsers,
|
||||
totalRepos
|
||||
},
|
||||
charts: {
|
||||
osDistribution: osDistributionFormatted,
|
||||
@@ -338,9 +355,9 @@ router.get('/hosts/:hostId', authenticateToken, requireViewHosts, async (req, re
|
||||
const hostWithStats = {
|
||||
...host,
|
||||
stats: {
|
||||
totalPackages: host.host_packages.length,
|
||||
outdatedPackages: host.host_packages.filter(hp => hp.needs_update).length,
|
||||
securityUpdates: host.host_packages.filter(hp => hp.needs_update && hp.is_security_update).length
|
||||
total_packages: host.host_packages.length,
|
||||
outdated_packages: host.host_packages.filter(hp => hp.needs_update).length,
|
||||
security_updates: host.host_packages.filter(hp => hp.needs_update && hp.is_security_update).length
|
||||
}
|
||||
};
|
||||
|
||||
@@ -351,4 +368,59 @@ router.get('/hosts/:hostId', authenticateToken, requireViewHosts, async (req, re
|
||||
}
|
||||
});
|
||||
|
||||
// Get recent users ordered by last_login desc
|
||||
router.get('/recent-users', authenticateToken, requireViewUsers, async (req, res) => {
|
||||
try {
|
||||
const users = await prisma.users.findMany({
|
||||
where: {
|
||||
last_login: {
|
||||
not: null
|
||||
}
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
email: true,
|
||||
role: true,
|
||||
last_login: true,
|
||||
created_at: true
|
||||
},
|
||||
orderBy: [
|
||||
{ last_login: 'desc' },
|
||||
{ created_at: 'desc' }
|
||||
],
|
||||
take: 5
|
||||
});
|
||||
|
||||
res.json(users);
|
||||
} catch (error) {
|
||||
console.error('Error fetching recent users:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch recent users' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get recent hosts that have sent data (ordered by last_update desc)
|
||||
router.get('/recent-collection', authenticateToken, requireViewHosts, async (req, res) => {
|
||||
try {
|
||||
const hosts = await prisma.hosts.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
friendly_name: true,
|
||||
hostname: true,
|
||||
last_update: true,
|
||||
status: true
|
||||
},
|
||||
orderBy: {
|
||||
last_update: 'desc'
|
||||
},
|
||||
take: 5
|
||||
});
|
||||
|
||||
res.json(hosts);
|
||||
} catch (error) {
|
||||
console.error('Error fetching recent collection:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch recent collection' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,6 +1,7 @@
|
||||
const express = require('express');
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
const { randomUUID } = require('crypto');
|
||||
const { authenticateToken } = require('../middleware/auth');
|
||||
const { requireManageHosts } = require('../middleware/permissions');
|
||||
|
||||
@@ -41,13 +42,13 @@ router.get('/:id', authenticateToken, async (req, res) => {
|
||||
hosts: {
|
||||
select: {
|
||||
id: true,
|
||||
friendlyName: true,
|
||||
friendly_name: true,
|
||||
hostname: true,
|
||||
ip: true,
|
||||
osType: true,
|
||||
osVersion: true,
|
||||
os_type: true,
|
||||
os_version: true,
|
||||
status: true,
|
||||
lastUpdate: true
|
||||
last_update: true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,9 +90,11 @@ router.post('/', authenticateToken, requireManageHosts, [
|
||||
|
||||
const hostGroup = await prisma.host_groups.create({
|
||||
data: {
|
||||
id: randomUUID(),
|
||||
name,
|
||||
description: description || null,
|
||||
color: color || '#3B82F6'
|
||||
color: color || '#3B82F6',
|
||||
updated_at: new Date()
|
||||
}
|
||||
});
|
||||
|
||||
@@ -143,7 +146,8 @@ router.put('/:id', authenticateToken, requireManageHosts, [
|
||||
data: {
|
||||
name,
|
||||
description: description || null,
|
||||
color: color || '#3B82F6'
|
||||
color: color || '#3B82F6',
|
||||
updated_at: new Date()
|
||||
}
|
||||
});
|
||||
|
||||
@@ -199,20 +203,20 @@ router.get('/:id/hosts', authenticateToken, async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
const hosts = await prisma.hosts.findMany({
|
||||
where: { hostGroupId: id },
|
||||
where: { host_group_id: id },
|
||||
select: {
|
||||
id: true,
|
||||
friendlyName: true,
|
||||
friendly_name: true,
|
||||
ip: true,
|
||||
osType: true,
|
||||
osVersion: true,
|
||||
os_type: true,
|
||||
os_version: true,
|
||||
architecture: true,
|
||||
status: true,
|
||||
lastUpdate: true,
|
||||
createdAt: true
|
||||
last_update: true,
|
||||
created_at: true
|
||||
},
|
||||
orderBy: {
|
||||
friendlyName: 'asc'
|
||||
friendly_name: 'asc'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
const express = require('express');
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
const { authenticateToken, requireAdmin } = require('../middleware/auth');
|
||||
const { requireManageSettings } = require('../middleware/permissions');
|
||||
const { requireManageSettings, requireManageUsers } = require('../middleware/permissions');
|
||||
|
||||
const router = express.Router();
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Get all role permissions
|
||||
router.get('/roles', authenticateToken, requireManageSettings, async (req, res) => {
|
||||
// Get all role permissions (allow users who can manage users to view roles)
|
||||
router.get('/roles', authenticateToken, requireManageUsers, async (req, res) => {
|
||||
try {
|
||||
const permissions = await prisma.role_permissions.findMany({
|
||||
orderBy: {
|
||||
|
||||
@@ -133,6 +133,18 @@ async function checkAndImportAgentVersion() {
|
||||
version: localVersion,
|
||||
release_notes: `Auto-imported on startup (${new Date().toISOString()})`,
|
||||
script_content: scriptContent,
|
||||
is_default: true,
|
||||
is_current: true,
|
||||
updated_at: new Date()
|
||||
}
|
||||
});
|
||||
|
||||
// Update all other versions to not be default or current
|
||||
await prisma.agent_versions.updateMany({
|
||||
where: {
|
||||
version: { not: localVersion }
|
||||
},
|
||||
data: {
|
||||
is_default: false,
|
||||
is_current: false,
|
||||
updated_at: new Date()
|
||||
|
||||
Reference in New Issue
Block a user