feat: живой цикл M2M с НРД + мастер установки ключа на флешку
Инфраструктура M2M (живой обмен с НРД через ИШ): - обработка M2MTransferResponse: ERROR(M2Mxx) → заявка Отклонена, сохранение ответа; INFO → ждём Decision; идемпотентность поллера - fallback-корреляция ответов с нулевым GUID (M2M14/M2M17) по FIFO - сырой XML ответа НРД в карточке заявки (для пересылки в ТП) - тестовый пакет роботу приведён к эталону m2m_robot_samples (CostInfo=Yes, 4 бумаги, IsolationStatus, DocumentSeries=сценарий); override паспорта - редирект из теста сразу в карточку заявки Мастер установки ключа Валидаты на флешку (admin/setup/keywizard): - пошаговый: загрузка .7z+пароль → выбор флешки → запись → справочник сертификатов (CRL) → перезапуск+проверка ИШ → готово - привилегированный воркер (bj-keymedia) в host-namespace через файл-обмен, bj-server остаётся в песочнице - сохранение структуры профиля архива (spr<N>), перечисление съёмных USB Прочее: - пакет-доказательство для ТП НРД + форма регистрации участника M2M - эталонные образцы робота (DOC/m2m_robot_samples) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,324 +1,384 @@
|
||||
{{define "content"}}
|
||||
{{if .Flash}}<div style="padding:12px 16px;background:rgba(63,191,108,0.1);border-left:3px solid var(--ok);border-radius:4px;margin-bottom:16px">{{.Flash}}</div>{{end}}
|
||||
{{if .Flash}}<div style="padding:12px 16px;background:var(--ok-weak);border-left:3px solid var(--ok);border-radius:8px;margin-bottom:16px">{{.Flash}}</div>{{end}}
|
||||
|
||||
<div class="card">
|
||||
<h2>Готовность системы: {{.ReadyCount}} из {{.TotalCount}}</h2>
|
||||
<div style="display:flex;gap:8px;margin-top:8px">
|
||||
{{range .Readiness}}
|
||||
<div style="flex:1;text-align:center;padding:8px;background:var(--bg);border:1px solid var(--border);border-radius:4px">
|
||||
<span class="dot {{if .Configured}}ok{{else}}err{{end}}"></span>
|
||||
<strong>{{.Name}}</strong><br>
|
||||
<span class="muted" style="font-size:11px">{{if .Configured}}настроено{{else}}не настроено{{end}}</span>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings">
|
||||
{{/* ===== Боковая навигация разделов с индикаторами ===== */}}
|
||||
<nav class="settings-nav">
|
||||
<button data-sec="overview" class="active"><span class="nico">◎</span>Обзор</button>
|
||||
<button data-sec="db"><span class="nico">🗄️</span>База данных<span class="ind {{if .Settings.Postgres.DSN}}ok{{else}}err{{end}}"></span></button>
|
||||
<button data-sec="crypto"><span class="nico">🔐</span>Криптография<span class="ind {{if .Settings.Crypto.Profile}}ok{{else}}warn{{end}}"></span></button>
|
||||
<button data-sec="nsd"><span class="nico">🏛️</span>НРД<span class="ind {{if .Settings.NSD.IGWBaseURL}}ok{{else}}warn{{end}}"></span></button>
|
||||
<button data-sec="tests"><span class="nico">🧪</span>Тесты</button>
|
||||
<button data-sec="update"><span class="nico">⬆️</span>Обновления{{if .Settings.Update.Available}}<span class="ind warn"></span>{{end}}</button>
|
||||
<button data-sec="license"><span class="nico">🔑</span>Лицензия{{if .License.Present}}<span class="ind {{if .License.Valid}}ok{{else}}err{{end}}"></span>{{end}}</button>
|
||||
</nav>
|
||||
|
||||
<!-- PostgreSQL -->
|
||||
<div class="card">
|
||||
<h2><span class="dot {{if .Settings.Postgres.DSN}}ok{{else}}err{{end}}"></span>PostgreSQL</h2>
|
||||
<p class="muted">Принимающая БД (fansy-store) и журнал сделок m2m-core. Сейчас:
|
||||
{{if .Settings.Postgres.DSN}}<code>настроено</code>{{else}}<code>in-memory</code> (M2-демо){{end}}.</p>
|
||||
<div class="settings-body">
|
||||
|
||||
{{if not .Settings.Postgres.DSN}}
|
||||
<div style="background:var(--bg);border:1px solid var(--accent);border-radius:6px;padding:14px;margin-top:12px">
|
||||
<h3 style="margin:0 0 8px 0;font-size:15px">Самый простой вариант — подключить автоматически</h3>
|
||||
<p class="muted" style="margin:0 0 10px 0">Если у вас ещё нет своего PostgreSQL, мы поднимем его сами в контейнере (podman-compose), применим все миграции и запишем DSN. Подходит для дев-стенда и тестирования. Для прода — лучше указать свой DSN ниже.</p>
|
||||
<form method="post" action="/admin/setup/postgres/quick-start" style="margin:0">
|
||||
<button type="submit" class="btn" style="background:var(--accent);color:white;border:none;padding:10px 18px;border-radius:4px;font-weight:600;cursor:pointer">⚡ Поднять локальный PostgreSQL автоматически</button>
|
||||
<span class="muted" style="margin-left:10px;font-size:12px">Займёт ~10-30 секунд. Требуется установленный <code>podman-compose</code>.</span>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
{{/* ============ ОБЗОР ============ */}}
|
||||
<section class="settings-section active" id="sec-overview">
|
||||
<h1>Обзор</h1>
|
||||
<div class="card">
|
||||
<h2>Готовность системы: {{.ReadyCount}} из {{.TotalCount}}</h2>
|
||||
<div class="grid" style="margin-top:12px">
|
||||
{{range .Readiness}}
|
||||
<div class="stat">
|
||||
<div><span class="dot {{if .Configured}}ok{{else}}err{{end}}"></span><strong>{{.Name}}</strong></div>
|
||||
<div class="muted" style="font-size:12px;margin-top:4px">{{if .Configured}}настроено{{else}}не настроено{{end}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<details {{if not .Settings.Postgres.DSN}}style="margin-top:12px"{{end}}>
|
||||
<summary style="cursor:pointer;color:var(--accent);font-size:13px">{{if .Settings.Postgres.DSN}}Изменить параметры подключения{{else}}…или ввести параметры подключения вручную (для существующего PostgreSQL){{end}}</summary>
|
||||
<form method="post" action="/admin/setup/postgres" style="margin-top:12px">
|
||||
<div class="form-row" style="display:grid;grid-template-columns:200px 1fr;gap:12px;align-items:center">
|
||||
<label>DSN <span class="muted" title="DSN = Data Source Name. Строка вида postgres://пользователь:пароль@хост:порт/база?опции" style="cursor:help">(?)</span></label>
|
||||
<input type="text" name="dsn" value="{{.Settings.Postgres.DSN}}" placeholder="postgres://bj:secret@127.0.0.1:5432/bj?sslmode=disable" style="width:100%;padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px">
|
||||
{{/* ============ БАЗА ДАННЫХ ============ */}}
|
||||
<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>
|
||||
<p class="muted" style="margin-top:8px">При сохранении выполняется Ping. Если БД недоступна — будет ошибка.</p>
|
||||
<button type="submit" class="btn" style="background:var(--accent);color:white;border:none;padding:8px 16px;border-radius:4px;margin-top:8px">Сохранить и проверить</button>
|
||||
</form>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<!-- СКЗИ через PKCS#11: КриптоПро CSP / Рутокен / Валидата / ViPNet -->
|
||||
<div class="card">
|
||||
<h2><span class="dot {{if and .Settings.Crypto.JCPPath .Settings.Crypto.LicenseKey}}ok{{else}}err{{end}}"></span>СКЗИ (КриптоПро CSP, Рутокен и др. через PKCS#11)</h2>
|
||||
<p class="muted">Go-клиент подключается к СКЗИ напрямую через стандартный PKCS#11 интерфейс. Поддерживаются КриптоПро CSP, Рутокен ЭЦП 2.0, Валидата, ViPNet — один клиент, разные .so модули. Подробно — раздел <a href="/admin/help/cryptopro">«КриптоПро»</a> в инструкциях.</p>
|
||||
<table style="margin-bottom:12px">
|
||||
<tr><td style="width:220px" class="muted">Текущий провайдер</td><td><code>{{.Settings.Crypto.Provider}}</code></td></tr>
|
||||
<tr><td class="muted">Путь к модулю PKCS#11</td><td><code>{{if .Settings.Crypto.JCPPath}}{{.Settings.Crypto.JCPPath}}{{else}}—{{end}}</code></td></tr>
|
||||
<tr><td class="muted">UDS-сокет (legacy)</td><td><code>{{.Settings.Crypto.SocketPath}}</code></td></tr>
|
||||
<tr><td class="muted">Лицензия введена</td><td>{{if .Settings.Crypto.LicenseKey}}<span style="color:var(--ok)">да</span>{{else}}<span style="color:var(--err)">нет</span>{{end}}</td></tr>
|
||||
</table>
|
||||
<details {{if or (eq .Settings.Crypto.Provider "stub") (not .Settings.Crypto.JCPPath)}}open{{end}}>
|
||||
<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">
|
||||
<div class="form-row" style="display:grid;grid-template-columns:220px 1fr;gap:12px;align-items:center">
|
||||
<label>Провайдер СКЗИ</label>
|
||||
<select name="provider" style="padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px">
|
||||
<option value="stub" {{if eq .Settings.Crypto.Provider "stub"}}selected{{end}}>stub — без криптографии (демо)</option>
|
||||
<option value="cryptopro" {{if eq .Settings.Crypto.Provider "cryptopro"}}selected{{end}}>КриптоПро CSP (через PKCS#11)</option>
|
||||
<option value="rutoken" {{if eq .Settings.Crypto.Provider "rutoken"}}selected{{end}}>Рутокен ЭЦП 2.0 (для подписи оператора)</option>
|
||||
<option value="validata" {{if eq .Settings.Crypto.Provider "validata"}}selected{{end}}>Валидата</option>
|
||||
<option value="vipnet" {{if eq .Settings.Crypto.Provider "vipnet"}}selected{{end}}>ViPNet</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" style="display:grid;grid-template-columns:220px 1fr;gap:12px;align-items:center">
|
||||
<label>Путь к модулю PKCS#11</label>
|
||||
<input type="text" name="jcp_path" value="{{.Settings.Crypto.JCPPath}}" placeholder="/opt/cprocsp/lib/amd64/libcppkcs11.so" style="padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px">
|
||||
</div>
|
||||
<div class="form-row" style="display:grid;grid-template-columns:220px 1fr;gap:12px;align-items:center">
|
||||
<label>UDS-сокет (legacy)</label>
|
||||
<input type="text" name="socket_path" value="{{.Settings.Crypto.SocketPath}}" placeholder="/run/bj/crypto.sock (только для совместимости со старым Java crypto-service)" style="padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px">
|
||||
</div>
|
||||
<div class="form-row" style="display:grid;grid-template-columns:220px 1fr;gap:12px;align-items:flex-start">
|
||||
<label>Серийный номер лицензии</label>
|
||||
<textarea name="license_key" rows="3" placeholder="XXXX-XXXXX-XXXXX-XXXXX-XXXXX (серийный номер КриптоПро CSP)" style="padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;font-family:monospace;font-size:12px">{{.Settings.Crypto.LicenseKey}}</textarea>
|
||||
</div>
|
||||
<p class="muted">
|
||||
<strong>КриптоПро CSP</strong>: установить пакеты <code>rpm -i cprocsp-*.rpm</code>, активировать лицензию командой <code>cpconfig -license -set XXXX-...</code>, указать <code>/opt/cprocsp/lib/amd64/libcppkcs11.so</code>.<br>
|
||||
<strong>Рутокен</strong>: подключить токен USB, указать <code>/usr/lib64/librtpkcs11ecp.so</code>.<br>
|
||||
Полная инструкция: <a href="/admin/help/cryptopro">/admin/help/cryptopro</a>. При сохранении проверим, что файл модуля существует.
|
||||
</p>
|
||||
<div style="display:flex;gap:8px">
|
||||
<button type="submit" class="btn" style="background:var(--accent);color:white;border:none;padding:8px 16px;border-radius:4px">Сохранить</button>
|
||||
</div>
|
||||
</form>
|
||||
<form method="post" action="/admin/setup/crypto/check" style="margin-top:12px">
|
||||
<button type="submit" class="btn" style="background:var(--border);color:var(--text);border:none;padding:8px 16px;border-radius:4px">Проверить подключение СКЗИ</button>
|
||||
<span class="muted" style="margin-left:8px">Загрузит PKCS#11 модуль, опросит список токенов, покажет результат сверху страницы.</span>
|
||||
</form>
|
||||
|
||||
<hr style="margin:18px 0;border:none;border-top:1px solid var(--border)">
|
||||
<h3 style="font-size:14px;margin:0 0 8px">Установка КриптоПро CSP</h3>
|
||||
<p class="muted">Дистрибутив с <a href="https://www.cryptopro.ru/products/csp/downloads" target="_blank" rel="noopener">cryptopro.ru</a> (например, <code>linux-amd64.tgz</code> или <code>linux-amd64.tar</code> для РЕД ОС/ALT/ROSA). Загрузите файл здесь — он будет распакован и установлен через <code>sudo rpm -Uvh</code>. Установка длится ~30 секунд.</p>
|
||||
<form method="post" action="/admin/setup/crypto/install" enctype="multipart/form-data" style="margin-top:12px;display:flex;gap:8px;align-items:center;flex-wrap:wrap">
|
||||
<input type="file" name="dist" accept=".tar,.tgz,.gz,.rpm" required style="padding:6px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;flex:1;min-width:300px">
|
||||
<button type="submit" class="btn" style="background:var(--accent);color:white;border:none;padding:8px 16px;border-radius:4px">Загрузить и установить</button>
|
||||
</form>
|
||||
|
||||
<hr style="margin:18px 0;border:none;border-top:1px solid var(--border)">
|
||||
<h3 style="font-size:14px;margin:0 0 8px">Сертификаты на токенах</h3>
|
||||
{{if .Certificates}}
|
||||
<table>
|
||||
<thead><tr><th>Кому</th><th>Кем выдан</th><th>ИНН</th><th>Действителен</th><th>Токен</th><th>Приватный ключ</th></tr></thead>
|
||||
<tbody>
|
||||
{{range .Certificates}}
|
||||
<tr>
|
||||
<td>{{.SubjectCN}}</td>
|
||||
<td class="muted">{{.IssuerCN}}</td>
|
||||
<td><code>{{.INN}}</code></td>
|
||||
<td class="muted">до {{.NotAfter.Format "02.01.2006"}}</td>
|
||||
<td class="muted">«{{.TokenLabel}}» (slot {{.SlotID}})</td>
|
||||
<td>{{if .HasPrivateKey}}<span style="color:var(--ok)">есть</span>{{else}}<span class="muted">нет</span>{{end}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{else}}
|
||||
<p class="muted">На подключенных токенах сертификатов не найдено. Загрузите .pfx ниже или подключите Рутокен с сертификатом.</p>
|
||||
{{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>
|
||||
|
||||
<hr style="margin:18px 0;border:none;border-top:1px solid var(--border)">
|
||||
<h3 style="font-size:14px;margin:0 0 8px">Импорт сертификата (.pfx / .cer / .crt)</h3>
|
||||
<p class="muted">PFX с приватным ключом (с PIN) — для серверной подписи и подписи оператора. CER/CRT без приватного ключа — для проверки чужих подписей (например, сертификаты УЦ НРД для проверки квитанций). Подробно — <a href="/admin/help/cryptopro">/admin/help/cryptopro</a>.</p>
|
||||
<form method="post" action="/admin/setup/crypto/import-cert" enctype="multipart/form-data" style="margin-top:8px;display:grid;gap:8px;grid-template-columns:auto auto auto auto;align-items:center">
|
||||
<input type="file" name="cert" accept=".pfx,.p12,.cer,.crt" required style="padding:6px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px">
|
||||
<input type="text" name="pin" placeholder="PIN (только для .pfx/.p12)" style="padding:6px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;font-family:monospace">
|
||||
<select name="store" style="padding:6px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px">
|
||||
<option value="uMy">uMy — личный (для подписи)</option>
|
||||
<option value="mroot">mroot — корневой УЦ (для проверки)</option>
|
||||
<option value="uRoot">uRoot — промежуточные УЦ</option>
|
||||
</select>
|
||||
<button type="submit" class="btn" style="background:var(--accent);color:white;border:none;padding:8px 16px;border-radius:4px">Импортировать</button>
|
||||
</form>
|
||||
{{/* ============ КРИПТОГРАФИЯ ============ */}}
|
||||
<section class="settings-section" id="sec-crypto">
|
||||
<h1>Криптография</h1>
|
||||
|
||||
<hr style="margin:18px 0;border:none;border-top:1px solid var(--border)">
|
||||
<h3 style="font-size:14px;margin:0 0 8px">Активация лицензии</h3>
|
||||
<form method="post" action="/admin/setup/crypto/activate" style="margin-top:6px;display:flex;gap:8px;align-items:center;flex-wrap:wrap">
|
||||
<input type="text" name="license_key" value="{{.Settings.Crypto.LicenseKey}}" placeholder="XXXX-XXXXX-XXXXX-XXXXX-XXXXX (серийный номер КриптоПро CSP)" style="padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;font-family:monospace;font-size:12px;min-width:340px">
|
||||
<button type="submit" class="btn" style="background:var(--ok);color:#0a0f1a;border:none;padding:8px 16px;border-radius:4px;font-weight:600">Активировать лицензию</button>
|
||||
</form>
|
||||
<p class="muted" style="margin-top:8px">Вызовет <code>cpconfig -license -set</code> и сохранит серийник. Если КриптоПро CSP ещё не установлен — покажет инструкцию.</p>
|
||||
</details>
|
||||
</div>
|
||||
<div class="card" style="border-left:3px solid var(--accent)">
|
||||
<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 .FlashContainers}}ok{{else}}warn{{end}}"></span>Контейнеры на USB-носителях (флешка/Рутокен)</h2>
|
||||
{{if .FlashContainers}}
|
||||
<p class="muted">Найдено {{len .FlashContainers}} контейнер(а) формата <code>name.000</code> на смонтированных USB-носителях. Кнопка ниже копирует папку в <code>/var/opt/cprocsp/keys/$USER/</code> — после этого контейнер виден как <code>\\.\HDIMAGE\name</code> и работает без вставленной флешки.</p>
|
||||
<table style="margin-top:8px">
|
||||
<thead><tr><th>Носитель</th><th>Имя контейнера</th><th>Файлы</th><th>Статус</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
{{range .FlashContainers}}
|
||||
<tr>
|
||||
<td><code style="font-size:12px">{{.Mountpoint}}</code></td>
|
||||
<td><strong>{{.Name}}</strong></td>
|
||||
<td><span class="muted" style="font-size:11px">{{len .Files}} файлов</span></td>
|
||||
<td>{{if .AlreadyImported}}<span style="color:var(--ok)">уже в HDIMAGE</span>{{else}}<span class="muted">только на флешке</span>{{end}}</td>
|
||||
<td>
|
||||
{{if not .AlreadyImported}}
|
||||
<form method="post" action="/admin/setup/crypto/copy-container" style="margin:0">
|
||||
<input type="hidden" name="src" value="{{.Path}}">
|
||||
<button type="submit" class="btn" style="background:var(--ok);color:#0a0f1a;padding:6px 12px;font-size:12px;font-weight:600">Скопировать в локальное хранилище</button>
|
||||
<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">
|
||||
<h2><span class="dot {{if .Media}}ok{{else}}warn{{end}}"></span>Носители ключей</h2>
|
||||
<p class="muted">USB-флешки сканируются автоматически. Образы (.iso/.img/.zip/.7z) загружаются ниже — bj-server распакует и найдёт профиль Валидаты, контейнеры, сертификаты.</p>
|
||||
<form method="post" action="/admin/setup/media/iso/upload" enctype="multipart/form-data" style="margin-top:12px;display:flex;gap:8px;align-items:center;flex-wrap:wrap">
|
||||
<input type="file" name="iso" accept=".iso,.img,.zip,.7z" required style="flex:1;min-width:240px">
|
||||
<input type="password" name="password" placeholder="Пароль архива (для MOEX — 11)" autocomplete="off" style="min-width:200px">
|
||||
<button type="submit" class="btn">Загрузить</button>
|
||||
</form>
|
||||
<p class="muted" style="margin-top:6px;font-size:12px">Лимит 500 МБ. Распаковка через 7z.</p>
|
||||
|
||||
{{if .Media}}
|
||||
{{range .Media}}
|
||||
<div style="margin-top:14px;padding:14px;background:var(--card-2);border:1px solid var(--border);border-radius:10px">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center">
|
||||
<div><strong>{{if eq .Kind "iso"}}📀 ISO{{else}}🔌 USB{{end}}</strong> <code style="font-size:12px;margin-left:8px">{{.Mountpoint}}</code>{{if .Source}}<span class="muted" style="font-size:11px;margin-left:8px">{{.Source}}</span>{{end}}</div>
|
||||
{{if eq .Kind "iso"}}
|
||||
<form method="post" action="/admin/setup/media/iso/unmount" style="margin:0" onsubmit="return confirm('Удалить распаковку {{.Mountpoint}}?');">
|
||||
<input type="hidden" name="id" value="{{.ID}}"><button type="submit" class="btn btn-secondary" style="padding:5px 11px;font-size:12px">Удалить распаковку</button>
|
||||
</form>
|
||||
{{end}}
|
||||
</td>
|
||||
</tr>
|
||||
</div>
|
||||
{{if .Profile}}
|
||||
<h3>Профиль Валидаты</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr><td>ПСП (.pse)</td><td class="muted" style="font-size:11px">{{range .Profile.PSEFiles}}{{.}}<br>{{end}}</td></tr>
|
||||
<tr><td>ЛСП (.gdbm)</td><td class="muted" style="font-size:11px">{{range .Profile.GDBMFiles}}{{.}}<br>{{end}}</td></tr>
|
||||
<tr><td>Ключи (.vdk)</td><td class="muted" style="font-size:11px">{{range .Profile.KeyFiles}}{{.}}<br>{{end}}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<form method="post" action="/admin/setup/media/import-profile" style="margin-top:8px;display:flex;gap:8px;align-items:center;flex-wrap:wrap">
|
||||
<input type="hidden" name="root" value="{{.Profile.Root}}">
|
||||
<input type="text" name="name" placeholder="Имя профиля, напр. nrd-edo">
|
||||
{{if .Profile.Imported}}<span style="color:var(--ok)">✓ импортирован</span>{{else}}<button type="submit" class="btn btn-ok" style="padding:6px 12px;font-size:12px">Импортировать профиль</button>{{end}}
|
||||
</form>
|
||||
{{end}}
|
||||
{{if .Containers}}
|
||||
<h3>Контейнеры ({{len .Containers}})</h3>
|
||||
<table>
|
||||
<thead><tr><th>Имя</th><th>Статус</th><th></th></tr></thead>
|
||||
<tbody>{{range .Containers}}
|
||||
<tr><td><strong>{{.Name}}</strong> <span class="muted" style="font-size:11px">{{.Path}}</span></td><td>{{if .Imported}}<span style="color:var(--ok)">импортирован</span>{{else}}<span class="muted">нет</span>{{end}}</td>
|
||||
<td>{{if not .Imported}}<form method="post" action="/admin/setup/media/import-container" style="margin:0"><input type="hidden" name="path" value="{{.Path}}"><button type="submit" class="btn btn-ok" style="padding:5px 11px;font-size:12px">Импортировать</button></form>{{end}}</td></tr>
|
||||
{{end}}</tbody>
|
||||
</table>
|
||||
{{end}}
|
||||
{{if and (not .Containers) (not .Certificates) (not .Profile)}}<p class="muted" style="margin-top:8px;font-size:12px">Профиль Валидаты не найден на носителе.</p>{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{else}}
|
||||
<p class="muted">Подключённые USB-носители с контейнерами КриптоПро (папки <code>name.000</code> с *.key) не обнаружены. Поиск идёт в <code>/run/media/$USER/</code>, <code>/media/$USER/</code>, <code>/media/</code>, <code>/mnt/</code>. Вставьте флешку и обновите страницу.</p>
|
||||
{{end}}
|
||||
</div>
|
||||
{{else}}<p class="muted" style="margin-top:10px">Носители не обнаружены. Подключите USB или загрузите образ.</p>{{end}}
|
||||
|
||||
<!-- Авто-загрузка сертификатов УЦ НРД -->
|
||||
<div class="card">
|
||||
<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/workflow/system/cryptography/</a>) и других УЦ. Каждый URL скачивается, парсится X.509, и автоматически импортируется в КриптоПро (<code>mroot</code> для корневых, <code>uRoot</code> для промежуточных). Включите авто-обновление — раз в сутки система перепроверит и переустановит, если сертификат изменился.</p>
|
||||
<form method="post" action="/admin/setup/cacerts" style="margin-top:10px;display:grid;gap:10px">
|
||||
<label>URL'ы .cer-файлов (один на строку)</label>
|
||||
<textarea name="urls" rows="4" placeholder="https://www.nsd.ru/path/to/root-ca.cer https://www.nsd.ru/path/to/sub-ca.cer" style="padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px;font-family: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}}>
|
||||
<span>Авто-обновление раз в сутки</span>
|
||||
</label>
|
||||
<div style="display:flex;gap:8px;align-items:center">
|
||||
<button type="submit" class="btn">Сохранить</button>
|
||||
{{if .ImportedProfiles}}
|
||||
<h3 style="margin-top:18px">Импортированные профили</h3>
|
||||
<table>
|
||||
<thead><tr><th>Имя</th><th>Состояние</th><th>Действия</th></tr></thead>
|
||||
<tbody>{{range .ImportedProfiles}}
|
||||
<tr><td><strong>{{.}}</strong></td><td>{{if eq . $.Settings.Crypto.Profile}}<span style="color:var(--ok)">✓ активен</span>{{else}}<span class="muted">не активен</span>{{end}}</td>
|
||||
<td style="display:flex;gap:6px;flex-wrap:wrap">
|
||||
{{if ne . $.Settings.Crypto.Profile}}<form method="post" action="/admin/setup/media/activate-profile" style="margin:0"><input type="hidden" name="name" value="{{.}}"><button type="submit" class="btn" style="padding:5px 11px;font-size:12px">Активировать</button></form>{{end}}
|
||||
<form method="post" action="/admin/setup/media/delete-profile" style="margin:0" onsubmit="return confirm('Удалить профиль «{{.}}»?');"><input type="hidden" name="name" value="{{.}}"><button type="submit" class="btn btn-danger" style="padding:5px 11px;font-size:12px">Удалить</button></form>
|
||||
</td></tr>
|
||||
{{end}}</tbody>
|
||||
</table>
|
||||
{{end}}
|
||||
</div>
|
||||
</form>
|
||||
<form method="post" action="/admin/setup/cacerts/fetch" style="margin-top:8px">
|
||||
<button type="submit" class="btn" style="background:var(--ok);color:#0a0f1a;font-weight:600">⬇ Скачать и импортировать сейчас</button>
|
||||
{{if not .Settings.CACerts.LastFetch.IsZero}}
|
||||
<span class="muted" style="margin-left:10px">Последнее обновление: {{.Settings.CACerts.LastFetch.Format "02.01.2006 15:04:05"}}</span>
|
||||
{{end}}
|
||||
</form>
|
||||
{{if .Settings.CACerts.FetchedCerts}}
|
||||
<table style="margin-top:14px">
|
||||
<thead><tr><th>URL</th><th>Владелец</th><th>Хранилище</th><th>Действителен до</th><th>SHA-256</th><th>Статус</th></tr></thead>
|
||||
<tbody>
|
||||
{{range .Settings.CACerts.FetchedCerts}}
|
||||
<tr>
|
||||
<td style="max-width:280px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="{{.URL}}"><code style="font-size:11px">{{.URL}}</code></td>
|
||||
<td>{{.SubjectCN}}</td>
|
||||
<td><code>{{.Store}}</code></td>
|
||||
<td>{{if not .NotAfter.IsZero}}{{.NotAfter.Format "02.01.2006"}}{{end}}</td>
|
||||
<td><code style="font-size:11px">{{if .SHA256}}{{slice .SHA256 0 12}}…{{end}}</code></td>
|
||||
<td>{{if .Error}}<span style="color:var(--err)" title="{{.Error}}">ошибка</span>{{else}}<span style="color:var(--ok)">ок</span>{{end}}</td>
|
||||
</tr>
|
||||
|
||||
{{/* Сертификаты УЦ */}}
|
||||
<div class="card">
|
||||
<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">
|
||||
<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"><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}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{end}}
|
||||
{{if .Settings.CACerts.LastFetchLog}}
|
||||
<details style="margin-top:10px">
|
||||
<summary style="cursor:pointer;color:var(--accent);font-size:13px">Лог последнего обновления</summary>
|
||||
<pre style="margin-top:8px">{{.Settings.CACerts.LastFetchLog}}</pre>
|
||||
</details>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- nsd-adapter / ИШ НРД -->
|
||||
<div class="card">
|
||||
<h2><span class="dot {{if .Settings.NSD.IGWBaseURL}}ok{{else}}err{{end}}"></span>Интеграционный шлюз НРД (ИШ)</h2>
|
||||
<p class="muted">{{if not .Settings.NSD.IGWBaseURL}}Сейчас <code>mock-режим</code> — Decision эмитируется через 3 секунды после Send.{{else}}Профиль <code>{{.Settings.NSD.Profile}}</code>, ИШ <code>{{.Settings.NSD.IGWBaseURL}}</code>.{{end}}</p>
|
||||
<p class="muted">Подключение к стендам: <a href="/admin/help/systems">/admin/help/systems</a> — там полная таблица URL контуров GUEST/TEST3/PROD и инструкция по установке ИШ. Дистрибутив ИШ скачивается с <code>www.nsd.ru/workflow/system/programs/#0-widget-faq-0-4</code>.</p>
|
||||
<details {{if not .Settings.NSD.IGWBaseURL}}open{{end}}>
|
||||
<summary style="cursor:pointer;color:var(--accent);font-size:13px">Изменить параметры ИШ</summary>
|
||||
<form method="post" action="/admin/setup/nsd" style="margin-top:12px;display:grid;gap:10px">
|
||||
<div class="form-row" style="display:grid;grid-template-columns:200px 1fr;gap:12px;align-items:center">
|
||||
<label>Профиль</label>
|
||||
<select name="profile" id="nsd-profile" style="padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px">
|
||||
<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>
|
||||
{{/* ============ НРД ============ */}}
|
||||
<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>
|
||||
</div>
|
||||
<div class="form-row" style="display:grid;grid-template-columns:200px 1fr;gap:12px;align-items:center">
|
||||
<label>URL ONYX (WSL) НРД</label>
|
||||
<input type="text" name="igw_base_url" id="nsd-url" value="{{.Settings.NSD.IGWBaseURL}}" placeholder="будет заполнено по профилю" style="padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px">
|
||||
</div>
|
||||
<div class="form-row" style="display:grid;grid-template-columns:200px 1fr;gap:12px;align-items:center">
|
||||
<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="GUEST_GOST_CONTAINER (или ваш контейнер УЦ НРД)" style="padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px">
|
||||
</div>
|
||||
<p class="muted">При смене профиля URL ONYX автозаполнится по таблице НРД (из <code>DOC/Ссылки для доступа в тестовые контуры.pdf</code>). При сохранении проверяется доступность URL.</p>
|
||||
<button type="submit" class="btn" style="background:var(--accent);color:white;border:none;padding:8px 16px;border-radius:4px">Сохранить и проверить</button>
|
||||
</form>
|
||||
<script>
|
||||
// Автозаполнение URL ONYX и дефолтного контейнера по выбранному профилю.
|
||||
(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 profile = document.getElementById("nsd-profile");
|
||||
var urlInput = document.getElementById("nsd-url");
|
||||
var contInput = document.getElementById("nsd-container");
|
||||
profile.addEventListener("change", function() {
|
||||
var p = profile.value;
|
||||
if (urls[p]) {
|
||||
if (!urlInput.value || confirm("Заменить URL и контейнер на дефолт для профиля " + p + "?")) {
|
||||
urlInput.value = urls[p][0];
|
||||
contInput.value = urls[p][1];
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</details>
|
||||
</div>
|
||||
<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="раздел депозитарного счёта">
|
||||
<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>
|
||||
|
||||
<!-- LK callback -->
|
||||
<div class="card">
|
||||
<h2><span class="dot {{if .Settings.LK.CallbackURL}}ok{{else}}err{{end}}"></span>Callback в ЛК</h2>
|
||||
<p class="muted">{{if .Settings.LK.CallbackURL}}Callback URL: <code>{{.Settings.LK.CallbackURL}}</code>{{else}}Сейчас используется встроенный lk-emulator (он сам зарегистрировал свой адрес при старте).{{end}}</p>
|
||||
<details {{if not .Settings.LK.CallbackURL}}open{{end}}>
|
||||
<summary style="cursor:pointer;color:var(--accent);font-size:13px">Указать URL реального ЛК</summary>
|
||||
<form method="post" action="/admin/setup/lk" style="margin-top:12px">
|
||||
<div class="form-row" style="display:grid;grid-template-columns:200px 1fr;gap:12px;align-items:center">
|
||||
<div class="card">
|
||||
<h2><span class="dot {{if .Settings.LK.CallbackURL}}ok{{else}}warn{{end}}"></span>Callback в личный кабинет <span class="muted" style="font-size:12px;font-weight:400">(необязательно)</span></h2>
|
||||
<p class="muted">{{if .Settings.LK.CallbackURL}}<code>{{.Settings.LK.CallbackURL}}</code>{{else}}Не настроен — уведомления в ЛК отключены. Для работы с НРД не требуется.{{end}}</p>
|
||||
<form method="post" action="/admin/setup/lk" style="margin-top:12px;display:grid;gap:10px;max-width:640px">
|
||||
<label>Callback URL</label>
|
||||
<input type="text" name="callback_url" value="{{.Settings.LK.CallbackURL}}" placeholder="http://lk.example.com" style="width:100%;padding:8px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px">
|
||||
<input type="text" name="callback_url" value="{{.Settings.LK.CallbackURL}}" placeholder="http://lk.example.com">
|
||||
<div><button type="submit" class="btn">Сохранить и проверить</button></div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{/* ============ ТЕСТЫ ============ */}}
|
||||
<section class="settings-section" id="sec-tests">
|
||||
<h1>Тесты</h1>
|
||||
|
||||
<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>
|
||||
<p class="muted" style="margin-top:8px">URL до базового хоста ЛК (без /api). При сохранении выполняется GET <code>{URL}/healthz</code>.</p>
|
||||
<button type="submit" class="btn" style="background:var(--accent);color:white;border:none;padding:8px 16px;border-radius:4px;margin-top:8px">Сохранить и проверить</button>
|
||||
</form>
|
||||
</details>
|
||||
{{end}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{/* ============ ОБНОВЛЕНИЯ ============ */}}
|
||||
<section class="settings-section" id="sec-update">
|
||||
<h1>Обновления</h1>
|
||||
|
||||
<div class="card">
|
||||
<h2>Версия bj-server</h2>
|
||||
<div style="display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-top:6px">
|
||||
<span class="stat-value" style="font-size:20px">{{.CurrentVersion}}</span>
|
||||
{{if .Settings.Update.Available}}
|
||||
{{if ne .Settings.Update.Available .CurrentVersion}}
|
||||
<span class="hero-status warn">● Доступна {{.Settings.Update.Available}}</span>
|
||||
<form method="post" action="/admin/setup/update/apply" style="margin:0" onsubmit="return confirm('Скачать и установить {{.Settings.Update.Available}}? bj-server перезапустится.');">
|
||||
<button type="submit" class="btn btn-ok">⬆ Установить {{.Settings.Update.Available}}</button>
|
||||
</form>
|
||||
{{else}}<span class="hero-status ok">● Актуальная версия</span>{{end}}
|
||||
{{end}}
|
||||
<form method="post" action="/admin/setup/update/check" style="margin:0"><button type="submit" class="btn btn-secondary">Проверить обновления</button></form>
|
||||
</div>
|
||||
{{if .Settings.Update.Notes}}<p class="muted" style="margin-top:10px">Что нового: {{.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 и sha256 — без валидной подписи установка не выполняется.</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://updates.example.com">
|
||||
<label>Канal</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">
|
||||
<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}}
|
||||
{{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}}
|
||||
<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">Статус</td><td>{{.License.Message}}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{{else}}
|
||||
<p class="muted">Лицензионный ключ не введён. Без лицензии сервис работает, но автообновления заблокированы.</p>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Активация</h2>
|
||||
<p class="muted">Вставьте лицензионный ключ, полученный от поставщика. Проверка офлайн по подписи — связь с сервером лицензий не требуется.</p>
|
||||
<form method="post" action="/admin/setup/license" style="margin-top:12px;display:grid;gap:10px;max-width:720px">
|
||||
<label>Лицензионный ключ</label>
|
||||
<textarea name="key" rows="3" style="font-family:ui-monospace,monospace;font-size:11px" placeholder="payload.signature.keyid">{{.Settings.License.Key}}</textarea>
|
||||
<details>
|
||||
<summary style="cursor:pointer;color:var(--accent);font-size:13px">Публичный ключ издателя (если не зашит)</summary>
|
||||
<input type="text" name="public_key" value="{{.Settings.License.PublicKey}}" placeholder="base64 Ed25519" style="margin-top:8px;width:100%">
|
||||
</details>
|
||||
<div><button type="submit" class="btn">Активировать</button></div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Тестовый прогон -->
|
||||
<div class="card">
|
||||
<h2>Тестовый прогон сквозной заявки</h2>
|
||||
<p class="muted">Создаст заявку с предзаполненными данными (инвестор Иванов И.И., 1500 акций Газпрома, ИИС T03), отправит её через всю цепочку и дождётся финального статуса. Если ИШ НРД настроен — пойдёт в реальный ИШ; иначе через mock с задержкой 3 сек.</p>
|
||||
<form method="post" action="/admin/setup/test-run" style="margin-top:12px">
|
||||
<button type="submit" class="btn" style="background:var(--accent);color:white;border:none;padding:10px 20px;border-radius:4px;font-weight:600">Запустить тестовую заявку</button>
|
||||
</form>
|
||||
{{if .Settings.LastTest}}
|
||||
<div style="margin-top:16px;padding:12px;background:var(--bg);border:1px solid var(--border);border-radius:4px">
|
||||
<strong>Последний прогон:</strong>
|
||||
<table style="margin-top:8px">
|
||||
<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.StartedAt.Format "02.01.2006 15:04:05"}} — длительность {{.Settings.LastTest.FinishedAt.Sub .Settings.LastTest.StartedAt}}</td></tr>
|
||||
<tr><td class="muted">Сообщение</td><td>{{.Settings.LastTest.Message}}</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
// Переключение разделов админ-центра + запоминание выбранного (hash).
|
||||
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 { history.replaceState(null, '', '#' + id); } catch(e) {}
|
||||
}
|
||||
navs.forEach(function(b){ b.addEventListener('click', function(){ show(b.dataset.sec); }); });
|
||||
var h = (location.hash || '').replace('#','');
|
||||
if (h && document.getElementById('sec-' + h)) show(h);
|
||||
})();
|
||||
</script>
|
||||
{{end}}
|
||||
|
||||
Reference in New Issue
Block a user