Files
location-mqtt-tracker-app/README.md
2025-11-24 16:30:37 +00:00

23 KiB

Location Tracker - Next.js Anwendung

Eine moderne Location-Tracking Anwendung basierend auf Next.js 14 mit MQTT/OwnTracks Integration, SQLite-Datenbank, Admin-Panel und Authentifizierung.

Location Tracker Screenshot

📋 Inhaltsverzeichnis


Features

Öffentliche Features

  • 🗺️ Interaktive Karte - Echtzeit-Standortverfolgung mit Leaflet.js
  • 🎨 Mehrere Kartenansichten - Standard, Satellit, Dark Mode
  • 🔍 Device-Filterung - Filtern nach Gerät und Zeitraum
  • ⏱️ Flexible Zeitfilter:
    • Quick Filters: 1h, 3h, 6h, 12h, 24h, All
    • Custom Range: Benutzerdefinierter Zeitraum mit DateTime-Picker (z.B. 16.11.2025 16:00 - 17.11.2025 06:00)
    • Kompakte UI - Custom Range nur sichtbar wenn aktiviert
  • 🔄 Auto-Refresh - Automatische Aktualisierung alle 5 Sekunden mit Pause/Resume Button
  • 🎯 Auto-Center - Karte zentriert automatisch auf neueste Position
  • ⏸️ Pause/Resume - Toggle-Button zum Stoppen/Starten des Auto-Refresh
  • 📱 Responsive Design - Optimiert für Desktop und Mobile
  • 📊 Polylines - Bewegungspfade mit farbcodierter Darstellung
  • 🎨 Marker-Sortierung - Neueste Position immer im Vordergrund (z-index optimiert)
  • 📍 Zoom-basierte Icon-Skalierung - Marker passen sich automatisch an Zoom-Level an

Admin-Panel (Login erforderlich)

  • 🔐 Authentifizierung - NextAuth.js v5 mit bcrypt-Hashing
  • 📊 Dashboard - Übersicht über Geräte, Statistiken und Datenbankstatus
  • ⏱️ System Status - Live-Uptime, Memory Usage, Runtime Info
  • 📱 Device Management - Geräte hinzufügen, bearbeiten, löschen
  • 💾 Datenbank-Wartung:
    • 🔄 Manueller Sync von n8n
    • 🧹 Cleanup alter Daten (7, 15, 30, 90 Tage)
    • Datenbank-Optimierung (VACUUM)
    • 📈 Detaillierte Statistiken
  • 🟢 Online/Offline Status - Echtzeit-Status (< 10 Min = Online)
  • 🔋 Telemetrie-Daten - Batterie, Geschwindigkeit, letzte Position (speed=0 wird korrekt behandelt)

🛠 Tech Stack

  • Framework: Next.js 14 (App Router)
  • Sprache: TypeScript 5.9
  • Styling: Tailwind CSS v4
  • Karten: Leaflet 1.9.4 + React-Leaflet 5.0
  • Authentifizierung: NextAuth.js v5 (beta)
  • Datenbank: SQLite (better-sqlite3)
  • Passwort-Hashing: bcryptjs
  • Datenquelle: n8n Webhook API + lokale SQLite-Cache

Dual-Database Architektur

  • database.sqlite - User, Geräte (kritische Daten)
  • locations.sqlite - Location-Tracking (hohe Schreibrate, isoliert)

📦 Installation

Voraussetzungen

  • Node.js 18+
  • npm oder yarn

Schritte

  1. Repository klonen
git clone https://github.com/yourusername/location-tracker-app.git
cd location-tracker-app
  1. Dependencies installieren
npm install
  1. Datenbank initialisieren
npm run db:init

Dies erstellt:

  • data/database.sqlite (User + Devices)
  • data/locations.sqlite (Location-Tracking)
  • Standard Admin-User: admin / admin123
  • Standard Devices (ID 10, 11)
  1. Development Server starten
npm run dev
  1. Im Browser öffnen

🗄️ Datenbank-Setup

Initialisierung

Beide Datenbanken erstellen:

npm run db:init

Nur database.sqlite (User/Devices):

npm run db:init:app

Nur locations.sqlite (Tracking):

