9737c787f9
Инфраструктура M2M (живой обмен с НРД через ИШ): - обработка M2MTransferResponse: ERROR(M2Mxx) → заявка Отклонена, сохранение ответа; INFO → ждём Decision; идемпотентность поллера - fallback-корреляция ответов с нулевым GUID (M2M14/M2M17) по FIFO - сырой XML ответа НРД в карточке заявки (для пересылки в ТП) - тестовый пакет роботу приведён к эталону m2m_robot_samples (CostInfo=Yes, 4 бумаги, IsolationStatus, DocumentSeries=сценарий); override паспорта - редирект из теста сразу в карточку заявки Мастер установки ключа Валидаты на флешку (admin/setup/keywizard): - пошаговый: загрузка .7z+пароль → выбор флешки → запись → справочник сертификатов (CRL) → перезапуск+проверка ИШ → готово - привилегированный воркер (bj-keymedia) в host-namespace через файл-обмен, bj-server остаётся в песочнице - сохранение структуры профиля архива (spr<N>), перечисление съёмных USB Прочее: - пакет-доказательство для ТП НРД + форма регистрации участника M2M - эталонные образцы робота (DOC/m2m_robot_samples) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
169 lines
4.9 KiB
JavaScript
169 lines
4.9 KiB
JavaScript
// Минимальный клиент wizard'а: рендерит страницы, ловит события из SSE,
|
|
// отправляет POST'ы на backend для перехода между стадиями.
|
|
|
|
let state = {
|
|
stage: "welcome",
|
|
precheck: [],
|
|
config: {},
|
|
steps: [],
|
|
errorMsg: "",
|
|
};
|
|
|
|
const STAGE_ORDER = ["welcome", "precheck", "config", "installing", "done"];
|
|
const STEP_ICONS = {
|
|
pending: "○",
|
|
running: "◐",
|
|
done: "✓",
|
|
skipped: "—",
|
|
failed: "✗",
|
|
};
|
|
|
|
function $(sel) { return document.querySelector(sel); }
|
|
function $$(sel) { return [...document.querySelectorAll(sel)]; }
|
|
|
|
function render() {
|
|
// stepper
|
|
$$("#stepper span").forEach(el => {
|
|
el.classList.remove("active", "done");
|
|
const stage = el.dataset.stage;
|
|
if (stage === state.stage) el.classList.add("active");
|
|
if (STAGE_ORDER.indexOf(stage) < STAGE_ORDER.indexOf(state.stage)) el.classList.add("done");
|
|
});
|
|
// pages
|
|
$$(".page").forEach(p => p.classList.toggle("active", p.dataset.stage === state.stage));
|
|
|
|
if (state.stage === "precheck") renderPrecheck();
|
|
if (state.stage === "installing" || state.stage === "done") renderSteps();
|
|
if (state.stage === "error") $("#error-message").textContent = state.errorMsg || "(нет деталей)";
|
|
if (state.stage === "done") {
|
|
// подставляем хост машины в админскую ссылку
|
|
const adminURL = window.location.protocol + "//" + window.location.hostname + ":8080/admin/setup";
|
|
$("#adminLink").href = adminURL;
|
|
$("#adminLink").textContent = "Перейти в " + adminURL + " →";
|
|
}
|
|
}
|
|
|
|
function renderPrecheck() {
|
|
const root = $("#precheck-results");
|
|
root.innerHTML = "";
|
|
let allOK = true;
|
|
for (const r of state.precheck || []) {
|
|
const div = document.createElement("div");
|
|
div.className = "check " + (r.ok ? "ok" : "bad");
|
|
div.innerHTML = `
|
|
<span class="check-icon">${r.ok ? "✓" : "✗"}</span>
|
|
<div>
|
|
<div class="check-title">${escapeHTML(r.title)}</div>
|
|
${r.message ? `<div class="check-msg">${escapeHTML(r.message)}</div>` : ""}
|
|
</div>`;
|
|
root.appendChild(div);
|
|
if (!r.ok) allOK = false;
|
|
}
|
|
$("#goConfigBtn").disabled = !allOK;
|
|
}
|
|
|
|
function renderSteps() {
|
|
const root = $("#step-list");
|
|
root.innerHTML = "";
|
|
let done = 0;
|
|
for (const s of state.steps || []) {
|
|
const li = document.createElement("li");
|
|
li.className = "step-" + s.status;
|
|
li.innerHTML = `
|
|
<span class="step-icon">${STEP_ICONS[s.status] || "○"}</span>
|
|
<div>
|
|
<div class="step-title">${escapeHTML(s.title)}</div>
|
|
${s.message ? `<div class="step-msg">${escapeHTML(s.message)}</div>` : ""}
|
|
</div>`;
|
|
root.appendChild(li);
|
|
if (s.status === "done" || s.status === "skipped") done++;
|
|
}
|
|
const total = state.steps.length;
|
|
const pct = total ? Math.round(100 * done / total) : 0;
|
|
$("#progress-bar").style.width = pct + "%";
|
|
}
|
|
|
|
function escapeHTML(s) {
|
|
return String(s).replace(/[&<>"']/g, c => ({
|
|
"&": "&", "<": "<", ">": ">", "\"": """, "'": "'"
|
|
}[c]));
|
|
}
|
|
|
|
// ------------- transitions -------------
|
|
|
|
async function startPrecheck() {
|
|
await fetch("/api/precheck", { method: "POST" });
|
|
}
|
|
|
|
function goWelcome() {
|
|
state.stage = "welcome";
|
|
render();
|
|
}
|
|
|
|
function goPrecheck() {
|
|
state.stage = "precheck";
|
|
render();
|
|
}
|
|
|
|
async function goConfig() {
|
|
state.stage = "config";
|
|
render();
|
|
}
|
|
|
|
async function startInstall() {
|
|
const form = $("#config-form");
|
|
const data = Object.fromEntries(new FormData(form).entries());
|
|
await fetch("/api/config", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(data),
|
|
});
|
|
await fetch("/api/install", { method: "POST" });
|
|
}
|
|
|
|
async function resetWizard() {
|
|
await fetch("/api/reset", { method: "POST" });
|
|
}
|
|
|
|
// ------------- SSE -------------
|
|
|
|
function connectSSE() {
|
|
const es = new EventSource("/api/events");
|
|
es.addEventListener("snapshot", e => {
|
|
const snap = JSON.parse(e.data);
|
|
state.stage = snap.stage;
|
|
state.precheck = snap.precheck || [];
|
|
state.config = snap.config || {};
|
|
state.steps = snap.steps || [];
|
|
state.errorMsg = snap.errorMsg || "";
|
|
render();
|
|
});
|
|
es.addEventListener("stage", e => {
|
|
state.stage = JSON.parse(e.data).stage;
|
|
render();
|
|
});
|
|
es.addEventListener("precheck", e => {
|
|
state.precheck = JSON.parse(e.data);
|
|
render();
|
|
});
|
|
es.addEventListener("step", e => {
|
|
const s = JSON.parse(e.data);
|
|
const idx = state.steps.findIndex(x => x.id === s.id);
|
|
if (idx >= 0) state.steps[idx] = s;
|
|
render();
|
|
});
|
|
es.addEventListener("error", e => {
|
|
state.errorMsg = JSON.parse(e.data).message;
|
|
state.stage = "error";
|
|
render();
|
|
});
|
|
es.addEventListener("reset", () => {
|
|
location.reload();
|
|
});
|
|
es.onerror = () => {
|
|
// авто-реконнект делает EventSource сам, ничего не делаем
|
|
};
|
|
}
|
|
|
|
connectSSE();
|