(function () { "use strict"; const channelPattern = /^CH(?:0[1-9]|[1-3][0-9]|4[0-8])$/; function mount(options) { // The API independently enforces log.clear. if (!["port", "system", "link"].includes(options.kind) || options.role !== "administrator") { return null; } const system = options.kind === "system"; const link = options.kind === "link"; const doc = options.document; const select = options.channelField; if (!doc || (!system && !link && !select) || !options.parent) { return null; } let busy = false; let disposed = false; function node(tag, className, text) { const element = doc.createElement(tag); element.className = className; element.textContent = text; return element; } const area = node("section", "port-log-clear", ""); area.setAttribute("aria-label", system ? "시스템 로그 관리" : link ? "포트 상태 이벤트 관리" : "포트 로그 관리"); const copy = node("div", "port-log-clear-copy", ""); const target = node("strong", "port-log-clear-target", ""); target.setAttribute("role", "status"); const scope = node("p", "port-log-clear-scope", "기간·검색 조건·표시 개수와 관계없이 선택한 포트의 저장 로그 전체를 삭제합니다. 새 로그 수집은 계속됩니다."); if (system) { scope.textContent = "기간·검색 조건·표시 개수와 관계없이 시스템 이벤트와 감사 기록 전체를 삭제합니다. 포트 로그·포트 상태 이벤트는 보존됩니다."; } if (link) { scope.textContent = "기간·검색 조건·표시 개수와 관계없이 저장된 포트 상태 이벤트 전체를 삭제합니다. 현재 링크 상태와 새 이벤트 수집은 유지됩니다."; } scope.id = "port-log-clear-scope"; const note = node("p", "port-log-clear-note", "다른 포트와 시스템·감사·포트 상태 이벤트는 유지됩니다. 설정 백업에는 로그 원문이 포함되지 않습니다."); if (system) { note.textContent = "새 시스템 로그는 계속 저장되며, 설정 백업에는 로그 원문이 포함되지 않습니다."; } if (link) { note.textContent = "시스템·감사·포트 통신 로그는 유지됩니다. 설정 백업에는 로그 원문이 포함되지 않습니다."; } const button = node("button", "btn danger", "로그 클리어 (삭제)"); button.type = "button"; button.dataset[system ? "systemLogClear" : link ? "linkLogClear" : "portLogClear"] = "true"; button.setAttribute("aria-describedby", scope.id); copy.append(target, scope, note); area.append(copy, button); options.parent.appendChild(area); function update() { const channel = system ? "SYSTEM" : link ? "LINKS" : (select.value || "ALL"); const valid = system || link || channel === "ALL" || channelPattern.test(channel); button.disabled = busy || disposed || !valid; button.textContent = busy ? "삭제 중..." : "로그 클리어 (삭제)"; target.textContent = busy ? "로그를 삭제하고 있습니다." : (valid ? "삭제 대상: " + channel + " · 저장된 통신 로그 전체" : "위 검색 조건에서 포트 1개를 선택하세요."); if (system && !busy) { target.textContent = "삭제 대상: 시스템 이벤트 및 감사 기록 전체"; } if (link && !busy) { target.textContent = "삭제 대상: 포트 상태 이벤트 전체"; } if (!system && !link) { if (channel === "ALL" && !busy) { target.textContent = "삭제 대상: 전체 포트(CH01~CH48) 통신 로그"; } scope.textContent = "기간·검색 조건·표시 개수와 관계없이 " + (channel === "ALL" ? "전체 포트의" : "선택한 포트의") + " 저장 로그 전체를 삭제합니다. 새 로그 수집은 계속됩니다."; note.textContent = (channel === "ALL" ? "시스템·감사·포트 상태 이벤트는 유지됩니다." : "다른 포트와 시스템·감사·포트 상태 이벤트는 유지됩니다.") + " 설정 백업에는 로그 원문이 포함되지 않습니다."; } area.dataset.ready = String(valid); area.setAttribute("aria-busy", String(busy)); } function requestPassword(channel) { return new Promise(function (resolve) { const dialog = doc.createElement("dialog"); dialog.className = "reauth-dialog"; const form = doc.createElement("form"); form.method = "dialog"; const heading = node("h3", "", "로그 삭제 확인"); const description = node("p", "", channel + " 로그 전체를 삭제합니다. 삭제한 기록은 복구할 수 없습니다."); const label = node("label", "", "현재 로그인 계정의 비밀번호"); const input = doc.createElement("input"); input.type = "password"; input.autocomplete = "current-password"; input.required = true; label.appendChild(input); const cancel = node("button", "btn secondary", "취소"); cancel.type = "button"; const submit = node("button", "btn danger", "인증 후 삭제"); submit.type = "submit"; form.append(heading, description, label, cancel, submit); dialog.appendChild(form); doc.body.appendChild(dialog); let result = ""; cancel.addEventListener("click", function () { dialog.close(); }); form.addEventListener("submit", function (event) { event.preventDefault(); result = input.value; input.value = ""; dialog.close(); }); dialog.addEventListener("close", function () { input.value = ""; dialog.remove(); resolve(result); }, { once: true }); dialog.showModal(); input.focus(); }); } async function clear() { // Capture the exact target: changing filters cannot retarget deletion. const channel = system ? "SYSTEM" : link ? "LINKS" : (select.value || "ALL"); if (busy || disposed || options.isDisposed() || (!system && !link && channel !== "ALL" && !channelPattern.test(channel))) { return; } const password = await requestPassword(channel); if (!password || disposed || options.isDisposed()) { return; } busy = true; update(); options.onBusy(); try { const result = await options.send(system || link ? { password: password } : { channel: channel, password: password }); if (!disposed && !options.isDisposed()) { options.onSuccess(channel, result); } } catch (error) { if (!disposed && !options.isDisposed()) { options.onError(error); } } finally { busy = false; update(); // Release page-level refresh guards even after navigation. await options.onSettled(); } } select?.addEventListener("change", update); button.addEventListener("click", clear); update(); return { update: update, dispose: function () { disposed = true; select?.removeEventListener("change", update); button.removeEventListener("click", clear); update(); } }; } const api = { mount: mount }; if (typeof module !== "undefined" && module.exports) { module.exports = api; } if (typeof window !== "undefined") { window.teraPortLogClear = api; } }());