npm run db:init:locations

Datenbank zurücksetzen

Admin-User neu anlegen:

node scripts/reset-admin.js

Alte Locations löschen:

npm run db:cleanup       # Älter als 7 Tage
npm run db:cleanup:7d    # Älter als 7 Tage
npm run db:cleanup:30d   # Älter als 30 Tage

Duplikate entfernen (falls vorhanden):

node scripts/remove-duplicates.js

Schema

database.sqlite (User & Devices)

User Tabelle:

id              TEXT PRIMARY KEY
username        TEXT UNIQUE NOT NULL
email           TEXT
passwordHash    TEXT NOT NULL
role            TEXT NOT NULL DEFAULT 'VIEWER'  -- ADMIN oder VIEWER
createdAt       TEXT DEFAULT (datetime('now'))
updatedAt       TEXT DEFAULT (datetime('now'))
lastLoginAt     TEXT

Device Tabelle:

id              TEXT PRIMARY KEY
name            TEXT NOT NULL
color           TEXT NOT NULL
ownerId         TEXT                            -- FK zu User.id
isActive        INTEGER DEFAULT 1               -- 0 oder 1
description     TEXT
icon            TEXT
createdAt       TEXT DEFAULT (datetime('now'))
updatedAt       TEXT DEFAULT (datetime('now'))

Indexes:

  • idx_user_username ON User(username)
  • idx_device_owner ON Device(ownerId)
  • idx_device_active ON Device(isActive)

locations.sqlite (Tracking Data)

Location Tabelle:

id              INTEGER PRIMARY KEY AUTOINCREMENT
latitude        REAL NOT NULL                   -- -90 bis +90
longitude       REAL NOT NULL                   -- -180 bis +180
timestamp       TEXT NOT NULL                   -- ISO 8601 format
user_id         INTEGER DEFAULT 0
first_name      TEXT
last_name       TEXT
username        TEXT                            -- Device Tracker ID
marker_label    TEXT
display_time    TEXT                            -- Formatierte Zeit für UI
chat_id         INTEGER DEFAULT 0
battery         INTEGER                         -- Batteriestand in %
speed           REAL                            -- Geschwindigkeit in km/h
created_at      TEXT DEFAULT (datetime('now'))

Indexes:

  • idx_location_timestamp ON Location(timestamp DESC)
  • idx_location_username ON Location(username)
  • idx_location_user_id ON Location(user_id)
  • idx_location_composite ON Location(user_id, username, timestamp DESC)
  • idx_location_unique UNIQUE ON Location(timestamp, username, latitude, longitude)

Constraints:

  • Latitude: -90 bis +90
  • Longitude: -180 bis +180
  • UNIQUE: Kombination aus timestamp, username, latitude, longitude verhindert Duplikate

WAL Mode: Beide Datenbanken nutzen Write-Ahead Logging für bessere Concurrency


🚀 Verwendung

Login

Standard-Zugangsdaten:

Benutzername: admin
Passwort: admin123

⚠️ Wichtig: Für Production neuen User anlegen und Passwort ändern!

Geräte hinzufügen

  1. Admin-Panel öffnen: /admin/devices
  2. "Add Device" klicken
  3. Device ID (muss mit OwnTracks tid übereinstimmen)
  4. Name und Farbe festlegen
  5. Speichern

Wichtig: Die Device ID muss mit der OwnTracks Tracker-ID übereinstimmen!

OwnTracks konfigurieren

In der OwnTracks App:

  • Tracker ID (tid): z.B. 12
  • Topic: owntracks/user/12
  • MQTT Broker wie gewohnt

Die n8n-Workflow holt die Daten, und die App synct automatisch alle 5 Sekunden.

Zeitfilter verwenden

Die App bietet zwei Modi für die Zeitfilterung:

Quick Filters (Schnellauswahl)

Vordefinierte Zeiträume für schnellen Zugriff:

  • 1 Hour - Locations der letzten Stunde
  • 3 Hours - Locations der letzten 3 Stunden
  • 6 Hours - Locations der letzten 6 Stunden
  • 12 Hours - Locations der letzten 12 Stunden
  • 24 Hours - Locations der letzten 24 Stunden
  • All - Alle verfügbaren Locations

