Fix: Login case-insensitive, Settings zusammengeführt, SVG-Icon, Copyright einzeilig

- Login: Benutzername wird case-insensitiv verglichen (func.lower auf beiden Seiten)
- Benutzer anlegen: Username wird immer lowercase gespeichert
- Einstellungen: Panels "Darstellung", "Ansicht & Raster" und "Ausgeblendete Kalender" zu einem einzigen Panel zusammengeführt
- App-Icon: Emoji 📅 durch plattformunabhängiges Inline-SVG ersetzt
- Copyright: white-space:nowrap +   damit Zeile nie umbricht
This commit is contained in:
2026-03-27 14:50:16 +01:00
parent c849f77651
commit 4f3db6142d
4 changed files with 19 additions and 23 deletions

View File

@@ -4,6 +4,7 @@ import pyotp
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm from fastapi.security import OAuth2PasswordRequestForm
from pydantic import BaseModel from pydantic import BaseModel
from sqlalchemy import func
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
import models import models
@@ -39,7 +40,7 @@ def setup(req: SetupRequest, db: Session = Depends(get_db)):
if db.query(models.User).count() > 0: if db.query(models.User).count() > 0:
raise HTTPException(400, "Setup already completed") raise HTTPException(400, "Setup already completed")
user = models.User( user = models.User(
username=req.username, username=req.username.lower(),
email=req.email, email=req.email,
password_hash=get_password_hash(req.password), password_hash=get_password_hash(req.password),
is_admin=True, is_admin=True,
@@ -58,7 +59,7 @@ def login(
form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db) form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)
): ):
user = ( user = (
db.query(models.User).filter(models.User.username == form_data.username).first() db.query(models.User).filter(func.lower(models.User.username) == form_data.username.lower()).first()
) )
if not user or not verify_password(form_data.password, user.password_hash): if not user or not verify_password(form_data.password, user.password_hash):
raise HTTPException( raise HTTPException(
@@ -78,7 +79,7 @@ def login(
@router.post("/login") @router.post("/login")
def login_json(req: LoginRequest, db: Session = Depends(get_db)): def login_json(req: LoginRequest, db: Session = Depends(get_db)):
user = ( user = (
db.query(models.User).filter(models.User.username == req.username).first() db.query(models.User).filter(func.lower(models.User.username) == req.username.lower()).first()
) )
if not user or not verify_password(req.password, user.password_hash): if not user or not verify_password(req.password, user.password_hash):
raise HTTPException( raise HTTPException(

View File

@@ -2,6 +2,7 @@ from typing import Optional
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel from pydantic import BaseModel
from sqlalchemy import func
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
import models import models
@@ -40,10 +41,10 @@ def create_user(
db: Session = Depends(get_db), db: Session = Depends(get_db),
_: models.User = Depends(get_current_admin), _: models.User = Depends(get_current_admin),
): ):
if db.query(models.User).filter(models.User.username == req.username).first(): if db.query(models.User).filter(func.lower(models.User.username) == req.username.lower()).first():
raise HTTPException(400, "Username already taken") raise HTTPException(400, "Username already taken")
user = models.User( user = models.User(
username=req.username, username=req.username.lower(),
email=req.email, email=req.email,
password_hash=get_password_hash(req.password), password_hash=get_password_hash(req.password),
is_admin=req.is_admin, is_admin=req.is_admin,

View File

@@ -355,6 +355,7 @@ a { color: var(--primary); text-decoration: none; }
text-align: center; cursor: pointer; text-align: center; cursor: pointer;
background: none; border: none; border-top: 1px solid var(--border); background: none; border: none; border-top: 1px solid var(--border);
transition: color var(--transition); transition: color var(--transition);
white-space: nowrap;
position: sticky; bottom: 0; position: sticky; bottom: 0;
background: var(--bg-sidebar); background: var(--bg-sidebar);
} }

View File

@@ -70,7 +70,7 @@
<button type="submit" class="btn btn-primary btn-full">Anmelden</button> <button type="submit" class="btn btn-primary btn-full">Anmelden</button>
</form> </form>
</div> </div>
<button class="impressum-link" onclick="openImpressum()">© 2026 Scarriffleservices</button> <button class="impressum-link" onclick="openImpressum()">©&nbsp;2026&nbsp;Scarriffleservices</button>
</div> </div>
<!-- ─── MAIN APP ──────────────────────────────────────────── --> <!-- ─── MAIN APP ──────────────────────────────────────────── -->
@@ -83,7 +83,7 @@
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/></svg> <svg viewBox="0 0 24 24" fill="currentColor"><path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/></svg>
</button> </button>
<div class="topbar-logo"> <div class="topbar-logo">
<span>📅</span> <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
<span class="logo-text">Calendarr</span> <span class="logo-text">Calendarr</span>
</div> </div>
</div> </div>
@@ -170,7 +170,7 @@
<div id="cal-list-items"></div> <div id="cal-list-items"></div>
</div> </div>
</div> </div>
<button class="sidebar-copyright" onclick="openImpressum()">© 2026 Scarriffleservices</button> <button class="sidebar-copyright" onclick="openImpressum()">©&nbsp;2026&nbsp;Scarriffleservices</button>
</aside> </aside>
<!-- MAIN VIEW --> <!-- MAIN VIEW -->
@@ -390,17 +390,16 @@
</div> </div>
<div class="settings-page-body"> <div class="settings-page-body">
<nav class="settings-nav"> <nav class="settings-nav">
<button class="settings-nav-btn active" data-panel="appearance">Darstellung</button> <button class="settings-nav-btn active" data-panel="general">Einstellungen</button>
<button class="settings-nav-btn" data-panel="view">Ansicht &amp; Raster</button>
<button class="settings-nav-btn" data-panel="google">Google Konten</button> <button class="settings-nav-btn" data-panel="google">Google Konten</button>
<button class="settings-nav-btn" data-panel="hidden">Ausgeblendete Kalender</button>
<button class="settings-nav-btn hidden" data-panel="users" id="settings-nav-users">Benutzerverwaltung</button> <button class="settings-nav-btn hidden" data-panel="users" id="settings-nav-users">Benutzerverwaltung</button>
</nav> </nav>
<div class="settings-panels"> <div class="settings-panels">
<!-- Darstellung --> <!-- Einstellungen (merged: Darstellung + Ansicht & Raster + Ausgeblendete Kalender) -->
<div class="settings-panel active" id="settings-panel-appearance"> <div class="settings-panel active" id="settings-panel-general">
<h4 class="panel-title">Farben</h4> <h4 class="panel-title">Farben</h4>
<div class="form-group"> <div class="form-group">
<label>Primärfarbe</label> <label>Primärfarbe</label>
@@ -441,11 +440,8 @@
<button class="contrast-btn" data-val="3"><span class="line-preview" style="border-color:#3a3a52"></span><span class="contrast-lbl">Normal</span></button> <button class="contrast-btn" data-val="3"><span class="line-preview" style="border-color:#3a3a52"></span><span class="contrast-lbl">Normal</span></button>
<button class="contrast-btn" data-val="4"><span class="line-preview" style="border-color:#5a5a78"></span><span class="contrast-lbl">Stark</span></button> <button class="contrast-btn" data-val="4"><span class="line-preview" style="border-color:#5a5a78"></span><span class="contrast-lbl">Stark</span></button>
</div> </div>
</div>
<!-- Ansicht & Raster --> <h4 class="panel-title" style="margin-top:24px">Kalenderansicht</h4>
<div class="settings-panel" id="settings-panel-view">
<h4 class="panel-title">Kalenderansicht</h4>
<div class="form-group"> <div class="form-group">
<label>Standardansicht</label> <label>Standardansicht</label>
<select id="cfg-default-view"> <select id="cfg-default-view">
@@ -477,6 +473,9 @@
<button class="contrast-btn" data-val="80"><span class="hour-preview">━━━━</span><span class="contrast-lbl">Komfort</span></button> <button class="contrast-btn" data-val="80"><span class="hour-preview">━━━━</span><span class="contrast-lbl">Komfort</span></button>
<button class="contrast-btn" data-val="100"><span class="hour-preview">━━━━━</span><span class="contrast-lbl">Gross</span></button> <button class="contrast-btn" data-val="100"><span class="hour-preview">━━━━━</span><span class="contrast-lbl">Gross</span></button>
</div> </div>
<h4 class="panel-title" style="margin-top:24px">Ausgeblendete Kalender</h4>
<div id="hidden-cals-list"><span style="font-size:13px;color:var(--text-3)">Keine ausgeblendeten Kalender</span></div>
</div> </div>
<!-- Google Konten --> <!-- Google Konten -->
@@ -485,12 +484,6 @@
<div id="google-accounts-list"><span style="font-size:13px;color:var(--text-3)">Keine Google-Konten verbunden</span></div> <div id="google-accounts-list"><span style="font-size:13px;color:var(--text-3)">Keine Google-Konten verbunden</span></div>
</div> </div>
<!-- Ausgeblendete Kalender -->
<div class="settings-panel" id="settings-panel-hidden">
<h4 class="panel-title">Ausgeblendete Kalender</h4>
<div id="hidden-cals-list"><span style="font-size:13px;color:var(--text-3)">Keine ausgeblendeten Kalender</span></div>
</div>
<!-- Benutzerverwaltung --> <!-- Benutzerverwaltung -->
<div class="settings-panel" id="settings-panel-users"> <div class="settings-panel" id="settings-panel-users">
<h4 class="panel-title">Benutzerverwaltung <span class="badge-admin">Admin</span></h4> <h4 class="panel-title">Benutzerverwaltung <span class="badge-admin">Admin</span></h4>