cli metrics, fixes

This commit is contained in:
Arseniy Kuznetsov
2021-01-17 20:34:17 +01:00
parent 8faa12786f
commit 31d0464eb2
28 changed files with 499 additions and 147 deletions

View File

@@ -17,3 +17,4 @@
initial_delay_on_failure = 120
max_delay_on_failure = 900
delay_inc_div = 5
bandwidth_test_interval = 420

View File

@@ -52,10 +52,14 @@ class MKTXPConfigKeys:
MKTXP_INITIAL_DELAY = 'initial_delay_on_failure'
MKTXP_MAX_DELAY = 'max_delay_on_failure'
MKTXP_INC_DIV = 'delay_inc_div'
MKTXP_BANDWIDTH_TEST_INTERVAL = 'bandwidth_test_interval'
# UnRegistered entries placeholder
NO_ENTRIES_REGISTERED = 'NoEntriesRegistered'
MKTXP_USE_COMMENTS_OVER_NAMES = 'use_comments_over_names'
# Base router id labels
ROUTERBOARD_NAME = 'routerboard_name'
ROUTERBOARD_ADDRESS = 'routerboard_address'
@@ -68,13 +72,15 @@ class MKTXPConfigKeys:
DEFAULT_MKTXP_INITIAL_DELAY = 120
DEFAULT_MKTXP_MAX_DELAY = 900
DEFAULT_MKTXP_INC_DIV = 5
DEFAULT_MKTXP_BANDWIDTH_TEST_INTERVAL = 420
BOOLEAN_KEYS = (ENABLED_KEY, SSL_KEY, NO_SSL_CERTIFICATE, SSL_CERTIFICATE_VERIFY,
FE_DHCP_KEY, FE_DHCP_LEASE_KEY, FE_DHCP_POOL_KEY, FE_INTERFACE_KEY,
FE_MONITOR_KEY, FE_ROUTE_KEY, FE_WIRELESS_KEY, FE_WIRELESS_CLIENTS_KEY, FE_CAPSMAN_KEY, FE_CAPSMAN_CLIENTS_KEY)
FE_MONITOR_KEY, FE_ROUTE_KEY, MKTXP_USE_COMMENTS_OVER_NAMES,
FE_WIRELESS_KEY, FE_WIRELESS_CLIENTS_KEY, FE_CAPSMAN_KEY, FE_CAPSMAN_CLIENTS_KEY)
STR_KEYS = [HOST_KEY, USER_KEY, PASSWD_KEY]
INT_KEYS = [PORT_KEY, MKTXP_SOCKET_TIMEOUT, MKTXP_INITIAL_DELAY, MKTXP_MAX_DELAY, MKTXP_INC_DIV]
STR_KEYS = (HOST_KEY, USER_KEY, PASSWD_KEY)
MKTXP_INT_KEYS = (PORT_KEY, MKTXP_SOCKET_TIMEOUT, MKTXP_INITIAL_DELAY, MKTXP_MAX_DELAY, MKTXP_INC_DIV, MKTXP_BANDWIDTH_TEST_INTERVAL)
# MKTXP config entry nane
MKTXP_CONFIG_ENTRY_NAME = 'MKTXP'
@@ -87,10 +93,11 @@ class ConfigEntry:
MKTXPConfigKeys.FE_DHCP_KEY, MKTXPConfigKeys.FE_DHCP_LEASE_KEY, MKTXPConfigKeys.FE_DHCP_POOL_KEY, MKTXPConfigKeys.FE_INTERFACE_KEY,
MKTXPConfigKeys.FE_MONITOR_KEY, MKTXPConfigKeys.FE_ROUTE_KEY, MKTXPConfigKeys.FE_WIRELESS_KEY, MKTXPConfigKeys.FE_WIRELESS_CLIENTS_KEY,
MKTXPConfigKeys.FE_CAPSMAN_KEY, MKTXPConfigKeys.FE_CAPSMAN_CLIENTS_KEY
MKTXPConfigKeys.FE_CAPSMAN_KEY, MKTXPConfigKeys.FE_CAPSMAN_CLIENTS_KEY, MKTXPConfigKeys.MKTXP_USE_COMMENTS_OVER_NAMES
])
_MKTXPEntry = namedtuple('_MKTXPEntry', [MKTXPConfigKeys.PORT_KEY, MKTXPConfigKeys.MKTXP_SOCKET_TIMEOUT,
MKTXPConfigKeys.MKTXP_INITIAL_DELAY, MKTXPConfigKeys.MKTXP_MAX_DELAY, MKTXPConfigKeys.MKTXP_INC_DIV])
MKTXPConfigKeys.MKTXP_INITIAL_DELAY, MKTXPConfigKeys.MKTXP_MAX_DELAY,
MKTXPConfigKeys.MKTXP_INC_DIV, MKTXPConfigKeys.MKTXP_BANDWIDTH_TEST_INTERVAL])
class OSConfig(metaclass = ABCMeta):
@@ -149,6 +156,8 @@ class MKTXPConfigHandler:
self._create_os_path(self.usr_conf_data_path, 'mktxp/cli/config/mktxp.conf')
self._create_os_path(self.mktxp_conf_path, 'mktxp/cli/config/_mktxp.conf')
self.re_compiled = {}
self._read_from_disk()
def _read_from_disk(self):
@@ -219,11 +228,13 @@ class MKTXPConfigHandler:
# Helpers
def entry_reader(self, entry_name):
entry_reader = {}
write_needed = False
for key in MKTXPConfigKeys.BOOLEAN_KEYS:
if self.config[entry_name].get(key):
entry_reader[key] = self.config[entry_name].as_bool(key)
else:
entry_reader[key] = False
write_needed = True # read from disk next time
for key in MKTXPConfigKeys.STR_KEYS:
entry_reader[key] = self.config[entry_name][key]
@@ -233,17 +244,29 @@ class MKTXPConfigHandler:
entry_reader[MKTXPConfigKeys.PORT_KEY] = self.config[entry_name].as_int(MKTXPConfigKeys.PORT_KEY)
else:
entry_reader[MKTXPConfigKeys.PORT_KEY] = self._default_value_for_key(MKTXPConfigKeys.SSL_KEY, entry_reader[MKTXPConfigKeys.SSL_KEY])
write_needed = True # read from disk next time
if write_needed:
self.config[entry_name] = entry_reader
self.config.write()
return entry_reader
def _entry_reader(self):
_entry_reader = {}
entry_name = MKTXPConfigKeys.MKTXP_CONFIG_ENTRY_NAME
for key in MKTXPConfigKeys.INT_KEYS:
write_needed = False
for key in MKTXPConfigKeys.MKTXP_INT_KEYS:
if self._config[entry_name].get(key):
_entry_reader[key] = self._config[entry_name].as_int(key)
else:
_entry_reader[key] = self._default_value_for_key(key)
write_needed = True # read from disk next time
if write_needed:
self._config[entry_name] = _entry_reader
self._config.write()
return _entry_reader
def _default_value_for_key(self, key, value = None):
@@ -253,9 +276,13 @@ class MKTXPConfigHandler:
MKTXPConfigKeys.MKTXP_SOCKET_TIMEOUT: lambda value: MKTXPConfigKeys.DEFAULT_MKTXP_SOCKET_TIMEOUT,
MKTXPConfigKeys.MKTXP_INITIAL_DELAY: lambda value: MKTXPConfigKeys.DEFAULT_MKTXP_INITIAL_DELAY,
MKTXPConfigKeys.MKTXP_MAX_DELAY: lambda value: MKTXPConfigKeys.DEFAULT_MKTXP_MAX_DELAY,
MKTXPConfigKeys.MKTXP_INC_DIV: lambda value: MKTXPConfigKeys.DEFAULT_MKTXP_INC_DIV
MKTXPConfigKeys.MKTXP_INC_DIV: lambda value: MKTXPConfigKeys.DEFAULT_MKTXP_INC_DIV,
MKTXPConfigKeys.MKTXP_BANDWIDTH_TEST_INTERVAL: lambda value: MKTXPConfigKeys.DEFAULT_MKTXP_BANDWIDTH_TEST_INTERVAL
}[key](value)
# Simplest possible Singleton impl
config_handler = MKTXPConfigHandler()

