Настройки: карточки по своим разделам, подтверждения у опасных действий

- НРД: «Шлюз НРД» (установка, контуры, папки) и «Регистрация участника» —
  над «Перенастроить», а не внутри; «Рабочее время» — из «Сервера» в «НРД»
- Zabbix — из «Диагностики» в «Оповещения»
- заголовок «Лицензия» — у своего раздела (стоял в конце «Экспорта QUIK»)
- подтверждения: режим «bj сам / Оператор», приём ИИС, «Обновить канал»
  (перезапуск шлюза), «Установить шлюз заново»

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
This commit is contained in:
fontvielle
2026-10-21 23:54:35 +03:00
parent 90aacf9ef4
commit 317bf85778
4 changed files with 48 additions and 36 deletions
@@ -241,7 +241,6 @@
<div style="display:flex;gap:8px;flex-wrap:wrap"><button type="submit" class="btn btn-ok">Сохранить</button><button type="submit" formaction="/admin/setup/npr/test" class="btn">Проверить подключение</button></div>
</form>
</div>{{end}}
<h1>Лицензия</h1>
</section>
{{end}}
@@ -337,12 +336,6 @@
<section class="settings-section" id="sec-nsd">
<h1>НРД</h1>
{{template "nsdStatus" .}}
<div class="card">
<details class="adv-box" {{if not (and (index orgEff "INN") (index orgEff "Code") .Settings.NSD.DeponentCode .Settings.NSD.AccountID .Settings.NSD.SectionID .Settings.NSD.IGWBaseURL)}}open{{end}}>
<summary><span class="sum-h">Перенастроить</span><span class="muted">организация, реквизиты в НРД, кто отвечает на переводы, шлюз и контуры, папки шлюза по сети</span></summary>
{{template "orgSection" .}}
{{template "nsdRequisites" .}}
{{template "m2mAutoCard" (withRet . "")}}
<div class="card" id="nsd-igate">
<h2><span class="dot {{if .Settings.NSD.IGWBaseURL}}ok{{else}}warn{{end}}"></span>Шлюз НРД</h2>
<p class="muted">bj сам ставит и настраивает интеграционный шлюз НРД на этом сервере: контуры TEST3 и боевой, переключение между ними, доступ к папкам шлюза по сети.</p>
@@ -359,6 +352,17 @@
<script>(function(){var d=document.getElementById('pf-box'),f=document.getElementById('pf-frame');
if(d&&f) d.addEventListener('toggle',function(){ if(d.open&&!f.getAttribute('src')) f.setAttribute('src','/admin/setup/participant-form'); });})();</script>
</div>
<div class="card" id="sec-workhours">
<h2><span class="dot {{if and .Settings.WorkHours.Enabled (not .Settings.WorkHours.SavedAt.IsZero)}}ok{{else}}warn{{end}}"></span>Рабочее время организации</h2>
<p class="muted">Когда депозитарий принимает запросы на перевод M2M.</p>
{{template "workHoursForm" (whForm .Settings.WorkHours "")}}
</div>
<div class="card">
<details class="adv-box" {{if not (and (index orgEff "INN") (index orgEff "Code") .Settings.NSD.DeponentCode .Settings.NSD.AccountID .Settings.NSD.SectionID .Settings.NSD.IGWBaseURL)}}open{{end}}>
<summary><span class="sum-h">Перенастроить</span><span class="muted">организация, реквизиты в НРД, кто отвечает на переводы</span></summary>
{{template "orgSection" .}}
{{template "nsdRequisites" .}}
{{template "m2mAutoCard" (withRet . "")}}
{{if devOn}}<div class="card">
<details class="adv-box">
<summary><span class="sum-h">Расширенные настройки</span><span class="muted">шлюз вручную: контур, адрес веб-сервиса, ключевой контейнер, версия ИШ — обычно bj задаёт их сам</span></summary>
@@ -843,24 +847,6 @@
</tr>{{end}}</tbody></table></div>
</div>
<div class="card" id="sec-zabbix">
<h2>Мониторинг Zabbix</h2>
<p class="muted">bj шлёт значение на элемент типа <strong>Zabbix trapper</strong> (протокол <code>zabbix_sender</code>). В Zabbix заведите host и item-trapper с этим ключом, затем триггер по значению — уведомления настроит сам Zabbix.</p>
<form method="post" action="/admin/setup/alerts-zabbix" style="margin-top:12px">
<label style="display:flex;align-items:center;gap:8px;font-weight:400;margin-bottom:12px"><input type="checkbox" name="enabled" style="width:auto" {{if .Settings.Alerts.Zabbix.Enabled}}checked{{end}}> Включить хук Zabbix</label>
<div class="grid" style="grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px">
<div><label>Сервер / прокси Zabbix</label><input type="text" name="server" value="{{.Settings.Alerts.Zabbix.Server}}" placeholder="zabbix.example.ru" style="margin-top:3px"></div>
<div><label>Порт</label><input type="text" name="port" value="{{if .Settings.Alerts.Zabbix.Port}}{{.Settings.Alerts.Zabbix.Port}}{{else}}10051{{end}}" style="margin-top:3px"></div>
<div><label>Имя хоста в Zabbix</label><input type="text" name="host" value="{{.Settings.Alerts.Zabbix.Host}}" placeholder="bj-server" style="margin-top:3px"></div>
<div><label>Ключ элемента (trapper)</label><input type="text" name="key" value="{{if .Settings.Alerts.Zabbix.Key}}{{.Settings.Alerts.Zabbix.Key}}{{else}}bj.alert{{end}}" style="margin-top:3px"></div>
</div>
<div style="margin-top:16px;display:flex;gap:8px;flex-wrap:wrap;align-items:center">
<button type="submit" class="btn">Сохранить</button>
<button type="submit" formaction="/admin/setup/alerts-zabbix-test" class="btn btn-secondary">Отправить проверочное значение</button>
</div>
</form>
{{if .Settings.Alerts.Zabbix.LastMsg}}<p class="muted" style="font-size:12px;margin-top:10px">Последняя проверка: {{.Settings.Alerts.Zabbix.LastMsg}}</p>{{end}}
</div>
<div class="card">
<h2>Здоровье сервиса</h2>
@@ -937,6 +923,24 @@
{{template "notifyGroupsCard" .}}
<div class="card" id="sec-zabbix">
<h2>Мониторинг Zabbix</h2>
<p class="muted">bj шлёт значение на элемент типа <strong>Zabbix trapper</strong> (протокол <code>zabbix_sender</code>). В Zabbix заведите host и item-trapper с этим ключом, затем триггер по значению — уведомления настроит сам Zabbix.</p>
<form method="post" action="/admin/setup/alerts-zabbix" style="margin-top:12px">
<label style="display:flex;align-items:center;gap:8px;font-weight:400;margin-bottom:12px"><input type="checkbox" name="enabled" style="width:auto" {{if .Settings.Alerts.Zabbix.Enabled}}checked{{end}}> Включить хук Zabbix</label>
<div class="grid" style="grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px">
<div><label>Сервер / прокси Zabbix</label><input type="text" name="server" value="{{.Settings.Alerts.Zabbix.Server}}" placeholder="zabbix.example.ru" style="margin-top:3px"></div>
<div><label>Порт</label><input type="text" name="port" value="{{if .Settings.Alerts.Zabbix.Port}}{{.Settings.Alerts.Zabbix.Port}}{{else}}10051{{end}}" style="margin-top:3px"></div>
<div><label>Имя хоста в Zabbix</label><input type="text" name="host" value="{{.Settings.Alerts.Zabbix.Host}}" placeholder="bj-server" style="margin-top:3px"></div>
<div><label>Ключ элемента (trapper)</label><input type="text" name="key" value="{{if .Settings.Alerts.Zabbix.Key}}{{.Settings.Alerts.Zabbix.Key}}{{else}}bj.alert{{end}}" style="margin-top:3px"></div>
</div>
<div style="margin-top:16px;display:flex;gap:8px;flex-wrap:wrap;align-items:center">
<button type="submit" class="btn">Сохранить</button>
<button type="submit" formaction="/admin/setup/alerts-zabbix-test" class="btn btn-secondary">Отправить проверочное значение</button>
</div>
</form>
{{if .Settings.Alerts.Zabbix.LastMsg}}<p class="muted" style="font-size:12px;margin-top:10px">Последняя проверка: {{.Settings.Alerts.Zabbix.LastMsg}}</p>{{end}}
</div>
</section>
{{/* ============ ОБНОВЛЕНИЯ ============ */}}
@@ -1018,11 +1022,6 @@
<form method="post" action="/admin/setup/https/selfsigned" style="margin-top:8px"><button type="submit" class="btn btn-secondary" onclick="return confirm('Создать новый самоподписанный сертификат? Сертификат организации будет заменён.')">Вернуть самоподписанный</button></form>
</details>
</div>
<div class="card" id="sec-workhours">
<h2><span class="dot {{if and .Settings.WorkHours.Enabled (not .Settings.WorkHours.SavedAt.IsZero)}}ok{{else}}warn{{end}}"></span>Рабочее время организации</h2>
<p class="muted">Когда депозитарий принимает запросы на перевод M2M.</p>
{{template "workHoursForm" (whForm .Settings.WorkHours "")}}
</div>
<div class="card" id="sec-network">
<h2>Сеть</h2>
@@ -1037,6 +1036,7 @@
</section>
<section class="settings-section" id="sec-license">
<h1>Лицензия</h1>
<div class="card">
<h2>
{{if .License.Valid}}<span class="dot ok"></span>Активна
@@ -313,7 +313,8 @@
function act(a,c,b){
if(a==='showprod'){ document.getElementById('ig-prod').style.display='block'; return; }
var p=new URLSearchParams(); p.set('contour',c); p.set('crypto',document.getElementById('ig-crypto').value);
if(a==='contour'){ b.disabled=true; cout.textContent='';
if(a==='contour'){ if(b.textContent.indexOf('Обновить')===0 && !confirm('Записать канал в шлюз заново? Шлюз перезапустится — обмен с НРД остановится до 2 минут.')) return;
b.disabled=true; cout.textContent='';
post('/admin/setup/igate/contour',p).then(function(d){ b.disabled=false; if(!d.ok){ cout.innerHTML=bad(d.error); return; } job.watch(c); })
.catch(function(){ b.disabled=false; job.watch(c); }); }
if(a==='switch'){ if(!confirm('Переключить bj на тестовый контур TEST3?')) return; cout.textContent='Переключаю…';
@@ -383,7 +384,9 @@
if(s.stage==='error'){ clearInterval(iv); clearInterval(tick); btn.disabled=false; out.innerHTML=bad(s.msg)+' — можно нажать «Установить ИШ заново»'; logbox.open=true; load(); }
}).catch(function(){});
if(Date.now()-t0>2700000){ clearInterval(iv); clearInterval(tick); } },3000); }
document.getElementById('ig-bnd').onclick=function(){ var p=new URLSearchParams(); p.set('bundled','1');
document.getElementById('ig-bnd').onclick=function(){
if(/заново/.test(this.textContent) && !confirm('Установить шлюз НРД заново? Шлюз остановится на 2–3 минуты, обмен с НРД будет приостановлен; настройки, каналы и ключи сохранятся.')) return;
var p=new URLSearchParams(); p.set('bundled','1');
out.textContent='Запускаю…'; cur={key:'',since:0,s:null}; post('/admin/setup/igate/install',p).then(function(d){ if(!d.ok){out.innerHTML=bad(d.error);return} out.textContent=d.output; poll(); }); };
document.getElementById('ig-rb-btn').onclick=function(){ var v=document.getElementById('ig-rb-v').textContent;
if(!confirm('Откатить ИШ к версии '+v+'? Шлюз остановится на 1–3 минуты; программа, данные и база ИШ вернутся к состоянию до обновления. Пришедшую версию bj сам ставить больше не будет, пока не выйдет следующая.')) return;
+11 -2
View File
@@ -51,7 +51,9 @@
<script>(function(){
var fi=document.getElementById("accept-iis-form"), si=document.getElementById("accept-iis-st");
if(fi&&window.fetch){
fi.addEventListener("change", function(){
fi.addEventListener("change", function(e){
var cb=fi.querySelector("input[name=enabled]");
if(cb && !confirm(cb.checked ? "Принимать входящие переводы на ИИС? Перевод на ИИС принимается только целиком." : "Перестать принимать переводы на ИИС? Такие переводы bj будет отклонять.")){ cb.checked=!cb.checked; return; }
fetch(fi.action,{method:"POST",body:new URLSearchParams(new FormData(fi)),headers:{"Content-Type":"application/x-www-form-urlencoded","X-BJ-Ajax":"1"},cache:"no-store"})
.then(function(r){ return r.text().then(function(t){ si.textContent=(r.ok?"\u2713 ":"\u2717 ")+t; }); })
.catch(function(){ si.textContent="\u2717 Не сохранилось — обновите страницу."; });
@@ -61,7 +63,14 @@
})();</script>
<script>(function(){
var f=document.getElementById('m2m-auto-form'), st=document.getElementById('m2m-auto-st'); if(!f||!window.fetch) return;
f.addEventListener('change', function(){
var was=(f.querySelector('input[name=mode]:checked')||{}).value;
f.addEventListener('change', function(e){
var v=(f.querySelector('input[name=mode]:checked')||{}).value;
if(v!==was && !confirm(v==='manual'
? 'Перейти в режим «Оператор»? bj перестанет сам отвечать на входящие переводы и подавать поручения — каждый перевод нужно будет обработать вручную в срок, иначе НРД отклонит его.'
: 'Включить режим «bj сам»? bj начнёт сам отвечать на входящие переводы по сверке с DEPO и подавать поручения (переводы, пришедшие не раньше чем за сутки).')){
var r=f.querySelector('input[name=mode][value="'+was+'"]'); if(r) r.checked=true; return; }
was=v;
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='✗ Не сохранилось — обновите страницу и выберите ещё раз.'; });
@@ -38,7 +38,7 @@
<div class="card" id="sec-nsd-status">
<div class="stc {{if $ok}}stc-ok{{else}}stc-err{{end}}" id="nsd-st-card"><span class="stc-ic"></span>
<div class="stc-b"><b id="nsd-st-title">{{if $ok}}Обмен с НРД настроен{{else}}Обмен с НРД настроен не до конца{{end}}</b>
<small>{{if $ok}}Организация, реквизиты и шлюз — ниже. Изменить — «Перенастроить».{{else}}Что не так — в строках ниже с ✗; поправить — в «Перенастроить» или мастером настройки.{{end}}</small></div></div>
<small>{{if $ok}}Шлюз и контуры — ниже; организация и реквизиты — «Перенастроить».{{else}}Что не так — в строках ниже с ✗; поправить — в «Перенастроить» или мастером настройки.{{end}}</small></div></div>
<dl class="kv">
<dt>Организация</dt><dd>{{if $org}}<b>{{with index $o "Name"}}{{.}}{{else}}—{{end}}</b> · ИНН <code>{{index $o "INN"}}</code> · код <code>{{index $o "Code"}}</code>{{else}}<span class="kv-no">✗</span> не задана{{end}}</dd>
<dt>Реквизиты в НРД</dt><dd>{{if $req}}код <code>{{$n.DeponentCode}}</code> · счёт <code>{{$n.AccountID}}</code> · раздел <code>{{$n.SectionID}}</code>{{else}}<span class="kv-no">✗</span> не заданы{{end}}</dd>
@@ -58,7 +58,7 @@
});
window.addEventListener('bj-igate-share',function(e){ var d=e.detail||{}; if(!sh) return;
if(d.manual){ sh.textContent='открыты вручную (smb.conf)'; return; }
if(!d.on){ sh.innerHTML='закрыты <span class="muted">— открыть: «Перенастроить» → «Папки шлюза по сети»</span>'; return; }
if(!d.on){ sh.innerHTML='закрыты <span class="muted">— открыть: карточка «Шлюз НРД» ниже → «Папки шлюза по сети»</span>'; return; }
var ip=d.ip||location.hostname, names=(d.shares&&d.shares.length)?d.shares:['igate_exchange'];
sh.innerHTML='открыты '+(d.mode==='domain'?'через домен '+esc(d.realm||'')+' — своим входом в Windows':'для '+esc(d.user||'—'))+': '+names.map(function(n){return '<code>\\\\'+esc(ip)+'\\'+esc(n)+'</code>'}).join(', ');
});