hospital-management/templates/communications/communication_channel_detail.html
2025-08-12 13:33:25 +03:00

665 lines
33 KiB
HTML

{% extends "base.html" %}
{% load static %}
{% block title %}{{ object.name }} - Channel 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">Channel 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:communication_channel_list' %}">Channels</a></li>
<li class="breadcrumb-item active">{{ object.name }}</li>
</ol>
</div>
</div>
</div>
</div>
<div class="row">
<!-- Main Content -->
<div class="col-lg-8">
<!-- Channel 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.channel_type == 'EMAIL' %}envelope{% elif object.channel_type == 'SMS' %}sms{% elif object.channel_type == 'PUSH' %}bell{% elif object.channel_type == 'WEBHOOK' %}link{% elif object.channel_type == 'SLACK' %}slack{% elif object.channel_type == 'TEAMS' %}microsoft{% else %}broadcast-tower{% endif %} me-2"></i>
{{ object.name }}
</h5>
<div class="d-flex gap-2">
<span class="badge bg-{% if object.channel_type == 'EMAIL' %}primary{% elif object.channel_type == 'SMS' %}success{% elif object.channel_type == 'PUSH' %}warning{% elif object.channel_type == 'WEBHOOK' %}info{% elif object.channel_type == 'SLACK' %}secondary{% else %}dark{% endif %} fs-6">
{{ object.get_channel_type_display }}
</span>
<span class="badge bg-{% if object.is_active %}success{% else %}secondary{% endif %} fs-6">
{% if object.is_active %}Active{% else %}Inactive{% endif %}
</span>
<span class="badge bg-{% if object.health_status == 'HEALTHY' %}success{% elif object.health_status == 'WARNING' %}warning{% else %}danger{% endif %} fs-6">
{{ object.get_health_status_display|default:"Unknown" }}
</span>
</div>
</div>
</div>
<div class="card-body">
<!-- Channel Description -->
{% if object.description %}
<div class="alert alert-light" role="alert">
<div class="d-flex">
<div class="flex-shrink-0">
<i class="fas fa-info-circle fa-lg"></i>
</div>
<div class="flex-grow-1 ms-3">
<p class="mb-0">{{ object.description }}</p>
</div>
</div>
</div>
{% endif %}
<!-- Channel 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">Channel Type:</td>
<td>{{ object.get_channel_type_display }}</td>
</tr>
<tr>
<td class="fw-bold text-muted">Status:</td>
<td>
<div class="d-flex align-items-center">
<span class="badge bg-{% if object.is_active %}success{% else %}secondary{% endif %} me-2">
{% if object.is_active %}Active{% else %}Inactive{% endif %}
</span>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" {% if object.is_active %}checked{% endif %}
onchange="toggleChannel(this.checked)">
</div>
</div>
</td>
</tr>
<tr>
<td class="fw-bold text-muted">Health Status:</td>
<td>
<span class="badge bg-{% if object.health_status == 'HEALTHY' %}success{% elif object.health_status == 'WARNING' %}warning{% else %}danger{% endif %}">
{{ object.get_health_status_display|default:"Unknown" }}
</span>
</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.get_priority_display|default:"Normal" }}
</span>
</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">Created:</td>
<td>{{ object.created_at|date:"M d, Y g:i A" }}</td>
</tr>
<tr>
<td class="fw-bold text-muted">Last Updated:</td>
<td>{{ object.updated_at|date:"M d, Y g:i A" }}</td>
</tr>
<tr>
<td class="fw-bold text-muted">Last Health Check:</td>
<td>
{% if object.last_health_check %}
{{ object.last_health_check|date:"M d, Y g:i A" }}
<br><small class="text-muted">{{ object.last_health_check|timesince }} ago</small>
{% else %}
<span class="text-muted">Never</span>
{% endif %}
</td>
</tr>
<tr>
<td class="fw-bold text-muted">Rate Limit:</td>
<td>
{% if object.rate_limit %}
{{ object.rate_limit }} messages/hour
{% else %}
<span class="text-muted">No limit</span>
{% endif %}
</td>
</tr>
</table>
</div>
</div>
</div>
<!-- Configuration Details -->
<div class="mb-4">
<h6 class="text-muted mb-2">Configuration:</h6>
<div class="bg-light p-3 rounded">
{% if object.channel_type == 'EMAIL' %}
<div class="row">
<div class="col-md-6">
<strong>SMTP Server:</strong> {{ object.configuration.smtp_host|default:"Not configured" }}<br>
<strong>Port:</strong> {{ object.configuration.smtp_port|default:"Not configured" }}<br>
<strong>Security:</strong> {{ object.configuration.smtp_security|default:"None" }}
</div>
<div class="col-md-6">
<strong>From Email:</strong> {{ object.configuration.from_email|default:"Not configured" }}<br>
<strong>From Name:</strong> {{ object.configuration.from_name|default:"Not configured" }}<br>
<strong>Authentication:</strong> {% if object.configuration.smtp_username %}Enabled{% else %}Disabled{% endif %}
</div>
</div>
{% elif object.channel_type == 'SMS' %}
<div class="row">
<div class="col-md-6">
<strong>Provider:</strong> {{ object.configuration.sms_provider|default:"Not configured" }}<br>
<strong>API Endpoint:</strong> {{ object.configuration.api_endpoint|default:"Not configured" }}
</div>
<div class="col-md-6">
<strong>From Number:</strong> {{ object.configuration.from_number|default:"Not configured" }}<br>
<strong>API Key:</strong> {% if object.configuration.api_key %}Configured{% else %}Not configured{% endif %}
</div>
</div>
{% elif object.channel_type == 'WEBHOOK' %}
<div class="row">
<div class="col-md-6">
<strong>URL:</strong> {{ object.configuration.webhook_url|default:"Not configured" }}<br>
<strong>Method:</strong> {{ object.configuration.http_method|default:"POST" }}
</div>
<div class="col-md-6">
<strong>Content Type:</strong> {{ object.configuration.content_type|default:"application/json" }}<br>
<strong>Authentication:</strong> {% if object.configuration.auth_header %}Enabled{% else %}Disabled{% endif %}
</div>
</div>
{% elif object.channel_type == 'SLACK' %}
<div class="row">
<div class="col-md-6">
<strong>Webhook URL:</strong> {% if object.configuration.slack_webhook %}Configured{% else %}Not configured{% endif %}<br>
<strong>Channel:</strong> {{ object.configuration.slack_channel|default:"#general" }}
</div>
<div class="col-md-6">
<strong>Username:</strong> {{ object.configuration.slack_username|default:"Hospital Bot" }}<br>
<strong>Icon:</strong> {{ object.configuration.slack_icon|default:":hospital:" }}
</div>
</div>
{% else %}
<pre class="mb-0">{{ object.configuration|pprint }}</pre>
{% endif %}
</div>
</div>
<!-- Retry Configuration -->
{% if object.retry_attempts or object.retry_delay %}
<div class="mb-4">
<h6 class="text-muted mb-2">Retry Configuration:</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">Max Attempts:</td>
<td>{{ object.retry_attempts|default:0 }}</td>
</tr>
<tr>
<td class="fw-bold text-muted">Retry Delay:</td>
<td>{{ object.retry_delay|default:0 }} seconds</td>
</tr>
</table>
</div>
</div>
<div class="col-md-6">
<div class="table-responsive">
<table class="table table-borderless table-sm">
<tr>
<td class="fw-bold text-muted">Backoff Strategy:</td>
<td>{{ object.backoff_strategy|default:"Linear" }}</td>
</tr>
<tr>
<td class="fw-bold text-muted">Max Delay:</td>
<td>{{ object.max_retry_delay|default:"No limit" }}</td>
</tr>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
{% endif %}
</div>
</div>
<!-- Performance Metrics -->
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-chart-line me-2"></i>
Performance Metrics
</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-3">
<div class="text-center">
<h4 class="mb-1">{{ object.message_count|default:0 }}</h4>
<p class="text-muted mb-0">Total Messages</p>
</div>
</div>
<div class="col-md-3">
<div class="text-center">
<h4 class="mb-1">{{ object.success_rate|default:0 }}%</h4>
<p class="text-muted mb-0">Success Rate</p>
</div>
</div>
<div class="col-md-3">
<div class="text-center">
<h4 class="mb-1">{{ object.avg_delivery_time|default:0 }}ms</h4>
<p class="text-muted mb-0">Avg Delivery Time</p>
</div>
</div>
<div class="col-md-3">
<div class="text-center">
<h4 class="mb-1">{{ object.failed_count|default:0 }}</h4>
<p class="text-muted mb-0">Failed Messages</p>
</div>
</div>
</div>
<hr>
<!-- Performance Chart Placeholder -->
<div class="text-center">
<canvas id="performanceChart" height="100"></canvas>
</div>
</div>
</div>
<!-- Recent Messages -->
<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-history me-2"></i>
Recent Messages
</h5>
<a href="{% url 'communications:delivery_log_list' %}?channel={{ object.id }}" class="btn btn-sm btn-outline-primary">
View All Logs
</a>
</div>
</div>
<div class="card-body">
{% if recent_messages %}
<div class="table-responsive">
<table class="table table-nowrap table-hover mb-0">
<thead class="table-light">
<tr>
<th>Message</th>
<th>Recipient</th>
<th>Status</th>
<th>Sent</th>
<th>Delivery Time</th>
</tr>
</thead>
<tbody>
{% for message in recent_messages %}
<tr>
<td>{{ message.subject|default:message.content|truncatechars:40 }}</td>
<td>{{ message.recipient|truncatechars:30 }}</td>
<td>
<span class="badge bg-{% if message.status == 'DELIVERED' %}success{% elif message.status == 'FAILED' %}danger{% elif message.status == 'PENDING' %}warning{% else %}secondary{% endif %}">
{{ message.get_status_display }}
</span>
</td>
<td>{{ message.sent_at|date:"M d, g:i A" }}</td>
<td>
{% if message.delivery_time %}
{{ message.delivery_time }}ms
{% else %}
<span class="text-muted">-</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-4">
<i class="fas fa-inbox fa-3x text-muted mb-3"></i>
<h6 class="text-muted">No recent messages</h6>
<p class="text-muted">Messages sent through this channel will appear here</p>
</div>
{% endif %}
</div>
</div>
</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">
<button class="btn btn-primary" onclick="testChannel()">
<i class="fas fa-flask me-1"></i>
Test Channel
</button>
<a href="{% url 'communications:communication_channel_update' object.pk %}" class="btn btn-outline-primary">
<i class="fas fa-edit me-1"></i>
Edit Channel
</a>
<button class="btn btn-outline-success" onclick="checkHealth()">
<i class="fas fa-heartbeat me-1"></i>
Check Health
</button>
<hr>
{% if object.is_active %}
<button class="btn btn-outline-warning" onclick="toggleChannel(false)">
<i class="fas fa-pause me-1"></i>
Disable Channel
</button>
{% else %}
<button class="btn btn-outline-success" onclick="toggleChannel(true)">
<i class="fas fa-play me-1"></i>
Enable Channel
</button>
{% endif %}
<button class="btn btn-outline-info" onclick="exportConfig()">
<i class="fas fa-download me-1"></i>
Export Config
</button>
<hr>
<a href="{% url 'communications:communication_channel_delete' object.pk %}" class="btn btn-outline-danger">
<i class="fas fa-trash me-1"></i>
Delete Channel
</a>
</div>
</div>
</div>
<!-- Channel Statistics -->
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-chart-bar me-2"></i>
Channel 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">Messages Today:</td>
<td>{{ object.messages_today|default:0 }}</td>
</tr>
<tr>
<td class="fw-bold text-muted">Messages This Week:</td>
<td>{{ object.messages_week|default:0 }}</td>
</tr>
<tr>
<td class="fw-bold text-muted">Messages This Month:</td>
<td>{{ object.messages_month|default:0 }}</td>
</tr>
<tr>
<td class="fw-bold text-muted">Error Rate:</td>
<td>{{ object.error_rate|default:0 }}%</td>
</tr>
<tr>
<td class="fw-bold text-muted">Avg Response Time:</td>
<td>{{ object.avg_response_time|default:0 }}ms</td>
</tr>
<tr>
<td class="fw-bold text-muted">Uptime:</td>
<td>{{ object.uptime_percentage|default:0 }}%</td>
</tr>
</table>
</div>
</div>
</div>
<!-- Health Status -->
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-heartbeat me-2"></i>
Health Status
</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-{% if object.health_status == 'HEALTHY' %}success{% elif object.health_status == 'WARNING' %}warning{% else %}danger{% endif %} text-{% if object.health_status == 'HEALTHY' %}success{% elif object.health_status == 'WARNING' %}warning{% else %}danger{% endif %} rounded">
<i class="fas fa-{% if object.health_status == 'HEALTHY' %}check-circle{% elif object.health_status == 'WARNING' %}exclamation-triangle{% else %}times-circle{% endif %}"></i>
</div>
</div>
<div>
<h6 class="mb-0">{{ object.get_health_status_display|default:"Unknown" }}</h6>
<small class="text-muted">
{% if object.last_health_check %}
Last checked {{ object.last_health_check|timesince }} ago
{% else %}
Never checked
{% endif %}
</small>
</div>
</div>
{% if object.health_details %}
<div class="bg-light p-2 rounded">
<small class="text-muted">{{ object.health_details }}</small>
</div>
{% endif %}
<div class="mt-3">
<button class="btn btn-sm btn-outline-primary" onclick="checkHealth()">
<i class="fas fa-sync-alt me-1"></i>
Check Now
</button>
</div>
</div>
</div>
<!-- Configuration Summary -->
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-cog me-2"></i>
Configuration Summary
</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-borderless table-sm">
<tr>
<td class="fw-bold text-muted">Type:</td>
<td>{{ object.get_channel_type_display }}</td>
</tr>
<tr>
<td class="fw-bold text-muted">Priority:</td>
<td>{{ object.get_priority_display|default:"Normal" }}</td>
</tr>
<tr>
<td class="fw-bold text-muted">Rate Limit:</td>
<td>
{% if object.rate_limit %}
{{ object.rate_limit }}/hour
{% else %}
No limit
{% endif %}
</td>
</tr>
<tr>
<td class="fw-bold text-muted">Retry Attempts:</td>
<td>{{ object.retry_attempts|default:0 }}</td>
</tr>
<tr>
<td class="fw-bold text-muted">Timeout:</td>
<td>{{ object.timeout|default:30 }}s</td>
</tr>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
// Performance Chart
const ctx = document.getElementById('performanceChart').getContext('2d');
const performanceChart = new Chart(ctx, {
type: 'line',
data: {
labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
datasets: [{
label: 'Messages Sent',
data: [12, 19, 3, 5, 2, 3, 7],
borderColor: 'rgb(75, 192, 192)',
backgroundColor: 'rgba(75, 192, 192, 0.1)',
tension: 0.1
}, {
label: 'Success Rate %',
data: [95, 98, 92, 96, 99, 94, 97],
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgba(255, 99, 132, 0.1)',
tension: 0.1,
yAxisID: 'y1'
}]
},
options: {
responsive: true,
plugins: {
legend: {
position: 'top',
}
},
scales: {
y: {
type: 'linear',
display: true,
position: 'left',
},
y1: {
type: 'linear',
display: true,
position: 'right',
grid: {
drawOnChartArea: false,
},
}
}
}
});
function toggleChannel(activate) {
const action = activate ? 'enable' : 'disable';
fetch(`{% url 'communications:communication_channel_detail' object.pk %}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 channel: ` + data.error);
}
});
}
function testChannel() {
if (confirm('Send a test message through this channel?')) {
fetch(`{% url 'communications:communication_channel_detail' object.pk %}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 message sent successfully!\n\nDelivery time: ${data.delivery_time}ms\nStatus: ${data.status}\nMessage ID: ${data.message_id}`);
} else {
alert('Test failed: ' + data.error);
}
});
}
}
function checkHealth() {
fetch(`{% url 'communications:communication_channel_detail' object.pk %}health/`, {
method: 'POST',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert(`Health check completed!\n\nStatus: ${data.health_status}\nResponse time: ${data.response_time}ms\nDetails: ${data.details || 'No additional details'}`);
location.reload();
} else {
alert('Health check failed: ' + data.error);
}
});
}
function exportConfig() {
window.open(`{% url 'communications:communication_channel_detail' object.pk %}export/`, '_blank');
}
// Auto-refresh health status
setInterval(function() {
fetch(`{% url 'communications:communication_channel_detail' object.pk %}health-status/`, {
method: 'GET',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Update health status badge
const healthBadges = document.querySelectorAll('.badge:contains("' + data.old_status + '")');
healthBadges.forEach(badge => {
badge.className = `badge bg-${data.health_status === 'HEALTHY' ? 'success' : data.health_status === 'WARNING' ? 'warning' : 'danger'} fs-6`;
badge.textContent = data.health_status;
});
}
});
}, 60000); // Update every minute
</script>
{% endblock %}