feat(privacy): fold sanitize into the header "Remove personal data" button
Drops the in-panel sanitize sub-block (button + preview table + "already sanitized" note) that duplicated affordances across the page. The header "Clear Data" button is renamed "Remove personal data" and now sanitizes the uploaded file and downloads the clean copy in one click - no preview, no confirmation. It is shown only when the source can be sanitized, and reads "Personal data removed" (disabled) when the file already looks de-identified. The old clearData() teardown is covered by Restart. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
21670801f3
commit
95f552a0e5
@@ -1047,60 +1047,3 @@ code {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.privacy-sanitize {
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.privacy-sanitize.hidden,
|
||||
.privacy-sanitize-preview.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#privacy-sanitize-btn,
|
||||
.privacy-sanitize-dl {
|
||||
font-size: 0.85em;
|
||||
padding: 5px 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.privacy-sanitize-preview {
|
||||
margin-top: 10px;
|
||||
font-size: 0.88em;
|
||||
}
|
||||
|
||||
.privacy-sanitize-preview table {
|
||||
margin: 6px 0;
|
||||
}
|
||||
|
||||
.privacy-sanitize-dl {
|
||||
margin-top: 8px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.privacy-sanitize-warn {
|
||||
margin-top: 8px;
|
||||
padding: 6px 8px;
|
||||
background: #fff8f0;
|
||||
border: 1px solid #f0e0c0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.privacy-sanitize-warn ul {
|
||||
margin: 4px 0 0;
|
||||
padding-left: 18px;
|
||||
font-family: monospace;
|
||||
font-size: 0.82em;
|
||||
}
|
||||
|
||||
.privacy-sanitize-err {
|
||||
color: var(--crit-fg);
|
||||
}
|
||||
|
||||
.privacy-sanitized-note {
|
||||
padding: 6px 8px;
|
||||
background: #eef7ee;
|
||||
border: 1px solid #cfe6cf;
|
||||
border-radius: 4px;
|
||||
font-size: 0.88em;
|
||||
}
|
||||
|
||||
+26
-98
@@ -1407,7 +1407,6 @@ let auditViewerNonce = 0;
|
||||
async function loadData(vendor, filename) {
|
||||
document.getElementById('upload-section').classList.add('hidden');
|
||||
document.getElementById('data-section').classList.remove('hidden');
|
||||
document.getElementById('clear-btn').classList.remove('hidden');
|
||||
document.getElementById('header-raw-btn').classList.remove('hidden');
|
||||
document.getElementById('header-reanimator-btn').classList.remove('hidden');
|
||||
document.getElementById('header-logs-csv-btn').classList.remove('hidden');
|
||||
@@ -1425,6 +1424,8 @@ async function loadPrivacyScan() {
|
||||
const customer = document.getElementById('privacy-customer');
|
||||
if (!section || !rows) return;
|
||||
|
||||
const clearBtn = document.getElementById('clear-btn');
|
||||
|
||||
let data;
|
||||
try {
|
||||
const resp = await fetch('/api/privacy-scan');
|
||||
@@ -1436,6 +1437,7 @@ async function loadPrivacyScan() {
|
||||
|
||||
if (!data || data.loaded === false) {
|
||||
section.classList.add('hidden');
|
||||
if (clearBtn) clearBtn.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1443,6 +1445,13 @@ async function loadPrivacyScan() {
|
||||
const customers = Array.isArray(data.customers) ? data.customers : [];
|
||||
const san = data.sanitized || null;
|
||||
const alreadySanitized = !!(san && san.detected);
|
||||
|
||||
if (clearBtn) {
|
||||
clearBtn.classList.toggle('hidden', !data.sanitizable);
|
||||
clearBtn.disabled = alreadySanitized;
|
||||
clearBtn.textContent = alreadySanitized ? 'Personal data removed' : 'Remove personal data';
|
||||
}
|
||||
|
||||
if (findings.length === 0 && customers.length === 0 && !alreadySanitized) {
|
||||
section.classList.add('hidden');
|
||||
return;
|
||||
@@ -1500,67 +1509,30 @@ async function loadPrivacyScan() {
|
||||
if (findingsBox) findingsBox.style.display = privacyCollapsed ? 'none' : '';
|
||||
}
|
||||
|
||||
const sanBox = document.getElementById('privacy-sanitize');
|
||||
if (sanBox) {
|
||||
sanBox.classList.toggle('hidden', !data.sanitizable && !alreadySanitized);
|
||||
const prev = document.getElementById('privacy-sanitize-preview');
|
||||
const btn = document.getElementById('privacy-sanitize-btn');
|
||||
if (alreadySanitized) {
|
||||
if (btn) btn.classList.add('hidden');
|
||||
if (prev) {
|
||||
prev.innerHTML = `<div class="privacy-sanitized-note">Файл выглядит уже обезличенным: ` +
|
||||
`${san.markers} маркер(ов) в ${san.files} файл(ах).` +
|
||||
((san.evidence && san.evidence.length)
|
||||
? `<ul class="privacy-evidence">${san.evidence.map(e => `<li>${escapeHtml(e)}</li>`).join('')}</ul>`
|
||||
: '') + `</div>`;
|
||||
prev.classList.remove('hidden');
|
||||
}
|
||||
} else {
|
||||
if (btn) { btn.classList.remove('hidden'); btn.disabled = false; btn.textContent = 'Обезличить и скачать копию'; }
|
||||
if (prev) { prev.classList.add('hidden'); prev.innerHTML = ''; }
|
||||
}
|
||||
}
|
||||
|
||||
section.classList.remove('hidden');
|
||||
}
|
||||
|
||||
async function sanitizePreview() {
|
||||
const btn = document.getElementById('privacy-sanitize-btn');
|
||||
const prev = document.getElementById('privacy-sanitize-preview');
|
||||
if (!btn || !prev) return;
|
||||
// Header "Remove personal data": sanitize the uploaded file and download the
|
||||
// clean copy in one click - no preview, no confirmation.
|
||||
async function removePersonalData() {
|
||||
const btn = document.getElementById('clear-btn');
|
||||
if (!btn || btn.disabled) return;
|
||||
const label = btn.textContent;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Обработка…';
|
||||
let data;
|
||||
btn.textContent = 'Working…';
|
||||
try {
|
||||
const resp = await fetch('/api/sanitize', { method: 'POST' });
|
||||
data = await resp.json();
|
||||
if (!resp.ok) throw new Error(data && data.error ? data.error : 'sanitize failed');
|
||||
if (!resp.ok) {
|
||||
const d = await resp.json().catch(() => ({}));
|
||||
throw new Error(d.error || ('HTTP ' + resp.status));
|
||||
}
|
||||
window.location = '/api/sanitize/download';
|
||||
setTimeout(() => { btn.disabled = false; btn.textContent = label; }, 2000);
|
||||
} catch (e) {
|
||||
prev.innerHTML = `<div class="privacy-sanitize-err">${escapeHtml(String(e.message || e))}</div>`;
|
||||
prev.classList.remove('hidden');
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Обезличить и скачать копию';
|
||||
return;
|
||||
console.error('remove personal data failed:', e);
|
||||
btn.textContent = 'Failed';
|
||||
setTimeout(() => { btn.disabled = false; btn.textContent = label; }, 2500);
|
||||
}
|
||||
|
||||
const changes = Array.isArray(data.changes) ? data.changes : [];
|
||||
const skipped = Array.isArray(data.skipped_binary) ? data.skipped_binary : [];
|
||||
let html = `<p><strong>${data.total_replaced}</strong> замен в <strong>${changes.length}</strong> файл(ах). ` +
|
||||
`Формат, имена и даты записей сохранены; правки той же длины.</p>`;
|
||||
if (changes.length) {
|
||||
html += '<table class="parse-errors-table"><thead><tr><th>Категория</th><th>Кол-во</th><th>Файл</th></tr></thead><tbody>' +
|
||||
changes.map(c => `<tr><td>${escapeHtml(c.category)}</td><td>${c.count}</td><td>${escapeHtml(c.path)}</td></tr>`).join('') +
|
||||
'</tbody></table>';
|
||||
}
|
||||
if (skipped.length) {
|
||||
html += `<div class="privacy-sanitize-warn"><strong>Не удалось отредактировать (обезличьте вручную):</strong><ul>` +
|
||||
skipped.map(s => `<li>${escapeHtml(s)}</li>`).join('') + '</ul></div>';
|
||||
}
|
||||
html += `<button type="button" class="privacy-sanitize-dl" onclick="window.location='/api/sanitize/download'">Скачать обезличенную копию</button>`;
|
||||
prev.innerHTML = html;
|
||||
prev.classList.remove('hidden');
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Пересобрать';
|
||||
}
|
||||
|
||||
// The header + customer summary stay visible; only the findings table collapses.
|
||||
@@ -1667,50 +1639,6 @@ function exportData(format) {
|
||||
window.location.href = `/api/export/${format}`;
|
||||
}
|
||||
|
||||
// Clear data
|
||||
async function clearData() {
|
||||
try {
|
||||
await fetch('/api/clear', { method: 'DELETE' });
|
||||
document.getElementById('upload-section').classList.remove('hidden');
|
||||
document.getElementById('data-section').classList.add('hidden');
|
||||
document.getElementById('clear-btn').classList.add('hidden');
|
||||
document.getElementById('header-raw-btn').classList.add('hidden');
|
||||
document.getElementById('header-reanimator-btn').classList.add('hidden');
|
||||
document.getElementById('header-logs-csv-btn').classList.add('hidden');
|
||||
document.getElementById('header-log-meta').classList.add('hidden');
|
||||
document.getElementById('upload-status').textContent = '';
|
||||
const frame = document.getElementById('audit-viewer-frame');
|
||||
if (frame) {
|
||||
frame.src = 'about:blank';
|
||||
}
|
||||
const parseErrSection = document.getElementById('parse-errors-section');
|
||||
if (parseErrSection) parseErrSection.classList.add('hidden');
|
||||
const parseErrRows = document.getElementById('parse-errors-rows');
|
||||
if (parseErrRows) parseErrRows.innerHTML = '';
|
||||
parseErrorsCollapsed = false;
|
||||
const parseErrBody = document.getElementById('parse-errors-body');
|
||||
if (parseErrBody) parseErrBody.style.display = '';
|
||||
const parseErrToggle = document.getElementById('parse-errors-toggle');
|
||||
if (parseErrToggle) parseErrToggle.textContent = '▲';
|
||||
const privacySection = document.getElementById('privacy-section');
|
||||
if (privacySection) privacySection.classList.add('hidden');
|
||||
const privacyRows = document.getElementById('privacy-rows');
|
||||
if (privacyRows) privacyRows.innerHTML = '';
|
||||
const privacyCustomer = document.getElementById('privacy-customer');
|
||||
if (privacyCustomer) privacyCustomer.innerHTML = '';
|
||||
privacyCollapsed = true;
|
||||
const privacyFindings = document.getElementById('privacy-findings');
|
||||
if (privacyFindings) privacyFindings.style.display = 'none';
|
||||
const privacyToggle = document.getElementById('privacy-toggle');
|
||||
if (privacyToggle) { privacyToggle.textContent = '▼'; privacyToggle.style.visibility = ''; }
|
||||
const privacySan = document.getElementById('privacy-sanitize');
|
||||
if (privacySan) privacySan.classList.add('hidden');
|
||||
const privacySanPrev = document.getElementById('privacy-sanitize-preview');
|
||||
if (privacySanPrev) { privacySanPrev.innerHTML = ''; privacySanPrev.classList.add('hidden'); }
|
||||
} catch (err) {
|
||||
console.error('Failed to clear data:', err);
|
||||
}
|
||||
}
|
||||
|
||||
// Restart app (reload page)
|
||||
function restartApp() {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</div>
|
||||
<div id="header-log-meta" class="header-log-meta hidden">
|
||||
<div class="header-actions">
|
||||
<button id="clear-btn" class="header-action hidden" onclick="clearData()">Clear Data</button>
|
||||
<button id="clear-btn" class="header-action hidden" onclick="removePersonalData()">Remove personal data</button>
|
||||
<button id="header-raw-btn" class="header-action hidden" onclick="exportData('json')">Raw Data</button>
|
||||
<button id="header-reanimator-btn" class="header-action hidden" onclick="exportData('reanimator')">Reanimator</button>
|
||||
<button id="header-logs-csv-btn" class="header-action hidden" onclick="exportData('logs-csv')">Logs Export</button>
|
||||
@@ -166,10 +166,6 @@
|
||||
</div>
|
||||
<div id="privacy-body" class="parse-errors-body">
|
||||
<div id="privacy-customer" class="privacy-customer"></div>
|
||||
<div id="privacy-sanitize" class="privacy-sanitize hidden">
|
||||
<button id="privacy-sanitize-btn" type="button" onclick="sanitizePreview()">Обезличить и скачать копию</button>
|
||||
<div id="privacy-sanitize-preview" class="privacy-sanitize-preview hidden"></div>
|
||||
</div>
|
||||
<div id="privacy-findings" class="privacy-findings" style="display:none">
|
||||
<table class="parse-errors-table">
|
||||
<thead>
|
||||
|
||||
Reference in New Issue
Block a user