Implement RBAC migration and role management enhancements

- Added AJAX functionality to fetch role permissions for copying.
- Introduced system role management with permission checks for updates.
- Implemented role deletion with confirmation modal and backend handling.
- Enhanced user role assignment migration scripts to transition from legacy profiles to RBAC.
- Created SQL migration scripts for user roles and permissions mapping.
- Updated user interface to support new role management features including copy permissions and system role indicators.
This commit is contained in:
“VeLiTi”
2026-01-27 15:10:21 +01:00
parent aeda4e4cb9
commit f7a91737bc
30 changed files with 1285 additions and 236 deletions

BIN
assets/.DS_Store vendored

Binary file not shown.

View File

@@ -0,0 +1,222 @@
-- ===================================================
-- PROFILE TO RBAC MIGRATION SCRIPT
-- Date: 2025-01-22
-- Description: Migrate from settingsprofiles.php to user_roles RBAC system
-- Note: Uses existing access_elements table (already populated)
-- ===================================================
START TRANSACTION;
-- ===================================================
-- PHASE 1: CREATE ROLES (matching existing profiles)
-- ===================================================
INSERT INTO `user_roles` (`name`, `description`, `is_active`, `created`, `createdby`) VALUES
('Standard', 'Basic user access - view equipment, history, service reports', 1, NOW(), 1),
('Superuser', 'Extended access - manage equipment, products, users', 1, NOW(), 1),
('Admin', 'Administrative access - full management capabilities', 1, NOW(), 1),
('AdminPlus', 'System administrator - complete system access', 1, NOW(), 1),
('Build', 'Build tool access only', 1, NOW(), 1),
('Commerce', 'E-commerce and catalog management', 1, NOW(), 1),
('Distribution', 'Distribution partner access', 1, NOW(), 1),
('Firmware', 'Firmware/software update access only', 1, NOW(), 1),
('Garage', 'Car testing and diagnostics', 1, NOW(), 1),
('Interface', 'API/Interface access', 1, NOW(), 1),
('Service', 'Service technician access', 1, NOW(), 1),
('Other', 'Miscellaneous access level', 1, NOW(), 1)
ON DUPLICATE KEY UPDATE `description` = VALUES(`description`);
-- ===================================================
-- PHASE 2: CREATE ROLE_ACCESS_PERMISSIONS MAPPINGS
-- ===================================================
-- Get role IDs
SET @role_standard = (SELECT rowID FROM user_roles WHERE name = 'Standard' LIMIT 1);
SET @role_superuser = (SELECT rowID FROM user_roles WHERE name = 'Superuser' LIMIT 1);
SET @role_admin = (SELECT rowID FROM user_roles WHERE name = 'Admin' LIMIT 1);
SET @role_adminplus = (SELECT rowID FROM user_roles WHERE name = 'AdminPlus' LIMIT 1);
SET @role_build = (SELECT rowID FROM user_roles WHERE name = 'Build' LIMIT 1);
SET @role_commerce = (SELECT rowID FROM user_roles WHERE name = 'Commerce' LIMIT 1);
SET @role_distribution = (SELECT rowID FROM user_roles WHERE name = 'Distribution' LIMIT 1);
SET @role_firmware = (SELECT rowID FROM user_roles WHERE name = 'Firmware' LIMIT 1);
SET @role_garage = (SELECT rowID FROM user_roles WHERE name = 'Garage' LIMIT 1);
SET @role_interface = (SELECT rowID FROM user_roles WHERE name = 'Interface' LIMIT 1);
SET @role_service = (SELECT rowID FROM user_roles WHERE name = 'Service' LIMIT 1);
SET @role_other = (SELECT rowID FROM user_roles WHERE name = 'Other' LIMIT 1);
-- ===================================================
-- STANDARD ROLE PERMISSIONS (Read-only)
-- Profile: application,firmwaretool,histories,history,servicereport,servicereports,dashboard,profile,equipment,equipments,products_software
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_standard, rowID, 0, 1, 0, 0 FROM access_elements WHERE access_path IN (
'application', 'firmwaretool', 'histories', 'history', 'servicereport', 'servicereports',
'dashboard', 'profile', 'equipment', 'equipments', 'products_software'
)
ON DUPLICATE KEY UPDATE can_read = 1;
-- ===================================================
-- SUPERUSER ROLE PERMISSIONS (Create, Read, Update)
-- Profile: application,assets,firmwaretool,histories,history,history_manage,marketing,partner,partners,
-- servicereport,servicereports,admin,dashboard,profile,equipment,equipment_manage,
-- equipment_manage_edit,equipments,equipments_mass_update,product,product_manage,products,
-- products_software,products_versions,user,user_manage,users
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_superuser, rowID, 1, 1, 1, 0 FROM access_elements WHERE access_path IN (
'application', 'firmwaretool', 'histories', 'history', 'history_manage',
'marketing', 'partner', 'partners', 'servicereport', 'servicereports',
'dashboard', 'profile', 'equipment', 'equipment_manage',
'equipments', 'equipments_mass_update', 'product', 'product_manage', 'products',
'products_software', 'products_versions', 'user', 'users'
)
ON DUPLICATE KEY UPDATE can_create = 1, can_read = 1, can_update = 1;
-- ===================================================
-- ADMIN ROLE PERMISSIONS (Full CRUD)
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_admin, rowID, 1, 1, 1, 1 FROM access_elements WHERE access_path IN (
'application', 'buildtool', 'cartest', 'cartest_manage', 'cartests',
'changelog', 'communication', 'communication_send', 'communications', 'firmwaretool',
'histories', 'history', 'history_manage', 'marketing', 'partner', 'partners',
'servicereport', 'servicereports', 'software_available', 'software_download',
'software_update', 'softwaretool', 'account', 'accounts', 'dashboard', 'profile',
'contract', 'contract_manage', 'contracts', 'equipment', 'equipment_data',
'equipment_healthindex', 'equipment_history', 'equipment_manage',
'equipments', 'equipments_mass_update', 'product', 'product_manage', 'products',
'products_software', 'products_software_assignment', 'products_software_assignments',
'products_software_licenses', 'products_versions', 'report_build',
'report_contracts_billing', 'report_healthindex', 'rma', 'rma_history',
'rma_manage', 'rmas', 'user', 'users'
)
ON DUPLICATE KEY UPDATE can_create = 1, can_read = 1, can_update = 1, can_delete = 1;
-- ===================================================
-- ADMINPLUS ROLE PERMISSIONS (Full access to everything)
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_adminplus, rowID, 1, 1, 1, 1 FROM access_elements WHERE is_active = 1
ON DUPLICATE KEY UPDATE can_create = 1, can_read = 1, can_update = 1, can_delete = 1;
-- ===================================================
-- BUILD ROLE PERMISSIONS
-- Profile: application,buildtool,firmwaretool,dashboard,profile,products_software
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_build, rowID, 1, 1, 1, 0 FROM access_elements WHERE access_path IN (
'application', 'buildtool', 'firmwaretool', 'dashboard', 'profile', 'products_software'
)
ON DUPLICATE KEY UPDATE can_create = 1, can_read = 1, can_update = 1;
-- ===================================================
-- COMMERCE ROLE PERMISSIONS
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_commerce, rowID, 1, 1, 1, 1 FROM access_elements WHERE access_path IN (
'application', 'catalog', 'categories', 'category', 'checkout', 'discount', 'discounts',
'identity', 'invoice', 'media', 'media_manage', 'order', 'orders', 'partner', 'partners',
'placeorder', 'pricelists', 'pricelists_items', 'pricelists_manage', 'shipping',
'shipping_manage', 'shopping_cart', 'taxes', 'transactions', 'transactions_items',
'translation_manage', 'translations', 'translations_details', 'uploader',
'dashboard', 'profile', 'product', 'product_manage', 'products', 'products_attributes',
'products_attributes_items', 'products_attributes_manage', 'products_categories',
'products_configurations', 'products_media', 'products_software', 'products_versions',
'user', 'users'
)
ON DUPLICATE KEY UPDATE can_create = 1, can_read = 1, can_update = 1, can_delete = 1;
-- ===================================================
-- DISTRIBUTION ROLE PERMISSIONS
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_distribution, rowID, 1, 1, 1, 0 FROM access_elements WHERE access_path IN (
'application', 'firmwaretool', 'histories', 'history', 'history_manage',
'marketing', 'partner', 'partners', 'servicereport', 'servicereports',
'dashboard', 'profile', 'equipment', 'equipment_manage',
'equipments', 'equipments_mass_update', 'product', 'product_manage', 'products',
'products_software', 'products_versions', 'user', 'users'
)
ON DUPLICATE KEY UPDATE can_create = 1, can_read = 1, can_update = 1;
-- ===================================================
-- FIRMWARE ROLE PERMISSIONS
-- Profile: application,software_available,software_download,software_update,softwaretool,
-- transactions,transactions_items,products_software_versions
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_firmware, rowID, 0, 1, 1, 0 FROM access_elements WHERE access_path IN (
'application', 'software_available', 'software_download', 'software_update',
'softwaretool', 'transactions', 'transactions_items', 'products_software_versions'
)
ON DUPLICATE KEY UPDATE can_read = 1, can_update = 1;
-- ===================================================
-- GARAGE ROLE PERMISSIONS
-- Profile: application,cartest,cartest_manage,cartests,dashboard,profile,products_versions
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_garage, rowID, 1, 1, 1, 0 FROM access_elements WHERE access_path IN (
'application', 'cartest', 'cartest_manage', 'cartests', 'dashboard', 'profile', 'products_versions'
)
ON DUPLICATE KEY UPDATE can_create = 1, can_read = 1, can_update = 1;
-- ===================================================
-- INTERFACE ROLE PERMISSIONS
-- Profile: application,firmwaretool,invoice,payment,transactions,transactions_items,
-- contract,contracts,equipment_manage,equipments,products_software,products_versions,users
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_interface, rowID, 1, 1, 1, 0 FROM access_elements WHERE access_path IN (
'application', 'firmwaretool', 'invoice', 'payment', 'transactions', 'transactions_items',
'contract', 'contracts', 'equipment_manage', 'equipments', 'products_software',
'products_versions', 'users'
)
ON DUPLICATE KEY UPDATE can_create = 1, can_read = 1, can_update = 1;
-- ===================================================
-- SERVICE ROLE PERMISSIONS
-- Profile: application,assets,firmwaretool,histories,history,history_manage,marketing,partner,partners,
-- servicereport,servicereports,admin,dashboard,profile,equipment,equipment_manage,equipments,
-- products_software,user,user_manage,users
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_service, rowID, 1, 1, 1, 0 FROM access_elements WHERE access_path IN (
'application', 'firmwaretool', 'histories', 'history', 'history_manage',
'marketing', 'partner', 'partners', 'servicereport', 'servicereports',
'dashboard', 'profile', 'equipment', 'equipment_manage', 'equipments', 'products_software',
'user', 'users'
)
ON DUPLICATE KEY UPDATE can_create = 1, can_read = 1, can_update = 1;
-- ===================================================
-- OTHER ROLE PERMISSIONS
-- Profile: application,assets,firmwaretool,histories,history,history_manage,marketing,partner,partners,
-- servicereport,servicereports,admin,dashboard,profile,equipment,equipment_manage,equipments,products_software
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_other, rowID, 0, 1, 1, 0 FROM access_elements WHERE access_path IN (
'application', 'firmwaretool', 'histories', 'history', 'history_manage',
'marketing', 'partner', 'partners', 'servicereport', 'servicereports',
'dashboard', 'profile', 'equipment', 'equipment_manage', 'equipments', 'products_software'
)
ON DUPLICATE KEY UPDATE can_read = 1, can_update = 1;
-- ===================================================
-- VERIFICATION QUERIES
-- ===================================================
-- Check roles created
SELECT rowID, name, description, is_active FROM user_roles ORDER BY rowID;
-- Check permissions per role
SELECT ur.name as role_name, COUNT(rap.rowID) as permission_count
FROM user_roles ur
LEFT JOIN role_access_permissions rap ON ur.rowID = rap.role_id
GROUP BY ur.rowID, ur.name
ORDER BY ur.rowID;
-- ===================================================
-- Change ROLLBACK to COMMIT when ready to apply
-- ===================================================
COMMIT;

