mt5-manager/docs/architecture.md
142 lines
11 KiB
Markdown
# Устройство приложения и API
[Документация](README.md) · [Конфигурация](configuration.md) · [Эксплуатация](maintenance.md)
## Компоненты
```text
Браузер → FastAPI → TerminalManager → процессы MT5 (/portable)
→ ConfigStore → личный JSON
→ MT5_Control → psutil + чтение SQLite
Каждый MT5 → ServiceStateWriter → общая SQLite в Common/Files
```
| Файл | Ответственность |
|---|---|
| `run.py` | Параметры запуска Uvicorn; `--reload` включается только явно |
| `main.py` | Страницы, проверка входных данных, HTTP API и время жизни менеджера |
| `config.py` | Проверка JSON, разрешение путей и атомарное сохранение |
| `terminal_manager.py` | Создание, подключение счетов, запуск, остановка и очередь задач |
| `mt5_control.py` | Поиск процессов по пути EXE и чтение последних данных сервиса |
| `project_updates.py` | Проверка upstream, fast-forward, сборка и копирование сервиса |
| `ServiceStateWriter.mq5` | Регистрация терминала и запись текущего состояния |
| `deploy_service.py` | Первоначальный сценарий развёртывания; общая функция компиляции |
| `templates/`, `static/` | Jinja2, локальные CSS/SVG и JavaScript без Bootstrap/jQuery |
SQLite содержит `terminals` с уникальным именем папки и сведениями об установке
и `states` с последним снимком счёта/соединения. Сервис работает независимо от
графиков, использует WAL, тайм-аут блокировки и UPSERT. История ещё не записывается.
Временные метки — Unix-секунды UTC (`TimeGMT()` на стороне MQL5).
Python определяет наличие процесса через `psutil`. Если процесс есть и данные
не старее 30 секунд, внутренний статус — `running`; иначе при живом процессе
`starting`, при отсутствии — `stopped`. Интерфейс дополнительно различает
подключённый счёт и отсутствие торгового соединения.
## Фоновые операции
Длительная команда возвращает HTTP 202 и объект задачи. Это подтверждает постановку
в очередь, а не успех. Клиент запрашивает `GET /api/jobs/{id}` до `done` или `error`.
Задачи выполняются одним рабочим потоком; повторная команда для того же экземпляра
пока его задача не завершена отклоняется. Глобальные операции проекта не принимаются
при других незавершённых задачах и на время своей работы блокируют новые команды
терминалам.
```json
{
"id": "идентификатор",
"folder": "MetaTrader5.5",
"action": "create",
"status": "queued",
"stage": "Ожидание",
"warnings": []
}
```
Состояния: `queued`, `running`, `done`, `error`. При ошибке добавляется `error`,
у операций проекта — также `result` при наличии результата. В глобальных задачах
`folder` — пустая строка. Задачи находятся в памяти; старые завершённые записи
постепенно удаляются (сохраняется примерно 50 последних). Они не переживают
перезапуск менеджера и не являются постоянным журналом.
## Маршруты
| Метод и путь | Назначение |
|---|---|
| `GET /` | Главная страница |
| `GET /settings` | Настройки |
| `POST /instances/{name}` | Текущее состояние указанного экземпляра |
| `POST /start/{name}` | Запуск и проверка, 202 |
| `POST /stop/{name}` | Остановка, 202 |
| `POST /create/{name}` | Создание с именем в пути, 202; тело `AccountInput` необязательно |
| `POST /api/terminals` | Создание, 202; имя можно предложить автоматически |
| `PUT /api/terminals/{name}/account` | Сохранение/применение реквизитов, 202 |
| `GET /api/jobs/{id}` | Состояние задачи; неизвестный id — 404 |
| `GET /api/settings` | Общие параметры, записи счетов без паролей, файлы и предлагаемое имя |
| `PUT /api/settings` | Сохранение общих настроек |
| `PUT /api/files/{kind}` | Загрузка исходного файла как необработанного тела запроса |
| `GET /api/project` | Локальные сведения Git, сборки, различий EX5 и автозапуска без сетевого fetch |
| `POST /api/project/check` | Проверка удалённой ветки, 202 |
| `POST /api/project/update` | Загрузка через fast-forward, 202 |
| `POST /api/project/deploy` | Принудительная сборка/копирование сервиса, 202 |
`AccountInput` содержит `name`, `login`, `server`, `password`, `clear_account`.
Для создания через `/api/terminals` к нему добавляется `folder`; пустое значение
выбирает свободное имя автоматически. Пример без счёта:
```json
{"folder": "MetaTrader5.5", "name": "Новый экземпляр"}
```
`PUT /api/settings` принимает полный набор редактируемых общих полей:
`mt5_folder`, `terminal_template`, `mt5_installation`, `service_ex5`, `startup_timeout_sec`, `disk_warning_mb`.
Остальная конфигурация сохраняется. Это не универсальный PATCH произвольного JSON.
Пути экземпляров вычисляются как `mt5_folder / ключ в terminals` общей функцией
`instance_directory()`. В JSON у экземпляров нет собственного `path`; старое поле
игнорируется при чтении. В ответах API поле `path` остаётся вычисляемым значением
для отображения, а не настройкой расположения.
Загрузки: `kind` равен `terminal`, `editor`, `servers` или `service`.
Используется бинарное тело, не multipart; предел для `servers` — 16 МиБ,
для остальных — 256 МиБ. Пустые файлы отклоняются, у EXE проверяется сигнатура MZ.
Это не полная проверка содержимого исполняемого файла.
Ошибки входных типов возвращают 422 без исходного объекта и пароля; известные
ошибки запуска задач/настроек — обычно 400. Ошибка уже принятой операции видна
в объекте задачи при HTTP 200 на запрос её состояния. Интерактивная схема FastAPI
доступна по `/docs`, OpenAPI — `/openapi.json`.
## Создание и авторизация
Новый экземпляр получает два EXE, `config/servers.dat`, сервис и `config/services.ini`
с именем общей базы. В JSON он регистрируется до запуска. Ожидание сервиса проверяет
свежесть, полный путь установки и при заданном счёте — логин, сервер и подключение.
При неудаче удаляется только новая папка, принадлежащая этой операции, и её запись.
Для первого входа используются временный INI и `KeepPrivate=1` в основной
конфигурации терминала. После успешного подключения главное окно закрывается
через `WM_CLOSE`, временный файл удаляется, затем проверяется обычный запуск.
Обновление существующего счёта не удаляет папку терминала при ошибке.
## Проверки разработки
```powershell
python -m unittest discover -s tests -v
```
`test_provisioning.py` использует временные папки и имитирует границу с MT5,
проверяя выбор файлов, учётные записи, сохранение и откат. `test_http.py` поднимает
изолированный сервер на свободном порту и проверяет маршруты и отсутствие паролей
в ответах. `test_project_updates.py` работает с временным локальным Git-remote:
проверяет fetch, fast-forward, защиту правок и распространение EX5 с имитацией ME.
Сетевой репозиторий и рабочие терминалы пользователя тесты не меняют.
Для ручной проверки реального MT5 есть `tools/smoke_create_terminal.py`.
Он создаёт отдельную тестовую копию и базу, затем удаляет собственные файлы.
Инструмент привязан к локальной установке `C:\MT5\MetaTrader5.1` и пути общей базы
из `config.json`; перед использованием проверьте эти значения. Вариант
`--with-account` запрашивает реквизиты при запуске; логин и сервер можно передать
через `--login` и `--server`, пароль вводится скрыто через `getpass` и не передаётся
аргументом командной строки. Файл `tools/test_first_login.py` не используется.
Это инструмент разработчика, а не шаг установки.