Switch CLI parsing to TextFSM and polish topology/workbench UX.

Use custom cli_templates plus community ntc-templates for LLDP and port traffic across ZTE/Huawei/Cisco/H3C/Juniper/Nokia, drop regex parsers, and surface Admin/Phy/Prot on port discover. Also batch-add topology NEs and tighten workbench branding.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-08-02 23:06:40 +08:00
parent 60186a12e3
commit 393fd0e0b4
41 changed files with 1771 additions and 1075 deletions

View file

@ -0,0 +1,57 @@
# NetX CLI TextFSM templates
Custom TextFSM templates that complement (and can **override**) community `ntc-templates`.
Organize by **vendor folder**; prefer adding templates here for every new CLI parse path.
## Layout
```text
cli_templates/
index # global index (paths relative to this dir)
README.md
zte/ # ZTE ZXROS / ROSNG, ...
cisco/ # overrides / extras beyond community cisco_*
huawei/
h3c/
juniper/
```
## Lookup order
1. **This tree** (`index` → `vendor/*.textfsm`)
2. Community `ntc-templates` package
No regex CLI parsers. If both miss, the call returns empty — fix or add a template.
## Vendor coverage (blind fill from community + NetX)
| NetX vendor_key | ntc platform | LLDP command | Port brief | Port detail | Notes |
|-----------------|--------------|--------------|------------|-------------|-------|
| `zte` | `zte_zxros` | `show lldp neighbor brief` | `show interface brief` | `show interface {if}` | NetX custom (community has iface only) |
| `huawei` | `huawei_vrp` | `display lldp neighbor` | `display interface brief` | `display interface {if}` | NetX custom (community prompt-fragile) |
| `cisco` | `cisco_ios` / `nxos` / `xr` | `show lldp neighbors detail` | `show ip interface brief` | `show interfaces {if}` | Community + NetX iface override |
| `h3c` | `hp_comware` | `display lldp neighbor-information list` | `display interface brief` | `display interface {if}` | Community |
| `juniper` | `juniper_junos` | `show lldp neighbors` | `show interfaces` | `show interfaces {if}` | Community (rates often sparse) |
| `nokia` | `alcatel_sros` | *(stub / no SROS LLDP tpl)* | `show port` | `show port {if}` | Community port status; AOS LLDP via `alcatel_aos` |
| `mikrotik` | `mikrotik_routeros` | — | `/interface print brief` | `/interface print detail …` | Community; no LLDP path |
| `ericsson` | `ericsson_ipos` | stub | — | — | No community LLDP/iface templates |
`alcatel_aos` device_type uses community `show lldp remote-system` (mapped to platform `alcatel_aos`).
## Adding a template
1. Put the file under the vendor folder, e.g. `zte/zte_zxros_show_xxx.textfsm`
2. Name: `{platform}_{command_slug}.textfsm` (same convention as ntc-templates)
3. Register in root `index` with a **relative path**:
`zte/zte_zxros_show_xxx.textfsm, .*, zte_zxros, sh[[ow]] ...`
4. Align Value names with community templates when possible
5. Add unit tests under `tests/`
When community already works, no file is required.
When community is wrong/incomplete for our lab, add an override under the vendor folder
with the same Platform+Command so NetX wins.
## Concurrency
Template files are read-only and safe across processes. Parsing goes through
`netx_api.ntc_parse` (fresh `CliTable` + process lock; never share instances across threads).

View file

View file

@ -0,0 +1,19 @@
Value Required INTERFACE (\S+)
Value LINK_STATUS (.+?)
Value PROTOCOL_STATUS (.+?)
Value DESCRIPTION (.+?)
Value BANDWIDTH (\d+\s+\w+)
Value RATE_PERIOD (\d+)
Value RATE_PERIOD_UNIT (second|minute|hour)
Value INPUT_RATE (\d+)
Value OUTPUT_RATE (\d+)
Start
^${INTERFACE}\s+is\s+${LINK_STATUS},\s*line\s+protocol\s+is\s+${PROTOCOL_STATUS}\s*$$
^\s+Description:\s+${DESCRIPTION}\s*$$
^\s+MTU\s+\d+\s+bytes,\s+BW\s+${BANDWIDTH}(?:/s(?:ec)?)?,
^\s+${RATE_PERIOD}\s+${RATE_PERIOD_UNIT}s?\s+input\s+rate\s+${INPUT_RATE}\s+bits/sec
^\s+\d+\s+(?:second|minute|hour)s?\s+output\s+rate\s+${OUTPUT_RATE}\s+bits/sec -> Record
^\s*$$
^\S+#\s*$$ -> End
^.* -> Start

