Einige kleine verbesserungen #1

Open
Scarriffle wants to merge 115 commits from beta into master
6 changed files with 25 additions and 15 deletions
Showing only changes of commit 2f8fed0600 - Show all commits

View File

@@ -1,3 +1,4 @@
from datetime import timedelta
from typing import Optional from typing import Optional
import pyotp import pyotp
@@ -11,6 +12,9 @@ import models
from auth import create_access_token, get_current_user, get_password_hash, verify_password from auth import create_access_token, get_current_user, get_password_hash, verify_password
from database import get_db from database import get_db
# When "Angemeldet bleiben" is ticked the token lives for half a year.
REMEMBER_ME_EXPIRY = timedelta(days=180)
router = APIRouter() router = APIRouter()
@@ -24,6 +28,7 @@ class LoginRequest(BaseModel):
username: str username: str
password: str password: str
totp_code: Optional[str] = None totp_code: Optional[str] = None
remember_me: Optional[bool] = False
def _user_dict(user: models.User) -> dict: def _user_dict(user: models.User) -> dict:
@@ -98,7 +103,8 @@ def login_json(req: LoginRequest, db: Session = Depends(get_db)):
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED,
detail="Ungültiger 2FA-Code", detail="Ungültiger 2FA-Code",
) )
token = create_access_token({"sub": user.username}) expires = REMEMBER_ME_EXPIRY if req.remember_me else None
token = create_access_token({"sub": user.username}, expires_delta=expires)
return {"access_token": token, "token_type": "bearer", "user": _user_dict(user)} return {"access_token": token, "token_type": "bearer", "user": _user_dict(user)}

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, viewport-fit=cover" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<!-- APP_VERSION: update here + version.js on every release --> <!-- APP_VERSION: update here + version.js on every release -->
<title>Calendarr v5</title> <title>Calendarr v6</title>
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/static/favicon.svg" />
<link rel="manifest" href="/manifest.json" /> <link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#4285f4" /> <meta name="theme-color" content="#4285f4" />
@@ -73,11 +73,14 @@
<label>2FA-Code</label> <label>2FA-Code</label>
<input type="text" id="login-totp" placeholder="6-stelliger Code" maxlength="6" inputmode="numeric" autocomplete="one-time-code" /> <input type="text" id="login-totp" placeholder="6-stelliger Code" maxlength="6" inputmode="numeric" autocomplete="one-time-code" />
</div> </div>
<label class="toggle-label" style="margin-bottom:14px">
<input type="checkbox" id="login-remember" /> Angemeldet bleiben
</label>
<div id="login-error" class="form-error hidden"></div> <div id="login-error" class="form-error hidden"></div>
<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()">©&nbsp;2026&nbsp;Scarriffleservices&nbsp;·&nbsp;v5</button> <button class="impressum-link" onclick="openImpressum()">©&nbsp;2026&nbsp;Scarriffleservices&nbsp;·&nbsp;v6</button>
</div> </div>
<!-- ─── MAIN APP ──────────────────────────────────────────── --> <!-- ─── MAIN APP ──────────────────────────────────────────── -->
@@ -196,7 +199,7 @@
<div id="cal-list-items"></div> <div id="cal-list-items"></div>
</div> </div>
</div> </div>
<button class="sidebar-copyright" onclick="openImpressum()">©&nbsp;2026&nbsp;Scarriffleservices&nbsp;·&nbsp;v5</button> <button class="sidebar-copyright" onclick="openImpressum()">©&nbsp;2026&nbsp;Scarriffleservices&nbsp;·&nbsp;v6</button>
</aside> </aside>
<div id="sidebar-backdrop" class="sidebar-backdrop"></div> <div id="sidebar-backdrop" class="sidebar-backdrop"></div>
@@ -232,7 +235,7 @@
<input type="hidden" id="ev-start" /> <input type="hidden" id="ev-start" />
<div class="dt-display" id="ev-start-display" tabindex="0" role="button"> <div class="dt-display" id="ev-start-display" tabindex="0" role="button">
<span class="dt-display-text"></span> <span class="dt-display-text"></span>
<svg class="dt-display-icon" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v24a2 2 0 002 2h14a2 2 0 002-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v21zM7 10h5v5H7z"/></svg> <svg class="dt-display-icon" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v24a2 2 0 002 2h14a2 2 0 002-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v21zM7 10h5v6H7z"/></svg>
</div> </div>
</div> </div>
<div class="form-group half"> <div class="form-group half">
@@ -240,7 +243,7 @@
<input type="hidden" id="ev-end" /> <input type="hidden" id="ev-end" />
<div class="dt-display" id="ev-end-display" tabindex="0" role="button"> <div class="dt-display" id="ev-end-display" tabindex="0" role="button">
<span class="dt-display-text"></span> <span class="dt-display-text"></span>
<svg class="dt-display-icon" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v24a2 2 0 002 2h14a2 2 0 002-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v21zM7 10h5v5H7z"/></svg> <svg class="dt-display-icon" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v24a2 2 0 002 2h14a2 2 0 002-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v21zM7 10h5v6H7z"/></svg>
</div> </div>
</div> </div>
</div> </div>
@@ -250,7 +253,7 @@
<input type="hidden" id="ev-start-date" /> <input type="hidden" id="ev-start-date" />
<div class="dt-display" id="ev-start-date-display" tabindex="0" role="button"> <div class="dt-display" id="ev-start-date-display" tabindex="0" role="button">
<span class="dt-display-text"></span> <span class="dt-display-text"></span>
<svg class="dt-display-icon" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v24a2 2 0 002 2h14a2 2 0 002-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v21zM7 10h5v5H7z"/></svg> <svg class="dt-display-icon" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v24a2 2 0 002 2h14a2 2 0 002-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v21zM7 10h5v6H7z"/></svg>
</div> </div>
</div> </div>
<div class="form-group half"> <div class="form-group half">
@@ -258,7 +261,7 @@
<input type="hidden" id="ev-end-date" /> <input type="hidden" id="ev-end-date" />
<div class="dt-display" id="ev-end-date-display" tabindex="0" role="button"> <div class="dt-display" id="ev-end-date-display" tabindex="0" role="button">
<span class="dt-display-text"></span> <span class="dt-display-text"></span>
<svg class="dt-display-icon" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v24a2 2 0 002 2h14a2 2 0 002-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v21zM7 10h5v5H7z"/></svg> <svg class="dt-display-icon" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v24a2 2 0 002 2h14a2 2 0 002-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v21zM7 10h5v6H7z"/></svg>
</div> </div>
</div> </div>
</div> </div>
@@ -308,7 +311,7 @@
<input type="hidden" id="ev-rec-until" /> <input type="hidden" id="ev-rec-until" />
<div class="dt-display" id="ev-rec-until-display" tabindex="0" role="button"> <div class="dt-display" id="ev-rec-until-display" tabindex="0" role="button">
<span class="dt-display-text"></span> <span class="dt-display-text"></span>
<svg class="dt-display-icon" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v24a2 2 0 002 2h14a2 2 0 002-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v21zM7 10h5v5H7z"/></svg> <svg class="dt-display-icon" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v24a2 2 0 002 2h14a2 2 0 002-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v21zM7 10h5v6H7z"/></svg>
</div> </div>
</div> </div>
</div> </div>
@@ -862,7 +865,7 @@
<a href="mailto:scarriffleservices@gmail.com">scarriffleservices@gmail.com</a></p> <a href="mailto:scarriffleservices@gmail.com">scarriffleservices@gmail.com</a></p>
</div> </div>
<div class="modal-footer" style="justify-content:space-between;align-items:center"> <div class="modal-footer" style="justify-content:space-between;align-items:center">
<span style="font-size:12px;color:var(--text-3)">Calendarr v5</span> <span style="font-size:12px;color:var(--text-3)">Calendarr v6</span>
<button class="btn btn-ghost" onclick="closeImpressum()">Schliessen</button> <button class="btn btn-ghost" onclick="closeImpressum()">Schliessen</button>
</div> </div>
</div> </div>

