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

16 KiB
Raw Permalink Blame History

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 (тот же зашифрованный файл):

{
  "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:

{
  "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

# Группы 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 цветов:

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 без groupsget_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. Переключить язык — все строки переведены