2025-08-12 13:33:25 +03:00

509 lines
26 KiB
HTML

{% extends "base.html" %}
{% load static %}
{% block title %}Alert Rules - 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 Rules</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 Rules</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 Rules</span>
<h4 class="mb-3">
<span class="counter-value" data-target="{{ total_rules }}">{{ total_rules }}</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-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">Active Rules</span>
<h4 class="mb-3">
<span class="counter-value" data-target="{{ active_rules }}">{{ active_rules }}</span>
</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-check-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">Triggered Today</span>
<h4 class="mb-3">
<span class="counter-value" data-target="{{ triggered_today }}">{{ triggered_today }}</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-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">Critical Alerts</span>
<h4 class="mb-3">
<span class="counter-value" data-target="{{ critical_alerts }}">{{ critical_alerts }}</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-circle text-white font-size-16"></i>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Alert Rules 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 Rules</h4>
</div>
<div class="col-auto">
<a href="{% url 'communications:alert_rule_create' %}" class="btn btn-primary">
<i class="fas fa-plus me-1"></i>
Create Alert Rule
</a>
</div>
</div>
</div>
<div class="card-body">
<!-- Search and Filters -->
<div class="row mb-3">
<div class="col-md-4">
<div class="search-box">
<div class="position-relative">
<input type="text" class="form-control search"
placeholder="Search alert rules..."
id="alertRuleSearch">
<i class="bx bx-search-alt search-icon"></i>
</div>
</div>
</div>
<div class="col-md-2">
<select class="form-select" id="severityFilter">
<option value="">All Severity</option>
<option value="LOW">Low</option>
<option value="MEDIUM">Medium</option>
<option value="HIGH">High</option>
<option value="CRITICAL">Critical</option>
</select>
</div>
<div class="col-md-2">
<select class="form-select" id="statusFilter">
<option value="">All Status</option>
<option value="true">Active</option>
<option value="false">Inactive</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">
<div class="btn-group" role="group">
<button type="button" class="btn btn-outline-secondary" id="refreshRules">
<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="exportRules('csv')">CSV</a></li>
<li><a class="dropdown-item" href="#" onclick="exportRules('json')">JSON</a></li>
</ul>
</div>
</div>
</div>
<!-- Alert Rules Table -->
<div id="alertRulesList">
<div class="table-responsive">
<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">Rule Name</th>
<th scope="col">Category</th>
<th scope="col">Severity</th>
<th scope="col">Condition</th>
<th scope="col">Status</th>
<th scope="col">Last Triggered</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
{% for rule in object_list %}
<tr>
<td>
<div class="form-check">
<input class="form-check-input" type="checkbox" value="{{ rule.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 rule.severity == 'CRITICAL' %}danger{% elif rule.severity == 'HIGH' %}warning{% elif rule.severity == 'MEDIUM' %}info{% else %}secondary{% endif %} text-{% if rule.severity == 'CRITICAL' %}danger{% elif rule.severity == 'HIGH' %}warning{% elif rule.severity == 'MEDIUM' %}info{% else %}secondary{% endif %} rounded-circle">
<i class="fas fa-{% if rule.severity == 'CRITICAL' %}exclamation-circle{% elif 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_rule_detail' rule.pk %}" class="text-dark">
{{ rule.rule_name }}
</a>
</h6>
<small class="text-muted">{{ rule.description|truncatechars:60 }}</small>
</div>
</div>
</td>
<td>
<span class="badge bg-{% if rule.category == 'SYSTEM' %}primary{% elif rule.category == 'PATIENT' %}success{% elif rule.category == 'EQUIPMENT' %}warning{% elif rule.category == 'MEDICATION' %}info{% else %}secondary{% endif %}">
{{ rule.get_category_display }}
</span>
</td>
<td>
<span class="badge bg-{% if rule.severity == 'CRITICAL' %}danger{% elif rule.severity == 'HIGH' %}warning{% elif rule.severity == 'MEDIUM' %}info{% else %}secondary{% endif %}">
{{ rule.get_severity_display }}
</span>
</td>
<td>
<code class="text-muted">{{ rule.condition_expression|truncatechars:30 }}</code>
</td>
<td>
<div class="d-flex align-items-center">
<span class="badge bg-{% if rule.is_active %}success{% else %}secondary{% endif %} me-2">
{% if rule.is_active %}Active{% else %}Inactive{% endif %}
</span>
{% if rule.is_active %}
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" checked
onchange="toggleRule({{ rule.id }}, this.checked)">
</div>
{% endif %}
</div>
</td>
<td>
{% if rule.last_triggered %}
<span class="text-muted">{{ rule.last_triggered|timesince }} ago</span>
{% else %}
<span class="text-muted">Never</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_rule_detail' rule.pk %}">
<i class="fas fa-eye me-2"></i>View Details
</a>
<a class="dropdown-item" href="{% url 'communications:alert_rule_update' rule.pk %}">
<i class="fas fa-edit me-2"></i>Edit Rule
</a>
<a class="dropdown-item" href="#" onclick="testRule({{ rule.id }})">
<i class="fas fa-flask me-2"></i>Test Rule
</a>
<a class="dropdown-item" href="#" onclick="duplicateRule({{ rule.id }})">
<i class="fas fa-copy me-2"></i>Duplicate
</a>
<div class="dropdown-divider"></div>
{% if rule.is_active %}
<a class="dropdown-item text-warning" href="#" onclick="toggleRule({{ rule.id }}, false)">
<i class="fas fa-pause me-2"></i>Disable
</a>
{% else %}
<a class="dropdown-item text-success" href="#" onclick="toggleRule({{ rule.id }}, true)">
<i class="fas fa-play me-2"></i>Enable
</a>
{% endif %}
<a class="dropdown-item text-danger" href="{% url 'communications:alert_rule_delete' rule.pk %}">
<i class="fas fa-trash me-2"></i>Delete
</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-exclamation-triangle fa-3x text-muted mb-3"></i>
<h5 class="text-muted">No alert rules found</h5>
<p class="text-muted">Create your first alert rule to monitor system events</p>
<a href="{% url 'communications:alert_rule_create' %}" class="btn btn-primary">
<i class="fas fa-plus me-1"></i>Create Alert Rule
</a>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</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 js %}
<script>
// Search functionality
document.getElementById('alertRuleSearch').addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
const rows = document.querySelectorAll('tbody tr');
rows.forEach(row => {
const text = row.textContent.toLowerCase();
row.style.display = text.includes(searchTerm) ? '' : 'none';
});
});
// Filter functionality
document.getElementById('severityFilter').addEventListener('change', filterRules);
document.getElementById('statusFilter').addEventListener('change', filterRules);
document.getElementById('categoryFilter').addEventListener('change', filterRules);
function filterRules() {
const severity = document.getElementById('severityFilter').value;
const status = document.getElementById('statusFilter').value;
const category = document.getElementById('categoryFilter').value;
const rows = document.querySelectorAll('tbody tr');
rows.forEach(row => {
let show = true;
if (severity) {
const severityBadge = row.querySelector('td:nth-child(4) .badge');
show = show && severityBadge && severityBadge.textContent.trim().toLowerCase().includes(severity.toLowerCase());
}
if (status !== '') {
const statusBadge = row.querySelector('td:nth-child(6) .badge');
const isActive = statusBadge && statusBadge.textContent.includes('Active');
show = show && ((status === 'true' && isActive) || (status === 'false' && !isActive));
}
if (category) {
const categoryBadge = row.querySelector('td:nth-child(3) .badge');
show = show && categoryBadge && categoryBadge.textContent.trim().toLowerCase().includes(category.toLowerCase());
}
row.style.display = show ? '' : 'none';
});
}
// Rule actions
function toggleRule(ruleId, activate) {
const action = activate ? 'enable' : 'disable';
fetch(`/communications/alert-rules/${ruleId}/toggle/`, {
method: 'POST',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
'Content-Type': 'application/json',
},
body: JSON.stringify({ active: activate })
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert(`Error ${action}ing rule: ` + data.error);
}
});
}
function testRule(ruleId) {
if (confirm('Test this alert rule with current system data?')) {
fetch(`/communications/alert-rules/${ruleId}/test/`, {
method: 'POST',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert(`Test completed!\n\nResult: ${data.result}\nCondition met: ${data.condition_met ? 'Yes' : 'No'}`);
} else {
alert('Error testing rule: ' + data.error);
}
});
}
}
function duplicateRule(ruleId) {
if (confirm('Create a copy of this alert rule?')) {
window.location.href = `/communications/alert-rules/create/?duplicate=${ruleId}`;
}
}
function exportRules(format) {
const url = `/communications/export/alert-rules/?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 rules
document.getElementById('refreshRules').addEventListener('click', function() {
location.reload();
});
// Bulk actions
function bulkAction(action) {
const selected = Array.from(document.querySelectorAll('tbody input[type="checkbox"]:checked'))
.map(cb => cb.value);
if (selected.length === 0) {
alert('Please select at least one alert rule.');
return;
}
if (confirm(`${action} ${selected.length} selected rule(s)?`)) {
fetch(`/communications/alert-rules/bulk-${action}/`, {
method: 'POST',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
'Content-Type': 'application/json',
},
body: JSON.stringify({ rule_ids: selected })
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert(`Error performing bulk ${action}: ` + data.error);
}
});
}
}
</script>
{% endblock %}