391 lines
17 KiB
HTML
391 lines
17 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}Session Details - Session Management{% endblock %}
|
|
|
|
{% block content %}
|
|
<!-- BEGIN breadcrumb -->
|
|
<ol class="breadcrumb float-xl-end">
|
|
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'accounts:user_list' %}">Users</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'accounts:session_list' %}">Sessions</a></li>
|
|
<li class="breadcrumb-item active">Session Details</li>
|
|
</ol>
|
|
<!-- END breadcrumb -->
|
|
|
|
<!-- BEGIN page-header -->
|
|
<h1 class="page-header">
|
|
Session Details
|
|
<small>{{ object.session_key|truncatechars:16 }}...</small>
|
|
</h1>
|
|
<!-- END page-header -->
|
|
|
|
<div class="row">
|
|
<div class="col-xl-8">
|
|
<!-- BEGIN panel -->
|
|
<div class="panel panel-inverse">
|
|
<div class="panel-heading">
|
|
<h4 class="panel-title">Session Information</h4>
|
|
<div class="panel-heading-btn">
|
|
{% if object.is_active %}
|
|
<button class="btn btn-xs btn-danger me-2" onclick="terminateSession()">
|
|
<i class="fa fa-sign-out-alt"></i> Terminate
|
|
</button>
|
|
{% endif %}
|
|
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
|
</div>
|
|
</div>
|
|
<div class="panel-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<table class="table table-borderless">
|
|
<tr>
|
|
<td class="fw-bold" width="150">Session Key:</td>
|
|
<td><code>{{ object.session_key }}</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">User:</td>
|
|
<td>
|
|
{% if object.user %}
|
|
<a href="{% url 'accounts:user_detail' object.user.pk %}" class="text-decoration-none">
|
|
{{ object.user.get_full_name }}
|
|
</a>
|
|
<br><small class="text-muted">{{ object.user.username }}</small>
|
|
{% else %}
|
|
<span class="text-muted">Anonymous Session</span>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Status:</td>
|
|
<td>
|
|
{% if object.is_active %}
|
|
<span class="badge bg-success">Active</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary">Expired</span>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Created:</td>
|
|
<td>{{ object.created_at|date:"M d, Y H:i:s" }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Last Activity:</td>
|
|
<td>
|
|
{{ object.last_activity|date:"M d, Y H:i:s" }}
|
|
<br><small class="text-muted">{{ object.last_activity|timesince }} ago</small>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<table class="table table-borderless">
|
|
<tr>
|
|
<td class="fw-bold" width="150">IP Address:</td>
|
|
<td>
|
|
{{ object.ip_address|default:"Unknown" }}
|
|
{% if object.ip_address %}
|
|
<br><small class="text-muted">
|
|
<a href="#" onclick="lookupIP('{{ object.ip_address }}')" class="text-decoration-none">
|
|
<i class="fa fa-search me-1"></i>Lookup Location
|
|
</a>
|
|
</small>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">User Agent:</td>
|
|
<td>
|
|
{% if object.user_agent %}
|
|
<div class="small">{{ object.browser_info.name }} {{ object.browser_info.version }}</div>
|
|
<div class="text-muted small">{{ object.browser_info.os }}</div>
|
|
{% else %}
|
|
<span class="text-muted">Unknown</span>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Duration:</td>
|
|
<td>
|
|
{% if object.is_active %}
|
|
{{ object.duration }}
|
|
<br><small class="text-success">Currently active</small>
|
|
{% else %}
|
|
{{ object.duration }}
|
|
<br><small class="text-muted">Session ended</small>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Expires:</td>
|
|
<td>
|
|
{% if object.expires_at %}
|
|
{{ object.expires_at|date:"M d, Y H:i:s" }}
|
|
{% if object.is_active %}
|
|
<br><small class="text-muted">{{ object.expires_at|timeuntil }} remaining</small>
|
|
{% endif %}
|
|
{% else %}
|
|
<span class="text-muted">No expiration</span>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Session Data -->
|
|
{% if object.session_data %}
|
|
<div class="mt-4">
|
|
<h6>Session Data</h6>
|
|
<div class="bg-light p-3 rounded">
|
|
<pre class="mb-0"><code>{{ object.session_data_formatted }}</code></pre>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<!-- END panel -->
|
|
|
|
<!-- BEGIN panel -->
|
|
<div class="panel panel-inverse">
|
|
<div class="panel-heading">
|
|
<h4 class="panel-title">Session Activity</h4>
|
|
<div class="panel-heading-btn">
|
|
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
|
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
|
|
</div>
|
|
</div>
|
|
<div class="panel-body">
|
|
{% if session_activities %}
|
|
<div class="timeline">
|
|
{% for activity in session_activities %}
|
|
<div class="timeline-item">
|
|
<div class="timeline-time">{{ activity.timestamp|date:"H:i" }}</div>
|
|
<div class="timeline-icon bg-{{ activity.get_activity_color }}">
|
|
<i class="fa fa-{{ activity.get_activity_icon }}"></i>
|
|
</div>
|
|
<div class="timeline-body">
|
|
<div class="timeline-header">
|
|
<span class="fw-bold">{{ activity.get_activity_display }}</span>
|
|
</div>
|
|
<div class="timeline-content">
|
|
<div class="small text-muted">
|
|
{{ activity.description }}
|
|
{% if activity.ip_address %}
|
|
• IP: {{ activity.ip_address }}
|
|
{% endif %}
|
|
</div>
|
|
{% if activity.details %}
|
|
<div class="small">{{ activity.details }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-4 text-muted">
|
|
<i class="fa fa-history fa-3x mb-3"></i>
|
|
<p>No activity records found for this session.</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<!-- END panel -->
|
|
</div>
|
|
|
|
<div class="col-xl-4">
|
|
<!-- BEGIN panel -->
|
|
<div class="panel panel-inverse">
|
|
<div class="panel-heading">
|
|
<h4 class="panel-title">Quick Actions</h4>
|
|
</div>
|
|
<div class="panel-body">
|
|
<div class="d-grid gap-2">
|
|
{% if object.is_active %}
|
|
<button class="btn btn-danger" onclick="terminateSession()">
|
|
<i class="fa fa-sign-out-alt me-2"></i>Terminate Session
|
|
</button>
|
|
{% endif %}
|
|
|
|
{% if object.user %}
|
|
<a href="{% url 'accounts:user_detail' object.user.pk %}" class="btn btn-primary">
|
|
<i class="fa fa-user me-2"></i>View User Profile
|
|
</a>
|
|
|
|
<button class="btn btn-warning" onclick="terminateUserSessions()">
|
|
<i class="fa fa-sign-out-alt me-2"></i>Terminate All User Sessions
|
|
</button>
|
|
{% endif %}
|
|
|
|
<button class="btn btn-info" onclick="refreshSessionData()">
|
|
<i class="fa fa-refresh me-2"></i>Refresh Data
|
|
</button>
|
|
|
|
<button class="btn btn-outline-secondary" onclick="exportSessionData()">
|
|
<i class="fa fa-download me-2"></i>Export Session Data
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- END panel -->
|
|
|
|
<!-- BEGIN panel -->
|
|
<div class="panel panel-inverse">
|
|
<div class="panel-heading">
|
|
<h4 class="panel-title">Security Information</h4>
|
|
</div>
|
|
<div class="panel-body">
|
|
<div class="mb-3">
|
|
<strong>Security Score:</strong>
|
|
<div class="progress mt-2">
|
|
<div class="progress-bar bg-{{ object.security_score_color }}" style="width: {{ object.security_score }}%">
|
|
{{ object.security_score }}%
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="small">
|
|
<div class="mb-2">
|
|
<i class="fa fa-{{ object.is_secure_connection|yesno:'lock text-success,unlock text-warning' }} me-2"></i>
|
|
{{ object.is_secure_connection|yesno:'Secure Connection,Insecure Connection' }}
|
|
</div>
|
|
|
|
<div class="mb-2">
|
|
<i class="fa fa-{{ object.is_trusted_ip|yesno:'shield-alt text-success,exclamation-triangle text-warning' }} me-2"></i>
|
|
{{ object.is_trusted_ip|yesno:'Trusted IP,Unknown IP' }}
|
|
</div>
|
|
|
|
<div class="mb-2">
|
|
<i class="fa fa-{{ object.is_known_device|yesno:'check text-success,question text-info' }} me-2"></i>
|
|
{{ object.is_known_device|yesno:'Known Device,New Device' }}
|
|
</div>
|
|
|
|
{% if object.suspicious_activity %}
|
|
<div class="mb-2 text-danger">
|
|
<i class="fa fa-exclamation-triangle me-2"></i>
|
|
Suspicious Activity Detected
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- END panel -->
|
|
|
|
<!-- BEGIN panel -->
|
|
<div class="panel panel-inverse">
|
|
<div class="panel-heading">
|
|
<h4 class="panel-title">Location Information</h4>
|
|
</div>
|
|
<div class="panel-body">
|
|
{% if object.location_info %}
|
|
<table class="table table-sm table-borderless">
|
|
<tr>
|
|
<td class="text-muted">Country:</td>
|
|
<td>{{ object.location_info.country|default:"Unknown" }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="text-muted">Region:</td>
|
|
<td>{{ object.location_info.region|default:"Unknown" }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="text-muted">City:</td>
|
|
<td>{{ object.location_info.city|default:"Unknown" }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="text-muted">ISP:</td>
|
|
<td>{{ object.location_info.isp|default:"Unknown" }}</td>
|
|
</tr>
|
|
</table>
|
|
{% else %}
|
|
<div class="text-center py-3 text-muted">
|
|
<i class="fa fa-map-marker-alt fa-2x mb-2"></i>
|
|
<p class="mb-0">Location information not available.</p>
|
|
{% if object.ip_address %}
|
|
<button class="btn btn-sm btn-outline-primary mt-2" onclick="lookupIP('{{ object.ip_address }}')">
|
|
Lookup Location
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<!-- END panel -->
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block js %}
|
|
<script>
|
|
function terminateSession() {
|
|
if (confirm('Are you sure you want to terminate this session?')) {
|
|
$.ajax({
|
|
url: '{% url "accounts:session_terminate" object.session_key %}',
|
|
method: 'POST',
|
|
data: {
|
|
'csrfmiddlewaretoken': '{{ csrf_token }}'
|
|
},
|
|
success: function(response) {
|
|
if (response.success) {
|
|
toastr.success('Session terminated successfully');
|
|
location.reload();
|
|
} else {
|
|
toastr.error('Failed to terminate session');
|
|
}
|
|
},
|
|
error: function() {
|
|
toastr.error('An error occurred while terminating the session');
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
{% if object.user %}
|
|
function terminateUserSessions() {
|
|
if (confirm('Are you sure you want to terminate ALL sessions for this user?')) {
|
|
$.ajax({
|
|
url: '{% url "accounts:user_terminate_sessions" object.user.pk %}',
|
|
method: 'POST',
|
|
data: {
|
|
'csrfmiddlewaretoken': '{{ csrf_token }}'
|
|
},
|
|
success: function(response) {
|
|
if (response.success) {
|
|
toastr.success('All user sessions terminated successfully');
|
|
location.reload();
|
|
} else {
|
|
toastr.error('Failed to terminate user sessions');
|
|
}
|
|
},
|
|
error: function() {
|
|
toastr.error('An error occurred while terminating user sessions');
|
|
}
|
|
});
|
|
}
|
|
}
|
|
{% endif %}
|
|
|
|
function refreshSessionData() {
|
|
location.reload();
|
|
}
|
|
|
|
function exportSessionData() {
|
|
window.location.href = '{% url "accounts:session_export" %}?session={{ object.session_key }}';
|
|
}
|
|
|
|
function lookupIP(ipAddress) {
|
|
// Implementation for IP lookup
|
|
toastr.info('IP lookup functionality will be implemented');
|
|
}
|
|
|
|
// Auto-refresh every 30 seconds if session is active
|
|
{% if object.is_active %}
|
|
setInterval(function() {
|
|
location.reload();
|
|
}, 30000);
|
|
{% endif %}
|
|
</script>
|
|
{% endblock %}
|
|
|