Skip to content

Commit 528983f

Browse files
committed
fix: nodes that don't serve traffic dont get a jump-link
1 parent 8916c0d commit 528983f

5 files changed

Lines changed: 94 additions & 4 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ PSSTD_ADVERTISE_HTTP=https://psstd.example.com/?psstd_node=node-a
143143

144144
Traefik supports query-param matchers in router rules, so this keeps link clicks and hot-potato refreshes on a single DNS name while still selecting a specific backend. Start from `deploy/traefik/single-host-query.yaml`.
145145

146+
That example also includes a low-priority host-only fallback route. If Traefik sees no matching `psstd_node` value, it sends the request to a shared `psstd-any` service instead of pinning fallback traffic to one node. Keep the `psstd-any` Endpoints list limited to nodes that should receive unrouted fallback traffic.
147+
146148
## Health Check
147149

148150
```bash

deploy/traefik/single-host-query.yaml

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#
1010
# Traefik routes by query parameter before forwarding the request, so hot-potato
1111
# refreshes and peer links still land on the intended node without per-node DNS.
12+
# Requests without a matching psstd_node value fall back to psstd-any. Keep that
13+
# Endpoints list limited to nodes that should receive unrouted fallback traffic.
1214
apiVersion: v1
1315
kind: Service
1416
metadata:
@@ -55,6 +57,30 @@ subsets:
5557
- name: http
5658
port: 8080
5759
---
60+
apiVersion: v1
61+
kind: Service
62+
metadata:
63+
name: psstd-any
64+
namespace: default
65+
spec:
66+
ports:
67+
- name: http
68+
port: 8080
69+
targetPort: 8080
70+
---
71+
apiVersion: v1
72+
kind: Endpoints
73+
metadata:
74+
name: psstd-any
75+
namespace: default
76+
subsets:
77+
- addresses:
78+
- ip: 10.0.1.25
79+
- ip: 10.0.1.26
80+
ports:
81+
- name: http
82+
port: 8080
83+
---
5884
apiVersion: traefik.io/v1alpha1
5985
kind: IngressRoute
6086
metadata:
@@ -80,6 +106,6 @@ spec:
80106
kind: Rule
81107
priority: 1
82108
services:
83-
- name: psstd-node-a
109+
- name: psstd-any
84110
port: 8080
85111
tls: {}

render.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ type cellData struct {
364364
URL string
365365
HTML template.HTML
366366
Offline bool
367+
Link bool
367368
}
368369

369370
type pageData struct {
@@ -392,12 +393,17 @@ func makeHandler(db *pebble.DB, selfName string) http.HandlerFunc {
392393
for _, s := range nodes {
393394
htmlBytes := ansihtml.ConvertToHTML([]byte(renderANSI(s)))
394395
offline := s.UpdatedAt == 0 || time.Since(time.Unix(0, s.UpdatedAt)) > 15*time.Second
396+
nodeURL := ""
397+
if s.WebURL != "" {
398+
nodeURL = pageURL(s.WebURL, displayQuery(r, winW, winH))
399+
}
395400

396401
cells = append(cells, cellData{
397402
Name: s.Name,
398-
URL: pageURL(s.WebURL, displayQuery(r, winW, winH)),
403+
URL: nodeURL,
399404
HTML: template.HTML(htmlBytes),
400405
Offline: offline,
406+
Link: nodeURL != "",
401407
})
402408
}
403409

render_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package main
2+
3+
import (
4+
"net/http/httptest"
5+
"strings"
6+
"testing"
7+
"time"
8+
)
9+
10+
func TestDashboardDoesNotLinkNodesWithoutWebURL(t *testing.T) {
11+
db := openTestDB(t)
12+
now := time.Now().UnixNano()
13+
nodes := []NodeStats{
14+
{
15+
Name: "sync-only",
16+
Version: appVersion,
17+
UpdatedAt: now,
18+
CPU: []float64{10},
19+
MemTotal: 100,
20+
MemUsed: 25,
21+
},
22+
{
23+
Name: "web-node",
24+
Version: appVersion,
25+
WebURL: "https://psstd.example.com/?psstd_node=web-node",
26+
UpdatedAt: now,
27+
CPU: []float64{20},
28+
MemTotal: 100,
29+
MemUsed: 30,
30+
},
31+
}
32+
for _, node := range nodes {
33+
if err := dbSet(db, node); err != nil {
34+
t.Fatalf("set %s: %v", node.Name, err)
35+
}
36+
}
37+
38+
req := httptest.NewRequest("GET", "/?theme=dark&palette=monochrome", nil)
39+
rr := httptest.NewRecorder()
40+
makeHandler(db, "web-node").ServeHTTP(rr, req)
41+
42+
if rr.Code != 200 {
43+
t.Fatalf("status = %d, want 200", rr.Code)
44+
}
45+
body := rr.Body.String()
46+
if !strings.Contains(body, `<span class="node-name">sync-only</span>`) {
47+
t.Fatalf("sync-only node was not rendered as non-link text:\n%s", body)
48+
}
49+
if strings.Contains(body, `<a href="/">sync-only</a>`) || strings.Contains(body, `>sync-only</a>`) {
50+
t.Fatalf("sync-only node rendered as a link:\n%s", body)
51+
}
52+
if !strings.Contains(body, `>web-node</a>`) {
53+
t.Fatalf("web node was not rendered as a link:\n%s", body)
54+
}
55+
}

templates/dashboard.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,8 @@
238238
.cell-header {
239239
margin-bottom: 8px;
240240
}
241-
.cell-header a {
241+
.cell-header a,
242+
.cell-header .node-name {
242243
color: var(--accent);
243244
text-decoration: none;
244245
font-weight: 700;
@@ -335,7 +336,7 @@
335336
</div>
336337
{{range .Nodes}}
337338
<div class="cell{{if .Offline}} offline{{end}}">
338-
<div class="cell-header"><a href="{{.URL}}">{{.Name}}</a></div>
339+
<div class="cell-header">{{if .Link}}<a href="{{.URL}}">{{.Name}}</a>{{else}}<span class="node-name">{{.Name}}</span>{{end}}</div>
339340
<pre>{{.HTML}}</pre>
340341
</div>
341342
{{end}}

0 commit comments

Comments
 (0)