Files
Bridge-and-Join-s/internal/lkgateway/web/templates/admin_wizard.html
T

405 lines
48 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{define "content"}}
<style>
.wiz-head { text-align:center; padding:8px 0 4px; }
.wiz-head h1 { font-size:26px; font-weight:720; letter-spacing:-0.02em; margin:0 0 6px; }
.wiz-head p { color:var(--muted); margin:0; }
.wizard-progress { display:flex; align-items:center; justify-content:center; gap:0; margin:24px auto 28px; max-width:680px; }
a.wstep { text-decoration:none; cursor:pointer; }
a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
.wstep { display:flex; flex-direction:column; align-items:center; gap:7px; flex:1; position:relative; }
.wstep .bub { width:34px; height:34px; border-radius:50%; display:flex; align-items:center; justify-content:center; font-size:14px; font-weight:650; background:var(--card-2); border:1.5px solid var(--border-strong); color:var(--muted); z-index:1; transition:all .2s; }
.wstep .lbl { font-size:12px; color:var(--muted); font-weight:550; }
.wstep::before { content:""; position:absolute; top:17px; left:-50%; width:100%; height:2px; background:var(--border); z-index:0; }
.wstep:first-child::before { display:none; }
.wstep.done .bub { background:var(--ok); border-color:var(--ok); color:#fff; }
.wstep.done .lbl { color:var(--text-2); }
.wstep.done::before { background:var(--ok); }
.wstep.current .bub { background:var(--accent); border-color:var(--accent); color:#fff; box-shadow:0 0 0 4px var(--accent-weak); }
.wstep.current .lbl { color:var(--accent); font-weight:650; }
.tooltip { display:inline-block; background:var(--border); color:var(--muted); border-radius:50%; width:16px; height:16px; line-height:16px; text-align:center; font-size:11px; cursor:help; margin-left:4px; }
.where { font-size:12px; color:var(--accent); margin-left:8px; }
.help-block { background:var(--accent-weak); background:var(--accent-weak); padding:10px 14px; margin:10px 0; font-size:13px; border-radius:0 8px 8px 0; }
.help-block strong { color:var(--accent); }
.wiz-card { max-width:680px; margin:0 auto; }
</style>
<div class="wiz-head">
<h1>Настройка Bridge&amp;Join</h1>
<p>Пошаговый мастер первого запуска — состояние сохраняется после каждого шага</p>
</div>
<div class="wizard-progress" style="max-width:860px">
<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>
<a href="/admin/wizard?step=4" title="Перейти к шагу 4" class="wstep {{if .Done.Crypto}}done{{end}} {{if eq .Step 4}}current{{end}}"><span class="bub">{{if .Done.Crypto}}✓{{else}}4{{end}}</span><span class="lbl">Валидата</span></a>
<a href="/admin/wizard?step=5" title="Перейти к шагу 5" class="wstep {{if .Done.Certs}}done{{end}} {{if eq .Step 5}}current{{end}}"><span class="bub">{{if .Done.Certs}}✓{{else}}5{{end}}</span><span class="lbl">Сертификаты</span></a>
<a href="/admin/wizard?step=6" title="Перейти к шагу 6" class="wstep {{if .Done.NSD}}done{{end}} {{if eq .Step 6}}current{{end}}"><span class="bub">{{if .Done.NSD}}✓{{else}}6{{end}}</span><span class="lbl">Шлюз НРД</span></a>
<a href="/admin/wizard?step=7" title="Перейти к шагу 7" class="wstep {{if .Done.TestRun}}done{{end}} {{if eq .Step 7}}current{{end}}"><span class="bub">{{if .Done.TestRun}}✓{{else}}7{{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>
{{if .Flash}}<div class="card wiz-card" style="background:var(--accent-weak)"><p style="margin:0">{{.Flash}}</p></div>{{end}}
<div class="wiz-card">
{{/* ============= ШАГ 1: Лицензия ============= */}}
{{if eq .Step 1}}
<div class="card">
<h2><span class="dot {{if .Done.License}}ok{{else}}err{{end}}"></span>Шаг 1. Лицензия</h2>
<div class="help-block"><strong>Что это.</strong> Ключ продукта (BJ-XXXX-XXXX-XXXX) привязывает этот сервер к вашей лицензии.<br>
<strong>Зачем.</strong> От лицензии зависят модули (обмен с DEPO и QUIK, личный кабинет, НПР/УДС) и получение обновлений: bj скачает и поставит модули, входящие в лицензию, автоматически.<br>
<strong>Где взять.</strong> Портал клиента <a href="https://lic.zetit.ru/portal/" target="_blank">lic.zetit.ru/portal</a> или письмо от ZETIT.</div>
{{if .Done.License}}<p>✓ <strong>Ключ продукта введён</strong>, лицензия активна: <strong>{{.Lic.Tenant}}</strong>, план {{.Lic.Plan}}, модули: {{if .Lic.Features}}{{range $i, $f := .Lic.Features}}{{if $i}}, {{end}}{{$f}}{{end}}{{else}}только ядро{{end}}; {{.Lic.Message}}</p>{{end}}
{{with .Update}}{{if .HasUpdate}}
<div style="border:1px solid var(--accent);border-radius:8px;padding:12px 14px;margin-top:12px;background:var(--accent-weak)">
<p style="margin:0 0 6px;font-weight:600">На сервере лицензий есть что поставить (выпуск {{.Available}})</p>
<p class="muted" style="margin:0 0 10px;font-size:13px">Образ установил ту версию bj, с которой был собран. На сервере лицензий есть более новая — с исправлениями ядра, установщика и служб. Поставьте её сейчас, до настройки: bj перезапустится примерно на 10 секунд, затем обновите страницу.<br>{{.Message}}</p>
<form method="post" action="/admin/setup/update/apply" style="margin:0">
<input type="hidden" name="return_to" value="/admin/wizard?step=1">
<button type="submit" class="btn">Обновить сейчас</button>
</form>
</div>
{{else}}<p class="muted" style="font-size:13px">✓ Установлена последняя версия bj ({{.Available}}).</p>{{end}}{{end}}
{{if .Done.License}}<details style="margin-top:12px"><summary style="cursor:pointer;color:var(--accent);font-size:13px">Сменить ключ продукта (обычно не нужно)</summary>
<form method="post" action="/admin/setup/license/activate" style="margin-top:12px;display:grid;gap:10px">
<input type="hidden" name="return_to" value="/admin/wizard?step=1">
<label>Ключ продукта</label>
<input type="text" name="code" placeholder="BJ-XXXX-XXXX-XXXX" autocomplete="off" style="padding:8px;text-transform:uppercase;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px">
<button type="submit" class="btn" style="justify-self:start">{{if .Done.License}}Сменить ключ{{else}}Активировать{{end}}</button>
</form>
</details>{{else}}
<form method="post" action="/admin/setup/license/activate" style="margin-top:12px;display:grid;gap:10px">
<input type="hidden" name="return_to" value="/admin/wizard?step=1">
<label>Ключ продукта</label>
<input type="text" name="code" placeholder="BJ-XXXX-XXXX-XXXX" autocomplete="off" style="padding:8px;text-transform:uppercase;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px">
<button type="submit" class="btn" style="justify-self:start">{{if .Done.License}}Сменить ключ{{else}}Активировать{{end}}</button>
</form>{{end}}
<div style="margin-top:20px;display:flex;justify-content:space-between"><span></span>
{{if .Done.License}}<a href="/admin/wizard?step=2" class="btn" style="text-decoration:none">К шагу 2 →</a>{{else}}<a href="/admin/wizard?step=2" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">Пропустить (ввести позже) →</a>{{end}}</div>
</div>
{{end}}
{{/* ============= ШАГ 3: Учёт DEPO (Fancy) ============= */}}
{{if eq .Step 3}}
<div class="card">
<h2><span class="dot {{if .Done.Depo}}ok{{else}}err{{end}}"></span>Шаг 3. Учётная система DEPO (Fancy)</h2>
<div class="help-block"><strong>Что это.</strong> Подключение к базе депозитарного учёта DEPO (MS SQL / PostgreSQL).<br>
<strong>Зачем.</strong> bj сверяет с DEPO каждый перевод (клиент, счёт, остаток, квалификация) и передаёт в DEPO поручения и их исполнение — документы создаются в учёте автоматически. Отсюда же bj возьмёт ваши реквизиты в НРД (шаг 6), чтобы не вводить их вручную.<br>
<strong>Что нужно.</strong> Адрес сервера БД и логин с правами чтения DEPO и записи в трансферные таблицы FTV_M2M (выдаёт администратор СУБД).</div>
{{if not .DepoLicensed}}
<p class="muted">Модуль обмена с DEPO и QUIK не входит в текущую лицензию — шаг пропускается. После добавления модуля в лицензию он станет доступен здесь и в «Настройка → Обмен Fancy».</p>
{{else}}
<p class="muted" style="margin:6px 0 0">Три части: подключение к базе, трансферные таблицы обмена, обработка (job), которая превращает записи bj в документы DEPO.</p>
<h3 style="margin-top:16px">1. Подключение к базе DEPO {{if .Settings.Fancy.Server}}<span style="color:var(--ok);font-size:14px">✓ {{.Settings.Fancy.Server}}</span>{{end}}</h3>
<form method="post" action="/admin/setup/fancy" style="margin-top:12px;display:grid;gap:10px">
<input type="hidden" name="return_to" value="/admin/wizard?step=3">
<div><label>Тип СУБД</label><select name="db_type" style="padding:8px;width:100%;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px"><option value="mssql" {{if ne .Settings.Fancy.DBType "postgres"}}selected{{end}}>MS SQL Server</option><option value="postgres" {{if eq .Settings.Fancy.DBType "postgres"}}selected{{end}}>PostgreSQL</option></select></div>
<div><label>Сервер (адрес или host\instance)</label><input type="text" name="server" value="{{.Settings.Fancy.Server}}" placeholder="10.0.0.21" style="padding:8px;width:100%;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px"></div>
<div><label>Порт</label><input type="text" name="port" value="{{if .Settings.Fancy.Port}}{{.Settings.Fancy.Port}}{{else}}1433{{end}}" style="padding:8px;width:100%;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px"></div>
<div><label>Логин БД</label><input type="text" name="user" value="{{.Settings.Fancy.User}}" autocomplete="off" style="padding:8px;width:100%;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px"></div>
<div><label>Пароль {{if .Settings.Fancy.Server}}<span class="muted">(пусто — оставить прежний)</span>{{end}}</label><input type="password" name="password" autocomplete="new-password" style="padding:8px;width:100%;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px"></div>
<input type="hidden" name="encrypt" value="{{if .Settings.Fancy.Encrypt}}{{.Settings.Fancy.Encrypt}}{{else}}disable{{end}}">
<div style="display:flex;gap:10px;flex-wrap:wrap"><button type="submit" class="btn">Сохранить</button>
{{if .Settings.Fancy.Server}}<button type="submit" class="btn" formaction="/admin/setup/fancy/test" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8)">Проверить соединение</button>{{end}}</div>
</form>
{{if .Settings.Fancy.Server}}
{{$btn2 := "background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8)"}}
<h3 style="margin-top:22px">2. Трансферные таблицы FTV_M2M {{if .Settings.Fancy.DepoTablesCheckOK}}<span style="color:var(--ok);font-size:14px">✓ на месте</span>{{end}}</h3>
<div class="help-block" style="font-size:13px"><strong>Что это.</strong> Четыре таблицы в базе DEPO, через которые идёт обмен: поручения <code>FTV_M2M</code> и их строки, события <code>FTV_M2M_EVENTS</code>, справочник участников НРД. Данные учёта они не трогают; создание можно повторять — добавляется только недостающее.</div>
<form method="post" action="/admin/setup/fancy/depo-tables" style="display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap">
<input type="hidden" name="return_to" value="/admin/wizard?step=3">
<input type="hidden" name="depo_tables_mode" value="auto">
<input type="hidden" name="iis_contract_marker" value="{{.Settings.Fancy.IISContractMarker}}">
<label style="flex:1;min-width:220px">База DEPO на сервере <input type="text" name="depo_database" value="{{.Settings.Fancy.DepoDatabase}}" placeholder="{{.DepoTables.DB}}" pattern="[A-Za-z][A-Za-z0-9_]*" style="width:100%;padding:8px"></label>
<button type="submit" class="btn" style="{{$btn2}}">Сохранить имя базы</button>
</form>
{{if not .Settings.Fancy.DepoTablesCheckOK}}
<form method="post" action="/admin/setup/fancy/depo-tables/create" style="margin-top:10px;display:grid;gap:8px">
<input type="hidden" name="return_to" value="/admin/wizard?step=3">
<p style="margin:0"><b>Создать таблицы в базе {{.DepoTables.DB}}.</b> <span class="muted" style="font-size:13px">Логин администратора СУБД ({{if .DepoTables.Postgres}}владелец базы{{else}}db_owner в базе или sa{{end}}) нужен один раз и не сохраняется; bj заодно выдаст права своему логину {{.DepoTables.User}}. Пусто — попробовать логином bj.</span></p>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px">
<input type="text" name="admin_user" autocomplete="off" placeholder="логин, например {{if .DepoTables.Postgres}}postgres{{else}}sa{{end}}" style="padding:8px">
<input type="password" name="admin_password" autocomplete="new-password" placeholder="пароль" style="padding:8px">
</div>
<div><button type="submit" class="btn">Создать таблицы</button> <a href="/admin/setup/fancy/depo-tables/script.sql" style="font-size:13px;margin-left:8px">или скачать скрипт для администратора</a></div>
</form>
{{end}}
<form method="post" action="/admin/setup/fancy/depo-tables/check" style="margin-top:8px"><input type="hidden" name="return_to" value="/admin/wizard?step=3"><button type="submit" class="btn" style="{{$btn2}}">Проверить таблицы</button></form>
{{if .Settings.Fancy.DepoTablesCheck}}<p style="font-size:13px;margin-top:6px;{{if .Settings.Fancy.DepoTablesCheckOK}}color:var(--ok){{else}}color:var(--err){{end}}">{{if .Settings.Fancy.DepoTablesCheckOK}}✓{{else}}✗{{end}} {{.Settings.Fancy.DepoTablesCheck}}</p>{{end}}
<h3 style="margin-top:22px">3. Обработка обмена (job) {{if .Settings.Fancy.DepoJobCheckOK}}<span style="color:var(--ok);font-size:14px">✓ работает</span>{{end}}</h3>
<div class="help-block" style="font-size:13px"><strong>Что это.</strong> Процедура <code>{{.DepoJob.Proc}}</code> в базе DEPO каждые {{.DepoJob.Every}} с забирает записи bj из трансферных таблиц и создаёт по ним документы и операции DEPO. Кто-то должен её запускать.<br>
{{if .DepoEdition}}<strong>Ваш сервер:</strong> {{.DepoEdition}} — {{if .DepoAgent}}есть SQL Server Agent, рекомендуем джобу Agent, её создаст bj.{{else}}редакция без SQL Server Agent, процедуру будет запускать сам bj.{{end}}{{else if .DepoTables.Postgres}}<strong>Ваш сервер:</strong> PostgreSQL — процедуру будет запускать сам bj.{{end}}</div>
{{$mode := .Settings.Fancy.DepoJobMode}}{{if not $mode}}{{$mode = .DepoJobDef}}{{end}}
<form method="post" action="/admin/setup/fancy/depo-job" style="display:grid;gap:6px">
<input type="hidden" name="return_to" value="/admin/wizard?step=3">
<input type="hidden" name="depo_job_every_sec" value="{{.DepoJob.Every}}">
<input type="hidden" name="depo_job_proc" value="{{.Settings.Fancy.DepoJobProc}}">
<input type="hidden" name="depo_job_run_as" value="{{.Settings.Fancy.DepoJobRunAs}}">
{{if not .DepoTables.Postgres}}<label style="display:flex;gap:8px;align-items:flex-start"><input type="radio" name="depo_job_mode" value="sql-auto" {{if eq $mode "sql-auto"}}checked{{end}}> <span><b>Джоба SQL Server Agent, создаёт bj</b> <span class="muted" style="font-size:13px">— рекомендуется: работает, даже когда bj перезапускается; нужен разовый логин администратора SQL.</span></span></label>{{end}}
<label style="display:flex;gap:8px;align-items:flex-start"><input type="radio" name="depo_job_mode" value="bj" {{if eq $mode "bj"}}checked{{end}}> <span><b>Запускает сам bj</b> <span class="muted" style="font-size:13px">— для SQL Server Express и PostgreSQL; нужно одно право на процедуру.</span></span></label>
{{if not .DepoTables.Postgres}}<label style="display:flex;gap:8px;align-items:flex-start"><input type="radio" name="depo_job_mode" value="sql-manual" {{if eq $mode "sql-manual"}}checked{{end}}> <span><b>Джоба SQL Server Agent, создаёт администратор</b> <span class="muted" style="font-size:13px">— bj даст готовый скрипт.</span></span></label>{{end}}
<div><button type="submit" class="btn" style="{{$btn2}}">{{if .Settings.Fancy.DepoJobMode}}Сменить способ{{else}}Выбрать способ{{end}}</button></div>
</form>
{{if eq .Settings.Fancy.DepoJobMode "sql-auto"}}
<form method="post" action="/admin/setup/fancy/depo-job/create" style="margin-top:10px;display:grid;gap:8px">
<input type="hidden" name="return_to" value="/admin/wizard?step=3">
<p style="margin:0"><b>Создать джобу {{.DepoJob.Name}}.</b> <span class="muted" style="font-size:13px">Логин администратора SQL (sysadmin, например sa) нужен один раз и не сохраняется. SQL Server Agent должен быть запущен.</span></p>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px">
<input type="text" name="admin_user" autocomplete="off" placeholder="логин, например sa" style="padding:8px">
<input type="password" name="admin_password" autocomplete="new-password" placeholder="пароль" style="padding:8px">
</div>
<div><button type="submit" class="btn">Создать джобу и проверить</button></div>
</form>
{{else if eq .Settings.Fancy.DepoJobMode "bj"}}
<p style="margin:10px 0 4px"><b>Администратору SQL — выполнить один раз:</b></p><pre class="copyable" style="font-size:12px">{{.DepoJob.GrantScript}}</pre>
{{else if eq .Settings.Fancy.DepoJobMode "sql-manual"}}
<p style="margin:10px 0 4px"><b>Скрипт для администратора SQL:</b></p><pre class="copyable" style="font-size:12px;max-height:260px;overflow:auto">{{.DepoJob.AgentScript}}</pre>
{{end}}
{{if .Settings.Fancy.DepoJobMode}}
<form method="post" action="/admin/setup/fancy/depo-job/check" style="margin-top:8px"><input type="hidden" name="return_to" value="/admin/wizard?step=3"><button type="submit" class="btn" style="{{$btn2}}">Проверить обработку</button> <span class="muted" style="font-size:12px">bj кладёт служебное событие и ждёт до 40 с, пока его обработают</span></form>
{{if .Settings.Fancy.DepoJobCheck}}<p style="font-size:13px;margin-top:6px;{{if .Settings.Fancy.DepoJobCheckOK}}color:var(--ok){{else}}color:var(--err){{end}}">{{if .Settings.Fancy.DepoJobCheckOK}}✓{{else}}✗{{end}} {{.Settings.Fancy.DepoJobCheck}}</p>{{end}}
{{end}}
{{end}}
{{end}}
<div style="margin-top:20px;display:flex;justify-content:space-between">
<a href="/admin/wizard?step=2" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">← К шагу 2</a>
<a href="/admin/wizard?step=4" class="btn" style="{{if not .Done.Depo}}background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);{{end}}text-decoration:none">{{if .Done.Depo}}К шагу 4 →{{else}}Пропустить →{{end}}</a></div>
</div>
{{end}}
{{/* ============= ШАГ 1: PostgreSQL ============= */}}
{{if eq .Step 2}}
<div class="card">
<h2><span class="dot {{if .Done.Postgres}}ok{{else}}err{{end}}"></span>Шаг 2. База данных bj (PostgreSQL)</h2>
<p>База самого bj: журнал заявок и поручений, история обмена с НРД и DEPO, пользователи.</p>
{{if .Done.Postgres}}<p>✓ База готова{{if .PGSummary}}: {{.PGSummary}}{{end}}. Её создал установщик — ничего делать не нужно, переходите к следующему шагу.</p>{{end}}
{{if not .Done.Postgres}}<div class="help-block">
<strong>Что выбрать?</strong> Если у вас уже есть рабочий PostgreSQL — нажмите «У меня уже есть PostgreSQL» и введите DSN. Если впервые настраиваете — выберите «Поднять автоматически», система сама развернёт контейнер с PostgreSQL и накатит миграции.
</div>{{end}}
{{if and devOn (not .Settings.Postgres.DSN)}}
<div style="background:var(--bg);border:1px solid var(--accent);border-radius:6px;padding:14px;margin-top:12px">
<h3 style="margin:0 0 8px 0;font-size:15px">Вариант А — для тех, у кого нет своего PostgreSQL</h3>
<p class="muted" style="margin:0 0 10px 0">Bridge-and-Join-s сам поднимет PostgreSQL в контейнере (podman-compose), создаст БД <code>bj</code> и накатит миграции. Подходит для дев-стенда. Для продакшена лучше указать свой DSN.</p>
<form method="post" action="/admin/setup/postgres/quick-start" style="margin:0">
<button type="submit" class="btn" style="background:var(--ok)">Поднять локальный PostgreSQL автоматически</button>
<span class="muted" style="margin-left:10px;font-size:12px">~10-30 сек</span>
</form>
</div>
{{end}}
<details style="margin-top:14px" {{if not .Settings.Postgres.DSN}}open{{end}}>
<summary style="cursor:pointer;color:var(--accent)">{{if .Settings.Postgres.DSN}}Подключить другую базу (обычно не нужно){{else}}Вариант Б — у меня уже есть PostgreSQL, введу DSN сам{{end}}</summary>
<form method="post" action="/admin/setup/postgres" style="margin-top:12px">
<label>DSN (строка подключения) <span class="tooltip" title="Формат: postgres://пользователь:пароль@хост:порт/база?sslmode=disable. Например: postgres://bj:secret@db.example.com:5432/bj?sslmode=require">?</span></label>
<input type="text" name="dsn" value="{{.Settings.Postgres.DSN}}" placeholder="postgres://bj:secret@127.0.0.1:5432/bj?sslmode=disable" style="width:100%;padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;margin-top:6px">
<p class="muted" style="margin-top:8px">При сохранении выполняется проверка подключения (Ping). Если БД недоступна — будет ошибка.</p>
<button type="submit" class="btn" style="margin-top:8px">Сохранить и проверить</button>
</form>
</details>
<div style="margin-top:20px;display:flex;justify-content:space-between">
<a href="/admin/wizard?step=1" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">← К шагу 1</a>
{{if .Done.Postgres}}<a href="/admin/wizard?step=3" class="btn" style="text-decoration:none">К шагу 3 →</a>{{else}}<button class="btn" disabled style="opacity:0.5;cursor:not-allowed">К шагу 2 → (сначала настройте PostgreSQL или нажмите «in-memory режим»)</button>{{end}}
</div>
{{if not .Done.Postgres}}<p style="margin-top:8px"><a href="/admin/wizard?step=3&skip=postgres" style="font-size:13px">Пропустить (буду работать в режиме in-memory — без сохранения сделок)</a></p>{{end}}
</div>
{{end}}
{{/* ============= ШАГ 2: Крипто (Валидата) ============= */}}
{{if eq .Step 4}}
<div class="card">
<h2><span class="dot {{if .Done.Crypto}}ok{{else}}err{{end}}"></span>Шаг 4. СКЗИ «Валидата Клиент L»</h2>
<p>СКЗИ «Валидата Клиент L» проверяет подписи НРД на квитанциях и сообщениях и нужно для работы с ключами и сертификатами (шаг 5).</p>
{{if .ValidataVer}}
<p>✓ Валидата установлена вместе с bj: версия {{.ValidataVer}}, каталог <code>/opt/Validata/VDCSP</code>. Ничего настраивать на этом шаге не нужно.</p>
<p class="muted" style="font-size:13px">bj работает с Валидатой через свою службу криптографии <code>bj-crypto</code>{{if eq .CryptoSvc "active"}} — она запущена{{else}} — сейчас она не запущена: служба ставится обновлением с сервера лицензий (шаг 1, кнопка «Обновить сейчас»), а работает с ключом, установленным на шаге 5{{end}}.</p>
{{else}}
<p style="color:var(--err)">Валидата на этом сервере не найдена.</p>
<div class="help-block">
<strong>Как поставить.</strong> С образа Bridge&amp;Join она ставится сама. Если bj ставили на готовую систему: пакеты <code>zpki</code> и <code>zsdk</code> из дистрибутива НРД (<a href="https://fs.moex.com/cdp/po/ClientL_Other.zip" target="_blank">ClientL_Other.zip</a> для Ubuntu 22.04, <a href="https://fs.moex.com/cdp/po/ClientL_ALSE.zip" target="_blank">ClientL_ALSE.zip</a> для Astra Linux) — <code>sudo apt install ./zpki_*.deb ./zsdk_*.deb</code>, подробнее — <a href="/admin/help/crypto">справка</a>. После установки обновите эту страницу.
</div>
{{if devOn}}<form method="post" action="/admin/setup/crypto" style="margin-top:12px">
<input type="hidden" name="provider" value="stub">
<button type="submit" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8)">Режим разработки: работать без криптографии (stub)</button>
</form>{{end}}
{{end}}
<div style="margin-top:20px;display:flex;justify-content:space-between">
<a href="/admin/wizard?step=3" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">← К шагу 3</a>
{{if .Done.Crypto}}<a href="/admin/wizard?step=5" class="btn" style="text-decoration:none">К шагу 5 →</a>{{else}}<a href="/admin/wizard?step=5&skip=crypto" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">Пропустить →</a>{{end}}
</div>
</div>
{{end}}
{{/* ============= ШАГ 3: Сертификаты ============= */}}
{{if eq .Step 5}}
<div class="card">
<h2><span class="dot {{if .Done.Certs}}ok{{else}}err{{end}}"></span>Шаг 5. Сертификаты</h2>
<p>Здесь две части: ключ и сертификат вашей организации (ставите вы) и сертификаты НРД и удостоверяющих центров (bj ведёт их сам).</p>
<h3 style="margin-top:14px">1. Ключ и сертификат организации</h3>
<div class="help-block"><strong>Что это.</strong> Ключ ЭП, которым подписываются сообщения в НРД, и его сертификат (выдаёт УЦ Московской биржи). Архив ключа с паролем вам передаёт НРД/УЦ.<br>
<strong>Как поставить.</strong> Мастер установки ключа: загрузить архив → запись на носитель → справочник сертификатов → проверка подписи Валидатой.</div>
<a href="/admin/setup/keywizard" class="btn" style="text-decoration:none;display:inline-block">Открыть мастер установки ключа →</a>
<h3 style="margin-top:22px">2. Сертификаты НРД и УЦ — автоматически</h3>
<p>✓ <strong>Автоматическое обновление {{if .Settings.NSDCerts.Manual}}<span style="color:var(--warn)">выключено</span> (включается в «Настройка → Криптография»){{else}}включено{{end}}.</strong> Делать на этом шаге ничего не нужно.</p>
<div class="help-block" style="font-size:13px">
<strong>Как это работает.</strong> Действующие сертификаты НРД и удостоверяющих центров Московской биржи поставляются вместе с bj и приезжают с обновлениями. Каждые 6 часов bj сверяет их со справочником сертификатов ИШ: добавляет недостающие, а заменённые НРД убирает строго после срока, который НРД объявил в уведомлении. Перед каждым изменением делается резервная копия и проверка подписи, при ошибке всё откатывается.<br>
<strong>Тестовые и боевые.</strong> Сертификат попадает только в справочник своего контура: боевые (издатель «Корневой УЦ») — только к боевому ключу, тестовые (издатель «Тестовый УЦ») — только к тестовому. Поэтому смешать контуры нельзя.<br>
<strong>Откуда берутся.</strong> НРД и Московская биржа публикуют сертификаты в постоянных местах: комплект ГОСТ МБ, НКЦ и НРД — <code>https://fs.moex.com/cdp/sert/GOST.zip</code>, сертификаты УЦ — <code>https://fs.moex.com/cdp/</code>. Когда там появляется новый сертификат, мы добавляем его в комплект bj, и он приезжает к вам с обновлением.<br>
Состояние, ручная проверка и выключение — «Настройка → Криптография → Сертификаты НРД в справочнике ИШ».
</div>
{{if .Settings.NSDCerts.LastRows}}<p class="muted" style="font-size:13px">Последняя сверка: {{.Settings.NSDCerts.LastRun.Format "02.01.2006 15:04"}}{{if .Settings.NSDCerts.LastProfile}}, профиль ИШ {{.Settings.NSDCerts.LastProfile}}{{end}}.</p>{{else}}<p class="muted" style="font-size:13px">Первая сверка пройдёт, когда будут настроены ключ и шлюз НРД (шаг 6).</p>{{end}}
{{if .Certs}}
<h3 style="margin-top:18px">Установленные сертификаты ({{len .Certs}})</h3>
<table>
<thead><tr><th>Владелец</th><th>Издатель</th><th>Действителен до</th><th>ИНН</th><th>Ключ</th></tr></thead>
<tbody>
{{range .Certs}}
<tr>
<td>{{.SubjectCN}}</td>
<td>{{.IssuerCN}}</td>
<td>{{.NotAfter.Format "02.01.2006"}}</td>
<td>{{if .INN}}<code>{{.INN}}</code>{{else}}—{{end}}</td>
<td>{{if .HasPrivateKey}}<span style="color:var(--ok)">есть</span>{{else}}<span class="muted">нет</span>{{end}}</td>
</tr>
{{end}}
</tbody>
</table>
{{else}}
<p class="muted" style="margin-top:12px">Ключ организации ещё не установлен. Можно пропустить этот шаг и вернуться к нему, когда будет архив ключа.</p>
{{end}}
<div style="margin-top:20px;display:flex;justify-content:space-between">
<a href="/admin/wizard?step=4" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">← К шагу 4</a>
<a href="/admin/wizard?step=6" class="btn" style="text-decoration:none">К шагу 6 →</a>
</div>
</div>
{{end}}
{{/* ============= ШАГ 4: НРД ============= */}}
{{if eq .Step 6}}
<div class="card">
<h2><span class="dot {{if .Done.NSD}}ok{{else}}err{{end}}"></span>Шаг 6. Интеграционный шлюз НРД</h2>
<p>Адрес web-сервиса ONYX и имя ключевого контейнера НРД.</p>
<div class="help-block">
<strong>Что это?</strong> Интеграционный шлюз (ИШ) НРД — это компонент, через который наши M2M-сообщения отправляются в НРД. {{if devOn}}У НРД есть 4 контура: <em>GUEST</em> (для разработки) и <em>TEST3</em> (предпродакшен), каждый в варианте ГОСТ или RSA.{{else}}У НРД есть тестовый контур (<em>TEST3</em>) и боевой контур, каждый в варианте ГОСТ или RSA.{{end}}<br>
<strong>Где взять?</strong> Дистрибутив ИШ и инструкции — на сайте НРД <a href="https://www.nsd.ru/workflow/system/programs/" target="_blank">nsd.ru/workflow/system/programs/</a>. Доступ к тестовым контурам выдаётся НРД по заявке (см. <code>DOC/instr_podkl_stend_v3.pdf</code>).
</div>
<form method="post" action="/admin/setup/nsd" style="margin-top:12px;display:grid;gap:10px">
<div>
<label>Профиль <span class="tooltip" title="{{if devOn}}GUEST — гостевой контур для разработчиков (gost-gt.nsd.ru), {{end}}TEST3 — тестовый контур НРД (gost-t3.nsd.ru), prod — боевой контур НРД">?</span></label>
<select name="profile" id="nsd-profile" style="padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;width:100%">
<option value="test3-gost" {{if eq .Settings.NSD.Profile "test3-gost"}}selected{{end}}>TEST3 · ГОСТ — тестовый контур НРД</option>
<option value="test3-rsa" {{if eq .Settings.NSD.Profile "test3-rsa"}}selected{{end}}>TEST3 · RSA — тестовый контур НРД</option>
{{if or devOn (eq .Settings.NSD.Profile "guest-gost") (eq .Settings.NSD.Profile "guest-rsa")}} <option value="guest-gost" {{if eq .Settings.NSD.Profile "guest-gost"}}selected{{end}}>GUEST · ГОСТ</option>
<option value="guest-rsa" {{if eq .Settings.NSD.Profile "guest-rsa"}}selected{{end}}>GUEST · RSA</option>{{end}}
<option value="prod" {{if eq .Settings.NSD.Profile "prod"}}selected{{end}}>prod — боевой контур НРД (осторожно)</option>
</select>
</div>
<div>
<label>URL ONYX <span class="tooltip" title="Базовый URL веб-сервиса ONYX. При выборе профиля выше — заполняется автоматически.">?</span></label>
<input type="text" name="igw_base_url" id="nsd-url" value="{{.Settings.NSD.IGWBaseURL}}" placeholder="https://gost-t3.nsd.ru/onyx-ms/OnyxEdoWSService/OnyxEdo" style="padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;width:100%">
</div>
<div>
<label>Ключевой контейнер НРД <span class="tooltip" title="Имя контейнера Валидаты с ключами ЭДО НРД (выдаются УЦ НРД)">?</span></label>
<input type="text" name="key_container" value="{{.Settings.NSD.KeyContainer}}" placeholder="nrd-edo" style="padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;width:100%">
</div>
<hr style="border:none;border-top:1px solid var(--border);margin:4px 0">
<p class="muted" style="margin:0">Депозитарные реквизиты организации в НРД. {{if .Settings.Fancy.Server}}Их можно взять из базы DEPO (Fancy): кнопка «Определить из DEPO» подставит найденное в пустые поля — проверьте и сохраните.{{else}}Если учёт ведётся в Fancy DEPO — сначала подключите базу DEPO на <a href="/admin/wizard?step=3">шаге 3</a>, тогда реквизиты подставятся из DEPO автоматически; иначе введите их из договора с НРД.{{end}}</p>
{{if .Settings.Fancy.Server}}<button type="submit" class="btn" formaction="/admin/setup/fancy/detect" formmethod="post" style="justify-self:start;background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8)">Определить из DEPO</button>{{end}}
{{if .Suggest}}{{if .Suggest.AccountID}}<div class="help-block" style="margin:0"><strong>Найдено в DEPO:</strong> счёт {{.Suggest.AccountID}}, код {{.Suggest.DeponentCode}}, раздел {{.Suggest.SectionID}}{{if .Suggest.OrgName}}; организация {{.Suggest.OrgName}}, ИНН {{.Suggest.OrgINN}}{{end}}.{{range .Suggest.Notes}}<br><span class="muted">⚠ {{.}}</span>{{end}}</div>{{end}}{{end}}
<div><label>Депозитарный код</label><input type="text" name="deponent_code" value="{{if .Settings.NSD.DeponentCode}}{{.Settings.NSD.DeponentCode}}{{else if .Suggest}}{{.Suggest.DeponentCode}}{{end}}" placeholder="MC0000000000" style="padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;width:100%"></div>
<div><label>Депозитарный счёт</label><input type="text" name="account_id" value="{{if .Settings.NSD.AccountID}}{{.Settings.NSD.AccountID}}{{else if .Suggest}}{{.Suggest.AccountID}}{{end}}" placeholder="HL000000000C" style="padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;width:100%"></div>
<div><label>Раздел счёта</label><input type="text" name="section_id" value="{{if .Settings.NSD.SectionID}}{{.Settings.NSD.SectionID}}{{else if .Suggest}}{{.Suggest.SectionID}}{{end}}" placeholder="36MC0000000000F00" style="padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;width:100%"></div>
<button type="submit" class="btn" style="justify-self:start">Сохранить</button>
</form>
{{if .Suggest}}{{if .Suggest.OrgName}}<form method="post" action="/admin/setup/org" style="margin-top:12px;display:grid;gap:10px">
<p class="muted" style="margin:0">Организация (из DEPO — проверьте): код участника, ИНН, наименование.</p>
<input type="hidden" name="return_to" value="/admin/wizard?step=6">
<div><label>Код участника</label><input type="text" name="org_code" value="{{if .Settings.NSD.OrgCode}}{{.Settings.NSD.OrgCode}}{{else}}{{.Suggest.OrgCode}}{{end}}" style="padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;width:100%"></div>
<div><label>ИНН</label><input type="text" name="org_inn" value="{{if .Settings.NSD.OrgINN}}{{.Settings.NSD.OrgINN}}{{else}}{{.Suggest.OrgINN}}{{end}}" style="padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;width:100%"></div>
<div><label>Наименование</label><input type="text" name="org_name" value="{{if .Settings.NSD.OrgName}}{{.Settings.NSD.OrgName}}{{else}}{{.Suggest.OrgName}}{{end}}" style="padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;width:100%"></div>
<button type="submit" class="btn" style="justify-self:start">Сохранить организацию</button>
</form>{{end}}{{end}}
<script>
// Автозаполнение URL по выбранному профилю
document.getElementById('nsd-profile').addEventListener('change', function(e){
var urls = {
'test3-gost': 'https://gost-t3.nsd.ru/onyx-ms/OnyxEdoWSService/OnyxEdo',
'test3-rsa': 'https://rsa-t3.nsd.ru/onyx-ms/OnyxEdoWSService/OnyxEdo',
'guest-gost': 'https://gost-gt.nsd.ru/onyx-ms/OnyxEdoWSService/OnyxEdo',
'guest-rsa': 'https://rsa-gt.nsd.ru/onyx-ms/OnyxEdoWSService/OnyxEdo',
'prod': ''
};
var u = document.getElementById('nsd-url');
if (urls[e.target.value]) u.value = urls[e.target.value];
});
</script>
<div style="margin-top:20px;display:flex;justify-content:space-between">
<a href="/admin/wizard?step=5" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">← К шагу 5</a>
{{if .Done.NSD}}<a href="/admin/wizard?step=7" class="btn" style="text-decoration:none">К шагу 7 →</a>{{else}}<a href="/admin/wizard?step=7&skip=nsd" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">Пропустить ({{if devOn}}mock-режим{{else}}обмен с НРД не настроен{{end}}) →</a>{{end}}
</div>
</div>
{{end}}
{{/* ============= ШАГ 5: Тест-ран (режим разработки) / проверка подключений ============= */}}
{{if eq .Step 7}}
<div class="card">
{{if devOn}}
<h2><span class="dot {{if .Done.TestRun}}ok{{else}}err{{end}}"></span>Шаг 7. Тестовая заявка</h2>
<p>Прогон полного цикла: создание заявки → валидация → подпись → отправка в НРД (или mock) → ожидание Decision → подтверждение.</p>
<div class="help-block">
<strong>Что произойдёт?</strong> Система создаст тестовую M2M-сделку, проведёт её через всю стейт-машину, и покажет результат каждого этапа. Если ИШ НРД не настроен — сработает mock (синтетический Decision через 3 секунды).
</div>
<form method="post" action="/admin/setup/test-run" style="margin-top:12px">
<button type="submit" class="btn" style="background:var(--ok);font-size:15px;padding:10px 20px">▶ Запустить тестовую заявку</button>
</form>
{{if .Settings.LastTest}}
<h3 style="margin-top:18px">Последний прогон: {{.Settings.LastTest.StartedAt.Format "02.01.2006 15:04:05"}}</h3>
<table>
<tr><td class="muted">Заявка</td><td><a href="/admin/claims/{{.Settings.LastTest.ClaimID}}">{{.Settings.LastTest.ClaimID}}</a></td></tr>
<tr><td class="muted">Финальное состояние</td><td>{{ruState .Settings.LastTest.FinalStatus}}</td></tr>
<tr><td class="muted">Результат</td><td>{{if .Settings.LastTest.OK}}<span style="color:var(--ok)">успех</span>{{else}}<span style="color:var(--err)">ошибка</span>{{end}}</td></tr>
{{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>Шаг 7. Проверка подключений</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}}
<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=6" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">← К шагу 6</a>
<a href="/admin/" class="btn" style="text-decoration:none">Перейти к дашборду</a>
</div>
</div>
{{end}}
</div>
{{end}}