Индикатор «Шлюз НРД», проверка ключа ИШ, папки шлюза по сети, сотрудники из домена, переключатель QUIK в настройках

- журнал ИШ: номер процесса выравнивается пробелами — опросы НРД не распознавались (серый индикатор)
- «Проверить СКЗИ и ключ»: Валидата, датчик случайных чисел, подпись профилем ИШ (а не старый bj-crypto);
  индикатор «Криптографии» — по готовности ключа; карточка автозагрузки УЦ — только для разработки
- шаг «Шлюз НРД»: папки обмена по сети (Samba): логин/пароль, ручная настройка не трогается
- «Учётные записи»: необязательно — подключить домен, проверить связь, найти сотрудника и добавить с ролью;
  роль из bj действует, пока группы домена с ролями не сопоставлены
- настройка QUIK МНП: «Только чтение / Чтение и запись», тонкая настройка цикла свёрнута
- индикатор «Обновления» — только когда есть что обновить; «Мастер настройки» в меню и в настройках

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-07 00:24:19 +03:00
parent 6a48f7e7a4
commit afc5776347
17 changed files with 421 additions and 55 deletions
@@ -520,33 +520,7 @@
{{if .Settings.Quik.Server}}{{template "mnpCheck" .}}{{end}}
{{if .Settings.Quik.M2MMissing}}<p style="color:var(--err);margin:8px 0">✗ Подключение есть, но под этим логином не видно объектов M2M: {{.Settings.Quik.M2MMissing}}. Проверьте имя базы МНП и права логина (роль InstrEu) и нажмите «Проверить» ещё раз.</p>{{end}}
{{if .Settings.Quik.Verified}}
<div class="card" id="quik-mode">
<h2>Как bj работает с QUIK</h2>
<form method="post" action="/admin/setup/quik/mode" id="quik-mode-form" class="dq">
<input type="hidden" name="return_to" value="/admin/wizard?step=5">
<div class="dq-tiles">
<label class="dq-tile"><input type="radio" name="mode" value="check" {{if not .Settings.Quik.WritesEnabled}}checked{{end}}><span><b>Только чтение</b><small>bj подключён к QUIK и читает данные, но ничего в нём не меняет: не блокирует и не разблокирует бумаги, не меняет лимиты. Для настройки и проверки — когда будете готовы, включите «Чтение и запись».</small></span></label>
<label class="dq-tile"><input type="radio" name="mode" value="auto" {{if .Settings.Quik.WritesEnabled}}checked{{end}}><span><b>Чтение и запись</b><small>Боевая работа: bj сам блокирует бумаги в QUIK при отправке перевода, списывает или снимает блокировку по ответу НРД, зачисляет входящие.</small></span></label>
</div>
<div class="dq-jobname" id="quik-acc">
<label>Торговый счёт для поручений<input type="text" name="account" value="{{.Settings.Quik.Account}}" placeholder="торговый счёт фирмы в QUIK" spellcheck="false" class="dq-mono"></label>
</div>
<p class="dq-note" id="quik-mode-st" aria-live="polite">Выбор сохраняется сразу. Тонкая настройка — инструменты МНП, коды клиентов, поручения из терминала — в «Настройка → QUIK».</p>
<noscript><button type="submit" class="btn">Сохранить</button></noscript>
</form>
<p class="dq-note">{{quikRecipientsInfo}} После первого заполнения справочника депозитариев-получателей попросите администратора QUIK перезапустить модуль неторговых поручений — QUIK читает справочник только при запуске.</p>
</div>
<script>(function(){
var f=document.getElementById('quik-mode-form'), st=document.getElementById('quik-mode-st'), acc=document.getElementById('quik-acc');
function sync(){ acc.hidden=f.mode.value!=='auto'; }
f.addEventListener('change', function(){ sync();
fetch(f.action,{method:'POST',body:new URLSearchParams(new FormData(f)),headers:{'Content-Type':'application/x-www-form-urlencoded','X-BJ-Ajax':'1'},cache:'no-store'})
.then(function(r){ return r.text().then(function(t){ st.textContent=(r.ok?'✓ Сохранено: ':'✗ ')+t; }); })
.catch(function(){ st.textContent='✗ Не сохранилось — обновите страницу и выберите ещё раз.'; });
});
f.addEventListener('submit', function(e){ if(window.fetch) e.preventDefault(); });
sync();
})();</script>
{{template "quikModeCard" (withRet . "/admin/wizard?step=5")}}
{{else}}<p class="muted" style="margin-top:12px">Когда подключение проверено, здесь появится выбор, как bj работает с QUIK.</p>{{end}}{{end}}
{{template "wizNav" .}}
</div>
@@ -774,6 +748,47 @@
<p class="muted" style="font-size:12.5px;margin:8px 0 0">Пароль передайте сотруднику лично; сменить его можно в разделе Настройка → Пользователи.</p>
<div style="margin-top:12px"><button type="submit" class="btn btn-ok">Создать учётную запись</button></div>
</form>
{{$A := .Settings.Auth}}
<details class="acc-ad" style="margin-top:20px;padding-top:14px;border-top:1px solid var(--border)">
<summary style="cursor:pointer;color:var(--accent);font-size:14px">Добавить сотрудника из домена (Active Directory) — необязательно</summary>
{{if and $A.ADEnabled $A.ADBindUser}}
<p class="muted" style="font-size:13.5px;margin:10px 0 0">Домен <b>{{$A.ADDomain}}</b> подключён. Найдите сотрудника — он будет входить в bj своим доменным логином и паролем; пароль проверяет домен.</p>
<input type="text" id="acc-ad-q" placeholder="Фамилия, логин или почта" autocomplete="off" style="width:100%;max-width:420px;margin-top:10px">
<div id="acc-ad-st" class="muted" style="font-size:13px;margin-top:6px"></div>
<ul id="acc-ad-res" style="list-style:none;margin:8px 0 0;padding:0;display:grid;gap:6px;max-width:640px"></ul>
<form method="post" action="/admin/setup/users" id="acc-ad-form" hidden style="margin-top:12px;display:flex;gap:10px;align-items:end;flex-wrap:wrap">
<input type="hidden" name="return_to" value="/admin/wizard?step=13"><input type="hidden" name="create" value="1"><input type="hidden" name="active" value="1"><input type="hidden" name="source" value="ad">
<input type="hidden" name="login"><input type="hidden" name="name"><input type="hidden" name="email">
<span>Добавить <b id="acc-ad-who"></b> с ролью</span>
<select name="role">{{range .Users.Roles}}<option value="{{.Key}}" {{if eq .Key "operator"}}selected{{end}}>{{.Title}}</option>{{end}}</select>
<button type="submit" class="btn btn-ok">Добавить из домена</button>
</form>
<script>(function(){
var q=document.getElementById('acc-ad-q'), st=document.getElementById('acc-ad-st'), ul=document.getElementById('acc-ad-res'), f=document.getElementById('acc-ad-form'), who=document.getElementById('acc-ad-who'), t=0;
function esc(s){return String(s==null?'':s).replace(/[&<>"]/g,function(c){return{'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]})}
q.addEventListener('input',function(){ clearTimeout(t); t=setTimeout(function(){
var v=q.value.trim(); if(v.length<2){ ul.innerHTML=''; st.textContent=''; return; } st.textContent='Ищу в домене…';
fetch('/admin/setup/users/ad-search?q='+encodeURIComponent(v),{cache:'no-store',headers:{'Accept':'application/json'}}).then(function(r){return r.json()}).then(function(d){
if(!d.ok){ st.textContent='Не получилось: '+d.error; ul.innerHTML=''; return; }
st.textContent=d.users.length?'Найдено: '+d.users.length:'Никого не нашлось';
ul.innerHTML=d.users.map(function(u,i){ return '<li><button type="button" class="btn btn-secondary" style="width:100%;text-align:left" data-i="'+i+'"'+(u.have?' disabled':'')+'><b>'+esc(u.name||u.login)+'</b> <span class="muted">'+esc(u.login)+(u.email?' · '+esc(u.email):'')+(u.have?' · уже есть в bj':'')+'</span></button></li>'; }).join('');
ul.querySelectorAll('button').forEach(function(b){ b.onclick=function(){ var u=d.users[+b.dataset.i]; f.login.value=u.login; f.name.value=u.name||''; f.email.value=u.email||''; who.textContent=(u.name||u.login)+' ('+u.login+')'; f.hidden=false; }; });
}).catch(function(){ st.textContent='Домен не ответил'; });
},350); });
})();</script>
{{end}}
<details {{if not (and $A.ADEnabled $A.ADBindUser)}}open{{end}} style="margin-top:12px"><summary style="cursor:pointer;font-size:13px;color:var(--accent)">{{if and $A.ADEnabled $A.ADBindUser}}Другой домен или учётная запись{{else}}Подключить домен{{end}}</summary>
<p class="muted" style="font-size:13px;margin:8px 0 0">Нужны адрес контроллера домена и учётная запись для поиска сотрудников — подойдёт обычная, без прав администратора домена. bj сразу проверит связь.</p>
<form method="post" action="/admin/setup/users/ad-connect" class="acc-form">
<input type="hidden" name="return_to" value="/admin/wizard?step=13">
<label>Контроллер домена<input type="text" name="ad_server" data-need="контроллер домена" value="{{$A.ADServer}}" placeholder="dc1.corp.local или ldaps://dc1.corp.local:636" autocomplete="off"></label>
<label>Домен<input type="text" name="ad_domain" data-need="домен" value="{{$A.ADDomain}}" placeholder="corp.local" autocomplete="off"></label>
<label>Учётная запись для поиска<input type="text" name="ad_bind_user" data-need="учётная запись для поиска" value="{{$A.ADBindUser}}" placeholder="svc-bj" autocomplete="off"></label>
<label>Пароль{{if $A.ADBindPassword}} <span class="muted">(задан; пусто — оставить)</span>{{end}}<input type="password" name="ad_bind_password" {{if not $A.ADBindPassword}}data-need="пароль учётной записи"{{end}} autocomplete="new-password"></label>
<div><button type="submit" class="btn">Подключить и проверить связь</button></div>
</form>
</details>
</details>
<script>(function(){
var g=document.getElementById('acc-gen'), f=document.getElementById('acc-pw'); if(!g||!f||!window.crypto) return;
g.addEventListener('click',function(){ var a='abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789', b=new Uint32Array(12), p='';