Skip to content

Commit 5146dc9

Browse files
author
root
committed
webui: configurable title/url, GitHub link, af-prefixed latency
- header: configurable title (raw HTML) + optional link, default "<b>VXLAN</b> Controller" - header: hardcoded GitHub icon link to PoemaIX/vxlan-controller - latency matrix + topology labels now show af prefix (e.g. "v4 5.1") - web_ui defaults moved into DefaultControllerConfig, overlaid at load time
1 parent 2930470 commit 5146dc9

6 files changed

Lines changed: 100 additions & 17 deletions

File tree

pkg/config/autogen.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ type AutogenConfig struct {
3737

3838
type AutogenWebUI struct {
3939
BindAddr string `yaml:"bind_addr"`
40+
Title string `yaml:"title"`
41+
URL string `yaml:"url"`
4042
Nodes map[string]*WebUINodeFile `yaml:"nodes"`
4143
}
4244

@@ -216,6 +218,8 @@ func (ag *AutogenConfig) buildControllerConfig(name string, keys map[string]*aut
216218
}
217219
cfg.WebUI = &WebUIConfigFile{
218220
BindAddr: bindAddr,
221+
Title: ag.WebUI.Title,
222+
URL: ag.WebUI.URL,
219223
}
220224
if len(ag.WebUI.Nodes) > 0 {
221225
cfg.WebUI.Nodes = make(map[string]*WebUINodeFile, len(ag.WebUI.Nodes))

pkg/config/controller.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ type ControllerConfigFile struct {
3434

3535
type WebUIConfigFile struct {
3636
BindAddr string `yaml:"bind_addr"`
37+
Title string `yaml:"title"`
38+
URL string `yaml:"url"`
3739
MacAliases map[string]string `yaml:"mac_aliases"`
3840
Nodes map[string]*WebUINodeFile `yaml:"nodes"`
3941
}
@@ -90,6 +92,8 @@ type ControllerConfig struct {
9092

9193
type WebUIConfig struct {
9294
BindAddr string
95+
Title string
96+
URL string
9397
MacAliases map[string]string
9498
Nodes map[string]*WebUINode
9599
}
@@ -130,6 +134,11 @@ func LoadControllerConfig(path string) (*ControllerConfig, error) {
130134
raw := DefaultControllerConfig
131135
raw.AFSettings = nil // clear so user must specify
132136
raw.AllowedClients = nil // clear so user must specify
137+
// Deep-clone pointer defaults so yaml.Unmarshal doesn't mutate the global.
138+
if raw.WebUI != nil {
139+
w := *raw.WebUI
140+
raw.WebUI = &w
141+
}
133142
if err := yaml.Unmarshal(data, &raw); err != nil {
134143
return nil, fmt.Errorf("parse yaml: %w", err)
135144
}
@@ -265,6 +274,8 @@ func LoadControllerConfig(path string) (*ControllerConfig, error) {
265274
if raw.WebUI != nil && raw.WebUI.BindAddr != "" {
266275
wui := &WebUIConfig{
267276
BindAddr: raw.WebUI.BindAddr,
277+
Title: raw.WebUI.Title,
278+
URL: raw.WebUI.URL,
268279
MacAliases: raw.WebUI.MacAliases,
269280
}
270281
if len(raw.WebUI.Nodes) > 0 {

pkg/config/defaults.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,4 +156,7 @@ var DefaultControllerConfig = ControllerConfigFile{
156156
ClientName: "node-1",
157157
},
158158
},
159+
WebUI: &WebUIConfigFile{
160+
Title: "<b>VXLAN</b> Controller",
161+
},
159162
}

pkg/controller/webui.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ func (c *Controller) buildStateSnapshot() *webui.StateSnapshot {
1717

1818
// Config (static)
1919
if c.Config.WebUI != nil {
20+
snap.Config.Title = c.Config.WebUI.Title
21+
snap.Config.URL = c.Config.WebUI.URL
2022
snap.Config.MacAliases = c.Config.WebUI.MacAliases
2123
if c.Config.WebUI.Nodes != nil {
2224
snap.Config.Nodes = make(map[string]webui.UINodeJSON, len(c.Config.WebUI.Nodes))

pkg/webui/index.html

Lines changed: 78 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,10 @@
7777
display: flex;
7878
align-items: center;
7979
gap: 10px;
80+
text-decoration: none;
81+
color: inherit;
8082
}
83+
a.header-left:hover h1 { color: var(--accent); }
8184
.header-logo {
8285
width: 6px;
8386
height: 20px;
@@ -88,14 +91,28 @@
8891
.header h1 {
8992
font-family: var(--sans);
9093
font-size: 14px;
91-
font-weight: 600;
92-
color: var(--text);
94+
font-weight: 400;
95+
color: var(--text2);
9396
letter-spacing: 0.5px;
97+
transition: color 0.15s;
98+
}
99+
.header h1 b {
100+
font-weight: 700;
101+
color: var(--text);
102+
}
103+
.header-right {
104+
display: flex;
105+
align-items: center;
106+
gap: 14px;
94107
}
95-
.header h1 span {
108+
.header-github {
109+
display: flex;
110+
align-items: center;
96111
color: var(--text2);
97-
font-weight: 400;
112+
transition: color 0.15s;
98113
}
114+
.header-github:hover { color: var(--text); }
115+
.header-github svg { display: block; }
99116
.status-badge {
100117
display: flex;
101118
align-items: center;
@@ -450,14 +467,21 @@
450467
<body>
451468

452469
<div class="header">
453-
<div class="header-left">
470+
<div class="header-left" id="header-left">
454471
<div class="header-logo"></div>
455-
<h1>VXLAN <span>Controller</span></h1>
472+
<h1 id="header-title"><b>VXLAN</b> Controller</h1>
456473
</div>
457-
<div class="status-badge">
458-
<div class="status-dot" id="ws-dot"></div>
459-
<span id="ws-status">connecting</span>
460-
<span id="site-count"></span>
474+
<div class="header-right">
475+
<div class="status-badge">
476+
<div class="status-dot" id="ws-dot"></div>
477+
<span id="ws-status">connecting</span>
478+
<span id="site-count"></span>
479+
</div>
480+
<a class="header-github" href="https://github.com/PoemaIX/vxlan-controller" target="_blank" rel="noopener noreferrer" title="GitHub">
481+
<svg width="18" height="18" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
482+
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z"/>
483+
</svg>
484+
</a>
461485
</div>
462486
</div>
463487

@@ -657,23 +681,53 @@ <h3 id="site-detail-title"></h3>
657681
return 'lat-bad';
658682
}
659683

660-
function getLatency(srcId, dstId) {
684+
function getLatencyEntry(srcId, dstId) {
661685
if (!state?.latency_matrix) return null;
662686
for (const row of state.latency_matrix) {
663687
if (row.src === srcId) {
664688
for (const e of (row.entries || [])) {
665-
if (e.dst === dstId) return e.latency;
689+
if (e.dst === dstId) return e;
666690
}
667691
}
668692
}
669693
return null;
670694
}
671695

672696
// --- Render ---
697+
function renderHeader() {
698+
const titleEl = document.getElementById('header-title');
699+
const leftEl = document.getElementById('header-left');
700+
const title = state?.config?.title;
701+
if (title) titleEl.innerHTML = title;
702+
const url = state?.config?.url;
703+
const isAnchor = leftEl.tagName === 'A';
704+
if (url) {
705+
if (!isAnchor) {
706+
const a = document.createElement('a');
707+
a.id = 'header-left';
708+
a.className = 'header-left';
709+
a.target = '_blank';
710+
a.rel = 'noopener noreferrer';
711+
while (leftEl.firstChild) a.appendChild(leftEl.firstChild);
712+
leftEl.parentNode.replaceChild(a, leftEl);
713+
a.href = url;
714+
} else if (leftEl.getAttribute('href') !== url) {
715+
leftEl.setAttribute('href', url);
716+
}
717+
} else if (isAnchor) {
718+
const d = document.createElement('div');
719+
d.id = 'header-left';
720+
d.className = 'header-left';
721+
while (leftEl.firstChild) d.appendChild(leftEl.firstChild);
722+
leftEl.parentNode.replaceChild(d, leftEl);
723+
}
724+
}
725+
673726
function render() {
674727
if (!state) return;
675728
const online = state.clients.filter(c => c.online).length;
676729
document.getElementById('site-count').textContent = online + '/' + state.clients.length + ' sites';
730+
renderHeader();
677731
renderTopology();
678732
renderRouteTable();
679733
renderLatencyMatrix();
@@ -727,10 +781,14 @@ <h3 id="site-detail-title"></h3>
727781
const key = sorted.join('-');
728782
if (!linkSet.has(key) && nodeMap[sorted[0]] && nodeMap[sorted[1]]) {
729783
linkSet.add(key);
784+
const fwd = getLatencyEntry(sorted[0], sorted[1]);
785+
const rev = getLatencyEntry(sorted[1], sorted[0]);
730786
links.push({
731787
src: nodeMap[sorted[0]], dst: nodeMap[sorted[1]], af: e.af,
732-
latFwd: getLatency(sorted[0], sorted[1]),
733-
latRev: getLatency(sorted[1], sorted[0]),
788+
latFwd: fwd ? fwd.latency : null,
789+
latRev: rev ? rev.latency : null,
790+
afFwd: fwd ? fwd.af : '',
791+
afRev: rev ? rev.af : '',
734792
});
735793
}
736794
});
@@ -788,14 +846,16 @@ <h3 id="site-detail-title"></h3>
788846
// Always show both directions as normal
789847
if (fwdOk) {
790848
const isSPTDir = fwdInSPT;
791-
const txt = l.latFwd.toFixed(1) + ' ms';
849+
const afPart = l.afFwd ? l.afFwd + ' ' : '';
850+
const txt = afPart + l.latFwd.toFixed(1) + ' ms';
792851
const tw = txt.length * 5.2 + 8;
793852
html += '<rect class="topo-link-label-bg" x="'+(x1+ox-tw/2)+'" y="'+(y1+oy-7)+'" width="'+tw+'" height="14"/>';
794853
html += '<text class="topo-link-label" style="fill:'+(isSPTDir?'var(--green)':'#fff')+'" x="'+(x1+ox)+'" y="'+(y1+oy)+'">'+txt+'</text>';
795854
}
796855
if (revOk) {
797856
const isSPTDir = revInSPT;
798-
const txt = l.latRev.toFixed(1) + ' ms';
857+
const afPart = l.afRev ? l.afRev + ' ' : '';
858+
const txt = afPart + l.latRev.toFixed(1) + ' ms';
799859
const tw = txt.length * 5.2 + 8;
800860
html += '<rect class="topo-link-label-bg" x="'+(x2+ox-tw/2)+'" y="'+(y2+oy-7)+'" width="'+tw+'" height="14"/>';
801861
html += '<text class="topo-link-label" style="fill:'+(isSPTDir?'var(--green)':'#fff')+'" x="'+(x2+ox)+'" y="'+(y2+oy)+'">'+txt+'</text>';
@@ -909,7 +969,8 @@ <h3 id="site-detail-title"></h3>
909969
} else {
910970
const e = latMap[src.id+'|'+dst.id];
911971
if (e && e.latency != null && e.latency < 1e15) {
912-
h += '<td class="lat-cell '+latClass(e.latency)+'"><span>'+e.latency.toFixed(1)+'</span></td>';
972+
const afPart = e.af ? '<span style="color:var(--text3);margin-right:4px">'+e.af+'</span>' : '';
973+
h += '<td class="lat-cell '+latClass(e.latency)+'">'+afPart+'<span>'+e.latency.toFixed(1)+'</span></td>';
913974
} else {
914975
h += '<td class="lat-cell" style="color:var(--text3)">?</td>';
915976
}

pkg/webui/server.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ type RejectDetailJSON struct {
9696
}
9797

9898
type UIConfigJSON struct {
99+
Title string `json:"title"`
100+
URL string `json:"url,omitempty"`
99101
MacAliases map[string]string `json:"mac_aliases"`
100102
Nodes map[string]UINodeJSON `json:"nodes"`
101103
}

0 commit comments

Comments
 (0)