v27: просмотрщик бекапов — открывает JSON и даёт скачать файлы

This commit is contained in:
Dauren777 2026-06-05 07:00:28 +00:00
parent 9e900d1c3d
commit e43471efcc

117
viewer.html Normal file
View File

@ -0,0 +1,117 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Просмотр бекапа ИИ-Агент ПБ</title>
<style>
:root{--ink:#0F1218;--cyan:#00E5FF;--cyan-50:#E8FCFF;--white:#fff;--gray-500:#5B6573;--gray-100:#F2F4F7;--gray-200:#E5E7EB;--green:#10B981;--red:#EF4444;--amber:#F59E0B;--blue:#3B82F6}
*{box-sizing:border-box;margin:0;padding:0}
body{font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,sans-serif;color:var(--ink);background:var(--gray-100);min-height:100vh;padding:40px 24px}
.container{max-width:900px;margin:0 auto}
h1{font-size:24px;font-weight:800;margin-bottom:8px}h1 span{color:var(--cyan)}
p.sub{color:var(--gray-500);margin-bottom:24px}
.drop-zone{border:3px dashed var(--cyan);border-radius:16px;padding:60px 24px;text-align:center;cursor:pointer;background:var(--white);margin-bottom:24px;transition:.15s}
.drop-zone:hover{background:var(--cyan-50)}
.drop-zone .icon{font-size:48px;display:block;margin-bottom:8px}
.drop-zone .txt{font-size:18px;font-weight:600;color:var(--ink);margin-bottom:4px}
.drop-zone .hint{font-size:14px;color:var(--gray-500)}
.ev{border:1px solid var(--gray-200);border-radius:12px;padding:20px;margin-bottom:16px;background:var(--white)}
.ev h3{font-size:16px;margin-bottom:8px}
.meta{display:flex;gap:16px;flex-wrap:wrap;font-size:12px;color:var(--gray-500);margin-bottom:8px}
.meta strong{color:var(--ink)}
.badge{display:inline-block;padding:3px 8px;border-radius:4px;font-size:11px;font-weight:700}
.g{background:#D1FAE5;color:#065F46}.a{background:#FEF3C7;color:#92400E}.r{background:#FEE2E2;color:#991B1B}.w{background:#eee;color:#666}
.month{background:var(--gray-100);padding:10px 14px;border-radius:8px;margin:8px 0}
.month strong{font-size:13px;color:var(--ink)}
.month .rep{font-size:13px;color:var(--gray-500);margin:4px 0}
.file-item{display:flex;align-items:center;gap:8px;padding:4px 0;font-size:13px}
.file-item a{color:var(--ink);font-weight:600;cursor:pointer;text-decoration:underline}
.file-item a:hover{color:var(--cyan)}
.file-item .sz{font-size:11px;color:var(--gray-500)}
.sub-blk{border-left:3px solid var(--cyan);padding-left:14px;margin:8px 0 8px 12px}
.sub-blk strong{font-size:13px}
.no-data{text-align:center;padding:40px;color:var(--gray-500);font-size:16px}
</style>
</head>
<body>
<div class="container">
<h1><span>📂 Просмотр бекапа</span> ИИ-Агент ПБ</h1>
<p class="sub">Загрузите файл backup_pb_*.json, сохранённый из платформы</p>
<div class="drop-zone" id="dropZone" onclick="document.getElementById('fileInput').click()">
<span class="icon">📥</span>
<div class="txt">Нажмите или перетащите файл бекапа</div>
<div class="hint">Файл backup_pb_YYYY-MM-DD.json</div>
</div>
<input type="file" id="fileInput" accept=".json" style="display:none" onchange="loadBackup(this.files[0])">
<div id="content"></div>
</div>
<script>
var statusMap={done:"Исполнено",warn:"На контроле",late:"Просрочено",wait:"В процессе"};
var sections=["I. Люди","II. Оборудование","III. Аварии и ЧС","IV. Информ. работа","V. ИИ и цифровизация"];
var branches=["Дирекция ПБ","Дивизион «Сеть»","Корпоративный бизнес","Розничный бизнес","Сервисная фабрика","Телеком Комплект","Корпоративный университет","Управление проектами","Цифровой бизнес"];
var monthNames=["Янв","Фев","Мар","Апр","Май","Июн","Июл","Авг","Сен","Окт","Ноя","Дек"];
function esc(s){return s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}
function M(k){var p=k.split("-");return monthNames[parseInt(p[1])-1]+" "+p[0]}
var dropZone=document.getElementById("dropZone");
dropZone.addEventListener("dragover",function(e){e.preventDefault();this.style.background="var(--cyan-50)"});
dropZone.addEventListener("dragleave",function(e){this.style.background="var(--white)"});
dropZone.addEventListener("drop",function(e){e.preventDefault();this.style.background="var(--white)";loadBackup(e.dataTransfer.files[0])});
function loadBackup(file){
if(!file){alert("Выберите файл");return}
var reader=new FileReader();
reader.onload=function(ev){
try{
var data=JSON.parse(ev.target.result);
if(!data.events||!data.files){alert("❌ Неверный формат файла");return}
renderBackup(data);
}catch(e){alert("❌ Ошибка чтения файла: "+e.message)}
};
reader.readAsText(file);
}
function getMD(data,id,si){var k=si>=0?'sf_'+id+'_s'+si:'sf_'+id;var raw=data.files[k];return raw?JSON.parse(raw):{}}
function renderBackup(data){
var h='';
if(data.date)h+='<p style="margin-bottom:16px;color:var(--gray-500)">Дата бекапа: <strong>'+new Date(data.date).toLocaleString()+'</strong> · Мероприятий: '+data.events.length+'</p>';
data.events.forEach(function(e){
var scls={done:"g",warn:"a",late:"r",wait:"w"}[e.s];
h+='<div class="ev"><h3>'+e.id+'. '+esc(e.t)+'</h3>';
h+='<div class="meta"><span>Раздел: <strong>'+sections[e.sec]+'</strong></span><span>Дивизион: <strong>'+branches[e.b]+'</strong></span><span>Срок: <strong>'+e.due+'</strong></span><span>Факт: <strong>'+(e.done||"—")+'</strong></span><span>Прогресс: <strong>'+e.p+'%</strong></span><span class="badge '+scls+'">'+statusMap[e.s]+'</span></div>';
// Main event files
var md=getMD(data,e.id,-1);
for(var key in md){if(md.hasOwnProperty(key)&&md[key]){
var d=md[key];if(d.report||(d.files&&d.files.length)){
h+='<div class="month"><strong>'+M(key)+'</strong>';
if(d.report)h+='<div class="rep">'+esc(d.report)+'</div>';
if(d.files&&d.files.length)d.files.forEach(function(f){h+='<div class="file-item"><a href="'+f.data+'" download="'+esc(f.name)+'">📄 '+esc(f.name)+'</a><span class="sz">'+esc(f.desc||'')+' ('+(f.size/1024).toFixed(0)+' КБ)</span></div>'});
h+='</div>'}}}}
// Sub-items
if(e.sub)e.sub.forEach(function(s,i){
var sd=getMD(data,e.id,i),hasSub=false;
for(var k in sd){if(sd.hasOwnProperty(k)&&sd[k]&&(sd[k].report||(sd[k].files&&sd[k].files.length)))hasSub=true}
if(!hasSub)return;
h+='<div class="sub-blk"><strong>'+s.l+') '+esc(s.t)+'</strong>';
for(var key in sd){if(sd.hasOwnProperty(key)&&sd[key]){
var d=sd[key];if(d.report||(d.files&&d.files.length)){
h+='<div class="month"><strong>'+M(key)+'</strong>';
if(d.report)h+='<div class="rep">'+esc(d.report)+'</div>';
if(d.files&&d.files.length)d.files.forEach(function(f){h+='<div class="file-item"><a href="'+f.data+'" download="'+esc(f.name)+'">📄 '+esc(f.name)+'</a><span class="sz">'+esc(f.desc||'')+' ('+(f.size/1024).toFixed(0)+' КБ)</span></div>'});
h+='</div>'}}}
h+='</div>'});
h+='</div>';
});
document.getElementById("content").innerHTML=h||'<div class="no-data">Нет данных для отображения</div>';
document.getElementById("dropZone").style.display="none";
}
</script>
</body>
</html>