agent_tracker/templates/user_management.html
nickviljoen 08038b066f Rebrand UI to OLIVER template: Montserrat, yellow accents, sticky nav, tab fixes
Swap the muddy #f3ae3e palette for the real OLIVER brand pulled from the master
PPT template: yellow #FFCB05 + near-black #1A1A1A + off-white #F6F7F7, Montserrat
font. White-first page with a brand-yellow highlight rectangle behind page titles,
stat tiles with yellow left-strip, and a short yellow accent line under each
card section title — picks up the template's "01" chapter-marker rhythm.

Fixes two production bugs along the way:
- Nav stays pinned at top while page scrolls. The conflicting
  `.navbar { position: relative !important }` rule was removed from nav.html
  so the `position: fixed` from style.css can take effect.
- Clicking admin tabs no longer scrolls the page. Converted
  `<a href="#users">` to `<button data-bs-target="#users">` (Bootstrap 5's
  recommended pattern), so the anchor jump can't happen.

Other refinements: table padding loosened, `transform: scale` row hover
removed (jittery on dense rows), modal headers switched to near-black,
Chart.js palette aligned with brand tokens.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 10:12:18 +02:00

318 lines
No EOL
10 KiB
HTML

{% extends "base.html" %}
{% block title %}Agent Management - AgentHub{% endblock %}
{% block content %}
<div class="container my-5">
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2 class="page-title">Agent Management</h2>
<p class="page-subtitle">Manage your account settings and profile</p>
</div>
<div>
<a href="{{ base_path }}/dashboard" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left me-2"></i>Back to Dashboard
</a>
</div>
</div>
</div>
</div>
<div class="row">
<!-- Profile Information -->
<div class="col-lg-8 mb-4">
<div class="card shadow-sm border-0">
<div class="card-header bg-white">
<h5 class="mb-0"><i class="fas fa-user me-2"></i>Profile Information</h5>
</div>
<div class="card-body">
<form id="profileForm">
<div class="row">
<div class="col-md-6 mb-3">
<label for="email" class="form-label">Email Address</label>
<input type="email" class="form-control" id="email" readonly>
</div>
<div class="col-md-6 mb-3">
<label for="fullName" class="form-label">Full Name</label>
<input type="text" class="form-control" id="fullName">
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Account Status</label>
<div class="form-control-plaintext">
<span class="badge bg-success" id="accountStatus">Active</span>
</div>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Account Type</label>
<div class="form-control-plaintext">
<span class="badge bg-primary" id="accountType">User</span>
</div>
</div>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">
<i class="fas fa-save me-2"></i>Update Profile
</button>
<button type="button" class="btn btn-outline-warning" id="changePasswordBtn">
<i class="fas fa-key me-2"></i>Change Password
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Quick Stats -->
<div class="col-lg-4 mb-4">
<div class="card shadow-sm border-0">
<div class="card-header bg-white">
<h5 class="mb-0"><i class="fas fa-chart-bar me-2"></i>Account Overview</h5>
</div>
<div class="card-body">
<div class="mb-3">
<div class="d-flex justify-content-between align-items-center">
<span>AI Agents Created</span>
<span class="badge bg-info" id="agentCount">0</span>
</div>
</div>
<div class="mb-3">
<div class="d-flex justify-content-between align-items-center">
<span>Account Created</span>
<span class="text-muted" id="accountCreated">-</span>
</div>
</div>
<div class="mb-3">
<div class="d-flex justify-content-between align-items-center">
<span>Last Login</span>
<span class="text-muted" id="lastLogin">Now</span>
</div>
</div>
<hr>
<div class="d-grid gap-2">
<a href="{{ base_path }}/agent-register" class="btn btn-success btn-sm">
<i class="fas fa-plus me-2"></i>Create New Agent
</a>
<a href="{{ base_path }}/agent-management" class="btn btn-outline-primary btn-sm">
<i class="fas fa-robot me-2"></i>Manage Agents
</a>
</div>
</div>
</div>
</div>
</div>
<!-- Recent Activity -->
<div class="row">
<div class="col-12">
<div class="card shadow-sm border-0">
<div class="card-header bg-white">
<h5 class="mb-0"><i class="fas fa-history me-2"></i>Recent Activity</h5>
</div>
<div class="card-body">
<div id="activityList">
<div class="text-center py-4">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Change Password Modal -->
<div class="modal fade" id="passwordModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Change Password</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="passwordForm">
<div class="mb-3">
<label for="currentPassword" class="form-label">Current Password</label>
<input type="password" class="form-control" id="currentPassword" required>
</div>
<div class="mb-3">
<label for="newPassword" class="form-label">New Password</label>
<input type="password" class="form-control" id="newPassword" required minlength="8">
</div>
<div class="mb-3">
<label for="confirmNewPassword" class="form-label">Confirm New Password</label>
<input type="password" class="form-control" id="confirmNewPassword" required>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" form="passwordForm" class="btn btn-warning">Change Password</button>
</div>
</div>
</div>
</div>
<style>
.card {
border-radius: 15px;
}
.card-header {
border-bottom: 1px solid #e9ecef;
border-radius: 15px 15px 0 0 !important;
}
.badge {
font-size: 0.75em;
}
.activity-item {
padding: 12px 0;
border-bottom: 1px solid #f8f9fa;
}
.activity-item:last-child {
border-bottom: none;
}
.activity-icon {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.875rem;
}
</style>
<script>
let currentUser = null;
// Load user data on page load
document.addEventListener('DOMContentLoaded', async function() {
await loadUserProfile();
await loadUserAgents();
loadRecentActivity();
});
async function loadUserProfile() {
try {
const response = await fetch('/me', {
headers: {
'Authorization': 'Bearer ' + localStorage.getItem('access_token')
}
});
if (response.ok) {
currentUser = await response.json();
document.getElementById('email').value = currentUser.email;
document.getElementById('fullName').value = currentUser.full_name || '';
document.getElementById('accountStatus').textContent = currentUser.is_active ? 'Active' : 'Inactive';
document.getElementById('accountType').textContent = currentUser.is_admin ? 'Admin' : 'User';
if (currentUser.is_admin) {
document.getElementById('accountType').className = 'badge bg-danger';
}
} else if (response.status === 401) {
window.location.href = '/login';
}
} catch (error) {
console.error('Error loading profile:', error);
}
}
async function loadUserAgents() {
try {
const response = await fetch('/api/agents', {
headers: {
'Authorization': 'Bearer ' + localStorage.getItem('access_token')
}
});
if (response.ok) {
const agents = await response.json();
document.getElementById('agentCount').textContent = agents.length;
}
} catch (error) {
console.error('Error loading agents:', error);
document.getElementById('agentCount').textContent = '0';
}
}
function loadRecentActivity() {
// Simulate recent activity
const activities = [
{ icon: 'fas fa-robot', color: 'bg-success', text: 'Created new AI agent', time: '2 hours ago' },
{ icon: 'fas fa-edit', color: 'bg-info', text: 'Updated profile information', time: '1 day ago' },
{ icon: 'fas fa-sign-in-alt', color: 'bg-primary', text: 'Logged into system', time: '2 days ago' }
];
const activityHtml = activities.map(activity => `
<div class="activity-item d-flex align-items-center">
<div class="activity-icon ${activity.color} text-white me-3">
<i class="${activity.icon}"></i>
</div>
<div class="flex-grow-1">
<div class="fw-medium">${activity.text}</div>
<div class="text-muted small">${activity.time}</div>
</div>
</div>
`).join('');
document.getElementById('activityList').innerHTML = activityHtml;
}
// Handle profile form submission
document.getElementById('profileForm').addEventListener('submit', async function(e) {
e.preventDefault();
const fullName = document.getElementById('fullName').value;
try {
// Note: This would need to be implemented in the backend
alert('Profile update feature coming soon!');
} catch (error) {
alert('Error updating profile');
}
});
// Show change password modal
document.getElementById('changePasswordBtn').addEventListener('click', function() {
const modal = new bootstrap.Modal(document.getElementById('passwordModal'));
modal.show();
});
// Handle password change form
document.getElementById('passwordForm').addEventListener('submit', async function(e) {
e.preventDefault();
const newPassword = document.getElementById('newPassword').value;
const confirmPassword = document.getElementById('confirmNewPassword').value;
if (newPassword !== confirmPassword) {
alert('New passwords do not match');
return;
}
try {
// Note: This would need to be implemented in the backend
alert('Password change feature coming soon!');
const modal = bootstrap.Modal.getInstance(document.getElementById('passwordModal'));
modal.hide();
} catch (error) {
alert('Error changing password');
}
});
</script>
{% endblock %}