Первичная настройка, часть 2: шаг 9 «Оповещения и резервные копии», шаг 10 — итоговая проверка готовности (живые проверки со ссылкой «Исправить»); проверка модуля неторговых поручений QUIK (объекты M2M, инструменты, справочник получателей, цикл, ошибки операций); восстановление из копии на /first-run по одноразовому коду; возврат на тот же шаг мастера после «Сохранить» (return_to с ?step); пароль SMTP не выводится в страницу; карточки почты, адресатов и копий — общие для настроек и мастера
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
@@ -94,6 +94,7 @@
|
||||
<section class="settings-section" id="sec-quik">
|
||||
<h1>QUIK — Модуль неторговых поручений</h1>
|
||||
{{template "quikSection" .}}
|
||||
{{if .Settings.Quik.Server}}{{template "mnpCheck" .}}{{end}}
|
||||
</section>
|
||||
|
||||
{{end}}{{/* /AdvancedUnlocked — конец перспективных модулей Fancy/QUIK */}}
|
||||
@@ -839,34 +840,7 @@
|
||||
<h1>Оповещения</h1>
|
||||
<p class="muted" style="max-width:60ch">Системные события (сбой или зависание фонового компонента, аварийный перезапуск) всегда попадают в ленту «Новости». Дополнительно их можно доставлять наружу — на почту оператору и/или в мониторинг Zabbix, чтобы узнать о проблеме, даже когда интерфейс bj никто не открыл.</p>
|
||||
|
||||
<div class="card">
|
||||
<h2>Почта (SMTP)</h2>
|
||||
<p class="muted">Отправка уведомлений по e-mail. Пароль хранится в конфигурации сервиса.</p>
|
||||
<form method="post" action="/admin/setup/alerts-smtp" 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.SMTP.Enabled}}checked{{end}}> Включить почтовые уведомления</label>
|
||||
<div class="grid" style="grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px">
|
||||
<div><label>SMTP-сервер</label><input type="text" name="host" value="{{.Settings.Alerts.SMTP.Host}}" placeholder="smtp.example.ru" style="margin-top:3px"></div>
|
||||
<div><label>Порт</label><input type="text" id="smtp-port" name="port" value="{{if .Settings.Alerts.SMTP.Port}}{{.Settings.Alerts.SMTP.Port}}{{else}}587{{end}}" style="margin-top:3px"></div>
|
||||
<div><label>Шифрование</label>
|
||||
<select id="smtp-security" name="security" style="margin-top:3px" onchange="smtpSyncPort(this.value)">
|
||||
<option value="starttls" {{if eq .Settings.Alerts.SMTP.Security "starttls"}}selected{{end}}>STARTTLS (587)</option>
|
||||
<option value="ssl" {{if eq .Settings.Alerts.SMTP.Security "ssl"}}selected{{end}}>SSL/TLS (465)</option>
|
||||
<option value="none" {{if eq .Settings.Alerts.SMTP.Security "none"}}selected{{end}}>Без шифрования (25)</option>
|
||||
</select></div>
|
||||
</div>
|
||||
<div class="grid" style="grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px;margin-top:12px">
|
||||
<div><label>Логин</label><input type="text" name="username" value="{{.Settings.Alerts.SMTP.Username}}" autocomplete="off" style="margin-top:3px"></div>
|
||||
<div><label>Пароль</label><input type="password" name="password" value="{{.Settings.Alerts.SMTP.Password}}" autocomplete="new-password" style="margin-top:3px"></div>
|
||||
<div><label>Отправитель (From)</label><input type="text" name="from" value="{{.Settings.Alerts.SMTP.From}}" placeholder="bj@example.ru" style="margin-top:3px"></div>
|
||||
<div><label>Получатели (через запятую)</label><input type="text" name="to" value="{{.Settings.Alerts.SMTP.To}}" placeholder="ops@example.ru, dev@example.ru" 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-smtp-test" class="btn btn-secondary">Отправить проверочное письмо</button>
|
||||
</div>
|
||||
</form>
|
||||
{{if .Settings.Alerts.SMTP.LastMsg}}<p class="muted" style="font-size:12px;margin-top:10px">Последняя проверка: {{.Settings.Alerts.SMTP.LastMsg}}</p>{{end}}
|
||||
</div>
|
||||
{{template "smtpCard" .}}
|
||||
|
||||
<div class="card">
|
||||
<h2>Zabbix (хук мониторинга)</h2>
|
||||
@@ -887,35 +861,7 @@
|
||||
{{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>
|
||||
<p class="muted">Три группы адресов и таблица «какой тип уведомления кому отправлять». Письма идут через SMTP из раздела «Почта». Адрес алертов из раздела «Почта» получает системные оповещения всегда; группы ниже добавляются по маршруту.</p>
|
||||
<form method="post" action="/admin/setup/incoming-notify" 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.Incoming.Enabled}}checked{{end}}> Включить уведомления о переводах</label>
|
||||
<div style="display:grid;gap:10px;max-width:640px">
|
||||
<div><label>Депозитарий (через запятую)</label><input type="text" name="emails" value="{{.Settings.Incoming.Emails}}" placeholder="depo@example.ru" style="margin-top:3px;width:100%"></div>
|
||||
<div><label>Бэк-офис (через запятую)</label><input type="text" name="emails_back" value="{{.Settings.Incoming.EmailsBack}}" placeholder="backoffice@example.ru" style="margin-top:3px;width:100%"></div>
|
||||
<div><label>Мидл-офис (через запятую)</label><input type="text" name="emails_middle" value="{{.Settings.Incoming.EmailsMiddle}}" placeholder="middle@example.ru" style="margin-top:3px;width:100%"></div>
|
||||
</div>
|
||||
<table style="margin-top:14px;max-width:760px">
|
||||
<thead><tr><th>Тип уведомления</th>{{range .NotifyGroups}}<th style="text-align:center">{{.Title}}</th>{{end}}</tr></thead>
|
||||
<tbody>{{$inc := .Settings.Incoming}}{{range $k := .NotifyKinds}}<tr><td>{{$k.Title}}</td>{{range $g := $.NotifyGroups}}<td style="text-align:center"><input type="checkbox" name="route_{{$k.Key}}_{{$g.Key}}" value="1" {{if $inc.RouteHas $k.Key $g.Key}}checked{{end}} style="width:auto"></td>{{end}}</tr>{{end}}</tbody>
|
||||
</table>
|
||||
<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/incoming-notify-test" class="btn btn-secondary">Отправить пробное уведомление</button>
|
||||
</div>
|
||||
</form>
|
||||
<p class="muted" style="font-size:12px;margin-top:8px">Требуется настроенный и включённый SMTP в разделе «Почта».</p>
|
||||
</div>
|
||||
<script>
|
||||
// Смена шифрования SMTP авто-подставляет типовой порт; править вручную можно.
|
||||
function smtpSyncPort(sec){
|
||||
var p=document.getElementById('smtp-port'); if(!p) return;
|
||||
var def={starttls:'587', ssl:'465', none:'25'}[sec];
|
||||
if(def) p.value=def;
|
||||
}
|
||||
</script>
|
||||
{{template "notifyGroupsCard" .}}
|
||||
</section>
|
||||
|
||||
{{/* ============ ОБНОВЛЕНИЯ ============ */}}
|
||||
@@ -992,68 +938,7 @@
|
||||
{{/* ============ РЕЗЕРВНОЕ КОПИРОВАНИЕ ============ */}}
|
||||
<section class="settings-section" id="sec-backup">
|
||||
<h1>Резервное копирование</h1>
|
||||
{{$b := .Settings.Backup}}{{$bs := backupStatus}}
|
||||
<div class="card">
|
||||
<h2><span class="dot {{if not $b.Enabled}}warn{{else if and $bs $bs.OK}}ok{{else}}err{{end}}"></span>Копии сервера bj на сетевое хранилище</h2>
|
||||
<p class="muted">Что копируется: база bj (заявки, сделки, кабинет), база и папка шлюза НРД (настройки, сертификаты, полученные от НРД документы), настройки и данные bj. Пароль хранилища bj у себя не хранит — он лежит на сервере в файле, доступном только системе. Восстановить копию можно в мастере настройки (шаг 1, «Восстановить из резервной копии»).</p>
|
||||
<p style="margin:8px 0">{{if $bs}}Последняя копия: <b>{{backupWhen $bs.At}}</b> — {{if $bs.OK}}<span style="color:var(--ok)">✓ {{$bs.Dir}}, {{$bs.Msg}}</span>{{else}}<span style="color:var(--err)">✗ {{$bs.Msg}}</span>{{end}}{{else}}<span class="muted">Копий ещё не было.</span>{{end}}</p>
|
||||
<style>#bk-form label:not(.chk){display:block;margin:0}#bk-form label:not(.chk) input,#bk-form label:not(.chk) select{width:100%;box-sizing:border-box;margin-top:4px}</style>
|
||||
<form method="post" action="/admin/setup/backup" id="bk-form" style="display:grid;gap:10px;max-width:640px">
|
||||
<label class="chk" style="display:flex;align-items:center;gap:8px;font-weight:400"><input type="checkbox" name="enabled" style="width:auto"{{if $b.Enabled}} checked{{end}}> <b>Делать копии по расписанию</b></label>
|
||||
<div style="display:grid;grid-template-columns:2fr 1fr;gap:10px">
|
||||
<label>Хранилище (SMB)<input name="share" value="{{$b.Share}}" placeholder="//nas/backup"></label>
|
||||
<label>Папка<input name="dir" value="{{$b.Dir}}" placeholder="bj-backup"></label>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
|
||||
<label>Логин хранилища<input name="user" value="{{$b.User}}" autocomplete="off"></label>
|
||||
<label>Пароль {{if $b.PasswordSet}}<span class="muted">(задан; пусто — оставить)</span>{{end}}<input name="password" type="password" autocomplete="new-password"></label>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
|
||||
<label>Как часто<select name="every_hours">{{$e := $b.EveryHours}}{{if not $e}}{{$e = 6}}{{end}}{{range $h := intList 1 2 3 4 6 8 12 24}}<option value="{{$h}}"{{if eq $h $e}} selected{{end}}>{{if eq $h 24}}раз в сутки (02:00){{else}}каждые {{$h}} ч{{end}}</option>{{end}}</select></label>
|
||||
<label>Хранить, дней<input name="keep_days" type="number" min="1" max="365" value="{{if $b.KeepDays}}{{$b.KeepDays}}{{else}}14{{end}}"></label>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
||||
<button class="btn" type="submit">Сохранить</button>
|
||||
<button class="btn" type="button" id="bk-test" style="background:var(--card);color:var(--text);border:1px solid var(--border)">Проверить доступ</button>
|
||||
<button class="btn" type="button" id="bk-run" style="background:var(--card);color:var(--text);border:1px solid var(--border)">Сделать копию сейчас</button>
|
||||
<button class="btn" type="button" id="bk-list" style="background:var(--card);color:var(--text);border:1px solid var(--border)">Копии на хранилище</button>
|
||||
</div>
|
||||
<div id="bk-out" class="muted" style="font-size:13px"></div>
|
||||
</form>
|
||||
<script>
|
||||
(function(){
|
||||
var f=document.getElementById('bk-form'), out=document.getElementById('bk-out');
|
||||
function esc(s){return String(s).replace(/[&<>"]/g,function(c){return{'&':'&','<':'<','>':'>','"':'"'}[c]})}
|
||||
function nice(n){ var m=n.match(/^(\d{4})-(\d{2})-(\d{2})_(\d{2})(\d{2})$/); return m? m[3]+'.'+m[2]+'.'+m[1]+' '+m[4]+':'+m[5] : n; }
|
||||
function post(url, withForm){
|
||||
var p=new URLSearchParams();
|
||||
if(withForm){ ['share','dir','user','password'].forEach(function(n){ var v=f[n].value.trim(); if(v) p.set(n,v); }); }
|
||||
return fetch(url,{method:'POST',body:p,cache:'no-store'}).then(function(r){return r.json()});
|
||||
}
|
||||
document.getElementById('bk-test').onclick=function(){ out.textContent='Проверяю доступ…';
|
||||
post('/admin/setup/backup/test',true).then(function(d){ out.innerHTML=d.ok?'<span style="color:var(--ok)">✓ '+esc(d.output)+'</span>':'<span style="color:var(--err)">✗ '+esc(d.error)+'</span>'; }); };
|
||||
document.getElementById('bk-run').onclick=function(){ out.textContent='Запускаю копию…';
|
||||
post('/admin/setup/backup/run',false).then(function(d){ if(!d.ok){out.innerHTML='<span style="color:var(--err)">✗ '+esc(d.error)+'</span>';return}
|
||||
out.textContent='Копия делается (обычно до минуты)…'; var t0=Date.now();
|
||||
var iv=setInterval(function(){ fetch('/admin/setup/backup/status',{cache:'no-store'}).then(function(r){return r.json()}).then(function(s){
|
||||
var b=s.backup; if(b && Date.parse(b.at)>=t0-60000){ clearInterval(iv); out.innerHTML=b.ok?'<span style="color:var(--ok)">✓ копия готова: '+esc(b.dir)+', '+esc(b.msg)+'</span>':'<span style="color:var(--err)">✗ '+esc(b.msg)+'</span>'; }
|
||||
if(Date.now()-t0>600000) clearInterval(iv); }); },3000); }); };
|
||||
document.getElementById('bk-list').onclick=function(){ out.textContent='Читаю список…';
|
||||
post('/admin/setup/backup/list',true).then(function(d){ if(!d.ok){out.innerHTML='<span style="color:var(--err)">✗ '+esc(d.error)+'</span>';return}
|
||||
if(!d.list||!d.list.length){ out.textContent='Копий на хранилище нет.'; return; }
|
||||
out.innerHTML='Копий: <b>'+d.list.length+'</b>. «Проверить» скачивает копию и сверяет контрольные суммы — ничего не восстанавливает.'+
|
||||
'<div style="display:grid;gap:4px;margin-top:6px">'+d.list.slice(0,60).map(function(n){
|
||||
return '<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap"><code>'+esc(nice(n))+'</code><button type="button" class="btn bk-verify" data-name="'+esc(n)+'" style="padding:2px 10px;font-size:12px;background:var(--card);color:var(--text);border:1px solid var(--border)">Проверить</button><span class="bk-vres" style="font-size:12px"></span></div>';
|
||||
}).join('')+'</div>';
|
||||
out.querySelectorAll('.bk-verify').forEach(function(btn){ btn.onclick=function(){
|
||||
var res=btn.nextElementSibling; res.textContent='скачиваю и проверяю…'; btn.disabled=true;
|
||||
var p=new URLSearchParams(); ['share','dir','user','password'].forEach(function(k){ var v=f[k].value.trim(); if(v) p.set(k,v); }); p.set('name',btn.dataset.name);
|
||||
fetch('/admin/setup/backup/verify',{method:'POST',body:p,cache:'no-store'}).then(function(r){return r.json()}).then(function(v){
|
||||
btn.disabled=false; res.innerHTML=v.ok?'<span style="color:var(--ok)">✓ '+esc(v.output)+'</span>':'<span style="color:var(--err)">✗ '+esc(v.error)+'</span>'; });
|
||||
}; }); }); };
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
{{template "backupCard" .}}
|
||||
</section>
|
||||
|
||||
<section class="settings-section" id="sec-license">
|
||||
|
||||
Reference in New Issue
Block a user