diff --git a/openvpncertupdate.py b/openvpncertupdate.py index 9801c9b..e397a61 100644 --- a/openvpncertupdate.py +++ b/openvpncertupdate.py @@ -483,7 +483,7 @@ def show_confirm(stdscr, message: str) -> bool: key = win.getch() if key in (ord("y"), ord("Y")): 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 @@ -492,27 +492,28 @@ class CertFormResult: cn: str email: str password: str - cancelled: bool + confirmed: bool -_FORM_FIELDS = ["cn", "email", "password"] +_FORM_FIELDS = ("cn", "email", "password") _FORM_LABELS = { - "cn": "Common Name (CN):", - "email": "Email address:", - "password": "Password [F5 = regenerate]:", + "cn": "CN", + "email": "Email", + "password": "Password", } -_FORM_FIELD_Y = {"cn": 3, "email": 6, "password": 9} +_FORM_FIELD_Y = {"cn": 3, "email": 5, "password": 7} def show_cert_form( stdscr, cn: str = "", email: str = "", + password: str = "", cn_readonly: bool = False, ) -> CertFormResult: """Modal cert-detail form. - Tab / Shift-Tab cycle fields. F5 regenerates password. - Enter on last field confirms. Escape cancels.""" + Tab / Shift-Tab / Up / Down cycle fields. F5 regenerates password. + Enter on last field or Ctrl-S confirms. Escape cancels.""" sh, sw = stdscr.getmaxyx() h, w = 16, 62 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), "email": InputField(win, _FORM_FIELD_Y["email"], 3, fw, initial=email), "password": InputField(win, _FORM_FIELD_Y["password"], 3, fw, - initial=generate_password()), + initial=password if password else generate_password(), + mask=True), } 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: win.clear() draw_box(win, "Certificate Details") @@ -549,7 +560,7 @@ def show_cert_form( else: 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: win.addstr(h - 2, 2, hint[:w - 4], curses.color_pair(COLOR_DISABLED)) except curses.error: @@ -559,29 +570,32 @@ def show_cert_form( key = win.getch() 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 focus = (focus + 1) % len(active) continue if key == curses.KEY_BTAB: # Shift-Tab focus = (focus - 1) % len(active) 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: 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 if key in (10, 13, curses.KEY_ENTER): if focus < len(active) - 1: focus += 1 continue - final_cn = cn if cn_readonly else fields["cn"].value - return CertFormResult( - cn=final_cn, - email=fields["email"].value, - password=fields["password"].value, - cancelled=False, - ) + return _submit() fields[active[focus]].handle_key(key) diff --git a/tests/test_dialogs.py b/tests/test_dialogs.py index 40009d6..1f1de3f 100644 --- a/tests/test_dialogs.py +++ b/tests/test_dialogs.py @@ -66,14 +66,14 @@ def test_show_confirm_esc(): # --------------------------------------------------------------------------- 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() win = _make_win(rows=20, cols=70) win.getch.side_effect = [27] with patch("curses.newwin", return_value=win): result = show_cert_form(stdscr, cn="alice", email="alice@example.com") assert isinstance(result, CertFormResult) - assert result.cancelled is True + assert result.confirmed is False def test_show_cert_form_confirm(): @@ -90,7 +90,7 @@ def test_show_cert_form_confirm(): with patch("curses.newwin", return_value=win): result = show_cert_form(stdscr, cn="bob", email="bob@example.com") assert isinstance(result, CertFormResult) - assert result.cancelled is False + assert result.confirmed is True assert result.cn == "bob" assert result.email == "bob@example.com" assert len(result.password) > 0