Compare commits
3 Commits
940984e34f
...
d05a230554
| Author | SHA1 | Date | |
|---|---|---|---|
| d05a230554 | |||
| a66379dc63 | |||
| 9023c41cab |
110
analytics.py
110
analytics.py
@ -378,36 +378,7 @@ def dashboard():
|
|||||||
params_for_filter = (start_str, filetype + '%')
|
params_for_filter = (start_str, filetype + '%')
|
||||||
|
|
||||||
# 1. Top files by access count
|
# 1. Top files by access count
|
||||||
query = f'''
|
# removed and moved to file_access() function
|
||||||
SELECT rel_path, COUNT(*) as access_count
|
|
||||||
FROM file_access_log
|
|
||||||
WHERE timestamp >= ? {filetype_filter_sql}
|
|
||||||
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': hf.extract_structure_from_string(rel_path)[0]
|
|
||||||
}
|
|
||||||
for rel_path, access_count in rows
|
|
||||||
]
|
|
||||||
|
|
||||||
categories = set(r['category'] for r in rows) # distinct categories
|
|
||||||
top20 = [
|
|
||||||
{
|
|
||||||
'category': categorie,
|
|
||||||
'files': [r for r in rows if r['category'] == categorie][:20]
|
|
||||||
}
|
|
||||||
for categorie in categories
|
|
||||||
]
|
|
||||||
|
|
||||||
# 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"
|
||||||
@ -614,7 +585,6 @@ def dashboard():
|
|||||||
return render_template(
|
return render_template(
|
||||||
"dashboard.html",
|
"dashboard.html",
|
||||||
timeframe=session['timeframe'],
|
timeframe=session['timeframe'],
|
||||||
top20 = top20,
|
|
||||||
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,
|
||||||
@ -629,6 +599,84 @@ def dashboard():
|
|||||||
title_long=title_long
|
title_long=title_long
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@require_secret
|
||||||
|
def file_access():
|
||||||
|
if 'timeframe' not in session:
|
||||||
|
session['timeframe'] = 'last24hours'
|
||||||
|
session['timeframe'] = request.args.get('timeframe', session['timeframe'])
|
||||||
|
|
||||||
|
now = datetime.now()
|
||||||
|
|
||||||
|
filetype = 'audio/'
|
||||||
|
|
||||||
|
# Determine start time based on session['timeframe']
|
||||||
|
if session['timeframe'] == 'last24hours':
|
||||||
|
start_dt = now - timedelta(hours=24)
|
||||||
|
elif session['timeframe'] == '7days':
|
||||||
|
start_dt = now - timedelta(days=7)
|
||||||
|
elif session['timeframe'] == '30days':
|
||||||
|
start_dt = now - timedelta(days=30)
|
||||||
|
elif session['timeframe'] == '365days':
|
||||||
|
start_dt = now - timedelta(days=365)
|
||||||
|
else:
|
||||||
|
start_dt = now - timedelta(hours=24)
|
||||||
|
|
||||||
|
# We'll compare the textual timestamp (ISO 8601).
|
||||||
|
start_str = start_dt.isoformat()
|
||||||
|
|
||||||
|
# Filter for mimes that start with the given type
|
||||||
|
filetype_filter_sql = "AND mime LIKE ?"
|
||||||
|
params_for_filter = (start_str, filetype + '%')
|
||||||
|
|
||||||
|
# 1. Top files by access count
|
||||||
|
query = f'''
|
||||||
|
SELECT rel_path, COUNT(*) as access_count
|
||||||
|
FROM file_access_log
|
||||||
|
WHERE timestamp >= ? {filetype_filter_sql}
|
||||||
|
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': hf.extract_structure_from_string(rel_path)[0]
|
||||||
|
}
|
||||||
|
for rel_path, access_count in rows
|
||||||
|
]
|
||||||
|
|
||||||
|
# Get possible categories from the rows
|
||||||
|
categories = sorted({r['category'] for r in rows if r['category'] is not None})
|
||||||
|
all_categories = [None] + categories
|
||||||
|
top20 = []
|
||||||
|
for category in all_categories:
|
||||||
|
label = category if category is not None else 'Keine Kategorie gefunden !'
|
||||||
|
files = [r for r in rows if r['category'] == category][:20]
|
||||||
|
top20.append({
|
||||||
|
'category': label,
|
||||||
|
'files': files
|
||||||
|
})
|
||||||
|
|
||||||
|
title_short = app_config.get('TITLE_SHORT', 'Default Title')
|
||||||
|
title_long = app_config.get('TITLE_LONG' , 'Default Title')
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"file_access.html",
|
||||||
|
timeframe=session['timeframe'],
|
||||||
|
top20 = top20,
|
||||||
|
admin_enabled=auth.is_admin(),
|
||||||
|
title_short=title_short,
|
||||||
|
title_long=title_long
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
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."""
|
||||||
|
|
||||||
|
|||||||
3
app.py
3
app.py
@ -43,6 +43,7 @@ if os.environ.get('FLASK_ENV') == 'production':
|
|||||||
app.config['SESSION_COOKIE_SECURE'] = True
|
app.config['SESSION_COOKIE_SECURE'] = True
|
||||||
|
|
||||||
app.add_url_rule('/dashboard', view_func=a.dashboard)
|
app.add_url_rule('/dashboard', view_func=a.dashboard)
|
||||||
|
app.add_url_rule('/file_access', view_func=a.file_access)
|
||||||
app.add_url_rule('/connections', view_func=a.connections)
|
app.add_url_rule('/connections', view_func=a.connections)
|
||||||
app.add_url_rule('/mylinks', view_func=auth.mylinks)
|
app.add_url_rule('/mylinks', view_func=auth.mylinks)
|
||||||
app.add_url_rule('/remove_secret', view_func=auth.remove_secret, methods=['POST'])
|
app.add_url_rule('/remove_secret', view_func=auth.remove_secret, methods=['POST'])
|
||||||
@ -206,6 +207,8 @@ def generate_breadcrumbs(subpath=None):
|
|||||||
path_accum = ""
|
path_accum = ""
|
||||||
for part in parts:
|
for part in parts:
|
||||||
path_accum = f"{path_accum}/{part}" if path_accum else part
|
path_accum = f"{path_accum}/{part}" if path_accum else part
|
||||||
|
if 'toplist' in part:
|
||||||
|
part = part.replace('toplist', 'oft angehört')
|
||||||
breadcrumbs.append({'name': part, 'path': path_accum})
|
breadcrumbs.append({'name': part, 'path': path_accum})
|
||||||
return breadcrumbs
|
return breadcrumbs
|
||||||
|
|
||||||
|
|||||||
@ -124,9 +124,12 @@ function renderContent(data) {
|
|||||||
if (admin_enabled && data.breadcrumbs.length != 1 && dir.share) {
|
if (admin_enabled && data.breadcrumbs.length != 1 && dir.share) {
|
||||||
share_link = `<a href="#" class="create-share" data-url="${dir.path}">⚙️</a>`;
|
share_link = `<a href="#" class="create-share" data-url="${dir.path}">⚙️</a>`;
|
||||||
}
|
}
|
||||||
contentHTML += `<li class="directory-item"><a href="#" class="directory-link" data-path="${dir.path}">📁 ${dir.name}</a>
|
if (dir.path.includes('toplist')) {
|
||||||
${share_link}
|
link_symbol = '⭐';
|
||||||
</li>`;
|
} else {
|
||||||
|
link_symbol = '📁';
|
||||||
|
}
|
||||||
|
contentHTML += `<li class="directory-item"><a href="#" class="directory-link" data-path="${dir.path}">${link_symbol} ${dir.name}</a>${share_link}</li>`;
|
||||||
});
|
});
|
||||||
if (data.breadcrumbs.length === 1) {
|
if (data.breadcrumbs.length === 1) {
|
||||||
contentHTML += `<li class="link-item" onclick="viewSearch()"><a onclick="viewSearch() class="link-link">🔎 Suche</a></li>`;
|
contentHTML += `<li class="link-item" onclick="viewSearch()"><a onclick="viewSearch() class="link-link">🔎 Suche</a></li>`;
|
||||||
@ -453,42 +456,6 @@ function reloadDirectory() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if ('mediaSession' in navigator) {
|
|
||||||
|
|
||||||
// Handler for the play action
|
|
||||||
navigator.mediaSession.setActionHandler('play', () => {
|
|
||||||
document.getElementById('globalAudio').play();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handler for the pause action
|
|
||||||
navigator.mediaSession.setActionHandler('pause', () => {
|
|
||||||
document.getElementById('globalAudio').pause();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handler for the previous track action
|
|
||||||
navigator.mediaSession.setActionHandler('previoustrack', () => {
|
|
||||||
if (currentMusicIndex > 0) {
|
|
||||||
const prevFile = currentMusicFiles[currentMusicIndex - 1];
|
|
||||||
const prevLink = document.querySelector(`.play-file[data-url="${prevFile.path}"]`);
|
|
||||||
if (prevLink) {
|
|
||||||
prevLink.click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handler for the next track action
|
|
||||||
navigator.mediaSession.setActionHandler('nexttrack', () => {
|
|
||||||
if (currentMusicIndex >= 0 && currentMusicIndex < currentMusicFiles.length - 1) {
|
|
||||||
const nextFile = currentMusicFiles[currentMusicIndex + 1];
|
|
||||||
const nextLink = document.querySelector(`.play-file[data-url="${nextFile.path}"]`);
|
|
||||||
if (nextLink) {
|
|
||||||
nextLink.click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('globalAudio').addEventListener('ended', () => {
|
document.getElementById('globalAudio').addEventListener('ended', () => {
|
||||||
|
|
||||||
reloadDirectory().then(() => {
|
reloadDirectory().then(() => {
|
||||||
|
|||||||
@ -3,11 +3,10 @@ const cssVar = getComputedStyle(document.documentElement).getPropertyValue('--da
|
|||||||
|
|
||||||
// player DOM elements
|
// player DOM elements
|
||||||
const nowPlayingInfo = document.getElementById('nowPlayingInfo');
|
const nowPlayingInfo = document.getElementById('nowPlayingInfo');
|
||||||
const audioPlayer = document.getElementById('globalAudio');
|
const audio = document.getElementById('globalAudio');
|
||||||
const audioPlayerContainer = document.getElementById('audioPlayerContainer');
|
const audioPlayerContainer = document.getElementById('audioPlayerContainer');
|
||||||
|
|
||||||
const playerButton = document.querySelector('.player-button'),
|
const playerButton = document.querySelector('.player-button'),
|
||||||
audio = document.querySelector('audio'),
|
|
||||||
timeline = document.querySelector('.timeline'),
|
timeline = document.querySelector('.timeline'),
|
||||||
timeInfo = document.getElementById('timeInfo'),
|
timeInfo = document.getElementById('timeInfo'),
|
||||||
playIcon = `
|
playIcon = `
|
||||||
@ -57,10 +56,24 @@ timeline.addEventListener('touchcancel', () => { isSeeking = false; });
|
|||||||
audio.addEventListener('loadedmetadata', () => {
|
audio.addEventListener('loadedmetadata', () => {
|
||||||
timeline.min = 0;
|
timeline.min = 0;
|
||||||
timeline.max = audio.duration;
|
timeline.max = audio.duration;
|
||||||
|
timeline.value = 0;
|
||||||
|
timeline.style.backgroundSize = '0% 100%';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
audio.addEventListener('play', () => {
|
||||||
|
if ('mediaSession' in navigator)
|
||||||
|
navigator.mediaSession.playbackState = 'playing';
|
||||||
|
});
|
||||||
|
audio.addEventListener('pause', () => {
|
||||||
|
if ('mediaSession' in navigator)
|
||||||
|
navigator.mediaSession.playbackState = 'paused';
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// --- Seek function: directly set audio.currentTime using slider's value (in seconds) ---
|
// --- Seek function: directly set audio.currentTime using slider's value (in seconds) ---
|
||||||
function changeSeek() {
|
function changeSeek() {
|
||||||
audio.currentTime = timeline.value;
|
audio.currentTime = timeline.value;
|
||||||
@ -75,30 +88,20 @@ function formatTime(seconds) {
|
|||||||
|
|
||||||
|
|
||||||
// --- Update timeline, time info, and media session on each time update ---
|
// --- Update timeline, time info, and media session on each time update ---
|
||||||
audio.ontimeupdate = function() {
|
function updateTimeline() {
|
||||||
|
if (!isSeeking && audio.duration) {
|
||||||
// --- Update the slider thumb position (in seconds) while playing ---
|
|
||||||
if (!isSeeking) {
|
|
||||||
timeline.value = audio.currentTime;
|
timeline.value = audio.currentTime;
|
||||||
const percentagePosition = (audio.currentTime / audio.duration) * 100;
|
timeline.style.backgroundSize = `${(audio.currentTime / audio.duration) * 100}% 100%`;
|
||||||
timeline.style.backgroundSize = `${percentagePosition}% 100%`;
|
timeInfo.textContent = `${formatTime(audio.currentTime)} / ${formatTime(audio.duration)}`;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- Update the time display ---
|
audio.ontimeupdate = updateTimeline;
|
||||||
const currentTimeFormatted = formatTime(audio.currentTime);
|
|
||||||
const durationFormatted = isNaN(audio.duration) ? "00:00" : formatTime(audio.duration);
|
|
||||||
timeInfo.innerHTML = `${currentTimeFormatted} / ${durationFormatted}`;
|
|
||||||
|
|
||||||
if ('mediaSession' in navigator &&
|
// Fallback for mobile throttling
|
||||||
'setPositionState' in navigator.mediaSession &&
|
setInterval(() => {
|
||||||
!isNaN(audio.duration)) {
|
if (!audio.paused && !isSeeking) updateTimeline();
|
||||||
navigator.mediaSession.setPositionState({
|
}, 500);
|
||||||
duration: audio.duration,
|
|
||||||
playbackRate: audio.playbackRate,
|
|
||||||
position: audio.currentTime
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// --- When audio ends ---
|
// --- When audio ends ---
|
||||||
@ -107,7 +110,6 @@ audio.onended = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
async function downloadAudio() {
|
async function downloadAudio() {
|
||||||
const audio = document.getElementById('globalAudio');
|
|
||||||
const src = audio.currentSrc || audio.src;
|
const src = audio.currentSrc || audio.src;
|
||||||
if (!src) return;
|
if (!src) return;
|
||||||
|
|
||||||
@ -132,8 +134,8 @@ let currentFetchController = null;
|
|||||||
|
|
||||||
async function startPlaying(relUrl) {
|
async function startPlaying(relUrl) {
|
||||||
// Pause the audio and clear its source.
|
// Pause the audio and clear its source.
|
||||||
audioPlayer.pause();
|
audio.pause();
|
||||||
audioPlayer.src = '';
|
audio.src = '';
|
||||||
|
|
||||||
// Display the audio player container.
|
// Display the audio player container.
|
||||||
audioPlayerContainer.style.display = "block";
|
audioPlayerContainer.style.display = "block";
|
||||||
@ -171,9 +173,9 @@ async function startPlaying(relUrl) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set the media URL, load, and play the audio.
|
// Set the media URL, load, and play the audio.
|
||||||
audioPlayer.src = mediaUrl;
|
audio.src = mediaUrl;
|
||||||
audioPlayer.load();
|
audio.load();
|
||||||
await audioPlayer.play();
|
await audio.play();
|
||||||
clearTimeout(spinnerTimer);
|
clearTimeout(spinnerTimer);
|
||||||
hideSpinner();
|
hideSpinner();
|
||||||
currentTrackPath = relUrl;
|
currentTrackPath = relUrl;
|
||||||
@ -194,6 +196,12 @@ async function startPlaying(relUrl) {
|
|||||||
{ src: '/icons/logo-192x192.png', sizes: '192x192', type: 'image/png' }
|
{ src: '/icons/logo-192x192.png', sizes: '192x192', type: 'image/png' }
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
navigator.mediaSession.playbackState = 'playing';
|
||||||
|
navigator.mediaSession.setPositionState({
|
||||||
|
duration: audio.duration,
|
||||||
|
playbackRate: audio.playbackRate,
|
||||||
|
position: 0
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
nowPlayingInfo.innerHTML = pathStr.replace(/\//g, ' > ') +
|
nowPlayingInfo.innerHTML = pathStr.replace(/\//g, ' > ') +
|
||||||
@ -208,3 +216,57 @@ async function startPlaying(relUrl) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if ('mediaSession' in navigator) {
|
||||||
|
|
||||||
|
// Handler for the play action
|
||||||
|
navigator.mediaSession.setActionHandler('play', () => {
|
||||||
|
navigator.mediaSession.playbackState = 'playing'
|
||||||
|
document.getElementById('globalAudio').play();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handler for the pause action
|
||||||
|
navigator.mediaSession.setActionHandler('pause', () => {
|
||||||
|
navigator.mediaSession.playbackState = 'paused'
|
||||||
|
document.getElementById('globalAudio').pause();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handler for the previous track action
|
||||||
|
navigator.mediaSession.setActionHandler('previoustrack', () => {
|
||||||
|
if (currentMusicIndex > 0) {
|
||||||
|
const prevFile = currentMusicFiles[currentMusicIndex - 1];
|
||||||
|
const prevLink = document.querySelector(`.play-file[data-url="${prevFile.path}"]`);
|
||||||
|
if (prevLink) {
|
||||||
|
prevLink.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handler for the next track action
|
||||||
|
navigator.mediaSession.setActionHandler('nexttrack', () => {
|
||||||
|
if (currentMusicIndex >= 0 && currentMusicIndex < currentMusicFiles.length - 1) {
|
||||||
|
const nextFile = currentMusicFiles[currentMusicIndex + 1];
|
||||||
|
const nextLink = document.querySelector(`.play-file[data-url="${nextFile.path}"]`);
|
||||||
|
if (nextLink) {
|
||||||
|
nextLink.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handler for the seek backward action
|
||||||
|
navigator.mediaSession.setActionHandler('seekto', ({seekTime, fastSeek}) => {
|
||||||
|
if (fastSeek && 'fastSeek' in audio) {
|
||||||
|
audio.fastSeek(seekTime);
|
||||||
|
} else {
|
||||||
|
audio.currentTime = seekTime;
|
||||||
|
}
|
||||||
|
// immediately update the remote clock
|
||||||
|
navigator.mediaSession.setPositionState({
|
||||||
|
duration: audio.duration,
|
||||||
|
playbackRate: audio.playbackRate,
|
||||||
|
position: audio.currentTime
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
@ -45,6 +45,8 @@
|
|||||||
<span> | </span>
|
<span> | </span>
|
||||||
<a href="{{ url_for('dashboard') }}">Dashbord</a>
|
<a href="{{ url_for('dashboard') }}">Dashbord</a>
|
||||||
<span> | </span>
|
<span> | </span>
|
||||||
|
<a href="{{ url_for('file_access') }}">Dateizugriffe</a>
|
||||||
|
<span> | </span>
|
||||||
<a href="{{ url_for('songs_dashboard') }}">Wiederholungen</a>
|
<a href="{{ url_for('songs_dashboard') }}">Wiederholungen</a>
|
||||||
<span> | </span>
|
<span> | </span>
|
||||||
<a href="{{ url_for('folder_secret_config_editor') }}" id="edit-folder-config">Ordnerkonfiguration</a>
|
<a href="{{ url_for('folder_secret_config_editor') }}" id="edit-folder-config">Ordnerkonfiguration</a>
|
||||||
|
|||||||
@ -210,38 +210,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Detailed Table of Top File Accesses -->
|
|
||||||
{% for top20_item in top20 %}
|
|
||||||
<div class="card mb-4">
|
|
||||||
<div class="card-header">
|
|
||||||
Top 20 Dateizugriffe ({{ top20_item['category'] }})
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Access Count</th>
|
|
||||||
<th>File Path</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for row in top20_item['files'] %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ row.access_count }}</td>
|
|
||||||
<td>{{ row.rel_path }}</td>
|
|
||||||
</tr>
|
|
||||||
{% else %}
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">No data available for the selected timeframe.</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
99
templates/file_access.html
Normal file
99
templates/file_access.html
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
{# templates/file_access.html #}
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{# page title #}
|
||||||
|
{% block title %}Dateizugriffe{% endblock %}
|
||||||
|
|
||||||
|
{# page content #}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<!-- Main Container -->
|
||||||
|
<div class="container">
|
||||||
|
<h2>Auswertung-Dateizugriffe</h2>
|
||||||
|
|
||||||
|
<!-- Dropdown Controls -->
|
||||||
|
<div class="mb-4 d-flex flex-wrap gap-2">
|
||||||
|
<!-- Timeframe Dropdown -->
|
||||||
|
<div class="dropdown">
|
||||||
|
<button class="btn btn-secondary dropdown-toggle"
|
||||||
|
type="button" id="timeframeDropdown" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
{% if session['timeframe'] == 'last24hours' %}
|
||||||
|
Last 24 Hours
|
||||||
|
{% elif session['timeframe'] == '7days' %}
|
||||||
|
Last 7 Days
|
||||||
|
{% elif session['timeframe'] == '30days' %}
|
||||||
|
Last 30 Days
|
||||||
|
{% elif session['timeframe'] == '365days' %}
|
||||||
|
Last 365 Days
|
||||||
|
{% else %}
|
||||||
|
Select Timeframe
|
||||||
|
{% endif %}
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu" aria-labelledby="timeframeDropdown">
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item {% if session['timeframe'] == 'last24hours' %}active{% endif %}"
|
||||||
|
href="{{ url_for('file_access', timeframe='last24hours') }}">
|
||||||
|
Last 24 Hours
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item {% if session['timeframe'] == '7days' %}active{% endif %}"
|
||||||
|
href="{{ url_for('file_access', timeframe='7days') }}">
|
||||||
|
Last 7 Days
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item {% if session['timeframe'] == '30days' %}active{% endif %}"
|
||||||
|
href="{{ url_for('file_access', timeframe='30days') }}">
|
||||||
|
Last 30 Days
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item {% if session['timeframe'] == '365days' %}active{% endif %}"
|
||||||
|
href="{{ url_for('file_access', timeframe='365days') }}">
|
||||||
|
Last 365 Days
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Detailed Table of Top File Accesses -->
|
||||||
|
{% for top20_item in top20 %}
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header">
|
||||||
|
Top 20 Dateizugriffe ({{ top20_item['category'] }})
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Access Count</th>
|
||||||
|
<th>File Path</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for row in top20_item['files'] %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ row.access_count }}</td>
|
||||||
|
<td>{{ row.rel_path }}</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">No data available for the selected timeframe.</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -8,7 +8,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<!-- Main Container -->
|
<!-- Main Container -->
|
||||||
<div class="container-fluid px-4">
|
<div class="container">
|
||||||
<h2>Analyse Wiederholungen</h2>
|
<h2>Analyse Wiederholungen</h2>
|
||||||
<!-- Dropdown Controls -->
|
<!-- Dropdown Controls -->
|
||||||
<div class="mb-4 d-flex flex-wrap gap-2">
|
<div class="mb-4 d-flex flex-wrap gap-2">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user