Add Frame admin panel with full remote config

- New frame_config API endpoint (GET/POST) stores all frame display
  settings in frame-config.json on the BirdNET server
- Frame menu item in the admin drawer opens a full settings panel
  with sections: Display, Titles, Collage, Hardware, Source, Auth
- Changes auto-save via debounced POST; the frame Pi picks them up
  on its next 15-min timer cycle
- TODAY window button in the time picker with configurable start
  hour (localStorage bird:todayStart, adjustable in Settings)

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
This commit is contained in:
Chris Sader
2026-07-17 18:02:59 -05:00
parent 896901f799
commit 5314b24c02
4 changed files with 242 additions and 0 deletions
+58
View File
@@ -184,6 +184,64 @@ switch ($action) {
break;
}
case 'frame_config': {
$cfg_path = __DIR__ . '/frame-config.json';
$defaults = [
'mode' => 'today',
'hours' => 24,
'start_time' => '06:00',
'hold_overnight' => true,
'species_source' => '',
'zip' => '',
'bw_days' => 7,
'bw_country' => 'us',
'image_url' => '',
'shoot' => true,
'shoot_title' => 'avian visitors',
'shoot_subtitle' => 'Heard Today',
'shoot_headline_px' => 42,
'shoot_eyebrow_px' => 18,
'shoot_lowercase' => false,
'shoot_mat' => 0.04,
'shoot_small_floor' => 0.04,
'shoot_count_exp' => 0.65,
'mat' => 0.0,
'mat_opening_w' => 0,
'mat_opening_h' => 0,
'rotate' => 90,
'saturation' => 0.6,
'panel' => '',
'quiet_start' => 22,
'quiet_end' => 6,
'heal_hours' => 24,
'timeout' => 45,
'basic_user' => '',
'basic_pass' => '',
];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input = json_decode(file_get_contents('php://input'), true);
if (!is_array($input)) {
http_response_code(400);
echo json_encode(['error' => 'invalid JSON']);
break;
}
$current = file_exists($cfg_path) ? json_decode(file_get_contents($cfg_path), true) : $defaults;
if (!is_array($current)) $current = $defaults;
$allowed = array_keys($defaults);
foreach ($input as $k => $v) {
if (in_array($k, $allowed, true)) $current[$k] = $v;
}
file_put_contents($cfg_path, json_encode($current, JSON_PRETTY_PRINT) . "\n");
echo json_encode($current);
} else {
$current = file_exists($cfg_path) ? json_decode(file_get_contents($cfg_path), true) : $defaults;
if (!is_array($current)) $current = $defaults;
echo json_encode(array_merge($defaults, $current));
}
break;
}
default:
http_response_code(404);
echo json_encode(['error' => 'unknown action']);
+1
View File
@@ -31,6 +31,7 @@ if (getenv('AV_REQUIRE_AUTH') === '1' && empty($_SERVER['HTTP_AUTHORIZATION']))
echo json_encode([
'items' => [
['label' => 'settings', 'href' => '/#admin=settings', 'native' => true],
['label' => 'frame', 'href' => '/#admin=frame', 'native' => true],
['label' => 'system', 'href' => '/#admin=system', 'native' => true],
['label' => 'logs', 'href' => '/#admin=logs', 'native' => true],
['label' => 'tools', 'href' => '/#admin=tools', 'native' => true],