Spaces:
Running
Running
Aditya-Jadhav150
Enhance settings management, admin user deletion notifications, and custom HTML5 login with T&C modal
0411a36 | <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Aegis-AI | Security Override</title> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet"> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| :root { | |
| --bg-dark: #050505; | |
| --bg-panel: #111111; | |
| --border-glow: #333333; | |
| --text-main: #ffffff; | |
| --text-muted: #888888; | |
| --accent: #00ffaa; | |
| --danger: #ff3366; | |
| --warning: #ffcc00; | |
| } | |
| body { | |
| margin: 0; | |
| padding: 0; | |
| background-color: var(--bg-dark); | |
| color: var(--text-main); | |
| font-family: 'Inter', sans-serif; | |
| background-image: | |
| radial-gradient(circle at 15% 50%, rgba(255, 255, 255, 0.03), transparent 25%), | |
| radial-gradient(circle at 85% 30%, rgba(255, 255, 255, 0.02), transparent 25%); | |
| min-height: 100vh; | |
| } | |
| /* Nav Header */ | |
| .admin-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 20px 40px; | |
| border-bottom: 1px solid rgba(255,255,255,0.05); | |
| background: rgba(0,0,0,0.5); | |
| backdrop-filter: blur(10px); | |
| position: sticky; | |
| top: 0; | |
| z-index: 100; | |
| } | |
| .brand { | |
| font-size: 1.2rem; | |
| font-weight: 800; | |
| letter-spacing: 2px; | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| color: #fff; | |
| } | |
| .brand i { | |
| color: var(--danger); | |
| animation: pulse-red 2s infinite; | |
| } | |
| .admin-badge { | |
| background: rgba(255, 51, 102, 0.1); | |
| color: var(--danger); | |
| padding: 4px 12px; | |
| font-size: 0.7rem; | |
| border-radius: 4px; | |
| border: 1px solid rgba(255, 51, 102, 0.3); | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| font-weight: 700; | |
| } | |
| .back-btn { | |
| color: var(--text-muted); | |
| text-decoration: none; | |
| font-size: 0.9rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| transition: color 0.3s ease; | |
| } | |
| .back-btn:hover { | |
| color: #fff; | |
| } | |
| /* Container */ | |
| .container { | |
| max-width: 1200px; | |
| margin: 40px auto; | |
| padding: 0 20px; | |
| } | |
| .page-title { | |
| font-size: 2rem; | |
| font-weight: 700; | |
| margin-bottom: 8px; | |
| letter-spacing: -0.5px; | |
| } | |
| .page-subtitle { | |
| color: var(--text-muted); | |
| font-size: 0.95rem; | |
| margin-bottom: 40px; | |
| } | |
| /* Data Panel */ | |
| .data-panel { | |
| background: var(--bg-panel); | |
| border: 1px solid var(--border-glow); | |
| border-radius: 12px; | |
| overflow: hidden; | |
| box-shadow: 0 20px 40px rgba(0,0,0,0.5); | |
| } | |
| .panel-header { | |
| padding: 20px 30px; | |
| border-bottom: 1px solid rgba(255,255,255,0.05); | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| background: linear-gradient(180deg, rgba(255,255,255,0.03) 0%, transparent 100%); | |
| } | |
| .panel-title { | |
| font-weight: 600; | |
| font-size: 1.1rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .refresh-btn { | |
| background: transparent; | |
| border: 1px solid var(--border-glow); | |
| color: var(--text-muted); | |
| padding: 8px 16px; | |
| border-radius: 6px; | |
| cursor: pointer; | |
| font-family: inherit; | |
| font-size: 0.85rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| transition: all 0.2s; | |
| } | |
| .refresh-btn:hover { | |
| border-color: #555; | |
| color: #fff; | |
| } | |
| /* Table Styles */ | |
| table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| text-align: left; | |
| } | |
| th { | |
| padding: 16px 30px; | |
| font-size: 0.75rem; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| color: var(--text-muted); | |
| border-bottom: 1px solid rgba(255,255,255,0.05); | |
| font-weight: 600; | |
| } | |
| td { | |
| padding: 18px 30px; | |
| font-size: 0.9rem; | |
| border-bottom: 1px solid rgba(255,255,255,0.02); | |
| color: #ddd; | |
| } | |
| tr:last-child td { | |
| border-bottom: none; | |
| } | |
| tr:hover td { | |
| background: rgba(255,255,255,0.02); | |
| } | |
| .user-id { | |
| color: var(--text-muted); | |
| font-family: monospace; | |
| } | |
| .auth-google { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 6px; | |
| background: rgba(255, 255, 255, 0.1); | |
| padding: 4px 10px; | |
| border-radius: 20px; | |
| font-size: 0.75rem; | |
| font-weight: 600; | |
| } | |
| .auth-google i { | |
| color: #fff; | |
| } | |
| .auth-password { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 6px; | |
| background: rgba(255, 255, 255, 0.05); | |
| padding: 4px 10px; | |
| border-radius: 20px; | |
| font-size: 0.75rem; | |
| font-weight: 600; | |
| color: var(--text-muted); | |
| } | |
| .status-locked { | |
| color: var(--warning); | |
| font-size: 0.8rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| } | |
| .status-ok { | |
| color: var(--accent); | |
| font-size: 0.8rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| } | |
| .empty-state { | |
| padding: 60px; | |
| text-align: center; | |
| color: var(--text-muted); | |
| } | |
| .empty-state i { | |
| font-size: 3rem; | |
| margin-bottom: 16px; | |
| opacity: 0.2; | |
| } | |
| @keyframes pulse-red { | |
| 0% { opacity: 0.5; text-shadow: 0 0 0 rgba(255,51,102,0); } | |
| 50% { opacity: 1; text-shadow: 0 0 15px rgba(255,51,102,0.8); } | |
| 100% { opacity: 0.5; text-shadow: 0 0 0 rgba(255,51,102,0); } | |
| } | |
| @keyframes spin { | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .spin { | |
| animation: spin 1s linear infinite; | |
| } | |
| .table-container { | |
| width: 100%; | |
| overflow-x: auto; | |
| -webkit-overflow-scrolling: touch; | |
| } | |
| .btn-danger { | |
| background: rgba(255, 51, 102, 0.1); | |
| color: var(--danger); | |
| border: 1px solid rgba(255, 51, 102, 0.3); | |
| padding: 6px 12px; | |
| border-radius: 6px; | |
| cursor: pointer; | |
| font-family: inherit; | |
| font-size: 0.8rem; | |
| font-weight: 600; | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 6px; | |
| transition: all 0.2s ease; | |
| } | |
| .btn-danger:hover { | |
| background: var(--danger); | |
| color: #fff; | |
| transform: translateY(-1px); | |
| box-shadow: 0 4px 10px rgba(255, 51, 102, 0.3); | |
| } | |
| @media (max-width: 768px) { | |
| .admin-header { | |
| padding: 12px 16px; | |
| } | |
| .brand { | |
| font-size: 1rem; | |
| gap: 8px; | |
| } | |
| .admin-badge { | |
| display: none; | |
| } | |
| .container { | |
| margin: 20px auto; | |
| padding: 0 12px; | |
| } | |
| .page-title { | |
| font-size: 1.5rem; | |
| } | |
| .page-subtitle { | |
| font-size: 0.85rem; | |
| margin-bottom: 20px; | |
| } | |
| .panel-header { | |
| padding: 15px 20px; | |
| } | |
| th, td { | |
| padding: 12px 15px; | |
| font-size: 0.8rem; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header class="admin-header"> | |
| <div class="brand"> | |
| <i class="fa-solid fa-shield-halved"></i> | |
| AEGIS-AI GLOBAL CONTROL | |
| <span class="admin-badge">Secure Override</span> | |
| </div> | |
| <a href="/" class="back-btn"> | |
| <i class="fa-solid fa-arrow-left"></i> Return to Main Sector | |
| </a> | |
| </header> | |
| <div class="container"> | |
| <div class="page-title">User Intelligence</div> | |
| <div class="page-subtitle">Real-time telemetry of all registered operators inside the Aegis-AI network.</div> | |
| <div class="data-panel"> | |
| <div class="panel-header"> | |
| <div class="panel-title"> | |
| <i class="fa-solid fa-users"></i> | |
| Active Operators | |
| </div> | |
| <button class="refresh-btn" onclick="fetchUsers()"> | |
| <i class="fa-solid fa-rotate-right" id="refresh-icon"></i> Refresh Data | |
| </button> | |
| </div> | |
| <div class="table-container"> | |
| <table id="users-table"> | |
| <thead> | |
| <tr> | |
| <th>Node ID</th> | |
| <th>Username</th> | |
| <th>Email Vector</th> | |
| <th>Auth Signature</th> | |
| <th>Identity Lock Status</th> | |
| <th>AI Data Opt-In</th> | |
| <th style="text-align: center;">Actions</th> | |
| </tr> | |
| </thead> | |
| <tbody id="users-tbody"> | |
| <tr> | |
| <td colspan="7"> | |
| <div class="empty-state"> | |
| <i class="fa-solid fa-circle-notch spin"></i> | |
| <div>Decrypting Database...</div> | |
| </div> | |
| </td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| const adminUserId = {{ user.id }}; | |
| async function fetchUsers() { | |
| const tbody = document.getElementById('users-tbody'); | |
| const refreshIcon = document.getElementById('refresh-icon'); | |
| refreshIcon.classList.add('spin'); | |
| try { | |
| const response = await fetch('/api/admin/users'); | |
| const data = await response.json(); | |
| if (data.success && data.users) { | |
| renderUsers(data.users); | |
| } else { | |
| showError(data.message || 'Failed to authorize fetch request.'); | |
| } | |
| } catch (error) { | |
| showError('Network disruption detected.'); | |
| } finally { | |
| setTimeout(() => { | |
| refreshIcon.classList.remove('spin'); | |
| }, 500); | |
| } | |
| } | |
| function renderUsers(users) { | |
| const tbody = document.getElementById('users-tbody'); | |
| if (users.length === 0) { | |
| tbody.innerHTML = ` | |
| <tr> | |
| <td colspan="7"> | |
| <div class="empty-state"> | |
| <i class="fa-solid fa-ghost"></i> | |
| <div>No operators found in sector.</div> | |
| </div> | |
| </td> | |
| </tr>`; | |
| return; | |
| } | |
| tbody.innerHTML = ''; | |
| users.forEach(user => { | |
| const tr = document.createElement('tr'); | |
| // Auth Type Badge | |
| const authBadge = user.auth_type === 'Google' | |
| ? `<div class="auth-google"><i class="fa-brands fa-google"></i> Google</div>` | |
| : `<div class="auth-password"><i class="fa-solid fa-key"></i> Key</div>`; | |
| // Name Change Status | |
| let lockStatusHtml = ''; | |
| if (user.last_username_change) { | |
| const lastChange = new Date(user.last_username_change); | |
| const now = new Date(); | |
| const diffTime = Math.abs(now - lastChange); | |
| const diffDays = diffTime / (1000 * 60 * 60 * 24); | |
| if (diffDays < 7) { | |
| const daysLeft = Math.ceil(7 - diffDays); | |
| lockStatusHtml = `<div class="status-locked"><i class="fa-solid fa-lock"></i> Locked (${daysLeft}d left)</div>`; | |
| } else { | |
| lockStatusHtml = `<div class="status-ok"><i class="fa-solid fa-unlock"></i> Unlocked</div>`; | |
| } | |
| } else { | |
| lockStatusHtml = `<div class="status-ok"><i class="fa-solid fa-unlock"></i> Unlocked</div>`; | |
| } | |
| // AI Opt-In Status | |
| const optInHtml = user.ai_data_optin | |
| ? `<div style="color: var(--accent); font-weight: 600;"><i class="fa-solid fa-check"></i> Enabled</div>` | |
| : `<div style="color: var(--text-muted);"><i class="fa-solid fa-xmark"></i> Disabled</div>`; | |
| // Actions Button | |
| let actionsHtml = ''; | |
| if (user.id !== adminUserId) { | |
| actionsHtml = `<td style="text-align: center;"><button class="btn-danger" onclick="confirmDeleteUser(${user.id}, '${user.username}')"><i class="fa-solid fa-trash"></i> Delete</button></td>`; | |
| } else { | |
| actionsHtml = `<td style="text-align: center; color: var(--text-muted); font-size: 0.8rem;">(Active Admin)</td>`; | |
| } | |
| tr.innerHTML = ` | |
| <td class="user-id">#${String(user.id).padStart(4, '0')}</td> | |
| <td style="font-weight: 500;">${user.username}</td> | |
| <td style="color: #aaa;">${user.email || '<span style="opacity: 0.3;">Redacted</span>'}</td> | |
| <td>${authBadge}</td> | |
| <td>${lockStatusHtml}</td> | |
| <td>${optInHtml}</td> | |
| ${actionsHtml} | |
| `; | |
| tbody.appendChild(tr); | |
| }); | |
| } | |
| async function confirmDeleteUser(userId, username) { | |
| const reason = prompt(`Enter deletion reason for operator '${username}':`); | |
| if (reason === null) return; // User cancelled prompt | |
| const trimmedReason = reason.trim(); | |
| if (!trimmedReason) { | |
| alert("Deletion blocked. A reason is required to notify the operator."); | |
| return; | |
| } | |
| if (!confirm(`Are you absolutely sure you want to permanently delete operator account '${username}'?`)) { | |
| return; | |
| } | |
| try { | |
| const response = await fetch('/api/admin/delete_user', { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify({ user_id: userId, reason: trimmedReason }) | |
| }); | |
| const data = await response.json(); | |
| if (data.success) { | |
| alert(data.message); | |
| fetchUsers(); // Refresh database view | |
| } else { | |
| alert("Deletion Error: " + data.message); | |
| } | |
| } catch (error) { | |
| alert("Network error. Failed to delete operator."); | |
| } | |
| } | |
| function showError(msg) { | |
| const tbody = document.getElementById('users-tbody'); | |
| tbody.innerHTML = ` | |
| <tr> | |
| <td colspan="7"> | |
| <div class="empty-state"> | |
| <i class="fa-solid fa-triangle-exclamation" style="color: var(--danger); opacity: 1;"></i> | |
| <div style="color: var(--danger);">${msg}</div> | |
| </div> | |
| </td> | |
| </tr>`; | |
| } | |
| // INIT | |
| document.addEventListener('DOMContentLoaded', fetchUsers); | |
| </script> | |
| </body> | |
| </html> | |