Add system settings, audit logging, user management, and dark mode fixes
System Settings: - Add SystemSettings.vue with Zabbix integration, SMTP/email config, SAML SSO settings - Add Setting model with key-value storage and typed values - Add settings API with caching Audit Logging: - Add AuditLog model tracking user, IP, action, entity changes - Add comprehensive audit logging to all CRUD operations: - Machines, Computers, Equipment, Network devices, VLANs, Subnets - Printers, USB devices (including checkout/checkin) - Applications, Settings, Users/Roles - Track old/new values for all field changes - Mask sensitive values (passwords, tokens) in logs User Management: - Add UsersList.vue with full user CRUD - Add Role management with granular permissions - Add 41 predefined permissions across 10 categories - Add users API with roles and permissions endpoints Reports: - Add TonerReport.vue for printer supply monitoring Dark Mode Fixes: - Fix map position section in PCForm, PrinterForm - Fix alert-warning in KnowledgeBaseDetail - All components now use CSS variables for theming CLI Commands: - Add flask seed permissions - Add flask seed settings Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,98 @@ userroles = db.Table(
|
||||
db.Column('roleid', db.Integer, db.ForeignKey('roles.roleid'), primary_key=True)
|
||||
)
|
||||
|
||||
# Association table for role permissions (many-to-many)
|
||||
rolepermissions = db.Table(
|
||||
'rolepermissions',
|
||||
db.Column('roleid', db.Integer, db.ForeignKey('roles.roleid'), primary_key=True),
|
||||
db.Column('permissionid', db.Integer, db.ForeignKey('permissions.permissionid'), primary_key=True)
|
||||
)
|
||||
|
||||
|
||||
class Permission(db.Model):
|
||||
"""
|
||||
Permission model for granular access control.
|
||||
|
||||
Permissions are predefined and assigned to roles.
|
||||
"""
|
||||
__tablename__ = 'permissions'
|
||||
|
||||
permissionid = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(50), unique=True, nullable=False)
|
||||
description = db.Column(db.String(255))
|
||||
category = db.Column(db.String(50), default='general') # For grouping in UI
|
||||
|
||||
# Predefined permissions
|
||||
PERMISSIONS = [
|
||||
# Assets
|
||||
('assets.view', 'View assets', 'assets'),
|
||||
('assets.create', 'Create assets', 'assets'),
|
||||
('assets.edit', 'Edit assets', 'assets'),
|
||||
('assets.delete', 'Delete assets', 'assets'),
|
||||
# Equipment
|
||||
('equipment.view', 'View equipment', 'equipment'),
|
||||
('equipment.create', 'Create equipment', 'equipment'),
|
||||
('equipment.edit', 'Edit equipment', 'equipment'),
|
||||
('equipment.delete', 'Delete equipment', 'equipment'),
|
||||
# Computers
|
||||
('computers.view', 'View computers', 'computers'),
|
||||
('computers.create', 'Create computers', 'computers'),
|
||||
('computers.edit', 'Edit computers', 'computers'),
|
||||
('computers.delete', 'Delete computers', 'computers'),
|
||||
# Printers
|
||||
('printers.view', 'View printers', 'printers'),
|
||||
('printers.create', 'Create printers', 'printers'),
|
||||
('printers.edit', 'Edit printers', 'printers'),
|
||||
('printers.delete', 'Delete printers', 'printers'),
|
||||
# Network
|
||||
('network.view', 'View network devices', 'network'),
|
||||
('network.create', 'Create network devices', 'network'),
|
||||
('network.edit', 'Edit network devices', 'network'),
|
||||
('network.delete', 'Delete network devices', 'network'),
|
||||
# Applications
|
||||
('applications.view', 'View applications', 'applications'),
|
||||
('applications.create', 'Create applications', 'applications'),
|
||||
('applications.edit', 'Edit applications', 'applications'),
|
||||
('applications.delete', 'Delete applications', 'applications'),
|
||||
# Knowledge Base
|
||||
('kb.view', 'View knowledge base', 'knowledgebase'),
|
||||
('kb.create', 'Create KB articles', 'knowledgebase'),
|
||||
('kb.edit', 'Edit KB articles', 'knowledgebase'),
|
||||
('kb.delete', 'Delete KB articles', 'knowledgebase'),
|
||||
# Notifications
|
||||
('notifications.view', 'View notifications', 'notifications'),
|
||||
('notifications.create', 'Create notifications', 'notifications'),
|
||||
('notifications.edit', 'Edit notifications', 'notifications'),
|
||||
('notifications.delete', 'Delete notifications', 'notifications'),
|
||||
# Reports
|
||||
('reports.view', 'View reports', 'reports'),
|
||||
('reports.export', 'Export reports', 'reports'),
|
||||
# Settings
|
||||
('settings.view', 'View settings', 'admin'),
|
||||
('settings.edit', 'Edit settings', 'admin'),
|
||||
# Users
|
||||
('users.view', 'View users', 'admin'),
|
||||
('users.create', 'Create users', 'admin'),
|
||||
('users.edit', 'Edit users', 'admin'),
|
||||
('users.delete', 'Delete users', 'admin'),
|
||||
# Audit
|
||||
('audit.view', 'View audit logs', 'admin'),
|
||||
]
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Permission {self.name}>"
|
||||
|
||||
@classmethod
|
||||
def seed(cls):
|
||||
"""Seed predefined permissions."""
|
||||
created = 0
|
||||
for name, description, category in cls.PERMISSIONS:
|
||||
if not cls.query.filter_by(name=name).first():
|
||||
perm = cls(name=name, description=description, category=category)
|
||||
db.session.add(perm)
|
||||
created += 1
|
||||
return created
|
||||
|
||||
|
||||
class Role(BaseModel):
|
||||
"""User role model."""
|
||||
@@ -21,9 +113,29 @@ class Role(BaseModel):
|
||||
rolename = db.Column(db.String(50), unique=True, nullable=False)
|
||||
description = db.Column(db.Text)
|
||||
|
||||
# Permissions relationship
|
||||
permissions = db.relationship(
|
||||
'Permission',
|
||||
secondary=rolepermissions,
|
||||
backref=db.backref('roles', lazy='dynamic')
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Role {self.rolename}>"
|
||||
|
||||
def haspermission(self, permission_name: str) -> bool:
|
||||
"""Check if role has a specific permission."""
|
||||
# Admin role has all permissions
|
||||
if self.rolename == 'admin':
|
||||
return True
|
||||
return any(p.name == permission_name for p in self.permissions)
|
||||
|
||||
def getpermissionnames(self) -> list:
|
||||
"""Get list of permission names."""
|
||||
if self.rolename == 'admin':
|
||||
return [p[0] for p in Permission.PERMISSIONS]
|
||||
return [p.name for p in self.permissions]
|
||||
|
||||
|
||||
class User(BaseModel):
|
||||
"""User model for authentication."""
|
||||
@@ -64,10 +176,19 @@ class User(BaseModel):
|
||||
"""Check if user has a specific role."""
|
||||
return any(r.rolename == rolename for r in self.roles)
|
||||
|
||||
def haspermission(self, permission_name: str) -> bool:
|
||||
"""Check if user has a specific permission through any role."""
|
||||
# Admin role has all permissions
|
||||
if self.hasrole('admin'):
|
||||
return True
|
||||
return any(r.haspermission(permission_name) for r in self.roles)
|
||||
|
||||
def getpermissions(self) -> list:
|
||||
"""Get list of permission names from roles."""
|
||||
# Simple role-based permissions
|
||||
perms = []
|
||||
"""Get list of all permission names from all roles."""
|
||||
if self.hasrole('admin'):
|
||||
return [p[0] for p in Permission.PERMISSIONS]
|
||||
|
||||
perms = set()
|
||||
for role in self.roles:
|
||||
perms.append(role.rolename)
|
||||
return perms
|
||||
perms.update(role.getpermissionnames())
|
||||
return list(perms)
|
||||
|
||||
Reference in New Issue
Block a user