diff --git a/board/netconf/rootfs/lib/infix/cli-pretty b/board/netconf/rootfs/lib/infix/cli-pretty new file mode 100755 index 000000000..fe9306f2b --- /dev/null +++ b/board/netconf/rootfs/lib/infix/cli-pretty @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 +import json +import sys +import argparse + +parser = argparse.ArgumentParser(description="JSON CLI Pretty Printer") +parser.add_argument("module", help="IETF Module") +parser.add_argument("-n", "--name", help="Focus on specific name") +args = parser.parse_args() + +class Pad: + iface = 15 + 2 # + 2 is ASCII tree for nesting + proto = 20 + state = 15 + data = 40 + _data = iface + proto + state + +class Decore(): + @staticmethod + def decorate(sgr, txt, restore="0"): + return f"\033[{sgr}m{txt}\033[{restore}m" + + @staticmethod + def invert(txt): + return Decore.decorate("7", txt) + + @staticmethod + def red(txt): + return Decore.decorate("31", txt, "39") + + @staticmethod + def green(txt): + return Decore.decorate("32", txt, "39") + +class Iface: + def __init__(self, data): + self.data = data + self.name = data.get('name', '') + self.index = data.get('if-index', None) + self.oper_status = data.get('oper-status', None) + self.phys_address = data.get('phys-address', None) + + self.parent = data.get('ietf-if-extensions:parent-interface', None) + + if self.data.get('ietf-ip:ipv4'): + self.mtu = self.data.get('ietf-ip:ipv4').get('mtu', None) + self.ipv4_addr = self.data.get('ietf-ip:ipv4').get('address', None) + else: + self.mtu = None + self.ipv4_addr = [] + + if self.data.get('infix-interfaces:bridge-port'): + self.bridge = self.data.get('infix-interfaces:bridge-port').get('bridge', None) + else: + self.bridge = None + + def is_vlan(self): + return self.data['type'] == "iana-if-type:l2vlan" + + def is_etherent(self): + return self.data['type'] == "iana-if-type:ethernetCsmacd" + + def is_bridge(self): + return self.data['type'] == "iana-if-type:bridge" + + def pr_name(self, pipe=""): + print(f"{pipe}{self.name:<{Pad.iface - len(pipe)}}", end="") + + + def pr_proto_ipv4(self, pipe=''): + addr = self.data.get("ietf-ip:ipv4") + if not addr: + return + + addresses = self.data.get("ietf-ip:ipv4", {}).get("address", []) + for addr in addresses: + row = f"{pipe:<{Pad.iface}}" + row += f"{'ipv4':<{Pad.proto}}" + row += f"{'':<{Pad.state}}{addr['ip']}/{addr['prefix-length']}" + print(row) + + def pr_proto_eth(self): + row = f"{'ethernet':<{Pad.proto}}" + dec = Decore.green if self.data['oper-status'] == "up" else Decore.red + row += dec(f"{self.data['oper-status'].upper():<{Pad.state}}") + row += f"{self.data['phys-address']:<{Pad.data}}" + print(row) + + def pr_bridge(self, _ifaces): + self.pr_name(pipe="") + self.pr_proto_eth() + + + lowers = [] + for _iface in [Iface(data) for data in _ifaces]: + if _iface.bridge and _iface.bridge == self.name: + lowers.append(_iface) + + if lowers: + self.pr_proto_ipv4(pipe='│') + else: + self.pr_proto_ipv4() + + for i, lower in enumerate(lowers): + pipe = '└ ' if (i == len(lowers) -1) else '├ ' + lower.pr_name(pipe) + lower.pr_proto_eth() + + def pr_vlan(self, _ifaces): + self.pr_name(pipe="") + self.pr_proto_eth() + + if self.parent: + self.pr_proto_ipv4(pipe='│') + else: + self.pr_proto_ipv4() + return + + parent = find_iface(_ifaces, self.parent) + if not parent: + print(f"Error, didn't find parent interface for vlan {self.name}") + sys.exit(1) + parent.pr_name(pipe='└ ') + parent.pr_proto_eth() + + def pr_iface(self): + print(f"{'name':<{20}}: {self.name}") + if self.index: + print(f"{'index':<{20}}: {self.index}") + if self.mtu: + print(f"{'mtu':<{20}}: {self.mtu}") + if self.oper_status: + print(f"{'operational status':<{20}}: {self.oper_status}") + if self.phys_address: + print(f"{'physical address':<{20}}: {self.phys_address}") + + if self.ipv4_addr: + first = True + print("") + for addr in self.ipv4_addr: + key = 'ipv4 addresses' if first else '' + print(f"{key:<{20}}: {addr['ip']}/{addr['prefix-length']}") + first = False + +def find_iface(_ifaces, name): + for _iface in [Iface(data) for data in _ifaces]: + if _iface.name == name: + return _iface + + return False + + +def pr_interface_list(json): + hdr = (f"{'INTERFACE':<{Pad.iface}}" + f"{'PROTOCOL':<{Pad.proto}}" + f"{'STATE':<{Pad.state}}" + f"{'DATA':<{Pad.data}}") + + print(Decore.invert(hdr)) + + ifaces = sorted(json["ietf-interfaces:interfaces"]["interface"], key=lambda x: x['name']) + + for iface in [Iface(data) for data in ifaces]: + if iface.is_bridge(): + iface.pr_bridge(ifaces) + continue + + if iface.is_vlan(): + iface.pr_vlan(ifaces) + continue + + # These interfaces are printed by there parent, such as bridge + if iface.parent: + continue + if iface.bridge: + continue + + iface.pr_name() + iface.pr_proto_eth() + iface.pr_proto_ipv4() + +def ietf_interfaces(json, name): + if not json or not json.get("ietf-interfaces:interfaces"): + print(f"Error, top level \"ietf-interfaces:interfaces\" missing") + sys.exit(1) + + if not name: + return pr_interface_list(json) + + iface = find_iface(json["ietf-interfaces:interfaces"]["interface"], name) + if not iface: + print(f"Interface {name} not found") + sys.exit(1) + return iface.pr_iface() + + +json = json.load(sys.stdin) + +if args.module == "ietf-interfaces": + sys.exit(ietf_interfaces(json, args.name)) +else: + print(f"Error, unknown module {args.module}") + sys.exit(1) diff --git a/board/netconf/rootfs/lib/infix/json-cfg-pretty b/board/netconf/rootfs/lib/infix/json-cfg-pretty deleted file mode 100755 index c286fc71c..000000000 --- a/board/netconf/rootfs/lib/infix/json-cfg-pretty +++ /dev/null @@ -1,127 +0,0 @@ -#!/bin/bash - -CYAN='\033[0;36m' # Used in headers -RED='\033[0;31m' -GREEN='\033[0;32m' -NC='\033[0m' # No Color - -GREY_BG="\e[48;5;235m" -RESET="\e[0m" - -HEADER_WIDHT=80 -declare -A IETF_TYPE_MAP - -IETF_TYPE_MAP["iana-if-type:softwareLoopback"]="loopback" -IETF_TYPE_MAP["iana-if-type:ethernetCsmacd"]="ethernet" -IETF_TYPE_MAP["iana-if-type:l2vlan"]="vlan" -IETF_TYPE_MAP["infix-if-type:veth"]="veth" -IETF_TYPE_MAP["iana-if-type:bridge"]="bridge" - -usage() { - printf "Please provide a valid base field as the first argument\n" - exit 1 -} - -if [ -z "$1" ]; then - usage -fi - -json=$(cat) - -show_ietf_interface() { - name="$1" - shift - - iface=$(echo "$json" | jq --arg name "$name" \ - '.["ietf-interfaces:interfaces"].interface[] | select(.name == $name)') - if [ -z "$iface" ]; then - printf "Error, interface named \"$name\" not found" - exit 1 - fi - - printf "%-20s : %s\n" "name" "$name" - printf "%-20s : %s\n" "interface-index" "$(echo "$iface" | jq -r '.["if-index"]')" - printf "%-20s : %s\n" "operational-status" "$(echo "$iface" | jq -r '.["oper-status"]')" - printf "%-20s : %s\n" "physical-address" "$(echo "$iface" | jq -r '.["phys-address"]')" - - # MTU isn't set for loopback - mtu="$(echo "$iface" | jq -r '.["ietf-ip:ipv4"] | select(.mtu != null) | .mtu')" - if [ -n "$mtu" ]; then - printf "%-20s : %s\n" "mtu" "$mtu" - fi - - printf "\n${CYAN}%-5s${NC}\n" "ipv4:" - addresses=$(echo "$iface" | jq -c --arg field "$field" '.["ietf-ip:ipv4"].address[]' 2>/dev/null) - for addr in $addresses; do - ip="$(echo "$addr" | jq -r '."ip"')" - prefix="$(echo "$addr" | jq -r '."prefix-length"')" - printf "%-5s %s/%s\n" "" "$ip" "$prefix" - done - - printf "\n${CYAN}%-5s %5s\n${NC}" "in:" "octets" - printf "%-5s %s\n" "" "$(echo "$iface" | jq -r '.statistics["in-octets"]')" - - printf "\n${CYAN}%-5s %5s${NC}\n" "out:" "octets" - printf "%-5s %s\n" "" "$(echo "$iface" | jq -r '.statistics["out-octets"]')" -} - -show_ietf_interfaces() { - field="ietf-interfaces:interfaces" - - if [ $# -ne 0 ]; then - show_ietf_interface "$1" - return - fi - - interfaces=$(echo "$json" | jq -c --arg field "$field" '.[$field].interface[]') - - HEADER=$(printf "%-15s %-14s %-25s %s\n" "INTERFACE" "STATE" "PROTOCOL/ADDRESS" "SOURCE") - HEADER_LEN=${#HEADER} - RIGHT_PADDING=$(( HEADER_WIDHT - HEADER_LEN )) - - printf "${GREY_BG}%s%${RIGHT_PADDING}s${RESET}\n" "$HEADER" "" - - for iface in $interfaces; do - name="$(echo "$iface" | jq -r '.["name"]')" - state="$(echo "$iface" | jq -r '.["oper-status"]')" - mac="$(echo "$iface" | jq -r '.["phys-address"]')" - type="$(echo "$iface" | jq -r '.["type"]')" - state_color="" - - if [[ -v IETF_TYPE_MAP["$type"] ]]; then - src="${IETF_TYPE_MAP["$type"]}" - else - src="unknown" - fi - - if [ "$state" = "up" ]; then - state_color="$GREEN" - elif [ "$state" = "down" ]; then - state_color="$RED" - fi - printf "%-15s ${state_color}%-15s${NC}%s" "$name" "$state" - - addresses=$(echo "$iface" | jq -c --arg field "$field" '.["ietf-ip:ipv4"].address[]' 2>/dev/null) - printf "%-25s %s\n" "$mac" "$src" - for addr in $addresses; do - ip="$(echo "$addr" | jq -r '."ip"')" - prefix="$(echo "$addr" | jq -r '."prefix-length"')" - printf "%-30s %s/%s\n" "" "$ip" "$prefix" - done - if [ -n "$addresses" ]; then - printf "\n" - fi - done -} - -field="$1" -shift - -case "$field" in - "ietf-interfaces") - show_ietf_interfaces $* - ;; - *) - usage - ;; -esac diff --git a/src/klish-plugin-infix/xml/infix.xml b/src/klish-plugin-infix/xml/infix.xml index b2c24f549..b004bca5a 100644 --- a/src/klish-plugin-infix/xml/infix.xml +++ b/src/klish-plugin-infix/xml/infix.xml @@ -239,9 +239,13 @@ - name="${KLISH_PARAM_name:-}" + if [ -n "$KLISH_PARAM_name" ]; then sysrepocfg -f json -X -d operational -m ietf-interfaces | \ - /lib/infix/json-cfg-pretty "ietf-interfaces" "$name" + /lib/infix/cli-pretty "ietf-interfaces" -n "$KLISH_PARAM_name" + else + sysrepocfg -f json -X -d operational -m ietf-interfaces | \ + /lib/infix/cli-pretty "ietf-interfaces" + fi