8099e50ab2
- по умолчанию https://lic.zetit.ru/updates; клиент артефактории передаёт лицензионный токен установки (Authorization: Bearer), сервер отдаёт модули по признакам лицензии - компоненты получили признак лицензии (ядро и криптосервис — updates, ЛК — web-cabinet, НПР — npr, экспорт QUIK — quik-export) - в bj зашит открытый ключ подписи релизов (закрытый хранится вне сервера лицензий) - проверено сквозной установкой bj-lk 0.2.0 из канала beta Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1122 lines
130 KiB
HTML
1122 lines
130 KiB
HTML
{{define "content"}}
|
||
{{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">
|
||
<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 or .AdvancedUnlocked (index .Modules "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>Обмен Fancy<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>
|
||
<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 {{if not .QuikExport.Licensed}}err{{else if and .QuikExport.S.Enabled (eq .QuikExport.Service.Active "active")}}ok{{else if .QuikExport.S.Enabled}}warn{{end}}"></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 .Settings.Crypto.Profile}}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>
|
||
<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="muted" style="font-size:10px;font-weight:500">ЕСИА Финанс</span><span class="ind {{if .Settings.LK.PublicURL}}ok{{else}}warn{{end}}"></span></button>
|
||
<button data-sec="tests"><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="M9 3h6M10 3v6l-5 8.5A2 2 0 0 0 6.7 21h10.6a2 2 0 0 0 1.7-3.5L14 9V3"/><path d="M8 15h8"/></svg></span>Тесты</button>
|
||
<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 .Settings.Update.Available}}<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>
|
||
<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>
|
||
</nav>
|
||
|
||
<div class="settings-body">
|
||
|
||
{{/* ============ ОБЗОР ============ */}}
|
||
<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>{{.Name}}</strong></div>
|
||
<div class="muted" style="font-size:12px;margin-top:4px">{{if .Configured}}настроено{{else}}не настроено{{end}}</div>
|
||
</div>
|
||
{{end}}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{{/* ============ БАЗА ДАННЫХ ============ */}}
|
||
<section class="settings-section" id="sec-db">
|
||
<h1>База данных</h1>
|
||
<div class="card">
|
||
<h2><span class="dot {{if .Settings.Postgres.DSN}}ok{{else}}err{{end}}"></span>PostgreSQL</h2>
|
||
<p class="muted">Журнал сделок m2m-core и принимающая БД. Сейчас: {{if .Settings.Postgres.DSN}}<code>подключено</code>{{else}}<code>in-memory</code> (данные не сохраняются){{end}}.</p>
|
||
{{if 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}}…или ввести DSN вручную{{end}}</summary>
|
||
<form method="post" action="/admin/setup/postgres" style="margin-top:12px;display:grid;gap:10px;max-width:640px">
|
||
<label>DSN</label>
|
||
<input type="text" name="dsn" value="{{.Settings.Postgres.DSN}}" placeholder="postgres://bj:secret@127.0.0.1:5432/bj?sslmode=disable">
|
||
<p class="muted" style="margin:0">При сохранении выполняется Ping.</p>
|
||
<div><button type="submit" class="btn">Сохранить и проверить</button></div>
|
||
</form>
|
||
</details>
|
||
</div>
|
||
</section>
|
||
|
||
{{if .AdvancedUnlocked}}
|
||
{{/* ============ ОБМЕН FANCY (MSSQL) — перспективный модуль ============ */}}
|
||
<section class="settings-section" id="sec-fancy">
|
||
<h1>Обмен Fancy (MSSQL / PostgreSQL)</h1>
|
||
<div class="card" id="sec-fancy-conn">
|
||
<h2><span class="dot {{if .Settings.Fancy.DepoTablesCheckOK}}ok{{else if .Settings.Fancy.Server}}warn{{else}}err{{end}}"></span>Подключение к серверу БД клиента</h2>
|
||
<p class="muted">Сервер учётной базы Fancy DEPO: картотека лиц, счета и договоры, остатки, опции (признак квалифицированного инвестора), трансферные таблицы обмена. Поддерживаются <strong>Microsoft SQL Server</strong> и <strong>PostgreSQL</strong>. База указывается ниже, в карточке «Трансферные таблицы DEPO».</p>
|
||
<form method="post" action="/admin/setup/fancy" style="margin-top:12px;display:grid;gap:10px;max-width:560px">
|
||
<label>Тип СУБД
|
||
<select name="db_type" id="fancy-dbtype" style="width:100%" onchange="var p=document.getElementById('fancy-port');if(!p.value||p.value=='1433'||p.value=='5432'){p.value=this.value=='postgres'?'5432':'1433';}">
|
||
<option value="mssql" {{if not .Settings.Fancy.IsPostgres}}selected{{end}}>Microsoft SQL Server</option>
|
||
<option value="postgres" {{if .Settings.Fancy.IsPostgres}}selected{{end}}>PostgreSQL</option>
|
||
</select></label>
|
||
<label>Сервер (host или host\instance)
|
||
<input type="text" name="server" value="{{.Settings.Fancy.Server}}" placeholder="10.10.10.x" style="width:100%" required></label>
|
||
<label>Порт
|
||
<input type="number" id="fancy-port" name="port" value="{{if .Settings.Fancy.Port}}{{.Settings.Fancy.Port}}{{else}}{{.Settings.Fancy.DefaultPort}}{{end}}" style="width:100%"></label>
|
||
<label>Пользователь (логин БД)
|
||
<input type="text" name="user" value="{{.Settings.Fancy.User}}" placeholder="bj_user" style="width:100%" required></label>
|
||
<label>Пароль
|
||
<input type="password" name="password" placeholder="{{if .Settings.Fancy.Password}}•••••• — оставьте пустым, чтобы не менять{{else}}пароль SQL-логина{{end}}" autocomplete="off" style="width:100%"></label>
|
||
<label>Шифрование канала
|
||
<select name="encrypt" style="width:100%">
|
||
<option value="disable" {{if eq .Settings.Fancy.Encrypt "disable"}}selected{{end}}>disable — без TLS (типично для LAN)</option>
|
||
<option value="true" {{if eq .Settings.Fancy.Encrypt "true"}}selected{{end}}>true — с TLS</option>
|
||
</select></label>
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap"><button type="submit" class="btn btn-ok">Сохранить</button><button type="submit" formaction="/admin/setup/fancy/test" class="btn">Проверить соединение</button></div>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="card" id="sec-fancy-depo-tables">
|
||
<h2><span class="dot {{if .Settings.Fancy.DepoTablesCheckOK}}ok{{else if .Settings.Fancy.Server}}warn{{else}}err{{end}}"></span>Трансферные таблицы DEPO</h2>
|
||
<p class="muted">Через эти таблицы bj обменивается с учётной системой DEPO: поручения <code>FTV_M2M</code> и их строки <code>FTV_M2M_LINES</code>, события <code>FTV_M2M_EVENTS</code>, справочник участников НРД <code>FTV_M2M_HANDBOOK</code>. Таблицы создаются в базе DEPO на сервере из подключения выше ({{if .DepoTables.Postgres}}PostgreSQL{{else}}Microsoft SQL Server{{end}}). Создание можно повторять: добавляются только недостающие таблицы, колонки и индексы, данные не меняются.</p>
|
||
<form method="post" action="/admin/setup/fancy/depo-tables" style="margin-top:10px;display:grid;gap:10px;max-width:720px">
|
||
<input type="hidden" name="section" value="sec-fancy-depo-tables">
|
||
<label>База DEPO
|
||
<input type="text" name="depo_database" value="{{.Settings.Fancy.DepoDatabase}}" placeholder="{{.DepoTables.DB}}" pattern="[A-Za-z][A-Za-z0-9_]*" style="width:100%"></label>
|
||
<label>Признак ИИС в названии брокерского договора <span class="muted" style="font-weight:400">(с чего начинается номер договора ИИС в DEPO; несколько — через запятую)</span>
|
||
<input type="text" name="iis_contract_marker" value="{{.Settings.Fancy.IISContractMarker}}" placeholder="БРИИС" style="width:100%"></label>
|
||
<label style="display:flex;gap:8px;align-items:flex-start;font-weight:600"><input type="radio" name="depo_tables_mode" value="auto" {{if eq .Settings.Fancy.DepoTablesMode "auto"}}checked{{end}}> <span>Создаёт bj <span class="muted" style="font-weight:400">— вводите логин с правами на создание таблиц в базе DEPO. Он используется один раз и не сохраняется; bj заодно выдаст права своему логину.</span></span></label>
|
||
<label style="display:flex;gap:8px;align-items:flex-start;font-weight:600"><input type="radio" name="depo_tables_mode" value="script" {{if eq .Settings.Fancy.DepoTablesMode "script"}}checked{{end}}> <span>Создаёт администратор СУБД по скрипту <span class="muted" style="font-weight:400">— скачайте скрипт и передайте администратору, после выполнения нажмите «Проверить таблицы».</span></span></label>
|
||
<div><button type="submit" class="btn">Сохранить</button></div>
|
||
</form>
|
||
|
||
{{if eq .Settings.Fancy.DepoTablesMode "auto"}}
|
||
<form method="post" action="/admin/setup/fancy/depo-tables/create" style="margin-top:12px;display:grid;gap:10px;max-width:720px">
|
||
<input type="hidden" name="section" value="sec-fancy-depo-tables">
|
||
<div><b>Логин с правами на базу {{.DepoTables.DB}}</b><br>
|
||
<span class="muted" style="font-size:12px">{{if .DepoTables.Postgres}}Владелец базы или суперпользователь.{{else}}Роль db_owner в базе (или db_ddladmin + db_securityadmin).{{end}} Если оставить поля пустыми, bj попробует своим логином {{.DepoTables.User}}.</span></div>
|
||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:10px">
|
||
<label>Логин <input type="text" name="admin_user" autocomplete="off" placeholder="{{if .DepoTables.Postgres}}postgres{{else}}sa{{end}}" style="width:100%"></label>
|
||
<label>Пароль <input type="password" name="admin_password" autocomplete="new-password" style="width:100%"></label>
|
||
</div>
|
||
<div><button type="submit" class="btn btn-ok" onclick="return confirm('Создать трансферные таблицы в базе {{.DepoTables.DB}}?')">⚙ Создать таблицы</button></div>
|
||
</form>
|
||
{{else if eq .Settings.Fancy.DepoTablesMode "script"}}
|
||
<div style="margin-top:12px"><b>Скрипт для администратора СУБД</b> — <a href="/admin/setup/fancy/depo-tables/script.sql">скачать {{.DepoTables.ScriptFile}}</a>
|
||
<pre class="copyable" style="max-height:360px;overflow:auto">{{.DepoTables.Script}}</pre></div>
|
||
{{end}}
|
||
|
||
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:12px">
|
||
<form method="post" action="/admin/setup/fancy/depo-tables/check" style="margin:0"><input type="hidden" name="section" value="sec-fancy-depo-tables"><button type="submit" class="btn btn-ok">✓ Проверить таблицы</button></form>
|
||
<span class="muted" style="font-size:12px">логином bj: таблицы на месте, колонки совпадают, есть права на чтение и запись</span>
|
||
</div>
|
||
{{if .Settings.Fancy.DepoTablesCheck}}<p style="margin-top:10px;font-size:13px;{{if .Settings.Fancy.DepoTablesCheckOK}}color:var(--ok){{else}}color:var(--err){{end}}">{{if .Settings.Fancy.DepoTablesCheckOK}}✓{{else}}✗{{end}} {{.Settings.Fancy.DepoTablesCheck}} <span class="muted">({{.Settings.Fancy.DepoTablesCheckAt.Format "02.01.2006 15:04"}})</span></p>{{end}}
|
||
</div>
|
||
|
||
<div class="card" id="sec-fancy-depo-job">
|
||
<h2><span class="dot {{if .Settings.Fancy.DepoJobCheckOK}}ok{{else if .Settings.Fancy.DepoJobMode}}warn{{else}}err{{end}}"></span>Где запускается обработка трансферных таблиц DEPO</h2>
|
||
<p class="muted">Поручения и события, которые bj пишет в <code>FTV_M2M*</code>, обрабатывает процедура <code>{{.DepoJob.Proc}}</code> в базе DEPO. Выберите, кто её запускает, выполните инструкцию и нажмите «Проверить готовность» — без зелёной отметки обмен с DEPO работать не будет.</p>
|
||
<form method="post" action="/admin/setup/fancy/depo-job" style="margin-top:10px;display:grid;gap:10px;max-width:720px">
|
||
<input type="hidden" name="section" value="sec-fancy-depo-job">
|
||
<label style="display:flex;gap:8px;align-items:flex-start;font-weight:600"><input type="radio" name="depo_job_mode" value="bj" {{if eq .Settings.Fancy.DepoJobMode "bj"}}checked{{end}}> <span>Внутри bj <span class="muted" style="font-weight:400">— bj сам вызывает процедуру по расписанию. На сервере клиента нужно одно право на процедуру. Рекомендуется.</span></span></label>
|
||
<label style="display:flex;gap:8px;align-items:flex-start;font-weight:600"><input type="radio" name="depo_job_mode" value="sql-auto" {{if eq .Settings.Fancy.DepoJobMode "sql-auto"}}checked{{end}}> <span>Джоба SQL Agent, создаёт bj <span class="muted" style="font-weight:400">— логину bj выдаётся роль оператора SQL Agent, bj создаёт джобу кнопкой.</span></span></label>
|
||
<label style="display:flex;gap:8px;align-items:flex-start;font-weight:600"><input type="radio" name="depo_job_mode" value="sql-manual" {{if eq .Settings.Fancy.DepoJobMode "sql-manual"}}checked{{end}}> <span>Джоба SQL Agent, настраивает администратор <span class="muted" style="font-weight:400">— передаёте скрипт, bj проверяет, что джоба работает.</span></span></label>
|
||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px">
|
||
<label>Интервал, секунд <input type="number" name="depo_job_every_sec" min="5" max="3600" value="{{.DepoJob.Every}}" style="width:100%"></label>
|
||
<label>Процедура <input type="text" name="depo_job_proc" value="{{.Settings.Fancy.DepoJobProc}}" placeholder="dbo.FTV_M2M_JOB" style="width:100%"></label>
|
||
<label>Работает от имени <input type="text" name="depo_job_run_as" value="{{.Settings.Fancy.DepoJobRunAs}}" placeholder="SYSDBA" style="width:100%"></label>
|
||
</div>
|
||
<p class="muted" style="font-size:12px;margin:0">Процедура обработки выполняется от имени пользователя базы DEPO (у разработчика Fancy это <code>SYSDBA</code>). У клиента такого пользователя может не быть или он называется иначе: «Проверить готовность» проверит, что он есть в базе.</p>
|
||
<div><button type="submit" class="btn">Сохранить способ</button></div>
|
||
</form>
|
||
|
||
{{if eq .Settings.Fancy.DepoJobMode "bj"}}
|
||
<div style="margin-top:12px"><b>Инструкция для администратора SQL клиента</b> — выполнить один раз:
|
||
<pre class="copyable">{{.DepoJob.GrantScript}}</pre></div>
|
||
{{else if eq .Settings.Fancy.DepoJobMode "sql-auto"}}
|
||
<div style="margin-top:12px"><b>Инструкция для администратора SQL клиента</b> — выдать логину bj права оператора SQL Agent (SQL Agent должен быть запущен):
|
||
<pre class="copyable">{{.DepoJob.AgentRights}}</pre>
|
||
<form method="post" action="/admin/setup/fancy/depo-job/create" style="margin:8px 0 0"><input type="hidden" name="section" value="sec-fancy-depo-job"><button type="submit" class="btn btn-ok" onclick="return confirm('Создать джобу SQL Agent {{.DepoJob.Name}} на сервере клиента?')">⚙ Создать джобу</button></form></div>
|
||
{{else if eq .Settings.Fancy.DepoJobMode "sql-manual"}}
|
||
<div style="margin-top:12px"><b>Инструкция для администратора SQL клиента</b> — создать джобу (SQL Agent должен быть запущен, служебная учётная запись Agent должна иметь право выполнять процедуру):
|
||
<pre class="copyable">{{.DepoJob.AgentScript}}</pre></div>
|
||
{{end}}
|
||
|
||
{{if .Settings.Fancy.DepoJobMode}}
|
||
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:12px">
|
||
<form method="post" action="/admin/setup/fancy/depo-job/check" style="margin:0"><input type="hidden" name="section" value="sec-fancy-depo-job"><button type="submit" class="btn btn-ok">✓ Проверить готовность</button></form>
|
||
<span class="muted" style="font-size:12px">для вариантов с джобой bj пишет служебное событие BJ_PING и ждёт до 40 с, пока обработчик его отметит</span>
|
||
</div>
|
||
{{if .Settings.Fancy.DepoJobCheck}}<p style="margin-top:10px;font-size:13px;{{if .Settings.Fancy.DepoJobCheckOK}}color:var(--ok){{else}}color:var(--err){{end}}">{{if .Settings.Fancy.DepoJobCheckOK}}✓{{else}}✗{{end}} {{.Settings.Fancy.DepoJobCheck}} <span class="muted">({{.Settings.Fancy.DepoJobCheckAt.Format "02.01.2006 15:04"}})</span></p>{{end}}
|
||
{{if and (eq .Settings.Fancy.DepoJobMode "bj") .Settings.Fancy.DepoJobLast}}<p class="muted" style="font-size:12px;margin-top:6px">Последний вызов процедуры — {{.Settings.Fancy.DepoJobLast}}</p>{{end}}
|
||
{{end}}
|
||
</div>
|
||
|
||
</section>
|
||
|
||
{{/* ============ QUIK МНП ============ */}}
|
||
<section class="settings-section" id="sec-quik">
|
||
<h1>QUIK — Модуль неторговых поручений</h1>
|
||
<div class="card">
|
||
<h2><span class="dot {{if .Settings.Quik.Verified}}ok{{else if .Settings.Quik.Server}}warn{{else}}err{{end}}"></span>Подключение к БД МНП QUIK (MSSQL)</h2>
|
||
<p class="muted">При выводе бумаг клиентом bj блокирует их количество в QUIK через API Модуля неторговых поручений (хранимые процедуры). БД <strong>боевая</strong> — тест только на чтение (версия + наличие M2M-объектов), без записи. Подключение под логином с ролью <code>InstrEu</code>.<br>
|
||
<a href="/admin/help/quik"><strong>Инструкция для администратора QUIK</strong></a> — что настроить в QuikAdmin для M2M · <a href="/admin/docs/">документы ARQA</a>.</p>
|
||
<form method="post" action="/admin/setup/quik" style="margin-top:12px;display:grid;gap:10px;max-width:560px">
|
||
<label>Сервер (host или host\instance)
|
||
<input type="text" name="server" value="{{.Settings.Quik.Server}}" placeholder="91.209.122.88" style="width:100%" required></label>
|
||
<label>Порт
|
||
<input type="number" name="port" value="{{if .Settings.Quik.Port}}{{.Settings.Quik.Port}}{{else}}1433{{end}}" placeholder="8849" style="width:100%"></label>
|
||
<label>Имя БД (если требуется, иначе оставьте пустым)
|
||
<input type="text" name="database" value="{{.Settings.Quik.Database}}" placeholder="(по умолчанию)" style="width:100%"></label>
|
||
<label>Логин (роль InstrEu)
|
||
<input type="text" name="user" value="{{.Settings.Quik.User}}" placeholder="BROKER_88" style="width:100%" required></label>
|
||
<label>Пароль
|
||
<input type="password" name="password" placeholder="{{if .Settings.Quik.Password}}•••••• — оставьте пустым, чтобы не менять{{else}}пароль SQL-логина{{end}}" autocomplete="off" style="width:100%"></label>
|
||
<label>Шифрование канала
|
||
<select name="encrypt" style="width:100%">
|
||
<option value="true" {{if eq .Settings.Quik.Encrypt "true"}}selected{{end}}>true — с TLS (боевой сервер обычно требует)</option>
|
||
<option value="disable" {{if eq .Settings.Quik.Encrypt "disable"}}selected{{end}}>disable — без TLS</option>
|
||
</select></label>
|
||
<label style="display:flex;gap:8px;align-items:center;font-weight:400">
|
||
<input type="checkbox" name="trust" value="1" {{if .Settings.Quik.Trust}}checked{{end}}>
|
||
Доверять сертификату сервера (TrustServerCertificate — для самоподписанного)</label>
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
||
<button type="submit" class="btn btn-ok">Сохранить</button>
|
||
</div>
|
||
</form>
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:12px">
|
||
<form method="post" action="/admin/setup/quik/test" style="margin:0"><button type="submit" class="btn">Проверить подключение (чтение)</button></form>
|
||
</div>
|
||
<p class="muted" style="margin-top:12px">Статус: {{if .Settings.Quik.Verified}}<span style="color:var(--ok)">✓ подключение проверено</span>{{else}}<span class="muted">не проверено</span>{{end}}</p>
|
||
</div>
|
||
|
||
<div class="card" id="sec-quik-cycle">
|
||
<h2><span class="dot {{if not .Settings.Quik.WritesEnabled}}warn{{else if or .Settings.Quik.AutoOut .Settings.Quik.AutoIn}}ok{{else}}warn{{end}}"></span>Автоматический цикл M2M в МНП</h2>
|
||
<p class="muted">Что делает bj в Модуле неторговых поручений по ходу перевода: <strong>исходящий</strong> — при отправке в НРД регистрирует «Вывод ЦБ (M2M)» (QUIK блокирует бумаги), по подтверждению подписывает (списание), по отказу снимает с причиной; <strong>входящий</strong> — после приёма нашего поручения НРД регистрирует «Ввод ЦБ (M2M)» (зачисление сразу). Проверено на боевом МНП 09.09.2026. Каждый шаг виден в карточке перевода и на странице <a href="/admin/quik">QUIK МНП</a>.</p>
|
||
<form method="post" action="/admin/setup/quik/cycle" style="margin-top:12px;display:grid;gap:10px;max-width:640px">
|
||
<input type="hidden" name="section" value="sec-quik-cycle">
|
||
<label style="display:flex;gap:8px;align-items:center;font-weight:600">
|
||
<input type="checkbox" name="writes_enabled" value="1" {{if .Settings.Quik.WritesEnabled}}checked{{end}}>
|
||
Разрешить записи в МНП (главный предохранитель — база боевая)</label>
|
||
<label style="display:flex;gap:8px;align-items:center;font-weight:400;margin-left:22px">
|
||
<input type="checkbox" name="auto_out" value="1" {{if .Settings.Quik.AutoOut}}checked{{end}}>
|
||
Исходящие переводы: регистрировать при отправке, подписывать / снимать по ответу автоматически</label>
|
||
<label style="display:flex;gap:8px;align-items:center;font-weight:400;margin-left:22px">
|
||
<input type="checkbox" name="auto_in" value="1" {{if .Settings.Quik.AutoIn}}checked{{end}}>
|
||
Входящие переводы: зачислять в QUIK после приёма поручения НРД автоматически</label>
|
||
<label style="display:flex;gap:8px;align-items:center;font-weight:400;margin-left:22px">
|
||
<input type="checkbox" name="poll_terminal" value="1" {{if .Settings.Quik.PollTerminal}}checked{{end}}>
|
||
Принимаем поручения M2M из QUIK: клиентам разрешена подача «Вывод ЦБ (M2M)» из терминала / мобильного приложения — bj опрашивает их и создаёт заявку оператору. Если фирма такую подачу не разрешает, оставьте выключенным: раздел «Поручения QUIK» их показывать не будет.</label>
|
||
<label>Торговый счёт для регистрации поручений
|
||
<input type="text" name="account" value="{{.Settings.Quik.Account}}" placeholder="L01+00000F00" style="width:100%"></label>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
|
||
<label>МНП-инструмент вывода <span class="muted">(пусто = взять из класса INSTROUTM2M)</span>
|
||
<input type="text" name="sec_out" value="{{.Settings.Quik.SecOut}}" placeholder="RUROUTM2M" style="width:100%"></label>
|
||
<label>МНП-инструмент ввода <span class="muted">(пусто = из класса INSTRINM2M)</span>
|
||
<input type="text" name="sec_in" value="{{.Settings.Quik.SecIn}}" placeholder="RURINM2M" style="width:100%"></label>
|
||
</div>
|
||
<label>Исключения кодов клиентов <span class="muted">(по умолчанию «БР-859» → «BR859»; строка «номер портфеля=код QUIK»)</span>
|
||
<textarea name="client_code_map" rows="3" style="width:100%;font-family:ui-monospace,monospace;font-size:12px" placeholder="БР-4-4=BR44">{{quikCodeMap .Settings.Quik.ClientCodeMap}}</textarea></label>
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap"><button type="submit" class="btn btn-ok">Сохранить</button></div>
|
||
</form>
|
||
</div>
|
||
|
||
{{if .Settings.Quik.LastReport}}
|
||
<div class="card">
|
||
<h2>Полный ответ сервера (последний тест)</h2>
|
||
<p class="muted">Можно выделить и скопировать целиком — для отправки коллегам/аутсорсеру QUIK.
|
||
{{if not .Settings.Quik.LastAt.IsZero}}Тест от {{.Settings.Quik.LastAt.Format "02.01.2006 15:04:05"}} UTC.{{end}}</p>
|
||
<div style="display:flex;gap:8px;margin:8px 0">
|
||
<button type="button" class="btn" onclick="var t=document.getElementById('quik-report').textContent;navigator.clipboard&&navigator.clipboard.writeText(t);this.textContent='Скопировано ✓';setTimeout(()=>this.textContent='Скопировать отчёт',1500)">Скопировать отчёт</button>
|
||
</div>
|
||
<pre id="quik-report" style="white-space:pre-wrap;word-break:break-word;background:var(--surface-2,#0d1117);color:var(--code-fg,#c9d1d9);padding:14px;border-radius:8px;font-size:12.5px;line-height:1.5;overflow:auto;max-height:420px;user-select:all">{{.Settings.Quik.LastReport}}</pre>
|
||
</div>
|
||
{{end}}
|
||
</section>
|
||
|
||
{{end}}{{/* /AdvancedUnlocked — конец перспективных модулей Fancy/QUIK */}}
|
||
|
||
{{/* ============ ПОЛЬЗОВАТЕЛИ И ВХОД ============ */}}
|
||
<section class="settings-section" id="sec-users">
|
||
{{$U := .Users}}{{$A := .Settings.Auth}}
|
||
<h1>Пользователи и вход</h1>
|
||
<p class="muted">Кто может входить в bj и что ему разрешено. Вход — локальной учётной записью или доменной (Active Directory), роль доменного пользователя назначается по его группам AD.</p>
|
||
{{if $U.Open}}<div class="card" 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><span class="dot {{if $U.Open}}warn{{else}}ok{{end}}"></span>Роли</h2>
|
||
<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><br><code style="font-size:11px">{{.Key}}</code></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>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>Пользователи ({{len $U.Users}})</h2>
|
||
{{range $U.Users}}
|
||
<form method="post" action="/admin/setup/users" style="display:grid;grid-template-columns:1.1fr 1.4fr 1.2fr 1fr auto;gap:8px;align-items:end;padding:10px 0;border-bottom:1px solid var(--border)">
|
||
<input type="hidden" name="login" value="{{.Login}}"><input type="hidden" name="source" value="{{.Source}}">
|
||
<div><div style="font-weight:600">{{.Login}} {{if eq .Source "ad"}}<span class="badge" style="font-size:10px">AD</span>{{end}}{{if not .Active}}<span class="badge rejected" style="font-size:10px">заблокирован</span>{{end}}</div>
|
||
<div class="muted" style="font-size:11px">{{if not .LastLogin.IsZero}}вход {{msk .LastLogin}}{{else}}ещё не входил{{end}}{{if .Note}} · {{.Note}}{{end}}</div>
|
||
{{if .ADGroups}}<div class="muted" style="font-size:11px">группы: {{range $i,$g := .ADGroups}}{{if $i}}, {{end}}{{$g}}{{end}}</div>{{end}}</div>
|
||
<label style="margin:0;font-size:11px">Имя <input type="text" name="name" value="{{.Name}}" style="width:100%"><span class="muted">E-mail</span> <input type="text" name="email" value="{{.Email}}" style="width:100%"></label>
|
||
<label style="margin:0;font-size:11px">Роль <select name="role" style="width:100%">{{$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 style="margin-top:4px">{{$m := .Modules}}{{range $U.Modules}}<label style="display:inline-flex;gap:4px;align-items:center;font-weight:400;margin:0 8px 0 0;font-size:11px"><input type="checkbox" name="modules" value="{{.Key}}" {{if has $m .Key}}checked{{end}}>{{.Title}}</label>{{end}}</div></label>
|
||
<label style="margin:0;font-size:11px">{{if eq .Source "local"}}Новый пароль <input type="password" name="password" placeholder="оставьте пустым" autocomplete="new-password" style="width:100%">{{else}}<span class="muted">пароль проверяет AD</span>{{end}}
|
||
<div style="margin-top:4px"><label style="display:inline-flex;gap:4px;align-items:center;font-weight:400;margin:0;font-size:11px"><input type="checkbox" name="active" value="1" {{if .Active}}checked{{end}}>активен</label></div></label>
|
||
<div style="display:flex;gap:6px"><button class="btn btn-ok" type="submit">Сохранить</button><button class="btn" type="submit" formaction="/admin/setup/users/delete" onclick="return confirm('Удалить пользователя {{.Login}}?')">Удалить</button></div>
|
||
</form>
|
||
{{else}}<p class="muted">Пользователей нет.</p>{{end}}
|
||
|
||
<h3 style="margin:16px 0 6px">{{if $U.Open}}Создать первого администратора{{else}}Добавить пользователя{{end}}</h3>
|
||
<form method="post" action="/admin/setup/users" style="display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:10px;max-width:900px;align-items:end">
|
||
<label>Логин <input type="text" name="login" placeholder="ivanov" required style="width:100%"></label>
|
||
<label>Имя <input type="text" name="name" placeholder="Иванов И.И." style="width:100%"></label>
|
||
<label>Роль <select name="role" style="width:100%">{{range $U.Roles}}<option value="{{.Key}}" {{if and $U.Open (eq .Key "admin")}}selected{{end}}>{{.Title}}</option>{{end}}</select></label>
|
||
<label>Источник <select name="source" style="width:100%" {{if $U.Open}}disabled{{end}}><option value="local">Локальный (пароль в bj)</option><option value="ad" {{if not $A.ADEnabled}}disabled{{end}}>Active Directory</option></select></label>
|
||
<label>Пароль (для локального, от 8 символов) <input type="password" name="password" autocomplete="new-password" style="width:100%"></label>
|
||
<label>E-mail <input type="text" name="email" style="width:100%"></label>
|
||
{{if $U.Modules}}<label>Модули <div>{{range $U.Modules}}<label style="display:inline-flex;gap:4px;align-items:center;font-weight:400;margin:0 8px 0 0;font-size:12px"><input type="checkbox" name="modules" value="{{.Key}}">{{.Title}}</label>{{end}}</div></label>{{end}}
|
||
<div><input type="hidden" name="active" value="1"><button class="btn btn-ok" type="submit">{{if $U.Open}}Создать администратора{{else}}Добавить{{end}}</button></div>
|
||
</form>
|
||
<p class="muted" style="font-size:12px;margin-top:8px">Доменного пользователя можно заранее завести с источником «Active Directory», чтобы выдать ему модули; роль при входе возьмётся из групп AD. Если потерян доступ администратора — удалите файл <code>/var/lib/bj/.bj/users.json</code> на сервере: система вернётся в режим первичной настройки.</p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2><span class="dot {{if $A.ADEnabled}}ok{{else}}warn{{end}}"></span>Вход через Active Directory</h2>
|
||
<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) <span class="muted">пусто = взять с контроллера</span> <input type="text" name="ad_base_dn" value="{{$A.ADBaseDN}}" placeholder="DC=corp,DC=example,DC=ru" 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>Сервисная учётная запись <span class="muted">для перепроверки групп, только чтение</span> <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;margin-bottom:4px">Группы AD → роли <span class="muted" style="font-weight:400;font-size:12px">(имя группы или полный 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 btn-ok" 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" type="submit">Проверить AD</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" type="submit">Перепроверить сейчас</button></form>
|
||
{{else}}<span class="muted"><span class="dot warn"></span>Перепроверка групп выключена: роль доменного пользователя меняется только при следующем входе. Укажите сервисную учётную запись выше.</span>{{end}}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>Активные сессии ({{len $U.Sessions}})</h2>
|
||
{{if $U.Sessions}}<table style="font-size:12.5px"><thead><tr><th>Пользователь</th><th>Вход</th><th>Активность</th><th>Адрес</th><th>Браузер</th><th></th></tr></thead><tbody>
|
||
{{range $U.Sessions}}<tr><td><b>{{.Login}}</b></td><td>{{msk .Created}}</td><td>{{msk .Seen}}</td><td><code>{{.IP}}</code></td><td class="muted" style="font-size:11px;max-width:320px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{.UA}}</td>
|
||
<td><form method="post" action="/admin/setup/users/session-drop"><input type="hidden" name="token" value="{{.Token}}"><button class="btn" type="submit">Завершить</button></form></td></tr>{{end}}
|
||
</tbody></table>{{else}}<p class="muted">Сессий нет.</p>{{end}}
|
||
</div>
|
||
</section>
|
||
|
||
{{/* ============ ЭКСПОРТ БИРЖЕВОЙ ИНФОРМАЦИИ (QUIK) ============ */}}
|
||
<section class="settings-section" id="sec-quikexport">
|
||
{{$X := .QuikExport}}{{$S := $X.S}}
|
||
<h1>QUIK — Экспорт биржевой информации</h1>
|
||
<p class="muted">Модуль ARQA «Экспорт биржевой информации» выгружает данные сервера QUIK (портфели клиентов, позиции, сделки, справочники) в базу PostgreSQL. bj управляет им отсюда: ключи и логин QUIK, где держим базу, какой объём данных выгружаем, состояние службы и место на диске. На этих данных работают модуль «НПР / УДС» и дополнительные проверки M2M. Лицензионный признак <code>quik-export</code>.</p>
|
||
{{range $X.Warnings}}<div class="card" style="padding:8px 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>{{.}}</div>{{end}}
|
||
|
||
<div class="card">
|
||
<h2><span class="dot {{if not $X.Licensed}}err{{else if and $S.Enabled (eq $X.Service.Active "active")}}ok{{else if $S.Enabled}}warn{{else}}err{{end}}"></span>Состояние модуля</h2>
|
||
<form method="post" action="/admin/setup/quikexport" style="display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin-bottom:12px">
|
||
<input type="hidden" name="section" value="sec-quikexport"><input type="hidden" name="part" value="module">
|
||
<label style="display:flex;gap:8px;align-items:center;font-weight:600;margin:0"><input type="checkbox" name="enabled" value="1" {{if $S.Enabled}}checked{{end}} {{if not $X.Licensed}}disabled{{end}}> Модуль подключён</label>
|
||
<button type="submit" class="btn" {{if not $X.Licensed}}disabled{{end}}>Сохранить</button>
|
||
{{if not $X.Licensed}}<span class="muted">не входит в лицензию — см. раздел «Лицензия»</span>{{end}}
|
||
</form>
|
||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:10px">
|
||
<div class="nmini" style="padding:10px;border:1px solid var(--border);border-radius:8px"><div class="muted" style="font-size:11px;text-transform:uppercase">Служба quikexport</div>
|
||
<strong>{{if not $X.Service.Installed}}не установлена{{else if eq $X.Service.Active "active"}}работает{{else}}{{$X.Service.Active}}{{end}}</strong>
|
||
<div class="muted" style="font-size:11.5px">{{if $X.Service.PID}}PID {{$X.Service.PID}} · {{end}}{{if $X.Service.MemoryMB}}память {{$X.Service.MemoryMB}} МБ · {{end}}автозапуск {{if $X.Service.Enabled}}да{{else}}нет{{end}}{{if $X.Service.Restarts}} · перезапусков {{$X.Service.Restarts}}{{end}}</div></div>
|
||
<div style="padding:10px;border:1px solid var(--border);border-radius:8px"><div class="muted" style="font-size:11px;text-transform:uppercase">Данные из QUIK</div>
|
||
{{if $X.DB.OK}}<strong>{{if $X.DB.LastTick.IsZero}}нет пульса{{else if lt $X.DB.TickAgeSec 180}}свежие{{else}}устарели{{end}}</strong>
|
||
<div class="muted" style="font-size:11.5px">{{if not $X.DB.LastTick.IsZero}}цикл {{msk $X.DB.LastTick}} ({{$X.DB.TickAgeSec}} с назад) · {{end}}клиентов {{$X.DB.Clients}}</div>
|
||
{{else}}<strong style="color:var(--err)">база недоступна</strong><div class="muted" style="font-size:11.5px">{{$X.DB.Error}}</div>{{end}}</div>
|
||
<div style="padding:10px;border:1px solid var(--border);border-radius:8px"><div class="muted" style="font-size:11px;text-transform:uppercase">Объём базы {{$S.DBName}}</div>
|
||
<strong>{{printf "%.0f" $X.DB.DBSizeMB}} МБ</strong>
|
||
<div class="muted" style="font-size:11.5px">история НПР {{$X.DB.HistoryRows}} строк{{if not $X.DB.HistoryFrom.IsZero}} с {{$X.DB.HistoryFrom.Format "02.01.2006"}}{{end}} · рост ≈ {{printf "%.1f" $X.DB.GrowthMBDay}} МБ/сутки ({{$X.DB.Rows24h}} событий за 24 ч)</div></div>
|
||
<div style="padding:10px;border:1px solid var(--border);border-radius:8px"><div class="muted" style="font-size:11px;text-transform:uppercase">Диск {{if $X.DiskDB.OK}}базы данных{{else}}экспортёра{{end}}</div>
|
||
{{with $X.DiskDB}}{{if .OK}}<strong style="{{if lt .FreeGB 10.0}}color:var(--err){{end}}">свободно {{printf "%.1f" .FreeGB}} ГБ из {{printf "%.0f" .TotalGB}}</strong><div class="muted" style="font-size:11.5px">занято {{.UsedPct}}% · {{.Path}}</div>{{end}}{{end}}
|
||
{{if not $X.DiskDB.OK}}{{with $X.DiskExp}}<strong>свободно {{printf "%.1f" .FreeGB}} ГБ из {{printf "%.0f" .TotalGB}}</strong><div class="muted" style="font-size:11.5px">занято {{.UsedPct}}%</div>{{end}}{{end}}</div>
|
||
<div style="padding:10px;border:1px solid var(--border);border-radius:8px"><div class="muted" style="font-size:11px;text-transform:uppercase">Логи и кэш экспортёра</div>
|
||
<strong>{{printf "%.0f" $X.LogsMB}} МБ логов</strong>
|
||
<div class="muted" style="font-size:11.5px">{{range $X.Logs}}{{if .Exists}}{{.Name}} {{printf "%.0f" .SizeMB}} МБ · {{end}}{{end}}кэш QAPI {{printf "%.0f" $X.QAPIMB}} МБ · ротация: ежедневно / 200 МБ, 5 копий</div></div>
|
||
</div>
|
||
{{if $X.DB.Tables}}<details style="margin-top:10px"><summary class="muted" style="cursor:pointer">Крупнейшие таблицы базы</summary>
|
||
<table style="font-size:12px;margin-top:6px"><thead><tr><th>Таблица</th><th>Размер</th><th>Строк (оценка)</th></tr></thead><tbody>{{range $X.DB.Tables}}<tr><td><code>{{.Name}}</code></td><td>{{.Size}}</td><td>{{.Rows}}</td></tr>{{end}}</tbody></table></details>{{end}}
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:12px">
|
||
<form method="post" action="/admin/setup/quikexport/service"><input type="hidden" name="section" value="sec-quikexport"><input type="hidden" name="op" value="restart"><button class="btn" {{if not $X.Service.Installed}}disabled{{end}}>Перезапустить службу</button></form>
|
||
<form method="post" action="/admin/setup/quikexport/service"><input type="hidden" name="section" value="sec-quikexport"><input type="hidden" name="op" value="{{if eq $X.Service.Active "active"}}stop{{else}}start{{end}}"><button class="btn" {{if not $X.Service.Installed}}disabled{{end}}>{{if eq $X.Service.Active "active"}}Остановить{{else}}Запустить{{end}}</button></form>
|
||
{{if and $X.Service.Installed (not $X.Service.Enabled)}}<form method="post" action="/admin/setup/quikexport/service"><input type="hidden" name="section" value="sec-quikexport"><input type="hidden" name="op" value="enable"><button class="btn">Включить автозапуск</button></form>{{end}}
|
||
</div>
|
||
{{if $X.LogTail}}<details style="margin-top:10px"><summary class="muted" style="cursor:pointer">Последние строки export.log{{if $X.ErrTail}} и export.err{{end}}</summary>
|
||
<pre style="font-size:11px;white-space:pre-wrap;max-height:260px;overflow:auto;background:var(--card-2);padding:8px;border-radius:6px">{{range $X.LogTail}}{{.}}
|
||
{{end}}{{if $X.ErrTail}}--- export.err ---
|
||
{{range $X.ErrTail}}{{.}}
|
||
{{end}}{{end}}</pre></details>{{end}}
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2><span class="dot {{if and $S.QuikHost $S.QuikUser $S.KeysNames}}ok{{else}}warn{{end}}"></span>1. Подключение к серверу QUIK</h2>
|
||
<p class="muted">Логин экспорта на сервере QUIK брокера (выдаёт администратор QUIK / ARQA) и пара ключей <code>pubring.txk</code> / <code>secring.txk</code> к нему. Одна учётная запись = одна сессия: второй экспортёр с тем же логином получит «Вы уже работаете в системе».</p>
|
||
<form method="post" action="/admin/setup/quikexport" style="display:grid;grid-template-columns:2fr 1fr;gap:10px;max-width:720px;margin-top:10px">
|
||
<input type="hidden" name="section" value="sec-quikexport"><input type="hidden" name="part" value="quik">
|
||
<label>Адрес сервера QUIK <input type="text" name="quik_host" value="{{$S.QuikHost}}" placeholder="91.209.122.88" style="width:100%" required></label>
|
||
<label>Порт <input type="number" name="quik_port" value="{{$S.QuikPort}}" style="width:100%"></label>
|
||
<label>Пользователь QUIK <input type="text" name="quik_user" value="{{$S.QuikUser}}" style="width:100%" required></label>
|
||
<label>Пароль <input type="password" name="quik_password" value="" placeholder="{{if $S.QuikPassword}}(сохранён, оставьте пустым){{else}}пароль{{end}}" style="width:100%"></label>
|
||
<label>Имя авторизации (auth-name) <input type="text" name="auth_name" value="{{$S.AuthName}}" placeholder="FontV" style="width:100%"></label>
|
||
<div style="align-self:end"><button type="submit" class="btn btn-ok">Сохранить</button></div>
|
||
</form>
|
||
<form method="post" action="/admin/setup/quikexport/keys" enctype="multipart/form-data" style="display:grid;grid-template-columns:1fr 1fr auto;gap:10px;max-width:720px;margin-top:14px;align-items:end">
|
||
<input type="hidden" name="section" value="sec-quikexport">
|
||
<label>Открытый ключ pubring.txk <input type="file" name="pubring" accept=".txk" style="width:100%"></label>
|
||
<label>Закрытый ключ secring.txk <input type="file" name="secring" accept=".txk" style="width:100%"></label>
|
||
<button type="submit" class="btn">Загрузить ключи</button>
|
||
</form>
|
||
<p class="muted" style="font-size:12px;margin-top:6px">Ключи: {{if $S.KeysNames}}{{range $S.KeysNames}}<code>{{.}}</code> {{end}}{{if not $S.KeysAt.IsZero}}(загружены {{msk $S.KeysAt}}){{end}}{{else}}не загружены{{end}}.
|
||
В каталоге экспортёра: {{range $X.Files}}{{.Name}} {{if .Exists}}✓ {{.ModTime.Format "02.01 15:04"}}{{else}}✗{{end}} · {{end}}</p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2><span class="dot {{if and $S.DBPassword $S.ROPassword}}ok{{else}}warn{{end}}"></span>2. База данных экспорта</h2>
|
||
<p class="muted">Куда экспортёр пишет данные. Обычно — наш PostgreSQL на этом же сервере (bj создаст базу, роли и схему одной кнопкой). Внешний сервер — если базу держит заказчик. Логин только для чтения используют модуль «НПР / УДС», проверки M2M и внешние системы (Grafana, отчёты).</p>
|
||
<form method="post" action="/admin/setup/quikexport" style="display:grid;gap:10px;max-width:720px;margin-top:10px">
|
||
<input type="hidden" name="section" value="sec-quikexport"><input type="hidden" name="part" value="db">
|
||
<div style="display:flex;gap:18px;flex-wrap:wrap">
|
||
<label style="display:flex;gap:6px;align-items:center;font-weight:600;margin:0"><input type="radio" name="db_mode" value="local" {{if ne $S.DBMode "external"}}checked{{end}} onchange="document.getElementById('qx-ext').style.display='none'"> Наш PostgreSQL (этот сервер)</label>
|
||
<label style="display:flex;gap:6px;align-items:center;font-weight:600;margin:0"><input type="radio" name="db_mode" value="external" {{if eq $S.DBMode "external"}}checked{{end}} onchange="document.getElementById('qx-ext').style.display='grid'"> Внешний сервер PostgreSQL</label>
|
||
</div>
|
||
<div id="qx-ext" style="display:{{if eq $S.DBMode "external"}}grid{{else}}none{{end}};grid-template-columns:2fr 1fr;gap:10px">
|
||
<label>Хост <input type="text" name="db_host" value="{{$S.DBHost}}" style="width:100%"></label>
|
||
<label>Порт <input type="number" name="db_port" value="{{$S.DBPort}}" style="width:100%"></label>
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px">
|
||
<label>База <input type="text" name="db_name" value="{{$S.DBName}}" style="width:100%"></label>
|
||
<label>Пользователь экспорта (запись) <input type="text" name="db_user" value="{{$S.DBUser}}" style="width:100%"></label>
|
||
<label>Пароль <input type="password" name="db_password" value="" placeholder="{{if $S.DBPassword}}(сохранён){{else}}задать{{end}}" style="width:100%"></label>
|
||
<label>Логин только для чтения <input type="text" name="ro_user" value="{{$S.ROUser}}" style="width:100%"></label>
|
||
<label>Пароль <input type="password" name="ro_password" value="" placeholder="{{if $S.ROPassword}}(сохранён){{else}}задать{{end}}" style="width:100%"></label>
|
||
</div>
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap"><button type="submit" class="btn btn-ok">Сохранить</button>
|
||
<button type="submit" formaction="/admin/setup/quikexport/dbinit" class="btn" {{if eq $S.DBMode "external"}}disabled{{end}}>Создать базу и роли на этом сервере</button>
|
||
<a class="btn" href="/admin/setup/quikexport/sql">SQL для внешнего сервера</a></div>
|
||
</form>
|
||
<p class="muted" style="font-size:12px;margin-top:8px">Доступ для внешних систем и других модулей: <code>{{if $X.ShowRO}}{{$X.RODSNFull}}{{else}}{{$X.RODSN}}{{end}}</code>
|
||
{{if $X.ShowRO}}<a href="/admin/setup#quikexport">скрыть пароль</a>{{else}}<a href="/admin/setup?reveal_ro=1#quikexport">показать пароль</a>{{end}}{{if ne $S.DBMode "external"}} · снаружи: хост <code>{{$X.ServerIP}}</code>, доступ по IP открывает администратор (pg_hba){{end}}.</p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2><span class="dot ok"></span>3. Объём данных</h2>
|
||
<p class="muted">Какие таблицы QUIK выгружаем. Чем больше набор, тем больше нагрузка на сервер QUIK, база и диск. Портфели пересчитываются каждые «период портфелей» секунд (ARQA рекомендует не менее 60); история изменений НПР/УДС хранится в схеме <code>npr</code>.</p>
|
||
<form method="post" action="/admin/setup/quikexport" style="display:grid;gap:10px;margin-top:10px">
|
||
<input type="hidden" name="section" value="sec-quikexport"><input type="hidden" name="part" value="volume">
|
||
<div style="display:grid;gap:6px">{{range $X.Presets}}
|
||
<label style="display:flex;gap:8px;align-items:flex-start;font-weight:400;margin:0"><input type="radio" name="preset" value="{{.Key}}" {{if eq $S.Preset .Key}}checked{{end}} onchange="document.getElementById('qx-tables').style.display=this.value==='custom'?'block':'none'"><span><b>{{.Title}}</b><br><span class="muted" style="font-size:12px">{{.Desc}}</span></span></label>{{end}}
|
||
</div>
|
||
<div id="qx-tables" style="display:{{if eq $S.Preset "custom"}}block{{else}}none{{end}};border:1px solid var(--border);border-radius:8px;padding:10px">
|
||
{{range $g := $X.Groups}}<div style="font-weight:600;margin:6px 0 4px">{{$g}}</div>
|
||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:4px 12px">{{range $X.Tables}}{{if eq .Group $g}}
|
||
<label style="display:flex;gap:6px;align-items:flex-start;font-weight:400;margin:0;font-size:12.5px"><input type="checkbox" name="tables" value="{{.Name}}" {{if index $X.Enabled .Name}}checked{{end}}><span><code>{{.Name}}</code> — {{.Title}} <span class="muted">· объём: {{.Load}}{{if .Note}} · {{.Note}}{{end}}</span></span></label>{{end}}{{end}}</div>{{end}}
|
||
</div>
|
||
<details><summary class="muted" style="cursor:pointer">Сейчас включено: {{len $S.Tables}} таблиц — {{range $S.Tables}}<code>{{.}}</code> {{end}}</summary></details>
|
||
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:10px;max-width:820px">
|
||
<label>Период портфелей, с <input type="number" name="portfolio_period" min="10" value="{{$S.PortfolioPeriod}}" style="width:100%"></label>
|
||
<label>Период «Текущих торгов», с <input type="number" name="param_period" min="0" value="{{$S.ParamPeriod}}" style="width:100%"></label>
|
||
<label>Виды лимитов (LimitKinds) <input type="text" name="limit_kinds" value="{{$S.LimitKinds}}" style="width:100%"></label>
|
||
<label>Уровень лога (DebugLevel) <input type="number" name="debug_level" min="0" max="255" value="{{$S.DebugLevel}}" style="width:100%"></label>
|
||
</div>
|
||
<div><button type="submit" class="btn btn-ok">Сохранить</button></div>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2><span class="dot {{if not $S.AppliedAt.IsZero}}ok{{else}}warn{{end}}"></span>4. Применить к экспортёру</h2>
|
||
<p class="muted">bj собирает <code>export.ini</code>, <code>info.ini</code>, секцию ODBC и ключи из настроек выше и устанавливает их в каталог службы (<code>{{$S.Dir}}</code>) с резервной копией прежних файлов, настраивает ротацию логов и перезапускает экспортёр. {{if not $S.AppliedAt.IsZero}}Последнее применение: {{msk $S.AppliedAt}}.{{end}}{{if not $S.Imported}} Если экспортёр уже настроен вручную — сначала «Прочитать настройки из экспортёра».{{end}}</p>
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:10px">
|
||
<a class="btn" href="/admin/setup/quikexport/preview" target="_blank">Предпросмотр файлов</a>
|
||
<form method="post" action="/admin/setup/quikexport/apply" onsubmit="return confirm('Установить настройки в экспортёр и перезапустить службу? Выгрузка прервётся на несколько секунд.')"><input type="hidden" name="section" value="sec-quikexport"><button class="btn btn-ok" {{if not $X.Licensed}}disabled{{end}}>Применить настройки и перезапустить экспортёр</button></form>
|
||
<form method="post" action="/admin/setup/quikexport/import"><input type="hidden" name="section" value="sec-quikexport"><button class="btn" {{if not $X.Service.Installed}}disabled{{end}}>Прочитать настройки из экспортёра</button></form>
|
||
</div>
|
||
<p class="muted" style="font-size:12px;margin-top:10px">Установка на новом сервере: дистрибутив ARQA (install_quikexport-12.x.run, Linux) ставится установщиком bj в <code>{{$S.Dir}}</code>; нужны пакеты unixodbc, odbc-postgresql, libjemalloc2. Обновления экспортёра приходят через канал обновлений bj после проверки у нас.</p>
|
||
</div>
|
||
</section>
|
||
|
||
{{/* ============ КРИПТОГРАФИЯ ============ */}}
|
||
<section class="settings-section" id="sec-crypto">
|
||
<h1>Криптография</h1>
|
||
|
||
<div class="card" style="background:var(--accent-weak)">
|
||
<h2>Установка ключа на флешку</h2>
|
||
<p class="muted">Пошаговый мастер: загрузить архив НРД с паролем → запись на флешку → справочник сертификатов → проверка Валидаты → готово.</p>
|
||
<a href="/admin/setup/keywizard" class="btn btn-ok" style="margin-top:10px;display:inline-block">Открыть мастер установки ключа →</a>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2><span class="dot {{if .Settings.Crypto.Profile}}ok{{else}}warn{{end}}"></span>СКЗИ «Валидата Клиент L»</h2>
|
||
<p class="muted">Активный профиль: <code>{{if .Settings.Crypto.Profile}}{{.Settings.Crypto.Profile}}{{else}}—{{end}}</code> · провайдер <code>{{.Settings.Crypto.Provider}}</code>. Подробно — <a href="/admin/help/crypto">справка</a>.</p>
|
||
<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>
|
||
<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('Перезапустить crypto-service? Поднимется через ~5 сек.');"><button type="submit" class="btn btn-warn">↻ crypto-service</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>
|
||
</div>
|
||
<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">
|
||
<option value="stub" {{if eq .Settings.Crypto.Provider "stub"}}selected{{end}}>stub — без криптографии (демо)</option>
|
||
<option value="validata" {{if eq .Settings.Crypto.Provider "validata"}}selected{{end}}>Валидата Клиент L</option>
|
||
</select>
|
||
<label>Путь к модулю PKCS#11</label>
|
||
<input type="text" name="module_path" value="{{.Settings.Crypto.ModulePath}}" placeholder="/opt/Validata/VDCSP/lib/amd64/libvdpkcs11.so">
|
||
<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>
|
||
</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 .NSDCerts.Profile}} (<code>{{.NSDCerts.Profile}}</code>){{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><div class="muted" style="font-size:11px">{{.SubjectCN}} · выдан: {{.IssuerCN}} · серийный <code>{{.Serial}}</code></div></td>
|
||
<td>{{.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 .NSDCerts.LastLog}}<pre style="margin-top:10px;font-size:11.5px;white-space:pre-wrap">{{.NSDCerts.LastLog}}</pre>{{end}}
|
||
{{if .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}}</td><td>{{.Store}}</td></tr>{{end}}</tbody></table></details>{{end}}
|
||
<p class="muted" style="font-size:12px;margin-top:8px">Свои сертификаты можно положить в <code>/var/lib/bj/.bj/nsd-certs/*.cer</code> — они добавляются по тем же правилам и никогда не удаляются автоматически.</p>
|
||
</div>
|
||
|
||
{{/* Сертификаты УЦ */}}
|
||
<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>
|
||
</section>
|
||
|
||
{{/* ============ НРД ============ */}}
|
||
<section class="settings-section" id="sec-nsd">
|
||
<h1>НРД</h1>
|
||
<div class="card">
|
||
<h2><span class="dot {{if .Settings.NSD.IGWBaseURL}}ok{{else}}warn{{end}}"></span>Интеграционный шлюз (ИШ)</h2>
|
||
<p class="muted">{{if not .Settings.NSD.IGWBaseURL}}<code>mock-режим</code> — без реального ИШ.{{else}}Профиль <code>{{.Settings.NSD.Profile}}</code>, ИШ <code>{{.Settings.NSD.IGWBaseURL}}</code>.{{end}} Стенды и установка — <a href="/admin/help/systems">справка</a>.</p>
|
||
<form method="post" action="/admin/setup/nsd" style="margin-top:12px;display:grid;gap:10px;max-width:680px">
|
||
<label>Профиль / контур</label>
|
||
<select name="profile" id="nsd-profile">
|
||
<option value="" {{if eq .Settings.NSD.Profile ""}}selected{{end}}>— mock (демо) —</option>
|
||
<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>
|
||
<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">
|
||
<hr style="border:none;border-top:1px solid var(--border);margin:6px 0">
|
||
<p class="muted" style="margin:0">Депозитарные реквизиты (откуда списываются бумаги) — из договора/письма НРД. Нужны для формирования заявки на перевод.</p>
|
||
<label>Депозитарный код</label>
|
||
<input type="text" name="deponent_code" value="{{.Settings.NSD.DeponentCode}}" placeholder="напр. MC0413600000">
|
||
<label>Депозитарный счёт</label>
|
||
<input type="text" name="account_id" value="{{.Settings.NSD.AccountID}}" placeholder="депозитарный счёт">
|
||
<label>Раздел счёта</label>
|
||
<input type="text" name="section_id" value="{{.Settings.NSD.SectionID}}" placeholder="раздел депозитарного счёта">
|
||
<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>
|
||
</div>
|
||
</section>
|
||
|
||
{{/* ============ ЛИЧНЫЙ КАБИНЕТ ============ */}}
|
||
<section class="settings-section" id="sec-lk">
|
||
<h1>Личный кабинет <span class="muted" style="font-size:16px;font-weight:500">· ЕСИА Финанс</span></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">Адрес ЛК (стенд/бой), куда клиент подаёт заявку M2M. Заявка приходит к нам по контракту <code>POST /api/v1/back_office/claims/</code>, а мы возвращаем статус через <strong>Callback URL</strong>.</p>
|
||
<form method="post" action="/admin/setup/lk" style="margin-top:12px;display:grid;gap:12px;max-width:640px">
|
||
<div>
|
||
<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://fontvielle.ef.winr.pro" 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">Показывается ссылкой на дашборде для первичного теста.</span>
|
||
</div>
|
||
<div>
|
||
<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://fontvielle.ef.winr.pro" style="margin-top:4px">
|
||
<span class="muted" style="font-size:12px">{{if not .Settings.LK.CallbackURL}}Не настроен — уведомления в ЛК отключены. Для обмена с НРД не требуется.{{end}}</span>
|
||
</div>
|
||
<div><button type="submit" class="btn">Сохранить и проверить</button></div>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>Подключение к API ЛК — тест и бой</h2>
|
||
<p class="muted">Реквизиты, по которым 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://fontvielle.ef.winr.pro/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>
|
||
<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>
|
||
</form>
|
||
</div>
|
||
|
||
<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>
|
||
|
||
<div class="card">
|
||
<h2>Забрать заявки из ЛК</h2>
|
||
<p class="muted">Коннектор bj вычитывает заявки <code>depo_m2m</code> активного окружения в статусах «В очереди» и «В работе», которые ещё не обрабатывал, для каждой собирает M2M-пакет, отправляет в НРД и переводит заявку в статус <strong>«В работе»</strong>. По ответу НРД статус в ЛК замыкается автоматически (Исполнена / Отклонена). В режиме имитации НРД перед отправкой идёт сверка с DEPO: отказ сразу уходит в ЛК с кодом и причиной, одобренная заявка блокируется в QUIK и через 30 с получает имитированный ответ контрагента. Штатно это делает фоновый поллер раз в минуту — кнопка для ручного запуска.</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>
|
||
|
||
{{/* ============ ТЕСТЫ ============ */}}
|
||
<section class="settings-section" id="sec-tests">
|
||
<h1>Тесты</h1>
|
||
|
||
<div class="card">
|
||
<h2>Здоровье сервиса</h2>
|
||
<p class="muted">Фоновые компоненты под самоконтролем: при сбое перезапускаются автоматически, при зависании — перезапуск всего сервиса и запись в «Новости». Ниже — живой статус.</p>
|
||
{{if .Health}}
|
||
<div class="tw" style="margin-top:12px">
|
||
<table>
|
||
<thead><tr><th>Компонент</th><th>Состояние</th><th>Последняя отметка</th><th>Последняя ошибка</th></tr></thead>
|
||
<tbody>
|
||
{{range .Health}}
|
||
<tr>
|
||
<td>{{healthLabel .Name}}</td>
|
||
<td>{{if .Healthy}}<span class="sys-status" style="--dot:#16a34a">работает</span>{{else}}<span class="sys-status" style="--dot:#dc2626">не отвечает</span>{{end}}</td>
|
||
<td class="mono">{{dur .Age}} назад</td>
|
||
<td class="muted">{{if .LastErr}}{{.LastErr}}{{else}}—{{end}}</td>
|
||
</tr>
|
||
{{end}}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{{else}}
|
||
<p class="muted" style="margin-top:10px">Компоненты ещё не зарегистрированы (сервис только запущен).</p>
|
||
{{end}}
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>Справочник участников M2M</h2>
|
||
<p class="muted">Запрос актуального справочника участников сервиса (пакет <code>#M2MHR</code> в НРД). В ответ НРД присылает <code>#M2MTH</code> — карту <strong>ИНН → MC-код</strong> депозитариев/брокеров и перечень мест расчётов. Нужно для сопоставления ИНН контрагента с кодом участника (блокеры вида M2M14/M2M15) и понимания, кто вообще зарегистрирован.</p>
|
||
<form method="post" action="/admin/setup/handbook-request" style="margin-top:12px"><button type="submit" class="btn">Запросить справочник у НРД</button></form>
|
||
<p class="muted" style="font-size:12px;margin-top:8px">Ответ придёт во входящие ИШ (~30-60с). Список появится в логе bj-server и в <code>m2m-handbook.json</code>.</p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>Тестовый пакет роботу НРД</h2>
|
||
<p class="muted">Робот <code>MC0012500000</code> эмулирует вторую сторону перевода. Выберите сценарий — bj-server отправит эталонный запрос через ИШ, ответ придёт во входящие. Требуется настроенный ИШ + профиль Валидаты.</p>
|
||
<form method="post" action="/admin/setup/test-nsd" style="margin-top:12px;display:flex;gap:8px;flex-wrap:wrap;align-items:center">
|
||
<select name="scenario">
|
||
<option value="2001">2001 — Принять все бумаги</option>
|
||
<option value="2002">2002 — Принять частично</option>
|
||
<option value="1111">1111 — Ответ с отказом</option>
|
||
<option value="3333">3333 — Робот принимающая сторона</option>
|
||
</select>
|
||
<button type="submit" class="btn">Отправить роботу</button>
|
||
</form>
|
||
<p class="muted" style="margin-top:6px;font-size:12px">Ответ робота — асинхронно (~30-60 сек) во входящие ИШ.</p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>Сквозной тестовый прогон (mock/реальный)</h2>
|
||
<p class="muted">Заявка с предзаполненными данными через всю цепочку до финального статуса.</p>
|
||
<form method="post" action="/admin/setup/test-run" style="margin-top:12px"><button type="submit" class="btn">Запустить тестовую заявку</button></form>
|
||
{{if .Settings.LastTest}}
|
||
<div style="margin-top:16px;padding:14px;background:var(--card-2);border:1px solid var(--border);border-radius:10px">
|
||
<strong>Последний прогон</strong>
|
||
<table style="margin-top:8px">
|
||
<tbody>
|
||
<tr><td style="width:160px" class="muted">Статус</td><td>{{if .Settings.LastTest.OK}}<span style="color:var(--ok)">✓ успешно</span>{{else}}<span style="color:var(--err)">✗ не прошёл</span>{{end}}</td></tr>
|
||
<tr><td class="muted">FSM-статус</td><td><code>{{.Settings.LastTest.FinalStatus}}</code></td></tr>
|
||
<tr><td class="muted">ClaimID</td><td><code>{{.Settings.LastTest.ClaimID}}</code> {{if .Settings.LastTest.ClaimID}}<a href="/admin/claims/{{.Settings.LastTest.ClaimID}}">→ карточка</a>{{end}}</td></tr>
|
||
<tr><td class="muted">Сообщение</td><td>{{.Settings.LastTest.Message}}</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{{end}}
|
||
</div>
|
||
</section>
|
||
|
||
{{/* ============ ОПОВЕЩЕНИЯ ============ */}}
|
||
<section class="settings-section" id="sec-alerts">
|
||
<h1>Оповещения</h1>
|
||
<p class="muted" style="max-width:60ch">Системные события (сбой или зависание фонового компонента, аварийный перезапуск) всегда попадают в ленту «Новости». Дополнительно их можно доставлять наружу — на почту оператору и/или в мониторинг Zabbix, чтобы узнать о проблеме, даже когда интерфейс bj никто не открыл.</p>
|
||
|
||
<div class="card">
|
||
<h2>Почта (SMTP)</h2>
|
||
<p class="muted">Отправка уведомлений по e-mail. Пароль хранится в конфигурации сервиса.</p>
|
||
<form method="post" action="/admin/setup/alerts-smtp" style="margin-top:12px">
|
||
<label style="display:flex;align-items:center;gap:8px;font-weight:400;margin-bottom:12px"><input type="checkbox" name="enabled" style="width:auto" {{if .Settings.Alerts.SMTP.Enabled}}checked{{end}}> Включить почтовые уведомления</label>
|
||
<div class="grid" style="grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px">
|
||
<div><label>SMTP-сервер</label><input type="text" name="host" value="{{.Settings.Alerts.SMTP.Host}}" placeholder="smtp.example.ru" style="margin-top:3px"></div>
|
||
<div><label>Порт</label><input type="text" id="smtp-port" name="port" value="{{if .Settings.Alerts.SMTP.Port}}{{.Settings.Alerts.SMTP.Port}}{{else}}587{{end}}" style="margin-top:3px"></div>
|
||
<div><label>Шифрование</label>
|
||
<select id="smtp-security" name="security" style="margin-top:3px" onchange="smtpSyncPort(this.value)">
|
||
<option value="starttls" {{if eq .Settings.Alerts.SMTP.Security "starttls"}}selected{{end}}>STARTTLS (587)</option>
|
||
<option value="ssl" {{if eq .Settings.Alerts.SMTP.Security "ssl"}}selected{{end}}>SSL/TLS (465)</option>
|
||
<option value="none" {{if eq .Settings.Alerts.SMTP.Security "none"}}selected{{end}}>Без шифрования (25)</option>
|
||
</select></div>
|
||
</div>
|
||
<div class="grid" style="grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px;margin-top:12px">
|
||
<div><label>Логин</label><input type="text" name="username" value="{{.Settings.Alerts.SMTP.Username}}" autocomplete="off" style="margin-top:3px"></div>
|
||
<div><label>Пароль</label><input type="password" name="password" value="{{.Settings.Alerts.SMTP.Password}}" autocomplete="new-password" style="margin-top:3px"></div>
|
||
<div><label>Отправитель (From)</label><input type="text" name="from" value="{{.Settings.Alerts.SMTP.From}}" placeholder="bj@example.ru" style="margin-top:3px"></div>
|
||
<div><label>Получатели (через запятую)</label><input type="text" name="to" value="{{.Settings.Alerts.SMTP.To}}" placeholder="ops@example.ru, dev@example.ru" style="margin-top:3px"></div>
|
||
</div>
|
||
<div style="margin-top:16px;display:flex;gap:8px;flex-wrap:wrap;align-items:center">
|
||
<button type="submit" class="btn">Сохранить</button>
|
||
<button type="submit" formaction="/admin/setup/alerts-smtp-test" class="btn btn-secondary">Отправить тестовое письмо</button>
|
||
</div>
|
||
</form>
|
||
{{if .Settings.Alerts.SMTP.LastMsg}}<p class="muted" style="font-size:12px;margin-top:10px">Последняя проверка: {{.Settings.Alerts.SMTP.LastMsg}}</p>{{end}}
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>Zabbix (хук мониторинга)</h2>
|
||
<p class="muted">bj шлёт значение на элемент типа <strong>Zabbix trapper</strong> (протокол <code>zabbix_sender</code>). В Zabbix заведите host и item-trapper с этим ключом, затем триггер по значению — уведомления настроит сам Zabbix.</p>
|
||
<form method="post" action="/admin/setup/alerts-zabbix" style="margin-top:12px">
|
||
<label style="display:flex;align-items:center;gap:8px;font-weight:400;margin-bottom:12px"><input type="checkbox" name="enabled" style="width:auto" {{if .Settings.Alerts.Zabbix.Enabled}}checked{{end}}> Включить хук Zabbix</label>
|
||
<div class="grid" style="grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px">
|
||
<div><label>Сервер / прокси Zabbix</label><input type="text" name="server" value="{{.Settings.Alerts.Zabbix.Server}}" placeholder="zabbix.example.ru" style="margin-top:3px"></div>
|
||
<div><label>Порт</label><input type="text" name="port" value="{{if .Settings.Alerts.Zabbix.Port}}{{.Settings.Alerts.Zabbix.Port}}{{else}}10051{{end}}" style="margin-top:3px"></div>
|
||
<div><label>Имя хоста в Zabbix</label><input type="text" name="host" value="{{.Settings.Alerts.Zabbix.Host}}" placeholder="bj-server" style="margin-top:3px"></div>
|
||
<div><label>Ключ элемента (trapper)</label><input type="text" name="key" value="{{if .Settings.Alerts.Zabbix.Key}}{{.Settings.Alerts.Zabbix.Key}}{{else}}bj.alert{{end}}" style="margin-top:3px"></div>
|
||
</div>
|
||
<div style="margin-top:16px;display:flex;gap:8px;flex-wrap:wrap;align-items:center">
|
||
<button type="submit" class="btn">Сохранить</button>
|
||
<button type="submit" formaction="/admin/setup/alerts-zabbix-test" class="btn btn-secondary">Отправить тестовое значение</button>
|
||
</div>
|
||
</form>
|
||
{{if .Settings.Alerts.Zabbix.LastMsg}}<p class="muted" style="font-size:12px;margin-top:10px">Последняя проверка: {{.Settings.Alerts.Zabbix.LastMsg}}</p>{{end}}
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>Адресаты уведомлений: депозитарий, бэк-офис, мидл-офис</h2>
|
||
<p class="muted">Три группы адресов и таблица «какой тип уведомления кому отправлять». Письма идут через SMTP из раздела «Почта». Адрес алертов из раздела «Почта» получает системные оповещения всегда; группы ниже добавляются по маршруту.</p>
|
||
<form method="post" action="/admin/setup/incoming-notify" style="margin-top:12px">
|
||
<label style="display:flex;align-items:center;gap:8px;font-weight:400;margin-bottom:12px"><input type="checkbox" name="enabled" style="width:auto" {{if .Settings.Incoming.Enabled}}checked{{end}}> Включить уведомления о переводах</label>
|
||
<div style="display:grid;gap:10px;max-width:640px">
|
||
<div><label>Депозитарий (через запятую)</label><input type="text" name="emails" value="{{.Settings.Incoming.Emails}}" placeholder="depo@example.ru" style="margin-top:3px;width:100%"></div>
|
||
<div><label>Бэк-офис (через запятую)</label><input type="text" name="emails_back" value="{{.Settings.Incoming.EmailsBack}}" placeholder="backoffice@example.ru" style="margin-top:3px;width:100%"></div>
|
||
<div><label>Мидл-офис (через запятую)</label><input type="text" name="emails_middle" value="{{.Settings.Incoming.EmailsMiddle}}" placeholder="middle@example.ru" style="margin-top:3px;width:100%"></div>
|
||
</div>
|
||
<table style="margin-top:14px;max-width:760px">
|
||
<thead><tr><th>Тип уведомления</th>{{range .NotifyGroups}}<th style="text-align:center">{{.Title}}</th>{{end}}</tr></thead>
|
||
<tbody>{{$inc := .Settings.Incoming}}{{range $k := .NotifyKinds}}<tr><td>{{$k.Title}}</td>{{range $g := $.NotifyGroups}}<td style="text-align:center"><input type="checkbox" name="route_{{$k.Key}}_{{$g.Key}}" value="1" {{if $inc.RouteHas $k.Key $g.Key}}checked{{end}} style="width:auto"></td>{{end}}</tr>{{end}}</tbody>
|
||
</table>
|
||
<div style="margin-top:16px;display:flex;gap:8px;flex-wrap:wrap;align-items:center">
|
||
<button type="submit" class="btn">Сохранить</button>
|
||
<button type="submit" formaction="/admin/setup/incoming-notify-test" class="btn btn-secondary">Отправить пробное уведомление</button>
|
||
</div>
|
||
</form>
|
||
<p class="muted" style="font-size:12px;margin-top:8px">Требуется настроенный и включённый SMTP в разделе «Почта».</p>
|
||
</div>
|
||
<script>
|
||
// Смена шифрования SMTP авто-подставляет типовой порт; править вручную можно.
|
||
function smtpSyncPort(sec){
|
||
var p=document.getElementById('smtp-port'); if(!p) return;
|
||
var def={starttls:'587', ssl:'465', none:'25'}[sec];
|
||
if(def) p.value=def;
|
||
}
|
||
</script>
|
||
</section>
|
||
|
||
{{/* ============ ОБНОВЛЕНИЯ ============ */}}
|
||
<section class="settings-section" id="sec-update">
|
||
<h1>Обновления</h1>
|
||
|
||
<div class="card">
|
||
<h2>Компоненты</h2>
|
||
<p class="muted">Каждый компонент обновляется отдельно: своя версия в релизе, перезапускается только его служба. Файлы ставит root-установщик после повторной проверки подписи и контрольных сумм. Модули, которых нет на этом сервере, обновлением не ставятся.</p>
|
||
<div style="overflow-x:auto"><table style="margin-top:8px">
|
||
<thead><tr><th>Компонент</th><th>Установлена</th><th>Доступна</th><th>Служба</th><th></th></tr></thead>
|
||
<tbody>
|
||
{{range .Components}}<tr>
|
||
<td><strong>{{.Title}}</strong><div class="muted" style="font-size:11px"><code>{{.Name}}</code></div></td>
|
||
<td>{{if .Present}}{{if .Installed}}{{.Installed}}{{else}}<span class="muted">неизвестна</span>{{end}}{{else}}<span class="muted">не установлен</span>{{end}}</td>
|
||
<td>{{if .Available}}{{.Available}}{{if .Update}} <span class="hero-status warn">новее</span>{{end}}{{else}}<span class="muted">нет данных</span>{{end}}</td>
|
||
<td><code style="font-size:11px">{{.Unit}}</code></td>
|
||
<td>{{if .Update}}<form method="post" action="/admin/setup/update/apply" style="margin:0" onsubmit="return confirm('Обновить компонент до {{.Available}}? Его служба перезапустится.');"><input type="hidden" name="section" value="sec-update"><input type="hidden" name="component" value="{{.Name}}"><button type="submit" class="btn btn-ok">Обновить</button></form>{{end}}</td>
|
||
</tr>{{end}}
|
||
</tbody>
|
||
</table></div>
|
||
<div style="display:flex;gap:10px;flex-wrap:wrap;margin-top:12px">
|
||
{{if .UpdatesAvailable}}<form method="post" action="/admin/setup/update/apply" style="margin:0" onsubmit="return confirm('Обновить все компоненты, для которых есть новая версия?');"><input type="hidden" name="section" value="sec-update"><button type="submit" class="btn btn-ok">Обновить всё</button></form>{{end}}
|
||
<form method="post" action="/admin/setup/update/check" style="margin:0"><input type="hidden" name="section" value="sec-update"><button type="submit" class="btn btn-secondary">Проверить обновления</button></form>
|
||
</div>
|
||
{{if .Settings.Update.Notes}}<p class="muted" style="margin-top:10px">Что нового в релизе {{.Settings.Update.Available}}: {{.Settings.Update.Notes}}</p>{{end}}
|
||
{{if not .Settings.Update.LastCheck.IsZero}}<p class="muted" style="margin-top:6px;font-size:12px">Последняя проверка: {{.Settings.Update.LastCheck.Format "02.01.2006 15:04"}}. {{.Settings.Update.LastResult}}</p>{{end}}
|
||
{{if and .License.Present (not .License.AllowsUpdates)}}<p class="muted" style="margin-top:6px;font-size:12px;color:var(--warn)">Текущий план «{{.License.Plan}}» не включает обновления.</p>{{end}}
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2><span class="dot {{if .Settings.Update.BaseURL}}ok{{else}}warn{{end}}"></span>Источник обновлений</h2>
|
||
<p class="muted">Обновления раздаёт сервер лицензий по лицензии этой установки: модуль скачивается, только если он входит в лицензию. Каждый релиз проверяется по подписи Ed25519 и контрольным суммам, без действительной подписи установка не выполняется.</p>
|
||
<form method="post" action="/admin/setup/update" style="margin-top:12px;display:grid;gap:10px;max-width:680px">
|
||
<label>URL артефактории</label>
|
||
<input type="text" name="base_url" value="{{.Settings.Update.BaseURL}}" placeholder="https://lic.zetit.ru/updates (по умолчанию)">
|
||
<label>Канал</label>
|
||
<select name="channel">
|
||
<option value="stable" {{if eq .Settings.Update.Channel "stable"}}selected{{end}}>stable — стабильный</option>
|
||
<option value="beta" {{if eq .Settings.Update.Channel "beta"}}selected{{end}}>beta — предварительный</option>
|
||
</select>
|
||
<label>Публичный ключ издателя (base64 Ed25519)</label>
|
||
<input type="text" name="public_key" value="{{.Settings.Update.PublicKey}}" placeholder="зашит в релиз; переопределить здесь">
|
||
<label style="display:flex;align-items:center;gap:8px;cursor:pointer"><input type="checkbox" name="auto_check" {{if .Settings.Update.AutoCheck}}checked{{end}} style="width:auto"> Проверять автоматически (раз в 6 часов)</label>
|
||
<div><button type="submit" class="btn">Сохранить</button></div>
|
||
</form>
|
||
</div>
|
||
</section>
|
||
|
||
{{/* ============ ЛИЦЕНЗИЯ ============ */}}
|
||
<section class="settings-section" id="sec-license">
|
||
<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>
|
||
<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>
|
||
<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">{{.License.Plan}}</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}} <code style="font-size:11px;color:var(--muted)">{{.Key}}</code>{{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.Online}}<button type="submit" formaction="/admin/setup/license/renew" class="btn">Продлить сейчас</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 МОСТ / НРД) и на титульной сводке пакета тестов. Встраивается прямо в 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>
|
||
<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)">
|
||
</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>
|
||
|
||
{{/* ============ ПЕРСПЕКТИВНЫЕ МОДУЛИ (под паролем) ============ */}}
|
||
<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>Обмен Fancy</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>«Обмен Fancy»</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>
|
||
|
||
</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(){ show(b.dataset.sec); }); });
|
||
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}}
|