Phase 1 (Foundation): project skeleton, TOML config + Pydantic validation, MQTT bus wrapper, SQLite schema (9 tables), Click CLI, process supervisor. Phase 2 (Camera): RTSP capture via OpenCV, MOG2 motion detection with configurable sensitivity/zones, adaptive FPS recording (2fps idle/30fps motion) via FFmpeg subprocess, HLS live streaming, pre-motion ring buffer. Phase 3 (Web UI): Flask + Bootstrap 5 dark theme, 6 blueprints, Jinja2 templates (dashboard, kiosk 2x2 grid, events, sensors, recordings, settings), PWA with service worker + Web Push, full admin settings UI with config persistence. Remote Access: WireGuard tunnel configs, nginx reverse proxy with HLS caching + rate limiting, bandwidth-optimized remote HLS stream (426x240 @ 500kbps), DO droplet setup script, certbot TLS. 29 tests passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
148 lines
5.6 KiB
HTML
148 lines
5.6 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Vigilar — Dashboard{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="row g-3 mb-3">
|
|
<div class="col-12">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0"><i class="bi bi-grid-fill me-2"></i>Live Cameras</h5>
|
|
<a href="/kiosk/" class="btn btn-sm btn-outline-light" target="_blank">
|
|
<i class="bi bi-fullscreen me-1"></i>Kiosk Mode
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 2x2 Camera Grid -->
|
|
<div class="row g-2" id="camera-grid">
|
|
{% for camera in cameras %}
|
|
<div class="col-md-6">
|
|
<div class="card bg-dark border-secondary camera-card">
|
|
<div class="card-body p-0 position-relative">
|
|
<div class="camera-feed ratio ratio-16x9">
|
|
<video id="feed-{{ camera.id }}" class="w-100" autoplay muted playsinline>
|
|
</video>
|
|
<div class="camera-offline-overlay d-flex align-items-center justify-content-center">
|
|
<div class="text-center text-muted">
|
|
<i class="bi bi-camera-video-off fs-1"></i>
|
|
<p class="mt-2 mb-0">{{ camera.display_name }}</p>
|
|
<small>Connecting...</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Overlay -->
|
|
<div class="camera-overlay">
|
|
<span class="camera-name">{{ camera.display_name }}</span>
|
|
<span class="camera-time" id="time-{{ camera.id }}">--:--</span>
|
|
</div>
|
|
<div class="camera-motion-dot d-none" id="motion-{{ camera.id }}">
|
|
<span class="motion-indicator"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
{% if not cameras %}
|
|
<div class="col-12">
|
|
<div class="alert alert-secondary text-center">
|
|
<i class="bi bi-camera-video-off me-2"></i>
|
|
No cameras configured. Edit <code>config/vigilar.toml</code> to add cameras.
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Quick Status Cards -->
|
|
<div class="row g-2 mt-3">
|
|
<div class="col-md-3">
|
|
<div class="card bg-dark border-secondary">
|
|
<div class="card-body py-2">
|
|
<div class="d-flex align-items-center">
|
|
<i class="bi bi-shield-check fs-4 me-3 text-success"></i>
|
|
<div>
|
|
<small class="text-muted">System</small>
|
|
<div id="system-status" class="fw-bold">Disarmed</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card bg-dark border-secondary">
|
|
<div class="card-body py-2">
|
|
<div class="d-flex align-items-center">
|
|
<i class="bi bi-camera-video fs-4 me-3 text-info"></i>
|
|
<div>
|
|
<small class="text-muted">Cameras</small>
|
|
<div id="cameras-online" class="fw-bold">{{ cameras|length }} configured</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card bg-dark border-secondary">
|
|
<div class="card-body py-2">
|
|
<div class="d-flex align-items-center">
|
|
<i class="bi bi-broadcast fs-4 me-3 text-warning"></i>
|
|
<div>
|
|
<small class="text-muted">Sensors</small>
|
|
<div id="sensors-online" class="fw-bold">--</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card bg-dark border-secondary">
|
|
<div class="card-body py-2">
|
|
<div class="d-flex align-items-center">
|
|
<i class="bi bi-battery-full fs-4 me-3 text-success"></i>
|
|
<div>
|
|
<small class="text-muted">UPS</small>
|
|
<div id="ups-status" class="fw-bold">--</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Events -->
|
|
<div class="row mt-3">
|
|
<div class="col-12">
|
|
<div class="card bg-dark border-secondary">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<span><i class="bi bi-list-ul me-2"></i>Recent Events</span>
|
|
<a href="/events/" class="btn btn-sm btn-outline-light">View All</a>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div class="table-responsive">
|
|
<table class="table table-dark table-hover mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>Time</th>
|
|
<th>Type</th>
|
|
<th>Source</th>
|
|
<th>Severity</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="recent-events">
|
|
<tr>
|
|
<td colspan="4" class="text-center text-muted py-3">
|
|
No events yet
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script src="{{ url_for('static', filename='js/grid.js') }}"></script>
|
|
{% endblock %}
|