diff --git a/.bin/Scripts/functions/hw_diags.py b/.bin/Scripts/functions/hw_diags.py index f516e5cc..48962299 100644 --- a/.bin/Scripts/functions/hw_diags.py +++ b/.bin/Scripts/functions/hw_diags.py @@ -102,6 +102,7 @@ class CpuObj(): self.tests = OrderedDict() self.get_details() self.name = self.lscpu.get('Model name', 'Unknown CPU') + self.description = self.name def get_details(self): """Get CPU details from lscpu.""" diff --git a/.bin/Scripts/functions/osticket.py b/.bin/Scripts/functions/osticket.py index 0d1561c0..8984be90 100644 --- a/.bin/Scripts/functions/osticket.py +++ b/.bin/Scripts/functions/osticket.py @@ -5,6 +5,11 @@ import mysql.connector as mariadb from functions.common import * from settings.osticket import * +# Regex +REGEX_BLOCK_GRAPH = re.compile(r'(▁|▂|▃|▄|▅|▆|▇|█)') +REGEX_NVME_SMART_ATTRIBUTES = re.compile(r'^\s*(\d+) / (\w+): (.{28})(.*)$') +REGEX_TEMPS = re.compile(r'^\s*(.*?)\s+(idle.*)$') + # Classes class osTicket(): """Class to track osTicket data and functions.""" @@ -171,6 +176,162 @@ class osTicket(): self.disconnect() # Functions +def convert_report(name, test): + """Convert report into an osTicket friendly format, returns list.""" + out_report = [] + source_report = test.source_report + status = strip_colors(test.status) + status = status.replace(test.label, '').strip() + + # Header + index = 1 + if name == 'NVMe / SMART': + out_report.append('{} ({})'.format(name, status)) + if not source_report: + index = 0 + source_report = test.dev.generate_attribute_source_report() + else: + out_report.append('{} ({})'.format(strip_colors(source_report[0]), status)) + + # Body + for line in source_report[index:]: + # Remove colors and leading spaces + line = strip_colors(line) + if line[:2] == ' ': + line = line[2:] + + # Test-specific modifications + if name == 'Prime95': + r = REGEX_TEMPS.match(line) + if r: + _sensor = '{:<20}'.format(r.group(1)) + _temps = r.group(2) + line = '{} {}'.format( + pad_with_dots(_sensor, pad_right=True), + _temps) + elif name == 'NVMe / SMART': + r = REGEX_NVME_SMART_ATTRIBUTES.match(line) + if r: + _dec = '{:>3}'.format(r.group(1)) + _hex = r.group(2) + _atr = r.group(3).strip() + _val = '{:<20}'.format(r.group(4)) + line = '{}/{}: {} {}'.format( + _hex, + pad_with_dots(_dec), + pad_with_dots(_val, pad_right=True), + _atr) + elif name == 'I/O Benchmark': + line = REGEX_BLOCK_GRAPH.sub('', line) + line = line.strip() + if not line: + continue + + # Remove extra spaces + line = line.strip() + line = re.sub(r'(\s+)', ' ', line) + + # Add line to report + out_report.append(line) + + # Done + return out_report + +def get_device_overall_results(dev): + """Get overall results from tests for device, returns dict.""" + results = { + 'Dev Type': 'Unknown', + 'Full Diag': False, + 'Asterisk': None, + 'Failed': 0, + 'N/A': 0, + 'Passed': 0, + 'Status': 'Unknown', + } + + # Get test list for device type + test_list = [] + if isinstance(dev, CpuObj): + results['Dev Type'] = 'CPU' + test_list = TESTS_CPU + elif isinstance(dev, DiskObj): + results['Dev Type'] = 'Disk' + test_list = TESTS_DISK + else: + raise GenericError('Unrecognized device type.') + + # Check if a full diag was run (i.e. all dev tests were enabled) + results['Full Diag'] = len(dev.tests) == len(test_list) + + # Tally test results + for test in dev.tests.value(): + if test.failed: + results['Failed'] += 1 + if test.passed: + results['Passed'] += 1 + if 'N/A' in test.status: + results['N/A'] += 1 + + # Set overall status + if results['Failed'] > 0: + dev.checkbox = False + results['Status'] = 'FAILED' + elif results['Passed'] + results['N/A'] == len(dev.tests): + dev.checkbox = True + results['Status'] = 'PASSED' + else: + results['Status'] = 'UNKNOWN' + if results['Full Diag'] and results['N/A'] > 0: + results['Asterisk'] = True + results['Status'] += '*' + + # Done + return results + +def generate_osticket_report(dev): + """Generate device report for osTicket, returns list.""" + report = [] + results = get_device_overall_results(dev) + + # Header + if results['Full Diag']: + report.append( + '{Dev Type} hardware diagnostic tests: {Status}'.format(**results)) + report.append(' ') + + # Device + report.append(dev.description) + report.append(' ') + + # Test reports + for name, test in dev.tests.items(): + report.extend(convert_report(name, test)) + if name == 'I/O Benchmark': + # TODO: Create PNG graph and upload to imgur/Nextcloud + report.append('Imgur: TODO') + report.append('Nextcloud: TODO') + report.append(' ') + + # Volumes + if results['Dev Type'] == 'Disk': + # TODO: Mount all volumes and extend report + report.append('Volumes:') + report.append('TODO') + report.append(' ') + + # Asterisk + if results['Asterisk']: + report.append('* NOTE: One or more tests were not run on this device') + +def pad_with_dots(s, pad_right=False): + """Replace space padding with dots, returns str.""" + s = str(s).replace(' ', '..') + if '.' in s: + if pad_right: + s = s + '.' + else: + s = '.' + s + return s if __name__ == '__main__': print("This file is not meant to be called directly.")