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:
@@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user