diff --git a/app/routes/admin_pages.py b/app/routes/admin_pages.py index d0aaf037..1c6193e5 100644 --- a/app/routes/admin_pages.py +++ b/app/routes/admin_pages.py @@ -499,7 +499,7 @@ async def admin_user_edit_page( raise HTTPException(status_code=403, detail="Super admin access required") return templates.TemplateResponse( - "admin/user-edit.html", + "admin/admin-user-edit.html", { "request": request, "user": current_user, diff --git a/app/templates/admin/admin-user-edit.html b/app/templates/admin/admin-user-edit.html new file mode 100644 index 00000000..071700a9 --- /dev/null +++ b/app/templates/admin/admin-user-edit.html @@ -0,0 +1,259 @@ +{# app/templates/admin/admin-user-edit.html #} +{% extends "admin/base.html" %} +{% from 'shared/macros/alerts.html' import loading_state %} +{% from 'shared/macros/headers.html' import edit_page_header %} + +{% block title %}Edit Admin User{% endblock %} + +{% block alpine_data %}adminUserEditPage(){% endblock %} + +{% block content %} +{% call edit_page_header('Edit Admin User', '/admin/admin-users', subtitle_show='adminUser', back_label='Back to Admin Users') %} + @ +{% endcall %} + +{{ loading_state('Loading admin user...', show_condition='loading') }} + + +
+ +
+

+ Quick Actions +

+
+ + + + + + + +
+ + Super Admin + + + Platform Admin + + + Active + + + Inactive + +
+
+
+ + +
+

+ Admin Information +

+
+ +
+
+ User ID +

+
+
+ Username +

+
+
+ Email +

+
+
+ +
+
+ First Name +

+
+
+ Last Name +

+
+
+
+
+ + + + + + + + +
+

+ Danger Zone +

+
+ + +
+

+ + Deleting an admin user is permanent and cannot be undone. +

+
+
+ + +
+
+
+

+ Assign Platform +

+ +
+ +
+ + +
+ +
+

+ All available platforms have been assigned to this admin. +

+
+ +
+ + +
+
+
+{% endblock %} + +{% block extra_scripts %} + +{% endblock %} diff --git a/static/admin/js/admin-user-edit.js b/static/admin/js/admin-user-edit.js new file mode 100644 index 00000000..e4f3872d --- /dev/null +++ b/static/admin/js/admin-user-edit.js @@ -0,0 +1,333 @@ +// static/admin/js/admin-user-edit.js + +// Create custom logger for admin user edit +const adminUserEditLog = window.LogConfig.createLogger('ADMIN-USER-EDIT'); + +function adminUserEditPage() { + return { + // Inherit base layout functionality from init-alpine.js + ...data(), + + // Admin user edit page specific state + currentPage: 'admin-users', + loading: false, + adminUser: null, + platforms: [], + errors: {}, + saving: false, + userId: null, + currentUserId: null, + + // Platform assignment state + showPlatformModal: false, + availablePlatforms: [], + selectedPlatformId: null, + + // Initialize + async init() { + adminUserEditLog.info('=== ADMIN USER EDIT PAGE INITIALIZING ==='); + + // Prevent multiple initializations + if (window._adminUserEditInitialized) { + adminUserEditLog.warn('Admin user edit page already initialized, skipping...'); + return; + } + window._adminUserEditInitialized = true; + + // Get current user ID + this.currentUserId = this.adminProfile?.id || null; + + // Get user ID from URL + const path = window.location.pathname; + const match = path.match(/\/admin\/admin-users\/(\d+)\/edit/); + + if (match) { + this.userId = parseInt(match[1], 10); + adminUserEditLog.info('Editing admin user:', this.userId); + await this.loadAdminUser(); + await this.loadAllPlatforms(); + } else { + adminUserEditLog.error('No user ID in URL'); + Utils.showToast('Invalid admin user URL', 'error'); + setTimeout(() => window.location.href = '/admin/admin-users', 2000); + } + + adminUserEditLog.info('=== ADMIN USER EDIT PAGE INITIALIZATION COMPLETE ==='); + }, + + // Load admin user data + async loadAdminUser() { + adminUserEditLog.info('Loading admin user data...'); + this.loading = true; + + try { + const url = `/admin/admin-users/${this.userId}`; + window.LogConfig.logApiCall('GET', url, null, 'request'); + + const startTime = performance.now(); + const response = await apiClient.get(url); + const duration = performance.now() - startTime; + + window.LogConfig.logApiCall('GET', url, response, 'response'); + window.LogConfig.logPerformance('Load Admin User', duration); + + // Transform API response + this.adminUser = { + ...response, + platforms: (response.platform_assignments || []).map(pa => ({ + id: pa.platform_id, + code: pa.platform_code, + name: pa.platform_name + })), + full_name: [response.first_name, response.last_name].filter(Boolean).join(' ') || null + }; + + adminUserEditLog.info(`Admin user loaded in ${duration}ms`, { + id: this.adminUser.id, + username: this.adminUser.username, + is_super_admin: this.adminUser.is_super_admin + }); + + } catch (error) { + window.LogConfig.logError(error, 'Load Admin User'); + Utils.showToast('Failed to load admin user', 'error'); + setTimeout(() => window.location.href = '/admin/admin-users', 2000); + } finally { + this.loading = false; + } + }, + + // Load all platforms for assignment + async loadAllPlatforms() { + try { + adminUserEditLog.debug('Loading all platforms...'); + const response = await apiClient.get('/admin/platforms'); + this.platforms = response.platforms || response.items || []; + adminUserEditLog.debug(`Loaded ${this.platforms.length} platforms`); + } catch (error) { + adminUserEditLog.error('Failed to load platforms:', error); + this.platforms = []; + } + }, + + // Get available platforms (not yet assigned) + get availablePlatformsForAssignment() { + if (!this.adminUser || this.adminUser.is_super_admin) return []; + const assignedIds = (this.adminUser.platforms || []).map(p => p.id); + return this.platforms.filter(p => !assignedIds.includes(p.id)); + }, + + // Format date + formatDate(dateString) { + if (!dateString) { + return '-'; + } + return Utils.formatDate(dateString); + }, + + // Toggle super admin status + async toggleSuperAdmin() { + const newStatus = !this.adminUser.is_super_admin; + const action = newStatus ? 'promote to' : 'demote from'; + adminUserEditLog.info(`Toggle super admin: ${action}`); + + // Prevent self-demotion + if (this.adminUser.id === this.currentUserId && !newStatus) { + Utils.showToast('You cannot demote yourself from super admin', 'error'); + return; + } + + if (!confirm(`Are you sure you want to ${action} super admin "${this.adminUser.username}"?`)) { + adminUserEditLog.info('Super admin toggle cancelled by user'); + return; + } + + this.saving = true; + try { + const url = `/admin/admin-users/${this.userId}/super-admin`; + window.LogConfig.logApiCall('PUT', url, { is_super_admin: newStatus }, 'request'); + + const response = await apiClient.put(url, { is_super_admin: newStatus }); + + window.LogConfig.logApiCall('PUT', url, response, 'response'); + + this.adminUser.is_super_admin = response.is_super_admin; + // Clear platforms if promoted to super admin + if (response.is_super_admin) { + this.adminUser.platforms = []; + } + + const actionDone = newStatus ? 'promoted to' : 'demoted from'; + Utils.showToast(`Admin ${actionDone} super admin successfully`, 'success'); + adminUserEditLog.info(`Admin ${actionDone} super admin successfully`); + + } catch (error) { + window.LogConfig.logError(error, `Toggle Super Admin (${action})`); + Utils.showToast(error.message || `Failed to ${action} super admin`, 'error'); + } finally { + this.saving = false; + } + }, + + // Toggle admin user status + async toggleStatus() { + const action = this.adminUser.is_active ? 'deactivate' : 'activate'; + adminUserEditLog.info(`Toggle status: ${action}`); + + // Prevent self-deactivation + if (this.adminUser.id === this.currentUserId) { + Utils.showToast('You cannot deactivate your own account', 'error'); + return; + } + + if (!confirm(`Are you sure you want to ${action} "${this.adminUser.username}"?`)) { + adminUserEditLog.info('Status toggle cancelled by user'); + return; + } + + this.saving = true; + try { + const url = `/admin/admin-users/${this.userId}/status`; + window.LogConfig.logApiCall('PUT', url, null, 'request'); + + const response = await apiClient.put(url); + + window.LogConfig.logApiCall('PUT', url, response, 'response'); + + this.adminUser.is_active = response.is_active; + Utils.showToast(`Admin user ${action}d successfully`, 'success'); + adminUserEditLog.info(`Admin user ${action}d successfully`); + + } catch (error) { + window.LogConfig.logError(error, `Toggle Status (${action})`); + Utils.showToast(error.message || `Failed to ${action} admin user`, 'error'); + } finally { + this.saving = false; + } + }, + + // Assign platform to admin + async assignPlatform(platformId) { + if (!platformId) return; + + adminUserEditLog.info('Assigning platform:', platformId); + this.saving = true; + + try { + const url = `/admin/admin-users/${this.userId}/platforms/${platformId}`; + window.LogConfig.logApiCall('POST', url, null, 'request'); + + const response = await apiClient.post(url); + + window.LogConfig.logApiCall('POST', url, response, 'response'); + + // Reload admin user to get updated platforms + await this.loadAdminUser(); + + Utils.showToast('Platform assigned successfully', 'success'); + adminUserEditLog.info('Platform assigned successfully'); + this.showPlatformModal = false; + this.selectedPlatformId = null; + + } catch (error) { + window.LogConfig.logError(error, 'Assign Platform'); + Utils.showToast(error.message || 'Failed to assign platform', 'error'); + } finally { + this.saving = false; + } + }, + + // Remove platform from admin + async removePlatform(platformId) { + const platform = this.adminUser.platforms.find(p => p.id === platformId); + const platformName = platform ? platform.name : platformId; + + // Validate: platform admin must have at least one platform + if (this.adminUser.platforms.length <= 1) { + Utils.showToast('Platform admin must be assigned to at least one platform', 'error'); + return; + } + + if (!confirm(`Are you sure you want to remove "${platformName}" from this admin?`)) { + adminUserEditLog.info('Platform removal cancelled by user'); + return; + } + + adminUserEditLog.info('Removing platform:', platformId); + this.saving = true; + + try { + const url = `/admin/admin-users/${this.userId}/platforms/${platformId}`; + window.LogConfig.logApiCall('DELETE', url, null, 'request'); + + await apiClient.delete(url); + + window.LogConfig.logApiCall('DELETE', url, null, 'response'); + + // Reload admin user to get updated platforms + await this.loadAdminUser(); + + Utils.showToast('Platform removed successfully', 'success'); + adminUserEditLog.info('Platform removed successfully'); + + } catch (error) { + window.LogConfig.logError(error, 'Remove Platform'); + Utils.showToast(error.message || 'Failed to remove platform', 'error'); + } finally { + this.saving = false; + } + }, + + // Open platform assignment modal + openPlatformModal() { + this.showPlatformModal = true; + this.selectedPlatformId = null; + }, + + // Delete admin user + async deleteAdminUser() { + adminUserEditLog.info('Delete admin user requested:', this.userId); + + // Prevent self-deletion + if (this.adminUser.id === this.currentUserId) { + Utils.showToast('You cannot delete your own account', 'error'); + return; + } + + if (!confirm(`Are you sure you want to delete admin user "${this.adminUser.username}"?\n\nThis action cannot be undone.`)) { + adminUserEditLog.info('Delete cancelled by user'); + return; + } + + // Second confirmation for safety + if (!confirm(`FINAL CONFIRMATION\n\nAre you absolutely sure you want to delete "${this.adminUser.username}"?`)) { + adminUserEditLog.info('Delete cancelled by user (second confirmation)'); + return; + } + + this.saving = true; + try { + const url = `/admin/admin-users/${this.userId}`; + window.LogConfig.logApiCall('DELETE', url, null, 'request'); + + await apiClient.delete(url); + + window.LogConfig.logApiCall('DELETE', url, null, 'response'); + + Utils.showToast('Admin user deleted successfully', 'success'); + adminUserEditLog.info('Admin user deleted successfully'); + + // Redirect to admin users list + setTimeout(() => window.location.href = '/admin/admin-users', 1500); + + } catch (error) { + window.LogConfig.logError(error, 'Delete Admin User'); + Utils.showToast(error.message || 'Failed to delete admin user', 'error'); + } finally { + this.saving = false; + } + } + }; +} + +adminUserEditLog.info('Admin user edit module loaded');