Dashboard V2
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user