agent_tracker/templates/admin/dashboard.html
2025-08-17 07:23:53 -05:00

683 lines
No EOL
21 KiB
HTML

{% extends "base.html" %}
{% block title %}Admin Dashboard - AgentHub{% endblock %}
{% block content %}
<div class="container my-5">
<!-- Header -->
<div class="row mb-4">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center">
<div>
<h2><i class="fas fa-users-cog me-3"></i>Admin Dashboard</h2>
<p class="text-muted mb-0">Manage users and AI agents across the system</p>
</div>
<div class="d-flex gap-2">
<button class="btn btn-outline-primary" id="refreshBtn">
<i class="fas fa-sync-alt me-2"></i>Refresh
</button>
<button class="btn btn-outline-danger" onclick="logout()">
<i class="fas fa-sign-out-alt me-2"></i>Logout
</button>
</div>
</div>
</div>
</div>
<!-- Statistics Cards -->
<div class="row mb-4">
<div class="col-lg-3 col-md-6 mb-3">
<div class="stat-card">
<div class="stat-icon">
<i class="fas fa-users"></i>
</div>
<div class="stat-info">
<h3 id="totalUsers">0</h3>
<p>Total Users</p>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6 mb-3">
<div class="stat-card admin-card">
<div class="stat-icon">
<i class="fas fa-user-shield"></i>
</div>
<div class="stat-info">
<h3 id="adminUsers">0</h3>
<p>Admin Users</p>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6 mb-3">
<div class="stat-card agent-card">
<div class="stat-icon">
<i class="fas fa-robot"></i>
</div>
<div class="stat-info">
<h3 id="totalAgents">0</h3>
<p>Total Agents</p>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6 mb-3">
<div class="stat-card active-card">
<div class="stat-icon">
<i class="fas fa-user-check"></i>
</div>
<div class="stat-info">
<h3 id="activeUsers">0</h3>
<p>Active Users</p>
</div>
</div>
</div>
</div>
<!-- Tabs Navigation -->
<div class="row">
<div class="col-12">
<div class="card shadow-sm border-0">
<div class="card-header bg-white">
<ul class="nav nav-tabs card-header-tabs" id="adminTabs">
<li class="nav-item">
<a class="nav-link active" id="users-tab" data-bs-toggle="tab" href="#users">
<i class="fas fa-users me-2"></i>Users Management
</a>
</li>
<li class="nav-item">
<a class="nav-link" id="agents-tab" data-bs-toggle="tab" href="#agents">
<i class="fas fa-robot me-2"></i>Agents Management
</a>
</li>
</ul>
</div>
<div class="card-body">
<div class="tab-content" id="adminTabsContent">
<!-- Users Management Tab -->
<div class="tab-pane fade show active" id="users">
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="mb-0">Agent Management</h5>
<div class="input-group" style="width: 300px;">
<span class="input-group-text"><i class="fas fa-search"></i></span>
<input type="text" class="form-control" id="userSearch" placeholder="Search users...">
</div>
</div>
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>User</th>
<th>Email</th>
<th>Type</th>
<th>Status</th>
<th>Agents</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="usersTableBody">
<tr>
<td colspan="7" class="text-center py-4">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Agents Management Tab -->
<div class="tab-pane fade" id="agents">
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="mb-0">Agent Management</h5>
<div class="d-flex gap-2">
<select class="form-select" id="agentStatusFilter" style="width: auto;">
<option value="">All Statuses</option>
<option value="Active">Active</option>
<option value="Development">Development</option>
<option value="Inactive">Inactive</option>
<option value="Deprecated">Deprecated</option>
</select>
<div class="input-group" style="width: 300px;">
<span class="input-group-text"><i class="fas fa-search"></i></span>
<input type="text" class="form-control" id="agentSearch" placeholder="Search agents...">
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Agent Name</th>
<th>Owner</th>
<th>Status</th>
<th>Version</th>
<th>Department</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="agentsTableBody">
<tr>
<td colspan="7" class="text-center py-4">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Edit User Modal -->
<div class="modal fade" id="editUserModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Edit User</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="editUserForm">
<input type="hidden" id="editUserId">
<div class="mb-3">
<label for="editUserEmail" class="form-label">Email Address</label>
<input type="email" class="form-control" id="editUserEmail" readonly>
</div>
<div class="mb-3">
<label for="editUserFullName" class="form-label">Full Name</label>
<input type="text" class="form-control" id="editUserFullName">
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="editUserIsActive">
<label class="form-check-label" for="editUserIsActive">
Account is active
</label>
</div>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="editUserIsAdmin">
<label class="form-check-label" for="editUserIsAdmin">
User has admin privileges
</label>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" form="editUserForm" class="btn btn-primary">Save Changes</button>
</div>
</div>
</div>
</div>
<style>
.stat-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
padding: 1.5rem;
color: white;
display: flex;
align-items: center;
margin-bottom: 1rem;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}
.admin-card {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.agent-card {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}
.active-card {
background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
}
.stat-icon {
font-size: 2.5rem;
margin-right: 1rem;
opacity: 0.8;
}
.stat-info h3 {
margin: 0;
font-size: 2rem;
font-weight: 700;
}
.stat-info p {
margin: 0;
opacity: 0.9;
font-size: 0.9rem;
}
.user-avatar-sm {
width: 32px;
height: 32px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.8rem;
font-weight: 600;
}
.table th {
border-top: none;
font-weight: 600;
color: #2d3748;
background-color: #f7fafc;
}
.table-hover tbody tr:hover {
background-color: rgba(102, 126, 234, 0.05);
}
.card {
border-radius: 16px;
border: none;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}
.card-header {
background: rgba(102, 126, 234, 0.05);
border-bottom: 1px solid #e2e8f0;
border-radius: 16px 16px 0 0 !important;
}
.nav-tabs .nav-link {
border: none;
color: #64748b;
padding: 12px 20px;
}
.nav-tabs .nav-link.active {
background-color: transparent;
border-bottom: 2px solid #667eea;
color: #667eea;
}
.status-Active { background-color: #d4edda; color: #155724; }
.status-Development { background-color: #fff3cd; color: #856404; }
.status-Inactive { background-color: #f8d7da; color: #721c24; }
.status-Deprecated { background-color: #e2e3e5; color: #41464b; }
@media (max-width: 768px) {
.stat-card {
text-align: center;
flex-direction: column;
}
.stat-icon {
margin-right: 0;
margin-bottom: 0.5rem;
}
.table-responsive {
font-size: 0.875rem;
}
}
</style>
<script>
let allUsers = [];
let allAgents = [];
document.addEventListener('DOMContentLoaded', function() {
loadAdminData();
setupEventListeners();
});
function setupEventListeners() {
document.getElementById('refreshBtn').addEventListener('click', loadAdminData);
document.getElementById('userSearch').addEventListener('input', filterUsers);
document.getElementById('agentSearch').addEventListener('input', filterAgents);
document.getElementById('agentStatusFilter').addEventListener('change', filterAgents);
document.getElementById('editUserForm').addEventListener('submit', handleEditUserSubmit);
}
async function loadAdminData() {
try {
// Load users and agents in parallel
const [usersResponse, agentsResponse] = await Promise.all([
fetch('{{ base_path }}/api/admin/users', {
credentials: 'include'
}),
fetch('{{ base_path }}/api/admin/agents', {
credentials: 'include'
})
]);
if (usersResponse.status === 401 || agentsResponse.status === 401) {
window.location.href = '{{ base_path }}/login';
return;
}
if (usersResponse.status === 403 || agentsResponse.status === 403) {
alert('Admin access required');
window.location.href = '{{ base_path }}/dashboard';
return;
}
if (usersResponse.ok && agentsResponse.ok) {
allUsers = await usersResponse.json();
allAgents = await agentsResponse.json();
updateStatistics();
displayUsers(allUsers);
displayAgents(allAgents);
} else {
throw new Error('Failed to load admin data');
}
} catch (error) {
console.error('Error loading admin data:', error);
showError('Failed to load admin data');
}
}
function updateStatistics() {
document.getElementById('totalUsers').textContent = allUsers.length;
document.getElementById('adminUsers').textContent = allUsers.filter(u => u.is_admin).length;
document.getElementById('totalAgents').textContent = allAgents.length;
document.getElementById('activeUsers').textContent = allUsers.filter(u => u.is_active).length;
}
function displayUsers(users) {
const tbody = document.getElementById('usersTableBody');
if (users.length === 0) {
tbody.innerHTML = '<tr><td colspan="7" class="text-center py-4">No users found</td></tr>';
return;
}
const usersHtml = users.map(user => {
const userAgents = allAgents.filter(agent => agent.created_by === user.email).length;
return `
<tr>
<td>
<div class="d-flex align-items-center">
<div class="user-avatar-sm me-2">
${(user.full_name || user.email)[0].toUpperCase()}
</div>
<div>
<div class="fw-medium">${user.full_name || 'No Name'}</div>
<small class="text-muted">ID: ${user.email}</small>
</div>
</div>
</td>
<td>${user.email}</td>
<td>
<span class="badge ${user.is_admin ? 'bg-danger' : 'bg-primary'}">
${user.is_admin ? 'Admin' : 'User'}
</span>
</td>
<td>
<span class="badge ${user.is_active ? 'bg-success' : 'bg-secondary'}">
${user.is_active ? 'Active' : 'Inactive'}
</span>
</td>
<td>
<span class="badge bg-info">${userAgents}</span>
</td>
<td>
<small class="text-muted">Recently</small>
</td>
<td>
<button class="btn btn-outline-primary btn-sm me-1" onclick="viewUserDetails('${user.email}')">
<i class="fas fa-eye"></i>
</button>
<button class="btn btn-outline-warning btn-sm me-1" onclick="editUser('${user.email}')">
<i class="fas fa-edit"></i>
</button>
<button class="btn btn-outline-secondary btn-sm me-1" onclick="toggleUserStatus('${user.email}')">
<i class="fas fa-${user.is_active ? 'ban' : 'check'}"></i>
</button>
</td>
</tr>
`;
}).join('');
tbody.innerHTML = usersHtml;
}
function displayAgents(agents) {
const tbody = document.getElementById('agentsTableBody');
if (agents.length === 0) {
tbody.innerHTML = '<tr><td colspan="7" class="text-center py-4">No agents found</td></tr>';
return;
}
const agentsHtml = agents.map(agent => {
const owner = allUsers.find(u => u.email === agent.created_by);
return `
<tr>
<td>
<div class="fw-medium">${agent.agent_name}</div>
<small class="text-muted">${agent.agent_description || 'No description'}</small>
</td>
<td>
<div class="d-flex align-items-center">
<div class="user-avatar-sm me-2">
${(owner?.full_name || owner?.email || 'U')[0].toUpperCase()}
</div>
<div>
<div class="fw-medium">${owner?.full_name || 'Unknown'}</div>
<small class="text-muted">${owner?.email || agent.created_by}</small>
</div>
</div>
</td>
<td>
<span class="badge status-${agent.agent_status || 'Development'}">
${agent.agent_status || 'Development'}
</span>
</td>
<td>${agent.agent_version || 'N/A'}</td>
<td>${agent.agent_department || 'N/A'}</td>
<td>
<small class="text-muted">${formatDate(agent.agent_created_at)}</small>
</td>
<td>
<button class="btn btn-outline-primary btn-sm me-1" onclick="viewAgentDetails('${agent.agent_id}')">
<i class="fas fa-eye"></i>
</button>
<button class="btn btn-outline-danger btn-sm" onclick="deleteAgentAdmin('${agent.agent_id}')">
<i class="fas fa-trash"></i>
</button>
</td>
</tr>
`;
}).join('');
tbody.innerHTML = agentsHtml;
}
function filterUsers() {
const searchTerm = document.getElementById('userSearch').value.toLowerCase();
const filtered = allUsers.filter(user =>
(user.full_name || '').toLowerCase().includes(searchTerm) ||
user.email.toLowerCase().includes(searchTerm)
);
displayUsers(filtered);
}
function filterAgents() {
const searchTerm = document.getElementById('agentSearch').value.toLowerCase();
const statusFilter = document.getElementById('agentStatusFilter').value;
let filtered = allAgents.filter(agent => {
const matchesSearch = agent.agent_name.toLowerCase().includes(searchTerm) ||
(agent.agent_description || '').toLowerCase().includes(searchTerm);
const matchesStatus = !statusFilter || agent.agent_status === statusFilter;
return matchesSearch && matchesStatus;
});
displayAgents(filtered);
}
function viewUserDetails(email) {
const user = allUsers.find(u => u.email === email);
const userAgents = allAgents.filter(agent => agent.created_by === email);
alert(`User Details:\nName: ${user.full_name || 'No Name'}\nEmail: ${user.email}\nType: ${user.is_admin ? 'Admin' : 'User'}\nStatus: ${user.is_active ? 'Active' : 'Inactive'}\nAgents Created: ${userAgents.length}`);
}
function viewAgentDetails(agentId) {
const agent = allAgents.find(a => a.agent_id === agentId);
if (!agent) return;
alert(`Agent Details:\nName: ${agent.agent_name}\nStatus: ${agent.agent_status || 'Development'}\nVersion: ${agent.agent_version || 'N/A'}\nDescription: ${agent.agent_description || 'No description'}\nOwner: ${agent.created_by}`);
}
function editUser(email) {
const user = allUsers.find(u => u.email === email);
if (!user) return;
// Populate the edit form
document.getElementById('editUserId').value = user.email;
document.getElementById('editUserEmail').value = user.email;
document.getElementById('editUserFullName').value = user.full_name || '';
document.getElementById('editUserIsActive').checked = user.is_active;
document.getElementById('editUserIsAdmin').checked = user.is_admin;
// Show the modal
const modal = new bootstrap.Modal(document.getElementById('editUserModal'));
modal.show();
}
async function toggleUserStatus(email) {
const user = allUsers.find(u => u.email === email);
if (!user) return;
const newStatus = !user.is_active;
const action = newStatus ? 'activate' : 'deactivate';
if (!confirm(`Are you sure you want to ${action} this user?`)) {
return;
}
try {
const response = await fetch(`{{ base_path }}/api/admin/users/${encodeURIComponent(email)}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify({
full_name: user.full_name,
is_active: newStatus,
is_admin: user.is_admin
})
});
if (response.ok) {
await loadAdminData();
showSuccess(`User ${action}d successfully`);
} else {
const error = await response.json();
showError(error.detail || `Failed to ${action} user`);
}
} catch (error) {
showError(`Failed to ${action} user`);
}
}
async function deleteAgentAdmin(agentId) {
if (!confirm('Are you sure you want to delete this agent? This action cannot be undone.')) {
return;
}
try {
const response = await fetch(`{{ base_path }}/api/agents/${agentId}`, {
method: 'DELETE',
credentials: 'include'
});
if (response.ok) {
await loadAdminData();
showSuccess('Agent deleted successfully');
} else {
const error = await response.json();
showError(error.detail || 'Failed to delete agent');
}
} catch (error) {
showError('Failed to delete agent');
}
}
function formatDate(dateString) {
if (!dateString) return 'N/A';
const date = new Date(dateString);
return date.toLocaleDateString();
}
function showSuccess(message) {
alert(message);
}
function showError(message) {
alert('Error: ' + message);
}
async function handleEditUserSubmit(e) {
e.preventDefault();
const email = document.getElementById('editUserId').value;
const fullName = document.getElementById('editUserFullName').value;
const isActive = document.getElementById('editUserIsActive').checked;
const isAdmin = document.getElementById('editUserIsAdmin').checked;
try {
const response = await fetch(`{{ base_path }}/api/admin/users/${encodeURIComponent(email)}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify({
full_name: fullName,
is_active: isActive,
is_admin: isAdmin
})
});
if (response.ok) {
// Hide the modal
const modal = bootstrap.Modal.getInstance(document.getElementById('editUserModal'));
modal.hide();
// Reload data and show success
await loadAdminData();
showSuccess('User updated successfully');
} else {
const error = await response.json();
showError(error.detail || 'Failed to update user');
}
} catch (error) {
showError('Failed to update user');
}
}
function logout() {
localStorage.removeItem('access_token');
window.location.href = '{{ base_path }}/';
}
</script>
{% endblock %}