TTS-Arena-V2 / templates /admin /campaign_detail.html
GitHub Actions
Sync from GitHub repo
5582677
{% extends "admin/base.html" %}
{% block admin_content %}
<div class="admin-header">
<div class="admin-title">Campaign #{{ campaign.id }} Details</div>
<a href="{{ url_for('admin.campaigns') }}" class="btn-secondary">Back to Campaigns</a>
</div>
<!-- Campaign Overview -->
<div class="admin-card">
<div class="admin-card-header">
<div class="admin-card-title">Campaign Overview</div>
<div class="campaign-status">
<span class="status-badge status-{{ campaign.status }}">
{{ campaign.status.replace('_', ' ').title() }}
</span>
</div>
</div>
<div class="campaign-details">
<div class="detail-grid">
<div class="detail-item">
<div class="detail-label">Target Model:</div>
<div class="detail-value">
<strong>{{ campaign.model.name }}</strong>
<span class="model-type-badge model-type-{{ campaign.model_type }}">
{{ campaign.model_type.upper() }}
</span>
</div>
</div>
<div class="detail-item">
<div class="detail-label">Detected At:</div>
<div class="detail-value">{{ campaign.detected_at.strftime('%Y-%m-%d %H:%M:%S') }}</div>
</div>
<div class="detail-item">
<div class="detail-label">Detection Window:</div>
<div class="detail-value">{{ campaign.time_window_hours }} hours</div>
</div>
<div class="detail-item">
<div class="detail-label">Total Votes:</div>
<div class="detail-value">{{ campaign.vote_count }}</div>
</div>
<div class="detail-item">
<div class="detail-label">Users Involved:</div>
<div class="detail-value">{{ campaign.user_count }}</div>
</div>
<div class="detail-item">
<div class="detail-label">Confidence Score:</div>
<div class="detail-value">
<div class="confidence-bar">
<div class="confidence-fill" style="width: {{ (campaign.confidence_score * 100)|round }}%"></div>
<span class="confidence-text">{{ (campaign.confidence_score * 100)|round }}%</span>
</div>
</div>
</div>
</div>
{% if campaign.resolved_at %}
<div class="resolution-info">
<h4>Resolution Information</h4>
<div class="detail-grid">
<div class="detail-item">
<div class="detail-label">Resolved By:</div>
<div class="detail-value">{{ campaign.resolver.username if campaign.resolver else 'System' }}</div>
</div>
<div class="detail-item">
<div class="detail-label">Resolved At:</div>
<div class="detail-value">{{ campaign.resolved_at.strftime('%Y-%m-%d %H:%M:%S') }}</div>
</div>
</div>
{% if campaign.admin_notes %}
<div class="admin-notes">
<div class="detail-label">Admin Notes:</div>
<div class="detail-value">{{ campaign.admin_notes }}</div>
</div>
{% endif %}
</div>
{% endif %}
</div>
</div>
<!-- Campaign Participants -->
<div class="admin-card">
<div class="admin-card-header">
<div class="admin-card-title">Campaign Participants ({{ participants|length }})</div>
</div>
{% if participants %}
<div class="table-responsive">
<table class="admin-table">
<thead>
<tr>
<th>User</th>
<th>Votes in Campaign</th>
<th>First Vote</th>
<th>Last Vote</th>
<th>Suspicion Level</th>
<th>Account Age</th>
<th>Current Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for participant, user in participants %}
<tr>
<td>
<a href="{{ url_for('admin.user_detail', user_id=user.id) }}">
{{ user.username }}
</a>
</td>
<td>{{ participant.votes_in_campaign }}</td>
<td>{{ participant.first_vote_at.strftime('%Y-%m-%d %H:%M') }}</td>
<td>{{ participant.last_vote_at.strftime('%Y-%m-%d %H:%M') }}</td>
<td>
<span class="suspicion-badge suspicion-{{ participant.suspicion_level }}">
{{ participant.suspicion_level.title() }}
</span>
</td>
<td>
{% if user.join_date %}
{{ ((campaign.detected_at - user.join_date).days) }} days
{% else %}
Unknown
{% endif %}
</td>
<td>
<div class="user-status" data-user-id="{{ user.id }}">
Checking...
</div>
</td>
<td>
<a href="{{ url_for('admin.user_detail', user_id=user.id) }}" class="action-btn">
View User
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p>No participants found.</p>
{% endif %}
</div>
<!-- Related Timeouts -->
<div class="admin-card">
<div class="admin-card-header">
<div class="admin-card-title">Related Timeouts ({{ related_timeouts|length }})</div>
</div>
{% if related_timeouts %}
<div class="table-responsive">
<table class="admin-table">
<thead>
<tr>
<th>User</th>
<th>Reason</th>
<th>Created</th>
<th>Expires</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for timeout in related_timeouts %}
<tr>
<td>
<a href="{{ url_for('admin.user_detail', user_id=timeout.user.id) }}">
{{ timeout.user.username }}
</a>
</td>
<td class="text-truncate" title="{{ timeout.reason }}">{{ timeout.reason }}</td>
<td>{{ timeout.created_at.strftime('%Y-%m-%d %H:%M') }}</td>
<td>{{ timeout.expires_at.strftime('%Y-%m-%d %H:%M') }}</td>
<td>
{% if timeout.is_currently_active() %}
<span class="status-badge status-active">Active</span>
{% else %}
<span class="status-badge status-expired">Expired</span>
{% endif %}
</td>
<td>
<a href="{{ url_for('admin.timeouts') }}" class="action-btn">
Manage
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p>No related timeouts.</p>
{% endif %}
</div>
<!-- Resolution Actions -->
{% if campaign.status == 'active' %}
<div class="admin-card">
<div class="admin-card-header">
<div class="admin-card-title">Resolve Campaign</div>
</div>
<form method="POST" action="{{ url_for('admin.resolve_campaign_route', campaign_id=campaign.id) }}" class="admin-form">
<div class="form-group">
<label for="status">Resolution Status</label>
<select id="status" name="status" class="form-control" required>
<option value="">Select resolution...</option>
<option value="resolved">Resolved - Legitimate coordinated campaign</option>
<option value="false_positive">False Positive - Not a real campaign</option>
</select>
</div>
<div class="form-group">
<label for="admin_notes">Admin Notes</label>
<textarea id="admin_notes" name="admin_notes" class="form-control" rows="3"
placeholder="Add notes about the resolution decision..."></textarea>
</div>
<button type="submit" class="btn-primary">Resolve Campaign</button>
</form>
</div>
{% endif %}
<style>
.campaign-details {
background-color: var(--light-gray);
padding: 20px;
border-radius: var(--radius);
border: 1px solid var(--border-color);
}
.detail-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 16px;
margin-bottom: 20px;
}
.detail-item {
display: flex;
flex-direction: column;
gap: 4px;
}
.detail-label {
font-weight: 500;
color: #666;
font-size: 14px;
}
.detail-value {
font-size: 16px;
display: flex;
align-items: center;
gap: 8px;
}
.campaign-status {
display: flex;
align-items: center;
}
.model-type-badge {
padding: 4px 8px;
border-radius: 4px;
font-size: 11px;
font-weight: 500;
color: white;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.model-type-tts {
background-color: #007bff;
}
.model-type-conversational {
background-color: #28a745;
}
.confidence-bar {
position: relative;
width: 120px;
height: 24px;
background-color: #e9ecef;
border-radius: 12px;
overflow: hidden;
}
.confidence-fill {
height: 100%;
background: linear-gradient(90deg, #dc3545 0%, #ffc107 50%, #28a745 100%);
transition: width 0.3s ease;
}
.confidence-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 12px;
font-weight: 500;
color: #333;
text-shadow: 0 0 2px rgba(255, 255, 255, 0.8);
}
.resolution-info {
border-top: 1px solid var(--border-color);
padding-top: 20px;
margin-top: 20px;
}
.resolution-info h4 {
margin: 0 0 16px 0;
color: var(--primary-color);
}
.admin-notes {
margin-top: 16px;
}
.suspicion-badge {
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
color: white;
}
.suspicion-low {
background-color: #28a745;
}
.suspicion-medium {
background-color: #ffc107;
color: black;
}
.suspicion-high {
background-color: #dc3545;
}
.status-badge {
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
color: white;
}
.status-active {
background-color: #dc3545;
}
.status-resolved {
background-color: #28a745;
}
.status-false_positive {
background-color: #ffc107;
color: black;
}
.status-expired {
background-color: #6c757d;
}
.user-status {
font-size: 12px;
}
.user-status.timed-out {
color: #dc3545;
font-weight: 500;
}
.user-status.active {
color: #28a745;
}
@media (max-width: 768px) {
.detail-grid {
grid-template-columns: 1fr;
}
.confidence-bar {
width: 100px;
}
.admin-header {
flex-direction: column;
gap: 12px;
align-items: flex-start;
}
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Check user timeout status for each participant
const userStatusElements = document.querySelectorAll('.user-status');
userStatusElements.forEach(async (element) => {
const userId = element.dataset.userId;
try {
// This would need to be implemented as an API endpoint
// For now, we'll just show a placeholder
element.textContent = 'Active';
element.className = 'user-status active';
} catch (error) {
element.textContent = 'Unknown';
element.className = 'user-status';
}
});
});
</script>
{% endblock %}