View File

@@ -22,8 +22,8 @@
use_ssl = False # enables connection via API-SSL servis
no_ssl_certificate = False # enables API_SSL connect without router SSL certificate
ssl_certificate_verify = False # turns SSL certificate verification on / off
ssl_certificate_verify = False # turns SSL certificate verification on / off
dhcp = True
dhcp_lease = True
pool = True
@@ -35,4 +35,4 @@
capsman = True
capsman_clients = True
use_comments_over_names = False # when available, use comments instead of interface names

View File

@@ -14,12 +14,11 @@
import sys
import subprocess
import pkg_resources
import mktxp.cli.checks.chk_pv
from mktxp.utils.utils import run_cmd
from mktxp.cli.options import MKTXPOptionsParser, MKTXPCommands
from mktxp.cli.config.config import config_handler, ConfigEntry
from mktxp.basep import MKTXPProcessor
from mktxp.basep import MKTXPProcessor, MKTXPCLIProcessor
class MKTXPDispatcher:
''' Base MKTXP Commands Dispatcher
@@ -31,10 +30,7 @@ class MKTXPDispatcher:
def dispatch(self):
args = self.option_parser.parse_options()
if args['sub_cmd'] == MKTXPCommands.VERSION:
self.print_version()
elif args['sub_cmd'] == MKTXPCommands.INFO:
if args['sub_cmd'] == MKTXPCommands.INFO:
self.print_info()
elif args['sub_cmd'] == MKTXPCommands.SHOW:
@@ -49,9 +45,12 @@ class MKTXPDispatcher:
elif args['sub_cmd'] == MKTXPCommands.DELETE:
self.delete_entry(args)
elif args['sub_cmd'] == MKTXPCommands.START:
elif args['sub_cmd'] == MKTXPCommands.EXPORT:
self.start_export(args)
elif args['sub_cmd'] == MKTXPCommands.PRINT:
self.print(args)
else:
# nothing to dispatch
return False
@@ -59,18 +58,11 @@ class MKTXPDispatcher:
return True
# Dispatched methods
def print_version(self):
''' Prints MKTXP version info
'''
version = pkg_resources.require("mktxp")[0].version
print(f'Mikrotik RouterOS Prometheus Exporter version {version}')
def print_info(self):
''' Prints MKTXP general info
'''
print(f'{self.option_parser.script_name}: {self.option_parser.description}')
def show_entries(self, args):
if args['config']:
print(f'MKTXP data config: {config_handler.usr_conf_data_path}')
@@ -100,7 +92,10 @@ class MKTXPDispatcher:
editor = args['editor']
if not editor:
print(f'No editor to edit the following file with: {config_handler.usr_conf_data_path}')
subprocess.check_call([editor, config_handler.usr_conf_data_path])
if args['internal']:
subprocess.check_call([editor, config_handler.mktxp_conf_path])
else:
subprocess.check_call([editor, config_handler.usr_conf_data_path])
def delete_entry(self, args):
config_handler.unregister_entry(entry_name = args['entry_name'])
@@ -108,6 +103,16 @@ class MKTXPDispatcher:
def start_export(self, args):
MKTXPProcessor.start()
def print(self, args):
if not (args['wifi_clients'] or args['capsman_clients']):
print("Select metric option(s) to print out, or run 'mktxp print' -h to find out more")
if args['wifi_clients']:
MKTXPCLIProcessor.wifi_clients(args['entry_name'])
if args['capsman_clients']:
MKTXPCLIProcessor.capsman_clients(args['entry_name'])
def main():
MKTXPDispatcher().dispatch()

View File

@@ -12,6 +12,7 @@
## GNU General Public License for more details.
import os
import pkg_resources
from argparse import ArgumentParser, HelpFormatter
from mktxp.cli.config.config import config_handler, MKTXPConfigKeys
from mktxp.utils.utils import FSHelper, UniquePartialMatchList, run_cmd
@@ -19,37 +20,38 @@ from mktxp.utils.utils import FSHelper, UniquePartialMatchList, run_cmd
class MKTXPCommands:
INFO = 'info'
VERSION = 'version'
EDIT = 'edit'
EXPORT = 'export'
PRINT = 'print'
SHOW = 'show'
ADD = 'add'
EDIT = 'edit'
DELETE = 'delete'
START = 'start'
@classmethod
def commands_meta(cls):
return ''.join(('{',
f'{cls.INFO}, ',
f'{cls.VERSION}, ',
f'{cls.EDIT}, ',
f'{cls.EXPORT}, ',
f'{cls.PRINT}, ',
f'{cls.SHOW}, ',
f'{cls.ADD}, ',
f'{cls.EDIT}, ',
f'{cls.DELETE}, ',
f'{cls.START}',
f'{cls.DELETE}',
'}'))
class MKTXPOptionsParser:
''' Base MKTXP Options Parser
'''
def __init__(self):
self._script_name = 'MKTXP'
self._description = \
'''
Prometheus Exporter for Mikrotik RouterOS.
Supports gathering metrics across multiple RouterOS devices, all easily configurable via built-in CLI interface.
Comes along with a dedicated Grafana dashboard(https://grafana.com/grafana/dashboards/13679)
'''
self._script_name = f'MKTXP'
version = pkg_resources.require("mktxp")[0].version
self._description = \
f'''
Prometheus Exporter for Mikrotik RouterOS, version {version}
Supports gathering metrics across multiple RouterOS devices, all easily configurable via built-in CLI interface.
Comes along with a dedicated Grafana dashboard (https://grafana.com/grafana/dashboards/13679)
Selected metrics info can be printed on the command line. For more information, run: 'mktxp -h'
'''
@property
def description(self):
@@ -64,7 +66,7 @@ class MKTXPOptionsParser:
''' General Options parsing workflow
'''
parser = ArgumentParser(prog = self._script_name,
description = self._description,
description = 'Prometheus Exporter for Mikrotik RouterOS',
formatter_class=MKTXPHelpFormatter)
self.parse_global_options(parser)
@@ -91,11 +93,6 @@ class MKTXPOptionsParser:
subparsers.add_parser(MKTXPCommands.INFO,
description = 'Displays MKTXP info',
formatter_class=MKTXPHelpFormatter)
# Version command
subparsers.add_parser(MKTXPCommands.VERSION,
description = 'Displays MKTXP version info',
formatter_class=MKTXPHelpFormatter)
# Show command
show_parser = subparsers.add_parser(MKTXPCommands.SHOW,
description = 'Displays MKTXP config router entries',
@@ -170,10 +167,14 @@ class MKTXPOptionsParser:
edit_parser = subparsers.add_parser(MKTXPCommands.EDIT,
description = 'Edits an existing MKTXP router entry',
formatter_class=MKTXPHelpFormatter)
edit_parser.add_argument('-ed', '--editor', dest='editor',
help = f"command line editor to use ({self._system_editor()} by default)",
optional_args_group = edit_parser.add_argument_group('Optional Arguments')
optional_args_group.add_argument('-ed', '--editor', dest='editor',
help = f"Command line editor to use ({self._system_editor()} by default)",
default = self._system_editor(),
type = str)
optional_args_group.add_argument('-i', '--internal', dest='internal',
help = f"Edit MKTXP internal configuration (advanced)",
action = 'store_true')
# Delete command
delete_parser = subparsers.add_parser(MKTXPCommands.DELETE,
@@ -183,10 +184,27 @@ class MKTXPOptionsParser:
self._add_entry_name(required_args_group, registered_only = True, help = "Name of entry to delete")
# Start command
start_parser = subparsers.add_parser(MKTXPCommands.START,
description = 'Starts exporting Miktorik Router Metrics',
start_parser = subparsers.add_parser(MKTXPCommands.EXPORT,
description = 'Starts exporting Miktorik Router Metrics to Prometheus',
formatter_class=MKTXPHelpFormatter)
# Print command
print_parser = subparsers.add_parser(MKTXPCommands.PRINT,
description = 'Displays seleted metrics on the command line',
formatter_class=MKTXPHelpFormatter)
required_args_group = print_parser.add_argument_group('Required Arguments')
self._add_entry_name(required_args_group, registered_only = True, help = "Name of config RouterOS entry")
optional_args_group = print_parser.add_argument_group('Optional Arguments')
optional_args_group.add_argument('-cc', '--capsman_clients', dest='capsman_clients',
help = "CAPsMAN clients metrics",
action = 'store_true')
optional_args_group.add_argument('-wc', '--wifi_clients', dest='wifi_clients',
help = "WiFi clients metrics",
action = 'store_true')
# Options checking
def _check_args(self, args, parser):
''' Validation of supplied CLI arguments
@@ -194,16 +212,21 @@ class MKTXPOptionsParser:
# check if there is a cmd to execute
self._check_cmd_args(args, parser)
if args['sub_cmd'] in (MKTXPCommands.DELETE, MKTXPCommands.SHOW):
if args['sub_cmd'] in (MKTXPCommands.DELETE, MKTXPCommands.SHOW, MKTXPCommands.PRINT):
# Registered Entry name could be a partial match, need to expand
if args['entry_name']:
args['entry_name'] = UniquePartialMatchList(config_handler.registered_entries()).find(args['entry_name'])
elif args['sub_cmd'] == MKTXPCommands.ADD:
if args['sub_cmd'] == MKTXPCommands.ADD:
if args['entry_name'] in (config_handler.registered_entries()):
print(f"{args['entry_name']}: entry name already exists")
parser.exit()
elif args['sub_cmd'] == MKTXPCommands.PRINT:
if not config_handler.entry(args['entry_name']).enabled:
print(f"Can not print metrics for disabled RouterOS entry: {args['entry_name']}\nRun 'mktxp edit' to review and enable it in the configuration file first")
parser.exit()
def _check_cmd_args(self, args, parser):
''' Validation of supplied CLI commands
'''
@@ -223,7 +246,7 @@ class MKTXPOptionsParser:
def _default_command(self):
''' If no command was specified, print INFO by default
'''
return MKTXPCommands.START
return MKTXPCommands.INFO
# Internal helpers

View File

View File

@@ -0,0 +1,101 @@
# coding=utf8
## Copyright (c) 2020 Arseniy Kuznetsov
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License
## as published by the Free Software Foundation; either version 2
## of the License, or (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
import re
from datetime import timedelta
from collections import namedtuple
from humanize import naturaldelta
from mktxp.cli.config.config import config_handler
class BaseOutputProcessor:
OutputCapsmanEntry = namedtuple('OutputCapsmanEntry', ['dhcp_name', 'dhcp_address', 'mac_address', 'rx_signal', 'interface', 'ssid', 'tx_rate', 'rx_rate', 'uptime'])
OutputWiFiEntry = namedtuple('OutputWiFiEntry', ['dhcp_name', 'dhcp_address', 'mac_address', 'signal_strength', 'signal_to_noise', 'interface', 'tx_rate', 'rx_rate', 'uptime'])
@staticmethod
def augment_record(router_metric, registration_record, dhcp_lease_records):
try:
dhcp_lease_record = next((dhcp_lease_record for dhcp_lease_record in dhcp_lease_records if dhcp_lease_record['mac_address']==registration_record['mac_address']))
dhcp_name = dhcp_lease_record.get('host_name')
dhcp_comment = dhcp_lease_record.get('comment')
if dhcp_name and dhcp_comment:
dhcp_name = f'{dhcp_name[0:20]} ({dhcp_comment[0:20]})' if not router_metric.router_entry.use_comments_over_names else dhcp_comment
elif dhcp_comment:
dhcp_name = dhcp_comment
else:
dhcp_name = dhcp_lease_record.get('mac_address') if not dhcp_name else dhcp_name
dhcp_address = dhcp_lease_record.get('address', '')
except StopIteration:
dhcp_name = registration_record['mac_address']
dhcp_address = 'No DHCP Record'
registration_record['dhcp_name'] = dhcp_name
registration_record['dhcp_address'] = dhcp_address
# split out tx/rx bytes
if registration_record.get('bytes'):
registration_record['tx_bytes'] = registration_record['bytes'].split(',')[0]
registration_record['rx_bytes'] = registration_record['bytes'].split(',')[1]
del registration_record['bytes']
registration_record['tx_rate'] = BaseOutputProcessor.parse_rates(registration_record['tx_rate'])
registration_record['rx_rate'] = BaseOutputProcessor.parse_rates(registration_record['rx_rate'])
registration_record['uptime'] = naturaldelta(BaseOutputProcessor.parse_timedelta_seconds(registration_record['uptime']), months=True, minimum_unit='seconds', when=None)
if registration_record.get('signal_strength'):
registration_record['signal_strength'] = BaseOutputProcessor.parse_signal_strength(registration_record['signal_strength'])
if registration_record.get('rx_signal'):
registration_record['rx_signal'] = BaseOutputProcessor.parse_signal_strength(registration_record['rx_signal'])
@staticmethod
def parse_rates(rate):
wifi_rates_rgx = config_handler.re_compiled.get('wifi_rates_rgx')
if not wifi_rates_rgx:
wifi_rates_rgx = re.compile(r'(\d*(?:\.\d*)?)([GgMmKk]bps?)')
config_handler.re_compiled['wifi_rates_rgx'] = wifi_rates_rgx
rc = wifi_rates_rgx.search(rate)
return f'{int(float(rc[1]))} {rc[2]}'
@staticmethod
def parse_timedelta(time):
duration_interval_rgx = config_handler.re_compiled.get('duration_interval_rgx')
if not duration_interval_rgx:
duration_interval_rgx = re.compile(r'((?P<weeks>\d+)w)?((?P<days>\d+)d)?((?P<hours>\d+)h)?((?P<minutes>\d+)m)?((?P<seconds>\d+)s)?')
config_handler.re_compiled['duration_interval_rgx'] = duration_interval_rgx
time_dict = duration_interval_rgx.match(time).groupdict()
return timedelta(**{key: int(value) for key, value in time_dict.items() if value})
@staticmethod
def parse_timedelta_seconds(time):
return BaseOutputProcessor.parse_timedelta(time).total_seconds()
@staticmethod
def parse_signal_strength(signal_strength):
wifi_signal_strength_rgx = config_handler.re_compiled.get('wifi_signal_strength_rgx')
if not wifi_signal_strength_rgx:
# wifi_signal_strength_rgx = re.compile(r'(-?\d+(?:\.\d+)?)(dBm)?')
wifi_signal_strength_rgx = re.compile(r'(-?\d+(?:\.\d+)?)')
config_handler.re_compiled['wifi_signal_strength_rgx'] = wifi_signal_strength_rgx
return wifi_signal_strength_rgx.search(signal_strength).group()
@staticmethod
def parse_interface_rate(interface_rate):
interface_rate_rgx = config_handler.re_compiled.get('interface_rate_rgx')
if not interface_rate_rgx:
interface_rate_rgx = re.compile(r'[^.\-\d]')
config_handler.re_compiled['interface_rate_rgx'] = interface_rate_rgx
rate = lambda interface_rate: 1000 if interface_rate.find('Mbps') < 0 else 1
return(int(float(interface_rate_rgx.sub('', interface_rate)) * rate(interface_rate)))

View File

@@ -0,0 +1,52 @@
# coding=utf8
## Copyright (c) 2020 Arseniy Kuznetsov
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License
## as published by the Free Software Foundation; either version 2
## of the License, or (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
from tabulate import tabulate
from mktxp.cli.output.base_out import BaseOutputProcessor
class CapsmanOutput:
''' CAPsMAN CLI Output
'''
@staticmethod
def clients_summary(router_metric):
registration_labels = ['interface', 'ssid', 'mac_address', 'rx_signal', 'uptime', 'tx_rate', 'rx_rate']
registration_records = router_metric.capsman_registration_table_records(registration_labels, False)
if not registration_records:
print('No CAPsMAN registration records')
return
# translate / trim / augment registration records
dhcp_lease_labels = ['host_name', 'comment', 'address', 'mac_address']
dhcp_lease_records = router_metric.dhcp_lease_records(dhcp_lease_labels, False)
dhcp_rt_by_interface = {}
for registration_record in sorted(registration_records, key = lambda rt_record: rt_record['rx_signal'], reverse=True):
BaseOutputProcessor.augment_record(router_metric, registration_record, dhcp_lease_records)
interface = registration_record['interface']
if interface in dhcp_rt_by_interface.keys():
dhcp_rt_by_interface[interface].append(registration_record)
else:
dhcp_rt_by_interface[interface] = [registration_record]
num_records = 0
output_table = []
for key in dhcp_rt_by_interface.keys():
for record in dhcp_rt_by_interface[key]:
output_table.append(BaseOutputProcessor.OutputCapsmanEntry(**record))
num_records += 1
output_table.append({})
print()
print(tabulate(output_table, headers = "keys", tablefmt="github"))
print(tabulate([{0:'Connected Wifi Devices:', 1:num_records}], tablefmt="text"))

View File

@@ -0,0 +1,51 @@
# coding=utf8
## Copyright (c) 2020 Arseniy Kuznetsov
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License
## as published by the Free Software Foundation; either version 2
## of the License, or (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
from tabulate import tabulate
from mktxp.cli.output.base_out import BaseOutputProcessor
class WirelessOutput:
''' Wireless Clients CLI Output
'''
@staticmethod
def clients_summary(router_metric):
registration_labels = ['interface', 'mac_address', 'signal_strength', 'uptime', 'tx_rate', 'rx_rate', 'signal_to_noise']
registration_records = router_metric.wireless_registration_table_records(registration_labels, False)
if not registration_records:
print('No wireless registration records')
return
# translate / trim / augment registration records
dhcp_lease_labels = ['host_name', 'comment', 'address', 'mac_address']
dhcp_lease_records = router_metric.dhcp_lease_records(dhcp_lease_labels, False)
dhcp_rt_by_interface = {}
for registration_record in sorted(registration_records, key = lambda rt_record: rt_record['signal_strength'], reverse=True):
BaseOutputProcessor.augment_record(router_metric, registration_record, dhcp_lease_records)
interface = registration_record['interface']
if interface in dhcp_rt_by_interface.keys():
dhcp_rt_by_interface[interface].append(registration_record)
else:
dhcp_rt_by_interface[interface] = [registration_record]
num_records = 0
output_table = []
for key in dhcp_rt_by_interface.keys():
for record in dhcp_rt_by_interface[key]:
output_table.append(BaseOutputProcessor.OutputWiFiEntry(**record))
num_records += 1
output_table.append({})
print()
print(tabulate(output_table, headers = "keys", tablefmt="github"))
print(tabulate([{0:'Connected Wifi Devices:', 1:num_records}], tablefmt="text"))