293 lines
9.7 KiB
HTML
293 lines
9.7 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>Dashboard - Verbindungsanalyse</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<style>
|
|
html, body {
|
|
height: 100%;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
/* Use full width */
|
|
.container-fluid {
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.card { margin-bottom: 20px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container-fluid">
|
|
<h1 class="mb-4">Dashboard - Verbindungsanalyse ({{ timeframe }})</h1>
|
|
<div class="mb-3">
|
|
<a href="{{ url_for('index') }}" class="btn btn-primary mt-1">App</a>
|
|
<a href="{{ url_for('mylinks') }}" class="btn btn-primary mt-1">Meine Links</a>
|
|
<a href="{{ url_for('connections') }}" class="btn btn-primary mt-1">Verbindungen</a>
|
|
<a href="{{ url_for('dashboard') }}" class="btn btn-primary mt-1">Auswertung</a>
|
|
</div>
|
|
<div class="mb-3">
|
|
<a href="{{ url_for('dashboard', timeframe='today') }}" class="btn btn-secondary btn-sm mt-1">Today</a>
|
|
<a href="{{ url_for('dashboard', timeframe='7days') }}" class="btn btn-secondary btn-sm mt-1">Last 7 Days</a>
|
|
<a href="{{ url_for('dashboard', timeframe='30days') }}" class="btn btn-secondary btn-sm mt-1">Last 30 Days</a>
|
|
<a href="{{ url_for('dashboard', timeframe='365days') }}" class="btn btn-secondary btn-sm mt-1">Last 365 Days</a>
|
|
</div>
|
|
|
|
<!-- Summary Cards -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-4">
|
|
<div class="card text-white bg-info">
|
|
<div class="card-body">
|
|
<h5 class="card-title">Alle Downloads</h5>
|
|
<p class="card-text">{{ total_accesses }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="card text-white bg-success">
|
|
<div class="card-body">
|
|
<h5 class="card-title">eindeutige Dateien</h5>
|
|
<p class="card-text">{{ unique_files }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="card text-white bg-warning">
|
|
<div class="card-body">
|
|
<h5 class="card-title">eindeutige Nutzer</h5>
|
|
<p class="card-text">{{ unique_user }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Charts Section -->
|
|
<div class="row">
|
|
<!-- Access Trend Chart -->
|
|
<div class="col-md-6 mb-4">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<h5 class="card-title">eindeutige Nutzer nach Zeit</h5>
|
|
<canvas id="distinctDeviceChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Timeframe Breakdown Chart (Bar Chart) -->
|
|
<div class="col-md-6 mb-4">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<h5 class="card-title">Downloads nach Zeit</h5>
|
|
<canvas id="downloadTimeframeChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- User Agent Distribution Chart -->
|
|
<div class="col-md-6 mb-4">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<h5 class="card-title">Verwendete Endgeräte</h5>
|
|
<canvas id="userAgentChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Folder Distribution Chart -->
|
|
<div class="col-md-6 mb-4">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<h5 class="card-title">Verteilung auf Ordner</h5>
|
|
<canvas id="folderChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- New Section: IP Address Access Data -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
Verteilung der Zugriffe
|
|
</div>
|
|
<div class="card-body">
|
|
<table class="table table-bordered">
|
|
<thead>
|
|
<tr>
|
|
<th>Anzahl Downloads</th>
|
|
<th>Stadt</th>
|
|
<th>Land</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for loc in location_data %}
|
|
<tr>
|
|
<td>{{ loc.count }}</td>
|
|
<td>{{ loc.city }}</td>
|
|
<td>{{ loc.country }}</td>
|
|
</tr>
|
|
{% else %}
|
|
<tr>
|
|
<td colspan="4">No access data available for the selected timeframe.</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Detailed Table of Top File Accesses -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
Detailierte Dateizugriffe (Top 20)
|
|
</div>
|
|
<div class="card-body">
|
|
<table class="table table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>File Path</th>
|
|
<th>Access Count</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for row in rows %}
|
|
<tr>
|
|
<td>{{ row.rel_path }}</td>
|
|
<td>{{ row.access_count }}</td>
|
|
</tr>
|
|
{% else %}
|
|
<tr>
|
|
<td colspan="2">No data available for the selected timeframe.</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- Load Chart.js from CDN -->
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
<script>
|
|
// Data passed from the backend as JSON
|
|
const distinctDeviceData = {{ distinct_device_data|tojson }};
|
|
// Replace topFilesData usage with timeframeData for this chart
|
|
const timeframeData = {{ timeframe_data|tojson }};
|
|
const userAgentData = {{ user_agent_data|tojson }};
|
|
const folderData = {{ folder_data|tojson }};
|
|
|
|
// shift the labels to local time zone
|
|
const timeframe = "{{ timeframe }}"; // e.g., 'today', '7days', '30days', or '365days'
|
|
const shiftedLabels = timeframeData.map(item => {
|
|
if (timeframe === 'today') {
|
|
// For "today", the bucket is an hour in UTC (e.g., "14")
|
|
const utcHour = parseInt(item.bucket, 10);
|
|
const now = new Date();
|
|
// Create Date objects for the start and end of the hour in UTC
|
|
const utcStart = new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate(), utcHour));
|
|
const utcEnd = new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate(), utcHour + 1));
|
|
// Convert to local time strings, e.g., "16:00"
|
|
const localStart = utcStart.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
const localEnd = utcEnd.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
return `${localStart} - ${localEnd}`;
|
|
} else if (timeframe === '7days' || timeframe === '30days') {
|
|
// For these timeframes, the bucket is a date in the format "YYYY-MM-DD"
|
|
const utcDate = new Date(item.bucket + 'T00:00:00Z');
|
|
return utcDate.toLocaleDateString(); // Adjust formatting as needed
|
|
} else if (timeframe === '365days') {
|
|
// For this timeframe, the bucket is a month in the format "YYYY-MM"
|
|
const [year, month] = item.bucket.split('-');
|
|
const dateObj = new Date(year, month - 1, 1);
|
|
// Format to something like "Mar 2025"
|
|
return dateObj.toLocaleString([], { month: 'short', year: 'numeric' });
|
|
} else {
|
|
// Fallback: use the bucket value as-is
|
|
return item.bucket;
|
|
}
|
|
});
|
|
|
|
// Distinct Device Chart
|
|
const ctxDistinctDevice = document.getElementById('distinctDeviceChart').getContext('2d');
|
|
new Chart(ctxDistinctDevice, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: shiftedLabels,
|
|
datasets: [{
|
|
label: 'Device Count',
|
|
data: distinctDeviceData.map(item => item.count),
|
|
borderWidth: 2
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
plugins: { legend: { display: false } },
|
|
scales: {
|
|
x: { title: { display: true, text: 'Time Range' } },
|
|
y: {
|
|
title: { display: true, text: 'Device Count' },
|
|
beginAtZero: true,
|
|
ticks: {
|
|
stepSize: 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Timeframe Breakdown Chart - Bar Chart (for "today" timeframe)
|
|
const ctxTimeframe = document.getElementById('downloadTimeframeChart').getContext('2d');
|
|
new Chart(ctxTimeframe, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: shiftedLabels,
|
|
datasets: [{
|
|
label: 'Download Count',
|
|
data: timeframeData.map(item => item.count),
|
|
borderWidth: 2
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
plugins: { legend: { display: false } },
|
|
scales: {
|
|
x: { title: { display: true, text: 'Time Range' } },
|
|
y: {
|
|
title: { display: true, text: 'Download Count' },
|
|
beginAtZero: true,
|
|
ticks: {
|
|
stepSize: 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// User Agent Distribution - Pie Chart (using aggregated device data)
|
|
const ctxUserAgent = document.getElementById('userAgentChart').getContext('2d');
|
|
new Chart(ctxUserAgent, {
|
|
type: 'pie',
|
|
data: {
|
|
labels: userAgentData.map(item => item.device),
|
|
datasets: [{
|
|
data: userAgentData.map(item => item.count)
|
|
}]
|
|
},
|
|
options: { responsive: true }
|
|
});
|
|
|
|
// folder Distribution - Pie Chart (with shortened folders)
|
|
const ctxfolder = document.getElementById('folderChart').getContext('2d');
|
|
new Chart(ctxfolder, {
|
|
type: 'pie',
|
|
data: {
|
|
labels: folderData.map(item => item.folder),
|
|
datasets: [{
|
|
data: folderData.map(item => item.count)
|
|
}]
|
|
},
|
|
options: { responsive: true }
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|