Update upload interface and ignore temporary E2E artifacts
Deploy drhider / validate (push) Canceled after 0s

This commit is contained in:
Repinoid
2026-09-16 07:42:31 +03:00
parent 49bb4d70d8
commit 52da9a3923
3 changed files with 860 additions and 541 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
- НЕЛЬЗЯ менять ЧТО-ЛИБО при вопросе (?, почему, как, где, что) — только ответ словами - НЕЛЬЗЯ менять ЧТО-ЛИБО при вопросе (?, почему, как, где, что) — только ответ словами
- НЕЛЬЗЯ спешить — обдумать, проверить, потом отвечать - НЕЛЬЗЯ спешить — обдумать, проверить, потом отвечать
- НЕЛЬЗЯ лезть в /home/naeel/nubes/contracts/ (кроме loadtest) - НЕЛЬЗЯ лезть в /home/naeel/nubes/contracts/ (кроме loadtest)
- НЕЛЬЗЯ предполагать — сомневаешься = спроси - НИКОГДА не руководствоваться догадками. Если что-либо неизвестно точно или недоступно — немедленно остановиться и сообщить об этом; не продолжать действие и не выдавать предположение за факт.
- НЕЛЬЗЯ грузить внешние CDN/библиотеки на фронтенд — всё в коде - НЕЛЬЗЯ грузить внешние CDN/библиотеки на фронтенд — всё в коде
# ✅ ПОРЯДОК ПРАВКИ # ✅ ПОРЯДОК ПРАВКИ
+2
View File
@@ -26,6 +26,8 @@ dist/
# about1500.md # about1500.md
*.har *.har
chrome-net-export-log.json chrome-net-export-log.json
History/tests/2026-09-15-*.md
tests/e2e_mass_processing.py
TSTFILES/ TSTFILES/
files/ files/
upload-platform/ upload-platform/
+407 -90
View File
@@ -1,5 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="ru"> <html lang="ru">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -20,127 +21,430 @@
--red: #ef4444; --red: #ef4444;
--green: #22c55e; --green: #22c55e;
} }
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font: 14px system-ui, sans-serif; color: var(--text); background: var(--bg); min-height: 100vh; } * {
.topbar { box-sizing: border-box;
background: var(--card); border-bottom: 1px solid var(--brand-gray); margin: 0;
padding: 0 16px; height: 48px; display: flex; align-items: center; gap: 10px; padding: 0;
} }
.topbar img { height: 18px; }
.topbar .title { font-weight: 600; font-size: 14px; } body {
.topbar .ver { font-weight: 400; color: var(--muted); font-size: 12px; } font: 14px system-ui, sans-serif;
color: var(--text);
background: var(--bg);
min-height: 100vh;
}
.topbar {
background: var(--card);
border-bottom: 1px solid var(--brand-gray);
padding: 0 16px;
height: 48px;
display: flex;
align-items: center;
gap: 10px;
}
.topbar img {
height: 18px;
}
.topbar .title {
font-weight: 600;
font-size: 14px;
}
.topbar .ver {
font-weight: 400;
color: var(--muted);
font-size: 12px;
}
.topbar .help { .topbar .help {
margin-left: auto; cursor: pointer; text-decoration: none; user-select: none; margin-left: auto;
display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
padding: 5px 14px; border-radius: 6px; text-decoration: none;
background: #1d4ed8; color: #fff; font-size: 13px; font-weight: 700; line-height: 1; user-select: none;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 5px 14px;
border-radius: 6px;
background: #1d4ed8;
color: #fff;
font-size: 13px;
font-weight: 700;
line-height: 1;
letter-spacing: .06em; letter-spacing: .06em;
box-shadow: 0 1px 3px rgba(0, 0, 0, .25); box-shadow: 0 1px 3px rgba(0, 0, 0, .25);
} }
.topbar .help:hover { background: #1e40af; color: #fff; }
.content { max-width: 820px; margin: 20px auto; padding: 0 12px; } .topbar .help:hover {
background: #1e40af;
color: #fff;
}
.content {
max-width: 820px;
margin: 20px auto;
padding: 0 12px;
}
.card { .card {
background: var(--card); border-radius: 12px; background: var(--card);
border: 1px solid var(--brand-gray); box-shadow: 0 1px 2px rgba(0,0,0,.04); border-radius: 12px;
border: 1px solid var(--brand-gray);
box-shadow: 0 1px 2px rgba(0, 0, 0, .04);
} }
.card-header { .card-header {
background: var(--brand-grey-light); padding: 10px 12px; background: var(--brand-grey-light);
font-weight: 600; font-size: 14px; border-bottom: 1px solid var(--brand-gray); padding: 10px 12px;
font-weight: 600;
font-size: 14px;
border-bottom: 1px solid var(--brand-gray);
}
.card-body {
padding: 12px;
}
.sub {
font-size: 12px;
color: var(--muted);
margin-bottom: 10px;
line-height: 1.5;
} }
.card-body { padding: 12px; }
.sub { font-size: 12px; color: var(--muted); margin-bottom: 10px; line-height: 1.5; }
/* FilePicker styling */ /* FilePicker styling */
.file-picker { font-family: inherit; color: var(--text); } .file-picker {
.file-picker .fp-toolbar { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; } font-family: inherit;
color: var(--text);
}
.file-picker .fp-toolbar {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 10px;
}
.file-picker .fp-toolbar button { .file-picker .fp-toolbar button {
height: 32px; border-radius: 6px; padding: 0 12px; font-size: 13px; height: 32px;
font-family: inherit; cursor: pointer; border: 1px solid var(--brand-gray); border-radius: 6px;
background: var(--card); color: var(--text); padding: 0 12px;
display: inline-flex; align-items: center; gap: 5px; font-weight: 500; font-size: 13px;
font-family: inherit;
cursor: pointer;
border: 1px solid var(--brand-gray);
background: var(--card);
color: var(--text);
display: inline-flex;
align-items: center;
gap: 5px;
font-weight: 500;
} }
.file-picker .fp-toolbar button#fp-files-btn { background: #eff6ff; border-color: var(--brand-primary); color: var(--brand-primary); }
.file-picker .fp-toolbar button#fp-folder-btn { background: var(--card); } .file-picker .fp-toolbar button#fp-files-btn {
.file-picker .fp-toolbar button#fp-clear-btn { color: var(--muted); border-color: transparent; } background: #eff6ff;
.file-picker .fp-toolbar button#fp-clear-btn:hover { color: var(--red); } border-color: var(--brand-primary);
.file-picker .fp-table-wrap { border: 1px solid var(--brand-gray); border-radius: 8px; overflow: hidden; margin-bottom: 6px; } color: var(--brand-primary);
.file-picker .fp-table { width: 100%; border-collapse: collapse; font-size: 13px; } }
.file-picker .fp-toolbar button#fp-folder-btn {
background: var(--card);
}
.file-picker .fp-toolbar button#fp-clear-btn {
color: var(--muted);
border-color: transparent;
}
.file-picker .fp-toolbar button#fp-clear-btn:hover {
color: var(--red);
}
.file-picker .fp-table-wrap {
border: 1px solid var(--brand-gray);
border-radius: 8px;
overflow: hidden;
margin-bottom: 6px;
}
.file-picker .fp-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.file-picker .fp-table th { .file-picker .fp-table th {
background: var(--brand-grey-light); text-transform: uppercase; padding: 6px 8px; background: var(--brand-grey-light);
border-right: 1px solid var(--brand-gray); border-bottom: 1px solid var(--brand-gray); text-transform: uppercase;
text-align: left; font-weight: 600; font-size: 10px; color: var(--muted); padding: 6px 8px;
border-right: 1px solid var(--brand-gray);
border-bottom: 1px solid var(--brand-gray);
text-align: left;
font-weight: 600;
font-size: 10px;
color: var(--muted);
} }
.file-picker .fp-table td { .file-picker .fp-table td {
padding: 5px 8px; border-right: 1px solid var(--brand-gray); border-bottom: 1px solid var(--brand-gray); padding: 5px 8px;
border-right: 1px solid var(--brand-gray);
border-bottom: 1px solid var(--brand-gray);
} }
.file-picker .tree-row:hover td { background: rgba(37,99,235,.03); }
.file-picker .tree-row.row-current td { background: rgba(37,99,235,.12) !important; color: #1d4ed8; font-weight: 600; } .file-picker .tree-row:hover td {
background: rgba(37, 99, 235, .03);
}
.file-picker .tree-row.row-current td {
background: rgba(37, 99, 235, .12) !important;
color: #1d4ed8;
font-weight: 600;
}
.file-picker .tree-name { .file-picker .tree-name {
display: inline-flex; align-items: center; gap: 6px; border: 0; background: transparent; display: inline-flex;
color: inherit; font: inherit; text-align: left; cursor: pointer; align-items: center;
gap: 6px;
border: 0;
background: transparent;
color: inherit;
font: inherit;
text-align: left;
cursor: pointer;
} }
.file-picker .tree-chevron { display: inline-block; width: 12px; color: var(--muted); font-size: 9px; }
.file-picker .fp-status { font-size: 12px; color: var(--muted); margin: 4px 0; } .file-picker .tree-chevron {
.file-picker .fp-count { display: none; } display: inline-block;
width: 12px;
color: var(--muted);
font-size: 9px;
}
.file-picker .fp-status {
font-size: 12px;
color: var(--muted);
margin: 4px 0;
}
.file-picker .fp-count {
display: none;
}
.file-picker .remove-btn { .file-picker .remove-btn {
cursor: pointer; color: #f87171; background: none; border: none; cursor: pointer;
padding: 2px 4px; font-size: 14px; line-height: 1; color: #f87171;
background: none;
border: none;
padding: 2px 4px;
font-size: 14px;
line-height: 1;
}
.file-picker .remove-btn:hover {
color: var(--red);
}
body.busy .file-picker button {
pointer-events: none;
opacity: 0.5;
}
body.busy .file-picker .remove-btn {
display: none;
} }
.file-picker .remove-btn:hover { color: var(--red); }
body.busy .file-picker button { pointer-events: none; opacity: 0.5; }
body.busy .file-picker .remove-btn { display: none; }
.footer-bar { .footer-bar {
margin-top: 10px; display: flex; justify-content: space-between; align-items: center; margin-top: 10px;
font-size: 12px; color: var(--muted); display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
color: var(--muted);
} }
.btn { .btn {
height: 32px; border-radius: 6px; padding: 0 12px; font-size: 13px; height: 32px;
font-family: inherit; cursor: pointer; border: 1px solid var(--brand-gray); border-radius: 6px;
background: var(--card); color: var(--text); padding: 0 12px;
display: inline-flex; align-items: center; gap: 5px; font-size: 13px;
font-family: inherit;
cursor: pointer;
border: 1px solid var(--brand-gray);
background: var(--card);
color: var(--text);
display: inline-flex;
align-items: center;
gap: 5px;
} }
.btn-primary { background: var(--brand-primary); border-color: var(--brand-primary); color: #fff; }
.btn-primary:hover { opacity: .9; } .btn-primary {
.btn:disabled { opacity: .4; cursor: not-allowed; } background: var(--brand-primary);
.status { margin-top: 10px; padding: 8px 12px; border-radius: 8px; font-size: 13px; display: none; } border-color: var(--brand-primary);
.status.progress { background: #eff6ff; border: 1px solid var(--brand-primary); color: var(--brand-primary); display: block; } color: #fff;
.status.done { background: #f0fdf4; border: 1px solid var(--green); color: #16a34a; display: block; } }
.status.error { background: #fef2f2; border: 1px solid var(--red); color: #991b1b; display: block; }
.dl-btns { display: none; gap: 8px; margin-top: 8px; } .btn-primary:hover {
.dl-btns.show { display: flex; } opacity: .9;
}
.btn:disabled {
opacity: .4;
cursor: not-allowed;
}
.status {
margin-top: 10px;
padding: 8px 12px;
border-radius: 8px;
font-size: 13px;
display: none;
}
.status.progress {
background: #eff6ff;
border: 1px solid var(--brand-primary);
color: var(--brand-primary);
display: block;
}
.status.done {
background: #f0fdf4;
border: 1px solid var(--green);
color: #16a34a;
display: block;
}
.status.error {
background: #fef2f2;
border: 1px solid var(--red);
color: #991b1b;
display: block;
}
.dl-btns {
display: none;
gap: 8px;
margin-top: 8px;
}
.dl-btns.show {
display: flex;
}
.stats-block { .stats-block {
display: none; margin-top: 12px; padding: 16px; display: none;
margin-top: 12px;
padding: 16px;
background: linear-gradient(135deg, #f0fdf4, #ecfdf5); background: linear-gradient(135deg, #f0fdf4, #ecfdf5);
border: 2px solid var(--green); border-radius: 12px; border: 2px solid var(--green);
border-radius: 12px;
} }
.stats-block.show { display: block; }
.stats-title { font-size: 16px; font-weight: 700; color: #14532d; margin-bottom: 10px; } .stats-block.show {
.stats-row { display: flex; flex-wrap: wrap; gap: 10px; } display: block;
}
.stats-title {
font-size: 16px;
font-weight: 700;
color: #14532d;
margin-bottom: 10px;
}
.stats-row {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.stat-item { .stat-item {
flex: 1; min-width: 130px; background: #fff; border: 1px solid #bbf7d0; flex: 1;
border-radius: 10px; padding: 10px 12px; text-align: center; min-width: 130px;
background: #fff;
border: 1px solid #bbf7d0;
border-radius: 10px;
padding: 10px 12px;
text-align: center;
} }
.stat-label { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); margin-bottom: 4px; }
.stat-value { font-size: 24px; font-weight: 800; color: #166534; } .stat-label {
font-size: 12px;
text-transform: uppercase;
letter-spacing: .04em;
color: var(--muted);
margin-bottom: 4px;
}
.stat-value {
font-size: 24px;
font-weight: 800;
color: #166534;
}
.live-block { .live-block {
display: none; margin-top: 12px; padding: 16px; display: none;
margin-top: 12px;
padding: 16px;
background: linear-gradient(135deg, #eff6ff, #dbeafe); background: linear-gradient(135deg, #eff6ff, #dbeafe);
border: 2px solid var(--brand-primary); border-radius: 12px; border: 2px solid var(--brand-primary);
border-radius: 12px;
} }
.live-block.show { display: block; }
.live-title { font-size: 18px; font-weight: 800; color: #1e40af; margin-bottom: 6px; } .live-block.show {
.live-file { font-size: 15px; color: #1e3a8a; margin-bottom: 10px; word-break: break-word; } display: block;
.live-timer { font-size: 40px; font-weight: 800; color: #1d4ed8; line-height: 1.1; } }
.live-title {
font-size: 18px;
font-weight: 800;
color: #1e40af;
margin-bottom: 6px;
}
.live-file {
font-size: 15px;
color: #1e3a8a;
margin-bottom: 10px;
word-break: break-word;
}
.live-timer {
font-size: 40px;
font-weight: 800;
color: #1d4ed8;
line-height: 1.1;
}
.live-llm { .live-llm {
display: none; margin-top: 8px; padding: 8px 12px; border-radius: 8px; display: none;
background: #fef3c7; border: 1px solid #f59e0b; color: #92400e; margin-top: 8px;
font-size: 18px; font-weight: 700; width: fit-content; padding: 8px 12px;
border-radius: 8px;
background: #fef3c7;
border: 1px solid #f59e0b;
color: #92400e;
font-size: 18px;
font-weight: 700;
width: fit-content;
}
.live-llm.show {
display: inline-block;
}
.live-eta {
margin-top: 8px;
font-size: 14px;
color: #1d4ed8;
font-weight: 600;
} }
.live-llm.show { display: inline-block; }
.live-eta { margin-top: 8px; font-size: 14px; color: #1d4ed8; font-weight: 600; }
</style> </style>
</head> </head>
<body> <body>
<div class="topbar"> <div class="topbar">
<img src="/static/logo.svg" alt="Nubes"> <img src="/static/logo.svg" alt="Nubes">
@@ -155,7 +459,9 @@
<p class="sub"> <p class="sub">
Загрузите документы (.docx, .doc, .pdf, .txt, .md, .zip) — получите ZIP с обезличенными копиями.<br> Загрузите документы (.docx, .doc, .pdf, .txt, .md, .zip) — получите ZIP с обезличенными копиями.<br>
CSV с таблицей замен скачивается отдельно.<br> CSV с таблицей замен скачивается отдельно.<br>
<span style="color:#c0392b;">Ограничения: один файл не более 50 МБ, суммарно не более 500 МБ. Файлы сверх лимитов помечаются статусом «пропущен» и не участвуют в обфускации. Пустые, повреждённые файлы и сканы без текстового слоя получают статус «не извлечён» — они тоже не попадают в результат.</span><br> <span style="color:#c0392b;">Ограничения: один файл не более 50 МБ, суммарно не более 500 МБ. Файлы сверх
лимитов помечаются статусом «пропущен» и не участвуют в обфускации. Пустые, повреждённые файлы и сканы без
текстового слоя получают статус «не извлечён» — они тоже не попадают в результат.</span><br>
<span style="color:#7d3c98;">⏱ Время обработки каждого файла — ориентировочное (обновляется по факту).</span> <span style="color:#7d3c98;">⏱ Время обработки каждого файла — ориентировочное (обновляется по факту).</span>
</p> </p>
@@ -612,19 +918,29 @@ window.downloadCsv = downloadCsv;
fetch('/health').catch(() => { }); fetch('/health').catch(() => { });
</script> </script>
<div id="helpModal" style="display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.5);z-index:999;justify-content:center;align-items:center" onclick="this.style.display='none'"> <div id="helpModal"
<div style="background:var(--card);border-radius:12px;padding:24px;max-width:420px;box-shadow:0 4px 24px rgba(0,0,0,.15)" onclick="event.stopPropagation()"> style="display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.5);z-index:999;justify-content:center;align-items:center"
onclick="this.style.display='none'">
<div
style="background:var(--card);border-radius:12px;padding:24px;max-width:420px;box-shadow:0 4px 24px rgba(0,0,0,.15)"
onclick="event.stopPropagation()">
<h3 style="margin-bottom:12px">DrHider — обфускация документов</h3> <h3 style="margin-bottom:12px">DrHider — обфускация документов</h3>
<p style="font-size:13px;color:var(--muted);line-height:1.6"> <p style="font-size:13px;color:var(--muted);line-height:1.6">
Загрузите документы (.docx, .doc, .pdf, .txt, .md, .zip) — получите ZIP-архив с обезличенными копиями. Загрузите документы (.docx, .doc, .pdf, .txt, .md, .zip) — получите ZIP-архив с обезличенными копиями.
Персональные данные, реквизиты компаний, телефоны, email и другие приватные сведения заменяются на обезличенные коды. Персональные данные, реквизиты компаний, телефоны, email и другие приватные сведения заменяются на обезличенные
коды.
CSV-файл с таблицей соответствия оригинал→замена скачивается отдельно. CSV-файл с таблицей соответствия оригинал→замена скачивается отдельно.
</p> </p>
<button class="btn" style="margin-top:12px;width:100%" onclick="document.getElementById('helpModal').style.display='none'">Закрыть</button> <button class="btn" style="margin-top:12px;width:100%"
onclick="document.getElementById('helpModal').style.display='none'">Закрыть</button>
</div> </div>
</div> </div>
<div id="cancelModal" style="display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.5);z-index:999;justify-content:center;align-items:center" onclick="this.style.display='none'"> <div id="cancelModal"
<div style="background:var(--card);border-radius:12px;padding:24px;max-width:440px;box-shadow:0 4px 24px rgba(0,0,0,.15)" onclick="event.stopPropagation()"> style="display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.5);z-index:999;justify-content:center;align-items:center"
onclick="this.style.display='none'">
<div
style="background:var(--card);border-radius:12px;padding:24px;max-width:440px;box-shadow:0 4px 24px rgba(0,0,0,.15)"
onclick="event.stopPropagation()">
<h3 style="margin-bottom:12px">⏹ Остановить обработку?</h3> <h3 style="margin-bottom:12px">⏹ Остановить обработку?</h3>
<p style="font-size:13px;color:var(--muted);line-height:1.6" id="cancelModalText"> <p style="font-size:13px;color:var(--muted);line-height:1.6" id="cancelModalText">
Будут сохранены: полностью обработанные файлы и таблица замен. Будут сохранены: полностью обработанные файлы и таблица замен.
@@ -638,4 +954,5 @@ fetch('/health').catch(() => {});
</div> </div>
</div> </div>
</body> </body>
</html> </html>