diff --git a/hwmonDaemon.py b/hwmonDaemon.py index 9fe780d..4adab5c 100644 --- a/hwmonDaemon.py +++ b/hwmonDaemon.py @@ -990,27 +990,33 @@ class SystemHealthMonitor: return "🟢 Low - Monitor Only" def _generate_detailed_description(self, issue: str, health_report: Dict[str, Any]) -> str: - """Generate detailed ticket description.""" + """Generate detailed ticket description with properly formatted ASCII art.""" hostname = socket.gethostname() timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") priority = "⚠ HIGH" if "CRITICAL" in issue else "ā— MEDIUM" - content_width = self.STANDARD_WIDTH - 2 + # Standard box width for all sections (80 chars total) + box_width = 78 + field_width = 62 # Width for value fields after label + banner = f""" -ā”{'━' * content_width}┓ -ā”ƒ{' HARDWARE MONITORING ALERT TICKET '.center(content_width)}ā”ƒ -┣{'━' * content_width}┫ -ā”ƒ Host : {hostname:<{content_width-13}}ā”ƒ -ā”ƒ Generated : {timestamp:<{content_width-13}}ā”ƒ -ā”ƒ Priority : {priority:<{content_width-13}}ā”ƒ -ā”—{'━' * content_width}ā”›""" - +ā”{'━' * box_width}┓ +ā”ƒ{' HARDWARE MONITORING ALERT TICKET '.center(box_width)}ā”ƒ +┣{'━' * box_width}┫ +ā”ƒ Host : {hostname:<{box_width - 15}}ā”ƒ +ā”ƒ Generated : {timestamp:<{box_width - 15}}ā”ƒ +ā”ƒ Priority : {priority:<{box_width - 15}}ā”ƒ +ā”—{'━' * box_width}ā”›""" + + issue_type = self._get_issue_type(issue) + impact_level = self._get_impact_level(issue) + executive_summary = f""" -ā”ā” EXECUTIVE SUMMARY ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -ā”ƒ Issue Type │ {self._get_issue_type(issue)} ā”ƒ -ā”ƒ Impact Level │ {self._get_impact_level(issue)} ā”ƒ -┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ -""" +ā”ā” EXECUTIVE SUMMARY {'━' * (box_width - 20)}┓ +ā”ƒ Issue Type │ {issue_type:<{field_width}}ā”ƒ +ā”ƒ Impact Level │ {impact_level:<{field_width}}ā”ƒ +ā”—{'━' * box_width}ā”›""" + description = banner + executive_summary # Add relevant SMART descriptions @@ -1042,7 +1048,7 @@ class SystemHealthMonitor: last_test_date = smart_data.get('last_test_date', 'N/A') age = f"{int(power_on_hours/24/365) if isinstance(power_on_hours, (int, float)) else 'N/A'} years" if power_on_hours != 'N/A' else 'N/A' - # Fix the formatting issue by ensuring all values are strings and not None + # Ensure all values are properly formatted strings device_safe = device or 'N/A' model_safe = drive_details.get('model') or 'N/A' serial_safe = drive_details.get('serial') or 'N/A' @@ -1051,14 +1057,14 @@ class SystemHealthMonitor: firmware_safe = drive_details.get('firmware') or 'N/A' description += f""" -ā”ā” DRIVE SPECIFICATIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -ā”ƒ Device Path │ {device_safe:<60} ā”ƒ -ā”ƒ Model │ {model_safe:<60} ā”ƒ -ā”ƒ Serial │ {serial_safe:<60} ā”ƒ -ā”ƒ Capacity │ {capacity_safe:<60} ā”ƒ -ā”ƒ Type │ {type_safe:<60} ā”ƒ -ā”ƒ Firmware │ {firmware_safe:<60} ā”ƒ -┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +ā”ā” DRIVE SPECIFICATIONS {'━' * (box_width - 23)}┓ +ā”ƒ Device Path │ {device_safe:<{field_width}}ā”ƒ +ā”ƒ Model │ {model_safe:<{field_width}}ā”ƒ +ā”ƒ Serial │ {serial_safe:<{field_width}}ā”ƒ +ā”ƒ Capacity │ {capacity_safe:<{field_width}}ā”ƒ +ā”ƒ Type │ {type_safe:<{field_width}}ā”ƒ +ā”ƒ Firmware │ {firmware_safe:<{field_width}}ā”ƒ +ā”—{'━' * box_width}ā”› """ if drive_info: @@ -1074,61 +1080,67 @@ class SystemHealthMonitor: age_safe = age or 'N/A' description += f""" -ā”ā” DRIVE TIMELINE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -ā”ƒ Power-On Hours │ {power_on_safe:<56} ā”ƒ -ā”ƒ Last SMART Test │ {last_test_safe:<56} ā”ƒ -ā”ƒ Drive Age │ {age_safe:<56} ā”ƒ -┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +ā”ā” DRIVE TIMELINE {'━' * (box_width - 17)}┓ +ā”ƒ Power-On Hours │ {power_on_safe:<{field_width - 4}}ā”ƒ +ā”ƒ Last SMART Test │ {last_test_safe:<{field_width - 4}}ā”ƒ +ā”ƒ Drive Age │ {age_safe:<{field_width - 4}}ā”ƒ +ā”—{'━' * box_width}ā”› """ smart_status_safe = drive_info.get('smart_status') or 'N/A' - temp_safe = f"{drive_info.get('temperature')}°C" if drive_info.get('temperature') else 'N/A' + # Properly handle temperature with None check + temp_value = drive_info.get('temperature') + temp_safe = f"{temp_value}°C" if temp_value is not None else 'N/A' description += f""" -ā”ā” SMART STATUS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -ā”ƒ Status │ {smart_status_safe:<60} ā”ƒ -ā”ƒ Temperature │ {temp_safe:<60} ā”ƒ -┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +ā”ā” SMART STATUS {'━' * (box_width - 15)}┓ +ā”ƒ Status │ {smart_status_safe:<{field_width}}ā”ƒ +ā”ƒ Temperature │ {temp_safe:<{field_width}}ā”ƒ +ā”—{'━' * box_width}ā”› """ if drive_info.get('smart_attributes'): - description += "\nā”ā” SMART ATTRIBUTES " + "━" * 48 + "┓\n" + description += f"\nā”ā” SMART ATTRIBUTES {'━' * (box_width - 19)}┓\n" for attr, value in drive_info['smart_attributes'].items(): attr_safe = str(attr).replace('_', ' ') if attr else 'Unknown' value_safe = str(value) if value is not None else 'N/A' - description += f"ā”ƒ {attr_safe:<25} │ {value_safe:<37} ā”ƒ\n" - description += "ā”—" + "━" * 71 + "ā”›\n" + description += f"ā”ƒ {attr_safe:<27} │ {value_safe:<{field_width - 14}}ā”ƒ\n" + description += f"ā”—{'━' * box_width}ā”›\n" if drive_info.get('partitions'): for partition in drive_info['partitions']: usage_percent = partition.get('usage_percent', 0) - blocks = int(usage_percent / 5) # 20 blocks total = 100% - usage_meter = 'ā–ˆ' * blocks + 'ā–‘' * (20 - blocks) - + # Create 50-char usage meter (2% per block) + blocks = int(usage_percent / 2) + usage_meter = 'ā–ˆ' * blocks + 'ā–‘' * (50 - blocks) + mountpoint_safe = partition.get('mountpoint') or 'N/A' fstype_safe = partition.get('fstype') or 'N/A' total_space_safe = partition.get('total_space') or 'N/A' used_space_safe = partition.get('used_space') or 'N/A' free_space_safe = partition.get('free_space') or 'N/A' - + usage_pct_str = f"{usage_percent}%" + + # Truncate mountpoint if too long for header + mountpoint_display = mountpoint_safe[:50] if len(mountpoint_safe) > 50 else mountpoint_safe + description += f""" -ā”ā” PARTITION [{mountpoint_safe:<60}] ━┓ -ā”ƒ Filesystem │ {fstype_safe:<60} ā”ƒ -ā”ƒ Usage Meter │ [{usage_meter:<58}] ā”ƒ -ā”ƒ Total Space │ {total_space_safe:<60} ā”ƒ -ā”ƒ Used Space │ {used_space_safe:<60} ā”ƒ -ā”ƒ Free Space │ {free_space_safe:<60} ā”ƒ -ā”ƒ Usage │ {usage_percent}%{'':<57} ā”ƒ -┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +ā”ā” PARTITION: {mountpoint_display} {'━' * (box_width - 14 - len(mountpoint_display))}┓ +ā”ƒ Filesystem │ {fstype_safe:<{field_width}}ā”ƒ +ā”ƒ Usage Meter │ {usage_meter} {usage_pct_str:>5}ā”ƒ +ā”ƒ Total Space │ {total_space_safe:<{field_width}}ā”ƒ +ā”ƒ Used Space │ {used_space_safe:<{field_width}}ā”ƒ +ā”ƒ Free Space │ {free_space_safe:<{field_width}}ā”ƒ +ā”—{'━' * box_width}ā”› """ firmware_info = self._check_disk_firmware(device) if firmware_info['is_problematic']: - description += "\nā”ā” FIRMWARE ALERTS " + "━" * 48 + "┓\n" + description += f"\nā”ā” FIRMWARE ALERTS {'━' * (box_width - 18)}┓\n" for issue_item in firmware_info['known_issues']: issue_safe = str(issue_item) if issue_item else 'Unknown issue' - description += f"ā”ƒ ⚠ {issue_safe:<67} ā”ƒ\n" - description += "ā”—" + "━" * 71 + "ā”›\n" + description += f"ā”ƒ ⚠ {issue_safe:<{box_width - 5}}ā”ƒ\n" + description += f"ā”—{'━' * box_width}ā”›\n" except Exception as e: description += f"\nError generating drive details: {str(e)}\n"