From a53b9f9e9f81c7764f63c2845901b4420738251a Mon Sep 17 00:00:00 2001 From: zuevav Date: Wed, 7 Oct 2026 08:29:25 +0300 Subject: [PATCH] =?UTF-8?q?=D0=90=D0=B2=D1=82=D0=BE=D0=BC=D0=B0=D1=82?= =?UTF-8?q?=D0=B8=D1=87=D0=B5=D1=81=D0=BA=D0=B8=D0=B9=20=D1=80=D0=B5=D0=B6?= =?UTF-8?q?=D0=B8=D0=BC=20M2M=20=D0=BF=D0=BE=20=D1=83=D0=BC=D0=BE=D0=BB?= =?UTF-8?q?=D1=87=D0=B0=D0=BD=D0=B8=D1=8E;=20=D0=BA=D0=BE=D0=BD=D1=82?= =?UTF-8?q?=D1=83=D1=80=D1=8B=20=D0=9D=D0=A0=D0=94=20=D0=BA=D0=B0=D1=80?= =?UTF-8?q?=D1=82=D0=BE=D1=87=D0=BA=D0=B0=D0=BC=D0=B8;=20=D0=B0=D0=B4?= =?UTF-8?q?=D1=80=D0=B5=D1=81=20=D0=BF=D0=B0=D0=BF=D0=BE=D0=BA=20=D1=88?= =?UTF-8?q?=D0=BB=D1=8E=D0=B7=D0=B0=20=D0=B4=D0=BB=D1=8F=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=BE=D0=B4=D0=BD=D0=B8=D0=BA=D0=B0;=20Zabbix=20?= =?UTF-8?q?=E2=80=94=20=D0=B2=20=C2=AB=D0=94=D0=B8=D0=B0=D0=B3=D0=BD=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D0=B8=D0=BA=D0=B5=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - M2M: «bj сам — автоматически» — режим по умолчанию (где ни разу не выбирали; момент включения записывается, берутся переводы не старше суток до него); «Оператор» — запасной; явный выбор не меняется. - Контуры НРД: TEST3 и боевой — карточки с состоянием (Активный / Готов / Нужен ключ / Канала нет); «Сделать активным» видна всегда, без ключа неактивна с пояснением; «Установить ключ», «Обновить канал». - Папки шлюза по сети: после открытия — адреса для проводника (\\IP\igate_exchange и по имени сервера, боевой — igate_exchange_prod) с кнопкой «Скопировать»; bj-igate-admin share status отдаёт ip, host, shares. - Zabbix перенесён из «Оповещений» в «Диагностику». Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi --- deploy/linux/sbin/bj-igate-admin | 6 +- internal/lkgateway/m2mauto.go | 11 +++ internal/lkgateway/m2mautodefault_test.go | 33 +++++++++ internal/lkgateway/runtimeconfig.go | 11 ++- .../lkgateway/web/templates/admin_setup.html | 39 +++++----- internal/lkgateway/web/templates/layout.html | 4 +- .../lkgateway/web/templates/part_igate.html | 71 ++++++++++++++----- .../lkgateway/web/templates/part_nsd.html | 6 +- 8 files changed, 138 insertions(+), 43 deletions(-) create mode 100644 internal/lkgateway/m2mautodefault_test.go diff --git a/deploy/linux/sbin/bj-igate-admin b/deploy/linux/sbin/bj-igate-admin index 30d36d9..107b38e 100755 --- a/deploy/linux/sbin/bj-igate-admin +++ b/deploy/linux/sbin/bj-igate-admin @@ -331,7 +331,11 @@ SQL mine=false; [ -f "$CONF" ] && grep -q "^$B" "$CONF" && mine=true u=$( [ "$mine" = true ] && sed -n "/^$B/,/^$E/p" "$CONF" | sed -n 's/^ *valid users *= *//p' | head -1 ) [ "$manual" = true ] && [ -z "$u" ] && u=$(sed -n '/^\[igate_exchange\]/,/^\[/p' "$CONF" | sed -n 's/^ *valid users *= *//p' | head -1) - echo "{\"ok\":true,\"on\":$( [ "$mine" = true ] || [ "$manual" = true ] && echo true || echo false),\"manual\":$manual,\"user\":$(q "$u"),\"smbd\":$(q "$(systemctl is-active smbd 2>/dev/null)")}"; exit 0 + # куда подключаться из проводника: адрес и имя сервера, открытые папки (test3 / prod) + ip=$(hostname -I 2>/dev/null | awk '{print $1}'); hn=$(hostname -s 2>/dev/null) + sh=""; [ "$mine" = true ] && for n in $(sed -n "/^$B/,/^$E/p" "$CONF" | sed -n 's/^\[\(.*\)\]$/\1/p'); do sh="$sh${sh:+,}$(q "$n")"; done + [ "$manual" = true ] && [ -z "$sh" ] && sh=$(q igate_exchange) + echo "{\"ok\":true,\"on\":$( [ "$mine" = true ] || [ "$manual" = true ] && echo true || echo false),\"manual\":$manual,\"user\":$(q "$u"),\"smbd\":$(q "$(systemctl is-active smbd 2>/dev/null)"),\"ip\":$(q "$ip"),\"host\":$(q "$hn"),\"shares\":[$sh]}"; exit 0 fi [ "$manual" = true ] && err "папка igate_exchange уже открыта вручную в $CONF — bj её не меняет; чтобы управлять из bj, уберите её из smb.conf" if [ "$on" != true ]; then diff --git a/internal/lkgateway/m2mauto.go b/internal/lkgateway/m2mauto.go index 761d874..e2c6a3e 100644 --- a/internal/lkgateway/m2mauto.go +++ b/internal/lkgateway/m2mauto.go @@ -32,6 +32,17 @@ type M2MAutoSettings struct { ChangedBy string `json:"changed_by,omitempty"` } +// migrateM2MAutoDefault — автоматический режим включён по умолчанию (решение 07.10.2026: «Оператор» — запасной +// режим). Установка, где режим ни разу не выбирали, получает «автоматически» с моментом включения сейчас: берутся +// только переводы, пришедшие не раньше чем за сутки до этого. Явный выбор (есть ChangedAt) не трогается. +func migrateM2MAutoDefault(s *Settings, now time.Time) bool { + if s.M2MAuto.Enabled || !s.M2MAuto.ChangedAt.IsZero() { + return false + } + s.M2MAuto = M2MAutoSettings{Enabled: true, ChangedAt: now, ChangedBy: "по умолчанию"} + return true +} + func (r *RuntimeConfig) UpdateM2MAuto(s M2MAutoSettings) error { r.mu.Lock() r.data.M2MAuto = s diff --git a/internal/lkgateway/m2mautodefault_test.go b/internal/lkgateway/m2mautodefault_test.go new file mode 100644 index 0000000..31bc3c3 --- /dev/null +++ b/internal/lkgateway/m2mautodefault_test.go @@ -0,0 +1,33 @@ +package lkgateway + +import ( + "os" + "path/filepath" + "testing" +) + +// Автоматический режим M2M — по умолчанию: где режим не выбирали, он включается и запоминается; +// явный выбор «Оператор» остаётся. +func TestM2MAutoDefaultOn(t *testing.T) { + dir := t.TempDir() + fresh := filepath.Join(dir, "fresh.json") + _ = os.WriteFile(fresh, []byte(`{"nsd":{"profile":"test3-gost"}}`), 0o600) + rc, err := NewRuntimeConfig(fresh) + if err != nil { + t.Fatal(err) + } + a := rc.Snapshot().M2MAuto + if !a.Enabled || a.ChangedAt.IsZero() || a.ChangedBy != "по умолчанию" { + t.Fatalf("не включён по умолчанию: %+v", a) + } + rc2, _ := NewRuntimeConfig(fresh) // записано: при следующем запуске момент включения тот же + if !rc2.Snapshot().M2MAuto.ChangedAt.Equal(a.ChangedAt) { + t.Fatal("момент включения не сохранился") + } + manual := filepath.Join(dir, "manual.json") + _ = os.WriteFile(manual, []byte(`{"m2m_auto":{"enabled":false,"changed_at":"2026-10-01T10:00:00Z","changed_by":"admin"}}`), 0o600) + rc3, _ := NewRuntimeConfig(manual) + if rc3.Snapshot().M2MAuto.Enabled { + t.Fatal("явный выбор «Оператор» перезаписан") + } +} diff --git a/internal/lkgateway/runtimeconfig.go b/internal/lkgateway/runtimeconfig.go index 75512dd..733f1ad 100644 --- a/internal/lkgateway/runtimeconfig.go +++ b/internal/lkgateway/runtimeconfig.go @@ -1,6 +1,7 @@ package lkgateway import ( + "log" "encoding/json" "errors" "os" @@ -364,10 +365,16 @@ func NewRuntimeConfig(path string) (*RuntimeConfig, error) { path = filepath.Join(home, ".bj", "setup.json") } rc := &RuntimeConfig{path: path} - if err := rc.load(); err != nil && !errors.Is(err, os.ErrNotExist) { - return nil, err + loadErr := rc.load() + if loadErr != nil && !errors.Is(loadErr, os.ErrNotExist) { + return nil, loadErr } migrateNotifyRoutes(&rc.data) // и на новой установке: версия маршрутов сохранится с первой записью настроек + if migrateM2MAutoDefault(&rc.data, time.Now()) && loadErr == nil { + if err := rc.save(); err != nil { // момент включения — отсчёт «за сутки до» для автоматики; должен пережить перезапуск + log.Printf("setup: автоматический режим M2M по умолчанию не записан: %v", err) + } + } // Гарантируем разумные дефолты. if rc.data.Crypto.Provider == "" { rc.data.Crypto.Provider = "stub" diff --git a/internal/lkgateway/web/templates/admin_setup.html b/internal/lkgateway/web/templates/admin_setup.html index 753b56c..8f5ba50 100644 --- a/internal/lkgateway/web/templates/admin_setup.html +++ b/internal/lkgateway/web/templates/admin_setup.html @@ -819,6 +819,25 @@ {{end}} +
+

