Update upload interface and ignore temporary E2E artifacts
Deploy drhider / validate (push) Canceled after 0s
Deploy drhider / validate (push) Canceled after 0s
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
- НЕЛЬЗЯ менять ЧТО-ЛИБО при вопросе (?, почему, как, где, что) — только ответ словами
|
- НЕЛЬЗЯ менять ЧТО-ЛИБО при вопросе (?, почему, как, где, что) — только ответ словами
|
||||||
- НЕЛЬЗЯ спешить — обдумать, проверить, потом отвечать
|
- НЕЛЬЗЯ спешить — обдумать, проверить, потом отвечать
|
||||||
- НЕЛЬЗЯ лезть в /home/naeel/nubes/contracts/ (кроме loadtest)
|
- НЕЛЬЗЯ лезть в /home/naeel/nubes/contracts/ (кроме loadtest)
|
||||||
- НЕЛЬЗЯ предполагать — сомневаешься = спроси
|
- НИКОГДА не руководствоваться догадками. Если что-либо неизвестно точно или недоступно — немедленно остановиться и сообщить об этом; не продолжать действие и не выдавать предположение за факт.
|
||||||
- НЕЛЬЗЯ грузить внешние CDN/библиотеки на фронтенд — всё в коде
|
- НЕЛЬЗЯ грузить внешние CDN/библиотеки на фронтенд — всё в коде
|
||||||
|
|
||||||
# ✅ ПОРЯДОК ПРАВКИ
|
# ✅ ПОРЯДОК ПРАВКИ
|
||||||
|
|||||||
@@ -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/
|
||||||
|
|||||||
+475
-158
@@ -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,142 +21,447 @@
|
|||||||
--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; }
|
* {
|
||||||
|
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 {
|
.topbar {
|
||||||
background: var(--card); border-bottom: 1px solid var(--brand-gray);
|
background: var(--card);
|
||||||
padding: 0 16px; height: 48px; display: flex; align-items: center; gap: 10px;
|
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 img {
|
||||||
.topbar .ver { font-weight: 400; color: var(--muted); font-size: 12px; }
|
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">
|
||||||
<span class="title">DrHider</span>
|
<span class="title">DrHider</span>
|
||||||
<span class="ver">v{{ version }}</span>
|
<span class="ver">v{{ version }}</span>
|
||||||
<span class="help" onclick="document.getElementById('helpModal').style.display='flex'">HELP</span>
|
<span class="help" onclick="document.getElementById('helpModal').style.display='flex'">HELP</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">Обфускация документов</div>
|
<div class="card-header">Обфускация документов</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<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>
|
||||||
|
|
||||||
@@ -198,29 +504,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="/file-picker/file-picker.iife.js?v={{ version }}"></script>
|
<script src="/file-picker/file-picker.iife.js?v={{ version }}"></script>
|
||||||
<script>
|
<script>
|
||||||
const DOC_EXTS = ['.pdf', '.doc', '.docx', '.txt', '.md'];
|
const DOC_EXTS = ['.pdf', '.doc', '.docx', '.txt', '.md'];
|
||||||
const ub = document.getElementById('uploadBtn');
|
const ub = document.getElementById('uploadBtn');
|
||||||
const cb = document.getElementById('cancelBtn');
|
const cb = document.getElementById('cancelBtn');
|
||||||
const nb = document.getElementById('newSessionBtn');
|
const nb = document.getElementById('newSessionBtn');
|
||||||
const fc = document.getElementById('fileCount');
|
const fc = document.getElementById('fileCount');
|
||||||
const st = document.getElementById('status');
|
const st = document.getElementById('status');
|
||||||
const db = document.getElementById('dlBtns');
|
const db = document.getElementById('dlBtns');
|
||||||
const MAX_FILE_BYTES = 50 * 1024 * 1024; // 50 МБ на один файл
|
const MAX_FILE_BYTES = 50 * 1024 * 1024; // 50 МБ на один файл
|
||||||
const MAX_SESSION_BYTES = 500 * 1024 * 1024; // 500 МБ суммарно на сессию
|
const MAX_SESSION_BYTES = 500 * 1024 * 1024; // 500 МБ суммарно на сессию
|
||||||
const VM_UPLOAD_URL = 'https://contracts.kube5s.ru/drhider-upload/'; // ВМ-буфер
|
const VM_UPLOAD_URL = 'https://contracts.kube5s.ru/drhider-upload/'; // ВМ-буфер
|
||||||
const TEST_UPLOAD_ONLY = new URLSearchParams(location.search).get('upload-only') === '1';
|
const TEST_UPLOAD_ONLY = new URLSearchParams(location.search).get('upload-only') === '1';
|
||||||
|
|
||||||
let currentSid = '';
|
let currentSid = '';
|
||||||
let activeES = null;
|
let activeES = null;
|
||||||
let abortCtrl = null;
|
let abortCtrl = null;
|
||||||
let busy = false;
|
let busy = false;
|
||||||
let sessionDone = false;
|
let sessionDone = false;
|
||||||
let ptimer = null, liveRefresh = null;
|
let ptimer = null, liveRefresh = null;
|
||||||
|
|
||||||
const picker = FilePicker.initFilePicker({
|
const picker = FilePicker.initFilePicker({
|
||||||
mount: '#filePicker',
|
mount: '#filePicker',
|
||||||
allowedExt: DOC_EXTS,
|
allowedExt: DOC_EXTS,
|
||||||
labels: {
|
labels: {
|
||||||
@@ -242,43 +548,43 @@ const picker = FilePicker.initFilePicker({
|
|||||||
ub.disabled = files.length === 0 || busy;
|
ub.disabled = files.length === 0 || busy;
|
||||||
fc.textContent = files.length ? `${files.length} файл(ов)` : '0 файлов';
|
fc.textContent = files.length ? `${files.length} файл(ов)` : '0 файлов';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function setBusy(b) {
|
function setBusy(b) {
|
||||||
busy = b;
|
busy = b;
|
||||||
document.body.classList.toggle('busy', b);
|
document.body.classList.toggle('busy', b);
|
||||||
}
|
}
|
||||||
|
|
||||||
function fmtSec(s) {
|
function fmtSec(s) {
|
||||||
s = Math.max(0, Math.round(s));
|
s = Math.max(0, Math.round(s));
|
||||||
return s >= 60 ? Math.floor(s / 60) + 'м ' + (s % 60) + 'с' : s + 'с';
|
return s >= 60 ? Math.floor(s / 60) + 'м ' + (s % 60) + 'с' : s + 'с';
|
||||||
}
|
}
|
||||||
|
|
||||||
function confirmCancel() {
|
function confirmCancel() {
|
||||||
document.getElementById('cancelModal').style.display = 'flex';
|
document.getElementById('cancelModal').style.display = 'flex';
|
||||||
}
|
}
|
||||||
|
|
||||||
function doCancel() {
|
function doCancel() {
|
||||||
document.getElementById('cancelModal').style.display = 'none';
|
document.getElementById('cancelModal').style.display = 'none';
|
||||||
if (abortCtrl) {
|
if (abortCtrl) {
|
||||||
abortCtrl.abort();
|
abortCtrl.abort();
|
||||||
}
|
}
|
||||||
if (currentSid) {
|
if (currentSid) {
|
||||||
fetch('/api/cancel/' + currentSid, { method: 'POST' }).catch(() => {});
|
fetch('/api/cancel/' + currentSid, { method: 'POST' }).catch(() => { });
|
||||||
st.textContent = '⏹ Остановка… завершаем текущий файл';
|
st.textContent = '⏹ Остановка… завершаем текущий файл';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function finishProcUI() {
|
function finishProcUI() {
|
||||||
if (activeES) { activeES.close(); activeES = null; }
|
if (activeES) { activeES.close(); activeES = null; }
|
||||||
if (ptimer) { clearInterval(ptimer); ptimer = null; }
|
if (ptimer) { clearInterval(ptimer); ptimer = null; }
|
||||||
if (liveRefresh) { clearInterval(liveRefresh); liveRefresh = null; }
|
if (liveRefresh) { clearInterval(liveRefresh); liveRefresh = null; }
|
||||||
document.getElementById('liveBlock').classList.remove('show');
|
document.getElementById('liveBlock').classList.remove('show');
|
||||||
cb.style.display = 'none';
|
cb.style.display = 'none';
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetAll() {
|
function resetAll() {
|
||||||
if (activeES) { activeES.close(); activeES = null; }
|
if (activeES) { activeES.close(); activeES = null; }
|
||||||
if (abortCtrl) { abortCtrl.abort(); abortCtrl = null; }
|
if (abortCtrl) { abortCtrl.abort(); abortCtrl = null; }
|
||||||
if (ptimer) { clearInterval(ptimer); ptimer = null; }
|
if (ptimer) { clearInterval(ptimer); ptimer = null; }
|
||||||
@@ -295,11 +601,11 @@ function resetAll() {
|
|||||||
document.getElementById('statsBlock').classList.remove('show');
|
document.getElementById('statsBlock').classList.remove('show');
|
||||||
document.getElementById('liveBlock').classList.remove('show');
|
document.getElementById('liveBlock').classList.remove('show');
|
||||||
ub.disabled = true;
|
ub.disabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('beforeunload', () => resetAll());
|
window.addEventListener('beforeunload', () => resetAll());
|
||||||
|
|
||||||
async function uploadFiles() {
|
async function uploadFiles() {
|
||||||
if (busy) return;
|
if (busy) return;
|
||||||
const files = picker.getFiles();
|
const files = picker.getFiles();
|
||||||
if (!files || files.length === 0) return;
|
if (!files || files.length === 0) return;
|
||||||
@@ -376,9 +682,9 @@ async function uploadFiles() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await startProcessingPhase(total, files);
|
await startProcessingPhase(total, files);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function startProcessingPhase(total, files) {
|
async function startProcessingPhase(total, files) {
|
||||||
st.className = 'status progress';
|
st.className = 'status progress';
|
||||||
st.textContent = 'Обработка (этап 2/2)…';
|
st.textContent = 'Обработка (этап 2/2)…';
|
||||||
cb.style.display = 'inline-block';
|
cb.style.display = 'inline-block';
|
||||||
@@ -421,7 +727,7 @@ async function startProcessingPhase(total, files) {
|
|||||||
try {
|
try {
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
activeES = new EventSource('/api/process_stream/' + currentSid);
|
activeES = new EventSource('/api/process_stream/' + currentSid);
|
||||||
activeES.addEventListener('start', function(e) {
|
activeES.addEventListener('start', function (e) {
|
||||||
const d = JSON.parse(e.data);
|
const d = JSON.parse(e.data);
|
||||||
const idx = d.idx;
|
const idx = d.idx;
|
||||||
const curRows = document.querySelectorAll('.tree-row.tree-file');
|
const curRows = document.querySelectorAll('.tree-row.tree-file');
|
||||||
@@ -434,12 +740,12 @@ async function startProcessingPhase(total, files) {
|
|||||||
currentLiveFile = 'Файл ' + (d.idx + 1) + '/' + total + ': ' + d.name;
|
currentLiveFile = 'Файл ' + (d.idx + 1) + '/' + total + ': ' + d.name;
|
||||||
liveFileEl.textContent = currentLiveFile;
|
liveFileEl.textContent = currentLiveFile;
|
||||||
});
|
});
|
||||||
activeES.addEventListener('extract_done', function(e) {
|
activeES.addEventListener('extract_done', function (e) {
|
||||||
procExtractDone = true;
|
procExtractDone = true;
|
||||||
const curRows = document.querySelectorAll('.tree-row.tree-file');
|
const curRows = document.querySelectorAll('.tree-row.tree-file');
|
||||||
curRows.forEach(r => r.classList.remove('row-current'));
|
curRows.forEach(r => r.classList.remove('row-current'));
|
||||||
});
|
});
|
||||||
activeES.addEventListener('file_start', function(e) {
|
activeES.addEventListener('file_start', function (e) {
|
||||||
const d = JSON.parse(e.data);
|
const d = JSON.parse(e.data);
|
||||||
const idx = d.idx;
|
const idx = d.idx;
|
||||||
fileTimers[idx] = performance.now();
|
fileTimers[idx] = performance.now();
|
||||||
@@ -452,12 +758,12 @@ async function startProcessingPhase(total, files) {
|
|||||||
}
|
}
|
||||||
liveFileEl.textContent = 'Файл ' + (d.idx + 1) + '/' + total + ': ' + d.name;
|
liveFileEl.textContent = 'Файл ' + (d.idx + 1) + '/' + total + ': ' + d.name;
|
||||||
});
|
});
|
||||||
activeES.addEventListener('file_chunk', function(e) {
|
activeES.addEventListener('file_chunk', function (e) {
|
||||||
const d = JSON.parse(e.data);
|
const d = JSON.parse(e.data);
|
||||||
liveFileEl.textContent = 'Файл ' + (d.idx + 1) + '/' + total + ': ' + d.name +
|
liveFileEl.textContent = 'Файл ' + (d.idx + 1) + '/' + total + ': ' + d.name +
|
||||||
(typeof d.eta_sec === 'number' ? ' — осталось ~' + fmtSec(d.eta_sec) : '');
|
(typeof d.eta_sec === 'number' ? ' — осталось ~' + fmtSec(d.eta_sec) : '');
|
||||||
});
|
});
|
||||||
activeES.addEventListener('file_done', function(e) {
|
activeES.addEventListener('file_done', function (e) {
|
||||||
const d = JSON.parse(e.data);
|
const d = JSON.parse(e.data);
|
||||||
const idx = d.idx;
|
const idx = d.idx;
|
||||||
const curRows = document.querySelectorAll('.tree-row.tree-file');
|
const curRows = document.querySelectorAll('.tree-row.tree-file');
|
||||||
@@ -467,7 +773,7 @@ async function startProcessingPhase(total, files) {
|
|||||||
if (td) td.innerHTML = '✅ готов';
|
if (td) td.innerHTML = '✅ готов';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
activeES.addEventListener('llm', function(e) {
|
activeES.addEventListener('llm', function (e) {
|
||||||
const d = JSON.parse(e.data);
|
const d = JSON.parse(e.data);
|
||||||
if (d.active) {
|
if (d.active) {
|
||||||
liveLlmTime.textContent = d.elapsed + ' с';
|
liveLlmTime.textContent = d.elapsed + ' с';
|
||||||
@@ -480,7 +786,7 @@ async function startProcessingPhase(total, files) {
|
|||||||
if (currentLiveFile) liveFileEl.textContent = currentLiveFile;
|
if (currentLiveFile) liveFileEl.textContent = currentLiveFile;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
activeES.addEventListener('done', function(e) {
|
activeES.addEventListener('done', function (e) {
|
||||||
const d = JSON.parse(e.data);
|
const d = JSON.parse(e.data);
|
||||||
const idx = d.idx;
|
const idx = d.idx;
|
||||||
const curRows = document.querySelectorAll('.tree-row.tree-file');
|
const curRows = document.querySelectorAll('.tree-row.tree-file');
|
||||||
@@ -499,7 +805,7 @@ async function startProcessingPhase(total, files) {
|
|||||||
if (td) td.innerHTML = '✅ ' + sec + 'с';
|
if (td) td.innerHTML = '✅ ' + sec + 'с';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
activeES.addEventListener('cancelled', function(e) {
|
activeES.addEventListener('cancelled', function (e) {
|
||||||
const d = JSON.parse(e.data);
|
const d = JSON.parse(e.data);
|
||||||
finishProcUI();
|
finishProcUI();
|
||||||
const totalSec = ((performance.now() - t0) / 1000).toFixed(1);
|
const totalSec = ((performance.now() - t0) / 1000).toFixed(1);
|
||||||
@@ -521,7 +827,7 @@ async function startProcessingPhase(total, files) {
|
|||||||
nb.style.display = 'inline-block';
|
nb.style.display = 'inline-block';
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
activeES.addEventListener('complete', function(e) {
|
activeES.addEventListener('complete', function (e) {
|
||||||
finishProcUI();
|
finishProcUI();
|
||||||
const d = JSON.parse(e.data);
|
const d = JSON.parse(e.data);
|
||||||
const totalSec = ((performance.now() - t0) / 1000).toFixed(1);
|
const totalSec = ((performance.now() - t0) / 1000).toFixed(1);
|
||||||
@@ -543,7 +849,7 @@ async function startProcessingPhase(total, files) {
|
|||||||
nb.style.display = 'inline-block';
|
nb.style.display = 'inline-block';
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
activeES.addEventListener('proc_error', function(e) {
|
activeES.addEventListener('proc_error', function (e) {
|
||||||
const d = JSON.parse(e.data);
|
const d = JSON.parse(e.data);
|
||||||
finishProcUI();
|
finishProcUI();
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
@@ -551,7 +857,7 @@ async function startProcessingPhase(total, files) {
|
|||||||
st.textContent = 'Ошибка обработки: ' + (d.error || 'неизвестная ошибка');
|
st.textContent = 'Ошибка обработки: ' + (d.error || 'неизвестная ошибка');
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
activeES.onerror = function() {
|
activeES.onerror = function () {
|
||||||
finishProcUI();
|
finishProcUI();
|
||||||
reject(new Error('SSE connection failed'));
|
reject(new Error('SSE connection failed'));
|
||||||
};
|
};
|
||||||
@@ -563,9 +869,9 @@ async function startProcessingPhase(total, files) {
|
|||||||
st.textContent = 'Ошибка: ' + err.message;
|
st.textContent = 'Ошибка: ' + err.message;
|
||||||
}
|
}
|
||||||
if (!sessionDone) ub.disabled = false;
|
if (!sessionDone) ub.disabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadZip() {
|
function downloadZip() {
|
||||||
if (!currentSid) return;
|
if (!currentSid) return;
|
||||||
fetch('/api/download/' + currentSid)
|
fetch('/api/download/' + currentSid)
|
||||||
.then(r => {
|
.then(r => {
|
||||||
@@ -582,9 +888,9 @@ function downloadZip() {
|
|||||||
document.body.removeChild(a);
|
document.body.removeChild(a);
|
||||||
URL.revokeObjectURL(a.href);
|
URL.revokeObjectURL(a.href);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadCsv() {
|
function downloadCsv() {
|
||||||
if (!currentSid) return;
|
if (!currentSid) return;
|
||||||
fetch('/api/csv/' + currentSid)
|
fetch('/api/csv/' + currentSid)
|
||||||
.then(r => {
|
.then(r => {
|
||||||
@@ -601,30 +907,40 @@ function downloadCsv() {
|
|||||||
document.body.removeChild(a);
|
document.body.removeChild(a);
|
||||||
URL.revokeObjectURL(a.href);
|
URL.revokeObjectURL(a.href);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
window.uploadFiles = uploadFiles;
|
window.uploadFiles = uploadFiles;
|
||||||
window.resetAll = resetAll;
|
window.resetAll = resetAll;
|
||||||
window.confirmCancel = confirmCancel;
|
window.confirmCancel = confirmCancel;
|
||||||
window.doCancel = doCancel;
|
window.doCancel = doCancel;
|
||||||
window.downloadZip = downloadZip;
|
window.downloadZip = downloadZip;
|
||||||
window.downloadCsv = downloadCsv;
|
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">
|
||||||
Будут сохранены: полностью обработанные файлы и таблица замен.
|
Будут сохранены: полностью обработанные файлы и таблица замен.
|
||||||
@@ -636,6 +952,7 @@ fetch('/health').catch(() => {});
|
|||||||
<button class="btn btn-primary" onclick="doCancel()">Остановить</button>
|
<button class="btn btn-primary" onclick="doCancel()">Остановить</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user