17 KiB
Логин в 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
- index.html загружается (основной SPA файл)
- css/styles.css импортируется через
<link>в<head> - app.js загружается в конце
<body>через<script src="js/app.js"></script> - login.html подгружается динамически:
// 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):
initializeEventListeners(); // Event delegation для data-* атрибутов
checkAuth(); // Проверка авторизации
checkAuth() функция (строка 648-658):
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)
<div id="login-overlay">
<!-- Выбор стенда -->
<select id="l-env">
<option value="dev">Dev</option>
<option value="test" selected>Test</option>
<option value="prod">Prod</option>
</select>
<!-- Ввод токена -->
<textarea id="l-token" placeholder="Введите токен..."></textarea>
<!-- Вывод ошибок -->
<div id="l-error" style="display:none; color:#ff6b6b;">
<!-- Здесь показываются ошибки валидации -->
</div>
<!-- Кнопка входа -->
<button id="l-btn" onclick="doLogin()">Войти</button>
</div>
3.2 Интеграция с UI
- Стенды: dev/test/prod (соответствуют разным окружениям облака)
- Токен: JWT токен пользователя из личного кабинета
- Email: извлекается из JWT при успешном логине
4. Процесс логина (подробно)
4.1 Нажатие кнопки "Войти"
HTML:
<button id="l-btn" onclick="doLogin()">Войти</button>
Функция doLogin() (из auth.js, строка 5):
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: Что сохраняется
// В момент успешного логина:
localStorage.setItem('auth_token', 'eyJhbGc...'); // JWT токен пользователя
localStorage.setItem('auth_env', 'test'); // Выбранное окружение
Как долго сохраняется:
- localStorage хранит данные до явного удаления (
localStorage.removeItem()) - Не удаляется при закрытии браузера (в отличие от sessionStorage)
- Восстанавливается при перезагрузке страницы
5.2 Восстановление сессии при перезагрузке
Сценарий:
- Пользователь вошёл (токен в localStorage)
- Закрыл браузер
- Открыл консоль снова
Процесс:
- Загружается app.js
- Вызывается
checkAuth()(строка 794) - Функция проверяет
localStorage.getItem('auth_token') - Если токен есть → автоматический вход (setUserAvatar + hideLoginOverlay + reloadAll)
- Если токена нет → показать форму логина
6. Использование токена в API запросах
6.1 authHeaders() функция
Каждый API запрос содержит авторизационные заголовки:
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 с авторизацией
async function getJSON(url) {
const r = await fetch(url, { headers: authHeaders() });
if (r.status === 401) { doLogout(); /* сессия истекла */ }
// ...
}
2. requestJSON(url, method, body) — POST/PUT/DELETE с авторизацией
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
Запрос:
POST /console/api/auth
Content-Type: application/json
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"env": "test"
}
Ответ (успех — 200):
{
"success": true
}
Ответ (ошибка — 400/401):
{
"error": "Invalid token" | "Token expired" | "Unknown environment"
}
7.2 Что проверяет API облака
- Валидность JWT токена — подпись, структура
- Срок действия токена — не истёк ли
- Существование пользователя — есть ли в базе
- Окружение — существует ли такой env (dev/test/prod)
- Права доступа — может ли этот пользователь войти в это окружение
8. Извлечение email из JWT
8.1 jwtEmail() функция
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() функция
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() функция
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):
// В getJSON()
if (r.status === 401) { doLogout(); throw new Error('Сессия истекла'); }
// В requestJSON()
if (r.status === 401) { doLogout(); throw new Error('Сессия истекла'); }
Причины 401:
- Токен истёк
- Токен был отозван (revoked) в облаке
- Пользователь был заблокирован
10. Показ/скрытие интерфейса
10.1 showLoginOverlay()
function showLoginOverlay() {
document.getElementById('login-overlay').style.display = 'flex';
}
10.2 hideLoginOverlay()
function hideLoginOverlay() {
document.getElementById('login-overlay').style.display = 'none';
}
11. Проверка статуса namespace
11.1 pollNSStatus() функция (строка 600)
После логина API проверяет готовность namespace пользователя:
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):
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 — основной HTML
- app.js — основная логика
- login.html template — форма логина
- auth.js (коммит f8c4b3e) — оригинальные функции логина