docs: stage browser alias + UX backlog (P0–P2)
This commit is contained in:
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
description: Как открыть stage UI в Cursor Browser (alias + proxy)
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Stage browser (Cursor)
|
||||||
|
|
||||||
|
`*.eventhub.local` часто не резолвится в Chromium/Cursor Browser.
|
||||||
|
|
||||||
|
Канон: **`http://ui.stage.eventhub.test:8787`** через локальный proxy.
|
||||||
|
|
||||||
|
Полная инструкция: `EventHubSpec/STAGE-BROWSER.md`
|
||||||
|
Скрипт: `EventHubSpec/scripts/stage-browser-alias-proxy.mjs` (запуск **Windows Node** на `:8787`).
|
||||||
|
|
||||||
|
Hosts: `127.0.0.1 ui.stage.eventhub.test`
|
||||||
@@ -3,6 +3,8 @@
|
|||||||
## 2. [EventHub Admin UI — Техническое задание](EventHubFrontAdminSpec.md)
|
## 2. [EventHub Admin UI — Техническое задание](EventHubFrontAdminSpec.md)
|
||||||
## 3. [EventHub Client UI — Техническое задание](EventHubFrontSpec.md)
|
## 3. [EventHub Client UI — Техническое задание](EventHubFrontSpec.md)
|
||||||
## 4. [Правила работы (workflow)](WORKFLOW.md)
|
## 4. [Правила работы (workflow)](WORKFLOW.md)
|
||||||
|
## 5. [Stage UI в Cursor Browser](STAGE-BROWSER.md)
|
||||||
|
## 6. [UX backlog (Client UI)](UX-BACKLOG.md)
|
||||||
|
|
||||||
# **Репозитории разработки EventHub**
|
# **Репозитории разработки EventHub**
|
||||||
## 1. [EventHub Backend](https://git.sabilin.com/EventHub/EventHubBack)
|
## 1. [EventHub Backend](https://git.sabilin.com/EventHub/EventHubBack)
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# Stage UI в Cursor Browser
|
||||||
|
|
||||||
|
Chromium / Cursor IDE Browser часто **не резолвят** `*.eventhub.local` (mDNS), даже если Windows/`nslookup` через OpenWRT уже видят stage.
|
||||||
|
|
||||||
|
Для агентов и ручного просмотра stage используем **локальный alias + HTTP-прокси**.
|
||||||
|
|
||||||
|
## URL
|
||||||
|
|
||||||
|
| Открывать в браузере | Upstream |
|
||||||
|
|----------------------|----------|
|
||||||
|
| `http://ui.stage.eventhub.test:8787` | `https://ui.stage.eventhub.local` → `195.208.119.190` |
|
||||||
|
|
||||||
|
## Одноразовая настройка hosts
|
||||||
|
|
||||||
|
В `C:\Windows\System32\drivers\etc\hosts` (или WSL `/etc/hosts` не помогает Cursor на Windows):
|
||||||
|
|
||||||
|
```text
|
||||||
|
127.0.0.1 ui.stage.eventhub.test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Прокси
|
||||||
|
|
||||||
|
Скрипт в репозитории: [`scripts/stage-browser-alias-proxy.mjs`](scripts/stage-browser-alias-proxy.mjs)
|
||||||
|
|
||||||
|
Копия для быстрого старта агента: `~/.cursor/eventhub/stage-browser-alias-proxy.mjs` (если есть — можно любой из двух).
|
||||||
|
|
||||||
|
**Важно:** процесс должен слушать на **Windows** `127.0.0.1:8787` (Node из Windows). Прокси только в WSL Cursor с Windows часто не достучится.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Start-Process -FilePath "C:\Program Files\nodejs\node.exe" `
|
||||||
|
-ArgumentList "$env:USERPROFILE\.cursor\eventhub\stage-browser-alias-proxy.mjs" `
|
||||||
|
-WindowStyle Hidden
|
||||||
|
```
|
||||||
|
|
||||||
|
Или из Spec (если скрипт уже скопирован в `~/.cursor/eventhub/`):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# из корня EventHubSpec
|
||||||
|
Start-Process -FilePath "C:\Program Files\nodejs\node.exe" `
|
||||||
|
-ArgumentList (Resolve-Path ".\scripts\stage-browser-alias-proxy.mjs") `
|
||||||
|
-WindowStyle Hidden
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверка: открыть `http://ui.stage.eventhub.test:8787/login`.
|
||||||
|
|
||||||
|
Переменные окружения (опционально):
|
||||||
|
|
||||||
|
| Env | Default |
|
||||||
|
|-----|---------|
|
||||||
|
| `ALIAS_LISTEN_HOST` | `127.0.0.1` |
|
||||||
|
| `ALIAS_LISTEN_PORT` | `8787` |
|
||||||
|
| `STAGE_IP` | `195.208.119.190` |
|
||||||
|
| `STAGE_UI_HOST` | `ui.stage.eventhub.local` |
|
||||||
|
| `ALIAS_HOST` | `ui.stage.eventhub.test` |
|
||||||
|
|
||||||
|
## Auth
|
||||||
|
|
||||||
|
Same-origin через прокси: `/v1/login`, `/v1/register`, logout — как на настоящем stage.
|
||||||
|
|
||||||
|
- Smoke: `~/.cursor/secrets/eventhub-stage.env` (`SMOKE_USER_*`).
|
||||||
|
- IFT e2e: только `~/.cursor/secrets/eventhub-ift.env` (не подмешивать stage).
|
||||||
|
- Прокси **не** подставляет пользователя: только Host/SNI rewrite на Traefik.
|
||||||
|
|
||||||
|
## Что не делать
|
||||||
|
|
||||||
|
- Не ждать, что Cursor Browser сам пойдёт на `https://ui.stage.eventhub.local`.
|
||||||
|
- Не поднимать прокси только в WSL, если агентский браузер на Windows.
|
||||||
|
- Не коммитить пароли/токены из smoke env.
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# EventHub Front — UX backlog (после stage walk)
|
||||||
|
|
||||||
|
Оценка baseline ~6.8/10. После P0 ориентир ~7.8–8.2; P0+P1 ~8.3–8.6.
|
||||||
|
|
||||||
|
См. также canvas `eventhub-front-ux-review` в Cursor.
|
||||||
|
Трекер: [EventHubFront#17](https://git.sabilin.com/EventHub/EventHubFront/issues/17).
|
||||||
|
|
||||||
|
## P0
|
||||||
|
|
||||||
|
- [x] Форматировать даты локалью (ru-RU / текущий i18n) везде: Записи, события, подписка
|
||||||
|
- [x] Убрать / спрятать opaque id из consumer UI (календари, поиск, специалисты)
|
||||||
|
- [x] Починить дубль title события (`displayTitle` — схлопывание `TitleTitle`)
|
||||||
|
|
||||||
|
## P1
|
||||||
|
|
||||||
|
- [x] Локализовать статусы + semantic pills (`StatusBadge`)
|
||||||
|
- [x] В заявках на событии показывать display name / email (`user_nickname` / `user_email` с API)
|
||||||
|
- [x] **Specialists invite: одно поле адресата** — lookup + email без отдельного «Или email»
|
||||||
|
|
||||||
|
## P2
|
||||||
|
|
||||||
|
- [ ] Управление календарями: поиск, фильтр personal/commercial, pin/недавние
|
||||||
|
- [ ] Контраст метаданных профиля в dark / «Энергичный» mood — частично (`text-foreground` на значениях)
|
||||||
+24
@@ -62,6 +62,19 @@ IFT smoke Admin UI (живой API после деплоя): `npm run test:e2e:i
|
|||||||
|
|
||||||
Окружение: **WSL + asdf**. PowerShell/Windows — только IDE; сборка и тесты — в WSL.
|
Окружение: **WSL + asdf**. PowerShell/Windows — только IDE; сборка и тесты — в WSL.
|
||||||
|
|
||||||
|
### Shell для Agent (обязательно)
|
||||||
|
|
||||||
|
PowerShell в Cursor **ломает экранирование** (кавычки, `$`, JSON, heredoc, вложенный `bash -lc "..."`).
|
||||||
|
|
||||||
|
| Нельзя | Нужно |
|
||||||
|
|--------|--------|
|
||||||
|
| Сложные one-liner’ы в PowerShell | Файл `.sh` + `wsl -e bash /mnt/c/.../script.sh` |
|
||||||
|
| `wsl -e bash -lc "…\"…\"…"` | `wsl -e bash -lc 'cd … && cmd'` (снаружи `'…'`) |
|
||||||
|
| Windows `npm` / `erl` / `rebar3` | npm и rebar **только в WSL** |
|
||||||
|
| Push Front без локальной проверки | WSL: `npm run lint && npm run build && npm run test:e2e:ift`, потом push |
|
||||||
|
|
||||||
|
Канон для агентов: `EventHubBack/.cursor/rules/shell-wsl-not-powershell.mdc`.
|
||||||
|
|
||||||
| Tool | Version | Path (WSL) |
|
| Tool | Version | Path (WSL) |
|
||||||
|------|---------|------------|
|
|------|---------|------------|
|
||||||
| Erlang/OTP | **28.2** | `/home/alexc/.asdf/installs/erlang/28.2/bin` |
|
| Erlang/OTP | **28.2** | `/home/alexc/.asdf/installs/erlang/28.2/bin` |
|
||||||
@@ -260,6 +273,15 @@ IFT и stage крутят **одну и ту же** сборку `sha-*`. Produc
|
|||||||
- **IFT smoke:** после успешного Deploy IFT — workflow `e2e-ift.yml` → `npm run test:e2e:ift` (без моков). Учётки с хоста IFT: `/opt/eventhub-ift/.env` (`SMOKE_ADMIN_*` → `ADMIN_SUPER_*`).
|
- **IFT smoke:** после успешного Deploy IFT — workflow `e2e-ift.yml` → `npm run test:e2e:ift` (без моков). Учётки с хоста IFT: `/opt/eventhub-ift/.env` (`SMOKE_ADMIN_*` → `ADMIN_SUPER_*`).
|
||||||
- Селекторы: `data-testid` + a11y; контракт в `EventHubFrontAdmin/e2e/TESTIDS.md`.
|
- Селекторы: `data-testid` + a11y; контракт в `EventHubFrontAdmin/e2e/TESTIDS.md`.
|
||||||
|
|
||||||
|
### Stage UI в Cursor Browser
|
||||||
|
|
||||||
|
`*.eventhub.local` часто не резолвится в Chromium/Cursor IDE Browser (mDNS). Для ручного и агентского просмотра stage:
|
||||||
|
|
||||||
|
- URL: `http://ui.stage.eventhub.test:8787`
|
||||||
|
- Инструкция + proxy: **`STAGE-BROWSER.md`**, скрипт `scripts/stage-browser-alias-proxy.mjs` (Windows Node на `:8787`)
|
||||||
|
- Hosts: `127.0.0.1 ui.stage.eventhub.test`
|
||||||
|
- Учётки smoke: `~/.cursor/secrets/eventhub-stage.env` (не коммитить)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. Нагрузочное тестирование IFT (Tsung)
|
## 7. Нагрузочное тестирование IFT (Tsung)
|
||||||
@@ -354,6 +376,8 @@ error rate > 1% **или** mean request > 500 ms (hold ≥2 мин в фазе)
|
|||||||
|
|
||||||
| Дата | Изменение |
|
| Дата | Изменение |
|
||||||
|------|-----------|
|
|------|-----------|
|
||||||
|
| 2026-07-22 | §6: Stage UI в Cursor Browser — `STAGE-BROWSER.md` + alias proxy |
|
||||||
|
| 2026-07-22 | §2.1: Agent shell — не PowerShell для сложного; `.sh` + WSL; Front push только после lint/build/IFT |
|
||||||
| 2026-07-15 | §7: анализ через EventHub `/metrics` + admin `nodes/metrics`; compose Prom опционален |
|
| 2026-07-15 | §7: анализ через EventHub `/metrics` + admin `nodes/metrics`; compose Prom опционален |
|
||||||
| 2026-07-15 | §7: cleanup дампов на IFT + мониторинг в анализе; KEEP_OBSERVABILITY |
|
| 2026-07-15 | §7: cleanup дампов на IFT + мониторинг в анализе; KEEP_OBSERVABILITY |
|
||||||
| 2026-07-15 | §7: сбор и анализ логов — обязательный gate после stress |
|
| 2026-07-15 | §7: сбор и анализ логов — обязательный gate после stress |
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Cursor-browser alias for stage UI.
|
||||||
|
*
|
||||||
|
* Why: Chromium treats *.local as mDNS and often ignores unicast DNS / hosts.
|
||||||
|
* This proxy serves http://ui.stage.eventhub.test:8787 → https://stage
|
||||||
|
* with Host/SNI ui.stage.eventhub.local (Traefik + existing cert).
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node stage-browser-alias-proxy.mjs
|
||||||
|
* open http://ui.stage.eventhub.test:8787/login
|
||||||
|
*
|
||||||
|
* Requires hosts: 127.0.0.1 ui.stage.eventhub.test
|
||||||
|
*
|
||||||
|
* Docs: EventHubSpec/STAGE-BROWSER.md
|
||||||
|
*/
|
||||||
|
import http from 'node:http';
|
||||||
|
import https from 'node:https';
|
||||||
|
|
||||||
|
const LISTEN_HOST = process.env.ALIAS_LISTEN_HOST || '127.0.0.1';
|
||||||
|
const LISTEN_PORT = Number(process.env.ALIAS_LISTEN_PORT || 8787);
|
||||||
|
const UPSTREAM_IP = process.env.STAGE_IP || '195.208.119.190';
|
||||||
|
const UPSTREAM_HOST = process.env.STAGE_UI_HOST || 'ui.stage.eventhub.local';
|
||||||
|
const ALIAS_HOST = process.env.ALIAS_HOST || 'ui.stage.eventhub.test';
|
||||||
|
|
||||||
|
const agent = new https.Agent({
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
servername: UPSTREAM_HOST,
|
||||||
|
keepAlive: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
function rewriteLocation(value) {
|
||||||
|
if (!value) return value;
|
||||||
|
return value
|
||||||
|
.replaceAll(`https://${UPSTREAM_HOST}`, `http://${ALIAS_HOST}:${LISTEN_PORT}`)
|
||||||
|
.replaceAll(`http://${UPSTREAM_HOST}`, `http://${ALIAS_HOST}:${LISTEN_PORT}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrubHeaders(headers) {
|
||||||
|
const out = { ...headers, host: UPSTREAM_HOST };
|
||||||
|
delete out['content-length'];
|
||||||
|
// Avoid compression surprises while piping
|
||||||
|
delete out['accept-encoding'];
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function proxyHttp(req, res) {
|
||||||
|
const opts = {
|
||||||
|
hostname: UPSTREAM_IP,
|
||||||
|
port: 443,
|
||||||
|
path: req.url,
|
||||||
|
method: req.method,
|
||||||
|
headers: scrubHeaders(req.headers),
|
||||||
|
agent,
|
||||||
|
};
|
||||||
|
const up = https.request(opts, (upRes) => {
|
||||||
|
const headers = { ...upRes.headers };
|
||||||
|
if (headers.location) headers.location = rewriteLocation(headers.location);
|
||||||
|
res.writeHead(upRes.statusCode || 502, headers);
|
||||||
|
upRes.pipe(res);
|
||||||
|
});
|
||||||
|
up.on('error', (err) => {
|
||||||
|
if (!res.headersSent) res.writeHead(502, { 'content-type': 'text/plain' });
|
||||||
|
res.end(`alias-proxy upstream error: ${err.message}`);
|
||||||
|
});
|
||||||
|
req.pipe(up);
|
||||||
|
}
|
||||||
|
|
||||||
|
function proxyUpgrade(req, socket, head) {
|
||||||
|
const opts = {
|
||||||
|
hostname: UPSTREAM_IP,
|
||||||
|
port: 443,
|
||||||
|
path: req.url,
|
||||||
|
method: 'GET',
|
||||||
|
headers: scrubHeaders(req.headers),
|
||||||
|
agent,
|
||||||
|
};
|
||||||
|
const up = https.request(opts);
|
||||||
|
up.on('upgrade', (upRes, upSocket, upHead) => {
|
||||||
|
const lines = [`HTTP/1.1 ${upRes.statusCode} Switching Protocols`];
|
||||||
|
for (const [k, v] of Object.entries(upRes.headers)) {
|
||||||
|
if (Array.isArray(v)) v.forEach((x) => lines.push(`${k}: ${x}`));
|
||||||
|
else lines.push(`${k}: ${v}`);
|
||||||
|
}
|
||||||
|
lines.push('', '');
|
||||||
|
socket.write(lines.join('\r\n'));
|
||||||
|
if (upHead?.length) socket.write(upHead);
|
||||||
|
upSocket.pipe(socket);
|
||||||
|
socket.pipe(upSocket);
|
||||||
|
});
|
||||||
|
up.on('error', () => socket.destroy());
|
||||||
|
up.end(head);
|
||||||
|
}
|
||||||
|
|
||||||
|
const server = http.createServer(proxyHttp);
|
||||||
|
server.on('upgrade', proxyUpgrade);
|
||||||
|
server.listen(LISTEN_PORT, LISTEN_HOST, () => {
|
||||||
|
console.log(
|
||||||
|
`stage browser alias: http://${ALIAS_HOST}:${LISTEN_PORT} → https://${UPSTREAM_HOST} (${UPSTREAM_IP})`
|
||||||
|
);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user