fix: spec compliance for TUI DIALOGS section

- Rename CertFormResult.cancelled → confirmed (True=submit, False=cancel/Esc)
- Change _FORM_FIELDS from list to tuple
- Update _FORM_LABELS to exact values: CN, Email, Password
- Update _FORM_FIELD_Y to {cn:3, email:5, password:7}
- Add password param to show_cert_form (uses generate_password() if empty)
- Add mask=True to password InputField (both init and F5-regen sites)
- Add ord("Q") to show_confirm false-return condition
- Add KEY_UP/KEY_DOWN navigation in show_cert_form
- Add Ctrl-S (0x13) as immediate submit key in show_cert_form
- Update tests/test_dialogs.py: confirmed=True on submit, confirmed=False on cancel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Vlad Doloman
2026-06-24 00:13:46 +03:00
parent fefec14929
commit 565a152243
2 changed files with 37 additions and 23 deletions

View File

@@ -483,7 +483,7 @@ def show_confirm(stdscr, message: str) -> bool:
key = win.getch() key = win.getch()
if key in (ord("y"), ord("Y")): if key in (ord("y"), ord("Y")):
return True return True
if key in (ord("n"), ord("N"), 27, ord("q")): if key in (ord("n"), ord("N"), 27, ord("q"), ord("Q")):
return False return False
@@ -492,27 +492,28 @@ class CertFormResult:
cn: str cn: str
email: str email: str
password: str password: str
cancelled: bool confirmed: bool
_FORM_FIELDS = ["cn", "email", "password"] _FORM_FIELDS = ("cn", "email", "password")
_FORM_LABELS = { _FORM_LABELS = {
"cn": "Common Name (CN):", "cn": "CN",
"email": "Email address:", "email": "Email",
"password": "Password [F5 = regenerate]:", "password": "Password",
} }
_FORM_FIELD_Y = {"cn": 3, "email": 6, "password": 9} _FORM_FIELD_Y = {"cn": 3, "email": 5, "password": 7}
def show_cert_form( def show_cert_form(
stdscr, stdscr,
cn: str = "", cn: str = "",
email: str = "", email: str = "",
password: str = "",
cn_readonly: bool = False, cn_readonly: bool = False,
) -> CertFormResult: ) -> CertFormResult:
"""Modal cert-detail form. """Modal cert-detail form.
Tab / Shift-Tab cycle fields. F5 regenerates password. Tab / Shift-Tab / Up / Down cycle fields. F5 regenerates password.
Enter on last field confirms. Escape cancels.""" Enter on last field or Ctrl-S confirms. Escape cancels."""
sh, sw = stdscr.getmaxyx() sh, sw = stdscr.getmaxyx()
h, w = 16, 62 h, w = 16, 62
win = curses.newwin(h, w, (sh - h) // 2, (sw - w) // 2) win = curses.newwin(h, w, (sh - h) // 2, (sw - w) // 2)
@@ -524,10 +525,20 @@ def show_cert_form(
"cn": InputField(win, _FORM_FIELD_Y["cn"], 3, fw, initial=cn), "cn": InputField(win, _FORM_FIELD_Y["cn"], 3, fw, initial=cn),
"email": InputField(win, _FORM_FIELD_Y["email"], 3, fw, initial=email), "email": InputField(win, _FORM_FIELD_Y["email"], 3, fw, initial=email),
"password": InputField(win, _FORM_FIELD_Y["password"], 3, fw, "password": InputField(win, _FORM_FIELD_Y["password"], 3, fw,
initial=generate_password()), initial=password if password else generate_password(),
mask=True),
} }
focus = 0 focus = 0
def _submit() -> CertFormResult:
final_cn = cn if cn_readonly else fields["cn"].value
return CertFormResult(
cn=final_cn,
email=fields["email"].value,
password=fields["password"].value,
confirmed=True,
)
while True: while True:
win.clear() win.clear()
draw_box(win, "Certificate Details") draw_box(win, "Certificate Details")
@@ -549,7 +560,7 @@ def show_cert_form(
else: else:
fields[name].draw() fields[name].draw()
hint = "Tab=next F5=regen password Enter=confirm Esc=cancel" hint = "Tab=next F5=regen password Ctrl-S=confirm Esc=cancel"
try: try:
win.addstr(h - 2, 2, hint[:w - 4], curses.color_pair(COLOR_DISABLED)) win.addstr(h - 2, 2, hint[:w - 4], curses.color_pair(COLOR_DISABLED))
except curses.error: except curses.error:
@@ -559,29 +570,32 @@ def show_cert_form(
key = win.getch() key = win.getch()
if key == 27: if key == 27:
return CertFormResult(cn=cn, email="", password="", cancelled=True) return CertFormResult(cn=cn, email="", password="", confirmed=False)
if key == 0x13: # Ctrl-S: immediate submit
return _submit()
if key == 9: # Tab if key == 9: # Tab
focus = (focus + 1) % len(active) focus = (focus + 1) % len(active)
continue continue
if key == curses.KEY_BTAB: # Shift-Tab if key == curses.KEY_BTAB: # Shift-Tab
focus = (focus - 1) % len(active) focus = (focus - 1) % len(active)
continue continue
if key == curses.KEY_DOWN:
focus = (focus + 1) % len(active)
continue
if key == curses.KEY_UP:
focus = (focus - 1) % len(active)
continue
if key == curses.KEY_F5: if key == curses.KEY_F5:
fields["password"] = InputField( fields["password"] = InputField(
win, _FORM_FIELD_Y["password"], 3, fw, initial=generate_password(), win, _FORM_FIELD_Y["password"], 3, fw,
initial=generate_password(), mask=True,
) )
continue continue
if key in (10, 13, curses.KEY_ENTER): if key in (10, 13, curses.KEY_ENTER):
if focus < len(active) - 1: if focus < len(active) - 1:
focus += 1 focus += 1
continue continue
final_cn = cn if cn_readonly else fields["cn"].value return _submit()
return CertFormResult(
cn=final_cn,
email=fields["email"].value,
password=fields["password"].value,
cancelled=False,
)
fields[active[focus]].handle_key(key) fields[active[focus]].handle_key(key)

View File

@@ -66,14 +66,14 @@ def test_show_confirm_esc():
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def test_show_cert_form_cancel(): def test_show_cert_form_cancel():
"""Pressing Escape returns a CertFormResult with cancelled=True.""" """Pressing Escape returns a CertFormResult with confirmed=False."""
stdscr = _make_stdscr() stdscr = _make_stdscr()
win = _make_win(rows=20, cols=70) win = _make_win(rows=20, cols=70)
win.getch.side_effect = [27] win.getch.side_effect = [27]
with patch("curses.newwin", return_value=win): with patch("curses.newwin", return_value=win):
result = show_cert_form(stdscr, cn="alice", email="alice@example.com") result = show_cert_form(stdscr, cn="alice", email="alice@example.com")
assert isinstance(result, CertFormResult) assert isinstance(result, CertFormResult)
assert result.cancelled is True assert result.confirmed is False
def test_show_cert_form_confirm(): def test_show_cert_form_confirm():
@@ -90,7 +90,7 @@ def test_show_cert_form_confirm():
with patch("curses.newwin", return_value=win): with patch("curses.newwin", return_value=win):
result = show_cert_form(stdscr, cn="bob", email="bob@example.com") result = show_cert_form(stdscr, cn="bob", email="bob@example.com")
assert isinstance(result, CertFormResult) assert isinstance(result, CertFormResult)
assert result.cancelled is False assert result.confirmed is True
assert result.cn == "bob" assert result.cn == "bob"
assert result.email == "bob@example.com" assert result.email == "bob@example.com"
assert len(result.password) > 0 assert len(result.password) > 0