"""WizardKit: osTicket hardware diagnostic functions""" # vim: sts=2 sw=2 ts=2 import logging import re from wk import osticket from wk.cfg.hw import ( REGEX_BLOCK_GRAPH, REGEX_SMART_ATTRIBUTES, ) from wk.hw import smart as hw_smart from wk.ui import cli # STATIC VARIABLES LOG = logging.getLogger(__name__) # Functions def build_report(dev, dev_type, num_disk_tests=None): """Build report for posting to osTicket, returns str.""" report = [] # Combined result if dev_type == 'CPU' or len(dev.tests) == num_disk_tests: # Build list of failed tests (if any) failed_tests = [t.name for t in dev.tests if t.failed] failed_tests = [name.replace('Disk ', '') for name in failed_tests] if len(failed_tests) > 2: failed_tests = f'{", ".join(failed_tests[:-1])}, & {failed_tests[-1]}' else: failed_tests = ' & '.join(failed_tests) # Get overall result result = 'UNKNOWN' if any(t.failed for t in dev.tests): result = 'FAILED' elif all(t.passed for t in dev.tests): result = 'PASSED' # Add to report report.append( f'{dev_type} hardware diagnostic tests: {result}' f'{" ("+failed_tests+")" if failed_tests else ""}' ) report.append('') # Description if hasattr(dev, 'cpu_description'): report.append(dev.cpu_description) else: report.append(dev.description) if hasattr(dev, 'ram_total'): if len(dev.ram_dimms) == 1 and 'justTotalRAM' in dev.ram_dimms[0]: report.append(f'{dev.ram_total} (Total - no DIMM info available)') else: report.append(f'{dev.ram_total} ({", ".join(dev.ram_dimms)})') if hasattr(dev, 'serial') and dev.serial: report.append(f'Serial Number: {dev.serial}') report.append('') # Notes if hasattr(dev, 'notes') and dev.notes: report.append('Notes') report.extend([f'... {note}' for note in dev.notes]) report.append('') # Tests for test in dev.tests: report.append(f'{test.name} ({test.status})') # Report if test.name == 'Disk Attributes' and dev.attributes: report.extend( convert_report( hw_smart.generate_attribute_report(dev), start_index=0, ), ) else: report.extend(convert_report(test.report, start_index=1)) # I/O graph upload report report.extend(getattr(test, 'upload_report', [])) # Spacer report.append('') # Remove last line if empty if not report[-1].strip(): report.pop(-1) # Done return cli.strip_colors('\n'.join(report)) def convert_report(original_report, start_index): """Convert report to an osTicket compatible type, returns list.""" report = [] # Convert report for line in original_report[start_index:]: # Remove colors and leading spaces line = cli.strip_colors(line) line = re.sub(r'^\s+', '', line) # Disk I/O Benchmark if REGEX_BLOCK_GRAPH.search(line): line = REGEX_BLOCK_GRAPH.sub('', line) line = line.strip() # SMART attributes match = REGEX_SMART_ATTRIBUTES.search(line) if match: # Switch decimal and hex labels _dec = f'{match.group("decimal"):>3}' _dec = osticket.pad_with_dots(_dec) _hex = match.group('hex') _data = match.group('data') line = f'{_hex}/{_dec}: {_data}' line = line.replace('failed', 'FAILED') # Skip empty lines if not line.strip(): continue # Fix inner spacing for spacing in re.findall(r'\s\s+', line): new_padding = osticket.pad_with_dots(spacing) new_padding += ' ' line = line.replace(spacing, new_padding) # Indent line line = f'... {line}' # Add to (converted) report report.append(line) # Done return report def post_disk_results(state, num_disk_tests): """Post disk test results for all disks.""" disk_tests = [] for group in state.test_groups: if group.name.startswith('Disk'): disk_tests.extend(group.test_objects) # Bail if no disk tests were run if not disk_tests or state.ost.disabled: return # Post disk results cli.print_info('Posting results to osTicket...') for disk in state.disks: state.ost.post_response( build_report(disk, 'Disk', num_disk_tests), color='Diags FAIL' if any(t.failed for t in disk.tests) else 'Diags', ) def update_checkboxes(state, num_disk_tests): """Update osTicket checkboxes after confirmation.""" cpu_tests = [] disk_tests = [] num_disk_tests_run = len(state.test_groups) # Build list of tests for group in state.test_groups: if group.name.startswith('CPU'): cpu_tests.extend(group.test_objects) num_disk_tests_run -= 1 elif group.name.startswith('Disk'): disk_tests.extend(group.test_objects) elif group.name.startswith('System'): num_disk_tests_run -= 1 # Bail if osTicket integration disabled if state.ost.disabled: return # Bail if values not confirmed if not cli.ask('Update osTicket checkboxes using the data above?'): return # CPU max temp and pass/fail if cpu_tests: state.ost.set_cpu_max_temp( state.cpu_max_temp, ) if any(t.failed for t in cpu_tests): state.ost.set_flag_failed('CPU') elif all(t.passed for t in cpu_tests): state.ost.set_flag_passed('CPU') # Check results for all disks if state.disks: all_disks_passed = True for disk in state.disks: if any(t.failed for t in disk.tests): # Mark failed disk in osTicket and stop checking results all_disks_passed = False state.ost.set_flag_failed('Disk') break if not all(t.passed for t in disk.tests): all_disks_passed = False break # All disks passed if all_disks_passed and num_disk_tests_run == num_disk_tests: # Only mark as passed if a full disk diagnostic passed state.ost.set_flag_passed('Disk') if __name__ == '__main__': print("This file is not meant to be called directly.")