export: align reanimator contract v2.7
This commit is contained in:
@@ -169,7 +169,10 @@ main {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#api-check-btn,
|
||||
#api-connect-btn,
|
||||
#api-power-on-collect-btn,
|
||||
#api-collect-off-btn,
|
||||
#convert-folder-btn,
|
||||
#convert-run-btn,
|
||||
#cancel-job-btn,
|
||||
@@ -185,7 +188,10 @@ main {
|
||||
transition: background-color 0.2s ease, opacity 0.2s ease;
|
||||
}
|
||||
|
||||
#api-check-btn:hover,
|
||||
#api-connect-btn:hover,
|
||||
#api-power-on-collect-btn:hover,
|
||||
#api-collect-off-btn:hover,
|
||||
#convert-folder-btn:hover,
|
||||
#convert-run-btn:hover,
|
||||
#cancel-job-btn:hover,
|
||||
@@ -195,7 +201,10 @@ main {
|
||||
|
||||
#convert-run-btn:disabled,
|
||||
#convert-folder-btn:disabled,
|
||||
#api-check-btn:disabled,
|
||||
#api-connect-btn:disabled,
|
||||
#api-power-on-collect-btn:disabled,
|
||||
#api-collect-off-btn:disabled,
|
||||
#cancel-job-btn:disabled,
|
||||
.upload-area button:disabled {
|
||||
opacity: 0.6;
|
||||
@@ -219,6 +228,10 @@ main {
|
||||
color: #0f4dba;
|
||||
}
|
||||
|
||||
.api-connect-status.warning {
|
||||
color: #b06a00;
|
||||
}
|
||||
|
||||
.convert-progress {
|
||||
margin-top: 0.9rem;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -63,9 +63,18 @@
|
||||
</div>
|
||||
|
||||
<div class="api-form-actions">
|
||||
<button id="api-connect-btn" type="submit">Подключиться</button>
|
||||
<button id="api-check-btn" type="button">Проверить</button>
|
||||
<button id="api-connect-btn" type="submit" disabled>Собрать</button>
|
||||
</div>
|
||||
<div id="api-connect-status" class="api-connect-status"></div>
|
||||
<div id="api-power-decision" class="api-connect-status hidden">
|
||||
<strong>Host выключен.</strong>
|
||||
<p id="api-power-decision-text">Если не выбрать действие, сбор начнется без включения через 5 секунд.</p>
|
||||
<div class="api-form-actions">
|
||||
<button id="api-power-on-collect-btn" type="button">Включить и собрать</button>
|
||||
<button id="api-collect-off-btn" type="button">Собирать выключенный</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<section id="api-job-status" class="job-status hidden" aria-live="polite">
|
||||
|
||||
Reference in New Issue
Block a user