{{define "configure-interfaces.html"}} {{template "base" .}} {{end}} {{define "content"}}
{{if .Loading}}
Downloading YANG schema from device… this takes a moment on first login.
{{template "configure-toolbar" .}}
{{else}} {{$d := .Desc}} {{if .Error}}
{{.Error}}
{{end}}
Interfaces
{{range $i, $r := .Interfaces}} {{/* ── Fold-out edit form ───────────────────────────────────────── */}} {{end}}{{/* range Interfaces */}}
Name Type Status Member of Config
{{.TypeDisplay}} {{if eq .OperStatus "up"}} up {{else if eq .OperStatus "down"}} down {{else}} {{.OperStatus}} {{end}} {{if .MemberOf}}{{.MemberOf}}{{else}}{{end}} {{range .ConfigTags}}{{.}}{{end}} {{if .AddrSummary}}{{.AddrSummary}}{{end}} {{if and (not .ConfigTags) (not .AddrSummary)}}{{end}} {{if ne .TypeSlug "loopback"}} {{end}}
{{/* ── General ──────────────────────────────────────────── */}} {{$ip := printf "/ietf-interfaces:interfaces/interface=%s" .Name}}

General

Description{{template "field-info" (index $d "description")}}
Enabled{{template "field-info" (index $d "enabled")}}
MAC address{{template "field-info" (index $d "mac")}}
{{/* ── IP Addresses ──────────────────────────────────────── */}} {{if .HasIP}}

IP Addresses

{{/* IPv4. Toggles, DHCP foldout, address list, and add-address row visually compose one IPv4 group; the Save button at the bottom is bound to the toggles form via the HTML5 `form` attribute, so users see the whole group before clicking Save. */}} {{template "iface-ipv4-block" .}} {{/* IPv6 — mirrors the IPv4 form structure above. */}} {{/* IPv6 — mirrors the IPv4 layout above. */}} {{template "iface-ipv6-block" .}}
{{end}}{{/* HasIP */}} {{/* ── Bridge port editor ──────────────────────────────── */}} {{if .IsBridgePort}}

Bridge Port Settings

{{$bpPath := printf "/ietf-interfaces:interfaces/interface=%s/infix-interfaces:bridge-port" .Name}} {{if .ParentBridgeIs8021Q}} {{end}}
Bridge{{template "field-info" (index $d "bp-bridge")}}
PVID{{template "field-info" (index $d "bp-pvid")}}
Flood unknown{{template "field-info" (index $d "bp-flood")}}
Multicast router{{template "field-info" (index $d "bp-mc-router")}} {{/* YANG default is auto. */}} {{$mr := "auto"}}{{if and .BridgePort .BridgePort.Multicast .BridgePort.Multicast.Router}}{{$mr = .BridgePort.Multicast.Router}}{{end}}
Multicast fast-leave{{template "field-info" (index $d "bp-mc-fast-leave")}} {{/* Hidden companion lets the handler distinguish "form omitted the field" from "user unchecked". */}}
{{end}}{{/* IsBridgePort */}} {{/* ── LAG port editor ─────────────────────────────────── */}} {{if .IsLagPort}}

LAG Port Settings

LACP state is operational-only; configure the LAG itself on its own row.

LAG{{template "field-info" (index $d "lp-lag")}}
{{end}}{{/* IsLagPort */}} {{/* ── Ethernet ────────────────────────────────────────── */}} {{if eq .TypeSlug "ethernet"}} {{$ep := printf "%s/ieee802-ethernet-interface:ethernet" $ip}}

Ethernet

Auto-negotiation{{template "field-info" (index $d "eth-autoneg")}}
Advertised speeds{{template "field-info" (index $d "eth-advertised")}} {{if .EthSupported}} {{$advertised := .EthAdvertised}}
{{if $advertised}} {{range $i, $v := $advertised}}{{if $i}}, {{end}}{{shortPMD $v}}{{end}} {{else}}(All){{end}}
{{range .EthSupported}} {{$cur := .}} {{$selected := false}} {{range $advertised}}{{if eq . $cur}}{{$selected = true}}{{end}}{{end}} {{end}}
{{else}} No supported-PMD data from device. {{end}}
Duplex{{template "field-info" (index $d "eth-duplex")}}
MDI-X{{template "field-info" (index $d "eth-mdix")}}
{{end}}{{/* TypeSlug ethernet */}} {{/* ── WiFi interface editor ───────────────────────────── */}} {{/* Radio + PSK pickers mirror the Add-Interface wizard pattern: each picker has Edit / + New buttons that toggle an inline form (radio: name/country/band/ channel; PSK: name/passphrase). The forms POST to the wizard endpoints and HTMX swaps the picker {{if and .WiFi .WiFi.AccessPoint}} {{$ap := .WiFi.AccessPoint}} {{else if and .WiFi .WiFi.Station}} {{$sta := .WiFi.Station}} {{end}}
Radio{{template "field-info" (index $d "wifi-radio")}}
{{template "wizard-radio-picker.html" (dict "Radios" $.WizardWifiRadios "Selected" $curRadio "ID" $radioPickerID)}}
SSID{{template "field-info" (index $d "wifi-ssid")}}
Hidden SSID{{template "field-info" (index $d "wifi-hidden")}}
Security mode{{template "field-info" (index $d "wifi-sec-mode")}} {{$sm := "wpa2-wpa3-personal"}}{{if and $ap.Security $ap.Security.Mode}}{{$sm = $ap.Security.Mode}}{{end}}
SSID{{template "field-info" (index $d "wifi-ssid")}}
Security mode{{template "field-info" (index $d "wifi-sec-mode")}} {{$sm := "auto"}}{{if and $sta.Security $sta.Security.Mode}}{{$sm = $sta.Security.Mode}}{{end}}
PSK{{template "field-info" (index $d "wifi-secret")}}
{{template "wizard-psk-picker.html" (dict "Keys" $.WizardSymKeys "Selected" $curSecret "ID" $pskPickerID)}}
{{end}}{{/* IsWifi */}} {{/* ── Bridge editor ────────────────────────────────────── */}} {{if .IsBridge}}
{{/* Bridge settings */}}

