345 lines
No EOL
10 KiB
HTML
345 lines
No EOL
10 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}My Profile - 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><i class="fas fa-user me-3"></i>My Profile</h2>
|
|
<p class="text-muted mb-0">Manage your account information and activity</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Profile Information -->
|
|
<div class="row mb-4">
|
|
<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-user-circle me-2"></i>Profile Information</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-2 text-center mb-3">
|
|
<div class="profile-avatar">
|
|
{{ current_user.full_name[0] if current_user.full_name else current_user.email[0] }}
|
|
</div>
|
|
</div>
|
|
<div class="col-md-10">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="info-item">
|
|
<span class="info-label">Full Name</span>
|
|
<span class="info-value">{{ current_user.full_name or 'Not set' }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="info-item">
|
|
<span class="info-label">Email Address</span>
|
|
<span class="info-value">{{ current_user.email }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="info-item">
|
|
<span class="info-label">Account Type</span>
|
|
<span class="info-value">
|
|
{% if current_user.is_admin %}
|
|
<span class="badge bg-danger">Administrator</span>
|
|
{% else %}
|
|
<span class="badge bg-primary">User</span>
|
|
{% endif %}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="info-item">
|
|
<span class="info-label">Member Since</span>
|
|
<span class="info-value">{{ current_user.created_at.strftime('%B %Y') if current_user.created_at else 'Unknown' }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Account Overview -->
|
|
<div class="row mb-4">
|
|
<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-chart-bar me-2"></i>Account Overview</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-3 text-center">
|
|
<div class="stat-card">
|
|
<div class="stat-number" id="totalAgents">-</div>
|
|
<div class="stat-label">Total Agents</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3 text-center">
|
|
<div class="stat-card">
|
|
<div class="stat-number" id="activeAgents">-</div>
|
|
<div class="stat-label">Active Agents</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3 text-center">
|
|
<div class="stat-card">
|
|
<div class="stat-number" id="developmentAgents">-</div>
|
|
<div class="stat-label">In Development</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3 text-center">
|
|
<div class="stat-card">
|
|
<div class="stat-number" id="lastLoginDate">-</div>
|
|
<div class="stat-label">Last Activity</div>
|
|
</div>
|
|
</div>
|
|
</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">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0"><i class="fas fa-clock me-2"></i>Recent Activity</h5>
|
|
<a href="{{ base_path }}/agent-management?view=my" class="btn btn-outline-primary btn-sm">
|
|
<i class="fas fa-robot me-1"></i>View All My Agents
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="recentActivity">
|
|
<div class="text-center py-3">
|
|
<div class="spinner-border text-primary" role="status">
|
|
<span class="visually-hidden">Loading...</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.profile-avatar {
|
|
width: 80px;
|
|
height: 80px;
|
|
border-radius: 50%;
|
|
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: white;
|
|
font-size: 2rem;
|
|
font-weight: bold;
|
|
margin: 0 auto;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.info-item {
|
|
padding: 0.75rem 0;
|
|
}
|
|
|
|
.info-label {
|
|
display: block;
|
|
font-weight: 500;
|
|
color: var(--text-light);
|
|
font-size: 0.9rem;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.info-value {
|
|
display: block;
|
|
color: var(--text-dark);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.stat-card {
|
|
padding: 1rem;
|
|
border-radius: 12px;
|
|
background: rgba(102, 126, 234, 0.05);
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.stat-number {
|
|
font-size: 2rem;
|
|
font-weight: bold;
|
|
color: var(--primary-color);
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.stat-label {
|
|
font-size: 0.9rem;
|
|
color: var(--text-light);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.recent-activity-item {
|
|
padding: 0.75rem 0;
|
|
border-bottom: 1px solid var(--border-color);
|
|
}
|
|
|
|
.recent-activity-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.activity-icon {
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: 8px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 0.875rem;
|
|
color: white;
|
|
}
|
|
|
|
.activity-created { background-color: #28a745; }
|
|
.activity-updated { background-color: #ffc107; }
|
|
.activity-deleted { background-color: #dc3545; }
|
|
|
|
@media (max-width: 768px) {
|
|
.info-item {
|
|
padding: 0.5rem 0;
|
|
text-align: center;
|
|
}
|
|
|
|
.stat-card {
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
let userAgents = [];
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
loadUserStats();
|
|
loadRecentActivity();
|
|
});
|
|
|
|
async function loadUserStats() {
|
|
try {
|
|
const response = await fetch('{{ base_path }}/api/agents', {
|
|
credentials: 'include'
|
|
});
|
|
|
|
if (response.ok) {
|
|
userAgents = await response.json();
|
|
updateStats();
|
|
} else if (response.status === 401) {
|
|
window.location.href = '{{ base_path }}/login';
|
|
} else {
|
|
throw new Error('Failed to load agent statistics');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading stats:', error);
|
|
document.getElementById('totalAgents').textContent = 'Error';
|
|
}
|
|
}
|
|
|
|
function updateStats() {
|
|
const totalAgents = userAgents.length;
|
|
const activeAgents = userAgents.filter(a => a.agent_status === 'Active').length;
|
|
const developmentAgents = userAgents.filter(a => a.agent_status === 'Development').length;
|
|
|
|
document.getElementById('totalAgents').textContent = totalAgents;
|
|
document.getElementById('activeAgents').textContent = activeAgents;
|
|
document.getElementById('developmentAgents').textContent = developmentAgents;
|
|
document.getElementById('lastLoginDate').textContent = 'Today';
|
|
}
|
|
|
|
function loadRecentActivity() {
|
|
if (userAgents.length === 0) {
|
|
setTimeout(loadRecentActivity, 500);
|
|
return;
|
|
}
|
|
|
|
// Sort agents by last updated date
|
|
const recentAgents = [...userAgents]
|
|
.sort((a, b) => new Date(b.agent_updated_at || b.agent_created_at) - new Date(a.agent_updated_at || a.agent_created_at))
|
|
.slice(0, 5);
|
|
|
|
if (recentAgents.length === 0) {
|
|
document.getElementById('recentActivity').innerHTML = `
|
|
<div class="text-center py-4">
|
|
<div class="mb-3">
|
|
<i class="fas fa-robot fa-2x text-muted"></i>
|
|
</div>
|
|
<h6>No Agent Activity Yet</h6>
|
|
<p class="text-muted mb-3">Create your first agent to see activity here</p>
|
|
<a href="{{ base_path }}/agent-register" class="btn btn-success">
|
|
<i class="fas fa-plus me-2"></i>Create Your First Agent
|
|
</a>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
const activityHtml = recentAgents.map(agent => {
|
|
const isNew = !agent.agent_updated_at || agent.agent_updated_at === agent.agent_created_at;
|
|
const activityType = isNew ? 'created' : 'updated';
|
|
const activityDate = new Date(agent.agent_updated_at || agent.agent_created_at);
|
|
|
|
return `
|
|
<div class="recent-activity-item">
|
|
<div class="d-flex align-items-center">
|
|
<div class="activity-icon activity-${activityType} me-3">
|
|
<i class="fas ${isNew ? 'fa-plus' : 'fa-edit'}"></i>
|
|
</div>
|
|
<div class="flex-grow-1">
|
|
<div class="d-flex justify-content-between align-items-start">
|
|
<div>
|
|
<h6 class="mb-1">${agent.agent_name}</h6>
|
|
<small class="text-muted">Agent ${activityType} • ${formatRelativeDate(activityDate)}</small>
|
|
</div>
|
|
<span class="badge bg-${getStatusColor(agent.agent_status)}">${agent.agent_status || 'Development'}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
document.getElementById('recentActivity').innerHTML = activityHtml;
|
|
}
|
|
|
|
function formatRelativeDate(date) {
|
|
const now = new Date();
|
|
const diffMs = now - date;
|
|
const diffMins = Math.floor(diffMs / (1000 * 60));
|
|
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
|
|
if (diffMins < 1) return 'Just now';
|
|
if (diffMins < 60) return `${diffMins} minutes ago`;
|
|
if (diffHours < 24) return `${diffHours} hours ago`;
|
|
if (diffDays < 7) return `${diffDays} days ago`;
|
|
return date.toLocaleDateString();
|
|
}
|
|
|
|
function getStatusColor(status) {
|
|
const colors = {
|
|
'Active': 'success',
|
|
'Development': 'warning',
|
|
'Inactive': 'secondary',
|
|
'Deprecated': 'danger'
|
|
};
|
|
return colors[status] || 'secondary';
|
|
}
|
|
</script>
|
|
{% endblock %} |