cleanup backend gui
This commit is contained in:
parent
1c0a1a0182
commit
262a31e65a
13
analytics.py
13
analytics.py
@ -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
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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 %}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user