Первичная настройка, часть 2: шаг 9 «Оповещения и резервные копии», шаг 10 — итоговая проверка готовности (живые проверки со ссылкой «Исправить»); проверка модуля неторговых поручений QUIK (объекты M2M, инструменты, справочник получателей, цикл, ошибки операций); восстановление из копии на /first-run по одноразовому коду; возврат на тот же шаг мастера после «Сохранить» (return_to с ?step); пароль SMTP не выводится в страницу; карточки почты, адресатов и копий — общие для настроек и мастера

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
zuevav
2026-10-05 16:18:28 +03:00
parent fdf0ddd498
commit 17df53f25b
15 changed files with 847 additions and 155 deletions
@@ -29,7 +29,7 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
<p>Пошаговый мастер первого запуска — состояние сохраняется после каждого шага</p>
</div>
<div class="wizard-progress" style="max-width:860px">
<div class="wizard-progress" style="max-width:940px">
<a href="/admin/wizard?step=1" title="Перейти к шагу 1" class="wstep {{if .Done.License}}done{{end}} {{if eq .Step 1}}current{{end}}"><span class="bub">{{if .Done.License}}✓{{else}}1{{end}}</span><span class="lbl">Лицензия</span></a>
<a href="/admin/wizard?step=2" title="Перейти к шагу 2" class="wstep {{if .Done.Postgres}}done{{end}} {{if eq .Step 2}}current{{end}}"><span class="bub">{{if .Done.Postgres}}✓{{else}}2{{end}}</span><span class="lbl">База bj</span></a>
<a href="/admin/wizard?step=3" title="Перейти к шагу 3" class="wstep {{if .Done.Depo}}done{{end}} {{if eq .Step 3}}current{{end}}"><span class="bub">{{if .Done.Depo}}✓{{else}}3{{end}}</span><span class="lbl">Учёт DEPO</span></a>
@@ -38,7 +38,8 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
<a href="/admin/wizard?step=6" title="Перейти к шагу 6" class="wstep {{if .Done.Crypto}}done{{end}} {{if eq .Step 6}}current{{end}}"><span class="bub">{{if .Done.Crypto}}✓{{else}}6{{end}}</span><span class="lbl">Валидата</span></a>
<a href="/admin/wizard?step=7" title="Перейти к шагу 7" class="wstep {{if .Done.Certs}}done{{end}} {{if eq .Step 7}}current{{end}}"><span class="bub">{{if .Done.Certs}}✓{{else}}7{{end}}</span><span class="lbl">Сертификаты</span></a>
<a href="/admin/wizard?step=8" title="Перейти к шагу 8" class="wstep {{if .Done.NSD}}done{{end}} {{if eq .Step 8}}current{{end}}"><span class="bub">{{if .Done.NSD}}✓{{else}}8{{end}}</span><span class="lbl">Шлюз НРД</span></a>
<a href="/admin/wizard?step=9" title="Перейти к шагу 9" class="wstep {{if .Done.TestRun}}done{{end}} {{if eq .Step 9}}current{{end}}"><span class="bub">{{if .Done.TestRun}}✓{{else}}9{{end}}</span><span class="lbl">Проверка</span></a>
<a href="/admin/wizard?step=9" title="Перейти к шагу 9" class="wstep {{if .Done.Alerts}}done{{end}} {{if eq .Step 9}}current{{end}}"><span class="bub">{{if .Done.Alerts}}✓{{else}}9{{end}}</span><span class="lbl">Оповещения</span></a>
<a href="/admin/wizard?step=10" title="Перейти к шагу 10" class="wstep {{if .Done.TestRun}}done{{end}} {{if eq .Step 10}}current{{end}}"><span class="bub">{{if .Done.TestRun}}✓{{else}}10{{end}}</span><span class="lbl">Проверка</span></a>
</div>
<p style="text-align:center;margin:-14px 0 18px;font-size:13px"><a href="/admin/">Пропустить мастер и перейти в систему →</a> <span class="muted">— вернуться к нему можно из «Настройка → Мастер настройки»</span></p>
@@ -366,6 +367,7 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
<strong>Если МНП уже есть.</strong> Адрес сервера и учётные данные — у вашего администратора QUIK.</div>
{{if not .DepoLicensed}}<p class="muted">Модуль неторговых поручений не входит в текущую лицензию — шаг пропускается.</p>
{{else}}{{with .Fancy}}{{template "quikSection" .}}{{end}}
{{if .Settings.Quik.Server}}{{template "mnpCheck" .}}{{end}}
{{if .Settings.Quik.M2MMissing}}<p style="color:var(--err);margin:8px 0">✗ Подключение есть, но под этим логином не видно объектов M2M: {{.Settings.Quik.M2MMissing}}. Проверьте имя базы МНП и права логина (роль InstrEu) и нажмите «Проверить» ещё раз.</p>{{end}}
<div class="help-block" style="margin-top:12px"><strong>Справочник депозитариев-получателей.</strong> Чтобы QUIK принимал поручения M2M, в МНП должен быть справочник депозитариев-получателей (<code>M2MDepoRecipients</code>): bj заполняет его сам по справочнику участников НРД. <b>Модуль МНП читает справочник только при запуске</b> — после первого заполнения попросите администратора QUIK перезапустить модуль неторговых поручений, иначе QUIK ответит «Неизвестный депозитарий получателя».<br><span class="muted">{{quikRecipientsInfo}}</span></div>{{end}}
<div style="margin-top:20px;display:flex;justify-content:space-between">
@@ -499,11 +501,34 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
</div>
{{end}}
{{/* ============= ШАГ 5: Тест-ран (режим разработки) / проверка подключений ============= */}}
{{/* ============= ШАГ 9: Оповещения и резервные копии ============= */}}
{{if eq .Step 9}}
<div class="card">
<h2><span class="dot {{if .Done.Alerts}}ok{{else}}warn{{end}}"></span>Шаг 9. Оповещения и резервные копии</h2>
<div class="help-block"><strong>Зачем.</strong> bj сам следит за обменом: НРД не отвечает, перевод не доставлен, ошибка обработки в DEPO, истекает сертификат ключа. Чтобы об этом узнали сразу, а не открыв bj, — укажите почту. Резервные копии позволят восстановить сервер целиком (настройки, заявки, шлюз НРД) после сбоя или переустановки.<br>
<strong>Можно позже.</strong> Шаг необязательный — обмен работает и без него, но мы настоятельно советуем настроить оба пункта.</div>
<style>.wiz9 label:not(:has(input[type=checkbox])){display:block;margin:0}.wiz9 label input:not([type=checkbox]),.wiz9 label select,.wiz9 .grid input,.wiz9 .grid select{width:100%;box-sizing:border-box;margin-top:4px}</style>
<div class="wiz9">
{{with .Fancy}}
<h3 style="margin:16px 0 6px">Почта для оповещений</h3>
{{template "smtpCard" .}}
{{template "notifyGroupsCard" .}}
<h3 style="margin:16px 0 6px">Резервные копии</h3>
{{template "backupCard" .}}
{{end}}
</div>
<div style="margin-top:20px;display:flex;justify-content:space-between">
<a href="/admin/wizard?step=8" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">← К шагу 8</a>
<a href="/admin/wizard?step=10" class="btn" style="{{if not .Done.Alerts}}background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);{{end}}text-decoration:none">{{if .Done.Alerts}}К проверке →{{else}}Пропустить →{{end}}</a>
</div>
</div>
{{end}}
{{/* ============= ШАГ 10: Проверка готовности (в режиме разработки — ещё тестовая заявка) ============= */}}
{{if eq .Step 10}}
<div class="card">
{{if devOn}}
<h2><span class="dot {{if .Done.TestRun}}ok{{else}}err{{end}}"></span>Шаг 9. Тестовая заявка</h2>
<h2><span class="dot {{if .Done.TestRun}}ok{{else}}err{{end}}"></span>Шаг 10. Тестовая заявка</h2>
<p>Прогон полного цикла: создание заявки → валидация → подпись → отправка в НРД (или mock) → ожидание Decision → подтверждение.</p>
<div class="help-block">
@@ -523,23 +548,33 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
{{if .Settings.LastTest.Message}}<tr><td class="muted">Сообщение</td><td>{{.Settings.LastTest.Message}}</td></tr>{{end}}
</table>
{{end}}
{{else}}
<h2><span class="dot {{if .Done.TestRun}}ok{{else}}err{{end}}"></span>Шаг 9. Проверка подключений</h2>
<p>Проверьте, что база данных, СКЗИ и шлюз НРД доступны: на странице «Статус» bj проверяет каждое подключение и показывает, что нужно исправить.</p>
<a href="/admin/status" class="btn" style="background:var(--ok);font-size:15px;padding:10px 20px;text-decoration:none;display:inline-block;margin-top:12px">Открыть проверку подключений →</a>
{{end}}
{{if not devOn}}<h2><span class="dot" id="rd-dot"></span>Шаг 10. Готовность к работе</h2>{{else}}<h3 style="margin-top:18px">Готовность к работе</h3>{{end}}
<p class="muted" style="margin:4px 0 10px">bj проверяет всё, что нужно для обмена с НРД, прямо сейчас. Красное — обмен работать не будет, жёлтое — работать будет, но лучше исправить. У каждой строки — ссылка туда, где это исправляется.</p>
<div id="rd-sum" style="font-weight:600;margin:6px 0">Проверяю…</div>
<table id="rd-tab" style="width:100%"><tbody></tbody></table>
<p style="margin-top:8px"><button type="button" class="btn" id="rd-again" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8)">Проверить ещё раз</button> <span class="muted" id="rd-at" style="font-size:12px"></span></p>
<script>
(function(){
var tb=document.querySelector('#rd-tab tbody'), sum=document.getElementById('rd-sum'), dot=document.getElementById('rd-dot');
function esc(s){return String(s).replace(/[&<>"]/g,function(c){return{'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]})}
var ic={ok:'<span style="color:var(--ok)">✓</span>',warn:'<span style="color:var(--warn)">⚠</span>',err:'<span style="color:var(--err)">✗</span>',skip:'<span class="muted">—</span>'};
function load(){ sum.textContent='Проверяю…'; tb.innerHTML='';
fetch('/admin/setup/readiness',{cache:'no-store'}).then(function(r){return r.json()}).then(function(d){
tb.innerHTML=d.checks.map(function(c){return '<tr><td style="width:24px;vertical-align:top">'+ic[c.state]+'</td><td style="vertical-align:top;white-space:nowrap;padding-right:10px"><b>'+esc(c.title)+'</b></td><td style="vertical-align:top">'+esc(c.detail)+(c.state!=='ok'&&c.link?' <a href="'+esc(c.link)+'">Исправить →</a>':'')+'</td></tr>'}).join('');
var red=d.checks.filter(function(c){return c.state==='err'}).length, yel=d.checks.filter(function(c){return c.state==='warn'}).length;
sum.innerHTML=d.ready?(yel?'<span style="color:var(--ok)">✓ Обмен с НРД готов к работе</span> <span class="muted" style="font-weight:400">— желательно исправить: '+yel+'</span>':'<span style="color:var(--ok)">✓ Всё готово к работе</span>'):'<span style="color:var(--err)">✗ Не готово: обязательных проблем — '+red+'</span>';
if(dot) dot.className='dot '+(d.ready?(yel?'warn':'ok'):'err');
document.getElementById('rd-at').textContent='проверено '+d.at;
}).catch(function(){ sum.textContent='Проверка не выполнилась — обновите страницу.'; }); }
document.getElementById('rd-again').onclick=load; load();
})();
</script>
<h3 style="margin-top:18px">Итоговая сводка</h3>
<table>
<tr><td class="muted">PostgreSQL</td><td>{{if .Done.Postgres}}<span style="color:var(--ok)">настроен</span>{{else}}<span class="muted">in-memory</span>{{end}}</td></tr>
<tr><td class="muted">Крипто-провайдер</td><td>{{if .Done.Crypto}}<span style="color:var(--ok)">Валидата Клиент L{{if .ValidataVer}} {{.ValidataVer}}{{end}}</span>{{else}}<span style="color:var(--err)">не настроен</span>{{end}}</td></tr>
<tr><td class="muted">Сертификатов установлено</td><td>{{len .Certs}}</td></tr>
<tr><td class="muted">ИШ НРД</td><td>{{if .Done.NSD}}<span style="color:var(--ok)">{{.Settings.NSD.Profile}}</span>{{else}}<span class="muted">{{if devOn}}mock-режим{{else}}не настроен{{end}}</span>{{end}}</td></tr>
{{if devOn}}<tr><td class="muted">Тестовый прогон</td><td>{{if .Done.TestRun}}<span style="color:var(--ok)">пройден</span>{{else}}<span class="muted">не запускался</span>{{end}}</td></tr>{{end}}
</table>
<div style="margin-top:20px;display:flex;justify-content:space-between">
<a href="/admin/wizard?step=8" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">← К шагу 8</a>
<a href="/admin/wizard?step=9" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">← К шагу 9</a>
<a href="/admin/" class="btn" style="text-decoration:none">Перейти к дашборду</a>
</div>
</div>