0616f69518
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
267 lines
25 KiB
HTML
267 lines
25 KiB
HTML
{{/* Установка ключа Валидаты: загрузка архива НРД → носитель → справочник → проверка ИШ. Один и тот же блок —
|
||
на шаге «Ключ подписи» мастера и на отдельной странице /admin/setup/keywizard (данные — keyWizardData). */}}
|
||
{{define "keyWizRet"}}{{if .InWizard}}<input type="hidden" name="ret" value="wizard">{{end}}{{if .WantContour}}<input type="hidden" name="want" value="{{.WantContour}}">{{end}}{{end}}
|
||
|
||
{{define "keyWizBody"}}
|
||
{{$s := .State}}
|
||
|
||
<style>
|
||
.kw-tiles{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin-top:6px}
|
||
.kw-tile{display:flex;gap:10px;align-items:flex-start;padding:12px 14px;border:1px solid var(--border);border-radius:12px;cursor:pointer;font-weight:400;margin:0}
|
||
.kw-tile:has(input:checked){border-color:var(--accent);background:var(--accent-weak)}
|
||
.kw-tile input{margin-top:3px;flex:none;width:auto}
|
||
.kw-tile b{display:block;font-size:14.5px}
|
||
.kw-tile small{display:block;color:var(--muted);font-size:13px;line-height:1.4;margin-top:3px}
|
||
.kw-panel{margin-top:16px}
|
||
.kw-done{display:flex;gap:14px;align-items:flex-start;padding:16px 18px;margin-top:4px;border-radius:14px;background:color-mix(in srgb,var(--ok) 9%,var(--card));border:1px solid color-mix(in srgb,var(--ok) 32%,transparent)}
|
||
.kw-done-ic{flex:none;width:30px;height:30px;border-radius:50%;background:var(--ok);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:15px}
|
||
.kw-done-b b{display:block;font-size:15.5px;margin-top:4px}
|
||
.kw-done-b p{margin:6px 0 0;font-size:14px;line-height:1.5}
|
||
.kw-done-act{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:12px}
|
||
.kw-link{background:none;border:0;padding:0;color:var(--accent);font:inherit;font-size:13.5px;cursor:pointer;text-decoration:underline;text-underline-offset:3px}
|
||
.kw-howto{margin:0 0 12px}.kw-howto summary{cursor:pointer;color:var(--accent);font-size:13.5px;margin-bottom:8px}
|
||
.kw-drive{display:flex;gap:10px;align-items:flex-start;padding:10px;border:1px solid var(--border,#ddd);border-radius:8px;cursor:pointer;margin:0}
|
||
.kw-pad{height:200px;margin-top:12px;border:2px dashed var(--border);border-radius:12px;display:flex;align-items:center;justify-content:center;text-align:center;padding:0 16px;color:var(--muted);user-select:none;-webkit-user-select:none;touch-action:none;cursor:default}
|
||
.kw-pad.on{border-color:var(--accent);background:var(--accent-weak);color:var(--accent);cursor:crosshair}
|
||
.kw-bar{height:8px;margin-top:12px;border-radius:4px;background:var(--surface-2,#eee);overflow:hidden}
|
||
.kw-bar i{display:block;height:100%;width:0;background:var(--accent);transition:width .3s}
|
||
.kw-rng.ok .kw-bar i{background:var(--ok)}
|
||
.kw-rng-row{display:flex;gap:12px;align-items:center;justify-content:space-between;flex-wrap:wrap;margin-top:10px}
|
||
.kw-locked{opacity:.45;pointer-events:none;filter:grayscale(.3)}
|
||
@media (max-width:600px){.kw-tiles{grid-template-columns:minmax(0,1fr)}}
|
||
</style>
|
||
{{/* ===== Датчик случайных чисел Валидаты: один раз для сервера, до первой работы с ключом (validatarng.go) ===== */}}
|
||
{{if not $s.Done}}
|
||
<div class="card kw-rng" id="kw-rng" hidden>
|
||
<h2>Датчик случайных чисел Валидаты</h2>
|
||
<p class="muted" style="margin:0">Перед первой работой с ключом СКЗИ Валидата набирает случайность от движений мыши — на рабочем месте она показывает окно «Пожалуйста, двигайте мышью». У сервера экрана нет: окно Валидаты открыто на сервере, а мышью водите вы — здесь, bj передаёт ваши движения в это окно. Делается один раз для сервера, около минуты.</p>
|
||
<div class="kw-pad" id="kw-pad">Нажмите «Начать» и водите мышью по этому полю, пока полоса не заполнится</div>
|
||
<div class="kw-bar"><i id="kw-rng-bar"></i></div>
|
||
<div class="kw-rng-row"><span id="kw-rng-msg" class="muted">Датчик ещё не инициализирован — без этого ключ на сервере не работает.</span>
|
||
<span style="display:flex;gap:8px"><button type="button" class="btn btn-secondary" id="kw-rng-stop" hidden>Остановить</button><button type="button" class="btn btn-ok" id="kw-rng-go">Начать</button></span></div>
|
||
</div>
|
||
<script>(function(){
|
||
var card=document.getElementById('kw-rng'), pad=document.getElementById('kw-pad'),
|
||
bar=document.getElementById('kw-rng-bar'), msg=document.getElementById('kw-rng-msg'), go=document.getElementById('kw-rng-go'), stop=document.getElementById('kw-rng-stop');
|
||
var active=false, buf=[], lastT=0, sending=false, lastSent=0, shown=false, finished=false;
|
||
var J={'Accept':'application/json'};
|
||
function lock(on){ var m=document.getElementById('kw-main'); if(m) m.classList.toggle('kw-locked', on); }
|
||
function pct(v){ return Math.round(Math.min(1,v||0)*100); }
|
||
function show(v){
|
||
if(finished) return;
|
||
if(v.ready){ lock(false); active=false;
|
||
if(shown||v.state==='done'){ card.hidden=false; card.classList.add('ok'); finished=true; bar.style.width='100%'; pad.hidden=true; go.hidden=true; stop.hidden=true;
|
||
msg.textContent='✓ Датчик готов — теперь подключите ключ ниже. Повторять это не нужно.'; }
|
||
return; }
|
||
shown=true; card.hidden=false; lock(true);
|
||
if(v.state==='running'||v.state==='starting'){
|
||
active=true; pad.classList.add('on'); go.hidden=true; stop.hidden=false; bar.style.width=pct(v.progress)+'%';
|
||
pad.textContent=v.window?'Водите мышью по полю — '+pct(v.progress)+' %':'Открываю окно Валидаты на сервере…';
|
||
msg.textContent=v.window?'Ведите мышью без остановки, пока полоса не заполнится.':'Ещё пара секунд…';
|
||
} else {
|
||
active=false; pad.classList.remove('on'); go.hidden=false; stop.hidden=true; bar.style.width='0';
|
||
pad.textContent='Нажмите «Начать» и водите мышью по этому полю, пока полоса не заполнится';
|
||
go.textContent=(v.state==='failed'||v.state==='cancelled')?'Начать заново':'Начать';
|
||
msg.textContent=v.error?('Не получилось: '+v.error):(v.ok===false?'Состояние датчика не проверено':'Датчик ещё не инициализирован — без этого ключ на сервере не работает.');
|
||
}
|
||
}
|
||
function status(){ return fetch('/admin/setup/rng/status',{headers:J,cache:'no-store'}).then(function(r){return r.json()}).then(show).catch(function(){}); }
|
||
go.addEventListener('click',function(){ go.disabled=true; msg.textContent='Открываю окно Валидаты на сервере…';
|
||
fetch('/admin/setup/rng/start',{method:'POST',headers:J,cache:'no-store'}).then(function(r){return r.json()}).then(show)
|
||
.catch(function(){ msg.textContent='Сервер не ответил — попробуйте ещё раз'; }).then(function(){ go.disabled=false; }); });
|
||
stop.addEventListener('click',function(){ active=false; buf=[];
|
||
fetch('/admin/setup/rng/cancel',{method:'POST',headers:J,cache:'no-store'}).then(function(r){return r.json()}).then(show).catch(function(){}); });
|
||
pad.addEventListener('pointermove',function(e){ if(!active) return;
|
||
var r=pad.getBoundingClientRect(), list=(e.getCoalescedEvents&&e.getCoalescedEvents().length)?e.getCoalescedEvents():[e];
|
||
list.forEach(function(ev){ var x=Math.round((ev.clientX-r.left)/r.width*1000), y=Math.round((ev.clientY-r.top)/r.height*1000), t=ev.timeStamp;
|
||
var dt=lastT?Math.round(t-lastT):0; lastT=t;
|
||
buf.push([Math.max(0,Math.min(1000,x)),Math.max(0,Math.min(1000,y)),Math.max(0,Math.min(1000,dt))]); });
|
||
});
|
||
setInterval(function(){
|
||
if(!active||sending) return;
|
||
if(buf.length){ var m=buf.splice(0,1500); sending=true; lastSent=Date.now();
|
||
fetch('/admin/setup/rng/moves',{method:'POST',headers:{'Accept':'application/json','Content-Type':'application/json'},body:JSON.stringify({m:m}),cache:'no-store'})
|
||
.then(function(r){return r.json()}).then(show).catch(function(){}).then(function(){ sending=false; });
|
||
} else if(Date.now()-lastSent>1000){ sending=true; lastSent=Date.now(); status().then(function(){ sending=false; }); }
|
||
},250);
|
||
document.addEventListener('DOMContentLoaded',status); if(document.readyState!=='loading') status();
|
||
})();</script>
|
||
{{end}}
|
||
<div id="kw-main">
|
||
{{/* ===== Лента шагов — когда путь выбран (флешка с ключом или новая флешка из архива) ===== */}}
|
||
{{if or $s.StagingID $s.Done}}
|
||
{{if $s.Done}}<details class="kw-howto"><summary>Как прошла установка — {{len $s.Steps}} этапов</summary>{{end}}
|
||
<div class="card">
|
||
<ol style="list-style:none;padding:0;margin:0;display:grid;gap:12px">
|
||
{{range $i, $step := $s.Steps}}
|
||
<li style="display:flex;gap:12px;align-items:flex-start">
|
||
<span style="flex:0 0 28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:13px;
|
||
{{if eq $step.Status "ok"}}background:var(--ok-weak);color:var(--ok)
|
||
{{else if eq $step.Status "error"}}background:var(--err-weak);color:var(--err)
|
||
{{else if eq $step.Status "active"}}background:var(--accent-weak);color:var(--accent)
|
||
{{else}}background:var(--surface-2,#eee);color:var(--muted,#999){{end}}">
|
||
{{if eq $step.Status "ok"}}✓{{else if eq $step.Status "error"}}{{else}}{{add $i 1}}{{end}}
|
||
</span>
|
||
<div style="flex:1">
|
||
<div style="font-weight:600">{{$step.Title}}</div>
|
||
{{if $step.Detail}}<div class="muted" style="font-size:13px;margin-top:2px">{{$step.Detail}}</div>{{end}}
|
||
</div>
|
||
</li>
|
||
{{end}}
|
||
</ol>
|
||
</div>
|
||
{{if $s.Done}}</details>{{end}}
|
||
|
||
{{/* ===== Действие в зависимости от состояния ===== */}}
|
||
{{end}}
|
||
{{if $s.Done}}
|
||
<div class="kw-done">
|
||
<span class="kw-done-ic" aria-hidden="true">✓</span>
|
||
<div class="kw-done-b">
|
||
<b>Ключ подключён и подписывает</b>
|
||
<p>{{if $s.MediaDevice}}Флешка с ключом подключена к шлюзу НРД как есть — на неё ничего не записывалось.{{else}}Ключ записан на флешку, справочник сертификатов сформирован, шлюз НРД подписывает им документы.{{end}} Флешка должна оставаться в сервере: без неё шлюз не запустится.</p>
|
||
{{if $s.Backup}}<p class="muted" style="font-size:12.5px">Копия прежнего содержимого флешки: <code>{{$s.Backup}}</code></p>{{end}}
|
||
<div class="kw-done-act">
|
||
{{if devOn}}<form method="post" action="/admin/setup/test-nsd" style="margin:0"><input type="hidden" name="scenario" value="2001"><button type="submit" class="btn btn-ok">→ Отправить тестовый документ роботу</button></form>{{else if not .InWizard}}<a href="/admin/wizard?step=8" class="btn btn-ok">→ Вернуться в мастер настройки</a> <a href="/admin/status" class="btn btn-secondary">Проверить подключения</a>{{end}}
|
||
<form method="post" action="/admin/setup/keywizard/reset" style="margin:0">{{template "keyWizRet" .}}<button type="submit" class="kw-link" title="Когда истечёт сертификат или понадобится ключ другого контура">Заменить ключ</button></form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{{else if and $s.StagingID $s.MediaDevice}}
|
||
{{/* ключ с подготовленной флешки — используется как есть, на флешку ничего не пишется */}}
|
||
<div class="card">
|
||
<h2>Шаг 2 — использовать ключ с флешки</h2>
|
||
<p class="muted">На флешке уже подготовленный ключ: <code>{{fallbackTpl $s.VDK "—"}}</code>{{if $s.MediaProfile}}, профиль <code>{{$s.MediaProfile}}</code>{{end}}.
|
||
bj подключит его к шлюзу НРД <b>как есть</b>: на флешку ничего не записывается — справочник для ИШ собирается на диске сервера, флешка подключается только для чтения.</p>
|
||
<form method="post" action="/admin/setup/keywizard/install" style="margin-top:12px;display:grid;gap:12px;max-width:640px"
|
||
onsubmit="this.querySelector('button[type=submit]').disabled=true;this.querySelector('button[type=submit]').textContent='Подключаю… (до пары минут)';">
|
||
{{template "keyWizRet" .}}
|
||
<input type="hidden" name="as_is" value="1">
|
||
<div>
|
||
<label style="font-weight:600;display:block;margin-bottom:6px">Для какого контура НРД этот ключ</label>
|
||
{{$c := .SuggestContour}}{{if not $c}}{{$c = .WantContour}}{{end}}{{if not $c}}{{$c = currentContour}}{{end}}
|
||
{{if .SuggestIssuer}}<p class="muted" style="font-size:12px;margin:0 0 4px">Сертификат от «{{.SuggestIssuer}}» — это {{if eq .SuggestContour "test3"}}тестовый{{else}}боевой{{end}} контур.</p>{{end}}
|
||
<label style="display:flex;gap:8px;align-items:center;font-weight:400"><input type="radio" name="contour" value="test3" style="width:auto" {{if ne $c "prod"}}checked{{end}}> Тестовый TEST3</label>
|
||
<label style="display:flex;gap:8px;align-items:center;font-weight:400"><input type="radio" name="contour" value="prod" style="width:auto" {{if eq $c "prod"}}checked{{end}}> Боевой</label>
|
||
</div>
|
||
<button type="submit" class="btn btn-ok">Использовать ключ с флешки</button>
|
||
</form>
|
||
<form method="post" action="/admin/setup/keywizard/reset" style="margin-top:8px">{{template "keyWizRet" .}}
|
||
<button type="submit" class="btn btn-secondary">Отмена</button>
|
||
</form>
|
||
</div>
|
||
{{else if $s.StagingID}}
|
||
{{/* Архив загружен — выбор флешки + запись */}}
|
||
<div class="card">
|
||
<h2>Шаг 2 — вставьте новую флешку и запишите ключ</h2>
|
||
<p class="muted">Архив распакован. Ключ: <code>{{fallbackTpl $s.VDK "—"}}</code>.
|
||
Вставьте в сервер чистую флешку — на неё запишется ключ и справочник сертификатов, затем bj
|
||
подключит её к шлюзу и перезапустит ИШ. Копия прежнего содержимого флешки сохраняется на сервере.
|
||
Не используйте флешку с рабочим ключом другого сервера.</p>
|
||
{{if not .Drives}}<p style="margin:10px 0 0">Флешка не найдена — вставьте её в сервер и <a href="javascript:location.reload()">обновите страницу</a>.</p>{{else}}
|
||
<form method="post" action="/admin/setup/keywizard/install" style="margin-top:12px;display:grid;gap:12px;max-width:640px"
|
||
onsubmit="this.querySelector('button[type=submit]').disabled=true;this.querySelector('button[type=submit]').textContent='Устанавливаю… (до нескольких минут)';">
|
||
{{template "keyWizRet" .}}
|
||
|
||
<div>
|
||
<label style="font-weight:600;display:block;margin-bottom:6px">Целевая флешка</label>
|
||
{{if .Drives}}
|
||
{{range $i, $d := .Drives}}
|
||
<label style="display:flex;gap:10px;align-items:flex-start;padding:10px;border:1px solid var(--border,#ddd);border-radius:8px;margin-bottom:8px;cursor:pointer">
|
||
<input type="radio" name="target_device" value="{{$d.Device}}" {{if $s.MediaDevice}}{{if eq $d.Device $s.MediaDevice}}checked{{end}}{{else if $d.IsKeymedia}}checked{{else if and (eq $i 0) (not (anyKeymedia $.Drives))}}checked{{end}} style="margin-top:3px">
|
||
<span>
|
||
<b>{{fallbackTpl $d.Model "USB-носитель"}}</b> · {{$d.Size}} · {{$d.FSType}}
|
||
{{if $d.Label}}· метка «{{$d.Label}}»{{end}}<br>
|
||
<span class="muted" style="font-size:12px">{{$d.Device}}{{if $d.Mountpoint}} · {{$d.Mountpoint}}{{end}}
|
||
{{if $d.IsKeymedia}}<b style="color:var(--accent)">← текущий ключевой носитель ИШ (рекомендуется)</b>{{end}}</span>
|
||
</span>
|
||
</label>
|
||
{{end}}
|
||
{{end}}
|
||
</div>
|
||
|
||
<div>
|
||
<label style="font-weight:600;display:block;margin-bottom:6px">Для какого контура НРД этот ключ</label>
|
||
{{$c := .SuggestContour}}{{if not $c}}{{$c = .WantContour}}{{end}}{{if not $c}}{{$c = currentContour}}{{end}}
|
||
{{if .SuggestIssuer}}<p class="muted" style="font-size:12px;margin:0 0 4px">В архиве сертификат от «{{.SuggestIssuer}}» — это {{if eq .SuggestContour "test3"}}тестовый{{else}}боевой{{end}} ключ.</p>{{end}}
|
||
<label style="display:flex;gap:8px;align-items:center;font-weight:400"><input type="radio" name="contour" value="test3" style="width:auto" {{if ne $c "prod"}}checked{{end}}> Тестовый TEST3 <span class="muted" style="font-size:12px">— сертификат от «Тестовый УЦ»</span></label>
|
||
<label style="display:flex;gap:8px;align-items:center;font-weight:400"><input type="radio" name="contour" value="prod" style="width:auto" {{if eq $c "prod"}}checked{{end}}> Боевой <span class="muted" style="font-size:12px">— выдан УЦ Московской биржи для работы</span></label>
|
||
<span class="muted" style="font-size:12px">У тестового и боевого контуров разные ключи: ключ подключается только к каналу ИШ своего контура.</span>
|
||
</div>
|
||
|
||
<div>
|
||
<label style="font-weight:600;display:block;margin-bottom:6px">Имя профиля в справочнике (необязательно)</label>
|
||
<input type="text" name="profile_name" value="{{$s.MediaProfile}}" placeholder="Авто из архива (напр. PrUser1046)" autocomplete="off"
|
||
pattern="[A-Za-z0-9_-]*" style="width:100%">
|
||
<span class="muted" style="font-size:12px">Пусто = имя берётся из архива автоматически.</span>
|
||
</div>
|
||
|
||
<button type="submit" class="btn btn-ok">Записать ключ на флешку</button>
|
||
</form>{{end}}
|
||
<form method="post" action="/admin/setup/keywizard/reset" style="margin-top:8px">{{template "keyWizRet" .}}
|
||
<button type="submit" class="btn btn-secondary">Отмена</button>
|
||
</form>
|
||
</div>
|
||
{{else}}
|
||
{{/* Начало: где ключ — на подготовленной флешке или в архиве НРД (тогда — на новую флешку) */}}
|
||
<div class="card">
|
||
<h2>Где ваш ключ?</h2>
|
||
<div class="kw-tiles">
|
||
<label class="kw-tile"><input type="radio" name="kwmode" value="media" checked><span><b>Подключить флешку с ключом</b><small>Флешка уже подготовлена (например, с другого сервера bj): bj найдёт на ней ключ и подключит к шлюзу как есть — ничего на неё не записывая.</small></span></label>
|
||
<label class="kw-tile"><input type="radio" name="kwmode" value="new"><span><b>Сформировать ключ на новой флешке</b><small>Есть архив ключа от НРД и пароль: bj распакует его и запишет ключ на чистую флешку.</small></span></label>
|
||
</div>
|
||
|
||
<details class="kw-howto" style="margin-top:12px"><summary>Что должно быть на флешке</summary>
|
||
<ul class="muted" style="font-size:13px;margin:4px 0 0;padding-left:18px;line-height:1.6">
|
||
<li><b>Ключ Валидаты</b> — файл <code>*.vdk</code> (обязательно; без него флешку не примет).</li>
|
||
<li><b>Справочник сертификатов</b> — папка с файлами <code>local.gdbm</code> и <code>local.pse</code> (оба обязательны). Имя папки любое — <code>spr1046</code>, <code>LAP_NEW</code> и т. п.; bj ищет эти файлы по имени в любой папке флешки.</li>
|
||
<li>Необязательно: файлы отзыва <code>*.crl</code>; из архива НРД также <code>key.reg</code> и папка <code>vdkeys/</code> — их наличие не требуется.</li>
|
||
</ul>
|
||
<p class="muted" style="font-size:13px;margin:8px 0 0">Раскладка любая: как в архиве НРД (<code>key.reg</code> + папка справочника + <code>vdkeys/*.vdk</code>) или произвольно — bj сам находит <code>*.vdk</code> и <code>local.gdbm</code>/<code>local.pse</code> в любой папке. Флешка FAT32/exFAT/NTFS или ext, подключается только на чтение.</p>
|
||
</details>
|
||
|
||
<div class="kw-panel" id="kw-media">
|
||
{{if .Drives}}
|
||
<form method="post" action="/admin/setup/keywizard/from-media" style="display:grid;gap:8px;max-width:640px">
|
||
{{template "keyWizRet" .}}
|
||
<p class="muted" style="margin:0">Флешка с ключом:</p>
|
||
{{range $i, $d := .Drives}}
|
||
<label class="kw-drive">
|
||
<input type="radio" name="source_device" value="{{$d.Device}}" {{if eq $i 0}}checked{{end}} style="margin-top:3px;width:auto">
|
||
<span><b>{{fallbackTpl $d.Model "USB-носитель"}}</b> · {{$d.Size}} · {{$d.FSType}}{{if $d.Label}} · метка «{{$d.Label}}»{{end}}<br>
|
||
<span class="muted" style="font-size:12px">{{$d.Device}}{{if $d.IsKeymedia}} · <b style="color:var(--accent)">сейчас ключевой носитель ИШ</b>{{end}}</span></span>
|
||
</label>
|
||
{{end}}
|
||
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center">
|
||
<button type="submit" class="btn btn-ok" onclick="this.textContent='Ищу ключ на флешке…'">Подключить флешку</button>
|
||
<button type="button" class="btn btn-secondary" onclick="this.textContent='Ищу флешку…';location.reload()">↻ Перепроверить флешку</button>
|
||
</div>
|
||
</form>
|
||
{{else}}
|
||
<p style="margin:0 0 8px">Флешка не найдена. Вставьте флешку с ключом в сервер и нажмите «Перепроверить флешку».</p>
|
||
<button type="button" class="btn btn-ok" onclick="this.textContent='Ищу флешку…';location.reload()">↻ Перепроверить флешку</button>
|
||
<p class="muted" style="font-size:12.5px;margin:8px 0 0">Если сервер — виртуальная машина (VMware/ESXi/Hyper-V), USB-носитель нужно сначала пробросить в гостя (в настройках ВМ подключить USB-устройство). Без проброса флешка в сервере не появляется.</p>
|
||
{{end}}
|
||
</div>
|
||
|
||
<div class="kw-panel" id="kw-new" hidden>
|
||
<p class="muted" style="margin:0 0 8px">Шаг 1 — архив ключа от НРД (.7z) и пароль архива. Флешку bj попросит после распаковки.</p>
|
||
<form method="post" action="/admin/setup/keywizard/upload" enctype="multipart/form-data"
|
||
style="display:grid;gap:10px;max-width:560px">
|
||
{{template "keyWizRet" .}}
|
||
<input type="file" name="archive" accept=".7z,.zip" required>
|
||
<input type="password" name="password" placeholder="Пароль архива (например 11)" autocomplete="off">
|
||
<button type="submit" class="btn btn-ok">Загрузить и распаковать</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
<script>(function(){
|
||
var r=document.querySelectorAll('input[name=kwmode]');
|
||
function sync(){ var m=document.querySelector('input[name=kwmode]:checked'); m=m?m.value:'media';
|
||
document.getElementById('kw-media').hidden=m!=='media'; document.getElementById('kw-new').hidden=m!=='new'; }
|
||
r.forEach(function(x){ x.addEventListener('change', sync); }); sync();
|
||
})();</script>
|
||
{{end}}
|
||
</div>
|
||
{{end}}
|