157 lines
7.5 KiB
HTML
157 lines
7.5 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>DrHider — обфускация документов</title>
|
||
<link rel="icon" href="/static/favicon.svg" type="image/svg+xml">
|
||
<style>
|
||
:root {
|
||
--brand: #2563eb; --brand-dark: #1d4ed8;
|
||
--bg: #f8fafc; --card: #fff; --fg: #1e293b; --muted: #64748b;
|
||
--border: #e2e8f0; --red: #ef4444; --green: #22c55e;
|
||
--grey-light: #f3f4f6; --radius: 8px;
|
||
}
|
||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||
body { font: 14px/1.6 -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: var(--bg); color: var(--fg); }
|
||
.header { background: var(--card); border-bottom: 1px solid var(--border); padding: 12px 24px; display: flex; align-items: center; gap: 12px; }
|
||
.header img { height: 24px; }
|
||
.header .sep { color: var(--muted); }
|
||
.header a { color: var(--brand); text-decoration: none; font-size: 13px; }
|
||
.header a:hover { color: var(--brand-dark); }
|
||
main { max-width: 700px; margin: 0 auto; padding: 40px 20px 60px; }
|
||
h1 { font-size: 22px; font-weight: 600; margin-bottom: 4px; }
|
||
.sub { color: var(--muted); font-size: 14px; margin-bottom: 24px; }
|
||
|
||
.card { background: var(--card); border: 1px solid var(--border); border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,.04); }
|
||
.card-header { background: var(--grey-light); padding: 12px 16px; border-radius: 12px 12px 0 0; font-size: 14px; font-weight: 600; border-bottom: 1px solid var(--border); }
|
||
.card-body { padding: 16px; }
|
||
|
||
#dropZone {
|
||
border: 2px dashed var(--border); border-radius: 12px; padding: 36px 20px;
|
||
text-align: center; cursor: pointer; transition: all .2s;
|
||
background: var(--grey-light); margin-bottom: 16px;
|
||
}
|
||
#dropZone:hover, #dropZone.active { border-color: var(--brand); background: #eff6ff; }
|
||
#dropZone p { color: var(--muted); margin: 4px 0; font-size: 14px; }
|
||
#dropZone .icon { font-size: 32px; margin-bottom: 8px; }
|
||
|
||
#fileList { display: none; }
|
||
#fileList.show { display: block; }
|
||
.file-row { display: flex; align-items: center; gap: 10px; padding: 8px 12px; background: var(--grey-light); border-radius: 6px; margin-bottom: 4px; font-size: 13px; }
|
||
.file-row .name { flex: 1; }
|
||
.file-row .size { color: var(--muted); }
|
||
.file-row .remove { cursor: pointer; color: var(--red); font-weight: bold; font-size: 16px; }
|
||
|
||
#status { display: none; margin-top: 12px; padding: 12px 16px; border-radius: 8px; font-size: 13px; }
|
||
#status.show { display: block; }
|
||
#status.progress { background: #eff6ff; border: 1px solid var(--brand); color: var(--brand); }
|
||
#status.done { background: #f0fdf4; border: 1px solid var(--green); color: #16a34a; }
|
||
#status.error { background: #fef2f2; border: 1px solid var(--red); color: var(--red); }
|
||
|
||
.btn { display: inline-flex; align-items: center; justify-content: center; height: 36px; padding: 0 16px; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; border: 1px solid var(--border); background: var(--card); transition: all .15s; }
|
||
.btn:hover { background: var(--grey-light); }
|
||
.btn-primary { background: var(--brand); color: #fff; border-color: var(--brand); width: 100%; margin-top: 8px; }
|
||
.btn-primary:hover { background: var(--brand-dark); }
|
||
.btn-primary:disabled { opacity: .5; cursor: not-allowed; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<header class="header">
|
||
<img src="/static/logo.svg" alt="Nubes">
|
||
<span class="sep">|</span>
|
||
<a href="/">Сверка договоров</a>
|
||
<span class="sep">|</span>
|
||
<strong>DrHider</strong>
|
||
<span style="font-size:11px;color:var(--muted);">v0.9</span>
|
||
</header>
|
||
|
||
<main>
|
||
<h1>🛡️ DrHider — обфускация документов</h1>
|
||
<p class="sub">Загрузите документы — получите ZIP с обезличенными копиями и таблицей соответствия. Без сохранения на сервере.</p>
|
||
|
||
<div class="card">
|
||
<div class="card-header">📁 Загрузка файлов</div>
|
||
<div class="card-body">
|
||
<div id="dropZone">
|
||
<div class="icon">📄</div>
|
||
<p>Выберите файлы или перетащите сюда</p>
|
||
<p style="font-size:12px;">.docx .pdf .doc .zip</p>
|
||
<input type="file" id="fileInput" multiple accept=".docx,.pdf,.doc,.zip" style="display:none">
|
||
</div>
|
||
<div id="fileList"></div>
|
||
<button class="btn btn-primary" id="btnGo" disabled>Обфусцировать</button>
|
||
<div id="status"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card" style="margin-top:16px;">
|
||
<div class="card-header">ℹ️ Как это работает</div>
|
||
<div class="card-body" style="font-size:13px;color:var(--muted);">
|
||
<p><strong>⚡ Всё в памяти.</strong> Файлы не сохраняются на сервер. После обработки данные удаляются.</p>
|
||
<p><strong>📋 mapping.csv</strong> — таблица «оригинал → замена» внутри ZIP.</p>
|
||
<p><strong>🔒 Результат</strong> — обезличенные файлы можно загружать в основной сервис сверки.</p>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
|
||
<script>
|
||
const DZ = document.getElementById('dropZone');
|
||
const INPUT = document.getElementById('fileInput');
|
||
const LIST = document.getElementById('fileList');
|
||
const BTN = document.getElementById('btnGo');
|
||
const STATUS = document.getElementById('status');
|
||
let files = [];
|
||
|
||
DZ.onclick = () => INPUT.click();
|
||
INPUT.onchange = () => { addFiles(INPUT.files); INPUT.value = ''; };
|
||
DZ.ondragover = e => { e.preventDefault(); DZ.classList.add('active'); };
|
||
DZ.ondragleave = () => DZ.classList.remove('active');
|
||
DZ.ondrop = e => { e.preventDefault(); DZ.classList.remove('active'); addFiles(e.dataTransfer.files); };
|
||
|
||
function addFiles(fl) { for (let f of fl) { if (files.find(x => x.name===f.name && x.size===f.size)) continue; files.push(f); } render(); }
|
||
function remove(i) { files.splice(i, 1); render(); }
|
||
function render() {
|
||
LIST.innerHTML = files.map((f,i) => `<div class="file-row"><span class="name">${esc(f.name)}</span><span class="size">${fmt(f.size)}</span><span class="remove" onclick="remove(${i})">×</span></div>`).join('');
|
||
LIST.className = files.length ? 'show' : '';
|
||
BTN.disabled = !files.length;
|
||
}
|
||
|
||
BTN.onclick = async () => {
|
||
if (!files.length) return;
|
||
BTN.disabled = true;
|
||
setStatus('progress', '⏳ Чтение файлов...');
|
||
try {
|
||
const payload = [];
|
||
for (const f of files) {
|
||
const buf = await f.arrayBuffer();
|
||
const b64 = btoa(String.fromCharCode(...new Uint8Array(buf)));
|
||
payload.push({name: f.name, data: b64, type: f.type || ''});
|
||
}
|
||
setStatus('progress', '⏳ Обработка...');
|
||
const r = await fetch('https://contracts.kube5s.ru/api/drhider', {
|
||
method:'POST',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify({files: payload})
|
||
});
|
||
if (!r.ok) throw new Error(await r.text());
|
||
const blob = await r.blob();
|
||
const a = document.createElement('a');
|
||
a.href = URL.createObjectURL(blob);
|
||
a.download = 'drhider_output.zip';
|
||
a.click();
|
||
setStatus('done', '✅ Готово!');
|
||
} catch(e) {
|
||
setStatus('error', '❌ ' + (e.message || 'Ошибка'));
|
||
}
|
||
BTN.disabled = false;
|
||
};
|
||
|
||
function setStatus(c,m) { STATUS.className = 'show '+c; STATUS.textContent = m; }
|
||
function esc(s) { return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
|
||
function fmt(n) { return n>1e6 ? (n/1e6).toFixed(1)+' MB' : n>1e3 ? (n/1e3).toFixed(0)+' KB' : n+' B'; }
|
||
</script>
|
||
</body>
|
||
</html>
|