# LPRA 0.1.3 — рабочее пространство (подпроект 1) Дата: 2026-10-15. Статус: согласовано в обсуждении, ждёт проверки спецификации. Подпроект 2 (встроенный ИИ-помощник) — отдельная спецификация, делается после этого. ## Цель Работа с несколькими машинами в одном месте, без потери открытых терминалов при переходах по панели. Терминалы, файловые менеджеры (своих машин и по SSH) живут во вкладках одного рабочего пространства; файлы перетаскиваются между панелями и в терминал. Критерии готовности: - переход Машины ↔ Терминал ↔ Настройки не обрывает сессии терминалов и не сбрасывает вкладки; - перезагрузка страницы сбрасывает пространство (с предупреждением браузера, если есть живые сессии); - файловый менеджер — тип панели во вкладке, в том числе по SSH; - перетаскивание файлов: между файловыми панелями (окно «переместить / копировать»), в терминал той же машины (вставка путей), в терминал другой машины — запрет с сообщением. ## Решения, принятые в обсуждении | вопрос | решение | |---|---| | Страницы «Порты», «Статус», «Изменить» | окна поверх текущей страницы | | Файлы рядом с терминалами в одной вкладке | да: любая из до 4 панелей — терминал, SSH-терминал, файлы, SSH-файлы | | Перезагрузка при живых сессиях | вопрос браузера «Покинуть сайт?» | | Архитектура | одностраничная оболочка, остальное — HTML-фрагменты с сервера (вариант A) | | Файлы по SSH | без SFTP-библиотеки: команды на хосте через SSH, как у существующей передачи tar | | Состояние | одно на вкладку браузера, в памяти страницы; не сохраняется при перезагрузке | ## 1. Каркас и маршруты **Оболочка.** Адреса `/`, `/terminal`, `/settings` отдают один шаблон `app.html`: шапка (LPRA, «Машины · Терминал · Настройки» — «Настройки» только администраторам, статус активной панели, аккаунт) и три области `
`. Старые адреса `/agents/{id}/terminal` и `/agents/{id}/files` отдают ту же оболочку и при загрузке создают вкладку нужного типа для этой машины (закладки не ломаются). **Роутер** (`static/app.js`, без библиотек): - перехватывает клики по внутренним ссылкам и `popstate`; меняет видимую область через `history.pushState`, подсвечивает пункт шапки; - область «Терминал» создаётся один раз и потом только скрывается (`hidden`) — сессии, WebSocket и xterm живут, пока жива вкладка браузера; - «Машины» и «Настройки» при каждом показе подгружаются заново как фрагмент: `GET /?frag=1`, `GET /settings?frag=1`. **Фрагменты на сервере.** `render()` получает режим фрагмента (параметр `frag=1`): шаблон отдаёт только содержимое, без ``, шапки и подключений. Общая разметка `head` и `topbar`, сейчас скопированная в каждый шаблон, выносится в `{{define}}`-блоки. **Формы во фрагментах** (`app.js`, перехват `submit` внутри областей и окон): 1. `onsubmit`-обработчики (`confirm(...)`) срабатывают как обычно; отказ — ничего не происходит; 2. форма отправляется через `fetch` (`redirect: 'follow'`, тело — `FormData`); 3. итоговый адрес ответа определяет, что перерисовать: тот же фрагмент по этому адресу (сохраняются `?err=...`, `?updated=1`, `#users`); 4. формы на `/logout` и ссылки наружу работают обычной загрузкой страницы. **Скрипты фрагментов.** Встроенные `