Refactor HTTP account system
CI / frontend (pull_request) Successful in 5m13s
CI / reuse (pull_request) Successful in 29s
CI / frontend (push) Successful in 4m15s
CI / reuse (push) Successful in 5s
CI / lint (pull_request) Successful in 2m25s
CI / test (pull_request) Successful in 9m24s
CI / lint (push) Successful in 2m23s
CI / test (push) Successful in 8m19s
CI / frontend (pull_request) Successful in 5m13s
CI / reuse (pull_request) Successful in 29s
CI / frontend (push) Successful in 4m15s
CI / reuse (push) Successful in 5s
CI / lint (pull_request) Successful in 2m25s
CI / test (pull_request) Successful in 9m24s
CI / lint (push) Successful in 2m23s
CI / test (push) Successful in 8m19s
This commit was merged in pull request #61.
This commit is contained in:
@@ -2,11 +2,12 @@
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
export type AuthRole = "rx" | "control";
|
||||
export type AuthRole = "user" | "admin";
|
||||
|
||||
export interface AuthSession {
|
||||
authenticated: boolean;
|
||||
role?: AuthRole;
|
||||
username?: string;
|
||||
auth_disabled?: boolean;
|
||||
}
|
||||
|
||||
@@ -18,7 +19,7 @@ function decodeAuthSession(value: unknown): AuthSession {
|
||||
if (typeof session.authenticated !== "boolean") {
|
||||
throw new TypeError("The authentication response has no authenticated flag");
|
||||
}
|
||||
if (session.role !== undefined && session.role !== "rx" && session.role !== "control") {
|
||||
if (session.role !== undefined && session.role !== "user" && session.role !== "admin") {
|
||||
throw new TypeError("The authentication response has an invalid role");
|
||||
}
|
||||
if (session.auth_disabled !== undefined && typeof session.auth_disabled !== "boolean") {
|
||||
@@ -26,13 +27,17 @@ function decodeAuthSession(value: unknown): AuthSession {
|
||||
}
|
||||
const decoded: AuthSession = { authenticated: session.authenticated };
|
||||
if (session.role !== undefined) decoded.role = session.role;
|
||||
if (session.username !== undefined) {
|
||||
if (typeof session.username !== "string") throw new TypeError("The authentication response has an invalid username");
|
||||
decoded.username = session.username;
|
||||
}
|
||||
if (session.auth_disabled !== undefined) decoded.auth_disabled = session.auth_disabled;
|
||||
return decoded;
|
||||
}
|
||||
|
||||
const authDisabledSession: AuthSession = {
|
||||
authenticated: true,
|
||||
role: "control",
|
||||
role: "admin",
|
||||
auth_disabled: true,
|
||||
};
|
||||
|
||||
@@ -48,11 +53,11 @@ export async function fetchAuthSession(): Promise<AuthSession> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function login(passphrase: string): Promise<AuthSession> {
|
||||
export async function login(username: string, password: string): Promise<AuthSession> {
|
||||
const response = await fetch("/auth/login", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ passphrase }),
|
||||
body: JSON.stringify({ username, password }),
|
||||
});
|
||||
if (response.status === 404) return authDisabledSession;
|
||||
if (!response.ok) {
|
||||
@@ -62,6 +67,41 @@ export async function login(passphrase: string): Promise<AuthSession> {
|
||||
return decodeAuthSession(await response.json());
|
||||
}
|
||||
|
||||
export interface ManagedUser { username: string; role: AuthRole }
|
||||
|
||||
async function userRequest(path: string, init?: RequestInit): Promise<Response> {
|
||||
const response = await fetch(path, init);
|
||||
if (!response.ok) {
|
||||
const payload = await response.json().catch(() => ({})) as { error?: string };
|
||||
throw new Error(payload.error || `User operation failed (${response.status})`);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
export async function listUsers(): Promise<ManagedUser[]> {
|
||||
const value: unknown = await userRequest("/auth/users").then(response => response.json());
|
||||
if (!Array.isArray(value) || !value.every((user: unknown) => {
|
||||
if (typeof user !== "object" || user === null) return false;
|
||||
const record = user as Record<string, unknown>;
|
||||
return typeof record.username === "string" && (record.role === "user" || record.role === "admin");
|
||||
})) {
|
||||
throw new TypeError("The user list response is malformed");
|
||||
}
|
||||
return value as ManagedUser[];
|
||||
}
|
||||
|
||||
export async function createUser(username: string, password: string, role: AuthRole): Promise<void> {
|
||||
await userRequest("/auth/users", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username, password, role }) });
|
||||
}
|
||||
|
||||
export async function updateUser(username: string, changes: { password?: string; role?: AuthRole }): Promise<void> {
|
||||
await userRequest(`/auth/users/${encodeURIComponent(username)}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(changes) });
|
||||
}
|
||||
|
||||
export async function deleteUser(username: string): Promise<void> {
|
||||
await userRequest(`/auth/users/${encodeURIComponent(username)}`, { method: "DELETE" });
|
||||
}
|
||||
|
||||
export async function logout(): Promise<void> {
|
||||
const response = await fetch("/auth/logout", { method: "POST" });
|
||||
if (response.status !== 404 && !response.ok) throw new Error("Logout failed");
|
||||
|
||||
@@ -21,6 +21,10 @@ import {
|
||||
fetchAuthSession,
|
||||
login,
|
||||
logout,
|
||||
listUsers,
|
||||
createUser,
|
||||
updateUser,
|
||||
deleteUser,
|
||||
} from "./api/auth.js";
|
||||
import {
|
||||
formatByteSize as recorderFormatSize,
|
||||
@@ -407,39 +411,38 @@ void loadDecoderRegistry(refreshOperatorLayoutCapabilities);
|
||||
|
||||
// --- Authentication ---
|
||||
let authRole: AuthRole | null = null;
|
||||
let authUsername: string | null = null;
|
||||
let authEnabled = true;
|
||||
|
||||
async function checkAuthStatus() {
|
||||
return fetchAuthSession();
|
||||
}
|
||||
|
||||
async function authLogin(passphrase: string) {
|
||||
return login(passphrase);
|
||||
async function authLogin(username: string, password: string) {
|
||||
return login(username, password);
|
||||
}
|
||||
|
||||
async function authLogout() {
|
||||
try {
|
||||
await logout();
|
||||
authRole = null;
|
||||
authUsername = null;
|
||||
// Disconnect and show auth gate without page reload
|
||||
disconnect();
|
||||
setDecodeHistoryOverlayVisible(false);
|
||||
requiredElement("content").style.display = "none";
|
||||
requiredElement("loading").style.display = "none";
|
||||
requiredElement<HTMLInputElement>("auth-passphrase").value = "";
|
||||
requiredElement<HTMLInputElement>("auth-password").value = "";
|
||||
updateAuthUI();
|
||||
|
||||
// Check if guest mode is available after logout
|
||||
const authStatus = await checkAuthStatus();
|
||||
const allowGuest = authStatus.role === "rx";
|
||||
showAuthGate(allowGuest);
|
||||
showAuthGate();
|
||||
} catch (e) {
|
||||
console.error("Logout failed:", e);
|
||||
showAuthError("Logout failed");
|
||||
}
|
||||
}
|
||||
|
||||
function showAuthGate(allowGuest = false) {
|
||||
function showAuthGate() {
|
||||
if (!authEnabled) return;
|
||||
setDecodeHistoryOverlayVisible(false);
|
||||
requiredElement("loading").style.display = "none";
|
||||
@@ -459,12 +462,6 @@ function showAuthGate(allowGuest = false) {
|
||||
panel.style.display = "none";
|
||||
});
|
||||
|
||||
// Show guest button if guest mode is available
|
||||
const guestBtn = document.getElementById("auth-guest-btn");
|
||||
if (guestBtn) {
|
||||
guestBtn.style.display = allowGuest ? "block" : "none";
|
||||
}
|
||||
|
||||
document.querySelectorAll<HTMLElement>(".tab-bar .tab").forEach((btn) => {
|
||||
btn.classList.toggle("active", btn.dataset.tab === "main");
|
||||
});
|
||||
@@ -516,7 +513,7 @@ function updateAuthUI() {
|
||||
|
||||
if (authRole) {
|
||||
if (badge) badge.style.display = "block";
|
||||
if (badgeRole) badgeRole.textContent = authRole === "control" ? "Control (full access)" : "RX (read-only)";
|
||||
if (badgeRole) badgeRole.textContent = `${authUsername || "local"} — ${authRole === "admin" ? "Admin" : "User (read-only)"}`;
|
||||
if (headerAuthBtn) {
|
||||
headerAuthBtn.textContent = "Logout";
|
||||
headerAuthBtn.style.display = "block";
|
||||
@@ -534,8 +531,8 @@ function updateAuthUI() {
|
||||
function applyAuthRestrictions() {
|
||||
if (!authRole) return;
|
||||
|
||||
// Disable TX/PTT/frequency/mode/VFO controls for rx role
|
||||
if (authRole === "rx") {
|
||||
// Disable TX/PTT/frequency/mode/VFO controls for user role
|
||||
if (authRole === "user") {
|
||||
const pttBtn = document.getElementById("ptt-btn") as HTMLButtonElement | null;
|
||||
const powerBtn = document.getElementById("power-btn") as HTMLButtonElement | null;
|
||||
const lockBtn = document.getElementById("lock-btn") as HTMLButtonElement | null;
|
||||
@@ -883,7 +880,7 @@ function syncTopBarAccess() {
|
||||
}
|
||||
|
||||
if (headerRigSwitchSelect) {
|
||||
headerRigSwitchSelect.disabled = loggedOut || authRole === "rx" || lastRigIds.length === 0;
|
||||
headerRigSwitchSelect.disabled = loggedOut || authRole === "user" || lastRigIds.length === 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1464,7 +1461,7 @@ function applyRigList(activeRigId: string | null, rigIds: string[], displayNames
|
||||
}
|
||||
const nextKey = lastRigIds.join("\0") + "|" + (lastActiveRigId || "");
|
||||
const rigListChanged = prevKey !== nextKey;
|
||||
const disableSwitch = lastRigIds.length === 0 || !authRole || authRole === "rx";
|
||||
const disableSwitch = lastRigIds.length === 0 || !authRole || authRole === "user";
|
||||
populateRigPicker(headerRigSwitchSelect, lastRigIds, lastActiveRigId, disableSwitch);
|
||||
updateRigSubtitle(lastActiveRigId);
|
||||
window.trxUi?.setActiveRig(lastActiveRigId);
|
||||
@@ -3352,7 +3349,7 @@ function scheduleTuneLinkSync() {
|
||||
async function applyTuneLink(link: TuneLink) {
|
||||
const wanted = link.rig || link.mode || link.freqHz != null || link.bandwidthHz != null;
|
||||
if (!wanted) return;
|
||||
if (authRole === "rx") {
|
||||
if (authRole === "user") {
|
||||
showHint("Read-only session — link not applied", 2500);
|
||||
return;
|
||||
}
|
||||
@@ -4191,8 +4188,8 @@ async function switchRigFromSelect(selectEl: HTMLSelectElement) {
|
||||
showHint("No rig selected", 1500);
|
||||
return;
|
||||
}
|
||||
if (authRole === "rx") {
|
||||
showHint("Control role required", 1500);
|
||||
if (authRole === "user") {
|
||||
showHint("Admin role required", 1500);
|
||||
return;
|
||||
}
|
||||
if (!lastRigIds.includes(selectEl.value)) {
|
||||
@@ -4875,7 +4872,7 @@ function navigateToTab(name: TabName, options: { updateHistory?: boolean; replac
|
||||
const leavingSatellites = _activeTab === "satellites" && name !== "satellites";
|
||||
const { updateHistory = true, replaceHistory = false } = options;
|
||||
if (authEnabled && !authRole && name !== "main") {
|
||||
showAuthGate(false);
|
||||
showAuthGate();
|
||||
return;
|
||||
}
|
||||
const btn = document.querySelector<HTMLElement>(`.tab-bar .tab[data-tab="${name}"]`);
|
||||
@@ -5008,12 +5005,12 @@ window.addEventListener("resize", () => { scheduleSpectrumLayout(); });
|
||||
|
||||
// --- Auth startup sequence ---
|
||||
async function initializeApp() {
|
||||
showAuthGate(false);
|
||||
showAuthGate();
|
||||
const authStatus = await checkAuthStatus();
|
||||
authEnabled = !authStatus.auth_disabled;
|
||||
|
||||
if (!authEnabled) {
|
||||
authRole = "control";
|
||||
authRole = "admin";
|
||||
hideAuthGate();
|
||||
updateAuthUI();
|
||||
connect();
|
||||
@@ -5027,6 +5024,7 @@ async function initializeApp() {
|
||||
if (authStatus.authenticated) {
|
||||
// User has valid session
|
||||
authRole = authStatus.role ?? null;
|
||||
authUsername = authStatus.username ?? null;
|
||||
hideAuthGate();
|
||||
updateAuthUI();
|
||||
applyAuthRestrictions();
|
||||
@@ -5036,10 +5034,7 @@ async function initializeApp() {
|
||||
resizeHeaderSignalCanvas();
|
||||
startHeaderSignalSampling();
|
||||
} else {
|
||||
// No valid session - show auth gate
|
||||
// Guest button is shown if guest mode is available (role granted without auth)
|
||||
const allowGuest = authStatus.role === "rx";
|
||||
showAuthGate(allowGuest);
|
||||
showAuthGate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5055,22 +5050,96 @@ function initSettingsUI() {
|
||||
window.trx.modules.backgroundDecode.initialize(lastActiveRigId, authRole);
|
||||
window.trx.modules.backgroundDecode.wireEvents();
|
||||
}
|
||||
void refreshUserManagement();
|
||||
}
|
||||
|
||||
async function refreshUserManagement() {
|
||||
const section = document.getElementById("user-management");
|
||||
if (!section) return;
|
||||
section.style.display = authEnabled && authRole === "admin" ? "block" : "none";
|
||||
if (section.style.display === "none") return;
|
||||
const list = requiredElement("user-list");
|
||||
try {
|
||||
const users = await listUsers();
|
||||
list.replaceChildren(...users.map((user) => {
|
||||
const row = document.createElement("div");
|
||||
row.className = "sch-row";
|
||||
row.style.cssText = "display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;margin:.4rem 0";
|
||||
const name = document.createElement("strong");
|
||||
name.textContent = user.username;
|
||||
name.style.minWidth = "10rem";
|
||||
const role = document.createElement("select");
|
||||
role.className = "auth-input";
|
||||
for (const value of ["user", "admin"] as AuthRole[]) {
|
||||
const option = document.createElement("option"); option.value = value; option.textContent = value === "admin" ? "Admin" : "User"; option.selected = user.role === value; role.append(option);
|
||||
}
|
||||
const password = document.createElement("input");
|
||||
password.type = "password"; password.placeholder = "New password"; password.autocomplete = "new-password"; password.className = "auth-input"; password.minLength = 8;
|
||||
const save = document.createElement("button"); save.type = "button"; save.textContent = "Save";
|
||||
save.addEventListener("click", async () => {
|
||||
const changes: { role?: AuthRole; password?: string } = { role: role.value as AuthRole };
|
||||
if (password.value) changes.password = password.value;
|
||||
await runUserOperation(() => updateUser(user.username, changes));
|
||||
});
|
||||
const remove = document.createElement("button"); remove.type = "button"; remove.textContent = "Remove"; remove.className = "danger";
|
||||
remove.disabled = user.username === authUsername;
|
||||
remove.addEventListener("click", async () => {
|
||||
if (await window.trxUi.confirm({ title: "Remove user?", message: `Remove ${user.username} and revoke their sessions?`, confirmLabel: "Remove", danger: true })) {
|
||||
await runUserOperation(() => deleteUser(user.username));
|
||||
}
|
||||
});
|
||||
row.append(name, role, password, save, remove);
|
||||
return row;
|
||||
}));
|
||||
} catch (error) {
|
||||
showUserManagementError(error);
|
||||
}
|
||||
}
|
||||
|
||||
function showUserManagementError(error: unknown) {
|
||||
const element = document.getElementById("user-management-error");
|
||||
if (!element) return;
|
||||
element.textContent = error instanceof Error ? error.message : String(error);
|
||||
element.style.display = "block";
|
||||
}
|
||||
|
||||
async function runUserOperation(operation: () => Promise<void>) {
|
||||
try {
|
||||
await operation();
|
||||
const error = document.getElementById("user-management-error");
|
||||
if (error) error.style.display = "none";
|
||||
await refreshUserManagement();
|
||||
} catch (reason) {
|
||||
showUserManagementError(reason);
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("user-create-form")?.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
const username = requiredElement<HTMLInputElement>("user-create-username");
|
||||
const password = requiredElement<HTMLInputElement>("user-create-password");
|
||||
const role = requiredElement<HTMLSelectElement>("user-create-role");
|
||||
void runUserOperation(async () => {
|
||||
await createUser(username.value, password.value, role.value as AuthRole);
|
||||
username.value = ""; password.value = ""; role.value = "user";
|
||||
});
|
||||
});
|
||||
|
||||
// Setup auth form
|
||||
requiredElement<HTMLFormElement>("auth-form").addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
const passphraseEl = requiredElement<HTMLInputElement>("auth-passphrase");
|
||||
const passphrase = passphraseEl.value;
|
||||
const usernameEl = requiredElement<HTMLInputElement>("auth-username");
|
||||
const passwordEl = requiredElement<HTMLInputElement>("auth-password");
|
||||
const btn = requiredElement<HTMLFormElement>("auth-form").querySelector<HTMLButtonElement>("button[type=submit]");
|
||||
if (!btn) return;
|
||||
btn.disabled = true;
|
||||
btn.textContent = "Logging in...";
|
||||
|
||||
try {
|
||||
const result = await authLogin(passphrase);
|
||||
const result = await authLogin(usernameEl.value, passwordEl.value);
|
||||
authRole = result.role ?? null;
|
||||
passphraseEl.value = "";
|
||||
authUsername = result.username ?? usernameEl.value;
|
||||
passwordEl.value = "";
|
||||
hideAuthGate();
|
||||
updateAuthUI();
|
||||
applyAuthRestrictions();
|
||||
@@ -5080,7 +5149,7 @@ requiredElement<HTMLFormElement>("auth-form").addEventListener("submit", async (
|
||||
resizeHeaderSignalCanvas();
|
||||
startHeaderSignalSampling();
|
||||
} catch (err) {
|
||||
showAuthError("Invalid passphrase");
|
||||
showAuthError("Invalid username or password");
|
||||
console.error("Login error:", err);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
@@ -5088,23 +5157,6 @@ requiredElement<HTMLFormElement>("auth-form").addEventListener("submit", async (
|
||||
}
|
||||
});
|
||||
|
||||
// Setup guest button
|
||||
const guestBtn = document.getElementById("auth-guest-btn") as HTMLButtonElement | null;
|
||||
if (guestBtn) {
|
||||
guestBtn.addEventListener("click", () => {
|
||||
authRole = "rx";
|
||||
requiredElement<HTMLInputElement>("auth-passphrase").value = "";
|
||||
hideAuthGate();
|
||||
updateAuthUI();
|
||||
applyAuthRestrictions();
|
||||
connect();
|
||||
connectDecode();
|
||||
initSettingsUI();
|
||||
resizeHeaderSignalCanvas();
|
||||
startHeaderSignalSampling();
|
||||
});
|
||||
}
|
||||
|
||||
// Setup header auth button (Login/Logout)
|
||||
const headerAuthBtn = document.getElementById("header-auth-btn") as HTMLButtonElement | null;
|
||||
if (headerAuthBtn) {
|
||||
@@ -5116,7 +5168,7 @@ if (headerAuthBtn) {
|
||||
}
|
||||
} else {
|
||||
// Not logged in - show auth gate
|
||||
showAuthGate(false);
|
||||
showAuthGate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+1
-1
@@ -468,7 +468,7 @@ const bgdWindow = window as unknown as BackgroundBridge;
|
||||
}
|
||||
|
||||
function isControlRole(): boolean {
|
||||
return backgroundDecodeRole === "control" || hostState.authEnabled === false;
|
||||
return backgroundDecodeRole === "admin" || hostState.authEnabled === false;
|
||||
}
|
||||
|
||||
function showToast(msg: string, isError: boolean): void {
|
||||
|
||||
@@ -100,7 +100,7 @@ function bmEsc(str: unknown): string {
|
||||
}
|
||||
|
||||
function bmCanControl() {
|
||||
return !hostState.authEnabled || hostState.authRole === "control";
|
||||
return !hostState.authEnabled || hostState.authRole === "admin";
|
||||
}
|
||||
|
||||
// Show/hide the Add Bookmark / Select All buttons based on the current auth role.
|
||||
|
||||
@@ -356,7 +356,7 @@ function schedulerOptionalEl(id: string): SchedulerElement | null {
|
||||
if (!prevBtn || !nextBtn) return;
|
||||
const state = schedulerInterleaveState(currentConfig);
|
||||
const enabled =
|
||||
schedulerRole === "control" &&
|
||||
schedulerRole === "admin" &&
|
||||
!!currentRigId &&
|
||||
!schedulerStepPending &&
|
||||
state.activeEntries.length > 1;
|
||||
@@ -466,7 +466,7 @@ function schedulerOptionalEl(id: string): SchedulerElement | null {
|
||||
if (!panel) return;
|
||||
|
||||
const mode = (currentConfig && currentConfig.mode) || "disabled";
|
||||
const isControl = schedulerRole === "control";
|
||||
const isControl = schedulerRole === "admin";
|
||||
|
||||
// Mode selector
|
||||
setSelected("scheduler-mode-select", mode);
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ test("background decode loads configuration for the explicitly selected rig", as
|
||||
const source = await bundleEntry(new URL("../src/plugins/background-decode.ts", import.meta.url));
|
||||
new vm.Script(source).runInContext(context);
|
||||
|
||||
window.trx.modules.backgroundDecode.initialize("rig/a", "control");
|
||||
window.trx.modules.backgroundDecode.initialize("rig/a", "admin");
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
assert.ok(requested.includes("/background-decode/rig%2Fa"));
|
||||
assert.ok(requested.includes("/bookmarks"));
|
||||
|
||||
@@ -32,7 +32,7 @@ function hostFixture(overrides = {}) {
|
||||
const calls = { postPath: [], setRigFrequency: [], armOptimisticFrequency: [], applyLocalTunedFrequency: [], syncBandwidthInput: [], scheduleSpectrumDraw: 0, syncModePicker: 0 };
|
||||
const state = {
|
||||
authEnabled: false,
|
||||
authRole: "control",
|
||||
authRole: "admin",
|
||||
lastActiveRigId: null,
|
||||
lastRigIds: [],
|
||||
lastRigDisplayNames: {},
|
||||
@@ -157,7 +157,7 @@ test("bookmark controls follow the host authentication state", async () => {
|
||||
if (!elements.has(id)) elements.set(id, new ElementFixture());
|
||||
return elements.get(id);
|
||||
};
|
||||
const { window } = hostFixture({ authEnabled: true, authRole: "rx" });
|
||||
const { window } = hostFixture({ authEnabled: true, authRole: "user" });
|
||||
const context = vm.createContext({
|
||||
window,
|
||||
document: documentFixture(element),
|
||||
@@ -171,7 +171,7 @@ test("bookmark controls follow the host authentication state", async () => {
|
||||
|
||||
assert.equal(element("bm-add-btn").style.display, "none");
|
||||
|
||||
window.trx.state.authRole = "control";
|
||||
window.trx.state.authRole = "admin";
|
||||
await window.trx.modules.bookmarks.fetch("");
|
||||
assert.equal(element("bm-add-btn").style.display, "");
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@ export function createHost({ state = {}, core = {}, modules = {} } = {}) {
|
||||
serverLat: null,
|
||||
serverLon: null,
|
||||
authEnabled: false,
|
||||
authRole: "control",
|
||||
authRole: "admin",
|
||||
lastActiveRigId: null,
|
||||
lastRigIds: [],
|
||||
lastRigDisplayNames: {},
|
||||
|
||||
@@ -76,7 +76,7 @@ test("scheduler self-initializes for the active rig when a role is already known
|
||||
return elements.get(id);
|
||||
};
|
||||
const window = {
|
||||
...createHost({ state: { authRole: "control", lastActiveRigId: "sdr" } }),
|
||||
...createHost({ state: { authRole: "admin", lastActiveRigId: "sdr" } }),
|
||||
trxUi: { confirm: async () => true },
|
||||
};
|
||||
const context = vm.createContext({
|
||||
|
||||
@@ -232,7 +232,7 @@ export async function startWebFixture({
|
||||
};
|
||||
|
||||
const jsonRoutes = new Map([
|
||||
["/auth/session", { authenticated: true, role: "control", auth_disabled: true }],
|
||||
["/auth/session", { authenticated: true, role: "admin", auth_disabled: true }],
|
||||
["/decoders", DECODER_REGISTRY],
|
||||
["/rigs", rigsResponse],
|
||||
["/status", status],
|
||||
|
||||
Reference in New Issue
Block a user