Compare commits
No commits in common. "91732472123e7334a4f32d80705373758d218aeb" and "1c9fe28fc114df7001147e1835077146d5e5a07a" have entirely different histories.
9173247212
...
1c9fe28fc1
30
analytics.py
30
analytics.py
@ -4,7 +4,6 @@ from datetime import datetime, timedelta, timezone
|
|||||||
import geoip2.database
|
import geoip2.database
|
||||||
from auth import require_secret
|
from auth import require_secret
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import pandas as pd
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import auth
|
import auth
|
||||||
@ -384,29 +383,12 @@ def dashboard():
|
|||||||
WHERE timestamp >= ? {filetype_filter_sql}
|
WHERE timestamp >= ? {filetype_filter_sql}
|
||||||
GROUP BY rel_path
|
GROUP BY rel_path
|
||||||
ORDER BY access_count DESC
|
ORDER BY access_count DESC
|
||||||
LIMIT 1000
|
LIMIT 20
|
||||||
'''
|
'''
|
||||||
with log_db:
|
with log_db:
|
||||||
cursor = log_db.execute(query, params_for_filter)
|
cursor = log_db.execute(query, params_for_filter)
|
||||||
rows = cursor.fetchall()
|
rows = cursor.fetchall()
|
||||||
|
|
||||||
# Convert rows to a list of dictionaries and add category
|
|
||||||
rows = [
|
|
||||||
{
|
|
||||||
'rel_path': rel_path,
|
|
||||||
'access_count': access_count,
|
|
||||||
'category': hf.extract_structure_from_string(rel_path)[0]
|
|
||||||
}
|
|
||||||
for rel_path, access_count in rows
|
|
||||||
]
|
|
||||||
|
|
||||||
top20_overall = rows[:20]
|
|
||||||
top20_predigt = [r for r in rows if r['category'] == 'Predigt'][:20]
|
|
||||||
top20_chor = [r for r in rows if r['category'] == 'Chor'][:20]
|
|
||||||
top20_ggesang = [r for r in rows if r['category'] == 'Gemeinsamer Gesang'][:20]
|
|
||||||
top20_glied = [r for r in rows if r['category'] == 'Gruppenlied'][:20]
|
|
||||||
|
|
||||||
|
|
||||||
# 2. Distinct device trend
|
# 2. Distinct device trend
|
||||||
# We'll group by hour if "today", by day if "7days"/"30days", by month if "365days"
|
# We'll group by hour if "today", by day if "7days"/"30days", by month if "365days"
|
||||||
if session['timeframe'] == 'last24hours':
|
if session['timeframe'] == 'last24hours':
|
||||||
@ -606,17 +588,16 @@ def dashboard():
|
|||||||
location_data.sort(key=lambda x: x['count'], reverse=True)
|
location_data.sort(key=lambda x: x['count'], reverse=True)
|
||||||
location_data = location_data[:20]
|
location_data = location_data[:20]
|
||||||
|
|
||||||
|
# Convert the top-files rows to a list of dictionaries
|
||||||
|
rows = [dict(rel_path=r[0], access_count=r[1]) for r in rows]
|
||||||
|
|
||||||
title_short = app_config.get('TITLE_SHORT', 'Default Title')
|
title_short = app_config.get('TITLE_SHORT', 'Default Title')
|
||||||
title_long = app_config.get('TITLE_LONG' , 'Default Title')
|
title_long = app_config.get('TITLE_LONG' , 'Default Title')
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"dashboard.html",
|
"dashboard.html",
|
||||||
timeframe=session['timeframe'],
|
timeframe=session['timeframe'],
|
||||||
top20_overall = top20_overall,
|
rows=rows,
|
||||||
top20_predigt = top20_predigt,
|
|
||||||
top20_chor = top20_chor,
|
|
||||||
top20_ggesang = top20_ggesang,
|
|
||||||
top20_glied = top20_glied,
|
|
||||||
distinct_device_data=distinct_device_data,
|
distinct_device_data=distinct_device_data,
|
||||||
user_agent_data=user_agent_data,
|
user_agent_data=user_agent_data,
|
||||||
folder_data=folder_data,
|
folder_data=folder_data,
|
||||||
@ -633,6 +614,7 @@ def dashboard():
|
|||||||
|
|
||||||
def export_to_excel():
|
def export_to_excel():
|
||||||
"""Export search_db to an Excel file and store it locally."""
|
"""Export search_db to an Excel file and store it locally."""
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
# Query all data from the search_db
|
# Query all data from the search_db
|
||||||
query = "SELECT * FROM files"
|
query = "SELECT * FROM files"
|
||||||
|
|||||||
22
app.py
22
app.py
@ -274,28 +274,6 @@ def api_browse(subpath):
|
|||||||
'folder_today': [],
|
'folder_today': [],
|
||||||
'folder_yesterday': []
|
'folder_yesterday': []
|
||||||
})
|
})
|
||||||
|
|
||||||
if subpath.startswith('toplist'):
|
|
||||||
foldernames = []
|
|
||||||
files = []
|
|
||||||
split_path = subpath.split('/')
|
|
||||||
|
|
||||||
if len(split_path) == 1 and split_path[0] == 'toplist':
|
|
||||||
foldernames = [
|
|
||||||
{'name': 'Predigten', 'path': 'toplist/Predigt'},
|
|
||||||
{'name': 'Chorlieder', 'path': 'toplist/Chor'},
|
|
||||||
{'name': 'Gemeinsamer Gesang', 'path': 'toplist/Gemeinsamer Gesang'},
|
|
||||||
{'name': 'Gruppenlieder', 'path': 'toplist/Gruppenlied'},
|
|
||||||
]
|
|
||||||
elif len(split_path) > 1 and split_path[0] == 'toplist':
|
|
||||||
files = hf.generate_top_list(split_path[1])
|
|
||||||
|
|
||||||
return jsonify({
|
|
||||||
'breadcrumbs': generate_breadcrumbs(subpath),
|
|
||||||
'directories': foldernames,
|
|
||||||
'files': files
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
root, *relative_parts = subpath.split('/')
|
root, *relative_parts = subpath.split('/')
|
||||||
base_path = session['folders'][root]
|
base_path = session['folders'][root]
|
||||||
|
|||||||
@ -1,10 +1,5 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import re
|
import re
|
||||||
import os
|
|
||||||
import sqlite3
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
|
|
||||||
log_db = sqlite3.connect("access_log.db", check_same_thread=False)
|
|
||||||
|
|
||||||
def extract_date_from_string(string_with_date):
|
def extract_date_from_string(string_with_date):
|
||||||
# grab X.Y.Z where X,Y,Z are 1–4 digits
|
# grab X.Y.Z where X,Y,Z are 1–4 digits
|
||||||
@ -48,95 +43,4 @@ def extract_date_from_string(string_with_date):
|
|||||||
dt = datetime.strptime(date_str, fmt)
|
dt = datetime.strptime(date_str, fmt)
|
||||||
return dt.strftime('%Y-%m-%d')
|
return dt.strftime('%Y-%m-%d')
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def extract_structure_from_string(input_string):
|
|
||||||
# extract category and titel from filename
|
|
||||||
filename_ext = os.path.splitext(input_string)[0]
|
|
||||||
left_side, right_side = filename_ext.split('-', 1) if '-' in filename_ext else (filename_ext, None)
|
|
||||||
try:
|
|
||||||
int(left_side.strip())
|
|
||||||
# first part is only a number
|
|
||||||
previous_right_side = right_side
|
|
||||||
left_side, right_side = previous_right_side.split('-', 1) if '-' in previous_right_side else (previous_right_side, None)
|
|
||||||
except:
|
|
||||||
# first part not a number
|
|
||||||
pass
|
|
||||||
|
|
||||||
if 'predig' in left_side.lower():
|
|
||||||
category = 'Predigt'
|
|
||||||
elif 'wort' in left_side.lower() or 'einladung' in left_side.lower():
|
|
||||||
category = 'Vorwort'
|
|
||||||
elif 'chor' in left_side.lower():
|
|
||||||
category = 'Chor'
|
|
||||||
elif 'orchester' in left_side.lower():
|
|
||||||
category = 'Orchester'
|
|
||||||
elif 'gruppenlied' in left_side.lower() or 'jugendlied' in left_side.lower():
|
|
||||||
category = 'Gruppenlied'
|
|
||||||
elif 'gemeinsam' in left_side.lower() or 'gesang' in left_side.lower() or 'lied' in left_side.lower():
|
|
||||||
category = 'Gemeinsamer Gesang'
|
|
||||||
elif 'gedicht' in left_side.lower():
|
|
||||||
category = 'Gedicht'
|
|
||||||
elif 'instrumental' in left_side.lower() or 'musikstück' in left_side.lower():
|
|
||||||
category = 'Instrumental'
|
|
||||||
else:
|
|
||||||
category = None
|
|
||||||
|
|
||||||
if right_side:
|
|
||||||
titel, name = right_side.split('-', 1) if '-' in right_side else (right_side, None)
|
|
||||||
if category == 'Predigt' or category == 'Vorwort' or category == 'Gedicht':
|
|
||||||
if not name: # kein Titel, nur Name
|
|
||||||
name = titel
|
|
||||||
titel = None
|
|
||||||
else:
|
|
||||||
titel = None
|
|
||||||
name = None
|
|
||||||
|
|
||||||
return category, titel, name
|
|
||||||
|
|
||||||
def generate_top_list(category):
|
|
||||||
|
|
||||||
now = datetime.now()
|
|
||||||
|
|
||||||
# We'll compare the timestamp
|
|
||||||
start_dt = now - timedelta(days=30)
|
|
||||||
start_str = start_dt.isoformat()
|
|
||||||
|
|
||||||
# Filter for mimes that start with the given type
|
|
||||||
params_for_filter = (start_str,)
|
|
||||||
|
|
||||||
# 1. Top files by access count
|
|
||||||
query = f'''
|
|
||||||
SELECT rel_path, COUNT(*) as access_count
|
|
||||||
FROM file_access_log
|
|
||||||
WHERE timestamp >= ? AND mime LIKE 'audio/%'
|
|
||||||
GROUP BY rel_path
|
|
||||||
ORDER BY access_count DESC
|
|
||||||
LIMIT 1000
|
|
||||||
'''
|
|
||||||
with log_db:
|
|
||||||
cursor = log_db.execute(query, params_for_filter)
|
|
||||||
rows = cursor.fetchall()
|
|
||||||
|
|
||||||
# Convert rows to a list of dictionaries and add category
|
|
||||||
rows = [
|
|
||||||
{
|
|
||||||
'rel_path': rel_path,
|
|
||||||
'access_count': access_count,
|
|
||||||
'category': extract_structure_from_string(rel_path)[0]
|
|
||||||
}
|
|
||||||
for rel_path, access_count in rows
|
|
||||||
]
|
|
||||||
rows = [r for r in rows if r['category'] == category][:20]
|
|
||||||
|
|
||||||
filelist = [
|
|
||||||
{
|
|
||||||
'name': rel_path.split('/')[-1],
|
|
||||||
'path': rel_path,
|
|
||||||
'file_type': 'music'
|
|
||||||
}
|
|
||||||
for rel_path in [r['rel_path'] for r in rows]
|
|
||||||
]
|
|
||||||
|
|
||||||
return filelist
|
|
||||||
@ -110,13 +110,13 @@ def updatefileindex():
|
|||||||
# Retrieve the hit count for this file.
|
# Retrieve the hit count for this file.
|
||||||
hit_count = get_hit_count(relative_path)
|
hit_count = get_hit_count(relative_path)
|
||||||
|
|
||||||
|
category, titel, name, performance_date, site = None, None, None, None, None
|
||||||
|
|
||||||
# Determine the site
|
# Determine the site
|
||||||
if foldername == 'Gottesdienste Speyer':
|
if foldername == 'Gottesdienste Speyer':
|
||||||
site = 'Speyer'
|
site = 'Speyer'
|
||||||
elif foldername == 'Gottesdienste Schwegenheim':
|
elif foldername == 'Gottesdienste Schwegenheim':
|
||||||
site = 'Schwegenheim'
|
site = 'Schwegenheim'
|
||||||
else:
|
|
||||||
site = None
|
|
||||||
|
|
||||||
# Check for a corresponding transcript file in a sibling "Transkription" folder.
|
# Check for a corresponding transcript file in a sibling "Transkription" folder.
|
||||||
parent_dir = os.path.dirname(entry_path)
|
parent_dir = os.path.dirname(entry_path)
|
||||||
@ -130,7 +130,46 @@ def updatefileindex():
|
|||||||
except Exception:
|
except Exception:
|
||||||
transcript = None
|
transcript = None
|
||||||
|
|
||||||
category, titel, name = hf.extract_structure_from_string(entry.name)
|
# extract category and titel from filename
|
||||||
|
filename_ext = os.path.splitext(entry.name)[0]
|
||||||
|
left_side, right_side = filename_ext.split('-', 1) if '-' in filename_ext else (filename_ext, None)
|
||||||
|
try:
|
||||||
|
int(left_side.strip())
|
||||||
|
# first part is only a number
|
||||||
|
previous_right_side = right_side
|
||||||
|
left_side, right_side = previous_right_side.split('-', 1) if '-' in previous_right_side else (previous_right_side, None)
|
||||||
|
except:
|
||||||
|
# first part not a number
|
||||||
|
pass
|
||||||
|
|
||||||
|
if 'predig' in left_side.lower():
|
||||||
|
category = 'Predigt'
|
||||||
|
elif 'wort' in left_side.lower() or 'einladung' in left_side.lower():
|
||||||
|
category = 'Vorwort'
|
||||||
|
elif 'chor' in left_side.lower():
|
||||||
|
category = 'Chor'
|
||||||
|
elif 'orchester' in left_side.lower():
|
||||||
|
category = 'Orchester'
|
||||||
|
elif 'gruppenlied' in left_side.lower() or 'jugendlied' in left_side.lower():
|
||||||
|
category = 'Gruppenlied'
|
||||||
|
elif 'gemeinsam' in left_side.lower() or 'gesang' in left_side.lower() or 'lied' in left_side.lower():
|
||||||
|
category = 'Gemeinsamer Gesang'
|
||||||
|
elif 'gedicht' in left_side.lower():
|
||||||
|
category = 'Gedicht'
|
||||||
|
elif 'instrumental' in left_side.lower() or 'musikstück' in left_side.lower():
|
||||||
|
category = 'Instrumental'
|
||||||
|
else:
|
||||||
|
category = None
|
||||||
|
|
||||||
|
if right_side:
|
||||||
|
titel, name = right_side.split('-', 1) if '-' in right_side else (right_side, None)
|
||||||
|
if category == 'Predigt' or category == 'Vorwort' or category == 'Gedicht':
|
||||||
|
if not name: # kein Titel, nur Name
|
||||||
|
name = titel
|
||||||
|
titel = None
|
||||||
|
else:
|
||||||
|
titel = None
|
||||||
|
name = None
|
||||||
|
|
||||||
performance_date = hf.extract_date_from_string(relative_path)
|
performance_date = hf.extract_date_from_string(relative_path)
|
||||||
|
|
||||||
|
|||||||
@ -116,9 +116,6 @@ function renderContent(data) {
|
|||||||
} else if (data.breadcrumbs.length === 1 && Array.isArray(data.folder_yesterday) && data.folder_yesterday.length > 0) {
|
} else if (data.breadcrumbs.length === 1 && Array.isArray(data.folder_yesterday) && data.folder_yesterday.length > 0) {
|
||||||
contentHTML += `<li class="directory-item"><a href="#" class="directory-link" data-path="gestern">📅 Gestern</a></li>`;
|
contentHTML += `<li class="directory-item"><a href="#" class="directory-link" data-path="gestern">📅 Gestern</a></li>`;
|
||||||
}
|
}
|
||||||
if (data.breadcrumbs.length === 1 ) {
|
|
||||||
contentHTML += `<li class="directory-item"><a href="#" class="directory-link" data-path="toplist">🔥 oft angehört</a></li>`;
|
|
||||||
}
|
|
||||||
console.log(data.folder_today, data.folder_yesterday);
|
console.log(data.folder_today, data.folder_yesterday);
|
||||||
data.directories.forEach(dir => {
|
data.directories.forEach(dir => {
|
||||||
if (admin_enabled && data.breadcrumbs.length != 1 && dir.share) {
|
if (admin_enabled && data.breadcrumbs.length != 1 && dir.share) {
|
||||||
|
|||||||
@ -119,8 +119,6 @@ async function downloadAudio() {
|
|||||||
// Create a “real” link to that URL and click it
|
// Create a “real” link to that URL and click it
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = downloadUrl.toString();
|
a.href = downloadUrl.toString();
|
||||||
a.download = ''; // tell Safari “this is a download”
|
|
||||||
a.target = '_blank'; // force a real navigation on iOS
|
|
||||||
// NOTE: do NOT set a.download here – we want the server's Content-Disposition to drive it
|
// NOTE: do NOT set a.download here – we want the server's Content-Disposition to drive it
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
|
|||||||
@ -211,21 +211,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Detailed Table of Top File Accesses -->
|
<!-- Detailed Table of Top File Accesses -->
|
||||||
{% for top20 in [top20_overall, top20_predigt, top20_chor, top20_ggesang, top20_glied] %}
|
|
||||||
<div class="card mb-4">
|
<div class="card mb-4">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
Top 20 Dateizugriffe
|
Detaillierte Dateizugriffe (Top 20)
|
||||||
{% if top20 == top20_overall %}
|
|
||||||
(Gesamt)
|
|
||||||
{% elif top20 == top20_predigt %}
|
|
||||||
(Predigt)
|
|
||||||
{% elif top20 == top20_chor %}
|
|
||||||
(Chor)
|
|
||||||
{% elif top20 == top20_ggesang %}
|
|
||||||
(Gemeinsamer Gesang)
|
|
||||||
{% elif top20 == top20_glied %}
|
|
||||||
(Gruppenlieder)
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
@ -234,15 +222,13 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>File Path</th>
|
<th>File Path</th>
|
||||||
<th>Access Count</th>
|
<th>Access Count</th>
|
||||||
<th>Kategorie</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for row in top20 %}
|
{% for row in rows %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ row.rel_path }}</td>
|
<td>{{ row.rel_path }}</td>
|
||||||
<td>{{ row.access_count }}</td>
|
<td>{{ row.access_count }}</td>
|
||||||
<td>{{ row.category }}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr>
|
<tr>
|
||||||
@ -254,7 +240,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user