Verwendung:

  1. Im Header unter "Time:" gewünschten Zeitraum auswählen
  2. Die Karte aktualisiert sich automatisch

Custom Range (Benutzerdefiniert)

Für spezifische Zeiträume, z.B. "Route von gestern Abend 18:00 bis heute Morgen 08:00":

Verwendung:

  1. Auf den "📅 Custom" Button klicken
  2. Custom Range Felder erscheinen:
    • From: Start-Datum und -Zeit wählen (z.B. 16.11.2025 16:00)
    • To: End-Datum und -Zeit wählen (z.B. 17.11.2025 06:00)
  3. Die Route wird automatisch für den gewählten Zeitraum angezeigt
  4. Zum Zurückschalten: "📅 Quick" Button klicken

Hinweis: Custom Range Controls sind nur sichtbar wenn aktiviert - spart Platz im Header!


🏗 Architektur

Datenfluss

flowchart TD
    A[📱 OwnTracks App] -->|MQTT Publish| B[🔌 MQTT Broker]
    B -->|Subscribe| C[⚙️ n8n MQTT Trigger]
    C -->|Store| D[💾 NocoDB]
    D -->|Webhook API| E[🌐 n8n Webhook<br/>/webhook/location]

    F[🖥️ Browser Client] -->|GET /api/locations<br/>alle 5 Sek| G[📡 Next.js API Route]

    G -->|1. Fetch Fresh Data| E
    E -->|JSON Response| G

    G -->|2. Sync New Locations| H[(🗄️ SQLite Cache<br/>locations.sqlite)]

    H -->|3. Query Filtered Data| G
    G -->|JSON Response| F

    F -->|Render| I[🗺️ React Leaflet Map]

    J[👤 Admin User] -->|Login| K[🔐 NextAuth.js]
    K -->|Authenticated| L[📊 Admin Panel]
    L -->|CRUD Operations| M[(💼 SQLite DB<br/>database.sqlite)]

    style A fill:#4CAF50
    style B fill:#FF9800
    style C fill:#2196F3
    style D fill:#9C27B0
    style E fill:#F44336
    style G fill:#00BCD4
    style H fill:#FFC107
    style I fill:#8BC34A
    style K fill:#E91E63
    style M fill:#FFC107

Komponenten-Übersicht

graph LR
    subgraph "External Services"
        A[OwnTracks App]
        B[MQTT Broker]
        C[n8n Automation]
        D[NocoDB]
    end

    subgraph "Next.js Application"
        E[Frontend<br/>React/Leaflet]
        F[API Routes]
        G[Auth Layer<br/>NextAuth.js]
    end

    subgraph "Data Layer"
        H[locations.sqlite<br/>Tracking Data]
        I[database.sqlite<br/>Users & Devices]
    end

    A -->|MQTT| B
    B -->|Subscribe| C
    C -->|Store| D
    C -->|Webhook| F

    E -->|HTTP| F
    F -->|Read/Write| H
    F -->|Read/Write| I

    E -->|Auth| G
    G -->|Validate| I

    style A fill:#4CAF50,color:#fff
    style B fill:#FF9800,color:#fff
    style C fill:#2196F3,color:#fff
    style D fill:#9C27B0,color:#fff
    style E fill:#00BCD4,color:#000
    style F fill:#00BCD4,color:#000
    style G fill:#E91E63,color:#fff
    style H fill:#FFC107,color:#000
    style I fill:#FFC107,color:#000

Datenbank-Architektur

