agent_tracker/templates/agent_register.html
nickviljoen 6c231cb094 Add read-only admin role, client verification workflow, and email notifications
- Three-tier role system: user, admin, readonly_admin with dashboard gating
- Client field (Yes/No) on registration with conditional Client Name and Studio Name
- Auto-tag client agents as "needs_verification" with Verification tab on admin dashboard
- Client agent email notification via Mailgun to configured recipients
- Daily agent digest email scheduled via APScheduler (configurable hour)
- Manual digest trigger endpoint: POST /api/admin/digest/send
- Role dropdown replaces is_admin checkbox in user edit modal
- Registration form reordered: Name, Description, Purpose, Client, Client Name, Studio, Tool
- Stat card CSS fix for text truncation on admin dashboard
- Updated CLAUDE.md documentation and PLAN file

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 20:47:02 +02:00

435 lines
No EOL
16 KiB
HTML

{% extends "base.html" %}
{% block title %}Register AI Agent - AgentHub{% endblock %}
{% block content %}
<div class="container my-5">
<div class="row justify-content-center">
<div class="col-md-8 col-lg-6">
<div class="card shadow-lg border-0">
<div class="card-body p-5">
<div class="text-center mb-4">
<div class="agent-icon mb-3">
<i class="fas fa-robot"></i>
</div>
<h2 class="card-title mb-2">Register AI Agent</h2>
<p class="text-muted">Create a new AI agent in the system</p>
</div>
{% if error %}
<div class="alert alert-danger" role="alert">
<i class="fas fa-exclamation-triangle me-2"></i>{{ error }}
</div>
{% endif %}
<form method="POST" id="agentForm" action="{{ base_path }}/agent-register">
<div class="mb-4">
<label for="agentName" class="form-label">
<i class="fas fa-tag me-2"></i>Agent Name *
</label>
<input type="text"
name="agent_name"
class="form-control form-control-lg"
id="agentName"
placeholder="Enter agent name"
required>
</div>
<div class="mb-4">
<label for="agentDescription" class="form-label">
<i class="fas fa-align-left me-2"></i>Description
</label>
<textarea name="agent_description"
class="form-control"
id="agentDescription"
rows="3"
maxlength="300"
placeholder="Describe what this agent does"></textarea>
<div class="form-text">Maximum 300 characters</div>
</div>
<div class="mb-4">
<label for="agentPurpose" class="form-label">
<i class="fas fa-bullseye me-2"></i>Purpose
</label>
<input type="text"
name="agent_purpose"
class="form-control"
id="agentPurpose"
maxlength="200"
placeholder="What is the main purpose of this agent?">
<div class="form-text">Maximum 200 characters</div>
</div>
<div class="row">
<div class="col-md-6 mb-4">
<label for="agentClient" class="form-label">
<i class="fas fa-handshake me-2"></i>Client *
</label>
<select name="client" class="form-select" id="agentClient" required onchange="toggleClientName()">
<option value="">Select</option>
<option value="yes">Yes</option>
<option value="no">No</option>
</select>
<div class="form-text">Is this agent for a client?</div>
</div>
<div class="col-md-6 mb-4" id="clientNameSection" style="display: none;">
<label for="agentClientName" class="form-label">
<i class="fas fa-building me-2"></i>Client Name *
</label>
<input type="text"
name="client_name"
class="form-control"
id="agentClientName"
placeholder="Enter client name">
</div>
</div>
<div class="mb-4">
<label for="agentStudioName" class="form-label">
<i class="fas fa-film me-2"></i>Studio Name
</label>
<input type="text"
name="studio_name"
class="form-control"
id="agentStudioName"
placeholder="Enter studio name (optional)">
</div>
<div class="mb-4">
<label for="agentTool" class="form-label">
<i class="fas fa-tools me-2"></i>Tool *
</label>
<input type="text"
name="agent_tool"
class="form-control form-control-lg"
id="agentTool"
placeholder="e.g., chat-sandbox (LibreChat), Copilot, Custom"
required>
<div class="form-text">The platform or environment where this agent operates</div>
</div>
<div class="row">
<div class="col-md-6 mb-4">
<label for="agentVersion" class="form-label">
<i class="fas fa-code-branch me-2"></i>Version
</label>
<input type="text"
name="agent_version"
class="form-control"
id="agentVersion"
placeholder="e.g., 1.0.0">
</div>
<div class="col-md-6 mb-4">
<label for="agentStatus" class="form-label">
<i class="fas fa-circle me-2"></i>Status
</label>
<select name="agent_status" class="form-select" id="agentStatus">
<option value="Development">Development</option>
<option value="Active">Active</option>
<option value="Inactive">Inactive</option>
<option value="Deprecated">Deprecated</option>
</select>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-4">
<label for="agentDiscipline" class="form-label">
<i class="fas fa-layer-group me-2"></i>Discipline *
</label>
<select name="discipline" class="form-select" id="agentDiscipline" required>
<option value="">Select Discipline</option>
<option value="Strategy">Strategy</option>
<option value="Creative">Creative</option>
<option value="Oversight including delivery">Oversight including delivery</option>
<option value="Optimization">Optimization</option>
<option value="Back Office including operations">Back Office including operations</option>
<option value="Pencil Agents">Pencil Agents</option>
</select>
</div>
<div class="col-md-6 mb-4">
<label for="agentLocation" class="form-label">
<i class="fas fa-map-marker-alt me-2"></i>Location
</label>
<input type="text"
name="agent_location"
class="form-control"
id="agentLocation"
placeholder="Physical or virtual location">
</div>
<div class="col-md-6 mb-4">
<label for="agentDepartment" class="form-label">
<i class="fas fa-building me-2"></i>Department
</label>
<input type="text"
name="agent_department"
class="form-control"
id="agentDepartment"
placeholder="Department or team">
</div>
</div>
<div class="mb-4">
<label for="agentContact" class="form-label">
<i class="fas fa-user-tie me-2"></i>Contact Person
</label>
<input type="text"
name="agent_contact_person"
class="form-control"
id="agentContact"
placeholder="Person responsible for this agent">
</div>
<div class="mb-4">
<label for="agentTags" class="form-label">
<i class="fas fa-tags me-2"></i>Tags
</label>
<input type="text"
name="agent_tags"
class="form-control"
id="agentTags"
placeholder="Enter tags separated by commas (e.g., AI, chatbot, automation)">
<div class="form-text">Separate multiple tags with commas</div>
</div>
<div class="mb-4">
<label for="agentUserbase" class="form-label">
<i class="fas fa-users me-2"></i>Target Userbase
</label>
<input type="text"
name="agent_userbase"
class="form-control"
id="agentUserbase"
placeholder="Target users (e.g., customers, employees, developers)">
<div class="form-text">Separate multiple groups with commas</div>
</div>
<div class="mb-4">
<label for="agentCapabilities" class="form-label">
<i class="fas fa-cogs me-2"></i>Capabilities
</label>
<input type="text"
name="agent_capabilities"
class="form-control"
id="agentCapabilities"
placeholder="Agent capabilities (e.g., data-processing, automated-testing)">
<div class="form-text">Separate multiple capabilities with commas</div>
</div>
<div class="mb-4">
<div class="form-check">
<input class="form-check-input"
type="checkbox"
name="quality_audit_status"
value="true"
id="qualityAuditStatus"
{% if not current_user.is_admin %}disabled{% endif %}
onchange="toggleRiskFactor()">
<label class="form-check-label" for="qualityAuditStatus">
<i class="fas fa-certificate me-2"></i>Quality Audit
{% if current_user.is_admin %}
<span class="badge bg-warning text-dark ms-2">Admin Only</span>
{% else %}
<span class="badge bg-secondary ms-2">Admin Required</span>
{% endif %}
</label>
</div>
<div class="form-text" id="qualityAuditNote">
{% if current_user.is_admin %}
Check this box if the agent has passed quality audit review.
{% else %}
Only administrators can mark agents as quality audited.
{% endif %}
</div>
</div>
<div class="mb-4" id="riskFactorSection" style="display: none;">
<label for="riskFactor" class="form-label">
<i class="fas fa-exclamation-triangle me-2"></i>Risk Factor
<span class="badge bg-warning text-dark ms-2">Admin Only</span>
<span class="text-danger">*</span>
</label>
<select name="risk_factor"
class="form-select"
id="riskFactor"
{% if not current_user.is_admin %}disabled{% endif %}>
<option value="">Select Risk Level</option>
<option value="1">1 - Very Low Risk</option>
<option value="2">2 - Low Risk</option>
<option value="3">3 - Medium Risk</option>
<option value="4">4 - High Risk</option>
<option value="5">5 - Very High Risk</option>
</select>
<div class="form-text">
{% if current_user.is_admin %}
Required when Quality Audit is checked. Select the appropriate risk level for this agent.
{% else %}
Only administrators can set risk factor levels.
{% endif %}
</div>
</div>
<div class="d-grid mb-4">
<button type="submit" class="btn btn-primary btn-lg">
<i class="fas fa-robot me-2"></i>Register Agent
</button>
</div>
<div class="text-center">
<p class="text-muted mb-0">
<a href="{{ base_path }}/agent-management" class="text-decoration-none fw-bold">
View My Agents
</a>
</p>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<style>
.agent-icon {
width: 80px;
height: 80px;
border-radius: 50%;
background: linear-gradient(135deg, #f3ae3e 0%, #f3ae3e 100%);
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto;
font-size: 2rem;
color: white;
}
.form-control-lg {
padding: 12px 16px;
font-size: 1rem;
}
.card {
border-radius: 20px;
backdrop-filter: blur(10px);
background: rgba(255, 255, 255, 0.95);
}
.form-select {
padding: 8px 12px;
}
@media (max-width: 576px) {
.card-body {
padding: 2rem 1.5rem !important;
}
.agent-icon {
width: 60px;
height: 60px;
font-size: 1.5rem;
}
}
</style>
<script>
// Simple form validation and duplicate submission prevention
let formSubmitted = false;
function toggleClientName() {
const clientSelect = document.getElementById('agentClient');
const clientNameSection = document.getElementById('clientNameSection');
const clientNameInput = document.getElementById('agentClientName');
if (clientSelect.value === 'yes') {
clientNameSection.style.display = 'block';
clientNameInput.required = true;
} else {
clientNameSection.style.display = 'none';
clientNameInput.required = false;
clientNameInput.value = '';
}
}
function toggleRiskFactor() {
const qualityAuditStatus = document.getElementById('qualityAuditStatus');
const riskFactorSection = document.getElementById('riskFactorSection');
const riskFactor = document.getElementById('riskFactor');
if (qualityAuditStatus.checked) {
riskFactorSection.style.display = 'block';
riskFactor.required = true;
} else {
riskFactorSection.style.display = 'none';
riskFactor.required = false;
riskFactor.value = '';
}
}
document.getElementById('agentForm').addEventListener('submit', function(e) {
const agentName = document.getElementById('agentName').value.trim();
const agentTool = document.getElementById('agentTool').value.trim();
const clientSelect = document.getElementById('agentClient');
const qualityAuditStatus = document.getElementById('qualityAuditStatus');
const riskFactor = document.getElementById('riskFactor');
if (!agentName) {
e.preventDefault();
alert('Agent name is required!');
return false;
}
if (!agentTool) {
e.preventDefault();
alert('Agent tool is required!');
return false;
}
if (!clientSelect.value) {
e.preventDefault();
alert('Client selection is required!');
return false;
}
if (clientSelect.value === 'yes') {
const clientName = document.getElementById('agentClientName').value.trim();
if (!clientName) {
e.preventDefault();
alert('Client Name is required when Client is set to Yes!');
return false;
}
}
const discipline = document.getElementById('agentDiscipline').value;
if (!discipline) {
e.preventDefault();
alert('Discipline is required!');
return false;
}
// Validate Risk Factor if Quality Audit is checked
if (qualityAuditStatus.checked && (!riskFactor.value || riskFactor.value === '')) {
e.preventDefault();
alert('Risk Factor is required when Quality Audit is checked!');
return false;
}
// Prevent duplicate submissions
if (formSubmitted) {
e.preventDefault();
return false;
}
formSubmitted = true;
// Disable the submit button to prevent multiple clicks
const submitBtn = e.target.querySelector('button[type="submit"]');
if (submitBtn) {
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Registering...';
}
return true;
});
</script>
{% endblock %}