completer.go 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905
  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", "certificate", "certificates",
  306. "vlan", "bridge", "wireguard", "eoip", "vxlan", "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 "www", "www-ssl", "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. if c.executor != nil && c.executor.servicesMgr != nil {
  362. var names []string
  363. for _, s := range c.executor.servicesMgr.ListServices() {
  364. names = append(names, s.Name)
  365. }
  366. return names
  367. }
  368. return []string{"www", "www-ssl", "telnet", "ssh"}
  369. }
  370. // Helper for certificates
  371. dynCertificates := func() []string {
  372. if c.executor != nil && c.executor.certMgr != nil {
  373. var names []string
  374. for _, cert := range c.executor.certMgr.ListCertificates() {
  375. names = append(names, cert.Name)
  376. }
  377. return names
  378. }
  379. return nil
  380. }
  381. dynCAs := func() []string {
  382. if c.executor != nil && c.executor.certMgr != nil {
  383. var names []string
  384. for _, cert := range c.executor.certMgr.ListCertificates() {
  385. if cert.IsCA {
  386. names = append(names, cert.Name)
  387. }
  388. }
  389. return names
  390. }
  391. return nil
  392. }
  393. // Common standard actions
  394. addPrint := func(n *Node) {
  395. n.AddChild(&Node{Name: "print", Aliases: []string{"pr", "show", "list"}})
  396. }
  397. addRemove := func(n *Node) {
  398. n.AddChild(&Node{Name: "remove", Aliases: []string{"rem", "delete"}})
  399. }
  400. addEnableDisable := func(n *Node) {
  401. n.AddChild(&Node{Name: "enable"})
  402. n.AddChild(&Node{Name: "disable"})
  403. }
  404. // 1. /interface
  405. ifaceNode := root.AddChild(&Node{Name: "interface", Aliases: []string{"interfaces"}})
  406. addPrint(ifaceNode)
  407. ifaceNode.AddChild(&Node{Name: "enable", Dynamic: dynInterfaces})
  408. ifaceNode.AddChild(&Node{Name: "disable", Dynamic: dynInterfaces})
  409. ifaceSet := ifaceNode.AddChild(&Node{Name: "set", Dynamic: dynInterfaces})
  410. ifaceSet.AddChild(&Node{Name: "mtu="})
  411. // /interface vlan
  412. vlanNode := ifaceNode.AddChild(&Node{Name: "vlan", Aliases: []string{"vlans"}})
  413. addPrint(vlanNode)
  414. addRemove(vlanNode)
  415. vlanAdd := vlanNode.AddChild(&Node{Name: "add"})
  416. vlanAdd.AddChild(&Node{Name: "name="})
  417. vlanAdd.AddChild(&Node{Name: "vlan-id="})
  418. vlanAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  419. // /interface bridge
  420. bridgeNode := ifaceNode.AddChild(&Node{Name: "bridge", Aliases: []string{"bridges"}})
  421. addPrint(bridgeNode)
  422. addRemove(bridgeNode)
  423. addEnableDisable(bridgeNode)
  424. bridgeAdd := bridgeNode.AddChild(&Node{Name: "add"})
  425. bridgeAdd.AddChild(&Node{Name: "name="})
  426. // /interface bridge port
  427. bridgePortNode := bridgeNode.AddChild(&Node{Name: "port", Aliases: []string{"ports"}})
  428. addPrint(bridgePortNode)
  429. addRemove(bridgePortNode)
  430. addEnableDisable(bridgePortNode)
  431. bridgePortAdd := bridgePortNode.AddChild(&Node{Name: "add"})
  432. bridgePortAdd.AddChild(&Node{Name: "bridge="})
  433. bridgePortAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  434. bridgePortAdd.AddChild(&Node{Name: "pvid="})
  435. // /interface wireguard
  436. wgNode := ifaceNode.AddChild(&Node{Name: "wireguard", Aliases: []string{"wg"}})
  437. addPrint(wgNode)
  438. addRemove(wgNode)
  439. addEnableDisable(wgNode)
  440. wgAdd := wgNode.AddChild(&Node{Name: "add"})
  441. wgAdd.AddChild(&Node{Name: "name="})
  442. wgAdd.AddChild(&Node{Name: "listen-port="})
  443. wgAdd.AddChild(&Node{Name: "private-key="})
  444. // /interface wireguard peers
  445. wgPeers := wgNode.AddChild(&Node{Name: "peers", Aliases: []string{"peer"}})
  446. addPrint(wgPeers)
  447. addRemove(wgPeers)
  448. wgPeersAdd := wgPeers.AddChild(&Node{Name: "add"})
  449. wgPeersAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  450. wgPeersAdd.AddChild(&Node{Name: "public-key="})
  451. wgPeersAdd.AddChild(&Node{Name: "allowed-address="})
  452. wgPeersAdd.AddChild(&Node{Name: "endpoint-address="})
  453. wgPeersAdd.AddChild(&Node{Name: "endpoint-port="})
  454. // /interface aerotun
  455. aero := ifaceNode.AddChild(&Node{Name: "aerotun"})
  456. names := func() []string {
  457. if c.executor.vpnMgr == nil || c.executor.vpnMgr.AeroTUN == nil {
  458. return nil
  459. }
  460. var out []string
  461. for _, inst := range c.executor.vpnMgr.AeroTUN.List() {
  462. out = append(out, inst.Name)
  463. }
  464. return out
  465. }
  466. aero.AddChild(&Node{Name: "print"}).AddChild(&Node{Name: "detail="})
  467. aero.AddChild(&Node{Name: "genkey"})
  468. for _, action := range []string{"add", "set", "remove", "enable", "disable", "start", "stop"} {
  469. n := aero.AddChild(&Node{Name: action, Dynamic: names})
  470. if action == "add" || action == "set" {
  471. 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="} {
  472. n.AddChild(&Node{Name: param})
  473. }
  474. }
  475. }
  476. // /interface eoip
  477. eoipNode := ifaceNode.AddChild(&Node{Name: "eoip"})
  478. addPrint(eoipNode)
  479. addRemove(eoipNode)
  480. addEnableDisable(eoipNode)
  481. eoipAdd := eoipNode.AddChild(&Node{Name: "add"})
  482. eoipAdd.AddChild(&Node{Name: "name="})
  483. eoipAdd.AddChild(&Node{Name: "remote-ip="})
  484. eoipAdd.AddChild(&Node{Name: "tunnel-id="})
  485. // /interface vxlan
  486. vxlanNode := ifaceNode.AddChild(&Node{Name: "vxlan"})
  487. addPrint(vxlanNode)
  488. addRemove(vxlanNode)
  489. addEnableDisable(vxlanNode)
  490. vxlanAdd := vxlanNode.AddChild(&Node{Name: "add"})
  491. for _, p := range []string{"name=", "vni=", "port=", "mtu=", "mac-address=", "local-address=", "remote-address=", "group=", "interface=", "learning=", "comment="} {
  492. vxlanAdd.AddChild(&Node{Name: p})
  493. }
  494. vxlanSet := vxlanNode.AddChild(&Node{Name: "set"})
  495. for _, p := range []string{"name=", "vni=", "port=", "mtu=", "mac-address=", "local-address=", "remote-address=", "group=", "interface=", "comment="} {
  496. vxlanSet.AddChild(&Node{Name: p})
  497. }
  498. vtepsNode := vxlanNode.AddChild(&Node{Name: "vteps"})
  499. addPrint(vtepsNode)
  500. addRemove(vtepsNode)
  501. vtepsAdd := vtepsNode.AddChild(&Node{Name: "add"})
  502. vtepsAdd.AddChild(&Node{Name: "interface="})
  503. vtepsAdd.AddChild(&Node{Name: "remote-ip="})
  504. vtepsAdd.AddChild(&Node{Name: "port="})
  505. vtepsAdd.AddChild(&Node{Name: "comment="})
  506. // /interface vrrp
  507. vrrpNode := ifaceNode.AddChild(&Node{Name: "vrrp"})
  508. addPrint(vrrpNode)
  509. addRemove(vrrpNode)
  510. addEnableDisable(vrrpNode)
  511. vrrpAdd := vrrpNode.AddChild(&Node{Name: "add"})
  512. vrrpAdd.AddChild(&Node{Name: "name="})
  513. vrrpAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  514. vrrpAdd.AddChild(&Node{Name: "vrid="})
  515. vrrpAdd.AddChild(&Node{Name: "priority="})
  516. // /interface pppoe-client
  517. pppoeNode := ifaceNode.AddChild(&Node{Name: "pppoe-client"})
  518. addPrint(pppoeNode)
  519. addRemove(pppoeNode)
  520. pppoeNode.AddChild(&Node{Name: "connect"})
  521. pppoeNode.AddChild(&Node{Name: "disconnect"})
  522. pppoeAdd := pppoeNode.AddChild(&Node{Name: "add"})
  523. pppoeAdd.AddChild(&Node{Name: "name="})
  524. pppoeAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  525. pppoeAdd.AddChild(&Node{Name: "user="})
  526. pppoeAdd.AddChild(&Node{Name: "password="})
  527. // 2. /ip
  528. ipNode := root.AddChild(&Node{Name: "ip"})
  529. // /ip address
  530. ipAddrNode := ipNode.AddChild(&Node{Name: "address", Aliases: []string{"addr"}})
  531. addPrint(ipAddrNode)
  532. addRemove(ipAddrNode)
  533. ipAddrAdd := ipAddrNode.AddChild(&Node{Name: "add"})
  534. ipAddrAdd.AddChild(&Node{Name: "address="})
  535. ipAddrAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  536. ipAddrAdd.AddChild(&Node{Name: "network="})
  537. // /ip route
  538. ipRouteNode := ipNode.AddChild(&Node{Name: "route", Aliases: []string{"routes"}})
  539. addPrint(ipRouteNode)
  540. addRemove(ipRouteNode)
  541. addEnableDisable(ipRouteNode)
  542. ipRouteAdd := ipRouteNode.AddChild(&Node{Name: "add"})
  543. ipRouteAdd.AddChild(&Node{Name: "dst-address="})
  544. ipRouteAdd.AddChild(&Node{Name: "gateway="})
  545. ipRouteAdd.AddChild(&Node{Name: "distance="})
  546. // /ip route rule
  547. ipRouteRule := ipRouteNode.AddChild(&Node{Name: "rule", Aliases: []string{"rules"}})
  548. addPrint(ipRouteRule)
  549. addRemove(ipRouteRule)
  550. ipRouteRuleAdd := ipRouteRule.AddChild(&Node{Name: "add"})
  551. ipRouteRuleAdd.AddChild(&Node{Name: "src-address="})
  552. ipRouteRuleAdd.AddChild(&Node{Name: "dst-address="})
  553. ipRouteRuleAdd.AddChild(&Node{Name: "table="})
  554. ipRouteRuleAdd.AddChild(&Node{Name: "action=", Dynamic: func() []string { return []string{"lookup", "unreachable", "drop"} }})
  555. // /ip route table
  556. ipRouteTable := ipRouteNode.AddChild(&Node{Name: "table", Aliases: []string{"tables"}})
  557. addPrint(ipRouteTable)
  558. addRemove(ipRouteTable)
  559. ipRouteTableAdd := ipRouteTable.AddChild(&Node{Name: "add"})
  560. ipRouteTableAdd.AddChild(&Node{Name: "name="})
  561. ipRouteTableAdd.AddChild(&Node{Name: "fib="})
  562. // /ip dhcp-client
  563. dhcpCliNode := ipNode.AddChild(&Node{Name: "dhcp-client"})
  564. addPrint(dhcpCliNode)
  565. addRemove(dhcpCliNode)
  566. dhcpCliNode.AddChild(&Node{Name: "renew"})
  567. dhcpCliNode.AddChild(&Node{Name: "release"})
  568. dhcpCliAdd := dhcpCliNode.AddChild(&Node{Name: "add"})
  569. dhcpCliAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  570. dhcpCliAdd.AddChild(&Node{Name: "disabled=", Dynamic: func() []string { return []string{"yes", "no"} }})
  571. // /ip dhcp-server
  572. dhcpSrvNode := ipNode.AddChild(&Node{Name: "dhcp-server"})
  573. addPrint(dhcpSrvNode)
  574. addRemove(dhcpSrvNode)
  575. addEnableDisable(dhcpSrvNode)
  576. dhcpSrvAdd := dhcpSrvNode.AddChild(&Node{Name: "add"})
  577. dhcpSrvAdd.AddChild(&Node{Name: "name="})
  578. dhcpSrvAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  579. dhcpSrvAdd.AddChild(&Node{Name: "address-pool="})
  580. dhcpSrvAdd.AddChild(&Node{Name: "lease-time="})
  581. // /ip dhcp-server lease
  582. dhcpLeaseNode := dhcpSrvNode.AddChild(&Node{Name: "lease", Aliases: []string{"leases"}})
  583. addPrint(dhcpLeaseNode)
  584. addRemove(dhcpLeaseNode)
  585. // /ip dhcp-server network
  586. dhcpNetNode := dhcpSrvNode.AddChild(&Node{Name: "network", Aliases: []string{"networks"}})
  587. addPrint(dhcpNetNode)
  588. addRemove(dhcpNetNode)
  589. dhcpNetAdd := dhcpNetNode.AddChild(&Node{Name: "add"})
  590. dhcpNetAdd.AddChild(&Node{Name: "address="})
  591. dhcpNetAdd.AddChild(&Node{Name: "gateway="})
  592. dhcpNetAdd.AddChild(&Node{Name: "dns-server="})
  593. // /ip dns
  594. dnsNode := ipNode.AddChild(&Node{Name: "dns"})
  595. addPrint(dnsNode)
  596. dnsSet := dnsNode.AddChild(&Node{Name: "set"})
  597. dnsSet.AddChild(&Node{Name: "servers="})
  598. dnsSet.AddChild(&Node{Name: "allow-remote-requests=", Dynamic: func() []string { return []string{"yes", "no"} }})
  599. dnsCache := dnsNode.AddChild(&Node{Name: "cache"})
  600. addPrint(dnsCache)
  601. dnsCache.AddChild(&Node{Name: "flush"})
  602. dnsStatic := dnsNode.AddChild(&Node{Name: "static"})
  603. addPrint(dnsStatic)
  604. addRemove(dnsStatic)
  605. dnsStaticAdd := dnsStatic.AddChild(&Node{Name: "add"})
  606. dnsStaticAdd.AddChild(&Node{Name: "name="})
  607. dnsStaticAdd.AddChild(&Node{Name: "address="})
  608. // /ip firewall
  609. fwNode := ipNode.AddChild(&Node{Name: "firewall"})
  610. // /ip firewall filter
  611. fwFilter := fwNode.AddChild(&Node{Name: "filter"})
  612. addPrint(fwFilter)
  613. addRemove(fwFilter)
  614. addEnableDisable(fwFilter)
  615. fwFilterAdd := fwFilter.AddChild(&Node{Name: "add"})
  616. fwFilterAdd.AddChild(&Node{Name: "chain=", Dynamic: func() []string { return []string{"forward", "input", "output"} }})
  617. fwFilterAdd.AddChild(&Node{Name: "action=", Dynamic: func() []string { return []string{"accept", "drop", "reject"} }})
  618. fwFilterAdd.AddChild(&Node{Name: "protocol=", Dynamic: func() []string { return []string{"icmp", "tcp", "udp"} }})
  619. fwFilterAdd.AddChild(&Node{Name: "src-address="})
  620. fwFilterAdd.AddChild(&Node{Name: "dst-address="})
  621. fwFilterAdd.AddChild(&Node{Name: "dst-port="})
  622. fwFilterAdd.AddChild(&Node{Name: "in-interface=", Dynamic: dynInterfaces})
  623. fwFilterAdd.AddChild(&Node{Name: "out-interface=", Dynamic: dynInterfaces})
  624. fwFilterAdd.AddChild(&Node{Name: "comment="})
  625. // /ip firewall nat
  626. fwNat := fwNode.AddChild(&Node{Name: "nat"})
  627. addPrint(fwNat)
  628. addRemove(fwNat)
  629. addEnableDisable(fwNat)
  630. fwNatAdd := fwNat.AddChild(&Node{Name: "add"})
  631. fwNatAdd.AddChild(&Node{Name: "chain=", Dynamic: func() []string { return []string{"dstnat", "srcnat"} }})
  632. fwNatAdd.AddChild(&Node{Name: "action=", Dynamic: func() []string { return []string{"accept", "dst-nat", "masquerade", "redirect", "src-nat"} }})
  633. fwNatAdd.AddChild(&Node{Name: "protocol=", Dynamic: func() []string { return []string{"icmp", "tcp", "udp"} }})
  634. fwNatAdd.AddChild(&Node{Name: "src-address="})
  635. fwNatAdd.AddChild(&Node{Name: "dst-address="})
  636. fwNatAdd.AddChild(&Node{Name: "to-addresses="})
  637. fwNatAdd.AddChild(&Node{Name: "to-ports="})
  638. fwNatAdd.AddChild(&Node{Name: "out-interface=", Dynamic: dynInterfaces})
  639. fwNatAdd.AddChild(&Node{Name: "comment="})
  640. // /ip firewall mangle
  641. fwMangle := fwNode.AddChild(&Node{Name: "mangle"})
  642. addPrint(fwMangle)
  643. addRemove(fwMangle)
  644. addEnableDisable(fwMangle)
  645. fwMangleAdd := fwMangle.AddChild(&Node{Name: "add"})
  646. fwMangleAdd.AddChild(&Node{Name: "chain=", Dynamic: func() []string { return []string{"forward", "input", "output", "postrouting", "prerouting"} }})
  647. fwMangleAdd.AddChild(&Node{Name: "action=", Dynamic: func() []string { return []string{"accept", "mark-packet", "mark-routing"} }})
  648. fwMangleAdd.AddChild(&Node{Name: "protocol=", Dynamic: func() []string { return []string{"icmp", "tcp", "udp"} }})
  649. fwMangleAdd.AddChild(&Node{Name: "src-address="})
  650. fwMangleAdd.AddChild(&Node{Name: "dst-address="})
  651. // /ip firewall address-list
  652. fwAddrList := fwNode.AddChild(&Node{Name: "address-list"})
  653. addPrint(fwAddrList)
  654. addRemove(fwAddrList)
  655. addEnableDisable(fwAddrList)
  656. fwAddrListAdd := fwAddrList.AddChild(&Node{Name: "add"})
  657. fwAddrListAdd.AddChild(&Node{Name: "list="})
  658. fwAddrListAdd.AddChild(&Node{Name: "address="})
  659. fwAddrListAdd.AddChild(&Node{Name: "comment="})
  660. // /ip service
  661. serviceNode := ipNode.AddChild(&Node{Name: "service", Aliases: []string{"services"}})
  662. addPrint(serviceNode)
  663. serviceNode.AddChild(&Node{Name: "enable", Dynamic: dynServices})
  664. serviceNode.AddChild(&Node{Name: "disable", Dynamic: dynServices})
  665. serviceSet := serviceNode.AddChild(&Node{Name: "set", Dynamic: dynServices})
  666. serviceSet.AddChild(&Node{Name: "port="})
  667. serviceSet.AddChild(&Node{Name: "disabled=", Dynamic: func() []string { return []string{"yes", "no"} }})
  668. serviceSet.AddChild(&Node{Name: "certificate=", Dynamic: dynCertificates})
  669. // 3. /routing
  670. routingNode := root.AddChild(&Node{Name: "routing"})
  671. // /routing bgp
  672. bgpNode := routingNode.AddChild(&Node{Name: "bgp"})
  673. bgpInst := bgpNode.AddChild(&Node{Name: "instance"})
  674. addPrint(bgpInst)
  675. bgpInstSet := bgpInst.AddChild(&Node{Name: "set"})
  676. bgpInstSet.AddChild(&Node{Name: "as="})
  677. bgpInstSet.AddChild(&Node{Name: "router-id="})
  678. bgpInstSet.AddChild(&Node{Name: "enabled=", Dynamic: func() []string { return []string{"yes", "no"} }})
  679. bgpPeer := bgpNode.AddChild(&Node{Name: "peer", Aliases: []string{"peers", "connection"}})
  680. addPrint(bgpPeer)
  681. addRemove(bgpPeer)
  682. addEnableDisable(bgpPeer)
  683. bgpPeerAdd := bgpPeer.AddChild(&Node{Name: "add"})
  684. bgpPeerAdd.AddChild(&Node{Name: "name="})
  685. bgpPeerAdd.AddChild(&Node{Name: "remote-address="})
  686. bgpPeerAdd.AddChild(&Node{Name: "remote-as="})
  687. bgpPeerAdd.AddChild(&Node{Name: "description="})
  688. bgpNet := bgpNode.AddChild(&Node{Name: "network"})
  689. addPrint(bgpNet)
  690. addRemove(bgpNet)
  691. bgpNetAdd := bgpNet.AddChild(&Node{Name: "add"})
  692. bgpNetAdd.AddChild(&Node{Name: "network="})
  693. bgpRoutes := bgpNode.AddChild(&Node{Name: "routes"})
  694. addPrint(bgpRoutes)
  695. // /routing ospf
  696. ospfNode := routingNode.AddChild(&Node{Name: "ospf"})
  697. ospfInst := ospfNode.AddChild(&Node{Name: "instance"})
  698. addPrint(ospfInst)
  699. ospfInstSet := ospfInst.AddChild(&Node{Name: "set"})
  700. ospfInstSet.AddChild(&Node{Name: "router-id="})
  701. ospfInstSet.AddChild(&Node{Name: "enabled=", Dynamic: func() []string { return []string{"yes", "no"} }})
  702. ospfArea := ospfNode.AddChild(&Node{Name: "area"})
  703. addPrint(ospfArea)
  704. addRemove(ospfArea)
  705. ospfAreaAdd := ospfArea.AddChild(&Node{Name: "add"})
  706. ospfAreaAdd.AddChild(&Node{Name: "name="})
  707. ospfAreaAdd.AddChild(&Node{Name: "area-id="})
  708. ospfIface := ospfNode.AddChild(&Node{Name: "interface"})
  709. addPrint(ospfIface)
  710. addRemove(ospfIface)
  711. ospfIfaceAdd := ospfIface.AddChild(&Node{Name: "add"})
  712. ospfIfaceAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  713. ospfIfaceAdd.AddChild(&Node{Name: "cost="})
  714. ospfIfaceAdd.AddChild(&Node{Name: "priority="})
  715. ospfIfaceAdd.AddChild(&Node{Name: "network-type=", Dynamic: func() []string { return []string{"broadcast", "point-to-point"} }})
  716. ospfNeighbor := ospfNode.AddChild(&Node{Name: "neighbor", Aliases: []string{"neighbors"}})
  717. addPrint(ospfNeighbor)
  718. // /routing filter
  719. routingFilter := routingNode.AddChild(&Node{Name: "filter"})
  720. addPrint(routingFilter)
  721. rfRule := routingFilter.AddChild(&Node{Name: "rule", Aliases: []string{"rules"}})
  722. addPrint(rfRule)
  723. addRemove(rfRule)
  724. rfRuleAdd := rfRule.AddChild(&Node{Name: "add"})
  725. rfRuleAdd.AddChild(&Node{Name: "chain="})
  726. rfRuleAdd.AddChild(&Node{Name: "action=", Dynamic: func() []string { return []string{"accept", "discard", "reject"} }})
  727. rfRuleAdd.AddChild(&Node{Name: "prefix="})
  728. rfRuleAdd.AddChild(&Node{Name: "prefix-len="})
  729. // 4. /tool
  730. toolNode := root.AddChild(&Node{Name: "tool"})
  731. toolPing := toolNode.AddChild(&Node{Name: "ping"})
  732. toolPing.AddChild(&Node{Name: "count="})
  733. toolPing.AddChild(&Node{Name: "size="})
  734. toolPing.AddChild(&Node{Name: "interval="})
  735. toolTrace := toolNode.AddChild(&Node{Name: "traceroute"})
  736. toolTrace.AddChild(&Node{Name: "max-hops="})
  737. toolBtest := toolNode.AddChild(&Node{Name: "bandwidth-test", Aliases: []string{"btest"}})
  738. toolBtest.AddChild(&Node{Name: "target="})
  739. toolBtest.AddChild(&Node{Name: "duration="})
  740. toolBtest.AddChild(&Node{Name: "direction=", Dynamic: func() []string { return []string{"receive", "send", "both"} }})
  741. // 5. /log
  742. logNode := root.AddChild(&Node{Name: "log"})
  743. logPrint := logNode.AddChild(&Node{Name: "print", Aliases: []string{"pr", "show"}})
  744. logPrint.AddChild(&Node{Name: "limit="})
  745. logPrint.AddChild(&Node{Name: "filter="})
  746. // 6. /user
  747. userNode := root.AddChild(&Node{Name: "user", Aliases: []string{"users"}})
  748. addPrint(userNode)
  749. addRemove(userNode)
  750. userAdd := userNode.AddChild(&Node{Name: "add"})
  751. userAdd.AddChild(&Node{Name: "name="})
  752. userAdd.AddChild(&Node{Name: "password="})
  753. userAdd.AddChild(&Node{Name: "role=", Dynamic: func() []string { return []string{"admin", "full", "read", "viewer"} }})
  754. userAdd.AddChild(&Node{Name: "comment="})
  755. userSet := userNode.AddChild(&Node{Name: "set"})
  756. userSet.AddChild(&Node{Name: "name="})
  757. userSet.AddChild(&Node{Name: "password="})
  758. userSet.AddChild(&Node{Name: "role=", Dynamic: func() []string { return []string{"admin", "full", "read", "viewer"} }})
  759. userSet.AddChild(&Node{Name: "comment="})
  760. userSet.AddChild(&Node{Name: "disabled=", Dynamic: func() []string { return []string{"yes", "no"} }})
  761. // 7. /system
  762. sysNode := root.AddChild(&Node{Name: "system"})
  763. sysRes := sysNode.AddChild(&Node{Name: "resource"})
  764. addPrint(sysRes)
  765. sysIdent := sysNode.AddChild(&Node{Name: "identity"})
  766. addPrint(sysIdent)
  767. sysIdentSet := sysIdent.AddChild(&Node{Name: "set"})
  768. sysIdentSet.AddChild(&Node{Name: "name="})
  769. sysNode.AddChild(&Node{Name: "reboot"})
  770. // 8. /certificate
  771. certNode := root.AddChild(&Node{Name: "certificate", Aliases: []string{"certificates", "cert"}})
  772. addPrint(certNode)
  773. certNode.AddChild(&Node{Name: "remove", Aliases: []string{"rem", "delete"}, Dynamic: dynCertificates})
  774. certNode.AddChild(&Node{Name: "export", Dynamic: dynCertificates})
  775. certAddCA := certNode.AddChild(&Node{Name: "add-ca"})
  776. certAddCA.AddChild(&Node{Name: "name="})
  777. certAddCA.AddChild(&Node{Name: "common-name="})
  778. certAddCA.AddChild(&Node{Name: "days="})
  779. certAddCA.AddChild(&Node{Name: "key-type=", Dynamic: func() []string { return []string{"RSA-2048", "RSA-4096", "ECDSA-P256"} }})
  780. certAdd := certNode.AddChild(&Node{Name: "add"})
  781. certAdd.AddChild(&Node{Name: "name="})
  782. certAdd.AddChild(&Node{Name: "common-name="})
  783. certAdd.AddChild(&Node{Name: "ca=", Dynamic: dynCAs})
  784. certAdd.AddChild(&Node{Name: "sans="})
  785. certAdd.AddChild(&Node{Name: "days="})
  786. certAdd.AddChild(&Node{Name: "key-type=", Dynamic: func() []string { return []string{"RSA-2048", "RSA-4096", "ECDSA-P256"} }})
  787. certLE := certNode.AddChild(&Node{Name: "issue-letsencrypt"})
  788. certLE.AddChild(&Node{Name: "domain="})
  789. certLE.AddChild(&Node{Name: "email="})
  790. certLE.AddChild(&Node{Name: "name="})
  791. certLE.AddChild(&Node{Name: "staging=", Dynamic: func() []string { return []string{"yes", "no"} }})
  792. // 9. Direct root utility commands
  793. root.AddChild(&Node{Name: "clear"})
  794. root.AddChild(&Node{Name: "help", Aliases: []string{"?"}})
  795. root.AddChild(&Node{Name: "quit"})
  796. root.AddChild(&Node{Name: "exit"})
  797. root.AddChild(&Node{Name: "ping"})
  798. root.AddChild(&Node{Name: "traceroute"})
  799. return root
  800. }