Интерфейс клиента без технических подробностей; дубли убраны

Техника — только в режиме разработчика (devOn):
- версии: клиенту — bj, шлюз НРД, СКЗИ, QUIK, DEPO, без «Подробностей»
  (Java, bj-crypto, пути, установщик, ОС, PostgreSQL — разработчикам)
- Криптография: строки crypto-service/java, параметры провайдера и сокет,
  серийные номера, журнал сверки (клиенту — только при ошибке), дамп справочника
  и путь nsd-certs; убрана карточка «Ключ организации» — дубль сводки
- НРД: строка версии ИШ (дубль сводки), «Расширенные» (адреса, контейнеры,
  config.xml), имя профиля ключа, адрес канала; контур по-русски
- Обновления: имена файлов и служб, адрес артефактории и ключ издателя
- Диагностика: имена служб и сырые состояния; справочник участников — без кодов
- ключи ролей и модулей лицензии, путь users.json, DSN/Ping/in-memory, FTV_M2M,
  путь Валидаты и служба bj-crypto в мастере, «проверена ZETIT», Samba

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
This commit is contained in:
fontvielle
2026-10-21 23:44:52 +03:00
parent 29e19c78a9
commit f1065e369a
9 changed files with 126 additions and 77 deletions
+4
View File
@@ -171,6 +171,10 @@ var templateFuncs = template.FuncMap{
"handbookDepos": handbookDepos,
"timeZones": func() any { return wizTimeZones },
"timeZoneKnown": timeZoneKnown,
"clientVer": clientVersion,
"verTitle": versionTitle,
"contourRu": contourRu,
"looksErr": looksErr,
"intList": func(v ...int) []int { return v },
"nsdSegTip": nsdSegTip,
"devOn": authTemplateFuncs["devOn"],
+7 -1
View File
@@ -70,9 +70,15 @@ func TestSetupPageShowsVersions(t *testing.T) {
t.Fatalf("код %d: %s", rec.Code, rec.Body.String())
}
body := rec.Body.String()
for _, want := range []string{"Версии систем", "<strong>21.3.0</strong>", "QUIK — Модуль неторговых поручений: <strong>21.3.0</strong> · обновлена 05.06.2026", "не определена", "нет связи"} {
// клиенту (без режима разработчика) — простое название и версия, без подробностей источника
for _, want := range []string{"Версии систем", "<strong>21.3.0</strong>", "QUIK — модуль неторговых поручений: <strong>21.3.0</strong>", "не определена"} {
if !strings.Contains(body, want) {
t.Errorf("в странице нет %q", want)
}
}
for _, not := range []string{"обновлена 05.06.2026"} {
if strings.Contains(body, not) {
t.Errorf("клиенту видно %q", not)
}
}
}
+50
View File
@@ -0,0 +1,50 @@
package lkgateway
// uiclient.go — что интерфейс показывает клиенту, а что только в режиме разработчика (devOn).
// Клиенту — состояние и действия; версии сторонних систем — только те, о которых спрашивает
// поддержка НРД и Московской биржи. Пути, службы, Java, внутренние модули — разработчикам.
import "strings"
// clientVersionTitles — версии для клиента и их названия без внутренних имён.
var clientVersionTitles = map[string]string{
"bj-server": "bj",
"ish": "Шлюз НРД",
"validata": "СКЗИ «Валидата Клиент L»",
"quik-mnp": "QUIK — модуль неторговых поручений",
"depo": "Fancy DEPO",
}
func clientVersion(key string) bool { _, ok := clientVersionTitles[key]; return ok }
// versionTitle — название строки версии: разработчику — полное (с внутренним именем), клиенту — простое.
func versionTitle(v SystemVersion) string {
if t, ok := clientVersionTitles[v.Key]; ok && !devMode(globalRC) {
return t
}
return v.Title
}
// contourRu — контур НРД по-русски (prod-gost, test3, manual… → «боевой», «тестовый (TEST3)»).
func contourRu(s string) string {
l := strings.ToLower(strings.TrimSpace(s))
switch {
case strings.HasPrefix(l, "prod"):
return "боевой"
case strings.HasPrefix(l, "test"):
return "тестовый (TEST3)"
case strings.HasPrefix(l, "guest"):
return "гостевой"
case l == "manual":
return "добавлен вручную"
case l == "":
return "—"
}
return s
}
// looksErr — в журнале есть ошибка (журнал клиенту показываем только тогда).
func looksErr(s string) bool {
l := strings.ToLower(s)
return strings.Contains(l, "ошиб") || strings.Contains(l, "не удалось") || strings.Contains(l, "error") || strings.Contains(l, "откат")
}
+1 -1
View File
@@ -137,7 +137,7 @@ func versionProbes(rc *RuntimeConfig) []versionProbe {
fn: func(ctx context.Context) (string, string, error) {
return componentVersion(ctx, "bj-component-install")
}},
{key: "bj-npr", title: "Модуль «НПР / УДС» (bj-npr)", section: "npr", when: comp("bj-npr"),
{key: "bj-npr", title: "Модуль «НПР / УДС» (bj-npr)", section: "quikexport", when: comp("bj-npr"),
fn: func(ctx context.Context) (string, string, error) { return componentVersion(ctx, "bj-npr") }},
{key: "bj-quikexport", title: "Модуль «Экспорт QUIK» (bj-quikexport)", section: "quikexport", when: comp("bj-quikexport"),
fn: func(ctx context.Context) (string, string, error) {
@@ -45,14 +45,14 @@
</div>
<div class="card" id="versions">
<h2>Версии систем</h2>
<p class="muted">Что установлено на этом сервере и с чем работает bj: собственные компоненты и внешние системы. Та же версия показана в разделе каждого модуля. Сведения на {{msk .Versions.At}}.</p>
<p class="muted">bj и системы, с которыми он работает. Сведения на {{msk .Versions.At}}.</p>
<div style="overflow-x:auto"><table style="margin-top:8px">
<thead><tr><th>Система</th><th>Версия</th><th>Подробности</th></tr></thead>
<tbody>{{range .Versions.Items}}<tr>
<td>{{if and .Section (ne .Section "overview")}}<a href="#{{.Section}}" onclick="var b=document.querySelector('.settings-nav [data-sec=&quot;{{.Section}}&quot;]');if(b){b.click();return false}">{{.Title}}</a>{{else}}{{.Title}}{{end}}</td>
<thead><tr><th>Система</th><th>Версия</th>{{if devOn}}<th>Подробности</th>{{end}}</tr></thead>
<tbody>{{range .Versions.Items}}{{if or devOn (clientVer .Key)}}<tr>
<td>{{if and .Section (ne .Section "overview")}}<a href="#{{.Section}}" onclick="var b=document.querySelector('.settings-nav [data-sec=&quot;{{.Section}}&quot;]');if(b){b.click();return false}">{{verTitle .}}</a>{{else}}{{verTitle .}}{{end}}</td>
<td>{{if .Version}}<strong>{{.Version}}</strong>{{else}}<span class="muted">не определена</span>{{end}}</td>
<td class="muted" style="font-size:12.5px">{{.Detail}}{{with .Err}} <span style="color:var(--err)">{{.}}</span>{{end}}</td>
</tr>{{end}}</tbody>
{{if devOn}}<td class="muted" style="font-size:12.5px">{{.Detail}}{{with .Err}} <span style="color:var(--err)">{{.}}</span>{{end}}</td>{{end}}
</tr>{{end}}{{end}}</tbody>
</table></div>
<form method="post" action="/admin/setup/versions/refresh" style="margin-top:10px"><input type="hidden" name="section" value="versions"><button type="submit" class="btn btn-secondary">Обновить сведения</button></form>
</div>
@@ -64,7 +64,7 @@
{{template "verline" (.Versions.Get "postgres")}}
<div class="card">
<h2><span class="dot {{if .Settings.Postgres.DSN}}ok{{else}}err{{end}}"></span>PostgreSQL</h2>
<p class="muted">Журнал сделок m2m-core и принимающая БД. Сейчас: {{if .Settings.Postgres.DSN}}<code>подключено</code>{{else}}<code>in-memory</code> (данные не сохраняются){{end}}.</p>
<p class="muted">Здесь bj хранит переводы, журнал и настройки. Сейчас: {{if .Settings.Postgres.DSN}}<b style="color:var(--ok)">подключена</b>{{else}}<b style="color:var(--err)">не подключена</b> — данные не сохраняются{{end}}.</p>
{{if and devOn (not .Settings.Postgres.DSN)}}
<div style="background:var(--card-2);border:1px solid var(--accent);border-radius:10px;padding:16px;margin-top:12px">
<h3 style="margin:0 0 8px">Подключить автоматически</h3>
@@ -75,11 +75,11 @@
</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>
<summary style="cursor:pointer;color:var(--accent);font-size:13px">{{if .Settings.Postgres.DSN}}Изменить подключение{{else}}…или ввести строку подключения вручную{{end}}</summary>
<form method="post" action="/admin/setup/postgres" style="margin-top:12px;display:grid;gap:10px;max-width:640px">
<label>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>
<label for="db-dsn">Строка подключения к базе</label>
<input type="text" id="db-dsn" name="dsn" value="{{.Settings.Postgres.DSN}}" placeholder="postgres://пользователь:пароль@адрес:5432/bj?sslmode=disable">
<p class="muted" style="margin:0">При сохранении bj проверит подключение.</p>
<div><button type="submit" class="btn">Сохранить и проверить</button></div>
</form>
</details>
@@ -170,7 +170,7 @@
<summary><span class="sum-h">Расширенные настройки</span><span class="muted">роли и права, домен вручную: контроллер, сертификат, группы, перепроверка, время сессии</span></summary>
<h3 class="adv-h">Роли и права</h3>
<table style="font-size:12.5px"><thead><tr><th>Роль</th><th>Что разрешено</th></tr></thead><tbody>
{{range $U.Roles}}<tr><td style="white-space:nowrap"><b>{{.Title}}</b><br><code style="font-size:11px">{{.Key}}</code></td><td>{{.Desc}}</td></tr>{{end}}
{{range $U.Roles}}<tr><td style="white-space:nowrap"><b>{{.Title}}</b>{{if devOn}}<br><code style="font-size:11px">{{.Key}}</code>{{end}}</td><td>{{.Desc}}</td></tr>{{end}}
{{range $U.Hidden}}<tr class="muted"><td style="white-space:nowrap">{{.Title}}</td><td>Недоступна: в лицензии нет модуля «{{.Missing}}».</td></tr>{{end}}
</tbody></table>
<p class="muted" style="font-size:12px;margin-top:6px">{{if $U.Modules}}Подключённые модули ({{range $i, $m := $U.Modules}}{{if $i}}, {{end}}«{{$m.Title}}»{{end}}) можно выдать любому пользователю дополнительно к роли. {{end}}Роли и модули показываются по лицензии: права модуля, которого нет в лицензии, не действуют. Настройки и пользователи — только администратор.</p>
@@ -219,7 +219,7 @@
<form method="post" action="/admin/setup/auth/recheck"><button class="btn btn-secondary" type="submit">Перепроверить сейчас</button></form>
{{else}}<span class="muted"><span class="dot warn"></span>Перепроверка групп выключена: роль доменного пользователя меняется только при следующем входе. Укажите сервисную учётную запись выше.</span>{{end}}
</div>
<p class="muted" style="font-size:12.5px;margin-top:14px">Если потерян доступ администратора — удалите файл <code>/var/lib/bj/.bj/users.json</code> на сервере: система вернётся в режим первичной настройки.</p>
{{if devOn}}<p class="muted" style="font-size:12.5px;margin-top:14px">Если потерян доступ администратора — удалите файл <code>/var/lib/bj/.bj/users.json</code> на сервере: система вернётся в режим первичной настройки.</p>{{else}}<p class="muted" style="font-size:12.5px;margin-top:14px">Если потерян доступ администратора — обратитесь в поддержку ZETIT.</p>{{end}}
</details>
</div>
</section>
@@ -252,18 +252,7 @@
<div class="card">
<details class="adv-box" {{if not (and cryptoReady orgKeyCert)}}open{{end}}>
<summary><span class="sum-h">Перенастроить</span><span class="muted">ключ организации, проверка СКЗИ, сертификаты НРД в справочнике шлюза, версии</span></summary>
<div style="margin-top:16px">{{template "verline" (.Versions.Get "crypto-service.jar")}}{{template "verline" (.Versions.Get "validata")}}{{template "verline" (.Versions.Get "java")}}</div>
{{$kc := orgKeyCert}}
{{if and cryptoReady $kc}}
<div class="card" id="sec-orgkey">
<h2><span class="dot {{if lt (index $kc "Days") 30}}warn{{else}}ok{{end}}"></span>Ключ организации</h2>
<p style="margin:6px 0">Сертификат «{{index $kc "Owner"}}», выдан: {{index $kc "Issuer"}} — действует до <b>{{index $kc "Until"}}</b> (осталось {{index $kc "Days"}} дн.).</p>
{{if lt (index $kc "Days") 30}}<p style="color:var(--warn);margin:6px 0">⚠ Сертификат скоро закончится. Получите новый ключ в Удостоверяющем центре Московской биржи и установите его — без этого обмен с НРД остановится.</p>
<a href="/admin/setup/keywizard" class="btn btn-ok" style="margin-top:6px;display:inline-block">Установить новый ключ →</a>
{{else}}<p class="muted" style="font-size:13px;margin:6px 0 0">Перевыпустили ключ (новый сертификат) или нужен ключ другого контура — <a href="/admin/setup/keywizard">заменить ключ</a>: bj поможет подключить флешку или записать новый ключ из архива.</p>{{end}}
</div>
{{end}}
{{if devOn}}<div style="margin-top:16px">{{template "verline" (.Versions.Get "crypto-service.jar")}}{{template "verline" (.Versions.Get "java")}}</div>{{end}}
<div class="card">
<h2><span class="dot {{if cryptoReady}}ok{{else}}warn{{end}}"></span>СКЗИ «Валидата Клиент L»</h2>
@@ -276,7 +265,7 @@
<form method="post" action="/admin/setup/restart-server" style="margin:0" onsubmit="return confirm('Перезапустить bj-server? Через 5-10 сек страница вернётся.');"><button type="submit" class="btn btn-secondary">↻ bj-server</button></form>
{{end}}
</div>
<details style="margin-top:14px">
{{if devOn}}<details style="margin-top:14px">
<summary style="cursor:pointer;color:var(--accent);font-size:13px">Параметры провайдера (для совместимости)</summary>
<form method="post" action="/admin/setup/crypto" style="margin-top:12px;display:grid;gap:10px;max-width:640px">
<label>Провайдер</label>
@@ -290,18 +279,18 @@
<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>
</details>{{end}}
</div>
{{/* Доверенные сертификаты НРД в справочнике ИШ */}}
<div class="card" id="sec-nsdcerts">
<h2><span class="dot {{if .NSDCerts.Pending}}warn{{else if .NSDCerts.LastRun.IsZero}}warn{{else}}ok{{end}}"></span>Сертификаты НРД в справочнике ИШ</h2>
<p class="muted">Комплект доверенных сертификатов крипто-сервера НРД поставляется вместе с bj и при обновлении приезжает всем клиентам. bj сам сверяет справочник активного профиля ИШ{{if .NSDCerts.Profile}} (<code>{{.NSDCerts.Profile}}</code>){{end}}: недостающие добавляет (только если их УЦ уже есть в справочнике — боевой сертификат не попадёт в тестовый профиль), старые удаляет строго после разрешённого НРД срока. Каждое изменение — с бэкапом, проверкой подписи и откатом при ошибке; ИШ перезапускается на несколько секунд.</p>
<h2><span class="dot {{if .NSDCerts.Pending}}warn{{else if .NSDCerts.LastRun.IsZero}}warn{{else}}ok{{end}}"></span>Сертификаты НРД в справочнике шлюза</h2>
<p class="muted">Сертификаты НРД приходят вместе с обновлениями bj. bj сам добавляет недостающие в справочник шлюза НРД и удаляет старые после срока, разрешённого НРД; перед каждым изменением — резервная копия.{{if devOn}}{{if .NSDCerts.Profile}} Профиль <code>{{.NSDCerts.Profile}}</code>.{{end}}{{end}}</p>
<table style="margin-top:12px">
<thead><tr><th>Сертификат</th><th>Контур</th><th>Действует</th><th>Статус</th></tr></thead>
<tbody>{{range .NSDCerts.Rows}}<tr>
<td><b>{{.Title}}</b><div class="muted" style="font-size:11px">{{.SubjectCN}} · выдан: {{.IssuerCN}} · серийный <code>{{.Serial}}</code></div></td>
<td>{{.Contour}}</td>
<td><b>{{.Title}}</b>{{if devOn}}<div class="muted" style="font-size:11px">{{.SubjectCN}} · выдан: {{.IssuerCN}} · серийный <code>{{.Serial}}</code></div>{{end}}</td>
<td>{{contourRu .Contour}}</td>
<td style="white-space:nowrap">{{.ValidFrom}} — {{.ValidTo}}</td>
<td>{{if eq .Kind "ok"}}<span style="color:var(--ok);font-weight:600">✓ {{.Status}}</span>{{else if eq .Kind "add"}}<span style="color:var(--warn);font-weight:600">⬆ {{.Status}}</span>{{else}}<span class="muted">{{.Status}}</span>{{end}}{{if .RemoveNote}}<div style="font-size:11.5px;margin-top:3px;{{if .RemoveSerial}}color:var(--warn);font-weight:600{{else}}color:var(--muted){{end}}">{{.RemoveNote}}</div>{{end}}</td>
</tr>{{end}}</tbody>
@@ -312,10 +301,10 @@
<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>
{{if and .NSDCerts.LastLog (or devOn (looksErr .NSDCerts.LastLog))}}<pre style="margin-top:10px;font-size:11.5px;white-space:pre-wrap">{{.NSDCerts.LastLog}}</pre>{{end}}
{{if and devOn .NSDCerts.Store}}<details style="margin-top:8px"><summary class="muted" style="cursor:pointer;font-size:12px">Содержимое справочника профиля ({{len .NSDCerts.Store}} записей)</summary>
<table style="margin-top:8px;font-size:12px"><thead><tr><th>Владелец</th><th>Издатель</th><th>Серийный</th><th>До</th><th>Хранилище</th></tr></thead><tbody>{{range .NSDCerts.Store}}<tr><td>{{if eq .Kind "crl"}}<span class="muted">CRL</span>{{else}}{{.SubjectCN}}{{if .SelfSigned}} <span class="muted">(УЦ)</span>{{end}}{{end}}</td><td>{{.IssuerCN}}</td><td><code>{{.Serial}}</code></td><td>{{.NotAfter}}{{if .CertNotAfter}}<div class="muted" style="font-size:11px" title="Срок закрытого ключа (PrivateKeyUsagePeriod): подписывать можно до {{.NotAfter}}. Срок самого сертификата — {{.CertNotAfter}}.">ключ; сертификат до {{.CertNotAfter}}</div>{{end}}</td><td>{{.Store}}</td></tr>{{end}}</tbody></table></details>{{end}}
<p class="muted" style="font-size:12px;margin-top:8px">Свои сертификаты можно положить в <code>/var/lib/bj/.bj/nsd-certs/*.cer</code> — они добавляются по тем же правилам и никогда не удаляются автоматически.</p>
{{if devOn}}<p class="muted" style="font-size:12px;margin-top:8px">Свои сертификаты можно положить в <code>/var/lib/bj/.bj/nsd-certs/*.cer</code> — они добавляются по тем же правилам и никогда не удаляются автоматически.</p>{{end}}
</div>
{{/* Сертификаты УЦ (старый способ: теперь сертификаты НРД приходят комплектом с bj) — только для разработки */}}
@@ -347,7 +336,6 @@
{{/* ============ НРД ============ */}}
<section class="settings-section" id="sec-nsd">
<h1>НРД</h1>
{{template "verline" (.Versions.Get "ish")}}
{{template "nsdStatus" .}}
<div class="card">
<details class="adv-box" {{if not (and (index orgEff "INN") (index orgEff "Code") .Settings.NSD.DeponentCode .Settings.NSD.AccountID .Settings.NSD.SectionID .Settings.NSD.IGWBaseURL)}}open{{end}}>
@@ -362,7 +350,7 @@
</div>
<div class="card" id="nsd-participant">
<h2>Регистрация участника M2M</h2>
<p class="muted">Анкета участника <code>M2MTransferParticipantForm</code> — НРД требует её перед работой: без регистрации сервис МОСТ отбивает запросы кодом <b>M2M14</b>. bj заполняет анкету по реквизитам фирмы (настройки НРД и справочник участников), даёт проверить и показывает XML, затем отправляет в боевой контур через ИШ (пакет <code>#M2MPF</code>) или письмом на <code>M2MOST@nsd.ru</code>.</p>
<p class="muted">НРД требует анкету участника перед началом работы: без регистрации НРД не принимает запросы на переводы. bj заполняет анкету по реквизитам организации, даёт проверить и отправляет в НРД через шлюз (или её можно отправить письмом на <code>M2MOST@nsd.ru</code>).</p>
<details id="pf-box">
<summary style="cursor:pointer;color:var(--accent)">Открыть анкету участника</summary>
<iframe id="pf-frame" title="Анкета участника M2M" style="width:100%;height:1200px;border:1px solid var(--border);border-radius:10px;margin-top:10px;background:#fff" loading="lazy"></iframe>
@@ -371,7 +359,7 @@
<script>(function(){var d=document.getElementById('pf-box'),f=document.getElementById('pf-frame');
if(d&&f) d.addEventListener('toggle',function(){ if(d.open&&!f.getAttribute('src')) f.setAttribute('src','/admin/setup/participant-form'); });})();</script>
</div>
<div class="card">
{{if devOn}}<div class="card">
<details class="adv-box">
<summary><span class="sum-h">Расширенные настройки</span><span class="muted">шлюз вручную: контур, адрес веб-сервиса, ключевой контейнер, версия ИШ — обычно bj задаёт их сам</span></summary>
<p class="muted" style="margin-top:10px">{{if not .Settings.NSD.IGWBaseURL}}{{if devOn}}<code>mock-режим</code> — без реального ИШ.{{else}}ИШ не настроен — обмен с НРД не выполняется.{{end}}{{else}}Профиль <code>{{.Settings.NSD.Profile}}</code>, ИШ <code>{{.Settings.NSD.IGWBaseURL}}</code>.{{end}}</p>
@@ -417,7 +405,7 @@
})();
</script>
</details>
</div>
</div>{{end}}
</details>
</div>
</section>
@@ -843,13 +831,13 @@
<h2><span class="dot {{$bad := 0}}{{range .Units}}{{if not .OK}}{{$bad = 1}}{{end}}{{end}}{{if eq $bad 1}}err{{else}}ok{{end}}"></span>Службы</h2>
<p class="muted">Ядро и модули работают отдельными службами: если служба модуля не запущена, его раздел в bj отвечает ошибкой связи. Здесь видно состояние и можно перезапустить службу, не заходя на сервер.</p>
<div class="tw" style="margin-top:8px"><table>
<thead><tr><th>Служба</th><th>Что делает</th><th>Состояние</th><th>С</th><th></th></tr></thead>
<thead><tr>{{if devOn}}<th>Служба</th>{{end}}<th>Что делает</th><th>Состояние</th><th>С</th><th></th></tr></thead>
<tbody>{{range .Units}}<tr>
<td style="white-space:nowrap"><code>{{.Unit}}</code></td>
{{if devOn}}<td style="white-space:nowrap"><code>{{.Unit}}</code></td>{{end}}
<td>{{.Title}}</td>
<td>{{if .OK}}<span class="badge ok">работает</span>{{else}}<span class="badge err">{{.State}}{{with .Sub}} / {{.}}{{end}}{{with .Result}} ({{.}}){{end}}</span>{{end}}</td>
<td>{{if .OK}}<span class="badge ok">работает</span>{{else}}<span class="badge err">не работает{{if devOn}}: {{.State}}{{with .Sub}} / {{.}}{{end}}{{with .Result}} ({{.}}){{end}}{{end}}</span>{{end}}</td>
<td class="muted">{{if .Since}}{{.Since}}{{else}}—{{end}}</td>
<td style="white-space:nowrap"><form method="post" action="/admin/setup/units/restart" style="margin:0" onsubmit="return confirm('{{if .Core}}Перезапустить ядро? Страница настроек на несколько секунд станет недоступна.{{else}}Перезапустить службу {{.Unit}}?{{end}}');">
<td style="white-space:nowrap"><form method="post" action="/admin/setup/units/restart" style="margin:0" onsubmit="return confirm('{{if .Core}}Перезапустить bj? Страница настроек на несколько секунд станет недоступна.{{else}}Перезапустить «{{.Title}}»?{{end}}');">
<input type="hidden" name="section" value="sec-units"><input type="hidden" name="unit" value="{{.Unit}}">
<button type="submit" class="btn btn-secondary">Перезапустить</button></form></td>
</tr>{{end}}</tbody></table></div>
@@ -900,9 +888,9 @@
<div class="card">
<h2>Справочник участников M2M</h2>
<p class="muted">Запрос актуального справочника участников сервиса (пакет <code>#M2MHR</code> в НРД). В ответ НРД присылает <code>#M2MTH</code> — карту <strong>ИНН → MC-код</strong> депозитариев/брокеров и перечень мест расчётов. Нужно для сопоставления ИНН контрагента с кодом участника (блокеры вида M2M14/M2M15) и понимания, кто вообще зарегистрирован.</p>
<p class="muted">Справочник участников M2M из НРД: по нему bj узнаёт код участника по ИНН контрагента. bj запрашивает его сам; кнопка — запросить сейчас.</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>
<p class="muted" style="font-size:12px;margin-top:8px">Ответ НРД приходит через 30–60 секунд.</p>
</div>
{{if devOn}} <div class="card">
@@ -1064,7 +1052,7 @@
{{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" style="vertical-align:top">Модули</td><td>{{range .Features}}<div style="display:flex;gap:8px;align-items:center;margin:2px 0"><span class="dot {{if .On}}ok{{else}}err{{end}}"></span>{{.Title}}{{if devOn}} <code style="font-size:11px;color:var(--muted)">{{.Key}}</code>{{end}}{{if not .On}}<span class="muted" style="font-size:11px">— не входит в лицензию</span>{{end}}</div>{{end}}</td></tr>
<tr><td class="muted">Статус</td><td>{{.License.Message}}</td></tr>
</tbody>
</table>
@@ -329,7 +329,7 @@
{{else if eq $sub "done"}}
<table style="margin:6px 0 10px">
<tr><td>Подключение к базе DEPO</td><td>{{if .Settings.Fancy.Server}}<span style="color:var(--ok)">✓ {{.Settings.Fancy.Server}}, база {{.DepoTables.DB}}, логин bj «{{.Settings.Fancy.User}}»</span>{{else}}<span style="color:var(--err)">не настроено</span>{{end}}</td></tr>
<tr><td>Таблицы обмена FTV_M2M</td><td>{{if .Settings.Fancy.DepoTablesCheckOK}}<span style="color:var(--ok)">✓ на месте</span>{{else}}<span style="color:var(--err)">✗ {{.Settings.Fancy.DepoTablesCheck}}</span>{{end}}</td></tr>
<tr><td>Таблицы обмена в базе DEPO</td><td>{{if .Settings.Fancy.DepoTablesCheckOK}}<span style="color:var(--ok)">✓ на месте</span>{{else}}<span style="color:var(--err)">✗ {{.Settings.Fancy.DepoTablesCheck}}</span>{{end}}</td></tr>
<tr><td>Запуск обработки</td><td>{{if .Settings.Fancy.DepoJobCheckOK}}<span style="color:var(--ok)">✓ {{.Settings.Fancy.DepoJobCheck}}</span>{{else}}<span style="color:var(--err)">✗ {{.Settings.Fancy.DepoJobCheck}}</span>{{end}}</td></tr>
<tr><td>Обработка событий (процедуры)</td><td>{{if .Settings.Fancy.DepoProcCheckOK}}<span style="color:var(--ok)">✓ {{.Settings.Fancy.DepoProcCheck}}</span>{{else if .Settings.Fancy.DepoProcCheck}}<span style="color:var(--warn)">⚠ {{.Settings.Fancy.DepoProcCheck}}</span>{{else}}<span class="muted">не проверялась — «Перепроверить всё»</span>{{end}}</td></tr>
</table>
@@ -409,11 +409,11 @@
{{end}}
{{if or devOn (not .Done.Postgres)}}<details style="margin-top:14px" {{if not .Settings.Postgres.DSN}}open{{end}}>
<summary style="cursor:pointer;color:var(--accent)">{{if .Settings.Postgres.DSN}}Подключить другую базу строкой подключения (без переноса данных){{else}}Вариант Б — у меня уже есть PostgreSQL, введу DSN сам{{end}}</summary>
<summary style="cursor:pointer;color:var(--accent)">{{if .Settings.Postgres.DSN}}Подключить другую базу строкой подключения (без переноса данных){{else}}Вариант Б — у меня уже есть PostgreSQL, введу строку подключения сам{{end}}</summary>
<form method="post" action="/admin/setup/postgres" style="margin-top:12px">
<label>DSN (строка подключения) <span class="tooltip" title="Формат: postgres://пользователь:пароль@хост:порт/база?sslmode=disable. Например: postgres://bj:secret@db.example.com:5432/bj?sslmode=require">?</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;margin-top:6px">
<p class="muted" style="margin-top:8px">При сохранении выполняется проверка подключения (Ping). Если БД недоступна — будет ошибка.</p>
<label for="wiz-dsn">Строка подключения к базе <span class="tooltip" title="Формат: postgres://пользователь:пароль@адрес:порт/база?sslmode=disable">?</span></label>
<input type="text" id="wiz-dsn" name="dsn" value="{{.Settings.Postgres.DSN}}" placeholder="postgres://пользователь:пароль@адрес:5432/bj?sslmode=disable" style="width:100%;padding:8px;margin-top:6px">
<p class="muted" style="margin-top:8px">При сохранении bj проверит подключение; если база недоступна — покажет ошибку.</p>
<button type="submit" class="btn" style="margin-top:8px">Сохранить и проверить</button>
</form>
</details>{{end}}
@@ -458,9 +458,9 @@
<h2><span class="dot {{if .Done.Crypto}}ok{{else}}err{{end}}"></span>СКЗИ «Валидата Клиент L»</h2>
<p>СКЗИ «Валидата Клиент L» проверяет подписи НРД на квитанциях и сообщениях и нужно для работы с ключами и сертификатами (раздел «Ключ»).</p>
{{if .ValidataVer}}
<dl class="wiz-kv"><dt>Версия</dt><dd>{{.ValidataVer}}</dd><dt>Каталог</dt><dd><code>/opt/Validata/VDCSP</code></dd></dl>
<dl class="wiz-kv"><dt>Версия</dt><dd>{{.ValidataVer}}</dd>{{if devOn}}<dt>Каталог</dt><dd><code>/opt/Validata/VDCSP</code></dd>{{end}}</dl>
<div class="wiz-ok"><i class="wiz-ic"></i><span>Валидата установлена вместе с bj — ничего настраивать не нужно.</span></div>
<p class="muted" style="font-size:13px">bj работает с Валидатой через свою службу криптографии <code>bj-crypto</code>{{if eq .CryptoSvc "active"}} — она запущена{{else}} — сейчас она не запущена: служба ставится обновлением с сервера лицензий (раздел «Лицензия», кнопка «Обновить сейчас»), а работает с ключом, установленным в разделе «Ключ»{{end}}.</p>
{{if devOn}}<p class="muted" style="font-size:13px">bj работает с Валидатой через свою службу криптографии <code>bj-crypto</code>{{if eq .CryptoSvc "active"}} — она запущена{{else}} — сейчас она не запущена: служба ставится обновлением с сервера лицензий (раздел «Лицензия», кнопка «Обновить сейчас»), а работает с ключом, установленным в разделе «Ключ»{{end}}.</p>{{else if ne .CryptoSvc "active"}}<p style="color:var(--warn);font-size:13px">⚠ Компонент подписи bj не запущен — установите обновление bj (шаг «Обновление»).</p>{{end}}
{{else}}
<p style="color:var(--err)">Валидата на этом сервере не найдена.</p>
<div class="help-block">
@@ -140,7 +140,7 @@
</div>
{{end}}
{{define "verline"}}{{if .Title}}<p class="muted" style="font-size:12.5px;margin:-6px 0 12px">{{.Title}}: {{if .Version}}<strong>{{.Version}}</strong>{{else}}не определена{{end}}{{with .Detail}} · {{.}}{{end}}{{with .Err}} · <span style="color:var(--err)">{{.}}</span>{{end}}</p>{{end}}{{end}}
{{define "verline"}}{{if and .Title (or devOn (clientVer .Key))}}<p class="muted" style="font-size:12.5px;margin:-6px 0 12px">{{verTitle .}}: {{if .Version}}<strong>{{.Version}}</strong>{{else}}не определена{{end}}{{if devOn}}{{with .Detail}} · {{.}}{{end}}{{with .Err}} · <span style="color:var(--err)">{{.}}</span>{{end}}{{end}}</p>{{end}}{{end}}
{{/* Сводка «Обмен с DEPO» (Настройка): что подключено и проверено — одним взглядом; формы — под «Перенастроить». */}}
{{define "fancyStatus"}}{{$f := .Settings.Fancy}}{{$ok := and $f.Server $f.DepoTablesCheckOK $f.DepoJobCheckOK}}
@@ -204,13 +204,13 @@
<details class="adv-box" {{if .UpdatesAvailable}}open{{end}}>
<summary><span class="sum-h">Компоненты и версии</span><span class="muted">что установлено на этом сервере и что есть в выпуске; можно обновить один компонент</span></summary>
<div style="overflow-x:auto"><table style="margin-top:8px">
<thead><tr><th>Компонент</th><th>Установлена</th><th>Доступна</th><th>Служба</th><th></th></tr></thead>
<thead><tr><th>Компонент</th><th>Установлена</th><th>Доступна</th>{{if devOn}}<th>Служба</th>{{end}}<th></th></tr></thead>
<tbody>
{{range .Components}}<tr>
<td><strong>{{.Title}}</strong><div class="muted" style="font-size:11px"><code>{{.Name}}</code>{{if eq .Name "crypto-service.jar"}}{{with $.Versions.Get "validata"}}{{if .Version}} · работает с «Валидата Клиент L» {{.Version}}{{end}}{{end}}{{end}}</div></td>
<td><strong>{{.Title}}</strong>{{if devOn}}<div class="muted" style="font-size:11px"><code>{{.Name}}</code>{{if eq .Name "crypto-service.jar"}}{{with $.Versions.Get "validata"}}{{if .Version}} · работает с «Валидата Клиент L» {{.Version}}{{end}}{{end}}{{end}}</div>{{end}}</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 .New}} <span class="hero-status warn">к установке</span>{{else if .Update}} <span class="hero-status warn">новее</span>{{end}}{{if .Blocked}}<div class="muted" style="font-size:11px;color:var(--warn)">{{.Blocked}}</div>{{end}}{{else}}<span class="muted">нет данных</span>{{end}}</td>
<td><code style="font-size:11px">{{.Unit}}</code></td>
{{if devOn}}<td><code style="font-size:11px">{{.Unit}}</code></td>{{end}}
<td>{{if .Update}}<form method="post" action="/admin/setup/update/apply" style="margin:0" onsubmit="return confirm('{{if .New}}Установить модуль {{.Available}}? Будет включена его служба.{{else}}Обновить компонент до {{.Available}}? Его служба перезапустится.{{end}}');"><input type="hidden" name="section" value="sec-update"><input type="hidden" name="component" value="{{.Name}}"><button type="submit" class="btn btn-ok">{{if .New}}Установить{{else}}Обновить{{end}}</button></form>{{end}}</td>
</tr>{{end}}
</tbody>
@@ -232,15 +232,15 @@
</div>
<h3 class="adv-h">Источник обновлений</h3>
<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 (по умолчанию)">
{{if devOn}}<label>URL артефактории</label>
<input type="text" name="base_url" value="{{.Settings.Update.BaseURL}}" placeholder="https://lic.zetit.ru/updates (по умолчанию)">{{else}}<input type="hidden" name="base_url" value="{{.Settings.Update.BaseURL}}">{{end}}
<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>
<option value="stable" {{if eq .Settings.Update.Channel "stable"}}selected{{end}}>стабильный</option>
<option value="beta" {{if eq .Settings.Update.Channel "beta"}}selected{{end}}>предварительный (новые версии раньше)</option>
</select>
<label>Публичный ключ издателя (base64 Ed25519)</label>
<input type="text" name="public_key" value="{{.Settings.Update.PublicKey}}" placeholder="зашит в релиз; переопределить здесь">
{{if devOn}}<label>Публичный ключ издателя (base64 Ed25519)</label>
<input type="text" name="public_key" value="{{.Settings.Update.PublicKey}}" placeholder="зашит в релиз; переопределить здесь">{{else}}<input type="hidden" name="public_key" value="{{.Settings.Update.PublicKey}}">{{end}}
<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>
@@ -50,7 +50,7 @@
<div class="card" style="margin:10px 0">
<h3 style="margin:0 0 4px">Шлюз на этом сервере</h3>
<div id="ig-head" class="muted">Проверяю…</div>
<p class="muted" style="font-size:12px;margin:4px 0 0">Версию ИШ проверяет ZETIT; новые приходят с обновлением bj.</p>
<p class="muted" style="font-size:12px;margin:4px 0 0">Новые версии шлюза приходят с обновлением bj и ставятся сами.</p>
<div id="ig-reinst" style="display:none;margin-top:10px"><button type="button" class="btn" id="ig-bnd">Установить ИШ</button> <span class="muted" id="ig-bnd-v" style="font-size:12.5px"></span></div>
<div id="ig-ov" style="display:none;position:fixed;inset:0;z-index:9999;background:rgba(20,24,35,.55);align-items:center;justify-content:center">
<div class="card" style="max-width:600px;width:92%;margin:0;box-shadow:0 10px 40px rgba(0,0,0,.25)">
@@ -105,7 +105,7 @@
<label class="f">Логин<input name="user" value="igate-share" autocomplete="off" spellcheck="false"></label>
<label class="f">Пароль (от 8 символов)<span style="display:flex;gap:6px"><input name="password" type="text" autocomplete="new-password" spellcheck="false" placeholder="пусто — прежний" style="flex:1;min-width:0"><button type="button" class="btn sm" id="ig-sh-gen" title="Создать надёжный пароль">Сгенерировать</button></span></label>
</div>
<small id="ig-sh-pwnote" class="muted" style="display:block;font-size:12.5px;margin-top:6px">Пароль вводят в Windows при первом открытии папок. Запишите его: bj передаёт пароль в Samba и показать позже не сможет. Пустое поле при повторном сохранении оставляет прежний пароль.</small>
<small id="ig-sh-pwnote" class="muted" style="display:block;font-size:12.5px;margin-top:6px">Пароль вводят в Windows при первом открытии папок. Запишите его: bj сохраняет пароль для сетевых папок и показать позже не сможет. Пустое поле при повторном сохранении оставляет прежний пароль.</small>
<div id="ig-sh-domain" hidden>
<div id="ig-sh-access" class="muted" style="font-size:13px;margin-top:10px"></div>
<div id="ig-sh-join" hidden style="margin-top:10px">
@@ -139,6 +139,7 @@
<div id="ig-cout" style="margin-top:8px;font-size:13px"></div>
</div>
</div>
<script>var IGDEV={{if devOn}}true{{else}}false{{end}};</script>
<script>
(function(){
var head=document.getElementById('ig-head'), tb=document.getElementById('ig-ct'), cout=document.getElementById('ig-cout'), out=document.getElementById('ig-out'), lg=document.getElementById('ig-log');
@@ -241,7 +242,7 @@
} else {
p.set('mode',mode()); if(f.enabled.checked) p.set('enabled','1');
if(!d){ p.set('user',f.user.value.trim()); p.set('password',f.password.value); }
o.textContent='Сохраняю… (первый раз — установка Samba, до пары минут)';
o.textContent='Сохраняю… (первый раз — до пары минут)';
}
var pwSet=(!d)?f.password.value:'';
post(url,p).then(function(r){
@@ -261,7 +262,7 @@
var running=d.installed&&d.engine==='Running';
var rb=document.getElementById('ig-rb'); if(rb){ var can=d.rollback&&d.rollback!==d.version; rb.style.display=can?'block':'none'; if(can) document.getElementById('ig-rb-v').textContent=d.rollback; }
if(!d.installed) head.innerHTML=bad('ИШ на этом сервере не установлен');
else head.innerHTML=(running?ok('ИШ '+d.version+' установлен и работает'):bad('ИШ '+d.version+' установлен, но не работает: движок '+(d.engine||'не отвечает')+', служба '+d.service));
else head.innerHTML=(running?ok('ИШ '+d.version+' установлен и работает'):bad('Шлюз НРД установлен, но не работает'+(IGDEV?': движок '+(d.engine||'не отвечает')+', служба '+d.service:'')));
if(d.busy) head.innerHTML='<span style="color:var(--accent)">⟳ Шлюз перезапускается — '+esc(d.busy)+'; состояние — последнее известное</span>';
if(!running && d.keymedia==='missing'){ head.innerHTML='<span style="color:var(--warn)">⚠ ИШ '+esc(d.version)+' ждёт флешку с ключом подписи — её нет в сервере. Вставьте флешку: bj перезапустит шлюз сам. Переустанавливать ИШ не нужно.</span>'; }
// не стоит или не работает — одна кнопка: поставить заново проверенную версию с сервера (систему переставлять не нужно)
@@ -269,11 +270,11 @@
if(d.installing){ // bj сам ставит/обновляет ИШ: остановка шлюза — штатная, переустанавливать не нужно
head.innerHTML='<span style="color:var(--accent)">⟳ Идёт установка ИШ'+(d.upgrade_to?' '+esc(d.upgrade_to):'')+' — '+esc(d.installing)+'. Шлюз недоступен 1–3 минуты; настройки и каналы сохраняются.</span>'; ri.style.display='none';
} else if(d.upgrade_to){
head.innerHTML+='<div class="muted" style="font-size:13px;margin-top:4px">⟳ Получена версия ИШ '+esc(d.upgrade_to)+' (проверена ZETIT) — bj обновит шлюз сам в ближайшие минуты; настройки и каналы сохранятся.</div>';
head.innerHTML+='<div class="muted" style="font-size:13px;margin-top:4px">⟳ Получена версия ИШ '+esc(d.upgrade_to)+' — bj обновит шлюз сам в ближайшие минуты; настройки и каналы сохранятся.</div>';
}
if(d.installing||d.upgrade_to) ovStart(); // обновление идёт или вот-вот начнётся — страница перекрыта ходом
if(!running){
if(d.bundled){ bb.style.display=''; bb.textContent=d.installed?'Установить ИШ заново':'Установить ИШ'; bv.textContent='версия '+d.bundled+' — проверена ZETIT, дистрибутив на сервере'; }
if(d.bundled){ bb.style.display=''; bb.textContent=d.installed?'Установить ИШ заново':'Установить ИШ'; bv.textContent='версия '+d.bundled; }
else { bb.style.display='none'; bv.textContent='Дистрибутива ИШ на сервере нет — он приходит с обновлением bj («Настройка → Обновления»).'; }
}
var dep=d.deponent||'', cr=document.getElementById('ig-crypto').value;
@@ -287,8 +288,8 @@
if(d.installed) a.push('<button type="button" class="btn sm" data-act="contour" data-c="'+x[0]+'">Создать канал</button>');
pill='Канала нет'; cls='no';
} else {
it(true,'канал <b>'+esc(ch.code)+'</b> <span class="muted">'+esc(ch.crypto+' · '+ch.endpoint)+'</span>');
it(!!ch.profile, ch.profile?'ключ: профиль '+esc(ch.profile):'ключ этого контура не установлен');
it(true,'канал <b>'+esc(ch.code)+'</b>'+(IGDEV?' <span class="muted">'+esc(ch.crypto+' · '+ch.endpoint)+'</span>':''));
it(!!ch.profile, ch.profile?(IGDEV?'ключ: профиль '+esc(ch.profile):'ключ установлен'):'ключ этого контура не установлен');
it(!!ch.enabled, ch.enabled?'канал включён':'канал выключен — включится после установки ключа');
var ready=ch.enabled&&ch.profile;
if(act){ pill='● Активный'; cls='cur'; }
@@ -27,8 +27,8 @@
{{if or (not $ok) (lt (index $kc "Days") 30)}}<a class="btn" href="/admin/setup/keywizard" style="flex:none">{{if or $ok $exp}}Установить новый ключ →{{else}}Установить ключ →{{end}}</a>{{end}}</div>
<dl class="kv">
{{if $kc}}<dt>Ключ организации</dt><dd>«{{index $kc "Owner"}}» · выдан {{index $kc "Issuer"}} · контур {{index $kc "Contour"}} · {{if $exp}}действовал до <b>{{index $kc "Until"}}</b> <span style="color:var(--err)">· ИСТЁК</span>{{else}}действует до <b>{{index $kc "Until"}}</b> <span class="muted">(осталось {{index $kc "Days"}} дн.)</span>{{end}} · <a href="/admin/setup/keywizard">заменить ключ</a></dd>{{end}}
{{with .Versions.Get "validata"}}<dt>СКЗИ «Валидата Клиент L»</dt><dd>{{if .Version}}{{if cryptoReady}}<span class="kv-ok">✓</span> {{end}}{{.Version}}{{else}}<span class="kv-no">✗</span> не определена{{end}}{{with .Detail}} <span class="muted">· {{.}}</span>{{end}}</dd>{{end}}
{{with .Versions.Get "crypto-service.jar"}}<dt>Проверка подписи (bj-crypto)</dt><dd>{{if .Version}}{{.Version}}{{else}}не определена{{end}}{{with .Err}} <span style="color:var(--err)">· {{.}}</span>{{end}}</dd>{{end}}
{{with .Versions.Get "validata"}}<dt>СКЗИ «Валидата Клиент L»</dt><dd>{{if .Version}}{{if cryptoReady}}<span class="kv-ok">✓</span> {{end}}{{.Version}}{{else}}<span class="kv-no">✗</span> не определена{{end}}{{if devOn}}{{with .Detail}} <span class="muted">· {{.}}</span>{{end}}{{end}}</dd>{{end}}
{{if devOn}}{{with .Versions.Get "crypto-service.jar"}}<dt>Проверка подписи (bj-crypto)</dt><dd>{{if .Version}}{{.Version}}{{else}}не определена{{end}}{{with .Err}} <span style="color:var(--err)">· {{.}}</span>{{end}}</dd>{{end}}{{end}}
<dt>Сертификаты НРД в справочнике шлюза</dt><dd>{{if .NSDCerts.Pending}}<span class="kv-warn">⚠</span> есть изменения к применению ({{.NSDCerts.Pending}}){{else if .NSDCerts.LastRun.IsZero}}ещё не сверялись{{else}}<span class="kv-ok">✓</span> сверены {{msk .NSDCerts.LastRun}}{{end}} <span class="muted">· bj сверяет сам при запуске и раз в 6 часов</span></dd>
</dl>
</div>
@@ -43,18 +43,18 @@
<dt>Организация</dt><dd>{{if $org}}<b>{{with index $o "Name"}}{{.}}{{else}}—{{end}}</b> · ИНН <code>{{index $o "INN"}}</code> · код <code>{{index $o "Code"}}</code>{{else}}<span class="kv-no">✗</span> не задана{{end}}</dd>
<dt>Реквизиты в НРД</dt><dd>{{if $req}}код <code>{{$n.DeponentCode}}</code> · счёт <code>{{$n.AccountID}}</code> · раздел <code>{{$n.SectionID}}</code>{{else}}<span class="kv-no">✗</span> не заданы{{end}}</dd>
<dt>Кто отвечает на переводы</dt><dd>{{if $a.Enabled}}<b>bj сам — автоматически</b>{{else}}<b>Оператор</b> <span class="muted">— запасной режим</span>{{end}}{{if not $a.ChangedAt.IsZero}} <span class="muted">· с {{msk $a.ChangedAt}}</span>{{end}}</dd>
<dt>Шлюз НРД</dt><dd id="nsd-st-gw">{{if $n.IGWBaseURL}}контур <code>{{$n.Profile}}</code> · <span class="muted">проверяю шлюз…</span>{{else}}<span class="kv-no">✗</span> не настроен{{end}}</dd>
<dt>Шлюз НРД</dt><dd id="nsd-st-gw">{{if $n.IGWBaseURL}}контур {{contourRu $n.Profile}} · <span class="muted">проверяю шлюз…</span>{{else}}<span class="kv-no">✗</span> не настроен{{end}}</dd>
<dt>Папки шлюза по сети</dt><dd id="nsd-st-share"><span class="muted">проверяю…</span></dd>
</dl>
</div>
<script>(function(){
function esc(s){return String(s==null?'':s).replace(/[&<>"]/g,function(c){return{'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]})}
var gw=document.getElementById('nsd-st-gw'), sh=document.getElementById('nsd-st-share');
var gw=document.getElementById('nsd-st-gw'), sh=document.getElementById('nsd-st-share'), dev={{if devOn}}true{{else}}false{{end}};
window.addEventListener('bj-igate-status',function(e){ var d=e.detail||{}; if(!gw) return;
if(!d.ok){ gw.innerHTML='<span class="kv-no">✗</span> состояние не получено'; return; }
if(!d.installed){ gw.innerHTML='<span class="kv-no">✗</span> шлюз на этом сервере не установлен'; return; }
var run=d.engine==='Running', c=d.active_contour==='prod'?'боевой':'тестовый TEST3', code=(d.active_contour==='prod'?'PROD':'TEST3')+(d.deponent||''), ch=(d.channels||[]).filter(function(x){return x.code===code})[0];
gw.innerHTML=(run?'<span class="kv-ok">✓</span> ИШ '+esc(d.version)+' работает':'<span class="kv-no">✗</span> ИШ '+esc(d.version)+' не работает'+(d.keymedia==='missing'?' — ждёт флешку с ключом':''))+' · активный контур <b>'+c+'</b>'+(ch?(' · канал '+(ch.enabled?'включён':'выключен')+(ch.profile?' · ключ '+esc(ch.profile):' · <span style="color:var(--err)">ключ не установлен</span>')):'');
gw.innerHTML=(run?'<span class="kv-ok">✓</span> ИШ '+esc(d.version)+' работает':'<span class="kv-no">✗</span> ИШ '+esc(d.version)+' не работает'+(d.keymedia==='missing'?' — ждёт флешку с ключом':''))+' · активный контур <b>'+c+'</b>'+(ch?(' · канал '+(ch.enabled?'включён':'выключен')+(ch.profile?(dev?' · ключ '+esc(ch.profile):' · ключ установлен'):' · <span style="color:var(--err)">ключ не установлен</span>')):'');
});
window.addEventListener('bj-igate-share',function(e){ var d=e.detail||{}; if(!sh) return;
if(d.manual){ sh.textContent='открыты вручную (smb.conf)'; return; }