Мастер настройки как помощник установки: шаги слева, одна карточка, «Назад»/«Далее» внизу

Слева — шаги этой установки по порядку (номер, галочка у сделанных, текущий выделен),
справа — карточка шага: заголовок, пояснение, сведения «название — значение», зелёная
плашка итога. Подготовка модулей — в том же виде.

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-06 13:20:11 +03:00
parent 8daa00d0a7
commit f81135a715
6 changed files with 159 additions and 112 deletions
@@ -1,33 +1,41 @@
{{define "content"}}
<style>
.prep-head { text-align:center; padding:8px 0 4px; }
.prep-head h1 { font-size:26px; font-weight:720; letter-spacing:-0.02em; margin:0 0 6px; }
.prep-head p { color:var(--muted); margin:0 auto; max-width:620px; }
.prep { max-width:680px; margin:24px auto 0; }
.prep-row { display:grid; grid-template-columns:26px 1fr; gap:4px 10px; padding:12px 0; border-bottom:1px solid var(--border); }
.prep-row:last-child { border-bottom:0; }
.prep-row .ic { font-size:16px; line-height:22px; text-align:center; }
.prep-row b { font-weight:620; }
/* Тот же язык, что у мастера: одна карточка, заголовок и пояснение, строки «название — значение», «Далее» справа внизу. */
.prep { max-width:720px; margin:10px auto 0; padding:28px 32px 22px; border-radius:16px; }
.prep h2 { font-size:21px; font-weight:680; letter-spacing:-0.015em; margin:0 0 6px; }
.prep-lead { color:var(--muted); font-size:14.5px; margin:0 0 18px; }
.prep-kv { display:grid; grid-template-columns:minmax(120px,170px) minmax(0,1fr); gap:10px 20px; font-size:14.5px; margin:0 0 14px; }
.prep-kv dt { color:var(--muted); } .prep-kv dd { margin:0; }
.prep-ok { display:flex; gap:10px; align-items:center; padding:12px 14px; border-radius:10px; font-size:14px; margin:0 0 16px; background:var(--ok-weak); border:1px solid color-mix(in srgb, var(--ok) 30%, transparent); }
.prep-row { display:grid; grid-template-columns:22px 1fr; gap:2px 12px; padding:12px 0; border-top:1px solid var(--border); font-size:14.5px; }
.prep-row .ic { display:grid; place-items:center; height:22px; }
.prep-row b { font-weight:600; }
.prep-row .muted { font-size:13px; }
.ic-ok { position:relative; width:18px; height:18px; border-radius:50%; background:var(--ok); }
.ic-ok::after { content:""; position:absolute; left:50%; top:45%; width:4px; height:8px; border:solid #fff; border-width:0 1.8px 1.8px 0; transform:translate(-50%,-50%) rotate(45deg); }
.ic-wait { width:16px; height:16px; border-radius:50%; border:1.5px solid var(--border-strong); }
.prep-bar { height:6px; border-radius:3px; background:var(--card-2); overflow:hidden; margin:6px 0 0; }
.prep-bar i { display:block; height:100%; width:100%; background:var(--accent); transform-origin:left center; transform:scaleX(0); transition:transform .4s ease; }
.prep-foot { display:flex; align-items:center; justify-content:space-between; gap:12px; margin-top:16px; flex-wrap:wrap; }
.prep-foot { display:flex; align-items:center; justify-content:space-between; gap:12px; margin-top:18px; padding-top:18px; border-top:1px solid var(--border); flex-wrap:wrap; }
.prep-foot .btn { border-radius:999px; padding:10px 22px; font-size:14.5px; font-weight:600; text-decoration:none; }
@keyframes prepspin { to { transform:rotate(360deg); } }
.spin { display:inline-block; width:14px; height:14px; border:2px solid var(--accent-weak); border-top-color:var(--accent); border-radius:50%; animation:prepspin .8s linear infinite; vertical-align:-2px; }
@media (prefers-reduced-motion: reduce) { .spin { animation:none; } .prep-bar i { transition:none; } }
@media (max-width:600px) { .prep { padding:20px 18px 16px; } .prep-kv { grid-template-columns:minmax(0,1fr); gap:2px 0; } .prep-kv dd { margin-bottom:8px; } }
</style>
<div class="prep-head">
<h1>Подготовка модулей</h1>
<p>Ключ принят: <b>{{.Tenant}}</b>. bj проверяет обновления, ставит и запускает модули, которые входят в вашу лицензию, — затем откроется мастер настройки.</p>
</div>
<div class="card prep">
{{if .Flash}}<p style="margin:0 0 6px;color:var(--ok)">✓ {{.Flash}}</p>{{end}}
<p class="muted" style="margin:0 0 4px;font-size:13px">Лицензия активна · {{.Until}}</p>
<div id="prep-rows"><p class="muted" style="margin:12px 0">Проверяю обновления и состав лицензии…</p></div>
<h2>Подготовка модулей</h2>
<p class="prep-lead">bj проверяет обновления, ставит и запускает модули вашей лицензии — затем откроется мастер настройки.</p>
{{if .Flash}}<div class="prep-ok"><i class="ic-ok"></i><span>{{.Flash}}</span></div>{{end}}
<dl class="prep-kv">
<dt>Организация</dt><dd>{{.Tenant}}</dd>
<dt>Лицензия</dt><dd>активна · {{.Until}}</dd>
</dl>
<div id="prep-rows"><p class="muted" style="margin:12px 0 0">Проверяю обновления и состав лицензии…</p></div>
<div id="prep-prog" style="display:none;margin-top:8px"><span class="muted" id="prep-stage" style="font-size:13px"></span><div class="prep-bar"><i id="prep-fill"></i></div></div>
<div class="prep-foot">
<span id="prep-sum" style="font-weight:600"></span>
<a class="btn" id="prep-next" href="{{.Next}}" style="text-decoration:none;display:none">Перейти к настройке →</a>
<a class="btn" id="prep-next" href="{{.Next}}" style="display:none">Перейти к настройке →</a>
</div>
</div>
<script>
@@ -36,7 +44,7 @@
var prog=document.getElementById('prep-prog'), stage=document.getElementById('prep-stage'), fill=document.getElementById('prep-fill');
var next={{.Next}}, started=false, redirect=null, boot=null, lost=0;
function esc(s){return String(s==null?'':s).replace(/[&<>"]/g,function(c){return{'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]})}
var ic={ok:'<span style="color:var(--ok)">✓</span>',run:'<span class="spin" aria-label="идёт"></span>',wait:'<span class="muted">○</span>',warn:'<span style="color:var(--warn)">⚠</span>',err:'<span style="color:var(--err)">✗</span>',info:'<span class="muted">ⓘ</span>'};
var ic={ok:'<i class="ic-ok" aria-label="готово"></i>',run:'<span class="spin" aria-label="идёт"></span>',wait:'<i class="ic-wait" aria-label="ждёт"></i>',warn:'<span style="color:var(--warn)">⚠</span>',err:'<span style="color:var(--err)">✗</span>',info:'<span class="muted">ⓘ</span>'};
function mb(n){return (n/1048576).toFixed(1).replace('.',',')+' МБ'}
function draw(d){
rows.innerHTML=d.items.map(function(it){return '<div class="prep-row"><div class="ic">'+ic[it.state]+'</div><div><b>'+esc(it.title)+'</b>'+(it.detail?'<br><span class="muted">'+esc(it.detail)+'</span>':'')+'</div></div>'}).join('');
@@ -1,54 +1,85 @@
{{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; min-width:0; }
.wiz-sub { display:flex; justify-content:center; gap:6px; flex-wrap:wrap; margin:-12px auto 18px; max-width:680px; }
.wiz-sub a { font-size:12.5px; padding:4px 12px; border-radius:14px; background:var(--card-2); color:var(--muted); text-decoration:none; border:1px solid var(--border); }
.wiz-sub a.done { color:var(--ok); }
.wiz-sub a.cur { background:var(--accent); color:#fff; border-color:var(--accent); }
.wiz-nav { margin-top:20px; display:flex; justify-content:space-between; gap:8px; flex-wrap:wrap; }
/* Мастер — как помощник установки: слева шаги по порядку, справа одна карточка, внизу «Назад» / «Далее». */
.wiz { display:grid; grid-template-columns:262px minmax(0,1fr); gap:32px; max-width:1080px; margin:10px auto 0; align-items:start; }
.wiz-rail { position:sticky; top:80px; min-width:0; }
.wiz-steps { list-style:none; margin:0; padding:0; display:grid; gap:2px; }
.wiz-st { display:flex; align-items:center; gap:12px; padding:9px 12px; border-radius:10px; color:var(--muted); font-size:14.5px; line-height:1.3; text-decoration:none; transition:background .15s var(--ease); }
.wiz-st:hover { background:var(--card-2); text-decoration:none; }
.wiz-st.done { color:var(--text-2); }
.wiz-st.cur { background:var(--accent-weak); color:var(--accent); font-weight:600; }
.wiz-num { flex:none; position:relative; width:26px; height:26px; border-radius:50%; border:1.5px solid var(--border-strong); display:grid; place-items:center; font-size:12.5px; font-weight:600; font-variant-numeric:tabular-nums; }
.wiz-st.cur .wiz-num { border-color:var(--accent); }
.wiz-st.done .wiz-num { background:var(--ok); border-color:var(--ok); }
.wiz-st.done .wiz-num::after, .wiz-ic::after { content:""; position:absolute; left:50%; top:45%; width:5px; height:10px; border:solid #fff; border-width:0 2px 2px 0; transform:translate(-50%,-50%) rotate(45deg); }
.wiz-rail-foot { margin:16px 12px 0; padding-top:14px; border-top:1px solid var(--border); font-size:12.5px; color:var(--muted); line-height:1.45; }
.wiz-rail-foot a { display:inline-block; margin-top:6px; }
.wiz-card { min-width:0; }
.wiz-card * { min-width:0; }
.wiz-row { display:flex; align-items:center; gap:10px 14px; flex-wrap:wrap; }
.wiz-row-l { font-weight:600; font-size:15px; min-width:130px; margin:0; }
.wiz-card > .card { padding:28px 32px 22px; border-radius:16px; }
.wiz-card > .card > h2:first-child { font-size:21px; font-weight:680; letter-spacing:-0.015em; margin:0 0 6px; }
.wiz-card > .card > h2:first-child .dot { display:none; }
.wiz-card > .card > h2:first-child + p { color:var(--muted); font-size:14.5px; margin:0 0 18px; }
.wiz-card input:not([type=radio]):not([type=checkbox]):not([type=hidden]):not([type=file]), .wiz-card select, .wiz-card textarea { background:var(--card-2) !important; border:1px solid var(--border) !important; border-radius:10px !important; padding:10px 13px !important; font-size:14.5px; color:var(--text); }
.wiz-card input:focus, .wiz-card select:focus, .wiz-card textarea:focus { background:var(--card) !important; border-color:var(--accent) !important; outline:3px solid var(--accent-weak); }
.wiz-card .btn, .wiz-card button { border-radius:10px; }
.wiz-card input[type=radio], .wiz-card input[type=checkbox] { flex:none; width:auto; margin-top:3px; }
/* вложенные блоки общих разделов (организация, почта, копии…) внутри шага — без своей рамки, через разделитель */
.wiz-card > .card .card { border:0; border-top:1px solid var(--border); border-radius:0; box-shadow:none; background:transparent; padding:18px 0 0; margin:18px 0 0; }
.wiz-card > .card > h2:first-child + p + .card, .wiz-card > .card > .wiz-ok + .card { border-top:0; padding-top:0; margin-top:0; }
.wiz-card > .card .card form { max-width:none !important; }
.wiz-card .dot:not(.ok):not(.warn):not(.err) { display:none; }
.wiz-flash { background:var(--accent-weak); border-radius:12px; padding:12px 16px; margin:0 0 14px; font-size:14px; }
/* сведения «название — значение» и итог шага (зелёная плашка), как в помощнике установки */
.wiz-kv { display:grid; grid-template-columns:minmax(120px,190px) minmax(0,1fr); gap:10px 20px; margin:6px 0 0; font-size:14.5px; }
.wiz-kv dt { color:var(--muted); }
.wiz-kv dd { margin:0; overflow-wrap:anywhere; }
.wiz-ok, .wiz-info { display:flex; gap:10px; align-items:flex-start; padding:12px 14px; border-radius:10px; font-size:14px; margin:18px 0 0; }
.wiz-ok { background:var(--ok-weak); border:1px solid color-mix(in srgb, var(--ok) 30%, transparent); }
.wiz-info { background:var(--accent-weak); border:1px solid color-mix(in srgb, var(--accent) 22%, transparent); }
.wiz-ic { flex:none; position:relative; width:18px; height:18px; border-radius:50%; background:var(--ok); margin-top:1px; }
.wiz-ic::after { width:4px; height:8px; border-width:0 1.8px 1.8px 0; }
.wiz-nav { margin-top:26px; padding-top:18px; border-top:1px solid var(--border); display:flex; justify-content:space-between; align-items:center; gap:8px; flex-wrap:wrap; }
.wiz-back { color:var(--text-2); font-size:14.5px; font-weight:500; padding:8px 4px; }
.wiz-back:hover { color:var(--text); text-decoration:none; }
.wiz-card .wiz-next, .wiz-card .wiz-skip { border-radius:999px; padding:10px 22px; font-size:14.5px; font-weight:600; text-decoration:none; }
.wiz-card .wiz-skip { background:transparent; color:var(--text); border-color:var(--border-strong); }
.wiz-row { display:flex; align-items:center; gap:10px 20px; flex-wrap:wrap; }
.wiz-row-l { color:var(--muted); font-weight:400; font-size:14.5px; min-width:170px; margin:0; }
.wiz-opt { display:flex; gap:8px; align-items:flex-start; font-weight:400; margin:0 0 6px; }
.wiz-opt .muted { font-size:13px; }
.wiz-card input[type=radio], .wiz-card input[type=checkbox] { flex:none; width:auto; margin-top:3px; }
.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; }
.help-block { background:var(--accent-weak); padding:10px 14px; margin:10px 0; font-size:13px; border-radius:10px; }
.help-block strong { color:var(--accent); }
/* узкий экран: шаги — строкой с прокруткой над карточкой */
@media (max-width:820px) {
.wiz { grid-template-columns:minmax(0,1fr); gap:14px; }
.wiz-rail { position:static; }
.wiz-steps { display:flex; overflow-x:auto; gap:4px; padding-bottom:4px; scrollbar-width:none; }
.wiz-st { white-space:nowrap; padding:7px 10px; font-size:13.5px; }
.wiz-rail-foot { display:none; }
.wiz-card > .card { padding:20px 18px 16px; }
.wiz-kv { grid-template-columns:minmax(0,1fr); gap:2px 0; }
.wiz-kv dd { margin-bottom:8px; }
}
@media (max-width:600px) { .wiz-card [style*="grid-template-columns"] { grid-template-columns:minmax(0,1fr) !important; } }
</style>
<div class="wiz-head">
<h1>Настройка Bridge&amp;Join</h1>
<p>Всё сохраняется сразу — можно прерваться и продолжить позже.</p>
</div>
<div class="wizard-progress" style="max-width:720px">
{{range .Chapters}}<a href="/admin/wizard?step={{.First}}" title="Глава {{.N}}: {{.Title}}" class="wstep {{if .Done}}done{{end}} {{if .Current}}current{{end}}"><span class="bub">{{if .Done}}✓{{else}}{{.N}}{{end}}</span><span class="lbl">{{.Title}}</span></a>
{{end}}
</div>
{{range .Chapters}}{{if and .Current (gt (len .Steps) 1)}}<div class="wiz-sub">{{range .Steps}}<a href="/admin/wizard?step={{.N}}" class="{{if .Current}}cur{{end}}{{if .Done}} done{{end}}">{{if .Done}}✓ {{end}}{{.Title}}</a>{{end}}</div>{{end}}{{end}}
<p style="text-align:center;margin:-14px 0 18px;font-size:13px"><a href="/admin/" title="Вернуться к мастеру — «Настройка → Мастер настройки»">Настроить позже и перейти в систему →</a></p>
{{if .Flash}}<div class="card wiz-card" style="background:var(--accent-weak)"><p style="margin:0">{{.Flash}}</p></div>{{end}}
<div class="wiz">
<aside class="wiz-rail" aria-label="Шаги настройки">
<ol class="wiz-steps">
{{range .Chapters}}{{range .Steps}}<li><a href="/admin/wizard?step={{.N}}" class="wiz-st{{if .Done}} done{{end}}{{if .Current}} cur{{end}}"{{if .Current}} aria-current="step"{{end}}><span class="wiz-num">{{if not .Done}}{{.Num}}{{end}}</span><span>{{.Title}}</span></a></li>{{end}}{{end}}
</ol>
<div class="wiz-rail-foot">Всё сохраняется сразу — можно прерваться и продолжить позже.<br><a href="/admin/">Настроить позже →</a></div>
</aside>
<div class="wiz-card">
{{if .Flash}}<div class="wiz-flash">{{.Flash}}</div>{{end}}
@@ -57,31 +88,35 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
{{/* восстановление из резервной копии — только на первой странице (/first-run), в мастере его нет */}}
<div class="card">
<h2><span class="dot {{if .Done.License}}ok{{else}}err{{end}}"></span>Лицензия</h2>
{{if .Done.License}}<p style="margin:4px 0 2px;font-size:16px">✓ Лицензия активна — <strong>{{.Lic.Tenant}}</strong></p>
<p class="muted" style="margin:0;font-size:13.5px">{{if eq .Lic.Plan "enterprise"}}Все модули{{else if .Lic.Features}}Входит: {{range $i, $f := .Lic.Features}}{{if $i}}, {{end}}{{modTitle $f}}{{end}}{{else}}Только ядро: обмен с НРД и переводы M2M{{end}} · {{.Lic.Message}}</p>
{{else}}<p style="margin:4px 0 0">Введите ключ продукта — bj сам скачает и поставит модули вашей лицензии.</p>
<p class="muted" style="margin:4px 0 0;font-size:13px">Ключ вида BJ-XXXX-XXXX-XXXX — в портале клиента <a href="https://lic.zetit.ru/portal/" target="_blank">lic.zetit.ru/portal</a> или в письме от ZETIT.</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>
{{if .Done.License}}<p>Ключ продукта открывает модули bj и обновления.</p>
<dl class="wiz-kv">
<dt>Организация</dt><dd>{{.Lic.Tenant}}</dd>
<dt>Модули</dt><dd>{{if eq .Lic.Plan "enterprise"}}все модули{{else if .Lic.Features}}{{range $i, $f := .Lic.Features}}{{if $i}}, {{end}}{{modTitle $f}}{{end}}{{else}}только ядро: обмен с НРД и переводы M2M{{end}}</dd>
<dt>Срок</dt><dd>{{.Lic.Message}}</dd>
</dl>
{{else}}<p>Введите ключ продукта — bj сам скачает и поставит модули вашей лицензии.</p>
<p class="muted" style="margin:-10px 0 0;font-size:13px">Ключ вида BJ-XXXX-XXXX-XXXX — в портале клиента <a href="https://lic.zetit.ru/portal/" target="_blank">lic.zetit.ru/portal</a> или в письме от ZETIT.</p>{{end}}
{{if and .Update .Update.HasUpdate}}
<div class="wiz-info" style="display:block">
<p style="margin:0 0 6px;font-weight:600">Есть новая версия bj — {{.Update.Available}}</p>
<p class="muted" style="margin:0 0 10px;font-size:13px">С исправлениями ядра, установщика и служб. Поставьте её до настройки: bj перезапустится примерно на 10 секунд.<br>{{.Update.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;margin-top:6px">✓ Установлена последняя версия bj</p>{{end}}{{end}}
{{else if .Done.License}}<div class="wiz-ok"><i class="wiz-ic"></i><span>Лицензия активна{{if .Update}}, установлена последняя версия bj{{end}}.</span></div>{{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">
<input type="text" name="code" placeholder="BJ-XXXX-XXXX-XXXX" autocomplete="off" style="text-transform:uppercase;max-width:340px">
<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:14px;display:flex;gap:10px;flex-wrap:wrap">
<input type="hidden" name="return_to" value="/admin/wizard?step=1">
<input type="text" name="code" placeholder="BJ-XXXX-XXXX-XXXX" aria-label="Ключ продукта" autocomplete="off" autofocus required style="flex:1 1 260px;padding:10px 12px;font-size:16px;letter-spacing:.04em;text-transform:uppercase;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:8px">
<input type="text" name="code" placeholder="BJ-XXXX-XXXX-XXXX" aria-label="Ключ продукта" autocomplete="off" autofocus required style="flex:1 1 260px;font-size:16px;letter-spacing:.04em;text-transform:uppercase">
<button type="submit" class="btn">Активировать</button>
</form>{{end}}
{{template "wizNav" .}}
@@ -99,20 +134,20 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
{{if eq $sub "setup"}}
<p style="margin:2px 0 0">bj сверяет каждый перевод с учётом DEPO и сам создаёт в нём поручения. Нужен только адрес сервера — базу DEPO bj найдёт сам.</p>
{{$mode := .Settings.Fancy.DepoJobMode}}
<form method="post" action="/admin/setup/wizard-depo/setup" id="wiz-depo" style="display:grid;gap:12px;max-width:680px;margin-top:10px">
<form method="post" action="/admin/setup/wizard-depo/setup" id="wiz-depo" style="display:grid;gap:12px">
<div style="display:grid;grid-template-columns:2.2fr .8fr 1.1fr;gap:8px">
<label style="margin:0">Сервер DEPO<input type="text" name="server" value="{{.Settings.Fancy.Server}}" placeholder="адрес или имя\экземпляр" required autofocus style="padding:8px;width:100%;box-sizing:border-box;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:8px;margin-top:4px"></label>
<label style="margin:0">Порт<input type="text" name="port" value="{{if .Settings.Fancy.Port}}{{.Settings.Fancy.Port}}{{end}}" placeholder="1433" style="padding:8px;width:100%;box-sizing:border-box;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:8px;margin-top:4px"></label>
<label style="margin:0">СУБД<select name="db_type" onchange="wizDepo()" style="padding:8px;width:100%;box-sizing:border-box;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:8px;margin-top:4px"><option value="mssql" {{if ne .Settings.Fancy.DBType "postgres"}}selected{{end}}>SQL Server</option><option value="postgres" {{if eq .Settings.Fancy.DBType "postgres"}}selected{{end}}>PostgreSQL</option></select></label>
<label style="margin:0">Сервер DEPO<input type="text" name="server" value="{{.Settings.Fancy.Server}}" placeholder="адрес или имя\экземпляр" required autofocus style="width:100%;margin-top:4px"></label>
<label style="margin:0">Порт<input type="text" name="port" value="{{if .Settings.Fancy.Port}}{{.Settings.Fancy.Port}}{{end}}" placeholder="1433" style="width:100%;margin-top:4px"></label>
<label style="margin:0">СУБД<select name="db_type" onchange="wizDepo()" style="width:100%;margin-top:4px"><option value="mssql" {{if ne .Settings.Fancy.DBType "postgres"}}selected{{end}}>SQL Server</option><option value="postgres" {{if eq .Settings.Fancy.DBType "postgres"}}selected{{end}}>PostgreSQL</option></select></label>
</div>
{{if .DepoCands}}<label style="margin:0">На сервере несколько баз DEPO — выберите нужную<select name="depo_database" style="padding:8px;width:100%;box-sizing:border-box;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;margin-top:4px">{{range .DepoCands}}<option>{{.}}</option>{{end}}</select></label>{{end}}
<div id="wiz-depo-pgdb" style="display:none"><label style="margin:0">База DEPO <span class="muted" style="font-weight:400">(для PostgreSQL указывается вручную)</span><input type="text" name="depo_database" value="{{.Settings.Fancy.DepoDatabase}}" pattern="[A-Za-z][A-Za-z0-9_]*" style="padding:8px;width:100%;box-sizing:border-box;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;margin-top:4px"></label></div>
{{if .DepoCands}}<label style="margin:0">На сервере несколько баз DEPO — выберите нужную<select name="depo_database" style="width:100%;margin-top:4px">{{range .DepoCands}}<option>{{.}}</option>{{end}}</select></label>{{end}}
<div id="wiz-depo-pgdb" style="display:none"><label style="margin:0">База DEPO <span class="muted" style="font-weight:400">(для PostgreSQL указывается вручную)</span><input type="text" name="depo_database" value="{{.Settings.Fancy.DepoDatabase}}" pattern="[A-Za-z][A-Za-z0-9_]*" style="width:100%;margin-top:4px"></label></div>
<div style="border-top:1px solid var(--border);padding-top:10px"><div style="font-weight:600;margin-bottom:6px">Как подготовить базу</div>
<label class="wiz-opt"><input type="radio" name="mode" value="auto" checked onchange="wizDepo()"> <span><b>bj сделает всё сам</b> <span class="muted">— нужен логин администратора SQL на один раз, bj его не сохраняет</span></span></label>
<div id="wiz-depo-auto" style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin:0 0 8px 26px">
<input type="text" name="admin_user" autocomplete="off" placeholder="логин администратора, например sa" style="padding:8px;width:100%;box-sizing:border-box;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px">
<input type="password" name="admin_password" autocomplete="new-password" placeholder="пароль" style="padding:8px;width:100%;box-sizing:border-box;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px">
<input type="text" name="admin_user" autocomplete="off" placeholder="логин администратора, например sa" style="width:100%">
<input type="password" name="admin_password" autocomplete="new-password" placeholder="пароль" style="width:100%">
</div>
<label class="wiz-opt"><input type="radio" name="mode" value="script" onchange="wizDepo()"> <span><b>Отдать скрипт администратору</b> <span class="muted">— если логин администратора не дают: один файл сделает то же самое</span></span></label>
<div id="wiz-depo-script" style="display:none;margin:0 0 8px 26px">
@@ -122,8 +157,8 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
</div>
<label class="wiz-opt"><input type="radio" name="mode" value="existing" onchange="wizDepo()"> <span><b>Логин для bj уже есть</b> <span class="muted">— база подготовлена раньше, только проверить</span></span></label>
<div id="wiz-depo-existing" style="display:none;grid-template-columns:1fr 1fr;gap:8px;margin:0 0 8px 26px">
<input type="text" name="user" value="{{.Settings.Fancy.User}}" autocomplete="off" placeholder="логин bj" style="padding:8px;width:100%;box-sizing:border-box;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px">
<input type="password" name="password" autocomplete="new-password" placeholder="{{if .Settings.Fancy.Password}}пароль (пусто — прежний){{else}}пароль{{end}}" style="padding:8px;width:100%;box-sizing:border-box;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px">
<input type="text" name="user" value="{{.Settings.Fancy.User}}" autocomplete="off" placeholder="логин bj" style="width:100%">
<input type="password" name="password" autocomplete="new-password" placeholder="{{if .Settings.Fancy.Password}}пароль (пусто — прежний){{else}}пароль{{end}}" style="width:100%">
</div>
</div>
<div id="wiz-depo-job" style="border-top:1px solid var(--border);padding-top:10px"><div style="font-weight:600;margin-bottom:6px">Кто запускает обработку обмена</div>
@@ -131,7 +166,7 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
<label class="wiz-opt"><input type="radio" name="depo_job" value="bj" {{if eq $mode "bj"}}checked{{end}}> <span><b>Сервер bj</b> <span class="muted">— если Agent нет (например, SQL Server Express)</span></span></label>
</div>
{{if not .DepoCands}}<details style="font-size:13px"><summary style="cursor:pointer;color:var(--accent)">Указать базу DEPO вручную (обычно не нужно)</summary>
<input type="text" name="depo_database" id="wiz-depo-dbman" value="" placeholder="имя базы DEPO на сервере" pattern="[A-Za-z][A-Za-z0-9_]*" style="padding:8px;width:100%;box-sizing:border-box;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;margin-top:6px;max-width:320px"></details>{{end}}
<input type="text" name="depo_database" id="wiz-depo-dbman" value="" placeholder="имя базы DEPO на сервере" pattern="[A-Za-z][A-Za-z0-9_]*" style="width:100%;margin-top:6px;max-width:320px"></details>{{end}}
<div><button type="submit" class="btn" id="wiz-depo-btn">Подготовить и проверить</button></div>
</form>
<script>function wizDepo(){var f=document.getElementById('wiz-depo'),pg=f.db_type.value==='postgres',m=f.mode.value;
@@ -166,7 +201,7 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
<div class="card">
<h2><span class="dot {{if .Done.Postgres}}ok{{else}}err{{end}}"></span>База данных bj (PostgreSQL)</h2>
<p>База самого bj: журнал заявок и поручений, история обмена с НРД и DEPO, пользователи.</p>
{{if .Done.Postgres}}<p>✓ База готова{{if .PGSummary}}: {{.PGSummary}}{{end}}. Её создал установщик — ничего делать не нужно, переходите к следующему шагу.</p>{{end}}
{{if .Done.Postgres}}<div class="wiz-ok"><i class="wiz-ic"></i><span>База готова{{if .PGSummary}}: {{.PGSummary}}{{end}}. Её создал установщик — ничего делать не нужно.</span></div>{{end}}
{{if not .Done.Postgres}}<div class="help-block">
<strong>Обычно базу создаёт установщик</strong> — если вы ставили bj с образа или командой установки, этот шаг уже выполнен. Если база bj на отдельном сервере PostgreSQL — введите строку подключения ниже{{if devOn}}, или поднимите локальную базу автоматически (режим разработки){{end}}.
@@ -187,7 +222,7 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
<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">
<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;margin-top:6px">
<p class="muted" style="margin-top:8px">При сохранении выполняется проверка подключения (Ping). Если БД недоступна — будет ошибка.</p>
<button type="submit" class="btn" style="margin-top:8px">Сохранить и проверить</button>
</form>
@@ -202,8 +237,8 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
{{if eq .Step 4}}
<div class="card">
<h2><span class="dot {{if .Done.Org}}ok{{else}}err{{end}}"></span>Наша организация</h2>
<p style="margin:2px 0 0">От имени вашей организации bj отправляет и принимает переводы. {{if .DepoLicensed}}Реквизиты bj берёт из DEPO — проверьте и сохраните.{{else}}Найдите себя в справочнике участников НРД.{{end}}</p>
{{if .Suggest}}{{if .Suggest.OrgName}}{{if not .Done.Org}}<p style="margin:8px 0;padding:8px 12px;border-radius:8px;background:var(--accent-weak)">✓ <strong>Определено из DEPO</strong>: {{.Suggest.OrgName}}, ИНН {{.Suggest.OrgINN}}, код {{.Suggest.OrgCode}}{{if .Suggest.AccountID}}, счёт НРД {{.Suggest.AccountID}}{{end}}. Поля ниже заполнены — проверьте и нажмите «Сохранить».</p>{{end}}{{end}}{{end}}
<p>От имени вашей организации bj отправляет и принимает переводы. {{if .DepoLicensed}}Реквизиты bj берёт из DEPO — проверьте и сохраните.{{else}}Найдите себя в справочнике участников НРД.{{end}}</p>
{{if .Suggest}}{{if .Suggest.OrgName}}{{if not .Done.Org}}<div class="wiz-ok" style="margin:0 0 14px"><i class="wiz-ic"></i><span><strong>Определено из DEPO</strong>: {{.Suggest.OrgName}}, ИНН {{.Suggest.OrgINN}}, код {{.Suggest.OrgCode}}{{if .Suggest.AccountID}}, счёт НРД {{.Suggest.AccountID}}{{end}}. Поля ниже заполнены — проверьте и нажмите «Сохранить».</span></div>{{end}}{{end}}{{end}}
{{with .Fancy}}{{template "orgSection" .}}{{end}}
<div class="card" id="nsd-req" style="margin-top:12px">
<h2><span class="dot {{if and .Settings.NSD.DeponentCode .Settings.NSD.AccountID .Settings.NSD.SectionID}}ok{{else}}err{{end}}"></span>Депозитарные реквизиты в НРД</h2>
@@ -212,9 +247,9 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
<form method="post" action="/admin/setup/nsd-requisites" style="display:grid;gap:10px;max-width:680px">
<input type="hidden" name="return_to" value="/admin/wizard?step=4">
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px">
<label style="margin:0">Депозитарный код<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;width:100%;box-sizing:border-box;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;margin-top:4px"></label>
<label style="margin:0">Депозитарный счёт<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;width:100%;box-sizing:border-box;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;margin-top:4px"></label>
<label style="margin:0">Раздел счёта<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;width:100%;box-sizing:border-box;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;margin-top:4px"></label>
<label style="margin:0">Депозитарный код<input type="text" name="deponent_code" value="{{if .Settings.NSD.DeponentCode}}{{.Settings.NSD.DeponentCode}}{{else if .Suggest}}{{.Suggest.DeponentCode}}{{end}}" placeholder="MC0000000000" style="width:100%;margin-top:4px"></label>
<label style="margin:0">Депозитарный счёт<input type="text" name="account_id" value="{{if .Settings.NSD.AccountID}}{{.Settings.NSD.AccountID}}{{else if .Suggest}}{{.Suggest.AccountID}}{{end}}" placeholder="HL000000000C" style="width:100%;margin-top:4px"></label>
<label style="margin:0">Раздел счёта<input type="text" name="section_id" value="{{if .Settings.NSD.SectionID}}{{.Settings.NSD.SectionID}}{{else if .Suggest}}{{.Suggest.SectionID}}{{end}}" placeholder="36MC0000000000F00" style="width:100%;margin-top:4px"></label>
</div>
<div style="display:flex;gap:8px;flex-wrap:wrap"><button type="submit" class="btn">Сохранить</button>{{if .Settings.Fancy.Server}}<button type="submit" class="btn" formaction="/admin/setup/fancy/detect" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8)">Определить из DEPO</button>{{end}}</div>
</form>
@@ -227,7 +262,7 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
{{if eq .Step 5}}
<div class="card">
<h2><span class="dot {{if .Done.Quik}}ok{{else}}err{{end}}"></span>QUIK</h2>
<p style="margin:2px 0 0">Адрес и логин модуля неторговых поручений — у вашего администратора QUIK. Если модуля ещё нет, его подключает ARQA Technologies — письмо для них готово ниже.</p>
<p>Адрес и логин модуля неторговых поручений — у вашего администратора QUIK. Если модуля ещё нет, его подключает ARQA Technologies — письмо для них готово ниже.</p>
{{if .DepoLicensed}}{{template "letterCard" (letter "arqa")}}{{end}}
{{if not .DepoLicensed}}<p class="muted">Модуль неторговых поручений не входит в текущую лицензию — шаг пропускается.</p>
{{else}}{{with .Fancy}}{{template "quikSection" .}}{{end}}
@@ -245,7 +280,8 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
<h2><span class="dot {{if .Done.Crypto}}ok{{else}}err{{end}}"></span>СКЗИ «Валидата Клиент L»</h2>
<p>СКЗИ «Валидата Клиент L» проверяет подписи НРД на квитанциях и сообщениях и нужно для работы с ключами и сертификатами (раздел «Ключ»).</p>
{{if .ValidataVer}}
<p>✓ Валидата установлена вместе с bj: версия {{.ValidataVer}}, каталог <code>/opt/Validata/VDCSP</code>. Ничего настраивать на этом шаге не нужно.</p>
<dl class="wiz-kv"><dt>Версия</dt><dd>{{.ValidataVer}}</dd><dt>Каталог</dt><dd><code>/opt/Validata/VDCSP</code></dd></dl>
<div class="wiz-ok"><i class="wiz-ic"></i><span>Валидата установлена вместе с bj — ничего настраивать не нужно.</span></div>
<p class="muted" style="font-size:13px">bj работает с Валидатой через свою службу криптографии <code>bj-crypto</code>{{if eq .CryptoSvc "active"}} — она запущена{{else}} — сейчас она не запущена: служба ставится обновлением с сервера лицензий (раздел «Лицензия», кнопка «Обновить сейчас»), а работает с ключом, установленным в разделе «Ключ»{{end}}.</p>
{{else}}
<p style="color:var(--err)">Валидата на этом сервере не найдена.</p>
@@ -266,7 +302,7 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
{{if eq .Step 7}}
<div class="card">
<h2><span class="dot {{if .Done.NSD}}ok{{else}}err{{end}}"></span>Шлюз НРД</h2>
<p style="margin:2px 0 0">Через интеграционный шлюз НРД (ИШ) на этом сервере bj отправляет переводы и получает ответы. bj ставит и настраивает шлюз сам; доступ к тестовому и боевому контуру НРД открывает по заявке — письма готовы.</p>
<p>Через интеграционный шлюз НРД (ИШ) на этом сервере bj отправляет переводы и получает ответы. bj ставит и настраивает шлюз сам; доступ к тестовому и боевому контуру НРД открывает по заявке — письма готовы.</p>
{{template "igateInstall" .}}
<details style="margin-top:4px"><summary style="cursor:pointer;color:var(--accent);font-size:13px">Письма в НРД о доступе к контурам</summary>
{{template "letterCard" (letter "nsd-test")}}
@@ -279,7 +315,7 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
<input type="hidden" name="keep_requisites" value="1">
<input type="hidden" name="profile" value="{{if .Settings.NSD.Profile}}{{.Settings.NSD.Profile}}{{else}}test3-gost{{end}}">
<label style="margin:0">Адрес шлюза на этом сервере
<input type="text" name="igw_base_url" value="{{if .Settings.NSD.IGWBaseURL}}{{.Settings.NSD.IGWBaseURL}}{{else}}http://localhost:8090{{end}}" placeholder="http://localhost:8090" style="padding:8px;width:100%;box-sizing:border-box;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;margin-top:4px"></label>
<input type="text" name="igw_base_url" value="{{if .Settings.NSD.IGWBaseURL}}{{.Settings.NSD.IGWBaseURL}}{{else}}http://localhost:8090{{end}}" placeholder="http://localhost:8090" style="width:100%;margin-top:4px"></label>
<p id="igw-onyx" style="display:none;color:var(--err);font-size:13px;margin:0">Это адрес веб-сервиса НРД (ONYX), а не шлюза: bj работает через ИШ на этом сервере. Укажите адрес шлюза — обычно <a href="#" id="igw-def">http://localhost:8090</a>.</p>
<script>(function(){var u=document.querySelector('[name=igw_base_url]'),w=document.getElementById('igw-onyx');
function chk(){w.style.display=/onyx|nsd\.ru/i.test(u.value)?'block':'none'} u.addEventListener('input',chk); chk();
@@ -296,8 +332,8 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
{{if eq .Step 8}}
<div class="card">
<h2><span class="dot {{if .Done.Certs}}ok{{else}}err{{end}}"></span>Ключ и сертификаты</h2>
<p style="margin:2px 0 0">Ключ электронной подписи организации для обмена с НРД. Его выдаёт Удостоверяющий центр Московской биржи: вам передадут архив ключа и пароль.</p>
{{if not .Done.NSD}}<p style="padding:8px 12px;border-radius:8px;background:var(--accent-weak);margin:10px 0 0">Ключ ставится в шлюз НРД — сначала установите <a href="/admin/wizard?step=7">шлюз</a>.</p>{{end}}
<p>Ключ электронной подписи организации для обмена с НРД. Его выдаёт Удостоверяющий центр Московской биржи: вам передадут архив ключа и пароль.</p>
{{if not .Done.NSD}}<div class="wiz-info"><span>Ключ ставится в шлюз НРД — сначала установите <a href="/admin/wizard?step=7">шлюз</a>.</span></div>{{end}}
<p style="margin:14px 0 0"><a href="/admin/setup/keywizard" class="btn" style="text-decoration:none;display:inline-block">{{if .Certs}}Установить другой ключ{{else}}Установить ключ{{end}}</a> <span class="muted" style="font-size:12.5px;margin-left:6px">архив → носитель → справочник → проверка подписи</span></p>
<div class="wiz-row" style="margin-top:20px">
@@ -336,14 +372,15 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
{{if eq .Step 12}}
<div class="card">
<h2><span class="dot {{if .Done.Server}}ok{{else}}warn{{end}}"></span>Время и сеть</h2>
<p>Часовой пояс, точное время и адрес сервера в сети.</p>
{{$tz := serverTimeZone}}
<form method="post" action="/admin/setup/timezone" id="wiz-tz" class="wiz-row">
<input type="hidden" name="return_to" value="/admin/wizard?step=12">
<label for="wiz-tz-zone" class="wiz-row-l">Часовой пояс</label>
<select id="wiz-tz-zone" name="zone" data-cur="{{$tz}}" onchange="this.form.submit()" style="padding:8px 10px;min-width:240px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:8px">{{range timeZones}}<option value="{{.Zone}}" {{if eq .Zone $tz}}selected{{end}}>{{.Title}}</option>{{end}}</select>
<select id="wiz-tz-zone" name="zone" data-cur="{{$tz}}" onchange="this.form.submit()" style="min-width:260px">{{range timeZones}}<option value="{{.Zone}}" {{if eq .Zone $tz}}selected{{end}}>{{.Title}}</option>{{end}}</select>
</form>
<p id="wiz-tz-hint" class="muted" style="margin:4px 0 0;font-size:13px">По нему — время в журналах, уведомлениях и сроках поручений. Время НРД — московское.</p>
<div id="wiz-tz-suggest" style="display:none;margin-top:10px;padding:10px 12px;border-radius:8px;background:var(--accent-weak);font-size:13.5px">На вашем компьютере пояс <b id="wiz-tz-name"></b>. <button type="button" class="btn" id="wiz-tz-use" style="margin-left:6px;padding:5px 12px;font-size:13px">Использовать его</button></div>
<div id="wiz-tz-suggest" class="wiz-info" style="display:none;align-items:center;flex-wrap:wrap"><span>На вашем компьютере пояс <b id="wiz-tz-name"></b>.</span> <button type="button" class="btn" id="wiz-tz-use" style="padding:5px 12px;font-size:13px">Использовать его</button></div>
<div class="wiz-row" style="margin-top:18px">
<span class="wiz-row-l">Точное время</span>
@@ -352,7 +389,7 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
<details style="margin-top:6px" {{if not .Done.Server}}open{{end}}><summary style="cursor:pointer;color:var(--accent);font-size:13px">{{if .Done.Server}}Свой сервер времени (обычно не нужно){{else}}Укажите сервер времени вашей сети — обычно контроллер домена или маршрутизатор{{end}}</summary>
<form method="post" action="/admin/setup/time" style="display:flex;gap:8px;flex-wrap:wrap;margin-top:8px">
<input type="hidden" name="return_to" value="/admin/wizard?step=12">
<input name="servers" value="{{.Settings.Time.Servers}}" placeholder="например, 10.0.0.1" aria-label="Сервер времени" style="padding:8px 10px;min-width:240px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:8px">
<input name="servers" value="{{.Settings.Time.Servers}}" placeholder="например, 10.0.0.1" aria-label="Сервер времени" style="min-width:260px">
<button type="submit" class="btn">Сохранить</button>
</form>
</details>
@@ -383,7 +420,7 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
{{if eq .Step 11}}
<div class="card">
<h2><span class="dot {{if .Done.Modules}}ok{{else}}warn{{end}}"></span>Модули</h2>
<p style="margin:2px 0 8px">Модули вашей лицензии уже установлены. Каждый настраивается в своём разделе — сейчас или позже.</p>
<p>Модули вашей лицензии уже установлены. Каждый настраивается в своём разделе — сейчас или позже.</p>
{{range .Modules}}
<div style="border:1px solid var(--border);border-radius:8px;padding:10px 12px;margin:8px 0">
<div style="display:flex;justify-content:space-between;gap:8px;align-items:center;flex-wrap:wrap">
@@ -402,7 +439,7 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
{{if eq .Step 9}}
<div class="card">
<h2><span class="dot {{if .Done.Alerts}}ok{{else}}warn{{end}}"></span>Оповещения и резервные копии</h2>
<p style="margin:2px 0 0">bj напишет на почту, если НРД не отвечает, перевод застрял{{if .DepoLicensed}}, DEPO не обработал поручение{{end}} или истекает ключ, — и будет сам делать копии сервера. Обмен работает и без этого, но мы советуем настроить оба пункта.</p>
<p>bj напишет на почту, если НРД не отвечает, перевод застрял{{if .DepoLicensed}}, DEPO не обработал поручение{{end}} или истекает ключ, — и будет сам делать копии сервера. Обмен работает и без этого, но мы советуем настроить оба пункта.</p>
<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}}
@@ -464,20 +501,19 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
document.getElementById('rd-again').onclick=load; load();
})();
</script>
</div>
{{template "chainTest" .}}
<div class="card">
<div style="margin-top:18px;padding-top:18px;border-top:1px solid var(--border)">
<h2>Письма для подключения</h2>
<p class="muted" style="margin:0 0 6px">Готовые письма с вашими реквизитами — тем, без кого подключение не закончить.</p>
{{template "letterCard" (letter "nsd-test")}}
{{template "letterCard" (letter "nsd-prod")}}
{{if .DepoLicensed}}{{template "letterCard" (letter "arqa")}}{{template "letterCard" (letter "dba")}}{{end}}
</div>
{{template "wizNav" .}}
</div>
{{end}}
</div>
</div>
<script>(function(){var c=document.querySelector(".wiz-st.cur");if(c&&c.scrollIntoView&&window.innerWidth<=820)c.scrollIntoView({block:"nearest",inline:"center"});})();</script>
{{end}}
@@ -62,6 +62,7 @@ body {
.brand b { font-weight:730; } .brand .brand-j { color:var(--muted); font-weight:600; }
.nav { display:flex; align-items:center; gap:1px; margin-left:6px; flex:1 1 auto; min-width:0; flex-wrap:wrap; }
.nav[hidden] { visibility:hidden; }
.topbar-sub { color:var(--muted); font-size:14px; margin-left:-4px; }
.nav::-webkit-scrollbar { display:none; }
.user-chip { display:inline-flex; flex-direction:column; line-height:1.15; margin-right:6px; max-width:180px; }
.user-chip .user-name { font-size:12.5px; font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
@@ -337,6 +338,7 @@ details:has(> pre.copyable) { min-width:0; max-width:100%; }
</svg>
<span><b>Bridge</b><span class="brand-j">&amp;Join</span></span>
</a>
{{if eq .Active "wizard"}}<span class="topbar-sub">Настройка</span>{{end}}
<nav class="nav"{{if eq .Active "wizard"}} hidden{{end}}>{{/* мастер и подготовка модулей — без меню системы, как помощник настройки */}}
{{if can "view"}}<a href="/admin/" class="{{if eq .Active "home"}}active{{end}}">Дашборд</a>
<span class="dd{{if or (eq .Active "journal") (eq .Active "claims") (eq .Active "incoming")}} active{{end}}">
@@ -4,7 +4,7 @@
{{if not .InWizard}}<h2><span class="dot {{if and .OrgINN .OrgCode}}ok{{else}}err{{end}}"></span>Наша организация</h2>
<p class="muted">Депозитарий — участник MOEX МОСТ, от имени которого bj отправляет и принимает переводы: ИНН передающего и зачисляющего депозитария в пакетах{{if mod "quik-mnp"}}, код отправителя и код фирмы в QUIK{{end}}. Выберите себя из справочника участников НРД или введите данные вручную.</p>{{end}}
{{if and .OrgINN .OrgCode}}<p style="margin:8px 0 0"><b>{{if .OrgName}}{{.OrgName}}{{else}}—{{end}}</b> · ИНН <code>{{.OrgINN}}</code> · код <code>{{.OrgCode}}</code>{{if not .Settings.NSD.OrgINN}} <span class="muted">(ИНН взят из справочника по коду)</span>{{end}}</p>
{{else}}<p style="margin:8px 0 0;color:var(--err)">Организация не задана — переводы не будут сформированы.</p>{{end}}
{{else if not .InWizard}}<p style="margin:8px 0 0;color:var(--err)">Организация не задана — переводы не будут сформированы.</p>{{end}}
<form method="post" action="/admin/setup/org" style="margin-top:12px;display:grid;gap:10px;max-width:680px">
<input type="hidden" name="section" value="sec-org">
<label>Найти себя в справочнике участников M2M</label>
@@ -1,7 +1,7 @@
{{/* Навигация шага мастера: соседние шаги этой установки (лицензия, сделанное автоматически — wizchapters.go). */}}
{{/* Низ карточки шага мастера: «Назад» слева, «Далее» справа (несделанный обязательный шаг — «Пропустить пока»). */}}
{{define "wizNav"}}
<div class="wiz-nav">
{{if .Prev}}<a href="/admin/wizard?step={{.Prev}}" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none">← Назад</a>{{else}}<span></span>{{end}}
{{if .Next}}{{if or .StepDone (not .StepRequired)}}<a href="/admin/wizard?step={{.Next}}" class="btn" style="text-decoration:none">Далее →</a>{{else}}<a href="/admin/wizard?step={{.Next}}" class="btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8);text-decoration:none" title="Можно вернуться к этому позже">Пропустить пока →</a>{{end}}{{else}}<a href="/admin/" class="btn" style="text-decoration:none">Перейти к работе →</a>{{end}}
{{if .Prev}}<a href="/admin/wizard?step={{.Prev}}" class="wiz-back">← Назад</a>{{else}}<span></span>{{end}}
{{if .Next}}{{if or .StepDone (not .StepRequired)}}<a href="/admin/wizard?step={{.Next}}" class="btn wiz-next">Далее →</a>{{else}}<a href="/admin/wizard?step={{.Next}}" class="btn wiz-skip" title="Можно вернуться к этому позже">Пропустить пока →</a>{{end}}{{else}}<a href="/admin/" class="btn wiz-next">Перейти к работе →</a>{{end}}
</div>
{{end}}
+4 -3
View File
@@ -24,7 +24,7 @@ var wizChapters = []struct {
var wizStepTitles = map[int]string{
1: "Лицензия", 2: "База bj", 6: "Валидата", 12: "Время и сеть",
3: "Учёт DEPO", 4: "Организация", 5: "QUIK МНП",
7: "Шлюз НРД", 8: "Ключ",
7: "Шлюз НРД", 8: "Ключ подписи",
11: "Модули", 9: "Оповещения и копии", 10: "Проверка",
}
@@ -36,7 +36,8 @@ var wizModules = []string{"web-cabinet", "esia-finance", "quik-export", "npr"}
// WizStep — шаг в пути мастера.
type WizStep struct {
N int
N int // номер шага в адресе (?step=N)
Num int // порядковый номер в списке шагов слева (1, 2, 3… — только шаги этой установки)
Title string
Done bool
Current bool
@@ -79,7 +80,7 @@ func wizPath(rc *RuntimeConfig, done map[int]bool, current int) (path []int, cha
continue
}
path = append(path, n)
c.Steps = append(c.Steps, WizStep{N: n, Title: wizStepTitles[n], Done: done[n], Current: n == current})
c.Steps = append(c.Steps, WizStep{N: n, Num: len(path), Title: wizStepTitles[n], Done: done[n], Current: n == current})
if c.First == 0 {
c.First = n
}