cleanup backend gui

This commit is contained in:
lelo 2025-04-02 20:09:50 +02:00
parent 1c0a1a0182
commit 262a31e65a
4 changed files with 389 additions and 370 deletions

View File

@ -145,13 +145,12 @@ def dashboard():
now = datetime.now() now = datetime.now()
# Determine which file type we're filtering by. # default filetype if not found
filetype = 'other' filetype = 'other'
# Some simplistic sets to decide how we match the MIME type # Some simplistic sets to decide how we match the MIME type
audio_list = ['mp3', 'wav', 'audio'] audio_list = ['mp3', 'wav', 'ton', 'audio']
image_list = ['jpg', 'jpeg', 'image', 'photo'] image_list = ['jpg', 'jpeg', 'image', 'photo', 'bild', 'foto']
video_list = ['mp4', 'mov', 'wmv', 'avi'] video_list = ['mp4', 'mov', 'wmv', 'avi', 'film', 'video']
if session['filetype'].lower() in audio_list: if session['filetype'].lower() in audio_list:
filetype = 'audio/' filetype = 'audio/'
@ -292,9 +291,9 @@ def dashboard():
dict(bucket=r[0], count=r[1]) for r in timeframe_data_rows dict(bucket=r[0], count=r[1]) for r in timeframe_data_rows
] ]
# 4. User agent distribution # 4. User agent distribution: Count user_agent once per device_id
query = f''' query = f'''
SELECT user_agent, COUNT(*) AS count SELECT user_agent, COUNT(DISTINCT device_id) AS count
FROM file_access_log FROM file_access_log
WHERE timestamp >= ? {filetype_filter_sql} WHERE timestamp >= ? {filetype_filter_sql}
GROUP BY user_agent GROUP BY user_agent

View File

