- 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>
415 lines
16 KiB
Markdown
415 lines
16 KiB
Markdown
# 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. Переключить язык — все строки переведены
|