Dashboard V2

This commit is contained in:
Jason Hirsch
2025-02-19 17:27:01 +01:00
parent 14423e0497
commit 2b53b40778
7 changed files with 684 additions and 194 deletions

View File

@@ -1,7 +1,15 @@
import sqlite3
from flask import Flask, render_template, request, redirect, url_for, session, g, flash
from flask import Flask, render_template, request, redirect, url_for, session, g, flash, jsonify
from datetime import datetime
from werkzeug.security import generate_password_hash, check_password_hash
import smtplib, ssl, secrets
from datetime import datetime, timedelta
import json
SMTP_SERVER = "smtp.gmail.com"
SMTP_PORT = 465
EMAIL_SENDER = "clickcandit@gmail.com"
EMAIL_PASSWORD = "iuxexntistlwilhl"
app = Flask(__name__)
app.secret_key = "SUPER_SECRET_KEY" # Bitte anpassen
@@ -55,6 +63,14 @@ def init_db():
is_read INTEGER DEFAULT 0
);
""")
db.execute("""
CREATE TABLE IF NOT EXISTS reset_tokens (
user_id INTEGER NOT NULL,
token TEXT NOT NULL,
expires_at DATETIME NOT NULL,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);
""")
# Time Tracking
db.execute("""
CREATE TABLE IF NOT EXISTS time_entries (
@@ -66,15 +82,41 @@ def init_db():
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);
""")
# Settings (OPTIONAL, falls du globale User-Einstellungen abspeichern willst)
db.execute("""
CREATE TABLE IF NOT EXISTS user_settings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
wallpaper TEXT DEFAULT '1.png',
city TEXT DEFAULT '',
show_forecast INTEGER DEFAULT 0,
bookmarks TEXT DEFAULT '',
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);
""")
db.execute("""
CREATE TABLE IF NOT EXISTS settings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
wallpaper TEXT DEFAULT '19.png',
city TEXT DEFAULT 'Berlin',
show_forecast INTEGER DEFAULT 1,
bookmarks TEXT DEFAULT '[]',
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);
""")
db.commit()
# ------------------------------------------------------------
# HILFSFUNKTIONEN
# ------------------------------------------------------------
def get_user_by_username_or_email(user_input):
db = get_db()
user = db.execute("""
return db.execute("""
SELECT * FROM users
WHERE username = :val OR email = :val
""", {"val": user_input}).fetchone()
return user
def get_user_by_username(username):
db = get_db()
@@ -87,13 +129,18 @@ def get_user_by_id(user_id):
def is_admin():
return session.get('is_admin', 0) == 1
# ------------------------------------------------------------
# STARTSEITE
# ------------------------------------------------------------
@app.route('/')
def index():
if 'user_id' in session:
return redirect(url_for('dashboard'))
return redirect(url_for('login'))
# --------------------- REGISTRIEREN -------------------------
# ------------------------------------------------------------
# REGISTRIEREN: erster User wird Admin
# ------------------------------------------------------------
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
@@ -114,7 +161,7 @@ def register():
# Erster registrierter User wird Admin
count = db.execute("SELECT COUNT(*) as cnt FROM users").fetchone()['cnt']
is_admin_val = 1 if count == 0 else 0 # Falls noch niemand existiert, ist das der Admin
is_admin_val = 1 if count == 0 else 0
hashed_pw = generate_password_hash(password)
db.execute("""
@@ -122,11 +169,25 @@ def register():
VALUES (?, ?, ?, ?)
""", (username, hashed_pw, email, is_admin_val))
db.commit()
# OPTIONAL: initialer Eintrag in user_settings
if is_admin_val == 1:
# Falls du globale Settings pro Benutzer willst
user_id = db.execute("SELECT id FROM users WHERE username=?",(username,)).fetchone()['id']
db.execute("""
INSERT INTO user_settings (user_id, wallpaper, city, show_forecast, bookmarks)
VALUES (?, '1.png', '', 0, '')
""", (user_id,))
db.commit()
flash('Registrierung erfolgreich! Bitte melde dich an.', 'green')
return redirect(url_for('login'))
return render_template('register.html')
# --------------------- LOGIN/LOGOUT -------------------------
# ------------------------------------------------------------
# LOGIN / LOGOUT
# ------------------------------------------------------------
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
@@ -149,33 +210,107 @@ def logout():
flash("Erfolgreich abgemeldet!", "green")
return redirect(url_for('login'))
# --------------------- PASSWORT VERGESSEN -------------------
# ------------------------------------------------------------
# PASSWORT VERGESSEN
# ------------------------------------------------------------
@app.route('/reset_password/<token>', methods=['GET', 'POST'])
def reset_password(token):
db = get_db()
row = db.execute("""
SELECT * FROM reset_tokens
WHERE token = ?
""", (token,)).fetchone()
if not row:
flash("Ungültiges oder bereits benutztes Token!", "red")
return redirect(url_for('forgot_password'))
# Ablaufdatum prüfen
expires_at = datetime.fromisoformat(row['expires_at'])
if datetime.now() > expires_at:
flash("Token abgelaufen! Bitte erneut anfordern.", "red")
db.execute("DELETE FROM reset_tokens WHERE token=?", (token,))
db.commit()
return redirect(url_for('forgot_password'))
if request.method == 'POST':
pw1 = request.form.get('pw1')
pw2 = request.form.get('pw2')
if not pw1 or not pw2:
flash("Bitte beide Felder ausfüllen!", "red")
return redirect(url_for('reset_password', token=token))
if pw1 != pw2:
flash("Passwörter stimmen nicht überein!", "red")
return redirect(url_for('reset_password', token=token))
hashed_pw = generate_password_hash(pw1)
# Passwort setzen
db.execute("UPDATE users SET password=? WHERE id=?", (hashed_pw, row['user_id']))
# Token direkt löschen
db.execute("DELETE FROM reset_tokens WHERE token=?", (token,))
db.commit()
flash("Passwort erfolgreich zurückgesetzt! Du kannst dich jetzt einloggen.", "green")
return redirect(url_for('login'))
# GET: Formular
return render_template('reset_password.html', token=token)
@app.route('/forgot_password', methods=['GET', 'POST'])
def forgot_password():
if request.method == 'POST':
user_input = request.form.get('username_or_email')
if not user_input:
flash("Bitte einen Benutzernamen oder eine E-Mail angeben!", "red")
flash("Bitte Benutzernamen oder E-Mail eingeben!", "red")
return redirect(url_for('forgot_password'))
user = get_user_by_username_or_email(user_input)
# Hier könnte man z.B. eine E-Mail mit Link senden oder im Notfall ein neues PW setzen
db = get_db()
# Schaue nach Username oder Email
user = db.execute("""
SELECT * FROM users
WHERE username=? OR email=?
""", (user_input, user_input)).fetchone()
if user:
# Demo: Wir setzen einfach das Passwort auf "reset123" (nicht sicher!)
# Besser: generiere Token, sende E-Mail etc.
new_pw_hashed = generate_password_hash("reset123")
db = get_db()
db.execute("UPDATE users SET password=? WHERE id=?", (new_pw_hashed, user['id']))
# Token generieren
token = secrets.token_urlsafe(32)
expires = datetime.now() + timedelta(hours=1)
db.execute("""
INSERT INTO reset_tokens (user_id, token, expires_at)
VALUES (?, ?, ?)
""", (user['id'], token, expires))
db.commit()
flash("Dein Passwort wurde zurückgesetzt auf 'reset123'. Bitte ändere es nach dem Login!", "green")
reset_link = f"{request.url_root}reset_password/{token}"
subject = "Passwort zurücksetzen"
body = f"""Hallo {user['username']},
klicke auf folgenden Link, um dein Passwort zurückzusetzen (gültig 1 Stunde):
{reset_link}
Wenn du das nicht angefordert hast, ignoriere diese Nachricht.
"""
message = f"Subject: {subject}\n\n{body}"
context = ssl.create_default_context()
try:
with smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT, context=context) as server:
server.login(EMAIL_SENDER, EMAIL_PASSWORD)
server.sendmail(EMAIL_SENDER, user['email'], message)
flash("Eine E-Mail zum Zurücksetzen deines Passworts wurde versendet!", "green")
except Exception as e:
flash(f"Fehler beim Senden der E-Mail: {e}", "red")
return redirect(url_for('login'))
else:
flash("Kein passender Benutzer gefunden. Versuche es erneut!", "red")
flash("Kein Nutzer gefunden!", "red")
return redirect(url_for('forgot_password'))
return render_template('forgot_password.html')
# --------------------- ADMIN-BEREICH ------------------------
# ------------------------------------------------------------
# ADMIN-BEREICH
# ------------------------------------------------------------
@app.route('/admin', methods=['GET', 'POST'])
def admin_panel():
if not is_admin():
@@ -214,7 +349,9 @@ def delete_user(user_id):
flash("Benutzer gelöscht!", "green")
return redirect(url_for('admin_panel'))
# --------------------- NOTIFICATIONS ------------------------
# ------------------------------------------------------------
# BENACHRICHTIGUNGEN
# ------------------------------------------------------------
@app.route('/admin/notifications', methods=['POST'])
def add_notification():
if not is_admin():
@@ -233,7 +370,9 @@ def add_notification():
flash("Benachrichtigung erstellt!", "green")
return redirect(url_for('admin_panel'))
# --------------------- BOOKMARKS (nur Admin pflegbar) -------
# ------------------------------------------------------------
# BOOKMARKS
# ------------------------------------------------------------
@app.route('/admin/bookmarks/<int:user_id>', methods=['GET', 'POST'])
def manage_bookmarks(user_id):
if not is_admin():
@@ -244,13 +383,14 @@ def manage_bookmarks(user_id):
if not user:
flash("Benutzer nicht gefunden!", "red")
return redirect(url_for('admin_panel'))
if request.method == 'POST':
title = request.form.get('title')
url = request.form.get('url')
url_ = request.form.get('url')
icon = request.form.get('icon_class', 'fas fa-bookmark')
if title and url:
if title and url_:
db.execute("INSERT INTO bookmarks (user_id, title, url, icon_class) VALUES (?, ?, ?, ?)",
(user_id, title, url, icon))
(user_id, title, url_, icon))
db.commit()
flash("Neues Lesezeichen hinzugefügt!", "green")
@@ -268,7 +408,9 @@ def delete_bookmark(bookmark_id, user_id):
flash("Lesezeichen gelöscht!", "green")
return redirect(url_for('manage_bookmarks', user_id=user_id))
# --------------------- ZEITERFASSUNG ------------------------
# ------------------------------------------------------------
# ZEITERFASSUNG
# ------------------------------------------------------------
@app.route('/time_tracking', methods=['POST'])
def time_tracking():
if 'user_id' not in session:
@@ -306,43 +448,193 @@ def time_tracking():
return redirect(url_for('dashboard'))
# --------------------- DASHBOARD ----------------------------
# ------------------------------------------------------------
# DASHBOARD
# ------------------------------------------------------------
@app.route('/dashboard')
def dashboard():
if 'user_id' not in session:
flash("Bitte erst einloggen!", "red")
flash("Bitte melde dich an!", "red")
return redirect(url_for('login'))
db = get_db()
user_id = session['user_id']
user = get_user_by_id(user_id)
db = get_db()
# Notifications (global oder speziell für diesen User)
notifications = db.execute("""
SELECT * FROM notifications
WHERE user_id = ? OR user_id IS NULL
ORDER BY created_at DESC
""", (user_id,)).fetchall()
# User abfragen
user = db.execute("SELECT * FROM users WHERE id = ?", (user_id,)).fetchone()
if not user:
flash("Benutzer nicht gefunden.", "red")
return redirect(url_for('logout'))
# Bookmarks für diesen Benutzer
user_bookmarks = db.execute("SELECT * FROM bookmarks WHERE user_id = ?", (user_id,)).fetchall()
# Settings aus user_settings
settings = db.execute("SELECT * FROM user_settings WHERE user_id = ?", (user_id,)).fetchone()
# Time-Entries
time_entries = db.execute("""
SELECT * FROM time_entries
WHERE user_id = ?
ORDER BY start_time DESC
""", (user_id,)).fetchall()
# Standardwerte
if not settings:
wallpaper = '19.png'
city = 'Berlin'
show_forecast = True
bookmarks = []
else:
wallpaper = settings['wallpaper']
city = settings['city']
show_forecast = bool(settings['show_forecast'])
if settings['bookmarks']:
bookmarks = settings['bookmarks'].split(",")
else:
bookmarks = []
return render_template('dashboard.html',
user=user['username'],
notifications=notifications,
user_bookmarks=user_bookmarks,
time_entries=time_entries,
domain="meinedomain.de",
logo_path="static/logo.png")
# Wetter holen (wenn gewünscht)
current_temp, weather_icon, forecast = get_weather(city)
if current_temp is None:
current_temp = "N/A"
weather_icon = "fa-question"
forecast = []
# --------------------- STARTUP ------------------------------
# Domain, Logo usw.
domain = "example.com"
logo_path = url_for('static', filename='clickcandit.png')
# Beispiel-Apps
user_app_chunks = [
[
{
'name': 'Mail',
'subdomain': 'mail',
'appkey': 'mailapp',
'icon_class': 'fa fa-envelope',
'bg_color': 'bg-blue-500'
},
{
'name': 'Calendar',
'subdomain': 'calendar',
'appkey': 'calendarapp',
'icon_class': 'fa fa-calendar',
'bg_color': 'bg-green-500'
},
]
]
return render_template(
'dashboard.html',
user=user['username'],
wallpaper=wallpaper,
city=city,
show_forecast=show_forecast,
bookmarks=bookmarks,
current_temp=current_temp,
weather_icon=weather_icon,
forecast=forecast,
domain=domain,
logo_path=logo_path,
user_app_chunks=user_app_chunks
)
# ------------------------------------------------------------
# SUPPORT & SETTINGS ROUTEN (Aus V1)
# ------------------------------------------------------------
@app.route('/send_support_message', methods=['POST'])
def send_support_message():
"""
Beispiel-Endpunkt, den dein Support-Modal per Fetch aufruft.
Erwartet JSON-Daten: { "email": ..., "problemType": ..., "message": ... }
"""
data = request.get_json()
email = data.get('email')
problem_type = data.get('problemType')
message = data.get('message')
if not email or not message:
return jsonify({"success": False, "error": "Fehlende Felder"}), 400
# Hier könntest du z.B. eine E-Mail verschicken oder einen Eintrag in der DB anlegen
# Demo: wir legen einfach einen Notification-Eintrag an
db = get_db()
db.execute("""
INSERT INTO notifications (user_id, message)
VALUES (NULL, ?)
""", (f"Support-Anfrage ({problem_type}) von {email}: {message}",))
db.commit()
return jsonify({"success": True})
@app.route('/get_settings', methods=['GET'])
def get_settings():
"""
Liefert das aktuell gespeicherte Setting zurück (V1-Modal).
Du brauchst user_settings oder ein eigenes Modell, wo du die Bookmarks/Stadt/etc. speicherst
"""
if 'user_id' not in session:
return jsonify({"error": "not logged in"}), 403
db = get_db()
settings_row = db.execute("SELECT * FROM user_settings WHERE user_id=?", (session['user_id'],)).fetchone()
if settings_row:
return jsonify({
"wallpaper": settings_row['wallpaper'],
"city": settings_row['city'],
"show_forecast": bool(settings_row['show_forecast']),
"bookmarks": settings_row['bookmarks'].split(",") if settings_row['bookmarks'] else []
})
else:
# Falls noch kein Datensatz existiert
return jsonify({
"wallpaper": "1.png",
"city": "",
"show_forecast": False,
"bookmarks": []
})
@app.route('/save_settings', methods=['POST'])
def save_settings():
"""
Speichert die Einstellungen, die vom Settings-Modal per Fetch geschickt werden
JSON body: { "wallpaper": ..., "city": ..., "show_forecast": ..., "bookmarks": ... }
"""
if 'user_id' not in session:
return jsonify({"success": False, "error": "not logged in"}), 403
data = request.get_json()
wallpaper = data.get('wallpaper', '1.png')
city = data.get('city', '')
show_forecast = data.get('show_forecast', False)
bookmarks_list = data.get('bookmarks', [])
db = get_db()
# Check if row exists
existing = db.execute("SELECT id FROM user_settings WHERE user_id=?", (session['user_id'],)).fetchone()
if existing:
db.execute("""
UPDATE user_settings
SET wallpaper=?, city=?, show_forecast=?, bookmarks=?
WHERE user_id=?
""", (wallpaper, city, int(show_forecast), ",".join(bookmarks_list), session['user_id']))
else:
db.execute("""
INSERT INTO user_settings (user_id, wallpaper, city, show_forecast, bookmarks)
VALUES (?, ?, ?, ?, ?)
""", (session['user_id'], wallpaper, city, int(show_forecast), ",".join(bookmarks_list)))
db.commit()
return jsonify({"success": True})
def get_weather(city):
"""
Gibt (current_temp, weather_icon, forecast) zurück
"""
# Hier kannst du z. B. eine Wetter-API anfragen
# oder Dummy-Werte für den Anfang setzen
# DUMMY Beispiel:
current_temp = 24
weather_icon = "fa-cloud"
forecast = []
return (current_temp, weather_icon, forecast)
# ------------------------------------------------------------
# STARTUP
# ------------------------------------------------------------
if __name__ == '__main__':
init_db()
app.run(debug=True)