Files
root 3cbf36e0ad Prep v1.1.0 release: version string, gitignore secrets, docs
- Add VERSION = "1.1.0" to server.py; print at startup, expose via public
  GET /api/config, and show it in the admin header (so deployed == released
  is verifiable at a glance).
- Ignore config.json in .gitignore — it holds the admin password hash and
  session secret; fresh installs use config.json.example.
- README/CLAUDE.md: document the in-app pixel editor (now with shape tools,
  select/move, reference overlay, custom/recent palettes), the receiver health
  dashboard, 9 sprite types, and the asset cache-busting convention.
- Bump admin asset cache-bust to ?v=20260609d.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 23:41:00 -07:00

304 lines
15 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ADS-Bit Admin</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/admin/admin.css?v=20260609d">
</head>
<body>
<!-- Login Screen -->
<div id="login-screen">
<div class="login-box">
<h1 class="retro-title">ADS-Bit Admin</h1>
<p class="login-subtitle">Enter admin password</p>
<form id="login-form">
<input type="password" id="login-password" placeholder="Password" autocomplete="current-password" required>
<button type="submit" class="btn btn-primary">LOGIN</button>
</form>
<div id="login-error" class="error-msg hidden"></div>
<a href="/" class="back-link">Back to Viewer</a>
</div>
</div>
<!-- Admin Panel -->
<div id="admin-panel" class="hidden">
<header class="admin-header">
<h1 class="retro-title">ADS-Bit Admin <span id="admin-version" class="admin-version"></span></h1>
<div class="header-actions">
<a href="/" class="btn btn-small">VIEWER</a>
<button id="logout-btn" class="btn btn-small btn-danger">LOGOUT</button>
</div>
</header>
<nav class="tab-bar">
<button class="tab active" data-tab="dashboard">DASHBOARD</button>
<button class="tab" data-tab="receivers">RECEIVERS</button>
<button class="tab" data-tab="location">LOCATION</button>
<button class="tab" data-tab="display">DISPLAY</button>
<button class="tab" data-tab="themes">THEMES</button>
<button class="tab" data-tab="tuning">TUNING</button>
<button class="tab" data-tab="sprites">SPRITES</button>
<button class="tab" data-tab="security">SECURITY</button>
</nav>
<main class="tab-content">
<!-- Dashboard Tab -->
<section id="tab-dashboard" class="tab-pane active">
<h2>Live Status</h2>
<div class="status-grid">
<div class="status-card">
<span class="status-label">ACTIVE FLIGHTS</span>
<span id="stat-flights" class="status-value">--</span>
</div>
<div class="status-card">
<span class="status-label">CONNECTED VIEWERS</span>
<span id="stat-viewers" class="status-value">--</span>
</div>
<div class="status-card">
<span class="status-label">RECEIVERS</span>
<span id="stat-receivers" class="status-value">--</span>
</div>
<div class="status-card">
<span class="status-label">UPTIME</span>
<span id="stat-uptime" class="status-value">--</span>
</div>
</div>
<div id="receiver-list" class="info-box">
<h3>Connected Receivers</h3>
<div id="receiver-ips">Loading...</div>
</div>
</section>
<!-- Receivers Tab -->
<section id="tab-receivers" class="tab-pane hidden">
<h2>Receiver Configuration</h2>
<!-- Live status of the receivers the app is currently using -->
<div id="receiver-status-box" class="info-box">
<h3>Active Receivers <span id="receiver-status-summary" class="rx-summary"></span></h3>
<div id="receiver-status-list">Loading...</div>
<div class="button-row">
<button id="test-receivers-btn" class="btn">TEST CONNECTIONS</button>
<button id="restart-receivers-btn" class="btn">RESTART CONNECTIONS</button>
</div>
<p class="hint">🟢 receiving data &nbsp; 🟡 connected, no data yet &nbsp; 🔴 unreachable</p>
</div>
<!-- Step 1: choose how receivers are selected -->
<div class="rx-section-label">1. Select Receivers</div>
<div class="form-group">
<label>Discovery Mode</label>
<select id="receiver-mode">
<option value="AUTO">AUTO (use all receivers found on the network)</option>
<option value="MANUAL">MANUAL (use only the IPs I specify)</option>
</select>
</div>
<div id="manual-ips-group" class="form-group hidden">
<label>Receiver IPs (one per line)</label>
<textarea id="receiver-ips-input" rows="4" placeholder="192.168.1.100&#10;192.168.1.101"></textarea>
<span class="hint">The app connects to every IP listed here.</span>
</div>
<div class="form-group">
<label>Receiver Port</label>
<input type="number" id="receiver-port" value="30003" min="1" max="65535">
</div>
<!-- Step 2: save + apply -->
<div class="rx-section-label">2. Save &amp; Apply</div>
<div class="button-row">
<button id="save-receivers-btn" class="btn btn-primary">SAVE &amp; APPLY</button>
</div>
<p class="hint">Saves your selection, reconnects immediately, then re-tests the receivers below.</p>
<!-- Find receivers on the network -->
<div class="rx-section-label">Find Receivers on Network</div>
<div class="form-group">
<label>Interface / Subnet to Scan</label>
<select id="scan-interface"></select>
<span class="hint">Pick a network interface to scan, or scan all local subnets.</span>
</div>
<div class="form-group">
<label>Custom Subnet (optional override)</label>
<input type="text" id="scan-subnet" placeholder="e.g. 10.0.0.0/24 — overrides the selection above">
</div>
<div class="button-row">
<button id="scan-receivers-btn" class="btn">SCAN NOW</button>
</div>
<div id="scan-results" class="info-box hidden">
<h3>Scan Results</h3>
<div id="scan-results-list"></div>
<div class="button-row" id="scan-use-row" style="display:none">
<button id="use-selected-btn" class="btn btn-primary">USE SELECTED RECEIVERS</button>
</div>
</div>
<div id="network-info-box" class="info-box">
<h3>Host Network</h3>
<div id="network-info">Loading...</div>
</div>
</section>
<!-- Location Tab -->
<section id="tab-location" class="tab-pane hidden">
<h2>Location Settings</h2>
<div class="form-group">
<label>Location Name</label>
<input type="text" id="location-name" placeholder="My Location">
</div>
<div class="form-row">
<div class="form-group">
<label>Latitude</label>
<input type="number" id="location-lat" step="0.0001" placeholder="0.0">
</div>
<div class="form-group">
<label>Longitude</label>
<input type="number" id="location-lon" step="0.0001" placeholder="0.0">
</div>
</div>
<div class="button-row">
<button id="browser-location-btn" class="btn">USE BROWSER LOCATION</button>
<button id="save-location-btn" class="btn btn-primary">SAVE</button>
</div>
</section>
<!-- Display Tab -->
<section id="tab-display" class="tab-pane hidden">
<h2>Display Settings</h2>
<div class="form-group">
<label>Theme</label>
<select id="theme-select"></select>
</div>
<div class="form-row">
<div class="form-group">
<label>Site Title</label>
<input type="text" id="site-title" placeholder="ADS-Bit">
</div>
<div class="form-group">
<label>Subtitle</label>
<input type="text" id="site-subtitle" placeholder="Retro Flight Tracker">
</div>
</div>
<div class="form-group">
<label>Temperature Unit</label>
<select id="temp-unit">
<option value="F">Fahrenheit (F)</option>
<option value="C">Celsius (C)</option>
</select>
</div>
<div class="form-row">
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" id="show-weather" checked> Show Weather
</label>
</div>
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" id="show-sidebar" checked> Show Sidebar
</label>
</div>
</div>
<div class="form-group">
<label>Default View Direction</label>
<select id="default-direction">
<option value="0">North</option>
<option value="90">East</option>
<option value="180">South</option>
<option value="270">West</option>
</select>
</div>
<div class="button-row">
<button id="save-display-btn" class="btn btn-primary">SAVE</button>
</div>
</section>
<!-- Themes Tab -->
<section id="tab-themes" class="tab-pane hidden">
<h2>Theme Management</h2>
<div class="theme-info">
<h3>Background Specifications</h3>
<p><strong>Size:</strong> 1536 x 1024 px &nbsp; <strong>Format:</strong> PNG</p>
<p><strong>Directions:</strong> North, East, South, West - each showing the horizon view from your location</p>
<p><strong>Tip:</strong> Place the horizon roughly at the vertical center of each image</p>
</div>
<div class="theme-create-row">
<input type="text" id="new-theme-name" placeholder="New theme name...">
<button id="create-theme-btn" class="btn btn-primary">CREATE THEME</button>
</div>
<div id="theme-grid"></div>
</section>
<!-- Tuning Tab -->
<section id="tab-tuning" class="tab-pane hidden">
<h2>Performance Tuning</h2>
<div class="form-group">
<label>Flight Timeout (seconds)</label>
<input type="number" id="flight-timeout" min="10" max="600" value="60">
<span class="hint">Remove flights not seen in this many seconds</span>
</div>
<div class="form-group">
<label>Broadcast Interval (seconds)</label>
<input type="number" id="broadcast-interval" min="1" max="30" value="1">
<span class="hint">How often to push updates to viewers</span>
</div>
<div class="form-group">
<label>Cleanup Interval (seconds)</label>
<input type="number" id="cleanup-interval" min="5" max="120" value="10">
<span class="hint">How often to check for stale flights</span>
</div>
<div class="form-group">
<label>Receiver Reconnect Delay (seconds)</label>
<input type="number" id="reconnect-delay" min="1" max="60" value="5">
<span class="hint">Wait time before reconnecting to a lost receiver</span>
</div>
<div class="button-row">
<button id="save-tuning-btn" class="btn btn-primary">SAVE</button>
</div>
</section>
<!-- Sprites Tab -->
<section id="tab-sprites" class="tab-pane hidden">
<h2>Aircraft Sprites</h2>
<div class="sprite-info">
<h3>Sprite Specifications</h3>
<p><strong>Size:</strong> 500 x 333 px &nbsp; <strong>Format:</strong> PNG 8-bit RGBA (transparent background)</p>
<p><strong>Orientation:</strong> Facing right (auto-flipped for westbound aircraft)</p>
<p><strong>Style:</strong> Retro SNES pixel art, white/blue/red color scheme</p>
</div>
<div id="sprite-grid" class="sprite-grid">
<!-- Populated by JS -->
</div>
</section>
<!-- Security Tab -->
<section id="tab-security" class="tab-pane hidden">
<h2>Change Admin Password</h2>
<div class="form-group">
<label>Current Password</label>
<input type="password" id="current-password" autocomplete="current-password">
</div>
<div class="form-group">
<label>New Password</label>
<input type="password" id="new-password" autocomplete="new-password">
</div>
<div class="form-group">
<label>Confirm New Password</label>
<input type="password" id="confirm-password" autocomplete="new-password">
</div>
<div class="button-row">
<button id="change-password-btn" class="btn btn-primary">CHANGE PASSWORD</button>
</div>
</section>
</main>
<div id="toast" class="toast hidden"></div>
</div>
<script src="/admin/pixel-editor.js?v=20260609d"></script>
<script src="/admin/admin.js?v=20260609d"></script>
</body>
</html>