hospital-management/accounts/templates/account/session_management.html
Marwan Alwali a710d1c4d8 update
2025-09-11 19:01:55 +03:00

433 lines
21 KiB
HTML

{% extends "base.html" %}
{% load static %}
{% block title %}Session Management - {{ block.super }}{% endblock %}
{% block css %}
<style>
.session-filters {
background: #f8f9fa;
padding: 1rem;
border-radius: 0.375rem;
margin-bottom: 1.5rem;
}
.session-item {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
padding: 1rem;
margin-bottom: 0.75rem;
transition: all 0.2s ease;
}
.session-item:hover {
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
border-color: #007bff;
}
.session-item.active {
border-left: 4px solid #28a745;
background: #f8fff9;
}
.session-item.expired {
border-left: 4px solid #dc3545;
background: #fff5f5;
}
.session-item.suspicious {
border-left: 4px solid #ffc107;
background: #fffbf0;
}
.session-meta {
font-size: 0.875rem;
color: #6c757d;
}
.device-icon {
width: 40px;
height: 40px;
border-radius: 0.375rem;
display: flex;
align-items: center;
justify-content: center;
margin-right: 1rem;
}
.device-icon.desktop {
background: #e3f2fd;
color: #1976d2;
}
.device-icon.mobile {
background: #f3e5f5;
color: #7b1fa2;
}
.device-icon.tablet {
background: #e8f5e8;
color: #388e3c;
}
.session-actions {
opacity: 0;
transition: opacity 0.2s ease;
}
.session-item:hover .session-actions {
opacity: 1;
}
.stats-card {
background: white;
border-radius: 0.5rem;
padding: 1.5rem;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
margin-bottom: 1.5rem;
text-align: center;
}
.stats-number {
font-size: 2rem;
font-weight: bold;
color: #007bff;
}
.location-map {
height: 200px;
background: #f8f9fa;
border-radius: 0.375rem;
display: flex;
align-items: center;
justify-content: center;
color: #6c757d;
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h4 class="card-title mb-0">
<i class="fas fa-desktop me-2"></i>
Session Management
</h4>
<p class="card-subtitle text-muted mt-1">
Monitor and manage user sessions across the system
</p>
</div>
<div class="card-body">
<!-- Statistics -->
<div class="row mb-4">
<div class="col-md-3">
<div class="stats-card">
<div class="stats-number" id="total-sessions">{{ sessions.count }}</div>
<div class="text-muted">Total Sessions</div>
</div>
</div>
<div class="col-md-3">
<div class="stats-card">
<div class="stats-number text-success" id="active-sessions">
{{ sessions|length }}
</div>
<div class="text-muted">Active Sessions</div>
</div>
</div>
<div class="col-md-3">
<div class="stats-card">
<div class="stats-number text-warning" id="suspicious-sessions">0</div>
<div class="text-muted">Suspicious</div>
</div>
</div>
<div class="col-md-3">
<div class="stats-card">
<div class="stats-number text-info" id="unique-users">
{{ sessions|regroup:"user"|length }}
</div>
<div class="text-muted">Unique Users</div>
</div>
</div>
</div>
<!-- Filters -->
<div class="session-filters">
<form method="get" class="row g-3">
<div class="col-md-3">
<label for="user_id" class="form-label">User</label>
<select name="user_id" id="user_id" class="form-select">
<option value="">All Users</option>
{% for session in sessions %}
{% ifchanged session.user %}
<option value="{{ session.user.id }}"
{% if request.GET.user_id == session.user.id|stringformat:"s" %}selected{% endif %}>
{{ session.user.get_full_name }}
</option>
{% endifchanged %}
{% endfor %}
</select>
</div>
<div class="col-md-2">
<label for="status" class="form-label">Status</label>
<select name="status" id="status" class="form-select">
<option value="">All Status</option>
<option value="active" {% if request.GET.status == 'active' %}selected{% endif %}>Active</option>
<option value="expired" {% if request.GET.status == 'expired' %}selected{% endif %}>Expired</option>
</select>
</div>
<div class="col-md-2">
<label for="device_type" class="form-label">Device Type</label>
<select name="device_type" id="device_type" class="form-select">
<option value="">All Devices</option>
<option value="DESKTOP" {% if request.GET.device_type == 'DESKTOP' %}selected{% endif %}>Desktop</option>
<option value="MOBILE" {% if request.GET.device_type == 'MOBILE' %}selected{% endif %}>Mobile</option>
<option value="TABLET" {% if request.GET.device_type == 'TABLET' %}selected{% endif %}>Tablet</option>
</select>
</div>
<div class="col-md-3">
<label for="search" class="form-label">Search</label>
<input type="text" name="search" id="search" class="form-control"
placeholder="IP address, location, browser..."
value="{{ request.GET.search }}">
</div>
<div class="col-md-2">
<label class="form-label">&nbsp;</label>
<div class="d-grid">
<button type="submit" class="btn btn-primary">
<i class="fas fa-search me-1"></i>Filter
</button>
</div>
</div>
</form>
</div>
<!-- Session List -->
<div id="session-list"
hx-get="{% url 'accounts:session_list' %}"
hx-trigger="load, every 30s"
hx-include="[name='user_id'], [name='status'], [name='device_type']">
{% if sessions %}
{% for session in sessions %}
<div class="session-item {% if session.is_active %}active{% elif session.expires_at < now %}expired{% endif %}"
data-session-id="{{ session.session_id }}">
<div class="row align-items-center">
<div class="col-auto">
<div class="device-icon {{ session.device_type|lower }}">
{% if session.device_type == 'DESKTOP' %}
<i class="fas fa-desktop"></i>
{% elif session.device_type == 'MOBILE' %}
<i class="fas fa-mobile-alt"></i>
{% elif session.device_type == 'TABLET' %}
<i class="fas fa-tablet-alt"></i>
{% else %}
<i class="fas fa-question"></i>
{% endif %}
</div>
</div>
<div class="col">
<div class="d-flex justify-content-between align-items-start">
<div>
<div class="fw-bold">
{{ session.user.get_full_name }}
{% if session.is_current_session %}
<span class="badge bg-primary ms-2">Current</span>
{% endif %}
</div>
<div class="session-meta">
<i class="fas fa-globe me-1"></i>{{ session.ip_address }}
<span class="mx-2"></span>
<i class="fas fa-map-marker-alt me-1"></i>{{ session.location|default:"Unknown location" }}
<span class="mx-2"></span>
<i class="fas fa-browser me-1"></i>{{ session.browser_name }} {{ session.browser_version }}
</div>
<div class="session-meta">
<i class="fas fa-clock me-1"></i>
Started: {{ session.created_at|date:"M d, Y H:i" }}
<span class="mx-2"></span>
Last activity: {{ session.last_activity_at|date:"M d, Y H:i" }}
{% if session.expires_at %}
<span class="mx-2"></span>
Expires: {{ session.expires_at|date:"M d, Y H:i" }}
{% endif %}
</div>
{% if session.user_agent %}
<div class="session-meta">
<i class="fas fa-info-circle me-1"></i>
{{ session.user_agent|truncatechars:80 }}
</div>
{% endif %}
</div>
<div class="session-actions">
<div class="btn-group">
<button class="btn btn-sm btn-outline-info"
title="View Details"
data-bs-toggle="modal"
data-bs-target="#session-detail-modal"
onclick="loadSessionDetails('{{ session.session_id }}')">
<i class="fas fa-eye"></i>
</button>
{% if session.is_active and not session.is_current_session %}
<button class="btn btn-sm btn-outline-warning"
title="End Session"
hx-post="{% url 'accounts:end_session' session.session_id %}"
hx-confirm="End this session?"
hx-target="closest .session-item"
hx-swap="outerHTML">
<i class="fas fa-sign-out-alt"></i>
</button>
{% endif %}
<button class="btn btn-sm btn-outline-secondary"
title="Session History">
<i class="fas fa-history"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
{% else %}
<div class="text-center py-5">
<i class="fas fa-desktop fa-3x text-muted mb-3"></i>
<h5 class="text-muted">No sessions found</h5>
<p class="text-muted">No sessions match your current filters.</p>
</div>
{% endif %}
</div>
<!-- Pagination -->
{% if is_paginated %}
<nav aria-label="Session pagination" class="mt-4">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1{% if request.GET.user_id %}&user_id={{ request.GET.user_id }}{% endif %}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.device_type %}&device_type={{ request.GET.device_type }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}">First</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if request.GET.user_id %}&user_id={{ request.GET.user_id }}{% endif %}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.device_type %}&device_type={{ request.GET.device_type }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}">Previous</a>
</li>
{% endif %}
<li class="page-item active">
<span class="page-link">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
</span>
</li>
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if request.GET.user_id %}&user_id={{ request.GET.user_id }}{% endif %}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.device_type %}&device_type={{ request.GET.device_type }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}">Next</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if request.GET.user_id %}&user_id={{ request.GET.user_id }}{% endif %}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.device_type %}&device_type={{ request.GET.device_type }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}">Last</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<!-- Session Detail Modal -->
<div class="modal fade" id="session-detail-modal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Session Details</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<!-- Content loaded via JavaScript -->
</div>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Auto-refresh session list every 30 seconds
setInterval(function() {
htmx.trigger('#session-list', 'refresh');
}, 30000);
// Update statistics
function updateStats() {
const sessions = document.querySelectorAll('.session-item');
const activeSessions = document.querySelectorAll('.session-item.active');
const suspiciousSessions = document.querySelectorAll('.session-item.suspicious');
document.getElementById('total-sessions').textContent = sessions.length;
document.getElementById('active-sessions').textContent = activeSessions.length;
document.getElementById('suspicious-sessions').textContent = suspiciousSessions.length;
}
// Load session details
window.loadSessionDetails = function(sessionId) {
fetch(`/accounts/session/${sessionId}/details/`)
.then(response => response.json())
.then(data => {
const modalBody = document.querySelector('#session-detail-modal .modal-body');
modalBody.innerHTML = `
<div class="row">
<div class="col-md-6">
<h6>Session Information</h6>
<table class="table table-sm">
<tr><td>Session ID</td><td>${data.session_id}</td></tr>
<tr><td>User</td><td>${data.user_name}</td></tr>
<tr><td>Status</td><td>${data.is_active ? 'Active' : 'Inactive'}</td></tr>
<tr><td>Created</td><td>${data.created_at}</td></tr>
<tr><td>Last Activity</td><td>${data.last_activity_at}</td></tr>
<tr><td>Expires</td><td>${data.expires_at || 'Never'}</td></tr>
</table>
</div>
<div class="col-md-6">
<h6>Device Information</h6>
<table class="table table-sm">
<tr><td>Device Type</td><td>${data.device_type}</td></tr>
<tr><td>Browser</td><td>${data.browser_name} ${data.browser_version}</td></tr>
<tr><td>Operating System</td><td>${data.operating_system}</td></tr>
<tr><td>IP Address</td><td>${data.ip_address}</td></tr>
<tr><td>Location</td><td>${data.location || 'Unknown'}</td></tr>
</table>
</div>
</div>
<div class="row mt-3">
<div class="col-12">
<h6>User Agent</h6>
<code>${data.user_agent}</code>
</div>
</div>
`;
})
.catch(error => {
console.error('Error loading session details:', error);
});
};
// Initial stats update
updateStats();
});
</script>
{% endblock %}