Merge remote-tracking branch 'origin/hw-diags-rewrite' into dev
This commit is contained in:
commit
c33d1b9706
5 changed files with 730 additions and 59 deletions
|
|
@ -5,6 +5,7 @@ import re
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from functions.osticket import *
|
||||||
from functions.sensors import *
|
from functions.sensors import *
|
||||||
from functions.tmux import *
|
from functions.tmux import *
|
||||||
|
|
||||||
|
|
@ -65,6 +66,11 @@ KEY_NVME = 'nvme_smart_health_information_log'
|
||||||
KEY_SMART = 'ata_smart_attributes'
|
KEY_SMART = 'ata_smart_attributes'
|
||||||
QUICK_LABEL = '{YELLOW}(Quick){CLEAR}'.format(**COLORS)
|
QUICK_LABEL = '{YELLOW}(Quick){CLEAR}'.format(**COLORS)
|
||||||
SIDE_PANE_WIDTH = 20
|
SIDE_PANE_WIDTH = 20
|
||||||
|
STATUSES = {
|
||||||
|
'RED': ['Denied', 'ERROR', 'NS', 'TimedOut'],
|
||||||
|
'YELLOW': ['Aborted', 'N/A', 'OVERRIDE', 'Unknown', 'Working'],
|
||||||
|
'GREEN': ['CS'],
|
||||||
|
}
|
||||||
TESTS_CPU = ['Prime95']
|
TESTS_CPU = ['Prime95']
|
||||||
TESTS_DISK = [
|
TESTS_DISK = [
|
||||||
'I/O Benchmark',
|
'I/O Benchmark',
|
||||||
|
|
@ -78,7 +84,10 @@ TMUX_LAYOUT = OrderedDict({
|
||||||
'Progress': {'x': SIDE_PANE_WIDTH, 'Check': True},
|
'Progress': {'x': SIDE_PANE_WIDTH, 'Check': True},
|
||||||
})
|
})
|
||||||
|
|
||||||
# Error Classe
|
# Regex
|
||||||
|
REGEX_ERROR_STATUS = re.compile('|'.join(STATUSES['RED']))
|
||||||
|
|
||||||
|
# Error Classes
|
||||||
class DeviceTooSmallError(Exception):
|
class DeviceTooSmallError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -90,6 +99,7 @@ class CpuObj():
|
||||||
self.tests = OrderedDict()
|
self.tests = OrderedDict()
|
||||||
self.get_details()
|
self.get_details()
|
||||||
self.name = self.lscpu.get('Model name', 'Unknown CPU')
|
self.name = self.lscpu.get('Model name', 'Unknown CPU')
|
||||||
|
self.description = self.name
|
||||||
|
|
||||||
def get_details(self):
|
def get_details(self):
|
||||||
"""Get CPU details from lscpu."""
|
"""Get CPU details from lscpu."""
|
||||||
|
|
@ -125,6 +135,7 @@ class CpuObj():
|
||||||
class DiskObj():
|
class DiskObj():
|
||||||
"""Object for tracking disk specific data."""
|
"""Object for tracking disk specific data."""
|
||||||
def __init__(self, disk_path):
|
def __init__(self, disk_path):
|
||||||
|
self.checkbox = None
|
||||||
self.disk_ok = True
|
self.disk_ok = True
|
||||||
self.labels = []
|
self.labels = []
|
||||||
self.lsblk = {}
|
self.lsblk = {}
|
||||||
|
|
@ -234,7 +245,9 @@ class DiskObj():
|
||||||
print_standard(' (Have you tried swapping the disk cable?)')
|
print_standard(' (Have you tried swapping the disk cable?)')
|
||||||
else:
|
else:
|
||||||
# Override?
|
# Override?
|
||||||
show_report(self.generate_attribute_report(description=True))
|
show_report(
|
||||||
|
self.generate_attribute_report(description=True),
|
||||||
|
log_report=True)
|
||||||
print_warning(' {} error(s) detected.'.format(attr_type))
|
print_warning(' {} error(s) detected.'.format(attr_type))
|
||||||
if override_disabled:
|
if override_disabled:
|
||||||
print_standard('Tests disabled for this device')
|
print_standard('Tests disabled for this device')
|
||||||
|
|
@ -482,6 +495,7 @@ class State():
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.cpu = None
|
self.cpu = None
|
||||||
self.disks = []
|
self.disks = []
|
||||||
|
self.ost = osTicket(TESTS_CPU, TESTS_DISK)
|
||||||
self.panes = {}
|
self.panes = {}
|
||||||
self.quick_mode = False
|
self.quick_mode = False
|
||||||
self.tests = OrderedDict({
|
self.tests = OrderedDict({
|
||||||
|
|
@ -506,6 +520,7 @@ class State():
|
||||||
'Objects': [],
|
'Objects': [],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
self.ticket_id = None
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
"""Remove test objects, set log, and add devices."""
|
"""Remove test objects, set log, and add devices."""
|
||||||
|
|
@ -566,7 +581,8 @@ class TestObj():
|
||||||
|
|
||||||
def update_status(self, new_status=None):
|
def update_status(self, new_status=None):
|
||||||
"""Update status strings."""
|
"""Update status strings."""
|
||||||
if self.disabled or re.search(r'ERROR|OVERRIDE', self.status):
|
if self.disabled or REGEX_ERROR_STATUS.search(self.status):
|
||||||
|
# Don't update error statuses if test is enabled
|
||||||
return
|
return
|
||||||
if new_status:
|
if new_status:
|
||||||
self.status = build_status_string(
|
self.status = build_status_string(
|
||||||
|
|
@ -603,12 +619,9 @@ def build_outer_panes(state):
|
||||||
def build_status_string(label, status, info_label=False):
|
def build_status_string(label, status, info_label=False):
|
||||||
"""Build status string with appropriate colors."""
|
"""Build status string with appropriate colors."""
|
||||||
status_color = COLORS['CLEAR']
|
status_color = COLORS['CLEAR']
|
||||||
if status in ['Denied', 'ERROR', 'NS', 'OVERRIDE', 'TimedOut']:
|
for k, v in STATUSES.items():
|
||||||
status_color = COLORS['RED']
|
if status in v:
|
||||||
elif status in ['Aborted', 'N/A', 'Skipped', 'Unknown', 'Working']:
|
status_color = COLORS[k]
|
||||||
status_color = COLORS['YELLOW']
|
|
||||||
elif status in ['CS']:
|
|
||||||
status_color = COLORS['GREEN']
|
|
||||||
|
|
||||||
return '{l_c}{l}{CLEAR}{s_c}{s:>{s_w}}{CLEAR}'.format(
|
return '{l_c}{l}{CLEAR}{s_c}{s:>{s_w}}{CLEAR}'.format(
|
||||||
l_c=COLORS['BLUE'] if info_label else '',
|
l_c=COLORS['BLUE'] if info_label else '',
|
||||||
|
|
@ -726,17 +739,6 @@ def get_read_rate(s):
|
||||||
real_rate = convert_to_bytes(human_rate)
|
real_rate = convert_to_bytes(human_rate)
|
||||||
return real_rate
|
return real_rate
|
||||||
|
|
||||||
def get_status_color(s):
|
|
||||||
"""Get color based on status, returns str."""
|
|
||||||
color = COLORS['CLEAR']
|
|
||||||
if s in ['Denied', 'ERROR', 'NS', 'OVERRIDE']:
|
|
||||||
color = COLORS['RED']
|
|
||||||
elif s in ['Aborted', 'N/A', 'Unknown', 'Working', 'Skipped']:
|
|
||||||
color = COLORS['YELLOW']
|
|
||||||
elif s in ['CS']:
|
|
||||||
color = COLORS['GREEN']
|
|
||||||
return color
|
|
||||||
|
|
||||||
def menu_diags(state, args):
|
def menu_diags(state, args):
|
||||||
"""Main menu to select and run HW tests."""
|
"""Main menu to select and run HW tests."""
|
||||||
args = [a.lower() for a in args]
|
args = [a.lower() for a in args]
|
||||||
|
|
@ -746,6 +748,7 @@ def menu_diags(state, args):
|
||||||
{'Base Name': 'Full Diagnostic', 'Enabled': False},
|
{'Base Name': 'Full Diagnostic', 'Enabled': False},
|
||||||
{'Base Name': 'Disk Diagnostic', 'Enabled': False},
|
{'Base Name': 'Disk Diagnostic', 'Enabled': False},
|
||||||
{'Base Name': 'Disk Diagnostic (Quick)', 'Enabled': False},
|
{'Base Name': 'Disk Diagnostic (Quick)', 'Enabled': False},
|
||||||
|
{'Base Name': 'osTicket Integration', 'Enabled': True},
|
||||||
{'Base Name': 'Prime95', 'Enabled': False, 'CRLF': True},
|
{'Base Name': 'Prime95', 'Enabled': False, 'CRLF': True},
|
||||||
{'Base Name': 'NVMe / SMART', 'Enabled': False},
|
{'Base Name': 'NVMe / SMART', 'Enabled': False},
|
||||||
{'Base Name': 'badblocks', 'Enabled': False},
|
{'Base Name': 'badblocks', 'Enabled': False},
|
||||||
|
|
@ -777,11 +780,11 @@ def menu_diags(state, args):
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
# Set quick mode as necessary
|
# Set quick mode as necessary
|
||||||
if main_options[2]['Enabled'] and main_options[4]['Enabled']:
|
if main_options[2]['Enabled'] and main_options[5]['Enabled']:
|
||||||
# Check if only Disk Diags (Quick) and NVMe/SMART are enabled
|
# Check if only Disk Diags (Quick) and NVMe/SMART are enabled
|
||||||
# If so, verify no other tests are enabled and set quick_mode
|
# If so, verify no other tests are enabled and set quick_mode
|
||||||
state.quick_mode = True
|
state.quick_mode = True
|
||||||
for opt in main_options[3:4] + main_options[5:]:
|
for opt in main_options[4:5] + main_options[6:]:
|
||||||
state.quick_mode &= not opt['Enabled']
|
state.quick_mode &= not opt['Enabled']
|
||||||
else:
|
else:
|
||||||
state.quick_mode = False
|
state.quick_mode = False
|
||||||
|
|
@ -795,13 +798,13 @@ def menu_diags(state, args):
|
||||||
|
|
||||||
# Verify preset selections
|
# Verify preset selections
|
||||||
num_tests_selected = 0
|
num_tests_selected = 0
|
||||||
for opt in main_options[3:]:
|
for opt in main_options[4:]:
|
||||||
if opt['Enabled']:
|
if opt['Enabled']:
|
||||||
num_tests_selected += 1
|
num_tests_selected += 1
|
||||||
if num_tests_selected == 4:
|
if num_tests_selected == 4:
|
||||||
# Full
|
# Full
|
||||||
main_options[0]['Enabled'] = True
|
main_options[0]['Enabled'] = True
|
||||||
elif num_tests_selected == 3 and not main_options[3]['Enabled']:
|
elif num_tests_selected == 3 and not main_options[4]['Enabled']:
|
||||||
# Disk
|
# Disk
|
||||||
main_options[1]['Enabled'] = True
|
main_options[1]['Enabled'] = True
|
||||||
|
|
||||||
|
|
@ -946,6 +949,10 @@ def run_hw_tests(state):
|
||||||
"""Run enabled hardware tests."""
|
"""Run enabled hardware tests."""
|
||||||
print_standard('Scanning devices...')
|
print_standard('Scanning devices...')
|
||||||
state.init()
|
state.init()
|
||||||
|
tests_enabled = False
|
||||||
|
|
||||||
|
# Disable osTicket Integration if in quick mode
|
||||||
|
state.ost.disabled |= state.quick_mode
|
||||||
|
|
||||||
# Build Panes
|
# Build Panes
|
||||||
update_progress_pane(state)
|
update_progress_pane(state)
|
||||||
|
|
@ -961,6 +968,8 @@ def run_hw_tests(state):
|
||||||
COLORS['CLEAR'],
|
COLORS['CLEAR'],
|
||||||
QUICK_LABEL if state.quick_mode and 'NVMe' in k else ''))
|
QUICK_LABEL if state.quick_mode and 'NVMe' in k else ''))
|
||||||
if v['Enabled']:
|
if v['Enabled']:
|
||||||
|
tests_enabled = True
|
||||||
|
|
||||||
# Create TestObj and track under both CpuObj/DiskObj and State
|
# Create TestObj and track under both CpuObj/DiskObj and State
|
||||||
if k in TESTS_CPU:
|
if k in TESTS_CPU:
|
||||||
test_obj = TestObj(
|
test_obj = TestObj(
|
||||||
|
|
@ -974,6 +983,15 @@ def run_hw_tests(state):
|
||||||
v['Objects'].append(test_obj)
|
v['Objects'].append(test_obj)
|
||||||
print_standard('')
|
print_standard('')
|
||||||
|
|
||||||
|
# Bail if no tests selected
|
||||||
|
if not tests_enabled:
|
||||||
|
tmux_kill_pane(*state.panes.values())
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get ticket_number
|
||||||
|
if not state.ost.disabled:
|
||||||
|
state.ticket_id = state.ost.get_ticket_number()
|
||||||
|
|
||||||
# Run disk safety checks (if necessary)
|
# Run disk safety checks (if necessary)
|
||||||
_disk_tests_enabled = False
|
_disk_tests_enabled = False
|
||||||
for k in TESTS_DISK:
|
for k in TESTS_DISK:
|
||||||
|
|
@ -991,6 +1009,9 @@ def run_hw_tests(state):
|
||||||
f = v['Function']
|
f = v['Function']
|
||||||
for test_obj in v['Objects']:
|
for test_obj in v['Objects']:
|
||||||
f(state, test_obj)
|
f(state, test_obj)
|
||||||
|
if k == TESTS_CPU[-1]:
|
||||||
|
# Last CPU test run, post CPU results
|
||||||
|
state.ost.post_device_results(state.cpu, state.ticket_id)
|
||||||
except GenericAbort:
|
except GenericAbort:
|
||||||
# Cleanup
|
# Cleanup
|
||||||
tmux_kill_pane(*state.panes.values())
|
tmux_kill_pane(*state.panes.values())
|
||||||
|
|
@ -1009,14 +1030,48 @@ def run_hw_tests(state):
|
||||||
# Update side pane
|
# Update side pane
|
||||||
update_progress_pane(state)
|
update_progress_pane(state)
|
||||||
|
|
||||||
# Done
|
# Show results
|
||||||
show_results(state)
|
show_results(state)
|
||||||
|
|
||||||
|
# Post disk results
|
||||||
|
if not state.ost.disabled:
|
||||||
|
print_standard('Posting results to osTicket...')
|
||||||
|
for disk in state.disks:
|
||||||
|
state.ost.post_device_results(disk, state.ticket_id)
|
||||||
|
|
||||||
|
# Check if disk checkbox needs updating
|
||||||
|
all_disks_passed = True
|
||||||
|
disk_failures = False
|
||||||
|
for disk in state.disks:
|
||||||
|
if disk.checkbox is None:
|
||||||
|
# Aborted/Unknown/etc
|
||||||
|
all_disks_passed = False
|
||||||
|
else:
|
||||||
|
all_disks_passed &= disk.checkbox
|
||||||
|
disk_failures |= not disk.checkbox
|
||||||
|
|
||||||
|
# Update checkbox if necessary
|
||||||
|
if disk_failures:
|
||||||
|
state.ost.set_disk_failed(state.ticket_id)
|
||||||
|
elif all_disks_passed:
|
||||||
|
state.ost.set_disk_passed(state.ticket_id)
|
||||||
|
|
||||||
|
# Spacer
|
||||||
|
print_standard(' ')
|
||||||
|
|
||||||
|
# Check for osTicket errors
|
||||||
|
if state.ost.errors:
|
||||||
|
print_warning('Errors encountered posting results to osTicket.')
|
||||||
|
print_standard(' ')
|
||||||
|
|
||||||
|
# Done
|
||||||
if state.quick_mode:
|
if state.quick_mode:
|
||||||
pause('Press Enter to exit...')
|
pause('Press Enter to exit...')
|
||||||
else:
|
else:
|
||||||
pause('Press Enter to return to main menu... ')
|
pause('Press Enter to return to main menu... ')
|
||||||
|
|
||||||
# Cleanup
|
# Cleanup
|
||||||
|
state.ost.disconnect(full=True)
|
||||||
tmux_kill_pane(*state.panes.values())
|
tmux_kill_pane(*state.panes.values())
|
||||||
|
|
||||||
def run_io_benchmark(state, test):
|
def run_io_benchmark(state, test):
|
||||||
|
|
@ -1060,7 +1115,6 @@ def run_io_benchmark(state, test):
|
||||||
try:
|
try:
|
||||||
test.merged_rates = []
|
test.merged_rates = []
|
||||||
test.read_rates = []
|
test.read_rates = []
|
||||||
test.vertical_graph = []
|
|
||||||
test.dev.calc_io_dd_values()
|
test.dev.calc_io_dd_values()
|
||||||
|
|
||||||
# Run dd read tests
|
# Run dd read tests
|
||||||
|
|
@ -1087,10 +1141,6 @@ def run_io_benchmark(state, test):
|
||||||
|
|
||||||
# Add rate to lists
|
# Add rate to lists
|
||||||
test.read_rates.append(cur_rate)
|
test.read_rates.append(cur_rate)
|
||||||
test.vertical_graph.append(
|
|
||||||
'{percent:0.1f} {rate}'.format(
|
|
||||||
percent=(i/test.dev.dd_chunks)*100,
|
|
||||||
rate=int(cur_rate/(1024**2))))
|
|
||||||
|
|
||||||
# Show progress
|
# Show progress
|
||||||
if i % IO_VARS['Progress Refresh Rate'] == 0:
|
if i % IO_VARS['Progress Refresh Rate'] == 0:
|
||||||
|
|
@ -1175,10 +1225,6 @@ def run_io_benchmark(state, test):
|
||||||
elif not 'N/A' in test.status:
|
elif not 'N/A' in test.status:
|
||||||
test.update_status('Unknown')
|
test.update_status('Unknown')
|
||||||
|
|
||||||
# Save log
|
|
||||||
with open(test.io_benchmark_out.replace('.', '-raw.'), 'a') as f:
|
|
||||||
f.write('\n'.join(test.vertical_graph))
|
|
||||||
|
|
||||||
# Done
|
# Done
|
||||||
update_progress_pane(state)
|
update_progress_pane(state)
|
||||||
|
|
||||||
|
|
@ -1453,8 +1499,6 @@ def run_nvme_smart_tests(state, test):
|
||||||
|
|
||||||
# Show attributes
|
# Show attributes
|
||||||
clear_screen()
|
clear_screen()
|
||||||
print_info('Device ({})'.format(test.dev.name))
|
|
||||||
print_standard(' {}'.format(test.dev.description))
|
|
||||||
show_report(test.dev.generate_attribute_report())
|
show_report(test.dev.generate_attribute_report())
|
||||||
print_standard(' ')
|
print_standard(' ')
|
||||||
|
|
||||||
|
|
@ -1518,7 +1562,7 @@ def run_nvme_smart_tests(state, test):
|
||||||
test.update_status('TimedOut')
|
test.update_status('TimedOut')
|
||||||
|
|
||||||
# Disable other drive tests if necessary
|
# Disable other drive tests if necessary
|
||||||
if not test.passed:
|
if test.failed:
|
||||||
for t in ['badblocks', 'I/O Benchmark']:
|
for t in ['badblocks', 'I/O Benchmark']:
|
||||||
test.dev.disable_test(t, 'Denied')
|
test.dev.disable_test(t, 'Denied')
|
||||||
|
|
||||||
|
|
@ -1542,10 +1586,11 @@ def secret_screensaver(screensaver=None):
|
||||||
raise Exception('Invalid screensaver')
|
raise Exception('Invalid screensaver')
|
||||||
run_program(cmd, check=False, pipe=False)
|
run_program(cmd, check=False, pipe=False)
|
||||||
|
|
||||||
def show_report(report):
|
def show_report(report, log_report=False):
|
||||||
"""Show report on screen and save to log w/out color."""
|
"""Show report on screen and optionally save to log w/out color."""
|
||||||
for line in report:
|
for line in report:
|
||||||
print(line)
|
print(line)
|
||||||
|
if log_report:
|
||||||
print_log(strip_colors(line))
|
print_log(strip_colors(line))
|
||||||
|
|
||||||
def show_results(state):
|
def show_results(state):
|
||||||
|
|
@ -1561,7 +1606,7 @@ def show_results(state):
|
||||||
_enabled |= state.tests[k]['Enabled']
|
_enabled |= state.tests[k]['Enabled']
|
||||||
if _enabled:
|
if _enabled:
|
||||||
print_success('CPU:'.format(k))
|
print_success('CPU:'.format(k))
|
||||||
show_report(state.cpu.generate_cpu_report())
|
show_report(state.cpu.generate_cpu_report(), log_report=True)
|
||||||
print_standard(' ')
|
print_standard(' ')
|
||||||
|
|
||||||
# Disk tests
|
# Disk tests
|
||||||
|
|
@ -1572,7 +1617,7 @@ def show_results(state):
|
||||||
print_success('Disk{}:'.format(
|
print_success('Disk{}:'.format(
|
||||||
'' if len(state.disks) == 1 else 's'))
|
'' if len(state.disks) == 1 else 's'))
|
||||||
for disk in state.disks:
|
for disk in state.disks:
|
||||||
show_report(disk.generate_disk_report())
|
show_report(disk.generate_disk_report(), log_report=True)
|
||||||
print_standard(' ')
|
print_standard(' ')
|
||||||
|
|
||||||
def update_main_options(state, selection, main_options):
|
def update_main_options(state, selection, main_options):
|
||||||
|
|
@ -1586,33 +1631,34 @@ def update_main_options(state, selection, main_options):
|
||||||
if main_options[index]['Enabled']:
|
if main_options[index]['Enabled']:
|
||||||
for opt in main_options[1:3]:
|
for opt in main_options[1:3]:
|
||||||
opt['Enabled'] = False
|
opt['Enabled'] = False
|
||||||
for opt in main_options[3:]:
|
for opt in main_options[4:]:
|
||||||
opt['Enabled'] = True
|
opt['Enabled'] = True
|
||||||
else:
|
else:
|
||||||
for opt in main_options[3:]:
|
for opt in main_options[4:]:
|
||||||
opt['Enabled'] = False
|
opt['Enabled'] = False
|
||||||
elif index == 1:
|
elif index == 1:
|
||||||
# Disk
|
# Disk
|
||||||
if main_options[index]['Enabled']:
|
if main_options[index]['Enabled']:
|
||||||
main_options[0]['Enabled'] = False
|
main_options[0]['Enabled'] = False
|
||||||
for opt in main_options[2:4]:
|
main_options[2]['Enabled'] = False
|
||||||
opt['Enabled'] = False
|
main_options[4]['Enabled'] = False
|
||||||
for opt in main_options[4:]:
|
for opt in main_options[5:]:
|
||||||
opt['Enabled'] = True
|
opt['Enabled'] = True
|
||||||
else:
|
else:
|
||||||
for opt in main_options[4:]:
|
for opt in main_options[5:]:
|
||||||
opt['Enabled'] = False
|
opt['Enabled'] = False
|
||||||
elif index == 2:
|
elif index == 2:
|
||||||
# Disk (Quick)
|
# Disk (Quick)
|
||||||
if main_options[index]['Enabled']:
|
if main_options[index]['Enabled']:
|
||||||
for opt in main_options[:2] + main_options[3:]:
|
for opt in main_options[:2] + main_options[4:]:
|
||||||
opt['Enabled'] = False
|
opt['Enabled'] = False
|
||||||
main_options[4]['Enabled'] = True
|
main_options[5]['Enabled'] = True
|
||||||
else:
|
else:
|
||||||
main_options[4]['Enabled'] = False
|
main_options[5]['Enabled'] = False
|
||||||
|
|
||||||
# Update state
|
# Update state
|
||||||
for opt in main_options[3:]:
|
state.ost.disabled = not main_options[3]['Enabled']
|
||||||
|
for opt in main_options[4:]:
|
||||||
state.tests[opt['Base Name']]['Enabled'] = opt['Enabled']
|
state.tests[opt['Base Name']]['Enabled'] = opt['Enabled']
|
||||||
|
|
||||||
# Done
|
# Done
|
||||||
|
|
|
||||||
113
.bin/Scripts/functions/io_graph.py
Normal file
113
.bin/Scripts/functions/io_graph.py
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
# Wizard Kit: Functions - PNG graph for I/O Benchmark
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import Gnuplot
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from functions.common import *
|
||||||
|
|
||||||
|
# Functions
|
||||||
|
def export_io_graph(disk):
|
||||||
|
"""Exports PNG graph using gnuplot, returns file path as str."""
|
||||||
|
read_rates = disk.tests['I/O Benchmark'].read_rates
|
||||||
|
max_rate = max(read_rates) / (1024**2)
|
||||||
|
max_rate = max(800, max_rate)
|
||||||
|
out_path = '{}/iobenchmark-{}.png'.format(
|
||||||
|
global_vars['LogDir'], disk.name)
|
||||||
|
plot_data = '{}/iobenchmark-{}-plot.data'.format(
|
||||||
|
global_vars['LogDir'], disk.name)
|
||||||
|
|
||||||
|
# Adjust Y-axis range to either 1000 or roughly max rate + 200
|
||||||
|
## Round up to the nearest 100 and then add 200
|
||||||
|
y_range = (math.ceil(max_rate/100)*100) + 200
|
||||||
|
|
||||||
|
# Save plot data to file for Gnuplot
|
||||||
|
with open(plot_data, 'w') as f:
|
||||||
|
for i in range(len(read_rates)):
|
||||||
|
_percent = ( (i+1) / len(read_rates) ) * 100
|
||||||
|
_rate = int( read_rates[i] / (1024**2) )
|
||||||
|
f.write('{:0.1f} {}\n'.format(_percent, _rate))
|
||||||
|
|
||||||
|
# Run gnuplot commands
|
||||||
|
g = Gnuplot.Gnuplot()
|
||||||
|
g('reset')
|
||||||
|
g('set output "{}"'.format(out_path))
|
||||||
|
g('set terminal png large size 660,300 truecolor font "Noto Sans,11"')
|
||||||
|
g('set title "I/O Benchmark"')
|
||||||
|
g('set yrange [0:{}]'.format(y_range))
|
||||||
|
g('set style data lines')
|
||||||
|
g('plot "{}" title "{}"'.format(
|
||||||
|
plot_data,
|
||||||
|
disk.description.replace('_', ' '),
|
||||||
|
))
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
g.close()
|
||||||
|
del(g)
|
||||||
|
|
||||||
|
return out_path
|
||||||
|
|
||||||
|
def upload_to_imgur(image_path):
|
||||||
|
"""Upload image to Imgur and return image url as str."""
|
||||||
|
image_data = None
|
||||||
|
image_link = None
|
||||||
|
|
||||||
|
# Bail early
|
||||||
|
if not image_path:
|
||||||
|
raise GenericError
|
||||||
|
|
||||||
|
# Read image file and convert to base64 then convert to str
|
||||||
|
with open(image_path, 'rb') as f:
|
||||||
|
image_data = base64.b64encode(f.read()).decode()
|
||||||
|
|
||||||
|
# POST image
|
||||||
|
url = "https://api.imgur.com/3/image"
|
||||||
|
boundary = '----WebKitFormBoundary7MA4YWxkTrZu0gW'
|
||||||
|
payload = ('--{boundary}\r\nContent-Disposition: form-data; '
|
||||||
|
'name="image"\r\n\r\n{data}\r\n--{boundary}--')
|
||||||
|
payload = payload.format(boundary=boundary, data=image_data)
|
||||||
|
headers = {
|
||||||
|
'content-type': 'multipart/form-data; boundary={}'.format(boundary),
|
||||||
|
'Authorization': 'Client-ID {}'.format(IMGUR_CLIENT_ID),
|
||||||
|
}
|
||||||
|
response = requests.request("POST", url, data=payload, headers=headers)
|
||||||
|
|
||||||
|
# Return image link
|
||||||
|
if response.ok:
|
||||||
|
json_data = json.loads(response.text)
|
||||||
|
image_link = json_data['data']['link']
|
||||||
|
return image_link
|
||||||
|
|
||||||
|
def upload_to_nextcloud(image_path, ticket_number, dev_name):
|
||||||
|
"""Upload image to Nextcloud server and return folder url as str."""
|
||||||
|
image_data = None
|
||||||
|
|
||||||
|
# Bail early
|
||||||
|
if not image_path:
|
||||||
|
raise GenericError
|
||||||
|
|
||||||
|
# Read image file and convert to base64
|
||||||
|
with open(image_path, 'rb') as f:
|
||||||
|
image_data = f.read()
|
||||||
|
|
||||||
|
# PUT image
|
||||||
|
url = '{base_url}/{ticket_number}_iobenchmark_{dev_name}_{date}.png'.format(
|
||||||
|
base_url=BENCHMARK_SERVER['Url'],
|
||||||
|
ticket_number=ticket_number,
|
||||||
|
dev_name=dev_name,
|
||||||
|
date=global_vars.get('Date-Time', 'Unknown Date-Time'))
|
||||||
|
requests.put(
|
||||||
|
url,
|
||||||
|
data=image_data,
|
||||||
|
headers = {'X-Requested-With': 'XMLHttpRequest'},
|
||||||
|
auth = (BENCHMARK_SERVER['User'], BENCHMARK_SERVER['Pass']))
|
||||||
|
|
||||||
|
# Return folder link
|
||||||
|
return BENCHMARK_SERVER['Short Url']
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print("This file is not meant to be called directly.")
|
||||||
|
|
||||||
|
# vim: sts=2 sw=2 ts=2
|
||||||
486
.bin/Scripts/functions/osticket.py
Normal file
486
.bin/Scripts/functions/osticket.py
Normal file
|
|
@ -0,0 +1,486 @@
|
||||||
|
# Wizard Kit: Functions - osTicket
|
||||||
|
|
||||||
|
import mysql.connector as mariadb
|
||||||
|
|
||||||
|
from functions.data import *
|
||||||
|
from functions.io_graph import *
|
||||||
|
from settings.osticket import *
|
||||||
|
|
||||||
|
# STATIC VARIABLES
|
||||||
|
KNOWN_DEV_TYPES = ('CPU', 'Disk')
|
||||||
|
|
||||||
|
# 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.*)$')
|
||||||
|
REGEX_SENSOR = re.compile(r'^(.*?)(\s*)$')
|
||||||
|
|
||||||
|
# Classes
|
||||||
|
class osTicket():
|
||||||
|
"""Class to track osTicket data and functions."""
|
||||||
|
def __init__(self, tests_cpu, tests_disk):
|
||||||
|
self.db_connection = None
|
||||||
|
self.db_cursor = None
|
||||||
|
self.disabled = False
|
||||||
|
self.errors = False
|
||||||
|
self.tests_cpu = tests_cpu
|
||||||
|
self.tests_disk = tests_disk
|
||||||
|
self.tunnel_proc = None
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
"""Establish connection to osTicket via a SSH tunnel."""
|
||||||
|
cmd = [
|
||||||
|
'ssh', '-N',
|
||||||
|
'-p', OSTICKET['SSH']['Port'],
|
||||||
|
'-L3306:127.0.0.1:{Port}'.format(**OSTICKET['Database']),
|
||||||
|
'{User}@{Host}'.format(**OSTICKET['SSH']),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Bail if disabled
|
||||||
|
if self.disabled:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Only open tunnel if one doesn't exist
|
||||||
|
if self.tunnel_proc is None or self.tunnel_proc.poll() is not None:
|
||||||
|
self.tunnel_proc = popen_program(cmd)
|
||||||
|
|
||||||
|
# Connect to database
|
||||||
|
for x in range(5):
|
||||||
|
sleep(2)
|
||||||
|
try:
|
||||||
|
self.db_connection = mariadb.connect(
|
||||||
|
user=OSTICKET['Database']['User'],
|
||||||
|
password=OSTICKET['Database']['Pass'],
|
||||||
|
database=OSTICKET['Database']['Name'],
|
||||||
|
)
|
||||||
|
self.db_cursor = self.db_connection.cursor()
|
||||||
|
except mariadb.errors.InterfaceError:
|
||||||
|
# SSH issue?, try again
|
||||||
|
pass
|
||||||
|
except mariadb.errors.Error:
|
||||||
|
# Bad creds or other SQL error, bail
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
# Unknown error
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# Connection established
|
||||||
|
break
|
||||||
|
|
||||||
|
# Disable if necessary
|
||||||
|
if self.db_cursor is None:
|
||||||
|
self.disabled = True
|
||||||
|
self.tunnel_proc.kill()
|
||||||
|
|
||||||
|
def convert_report(self, name, test):
|
||||||
|
"""Convert report into an osTicket friendly format, returns list."""
|
||||||
|
out_report = []
|
||||||
|
source_report = test.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_report()
|
||||||
|
elif not source_report:
|
||||||
|
index = 0
|
||||||
|
out_report.append('{} ({})'.format(name, status))
|
||||||
|
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)
|
||||||
|
r2 = REGEX_SENSOR.match(_sensor)
|
||||||
|
_sensor = r2.group(1)
|
||||||
|
_spacer = pad_with_dots(r2.group(2))
|
||||||
|
line = '{}{} {}'.format(_sensor, _spacer, _temps)
|
||||||
|
if line == 'Temps':
|
||||||
|
out_report.append(' ')
|
||||||
|
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 disconnect(self, full=False):
|
||||||
|
"""Close osTicket connection."""
|
||||||
|
try:
|
||||||
|
self.db_cursor.close()
|
||||||
|
self.db_connection.close()
|
||||||
|
except Exception:
|
||||||
|
# Ignore errors since vars will be reset below
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Reset errors
|
||||||
|
if full:
|
||||||
|
self.errors = False
|
||||||
|
|
||||||
|
# Reset vars
|
||||||
|
self.db_cursor = None
|
||||||
|
self.db_connection = None
|
||||||
|
|
||||||
|
# Close tunnel
|
||||||
|
if full:
|
||||||
|
try:
|
||||||
|
self.tunnel_proc.kill()
|
||||||
|
self.tunnel_proc.wait(timeout=2)
|
||||||
|
except (AttributeError, subprocess.TimeoutExpired):
|
||||||
|
# Ignore and continue
|
||||||
|
pass
|
||||||
|
self.tunnel_proc = None
|
||||||
|
|
||||||
|
def generate_report(self, dev, ticket_id):
|
||||||
|
"""Generate device report for osTicket, returns list."""
|
||||||
|
report = []
|
||||||
|
results = self.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(self.convert_report(name, test))
|
||||||
|
if name == 'I/O Benchmark':
|
||||||
|
# Create PNG graph
|
||||||
|
try:
|
||||||
|
graph_file = export_io_graph(dev)
|
||||||
|
except (AttributeError, KeyError):
|
||||||
|
report.append('Failed to export graph')
|
||||||
|
else:
|
||||||
|
# Upload to Imgur
|
||||||
|
try:
|
||||||
|
url = upload_to_imgur(graph_file)
|
||||||
|
report.append('Imgur: {}'.format(url))
|
||||||
|
except Exception:
|
||||||
|
report.append('Imgur: Failed to upload graph')
|
||||||
|
|
||||||
|
# Upload to Nextcloud
|
||||||
|
try:
|
||||||
|
url = upload_to_nextcloud(graph_file, ticket_id, dev.name)
|
||||||
|
report.append('Nextcloud: {}'.format(url))
|
||||||
|
except Exception:
|
||||||
|
report.append('Nextcloud: Failed to upload graph')
|
||||||
|
report.append(' ')
|
||||||
|
|
||||||
|
# Volumes
|
||||||
|
if results['Dev Type'] == 'Disk' and results['Full Diag']:
|
||||||
|
# Mount all volumes and extend report
|
||||||
|
report.append('Volumes:')
|
||||||
|
report.extend(self.generate_volume_report(dev, results))
|
||||||
|
report.append(' ')
|
||||||
|
|
||||||
|
# Asterisk
|
||||||
|
if results['Asterisk']:
|
||||||
|
report.append('* NOTE: One or more tests were not run on this device')
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return report
|
||||||
|
|
||||||
|
def generate_volume_report(self, dev, results):
|
||||||
|
"""Mount all volumes for dev and generate report, returns list."""
|
||||||
|
report = []
|
||||||
|
mount_report = mount_volumes(
|
||||||
|
all_devices=False,
|
||||||
|
device_path='{}'.format(dev.path),
|
||||||
|
core_storage=results['Core'])
|
||||||
|
|
||||||
|
# Format report
|
||||||
|
for v_path, v_data in sorted(mount_report.items()):
|
||||||
|
label = v_data.get('label', '')
|
||||||
|
if label:
|
||||||
|
label = '"{}"'.format(label)
|
||||||
|
else:
|
||||||
|
# Ensure string type
|
||||||
|
label = ''
|
||||||
|
size = v_data.get('size', '')
|
||||||
|
if size:
|
||||||
|
size = '{} {}B'.format(size[:-1], size[-1:]).upper()
|
||||||
|
else:
|
||||||
|
size = 'UNKNOWN'
|
||||||
|
size_used = v_data.get('size_used', 'UNKNOWN').upper()
|
||||||
|
size_avail = v_data.get('size_avail', 'UNKNOWN').upper()
|
||||||
|
v_data = [v_path, label, size, size_used, size_avail]
|
||||||
|
v_data = [v.strip().replace(' ', '_') for v in v_data]
|
||||||
|
for i in range(len(v_data)):
|
||||||
|
pad = 8
|
||||||
|
if i < 2:
|
||||||
|
pad += 4 * (2 - i)
|
||||||
|
v_data[i] = pad_with_dots(
|
||||||
|
'{s:<{p}}'.format(s=v_data[i], p=pad),
|
||||||
|
pad_right=True)
|
||||||
|
v_data[-1] = re.sub(r'\.*$', '', v_data[-1])
|
||||||
|
v_data = [v.replace('_', ' ') for v in v_data]
|
||||||
|
report.append(
|
||||||
|
'{}..{}..Total..{}..(Used..{}..Free..{})'.format(*v_data))
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return report
|
||||||
|
|
||||||
|
def get_device_overall_results(self, dev):
|
||||||
|
"""Get overall results from tests for device, returns dict."""
|
||||||
|
results = {
|
||||||
|
'Core': False,
|
||||||
|
'Dev Type': self.get_device_type(dev),
|
||||||
|
'Full Diag': False,
|
||||||
|
'Asterisk': None,
|
||||||
|
'Failed': 0,
|
||||||
|
'N/A': 0,
|
||||||
|
'OVERRIDE': 0,
|
||||||
|
'Passed': 0,
|
||||||
|
'Status': 'Unknown',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Bail on unknown device type
|
||||||
|
if results['Dev Type'] not in KNOWN_DEV_TYPES:
|
||||||
|
raise GenericError(
|
||||||
|
'Unrecognized device type: {}.'.format(results['Dev Type']))
|
||||||
|
|
||||||
|
# Get test list for device type
|
||||||
|
test_list = []
|
||||||
|
if results['Dev Type'] == 'CPU':
|
||||||
|
test_list = self.tests_cpu
|
||||||
|
elif results['Dev Type'] == 'Disk':
|
||||||
|
test_list = self.tests_disk
|
||||||
|
|
||||||
|
# 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.values():
|
||||||
|
if test.failed:
|
||||||
|
results['Failed'] += 1
|
||||||
|
if test.passed:
|
||||||
|
results['Passed'] += 1
|
||||||
|
if 'N/A' in test.status:
|
||||||
|
results['N/A'] += 1
|
||||||
|
if 'OVERRIDE' in test.status:
|
||||||
|
results['OVERRIDE'] += 1
|
||||||
|
|
||||||
|
# Set overall status
|
||||||
|
if results['Failed'] > 0:
|
||||||
|
dev.checkbox = False
|
||||||
|
results['Status'] = 'FAILED'
|
||||||
|
elif results['Passed'] + results['N/A'] == len(test_list):
|
||||||
|
# Only mark true if all tests are enabled and all are "Passed" / "N/A"
|
||||||
|
dev.checkbox = True
|
||||||
|
results['Status'] = 'PASSED'
|
||||||
|
else:
|
||||||
|
results['Status'] = 'UNKNOWN'
|
||||||
|
if results['Full Diag'] and results['N/A'] > 0:
|
||||||
|
results['Asterisk'] = True
|
||||||
|
results['Status'] += '*'
|
||||||
|
|
||||||
|
# Enable CoreStorage searches
|
||||||
|
results['Core'] = (results['Full Diag'] and
|
||||||
|
results['Passed']+results['N/A']+results['OVERRIDE'] == len(test_list))
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return results
|
||||||
|
|
||||||
|
def get_device_type(self, dev):
|
||||||
|
"""Terrible hack to determine device type, returns str."""
|
||||||
|
# TODO: Fix with proper isinstance() call
|
||||||
|
type_str = str(dev.__class__)
|
||||||
|
if 'CpuObj' in type_str:
|
||||||
|
type_str = 'CPU'
|
||||||
|
elif 'DiskObj' in type_str:
|
||||||
|
type_str = 'Disk'
|
||||||
|
|
||||||
|
return type_str
|
||||||
|
|
||||||
|
def get_ticket_name(self, ticket_id):
|
||||||
|
"""Lookup ticket and return name as str."""
|
||||||
|
name = None
|
||||||
|
sql_cmd = "SELECT name FROM `{Ticket}`".format(**OSTICKET['Tables'])
|
||||||
|
sql_cmd += " WHERE `ticket_id` = {}".format(ticket_id)
|
||||||
|
sql_cmd += ";"
|
||||||
|
|
||||||
|
# Lookup name
|
||||||
|
# NOTE: If multiple entries are found it will return the last
|
||||||
|
self.db_cursor.execute(sql_cmd)
|
||||||
|
for s in self.db_cursor:
|
||||||
|
name = s[0]
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return name
|
||||||
|
|
||||||
|
def get_ticket_number(self):
|
||||||
|
"""Get ticket number and confirm with name from osTicket DB."""
|
||||||
|
ticket_number = None
|
||||||
|
self.connect()
|
||||||
|
|
||||||
|
# Bail if disabled
|
||||||
|
if self.disabled:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Main loop
|
||||||
|
while ticket_number is None:
|
||||||
|
print_standard(' ')
|
||||||
|
_input = input('Enter ticket number (or leave blank to disable): ')
|
||||||
|
_input = _input.strip()
|
||||||
|
|
||||||
|
# No ticket ID entered
|
||||||
|
if re.match(r'^\s*$', _input):
|
||||||
|
if ask('Disable osTicket integration for this run?'):
|
||||||
|
break
|
||||||
|
|
||||||
|
# Invalid ID entered
|
||||||
|
if not re.match(r'^(\d+)$', _input):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Valid ID entered, lookup name and verify
|
||||||
|
try:
|
||||||
|
_name = self.get_ticket_name(_input)
|
||||||
|
except Exception:
|
||||||
|
# Ignore and return None below
|
||||||
|
break
|
||||||
|
if _name:
|
||||||
|
print_standard('You have selected ticket #{} {}'.format(
|
||||||
|
_input, _name))
|
||||||
|
if ask('Is this correct?'):
|
||||||
|
ticket_number = _input
|
||||||
|
|
||||||
|
# Done
|
||||||
|
self.disconnect()
|
||||||
|
return ticket_number
|
||||||
|
|
||||||
|
def post_device_results(self, dev, ticket_id):
|
||||||
|
"""Generate osTicket friendly report and post as response to ticket."""
|
||||||
|
if not dev.tests:
|
||||||
|
# No test results available, aborting post
|
||||||
|
return
|
||||||
|
response = self.generate_report(dev, ticket_id)
|
||||||
|
self.post_response(response, ticket_id)
|
||||||
|
|
||||||
|
def post_response(self, response, ticket_id):
|
||||||
|
"""Post a reply to a ticket in osTicket."""
|
||||||
|
self.connect()
|
||||||
|
|
||||||
|
# Bail if disabled
|
||||||
|
if self.disabled:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Convert response to string
|
||||||
|
response = '\n'.join(response)
|
||||||
|
response = response.replace('`', '')
|
||||||
|
|
||||||
|
# Build SQL cmd
|
||||||
|
sql_cmd = "INSERT INTO `{Name}`.`{Response}`".format(
|
||||||
|
**OSTICKET['Database'], **OSTICKET['Tables'])
|
||||||
|
sql_cmd += " (ticket_id, staff_id, staff_name, response, created)"
|
||||||
|
sql_cmd += " VALUES ("
|
||||||
|
sql_cmd += " '{}',".format(ticket_id)
|
||||||
|
sql_cmd += " '{ID}', '{Name}',".format(**OSTICKET['Staff'])
|
||||||
|
sql_cmd += " '{}',".format(response)
|
||||||
|
sql_cmd += " '{}'".format(time.strftime("%Y-%m-%d %H:%M:%S"))
|
||||||
|
sql_cmd += " );"
|
||||||
|
|
||||||
|
# Run SQL cmd
|
||||||
|
try:
|
||||||
|
self.db_cursor.execute(sql_cmd)
|
||||||
|
except mariadb.errors.Error:
|
||||||
|
# Set self.errors to enable warning line on results screen
|
||||||
|
self.errors = True
|
||||||
|
|
||||||
|
# Done
|
||||||
|
self.disconnect()
|
||||||
|
|
||||||
|
def set_disk_failed(self, ticket_id):
|
||||||
|
"""Mark disk as failed in osTicket."""
|
||||||
|
self.set_flag(
|
||||||
|
ticket_id,
|
||||||
|
OSTICKET['Disk Flag']['Name'],
|
||||||
|
OSTICKET['Disk Flag']['Fail'])
|
||||||
|
|
||||||
|
def set_disk_passed(self, ticket_id):
|
||||||
|
"""Mark disk as passed in osTicket."""
|
||||||
|
self.set_flag(
|
||||||
|
ticket_id,
|
||||||
|
OSTICKET['Disk Flag']['Name'],
|
||||||
|
OSTICKET['Disk Flag']['Pass'])
|
||||||
|
|
||||||
|
def set_flag(self, ticket_id, flag_name, flag_value):
|
||||||
|
"""Set flag in osTicket."""
|
||||||
|
self.connect()
|
||||||
|
|
||||||
|
# Bail if disabled
|
||||||
|
if self.disabled:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Build SQL cmd
|
||||||
|
sql_cmd = "UPDATE `{Name}`.`{Ticket}`".format(
|
||||||
|
**OSTICKET['Database'], **OSTICKET['Tables'])
|
||||||
|
sql_cmd += " SET `{}` = '{}'".format(flag_name, flag_value)
|
||||||
|
sql_cmd += " WHERE `{Ticket}`.`ticket_id` = {ticket_id}".format(
|
||||||
|
ticket_id=ticket_id, **OSTICKET['Tables'])
|
||||||
|
sql_cmd += ";"
|
||||||
|
|
||||||
|
# Run SQL cmd
|
||||||
|
try:
|
||||||
|
self.db_cursor.execute(sql_cmd)
|
||||||
|
except mariadb.errors.Error:
|
||||||
|
# Set self.errors to enable warning line on results screen
|
||||||
|
self.errors = True
|
||||||
|
|
||||||
|
# Done
|
||||||
|
self.disconnect()
|
||||||
|
|
||||||
|
# Functions
|
||||||
|
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.")
|
||||||
|
|
||||||
|
# vim: sts=2 sw=2 ts=2
|
||||||
|
|
@ -14,13 +14,6 @@ ARCHIVE_PASSWORD='Sorted1201'
|
||||||
KIT_NAME_FULL='1201-WizardKit'
|
KIT_NAME_FULL='1201-WizardKit'
|
||||||
KIT_NAME_SHORT='1201'
|
KIT_NAME_SHORT='1201'
|
||||||
SUPPORT_MESSAGE='Please let support know by opening an issue on Gogs'
|
SUPPORT_MESSAGE='Please let support know by opening an issue on Gogs'
|
||||||
# osTicket
|
|
||||||
DB_HOST='osticket.1201.com'
|
|
||||||
DB_NAME='osticket'
|
|
||||||
DB_USER='wizardkit'
|
|
||||||
DB_PASS='U9bJnF9eamVkfsVw'
|
|
||||||
SSH_PORT='22'
|
|
||||||
SSH_USER='sql_tunnel'
|
|
||||||
# imgur
|
# imgur
|
||||||
IMGUR_CLIENT_ID='3d1ee1d38707b85'
|
IMGUR_CLIENT_ID='3d1ee1d38707b85'
|
||||||
# Live Linux
|
# Live Linux
|
||||||
|
|
|
||||||
33
.bin/Scripts/settings/osticket.py
Normal file
33
.bin/Scripts/settings/osticket.py
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Wizard Kit: Settings - osTicket
|
||||||
|
|
||||||
|
OSTICKET = {
|
||||||
|
'Database': {
|
||||||
|
'Name': 'osticket',
|
||||||
|
'User': 'wizardkit',
|
||||||
|
'Pass': 'U9bJnF9eamVkfsVw',
|
||||||
|
'Port': '3306',
|
||||||
|
},
|
||||||
|
'Disk Flag': {
|
||||||
|
'Name': 'zHDTune',
|
||||||
|
'Pass': 1,
|
||||||
|
'Fail': 2,
|
||||||
|
},
|
||||||
|
'SSH': {
|
||||||
|
'Host': 'osticket.1201.com',
|
||||||
|
'Port': '22',
|
||||||
|
'User': 'sql_tunnel',
|
||||||
|
},
|
||||||
|
'Staff': {
|
||||||
|
'ID': '23',
|
||||||
|
'Name': 'Wizard Kit',
|
||||||
|
},
|
||||||
|
'Tables': {
|
||||||
|
'Response': 'ost_ticket_response',
|
||||||
|
'Ticket': 'ost_ticket',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print("This file is not meant to be called directly.")
|
||||||
|
|
||||||
|
# vim: sts=2 sw=2 ts=2
|
||||||
Loading…
Reference in a new issue