[feat](trx-rs): harden auth UI and extend planning docs
Co-authored-by: OpenAI Codex <codex@openai.com> Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
This commit is contained in:
@@ -12,10 +12,15 @@ function loadSetting(key, fallback) {
|
||||
|
||||
// --- Authentication ---
|
||||
let authRole = null; // null (not authenticated), "rx" (read-only), or "control" (full access)
|
||||
let authEnabled = true;
|
||||
|
||||
async function checkAuthStatus() {
|
||||
try {
|
||||
const resp = await fetch("/auth/session");
|
||||
if (resp.status === 404) {
|
||||
// Auth API not exposed -> treat as auth-disabled mode.
|
||||
return { authenticated: true, role: "control", auth_disabled: true };
|
||||
}
|
||||
if (!resp.ok) return { authenticated: false };
|
||||
const data = await resp.json();
|
||||
return data;
|
||||
@@ -32,6 +37,9 @@ async function authLogin(passphrase) {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ passphrase }),
|
||||
});
|
||||
if (resp.status === 404) {
|
||||
return { authenticated: true, role: "control", auth_disabled: true };
|
||||
}
|
||||
if (!resp.ok) {
|
||||
const text = await resp.text();
|
||||
throw new Error(text || "Login failed");
|
||||
@@ -46,7 +54,7 @@ async function authLogin(passphrase) {
|
||||
async function authLogout() {
|
||||
try {
|
||||
const resp = await fetch("/auth/logout", { method: "POST" });
|
||||
if (!resp.ok) throw new Error("Logout failed");
|
||||
if (resp.status !== 404 && !resp.ok) throw new Error("Logout failed");
|
||||
authRole = null;
|
||||
// Disconnect and show auth gate without page reload
|
||||
disconnect();
|
||||
@@ -66,6 +74,7 @@ async function authLogout() {
|
||||
}
|
||||
|
||||
function showAuthGate(allowGuest = false) {
|
||||
if (!authEnabled) return;
|
||||
document.getElementById("loading").style.display = "none";
|
||||
document.getElementById("content").style.display = "none";
|
||||
document.getElementById("auth-gate").style.display = "block";
|
||||
@@ -121,6 +130,12 @@ function updateAuthUI() {
|
||||
const badgeRole = document.getElementById("auth-role-badge");
|
||||
const headerAuthBtn = document.getElementById("header-auth-btn");
|
||||
|
||||
if (!authEnabled) {
|
||||
if (badge) badge.style.display = "none";
|
||||
if (headerAuthBtn) headerAuthBtn.style.display = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
if (authRole) {
|
||||
badge.style.display = "block";
|
||||
badgeRole.textContent = authRole === "control" ? "Control (full access)" : "RX (read-only)";
|
||||
@@ -990,7 +1005,7 @@ function disconnect() {
|
||||
|
||||
async function postPath(path) {
|
||||
const resp = await fetch(path, { method: "POST" });
|
||||
if (resp.status === 401) {
|
||||
if (authEnabled && resp.status === 401) {
|
||||
// Not authenticated - return to login
|
||||
authRole = null;
|
||||
if (es) es.close();
|
||||
@@ -1247,6 +1262,18 @@ document.querySelector(".tab-bar").addEventListener("click", (e) => {
|
||||
// --- Auth startup sequence ---
|
||||
async function initializeApp() {
|
||||
const authStatus = await checkAuthStatus();
|
||||
authEnabled = !authStatus.auth_disabled;
|
||||
|
||||
if (!authEnabled) {
|
||||
authRole = "control";
|
||||
hideAuthGate();
|
||||
updateAuthUI();
|
||||
connect();
|
||||
resizeHeaderSignalCanvas();
|
||||
startHeaderSignalSampling();
|
||||
return;
|
||||
}
|
||||
|
||||
if (authStatus.authenticated) {
|
||||
// User has valid session
|
||||
authRole = authStatus.role;
|
||||
|
||||
@@ -54,15 +54,23 @@
|
||||
</div>
|
||||
<div id="content" style="display:none;">
|
||||
<div class="status">
|
||||
<div class="full-row freq-row label-below-row">
|
||||
<div class="label"><span>Frequency</span></div>
|
||||
<div class="full-row freq-row">
|
||||
<div class="inline freq-inline">
|
||||
<div class="wavelength-display" id="wavelength" title="Wavelength">--</div>
|
||||
<input class="status-input" id="freq" type="text" value="--" />
|
||||
<div class="jog-step" id="jog-step">
|
||||
<button type="button" data-step="1000000">MHz</button>
|
||||
<button type="button" data-step="1000" class="active">kHz</button>
|
||||
<button type="button" data-step="1">Hz</button>
|
||||
<div class="freq-field wavelength-col">
|
||||
<div class="wavelength-display" id="wavelength" title="Wavelength">--</div>
|
||||
<div class="label"><span>Wavelength</span></div>
|
||||
</div>
|
||||
<div class="freq-field frequency-col">
|
||||
<input class="status-input" id="freq" type="text" value="--" />
|
||||
<div class="label"><span>Frequency</span></div>
|
||||
</div>
|
||||
<div class="freq-field unit-col">
|
||||
<div class="jog-step" id="jog-step">
|
||||
<button type="button" data-step="1000000">MHz</button>
|
||||
<button type="button" data-step="1000" class="active">kHz</button>
|
||||
<button type="button" data-step="1">Hz</button>
|
||||
</div>
|
||||
<div class="label"><span>Unit</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -234,7 +234,33 @@ button { padding: 0.5rem 0.9rem; border-radius: 6px; border: 1px solid var(--btn
|
||||
button:disabled { opacity: 0.6; cursor: not-allowed; }
|
||||
.hint { color: var(--text-muted); font-size: 0.85rem; }
|
||||
.inline { display: flex; gap: 0.5rem; align-items: center; }
|
||||
.freq-inline #freq { flex: 1 1 auto; }
|
||||
.freq-inline {
|
||||
align-items: flex-start;
|
||||
}
|
||||
.freq-field {
|
||||
display: grid;
|
||||
grid-template-rows: 3.35rem auto;
|
||||
align-items: start;
|
||||
}
|
||||
.freq-field .label {
|
||||
margin-top: 0.45rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.frequency-col {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
.frequency-col #freq {
|
||||
width: 100%;
|
||||
height: 3.35rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.wavelength-col {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.unit-col {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.label-below-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user