1c5b728e8b
FastAPI web server with REST + WebSocket, 5 Jinja2 HTML pages, mobile-friendly dark theme CSS, page-specific JS controllers, and Avahi mDNS service advertisement for pedal.local. Files: src/web/server.py — FastAPI app with 17 REST endpoints + /ws WebSocket src/web/__init__.py — Package init with WebServer, WebServerDeps exports src/web/templates/*.html — Dashboard, Presets, Models, IRs, Settings (base) src/web/static/style.css — Dark mobile-first CSS (80-82% max-width) src/web/static/websocket.js — Auto-reconnecting WebSocket manager src/web/static/app.js — Dashboard live updates + REST helpers src/web/static/presets.js — Bank/preset CRUD modal UI src/web/static/models.js — NAM model list/load/upload src/web/static/irs.js — IR file list/load/upload etc/avahi/pi-multifx-pedal.service — Avahi service advertisement scripts/setup-mdns.sh — One-shot mDNS setup (avahi, hostname, restart) main.py — Wire WebServer into boot/shutdown requirements.txt — Add fastapi, uvicorn, jinja2, python-multipart tests/test_web.py — 36 tests (pages, REST, WebSocket, errors) All 36 tests pass.
89 lines
3.3 KiB
HTML
89 lines
3.3 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Dashboard — Pi Multi-FX Pedal{% endblock %}
|
|
{% block content %}
|
|
<div class="dashboard">
|
|
<!-- Current Preset Card -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h2>Current Preset</h2>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="preset-name-display" id="preset-name">{{ current_preset.name if current_preset else "—" }}</div>
|
|
<div class="preset-meta" id="preset-meta">
|
|
Bank {{ current_preset.bank if current_preset else 0 }} — Program {{ current_preset.program if current_preset else 0 }}
|
|
</div>
|
|
<div class="preset-controls">
|
|
<button class="btn btn-primary" onclick="activatePreset('prev', -1)">◀ Prev</button>
|
|
<button class="btn btn-primary" onclick="activatePreset('next', 1)">Next ▶</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Status Row -->
|
|
<div class="status-row">
|
|
<div class="card status-card">
|
|
<div class="status-label">Bypass</div>
|
|
<div class="status-value">
|
|
<button class="btn toggle-btn {% if bypass %}active{% endif %}" id="bypass-btn" onclick="toggleBypass()">
|
|
{% if bypass %}BYPASSED{% else %}ACTIVE{% endif %}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="card status-card">
|
|
<div class="status-label">Tuner</div>
|
|
<div class="status-value">
|
|
<button class="btn toggle-btn {% if tuner_enabled %}active{% endif %}" id="tuner-btn" onclick="toggleTuner()">
|
|
{% if tuner_enabled %}ON{% else %}OFF{% endif %}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="card status-card">
|
|
<div class="status-label">Volume</div>
|
|
<div class="status-value">
|
|
<input type="range" class="slider" id="volume-slider"
|
|
min="0" max="100" value="{{ (master_volume * 100)|int }}"
|
|
oninput="setVolume(this.value)">
|
|
<span class="volume-text" id="volume-text">{{ (master_volume * 100)|int }}%</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- FX Chain -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h2>FX Chain</h2>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="fx-chain" id="fx-chain">
|
|
<div class="fx-block-chain-placeholder" id="fx-chain-placeholder">
|
|
<p>Connect to pedal to see active FX chain.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- NAM Model Status -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h2>NAM Model</h2>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="model-status" id="model-status">
|
|
<span class="model-name" id="model-name">{{ nam_model if nam_model else "None loaded" }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- IR Status -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h2>IR Cabinet</h2>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="ir-status" id="ir-status">
|
|
<span class="ir-name" id="ir-name">{{ ir_name if ir_name else "None loaded" }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %} |