diff --git a/core/server_store.py b/core/server_store.py index b873ff3..8c99371 100644 --- a/core/server_store.py +++ b/core/server_store.py @@ -58,6 +58,7 @@ class ServerStore: self._last_backup_hash: str = "" self._terminal_font_size: int = 11 self._window_geometry: str = "" + self._sidebar_width: int = 250 self._servers_file: str = DEFAULT_SERVERS_FILE # Update settings self._update_mode: str = "auto-download" # "notify-only" | "auto-download" | "full-auto" @@ -83,6 +84,7 @@ class ServerStore: self._check_interval = settings.get("check_interval", 60) self._terminal_font_size = settings.get("terminal_font_size", 11) self._window_geometry = settings.get("window_geometry", "") + self._sidebar_width = settings.get("sidebar_width", 250) self._update_mode = settings.get("update_mode", "auto-download") self._last_update_check = settings.get("last_update_check", 0) self._skip_version = settings.get("skip_version", "") @@ -100,6 +102,7 @@ class ServerStore: "check_interval": self._check_interval, "terminal_font_size": self._terminal_font_size, "window_geometry": self._window_geometry, + "sidebar_width": self._sidebar_width, "update_mode": self._update_mode, "last_update_check": self._last_update_check, "skip_version": self._skip_version, @@ -394,6 +397,89 @@ class ServerStore: self._save() self._notify() + # ── Groups CRUD ───────────────────────────────── + + def get_groups(self) -> list[dict]: + """Return all groups sorted by order.""" + groups = list(self._data.get("groups", [])) + groups.sort(key=lambda g: g.get("order", 0)) + return groups + + def get_group(self, group_id: str) -> Optional[dict]: + for g in self._data.get("groups", []): + if g["id"] == group_id: + return dict(g) + return None + + def add_group(self, name: str, color: str = "#6b7280") -> dict: + """Create a new group, return the created dict.""" + import uuid + groups = self._data.setdefault("groups", []) + max_order = max((g.get("order", 0) for g in groups), default=-1) + group = { + "id": uuid.uuid4().hex[:8], + "name": name, + "color": color, + "collapsed": False, + "order": max_order + 1, + } + groups.append(group) + self._save() + self._notify() + return group + + def update_group(self, group_id: str, **kwargs): + """Update group fields (name, color, collapsed, order).""" + for g in self._data.get("groups", []): + if g["id"] == group_id: + for k, v in kwargs.items(): + if k in ("name", "color", "collapsed", "order"): + g[k] = v + self._save() + self._notify() + return + raise ValueError(f"Group '{group_id}' not found") + + def remove_group(self, group_id: str): + """Delete group. Servers in it become ungrouped.""" + self._data["groups"] = [g for g in self._data.get("groups", []) if g["id"] != group_id] + for s in self._data.get("servers", []): + if s.get("group") == group_id: + s.pop("group", None) + self._save() + self._notify() + + def reorder_groups(self, ordered_ids: list[str]): + """Set group order based on list of IDs.""" + id_to_order = {gid: i for i, gid in enumerate(ordered_ids)} + for g in self._data.get("groups", []): + if g["id"] in id_to_order: + g["order"] = id_to_order[g["id"]] + self._save() + self._notify() + + def set_server_group(self, alias: str, group_id: Optional[str]): + """Move a server to a group (or None to ungroup).""" + for s in self._data.get("servers", []): + if s["alias"] == alias: + if group_id: + s["group"] = group_id + else: + s.pop("group", None) + self._save() + self._notify() + return + raise ValueError(f"Server '{alias}' not found") + + def get_servers_in_group(self, group_id: Optional[str]) -> list[dict]: + """Return servers in a group. None = ungrouped.""" + all_servers = self._data.get("servers", []) + group_ids = {g["id"] for g in self._data.get("groups", [])} + if group_id is None: + return [s for s in all_servers + if not s.get("group") or s.get("group") not in group_ids] + return [s for s in all_servers if s.get("group") == group_id] + def get_ssh_key_path(self) -> str: path = self._data.get("ssh_key", {}).get("path", "~/.ssh/id_ed25519") return os.path.expanduser(path) diff --git a/gui/app.py b/gui/app.py index 6bfda74..f08946a 100644 --- a/gui/app.py +++ b/gui/app.py @@ -125,7 +125,7 @@ class App(ctk.CTk): # Sidebar self.sidebar = Sidebar(self._paned, self.store, on_select=self._on_server_select, session_pool=self.session_pool) - self._paned.add(self.sidebar, minsize=180, width=250) + self._paned.add(self.sidebar, minsize=180, width=self.store._sidebar_width) self.sidebar.add_callback = self._add_server self.sidebar.edit_callback = self._edit_server self.sidebar.delete_callback = self._delete_server @@ -647,9 +647,16 @@ class App(ctk.CTk): pass def _on_close(self): - # Save window geometry (size + position) + # Save window geometry (size + position) and sidebar width try: self.store._window_geometry = self.geometry() + # Save sidebar width from PanedWindow sash position + try: + sash_pos = self._paned.sash_coord(0) + if sash_pos: + self.store._sidebar_width = sash_pos[0] + except Exception: + pass self.store._save_settings() except Exception: pass diff --git a/releases/ServerManager-v1.8.96-win-x64.exe b/releases/ServerManager-v1.8.96-win-x64.exe new file mode 100644 index 0000000..017b1fa Binary files /dev/null and b/releases/ServerManager-v1.8.96-win-x64.exe differ diff --git a/version.py b/version.py index 73ec036..c8d7886 100755 --- a/version.py +++ b/version.py @@ -1,6 +1,6 @@ """Version info for ServerManager.""" -__version__ = "1.8.95" +__version__ = "1.8.96" __app_name__ = "ServerManager" __author__ = "aibot777" __description__ = "Desktop GUI for managing remote servers"