Files
yaris-obd/server/templates/base.html
T
Mathieu MoranandCopilot c4dfcbaf25 Phase 24: remote trip + dashcam portal
- Add Flask-Login browser session auth (separate from device X-Api-Key auth)
  - modules/user_auth.py: User model, require_login decorator, bcrypt helpers
  - /login with CSRF token, /logout
  - scripts/create_user.py to provision admin user

- Add server-side trip computation from tracking_points
  - db.compute_trips_for_device(): gap > 5 min = new trip, haversine distance,
    upserts into trips table, auto-triggered in bg thread after each upload batch
  - db.list_trips(), db.get_trip_route(): same JSON shape as Pi API
  - db.get_overview_stats(): aggregated totals for overview page
  - db.list_dashcam_files(), db.get_dashcam_file(): browse/stream helpers

- Add web portal to server/app.py
  - Overview page (/): stat cards + recent trips + recent clips
  - Trip history (/history): reuses Pi history.js + data-list.js unchanged
    via /api/trips + /api/trip/<id> compatibility aliases
  - Dashcam archive (/dashcam): file list + inline video player
  - /api/v1/portal/dashcam/stream/<id>: Range-request streaming, path traversal guard
  - /api/geocode: same Nominatim→reverse_geocoder offline fallback as Pi

- Add server templates (server/templates/)
  - base.html: shared shell with 'Remote server' badge in sidebar + mobile topbar
  - login.html, index.html, history.html, dashcam.html, components.html

- DB schema: users + trips tables added to init.sql (fresh deploys) +
  migrate_phase24.sql (existing deployments)

- Infrastructure: Dockerfile uses repo-root build context to copy shared
  static/ assets; docker-compose updated; SECRET_KEY + DEVICE_NAME in .env;
  requirements: Flask-Login, bcrypt, reverse_geocoder

Co-authored-by: Copilot <[email protected]>
2026-08-10 03:58:45 -04:00

111 lines
5.6 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html data-bs-theme="light" lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>{% block title %}Yaris OBD – Remote{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='bootstrap/css/bootstrap.min.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='yaris.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='fonts/fontawesome-all.min.css') }}">
{% block styles %}{% endblock %}
</head>
<body id="page-top">
{# ── Desktop sidebar ─────────────────────────────────────────────────── #}
<nav id="app-sidebar" class="app-sidebar d-none d-md-flex flex-column">
<a class="sidebar-brand" href="{{ url_for('portal_index') }}" aria-label="Home">
<i class="fas fa-car sidebar-brand-icon"></i>
<span class="sidebar-brand-text">Yaris OBD</span>
</a>
<div class="px-3 pb-1">
<span class="badge bg-warning text-dark w-100 text-center" style="font-size:.65rem;letter-spacing:.04em;">
<i class="fas fa-cloud fa-fw"></i> Remote server
</span>
</div>
<hr class="sidebar-divider">
<ul class="nav flex-column flex-grow-1">
<li class="nav-item">
<a class="nav-link{% if request.endpoint == 'portal_index' %} active{% endif %}" href="{{ url_for('portal_index') }}">
<i class="fas fa-chart-line fa-fw"></i><span>Overview</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link{% if request.endpoint == 'portal_history' %} active{% endif %}" href="{{ url_for('portal_history') }}">
<i class="fas fa-route fa-fw"></i><span>Trip history</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link{% if request.endpoint == 'portal_dashcam' %} active{% endif %}" href="{{ url_for('portal_dashcam') }}">
<i class="fas fa-video fa-fw"></i><span>Dashcam archive</span>
</a>
</li>
</ul>
<div class="px-3 pb-2 mt-auto text-muted" style="font-size:.75rem;">
<i class="fas fa-user fa-fw"></i> {{ current_user.username }}
&nbsp;·&nbsp;
<a href="{{ url_for('logout') }}" class="text-muted">Logout</a>
</div>
<button class="sidebar-collapse-btn" id="sidebarToggle" type="button" aria-label="Collapse sidebar">
<i class="fas fa-chevron-left"></i>
</button>
</nav>
{# ── Main content area ────────────────────────────────────────────────── #}
<div id="content-wrapper">
{# Top bar #}
<nav class="topbar navbar navbar-light bg-white shadow-sm">
<div class="container-fluid px-3 gap-2">
<a class="navbar-brand d-flex d-md-none align-items-center gap-2 text-decoration-none" href="{{ url_for('portal_index') }}">
<i class="fas fa-car text-primary"></i>
<span class="fw-bold text-dark">Yaris OBD</span>
<span class="badge bg-warning text-dark ms-1" style="font-size:.6rem;">Remote</span>
</a>
<div class="ms-auto d-flex align-items-center gap-2">
<span class="text-muted small d-none d-sm-inline">{{ current_user.username }}</span>
<a href="{{ url_for('logout') }}" class="btn btn-outline-secondary btn-sm" title="Logout">
<i class="fas fa-sign-out-alt fa-fw"></i>
</a>
</div>
</div>
</nav>
<main class="container-fluid px-3 px-md-4 main-content">
{% block content %}{% endblock %}
</main>
<footer class="bg-white border-top text-center py-3 small text-muted d-none d-md-block">
&copy; vroom vroom yaris de course 2023 &mdash; remote server
</footer>
</div>
{# ── Mobile bottom navigation ──────────────────────────────────────────── #}
<nav class="mobile-bottom-nav d-flex d-md-none" aria-label="Main navigation">
<a class="mobile-nav-item{% if request.endpoint == 'portal_index' %} active{% endif %}" href="{{ url_for('portal_index') }}">
<i class="fas fa-chart-line"></i>
<span>Overview</span>
</a>
<a class="mobile-nav-item{% if request.endpoint == 'portal_history' %} active{% endif %}" href="{{ url_for('portal_history') }}">
<i class="fas fa-route"></i>
<span>History</span>
</a>
<a class="mobile-nav-item{% if request.endpoint == 'portal_dashcam' %} active{% endif %}" href="{{ url_for('portal_dashcam') }}">
<i class="fas fa-video"></i>
<span>Dashcam</span>
</a>
<a class="mobile-nav-item" href="{{ url_for('logout') }}">
<i class="fas fa-sign-out-alt"></i>
<span>Logout</span>
</a>
</nav>
<script src="{{ url_for('static', filename='bootstrap/js/bootstrap.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/bs-init.js') }}"></script>
<script src="{{ url_for('static', filename='js/theme.js') }}"></script>
{% block scripts %}{% endblock %}
</body>
</html>