View File

@@ -64,8 +64,8 @@ export const api = {
delete: (path) => request('DELETE', path), delete: (path) => request('DELETE', path),
upload: (path, form) => uploadRequest(path, form), upload: (path, form) => uploadRequest(path, form),
login: (username, password, totp_code = null) => login: (username, password, totp_code = null, remember_me = false) =>
request('POST', '/auth/login', { username, password, totp_code }), request('POST', '/auth/login', { username, password, totp_code, remember_me }),
setupRequired: () => request('GET', '/auth/setup-required'), setupRequired: () => request('GET', '/auth/setup-required'),
setup: (data) => request('POST', '/auth/setup', data), setup: (data) => request('POST', '/auth/setup', data),

View File

@@ -141,11 +141,12 @@ function bindLoginForm() {
const username = document.getElementById('login-username').value.trim(); const username = document.getElementById('login-username').value.trim();
const password = document.getElementById('login-password').value; const password = document.getElementById('login-password').value;
const totpCode = document.getElementById('login-totp')?.value.trim() || null; const totpCode = document.getElementById('login-totp')?.value.trim() || null;
const remember = document.getElementById('login-remember')?.checked || false;
const errEl = document.getElementById('login-error'); const errEl = document.getElementById('login-error');
errEl.classList.add('hidden'); errEl.classList.add('hidden');
try { try {
const res = await api.login(username, password, totpCode); const res = await api.login(username, password, totpCode, remember);
localStorage.setItem('token', res.access_token); localStorage.setItem('token', res.access_token);
localStorage.setItem('user', JSON.stringify(res.user)); localStorage.setItem('user', JSON.stringify(res.user));
await launchApp(); await launchApp();

View File

@@ -1,2 +1,2 @@
// Increment APP_VERSION with every code change // Increment APP_VERSION with every code change
export const APP_VERSION = 'v5'; export const APP_VERSION = 'v6';

View File

@@ -1,7 +1,7 @@
// Calendarr Service Worker // Calendarr Service Worker
// Cache-first for static assets, network-first for /api/* (graceful offline) // Cache-first for static assets, network-first for /api/* (graceful offline)
const CACHE_VERSION = 'calendarr-v5'; const CACHE_VERSION = 'calendarr-v6';
const STATIC_ASSETS = [ const STATIC_ASSETS = [
'/', '/',
'/index.html', '/index.html',