completer.go 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015
  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", "status":
  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", "reverse-proxy", "sdwan", "sd-wan", "member", "members",
  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. dynSDWANGroups := func() []string {
  394. if c.executor != nil && c.executor.sdwanMgr != nil {
  395. var names []string
  396. for _, g := range c.executor.sdwanMgr.List() {
  397. names = append(names, g.Name)
  398. }
  399. return names
  400. }
  401. return nil
  402. }
  403. // Common standard actions
  404. addPrint := func(n *Node) {
  405. n.AddChild(&Node{Name: "print", Aliases: []string{"pr", "show", "list"}})
  406. }
  407. addRemove := func(n *Node) {
  408. n.AddChild(&Node{Name: "remove", Aliases: []string{"rem", "delete"}})
  409. }
  410. addEnableDisable := func(n *Node) {
  411. n.AddChild(&Node{Name: "enable"})
  412. n.AddChild(&Node{Name: "disable"})
  413. }
  414. // 1. /interface
  415. ifaceNode := root.AddChild(&Node{Name: "interface", Aliases: []string{"interfaces"}})
  416. addPrint(ifaceNode)
  417. ifaceNode.AddChild(&Node{Name: "enable", Dynamic: dynInterfaces})
  418. ifaceNode.AddChild(&Node{Name: "disable", Dynamic: dynInterfaces})
  419. ifaceSet := ifaceNode.AddChild(&Node{Name: "set", Dynamic: dynInterfaces})
  420. ifaceSet.AddChild(&Node{Name: "mtu="})
  421. // /interface vlan
  422. vlanNode := ifaceNode.AddChild(&Node{Name: "vlan", Aliases: []string{"vlans"}})
  423. addPrint(vlanNode)
  424. addRemove(vlanNode)
  425. vlanAdd := vlanNode.AddChild(&Node{Name: "add"})
  426. vlanAdd.AddChild(&Node{Name: "name="})
  427. vlanAdd.AddChild(&Node{Name: "vlan-id="})
  428. vlanAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  429. // /interface bridge
  430. bridgeNode := ifaceNode.AddChild(&Node{Name: "bridge", Aliases: []string{"bridges"}})
  431. addPrint(bridgeNode)
  432. addRemove(bridgeNode)
  433. addEnableDisable(bridgeNode)
  434. bridgeAdd := bridgeNode.AddChild(&Node{Name: "add"})
  435. bridgeAdd.AddChild(&Node{Name: "name="})
  436. // /interface bridge port
  437. bridgePortNode := bridgeNode.AddChild(&Node{Name: "port", Aliases: []string{"ports"}})
  438. addPrint(bridgePortNode)
  439. addRemove(bridgePortNode)
  440. addEnableDisable(bridgePortNode)
  441. bridgePortAdd := bridgePortNode.AddChild(&Node{Name: "add"})
  442. bridgePortAdd.AddChild(&Node{Name: "bridge="})
  443. bridgePortAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  444. bridgePortAdd.AddChild(&Node{Name: "pvid="})
  445. // /interface wireguard
  446. wgNode := ifaceNode.AddChild(&Node{Name: "wireguard", Aliases: []string{"wg"}})
  447. addPrint(wgNode)
  448. addRemove(wgNode)
  449. addEnableDisable(wgNode)
  450. wgAdd := wgNode.AddChild(&Node{Name: "add"})
  451. wgAdd.AddChild(&Node{Name: "name="})
  452. wgAdd.AddChild(&Node{Name: "listen-port="})
  453. wgAdd.AddChild(&Node{Name: "private-key="})
  454. // /interface wireguard peers
  455. wgPeers := wgNode.AddChild(&Node{Name: "peers", Aliases: []string{"peer"}})
  456. addPrint(wgPeers)
  457. addRemove(wgPeers)
  458. wgPeersAdd := wgPeers.AddChild(&Node{Name: "add"})
  459. wgPeersAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  460. wgPeersAdd.AddChild(&Node{Name: "public-key="})
  461. wgPeersAdd.AddChild(&Node{Name: "allowed-address="})
  462. wgPeersAdd.AddChild(&Node{Name: "endpoint-address="})
  463. wgPeersAdd.AddChild(&Node{Name: "endpoint-port="})
  464. // /interface aerotun
  465. aero := ifaceNode.AddChild(&Node{Name: "aerotun"})
  466. names := func() []string {
  467. if c.executor.vpnMgr == nil || c.executor.vpnMgr.AeroTUN == nil {
  468. return nil
  469. }
  470. var out []string
  471. for _, inst := range c.executor.vpnMgr.AeroTUN.List() {
  472. out = append(out, inst.Name)
  473. }
  474. return out
  475. }
  476. aero.AddChild(&Node{Name: "print"}).AddChild(&Node{Name: "detail="})
  477. aero.AddChild(&Node{Name: "genkey"})
  478. for _, action := range []string{"add", "set", "remove", "enable", "disable", "start", "stop"} {
  479. n := aero.AddChild(&Node{Name: action, Dynamic: names})
  480. if action == "add" || action == "set" {
  481. 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="} {
  482. n.AddChild(&Node{Name: param})
  483. }
  484. }
  485. }
  486. // /interface eoip
  487. eoipNode := ifaceNode.AddChild(&Node{Name: "eoip"})
  488. addPrint(eoipNode)
  489. addRemove(eoipNode)
  490. addEnableDisable(eoipNode)
  491. eoipAdd := eoipNode.AddChild(&Node{Name: "add"})
  492. eoipAdd.AddChild(&Node{Name: "name="})
  493. eoipAdd.AddChild(&Node{Name: "remote-ip="})
  494. eoipAdd.AddChild(&Node{Name: "tunnel-id="})
  495. // /interface vxlan
  496. vxlanNode := ifaceNode.AddChild(&Node{Name: "vxlan"})
  497. addPrint(vxlanNode)
  498. addRemove(vxlanNode)
  499. addEnableDisable(vxlanNode)
  500. vxlanAdd := vxlanNode.AddChild(&Node{Name: "add"})
  501. for _, p := range []string{"name=", "vni=", "port=", "mtu=", "mac-address=", "local-address=", "remote-address=", "group=", "interface=", "learning=", "comment="} {
  502. vxlanAdd.AddChild(&Node{Name: p})
  503. }
  504. vxlanSet := vxlanNode.AddChild(&Node{Name: "set"})
  505. for _, p := range []string{"name=", "vni=", "port=", "mtu=", "mac-address=", "local-address=", "remote-address=", "group=", "interface=", "comment="} {
  506. vxlanSet.AddChild(&Node{Name: p})
  507. }
  508. vtepsNode := vxlanNode.AddChild(&Node{Name: "vteps"})
  509. addPrint(vtepsNode)
  510. addRemove(vtepsNode)
  511. vtepsAdd := vtepsNode.AddChild(&Node{Name: "add"})
  512. vtepsAdd.AddChild(&Node{Name: "interface="})
  513. vtepsAdd.AddChild(&Node{Name: "remote-ip="})
  514. vtepsAdd.AddChild(&Node{Name: "port="})
  515. vtepsAdd.AddChild(&Node{Name: "comment="})
  516. // /interface vrrp
  517. vrrpNode := ifaceNode.AddChild(&Node{Name: "vrrp"})
  518. addPrint(vrrpNode)
  519. addRemove(vrrpNode)
  520. addEnableDisable(vrrpNode)
  521. vrrpAdd := vrrpNode.AddChild(&Node{Name: "add"})
  522. vrrpAdd.AddChild(&Node{Name: "name="})
  523. vrrpAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  524. vrrpAdd.AddChild(&Node{Name: "vrid="})
  525. vrrpAdd.AddChild(&Node{Name: "priority="})
  526. // /interface pppoe-client
  527. pppoeNode := ifaceNode.AddChild(&Node{Name: "pppoe-client"})
  528. addPrint(pppoeNode)
  529. addRemove(pppoeNode)
  530. pppoeNode.AddChild(&Node{Name: "connect"})
  531. pppoeNode.AddChild(&Node{Name: "disconnect"})
  532. pppoeAdd := pppoeNode.AddChild(&Node{Name: "add"})
  533. pppoeAdd.AddChild(&Node{Name: "name="})
  534. pppoeAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  535. pppoeAdd.AddChild(&Node{Name: "user="})
  536. pppoeAdd.AddChild(&Node{Name: "password="})
  537. // 2. /ip
  538. ipNode := root.AddChild(&Node{Name: "ip"})
  539. // /ip address
  540. ipAddrNode := ipNode.AddChild(&Node{Name: "address", Aliases: []string{"addr"}})
  541. addPrint(ipAddrNode)
  542. addRemove(ipAddrNode)
  543. ipAddrAdd := ipAddrNode.AddChild(&Node{Name: "add"})
  544. ipAddrAdd.AddChild(&Node{Name: "address="})
  545. ipAddrAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  546. ipAddrAdd.AddChild(&Node{Name: "network="})
  547. // /ip route
  548. ipRouteNode := ipNode.AddChild(&Node{Name: "route", Aliases: []string{"routes"}})
  549. addPrint(ipRouteNode)
  550. addRemove(ipRouteNode)
  551. addEnableDisable(ipRouteNode)
  552. ipRouteAdd := ipRouteNode.AddChild(&Node{Name: "add"})
  553. ipRouteAdd.AddChild(&Node{Name: "dst-address="})
  554. ipRouteAdd.AddChild(&Node{Name: "gateway="})
  555. ipRouteAdd.AddChild(&Node{Name: "distance="})
  556. // /ip route rule
  557. ipRouteRule := ipRouteNode.AddChild(&Node{Name: "rule", Aliases: []string{"rules"}})
  558. addPrint(ipRouteRule)
  559. addRemove(ipRouteRule)
  560. ipRouteRuleAdd := ipRouteRule.AddChild(&Node{Name: "add"})
  561. ipRouteRuleAdd.AddChild(&Node{Name: "src-address="})
  562. ipRouteRuleAdd.AddChild(&Node{Name: "dst-address="})
  563. ipRouteRuleAdd.AddChild(&Node{Name: "table="})
  564. ipRouteRuleAdd.AddChild(&Node{Name: "action=", Dynamic: func() []string { return []string{"lookup", "unreachable", "drop"} }})
  565. // /ip route table
  566. ipRouteTable := ipRouteNode.AddChild(&Node{Name: "table", Aliases: []string{"tables"}})
  567. addPrint(ipRouteTable)
  568. addRemove(ipRouteTable)
  569. ipRouteTableAdd := ipRouteTable.AddChild(&Node{Name: "add"})
  570. ipRouteTableAdd.AddChild(&Node{Name: "name="})
  571. ipRouteTableAdd.AddChild(&Node{Name: "fib="})
  572. // /ip dhcp-client
  573. dhcpCliNode := ipNode.AddChild(&Node{Name: "dhcp-client"})
  574. addPrint(dhcpCliNode)
  575. addRemove(dhcpCliNode)
  576. dhcpCliNode.AddChild(&Node{Name: "renew"})
  577. dhcpCliNode.AddChild(&Node{Name: "release"})
  578. dhcpCliAdd := dhcpCliNode.AddChild(&Node{Name: "add"})
  579. dhcpCliAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  580. dhcpCliAdd.AddChild(&Node{Name: "disabled=", Dynamic: func() []string { return []string{"yes", "no"} }})
  581. // /ip dhcp-server
  582. dhcpSrvNode := ipNode.AddChild(&Node{Name: "dhcp-server"})
  583. addPrint(dhcpSrvNode)
  584. addRemove(dhcpSrvNode)
  585. addEnableDisable(dhcpSrvNode)
  586. dhcpSrvAdd := dhcpSrvNode.AddChild(&Node{Name: "add"})
  587. dhcpSrvAdd.AddChild(&Node{Name: "name="})
  588. dhcpSrvAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  589. dhcpSrvAdd.AddChild(&Node{Name: "address-pool="})
  590. dhcpSrvAdd.AddChild(&Node{Name: "lease-time="})
  591. // /ip dhcp-server lease
  592. dhcpLeaseNode := dhcpSrvNode.AddChild(&Node{Name: "lease", Aliases: []string{"leases"}})
  593. addPrint(dhcpLeaseNode)
  594. addRemove(dhcpLeaseNode)
  595. // /ip dhcp-server network
  596. dhcpNetNode := dhcpSrvNode.AddChild(&Node{Name: "network", Aliases: []string{"networks"}})
  597. addPrint(dhcpNetNode)
  598. addRemove(dhcpNetNode)
  599. dhcpNetAdd := dhcpNetNode.AddChild(&Node{Name: "add"})
  600. dhcpNetAdd.AddChild(&Node{Name: "address="})
  601. dhcpNetAdd.AddChild(&Node{Name: "gateway="})
  602. dhcpNetAdd.AddChild(&Node{Name: "dns-server="})
  603. // /ip dns
  604. dnsNode := ipNode.AddChild(&Node{Name: "dns"})
  605. addPrint(dnsNode)
  606. dnsSet := dnsNode.AddChild(&Node{Name: "set"})
  607. dnsSet.AddChild(&Node{Name: "servers="})
  608. dnsSet.AddChild(&Node{Name: "allow-remote-requests=", Dynamic: func() []string { return []string{"yes", "no"} }})
  609. dnsCache := dnsNode.AddChild(&Node{Name: "cache"})
  610. addPrint(dnsCache)
  611. dnsCache.AddChild(&Node{Name: "flush"})
  612. dnsStatic := dnsNode.AddChild(&Node{Name: "static"})
  613. addPrint(dnsStatic)
  614. addRemove(dnsStatic)
  615. dnsStaticAdd := dnsStatic.AddChild(&Node{Name: "add"})
  616. dnsStaticAdd.AddChild(&Node{Name: "name="})
  617. dnsStaticAdd.AddChild(&Node{Name: "address="})
  618. // /ip firewall
  619. fwNode := ipNode.AddChild(&Node{Name: "firewall"})
  620. // /ip firewall filter
  621. fwFilter := fwNode.AddChild(&Node{Name: "filter"})
  622. addPrint(fwFilter)
  623. addRemove(fwFilter)
  624. addEnableDisable(fwFilter)
  625. fwFilterAdd := fwFilter.AddChild(&Node{Name: "add"})
  626. fwFilterAdd.AddChild(&Node{Name: "chain=", Dynamic: func() []string { return []string{"forward", "input", "output"} }})
  627. fwFilterAdd.AddChild(&Node{Name: "action=", Dynamic: func() []string { return []string{"accept", "drop", "reject"} }})
  628. fwFilterAdd.AddChild(&Node{Name: "protocol=", Dynamic: func() []string { return []string{"icmp", "tcp", "udp"} }})
  629. fwFilterAdd.AddChild(&Node{Name: "src-address="})
  630. fwFilterAdd.AddChild(&Node{Name: "dst-address="})
  631. fwFilterAdd.AddChild(&Node{Name: "dst-port="})
  632. fwFilterAdd.AddChild(&Node{Name: "in-interface=", Dynamic: dynInterfaces})
  633. fwFilterAdd.AddChild(&Node{Name: "out-interface=", Dynamic: dynInterfaces})
  634. fwFilterAdd.AddChild(&Node{Name: "comment="})
  635. // /ip firewall nat
  636. fwNat := fwNode.AddChild(&Node{Name: "nat"})
  637. addPrint(fwNat)
  638. addRemove(fwNat)
  639. addEnableDisable(fwNat)
  640. fwNatAdd := fwNat.AddChild(&Node{Name: "add"})
  641. fwNatAdd.AddChild(&Node{Name: "chain=", Dynamic: func() []string { return []string{"dstnat", "srcnat"} }})
  642. fwNatAdd.AddChild(&Node{Name: "action=", Dynamic: func() []string { return []string{"accept", "dst-nat", "masquerade", "redirect", "src-nat"} }})
  643. fwNatAdd.AddChild(&Node{Name: "protocol=", Dynamic: func() []string { return []string{"icmp", "tcp", "udp"} }})
  644. fwNatAdd.AddChild(&Node{Name: "src-address="})
  645. fwNatAdd.AddChild(&Node{Name: "dst-address="})
  646. fwNatAdd.AddChild(&Node{Name: "to-addresses="})
  647. fwNatAdd.AddChild(&Node{Name: "to-ports="})
  648. fwNatAdd.AddChild(&Node{Name: "out-interface=", Dynamic: dynInterfaces})
  649. fwNatAdd.AddChild(&Node{Name: "comment="})
  650. // /ip firewall mangle
  651. fwMangle := fwNode.AddChild(&Node{Name: "mangle"})
  652. addPrint(fwMangle)
  653. addRemove(fwMangle)
  654. addEnableDisable(fwMangle)
  655. fwMangleAdd := fwMangle.AddChild(&Node{Name: "add"})
  656. fwMangleAdd.AddChild(&Node{Name: "chain=", Dynamic: func() []string { return []string{"forward", "input", "output", "postrouting", "prerouting"} }})
  657. fwMangleAdd.AddChild(&Node{Name: "action=", Dynamic: func() []string { return []string{"accept", "mark-packet", "mark-routing"} }})
  658. fwMangleAdd.AddChild(&Node{Name: "protocol=", Dynamic: func() []string { return []string{"icmp", "tcp", "udp"} }})
  659. fwMangleAdd.AddChild(&Node{Name: "src-address="})
  660. fwMangleAdd.AddChild(&Node{Name: "dst-address="})
  661. // /ip firewall address-list
  662. fwAddrList := fwNode.AddChild(&Node{Name: "address-list"})
  663. addPrint(fwAddrList)
  664. addRemove(fwAddrList)
  665. addEnableDisable(fwAddrList)
  666. fwAddrListAdd := fwAddrList.AddChild(&Node{Name: "add"})
  667. fwAddrListAdd.AddChild(&Node{Name: "list="})
  668. fwAddrListAdd.AddChild(&Node{Name: "address="})
  669. fwAddrListAdd.AddChild(&Node{Name: "comment="})
  670. // /ip service
  671. serviceNode := ipNode.AddChild(&Node{Name: "service", Aliases: []string{"services"}})
  672. addPrint(serviceNode)
  673. serviceNode.AddChild(&Node{Name: "enable", Dynamic: dynServices})
  674. serviceNode.AddChild(&Node{Name: "disable", Dynamic: dynServices})
  675. serviceSet := serviceNode.AddChild(&Node{Name: "set", Dynamic: dynServices})
  676. serviceSet.AddChild(&Node{Name: "port="})
  677. serviceSet.AddChild(&Node{Name: "disabled=", Dynamic: func() []string { return []string{"yes", "no"} }})
  678. serviceSet.AddChild(&Node{Name: "certificate=", Dynamic: dynCertificates})
  679. // /ip sdwan
  680. sdwanNode := ipNode.AddChild(&Node{Name: "sdwan", Aliases: []string{"sd-wan"}})
  681. addPrint(sdwanNode)
  682. sdwanNode.AddChild(&Node{Name: "status"})
  683. addRemove(sdwanNode)
  684. addEnableDisable(sdwanNode)
  685. sdwanAdd := sdwanNode.AddChild(&Node{Name: "add"})
  686. sdwanAdd.AddChild(&Node{Name: "name="})
  687. sdwanAdd.AddChild(&Node{Name: "mode=", Dynamic: func() []string {
  688. return []string{"weighted", "pcc", "lowest-latency", "lowest-loss", "failover"}
  689. }})
  690. sdwanAdd.AddChild(&Node{Name: "auto-nat=", Dynamic: func() []string { return []string{"yes", "no"} }})
  691. sdwanAdd.AddChild(&Node{Name: "probe-interval="})
  692. sdwanAdd.AddChild(&Node{Name: "comment="})
  693. sdwanSet := sdwanNode.AddChild(&Node{Name: "set", Dynamic: dynSDWANGroups})
  694. sdwanSet.AddChild(&Node{Name: "mode=", Dynamic: func() []string {
  695. return []string{"weighted", "pcc", "lowest-latency", "lowest-loss", "failover"}
  696. }})
  697. sdwanSet.AddChild(&Node{Name: "auto-nat=", Dynamic: func() []string { return []string{"yes", "no"} }})
  698. sdwanSet.AddChild(&Node{Name: "probe-interval="})
  699. sdwanSet.AddChild(&Node{Name: "comment="})
  700. // /ip sdwan member
  701. sdwanMemNode := sdwanNode.AddChild(&Node{Name: "member", Aliases: []string{"members"}})
  702. addPrint(sdwanMemNode)
  703. addRemove(sdwanMemNode)
  704. addEnableDisable(sdwanMemNode)
  705. sdwanMemAdd := sdwanMemNode.AddChild(&Node{Name: "add"})
  706. sdwanMemAdd.AddChild(&Node{Name: "group=", Dynamic: dynSDWANGroups})
  707. sdwanMemAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  708. sdwanMemAdd.AddChild(&Node{Name: "gateway="})
  709. sdwanMemAdd.AddChild(&Node{Name: "weight="})
  710. sdwanMemAdd.AddChild(&Node{Name: "priority="})
  711. sdwanMemAdd.AddChild(&Node{Name: "target-probe="})
  712. sdwanMemAdd.AddChild(&Node{Name: "max-latency="})
  713. sdwanMemAdd.AddChild(&Node{Name: "max-loss="})
  714. sdwanMemAdd.AddChild(&Node{Name: "comment="})
  715. sdwanMemSet := sdwanMemNode.AddChild(&Node{Name: "set"})
  716. sdwanMemSet.AddChild(&Node{Name: "group=", Dynamic: dynSDWANGroups})
  717. sdwanMemSet.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  718. sdwanMemSet.AddChild(&Node{Name: "gateway="})
  719. sdwanMemSet.AddChild(&Node{Name: "weight="})
  720. sdwanMemSet.AddChild(&Node{Name: "priority="})
  721. sdwanMemSet.AddChild(&Node{Name: "target-probe="})
  722. sdwanMemSet.AddChild(&Node{Name: "max-latency="})
  723. sdwanMemSet.AddChild(&Node{Name: "max-loss="})
  724. sdwanMemSet.AddChild(&Node{Name: "comment="})
  725. // Also accessible directly at root /sdwan
  726. root.AddChild(sdwanNode)
  727. // 3. /routing
  728. routingNode := root.AddChild(&Node{Name: "routing"})
  729. // /routing bgp
  730. bgpNode := routingNode.AddChild(&Node{Name: "bgp"})
  731. bgpInst := bgpNode.AddChild(&Node{Name: "instance"})
  732. addPrint(bgpInst)
  733. bgpInstSet := bgpInst.AddChild(&Node{Name: "set"})
  734. bgpInstSet.AddChild(&Node{Name: "as="})
  735. bgpInstSet.AddChild(&Node{Name: "router-id="})
  736. bgpInstSet.AddChild(&Node{Name: "enabled=", Dynamic: func() []string { return []string{"yes", "no"} }})
  737. bgpPeer := bgpNode.AddChild(&Node{Name: "peer", Aliases: []string{"peers", "connection"}})
  738. addPrint(bgpPeer)
  739. addRemove(bgpPeer)
  740. addEnableDisable(bgpPeer)
  741. bgpPeerAdd := bgpPeer.AddChild(&Node{Name: "add"})
  742. bgpPeerAdd.AddChild(&Node{Name: "name="})
  743. bgpPeerAdd.AddChild(&Node{Name: "remote-address="})
  744. bgpPeerAdd.AddChild(&Node{Name: "remote-as="})
  745. bgpPeerAdd.AddChild(&Node{Name: "description="})
  746. bgpNet := bgpNode.AddChild(&Node{Name: "network"})
  747. addPrint(bgpNet)
  748. addRemove(bgpNet)
  749. bgpNetAdd := bgpNet.AddChild(&Node{Name: "add"})
  750. bgpNetAdd.AddChild(&Node{Name: "network="})
  751. bgpRoutes := bgpNode.AddChild(&Node{Name: "routes"})
  752. addPrint(bgpRoutes)
  753. // /routing ospf
  754. ospfNode := routingNode.AddChild(&Node{Name: "ospf"})
  755. ospfInst := ospfNode.AddChild(&Node{Name: "instance", Aliases: []string{"instances"}})
  756. addPrint(ospfInst)
  757. addRemove(ospfInst)
  758. ospfInstAdd := ospfInst.AddChild(&Node{Name: "add"})
  759. ospfInstAdd.AddChild(&Node{Name: "name="})
  760. ospfInstAdd.AddChild(&Node{Name: "router-id="})
  761. ospfInstAdd.AddChild(&Node{Name: "redistribute-connected=", Dynamic: func() []string { return []string{"yes", "no"} }})
  762. ospfInstAdd.AddChild(&Node{Name: "redistribute-static=", Dynamic: func() []string { return []string{"yes", "no"} }})
  763. ospfInstAdd.AddChild(&Node{Name: "redistribute-bgp=", Dynamic: func() []string { return []string{"yes", "no"} }})
  764. ospfInstAdd.AddChild(&Node{Name: "default-metric="})
  765. ospfInstSet := ospfInst.AddChild(&Node{Name: "set"})
  766. ospfInstSet.AddChild(&Node{Name: "router-id="})
  767. ospfInstSet.AddChild(&Node{Name: "redistribute-connected=", Dynamic: func() []string { return []string{"yes", "no"} }})
  768. ospfInstSet.AddChild(&Node{Name: "redistribute-static=", Dynamic: func() []string { return []string{"yes", "no"} }})
  769. ospfInstSet.AddChild(&Node{Name: "redistribute-bgp=", Dynamic: func() []string { return []string{"yes", "no"} }})
  770. ospfInstSet.AddChild(&Node{Name: "default-metric="})
  771. ospfInstSet.AddChild(&Node{Name: "disabled=", Dynamic: func() []string { return []string{"yes", "no"} }})
  772. ospfArea := ospfNode.AddChild(&Node{Name: "area", Aliases: []string{"areas"}})
  773. addPrint(ospfArea)
  774. addRemove(ospfArea)
  775. ospfAreaAdd := ospfArea.AddChild(&Node{Name: "add"})
  776. ospfAreaAdd.AddChild(&Node{Name: "name="})
  777. ospfAreaAdd.AddChild(&Node{Name: "area-id="})
  778. ospfAreaAdd.AddChild(&Node{Name: "instance="})
  779. ospfAreaAdd.AddChild(&Node{Name: "type=", Dynamic: func() []string { return []string{"default", "stub", "nssa"} }})
  780. ospfAreaAdd.AddChild(&Node{Name: "default-cost="})
  781. ospfAreaAdd.AddChild(&Node{Name: "no-summaries=", Dynamic: func() []string { return []string{"yes", "no"} }})
  782. ospfAreaRange := ospfNode.AddChild(&Node{Name: "area-range", Aliases: []string{"range", "ranges"}})
  783. addPrint(ospfAreaRange)
  784. addRemove(ospfAreaRange)
  785. ospfRangeAdd := ospfAreaRange.AddChild(&Node{Name: "add"})
  786. ospfRangeAdd.AddChild(&Node{Name: "prefix="})
  787. ospfRangeAdd.AddChild(&Node{Name: "area="})
  788. ospfRangeAdd.AddChild(&Node{Name: "cost="})
  789. ospfRangeAdd.AddChild(&Node{Name: "advertise=", Dynamic: func() []string { return []string{"yes", "no"} }})
  790. ospfIface := ospfNode.AddChild(&Node{Name: "interface", Aliases: []string{"interfaces", "template", "templates"}})
  791. addPrint(ospfIface)
  792. addRemove(ospfIface)
  793. ospfIfaceAdd := ospfIface.AddChild(&Node{Name: "add"})
  794. ospfIfaceAdd.AddChild(&Node{Name: "interface=", Dynamic: dynInterfaces})
  795. ospfIfaceAdd.AddChild(&Node{Name: "area="})
  796. ospfIfaceAdd.AddChild(&Node{Name: "networks="})
  797. ospfIfaceAdd.AddChild(&Node{Name: "cost="})
  798. ospfIfaceAdd.AddChild(&Node{Name: "priority="})
  799. ospfIfaceAdd.AddChild(&Node{Name: "network-type=", Dynamic: func() []string { return []string{"broadcast", "point-to-point", "nonbroadcast", "point-to-multipoint"} }})
  800. ospfIfaceAdd.AddChild(&Node{Name: "passive=", Dynamic: func() []string { return []string{"yes", "no"} }})
  801. ospfIfaceAdd.AddChild(&Node{Name: "auth=", Dynamic: func() []string { return []string{"none", "simple", "md5"} }})
  802. ospfSNbr := ospfNode.AddChild(&Node{Name: "static-neighbor", Aliases: []string{"static-neighbors"}})
  803. addPrint(ospfSNbr)
  804. addRemove(ospfSNbr)
  805. ospfSNbrAdd := ospfSNbr.AddChild(&Node{Name: "add"})
  806. ospfSNbrAdd.AddChild(&Node{Name: "address="})
  807. ospfSNbrAdd.AddChild(&Node{Name: "area="})
  808. ospfSNbrAdd.AddChild(&Node{Name: "poll-interval="})
  809. ospfSNbrAdd.AddChild(&Node{Name: "cost="})
  810. ospfNeighbor := ospfNode.AddChild(&Node{Name: "neighbor", Aliases: []string{"neighbors"}})
  811. addPrint(ospfNeighbor)
  812. ospfLSA := ospfNode.AddChild(&Node{Name: "lsa", Aliases: []string{"lsadb"}})
  813. addPrint(ospfLSA)
  814. ospfRoute := ospfNode.AddChild(&Node{Name: "route", Aliases: []string{"routes"}})
  815. addPrint(ospfRoute)
  816. // /routing filter
  817. routingFilter := routingNode.AddChild(&Node{Name: "filter"})
  818. addPrint(routingFilter)
  819. rfRule := routingFilter.AddChild(&Node{Name: "rule", Aliases: []string{"rules"}})
  820. addPrint(rfRule)
  821. addRemove(rfRule)
  822. rfRuleAdd := rfRule.AddChild(&Node{Name: "add"})
  823. rfRuleAdd.AddChild(&Node{Name: "chain="})
  824. rfRuleAdd.AddChild(&Node{Name: "action=", Dynamic: func() []string { return []string{"accept", "discard", "reject"} }})
  825. rfRuleAdd.AddChild(&Node{Name: "prefix="})
  826. rfRuleAdd.AddChild(&Node{Name: "prefix-len="})
  827. // 4. /tool
  828. toolNode := root.AddChild(&Node{Name: "tool"})
  829. toolPing := toolNode.AddChild(&Node{Name: "ping"})
  830. toolPing.AddChild(&Node{Name: "count="})
  831. toolPing.AddChild(&Node{Name: "size="})
  832. toolPing.AddChild(&Node{Name: "interval="})
  833. toolTrace := toolNode.AddChild(&Node{Name: "traceroute"})
  834. toolTrace.AddChild(&Node{Name: "max-hops="})
  835. toolBtest := toolNode.AddChild(&Node{Name: "bandwidth-test", Aliases: []string{"btest"}})
  836. toolBtest.AddChild(&Node{Name: "target="})
  837. toolBtest.AddChild(&Node{Name: "duration="})
  838. toolBtest.AddChild(&Node{Name: "direction=", Dynamic: func() []string { return []string{"receive", "send", "both"} }})
  839. // 5. /log
  840. logNode := root.AddChild(&Node{Name: "log"})
  841. logPrint := logNode.AddChild(&Node{Name: "print", Aliases: []string{"pr", "show"}})
  842. logPrint.AddChild(&Node{Name: "limit="})
  843. logPrint.AddChild(&Node{Name: "filter="})
  844. // 6. /user
  845. userNode := root.AddChild(&Node{Name: "user", Aliases: []string{"users"}})
  846. addPrint(userNode)
  847. addRemove(userNode)
  848. userAdd := userNode.AddChild(&Node{Name: "add"})
  849. userAdd.AddChild(&Node{Name: "name="})
  850. userAdd.AddChild(&Node{Name: "password="})
  851. userAdd.AddChild(&Node{Name: "role=", Dynamic: func() []string { return []string{"admin", "full", "read", "viewer"} }})
  852. userAdd.AddChild(&Node{Name: "comment="})
  853. userSet := userNode.AddChild(&Node{Name: "set"})
  854. userSet.AddChild(&Node{Name: "name="})
  855. userSet.AddChild(&Node{Name: "password="})
  856. userSet.AddChild(&Node{Name: "role=", Dynamic: func() []string { return []string{"admin", "full", "read", "viewer"} }})
  857. userSet.AddChild(&Node{Name: "comment="})
  858. userSet.AddChild(&Node{Name: "disabled=", Dynamic: func() []string { return []string{"yes", "no"} }})
  859. // 7. /system
  860. sysNode := root.AddChild(&Node{Name: "system"})
  861. sysRes := sysNode.AddChild(&Node{Name: "resource"})
  862. addPrint(sysRes)
  863. sysIdent := sysNode.AddChild(&Node{Name: "identity"})
  864. addPrint(sysIdent)
  865. sysIdentSet := sysIdent.AddChild(&Node{Name: "set"})
  866. sysIdentSet.AddChild(&Node{Name: "name="})
  867. sysNode.AddChild(&Node{Name: "reboot"})
  868. // 8. /certificate
  869. certNode := root.AddChild(&Node{Name: "certificate", Aliases: []string{"certificates", "cert"}})
  870. addPrint(certNode)
  871. certNode.AddChild(&Node{Name: "remove", Aliases: []string{"rem", "delete"}, Dynamic: dynCertificates})
  872. certNode.AddChild(&Node{Name: "export", Dynamic: dynCertificates})
  873. certAddCA := certNode.AddChild(&Node{Name: "add-ca"})
  874. certAddCA.AddChild(&Node{Name: "name="})
  875. certAddCA.AddChild(&Node{Name: "common-name="})
  876. certAddCA.AddChild(&Node{Name: "days="})
  877. certAddCA.AddChild(&Node{Name: "key-type=", Dynamic: func() []string { return []string{"RSA-2048", "RSA-4096", "ECDSA-P256"} }})
  878. certAdd := certNode.AddChild(&Node{Name: "add"})
  879. certAdd.AddChild(&Node{Name: "name="})
  880. certAdd.AddChild(&Node{Name: "common-name="})
  881. certAdd.AddChild(&Node{Name: "ca=", Dynamic: dynCAs})
  882. certAdd.AddChild(&Node{Name: "sans="})
  883. certAdd.AddChild(&Node{Name: "days="})
  884. certAdd.AddChild(&Node{Name: "key-type=", Dynamic: func() []string { return []string{"RSA-2048", "RSA-4096", "ECDSA-P256"} }})
  885. certLE := certNode.AddChild(&Node{Name: "issue-letsencrypt"})
  886. certLE.AddChild(&Node{Name: "domain="})
  887. certLE.AddChild(&Node{Name: "email="})
  888. certLE.AddChild(&Node{Name: "name="})
  889. certLE.AddChild(&Node{Name: "staging=", Dynamic: func() []string { return []string{"yes", "no"} }})
  890. // 9. Direct root utility commands
  891. root.AddChild(&Node{Name: "clear"})
  892. root.AddChild(&Node{Name: "help", Aliases: []string{"?"}})
  893. root.AddChild(&Node{Name: "quit"})
  894. root.AddChild(&Node{Name: "exit"})
  895. root.AddChild(&Node{Name: "ping"})
  896. root.AddChild(&Node{Name: "traceroute"})
  897. return root
  898. }