813 lines
40 KiB
HTML
813 lines
40 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}Alert Instances - Communications{% 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 Instances</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 active">Alert Instances</li>
|
|
</ol>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Statistics Cards -->
|
|
<div class="row">
|
|
<div class="col-xl-3 col-md-6">
|
|
<div class="card card-h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-grow-1">
|
|
<span class="text-muted mb-3 lh-1 d-block text-truncate">Total Instances</span>
|
|
<h4 class="mb-3">
|
|
<span class="counter-value" data-target="{{ total_instances }}">{{ total_instances }}</span>
|
|
</h4>
|
|
</div>
|
|
<div class="flex-shrink-0">
|
|
<div class="avatar-sm rounded-circle bg-primary">
|
|
<span class="avatar-title rounded-circle bg-primary">
|
|
<i class="fas fa-bell text-white font-size-16"></i>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xl-3 col-md-6">
|
|
<div class="card card-h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-grow-1">
|
|
<span class="text-muted mb-3 lh-1 d-block text-truncate">Active Alerts</span>
|
|
<h4 class="mb-3">
|
|
<span class="counter-value" data-target="{{ active_instances }}">{{ active_instances }}</span>
|
|
</h4>
|
|
</div>
|
|
<div class="flex-shrink-0">
|
|
<div class="avatar-sm rounded-circle bg-danger">
|
|
<span class="avatar-title rounded-circle bg-danger">
|
|
<i class="fas fa-exclamation-triangle text-white font-size-16"></i>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xl-3 col-md-6">
|
|
<div class="card card-h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-grow-1">
|
|
<span class="text-muted mb-3 lh-1 d-block text-truncate">Critical Alerts</span>
|
|
<h4 class="mb-3">
|
|
<span class="counter-value" data-target="{{ critical_instances }}">{{ critical_instances }}</span>
|
|
</h4>
|
|
</div>
|
|
<div class="flex-shrink-0">
|
|
<div class="avatar-sm rounded-circle bg-warning">
|
|
<span class="avatar-title rounded-circle bg-warning">
|
|
<i class="fas fa-exclamation-circle text-white font-size-16"></i>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xl-3 col-md-6">
|
|
<div class="card card-h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-grow-1">
|
|
<span class="text-muted mb-3 lh-1 d-block text-truncate">Avg Response Time</span>
|
|
<h4 class="mb-3">
|
|
<span class="counter-value" data-target="{{ avg_response_time }}">{{ avg_response_time }}</span>
|
|
<small class="text-muted">min</small>
|
|
</h4>
|
|
</div>
|
|
<div class="flex-shrink-0">
|
|
<div class="avatar-sm rounded-circle bg-success">
|
|
<span class="avatar-title rounded-circle bg-success">
|
|
<i class="fas fa-clock text-white font-size-16"></i>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Alert Instances List -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<div class="row align-items-center">
|
|
<div class="col">
|
|
<h4 class="card-title">Alert Instances</h4>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="btn-group" role="group">
|
|
<button type="button" class="btn btn-outline-primary" onclick="acknowledgeSelected()">
|
|
<i class="fas fa-check me-1"></i>
|
|
Acknowledge Selected
|
|
</button>
|
|
<button type="button" class="btn btn-outline-success" onclick="resolveSelected()">
|
|
<i class="fas fa-check-circle me-1"></i>
|
|
Resolve Selected
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<!-- Search and Filters -->
|
|
<div class="row mb-3">
|
|
<div class="col-md-3">
|
|
<div class="search-box">
|
|
<div class="position-relative">
|
|
<input type="text" class="form-control search"
|
|
placeholder="Search alerts..."
|
|
id="alertSearch">
|
|
<i class="bx bx-search-alt search-icon"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<select class="form-select" id="statusFilter">
|
|
<option value="">All Status</option>
|
|
<option value="ACTIVE">Active</option>
|
|
<option value="ACKNOWLEDGED">Acknowledged</option>
|
|
<option value="RESOLVED">Resolved</option>
|
|
<option value="ESCALATED">Escalated</option>
|
|
<option value="SNOOZED">Snoozed</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<select class="form-select" id="severityFilter">
|
|
<option value="">All Severity</option>
|
|
<option value="CRITICAL">Critical</option>
|
|
<option value="HIGH">High</option>
|
|
<option value="MEDIUM">Medium</option>
|
|
<option value="LOW">Low</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<select class="form-select" id="categoryFilter">
|
|
<option value="">All Categories</option>
|
|
<option value="SYSTEM">System</option>
|
|
<option value="PATIENT">Patient</option>
|
|
<option value="EQUIPMENT">Equipment</option>
|
|
<option value="MEDICATION">Medication</option>
|
|
<option value="APPOINTMENT">Appointment</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<select class="form-select" id="timeFilter">
|
|
<option value="">All Time</option>
|
|
<option value="today">Today</option>
|
|
<option value="yesterday">Yesterday</option>
|
|
<option value="week">This Week</option>
|
|
<option value="month">This Month</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-1">
|
|
<div class="btn-group" role="group">
|
|
<button type="button" class="btn btn-outline-secondary" id="refreshAlerts">
|
|
<i class="fas fa-sync-alt"></i>
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
|
|
<i class="fas fa-download"></i>
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a class="dropdown-item" href="#" onclick="exportAlerts('csv')">CSV</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="exportAlerts('pdf')">PDF Report</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- View Toggle -->
|
|
<div class="row mb-3">
|
|
<div class="col-md-6">
|
|
<div class="btn-group btn-group-sm" role="group">
|
|
<input type="radio" class="btn-check" name="viewMode" id="tableView" checked>
|
|
<label class="btn btn-outline-secondary" for="tableView">
|
|
<i class="fas fa-table me-1"></i>Table
|
|
</label>
|
|
<input type="radio" class="btn-check" name="viewMode" id="cardView">
|
|
<label class="btn btn-outline-secondary" for="cardView">
|
|
<i class="fas fa-th-large me-1"></i>Cards
|
|
</label>
|
|
<input type="radio" class="btn-check" name="viewMode" id="timelineView">
|
|
<label class="btn btn-outline-secondary" for="timelineView">
|
|
<i class="fas fa-stream me-1"></i>Timeline
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6 text-end">
|
|
<small class="text-muted">
|
|
Auto-refresh: <span id="autoRefreshStatus">ON</span>
|
|
<button class="btn btn-sm btn-outline-secondary ms-2" onclick="toggleAutoRefresh()">
|
|
<i class="fas fa-pause" id="refreshIcon"></i>
|
|
</button>
|
|
</small>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Alert Instances Table -->
|
|
<div id="alertInstancesList">
|
|
<div class="table-responsive" id="tableViewContent">
|
|
<table class="table table-nowrap table-hover mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th scope="col">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="checkAll">
|
|
</div>
|
|
</th>
|
|
<th scope="col">Alert</th>
|
|
<th scope="col">Rule</th>
|
|
<th scope="col">Severity</th>
|
|
<th scope="col">Status</th>
|
|
<th scope="col">Triggered</th>
|
|
<th scope="col">Response Time</th>
|
|
<th scope="col">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for instance in object_list %}
|
|
<tr class="alert-row" data-severity="{{ instance.alert_rule.severity }}" data-status="{{ instance.status }}">
|
|
<td>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" value="{{ instance.id }}">
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="d-flex align-items-center">
|
|
<div class="flex-shrink-0 me-2">
|
|
<div class="avatar-xs">
|
|
<div class="avatar-title bg-soft-{% if instance.alert_rule.severity == 'CRITICAL' %}danger{% elif instance.alert_rule.severity == 'HIGH' %}warning{% elif instance.alert_rule.severity == 'MEDIUM' %}info{% else %}secondary{% endif %} text-{% if instance.alert_rule.severity == 'CRITICAL' %}danger{% elif instance.alert_rule.severity == 'HIGH' %}warning{% elif instance.alert_rule.severity == 'MEDIUM' %}info{% else %}secondary{% endif %} rounded-circle">
|
|
<i class="fas fa-{% if instance.alert_rule.severity == 'CRITICAL' %}exclamation-circle{% elif instance.alert_rule.severity == 'HIGH' %}exclamation-triangle{% else %}bell{% endif %}"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex-grow-1">
|
|
<h6 class="mb-0">
|
|
<a href="{% url 'communications:alert_instance_detail' instance.pk %}" class="text-dark">
|
|
{{ instance.message|truncatechars:40 }}
|
|
</a>
|
|
</h6>
|
|
<small class="text-muted">{{ instance.alert_rule.get_category_display }}</small>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<a href="{% url 'communications:alert_rule_detail' instance.alert_rule.pk %}" class="text-primary">
|
|
{{ instance.alert_rule.rule_name|truncatechars:25 }}
|
|
</a>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-{% if instance.alert_rule.severity == 'CRITICAL' %}danger{% elif instance.alert_rule.severity == 'HIGH' %}warning{% elif instance.alert_rule.severity == 'MEDIUM' %}info{% else %}secondary{% endif %}">
|
|
{{ instance.alert_rule.get_severity_display }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-{% if instance.status == 'RESOLVED' %}success{% elif instance.status == 'ACKNOWLEDGED' %}info{% elif instance.status == 'ESCALATED' %}warning{% elif instance.status == 'SNOOZED' %}secondary{% else %}danger{% endif %}">
|
|
{{ instance.get_status_display }}
|
|
</span>
|
|
{% if instance.status == 'SNOOZED' and instance.snoozed_until %}
|
|
<br><small class="text-muted">Until {{ instance.snoozed_until|date:"M d, g:i A" }}</small>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<span class="text-muted">{{ instance.triggered_at|timesince }} ago</span>
|
|
<br><small class="text-muted">{{ instance.triggered_at|date:"M d, g:i A" }}</small>
|
|
</td>
|
|
<td>
|
|
{% if instance.acknowledged_at %}
|
|
<span class="text-success">{{ instance.response_time_minutes|default:0 }} min</span>
|
|
{% else %}
|
|
<span class="text-muted">-</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div class="dropdown">
|
|
<a href="#" class="dropdown-toggle btn btn-light btn-sm" data-bs-toggle="dropdown">
|
|
<i class="fas fa-ellipsis-v"></i>
|
|
</a>
|
|
<div class="dropdown-menu dropdown-menu-end">
|
|
<a class="dropdown-item" href="{% url 'communications:alert_instance_detail' instance.pk %}">
|
|
<i class="fas fa-eye me-2"></i>View Details
|
|
</a>
|
|
{% if instance.status == 'ACTIVE' %}
|
|
<a class="dropdown-item" href="#" onclick="acknowledgeAlert({{ instance.id }})">
|
|
<i class="fas fa-check me-2"></i>Acknowledge
|
|
</a>
|
|
<a class="dropdown-item" href="#" onclick="resolveAlert({{ instance.id }})">
|
|
<i class="fas fa-check-circle me-2"></i>Resolve
|
|
</a>
|
|
<a class="dropdown-item" href="#" onclick="snoozeAlert({{ instance.id }})">
|
|
<i class="fas fa-clock me-2"></i>Snooze
|
|
</a>
|
|
<a class="dropdown-item" href="#" onclick="escalateAlert({{ instance.id }})">
|
|
<i class="fas fa-arrow-up me-2"></i>Escalate
|
|
</a>
|
|
{% elif instance.status == 'ACKNOWLEDGED' %}
|
|
<a class="dropdown-item" href="#" onclick="resolveAlert({{ instance.id }})">
|
|
<i class="fas fa-check-circle me-2"></i>Resolve
|
|
</a>
|
|
<a class="dropdown-item" href="#" onclick="escalateAlert({{ instance.id }})">
|
|
<i class="fas fa-arrow-up me-2"></i>Escalate
|
|
</a>
|
|
{% elif instance.status == 'SNOOZED' %}
|
|
<a class="dropdown-item" href="#" onclick="unsnoozeAlert({{ instance.id }})">
|
|
<i class="fas fa-play me-2"></i>Unsnooze
|
|
</a>
|
|
{% endif %}
|
|
<div class="dropdown-divider"></div>
|
|
<a class="dropdown-item" href="#" onclick="addComment({{ instance.id }})">
|
|
<i class="fas fa-comment me-2"></i>Add Comment
|
|
</a>
|
|
<a class="dropdown-item" href="#" onclick="viewHistory({{ instance.id }})">
|
|
<i class="fas fa-history me-2"></i>View History
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr>
|
|
<td colspan="8" class="text-center py-4">
|
|
<div class="d-flex flex-column align-items-center">
|
|
<i class="fas fa-bell-slash fa-3x text-muted mb-3"></i>
|
|
<h5 class="text-muted">No alert instances found</h5>
|
|
<p class="text-muted">No alerts match your current filters</p>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Card View (Hidden by default) -->
|
|
<div id="cardViewContent" style="display: none;">
|
|
<div class="row">
|
|
{% for instance in object_list %}
|
|
<div class="col-md-6 col-lg-4 mb-3">
|
|
<div class="card alert-card h-100 border-start border-4 border-{% if instance.alert_rule.severity == 'CRITICAL' %}danger{% elif instance.alert_rule.severity == 'HIGH' %}warning{% elif instance.alert_rule.severity == 'MEDIUM' %}info{% else %}secondary{% endif %}">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-start mb-2">
|
|
<span class="badge bg-{% if instance.status == 'RESOLVED' %}success{% elif instance.status == 'ACKNOWLEDGED' %}info{% elif instance.status == 'ESCALATED' %}warning{% elif instance.status == 'SNOOZED' %}secondary{% else %}danger{% endif %}">
|
|
{{ instance.get_status_display }}
|
|
</span>
|
|
<span class="badge bg-{% if instance.alert_rule.severity == 'CRITICAL' %}danger{% elif instance.alert_rule.severity == 'HIGH' %}warning{% elif instance.alert_rule.severity == 'MEDIUM' %}info{% else %}secondary{% endif %}">
|
|
{{ instance.alert_rule.get_severity_display }}
|
|
</span>
|
|
</div>
|
|
<h6 class="card-title">{{ instance.message|truncatechars:50 }}</h6>
|
|
<p class="card-text text-muted">{{ instance.alert_rule.rule_name }}</p>
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<small class="text-muted">{{ instance.triggered_at|timesince }} ago</small>
|
|
<a href="{% url 'communications:alert_instance_detail' instance.pk %}" class="btn btn-sm btn-outline-primary">
|
|
View
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Timeline View (Hidden by default) -->
|
|
<div id="timelineViewContent" style="display: none;">
|
|
<div class="timeline">
|
|
{% for instance in object_list %}
|
|
<div class="timeline-item">
|
|
<div class="timeline-marker bg-{% if instance.alert_rule.severity == 'CRITICAL' %}danger{% elif instance.alert_rule.severity == 'HIGH' %}warning{% elif instance.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">
|
|
<div class="d-flex justify-content-between align-items-start">
|
|
<div>
|
|
<h6 class="mb-1">{{ instance.message }}</h6>
|
|
<p class="text-muted mb-1">{{ instance.alert_rule.rule_name }}</p>
|
|
<small class="text-muted">{{ instance.triggered_at|date:"M d, Y g:i A" }}</small>
|
|
</div>
|
|
<div class="text-end">
|
|
<span class="badge bg-{% if instance.status == 'RESOLVED' %}success{% elif instance.status == 'ACKNOWLEDGED' %}info{% elif instance.status == 'ESCALATED' %}warning{% elif instance.status == 'SNOOZED' %}secondary{% else %}danger{% endif %}">
|
|
{{ instance.get_status_display }}
|
|
</span>
|
|
<br>
|
|
<span class="badge bg-{% if instance.alert_rule.severity == 'CRITICAL' %}danger{% elif instance.alert_rule.severity == 'HIGH' %}warning{% elif instance.alert_rule.severity == 'MEDIUM' %}info{% else %}secondary{% endif %} mt-1">
|
|
{{ instance.alert_rule.get_severity_display }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
{% if is_paginated %}
|
|
<div class="row">
|
|
<div class="col-lg-12">
|
|
<ul class="pagination pagination-rounded justify-content-center mt-3 mb-4 pb-1">
|
|
{% if page_obj.has_previous %}
|
|
<li class="page-item">
|
|
<a href="?page={{ page_obj.previous_page_number }}" class="page-link">
|
|
<i class="mdi mdi-chevron-left"></i>
|
|
</a>
|
|
</li>
|
|
{% endif %}
|
|
|
|
{% for num in page_obj.paginator.page_range %}
|
|
{% if page_obj.number == num %}
|
|
<li class="page-item active">
|
|
<span class="page-link">{{ num }}</span>
|
|
</li>
|
|
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
|
<li class="page-item">
|
|
<a href="?page={{ num }}" class="page-link">{{ num }}</a>
|
|
</li>
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
{% if page_obj.has_next %}
|
|
<li class="page-item">
|
|
<a href="?page={{ page_obj.next_page_number }}" class="page-link">
|
|
<i class="mdi mdi-chevron-right"></i>
|
|
</a>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</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;
|
|
}
|
|
|
|
.alert-row.table-danger {
|
|
background-color: rgba(220, 53, 69, 0.1);
|
|
}
|
|
|
|
.alert-row.table-warning {
|
|
background-color: rgba(255, 193, 7, 0.1);
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block js %}
|
|
<script>
|
|
let autoRefresh = true;
|
|
let refreshInterval;
|
|
|
|
// View mode switching
|
|
document.querySelectorAll('input[name="viewMode"]').forEach(radio => {
|
|
radio.addEventListener('change', function() {
|
|
const tableView = document.getElementById('tableViewContent');
|
|
const cardView = document.getElementById('cardViewContent');
|
|
const timelineView = document.getElementById('timelineViewContent');
|
|
|
|
// Hide all views
|
|
tableView.style.display = 'none';
|
|
cardView.style.display = 'none';
|
|
timelineView.style.display = 'none';
|
|
|
|
// Show selected view
|
|
if (this.id === 'tableView') {
|
|
tableView.style.display = 'block';
|
|
} else if (this.id === 'cardView') {
|
|
cardView.style.display = 'block';
|
|
} else if (this.id === 'timelineView') {
|
|
timelineView.style.display = 'block';
|
|
}
|
|
});
|
|
});
|
|
|
|
// Search functionality
|
|
document.getElementById('alertSearch').addEventListener('input', function() {
|
|
const searchTerm = this.value.toLowerCase();
|
|
filterAlerts();
|
|
});
|
|
|
|
// Filter functionality
|
|
document.getElementById('statusFilter').addEventListener('change', filterAlerts);
|
|
document.getElementById('severityFilter').addEventListener('change', filterAlerts);
|
|
document.getElementById('categoryFilter').addEventListener('change', filterAlerts);
|
|
document.getElementById('timeFilter').addEventListener('change', filterAlerts);
|
|
|
|
function filterAlerts() {
|
|
const searchTerm = document.getElementById('alertSearch').value.toLowerCase();
|
|
const status = document.getElementById('statusFilter').value;
|
|
const severity = document.getElementById('severityFilter').value;
|
|
const category = document.getElementById('categoryFilter').value;
|
|
const timeFilter = document.getElementById('timeFilter').value;
|
|
|
|
const rows = document.querySelectorAll('.alert-row');
|
|
|
|
rows.forEach(row => {
|
|
let show = true;
|
|
const text = row.textContent.toLowerCase();
|
|
|
|
// Search filter
|
|
if (searchTerm && !text.includes(searchTerm)) {
|
|
show = false;
|
|
}
|
|
|
|
// Status filter
|
|
if (status) {
|
|
const statusBadge = row.querySelector('td:nth-child(5) .badge');
|
|
show = show && statusBadge && statusBadge.textContent.trim().toLowerCase().includes(status.toLowerCase());
|
|
}
|
|
|
|
// Severity filter
|
|
if (severity) {
|
|
show = show && row.dataset.severity === severity;
|
|
}
|
|
|
|
// Category filter
|
|
if (category) {
|
|
const categoryText = row.querySelector('td:nth-child(2) small');
|
|
show = show && categoryText && categoryText.textContent.trim().toLowerCase().includes(category.toLowerCase());
|
|
}
|
|
|
|
row.style.display = show ? '' : 'none';
|
|
});
|
|
}
|
|
|
|
// Alert actions
|
|
function acknowledgeAlert(instanceId) {
|
|
if (confirm('Acknowledge this alert?')) {
|
|
updateAlertStatus(instanceId, 'ACKNOWLEDGED');
|
|
}
|
|
}
|
|
|
|
function resolveAlert(instanceId) {
|
|
if (confirm('Resolve this alert?')) {
|
|
updateAlertStatus(instanceId, 'RESOLVED');
|
|
}
|
|
}
|
|
|
|
function snoozeAlert(instanceId) {
|
|
const minutes = prompt('Snooze this alert for how many minutes?', '60');
|
|
if (minutes && !isNaN(minutes)) {
|
|
fetch(`/communications/alert-instances/${instanceId}/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(instanceId) {
|
|
if (confirm('Escalate this alert?')) {
|
|
updateAlertStatus(instanceId, 'ESCALATED');
|
|
}
|
|
}
|
|
|
|
function unsnoozeAlert(instanceId) {
|
|
if (confirm('Unsnooze this alert?')) {
|
|
updateAlertStatus(instanceId, 'ACTIVE');
|
|
}
|
|
}
|
|
|
|
function updateAlertStatus(instanceId, status) {
|
|
fetch(`/communications/alert-instances/${instanceId}/update-status/`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ status: status })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
location.reload();
|
|
} else {
|
|
alert('Error updating alert: ' + data.error);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Bulk actions
|
|
function acknowledgeSelected() {
|
|
const selected = getSelectedAlerts();
|
|
if (selected.length === 0) {
|
|
alert('Please select at least one alert.');
|
|
return;
|
|
}
|
|
|
|
if (confirm(`Acknowledge ${selected.length} selected alert(s)?`)) {
|
|
bulkUpdateStatus(selected, 'ACKNOWLEDGED');
|
|
}
|
|
}
|
|
|
|
function resolveSelected() {
|
|
const selected = getSelectedAlerts();
|
|
if (selected.length === 0) {
|
|
alert('Please select at least one alert.');
|
|
return;
|
|
}
|
|
|
|
if (confirm(`Resolve ${selected.length} selected alert(s)?`)) {
|
|
bulkUpdateStatus(selected, 'RESOLVED');
|
|
}
|
|
}
|
|
|
|
function getSelectedAlerts() {
|
|
return Array.from(document.querySelectorAll('tbody input[type="checkbox"]:checked'))
|
|
.map(cb => cb.value);
|
|
}
|
|
|
|
function bulkUpdateStatus(alertIds, status) {
|
|
fetch('/communications/alert-instances/bulk-update-status/', {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ alert_ids: alertIds, status: status })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
location.reload();
|
|
} else {
|
|
alert('Error updating alerts: ' + data.error);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Auto-refresh functionality
|
|
function toggleAutoRefresh() {
|
|
autoRefresh = !autoRefresh;
|
|
const status = document.getElementById('autoRefreshStatus');
|
|
const icon = document.getElementById('refreshIcon');
|
|
|
|
if (autoRefresh) {
|
|
status.textContent = 'ON';
|
|
icon.className = 'fas fa-pause';
|
|
startAutoRefresh();
|
|
} else {
|
|
status.textContent = 'OFF';
|
|
icon.className = 'fas fa-play';
|
|
stopAutoRefresh();
|
|
}
|
|
}
|
|
|
|
function startAutoRefresh() {
|
|
refreshInterval = setInterval(() => {
|
|
if (autoRefresh) {
|
|
location.reload();
|
|
}
|
|
}, 30000); // Refresh every 30 seconds
|
|
}
|
|
|
|
function stopAutoRefresh() {
|
|
if (refreshInterval) {
|
|
clearInterval(refreshInterval);
|
|
}
|
|
}
|
|
|
|
// Export functionality
|
|
function exportAlerts(format) {
|
|
const url = `/communications/export/alert-instances/?format=${format}`;
|
|
window.open(url, '_blank');
|
|
}
|
|
|
|
// Check all functionality
|
|
document.getElementById('checkAll').addEventListener('change', function() {
|
|
const checkboxes = document.querySelectorAll('tbody input[type="checkbox"]');
|
|
checkboxes.forEach(checkbox => {
|
|
checkbox.checked = this.checked;
|
|
});
|
|
});
|
|
|
|
// Refresh alerts
|
|
document.getElementById('refreshAlerts').addEventListener('click', function() {
|
|
location.reload();
|
|
});
|
|
|
|
// Additional actions
|
|
function addComment(instanceId) {
|
|
const comment = prompt('Add a comment to this alert:');
|
|
if (comment && comment.trim()) {
|
|
fetch(`/communications/alert-instances/${instanceId}/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');
|
|
} else {
|
|
alert('Error adding comment: ' + data.error);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function viewHistory(instanceId) {
|
|
window.open(`/communications/alert-instances/${instanceId}/history/`, '_blank');
|
|
}
|
|
|
|
// Initialize
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
if (autoRefresh) {
|
|
startAutoRefresh();
|
|
}
|
|
});
|
|
|
|
// Cleanup
|
|
window.addEventListener('beforeunload', function() {
|
|
stopAutoRefresh();
|
|
});
|
|
</script>
|
|
{% endblock %}
|
|
|