hospital-management/templates/analytics/report_execution_detail.html
2025-08-12 13:33:25 +03:00

601 lines
27 KiB
HTML

{% extends 'base.html' %}
{% load static %}
{% block title %}Report Execution Details - Hospital Management{% endblock %}
{% block content %}
<div class="content">
<div class="container-fluid">
<!-- Page Header -->
<div class="row">
<div class="col-12">
<div class="page-header">
<div class="page-title">
<h4>Report Execution Details</h4>
<h6>View detailed information about report execution</h6>
</div>
<div class="page-btn">
<a href="{% url 'analytics:report_execution_list' %}" class="btn btn-outline-secondary me-2">
<i class="fas fa-arrow-left me-1"></i>Back to List
</a>
{% if execution.status == 'completed' and execution.output_file %}
<a href="{% url 'analytics:download_execution' execution.pk %}" class="btn btn-success">
<i class="fas fa-download me-1"></i>Download Report
</a>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Execution Status Card -->
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h5 class="card-title">
<i class="fas fa-info-circle me-2"></i>Execution Status
</h5>
<div class="card-tools">
{% if execution.status == 'running' %}
<button type="button" class="btn btn-outline-warning btn-sm" onclick="cancelExecution()">
<i class="fas fa-stop me-1"></i>Cancel
</button>
{% elif execution.status == 'failed' %}
<button type="button" class="btn btn-outline-info btn-sm" onclick="retryExecution()">
<i class="fas fa-redo me-1"></i>Retry
</button>
{% endif %}
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="refreshStatus()">
<i class="fas fa-sync me-1"></i>Refresh
</button>
</div>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="execution-status-info">
<div class="status-badge mb-3">
{% if execution.status == 'completed' %}
<span class="badge bg-success fs-6">
<i class="fas fa-check me-2"></i>Completed Successfully
</span>
{% elif execution.status == 'running' %}
<span class="badge bg-warning fs-6">
<i class="fas fa-spinner fa-spin me-2"></i>Running
</span>
{% elif execution.status == 'failed' %}
<span class="badge bg-danger fs-6">
<i class="fas fa-times me-2"></i>Failed
</span>
{% elif execution.status == 'cancelled' %}
<span class="badge bg-secondary fs-6">
<i class="fas fa-ban me-2"></i>Cancelled
</span>
{% else %}
<span class="badge bg-info fs-6">
<i class="fas fa-clock me-2"></i>Pending
</span>
{% endif %}
</div>
{% if execution.status == 'running' %}
<div class="progress mb-3">
<div class="progress-bar progress-bar-striped progress-bar-animated"
role="progressbar" style="width: {{ execution.progress_percentage|default:0 }}%">
{{ execution.progress_percentage|default:0 }}%
</div>
</div>
{% endif %}
<table class="table table-sm">
<tr>
<td><strong>Execution ID:</strong></td>
<td>{{ execution.id }}</td>
</tr>
<tr>
<td><strong>Started At:</strong></td>
<td>{{ execution.started_at|date:"M d, Y H:i:s" }}</td>
</tr>
{% if execution.completed_at %}
<tr>
<td><strong>Completed At:</strong></td>
<td>{{ execution.completed_at|date:"M d, Y H:i:s" }}</td>
</tr>
{% endif %}
<tr>
<td><strong>Duration:</strong></td>
<td>
{% if execution.duration %}
{{ execution.duration }}
{% elif execution.status == 'running' %}
<span id="runningDuration">Calculating...</span>
{% else %}
N/A
{% endif %}
</td>
</tr>
<tr>
<td><strong>Executed By:</strong></td>
<td>{{ execution.executed_by.get_full_name|default:execution.executed_by.username }}</td>
</tr>
</table>
</div>
</div>
<div class="col-md-6">
<div class="execution-metrics">
<h6 class="mb-3">Execution Metrics</h6>
<div class="row">
<div class="col-6">
<div class="metric-card text-center p-3 bg-light rounded">
<div class="metric-value text-primary fs-4 fw-bold">
{{ execution.record_count|default:"0"|floatformat:0 }}
</div>
<div class="metric-label text-muted">Records Processed</div>
</div>
</div>
<div class="col-6">
<div class="metric-card text-center p-3 bg-light rounded">
<div class="metric-value text-success fs-4 fw-bold">
{{ execution.file_size|filesizeformat|default:"N/A" }}
</div>
<div class="metric-label text-muted">Output Size</div>
</div>
</div>
</div>
{% if execution.error_message %}
<div class="alert alert-danger mt-3">
<h6 class="alert-heading">
<i class="fas fa-exclamation-triangle me-2"></i>Error Details
</h6>
<p class="mb-0">{{ execution.error_message }}</p>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Report Information -->
<div class="row">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h5 class="card-title">
<i class="fas fa-file-alt me-2"></i>Report Information
</h5>
</div>
<div class="card-body">
<div class="report-details">
<h6 class="text-primary">{{ execution.report.name }}</h6>
{% if execution.report.description %}
<p class="text-muted mb-3">{{ execution.report.description }}</p>
{% endif %}
<div class="row">
<div class="col-md-6">
<table class="table table-sm">
<tr>
<td><strong>Report Type:</strong></td>
<td>{{ execution.report.get_report_type_display }}</td>
</tr>
<tr>
<td><strong>Category:</strong></td>
<td>{{ execution.report.get_category_display }}</td>
</tr>
<tr>
<td><strong>Output Format:</strong></td>
<td>{{ execution.output_format|upper }}</td>
</tr>
<tr>
<td><strong>Data Source:</strong></td>
<td>{{ execution.report.data_source.name }}</td>
</tr>
</table>
</div>
<div class="col-md-6">
<table class="table table-sm">
<tr>
<td><strong>Created By:</strong></td>
<td>{{ execution.report.created_by.get_full_name|default:execution.report.created_by.username }}</td>
</tr>
<tr>
<td><strong>Created At:</strong></td>
<td>{{ execution.report.created_at|date:"M d, Y" }}</td>
</tr>
<tr>
<td><strong>Last Modified:</strong></td>
<td>{{ execution.report.updated_at|date:"M d, Y" }}</td>
</tr>
<tr>
<td><strong>Is Active:</strong></td>
<td>
{% if execution.report.is_active %}
<span class="badge bg-success">Active</span>
{% else %}
<span class="badge bg-secondary">Inactive</span>
{% endif %}
</td>
</tr>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h5 class="card-title">
<i class="fas fa-cogs me-2"></i>Execution Parameters
</h5>
</div>
<div class="card-body">
{% if execution.parameters %}
<div class="parameters-list">
{% for key, value in execution.parameters.items %}
<div class="parameter-item mb-2">
<strong>{{ key|title }}:</strong>
<span class="text-muted">{{ value }}</span>
</div>
{% endfor %}
</div>
{% else %}
<p class="text-muted">No parameters specified for this execution.</p>
{% endif %}
</div>
</div>
<div class="card mt-3">
<div class="card-header">
<h5 class="card-title">
<i class="fas fa-chart-bar me-2"></i>Quick Actions
</h5>
</div>
<div class="card-body">
<div class="d-grid gap-2">
{% if execution.status == 'completed' %}
<button type="button" class="btn btn-outline-primary" onclick="previewReport()">
<i class="fas fa-eye me-2"></i>Preview Report
</button>
<button type="button" class="btn btn-outline-info" onclick="shareReport()">
<i class="fas fa-share me-2"></i>Share Report
</button>
{% endif %}
<button type="button" class="btn btn-outline-secondary" onclick="viewLogs()">
<i class="fas fa-file-alt me-2"></i>View Execution Logs
</button>
<button type="button" class="btn btn-outline-warning" onclick="scheduleReport()">
<i class="fas fa-calendar me-2"></i>Schedule Report
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Execution Timeline -->
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h5 class="card-title">
<i class="fas fa-history me-2"></i>Execution Timeline
</h5>
</div>
<div class="card-body">
<div class="timeline">
<div class="timeline-item">
<div class="timeline-marker bg-info"></div>
<div class="timeline-content">
<h6 class="timeline-title">Execution Started</h6>
<p class="timeline-description">Report execution initiated by {{ execution.executed_by.get_full_name|default:execution.executed_by.username }}</p>
<small class="text-muted">{{ execution.started_at|date:"M d, Y H:i:s" }}</small>
</div>
</div>
{% if execution.status != 'pending' %}
<div class="timeline-item">
<div class="timeline-marker bg-primary"></div>
<div class="timeline-content">
<h6 class="timeline-title">Data Processing</h6>
<p class="timeline-description">Processing data from {{ execution.report.data_source.name }}</p>
<small class="text-muted">{{ execution.started_at|date:"M d, Y H:i:s" }}</small>
</div>
</div>
{% endif %}
{% if execution.status == 'completed' %}
<div class="timeline-item">
<div class="timeline-marker bg-success"></div>
<div class="timeline-content">
<h6 class="timeline-title">Execution Completed</h6>
<p class="timeline-description">Report generated successfully with {{ execution.record_count|default:"0" }} records</p>
<small class="text-muted">{{ execution.completed_at|date:"M d, Y H:i:s" }}</small>
</div>
</div>
{% elif execution.status == 'failed' %}
<div class="timeline-item">
<div class="timeline-marker bg-danger"></div>
<div class="timeline-content">
<h6 class="timeline-title">Execution Failed</h6>
<p class="timeline-description">{{ execution.error_message|default:"An error occurred during execution" }}</p>
<small class="text-muted">{{ execution.completed_at|date:"M d, Y H:i:s"|default:"N/A" }}</small>
</div>
</div>
{% elif execution.status == 'cancelled' %}
<div class="timeline-item">
<div class="timeline-marker bg-secondary"></div>
<div class="timeline-content">
<h6 class="timeline-title">Execution Cancelled</h6>
<p class="timeline-description">Report execution was cancelled by user</p>
<small class="text-muted">{{ execution.completed_at|date:"M d, Y H:i:s"|default:"N/A" }}</small>
</div>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Execution Logs Modal -->
<div class="modal fade" id="executionLogsModal" tabindex="-1" aria-labelledby="executionLogsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="executionLogsModalLabel">
<i class="fas fa-file-alt me-2"></i>Execution Logs
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="executionLogsContent">
<!-- Content will be loaded here -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" onclick="downloadLogs()">
<i class="fas fa-download me-1"></i>Download Logs
</button>
</div>
</div>
</div>
</div>
<script>
function refreshStatus() {
window.location.reload();
}
function cancelExecution() {
if (confirm('Are you sure you want to cancel this report execution?')) {
fetch(`/analytics/executions/{{ execution.id }}/cancel/`, {
method: 'POST',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
'Content-Type': 'application/json',
},
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Report execution cancelled successfully.');
window.location.reload();
} else {
alert('Failed to cancel execution: ' + data.error);
}
})
.catch(error => {
alert('Error cancelling execution: ' + error);
});
}
}
function retryExecution() {
if (confirm('Retry this failed report execution?')) {
fetch(`/analytics/executions/{{ execution.id }}/retry/`, {
method: 'POST',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
'Content-Type': 'application/json',
},
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Report execution retry initiated.');
window.location.reload();
} else {
alert('Failed to retry execution: ' + data.error);
}
})
.catch(error => {
alert('Error retrying execution: ' + error);
});
}
}
function previewReport() {
window.open(`/analytics/executions/{{ execution.id }}/preview/`, '_blank');
}
function shareReport() {
alert('Share report functionality will be implemented.');
}
function viewLogs() {
// Show loading state
document.getElementById('executionLogsContent').innerHTML = `
<div class="text-center py-4">
<i class="fas fa-spinner fa-spin fa-2x text-primary mb-3"></i>
<p>Loading execution logs...</p>
</div>
`;
// Show modal
const modal = new bootstrap.Modal(document.getElementById('executionLogsModal'));
modal.show();
// Load logs
fetch(`/analytics/executions/{{ execution.id }}/logs/`)
.then(response => response.json())
.then(data => {
if (data.success) {
document.getElementById('executionLogsContent').innerHTML = `
<div class="logs-container">
<pre class="bg-dark text-light p-3 rounded" style="max-height: 400px; overflow-y: auto;">${data.logs}</pre>
</div>
`;
} else {
document.getElementById('executionLogsContent').innerHTML = `
<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle me-2"></i>
No logs available for this execution.
</div>
`;
}
})
.catch(error => {
document.getElementById('executionLogsContent').innerHTML = `
<div class="alert alert-danger">
<i class="fas fa-exclamation-triangle me-2"></i>
Error loading logs: ${error}
</div>
`;
});
}
function scheduleReport() {
window.location.href = `/analytics/reports/{{ execution.report.id }}/schedule/`;
}
function downloadLogs() {
window.location.href = `/analytics/executions/{{ execution.id }}/logs/download/`;
}
// Auto-refresh for running executions
document.addEventListener('DOMContentLoaded', function() {
{% if execution.status == 'running' %}
// Update running duration every second
function updateRunningDuration() {
const startTime = new Date('{{ execution.started_at|date:"c" }}');
const now = new Date();
const duration = Math.floor((now - startTime) / 1000);
const hours = Math.floor(duration / 3600);
const minutes = Math.floor((duration % 3600) / 60);
const seconds = duration % 60;
const durationElement = document.getElementById('runningDuration');
if (durationElement) {
durationElement.textContent = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
}
}
// Update immediately and then every second
updateRunningDuration();
setInterval(updateRunningDuration, 1000);
// Refresh page every 30 seconds for running executions
setTimeout(() => {
window.location.reload();
}, 30000);
{% endif %}
});
</script>
<style>
.execution-status-info .status-badge {
display: block;
}
.metric-card {
border: 1px solid #e9ecef;
}
.timeline {
position: relative;
padding-left: 30px;
}
.timeline::before {
content: '';
position: absolute;
left: 15px;
top: 0;
bottom: 0;
width: 2px;
background-color: #e9ecef;
}
.timeline-item {
position: relative;
margin-bottom: 30px;
}
.timeline-marker {
position: absolute;
left: -22px;
top: 0;
width: 14px;
height: 14px;
border-radius: 50%;
border: 3px solid #fff;
box-shadow: 0 0 0 2px #e9ecef;
}
.timeline-content {
background-color: #f8f9fa;
padding: 15px;
border-radius: 8px;
border-left: 4px solid #0072b5;
}
.timeline-title {
margin-bottom: 5px;
color: #2c3e50;
}
.timeline-description {
margin-bottom: 5px;
color: #6c757d;
}
.logs-container pre {
font-family: 'Courier New', monospace;
font-size: 0.875rem;
line-height: 1.4;
}
@media (max-width: 768px) {
.page-btn {
margin-top: 15px;
}
.card-tools {
margin-top: 15px;
}
.timeline {
padding-left: 20px;
}
.timeline-marker {
left: -17px;
}
.metric-card {
margin-bottom: 15px;
}
}
</style>
{% endblock %}