export: align reanimator contract v2.7

This commit is contained in:
Mikhail Chusavitin
2026-03-15 23:27:32 +03:00
parent 9007f1b360
commit 476630190d
31 changed files with 3502 additions and 689 deletions

View File

@@ -24,6 +24,8 @@ let collectionJobPollTimer = null;
let collectionJobLogCounter = 0;
let apiPortTouchedByUser = false;
let isAutoUpdatingApiPort = false;
let apiProbeResult = null;
let apiPowerDecisionTimer = null;
function initSourceType() {
const sourceButtons = document.querySelectorAll('.source-switch-btn');
@@ -66,22 +68,14 @@ function initApiSource() {
}
const cancelJobButton = document.getElementById('cancel-job-btn');
const checkButton = document.getElementById('api-check-btn');
const collectOffButton = document.getElementById('api-collect-off-btn');
const powerOnCollectButton = document.getElementById('api-power-on-collect-btn');
const fieldNames = ['host', 'port', 'username', 'password'];
apiForm.addEventListener('submit', (event) => {
event.preventDefault();
const { isValid, payload, errors } = validateCollectForm();
renderFormErrors(errors);
if (!isValid) {
renderApiConnectStatus(false, null);
apiConnectPayload = null;
return;
}
apiConnectPayload = payload;
renderApiConnectStatus(true, payload);
startCollectionJob(payload);
startCollectionFromCurrentProbe(false);
});
if (cancelJobButton) {
@@ -89,6 +83,23 @@ function initApiSource() {
cancelCollectionJob();
});
}
if (checkButton) {
checkButton.addEventListener('click', () => {
startApiProbe();
});
}
if (collectOffButton) {
collectOffButton.addEventListener('click', () => {
clearApiPowerDecisionTimer();
startCollectionFromCurrentProbe(false);
});
}
if (powerOnCollectButton) {
powerOnCollectButton.addEventListener('click', () => {
clearApiPowerDecisionTimer();
startCollectionFromCurrentProbe(true);
});
}
fieldNames.forEach((fieldName) => {
const field = apiForm.elements.namedItem(fieldName);
@@ -105,6 +116,7 @@ function initApiSource() {
const { errors } = validateCollectForm();
renderFormErrors(errors);
clearApiConnectStatus();
resetApiProbeState();
if (collectionJob && isCollectionJobTerminal(collectionJob.status)) {
resetCollectionJobState();
@@ -116,6 +128,144 @@ function initApiSource() {
renderCollectionJob();
}
function startApiProbe() {
const { isValid, payload, errors } = validateCollectForm();
renderFormErrors(errors);
if (!isValid) {
renderApiConnectStatus(false, null);
resetApiProbeState();
return;
}
apiConnectPayload = payload;
resetApiProbeState();
setApiFormBlocked(true);
renderApiConnectStatus(true, { ...payload, password: '***' });
fetch('/api/collect/probe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
})
.then(async (response) => {
const body = await response.json().catch(() => ({}));
if (!response.ok) {
throw new Error(body.error || 'Проверка подключения не удалась');
}
apiProbeResult = body;
renderApiProbeState();
})
.catch((err) => {
resetApiProbeState();
renderApiConnectStatus(false, null);
const status = document.getElementById('api-connect-status');
if (status) {
status.textContent = err.message || 'Проверка подключения не удалась';
status.className = 'api-connect-status error';
}
})
.finally(() => {
if (!collectionJob || isCollectionJobTerminal(collectionJob.status)) {
setApiFormBlocked(false);
}
});
}
function startCollectionFromCurrentProbe(powerOnIfHostOff) {
const { isValid, payload, errors } = validateCollectForm();
renderFormErrors(errors);
if (!isValid) {
renderApiConnectStatus(false, null);
resetApiProbeState();
return;
}
if (!apiProbeResult || !apiProbeResult.reachable) {
const status = document.getElementById('api-connect-status');
if (status) {
status.textContent = 'Сначала выполните проверку подключения.';
status.className = 'api-connect-status error';
}
return;
}
clearApiPowerDecisionTimer();
payload.power_on_if_host_off = Boolean(powerOnIfHostOff);
startCollectionJob(payload);
}
function renderApiProbeState() {
const collectButton = document.getElementById('api-connect-btn');
const status = document.getElementById('api-connect-status');
const decision = document.getElementById('api-power-decision');
const decisionText = document.getElementById('api-power-decision-text');
if (!collectButton || !status || !decision || !decisionText) {
return;
}
decision.classList.add('hidden');
clearApiPowerDecisionTimer();
collectButton.disabled = !apiProbeResult || !apiProbeResult.reachable;
if (!apiProbeResult || !apiProbeResult.reachable) {
status.textContent = 'Проверка подключения не пройдена.';
status.className = 'api-connect-status error';
return;
}
if (apiProbeResult.host_powered_on) {
status.textContent = apiProbeResult.message || 'Связь с BMC есть, host включен.';
status.className = 'api-connect-status success';
collectButton.disabled = false;
return;
}
status.textContent = apiProbeResult.message || 'Связь с BMC есть, host выключен.';
status.className = 'api-connect-status warning';
if (!apiProbeResult.power_control_available) {
collectButton.disabled = false;
return;
}
decision.classList.remove('hidden');
let secondsLeft = 5;
const updateDecisionText = () => {
decisionText.textContent = `Если не выбрать действие, сбор начнется без включения через ${secondsLeft} сек.`;
};
updateDecisionText();
apiPowerDecisionTimer = window.setInterval(() => {
secondsLeft -= 1;
if (secondsLeft <= 0) {
clearApiPowerDecisionTimer();
startCollectionFromCurrentProbe(false);
return;
}
updateDecisionText();
}, 1000);
}
function resetApiProbeState() {
apiProbeResult = null;
clearApiPowerDecisionTimer();
const collectButton = document.getElementById('api-connect-btn');
const decision = document.getElementById('api-power-decision');
if (collectButton) {
collectButton.disabled = true;
}
if (decision) {
decision.classList.add('hidden');
}
}
function clearApiPowerDecisionTimer() {
if (!apiPowerDecisionTimer) {
return;
}
window.clearInterval(apiPowerDecisionTimer);
apiPowerDecisionTimer = null;
}
function validateCollectForm() {
const host = getApiValue('host');
const portRaw = getApiValue('port');
@@ -230,6 +380,7 @@ function clearApiConnectStatus() {
}
function startCollectionJob(payload) {
clearApiPowerDecisionTimer();
resetCollectionJobState();
setApiFormBlocked(true);