chore: sync all changes from feat/ui-archive-upload session
This commit is contained in:
@@ -0,0 +1,560 @@
|
||||
# Логин в 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** импортируется через `<link>` в `<head>`
|
||||
3. **app.js** загружается в конце `<body>` через `<script src="js/app.js"></script>`
|
||||
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
|
||||
<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:**
|
||||
```html
|
||||
<button id="l-btn" onclick="doLogin()">Войти</button>
|
||||
```
|
||||
|
||||
**Функция 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) — оригинальные функции логина
|
||||
Reference in New Issue
Block a user