Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
c271bc1f60 | |||
9c04bb973e | |||
6d7298548b | |||
42a11abe61 | |||
3c6d2f7c45 | |||
d143d6c7b6 | |||
e6638b737d | |||
1f493e0a37 | |||
469ad0ce05 | |||
8a8c13e407 | |||
18974eb69b | |||
9e406ed7a3 | |||
8b82a44ad8 | |||
528baff7b5 | |||
e105dc4663 |
19
CHANGELOG.md
19
CHANGELOG.md
@@ -5,6 +5,21 @@ 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/),
|
||||
und dieses Projekt adhäriert zu [Semantic Versioning](https://semver.org/lang/de/).
|
||||
|
||||
## [v1.2.0] - 2024-03-17
|
||||
|
||||
### Hinzugefügt
|
||||
- Benutzer-Login-Funktionalität
|
||||
- Login-Seite mit Passwortüberprüfung
|
||||
- Umgebungsvariable für Login-Passwort
|
||||
|
||||
## [v1.1.0] - 2024-03-17
|
||||
|
||||
### Geändert
|
||||
- Verbesserte Darstellung der Telefonnummern:
|
||||
- Separate Felder für private Telefonnummer, Firmennummer und Mobilfunknummer
|
||||
- Entfernung der Faxnummer aus der Anzeige
|
||||
- Übersichtlichere Darstellung der Kontaktinformationen
|
||||
|
||||
## [1.0.6] - 2024-03-17
|
||||
### Geändert
|
||||
- Verbesserte Suchfunktion: Kombinierte Suche über mehrere Felder möglich
|
||||
@@ -34,8 +49,10 @@ und dieses Projekt adhäriert zu [Semantic Versioning](https://semver.org/lang/d
|
||||
- Wetterinformationen für Kundensitz
|
||||
- Caching für Wetterdaten
|
||||
|
||||
## [1.0.0] - 2024-03-17
|
||||
## [v1.0.0] - 2024-03-17
|
||||
|
||||
### Hinzugefügt
|
||||
- Erste Version der Kundensuche
|
||||
- Grundlegende Suchfunktionen
|
||||
- Responsive Design
|
||||
- Docker-Integration
|
39
README.md
39
README.md
@@ -1,27 +1,14 @@
|
||||
# medisoftware Kundensuche
|
||||
|
||||
Eine webbasierte Kundensuche für medisoftware mit erweiterten Suchfunktionen.
|
||||
Eine webbasierte Suchanwendung für medisoftware Kunden, die eine schnelle und effiziente Suche nach Kundendaten ermöglicht.
|
||||
|
||||
## Features
|
||||
|
||||
- Schnelle und präzise Kundensuche
|
||||
- Mehrere Suchfelder für gezielte Suche:
|
||||
- Name (Vor- und Nachname)
|
||||
- Ort
|
||||
- Kundennummer
|
||||
- Fachrichtung
|
||||
- Telefon
|
||||
- Allgemeine Suche über alle Felder
|
||||
- Kombinierte Suche über mehrere Felder
|
||||
- Hervorhebung der Suchbegriffe in den Ergebnissen
|
||||
- Direkte Links zu:
|
||||
- medisoftware Kundenkartei (Kundennummer)
|
||||
- Google Maps (Adresse)
|
||||
- Telefon (Klick zum Anrufen)
|
||||
- E-Mail (Klick zum Mailen)
|
||||
- Responsive Design für alle Geräte
|
||||
- Automatische Aktualisierung der Ergebnisse
|
||||
- Leere Ergebnisliste bei leeren Suchfeldern
|
||||
- Echtzeit-Suche über Kundendaten
|
||||
- Hervorhebung von Suchbegriffen in den Ergebnissen
|
||||
- Klickbare Links für Telefonnummern, E-Mail-Adressen und Adressen
|
||||
- Responsive Design für mobile Geräte
|
||||
- Docker-Container für einfache Installation und Deployment
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -36,15 +23,13 @@ cd medi-customers
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
3. Die Anwendung ist unter `http://localhost:5001` erreichbar.
|
||||
Die Anwendung ist dann unter `http://localhost:5000` erreichbar.
|
||||
|
||||
## Entwicklung
|
||||
## Versionen
|
||||
|
||||
- Python 3.11
|
||||
- Flask
|
||||
- Docker
|
||||
- Bootstrap 5
|
||||
- Font Awesome
|
||||
- v1.2.0 (2024-03-17): Benutzer-Login hinzugefügt
|
||||
- v1.1.0 (2024-03-17): Verbesserte Darstellung der Telefonnummern
|
||||
- v1.0.0 (2024-03-17): Erste Version mit grundlegenden Suchfunktionen
|
||||
|
||||
## Lizenz
|
||||
|
||||
@@ -96,4 +81,4 @@ curl "http://localhost:5001/search?fachrichtung=Zahnarzt&ort=Berlin&name=Schmidt
|
||||
|
||||
## Version
|
||||
|
||||
Aktuelle Version: [1.0.5](CHANGELOG.md#105---2024-03-17)
|
||||
Aktuelle Version: [v1.2.0](CHANGELOG.md#v120---2024-03-17)
|
42
app.py
42
app.py
@@ -1,4 +1,4 @@
|
||||
from flask import Flask, render_template, request, jsonify, url_for
|
||||
from flask import Flask, render_template, request, jsonify, url_for, redirect, session
|
||||
import pandas as pd
|
||||
import os
|
||||
import logging
|
||||
@@ -7,8 +7,10 @@ from datetime import datetime, timedelta
|
||||
from dotenv import load_dotenv
|
||||
import requests
|
||||
from collections import defaultdict
|
||||
import ipaddress
|
||||
|
||||
app = Flask(__name__, static_folder='static')
|
||||
app.secret_key = 'your_secret_key' # Setzen Sie einen sicheren geheimen Schlüssel für die Session
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -21,6 +23,9 @@ CSV_FILE = "data/customers.csv"
|
||||
# Lade Umgebungsvariablen
|
||||
load_dotenv()
|
||||
|
||||
# Statisches Passwort aus der .env Datei
|
||||
STATIC_PASSWORD = os.getenv('LOGIN_PASSWORD', 'changeme')
|
||||
|
||||
def clean_dataframe(df):
|
||||
"""Konvertiert NaN-Werte in None für JSON-Kompatibilität"""
|
||||
return df.replace({np.nan: None})
|
||||
@@ -48,12 +53,47 @@ def load_data():
|
||||
logger.error(f"Fehler beim Laden der CSV-Datei: {str(e)}")
|
||||
return None
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
# Versuche, die tatsächliche Client-IP aus dem X-Forwarded-For-Header zu erhalten
|
||||
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}")
|
||||
logger.info(f"Erlaubte IP-Bereiche: {allowed_ip_ranges}")
|
||||
|
||||
# Überprüfen, ob die IP-Adresse in einem der erlaubten Subnetze liegt
|
||||
client_ip_obj = ipaddress.ip_address(client_ip)
|
||||
for ip_range in allowed_ip_ranges:
|
||||
try:
|
||||
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
|
||||
return redirect(url_for('index'))
|
||||
except ValueError:
|
||||
logger.error(f"Ungültiges Netzwerkformat: {ip_range}")
|
||||
|
||||
if request.method == 'POST':
|
||||
password = request.form.get('password')
|
||||
if password == STATIC_PASSWORD:
|
||||
session['logged_in'] = True
|
||||
return redirect(url_for('index'))
|
||||
else:
|
||||
return render_template('login.html', error="Falsches Passwort")
|
||||
return render_template('login.html')
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
if not session.get('logged_in'):
|
||||
return redirect(url_for('login'))
|
||||
return render_template('index.html')
|
||||
|
||||
@app.route('/search')
|
||||
def search():
|
||||
if not session.get('logged_in'):
|
||||
return redirect(url_for('login'))
|
||||
try:
|
||||
# CSV-Datei laden
|
||||
df = load_data()
|
||||
|
@@ -1,5 +1,3 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
|
BIN
static/medisoftware_logo_rb_200.png
Normal file
BIN
static/medisoftware_logo_rb_200.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.4 KiB |
@@ -166,11 +166,32 @@
|
||||
.customer-info {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.footer-content {
|
||||
padding: 1rem;
|
||||
background-color: #f8f9fa;
|
||||
border-top: 1px solid #dee2e6;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
.footer-link {
|
||||
color: #0d6efd;
|
||||
text-decoration: none;
|
||||
}
|
||||
.footer-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="main-content">
|
||||
<div class="container search-container">
|
||||
<div class="container">
|
||||
<div class="text-center mb-4">
|
||||
<img src="{{ url_for('static', filename='medisoftware_logo_rb_200.png') }}" alt="medisoftware Logo" class="img-fluid" style="max-width: 200px;">
|
||||
</div>
|
||||
<div class="search-container">
|
||||
<h1 class="text-center mb-4">medisoftware Kundensuche</h1>
|
||||
|
||||
<div class="input-group mb-4 position-relative">
|
||||
@@ -223,13 +244,17 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="shareFeedback" class="share-feedback">
|
||||
Link kopiert!
|
||||
</div>
|
||||
|
||||
<footer class="footer">
|
||||
<p class="mb-0">(c) 2025 <a href="https://medisoftware.de" target="_blank" rel="noopener noreferrer" class="text-decoration-none">medisoftware</a></p>
|
||||
<div class="footer-content">
|
||||
Made with ❤️ and 🍺 by <a href="https://www.medisoftware.de" target="_blank" class="footer-link">medisoftware</a>
|
||||
<div style="font-size: 0.8em;">Version: v1.2.0</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
@@ -303,9 +328,13 @@
|
||||
</a>`;
|
||||
}
|
||||
|
||||
function createCustomerLink(customerNumber) {
|
||||
if (!customerNumber) return 'N/A';
|
||||
return `<a href="medisw:openkkbefe/P${customerNumber}?NetGrp=4" class="customer-link">${customerNumber}</a>`;
|
||||
function adjustCustomerNumber(number) {
|
||||
return number - 12000;
|
||||
}
|
||||
|
||||
function createCustomerLink(number) {
|
||||
const adjustedNumber = adjustCustomerNumber(number);
|
||||
return `<a href="medisw:openkkbefe/P${adjustedNumber}?NetGrp=4" class="customer-link">${adjustedNumber}</a>`;
|
||||
}
|
||||
|
||||
function showCopyFeedback() {
|
||||
|
60
templates/login.html
Normal file
60
templates/login.html
Normal file
@@ -0,0 +1,60 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>medisoftware Kundensuche</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.main-content {
|
||||
flex: 1 0 auto;
|
||||
padding: 2rem 0;
|
||||
margin-bottom: 4rem; /* Platz für die fixierte Fußzeile */
|
||||
}
|
||||
.footer {
|
||||
flex-shrink: 0;
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
background-color: #f8f9fa;
|
||||
border-top: 1px solid #dee2e6;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-5">
|
||||
<div class="text-center mb-4">
|
||||
<img src="{{ url_for('static', filename='medisoftware_logo_rb_200.png') }}" alt="medisoftware Logo" class="img-fluid" style="max-width: 200px;">
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-4">
|
||||
<h2 class="text-center">Login</h2>
|
||||
<form method="POST" action="/login">
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Passwort</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100">Einloggen</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="footer">
|
||||
<div class="footer-content">
|
||||
Made with ❤️ and 🍺 by <a href="https://www.medisoftware.de" target="_blank" class="footer-link">medisoftware</a>
|
||||
<div style="font-size: 0.8em;">Version: v1.2.0</div>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
Reference in New Issue
Block a user