Статус: карточка ключа организации — контур и пометка «ИСТЁК»; SMB-пароль папок шлюза — генерация и показ

Ключ организации на странице «Статус» теперь строится из своего сертификата
в справочнике активного профиля ИШ (ownStoreCerts), включая истёкший: карточка
показывает владельца, издателя, контур (боевой/тестовый) и срок. Истёкший ключ
виден явной пометкой «ИСТЁК» и отдельным заголовком, а не маскируется под
«ключ не подключён». Показать владельца ДО установки ключа нельзя — сертификата
нет в файлах флешки, он появляется в справочнике профиля только после установки.

Папки шлюза по сети (простой режим): кнопка «Сгенерировать» создаёт надёжный
пароль (16 символов, crypto.getRandomValues) прямо в поле, поле теперь видимое;
после сохранения пароль показывается один раз с кнопкой «Скопировать» —
записать, так как позже bj его не покажет. Пустое поле по-прежнему оставляет
прежний пароль при повторном сохранении.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
This commit is contained in:
fontvielle
2026-10-21 11:29:48 +03:00
parent 847f858cd9
commit 365ec1196d
3 changed files with 32 additions and 14 deletions
+10 -6
View File
@@ -68,17 +68,21 @@ var templateFuncs = template.FuncMap{
if globalRC == nil {
return nil
}
own := ownValidCerts(storeCertsForUI(globalRC.Snapshot()))
// Свой сертификат из справочника активного профиля ИШ — включая истёкший,
// чтобы «Статус» показал владельца и пометку «ИСТЁК», а не просто «не подключён».
own := ownStoreCerts(globalRC.Snapshot())
if len(own) == 0 {
return nil
}
c := own[0]
for _, x := range own[1:] { // самый долгий — тот, что в работе
if x.NotAfter.After(c.NotAfter) {
c = x
c, ca := own[0], storeNotAfter(own[0])
for _, x := range own[1:] { // самый долгий срок — тот ключ, что в работе
if t := storeNotAfter(x); t.After(ca) {
c, ca = x, t
}
}
return map[string]any{"Owner": c.SubjectCN, "Issuer": c.IssuerCN, "Until": c.NotAfter.Format("02.01.2006"), "Days": certDaysLeft(c.NotAfter)}
contour := map[string]string{"prod": "боевой", "guest": "гостевой", "test3": "тестовый"}[contourOf(currentNSDProfile())]
return map[string]any{"Owner": c.SubjectCN, "Issuer": c.IssuerCN, "Until": ca.Format("02.01.2006"),
"Days": certDaysLeft(ca), "Expired": !ca.IsZero() && ca.Before(time.Now()), "Contour": contour}
},
// СКЗИ готова: Валидата установлена и ключ организации подключён (как шаги мастера «Валидата» и «Ключ»)
"cryptoReady": func() bool {
@@ -94,8 +94,9 @@
<label style="display:flex;gap:8px;align-items:center;font-weight:500;margin:12px 0 0"><input type="checkbox" name="enabled" style="width:auto"> Открыть папки шлюза по сети</label>
<div id="ig-sh-simple" class="ig-sh-grid">
<label class="f">Логин<input name="user" value="igate-share" autocomplete="off" spellcheck="false"></label>
<label class="f">Пароль (от 8 символов)<input name="password" type="password" autocomplete="new-password" spellcheck="false" placeholder="пусто — прежний"></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>
<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">
@@ -214,6 +215,13 @@
}
function load(){ return fetch('/admin/setup/igate/share',{cache:'no-store'}).then(function(r){return r.json()}).then(show).catch(function(){ stl.textContent='Состояние не получено.'; }); }
f.addEventListener('change',function(e){ if(e.target.name==='mode') sync(); });
var genbtn=document.getElementById('ig-sh-gen');
if(genbtn) genbtn.addEventListener('click',function(){
var cs='ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789', a=new Uint32Array(16), s='';
crypto.getRandomValues(a); for(var i=0;i<16;i++) s+=cs[a[i]%cs.length];
f.password.type='text'; f.password.value=s; f.password.focus(); f.password.select();
o.innerHTML='<span class="muted">Пароль создан — запишите его перед сохранением.</span>';
});
f.addEventListener('submit',function(e){ e.preventDefault();
var p=new URLSearchParams(), url='/admin/setup/igate/share', d=mode()==='domain';
if(d && !cur.joined){
@@ -225,7 +233,13 @@
if(!d){ p.set('user',f.user.value.trim()); p.set('password',f.password.value); }
o.textContent='Сохраняю… (первый раз — установка Samba, до пары минут)';
}
post(url,p).then(function(r){ o.innerHTML=r.ok?ok(r.output):bad(r.error); f.password.value=''; f.adminpw.value=''; return load(); }).catch(function(){ o.innerHTML=bad('не сохранено'); }); });
var pwSet=(!d)?f.password.value:'';
post(url,p).then(function(r){
if(r.ok){ o.innerHTML=ok(r.output)+(pwSet?' <b>Пароль:</b> <code>'+esc(pwSet)+'</code> <button type="button" class="btn sm" data-copy="'+esc(pwSet)+'">Скопировать</button> — запишите, посмотреть позже нельзя.':'');
var cb=o.querySelector('[data-copy]'); if(cb) cb.onclick=function(){ (window.bjCopy||function(t){navigator.clipboard&&navigator.clipboard.writeText(t)})(cb.dataset.copy); cb.textContent='Скопировано ✓'; };
} else o.innerHTML=bad(r.error);
f.password.value=''; f.adminpw.value=''; return load();
}).catch(function(){ o.innerHTML=bad('не сохранено'); }); });
load();
})();
var st=null;
@@ -19,14 +19,14 @@
</div>
{{end}}
{{define "cryptoStatus"}}{{$kc := orgKeyCert}}{{$ok := and cryptoReady $kc}}
{{define "cryptoStatus"}}{{$kc := orgKeyCert}}{{$exp := and $kc (index $kc "Expired")}}{{$ok := and cryptoReady $kc (not $exp)}}
<div class="card" id="sec-crypto-status">
<div class="stc {{if $ok}}{{if lt (index $kc "Days") 30}}stc-err{{else}}stc-ok{{end}}{{else}}stc-warn{{end}}"><span class="stc-ic"></span>
<div class="stc-b"><b>{{if $ok}}{{if lt (index $kc "Days") 30}}Ключ организации скоро закончится{{else}}Подпись работает{{end}}{{else}}Ключ организации не подключён{{end}}</b>
<small>{{if $ok}}СКЗИ «Валидата» установлена, ключ организации подключён — документы для НРД подписываются.{{if lt (index $kc "Days") 30}} Получите новый ключ в Удостоверяющем центре и установите его.{{end}}{{else}}Пошаговая установка: флешка с ключом или архив ключа от НРД с паролем → справочник сертификатов → проверка подписи.{{end}}</small></div>
{{if or (not $ok) (lt (index $kc "Days") 30)}}<a class="btn" href="/admin/setup/keywizard" style="flex:none">{{if $ok}}Установить новый ключ →{{else}}Установить ключ →{{end}}</a>{{end}}</div>
<div class="stc {{if $exp}}stc-err{{else if $ok}}{{if lt (index $kc "Days") 30}}stc-err{{else}}stc-ok{{end}}{{else}}stc-warn{{end}}"><span class="stc-ic"></span>
<div class="stc-b"><b>{{if $exp}}Ключ организации истёк{{else if $ok}}{{if lt (index $kc "Days") 30}}Ключ организации скоро закончится{{else}}Подпись работает{{end}}{{else}}Ключ организации не подключён{{end}}</b>
<small>{{if $exp}}Ключ «{{index $kc "Owner"}}» истёк {{index $kc "Until"}} — подпись для НРД не проходит. Получите новый ключ в Удостоверяющем центре и установите его.{{else if $ok}}СКЗИ «Валидата» установлена, ключ организации подключён — документы для НРД подписываются.{{if lt (index $kc "Days") 30}} Получите новый ключ в Удостоверяющем центре и установите его.{{end}}{{else}}Пошаговая установка: флешка с ключом или архив ключа от НРД с паролем → справочник сертификатов → проверка подписи.{{end}}</small></div>
{{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"}} · действует до <b>{{index $kc "Until"}}</b> <span class="muted">(осталось {{index $kc "Days"}} дн.)</span> · <a href="/admin/setup/keywizard">заменить ключ</a></dd>{{end}}
{{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}}
<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>