Feature: Parallele Bild-Uploads, Heartbeat last_seen, Admin zuletzt aktiv, SW by-v1071
- Tagebuch: Bilder werden parallel hochgeladen (Promise.all), Button zeigt Fortschritt - Auth: /heartbeat Route ergänzt — aktualisiert last_seen alle 5 Min - Admin: last_seen + last_login in Nutzer-Liste angezeigt (🟢/🔵/⚪) - Bump SW by-v1071
This commit is contained in:
parent
9677d1e71a
commit
3abf974d29
8 changed files with 44 additions and 25 deletions
|
|
@ -410,7 +410,7 @@ async def serve_media(path: str, request: _Request):
|
||||||
raise _HE(404, "Nicht gefunden.")
|
raise _HE(404, "Nicht gefunden.")
|
||||||
return _media_response(filepath)
|
return _media_response(filepath)
|
||||||
|
|
||||||
APP_VER = "1070" # muss mit APP_VER in app.js übereinstimmen
|
APP_VER = "1071" # muss mit APP_VER in app.js übereinstimmen
|
||||||
|
|
||||||
@app.get("/.well-known/assetlinks.json")
|
@app.get("/.well-known/assetlinks.json")
|
||||||
async def assetlinks():
|
async def assetlinks():
|
||||||
|
|
|
||||||
|
|
@ -359,7 +359,7 @@ async def list_users(
|
||||||
SELECT u.id, u.name, {_email_col}, u.rolle, u.is_premium,
|
SELECT u.id, u.name, {_email_col}, u.rolle, u.is_premium,
|
||||||
u.is_moderator, u.is_banned, u.ban_reason,
|
u.is_moderator, u.is_banned, u.ban_reason,
|
||||||
u.is_founder, u.is_partner, u.founder_number,
|
u.is_founder, u.is_partner, u.founder_number,
|
||||||
u.created_at, u.last_login, u.subscription_tier,
|
u.created_at, u.last_login, u.last_seen, u.subscription_tier,
|
||||||
(SELECT COUNT(*) FROM dogs d WHERE d.user_id=u.id) AS dog_count,
|
(SELECT COUNT(*) FROM dogs d WHERE d.user_id=u.id) AS dog_count,
|
||||||
(SELECT COUNT(*) FROM forum_threads t WHERE t.user_id=u.id AND t.is_deleted=0) AS thread_count,
|
(SELECT COUNT(*) FROM forum_threads t WHERE t.user_id=u.id AND t.is_deleted=0) AS thread_count,
|
||||||
ROUND(COALESCE((SELECT SUM(r.distanz_km) FROM routes r WHERE r.user_id=u.id), 0), 1) AS total_km,
|
ROUND(COALESCE((SELECT SUM(r.distanz_km) FROM routes r WHERE r.user_id=u.id), 0), 1) AS total_km,
|
||||||
|
|
|
||||||
|
|
@ -479,3 +479,10 @@ async def select_primary_dog(body: dict, user=Depends(get_current_user)):
|
||||||
"UPDATE users SET needs_dog_selection=0 WHERE id=?", (user["id"],)
|
"UPDATE users SET needs_dog_selection=0 WHERE id=?", (user["id"],)
|
||||||
)
|
)
|
||||||
return {"ok": True}
|
return {"ok": True}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/heartbeat")
|
||||||
|
async def heartbeat(user=Depends(get_current_user)):
|
||||||
|
with db() as conn:
|
||||||
|
conn.execute("UPDATE users SET last_seen=datetime('now') WHERE id=?", (user["id"],))
|
||||||
|
return {"ok": True}
|
||||||
|
|
|
||||||
|
|
@ -101,9 +101,9 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
|
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
|
||||||
<link rel="stylesheet" href="/css/design-system.css?v=1070">
|
<link rel="stylesheet" href="/css/design-system.css?v=1071">
|
||||||
<link rel="stylesheet" href="/css/layout.css?v=1070">
|
<link rel="stylesheet" href="/css/layout.css?v=1071">
|
||||||
<link rel="stylesheet" href="/css/components.css?v=1070">
|
<link rel="stylesheet" href="/css/components.css?v=1071">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|
@ -616,10 +616,10 @@
|
||||||
<div id="modal-container"></div>
|
<div id="modal-container"></div>
|
||||||
|
|
||||||
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
|
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
|
||||||
<script src="/js/api.js?v=1070"></script>
|
<script src="/js/api.js?v=1071"></script>
|
||||||
<script src="/js/ui.js?v=1070"></script>
|
<script src="/js/ui.js?v=1071"></script>
|
||||||
<script src="/js/app.js?v=1070"></script>
|
<script src="/js/app.js?v=1071"></script>
|
||||||
<script src="/js/worlds.js?v=1070"></script>
|
<script src="/js/worlds.js?v=1071"></script>
|
||||||
|
|
||||||
<!-- Feature-Seiten werden lazy geladen -->
|
<!-- Feature-Seiten werden lazy geladen -->
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Router, State-Management, Navigation, Initialisierung.
|
Router, State-Management, Navigation, Initialisierung.
|
||||||
============================================================ */
|
============================================================ */
|
||||||
|
|
||||||
const APP_VER = '1070'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
const APP_VER = '1071'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||||
const APP_VERSION = '1.6.0'; // ← semantische Version, wird bei make release gesetzt
|
const APP_VERSION = '1.6.0'; // ← semantische Version, wird bei make release gesetzt
|
||||||
const IS_STAGING = location.hostname === 'staging.banyaro.app';
|
const IS_STAGING = location.hostname === 'staging.banyaro.app';
|
||||||
// Cache-Bust-Parameter nach Update-Reload sofort entfernen.
|
// Cache-Bust-Parameter nach Update-Reload sofort entfernen.
|
||||||
|
|
|
||||||
|
|
@ -820,7 +820,13 @@ window.Page_admin = (() => {
|
||||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:2px">
|
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:2px">
|
||||||
🗺 ${u.route_count} Routen · ${u.total_km} km
|
🗺 ${u.route_count} Routen · ${u.total_km} km
|
||||||
· 📍 ${u.poi_count} POIs
|
· 📍 ${u.poi_count} POIs
|
||||||
${u.last_route ? '· zuletzt ' + new Date(u.last_route).toLocaleDateString('de-DE') : ''}
|
</div>
|
||||||
|
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:2px">
|
||||||
|
${u.last_seen
|
||||||
|
? '🟢 zuletzt aktiv ' + new Date(u.last_seen).toLocaleString('de-DE', {day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'})
|
||||||
|
: u.last_login
|
||||||
|
? '🔵 zuletzt eingeloggt ' + new Date(u.last_login).toLocaleString('de-DE', {day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'})
|
||||||
|
: '⚪ nie aktiv'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1722,29 +1722,35 @@ window.Page_diary = (() => {
|
||||||
};
|
};
|
||||||
|
|
||||||
async function _uploadNewFiles(entryId) {
|
async function _uploadNewFiles(entryId) {
|
||||||
let failCount = 0;
|
const total = _newFiles.length;
|
||||||
const uploaded = [];
|
const saveBtn = document.querySelector('button[form="diary-form"]');
|
||||||
let exifGps = null;
|
let done = 0;
|
||||||
for (const file of _newFiles) {
|
if (saveBtn) saveBtn.textContent = `0 von ${total} hochgeladen…`;
|
||||||
try {
|
|
||||||
|
const results = await Promise.all(_newFiles.map(async file => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file);
|
formData.append('file', file);
|
||||||
|
try {
|
||||||
const m = await API.diary.uploadMedia(_appState.activeDog.id, entryId, formData);
|
const m = await API.diary.uploadMedia(_appState.activeDog.id, entryId, formData);
|
||||||
uploaded.push(m);
|
if (saveBtn) saveBtn.textContent = `${++done} von ${total} hochgeladen…`;
|
||||||
if (m.exif_lat != null && m.exif_lon != null && !exifGps) {
|
return { ok: true, m };
|
||||||
exifGps = { lat: m.exif_lat, lon: m.exif_lon };
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
failCount++;
|
if (saveBtn) saveBtn.textContent = `${++done} von ${total} hochgeladen…`;
|
||||||
}
|
return { ok: false };
|
||||||
}
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
const uploaded = results.filter(r => r.ok).map(r => r.m);
|
||||||
|
const failCount = results.filter(r => !r.ok).length;
|
||||||
|
const exifGps = results.find(r => r.ok && r.m.exif_lat != null)?.m;
|
||||||
|
|
||||||
if (failCount > 0) {
|
if (failCount > 0) {
|
||||||
UI.toast.warning(`${failCount} Medium${failCount > 1 ? 'en' : ''} konnte${failCount > 1 ? 'n' : ''} nicht hochgeladen werden.`);
|
UI.toast.warning(`${failCount} Medium${failCount > 1 ? 'en' : ''} konnte${failCount > 1 ? 'n' : ''} nicht hochgeladen werden.`);
|
||||||
}
|
}
|
||||||
if (exifGps) {
|
if (exifGps) {
|
||||||
UI.toast.success(`📍 Standort aus Foto-GPS übernommen`);
|
UI.toast.success(`📍 Standort aus Foto-GPS übernommen`);
|
||||||
}
|
}
|
||||||
return { uploaded, exifGps };
|
return { uploaded, exifGps: exifGps ? { lat: exifGps.exif_lat, lon: exifGps.exif_lon } : null };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
============================================================ */
|
============================================================ */
|
||||||
|
|
||||||
// ← EINZIGE Stelle für die Version — STATIC_ASSETS und CACHE_VERSION leiten sich ab
|
// ← EINZIGE Stelle für die Version — STATIC_ASSETS und CACHE_VERSION leiten sich ab
|
||||||
const VER = '1070';
|
const VER = '1071';
|
||||||
const CACHE_VERSION = `by-v${VER}`;
|
const CACHE_VERSION = `by-v${VER}`;
|
||||||
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
||||||
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
|
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue