«Диагностика» объединена со «Статусом системы»

Службы (с перезапуском), здоровье фоновых компонентов, запрос справочника
участников и тестовые прогоны переехали из «Настройка → Диагностика» в
«Статус системы» — одно место про состояние bj. Раздел «Диагностика» и
пункт меню убраны, старые ссылки (#tests, #units) ведут в «Статус».
«Настройка» больше не опрашивает systemctl при каждом открытии.

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-22 11:05:24 +03:00
parent 077e542c1a
commit f50b338294
7 changed files with 92 additions and 92 deletions
+4
View File
@@ -629,6 +629,9 @@ type statusData struct {
StripFrom string // начало полосы
Fails []SendFailView // недоставленные переводы
Probe bool // только что запрошена проверка связи
Units []UnitView // службы bj и модулей
Health []HealthStatus // фоновые компоненты
LastTest any // последний сквозной тестовый прогон
}
// RegisterAdmin вешает HTML-маршруты /admin/* на mux. Возвращает admin
@@ -1019,6 +1022,7 @@ func (a *admin) renderStatus(w http.ResponseWriter, r *http.Request, opts CheckO
page: pg, Checks: s.Checks, CheckedAt: s.CheckedAt,
Strip: nsdStrip(titles, now), StripFrom: now.Add(-24 * time.Hour).Format("02.01 15:04"),
Fails: sendFailViews(), Probe: r.URL.Query().Get("probe") != "",
Units: unitViews(globalRC), Health: HealthSnapshot(), LastTest: globalRC.Snapshot().LastTest,
})
}
-1
View File
@@ -639,7 +639,6 @@ func (h *setupHandlers) setupData(w http.ResponseWriter, req *http.Request, flas
LKAgent: lkAgentStatus(s),
LKPreview: lkPreviewStatus(h.rc, req),
LKData: lkDataView(h.rc),
Units: unitViews(h.rc),
UpdateFailed: updateResultFailed(s.Update.LastResult),
LKKeyMask: lkKeyForPage(s.LK.APIKey),
Versions: systemVersions(h.rc),
+1 -1
View File
@@ -61,7 +61,7 @@ func watchedUnits(rc *RuntimeConfig) []watchedUnit {
st += ", с " + v.Since
}
out = append(out, watchedUnit{Key: v.Unit, Title: v.Title, Unit: v.Unit, OK: v.OK(), State: st,
Hint: "Перезапустить — Настройка → Диагностика → «Службы», кнопка «Перезапустить»; если не поднимается — journalctl -u " + v.Unit + "."})
Hint: "Перезапустить — Статус системы → «Службы», кнопка «Перезапустить»; если не поднимается — journalctl -u " + v.Unit + "."})
}
// служба ключей — когда она нужна: включена в автозапуск (bj включает её при установке ключа) или уже работала;
// на свежем сервере до установки ключа её может и не быть — это не сбой
@@ -12,7 +12,7 @@
{{if not .Participants}}
<div style="margin-top:12px;padding:14px;background:var(--warn-weak,#f6eed8);border-radius:10px">
<strong>Справочник ещё не загружен.</strong>
<p class="muted" style="margin:6px 0 0">Запросите его у НРД: <a href="/admin/setup">Настройка → Диагностика → «Запросить справочник у НРД»</a>. Ответ придёт во входящие ИШ (~30–60 сек) и появится здесь.</p>
<p class="muted" style="margin:6px 0 0">Запросите его у НРД: <a href="/admin/status#diag">Статус системы → «Запросить справочник у НРД»</a>. Ответ придёт во входящие шлюза НРД (~30–60 сек) и появится здесь.</p>
</div>
{{end}}
</div>
@@ -80,7 +80,7 @@
<li><b>Можно ли изменить поручение после отправки?</b> Нет. Если данные неверны, дождитесь отказа или исполнения и введите новое поручение.</li>
<li><b>Отправка не удалась.</b> Причина показана в карточке, поручение остаётся черновиком. Устраните причину (например, связь с НРД или торговой системой) и отправьте снова.</li>
<li><b>Нет кнопки «Найти в DEPO».</b> Учётная система не подключена к bj. Заполните поля вручную и сверьте данные в учётной системе сами.</li>
<li><b>Справочник участников пуст.</b> Администратор может запросить справочник в «Настройка → Диагностика», а пока ИНН можно ввести вручную.</li>
<li><b>Справочник участников пуст.</b> Администратор может запросить справочник в «Статус системы» (карточка «Справочник участников M2M»), а пока ИНН можно ввести вручную.</li>
</ul>
</div>
{{end}}
@@ -1,4 +1,5 @@
{{define "content"}}
<script>if(/^#(sec-)?(tests|units)$/.test(location.hash)) location.replace('/admin/status#diag');</script>
{{if .Flash}}<div style="padding:12px 16px;background:var(--ok-weak);background:var(--ok-weak);border-radius:8px;margin-bottom:16px">{{.Flash}}</div>{{end}}
<div class="settings">
@@ -16,7 +17,6 @@
{{if mod "web-cabinet"}}<button data-sec="cabinet"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="9" r="3.2"/><path d="M6 19c.6-3 3-4.6 6-4.6s5.4 1.6 6 4.6"/><rect x="3" y="4" width="18" height="16" rx="2"/></svg></span>Кабинет клиента<span class="ind {{if .Settings.LK.APIKey}}ok{{else}}warn{{end}}"></span></button>{{end}}
{{if mod "esia-finance"}}<button data-sec="lk"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><circle cx="9" cy="11" r="2"/><path d="M6 16c.4-1.4 1.6-2.2 3-2.2s2.6.8 3 2.2"/><path d="M15 10h4M15 13h3"/></svg></span>ЕСИА Финанс<span class="ind {{if .Settings.LK.PublicURL}}ok{{else}}warn{{end}}"></span></button>{{end}}
{{if mod "onec-back"}}<button data-sec="onec"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="10" cy="6" rx="6" ry="2.5"/><path d="M4 6v10c0 1.4 2.7 2.5 6 2.5"/><path d="M16 6v4"/><path d="M4 11c0 1.4 2.7 2.5 6 2.5"/><path d="M14 16h7M18 13l3 3-3 3"/></svg></span>1С: бэк-офис<span class="ind {{if .Settings.OneC.CheckOK}}ok{{else}}warn{{end}}"></span></button>{{end}}
<button data-sec="tests"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M9 3h6M10 3v6l-5 8.5A2 2 0 0 0 6.7 21h10.6a2 2 0 0 0 1.7-3.5L14 9V3"/><path d="M8 15h8"/></svg></span>Диагностика</button>
<button data-sec="alerts"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/></svg></span>Оповещения<span class="ind {{if or .Settings.Alerts.SMTP.Enabled .Settings.Alerts.Zabbix.Enabled}}ok{{end}}"></span></button>
<button data-sec="update"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20V8M6 12l6-6 6 6"/><path d="M5 4h14"/></svg></span>Обновления{{if .UpdatesAvailable}}<span class="ind warn"></span>{{end}}</button>
<button data-sec="backup"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="6" rx="7" ry="2.5"/><path d="M5 6v6c0 1.4 3.1 2.5 7 2.5s7-1.1 7-2.5V6"/><path d="M12 17v4M9 19l3 2 3-2"/></svg></span>Резервное копирование{{$bs := backupStatus}}{{if and .Settings.Backup.Enabled $bs}}{{if not $bs.OK}}<span class="ind err"></span>{{end}}{{else if not .Settings.Backup.Enabled}}<span class="ind warn"></span>{{end}}</button>
@@ -829,97 +829,11 @@
</section>
{{end}}
{{/* ============ ДИАГНОСТИКА (id sec-tests сохранён для ссылок) ============ */}}
<section class="settings-section" id="sec-tests">
<h1>Диагностика</h1>
<div class="card" id="sec-units">
<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>{{if devOn}}<th>Служба</th>{{end}}<th>Что делает</th><th>Состояние</th><th>С</th><th></th></tr></thead>
<tbody>{{range .Units}}<tr>
{{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">не работает{{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}}Перезапустить 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>
</div>
<div class="card">
<h2>Здоровье сервиса</h2>
<p class="muted">Фоновые компоненты под самоконтролем: при сбое перезапускаются автоматически, при зависании — перезапуск всего сервиса и запись в «Новости». Ниже — живой статус.</p>
{{if .Health}}
<div class="tw" style="margin-top:12px">
<table>
<thead><tr><th>Компонент</th><th>Состояние</th><th>Последняя отметка</th><th>Последняя ошибка</th></tr></thead>
<tbody>
{{range .Health}}
<tr>
<td>{{healthLabel .Name}}</td>
<td>{{if .Healthy}}<span class="sys-status" style="--dot:#16a34a">работает</span>{{else}}<span class="sys-status" style="--dot:#dc2626">не отвечает</span>{{end}}</td>
<td class="mono">{{dur .Age}} назад</td>
<td class="muted">{{if .LastErr}}{{.LastErr}}{{else}}—{{end}}</td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{else}}
<p class="muted" style="margin-top:10px">Компоненты ещё не зарегистрированы (сервис только запущен).</p>
{{end}}
</div>
<div class="card">
<h2>Справочник участников M2M</h2>
<p class="muted">Справочник участников 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 секунд.</p>
</div>
{{if devOn}} <div class="card">
<h2>Тестовый пакет роботу НРД</h2>
<p class="muted">Робот <code>MC0012500000</code> эмулирует вторую сторону перевода. Выберите сценарий — bj-server отправит эталонный запрос через шлюз НРД, ответ придёт во входящие. Требуется настроенный шлюз НРД и ключ подписи.</p>
<form method="post" action="/admin/setup/test-nsd" style="margin-top:12px;display:flex;gap:8px;flex-wrap:wrap;align-items:center">
<select name="scenario">
<option value="2001">2001 — Принять все бумаги</option>
<option value="2002">2002 — Принять частично</option>
<option value="1111">1111 — Ответ с отказом</option>
<option value="3333">3333 — Робот принимающая сторона</option>
</select>
<button type="submit" class="btn">Отправить роботу</button>
</form>
<p class="muted" style="margin-top:6px;font-size:12px">Ответ робота — асинхронно (~30-60 сек) во входящие шлюза НРД.</p>
</div>
<div class="card">
<h2>Сквозной тестовый прогон (mock/реальный)</h2>
<p class="muted">Заявка с предзаполненными данными через всю цепочку до финального статуса.</p>
<form method="post" action="/admin/setup/test-run" style="margin-top:12px"><button type="submit" class="btn">Запустить тестовую заявку</button></form>
{{if .Settings.LastTest}}
<div style="margin-top:16px;padding:14px;background:var(--card-2);border:1px solid var(--border);border-radius:10px">
<strong>Последний прогон</strong>
<table style="margin-top:8px">
<tbody>
<tr><td style="width:160px" class="muted">Статус</td><td>{{if .Settings.LastTest.OK}}<span style="color:var(--ok)">✓ успешно</span>{{else}}<span style="color:var(--err)">✗ не прошёл</span>{{end}}</td></tr>
<tr><td class="muted">FSM-статус</td><td><code>{{.Settings.LastTest.FinalStatus}}</code></td></tr>
<tr><td class="muted">ClaimID</td><td><code>{{.Settings.LastTest.ClaimID}}</code> {{if .Settings.LastTest.ClaimID}}<a href="/admin/claims/{{.Settings.LastTest.ClaimID}}">→ карточка</a>{{end}}</td></tr>
<tr><td class="muted">Сообщение</td><td>{{.Settings.LastTest.Message}}</td></tr>
</tbody>
</table>
</div>
{{end}}
</div>{{end}}
</section>
{{/* ============ ОПОВЕЩЕНИЯ ============ */}}
<section class="settings-section" id="sec-alerts">
<h1>Оповещения</h1>
<p class="muted">Кому и о чём bj пишет на почту: администратору системы — о сбоях и обновлениях, отделам — о переводах и поручениях. Все события видны и в ленте «Новости»; передать их в мониторинг Zabbix — Настройка → Диагностика.</p>
<p class="muted">Кому и о чём bj пишет на почту: администратору системы — о сбоях и обновлениях, отделам — о переводах и поручениях. Все события видны и в ленте «Новости»; передать их в мониторинг Zabbix — карточка «Zabbix» ниже.</p>
{{template "smtpCard" .}}
@@ -72,6 +72,89 @@
<p class="muted" style="margin-top: 16px">Проверка выполнена в {{.CheckedAt.Format "15:04:05 02.01.2006"}}.</p>
</div>
{{/* Диагностика: службы, здоровье фоновых компонентов, справочник, тестовые прогоны (раньше — Настройка → Диагностика) */}}
<div class="card" id="sec-units"><a id="diag"></a>
<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>{{if devOn}}<th>Служба</th>{{end}}<th>Что делает</th><th>Состояние</th><th>С</th><th></th></tr></thead>
<tbody>{{range .Units}}<tr>
{{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">не работает{{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}}Перезапустить 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>
</div>
<div class="card">
<h2>Здоровье сервиса</h2>
<p class="muted">Фоновые компоненты под самоконтролем: при сбое перезапускаются автоматически, при зависании — перезапуск всего сервиса и запись в «Новости». Ниже — живой статус.</p>
{{if .Health}}
<div class="tw" style="margin-top:12px">
<table>
<thead><tr><th>Компонент</th><th>Состояние</th><th>Последняя отметка</th><th>Последняя ошибка</th></tr></thead>
<tbody>
{{range .Health}}
<tr>
<td>{{healthLabel .Name}}</td>
<td>{{if .Healthy}}<span class="sys-status" style="--dot:#16a34a">работает</span>{{else}}<span class="sys-status" style="--dot:#dc2626">не отвечает</span>{{end}}</td>
<td class="mono">{{dur .Age}} назад</td>
<td class="muted">{{if .LastErr}}{{.LastErr}}{{else}}—{{end}}</td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{else}}
<p class="muted" style="margin-top:10px">Компоненты ещё не зарегистрированы (сервис только запущен).</p>
{{end}}
</div>
<div class="card">
<h2>Справочник участников M2M</h2>
<p class="muted">Справочник участников 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 секунд.</p>
</div>
{{if devOn}} <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 .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 .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>{{.LastTest.FinalStatus}}</code></td></tr>
<tr><td class="muted">ClaimID</td><td><code>{{.LastTest.ClaimID}}</code> {{if .LastTest.ClaimID}}<a href="/admin/claims/{{.LastTest.ClaimID}}">→ карточка</a>{{end}}</td></tr>
<tr><td class="muted">Сообщение</td><td>{{.LastTest.Message}}</td></tr>
</tbody>
</table>
</div>
{{end}}
</div>{{end}}
{{if devOn}}{{/* дорожная карта разработки — только в режиме разработки */}}
<div class="card">
<h2>Что подключается на следующих этапах</h2>