Update sys.path reference in es24n_conf.py and all documentation to reflect the new folder name. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
121 lines
3.5 KiB
Python
121 lines
3.5 KiB
Python
"""
|
|
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.")
|