Compare commits

...

3 Commits

Author SHA1 Message Date
ske087 0fe7cee765 updated roles ant permissions 2025-09-12 22:14:51 +03:00
ske087 fc03c82733 updated creation of the database for users 2025-09-12 21:24:22 +03:00
ske087 643dcefce4 Updated login 2025-09-12 20:53:52 +03:00
17 changed files with 2296 additions and 208 deletions
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,110 @@
#!/usr/bin/env python3
import mariadb
import os
import sys
def get_external_db_connection():
"""Reads the external_server.conf file and returns a MariaDB database connection."""
# Get the instance folder path
current_dir = os.path.dirname(os.path.abspath(__file__))
instance_folder = os.path.join(current_dir, '../../instance')
settings_file = os.path.join(instance_folder, 'external_server.conf')
if not os.path.exists(settings_file):
raise FileNotFoundError(f"The external_server.conf file is missing: {settings_file}")
# Read settings from the configuration file
settings = {}
with open(settings_file, 'r') as f:
for line in f:
line = line.strip()
if line and '=' in line:
key, value = line.split('=', 1)
settings[key] = value
print(f"Connecting to MariaDB:")
print(f" Host: {settings.get('server_domain', 'N/A')}")
print(f" Port: {settings.get('port', 'N/A')}")
print(f" Database: {settings.get('database_name', 'N/A')}")
return mariadb.connect(
user=settings['username'],
password=settings['password'],
host=settings['server_domain'],
port=int(settings['port']),
database=settings['database_name']
)
def main():
try:
print("=== Adding Email Column to Users Table ===")
conn = get_external_db_connection()
cursor = conn.cursor()
# First, check the current table structure
print("\n1. Checking current table structure...")
cursor.execute("DESCRIBE users")
columns = cursor.fetchall()
has_email = False
for column in columns:
print(f" Column: {column[0]} ({column[1]})")
if column[0] == 'email':
has_email = True
if not has_email:
print("\n2. Adding email column...")
cursor.execute("ALTER TABLE users ADD COLUMN email VARCHAR(255)")
conn.commit()
print(" ✓ Email column added successfully")
else:
print("\n2. Email column already exists")
# Now check and display all users
print("\n3. Current users in database:")
cursor.execute("SELECT id, username, role, email FROM users")
users = cursor.fetchall()
if users:
print(f" Found {len(users)} users:")
for user in users:
email = user[3] if user[3] else "No email"
print(f" - ID: {user[0]}, Username: {user[1]}, Role: {user[2]}, Email: {email}")
else:
print(" No users found - creating test users...")
# Create some test users
test_users = [
('admin_user', 'admin123', 'admin', 'admin@company.com'),
('manager_user', 'manager123', 'manager', 'manager@company.com'),
('warehouse_user', 'warehouse123', 'warehouse_manager', 'warehouse@company.com'),
('quality_user', 'quality123', 'quality_manager', 'quality@company.com')
]
for username, password, role, email in test_users:
try:
cursor.execute("""
INSERT INTO users (username, password, role, email)
VALUES (%s, %s, %s, %s)
""", (username, password, role, email))
print(f" ✓ Created user: {username} ({role})")
except mariadb.IntegrityError as e:
print(f" ⚠ User {username} already exists: {e}")
conn.commit()
print(" ✓ Test users created successfully")
conn.close()
print("\n=== Database Update Complete ===")
except Exception as e:
print(f"❌ Error: {e}")
import traceback
traceback.print_exc()
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,105 @@
#!/usr/bin/env python3
import mariadb
import os
import sys
def get_external_db_connection():
"""Reads the external_server.conf file and returns a MariaDB database connection."""
# Get the instance folder path
current_dir = os.path.dirname(os.path.abspath(__file__))
instance_folder = os.path.join(current_dir, '../../instance')
settings_file = os.path.join(instance_folder, 'external_server.conf')
if not os.path.exists(settings_file):
raise FileNotFoundError(f"The external_server.conf file is missing: {settings_file}")
# Read settings from the configuration file
settings = {}
with open(settings_file, 'r') as f:
for line in f:
line = line.strip()
if line and '=' in line:
key, value = line.split('=', 1)
settings[key] = value
print(f"Connecting to MariaDB with settings:")
print(f" Host: {settings.get('server_domain', 'N/A')}")
print(f" Port: {settings.get('port', 'N/A')}")
print(f" Database: {settings.get('database_name', 'N/A')}")
print(f" Username: {settings.get('username', 'N/A')}")
# Create a database connection
return mariadb.connect(
user=settings['username'],
password=settings['password'],
host=settings['server_domain'],
port=int(settings['port']),
database=settings['database_name']
)
def main():
try:
print("=== Checking External MariaDB Database ===")
conn = get_external_db_connection()
cursor = conn.cursor()
# Create users table if it doesn't exist
print("\n1. Creating/verifying users table...")
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
role VARCHAR(50) NOT NULL,
email VARCHAR(255)
)
''')
print(" ✓ Users table created/verified")
# Check existing users
print("\n2. Checking existing users...")
cursor.execute("SELECT id, username, role, email FROM users")
users = cursor.fetchall()
if users:
print(f" Found {len(users)} existing users:")
for user in users:
email = user[3] if user[3] else "No email"
print(f" - ID: {user[0]}, Username: {user[1]}, Role: {user[2]}, Email: {email}")
else:
print(" No users found in external database")
# Create some test users
print("\n3. Creating test users...")
test_users = [
('admin_user', 'admin123', 'admin', 'admin@company.com'),
('manager_user', 'manager123', 'manager', 'manager@company.com'),
('warehouse_user', 'warehouse123', 'warehouse_manager', 'warehouse@company.com'),
('quality_user', 'quality123', 'quality_manager', 'quality@company.com')
]
for username, password, role, email in test_users:
try:
cursor.execute("""
INSERT INTO users (username, password, role, email)
VALUES (%s, %s, %s, %s)
""", (username, password, role, email))
print(f" ✓ Created user: {username} ({role})")
except mariadb.IntegrityError as e:
print(f" ⚠ User {username} already exists: {e}")
conn.commit()
print(" ✓ Test users created successfully")
conn.close()
print("\n=== Database Check Complete ===")
except Exception as e:
print(f"❌ Error: {e}")
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,60 @@
import mariadb
import os
def get_external_db_connection():
"""Get MariaDB connection using external_server.conf"""
settings_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../instance/external_server.conf'))
settings = {}
with open(settings_file, 'r') as f:
for line in f:
key, value = line.strip().split('=', 1)
settings[key] = value
return mariadb.connect(
user=settings['username'],
password=settings['password'],
host=settings['server_domain'],
port=int(settings['port']),
database=settings['database_name']
)
def create_external_users_table():
"""Create users table and superadmin user in external MariaDB database"""
try:
conn = get_external_db_connection()
cursor = conn.cursor()
# Create users table if not exists (MariaDB syntax)
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
role VARCHAR(50) NOT NULL
)
''')
# Insert superadmin user if not exists
cursor.execute('''
INSERT IGNORE INTO users (username, password, role)
VALUES (%s, %s, %s)
''', ('superadmin', 'superadmin123', 'superadmin'))
# Check if user was created/exists
cursor.execute("SELECT username, password, role FROM users WHERE username = %s", ('superadmin',))
result = cursor.fetchone()
if result:
print(f"SUCCESS: Superadmin user exists in external database")
print(f"Username: {result[0]}, Password: {result[1]}, Role: {result[2]}")
else:
print("ERROR: Failed to create/find superadmin user")
conn.commit()
conn.close()
print("External MariaDB users table setup completed.")
except Exception as e:
print(f"ERROR: {e}")
if __name__ == "__main__":
create_external_users_table()
@@ -0,0 +1,141 @@
#!/usr/bin/env python3
import mariadb
import os
import sys
def get_external_db_connection():
"""Reads the external_server.conf file and returns a MariaDB database connection."""
# Get the instance folder path
current_dir = os.path.dirname(os.path.abspath(__file__))
instance_folder = os.path.join(current_dir, '../../instance')
settings_file = os.path.join(instance_folder, 'external_server.conf')
if not os.path.exists(settings_file):
raise FileNotFoundError(f"The external_server.conf file is missing: {settings_file}")
# Read settings from the configuration file
settings = {}
with open(settings_file, 'r') as f:
for line in f:
line = line.strip()
if line and '=' in line:
key, value = line.split('=', 1)
settings[key] = value
return mariadb.connect(
user=settings['username'],
password=settings['password'],
host=settings['server_domain'],
port=int(settings['port']),
database=settings['database_name']
)
def main():
try:
print("=== Creating Permission Management Tables ===")
conn = get_external_db_connection()
cursor = conn.cursor()
# 1. Create permissions table
print("\n1. Creating permissions table...")
cursor.execute('''
CREATE TABLE IF NOT EXISTS permissions (
id INT AUTO_INCREMENT PRIMARY KEY,
permission_key VARCHAR(255) UNIQUE NOT NULL,
page VARCHAR(100) NOT NULL,
page_name VARCHAR(255) NOT NULL,
section VARCHAR(100) NOT NULL,
section_name VARCHAR(255) NOT NULL,
action VARCHAR(50) NOT NULL,
action_name VARCHAR(255) NOT NULL,
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
''')
print(" ✓ Permissions table created/verified")
# 2. Create role_permissions table
print("\n2. Creating role_permissions table...")
cursor.execute('''
CREATE TABLE IF NOT EXISTS role_permissions (
id INT AUTO_INCREMENT PRIMARY KEY,
role VARCHAR(50) NOT NULL,
permission_key VARCHAR(255) NOT NULL,
granted BOOLEAN DEFAULT TRUE,
granted_by VARCHAR(50),
granted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY unique_role_permission (role, permission_key),
FOREIGN KEY (permission_key) REFERENCES permissions(permission_key) ON DELETE CASCADE
)
''')
print(" ✓ Role permissions table created/verified")
# 3. Create role_hierarchy table for role management
print("\n3. Creating role_hierarchy table...")
cursor.execute('''
CREATE TABLE IF NOT EXISTS role_hierarchy (
id INT AUTO_INCREMENT PRIMARY KEY,
role_name VARCHAR(50) UNIQUE NOT NULL,
display_name VARCHAR(255) NOT NULL,
description TEXT,
level INT DEFAULT 0,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
''')
print(" ✓ Role hierarchy table created/verified")
# 4. Create permission_audit_log table for tracking changes
print("\n4. Creating permission_audit_log table...")
cursor.execute('''
CREATE TABLE IF NOT EXISTS permission_audit_log (
id INT AUTO_INCREMENT PRIMARY KEY,
role VARCHAR(50) NOT NULL,
permission_key VARCHAR(255) NOT NULL,
action ENUM('granted', 'revoked') NOT NULL,
changed_by VARCHAR(50) NOT NULL,
changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
reason TEXT,
ip_address VARCHAR(45)
)
''')
print(" ✓ Permission audit log table created/verified")
conn.commit()
# 5. Check if we need to populate initial data
print("\n5. Checking for existing data...")
cursor.execute("SELECT COUNT(*) FROM permissions")
permission_count = cursor.fetchone()[0]
if permission_count == 0:
print(" No permissions found - will need to populate with default data")
print(" Run 'populate_permissions.py' to initialize the permission system")
else:
print(f" Found {permission_count} existing permissions")
cursor.execute("SELECT COUNT(*) FROM role_hierarchy")
role_count = cursor.fetchone()[0]
if role_count == 0:
print(" No roles found - will need to populate with default roles")
else:
print(f" Found {role_count} existing roles")
conn.close()
print("\n=== Permission Database Schema Created Successfully ===")
except Exception as e:
print(f"❌ Error: {e}")
import traceback
traceback.print_exc()
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,53 @@
import sqlite3
import os
def check_database(db_path, description):
"""Check if a database exists and show its users."""
if os.path.exists(db_path):
print(f"\n{description}: FOUND at {db_path}")
try:
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Check if users table exists
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='users'")
if cursor.fetchone():
cursor.execute("SELECT id, username, password, role FROM users")
users = cursor.fetchall()
if users:
print("Users in this database:")
for user in users:
print(f" ID: {user[0]}, Username: {user[1]}, Password: {user[2]}, Role: {user[3]}")
else:
print(" Users table exists but is empty")
else:
print(" No users table found")
conn.close()
except Exception as e:
print(f" Error reading database: {e}")
else:
print(f"\n{description}: NOT FOUND at {db_path}")
if __name__ == "__main__":
# Check different possible locations for users.db
# 1. Root quality_recticel/instance/users.db
root_instance = "/home/ske087/quality_recticel/instance/users.db"
check_database(root_instance, "Root instance users.db")
# 2. App instance folder
app_instance = "/home/ske087/quality_recticel/py_app/instance/users.db"
check_database(app_instance, "App instance users.db")
# 3. Current working directory
cwd_db = "/home/ske087/quality_recticel/py_app/users.db"
check_database(cwd_db, "Working directory users.db")
# 4. Flask app database (relative to py_app)
flask_db = "/home/ske087/quality_recticel/py_app/app/users.db"
check_database(flask_db, "Flask app users.db")
print("\n" + "="*50)
print("RECOMMENDATION:")
print("The login should use the external MariaDB database.")
print("Make sure you have created the superadmin user in MariaDB using create_roles_table.py")
@@ -0,0 +1,143 @@
#!/usr/bin/env python3
import mariadb
import os
import sys
# Add the app directory to the path so we can import our permissions module
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
from permissions import APP_PERMISSIONS, ROLE_HIERARCHY, ACTIONS, get_all_permissions, get_default_permissions_for_role
def get_external_db_connection():
"""Reads the external_server.conf file and returns a MariaDB database connection."""
current_dir = os.path.dirname(os.path.abspath(__file__))
instance_folder = os.path.join(current_dir, '../../instance')
settings_file = os.path.join(instance_folder, 'external_server.conf')
if not os.path.exists(settings_file):
raise FileNotFoundError(f"The external_server.conf file is missing: {settings_file}")
settings = {}
with open(settings_file, 'r') as f:
for line in f:
line = line.strip()
if line and '=' in line:
key, value = line.split('=', 1)
settings[key] = value
return mariadb.connect(
user=settings['username'],
password=settings['password'],
host=settings['server_domain'],
port=int(settings['port']),
database=settings['database_name']
)
def main():
try:
print("=== Populating Permission System ===")
conn = get_external_db_connection()
cursor = conn.cursor()
# 1. Populate all permissions
print("\n1. Populating permissions...")
permissions = get_all_permissions()
for perm in permissions:
try:
cursor.execute('''
INSERT INTO permissions (permission_key, page, page_name, section, section_name, action, action_name)
VALUES (%s, %s, %s, %s, %s, %s, %s)
ON DUPLICATE KEY UPDATE
page_name = VALUES(page_name),
section_name = VALUES(section_name),
action_name = VALUES(action_name),
updated_at = CURRENT_TIMESTAMP
''', (
perm['key'],
perm['page'],
perm['page_name'],
perm['section'],
perm['section_name'],
perm['action'],
perm['action_name']
))
except Exception as e:
print(f" ⚠ Error inserting permission {perm['key']}: {e}")
conn.commit()
print(f" ✓ Populated {len(permissions)} permissions")
# 2. Populate role hierarchy
print("\n2. Populating role hierarchy...")
for role_name, role_data in ROLE_HIERARCHY.items():
try:
cursor.execute('''
INSERT INTO role_hierarchy (role_name, display_name, description, level)
VALUES (%s, %s, %s, %s)
ON DUPLICATE KEY UPDATE
display_name = VALUES(display_name),
description = VALUES(description),
level = VALUES(level),
updated_at = CURRENT_TIMESTAMP
''', (
role_name,
role_data['name'],
role_data['description'],
role_data['level']
))
except Exception as e:
print(f" ⚠ Error inserting role {role_name}: {e}")
conn.commit()
print(f" ✓ Populated {len(ROLE_HIERARCHY)} roles")
# 3. Set default permissions for each role
print("\n3. Setting default role permissions...")
for role_name in ROLE_HIERARCHY.keys():
default_permissions = get_default_permissions_for_role(role_name)
print(f" Setting permissions for {role_name}: {len(default_permissions)} permissions")
for permission_key in default_permissions:
try:
cursor.execute('''
INSERT INTO role_permissions (role, permission_key, granted, granted_by)
VALUES (%s, %s, TRUE, 'system')
ON DUPLICATE KEY UPDATE
granted = TRUE,
updated_at = CURRENT_TIMESTAMP
''', (role_name, permission_key))
except Exception as e:
print(f" ⚠ Error setting permission {permission_key} for {role_name}: {e}")
conn.commit()
# 4. Show summary
print("\n4. Permission Summary:")
cursor.execute('''
SELECT r.role_name, r.display_name, COUNT(rp.permission_key) as permission_count
FROM role_hierarchy r
LEFT JOIN role_permissions rp ON r.role_name = rp.role AND rp.granted = TRUE
GROUP BY r.role_name, r.display_name
ORDER BY r.level DESC
''')
results = cursor.fetchall()
for role_name, display_name, count in results:
print(f" {display_name} ({role_name}): {count} permissions")
conn.close()
print("\n=== Permission System Initialization Complete ===")
except Exception as e:
print(f"❌ Error: {e}")
import traceback
traceback.print_exc()
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
+344
View File
@@ -0,0 +1,344 @@
"""
Role-Based Access Control (RBAC) System
Hierarchical permission structure: Pages → Sections → Actions
"""
# Permission Actions
ACTIONS = {
'view': 'View/Read Access',
'create': 'Create/Add New',
'edit': 'Edit/Modify',
'delete': 'Delete/Remove',
'upload': 'Upload Files',
'download': 'Download Files',
'export': 'Export Data',
'import': 'Import Data'
}
# Application Structure with Hierarchical Permissions
APP_PERMISSIONS = {
'dashboard': {
'name': 'Dashboard',
'sections': {
'overview': {
'name': 'Overview Statistics',
'actions': ['view']
},
'recent_activity': {
'name': 'Recent Activity Feed',
'actions': ['view']
},
'quick_actions': {
'name': 'Quick Action Buttons',
'actions': ['view']
}
}
},
'settings': {
'name': 'Settings & Administration',
'sections': {
'user_management': {
'name': 'User Management',
'actions': ['view', 'create', 'edit', 'delete']
},
'role_permissions': {
'name': 'Role & Permissions',
'actions': ['view', 'edit']
},
'external_database': {
'name': 'External Database Config',
'actions': ['view', 'edit']
},
'system_settings': {
'name': 'System Configuration',
'actions': ['view', 'edit']
}
}
},
'warehouse': {
'name': 'Warehouse Management',
'sections': {
'inventory': {
'name': 'Inventory Management',
'actions': ['view', 'create', 'edit', 'delete', 'export']
},
'stock_movements': {
'name': 'Stock Movements',
'actions': ['view', 'create', 'edit', 'delete']
},
'receiving': {
'name': 'Goods Receiving',
'actions': ['view', 'create', 'edit', 'upload']
},
'shipping': {
'name': 'Goods Shipping',
'actions': ['view', 'create', 'edit', 'delete']
},
'locations': {
'name': 'Storage Locations',
'actions': ['view', 'create', 'edit', 'delete']
},
'reports': {
'name': 'Warehouse Reports',
'actions': ['view', 'export', 'download']
}
}
},
'quality': {
'name': 'Quality Control',
'sections': {
'inspections': {
'name': 'Quality Inspections',
'actions': ['view', 'create', 'edit', 'delete']
},
'test_results': {
'name': 'Test Results',
'actions': ['view', 'create', 'edit', 'upload']
},
'certificates': {
'name': 'Quality Certificates',
'actions': ['view', 'create', 'edit', 'delete', 'upload', 'download']
},
'compliance': {
'name': 'Compliance Management',
'actions': ['view', 'create', 'edit']
},
'quality_reports': {
'name': 'Quality Reports',
'actions': ['view', 'export', 'download']
}
}
},
'production': {
'name': 'Production Management',
'sections': {
'work_orders': {
'name': 'Work Orders',
'actions': ['view', 'create', 'edit', 'delete']
},
'production_lines': {
'name': 'Production Lines',
'actions': ['view', 'create', 'edit', 'delete']
},
'scheduling': {
'name': 'Production Scheduling',
'actions': ['view', 'create', 'edit', 'delete']
},
'equipment': {
'name': 'Equipment Management',
'actions': ['view', 'create', 'edit', 'delete']
},
'maintenance': {
'name': 'Maintenance Records',
'actions': ['view', 'create', 'edit', 'delete', 'upload']
}
}
},
'traceability': {
'name': 'Product Traceability',
'sections': {
'batch_tracking': {
'name': 'Batch Tracking',
'actions': ['view', 'create', 'edit']
},
'lot_genealogy': {
'name': 'Lot Genealogy',
'actions': ['view', 'export']
},
'recall_management': {
'name': 'Product Recall',
'actions': ['view', 'create', 'edit', 'delete']
},
'chain_of_custody': {
'name': 'Chain of Custody',
'actions': ['view', 'create', 'edit']
}
}
},
'reports': {
'name': 'Reports & Analytics',
'sections': {
'standard_reports': {
'name': 'Standard Reports',
'actions': ['view', 'export', 'download']
},
'custom_reports': {
'name': 'Custom Reports',
'actions': ['view', 'create', 'edit', 'delete', 'export']
},
'dashboards': {
'name': 'Analytics Dashboards',
'actions': ['view', 'create', 'edit', 'delete']
},
'data_export': {
'name': 'Data Export Tools',
'actions': ['view', 'export', 'download']
}
}
}
}
# Role Hierarchy and Default Permissions
ROLE_HIERARCHY = {
'superadmin': {
'name': 'Super Administrator',
'description': 'Full system access - can manage all aspects including system configuration',
'level': 100,
'default_permissions': 'ALL' # Gets all permissions by default
},
'admin': {
'name': 'Administrator',
'description': 'Administrative access - can manage users and most system functions',
'level': 90,
'default_sections': [
'dashboard',
'settings.user_management',
'settings.system_settings.view',
'warehouse',
'quality',
'production',
'reports',
'traceability'
],
'restrictions': [
'settings.role_permissions.edit', # Cannot modify role permissions
'settings.external_database.edit', # Cannot modify external DB config
]
},
'manager': {
'name': 'Manager',
'description': 'Management level access - can view and manage operational data',
'level': 70,
'default_sections': [
'dashboard',
'warehouse.inventory.view',
'warehouse.reports.view',
'quality.inspections.view',
'quality.quality_reports.view',
'production.work_orders',
'reports.standard_reports'
]
},
'warehouse_manager': {
'name': 'Warehouse Manager',
'description': 'Full warehouse access with limited system access',
'level': 60,
'default_sections': [
'dashboard.overview.view',
'warehouse', # Full warehouse access
'traceability.batch_tracking',
'reports.standard_reports.view'
]
},
'warehouse_worker': {
'name': 'Warehouse Worker',
'description': 'Limited warehouse operations access',
'level': 50,
'default_sections': [
'dashboard.overview.view',
'warehouse.inventory.view',
'warehouse.stock_movements',
'warehouse.receiving',
'warehouse.shipping.view'
]
},
'quality_manager': {
'name': 'Quality Manager',
'description': 'Full quality control access',
'level': 60,
'default_sections': [
'dashboard.overview.view',
'quality', # Full quality access
'traceability',
'reports.standard_reports.view'
]
},
'quality_worker': {
'name': 'Quality Worker',
'description': 'Limited quality control operations',
'level': 50,
'default_sections': [
'dashboard.overview.view',
'quality.inspections',
'quality.test_results',
'quality.certificates.view'
]
}
}
def get_permission_key(page, section, action):
"""Generate a standardized permission key"""
return f"{page}.{section}.{action}"
def parse_permission_key(permission_key):
"""Parse a permission key into its components"""
parts = permission_key.split('.')
if len(parts) == 3:
return parts[0], parts[1], parts[2]
return None, None, None
def get_all_permissions():
"""Get a flat list of all possible permissions"""
permissions = []
for page_key, page_data in APP_PERMISSIONS.items():
for section_key, section_data in page_data['sections'].items():
for action in section_data['actions']:
permissions.append({
'key': get_permission_key(page_key, section_key, action),
'page': page_key,
'page_name': page_data['name'],
'section': section_key,
'section_name': section_data['name'],
'action': action,
'action_name': ACTIONS.get(action, action)
})
return permissions
def get_default_permissions_for_role(role):
"""Get default permissions for a specific role"""
if role not in ROLE_HIERARCHY:
return []
role_config = ROLE_HIERARCHY[role]
# Superadmin gets everything
if role_config.get('default_permissions') == 'ALL':
return [p['key'] for p in get_all_permissions()]
# Other roles get specific sections
permissions = []
default_sections = role_config.get('default_sections', [])
for section_pattern in default_sections:
if section_pattern == 'dashboard':
# Full dashboard access
for section_key in APP_PERMISSIONS['dashboard']['sections'].keys():
for action in APP_PERMISSIONS['dashboard']['sections'][section_key]['actions']:
permissions.append(get_permission_key('dashboard', section_key, action))
elif '.' in section_pattern:
# Specific page.section or page.section.action
parts = section_pattern.split('.')
if len(parts) == 2: # page.section - all actions
page, section = parts
if page in APP_PERMISSIONS and section in APP_PERMISSIONS[page]['sections']:
for action in APP_PERMISSIONS[page]['sections'][section]['actions']:
permissions.append(get_permission_key(page, section, action))
elif len(parts) == 3: # page.section.action - specific action
page, section, action = parts
if (page in APP_PERMISSIONS and
section in APP_PERMISSIONS[page]['sections'] and
action in APP_PERMISSIONS[page]['sections'][section]['actions']):
permissions.append(get_permission_key(page, section, action))
else:
# Full page access
page = section_pattern
if page in APP_PERMISSIONS:
for section_key, section_data in APP_PERMISSIONS[page]['sections'].items():
for action in section_data['actions']:
permissions.append(get_permission_key(page, section_key, action))
# Remove any restricted permissions
restrictions = role_config.get('restrictions', [])
permissions = [p for p in permissions if p not in restrictions]
return permissions
+95 -109
View File
@@ -9,17 +9,19 @@ from reportlab.pdfgen import canvas
from flask import Blueprint, render_template, request, redirect, url_for, flash from flask import Blueprint, render_template, request, redirect, url_for, flash
import csv import csv
from .warehouse import add_location from .warehouse import add_location
from .settings import settings_handler, edit_access_roles_handler from .settings import (
settings_handler,
role_permissions_handler,
save_role_permissions_handler,
reset_role_permissions_handler,
create_user_handler,
edit_user_handler,
delete_user_handler,
save_external_db_handler
)
bp = Blueprint('main', __name__) bp = Blueprint('main', __name__)
warehouse_bp = Blueprint('warehouse', __name__) warehouse_bp = Blueprint('warehouse', __name__)
bp = Blueprint('main', __name__)
warehouse_bp = Blueprint('warehouse', __name__)
@bp.route('/update_role_access/<role>', methods=['POST'])
def update_role_access(role):
from .settings import update_role_access_handler
return update_role_access_handler(role)
@bp.route('/store_articles') @bp.route('/store_articles')
def store_articles(): def store_articles():
@@ -53,25 +55,80 @@ def get_db_connection():
@bp.route('/login', methods=['GET', 'POST']) @bp.route('/login', methods=['GET', 'POST'])
def login(): def login():
import sqlite3
if request.method == 'POST': if request.method == 'POST':
username = request.form['username'] # Debug: print all form data received
password = request.form['password'] print("All form data received:", dict(request.form))
# Safely get username and password with fallback
username = request.form.get('username', '').strip()
password = request.form.get('password', '').strip()
if not username or not password:
print("Missing username or password")
flash('Please enter both username and password.')
return render_template('login.html')
user = None user = None
print("Raw form input:", repr(username), repr(password)) print("Raw form input:", repr(username), repr(password))
# Only check external MariaDB for user authentication
try: # Logic: If username starts with #, check internal SQLite database
conn = get_db_connection() if username.startswith('#'):
cursor = conn.cursor() username_clean = username[1:].strip()
cursor.execute("SHOW TABLES LIKE 'users'") password_clean = password.strip()
if cursor.fetchone(): print(f"Checking internal database for: {username_clean}")
cursor.execute("SELECT username, password, role FROM users WHERE username=%s AND password=%s", (username.strip(), password.strip()))
row = cursor.fetchone() # Check internal SQLite database (py_app/instance/users.db)
print("External DB query result:", row) internal_db_path = os.path.join(os.path.dirname(__file__), '../instance/users.db')
if row: try:
user = {'username': row[0], 'password': row[1], 'role': row[2]} conn = sqlite3.connect(internal_db_path)
conn.close() cursor = conn.cursor()
except Exception as e: cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='users'")
print("External DB error:", e) if cursor.fetchone():
cursor.execute("SELECT username, password, role FROM users WHERE username=? AND password=?", (username_clean, password_clean))
row = cursor.fetchone()
print("Internal DB query result:", row)
if row:
user = {'username': row[0], 'password': row[1], 'role': row[2]}
else:
print("No users table in internal database")
conn.close()
except Exception as e:
print("Internal DB error:", e)
else:
# Check external MariaDB database first
try:
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute("SHOW TABLES LIKE 'users'")
if cursor.fetchone():
cursor.execute("SELECT username, password, role FROM users WHERE username=%s AND password=%s", (username.strip(), password.strip()))
row = cursor.fetchone()
print("External DB query result:", row)
if row:
user = {'username': row[0], 'password': row[1], 'role': row[2]}
conn.close()
except Exception as e:
print("External DB error:", e)
# Fallback to internal database if external fails
print("Falling back to internal database")
internal_db_path = os.path.join(os.path.dirname(__file__), '../instance/users.db')
try:
conn = sqlite3.connect(internal_db_path)
cursor = conn.cursor()
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='users'")
if cursor.fetchone():
cursor.execute("SELECT username, password, role FROM users WHERE username=? AND password=?", (username.strip(), password.strip()))
row = cursor.fetchone()
print("Internal DB fallback query result:", row)
if row:
user = {'username': row[0], 'password': row[1], 'role': row[2]}
conn.close()
except Exception as e2:
print("Internal DB fallback error:", e2)
if user: if user:
session['user'] = user['username'] session['user'] = user['username']
session['role'] = user['role'] session['role'] = user['role']
@@ -93,11 +150,6 @@ def dashboard():
def settings(): def settings():
return settings_handler() return settings_handler()
# Route for editing access roles (superadmin only)
@bp.route('/edit_access_roles')
def edit_access_roles():
return edit_access_roles_handler()
@bp.route('/quality') @bp.route('/quality')
def quality(): def quality():
if 'role' not in session or session['role'] not in ['superadmin', 'quality']: if 'role' not in session or session['role'] not in ['superadmin', 'quality']:
@@ -190,98 +242,32 @@ def logout():
@bp.route('/create_user', methods=['POST']) @bp.route('/create_user', methods=['POST'])
def create_user(): def create_user():
if 'role' not in session or session['role'] != 'superadmin': return create_user_handler()
flash('Access denied: Superadmin only.')
return redirect(url_for('main.settings'))
username = request.form['username']
password = request.form['password']
role = request.form['role']
# Check if the username already exists
if User.query.filter_by(username=username).first():
flash('User already exists.')
return redirect(url_for('main.settings'))
# Create a new user
new_user = User(username=username, password=password, role=role)
db.session.add(new_user)
db.session.commit()
flash('User created successfully.')
return redirect(url_for('main.settings'))
@bp.route('/edit_user', methods=['POST']) @bp.route('/edit_user', methods=['POST'])
def edit_user(): def edit_user():
if 'role' not in session or session['role'] != 'superadmin': return edit_user_handler()
flash('Access denied: Superadmin only.')
return redirect(url_for('main.settings'))
user_id = request.form['user_id']
password = request.form['password']
role = request.form['role']
# Fetch the user from the database
user = User.query.get(user_id)
if not user:
flash('User not found.')
return redirect(url_for('main.settings'))
# Update the user's details
if password:
user.password = password
user.role = role
db.session.commit()
flash('User updated successfully.')
return redirect(url_for('main.settings'))
@bp.route('/delete_user', methods=['POST']) @bp.route('/delete_user', methods=['POST'])
def delete_user(): def delete_user():
if 'role' not in session or session['role'] != 'superadmin': return delete_user_handler()
flash('Access denied: Superadmin only.')
return redirect(url_for('main.settings'))
user_id = request.form['user_id']
# Fetch the user from the database
user = User.query.get(user_id)
if not user:
flash('User not found.')
return redirect(url_for('main.settings'))
# Delete the user
db.session.delete(user)
db.session.commit()
flash('User deleted successfully.')
return redirect(url_for('main.settings'))
@bp.route('/save_external_db', methods=['POST']) @bp.route('/save_external_db', methods=['POST'])
def save_external_db(): def save_external_db():
if 'role' not in session or session['role'] != 'superadmin': return save_external_db_handler()
flash('Access denied: Superadmin only.')
return redirect(url_for('main.settings'))
# Get form data # Role Permissions Management Routes
server_domain = request.form['server_domain'] @bp.route('/role_permissions')
port = request.form['port'] def role_permissions():
database_name = request.form['database_name'] return role_permissions_handler()
username = request.form['username']
password = request.form['password']
# Save data to a file in the instance folder @bp.route('/settings/save_role_permissions', methods=['POST'])
settings_file = os.path.join(current_app.instance_path, 'external_server.conf') def save_role_permissions():
os.makedirs(os.path.dirname(settings_file), exist_ok=True) return save_role_permissions_handler()
with open(settings_file, 'w') as f:
f.write(f"server_domain={server_domain}\n")
f.write(f"port={port}\n")
f.write(f"database_name={database_name}\n")
f.write(f"username={username}\n")
f.write(f"password={password}\n")
flash('External database settings saved/updated successfully.') @bp.route('/settings/reset_role_permissions', methods=['POST'])
return redirect(url_for('main.settings')) def reset_role_permissions():
return reset_role_permissions_handler()
@bp.route('/get_report_data', methods=['GET']) @bp.route('/get_report_data', methods=['GET'])
def get_report_data(): def get_report_data():
+465 -48
View File
@@ -1,76 +1,493 @@
from flask import render_template, request, session, redirect, url_for, flash from flask import render_template, request, session, redirect, url_for, flash, current_app, jsonify
from .models import User from .models import User
from . import db from . import db
from .permissions import APP_PERMISSIONS, ROLE_HIERARCHY, ACTIONS, get_all_permissions, get_default_permissions_for_role
import mariadb
import os
import json
# Global permission cache to avoid repeated database queries
_permission_cache = {}
def check_permission(permission_key, user_role=None):
"""
Check if the current user (or specified role) has a specific permission.
Args:
permission_key (str): The permission key like 'settings.user_management.create'
user_role (str, optional): Role to check. If None, uses current session role.
Returns:
bool: True if user has the permission, False otherwise
"""
if user_role is None:
user_role = session.get('role')
if not user_role:
return False
# Superadmin always has all permissions
if user_role == 'superadmin':
return True
# Check cache first
cache_key = f"{user_role}:{permission_key}"
if cache_key in _permission_cache:
return _permission_cache[cache_key]
try:
conn = get_external_db_connection()
cursor = conn.cursor()
cursor.execute("""
SELECT granted FROM role_permissions
WHERE role = %s AND permission_key = %s
""", (user_role, permission_key))
result = cursor.fetchone()
conn.close()
# Cache the result
has_permission = bool(result and result[0])
_permission_cache[cache_key] = has_permission
return has_permission
except Exception as e:
print(f"Error checking permission {permission_key} for role {user_role}: {e}")
return False
def clear_permission_cache():
"""Clear the permission cache (call after permission updates)"""
global _permission_cache
_permission_cache = {}
def require_permission(permission_key):
"""
Decorator to require a specific permission for a route.
Usage:
@require_permission('settings.user_management.create')
def create_user():
...
"""
def decorator(f):
from functools import wraps
@wraps(f)
def decorated_function(*args, **kwargs):
if not check_permission(permission_key):
flash(f'Access denied: You do not have permission to {permission_key}')
return redirect(url_for('main.dashboard'))
return f(*args, **kwargs)
return decorated_function
return decorator
def get_user_permissions(user_role):
"""Get all permissions for a specific role"""
if user_role == 'superadmin':
return [p['key'] for p in get_all_permissions()]
try:
conn = get_external_db_connection()
cursor = conn.cursor()
cursor.execute("""
SELECT permission_key FROM role_permissions
WHERE role = %s AND granted = TRUE
""", (user_role,))
result = cursor.fetchall()
conn.close()
return [row[0] for row in result]
except Exception as e:
print(f"Error getting permissions for role {user_role}: {e}")
return []
# Settings module logic # Settings module logic
import sqlite3
import os
def ensure_roles_table():
instance_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../instance'))
if not os.path.exists(instance_folder):
os.makedirs(instance_folder)
db_path = os.path.join(instance_folder, 'users.db')
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS roles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL,
access_level TEXT NOT NULL,
description TEXT
)
""")
cursor.execute("""
INSERT OR IGNORE INTO roles (name, access_level, description)
VALUES (?, ?, ?)
""", ('superadmin', 'full', 'Full access to all app areas and functions'))
conn.commit()
conn.close()
# List of roles (should match your app's roles)
ROLES = [
'superadmin', 'admin', 'manager', 'warehouse_manager', 'warehouse_worker', 'quality_manager', 'quality_worker'
]
# Helper to check if current user is superadmin # Helper to check if current user is superadmin
def is_superadmin(): def is_superadmin():
return session.get('role') == 'superadmin' return session.get('role') == 'superadmin'
# Route handler for editing access roles # Route handler for role permissions management
def edit_access_roles_handler(): def role_permissions_handler():
if not is_superadmin(): if not is_superadmin():
flash('Access denied: Superadmin only.') flash('Access denied: Superadmin only.')
return redirect(url_for('main.dashboard')) return redirect(url_for('main.dashboard'))
ensure_roles_table()
return render_template('edit_access_roles.html', roles=ROLES)
# Handler for updating role access (stub, to be implemented) try:
def update_role_access_handler(role): # Get roles and their current permissions
if not is_superadmin(): conn = get_external_db_connection()
flash('Access denied: Superadmin only.') cursor = conn.cursor()
return redirect(url_for('main.dashboard'))
if role == 'superadmin': # Get roles from role_hierarchy table
flash('Superadmin access cannot be changed.') cursor.execute("SELECT role_name, display_name, description, level FROM role_hierarchy ORDER BY level DESC")
return redirect(url_for('main.edit_access_roles')) role_data = cursor.fetchall()
access_level = request.form.get('access_level')
# TODO: Save access_level for the role in the database or config roles = {}
flash(f'Access for role {role} updated to {access_level}.') for role_name, display_name, description, level in role_data:
return redirect(url_for('main.edit_access_roles')) roles[role_name] = {
'display_name': display_name,
'description': description,
'level': level
}
# Get current role permissions
cursor.execute("""
SELECT role, permission_key
FROM role_permissions
WHERE granted = TRUE
""")
permission_data = cursor.fetchall()
role_permissions = {}
for role, permission_key in permission_data:
if role not in role_permissions:
role_permissions[role] = []
role_permissions[role].append(permission_key)
conn.close()
# Convert to JSON for JavaScript
permissions_json = json.dumps(get_all_permissions())
role_permissions_json = json.dumps(role_permissions)
return render_template('role_permissions.html',
roles=roles,
pages=APP_PERMISSIONS,
action_names=ACTIONS,
permissions_json=permissions_json,
role_permissions_json=role_permissions_json)
except Exception as e:
flash(f'Error loading role permissions: {e}')
return redirect(url_for('main.settings'))
def settings_handler(): def settings_handler():
if 'role' not in session or session['role'] != 'superadmin': if 'role' not in session or session['role'] != 'superadmin':
flash('Access denied: Superadmin only.') flash('Access denied: Superadmin only.')
return redirect(url_for('main.dashboard')) return redirect(url_for('main.dashboard'))
users = User.query.all()
# Get users from external MariaDB database
users = []
try:
conn = get_external_db_connection()
cursor = conn.cursor()
# Create users table if it doesn't exist
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
role VARCHAR(50) NOT NULL,
email VARCHAR(255)
)
''')
# Get all users from external database
cursor.execute("SELECT id, username, password, role, email FROM users")
users_data = cursor.fetchall()
# Convert to list of dictionaries for template compatibility
users = []
for user_data in users_data:
users.append({
'id': user_data[0],
'username': user_data[1],
'password': user_data[2],
'role': user_data[3],
'email': user_data[4] if len(user_data) > 4 else None
})
conn.close()
except Exception as e:
print(f"Error fetching users from external database: {e}")
flash(f'Error loading users: {e}')
# Load external database settings from the instance folder # Load external database settings from the instance folder
external_settings = {} external_settings = {}
import os
from flask import current_app
settings_file = os.path.join(current_app.instance_path, 'external_server.conf') settings_file = os.path.join(current_app.instance_path, 'external_server.conf')
if os.path.exists(settings_file): if os.path.exists(settings_file):
with open(settings_file, 'r') as f: with open(settings_file, 'r') as f:
for line in f: for line in f:
key, value = line.strip().split('=', 1) key, value = line.strip().split('=', 1)
external_settings[key] = value external_settings[key] = value
return render_template('settings.html', users=users, external_settings=external_settings) return render_template('settings.html', users=users, external_settings=external_settings)
# Add more settings-related functions here as needed # Helper function to get external database connection
def get_external_db_connection():
"""Reads the external_server.conf file and returns a MariaDB database connection."""
settings_file = os.path.join(current_app.instance_path, 'external_server.conf')
if not os.path.exists(settings_file):
raise FileNotFoundError("The external_server.conf file is missing in the instance folder.")
# Read settings from the configuration file
settings = {}
with open(settings_file, 'r') as f:
for line in f:
key, value = line.strip().split('=', 1)
settings[key] = value
# Create a database connection
return mariadb.connect(
user=settings['username'],
password=settings['password'],
host=settings['server_domain'],
port=int(settings['port']),
database=settings['database_name']
)
# User management handlers
def create_user_handler():
if 'role' not in session or session['role'] != 'superadmin':
flash('Access denied: Superadmin only.')
return redirect(url_for('main.settings'))
username = request.form['username']
password = request.form['password']
role = request.form['role']
email = request.form.get('email', '').strip() or None # Optional field
try:
# Connect to external MariaDB database
conn = get_external_db_connection()
cursor = conn.cursor()
# Create users table if it doesn't exist
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
role VARCHAR(50) NOT NULL,
email VARCHAR(255)
)
''')
# Check if the username already exists
cursor.execute("SELECT id FROM users WHERE username = %s", (username,))
if cursor.fetchone():
flash('User already exists.')
conn.close()
return redirect(url_for('main.settings'))
# Create a new user in external MariaDB
cursor.execute("""
INSERT INTO users (username, password, role, email)
VALUES (%s, %s, %s, %s)
""", (username, password, role, email))
conn.commit()
conn.close()
flash('User created successfully in external database.')
except Exception as e:
print(f"Error creating user in external database: {e}")
flash(f'Error creating user: {e}')
return redirect(url_for('main.settings'))
def edit_user_handler():
if 'role' not in session or session['role'] != 'superadmin':
flash('Access denied: Superadmin only.')
return redirect(url_for('main.settings'))
user_id = request.form.get('user_id')
password = request.form.get('password', '').strip()
role = request.form.get('role')
email = request.form.get('email', '').strip() or None # Optional field
if not user_id or not role:
flash('Missing required fields.')
return redirect(url_for('main.settings'))
try:
# Connect to external MariaDB database
conn = get_external_db_connection()
cursor = conn.cursor()
# Check if the user exists
cursor.execute("SELECT id FROM users WHERE id = %s", (user_id,))
if not cursor.fetchone():
flash('User not found.')
conn.close()
return redirect(url_for('main.settings'))
# Update the user's details in external MariaDB
if password: # Only update password if provided
cursor.execute("""
UPDATE users SET password = %s, role = %s, email = %s WHERE id = %s
""", (password, role, email, user_id))
flash('User updated successfully (including password).')
else: # Just update role and email if no password provided
cursor.execute("""
UPDATE users SET role = %s, email = %s WHERE id = %s
""", (role, email, user_id))
flash('User role updated successfully.')
conn.commit()
conn.close()
except Exception as e:
print(f"Error updating user in external database: {e}")
flash(f'Error updating user: {e}')
return redirect(url_for('main.settings'))
def delete_user_handler():
if 'role' not in session or session['role'] != 'superadmin':
flash('Access denied: Superadmin only.')
return redirect(url_for('main.settings'))
user_id = request.form['user_id']
try:
# Connect to external MariaDB database
conn = get_external_db_connection()
cursor = conn.cursor()
# Check if the user exists
cursor.execute("SELECT id FROM users WHERE id = %s", (user_id,))
if not cursor.fetchone():
flash('User not found.')
conn.close()
return redirect(url_for('main.settings'))
# Delete the user from external MariaDB
cursor.execute("DELETE FROM users WHERE id = %s", (user_id,))
conn.commit()
conn.close()
flash('User deleted successfully from external database.')
except Exception as e:
print(f"Error deleting user from external database: {e}")
flash(f'Error deleting user: {e}')
return redirect(url_for('main.settings'))
def save_external_db_handler():
if 'role' not in session or session['role'] != 'superadmin':
flash('Access denied: Superadmin only.')
return redirect(url_for('main.settings'))
# Get form data
server_domain = request.form['server_domain']
port = request.form['port']
database_name = request.form['database_name']
username = request.form['username']
password = request.form['password']
# Save data to a file in the instance folder
settings_file = os.path.join(current_app.instance_path, 'external_server.conf')
os.makedirs(os.path.dirname(settings_file), exist_ok=True)
with open(settings_file, 'w') as f:
f.write(f"server_domain={server_domain}\n")
f.write(f"port={port}\n")
f.write(f"database_name={database_name}\n")
f.write(f"username={username}\n")
f.write(f"password={password}\n")
flash('External database settings saved/updated successfully.')
return redirect(url_for('main.settings'))
def save_role_permissions_handler():
"""Save role permissions via AJAX"""
if not is_superadmin():
return jsonify({'success': False, 'error': 'Access denied: Superadmin only.'})
try:
data = request.get_json()
role = data.get('role')
permissions = data.get('permissions', [])
if not role:
return jsonify({'success': False, 'error': 'Role is required'})
conn = get_external_db_connection()
cursor = conn.cursor()
# Clear existing permissions for this role
cursor.execute("DELETE FROM role_permissions WHERE role = %s", (role,))
# Add new permissions
current_user = session.get('username', 'system')
for permission_key in permissions:
cursor.execute("""
INSERT INTO role_permissions (role, permission_key, granted, granted_by)
VALUES (%s, %s, TRUE, %s)
""", (role, permission_key, current_user))
# Log the change
cursor.execute("""
INSERT INTO permission_audit_log (role, permission_key, action, changed_by, reason)
VALUES (%s, %s, 'bulk_update', %s, %s)
""", (role, f"Updated {len(permissions)} permissions", current_user, f"Bulk update via UI"))
conn.commit()
conn.close()
# Clear permission cache since permissions changed
clear_permission_cache()
return jsonify({'success': True, 'message': f'Saved {len(permissions)} permissions for {role}'})
except Exception as e:
return jsonify({'success': False, 'error': str(e)})
def reset_role_permissions_handler():
"""Reset role permissions to defaults"""
if not is_superadmin():
return jsonify({'success': False, 'error': 'Access denied: Superadmin only.'})
try:
data = request.get_json()
role = data.get('role')
if not role:
return jsonify({'success': False, 'error': 'Role is required'})
# Get default permissions for the role
default_permissions = get_default_permissions_for_role(role)
conn = get_external_db_connection()
cursor = conn.cursor()
# Clear existing permissions for this role
cursor.execute("DELETE FROM role_permissions WHERE role = %s", (role,))
# Add default permissions
current_user = session.get('username', 'system')
for permission_key in default_permissions:
cursor.execute("""
INSERT INTO role_permissions (role, permission_key, granted, granted_by)
VALUES (%s, %s, TRUE, %s)
""", (role, permission_key, current_user))
# Log the change
cursor.execute("""
INSERT INTO permission_audit_log (role, permission_key, action, changed_by, reason)
VALUES (%s, %s, 'reset_defaults', %s, %s)
""", (role, f"Reset {len(default_permissions)} permissions", current_user, "Reset to default permissions"))
conn.commit()
conn.close()
# Clear permission cache since permissions changed
clear_permission_cache()
return jsonify({
'success': True,
'permissions': default_permissions,
'message': f'Reset {len(default_permissions)} permissions for {role} to defaults'
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)})
@@ -1,43 +0,0 @@
{% extends "base.html" %}
{% block title %}Edit Access Roles{% endblock %}
{% block content %}
<div class="card" style="max-width: 700px; margin: 32px auto;">
<h3>Role Access Management</h3>
<p>Configure which roles can view or execute functions on each app page and feature.</p>
<table class="scan-table" style="width:100%;">
<thead>
<tr>
<th>Role</th>
<th>Access Level</th>
<th>Editable</th>
</tr>
</thead>
<tbody>
<tr>
<td>superadmin</td>
<td>Full access to all pages and functions</td>
<td><span style="color:#888;">Not editable</span></td>
</tr>
{% for role in roles %}
{% if role != 'superadmin' %}
<tr>
<td>{{ role }}</td>
<td>
<form method="POST" action="{{ url_for('main.update_role_access', role=role) }}">
<select name="access_level">
<option value="view">View Only</option>
<option value="execute">View & Execute</option>
<option value="none">No Access</option>
</select>
<button type="submit" class="btn">Save</button>
</form>
</td>
<td>Editable</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
<p style="margin-top:16px; color:#888;">Only superadmin users can view and manage role access.</p>
</div>
{% endblock %}
+748
View File
@@ -0,0 +1,748 @@
{% extends "base.html" %}
{% block title %}Role Permissions Management{% endblock %}
{% block head %}
<style>
.permissions-container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.header-section {
text-align: center;
margin-bottom: 30px;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 12px;
color: white;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}
.header-section h2 {
margin: 0 0 10px 0;
font-size: 28px;
font-weight: 600;
}
.header-section p {
margin: 0;
opacity: 0.9;
font-size: 16px;
}
.role-tabs {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 8px;
margin-bottom: 25px;
background-color: #f8f9fa;
padding: 8px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.role-tab {
padding: 15px 20px;
cursor: pointer;
background-color: white;
border: 2px solid transparent;
border-radius: 8px;
text-align: center;
transition: all 0.3s ease;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.role-tab.active {
background: linear-gradient(135deg, #007bff, #0056b3);
color: white;
border-color: #0056b3;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,123,255,0.3);
}
.role-tab:hover:not(.active) {
background-color: #e3f2fd;
border-color: #007bff;
transform: translateY(-1px);
}
.permission-tree {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.page-section {
background: white;
border-radius: 12px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
overflow: hidden;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.page-section:hover {
transform: translateY(-2px);
box-shadow: 0 6px 25px rgba(0,0,0,0.15);
}
.page-header {
padding: 18px 24px;
background: linear-gradient(135deg, #6c5ce7, #a29bfe);
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
font-weight: 600;
color: white;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.page-header::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
transition: left 0.5s;
}
.page-header:hover::before {
left: 100%;
}
.page-header.expanded {
background: linear-gradient(135deg, #007bff, #0056b3);
}
.expand-icon {
transition: transform 0.3s;
font-size: 12px;
}
.expanded .expand-icon {
transform: rotate(90deg);
}
.page-content {
display: none;
padding: 0;
}
.page-content.expanded {
display: block;
}
.section-item {
border-bottom: 1px solid #f1f1f1;
}
.section-item:last-child {
border-bottom: none;
}
.section-header {
padding: 16px 24px;
background: linear-gradient(135deg, #f8f9fa, #e9ecef);
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
font-weight: 500;
color: #495057;
transition: all 0.3s ease;
border-left: 3px solid transparent;
}
.section-header:hover {
background: linear-gradient(135deg, #e3f2fd, #bbdefb);
border-left-color: #2196f3;
color: #1565c0;
}
.section-header.expanded {
background: linear-gradient(135deg, #e3f2fd, #bbdefb);
color: #1976d2;
border-left-color: #1976d2;
}
.section-content {
display: none;
padding: 20px 24px;
background: linear-gradient(145deg, #ffffff, #f8f9fa);
}
.section-content.expanded {
display: block;
}
.action-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 12px;
margin-top: 10px;
}
.action-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background: white;
border-radius: 8px;
border: 2px solid #f1f3f4;
transition: all 0.3s ease;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.action-item:hover {
border-color: #007bff;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0,123,255,0.15);
}
.action-item.granted {
border-color: #28a745;
background: linear-gradient(145deg, #ffffff, #f8fff9);
}
.action-label {
display: flex;
align-items: center;
font-size: 14px;
color: #495057;
font-weight: 500;
}
.action-icon {
width: 20px;
height: 20px;
margin-right: 12px;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
color: white;
font-weight: bold;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
.action-view { background-color: #28a745; }
.action-create { background-color: #007bff; }
.action-edit { background-color: #ffc107; color: #212529; }
.action-delete { background-color: #dc3545; }
.action-upload { background-color: #6f42c1; }
.action-download { background-color: #20c997; }
.action-export { background-color: #fd7e14; }
.action-import { background-color: #e83e8c; }
.permission-toggle {
position: relative;
display: inline-block;
width: 50px;
height: 25px;
}
.permission-toggle input {
opacity: 0;
width: 0;
height: 0;
}
.toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: 0.4s;
border-radius: 25px;
}
.toggle-slider:before {
position: absolute;
content: "";
height: 19px;
width: 19px;
left: 3px;
bottom: 3px;
background-color: white;
transition: 0.4s;
border-radius: 50%;
}
input:checked + .toggle-slider {
background-color: #007bff;
}
input:checked + .toggle-slider:before {
transform: translateX(25px);
}
.permission-summary {
background: linear-gradient(145deg, #ffffff, #f8f9fa);
padding: 25px;
margin-bottom: 25px;
border-radius: 12px;
border-left: 5px solid #007bff;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}
.summary-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 20px;
margin-top: 15px;
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 15px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
transition: transform 0.2s ease;
}
.stat-item:hover {
transform: translateY(-2px);
}
.stat-number {
font-size: 28px;
font-weight: bold;
color: #007bff;
margin-bottom: 5px;
}
.stat-label {
font-size: 11px;
color: #6c757d;
text-transform: uppercase;
font-weight: 600;
letter-spacing: 0.5px;
}
.action-buttons {
margin-top: 30px;
padding: 25px;
background: linear-gradient(145deg, #ffffff, #f8f9fa);
border-radius: 12px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
display: flex;
gap: 15px;
justify-content: center;
flex-wrap: wrap;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
.btn-primary {
background: linear-gradient(135deg, #007bff, #0056b3);
color: white;
}
.btn-primary:hover {
background: linear-gradient(135deg, #0056b3, #004494);
}
.btn-secondary {
background: linear-gradient(135deg, #6c757d, #545b62);
color: white;
}
.btn-secondary:hover {
background: linear-gradient(135deg, #545b62, #383d41);
}
.btn-success {
background: linear-gradient(135deg, #28a745, #1e7e34);
color: white;
}
.btn-success:hover {
background: linear-gradient(135deg, #1e7e34, #155724);
}
/* Responsive Design */
@media (max-width: 768px) {
.permissions-container {
padding: 10px;
}
.role-tabs {
grid-template-columns: 1fr;
}
.permission-tree {
grid-template-columns: 1fr;
}
.action-grid {
grid-template-columns: 1fr;
}
.summary-stats {
grid-template-columns: repeat(2, 1fr);
}
.action-buttons {
flex-direction: column;
}
.btn {
width: 100%;
}
}
</style>
{% endblock %}
{% block content %}
<div class="permissions-container">
<div class="header-section">
<h2>Role Permissions Management</h2>
<p>Configure granular access permissions for each role in the system</p>
</div>
<!-- Role Tabs -->
<div class="role-tabs">
{% for role_name, role_data in roles.items() %}
<div class="role-tab {% if loop.first %}active{% endif %}"
data-role="{{ role_name }}"
onclick="switchRole('{{ role_name }}')">
<div>{{ role_data.display_name }}</div>
<small>Level {{ role_data.level }}</small>
</div>
{% endfor %}
</div>
{% for role_name, role_data in roles.items() %}
<div class="role-content" id="role-{{ role_name }}"
style="{% if not loop.first %}display: none;{% endif %}">
<!-- Permission Summary -->
<div class="permission-summary">
<h4>{{ role_data.display_name }} Permissions Summary</h4>
<p>{{ role_data.description }}</p>
<div class="summary-stats">
<div class="stat-item">
<div class="stat-number" id="total-permissions-{{ role_name }}">0</div>
<div class="stat-label">Total Permissions</div>
</div>
<div class="stat-item">
<div class="stat-number" id="granted-permissions-{{ role_name }}">0</div>
<div class="stat-label">Granted</div>
</div>
<div class="stat-item">
<div class="stat-number" id="denied-permissions-{{ role_name }}">0</div>
<div class="stat-label">Denied</div>
</div>
</div>
</div>
<!-- Permission Tree -->
<div class="permission-tree">
{% for page_key, page_data in pages.items() %}
<div class="page-section">
<div class="page-header" onclick="togglePage('{{ role_name }}', '{{ page_key }}')">
<span>
<span class="expand-icon"></span>
{{ page_data.name }}
</span>
<span class="page-stats" id="page-stats-{{ role_name }}-{{ page_key }}">0/0</span>
</div>
<div class="page-content" id="page-content-{{ role_name }}-{{ page_key }}">
{% for section_key, section_data in page_data.sections.items() %}
<div class="section-item">
<div class="section-header" onclick="toggleSection('{{ role_name }}', '{{ page_key }}', '{{ section_key }}')">
<span>
<span class="expand-icon"></span>
{{ section_data.name }}
</span>
<span class="section-stats" id="section-stats-{{ role_name }}-{{ page_key }}-{{ section_key }}">0/{{ section_data.actions|length }}</span>
</div>
<div class="section-content" id="section-content-{{ role_name }}-{{ page_key }}-{{ section_key }}">
<div class="action-grid">
{% for action in section_data.actions %}
<div class="action-item" id="action-{{ role_name }}-{{ page_key }}-{{ section_key }}-{{ action }}">
<div class="action-label">
<div class="action-icon action-{{ action }}">
{% if action == 'view' %}👁{% elif action == 'create' %}{% elif action == 'edit' %}✏️{% elif action == 'delete' %}🗑{% elif action == 'upload' %}📤{% elif action == 'download' %}📥{% elif action == 'export' %}📊{% elif action == 'import' %}📈{% endif %}
</div>
<span>{{ action_names.get(action, action) }}</span>
</div>
<label class="permission-toggle">
<input type="checkbox"
data-role="{{ role_name }}"
data-permission="{{ page_key }}.{{ section_key }}.{{ action }}"
onchange="updatePermission('{{ role_name }}', '{{ page_key }}.{{ section_key }}.{{ action }}', this.checked)">
<span class="toggle-slider"></span>
</label>
</div>
{% endfor %}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
<!-- Action Buttons -->
<div class="action-buttons">
<button class="btn btn-secondary" onclick="resetToDefaults('{{ role_name }}')">Reset to Defaults</button>
<button class="btn btn-primary" onclick="savePermissions('{{ role_name }}')">Save Changes</button>
<button class="btn btn-success" onclick="copyFromRole('{{ role_name }}')">Copy from Another Role</button>
</div>
</div>
{% endfor %}
</div>
<script>
let currentRole = '{{ roles.keys()|first }}';
let permissions = {{ permissions_json|safe }};
let rolePermissions = {{ role_permissions_json|safe }};
function switchRole(roleName) {
// Hide all role contents
document.querySelectorAll('.role-content').forEach(content => {
content.style.display = 'none';
});
// Remove active class from all tabs
document.querySelectorAll('.role-tab').forEach(tab => {
tab.classList.remove('active');
});
// Show selected role content
document.getElementById('role-' + roleName).style.display = 'block';
// Add active class to selected tab
document.querySelector(`[data-role="${roleName}"]`).classList.add('active');
currentRole = roleName;
loadPermissions(roleName);
}
function togglePage(roleName, pageKey) {
const header = document.querySelector(`#page-content-${roleName}-${pageKey}`).previousElementSibling;
const content = document.getElementById(`page-content-${roleName}-${pageKey}`);
if (content.classList.contains('expanded')) {
content.classList.remove('expanded');
header.classList.remove('expanded');
} else {
content.classList.add('expanded');
header.classList.add('expanded');
}
}
function toggleSection(roleName, pageKey, sectionKey) {
const header = document.querySelector(`#section-content-${roleName}-${pageKey}-${sectionKey}`).previousElementSibling;
const content = document.getElementById(`section-content-${roleName}-${pageKey}-${sectionKey}`);
if (content.classList.contains('expanded')) {
content.classList.remove('expanded');
header.classList.remove('expanded');
} else {
content.classList.add('expanded');
header.classList.add('expanded');
}
}
function loadPermissions(roleName) {
const rolePerms = rolePermissions[roleName] || [];
// Reset all checkboxes and update visual feedback
document.querySelectorAll(`input[data-role="${roleName}"]`).forEach(checkbox => {
const permissionKey = checkbox.getAttribute('data-permission');
const isGranted = rolePerms.includes(permissionKey);
checkbox.checked = isGranted;
// Update visual feedback
const parts = permissionKey.split('.');
const actionElement = document.getElementById(`action-${roleName}-${parts[0]}-${parts[1]}-${parts[2]}`);
if (actionElement) {
if (isGranted) {
actionElement.classList.add('granted');
} else {
actionElement.classList.remove('granted');
}
}
});
updateAllStats(roleName);
}
function updatePermission(roleName, permissionKey, granted) {
// Update local state
if (!rolePermissions[roleName]) {
rolePermissions[roleName] = [];
}
if (granted && !rolePermissions[roleName].includes(permissionKey)) {
rolePermissions[roleName].push(permissionKey);
} else if (!granted) {
const index = rolePermissions[roleName].indexOf(permissionKey);
if (index > -1) {
rolePermissions[roleName].splice(index, 1);
}
}
// Update visual feedback
const parts = permissionKey.split('.');
const actionElement = document.getElementById(`action-${roleName}-${parts[0]}-${parts[1]}-${parts[2]}`);
if (actionElement) {
if (granted) {
actionElement.classList.add('granted');
} else {
actionElement.classList.remove('granted');
}
}
updateAllStats(roleName);
}
function updateAllStats(roleName) {
const totalPerms = document.querySelectorAll(`input[data-role="${roleName}"]`).length;
const grantedPerms = document.querySelectorAll(`input[data-role="${roleName}"]:checked`).length;
document.getElementById(`total-permissions-${roleName}`).textContent = totalPerms;
document.getElementById(`granted-permissions-${roleName}`).textContent = grantedPerms;
document.getElementById(`denied-permissions-${roleName}`).textContent = totalPerms - grantedPerms;
// Update page and section stats
updatePageStats(roleName);
}
function updatePageStats(roleName) {
const pages = {{ pages.keys()|list|tojson }};
pages.forEach(pageKey => {
const pageCheckboxes = document.querySelectorAll(`input[data-role="${roleName}"][data-permission^="${pageKey}."]`);
const pageGranted = Array.from(pageCheckboxes).filter(cb => cb.checked).length;
const pageTotal = pageCheckboxes.length;
const pageStatsEl = document.getElementById(`page-stats-${roleName}-${pageKey}`);
if (pageStatsEl) {
pageStatsEl.textContent = `${pageGranted}/${pageTotal}`;
}
// Update section stats within this page
const sections = {{ pages|tojson }};
Object.keys(sections[pageKey].sections).forEach(sectionKey => {
const sectionCheckboxes = document.querySelectorAll(`input[data-role="${roleName}"][data-permission^="${pageKey}.${sectionKey}."]`);
const sectionGranted = Array.from(sectionCheckboxes).filter(cb => cb.checked).length;
const sectionTotal = sectionCheckboxes.length;
const sectionStatsEl = document.getElementById(`section-stats-${roleName}-${pageKey}-${sectionKey}`);
if (sectionStatsEl) {
sectionStatsEl.textContent = `${sectionGranted}/${sectionTotal}`;
}
});
});
}
function savePermissions(roleName) {
const permissions = rolePermissions[roleName] || [];
fetch('/settings/save_role_permissions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
role: roleName,
permissions: permissions
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Permissions saved successfully!');
} else {
alert('Error saving permissions: ' + data.error);
}
})
.catch(error => {
alert('Error saving permissions: ' + error);
});
}
function resetToDefaults(roleName) {
if (confirm('Are you sure you want to reset permissions to defaults? This will overwrite all current settings for this role.')) {
fetch('/settings/reset_role_permissions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
role: roleName
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
rolePermissions[roleName] = data.permissions;
loadPermissions(roleName);
alert('Permissions reset to defaults!');
} else {
alert('Error resetting permissions: ' + data.error);
}
})
.catch(error => {
alert('Error resetting permissions: ' + error);
});
}
}
function copyFromRole(targetRole) {
const roles = Object.keys(rolePermissions);
const sourceRole = prompt(`Enter the role to copy permissions from:\nAvailable roles: ${roles.join(', ')}`);
if (sourceRole && roles.includes(sourceRole) && sourceRole !== targetRole) {
if (confirm(`Copy all permissions from ${sourceRole} to ${targetRole}?`)) {
rolePermissions[targetRole] = [...(rolePermissions[sourceRole] || [])];
loadPermissions(targetRole);
alert('Permissions copied successfully!');
}
} else if (sourceRole) {
alert('Invalid role name or same as target role.');
}
}
// Initialize
document.addEventListener('DOMContentLoaded', function() {
loadPermissions(currentRole);
});
</script>
{% endblock %}
+32 -8
View File
@@ -12,7 +12,7 @@
<span class="user-name">{{ user.username }}</span> <span class="user-name">{{ user.username }}</span>
<span class="user-role">Role: {{ user.role }}</span> <span class="user-role">Role: {{ user.role }}</span>
<button class="btn edit-user-btn" data-user-id="{{ user.id }}" data-username="{{ user.username }}" data-email="{{ user.email if user.email else '' }}" data-role="{{ user.role }}">Edit User</button> <button class="btn edit-user-btn" data-user-id="{{ user.id }}" data-username="{{ user.username }}" data-email="{{ user.email if user.email else '' }}" data-role="{{ user.role }}">Edit User</button>
<button class="btn delete-btn">Delete User</button> <button class="btn delete-btn delete-user-btn" data-user-id="{{ user.id }}" data-username="{{ user.username }}">Delete User</button>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
@@ -37,9 +37,9 @@
</div> </div>
<div class="card" style="margin-top: 32px;"> <div class="card" style="margin-top: 32px;">
<h3>Edit Access Roles</h3> <h3>Role & Permissions Management</h3>
<p>Manage which roles can view or execute functions on each app page and feature.</p> <p>Configure granular permissions for each role in the system with expandable sections and detailed access control.</p>
<a href="{{ url_for('main.edit_access_roles') }}" class="btn">Edit Access Roles</a> <a href="{{ url_for('main.role_permissions') }}" class="btn">Manage Role Permissions</a>
</div> </div>
</div> </div>
@@ -48,10 +48,11 @@
<div class="popup-content" style="margin:auto; padding:32px; border-radius:8px; box-shadow:0 2px 8px #333; min-width:320px; max-width:400px; text-align:center;"> <div class="popup-content" style="margin:auto; padding:32px; border-radius:8px; box-shadow:0 2px 8px #333; min-width:320px; max-width:400px; text-align:center;">
<h3 id="user-popup-title">Create/Edit User</h3> <h3 id="user-popup-title">Create/Edit User</h3>
<form id="user-form" method="POST" action="{{ url_for('main.create_user') }}"> <form id="user-form" method="POST" action="{{ url_for('main.create_user') }}">
<input type="hidden" id="user-id" name="user_id">
<label for="username">Username:</label> <label for="username">Username:</label>
<input type="text" id="username" name="username" required> <input type="text" id="username" name="username" required>
<label for="email">Email Address:</label> <label for="email">Email (Optional):</label>
<input type="email" id="email" name="email" required> <input type="email" id="email" name="email">
<label for="password">Password:</label> <label for="password">Password:</label>
<input type="password" id="password" name="password" required> <input type="password" id="password" name="password" required>
<label for="role">Role:</label> <label for="role">Role:</label>
@@ -87,21 +88,44 @@ document.getElementById('create-user-btn').onclick = function() {
document.getElementById('user-popup-title').innerText = 'Create User'; document.getElementById('user-popup-title').innerText = 'Create User';
document.getElementById('user-form').reset(); document.getElementById('user-form').reset();
document.getElementById('user-form').setAttribute('action', '{{ url_for("main.create_user") }}'); document.getElementById('user-form').setAttribute('action', '{{ url_for("main.create_user") }}');
document.getElementById('user-id').value = '';
document.getElementById('password').required = true;
document.getElementById('password').placeholder = '';
document.getElementById('username').readOnly = false;
}; };
document.getElementById('close-user-popup-btn').onclick = function() { document.getElementById('close-user-popup-btn').onclick = function() {
document.getElementById('user-popup').style.display = 'none'; document.getElementById('user-popup').style.display = 'none';
}; };
// Edit User button logic // Edit User button logic
Array.from(document.getElementsByClassName('edit-user-btn')).forEach(function(btn) { Array.from(document.getElementsByClassName('edit-user-btn')).forEach(function(btn) {
btn.onclick = function() { btn.onclick = function() {
document.getElementById('user-popup').style.display = 'flex'; document.getElementById('user-popup').style.display = 'flex';
document.getElementById('user-popup-title').innerText = 'Edit User'; document.getElementById('user-popup-title').innerText = 'Edit User';
document.getElementById('user-id').value = btn.getAttribute('data-user-id');
document.getElementById('username').value = btn.getAttribute('data-username'); document.getElementById('username').value = btn.getAttribute('data-username');
document.getElementById('email').value = btn.getAttribute('data-email'); document.getElementById('email').value = btn.getAttribute('data-email') || '';
document.getElementById('role').value = btn.getAttribute('data-role'); document.getElementById('role').value = btn.getAttribute('data-role');
document.getElementById('password').value = ''; document.getElementById('password').value = '';
document.getElementById('user-form').setAttribute('action', '/edit_user/' + btn.getAttribute('data-user-id')); document.getElementById('password').required = false;
document.getElementById('password').placeholder = 'Leave blank to keep current password';
document.getElementById('username').readOnly = true;
document.getElementById('user-form').setAttribute('action', '{{ url_for("main.edit_user") }}');
}; };
}); });
// Delete User button logic
Array.from(document.getElementsByClassName('delete-user-btn')).forEach(function(btn) {
btn.onclick = function() {
document.getElementById('delete-user-popup').style.display = 'flex';
document.getElementById('delete-username').innerText = btn.getAttribute('data-username');
document.getElementById('delete-user-id').value = btn.getAttribute('data-user-id');
};
});
document.getElementById('close-delete-popup-btn').onclick = function() {
document.getElementById('delete-user-popup').style.display = 'none';
};
</script> </script>
{% endblock %} {% endblock %}