181 lines
7.3 KiB
JavaScript
181 lines
7.3 KiB
JavaScript
/* init.js — инициализация namespace, утилиты таймера и статуса */
|
||
|
||
var _initPolling = false;
|
||
var _initTickBusy = false;
|
||
var _initStartedAt = 0;
|
||
|
||
function initLog(msg) {
|
||
var el = document.getElementById('init-log');
|
||
var ts = new Date().toLocaleTimeString('ru-RU');
|
||
el.textContent += '[' + ts + '] ' + msg + '\n';
|
||
el.scrollTop = el.scrollHeight;
|
||
}
|
||
|
||
function formatSeconds(totalSeconds) {
|
||
totalSeconds = Math.max(0, Number(totalSeconds) || 0);
|
||
var minutes = Math.floor(totalSeconds / 60);
|
||
var seconds = totalSeconds % 60;
|
||
if (minutes <= 0) return seconds + 'с';
|
||
return minutes + 'м ' + seconds + 'с';
|
||
}
|
||
|
||
function setInitElapsed(seconds) {
|
||
document.getElementById('init-elapsed').textContent = formatSeconds(seconds);
|
||
}
|
||
|
||
function setInitCurrentStage(text) {
|
||
document.getElementById('init-current-stage').textContent = text || 'Ожидание старта';
|
||
}
|
||
|
||
function setBox(id, text) {
|
||
var el = document.getElementById(id);
|
||
if (!el) return;
|
||
if (!text) {
|
||
el.style.display = 'none';
|
||
el.textContent = '';
|
||
return;
|
||
}
|
||
el.style.display = 'block';
|
||
el.textContent = text;
|
||
}
|
||
|
||
function missingKeys(obj) {
|
||
return Object.keys(obj || {}).filter(function (key) {
|
||
return !obj[key];
|
||
});
|
||
}
|
||
|
||
function firstPendingStage(stages) {
|
||
var list = stages || [];
|
||
for (var i = 0; i < list.length; i++) {
|
||
if (!list[i].done) return list[i].name;
|
||
}
|
||
return list.length ? list[list.length - 1].name : 'Ожидание старта';
|
||
}
|
||
|
||
function describeInitReason(debug) {
|
||
if (!debug) return 'Собираем диагностические данные по namespace и control-plane...';
|
||
if (!debug.exists) return 'Ещё не создан Kubernetes namespace пользователя.';
|
||
if (!debug.active) return 'Namespace создан, но Kubernetes ещё не перевёл его в состояние Active.';
|
||
|
||
var preparation = debug.preparation || {};
|
||
if (!preparation.managedByConsole) return 'Namespace существует, но ещё не помечен как managed-by=fission-console.';
|
||
if (!preparation.watcherLabel) return 'Namespace ещё не помечен label fission.io/managed=true для NSWatcher.';
|
||
|
||
var missingSAs = missingKeys(preparation.serviceAccounts);
|
||
if (missingSAs.length) return 'Не готовы bootstrap service accounts: ' + missingSAs.join(', ') + '.';
|
||
|
||
var missingRBs = missingKeys(preparation.roleBindings);
|
||
if (missingRBs.length) return 'Не готовы bootstrap role bindings: ' + missingRBs.join(', ') + '.';
|
||
|
||
var control = debug.controlPlane || {};
|
||
if (!control.executor || !control.executor.ready) {
|
||
return 'Executor ещё не ready. Возможна задержка из-за cold start, image pull или specialization.';
|
||
}
|
||
if (!control.router || !control.router.ready) {
|
||
return 'Router ещё не ready. HTTP маршрутизация пока не активна.';
|
||
}
|
||
|
||
return 'Ожидаем финальное подтверждение готовности control-plane.';
|
||
}
|
||
|
||
function summarizeInit(status, debug) {
|
||
var done = status && status.stages ? status.stages.filter(function (s) { return s.done; }).length : 0;
|
||
var namespace = debug && debug.namespace ? debug.namespace : 'не определён';
|
||
return 'Namespace: ' + namespace + '. Завершено шагов: ' + done + '/3.';
|
||
}
|
||
|
||
function explainDelay(seconds) {
|
||
if (seconds < 5) return 'Обычная операция без заметной задержки.';
|
||
if (seconds < 10) return 'Вероятен cold start: прогрев пула, image pull или ожидание specialization.';
|
||
return 'Задержка выше нормы: возможны cold start, specialization или ожидание готовности control-plane.';
|
||
}
|
||
|
||
function startTimedStatus(startText, waitingPrefix, waitingHint) {
|
||
var startedAt = Date.now();
|
||
showStatus(startText, '');
|
||
var timer = setInterval(function () {
|
||
var elapsed = Math.max(1, Math.floor((Date.now() - startedAt) / 1000));
|
||
var text = waitingPrefix + ' ' + formatSeconds(elapsed);
|
||
if (waitingHint) text += ' · ' + waitingHint(elapsed);
|
||
showStatus(text, '');
|
||
}, 1000);
|
||
return {
|
||
stop: function (message, kind) {
|
||
clearInterval(timer);
|
||
var elapsed = Math.max(0, Math.round((Date.now() - startedAt) / 1000));
|
||
var text = message;
|
||
if (message) text += ' за ' + formatSeconds(elapsed);
|
||
showStatus(text, kind || '');
|
||
}
|
||
};
|
||
}
|
||
|
||
function renderInitStages(stages) {
|
||
var el = document.getElementById('init-stages');
|
||
el.innerHTML = stages.map(function (s) {
|
||
var icon = s.done ? '✅' : '⏳';
|
||
var color = s.done ? '#4caf50' : '#8bc7ff';
|
||
return '<div style="font-size:13px; color:' + color + ';">' + icon + ' ' + s.name + '</div>';
|
||
}).join('');
|
||
}
|
||
|
||
async function pollNSStatus() {
|
||
if (_initPolling) return;
|
||
_initPolling = true;
|
||
_initTickBusy = false;
|
||
_initStartedAt = Date.now();
|
||
var overlay = document.getElementById('init-overlay');
|
||
overlay.style.display = 'flex';
|
||
document.getElementById('init-log').textContent = '';
|
||
setInitElapsed(0);
|
||
setInitCurrentStage('Подготовка запуска');
|
||
setBox('init-summary', 'Проверяем состояние namespace и control-plane...');
|
||
setBox('init-reason', 'Если пользователь новый, возможны cold start и создание bootstrap ресурсов.');
|
||
setBox('init-error', '');
|
||
initLog('Запуск инициализации...');
|
||
var attempt = 0;
|
||
var maxAttempts = 60; // 5 минут
|
||
var interval = setInterval(async function () {
|
||
if (_initTickBusy) return;
|
||
_initTickBusy = true;
|
||
attempt++;
|
||
try {
|
||
var elapsed = Math.floor((Date.now() - _initStartedAt) / 1000);
|
||
setInitElapsed(elapsed);
|
||
var statusResp = await fetch(API_BASE + '/ns/status', { headers: authHeaders() });
|
||
var debugResp = await fetch(API_BASE + '/ns/debug', { headers: authHeaders() });
|
||
var d = await statusResp.json();
|
||
var dbg = await debugResp.json().catch(function () { return null; });
|
||
if (d.stages) renderInitStages(d.stages);
|
||
setInitCurrentStage(firstPendingStage(d.stages));
|
||
setBox('init-summary', summarizeInit(d, dbg));
|
||
setBox('init-reason', describeInitReason(dbg));
|
||
setBox('init-error', '');
|
||
var done = d.stages ? d.stages.filter(function (s) { return s.done; }).length : 0;
|
||
initLog('Шагов завершено: ' + done + '/3');
|
||
if (d.ready) {
|
||
initLog('✅ Готово! Загружаем консоль...');
|
||
clearInterval(interval);
|
||
_initPolling = false;
|
||
_initTickBusy = false;
|
||
overlay.style.display = 'none';
|
||
reloadAll();
|
||
return;
|
||
}
|
||
} catch (e) {
|
||
setBox('init-error', 'Последняя ошибка: ' + e.message);
|
||
initLog('Ошибка опроса: ' + e.message);
|
||
} finally {
|
||
_initTickBusy = false;
|
||
}
|
||
if (attempt >= maxAttempts) {
|
||
clearInterval(interval);
|
||
_initPolling = false;
|
||
_initTickBusy = false;
|
||
setBox('init-error', 'Превышено время ожидания. Чаще всего это bootstrap RBAC, control-plane readiness или холодный старт.');
|
||
initLog('⚠️ Превышено время ожидания. Попробуйте обновить страницу.');
|
||
}
|
||
}, 5000);
|
||
}
|