Files
server-manager/plans/server-groups.md
chrome-storm-c442 c9e3ee8fc5 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>
2026-03-03 02:12:35 -05:00

415 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Server Groups — полный план UI/UX и реализации
## Контекст
ServerManager хранит серверы в плоском списке. При 10+ серверах разных типов (SSH, SQL, Redis, VPN, API, тестовые) становится неудобно. Нужна организация по группам: "Production", "Testing", "VPN Servers", "Hosting" и т.д.
## Исследование рынка
| Инструмент | Подход |
|---|---|
| MobaXterm, mRemoteNG, SecureCRT | Дерево папок, вложенность, drag-and-drop |
| Termius | Группы (вложенные) + теги + цвета — золотой стандарт |
| Royal TS | Папки + наследование credentials от папки |
| AWS/Azure/GCP | Resource Groups + теги (key:value) |
| Zabbix, Datadog | Host Groups + теги, сервер в нескольких группах |
**Выбранный паттерн: Группы с цветами (как Termius)** — один уровень групп, цветовая кодировка, сворачивание/разворачивание.
---
## 1. Модель данных
### Группы хранятся в `servers.json` (тот же зашифрованный файл):
```json
{
"servers": [...],
"ssh_key": {...},
"groups": [
{
"id": "a1b2c3d4",
"name": "Production",
"color": "#ef4444",
"collapsed": false,
"order": 0
},
{
"id": "e5f6g7h8",
"name": "Testing",
"color": "#22c55e",
"collapsed": false,
"order": 1
}
]
}
```
### Сервер получает поле `group`:
```json
{
"alias": "my-prod-server",
"ip": "1.2.3.4",
"type": "ssh",
"group": "a1b2c3d4",
...
}
```
- `group` ссылается на `groups[].id`
- Без `group` или пустой = "Без группы"
- При удалении группы серверы становятся ungrouped
### Почему в `servers.json`, а не `settings.json`:
- Группы тесно связаны с серверами
- Одна атомарная операция save
- `ssh.py` просто игнорирует ключ `groups` — обратная совместимость
---
## 2. API в ServerStore
```python
# Группы CRUD
get_groups() -> list[dict] # Все группы, отсортированы по order
get_group(group_id) -> dict | None # Одна группа по ID
add_group(name, color) -> dict # Создать, вернуть dict
update_group(group_id, **kwargs) # Обновить name/color/collapsed/order
remove_group(group_id) # Удалить, серверы → ungrouped
reorder_groups(ordered_ids: list[str]) # Установить порядок
# Серверы в группах
set_server_group(alias, group_id|None) # Переместить сервер в группу
get_servers_in_group(group_id|None) # Серверы группы (None = ungrouped)
```
---
## 3. UI Sidebar — Сгруппированный вид
### Было (плоский список):
```
+-------------------------------------+
| Servers |
+-------------------------------------+
| [ Search... ] |
+-------------------------------------+
| ● SSH investor 1.2.3.4 |
| ● SSH main-ovh 5.6.7.8 |
| ● SSH API TOR... 9.10.11.12 |
| ● RDP 116 Windows 13.14.15.16 |
| ● RDS Reddis main 5.6.7.8 |
| ● MDB Maria Db... 5.6.7.8 |
| ● SSH sensey24.ru 17.18.19.20 |
| |
| Active: 2 |
| [+ Add] [Edit] [Delete] |
+-------------------------------------+
```
### Станет (группы):
```
+------------------------------------------+
| Servers |
+------------------------------------------+
| [ Search... ] [+ Grp] |
+------------------------------------------+
| |
| v ● Production (3) |
| +----------------------------------+ |
| | ○ SSH main-ovh 5.6.7.8 | |
| +----------------------------------+ |
| | ○ RDS Reddis main 5.6.7.8 | |
| +----------------------------------+ |
| | ○ MDB Maria Db 5.6.7.8 | |
| +----------------------------------+ |
| |
| > ● VPN/Proxy (2) |
| (свёрнуто — серверы скрыты) |
| |
| v ● Hosting (3) |
| +----------------------------------+ |
| | ○ SSH sensey24.ru 17.18.19.20 | |
| +----------------------------------+ |
| | ○ SSH git.sensey 17.18.19.20 | |
| +----------------------------------+ |
| | ○ SSH 1gb server 21.22.23.24 | |
| +----------------------------------+ |
| |
| v ● Testing (1) |
| +----------------------------------+ |
| | ○ SSH thehost 25.26.27.28 | |
| +----------------------------------+ |
| |
| Без группы (1) |
| +----------------------------------+ |
| | ○ RDP 116 Windows 13.14.15.16 | |
| +----------------------------------+ |
| |
| Active: 2 |
| [+ Add] [Edit] [Delete] |
+------------------------------------------+
```
### Заголовок группы (детально):
```
+----------------------------------------------+
| v ● Production (3) |
| ^ ^ ^ ^ |
| | | | | |
| | цвет название кол-во |
| | группы группы серверов |
| стрелка |
| свернуть/развернуть |
+----------------------------------------------+
```
- `v` / `>` — кликабельная стрелка toggle
- `●` — цветная точка (`\u25cf`) цветом группы
- Название — жирный шрифт
- `(3)` — серый, кол-во серверов
- Вся строка кликабельна для toggle
- ПКМ — контекстное меню группы
### Серверы внутри группы:
- Отступ `padx=(12, 2)` для визуальной вложенности
- Всё остальное как сейчас: StatusBadge, TypeBadge, Name, IP
### Поведение:
- **Нет групп** → плоский список (как сейчас, полная обратная совместимость)
- **Есть группы** → автоматически сгруппированный вид
- **Поиск** → все группы разворачиваются, пустые группы скрываются
- **"Без группы"** → только если есть группы И есть серверы без группы
---
## 4. Создание группы
### Кнопка `[+ Grp]` рядом с поиском:
```
| [ Search... ] [+ Grp] |
```
### GroupDialog (новый файл `gui/group_dialog.py`):
```
+----------------------------------+
| New Group |
+----------------------------------+
| |
| Name: |
| [ Production ] |
| |
| Color: |
| (●)(●)(●)(●)(●)(●)(●)(●) |
| red org amb grn blu ind pur pnk |
| |
| [ Cancel ] [ Save ] |
+----------------------------------+
```
Палитра из 8 цветов:
```python
GROUP_COLORS = [
"#ef4444", # красный
"#f97316", # оранжевый
"#f59e0b", # янтарный
"#22c55e", # зелёный
"#3b82f6", # синий
"#6366f1", # индиго
"#a855f7", # фиолетовый
"#ec4899", # розовый
]
```
Каждый цвет — кнопка-кружок. Выбранный — с рамкой.
---
## 5. Перемещение серверов между группами
### Способ 1: ПКМ на сервере → "Переместить в группу"
```
+-----------------------------+
| Open Terminal |
| Browse Files |
| Install Key |
|-----------------------------|
| Move to Group > |
| +---------------------+ |
| | ● Production | |
| | ● VPN/Proxy | |
| | ● Hosting | |
| | ● Testing | |
| |---------------------| |
| | Без группы | |
| +---------------------+ |
|-----------------------------|
| Check Status |
| Copy Alias |
|-----------------------------|
| Edit |
| Delete |
+-----------------------------+
```
### Способ 2: Поле "Группа" в ServerDialog (создание/редактирование)
```
+----------------------------------+
| Add Server |
+----------------------------------+
| Alias: [ ] |
| IP: [ ] |
| Type: [ SSH v ] |
| Port: [ 22 ] |
| Group: [ ● Production v ] |
| +-----------------------+|
| | No group ||
| | ● Production ||
| | ● VPN/Proxy ||
| | ● Hosting ||
| | ● Testing ||
| +-----------------------+|
| User: [ root ] |
| Pass: [ •••••••• [👁] ] |
| ... |
+----------------------------------+
```
- Dropdown появляется только когда есть группы
- По умолчанию "No group"
- При редактировании — предвыбрана текущая группа
### Drag-and-drop:
**НЕ реализуем.** CustomTkinter не имеет нативного DnD для ScrollableFrame. Реализация через низкоуровневый tkinter DnD = хрупко и сложно. ПКМ "Move to Group" — надёжнее и понятнее.
---
## 6. Контекстное меню группы (ПКМ на заголовке)
```
+------------------------+
| Rename |
| Change Color > |
| +------------------+ |
| | ● Red | |
| | ● Orange | |
| | ● Amber | |
| | ● Green | |
| | ● Blue | |
| | ● Indigo | |
| | ● Purple | |
| | ● Pink | |
| +------------------+ |
|------------------------|
| Move Up |
| Move Down |
|------------------------|
| Delete Group |
+------------------------+
```
- **Rename** → открывает GroupDialog в режиме редактирования
- **Change Color** → подменю с 8 цветами
- **Move Up/Down** → меняет `order` местами с соседней группой
- **Delete Group** → подтверждение, серверы → ungrouped
---
## 7. i18n ключи (~17 штук)
| Ключ | EN | RU | ZH |
|------|----|----|-----|
| group | Group | Группа | 分组 |
| groups | Groups | Группы | 分组 |
| no_group | No group | Без группы | 无分组 |
| ungrouped | Ungrouped | Без группы | 未分组 |
| add_group | Add Group | Добавить группу | 添加分组 |
| edit_group | Edit Group | Редактировать группу | 编辑分组 |
| rename_group | Rename | Переименовать | 重命名 |
| delete_group | Delete Group | Удалить группу | 删除分组 |
| delete_group_confirm | Delete '{name}'? Servers become ungrouped. | Удалить '{name}'? Серверы станут без группы. | 删除'{name}'?服务器将变为未分组。 |
| group_name | Group Name | Название группы | 分组名称 |
| group_color | Color | Цвет | 颜色 |
| group_name_required | Group name is required | Название обязательно | 名称为必填项 |
| move_to_group | Move to Group | Переместить в группу | 移动到分组 |
| move_up | Move Up | Вверх | 上移 |
| move_down | Move Down | Вниз | 下移 |
| change_color | Change Color | Изменить цвет | 更改颜色 |
| new_group | New Group | Новая группа | 新建分组 |
---
## 8. Файлы и изменения
| Файл | Что меняется |
|------|-------------|
| `core/server_store.py` | +8 методов для Groups CRUD, `get_servers_in_group()`, `set_server_group()` |
| `gui/sidebar.py` | Рефакторинг `_refresh_list()` на grouped layout, заголовки групп, контекстные меню, collapse/expand, кнопка "+ Grp", "Move to Group" submenu |
| `gui/group_dialog.py` | **НОВЫЙ** — диалог создания/редактирования группы (имя + палитра цветов) |
| `gui/server_dialog.py` | +dropdown "Group" (виден только когда есть группы) |
| `core/i18n.py` | +17 ключей EN/RU/ZH |
| `gui/app.py` | Привязка `sidebar.add_group_callback` к открытию GroupDialog |
---
## 9. Порядок реализации
### Фаза 1: Данные (без UI)
1. Методы GroupsCRUD в `server_store.py`
2. Проверить что `ssh.py` не ломается
### Фаза 2: Sidebar — grouped rendering
1. Рефакторинг `_refresh_list()` на helper-методы
2. `_render_group_header()` с collapse toggle
3. Отступ для серверов внутри групп
4. Поиск с автораскрытием групп
### Фаза 3: Управление группами
1. `group_dialog.py`
2. Кнопка "+ Grp" в sidebar
3. Контекстное меню группы (rename, delete, reorder, color)
### Фаза 4: Назначение серверов группам
1. "Move to Group" в контекстном меню сервера
2. Dropdown "Group" в ServerDialog
### Фаза 5: i18n + polish
1. Все переводы
2. Edge cases: пустые группы, все ungrouped, одна группа
---
## 10. Edge Cases
- **Нет групп** → плоский список, полная обратная совместимость
- **Удаление группы** → серверы переходят в "Без группы"
- **Группа ссылается на удалённый ID** → сервер отображается как ungrouped
- **Поиск** → все группы разворачиваются, пустые скрываются
- **Миграция** → старый `servers.json` без `groups``get_groups()` возвращает `[]`
- **ssh.py** → игнорирует `groups` и `group` поля, нулевой impact
---
## 11. Верификация
1. `python build.py` — собрать exe
2. Без групп — плоский список как раньше
3. Создать группу "Production" красным цветом
4. Создать группу "Testing" зелёным
5. Переместить серверы в группы через ПКМ
6. Свернуть/развернуть группу
7. Поиск — группы разворачиваются
8. Добавить новый сервер — выбрать группу в диалоге
9. Удалить группу — серверы стали ungrouped
10. Переименовать группу, сменить цвет
11. Move Up/Down — порядок меняется
12. Переключить язык — все строки переведены