Резервное копирование bj на SMB: настройки (хранилище, расписание, хранение), копия сейчас, список и проверка копий; восстановление из копии на шаге 1 мастера; таймер bj-backup, smbclient в установщике
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
@@ -50,6 +50,62 @@ a.wstep:hover .bub { box-shadow:0 0 0 4px var(--accent-weak); }
|
||||
|
||||
{{/* ============= ШАГ 1: Лицензия ============= */}}
|
||||
{{if eq .Step 1}}
|
||||
<div class="card" id="wiz-restore" style="border-style:dashed">
|
||||
<details>
|
||||
<summary style="cursor:pointer;font-weight:600">Переустановка сервера? Восстановить из резервной копии</summary>
|
||||
<p class="muted" style="font-size:13px;margin:8px 0">Если у вас есть резервная копия bj на сетевом хранилище (Настройка → Резервное копирование), можно восстановить её вместо настройки с нуля: вернутся настройки, пользователи, лицензия, журнал заявок, база и папка шлюза НРД. Текущие данные этого сервера будут заменены. bj и шлюз НРД на время восстановления остановятся — обычно 1–3 минуты.</p>
|
||||
<form id="rs-form" style="display:grid;gap:8px;max-width:640px" onsubmit="return false">
|
||||
<div style="display:grid;grid-template-columns:2fr 1fr;gap:8px">
|
||||
<label>Хранилище (SMB)<input name="share" placeholder="//10.10.10.11/Backup" value="{{.Settings.Backup.Share}}"></label>
|
||||
<label>Папка<input name="dir" placeholder="BJ_Backup/bj" value="{{.Settings.Backup.Dir}}"></label>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">
|
||||
<label>Логин хранилища<input name="user" autocomplete="off" value="{{.Settings.Backup.User}}"></label>
|
||||
<label>Пароль<input name="password" type="password" autocomplete="new-password"></label>
|
||||
</div>
|
||||
<div><button class="btn" type="button" id="rs-list">Показать копии</button></div>
|
||||
<div id="rs-pick" style="display:none;gap:8px;align-items:flex-end;flex-wrap:wrap">
|
||||
<label>Копия<select name="name" id="rs-name" style="min-width:220px"></select></label>
|
||||
<button class="btn" type="button" id="rs-go" style="background:var(--err);border-color:var(--err)">Восстановить</button>
|
||||
</div>
|
||||
<div id="rs-out" class="muted" style="font-size:13px"></div>
|
||||
</form>
|
||||
<script>
|
||||
(function(){
|
||||
var f=document.getElementById('rs-form'), out=document.getElementById('rs-out'), pick=document.getElementById('rs-pick'), sel=document.getElementById('rs-name');
|
||||
function esc(s){return String(s).replace(/[&<>"]/g,function(c){return{'&':'&','<':'<','>':'>','"':'"'}[c]})}
|
||||
function params(){ var p=new URLSearchParams(); ['share','dir','user','password'].forEach(function(n){ var v=f[n].value.trim(); if(v) p.set(n,v); }); return p; }
|
||||
function nice(n){ var m=n.match(/^(\d{4})-(\d{2})-(\d{2})_(\d{2})(\d{2})$/); return m? m[3]+'.'+m[2]+'.'+m[1]+' '+m[4]+':'+m[5] : n; }
|
||||
document.getElementById('rs-list').onclick=function(){
|
||||
out.textContent='Читаю список копий…'; pick.style.display='none';
|
||||
fetch('/admin/setup/backup/list',{method:'POST',body:params(),cache:'no-store'}).then(function(r){return r.json()}).then(function(d){
|
||||
if(!d.ok){ out.innerHTML='<span style="color:var(--err)">✗ '+esc(d.error)+'</span>'; return; }
|
||||
if(!d.list||!d.list.length){ out.textContent='На хранилище копий нет.'; return; }
|
||||
sel.innerHTML=d.list.map(function(n){return '<option value="'+esc(n)+'">'+esc(nice(n))+'</option>'}).join('');
|
||||
pick.style.display='flex'; out.textContent='Найдено копий: '+d.list.length+'. Выберите копию — обычно самую свежую.';
|
||||
}).catch(function(){ out.textContent='Не удалось получить список.'; });
|
||||
};
|
||||
document.getElementById('rs-go').onclick=function(){
|
||||
if(!confirm('Восстановить копию от '+nice(sel.value)+'? Текущие настройки и данные этого сервера будут заменены, bj и шлюз НРД перезапустятся.')) return;
|
||||
var p=params(); p.set('name', sel.value); var t0=Date.now();
|
||||
out.textContent='Запускаю восстановление…';
|
||||
fetch('/admin/setup/backup/restore',{method:'POST',body:p,cache:'no-store'}).then(function(r){return r.json()}).then(function(d){
|
||||
if(!d.ok){ out.innerHTML='<span style="color:var(--err)">✗ '+esc(d.error)+'</span>'; return; }
|
||||
var iv=setInterval(function(){
|
||||
fetch('/admin/setup/backup/status',{cache:'no-store'}).then(function(r){return r.json()}).then(function(s){
|
||||
var r=s.restore; if(!r) return;
|
||||
out.textContent='Восстановление: '+r.msg;
|
||||
if(r.stage==='done'){ clearInterval(iv); out.innerHTML='<span style="color:var(--ok)">✓ '+esc(r.msg)+'. Сейчас откроется вход — войдите учётной записью из восстановленной копии.</span>'; setTimeout(function(){ location.href='/admin/'; }, 4000); }
|
||||
if(r.stage==='error'){ clearInterval(iv); out.innerHTML='<span style="color:var(--err)">✗ '+esc(r.msg)+'</span>'; }
|
||||
}).catch(function(){ out.textContent='bj перезапускается, жду…'; });
|
||||
if(Date.now()-t0>900000) clearInterval(iv);
|
||||
}, 3000);
|
||||
});
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
</details>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2><span class="dot {{if .Done.License}}ok{{else}}err{{end}}"></span>Шаг 1. Лицензия</h2>
|
||||
<div class="help-block"><strong>Что это.</strong> Ключ продукта (BJ-XXXX-XXXX-XXXX) привязывает этот сервер к вашей лицензии.<br>
|
||||
|
||||
Reference in New Issue
Block a user