feat: Repeater admin view with login, CLI, and password storage#76
Conversation
Adds a full repeater administration feature: login/logout to repeaters (admin or guest), send CLI commands with a terminal-style console, and optionally persist repeater passwords in the local database. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 03e27c4672
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| contact=contact, | ||
| local_identity=node.identity, | ||
| message=command, | ||
| txt_type=TXT_TYPE_CLI_DATA, |
There was a problem hiding this comment.
Remove unsupported txt_type argument from create_text_message
send_repeater_command() passes txt_type=TXT_TYPE_CLI_DATA into PacketBuilder.create_text_message, but the pyMC_core 1.0.10 API for create_text_message only accepts contact, local_identity, message, attempt, message_type, and out_path. On real hardware this call path will raise a TypeError before any packet is sent, so every CLI command from the new Admin view fails at runtime.
Useful? React with 👍 / 👎.
| if self._selected_repeater is None: | ||
| return | ||
| peer_name = self._selected_repeater.display_name | ||
| self._service.logout_from_repeater(peer_name) |
There was a problem hiding this comment.
Avoid blocking GTK main loop during repeater logout
_on_logout_clicked() calls logout_from_repeater() synchronously on the UI thread, and that client method can block for up to 5 seconds while waiting on radio I/O (_run_async(..., timeout=5.0)). If the repeater is slow or unreachable, the whole GTK interface freezes during logout, unlike login/command flows that already use background threads.
Useful? React with 👍 / 👎.
| def _on_response(message_text: str, sender_contact: object) -> None: | ||
| response_data["text"] = message_text | ||
| response_event.set() |
There was a problem hiding this comment.
Filter command responses by originating repeater
send_repeater_command() installs a global command-response callback and resolves the wait on the first callback invocation, but _on_response ignores the provided sender_contact. If another repeater emits a command response while this request is in flight, the wrong payload can satisfy the wait and be attributed to peer_name, producing cross-repeater response mixups.
Useful? React with 👍 / 👎.
| label.set_wrap(True) | ||
| label.set_wrap_mode(Pango.WrapMode.CHAR) |
There was a problem hiding this comment.
Constrain console response label width explicitly
The console appends unbounded Gtk.Labels and only enables wrapping; in GTK4, wrapping alone does not make requisition depend on parent width, so long unbroken CLI output can report very large natural widths and force window expansion. This reproduces the known GTK sizing pitfall and can blow out the Admin layout when responses are lengthy.
Useful? React with 👍 / 👎.
| text = result.get("response_text", "") | ||
| if text: | ||
| self._append_console_response(text) |
There was a problem hiding this comment.
Keep command completion scoped to the original repeater
A command request captures peer_name, but the completion handler always appends output to the currently visible console without checking whether the user switched selection while the worker thread was running. If the operator selects repeater B before repeater A’s response arrives, A’s output is rendered under B, which misattributes administrative command results.
Useful? React with 👍 / 👎.
| state = self._service.get_repeater_login_state(peer_name) | ||
| self._update_login_ui(state) | ||
| self._append_console_response("Login successful") |
There was a problem hiding this comment.
Ignore stale login completion after selection changes
Login completion updates status widgets unconditionally after the background thread returns. Because row selection remains interactive during login, switching to another repeater before completion lets repeater A’s result drive repeater B’s login banner/buttons, producing incorrect UI state and potentially enabling or disabling controls for the wrong target.
Useful? React with 👍 / 👎.
- Filter command responses by originating repeater public key - Move logout to background thread to avoid blocking GTK main loop - Guard login/command completion handlers against stale selection - Constrain console label width to prevent GTK4 window blowout Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
RepeaterPasswordStorebacked by SQLite (base64-obfuscated) for optional password persistence across sessionsMeshcoreServiceprotocol,MeshcoreClient,MockMeshcoreClient, andPyMCCoreSessionwith repeater admin operations (login,logout,send_repeater_command)_resolve_contact()helper inoperations.pyto deduplicate contact lookup across all 5 async operationsTXT_TYPE_CLI_DATAconstant to replace magic numberTest plan
uv run pytest— 98 passed, 1 skippedMESHCORE_MOCK=1 ./scripts/run-dev.sh), navigate to Admin tabstatus,ver,help), verify console output🤖 Generated with Claude Code