521 lines
22 KiB
HTML
521 lines
22 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}Communications Dashboard - {{ block.super }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid">
|
|
<!-- Page Header -->
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<div>
|
|
<h1 class="h3 mb-1">Communications Dashboard</h1>
|
|
<nav aria-label="breadcrumb">
|
|
<ol class="breadcrumb mb-0">
|
|
<li class="breadcrumb-item"><a href="{% url 'accounts:dashboard' %}">Dashboard</a></li>
|
|
<li class="breadcrumb-item active">Communications</li>
|
|
</ol>
|
|
</nav>
|
|
</div>
|
|
<div class="btn-group">
|
|
<a href="{% url 'communications:message_compose' %}" class="btn btn-primary">
|
|
<i class="fas fa-plus me-2"></i>Compose Message
|
|
</a>
|
|
<button type="button" class="btn btn-outline-primary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown">
|
|
<span class="visually-hidden">Toggle Dropdown</span>
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a class="dropdown-item" href="{% url 'communications:broadcast_create' %}">
|
|
<i class="fas fa-bullhorn me-2"></i>Create Broadcast
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="{% url 'communications:template_create' %}">
|
|
<i class="fas fa-file-alt me-2"></i>Create Template
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="{% url 'communications:alert_rule_create' %}">
|
|
<i class="fas fa-exclamation-triangle me-2"></i>Create Alert Rule
|
|
</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Stats -->
|
|
<div class="row mb-4" hx-get="{% url 'communications:dashboard_stats' %}" hx-trigger="load, every 30s">
|
|
<div class="col-xl-3 col-md-6 mb-3">
|
|
<div class="card bg-gradient-primary text-white h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<div class="text-white-75 small">Unread Messages</div>
|
|
<div class="h4 mb-0" id="unread-messages">{{ stats.unread_messages|default:0 }}</div>
|
|
</div>
|
|
<div class="text-white-50">
|
|
<i class="fas fa-envelope fa-2x"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-footer d-flex align-items-center justify-content-between small">
|
|
<a class="text-white stretched-link" href="{% url 'communications:message_inbox' %}">View Inbox</a>
|
|
<div class="text-white"><i class="fas fa-angle-right"></i></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-xl-3 col-md-6 mb-3">
|
|
<div class="card bg-gradient-success text-white h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<div class="text-white-75 small">Messages Sent Today</div>
|
|
<div class="h4 mb-0" id="messages-sent-today">{{ stats.messages_sent_today|default:0 }}</div>
|
|
</div>
|
|
<div class="text-white-50">
|
|
<i class="fas fa-paper-plane fa-2x"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-footer d-flex align-items-center justify-content-between small">
|
|
<a class="text-white stretched-link" href="{% url 'communications:message_sent' %}">View Sent</a>
|
|
<div class="text-white"><i class="fas fa-angle-right"></i></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-xl-3 col-md-6 mb-3">
|
|
<div class="card bg-gradient-warning text-white h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<div class="text-white-75 small">Active Alerts</div>
|
|
<div class="h4 mb-0" id="active-alerts">{{ stats.active_alerts|default:0 }}</div>
|
|
</div>
|
|
<div class="text-white-50">
|
|
<i class="fas fa-exclamation-triangle fa-2x"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-footer d-flex align-items-center justify-content-between small">
|
|
<a class="text-white stretched-link" href="{% url 'communications:alert_list' %}">View Alerts</a>
|
|
<div class="text-white"><i class="fas fa-angle-right"></i></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-xl-3 col-md-6 mb-3">
|
|
<div class="card bg-gradient-info text-white h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<div class="text-white-75 small">Pending Notifications</div>
|
|
<div class="h4 mb-0" id="pending-notifications">{{ stats.pending_notifications|default:0 }}</div>
|
|
</div>
|
|
<div class="text-white-50">
|
|
<i class="fas fa-bell fa-2x"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-footer d-flex align-items-center justify-content-between small">
|
|
<a class="text-white stretched-link" href="{% url 'communications:notification_list' %}">View Notifications</a>
|
|
<div class="text-white"><i class="fas fa-angle-right"></i></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<!-- Recent Messages -->
|
|
<div class="col-lg-6 mb-4">
|
|
<div class="card h-100">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-inbox me-2"></i>Recent Messages
|
|
</h5>
|
|
<div class="btn-group btn-group-sm">
|
|
<button type="button" class="btn btn-outline-primary" hx-get="{% url 'communications:recent_messages' %}" hx-target="#recent-messages-list">
|
|
<i class="fas fa-sync-alt"></i>
|
|
</button>
|
|
<a href="{% url 'communications:message_inbox' %}" class="btn btn-outline-primary">
|
|
<i class="fas fa-external-link-alt"></i>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div id="recent-messages-list" hx-get="{% url 'communications:recent_messages' %}" hx-trigger="load">
|
|
<div class="text-center p-4">
|
|
<div class="spinner-border text-primary" role="status">
|
|
<span class="visually-hidden">Loading...</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Communication Activity -->
|
|
<div class="col-lg-6 mb-4">
|
|
<div class="card h-100">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-chart-line me-2"></i>Communication Activity
|
|
</h5>
|
|
<div class="btn-group btn-group-sm">
|
|
<button type="button" class="btn btn-outline-primary" onclick="refreshActivityChart()">
|
|
<i class="fas fa-sync-alt"></i>
|
|
</button>
|
|
<div class="dropdown">
|
|
<button class="btn btn-outline-primary dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
|
<i class="fas fa-calendar"></i>
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a class="dropdown-item" href="#" onclick="updateActivityPeriod('7d')">Last 7 Days</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="updateActivityPeriod('30d')">Last 30 Days</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="updateActivityPeriod('90d')">Last 90 Days</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<canvas id="activityChart" width="100%" height="50"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<!-- Active Alerts -->
|
|
<div class="col-lg-8 mb-4">
|
|
<div class="card h-100">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-exclamation-triangle me-2"></i>Active Alerts
|
|
</h5>
|
|
<div class="btn-group btn-group-sm">
|
|
<button type="button" class="btn btn-outline-primary" hx-get="{% url 'communications:active_alerts' %}" hx-target="#active-alerts-list">
|
|
<i class="fas fa-sync-alt"></i>
|
|
</button>
|
|
<a href="{% url 'communications:alert_list' %}" class="btn btn-outline-primary">
|
|
<i class="fas fa-external-link-alt"></i>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div id="active-alerts-list" hx-get="{% url 'communications:active_alerts' %}" hx-trigger="load">
|
|
<div class="text-center p-4">
|
|
<div class="spinner-border text-warning" role="status">
|
|
<span class="visually-hidden">Loading...</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Actions -->
|
|
<div class="col-lg-4 mb-4">
|
|
<div class="card h-100">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-bolt me-2"></i>Quick Actions
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-grid gap-2">
|
|
<a href="{% url 'communications:message_compose' %}" class="btn btn-outline-primary">
|
|
<i class="fas fa-edit me-2"></i>Compose Message
|
|
</a>
|
|
<a href="{% url 'communications:broadcast_create' %}" class="btn btn-outline-success">
|
|
<i class="fas fa-bullhorn me-2"></i>Send Broadcast
|
|
</a>
|
|
<a href="{% url 'communications:template_list' %}" class="btn btn-outline-info">
|
|
<i class="fas fa-file-alt me-2"></i>Manage Templates
|
|
</a>
|
|
<a href="{% url 'communications:notification_settings' %}" class="btn btn-outline-warning">
|
|
<i class="fas fa-cog me-2"></i>Notification Settings
|
|
</a>
|
|
<a href="{% url 'communications:communication_log' %}" class="btn btn-outline-secondary">
|
|
<i class="fas fa-history me-2"></i>Communication Log
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Message Types Distribution -->
|
|
<div class="row">
|
|
<div class="col-lg-6 mb-4">
|
|
<div class="card h-100">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-chart-pie me-2"></i>Message Types Distribution
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<canvas id="messageTypesChart" width="100%" height="50"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Delivery Status Overview -->
|
|
<div class="col-lg-6 mb-4">
|
|
<div class="card h-100">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-chart-bar me-2"></i>Delivery Status Overview
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<canvas id="deliveryStatusChart" width="100%" height="50"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- System Status -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-server me-2"></i>Communication System Status
|
|
</h5>
|
|
<div class="btn-group btn-group-sm">
|
|
<button type="button" class="btn btn-outline-primary" hx-get="{% url 'communications:system_status' %}" hx-target="#system-status-content">
|
|
<i class="fas fa-sync-alt"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="system-status-content" hx-get="{% url 'communications:system_status' %}" hx-trigger="load, every 60s">
|
|
<div class="text-center p-4">
|
|
<div class="spinner-border text-info" role="status">
|
|
<span class="visually-hidden">Loading...</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Chart.js configurations
|
|
let activityChart, messageTypesChart, deliveryStatusChart;
|
|
|
|
// Initialize charts when page loads
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
initializeCharts();
|
|
|
|
// Auto-refresh dashboard every 5 minutes
|
|
setInterval(function() {
|
|
refreshDashboard();
|
|
}, 300000);
|
|
});
|
|
|
|
function initializeCharts() {
|
|
// Activity Chart
|
|
const activityCtx = document.getElementById('activityChart').getContext('2d');
|
|
activityChart = new Chart(activityCtx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: [],
|
|
datasets: [{
|
|
label: 'Messages Sent',
|
|
data: [],
|
|
borderColor: 'rgb(75, 192, 192)',
|
|
backgroundColor: 'rgba(75, 192, 192, 0.1)',
|
|
tension: 0.1
|
|
}, {
|
|
label: 'Messages Received',
|
|
data: [],
|
|
borderColor: 'rgb(255, 99, 132)',
|
|
backgroundColor: 'rgba(255, 99, 132, 0.1)',
|
|
tension: 0.1
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Message Types Chart
|
|
const messageTypesCtx = document.getElementById('messageTypesChart').getContext('2d');
|
|
messageTypesChart = new Chart(messageTypesCtx, {
|
|
type: 'doughnut',
|
|
data: {
|
|
labels: ['Email', 'SMS', 'Internal', 'Push', 'Slack'],
|
|
datasets: [{
|
|
data: [0, 0, 0, 0, 0],
|
|
backgroundColor: [
|
|
'#FF6384',
|
|
'#36A2EB',
|
|
'#FFCE56',
|
|
'#4BC0C0',
|
|
'#9966FF'
|
|
]
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false
|
|
}
|
|
});
|
|
|
|
// Delivery Status Chart
|
|
const deliveryStatusCtx = document.getElementById('deliveryStatusChart').getContext('2d');
|
|
deliveryStatusChart = new Chart(deliveryStatusCtx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: ['Sent', 'Delivered', 'Read', 'Failed'],
|
|
datasets: [{
|
|
label: 'Count',
|
|
data: [0, 0, 0, 0],
|
|
backgroundColor: [
|
|
'#28a745',
|
|
'#17a2b8',
|
|
'#6f42c1',
|
|
'#dc3545'
|
|
]
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Load chart data
|
|
loadChartData();
|
|
}
|
|
|
|
function loadChartData() {
|
|
// Load activity chart data
|
|
fetch('{% url "communications:activity_chart_data" %}')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
activityChart.data.labels = data.labels;
|
|
activityChart.data.datasets[0].data = data.sent;
|
|
activityChart.data.datasets[1].data = data.received;
|
|
activityChart.update();
|
|
})
|
|
.catch(error => console.error('Error loading activity chart data:', error));
|
|
|
|
// Load message types chart data
|
|
fetch('{% url "communications:message_types_chart_data" %}')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
messageTypesChart.data.labels = data.labels;
|
|
messageTypesChart.data.datasets[0].data = data.data;
|
|
messageTypesChart.update();
|
|
})
|
|
.catch(error => console.error('Error loading message types chart data:', error));
|
|
|
|
// Load delivery status chart data
|
|
fetch('{% url "communications:delivery_status_chart_data" %}')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
deliveryStatusChart.data.labels = data.labels;
|
|
deliveryStatusChart.data.datasets[0].data = data.data;
|
|
deliveryStatusChart.update();
|
|
})
|
|
.catch(error => console.error('Error loading delivery status chart data:', error));
|
|
}
|
|
|
|
function refreshActivityChart() {
|
|
loadChartData();
|
|
}
|
|
|
|
function updateActivityPeriod(period) {
|
|
fetch(`{% url "communications:activity_chart_data" %}?period=${period}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
activityChart.data.labels = data.labels;
|
|
activityChart.data.datasets[0].data = data.sent;
|
|
activityChart.data.datasets[1].data = data.received;
|
|
activityChart.update();
|
|
})
|
|
.catch(error => console.error('Error updating activity chart:', error));
|
|
}
|
|
|
|
function refreshDashboard() {
|
|
// Trigger HTMX refresh for dynamic content
|
|
htmx.trigger('#recent-messages-list', 'refresh');
|
|
htmx.trigger('#active-alerts-list', 'refresh');
|
|
htmx.trigger('#system-status-content', 'refresh');
|
|
|
|
// Refresh charts
|
|
loadChartData();
|
|
}
|
|
|
|
// Real-time notifications
|
|
if (typeof EventSource !== "undefined") {
|
|
const eventSource = new EventSource('{% url "communications:sse_notifications" %}');
|
|
|
|
eventSource.onmessage = function(event) {
|
|
const data = JSON.parse(event.data);
|
|
|
|
if (data.type === 'new_message') {
|
|
// Update unread count
|
|
const unreadElement = document.getElementById('unread-messages');
|
|
if (unreadElement) {
|
|
unreadElement.textContent = parseInt(unreadElement.textContent) + 1;
|
|
}
|
|
|
|
// Refresh recent messages
|
|
htmx.trigger('#recent-messages-list', 'refresh');
|
|
|
|
// Show toast notification
|
|
showToast('New Message', data.message, 'info');
|
|
} else if (data.type === 'new_alert') {
|
|
// Update alert count
|
|
const alertElement = document.getElementById('active-alerts');
|
|
if (alertElement) {
|
|
alertElement.textContent = parseInt(alertElement.textContent) + 1;
|
|
}
|
|
|
|
// Refresh alerts list
|
|
htmx.trigger('#active-alerts-list', 'refresh');
|
|
|
|
// Show toast notification
|
|
showToast('New Alert', data.message, 'warning');
|
|
}
|
|
};
|
|
|
|
eventSource.onerror = function(event) {
|
|
console.error('SSE connection error:', event);
|
|
};
|
|
}
|
|
|
|
function showToast(title, message, type) {
|
|
// Create toast notification
|
|
const toastHtml = `
|
|
<div class="toast align-items-center text-white bg-${type === 'info' ? 'primary' : 'warning'} border-0" role="alert">
|
|
<div class="d-flex">
|
|
<div class="toast-body">
|
|
<strong>${title}</strong><br>
|
|
${message}
|
|
</div>
|
|
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Add to toast container (assuming it exists in base template)
|
|
const toastContainer = document.getElementById('toast-container');
|
|
if (toastContainer) {
|
|
toastContainer.insertAdjacentHTML('beforeend', toastHtml);
|
|
const toast = new bootstrap.Toast(toastContainer.lastElementChild);
|
|
toast.show();
|
|
}
|
|
}
|
|
</script>
|
|
{% endblock %}
|
|
|