Мониторинг Zabbix

+

bj шлёт значение на элемент типа Zabbix trapper (протокол zabbix_sender). В Zabbix заведите host и item-trapper с этим ключом, затем триггер по значению — уведомления настроит сам Zabbix.

+
+ +
+
+
+
+
+
+
+ + +
+
+ {{if .Settings.Alerts.Zabbix.LastMsg}}

Последняя проверка: {{.Settings.Alerts.Zabbix.LastMsg}}

{{end}} +
+

Здоровье сервиса

Фоновые компоненты под самоконтролем: при сбое перезапускаются автоматически, при зависании — перезапуск всего сервиса и запись в «Новости». Ниже — живой статус.

@@ -888,28 +907,10 @@ {{/* ============ ОПОВЕЩЕНИЯ ============ */}}

Оповещения

-

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

+

Кому и о чём bj пишет на почту: администратору системы — о сбоях и обновлениях, отделам — о переводах и поручениях. Все события видны и в ленте «Новости»; передать их в мониторинг Zabbix — Настройка → Диагностика.

{{template "smtpCard" .}} -
-

Zabbix (хук мониторинга)

-

bj шлёт значение на элемент типа Zabbix trapper (протокол zabbix_sender). В Zabbix заведите host и item-trapper с этим ключом, затем триггер по значению — уведомления настроит сам Zabbix.