View File

@@ -0,0 +1,228 @@
-- ===================================================
-- USER TO RBAC ROLE ASSIGNMENT MIGRATION SCRIPT
-- Date: 2025-01-22
-- Description: Migrate users from settings/view fields to user_role_assignments
-- Prerequisites: Run migration_profiles_to_rbac.sql first to create roles
-- ===================================================
START TRANSACTION;
-- ===================================================
-- MAPPING REFERENCE:
--
-- users.settings field values -> role names:
-- 'standard_profile' or empty with view 0-2 -> Standard
-- 'superuser_profile' or view=2 -> Superuser
-- 'admin_profile' or view=4 -> Admin
-- 'adminplus_profile' or view=5 -> AdminPlus
-- 'build' -> Build
-- 'commerce' -> Commerce
-- 'distribution' -> Distribution
-- 'firmware' -> Firmware
-- 'garage' -> Garage
-- 'interface' -> Interface
-- 'service' -> Service
-- 'other' -> Other
--
-- users.view field (legacy permission level):
-- 1 = SuperUser
-- 2 = Create & Update
-- 3 = Read-only
-- 4 = Admin
-- 5 = Admin+
-- ===================================================
-- Get role IDs
SET @role_standard = (SELECT rowID FROM user_roles WHERE name = 'Standard' LIMIT 1);
SET @role_superuser = (SELECT rowID FROM user_roles WHERE name = 'Superuser' LIMIT 1);
SET @role_admin = (SELECT rowID FROM user_roles WHERE name = 'Admin' LIMIT 1);
SET @role_adminplus = (SELECT rowID FROM user_roles WHERE name = 'AdminPlus' LIMIT 1);
SET @role_build = (SELECT rowID FROM user_roles WHERE name = 'Build' LIMIT 1);
SET @role_commerce = (SELECT rowID FROM user_roles WHERE name = 'Commerce' LIMIT 1);
SET @role_distribution = (SELECT rowID FROM user_roles WHERE name = 'Distribution' LIMIT 1);
SET @role_firmware = (SELECT rowID FROM user_roles WHERE name = 'Firmware' LIMIT 1);
SET @role_garage = (SELECT rowID FROM user_roles WHERE name = 'Garage' LIMIT 1);
SET @role_interface = (SELECT rowID FROM user_roles WHERE name = 'Interface' LIMIT 1);
SET @role_service = (SELECT rowID FROM user_roles WHERE name = 'Service' LIMIT 1);
SET @role_other = (SELECT rowID FROM user_roles WHERE name = 'Other' LIMIT 1);
-- ===================================================
-- PHASE 1: MIGRATE USERS BY SETTINGS FIELD (profile name)
-- ===================================================
-- Users with 'standard_profile' setting
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_standard, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'standard_profile'
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with 'superuser_profile' setting
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_superuser, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'superuser_profile'
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with 'admin_profile' setting
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_admin, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'admin_profile'
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with 'adminplus_profile' setting
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_adminplus, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'adminplus_profile'
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with 'build' setting
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_build, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'build'
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with 'commerce' setting
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_commerce, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'commerce'
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with 'distribution' setting
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_distribution, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'distribution'
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with 'firmware' setting
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_firmware, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'firmware'
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with 'garage' setting
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_garage, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'garage'
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with 'interface' setting
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_interface, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'interface'
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with 'service' setting
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_service, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'service'
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with 'other' setting
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_other, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'other'
ON DUPLICATE KEY UPDATE updated = NOW();
-- ===================================================
-- PHASE 2: MIGRATE USERS WITH EMPTY/NULL SETTINGS (use view field)
-- Only for users not already assigned a role
-- ===================================================
-- Users with view=5 (Admin+) and no settings
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT u.id, @role_adminplus, 1, 'migration_script', NOW(), NOW(), 1
FROM users u
LEFT JOIN user_role_assignments ura ON u.id = ura.user_id AND ura.is_active = 1
WHERE (u.settings IS NULL OR u.settings = '')
AND u.view = '5'
AND ura.rowID IS NULL
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with view=4 (Admin) and no settings
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT u.id, @role_admin, 1, 'migration_script', NOW(), NOW(), 1
FROM users u
LEFT JOIN user_role_assignments ura ON u.id = ura.user_id AND ura.is_active = 1
WHERE (u.settings IS NULL OR u.settings = '')
AND u.view = '4'
AND ura.rowID IS NULL
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with view=1 (SuperUser) and no settings
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT u.id, @role_superuser, 1, 'migration_script', NOW(), NOW(), 1
FROM users u
LEFT JOIN user_role_assignments ura ON u.id = ura.user_id AND ura.is_active = 1
WHERE (u.settings IS NULL OR u.settings = '')
AND u.view = '1'
AND ura.rowID IS NULL
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with view=2 or view=3 (Create/Update or Read-only) and no settings -> Standard
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT u.id, @role_standard, 1, 'migration_script', NOW(), NOW(), 1
FROM users u
LEFT JOIN user_role_assignments ura ON u.id = ura.user_id AND ura.is_active = 1
WHERE (u.settings IS NULL OR u.settings = '')
AND u.view IN ('2', '3')
AND ura.rowID IS NULL
ON DUPLICATE KEY UPDATE updated = NOW();
-- ===================================================
-- PHASE 3: CATCH-ALL - Any remaining users without role -> Standard
-- ===================================================
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT u.id, @role_standard, 1, 'migration_script', NOW(), NOW(), 1
FROM users u
LEFT JOIN user_role_assignments ura ON u.id = ura.user_id AND ura.is_active = 1
WHERE ura.rowID IS NULL
ON DUPLICATE KEY UPDATE updated = NOW();
-- ===================================================
-- VERIFICATION QUERIES
-- ===================================================
-- Check migration results: users per role
SELECT
ur.name as role_name,
COUNT(ura.user_id) as user_count
FROM user_roles ur
LEFT JOIN user_role_assignments ura ON ur.rowID = ura.role_id AND ura.is_active = 1
GROUP BY ur.rowID, ur.name
ORDER BY user_count DESC;
-- Check for users without role assignments (should be 0)
SELECT COUNT(*) as users_without_role
FROM users u
LEFT JOIN user_role_assignments ura ON u.id = ura.user_id AND ura.is_active = 1
WHERE ura.rowID IS NULL;
-- Compare old vs new: show users with their old settings and new role
SELECT
u.id,
u.username,
u.settings as old_profile,
u.view as old_view_level,
ur.name as new_role
FROM users u
LEFT JOIN user_role_assignments ura ON u.id = ura.user_id AND ura.is_active = 1
LEFT JOIN user_roles ur ON ura.role_id = ur.rowID
ORDER BY u.id
LIMIT 50;
-- ===================================================
-- Change ROLLBACK to COMMIT when ready to apply
-- ===================================================
COMMIT;