Bridge Settings

Type{{template "field-info" (index $d "bridge-type")}}
Ports {{if $r.PortCandidates}}
{{range $r.PortCandidates}} {{end}}
{{else}} No candidate port interfaces available. {{end}}
{{$brPath := printf "/ietf-interfaces:interfaces/interface=%s/infix-interfaces:bridge" .Name}} {{/* STP — own form, save inside the fold-out so the section is self-contained. YANG defaults are rendered inline (value="..." not a placeholder) so the user sees the actual default; the reset button drops the candidate value and lets YANG re-apply. */}}
Spanning Tree Protocol
Protocol{{template "field-info" (index $d "stp-force")}} {{$stpForce := "rstp"}}{{if and .Bridge .Bridge.STP.ForceProtocol}}{{$stpForce = .Bridge.STP.ForceProtocol}}{{end}}
Hello time (s){{template "field-info" (index $d "stp-hello")}}
Forward delay (s){{template "field-info" (index $d "stp-fwd-delay")}}
Max age (s){{template "field-info" (index $d "stp-max-age")}}
Transmit hold count{{template "field-info" (index $d "stp-hold-count")}}
Max hops{{template "field-info" (index $d "stp-max-hops")}}
{{/* Multicast Snooping — parallel STP layout. YANG defaults inline: querier=auto, query-interval=125. */}}
Multicast Snooping
Snooping{{template "field-info" (index $d "mc-snoop")}}
Querier{{template "field-info" (index $d "mc-querier")}} {{$q := "auto"}}{{if and .Bridge .Bridge.Multicast .Bridge.Multicast.Querier}}{{$q = .Bridge.Multicast.Querier}}{{end}}
Query interval (s){{template "field-info" (index $d "mc-query-int")}}
{{/* 802.1Q VLANs — matrix layout: rows = VLAN, columns = bridge ports + self, cells = U/T/-. Click VID to expand the existing per-port checkbox editor. */}} {{if .BridgeIs8021Q}}

VLANs

{{range $r.BridgeMembers}}{{end}} {{range $vi, $vlan := .VLANRows}} {{range $r.BridgeMembers}} {{if index $vlan.UntaggedSet .}} {{else if index $vlan.TaggedSet .}} {{else}}{{end}} {{end}} {{if index $vlan.UntaggedSet $r.Name}} {{else if index $vlan.TaggedSet $r.Name}} {{else}}{{end}} {{end}} {{if not .VLANRows}} {{end}} {{/* Add VLAN inline row */}}
VID{{.}}{{$r.Name}}
(self)
UTUT
Untagged
{{range $r.BridgeMembers}} {{end}}
Tagged
{{range $r.BridgeMembers}} {{end}}
No VLANs configured
{{end}}{{/* BridgeIs8021Q */}}
{{end}}{{/* IsBridge */}} {{/* ── LAG editor ───────────────────────────────────────── */}} {{if .IsLag}}

Members

{{if $r.PortCandidates}}
{{range $r.PortCandidates}} {{end}}
{{else}}

No candidate port interfaces available.

{{end}}

LAG Settings

