145 lines
5.6 KiB
HTML
145 lines
5.6 KiB
HTML
{% 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 %}
|