Шапка «Шлюз НРД»: зелёная при работающем обмене, всплывающая панель; плашка анкеты в НРД
Общий цвет цепочки — по участкам с известным состоянием: несделанная M2M-проверка больше не делает серым работающий обмен. Вместо title — всплывающая панель по участкам с цветными точками, без повтора «последнего ответа НРД». Раздел НРД: наверху плашка, пока анкета участника не принята НРД (не отправлена / ждёт ответа / отклонена с текстом НРД), со ссылкой на анкету; пропадает после приёма. Только в боевом контуре. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
This commit is contained in:
+15
-11
@@ -153,17 +153,21 @@ var templateFuncs = template.FuncMap{
|
|||||||
"looksErr": looksErr,
|
"looksErr": looksErr,
|
||||||
"intList": func(v ...int) []int { return v },
|
"intList": func(v ...int) []int { return v },
|
||||||
"nsdSegTip": nsdSegTip,
|
"nsdSegTip": nsdSegTip,
|
||||||
"devOn": authTemplateFuncs["devOn"],
|
"nsdSegPop": nsdSegPop,
|
||||||
"devLicensed": authTemplateFuncs["devLicensed"],
|
// participantOK / participantBanner — анкета участника M2M принята НРД; плашка наверху раздела НРД, пока нет.
|
||||||
"mod": func(f string) bool { return globalRC == nil || moduleAllowed(globalRC, f) }, // модуль в лицензии
|
"participantOK": participantAccepted,
|
||||||
"modTitle": moduleTitle, // название признака лицензии по-человечески
|
"participantBanner": participantBanner,
|
||||||
"isIIS": incomingIsIIS, // входящий перевод на ИИС
|
"devOn": authTemplateFuncs["devOn"],
|
||||||
"depoSubs": func() any { return depoWizardSubs }, // экраны шага 3 мастера
|
"devLicensed": authTemplateFuncs["devLicensed"],
|
||||||
"authOpen": authOpen,
|
"mod": func(f string) bool { return globalRC == nil || moduleAllowed(globalRC, f) }, // модуль в лицензии
|
||||||
"internalDocs": func() bool { return internalDocsBuilt },
|
"modTitle": moduleTitle, // название признака лицензии по-человечески
|
||||||
"roleTitle": roleTitle,
|
"isIIS": incomingIsIIS, // входящий перевод на ИИС
|
||||||
"stageCls": func(r depoQueueRow) string { c, _ := r.StageRu(); return c },
|
"depoSubs": func() any { return depoWizardSubs }, // экраны шага 3 мастера
|
||||||
"stageText": func(r depoQueueRow) string { _, t := r.StageRu(); return t },
|
"authOpen": authOpen,
|
||||||
|
"internalDocs": func() bool { return internalDocsBuilt },
|
||||||
|
"roleTitle": roleTitle,
|
||||||
|
"stageCls": func(r depoQueueRow) string { c, _ := r.StageRu(); return c },
|
||||||
|
"stageText": func(r depoQueueRow) string { _, t := r.StageRu(); return t },
|
||||||
"deref": func(v *float64) float64 {
|
"deref": func(v *float64) float64 {
|
||||||
if v == nil {
|
if v == nil {
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -234,12 +235,17 @@ func nsdHealthNow(rc *RuntimeConfig, now time.Time) NSDHealth {
|
|||||||
nsdH.Unlock()
|
nsdH.Unlock()
|
||||||
|
|
||||||
h.Segs = []NSDSeg{gw, ns, m, sd}
|
h.Segs = []NSDSeg{gw, ns, m, sd}
|
||||||
|
// Общий цвет — по участкам с известным состоянием: «проверка ещё не выполнялась» не делает
|
||||||
|
// серым работающий обмен. Серый — только когда по известным участкам связь не подтверждена.
|
||||||
h.State = segOK
|
h.State = segOK
|
||||||
for _, s := range h.Segs {
|
for _, s := range h.Segs {
|
||||||
if segRank(s.State) > segRank(h.State) {
|
if s.State != segUnknown && segRank(s.State) > segRank(h.State) {
|
||||||
h.State = s.State
|
h.State = s.State
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if h.State == segOK && !nsdLinkOK(h) {
|
||||||
|
h.State = segUnknown
|
||||||
|
}
|
||||||
h.Ago = agoText(h.LastAnswer, now)
|
h.Ago = agoText(h.LastAnswer, now)
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
@@ -553,6 +559,23 @@ func nsdClientNotice(h NSDHealth) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nsdSegPop — всплывающая панель цепочки в шапке: участки с цветной точкой. Строки «последний ответ»
|
||||||
|
// нет — время ответа уже в участке «НРД».
|
||||||
|
func nsdSegPop(h NSDHealth) template.HTML {
|
||||||
|
esc := template.HTMLEscapeString
|
||||||
|
var b strings.Builder
|
||||||
|
b.WriteString(`<span class="nsd-pop" role="tooltip"><span class="nsd-pop-b">`)
|
||||||
|
for _, s := range h.Segs {
|
||||||
|
b.WriteString(`<span class="np-row"><i class="np-d ` + esc(s.State) + `"></i><b>` + esc(s.Title) + `</b><span>` + esc(s.Text))
|
||||||
|
if s.Detail != "" {
|
||||||
|
b.WriteString(`<small>` + esc(s.Detail) + `</small>`)
|
||||||
|
}
|
||||||
|
b.WriteString(`</span></span>`)
|
||||||
|
}
|
||||||
|
b.WriteString(`<span class="np-foot">Подробно — «Статус»</span></span></span>`)
|
||||||
|
return template.HTML(b.String())
|
||||||
|
}
|
||||||
|
|
||||||
// nsdSegTip — подсказка к цепочке в шапке: все участки построчно.
|
// nsdSegTip — подсказка к цепочке в шапке: все участки построчно.
|
||||||
func nsdSegTip(h NSDHealth) string {
|
func nsdSegTip(h NSDHealth) string {
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package lkgateway
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"html/template"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -138,3 +139,31 @@ func recordParticipantReply(status string, resp *m2m.M2MTransferResponse, rawFil
|
|||||||
r.RawFile = rawFile
|
r.RawFile = rawFile
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// participantAccepted — последняя отправленная анкета принята НРД.
|
||||||
|
func participantAccepted() bool {
|
||||||
|
r := loadParticipantReg()
|
||||||
|
return !r.RespAt.IsZero() && !strings.EqualFold(r.Status, "ERROR")
|
||||||
|
}
|
||||||
|
|
||||||
|
// participantBanner — плашка наверху раздела НРД: анкета не отправлена, ждёт ответа или отклонена.
|
||||||
|
// Пропадает, когда НРД принял анкету. Анкету шлём только в боевом контуре — в тестовом плашки нет.
|
||||||
|
func participantBanner() template.HTML {
|
||||||
|
if participantAccepted() || contourOf(currentNSDProfile()) != "prod" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
esc := template.HTMLEscapeString
|
||||||
|
r := loadParticipantReg()
|
||||||
|
cls, head, body := "stc-err", "Анкета участника M2M не отправлена",
|
||||||
|
"Без регистрации участника НРД не принимает запросы на переводы. Проверьте анкету и отправьте её."
|
||||||
|
switch {
|
||||||
|
case r.SentAt.IsZero():
|
||||||
|
case r.RespAt.IsZero():
|
||||||
|
cls, head, body = "stc-run", "Анкета участника M2M ждёт ответа НРД", "Отправлена "+r.SentAt.In(listMSK).Format("02.01.2006 15:04")+". Плашка пропадёт, когда НРД примет анкету."
|
||||||
|
case strings.EqualFold(r.Status, "ERROR"):
|
||||||
|
cls, head = "stc-err", "НРД отклонил анкету участника M2M"
|
||||||
|
body = r.RespAt.In(listMSK).Format("02.01.2006 15:04") + ": " + r.RespText + ". Исправьте и отправьте снова."
|
||||||
|
}
|
||||||
|
return template.HTML(`<div class="stc ` + cls + `" style="margin:0 0 14px"><span class="stc-ic"></span><div class="stc-b"><b>` + esc(head) +
|
||||||
|
`</b><small>` + esc(body) + ` <a href="#nsd-participant" onclick="var d=document.getElementById('pf-box');if(d)d.open=true">Открыть анкету →</a></small></div></div>`)
|
||||||
|
}
|
||||||
|
|||||||
@@ -335,6 +335,7 @@
|
|||||||
{{/* ============ НРД ============ */}}
|
{{/* ============ НРД ============ */}}
|
||||||
<section class="settings-section" id="sec-nsd">
|
<section class="settings-section" id="sec-nsd">
|
||||||
<h1>НРД</h1>
|
<h1>НРД</h1>
|
||||||
|
{{participantBanner}}
|
||||||
{{template "nsdStatus" .}}
|
{{template "nsdStatus" .}}
|
||||||
<div class="card" id="nsd-igate">
|
<div class="card" id="nsd-igate">
|
||||||
<h2><span class="dot {{if .Settings.NSD.IGWBaseURL}}ok{{else}}warn{{end}}"></span>Шлюз НРД</h2>
|
<h2><span class="dot {{if .Settings.NSD.IGWBaseURL}}ok{{else}}warn{{end}}"></span>Шлюз НРД</h2>
|
||||||
@@ -342,7 +343,8 @@
|
|||||||
{{template "igateInstall" .}}
|
{{template "igateInstall" .}}
|
||||||
</div>
|
</div>
|
||||||
<div class="card" id="nsd-participant">
|
<div class="card" id="nsd-participant">
|
||||||
<h2>Регистрация участника M2M</h2>
|
<h2><span class="dot {{if participantOK}}ok{{else}}warn{{end}}"></span>Регистрация участника M2M</h2>
|
||||||
|
{{if participantOK}}<p style="margin:0 0 6px">✓ Анкета принята НРД — организация зарегистрирована в сервисе M2M.</p>{{end}}
|
||||||
<p class="muted">НРД требует анкету участника перед началом работы: без регистрации НРД не принимает запросы на переводы. bj заполняет анкету по реквизитам организации, даёт проверить и отправляет в НРД через шлюз (или её можно отправить письмом на <code>M2MOST@nsd.ru</code>).</p>
|
<p class="muted">НРД требует анкету участника перед началом работы: без регистрации НРД не принимает запросы на переводы. bj заполняет анкету по реквизитам организации, даёт проверить и отправляет в НРД через шлюз (или её можно отправить письмом на <code>M2MOST@nsd.ru</code>).</p>
|
||||||
<details id="pf-box">
|
<details id="pf-box">
|
||||||
<summary style="cursor:pointer;color:var(--accent)">Открыть анкету участника</summary>
|
<summary style="cursor:pointer;color:var(--accent)">Открыть анкету участника</summary>
|
||||||
|
|||||||
@@ -112,6 +112,16 @@ body {
|
|||||||
.nsd-chain.ok i { background:var(--ok); } .nsd-chain.warn i { background:var(--warn); } .nsd-chain.err i { background:var(--err); }
|
.nsd-chain.ok i { background:var(--ok); } .nsd-chain.warn i { background:var(--warn); } .nsd-chain.err i { background:var(--err); }
|
||||||
.nsd-chain.err { background:var(--err-weak); border-color:var(--err); color:var(--err); }
|
.nsd-chain.err { background:var(--err-weak); border-color:var(--err); color:var(--err); }
|
||||||
.nsd-chain.warn { background:var(--warn-weak); }
|
.nsd-chain.warn { background:var(--warn-weak); }
|
||||||
|
.nsd-chain { position:relative; }
|
||||||
|
.nsd-pop { display:none; position:absolute; top:100%; right:0; padding-top:8px; z-index:60; cursor:default; }
|
||||||
|
.nsd-chain:hover .nsd-pop, .nsd-chain:focus .nsd-pop { display:block; }
|
||||||
|
.nsd-pop-b { display:block; min-width:300px; max-width:420px; background:var(--card); color:var(--text); border:1px solid var(--border); border-radius:12px; box-shadow:0 10px 30px rgba(0,0,0,.14); padding:10px 14px; font-size:13px; line-height:1.4; text-align:left; white-space:normal; font-weight:400; }
|
||||||
|
.np-row { display:grid; grid-template-columns:10px 72px 1fr; gap:8px; align-items:baseline; padding:4px 0; }
|
||||||
|
.np-row + .np-row { border-top:1px solid var(--border); }
|
||||||
|
.np-row small { display:block; color:var(--muted); font-size:11.5px; }
|
||||||
|
.np-d { width:8px; height:8px; border-radius:50%; background:var(--muted); display:inline-block; }
|
||||||
|
.np-d.ok { background:var(--ok); } .np-d.warn { background:var(--warn); } .np-d.err { background:var(--err); }
|
||||||
|
.np-foot { display:block; margin-top:6px; color:var(--muted); font-size:12px; }
|
||||||
.sys-block { display:inline-flex; flex-direction:column; align-items:center; gap:2px; line-height:1.1; }
|
.sys-block { display:inline-flex; flex-direction:column; align-items:center; gap:2px; line-height:1.1; }
|
||||||
.sys-block .sys-status { padding:3px 9px 3px 7px; font-size:11.5px; }
|
.sys-block .sys-status { padding:3px 9px 3px 7px; font-size:11.5px; }
|
||||||
.user-dd .ddbtn.user-chip { display:inline-flex; flex-direction:column; align-items:flex-start; padding:4px 10px; border-radius:8px; margin:0; max-width:220px; }
|
.user-dd .ddbtn.user-chip { display:inline-flex; flex-direction:column; align-items:flex-start; padding:4px 10px; border-radius:8px; margin:0; max-width:220px; }
|
||||||
@@ -587,7 +597,7 @@ button.dq-link, .dq-link { background:none !important; border:0 !important; padd
|
|||||||
</nav>
|
</nav>
|
||||||
<div class="topbar-right">
|
<div class="topbar-right">
|
||||||
<span class="sys-block">
|
<span class="sys-block">
|
||||||
{{if and .NSDLink.Configured (not .IsMockMode)}}<a id="nsd-chain" href="/admin/status#nsd" class="sys-status nsd-chain {{.NSDLink.State}}" title="{{nsdSegTip .NSDLink}}"><i></i><span>Шлюз НРД</span></a>{{else}}<span class="sys-status {{if .IsMockMode}}warn{{end}}" title="Состояние обмена с НРД">
|
{{if and .NSDLink.Configured (not .IsMockMode)}}<a id="nsd-chain" href="/admin/status#nsd" class="sys-status nsd-chain {{.NSDLink.State}}" aria-label="{{nsdSegTip .NSDLink}}"><i></i><span>Шлюз НРД</span>{{nsdSegPop .NSDLink}}</a>{{else}}<span class="sys-status {{if .IsMockMode}}warn{{end}}" title="Состояние обмена с НРД">
|
||||||
<span class="pulse"></span>{{if .IsMockMode}}{{if devOn}}Эмуляция{{else}}НРД не подключён{{end}}{{else}}НРД на связи{{end}}</span>{{end}}
|
<span class="pulse"></span>{{if .IsMockMode}}{{if devOn}}Эмуляция{{else}}НРД не подключён{{end}}{{else}}НРД на связи{{end}}</span>{{end}}
|
||||||
<span class="clock">{{.Now}}</span>
|
<span class="clock">{{.Now}}</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -837,14 +847,16 @@ button.dq-link, .dq-link { background:none !important; border:0 !important; padd
|
|||||||
function upd(){
|
function upd(){
|
||||||
fetch('/admin/nsd-health.json', {cache:'no-store'}).then(function(r){ return r.json(); }).then(function(h){
|
fetch('/admin/nsd-health.json', {cache:'no-store'}).then(function(r){ return r.json(); }).then(function(h){
|
||||||
if (!h.configured) return;
|
if (!h.configured) return;
|
||||||
var tip = '';
|
var esc = function(t){ var d = document.createElement('span'); d.textContent = t || ''; return d.innerHTML; };
|
||||||
(h.segs||[]).forEach(function(s){ tip += s.title + ': ' + s.text + (s.detail ? ' (' + s.detail + ')' : '') + '\n'; });
|
var pop = '';
|
||||||
tip += (h.ago ? 'Последний ответ НРД ' + h.ago : 'Ответа НРД ещё не было') + '\n';
|
(h.segs||[]).forEach(function(s){ pop += '<span class="np-row"><i class="np-d ' + esc(s.state) + '"></i><b>' + esc(s.title) + '</b><span>' + esc(s.text) + (s.detail ? '<small>' + esc(s.detail) + '</small>' : '') + '</span></span>'; });
|
||||||
el.className = 'sys-status nsd-chain ' + h.state;
|
el.className = 'sys-status nsd-chain ' + h.state;
|
||||||
el.title = tip + 'Подробно — «Статус». Обновляется каждые 30 с.';
|
var b = el.querySelector('.nsd-pop-b');
|
||||||
|
if (b) b.innerHTML = pop + '<span class="np-foot">Подробно — «Статус»</span>';
|
||||||
}).catch(function(){
|
}).catch(function(){
|
||||||
el.className = 'sys-status nsd-chain warn';
|
el.className = 'sys-status nsd-chain warn';
|
||||||
el.title = 'Нет связи с bj — состояние НРД не обновляется';
|
var b = el.querySelector('.nsd-pop-b');
|
||||||
|
if (b) b.innerHTML = '<span class="np-row"><i class="np-d warn"></i><b>bj</b><span>нет связи — состояние НРД не обновляется</span></span>';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setInterval(upd, 30000);
|
setInterval(upd, 30000);
|
||||||
|
|||||||
Reference in New Issue
Block a user