erDiagram
    USER ||--o{ DEVICE : owns
    DEVICE ||--o{ LOCATION : tracks

    USER {
        string id PK
        string username UK
        string email
        string passwordHash
        string role
        datetime createdAt
        datetime lastLoginAt
    }

    DEVICE {
        string id PK
        string name
        string color
        string ownerId FK
        boolean isActive
        string description
        datetime createdAt
    }

    LOCATION {
        int id PK
        float latitude
        float longitude
        datetime timestamp
        string username FK
        int user_id
        string display_time
        int battery
        float speed
        int chat_id
    }

Auto-Sync Mechanismus

Die App verwendet einen Hybrid-Ansatz:

  1. Frontend polling (alle 5 Sek.) → /api/locations
  2. API prüft ob neue Daten in n8n verfügbar
  3. Nur neue Locations werden in SQLite gespeichert
  4. Duplikate werden durch UNIQUE Index verhindert
  5. Antwort kommt aus lokalem SQLite Cache

Vorteile:

  • Schnelle Antwortzeiten (SQLite statt n8n)
  • Längere Zeiträume abrufbar (24h+)
  • Funktioniert auch wenn n8n nicht erreichbar ist (Fallback auf n8n-Daten)
  • Duplikate werden automatisch verhindert (UNIQUE Index)

Datenvalidierung & Normalisierung

Die App behandelt spezielle Fälle bei speed/battery korrekt:

speed = 0 Behandlung:

  • n8n sendet speed: 0 (gültig - Gerät steht still)
  • Wird mit typeof === 'number' Check explizit als 0 gespeichert
  • Wird NICHT zu null konvertiert (wichtig für Telemetrie)
  • Popup zeigt "Speed: 0.0 km/h" an

n8n Fallback:

  • Wenn DB leer ist, gibt API direkt n8n-Daten zurück
  • Alle Filter (username, timeRangeHours) funktionieren auch mit Fallback
  • Ermöglicht sofortigen Betrieb ohne DB-Initialisierung

Debug-Logging:

  • Server-Logs zeigen n8n Sync-Aktivität
  • Browser Console zeigt Daten-Flow (MapView, Popup)
  • Hilfreich für Troubleshooting

📡 API-Endpunkte

Öffentlich

GET /api/locations

  • Location-Daten abrufen (mit Auto-Sync)
  • Query-Parameter:
    • username - Device-Filter (z.B. "10", "11")
    • Zeitfilter (wähle eine Methode):
      • timeRangeHours - Quick Filter (1, 3, 6, 12, 24)
      • startTime & endTime - Custom Range (ISO 8601 Format)
    • limit - Max. Anzahl (Standard: 1000)
    • sync=false - Nur Cache ohne n8n Sync

Beispiele:

# Quick Filter: Letzte 3 Stunden
GET /api/locations?timeRangeHours=3

# Custom Range: Spezifischer Zeitraum
GET /api/locations?startTime=2025-11-16T16:00:00.000Z&endTime=2025-11-17T06:00:00.000Z

# Kombiniert: Device + Custom Range
GET /api/locations?username=10&startTime=2025-11-16T16:00:00.000Z&endTime=2025-11-17T06:00:00.000Z

GET /api/devices/public

  • Öffentliche Device-Liste (nur ID, Name, Color)

Geschützt (Login erforderlich)

GET /api/devices

  • Alle Geräte mit Latest Location und Telemetrie

POST /api/devices

  • Neues Gerät erstellen
  • Body: { id, name, color, description? }

PATCH /api/devices/:id

  • Gerät aktualisieren
  • Body: { name?, color?, description? }

DELETE /api/devices/:id

  • Gerät löschen (soft delete)

POST /api/locations/sync

  • Manueller Sync von n8n
  • Gibt Anzahl neu eingefügter Locations zurück

POST /api/locations/cleanup

  • Alte Locations löschen
  • Body: { retentionHours }

POST /api/locations/optimize

  • VACUUM + ANALYZE ausführen
  • Gibt freigegebenen Speicher zurück

GET /api/locations/stats

  • Detaillierte DB-Statistiken
  • Größe, Zeitraum, Locations pro Device

GET /api/system/status

  • System-Status abrufen
  • Returns: Uptime, Memory Usage, Node.js Version, Platform
  • Auto-Refresh: Alle 10 Sekunden im Admin-Panel

📱 Device Management

Device-Karte zeigt:

  • 🟢/ Online/Offline Status
    • Online = letzte Location < 10 Minuten
    • Offline = letzte Location > 10 Minuten
  • 🕒 Last Seen - Zeitstempel letzter Location
  • 🔋 Batterie - Prozent (Rot bei < 20%)
  • 🚗 Geschwindigkeit - km/h (umgerechnet von m/s)
  • 📍 Koordinaten - Lat/Lon mit 5 Dezimalen

Auto-Refresh

  • Devices-Seite aktualisiert sich alle 10 Sekunden
  • Online/Offline Status wird automatisch aktualisiert

🧹 Wartung

Datenbank aufräumen

Via Admin-Panel:

  • /admin → Database Maintenance → Cleanup Buttons

Via CLI:

npm run db:cleanup        # 7 Tage
npm run db:cleanup:30d    # 30 Tage

Datenbank optimieren

Via Admin-Panel:

  • /admin → Database Maintenance → Optimize Button

Via CLI:

# Manuell
node scripts/optimize-db.js

Was macht Optimize:

  • VACUUM - Speicherplatz freigeben
  • ANALYZE - Query-Statistiken aktualisieren

Sync von n8n

Via Admin-Panel:

  • /admin → Database Maintenance → Sync Now

Automatisch:

  • Passiert alle 5 Sekunden beim Abruf der Karte

Logs prüfen

# Development Server Logs
npm run dev

# Production Logs (PM2)
pm2 logs location-tracker-app

Zeitfilter debuggen

Bei Problemen mit der Zeitfilterung (z.B. alte Locations werden nicht ausgefiltert):

node scripts/test-time-filter.js

Das Script zeigt:

  • Aktuelle Zeit (UTC und lokal)
  • Letzte Locations in der Datenbank
  • Welche Locations mit 1-Stunden-Filter angezeigt werden sollten
  • Vergleich zwischen alter (SQLite datetime) und neuer (JavaScript) Methode
  • Anzahl der gefilterten Locations

Verwendung:

  1. Script ausführen um zu sehen, welche Locations in der DB sind
  2. Überprüfen ob die Zeitfilterung korrekt funktioniert
  3. Bei Problemen: App neu starten nach Code-Updates

🚀 Deployment

Environment Variables

Erstelle .env.local:

# NextAuth
AUTH_SECRET=<generiere-mit-openssl-rand-base64-32>
NEXTAUTH_URL=https://your-domain.com

# Optional: n8n API URL (Standard in Code definiert)
N8N_API_URL=https://n8n.example.com/webhook/location

Secret generieren:

openssl rand -base64 32

Production Build

# Build
npm run build

# Start
npm run start

Mit PM2 (empfohlen)

# PM2 installieren
npm install -g pm2

# App starten
pm2 start npm --name "location-tracker-app" -- start

# Auto-Start bei Server-Neustart
pm2 startup
pm2 save

Nginx Reverse Proxy

server {
    listen 80;
    server_name your-domain.com;

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

🔒 Sicherheit

Production Checklist

  • AUTH_SECRET mit starkem Wert setzen
  • NEXTAUTH_URL auf Production-Domain setzen
  • Admin-Passwort ändern (nicht admin123)
  • Ggf. weitere User anlegen mit VIEWER Rolle
  • HTTPS aktivieren (Let's Encrypt)
  • Firewall-Regeln prüfen
  • Regelmäßige Backups einrichten

User-Rollen

  • ADMIN - Voller Zugriff auf alle Admin-Funktionen
  • VIEWER - Nur lesender Zugriff (geplant, noch nicht implementiert)

📂 Projektstruktur

location-tracker-app/
├── app/
│   ├── api/
│   │   ├── auth/[...nextauth]/      # NextAuth API
│   │   ├── devices/                 # Device CRUD
│   │   ├── locations/               # Location API + Sync/Cleanup/Stats
│   │   └── system/status/           # System Status (Uptime, Memory)
│   ├── admin/
│   │   ├── devices/                 # Device Management
│   │   └── page.tsx                 # Dashboard
│   ├── login/                       # Login-Seite
│   ├── page.tsx                     # Öffentliche Karte
│   └── layout.tsx                   # Root Layout
├── components/
│   └── map/
│       └── MapView.tsx              # Leaflet Map Component
├── lib/
│   ├── auth.ts                      # NextAuth Config
│   └── db.ts                        # SQLite Database Layer
├── scripts/
│   ├── init-database.js             # Database.sqlite Setup
│   ├── init-locations-db.js         # Locations.sqlite Setup
│   ├── reset-admin.js               # Admin User Reset
│   ├── remove-duplicates.js         # Duplikate bereinigen
│   └── cleanup-old-locations.js     # Alte Daten löschen
├── data/
│   ├── database.sqlite              # User + Devices
│   └── locations.sqlite             # Location Tracking
├── types/
│   └── location.ts                  # TypeScript Interfaces
└── middleware.ts                    # Route Protection

📝 Changelog

Version 1.1.0 - November 2025

🆕 Neue Features

  • Custom Date Range Filter
    • Benutzerdefinierte Zeiträume mit DateTime-Picker (z.B. 16.11.2025 16:00 - 17.11.2025 06:00)
    • Toggle-Button zwischen Quick Filters und Custom Range
    • Kompakte UI - Custom Range nur sichtbar wenn aktiviert
    • Backend-Support mit startTime und endTime API-Parametern

🐛 Bug Fixes

  • Zeitfilter-Bug behoben
    • Alte Locations (z.B. 6+ Stunden alt) werden jetzt korrekt ausgefiltert
    • JavaScript-basierte Zeitberechnung statt SQLite datetime('now')
    • Verhindert Zeitversatz-Probleme

🛠️ Verbesserungen

  • Zoom-basierte Icon-Skalierung für bessere Sichtbarkeit
  • Optimierte Zeitfilter-Logik in Datenbank-Queries
  • Debug-Script test-time-filter.js für Troubleshooting

📚 Dokumentation

  • README aktualisiert mit Custom Range Anleitung
  • API-Endpunkte Dokumentation erweitert
  • Wartungs-Abschnitt mit Debug-Script Information

🐛 Troubleshooting

"Invalid username or password"

Lösung:

node scripts/reset-admin.js

Datenbank-Dateien fehlen

Lösung:

npm run db:init

Duplikate in locations.sqlite

Lösung:

# Erst Duplikate entfernen
node scripts/remove-duplicates.js

# Dann UNIQUE Index hinzufügen
node scripts/init-locations-db.js

Map zeigt keine Daten

  1. n8n Webhook erreichbar? curl https://n8n.example.com/webhook/location
  2. Locations in Datenbank? /admin → Database Statistics prüfen
  3. Auto-Sync aktiv? Browser Console öffnen

"ENOENT: no such file or directory, open 'data/database.sqlite'"

Lösung:

mkdir -p data
npm run db:init

📝 NPM Scripts

# Development
npm run dev              # Dev Server starten

# Production
npm run build            # Production Build
npm run start            # Production Server

# Database
npm run db:init          # Beide DBs initialisieren
npm run db:init:app      # Nur database.sqlite
npm run db:init:locations # Nur locations.sqlite
npm run db:cleanup       # Cleanup 7 Tage
npm run db:cleanup:7d    # Cleanup 7 Tage
npm run db:cleanup:30d   # Cleanup 30 Tage

# Linting
npm run lint             # ESLint ausführen

📄 Lizenz

MIT License - Open Source


📜 Open Source Lizenzen

Diese Anwendung verwendet folgende Open-Source-Software:

MIT License

Apache License 2.0

  • TypeScript - Copyright (c) Microsoft Corporation

ISC License

BSD-2-Clause License

  • Leaflet - Copyright (c) Vladimir Agafonkin

Hippocratic License 2.1

Vollständige Lizenztexte: Alle vollständigen Lizenztexte der verwendeten Bibliotheken finden Sie in den jeweiligen GitHub-Repositories oder in der node_modules Directory nach Installation.


🙏 Credits

  • Next.js 14 - React Framework
  • Leaflet.js - Karten-Bibliothek
  • NextAuth.js - Authentifizierung
  • better-sqlite3 - SQLite für Node.js
  • Tailwind CSS - Utility-First CSS
  • n8n - Workflow Automation (Backend)
  • OwnTracks - Location Tracking Apps

📞 Support

Bei Fragen oder Problemen:

  1. Logs prüfen (npm run dev Output)
  2. Browser Console öffnen (F12)
  3. Datenbank-Status in /admin prüfen
  4. Issues im Repository erstellen