-
- -
-
-
-
-
-
-
- - -
-
- {{if .Settings.Alerts.Zabbix.LastMsg}}

Последняя проверка: {{.Settings.Alerts.Zabbix.LastMsg}}

{{end}} -
{{template "notifyGroupsCard" .}}
diff --git a/internal/lkgateway/web/templates/layout.html b/internal/lkgateway/web/templates/layout.html index e1c3e09..df4003e 100644 --- a/internal/lkgateway/web/templates/layout.html +++ b/internal/lkgateway/web/templates/layout.html @@ -382,8 +382,8 @@ label { font-size:13px; font-weight:500; } .stc{display:flex;gap:14px;align-items:center;padding:14px 18px;border-radius:12px;border:1px solid var(--border);background:var(--card-2)} .stc-ic{flex:none;position:relative;width:28px;height:28px;border-radius:50%;background:var(--border-strong)} .stc-b{flex:1;min-width:0} -.stc-b b{display:block;font-size:15px;font-weight:650} -.stc-b small{display:block;color:var(--muted);font-size:13px;line-height:1.4;margin-top:2px;overflow-wrap:anywhere} +.stc-b > b{display:block;font-size:15px;font-weight:650} +.stc-b > small{display:block;color:var(--muted);font-size:13px;line-height:1.4;margin-top:2px;overflow-wrap:anywhere} .stc form, .stc .btn{flex:none} .stc-ok{background:color-mix(in srgb,var(--ok) 9%,var(--card));border-color:color-mix(in srgb,var(--ok) 40%,var(--border))} .stc-ok .stc-ic{background:var(--ok)} diff --git a/internal/lkgateway/web/templates/part_igate.html b/internal/lkgateway/web/templates/part_igate.html index a1914e1..adb7712 100644 --- a/internal/lkgateway/web/templates/part_igate.html +++ b/internal/lkgateway/web/templates/part_igate.html @@ -4,8 +4,28 @@ #ig-box h3{margin:0 0 6px;font-size:15px} #ig-box label.f{display:block;margin:0;font-size:13px} #ig-box label.f input,#ig-box label.f select{display:block;margin-top:4px;padding:7px 9px;background:var(--bg);border:1px solid var(--border);color:var(--text);border-radius:4px} -#ig-ct{width:100%;margin-top:8px} -#ig-ct td{vertical-align:top;padding:6px 8px 6px 0} +.ct-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin-top:10px} +.ct{border:1px solid var(--border);border-radius:12px;padding:14px 16px;background:var(--card);display:flex;flex-direction:column;gap:8px} +.ct-on{border-color:color-mix(in srgb,var(--accent) 55%,var(--border));background:color-mix(in srgb,var(--accent-weak) 45%,var(--card))} +.ct-h{display:flex;justify-content:space-between;align-items:flex-start;gap:10px} +.ct-h b{display:block;font-size:15px} +.ct-h small{display:block;color:var(--muted);font-size:12.5px;margin-top:1px} +.ct-pill{flex:none;font-size:12px;font-weight:600;padding:3px 10px;border-radius:999px;background:var(--card-2);border:1px solid var(--border);color:var(--text-2);white-space:nowrap} +.ct-act .ct-pill, .ct-on .ct-pill{background:var(--accent);border-color:var(--accent);color:#fff} +.ct-ok .ct-pill{background:var(--ok-weak);border-color:color-mix(in srgb,var(--ok) 35%,transparent);color:var(--ok)} +.ct-no .ct-pill{background:var(--warn-weak);border-color:color-mix(in srgb,var(--warn) 35%,transparent);color:var(--warn)} +.ct-li{list-style:none;margin:0;padding:0;display:grid;gap:3px;font-size:13px} +.ct-li li{position:relative;padding-left:20px;overflow-wrap:anywhere} +.ct-li li::before{position:absolute;left:0;top:0;font-weight:700} +.ct-li li.ok::before{content:"✓";color:var(--ok)} .ct-li li.no::before{content:"✗";color:var(--err)} .ct-li li.na::before{content:"—";color:var(--muted)} +.ct-li .muted{font-size:12px} +.ct .ct-why{margin:0;font-size:12.5px;color:var(--muted)} +.ct-act{display:flex;gap:6px;flex-wrap:wrap;margin-top:auto;padding-top:4px} +.ct-act .btn[disabled]{opacity:.5;cursor:not-allowed} +@media (max-width:760px){.ct-grid{grid-template-columns:minmax(0,1fr)}} +.ig-addr{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:6px} +.ig-addr code{font-size:14px;padding:4px 10px;border-radius:8px;background:var(--card);border:1px solid var(--border);user-select:all} +.ig-addr .muted{font-size:12.5px} #ig-box .btn.sm{padding:4px 10px;font-size:12.5px} #ig-box .btn.ghost{background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8)} /* ход установки ИШ: этапы по порядку, у текущего — сколько идёт и сколько обычно */ @@ -48,7 +68,7 @@

Контуры НРД

Сначала — тестовый контур TEST3; когда переводы на нём проходят, переходите на боевой. У каждого контура свой ключ.

-
+