completer.go 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883
  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", "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 vrrp
  486. vrrpNode := ifaceNode.AddChild(&Node{Name: "vrrp"})
  487. addPrint(vrrpNode)
  488. addRemove(vrrpNode)
  489. addEnableDisable(vrrpNode)
  490. vrrpAdd := vrrpNode.AddChild(&Node{Name: "add"})
  491. vrrpAdd.AddChild(&Node{Name: "name="})
  492. vrrpAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  493. vrrpAdd.AddChild(&Node{Name: "vrid="})
  494. vrrpAdd.AddChild(&Node{Name: "priority="})
  495. // /interface pppoe-client
  496. pppoeNode := ifaceNode.AddChild(&Node{Name: "pppoe-client"})
  497. addPrint(pppoeNode)
  498. addRemove(pppoeNode)
  499. pppoeNode.AddChild(&Node{Name: "connect"})
  500. pppoeNode.AddChild(&Node{Name: "disconnect"})
  501. pppoeAdd := pppoeNode.AddChild(&Node{Name: "add"})
  502. pppoeAdd.AddChild(&Node{Name: "name="})
  503. pppoeAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  504. pppoeAdd.AddChild(&Node{Name: "user="})
  505. pppoeAdd.AddChild(&Node{Name: "password="})
  506. // 2. /ip
  507. ipNode := root.AddChild(&Node{Name: "ip"})
  508. // /ip address
  509. ipAddrNode := ipNode.AddChild(&Node{Name: "address", Aliases: []string{"addr"}})
  510. addPrint(ipAddrNode)
  511. addRemove(ipAddrNode)
  512. ipAddrAdd := ipAddrNode.AddChild(&Node{Name: "add"})
  513. ipAddrAdd.AddChild(&Node{Name: "address="})
  514. ipAddrAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  515. ipAddrAdd.AddChild(&Node{Name: "network="})
  516. // /ip route
  517. ipRouteNode := ipNode.AddChild(&Node{Name: "route", Aliases: []string{"routes"}})
  518. addPrint(ipRouteNode)
  519. addRemove(ipRouteNode)
  520. addEnableDisable(ipRouteNode)
  521. ipRouteAdd := ipRouteNode.AddChild(&Node{Name: "add"})
  522. ipRouteAdd.AddChild(&Node{Name: "dst-address="})
  523. ipRouteAdd.AddChild(&Node{Name: "gateway="})
  524. ipRouteAdd.AddChild(&Node{Name: "distance="})
  525. // /ip route rule
  526. ipRouteRule := ipRouteNode.AddChild(&Node{Name: "rule", Aliases: []string{"rules"}})
  527. addPrint(ipRouteRule)
  528. addRemove(ipRouteRule)
  529. ipRouteRuleAdd := ipRouteRule.AddChild(&Node{Name: "add"})
  530. ipRouteRuleAdd.AddChild(&Node{Name: "src-address="})
  531. ipRouteRuleAdd.AddChild(&Node{Name: "dst-address="})
  532. ipRouteRuleAdd.AddChild(&Node{Name: "table="})
  533. ipRouteRuleAdd.AddChild(&Node{Name: "action=", Dynamic: func() []string { return []string{"lookup", "unreachable", "drop"} }})
  534. // /ip route table
  535. ipRouteTable := ipRouteNode.AddChild(&Node{Name: "table", Aliases: []string{"tables"}})
  536. addPrint(ipRouteTable)
  537. addRemove(ipRouteTable)
  538. ipRouteTableAdd := ipRouteTable.AddChild(&Node{Name: "add"})
  539. ipRouteTableAdd.AddChild(&Node{Name: "name="})
  540. ipRouteTableAdd.AddChild(&Node{Name: "fib="})
  541. // /ip dhcp-client
  542. dhcpCliNode := ipNode.AddChild(&Node{Name: "dhcp-client"})
  543. addPrint(dhcpCliNode)
  544. addRemove(dhcpCliNode)
  545. dhcpCliNode.AddChild(&Node{Name: "renew"})
  546. dhcpCliNode.AddChild(&Node{Name: "release"})
  547. dhcpCliAdd := dhcpCliNode.AddChild(&Node{Name: "add"})
  548. dhcpCliAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  549. dhcpCliAdd.AddChild(&Node{Name: "disabled=", Dynamic: func() []string { return []string{"yes", "no"} }})
  550. // /ip dhcp-server
  551. dhcpSrvNode := ipNode.AddChild(&Node{Name: "dhcp-server"})
  552. addPrint(dhcpSrvNode)
  553. addRemove(dhcpSrvNode)
  554. addEnableDisable(dhcpSrvNode)
  555. dhcpSrvAdd := dhcpSrvNode.AddChild(&Node{Name: "add"})
  556. dhcpSrvAdd.AddChild(&Node{Name: "name="})
  557. dhcpSrvAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  558. dhcpSrvAdd.AddChild(&Node{Name: "address-pool="})
  559. dhcpSrvAdd.AddChild(&Node{Name: "lease-time="})
  560. // /ip dhcp-server lease
  561. dhcpLeaseNode := dhcpSrvNode.AddChild(&Node{Name: "lease", Aliases: []string{"leases"}})
  562. addPrint(dhcpLeaseNode)
  563. addRemove(dhcpLeaseNode)
  564. // /ip dhcp-server network
  565. dhcpNetNode := dhcpSrvNode.AddChild(&Node{Name: "network", Aliases: []string{"networks"}})
  566. addPrint(dhcpNetNode)
  567. addRemove(dhcpNetNode)
  568. dhcpNetAdd := dhcpNetNode.AddChild(&Node{Name: "add"})
  569. dhcpNetAdd.AddChild(&Node{Name: "address="})
  570. dhcpNetAdd.AddChild(&Node{Name: "gateway="})
  571. dhcpNetAdd.AddChild(&Node{Name: "dns-server="})
  572. // /ip dns
  573. dnsNode := ipNode.AddChild(&Node{Name: "dns"})
  574. addPrint(dnsNode)
  575. dnsSet := dnsNode.AddChild(&Node{Name: "set"})
  576. dnsSet.AddChild(&Node{Name: "servers="})
  577. dnsSet.AddChild(&Node{Name: "allow-remote-requests=", Dynamic: func() []string { return []string{"yes", "no"} }})
  578. dnsCache := dnsNode.AddChild(&Node{Name: "cache"})
  579. addPrint(dnsCache)
  580. dnsCache.AddChild(&Node{Name: "flush"})
  581. dnsStatic := dnsNode.AddChild(&Node{Name: "static"})
  582. addPrint(dnsStatic)
  583. addRemove(dnsStatic)
  584. dnsStaticAdd := dnsStatic.AddChild(&Node{Name: "add"})
  585. dnsStaticAdd.AddChild(&Node{Name: "name="})
  586. dnsStaticAdd.AddChild(&Node{Name: "address="})
  587. // /ip firewall
  588. fwNode := ipNode.AddChild(&Node{Name: "firewall"})
  589. // /ip firewall filter
  590. fwFilter := fwNode.AddChild(&Node{Name: "filter"})
  591. addPrint(fwFilter)
  592. addRemove(fwFilter)
  593. addEnableDisable(fwFilter)
  594. fwFilterAdd := fwFilter.AddChild(&Node{Name: "add"})
  595. fwFilterAdd.AddChild(&Node{Name: "chain=", Dynamic: func() []string { return []string{"forward", "input", "output"} }})
  596. fwFilterAdd.AddChild(&Node{Name: "action=", Dynamic: func() []string { return []string{"accept", "drop", "reject"} }})
  597. fwFilterAdd.AddChild(&Node{Name: "protocol=", Dynamic: func() []string { return []string{"icmp", "tcp", "udp"} }})
  598. fwFilterAdd.AddChild(&Node{Name: "src-address="})
  599. fwFilterAdd.AddChild(&Node{Name: "dst-address="})
  600. fwFilterAdd.AddChild(&Node{Name: "dst-port="})
  601. fwFilterAdd.AddChild(&Node{Name: "in-interface=", Dynamic: dynInterfaces})
  602. fwFilterAdd.AddChild(&Node{Name: "out-interface=", Dynamic: dynInterfaces})
  603. fwFilterAdd.AddChild(&Node{Name: "comment="})
  604. // /ip firewall nat
  605. fwNat := fwNode.AddChild(&Node{Name: "nat"})
  606. addPrint(fwNat)
  607. addRemove(fwNat)
  608. addEnableDisable(fwNat)
  609. fwNatAdd := fwNat.AddChild(&Node{Name: "add"})
  610. fwNatAdd.AddChild(&Node{Name: "chain=", Dynamic: func() []string { return []string{"dstnat", "srcnat"} }})
  611. fwNatAdd.AddChild(&Node{Name: "action=", Dynamic: func() []string { return []string{"accept", "dst-nat", "masquerade", "redirect", "src-nat"} }})
  612. fwNatAdd.AddChild(&Node{Name: "protocol=", Dynamic: func() []string { return []string{"icmp", "tcp", "udp"} }})
  613. fwNatAdd.AddChild(&Node{Name: "src-address="})
  614. fwNatAdd.AddChild(&Node{Name: "dst-address="})
  615. fwNatAdd.AddChild(&Node{Name: "to-addresses="})
  616. fwNatAdd.AddChild(&Node{Name: "to-ports="})
  617. fwNatAdd.AddChild(&Node{Name: "out-interface=", Dynamic: dynInterfaces})
  618. fwNatAdd.AddChild(&Node{Name: "comment="})
  619. // /ip firewall mangle
  620. fwMangle := fwNode.AddChild(&Node{Name: "mangle"})
  621. addPrint(fwMangle)
  622. addRemove(fwMangle)
  623. addEnableDisable(fwMangle)
  624. fwMangleAdd := fwMangle.AddChild(&Node{Name: "add"})
  625. fwMangleAdd.AddChild(&Node{Name: "chain=", Dynamic: func() []string { return []string{"forward", "input", "output", "postrouting", "prerouting"} }})
  626. fwMangleAdd.AddChild(&Node{Name: "action=", Dynamic: func() []string { return []string{"accept", "mark-packet", "mark-routing"} }})
  627. fwMangleAdd.AddChild(&Node{Name: "protocol=", Dynamic: func() []string { return []string{"icmp", "tcp", "udp"} }})
  628. fwMangleAdd.AddChild(&Node{Name: "src-address="})
  629. fwMangleAdd.AddChild(&Node{Name: "dst-address="})
  630. // /ip firewall address-list
  631. fwAddrList := fwNode.AddChild(&Node{Name: "address-list"})
  632. addPrint(fwAddrList)
  633. addRemove(fwAddrList)
  634. addEnableDisable(fwAddrList)
  635. fwAddrListAdd := fwAddrList.AddChild(&Node{Name: "add"})
  636. fwAddrListAdd.AddChild(&Node{Name: "list="})
  637. fwAddrListAdd.AddChild(&Node{Name: "address="})
  638. fwAddrListAdd.AddChild(&Node{Name: "comment="})
  639. // /ip service
  640. serviceNode := ipNode.AddChild(&Node{Name: "service", Aliases: []string{"services"}})
  641. addPrint(serviceNode)
  642. serviceNode.AddChild(&Node{Name: "enable", Dynamic: dynServices})
  643. serviceNode.AddChild(&Node{Name: "disable", Dynamic: dynServices})
  644. serviceSet := serviceNode.AddChild(&Node{Name: "set", Dynamic: dynServices})
  645. serviceSet.AddChild(&Node{Name: "port="})
  646. serviceSet.AddChild(&Node{Name: "disabled=", Dynamic: func() []string { return []string{"yes", "no"} }})
  647. serviceSet.AddChild(&Node{Name: "certificate=", Dynamic: dynCertificates})
  648. // 3. /routing
  649. routingNode := root.AddChild(&Node{Name: "routing"})
  650. // /routing bgp
  651. bgpNode := routingNode.AddChild(&Node{Name: "bgp"})
  652. bgpInst := bgpNode.AddChild(&Node{Name: "instance"})
  653. addPrint(bgpInst)
  654. bgpInstSet := bgpInst.AddChild(&Node{Name: "set"})
  655. bgpInstSet.AddChild(&Node{Name: "as="})
  656. bgpInstSet.AddChild(&Node{Name: "router-id="})
  657. bgpInstSet.AddChild(&Node{Name: "enabled=", Dynamic: func() []string { return []string{"yes", "no"} }})
  658. bgpPeer := bgpNode.AddChild(&Node{Name: "peer", Aliases: []string{"peers", "connection"}})
  659. addPrint(bgpPeer)
  660. addRemove(bgpPeer)
  661. addEnableDisable(bgpPeer)
  662. bgpPeerAdd := bgpPeer.AddChild(&Node{Name: "add"})
  663. bgpPeerAdd.AddChild(&Node{Name: "name="})
  664. bgpPeerAdd.AddChild(&Node{Name: "remote-address="})
  665. bgpPeerAdd.AddChild(&Node{Name: "remote-as="})
  666. bgpPeerAdd.AddChild(&Node{Name: "description="})
  667. bgpNet := bgpNode.AddChild(&Node{Name: "network"})
  668. addPrint(bgpNet)
  669. addRemove(bgpNet)
  670. bgpNetAdd := bgpNet.AddChild(&Node{Name: "add"})
  671. bgpNetAdd.AddChild(&Node{Name: "network="})
  672. bgpRoutes := bgpNode.AddChild(&Node{Name: "routes"})
  673. addPrint(bgpRoutes)
  674. // /routing ospf
  675. ospfNode := routingNode.AddChild(&Node{Name: "ospf"})
  676. ospfInst := ospfNode.AddChild(&Node{Name: "instance"})
  677. addPrint(ospfInst)
  678. ospfInstSet := ospfInst.AddChild(&Node{Name: "set"})
  679. ospfInstSet.AddChild(&Node{Name: "router-id="})
  680. ospfInstSet.AddChild(&Node{Name: "enabled=", Dynamic: func() []string { return []string{"yes", "no"} }})
  681. ospfArea := ospfNode.AddChild(&Node{Name: "area"})
  682. addPrint(ospfArea)
  683. addRemove(ospfArea)
  684. ospfAreaAdd := ospfArea.AddChild(&Node{Name: "add"})
  685. ospfAreaAdd.AddChild(&Node{Name: "name="})
  686. ospfAreaAdd.AddChild(&Node{Name: "area-id="})
  687. ospfIface := ospfNode.AddChild(&Node{Name: "interface"})
  688. addPrint(ospfIface)
  689. addRemove(ospfIface)
  690. ospfIfaceAdd := ospfIface.AddChild(&Node{Name: "add"})
  691. ospfIfaceAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  692. ospfIfaceAdd.AddChild(&Node{Name: "cost="})
  693. ospfIfaceAdd.AddChild(&Node{Name: "priority="})
  694. ospfIfaceAdd.AddChild(&Node{Name: "network-type=", Dynamic: func() []string { return []string{"broadcast", "point-to-point"} }})
  695. ospfNeighbor := ospfNode.AddChild(&Node{Name: "neighbor", Aliases: []string{"neighbors"}})
  696. addPrint(ospfNeighbor)
  697. // /routing filter
  698. routingFilter := routingNode.AddChild(&Node{Name: "filter"})
  699. addPrint(routingFilter)
  700. rfRule := routingFilter.AddChild(&Node{Name: "rule", Aliases: []string{"rules"}})
  701. addPrint(rfRule)
  702. addRemove(rfRule)
  703. rfRuleAdd := rfRule.AddChild(&Node{Name: "add"})
  704. rfRuleAdd.AddChild(&Node{Name: "chain="})
  705. rfRuleAdd.AddChild(&Node{Name: "action=", Dynamic: func() []string { return []string{"accept", "discard", "reject"} }})
  706. rfRuleAdd.AddChild(&Node{Name: "prefix="})
  707. rfRuleAdd.AddChild(&Node{Name: "prefix-len="})
  708. // 4. /tool
  709. toolNode := root.AddChild(&Node{Name: "tool"})
  710. toolPing := toolNode.AddChild(&Node{Name: "ping"})
  711. toolPing.AddChild(&Node{Name: "count="})
  712. toolPing.AddChild(&Node{Name: "size="})
  713. toolPing.AddChild(&Node{Name: "interval="})
  714. toolTrace := toolNode.AddChild(&Node{Name: "traceroute"})
  715. toolTrace.AddChild(&Node{Name: "max-hops="})
  716. toolBtest := toolNode.AddChild(&Node{Name: "bandwidth-test", Aliases: []string{"btest"}})
  717. toolBtest.AddChild(&Node{Name: "target="})
  718. toolBtest.AddChild(&Node{Name: "duration="})
  719. toolBtest.AddChild(&Node{Name: "direction=", Dynamic: func() []string { return []string{"receive", "send", "both"} }})
  720. // 5. /log
  721. logNode := root.AddChild(&Node{Name: "log"})
  722. logPrint := logNode.AddChild(&Node{Name: "print", Aliases: []string{"pr", "show"}})
  723. logPrint.AddChild(&Node{Name: "limit="})
  724. logPrint.AddChild(&Node{Name: "filter="})
  725. // 6. /user
  726. userNode := root.AddChild(&Node{Name: "user", Aliases: []string{"users"}})
  727. addPrint(userNode)
  728. addRemove(userNode)
  729. userAdd := userNode.AddChild(&Node{Name: "add"})
  730. userAdd.AddChild(&Node{Name: "name="})
  731. userAdd.AddChild(&Node{Name: "password="})
  732. userAdd.AddChild(&Node{Name: "role=", Dynamic: func() []string { return []string{"admin", "full", "read", "viewer"} }})
  733. userAdd.AddChild(&Node{Name: "comment="})
  734. userSet := userNode.AddChild(&Node{Name: "set"})
  735. userSet.AddChild(&Node{Name: "name="})
  736. userSet.AddChild(&Node{Name: "password="})
  737. userSet.AddChild(&Node{Name: "role=", Dynamic: func() []string { return []string{"admin", "full", "read", "viewer"} }})
  738. userSet.AddChild(&Node{Name: "comment="})
  739. userSet.AddChild(&Node{Name: "disabled=", Dynamic: func() []string { return []string{"yes", "no"} }})
  740. // 7. /system
  741. sysNode := root.AddChild(&Node{Name: "system"})
  742. sysRes := sysNode.AddChild(&Node{Name: "resource"})
  743. addPrint(sysRes)
  744. sysIdent := sysNode.AddChild(&Node{Name: "identity"})
  745. addPrint(sysIdent)
  746. sysIdentSet := sysIdent.AddChild(&Node{Name: "set"})
  747. sysIdentSet.AddChild(&Node{Name: "name="})
  748. sysNode.AddChild(&Node{Name: "reboot"})
  749. // 8. /certificate
  750. certNode := root.AddChild(&Node{Name: "certificate", Aliases: []string{"certificates", "cert"}})
  751. addPrint(certNode)
  752. certNode.AddChild(&Node{Name: "remove", Aliases: []string{"rem", "delete"}, Dynamic: dynCertificates})
  753. certNode.AddChild(&Node{Name: "export", Dynamic: dynCertificates})
  754. certAddCA := certNode.AddChild(&Node{Name: "add-ca"})
  755. certAddCA.AddChild(&Node{Name: "name="})
  756. certAddCA.AddChild(&Node{Name: "common-name="})
  757. certAddCA.AddChild(&Node{Name: "days="})
  758. certAddCA.AddChild(&Node{Name: "key-type=", Dynamic: func() []string { return []string{"RSA-2048", "RSA-4096", "ECDSA-P256"} }})
  759. certAdd := certNode.AddChild(&Node{Name: "add"})
  760. certAdd.AddChild(&Node{Name: "name="})
  761. certAdd.AddChild(&Node{Name: "common-name="})
  762. certAdd.AddChild(&Node{Name: "ca=", Dynamic: dynCAs})
  763. certAdd.AddChild(&Node{Name: "sans="})
  764. certAdd.AddChild(&Node{Name: "days="})
  765. certAdd.AddChild(&Node{Name: "key-type=", Dynamic: func() []string { return []string{"RSA-2048", "RSA-4096", "ECDSA-P256"} }})
  766. certLE := certNode.AddChild(&Node{Name: "issue-letsencrypt"})
  767. certLE.AddChild(&Node{Name: "domain="})
  768. certLE.AddChild(&Node{Name: "email="})
  769. certLE.AddChild(&Node{Name: "name="})
  770. certLE.AddChild(&Node{Name: "staging=", Dynamic: func() []string { return []string{"yes", "no"} }})
  771. // 9. Direct root utility commands
  772. root.AddChild(&Node{Name: "clear"})
  773. root.AddChild(&Node{Name: "help", Aliases: []string{"?"}})
  774. root.AddChild(&Node{Name: "quit"})
  775. root.AddChild(&Node{Name: "exit"})
  776. root.AddChild(&Node{Name: "ping"})
  777. root.AddChild(&Node{Name: "traceroute"})
  778. return root
  779. }