570 lines
42 KiB
JSON
570 lines
42 KiB
JSON
[
|
||
{
|
||
"date": "2026-09-22",
|
||
"repo": "svx-web",
|
||
"hash": "shared-libs-round-3",
|
||
"type": "change",
|
||
"summary": "Full shared lib set from callmgr2 (lib/*.js incl. ui_field, auth/lib/*.php incl. html.php); monitor and hover escapers on lib/fmt.js; valid_callsign/require_callsign once in auth/lib.php (node_cmd_api keeps its stricter amateur-format check as the validator argument). Not deployed yet."
|
||
},
|
||
{
|
||
"date": "2026-09-22",
|
||
"repo": "svx-web",
|
||
"hash": "shared-libs-round-2",
|
||
"type": "change",
|
||
"summary": "Shared libraries from callmgr2: changelog viewer, pager (monitor/TG monitor/trunks), message helpers, sign-in/register/sign-out transport, one theme.js (lib/theme.js), and PHP auth/lib/http.php + acl.php (JSON, CSRF, rate limit with this site's key namespace, require_privilege). No behaviour change; not deployed yet."
|
||
},
|
||
{
|
||
"date": "2026-09-22",
|
||
"repo": "svx-web",
|
||
"hash": "shared-libs",
|
||
"type": "change",
|
||
"summary": "Shared libraries from callmgr2 (copies under lib/ and auth/lib/): lib/api.js (status, lastheard and monitor fetches), lib/fmt.js (fmt2, callsign sort, frequency helpers in the CTCSS view), lib/ui_list.js (list editors, for coming editors), auth/lib/redis.php (Redis connection + batched read). No behaviour change."
|
||
},
|
||
{
|
||
"date": "2026-08-27",
|
||
"repo": "svxdr",
|
||
"hash": "tglist-sole-source",
|
||
"type": "change",
|
||
"summary": "FROZEN: the tglist is now the SOLE source of TG name/Location in Lastheard — all fallback removed (locForTG/systemForTG/bridgeIdentity no longer read live astmeta gateway location or nodes[].system). Every talker on a TG shows exactly the tglist name for that TG (blank if absent), so Lastheard is identical on every box. The master list is edited on speed and every box syncs it."
|
||
},
|
||
{
|
||
"date": "2026-08-27",
|
||
"repo": "svx-web",
|
||
"hash": "theme-focus-guard",
|
||
"type": "fix",
|
||
"summary": "Mirror of the DMR fix: the morning 'color' theme jump is gone. A theme <select>/color-swatch change is honored only when that control is focused (document.activeElement), so browser session-restore events (which never focus the control) can no longer flip the theme; added a pageshow re-sync."
|
||
},
|
||
{
|
||
"date": "2026-08-26",
|
||
"repo": "svx-web",
|
||
"hash": "tglist-canonical-admin-edit",
|
||
"type": "feature",
|
||
"summary": "New canonical TG list: one source, many readers. A single editable TG -> {name, system, allstar, mode, owner} list stored in Redis, edited from a new admin page (nav: user menu -> TG List) and served publicly at /tglist.php for every box/program to fetch (each consumer gets a 'tglist_url' config key, default this URL — no hardcoding). The editor has add/edit/delete, TG/name/system sort with an asc/desc toggle, and a search box with regex (invalid pattern falls back to literal substring, never throws). Delegated ownership: admins (LVL2+) manage all entries and assign an owner callsign; an owner may edit only their own TG's name/system/allstar/mode — enforced server-side, not just in the UI. This replaces the scattered per-box TG-name config so Last Heard / Location render identically everywhere. Consumers (svxdr, tgmonitor, proxy) are rewired to read this list in following steps."
|
||
},
|
||
{
|
||
"date": "2026-08-26",
|
||
"repo": "svx-web",
|
||
"hash": "theme-session-restore-guard",
|
||
"type": "fix",
|
||
"summary": "Theme no longer jumps to 'color' on the first browser start of the day. Root cause: browser session-restore ('continue where you left off') restores the previous session's form-control values on startup and fires synthetic change/input events on the theme <select> and the color swatches — the swatch handler then forced 'color' with no user action, overwriting the cookie theme read at boot. Fix: autocomplete=off on the theme select and swatches, plus a user-gesture gate so a restore-fired event before any real interaction is ignored (swatches also require the Color modal to be open). Logged-out cookie behavior unchanged: no cookie -> dark default, saved cookie -> that theme."
|
||
},
|
||
{
|
||
"date": "2026-08-24",
|
||
"repo": "svx-web",
|
||
"hash": "theme-adopt-on-login",
|
||
"type": "fix",
|
||
"summary": "Theme now follows your account on login. The account (uuid) profile is the source of truth: signing in applies your saved theme + colours, overriding whatever the pre-paint boot used from the local cookie. Previously the profile was adopted only on a device with no theme cookie yet, so once you had ever picked a theme, logging in on that browser never restored your account theme. Adoption still runs once per page load (a change made later in the session is not clobbered), an account that never saved a theme keeps the cookie/dark boot, anonymous changes save to the cookie only, and logged-in changes save to both the cookie and the profile."
|
||
},
|
||
{
|
||
"date": "2026-08-22",
|
||
"repo": "svx-web",
|
||
"hash": "admin-secret-boundary",
|
||
"type": "security",
|
||
"summary": "Reflector/MQTT system secrets are now visible only to Admin L3 — the reflector config already masked KEY/PASSWORD/private values below L3, and that stays. Admin L1 is now strictly read-only: every mutating admin action (cert sign/revoke, node block, service reload/restart, config set/save, MQTT settings) is rejected with 'read_only_admin'. Admin L2 can now edit the reflector config and set secrets blind: secrets show as ******** and, on save, any purely-masked value is restored from the live file so a blind save can never overwrite a real secret with the mask, while a value L2 actually types is written through. A new admin_rank in me.php lets the UI tell L1 from L2 (admin_level collapsed both to 2)."
|
||
},
|
||
{
|
||
"date": "2026-08-22",
|
||
"repo": "svx-web",
|
||
"hash": "theme-colors-array-poison",
|
||
"type": "fix",
|
||
"summary": "Theme colour handling hardened (mirror of the DMR fix): colours could be stored as an empty array '[]' instead of an object; all read/write/adopt now funnels through one normaliser that collapses '[]', malformed cookies and stray values to {}, so a bad value can never throw or paint a broken palette, and the save endpoint can no longer emit '[]'."
|
||
},
|
||
{
|
||
"date": "2026-08-11",
|
||
"repo": "svx-web",
|
||
"hash": "theme-color-editor-restored",
|
||
"type": "feat",
|
||
"summary": "Color editor restored (🎨 next to the theme selector), now on the fixed CSP-safe engine so it actually works: opens a dialog to pick each part of the Color theme with labelled swatches (Background, Text, Secondary text, Borders, Panel, Accent) + accent quick-picks + reset. Colours are stored in a cookie (svxcolors) for everyone and synced to the account profile when signed in."
|
||
},
|
||
{
|
||
"date": "2026-08-11",
|
||
"repo": "svx-web",
|
||
"hash": "theme-csp-fix-cookie",
|
||
"type": "fix",
|
||
"summary": "Theme switch now actually works: the no-flash engine was an inline <script>, which the site CSP (script-src 'self') blocks — so window.svxTheme never loaded and switching did nothing. Moved the engine to an external theme.js loaded in <head> ('self', CSP-allowed). Anonymous preference now stored in a first-party cookie (svxtheme) as intended, not localStorage; signed-in accounts still sync to the profile. Removed the color-edit button + dialog (not wanted) — just the Dark/Light/Color switch remains."
|
||
},
|
||
{
|
||
"date": "2026-08-11",
|
||
"repo": "svx-web",
|
||
"hash": "theme-system-dark-light-color",
|
||
"type": "feat",
|
||
"summary": "Theme switch in the navbar — Dark (default) / Light / Color — remembered per device (localStorage) and, for signed-in accounts, per account (synced to the profile so it follows you across devices). A 'Color edit' 🎨 dialog lets you pick each part of the Color theme with labelled colour swatches (Background, Text, Secondary text, Borders, Panel, Accent) plus accent quick-picks — no hex typing. No-flash: the saved theme applies before first paint. Dark stays byte-identical; all the existing cyan is now driven off theme tokens."
|
||
},
|
||
{
|
||
"date": "2026-08-07",
|
||
"repo": "svx-web",
|
||
"hash": "svxdr-monitor-node-key-consistency",
|
||
"type": "fix",
|
||
"summary": "Monitor->Nodes now shows the SAME name as Reflector clients. It uses the node KEY (the reflector /status map key, which Reflector clients displays), not the callsign field — so a node like key SA7AUX-AC (callsign SA7AUX) is SA7AUX-AC in both views, not two different names. Top-level (Local) names are never renamed/suffixed since they are the canonical keys; only trunk/satellite array nodes (no key, absent from Reflector clients) get a -N suffix to stay unique, and only enough to clear names already taken."
|
||
},
|
||
{
|
||
"date": "2026-08-07",
|
||
"repo": "svx-web",
|
||
"hash": "svxdr-monitor-unique-names-backend",
|
||
"type": "fix",
|
||
"summary": "Monitor->Nodes uniqueness done right: make each node NAME itself unique in the BACKEND (before the web), not a web-side badge. Any callsign that repeats across the whole view gets a number appended (SA7AUX -> SA7AUX1, SA7AUX2, … globally); a unique callsign is untouched. Reverted the web-side #index rendering — the web just displays the names it receives, no special logic."
|
||
},
|
||
{
|
||
"date": "2026-08-07",
|
||
"repo": "svx-web",
|
||
"hash": "svxdr-monitor-node-unique-index",
|
||
"type": "feat",
|
||
"summary": "Monitor->Nodes: every row is now unique. Nodes that share a callsign within a section get a 1-based #index (separate field, the callsign is never modified or lengthened). Data-driven; a unique callsign gets no index. Frontend shows it as a small #N after the callsign. Real stable identity still needs a source-provided id (DMR-ID); the index is the display disambiguator."
|
||
},
|
||
{
|
||
"date": "2026-08-07",
|
||
"repo": "svx-web",
|
||
"hash": "svxdr-monitor-node-system-field",
|
||
"type": "feat",
|
||
"summary": "svxdr Monitor->Nodes now shows each node type/link from the DATA per-node system field (DMR, DMR/YSF, DMR/Hytera), data-driven. Priority: config nodes[].mode override, else the node system field, else the sw-derived type. No hardcoding."
|
||
},
|
||
{
|
||
"date": "2026-08-07",
|
||
"repo": "svx-web",
|
||
"hash": "svxdr-nodes-groups-modes-config",
|
||
"type": "feat",
|
||
"summary": "svxdr node display is fully config-driven for BOTH shapes and free-text type. (1) statusnorm now rewrites top-level nodes[] too (by the node key/login), not just trunks/satellites — so a group that shows as a plain node (e.g. a DMR gateway) is renamed the same as a trunk group. (2) per-(group,TG) via: two different nodes (ASL + DMR) can share a TG and each keeps its own group name; talkerMetaFor matches against the set of configured vias for a TG. (3) new nodes[].mode = free-text type (AllStar/DMR/YSF/anything) shown per node, config wins over the data-derived type. (4) a configured node is always shown (never hidden by the bridge/infra filter). No config on a node = untouched. Zero hardcoding/per-type/per-box special."
|
||
},
|
||
{
|
||
"date": "2026-08-07",
|
||
"repo": "svx-web",
|
||
"hash": "svxdr-node-groups-config-driven",
|
||
"type": "feat",
|
||
"summary": "svxdr per-TG node groups are now fully config-driven, no hardcoding. Added a group field to nodes[]; each entry names the shared reflector login it belongs to (e.g. KT4ROY-ASL, KT4ROY-DMR). satLogins + satTrunkTGs are derived from that group field alone — removed the ASL-only lock (Trunk.SatelliteID) and the strip-digits heuristic. Any group renders identically; adding a new one (DMR, etc) is pure config, zero web changes."
|
||
},
|
||
{
|
||
"date": "2026-08-07",
|
||
"repo": "svx-web",
|
||
"hash": "svxdr-monitor-nodes-uniform",
|
||
"type": "fix",
|
||
"summary": "svxdr Monitor->Nodes: make it behave IDENTICALLY on every box, no per-conf-dialect special case. Bug: the Local-vs-trunk split only happened when the master trunk name could be derived from svxreflector.conf ([ReflectorTrunk] Peers), which only speed s dialect has — so aldmr (dialect [TRUNK_*]) never split and dumped the whole SE hub s 18 nodes into LOCAL. Now the split is decided purely by is the monitored master remote (reflectorURL != monitor_status_url), and the master-section label is derived from the DATA (the Reflector field the master s nodes report, masterReflectorName) instead of the local conf. Both boxes now show LOCAL = only their own nodes and the master s nodes under one identical, data-driven section. Removed dead/special code: the whole trunk_status.go topology-feed dead end, masterTrunkName and uplinkTrunkName (conf-dialect helpers), and the trunk_status config field."
|
||
},
|
||
{
|
||
"date": "2026-08-07",
|
||
"repo": "svx-web",
|
||
"hash": "svxdr-topology-crawl-restored",
|
||
"type": "fix",
|
||
"summary": "svxdr topology: restore the network crawl (go externalPuller) so the graph shows the connection LINES again, not just nodes. An aggregated hub status (sm2ampr/geu) carries empty trunk host fields, so it can never draw the server-to-server edges; only crawling each reflector s OWN /status yields the real peer hosts topology_view.js resolves into links. The crawl seeds from the local reflector s trunk hosts + the network topology file and BFS-reads each reflector; interval bumped to 60s (EXTERNAL_PULL_INTERVAL) with last-good retention so it is spread out and gentle. aldmr 17 reflectors/34 trunk-links, speed 18/42, local reflector self=true anchored at centre. The per-trunk trunk_status feed added earlier stays dormant (could not produce edges)."
|
||
},
|
||
{
|
||
"date": "2026-08-07",
|
||
"repo": "svx-web",
|
||
"hash": "svxdr-topology-full-net",
|
||
"type": "fix",
|
||
"summary": "svxdr topology: show the WHOLE network again, local reflector centred. readTrunkStatuses now reads ALL uplink trunks from svxreflector.conf (default address = the trunk s conf Host; nodelist.trunk_status json is only the exception, e.g. SE hub -> sm2ampr), and unfolds each hub response: every sub-trunk that carries nodes becomes its own reflector entity, so one hub read yields the full aggregated net (no crawl, no recursion beyond parsing the single response). aldmr 19 reflectors/1357 nodes, speed 9/661, each with type=local self=true so topology_view.js anchors it at centre. Read cadence stays local-fast / remote-1-per-min."
|
||
},
|
||
{
|
||
"date": "2026-08-07",
|
||
"repo": "svx-web",
|
||
"hash": "svxdr-trunk-status-topology",
|
||
"type": "fix",
|
||
"summary": "svxdr nodelist: rebuild network topology from OUR OWN trunks (svxreflector.conf) instead of a crawl. Each trunk s /status address is configured per trunk in svxdr.json nodelist.trunk_status, because a trunk s HTTP status address is not its trunk-protocol Host in the conf (SE hub trunk reaches reflector.reflector-network.se:5302 but publishes status at reflector.sm2ampr.net/geu/status/). readTrunkStatuses reads only the configured addresses, one level deep (no recursion), and fills externalState.sources so buildNetworkDoc reflectors[] is whole again. Read cadence split local vs remote: a loopback status URL refreshes fast (5s), a remote master/trunk URL is throttled to nodelist.monitor_interval (default 60s) so a shared hub is never hammered. No crawl, no hardcoded addresses."
|
||
},
|
||
{
|
||
"date": "2026-08-06",
|
||
"repo": "svx-web",
|
||
"hash": "svxdr-merge-code-complete",
|
||
"type": "feat",
|
||
"summary": "svxdr: merge code-complete — proxy, astmeta and nodelist (+ xlink inside nodelist) all run as subsystem goroutines in one binary, toggled per box by modules.* in /etc/svxlink/svxdr.json. proxy/astmeta ported to instance-scoped ctx-aware modules; nodelist folded by copying its sources and surgically rewiring config (applyConfig overrides env-derived globals from svxdr.json; applyRenderFromConfig builds the Location render from lastheard+nodes[]) with all crawl/lastheard/federation logic preserved. Builds, vets, runs (all modules start, graceful stop). Old standalone binaries stay until cutover. Remaining: shared in-memory talker hand-off (drop the localhost HTTP hop), svxdr.service + deploy, and the box-by-box cutover."
|
||
},
|
||
{
|
||
"date": "2026-08-06",
|
||
"repo": "svx-web",
|
||
"hash": "svxdr-merge-phase1-scaffold",
|
||
"type": "feat",
|
||
"summary": "svxdr: start merging astmeta + nodelist + proxy + xlink into ONE process (fewer daemons to track). Phase 1 scaffold: one config /etc/svxlink/svxdr.json (sectioned per subsystem + shared redis/nodes/trunk/lastheard), per-box module toggles so the same binary runs all subsystems on speed/aldmr and only astmeta on the allstar box, and a start/SIGHUP-reload/graceful-stop lifecycle. Subsystems are stubs for now and are folded in one at a time; the existing standalone binaries keep running until cutover (no regression). Builds + vets; verified it parses the example and toggles modules."
|
||
},
|
||
{
|
||
"date": "2026-08-06",
|
||
"repo": "svx-web",
|
||
"hash": "aldmr-nodes-json-deploy",
|
||
"type": "fix",
|
||
"summary": "aldmr: deployed the nodes.json bridge map (deploy-aldmr/etc/nodes.json) and the new astmeta/nodelist. Fixes the Location regression on TG 3149050: it was mapped in astmeta to the dead node 403872 while the live carrier 1971 was unwatched (also ended a per-second 'no RPT_ALINKS for 403872' reconnect storm). 1971 is a fan-out endpoint whose only keyed alink is our own 48305, so the allstar remap reports the real far node 45225. Verified live: TG 3109050 and 3149050 now pair on node 45225 / East Coast Reflector. callsign KT4ROY-ASL uniform for all; reflector name lives in nodeLocation."
|
||
},
|
||
{
|
||
"date": "2026-08-06",
|
||
"repo": "svx-web",
|
||
"hash": "astmeta-nodes-allstar-remap",
|
||
"type": "fix",
|
||
"summary": "astmeta: a fan-out endpoint (e.g. a local 19xx node) whose only keyed alink is one of our own upstream bridge nodes now reports the configured far AllStar node from nodes.json (allstar field) instead of the local node -- so the Location column gets the right reflector name AND the exact 'via <node>' on the fanned-out TG. Strictly guarded: only remaps when the seen node is one of our own bridge nodes; a node that sees a real external AllStar node directly is unchanged. No-op when nodes.json is absent."
|
||
},
|
||
{
|
||
"date": "2026-08-06",
|
||
"repo": "svx-web",
|
||
"hash": "nodes-json-single-source",
|
||
"type": "feat",
|
||
"summary": "nodes.json: one source-of-truth bridge map that astmeta and nodelist both read directly (svx_node/tg/allstar/system/via/callsign per node). astmeta derives watch_nodes/[node.*]/[names] from it; nodelist derives the Location column's reflector_names + format from it -- ending the multi-file drift that broke the Location column. Additive: absent file = old config, no regression. proxy is a pure relay and stays untouched. Tooling: deploy-aldmr/import-live.py bootstraps nodes.json from the live config (derives the AllStar link from rpt startup_macro, flags what it cannot derive instead of guessing); deploy-aldmr/gen-asterisk.py renders the svx_sat.conf node blocks as a managed #include."
|
||
},
|
||
{
|
||
"date": "2026-08-06",
|
||
"repo": "svx-web",
|
||
"hash": "monitor-underline-visible",
|
||
"type": "fix",
|
||
"summary": "Monitor -> Nodes: make the section line actually visible -- node rows are now fully borderless (table-borderless) and each section name (Local / trunk) carries an explicit 2px grey line under it, so the header stands apart in both light and dark themes instead of blending into Bootstrap's faint default row borders."
|
||
},
|
||
{
|
||
"date": "2026-08-06",
|
||
"repo": "svx-web",
|
||
"hash": "monitor-section-underline",
|
||
"type": "change",
|
||
"summary": "Monitor -> Nodes: each section name (Local, and every trunk) now has a thin line under it so the sections read apart at a glance. The line is under the header only -- node rows stay borderless."
|
||
},
|
||
{
|
||
"date": "2026-08-06",
|
||
"repo": "svx-web",
|
||
"hash": "monitor-local-vs-trunk",
|
||
"type": "fix",
|
||
"summary": "Monitor -> Nodes: Local now means ONLY this server's own reflector nodes. When a box monitors a foreign master (speed reads the HUB SE master status), the master's own nodes used to land under Local -- they are a different server reached over a trunk, not local. They are now grouped under that trunk's section, named from config (the svxreflector.conf trunk whose host matches the monitored master, e.g. SE-AUX -- never hardcoded). Local is read from our OWN reflector (REFLECTOR_STATUS_URL) and self nodes are de-duplicated out of the master section. A box that monitors its own reflector (aldmr) is unaffected: same URL -> no split, top-level stays Local."
|
||
},
|
||
{
|
||
"date": "2026-08-06",
|
||
"repo": "svx-web",
|
||
"hash": "trunks-collapsible-tgs",
|
||
"type": "fix",
|
||
"summary": "Monitor -> Trunks: a satellite trunk with talkgroups is now collapsible -- it shows a chevron and '(N TG)', and the TG list expands/collapses on click (accordion, same feel as the CTCSS list) instead of always spilling every TG. Expanded state persists across the 5 s status refresh."
|
||
},
|
||
{
|
||
"date": "2026-08-06",
|
||
"repo": "svx-web",
|
||
"hash": "trunks-satellite-grouped",
|
||
"type": "fix",
|
||
"summary": "Monitor -> Trunks shows ONE trunk per satellite, named by the satellite id (e.g. KT4ROY-ASL), with all its talkgroups listed underneath by their own per-TG label + number. Before, several chan_svx_sat entries sharing a satellite_id collapsed to whichever callsign was parsed last (only 'ASL-dodropin' showed). Now they are grouped as the single trunk they actually are. Fully dynamic: the satellite set is the union of the [SATELLITE] ids in svxreflector.conf and the live-authenticated satellites in /status, so new satellites (Chicago, ...) appear automatically and offline ones still show. Secrets are never read or exposed -- only the id."
|
||
},
|
||
{
|
||
"date": "2026-08-06",
|
||
"repo": "svx-web",
|
||
"hash": "topo-strip-status-url",
|
||
"type": "fix",
|
||
"summary": "Monitor -> Topology never shows a raw http(s)://host:port/status URL. Federation-seed reflectors/trunks arrive with their status URL as the name; every label (node, edge, leaf, popup) is now stripped to the hostname only -- host:port/status is an attack surface. Popup also shows the name only (no IP/port/hostname/aliases/version)."
|
||
},
|
||
{
|
||
"date": "2026-08-05",
|
||
"repo": "callmgr",
|
||
"hash": "limiter-dbfs",
|
||
"type": "change",
|
||
"summary": "The analog limiter ceiling is now configured in dBFS instead of a raw linear PCM number, on every analog bridge (SVX peer, SVX satellite, USRP link). 0 dBFS is full scale (32767) and the default is -0.8 dBFS; the DSP still works on the linear peak internally, the value is just converted at the config boundary. Fully back-compatible -- any config still carrying the old linear 'lim_ceil' key keeps working (read and converted on load), and the sibling C drivers (chan-svx, chan-svx-sat, pbx-sat) plus the svxmixer TUI and the FreePBX/DMR web editors all moved to the same dBFS scale so a trunk sounds identical everywhere."
|
||
},
|
||
{
|
||
"date": "2026-08-05",
|
||
"repo": "callmgr",
|
||
"hash": "svx-dmr-audio",
|
||
"type": "fix",
|
||
"summary": "SVX<->DMR audio is fixed end-to-end on the analog bridges (SVX peer, SVX satellite and USRP link, plus the same voice-sync marker on NXDN->DMR). Three faults, all in the paths that build the DMR voice burst themselves: (1) SVX->DMR was completely silent because every synthesized voice burst was tagged as a plain voice frame with no A-F voice-sync marker, so the far DMR side never locked onto the stream; (2) the audio that did get through was harsh/overmodulated because the burst payload left the sync region empty -- the 48-bit MS voice-sync pattern that YSF/M17 already write was never added; (3) DMR->SVX crackled and started sluggishly because the three Opus frames produced from each 60 ms DMR burst were sent back-to-back instead of paced. Now every burst carries the correct A-F frame-sync marker and the MS voice-sync pattern, and audio is released one frame per 20 ms -- the first frame fires instantly for a snappy key-up, a 5 ms drain poll keeps the cadence tight, and the SVX-satellite trunk sets TCP_NODELAY so nothing is coalesced. Verified clean on the wire (steady 19-20 ms spacing)."
|
||
},
|
||
{
|
||
"date": "2026-08-04",
|
||
"repo": "web",
|
||
"hash": "1a0b8ab",
|
||
"type": "feature",
|
||
"summary": "help.html PBX <-> SVX tab gained an 'Audio tuning' section: the per-trunk preamp -> 7-band EQ -> AGC -> limiter chain (per direction, off by default = bit-identical passthrough), where to set it in the FreePBX module, the live-preview workflow (Start/Stop/Discard/Save, heard mid-call, auto-reverts after 30 s) and the equivalent pbx_sat.conf keys with a 'Voice' EQ preset. Matches the chan-pbx-sat 0.2.0 packages."
|
||
},
|
||
{
|
||
"date": "2026-08-04",
|
||
"repo": "web",
|
||
"hash": "d71315f",
|
||
"type": "feature",
|
||
"summary": "help.html PBX <-> SVX tab gained a complete 'Full FreePBX setup' walk-through (0-8): prerequisites, install, loading chan_pbx_sat, creating the trunk in the GUI (now with per-field default/example/range/help), the global lists (callsigns/talkgroups/routing/settings), the PBX -> SVX dial-plan wiring (extensions_custom.conf context off the trunk's Group number, ptt/cmd_select_tg), the optional SVX -> PBX ring path (standing + monitor + TG->SIP), the access list, and apply/verify via the status panel, dashboard widget and 'pbxsat status'."
|
||
},
|
||
{
|
||
"date": "2026-08-03",
|
||
"repo": "web",
|
||
"hash": "168c9d8",
|
||
"type": "feature",
|
||
"summary": "help.html gained a 'PBX <-> SVX' Downloads tab (mirroring the AllStar tab): what chan_pbx_sat is, the two packages (chan-pbx-sat driver + pbxsat-freepbx FreePBX GUI), apt/dnf install snippets for Debian 12/13 and CentOS/RHEL 7/8/9, and a link to the public packages repo (git.sa7aux.se/bjorne/pbx-sat-deb). Downloads live on the repo page so the tab needs no per-file update."
|
||
},
|
||
{
|
||
"date": "2026-08-01",
|
||
"repo": "proxy",
|
||
"hash": "fa69ae8",
|
||
"type": "feature",
|
||
"summary": "svx-web/proxy gained a meta-sync conduit so a box with NO local astmeta (speed) still shows the real talker on Lastheard. It sets UPSTREAM_STATUS_URL to the proxy of the box that HAS the meta (aldmr) and FORWARD_TALKER_URL to its own nodelist: every 2s it fetches the upstream /status ('hämta status'), keeps the talker_meta (so its own /status carries it too) and POSTs each entry to the local nodelist /api/talker-meta ('api') with network left empty. The frozen Lastheard code enriches the existing trunk row IN PLACE — callsign, via, AllStar node, system — while the backend stays 'trunk' (it arrived over the trunk). Only trunk/status/API cross-box; no bridge, no polling of anyone else, no code change to nodelist/web, no dupes. No-op unless UPSTREAM_STATUS_URL is set (aldmr stays fed by its local astmeta)."
|
||
},
|
||
{
|
||
"date": "2026-07-31",
|
||
"repo": "astmeta",
|
||
"hash": "33607dc",
|
||
"type": "feature",
|
||
"summary": "astmeta can now POST talker-meta to SEVERAL reflectors, not just the local one: [nodelist] peers = url|token, url|token. This lets a partner reflector that receives the same talker over the trunk (e.g. speed hearing an East Coast talker relayed from aldmr) enrich its Lastheard IN PLACE with the real source — 'East Coast Reflector via KT4ROY-SAT1', AllStar node, backend AllStar — instead of the bare trunk name 'KT4ROY-SAT1 / trunk'. No dupes: the receiving nodelist already applies talker-meta to trunk-delivered rows (via-gated), so it enriches the existing single row. Backward compatible; no peers = unchanged."
|
||
},
|
||
{
|
||
"date": "2026-07-31",
|
||
"repo": "web",
|
||
"hash": "4f5a617",
|
||
"type": "feature",
|
||
"summary": "On the AlabamaLink box (aldmr) the AlabamaLink line is now folded into the SAME federation marquee — 'Part of the reflector-network.com federation … • Powered by Executor — a multi-mode digital voice server, part of the AlabamaLink network.' scrolling together in one bar, matching the DMR site's notice. No second banner. Gated on the local reflector identity (KT4ROY) from the status the page already fetches, so every other box keeps just the federation line."
|
||
},
|
||
{
|
||
"date": "2026-07-31",
|
||
"repo": "web",
|
||
"hash": "8b021da",
|
||
"type": "fix",
|
||
"summary": "Monitor — Topology now draws the local reflector's OWN satellite trunks (chan_svx_sat, e.g. KT4ROY-SAT1/SAT2) as leaves off the centre node. They were silently dropped: local-node trunks that don't resolve to a known reflector were all suppressed to avoid drawing the dangling uplink trunk as a leaf, but that filter also swallowed the local satellites. Satellites are now exempt from that suppression while the uplink stays hidden, so your satellite trunks finally appear in Topology like they already do in Monitor — Trunks and Monitor — Nodes."
|
||
},
|
||
{
|
||
"date": "2026-07-31",
|
||
"repo": "nodelist",
|
||
"hash": "ee4714c",
|
||
"type": "fix",
|
||
"summary": "Satellite trunks (chan_svx_sat) now appear in EVERY trunk view, not just some. The Monitor — Trunks table (/api/local-trunks) read only svxreflector.conf and so omitted satellites entirely; it now also reads svx_sat.conf and lists the enabled satellite trunks under their real callsign (e.g. KT4ROY-SAT1) with live authenticated/node counts from the reflector status — matching Topology and Monitor — Nodes, which already carried them. Names are resolved per-box from svx_sat.conf so this is generic for anyone running the web."
|
||
},
|
||
{
|
||
"date": "2026-07-30",
|
||
"repo": "astmeta",
|
||
"hash": "32e216d",
|
||
"type": "fix",
|
||
"summary": "A talker heard through a named hub (a node with a [names] entry, e.g. an AllStar reflector) now keeps its REAL callsign: the [names] value becomes the system name and the astdb still resolves the operator callsign, returned separately. Previously a named node reported the system name as the callsign and threw the real one away. So an East Coast talker now shows 'W2ECR via KT4ROY-SAT1' with system 'East Coast Reflector' — and the DMR side can resolve the DMR id from the real callsign."
|
||
},
|
||
{
|
||
"date": "2026-07-29",
|
||
"repo": "nodelist",
|
||
"hash": "6bd338e",
|
||
"type": "feature",
|
||
"summary": "Last Heard now shows a Backend column — how a talker enters the system (AllStar, SVXLink, trunk, and DMR/DMR-YSF/DMR-OB… later) — in place of the free-text System name, matching the DMR side where Backend already means the ingress path (there Mode is the DV protocol). The Node column is relabelled \"AllStar node\" and only fills for AllStar ingress. The value is carried end-to-end: a bridge reports it via /api/talker-meta (astmeta sends AllStar), otherwise a row defaults to trunk or SVXLink by source."
|
||
},
|
||
{
|
||
"date": "2026-07-28",
|
||
"repo": "web",
|
||
"hash": "66c7279",
|
||
"type": "fix",
|
||
"summary": "Admin accounts flags modal: each admin level (L1/L2/L3) is now gated behind 'normal' with a cascade (dropping normal drops admin), mirroring the dmr site where admin requires 'verified'. Enforced in both the modal (admin.js) and the backend (admin_accounts.php), so svx and dmr share the same verified-email -> reg -> normal -> admin progression instead of admin being settable ungated."
|
||
},
|
||
{
|
||
"date": "2026-07-28",
|
||
"repo": "web",
|
||
"hash": "ad1f081",
|
||
"type": "feature",
|
||
"summary": "Scrolling federation banner under the nav, shown on every page — \"Part of the reflector-network.com federation — a growing mesh of independent networks\" in amber with a soft glow so it stands out against the cyan theme; the marquee pauses on hover and honours prefers-reduced-motion."
|
||
},
|
||
{
|
||
"date": "2026-07-28",
|
||
"repo": "deploy",
|
||
"hash": "8ee1f46",
|
||
"type": "feature",
|
||
"summary": "deploy-remote.sh --conf deploys the per-box system config under /etc (deploy/etc for speed, deploy-aldmr/etc for aldmr). Secret-safe: each @@PLACEHOLDER@@ is filled from the CURRENT live file (section+key aware) so real passwords/tokens are never overwritten; a placeholder that can't be resolved skips the file (never writes @@ into a live config). Per-file diff + y/N confirm (or -y), -n dry-run, preserves target owner/mode. Runs before --bin so a following --bin restart picks up the new config. Also reconciled the astmeta.conf template with the live file (added the [nodelist] section) so its diff is clean."
|
||
},
|
||
{
|
||
"date": "2026-07-28",
|
||
"repo": "astmeta",
|
||
"hash": "39f4594",
|
||
"type": "feature",
|
||
"summary": "astmeta gains per-watch-node TG/via via [node.<N>] sections, so ONE instance can cover several bridge nodes that each report a different talk group and via callsign (e.g. 48305 -> 3109050/KT4ROY-A and 403872 -> 3109051/SA7AUX). Talker state is now keyed per (watch-node, remote-node) so the same remote heard through two bridges no longer clobbers the other. Fixes Lastheard/meta being bare on the second bridge's TG. Global [usrp] tg / [nodelist] via / [mqtt] via remain the fallback — existing single-node configs unchanged."
|
||
},
|
||
{
|
||
"date": "2026-07-28",
|
||
"repo": "nodelist",
|
||
"hash": "6ded491",
|
||
"type": "fix",
|
||
"summary": "Lastheard now captures trunk/satellite talkers, not only node talkers. The reflector-log tailer regex only matched 'Talker start/stop'; a satellite/trunk-delivered talker is logged as 'Trunk talker start/stop' (e.g. sat403872 on TG 3109051) and was silently dropped, so those TGs never appeared in Lastheard. The regex now also matches the optional 'Trunk ' form and tags the event source=trunk."
|
||
},
|
||
{
|
||
"date": "2026-07-28",
|
||
"repo": "web",
|
||
"hash": "b127801",
|
||
"type": "fix",
|
||
"summary": "Topology centre node now shows the local SERVER's name (self hostname, e.g. alabama.reflector-network.com, else the reflector name) instead of its first trunk name — the trunk name pointed at a remote hub and read as 'the hub is in the centre'. The local server is now clearly the centre."
|
||
},
|
||
{
|
||
"date": "2026-07-28",
|
||
"repo": "web",
|
||
"hash": "ef7878e",
|
||
"type": "change",
|
||
"summary": "Monitor Topology is always anchored on THIS reflector — the local server sits in the centre and every other reflector rings around it, instead of centring on the network hub/uplink. Legend simplified (the centre is 'you')."
|
||
},
|
||
{
|
||
"date": "2026-07-28",
|
||
"repo": "nodelist",
|
||
"hash": "3ac5dd1",
|
||
"type": "fix",
|
||
"summary": "Monitor Trunks showed a configured trunk as offline when the reflector reports it under trunks_UDP (named like TrunkPeer#SE-AUX) instead of the trunks dict. /api/local-trunks now also reads trunks_UDP and matches the conf name against the TrunkPeer#-prefixed name, so an online trunk shows online."
|
||
},
|
||
{
|
||
"date": "2026-07-28",
|
||
"repo": "nodelist",
|
||
"hash": "ccc516d",
|
||
"type": "change",
|
||
"summary": "Monitor Trunks now lists ONLY the trunks this reflector is configured to connect to, read straight from the local /etc/svxlink/svxreflector.conf (new /api/local-trunks). Handles both config dialects — [ReflectorTrunk]+[TrunkPeer#name] and [TRUNK_xxx] HOST/PORT/REMOTE_PREFIX. Each trunk is matched to the local reflector's /status for online + node count; the host/IP is used only for matching and never sent to the client. The whole-network view stays on Monitor → Topology."
|
||
},
|
||
{
|
||
"date": "2026-07-28",
|
||
"repo": "web",
|
||
"hash": "ad32bd9",
|
||
"type": "change",
|
||
"summary": "Monitor Trunks reworked: Local at the top, everything else under Network; dropped the Owner and Host columns (host/IP was never meant to be exposed); the Network list gets the same footer pagination as the node page (first/prev/next/last, rows/page fit the screen) when it grows; search box is now a case-insensitive regex. Monitor Nodes trunk headers no longer print the host either."
|
||
},
|
||
{
|
||
"date": "2026-07-28",
|
||
"repo": "nodelist",
|
||
"hash": "50d5732",
|
||
"type": "change",
|
||
"summary": "Monitor Nodes shows only real users — every bridge is dropped: sw *_Bridge / Janus Bridge / chan_svx (chan_svx_sat kept as a real AllStar node), nodeClass bridge/xlx/dmr/ysf/mumble/stream/sip/echolink/zello, and gateway callsigns (SIP*, DCS*, XLX*, *Mumble*). On the SE hub that is 10 real users instead of 28 mixed rows."
|
||
},
|
||
{
|
||
"date": "2026-07-28",
|
||
"repo": "web",
|
||
"hash": "4255924",
|
||
"type": "fix",
|
||
"summary": "Monitor Nodes gets its footer pagination back (first « · ‹ · page numbers · › · last »), rows/page fit to the screen so it never scrolls — same pager as TG Monitor. Local + per-trunk sections, search and the All nodes / All active toggle all kept."
|
||
},
|
||
{
|
||
"date": "2026-07-28",
|
||
"repo": "nodelist",
|
||
"hash": "174b09d",
|
||
"type": "change",
|
||
"summary": "Monitor Nodes rebuilt to read ONE reflector /status (new /api/monitor-nodes) instead of the recursive crawl+aggregate: shows a Local section then one section per trunk with its nodes. Per-box source via MONITOR_STATUS_URL (speed=sm2ampr /geu/status/, aldmr=its own /status). Search + mode toggle kept, borderless."
|
||
},
|
||
{
|
||
"date": "2026-07-28",
|
||
"repo": "web",
|
||
"hash": "a703cfb",
|
||
"type": "feature",
|
||
"summary": "Reflector Dashboard remembers the last view across reloads (Ctrl+Shift+R lands where you were), and TG Monitor gets a search box — type a TG number to see who is on it, or a callsign/node to see which talkgroups it is on"
|
||
},
|
||
{
|
||
"date": "2026-07-28",
|
||
"repo": "nodelist",
|
||
"hash": "a09a9ed",
|
||
"type": "feature",
|
||
"summary": "All Nodes now covers the WHOLE network — folds the federation aggregate (reflector-network.com, ~627 nodes) into the flat list with real reflector names; crawl still wins for live TG, aggregate fills the rest and replaces any bare-IP ref with the real reflector name (or a hostname alias)"
|
||
},
|
||
{
|
||
"date": "2026-07-28",
|
||
"repo": "nodelist",
|
||
"hash": "bf19e22",
|
||
"type": "fix",
|
||
"summary": "All Nodes ref column never exposes a raw http(s)://host:port/status URL — federation-seed sources are shown by hostname only"
|
||
},
|
||
{
|
||
"date": "2026-07-27",
|
||
"repo": "web",
|
||
"hash": "3b87ec6",
|
||
"type": "change",
|
||
"summary": "Monitor → Nodes is now the total deduped list itself — one row per callsign with the REAL reflector it's connected to (no per-reflector boxes, no divider lines), sorted by callsign, regex filter + pagination + live last-heard. Dropped the separate All Nodes page; nodelist attributes the local reflector by its real name, never 127.0.0.1"
|
||
},
|
||
{
|
||
"date": "2026-07-27",
|
||
"repo": "nodelist",
|
||
"hash": "85e875e",
|
||
"type": "feature",
|
||
"summary": "All Nodes now streams true per-node deltas: nodelist maintains a deduped flat map (federation crawl + fresh local statusKey overlay, local reflector owns its calls) and serves /api/nodes (full) + /api/nodes-stream (SSE nodes-full then nodes-delta added/changed/removed). Rebuild fires instantly on pubsub + a 3s safety tick; the wire only carries what changed"
|
||
},
|
||
{
|
||
"date": "2026-07-27",
|
||
"repo": "web",
|
||
"hash": "e1515bd",
|
||
"type": "feature",
|
||
"summary": "All Nodes page — one total, deduped list across every reflector/trunk (mesh dupes collapsed to the owning ref): Callsign · Reflector · TG · live Last-heard (NOW highlight when transmitting). Regex search top-right, first/prev/pages/next/last pagination, built once then re-synced event-driven off the SSE (no polling)"
|
||
},
|
||
{
|
||
"date": "2026-07-27",
|
||
"repo": "web",
|
||
"hash": "0a017b2",
|
||
"type": "feature",
|
||
"summary": "Notification bell now matches the dmr bell: a Filter pane (show/hide categories, saved in localStorage), a Clear all button and a per-row dismiss (×) across Pending CSRs / New registrations / Certificate expiry / Account events, with the badge counting only what's visible"
|
||
},
|
||
{
|
||
"date": "2026-07-27",
|
||
"repo": "web",
|
||
"hash": "3a18be1",
|
||
"type": "feature",
|
||
"summary": "Admin account editor: one-click Approve / Unapprove buttons (shown by state) so an account missed in the bell can be approved directly — on par with the dmr Register/Unregister"
|
||
},
|
||
{
|
||
"date": "2026-07-27",
|
||
"repo": "web",
|
||
"hash": "7e56b7c",
|
||
"type": "feature",
|
||
"summary": "Admin: approval-status badge on the account editor (Approved / Pending approval / Not verified / Banned) and a matching account-list status; the notification bell now has an inline Approve button on new registrations (sets reg+normal so the user can log in) with the error shown inline; sign-in label clarified to Callsign or email (email login already supported)"
|
||
},
|
||
{
|
||
"date": "2026-07-27",
|
||
"repo": "nodelist",
|
||
"hash": "1a7b699",
|
||
"type": "feature",
|
||
"summary": "Federation gap detector (/api/gaps) — diffs the network-wide aggregate against our crawl coverage; names reflectors to add to FEDERATION_SEEDS"
|
||
},
|
||
{
|
||
"date": "2026-07-27",
|
||
"repo": "nodelist",
|
||
"hash": "6e721f3",
|
||
"type": "feature",
|
||
"summary": "Federation seeds — recursive crawl now reaches the Swedish Reflector-network core (FEDERATION_SEEDS) and branches via their trunks"
|
||
},
|
||
{
|
||
"date": "2026-07-26",
|
||
"repo": "web",
|
||
"hash": "cf75e28",
|
||
"type": "docs",
|
||
"summary": "Help → AllStar tab rewritten for chan_svx (native SVXReflector driver for ASL3, replaces USRP bridge)"
|
||
},
|
||
{
|
||
"date": "2026-07-26",
|
||
"repo": "web",
|
||
"hash": "9f32a6c",
|
||
"type": "fix",
|
||
"summary": "TG Monitor hides bridge nodes (chan_svx asterisk etc) — only real radio clients"
|
||
},
|
||
{
|
||
"date": "2026-07-26",
|
||
"repo": "web",
|
||
"hash": "49548b3",
|
||
"type": "feature",
|
||
"summary": "TG Monitor view — clients per talkgroup, per mode, both sides (SVX + DMR via trunk), deduped"
|
||
},
|
||
{
|
||
"date": "2026-07-26",
|
||
"repo": "nodelist",
|
||
"hash": "077f7e0",
|
||
"type": "feature",
|
||
"summary": "Talker-meta ingest accepts localhost without a token (zero-config bridge)"
|
||
},
|
||
{
|
||
"date": "2026-07-26",
|
||
"repo": "nodelist",
|
||
"hash": "38734d1",
|
||
"type": "change",
|
||
"summary": "One shared nodelist source — removed the separate nodelist-al build"
|
||
},
|
||
{
|
||
"date": "2026-07-26",
|
||
"repo": "web",
|
||
"hash": "26126d6",
|
||
"type": "change",
|
||
"summary": "Enable/Disable toggle buttons in admin (matches the DMR side)"
|
||
},
|
||
{
|
||
"date": "2026-07-25",
|
||
"repo": "web",
|
||
"hash": "56202f7",
|
||
"type": "feature",
|
||
"summary": "Lastheard fills node/system + \"system via bridge\" from the bridge directory"
|
||
},
|
||
{
|
||
"date": "2026-07-25",
|
||
"repo": "auth",
|
||
"hash": "898cfe6",
|
||
"type": "feature",
|
||
"summary": "Mail send via direct SSL-SMTP when a relay is configured (no local MTA needed)"
|
||
},
|
||
{
|
||
"date": "2026-07-25",
|
||
"repo": "web",
|
||
"hash": "89b8719",
|
||
"type": "feature",
|
||
"summary": "\"Talking now\" identical local/remote — trunk talkers in the popup"
|
||
},
|
||
{
|
||
"date": "2026-07-25",
|
||
"repo": "web",
|
||
"hash": "cb1d59a",
|
||
"type": "feature",
|
||
"summary": "Enrich lastheard + talker popup from the bridge directory (trunk bridges)"
|
||
},
|
||
{
|
||
"date": "2026-07-25",
|
||
"repo": "nodelist",
|
||
"hash": "354a82a",
|
||
"type": "fix",
|
||
"summary": "Bridge crawl uses the reflector IP (URL often empty) + runs in parallel"
|
||
}
|
||
]
|