[style](trx-frontend-http): make bookmark decoders selectable; gate add on control role
Replace the free-text decoder input with FT8/WSPR checkboxes so users cannot enter arbitrary decoder strings. Hide the "+ Add Bookmark" button by default and show it only when the authenticated role is `control` (or auth is disabled), matching the server-side write-endpoint guard. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -266,7 +266,7 @@
|
|||||||
<select id="bm-category-filter" class="status-input" aria-label="Filter by category">
|
<select id="bm-category-filter" class="status-input" aria-label="Filter by category">
|
||||||
<option value="">All categories</option>
|
<option value="">All categories</option>
|
||||||
</select>
|
</select>
|
||||||
<button id="bm-add-btn" type="button" class="bm-add-btn">+ Add Bookmark</button>
|
<button id="bm-add-btn" type="button" class="bm-add-btn" style="display:none;">+ Add Bookmark</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="bm-form-wrap" style="display:none;">
|
<div id="bm-form-wrap" style="display:none;">
|
||||||
<form id="bm-form" class="bm-form">
|
<form id="bm-form" class="bm-form">
|
||||||
@@ -297,9 +297,12 @@
|
|||||||
<label class="bm-label">Bandwidth (Hz)
|
<label class="bm-label">Bandwidth (Hz)
|
||||||
<input type="number" id="bm-bw" class="status-input" min="0" placeholder="optional" />
|
<input type="number" id="bm-bw" class="status-input" min="0" placeholder="optional" />
|
||||||
</label>
|
</label>
|
||||||
<label class="bm-label">Decoders (comma-separated)
|
<div class="bm-label">Decoders
|
||||||
<input type="text" id="bm-decoders-input" class="status-input" placeholder="e.g. ft8, wspr" />
|
<div class="bm-decoder-checks">
|
||||||
</label>
|
<label class="bm-decoder-check"><input type="checkbox" id="bm-dec-ft8" value="ft8" /> FT8</label>
|
||||||
|
<label class="bm-decoder-check"><input type="checkbox" id="bm-dec-wspr" value="wspr" /> WSPR</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<label class="bm-label bm-label-wide">Comment
|
<label class="bm-label bm-label-wide">Comment
|
||||||
<input type="text" id="bm-comment" class="status-input" placeholder="optional" />
|
<input type="text" id="bm-comment" class="status-input" placeholder="optional" />
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -17,6 +17,19 @@ function bmEsc(str) {
|
|||||||
return d.innerHTML;
|
return d.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function bmCanControl() {
|
||||||
|
return (
|
||||||
|
(typeof authEnabled !== "undefined" && !authEnabled) ||
|
||||||
|
(typeof authRole !== "undefined" && authRole === "control")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show/hide the Add Bookmark button based on the current auth role.
|
||||||
|
function bmSyncAccess() {
|
||||||
|
const addBtn = document.getElementById("bm-add-btn");
|
||||||
|
if (addBtn) addBtn.style.display = bmCanControl() ? "" : "none";
|
||||||
|
}
|
||||||
|
|
||||||
async function bmFetch(categoryFilter) {
|
async function bmFetch(categoryFilter) {
|
||||||
let url = "/bookmarks";
|
let url = "/bookmarks";
|
||||||
if (categoryFilter && categoryFilter !== "") {
|
if (categoryFilter && categoryFilter !== "") {
|
||||||
@@ -30,6 +43,7 @@ async function bmFetch(categoryFilter) {
|
|||||||
console.error("Failed to fetch bookmarks:", e);
|
console.error("Failed to fetch bookmarks:", e);
|
||||||
bmList = [];
|
bmList = [];
|
||||||
}
|
}
|
||||||
|
bmSyncAccess();
|
||||||
bmRender(bmList);
|
bmRender(bmList);
|
||||||
bmRefreshCategoryFilter(categoryFilter);
|
bmRefreshCategoryFilter(categoryFilter);
|
||||||
}
|
}
|
||||||
@@ -65,17 +79,14 @@ function bmRender(list) {
|
|||||||
}
|
}
|
||||||
if (emptyEl) emptyEl.style.display = "none";
|
if (emptyEl) emptyEl.style.display = "none";
|
||||||
|
|
||||||
// canControl: auth is disabled, or user has control role
|
const canControl = bmCanControl();
|
||||||
const canControl =
|
|
||||||
(typeof authEnabled !== "undefined" && !authEnabled) ||
|
|
||||||
(typeof authRole !== "undefined" && authRole === "control");
|
|
||||||
|
|
||||||
list.forEach((bm) => {
|
list.forEach((bm) => {
|
||||||
const tr = document.createElement("tr");
|
const tr = document.createElement("tr");
|
||||||
tr.dataset.bmId = bm.id;
|
tr.dataset.bmId = bm.id;
|
||||||
const bwCell = bm.bandwidth_hz ? bmFmtFreq(bm.bandwidth_hz) : "--";
|
const bwCell = bm.bandwidth_hz ? bmFmtFreq(bm.bandwidth_hz) : "--";
|
||||||
const catCell = bm.category || "Uncategorised";
|
const catCell = bm.category || "Uncategorised";
|
||||||
const decoderCell = (bm.decoders || []).join(", ") || "--";
|
const decoderCell = (bm.decoders || []).join(", ").toUpperCase() || "--";
|
||||||
const commentCell = bm.comment || "";
|
const commentCell = bm.comment || "";
|
||||||
tr.innerHTML =
|
tr.innerHTML =
|
||||||
`<td class="bm-col-name">${bmEsc(bm.name)}</td>` +
|
`<td class="bm-col-name">${bmEsc(bm.name)}</td>` +
|
||||||
@@ -96,6 +107,21 @@ function bmRender(list) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read decoder checkboxes and return an array of selected decoder names.
|
||||||
|
function bmReadDecoders() {
|
||||||
|
const decoders = [];
|
||||||
|
if (document.getElementById("bm-dec-ft8").checked) decoders.push("ft8");
|
||||||
|
if (document.getElementById("bm-dec-wspr").checked) decoders.push("wspr");
|
||||||
|
return decoders;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set decoder checkboxes to match the given array.
|
||||||
|
function bmWriteDecoders(decoders) {
|
||||||
|
const list = decoders || [];
|
||||||
|
document.getElementById("bm-dec-ft8").checked = list.includes("ft8");
|
||||||
|
document.getElementById("bm-dec-wspr").checked = list.includes("wspr");
|
||||||
|
}
|
||||||
|
|
||||||
function bmOpenForm(bm) {
|
function bmOpenForm(bm) {
|
||||||
const wrap = document.getElementById("bm-form-wrap");
|
const wrap = document.getElementById("bm-form-wrap");
|
||||||
if (!wrap) return;
|
if (!wrap) return;
|
||||||
@@ -108,7 +134,7 @@ function bmOpenForm(bm) {
|
|||||||
document.getElementById("bm-bw").value = bm && bm.bandwidth_hz ? bm.bandwidth_hz : "";
|
document.getElementById("bm-bw").value = bm && bm.bandwidth_hz ? bm.bandwidth_hz : "";
|
||||||
document.getElementById("bm-category-input").value = bm ? (bm.category || "") : "";
|
document.getElementById("bm-category-input").value = bm ? (bm.category || "") : "";
|
||||||
document.getElementById("bm-comment").value = bm ? (bm.comment || "") : "";
|
document.getElementById("bm-comment").value = bm ? (bm.comment || "") : "";
|
||||||
document.getElementById("bm-decoders-input").value = bm ? (bm.decoders || []).join(", ") : "";
|
bmWriteDecoders(bm ? bm.decoders : []);
|
||||||
document.getElementById("bm-form-title").textContent = bm ? "Edit Bookmark" : "Add Bookmark";
|
document.getElementById("bm-form-title").textContent = bm ? "Edit Bookmark" : "Add Bookmark";
|
||||||
|
|
||||||
wrap.style.display = "";
|
wrap.style.display = "";
|
||||||
@@ -145,11 +171,7 @@ async function bmSave(e) {
|
|||||||
const bandwidth_hz = bwStr ? parseInt(bwStr, 10) : null;
|
const bandwidth_hz = bwStr ? parseInt(bwStr, 10) : null;
|
||||||
const category = document.getElementById("bm-category-input").value.trim();
|
const category = document.getElementById("bm-category-input").value.trim();
|
||||||
const comment = document.getElementById("bm-comment").value.trim();
|
const comment = document.getElementById("bm-comment").value.trim();
|
||||||
const decoderStr = document.getElementById("bm-decoders-input").value;
|
const decoders = bmReadDecoders();
|
||||||
const decoders = decoderStr
|
|
||||||
.split(",")
|
|
||||||
.map((s) => s.trim())
|
|
||||||
.filter(Boolean);
|
|
||||||
|
|
||||||
if (!name || !Number.isFinite(freq_hz) || !mode) {
|
if (!name || !Number.isFinite(freq_hz) || !mode) {
|
||||||
alert("Name, Frequency, and Mode are required.");
|
alert("Name, Frequency, and Mode are required.");
|
||||||
@@ -228,7 +250,11 @@ async function bmApply(bm) {
|
|||||||
|
|
||||||
// --- Event wiring ---
|
// --- Event wiring ---
|
||||||
(function initBookmarks() {
|
(function initBookmarks() {
|
||||||
// Refresh list when the Bookmarks tab is activated
|
// Set initial button visibility (auth may already be resolved by the time
|
||||||
|
// scripts run if auth is disabled; otherwise bmFetch() will sync it).
|
||||||
|
bmSyncAccess();
|
||||||
|
|
||||||
|
// Refresh list and sync access when the Bookmarks tab is activated
|
||||||
document.querySelector(".tab-bar").addEventListener("click", (e) => {
|
document.querySelector(".tab-bar").addEventListener("click", (e) => {
|
||||||
const btn = e.target.closest('.tab[data-tab="bookmarks"]');
|
const btn = e.target.closest('.tab[data-tab="bookmarks"]');
|
||||||
if (!btn) return;
|
if (!btn) return;
|
||||||
|
|||||||
Reference in New Issue
Block a user