""" ui.py — ANSI colour helpers, display primitives, and input prompts. Shared by all ES24N workflows. """ import ipaddress import sys # ── ANSI colour helpers ─────────────────────────────────────────────────────── class C: RED = "\033[0;31m" GRN = "\033[0;32m" YEL = "\033[1;33m" CYN = "\033[0;36m" WHT = "\033[1;37m" DIM = "\033[2m" BOLD = "\033[1m" RESET = "\033[0m" CLEAR = "\033[2J\033[H" def _c(colour: str, text: str) -> str: return f"{colour}{text}{C.RESET}" def info(msg: str): print(f" {_c(C.CYN, 'i')} {msg}") def ok(msg: str): print(f" {_c(C.GRN, 'OK')} {msg}") def warn(msg: str): print(f" {_c(C.YEL, '!')} {msg}") def error(msg: str): print(f" {_c(C.RED, 'X')} {msg}") def banner(): print(C.CLEAR, end="") w = 60 print(_c(C.CYN, " +" + "-" * w + "+")) print(_c(C.CYN, " |") + _c(C.WHT + C.BOLD, " TrueNAS ES24N IOM Configuration Tool ") + _c(C.CYN, " |")) print(_c(C.CYN, " |") + _c(C.DIM, " Serial Config & Firmware Updates (stdlib only) ") + _c(C.CYN, " |")) print(_c(C.CYN, " +" + "-" * w + "+")) print() def rule(title: str = ""): width = 60 if title: pad = max(0, width - len(title) - 2) left = pad // 2 right = pad - left line = f"{'-' * left} {title} {'-' * right}" else: line = "-" * width print(f"\n {_c(C.YEL, line)}\n") def draw_table(headers: list, rows: list, col_widths: list): sep = " +-" + "-+-".join("-" * w for w in col_widths) + "-+" def fmt_row(cells): return " | " + " | ".join( str(c).ljust(w) for c, w in zip(cells, col_widths) ) + " |" print(_c(C.DIM, sep)) print(_c(C.BOLD, fmt_row(headers))) print(_c(C.DIM, sep)) for row in rows: print(fmt_row(row)) print(_c(C.DIM, sep)) def draw_box(lines: list, colour: str = C.CYN): width = max(len(l) for l in lines) + 4 print(f" {_c(colour, '+' + '-' * width + '+')}") for line in lines: pad = width - len(line) - 2 print(f" {_c(colour, '|')} {line}{' ' * pad} {_c(colour, '|')}") print(f" {_c(colour, '+' + '-' * width + '+')}") # ── Input helpers ───────────────────────────────────────────────────────────── def prompt(label: str, default: str = "") -> str: display = f" {_c(C.CYN, label)}" if default: display += f" {_c(C.DIM, f'[{default}]')}" display += ": " sys.stdout.write(display) sys.stdout.flush() val = sys.stdin.readline().strip() return val if val else default def prompt_ip(label: str) -> str: while True: val = prompt(label) try: ipaddress.IPv4Address(val) return val except ValueError: warn(f"'{val}' is not a valid IPv4 address — please try again.") def prompt_yn(label: str, default: bool = True) -> bool: hint = "Y/n" if default else "y/N" val = prompt(f"{label} [{hint}]").strip().lower() if not val: return default return val in ("y", "yes") def prompt_password() -> str: while True: val = prompt( "Admin password (BMC/chassis serial, e.g. MXE3000043CHA007)", ) if val: return val warn("Password cannot be empty.")