Adding a Web Dashboard to a Windows Console App
A console bot only shows you logs if you keep the window open. Away from your desk? You're blind. This is how I added a real-time web dashboard using only net/http, keeping zero external dependencies and the result as a single binary.
Architecture — State Snapshot Polling
You don't need WebSockets for real-time. Build one endpoint (/api/state) that returns bot state as JSON, and let the browser poll it every few seconds. Simple, resilient—connection drops? Self-heals.
func apiState(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(GlobalDash.Snapshot())
}
The key: Snapshot() must be race-condition-free. HTTP handlers take concurrent requests; the bot engine updates state every tick.
Display State Owned by Actor Goroutine
Again, channels instead of mutexes. One actor goroutine owns the display state, and all updates and reads happen only through channels:
type Dash struct {
cmds chan func(*dashCore) // state mutations
query chan chan StateSnapshot // snapshot requests
}
func (d *Dash) run() {
core := &dashCore{}
for {
select {
case fn := <-d.cmds:
fn(core) // apply engine updates serially
case rep := <-d.query:
rep <- core.snapshot() // return a read-only copy
}
}
}
// Called by HTTP handler
func (d *Dash) Snapshot() StateSnapshot {
rep := make(chan StateSnapshot)
d.query <- rep
return <-rep
}
Engine pushes balances, positions, and signals via UpdateTick(...) every tick. Browser fetches /api/state and gets a safe copy. No locks needed. The same actor also keeps the latest 100 log lines, so memory never bloats infinitely.
External Access — Binding and Auth
To see the bot from outside your LAN, bind to all interfaces, not localhost:
addr := fmt.Sprintf("0.0.0.0:%d", port)
http.ListenAndServe(addr, mux)
But 0.0.0.0 exposes your bot control API to the internet. Add authentication middleware. If access_key is configured, validate it from query params or headers; if blank, skip auth (local-only mode):
func authMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if serverAccessKey == "" { // no key configured = unauthenticated (local only)
next(w, r)
return
}
provided := r.URL.Query().Get("key")
if provided == "" {
provided = r.Header.Get("X-Access-Key")
}
// Constant-time comparison: prevent timing attacks
if subtle.ConstantTimeCompare([]byte(provided), []byte(serverAccessKey)) != 1 {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next(w, r)
}
}
subtle.ConstantTimeCompare is crucial. If you use == to compare strings, the response time leaks how long the matching prefix is—an attacker could theoretically guess your key one character at a time via timing. Constant-time comparison plugs this hole. Wrap all routes:
mux.HandleFunc("/api/state", authMiddleware(apiState))
mux.HandleFunc("/api/force-exit", authMiddleware(apiForceExit))
Minimize exposure. If you truly don't need to control the bot from outside, open only read endpoints. For live trading, layer HTTPS reverse proxy or VPN in front for extra safety.
Bonus: Console Window Control from the Web
On Windows, you can show/hide the console window from the dashboard by calling user32.dll directly:
var user32 = syscall.NewLazyDLL("user32.dll")
var showWindow = user32.NewProc("ShowWindow")
const swHide, swShowMin = 0, 2
func hideMyConsole() bool {
hwnd, _, _ := kernel32.NewProc("GetConsoleWindow").Call()
if hwnd == 0 {
return false
}
showWindow.Call(hwnd, swHide)
return true
}
Wire an endpoint like /api/window?action=hide and you can hide the bot window from the browser—run it as a quiet tray app. Auto-minimize at startup for polish. Isolate OS-specific code with build tags (window_ctrl_windows.go) so other OS builds don't break.
Summary
- Real-time UI with state polling—no WebSocket plumbing needed
- Actor goroutine owns display state; no locks, fully thread-safe
- External access =
0.0.0.0+ constant-time auth—both required - OS-specific code isolated by build tags
A console bot, with a web dashboard running nowhere else. Big quality-of-life gain for minimal code.
This covers monitoring UI implementation and is unrelated to specific investment returns. It is technical documentation.
댓글
댓글 쓰기