feat: execution failure reports, auto-printer for WMT, UTC timezone fix for all timestamps
This commit is contained in:
144
templates/ansible/failure_reports.html
Normal file
144
templates/ansible/failure_reports.html
Normal file
@@ -0,0 +1,144 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Execution Failure Reports — Server Monitoring{% endblock %}
|
||||
{% block page_title %}Execution Failure Reports{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
|
||||
<div id="alertArea"></div>
|
||||
|
||||
<!-- Header row -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<p class="text-muted mb-0">
|
||||
Saved records of failed or unreachable hosts from completed playbook executions.
|
||||
</p>
|
||||
<span class="badge bg-secondary fs-6">{{ reports | length }} report(s)</span>
|
||||
</div>
|
||||
|
||||
{% if reports %}
|
||||
<div class="row g-3" id="reportsList">
|
||||
{% for report in reports %}
|
||||
<div class="col-12" id="report-{{ report.id }}">
|
||||
<div class="card shadow-sm border-{% if report.unreachable_count > 0 and report.failed_count == 0 %}warning{% elif report.failed_count > 0 %}danger{% else %}secondary{% endif %}">
|
||||
|
||||
<!-- Card header -->
|
||||
<div class="card-header d-flex justify-content-between align-items-start flex-wrap gap-2 py-2">
|
||||
<div>
|
||||
<i class="fas fa-exclamation-triangle me-2 text-{% if report.failed_count > 0 %}danger{% else %}warning{% endif %}"></i>
|
||||
<strong>{{ report.playbook_name }}</strong>
|
||||
<span class="text-muted ms-2" style="font-size:.82rem;">
|
||||
Execution <code>{{ report.execution_id[:8] }}…</code>
|
||||
</span>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
{% if report.failed_count > 0 %}
|
||||
<span class="badge bg-danger"><i class="fas fa-times-circle me-1"></i>{{ report.failed_count }} failed</span>
|
||||
{% endif %}
|
||||
{% if report.unreachable_count > 0 %}
|
||||
<span class="badge bg-warning text-dark"><i class="fas fa-plug me-1"></i>{{ report.unreachable_count }} unreachable</span>
|
||||
{% endif %}
|
||||
<span class="text-muted" style="font-size:.78rem;">
|
||||
<i class="fas fa-calendar-alt me-1"></i>{{ report.saved_at[:19].replace('T',' ') }}
|
||||
</span>
|
||||
<a href="/ansible/executions/{{ report.execution_id }}" class="btn btn-sm btn-outline-secondary" target="_blank"
|
||||
title="View full execution">
|
||||
<i class="fas fa-external-link-alt"></i>
|
||||
</a>
|
||||
<button class="btn btn-sm btn-outline-danger" onclick="deleteReport({{ report.id }})"
|
||||
title="Delete this report">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Host list -->
|
||||
<div class="card-body py-2 px-0">
|
||||
{% if report.note %}
|
||||
<div class="px-3 pb-2">
|
||||
<i class="fas fa-sticky-note text-muted me-1"></i>
|
||||
<small class="text-muted">{{ report.note }}</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width:50%">Hostname</th>
|
||||
<th style="width:20%">Status</th>
|
||||
<th>Reason</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for host in report.failed_hosts %}
|
||||
<tr>
|
||||
<td><strong>{{ host.hostname }}</strong></td>
|
||||
<td>
|
||||
{% if host.status == 'unreachable' %}
|
||||
<span class="badge bg-warning text-dark">unreachable</span>
|
||||
{% else %}
|
||||
<span class="badge bg-danger">failed</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-muted" style="font-size:.85rem;">{{ host.reason }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<div class="card">
|
||||
<div class="card-body text-center py-5 text-muted">
|
||||
<i class="fas fa-check-circle fa-3x mb-3 text-success"></i>
|
||||
<p class="mb-0">No failure reports saved yet.<br>
|
||||
Use the <strong>Save Report</strong> button in the execution popup when a playbook has failed or unreachable hosts.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const API = '/api/ansible';
|
||||
|
||||
function showAlert(html, type='info') {
|
||||
const area = document.getElementById('alertArea');
|
||||
area.innerHTML = `<div class="alert alert-${type} alert-dismissible fade show">
|
||||
${html}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>`;
|
||||
setTimeout(() => { if (area.firstChild) area.firstChild.remove(); }, 5000);
|
||||
}
|
||||
|
||||
async function deleteReport(reportId) {
|
||||
if (!confirm('Delete this failure report?')) return;
|
||||
try {
|
||||
const r = await fetch(`${API}/failure-reports/${reportId}`, {method:'DELETE'});
|
||||
const d = await r.json();
|
||||
if (d.success) {
|
||||
document.getElementById(`report-${reportId}`).remove();
|
||||
showAlert('<i class="fas fa-check-circle me-1"></i>Report deleted.', 'success');
|
||||
// Show empty state if no more reports
|
||||
if (!document.querySelector('#reportsList .col-12')) {
|
||||
document.getElementById('reportsList').innerHTML =
|
||||
'<div class="col-12"><div class="card"><div class="card-body text-center py-5 text-muted">' +
|
||||
'<i class="fas fa-check-circle fa-3x mb-3 text-success"></i>' +
|
||||
'<p class="mb-0">No failure reports saved yet.</p></div></div></div>';
|
||||
}
|
||||
} else {
|
||||
showAlert(`Error: ${d.error}`, 'danger');
|
||||
}
|
||||
} catch(e) {
|
||||
showAlert('Network error: ' + e, 'danger');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user