Skip to content

Commit 1dff595

Browse files
Rewrite WebUI with Preact + Vite, add WebSocket runtime controls
- New Preact SPA in ui/ with Vite build to content/ (23KB gzipped) - FFT/waterfall with peak hold, click-to-tune, modern dark theme - Collapsible control panel: frequency, demod mode, filter BW, display - WebSocket service with reconnect, audio playback, control messages - Go: add control.go handler for runtime param changes + pipeline rebuild - Go: add WS read loop for incoming control messages - Dockerfile: add node build stage, CI: add setup-node + UI build step - Updated content.go embed paths for Vite output (content/assets/) 🤖 Generated with Mister Maluco Co-Authored-By: MisterMal <teskeslab@lucasteske.dev>
1 parent 58b49ec commit 1dff595

24 files changed

Lines changed: 1109 additions & 646 deletions

.github/workflows/ci.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ jobs:
1616
with:
1717
go-version: "1.25"
1818

19+
- uses: actions/setup-node@v4
20+
with:
21+
node-version: "22"
22+
23+
- name: Build UI
24+
run: cd ui && npm ci && npm run build
25+
1926
- name: golangci-lint
2027
uses: golangci/golangci-lint-action@v7
2128
with:

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,6 @@ bins
2323
qemu-*
2424
x86_64_*
2525
segdsp_worker*
26+
27+
ui/node_modules/
28+
ui/dist/

AGENTS.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ Go SDR demodulator. Single-module, single-binary project.
55
## Commands
66

