201 lines
8.6 KiB
HTML
201 lines
8.6 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Admin - Registered Devices{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h1>Registered Devices</h1>
|
|
<div>
|
|
<span class="badge bg-secondary">Total: {{ devices }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
{% if devices %}
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-striped table-hover">
|
|
<thead class="table-dark">
|
|
<tr>
|
|
<th>Device Name</th>
|
|
<th>Fingerprint</th>
|
|
<th>First Seen</th>
|
|
<th>Last Seen</th>
|
|
<th>User Agent</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for device in devices %}
|
|
<tr>
|
|
<td>
|
|
{% if device.device_name %}
|
|
<strong>{{ device.device_name }}</strong>
|
|
{% else %}
|
|
<em class="text-muted">Unnamed Device</em>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<code class="small">{{ device.fingerprint_hash }}...</code>
|
|
<button class="btn btn-sm btn-outline-secondary ms-1"
|
|
onclick="copyToClipboard('{{ device.fingerprint_hash }}')"
|
|
title="Copy full fingerprint">
|
|
📋
|
|
</button>
|
|
</td>
|
|
<td class="small">{{ device.first_seen }}</td>
|
|
<td class="small">{{ device.last_seen }}</td>
|
|
<td class="small text-muted" style="max-width: 300px;">
|
|
{% if device.user_agent %}
|
|
<div class="text-truncate" title="{{ device.user_agent }}">
|
|
{{ device.user_agent }}
|
|
</div>
|
|
{% else %}
|
|
<em>Unknown</em>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div class="btn-group btn-group-sm">
|
|
<button class="btn btn-outline-info"
|
|
onclick="showDeviceDetails('{{ device.id }}', '{{ device.fingerprint_hash }}', '{{ device.device_name }}', '{{ device.first_seen }}', '{{ device.last_seen }}', '{{ device.user_agent }}')"
|
|
title="View Details">
|
|
👁️
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-4">
|
|
<h3>Statistics</h3>
|
|
<div class="row">
|
|
<div class="col-md-3">
|
|
<div class="card bg-primary text-white">
|
|
<div class="card-body">
|
|
<h5>Total Devices</h5>
|
|
<h2>{{ devices }}</h2>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card bg-success text-white">
|
|
<div class="card-body">
|
|
<h5>Named Devices</h5>
|
|
<h2>{{ named_devices_count }}</h2>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card bg-warning text-white">
|
|
<div class="card-body">
|
|
<h5>Recent (24h)</h5>
|
|
<h2>{{ recent_devices_count }}</h2>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card bg-info text-white">
|
|
<div class="card-body">
|
|
<h5>This Week</h5>
|
|
<h2>{{ weekly_devices_count }}</h2>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% else %}
|
|
<div class="text-center py-5">
|
|
<h3 class="text-muted">No Devices Registered</h3>
|
|
<p class="text-muted">Visit the <a href="/">home page</a> to register your first device.</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Device Details Modal -->
|
|
<div class="modal fade" id="deviceModal" tabindex="-1">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Device Details</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div id="device-details-content">
|
|
<!-- Content will be populated by JavaScript -->
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
function copyToClipboard(text) {
|
|
navigator.clipboard.writeText(text).then(function() {
|
|
// Could add a toast notification here
|
|
console.log('Fingerprint copied to clipboard');
|
|
});
|
|
}
|
|
|
|
function showDeviceDetails(id, fingerprint, name, firstSeen, lastSeen, userAgent) {
|
|
const content = document.getElementById('device-details-content');
|
|
content.innerHTML = `
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<table class="table table-borderless">
|
|
<tbody>
|
|
<tr>
|
|
<td><strong>Device ID:</strong></td>
|
|
<td><code>${id}</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Device Name:</strong></td>
|
|
<td>${name || '<em>Unnamed</em>'}</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Fingerprint Hash:</strong></td>
|
|
<td>
|
|
<code style="word-break: break-all;">${fingerprint}</code>
|
|
<button class="btn btn-sm btn-outline-secondary ms-2"
|
|
onclick="copyToClipboard('${fingerprint}')"
|
|
title="Copy fingerprint">
|
|
📋 Copy
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>First Seen:</strong></td>
|
|
<td>${firstSeen}</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Last Seen:</strong></td>
|
|
<td>${lastSeen}</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>User Agent:</strong></td>
|
|
<td style="word-break: break-word;">${userAgent || '<em>Unknown</em>'}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
const modal = new bootstrap.Modal(document.getElementById('deviceModal'));
|
|
modal.show();
|
|
}
|
|
</script>
|
|
{% endblock %} |