View file

View file

View file

@ -0,0 +1,20 @@
Value Filldown INTERFACE (\S+)
Value Filldown ADMIN_OPER (UP|DOWN|Administratively\s+DOWN)
Value Filldown DESCRIPTION (.*)
Value Filldown RATE_PERIOD (\d+)
Value Filldown INPUT_BPS ([\d.]+)
Value Filldown OUTPUT_BPS ([\d.]+)
Value Filldown IN_UTIL ([\d.]+)
Value OUT_UTIL ([\d.]+)
Start
^${INTERFACE}\s+current\s+state\s*:\s*${ADMIN_OPER}
^Description:\s*${DESCRIPTION}\s*$$
^\s*Last\s+${RATE_PERIOD}\s+seconds\s+input\s+rate:\s*${INPUT_BPS}\s*bits/sec
^\s*Last\s+\d+\s+seconds\s+output\s+rate:\s*${OUTPUT_BPS}\s*bits/sec
^\s*Last\s+\d+\s+seconds\s+input\s+utility\s+rate:\s*${IN_UTIL}\s*%
^\s*Last\s+\d+\s+seconds\s+output\s+utility\s+rate:\s*${OUT_UTIL}\s*% -> Record
^\s*$$
^\s*<.*>\s*$$
^.*#\s*$$
^.* -> Start

View file

@ -0,0 +1,14 @@
Value INTERFACE (\S+)
Value PHY (down|[\*\^]down|up|up\(\w+\))
Value PROTOCOL (down|[\*\^]down|up|up\(\w+\))
Value INUTI (\d*\.?\d*%|\-\-)
Value OUTUTI (\d*\.?\d*%|\-\-)
Value INERRORS (\d+)
Value OUTERRORS (\d+)
Start
^\s*${INTERFACE}\s+${PHY}\s+${PROTOCOL}\s+${INUTI}\s+${OUTUTI}\s+${INERRORS}\s+${OUTERRORS}\s*$$ -> Record
^\s*$$
^\s*<.*>\s*$$
^.*#\s*$$
^.* -> Start

View file

@ -0,0 +1,23 @@
Value Filldown LOCAL_INTERFACE (\S+)
Value CHASSIS_ID (\S+)
Value NEIGHBOR_PORT_ID (\S+)
Value NEIGHBOR_NAME (\S+)
Value MGMT_ADDRESS (\S+)
Start
^${LOCAL_INTERFACE}\s+has\s+\d+\s+neighbor -> Neighbor
^\s*$$
^.* -> Start
Neighbor
^Chassis\s+ID\s*:\s*${CHASSIS_ID}
^Port\s+ID\s*:\s*${NEIGHBOR_PORT_ID}
^System\s+name\s*:\s*${NEIGHBOR_NAME}
^Management\s+address\s*:\s*${MGMT_ADDRESS} -> Record
^Local\s+Interface\s*:\s*${LOCAL_INTERFACE}
^Neighbor\s+index\s*:
^${LOCAL_INTERFACE}\s+has\s+\d+\s+neighbor -> Neighbor
^\s*$$
^\s*<.*>\s*$$
^.*#\s*$$
^.* -> Neighbor

View file

