Refine web quick panels and Wi-Fi controls
This commit is contained in:
+35
-27
@@ -26,9 +26,13 @@ static const char s_index_html[] =
|
||||
"--good:#52d68b;--warn:#ffc857;--bad:#ff6b7a;--radius:14px}\n"
|
||||
"*{box-sizing:border-box}\n"
|
||||
"[hidden]{display:none!important}\n"
|
||||
".quick-trigger{background:none;border:0;padding:0;color:inherit;text-align:left;cursor:pointer}.quick-trigger:focus-visible{outline:2px solid var(--accent);outline-offset:3px}\n"
|
||||
".quick-trigger{display:block;width:100%;color:inherit;text-align:left;font:inherit;cursor:pointer;overflow:hidden}.quick-trigger:disabled{cursor:default}.quick-trigger:focus-visible{outline:2px solid var(--accent);outline-offset:3px}\n"
|
||||
".settings-page[data-quick='true']{position:fixed;z-index:10;top:8px;right:8px;width:min(600px,calc(100vw - 16px));max-height:calc(100vh - 16px);max-height:calc(100dvh - 16px);overflow:auto;background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:16px;box-shadow:0 8px 32px #0008}\n"
|
||||
"[data-quick='true']>#settings-navigation,[data-quick='true'] #network-password-mode-label,[data-quick='true'] #network-password-label,[data-quick='true'] #network-password-status{display:none}\n"
|
||||
"[data-quick='true'] .full-settings-only,[data-quick='true'] #settings-navigation,[data-quick='true'] #quick-help,[data-quick='true'] h3,[data-quick='true']>div>h2,[data-quick='true'] p:not([role=status]),[data-quick='true'] #network-summary,[data-quick='true'] #broker-values,[data-quick='true'] #network-ssid-detail{display:none}\n"
|
||||
"[data-quick='true'] #quick-header>h2{display:block}[data-quick='true'] #settings-values>:nth-child(n+3){display:none}\n"
|
||||
"[data-quick='true'] #network-edit>*{display:none}[data-quick='true'] #network-edit>.settings-edit:first-of-type{display:grid}[data-quick='true'] #network-edit>.network-quick-actions{display:flex}\n"
|
||||
"[data-quick='true'] #network-edit>.settings-edit>label{display:none}[data-quick='true'] #network-edit>.settings-edit>#network-target-label,[data-quick='true'] #network-edit>.settings-edit>#network-policy-label{display:grid}[data-quick='true'] #network-edit>.settings-edit>#network-enabled-label{display:flex}\n"
|
||||
"[data-quick='true'] #network-wifi-load{display:none}\n"
|
||||
"html,body{height:100%;margin:0;overflow:hidden}\n"
|
||||
"body{background:radial-gradient(circle at top left,#142033 0,var(--bg) 42rem);"
|
||||
"color:var(--text);font:14px/1.45 system-ui,-apple-system,BlinkMacSystemFont,"
|
||||
@@ -55,7 +59,7 @@ static const char s_index_html[] =
|
||||
".dashboard{display:grid;grid-template-columns:minmax(0,1.35fr) minmax(280px,.65fr);gap:16px}\n"
|
||||
".panel{background:linear-gradient(145deg,#151e2cdd,#0f1621ee);border:1px solid var(--line);"
|
||||
"border-radius:var(--radius);box-shadow:0 16px 45px #0005}\n"
|
||||
".status-grid{padding:16px;display:grid;grid-template-columns:repeat(4,minmax(105px,1fr));gap:12px}\n"
|
||||
".status-grid{min-width:0;padding:16px;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px}\n"
|
||||
".status-item{min-width:0;padding:10px 12px;background:#0b111b99;border:1px solid #202c3e;"
|
||||
"border-radius:10px}\n"
|
||||
".status-item.wide{grid-column:span 2}\n"
|
||||
@@ -152,14 +156,10 @@ static const char s_index_html[] =
|
||||
"<span id=\"role-status\" class=\"badge\" data-tone=\"warn\">Observer</span></div>\n"
|
||||
"<div class=\"status-item\"><span class=\"label\">Broker client</span>"
|
||||
"<span id=\"client-id\" class=\"value\">—</span></div>\n"
|
||||
"<div class=\"status-item\"><span class=\"label\">Active writer</span>"
|
||||
"<button type=\"button\" id=\"quick-writer\" class=\"quick-trigger\" aria-label=\"Active writer management\" aria-controls=\"serial-settings\" aria-expanded=\"false\" aria-haspopup=\"dialog\" disabled><span id=\"writer-id\" class=\"value\">None</span></button></div>\n"
|
||||
"<div class=\"status-item\"><span class=\"label\">Broker clients</span>"
|
||||
"<button type=\"button\" id=\"quick-broker\" class=\"quick-trigger\" aria-label=\"Live broker clients\" aria-controls=\"serial-settings\" aria-expanded=\"false\" aria-haspopup=\"dialog\" disabled><span id=\"broker-clients\" class=\"value\">—</span></button></div>\n"
|
||||
"<div class=\"status-item wide\"><span class=\"label\">Wi-Fi</span>"
|
||||
"<button type=\"button\" id=\"quick-network\" class=\"quick-trigger\" aria-label=\"Wi-Fi quick settings\" aria-controls=\"serial-settings\" aria-expanded=\"false\" aria-haspopup=\"dialog\" disabled><span id=\"wifi-summary\" class=\"value\">Loading…</span></button></div>\n"
|
||||
"<div class=\"status-item wide\"><span class=\"label\">Serial</span>"
|
||||
"<button type=\"button\" id=\"quick-serial\" class=\"quick-trigger\" aria-label=\"Serial quick settings\" aria-controls=\"serial-settings\" aria-expanded=\"false\" aria-haspopup=\"dialog\" disabled><span id=\"serial-summary\" class=\"value\">Loading…</span></button></div>\n"
|
||||
"<button type=\"button\" id=\"quick-writer\" class=\"status-item quick-trigger\" aria-label=\"Active writer management\" aria-controls=\"serial-settings\" aria-expanded=\"false\" aria-haspopup=\"dialog\" disabled><span class=\"label\">Active writer</span><span id=\"writer-id\" class=\"value\">None</span></button>\n"
|
||||
"<button type=\"button\" id=\"quick-broker\" class=\"status-item quick-trigger\" aria-label=\"Live broker clients\" aria-controls=\"serial-settings\" aria-expanded=\"false\" aria-haspopup=\"dialog\" disabled><span class=\"label\">Broker clients</span><span id=\"broker-clients\" class=\"value\">—</span></button>\n"
|
||||
"<button type=\"button\" id=\"quick-network\" class=\"status-item wide quick-trigger\" aria-label=\"Wi-Fi quick settings\" aria-controls=\"serial-settings\" aria-expanded=\"false\" aria-haspopup=\"dialog\" disabled><span class=\"label\">Wi-Fi</span><span id=\"wifi-summary\" class=\"value\">Loading…</span></button>\n"
|
||||
"<button type=\"button\" id=\"quick-serial\" class=\"status-item wide quick-trigger\" aria-label=\"Serial quick settings\" aria-controls=\"serial-settings\" aria-expanded=\"false\" aria-haspopup=\"dialog\" disabled><span class=\"label\">Serial</span><span id=\"serial-summary\" class=\"value\">Loading…</span></button>\n"
|
||||
"</div>\n"
|
||||
"<div class=\"panel controls\">\n"
|
||||
"<div class=\"button-row\">\n"
|
||||
@@ -201,7 +201,7 @@ static const char s_index_html[] =
|
||||
"<div class=\"serial-actions\"><button id=\"lifecycle-stop\" class=\"button\" type=\"button\">Stop HTTPS…</button><button id=\"lifecycle-restart\" class=\"button\" type=\"button\">Restart HTTPS…</button><button id=\"lifecycle-reboot\" class=\"button\" type=\"button\">Reboot device…</button><button id=\"lifecycle-rotate\" class=\"button\" type=\"button\">Rotate HTTPS identity…</button><button id=\"lifecycle-result\" class=\"button\" type=\"button\">Check Operation Result</button></div>"
|
||||
"<p id=\"lifecycle-operation-detail\" class=\"connection-detail\" role=\"status\">Explicit confirmation required. Acknowledgement is not peer receipt or completion. Connection loss, expiry, revocation or timeout does not prove cancellation after admission. No automatic mutation retry or restore. Check Result, inspect state, then act explicitly.</p><a href=\"/\">Reload / sign in after recovery</a></div>\n"
|
||||
"<div id=\"ssh-settings\" hidden><h2>SSH service and sessions</h2><p class=\"connection-detail\">SSH only, TCP port 22. Start/Stop do not change saved settings or host identity. Stop closes all SSH sessions, including any admitted before execution; an SSH administrator's already executing command may finish. HTTPS login, browser terminals, Wi-Fi, USB and UART0 are not stopped. Targeted disconnect affects only the selected SSH connection, not all logins for its account. Viewing or selecting never changes services or writer ownership.</p><button id=\"ssh-refresh\" class=\"button\" type=\"button\">Refresh</button><p id=\"ssh-detail\" class=\"connection-detail\" role=\"status\"></p><dl id=\"ssh-values\" class=\"settings-values\"></dl><div class=\"settings-edit\"><label>Disconnect SSH session<select id=\"ssh-target\"><option value=\"\">Select a session</option><option id=\"ssh-option-0\" hidden disabled></option><option id=\"ssh-option-1\" hidden disabled></option></select></label></div><div class=\"serial-actions\"><button id=\"ssh-start\" class=\"button\" type=\"button\">Start SSH…</button><button id=\"ssh-stop\" class=\"button\" type=\"button\">Stop SSH…</button><button id=\"ssh-rotate\" class=\"button\" type=\"button\">Rotate SSH host identity…</button><button id=\"ssh-disconnect\" class=\"button\" type=\"button\">Disconnect selected…</button><button id=\"ssh-result\" class=\"button\" type=\"button\">Check Operation Result</button></div><p id=\"ssh-operation-detail\" class=\"connection-detail\" role=\"status\">Explicit confirmation required. After submission use Check Operation Result, then Refresh. Navigation or timeout does not cancel admitted work. No automatic mutation retry.</p></div>\n"
|
||||
"<div id=\"broker-settings\" hidden><h2>Broker clients and writer</h2><p class=\"connection-detail\">One writer, multiple isolated observers. Viewing, refreshing and selecting do not change the lease or either terminal. Assignment revokes the previous writer, without recalling bytes already accepted by UART. Any intervening lease transition rejects stale confirmation, even release and reacquire by the same writer.</p><p class=\"connection-detail\">Pending and high-water are bounded output bytes; dropped counts cover this connection or the last shell counter clear. No UART data is consumed. Refresh retains explicit selection without renewing its lease token. Stale selections require choosing the blank option then the target again. No persistence or disconnect controls.</p><button id=\"broker-refresh\" class=\"button\" type=\"button\">Refresh</button><p id=\"broker-detail\" class=\"connection-detail\" role=\"status\"></p><dl id=\"broker-values\" class=\"settings-values\"></dl><div class=\"settings-edit\"><label>Assign writer to<select id=\"broker-target\"><option value=\"\">Select a connected client</option><option id=\"broker-option-0\" hidden disabled></option><option id=\"broker-option-1\" hidden disabled></option><option id=\"broker-option-2\" hidden disabled></option><option id=\"broker-option-3\" hidden disabled></option><option id=\"broker-option-4\" hidden disabled></option><option id=\"broker-option-5\" hidden disabled></option><option id=\"broker-option-6\" hidden disabled></option><option id=\"broker-option-7\" hidden disabled></option></select></label></div><div class=\"serial-actions\"><button id=\"broker-assign\" class=\"button\" type=\"button\">Assign writer…</button><button id=\"broker-result\" class=\"button\" type=\"button\">Check Operation Result</button></div><p id=\"broker-operation-detail\" class=\"connection-detail\" role=\"status\">Explicit confirmation required. Navigation or timeout does not cancel admitted work. Check Result after uncertainty; no automatic mutation retry.</p></div>\n"
|
||||
"<div id=\"broker-settings\" hidden><h2>Broker clients and writer</h2><p class=\"connection-detail\">One writer, multiple isolated observers. Viewing, refreshing and selecting do not change the lease or either terminal. Assignment revokes the previous writer, without recalling bytes already accepted by UART. Any intervening lease transition rejects stale confirmation, even release and reacquire by the same writer.</p><p class=\"connection-detail\">Pending and high-water are bounded output bytes; dropped counts cover this connection or the last shell counter clear. No UART data is consumed. Refresh retains explicit selection without renewing its lease token. Stale selections require choosing the blank option then the target again. No persistence or disconnect controls.</p><button id=\"broker-refresh\" class=\"button\" type=\"button\">Refresh</button><p id=\"broker-detail\" class=\"connection-detail\" role=\"status\"></p><dl id=\"broker-values\" class=\"settings-values\"></dl><div class=\"settings-edit\"><label>Assign writer to<select id=\"broker-target\"><option value=\"\">Select a connected client</option><option id=\"broker-option-0\" hidden disabled></option><option id=\"broker-option-1\" hidden disabled></option><option id=\"broker-option-2\" hidden disabled></option><option id=\"broker-option-3\" hidden disabled></option><option id=\"broker-option-4\" hidden disabled></option><option id=\"broker-option-5\" hidden disabled></option><option id=\"broker-option-6\" hidden disabled></option><option id=\"broker-option-7\" hidden disabled></option></select></label></div><div class=\"serial-actions\"><button id=\"broker-assign\" class=\"button\" type=\"button\">Assign writer…</button><button id=\"broker-result\" class=\"button\" type=\"button\">Check Operation Result</button></div><p id=\"broker-operation-detail\" class=\"connection-detail\" role=\"status\"><span class=\"full-settings-only\">Explicit confirmation required. Navigation or timeout does not cancel admitted work. Check Result after uncertainty; no automatic mutation retry.</span></p></div>\n"
|
||||
"<div id=\"display-settings\" hidden><h2>Display</h2>\n"
|
||||
"<p class=\"connection-detail\">Working OLED inactivity settings, not saved NVS values. Zero disables a transition. Each timeout is 0–86400 seconds; when both are enabled, Off must be later than Dim.</p>\n"
|
||||
"<p class=\"connection-detail\">Apply and Defaults change RAM only. Save persists the working snapshot, not browser drafts. Load discards drafts and uses stored settings, or defaults if storage is absent/incompatible; it does not change NVS. Reset saves defaults and applies them. Refresh discards drafts. Intervening configuration edits reject stale operations: Refresh and review before retrying.</p>\n"
|
||||
@@ -227,9 +227,9 @@ static const char s_index_html[] =
|
||||
"<button id=\"network-refresh\" class=\"button\" type=\"button\">Refresh</button>"
|
||||
"<p id=\"network-detail\" class=\"connection-detail\" role=\"status\"></p><dl id=\"network-summary\" class=\"settings-values\"></dl>"
|
||||
"<div id=\"network-edit\" hidden><h3>Wi-Fi working configuration</h3><div class=\"settings-edit\">"
|
||||
"<label>Target<select id=\"network-target\"><option value=\"ap\">Access point / boot policy</option>"
|
||||
"<option value=\"0\">STA profile 0</option><option value=\"1\">STA profile 1</option>"
|
||||
"<option value=\"2\">STA profile 2</option><option value=\"3\">STA profile 3</option></select></label>"
|
||||
"<label id=\"network-target-label\">Profile / AP mode<select id=\"network-target\"><option value=\"ap\">Access point</option>"
|
||||
"<option id=\"network-profile-0\" value=\"0\">STA profile 0</option><option id=\"network-profile-1\" value=\"1\">STA profile 1</option>"
|
||||
"<option id=\"network-profile-2\" value=\"2\">STA profile 2</option><option id=\"network-profile-3\" value=\"3\">STA profile 3</option></select></label>"
|
||||
"<label id=\"network-boot-label\" class=\"settings-check\">Enabled at boot<input id=\"network-boot\" type=\"checkbox\"></label>"
|
||||
"<label id=\"network-policy-label\">AP policy<select id=\"network-policy\"><option>off</option><option>fallback</option><option>always</option></select></label>"
|
||||
"<label id=\"network-channel-label\">AP channel<input id=\"network-channel\" type=\"number\" min=\"1\" max=\"11\" step=\"1\"></label>"
|
||||
@@ -246,7 +246,7 @@ static const char s_index_html[] =
|
||||
"Switching modes is lossless or refused. Password replacement requires 8–63 printable ASCII characters; blank never clears. "
|
||||
"AP always requires a PSK. STA clear requires disabled state. Transient passwords expire after 60 seconds and clear on submission or context change. "
|
||||
"NVS is unencrypted; replacement/clear is not secure flash erasure. JavaScript cannot securely zero strings.</p>"
|
||||
"<div class=\"serial-actions\"><button id=\"network-apply\" class=\"button\" type=\"button\">Apply selected Wi-Fi target to RAM</button>"
|
||||
"<div class=\"serial-actions network-quick-actions\"><button id=\"network-apply\" class=\"button\" type=\"button\">Apply selected Wi-Fi target to RAM</button>"
|
||||
"<button id=\"network-wifi-save\" class=\"button\" type=\"button\">Save working Wi-Fi to NVS</button>"
|
||||
"<button id=\"network-wifi-load\" class=\"button\" type=\"button\">Load stored Wi-Fi</button>"
|
||||
"</div><h3>Wi-Fi connection</h3><div class=\"serial-actions\"><button id=\"network-start\" class=\"button\" type=\"button\">Start</button><button id=\"network-stop\" class=\"button\" type=\"button\">Stop</button>"
|
||||
@@ -261,7 +261,7 @@ static const char s_index_html[] =
|
||||
"<button id=\"network-mdns-load\" class=\"button\" type=\"button\">Load mDNS</button>"
|
||||
"<button id=\"network-mdns-defaults\" class=\"button\" type=\"button\">mDNS defaults in RAM</button></div></div>"
|
||||
"<button id=\"network-result\" class=\"button\" type=\"button\">Check Operation Result</button>"
|
||||
"<p id=\"network-operation-detail\" class=\"connection-detail\" role=\"status\">After uncertainty, Check Result and Refresh. Never assume timeout or navigation cancels work; no automatic mutation retry.</p></div>"
|
||||
"<p id=\"network-operation-detail\" class=\"connection-detail\" role=\"status\"><span class=\"full-settings-only\">After uncertainty, Check Result and Refresh. Never assume timeout or navigation cancels work; no automatic mutation retry.</span></p></div>"
|
||||
"<div id=\"serial-settings-content\"><h2>Serial</h2><p class=\"connection-detail\">Working UART1 configuration below is not a saved NVS snapshot. "
|
||||
"Navigation leaves both terminals connected and preserves the serial writer lease.</p>"
|
||||
"<button id=\"refresh-settings\" class=\"button\" type=\"button\">Refresh</button>"
|
||||
@@ -296,7 +296,7 @@ static const char s_index_html[] =
|
||||
"<button id=\"serial-start\" class=\"button\" type=\"button\">Start</button>"
|
||||
"<button id=\"serial-stop\" class=\"button\" type=\"button\">Stop</button></div></div>"
|
||||
"<button id=\"serial-result\" class=\"button\" type=\"button\">Check Operation Result</button>"
|
||||
"<p id=\"serial-operation-detail\" class=\"connection-detail\" role=\"status\">Check Result after any uncertain submission; never assume timeout or navigation cancels an operation.</p></div>"
|
||||
"<p id=\"serial-operation-detail\" class=\"connection-detail\" role=\"status\"><span class=\"full-settings-only\">Check Result after any uncertain submission; never assume timeout or navigation cancels an operation.</span></p></div>"
|
||||
"<div id=\"account-settings\" hidden><h2>Accounts</h2>"
|
||||
"<p class=\"connection-detail\">Role and deletion changes are saved immediately and revoke only the affected account's network sessions. The final administrator is protected. "
|
||||
"Self changes can immediately close all this account's web/SSH sessions, including browser serial/admin. A disconnect or 401 is NOT proof of success; re-login and inspect before retrying.</p>"
|
||||
@@ -439,7 +439,7 @@ static const char s_app_js[] =
|
||||
" element('quick-title').textContent = quickTitle(domain);\n"
|
||||
" element('quick-help').textContent = brokerContext ? 'Live clients share one writer and isolated observation. Opening, refreshing and selecting never transfers the lease. Assign writer requires separate confirmation. Closing does not cancel admitted work; after uncertainty use Check Result, never automatic retry.' : 'Opening and selecting never applies changes. Apply changes RAM; Save persists working device values, not drafts. Closing discards drafts; admitted work is not cancelled. Password changes are available only on the full Network page.';\n"
|
||||
" element('quick-header').hidden = false; trigger.setAttribute('aria-expanded', 'true');\n"
|
||||
" terminalHost.hidden = previous !== 'serial'; adminHost.hidden = previous !== 'admin';\n"
|
||||
" renderTerminalView(previous);\n"
|
||||
" refreshSettings(); if (activate) element('quick-close').focus();\n"
|
||||
"}\n"
|
||||
"function quickContains(target) { return !!quick && !!target && (settingsHost.contains(target) || quick.trigger.contains(target)); }\n"
|
||||
@@ -458,7 +458,7 @@ static const char s_app_js[] =
|
||||
"settingsHost.addEventListener('pointerenter', () => window.clearTimeout(quickLeaveTimer));\n"
|
||||
"settingsHost.addEventListener('pointerleave', quickLeave); settingsHost.addEventListener('focusout', quickLeave);\n"
|
||||
"element('quick-close').addEventListener('click', () => closeQuick(true));\n"
|
||||
"element('quick-full').addEventListener('click', event => { event.preventDefault(); if (!quick) return; resetQuick(); clearNetworkSecret(); networkButtons(); terminalHost.hidden = adminHost.hidden = true; element('settings-' + settingsDomain).focus(); });\n"
|
||||
"element('quick-full').addEventListener('click', event => { event.preventDefault(); if (!quick) return; resetQuick(); clearNetworkSecret(); networkButtons(); renderTerminalView('settings'); element('settings-' + settingsDomain).focus(); });\n"
|
||||
"window.addEventListener('pointerdown', event => { if (quick && !quickContains(event.target)) closeQuick(); });\n"
|
||||
"window.addEventListener('keydown', event => { if (quick && event.key === 'Escape') { event.preventDefault(); event.stopPropagation(); closeQuick(true); } });\n"
|
||||
"function clearSettings() {\n"
|
||||
@@ -498,7 +498,7 @@ static const char s_app_js[] =
|
||||
" !['inactive', 'active', 'on-connect'].includes(value.dtr) || !Number.isInteger(value.rts_threshold) || value.rts_threshold < 1 || value.rts_threshold > 127) throw new Error('Invalid snapshot');\n"
|
||||
" for (const key of settingsFields) element('setting-' + key).textContent = key === 'running' ? (value[key] ? 'Running' : 'Stopped') : String(value[key]);\n"
|
||||
" for (const key of settingsFields.slice(1)) element('edit-' + key).value = String(value[key]);\n"
|
||||
" element('serial-edit').hidden = false; element('settings-values').hidden = false; settingsDetail.textContent = (serialOperationPending ? 'Snapshot may be stale: operation outcome pending or unknown. ' : 'Working snapshot loaded. ') + 'Apply changes RAM; Save explicitly persists working settings. Refresh replaces your draft.';\n"
|
||||
" element('serial-edit').hidden = false; element('settings-values').hidden = false; settingsDetail.textContent = (serialOperationPending ? 'Snapshot may be stale: operation outcome pending or unknown. ' : 'Working snapshot loaded. ') + (quick ? '' : 'Apply changes RAM; Save explicitly persists working settings. Refresh replaces your draft.');\n"
|
||||
" } catch (error) {\n"
|
||||
" if (live(generation) && current()) settingsDetail.textContent = (error.status ? error.message : 'Serial snapshot could not be read or was invalid.') + ' Snapshot stale or unavailable. Select Refresh to retry.';\n"
|
||||
" } finally {\n"
|
||||
@@ -817,7 +817,7 @@ static const char s_app_js[] =
|
||||
" }\n"
|
||||
" }\n"
|
||||
" element('broker-target').value = brokerSelection && !brokerSelection.missing && brokerClients.some(c => c.id === brokerSelection.target) ? String(brokerSelection.target) : '';\n"
|
||||
" brokerDetail.textContent = (brokerOperationPending ? 'Outcome pending or unknown. ' : '') + 'Writer: ' + (brokerWriter || 'None') + '. ' + brokerClients.length + ' connected clients. ' + (brokerSelection?.stale ? 'Selection stale: lease or client changed. Choose the blank option then explicitly select again before confirmation. ' : 'Refresh preserves selection, never renews its confirmation token. ') + (quick ? 'Live refresh every 5 seconds after completion. ' : '') + (brokerGeneration === 4294967295 ? 'Confirmation generation exhausted; use the admin shell.' : '');\n"
|
||||
" brokerDetail.textContent = (brokerOperationPending ? 'Outcome pending or unknown. ' : '') + 'Writer: ' + (brokerWriter || 'None') + '. ' + brokerClients.length + ' connected clients. ' + (brokerSelection?.stale ? 'Selection stale: choose the blank option, then reselect a client. ' : quick ? '' : 'Refresh preserves selection, never renews its confirmation token. ') + (brokerGeneration === 4294967295 ? 'Confirmation generation exhausted; use the admin shell.' : '');\n"
|
||||
" } catch (error) {\n"
|
||||
" if (live(generation) && current()) { brokerGeneration = 0; if (brokerSelection) brokerSelection.stale = true; brokerDetail.textContent = (error.status ? error.message : 'Broker snapshot unavailable or invalid.') + ' Selection stale. Select Refresh and reselect to retry.'; }\n"
|
||||
" } finally { window.clearTimeout(deadline); if (current()) { brokerAbort = null; brokerButtons(); if (brokerGeneration) scheduleBrokerLive(); } }\n"
|
||||
@@ -1283,6 +1283,11 @@ static const char s_app_js[] =
|
||||
" net('refresh').disabled = net('result').disabled = busy;\n"
|
||||
" net('password-clear').hidden = net('password-clear').disabled = net('target').value === 'ap';\n"
|
||||
" if (quick) { clearNetworkSecret(); net('password-mode').disabled = true; }\n"
|
||||
" for (let i = 0; i < 4; ++i) {\n"
|
||||
" const option = net('profile-' + i), profile = networkSnapshot?.wifi.profiles[i];\n"
|
||||
" option.textContent = 'STA ' + i + (profile?.ssid ? ' — ' + networkSSIDSummary(profile.ssid) : ' (empty)');\n"
|
||||
" option.hidden = option.disabled = !!quick && !profile?.ssid;\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"function clearNetwork() {\n"
|
||||
" clearNetworkSecret();\n"
|
||||
@@ -1357,9 +1362,9 @@ static const char s_app_js[] =
|
||||
" ['AP running', r.ap_running], ['AP clients', r.ap_clients], ['Wi-Fi last error', r.last_error],\n"
|
||||
" ['mDNS generation', m.generation], ['Hostname', m.hostname + '.local'], ['Expected announcement', m.announced],\n"
|
||||
" ['mDNS last error', m.last_error], ['DNS verification', 'Not client-verified DNS.']]);\n"
|
||||
" if (!['ap','0','1','2','3'].includes(net('target').value)) net('target').value = 'ap';\n"
|
||||
" if (!['ap','0','1','2','3'].includes(net('target').value) || quick && net('target').value !== 'ap' && !w.profiles[Number(net('target').value)]?.ssid) net('target').value = 'ap';\n"
|
||||
" renderNetworkTarget(); net('suffix').value = m.suffix; net('edit').hidden = false;\n"
|
||||
" net('detail').textContent = (networkPending ? 'Snapshot may be stale: outcome pending or unknown. ' : 'Working snapshot refreshed (Wi-Fi and mDNS are separate consistent copies). ') + 'Browser drafts are not saved; Save persists device working state.';\n"
|
||||
" net('detail').textContent = (networkPending ? 'Snapshot may be stale: outcome pending or unknown. ' : '') + (quick ? r.state + ' · IP: ' + (r.ip || 'none') + ' · AP: ' + (r.ap_running ? 'running' : 'off') + ' · Profile: ' + (r.active_profile < 0 ? 'none' : r.active_profile) : 'Working snapshot refreshed (Wi-Fi and mDNS are separate consistent copies). Browser drafts are not saved; Save persists device working state.');\n"
|
||||
" } catch (error) { if (live(generation) && current()) net('detail').textContent = 'Network snapshot stale or unavailable/invalid. Refresh explicitly to retry. No values inferred.'; }\n"
|
||||
" finally { if (current()) { networkAbort = null; networkButtons(); } }\n"
|
||||
"}\n"
|
||||
@@ -1555,15 +1560,18 @@ static const char s_app_js[] =
|
||||
" for (let offset = 0; offset < bytes.length; offset += 512) adminSocket.send(bytes.subarray(offset, offset + 512));\n"
|
||||
" });\n"
|
||||
" }\n"
|
||||
" renderTerminalView(mode);\n"
|
||||
" settingsHost.hidden = mode !== 'settings';\n"
|
||||
" lastFitWidth = lastFitHeight = 0; updateControls(); scheduleFit();\n"
|
||||
" if (mode === 'settings') { if (read) refreshSettings(); } else if (focus) (mode === 'serial' ? terminal : adminTerminal).focus();\n"
|
||||
"}\n"
|
||||
"function renderTerminalView(mode) {\n"
|
||||
" terminalHost.hidden = mode !== 'serial'; adminHost.hidden = mode !== 'admin';\n"
|
||||
" adminToggle.hidden = adminDetail.hidden = mode !== 'admin';\n"
|
||||
" element('select-serial').setAttribute('aria-pressed', String(mode === 'serial'));\n"
|
||||
" element('select-admin').setAttribute('aria-pressed', String(mode === 'admin'));\n"
|
||||
" element('select-settings').setAttribute('aria-pressed', String(mode === 'settings'));\n"
|
||||
" settingsHost.hidden = mode !== 'settings';\n"
|
||||
" element('terminal-title').textContent = mode === 'serial' ? 'Live serial stream' : mode === 'admin' ? 'Administration shell' : 'Settings';\n"
|
||||
" lastFitWidth = lastFitHeight = 0; updateControls(); scheduleFit();\n"
|
||||
" if (mode === 'settings') { if (read) refreshSettings(); } else if (focus) (mode === 'serial' ? terminal : adminTerminal).focus();\n"
|
||||
"}\n"
|
||||
"async function openAdmin() {\n"
|
||||
" if (accountRole !== 'admin' || selected !== 'admin' || unloading || navigating || loggingOut || suspended || !csrf || adminSocket || adminAbort) return;\n"
|
||||
|
||||
Reference in New Issue
Block a user