Veritas-AI / templates /admin.html
Aditya-Jadhav150
Enhance settings management, admin user deletion notifications, and custom HTML5 login with T&C modal
0411a36
<!DOCTYPE html>
<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>