gui improvements

This commit is contained in:
lelo 2026-01-26 19:02:43 +00:00
parent 6cc1fb8d56
commit 5a3ad7ef6e
7 changed files with 98 additions and 32 deletions

View File

@ -4,6 +4,7 @@ import sqlite3
from datetime import datetime
from time import monotonic
import re
from typing import Optional
import helperfunctions as hf
SEARCH_DB_NAME = 'search.db'
@ -139,6 +140,20 @@ def build_transcript_index(transcript_dir: str, stats: dict):
return index
except FileNotFoundError:
return None
def read_text_file(path: str) -> Optional[str]:
try:
with open(path, 'r', encoding='utf-8') as handle:
return handle.read()
except UnicodeDecodeError:
try:
with open(path, 'r', encoding='cp1252') as handle:
return handle.read()
except Exception:
return None
except Exception:
return None
except PermissionError:
log_permission_error(transcript_dir, stats)
return None
@ -264,6 +279,14 @@ def updatefileindex():
except Exception:
transcript_errors += 1
if transcript is None and filetype == '.sng':
sng_text = read_text_file(entry_path)
if sng_text is not None:
transcript = sng_text
transcripts_read += 1
else:
transcript_errors += 1
category, titel, name = extract_structure(entry.name)
performance_date = extract_date(relative_path)

View File

@ -3,6 +3,7 @@ from flask import Flask, render_template, request, request, jsonify, session
import random
import json
from datetime import datetime
from urllib.parse import quote
app = Flask(__name__)
@ -122,6 +123,8 @@ def searchcommand():
transcript.lower().count(w.lower()) for w in words
)
record.pop('transcript', None)
record['fulltext_url'] = f"/media/{quote(record.get('relative_path', ''), safe='/')}"
record['fulltext_type'] = 'sng' if (record.get('filetype') or '').lower() == '.sng' else 'transcript'
# convert date to TT.MM.YYYY format
if record.get('performance_date'):
try:

View File

