diff --git a/app/admin.py b/app/admin.py index 8c38f3f..0a09c31 100644 --- a/app/admin.py +++ b/app/admin.py @@ -1,3 +1,12 @@ from django.contrib import admin -# Register your models here. +from .models import UsageEvent + + +@admin.register(UsageEvent) +class UsageEventAdmin(admin.ModelAdmin): + list_display = ("device_id", "created_at", "app_version", "platform", "browser", "is_standalone") + list_filter = ("platform", "browser", "is_standalone", "app_version") + search_fields = ("device_id",) + date_hierarchy = "created_at" + ordering = ("-created_at",) diff --git a/app/models.py b/app/models.py index 71a8362..352baa7 100644 --- a/app/models.py +++ b/app/models.py @@ -1,3 +1,27 @@ from django.db import models -# Create your models here. + +class UsageEvent(models.Model): + """Anonymes Nutzungssignal für das Admin-Dashboard (siehe home01.js: pingUsageIfNeeded()). + + Ein Eintrag pro Gerät und Tag - device_id ist eine zufällige, nur lokal im Browser + (localStorage) gespeicherte ID ohne jeden Bezug zu einer echten Person. Plattform und + Browser werden serverseitig aus dem User-Agent-Header grob abgeleitet (siehe app/utils.py). + """ + + device_id = models.CharField(max_length=64, db_index=True) + app_version = models.CharField(max_length=20, blank=True) + user_agent = models.TextField(blank=True) + platform = models.CharField(max_length=40, blank=True) + browser = models.CharField(max_length=40, blank=True) + is_standalone = models.BooleanField( + default=False, + help_text="True, wenn als installierte PWA (Homescreen) geöffnet statt im Browser-Tab.", + ) + created_at = models.DateTimeField(auto_now_add=True, db_index=True) + + class Meta: + ordering = ["-created_at"] + + def __str__(self): + return f"{self.device_id[:8]}… – {self.created_at:%d.%m.%Y %H:%M}" diff --git a/app/static/app/js/home01.js b/app/static/app/js/home01.js index f2e4fc4..4f2d0b6 100644 --- a/app/static/app/js/home01.js +++ b/app/static/app/js/home01.js @@ -52,8 +52,61 @@ var currentMonthEntries = []; // Trigger // +// +// ANONYME NUTZUNGSSTATISTIK (Admin-Dashboard) +// + +// Liest die zufällige Geräte-ID aus dem localStorage oder legt einmalig eine neue an. +// Kein Bezug zu einer Person - rein lokal generiert, dient nur der Dashboard-Zählung. +function getOrCreateDeviceId() { + try { + let id = localStorage.getItem('piomintDeviceId') + if (!id) { + id = (window.crypto && crypto.randomUUID) ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(16).slice(2)}` + localStorage.setItem('piomintDeviceId', id) + } + return id + } catch (error) { + return null + } +} + +// Meldet höchstens einmal pro Gerät und Tag ein anonymes "App genutzt"-Signal ans Backend. +// Darf die App unter keinen Umständen blockieren oder zum Absturz bringen - daher überall try/catch. +function pingUsageIfNeeded() { + try { + const today = new Date().toISOString().slice(0, 10) + if (localStorage.getItem('piomintLastPing') === today) { + return + } + const deviceId = getOrCreateDeviceId() + if (!deviceId) { + return + } + const standalone = (window.matchMedia && window.matchMedia('(display-mode: standalone)').matches) || window.navigator.standalone === true + + fetch('/app/track/', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + device_id: deviceId, + app_version: document.body.dataset.appVersion || '', + standalone: standalone, + }), + }).then(() => { + localStorage.setItem('piomintLastPing', today) + }).catch(() => { + // Kein Netz/Fehler beim Tracking - einfach beim nächsten Öffnen erneut versuchen + }) + } catch (error) { + console.error('Nutzungssignal konnte nicht gesendet werden:', error) + } +} + // Wenn das Dokument komplett geladen ist document.addEventListener('DOMContentLoaded', (event) => { + pingUsageIfNeeded() + initDB().then(async () => { console.log('IndexedDB erfolgreich initialisiert.'); // Hier kannst du die App weiter initialisieren, z.B. Daten laden oder die UI aufbauen diff --git a/app/templates/app/base.html b/app/templates/app/base.html index e765bf7..7040336 100644 --- a/app/templates/app/base.html +++ b/app/templates/app/base.html @@ -15,7 +15,7 @@
+ {{ standalone_count }} von {{ unique_devices_total }} Geräten insgesamt haben + PioMint (mindestens einmal als) installierte PWA geöffnet statt im Browser-Tab. +
+| Zeit | +Gerät | +Version | +Plattform | +Browser | +Standalone | +
|---|---|---|---|---|---|
| {{ event.created_at|date:"d.m.Y H:i" }} | +{{ event.device_id|slice:":8" }}… |
+ {{ event.app_version|default:"–" }} | +{{ event.platform }} | +{{ event.browser }} | +{{ event.is_standalone|yesno:"Ja,Nein" }} | +
| Noch keine Daten | |||||
+ Komplett anonym: device_id ist eine zufällige, nur lokal im Browser
+ gespeicherte ID ohne Bezug zu einer Person. Ein Signal pro Gerät und Tag.
+