mirror of
https://github.com/9technologygroup/patchmon.net.git
synced 2025-11-04 22:13:21 +00:00
Fixed permissions issues
Created default user role modified server.js to check if roles of admin/user is present modified server.js to check dashboard cards set up default dashboard cards to show
This commit is contained in:
@@ -1,3 +0,0 @@
|
|||||||
Join my discord for Instructions, support and feedback :
|
|
||||||
|
|
||||||
https://discord.gg/S7RXUHwg
|
|
||||||
@@ -20,3 +20,6 @@ AGENT_RATE_LIMIT_MAX=1000
|
|||||||
# Logging
|
# Logging
|
||||||
LOG_LEVEL=info
|
LOG_LEVEL=info
|
||||||
ENABLE_LOGGING=true
|
ENABLE_LOGGING=true
|
||||||
|
|
||||||
|
# User Registration
|
||||||
|
DEFAULT_USER_ROLE=user
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "settings" ADD COLUMN "default_user_role" TEXT NOT NULL DEFAULT 'user';
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
-- Initialize default dashboard preferences for all existing users
|
||||||
|
-- This migration ensures that all users have proper role-based dashboard preferences
|
||||||
|
|
||||||
|
-- Function to create default dashboard preferences for a user
|
||||||
|
CREATE OR REPLACE FUNCTION init_user_dashboard_preferences(user_id TEXT, user_role TEXT)
|
||||||
|
RETURNS VOID AS $$
|
||||||
|
DECLARE
|
||||||
|
pref_record RECORD;
|
||||||
|
BEGIN
|
||||||
|
-- Delete any existing preferences for this user
|
||||||
|
DELETE FROM dashboard_preferences WHERE dashboard_preferences.user_id = init_user_dashboard_preferences.user_id;
|
||||||
|
|
||||||
|
-- Insert role-based preferences
|
||||||
|
IF user_role = 'admin' THEN
|
||||||
|
-- Admin gets full access to all cards (iby's preferred layout)
|
||||||
|
INSERT INTO dashboard_preferences (id, user_id, card_id, enabled, "order", created_at, updated_at)
|
||||||
|
VALUES
|
||||||
|
(gen_random_uuid(), user_id, 'totalHosts', true, 0, NOW(), NOW()),
|
||||||
|
(gen_random_uuid(), user_id, 'hostsNeedingUpdates', true, 1, NOW(), NOW()),
|
||||||
|
(gen_random_uuid(), user_id, 'totalOutdatedPackages', true, 2, NOW(), NOW()),
|
||||||
|
(gen_random_uuid(), user_id, 'securityUpdates', true, 3, NOW(), NOW()),
|
||||||
|
(gen_random_uuid(), user_id, 'totalHostGroups', true, 4, NOW(), NOW()),
|
||||||
|
(gen_random_uuid(), user_id, 'upToDateHosts', true, 5, NOW(), NOW()),
|
||||||
|
(gen_random_uuid(), user_id, 'totalRepos', true, 6, NOW(), NOW()),
|
||||||
|
(gen_random_uuid(), user_id, 'totalUsers', true, 7, NOW(), NOW()),
|
||||||
|
(gen_random_uuid(), user_id, 'osDistribution', true, 8, NOW(), NOW()),
|
||||||
|
(gen_random_uuid(), user_id, 'osDistributionBar', true, 9, NOW(), NOW()),
|
||||||
|
(gen_random_uuid(), user_id, 'recentCollection', true, 10, NOW(), NOW()),
|
||||||
|
(gen_random_uuid(), user_id, 'updateStatus', true, 11, NOW(), NOW()),
|
||||||
|
(gen_random_uuid(), user_id, 'packagePriority', true, 12, NOW(), NOW()),
|
||||||
|
(gen_random_uuid(), user_id, 'recentUsers', true, 13, NOW(), NOW()),
|
||||||
|
(gen_random_uuid(), user_id, 'quickStats', true, 14, NOW(), NOW());
|
||||||
|
ELSE
|
||||||
|
-- Regular users get comprehensive layout but without user management cards
|
||||||
|
INSERT INTO dashboard_preferences (id, user_id, card_id, enabled, "order", created_at, updated_at)
|
||||||
|
VALUES
|
||||||
|
(gen_random_uuid(), user_id, 'totalHosts', true, 0, NOW(), NOW()),
|
||||||
|
(gen_random_uuid(), user_id, 'hostsNeedingUpdates', true, 1, NOW(), NOW()),
|
||||||
|
(gen_random_uuid(), user_id, 'totalOutdatedPackages', true, 2, NOW(), NOW()),
|
||||||
|
(gen_random_uuid(), user_id, 'securityUpdates', true, 3, NOW(), NOW()),
|
||||||
|
(gen_random_uuid(), user_id, 'totalHostGroups', true, 4, NOW(), NOW()),
|
||||||
|
(gen_random_uuid(), user_id, 'upToDateHosts', true, 5, NOW(), NOW()),
|
||||||
|
(gen_random_uuid(), user_id, 'totalRepos', true, 6, NOW(), NOW()),
|
||||||
|
(gen_random_uuid(), user_id, 'osDistribution', true, 7, NOW(), NOW()),
|
||||||
|
(gen_random_uuid(), user_id, 'osDistributionBar', true, 8, NOW(), NOW()),
|
||||||
|
(gen_random_uuid(), user_id, 'recentCollection', true, 9, NOW(), NOW()),
|
||||||
|
(gen_random_uuid(), user_id, 'updateStatus', true, 10, NOW(), NOW()),
|
||||||
|
(gen_random_uuid(), user_id, 'packagePriority', true, 11, NOW(), NOW()),
|
||||||
|
(gen_random_uuid(), user_id, 'quickStats', true, 12, NOW(), NOW());
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- Apply default preferences to all existing users
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
user_record RECORD;
|
||||||
|
BEGIN
|
||||||
|
FOR user_record IN SELECT id, role FROM users LOOP
|
||||||
|
PERFORM init_user_dashboard_preferences(user_record.id, user_record.role);
|
||||||
|
END LOOP;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- Drop the temporary function
|
||||||
|
DROP FUNCTION init_user_dashboard_preferences(TEXT, TEXT);
|
||||||
@@ -168,6 +168,7 @@ model settings {
|
|||||||
latest_version String?
|
latest_version String?
|
||||||
update_available Boolean @default(false)
|
update_available Boolean @default(false)
|
||||||
signup_enabled Boolean @default(false)
|
signup_enabled Boolean @default(false)
|
||||||
|
default_user_role String @default("user")
|
||||||
}
|
}
|
||||||
|
|
||||||
model update_history {
|
model update_history {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const { body, validationResult } = require('express-validator');
|
|||||||
const { authenticateToken, requireAdmin } = require('../middleware/auth');
|
const { authenticateToken, requireAdmin } = require('../middleware/auth');
|
||||||
const { requireViewUsers, requireManageUsers } = require('../middleware/permissions');
|
const { requireViewUsers, requireManageUsers } = require('../middleware/permissions');
|
||||||
const { v4: uuidv4 } = require('uuid');
|
const { v4: uuidv4 } = require('uuid');
|
||||||
|
const { createDefaultDashboardPreferences } = require('./dashboardPreferencesRoutes');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
@@ -32,6 +33,8 @@ router.get('/check-admin-users', async (req, res) => {
|
|||||||
|
|
||||||
// Create first admin user (for first-time setup)
|
// Create first admin user (for first-time setup)
|
||||||
router.post('/setup-admin', [
|
router.post('/setup-admin', [
|
||||||
|
body('firstName').isLength({ min: 1 }).withMessage('First name is required'),
|
||||||
|
body('lastName').isLength({ min: 1 }).withMessage('Last name is required'),
|
||||||
body('username').isLength({ min: 1 }).withMessage('Username is required'),
|
body('username').isLength({ min: 1 }).withMessage('Username is required'),
|
||||||
body('email').isEmail().withMessage('Valid email is required'),
|
body('email').isEmail().withMessage('Valid email is required'),
|
||||||
body('password').isLength({ min: 8 }).withMessage('Password must be at least 8 characters for security')
|
body('password').isLength({ min: 8 }).withMessage('Password must be at least 8 characters for security')
|
||||||
@@ -45,7 +48,7 @@ router.post('/setup-admin', [
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { username, email, password } = req.body;
|
const { firstName, lastName, username, email, password } = req.body;
|
||||||
|
|
||||||
// Check if any admin users already exist
|
// Check if any admin users already exist
|
||||||
const adminCount = await prisma.users.count({
|
const adminCount = await prisma.users.count({
|
||||||
@@ -84,6 +87,8 @@ router.post('/setup-admin', [
|
|||||||
username: username.trim(),
|
username: username.trim(),
|
||||||
email: email.trim(),
|
email: email.trim(),
|
||||||
password_hash: passwordHash,
|
password_hash: passwordHash,
|
||||||
|
first_name: firstName.trim(),
|
||||||
|
last_name: lastName.trim(),
|
||||||
role: 'admin',
|
role: 'admin',
|
||||||
is_active: true,
|
is_active: true,
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
@@ -98,6 +103,9 @@ router.post('/setup-admin', [
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Create default dashboard preferences for the new admin user
|
||||||
|
await createDefaultDashboardPreferences(user.id, 'admin');
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
message: 'Admin user created successfully',
|
message: 'Admin user created successfully',
|
||||||
user: user
|
user: user
|
||||||
@@ -173,7 +181,14 @@ router.post('/admin/users', authenticateToken, requireManageUsers, [
|
|||||||
return res.status(400).json({ errors: errors.array() });
|
return res.status(400).json({ errors: errors.array() });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { username, email, password, first_name, last_name, role = 'user' } = req.body;
|
const { username, email, password, first_name, last_name, role } = req.body;
|
||||||
|
|
||||||
|
// Get default user role from settings if no role specified
|
||||||
|
let userRole = role;
|
||||||
|
if (!userRole) {
|
||||||
|
const settings = await prisma.settings.findFirst();
|
||||||
|
userRole = settings?.default_user_role || 'user';
|
||||||
|
}
|
||||||
|
|
||||||
// Check if user already exists
|
// Check if user already exists
|
||||||
const existingUser = await prisma.users.findFirst({
|
const existingUser = await prisma.users.findFirst({
|
||||||
@@ -201,7 +216,7 @@ router.post('/admin/users', authenticateToken, requireManageUsers, [
|
|||||||
password_hash: passwordHash,
|
password_hash: passwordHash,
|
||||||
first_name: first_name || null,
|
first_name: first_name || null,
|
||||||
last_name: last_name || null,
|
last_name: last_name || null,
|
||||||
role,
|
role: userRole,
|
||||||
updated_at: new Date()
|
updated_at: new Date()
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
@@ -216,6 +231,9 @@ router.post('/admin/users', authenticateToken, requireManageUsers, [
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Create default dashboard preferences for the new user
|
||||||
|
await createDefaultDashboardPreferences(user.id, userRole);
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
message: 'User created successfully',
|
message: 'User created successfully',
|
||||||
user
|
user
|
||||||
@@ -449,6 +467,8 @@ router.get('/signup-enabled', async (req, res) => {
|
|||||||
|
|
||||||
// Public signup endpoint
|
// Public signup endpoint
|
||||||
router.post('/signup', [
|
router.post('/signup', [
|
||||||
|
body('firstName').isLength({ min: 1 }).withMessage('First name is required'),
|
||||||
|
body('lastName').isLength({ min: 1 }).withMessage('Last name is required'),
|
||||||
body('username').isLength({ min: 3 }).withMessage('Username must be at least 3 characters'),
|
body('username').isLength({ min: 3 }).withMessage('Username must be at least 3 characters'),
|
||||||
body('email').isEmail().withMessage('Valid email is required'),
|
body('email').isEmail().withMessage('Valid email is required'),
|
||||||
body('password').isLength({ min: 6 }).withMessage('Password must be at least 6 characters')
|
body('password').isLength({ min: 6 }).withMessage('Password must be at least 6 characters')
|
||||||
@@ -465,7 +485,7 @@ router.post('/signup', [
|
|||||||
return res.status(400).json({ errors: errors.array() });
|
return res.status(400).json({ errors: errors.array() });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { username, email, password } = req.body;
|
const { firstName, lastName, username, email, password } = req.body;
|
||||||
|
|
||||||
// Check if user already exists
|
// Check if user already exists
|
||||||
const existingUser = await prisma.users.findFirst({
|
const existingUser = await prisma.users.findFirst({
|
||||||
@@ -484,14 +504,19 @@ router.post('/signup', [
|
|||||||
// Hash password
|
// Hash password
|
||||||
const passwordHash = await bcrypt.hash(password, 12);
|
const passwordHash = await bcrypt.hash(password, 12);
|
||||||
|
|
||||||
// Create user with default 'user' role
|
// Get default user role from settings or environment variable
|
||||||
|
const defaultRole = settings?.default_user_role || process.env.DEFAULT_USER_ROLE || 'user';
|
||||||
|
|
||||||
|
// Create user with default role from settings
|
||||||
const user = await prisma.users.create({
|
const user = await prisma.users.create({
|
||||||
data: {
|
data: {
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
username,
|
username,
|
||||||
email,
|
email,
|
||||||
password_hash: passwordHash,
|
password_hash: passwordHash,
|
||||||
role: 'user',
|
first_name: firstName.trim(),
|
||||||
|
last_name: lastName.trim(),
|
||||||
|
role: defaultRole,
|
||||||
updated_at: new Date()
|
updated_at: new Date()
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
@@ -504,6 +529,9 @@ router.post('/signup', [
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Create default dashboard preferences for the new user
|
||||||
|
await createDefaultDashboardPreferences(user.id, defaultRole);
|
||||||
|
|
||||||
console.log(`New user registered: ${user.username} (${user.email})`);
|
console.log(`New user registered: ${user.username} (${user.email})`);
|
||||||
|
|
||||||
// Generate token for immediate login
|
// Generate token for immediate login
|
||||||
|
|||||||
@@ -2,10 +2,115 @@ const express = require('express');
|
|||||||
const { body, validationResult } = require('express-validator');
|
const { body, validationResult } = require('express-validator');
|
||||||
const { PrismaClient } = require('@prisma/client');
|
const { PrismaClient } = require('@prisma/client');
|
||||||
const { authenticateToken } = require('../middleware/auth');
|
const { authenticateToken } = require('../middleware/auth');
|
||||||
|
const { v4: uuidv4 } = require('uuid');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
// Helper function to get user permissions based on role
|
||||||
|
async function getUserPermissions(userRole) {
|
||||||
|
try {
|
||||||
|
const permissions = await prisma.role_permissions.findUnique({
|
||||||
|
where: { role: userRole }
|
||||||
|
});
|
||||||
|
|
||||||
|
// If no specific permissions found, return default admin permissions (for backward compatibility)
|
||||||
|
if (!permissions) {
|
||||||
|
console.warn(`No permissions found for role: ${userRole}, defaulting to admin access`);
|
||||||
|
return {
|
||||||
|
can_view_dashboard: true,
|
||||||
|
can_view_hosts: true,
|
||||||
|
can_manage_hosts: true,
|
||||||
|
can_view_packages: true,
|
||||||
|
can_manage_packages: true,
|
||||||
|
can_view_users: true,
|
||||||
|
can_manage_users: true,
|
||||||
|
can_view_reports: true,
|
||||||
|
can_export_data: true,
|
||||||
|
can_manage_settings: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return permissions;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching user permissions:', error);
|
||||||
|
// Return admin permissions as fallback
|
||||||
|
return {
|
||||||
|
can_view_dashboard: true,
|
||||||
|
can_view_hosts: true,
|
||||||
|
can_manage_hosts: true,
|
||||||
|
can_view_packages: true,
|
||||||
|
can_manage_packages: true,
|
||||||
|
can_view_users: true,
|
||||||
|
can_manage_users: true,
|
||||||
|
can_view_reports: true,
|
||||||
|
can_export_data: true,
|
||||||
|
can_manage_settings: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to create permission-based dashboard preferences for a new user
|
||||||
|
async function createDefaultDashboardPreferences(userId, userRole = 'user') {
|
||||||
|
try {
|
||||||
|
// Get user's actual permissions
|
||||||
|
const permissions = await getUserPermissions(userRole);
|
||||||
|
|
||||||
|
// Define all possible dashboard cards with their required permissions
|
||||||
|
const allCards = [
|
||||||
|
// Host-related cards
|
||||||
|
{ cardId: 'totalHosts', requiredPermission: 'can_view_hosts', order: 0 },
|
||||||
|
{ cardId: 'hostsNeedingUpdates', requiredPermission: 'can_view_hosts', order: 1 },
|
||||||
|
{ cardId: 'upToDateHosts', requiredPermission: 'can_view_hosts', order: 2 },
|
||||||
|
{ cardId: 'totalHostGroups', requiredPermission: 'can_view_hosts', order: 3 },
|
||||||
|
|
||||||
|
// Package-related cards
|
||||||
|
{ cardId: 'totalOutdatedPackages', requiredPermission: 'can_view_packages', order: 4 },
|
||||||
|
{ cardId: 'securityUpdates', requiredPermission: 'can_view_packages', order: 5 },
|
||||||
|
{ cardId: 'packagePriority', requiredPermission: 'can_view_packages', order: 6 },
|
||||||
|
|
||||||
|
// Repository-related cards
|
||||||
|
{ cardId: 'totalRepos', requiredPermission: 'can_view_hosts', order: 7 }, // Repos are host-related
|
||||||
|
|
||||||
|
// User management cards (admin only)
|
||||||
|
{ cardId: 'totalUsers', requiredPermission: 'can_view_users', order: 8 },
|
||||||
|
{ cardId: 'recentUsers', requiredPermission: 'can_view_users', order: 9 },
|
||||||
|
|
||||||
|
// System/Report cards
|
||||||
|
{ cardId: 'osDistribution', requiredPermission: 'can_view_reports', order: 10 },
|
||||||
|
{ cardId: 'osDistributionBar', requiredPermission: 'can_view_reports', order: 11 },
|
||||||
|
{ cardId: 'updateStatus', requiredPermission: 'can_view_reports', order: 12 },
|
||||||
|
{ cardId: 'recentCollection', requiredPermission: 'can_view_hosts', order: 13 }, // Collection is host-related
|
||||||
|
{ cardId: 'quickStats', requiredPermission: 'can_view_dashboard', order: 14 }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Filter cards based on user's permissions
|
||||||
|
const allowedCards = allCards.filter(card => {
|
||||||
|
return permissions[card.requiredPermission] === true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create preferences data
|
||||||
|
const preferencesData = allowedCards.map((card) => ({
|
||||||
|
id: uuidv4(),
|
||||||
|
user_id: userId,
|
||||||
|
card_id: card.cardId,
|
||||||
|
enabled: true,
|
||||||
|
order: card.order, // Preserve original order from allCards
|
||||||
|
created_at: new Date(),
|
||||||
|
updated_at: new Date()
|
||||||
|
}));
|
||||||
|
|
||||||
|
await prisma.dashboard_preferences.createMany({
|
||||||
|
data: preferencesData
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Permission-based dashboard preferences created for user ${userId} with role ${userRole}: ${allowedCards.length} cards`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating default dashboard preferences:', error);
|
||||||
|
// Don't throw error - this shouldn't break user creation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get user's dashboard preferences
|
// Get user's dashboard preferences
|
||||||
router.get('/', authenticateToken, async (req, res) => {
|
router.get('/', authenticateToken, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
@@ -69,22 +174,24 @@ router.put('/', authenticateToken, [
|
|||||||
// Get default dashboard card configuration
|
// Get default dashboard card configuration
|
||||||
router.get('/defaults', authenticateToken, async (req, res) => {
|
router.get('/defaults', authenticateToken, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
// Default configuration based on iby's (Muhammad Ibrahim) preferred layout
|
||||||
|
// This provides a comprehensive dashboard view for all new users
|
||||||
const defaultCards = [
|
const defaultCards = [
|
||||||
{ cardId: 'totalHosts', title: 'Total Hosts', icon: 'Server', enabled: true, order: 0 },
|
{ cardId: 'totalHosts', title: 'Total Hosts', icon: 'Server', enabled: true, order: 0 },
|
||||||
{ cardId: 'hostsNeedingUpdates', title: 'Needs Updating', icon: 'AlertTriangle', enabled: true, order: 1 },
|
{ cardId: 'hostsNeedingUpdates', title: 'Needs Updating', icon: 'AlertTriangle', enabled: true, order: 1 },
|
||||||
{ cardId: 'totalOutdatedPackages', title: 'Outdated Packages', icon: 'Package', enabled: true, order: 2 },
|
{ cardId: 'totalOutdatedPackages', title: 'Outdated Packages', icon: 'Package', enabled: true, order: 2 },
|
||||||
{ cardId: 'securityUpdates', title: 'Security Updates', icon: 'Shield', enabled: true, order: 3 },
|
{ cardId: 'securityUpdates', title: 'Security Updates', icon: 'Shield', enabled: true, order: 3 },
|
||||||
{ cardId: 'upToDateHosts', title: 'Up to date', icon: 'CheckCircle', enabled: true, order: 4 },
|
{ cardId: 'totalHostGroups', title: 'Host Groups', icon: 'Folder', enabled: true, order: 4 },
|
||||||
{ cardId: 'totalHostGroups', title: 'Host Groups', icon: 'Folder', enabled: false, order: 5 },
|
{ cardId: 'upToDateHosts', title: 'Up to date', icon: 'CheckCircle', enabled: true, order: 5 },
|
||||||
{ cardId: 'totalUsers', title: 'Users', icon: 'Users', enabled: false, order: 6 },
|
{ cardId: 'totalRepos', title: 'Repositories', icon: 'GitBranch', enabled: true, order: 6 },
|
||||||
{ cardId: 'totalRepos', title: 'Repositories', icon: 'GitBranch', enabled: false, order: 7 },
|
{ cardId: 'totalUsers', title: 'Users', icon: 'Users', enabled: true, order: 7 },
|
||||||
{ cardId: 'osDistribution', title: 'OS Distribution', icon: 'BarChart3', enabled: true, order: 8 },
|
{ cardId: 'osDistribution', title: 'OS Distribution', icon: 'BarChart3', enabled: true, order: 8 },
|
||||||
{ cardId: 'osDistributionBar', title: 'OS Distribution (Bar)', icon: 'BarChart3', enabled: false, order: 9 },
|
{ cardId: 'osDistributionBar', title: 'OS Distribution (Bar)', icon: 'BarChart3', enabled: true, order: 9 },
|
||||||
{ cardId: 'updateStatus', title: 'Update Status', icon: 'BarChart3', enabled: true, order: 10 },
|
{ cardId: 'recentCollection', title: 'Recent Collection', icon: 'Server', enabled: true, order: 10 },
|
||||||
{ cardId: 'packagePriority', title: 'Package Priority', icon: 'BarChart3', enabled: true, order: 11 },
|
{ cardId: 'updateStatus', title: 'Update Status', icon: 'BarChart3', enabled: true, order: 11 },
|
||||||
{ cardId: 'quickStats', title: 'Quick Stats', icon: 'TrendingUp', enabled: true, order: 12 },
|
{ cardId: 'packagePriority', title: 'Package Priority', icon: 'BarChart3', enabled: true, order: 12 },
|
||||||
{ cardId: 'recentUsers', title: 'Recent Users Logged in', icon: 'Users', enabled: true, order: 13 },
|
{ cardId: 'recentUsers', title: 'Recent Users Logged in', icon: 'Users', enabled: true, order: 13 },
|
||||||
{ cardId: 'recentCollection', title: 'Recent Collection', icon: 'Server', enabled: true, order: 14 }
|
{ cardId: 'quickStats', title: 'Quick Stats', icon: 'TrendingUp', enabled: true, order: 14 }
|
||||||
];
|
];
|
||||||
|
|
||||||
res.json(defaultCards);
|
res.json(defaultCards);
|
||||||
@@ -94,4 +201,4 @@ router.get('/defaults', authenticateToken, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = { router, createDefaultDashboardPreferences };
|
||||||
|
|||||||
@@ -59,9 +59,9 @@ router.put('/roles/:role', authenticateToken, requireManageSettings, async (req,
|
|||||||
can_manage_settings
|
can_manage_settings
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
// Prevent modifying admin role permissions (admin should always have full access)
|
// Prevent modifying admin and user role permissions (built-in roles)
|
||||||
if (role === 'admin') {
|
if (role === 'admin' || role === 'user') {
|
||||||
return res.status(400).json({ error: 'Cannot modify admin role permissions' });
|
return res.status(400).json({ error: `Cannot modify ${role} role permissions - this is a built-in role` });
|
||||||
}
|
}
|
||||||
|
|
||||||
const permissions = await prisma.role_permissions.upsert({
|
const permissions = await prisma.role_permissions.upsert({
|
||||||
@@ -111,9 +111,9 @@ router.delete('/roles/:role', authenticateToken, requireManageSettings, async (r
|
|||||||
try {
|
try {
|
||||||
const { role } = req.params;
|
const { role } = req.params;
|
||||||
|
|
||||||
// Prevent deleting admin role
|
// Prevent deleting admin and user roles (built-in roles)
|
||||||
if (role === 'admin') {
|
if (role === 'admin' || role === 'user') {
|
||||||
return res.status(400).json({ error: 'Cannot delete admin role' });
|
return res.status(400).json({ error: `Cannot delete ${role} role - this is a built-in role` });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if any users are using this role
|
// Check if any users are using this role
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ router.put('/', authenticateToken, requireManageSettings, [
|
|||||||
body('updateInterval').isInt({ min: 5, max: 1440 }).withMessage('Update interval must be between 5 and 1440 minutes'),
|
body('updateInterval').isInt({ min: 5, max: 1440 }).withMessage('Update interval must be between 5 and 1440 minutes'),
|
||||||
body('autoUpdate').isBoolean().withMessage('Auto update must be a boolean'),
|
body('autoUpdate').isBoolean().withMessage('Auto update must be a boolean'),
|
||||||
body('signupEnabled').isBoolean().withMessage('Signup enabled must be a boolean'),
|
body('signupEnabled').isBoolean().withMessage('Signup enabled must be a boolean'),
|
||||||
|
body('defaultUserRole').optional().isLength({ min: 1 }).withMessage('Default user role must be a non-empty string'),
|
||||||
body('githubRepoUrl').optional().isLength({ min: 1 }).withMessage('GitHub repo URL must be a non-empty string'),
|
body('githubRepoUrl').optional().isLength({ min: 1 }).withMessage('GitHub repo URL must be a non-empty string'),
|
||||||
body('repositoryType').optional().isIn(['public', 'private']).withMessage('Repository type must be public or private'),
|
body('repositoryType').optional().isIn(['public', 'private']).withMessage('Repository type must be public or private'),
|
||||||
body('sshKeyPath').optional().custom((value) => {
|
body('sshKeyPath').optional().custom((value) => {
|
||||||
@@ -130,7 +131,7 @@ router.put('/', authenticateToken, requireManageSettings, [
|
|||||||
return res.status(400).json({ errors: errors.array() });
|
return res.status(400).json({ errors: errors.array() });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { serverProtocol, serverHost, serverPort, updateInterval, autoUpdate, signupEnabled, githubRepoUrl, repositoryType, sshKeyPath } = req.body;
|
const { serverProtocol, serverHost, serverPort, updateInterval, autoUpdate, signupEnabled, defaultUserRole, githubRepoUrl, repositoryType, sshKeyPath } = req.body;
|
||||||
|
|
||||||
// Get current settings to check for update interval changes
|
// Get current settings to check for update interval changes
|
||||||
const currentSettings = await getSettings();
|
const currentSettings = await getSettings();
|
||||||
@@ -144,6 +145,7 @@ router.put('/', authenticateToken, requireManageSettings, [
|
|||||||
update_interval: updateInterval || 60,
|
update_interval: updateInterval || 60,
|
||||||
auto_update: autoUpdate || false,
|
auto_update: autoUpdate || false,
|
||||||
signup_enabled: signupEnabled || false,
|
signup_enabled: signupEnabled || false,
|
||||||
|
default_user_role: defaultUserRole || process.env.DEFAULT_USER_ROLE || 'user',
|
||||||
github_repo_url: githubRepoUrl !== undefined ? githubRepoUrl : 'git@github.com:9technologygroup/patchmon.net.git',
|
github_repo_url: githubRepoUrl !== undefined ? githubRepoUrl : 'git@github.com:9technologygroup/patchmon.net.git',
|
||||||
repository_type: repositoryType || 'public',
|
repository_type: repositoryType || 'public',
|
||||||
ssh_key_path: sshKeyPath || null,
|
ssh_key_path: sshKeyPath || null,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const packageRoutes = require('./routes/packageRoutes');
|
|||||||
const dashboardRoutes = require('./routes/dashboardRoutes');
|
const dashboardRoutes = require('./routes/dashboardRoutes');
|
||||||
const permissionsRoutes = require('./routes/permissionsRoutes');
|
const permissionsRoutes = require('./routes/permissionsRoutes');
|
||||||
const settingsRoutes = require('./routes/settingsRoutes');
|
const settingsRoutes = require('./routes/settingsRoutes');
|
||||||
const dashboardPreferencesRoutes = require('./routes/dashboardPreferencesRoutes');
|
const { router: dashboardPreferencesRoutes, createDefaultDashboardPreferences } = require('./routes/dashboardPreferencesRoutes');
|
||||||
const repositoryRoutes = require('./routes/repositoryRoutes');
|
const repositoryRoutes = require('./routes/repositoryRoutes');
|
||||||
const versionRoutes = require('./routes/versionRoutes');
|
const versionRoutes = require('./routes/versionRoutes');
|
||||||
const tfaRoutes = require('./routes/tfaRoutes');
|
const tfaRoutes = require('./routes/tfaRoutes');
|
||||||
@@ -188,6 +188,111 @@ async function checkAndImportAgentVersion() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function to check and create default role permissions on startup
|
||||||
|
async function checkAndCreateRolePermissions() {
|
||||||
|
console.log('🔐 Starting role permissions auto-creation check...');
|
||||||
|
|
||||||
|
// Skip if auto-creation is disabled
|
||||||
|
if (process.env.AUTO_CREATE_ROLE_PERMISSIONS === 'false') {
|
||||||
|
console.log('❌ Auto-creation of role permissions is disabled');
|
||||||
|
if (process.env.ENABLE_LOGGING === 'true') {
|
||||||
|
logger.info('Auto-creation of role permissions is disabled');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
// Define default roles and permissions
|
||||||
|
const defaultRoles = [
|
||||||
|
{
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
role: 'admin',
|
||||||
|
can_view_dashboard: true,
|
||||||
|
can_view_hosts: true,
|
||||||
|
can_manage_hosts: true,
|
||||||
|
can_view_packages: true,
|
||||||
|
can_manage_packages: true,
|
||||||
|
can_view_users: true,
|
||||||
|
can_manage_users: true,
|
||||||
|
can_view_reports: true,
|
||||||
|
can_export_data: true,
|
||||||
|
can_manage_settings: true,
|
||||||
|
created_at: new Date(),
|
||||||
|
updated_at: new Date()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
role: 'user',
|
||||||
|
can_view_dashboard: true,
|
||||||
|
can_view_hosts: true,
|
||||||
|
can_manage_hosts: false,
|
||||||
|
can_view_packages: true,
|
||||||
|
can_manage_packages: false,
|
||||||
|
can_view_users: false,
|
||||||
|
can_manage_users: false,
|
||||||
|
can_view_reports: true,
|
||||||
|
can_export_data: false,
|
||||||
|
can_manage_settings: false,
|
||||||
|
created_at: new Date(),
|
||||||
|
updated_at: new Date()
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const createdRoles = [];
|
||||||
|
const existingRoles = [];
|
||||||
|
|
||||||
|
for (const roleData of defaultRoles) {
|
||||||
|
// Check if role already exists
|
||||||
|
const existingRole = await prisma.role_permissions.findUnique({
|
||||||
|
where: { role: roleData.role }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingRole) {
|
||||||
|
console.log(`✅ Role '${roleData.role}' already exists in database`);
|
||||||
|
existingRoles.push(existingRole);
|
||||||
|
if (process.env.ENABLE_LOGGING === 'true') {
|
||||||
|
logger.info(`Role '${roleData.role}' already exists in database`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Create new role permission
|
||||||
|
const permission = await prisma.role_permissions.create({
|
||||||
|
data: roleData
|
||||||
|
});
|
||||||
|
createdRoles.push(permission);
|
||||||
|
console.log(`🆕 Created role '${roleData.role}' with permissions`);
|
||||||
|
if (process.env.ENABLE_LOGGING === 'true') {
|
||||||
|
logger.info(`Created role '${roleData.role}' with permissions`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (createdRoles.length > 0) {
|
||||||
|
console.log(`🎉 Successfully auto-created ${createdRoles.length} role permissions on startup`);
|
||||||
|
console.log('📋 Created roles:');
|
||||||
|
createdRoles.forEach(role => {
|
||||||
|
console.log(` • ${role.role}: dashboard=${role.can_view_dashboard}, hosts=${role.can_manage_hosts}, packages=${role.can_manage_packages}, users=${role.can_manage_users}, settings=${role.can_manage_settings}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (process.env.ENABLE_LOGGING === 'true') {
|
||||||
|
logger.info(`✅ Auto-created ${createdRoles.length} role permissions on startup`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(`✅ All default role permissions already exist (${existingRoles.length} roles verified)`);
|
||||||
|
if (process.env.ENABLE_LOGGING === 'true') {
|
||||||
|
logger.info(`All default role permissions already exist (${existingRoles.length} roles verified)`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to check/create role permissions on startup:', error.message);
|
||||||
|
if (process.env.ENABLE_LOGGING === 'true') {
|
||||||
|
logger.error('Failed to check/create role permissions on startup:', error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize logger - only if logging is enabled
|
// Initialize logger - only if logging is enabled
|
||||||
const logger = process.env.ENABLE_LOGGING === 'true' ? winston.createLogger({
|
const logger = process.env.ENABLE_LOGGING === 'true' ? winston.createLogger({
|
||||||
level: process.env.LOG_LEVEL || 'info',
|
level: process.env.LOG_LEVEL || 'info',
|
||||||
@@ -405,6 +510,201 @@ process.on('SIGTERM', async () => {
|
|||||||
process.exit(0);
|
process.exit(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Initialize dashboard preferences for all users
|
||||||
|
async function initializeDashboardPreferences() {
|
||||||
|
try {
|
||||||
|
console.log('🔧 Initializing dashboard preferences for all users...');
|
||||||
|
|
||||||
|
// Get all users
|
||||||
|
const users = await prisma.users.findMany({
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
username: true,
|
||||||
|
email: true,
|
||||||
|
role: true,
|
||||||
|
dashboard_preferences: {
|
||||||
|
select: {
|
||||||
|
card_id: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (users.length === 0) {
|
||||||
|
console.log('ℹ️ No users found in database');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`📊 Found ${users.length} users to initialize`);
|
||||||
|
|
||||||
|
let initializedCount = 0;
|
||||||
|
let updatedCount = 0;
|
||||||
|
|
||||||
|
for (const user of users) {
|
||||||
|
const hasPreferences = user.dashboard_preferences.length > 0;
|
||||||
|
|
||||||
|
// Get permission-based preferences for this user's role
|
||||||
|
const expectedPreferences = await getPermissionBasedPreferences(user.role);
|
||||||
|
const expectedCardCount = expectedPreferences.length;
|
||||||
|
|
||||||
|
if (!hasPreferences) {
|
||||||
|
// User has no preferences - create them
|
||||||
|
console.log(`⚙️ Creating preferences for ${user.username} (${user.role})`);
|
||||||
|
|
||||||
|
const preferencesData = expectedPreferences.map(pref => ({
|
||||||
|
id: require('uuid').v4(),
|
||||||
|
user_id: user.id,
|
||||||
|
card_id: pref.cardId,
|
||||||
|
enabled: pref.enabled,
|
||||||
|
order: pref.order,
|
||||||
|
created_at: new Date(),
|
||||||
|
updated_at: new Date()
|
||||||
|
}));
|
||||||
|
|
||||||
|
await prisma.dashboard_preferences.createMany({
|
||||||
|
data: preferencesData
|
||||||
|
});
|
||||||
|
|
||||||
|
initializedCount++;
|
||||||
|
console.log(` ✅ Created ${expectedCardCount} cards based on permissions`);
|
||||||
|
} else {
|
||||||
|
// User already has preferences - check if they need updating
|
||||||
|
const currentCardCount = user.dashboard_preferences.length;
|
||||||
|
|
||||||
|
if (currentCardCount !== expectedCardCount) {
|
||||||
|
console.log(`🔄 Updating preferences for ${user.username} (${user.role}) - ${currentCardCount} → ${expectedCardCount} cards`);
|
||||||
|
|
||||||
|
// Delete existing preferences
|
||||||
|
await prisma.dashboard_preferences.deleteMany({
|
||||||
|
where: { user_id: user.id }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create new preferences based on permissions
|
||||||
|
const preferencesData = expectedPreferences.map(pref => ({
|
||||||
|
id: require('uuid').v4(),
|
||||||
|
user_id: user.id,
|
||||||
|
card_id: pref.cardId,
|
||||||
|
enabled: pref.enabled,
|
||||||
|
order: pref.order,
|
||||||
|
created_at: new Date(),
|
||||||
|
updated_at: new Date()
|
||||||
|
}));
|
||||||
|
|
||||||
|
await prisma.dashboard_preferences.createMany({
|
||||||
|
data: preferencesData
|
||||||
|
});
|
||||||
|
|
||||||
|
updatedCount++;
|
||||||
|
console.log(` ✅ Updated to ${expectedCardCount} cards based on permissions`);
|
||||||
|
} else {
|
||||||
|
console.log(`✅ ${user.username} already has correct preferences (${currentCardCount} cards)`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n📋 Dashboard Preferences Initialization Complete:`);
|
||||||
|
console.log(` - New users initialized: ${initializedCount}`);
|
||||||
|
console.log(` - Existing users updated: ${updatedCount}`);
|
||||||
|
console.log(` - Users with correct preferences: ${users.length - initializedCount - updatedCount}`);
|
||||||
|
console.log(`\n🎯 Permission-based preferences:`);
|
||||||
|
console.log(` - Cards are now assigned based on actual user permissions`);
|
||||||
|
console.log(` - Each card requires specific permissions (can_view_hosts, can_view_users, etc.)`);
|
||||||
|
console.log(` - Users only see cards they have permission to access`);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error initializing dashboard preferences:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to get user permissions based on role
|
||||||
|
async function getUserPermissions(userRole) {
|
||||||
|
try {
|
||||||
|
const permissions = await prisma.role_permissions.findUnique({
|
||||||
|
where: { role: userRole }
|
||||||
|
});
|
||||||
|
|
||||||
|
// If no specific permissions found, return default admin permissions (for backward compatibility)
|
||||||
|
if (!permissions) {
|
||||||
|
console.warn(`No permissions found for role: ${userRole}, defaulting to admin access`);
|
||||||
|
return {
|
||||||
|
can_view_dashboard: true,
|
||||||
|
can_view_hosts: true,
|
||||||
|
can_manage_hosts: true,
|
||||||
|
can_view_packages: true,
|
||||||
|
can_manage_packages: true,
|
||||||
|
can_view_users: true,
|
||||||
|
can_manage_users: true,
|
||||||
|
can_view_reports: true,
|
||||||
|
can_export_data: true,
|
||||||
|
can_manage_settings: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return permissions;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching user permissions:', error);
|
||||||
|
// Return admin permissions as fallback
|
||||||
|
return {
|
||||||
|
can_view_dashboard: true,
|
||||||
|
can_view_hosts: true,
|
||||||
|
can_manage_hosts: true,
|
||||||
|
can_view_packages: true,
|
||||||
|
can_manage_packages: true,
|
||||||
|
can_view_users: true,
|
||||||
|
can_manage_users: true,
|
||||||
|
can_view_reports: true,
|
||||||
|
can_export_data: true,
|
||||||
|
can_manage_settings: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to get permission-based dashboard preferences for a role
|
||||||
|
async function getPermissionBasedPreferences(userRole) {
|
||||||
|
// Get user's actual permissions
|
||||||
|
const permissions = await getUserPermissions(userRole);
|
||||||
|
|
||||||
|
// Define all possible dashboard cards with their required permissions
|
||||||
|
const allCards = [
|
||||||
|
// Host-related cards
|
||||||
|
{ cardId: 'totalHosts', requiredPermission: 'can_view_hosts', order: 0 },
|
||||||
|
{ cardId: 'hostsNeedingUpdates', requiredPermission: 'can_view_hosts', order: 1 },
|
||||||
|
{ cardId: 'upToDateHosts', requiredPermission: 'can_view_hosts', order: 2 },
|
||||||
|
{ cardId: 'totalHostGroups', requiredPermission: 'can_view_hosts', order: 3 },
|
||||||
|
|
||||||
|
// Package-related cards
|
||||||
|
{ cardId: 'totalOutdatedPackages', requiredPermission: 'can_view_packages', order: 4 },
|
||||||
|
{ cardId: 'securityUpdates', requiredPermission: 'can_view_packages', order: 5 },
|
||||||
|
{ cardId: 'packagePriority', requiredPermission: 'can_view_packages', order: 6 },
|
||||||
|
|
||||||
|
// Repository-related cards
|
||||||
|
{ cardId: 'totalRepos', requiredPermission: 'can_view_hosts', order: 7 }, // Repos are host-related
|
||||||
|
|
||||||
|
// User management cards (admin only)
|
||||||
|
{ cardId: 'totalUsers', requiredPermission: 'can_view_users', order: 8 },
|
||||||
|
{ cardId: 'recentUsers', requiredPermission: 'can_view_users', order: 9 },
|
||||||
|
|
||||||
|
// System/Report cards
|
||||||
|
{ cardId: 'osDistribution', requiredPermission: 'can_view_reports', order: 10 },
|
||||||
|
{ cardId: 'osDistributionBar', requiredPermission: 'can_view_reports', order: 11 },
|
||||||
|
{ cardId: 'updateStatus', requiredPermission: 'can_view_reports', order: 12 },
|
||||||
|
{ cardId: 'recentCollection', requiredPermission: 'can_view_hosts', order: 13 }, // Collection is host-related
|
||||||
|
{ cardId: 'quickStats', requiredPermission: 'can_view_dashboard', order: 14 }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Filter cards based on user's permissions
|
||||||
|
const allowedCards = allCards.filter(card => {
|
||||||
|
return permissions[card.requiredPermission] === true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return allowedCards.map((card) => ({
|
||||||
|
cardId: card.cardId,
|
||||||
|
enabled: true,
|
||||||
|
order: card.order // Preserve original order from allCards
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
// Start server with database health check
|
// Start server with database health check
|
||||||
async function startServer() {
|
async function startServer() {
|
||||||
try {
|
try {
|
||||||
@@ -430,7 +730,12 @@ async function startServer() {
|
|||||||
|
|
||||||
// Check and import agent version on startup
|
// Check and import agent version on startup
|
||||||
await checkAndImportAgentVersion();
|
await checkAndImportAgentVersion();
|
||||||
|
|
||||||
|
// Check and create default role permissions on startup
|
||||||
|
await checkAndCreateRolePermissions();
|
||||||
|
|
||||||
|
// Initialize dashboard preferences for all users
|
||||||
|
await initializeDashboardPreferences();
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
if (process.env.ENABLE_LOGGING === 'true') {
|
if (process.env.ENABLE_LOGGING === 'true') {
|
||||||
logger.info(`Server running on port ${PORT}`);
|
logger.info(`Server running on port ${PORT}`);
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ const FirstTimeAdminSetup = () => {
|
|||||||
username: '',
|
username: '',
|
||||||
email: '',
|
email: '',
|
||||||
password: '',
|
password: '',
|
||||||
confirmPassword: ''
|
confirmPassword: '',
|
||||||
|
firstName: '',
|
||||||
|
lastName: ''
|
||||||
})
|
})
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const [error, setError] = useState('')
|
const [error, setError] = useState('')
|
||||||
@@ -25,6 +27,14 @@ const FirstTimeAdminSetup = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const validateForm = () => {
|
const validateForm = () => {
|
||||||
|
if (!formData.firstName.trim()) {
|
||||||
|
setError('First name is required')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!formData.lastName.trim()) {
|
||||||
|
setError('Last name is required')
|
||||||
|
return false
|
||||||
|
}
|
||||||
if (!formData.username.trim()) {
|
if (!formData.username.trim()) {
|
||||||
setError('Username is required')
|
setError('Username is required')
|
||||||
return false
|
return false
|
||||||
@@ -69,7 +79,9 @@ const FirstTimeAdminSetup = () => {
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
username: formData.username.trim(),
|
username: formData.username.trim(),
|
||||||
email: formData.email.trim(),
|
email: formData.email.trim(),
|
||||||
password: formData.password
|
password: formData.password,
|
||||||
|
firstName: formData.firstName.trim(),
|
||||||
|
lastName: formData.lastName.trim()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -145,6 +157,41 @@ const FirstTimeAdminSetup = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-6">
|
<form onSubmit={handleSubmit} className="space-y-6">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label htmlFor="firstName" className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-2">
|
||||||
|
First Name
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="firstName"
|
||||||
|
name="firstName"
|
||||||
|
value={formData.firstName}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
className="input w-full"
|
||||||
|
placeholder="Enter your first name"
|
||||||
|
required
|
||||||
|
disabled={isLoading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="lastName" className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-2">
|
||||||
|
Last Name
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="lastName"
|
||||||
|
name="lastName"
|
||||||
|
value={formData.lastName}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
className="input w-full"
|
||||||
|
placeholder="Enter your last name"
|
||||||
|
required
|
||||||
|
disabled={isLoading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="username" className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-2">
|
<label htmlFor="username" className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-2">
|
||||||
Username
|
Username
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ const Layout = ({ children }) => {
|
|||||||
const [userMenuOpen, setUserMenuOpen] = useState(false)
|
const [userMenuOpen, setUserMenuOpen] = useState(false)
|
||||||
const [githubStars, setGithubStars] = useState(null)
|
const [githubStars, setGithubStars] = useState(null)
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const { user, logout, canViewHosts, canManageHosts, canViewPackages, canViewUsers, canManageUsers, canManageSettings } = useAuth()
|
const { user, logout, canViewDashboard, canViewHosts, canManageHosts, canViewPackages, canViewUsers, canManageUsers, canViewReports, canExportData, canManageSettings } = useAuth()
|
||||||
const { updateAvailable } = useUpdateNotification()
|
const { updateAvailable } = useUpdateNotification()
|
||||||
const userMenuRef = useRef(null)
|
const userMenuRef = useRef(null)
|
||||||
|
|
||||||
@@ -66,44 +66,103 @@ const Layout = ({ children }) => {
|
|||||||
staleTime: 300000, // Consider data stale after 5 minutes
|
staleTime: 300000, // Consider data stale after 5 minutes
|
||||||
})
|
})
|
||||||
|
|
||||||
const navigation = [
|
// Build navigation based on permissions
|
||||||
{ name: 'Dashboard', href: '/', icon: Home },
|
const buildNavigation = () => {
|
||||||
{
|
const nav = []
|
||||||
section: 'Inventory',
|
|
||||||
items: [
|
// Dashboard - only show if user can view dashboard
|
||||||
...(canViewHosts() ? [{ name: 'Hosts', href: '/hosts', icon: Server }] : []),
|
if (canViewDashboard()) {
|
||||||
...(canViewPackages() ? [{ name: 'Packages', href: '/packages', icon: Package }] : []),
|
nav.push({ name: 'Dashboard', href: '/', icon: Home })
|
||||||
...(canViewHosts() ? [{ name: 'Repos', href: '/repositories', icon: GitBranch }] : []),
|
}
|
||||||
{ name: 'Services', href: '/services', icon: Activity, comingSoon: true },
|
|
||||||
{ name: 'Docker', href: '/docker', icon: Container, comingSoon: true },
|
// Inventory section - only show if user has any inventory permissions
|
||||||
{ name: 'Reporting', href: '/reporting', icon: BarChart3, comingSoon: true },
|
if (canViewHosts() || canViewPackages() || canViewReports()) {
|
||||||
]
|
const inventoryItems = []
|
||||||
},
|
|
||||||
...(canViewUsers() || canManageUsers() ? [{
|
if (canViewHosts()) {
|
||||||
section: 'PatchMon Users',
|
inventoryItems.push({ name: 'Hosts', href: '/hosts', icon: Server })
|
||||||
items: [
|
inventoryItems.push({ name: 'Repos', href: '/repositories', icon: GitBranch })
|
||||||
...(canViewUsers() ? [{ name: 'Users', href: '/users', icon: Users }] : []),
|
}
|
||||||
...(canManageSettings() ? [{ name: 'Permissions', href: '/permissions', icon: Shield }] : []),
|
|
||||||
]
|
if (canViewPackages()) {
|
||||||
}] : []),
|
inventoryItems.push({ name: 'Packages', href: '/packages', icon: Package })
|
||||||
{
|
}
|
||||||
section: 'Settings',
|
|
||||||
items: [
|
if (canViewReports()) {
|
||||||
...(canManageHosts() ? [{
|
inventoryItems.push(
|
||||||
|
{ name: 'Services', href: '/services', icon: Activity, comingSoon: true },
|
||||||
|
{ name: 'Docker', href: '/docker', icon: Container, comingSoon: true },
|
||||||
|
{ name: 'Reporting', href: '/reporting', icon: BarChart3, comingSoon: true }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inventoryItems.length > 0) {
|
||||||
|
nav.push({
|
||||||
|
section: 'Inventory',
|
||||||
|
items: inventoryItems
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PatchMon Users section - only show if user can view/manage users
|
||||||
|
if (canViewUsers() || canManageUsers()) {
|
||||||
|
const userItems = []
|
||||||
|
|
||||||
|
if (canViewUsers()) {
|
||||||
|
userItems.push({ name: 'Users', href: '/users', icon: Users })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canManageSettings()) {
|
||||||
|
userItems.push({ name: 'Permissions', href: '/permissions', icon: Shield })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userItems.length > 0) {
|
||||||
|
nav.push({
|
||||||
|
section: 'PatchMon Users',
|
||||||
|
items: userItems
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Settings section - only show if user has any settings permissions
|
||||||
|
if (canManageSettings() || canViewReports() || canExportData()) {
|
||||||
|
const settingsItems = []
|
||||||
|
|
||||||
|
if (canManageSettings()) {
|
||||||
|
settingsItems.push({
|
||||||
name: 'PatchMon Options',
|
name: 'PatchMon Options',
|
||||||
href: '/options',
|
href: '/options',
|
||||||
icon: Settings
|
icon: Settings
|
||||||
}] : []),
|
})
|
||||||
{ name: 'Audit Log', href: '/audit-log', icon: FileText, comingSoon: true },
|
settingsItems.push({
|
||||||
...(canManageSettings() ? [{
|
|
||||||
name: 'Server Config',
|
name: 'Server Config',
|
||||||
href: '/settings',
|
href: '/settings',
|
||||||
icon: Wrench,
|
icon: Wrench,
|
||||||
showUpgradeIcon: updateAvailable
|
showUpgradeIcon: updateAvailable
|
||||||
}] : []),
|
})
|
||||||
]
|
}
|
||||||
|
|
||||||
|
if (canViewReports() || canExportData()) {
|
||||||
|
settingsItems.push({
|
||||||
|
name: 'Audit Log',
|
||||||
|
href: '/audit-log',
|
||||||
|
icon: FileText,
|
||||||
|
comingSoon: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settingsItems.length > 0) {
|
||||||
|
nav.push({
|
||||||
|
section: 'Settings',
|
||||||
|
items: settingsItems
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
|
||||||
|
return nav
|
||||||
|
}
|
||||||
|
|
||||||
|
const navigation = buildNavigation()
|
||||||
|
|
||||||
const isActive = (path) => location.pathname === path
|
const isActive = (path) => location.pathname === path
|
||||||
|
|
||||||
@@ -221,6 +280,15 @@ const Layout = ({ children }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<nav className="mt-8 flex-1 space-y-6 px-2">
|
<nav className="mt-8 flex-1 space-y-6 px-2">
|
||||||
|
{/* Show message for users with very limited permissions */}
|
||||||
|
{navigation.length === 0 && (
|
||||||
|
<div className="px-2 py-4 text-center">
|
||||||
|
<div className="text-sm text-secondary-500 dark:text-secondary-400">
|
||||||
|
<p className="mb-2">Limited access</p>
|
||||||
|
<p className="text-xs">Contact your administrator for additional permissions</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{navigation.map((item, index) => {
|
{navigation.map((item, index) => {
|
||||||
if (item.name) {
|
if (item.name) {
|
||||||
// Single item (Dashboard)
|
// Single item (Dashboard)
|
||||||
@@ -346,6 +414,15 @@ const Layout = ({ children }) => {
|
|||||||
</div>
|
</div>
|
||||||
<nav className="flex flex-1 flex-col">
|
<nav className="flex flex-1 flex-col">
|
||||||
<ul className="flex flex-1 flex-col gap-y-6">
|
<ul className="flex flex-1 flex-col gap-y-6">
|
||||||
|
{/* Show message for users with very limited permissions */}
|
||||||
|
{navigation.length === 0 && (
|
||||||
|
<li className="px-2 py-4 text-center">
|
||||||
|
<div className="text-sm text-secondary-500 dark:text-secondary-400">
|
||||||
|
<p className="mb-2">Limited access</p>
|
||||||
|
<p className="text-xs">Contact your administrator for additional permissions</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
{navigation.map((item, index) => {
|
{navigation.map((item, index) => {
|
||||||
if (item.name) {
|
if (item.name) {
|
||||||
// Single item (Dashboard)
|
// Single item (Dashboard)
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ const Login = () => {
|
|||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
username: '',
|
username: '',
|
||||||
email: '',
|
email: '',
|
||||||
password: ''
|
password: '',
|
||||||
|
firstName: '',
|
||||||
|
lastName: ''
|
||||||
})
|
})
|
||||||
const [tfaData, setTfaData] = useState({
|
const [tfaData, setTfaData] = useState({
|
||||||
token: ''
|
token: ''
|
||||||
@@ -76,8 +78,7 @@ const Login = () => {
|
|||||||
setError('')
|
setError('')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await authAPI.signup(formData.username, formData.email, formData.password)
|
const response = await authAPI.signup(formData.username, formData.email, formData.password, formData.firstName, formData.lastName)
|
||||||
|
|
||||||
if (response.data && response.data.token) {
|
if (response.data && response.data.token) {
|
||||||
// Store token and user data
|
// Store token and user data
|
||||||
localStorage.setItem('token', response.data.token)
|
localStorage.setItem('token', response.data.token)
|
||||||
@@ -162,7 +163,9 @@ const Login = () => {
|
|||||||
setFormData({
|
setFormData({
|
||||||
username: '',
|
username: '',
|
||||||
email: '',
|
email: '',
|
||||||
password: ''
|
password: '',
|
||||||
|
firstName: '',
|
||||||
|
lastName: ''
|
||||||
})
|
})
|
||||||
setError('')
|
setError('')
|
||||||
}
|
}
|
||||||
@@ -211,10 +214,53 @@ const Login = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isSignupMode && (
|
{isSignupMode && (
|
||||||
<div>
|
<>
|
||||||
<label htmlFor="email" className="block text-sm font-medium text-secondary-700">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
Email
|
<div>
|
||||||
</label>
|
<label htmlFor="firstName" className="block text-sm font-medium text-secondary-700">
|
||||||
|
First Name
|
||||||
|
</label>
|
||||||
|
<div className="mt-1 relative">
|
||||||
|
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||||
|
<User className="h-5 w-5 text-secondary-400" />
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
id="firstName"
|
||||||
|
name="firstName"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
value={formData.firstName}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
className="appearance-none rounded-md relative block w-full pl-10 pr-3 py-2 border border-secondary-300 placeholder-secondary-500 text-secondary-900 focus:outline-none focus:ring-primary-500 focus:border-primary-500 focus:z-10 sm:text-sm"
|
||||||
|
placeholder="Enter your first name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="lastName" className="block text-sm font-medium text-secondary-700">
|
||||||
|
Last Name
|
||||||
|
</label>
|
||||||
|
<div className="mt-1 relative">
|
||||||
|
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||||
|
<User className="h-5 w-5 text-secondary-400" />
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
id="lastName"
|
||||||
|
name="lastName"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
value={formData.lastName}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
className="appearance-none rounded-md relative block w-full pl-10 pr-3 py-2 border border-secondary-300 placeholder-secondary-500 text-secondary-900 focus:outline-none focus:ring-primary-500 focus:border-primary-500 focus:z-10 sm:text-sm"
|
||||||
|
placeholder="Enter your last name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="email" className="block text-sm font-medium text-secondary-700">
|
||||||
|
Email
|
||||||
|
</label>
|
||||||
<div className="mt-1 relative">
|
<div className="mt-1 relative">
|
||||||
<input
|
<input
|
||||||
id="email"
|
id="email"
|
||||||
@@ -235,6 +281,7 @@ const Login = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ const RolePermissionsCard = ({ role, isEditing, onEdit, onCancel, onSave, onDele
|
|||||||
onSave(role.role, permissions)
|
onSave(role.role, permissions)
|
||||||
}
|
}
|
||||||
|
|
||||||
const isAdminRole = role.role === 'admin'
|
const isBuiltInRole = role.role === 'admin' || role.role === 'user'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white dark:bg-secondary-800 shadow rounded-lg">
|
<div className="bg-white dark:bg-secondary-800 shadow rounded-lg">
|
||||||
@@ -183,9 +183,9 @@ const RolePermissionsCard = ({ role, isEditing, onEdit, onCancel, onSave, onDele
|
|||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Shield className="h-5 w-5 text-primary-600 mr-3" />
|
<Shield className="h-5 w-5 text-primary-600 mr-3" />
|
||||||
<h3 className="text-lg font-medium text-secondary-900 dark:text-white capitalize">{role.role}</h3>
|
<h3 className="text-lg font-medium text-secondary-900 dark:text-white capitalize">{role.role}</h3>
|
||||||
{isAdminRole && (
|
{isBuiltInRole && (
|
||||||
<span className="ml-2 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary-100 text-primary-800">
|
<span className="ml-2 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary-100 text-primary-800">
|
||||||
System Role
|
Built-in Role
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -211,13 +211,13 @@ const RolePermissionsCard = ({ role, isEditing, onEdit, onCancel, onSave, onDele
|
|||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={onEdit}
|
onClick={onEdit}
|
||||||
disabled={isAdminRole}
|
disabled={isBuiltInRole}
|
||||||
className="inline-flex items-center px-3 py-1 border border-transparent text-sm font-medium rounded-md text-white bg-primary-600 hover:bg-primary-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
className="inline-flex items-center px-3 py-1 border border-transparent text-sm font-medium rounded-md text-white bg-primary-600 hover:bg-primary-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
<Edit className="h-4 w-4 mr-1" />
|
<Edit className="h-4 w-4 mr-1" />
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
{!isAdminRole && (
|
{!isBuiltInRole && (
|
||||||
<button
|
<button
|
||||||
onClick={() => onDelete(role.role)}
|
onClick={() => onDelete(role.role)}
|
||||||
className="inline-flex items-center px-3 py-1 border border-transparent text-sm font-medium rounded-md text-white bg-red-600 hover:bg-red-700"
|
className="inline-flex items-center px-3 py-1 border border-transparent text-sm font-medium rounded-md text-white bg-red-600 hover:bg-red-700"
|
||||||
@@ -245,7 +245,7 @@ const RolePermissionsCard = ({ role, isEditing, onEdit, onCancel, onSave, onDele
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={isChecked}
|
checked={isChecked}
|
||||||
onChange={(e) => handlePermissionChange(field.key, e.target.checked)}
|
onChange={(e) => handlePermissionChange(field.key, e.target.checked)}
|
||||||
disabled={!isEditing || (isAdminRole && field.key === 'can_manage_users')}
|
disabled={!isEditing || (isBuiltInRole && field.key === 'can_manage_users')}
|
||||||
className="h-4 w-4 text-primary-600 focus:ring-primary-500 border-secondary-300 rounded disabled:opacity-50"
|
className="h-4 w-4 text-primary-600 focus:ring-primary-500 border-secondary-300 rounded disabled:opacity-50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { Save, Server, Shield, AlertCircle, CheckCircle, Code, Plus, Trash2, Star, Download, X, Settings as SettingsIcon, Clock } from 'lucide-react';
|
import { Save, Server, Shield, AlertCircle, CheckCircle, Code, Plus, Trash2, Star, Download, X, Settings as SettingsIcon, Clock } from 'lucide-react';
|
||||||
import { settingsAPI, agentVersionAPI, versionAPI } from '../utils/api';
|
import { settingsAPI, agentVersionAPI, versionAPI, permissionsAPI } from '../utils/api';
|
||||||
import { useUpdateNotification } from '../contexts/UpdateNotificationContext';
|
import { useUpdateNotification } from '../contexts/UpdateNotificationContext';
|
||||||
import UpgradeNotificationIcon from '../components/UpgradeNotificationIcon';
|
import UpgradeNotificationIcon from '../components/UpgradeNotificationIcon';
|
||||||
|
|
||||||
@@ -13,6 +13,7 @@ const Settings = () => {
|
|||||||
updateInterval: 60,
|
updateInterval: 60,
|
||||||
autoUpdate: false,
|
autoUpdate: false,
|
||||||
signupEnabled: false,
|
signupEnabled: false,
|
||||||
|
defaultUserRole: 'user',
|
||||||
githubRepoUrl: 'git@github.com:9technologygroup/patchmon.net.git',
|
githubRepoUrl: 'git@github.com:9technologygroup/patchmon.net.git',
|
||||||
repositoryType: 'public',
|
repositoryType: 'public',
|
||||||
sshKeyPath: '',
|
sshKeyPath: '',
|
||||||
@@ -68,6 +69,12 @@ const Settings = () => {
|
|||||||
queryFn: () => settingsAPI.get().then(res => res.data)
|
queryFn: () => settingsAPI.get().then(res => res.data)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Fetch available roles for default user role dropdown
|
||||||
|
const { data: roles, isLoading: rolesLoading } = useQuery({
|
||||||
|
queryKey: ['rolePermissions'],
|
||||||
|
queryFn: () => permissionsAPI.getRoles().then(res => res.data)
|
||||||
|
});
|
||||||
|
|
||||||
// Update form data when settings are loaded
|
// Update form data when settings are loaded
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (settings) {
|
if (settings) {
|
||||||
@@ -78,6 +85,7 @@ const Settings = () => {
|
|||||||
updateInterval: settings.update_interval || 60,
|
updateInterval: settings.update_interval || 60,
|
||||||
autoUpdate: settings.auto_update || false,
|
autoUpdate: settings.auto_update || false,
|
||||||
signupEnabled: settings.signup_enabled === true ? true : false, // Explicit boolean conversion
|
signupEnabled: settings.signup_enabled === true ? true : false, // Explicit boolean conversion
|
||||||
|
defaultUserRole: settings.default_user_role || 'user',
|
||||||
githubRepoUrl: settings.github_repo_url || 'git@github.com:9technologygroup/patchmon.net.git',
|
githubRepoUrl: settings.github_repo_url || 'git@github.com:9technologygroup/patchmon.net.git',
|
||||||
repositoryType: settings.repository_type || 'public',
|
repositoryType: settings.repository_type || 'public',
|
||||||
sshKeyPath: settings.ssh_key_path || '',
|
sshKeyPath: settings.ssh_key_path || '',
|
||||||
@@ -560,6 +568,37 @@ const Settings = () => {
|
|||||||
Enable User Self-Registration
|
Enable User Self-Registration
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
{/* Default User Role Dropdown */}
|
||||||
|
{formData.signupEnabled && (
|
||||||
|
<div className="mt-3 ml-6">
|
||||||
|
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2">
|
||||||
|
Default Role for New Users
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
value={formData.defaultUserRole}
|
||||||
|
onChange={(e) => handleInputChange('defaultUserRole', e.target.value)}
|
||||||
|
className="w-full max-w-xs border-secondary-300 dark:border-secondary-600 rounded-md shadow-sm focus:ring-primary-500 focus:border-primary-500 bg-white dark:bg-secondary-700 text-secondary-900 dark:text-white"
|
||||||
|
disabled={rolesLoading}
|
||||||
|
>
|
||||||
|
{rolesLoading ? (
|
||||||
|
<option>Loading roles...</option>
|
||||||
|
) : roles && Array.isArray(roles) ? (
|
||||||
|
roles.map((role) => (
|
||||||
|
<option key={role.role} value={role.role}>
|
||||||
|
{role.role.charAt(0).toUpperCase() + role.role.slice(1)}
|
||||||
|
</option>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<option value="user">User</option>
|
||||||
|
)}
|
||||||
|
</select>
|
||||||
|
<p className="mt-1 text-xs text-secondary-500 dark:text-secondary-400">
|
||||||
|
New users will be assigned this role when they register.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<p className="mt-1 text-sm text-secondary-500 dark:text-secondary-400">
|
<p className="mt-1 text-sm text-secondary-500 dark:text-secondary-400">
|
||||||
When enabled, users can create their own accounts through the signup page. When disabled, only administrators can create user accounts.
|
When enabled, users can create their own accounts through the signup page. When disabled, only administrators can create user accounts.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ export const versionAPI = {
|
|||||||
export const authAPI = {
|
export const authAPI = {
|
||||||
login: (username, password) => api.post('/auth/login', { username, password }),
|
login: (username, password) => api.post('/auth/login', { username, password }),
|
||||||
verifyTfa: (username, token) => api.post('/auth/verify-tfa', { username, token }),
|
verifyTfa: (username, token) => api.post('/auth/verify-tfa', { username, token }),
|
||||||
signup: (username, email, password) => api.post('/auth/signup', { username, email, password }),
|
signup: (username, email, password, firstName, lastName) => api.post('/auth/signup', { username, email, password, firstName, lastName }),
|
||||||
}
|
}
|
||||||
|
|
||||||
// TFA API
|
// TFA API
|
||||||
|
|||||||
Reference in New Issue
Block a user