c6f83e212e
Каждая «Залить сейчас» оставляла строку навсегда (до перезапуска). Теперь ручная заливка заменяет прежнюю ручную; клиенты постоянного заполнения остаются. Пропущенные бумаги — свёрнутым списком «пропущено бумаг: N». Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
1102 lines
128 KiB
HTML
1102 lines
128 KiB
HTML
{{define "content"}}
|
||
<script>if(/^#(sec-)?(tests|units)$/.test(location.hash)) location.replace('/admin/status#diag');</script>
|
||
{{if .Flash}}<div style="padding:12px 16px;background:var(--ok-weak);background:var(--ok-weak);border-radius:8px;margin-bottom:16px">{{.Flash}}</div>{{end}}
|
||
|
||
<div class="settings">
|
||
{{/* ===== Боковая навигация разделов с индикаторами ===== */}}
|
||
<nav class="settings-nav">
|
||
<a href="/admin/wizard" class="snav-wiz" style="display:block;margin:0 0 10px;padding:8px 12px;border:1px dashed var(--border-strong,#c9cdd8);border-radius:10px;font-size:13.5px;text-decoration:none;color:var(--accent)">↺ Мастер настройки — пройти по шагам</a>
|
||
<button data-sec="overview" class="active"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="3"/></svg></span>Обзор</button>
|
||
<button data-sec="db"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5.5" rx="7" ry="2.6"/><path d="M5 5.5v13c0 1.4 3.1 2.6 7 2.6s7-1.2 7-2.6v-13"/><path d="M5 12c0 1.4 3.1 2.6 7 2.6s7-1.2 7-2.6"/></svg></span>База данных<span class="ind {{if .Settings.Postgres.DSN}}ok{{else}}err{{end}}"></span></button>
|
||
{{if mod "quik-mnp"}}<button data-sec="fancy"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M4 8h12M12 4l4 4-4 4"/><path d="M20 16H8M12 20l-4-4 4-4"/></svg></span>Обмен с DEPO<span class="ind {{if .Settings.Fancy.DepoTablesCheckOK}}ok{{else if .Settings.Fancy.Server}}warn{{end}}"></span></button>
|
||
<button data-sec="quik"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M4 20V4M4 20h16"/><rect x="7" y="12" width="2.6" height="5"/><rect x="12" y="8" width="2.6" height="9"/><rect x="17" y="14" width="2.6" height="3"/></svg></span>QUIK МНП<span class="ind {{if .Settings.Quik.Verified}}ok{{else if .Settings.Quik.Server}}warn{{end}}"></span></button>{{end}}
|
||
{{if or (mod "quik-export") (mod "npr")}}<button data-sec="quikexport"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M4 17l5-5 4 4 7-8"/><path d="M14 8h6v6"/></svg></span>Экспорт QUIK<span class="ind {{.QuikExport.Ind}}"></span></button>{{end}}
|
||
<button data-sec="users"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="8" r="3.5"/><path d="M3 20c0-3.3 2.7-6 6-6s6 2.7 6 6"/><circle cx="17" cy="9" r="2.5"/><path d="M15.5 14.5c2.8.3 5.5 2.5 5.5 5.5"/></svg></span>Пользователи и вход<span class="ind {{if .Users.Open}}warn{{else}}ok{{end}}"></span></button>
|
||
<button data-sec="crypto"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></svg></span>Криптография<span class="ind {{if cryptoReady}}ok{{else}}warn{{end}}"></span></button>
|
||
<button data-sec="nsd"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M3 21h18M4 21V10M20 21V10M3 10l9-6 9 6M8 21v-7M12 21v-7M16 21v-7"/></svg></span>НРД<span class="ind {{if .Settings.NSD.IGWBaseURL}}ok{{else}}warn{{end}}"></span></button>
|
||
{{if mod "web-cabinet"}}<button data-sec="cabinet"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="9" r="3.2"/><path d="M6 19c.6-3 3-4.6 6-4.6s5.4 1.6 6 4.6"/><rect x="3" y="4" width="18" height="16" rx="2"/></svg></span>Кабинет клиента<span class="ind {{if .Settings.LK.APIKey}}ok{{else}}warn{{end}}"></span></button>{{end}}
|
||
{{if mod "esia-finance"}}<button data-sec="lk"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><circle cx="9" cy="11" r="2"/><path d="M6 16c.4-1.4 1.6-2.2 3-2.2s2.6.8 3 2.2"/><path d="M15 10h4M15 13h3"/></svg></span>ЕСИА Финанс<span class="ind {{if .Settings.LK.PublicURL}}ok{{else}}warn{{end}}"></span></button>{{end}}
|
||
{{if mod "onec-back"}}<button data-sec="onec"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="10" cy="6" rx="6" ry="2.5"/><path d="M4 6v10c0 1.4 2.7 2.5 6 2.5"/><path d="M16 6v4"/><path d="M4 11c0 1.4 2.7 2.5 6 2.5"/><path d="M14 16h7M18 13l3 3-3 3"/></svg></span>1С: бэк-офис<span class="ind {{if .Settings.OneC.CheckOK}}ok{{else}}warn{{end}}"></span></button>{{end}}
|
||
<button data-sec="alerts"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/></svg></span>Оповещения<span class="ind {{if or .Settings.Alerts.SMTP.Enabled .Settings.Alerts.Zabbix.Enabled}}ok{{end}}"></span></button>
|
||
<button data-sec="update"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20V8M6 12l6-6 6 6"/><path d="M5 4h14"/></svg></span>Обновления{{if .UpdatesAvailable}}<span class="ind warn"></span>{{end}}</button>
|
||
<button data-sec="backup"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="6" rx="7" ry="2.5"/><path d="M5 6v6c0 1.4 3.1 2.5 7 2.5s7-1.1 7-2.5V6"/><path d="M12 17v4M9 19l3 2 3-2"/></svg></span>Резервное копирование{{$bs := backupStatus}}{{if and .Settings.Backup.Enabled $bs}}{{if not $bs.OK}}<span class="ind err"></span>{{end}}{{else if not .Settings.Backup.Enabled}}<span class="ind warn"></span>{{end}}</button>
|
||
<button data-sec="server"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="7" rx="1.5"/><rect x="3" y="13" width="18" height="7" rx="1.5"/><path d="M7 7.5h.01M7 16.5h.01"/></svg></span>Сервер: HTTPS, время, сеть{{if or (not .Settings.HTTPS.RedirectOn) (not timeSynced)}}<span class="ind warn"></span>{{end}}</button>
|
||
<button data-sec="license"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="12" r="4"/><path d="M12 12h9M18 12v3M15 12v2"/></svg></span>Лицензия{{if .License.Present}}<span class="ind {{if .License.Valid}}ok{{else}}err{{end}}"></span>{{end}}</button>
|
||
<button data-sec="branding"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><circle cx="8.5" cy="10" r="1.5"/><path d="M21 16l-5-5L5 19"/></svg></span>Оформление отчётов{{if .Settings.Branding.LogoDataURI}}<span class="ind ok"></span>{{end}}</button>
|
||
{{if devOn}} <button data-sec="advanced"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></svg></span>Перспективные{{if .AdvancedUnlocked}}<span class="ind ok"></span>{{end}}</button>{{end}}
|
||
</nav>
|
||
|
||
<div class="settings-body">
|
||
{{if .FromWizard}}<div class="card" style="background:var(--accent-weak);margin-bottom:12px;display:flex;justify-content:space-between;gap:8px;flex-wrap:wrap;align-items:center"><span>Вы настраиваете модуль из мастера настройки.</span><a href="/admin/wizard?step=11" class="btn" style="text-decoration:none">← Вернуться в мастер</a></div>{{end}}
|
||
|
||
{{/* ============ ОБЗОР ============ */}}
|
||
<section class="settings-section active" id="sec-overview">
|
||
<h1>Обзор</h1>
|
||
<div class="card">
|
||
<h2>Готовность системы: {{.ReadyCount}} из {{.TotalCount}}</h2>
|
||
<div class="grid" style="margin-top:12px">
|
||
{{range .Readiness}}
|
||
<div class="stat">
|
||
<div><span class="dot {{if .Configured}}ok{{else}}err{{end}}"></span><strong>{{if .Title}}{{.Title}}{{else}}{{.Name}}{{end}}</strong></div>
|
||
<div class="muted" style="font-size:12px;margin-top:4px">{{if .Configured}}настроено{{else}}не настроено{{end}}</div>
|
||
</div>
|
||
{{end}}
|
||
</div>
|
||
</div>
|
||
<div class="card" id="versions">
|
||
<h2>Версии систем</h2>
|
||
<p class="muted">bj и системы, с которыми он работает. Сведения на {{msk .Versions.At}}.</p>
|
||
<div style="overflow-x:auto"><table style="margin-top:8px">
|
||
<thead><tr><th>Система</th><th>Версия</th>{{if devOn}}<th>Подробности</th>{{end}}</tr></thead>
|
||
<tbody>{{range .Versions.Items}}{{if or devOn (clientVer .Key)}}<tr>
|
||
<td>{{if and .Section (ne .Section "overview")}}<a href="#{{.Section}}" onclick="var b=document.querySelector('.settings-nav [data-sec="{{.Section}}"]');if(b){b.click();return false}">{{verTitle .}}</a>{{else}}{{verTitle .}}{{end}}</td>
|
||
<td>{{if .Version}}<strong>{{.Version}}</strong>{{else}}<span class="muted">не определена</span>{{end}}</td>
|
||
{{if devOn}}<td class="muted" style="font-size:12.5px">{{.Detail}}{{with .Err}} <span style="color:var(--err)">{{.}}</span>{{end}}</td>{{end}}
|
||
</tr>{{end}}{{end}}</tbody>
|
||
</table></div>
|
||
<form method="post" action="/admin/setup/versions/refresh" style="margin-top:10px"><input type="hidden" name="section" value="versions"><button type="submit" class="btn btn-secondary">Обновить сведения</button></form>
|
||
</div>
|
||
</section>
|
||
|
||
{{/* ============ БАЗА ДАННЫХ ============ */}}
|
||
<section class="settings-section" id="sec-db">
|
||
<h1>База данных</h1>
|
||
{{template "verline" (.Versions.Get "postgres")}}
|
||
<div class="card">
|
||
<h2><span class="dot {{if .Settings.Postgres.DSN}}ok{{else}}err{{end}}"></span>PostgreSQL</h2>
|
||
<p class="muted">Здесь bj хранит переводы, журнал и настройки. Сейчас: {{if .Settings.Postgres.DSN}}<b style="color:var(--ok)">подключена</b>{{else}}<b style="color:var(--err)">не подключена</b> — данные не сохраняются{{end}}.</p>
|
||
{{if and devOn (not .Settings.Postgres.DSN)}}
|
||
<div style="background:var(--card-2);border:1px solid var(--accent);border-radius:10px;padding:16px;margin-top:12px">
|
||
<h3 style="margin:0 0 8px">Подключить автоматически</h3>
|
||
<p class="muted" style="margin:0 0 12px">Поднимем локальный PostgreSQL в контейнере, применим миграции и запишем DSN. Для дев-стенда. Для прода — укажите свой DSN ниже.</p>
|
||
<form method="post" action="/admin/setup/postgres/quick-start" style="margin:0">
|
||
<button type="submit" class="btn">Поднять локальный PostgreSQL</button>
|
||
</form>
|
||
</div>
|
||
{{end}}
|
||
<details {{if not .Settings.Postgres.DSN}}open{{end}} style="margin-top:14px">
|
||
<summary style="cursor:pointer;color:var(--accent);font-size:13px">{{if .Settings.Postgres.DSN}}Изменить подключение{{else}}…или ввести строку подключения вручную{{end}}</summary>
|
||
<form method="post" action="/admin/setup/postgres" style="margin-top:12px;display:grid;gap:10px;max-width:640px">
|
||
<label for="db-dsn">Строка подключения к базе</label>
|
||
<input type="text" id="db-dsn" name="dsn" value="{{.Settings.Postgres.DSN}}" placeholder="postgres://пользователь:пароль@адрес:5432/bj?sslmode=disable">
|
||
<p class="muted" style="margin:0">При сохранении bj проверит подключение.</p>
|
||
<div><button type="submit" class="btn">Сохранить и проверить</button></div>
|
||
</form>
|
||
</details>
|
||
</div>
|
||
</section>
|
||
|
||
{{if mod "quik-mnp"}}
|
||
{{/* ============ ОБМЕН FANCY (MSSQL) — перспективный модуль ============ */}}
|
||
<section class="settings-section" id="sec-fancy">
|
||
<h1>Обмен с DEPO</h1>
|
||
{{template "verline" (.Versions.Get "depo")}}
|
||
{{template "fancyStatus" .}}
|
||
{{$fok := and .Settings.Fancy.Server .Settings.Fancy.DepoTablesCheckOK .Settings.Fancy.DepoJobCheckOK}}
|
||
<div class="card">
|
||
<details class="adv-box" {{if not $fok}}open{{end}}>
|
||
<summary><span class="sum-h">Перенастроить</span><span class="muted">подключение к базе DEPO, торговые счета, трансферные таблицы, признак ИИС, кто запускает обработку — если что-то изменилось</span></summary>
|
||
{{template "fancySection" .}}
|
||
</details>
|
||
</div>
|
||
|
||
</section>
|
||
|
||
{{/* ============ QUIK МНП ============ */}}
|
||
<section class="settings-section" id="sec-quik">
|
||
<h1>QUIK — Модуль неторговых поручений</h1>
|
||
{{template "quikStatus" .}}
|
||
<div class="card">
|
||
<details class="adv-box" {{if not (and .Settings.Quik.Server .Settings.Quik.Verified)}}open{{end}}>
|
||
<summary><span class="sum-h">Перенастроить</span><span class="muted">подключение к модулю, как bj работает с QUIK, поручения из терминала, расширенные настройки, проверка модуля</span></summary>
|
||
{{template "quikSection" .}}
|
||
{{if .Settings.Quik.Server}}{{template "mnpCheck" .}}{{end}}
|
||
</details>
|
||
</div>
|
||
</section>
|
||
|
||
{{end}}{{/* /AdvancedUnlocked — конец перспективных модулей Fancy/QUIK */}}
|
||
|
||
{{/* ============ ПОЛЬЗОВАТЕЛИ И ВХОД ============ */}}
|
||
<section class="settings-section" id="sec-users">
|
||
{{$U := .Users}}{{$A := .Settings.Auth}}
|
||
<h1>Пользователи и вход</h1>
|
||
<p class="muted">Кто может входить в bj и что ему разрешено. Сотрудник входит своей учётной записью bj или доменной (Active Directory) — тогда пароль проверяет домен.</p>
|
||
{{if $U.Open}}<div class="card callout" style="padding:10px 14px;margin-bottom:8px;background:color-mix(in srgb,var(--warn) 10%,var(--card));display:flex;gap:8px;align-items:center"><span class="dot warn"></span><span><b>Вход пока не требуется:</b> пользователи не созданы. Создайте первого администратора — после этого все страницы потребуют входа.</span></div>{{end}}
|
||
|
||
<div class="card">
|
||
<h2>Сотрудники ({{len $U.Users}})</h2>
|
||
{{if $U.Users}}<div class="usr-list">
|
||
{{range $U.Users}}
|
||
<details class="usr">
|
||
<summary>
|
||
<span class="usr-av" aria-hidden="true">{{avatarLetter (or .Name .Login)}}</span>
|
||
<span class="usr-main"><b>{{if .Name}}{{.Name}}{{else}}{{.Login}}{{end}}</b><small>{{.Login}}{{if .Email}} · {{.Email}}{{end}} · {{if eq .Source "ad"}}доменная учётная запись{{else}}пароль в bj{{end}} · {{if not .LastLogin.IsZero}}вход {{msk .LastLogin}}{{else}}ещё не входил{{end}}</small></span>
|
||
{{if not .Active}}<span class="badge rejected">заблокирован</span>{{end}}
|
||
<span class="usr-role">{{if $U.RoleShown .Role}}{{roleTitle .Role}}{{else}}{{$U.RoleNote .Role}}{{end}}</span>
|
||
<span class="usr-edit">Изменить</span>
|
||
</summary>
|
||
<form method="post" action="/admin/setup/users" class="usr-form">
|
||
<input type="hidden" name="login" value="{{.Login}}"><input type="hidden" name="source" value="{{.Source}}">
|
||
<label>Имя<input type="text" name="name" value="{{.Name}}" autocomplete="off"></label>
|
||
<label>E-mail<input type="text" name="email" value="{{.Email}}" autocomplete="off"></label>
|
||
<label>Роль<select name="role">{{$r := .Role}}{{if not ($U.RoleShown $r)}}<option value="{{$r}}" selected>{{$U.RoleNote $r}}</option>{{end}}{{range $U.Roles}}<option value="{{.Key}}" {{if eq .Key $r}}selected{{end}}>{{.Title}}</option>{{end}}</select></label>
|
||
{{if eq .Source "local"}}<label>Новый пароль<input type="password" name="password" placeholder="пусто — оставить прежний" autocomplete="new-password"></label>{{else}}<div class="usr-note">Пароль проверяет домен{{if .ADGroups}} · группы: {{range $i,$g := .ADGroups}}{{if $i}}, {{end}}{{$g}}{{end}}{{end}}{{if .Note}} · {{.Note}}{{end}}</div>{{end}}
|
||
<div class="usr-checks">{{$m := .Modules}}{{range $U.Modules}}<label><input type="checkbox" name="modules" value="{{.Key}}" {{if has $m .Key}}checked{{end}}>{{.Title}}</label>{{end}}
|
||
<label><input type="checkbox" name="active" value="1" {{if .Active}}checked{{end}}>может входить</label></div>
|
||
<div class="usr-act"><button class="btn" type="submit">Сохранить</button>
|
||
<button class="usr-del" type="submit" formaction="/admin/setup/users/delete" onclick="return confirm('Удалить сотрудника {{.Login}}? Войти он больше не сможет.')">Удалить сотрудника</button></div>
|
||
</form>
|
||
</details>
|
||
{{end}}</div>{{else}}<p class="muted">Пока никого — добавьте сотрудника ниже.</p>{{end}}
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>{{if $U.Open}}Создать первого администратора{{else}}Добавить сотрудника{{end}}</h2>
|
||
{{template "accountsAdd" (withRet . "/admin/setup")}}
|
||
{{template "accountsAD" (withRet . "/admin/setup")}}
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>Активные сессии ({{len $U.Sessions}})</h2>
|
||
{{if $U.Sessions}}<div class="ses-list">
|
||
{{range $U.Sessions}}<div class="ses"><span class="ses-main"><b>{{.Login}}</b><small>вход {{msk .Created}} · активность {{msk .Seen}} · <code>{{.IP}}</code> · <span title="{{.UA}}">{{.Device}}</span></small></span>
|
||
<form method="post" action="/admin/setup/users/session-drop" style="margin:0"><input type="hidden" name="id" value="{{.ID}}"><button class="btn btn-secondary btn-sm" type="submit">Завершить</button></form></div>{{end}}
|
||
</div>{{else}}<p class="muted">Сессий нет.</p>{{end}}
|
||
</div>
|
||
|
||
<div class="card">
|
||
<details class="adv-box">
|
||
<summary><span class="sum-h">Расширенные настройки</span><span class="muted">роли и права, домен вручную: контроллер, сертификат, группы, перепроверка, время сессии</span></summary>
|
||
<h3 class="adv-h">Роли и права</h3>
|
||
<table style="font-size:12.5px"><thead><tr><th>Роль</th><th>Что разрешено</th></tr></thead><tbody>
|
||
{{range $U.Roles}}<tr><td style="white-space:nowrap"><b>{{.Title}}</b>{{if devOn}}<br><code style="font-size:11px">{{.Key}}</code>{{end}}</td><td>{{.Desc}}</td></tr>{{end}}
|
||
{{range $U.Hidden}}<tr class="muted"><td style="white-space:nowrap">{{.Title}}</td><td>Недоступна: в лицензии нет модуля «{{.Missing}}».</td></tr>{{end}}
|
||
</tbody></table>
|
||
<p class="muted" style="font-size:12px;margin-top:6px">{{if $U.Modules}}Подключённые модули ({{range $i, $m := $U.Modules}}{{if $i}}, {{end}}«{{$m.Title}}»{{end}}) можно выдать любому пользователю дополнительно к роли. {{end}}Роли и модули показываются по лицензии: права модуля, которого нет в лицензии, не действуют. Настройки и пользователи — только администратор.</p>
|
||
<h3 class="adv-h">Вход через Active Directory</h3>
|
||
<p class="muted">bj подключается к контроллеру домена и проверяет пароль пользователя (LDAP bind его же учётной записью). Роль назначается по членству в группах AD, включая вложенные. Пользователь появляется в списке при первом входе. Чтобы изменения групп действовали без повторного входа, укажите сервисную учётную запись только на чтение: bj будет перечитывать группы доменных пользователей по расписанию, менять роль и снимать доступ у удалённых из групп.</p>
|
||
<form method="post" action="/admin/setup/auth" style="display:grid;gap:10px;max-width:820px;margin-top:10px">
|
||
<label style="display:flex;gap:8px;align-items:center;font-weight:600;margin:0"><input type="checkbox" name="ad_enabled" value="1" {{if $A.ADEnabled}}checked{{end}}> Разрешить вход доменной учётной записью</label>
|
||
<div style="display:grid;grid-template-columns:2fr 1fr;gap:10px">
|
||
<label>Контроллер домена <input type="text" name="ad_server" value="{{$A.ADServer}}" placeholder="ldaps://dc1.corp.example.ru:636" style="width:100%"></label>
|
||
<label>Домен (суффикс UPN) <input type="text" name="ad_domain" value="{{$A.ADDomain}}" placeholder="corp.example.ru" style="width:100%"></label>
|
||
</div>
|
||
<label>База поиска (Base DN) <input type="text" name="ad_base_dn" value="{{$A.ADBaseDN}}" placeholder="пусто — возьмётся с контроллера" style="width:100%"></label>
|
||
<div style="display:flex;gap:18px;flex-wrap:wrap">
|
||
<label style="display:flex;gap:6px;align-items:center;font-weight:400;margin:0"><input type="checkbox" name="ad_starttls" value="1" {{if $A.ADStartTLS}}checked{{end}}> StartTLS для ldap://</label>
|
||
<label style="display:flex;gap:6px;align-items:center;font-weight:400;margin:0"><input type="checkbox" name="ad_skip_verify" value="1" {{if $A.ADSkipVerify}}checked{{end}}> Не проверять сертификат контроллера</label>
|
||
</div>
|
||
<label>Сертификат центра сертификации домена (PEM) <textarea name="ad_ca_pem" rows="3" style="width:100%;font-family:ui-monospace,monospace;font-size:11px" placeholder="-----BEGIN CERTIFICATE-----">{{$A.ADCAPEM}}</textarea></label>
|
||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:10px">
|
||
<label>Служебная учётная запись <input type="text" name="ad_bind_user" value="{{$A.ADBindUser}}" placeholder="svc-bj@corp.example.ru" autocomplete="off" style="width:100%"></label>
|
||
<label>Её пароль <input type="password" name="ad_bind_password" placeholder="{{if $A.ADBindPassword}}задан — пусто, чтобы не менять{{else}}пароль{{end}}" autocomplete="new-password" style="width:100%"></label>
|
||
<label>Перепроверять группы, минут <input type="number" name="ad_recheck_minutes" min="5" max="1440" value="{{if $A.ADRecheckMinutes}}{{$A.ADRecheckMinutes}}{{else}}15{{end}}" style="width:100%"></label>
|
||
</div>
|
||
<div>
|
||
<div style="font-weight:600;font-size:13.5px;margin:4px 0 6px">Группы домена → роли <span class="muted" style="font-weight:400;font-size:12.5px">имя группы или полный DN; действует первое совпадение сверху</span></div>
|
||
<div id="ad-groups" style="display:grid;gap:6px">
|
||
{{range $A.ADGroups}}<div style="display:grid;grid-template-columns:2fr 1fr;gap:8px"><input type="text" name="ad_group" value="{{.Group}}" placeholder="BJ-Operators"><select name="ad_role">{{$r := .Role}}{{if not ($U.RoleShown $r)}}<option value="{{$r}}" selected>{{$U.RoleNote $r}} (группа не действует)</option>{{end}}{{range $U.Roles}}<option value="{{.Key}}" {{if eq .Key $r}}selected{{end}}>{{.Title}}</option>{{end}}</select></div>{{end}}
|
||
{{range $i := list "1" "2"}}<div style="display:grid;grid-template-columns:2fr 1fr;gap:8px"><input type="text" name="ad_group" value="" placeholder="имя группы AD"><select name="ad_role">{{range $U.Roles}}<option value="{{.Key}}">{{.Title}}</option>{{end}}</select></div>{{end}}
|
||
</div>
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
|
||
<label>Роль, если ни одна группа не совпала <select name="ad_default_role" style="width:100%"><option value="">отказать во входе</option>{{if and $A.ADDefaultRole (not ($U.RoleShown $A.ADDefaultRole))}}<option value="{{$A.ADDefaultRole}}" selected>{{$U.RoleNote $A.ADDefaultRole}} (вход будет отклонён)</option>{{end}}{{range $U.Roles}}<option value="{{.Key}}" {{if eq .Key $A.ADDefaultRole}}selected{{end}}>{{.Title}}</option>{{end}}</select></label>
|
||
<label>Выход при бездействии, часов <input type="number" name="session_hours" min="1" max="24" value="{{if $A.SessionHours}}{{$A.SessionHours}}{{else}}8{{end}}" style="width:100%"></label>
|
||
</div>
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap"><button class="btn" type="submit">Сохранить</button></div>
|
||
</form>
|
||
<form method="post" action="/admin/setup/auth/test" style="display:grid;grid-template-columns:1fr 1fr auto;gap:10px;max-width:820px;margin-top:12px;align-items:end">
|
||
<label>Проверить вход: логин домена <input type="text" name="test_login" placeholder="ivanov" style="width:100%" autocomplete="off"></label>
|
||
<label>Пароль <input type="password" name="test_password" style="width:100%" autocomplete="off"></label>
|
||
<button class="btn btn-secondary" type="submit">Проверить</button>
|
||
</form>
|
||
<p class="muted" style="font-size:12px;margin-top:6px">Без логина и пароля проверяется только доступность контроллера. С логином — вход, группы и вычисленная роль. Пароль проверки никуда не сохраняется.</p>
|
||
{{$R := $U.ADRecheck}}
|
||
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:10px;font-size:12.5px">
|
||
{{if and $A.ADEnabled $A.ADBindUser $A.ADBindPassword}}
|
||
<span><span class="dot {{if $R.Err}}err{{else if $R.At.IsZero}}warn{{else}}ok{{end}}"></span>Перепроверка групп каждые {{if $A.ADRecheckMinutes}}{{$A.ADRecheckMinutes}}{{else}}15{{end}} мин: {{if $R.At.IsZero}}ещё не выполнялась{{else}}последняя {{msk $R.At}}, проверено {{$R.Checked}}, изменений {{$R.Changed}}{{if $R.Err}} — <b>ошибка:</b> {{$R.Err}}{{end}}{{end}}</span>
|
||
<form method="post" action="/admin/setup/auth/recheck"><button class="btn btn-secondary" type="submit">Перепроверить сейчас</button></form>
|
||
{{else}}<span class="muted"><span class="dot warn"></span>Перепроверка групп выключена: роль доменного пользователя меняется только при следующем входе. Укажите сервисную учётную запись выше.</span>{{end}}
|
||
</div>
|
||
{{if devOn}}<p class="muted" style="font-size:12.5px;margin-top:14px">Если потерян доступ администратора — удалите файл <code>/var/lib/bj/.bj/users.json</code> на сервере: система вернётся в режим первичной настройки.</p>{{else}}<p class="muted" style="font-size:12.5px;margin-top:14px">Если потерян доступ администратора — обратитесь в поддержку ZETIT.</p>{{end}}
|
||
</details>
|
||
</div>
|
||
</section>
|
||
|
||
{{/* ============ ЭКСПОРТ БИРЖЕВОЙ ИНФОРМАЦИИ (QUIK) ============ */}}
|
||
{{if or (mod "quik-export") (mod "npr")}}
|
||
<section class="settings-section" id="sec-quikexport">
|
||
{{if mod "quik-export"}}{{.QuikExport.HTML}}{{end}}
|
||
|
||
{{/* модуль НПР/УДС работает поверх базы экспорта QUIK — его настройки здесь же */}}
|
||
{{if mod "npr"}}<div class="card" id="sec-npr">
|
||
<h2><span class="dot {{if .Settings.NPR.Enabled}}ok{{else}}warn{{end}}"></span>Модуль «НПР / УДС» (риск-монитор по данным QUIK)</h2>
|
||
<p class="muted">Показывает НПР1 / НПР2 / УДС клиентов маржинальной торговли по базе экспорта QUIK (схема <code>npr</code>). Отдельно лицензируемый модуль (признак <code>npr</code>); при включении в меню появляется пункт «НПР / УДС». Подключение — логин только для чтения.</p>
|
||
{{template "verline" (.Versions.Get "bj-npr")}}
|
||
<form method="post" action="/admin/setup/npr" style="margin-top:10px;display:grid;gap:10px;max-width:640px">
|
||
<input type="hidden" name="section" value="sec-npr">
|
||
<label style="display:flex;gap:8px;align-items:center;font-weight:600"><input type="checkbox" name="enabled" value="1" {{if .Settings.NPR.Enabled}}checked{{end}}> Включить модуль</label>
|
||
<label>Строка подключения к базе экспорта QUIK <input type="text" name="dsn" value="{{.Settings.NPR.DSN}}" placeholder="postgres://quik_ro:пароль@localhost:5432/quik_export" style="width:100%;font-family:ui-monospace,monospace;font-size:12px"></label>
|
||
<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}}
|
||
</section>
|
||
{{end}}
|
||
|
||
{{/* ============ КРИПТОГРАФИЯ ============ */}}
|
||
<section class="settings-section" id="sec-crypto">
|
||
<h1>Криптография</h1>
|
||
{{template "cryptoStatus" .}}
|
||
<div class="card">
|
||
<details class="adv-box" {{if not (and cryptoReady orgKeyCert)}}open{{end}}>
|
||
<summary><span class="sum-h">Перенастроить</span><span class="muted">ключ организации, проверка СКЗИ, сертификаты НРД в справочнике шлюза, версии</span></summary>
|
||
{{if devOn}}<div style="margin-top:16px">{{template "verline" (.Versions.Get "crypto-service.jar")}}{{template "verline" (.Versions.Get "java")}}</div>{{end}}
|
||
|
||
<div class="card">
|
||
<h2><span class="dot {{if cryptoReady}}ok{{else}}warn{{end}}"></span>СКЗИ «Валидата Клиент L»</h2>
|
||
<p style="margin:6px 0">{{if cryptoReady}}<span style="color:var(--ok)">✓ СКЗИ установлена, ключ организации подключён — документы для НРД подписывает шлюз НРД.</span>{{else}}<span style="color:var(--warn)">⚠ Ключ организации не подключён — мастер настройки, шаг «Ключ подписи».</span>{{end}}</p>
|
||
{{if devOn}}<p class="muted">Активный профиль: <code>{{if .Settings.Crypto.Profile}}{{.Settings.Crypto.Profile}}{{else}}—{{end}}</code> · провайдер {{if and (not devOn) (or (eq .Settings.Crypto.Provider "stub") (eq .Settings.Crypto.Provider ""))}}<code>не настроен</code>{{else}}<code>{{.Settings.Crypto.Provider}}</code>{{end}}. {{if internalDocs}}Подробно — <a href="/admin/help/crypto">справка</a>{{end}}.</p>{{end}}
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:12px">
|
||
<form method="post" action="/admin/setup/crypto/check" style="margin:0"><button type="submit" class="btn btn-ok">✓ Проверить СКЗИ и ключ</button></form>
|
||
{{if devOn}}<form method="post" action="/admin/setup/crypto/test-sign" style="margin:0"><button type="submit" class="btn">✎ Подписать проверочный документ</button></form>
|
||
<form method="post" action="/admin/setup/restart-crypto" style="margin:0" onsubmit="return confirm('Перезапустить проверку ключа подписи? Займёт около 5 секунд.');"><button type="submit" class="btn btn-warn">↻ Перезапустить проверку ключа</button></form>
|
||
<form method="post" action="/admin/setup/restart-server" style="margin:0" onsubmit="return confirm('Перезапустить bj-server? Через 5-10 сек страница вернётся.');"><button type="submit" class="btn btn-secondary">↻ bj-server</button></form>
|
||
{{end}}
|
||
</div>
|
||
{{if devOn}}<details style="margin-top:14px">
|
||
<summary style="cursor:pointer;color:var(--accent);font-size:13px">Параметры провайдера (для совместимости)</summary>
|
||
<form method="post" action="/admin/setup/crypto" style="margin-top:12px;display:grid;gap:10px;max-width:640px">
|
||
<label>Провайдер</label>
|
||
<select name="provider">
|
||
{{if devOn}} <option value="stub" {{if eq .Settings.Crypto.Provider "stub"}}selected{{end}}>stub — без криптографии (демо)</option>{{end}}
|
||
<option value="validata" {{if eq .Settings.Crypto.Provider "validata"}}selected{{end}}>Валидата Клиент L</option>
|
||
</select>
|
||
{{/* путь к PKCS#11 не используется: bj работает с Валидатой через bj-crypto; в Валидате 6.0 модуля libvdpkcs11.so нет */}}
|
||
<input type="hidden" name="module_path" value="{{.Settings.Crypto.ModulePath}}">
|
||
<label>UDS-сокет</label>
|
||
<input type="text" name="socket_path" value="{{.Settings.Crypto.SocketPath}}" placeholder="/run/bj/crypto.sock">
|
||
<div><button type="submit" class="btn">Сохранить</button></div>
|
||
</form>
|
||
</details>{{end}}
|
||
</div>
|
||
|
||
{{/* Доверенные сертификаты НРД в справочнике ИШ */}}
|
||
<div class="card" id="sec-nsdcerts">
|
||
<h2><span class="dot {{if .NSDCerts.Pending}}warn{{else if .NSDCerts.LastRun.IsZero}}warn{{else}}ok{{end}}"></span>Сертификаты НРД в справочнике шлюза</h2>
|
||
<p class="muted">Сертификаты НРД приходят вместе с обновлениями bj. bj сам добавляет недостающие в справочник шлюза НРД и удаляет старые после срока, разрешённого НРД; перед каждым изменением — резервная копия.{{if devOn}}{{if .NSDCerts.Profile}} Профиль <code>{{.NSDCerts.Profile}}</code>.{{end}}{{end}}</p>
|
||
<table style="margin-top:12px">
|
||
<thead><tr><th>Сертификат</th><th>Контур</th><th>Действует</th><th>Статус</th></tr></thead>
|
||
<tbody>{{range .NSDCerts.Rows}}<tr>
|
||
<td><b>{{.Title}}</b>{{if devOn}}<div class="muted" style="font-size:11px">{{.SubjectCN}} · выдан: {{.IssuerCN}} · серийный <code>{{.Serial}}</code></div>{{end}}</td>
|
||
<td>{{contourRu .Contour}}</td>
|
||
<td style="white-space:nowrap">{{.ValidFrom}} — {{.ValidTo}}</td>
|
||
<td>{{if eq .Kind "ok"}}<span style="color:var(--ok);font-weight:600">✓ {{.Status}}</span>{{else if eq .Kind "add"}}<span style="color:var(--warn);font-weight:600">⬆ {{.Status}}</span>{{else}}<span class="muted">{{.Status}}</span>{{end}}{{if .RemoveNote}}<div style="font-size:11.5px;margin-top:3px;{{if .RemoveSerial}}color:var(--warn);font-weight:600{{else}}color:var(--muted){{end}}">{{.RemoveNote}}</div>{{end}}</td>
|
||
</tr>{{end}}</tbody>
|
||
</table>
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-top:10px">
|
||
<form method="post" action="/admin/setup/nsdcerts/check" style="margin:0"><input type="hidden" name="section" value="sec-nsdcerts"><button type="submit" class="btn">↻ Сверить со справочником</button></form>
|
||
<form method="post" action="/admin/setup/nsdcerts/apply" style="margin:0" onsubmit="return confirm('Применить изменения в справочнике шлюза НРД сейчас? Будет сделана резервная копия, шлюз перезапустится на несколько секунд.');"><input type="hidden" name="section" value="sec-nsdcerts"><button type="submit" class="btn btn-ok" {{if not .NSDCerts.Pending}}disabled{{end}}>▶ Применить сейчас{{if .NSDCerts.Pending}} ({{.NSDCerts.Pending}}){{end}}</button></form>
|
||
<form method="post" action="/admin/setup/nsdcerts" style="margin:0;display:flex;align-items:center;gap:8px"><input type="hidden" name="section" value="sec-nsdcerts"><label style="display:flex;align-items:center;gap:6px;cursor:pointer;margin:0"><input type="checkbox" name="auto" {{if not .NSDCerts.Manual}}checked{{end}} style="width:auto" onchange="this.form.submit()"> автоматически (при старте и каждые 6 ч)</label></form>
|
||
{{if not .NSDCerts.LastRun.IsZero}}<span class="muted" style="font-size:12px">сверено {{.NSDCerts.LastRun.Format "02.01.2006 15:04"}}</span>{{end}}
|
||
</div>
|
||
{{if and .NSDCerts.LastLog (or devOn (looksErr .NSDCerts.LastLog))}}<pre style="margin-top:10px;font-size:11.5px;white-space:pre-wrap">{{.NSDCerts.LastLog}}</pre>{{end}}
|
||
{{if and devOn .NSDCerts.Store}}<details style="margin-top:8px"><summary class="muted" style="cursor:pointer;font-size:12px">Содержимое справочника профиля ({{len .NSDCerts.Store}} записей)</summary>
|
||
<table style="margin-top:8px;font-size:12px"><thead><tr><th>Владелец</th><th>Издатель</th><th>Серийный</th><th>До</th><th>Хранилище</th></tr></thead><tbody>{{range .NSDCerts.Store}}<tr><td>{{if eq .Kind "crl"}}<span class="muted">CRL</span>{{else}}{{.SubjectCN}}{{if .SelfSigned}} <span class="muted">(УЦ)</span>{{end}}{{end}}</td><td>{{.IssuerCN}}</td><td><code>{{.Serial}}</code></td><td>{{.NotAfter}}{{if .CertNotAfter}}<div class="muted" style="font-size:11px" title="Срок закрытого ключа (PrivateKeyUsagePeriod): подписывать можно до {{.NotAfter}}. Срок самого сертификата — {{.CertNotAfter}}.">ключ; сертификат до {{.CertNotAfter}}</div>{{end}}</td><td>{{.Store}}</td></tr>{{end}}</tbody></table></details>{{end}}
|
||
{{if devOn}}<p class="muted" style="font-size:12px;margin-top:8px">Свои сертификаты можно положить в <code>/var/lib/bj/.bj/nsd-certs/*.cer</code> — они добавляются по тем же правилам и никогда не удаляются автоматически.</p>{{end}}
|
||
</div>
|
||
|
||
{{/* Сертификаты УЦ (старый способ: теперь сертификаты НРД приходят комплектом с bj) — только для разработки */}}
|
||
{{if devOn}}
|
||
<div class="card" id="sec-cacerts">
|
||
<h2><span class="dot {{if .Settings.CACerts.URLs}}ok{{else}}warn{{end}}"></span>Сертификаты УЦ (авто-загрузка)</h2>
|
||
<p class="muted">URL .cer-файлов УЦ НРД (<a href="https://www.nsd.ru/workflow/system/cryptography/" target="_blank" rel="noopener">nsd.ru</a>). Скачиваются, парсятся и сохраняются в <code>/var/lib/bj/ca-certs/</code>.</p>
|
||
<form method="post" action="/admin/setup/cacerts" style="margin-top:10px;display:grid;gap:10px;max-width:720px">
|
||
<input type="hidden" name="section" value="sec-cacerts">
|
||
<label>URL'ы (один на строку)</label>
|
||
<textarea name="urls" rows="3" style="font-family:ui-monospace,monospace;font-size:12px">{{range .Settings.CACerts.URLs}}{{.}}
|
||
{{end}}</textarea>
|
||
<label style="display:flex;align-items:center;gap:8px;cursor:pointer"><input type="checkbox" name="auto_update" {{if .Settings.CACerts.AutoUpdate}}checked{{end}} style="width:auto"> Авто-обновление раз в сутки</label>
|
||
<div style="display:flex;gap:8px"><button type="submit" class="btn">Сохранить</button></div>
|
||
</form>
|
||
<form method="post" action="/admin/setup/cacerts/fetch" style="margin-top:8px"><input type="hidden" name="section" value="sec-cacerts"><button type="submit" class="btn btn-ok">⬇ Скачать сейчас</button>{{if not .Settings.CACerts.LastFetch.IsZero}}<span class="muted" style="margin-left:10px">обновлено {{.Settings.CACerts.LastFetch.Format "02.01.2006 15:04"}}</span>{{end}}</form>
|
||
{{if .Settings.CACerts.FetchedCerts}}
|
||
<table style="margin-top:14px">
|
||
<thead><tr><th>Владелец</th><th>Тип</th><th>До</th><th>Статус</th></tr></thead>
|
||
<tbody>{{range .Settings.CACerts.FetchedCerts}}<tr><td>{{.SubjectCN}}</td><td><code>{{.Store}}</code></td><td>{{if not .NotAfter.IsZero}}{{.NotAfter.Format "02.01.2006"}}{{end}}</td><td>{{if .Error}}<span style="color:var(--err)" title="{{.Error}}">ошибка</span>{{else}}<span style="color:var(--ok)">ок</span>{{end}}</td></tr>{{end}}</tbody>
|
||
</table>
|
||
{{end}}
|
||
</div>
|
||
{{end}}
|
||
</details>
|
||
</div>
|
||
</section>
|
||
|
||
{{/* ============ НРД ============ */}}
|
||
<section class="settings-section" id="sec-nsd">
|
||
<h1>НРД</h1>
|
||
{{participantBanner}}
|
||
{{template "nsdStatus" .}}
|
||
<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>
|
||
{{template "igateInstall" .}}
|
||
</div>
|
||
<div class="card" id="nsd-participant">
|
||
<h2><span class="dot {{if participantOK}}ok{{else}}warn{{end}}"></span>Регистрация участника M2M</h2>
|
||
{{if participantOK}}<p style="margin:0 0 6px">✓ Анкета принята НРД — организация зарегистрирована в сервисе M2M.</p>{{end}}
|
||
<p class="muted">НРД требует анкету участника перед началом работы: без регистрации НРД не принимает запросы на переводы. bj заполняет анкету по реквизитам организации, даёт проверить и отправляет в НРД через шлюз (или её можно отправить письмом на <code>M2MOST@nsd.ru</code>).</p>
|
||
<details id="pf-box">
|
||
<summary style="cursor:pointer;color:var(--accent)">Открыть анкету участника</summary>
|
||
<iframe id="pf-frame" title="Анкета участника M2M" style="width:100%;height:1200px;border:1px solid var(--border);border-radius:10px;margin-top:10px;background:#fff" loading="lazy"></iframe>
|
||
<p class="muted" style="font-size:12px;margin:6px 0 0"><a href="/admin/setup/participant-form" target="_blank" rel="noopener">Открыть в отдельной вкладке →</a></p>
|
||
</details>
|
||
<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>
|
||
<p class="muted" style="margin-top:10px">{{if not .Settings.NSD.IGWBaseURL}}{{if devOn}}<code>mock-режим</code> — без реального ИШ.{{else}}ИШ не настроен — обмен с НРД не выполняется.{{end}}{{else}}Профиль <code>{{.Settings.NSD.Profile}}</code>, ИШ <code>{{.Settings.NSD.IGWBaseURL}}</code>.{{end}}</p>
|
||
<form method="post" action="/admin/setup/nsd" style="margin-top:12px;display:grid;gap:10px;max-width:680px">
|
||
<input type="hidden" name="keep_requisites" value="1">
|
||
<label>Профиль / контур</label>
|
||
<select name="profile" id="nsd-profile">
|
||
{{/* mock и GUEST — для разработчиков; у клиента — только контуры НРД */}}
|
||
<option value="" {{if eq .Settings.NSD.Profile ""}}selected{{end}}>{{if devOn}}— mock (демо) —{{else}}— не выбран —{{end}}</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-gost — GUEST, ГОСТ</option>
|
||
<option value="guest-rsa" {{if eq .Settings.NSD.Profile "guest-rsa"}}selected{{end}}>guest-rsa — GUEST, RSA</option>{{end}}
|
||
<option value="test3-gost" {{if eq .Settings.NSD.Profile "test3-gost"}}selected{{end}}>test3-gost — тестовый контур НРД (TEST3), ГОСТ</option>
|
||
<option value="test3-rsa" {{if eq .Settings.NSD.Profile "test3-rsa"}}selected{{end}}>test3-rsa — тестовый контур НРД (TEST3), RSA</option>
|
||
<option value="prod-gost" {{if eq .Settings.NSD.Profile "prod-gost"}}selected{{end}}>prod-gost — боевой контур НРД, ГОСТ</option>
|
||
<option value="prod-rsa" {{if eq .Settings.NSD.Profile "prod-rsa"}}selected{{end}}>prod-rsa — боевой контур НРД, RSA</option>
|
||
</select>
|
||
<label>URL ONYX (WSL) НРД</label>
|
||
<input type="text" name="igw_base_url" id="nsd-url" value="{{.Settings.NSD.IGWBaseURL}}" placeholder="автозаполнится по профилю">
|
||
<label>Ключевой контейнер</label>
|
||
<input type="text" name="key_container" id="nsd-container" value="{{.Settings.NSD.KeyContainer}}" placeholder="напр. TEST3_GOST_CONTAINER">
|
||
<label style="display:flex;align-items:flex-start;gap:9px;font-weight:400;margin-top:6px;cursor:pointer">
|
||
<input type="checkbox" name="igw_v904" style="width:auto;margin-top:3px" {{if .Settings.NSD.IGWv904}}checked{{end}}>
|
||
<span>ИШ версии <strong>107.0.0.904+</strong> (Astra 107.0.907+)<br>
|
||
<span class="muted" style="font-size:12px">С этой версии в <code>config.xml</code> добавляется <code>businessLine=MOST</code> (обязательно для M2M по Указанию ЦБ 7317-У). На версиях младше — не включать.</span></span>
|
||
</label>
|
||
<div><button type="submit" class="btn">Сохранить и проверить</button></div>
|
||
</form>
|
||
<script>
|
||
(function() {
|
||
var urls = {
|
||
"guest-gost": ["https://gost-gt.nsd.ru/onyx-ms/OnyxEdoWSService/OnyxEdo", "GUEST_GOST_CONTAINER"],
|
||
"guest-rsa": ["https://rsa-gt.nsd.ru/onyx-ms/OnyxEdoWSService/OnyxEdo", "GUEST_RSA_CONTAINER"],
|
||
"test3-gost": ["https://gost-t3.nsd.ru/onyx-ms/OnyxEdoWSService/OnyxEdo", "TEST3_GOST_CONTAINER"],
|
||
"test3-rsa": ["https://rsa-t3.nsd.ru/onyx-ms/OnyxEdoWSService/OnyxEdo", "TEST3_RSA_CONTAINER"],
|
||
"prod-gost": ["https://gost.nsd.ru/onyx-ms/OnyxEdoWSService/OnyxEdo", "PROD_GOST_CONTAINER"],
|
||
"prod-rsa": ["https://rsa.nsd.ru/onyx-ms/OnyxEdoWSService/OnyxEdo", "PROD_RSA_CONTAINER"]
|
||
};
|
||
var p = document.getElementById("nsd-profile"), u = document.getElementById("nsd-url"), c = document.getElementById("nsd-container");
|
||
if (p) p.addEventListener("change", function() {
|
||
var v = p.value;
|
||
if (urls[v] && (!u.value || confirm("Заменить URL и контейнер на дефолт для " + v + "?"))) { u.value = urls[v][0]; c.value = urls[v][1]; }
|
||
});
|
||
})();
|
||
</script>
|
||
</details>
|
||
</div>{{end}}
|
||
</details>
|
||
</div>
|
||
</section>
|
||
|
||
{{/* ============ ЛИЧНЫЙ КАБИНЕТ ============ */}}
|
||
{{/* ============ КАБИНЕТ КЛИЕНТА (наш модуль bj-lk) ============ */}}
|
||
{{if mod "web-cabinet"}}
|
||
<section class="settings-section" id="sec-cabinet">
|
||
<h1>Кабинет клиента</h1>
|
||
<p class="muted" style="margin:-8px 0 16px">Наш личный кабинет для клиентов депозитария: модуль <code>bj-lk</code> на своём сервере или PHP-хостинге, связь с ядром, учётные записи клиентов и сотрудников. Интеграция с платформой ЕСИА Финанс — отдельный раздел.</p>
|
||
{{template "verline" (.Versions.Get "bj-lk")}}
|
||
|
||
<div class="card" id="sec-lk-dist">
|
||
<h2><span class="dot {{if .Settings.LK.APIKey}}ok{{else}}warn{{end}}"></span>Кабинет клиента на своём сервере</h2>
|
||
<p class="muted">Кабинет (<code>bj-lk</code>) — отдельная программа для веб-сервера депозитария или хостинга. Своей базы у неё нет: данные она берёт у ядра по защищённому каналу и не хранит дольше запроса. Порядок: <b>1)</b> создайте ключ доступа, <b>2)</b> выберите способ связи, <b>3)</b> скачайте архив и распакуйте его на сервере кабинета, <b>4)</b> запустите программу (<code>sudo ./install.sh</code> заводит службу), <b>5)</b> откройте адрес кабинета в браузере — установщик проверит сервер, спросит ключ, проверит связь и заведёт администратора, <b>6)</b> опубликуйте кабинет через nginx с сертификатом (пример в README архива).</p>
|
||
|
||
<form method="post" action="/admin/setup/lk/dist" style="display:grid;gap:14px;max-width:780px;margin-top:12px">
|
||
<input type="hidden" name="section" value="sec-lk-dist"><input type="hidden" name="core_url_set" value="1"><input type="hidden" name="mode_set" value="1">
|
||
|
||
<div class="fld">
|
||
<label>Ключ доступа модуля</label>
|
||
<div style="display:flex;gap:8px;align-items:center;margin-top:4px;flex-wrap:wrap">
|
||
<code style="flex:1;min-width:260px;overflow-wrap:anywhere">{{if .LKKeyMask}}{{.LKKeyMask}}{{else}}не создан — кабинет не сможет обращаться к ядру{{end}}</code>
|
||
<button type="submit" name="op" value="newkey" class="btn btn-secondary">{{if .Settings.LK.APIKey}}Сменить ключ{{else}}Создать ключ{{end}}</button>
|
||
{{if .Settings.LK.APIKey}}<button type="submit" name="op" value="clearkey" class="btn btn-secondary" onclick="return confirm('Удалить ключ? Кабинет перестанет получать данные, пока не впишете новый.')">Удалить</button>{{end}}
|
||
</div>
|
||
<span class="muted" style="font-size:12px">В архив ключ не попадает: его вводят в установщике кабинета (или вписывают в <code>lk.env</code> при установке без браузера). Здесь он показан целиком — понадобится при переустановке кабинета.</span>
|
||
</div>
|
||
|
||
<div class="fld" style="gap:2px">
|
||
<label style="margin-bottom:6px">Как кабинет и ядро находят друг друга</label>
|
||
<label style="display:flex;gap:8px;align-items:flex-start;font-weight:600"><input type="radio" name="mode" value="direct" {{if not .Settings.LK.AgentMode}}checked{{end}}> <span>Кабинет обращается к ядру <span class="muted" style="font-weight:400">— обычный случай: у ядра есть адрес, доступный серверу кабинета.</span></span></label>
|
||
<label style="display:flex;gap:8px;align-items:flex-start;font-weight:600;margin-top:6px"><input type="radio" name="mode" value="agent" {{if .Settings.LK.AgentMode}}checked{{end}}> <span>Ядро подключается к кабинету <span class="muted" style="font-weight:400">— если у ядра нет адреса в интернете. Ядро само звонит кабинету по его имени, забирает запросы клиентов и возвращает ответы; входящих соединений и открытых портов не требуется.</span></span></label>
|
||
</div>
|
||
|
||
<div class="fld">
|
||
<label>Адрес ядра для кабинета <span class="muted" style="font-weight:400">— для режима «кабинет обращается к ядру»</span></label>
|
||
<input type="text" name="core_url" value="{{.Settings.LK.CoreURL}}" placeholder="https://bj.example.ru" style="margin-top:4px">
|
||
<span class="muted" style="font-size:12px">Куда кабинет обращается за данными. Если кабинет и ядро в одной сети — внутренний адрес; если в разных — тот, что открыт межсетевым экраном. Только <code>https</code>: по этому каналу идут ключ и сессии клиентов.</span>
|
||
</div>
|
||
|
||
<div class="fld">
|
||
<label>Адрес кабинета на хостинге <span class="muted" style="font-weight:400">— для режима «ядро подключается к кабинету»</span></label>
|
||
<input type="text" name="cabinet_url" value="{{.Settings.LK.CabinetURL}}" placeholder="https://lk.example.ru" style="margin-top:4px">
|
||
<span class="muted" style="font-size:12px">Имя, по которому ядро дозванивается до кабинета. Оно же — адрес, по которому входят клиенты.</span>
|
||
</div>
|
||
|
||
<label style="display:flex;gap:8px;align-items:flex-start;font-weight:600"><input type="checkbox" name="bind_network" value="1" {{if .Settings.LK.BindNetwork}}checked{{end}}> <span>Привязывать сессию клиента к его сети <span class="muted" style="font-weight:400">— к браузеру сессия привязана всегда; эта отметка добавляет сверку сети. Строже, но клиентов с мобильным интернетом будет выбрасывать при смене адреса оператором.</span></span></label>
|
||
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
||
<button type="submit" class="btn btn-ok">Сохранить</button>
|
||
{{if .Settings.LK.APIKey}}<a class="btn" href="/admin/setup/lk/dist.tar.gz">Скачать для своего сервера (Linux)</a>
|
||
<a class="btn" href="/admin/setup/lk/relay-php.zip">Скачать для PHP-хостинга</a>{{end}}
|
||
<a class="btn btn-secondary" href="/admin/help/lk-api">Инструкция по размещению</a>
|
||
</div>
|
||
</form>
|
||
|
||
{{if .Settings.LK.AgentMode}}
|
||
<div style="margin-top:14px;padding:12px 14px;border-radius:8px;background:var(--bg-soft,#f6f7f9)">
|
||
<b>Связь с кабинетом</b> <span class="dot {{if .LKAgent.Connected}}ok{{else}}err{{end}}"></span>
|
||
{{if .LKAgent.Connected}}ядро подключено к {{.LKAgent.Cabinet}}{{else}}ядро не дозвонилось до кабинета{{end}}
|
||
<div class="muted" style="font-size:12px;margin-top:4px">
|
||
{{if .LKAgent.LastOK}}Последний обмен: {{.LKAgent.LastOK}}. {{end}}Обслужено запросов: {{.LKAgent.Served}}.
|
||
{{if .LKAgent.LastErr}}<br>Ошибка: {{.LKAgent.LastErr}}{{end}}
|
||
</div>
|
||
</div>
|
||
{{end}}
|
||
|
||
<p class="muted" style="font-size:12px;margin-top:10px"><b>Где собираются страницы.</b> Для PHP-хостинга — всегда в ядре: на хостинге лежит только ретранслятор (index.php), который передаёт запросы браузера ядру. Для своего сервера установщик спросит: собирать страницы в ядре (тогда программа на сервере — тоже ретранслятор, новые разделы появляются обновлением ядра) или на месте. Ключа ни в одном архиве нет.</p>
|
||
<p class="muted" style="font-size:12px;margin-top:6px">В архиве для своего сервера: исполняемый файл, служба systemd (отдельный пользователь, ограничения доступа к файловой системе), файл настроек службы, установщик и README. Ключа в архиве нет — его спрашивает веб-установщик. Кабинет входит в модуль «Личный кабинет клиента» и обновляется отдельно от ядра.</p>
|
||
</div>
|
||
|
||
<div class="card" id="sec-lk-preview">
|
||
<h2><span class="dot {{if .LKPreview.On}}ok{{else}}warn{{end}}"></span>Кабинет на этом сервере — для просмотра</h2>
|
||
<p class="muted">Посмотреть кабинет глазами клиента можно, не размещая его на хостинге: ядро поднимает тот же кабинет на отдельном порту. Открывается по http из сети депозитария, поэтому подходит для проверки, но не для клиентов из интернета — им кабинет публикуется через хостинг или nginx с сертификатом.</p>
|
||
<form method="post" action="/admin/setup/lk/preview" style="display:flex;gap:12px;align-items:flex-end;flex-wrap:wrap;margin-top:10px">
|
||
<input type="hidden" name="section" value="sec-lk-preview">
|
||
<label style="display:flex;gap:8px;align-items:center;font-weight:600;margin:0"><input type="checkbox" name="preview_on" value="1" {{if .Settings.LK.PreviewOn}}checked{{end}}> Запустить кабинет на этом сервере</label>
|
||
<div class="fld"><label>Порт</label><input type="text" name="preview_listen" value="{{.LKPreview.Listen}}" style="width:120px"></div>
|
||
<button type="submit" class="btn btn-ok">Применить</button>
|
||
{{if .LKPreview.On}}<a class="btn" href="{{.LKPreview.URL}}" target="_blank" rel="noopener">Открыть кабинет →</a>{{end}}
|
||
</form>
|
||
{{if .LKPreview.Err}}<p class="muted" style="color:var(--err);font-size:12px;margin-top:8px">Не запущен: {{.LKPreview.Err}}</p>{{end}}
|
||
<p class="muted" style="font-size:12px;margin-top:8px">Вход — по учётным записям из карточки «Пользователи кабинета» ниже; сотрудник видит список клиентов, клиент — свой портфель. Данные те же, что получит кабинет на хостинге.</p>
|
||
</div>
|
||
|
||
<div class="card" id="sec-lk-data">
|
||
<h2><span class="dot {{if .LKData.SnapshotOn}}ok{{else}}warn{{end}}"></span>Данные кабинета: курсы и история</h2>
|
||
<p class="muted">Чем кабинет считает стоимость портфеля. Курс нужен для выпусков с валютным номиналом (замещающие и еврооблигации): цена по ним — процент от номинала в долларах, юанях, евро. История стоимости накапливается только у нас: ни торговая система, ни брокерский учёт её не хранят.</p>
|
||
|
||
<form method="post" action="/admin/setup/lk/data" style="display:grid;gap:14px;max-width:780px;margin-top:12px">
|
||
<input type="hidden" name="section" value="sec-lk-data">
|
||
|
||
<div class="fld">
|
||
<label for="lk_fx_source">Курсы валют</label>
|
||
<select id="lk_fx_source" name="fx_source">
|
||
<option value="cbr" {{if ne .LKData.FXSource "accounting"}}selected{{end}}>Банк России — официальный курс, открытый канал без ключа</option>
|
||
<option value="accounting" {{if eq .LKData.FXSource "accounting"}}selected{{end}}>Учётная система — курс из OD_VALUES_RATES (DEPO / SPECTRE)</option>
|
||
</select>
|
||
<span class="muted">{{if .LKData.FXAt}}Последний известный комплект: {{.LKData.FXAt}}, валют {{.LKData.FXCount}}.{{else}}Курсы ещё не загружались.{{end}} Полученные курсы сохраняются в базе bj по дням, поэтому при потере связи кабинет продолжит считать по последнему известному, а не покажет «нет курса». Курс учётной системы в нашей копии отстаёт на месяцы — выбирайте его только на сервере без выхода в интернет.{{with .LKData.FXNote}} {{.}}{{end}}</span>
|
||
</div>
|
||
|
||
<div class="fld">
|
||
<label>Ежедневный срез стоимости портфелей</label>
|
||
<div style="display:flex;gap:14px;align-items:center;flex-wrap:wrap">
|
||
<select name="snapshot_mode" style="width:auto;min-width:320px">
|
||
<option value="" {{if eq .LKData.SnapshotMode ""}}selected{{end}}>Автоматически — ведём, если настроен экспорт QUIK{{if .LKData.SnapshotOn}} (сейчас ведём){{else}} (сейчас нет: экспорт не настроен){{end}}</option>
|
||
<option value="on" {{if eq .LKData.SnapshotMode "on"}}selected{{end}}>Всегда вести</option>
|
||
<option value="off" {{if eq .LKData.SnapshotMode "off"}}selected{{end}}>Не вести</option>
|
||
</select>
|
||
<label style="display:flex;gap:7px;align-items:center;font-weight:400;text-transform:none;letter-spacing:0">время
|
||
<input type="text" name="snapshot_at" value="{{.LKData.SnapshotAt}}" style="width:90px" placeholder="20:30"></label>
|
||
</div>
|
||
<span class="muted">{{if .LKData.Snapshots}}Накоплено дней: {{.LKData.Snapshots}}; последний срез {{.LKData.SnapLast}} — договоров {{.LKData.SnapRows}}.{{else}}Срезов пока нет: график в кабинете появится после первых двух дней.{{end}} Срез считает стоимость каждого портфеля по экспорту QUIK один раз в день, после закрытия торгов. Задним числом историю восстановить нельзя, поэтому включать лучше сразу.</span>
|
||
</div>
|
||
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
||
<button class="btn" type="submit">Сохранить</button>
|
||
<button class="btn btn-secondary" type="submit" name="op" value="fx_now">Обновить курсы сейчас</button>
|
||
<button class="btn btn-secondary" type="submit" name="op" value="snapshot_now">Снять срез сейчас</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="card" id="sec-lk-users">
|
||
<h2><span class="dot {{if .LKUsers}}ok{{else}}warn{{end}}"></span>Пользователи кабинета</h2>
|
||
<p class="muted">Учётные записи кабинета живут здесь, в базе ядра: кабинет на хостинге своей базы не имеет. Клиент привязан к номеру договора депозитария — по нему кабинет показывает портфель. Сотрудник («администратор кабинета») видит список клиентов и открывает их кабинеты на просмотр. <b>Письма не отправляются</b>: почта только хранится, пароль сообщите лично.</p>
|
||
{{if .LKUsersError}}<div class="alert alert-warn">База кабинета недоступна: {{.LKUsersError}}</div>{{end}}
|
||
{{if .LKUsers}}
|
||
<div style="overflow-x:auto"><table style="margin-top:10px">
|
||
<tr><th>Логин</th><th>Договор</th><th>Имя</th><th>Почта</th><th>Роль</th><th>Поручения</th><th>Состояние</th><th></th></tr>
|
||
{{range .LKUsers}}<tr>
|
||
<td><code>{{.Login}}</code></td><td>{{.Contract}}</td><td>{{.FullName}}</td><td>{{.Email}}</td>
|
||
<td>{{if .IsAdmin}}сотрудник{{else}}клиент{{end}}</td>
|
||
<td>{{if .CanTransfer}}разрешены{{else}}<span class="muted">просмотр</span>{{end}}</td>
|
||
<td>{{if .Active}}<span class="dot ok"></span>активна{{else}}<span class="dot err"></span>заблокирована{{end}}</td>
|
||
<td style="white-space:nowrap">
|
||
<form method="post" action="/admin/setup/lk/users" style="display:inline"><input type="hidden" name="section" value="sec-lk-users"><input type="hidden" name="id" value="{{.ID}}">
|
||
{{if .Active}}<button class="btn btn-secondary btn-sm" name="op" value="block">Заблокировать</button>{{else}}<button class="btn btn-secondary btn-sm" name="op" value="unblock">Разблокировать</button>{{end}}
|
||
{{if .IsAdmin}}<button class="btn btn-secondary btn-sm" name="op" value="admin_off">Снять права сотрудника</button>{{else}}<button class="btn btn-secondary btn-sm" name="op" value="admin_on">Сделать сотрудником</button>
|
||
{{if .CanTransfer}}<button class="btn btn-secondary btn-sm" name="op" value="transfer_off">Запретить поручения</button>{{else}}<button class="btn btn-secondary btn-sm" name="op" value="transfer_on">Разрешить поручения</button>{{end}}{{end}}
|
||
<button class="btn btn-secondary btn-sm" name="op" value="delete" onclick="return confirm('Удалить учётную запись {{.Login}}? Поручения и журнал останутся.')">Удалить</button>
|
||
</form>
|
||
</td>
|
||
</tr>{{end}}
|
||
</table></div>
|
||
{{else}}{{if not .LKUsersError}}<p class="muted">Учётных записей пока нет. Первого администратора можно завести из установщика кабинета или здесь.</p>{{end}}{{end}}
|
||
|
||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:16px;margin-top:14px">
|
||
<form method="post" action="/admin/setup/lk/users" style="display:grid;gap:8px">
|
||
<input type="hidden" name="section" value="sec-lk-users"><input type="hidden" name="op" value="create">
|
||
<b>Новая учётная запись</b>
|
||
<input type="text" name="login" placeholder="Логин (латиницей или номер договора)" required autocomplete="off">
|
||
<input type="text" name="contract" placeholder="Номер договора в депозитарии, например БР-859" autocomplete="off">
|
||
<input type="text" name="full_name" placeholder="ФИО" autocomplete="off">
|
||
<input type="email" name="email" placeholder="Электронная почта (не рассылается)" autocomplete="off">
|
||
<input type="text" name="phone" placeholder="Телефон" autocomplete="off">
|
||
<input type="password" name="password" placeholder="Пароль: от 10 символов, буквы и цифра" required autocomplete="new-password">
|
||
<label style="font-weight:400"><input type="checkbox" name="is_admin" value="1"> сотрудник депозитария (видит клиентов, договор не нужен)</label>
|
||
<label style="font-weight:400"><input type="checkbox" name="can_transfer" value="1"> разрешить подачу поручений (по умолчанию — только просмотр)</label>
|
||
<div><button type="submit" class="btn btn-ok">Создать</button></div>
|
||
</form>
|
||
<form method="post" action="/admin/setup/lk/users" style="display:grid;gap:8px;align-content:start">
|
||
<input type="hidden" name="section" value="sec-lk-users"><input type="hidden" name="op" value="password">
|
||
<b>Сменить пароль</b>
|
||
<input type="text" name="login" placeholder="Логин" required autocomplete="off">
|
||
<input type="password" name="password" placeholder="Новый пароль" required autocomplete="new-password">
|
||
<span class="muted" style="font-size:12px">Все сессии пользователя будут завершены.</span>
|
||
<div><button type="submit" class="btn btn-secondary">Сменить</button></div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
</section>
|
||
{{end}}
|
||
|
||
{{/* ============ ЕСИА ФИНАНС (внешняя платформа ЛК) ============ */}}
|
||
{{if mod "esia-finance"}}
|
||
<section class="settings-section" id="sec-lk">
|
||
<h1>ЕСИА Финанс</h1>
|
||
<p class="muted" style="margin:-8px 0 16px">Интеграция с внешней платформой личного кабинета <strong>ЕСИА Финанс</strong>: адрес для клиента, приём заявок M2M, возврат статуса и подключение к API (тест/бой). Наш собственный кабинет настраивается в разделе «Кабинет клиента».</p>
|
||
<div class="card">
|
||
<h2><span class="dot {{if .Settings.LK.PublicURL}}ok{{else}}warn{{end}}"></span>Личный кабинет — интеграция</h2>
|
||
<p class="muted"><b>Адрес ЛК</b> — ссылка, по которой клиент входит в кабинет (для кнопки «Открыть» и дашборда). Заявками M2M bj и ЛК обмениваются одной из двух моделей: <b>pull</b> — bj сам забирает заявки из API ЛК и туда же возвращает статус (блок «Подключение к API ЛК», тест/бой); <b>push</b> — ЛК сам шлёт заявки на bj (<code>POST /api/v1/back_office/claims/</code>), а статус bj возвращает на <b>Callback URL</b> (ниже, свёрнуто).</p>
|
||
<form method="post" action="/admin/setup/lk" style="margin-top:12px;display:grid;gap:12px;max-width:640px">
|
||
<div class="fld">
|
||
<label>Адрес ЛК</label>
|
||
<div style="display:flex;gap:8px;align-items:center;margin-top:4px">
|
||
<input type="text" name="lk_public_url" value="{{.Settings.LK.PublicURL}}" placeholder="https://lk.example.ru" style="flex:1">
|
||
{{if .Settings.LK.PublicURL}}<a class="btn btn-secondary" href="{{.Settings.LK.PublicURL}}" target="_blank" rel="noopener">Открыть →</a>{{end}}
|
||
</div>
|
||
<span class="muted" style="font-size:12px">{{if devOn}}Показывается ссылкой на дашборде для первичного теста.{{else}}Адрес, по которому клиенты входят в личный кабинет.{{end}}</span>
|
||
</div>
|
||
<details {{if .Settings.LK.CallbackURL}}open{{end}} class="fld">
|
||
<summary style="cursor:pointer;color:var(--accent);font-size:13px">Callback URL — push-модель (ЛК сам шлёт заявки нам)</summary>
|
||
<div style="margin-top:8px">
|
||
<label>Callback URL <span class="muted" style="font-weight:400">(куда bj шлёт статус заявки)</span></label>
|
||
<input type="text" name="callback_url" value="{{.Settings.LK.CallbackURL}}" placeholder="https://lk.example.ru" style="margin-top:4px">
|
||
<span class="muted" style="font-size:12px">{{if not .Settings.LK.CallbackURL}}Не настроен. Нужен только в push-модели: когда ЛК сам шлёт заявки на bj. При подключении через API ЛК (pull) не используется. Для обмена с НРД не требуется.{{end}}</span>
|
||
</div>
|
||
</details>
|
||
<div><button type="submit" class="btn">Сохранить и проверить</button></div>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>Подключение к API ЛК (pull) — тест и бой</h2>
|
||
<p class="muted"><b>Основная модель.</b> Реквизиты, по которым bj сам обращается к API платформы ЛК ЕСИА Финанс (HTTP Basic): забирает заявки и туда же возвращает статус. Заполните нужные окружения и нажмите <strong>«Проверить связь»</strong> — сразу видно, есть подключение или нет.</p>
|
||
|
||
<div class="grid" style="grid-template-columns:repeat(auto-fit,minmax(300px,1fr));margin-top:12px">
|
||
{{/* --- Тест --- */}}
|
||
<div style="border:1px solid var(--border);border-radius:10px;padding:16px;background:var(--card-2)">
|
||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:10px">
|
||
<strong>Тестовый стенд</strong>
|
||
{{if .Settings.LK.Test.LastMsg}}<span class="badge {{if .Settings.LK.Test.LastOK}}confirmed{{else}}rejected{{end}}">{{if .Settings.LK.Test.LastOK}}связь есть{{else}}нет связи{{end}}</span>{{end}}
|
||
{{if eq .Settings.LK.ActiveEnv "test"}}<span class="badge" style="margin-left:auto">активно</span>{{end}}
|
||
</div>
|
||
<form method="post" action="/admin/setup/lk-integration" style="display:grid;gap:9px">
|
||
<input type="hidden" name="env" value="test">
|
||
<div><label>Адрес API</label><input type="text" name="base_url" value="{{.Settings.LK.Test.BaseURL}}" placeholder="https://lk.example.ru/manager/api" style="margin-top:3px"></div>
|
||
<div><label>Логин</label><input type="text" name="login" value="{{.Settings.LK.Test.Login}}" autocomplete="off" style="margin-top:3px"></div>
|
||
<div><label>Пароль</label><input type="password" name="password" value="" placeholder="{{if .Settings.LK.Test.Password}}•••••• (не менять — оставьте пустым){{end}}" autocomplete="new-password" style="margin-top:3px"></div>
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:2px">
|
||
<button type="submit" class="btn btn-secondary">Сохранить</button>
|
||
<button type="submit" formaction="/admin/setup/lk-check" class="btn">Проверить связь</button>
|
||
</div>
|
||
</form>
|
||
{{if .Settings.LK.Test.LastMsg}}<p class="muted" style="font-size:12px;margin-top:8px">Последняя проверка: {{.Settings.LK.Test.LastMsg}} · {{.Settings.LK.Test.LastAt.Format "02.01 15:04"}}</p>{{end}}
|
||
</div>
|
||
|
||
{{/* --- Бой --- */}}
|
||
<div style="border:1px solid var(--border);border-radius:10px;padding:16px;background:var(--card-2)">
|
||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:10px">
|
||
<strong>Боевой контур</strong>
|
||
{{if .Settings.LK.Prod.LastMsg}}<span class="badge {{if .Settings.LK.Prod.LastOK}}confirmed{{else}}rejected{{end}}">{{if .Settings.LK.Prod.LastOK}}связь есть{{else}}нет связи{{end}}</span>{{end}}
|
||
{{if eq .Settings.LK.ActiveEnv "prod"}}<span class="badge" style="margin-left:auto">активно</span>{{end}}
|
||
</div>
|
||
<form method="post" action="/admin/setup/lk-integration" style="display:grid;gap:9px">
|
||
<input type="hidden" name="env" value="prod">
|
||
<div><label>Адрес API</label><input type="text" name="base_url" value="{{.Settings.LK.Prod.BaseURL}}" placeholder="https://lk.example.ru/manager/api" style="margin-top:3px"></div>
|
||
<div><label>Логин</label><input type="text" name="login" value="{{.Settings.LK.Prod.Login}}" autocomplete="off" style="margin-top:3px"></div>
|
||
<div><label>Пароль</label><input type="password" name="password" value="" placeholder="{{if .Settings.LK.Prod.Password}}•••••• (не менять — оставьте пустым){{end}}" autocomplete="new-password" style="margin-top:3px"></div>
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:2px">
|
||
<button type="submit" class="btn btn-secondary">Сохранить</button>
|
||
<button type="submit" formaction="/admin/setup/lk-check" class="btn">Проверить связь</button>
|
||
</div>
|
||
</form>
|
||
{{if .Settings.LK.Prod.LastMsg}}<p class="muted" style="font-size:12px;margin-top:8px">Последняя проверка: {{.Settings.LK.Prod.LastMsg}} · {{.Settings.LK.Prod.LastAt.Format "02.01 15:04"}}</p>{{end}}
|
||
</div>
|
||
</div>
|
||
|
||
<form method="post" action="/admin/setup/lk-integration" style="margin-top:14px;display:flex;gap:10px;align-items:center;flex-wrap:wrap">
|
||
<input type="hidden" name="env" value="test">
|
||
<label style="font-weight:500">Активное окружение:</label>
|
||
<select name="active_env" onchange="this.form.submit()">
|
||
<option value="test" {{if eq .Settings.LK.ActiveEnv "test"}}selected{{end}}>Тест</option>
|
||
<option value="prod" {{if eq .Settings.LK.ActiveEnv "prod"}}selected{{end}}>Бой</option>
|
||
</select>
|
||
<span class="muted" style="font-size:12px">bj работает с ЛК через выбранное окружение.</span>
|
||
{{if devOn}} <input type="hidden" name="emulate_nsd_form" value="1">
|
||
<label style="display:flex;align-items:flex-start;gap:8px;font-weight:400;flex-basis:100%;margin-top:6px"><input type="checkbox" name="emulate_nsd" style="width:auto;margin-top:3px" {{if .Settings.LK.EmulateNSD}}checked{{end}} onchange="this.form.submit()"> <span><b>Имитация НРД</b> — заявки ЛК проходят сверку с DEPO, блокировку в QUIK и получают статус в ЛК, но в НРД не отправляются: ответ контрагента имитирует bj.{{if and (eq .Settings.LK.ActiveEnv "prod") (not .Settings.LK.EmulateNSD)}} <span style="color:var(--warn)">Сейчас включена принудительно: боевой ЛК при тестовом профиле ИШ ({{.Settings.NSD.Profile}}).</span>{{end}}</span></label>{{end}}
|
||
</form>
|
||
<div id="lk-portfolio" style="margin-top:14px;padding:12px 14px;border:1px solid var(--border);border-radius:10px">
|
||
<b>Заполнение портфеля ДЕПО бумагами из DEPO</b> {{if .Settings.LK.PortfolioSync.Enabled}}<span class="badge confirmed">включено{{if .Settings.LK.PortfolioSync.AllClients}} — все клиенты{{else}} — по списку{{end}}</span>{{else}}<span class="badge draft">выключено</span>{{end}}
|
||
<p class="muted" style="margin:6px 0 8px;font-size:13px">Каждые 10 секунд, в том же проходе, что и приём заявок, bj сверяет портфель клиента в DEPO и отправляет его в ЛК (<code>/portfolio/import</code>, портфель ДЕПО) — только если остатки изменились. Номер гос. регистрации — из экспорта QUIK, раздел счёта — «Основной», хранение — НРД. Бумаги без номера гос. регистрации не отправляются (ЛК отвергает весь импорт).</p>
|
||
<form method="post" action="/admin/setup/lk-portfolio" style="margin:0 0 8px">
|
||
{{if .Settings.LK.PortfolioSync.Enabled}}<button class="btn" type="submit" name="action" value="off" style="background:var(--card);color:var(--err);border:1px solid var(--err)">Выключить заполнение портфеля ДЕПО бумагами</button>
|
||
<button class="btn" type="submit" name="action" value="sync">Заполнить сейчас</button>
|
||
{{if not .Settings.LK.PortfolioSync.AllClients}}<button class="btn" type="submit" name="action" value="on_all" onclick="return confirm('Включаю заполнение портфеля ДЕПО для ВСЕХ клиентов — все открытые торговые счёта. Портфели всех пользователей начнут отправляться в ЛК. Продолжить?')">Включить для всех пользователей</button>{{end}}
|
||
{{else}}<button class="btn btn-ok" type="submit" name="action" value="on_all" onclick="return confirm('Включаю заполнение портфеля ДЕПО для ВСЕХ клиентов — все открытые торговые счёта. Портфели всех пользователей начнут отправляться в ЛК. Продолжить?')">Включить для всех пользователей</button>
|
||
<button class="btn" type="submit" name="action" value="on" onclick="if(!this.form.pairs.value.trim()){alert('Укажите договор ЛК и портфель DEPO, например ДД-816=БР-859');return false}">Включить по списку</button>{{end}}
|
||
<div style="display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap;margin-top:8px">
|
||
<label style="display:flex;flex-direction:column;gap:3px;font-size:12px;min-width:260px">Каких клиентов заполнять: договор ЛК = портфель DEPO (через «;»)
|
||
<input name="pairs" value="{{.Settings.LK.PortfolioSync.Pairs}}" placeholder="ДД-816=БР-859"></label>
|
||
<button class="btn" type="submit" name="action" value="save">Сохранить список</button>
|
||
</div>
|
||
</form>
|
||
<details style="margin:8px 0 0"><summary style="cursor:pointer;font-size:12.5px">Именование у депозитария (счета, договоры, раздел, хранение)</summary>
|
||
<form method="post" action="/admin/setup/lk-portfolio" style="display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap;margin-top:8px">
|
||
<input type="hidden" name="naming" value="1">
|
||
<label style="display:flex;flex-direction:column;gap:3px;font-size:12px">Префикс торгового счёта в DEPO
|
||
<input name="account_prefix" value="{{if .Settings.LK.PortfolioSync.AccountPrefix}}{{.Settings.LK.PortfolioSync.AccountPrefix}}{{else}}{{depoAccPrefix}}{{end}}" placeholder="{{depoAccPrefix}}" style="width:120px"></label>
|
||
<label style="display:flex;flex-direction:column;gap:3px;font-size:12px">Номер договора депо в ЛК ({n} — номер счёта)
|
||
<input name="contract_template" value="{{if .Settings.LK.PortfolioSync.ContractTemplate}}{{.Settings.LK.PortfolioSync.ContractTemplate}}{{else}}ДД-{n}{{end}}" placeholder="ДД-{n}" style="width:160px"></label>
|
||
<label style="display:flex;flex-direction:column;gap:3px;font-size:12px">Раздел счёта
|
||
<input name="section" value="{{if .Settings.LK.PortfolioSync.Section}}{{.Settings.LK.PortfolioSync.Section}}{{else}}Основной{{end}}" placeholder="Основной" style="width:130px"></label>
|
||
<label style="display:flex;flex-direction:column;gap:3px;font-size:12px">Место хранения
|
||
<input name="storage" value="{{if .Settings.LK.PortfolioSync.Storage}}{{.Settings.LK.PortfolioSync.Storage}}{{else}}НРД{{end}}" placeholder="НРД" style="width:110px"></label>
|
||
<button class="btn" type="submit" name="action" value="save">Сохранить</button>
|
||
</form>
|
||
<p class="muted" style="font-size:12px;margin:4px 0 0">Заполнено из базы DEPO (префикс счёта) и значений по умолчанию — можно изменить. Пример: счёт ВТНКЦ00000816 и шаблон ДД-{n} → договор ДД-816.</p>
|
||
</details>
|
||
<form method="post" action="/admin/setup/lk-portfolio" id="lkpf-client" style="margin:10px 0 0;padding-top:10px;border-top:1px dashed var(--border)">
|
||
<div style="font-size:13px;font-weight:600;margin-bottom:4px">Залить одному клиенту (для теста)</div>
|
||
<div style="display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap">
|
||
<label style="display:flex;flex-direction:column;gap:3px;font-size:12px;min-width:320px">Клиент DEPO — ФИО, портфель или счёт
|
||
<input id="lkpf-q" autocomplete="off" placeholder="нажмите — список клиентов; или ФИО, портфель, счёт"></label>
|
||
<input type="hidden" name="client_port" id="lkpf-port"><input type="hidden" name="client_lk" id="lkpf-lk">
|
||
<button class="btn" type="submit" name="action" value="client_push" id="lkpf-push" disabled>Залить сейчас</button>
|
||
<button class="btn" type="submit" name="action" value="client_add" id="lkpf-add" disabled>Добавить в постоянное заполнение</button>
|
||
</div>
|
||
<div id="lkpf-list" style="margin-top:6px;font-size:12.5px"></div>
|
||
<div id="lkpf-sel" class="muted" style="margin-top:4px;font-size:12.5px"></div>
|
||
</form>
|
||
<script>
|
||
(function(){
|
||
var q=document.getElementById('lkpf-q'), list=document.getElementById('lkpf-list'), sel=document.getElementById('lkpf-sel'), t;
|
||
function esc(s){return String(s).replace(/[&<>"]/g,function(c){return{'&':'&','<':'<','>':'>','"':'"'}[c]})}
|
||
function pick(c){
|
||
document.getElementById('lkpf-port').value=c.port; document.getElementById('lkpf-lk').value=c.lk;
|
||
var ok=!!c.lk; document.getElementById('lkpf-push').disabled=!ok; document.getElementById('lkpf-add').disabled=!ok;
|
||
sel.innerHTML=ok?('Выбран: <b>'+esc(c.name)+'</b> · портфель DEPO '+esc(c.port)+' · счёт '+esc(c.account)+' → договор в ЛК <b>'+esc(c.lk)+'</b>'):'У клиента нет торгового счёта — договор в ЛК не определить';
|
||
list.innerHTML='';
|
||
}
|
||
function load(){
|
||
clearTimeout(t); var v=q.value.trim(); if(v.length===1){list.innerHTML='';return}
|
||
list.innerHTML='<span class="muted">ищу в DEPO…</span>';
|
||
t=setTimeout(function(){
|
||
fetch('/admin/setup/lk-portfolio/clients?q='+encodeURIComponent(v),{cache:'no-store'}).then(function(r){if(!r.ok) throw new Error(r.status); return r.json()}).then(function(d){
|
||
if(d&&d.error){list.innerHTML='<span style="color:var(--err)">DEPO: '+esc(d.error)+'</span>';return}
|
||
if(!d||!d.length){list.innerHTML='<span class="muted">не найдено</span>';return}
|
||
list.innerHTML=''; d.forEach(function(c){
|
||
var a=document.createElement('a'); a.href='#'; a.style.display='block'; a.style.padding='2px 0';
|
||
a.innerHTML=esc(c.name)+' · '+esc(c.port)+' · '+esc(c.account)+(c.lk?' → '+esc(c.lk):'');
|
||
a.onclick=function(e){e.preventDefault(); pick(c)}; list.appendChild(a);
|
||
});
|
||
}).catch(function(e){list.innerHTML='<span style="color:var(--err)">поиск не ответил ('+esc(e&&e.message||'сеть')+') — повторите</span>'});
|
||
},300);
|
||
}
|
||
q.addEventListener('input',load);
|
||
q.addEventListener('focus',function(){ if(!list.innerHTML) load(); });
|
||
})();
|
||
</script>
|
||
{{range lkPortfolioStates}}<div class="muted" style="font-size:12.5px;margin-top:8px"><b>{{.LK}}</b> ← {{.Depo}}: {{if .Err}}<span style="color:var(--err)">ошибка — {{.Err}}</span>{{else if .PushedAt.IsZero}}сверено {{.CheckedAt.Format "15:04:05"}}, отправки ещё не было{{else}}отправлено {{.PushedAt.Format "02.01 15:04:05"}} ({{.Lines}} бумаг), сверено {{.CheckedAt.Format "15:04:05"}}{{end}}{{if .Skipped}}<details style="margin-top:2px"><summary style="cursor:pointer">пропущено бумаг: {{len .Skipped}} — без номера гос. регистрации</summary>{{range .Skipped}}<div>{{.}}</div>{{end}}</details>{{end}}</div>{{end}}
|
||
</div>
|
||
<div id="lk-real-nsd" style="margin-top:14px;padding:12px 14px;border:1px solid var(--border);border-radius:10px;{{if .Settings.LK.RealNSDTest.Enabled}}background:var(--warn-weak){{end}}">
|
||
<b>Тест «боевой ЛК → тестовый контур НРД»</b> {{if .Settings.LK.RealNSDTest.Enabled}}<span class="badge awaiting_decision">включён</span>{{else}}<span class="badge draft">выключен</span>{{end}}
|
||
<p class="muted" style="margin:6px 0 8px;font-size:13px">Заявки клиентов из списка проходят у нас всё по-настоящему: сверка и поручение в DEPO, блокировка и списание в QUIK МНП (<b>боевая запись</b>), статусы в ЛК. В НРД (тестовый контур) запрос уходит с данными тест-инвестора <b>{{realNSDAs}}</b>; GUID, бумаги и счета — настоящие, ответы НРД применяются к заявке клиента. «Исполнена» в ЛК — по отчёту НРД об исполнении (MS010). Остальные заявки ЛК — как раньше. Включайте до того, как клиент подпишет заявку.</p>
|
||
<form method="post" action="/admin/setup/lk-real-nsd" style="display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap" onsubmit="return !this.enabled.checked || confirm('Включить? Для клиентов из списка QUIK МНП будет записывать по-настоящему (блокировка и списание бумаг).');">
|
||
<label style="display:flex;align-items:center;gap:6px;font-weight:400"><input type="checkbox" name="enabled" style="width:auto"{{if .Settings.LK.RealNSDTest.Enabled}} checked{{end}}> включить</label>
|
||
<label style="display:flex;flex-direction:column;gap:3px;font-size:12px;min-width:260px">Паспорта клиентов (серия номер, через «;»)
|
||
<input name="docs" value="{{.Settings.LK.RealNSDTest.Docs}}" placeholder="1110 754503"></label>
|
||
<button class="btn" type="submit">Сохранить</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
{{if devOn}} <div class="card">
|
||
<h2>Симулятор заявки M2M</h2>
|
||
<p class="muted">Пока подача через веб-форму у ЕСИА Финанс не готова — bj сам собирает заявку <code>depo_m2m</code> и <strong>отправляет её в API ЛК</strong> (<code>POST /claims/</code>) через активное окружение. Ответ показывается ниже; затем заявку ловим опросом в «Переводы». Поля предзаполнены рабочим тестовым договором.</p>
|
||
<form method="post" action="/admin/setup/lk-simulate" style="margin-top:12px">
|
||
<div class="grid" style="grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px">
|
||
<div><label>ID договора (owner_id)</label><input type="text" name="contract_id" value="1335" style="margin-top:3px"></div>
|
||
<div><label>ID клиента (user_id)</label><input type="text" name="user_id" value="771" style="margin-top:3px"></div>
|
||
<div><label>Счёт депо (account)</label><input type="text" name="account" value="123" style="margin-top:3px"></div>
|
||
<div><label>Раздел (section)</label><input type="text" name="section" value="124" style="margin-top:3px"></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="paper_name" value="ВК ао" style="margin-top:3px"></div>
|
||
<div><label>ISIN</label><input type="text" name="paper_isin" value="RU000A106YF0" style="margin-top:3px"></div>
|
||
<div><label>Вид бумаги</label>
|
||
<select name="paper_type" style="margin-top:3px">
|
||
<option value="share">Акция (share)</option>
|
||
<option value="bond">Облигация (bond)</option>
|
||
<option value="fund">Фонд (fund)</option>
|
||
<option value="depositary_receipt">Депозитарная расписка</option>
|
||
</select></div>
|
||
<div><label>№ госрегистрации</label><input type="text" name="paper_reg" value="1-01-16453-A" style="margin-top:3px"></div>
|
||
<div><label>Количество</label><input type="text" name="paper_count" value="1" style="margin-top:3px"></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="contragent_inn" value="7702165310" style="margin-top:3px"></div>
|
||
<div><label>Наименование контрагента</label><input type="text" name="contragent_name" value="Тест-депозитарий" style="margin-top:3px"></div>
|
||
<div><label>Место расчётов</label>
|
||
<select name="place_of_payment" style="margin-top:3px">
|
||
<option value="nrd">НРД</option>
|
||
<option value="spb">СПБ</option>
|
||
</select></div>
|
||
<div style="display:flex;align-items:flex-end"><label style="display:flex;align-items:center;gap:8px;font-weight:400"><input type="checkbox" name="expense_information" style="width:auto"> Передать информацию о расходах</label></div>
|
||
</div>
|
||
<div style="margin-top:16px"><button type="submit" class="btn">Собрать и отправить в ЛК</button></div>
|
||
</form>
|
||
<p class="muted" style="font-size:12px;margin-top:10px">Результат (сырой ответ API) появится вверху страницы. Если «Раздел счёта не доступен для быстрых переводов» — Финансы должны включить признак M2M на разделе счёта.</p>
|
||
</div>{{end}}
|
||
|
||
<div class="card">
|
||
<h2>Забрать заявки из ЛК</h2>
|
||
<p class="muted">Коннектор bj вычитывает заявки <code>depo_m2m</code> активного окружения в статусах «В очереди» и «В работе», которые ещё не обрабатывал, для каждой собирает M2M-пакет, отправляет в НРД и переводит заявку в статус <strong>«В работе»</strong>. По ответу НРД статус в ЛК замыкается автоматически (Исполнена / Отклонена).{{if devOn}} В режиме имитации НРД перед отправкой идёт сверка с DEPO: отказ сразу уходит в ЛК с кодом и причиной, одобренная заявка блокируется в QUIK и через 30 с получает имитированный ответ контрагента.{{end}} Штатно это делает фоновый поллер раз в минуту — кнопка для ручного запуска.</p>
|
||
<form method="post" action="/admin/setup/lk-pull" style="margin-top:12px"><button type="submit" class="btn">Забрать заявки сейчас</button></form>
|
||
<p class="muted" style="font-size:12px;margin-top:10px">Итог (сколько получено / отправлено, ошибки) появится вверху страницы.</p>
|
||
</div>
|
||
</section>
|
||
{{end}}
|
||
|
||
{{/* ============ 1С: БАЗА БЭК-ОФИСА (модуль onec-back, тестовый) ============ */}}
|
||
{{if mod "onec-back"}}
|
||
<section class="settings-section" id="sec-onec">
|
||
<h1>1С: база бэк-офиса</h1>
|
||
<p class="muted" style="margin:-8px 0 16px">Связь bj с базой 1С бэк-офиса. Модуль в разработке: сейчас здесь задаётся и проверяется подключение к базе, обмен данными появится в следующих выпусках.</p>
|
||
<div class="card">
|
||
<h2><span class="dot {{if .Settings.OneC.CheckOK}}ok{{else}}warn{{end}}"></span>Подключение к базе 1С</h2>
|
||
<p class="muted">bj обращается к базе через стандартный интерфейс OData. Его включают при публикации базы: конфигуратор 1С → Администрирование → Публикация на веб-сервере → «Публиковать стандартный интерфейс OData».</p>
|
||
<form method="post" action="/admin/setup/onec" style="margin-top:12px;display:grid;gap:12px;max-width:640px">
|
||
<div class="fld">
|
||
<label>Адрес публикации базы</label>
|
||
<input type="text" name="onec_url" value="{{.Settings.OneC.URL}}" placeholder="http://1c-server/middle" data-need="адрес публикации базы" style="margin-top:4px">
|
||
<span class="muted" style="font-size:12px">Адрес, по которому база открывается в браузере, без /odata — например http://1c-server/middle.</span>
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px">
|
||
<div class="fld"><label>Пользователь 1С</label><input type="text" name="onec_user" value="{{.Settings.OneC.User}}" data-need="пользователь 1С" autocomplete="off" style="margin-top:4px"></div>
|
||
<div class="fld"><label>Пароль</label><input type="password" name="onec_password" {{if .Settings.OneC.Password}}placeholder="сохранён — не меняйте, если тот же"{{else}}data-need="пароль"{{end}} autocomplete="new-password" style="margin-top:4px"></div>
|
||
</div>
|
||
<span class="muted" style="font-size:12px">Лучше завести в 1С отдельного пользователя для bj — с правом только на нужные данные.</span>
|
||
<div><button type="submit" class="btn">Сохранить и проверить</button></div>
|
||
</form>
|
||
{{with .Settings.OneC}}{{if not .CheckedAt.IsZero}}<p style="margin:14px 0 0;font-size:13.5px"><b style="color:var(--{{if .CheckOK}}ok{{else}}err{{end}})">{{if .CheckOK}}✓ Подключение работает{{else}}✗ Подключение не работает{{end}}</b> — {{.CheckMsg}} <span class="muted">(проверено {{.CheckedAt.Format "02.01.2006 15:04"}})</span></p>{{end}}{{end}}
|
||
</div>
|
||
</section>
|
||
{{end}}
|
||
|
||
|
||
{{/* ============ ОПОВЕЩЕНИЯ ============ */}}
|
||
<section class="settings-section" id="sec-alerts">
|
||
<h1>Оповещения</h1>
|
||
<p class="muted">Кому и о чём bj пишет на почту: администратору системы — о сбоях и обновлениях, отделам — о переводах и поручениях. Все события видны и в ленте «Новости»; передать их в мониторинг Zabbix — карточка «Zabbix» ниже.</p>
|
||
|
||
{{template "smtpCard" .}}
|
||
|
||
|
||
{{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>
|
||
|
||
{{/* ============ ОБНОВЛЕНИЯ ============ */}}
|
||
{{template "updateSection" .}}
|
||
|
||
{{/* ============ ЛИЦЕНЗИЯ ============ */}}
|
||
{{/* ============ РЕЗЕРВНОЕ КОПИРОВАНИЕ ============ */}}
|
||
<section class="settings-section" id="sec-server">
|
||
<h1>Сервер: HTTPS, время, сеть</h1>
|
||
{{$tz := serverTimeZone}}{{$sync := timeSynced}}
|
||
<div class="card" id="time">
|
||
<h2><span class="dot {{if $sync}}ok{{else}}warn{{end}}"></span>Время сервера</h2>
|
||
<div style="display:flex;gap:28px;align-items:center;flex-wrap:wrap;margin:4px 0 2px">
|
||
<div><div id="srv-clock" style="font-size:36px;font-weight:700;font-variant-numeric:tabular-nums;line-height:1.1">--:--:--</div>
|
||
<div id="srv-date" class="muted" style="font-size:13.5px;margin-top:2px">{{$tz}}</div></div>
|
||
<div style="font-size:14px;line-height:1.75;min-width:240px">
|
||
<div id="srv-sync">{{if $sync}}<span style="color:var(--ok)">✓ время синхронизируется</span>{{else}}<span style="color:var(--warn)">⚠ время не синхронизируется</span>{{end}}</div>
|
||
<div id="srv-src" class="muted" style="font-size:13px"></div>
|
||
<div id="srv-diff" class="muted" style="font-size:13px"></div>
|
||
</div>
|
||
</div>
|
||
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:end;margin-top:12px">
|
||
<form method="post" action="/admin/setup/timezone" style="margin:0">
|
||
<input type="hidden" name="section" value="time">
|
||
<label style="margin:0;font-size:13px">Часовой пояс
|
||
<select name="zone" onchange="if(confirm('Сменить часовой пояс сервера на '+this.options[this.selectedIndex].text+'? bj перезапустится (около 15 секунд).')) this.form.submit(); else this.value='{{$tz}}';" style="display:block;min-width:250px;margin-top:4px">{{if not (timeZoneKnown $tz)}}<option value="{{$tz}}" selected>{{$tz}}</option>{{end}}{{range timeZones}}<option value="{{.Zone}}" {{if eq .Zone $tz}}selected{{end}}>{{.Title}}</option>{{end}}</select></label>
|
||
</form>
|
||
<form method="post" action="/admin/setup/time-sync" style="margin:0" onsubmit="var b=this.querySelector('button');b.disabled=true;b.textContent='Синхронизирую… (до минуты)';">
|
||
<input type="hidden" name="section" value="time">
|
||
<button type="submit" class="btn">Синхронизировать сейчас</button>
|
||
</form>
|
||
</div>
|
||
<p class="muted" style="font-size:12.5px;margin:8px 0 0">Точное время нужно для подписей и обмена с НРД (время НРД — московское). «Синхронизировать сейчас» сразу ставит часы по серверам времени; если свои серверы не заданы или не отвечают — bj подберёт российские серверы ВНИИФТРИ.</p>
|
||
<details style="margin-top:8px" {{if not $sync}}open{{end}}><summary style="cursor:pointer;color:var(--accent);font-size:13.5px">Серверы времени{{if .Settings.Time.Servers}}: <code>{{.Settings.Time.Servers}}</code>{{end}}</summary>
|
||
<p class="muted" style="font-size:13px;margin:6px 0">В закрытом контуре внешние серверы времени недоступны — укажите сервер времени вашей сети (обычно контроллер домена или маршрутизатор).</p>
|
||
<form method="post" action="/admin/setup/time" style="display:flex;gap:8px;flex-wrap:wrap;align-items:end;max-width:640px">
|
||
<input type="hidden" name="section" value="time">
|
||
<label style="flex:1;min-width:240px;margin:0">Серверы времени (через пробел)<input name="servers" value="{{.Settings.Time.Servers}}" placeholder="10.10.10.1 ntp.example.ru" style="width:100%;margin-top:4px"></label>
|
||
<button type="submit" class="btn">Применить</button>
|
||
</form>
|
||
</details>
|
||
<script>
|
||
(function(){ // часы сервера: сверка раз в 30 с, между сверками идут по часам браузера
|
||
var base=null, DAYS=['вс','пн','вт','ср','чт','пт','сб'];
|
||
function p2(n){ return ('0'+n).slice(-2); }
|
||
function esc(s){ return String(s==null?'':s).replace(/[&<>"]/g,function(c){return {'&':'&','<':'<','>':'>','"':'"'}[c]}); }
|
||
function draw(){ if(!base) return;
|
||
var t=new Date(Date.now()+base.shift+base.d.offset*1000);
|
||
document.getElementById('srv-clock').textContent=p2(t.getUTCHours())+':'+p2(t.getUTCMinutes())+':'+p2(t.getUTCSeconds());
|
||
var o=base.d.offset/3600; document.getElementById('srv-date').textContent=DAYS[t.getUTCDay()]+', '+p2(t.getUTCDate())+'.'+p2(t.getUTCMonth()+1)+'.'+t.getUTCFullYear()+' · '+base.d.zone+' ('+base.d.abbr+', UTC'+(o>=0?'+':'−')+Math.abs(o)+')'; }
|
||
function sync(){ var t0=Date.now();
|
||
fetch('/admin/setup/time-now',{cache:'no-store',credentials:'same-origin'}).then(function(r){return r.json()}).then(function(d){
|
||
var t1=Date.now(); base={d:d, shift:d.epoch_ms-(t0+t1)/2}; draw();
|
||
document.getElementById('srv-sync').innerHTML=d.synced?'<span style="color:var(--ok)">✓ время синхронизируется</span>':'<span style="color:var(--warn)">⚠ время не синхронизируется — нажмите «Синхронизировать сейчас»</span>';
|
||
document.getElementById('srv-src').innerHTML=d.source?'источник: <code>'+esc(d.source)+'</code>'+(d.source_offset?' · расхождение '+esc(d.source_offset):''):'';
|
||
var s=Math.round(base.shift/1000), df=document.getElementById('srv-diff');
|
||
df.style.color=Math.abs(s)>=5?'var(--warn)':'';
|
||
df.textContent=Math.abs(s)<2?'часы вашего компьютера совпадают с сервером':'часы вашего компьютера '+(s>0?'отстают от сервера':'спешат относительно сервера')+' на '+Math.abs(s)+' с'+(Math.abs(s)>=5&&d.synced?' — время сервера точное, поправьте часы компьютера':'');
|
||
}).catch(function(){}); }
|
||
sync(); setInterval(sync,30000); setInterval(draw,1000);
|
||
})();
|
||
</script>
|
||
</div>
|
||
{{$h := httpsCert}}
|
||
<div class="card" id="sec-https">
|
||
<h2><span class="dot {{if and .Settings.HTTPS.RedirectOn (not $h.Err)}}ok{{else}}warn{{end}}"></span>HTTPS — шифрование входа в bj</h2>
|
||
<p class="muted">Пароли администратора, баз данных, хранилища и ключ продукта не должны идти по сети открытым текстом. bj всегда слушает HTTPS: <a href="https://{{serverIP}}:{{if .Settings.HTTPS.Port}}{{.Settings.HTTPS.Port}}{{else}}8443{{end}}/admin/">https://{{serverIP}}:{{if .Settings.HTTPS.Port}}{{.Settings.HTTPS.Port}}{{else}}8443{{end}}/</a>. Модули bj и API на этом сервере работают по HTTP, как раньше.</p>
|
||
<p style="margin:8px 0">{{if $h.Err}}<span style="color:var(--err)">✗ сертификат: {{$h.Err}}</span>{{else}}Сертификат: <b>{{$h.CN}}</b>, до {{$h.Until.Format "02.01.2006"}} — {{if .Settings.HTTPS.Custom}}сертификат организации{{else}}самоподписанный: браузер один раз попросит подтвердить исключение; чтобы не просил — загрузите сертификат организации{{end}}.{{end}}</p>
|
||
<form method="post" action="/admin/setup/https" style="margin:8px 0">
|
||
<label style="display:flex;align-items:center;gap:8px;font-weight:400"><input type="checkbox" name="redirect" style="width:auto"{{if .Settings.HTTPS.RedirectOn}} checked{{end}}> <b>Открывать bj только по HTTPS</b> — браузер с http:// перенаправляется на https://</label>
|
||
<button type="submit" class="btn" style="margin-top:8px">Сохранить</button>
|
||
</form>
|
||
<details style="margin-top:8px"><summary style="cursor:pointer;color:var(--accent)">Сертификат организации</summary>
|
||
<form method="post" action="/admin/setup/https/cert" enctype="multipart/form-data" style="display:grid;gap:8px;max-width:520px;margin-top:8px">
|
||
<label>Сертификат (PEM, с цепочкой)<input type="file" name="cert" accept=".pem,.crt,.cer" style="width:100%"></label>
|
||
<label>Закрытый ключ (PEM)<input type="file" name="key" accept=".pem,.key" style="width:100%"></label>
|
||
<div><button type="submit" class="btn">Установить сертификат</button></div>
|
||
</form>
|
||
<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-network">
|
||
<h2>Сеть</h2>
|
||
<p style="margin:6px 0">Сервер: <code>{{serverAddrs}}</code></p>
|
||
<p class="muted">Чтобы адрес bj не менялся, задайте серверу постоянный адрес — на консоли сервера (или по SSH): <code>sudo bj-network</code>. Утилита спросит адрес, шлюз и DNS и применит их с защитой: если связь пропадёт, через 2 минуты вернутся прежние настройки. На Astra Linux сеть настраивается средствами системы.</p>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="settings-section" id="sec-backup">
|
||
<h1>Резервное копирование</h1>
|
||
{{template "backupCard" .}}
|
||
</section>
|
||
|
||
<section class="settings-section" id="sec-license">
|
||
<h1>Лицензия</h1>
|
||
<div class="card">
|
||
<h2>
|
||
{{if .License.Valid}}<span class="dot ok"></span>Активна
|
||
{{else if .License.Present}}<span class="dot err"></span>Недействительна
|
||
{{else}}<span class="dot warn"></span>Не активирована{{end}}
|
||
</h2>
|
||
{{if .License.Present}}
|
||
<table style="margin-top:8px">
|
||
<tbody>
|
||
{{if .License.Tenant}}<tr><td style="width:180px" class="muted">Организация</td><td><strong>{{.License.Tenant}}</strong></td></tr>{{end}}
|
||
{{if .License.Plan}}<tr><td class="muted">Лицензия</td><td><span class="badge confirmed">{{if eq .License.Plan "enterprise"}}полная (все модули){{else}}базовая{{end}}</span></td></tr>{{end}}
|
||
<tr><td class="muted">Тип ключа</td><td>{{if .License.Online}}онлайн: выдан сервером лицензий этой установке, продлевается автоматически{{else}}офлайн (закрытый контур){{end}}</td></tr>
|
||
{{if not .License.ExpiresAt.IsZero}}<tr><td class="muted">Ключ действует до</td><td>{{.License.ExpiresAt.Format "02.01.2006"}} {{if .License.Valid}}<span class="muted">(осталось {{.License.DaysLeft}} дн.)</span>{{end}}</td></tr>{{end}}
|
||
{{if not .License.ContractUntil.IsZero}}<tr><td class="muted">Договор до</td><td>{{.License.ContractUntil.Format "02.01.2006"}}</td></tr>{{end}}
|
||
<tr><td class="muted">Обновления</td><td>{{if .License.AllowsUpdates}}<span style="color:var(--ok)">включены</span>{{else}}<span class="muted">не входят в план</span>{{end}}</td></tr>
|
||
<tr><td class="muted" style="vertical-align:top">Модули</td><td>{{range .Features}}<div style="display:flex;gap:8px;align-items:center;margin:2px 0"><span class="dot {{if .On}}ok{{else}}err{{end}}"></span>{{.Title}}{{if devOn}} <code style="font-size:11px;color:var(--muted)">{{.Key}}</code>{{end}}{{if not .On}}<span class="muted" style="font-size:11px">— не входит в лицензию</span>{{end}}</div>{{end}}</td></tr>
|
||
<tr><td class="muted">Статус</td><td>{{.License.Message}}</td></tr>
|
||
</tbody>
|
||
</table>
|
||
{{else}}
|
||
<p class="muted">Лицензионный ключ не введён. Без лицензии сервис работает, но автообновления заблокированы.</p>
|
||
{{end}}
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>Активация на сервере лицензий</h2>
|
||
<p class="muted">Введите код активации от поставщика (вида <code>BJ-XXXX-XXXX-XXXX</code>). bj зарегистрирует эту установку на сервере лицензий <code>lic.zetit.ru</code> и дальше сам продлевает ключ раз в сутки. Серверу bj нужен доступ по HTTPS к lic.zetit.ru.</p>
|
||
<form method="post" action="/admin/setup/license/activate" style="margin-top:12px;display:grid;gap:10px;max-width:520px">
|
||
<label>Код активации <input type="text" name="code" placeholder="BJ-XXXX-XXXX-XXXX" autocomplete="off" spellcheck="false" style="width:100%;font-family:ui-monospace,monospace;letter-spacing:.04em"></label>
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap"><button type="submit" class="btn btn-ok">Активировать</button>{{if .License.Present}}<button type="submit" formaction="/admin/setup/license/renew" class="btn" title="Запросить у сервера лицензий свежий ключ: срок, модули, режим разработки">Обновить с сервера лицензий</button>{{end}}</div>
|
||
</form>
|
||
<p class="muted" style="font-size:12px;margin-top:12px">Установка <code>{{if .Settings.License.InstallID}}{{.Settings.License.InstallID}}{{else}}будет создана при первом обращении{{end}}</code>{{if not .Settings.License.LastSync.IsZero}} · последнее продление {{.Settings.License.LastSync.Format "02.01.2006 15:04"}}{{end}}</p>
|
||
{{if .Settings.License.LastMsg}}<p style="font-size:13px;margin-top:6px;{{if .Settings.License.LastOK}}color:var(--ok){{else}}color:var(--err){{end}}">{{if .Settings.License.LastOK}}✓{{else}}✗{{end}} {{.Settings.License.LastMsg}} <span class="muted">({{.Settings.License.LastAttempt.Format "02.01.2006 15:04"}})</span></p>{{end}}
|
||
</div>
|
||
|
||
<div class="card">
|
||
<details>
|
||
<summary style="cursor:pointer;font-weight:600">Офлайн-ключ для закрытого контура</summary>
|
||
<p class="muted" style="margin-top:8px">Если у сервера bj нет доступа в интернет, вставьте офлайн-ключ, выданный поставщиком. Он проверяется по подписи без связи с сервером лицензий и не продлевается.</p>
|
||
<form method="post" action="/admin/setup/license" style="margin-top:10px;display:grid;gap:10px;max-width:720px">
|
||
<textarea name="key" rows="3" style="font-family:ui-monospace,monospace;font-size:11px" placeholder="payload.signature.keyid"></textarea>
|
||
<div><button type="submit" class="btn">Сохранить офлайн-ключ</button></div>
|
||
</form>
|
||
</details>
|
||
</div>
|
||
</section>
|
||
|
||
{{/* ============ ОФОРМЛЕНИЕ (логотип отчётов) ============ */}}
|
||
<section class="settings-section" id="sec-branding">
|
||
<h1>Оформление отчётов</h1>
|
||
<div class="card">
|
||
<h2>Логотип организации в шапке отчётов</h2>
|
||
<p class="muted">Логотип показывается <b>справа</b> в шапке всех отчётов для регулятора (слева — логотип MOEX МОСТ / НРД){{if devOn}} и на титульной сводке пакета тестов{{end}}. Встраивается прямо в HTML — отчёт остаётся автономным. Если не загружать — используется стандартный логотип «Фонтвьель».</p>
|
||
|
||
<div style="display:flex;align-items:center;gap:18px;flex-wrap:wrap;margin:14px 0;padding:14px;background:var(--card-2);border-radius:12px">
|
||
<div>
|
||
<div class="muted" style="font-size:12px;margin-bottom:6px">Сейчас в отчётах:</div>
|
||
{{if logoSrc .Settings.Branding.LogoDataURI}}<img src="{{logoSrc .Settings.Branding.LogoDataURI}}" alt="логотип" style="height:40px;max-width:280px;background:#fff;padding:6px 10px;border-radius:8px;border:1px solid var(--border)">{{else}}<span class="muted">логотип не загружен</span>{{end}}
|
||
</div>
|
||
<div class="muted" style="font-size:12px">
|
||
{{if .Settings.Branding.LogoDataURI}}Загружен свой логотип{{if .Settings.Branding.LogoName}}: <b>{{.Settings.Branding.LogoName}}</b>{{end}}.{{else}}Свой логотип не загружен — в шапке отчётов справа пусто.{{end}}
|
||
</div>
|
||
</div>
|
||
|
||
<form method="post" action="/admin/setup/logo" enctype="multipart/form-data" style="display:flex;gap:10px;flex-wrap:wrap;align-items:center">
|
||
<input type="file" name="logo" accept="image/png,image/jpeg,image/gif,image/webp,image/svg+xml,.png,.jpg,.jpeg,.gif,.webp,.svg" required>
|
||
<button type="submit" class="btn btn-ok">Загрузить логотип</button>
|
||
</form>
|
||
<p class="muted" style="font-size:12px;margin-top:8px">Форматы: PNG, JPG, GIF, WEBP, SVG. Максимум 512 КБ. Рекомендуется горизонтальный логотип (высота в шапке — 28px).</p>
|
||
|
||
{{if .Settings.Branding.LogoDataURI}}
|
||
<form method="post" action="/admin/setup/logo/reset" style="margin-top:8px" onsubmit="return confirm('Удалить загруженный логотип?');">
|
||
<button type="submit" class="btn">Удалить логотип</button>
|
||
</form>
|
||
{{end}}
|
||
</div>
|
||
</section>
|
||
|
||
{{if devOn}} {{/* ============ ПЕРСПЕКТИВНЫЕ МОДУЛИ (под паролем) ============ */}}
|
||
<section class="settings-section" id="sec-advanced">
|
||
<h1>Перспективные модули</h1>
|
||
<p class="muted" style="max-width:64ch">Модули для будущих профилей внедрения <strong>«автомат»</strong> и <strong>«полуавтомат»</strong> (когда используется собственный личный кабинет, а не ЕСИА Финанс). Пока не в проде — скрыты, чтобы не мешать текущей работе. В профиле «через Финансы» они не нужны: проверку и блокировку бумаг закрывает сам ЕСИА Финанс.</p>
|
||
|
||
<div class="card">
|
||
<h2>Что здесь будет</h2>
|
||
<div class="tw" style="margin-top:10px"><table>
|
||
<tbody>
|
||
<tr><td style="width:34%"><strong>QUIK — Модуль неторговых поручений</strong></td><td class="muted">Блокировка/разблокировка и проверка количества бумаг в торговой системе — для собственного ЛК.</td></tr>
|
||
<tr><td><strong>Обмен с DEPO</strong></td><td class="muted">Проверка заявки по учёту клиента через БД («кран») перед отправкой в НРД — для фирм без ЕСИА Финанс.</td></tr>
|
||
</tbody>
|
||
</table></div>
|
||
</div>
|
||
|
||
{{if .AdvancedUnlocked}}
|
||
<div class="card" style="border-color:var(--ok,#16a34a)">
|
||
<h2><span class="dot ok"></span>Доступ открыт</h2>
|
||
<p class="muted">Разделы <strong>«Обмен с DEPO»</strong> и <strong>«QUIK МНП»</strong> появились в меню слева. Это незавершённые модули — меняйте настройки, только если понимаете последствия.</p>
|
||
<form method="post" action="/admin/setup/advanced-lock" style="margin-top:12px"><button type="submit" class="btn">Скрыть снова</button></form>
|
||
</div>
|
||
{{else}}
|
||
<div class="card">
|
||
<h2>Открыть модули</h2>
|
||
<p class="muted">Введите пароль администратора, чтобы временно открыть перспективные разделы. Доступ запоминается в этом браузере до нажатия «Скрыть».</p>
|
||
<form method="post" action="/admin/setup/advanced-unlock" style="margin-top:12px;display:flex;gap:8px;flex-wrap:wrap;align-items:center">
|
||
<input type="password" name="password" placeholder="Пароль" autocomplete="off" style="max-width:260px">
|
||
<button type="submit" class="btn">Открыть</button>
|
||
</form>
|
||
</div>
|
||
{{end}}
|
||
</section>{{end}}
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
(function() {
|
||
// Переключение разделов админ-центра + запоминание выбранного.
|
||
var navs = document.querySelectorAll('.settings-nav button');
|
||
var secs = document.querySelectorAll('.settings-section');
|
||
function show(id) {
|
||
navs.forEach(function(b){ b.classList.toggle('active', b.dataset.sec === id); });
|
||
secs.forEach(function(s){ s.classList.toggle('active', s.id === 'sec-' + id); });
|
||
// Запоминаем вкладку — чтобы после сохранения (редирект без хэша) вернуться
|
||
// на неё, а не на дефолтный «Обзор».
|
||
try { sessionStorage.setItem('bj-setup-tab', id); } catch(e) {}
|
||
try { history.replaceState(null, '', '#' + id); } catch(e) {}
|
||
}
|
||
// выбрали другой раздел — с его начала, а не с той же высоты, где листали прежний
|
||
navs.forEach(function(b){ b.addEventListener('click', function(){ var same=b.classList.contains('active'); show(b.dataset.sec); if(!same) window.scrollTo(0,0); }); });
|
||
var h = (location.hash || '').replace('#','');
|
||
// Хэш в URL имеет приоритет. Иначе — после сохранения (есть ?flash/?scrollto)
|
||
// восстанавливаем последнюю вкладку из sessionStorage.
|
||
var afterSave = /[?&](flash|scrollto)=/.test(location.search);
|
||
if (h && document.getElementById('sec-' + h)) {
|
||
show(h);
|
||
} else if (afterSave) {
|
||
var saved = null;
|
||
try { saved = sessionStorage.getItem('bj-setup-tab'); } catch(e) {}
|
||
if (saved && document.getElementById('sec-' + saved)) show(saved);
|
||
}
|
||
})();
|
||
</script>
|
||
{{end}}
|