# Логин в Fission Console — Полное описание логики **Дата:** май 2026 **Версия консоли:** v1.3.20+ **Статус:** Рабочая конфигурация (необходимо восстановить функции логина в app.js) --- ## 1. Архитектура логина ``` Пользователь вводит токен ↓ [login.html] форма (вводится токен + стенд) ↓ [onclick="doLogin()"] → JavaScript функция ↓ POST /console/api/auth {token, env} ↓ API Cloud: проверка токена (валидность, срок действия) ↓ Успех? → localStorage сохранение → showMain() Ошибка? → errEl.textContent → showError() ``` --- ## 2. Первоначальная загрузка (инициализация) ### 2.1 Загрузка HTML и JS 1. **index.html** загружается (основной SPA файл) 2. **css/styles.css** импортируется через `` в `` 3. **app.js** загружается в конце `` через `` 4. **login.html** подгружается динамически: ```javascript // app.js, строка 433 fetch('templates/modals/login.html') .then(r => r.text()) .then(html => { document.body.insertAdjacentHTML('afterbegin', html); }); ``` ### 2.2 Инициализация приложения При загрузке app.js (строка 790-794): ```javascript initializeEventListeners(); // Event delegation для data-* атрибутов checkAuth(); // Проверка авторизации ``` **checkAuth()** функция (строка 648-658): ```javascript function checkAuth() { var storedToken = localStorage.getItem('auth_token'); if (!storedToken) { showLoginOverlay(); // Показать форму логина return; } setUserAvatar(storedToken); // Установить аватар с email hideLoginOverlay(); // Скрыть форму логина reloadAll(); // Загрузить данные } ``` --- ## 3. HTML структура логина ### 3.1 Форма входа (templates/modals/login.html) ```html
``` ### 3.2 Интеграция с UI - **Стенды:** dev/test/prod (соответствуют разным окружениям облака) - **Токен:** JWT токен пользователя из личного кабинета - **Email:** извлекается из JWT при успешном логине --- ## 4. Процесс логина (подробно) ### 4.1 Нажатие кнопки "Войти" **HTML:** ```html ``` **Функция doLogin()** (из auth.js, строка 5): ```javascript async function doLogin() { // 1. Получить значения из формы var btn = document.getElementById('l-btn'); var errEl = document.getElementById('l-error'); var token = (document.getElementById('l-token').value || '').trim(); var env = document.getElementById('l-env').value; // 2. Базовая валидация (не пусто) if (!token) { errEl.textContent = 'Введите токен'; errEl.style.display = 'block'; return; } // 3. Заблокировать кнопку, очистить ошибки btn.disabled = true; errEl.style.display = 'none'; try { // 4. ОТПРАВИТЬ НА ПРОВЕРКУ К API ОБЛАКА const res = await fetch(API_BASE + '/auth', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token: token, env: env }) }); // 5. Обработать ответ if (!res.ok) { const d = await res.json().catch(() => ({})); throw new Error(d.error || 'Ошибка входа'); } // 6. УСПЕХ: СОХРАНИТЬ В localStorage localStorage.setItem('auth_token', token); localStorage.setItem('auth_env', env); // 7. ПОКАЗАТЬ АВАТАР С EMAIL setUserAvatar(token); // 8. Скрыть форму логина hideLoginOverlay(); // 9. ПРОВЕРИТЬ СТАТУС NAMESPACE // Если namespace не готов (not ready) → показать init overlay // и ждать пока инициализируется try { var sr = await fetch(API_BASE + '/ns/status', { headers: { 'X-Auth-Token': token, 'X-Auth-Env': env } }); var sd = await sr.json(); if (!sd.ready) { pollNSStatus(); // Начать полинг (ждать готовности) return; } } catch (_) { } // 10. ЗАГРУЗИТЬ ОСНОВНОЙ ИНТЕРФЕЙС reloadAll(); } catch (e) { // Ошибка: показать сообщение errEl.textContent = e.message; errEl.style.display = 'block'; } finally { // Разблокировать кнопку btn.disabled = false; } } ``` --- ## 5. Сохранение состояния в браузер ### 5.1 localStorage: Что сохраняется ```javascript // В момент успешного логина: localStorage.setItem('auth_token', 'eyJhbGc...'); // JWT токен пользователя localStorage.setItem('auth_env', 'test'); // Выбранное окружение ``` **Как долго сохраняется:** - localStorage хранит данные до явного удаления (`localStorage.removeItem()`) - Не удаляется при закрытии браузера (в отличие от sessionStorage) - Восстанавливается при перезагрузке страницы ### 5.2 Восстановление сессии при перезагрузке **Сценарий:** 1. Пользователь вошёл (токен в localStorage) 2. Закрыл браузер 3. Открыл консоль снова **Процесс:** 1. Загружается app.js 2. Вызывается `checkAuth()` (строка 794) 3. Функция проверяет `localStorage.getItem('auth_token')` 4. Если токен есть → автоматический вход (setUserAvatar + hideLoginOverlay + reloadAll) 5. Если токена нет → показать форму логина --- ## 6. Использование токена в API запросах ### 6.1 authHeaders() функция Каждый API запрос содержит авторизационные заголовки: ```javascript function authHeaders() { return { 'X-Auth-Token': localStorage.getItem('auth_token') || '', 'X-Auth-Env': localStorage.getItem('auth_env') || 'test' }; } ``` ### 6.2 Типы API запросов **1. getJSON(url)** — GET с авторизацией ```javascript async function getJSON(url) { const r = await fetch(url, { headers: authHeaders() }); if (r.status === 401) { doLogout(); /* сессия истекла */ } // ... } ``` **2. requestJSON(url, method, body)** — POST/PUT/DELETE с авторизацией ```javascript async function requestJSON(url, method, body) { const r = await fetch(url, { method: method, headers: Object.assign( { 'Content-Type': 'application/json' }, authHeaders() ), body: body ? JSON.stringify(body) : undefined }); if (r.status === 401) { doLogout(); /* сессия истекла */ } // ... } ``` --- ## 7. Проверка токена на API облака ### 7.1 Эндпоинт /console/api/auth **Запрос:** ```http POST /console/api/auth Content-Type: application/json { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "env": "test" } ``` **Ответ (успех — 200):** ```json { "success": true } ``` **Ответ (ошибка — 400/401):** ```json { "error": "Invalid token" | "Token expired" | "Unknown environment" } ``` ### 7.2 Что проверяет API облака 1. **Валидность JWT токена** — подпись, структура 2. **Срок действия токена** — не истёк ли 3. **Существование пользователя** — есть ли в базе 4. **Окружение** — существует ли такой env (dev/test/prod) 5. **Права доступа** — может ли этот пользователь войти в это окружение --- ## 8. Извлечение email из JWT ### 8.1 jwtEmail() функция ```javascript function jwtEmail(token) { try { // JWT формат: header.payload.signature // payload — base64url кодирован var payload = JSON.parse( atob(token.split('.')[1].replace(/-/g,'+').replace(/_/g,'/')) ); // Попытаться вытащить email или sub (subject = идентификатор) return payload.email || payload.sub || null; } catch (_) { return null; } } ``` ### 8.2 setUserAvatar() функция ```javascript function setUserAvatar(token) { var el = document.getElementById('user-avatar'); if (!el) return; var email = token ? jwtEmail(token) : null; if (email) { // Показать email в аватаре (правый верхний угол) el.textContent = email; el.title = email; el.style.fontSize = '0.7rem'; el.style.minWidth = '60px'; el.style.padding = '0 8px'; } else { // Вернуть в дефолтное состояние (буква "N") el.textContent = 'N'; el.title = ''; el.style.fontSize = ''; el.style.minWidth = ''; el.style.padding = ''; } } ``` --- ## 9. Логаут ### 9.1 doLogout() функция ```javascript function doLogout() { // 1. Удалить токен и окружение из localStorage localStorage.removeItem('auth_token'); localStorage.removeItem('auth_env'); // 2. Очистить форму логина try { document.getElementById('l-token').value = ''; } catch (_) { } // 3. Вернуть аватар в дефолт setUserAvatar(null); // 4. Показать форму логина showLoginOverlay(); } ``` ### 9.2 Автоматический логаут Если API вернёт **401 (Unauthorized)**: ```javascript // В getJSON() if (r.status === 401) { doLogout(); throw new Error('Сессия истекла'); } // В requestJSON() if (r.status === 401) { doLogout(); throw new Error('Сессия истекла'); } ``` **Причины 401:** - Токен истёк - Токен был отозван (revoked) в облаке - Пользователь был заблокирован --- ## 10. Показ/скрытие интерфейса ### 10.1 showLoginOverlay() ```javascript function showLoginOverlay() { document.getElementById('login-overlay').style.display = 'flex'; } ``` ### 10.2 hideLoginOverlay() ```javascript function hideLoginOverlay() { document.getElementById('login-overlay').style.display = 'none'; } ``` --- ## 11. Проверка статуса namespace ### 11.1 pollNSStatus() функция (строка 600) После логина API проверяет готовность namespace пользователя: ```javascript try { var sr = await fetch(API_BASE + '/ns/status', { headers: { 'X-Auth-Token': token, 'X-Auth-Env': env } }); var sd = await sr.json(); if (!sd.ready) { pollNSStatus(); // ← Начать полинг return; } } catch (_) { } ``` **pollNSStatus()** (строка 600): ```javascript function pollNSStatus() { // Показать инит-оверлей (ждём создания namespace) document.getElementById('init-overlay').style.display = 'flex'; // Полинг каждые 5 сек const pollInterval = setInterval(async function() { try { var statusResp = await fetch(API_BASE + '/ns/status', { headers: authHeaders() }); var debugResp = await fetch(API_BASE + '/ns/debug', { headers: authHeaders() }); var statusData = await statusResp.json(); var debugData = await debugResp.json(); // Обновить UI с информацией о статусе // Когда ready === true → загрузить основной UI if (statusData.ready) { clearInterval(pollInterval); document.getElementById('init-overlay').style.display = 'none'; reloadAll(); // Загрузить данные } } catch (_) { } }, 5000); } ``` --- ## 12. Схема данных в браузере ``` localStorage ├── auth_token: "eyJhbGc..." ← JWT токен пользователя ├── auth_env: "test" ← Выбранное окружение └── (другие данные могут быть) DOM элементы: ├── #login-overlay ← Форма логина │ ├── #l-env ← Dropdown стендов │ ├── #l-token ← Textarea с токеном │ ├── #l-error ← Div для ошибок │ └── #l-btn ← Кнопка "Войти" ├── #init-overlay ← Ожидание инициализации NS └── #user-avatar ← Отображение email в navbar ``` --- ## 13. Полный flow диаграмма ``` START ↓ [Загрузка index.html] ↓ [Загрузка app.js] ↓ [Загрузка login.html через fetch] ↓ [initializeEventListeners() — event delegation] ↓ [checkAuth()] — Есть ли токен в localStorage? ├─ НЕТ → [showLoginOverlay()] │ ↓ │ [Пользователь вводит токен + env] │ ↓ │ [onclick="doLogin()"] │ ↓ │ [POST /console/api/auth {token, env}] │ ↓ │ [API облака проверяет токен] │ ├─ ОШИБКА → [errEl.display=block; errEl.textContent=...] │ │ ↓ │ │ [Пользователь вводит заново] │ │ │ └─ OK → [localStorage.setItem(auth_token, token)] │ [localStorage.setItem(auth_env, env)] │ ↓ │ [setUserAvatar(token)] — извлечь email из JWT │ ↓ │ [hideLoginOverlay()] │ ↓ │ [Проверить /ns/status] │ ├─ NOT READY → [pollNSStatus()] — ждать 5сек * N │ └─ READY → [reloadAll()] │ └─ ДА → [setUserAvatar(token)] [hideLoginOverlay()] [reloadAll()] — загрузить основной UI [Пользователь работает с консолью] ↓ [Все запросы содержат authHeaders(): X-Auth-Token + X-Auth-Env] ↓ [Если API вернёт 401] ├─ → [doLogout()] — удалить токены, вернуть на логин [При нажатии "Выход"] ├─ → [doLogout()] END ``` --- ## 14. ТЕКУЩЕЕ СОСТОЯНИЕ (v1.3.20) ### Проблема Функции логина **отсутствуют в app.js**: - ❌ `doLogin()` — НЕ НАЙДЕНА - ❌ `showLoginOverlay()` — НЕ НАЙДЕНА - ❌ `hideLoginOverlay()` — НЕ НАЙДЕНА - ❌ `setUserAvatar()` — НЕ НАЙДЕНА - ❌ `jwtEmail()` — НЕ НАЙДЕНА - ❌ `doLogout()` — НЕ НАЙДЕНА - ❌ `pollNSStatus()` — НЕ НАЙДЕНА (строка 600 пустая) ### Почему логин всё ещё работает? - Кнопка "Войти" имеет `onclick="doLogin()"` (inline обработчик, не через event delegation) - Функции должны быть где-то определены, но их нет в app.js - Возможно: отсутствуют в текущей версии после рефакторинга ### Необходимо восстановить Все 6 функций должны быть в app.js перед инициализацией (до `initializeEventListeners()` и `checkAuth()`). --- ## 15. Ссылки на код - [index.html](../../console/ui/index.html) — основной HTML - [app.js](../../console/ui/js/app.js) — основная логика - [login.html template](../../console/ui/templates/modals/login.html) — форма логина - [auth.js (коммит f8c4b3e)](../../.git/refs/heads/main) — оригинальные функции логина