webui: type-aware Config column in interface overview

The collapsed Interfaces list left DHCP/SLAAC/zeroconf invisible until a
row was unfolded, so L3 interfaces showed only empty cells. Replace the
Addresses column with a Config column whose pills are chosen by role,
mirroring the Status > Interfaces data column but slanted to configured
intent: vid for VLANs, mode for WiFi, 802.1Q for filtering bridges, and
DHCPv4/DHCPv6/SLAAC/Zeroconf for IP interfaces, alongside any static
addresses.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2026-06-15 19:45:28 +02:00
parent a78eeee651
commit 9272ae382d
3 changed files with 64 additions and 2 deletions
@@ -94,6 +94,7 @@ type cfgIfaceRow struct {
AdminEnabled bool // true when enabled leaf absent (YANG default) or explicitly true
MemberOf string // bridge or lag name this interface belongs to
AddrSummary string
ConfigTags []string // type-aware overview pills (DHCP, SLAAC, vid N, …)
BridgeMembers []string // interface names that are ports of this bridge/lag
BridgeMemberSet map[string]bool // for checkbox pre-selection
PortCandidates []string // free ports + current members of this bridge/lag
@@ -2042,6 +2043,7 @@ func (h *ConfigureInterfacesHandler) buildRows(ifaces []ifaceJSON, oper []ifaceJ
}
}
row.AddrSummary = addrSummary(iface)
row.ConfigTags = configSummary(&row)
if row.IsBridge {
members := bridgeMembers[iface.Name]
@@ -2647,3 +2649,40 @@ func addrSummary(iface ifaceJSON) string {
return fmt.Sprintf("%d addresses", len(addrs))
}
}
// configSummary builds the type-aware overview pills for an interface in the
// Configure list. It mirrors the Status > Interfaces "Data" column but is
// slanted to configured intent rather than operational result: each role
// contributes the properties worth seeing without unfolding the row — IP
// acquisition (DHCP, SLAAC, zeroconf) for L3 interfaces, the tag id for VLANs,
// the radio mode for WiFi, and 802.1Q for VLAN-filtering bridges. Static
// addresses are rendered separately via AddrSummary.
func configSummary(row *cfgIfaceRow) []string {
var tags []string
if row.IsVlan && row.Vlan != nil {
tags = append(tags, fmt.Sprintf("vid %d", row.Vlan.ID))
}
if row.IsWifi && row.WifiMode != "" {
tags = append(tags, row.WifiMode)
}
if row.IsBridge && row.BridgeIs8021Q {
tags = append(tags, "802.1Q")
}
if row.HasIP {
if row.DHCPv4Enabled {
tags = append(tags, "DHCPv4")
}
if row.IPv4 != nil && row.IPv4.Autoconf != nil {
tags = append(tags, "Zeroconf")
}
if row.DHCPv6Enabled {
tags = append(tags, "DHCPv6")
}
if row.IPv6 != nil && row.IPv6.SLAACv6 != nil {
tags = append(tags, "SLAAC")
}
}
return tags
}
+19
View File
@@ -1099,6 +1099,25 @@ details[open].nav-group-top > summary.nav-group-summary-top::before {
.badge-drop { background: var(--slate-100); color: var(--slate-600); }
.badge-continue { background: #dbeafe; color: #1e40af; }
/* Type-aware Config column pills in the Configure > Interfaces overview. */
.iface-cfg-cell { white-space: normal; }
.iface-tag {
display: inline-block;
margin: 0.1rem 0.25rem 0.1rem 0;
padding: 0.05rem 0.4rem;
border-radius: var(--radius-sm);
background: var(--slate-100);
color: var(--slate-600);
font-size: 0.7rem;
font-weight: 600;
white-space: nowrap;
}
.iface-cfg-addr { color: var(--slate-600); }
.dark .iface-tag { background: var(--slate-800); color: var(--slate-300); }
@media (prefers-color-scheme: dark) {
.iface-tag { background: var(--slate-800); color: var(--slate-300); }
}
@media (prefers-color-scheme: dark) {
.badge-accept { background: rgba(22, 163, 74, 0.2); color: #86efac; }
.badge-reject { background: rgba(220, 38, 38, 0.2); color: #fca5a5; }
@@ -30,7 +30,7 @@
<th>Type</th>
<th>Status</th>
<th>Member of</th>
<th>Addresses</th>
<th>Config</th>
<th></th>
</tr>
</thead>
@@ -54,7 +54,11 @@
{{end}}
</td>
<td>{{if .MemberOf}}{{.MemberOf}}{{else}}<span class="text-muted"></span>{{end}}</td>
<td>{{if .AddrSummary}}{{.AddrSummary}}{{else}}<span class="text-muted"></span>{{end}}</td>
<td class="iface-cfg-cell">
{{range .ConfigTags}}<span class="iface-tag">{{.}}</span>{{end}}
{{if .AddrSummary}}<span class="iface-cfg-addr">{{.AddrSummary}}</span>{{end}}
{{if and (not .ConfigTags) (not .AddrSummary)}}<span class="text-muted"></span>{{end}}
</td>
<td style="text-align:right">
{{if ne .TypeSlug "loopback"}}
<button type="button" class="btn-icon btn-icon-danger"