[feat](trx-frontend-http): separate transmit permission
CI / frontend (pull_request) Successful in 5m21s
CI / lint (pull_request) Successful in 2m24s
CI / test (pull_request) Successful in 9m11s
CI / test (push) Successful in 8m16s
CI / frontend (push) Successful in 4m24s
CI / reuse (push) Successful in 5s
CI / reuse (pull_request) Successful in 5s
CI / lint (push) Successful in 2m25s

Assisted-By: Codex (GPT-5)
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit was merged in pull request #64.
This commit is contained in:
sjg
2026-08-11 18:36:33 +02:00
parent 44870bc941
commit e978cf8a84
22 changed files with 217 additions and 62 deletions
@@ -13,7 +13,7 @@ import {
logout,
normalizeAuthRoles,
updateUser
} from "./chunk-FT2RH7BL.js";
} from "./chunk-PISLBJGN.js";
// src/webgl-renderer.ts
(function initTrxWebGl(global) {
@@ -1972,30 +1972,33 @@ function updateAuthUI() {
}
function applyAuthRestrictions() {
if (authRoles.length === 0) return;
if (!hasAuthRole2("control")) {
if (!hasAuthRole2("transmit")) {
const pttBtn2 = document.getElementById("ptt-btn");
const txLimitInput2 = document.getElementById("tx-limit");
const txLimitBtn2 = document.getElementById("tx-limit-btn");
const txAudioBtn2 = document.getElementById("tx-audio-btn");
const txLimitRow2 = document.getElementById("tx-limit-row");
if (pttBtn2) pttBtn2.disabled = true;
if (txAudioBtn2) txAudioBtn2.disabled = true;
if (txLimitBtn2) txLimitBtn2.disabled = true;
if (txLimitInput2) txLimitInput2.disabled = true;
if (txLimitRow2) txLimitRow2.style.opacity = "0.5";
}
if (!hasAuthRole2("control")) {
const powerBtn2 = document.getElementById("power-btn");
const lockBtn2 = document.getElementById("lock-btn");
const freqInput = document.getElementById("freq");
const centerFreqInput = document.getElementById("center-freq");
const modeSelect = document.getElementById("mode");
const txLimitInput2 = document.getElementById("tx-limit");
const txLimitBtn2 = document.getElementById("tx-limit-btn");
const txAudioBtn2 = document.getElementById("tx-audio-btn");
const txLimitRow2 = document.getElementById("tx-limit-row");
const jogUp = document.getElementById("jog-up");
const jogDown = document.getElementById("jog-down");
const jogButtons = document.querySelectorAll(".jog-step button");
const vfoButtons = document.querySelectorAll("#vfo-picker button");
if (pttBtn2) pttBtn2.disabled = true;
if (powerBtn2) powerBtn2.disabled = true;
if (lockBtn2) lockBtn2.disabled = true;
if (txAudioBtn2) txAudioBtn2.disabled = true;
if (txLimitBtn2) txLimitBtn2.disabled = true;
if (freqInput) freqInput.disabled = true;
if (centerFreqInput) centerFreqInput.disabled = true;
if (modeSelect) modeSelect.disabled = true;
if (txLimitInput2) txLimitInput2.disabled = true;
vfoButtons.forEach((btn) => btn.disabled = true);
const jogWheel2 = document.getElementById("jog-wheel");
if (jogUp) jogUp.disabled = true;
@@ -2032,7 +2035,6 @@ function applyAuthRestrictions() {
btn.disabled = true;
}
});
if (txLimitRow2) txLimitRow2.style.opacity = "0.5";
}
}
function applyCapabilities(caps) {
@@ -4250,9 +4252,16 @@ function formatSignal(sUnits) {
return overDb === 0 ? `${sigUnit("S")}9` : `${sigUnit("S")}9+${overDb}${sigUnit("dB")}`;
}
function setDisabled(disabled) {
[freqEl, centerFreqEl, modeEl, pttBtn, powerBtn, txLimitInput, txLimitBtn, lockBtn].forEach((el) => {
if (el) el.disabled = disabled;
const controlDisabled = disabled || authEnabled && !hasAuthRole2("control");
const transmitDisabled = disabled || authEnabled && !hasAuthRole2("transmit");
[freqEl, centerFreqEl, modeEl, powerBtn, lockBtn].forEach((el) => {
if (el) el.disabled = controlDisabled;
});
[pttBtn, txLimitInput, txLimitBtn].forEach((el) => {
if (el) el.disabled = transmitDisabled;
});
const transmitAudio = document.getElementById("tx-audio-btn");
if (transmitAudio) transmitAudio.disabled = transmitDisabled || !hasWebCodecs;
syncModePicker();
}
var serverVersion = null;
@@ -7329,6 +7338,10 @@ function startTxAudio() {
void stopTxAudio();
return;
}
if (authEnabled && !hasAuthRole2("transmit")) {
audioStatus.textContent = "Transmit role required";
return;
}
if (!hasWebCodecs) {
audioStatus.textContent = "Audio requires Chrome/Edge";
return;
@@ -1,6 +1,6 @@
import {
hasAuthRole
} from "./chunk-FT2RH7BL.js";
} from "./chunk-PISLBJGN.js";
import {
hostState
} from "./chunk-KL66PICH.js";
@@ -1,6 +1,6 @@
import {
hasAuthRole
} from "./chunk-FT2RH7BL.js";
} from "./chunk-PISLBJGN.js";
import {
hostCore,
hostState
@@ -1,10 +1,11 @@
// src/api/auth.ts
var AUTH_ROLES = ["guest", "read", "control", "write", "administrator"];
var AUTH_ROLES = ["guest", "read", "control", "transmit", "write", "administrator"];
var AUTH_ADMIN_ROLES = AUTH_ROLES.filter((role) => role !== "guest");
var AUTH_ROLE_LABELS = {
guest: "Guest",
read: "Read",
control: "Control",
transmit: "Transmit",
write: "Write",
administrator: "Administrator"
};
@@ -15,7 +16,7 @@ function normalizeAuthRoles(roles) {
return AUTH_ROLES.filter((role) => roles.includes(role));
}
function hasAuthRole(roles, required) {
return roles.includes("administrator") || roles.includes(required) || required === "read" && roles.includes("guest") || required === "read" && roles.includes("control");
return roles.includes("administrator") || roles.includes(required) || required === "read" && roles.includes("guest") || required === "read" && (roles.includes("control") || roles.includes("transmit"));
}
function hasAccountControls(roles) {
return roles.length > 0 && !roles.includes("guest");
@@ -1,6 +1,6 @@
import {
hasAuthRole
} from "./chunk-FT2RH7BL.js";
} from "./chunk-PISLBJGN.js";
import {
hostCore,
hostState
@@ -1,6 +1,6 @@
import {
hasAuthRole
} from "./chunk-FT2RH7BL.js";
} from "./chunk-PISLBJGN.js";
import {
hostState
} from "./chunk-KL66PICH.js";