diff --git a/avian/api/birdnet-api.php b/avian/api/birdnet-api.php
index b33c379..575fada 100644
--- a/avian/api/birdnet-api.php
+++ b/avian/api/birdnet-api.php
@@ -216,6 +216,7 @@ switch ($action) {
'quiet_end' => 6,
'heal_hours' => 24,
'timeout' => 45,
+ 'frame_host' => '',
'basic_user' => '',
'basic_pass' => '',
];
diff --git a/avian/frontend/apt.js b/avian/frontend/apt.js
index 5b18827..5c87f61 100644
--- a/avian/frontend/apt.js
+++ b/avian/frontend/apt.js
@@ -2435,6 +2435,9 @@
.then(function (fc) {
adminBody.innerHTML =
'
'
+ + ''
+ '
Display
'
+ frameSegmented('mode', 'Mode', 'how the time window works', fc.mode, [
{ v: 'today', label: 'today' },
@@ -2465,6 +2468,7 @@
+ frameSlider('mat_opening_w', 'Mat width', 'matte opening width (inches, 0=default)', fc.mat_opening_w, 0, 12, 0.5, 1)
+ frameSlider('mat_opening_h', 'Mat height', 'matte opening height (inches, 0=default)', fc.mat_opening_h, 0, 14, 0.5, 1)
+ frameSlider('mat', 'Extra mat', 'global content shrink inside opening', fc.mat, 0, 0.1, 0.01, 2)
+ + frameInput('frame_host', 'Frame address', 'IP or hostname of the frame Pi', fc.frame_host || '')
+ frameInput('panel', 'Panel driver', 'force driver (blank=auto)', fc.panel || '')
+ frameSlider('timeout', 'Timeout', 'seconds to wait for page render', fc.timeout, 10, 120, 5, 0)
+ '
Source
'
@@ -2481,7 +2485,7 @@
+ frameInput('basic_user', 'Username', 'basic auth username', fc.basic_user || '')
+ frameInput('basic_pass', 'Password', 'basic auth password', fc.basic_pass || '', true)
+ '
';
- wireFrameControls();
+ wireFrameControls(fc);
})
.catch(function (err) {
adminBody.innerHTML =
@@ -2549,8 +2553,19 @@
}, 500);
}
- function wireFrameControls() {
+ function wireFrameControls(fc) {
var pending = {};
+ var refreshBtn = document.getElementById('frameRefreshBtn');
+ if (refreshBtn) {
+ refreshBtn.addEventListener('click', function () {
+ refreshBtn.textContent = 'Refreshing...';
+ refreshBtn.disabled = true;
+ fetch('http://' + (fc.frame_host || 'birdpic.local') + ':8080/refresh', { method: 'POST', mode: 'cors' })
+ .then(function () { refreshBtn.textContent = 'Triggered'; })
+ .catch(function () { refreshBtn.textContent = 'Failed (check LAN)'; })
+ .finally(function () { setTimeout(function () { refreshBtn.textContent = 'Refresh Now'; refreshBtn.disabled = false; }, 3000); });
+ });
+ }
adminBody.querySelectorAll('.switch[data-frame-key]').forEach(function (sw) {
sw.addEventListener('click', function () {
var on = sw.getAttribute('aria-checked') !== 'true';
diff --git a/avian/frontend/styles.css b/avian/frontend/styles.css
index ceed7bd..04c4643 100644
--- a/avian/frontend/styles.css
+++ b/avian/frontend/styles.css
@@ -1634,3 +1634,17 @@ body.av-local #dd-items { display: block; }
}
.frame-section-label:first-child { padding-top: 4px; }
+
+ .frame-refresh-btn {
+ font: 600 13px/1 var(--sans, ui-sans-serif, system-ui, sans-serif);
+ padding: 10px 24px;
+ border: 1px solid var(--ink-soft, #8a8078);
+ border-radius: 6px;
+ background: transparent;
+ color: var(--ink, #1a1612);
+ cursor: pointer;
+ transition: background 0.15s, opacity 0.15s;
+ }
+ .frame-refresh-btn:hover { background: rgba(26,22,18,0.05); }
+ .frame-refresh-btn:disabled { opacity: 0.5; cursor: default; }
+