| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883 |
- 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":
- return ColorGreen + s + ColorReset
- case "interface", "ip", "routing", "tool", "log", "user", "system", "certificate", "certificates",
- "vlan", "bridge", "wireguard", "eoip", "vrrp", "pppoe-client",
- "address", "route", "dhcp-client", "dhcp-server", "dns", "firewall", "service",
- "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
- }
- // 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 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="})
- 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 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})
- // 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"})
- addPrint(ospfInst)
- ospfInstSet := ospfInst.AddChild(&Node{Name: "set"})
- ospfInstSet.AddChild(&Node{Name: "router-id="})
- ospfInstSet.AddChild(&Node{Name: "enabled=", Dynamic: func() []string { return []string{"yes", "no"} }})
- ospfArea := ospfNode.AddChild(&Node{Name: "area"})
- addPrint(ospfArea)
- addRemove(ospfArea)
- ospfAreaAdd := ospfArea.AddChild(&Node{Name: "add"})
- ospfAreaAdd.AddChild(&Node{Name: "name="})
- ospfAreaAdd.AddChild(&Node{Name: "area-id="})
- ospfIface := ospfNode.AddChild(&Node{Name: "interface"})
- addPrint(ospfIface)
- addRemove(ospfIface)
- ospfIfaceAdd := ospfIface.AddChild(&Node{Name: "add"})
- ospfIfaceAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
- 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"} }})
- ospfNeighbor := ospfNode.AddChild(&Node{Name: "neighbor", Aliases: []string{"neighbors"}})
- addPrint(ospfNeighbor)
- // /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="})
- // 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"} }})
- // 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="})
- sysNode.AddChild(&Node{Name: "reboot"})
- // 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
- }
|