feat: generate frontend status metadata contract
This commit is contained in:
@@ -1640,7 +1640,7 @@ async function responseJsonUnknown(response) {
|
|||||||
return await response.json();
|
return await response.json();
|
||||||
}
|
}
|
||||||
function isAppUpdate(value) {
|
function isAppUpdate(value) {
|
||||||
return isRecord2(value) && (value.status === void 0 || isRecord2(value.status));
|
return isRecord2(value) && typeof value.clients === "number" && typeof value.audio_clients === "number" && typeof value.rigctl_clients === "number" && Array.isArray(value.remotes) && value.remotes.every((remote) => typeof remote === "string") && typeof value.show_sdr_gain_control === "boolean" && typeof value.initial_map_zoom === "number" && typeof value.spectrum_coverage_margin_hz === "number" && typeof value.spectrum_usable_span_ratio === "number" && typeof value.bandplan_enabled === "boolean" && typeof value.bandplan_region === "string" && typeof value.decode_history_retention_min === "number" && typeof value.server_connected === "boolean" && isRigSnapshot(value);
|
||||||
}
|
}
|
||||||
function isAudioStreamInfo(value) {
|
function isAudioStreamInfo(value) {
|
||||||
return isRecord2(value) && typeof value.sample_rate === "number" && typeof value.channels === "number";
|
return isRecord2(value) && typeof value.sample_rate === "number" && typeof value.channels === "number";
|
||||||
@@ -4767,7 +4767,7 @@ async function pollFreshSnapshot() {
|
|||||||
const resp = await fetch(statusUrl, { cache: "no-store" });
|
const resp = await fetch(statusUrl, { cache: "no-store" });
|
||||||
if (!resp.ok) return;
|
if (!resp.ok) return;
|
||||||
const data = await responseJsonUnknown(resp);
|
const data = await responseJsonUnknown(resp);
|
||||||
if (!isRigSnapshot(data)) return;
|
if (!isAppUpdate(data)) return;
|
||||||
render(data);
|
render(data);
|
||||||
void refreshRigList();
|
void refreshRigList();
|
||||||
lastEventAt = Date.now();
|
lastEventAt = Date.now();
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ use trx_core::rig::{
|
|||||||
};
|
};
|
||||||
use trx_core::{DecoderConfig, RdsData, RigFilterState, RigMode, RigSnapshot, WfmDenoiseLevel};
|
use trx_core::{DecoderConfig, RdsData, RigFilterState, RigMode, RigSnapshot, WfmDenoiseLevel};
|
||||||
use trx_frontend_http::server::api::rig::{RigListItem, RigListResponse};
|
use trx_frontend_http::server::api::rig::{RigListItem, RigListResponse};
|
||||||
|
use trx_frontend_http::server::api::FrontendMeta;
|
||||||
use trx_protocol::{DecoderActivation, DecoderDescriptor};
|
use trx_protocol::{DecoderActivation, DecoderDescriptor};
|
||||||
use ts_rs::{Config, TS};
|
use ts_rs::{Config, TS};
|
||||||
|
|
||||||
@@ -53,6 +54,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
export!(RigSnapshot);
|
export!(RigSnapshot);
|
||||||
export!(RigListItem);
|
export!(RigListItem);
|
||||||
export!(RigListResponse);
|
export!(RigListResponse);
|
||||||
|
export!(FrontendMeta);
|
||||||
export!(DecoderActivation);
|
export!(DecoderActivation);
|
||||||
export!(DecoderDescriptor);
|
export!(DecoderDescriptor);
|
||||||
|
|
||||||
|
|||||||
@@ -117,6 +117,8 @@ export type RigListItem = { remote: string, display_name: string | null, manufac
|
|||||||
|
|
||||||
export type RigListResponse = { active_remote: string | null, rigs: Array<RigListItem>, };
|
export type RigListResponse = { active_remote: string | null, rigs: Array<RigListItem>, };
|
||||||
|
|
||||||
|
export type FrontendMeta = { clients: number, rigctl_clients: number, audio_clients: number, rigctl_addr: string | null, active_remote: string | null, remotes: Array<string>, owner_callsign: string | null, owner_website_url: string | null, owner_website_name: string | null, ais_vessel_url_base: string | null, show_sdr_gain_control: boolean, initial_map_zoom: number, spectrum_coverage_margin_hz: number, spectrum_usable_span_ratio: number, bandplan_enabled: boolean, bandplan_region: string, decode_history_retention_min: bigint, server_connected: boolean, };
|
||||||
|
|
||||||
export type DecoderActivation = "mode_bound" | "toggle";
|
export type DecoderActivation = "mode_bound" | "toggle";
|
||||||
|
|
||||||
export type DecoderDescriptor = {
|
export type DecoderDescriptor = {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ import {
|
|||||||
} from "./features/spectrum/math.js";
|
} from "./features/spectrum/math.js";
|
||||||
import type { NumericBins } from "./features/spectrum/math.js";
|
import type { NumericBins } from "./features/spectrum/math.js";
|
||||||
import type { AuthRole } from "./api/auth.js";
|
import type { AuthRole } from "./api/auth.js";
|
||||||
import type { RdsData, RigCapabilities, RigListItem, RigSnapshot, VchanRdsEntry } from "./api/generated.js";
|
import type { FrontendMeta, RdsData, RigCapabilities, RigListItem, RigSnapshot, VchanRdsEntry } from "./api/generated.js";
|
||||||
import type { TrxPluginRuntime } from "./plugins/runtime-contract.js";
|
import type { TrxPluginRuntime } from "./plugins/runtime-contract.js";
|
||||||
import { loadEagerPlugins, loadPluginsForTab } from "./plugin-loader.js";
|
import { loadEagerPlugins, loadPluginsForTab } from "./plugin-loader.js";
|
||||||
import { isRigListResponse, isRigSnapshot } from "./api/client.js";
|
import { isRigListResponse, isRigSnapshot } from "./api/client.js";
|
||||||
@@ -126,25 +126,8 @@ interface Bookmark {
|
|||||||
locator?: string | null;
|
locator?: string | null;
|
||||||
comment?: string | null;
|
comment?: string | null;
|
||||||
}
|
}
|
||||||
type AppUpdate = Partial<RigSnapshot> & {
|
type AppUpdate = RigSnapshot & FrontendMeta & {
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
owner_callsign?: string;
|
|
||||||
owner_website_url?: string;
|
|
||||||
owner_website_name?: string;
|
|
||||||
ais_vessel_url_base?: string;
|
|
||||||
initial_map_zoom?: number;
|
|
||||||
spectrum_coverage_margin_hz?: number;
|
|
||||||
spectrum_usable_span_ratio?: number;
|
|
||||||
decode_history_retention_min?: number;
|
|
||||||
show_sdr_gain_control?: boolean;
|
|
||||||
bandplan_enabled?: boolean;
|
|
||||||
bandplan_region?: string;
|
|
||||||
clients?: number;
|
|
||||||
audio_clients?: number;
|
|
||||||
rigctl_clients?: number;
|
|
||||||
rigctl_addr?: string;
|
|
||||||
remotes?: string[];
|
|
||||||
active_remote?: string | null;
|
|
||||||
};
|
};
|
||||||
interface BandplanSegment { low_hz: number; high_hz: number; mode: string; label: string }
|
interface BandplanSegment { low_hz: number; high_hz: number; mode: string; label: string }
|
||||||
interface BandplanBand { name: string; low_hz: number; high_hz: number; segments: BandplanSegment[] }
|
interface BandplanBand { name: string; low_hz: number; high_hz: number; segments: BandplanSegment[] }
|
||||||
@@ -317,7 +300,21 @@ async function responseJsonUnknown(response: Response): Promise<unknown> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isAppUpdate(value: unknown): value is AppUpdate {
|
function isAppUpdate(value: unknown): value is AppUpdate {
|
||||||
return isRecord(value) && (value.status === undefined || isRecord(value.status));
|
return isRecord(value)
|
||||||
|
&& typeof value.clients === "number"
|
||||||
|
&& typeof value.audio_clients === "number"
|
||||||
|
&& typeof value.rigctl_clients === "number"
|
||||||
|
&& Array.isArray(value.remotes)
|
||||||
|
&& value.remotes.every((remote) => typeof remote === "string")
|
||||||
|
&& typeof value.show_sdr_gain_control === "boolean"
|
||||||
|
&& typeof value.initial_map_zoom === "number"
|
||||||
|
&& typeof value.spectrum_coverage_margin_hz === "number"
|
||||||
|
&& typeof value.spectrum_usable_span_ratio === "number"
|
||||||
|
&& typeof value.bandplan_enabled === "boolean"
|
||||||
|
&& typeof value.bandplan_region === "string"
|
||||||
|
&& typeof value.decode_history_retention_min === "number"
|
||||||
|
&& typeof value.server_connected === "boolean"
|
||||||
|
&& isRigSnapshot(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAudioStreamInfo(value: unknown): value is AudioStreamInfo {
|
function isAudioStreamInfo(value: unknown): value is AudioStreamInfo {
|
||||||
@@ -3800,7 +3797,7 @@ async function pollFreshSnapshot() {
|
|||||||
const resp = await fetch(statusUrl, { cache: "no-store" });
|
const resp = await fetch(statusUrl, { cache: "no-store" });
|
||||||
if (!resp.ok) return;
|
if (!resp.ok) return;
|
||||||
const data = await responseJsonUnknown(resp);
|
const data = await responseJsonUnknown(resp);
|
||||||
if (!isRigSnapshot(data)) return;
|
if (!isAppUpdate(data)) return;
|
||||||
render(data);
|
render(data);
|
||||||
void refreshRigList();
|
void refreshRigList();
|
||||||
lastEventAt = Date.now();
|
lastEventAt = Date.now();
|
||||||
|
|||||||
@@ -59,6 +59,18 @@ const jsonRoutes = new Map([
|
|||||||
lrpt_decode_enabled: false,
|
lrpt_decode_enabled: false,
|
||||||
wefax_decode_enabled: false,
|
wefax_decode_enabled: false,
|
||||||
recorder_enabled: false,
|
recorder_enabled: false,
|
||||||
|
clients: 1,
|
||||||
|
rigctl_clients: 0,
|
||||||
|
audio_clients: 0,
|
||||||
|
remotes: [],
|
||||||
|
show_sdr_gain_control: false,
|
||||||
|
initial_map_zoom: 10,
|
||||||
|
spectrum_coverage_margin_hz: 50_000,
|
||||||
|
spectrum_usable_span_ratio: 0.92,
|
||||||
|
bandplan_enabled: false,
|
||||||
|
bandplan_region: "iaru1",
|
||||||
|
decode_history_retention_min: 1440,
|
||||||
|
server_connected: true,
|
||||||
}],
|
}],
|
||||||
["/bandplan.json", {}],
|
["/bandplan.json", {}],
|
||||||
["/api/recorder/status", []],
|
["/api/recorder/status", []],
|
||||||
|
|||||||
@@ -60,8 +60,8 @@ pub struct StatusQuery {
|
|||||||
pub remote: Option<String>,
|
pub remote: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize, ts_rs::TS)]
|
||||||
struct FrontendMeta {
|
pub struct FrontendMeta {
|
||||||
#[serde(rename = "clients")]
|
#[serde(rename = "clients")]
|
||||||
http_clients: usize,
|
http_clients: usize,
|
||||||
rigctl_clients: usize,
|
rigctl_clients: usize,
|
||||||
|
|||||||
Reference in New Issue
Block a user