714 lines
36 KiB
HTML
714 lines
36 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}Delivery Logs - 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">Delivery Logs</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">Delivery Logs</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 Messages</span>
|
|
<h4 class="mb-3">
|
|
<span class="counter-value" data-target="{{ total_messages }}">{{ total_messages }}</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-envelope 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">Delivered</span>
|
|
<h4 class="mb-3">
|
|
<span class="counter-value" data-target="{{ delivered_messages }}">{{ delivered_messages }}</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">Failed</span>
|
|
<h4 class="mb-3">
|
|
<span class="counter-value" data-target="{{ failed_messages }}">{{ failed_messages }}</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-times-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">Success Rate</span>
|
|
<h4 class="mb-3">
|
|
<span class="counter-value" data-target="{{ success_rate }}">{{ success_rate }}</span>
|
|
<small class="text-muted">%</small>
|
|
</h4>
|
|
</div>
|
|
<div class="flex-shrink-0">
|
|
<div class="avatar-sm rounded-circle bg-info">
|
|
<span class="avatar-title rounded-circle bg-info">
|
|
<i class="fas fa-chart-line text-white font-size-16"></i>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Delivery Logs 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">Message Delivery Logs</h4>
|
|
</div>
|
|
<div class="col-auto">
|
|
<div class="btn-group" role="group">
|
|
<button type="button" class="btn btn-outline-primary" onclick="retryFailed()">
|
|
<i class="fas fa-redo me-1"></i>
|
|
Retry Failed
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary" onclick="clearOldLogs()">
|
|
<i class="fas fa-trash me-1"></i>
|
|
Clear Old Logs
|
|
</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 messages..."
|
|
id="messageSearch">
|
|
<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="PENDING">Pending</option>
|
|
<option value="DELIVERED">Delivered</option>
|
|
<option value="FAILED">Failed</option>
|
|
<option value="RETRYING">Retrying</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<select class="form-select" id="channelFilter">
|
|
<option value="">All Channels</option>
|
|
{% for channel in channels %}
|
|
<option value="{{ channel.id }}">{{ channel.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<select class="form-select" id="typeFilter">
|
|
<option value="">All Types</option>
|
|
<option value="EMAIL">Email</option>
|
|
<option value="SMS">SMS</option>
|
|
<option value="PUSH">Push</option>
|
|
<option value="WEBHOOK">Webhook</option>
|
|
<option value="SLACK">Slack</option>
|
|
<option value="TEAMS">Teams</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="refreshLogs">
|
|
<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="exportLogs('csv')">CSV</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="exportLogs('json')">JSON</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="exportLogs('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="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>
|
|
|
|
<!-- Delivery Logs Table -->
|
|
<div id="deliveryLogsList">
|
|
<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">Message</th>
|
|
<th scope="col">Channel</th>
|
|
<th scope="col">Recipient</th>
|
|
<th scope="col">Status</th>
|
|
<th scope="col">Sent</th>
|
|
<th scope="col">Delivery Time</th>
|
|
<th scope="col">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for log in object_list %}
|
|
<tr class="log-row">
|
|
<td>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" value="{{ log.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 log.channel.channel_type == 'EMAIL' %}primary{% elif log.channel.channel_type == 'SMS' %}success{% elif log.channel.channel_type == 'PUSH' %}warning{% elif log.channel.channel_type == 'WEBHOOK' %}info{% elif log.channel.channel_type == 'SLACK' %}secondary{% else %}dark{% endif %} text-{% if log.channel.channel_type == 'EMAIL' %}primary{% elif log.channel.channel_type == 'SMS' %}success{% elif log.channel.channel_type == 'PUSH' %}warning{% elif log.channel.channel_type == 'WEBHOOK' %}info{% elif log.channel.channel_type == 'SLACK' %}secondary{% else %}dark{% endif %} rounded-circle">
|
|
<i class="fas fa-{% if log.channel.channel_type == 'EMAIL' %}envelope{% elif log.channel.channel_type == 'SMS' %}sms{% elif log.channel.channel_type == 'PUSH' %}bell{% elif log.channel.channel_type == 'WEBHOOK' %}link{% elif log.channel.channel_type == 'SLACK' %}slack{% elif log.channel.channel_type == 'TEAMS' %}microsoft{% else %}broadcast-tower{% endif %}"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex-grow-1">
|
|
<h6 class="mb-0">
|
|
<a href="{% url 'communications:delivery_log_detail' log.pk %}" class="text-dark">
|
|
{{ log.subject|default:log.content|truncatechars:40 }}
|
|
</a>
|
|
</h6>
|
|
{% if log.message_type %}
|
|
<small class="text-muted">{{ log.get_message_type_display }}</small>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<a href="{% url 'communications:communication_channel_detail' log.channel.pk %}" class="text-primary">
|
|
{{ log.channel.name|truncatechars:20 }}
|
|
</a>
|
|
<br><small class="text-muted">{{ log.channel.get_channel_type_display }}</small>
|
|
</td>
|
|
<td>
|
|
<div>
|
|
{{ log.recipient|truncatechars:25 }}
|
|
{% if log.recipient_name %}
|
|
<br><small class="text-muted">{{ log.recipient_name|truncatechars:20 }}</small>
|
|
{% endif %}
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-{% if log.status == 'DELIVERED' %}success{% elif log.status == 'FAILED' %}danger{% elif log.status == 'PENDING' %}warning{% elif log.status == 'RETRYING' %}info{% else %}secondary{% endif %}">
|
|
{{ log.get_status_display }}
|
|
</span>
|
|
{% if log.retry_count > 0 %}
|
|
<br><small class="text-muted">Retry {{ log.retry_count }}</small>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<span class="text-muted">{{ log.sent_at|timesince }} ago</span>
|
|
<br><small class="text-muted">{{ log.sent_at|date:"M d, g:i A" }}</small>
|
|
</td>
|
|
<td>
|
|
{% if log.delivery_time %}
|
|
<span class="text-{% if log.delivery_time < 1000 %}success{% elif log.delivery_time < 5000 %}warning{% else %}danger{% endif %}">
|
|
{{ log.delivery_time }}ms
|
|
</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:delivery_log_detail' log.pk %}">
|
|
<i class="fas fa-eye me-2"></i>View Details
|
|
</a>
|
|
{% if log.status == 'FAILED' %}
|
|
<a class="dropdown-item" href="#" onclick="retryMessage({{ log.id }})">
|
|
<i class="fas fa-redo me-2"></i>Retry Delivery
|
|
</a>
|
|
{% endif %}
|
|
{% if log.status == 'DELIVERED' and log.channel.channel_type == 'EMAIL' %}
|
|
<a class="dropdown-item" href="#" onclick="viewEmailContent({{ log.id }})">
|
|
<i class="fas fa-envelope-open me-2"></i>View Email
|
|
</a>
|
|
{% endif %}
|
|
<div class="dropdown-divider"></div>
|
|
<a class="dropdown-item" href="#" onclick="exportLog({{ log.id }})">
|
|
<i class="fas fa-download me-2"></i>Export Log
|
|
</a>
|
|
{% if log.error_message %}
|
|
<a class="dropdown-item text-danger" href="#" onclick="viewError({{ log.id }})">
|
|
<i class="fas fa-exclamation-triangle me-2"></i>View Error
|
|
</a>
|
|
{% endif %}
|
|
</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-inbox fa-3x text-muted mb-3"></i>
|
|
<h5 class="text-muted">No delivery logs found</h5>
|
|
<p class="text-muted">Message delivery logs will appear here</p>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Timeline View (Hidden by default) -->
|
|
<div id="timelineViewContent" style="display: none;">
|
|
<div class="timeline">
|
|
{% for log in object_list %}
|
|
<div class="timeline-item">
|
|
<div class="timeline-marker bg-{% if log.status == 'DELIVERED' %}success{% elif log.status == 'FAILED' %}danger{% elif log.status == 'PENDING' %}warning{% elif log.status == 'RETRYING' %}info{% else %}secondary{% endif %}">
|
|
<i class="fas fa-{% if log.channel.channel_type == 'EMAIL' %}envelope{% elif log.channel.channel_type == 'SMS' %}sms{% elif log.channel.channel_type == 'PUSH' %}bell{% elif log.channel.channel_type == 'WEBHOOK' %}link{% elif log.channel.channel_type == 'SLACK' %}slack{% elif log.channel.channel_type == 'TEAMS' %}microsoft{% else %}broadcast-tower{% endif %}"></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">{{ log.subject|default:log.content|truncatechars:50 }}</h6>
|
|
<p class="text-muted mb-1">
|
|
<strong>To:</strong> {{ log.recipient|truncatechars:30 }}<br>
|
|
<strong>Via:</strong> {{ log.channel.name }} ({{ log.channel.get_channel_type_display }})
|
|
</p>
|
|
<small class="text-muted">{{ log.sent_at|date:"M d, Y g:i A" }}</small>
|
|
</div>
|
|
<div class="text-end">
|
|
<span class="badge bg-{% if log.status == 'DELIVERED' %}success{% elif log.status == 'FAILED' %}danger{% elif log.status == 'PENDING' %}warning{% elif log.status == 'RETRYING' %}info{% else %}secondary{% endif %}">
|
|
{{ log.get_status_display }}
|
|
</span>
|
|
{% if log.delivery_time %}
|
|
<br><small class="text-muted">{{ log.delivery_time }}ms</small>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% if log.error_message %}
|
|
<div class="mt-2">
|
|
<div class="alert alert-danger alert-sm mb-0">
|
|
<small><strong>Error:</strong> {{ log.error_message|truncatechars:100 }}</small>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</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-sm {
|
|
padding: 0.25rem 0.5rem;
|
|
font-size: 0.75rem;
|
|
}
|
|
</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 timelineView = document.getElementById('timelineViewContent');
|
|
|
|
if (this.id === 'tableView') {
|
|
tableView.style.display = 'block';
|
|
timelineView.style.display = 'none';
|
|
} else {
|
|
tableView.style.display = 'none';
|
|
timelineView.style.display = 'block';
|
|
}
|
|
});
|
|
});
|
|
|
|
// Search and filter functionality
|
|
document.getElementById('messageSearch').addEventListener('input', filterLogs);
|
|
document.getElementById('statusFilter').addEventListener('change', filterLogs);
|
|
document.getElementById('channelFilter').addEventListener('change', filterLogs);
|
|
document.getElementById('typeFilter').addEventListener('change', filterLogs);
|
|
document.getElementById('timeFilter').addEventListener('change', filterLogs);
|
|
|
|
function filterLogs() {
|
|
const searchTerm = document.getElementById('messageSearch').value.toLowerCase();
|
|
const status = document.getElementById('statusFilter').value;
|
|
const channel = document.getElementById('channelFilter').value;
|
|
const type = document.getElementById('typeFilter').value;
|
|
const timeFilter = document.getElementById('timeFilter').value;
|
|
|
|
const rows = document.querySelectorAll('.log-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());
|
|
}
|
|
|
|
// Channel filter
|
|
if (channel) {
|
|
const channelLink = row.querySelector('td:nth-child(3) a');
|
|
show = show && channelLink && channelLink.href.includes(`/${channel}/`);
|
|
}
|
|
|
|
// Type filter
|
|
if (type) {
|
|
const typeText = row.querySelector('td:nth-child(3) small');
|
|
show = show && typeText && typeText.textContent.trim().toLowerCase().includes(type.toLowerCase());
|
|
}
|
|
|
|
row.style.display = show ? '' : 'none';
|
|
});
|
|
}
|
|
|
|
// Message actions
|
|
function retryMessage(logId) {
|
|
if (confirm('Retry delivery for this message?')) {
|
|
fetch(`/communications/delivery-logs/${logId}/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('Message queued for retry');
|
|
location.reload();
|
|
} else {
|
|
alert('Error retrying message: ' + data.error);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function retryFailed() {
|
|
if (confirm('Retry all failed messages?')) {
|
|
fetch('/communications/delivery-logs/retry-failed/', {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
|
|
'Content-Type': 'application/json',
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
alert(`${data.count} messages queued for retry`);
|
|
location.reload();
|
|
} else {
|
|
alert('Error retrying messages: ' + data.error);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function clearOldLogs() {
|
|
const days = prompt('Clear logs older than how many days?', '30');
|
|
if (days && !isNaN(days) && parseInt(days) > 0) {
|
|
if (confirm(`Clear all logs older than ${days} days? This cannot be undone.`)) {
|
|
fetch('/communications/delivery-logs/clear-old/', {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ days: parseInt(days) })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
alert(`${data.count} logs cleared`);
|
|
location.reload();
|
|
} else {
|
|
alert('Error clearing logs: ' + data.error);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
function viewEmailContent(logId) {
|
|
window.open(`/communications/delivery-logs/${logId}/email-content/`, '_blank');
|
|
}
|
|
|
|
function viewError(logId) {
|
|
fetch(`/communications/delivery-logs/${logId}/error/`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
alert(`Error Details:\n\n${data.error_message}\n\nStack Trace:\n${data.stack_trace || 'Not available'}`);
|
|
} else {
|
|
alert('Error retrieving error details');
|
|
}
|
|
});
|
|
}
|
|
|
|
function exportLog(logId) {
|
|
window.open(`/communications/delivery-logs/${logId}/export/`, '_blank');
|
|
}
|
|
|
|
function exportLogs(format) {
|
|
const url = `/communications/export/delivery-logs/?format=${format}`;
|
|
window.open(url, '_blank');
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
// 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 logs
|
|
document.getElementById('refreshLogs').addEventListener('click', function() {
|
|
location.reload();
|
|
});
|
|
|
|
// Initialize
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
if (autoRefresh) {
|
|
startAutoRefresh();
|
|
}
|
|
});
|
|
|
|
// Cleanup
|
|
window.addEventListener('beforeunload', function() {
|
|
stopAutoRefresh();
|
|
});
|
|
</script>
|
|
{% endblock %}
|
|
|