Учёт DEPO: ход подготовки по шагам, понятный блок «Логин bj», пауза после неудачного входа
- «Подготовить» / «Проверить подключение» показывают ход: подключение логином администратора, проверка, что это база DEPO, применение скрипта, вход логином bj, таблицы обмена, задание, процедуры, «Готово». Сервер отдаёт шаги строками JSON (stream=1), на ошибке — шаг с крестиком и причина, без перехода со страницы. - Блоки «Администратор SQL — нужен один раз, не сохраняется» и «Логин bj — под ним bj будет работать»: bj сохраняет и использует ровно то, что вписано; «другой пароль»; на итоге шага — каким логином bj работает. - Пауза 15 минут после неудачного входа в DEPO (логин/пароль отвергнуты) для фоновых служб: с неверным паролем они за минуты блокировали логин на сервере (CHECK_POLICY) у всех, кто им пользуется. Действия человека идут без паузы; новый пароль — сразу; системное событие в «Новостях». Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
@@ -83,6 +83,36 @@
|
||||
.dq-sql pre { max-height:380px; min-height:140px; font-size:12px; margin:0; transition:opacity .2s; }
|
||||
.dq-sql pre.dq-busy { opacity:.5; }
|
||||
.dq.is-pg [data-ms], .dq:not(.is-pg) [data-pg] { display:none !important; }
|
||||
.dq-box { border:1px solid var(--border); border-radius:12px; padding:14px 16px 16px; }
|
||||
.dq-box-bj { background:color-mix(in srgb, var(--accent-weak) 45%, transparent); }
|
||||
.dq-box-h { font-weight:600; font-size:14.5px; margin:0 0 10px; }
|
||||
.dq-box-h span { color:var(--muted); font-weight:400; font-size:13px; }
|
||||
.dq-lbl-row { display:flex; justify-content:space-between; align-items:baseline; gap:8px; }
|
||||
.wiz-card .dq-link { background:none; border:0; padding:0; color:var(--accent); font-size:12.5px; font-weight:500; cursor:pointer; border-radius:4px; }
|
||||
.dq-link:hover { text-decoration:underline; }
|
||||
.dq-warn { margin:10px 0 0; padding:8px 12px; border-radius:8px; background:var(--warn-weak); color:var(--text-2); font-size:13px; }
|
||||
/* ход подготовки: строки шагов со значком состояния */
|
||||
.dq-run { margin-top:16px; border:1px solid var(--border); border-radius:12px; padding:12px 16px; }
|
||||
.dq-steps { list-style:none; margin:0; padding:0; display:grid; gap:2px; }
|
||||
.dq-st { display:flex; gap:10px; align-items:flex-start; padding:6px 0; font-size:14px; }
|
||||
.dq-st-t { min-width:0; }
|
||||
.dq-st-d { color:var(--muted); font-size:12.5px; margin-top:2px; overflow-wrap:anywhere; }
|
||||
.dq-st-ic { flex:none; position:relative; width:18px; height:18px; border-radius:50%; margin-top:1px; box-sizing:border-box; }
|
||||
.dq-st.is-run .dq-st-ic { border:2px solid var(--accent-weak); border-top-color:var(--accent); animation:dq-spin .8s linear infinite; }
|
||||
.dq-st.is-run { color:var(--text); font-weight:500; }
|
||||
.dq-st.is-ok .dq-st-ic { background:var(--ok); }
|
||||
.dq-st.is-ok .dq-st-ic::after { content:""; position:absolute; left:50%; top:45%; width:4px; height:8px; border:solid #fff; border-width:0 1.8px 1.8px 0; transform:translate(-50%,-50%) rotate(45deg); }
|
||||
.dq-st.is-ok:not(.is-final) { color:var(--text-2); }
|
||||
.dq-st.is-final { font-weight:600; color:var(--ok); }
|
||||
.dq-st.is-warn .dq-st-ic, .dq-st.is-err .dq-st-ic { color:#fff; font-size:12px; font-weight:700; line-height:18px; text-align:center; }
|
||||
.dq-st.is-warn .dq-st-ic { background:var(--warn); }
|
||||
.dq-st.is-warn .dq-st-ic::after { content:"!"; }
|
||||
.dq-st.is-err .dq-st-ic { background:var(--err); }
|
||||
.dq-st.is-err .dq-st-ic::after { content:"×"; }
|
||||
.dq-st.is-err { color:var(--err); }
|
||||
.dq-run-msg { margin-top:8px; padding:10px 12px; border-radius:8px; background:var(--err-weak, rgba(200,40,40,.08)); font-size:13.5px; overflow-wrap:anywhere; white-space:pre-wrap; }
|
||||
@keyframes dq-spin { to { transform:rotate(360deg); } }
|
||||
@media (prefers-reduced-motion: reduce) { .dq-st.is-run .dq-st-ic { animation:none; border-color:var(--accent); } }
|
||||
@media (max-width:600px) { .dq-tiles, .dq-srv, .dq-2 { grid-template-columns:minmax(0,1fr); } .dq-sql-head { flex-wrap:wrap; } .dq-sql-st { flex-basis:100%; } }
|
||||
.tooltip { display:inline-block; background:var(--border); color:var(--muted); border-radius:50%; width:16px; height:16px; line-height:16px; text-align:center; font-size:11px; cursor:help; margin-left:4px; }
|
||||
.help-block { background:var(--accent-weak); padding:10px 14px; margin:10px 0; font-size:13px; border-radius:10px; }
|
||||
@@ -202,20 +232,23 @@
|
||||
</div>
|
||||
<label class="dq-alt"><input type="radio" name="mode" value="existing"> База уже подготовлена раньше — у bj есть свой логин</label>
|
||||
|
||||
<div class="dq-panel" id="wiz-depo-auto">
|
||||
<div class="dq-panel dq-box" id="wiz-depo-auto">
|
||||
<div class="dq-box-h">Администратор SQL <span>— нужен один раз, чтобы подготовить базу; bj его не сохраняет</span></div>
|
||||
<div class="dq-grid dq-2">
|
||||
<label>Логин администратора SQL<input type="text" name="admin_user" autocomplete="off" spellcheck="false" placeholder="{{if $pg}}postgres{{else}}sa{{end}}"></label>
|
||||
<label>Пароль администратора<input type="password" name="admin_password" autocomplete="new-password"></label>
|
||||
<label>Логин<input type="text" name="admin_user" autocomplete="off" spellcheck="false" placeholder="{{if $pg}}postgres{{else}}sa{{end}}"></label>
|
||||
<label>Пароль<input type="password" name="admin_password" autocomplete="new-password"></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{/* логин bj — в обоих способах: на общем сервере SQL bj_user может быть уже занят другим bj */}}
|
||||
<div class="dq-panel" id="wiz-depo-login">
|
||||
<div class="dq-panel dq-box dq-box-bj" id="wiz-depo-login">
|
||||
<div class="dq-box-h">Логин bj <span>— под ним bj будет работать с базой DEPO</span></div>
|
||||
<div class="dq-grid dq-2">
|
||||
<label>Логин bj<input type="text" name="bj_user" value="{{.DepoUser}}" autocomplete="off" spellcheck="false"></label>
|
||||
<label>Пароль bj<input type="text" name="bj_password" value="{{.DepoPass}}" autocomplete="off" spellcheck="false" class="dq-mono"></label>
|
||||
<label>Логин<input type="text" name="bj_user" value="{{.DepoUser}}" autocomplete="off" spellcheck="false"></label>
|
||||
<label><span class="dq-lbl-row">Пароль <button type="button" class="dq-link" id="wiz-depo-gen">↻ другой пароль</button></span><input type="text" name="bj_password" value="{{.DepoPass}}" autocomplete="off" spellcheck="false" class="dq-mono"></label>
|
||||
</div>
|
||||
<p class="dq-note">С этим логином bj будет входить в базу DEPO — его создаст подготовка. Пароль придумал bj, можно вписать свой. <b>Если такой логин на сервере уже есть, его пароль будет заменён</b> — когда этим логином пользуется другая программа, впишите другое имя.</p>
|
||||
<p class="dq-note">bj придумал их сам. Можно заменить своими — <b>bj сохранит и будет использовать ровно то, что здесь написано</b>: подготовка создаст на сервере этот логин с этим паролем<span id="wiz-depo-login-script"> (в скрипте — они же)</span>.</p>
|
||||
<p class="dq-warn">Если логин с таким именем на сервере уже есть, ему будет поставлен этот пароль — программа, которая им пользуется, перестанет входить. Тогда впишите другое имя.</p>
|
||||
</div>
|
||||
|
||||
<div class="dq-panel" id="wiz-depo-autogo">
|
||||
@@ -244,6 +277,12 @@
|
||||
</div>
|
||||
<button type="submit" class="btn dq-go">Проверить подключение</button>
|
||||
</div>
|
||||
|
||||
{{/* ход подготовки/проверки — по шагам, пока сервер работает (ответ приходит строками) */}}
|
||||
<div class="dq-run" id="wiz-depo-run" hidden aria-live="polite">
|
||||
<ol class="dq-steps" id="wiz-depo-steps"></ol>
|
||||
<div class="dq-run-msg" id="wiz-depo-runmsg" hidden></div>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
<script>(function(){
|
||||
@@ -256,6 +295,7 @@
|
||||
f.admin_user.placeholder=pg?'postgres':'sa';
|
||||
var show={auto:['auto','login','autogo'], script:['login','script'], existing:['existing']}[m]||[];
|
||||
['auto','login','autogo','script','existing'].forEach(function(k){ document.getElementById('wiz-depo-'+k).hidden=show.indexOf(k)<0; });
|
||||
document.getElementById('wiz-depo-login-script').hidden=m!=='script';
|
||||
}
|
||||
function load(){ // скрипт — из текущих ответов; устаревший ответ (пока печатали дальше) не показываем
|
||||
if(f.mode.value!=='script') return;
|
||||
@@ -275,12 +315,64 @@
|
||||
document.getElementById('wiz-depo-copy').onclick=function(){ var b=this;
|
||||
window.bjCopy(pre.textContent).then(function(ok){ b.textContent=ok?'Скопировано ✓':'Выделите и скопируйте вручную'; b.classList.toggle('done',!!ok);
|
||||
setTimeout(function(){ b.textContent='Скопировать'; b.classList.remove('done'); }, 2000); }); };
|
||||
document.getElementById('wiz-depo-gen').onclick=function(){ // тот же набор, что у bj: без похожих 0/O, 1/l/I
|
||||
var up='ABCDEFGHJKLMNPQRSTUVWXYZ', low='abcdefghijkmnpqrstuvwxyz', dig='23456789', all=up+low+dig, a=new Uint32Array(24), p=[];
|
||||
crypto.getRandomValues(a);
|
||||
p.push(up[a[0]%up.length], low[a[1]%low.length], dig[a[2]%dig.length]);
|
||||
for(var i=3;i<24;i++) p.push(all[a[i]%all.length]);
|
||||
for(var j=p.length-1;j>0;j--){ var k=a[j]%(j+1), t=p[j]; p[j]=p[k]; p[k]=t; }
|
||||
f.bj_password.value=p.join(''); changed();
|
||||
};
|
||||
|
||||
// «Подготовить» / «Проверить подключение»: ход по шагам вместо тишины до перехода на следующий экран
|
||||
var run=document.getElementById('wiz-depo-run'), list=document.getElementById('wiz-depo-steps'), rmsg=document.getElementById('wiz-depo-runmsg');
|
||||
function li(key){ return list.querySelector('[data-k="'+key+'"]'); }
|
||||
function mark(el, st, text){ if(!el) return; el.className='dq-st is-'+st;
|
||||
if(text){ var d=el.querySelector('.dq-st-d'); if(!d){ d=document.createElement('div'); d.className='dq-st-d'; el.querySelector('.dq-st-t').appendChild(d); } d.textContent=text; } }
|
||||
function onStep(x){
|
||||
if(x.state==='run'){
|
||||
list.querySelectorAll('.is-run').forEach(function(e){ mark(e,'ok'); });
|
||||
var e=li(x.step); if(!e){ e=document.createElement('li'); e.dataset.k=x.step; e.innerHTML='<span class="dq-st-ic"></span><div class="dq-st-t"><span></span></div>'; list.appendChild(e); }
|
||||
e.querySelector('.dq-st-t > span').textContent=x.text; mark(e,'run');
|
||||
} else mark(li(x.step), x.state, x.text);
|
||||
}
|
||||
function finish(x, btns){
|
||||
var cur=list.querySelector('.is-run');
|
||||
if(x&&x.ok){ if(cur) mark(cur,'ok');
|
||||
var e=document.createElement('li'); e.className='dq-st is-ok is-final'; e.innerHTML='<span class="dq-st-ic"></span><div class="dq-st-t"><span>Готово — открываю итог</span></div>'; list.appendChild(e);
|
||||
setTimeout(function(){ location.href=x.go; }, 1200); return; }
|
||||
if(cur) mark(cur,'err');
|
||||
rmsg.textContent=(x&&x.msg)?x.msg.replace(/^./,function(c){return c.toUpperCase()}):'Связь с bj прервалась — обновите страницу и попробуйте ещё раз.';
|
||||
rmsg.hidden=false; btns.forEach(function(b){ b.disabled=false; });
|
||||
}
|
||||
f.addEventListener('submit', function(ev){
|
||||
var b=ev.submitter; if(!b||!b.classList.contains('dq-go')||!window.fetch||!window.TextDecoder) return;
|
||||
ev.preventDefault();
|
||||
var btns=[].slice.call(f.querySelectorAll('.dq-go')); btns.forEach(function(x){ x.disabled=true; });
|
||||
list.innerHTML=''; rmsg.hidden=true; run.hidden=false; run.scrollIntoView({block:'nearest',behavior:'smooth'});
|
||||
var fd=new FormData(f); fd.set('stream','1');
|
||||
var final=null;
|
||||
fetch(f.action,{method:'POST',body:new URLSearchParams(fd),headers:{'Content-Type':'application/x-www-form-urlencoded'},cache:'no-store'})
|
||||
.then(function(r){
|
||||
if(!r.ok||!r.body||(r.headers.get('Content-Type')||'').indexOf('ndjson')<0) throw new Error('bad');
|
||||
var rd=r.body.getReader(), dec=new TextDecoder(), buf='';
|
||||
function line(t){ if(!t.trim()) return; var x=JSON.parse(t); if(x.done) final=x; else onStep(x); }
|
||||
function pump(){ return rd.read().then(function(c){
|
||||
if(c.done){ line(buf); return; }
|
||||
buf+=dec.decode(c.value,{stream:true}); var i;
|
||||
while((i=buf.indexOf('\n'))>=0){ line(buf.slice(0,i)); buf=buf.slice(i+1); }
|
||||
return pump(); }); }
|
||||
return pump();
|
||||
})
|
||||
.then(function(){ finish(final, btns); }, function(){ finish(null, btns); });
|
||||
});
|
||||
|
||||
sync(); load();
|
||||
})();</script>
|
||||
|
||||
{{else if eq $sub "done"}}
|
||||
<table style="margin:6px 0 10px">
|
||||
<tr><td>Подключение к базе DEPO</td><td>{{if .Settings.Fancy.Server}}<span style="color:var(--ok)">✓ {{.Settings.Fancy.Server}}, база {{.DepoTables.DB}}</span>{{else}}<span style="color:var(--err)">не настроено</span>{{end}}</td></tr>
|
||||
<tr><td>Подключение к базе DEPO</td><td>{{if .Settings.Fancy.Server}}<span style="color:var(--ok)">✓ {{.Settings.Fancy.Server}}, база {{.DepoTables.DB}}, логин bj «{{.Settings.Fancy.User}}»</span>{{else}}<span style="color:var(--err)">не настроено</span>{{end}}</td></tr>
|
||||
<tr><td>Таблицы обмена FTV_M2M</td><td>{{if .Settings.Fancy.DepoTablesCheckOK}}<span style="color:var(--ok)">✓ на месте</span>{{else}}<span style="color:var(--err)">✗ {{.Settings.Fancy.DepoTablesCheck}}</span>{{end}}</td></tr>
|
||||
<tr><td>Запуск обработки</td><td>{{if .Settings.Fancy.DepoJobCheckOK}}<span style="color:var(--ok)">✓ {{.Settings.Fancy.DepoJobCheck}}</span>{{else}}<span style="color:var(--err)">✗ {{.Settings.Fancy.DepoJobCheck}}</span>{{end}}</td></tr>
|
||||
<tr><td>Обработка событий (процедуры)</td><td>{{if .Settings.Fancy.DepoProcCheckOK}}<span style="color:var(--ok)">✓ {{.Settings.Fancy.DepoProcCheck}}</span>{{else if .Settings.Fancy.DepoProcCheck}}<span style="color:var(--warn)">⚠ {{.Settings.Fancy.DepoProcCheck}}</span>{{else}}<span class="muted">не проверялась — «Перепроверить всё»</span>{{end}}</td></tr>
|
||||
|
||||
Reference in New Issue
Block a user