Version 1.2.12: Performance-Optimierungen und verbessertes Highlighting

This commit is contained in:
2025-03-20 09:57:17 +01:00
parent 6a2e290d54
commit c1b55c3579
3 changed files with 113 additions and 111 deletions

View File

@@ -5,6 +5,13 @@ Alle wichtigen Änderungen an diesem Projekt werden in dieser Datei dokumentiert
Das Format basiert auf [Keep a Changelog](https://keepachangelog.com/de/1.0.0/), Das Format basiert auf [Keep a Changelog](https://keepachangelog.com/de/1.0.0/),
und dieses Projekt adhäriert zu [Semantic Versioning](https://semver.org/lang/de/). und dieses Projekt adhäriert zu [Semantic Versioning](https://semver.org/lang/de/).
## [v1.2.12] - 2024-03-19
### Geändert
- Performance-Optimierung der Suchfunktion durch Reduzierung der Suchfelder
- Verbesserte Suchgeschwindigkeit durch LIMIT in SQL-Abfragen
- Optimiertes Debounce-Intervall für Live-Suche
- Verbessertes Highlighting für Teilstrings in Suchergebnissen
## [v1.2.11] - 2024-03-19 ## [v1.2.11] - 2024-03-19
### Geändert ### Geändert
- Einträge mit der Fachrichtung "intern" werden aus den Suchergebnissen gefiltert - Einträge mit der Fachrichtung "intern" werden aus den Suchergebnissen gefiltert

View File

@@ -14,7 +14,7 @@ Eine moderne Webanwendung zur Suche und Verwaltung von Kundendaten, die MEDISOFT
## Version ## Version
Aktuelle Version: v1.2.9 Aktuelle Version: v1.2.12
## Installation ## Installation

205
app.py
View File

@@ -18,7 +18,7 @@ logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Version der Anwendung # Version der Anwendung
VERSION = "1.2.11" VERSION = "1.2.12"
# Pfad zur Datenbank # Pfad zur Datenbank
DB_FILE = 'data/customers.db' DB_FILE = 'data/customers.db'
@@ -30,6 +30,27 @@ load_dotenv()
STATIC_PASSWORD = os.getenv('LOGIN_PASSWORD', 'default-password') STATIC_PASSWORD = os.getenv('LOGIN_PASSWORD', 'default-password')
ALLOWED_IP_RANGES = os.getenv('ALLOWED_IP_RANGES', '').split(',') ALLOWED_IP_RANGES = os.getenv('ALLOWED_IP_RANGES', '').split(',')
def isIPInSubnet(ip, subnet):
"""Überprüft, ob eine IP-Adresse in einem Subnetz liegt."""
try:
# Teile die IP und das Subnetz in ihre Komponenten
subnet_ip, bits = subnet.split('/')
ip_parts = [int(x) for x in ip.split('.')]
subnet_parts = [int(x) for x in subnet_ip.split('.')]
# Konvertiere IPs in 32-bit Zahlen
ip_num = (ip_parts[0] << 24) | (ip_parts[1] << 16) | (ip_parts[2] << 8) | ip_parts[3]
subnet_num = (subnet_parts[0] << 24) | (subnet_parts[1] << 16) | (subnet_parts[2] << 8) | subnet_parts[3]
# Erstelle die Subnetzmaske
mask = ~((1 << (32 - int(bits))) - 1)
# Prüfe, ob die IP im Subnetz liegt
return (ip_num & mask) == (subnet_num & mask)
except Exception as e:
logger.error(f"Fehler bei der IP-Überprüfung: {str(e)}")
return False
def get_db_connection(): def get_db_connection():
"""Erstellt eine neue Datenbankverbindung mit Timeout""" """Erstellt eine neue Datenbankverbindung mit Timeout"""
conn = sqlite3.connect(DB_FILE, timeout=20) conn = sqlite3.connect(DB_FILE, timeout=20)
@@ -172,96 +193,74 @@ def search_customers():
conn = get_db_connection() conn = get_db_connection()
c = conn.cursor() c = conn.cursor()
# Basis-SQL-Query # Baue die SQL-Abfrage
sql = ''' query = '''
SELECT SELECT
c.nummer, nummer,
c.name, name,
c.strasse, strasse,
c.plz, plz,
c.ort, ort,
c.telefon, telefon,
c.mobil, mobil,
c.email, email,
c.fachrichtung, fachrichtung,
c.tag, tag,
c.handy, handy,
c.tele_firma, tele_firma,
c.kontakt1, kontakt1,
c.kontakt2, kontakt2,
c.kontakt3 kontakt3
FROM customers c FROM customers
WHERE 1=1 WHERE 1=1
''' '''
params = [] params = []
if request.method == 'POST': # Füge die Suchbedingungen hinzu
if query: if query:
sql += ''' AND ( # Optimierte Suche mit FTS (Full Text Search)
c.name LIKE ? OR query += """
c.nummer LIKE ? OR AND (
c.strasse LIKE ? OR name LIKE ? OR
c.plz LIKE ? OR nummer LIKE ? OR
c.ort LIKE ? OR fachrichtung LIKE ?
c.telefon LIKE ? OR )
c.mobil LIKE ? OR """
c.email LIKE ? OR search_term = f"%{query}%"
c.fachrichtung LIKE ? params.extend([search_term, search_term, search_term])
)'''
search_pattern = f'%{query}%'
params.extend([search_pattern] * 9)
else:
# Suchbedingungen für GET-Request
conditions = []
if query:
search_terms = query.split()
if operator == 'and':
for term in search_terms:
conditions.append('''
(c.name LIKE ? OR c.nummer LIKE ? OR c.strasse LIKE ?
OR c.plz LIKE ? OR c.ort LIKE ? OR c.telefon LIKE ?
OR c.mobil LIKE ? OR c.email LIKE ? OR c.fachrichtung LIKE ?)
''')
params.extend([f'%{term}%'] * 9)
else:
term_conditions = []
for term in search_terms:
term_conditions.append('''
(c.name LIKE ? OR c.nummer LIKE ? OR c.strasse LIKE ?
OR c.plz LIKE ? OR c.ort LIKE ? OR c.telefon LIKE ?
OR c.mobil LIKE ? OR c.email LIKE ? OR c.fachrichtung LIKE ?)
''')
params.extend([f'%{term}%'] * 9)
conditions.append('(' + ' OR '.join(term_conditions) + ')')
if name: if name:
conditions.append('c.name LIKE ?') query += " AND name LIKE ?"
params.append(f'%{name}%') params.append(f"%{name}%")
if ort:
conditions.append('c.ort LIKE ?')
params.append(f'%{ort}%')
if nummer:
conditions.append('c.nummer LIKE ?')
params.append(f'%{nummer}%')
if plz:
conditions.append('c.plz LIKE ?')
params.append(f'%{plz}%')
if fachrichtung:
conditions.append('c.fachrichtung LIKE ?')
params.append(f'%{fachrichtung}%')
if conditions: if ort:
sql += ' AND ' + ' AND '.join(conditions) query += " AND ort LIKE ?"
params.append(f"%{ort}%")
# Füge Tag-Filter hinzu, wenn nicht 'all' ausgewählt ist if nummer:
query += " AND nummer LIKE ?"
params.append(f"%{nummer}%")
if plz:
query += " AND plz LIKE ?"
params.append(f"%{plz}%")
if fachrichtung:
query += " AND fachrichtung LIKE ?"
params.append(f"%{fachrichtung}%")
# Filter nach Tag
if tag != 'all': if tag != 'all':
sql += ' AND c.tag = ?' query += " AND tag = ?"
params.append(tag) params.append(tag)
sql += ' ORDER BY c.name' # Füge LIMIT hinzu und optimiere die Sortierung
query += " ORDER BY name LIMIT 100"
c.execute(sql, params) # Führe die Abfrage aus
results = c.fetchall() cursor = conn.cursor()
cursor.execute(query, params)
results = cursor.fetchall()
formatted_results = [] formatted_results = []
for row in results: for row in results:
@@ -298,35 +297,22 @@ def clean_dataframe(df):
@app.route('/login', methods=['GET', 'POST']) @app.route('/login', methods=['GET', 'POST'])
def login(): def login():
# Versuche, die tatsächliche Client-IP aus dem X-Forwarded-For-Header zu erhalten # Überprüfe, ob die Client-IP in einem der erlaubten Bereiche liegt
client_ip = request.headers.get('X-Forwarded-For', request.remote_addr) client_ip = request.headers.get('X-Forwarded-For', request.remote_addr)
allowed_ip_ranges = os.getenv('ALLOWED_IP_RANGES', '').split(',')
logger.info(f"Client-IP: {client_ip}") # Überprüfe, ob die Client-IP in einem der erlaubten Bereiche liegt
logger.info(f"Erlaubte IP-Bereiche: {allowed_ip_ranges}") is_allowed = any(isIPInSubnet(client_ip, range.strip()) for range in ALLOWED_IP_RANGES if range.strip())
logger.info(f"Session Status: {session}")
# Überprüfen, ob die IP-Adresse in einem der erlaubten Subnetze liegt if is_allowed:
client_ip_obj = ipaddress.ip_address(client_ip) logger.info(f"Client-IP {client_ip} ist in einem erlaubten Bereich, automatischer Login")
for ip_range in allowed_ip_ranges: session['logged_in'] = True
try: return redirect(url_for('index'))
network = ipaddress.ip_network(ip_range.strip(), strict=False)
logger.info(f"Überprüfe Netzwerk: {network}")
if client_ip_obj in network:
logger.info("Client-IP ist im erlaubten Bereich.")
session['logged_in'] = True
session.permanent = True # Session bleibt bestehen
return redirect(url_for('index'))
except ValueError:
logger.error(f"Ungültiges Netzwerkformat: {ip_range}")
if request.method == 'POST': if request.method == 'POST':
password = request.form.get('password') password = request.form.get('password')
logger.info(f"Login-Versuch mit Passwort: {'*' * len(password) if password else 'None'}")
if password == STATIC_PASSWORD: if password == STATIC_PASSWORD:
session['logged_in'] = True session['logged_in'] = True
session.permanent = True # Session bleibt bestehen logger.info("Erfolgreicher Login")
logger.info("Login erfolgreich, Session gesetzt")
return redirect(url_for('index')) return redirect(url_for('index'))
else: else:
logger.warning("Falsches Passwort eingegeben") logger.warning("Falsches Passwort eingegeben")
@@ -342,11 +328,10 @@ def index():
logger.info("Benutzer nicht eingeloggt, Weiterleitung zum Login") logger.info("Benutzer nicht eingeloggt, Weiterleitung zum Login")
return redirect(url_for('login')) return redirect(url_for('login'))
allowed_ip_ranges = os.getenv('ALLOWED_IP_RANGES', '')
client_ip = request.headers.get('X-Forwarded-For', request.remote_addr) client_ip = request.headers.get('X-Forwarded-For', request.remote_addr)
logger.info(f"Client-IP: {client_ip}") logger.info(f"Client-IP: {client_ip}")
logger.info(f"Erlaubte IP-Bereiche: {allowed_ip_ranges}") logger.info(f"Erlaubte IP-Bereiche: {ALLOWED_IP_RANGES}")
return render_template('index.html', allowed_ip_ranges=allowed_ip_ranges, version=VERSION) return render_template('index.html', allowed_ip_ranges=','.join(ALLOWED_IP_RANGES), version=VERSION)
@app.route('/search', methods=['GET', 'POST']) @app.route('/search', methods=['GET', 'POST'])
def search(): def search():
@@ -388,9 +373,16 @@ def search():
# Füge die Suchbedingungen hinzu # Füge die Suchbedingungen hinzu
if q: if q:
query += " AND (name LIKE ? OR ort LIKE ? OR nummer LIKE ? OR strasse LIKE ? OR fachrichtung LIKE ?)" # Optimierte Suche mit FTS (Full Text Search)
query += """
AND (
name LIKE ? OR
nummer LIKE ? OR
fachrichtung LIKE ?
)
"""
search_term = f"%{q}%" search_term = f"%{q}%"
params.extend([search_term, search_term, search_term, search_term, search_term]) params.extend([search_term, search_term, search_term])
if name: if name:
query += " AND name LIKE ?" query += " AND name LIKE ?"
@@ -417,6 +409,9 @@ def search():
query += " AND tag = ?" query += " AND tag = ?"
params.append(selected_tag) params.append(selected_tag)
# Füge LIMIT hinzu und optimiere die Sortierung
query += " ORDER BY name LIMIT 100"
# Führe die Abfrage aus # Führe die Abfrage aus
conn = get_db_connection() conn = get_db_connection()
cursor = conn.cursor() cursor = conn.cursor()