790 lines
39 KiB
HTML
790 lines
39 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}{{ object.message|truncatechars:50 }} - Alert Instance Details{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid">
|
|
<!-- Breadcrumb -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="page-title-box d-sm-flex align-items-center justify-content-between">
|
|
<h4 class="mb-sm-0">Alert Instance Details</h4>
|
|
<div class="page-title-right">
|
|
<ol class="breadcrumb m-0">
|
|
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'communications:dashboard' %}">Communications</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'communications:alert_instance_list' %}">Alert Instances</a></li>
|
|
<li class="breadcrumb-item active">Details</li>
|
|
</ol>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<!-- Main Content -->
|
|
<div class="col-lg-8">
|
|
<!-- Alert Overview -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-{% if object.alert_rule.severity == 'CRITICAL' %}exclamation-circle{% elif object.alert_rule.severity == 'HIGH' %}exclamation-triangle{% else %}bell{% endif %} me-2"></i>
|
|
Alert Instance
|
|
</h5>
|
|
<div class="d-flex gap-2">
|
|
<span class="badge bg-{% if object.alert_rule.category == 'SYSTEM' %}primary{% elif object.alert_rule.category == 'PATIENT' %}success{% elif object.alert_rule.category == 'EQUIPMENT' %}warning{% elif object.alert_rule.category == 'MEDICATION' %}info{% else %}secondary{% endif %} fs-6">
|
|
{{ object.alert_rule.get_category_display }}
|
|
</span>
|
|
<span class="badge bg-{% if object.alert_rule.severity == 'CRITICAL' %}danger{% elif object.alert_rule.severity == 'HIGH' %}warning{% elif object.alert_rule.severity == 'MEDIUM' %}info{% else %}secondary{% endif %} fs-6">
|
|
{{ object.alert_rule.get_severity_display }}
|
|
</span>
|
|
<span class="badge bg-{% if object.status == 'RESOLVED' %}success{% elif object.status == 'ACKNOWLEDGED' %}info{% elif object.status == 'ESCALATED' %}warning{% elif object.status == 'SNOOZED' %}secondary{% else %}danger{% endif %} fs-6">
|
|
{{ object.get_status_display }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<!-- Alert Message -->
|
|
<div class="alert alert-{% if object.alert_rule.severity == 'CRITICAL' %}danger{% elif object.alert_rule.severity == 'HIGH' %}warning{% elif object.alert_rule.severity == 'MEDIUM' %}info{% else %}light{% endif %}" role="alert">
|
|
<div class="d-flex">
|
|
<div class="flex-shrink-0">
|
|
<i class="fas fa-{% if object.alert_rule.severity == 'CRITICAL' %}exclamation-circle{% elif object.alert_rule.severity == 'HIGH' %}exclamation-triangle{% else %}info-circle{% endif %} fa-2x"></i>
|
|
</div>
|
|
<div class="flex-grow-1 ms-3">
|
|
<h5 class="alert-heading">{{ object.message }}</h5>
|
|
{% if object.details %}
|
|
<p class="mb-0">{{ object.details }}</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Alert Information -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-6">
|
|
<div class="table-responsive">
|
|
<table class="table table-borderless">
|
|
<tr>
|
|
<td class="fw-bold text-muted">Alert Rule:</td>
|
|
<td>
|
|
<a href="{% url 'communications:alert_rule_detail' object.alert_rule.pk %}" class="text-primary">
|
|
{{ object.alert_rule.rule_name }}
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold text-muted">Category:</td>
|
|
<td>{{ object.alert_rule.get_category_display }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold text-muted">Severity:</td>
|
|
<td>{{ object.alert_rule.get_severity_display }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold text-muted">Status:</td>
|
|
<td>{{ object.get_status_display }}</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="table-responsive">
|
|
<table class="table table-borderless">
|
|
<tr>
|
|
<td class="fw-bold text-muted">Triggered:</td>
|
|
<td>{{ object.triggered_at|date:"M d, Y g:i A" }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold text-muted">Time Ago:</td>
|
|
<td>{{ object.triggered_at|timesince }} ago</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold text-muted">Response Time:</td>
|
|
<td>
|
|
{% if object.acknowledged_at %}
|
|
{{ object.response_time_minutes|default:0 }} minutes
|
|
{% else %}
|
|
<span class="text-muted">Not acknowledged</span>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold text-muted">Priority:</td>
|
|
<td>
|
|
<span class="badge bg-{% if object.priority == 'HIGH' %}danger{% elif object.priority == 'MEDIUM' %}warning{% else %}secondary{% endif %}">
|
|
{{ object.priority|default:"Normal" }}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Trigger Data -->
|
|
{% if object.trigger_data %}
|
|
<div class="mb-4">
|
|
<h6 class="text-muted mb-2">Trigger Data:</h6>
|
|
<div class="bg-light p-3 rounded">
|
|
<pre class="mb-0">{{ object.trigger_data|pprint }}</pre>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Context Data -->
|
|
{% if object.context_data %}
|
|
<div class="mb-4">
|
|
<h6 class="text-muted mb-2">Context Information:</h6>
|
|
<div class="bg-light p-3 rounded">
|
|
<pre class="mb-0">{{ object.context_data|pprint }}</pre>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Acknowledgment Information -->
|
|
{% if object.acknowledged_at %}
|
|
<div class="mb-4">
|
|
<h6 class="text-muted mb-2">Acknowledgment Details:</h6>
|
|
<div class="card bg-light">
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="table-responsive">
|
|
<table class="table table-borderless table-sm">
|
|
<tr>
|
|
<td class="fw-bold text-muted">Acknowledged By:</td>
|
|
<td>
|
|
{% if object.acknowledged_by %}
|
|
<div class="d-flex align-items-center">
|
|
<div class="avatar-xs me-2">
|
|
<div class="avatar-title bg-soft-success text-success rounded-circle">
|
|
{{ object.acknowledged_by.first_name.0 }}{{ object.acknowledged_by.last_name.0 }}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<h6 class="mb-0">{{ object.acknowledged_by.get_full_name }}</h6>
|
|
<small class="text-muted">{{ object.acknowledged_by.email }}</small>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<span class="text-muted">System</span>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold text-muted">Acknowledged At:</td>
|
|
<td>{{ object.acknowledged_at|date:"M d, Y g:i A" }}</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
{% if object.acknowledgment_note %}
|
|
<div>
|
|
<h6 class="text-muted">Acknowledgment Note:</h6>
|
|
<div class="bg-white p-2 rounded border">
|
|
{{ object.acknowledgment_note|linebreaks }}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Resolution Information -->
|
|
{% if object.resolved_at %}
|
|
<div class="mb-4">
|
|
<h6 class="text-muted mb-2">Resolution Details:</h6>
|
|
<div class="card bg-light">
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="table-responsive">
|
|
<table class="table table-borderless table-sm">
|
|
<tr>
|
|
<td class="fw-bold text-muted">Resolved By:</td>
|
|
<td>
|
|
{% if object.resolved_by %}
|
|
<div class="d-flex align-items-center">
|
|
<div class="avatar-xs me-2">
|
|
<div class="avatar-title bg-soft-success text-success rounded-circle">
|
|
{{ object.resolved_by.first_name.0 }}{{ object.resolved_by.last_name.0 }}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<h6 class="mb-0">{{ object.resolved_by.get_full_name }}</h6>
|
|
<small class="text-muted">{{ object.resolved_by.email }}</small>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<span class="text-muted">System</span>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold text-muted">Resolved At:</td>
|
|
<td>{{ object.resolved_at|date:"M d, Y g:i A" }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold text-muted">Resolution Time:</td>
|
|
<td>{{ object.resolution_time_minutes|default:0 }} minutes</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
{% if object.resolution_note %}
|
|
<div>
|
|
<h6 class="text-muted">Resolution Note:</h6>
|
|
<div class="bg-white p-2 rounded border">
|
|
{{ object.resolution_note|linebreaks }}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Snooze Information -->
|
|
{% if object.status == 'SNOOZED' and object.snoozed_until %}
|
|
<div class="mb-4">
|
|
<h6 class="text-muted mb-2">Snooze Information:</h6>
|
|
<div class="alert alert-info" role="alert">
|
|
<div class="d-flex">
|
|
<div class="flex-shrink-0">
|
|
<i class="fas fa-clock fa-lg"></i>
|
|
</div>
|
|
<div class="flex-grow-1 ms-3">
|
|
<h6 class="alert-heading">Alert Snoozed</h6>
|
|
<p class="mb-1">This alert is snoozed until: <strong>{{ object.snoozed_until|date:"M d, Y g:i A" }}</strong></p>
|
|
<p class="mb-0">
|
|
{% if object.snoozed_by %}
|
|
Snoozed by: {{ object.snoozed_by.get_full_name }}
|
|
{% endif %}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Alert Timeline -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-history me-2"></i>
|
|
Alert Timeline
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="timeline">
|
|
<!-- Alert Triggered -->
|
|
<div class="timeline-item">
|
|
<div class="timeline-marker bg-{% if object.alert_rule.severity == 'CRITICAL' %}danger{% elif object.alert_rule.severity == 'HIGH' %}warning{% elif object.alert_rule.severity == 'MEDIUM' %}info{% else %}secondary{% endif %}">
|
|
<i class="fas fa-bell"></i>
|
|
</div>
|
|
<div class="timeline-content">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<h6 class="mb-1">Alert Triggered</h6>
|
|
<p class="text-muted mb-1">{{ object.message }}</p>
|
|
<small class="text-muted">{{ object.triggered_at|date:"M d, Y g:i A" }}</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Alert Acknowledged -->
|
|
{% if object.acknowledged_at %}
|
|
<div class="timeline-item">
|
|
<div class="timeline-marker bg-info">
|
|
<i class="fas fa-check"></i>
|
|
</div>
|
|
<div class="timeline-content">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<h6 class="mb-1">Alert Acknowledged</h6>
|
|
<p class="text-muted mb-1">
|
|
{% if object.acknowledged_by %}
|
|
By {{ object.acknowledged_by.get_full_name }}
|
|
{% else %}
|
|
By System
|
|
{% endif %}
|
|
</p>
|
|
{% if object.acknowledgment_note %}
|
|
<p class="mb-1">{{ object.acknowledgment_note }}</p>
|
|
{% endif %}
|
|
<small class="text-muted">{{ object.acknowledged_at|date:"M d, Y g:i A" }}</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Alert Escalated -->
|
|
{% if object.escalated_at %}
|
|
<div class="timeline-item">
|
|
<div class="timeline-marker bg-warning">
|
|
<i class="fas fa-arrow-up"></i>
|
|
</div>
|
|
<div class="timeline-content">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<h6 class="mb-1">Alert Escalated</h6>
|
|
<p class="text-muted mb-1">
|
|
{% if object.escalated_by %}
|
|
By {{ object.escalated_by.get_full_name }}
|
|
{% else %}
|
|
Automatic escalation
|
|
{% endif %}
|
|
</p>
|
|
<small class="text-muted">{{ object.escalated_at|date:"M d, Y g:i A" }}</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Alert Resolved -->
|
|
{% if object.resolved_at %}
|
|
<div class="timeline-item">
|
|
<div class="timeline-marker bg-success">
|
|
<i class="fas fa-check-circle"></i>
|
|
</div>
|
|
<div class="timeline-content">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<h6 class="mb-1">Alert Resolved</h6>
|
|
<p class="text-muted mb-1">
|
|
{% if object.resolved_by %}
|
|
By {{ object.resolved_by.get_full_name }}
|
|
{% else %}
|
|
By System
|
|
{% endif %}
|
|
</p>
|
|
{% if object.resolution_note %}
|
|
<p class="mb-1">{{ object.resolution_note }}</p>
|
|
{% endif %}
|
|
<small class="text-muted">{{ object.resolved_at|date:"M d, Y g:i A" }}</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Related Alerts -->
|
|
{% if related_alerts %}
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-link me-2"></i>
|
|
Related Alerts
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-nowrap table-hover mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>Alert</th>
|
|
<th>Rule</th>
|
|
<th>Status</th>
|
|
<th>Triggered</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for alert in related_alerts %}
|
|
<tr>
|
|
<td>{{ alert.message|truncatechars:40 }}</td>
|
|
<td>{{ alert.alert_rule.rule_name|truncatechars:25 }}</td>
|
|
<td>
|
|
<span class="badge bg-{% if alert.status == 'RESOLVED' %}success{% elif alert.status == 'ACKNOWLEDGED' %}info{% elif alert.status == 'ESCALATED' %}warning{% elif alert.status == 'SNOOZED' %}secondary{% else %}danger{% endif %}">
|
|
{{ alert.get_status_display }}
|
|
</span>
|
|
</td>
|
|
<td>{{ alert.triggered_at|timesince }} ago</td>
|
|
<td>
|
|
<a href="{% url 'communications:alert_instance_detail' alert.pk %}" class="btn btn-sm btn-outline-primary">
|
|
<i class="fas fa-eye"></i>
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Sidebar -->
|
|
<div class="col-lg-4">
|
|
<!-- Quick Actions -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-bolt me-2"></i>
|
|
Quick Actions
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-grid gap-2">
|
|
{% if object.status == 'ACTIVE' %}
|
|
<button class="btn btn-success" onclick="acknowledgeAlert()">
|
|
<i class="fas fa-check me-1"></i>
|
|
Acknowledge Alert
|
|
</button>
|
|
<button class="btn btn-primary" onclick="resolveAlert()">
|
|
<i class="fas fa-check-circle me-1"></i>
|
|
Resolve Alert
|
|
</button>
|
|
<button class="btn btn-outline-warning" onclick="snoozeAlert()">
|
|
<i class="fas fa-clock me-1"></i>
|
|
Snooze Alert
|
|
</button>
|
|
<button class="btn btn-outline-danger" onclick="escalateAlert()">
|
|
<i class="fas fa-arrow-up me-1"></i>
|
|
Escalate Alert
|
|
</button>
|
|
{% elif object.status == 'ACKNOWLEDGED' %}
|
|
<button class="btn btn-primary" onclick="resolveAlert()">
|
|
<i class="fas fa-check-circle me-1"></i>
|
|
Resolve Alert
|
|
</button>
|
|
<button class="btn btn-outline-danger" onclick="escalateAlert()">
|
|
<i class="fas fa-arrow-up me-1"></i>
|
|
Escalate Alert
|
|
</button>
|
|
{% elif object.status == 'SNOOZED' %}
|
|
<button class="btn btn-outline-success" onclick="unsnoozeAlert()">
|
|
<i class="fas fa-play me-1"></i>
|
|
Unsnooze Alert
|
|
</button>
|
|
{% endif %}
|
|
|
|
<hr>
|
|
|
|
<button class="btn btn-outline-secondary" onclick="addComment()">
|
|
<i class="fas fa-comment me-1"></i>
|
|
Add Comment
|
|
</button>
|
|
<button class="btn btn-outline-info" onclick="exportAlert()">
|
|
<i class="fas fa-download me-1"></i>
|
|
Export Details
|
|
</button>
|
|
<button class="btn btn-outline-secondary" onclick="shareAlert()">
|
|
<i class="fas fa-share me-1"></i>
|
|
Share Alert
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Alert Statistics -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-chart-bar me-2"></i>
|
|
Alert Statistics
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-borderless table-sm">
|
|
<tr>
|
|
<td class="fw-bold text-muted">Response Time:</td>
|
|
<td>
|
|
{% if object.acknowledged_at %}
|
|
{{ object.response_time_minutes|default:0 }} min
|
|
{% else %}
|
|
<span class="text-muted">Pending</span>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold text-muted">Resolution Time:</td>
|
|
<td>
|
|
{% if object.resolved_at %}
|
|
{{ object.resolution_time_minutes|default:0 }} min
|
|
{% else %}
|
|
<span class="text-muted">Pending</span>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold text-muted">Age:</td>
|
|
<td>{{ object.triggered_at|timesince }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold text-muted">Notifications Sent:</td>
|
|
<td>{{ object.notifications_sent|default:0 }}</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Alert Rule Info -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-cog me-2"></i>
|
|
Alert Rule
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center mb-3">
|
|
<div class="avatar-sm me-3">
|
|
<div class="avatar-title bg-soft-primary text-primary rounded">
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<h6 class="mb-0">
|
|
<a href="{% url 'communications:alert_rule_detail' object.alert_rule.pk %}">
|
|
{{ object.alert_rule.rule_name }}
|
|
</a>
|
|
</h6>
|
|
<small class="text-muted">{{ object.alert_rule.get_category_display }}</small>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-responsive">
|
|
<table class="table table-borderless table-sm">
|
|
<tr>
|
|
<td class="fw-bold text-muted">Condition:</td>
|
|
<td><code>{{ object.alert_rule.condition_expression|truncatechars:30 }}</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold text-muted">Cooldown:</td>
|
|
<td>{{ object.alert_rule.cooldown_minutes|default:0 }} min</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold text-muted">Auto Resolve:</td>
|
|
<td>
|
|
{% if object.alert_rule.auto_resolve %}
|
|
<span class="text-success"><i class="fas fa-check"></i></span>
|
|
{% else %}
|
|
<span class="text-muted"><i class="fas fa-times"></i></span>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="mt-3">
|
|
<a href="{% url 'communications:alert_rule_detail' object.alert_rule.pk %}" class="btn btn-sm btn-outline-primary">
|
|
View Rule Details
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Similar Alerts -->
|
|
{% if similar_alerts %}
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-history me-2"></i>
|
|
Recent Similar Alerts
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
{% for alert in similar_alerts %}
|
|
<div class="d-flex align-items-center mb-2">
|
|
<div class="avatar-xs me-2">
|
|
<div class="avatar-title bg-soft-{% if alert.status == 'RESOLVED' %}success{% elif alert.status == 'ACKNOWLEDGED' %}info{% else %}danger{% endif %} text-{% if alert.status == 'RESOLVED' %}success{% elif alert.status == 'ACKNOWLEDGED' %}info{% else %}danger{% endif %} rounded">
|
|
<i class="fas fa-bell"></i>
|
|
</div>
|
|
</div>
|
|
<div class="flex-grow-1">
|
|
<h6 class="mb-0">
|
|
<a href="{% url 'communications:alert_instance_detail' alert.pk %}">
|
|
{{ alert.message|truncatechars:25 }}
|
|
</a>
|
|
</h6>
|
|
<small class="text-muted">{{ alert.triggered_at|timesince }} ago</small>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block css %}
|
|
<style>
|
|
.timeline {
|
|
position: relative;
|
|
padding-left: 30px;
|
|
}
|
|
|
|
.timeline::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: 15px;
|
|
top: 0;
|
|
bottom: 0;
|
|
width: 2px;
|
|
background: #dee2e6;
|
|
}
|
|
|
|
.timeline-item {
|
|
position: relative;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.timeline-marker {
|
|
position: absolute;
|
|
left: -22px;
|
|
width: 30px;
|
|
height: 30px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: white;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.timeline-content {
|
|
margin-left: 20px;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block js %}
|
|
<script>
|
|
function acknowledgeAlert() {
|
|
const note = prompt('Add an acknowledgment note (optional):');
|
|
updateAlertStatus('ACKNOWLEDGED', note);
|
|
}
|
|
|
|
function resolveAlert() {
|
|
const note = prompt('Add a resolution note (optional):');
|
|
updateAlertStatus('RESOLVED', note);
|
|
}
|
|
|
|
function snoozeAlert() {
|
|
const minutes = prompt('Snooze this alert for how many minutes?', '60');
|
|
if (minutes && !isNaN(minutes)) {
|
|
fetch(`{% url 'communications:alert_instance_detail' object.pk %}snooze/`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ minutes: parseInt(minutes) })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
location.reload();
|
|
} else {
|
|
alert('Error snoozing alert: ' + data.error);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function escalateAlert() {
|
|
if (confirm('Escalate this alert?')) {
|
|
updateAlertStatus('ESCALATED');
|
|
}
|
|
}
|
|
|
|
function unsnoozeAlert() {
|
|
if (confirm('Unsnooze this alert?')) {
|
|
updateAlertStatus('ACTIVE');
|
|
}
|
|
}
|
|
|
|
function updateAlertStatus(status, note = null) {
|
|
const data = { status: status };
|
|
if (note) {
|
|
data.note = note;
|
|
}
|
|
|
|
fetch(`{% url 'communications:alert_instance_detail' object.pk %}update-status/`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(data)
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
location.reload();
|
|
} else {
|
|
alert('Error updating alert: ' + data.error);
|
|
}
|
|
});
|
|
}
|
|
|
|
function addComment() {
|
|
const comment = prompt('Add a comment to this alert:');
|
|
if (comment && comment.trim()) {
|
|
fetch(`{% url 'communications:alert_instance_detail' object.pk %}add-comment/`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ comment: comment.trim() })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
alert('Comment added successfully');
|
|
location.reload();
|
|
} else {
|
|
alert('Error adding comment: ' + data.error);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function exportAlert() {
|
|
window.open(`{% url 'communications:alert_instance_detail' object.pk %}export/`, '_blank');
|
|
}
|
|
|
|
function shareAlert() {
|
|
const url = window.location.href;
|
|
if (navigator.share) {
|
|
navigator.share({
|
|
title: 'Alert: {{ object.message|escapejs }}',
|
|
text: 'Alert from {{ object.alert_rule.rule_name|escapejs }}',
|
|
url: url
|
|
});
|
|
} else {
|
|
// Fallback: copy to clipboard
|
|
navigator.clipboard.writeText(url).then(() => {
|
|
alert('Alert URL copied to clipboard');
|
|
});
|
|
}
|
|
}
|
|
</script>
|
|
{% endblock %}
|
|
|