View File

@@ -515,10 +515,19 @@ echo <<<EOT
// Intercept form submissions
setupFormInterception();
// Intercept fetch and XMLHttpRequest
interceptNetworkRequests();
// Intercept form submissions to show loading
function setupFormInterception() {
document.querySelectorAll('form').forEach(function(form) {
form.addEventListener('submit', function() {
showLoading();
});
});
}
// Intercept all network requests (fetch and XMLHttpRequest)
function interceptNetworkRequests() {
// Track active requests
@@ -1637,42 +1646,25 @@ function getProfile($profile, $permission){
// Always allowed collections: [collection => allowed_actions_string]
$always_allowed = [
'com_log' => 'U',
'com_log' => 'CRU',
'application' => 'CRU',
'user_permissions' => 'R',
'software_update' => 'R',
'software_download' => 'R',
'software_available' => 'R',
'history' => 'U',
'payment' => 'U',
'marketing_files' => 'CRUD',
'marketing_folders' => 'CRUD',
'marketing_tags' => 'CRUD',
'marketing_upload' => 'CRUD',
'marketing_delete' => 'CRUD'
'history' => 'RU',
'payment' => 'U'
];
// Debug log - initial call
if(debug){
$perm_count = is_array($permissions) ? count($permissions) : 'not_array';
$test = "$date - isAllowed called: access_element=$access_element, basic_permission_level=$basic_permission_level, action=$action, permissions_count=$perm_count".PHP_EOL;
error_log($test, 3, $filelocation);
}
// 1. Check if basic_permission_level is 4 (System-admin+) - always allow
if ($basic_permission_level !== null && $basic_permission_level == 4) {
if(debug){
$test = "$date - Allowed by system permission (level 5)".PHP_EOL;
error_log($test, 3, $filelocation);
}
return 1;
}
// 2. Check always_allowed list
if (isset($always_allowed[$access_element]) && str_contains($always_allowed[$access_element], $action)) {
if(debug){
$test = "$date - Allowed by always_allowed list".PHP_EOL;
error_log($test, 3, $filelocation);
}
return 1;
}
@@ -1691,20 +1683,21 @@ function getProfile($profile, $permission){
$permission_key = $action_map[$action] ?? null;
if ($permission_key && isset($element_permissions[$permission_key]) && $element_permissions[$permission_key] == 1) {
if(debug){
$test = "$date - Allowed by RBAC permissions: $access_element -> $permission_key = 1".PHP_EOL;
error_log($test, 3, $filelocation);
}
return 1;
}
if(debug){
$test = "$date - isAllowed called: access_element=$access_element, basic_permission_level=$basic_permission_level, action=$action".PHP_EOL;
error_log($test, 3, $filelocation);
$perm_value = $element_permissions[$permission_key] ?? 'not_set';
$test = "$date - RBAC check failed: $access_element -> $permission_key = $perm_value".PHP_EOL;
error_log($test, 3, $filelocation);
}
} else {
if(debug){
$test = "$date - isAllowed called: access_element=$access_element, basic_permission_level=$basic_permission_level, action=$action".PHP_EOL;
error_log($test, 3, $filelocation);
$test = "$date - Access element '$access_element' not found in permissions array".PHP_EOL;
error_log($test, 3, $filelocation);
}
@@ -1712,9 +1705,12 @@ function getProfile($profile, $permission){
// Not allowed
if(debug){
$test = "$date - isAllowed called: access_element=$access_element, basic_permission_level=$basic_permission_level, action=$action".PHP_EOL;
error_log($test, 3, $filelocation);
$test = "$date - Not allowed: access_element=$access_element, action=$action".PHP_EOL;
error_log($test, 3, $filelocation);
}
return 0;
}
@@ -3913,27 +3909,29 @@ function dateInRange($start_date, $end_date, $date_check)
function getLatestVersion($productcode,$token){
//CALL TO API TO GET ALL ACTIVE CONTRACTS
$api_url = '/v2/products_software/productcode='.$productcode;
$responses = ioAPIv2($api_url,'',$token);
//$pdo = dbConnect($dbname);
//Decode Payload
if (!empty($responses)){$responses = json_decode($responses,true);
}
else{
$responses = $output = array(
"productcode" => "",
"version"=> "",
"mandatory"=> "",
"latest"=> "",
"software"=> "",
"source" => "",
"source_type" => ""
);
;}
//CALL TO API TO GET ALL ACTIVE CONTRACTS
$api_url = '/v2/products_software/productcode='.$productcode;
$responses = ioAPIv2($api_url,'',$token);
//Decode Payload
if (!empty($responses)){$responses = json_decode($responses,true);
}
else{
$responses = $output = array(
"productcode" => "",
"version"=> "",
"mandatory"=> "",
"latest"=> "",
"software"=> "",
"source" => "",
"source_type" => ""
);
;}
//DEFAULT OUTPUT
return $responses;
//DEFAULT OUTPUT
return $responses;
}
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++

View File

@@ -249,15 +249,19 @@ class MarketingFileManager {
emptyState.style.display = 'none';
try {
// Use proper folder ID (null for root, or the folder ID)
const folderId = this.currentFolder ? this.currentFolder : 'null';
// Add cache busting to prevent browser caching
let url = `index.php?page=marketing&action=marketing_files&folder_id=${folderId}&limit=50&_t=${Date.now()}`;
let url = `index.php?page=marketing&action=marketing_files&limit=50&_t=${Date.now()}`;
// Only filter by folder if no tag filter is active (tag search is across all folders)
if (!this.filters.tag) {
const folderId = this.currentFolder ? this.currentFolder : 'null';
url += `&folder_id=${folderId}`;
}
if (this.filters.search) {
url += `&search=${encodeURIComponent(this.filters.search)}`;
}
if (this.filters.tag) {
url += `&tag=${encodeURIComponent(this.filters.tag)}`;
}
@@ -289,21 +293,33 @@ class MarketingFileManager {
if (data && data.length > 0) {
let files = data;
// Client-side file type filtering
if (this.filters.fileTypes.length > 0) {
files = files.filter(file =>
files = files.filter(file =>
this.filters.fileTypes.includes(file.file_type.toLowerCase())
);
}
if (files.length === 0) {
emptyState.style.display = 'block';
// No files after filtering, check for subfolders
const subfolders = this.getSubfolders(this.currentFolder);
if (subfolders.length > 0) {
this.renderFolderTiles(subfolders);
} else {
emptyState.style.display = 'block';
}
} else {
this.renderFiles(files);
}
} else {
emptyState.style.display = 'block';
// No files, check for subfolders
const subfolders = this.getSubfolders(this.currentFolder);
if (subfolders.length > 0) {
this.renderFolderTiles(subfolders);
} else {
emptyState.style.display = 'block';
}
}
} catch (error) {
console.error('Error loading files:', error);
@@ -372,12 +388,73 @@ class MarketingFileManager {
renderFiles(files) {
const container = document.getElementById('filesContainer');
container.innerHTML = '';
files.forEach(file => {
const fileElement = this.createFileElement(file);
container.appendChild(fileElement);
});
}
getSubfolders(folderId) {
// Find immediate children of the specified folder
if (!folderId || folderId === '') {
// Root folder - return top-level folders
return this.folders;
}
// Recursively search for the folder and return its children
const findFolder = (folders, targetId) => {
for (const folder of folders) {
if (folder.id === targetId) {
return folder.children || [];
}
if (folder.children && folder.children.length > 0) {
const found = findFolder(folder.children, targetId);
if (found) return found;
}
}
return [];
};
return findFolder(this.folders, folderId);
}
renderFolderTiles(subfolders) {
const container = document.getElementById('filesContainer');
container.innerHTML = '';
subfolders.forEach(folder => {
const folderElement = this.createFolderTileElement(folder);
container.appendChild(folderElement);
});
}
createFolderTileElement(folder) {
const folderElement = document.createElement('div');
folderElement.className = `folder-tile ${this.viewMode}-item`;
folderElement.setAttribute('data-folder-id', folder.id);
folderElement.innerHTML = `
<div class="folder-tile-icon">
<i class="fa fa-folder"></i>
</div>
<div class="folder-tile-info">
<div class="folder-tile-name" title="${this.escapeHtml(folder.folder_name)}">
${this.escapeHtml(folder.folder_name)}
</div>
<div class="folder-tile-meta">
<span class="folder-file-count">${folder.file_count || 0} files</span>
</div>
</div>
`;
// Click to navigate to folder
folderElement.addEventListener('click', () => {
this.selectFolder(folder.id);
});
return folderElement;
}
createFileElement(file) {
const fileElement = document.createElement('div');
@@ -385,7 +462,7 @@ class MarketingFileManager {
fileElement.setAttribute('data-file-id', file.id);
const thumbnail = this.getThumbnail(file);
const tags = file.tags.map(tag => `<span class="tag">${this.escapeHtml(tag)}</span>`).join('');
const tags = file.tags.map(tag => `<span class="tag clickable" data-tag="${this.escapeHtml(tag)}">${this.escapeHtml(tag)}</span>`).join('');
fileElement.innerHTML = `
<div class="file-thumbnail">
@@ -429,11 +506,20 @@ class MarketingFileManager {
fileElement.querySelector('.edit-btn').addEventListener('click', () => {
this.editFile(file);
});
// Make tags clickable to filter by tag
fileElement.querySelectorAll('.tag.clickable').forEach(tagElement => {
tagElement.addEventListener('click', (e) => {
e.stopPropagation();
const tagName = tagElement.getAttribute('data-tag');
this.filterByTag(tagName);
});
});
fileElement.addEventListener('dblclick', () => {
this.previewFile(file);
});
return fileElement;
}
@@ -843,15 +929,41 @@ class MarketingFileManager {
updateFileTypeFilters() {
this.filters.fileTypes = [];
document.querySelectorAll('.file-type-filters input[type="checkbox"]:checked').forEach(checkbox => {
const types = checkbox.value.split(',');
this.filters.fileTypes.push(...types);
});
this.loadFiles();
}
filterByTag(tagName) {
// Set the tag filter
this.filters.tag = tagName;
// Update the dropdown to show the selected tag
const tagSelect = document.getElementById('tagFilter');
if (tagSelect) {
tagSelect.value = tagName;
}
// Clear folder selection to search across all folders
this.currentFolder = '';
// Update folder tree UI to show root as active
document.querySelectorAll('.folder-item').forEach(item => {
item.classList.remove('active');
});
const rootFolder = document.querySelector('.folder-item.root');
if (rootFolder) {
rootFolder.classList.add('active');
}
// Reload files with the tag filter
this.loadFiles();
}
populateTagFilter(tags) {
const select = document.getElementById('tagFilter');
select.innerHTML = '<option value="">All Tags</option>';

View File

@@ -100,6 +100,45 @@ if (document.readyState === 'loading') {
checkBrowserCompatibility();
}
// Shared serial port reference for upload.js to use
window.sharedSerialPort = null;
// Override requestPort to minimize user prompts
// This intercepts all requestPort calls (including from upload.js) to reuse authorized ports
if ('serial' in navigator) {
const originalRequestPort = navigator.serial.requestPort.bind(navigator.serial);
navigator.serial.requestPort = async function(options) {
// If we have a shared port, return it instead of prompting
if (window.sharedSerialPort) {
console.log('Using shared serial port (no prompt needed)');
return window.sharedSerialPort;
}
// Try already-authorized ports matching the filters
const ports = await navigator.serial.getPorts();
if (ports.length > 0 && options?.filters) {
const match = ports.find(p => {
const info = p.getInfo();
return options.filters.some(f =>
info.usbVendorId === f.usbVendorId &&
info.usbProductId === f.usbProductId
);
});
if (match) {
console.log('Using previously authorized port (no prompt needed)');
window.sharedSerialPort = match;
return match;
}
}
// Fallback: original prompt behavior
const port = await originalRequestPort(options);
window.sharedSerialPort = port; // Store for future use
return port;
};
}
// Function to log communication to API (reused from scripts.js)
async function logCommunication(data, direction) {
// Only log if debug mode is enabled
@@ -400,7 +439,11 @@ async function closePortAfterRead() {
await port.close();
await logCommunication('Port closed successfully', 'info');
// Reset for next connection
// Keep port reference in sharedSerialPort for upload.js to reuse
// This prevents the need for another user prompt during firmware upload
window.sharedSerialPort = port;
// Reset local variables for next connection
reader = null;
writer = null;
readableStreamClosed = null;
@@ -410,7 +453,12 @@ async function closePortAfterRead() {
console.error('Error closing port after read:', error);
await logCommunication(`Error closing port: ${error.message}`, 'error');
// Force reset even on error
// Keep port reference even on error if port exists
if (port) {
window.sharedSerialPort = port;
}
// Force reset local variables even on error
reader = null;
writer = null;
readableStreamClosed = null;