Добавить статистику и очистку аудита; доработать UI расшифровки.

- Страница /admin/stats: MinIO, pending/consumed, загрузки, audit-метрики
- Кнопка «Очистить» в аудите с подтверждением
- Красивые callout’ы и ошибки пароля; пустой пароль не тратит попытку
- Убрать автоопределение языка; «Расшифруй» в RU UI
This commit is contained in:
Sergey Antropoff
2026-07-18 23:50:44 +03:00
parent 91719740b9
commit 8361b8e3f2
17 changed files with 1007 additions and 258 deletions
+2 -57
View File
@@ -3,16 +3,12 @@
const files = [];
let settings = null;
let captchaWidgetId = null;
let langManual = false;
let detectTimer = null;
const els = {
text: document.getElementById("payload-text"),
lang: document.getElementById("text-language"),
langChip: document.getElementById("lang-chip"),
langChipText: document.getElementById("lang-chip-text"),
langChange: document.getElementById("lang-change"),
langHint: document.getElementById("lang-hint"),
langModal: document.getElementById("lang-modal"),
langGrid: document.getElementById("lang-grid"),
highlight: document.getElementById("code-highlight"),
@@ -57,11 +53,10 @@
return t(`lang.${id}`) || id;
}
function setLanguage(id, { manual = false, silent = false } = {}) {
function setLanguage(id, { silent = false } = {}) {
const lang = window.WrappedHighlight.languages().includes(id) ? id : "plaintext";
els.lang.value = lang;
if (els.langChipText) els.langChipText.textContent = langLabel(lang);
if (manual) langManual = true;
if (!silent) refreshHighlight();
}
@@ -77,49 +72,10 @@
function syncEditorHeight() {
if (!els.text || !els.highlight) return;
els.highlight.parentElement.style.height = "auto";
// Keep highlight pre matching textarea scroll height
const pre = els.highlight.parentElement;
pre.style.minHeight = `${Math.max(220, els.text.scrollHeight)}px`;
}
function updateLangHint(detection) {
if (!els.langHint) return;
if (langManual || !els.text.value.trim()) {
els.langHint.classList.add("hidden");
els.langHint.textContent = "";
return;
}
if (detection.confidence >= 0.6) {
els.langHint.classList.add("hidden");
els.langHint.textContent = "";
return;
}
const suggestions = (detection.suggestions || [])
.filter((l) => l !== detection.lang)
.slice(0, 3)
.map(langLabel);
els.langHint.textContent = suggestions.length
? t("create.languageUnsure", { suggestions: suggestions.join(", ") })
: t("create.languageUnsurePlain");
els.langHint.classList.remove("hidden");
}
function autoDetectLanguage() {
if (langManual) {
refreshHighlight();
return;
}
const detection = window.WrappedHighlight.detectLanguage(els.text.value);
setLanguage(detection.lang, { silent: true });
refreshHighlight();
updateLangHint(detection);
}
function scheduleDetect() {
clearTimeout(detectTimer);
detectTimer = setTimeout(autoDetectLanguage, 220);
}
function openLangModal() {
if (!els.langModal || !els.langGrid) return;
els.langGrid.innerHTML = "";
@@ -129,8 +85,7 @@
btn.className = "lang-option" + (id === els.lang.value ? " active" : "");
btn.textContent = langLabel(id);
btn.addEventListener("click", () => {
setLanguage(id, { manual: true });
updateLangHint({ confidence: 1, suggestions: [] });
setLanguage(id);
closeLangModal();
});
els.langGrid.appendChild(btn);
@@ -283,20 +238,11 @@
if (pasted.length) {
e.preventDefault();
addFiles(pasted);
return;
}
// Text paste into textarea triggers input; detect after paste event
if (document.activeElement === els.text) {
setTimeout(() => {
langManual = false;
autoDetectLanguage();
}, 0);
}
});
els.text.addEventListener("input", () => {
refreshHighlight();
scheduleDetect();
});
els.text.addEventListener("scroll", () => {
const pre = els.highlight.parentElement;
@@ -305,7 +251,6 @@
});
els.langChip?.addEventListener("click", openLangModal);
els.langChange?.addEventListener("click", openLangModal);
els.langModal?.addEventListener("click", (e) => {
if (e.target.closest("[data-lang-close]")) closeLangModal();
});