refactor(ui): step10 - extract init.js (pollNSStatus, initLog, formatSeconds, explainDelay, startTimedStatus, renderInitStages и др.)
This commit is contained in:
+1
-179
@@ -19,6 +19,7 @@
|
||||
<script src="js/auth.js"></script>
|
||||
<script src="js/triggers.js"></script>
|
||||
<script src="js/modals.js"></script>
|
||||
<script src="js/init.js"></script>
|
||||
<script src="js/functions.js"></script>
|
||||
<script src="js/ai.js"></script>
|
||||
</head>
|
||||
@@ -417,185 +418,6 @@
|
||||
currentInvoke: null
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
checkAuth();
|
||||
</script>
|
||||
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
/* 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);
|
||||
}
|
||||
Reference in New Issue
Block a user