fix(iot-console): убрать namespace из UI полностью
- Поле Namespace удалено из формы входа
- namespace вычисляется из токена: SHA256(sub) → sless-{hex}
- navbar: убран badge с ns
- Телеметрия: убрана техническая подсказка про namespace
- Пользователь не видит и не вводит namespace нигде
This commit is contained in:
@@ -191,6 +191,7 @@
|
||||
const S = {
|
||||
// Сохраняемые настройки
|
||||
token: localStorage.getItem('iot_token') || '',
|
||||
// ns вычисляется из токена автоматически (SHA256(sub) → "sless-{hex}"), не вводится вручную
|
||||
ns: localStorage.getItem('iot_ns') || '',
|
||||
apiBase: localStorage.getItem('iot_api_base') || 'https://sless.kube5s.ru',
|
||||
mqttBase: localStorage.getItem('iot_mqtt_base')|| 'ws://iot.kube5s.ru/mqtt',
|
||||
@@ -213,11 +214,39 @@ const S = {
|
||||
// Сохраняем auth-параметры в localStorage
|
||||
function saveSettings() {
|
||||
localStorage.setItem('iot_token', S.token);
|
||||
localStorage.setItem('iot_ns', S.ns);
|
||||
localStorage.setItem('iot_ns', S.ns); // сохраняем для восстановления сессии
|
||||
localStorage.setItem('iot_api_base', S.apiBase);
|
||||
localStorage.setItem('iot_mqtt_base',S.mqttBase);
|
||||
}
|
||||
|
||||
// namespaceFromToken — вычисляет k8s namespace из JWT токена.
|
||||
// Алгоритм ИДЕНТИЧЕН Terraform провайдеру (client.NamespaceFromSub):
|
||||
// JWT payload → sub claim → SHA256(sub) → первые 8 байт → "sless-{16 hex символов}"
|
||||
// Пользователь НИКОГДА не вводит namespace вручную — это гарантирует изоляцию.
|
||||
async function namespaceFromToken(token) {
|
||||
const parts = token.split('.');
|
||||
if (parts.length !== 3) throw new Error('Неверный формат токена (не JWT)');
|
||||
|
||||
// base64url → base64 (заменяем символы, добавляем padding)
|
||||
let payload = parts[1].replace(/-/g, '+').replace(/_/g, '/');
|
||||
while (payload.length % 4) payload += '=';
|
||||
|
||||
let claims;
|
||||
try { claims = JSON.parse(atob(payload)); }
|
||||
catch(e) { throw new Error('Не удалось декодировать токен: ' + e.message); }
|
||||
|
||||
if (!claims.sub) throw new Error('В токене нет поля sub — токен не поддерживается');
|
||||
|
||||
// SHA256(sub) через Web Crypto API (встроен в все современные браузеры)
|
||||
const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(claims.sub));
|
||||
|
||||
// Берём первые 8 байт → 16 hex символов — идентично Go: hash[:8]
|
||||
const hex = Array.from(new Uint8Array(buf).slice(0, 8))
|
||||
.map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
|
||||
return 'sless-' + hex;
|
||||
}
|
||||
|
||||
// =============================================================
|
||||
// API
|
||||
// =============================================================
|
||||
@@ -452,13 +481,10 @@ function renderLogin() {
|
||||
<label>MQTT Broker WebSocket</label>
|
||||
<input id="f-mqtt" value="${h(S.mqttBase)}" placeholder="ws://iot.kube5s.ru/mqtt">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Namespace</label>
|
||||
<input id="f-ns" value="${h(S.ns)}" placeholder="my-namespace" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Bearer токен</label>
|
||||
<input id="f-tok" type="password" value="${h(S.token)}" placeholder="eyJ...">
|
||||
<div class="hint" style="margin-top:6px;">Данные вашего аккаунта определяются автоматически</div>
|
||||
</div>
|
||||
<button class="btn btn-primary mt8" style="width:100%;" id="login-btn" onclick="doLogin()">
|
||||
Войти
|
||||
@@ -471,31 +497,42 @@ function renderLogin() {
|
||||
});
|
||||
}
|
||||
|
||||
// doLogin — проверить настройки и перейти к устройствам
|
||||
// doLogin — вычислить namespace из токена, проверить доступность API, перейти к устройствам.
|
||||
// Пользователь НИКОГДА не вводит namespace — он однозначно определяется из токена.
|
||||
async function doLogin() {
|
||||
const api = (document.getElementById('f-api').value || '').trim().replace(/\/$/, '');
|
||||
const mqtt = (document.getElementById('f-mqtt').value || '').trim();
|
||||
const ns = (document.getElementById('f-ns').value || '').trim();
|
||||
const tok = (document.getElementById('f-tok').value || '').trim();
|
||||
|
||||
if (!ns) { loginErr('Укажите namespace'); return; }
|
||||
if (!tok) { loginErr('Укажите токен'); return; }
|
||||
|
||||
const btn = document.getElementById('login-btn');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Проверка...';
|
||||
|
||||
// Вычисляем namespace из токена — SHA256(sub) → "sless-{hex}"
|
||||
let ns;
|
||||
try {
|
||||
ns = await namespaceFromToken(tok);
|
||||
} catch(e) {
|
||||
loginErr('Ошибка токена: ' + e.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Войти';
|
||||
return;
|
||||
}
|
||||
|
||||
S.apiBase = api || 'https://sless.kube5s.ru';
|
||||
S.mqttBase = mqtt || 'ws://iot.kube5s.ru/mqtt';
|
||||
S.ns = ns;
|
||||
S.token = tok;
|
||||
|
||||
const btn = document.getElementById('login-btn');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Проверка...';
|
||||
|
||||
try {
|
||||
await apiListDevices(); // проверяем что токен и namespace валидны
|
||||
await apiListDevices(); // проверяем что токен валиден и API доступен
|
||||
saveSettings();
|
||||
nav('devices');
|
||||
} catch(e) {
|
||||
S.token = '';
|
||||
S.ns = '';
|
||||
loginErr(e.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Войти';
|
||||
@@ -906,7 +943,7 @@ function telemetryTab() {
|
||||
<h3>Телеметрия</h3>
|
||||
<p class="hint" style="margin-top:8px;">Скоро — хранение данных и API чтения</p>
|
||||
<p class="hint" style="margin-top:4px;color:#334155;">
|
||||
Требует: Postgres StatefulSet в namespace <span class="mono">iot</span>
|
||||
В разработке — появится в следующем обновлении
|
||||
</p>
|
||||
</div>
|
||||
</div>`;
|
||||
@@ -919,7 +956,6 @@ function navbar() {
|
||||
return `
|
||||
<nav class="navbar">
|
||||
<div class="navbar-brand" onclick="nav('devices')">⚡ IoT Консоль</div>
|
||||
<div class="navbar-ns">${h(S.ns)}</div>
|
||||
<div class="navbar-spacer"></div>
|
||||
<button class="btn btn-ghost btn-sm" onclick="doLogout()">Выйти</button>
|
||||
</nav>`;
|
||||
|
||||
Reference in New Issue
Block a user