Source code for exlab_wizard.ui.components.credential_field

"""Credential field component (Frontend Spec §7.4.1).

Three resting / transient states:

* **not_set**  -- ``Status: Not set``  with a ``[Set]`` button.
* **set**      -- ``Status: Set ✓``    with ``[Replace]`` and ``[Clear]``.
* **editing**  -- inline password input with Save / Cancel.

Never displays a stored secret.
"""

from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass
from typing import Any

from exlab_wizard.logging import get_logger

_log = get_logger(__name__)

STATE_NOT_SET = "not_set"
STATE_SET = "set"
STATE_EDITING = "editing"


[docs] @dataclass class CredentialState: """In-memory state for a credential row.""" state: str = STATE_NOT_SET pending_value: str | None = None # only populated while editing
[docs] def credential_props(state: CredentialState) -> dict[str, Any]: """Compute the visible labels / button names for a state. Returns a dict with ``state``, ``label``, ``primary_button``, ``secondary_button`` (optional), and ``input_visible`` keys. """ if state.state == STATE_NOT_SET: return { "state": STATE_NOT_SET, "label": "Status: Not set", "primary_button": "Set", "secondary_button": None, "input_visible": False, } if state.state == STATE_SET: return { "state": STATE_SET, "label": "Status: Set", "primary_button": "Replace", "secondary_button": "Clear", "input_visible": False, } return { "state": STATE_EDITING, "label": "Status: Editing", "primary_button": "Save", "secondary_button": "Cancel", "input_visible": True, }
[docs] def credential_field( *, label: str, on_save: Callable[[str], None], on_clear: Callable[[], None], initial_state: CredentialState | None = None, ) -> Any: """Build a credential row. The ``on_save`` callback is invoked with the typed password when the operator clicks Save while editing; ``on_clear`` is invoked when the operator confirms the Clear action. """ state = initial_state or CredentialState() props = credential_props(state) payload = {"label": label, "props": props} try: from nicegui import ui except Exception: return payload container = ui.column().classes("w-full").style("gap: 0.25rem;") with container: ui.label(label).style( "font-family: var(--font-mono); " "font-size: var(--text-xs); " "letter-spacing: 0.08em; " "text-transform: uppercase; " "color: var(--color-muted);" ) with ui.row().classes("items-center"): status_label = ui.label(props["label"]).style( "font-family: var(--font-mono); font-size: var(--text-sm);" ) def _set_state(new_state: str) -> None: state.state = new_state state.pending_value = None container.clear() with container: ui.label(label) new_props = credential_props(state) status_label.text = new_props["label"] def _on_primary() -> None: if state.state == STATE_NOT_SET or state.state == STATE_SET: state.state = STATE_EDITING else: # editing if state.pending_value: on_save(state.pending_value) state.state = STATE_SET _set_state(state.state) def _on_secondary() -> None: if state.state == STATE_EDITING: state.state = STATE_NOT_SET elif state.state == STATE_SET: on_clear() state.state = STATE_NOT_SET _set_state(state.state) ui.button(props["primary_button"], on_click=_on_primary).props("flat") if props["secondary_button"]: ui.button(props["secondary_button"], on_click=_on_secondary).props("flat") return container