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>
318 lines
No EOL
10 KiB
HTML
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 %} |