77
```bash
8+
# UI (run from ui/)
9+
cd ui && npm ci && npm run build # builds to content/
10+
cd ui && npm run dev # dev server with proxy to :8080
11+
12+
# Go
813
go build -o segdsp .
914
go test -v -race ./...
1015
golangci-lint run
@@ -23,7 +28,11 @@ No Makefile or task runner exists.
2328
- `demodcore/` — Demodulator interface + FM/AM implementations
2429
- `eventmanager/` — Channel-based pub/sub event bus
2530
- `recorders/` — WAV file recording
26-
- `content/` — Static web UI embedded via `go:embed`
31+
- `content/` — Built web UI (Vite output), embedded via `go:embed`
32+
- `ui/` — Preact + Vite source for the web UI
33+
- `control.go` — WebSocket control message handler (runtime param changes)
34+
- `helpers.go` — Message types (FFT, device, control)
35+
- `websocket.go` — WebSocket server with broadcast + incoming control read loop
2736

2837
## Gotchas
2938

@@ -32,6 +41,8 @@ No Makefile or task runner exists.
3241
- The app **cannot start** without a running `radioserver` instance (external SDR IQ source, not in this repo)
3342
- Default branch is `master`; releases are tag-triggered via GitHub Actions (`release.yml`)
3443
- Multi-arch Docker builds (amd64, arm32v6, arm64v8) use QEMU emulation in CI — no host-side cross-compilation needed
44+
- `content/` is auto-generated by `npm run build` in `ui/` — do not edit directly
45+
- The Dockerfile has a multi-stage build: node → go → alpine runtime
3546

3647
## Testing
3748

@@ -41,4 +52,7 @@ All tests are pure unit tests (DSP math primitives). No integration tests, no ex
4152

4253
- HTTP routing uses stdlib `http.HandleFunc` directly, no router library
4354
- CLI flags parsed with stdlib `flag`, env vars set in `common.go:setEnv()`
44-
- WebSocket endpoint at `/ws`, static UI at `/` and `/static/*`
55+
- WebSocket endpoint at `/ws`, UI at `/`, assets at `/assets/*`
56+
- Control messages: clients send `{"MessageType":"control", ...}` JSON to `/ws` when `WebCanControl=true`
57+
- Runtime changes to demod mode / filter BW / squelch trigger a DSP pipeline rebuild
58+
- Frequency changes are applied directly to the RadioServer without rebuild

Dockerfile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
FROM node:22-alpine AS uibuild
2+
3+
WORKDIR /ui
4+
COPY ui/package.json ui/package-lock.json ./
5+
RUN npm ci
6+
COPY ui/ .
7+
RUN npm run build
8+
19
FROM golang:1.25-alpine AS build
210

311
WORKDIR /build
@@ -6,6 +14,7 @@ COPY go.mod go.sum ./
614
RUN go mod download
715

816
COPY . .
17+
COPY --from=uibuild /build/content/ content/
918
RUN CGO_ENABLED=0 GOOS=linux go build -o segdsp_worker .
1019

1120
FROM alpine:latest

content.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,21 @@ import (
99
//go:embed content/index.html
1010
var indexHTML []byte
1111

12-
//go:embed content/static
13-
var staticFS embed.FS
12+
//go:embed content/assets
13+
var assetsFS embed.FS
1414

1515
func content(w http.ResponseWriter, r *http.Request) {
16-
w.Header().Set("Content-Type", "text/html; charset=utf-8")
17-
_, _ = w.Write(indexHTML)
16+
if r.URL.Path == "/" {
17+
w.Header().Set("Content-Type", "text/html; charset=utf-8")
18+
_, _ = w.Write(indexHTML)
19+
return
20+
}
21+
assetsServer.ServeHTTP(w, r)
1822
}
1923

24+
var assetsServer http.Handler
25+
2026
func init() {
21-
sub, _ := fs.Sub(staticFS, "content/static")
22-
staticFileServer = http.FileServer(http.FS(sub))
27+
sub, _ := fs.Sub(assetsFS, "content/assets")
28+
assetsServer = http.StripPrefix("/assets/", http.FileServer(http.FS(sub)))
2329
}
24-
25-
var staticFileServer http.Handler

content/assets/index-B0TpdYkt.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

content/assets/index-DIhbbABO.css

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

content/index.html

Lines changed: 8 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,13 @@
11
<!DOCTYPE html>
22
<html lang="en">
33
<head>
4-
<meta charset="UTF-8">
5-
<title>SegDSP</title>
6-
<style type="text/css">
7-
body {
8-
padding: 0;
9-
margin: 0.5cm 0 0 0;
10-
background-color: #001111;
11-
color: white;
12-
font-family: Arial, serif;
13-
font-size: 15px;
14-
}
15-
.content {
16-
position: relative;
17-
margin: auto;
18-
padding: 10px;
19-
}
20-
.lockedBorder {
21-
-webkit-border-radius: 10px;
22-
-moz-border-radius: 10px;
23-
border-radius: 10px;
24-
border: 2px dashed goldenrod;
25-
}
26-
.locked {
27-
position: absolute;
28-
right: -12px;
29-
top: -12px;
30-
}
31-
.unlocked {
32-
position: absolute;
33-
right: -12px;
34-
top: -12px;
35-
display: none;
36-
}
37-
#headText {
38-
color: darkgreen;
39-
}
40-
#description {
41-
position: relative;
42-
padding: 0;
43-
line-height: 20px;
44-
}
45-
#avgTraffic {
46-
position: absolute;
47-
right: 10px;
48-
bottom: 0;
49-
}
50-
</style>
51-
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
52-
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
53-
<script type="text/javascript" src="/static/segdsp.js"></script>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
6+
<title>SegDSP</title>
7+
<script type="module" crossorigin src="/assets/index-B0TpdYkt.js"></script>
8+
<link rel="stylesheet" crossorigin href="/assets/index-DIhbbABO.css">
549
</head>
55-
<body onLoad="Init()">
56-
<div id="contentDiv" class="content" style="margin: auto; max-width: 400px">
57-
<div id="lockedImg" class="unlocked">
58-
<img src="/static/locked.png" width="24"/>
59-
</div>
60-
<div id="headText">Connecting...</div>
61-
<canvas id="fft" width="400" height="400"></canvas>
62-
<div id="description">
63-
Demodulator Mode: <span id="demodMode">FM</span><BR>
64-
Demodulator Level: <span id="channelLevel">0 dB</span><BR>
65-
Filter BW: <span id="filterBw">120 kHz</span><BR>
66-
Channel BW: <span id="channelBw">1.5 MHz</span><BR>
67-
FFT Center Frequency: <span id="fftFreq">106.3 MHz</span><BR>
68-
Channel Center Frequency: <span id="channelFreq">106.3 MHz</span><BR>
69-
<div id="avgTraffic">
70-
Avg. Traffic: 207.38 kb/s
71-
</div>
72-
</div>
73-
</div>
10+
<body>
11+
<div id="app"></div>
7412
</body>
75-
</html>
13+
</html>

content/static/locked.png

-10.6 KB
Binary file not shown.

0 commit comments

Comments
 (0)