feat(wavelog): push station frequency/mode to Wavelog per operator

Add a CAT integration so the station's current frequency and mode are
reported to each operator's personal Wavelog logbook via Wavelog's radio
API (POST <baseUrl>/index.php/api/radio).

- New plugin rms.wavelog.cat (capability logging.cat.publish): posts
  {key, radio, frequency, mode}. Base URL / radio name come from plugin
  settings or env (WAVELOG_BASE_URL, WAVELOG_RADIO_NAME); no secrets in repo.
- Core hook in the OpenWebRX band-select flow pushes centerFreqHz + startMod
  for the active operator, best-effort (never breaks the band change).
- Per-user Wavelog API key stored on the user record; PUT /v1/me/wavelog-key
  to set it, returned by GET /v1/me and the OpenWebRX session issue response.
- UI: Wavelog API key field in the OpenWebRX panel (visible when generating
  the session link), with i18n (de/en).
- Docs, .env.example entries, and a unit test for the mode mapping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ARCG Admin (Claude)
2026-10-22 20:47:58 +02:00
parent 2bcf6ab1e5
commit 0488f6e25d
11 changed files with 308 additions and 2 deletions

View File

@@ -135,6 +135,9 @@ const els = {
openwebrxSessionLink: document.getElementById("openwebrxSessionLink"),
openwebrxCopyLinkBtn: document.getElementById("openwebrxCopyLinkBtn"),
openwebrxSessionTicket: document.getElementById("openwebrxSessionTicket"),
wavelogKeyInput: document.getElementById("wavelogKeyInput"),
wavelogKeySaveBtn: document.getElementById("wavelogKeySaveBtn"),
wavelogKeyMessage: document.getElementById("wavelogKeyMessage"),
openwebrxTxStatePill: document.getElementById("openwebrxTxStatePill"),
rotorCurrent: document.getElementById("rotorCurrent"),
rotorCompass: document.getElementById("rotorCompass"),
@@ -690,6 +693,11 @@ function bindEvents() {
await copyOpenWebRxLink();
});
}
if (els.wavelogKeySaveBtn) {
els.wavelogKeySaveBtn.addEventListener("click", async () => {
await saveWavelogKey();
});
}
if (els.openwebrxEnableTxBtn) {
els.openwebrxEnableTxBtn.addEventListener("click", async (event) => {
if (event) {
@@ -1496,6 +1504,8 @@ async function refreshCurrentUser() {
try {
const result = await api("/v1/me", { authRequired: true });
state.user = result.user;
state.wavelogApiKey = typeof result.wavelogApiKey === "string" ? result.wavelogApiKey : "";
applyWavelogKeyToInput();
renderUserSettingsAuthMethods();
await ensureLanguageFromUserPreference();
} catch {
@@ -1684,12 +1694,37 @@ async function cancelReservationByUserId(userId) {
}
}
function applyWavelogKeyToInput() {
if (els.wavelogKeyInput) {
els.wavelogKeyInput.value = String(state.wavelogApiKey || "");
}
}
async function saveWavelogKey() {
if (!els.wavelogKeyInput) {
return;
}
const apiKey = String(els.wavelogKeyInput.value || "").trim();
try {
const result = await api("/v1/me/wavelog-key", { method: "PUT", body: { apiKey } });
state.wavelogApiKey = result && typeof result.wavelogApiKey === "string" ? result.wavelogApiKey : apiKey;
applyWavelogKeyToInput();
renderMessage(els.wavelogKeyMessage, translateLiteral("Wavelog API Key gespeichert."), false);
} catch (error) {
renderMessage(els.wavelogKeyMessage, error.message, true);
}
}
async function openOpenWebRxSession() {
clearMessages("status");
setOpenWebRxBusy(true);
try {
const result = await requestOpenWebRxSessionWithRetry();
const session = result && result.session ? result.session : null;
if (result && typeof result.wavelogApiKey === "string") {
state.wavelogApiKey = result.wavelogApiKey;
applyWavelogKeyToInput();
}
const sessionTicket = session && session.ticket ? String(session.ticket) : "";
state.openWebRx.sessionUrl = buildOpenWebRxSessionUrl(session && session.iframeUrl ? session.iframeUrl : "", sessionTicket);
state.openWebRx.sessionTicket = sessionTicket;
@@ -1715,6 +1750,10 @@ async function openOpenWebRxExternal() {
try {
const result = await requestOpenWebRxSessionWithRetry();
const session = result && result.session ? result.session : null;
if (result && typeof result.wavelogApiKey === "string") {
state.wavelogApiKey = result.wavelogApiKey;
applyWavelogKeyToInput();
}
const sessionTicket = session && session.ticket ? String(session.ticket) : "";
const sessionUrl = buildOpenWebRxSessionUrl(session && session.iframeUrl ? String(session.iframeUrl) : "", sessionTicket);
if (!sessionUrl) {