{{$lagMode := ""}}{{if and .Lag .Lag.Mode}}{{$lagMode = .Lag.Mode}}{{end}} {{if $.LagHashOptions}} {{end}} {{/* LACP options — hidden unless mode=lacp via JS or always shown */}}
Mode{{template "field-info" (index $d "lag-mode")}}
{{range $.LagModeOptions}} {{end}}
Hash policy{{template "field-info" (index $d "lag-hash")}} {{$lagHash := ""}}{{if and .Lag .Lag.Hash}}{{$lagHash = .Lag.Hash}}{{end}}
LACP mode{{template "field-info" (index $d "lacp-mode")}} {{$lacpMode := ""}}{{if and .Lag .Lag.LACP.Mode}}{{$lacpMode = .Lag.LACP.Mode}}{{end}}
LACP rate{{template "field-info" (index $d "lacp-rate")}} {{$lacpRate := ""}}{{if and .Lag .Lag.LACP.Rate}}{{$lacpRate = .Lag.LACP.Rate}}{{end}}
System priority{{template "field-info" (index $d "lacp-sysprio")}}
{{end}}{{/* IsLag */}} {{/* key-detail-body */}}
{{/* ── Add Interface modal dialog ─────────────────────────────────────── One
, multiple per-type
. The type pulldown enables exactly one fieldset so only its inputs submit. Bridge type adds a vlan-filtering radio (default plain). Ethernet uses a datalist-backed input that doubles as a "Restore deleted interface" picker for free-form names and known unconfigured interfaces alike. Unsupported types share one panel pointing to Advanced YANG tree. */}}

Add Interface

Interface Type{{template "field-info" (index $d "type")}}
{{/* Bridge */}} {{/* LAG */}} {{/* VLAN — name auto-built from . via JS as the user changes either input; user can still override by typing. */}} {{/* Ethernet — re-bind a previously deleted physical interface. Ethernet names are tied to detected hardware, so this is a plain {{if .UnconfiguredPhysical}} {{range .UnconfiguredPhysical}}{{end}} {{else}} {{end}}
{{/* VETH */}} {{/* Dummy */}} {{/* Loopback */}} {{/* GRE — point-to-point tunnel. tunnel-common requires local and remote IPs of the same family. */}} {{/* GRETAP — like GRE but carries Ethernet frames. Same backend container (infix-interfaces:gre); YANG when-clause discriminates by if:type. */}} {{/* VXLAN — VNI mandatory (0..16777215); UDP port defaults to 4789. */}} {{/* WireGuard — private-key references a keystore asymmetric-key by name. The YANG `must` clause additionally enforces X25519 format at commit; we expose all asym keys here and let the backend validate. Inline keystore key creation is a Phase 4 follow-up (same keystore-integration work as the WiFi PSK picker). */}} {{/* WiFi — radio reference is mandatory; mode (Station / AP) drives which security mode options apply and whether the PSK picker shows. AP-only fields (hidden checkbox + AP security modes) are swapped in by JS based on the mode radio. */}} {{/* Unsupported types fall here: "other" only now. */}}
{{template "configure-toolbar" .}} {{end}}{{/* else schema ready */}} {{end}} {{define "iface-ipv4-block"}}
{{$ipv4Form := printf "ipv4-form-%s" $.Name}} {{$ipv4Fold := printf "ipv4-dhcp-%s" $.Name}}
IPv4{{template "field-info" (index $.Desc "ipv4-address")}}
{{/* DHCPv4 foldout — always in the DOM, hidden when DHCP is off. The data-fold-target hook on the DHCP checkbox toggles this without waiting for a server round-trip so the foldout is discoverable before clicking Save. */}}
DHCP Settings & Options
Client ID
ARP check
Route preference
DHCP Options
{{if .IPv4.DHCP.Options}} {{range .IPv4.DHCP.Options}} {{end}}
OptionValue
{{.ID}} {{if .Value}}{{.Value}}{{else if .Hex}}hex:{{.Hex}}{{else}}{{end}}
{{end}} {{if $.DHCPv4Options}}
{{end}}
{{if and .IPv4 .IPv4.Address}} {{range .IPv4.Address}} {{end}}
{{.IP}}/{{.PrefixLength}} {{if .Origin}}{{.Origin}}{{end}} {{if or (eq .Origin "") (eq .Origin "static")}} {{end}}
{{else}}

No IPv4 addresses configured.

{{end}}
/
{{end}} {{define "iface-ipv6-block"}}
{{$ipv6Form := printf "ipv6-form-%s" $.Name}} {{$ipv6Fold := printf "ipv6-dhcp-%s" $.Name}}
IPv6{{template "field-info" (index $.Desc "ipv6-address")}}
DHCPv6 Settings & Options
DUID
Information only
Route preference
DHCPv6 Options
{{if .IPv6.DHCPv6.Options}} {{range .IPv6.DHCPv6.Options}} {{end}}
OptionValue
{{.ID}} {{if .Value}}{{.Value}}{{else if .Hex}}hex:{{.Hex}}{{else}}{{end}}
{{end}} {{if $.DHCPv6Options}}
{{end}}
{{if and .IPv6 .IPv6.Address}} {{range .IPv6.Address}} {{end}}
{{.IP}}/{{.PrefixLength}} {{if .Origin}}{{.Origin}}{{end}} {{if or (eq .Origin "") (eq .Origin "static")}} {{end}}
{{else}}

No IPv6 addresses configured.

{{end}}
/
{{end}}