@ -268,24 +268,40 @@ class SimpleAudioPlayer {
}
async fileDownload() {
let relUrl = this.currentRelUrl || '';
const src = this.audio.currentSrc || this.audio.src;
if (!src) return;
// Extract the subpath from the src
const urlObj = new URL(src, window.location.href);
let subpath = urlObj.pathname;
subpath = subpath.slice('/media'.length);
if (!relUrl && src) {
try {
const urlObj = new URL(src, window.location.href);
if (urlObj.pathname.startsWith('/media/')) {
relUrl = urlObj.pathname.slice('/media/'.length);
}
} catch {
// ignore parsing errors
}
}
if (!relUrl) return;
const encodedSubpath = relUrl
.replace(/^\/+/, '')
.split('/')
.map(segment => encodeURIComponent(decodeURIComponent(segment)))
.join('/');
// Fetch the tokenized URL from your backend
let tokenizedUrl;
try {
const resp = await fetch(`/create_dltoken${subpath}`);
const resp = await fetch(`/create_dltoken/${encodedSubpath}`);
if (!resp.ok) return;
tokenizedUrl = await resp.text();
tokenizedUrl = (await resp.text()).trim();
} catch {
return;
}
if (!tokenizedUrl) return;
// Build the URL with cache-buster
const downloadUrl = new URL(tokenizedUrl, window.location.href);

View File

@ -5,6 +5,7 @@
let animateInterval = null;
let statsInterval = null;
let refreshMapSize = null;
const TABLE_LIMIT = 20;
function initConnectionsPage() {
const mapEl = document.getElementById('map');
@ -190,6 +191,8 @@
});
function updateStats(data) {
const headerCount = document.getElementById('connectionsTotalHeader');
if (headerCount) headerCount.textContent = `${data.length} Verbindungen`;
const totalConnections = document.getElementById('totalConnections');
if (totalConnections) totalConnections.textContent = data.length;
const totalBytes = data.reduce((sum, rec) => {
@ -276,7 +279,8 @@
socket.on('recent_connections', data => {
updateStats(data);
animateTableWithNewRow(data);
const tableData = data.slice(0, TABLE_LIMIT);
animateTableWithNewRow(tableData);
});
}

View File

@ -23,6 +23,12 @@ function initSearch() {
const isAudio = audioExts.includes((file.filetype || '').toLowerCase());
const isSng = (file.filetype || '').toLowerCase() === '.sng' || (file.filename || '').toLowerCase().endsWith('.sng');
const encodedRelPath = encodeURI(file.relative_path);
const fulltextType = file.fulltext_type || 'transcript';
const fulltextUrl = file.fulltext_url || transcriptURL;
const fulltextLabel = 'Treffer im Volltext';
const fulltextAction = fulltextType === 'sng'
? `<a href="#" class="open-sng-link" data-path="${file.relative_path}" title="Liedtext öffnen"><i class="bi bi-journal-text"></i></a>`
: `<a href="#" class="show-transcript" data-url="${fulltextUrl}" data-audio-url="${file.relative_path}" highlight="${file.query}"><i class="bi bi-journal-text"></i></a>`;
card.className = 'card';
let fileAction = '';
@ -39,8 +45,8 @@ function initSearch() {
<p>${fileAction}</p>
<p><button class="btn btn-light btn-sm folder-open-btn" data-folder="${parentFolder}" data-file="${file.relative_path}" style="width:100%;"><i class="bi bi-folder"></i> ${parentFolder || 'Ordner'}</button></p>
<p class="card-text">Anzahl Downloads: ${file.hitcount}</p>
${ file.performance_date !== undefined ? `<p class="card-text">Datum: ${file.performance_date}</p>` : ``}
${ file.transcript_hits !== undefined ? `<p class="card-text">Treffer im Transkript: ${file.transcript_hits} <a href="#" class="show-transcript" data-url="${transcriptURL}" data-audio-url="${file.relative_path}" highlight="${file.query}"><i class="bi bi-journal-text"></i></a></p>` : ``}
${ (file.performance_date !== undefined && file.performance_date !== null && String(file.performance_date).trim() !== '') ? `<p class="card-text">Datum: ${file.performance_date}</p>` : ``}
${ file.transcript_hits !== undefined ? `<p class="card-text">${fulltextLabel}: ${file.transcript_hits} ${fulltextAction}</p>` : ``}
</div>
`;
resultsDiv.appendChild(card);
@ -54,6 +60,7 @@ function initSearch() {
attachEventListeners();
attachSearchFolderButtons();
attachSearchSngButtons();
attachSearchFulltextButtons();
} else {
resultsDiv.innerHTML = `<h5>${total} Treffer</h5><p>Keine Treffer gefunden.</p>`;
}
@ -248,6 +255,18 @@ function attachSearchSngButtons() {
});
}
function attachSearchFulltextButtons() {
document.querySelectorAll('.open-sng-link').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const path = link.dataset.path;
if (typeof window.openSngModal === 'function') {
window.openSngModal(path);
}
});
});
}
function openFolderAndHighlight(folderPath, filePath) {
const targetFolder = folderPath || '';
// Switch back to main view before loading folder

View File

@ -63,6 +63,11 @@
<span aria-hidden="true">&times;</span>
</button>
</div>
<!-- Transkript durchsuchen -->
<div class="form-check mb-3">
<input type="checkbox" class="form-check-input" id="includeTranscript" name="includeTranscript">
<label class="form-check-label" for="includeTranscript">Im Volltext suchen</label>
</div>
</div>
<!-- Toggle für Suchoptionen -->
@ -81,6 +86,20 @@
<!-- Suchoptionen einklappbar -->
<div id="searchOptions" class="collapse border rounded p-3 mb-3">
<!-- In Ordner suchen -->
<div class="mb-3">
<label for="folder" class="form-label">In Ordner suchen:</label>
<select id="folder" name="folder" class="form-select">
<option value="">Alle</option>
{% for folder in search_folders %}
<option value="{{ folder }}">{{ folder }}</option>
{% endfor %}
</select>
</div>
<hr>
<!-- Kategorie -->
<div class="mb-3">
<label class="form-label">Kategorie:</label>
@ -114,19 +133,6 @@
<hr>
<!-- In Ordner suchen -->
<div class="mb-3">
<label for="folder" class="form-label">In Ordner suchen:</label>
<select id="folder" name="folder" class="form-select">
<option value="">Alle</option>
{% for folder in search_folders %}
<option value="{{ folder }}">{{ folder }}</option>
{% endfor %}
</select>
</div>
<hr>
<!-- Dateityp -->
<div class="mb-3">
<label class="form-label">Dateityp:</label>
@ -157,14 +163,6 @@
<hr>
<!-- Transkript durchsuchen -->
<div class="form-check mb-3">
<input type="checkbox" class="form-check-input" id="includeTranscript" name="includeTranscript">
<label class="form-check-label" for="includeTranscript">Im Transkript suchen</label>
</div>
<hr>
<!-- Zeitraum -->
<div class="row g-2 mb-3">
<div class="col-12 col-md-6">

View File

@ -103,7 +103,10 @@
{% block content %}
<div class="page-content">
<div class="section-header">
<h2 style="margin: 0;">Verbindungen der letzten 10 Minuten (nur Audio)</h2>
<h2 style="margin: 0;">
Verbindungen der letzten 10 Minuten (nur Audio)
<span class="text-muted" id="connectionsTotalHeader" style="font-size: 14px; margin-left: 8px;">0 Verbindungen</span>
</h2>
<p class="text-muted" style="margin: 4px 0 0 0;">Diese Ansicht listet ausschließlich Zugriffe auf Audio-Dateien.</p>
<div class="stats">
<div class="stat-item">