@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Recent Connections</title> <title>Recent Connections</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet">
@ -11,13 +11,11 @@
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
/* Use full width */
.container-fluid { .container-fluid {
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
/* Header area takes as much room as needed; the table container fills the rest */
.table-container { .table-container {
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
@ -25,17 +23,25 @@
</style> </style>
</head> </head>
<body> <body>
<div class="container-fluid"> <!-- Navigation Bar -->
<h1 class="mb-4">Downloads in den letzten 10 Minute</h1> <nav class="navbar navbar-expand-lg navbar-dark bg-secondary mb-3">
<div class="mb-3"> <div class="container-fluid">
<a href="{{ url_for('index') }}" class="btn btn-primary mt-1">App</a> <a class="navbar-brand" href="#">Downloads der letzten 10 Minuten</a>
<a href="{{ url_for('mylinks') }}" class="btn btn-primary mt-1">Meine Links</a> <div class="navbar-collapse" id="navbarNav">
<a href="{{ url_for('connections') }}" class="btn btn-primary mt-1">Verbindungen</a> <ul class="navbar-nav ms-auto">
<a href="{{ url_for('dashboard') }}" class="btn btn-primary mt-1">Auswertung</a> <li class="nav-item"><a href="{{ url_for('index') }}" class="nav-link">App</a></li>
<li class="nav-item"><a href="{{ url_for('mylinks') }}" class="nav-link">Meine Links</a></li>
<li class="nav-item"><a href="{{ url_for('connections') }}" class="nav-link">Verbindungen</a></li>
<li class="nav-item"><a href="{{ url_for('dashboard') }}" class="nav-link">Auswertung</a></li>
</ul>
</div>
</div> </div>
</nav>
<div class="container-fluid">
<div class="table-responsive table-container"> <div class="table-responsive table-container">
<table class="table table-hover"> <table class="table table-hover">
<thead class="table-info"> <thead class="table-secondary">
<tr> <tr>
<th>Timestamp</th> <th>Timestamp</th>
<th>IP Address</th> <th>IP Address</th>

View File

@ -11,375 +11,382 @@
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
/* Use full width */
.container-fluid { .container-fluid {
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.card { margin-bottom: 20px; } .card {
margin-bottom: 1.5rem;
}
</style> </style>
</head> </head>
<body> <body>
<div class="container-fluid"> <!-- Navigation Bar -->
<h1 class="mb-4">Dashboard - Verbindungsanalyse ({{ timeframe }})</h1> <nav class="navbar navbar-expand-lg navbar-dark bg-secondary mb-3">
<div class="mb-3"> <div class="container-fluid">
<a href="{{ url_for('index') }}" class="btn btn-primary mt-1">App</a> <a class="navbar-brand" href="#">Dashboard - Verbindungsanalyse</a>
<a href="{{ url_for('mylinks') }}" class="btn btn-primary mt-1">Meine Links</a> <div class="navbar-collapse" id="navbarNav">
<a href="{{ url_for('connections') }}" class="btn btn-primary mt-1">Verbindungen</a> <ul class="navbar-nav ms-auto">
<a href="{{ url_for('dashboard') }}" class="btn btn-primary mt-1">Auswertung</a> <li class="nav-item"><a href="{{ url_for('index') }}" class="nav-link">App</a></li>
</div> <li class="nav-item"><a href="{{ url_for('mylinks') }}" class="nav-link">Meine Links</a></li>
<!-- Timeframe Dropdown --> <li class="nav-item"><a href="{{ url_for('connections') }}" class="nav-link">Verbindungen</a></li>
<div class="mb-3"> <li class="nav-item"><a href="{{ url_for('dashboard') }}" class="nav-link">Auswertung</a></li>
<div class="dropdown"> </ul>
<button class="btn btn-sm mt-1 dropdown-toggle btn-secondary" </div>
type="button" id="timeframeDropdown" data-bs-toggle="dropdown" aria-expanded="false"> </div>
{% if session['timeframe'] == 'last24hours' %} </nav>
Last 24 Hours
{% elif session['timeframe'] == '7days' %} <!-- Main Container -->
Last 7 Days <div class="container-fluid px-4">
{% elif session['timeframe'] == '30days' %} <!-- Dropdown Controls -->
Last 30 Days <div class="mb-4 d-flex flex-wrap gap-2">
{% elif session['timeframe'] == '365days' %} <!-- Timeframe Dropdown -->
Last 365 Days <div class="dropdown">
{% else %} <button class="btn btn-secondary dropdown-toggle"
Select Timeframe type="button" id="timeframeDropdown" data-bs-toggle="dropdown" aria-expanded="false">
{% endif %} {% if session['timeframe'] == 'last24hours' %}
</button>
<ul class="dropdown-menu" aria-labelledby="timeframeDropdown">
<li>
<a class="dropdown-item {% if session['timeframe'] == 'last24hours' %}active{% endif %}"
href="{{ url_for('dashboard', timeframe='last24hours') }}">
Last 24 Hours Last 24 Hours
</a> {% elif session['timeframe'] == '7days' %}
</li>
<li>
<a class="dropdown-item {% if session['timeframe'] == '7days' %}active{% endif %}"
href="{{ url_for('dashboard', timeframe='7days') }}">
Last 7 Days Last 7 Days
</a> {% elif session['timeframe'] == '30days' %}
</li>
<li>
<a class="dropdown-item {% if session['timeframe'] == '30days' %}active{% endif %}"
href="{{ url_for('dashboard', timeframe='30days') }}">
Last 30 Days Last 30 Days
</a> {% elif session['timeframe'] == '365days' %}
</li>
<li>
<a class="dropdown-item {% if session['timeframe'] == '365days' %}active{% endif %}"
href="{{ url_for('dashboard', timeframe='365days') }}">
Last 365 Days Last 365 Days
</a> {% else %}
</li> Select Timeframe
</ul> {% endif %}
<!-- Filetype Dropdown --> </button>
<button class="btn btn-sm mt-1 dropdown-toggle btn-secondary" <ul class="dropdown-menu" aria-labelledby="timeframeDropdown">
type="button" id="filetypeDropdown" data-bs-toggle="dropdown" aria-expanded="false"> <li>
{% if session['filetype'] == 'audio' %} <a class="dropdown-item {% if session['timeframe'] == 'last24hours' %}active{% endif %}"
Audio href="{{ url_for('dashboard', timeframe='last24hours') }}">
{% elif session['filetype'] == 'video' %} Last 24 Hours
Video </a>
{% elif session['filetype'] == 'photo' %} </li>
Photo <li>
{% elif session['filetype'] == 'other' %} <a class="dropdown-item {% if session['timeframe'] == '7days' %}active{% endif %}"
Other href="{{ url_for('dashboard', timeframe='7days') }}">
{% else %} Last 7 Days
Select File Type </a>
{% endif %} </li>
</button> <li>
<ul class="dropdown-menu" aria-labelledby="filetypeDropdown"> <a class="dropdown-item {% if session['timeframe'] == '30days' %}active{% endif %}"
<li> href="{{ url_for('dashboard', timeframe='30days') }}">
<a class="dropdown-item {% if session['filetype'] == 'audio' %}active{% endif %}" Last 30 Days
href="{{ url_for('dashboard', filetype='audio') }}"> </a>
</li>
<li>
<a class="dropdown-item {% if session['timeframe'] == '365days' %}active{% endif %}"
href="{{ url_for('dashboard', timeframe='365days') }}">
Last 365 Days
</a>
</li>
</ul>
</div>
<!-- Filetype Dropdown -->
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle"
type="button" id="filetypeDropdown" data-bs-toggle="dropdown" aria-expanded="false">
{% if session['filetype'] == 'audio' %}
Audio Audio
</a> {% elif session['filetype'] == 'video' %}
</li>
<li>
<a class="dropdown-item {% if session['filetype'] == 'video' %}active{% endif %}"
href="{{ url_for('dashboard', filetype='video') }}">
Video Video
</a> {% elif session['filetype'] == 'photo' %}
</li>
<li>
<a class="dropdown-item {% if session['filetype'] == 'photo' %}active{% endif %}"
href="{{ url_for('dashboard', filetype='photo') }}">
Photo Photo
</a> {% elif session['filetype'] == 'other' %}
</li>
<li>
<a class="dropdown-item {% if session['filetype'] == 'other' %}active{% endif %}"
href="{{ url_for('dashboard', filetype='other') }}">
Other Other
</a>
</li>
</ul>
</div>
</div>
<!-- Summary Cards -->
<div class="row mb-4">
<div class="col-md-3">
<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-3">
<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-3">
<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 class="col-md-3">
<div class="card text-white bg-secondary">
<div class="card-body">
<h5 class="card-title">beschleunigte Downloads</h5>
<p class="card-text">{{ cached_percentage }} %</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 %} {% else %}
<tr> Select File Type
<td colspan="4">No access data available for the selected timeframe.</td> {% endif %}
</tr> </button>
{% endfor %} <ul class="dropdown-menu" aria-labelledby="filetypeDropdown">
</tbody> <li>
</table> <a class="dropdown-item {% if session['filetype'] == 'audio' %}active{% endif %}"
href="{{ url_for('dashboard', filetype='audio') }}">
Audio
</a>
</li>
<li>
<a class="dropdown-item {% if session['filetype'] == 'video' %}active{% endif %}"
href="{{ url_for('dashboard', filetype='video') }}">
Video
</a>
</li>
<li>
<a class="dropdown-item {% if session['filetype'] == 'photo' %}active{% endif %}"
href="{{ url_for('dashboard', filetype='photo') }}">
Photo
</a>
</li>
<li>
<a class="dropdown-item {% if session['filetype'] == 'other' %}active{% endif %}"
href="{{ url_for('dashboard', filetype='other') }}">
Other
</a>
</li>
</ul>
</div>
</div>
<!-- Summary Cards -->
<div class="row mb-4">
<div class="col-md-3 mb-3">
<div class="card bg-info text-white">
<div class="card-body">
<h5 class="card-title">Alle Downloads</h5>
<p class="card-text display-6">{{ total_accesses }}</p>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card bg-success text-white">
<div class="card-body">
<h5 class="card-title">unterschiedliche Dateien</h5>
<p class="card-text display-6">{{ unique_files }}</p>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card bg-warning text-white">
<div class="card-body">
<h5 class="card-title">unterschiedliche Nutzer</h5>
<p class="card-text display-6">{{ unique_user }}</p>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card bg-secondary text-white">
<div class="card-body">
<h5 class="card-title">beschleunigte Downloads</h5>
<p class="card-text display-6">{{ cached_percentage }} %</p>
</div>
</div>
</div>
</div>
<!-- Charts Section -->
<div class="row mb-4">
<!-- Access Trend Chart -->
<div class="col-md-6 mb-3">
<div class="card">
<div class="card-body">
<h5 class="card-title">Anzahl Nutzer</h5>
<canvas id="distinctDeviceChart"></canvas>
</div>
</div>
</div>
<!-- Timeframe Breakdown Chart -->
<div class="col-md-6 mb-3">
<div class="card">
<div class="card-body">
<h5 class="card-title">Anzahl Downloads</h5>
<canvas id="downloadTimeframeChart"></canvas>
</div>
</div>
</div>
<!-- User Agent Distribution Chart -->
<div class="col-md-6 mb-3">
<div class="card">
<div class="card-body">
<h5 class="card-title">Endgeräte der Nutzer</h5>
<canvas id="userAgentChart"></canvas>
</div>
</div>
</div>
<!-- Folder Distribution Chart -->
<div class="col-md-6 mb-3">
<div class="card">
<div class="card-body">
<h5 class="card-title">Verteilung auf Ordner</h5>
<canvas id="folderChart"></canvas>
</div>
</div>
</div>
</div>
<!-- IP Address Access Data -->
<div class="card mb-4">
<div class="card-header">
Verteilung der Zugriffe
</div>
<div class="card-body">
<div class="table-responsive">
<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="3">No access data available for the selected timeframe.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- Detailed Table of Top File Accesses -->
<div class="card mb-4">
<div class="card-header">
Detaillierte Dateizugriffe (Top 20)
</div>
<div class="card-body">
<div class="table-responsive">
<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> </div>
</div> </div>
<!-- Detailed Table of Top File Accesses --> <!-- Scripts -->
<div class="card mb-4"> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<div class="card-header"> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
Detailierte Dateizugriffe (Top 20) <script>
</div> // Data passed from the backend as JSON
<div class="card-body"> let distinctDeviceData = {{ distinct_device_data|tojson }};
<table class="table table-striped"> let timeframeData = {{ timeframe_data|tojson }};
<thead> const userAgentData = {{ user_agent_data|tojson }};
<tr> const folderData = {{ folder_data|tojson }};
<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> // Remove the first (incomplete) bucket from the arrays
distinctDeviceData = distinctDeviceData.slice(1);
timeframeData = timeframeData.slice(1);
<!-- Load Chart.js from CDN --> // Shift the labels to local time zone
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> const timeframe = "{{ timeframe }}"; // e.g., 'last24hours', '7days', '30days', or '365days'
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> const shiftedLabels = timeframeData.map((item) => {
<script> if (timeframe === 'last24hours') {
// Data passed from the backend as JSON const bucketDate = new Date(item.bucket);
let distinctDeviceData = {{ distinct_device_data|tojson }}; const now = new Date();
let timeframeData = {{ timeframe_data|tojson }}; const isCurrentHour =
const userAgentData = {{ user_agent_data|tojson }}; bucketDate.getFullYear() === now.getFullYear() &&
const folderData = {{ folder_data|tojson }}; bucketDate.getMonth() === now.getMonth() &&
bucketDate.getDate() === now.getDate() &&
bucketDate.getHours() === now.getHours();
const bucketEnd = isCurrentHour ? now : new Date(bucketDate.getTime() + 3600 * 1000);
return `${bucketDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} - ${bucketEnd.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`;
} else if (timeframe === '7days' || timeframe === '30days') {
const localDate = new Date(item.bucket);
return localDate.toLocaleDateString();
} else if (timeframe === '365days') {
const [year, month] = item.bucket.split('-');
const dateObj = new Date(year, month - 1, 1);
return dateObj.toLocaleString([], { month: 'short', year: 'numeric' });
} else {
return item.bucket;
}
});
// Remove the first (incomplete) bucket from the arrays // Distinct Device Chart
distinctDeviceData = distinctDeviceData.slice(1); const ctxDistinctDevice = document.getElementById('distinctDeviceChart').getContext('2d');
timeframeData = timeframeData.slice(1); new Chart(ctxDistinctDevice, {
type: 'bar',
// shift the labels to local time zone data: {
const timeframe = "{{ timeframe }}"; // e.g., 'last24hours', '7days', '30days', or '365days' labels: shiftedLabels,
const shiftedLabels = timeframeData.map((item, index) => { datasets: [{
if (timeframe === 'last24hours') { label: 'Device Count',
data: distinctDeviceData.map(item => item.count),
// item.bucket is now in the format "YYYY-MM-DDTHH:00:00Z" borderWidth: 2
const bucketDate = new Date(item.bucket); }]
const now = new Date(); },
options: {
// Check if this bucket corresponds to the current hour (in client's local time) responsive: true,
const isCurrentHour = plugins: { legend: { display: false } },
bucketDate.getFullYear() === now.getFullYear() && scales: {
bucketDate.getMonth() === now.getMonth() && x: { title: { display: true, text: 'Time Range' } },
bucketDate.getDate() === now.getDate() && y: {
bucketDate.getHours() === now.getHours(); title: { display: true, text: 'Device Count' },
beginAtZero: true,
// For the current hour, use the current time as the bucket end. ticks: { stepSize: 1 }
const bucketEnd = isCurrentHour ? now : new Date(bucketDate.getTime() + 3600 * 1000);
return `${bucketDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} - ${bucketEnd.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`;
} else if (timeframe === '7days' || timeframe === '30days') {
const localDate = new Date(item.bucket);
return localDate.toLocaleDateString();
} else if (timeframe === '365days') {
const [year, month] = item.bucket.split('-');
const dateObj = new Date(year, month - 1, 1);
return dateObj.toLocaleString([], { month: 'short', year: 'numeric' });
} else {
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 'last24hours' timeframe) // Timeframe Breakdown Chart
const ctxTimeframe = document.getElementById('downloadTimeframeChart').getContext('2d'); const ctxTimeframe = document.getElementById('downloadTimeframeChart').getContext('2d');
new Chart(ctxTimeframe, { new Chart(ctxTimeframe, {
type: 'bar', type: 'bar',
data: { data: {
labels: shiftedLabels, labels: shiftedLabels,
datasets: [{ datasets: [{
label: 'Download Count', label: 'Download Count',
data: timeframeData.map(item => item.count), data: timeframeData.map(item => item.count),
borderWidth: 2 borderWidth: 2
}] }]
}, },
options: { options: {
responsive: true, responsive: true,
plugins: { legend: { display: false } }, plugins: { legend: { display: false } },
scales: { scales: {
x: { title: { display: true, text: 'Time Range' } }, x: { title: { display: true, text: 'Time Range' } },
y: { y: {
title: { display: true, text: 'Download Count' }, title: { display: true, text: 'Download Count' },
beginAtZero: true, beginAtZero: true,
ticks: { ticks: { stepSize: 1 }
stepSize: 1
} }
} }
} }
} });
});
// User Agent Distribution - Pie Chart (using aggregated device data) // User Agent Distribution Chart - Pie Chart
const ctxUserAgent = document.getElementById('userAgentChart').getContext('2d'); const ctxUserAgent = document.getElementById('userAgentChart').getContext('2d');
new Chart(ctxUserAgent, { new Chart(ctxUserAgent, {
type: 'pie', type: 'pie',
data: { data: {
labels: userAgentData.map(item => item.device), labels: userAgentData.map(item => item.device),
datasets: [{ datasets: [{
data: userAgentData.map(item => item.count) data: userAgentData.map(item => item.count)
}] }]
}, },
options: { responsive: true } options: { responsive: true }
}); });
// folder Distribution - Pie Chart (with shortened folders) // Folder Distribution Chart - Pie Chart
const ctxfolder = document.getElementById('folderChart').getContext('2d'); const ctxFolder = document.getElementById('folderChart').getContext('2d');
new Chart(ctxfolder, { new Chart(ctxFolder, {
type: 'pie', type: 'pie',
data: { data: {
labels: folderData.map(item => item.folder), labels: folderData.map(item => item.folder),
datasets: [{ datasets: [{
data: folderData.map(item => item.count) data: folderData.map(item => item.count)
}] }]
}, },
options: { responsive: true } options: { responsive: true }
}); });
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,17 +1,16 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="UTF-8">
<title>Meine Links</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <title>Meine Links</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet">
<style> <style>
html, body { html, body {
height: 100%; height: 100%;
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
/* Use full width */
.container-fluid { .container-fluid {
height: 100%; height: 100%;
display: flex; display: flex;
@ -25,14 +24,22 @@
</style> </style>
</head> </head>
<body> <body>
<div class="container-fluid"> <!-- Navigation Bar -->
<h1 class="mb-4">Übersicht deiner gültigen Links</h1> <nav class="navbar navbar-expand-lg navbar-dark bg-secondary mb-3">
<div class="mb-3"> <div class="container-fluid">
<a href="{{ url_for('index') }}" class="btn btn-primary mt-1">App</a> <a class="navbar-brand" href="#">Übersicht deiner gültigen Links</a>
<a href="{{ url_for('mylinks') }}" class="btn btn-primary mt-1">Meine Links</a> <div class="navbar-collapse" id="navbarNav">
<a href="{{ url_for('connections') }}" class="btn btn-primary mt-1">Verbindungen</a> <ul class="navbar-nav ms-auto">
<a href="{{ url_for('dashboard') }}" class="btn btn-primary mt-1">Auswertung</a> <li class="nav-item"><a href="{{ url_for('index') }}" class="nav-link">App</a></li>
<li class="nav-item"><a href="{{ url_for('mylinks') }}" class="nav-link">Meine Links</a></li>
<li class="nav-item"><a href="{{ url_for('connections') }}" class="nav-link">Verbindungen</a></li>
<li class="nav-item"><a href="{{ url_for('dashboard') }}" class="nav-link">Auswertung</a></li>
</ul>
</div>
</div> </div>
</nav>
<div class="container-fluid">
{% if allowed_secrets %} {% if allowed_secrets %}
<div class="row"> <div class="row">
{% for secret in allowed_secrets %} {% for secret in allowed_secrets %}