Рефакторинг: auth/login вынесены в отдельные модули, правила обновлены под локальный git
This commit is contained in:
@@ -27,10 +27,12 @@
|
|||||||
~/fission/ \
|
~/fission/ \
|
||||||
naeel@5.172.178.213:~/terra/fission/
|
naeel@5.172.178.213:~/terra/fission/
|
||||||
|
|
||||||
3. Все команды выполнять ТОЛЬКО через SSH:
|
|
||||||
|
3. Git (add/commit/push) выполнять ЛОКАЛЬНО в ~/fission
|
||||||
|
4. Docker, kubectl и другие инфраструктурные команды — только через SSH на ВМ:
|
||||||
ssh -i ~/.ssh/naeel_vm_id_ed25519 -o StrictHostKeyChecking=no -o ConnectTimeout=10 naeel@5.172.178.213 'КОМАНДА'
|
ssh -i ~/.ssh/naeel_vm_id_ed25519 -o StrictHostKeyChecking=no -o ConnectTimeout=10 naeel@5.172.178.213 'КОМАНДА'
|
||||||
|
|
||||||
- не выполнять команды локально
|
- не выполнять инфраструктурные команды локально
|
||||||
- не открывать интерактивные сессии
|
- не открывать интерактивные сессии
|
||||||
- не делать цепочки без необходимости
|
- не делать цепочки без необходимости
|
||||||
|
|
||||||
@@ -42,8 +44,8 @@
|
|||||||
- ломать рабочее состояние
|
- ломать рабочее состояние
|
||||||
|
|
||||||
6. После каждого исправления:
|
6. После каждого исправления:
|
||||||
- синхронизация (rsync)
|
- git add/commit ЛОКАЛЬНО
|
||||||
- затем commit через SSH
|
- затем синхронизация (rsync) на ВМ
|
||||||
|
|
||||||
## ⛔⛔⛔ ДЕЛАТЬ ТОЛЬКО ЧТО ПРЯМО ПРИКАЗАНО
|
## ⛔⛔⛔ ДЕЛАТЬ ТОЛЬКО ЧТО ПРЯМО ПРИКАЗАНО
|
||||||
|
|
||||||
|
|||||||
+16
-17
@@ -1,25 +1,24 @@
|
|||||||
# Правила работы агента
|
# Правила работы агента
|
||||||
|
|
||||||
## Файловая система
|
## Файловая система (актуально)
|
||||||
|
|
||||||
`~/remote_dev/` (локально) примонтирован через sshfs к `~/terra/` на ВМ — **одна ФС**.
|
1. Все файлы редактируются локально: `~/fission`
|
||||||
Файлы, сохранённые локально, мгновенно видны на ВМ. SCP не нужен.
|
2. После любых изменений — обязательно rsync на ВМ:
|
||||||
|
rsync -az \
|
||||||
|
-e "ssh -i ~/.ssh/naeel_vm_id_ed25519 -o StrictHostKeyChecking=no -o ConnectTimeout=10" \
|
||||||
|
~/fission/ \
|
||||||
|
naeel@5.172.178.213:~/terra/fission/
|
||||||
|
|
||||||
Перед любым запуском команд сначала убедиться, что локальная копия и содержимое на ВМ синхронизированы; если есть сомнение в рассинхроне, сверяться с файлами именно на ВМ.
|
3. Git (add/commit/push) выполнять ЛОКАЛЬНО в ~/fission
|
||||||
|
4. Docker, kubectl и другие инфраструктурные команды — только через SSH на ВМ
|
||||||
|
5. Перед запуском любой команды на ВМ обязательно убедиться, что синхронизация (rsync) выполнена
|
||||||
|
6. SCP, sshfs, remote_dev и маунты больше НЕ используются
|
||||||
|
7. Только rsync для синхронизации
|
||||||
|
|
||||||
Монтирование может слетать. Признак: файлы рассинхронизированы.
|
Пример:
|
||||||
|
1. Редактируешь локально
|
||||||
```bash
|
2. rsync на ВМ
|
||||||
# Размонтировать
|
3. Выполняешь команды через SSH на ВМ
|
||||||
fusermount -u ~/remote_dev
|
|
||||||
# Если завис: sudo umount -l /home/naeel/remote_dev
|
|
||||||
|
|
||||||
# Примонтировать
|
|
||||||
sshfs naeel@5.172.178.213:/home/naeel/terra ~/remote_dev \
|
|
||||||
-o cache=no -o no_readahead -o reconnect \
|
|
||||||
-o ServerAliveInterval=15 -o ServerAliveCountMax=3 \
|
|
||||||
-o IdentityFile=~/.ssh/naeel_vm_id_ed25519
|
|
||||||
```
|
|
||||||
|
|
||||||
## SSH
|
## SSH
|
||||||
|
|
||||||
|
|||||||
+12
-110
@@ -337,37 +337,8 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="login-overlay"
|
|
||||||
style="display:none; position:fixed; inset:0; background:rgba(0,0,0,.85); z-index:999; align-items:center; justify-content:center; padding:16px;">
|
<!-- login-overlay будет динамически подгружен из templates/modals/login.html -->
|
||||||
<div class="panel" style="width:min(440px,100%);">
|
|
||||||
<div class="brand" style="margin-bottom:24px;">
|
|
||||||
<div class="brand-mark">N</div>
|
|
||||||
<div class="brand-text">
|
|
||||||
<div class="nubes">NUBES</div>
|
|
||||||
<div class="product">FISSION CONSOLE</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row" style="flex-direction:column; gap:6px; margin-bottom:12px;">
|
|
||||||
<label style="font-size:12px; color:#999;">Стенд</label>
|
|
||||||
<select id="l-env"
|
|
||||||
style="background:var(--bg-base); border:1px solid var(--border); color:var(--fg); padding:8px 10px; border-radius:6px;">
|
|
||||||
<option value="dev">Dev</option>
|
|
||||||
<option value="test" selected>Test</option>
|
|
||||||
<option value="prod">Prod</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="row" style="flex-direction:column; gap:6px; margin-bottom:12px;">
|
|
||||||
<label style="font-size:12px; color:#999;">Токен</label>
|
|
||||||
<textarea id="l-token" rows="5"
|
|
||||||
style="background:var(--bg-base); border:1px solid var(--border); color:var(--fg); padding:8px 10px; border-radius:6px; font-family:monospace; font-size:12px; resize:vertical; width:100%; box-sizing:border-box;"
|
|
||||||
placeholder="Введите токен..."></textarea>
|
|
||||||
<div style="font-size:11px; color:var(--text-secondary); margin-top:4px;">Токен: <strong
|
|
||||||
style="color:#8bc7ff;">Личный кабинет → Профиль пользователя → Токены</strong></div>
|
|
||||||
</div>
|
|
||||||
<div id="l-error" style="display:none; color:#ff6b6b; font-size:13px; margin-bottom:10px;"></div>
|
|
||||||
<button id="l-btn" class="btn" style="width:100%;" onclick="doLogin()">Войти</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Overlay: инициализация нового окружения -->
|
<!-- Overlay: инициализация нового окружения -->
|
||||||
<div id="init-overlay"
|
<div id="init-overlay"
|
||||||
style="display:none; position:fixed; inset:0; background:rgba(0,0,0,.88); z-index:1000; align-items:center; justify-content:center; padding:16px;">
|
style="display:none; position:fixed; inset:0; background:rgba(0,0,0,.88); z-index:1000; align-items:center; justify-content:center; padding:16px;">
|
||||||
@@ -1215,87 +1186,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showLoginOverlay() {
|
|
||||||
document.getElementById('login-overlay').style.display = 'flex';
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideLoginOverlay() {
|
// Динамически подгружаем login-overlay
|
||||||
document.getElementById('login-overlay').style.display = 'none';
|
fetch('templates/modals/login.html')
|
||||||
}
|
.then(r => r.text())
|
||||||
|
.then(html => {
|
||||||
|
document.body.insertAdjacentHTML('afterbegin', html);
|
||||||
|
});
|
||||||
|
|
||||||
async function doLogin() {
|
// Подключаем логику авторизации
|
||||||
var btn = document.getElementById('l-btn');
|
const script = document.createElement('script');
|
||||||
var errEl = document.getElementById('l-error');
|
script.src = 'js/auth.js';
|
||||||
var token = (document.getElementById('l-token').value || '').trim();
|
document.head.appendChild(script);
|
||||||
var env = document.getElementById('l-env').value;
|
|
||||||
if (!token) { errEl.textContent = 'Введите токен'; errEl.style.display = 'block'; return; }
|
|
||||||
btn.disabled = true;
|
|
||||||
errEl.style.display = 'none';
|
|
||||||
try {
|
|
||||||
const res = await fetch(API_BASE + '/auth', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ token: token, env: env })
|
|
||||||
});
|
|
||||||
if (!res.ok) {
|
|
||||||
const d = await res.json().catch(function () { return {}; });
|
|
||||||
throw new Error(d.error || 'Ошибка входа');
|
|
||||||
}
|
|
||||||
localStorage.setItem('auth_token', token);
|
|
||||||
localStorage.setItem('auth_env', env);
|
|
||||||
setUserAvatar(token);
|
|
||||||
hideLoginOverlay();
|
|
||||||
// Проверяем статус NS — если не 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 (_) { }
|
|
||||||
reloadAll();
|
|
||||||
} catch (e) {
|
|
||||||
errEl.textContent = e.message;
|
|
||||||
errEl.style.display = 'block';
|
|
||||||
} finally {
|
|
||||||
btn.disabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function jwtEmail(token) {
|
|
||||||
try {
|
|
||||||
var payload = JSON.parse(atob(token.split('.')[1].replace(/-/g,'+').replace(/_/g,'/')));
|
|
||||||
return payload.email || payload.sub || null;
|
|
||||||
} catch (_) { return null; }
|
|
||||||
}
|
|
||||||
|
|
||||||
function setUserAvatar(token) {
|
|
||||||
var el = document.getElementById('user-avatar');
|
|
||||||
if (!el) return;
|
|
||||||
var email = token ? jwtEmail(token) : null;
|
|
||||||
if (email) {
|
|
||||||
el.textContent = email;
|
|
||||||
el.title = email;
|
|
||||||
el.style.fontSize = '0.7rem';
|
|
||||||
el.style.minWidth = '60px';
|
|
||||||
el.style.padding = '0 8px';
|
|
||||||
} else {
|
|
||||||
el.textContent = 'N';
|
|
||||||
el.title = '';
|
|
||||||
el.style.fontSize = '';
|
|
||||||
el.style.minWidth = '';
|
|
||||||
el.style.padding = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function doLogout() {
|
|
||||||
localStorage.removeItem('auth_token');
|
|
||||||
localStorage.removeItem('auth_env');
|
|
||||||
try { document.getElementById('l-token').value = ''; } catch (_) { }
|
|
||||||
setUserAvatar(null);
|
|
||||||
showLoginOverlay();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function aiCheck(codeId, langId, resultId) {
|
async function aiCheck(codeId, langId, resultId) {
|
||||||
var code = document.getElementById(codeId).value.trim();
|
var code = document.getElementById(codeId).value.trim();
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
// Логика авторизации (логин/логаут, токен, email)
|
||||||
|
|
||||||
|
function showLoginOverlay() {
|
||||||
|
document.getElementById('login-overlay').style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideLoginOverlay() {
|
||||||
|
document.getElementById('login-overlay').style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function doLogin() {
|
||||||
|
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;
|
||||||
|
if (!token) { errEl.textContent = 'Введите токен'; errEl.style.display = 'block'; return; }
|
||||||
|
btn.disabled = true;
|
||||||
|
errEl.style.display = 'none';
|
||||||
|
try {
|
||||||
|
const res = await fetch(API_BASE + '/auth', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ token: token, env: env })
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
const d = await res.json().catch(function () { return {}; });
|
||||||
|
throw new Error(d.error || 'Ошибка входа');
|
||||||
|
}
|
||||||
|
localStorage.setItem('auth_token', token);
|
||||||
|
localStorage.setItem('auth_env', env);
|
||||||
|
setUserAvatar(token);
|
||||||
|
hideLoginOverlay();
|
||||||
|
// Проверяем статус NS — если не 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 (_) { }
|
||||||
|
reloadAll();
|
||||||
|
} catch (e) {
|
||||||
|
errEl.textContent = e.message;
|
||||||
|
errEl.style.display = 'block';
|
||||||
|
} finally {
|
||||||
|
btn.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function jwtEmail(token) {
|
||||||
|
try {
|
||||||
|
var payload = JSON.parse(atob(token.split('.')[1].replace(/-/g,'+').replace(/_/g,'/')));
|
||||||
|
return payload.email || payload.sub || null;
|
||||||
|
} catch (_) { return null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function setUserAvatar(token) {
|
||||||
|
var el = document.getElementById('user-avatar');
|
||||||
|
if (!el) return;
|
||||||
|
var email = token ? jwtEmail(token) : null;
|
||||||
|
if (email) {
|
||||||
|
el.textContent = email;
|
||||||
|
el.title = email;
|
||||||
|
el.style.fontSize = '0.7rem';
|
||||||
|
el.style.minWidth = '60px';
|
||||||
|
el.style.padding = '0 8px';
|
||||||
|
} else {
|
||||||
|
el.textContent = 'N';
|
||||||
|
el.title = '';
|
||||||
|
el.style.fontSize = '';
|
||||||
|
el.style.minWidth = '';
|
||||||
|
el.style.padding = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function doLogout() {
|
||||||
|
localStorage.removeItem('auth_token');
|
||||||
|
localStorage.removeItem('auth_env');
|
||||||
|
try { document.getElementById('l-token').value = ''; } catch (_) { }
|
||||||
|
setUserAvatar(null);
|
||||||
|
showLoginOverlay();
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<!-- Модальное окно логина -->
|
||||||
|
<div id="login-overlay"
|
||||||
|
style="display:none; position:fixed; inset:0; background:rgba(0,0,0,.85); z-index:999; align-items:center; justify-content:center; padding:16px;">
|
||||||
|
<div class="panel" style="width:min(440px,100%);">
|
||||||
|
<div class="brand" style="margin-bottom:24px;">
|
||||||
|
<div class="brand-mark">N</div>
|
||||||
|
<div class="brand-text">
|
||||||
|
<div class="nubes">NUBES</div>
|
||||||
|
<div class="product">FISSION CONSOLE</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row" style="flex-direction:column; gap:6px; margin-bottom:12px;">
|
||||||
|
<label style="font-size:12px; color:#999;">Стенд</label>
|
||||||
|
<select id="l-env"
|
||||||
|
style="background:var(--bg-base); border:1px solid var(--border); color:var(--fg); padding:8px 10px; border-radius:6px;">
|
||||||
|
<option value="dev">Dev</option>
|
||||||
|
<option value="test" selected>Test</option>
|
||||||
|
<option value="prod">Prod</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="row" style="flex-direction:column; gap:6px; margin-bottom:12px;">
|
||||||
|
<label style="font-size:12px; color:#999;">Токен</label>
|
||||||
|
<textarea id="l-token" rows="5"
|
||||||
|
style="background:var(--bg-base); border:1px solid var(--border); color:var(--fg); padding:8px 10px; border-radius:6px; font-family:monospace; font-size:12px; resize:vertical; width:100%; box-sizing:border-box;"
|
||||||
|
placeholder="Введите токен..."></textarea>
|
||||||
|
<div style="font-size:11px; color:var(--text-secondary); margin-top:4px;">Токен: <strong
|
||||||
|
style="color:#8bc7ff;">Личный кабинет → Профиль пользователя → Токены</strong></div>
|
||||||
|
</div>
|
||||||
|
<div id="l-error" style="display:none; color:#ff6b6b; font-size:13px; margin-bottom:10px;"></div>
|
||||||
|
<button id="l-btn" class="btn" style="width:100%;" onclick="doLogin()">Войти</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user