package cli import ( "sort" "strings" "sync" "time" ) // TerminalWriter is an interface satisfied by *terminal.Terminal and mock terminals. type TerminalWriter interface { Write(buf []byte) (n int, err error) } // Node represents a command or subsection in the CLI hierarchy. type Node struct { Name string Aliases []string Description string Children []*Node Dynamic func() []string } // AddChild appends a child node to this node and returns the child. func (n *Node) AddChild(child *Node) *Node { n.Children = append(n.Children, child) return child } // FindChild searches for a child matching token by Name, Aliases, or unique prefix. func (n *Node) FindChild(token string) *Node { token = strings.ToLower(token) // 1. Exact Name match for _, ch := range n.Children { if strings.ToLower(ch.Name) == token { return ch } } // 2. Exact Aliases match for _, ch := range n.Children { for _, a := range ch.Aliases { if strings.ToLower(a) == token { return ch } } } // 3. Unique prefix match among children (excluding parameters ending in '=') var match *Node for _, ch := range n.Children { if !strings.HasSuffix(ch.Name, "=") && strings.HasPrefix(strings.ToLower(ch.Name), token) { if match != nil { return nil // Ambiguous } match = ch } } return match } // Completer manages tab-completion state and double-tab candidate listing. type Completer struct { term TerminalWriter executor *Executor root *Node termWidth int mu sync.Mutex lastTabTime time.Time lastTabLine string lastTabPos int } // NewCompleter creates and initializes a Completer with the full command tree. func NewCompleter(term TerminalWriter, executor *Executor) *Completer { c := &Completer{ term: term, executor: executor, termWidth: 80, } c.root = c.buildCommandTree() return c } // SetWidth sets the terminal width for candidate column formatting. func (c *Completer) SetWidth(w int) { c.mu.Lock() defer c.mu.Unlock() if w > 20 { c.termWidth = w } } // Callback is the AutoCompleteCallback for golang.org/x/term.Terminal. func (c *Completer) Callback(line string, pos int, key rune) (newLine string, newPos int, ok bool) { if key != '\t' { return "", 0, false } c.mu.Lock() defer c.mu.Unlock() candidates, prefixBeforeWord, currentWord, isParamVal, paramPrefix := c.getCompletions(line, pos) if len(candidates) == 0 { // Nothing to complete c.lastTabTime = time.Time{} c.lastTabLine = "" c.lastTabPos = 0 return "", 0, false } suffix := "" if pos < len(line) { suffix = line[pos:] } // Single match: complete it immediately! if len(candidates) == 1 { c.lastTabTime = time.Time{} c.lastTabLine = "" c.lastTabPos = 0 match := candidates[0] completedToken := match if isParamVal { completedToken = paramPrefix + match + " " } else if strings.HasSuffix(match, "=") { // Parameter key, e.g. "address=" -> no trailing space completedToken = match } else { completedToken = match + " " } newLine = prefixBeforeWord + completedToken + suffix newPos = len(prefixBeforeWord) + len(completedToken) return newLine, newPos, true } // Multiple candidates: find Longest Common Prefix (LCP) lcp := longestCommonPrefix(candidates) targetWord := currentWord if isParamVal { targetWord = currentWord[len(paramPrefix):] } // If LCP can extend the current input, expand it if len(lcp) > len(targetWord) { extendedToken := lcp if isParamVal { extendedToken = paramPrefix + lcp } newLine = prefixBeforeWord + extendedToken + suffix newPos = len(prefixBeforeWord) + len(extendedToken) // Record tab state so an immediate second tab shows remaining options c.lastTabTime = time.Now() c.lastTabLine = newLine c.lastTabPos = newPos return newLine, newPos, true } // LCP cannot extend input further: check for 2nd Tab (within 1000ms) isSecondTab := time.Since(c.lastTabTime) <= 1000*time.Millisecond && c.lastTabLine == line && c.lastTabPos == pos if !isSecondTab { // 1st Tab: Record state and wait for potential 2nd Tab c.lastTabTime = time.Now() c.lastTabLine = line c.lastTabPos = pos return line, pos, true } // 2nd Tab: DISPLAY ALL CANDIDATES! c.lastTabTime = time.Time{} c.lastTabLine = "" c.lastTabPos = 0 if c.term != nil { formatted := c.formatCandidates(candidates) _, _ = c.term.Write([]byte(formatted)) } return line, pos, true } // getCompletions computes the candidate completions for the current cursor context. func (c *Completer) getCompletions(line string, pos int) (candidates []string, prefixBeforeWord string, currentWord string, isParamVal bool, paramPrefix string) { if pos > len(line) { pos = len(line) } input := line[:pos] lastSpace := strings.LastIndexAny(input, " \t") if lastSpace == -1 { // Single word so far (or empty) if strings.HasPrefix(input, "/") { prefixBeforeWord = "/" currentWord = input[1:] } else { prefixBeforeWord = "" currentWord = input } } else { prefixBeforeWord = input[:lastSpace+1] currentWord = input[lastSpace+1:] } // Parse preceding words to navigate tree rawPrefix := strings.TrimSpace(prefixBeforeWord) hasSlash := strings.HasPrefix(rawPrefix, "/") if hasSlash { rawPrefix = strings.TrimPrefix(rawPrefix, "/") } var precedingTokens []string if rawPrefix != "" { precedingTokens = strings.Fields(rawPrefix) } // Traverse tree to find current node and track used parameter keys currNode := c.root usedParams := make(map[string]bool) for _, token := range precedingTokens { if idx := strings.Index(token, "="); idx != -1 { // Parameter key=value, stay at current node key := strings.ToLower(token[:idx+1]) usedParams[key] = true continue } next := currNode.FindChild(token) if next != nil { currNode = next } } // Check if user is typing a parameter value (e.g. "interface=eth" or "role=") if idx := strings.Index(currentWord, "="); idx != -1 { paramKey := strings.ToLower(currentWord[:idx+1]) paramVal := strings.ToLower(currentWord[idx+1:]) paramChild := currNode.FindChild(paramKey) if paramChild != nil && paramChild.Dynamic != nil { isParamVal = true paramPrefix = currentWord[:idx+1] for _, val := range paramChild.Dynamic() { if strings.HasPrefix(strings.ToLower(val), paramVal) { candidates = append(candidates, val) } } sort.Strings(candidates) return } } // Gather child commands and parameters search := strings.ToLower(currentWord) for _, ch := range currNode.Children { nameLower := strings.ToLower(ch.Name) if strings.HasSuffix(nameLower, "=") { if usedParams[nameLower] { continue // parameter already provided } } if strings.HasPrefix(nameLower, search) { candidates = append(candidates, ch.Name) } } // Check if current node has dynamic positional choices (e.g. interface names or service names) if currNode.Dynamic != nil { for _, dyn := range currNode.Dynamic() { if strings.HasPrefix(strings.ToLower(dyn), search) { candidates = append(candidates, dyn) } } } // Deduplicate and sort candidates = uniqueStrings(candidates) sort.Strings(candidates) return } func (c *Completer) formatCandidates(candidates []string) string { if len(candidates) == 0 { return "" } termWidth := c.termWidth if termWidth < 40 { termWidth = 80 } maxLen := 0 for _, cand := range candidates { if len(cand) > maxLen { maxLen = len(cand) } } colWidth := maxLen + 3 if colWidth < 10 { colWidth = 10 } numCols := (termWidth - 4) / colWidth if numCols < 1 { numCols = 1 } if numCols > len(candidates) { numCols = len(candidates) } numRows := (len(candidates) + numCols - 1) / numCols var sb strings.Builder sb.WriteString("\n") for r := 0; r < numRows; r++ { sb.WriteString(" ") for col := 0; col < numCols; col++ { idx := col*numRows + r if idx < len(candidates) { cand := candidates[idx] colored := colorizeCandidate(cand) sb.WriteString(colored) if col < numCols-1 && (col+1)*numRows+r < len(candidates) { pad := colWidth - len(cand) if pad > 0 { sb.WriteString(strings.Repeat(" ", pad)) } } } } sb.WriteString("\n") } return sb.String() } func colorizeCandidate(s string) string { if strings.HasSuffix(s, "=") { return ColorYellow + s + ColorReset } switch s { case "print", "add", "remove", "enable", "disable", "set", "reboot", "flush", "renew", "release", "connect", "disconnect", "status": return ColorGreen + s + ColorReset case "interface", "ip", "routing", "tool", "log", "user", "system", "certificate", "certificates", "vlan", "bridge", "wireguard", "eoip", "vxlan", "vrrp", "pppoe-client", "address", "route", "dhcp-client", "dhcp-server", "dns", "firewall", "service", "reverse-proxy", "sdwan", "sd-wan", "member", "members", "filter", "nat", "mangle", "address-list", "bgp", "ospf", "port", "peers", "lease", "network", "cache", "static", "rule", "table", "instance", "area", "neighbor", "resource", "identity": return ColorCyan + s + ColorReset case "www", "www-ssl", "ssh", "telnet", "web", "web-ssl": return ColorMagenta + s + ColorReset default: return ColorWhite + s + ColorReset } } func longestCommonPrefix(strs []string) string { if len(strs) == 0 { return "" } prefix := strs[0] for _, s := range strs[1:] { for !strings.HasPrefix(strings.ToLower(s), strings.ToLower(prefix)) { if len(prefix) == 0 { return "" } prefix = prefix[:len(prefix)-1] } } return prefix } func uniqueStrings(slice []string) []string { seen := make(map[string]bool) var result []string for _, s := range slice { if !seen[s] { seen[s] = true result = append(result, s) } } return result } // buildCommandTree creates the full tree of commands, subsections, actions, parameters, and dynamic providers. func (c *Completer) buildCommandTree() *Node { root := &Node{Name: "root"} // Helper for dynamic interfaces dynInterfaces := func() []string { if c.executor != nil && c.executor.netMgr != nil { ifaces, err := c.executor.netMgr.ListInterfaces() if err == nil { var names []string for _, iface := range ifaces { names = append(names, iface.Name) } return names } } return []string{"eth0", "eth1", "lo"} } // Helper for services dynServices := func() []string { if c.executor != nil && c.executor.servicesMgr != nil { var names []string for _, s := range c.executor.servicesMgr.ListServices() { names = append(names, s.Name) } return names } return []string{"www", "www-ssl", "telnet", "ssh"} } // Helper for certificates dynCertificates := func() []string { if c.executor != nil && c.executor.certMgr != nil { var names []string for _, cert := range c.executor.certMgr.ListCertificates() { names = append(names, cert.Name) } return names } return nil } dynCAs := func() []string { if c.executor != nil && c.executor.certMgr != nil { var names []string for _, cert := range c.executor.certMgr.ListCertificates() { if cert.IsCA { names = append(names, cert.Name) } } return names } return nil } dynSDWANGroups := func() []string { if c.executor != nil && c.executor.sdwanMgr != nil { var names []string for _, g := range c.executor.sdwanMgr.List() { names = append(names, g.Name) } return names } return nil } // Common standard actions addPrint := func(n *Node) { n.AddChild(&Node{Name: "print", Aliases: []string{"pr", "show", "list"}}) } addRemove := func(n *Node) { n.AddChild(&Node{Name: "remove", Aliases: []string{"rem", "delete"}}) } addEnableDisable := func(n *Node) { n.AddChild(&Node{Name: "enable"}) n.AddChild(&Node{Name: "disable"}) } // 1. /interface ifaceNode := root.AddChild(&Node{Name: "interface", Aliases: []string{"interfaces"}}) addPrint(ifaceNode) ifaceNode.AddChild(&Node{Name: "enable", Dynamic: dynInterfaces}) ifaceNode.AddChild(&Node{Name: "disable", Dynamic: dynInterfaces}) ifaceSet := ifaceNode.AddChild(&Node{Name: "set", Dynamic: dynInterfaces}) ifaceSet.AddChild(&Node{Name: "mtu="}) // /interface vlan vlanNode := ifaceNode.AddChild(&Node{Name: "vlan", Aliases: []string{"vlans"}}) addPrint(vlanNode) addRemove(vlanNode) vlanAdd := vlanNode.AddChild(&Node{Name: "add"}) vlanAdd.AddChild(&Node{Name: "name="}) vlanAdd.AddChild(&Node{Name: "vlan-id="}) vlanAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces}) // /interface bridge bridgeNode := ifaceNode.AddChild(&Node{Name: "bridge", Aliases: []string{"bridges"}}) addPrint(bridgeNode) addRemove(bridgeNode) addEnableDisable(bridgeNode) bridgeAdd := bridgeNode.AddChild(&Node{Name: "add"}) bridgeAdd.AddChild(&Node{Name: "name="}) // /interface bridge port bridgePortNode := bridgeNode.AddChild(&Node{Name: "port", Aliases: []string{"ports"}}) addPrint(bridgePortNode) addRemove(bridgePortNode) addEnableDisable(bridgePortNode) bridgePortAdd := bridgePortNode.AddChild(&Node{Name: "add"}) bridgePortAdd.AddChild(&Node{Name: "bridge="}) bridgePortAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces}) bridgePortAdd.AddChild(&Node{Name: "pvid="}) // /interface wireguard wgNode := ifaceNode.AddChild(&Node{Name: "wireguard", Aliases: []string{"wg"}}) addPrint(wgNode) addRemove(wgNode) addEnableDisable(wgNode) wgAdd := wgNode.AddChild(&Node{Name: "add"}) wgAdd.AddChild(&Node{Name: "name="}) wgAdd.AddChild(&Node{Name: "listen-port="}) wgAdd.AddChild(&Node{Name: "private-key="}) // /interface wireguard peers wgPeers := wgNode.AddChild(&Node{Name: "peers", Aliases: []string{"peer"}}) addPrint(wgPeers) addRemove(wgPeers) wgPeersAdd := wgPeers.AddChild(&Node{Name: "add"}) wgPeersAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces}) wgPeersAdd.AddChild(&Node{Name: "public-key="}) wgPeersAdd.AddChild(&Node{Name: "allowed-address="}) wgPeersAdd.AddChild(&Node{Name: "endpoint-address="}) wgPeersAdd.AddChild(&Node{Name: "endpoint-port="}) // /interface aerotun aero := ifaceNode.AddChild(&Node{Name: "aerotun"}) names := func() []string { if c.executor.vpnMgr == nil || c.executor.vpnMgr.AeroTUN == nil { return nil } var out []string for _, inst := range c.executor.vpnMgr.AeroTUN.List() { out = append(out, inst.Name) } return out } aero.AddChild(&Node{Name: "print"}).AddChild(&Node{Name: "detail="}) aero.AddChild(&Node{Name: "genkey"}) for _, action := range []string{"add", "set", "remove", "enable", "disable", "start", "stop"} { n := aero.AddChild(&Node{Name: action, Dynamic: names}) if action == "add" || action == "set" { for _, param := range []string{"name=", "protocol=", "mode=", "listen-port=", "endpoint=", "local-address=", "peer-address=", "peer-public-key=", "allowed-ips=", "psk=", "mtu=", "disabled=", "compression=", "default-route=", "comment="} { n.AddChild(&Node{Name: param}) } } } // /interface eoip eoipNode := ifaceNode.AddChild(&Node{Name: "eoip"}) addPrint(eoipNode) addRemove(eoipNode) addEnableDisable(eoipNode) eoipAdd := eoipNode.AddChild(&Node{Name: "add"}) eoipAdd.AddChild(&Node{Name: "name="}) eoipAdd.AddChild(&Node{Name: "remote-ip="}) eoipAdd.AddChild(&Node{Name: "tunnel-id="}) // /interface vxlan vxlanNode := ifaceNode.AddChild(&Node{Name: "vxlan"}) addPrint(vxlanNode) addRemove(vxlanNode) addEnableDisable(vxlanNode) vxlanAdd := vxlanNode.AddChild(&Node{Name: "add"}) for _, p := range []string{"name=", "vni=", "port=", "mtu=", "mac-address=", "local-address=", "remote-address=", "group=", "interface=", "learning=", "comment="} { vxlanAdd.AddChild(&Node{Name: p}) } vxlanSet := vxlanNode.AddChild(&Node{Name: "set"}) for _, p := range []string{"name=", "vni=", "port=", "mtu=", "mac-address=", "local-address=", "remote-address=", "group=", "interface=", "comment="} { vxlanSet.AddChild(&Node{Name: p}) } vtepsNode := vxlanNode.AddChild(&Node{Name: "vteps"}) addPrint(vtepsNode) addRemove(vtepsNode) vtepsAdd := vtepsNode.AddChild(&Node{Name: "add"}) vtepsAdd.AddChild(&Node{Name: "interface="}) vtepsAdd.AddChild(&Node{Name: "remote-ip="}) vtepsAdd.AddChild(&Node{Name: "port="}) vtepsAdd.AddChild(&Node{Name: "comment="}) // /interface vrrp vrrpNode := ifaceNode.AddChild(&Node{Name: "vrrp"}) addPrint(vrrpNode) addRemove(vrrpNode) addEnableDisable(vrrpNode) vrrpAdd := vrrpNode.AddChild(&Node{Name: "add"}) vrrpAdd.AddChild(&Node{Name: "name="}) vrrpAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces}) vrrpAdd.AddChild(&Node{Name: "vrid="}) vrrpAdd.AddChild(&Node{Name: "priority="}) // /interface pppoe-client pppoeNode := ifaceNode.AddChild(&Node{Name: "pppoe-client"}) addPrint(pppoeNode) addRemove(pppoeNode) pppoeNode.AddChild(&Node{Name: "connect"}) pppoeNode.AddChild(&Node{Name: "disconnect"}) pppoeAdd := pppoeNode.AddChild(&Node{Name: "add"}) pppoeAdd.AddChild(&Node{Name: "name="}) pppoeAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces}) pppoeAdd.AddChild(&Node{Name: "user="}) pppoeAdd.AddChild(&Node{Name: "password="}) // 2. /ip ipNode := root.AddChild(&Node{Name: "ip"}) // /ip address ipAddrNode := ipNode.AddChild(&Node{Name: "address", Aliases: []string{"addr"}}) addPrint(ipAddrNode) addRemove(ipAddrNode) ipAddrAdd := ipAddrNode.AddChild(&Node{Name: "add"}) ipAddrAdd.AddChild(&Node{Name: "address="}) ipAddrAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces}) ipAddrAdd.AddChild(&Node{Name: "network="}) // /ip route ipRouteNode := ipNode.AddChild(&Node{Name: "route", Aliases: []string{"routes"}}) addPrint(ipRouteNode) addRemove(ipRouteNode) addEnableDisable(ipRouteNode) ipRouteAdd := ipRouteNode.AddChild(&Node{Name: "add"}) ipRouteAdd.AddChild(&Node{Name: "dst-address="}) ipRouteAdd.AddChild(&Node{Name: "gateway=", Dynamic: dynInterfaces}) ipRouteAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces}) ipRouteAdd.AddChild(&Node{Name: "distance="}) // /ip route rule ipRouteRule := ipRouteNode.AddChild(&Node{Name: "rule", Aliases: []string{"rules"}}) addPrint(ipRouteRule) addRemove(ipRouteRule) ipRouteRuleAdd := ipRouteRule.AddChild(&Node{Name: "add"}) ipRouteRuleAdd.AddChild(&Node{Name: "src-address="}) ipRouteRuleAdd.AddChild(&Node{Name: "dst-address="}) ipRouteRuleAdd.AddChild(&Node{Name: "table="}) ipRouteRuleAdd.AddChild(&Node{Name: "action=", Dynamic: func() []string { return []string{"lookup", "unreachable", "drop"} }}) // /ip route table ipRouteTable := ipRouteNode.AddChild(&Node{Name: "table", Aliases: []string{"tables"}}) addPrint(ipRouteTable) addRemove(ipRouteTable) ipRouteTableAdd := ipRouteTable.AddChild(&Node{Name: "add"}) ipRouteTableAdd.AddChild(&Node{Name: "name="}) ipRouteTableAdd.AddChild(&Node{Name: "fib="}) // /ip dhcp-client dhcpCliNode := ipNode.AddChild(&Node{Name: "dhcp-client"}) addPrint(dhcpCliNode) addRemove(dhcpCliNode) dhcpCliNode.AddChild(&Node{Name: "renew"}) dhcpCliNode.AddChild(&Node{Name: "release"}) dhcpCliAdd := dhcpCliNode.AddChild(&Node{Name: "add"}) dhcpCliAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces}) dhcpCliAdd.AddChild(&Node{Name: "disabled=", Dynamic: func() []string { return []string{"yes", "no"} }}) // /ip dhcp-server dhcpSrvNode := ipNode.AddChild(&Node{Name: "dhcp-server"}) addPrint(dhcpSrvNode) addRemove(dhcpSrvNode) addEnableDisable(dhcpSrvNode) dhcpSrvAdd := dhcpSrvNode.AddChild(&Node{Name: "add"}) dhcpSrvAdd.AddChild(&Node{Name: "name="}) dhcpSrvAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces}) dhcpSrvAdd.AddChild(&Node{Name: "address-pool="}) dhcpSrvAdd.AddChild(&Node{Name: "lease-time="}) // /ip dhcp-server lease dhcpLeaseNode := dhcpSrvNode.AddChild(&Node{Name: "lease", Aliases: []string{"leases"}}) addPrint(dhcpLeaseNode) addRemove(dhcpLeaseNode) // /ip dhcp-server network dhcpNetNode := dhcpSrvNode.AddChild(&Node{Name: "network", Aliases: []string{"networks"}}) addPrint(dhcpNetNode) addRemove(dhcpNetNode) dhcpNetAdd := dhcpNetNode.AddChild(&Node{Name: "add"}) dhcpNetAdd.AddChild(&Node{Name: "address="}) dhcpNetAdd.AddChild(&Node{Name: "gateway="}) dhcpNetAdd.AddChild(&Node{Name: "dns-server="}) // /ip dns dnsNode := ipNode.AddChild(&Node{Name: "dns"}) addPrint(dnsNode) dnsSet := dnsNode.AddChild(&Node{Name: "set"}) dnsSet.AddChild(&Node{Name: "servers="}) dnsSet.AddChild(&Node{Name: "allow-remote-requests=", Dynamic: func() []string { return []string{"yes", "no"} }}) dnsCache := dnsNode.AddChild(&Node{Name: "cache"}) addPrint(dnsCache) dnsCache.AddChild(&Node{Name: "flush"}) dnsStatic := dnsNode.AddChild(&Node{Name: "static"}) addPrint(dnsStatic) addRemove(dnsStatic) dnsStaticAdd := dnsStatic.AddChild(&Node{Name: "add"}) dnsStaticAdd.AddChild(&Node{Name: "name="}) dnsStaticAdd.AddChild(&Node{Name: "address="}) // /ip firewall fwNode := ipNode.AddChild(&Node{Name: "firewall"}) // /ip firewall filter fwFilter := fwNode.AddChild(&Node{Name: "filter"}) addPrint(fwFilter) addRemove(fwFilter) addEnableDisable(fwFilter) fwFilterAdd := fwFilter.AddChild(&Node{Name: "add"}) fwFilterAdd.AddChild(&Node{Name: "chain=", Dynamic: func() []string { return []string{"forward", "input", "output"} }}) fwFilterAdd.AddChild(&Node{Name: "action=", Dynamic: func() []string { return []string{"accept", "drop", "reject"} }}) fwFilterAdd.AddChild(&Node{Name: "protocol=", Dynamic: func() []string { return []string{"icmp", "tcp", "udp"} }}) fwFilterAdd.AddChild(&Node{Name: "src-address="}) fwFilterAdd.AddChild(&Node{Name: "dst-address="}) fwFilterAdd.AddChild(&Node{Name: "dst-port="}) fwFilterAdd.AddChild(&Node{Name: "in-interface=", Dynamic: dynInterfaces}) fwFilterAdd.AddChild(&Node{Name: "out-interface=", Dynamic: dynInterfaces}) fwFilterAdd.AddChild(&Node{Name: "comment="}) // /ip firewall nat fwNat := fwNode.AddChild(&Node{Name: "nat"}) addPrint(fwNat) addRemove(fwNat) addEnableDisable(fwNat) fwNatAdd := fwNat.AddChild(&Node{Name: "add"}) fwNatAdd.AddChild(&Node{Name: "chain=", Dynamic: func() []string { return []string{"dstnat", "srcnat"} }}) fwNatAdd.AddChild(&Node{Name: "action=", Dynamic: func() []string { return []string{"accept", "dst-nat", "masquerade", "redirect", "src-nat"} }}) fwNatAdd.AddChild(&Node{Name: "protocol=", Dynamic: func() []string { return []string{"icmp", "tcp", "udp"} }}) fwNatAdd.AddChild(&Node{Name: "src-address="}) fwNatAdd.AddChild(&Node{Name: "dst-address="}) fwNatAdd.AddChild(&Node{Name: "to-addresses="}) fwNatAdd.AddChild(&Node{Name: "to-ports="}) fwNatAdd.AddChild(&Node{Name: "out-interface=", Dynamic: dynInterfaces}) fwNatAdd.AddChild(&Node{Name: "comment="}) // /ip firewall mangle fwMangle := fwNode.AddChild(&Node{Name: "mangle"}) addPrint(fwMangle) addRemove(fwMangle) addEnableDisable(fwMangle) fwMangleAdd := fwMangle.AddChild(&Node{Name: "add"}) fwMangleAdd.AddChild(&Node{Name: "chain=", Dynamic: func() []string { return []string{"forward", "input", "output", "postrouting", "prerouting"} }}) fwMangleAdd.AddChild(&Node{Name: "action=", Dynamic: func() []string { return []string{"accept", "mark-packet", "mark-routing"} }}) fwMangleAdd.AddChild(&Node{Name: "protocol=", Dynamic: func() []string { return []string{"icmp", "tcp", "udp"} }}) fwMangleAdd.AddChild(&Node{Name: "src-address="}) fwMangleAdd.AddChild(&Node{Name: "dst-address="}) // /ip firewall address-list fwAddrList := fwNode.AddChild(&Node{Name: "address-list"}) addPrint(fwAddrList) addRemove(fwAddrList) addEnableDisable(fwAddrList) fwAddrListAdd := fwAddrList.AddChild(&Node{Name: "add"}) fwAddrListAdd.AddChild(&Node{Name: "list="}) fwAddrListAdd.AddChild(&Node{Name: "address="}) fwAddrListAdd.AddChild(&Node{Name: "comment="}) // /ip firewall connection fwConn := fwNode.AddChild(&Node{Name: "connection", Aliases: []string{"connections"}}) addPrint(fwConn) // /ip service serviceNode := ipNode.AddChild(&Node{Name: "service", Aliases: []string{"services"}}) addPrint(serviceNode) serviceNode.AddChild(&Node{Name: "enable", Dynamic: dynServices}) serviceNode.AddChild(&Node{Name: "disable", Dynamic: dynServices}) serviceSet := serviceNode.AddChild(&Node{Name: "set", Dynamic: dynServices}) serviceSet.AddChild(&Node{Name: "port="}) serviceSet.AddChild(&Node{Name: "disabled=", Dynamic: func() []string { return []string{"yes", "no"} }}) serviceSet.AddChild(&Node{Name: "certificate=", Dynamic: dynCertificates}) // /ip sdwan sdwanNode := ipNode.AddChild(&Node{Name: "sdwan", Aliases: []string{"sd-wan"}}) addPrint(sdwanNode) sdwanNode.AddChild(&Node{Name: "status"}) addRemove(sdwanNode) addEnableDisable(sdwanNode) sdwanAdd := sdwanNode.AddChild(&Node{Name: "add"}) sdwanAdd.AddChild(&Node{Name: "name="}) sdwanAdd.AddChild(&Node{Name: "mode=", Dynamic: func() []string { return []string{"weighted", "pcc", "lowest-latency", "lowest-loss", "failover"} }}) sdwanAdd.AddChild(&Node{Name: "auto-nat=", Dynamic: func() []string { return []string{"yes", "no"} }}) sdwanAdd.AddChild(&Node{Name: "probe-interval="}) sdwanAdd.AddChild(&Node{Name: "comment="}) sdwanSet := sdwanNode.AddChild(&Node{Name: "set", Dynamic: dynSDWANGroups}) sdwanSet.AddChild(&Node{Name: "mode=", Dynamic: func() []string { return []string{"weighted", "pcc", "lowest-latency", "lowest-loss", "failover"} }}) sdwanSet.AddChild(&Node{Name: "auto-nat=", Dynamic: func() []string { return []string{"yes", "no"} }}) sdwanSet.AddChild(&Node{Name: "probe-interval="}) sdwanSet.AddChild(&Node{Name: "comment="}) // /ip sdwan member sdwanMemNode := sdwanNode.AddChild(&Node{Name: "member", Aliases: []string{"members"}}) addPrint(sdwanMemNode) addRemove(sdwanMemNode) addEnableDisable(sdwanMemNode) sdwanMemAdd := sdwanMemNode.AddChild(&Node{Name: "add"}) sdwanMemAdd.AddChild(&Node{Name: "group=", Dynamic: dynSDWANGroups}) sdwanMemAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces}) sdwanMemAdd.AddChild(&Node{Name: "gateway="}) sdwanMemAdd.AddChild(&Node{Name: "weight="}) sdwanMemAdd.AddChild(&Node{Name: "priority="}) sdwanMemAdd.AddChild(&Node{Name: "target-probe="}) sdwanMemAdd.AddChild(&Node{Name: "max-latency="}) sdwanMemAdd.AddChild(&Node{Name: "max-loss="}) sdwanMemAdd.AddChild(&Node{Name: "comment="}) sdwanMemSet := sdwanMemNode.AddChild(&Node{Name: "set"}) sdwanMemSet.AddChild(&Node{Name: "group=", Dynamic: dynSDWANGroups}) sdwanMemSet.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces}) sdwanMemSet.AddChild(&Node{Name: "gateway="}) sdwanMemSet.AddChild(&Node{Name: "weight="}) sdwanMemSet.AddChild(&Node{Name: "priority="}) sdwanMemSet.AddChild(&Node{Name: "target-probe="}) sdwanMemSet.AddChild(&Node{Name: "max-latency="}) sdwanMemSet.AddChild(&Node{Name: "max-loss="}) sdwanMemSet.AddChild(&Node{Name: "comment="}) // Also accessible directly at root /sdwan root.AddChild(sdwanNode) // 3. /routing routingNode := root.AddChild(&Node{Name: "routing"}) // /routing bgp bgpNode := routingNode.AddChild(&Node{Name: "bgp"}) bgpInst := bgpNode.AddChild(&Node{Name: "instance"}) addPrint(bgpInst) bgpInstSet := bgpInst.AddChild(&Node{Name: "set"}) bgpInstSet.AddChild(&Node{Name: "as="}) bgpInstSet.AddChild(&Node{Name: "router-id="}) bgpInstSet.AddChild(&Node{Name: "enabled=", Dynamic: func() []string { return []string{"yes", "no"} }}) bgpPeer := bgpNode.AddChild(&Node{Name: "peer", Aliases: []string{"peers", "connection"}}) addPrint(bgpPeer) addRemove(bgpPeer) addEnableDisable(bgpPeer) bgpPeerAdd := bgpPeer.AddChild(&Node{Name: "add"}) bgpPeerAdd.AddChild(&Node{Name: "name="}) bgpPeerAdd.AddChild(&Node{Name: "remote-address="}) bgpPeerAdd.AddChild(&Node{Name: "remote-as="}) bgpPeerAdd.AddChild(&Node{Name: "description="}) bgpNet := bgpNode.AddChild(&Node{Name: "network"}) addPrint(bgpNet) addRemove(bgpNet) bgpNetAdd := bgpNet.AddChild(&Node{Name: "add"}) bgpNetAdd.AddChild(&Node{Name: "network="}) bgpRoutes := bgpNode.AddChild(&Node{Name: "routes"}) addPrint(bgpRoutes) // /routing ospf ospfNode := routingNode.AddChild(&Node{Name: "ospf"}) ospfInst := ospfNode.AddChild(&Node{Name: "instance", Aliases: []string{"instances"}}) addPrint(ospfInst) addRemove(ospfInst) ospfInstAdd := ospfInst.AddChild(&Node{Name: "add"}) ospfInstAdd.AddChild(&Node{Name: "name="}) ospfInstAdd.AddChild(&Node{Name: "router-id="}) ospfInstAdd.AddChild(&Node{Name: "redistribute-connected=", Dynamic: func() []string { return []string{"yes", "no"} }}) ospfInstAdd.AddChild(&Node{Name: "redistribute-static=", Dynamic: func() []string { return []string{"yes", "no"} }}) ospfInstAdd.AddChild(&Node{Name: "redistribute-bgp=", Dynamic: func() []string { return []string{"yes", "no"} }}) ospfInstAdd.AddChild(&Node{Name: "default-metric="}) ospfInstSet := ospfInst.AddChild(&Node{Name: "set"}) ospfInstSet.AddChild(&Node{Name: "router-id="}) ospfInstSet.AddChild(&Node{Name: "redistribute-connected=", Dynamic: func() []string { return []string{"yes", "no"} }}) ospfInstSet.AddChild(&Node{Name: "redistribute-static=", Dynamic: func() []string { return []string{"yes", "no"} }}) ospfInstSet.AddChild(&Node{Name: "redistribute-bgp=", Dynamic: func() []string { return []string{"yes", "no"} }}) ospfInstSet.AddChild(&Node{Name: "default-metric="}) ospfInstSet.AddChild(&Node{Name: "disabled=", Dynamic: func() []string { return []string{"yes", "no"} }}) ospfArea := ospfNode.AddChild(&Node{Name: "area", Aliases: []string{"areas"}}) addPrint(ospfArea) addRemove(ospfArea) ospfAreaAdd := ospfArea.AddChild(&Node{Name: "add"}) ospfAreaAdd.AddChild(&Node{Name: "name="}) ospfAreaAdd.AddChild(&Node{Name: "area-id="}) ospfAreaAdd.AddChild(&Node{Name: "instance="}) ospfAreaAdd.AddChild(&Node{Name: "type=", Dynamic: func() []string { return []string{"default", "stub", "nssa"} }}) ospfAreaAdd.AddChild(&Node{Name: "default-cost="}) ospfAreaAdd.AddChild(&Node{Name: "no-summaries=", Dynamic: func() []string { return []string{"yes", "no"} }}) ospfAreaRange := ospfNode.AddChild(&Node{Name: "area-range", Aliases: []string{"range", "ranges"}}) addPrint(ospfAreaRange) addRemove(ospfAreaRange) ospfRangeAdd := ospfAreaRange.AddChild(&Node{Name: "add"}) ospfRangeAdd.AddChild(&Node{Name: "prefix="}) ospfRangeAdd.AddChild(&Node{Name: "area="}) ospfRangeAdd.AddChild(&Node{Name: "cost="}) ospfRangeAdd.AddChild(&Node{Name: "advertise=", Dynamic: func() []string { return []string{"yes", "no"} }}) ospfIface := ospfNode.AddChild(&Node{Name: "interface", Aliases: []string{"interfaces", "template", "templates"}}) addPrint(ospfIface) addRemove(ospfIface) ospfIfaceAdd := ospfIface.AddChild(&Node{Name: "add"}) ospfIfaceAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces}) ospfIfaceAdd.AddChild(&Node{Name: "area="}) ospfIfaceAdd.AddChild(&Node{Name: "networks="}) ospfIfaceAdd.AddChild(&Node{Name: "cost="}) ospfIfaceAdd.AddChild(&Node{Name: "priority="}) ospfIfaceAdd.AddChild(&Node{Name: "network-type=", Dynamic: func() []string { return []string{"broadcast", "point-to-point", "nonbroadcast", "point-to-multipoint"} }}) ospfIfaceAdd.AddChild(&Node{Name: "passive=", Dynamic: func() []string { return []string{"yes", "no"} }}) ospfIfaceAdd.AddChild(&Node{Name: "auth=", Dynamic: func() []string { return []string{"none", "simple", "md5"} }}) ospfSNbr := ospfNode.AddChild(&Node{Name: "static-neighbor", Aliases: []string{"static-neighbors"}}) addPrint(ospfSNbr) addRemove(ospfSNbr) ospfSNbrAdd := ospfSNbr.AddChild(&Node{Name: "add"}) ospfSNbrAdd.AddChild(&Node{Name: "address="}) ospfSNbrAdd.AddChild(&Node{Name: "area="}) ospfSNbrAdd.AddChild(&Node{Name: "poll-interval="}) ospfSNbrAdd.AddChild(&Node{Name: "cost="}) ospfNeighbor := ospfNode.AddChild(&Node{Name: "neighbor", Aliases: []string{"neighbors"}}) addPrint(ospfNeighbor) ospfLSA := ospfNode.AddChild(&Node{Name: "lsa", Aliases: []string{"lsadb"}}) addPrint(ospfLSA) ospfRoute := ospfNode.AddChild(&Node{Name: "route", Aliases: []string{"routes"}}) addPrint(ospfRoute) // /routing filter routingFilter := routingNode.AddChild(&Node{Name: "filter"}) addPrint(routingFilter) rfRule := routingFilter.AddChild(&Node{Name: "rule", Aliases: []string{"rules"}}) addPrint(rfRule) addRemove(rfRule) rfRuleAdd := rfRule.AddChild(&Node{Name: "add"}) rfRuleAdd.AddChild(&Node{Name: "chain="}) rfRuleAdd.AddChild(&Node{Name: "action=", Dynamic: func() []string { return []string{"accept", "discard", "reject"} }}) rfRuleAdd.AddChild(&Node{Name: "prefix="}) rfRuleAdd.AddChild(&Node{Name: "prefix-len="}) rfRuleAdd.AddChild(&Node{Name: "rule-order="}) rfComm := routingFilter.AddChild(&Node{Name: "community-list", Aliases: []string{"community-lists"}}) addPrint(rfComm) addRemove(rfComm) rfCommAdd := rfComm.AddChild(&Node{Name: "add"}) rfCommAdd.AddChild(&Node{Name: "name="}) rfCommAdd.AddChild(&Node{Name: "communities="}) rfAS := routingFilter.AddChild(&Node{Name: "as-list", Aliases: []string{"as-lists", "num-list", "num-lists"}}) addPrint(rfAS) addRemove(rfAS) rfASAdd := rfAS.AddChild(&Node{Name: "add"}) rfASAdd.AddChild(&Node{Name: "name="}) rfASAdd.AddChild(&Node{Name: "range="}) // /routing rip ripNode := routingNode.AddChild(&Node{Name: "rip"}) ripInst := ripNode.AddChild(&Node{Name: "instance", Aliases: []string{"instances"}}) addPrint(ripInst) addRemove(ripInst) addEnableDisable(ripInst) ripInstAdd := ripInst.AddChild(&Node{Name: "add"}) ripInstAdd.AddChild(&Node{Name: "name="}) ripInstAdd.AddChild(&Node{Name: "version=", Dynamic: func() []string { return []string{"1", "2"} }}) ripInstAdd.AddChild(&Node{Name: "redistribute-connected=", Dynamic: func() []string { return []string{"yes", "no"} }}) ripInstAdd.AddChild(&Node{Name: "redistribute-static=", Dynamic: func() []string { return []string{"yes", "no"} }}) ripInstAdd.AddChild(&Node{Name: "redistribute-ospf=", Dynamic: func() []string { return []string{"yes", "no"} }}) ripInstAdd.AddChild(&Node{Name: "redistribute-bgp=", Dynamic: func() []string { return []string{"yes", "no"} }}) ripInstAdd.AddChild(&Node{Name: "redistribute-default=", Dynamic: func() []string { return []string{"yes", "no"} }}) ripIface := ripNode.AddChild(&Node{Name: "interface", Aliases: []string{"interfaces"}}) addPrint(ripIface) addRemove(ripIface) addEnableDisable(ripIface) ripIfaceAdd := ripIface.AddChild(&Node{Name: "add"}) ripIfaceAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces}) ripIfaceAdd.AddChild(&Node{Name: "instance="}) ripIfaceAdd.AddChild(&Node{Name: "metric="}) ripIfaceAdd.AddChild(&Node{Name: "mode=", Dynamic: func() []string { return []string{"multicast", "broadcast"} }}) ripIfaceAdd.AddChild(&Node{Name: "passive=", Dynamic: func() []string { return []string{"yes", "no"} }}) ripIfaceAdd.AddChild(&Node{Name: "use-bfd=", Dynamic: func() []string { return []string{"yes", "no"} }}) ripNbr := ripNode.AddChild(&Node{Name: "neighbor", Aliases: []string{"neighbors"}}) addPrint(ripNbr) addRemove(ripNbr) ripNbrAdd := ripNbr.AddChild(&Node{Name: "add"}) ripNbrAdd.AddChild(&Node{Name: "address="}) ripNbrAdd.AddChild(&Node{Name: "instance="}) ripRoutes := ripNode.AddChild(&Node{Name: "routes", Aliases: []string{"route"}}) addPrint(ripRoutes) // /routing bfd bfdNode := routingNode.AddChild(&Node{Name: "bfd"}) bfdInst := bfdNode.AddChild(&Node{Name: "instance"}) addPrint(bfdInst) bfdInstSet := bfdInst.AddChild(&Node{Name: "set"}) bfdInstSet.AddChild(&Node{Name: "enabled=", Dynamic: func() []string { return []string{"yes", "no"} }}) bfdIface := bfdNode.AddChild(&Node{Name: "interface", Aliases: []string{"interfaces"}}) addPrint(bfdIface) addRemove(bfdIface) addEnableDisable(bfdIface) bfdIfaceAdd := bfdIface.AddChild(&Node{Name: "add"}) bfdIfaceAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces}) bfdIfaceAdd.AddChild(&Node{Name: "min-rx="}) bfdIfaceAdd.AddChild(&Node{Name: "min-tx="}) bfdIfaceAdd.AddChild(&Node{Name: "idle-tx="}) bfdIfaceAdd.AddChild(&Node{Name: "multiplier="}) bfdIfaceAdd.AddChild(&Node{Name: "passive=", Dynamic: func() []string { return []string{"yes", "no"} }}) bfdPeer := bfdNode.AddChild(&Node{Name: "peer", Aliases: []string{"peers", "neighbor", "neighbors"}}) addPrint(bfdPeer) addRemove(bfdPeer) bfdPeerAdd := bfdPeer.AddChild(&Node{Name: "add"}) bfdPeerAdd.AddChild(&Node{Name: "address="}) bfdPeerAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces}) bfdPeerAdd.AddChild(&Node{Name: "multihop=", Dynamic: func() []string { return []string{"yes", "no"} }}) bfdSession := bfdNode.AddChild(&Node{Name: "session", Aliases: []string{"sessions"}}) addPrint(bfdSession) // /routing babel babelNode := routingNode.AddChild(&Node{Name: "babel"}) babelInst := babelNode.AddChild(&Node{Name: "instance", Aliases: []string{"instances"}}) addPrint(babelInst) addRemove(babelInst) addEnableDisable(babelInst) babelInstAdd := babelInst.AddChild(&Node{Name: "add"}) babelInstAdd.AddChild(&Node{Name: "name="}) babelInstAdd.AddChild(&Node{Name: "router-id="}) babelInstAdd.AddChild(&Node{Name: "redistribute-connected=", Dynamic: func() []string { return []string{"yes", "no"} }}) babelInstAdd.AddChild(&Node{Name: "redistribute-static=", Dynamic: func() []string { return []string{"yes", "no"} }}) babelInstAdd.AddChild(&Node{Name: "redistribute-ospf=", Dynamic: func() []string { return []string{"yes", "no"} }}) babelInstAdd.AddChild(&Node{Name: "redistribute-bgp=", Dynamic: func() []string { return []string{"yes", "no"} }}) babelIface := babelNode.AddChild(&Node{Name: "interface", Aliases: []string{"interfaces"}}) addPrint(babelIface) addRemove(babelIface) addEnableDisable(babelIface) babelIfaceAdd := babelIface.AddChild(&Node{Name: "add"}) babelIfaceAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces}) babelIfaceAdd.AddChild(&Node{Name: "instance="}) babelIfaceAdd.AddChild(&Node{Name: "type=", Dynamic: func() []string { return []string{"wired", "wireless"} }}) babelIfaceAdd.AddChild(&Node{Name: "rxcost="}) babelIfaceAdd.AddChild(&Node{Name: "hello-interval="}) babelIfaceAdd.AddChild(&Node{Name: "update-interval="}) babelNbr := babelNode.AddChild(&Node{Name: "neighbor", Aliases: []string{"neighbors"}}) addPrint(babelNbr) babelRoutes := babelNode.AddChild(&Node{Name: "routes", Aliases: []string{"route"}}) addPrint(babelRoutes) // /routing radv radvNode := routingNode.AddChild(&Node{Name: "radv"}) radvInst := radvNode.AddChild(&Node{Name: "instance", Aliases: []string{"instances"}}) addPrint(radvInst) addRemove(radvInst) addEnableDisable(radvInst) radvInstAdd := radvInst.AddChild(&Node{Name: "add"}) radvInstAdd.AddChild(&Node{Name: "name="}) radvIface := radvNode.AddChild(&Node{Name: "interface", Aliases: []string{"interfaces"}}) addPrint(radvIface) addRemove(radvIface) addEnableDisable(radvIface) radvIfaceAdd := radvIface.AddChild(&Node{Name: "add"}) radvIfaceAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces}) radvIfaceAdd.AddChild(&Node{Name: "instance="}) radvIfaceAdd.AddChild(&Node{Name: "min-ra-interval="}) radvIfaceAdd.AddChild(&Node{Name: "max-ra-interval="}) radvIfaceAdd.AddChild(&Node{Name: "default-lifetime="}) radvIfaceAdd.AddChild(&Node{Name: "prefixes="}) radvIfaceAdd.AddChild(&Node{Name: "rdnss="}) // 4. /tool toolNode := root.AddChild(&Node{Name: "tool"}) toolPing := toolNode.AddChild(&Node{Name: "ping"}) toolPing.AddChild(&Node{Name: "count="}) toolPing.AddChild(&Node{Name: "size="}) toolPing.AddChild(&Node{Name: "interval="}) toolTrace := toolNode.AddChild(&Node{Name: "traceroute"}) toolTrace.AddChild(&Node{Name: "max-hops="}) toolBtest := toolNode.AddChild(&Node{Name: "bandwidth-test", Aliases: []string{"btest"}}) toolBtest.AddChild(&Node{Name: "target="}) toolBtest.AddChild(&Node{Name: "duration="}) toolBtest.AddChild(&Node{Name: "direction=", Dynamic: func() []string { return []string{"receive", "send", "both"} }}) toolNetwatch := toolNode.AddChild(&Node{Name: "netwatch"}) addPrint(toolNetwatch) addRemove(toolNetwatch) toolNWAdd := toolNetwatch.AddChild(&Node{Name: "add"}) toolNWAdd.AddChild(&Node{Name: "host="}) toolNWAdd.AddChild(&Node{Name: "interval="}) toolNWAdd.AddChild(&Node{Name: "timeout="}) toolIPScan := toolNode.AddChild(&Node{Name: "ip-scan"}) toolIPScan.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces}) toolIPScan.AddChild(&Node{Name: "address-range="}) toolTorch := toolNode.AddChild(&Node{Name: "torch", Aliases: []string{"sniff"}}) toolTorch.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces}) // 5. /log logNode := root.AddChild(&Node{Name: "log"}) logPrint := logNode.AddChild(&Node{Name: "print", Aliases: []string{"pr", "show"}}) logPrint.AddChild(&Node{Name: "limit="}) logPrint.AddChild(&Node{Name: "filter="}) // 6. /user userNode := root.AddChild(&Node{Name: "user", Aliases: []string{"users"}}) addPrint(userNode) addRemove(userNode) userAdd := userNode.AddChild(&Node{Name: "add"}) userAdd.AddChild(&Node{Name: "name="}) userAdd.AddChild(&Node{Name: "password="}) userAdd.AddChild(&Node{Name: "role=", Dynamic: func() []string { return []string{"admin", "full", "read", "viewer"} }}) userAdd.AddChild(&Node{Name: "comment="}) userSet := userNode.AddChild(&Node{Name: "set"}) userSet.AddChild(&Node{Name: "name="}) userSet.AddChild(&Node{Name: "password="}) userSet.AddChild(&Node{Name: "role=", Dynamic: func() []string { return []string{"admin", "full", "read", "viewer"} }}) userSet.AddChild(&Node{Name: "comment="}) userSet.AddChild(&Node{Name: "disabled=", Dynamic: func() []string { return []string{"yes", "no"} }}) // 7. /system sysNode := root.AddChild(&Node{Name: "system"}) sysRes := sysNode.AddChild(&Node{Name: "resource"}) addPrint(sysRes) sysIdent := sysNode.AddChild(&Node{Name: "identity"}) addPrint(sysIdent) sysIdentSet := sysIdent.AddChild(&Node{Name: "set"}) sysIdentSet.AddChild(&Node{Name: "name="}) sysPkg := sysNode.AddChild(&Node{Name: "package", Aliases: []string{"upgrade"}}) addPrint(sysPkg) sysPkgUpdate := sysPkg.AddChild(&Node{Name: "update"}) sysPkgUpdate.AddChild(&Node{Name: "check"}) sysPkgUpdate.AddChild(&Node{Name: "install"}) sysNode.AddChild(&Node{Name: "reboot"}) sysNode.AddChild(&Node{Name: "shutdown", Aliases: []string{"poweroff"}}) // 8. /certificate certNode := root.AddChild(&Node{Name: "certificate", Aliases: []string{"certificates", "cert"}}) addPrint(certNode) certNode.AddChild(&Node{Name: "remove", Aliases: []string{"rem", "delete"}, Dynamic: dynCertificates}) certNode.AddChild(&Node{Name: "export", Dynamic: dynCertificates}) certAddCA := certNode.AddChild(&Node{Name: "add-ca"}) certAddCA.AddChild(&Node{Name: "name="}) certAddCA.AddChild(&Node{Name: "common-name="}) certAddCA.AddChild(&Node{Name: "days="}) certAddCA.AddChild(&Node{Name: "key-type=", Dynamic: func() []string { return []string{"RSA-2048", "RSA-4096", "ECDSA-P256"} }}) certAdd := certNode.AddChild(&Node{Name: "add"}) certAdd.AddChild(&Node{Name: "name="}) certAdd.AddChild(&Node{Name: "common-name="}) certAdd.AddChild(&Node{Name: "ca=", Dynamic: dynCAs}) certAdd.AddChild(&Node{Name: "sans="}) certAdd.AddChild(&Node{Name: "days="}) certAdd.AddChild(&Node{Name: "key-type=", Dynamic: func() []string { return []string{"RSA-2048", "RSA-4096", "ECDSA-P256"} }}) certLE := certNode.AddChild(&Node{Name: "issue-letsencrypt"}) certLE.AddChild(&Node{Name: "domain="}) certLE.AddChild(&Node{Name: "email="}) certLE.AddChild(&Node{Name: "name="}) certLE.AddChild(&Node{Name: "staging=", Dynamic: func() []string { return []string{"yes", "no"} }}) // 9. Direct root utility commands root.AddChild(&Node{Name: "clear"}) root.AddChild(&Node{Name: "help", Aliases: []string{"?"}}) root.AddChild(&Node{Name: "quit"}) root.AddChild(&Node{Name: "exit"}) root.AddChild(&Node{Name: "ping"}) root.AddChild(&Node{Name: "traceroute"}) return root }