Add User Manager panel for sys::UserService components

New user_service_view.py with table of sys::User children, add/edit/delete
dialogs, SHA-1 credential hashing, 32-bit permission bitmask (4 groups x 7
flags), 8-bit provisioning byte, and undo support. Wired into main.py with
context menu entry, panel management, history navigation, and live-mode DOM
resolution for correct display on connected devices.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-09-22 20:10:03 +03:00
co-authored by Claude Opus 4.6
parent 190ab79bdc
commit 43549046e5
3 changed files with 522 additions and 4 deletions
+55 -3
View File
@@ -18,6 +18,7 @@ import properties
import sax_file
import schema
import undo
import user_service_view
import wiresheet
from sax_dom import (find_component_by_path, find_parent_element, get_component_path,
read_meta_property, update_meta_property)
@@ -78,6 +79,7 @@ HISTORY_LIMIT = 50
VIEW_SHEET = "sheet"
VIEW_PROPERTIES = "properties"
VIEW_LINKS = "links"
VIEW_USERS = "users"
# Tk class names of the widgets that own the keyboard while they have focus. Del and
# Backspace mean "edit this text" in each of them, never "delete the selected component".
@@ -122,6 +124,7 @@ show_left_column_var = None
properties_panel = None
link_panel = None
kits_panel = None
user_service_panel = None
# The sheet to put the tree back on when a sheet change is refused because of unsaved
# edits, and the flag that stops the restoring selection from being read as a fresh
@@ -189,6 +192,12 @@ def refresh_all_views():
else:
close_editor_panel(force=True)
if user_service_panel is not None:
if _component_in_document(user_service_panel.comp):
user_service_panel.refresh()
else:
close_editor_panel(force=True)
def _component_in_document(comp):
root = app_state.xml_root_element
@@ -210,7 +219,7 @@ def delete_component(comp):
than going through refresh_all_views, so the panel would otherwise be left describing
a component the document no longer has.
"""
panel = properties_panel or link_panel
panel = properties_panel or link_panel or user_service_panel
showing = panel is not None and panel.comp is comp
if sax_file.execute_cascading_deletion(comp) and showing:
close_editor_panel(force=True)
@@ -218,7 +227,8 @@ def delete_component(comp):
def editor_panel_showing():
"""Whether something other than the wiresheet has the editor pane."""
return properties_panel is not None or link_panel is not None or kits_panel is not None
return (properties_panel is not None or link_panel is not None
or kits_panel is not None or user_service_panel is not None)
def _take_editor_pane():
@@ -330,6 +340,38 @@ def dismiss_link_view(restore_canvas=True):
_give_editor_pane_back()
def show_user_service(comp):
"""Puts the User Manager for a sys::UserService in the editor pane."""
global user_service_panel
if comp is None:
return
if not close_editor_panel():
return
_take_editor_pane()
user_service_panel = user_service_view.build_user_service_panel(
frame_editor, comp, on_change=refresh_all_views,
on_close=dismiss_user_service,
)
user_service_panel.frame.pack(fill=tk.BOTH, expand=True)
record_visit(VIEW_USERS, comp)
app_state.set_status(f"User Manager: {comp.get('name', '?')}")
def dismiss_user_service(restore_canvas=True):
"""Takes the User Manager down and gives the pane back."""
global user_service_panel
if user_service_panel is None:
return
user_service_panel.destroy()
user_service_panel = None
if restore_canvas:
_give_editor_pane_back()
def show_kits():
"""Puts the kit table in the editor pane, in place of the wiresheet."""
global kits_panel
@@ -378,6 +420,9 @@ def close_editor_panel(force=False):
if kits_panel is not None:
dismiss_kits()
return True
if user_service_panel is not None:
dismiss_user_service()
return True
if properties_panel is None:
return True
if force:
@@ -1207,6 +1252,11 @@ def fill_component_popup(menu, comp):
label="Link View",
command=lambda: show_link_view(comp),
)
if user_service_view.is_user_service(comp):
menu.add_command(
label="User Manager",
command=lambda: show_user_service(comp),
)
menu.add_separator()
menu.add_command(
label="Delete Component",
@@ -1644,6 +1694,8 @@ def _show_history_entry(position):
_show_sheet_entry(element)
elif kind == VIEW_PROPERTIES:
show_properties(element)
elif kind == VIEW_USERS:
show_user_service(element)
else:
show_link_view(element)
finally:
@@ -1707,7 +1759,7 @@ def on_tree_filled():
# A different file has no such component, and a rebuild after a delete may have taken
# it. Neither is a moment to ask about edits that now have nowhere to go.
panel = properties_panel or link_panel
panel = properties_panel or link_panel or user_service_panel
if panel is not None and not _component_in_document(panel.comp):
close_editor_panel(force=True)