completer.go 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801
  1. package cli
  2. import (
  3. "sort"
  4. "strings"
  5. "sync"
  6. "time"
  7. )
  8. // TerminalWriter is an interface satisfied by *terminal.Terminal and mock terminals.
  9. type TerminalWriter interface {
  10. Write(buf []byte) (n int, err error)
  11. }
  12. // Node represents a command or subsection in the CLI hierarchy.
  13. type Node struct {
  14. Name string
  15. Aliases []string
  16. Description string
  17. Children []*Node
  18. Dynamic func() []string
  19. }
  20. // AddChild appends a child node to this node and returns the child.
  21. func (n *Node) AddChild(child *Node) *Node {
  22. n.Children = append(n.Children, child)
  23. return child
  24. }
  25. // FindChild searches for a child matching token by Name, Aliases, or unique prefix.
  26. func (n *Node) FindChild(token string) *Node {
  27. token = strings.ToLower(token)
  28. // 1. Exact Name match
  29. for _, ch := range n.Children {
  30. if strings.ToLower(ch.Name) == token {
  31. return ch
  32. }
  33. }
  34. // 2. Exact Aliases match
  35. for _, ch := range n.Children {
  36. for _, a := range ch.Aliases {
  37. if strings.ToLower(a) == token {
  38. return ch
  39. }
  40. }
  41. }
  42. // 3. Unique prefix match among children (excluding parameters ending in '=')
  43. var match *Node
  44. for _, ch := range n.Children {
  45. if !strings.HasSuffix(ch.Name, "=") && strings.HasPrefix(strings.ToLower(ch.Name), token) {
  46. if match != nil {
  47. return nil // Ambiguous
  48. }
  49. match = ch
  50. }
  51. }
  52. return match
  53. }
  54. // Completer manages tab-completion state and double-tab candidate listing.
  55. type Completer struct {
  56. term TerminalWriter
  57. executor *Executor
  58. root *Node
  59. termWidth int
  60. mu sync.Mutex
  61. lastTabTime time.Time
  62. lastTabLine string
  63. lastTabPos int
  64. }
  65. // NewCompleter creates and initializes a Completer with the full command tree.
  66. func NewCompleter(term TerminalWriter, executor *Executor) *Completer {
  67. c := &Completer{
  68. term: term,
  69. executor: executor,
  70. termWidth: 80,
  71. }
  72. c.root = c.buildCommandTree()
  73. return c
  74. }
  75. // SetWidth sets the terminal width for candidate column formatting.
  76. func (c *Completer) SetWidth(w int) {
  77. c.mu.Lock()
  78. defer c.mu.Unlock()
  79. if w > 20 {
  80. c.termWidth = w
  81. }
  82. }
  83. // Callback is the AutoCompleteCallback for golang.org/x/term.Terminal.
  84. func (c *Completer) Callback(line string, pos int, key rune) (newLine string, newPos int, ok bool) {
  85. if key != '\t' {
  86. return "", 0, false
  87. }
  88. c.mu.Lock()
  89. defer c.mu.Unlock()
  90. candidates, prefixBeforeWord, currentWord, isParamVal, paramPrefix := c.getCompletions(line, pos)
  91. if len(candidates) == 0 {
  92. // Nothing to complete
  93. c.lastTabTime = time.Time{}
  94. c.lastTabLine = ""
  95. c.lastTabPos = 0
  96. return "", 0, false
  97. }
  98. suffix := ""
  99. if pos < len(line) {
  100. suffix = line[pos:]
  101. }
  102. // Single match: complete it immediately!
  103. if len(candidates) == 1 {
  104. c.lastTabTime = time.Time{}
  105. c.lastTabLine = ""
  106. c.lastTabPos = 0
  107. match := candidates[0]
  108. completedToken := match
  109. if isParamVal {
  110. completedToken = paramPrefix + match + " "
  111. } else if strings.HasSuffix(match, "=") {
  112. // Parameter key, e.g. "address=" -> no trailing space
  113. completedToken = match
  114. } else {
  115. completedToken = match + " "
  116. }
  117. newLine = prefixBeforeWord + completedToken + suffix
  118. newPos = len(prefixBeforeWord) + len(completedToken)
  119. return newLine, newPos, true
  120. }
  121. // Multiple candidates: find Longest Common Prefix (LCP)
  122. lcp := longestCommonPrefix(candidates)
  123. targetWord := currentWord
  124. if isParamVal {
  125. targetWord = currentWord[len(paramPrefix):]
  126. }
  127. // If LCP can extend the current input, expand it
  128. if len(lcp) > len(targetWord) {
  129. extendedToken := lcp
  130. if isParamVal {
  131. extendedToken = paramPrefix + lcp
  132. }
  133. newLine = prefixBeforeWord + extendedToken + suffix
  134. newPos = len(prefixBeforeWord) + len(extendedToken)
  135. // Record tab state so an immediate second tab shows remaining options
  136. c.lastTabTime = time.Now()
  137. c.lastTabLine = newLine
  138. c.lastTabPos = newPos
  139. return newLine, newPos, true
  140. }
  141. // LCP cannot extend input further: check for 2nd Tab (within 1000ms)
  142. isSecondTab := time.Since(c.lastTabTime) <= 1000*time.Millisecond &&
  143. c.lastTabLine == line &&
  144. c.lastTabPos == pos
  145. if !isSecondTab {
  146. // 1st Tab: Record state and wait for potential 2nd Tab
  147. c.lastTabTime = time.Now()
  148. c.lastTabLine = line
  149. c.lastTabPos = pos
  150. return line, pos, true
  151. }
  152. // 2nd Tab: DISPLAY ALL CANDIDATES!
  153. c.lastTabTime = time.Time{}
  154. c.lastTabLine = ""
  155. c.lastTabPos = 0
  156. if c.term != nil {
  157. formatted := c.formatCandidates(candidates)
  158. _, _ = c.term.Write([]byte(formatted))
  159. }
  160. return line, pos, true
  161. }
  162. // getCompletions computes the candidate completions for the current cursor context.
  163. func (c *Completer) getCompletions(line string, pos int) (candidates []string, prefixBeforeWord string, currentWord string, isParamVal bool, paramPrefix string) {
  164. if pos > len(line) {
  165. pos = len(line)
  166. }
  167. input := line[:pos]
  168. lastSpace := strings.LastIndexAny(input, " \t")
  169. if lastSpace == -1 {
  170. // Single word so far (or empty)
  171. if strings.HasPrefix(input, "/") {
  172. prefixBeforeWord = "/"
  173. currentWord = input[1:]
  174. } else {
  175. prefixBeforeWord = ""
  176. currentWord = input
  177. }
  178. } else {
  179. prefixBeforeWord = input[:lastSpace+1]
  180. currentWord = input[lastSpace+1:]
  181. }
  182. // Parse preceding words to navigate tree
  183. rawPrefix := strings.TrimSpace(prefixBeforeWord)
  184. hasSlash := strings.HasPrefix(rawPrefix, "/")
  185. if hasSlash {
  186. rawPrefix = strings.TrimPrefix(rawPrefix, "/")
  187. }
  188. var precedingTokens []string
  189. if rawPrefix != "" {
  190. precedingTokens = strings.Fields(rawPrefix)
  191. }
  192. // Traverse tree to find current node and track used parameter keys
  193. currNode := c.root
  194. usedParams := make(map[string]bool)
  195. for _, token := range precedingTokens {
  196. if idx := strings.Index(token, "="); idx != -1 {
  197. // Parameter key=value, stay at current node
  198. key := strings.ToLower(token[:idx+1])
  199. usedParams[key] = true
  200. continue
  201. }
  202. next := currNode.FindChild(token)
  203. if next != nil {
  204. currNode = next
  205. }
  206. }
  207. // Check if user is typing a parameter value (e.g. "interface=eth" or "role=")
  208. if idx := strings.Index(currentWord, "="); idx != -1 {
  209. paramKey := strings.ToLower(currentWord[:idx+1])
  210. paramVal := strings.ToLower(currentWord[idx+1:])
  211. paramChild := currNode.FindChild(paramKey)
  212. if paramChild != nil && paramChild.Dynamic != nil {
  213. isParamVal = true
  214. paramPrefix = currentWord[:idx+1]
  215. for _, val := range paramChild.Dynamic() {
  216. if strings.HasPrefix(strings.ToLower(val), paramVal) {
  217. candidates = append(candidates, val)
  218. }
  219. }
  220. sort.Strings(candidates)
  221. return
  222. }
  223. }
  224. // Gather child commands and parameters
  225. search := strings.ToLower(currentWord)
  226. for _, ch := range currNode.Children {
  227. nameLower := strings.ToLower(ch.Name)
  228. if strings.HasSuffix(nameLower, "=") {
  229. if usedParams[nameLower] {
  230. continue // parameter already provided
  231. }
  232. }
  233. if strings.HasPrefix(nameLower, search) {
  234. candidates = append(candidates, ch.Name)
  235. }
  236. }
  237. // Check if current node has dynamic positional choices (e.g. interface names or service names)
  238. if currNode.Dynamic != nil {
  239. for _, dyn := range currNode.Dynamic() {
  240. if strings.HasPrefix(strings.ToLower(dyn), search) {
  241. candidates = append(candidates, dyn)
  242. }
  243. }
  244. }
  245. // Deduplicate and sort
  246. candidates = uniqueStrings(candidates)
  247. sort.Strings(candidates)
  248. return
  249. }
  250. func (c *Completer) formatCandidates(candidates []string) string {
  251. if len(candidates) == 0 {
  252. return ""
  253. }
  254. termWidth := c.termWidth
  255. if termWidth < 40 {
  256. termWidth = 80
  257. }
  258. maxLen := 0
  259. for _, cand := range candidates {
  260. if len(cand) > maxLen {
  261. maxLen = len(cand)
  262. }
  263. }
  264. colWidth := maxLen + 3
  265. if colWidth < 10 {
  266. colWidth = 10
  267. }
  268. numCols := (termWidth - 4) / colWidth
  269. if numCols < 1 {
  270. numCols = 1
  271. }
  272. if numCols > len(candidates) {
  273. numCols = len(candidates)
  274. }
  275. numRows := (len(candidates) + numCols - 1) / numCols
  276. var sb strings.Builder
  277. sb.WriteString("\n")
  278. for r := 0; r < numRows; r++ {
  279. sb.WriteString(" ")
  280. for col := 0; col < numCols; col++ {
  281. idx := col*numRows + r
  282. if idx < len(candidates) {
  283. cand := candidates[idx]
  284. colored := colorizeCandidate(cand)
  285. sb.WriteString(colored)
  286. if col < numCols-1 && (col+1)*numRows+r < len(candidates) {
  287. pad := colWidth - len(cand)
  288. if pad > 0 {
  289. sb.WriteString(strings.Repeat(" ", pad))
  290. }
  291. }
  292. }
  293. }
  294. sb.WriteString("\n")
  295. }
  296. return sb.String()
  297. }
  298. func colorizeCandidate(s string) string {
  299. if strings.HasSuffix(s, "=") {
  300. return ColorYellow + s + ColorReset
  301. }
  302. switch s {
  303. case "print", "add", "remove", "enable", "disable", "set", "reboot", "flush", "renew", "release", "connect", "disconnect":
  304. return ColorGreen + s + ColorReset
  305. case "interface", "ip", "routing", "tool", "log", "user", "system",
  306. "vlan", "bridge", "wireguard", "eoip", "vrrp", "pppoe-client",
  307. "address", "route", "dhcp-client", "dhcp-server", "dns", "firewall", "service",
  308. "filter", "nat", "mangle", "address-list", "bgp", "ospf", "port", "peers", "lease", "network", "cache", "static", "rule", "table", "instance", "area", "neighbor", "resource", "identity":
  309. return ColorCyan + s + ColorReset
  310. case "api", "api-ssl", "ftp", "ssh", "telnet", "web", "web-ssl":
  311. return ColorMagenta + s + ColorReset
  312. default:
  313. return ColorWhite + s + ColorReset
  314. }
  315. }
  316. func longestCommonPrefix(strs []string) string {
  317. if len(strs) == 0 {
  318. return ""
  319. }
  320. prefix := strs[0]
  321. for _, s := range strs[1:] {
  322. for !strings.HasPrefix(strings.ToLower(s), strings.ToLower(prefix)) {
  323. if len(prefix) == 0 {
  324. return ""
  325. }
  326. prefix = prefix[:len(prefix)-1]
  327. }
  328. }
  329. return prefix
  330. }
  331. func uniqueStrings(slice []string) []string {
  332. seen := make(map[string]bool)
  333. var result []string
  334. for _, s := range slice {
  335. if !seen[s] {
  336. seen[s] = true
  337. result = append(result, s)
  338. }
  339. }
  340. return result
  341. }
  342. // buildCommandTree creates the full tree of commands, subsections, actions, parameters, and dynamic providers.
  343. func (c *Completer) buildCommandTree() *Node {
  344. root := &Node{Name: "root"}
  345. // Helper for dynamic interfaces
  346. dynInterfaces := func() []string {
  347. if c.executor != nil && c.executor.netMgr != nil {
  348. ifaces, err := c.executor.netMgr.ListInterfaces()
  349. if err == nil {
  350. var names []string
  351. for _, iface := range ifaces {
  352. names = append(names, iface.Name)
  353. }
  354. return names
  355. }
  356. }
  357. return []string{"eth0", "eth1", "lo"}
  358. }
  359. // Helper for services
  360. dynServices := func() []string {
  361. return []string{"api", "api-ssl", "ftp", "ssh", "telnet", "web", "web-ssl"}
  362. }
  363. // Common standard actions
  364. addPrint := func(n *Node) {
  365. n.AddChild(&Node{Name: "print", Aliases: []string{"pr", "show", "list"}})
  366. }
  367. addRemove := func(n *Node) {
  368. n.AddChild(&Node{Name: "remove", Aliases: []string{"rem", "delete"}})
  369. }
  370. addEnableDisable := func(n *Node) {
  371. n.AddChild(&Node{Name: "enable"})
  372. n.AddChild(&Node{Name: "disable"})
  373. }
  374. // 1. /interface
  375. ifaceNode := root.AddChild(&Node{Name: "interface", Aliases: []string{"interfaces"}})
  376. addPrint(ifaceNode)
  377. ifaceNode.AddChild(&Node{Name: "enable", Dynamic: dynInterfaces})
  378. ifaceNode.AddChild(&Node{Name: "disable", Dynamic: dynInterfaces})
  379. ifaceSet := ifaceNode.AddChild(&Node{Name: "set", Dynamic: dynInterfaces})
  380. ifaceSet.AddChild(&Node{Name: "mtu="})
  381. // /interface vlan
  382. vlanNode := ifaceNode.AddChild(&Node{Name: "vlan", Aliases: []string{"vlans"}})
  383. addPrint(vlanNode)
  384. addRemove(vlanNode)
  385. vlanAdd := vlanNode.AddChild(&Node{Name: "add"})
  386. vlanAdd.AddChild(&Node{Name: "name="})
  387. vlanAdd.AddChild(&Node{Name: "vlan-id="})
  388. vlanAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  389. // /interface bridge
  390. bridgeNode := ifaceNode.AddChild(&Node{Name: "bridge", Aliases: []string{"bridges"}})
  391. addPrint(bridgeNode)
  392. addRemove(bridgeNode)
  393. addEnableDisable(bridgeNode)
  394. bridgeAdd := bridgeNode.AddChild(&Node{Name: "add"})
  395. bridgeAdd.AddChild(&Node{Name: "name="})
  396. // /interface bridge port
  397. bridgePortNode := bridgeNode.AddChild(&Node{Name: "port", Aliases: []string{"ports"}})
  398. addPrint(bridgePortNode)
  399. addRemove(bridgePortNode)
  400. addEnableDisable(bridgePortNode)
  401. bridgePortAdd := bridgePortNode.AddChild(&Node{Name: "add"})
  402. bridgePortAdd.AddChild(&Node{Name: "bridge="})
  403. bridgePortAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  404. bridgePortAdd.AddChild(&Node{Name: "pvid="})
  405. // /interface wireguard
  406. wgNode := ifaceNode.AddChild(&Node{Name: "wireguard", Aliases: []string{"wg"}})
  407. addPrint(wgNode)
  408. addRemove(wgNode)
  409. addEnableDisable(wgNode)
  410. wgAdd := wgNode.AddChild(&Node{Name: "add"})
  411. wgAdd.AddChild(&Node{Name: "name="})
  412. wgAdd.AddChild(&Node{Name: "listen-port="})
  413. wgAdd.AddChild(&Node{Name: "private-key="})
  414. // /interface wireguard peers
  415. wgPeers := wgNode.AddChild(&Node{Name: "peers", Aliases: []string{"peer"}})
  416. addPrint(wgPeers)
  417. addRemove(wgPeers)
  418. wgPeersAdd := wgPeers.AddChild(&Node{Name: "add"})
  419. wgPeersAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  420. wgPeersAdd.AddChild(&Node{Name: "public-key="})
  421. wgPeersAdd.AddChild(&Node{Name: "allowed-address="})
  422. wgPeersAdd.AddChild(&Node{Name: "endpoint-address="})
  423. wgPeersAdd.AddChild(&Node{Name: "endpoint-port="})
  424. // /interface eoip
  425. eoipNode := ifaceNode.AddChild(&Node{Name: "eoip"})
  426. addPrint(eoipNode)
  427. addRemove(eoipNode)
  428. addEnableDisable(eoipNode)
  429. eoipAdd := eoipNode.AddChild(&Node{Name: "add"})
  430. eoipAdd.AddChild(&Node{Name: "name="})
  431. eoipAdd.AddChild(&Node{Name: "remote-ip="})
  432. eoipAdd.AddChild(&Node{Name: "tunnel-id="})
  433. // /interface vrrp
  434. vrrpNode := ifaceNode.AddChild(&Node{Name: "vrrp"})
  435. addPrint(vrrpNode)
  436. addRemove(vrrpNode)
  437. addEnableDisable(vrrpNode)
  438. vrrpAdd := vrrpNode.AddChild(&Node{Name: "add"})
  439. vrrpAdd.AddChild(&Node{Name: "name="})
  440. vrrpAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  441. vrrpAdd.AddChild(&Node{Name: "vrid="})
  442. vrrpAdd.AddChild(&Node{Name: "priority="})
  443. // /interface pppoe-client
  444. pppoeNode := ifaceNode.AddChild(&Node{Name: "pppoe-client"})
  445. addPrint(pppoeNode)
  446. addRemove(pppoeNode)
  447. pppoeNode.AddChild(&Node{Name: "connect"})
  448. pppoeNode.AddChild(&Node{Name: "disconnect"})
  449. pppoeAdd := pppoeNode.AddChild(&Node{Name: "add"})
  450. pppoeAdd.AddChild(&Node{Name: "name="})
  451. pppoeAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  452. pppoeAdd.AddChild(&Node{Name: "user="})
  453. pppoeAdd.AddChild(&Node{Name: "password="})
  454. // 2. /ip
  455. ipNode := root.AddChild(&Node{Name: "ip"})
  456. // /ip address
  457. ipAddrNode := ipNode.AddChild(&Node{Name: "address", Aliases: []string{"addr"}})
  458. addPrint(ipAddrNode)
  459. addRemove(ipAddrNode)
  460. ipAddrAdd := ipAddrNode.AddChild(&Node{Name: "add"})
  461. ipAddrAdd.AddChild(&Node{Name: "address="})
  462. ipAddrAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  463. ipAddrAdd.AddChild(&Node{Name: "network="})
  464. // /ip route
  465. ipRouteNode := ipNode.AddChild(&Node{Name: "route", Aliases: []string{"routes"}})
  466. addPrint(ipRouteNode)
  467. addRemove(ipRouteNode)
  468. addEnableDisable(ipRouteNode)
  469. ipRouteAdd := ipRouteNode.AddChild(&Node{Name: "add"})
  470. ipRouteAdd.AddChild(&Node{Name: "dst-address="})
  471. ipRouteAdd.AddChild(&Node{Name: "gateway="})
  472. ipRouteAdd.AddChild(&Node{Name: "distance="})
  473. // /ip route rule
  474. ipRouteRule := ipRouteNode.AddChild(&Node{Name: "rule", Aliases: []string{"rules"}})
  475. addPrint(ipRouteRule)
  476. addRemove(ipRouteRule)
  477. ipRouteRuleAdd := ipRouteRule.AddChild(&Node{Name: "add"})
  478. ipRouteRuleAdd.AddChild(&Node{Name: "src-address="})
  479. ipRouteRuleAdd.AddChild(&Node{Name: "dst-address="})
  480. ipRouteRuleAdd.AddChild(&Node{Name: "table="})
  481. ipRouteRuleAdd.AddChild(&Node{Name: "action=", Dynamic: func() []string { return []string{"lookup", "unreachable", "drop"} }})
  482. // /ip route table
  483. ipRouteTable := ipRouteNode.AddChild(&Node{Name: "table", Aliases: []string{"tables"}})
  484. addPrint(ipRouteTable)
  485. addRemove(ipRouteTable)
  486. ipRouteTableAdd := ipRouteTable.AddChild(&Node{Name: "add"})
  487. ipRouteTableAdd.AddChild(&Node{Name: "name="})
  488. ipRouteTableAdd.AddChild(&Node{Name: "fib="})
  489. // /ip dhcp-client
  490. dhcpCliNode := ipNode.AddChild(&Node{Name: "dhcp-client"})
  491. addPrint(dhcpCliNode)
  492. addRemove(dhcpCliNode)
  493. dhcpCliNode.AddChild(&Node{Name: "renew"})
  494. dhcpCliNode.AddChild(&Node{Name: "release"})
  495. dhcpCliAdd := dhcpCliNode.AddChild(&Node{Name: "add"})
  496. dhcpCliAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  497. dhcpCliAdd.AddChild(&Node{Name: "disabled=", Dynamic: func() []string { return []string{"yes", "no"} }})
  498. // /ip dhcp-server
  499. dhcpSrvNode := ipNode.AddChild(&Node{Name: "dhcp-server"})
  500. addPrint(dhcpSrvNode)
  501. addRemove(dhcpSrvNode)
  502. addEnableDisable(dhcpSrvNode)
  503. dhcpSrvAdd := dhcpSrvNode.AddChild(&Node{Name: "add"})
  504. dhcpSrvAdd.AddChild(&Node{Name: "name="})
  505. dhcpSrvAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  506. dhcpSrvAdd.AddChild(&Node{Name: "address-pool="})
  507. dhcpSrvAdd.AddChild(&Node{Name: "lease-time="})
  508. // /ip dhcp-server lease
  509. dhcpLeaseNode := dhcpSrvNode.AddChild(&Node{Name: "lease", Aliases: []string{"leases"}})
  510. addPrint(dhcpLeaseNode)
  511. addRemove(dhcpLeaseNode)
  512. // /ip dhcp-server network
  513. dhcpNetNode := dhcpSrvNode.AddChild(&Node{Name: "network", Aliases: []string{"networks"}})
  514. addPrint(dhcpNetNode)
  515. addRemove(dhcpNetNode)
  516. dhcpNetAdd := dhcpNetNode.AddChild(&Node{Name: "add"})
  517. dhcpNetAdd.AddChild(&Node{Name: "address="})
  518. dhcpNetAdd.AddChild(&Node{Name: "gateway="})
  519. dhcpNetAdd.AddChild(&Node{Name: "dns-server="})
  520. // /ip dns
  521. dnsNode := ipNode.AddChild(&Node{Name: "dns"})
  522. addPrint(dnsNode)
  523. dnsSet := dnsNode.AddChild(&Node{Name: "set"})
  524. dnsSet.AddChild(&Node{Name: "servers="})
  525. dnsSet.AddChild(&Node{Name: "allow-remote-requests=", Dynamic: func() []string { return []string{"yes", "no"} }})
  526. dnsCache := dnsNode.AddChild(&Node{Name: "cache"})
  527. addPrint(dnsCache)
  528. dnsCache.AddChild(&Node{Name: "flush"})
  529. dnsStatic := dnsNode.AddChild(&Node{Name: "static"})
  530. addPrint(dnsStatic)
  531. addRemove(dnsStatic)
  532. dnsStaticAdd := dnsStatic.AddChild(&Node{Name: "add"})
  533. dnsStaticAdd.AddChild(&Node{Name: "name="})
  534. dnsStaticAdd.AddChild(&Node{Name: "address="})
  535. // /ip firewall
  536. fwNode := ipNode.AddChild(&Node{Name: "firewall"})
  537. // /ip firewall filter
  538. fwFilter := fwNode.AddChild(&Node{Name: "filter"})
  539. addPrint(fwFilter)
  540. addRemove(fwFilter)
  541. addEnableDisable(fwFilter)
  542. fwFilterAdd := fwFilter.AddChild(&Node{Name: "add"})
  543. fwFilterAdd.AddChild(&Node{Name: "chain=", Dynamic: func() []string { return []string{"forward", "input", "output"} }})
  544. fwFilterAdd.AddChild(&Node{Name: "action=", Dynamic: func() []string { return []string{"accept", "drop", "reject"} }})
  545. fwFilterAdd.AddChild(&Node{Name: "protocol=", Dynamic: func() []string { return []string{"icmp", "tcp", "udp"} }})
  546. fwFilterAdd.AddChild(&Node{Name: "src-address="})
  547. fwFilterAdd.AddChild(&Node{Name: "dst-address="})
  548. fwFilterAdd.AddChild(&Node{Name: "dst-port="})
  549. fwFilterAdd.AddChild(&Node{Name: "in-interface=", Dynamic: dynInterfaces})
  550. fwFilterAdd.AddChild(&Node{Name: "out-interface=", Dynamic: dynInterfaces})
  551. fwFilterAdd.AddChild(&Node{Name: "comment="})
  552. // /ip firewall nat
  553. fwNat := fwNode.AddChild(&Node{Name: "nat"})
  554. addPrint(fwNat)
  555. addRemove(fwNat)
  556. addEnableDisable(fwNat)
  557. fwNatAdd := fwNat.AddChild(&Node{Name: "add"})
  558. fwNatAdd.AddChild(&Node{Name: "chain=", Dynamic: func() []string { return []string{"dstnat", "srcnat"} }})
  559. fwNatAdd.AddChild(&Node{Name: "action=", Dynamic: func() []string { return []string{"accept", "dst-nat", "masquerade", "redirect", "src-nat"} }})
  560. fwNatAdd.AddChild(&Node{Name: "protocol=", Dynamic: func() []string { return []string{"icmp", "tcp", "udp"} }})
  561. fwNatAdd.AddChild(&Node{Name: "src-address="})
  562. fwNatAdd.AddChild(&Node{Name: "dst-address="})
  563. fwNatAdd.AddChild(&Node{Name: "to-addresses="})
  564. fwNatAdd.AddChild(&Node{Name: "to-ports="})
  565. fwNatAdd.AddChild(&Node{Name: "out-interface=", Dynamic: dynInterfaces})
  566. fwNatAdd.AddChild(&Node{Name: "comment="})
  567. // /ip firewall mangle
  568. fwMangle := fwNode.AddChild(&Node{Name: "mangle"})
  569. addPrint(fwMangle)
  570. addRemove(fwMangle)
  571. addEnableDisable(fwMangle)
  572. fwMangleAdd := fwMangle.AddChild(&Node{Name: "add"})
  573. fwMangleAdd.AddChild(&Node{Name: "chain=", Dynamic: func() []string { return []string{"forward", "input", "output", "postrouting", "prerouting"} }})
  574. fwMangleAdd.AddChild(&Node{Name: "action=", Dynamic: func() []string { return []string{"accept", "mark-packet", "mark-routing"} }})
  575. fwMangleAdd.AddChild(&Node{Name: "protocol=", Dynamic: func() []string { return []string{"icmp", "tcp", "udp"} }})
  576. fwMangleAdd.AddChild(&Node{Name: "src-address="})
  577. fwMangleAdd.AddChild(&Node{Name: "dst-address="})
  578. // /ip firewall address-list
  579. fwAddrList := fwNode.AddChild(&Node{Name: "address-list"})
  580. addPrint(fwAddrList)
  581. addRemove(fwAddrList)
  582. addEnableDisable(fwAddrList)
  583. fwAddrListAdd := fwAddrList.AddChild(&Node{Name: "add"})
  584. fwAddrListAdd.AddChild(&Node{Name: "list="})
  585. fwAddrListAdd.AddChild(&Node{Name: "address="})
  586. fwAddrListAdd.AddChild(&Node{Name: "comment="})
  587. // /ip service
  588. serviceNode := ipNode.AddChild(&Node{Name: "service", Aliases: []string{"services"}})
  589. addPrint(serviceNode)
  590. serviceNode.AddChild(&Node{Name: "enable", Dynamic: dynServices})
  591. serviceNode.AddChild(&Node{Name: "disable", Dynamic: dynServices})
  592. serviceSet := serviceNode.AddChild(&Node{Name: "set", Dynamic: dynServices})
  593. serviceSet.AddChild(&Node{Name: "port="})
  594. serviceSet.AddChild(&Node{Name: "disabled=", Dynamic: func() []string { return []string{"yes", "no"} }})
  595. // 3. /routing
  596. routingNode := root.AddChild(&Node{Name: "routing"})
  597. // /routing bgp
  598. bgpNode := routingNode.AddChild(&Node{Name: "bgp"})
  599. bgpInst := bgpNode.AddChild(&Node{Name: "instance"})
  600. addPrint(bgpInst)
  601. bgpInstSet := bgpInst.AddChild(&Node{Name: "set"})
  602. bgpInstSet.AddChild(&Node{Name: "as="})
  603. bgpInstSet.AddChild(&Node{Name: "router-id="})
  604. bgpInstSet.AddChild(&Node{Name: "enabled=", Dynamic: func() []string { return []string{"yes", "no"} }})
  605. bgpPeer := bgpNode.AddChild(&Node{Name: "peer", Aliases: []string{"peers", "connection"}})
  606. addPrint(bgpPeer)
  607. addRemove(bgpPeer)
  608. addEnableDisable(bgpPeer)
  609. bgpPeerAdd := bgpPeer.AddChild(&Node{Name: "add"})
  610. bgpPeerAdd.AddChild(&Node{Name: "name="})
  611. bgpPeerAdd.AddChild(&Node{Name: "remote-address="})
  612. bgpPeerAdd.AddChild(&Node{Name: "remote-as="})
  613. bgpPeerAdd.AddChild(&Node{Name: "description="})
  614. bgpNet := bgpNode.AddChild(&Node{Name: "network"})
  615. addPrint(bgpNet)
  616. addRemove(bgpNet)
  617. bgpNetAdd := bgpNet.AddChild(&Node{Name: "add"})
  618. bgpNetAdd.AddChild(&Node{Name: "network="})
  619. bgpRoutes := bgpNode.AddChild(&Node{Name: "routes"})
  620. addPrint(bgpRoutes)
  621. // /routing ospf
  622. ospfNode := routingNode.AddChild(&Node{Name: "ospf"})
  623. ospfInst := ospfNode.AddChild(&Node{Name: "instance"})
  624. addPrint(ospfInst)
  625. ospfInstSet := ospfInst.AddChild(&Node{Name: "set"})
  626. ospfInstSet.AddChild(&Node{Name: "router-id="})
  627. ospfInstSet.AddChild(&Node{Name: "enabled=", Dynamic: func() []string { return []string{"yes", "no"} }})
  628. ospfArea := ospfNode.AddChild(&Node{Name: "area"})
  629. addPrint(ospfArea)
  630. addRemove(ospfArea)
  631. ospfAreaAdd := ospfArea.AddChild(&Node{Name: "add"})
  632. ospfAreaAdd.AddChild(&Node{Name: "name="})
  633. ospfAreaAdd.AddChild(&Node{Name: "area-id="})
  634. ospfIface := ospfNode.AddChild(&Node{Name: "interface"})
  635. addPrint(ospfIface)
  636. addRemove(ospfIface)
  637. ospfIfaceAdd := ospfIface.AddChild(&Node{Name: "add"})
  638. ospfIfaceAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  639. ospfIfaceAdd.AddChild(&Node{Name: "cost="})
  640. ospfIfaceAdd.AddChild(&Node{Name: "priority="})
  641. ospfIfaceAdd.AddChild(&Node{Name: "network-type=", Dynamic: func() []string { return []string{"broadcast", "point-to-point"} }})
  642. ospfNeighbor := ospfNode.AddChild(&Node{Name: "neighbor", Aliases: []string{"neighbors"}})
  643. addPrint(ospfNeighbor)
  644. // /routing filter
  645. routingFilter := routingNode.AddChild(&Node{Name: "filter"})
  646. addPrint(routingFilter)
  647. rfRule := routingFilter.AddChild(&Node{Name: "rule", Aliases: []string{"rules"}})
  648. addPrint(rfRule)
  649. addRemove(rfRule)
  650. rfRuleAdd := rfRule.AddChild(&Node{Name: "add"})
  651. rfRuleAdd.AddChild(&Node{Name: "chain="})
  652. rfRuleAdd.AddChild(&Node{Name: "action=", Dynamic: func() []string { return []string{"accept", "discard", "reject"} }})
  653. rfRuleAdd.AddChild(&Node{Name: "prefix="})
  654. rfRuleAdd.AddChild(&Node{Name: "prefix-len="})
  655. // 4. /tool
  656. toolNode := root.AddChild(&Node{Name: "tool"})
  657. toolPing := toolNode.AddChild(&Node{Name: "ping"})
  658. toolPing.AddChild(&Node{Name: "count="})
  659. toolPing.AddChild(&Node{Name: "size="})
  660. toolPing.AddChild(&Node{Name: "interval="})
  661. toolTrace := toolNode.AddChild(&Node{Name: "traceroute"})
  662. toolTrace.AddChild(&Node{Name: "max-hops="})
  663. toolBtest := toolNode.AddChild(&Node{Name: "bandwidth-test", Aliases: []string{"btest"}})
  664. toolBtest.AddChild(&Node{Name: "target="})
  665. toolBtest.AddChild(&Node{Name: "duration="})
  666. toolBtest.AddChild(&Node{Name: "direction=", Dynamic: func() []string { return []string{"receive", "send", "both"} }})
  667. // 5. /log
  668. logNode := root.AddChild(&Node{Name: "log"})
  669. logPrint := logNode.AddChild(&Node{Name: "print", Aliases: []string{"pr", "show"}})
  670. logPrint.AddChild(&Node{Name: "limit="})
  671. logPrint.AddChild(&Node{Name: "filter="})
  672. // 6. /user
  673. userNode := root.AddChild(&Node{Name: "user", Aliases: []string{"users"}})
  674. addPrint(userNode)
  675. addRemove(userNode)
  676. userAdd := userNode.AddChild(&Node{Name: "add"})
  677. userAdd.AddChild(&Node{Name: "name="})
  678. userAdd.AddChild(&Node{Name: "password="})
  679. userAdd.AddChild(&Node{Name: "role=", Dynamic: func() []string { return []string{"admin", "full", "read", "viewer"} }})
  680. userAdd.AddChild(&Node{Name: "comment="})
  681. userSet := userNode.AddChild(&Node{Name: "set"})
  682. userSet.AddChild(&Node{Name: "name="})
  683. userSet.AddChild(&Node{Name: "password="})
  684. userSet.AddChild(&Node{Name: "role=", Dynamic: func() []string { return []string{"admin", "full", "read", "viewer"} }})
  685. userSet.AddChild(&Node{Name: "comment="})
  686. userSet.AddChild(&Node{Name: "disabled=", Dynamic: func() []string { return []string{"yes", "no"} }})
  687. // 7. /system
  688. sysNode := root.AddChild(&Node{Name: "system"})
  689. sysRes := sysNode.AddChild(&Node{Name: "resource"})
  690. addPrint(sysRes)
  691. sysIdent := sysNode.AddChild(&Node{Name: "identity"})
  692. addPrint(sysIdent)
  693. sysIdentSet := sysIdent.AddChild(&Node{Name: "set"})
  694. sysIdentSet.AddChild(&Node{Name: "name="})
  695. sysNode.AddChild(&Node{Name: "reboot"})
  696. // 8. Direct root utility commands
  697. root.AddChild(&Node{Name: "clear"})
  698. root.AddChild(&Node{Name: "help", Aliases: []string{"?"}})
  699. root.AddChild(&Node{Name: "quit"})
  700. root.AddChild(&Node{Name: "exit"})
  701. root.AddChild(&Node{Name: "ping"})
  702. root.AddChild(&Node{Name: "traceroute"})
  703. return root
  704. }