server groups: grouped sidebar, GroupDialog, context menus, i18n + cleanup old releases

- Groups CRUD in sidebar (create, rename, change color, reorder, delete)
- Collapsible group headers with color dots and server count
- "Move to Group" context menu on servers
- Group dropdown in ServerDialog (add/edit)
- 17 i18n keys (EN/RU/ZH)
- Search auto-expands groups
- Cleaned up old release binaries

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
chrome-storm-c442
2026-03-03 02:12:35 -05:00
parent 2f84429b10
commit c9e3ee8fc5
17 changed files with 852 additions and 24 deletions

View File

@@ -99,6 +99,33 @@ class ServerDialog(ctk.CTkToplevel):
self.port_entry = ctk.CTkEntry(port_frame, placeholder_text=t("placeholder_port"))
self.port_entry.pack(fill="x")
# ── Group selector (only when groups exist) ──
self._group_id_map: dict[str, str | None] = {}
self._group_var = ctk.StringVar(value=t("no_group"))
groups = self.store.get_groups()
if groups:
group_frame = ctk.CTkFrame(self, fg_color="transparent")
group_frame.pack(fill="x", padx=20, pady=(5, 5))
ctk.CTkLabel(group_frame, text=t("group"), anchor="w").pack(fill="x")
no_group_label = t("no_group")
group_values = [no_group_label]
self._group_id_map[no_group_label] = None
for g in groups:
display = f"\u25cf {g['name']}"
group_values.append(display)
self._group_id_map[display] = g["id"]
ctk.CTkOptionMenu(group_frame, values=group_values,
variable=self._group_var).pack(fill="x")
# Pre-select if editing
if server and server.get("group"):
for display, gid in self._group_id_map.items():
if gid == server.get("group"):
self._group_var.set(display)
break
# ── Conditional fields container — all packed here, shown/hidden dynamically ──
# We use self as parent but wrap each field group in a frame for easy show/hide.
@@ -347,6 +374,12 @@ class ServerDialog(ctk.CTkToplevel):
}
if totp_secret:
server_data["totp_secret"] = totp_secret
# Group assignment
if self._group_id_map:
selected_group = self._group_id_map.get(self._group_var.get())
if selected_group:
server_data["group"] = selected_group
if self.skip_check_var.get():
server_data["skip_check"] = True