@ -0,0 +1,25 @@
# NetX custom TextFSM index (checked before community ntc-templates).
# Templates live under vendor folders; paths are relative to this directory.
Template, Hostname, Platform, Command
# --- zte ---
zte/zte_zxros_show_lldp_neighbor_brief.textfsm, .*, zte_zxros, sh[[ow]] lld[[p]] nei[[ghbor]] br[[ief]]
zte/zte_zxros_show_interface_brief.textfsm, .*, zte_zxros, sh[[ow]] int[[erface]] br[[ief]]
zte/zte_zxros_show_interface.textfsm, .*, zte_zxros, sh[[ow]] int[[erface]] .+
# --- cisco ---
cisco/cisco_ios_show_interfaces.textfsm, .*, cisco_ios, sh[[ow]] int[[erfaces]] .+
# --- huawei ---
huawei/huawei_vrp_display_lldp_neighbor.textfsm, .*, huawei_vrp, dis[[play]] lldp nei[[ghbor]]
huawei/huawei_vrp_display_interface_brief.textfsm, .*, huawei_vrp, dis[[play]] inter[[face]] br[[ief]]
huawei/huawei_vrp_display_interface.textfsm, .*, huawei_vrp, dis[[play]] inter[[face]]\s*((?!brief|counters|description).)*$
# --- h3c (community hp_comware covers list/brief/detail; override here if lab differs) ---
# h3c/hp_comware_display_lldp_neighbor-information_list.textfsm, .*, hp_comware, ...
# --- juniper (community juniper_junos covers lldp + show interfaces) ---
# juniper/juniper_junos_....textfsm, .*, juniper_junos, ...
# --- nokia / alcatel (community alcatel_sros show port; aos lldp in community) ---
# (add overrides when SROS LLDP / rates are captured from lab)

View file

View file

@ -0,0 +1,30 @@
Value Filldown IFNAME (\S+)
Value Filldown ADMIN_OPER (up|down)
Value Filldown DESCRIPTION (.+?)
Value Filldown BW_RAW (\d+(?:\.\d+)?\s*[kKmMgGtT]?\s*(?:G?bit|bit)/s(?:ec)?)
Value Filldown RATE_PERIOD (\d+)
Value Filldown INPUT_BPS ([\d.]+)
Value Filldown OUTPUT_BPS ([\d.]+)
Value IN_UTIL ([\d.]+)
Value OUT_UTIL ([\d.]+)
Start
^${IFNAME}\s+is\s+${ADMIN_OPER}\b
^\s*Description:\s*${DESCRIPTION}\s*$$
^\s*BW\s+${BW_RAW}
^\s*Rate\s+period\s*:\s*${RATE_PERIOD}\s*s -> Rates
^\s*$$
^.* -> Start
Rates
^\s*Input\s*:\s*${INPUT_BPS}\s*bit/s
^\s*Output\s*:\s*${OUTPUT_BPS}\s*bit/s
^\s*Peak\s+rate: -> AfterPeak
^\s*Intf\s+utilization:\s*input\s*${IN_UTIL}%\s+output\s*${OUT_UTIL}% -> Record
^\s*$$
^.* -> Rates
AfterPeak
^\s*Intf\s+utilization:\s*input\s*${IN_UTIL}%\s+output\s*${OUT_UTIL}% -> Record
^\s*$$
^.* -> AfterPeak

View file

@ -0,0 +1,21 @@
Value INTERFACE (\S+)
Value ATTRIBUTE (\S+)
Value MODE (\S+)
Value BW (\S+)
Value ADMIN (up|down)
Value PHY (up|down)
Value PROT (up|down)
Value DESCRIPTION (.*)
Start
^Interface\s+Attribute\s+Mode -> Ports
^\s*$$
^.* -> Start
Ports
^${INTERFACE}\s+${ATTRIBUTE}\s+${MODE}\s+${BW}\s+${ADMIN}\s+${PHY}\s+${PROT}(?:\s+${DESCRIPTION})?\s*$$ -> Record
^${INTERFACE}\s+${ATTRIBUTE}\s+${MODE}\s+${ADMIN}\s+${PHY}\s+${PROT}(?:\s+${DESCRIPTION})?\s*$$ -> Record
^\s*$$
^\S+\s*#\s*$$ -> End
^.*#\s*$$ -> End
^.* -> Ports

View file

@ -0,0 +1,15 @@
Value LOCAL_INTERFACE (\S+)
Value CHASSIS_ID (\S+)
Value NEIGHBOR_PORT_ID (\S+)
Value NEIGHBOR_NAME (\S+.*)
Start
^Local\s+Interface\s+Chassis\s+ID -> Neighbors
^\s*$$
^.* -> Start
Neighbors
^${LOCAL_INTERFACE}\s+${CHASSIS_ID}\s+${NEIGHBOR_PORT_ID}\s+${NEIGHBOR_NAME}\s*$$ -> Record
^\s*$$
^Local\s+Interface -> Neighbors
^.* -> Start