Improve User Manager: Workbench-style permissions dialog, live mode support, double-click to open
- Permissions dialog now uses two-level UI: compact Edit User with >> buttons opening a separate grid dialog (Groups x Operator/Admin columns) with Clear All / Set All and auto-cascade (write implies read, admin implies operator) - Provisioning has its own >> popup dialog - Removed live mode restrictions for add/edit/delete user operations - Broadened type detection and added safe int conversion for live mode values - Double-click on tree node or canvas box opens special view (User Manager for UserService, Properties for other components) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -8,7 +8,7 @@ so later rebinding would only ever be seen by the module that did it.
|
||||
import configparser
|
||||
import os
|
||||
|
||||
VERSION = "0.0.0.117"
|
||||
VERSION = "0.0.0.118"
|
||||
PROPERTIES_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "editor.properties")
|
||||
DEFAULT_GRID_SIZE = 10
|
||||
DEFAULT_ADD_TIMESTAMP = True
|
||||
|
||||
@@ -985,6 +985,42 @@ def on_tree_right_click(event):
|
||||
tree_comp_context_menu.post(event.x_root, event.y_root)
|
||||
|
||||
|
||||
def _open_special_view(comp):
|
||||
"""Opens the type-specific view for a component, or Properties as fallback."""
|
||||
if user_service_view.is_user_service(comp):
|
||||
show_user_service(comp)
|
||||
return
|
||||
show_properties(comp)
|
||||
|
||||
|
||||
def on_tree_double_click(event):
|
||||
"""Double-click on a tree node opens its special view (or Properties)."""
|
||||
tree = app_state.tree
|
||||
item_id = tree.identify_row(event.y)
|
||||
if not item_id or tree.parent(item_id) == "":
|
||||
return
|
||||
comp = app_state.tree_element_map.get(item_id)
|
||||
if comp is not None:
|
||||
_open_special_view(comp)
|
||||
|
||||
|
||||
def on_canvas_double_click(event):
|
||||
"""Double-click on a canvas component opens its special view (or Properties)."""
|
||||
canvas = app_state.canvas
|
||||
cx = canvas.canvasx(event.x)
|
||||
cy = canvas.canvasy(event.y)
|
||||
clicked = canvas.find_closest(cx, cy, halo=3)
|
||||
if not clicked:
|
||||
return
|
||||
tags = canvas.gettags(clicked[0])
|
||||
group_tag = next((t for t in tags if t.startswith("comp_group_")), None)
|
||||
if group_tag is None:
|
||||
return
|
||||
comp = app_state.canvas_comp_map.get(group_tag)
|
||||
if comp is not None:
|
||||
_open_special_view(comp)
|
||||
|
||||
|
||||
def on_canvas_click(event):
|
||||
canvas = app_state.canvas
|
||||
canvas.itemconfig("block_outline", outline="#7f9db9", width=2)
|
||||
@@ -2502,6 +2538,7 @@ tree.bind("<<TreeviewSelect>>", on_tree_select)
|
||||
|
||||
tree.bind("<ButtonPress-3>", on_tree_right_click)
|
||||
tree.bind("<ButtonPress-2>", on_tree_right_click)
|
||||
tree.bind("<Double-1>", on_tree_double_click)
|
||||
|
||||
left_pane.add(frame_tree, weight=3)
|
||||
|
||||
@@ -2729,6 +2766,7 @@ canvas.bind("<Configure>", lambda e: wiresheet.draw_grid())
|
||||
|
||||
canvas.bind("<ButtonPress-3>", on_canvas_right_click)
|
||||
canvas.bind("<ButtonPress-2>", on_canvas_right_click)
|
||||
canvas.bind("<Double-1>", on_canvas_double_click)
|
||||
|
||||
# Wheel zoom. Windows and macOS report a delta; X11 sends button 4 and 5 instead.
|
||||
canvas.bind("<Control-MouseWheel>", on_canvas_wheel_zoom)
|
||||
|
||||
+225
-75
@@ -99,6 +99,18 @@ def _get_prop(comp, name):
|
||||
return prop.get("val") if prop is not None else None
|
||||
|
||||
|
||||
def _safe_int(val):
|
||||
if val is None:
|
||||
return 0
|
||||
try:
|
||||
return int(val)
|
||||
except (TypeError, ValueError):
|
||||
try:
|
||||
return int(float(val))
|
||||
except (TypeError, ValueError):
|
||||
return 0
|
||||
|
||||
|
||||
def _get_users(service_comp):
|
||||
"""Returns child comp elements, excluding uncached placeholders."""
|
||||
return [c for c in service_comp
|
||||
@@ -124,23 +136,176 @@ def is_user_service(comp):
|
||||
if comp is None:
|
||||
return False
|
||||
type_name = comp.get("type", "")
|
||||
if type_name.endswith("::UserService") or type_name == "UserService":
|
||||
if "UserService" in type_name:
|
||||
return True
|
||||
for child in comp:
|
||||
if child.tag == "comp" and child.get("type", "").endswith("::User"):
|
||||
if child.tag == "comp" and "User" in child.get("type", ""):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class _PermissionsDialog:
|
||||
"""Workbench-style permissions grid: Groups x Operator/Admin columns."""
|
||||
|
||||
def __init__(self, parent, perm_val):
|
||||
self.result = None
|
||||
groups = _decode_perm(perm_val)
|
||||
|
||||
self._dlg = tk.Toplevel(parent)
|
||||
self._dlg.title("Permissions")
|
||||
self._dlg.transient(parent)
|
||||
self._dlg.grab_set()
|
||||
self._dlg.resizable(False, False)
|
||||
|
||||
grid = tk.Frame(self._dlg, padx=8, pady=8)
|
||||
grid.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
COL_HEADERS = [
|
||||
("Operator", ["Read", "Write", "Invoke"]),
|
||||
("Admin", ["Read", "Write", "Invoke", "User"]),
|
||||
]
|
||||
|
||||
# header row 0: "Groups" + category spans
|
||||
tk.Label(grid, text="Groups", relief=tk.GROOVE, bg="#e8e8e8",
|
||||
padx=4, pady=2).grid(row=0, column=0, rowspan=2, sticky=tk.NSEW)
|
||||
col = 1
|
||||
for cat_name, sub_cols in COL_HEADERS:
|
||||
span = len(sub_cols)
|
||||
tk.Label(grid, text=cat_name, relief=tk.GROOVE, bg="#d8e8f0",
|
||||
font=("TkDefaultFont", 8, "bold"),
|
||||
padx=4, pady=2).grid(row=0, column=col, columnspan=span,
|
||||
sticky=tk.NSEW)
|
||||
col += span
|
||||
|
||||
# header row 1: sub-column names
|
||||
col = 1
|
||||
for _, sub_cols in COL_HEADERS:
|
||||
for sub in sub_cols:
|
||||
tk.Label(grid, text=sub, relief=tk.GROOVE, bg="#e8e8e8",
|
||||
font=("TkDefaultFont", 7), padx=4, pady=1).grid(
|
||||
row=1, column=col, sticky=tk.NSEW)
|
||||
col += 1
|
||||
|
||||
# data rows
|
||||
self._perm_vars = []
|
||||
for g in range(4):
|
||||
r = g + 2
|
||||
tk.Label(grid, text=f"Group {g + 1}", relief=tk.GROOVE,
|
||||
bg="#f4f4f4", padx=4, pady=1).grid(row=r, column=0,
|
||||
sticky=tk.NSEW)
|
||||
gvars = []
|
||||
for f_idx in range(7):
|
||||
var = tk.BooleanVar(value=groups[g][f_idx])
|
||||
var.trace_add("write", lambda *_a, g=g, f=f_idx: self._on_perm_toggle(g, f))
|
||||
ttk.Checkbutton(grid, variable=var).grid(
|
||||
row=r, column=f_idx + 1, padx=6, pady=1)
|
||||
gvars.append(var)
|
||||
self._perm_vars.append(gvars)
|
||||
|
||||
# Clear All / Set All
|
||||
action_frame = tk.Frame(grid)
|
||||
action_frame.grid(row=6, column=0, columnspan=8, sticky=tk.E, pady=(6, 0))
|
||||
ttk.Button(action_frame, text="Clear All", width=8,
|
||||
command=self._clear_all).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(action_frame, text="Set All", width=8,
|
||||
command=self._set_all).pack(side=tk.LEFT, padx=2)
|
||||
|
||||
# OK / Cancel
|
||||
btn_frame = tk.Frame(self._dlg, pady=6)
|
||||
btn_frame.pack()
|
||||
ttk.Button(btn_frame, text="OK", width=10, command=self._on_ok).pack(
|
||||
side=tk.LEFT, padx=4)
|
||||
ttk.Button(btn_frame, text="Cancel", width=10, command=self._on_cancel).pack(
|
||||
side=tk.LEFT, padx=4)
|
||||
|
||||
self._dlg.protocol("WM_DELETE_WINDOW", self._on_cancel)
|
||||
self._dlg.wait_window()
|
||||
|
||||
def _on_perm_toggle(self, group, flag_idx):
|
||||
"""Auto-cascade: write implies read, admin implies operator."""
|
||||
gvars = self._perm_vars[group]
|
||||
if not gvars[flag_idx].get():
|
||||
return
|
||||
# Flags: 0=or, 1=ow, 2=oi, 3=ar, 4=aw, 5=ai, 6=ua
|
||||
# Write (1) → Read (0); Admin Write (4) → Admin Read (3)
|
||||
if flag_idx in (1, 4):
|
||||
gvars[flag_idx - 1].set(True)
|
||||
# Admin flag (3,4,5) → same Operator flag (0,1,2)
|
||||
if 3 <= flag_idx <= 5:
|
||||
gvars[flag_idx - 3].set(True)
|
||||
|
||||
def _clear_all(self):
|
||||
for gvars in self._perm_vars:
|
||||
for v in gvars:
|
||||
v.set(False)
|
||||
|
||||
def _set_all(self):
|
||||
for gvars in self._perm_vars:
|
||||
for v in gvars:
|
||||
v.set(True)
|
||||
|
||||
def _on_ok(self):
|
||||
groups = [[v.get() for v in gvars] for gvars in self._perm_vars]
|
||||
self.result = _encode_perm(groups)
|
||||
self._dlg.destroy()
|
||||
|
||||
def _on_cancel(self):
|
||||
self._dlg.destroy()
|
||||
|
||||
|
||||
class _ProvisioningDialog:
|
||||
"""Provisioning flags popup: app, kits, svm checkboxes."""
|
||||
|
||||
def __init__(self, parent, prov_val):
|
||||
self.result = None
|
||||
flags = _decode_prov(prov_val)
|
||||
|
||||
self._dlg = tk.Toplevel(parent)
|
||||
self._dlg.title("Provisioning Permissions")
|
||||
self._dlg.transient(parent)
|
||||
self._dlg.grab_set()
|
||||
self._dlg.resizable(False, False)
|
||||
|
||||
main = ttk.Frame(self._dlg, padding=10)
|
||||
main.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
self._prov_vars = {}
|
||||
for name, _ in PROV_FLAGS:
|
||||
var = tk.BooleanVar(value=flags.get(name, False))
|
||||
ttk.Checkbutton(main, text=name, variable=var).pack(
|
||||
anchor=tk.W, pady=2)
|
||||
self._prov_vars[name] = var
|
||||
|
||||
btn_frame = ttk.Frame(main)
|
||||
btn_frame.pack(pady=(10, 0))
|
||||
ttk.Button(btn_frame, text="OK", width=10, command=self._on_ok).pack(
|
||||
side=tk.LEFT, padx=4)
|
||||
ttk.Button(btn_frame, text="Cancel", width=10, command=self._on_cancel).pack(
|
||||
side=tk.LEFT, padx=4)
|
||||
|
||||
self._dlg.protocol("WM_DELETE_WINDOW", self._on_cancel)
|
||||
self._dlg.wait_window()
|
||||
|
||||
def _on_ok(self):
|
||||
self.result = _encode_prov(
|
||||
{name: var.get() for name, var in self._prov_vars.items()})
|
||||
self._dlg.destroy()
|
||||
|
||||
def _on_cancel(self):
|
||||
self._dlg.destroy()
|
||||
|
||||
|
||||
class _UserDialog:
|
||||
"""Modal dialog for adding or editing a user."""
|
||||
"""Compact Edit/Add User dialog with >> buttons opening Permissions/Provisioning popups."""
|
||||
|
||||
def __init__(self, parent, service_comp, user_elem, on_done):
|
||||
self._service_comp = service_comp
|
||||
self._user_elem = user_elem
|
||||
self._on_done = on_done
|
||||
self._is_edit = user_elem is not None
|
||||
self.result = None
|
||||
|
||||
self._perm_val = _safe_int(_get_prop(user_elem, "perm")) if self._is_edit else 0
|
||||
self._prov_val = _safe_int(_get_prop(user_elem, "prov")) if self._is_edit else 0
|
||||
|
||||
self._dlg = tk.Toplevel(parent)
|
||||
self._dlg.title("Edit User" if self._is_edit else "Add User")
|
||||
@@ -152,81 +317,78 @@ class _UserDialog:
|
||||
main.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
row = 0
|
||||
ttk.Label(main, text="Username:").grid(row=row, column=0, sticky=tk.W, pady=2)
|
||||
ttk.Label(main, text="Username").grid(row=row, column=0, sticky=tk.W, pady=2)
|
||||
self._name_var = tk.StringVar(
|
||||
value=user_elem.get("name", "") if self._is_edit else "")
|
||||
name_entry = ttk.Entry(main, textvariable=self._name_var, width=24)
|
||||
name_entry.grid(row=row, column=1, columnspan=3, sticky=tk.EW, pady=2)
|
||||
name_entry = ttk.Entry(main, textvariable=self._name_var, width=28)
|
||||
name_entry.grid(row=row, column=1, columnspan=2, sticky=tk.EW, pady=2)
|
||||
if self._is_edit:
|
||||
name_entry.config(state="disabled")
|
||||
|
||||
row += 1
|
||||
ttk.Label(main, text="Password:").grid(row=row, column=0, sticky=tk.W, pady=2)
|
||||
ttk.Label(main, text="Password").grid(row=row, column=0, sticky=tk.W, pady=2)
|
||||
self._pass_var = tk.StringVar()
|
||||
ttk.Entry(main, textvariable=self._pass_var, show="*", width=24).grid(
|
||||
row=row, column=1, columnspan=3, sticky=tk.EW, pady=2)
|
||||
ttk.Entry(main, textvariable=self._pass_var, show="*", width=28).grid(
|
||||
row=row, column=1, columnspan=2, sticky=tk.EW, pady=2)
|
||||
|
||||
row += 1
|
||||
ttk.Label(main, text="Confirm:").grid(row=row, column=0, sticky=tk.W, pady=2)
|
||||
ttk.Label(main, text="Confirm").grid(row=row, column=0, sticky=tk.W, pady=2)
|
||||
self._confirm_var = tk.StringVar()
|
||||
ttk.Entry(main, textvariable=self._confirm_var, show="*", width=24).grid(
|
||||
row=row, column=1, columnspan=3, sticky=tk.EW, pady=2)
|
||||
ttk.Entry(main, textvariable=self._confirm_var, show="*", width=28).grid(
|
||||
row=row, column=1, columnspan=2, sticky=tk.EW, pady=2)
|
||||
|
||||
# permissions
|
||||
# permissions summary + >> button
|
||||
row += 1
|
||||
ttk.Separator(main, orient=tk.HORIZONTAL).grid(
|
||||
row=row, column=0, columnspan=4, sticky=tk.EW, pady=6)
|
||||
row=row, column=0, columnspan=3, sticky=tk.EW, pady=6)
|
||||
|
||||
row += 1
|
||||
ttk.Label(main, text="Group Permissions:").grid(
|
||||
row=row, column=0, columnspan=4, sticky=tk.W, pady=2)
|
||||
ttk.Label(main, text="Permissions").grid(row=row, column=0, sticky=tk.W, pady=2)
|
||||
self._perm_label = tk.StringVar(value=_format_perm(self._perm_val))
|
||||
ttk.Label(main, textvariable=self._perm_label,
|
||||
font=("TkDefaultFont", 8)).grid(
|
||||
row=row, column=1, sticky=tk.W, pady=2)
|
||||
ttk.Button(main, text="»", width=3,
|
||||
command=self._open_permissions).grid(
|
||||
row=row, column=2, sticky=tk.E, pady=2)
|
||||
|
||||
perm_groups = (_decode_perm(_get_prop(user_elem, "perm"))
|
||||
if self._is_edit else [[False] * 7 for _ in range(4)])
|
||||
self._perm_vars = []
|
||||
for g in range(4):
|
||||
row += 1
|
||||
ttk.Label(main, text=f"Group {g + 1}:").grid(row=row, column=0, sticky=tk.W)
|
||||
gframe = ttk.Frame(main)
|
||||
gframe.grid(row=row, column=1, columnspan=3, sticky=tk.W)
|
||||
gvars = []
|
||||
for f_idx, label in enumerate(PERM_LABELS):
|
||||
var = tk.BooleanVar(value=perm_groups[g][f_idx])
|
||||
ttk.Checkbutton(gframe, text=label, variable=var).pack(
|
||||
side=tk.LEFT, padx=1)
|
||||
gvars.append(var)
|
||||
self._perm_vars.append(gvars)
|
||||
|
||||
# provisioning
|
||||
# provisioning summary + >> button
|
||||
row += 1
|
||||
ttk.Separator(main, orient=tk.HORIZONTAL).grid(
|
||||
row=row, column=0, columnspan=4, sticky=tk.EW, pady=6)
|
||||
ttk.Label(main, text="Provisioning Permissions").grid(
|
||||
row=row, column=0, sticky=tk.W, pady=2)
|
||||
self._prov_label = tk.StringVar(value=_format_prov(self._prov_val))
|
||||
ttk.Label(main, textvariable=self._prov_label,
|
||||
font=("TkDefaultFont", 8)).grid(
|
||||
row=row, column=1, sticky=tk.W, pady=2)
|
||||
ttk.Button(main, text="»", width=3,
|
||||
command=self._open_provisioning).grid(
|
||||
row=row, column=2, sticky=tk.E, pady=2)
|
||||
|
||||
row += 1
|
||||
ttk.Label(main, text="Provisioning:").grid(row=row, column=0, sticky=tk.W, pady=2)
|
||||
prov_dict = (_decode_prov(_get_prop(user_elem, "prov"))
|
||||
if self._is_edit else {n: False for n, _ in PROV_FLAGS})
|
||||
prov_frame = ttk.Frame(main)
|
||||
prov_frame.grid(row=row, column=1, columnspan=3, sticky=tk.W)
|
||||
self._prov_vars = {}
|
||||
for name, _ in PROV_FLAGS:
|
||||
var = tk.BooleanVar(value=prov_dict.get(name, False))
|
||||
ttk.Checkbutton(prov_frame, text=name, variable=var).pack(
|
||||
side=tk.LEFT, padx=4)
|
||||
self._prov_vars[name] = var
|
||||
|
||||
# buttons
|
||||
# OK / Cancel
|
||||
row += 1
|
||||
btn_frame = ttk.Frame(main)
|
||||
btn_frame.grid(row=row, column=0, columnspan=4, pady=(10, 0))
|
||||
ttk.Button(btn_frame, text="OK", command=self._on_ok).pack(side=tk.LEFT, padx=4)
|
||||
ttk.Button(btn_frame, text="Cancel", command=self._on_cancel).pack(
|
||||
btn_frame.grid(row=row, column=0, columnspan=3, pady=(10, 0))
|
||||
ttk.Button(btn_frame, text="OK", width=10, command=self._on_ok).pack(
|
||||
side=tk.LEFT, padx=4)
|
||||
ttk.Button(btn_frame, text="Cancel", width=10, command=self._on_cancel).pack(
|
||||
side=tk.LEFT, padx=4)
|
||||
|
||||
self._dlg.protocol("WM_DELETE_WINDOW", self._on_cancel)
|
||||
name_entry.focus_set()
|
||||
self._dlg.wait_window()
|
||||
|
||||
def _open_permissions(self):
|
||||
dlg = _PermissionsDialog(self._dlg, self._perm_val)
|
||||
if dlg.result is not None:
|
||||
self._perm_val = dlg.result
|
||||
self._perm_label.set(_format_perm(self._perm_val))
|
||||
|
||||
def _open_provisioning(self):
|
||||
dlg = _ProvisioningDialog(self._dlg, self._prov_val)
|
||||
if dlg.result is not None:
|
||||
self._prov_val = dlg.result
|
||||
self._prov_label.set(_format_prov(self._prov_val))
|
||||
|
||||
def _on_ok(self):
|
||||
username = self._name_var.get().strip()
|
||||
password = self._pass_var.get()
|
||||
@@ -243,32 +405,29 @@ class _UserDialog:
|
||||
messagebox.showwarning("Validation", "Passwords do not match.", parent=self._dlg)
|
||||
return
|
||||
|
||||
# check duplicate name on add
|
||||
if not self._is_edit:
|
||||
for c in _get_users(self._service_comp):
|
||||
if c.get("name", "") == username:
|
||||
messagebox.showwarning("Validation", f"User '{username}' already exists.",
|
||||
messagebox.showwarning("Validation",
|
||||
f"User '{username}' already exists.",
|
||||
parent=self._dlg)
|
||||
return
|
||||
|
||||
perm_groups = [[v.get() for v in gvars] for gvars in self._perm_vars]
|
||||
prov_dict = {name: var.get() for name, var in self._prov_vars.items()}
|
||||
|
||||
if self._is_edit:
|
||||
self._apply_edit(username, password, perm_groups, prov_dict)
|
||||
self._apply_edit(username, password)
|
||||
else:
|
||||
self._apply_add(username, password, perm_groups, prov_dict)
|
||||
self._apply_add(username, password)
|
||||
|
||||
self._dlg.destroy()
|
||||
if self._on_done:
|
||||
self._on_done()
|
||||
|
||||
def _apply_add(self, username, password, perm_groups, prov_dict):
|
||||
def _apply_add(self, username, password):
|
||||
elem = ET.Element("comp", name=username, type="sys::User")
|
||||
cred = _hash_credentials(username, password)
|
||||
ET.SubElement(elem, "prop", name="cred", val=cred)
|
||||
ET.SubElement(elem, "prop", name="perm", val=str(_encode_perm(perm_groups)))
|
||||
ET.SubElement(elem, "prop", name="prov", val=str(_encode_prov(prov_dict)))
|
||||
ET.SubElement(elem, "prop", name="perm", val=str(self._perm_val))
|
||||
ET.SubElement(elem, "prop", name="prov", val=str(self._prov_val))
|
||||
|
||||
index = len(list(self._service_comp))
|
||||
cmd = undo.AddComponentCommand(self._service_comp, index, elem, label="add user")
|
||||
@@ -276,16 +435,16 @@ class _UserDialog:
|
||||
undo.push(cmd)
|
||||
app_state.mark_dirty()
|
||||
|
||||
def _apply_edit(self, username, password, perm_groups, prov_dict):
|
||||
def _apply_edit(self, username, password):
|
||||
user = self._user_elem
|
||||
changes = []
|
||||
|
||||
new_perm = str(_encode_perm(perm_groups))
|
||||
new_perm = str(self._perm_val)
|
||||
old_perm = _get_prop(user, "perm")
|
||||
if new_perm != old_perm:
|
||||
changes.append((user, "perm", old_perm, new_perm))
|
||||
|
||||
new_prov = str(_encode_prov(prov_dict))
|
||||
new_prov = str(self._prov_val)
|
||||
old_prov = _get_prop(user, "prov")
|
||||
if new_prov != old_prov:
|
||||
changes.append((user, "prov", old_prov, new_prov))
|
||||
@@ -412,9 +571,6 @@ class UserServicePanel:
|
||||
self.on_change()
|
||||
|
||||
def _on_add(self):
|
||||
if app_state.live_mode:
|
||||
app_state.set_status("Add user is not available in live mode", warn=True)
|
||||
return
|
||||
_UserDialog(self.frame.winfo_toplevel(), self.comp, None, self._after_change)
|
||||
|
||||
def _on_edit(self):
|
||||
@@ -423,9 +579,6 @@ class UserServicePanel:
|
||||
messagebox.showinfo("Edit User", "Select a user first.",
|
||||
parent=self.frame.winfo_toplevel())
|
||||
return
|
||||
if app_state.live_mode:
|
||||
app_state.set_status("Edit user is not available in live mode", warn=True)
|
||||
return
|
||||
_UserDialog(self.frame.winfo_toplevel(), self.comp, user_elem, self._after_change)
|
||||
|
||||
def _on_delete(self):
|
||||
@@ -434,9 +587,6 @@ class UserServicePanel:
|
||||
messagebox.showinfo("Delete User", "Select a user first.",
|
||||
parent=self.frame.winfo_toplevel())
|
||||
return
|
||||
if app_state.live_mode:
|
||||
app_state.set_status("Delete user is not available in live mode", warn=True)
|
||||
return
|
||||
|
||||
name = user_elem.get("name", "?")
|
||||
if not messagebox.askyesno("Delete User", f"Delete user '{name}'?",
|
||||
|
||||
Reference in New Issue
Block a user