41d33fefad
- Одобрен дистрибутив ИШ 110.0.997 (deploy/release/igate-approved): клиенты получают его компонентом igate-dist, bj-server обновляет ИШ сам (igateAutoUpgrade) после применения обновления. Проверено на 28: установка 107.0.930 → 110.0.997 штатным bj-igate-admin, каналы сохранены, круг #M2MHR → #M2MTH с НРД TEST3 и приём ответа поллером bj — работают. - Бэкап перед установкой/обновлением ИШ стал полным: кроме /opt/nsd/igate и дампа базы — данные /var/lib/igate (без флешки с ключом и кэша), юнит, обёртка запуска, версия. Раньше откатиться было нечем: igate-dist.zip перезаписывается новой версией. - bj-igate-admin rollback / rollback-now: возврат программы, данных и базы ИШ из последнего бэкапа (pg_restore от postgres — с мандатными метками Astra). После отката пришедшая версия не ставится автоматически (/var/lib/bj/igate-autoupgrade-skip), следующая — снова да. Проверено на 28: 110.0.997 → 107.0.930 за 40 с, обмен с НРД работает. - «Шлюз НРД»: кнопка «Откатить ИШ к <версия>», когда есть полная резервная копия. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
366 lines
39 KiB
HTML
366 lines
39 KiB
HTML
{{/* Шлюз НРД: установка ИШ, каналы контуров TEST3 и боевого, переключение bj между контурами (мастер, шаг 8). */}}
|
||
{{define "igateInstall"}}
|
||
<style>
|
||
#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}
|
||
.ct-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin-top:10px;align-items:stretch;grid-auto-rows:1fr}
|
||
.ct{border:1px solid var(--border);border-radius:12px;padding:14px 16px;background:var(--card);display:flex;flex-direction:column;gap:8px;margin:0;height:100%;box-sizing:border-box;align-self:stretch}
|
||
.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-cur .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-share{margin-top:18px;padding-top:16px;border-top:1px solid var(--border)}
|
||
.ig-sh-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin-top:10px}
|
||
.ig-sh-grid label.f input{width:100%;box-sizing:border-box}
|
||
@media (max-width:600px){.ig-sh-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)}
|
||
/* ход установки ИШ: этапы по порядку, у текущего — сколько идёт и сколько обычно */
|
||
.ig-steps{list-style:none;margin:10px 0 0;padding:10px 14px;border:1px solid var(--border);border-radius:10px;display:grid;gap:2px}
|
||
.ig-steps li{display:flex;gap:10px;align-items:flex-start;padding:4px 0;font-size:13.5px;color:var(--muted)}
|
||
.ig-steps li.done{color:var(--text-2)} .ig-steps li.cur{color:var(--text);font-weight:500} .ig-steps li.err{color:var(--err)}
|
||
.ig-ic{flex:none;position:relative;width:16px;height:16px;border-radius:50%;margin-top:2px;box-sizing:border-box;border:1.5px solid var(--border-strong)}
|
||
.ig-steps li.done .ig-ic{background:var(--ok);border-color:var(--ok)}
|
||
.ig-steps li.done .ig-ic::after{content:"";position:absolute;left:50%;top:45%;width:3.5px;height:7px;border:solid #fff;border-width:0 1.6px 1.6px 0;transform:translate(-50%,-50%) rotate(45deg)}
|
||
.ig-steps li.cur .ig-ic{border:2px solid var(--accent-weak);border-top-color:var(--accent);animation:ig-spin .8s linear infinite}
|
||
.ig-steps li.err .ig-ic{background:var(--err);border-color:var(--err)}
|
||
.ig-t small{display:block;font-weight:400;color:var(--muted);font-size:12px}
|
||
.ig-t small.slow{color:var(--warn)}
|
||
@keyframes ig-spin{to{transform:rotate(360deg)}}
|
||
@media (prefers-reduced-motion:reduce){.ig-steps li.cur .ig-ic{animation:none;border-color:var(--accent)}}
|
||
</style>
|
||
<div id="ig-box">
|
||
<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>
|
||
<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-rb" style="display:none;margin-top:8px"><button type="button" class="btn" id="ig-rb-btn" style="background:var(--card);color:var(--text);border:1px solid var(--border-strong,#c9cdd8)">Откатить ИШ к <span id="ig-rb-v"></span></button> <span class="muted" style="font-size:12.5px">вернуть версию, которая стояла до последнего обновления (программа, данные, база ИШ)</span></div>
|
||
<ol class="ig-steps" id="ig-steps" hidden></ol>
|
||
<div id="ig-out" style="margin-top:8px;font-size:13px"></div>
|
||
<details id="ig-logbox" hidden style="margin-top:6px;font-size:12.5px"><summary style="cursor:pointer;color:var(--accent)">Журнал установки</summary>
|
||
<pre id="ig-log" style="max-height:220px;overflow:auto;font-size:11px;margin-top:6px"></pre></details>
|
||
{{if devOn}}<details style="margin-top:8px"><summary style="cursor:pointer;color:var(--accent);font-size:13px">Для разработчиков: другая версия ИШ</summary>
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:end;margin-top:8px">
|
||
<label class="f">Версия на сайте НРД<input id="ig-ver" value="107.0.930" style="width:140px"></label>
|
||
<button type="button" class="btn" id="ig-dl">Скачать с сайта НРД и установить</button>
|
||
</div>
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-top:10px">
|
||
<input type="file" id="ig-file" accept=".zip,.deb">
|
||
<button type="button" class="btn ghost" id="ig-up">Установить из файла</button>
|
||
</div>
|
||
</details>{{end}}
|
||
</div>
|
||
|
||
<div class="card" style="margin:10px 0">
|
||
<h3>Контуры НРД</h3>
|
||
<p class="muted" style="font-size:13px;margin:0 0 8px">Сначала — тестовый контур TEST3; когда переводы на нём проходят, переходите на боевой. У каждого контура свой ключ.</p>
|
||
<label class="f" style="max-width:220px">Криптография<select id="ig-crypto"><option value="gost">ГОСТ</option><option value="rsa">RSA</option></select></label>
|
||
<div id="ig-ct" class="ct-grid"></div>
|
||
<div id="ig-prod" style="display:none;border:1px solid var(--warn);border-radius:8px;padding:10px 12px;margin-top:10px">
|
||
<b>Переход на боевой контур</b>
|
||
<p class="muted" style="font-size:13px;margin:6px 0">После перехода bj отправляет в НРД настоящие переводы. На боевом контуре обычно работает настоящий учёт DEPO — укажите его базу (на TEST3 часто использовали копию). Вернуться на TEST3 можно здесь же.</p>
|
||
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:end">
|
||
<label class="f">База DEPO для боевого контура<input id="ig-depo" placeholder="как сейчас" style="width:220px"></label>
|
||
<label class="f">Для подтверждения введите БОЕВОЙ<input id="ig-confirm" autocomplete="off" style="width:160px"></label>
|
||
<button type="button" class="btn" id="ig-goprod" style="background:var(--err);border-color:var(--err)">Перейти на боевой</button>
|
||
</div>
|
||
</div>
|
||
<div class="ig-share" id="ig-share">
|
||
<h3>Папки шлюза по сети <span class="muted" style="font-weight:400;font-size:13px">— необязательно</span></h3>
|
||
<p class="muted" style="font-size:13px;margin:4px 0 0">Папки обмена шлюза (INBOX, OUTBOX, SENT, ERRORS) можно открыть по сети Windows — если другие программы организации кладут или забирают файлы через этот шлюз. bj для работы это не нужно; файлы пишутся от имени шлюза.</p>
|
||
<div id="ig-share-st" class="muted" style="font-size:13px;margin-top:8px">Проверяю…</div>
|
||
<form id="ig-share-f" class="dq" style="margin-top:12px;max-width:680px">
|
||
<div class="dq-tiles">
|
||
<label class="dq-tile"><input type="radio" name="mode" value="simple" checked><span><b>Простой</b><small>Один логин и пароль на всех, кому нужны папки.</small></span></label>
|
||
<label class="dq-tile" id="ig-sh-dtile"><input type="radio" name="mode" value="domain"><span><b>Через домен</b><small id="ig-sh-dnote">Те же группы и сотрудники домена, что и в bj, — своим входом в Windows, без отдельного пароля. Убрали из группы — доступ пропал.</small></span></label>
|
||
</div>
|
||
<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 символов)<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">
|
||
<p style="margin:0;font-size:13.5px">Сервер bj ещё не подключён к домену <b id="ig-sh-realm"></b>. Подключить нужно один раз — учётной записью с правом добавлять компьютеры в домен; bj её не сохраняет. В домене появится компьютер <b id="ig-sh-host"></b>.</p>
|
||
<div class="ig-sh-grid">
|
||
<label class="f">Администратор домена<input name="admin" autocomplete="off" spellcheck="false" placeholder="например admin или admin@corp.local"></label>
|
||
<label class="f">Пароль<input name="adminpw" type="password" autocomplete="new-password"></label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:12px"><button type="submit" class="btn sm" id="ig-sh-save">Сохранить</button><span id="ig-share-out" style="font-size:13px"></span></div>
|
||
</form>
|
||
</div>
|
||
<div class="ig-job" id="ig-job" hidden aria-live="polite">
|
||
<div class="ig-job-h"><span class="ig-spin"></span><b id="ig-job-t">Записываю канал в шлюз НРД</b><span class="muted" id="ig-job-time"></span></div>
|
||
<ol class="dq-steps" id="ig-job-steps"></ol>
|
||
<p class="muted" id="ig-job-note" style="margin:8px 0 0;font-size:13px">Обычно до двух минут: шлюз перезапускается. Не уходите со страницы — после перезагрузки ход покажется снова.</p>
|
||
</div>
|
||
<style>
|
||
.ig-job{margin-top:12px;border:2px solid var(--accent);border-radius:12px;padding:14px 16px;background:var(--accent-weak)}
|
||
.ig-job.is-ok{border-color:var(--ok);background:color-mix(in srgb,var(--ok) 8%,var(--card))}
|
||
.ig-job.is-err{border-color:var(--err);background:color-mix(in srgb,var(--err) 7%,var(--card))}
|
||
.ig-job-h{display:flex;gap:10px;align-items:center;margin-bottom:8px;flex-wrap:wrap}
|
||
.ig-job-h .muted{margin-left:auto;font-variant-numeric:tabular-nums}
|
||
.ig-spin{width:18px;height:18px;border-radius:50%;border:2px solid var(--accent-weak);border-top-color:var(--accent);animation:dq-spin .8s linear infinite;flex:none}
|
||
.ig-job.is-ok .ig-spin,.ig-job.is-err .ig-spin{display:none}
|
||
.ig-job.shake{animation:ig-shake .4s}
|
||
@keyframes ig-shake{25%{transform:translateX(-6px)}75%{transform:translateX(6px)}}
|
||
@keyframes dq-spin{to{transform:rotate(360deg)}}
|
||
</style>
|
||
<div id="ig-cout" style="margin-top:8px;font-size:13px"></div>
|
||
</div>
|
||
</div>
|
||
<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');
|
||
function esc(s){return String(s==null?'':s).replace(/[&<>"]/g,function(c){return{'&':'&','<':'<','>':'>','"':'"'}[c]})}
|
||
function ok(t){return '<span style="color:var(--ok)">✓ '+esc(t)+'</span>'} function bad(t){return '<span style="color:var(--err)">✗ '+esc(t)+'</span>'}
|
||
function post(u,p){return fetch(u,{method:'POST',body:p,cache:'no-store'}).then(function(r){return r.json()})}
|
||
// Запись канала идёт в фоне bj: показываем её ход (и после перезагрузки страницы), пока идёт — не даём уйти.
|
||
var job=(function(){
|
||
var box=document.getElementById('ig-job'), list=document.getElementById('ig-job-steps'), tt=document.getElementById('ig-job-t'),
|
||
tm=document.getElementById('ig-job-time'), note=document.getElementById('ig-job-note'), busy=false, t0=0, timer=0, fails=0;
|
||
var ST=[['write','Запись канала в базу шлюза'],['engine','Перезапуск шлюза и запуск движка'],['channel','Проверка: шлюз загрузил канал']];
|
||
function draw(v){
|
||
var idx=-1; ST.forEach(function(x,i){ if(x[0]===v.stage) idx=i; }); if(v.done) idx=ST.length;
|
||
list.innerHTML=ST.map(function(x,i){ var cls=i<idx?'is-ok':(i===idx?(v.done?'':'is-run'):'is-wait'); if(v.done&&!v.ok&&i===Math.min(idx,ST.length)-1) cls='is-err';
|
||
return '<li class="dq-st '+cls+'"><span class="dq-st-ic"></span><div class="dq-st-t"><span>'+x[1]+'</span></div></li>'; }).join('');
|
||
}
|
||
function tick(){ if(!t0) return; var n=Math.max(0,Math.round(Date.now()/1000-t0)); tm.textContent=Math.floor(n/60)+':'+('0'+n%60).slice(-2); }
|
||
function finish(v){
|
||
busy=false; clearInterval(timer); box.classList.remove('shake');
|
||
box.classList.add(v.ok?'is-ok':'is-err'); tt.textContent=v.ok?'Канал '+String(v.contour||'').toUpperCase()+' записан, шлюз работает':'Канал не записан';
|
||
note.textContent=v.msg||''; draw(v); load();
|
||
if(v.ok && /перезапускается/.test(v.msg||'')) setTimeout(function(){ location.reload(); },20000);
|
||
}
|
||
function poll(){
|
||
fetch('/admin/setup/igate/contour-status',{cache:'no-store'}).then(function(r){return r.json()}).then(function(v){
|
||
fails=0; if(v.started) t0=v.started;
|
||
if(v.running){ draw(v); setTimeout(poll,2000); return; }
|
||
if(v.done) finish(v); else { busy=false; box.hidden=true; }
|
||
}).catch(function(){ if(++fails<30) setTimeout(poll,3000); }); // bj мог перезапуститься — ждём
|
||
}
|
||
function watch(c){
|
||
busy=true; box.hidden=false; box.classList.remove('is-ok','is-err'); tt.textContent='Записываю канал '+String(c||'').toUpperCase()+' в шлюз НРД';
|
||
note.textContent='Обычно до двух минут: шлюз перезапускается. Не уходите со страницы — после перезагрузки ход покажется снова.';
|
||
t0=t0||Date.now()/1000; clearInterval(timer); timer=setInterval(tick,1000); tick();
|
||
box.scrollIntoView({block:'center',behavior:'smooth'}); poll();
|
||
}
|
||
// пока идёт запись — переходы и кнопки вне карточки не срабатывают
|
||
document.addEventListener('click',function(e){
|
||
if(!busy) return; var a=e.target.closest('a[href],button,input[type=submit]'); if(!a||box.contains(a)) return;
|
||
e.preventDefault(); e.stopPropagation(); box.classList.remove('shake'); void box.offsetWidth; box.classList.add('shake');
|
||
note.textContent='Подождите: идёт запись канала, шлюз перезапускается. Переход станет доступен, когда она закончится.';
|
||
box.scrollIntoView({block:'center',behavior:'smooth'});
|
||
},true);
|
||
window.addEventListener('beforeunload',function(e){ if(busy){ e.preventDefault(); e.returnValue=''; } });
|
||
// при открытии страницы: запись уже идёт или только что закончилась — показать
|
||
fetch('/admin/setup/igate/contour-status',{cache:'no-store'}).then(function(r){return r.json()}).then(function(v){
|
||
if(v.running){ t0=v.started||0; watch(v.contour); } else if(v.done){ box.hidden=false; t0=0; finish(v); }
|
||
}).catch(function(){});
|
||
return {watch:watch};
|
||
})();
|
||
// доступ к папкам шлюза по сети (Samba): простой режим или через домен
|
||
(function(){
|
||
var f=document.getElementById('ig-share-f'), stl=document.getElementById('ig-share-st'), o=document.getElementById('ig-share-out');
|
||
if(!f) return;
|
||
var cur={}, sim=document.getElementById('ig-sh-simple'), dom=document.getElementById('ig-sh-domain'), join=document.getElementById('ig-sh-join'), acc=document.getElementById('ig-sh-access');
|
||
function mode(){ return f.mode.value; }
|
||
function sync(){ var d=mode()==='domain'; sim.hidden=d; dom.hidden=!d; join.hidden=!(d && !cur.joined);
|
||
document.getElementById('ig-sh-save').textContent=(d && !cur.joined)?'Подключить сервер к домену и открыть папки':'Сохранить'; }
|
||
function addrs(d){ var hosts=[d.ip||location.hostname]; if(d.host && d.host!==hosts[0]) hosts.push(d.host);
|
||
var names=(d.shares&&d.shares.length)?d.shares:['igate_exchange'], rows=[];
|
||
names.forEach(function(n){ hosts.forEach(function(h){ var p='\\\\'+h+'\\'+n;
|
||
rows.push('<div class="ig-addr"><code>'+esc(p)+'</code><button type="button" class="btn sm ghost" data-copy="'+esc(p)+'">Скопировать</button><span class="muted">'+(/_test$/.test(n)?'тестовый контур TEST3':'боевой контур')+(h===d.host?' · по имени сервера':'')+'</span></div>'); }); });
|
||
return rows.join(''); }
|
||
function show(d){
|
||
if(!d.ok){ stl.innerHTML=bad(d.error||'состояние не получено'); return; }
|
||
cur=d; try{ window.dispatchEvent(new CustomEvent('bj-igate-share',{detail:d})); }catch(e){}
|
||
if(d.manual){ stl.innerHTML=ok('папки открыты вручную (smb.conf) для '+esc(d.user||'—')+' — bj их не меняет'); f.hidden=true; return; }
|
||
var a=d.access||{}, g=a.groups||[], u=a.users||[];
|
||
acc.innerHTML=(g.length||u.length)?('Доступ получат: '+(g.length?'группы <b>'+g.map(esc).join('</b>, <b>')+'</b>':'')+(g.length&&u.length?'; ':'')+(u.length?'сотрудники <b>'+u.map(esc).join('</b>, <b>')+'</b>':'')+'. Список — из Настройка → Пользователи, обновляется сам.'):'<span style="color:var(--warn)">В bj пока нет групп и сотрудников домена с доступом — добавьте их в Настройка → Пользователи («Сотрудники из домена»).</span>';
|
||
var dt=document.getElementById('ig-sh-dtile'), dr=f.querySelector('input[name=mode][value=domain]');
|
||
dr.disabled=!d.ad_ready; dt.style.opacity=d.ad_ready?'':'.55';
|
||
if(!d.ad_ready) document.getElementById('ig-sh-dnote').textContent='Сначала подключите домен в bj: Настройка → Пользователи → «Сотрудники из домена».';
|
||
document.getElementById('ig-sh-realm').textContent=d.ad_domain||d.realm||'';
|
||
document.getElementById('ig-sh-host').textContent=d.host||'';
|
||
f.querySelector('input[name=mode][value='+(d.mode==='domain'?'domain':'simple')+']').checked=true;
|
||
if(d.on){
|
||
var who=d.mode==='domain'?'Входят сотрудники домена с доступом к bj — своей учётной записью Windows, пароль не спрашивается.':'Вход — логин <b>'+esc(d.user||'—')+'</b> и пароль, который задали здесь.';
|
||
stl.innerHTML='<div class="stc stc-ok" style="margin-top:4px"><span class="stc-ic"></span><div class="stc-b"><b>Папки открыты по сети'+(d.mode==='domain'?' — через домен '+esc(d.realm||''):'')+'</b><small>Вставьте адрес в адресную строку проводника Windows. '+who+'</small>'+addrs(d)+'</div></div>';
|
||
stl.querySelectorAll('[data-copy]').forEach(function(b){ b.onclick=function(){ (window.bjCopy||function(t){navigator.clipboard&&navigator.clipboard.writeText(t)})(b.dataset.copy); b.textContent='Скопировано ✓'; setTimeout(function(){b.textContent='Скопировать'},1500); }; });
|
||
} else stl.innerHTML='<span class="muted">Сейчас закрыты. Выберите, как входить, отметьте «Открыть папки шлюза по сети» и сохраните — здесь появится адрес для проводника.</span>';
|
||
f.enabled.checked=!!d.on; if(d.mode!=='domain' && d.user) f.user.value=d.user;
|
||
if(!d.on && !f.password.value){ f.password.type='text'; f.password.value=genPw();
|
||
var pn=document.getElementById('ig-sh-pwnote'); if(pn) pn.textContent='Пароль создан автоматически — запишите его (или впишите свой). Его вводят в Windows при первом открытии папок; позже bj его не покажет.'; }
|
||
sync();
|
||
}
|
||
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(); });
|
||
function genPw(){ var cs='ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789', a=new Uint32Array(16), s=''; crypto.getRandomValues(a); for(var i=0;i<16;i++) s+=cs[a[i]%cs.length]; return s; }
|
||
var genbtn=document.getElementById('ig-sh-gen');
|
||
if(genbtn) genbtn.addEventListener('click',function(){
|
||
f.password.type='text'; f.password.value=genPw(); 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){
|
||
if(!f.admin.value.trim()||!f.adminpw.value){ o.innerHTML=bad('не заполнено: администратор домена и пароль'); return; }
|
||
url='/admin/setup/igate/share-join'; p.set('admin',f.admin.value.trim()); p.set('adminpw',f.adminpw.value);
|
||
o.textContent='Подключаю сервер к домену… (первый раз — установка winbind, до пары минут)';
|
||
} 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, до пары минут)';
|
||
}
|
||
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;
|
||
function load(){
|
||
fetch('/admin/setup/igate/status',{cache:'no-store'}).then(function(r){return r.json()}).then(function(d){
|
||
st=d; try{ window.dispatchEvent(new CustomEvent('bj-igate-status',{detail:d})); }catch(e){} // сводка раздела «НРД»
|
||
if(!d.ok){ head.innerHTML=bad(d.error); return; }
|
||
var ri=document.getElementById('ig-reinst'), bb=document.getElementById('ig-bnd'), bv=document.getElementById('ig-bnd-v');
|
||
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));
|
||
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>'; }
|
||
// не стоит или не работает — одна кнопка: поставить заново проверенную версию с сервера (систему переставлять не нужно)
|
||
ri.style.display=(running||d.keymedia==='missing')?'none':'block'; // без флешки переустановка не поможет
|
||
if(!running){
|
||
if(d.bundled){ bb.style.display=''; bb.textContent=d.installed?'Установить ИШ заново':'Установить ИШ'; bv.textContent='версия '+d.bundled+' — проверена ZETIT, дистрибутив на сервере'; }
|
||
else { bb.style.display='none'; bv.textContent='Дистрибутива ИШ на сервере нет — он приходит с обновлением bj («Настройка → Обновления»).'; }
|
||
}
|
||
var dep=d.deponent||'', cr=document.getElementById('ig-crypto').value;
|
||
var rows=[['test3','Тестовый TEST3','проверка переводов до боевой работы'],['prod','Боевой','настоящие переводы']].map(function(x){
|
||
var code=(x[0]==='prod'?'PROD':'TEST3')+dep, ch=(d.channels||[]).filter(function(c){return c.code===code})[0], act=d.active_contour===x[0];
|
||
var li=[], a=[], pill, cls;
|
||
function it(okk,t){ li.push('<li class="'+(okk===true?'ok':okk===false?'no':'na')+'">'+t+'</li>'); }
|
||
if(!dep){ it(false,'нет депозитарного кода — раздел «Организация»'); pill='Нет кода'; cls='no'; }
|
||
else if(!ch){
|
||
it(false,'канала в шлюзе нет'+(d.installed?' — bj создаст его сам':' — появится после установки шлюза'));
|
||
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(!!ch.enabled, ch.enabled?'канал включён':'канал выключен — включится после установки ключа');
|
||
var ready=ch.enabled&&ch.profile;
|
||
if(act){ pill='● Активный'; cls='cur'; }
|
||
else if(ready){ pill='Готов'; cls='ok'; }
|
||
else { pill='Нужен ключ'; cls='no'; }
|
||
if(!ch.profile) a.push('<a class="btn sm" href="/admin/wizard?step=8&contour='+x[0]+'" style="text-decoration:none">Установить ключ</a>');
|
||
if(!act){
|
||
if(ready) a.push(x[0]==='prod'?'<button type="button" class="btn sm" data-act="showprod">Сделать активным…</button>':'<button type="button" class="btn sm" data-act="switch" data-c="test3">Сделать активным</button>');
|
||
else a.push('<button type="button" class="btn sm" disabled title="Сначала установите ключ этого контура">Сделать активным</button>');
|
||
}
|
||
a.push('<button type="button" class="btn sm ghost" data-act="contour" data-c="'+x[0]+'" title="Записать канал в шлюз заново: адрес, сертификат, ключ">Обновить канал</button>');
|
||
}
|
||
var why=act?'<p class="ct-why">Через этот контур bj сейчас отправляет и получает переводы.</p>':(ch&&!(ch.enabled&&ch.profile))?'<p class="ct-why">Чтобы сделать контур активным, сначала установите его ключ (флешка с ключом этого контура).</p>':'';
|
||
return '<div class="ct ct-'+cls+(act?' ct-on':'')+'"><div class="ct-h"><div><b>'+x[1]+'</b><small>'+x[2]+'</small></div><span class="ct-pill">'+pill+'</span></div><ul class="ct-li">'+li.join('')+'</ul>'+why+(a.length?'<div class="ct-act">'+a.join('')+'</div>':'')+'</div>';
|
||
});
|
||
tb.innerHTML=rows.join('');
|
||
document.getElementById('ig-depo').placeholder=d.depo_db_prod||d.depo_db||'как сейчас';
|
||
tb.querySelectorAll('button[data-act]').forEach(function(b){ b.onclick=function(){ act(b.dataset.act,b.dataset.c,b); }; });
|
||
}).catch(function(){ head.textContent='Состояние ИШ не получено.'; });
|
||
}
|
||
function act(a,c,b){
|
||
if(a==='showprod'){ document.getElementById('ig-prod').style.display='block'; return; }
|
||
var p=new URLSearchParams(); p.set('contour',c); p.set('crypto',document.getElementById('ig-crypto').value);
|
||
if(a==='contour'){ b.disabled=true; cout.textContent='';
|
||
post('/admin/setup/igate/contour',p).then(function(d){ b.disabled=false; if(!d.ok){ cout.innerHTML=bad(d.error); return; } job.watch(c); })
|
||
.catch(function(){ b.disabled=false; job.watch(c); }); }
|
||
if(a==='switch'){ if(!confirm('Переключить bj на тестовый контур TEST3?')) return; cout.textContent='Переключаю…';
|
||
post('/admin/setup/igate/switch',p).then(function(d){ cout.innerHTML=d.ok?ok(d.output):bad(d.error); if(d.ok) setTimeout(function(){location.reload()},15000); }); }
|
||
}
|
||
document.getElementById('ig-goprod').onclick=function(){
|
||
var p=new URLSearchParams(); p.set('contour','prod'); p.set('crypto',document.getElementById('ig-crypto').value);
|
||
p.set('depo_db',document.getElementById('ig-depo').value.trim()); p.set('confirm',document.getElementById('ig-confirm').value.trim());
|
||
if(!confirm('Перейти на БОЕВОЙ контур? bj начнёт отправлять в НРД настоящие переводы.')) return;
|
||
cout.textContent='Переключаю…';
|
||
post('/admin/setup/igate/switch',p).then(function(d){ cout.innerHTML=d.ok?ok(d.output):bad(d.error); if(d.ok) setTimeout(function(){location.reload()},15000); });
|
||
};
|
||
document.getElementById('ig-crypto').onchange=load;
|
||
// этапы установщика (bj-igate-admin) по порядку и сколько каждый обычно длится, секунд
|
||
var STAGES=[['queued','Запуск установки',15],['deps','Системные пакеты для работы без графики',120],['dist','Дистрибутив ИШ',30],
|
||
['user','Пользователь igate и хранилище паролей',15],['db','База ИШ в PostgreSQL',20],['install','Установка пакета ИШ',90],
|
||
['config','Настройка для работы без графики',15],['schema','Создание схемы базы ИШ',90],['start','Запуск ИШ и движка',120]];
|
||
var stepsEl=document.getElementById('ig-steps'), logbox=document.getElementById('ig-logbox'), cur={key:'',since:0,s:null}, tick=0;
|
||
function stageKey(s){ // прежний установщик называл два этапа «package», а первый и последний — «start»
|
||
if(s.stage==='package') return /дистрибутив/.test(s.msg||'')?'dist':'install';
|
||
if(s.stage==='start' && /запущена/.test(s.msg||'')) return 'queued';
|
||
return s.stage; }
|
||
function mmss(n){ n=Math.max(0,Math.round(n)); return Math.floor(n/60)+':'+('0'+n%60).slice(-2); }
|
||
function draw(){
|
||
var s=cur.s; if(!s) return;
|
||
var k=stageKey(s), idx=-1; STAGES.forEach(function(x,i){ if(x[0]===k) idx=i; });
|
||
if(s.stage==='done') idx=STAGES.length;
|
||
var el=(Date.now()-cur.since)/1000;
|
||
stepsEl.hidden=false;
|
||
stepsEl.innerHTML=STAGES.map(function(x,i){
|
||
var cls=i<idx?'done':i===idx?(s.stage==='error'?'err':'cur'):'', note='';
|
||
if(i===idx && s.stage!=='error'){ var slow=el>x[2]*2;
|
||
note='<small'+(slow?' class="slow"':'')+'>идёт '+mmss(el)+' · обычно до '+mmss(x[2])+(slow?' — дольше обычного; установка работает, пока счётчик идёт; зависшая снимается сама через 40 минут':'')+'</small>'; }
|
||
return '<li class="'+cls+'"><span class="ig-ic"></span><span class="ig-t">'+esc(x[1])+note+'</span></li>'; }).join('');
|
||
}
|
||
function poll(){ var t0=Date.now(), btn=document.getElementById('ig-bnd');
|
||
btn.disabled=true; // пока идёт установка — второй раз не нажать
|
||
clearInterval(tick); tick=setInterval(draw,1000);
|
||
var iv=setInterval(function(){
|
||
fetch('/admin/setup/igate/install-status',{cache:'no-store'}).then(function(r){return r.json()}).then(function(s){
|
||
if(!s.stage) return;
|
||
if(stageKey(s)!==cur.key){ cur.key=stageKey(s); cur.since=Date.now()-(s.elapsed||0)*1000; }
|
||
cur.s=s; draw(); out.textContent='';
|
||
if(s.log){ logbox.hidden=false; lg.textContent=s.log; lg.scrollTop=lg.scrollHeight; }
|
||
if(s.stage==='done'){ clearInterval(iv); clearInterval(tick); btn.disabled=false; out.innerHTML=ok(s.msg); load(); }
|
||
if(s.stage==='error'){ clearInterval(iv); clearInterval(tick); btn.disabled=false; out.innerHTML=bad(s.msg)+' — можно нажать «Установить ИШ заново»'; logbox.open=true; load(); }
|
||
}).catch(function(){});
|
||
if(Date.now()-t0>2700000){ clearInterval(iv); clearInterval(tick); } },3000); }
|
||
document.getElementById('ig-bnd').onclick=function(){ var p=new URLSearchParams(); p.set('bundled','1');
|
||
out.textContent='Запускаю…'; cur={key:'',since:0,s:null}; post('/admin/setup/igate/install',p).then(function(d){ if(!d.ok){out.innerHTML=bad(d.error);return} out.textContent=d.output; poll(); }); };
|
||
document.getElementById('ig-rb-btn').onclick=function(){ var v=document.getElementById('ig-rb-v').textContent;
|
||
if(!confirm('Откатить ИШ к версии '+v+'? Шлюз остановится на 1–3 минуты; программа, данные и база ИШ вернутся к состоянию до обновления. Пришедшую версию bj сам ставить больше не будет, пока не выйдет следующая.')) return;
|
||
out.textContent='Запускаю откат…'; cur={key:'',since:0,s:null}; post('/admin/setup/igate/rollback',new URLSearchParams()).then(function(d){ if(!d.ok){out.innerHTML=bad(d.error);return} out.textContent=d.output; poll(); }); };
|
||
if(document.getElementById('ig-dl')) document.getElementById('ig-dl').onclick=function(){ var p=new URLSearchParams(); p.set('version',document.getElementById('ig-ver').value.trim());
|
||
out.textContent='Запускаю…'; cur={key:'',since:0,s:null}; post('/admin/setup/igate/install',p).then(function(d){ if(!d.ok){out.innerHTML=bad(d.error);return} out.textContent=d.output; poll(); }); };
|
||
if(document.getElementById('ig-up')) document.getElementById('ig-up').onclick=function(){ var f=document.getElementById('ig-file').files[0]; if(!f){ out.textContent='Выберите файл igate-…-amd64.zip'; return; }
|
||
var fd=new FormData(); fd.append('file',f); out.textContent='Загружаю '+f.name+'…';
|
||
fetch('/admin/setup/igate/install',{method:'POST',body:fd}).then(function(r){return r.json()}).then(function(d){ if(!d.ok){out.innerHTML=bad(d.error);return} out.textContent=d.output; poll(); }); };
|
||
load();
|
||
// установка уже идёт (например, её начала «Подготовка модулей») — показать ход
|
||
fetch('/admin/setup/igate/install-status',{cache:'no-store'}).then(function(r){return r.json()}).then(function(s){ if(s.stage&&s.stage!=='done'&&s.stage!=='error') poll(); }).catch(function(){});
|
||
})();
|
||
</script>
|
||
{{end}}
|