['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { document.addEventListener(eventName, e => { e.preventDefault(); }); }); document.addEventListener('DOMContentLoaded', () => { const { dict, langMap } = window.AppI18n; const appState = { lang: "english_switch", theme: "system_switch", scanning: false, firstLaunch: false, taskmgrTimer: null, taskmgrActive: false, netmonTimer: null, netmonActive: false, virusMap: new Map(), virusResults: [], junkList: [] }; const reverseI18nMap = Object.entries(dict["traditional_switch"]).reduce((acc, [key, val]) => { acc[val] = key; return acc; }, {}); const initI18nKeys = () => { document.querySelectorAll("[data-i18n]").forEach(el => { let key = el.getAttribute('data-i18n'); if (!key) { const text = el.textContent.trim(); key = reverseI18nMap[text] || text; el.setAttribute('data-i18n', key); } el.dataset.originText = el.textContent.trim(); }); document.querySelectorAll("[data-i18n-placeholder]").forEach(el => { let key = el.getAttribute('data-i18n-placeholder'); if (reverseI18nMap[key]) { el.setAttribute('data-i18n-placeholder', reverseI18nMap[key]); } el.dataset.originPlaceholder = el.getAttribute('placeholder'); }); document.querySelectorAll("option[data-i18n]").forEach(el => { let key = el.getAttribute('data-i18n'); if (!key) { const text = el.textContent.trim(); key = reverseI18nMap[text] || text; el.setAttribute('data-i18n', key); } }); }; let openCustomSelect = null; const closeCustomSelect = () => { if (!openCustomSelect) return; const { wrapper, menu } = openCustomSelect; wrapper.classList.remove('open'); menu.classList.remove('open'); menu.removeAttribute('style'); if (wrapper.isConnected) wrapper.appendChild(menu); else menu.remove(); openCustomSelect = null; }; const positionCustomSelect = () => { if (!openCustomSelect) return; const { wrapper, trigger, menu } = openCustomSelect; const rect = trigger.getBoundingClientRect(); const viewport = window.visualViewport; const leftEdge = (viewport?.offsetLeft || 0) + 8; const topEdge = (viewport?.offsetTop || 0) + 8; const rightEdge = leftEdge + (viewport?.width || document.documentElement.clientWidth) - 16; const bottomEdge = topEdge + (viewport?.height || document.documentElement.clientHeight) - 16; if (!wrapper.isConnected || !trigger.getClientRects().length || getComputedStyle(trigger).visibility === 'hidden' || rect.bottom <= topEdge || rect.top >= bottomEdge || rect.right <= leftEdge || rect.left >= rightEdge) { closeCustomSelect(); return; } const above = Math.max(0, rect.top - topEdge - 8); const below = Math.max(0, bottomEdge - rect.bottom - 8); const opensUp = above > below; const available = opensUp ? above : below; if (available < 32) { closeCustomSelect(); return; } menu.style.maxHeight = `${Math.min(260, available)}px`; menu.style.maxWidth = `${rightEdge - leftEdge}px`; menu.style.minWidth = `${Math.min(rect.width, rightEdge - leftEdge)}px`; menu.style.setProperty('--select-enter-offset', opensUp ? '4px' : '-4px'); menu.style.left = `${Math.max(leftEdge, Math.min(rect.left, rightEdge - menu.offsetWidth))}px`; menu.style.top = `${opensUp ? rect.top - 8 - menu.offsetHeight : rect.bottom + 8}px`; }; document.addEventListener('click', closeCustomSelect); document.addEventListener('keydown', (event) => { if (event.key === 'Escape') closeCustomSelect(); }); document.addEventListener('scroll', (event) => { if (openCustomSelect && !openCustomSelect.menu.contains(event.target)) closeCustomSelect(); }, true); window.addEventListener('resize', positionCustomSelect); window.visualViewport?.addEventListener('resize', positionCustomSelect); window.visualViewport?.addEventListener('scroll', positionCustomSelect); const getMsg = (key) => (dict[appState.lang] || dict["english_switch"])[key] || key; let startupOverlayClosed = false; const closeStartupOverlay = () => { if (startupOverlayClosed) return; startupOverlayClosed = true; const overlay = document.getElementById('loading_overlay'); if (overlay && !overlay.classList.contains('fade-out')) { overlay.classList.add('fade-out'); } setTimeout(() => { document.querySelector('.app-container')?.classList.add('fade-in'); }, 400); }; const showStartupError = (message) => { const overlay = document.getElementById('loading_overlay'); if (!overlay || startupOverlayClosed) return; overlay.classList.add('startup-error'); overlay.innerHTML = `

PYAS failed to open

${escapeHtml(message)}

`; }; setTimeout(() => { if (!startupOverlayClosed) { showStartupError("PYAS WebView2 initialization is taking too long. The application will retry automatically."); } }, 20000); const formatMsg = (key, ...args) => { let msg = getMsg(key); args.forEach((arg, i) => msg = msg.split(`{${i}}`).join(arg)); return msg; }; const escapeHtml = (unsafe) => { if (typeof unsafe !== 'string') return unsafe; return unsafe.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); }; const themes = { "white_switch": { "--bg-window": "245, 245, 247", "--bg-nav": "255, 255, 255", "--bg-panel": "255, 255, 255", "--bg-hover": "rgba(0,0,0,0.05)", "--text-primary": "29, 29, 31", "--text-secondary": "134, 134, 139", "--text-on-accent": "255, 255, 255", "--border-color":"210, 210, 215", "--accent-color":"0, 122, 255", "--accent-alpha":"rgba(0, 122, 255, 0.1)", "--accent-shadow":"rgba(0, 122, 255, 0.2)", "--accent-hover":"rgba(0, 122, 255, 0.3)" }, "black_switch": { "--bg-window": "0, 0, 0", "--bg-nav": "28, 28, 30", "--bg-panel": "28, 28, 30", "--bg-hover": "rgba(255,255,255,0.1)", "--text-primary": "216, 216, 220", "--text-secondary": "134, 134, 139", "--text-on-accent": "216, 216, 220", "--border-color":"44, 44, 46", "--accent-color":"10, 132, 255", "--accent-alpha":"rgba(10, 132, 255, 0.1)", "--accent-shadow":"rgba(10, 132, 255, 0.2)", "--accent-hover":"rgba(10, 132, 255, 0.3)" }, "red_switch": { "--bg-window": "253, 246, 246", "--bg-nav": "255, 240, 240", "--bg-panel": "255, 255, 255", "--bg-hover": "rgba(255,59,48,0.05)", "--text-primary": "29, 29, 31", "--text-secondary": "134, 134, 139", "--text-on-accent": "255, 255, 255", "--border-color":"250, 220, 220", "--accent-color":"255, 59, 48", "--accent-alpha":"rgba(255, 59, 48, 0.1)", "--accent-shadow":"rgba(255, 59, 48, 0.2)", "--accent-hover":"rgba(255, 59, 48, 0.3)" }, "yellow_switch": { "--bg-window": "254, 251, 243", "--bg-nav": "255, 248, 230", "--bg-panel": "255, 255, 255", "--bg-hover": "rgba(255,149,0,0.05)", "--text-primary": "29, 29, 31", "--text-secondary": "134, 134, 139", "--text-on-accent": "255, 255, 255", "--border-color":"250, 240, 215", "--accent-color":"255, 149, 0", "--accent-alpha":"rgba(255, 149, 0, 0.1)", "--accent-shadow":"rgba(255, 149, 0, 0.2)", "--accent-hover":"rgba(255, 149, 0, 0.3)" }, "green_switch": { "--bg-window": "246, 253, 248", "--bg-nav": "235, 250, 240", "--bg-panel": "255, 255, 255", "--bg-hover": "rgba(52,199,89,0.05)", "--text-primary": "29, 29, 31", "--text-secondary": "134, 134, 139", "--text-on-accent": "255, 255, 255", "--border-color":"220, 240, 225", "--accent-color":"52, 199, 89", "--accent-alpha":"rgba(52, 199, 89, 0.1)", "--accent-shadow":"rgba(52, 199, 89, 0.2)", "--accent-hover":"rgba(52, 199, 89, 0.3)" }, "blue_switch": { "--bg-window": "246, 249, 253", "--bg-nav": "235, 244, 255", "--bg-panel": "255, 255, 255", "--bg-hover": "rgba(0,122,255,0.05)", "--text-primary": "29, 29, 31", "--text-secondary": "134, 134, 139", "--text-on-accent": "255, 255, 255", "--border-color":"220, 230, 250", "--accent-color":"0, 122, 255", "--accent-alpha":"rgba(0, 122, 255, 0.1)", "--accent-shadow":"rgba(0, 122, 255, 0.2)", "--accent-hover":"rgba(0, 122, 255, 0.3)" } }; const cols = { log: [{key: 'time_str', label: 'col_date', flex: 1.5}, {key: 'level', label: 'col_type', flex: 0.8, isI18n: true}, {key: 'action', label: 'col_func', flex: 1.2, isI18n: true}, {key: 'source', label: 'col_path', flex: 3}], process: [{key: 'name', label: 'col_name', flex: 1.5}, {key: 'pid', label: 'col_pid', flex: 0.5}, {key: 'path', label: 'col_path', flex: 3}], startup: [{key: 'type', label: 'col_type', flex: 0.5, isI18n: true}, {key: 'name', label: 'col_name', flex: 1.5}, {key: 'status', label: 'col_status', flex: 0.5, isI18n: true}, {key: 'path', label: 'col_path', flex: 3}], virus: [{key: 'label', label: 'col_type', flex: 1, isI18n: true}, {key: 'path', label: 'col_path', flex: 3}], pathOnly: [{key: 'path', label: 'col_path', flex: 1}], fileList: [{key: 'time_str', label: 'col_date', flex: 1.5}, {key: 'path', label: 'col_path', flex: 5}], junk: [{key: 'path', label: 'col_path', flex: 3}, {key: 'sizeStr', label: 'col_size', flex: 1}], popup: [{key: 'mode', label: 'col_status', flex: 0.7, isI18n: true}, {key: 'exe', label: 'col_prog', flex: 1}, {key: 'class', label: 'col_class', flex: 1}, {key: 'title', label: 'col_title', flex: 2}], repair: [{key: 'display', label: 'col_repair', flex: 1, isI18n: true}, {key: 'path', label: 'col_path', flex: 3}], netmon: [{key: 'name', label: 'col_name', flex: 1.5}, {key: 'pid', label: 'col_pid', flex: 0.5}, {key: 'downStr', label: 'col_down_speed', flex: 1}, {key: 'upStr', label: 'col_up_speed', flex: 1}, {key: 'connCount', label: 'col_conn_count', flex: 0.8}] }; const buildCustomSelectElement = (select) => { select.style.display = 'none'; const wrapper = document.createElement('div'); wrapper.className = 'custom-select-wrapper'; const selectedOpt = select.options[select.selectedIndex]; const triggerKey = selectedOpt ? selectedOpt.getAttribute('data-i18n') : ''; const triggerTextContent = selectedOpt ? getMsg(triggerKey || selectedOpt.textContent) : ''; const triggerHTML = `
${triggerTextContent}
`; wrapper.innerHTML = triggerHTML; const optionsContainer = document.createElement('ul'); optionsContainer.className = 'custom-select-options'; const phantom = document.createElement('div'); phantom.className = 'custom-select-phantom'; phantom.setAttribute('aria-hidden', 'true'); Array.from(select.options).forEach(option => { if (option.disabled) return; const optKey = option.getAttribute('data-i18n'); const optText = optKey ? getMsg(optKey) : option.textContent; const li = document.createElement('li'); li.className = `custom-select-option ${option.selected ? 'selected' : ''}`; li.textContent = optText; li.dataset.value = option.value; if (optKey) li.setAttribute('data-i18n', optKey); li.addEventListener('click', (e) => { e.stopPropagation(); select.value = option.value; const triggerText = wrapper.querySelector('.custom-select-text'); triggerText.textContent = li.textContent; if (optKey) triggerText.setAttribute('data-i18n', optKey); closeCustomSelect(); optionsContainer.querySelectorAll('.custom-select-option').forEach(el => el.classList.remove('selected')); li.classList.add('selected'); select.dispatchEvent(new Event('change')); }); optionsContainer.appendChild(li); const phantomSpan = document.createElement('span'); phantomSpan.textContent = optText; if (optKey) phantomSpan.setAttribute('data-i18n', optKey); phantom.appendChild(phantomSpan); }); wrapper.appendChild(phantom); wrapper.appendChild(optionsContainer); select.parentNode.insertBefore(wrapper, select.nextSibling); const trigger = wrapper.querySelector('.custom-select-trigger'); trigger.addEventListener('click', (e) => { e.stopPropagation(); const isOpen = wrapper.classList.contains('open'); closeCustomSelect(); if (!isOpen && !select.disabled) { wrapper.classList.add('open'); document.body.appendChild(optionsContainer); optionsContainer.classList.add('open'); openCustomSelect = { wrapper, trigger, menu: optionsContainer }; positionCustomSelect(); } }); }; const updateCustomSelectUI = (selectId, val) => { const select = document.getElementById(selectId); if (!select) return; select.value = val; const wrapper = select.nextElementSibling; if (openCustomSelect?.wrapper === wrapper) closeCustomSelect(); if (wrapper && wrapper.classList.contains('custom-select-wrapper')) { const triggerText = wrapper.querySelector('.custom-select-text'); const selectedOpt = select.options[select.selectedIndex]; if (selectedOpt) { const optKey = selectedOpt.getAttribute('data-i18n'); triggerText.textContent = optKey ? getMsg(optKey) : selectedOpt.textContent; if (optKey) triggerText.setAttribute('data-i18n', optKey); } wrapper.querySelectorAll('.custom-select-option').forEach(el => { el.classList.toggle('selected', el.dataset.value === val); }); } }; const translateText = (lang) => { appState.lang = lang; document.querySelectorAll("[data-i18n]").forEach(el => { const key = el.getAttribute('data-i18n'); if (key) el.textContent = getMsg(key); }); document.querySelectorAll("[data-i18n-placeholder]").forEach(el => { const key = el.getAttribute('data-i18n-placeholder'); if (key) el.setAttribute('placeholder', getMsg(key)); }); document.querySelectorAll('.manage-widget, .virus-widget').forEach(widget => { if (widget.buildHeader) widget.buildHeader(); if (widget.renderData) widget.renderData(); }); document.documentElement.lang = langMap[lang] || "en"; const progressText = document.getElementById('progress_text'); if (progressText && progressText.dataset.dynamicMsg) { try { const msgs = JSON.parse(progressText.dataset.dynamicMsg); if (msgs[lang]) progressText.textContent = msgs[lang]; } catch (e) {} } }; const applyTheme = (theme) => { appState.theme = theme; const actualTheme = theme === "system_switch" ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? "black_switch" : "white_switch") : theme; const themeVars = themes[actualTheme]; if (themeVars) { for (const [key, value] of Object.entries(themeVars)) { document.documentElement.style.setProperty(key, value.startsWith("rgba") ? value : `rgb(${value})`); } } }; window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { if (appState.theme === "system_switch") applyTheme("system_switch"); }); const renderDataGrid = (containerSelector, dataList, columns, valKey, defaultChecked = false) => { const widget = document.querySelector(`${containerSelector} .manage-widget`) || document.querySelector(`${containerSelector} .virus-widget`); const listUl = document.querySelector(`${containerSelector} .manage-list`) || document.querySelector(`${containerSelector} .virus-list`); const searchInput = document.querySelector(`${containerSelector} .search-box input`); if (!widget || !listUl) return; const isInit = !widget.gridState; if (isInit) { const containerWidth = widget.clientWidth || 800; const availableWidth = Math.max(containerWidth - 60 - (columns.length * 16), 400); const totalFlex = columns.reduce((sum, c) => sum + (c.flex || 1), 0); widget.gridState = { sortKey: columns[0]?.key || '', sortAsc: true, filterText: "", colWidths: columns.map(c => Math.max(((c.flex || 1) / totalFlex) * availableWidth, 60)), checkedSet: new Set() }; widget.gridState.colWidths.forEach((w, idx) => widget.style.setProperty(`--col-${idx}`, `${w}px`)); const header = document.createElement('div'); header.className = 'manage-list-header'; widget.insertBefore(header, widget.firstChild); widget.buildHeader = () => { const headerColsHtml = columns.map((col, idx) => `
${getMsg(col.label)} ${widget.gridState.sortKey === col.key ? (widget.gridState.sortAsc ? '▲' : '▼') : ''}
`).join(''); header.innerHTML = `
${headerColsHtml}
`; header.querySelectorAll('.col-header').forEach(el => { el.addEventListener('click', () => { const key = el.dataset.key; widget.gridState.sortAsc = widget.gridState.sortKey === key ? !widget.gridState.sortAsc : true; widget.gridState.sortKey = key; widget.buildHeader(); widget.renderData(widget.gridState.filterText, false); }); }); header.querySelectorAll('.col-resizer').forEach(resizer => { resizer.addEventListener('mousedown', (e) => { e.stopPropagation(); const idx = parseInt(resizer.parentElement.dataset.idx); const startX = e.clientX; const startWidth = widget.gridState.colWidths[idx]; const onMouseMove = (me) => { const newWidth = Math.max(startWidth + (me.clientX - startX), 40); widget.gridState.colWidths[idx] = newWidth; widget.style.setProperty(`--col-${idx}`, `${newWidth}px`); }; const onMouseUp = () => { document.body.classList.remove('resizing-active'); document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); }; document.body.classList.add('resizing-active'); document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); }); resizer.addEventListener('click', (e) => e.stopPropagation()); }); const selectAllCb = header.querySelector('.select-all-cb'); if (selectAllCb) { selectAllCb.addEventListener('change', (e) => { const isChecked = e.target.checked; listUl.querySelectorAll('.manage-list-item input[type="checkbox"]').forEach(cb => cb.checked = isChecked); (widget.gridState.lastDisplayData || []).forEach(item => { const valStr = String(item[valKey]); isChecked ? widget.gridState.checkedSet.add(valStr) : widget.gridState.checkedSet.delete(valStr); if (containerSelector === '#scan_window') appState.virusMap.set(valStr, isChecked); }); if (containerSelector === '#scan_window') checkVirusListEmpty(); }); } }; widget.buildHeader(); listUl.addEventListener('click', (e) => { const li = e.target.closest('.manage-list-item'); if (!li) return; const cb = li.querySelector('input[type="checkbox"]'); if (e.target.tagName !== 'INPUT') cb.checked = !cb.checked; cb.checked ? widget.gridState.checkedSet.add(cb.value) : widget.gridState.checkedSet.delete(cb.value); if (containerSelector === '#scan_window') { appState.virusMap.set(cb.value, cb.checked); checkVirusListEmpty(); } if (widget.updateSelectAllState) widget.updateSelectAllState(); }); if (searchInput) searchInput.addEventListener('input', (e) => widget.renderData(e.target.value, false)); } const state = widget.gridState; widget.renderData = (filterText = state.filterText, resetDataCheck = false) => { state.filterText = filterText; if (resetDataCheck) { state.checkedSet.clear(); if (defaultChecked) dataList.forEach(item => state.checkedSet.add(String(item[valKey]))); } else { const validKeys = new Set(dataList.map(item => String(item[valKey]))); for (const val of state.checkedSet) if (!validKeys.has(val)) state.checkedSet.delete(val); if (state.checkedSet.size === 0 && defaultChecked && dataList.length > 0) { if (!listUl.querySelector('input[type="checkbox"]:checked')) { dataList.forEach(item => state.checkedSet.add(String(item[valKey]))); } } } const header = widget.querySelector('.manage-list-header'); const selectAllCb = header ? header.querySelector('.select-all-cb') : null; if (selectAllCb) selectAllCb.disabled = dataList.length === 0; const ft = filterText.toLowerCase(); let displayData = dataList.filter(item => !filterText || columns.some(col => { const rawVal = String(item[col.key] || ''); if (rawVal.toLowerCase().includes(ft)) return true; if (col.isI18n) { const transVal = getMsg(rawVal); if (transVal && transVal.toLowerCase().includes(ft)) return true; } return false; })); displayData.sort((a, b) => { const valA = a[state.sortKey] ?? '', valB = b[state.sortKey] ?? ''; const rawKeyMap = { 'sizeStr': 'size', 'downStr': 'down', 'upStr': 'up' }; if (rawKeyMap[state.sortKey] && rawKeyMap[state.sortKey] in a && rawKeyMap[state.sortKey] in b) { const rawKey = rawKeyMap[state.sortKey]; return state.sortAsc ? a[rawKey] - b[rawKey] : b[rawKey] - a[rawKey]; } const numA = parseFloat(valA), numB = parseFloat(valB); if (!isNaN(numA) && !isNaN(numB) && String(valA).trim() !== '' && String(valB).trim() !== '') return state.sortAsc ? numA - numB : numB - numA; return state.sortAsc ? String(valA).localeCompare(String(valB)) : String(valB).localeCompare(String(valA)); }); widget.gridState.lastDisplayData = displayData; widget.updateSelectAllState = () => { if (!selectAllCb) return; const cData = widget.gridState.lastDisplayData || []; if (cData.length === 0) { selectAllCb.checked = false; selectAllCb.indeterminate = false; return; } const checkedCount = cData.filter(item => containerSelector === '#scan_window' ? appState.virusMap.get(String(item[valKey])) : widget.gridState.checkedSet.has(String(item[valKey]))).length; selectAllCb.checked = (checkedCount > 0 && checkedCount === cData.length); selectAllCb.indeterminate = (checkedCount > 0 && checkedCount < cData.length); }; const MAX_ITEMS = 1000; const limitData = displayData.slice(0, MAX_ITEMS); let htmlContent = limitData.map(item => { const itemValStr = String(item[valKey]); let isChecked = containerSelector === '#scan_window' ? (appState.virusMap.get(itemValStr) ?? true) : state.checkedSet.has(itemValStr); if (containerSelector === '#scan_window') appState.virusMap.set(itemValStr, isChecked); const rowColsHtml = columns.map((col, idx) => { const rawVal = String(item[col.key] || ''); let valStr = col.isI18n ? getMsg(rawVal) : rawVal; if (rawVal === '未知路徑' || rawVal === 'path_unknown') valStr = getMsg('path_unknown'); return `
${escapeHtml(valStr)}
`; }).join(''); return `
  • ${rowColsHtml}
  • `; }).join(''); if (displayData.length > MAX_ITEMS) { htmlContent += `
  • ${escapeHtml(formatMsg("msg_too_many", MAX_ITEMS))}
  • `; } const scrollContainer = listUl.parentElement; const scrollTop = scrollContainer ? scrollContainer.scrollTop : 0; listUl.innerHTML = htmlContent; if (scrollContainer) scrollContainer.scrollTop = scrollTop; widget.updateSelectAllState(); }; widget.renderData(state.filterText, isInit); }; const getCheckedValues = (selector) => { if (selector === '#log_export_window') { const widget = document.querySelector(`${selector} .manage-widget`); if (widget?.gridState) return Array.from(widget.gridState.checkedSet); } return Array.from(document.querySelectorAll(`${selector} .manage-list-item input[type="checkbox"]:checked`)).map(cb => cb.value); }; const formatSize = (bytes) => { if (bytes === 0) return '0 B'; const k = 1024, sizes = ['B', 'KB', 'MB', 'GB'], i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }; const rebuildCustomSelect = (selectId) => { const select = document.getElementById(selectId); if (!select) return; if (openCustomSelect?.wrapper === select.nextElementSibling) closeCustomSelect(); let sibling = select.nextElementSibling; while (sibling && sibling.classList.contains('custom-select-wrapper')) { const toRemove = sibling; sibling = sibling.nextElementSibling; toRemove.remove(); } buildCustomSelectElement(select); }; const changeScanSelectMode = (mode) => { const select = document.getElementById('scan_method_select'); if (!select) return; select.innerHTML = ``; if (mode === 'scan') { select.innerHTML += ` `; } else if (mode === 'action') { select.innerHTML += ` `; } rebuildCustomSelect('scan_method_select'); }; const checkVirusListEmpty = () => { updateCustomSelectUI('scan_method_select', 'none'); if (appState.virusResults.length === 0) { appState.virusMap.clear(); changeScanSelectMode('scan'); const title = document.querySelector('#scan_window .section-title'); const text = document.getElementById('progress_text'); if (title) { title.setAttribute('data-i18n', 'scan_virus'); title.textContent = getMsg('scan_virus'); } if (text) { text.setAttribute('data-i18n', 'scan_virus_desc'); text.removeAttribute('data-dynamic-msg'); text.textContent = getMsg('scan_virus_desc'); } } }; const triggerScan = (method) => { if (!window.pywebview) return; appState.scanning = true; appState.virusMap.clear(); appState.virusResults = []; renderDataGrid('#scan_window', appState.virusResults, cols.virus, 'path', true); changeScanSelectMode('scan'); document.getElementById('scan_method_select')?.classList.add('hidden'); const stopBtn = document.getElementById('stop_btn'); if (stopBtn) { stopBtn.classList.remove('hidden'); stopBtn.disabled = false; delete stopBtn.dataset.stopping; } const title = document.querySelector('#scan_window .section-title'); const text = document.getElementById('progress_text'); if (title) { title.setAttribute('data-i18n', 'status_scanning'); title.textContent = getMsg('status_scanning'); } if (text) { text.setAttribute('data-i18n', 'msg_init'); text.removeAttribute('data-dynamic-msg'); text.textContent = getMsg('msg_init'); } window.pywebview.api.trigger_scan(method); }; const handleVirusActions = (action) => { if (!window.pywebview) return; const paths = Array.from(appState.virusMap.entries()).filter(([_, checked]) => checked).map(([p]) => p); if (paths.length === 0) return updateCustomSelectUI('scan_method_select', 'none'); const wrapper = document.getElementById('scan_method_select').nextElementSibling; if (wrapper) wrapper.style.pointerEvents = 'none'; const finalize = () => { if (wrapper) wrapper.style.pointerEvents = ''; checkVirusListEmpty(); }; const removeItems = (removedPaths) => { const removedSet = new Set(removedPaths); removedPaths.forEach(p => appState.virusMap.delete(p)); appState.virusResults = appState.virusResults.filter(r => !removedSet.has(r.path)); renderDataGrid('#scan_window', appState.virusResults, cols.virus, 'path', false); }; if (action === 'delete') { window.pywebview.api.solve_scan(paths).then((deleted) => { removeItems(deleted); finalize(); }); } else if (action === 'ignore') { window.pywebview.api.remove_virus_result(paths).then(() => { removeItems(paths); finalize(); }); } else if (action === 'quarantine' || action === 'whitelist') { const listKey = action === 'quarantine' ? 'quarantine' : 'white_list'; window.pywebview.api.manage_named_list(listKey, paths, 'add').then(() => { window.pywebview.api.remove_virus_result(paths).then(() => { removeItems(paths); finalize(); }); }); } }; const fetchProcs = () => { if (!appState.taskmgrActive || !window.pywebview) return; const widget = document.querySelector('#taskmgr_window .manage-widget'); const blockFetch = (widget && widget.gridState?.checkedSet.size > 0) || document.querySelector('.custom-context-menu.show') || document.body.classList.contains('resizing-active'); if (!blockFetch) { window.pywebview.api.get_process_list().then(procs => { const mapped = procs.map(p => ({ name: p.name, pid: p.pid, path: (p.path && p.path !== "None" && p.path !== "Unknown") ? p.path : "path_unknown" })); renderDataGrid('#taskmgr_window', mapped, cols.process, 'pid', false); if (appState.taskmgrActive) appState.taskmgrTimer = setTimeout(fetchProcs, 1000); }).catch(() => { if (appState.taskmgrActive) appState.taskmgrTimer = setTimeout(fetchProcs, 1000); }); } else { if (appState.taskmgrActive) appState.taskmgrTimer = setTimeout(fetchProcs, 1000); } }; const fetchNetmon = () => { if (!appState.netmonActive || !window.pywebview) return; const widget = document.querySelector('#netmon_window .manage-widget'); const blockFetch = (widget && widget.gridState?.checkedSet.size > 0) || document.querySelector('.custom-context-menu.show') || document.body.classList.contains('resizing-active'); if (!blockFetch) { window.pywebview.api.get_traffic_list().then(list => { const mapped = list.map(item => ({ name: item.name, pid: item.pid, downStr: formatSize(item.down) + '/s', down: item.down, upStr: formatSize(item.up) + '/s', up: item.up, connCount: item.conn, path: (item.path && item.path !== "None" && item.path !== "Unknown") ? item.path : "path_unknown" })); renderDataGrid('#netmon_window', mapped, cols.netmon, 'pid', false); if (appState.netmonActive) appState.netmonTimer = setTimeout(fetchNetmon, 1000); }).catch(() => { if (appState.netmonActive) appState.netmonTimer = setTimeout(fetchNetmon, 1000); }); } else { if (appState.netmonActive) appState.netmonTimer = setTimeout(fetchNetmon, 1000); } }; const fetchStartup = () => { if (!window.pywebview) return; const listUl = document.querySelector('#startup_window .manage-list'); if (listUl && !listUl.children.length) { listUl.innerHTML = `
  • ${getMsg('msg_init')}
  • `; } window.pywebview.api.get_startup_list().then(items => { const mapped = items.map(item => ({ id: item.id, type: item.type, name: item.name, status: item.status === 'enabled' ? 'status_enabled' : 'status_disabled', path: item.path || 'path_unknown' })); renderDataGrid('#startup_window', mapped, cols.startup, 'id', false); }); }; const fetchJunk = () => { if (!window.pywebview) return; const widget = document.querySelector('#junk_window .manage-widget'); if (widget?.gridState) widget.gridState.checkedSet.clear(); const listUl = document.querySelector('#junk_window .manage-list'); if (listUl && !listUl.children.length) { listUl.innerHTML = `
  • ${getMsg('msg_init')}
  • `; } window.pywebview.api.scan_system_junk().then(list => { appState.junkList = list.map(item => ({ path: item.path, sizeStr: formatSize(item.size), size: item.size })); renderDataGrid('#junk_window', appState.junkList, cols.junk, 'path', true); }); }; const fetchRepair = () => { if (!window.pywebview) return; const listUl = document.querySelector('#repair_window .manage-list'); if (listUl && !listUl.children.length) { listUl.innerHTML = `
  • ${getMsg('msg_init')}
  • `; } window.pywebview.api.scan_system_repair().then(list => renderDataGrid('#repair_window', list, cols.repair, 'id', true)); }; const refreshConfigLists = () => { if (!window.pywebview) return; window.pywebview.api.get_config().then(cfg => { const formatTime = (ts) => { if (!ts) return '-'; const d = new Date(ts * 1000); const pad = n => n.toString().padStart(2, '0'); return `[${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}]`; }; const mapList = (arr) => (arr || []).map(i => ({ path: i.file || (typeof i === 'string' ? i : ''), time_str: formatTime(i.time) })); renderDataGrid('#whitelist_window', mapList(cfg.white_list), cols.fileList, 'path'); renderDataGrid('#quarantine_window', mapList(cfg.quarantine), cols.fileList, 'path'); renderDataGrid('#custom_protect_window', mapList(cfg.custom_rule), cols.fileList, 'path'); const popupRules = (cfg.block_list || []).map((item, index) => ({ mode: item.schema_version === 2 ? (item.match_mode === 'title' ? 'popup_fallback' : 'popup_adaptive') : 'popup_fallback', exe: item.exe || '*', class: item.class || '*', title: item.title || '*', value: item.id || `legacy:${index}` })); renderDataGrid('#popup_window', popupRules, cols.popup, 'value'); }); }; const switchPage = (targetId) => { const oldActive = document.querySelector('.page.active'); if (oldActive && oldActive.id !== targetId) { if (oldActive.id === 'junk_window') { appState.junkList = []; renderDataGrid('#junk_window', [], cols.junk, 'path', true); const input = document.querySelector('#junk_window .search-box input'); if (input) input.value = ''; } else if (oldActive.id === 'repair_window') { renderDataGrid('#repair_window', [], cols.repair, 'id', true); } } closeCustomSelect(); document.querySelectorAll('.page, .nav-btn').forEach(el => el.classList.remove('active')); document.getElementById(targetId)?.classList.add('active'); document.querySelector(`aside .nav-btn[data-target="${targetId}"]`)?.classList.add('active'); appState.taskmgrActive = targetId === 'taskmgr_window'; if (appState.taskmgrActive) fetchProcs(); else if (appState.taskmgrTimer) { clearTimeout(appState.taskmgrTimer); appState.taskmgrTimer = null; } appState.netmonActive = targetId === 'netmon_window'; if (appState.netmonActive) fetchNetmon(); else if (appState.netmonTimer) { clearTimeout(appState.netmonTimer); appState.netmonTimer = null; } if (targetId === 'startup_window') fetchStartup(); if (targetId === 'junk_window') fetchJunk(); if (targetId === 'repair_window') fetchRepair(); if (['whitelist_window', 'quarantine_window', 'popup_window', 'custom_protect_window'].includes(targetId)) refreshConfigLists(); if (targetId === 'log_export_window' && window.pywebview) { window.pywebview.api.get_logs().then(logs => { const logList = logs.reverse().map(log => ({ time_str: `[${log.time_str}]`, level: log.level, action: log.action, source: log.source || '-', id: log.id })); renderDataGrid('#log_export_window', logList, cols.log, 'id', true); }); } }; window.addVirusResult = (label, path) => { if (!appState.virusMap.has(path)) { appState.virusResults.push({ label, path }); appState.virusMap.set(path, true); const widget = document.querySelector('#scan_window .virus-widget'); if (widget && widget.renderData) { if (window.virusRenderTimeout) clearTimeout(window.virusRenderTimeout); window.virusRenderTimeout = setTimeout(() => widget.renderData(widget.gridState.filterText, false), 50); } } }; window.updateScanProgress = (path) => { const title = document.querySelector('#scan_window .section-title'); const text = document.getElementById('progress_text'); if (title) { title.setAttribute('data-i18n', 'status_scanning'); title.textContent = getMsg('status_scanning'); } if (text) { text.removeAttribute('data-i18n'); text.removeAttribute('data-dynamic-msg'); text.textContent = path; } }; window.updateDeleteProgress = (path) => { const title = document.querySelector('#scan_window .section-title'); const text = document.getElementById('progress_text'); if (title) { title.setAttribute('data-i18n', 'status_deleting'); title.textContent = getMsg('status_deleting'); } if (text) { text.removeAttribute('data-i18n'); text.removeAttribute('data-dynamic-msg'); text.textContent = path; } }; window.finishScan = (msgData, count) => { appState.scanning = false; const title = document.querySelector('#scan_window .section-title'); const text = document.getElementById('progress_text'); if (title) { title.setAttribute('data-i18n', 'scan_virus'); title.textContent = getMsg('scan_virus'); } if (text) { if (typeof msgData === 'object' && msgData !== null) { text.removeAttribute('data-i18n'); text.dataset.dynamicMsg = JSON.stringify(msgData); text.textContent = msgData[appState.lang] || msgData["english_switch"]; } else { text.removeAttribute('data-dynamic-msg'); text.setAttribute('data-i18n', msgData); text.textContent = getMsg(msgData); } } const stopBtn = document.getElementById('stop_btn'); if (stopBtn) { stopBtn.classList.add('hidden'); stopBtn.disabled = false; delete stopBtn.dataset.stopping; } document.getElementById('scan_method_select')?.classList.remove('hidden'); changeScanSelectMode(count > 0 ? 'action' : 'scan'); updateCustomSelectUI('scan_method_select', 'none'); }; window.updateLogs = (entry) => { const logWidget = document.querySelector('.log-text'); if (logWidget) { let parts = [`[${entry.time_str}]`, getMsg(entry.level || ''), getMsg(entry.action || '')]; if (entry.source) parts.push(`Src: ${entry.source}`); if (entry.target) parts.push(`Tgt: ${entry.target}`); if (entry.code) parts.push(`Code: ${entry.code}`); if (entry.pid) parts.push(`PID: ${entry.pid}`); if (entry.hash) parts.push(`Hash: ${entry.hash}`); if (entry.detail) { const transDetail = getMsg(entry.detail); parts.push(`Detail: ${transDetail !== entry.detail ? transDetail : entry.detail}`); } if (entry.operate !== null) parts.push(`Op: ${entry.operate}`); parts.push(`Success: ${entry.success}`); let val = logWidget.value + parts.join(' | ') + '\n'; if (val.length > 100000) { const newline = val.indexOf('\n', val.length - 90000); val = newline >= 0 && newline < val.length - 1 ? val.slice(newline + 1) : val.slice(-90000); } logWidget.value = val; logWidget.scrollTop = logWidget.scrollHeight; } if (document.getElementById('log_export_window')?.classList.contains('active') && window.pywebview) { window.pywebview.api.get_logs().then(logs => { const logList = logs.reverse().map(log => ({ time_str: `[${log.time_str}]`, level: log.level, action: log.action, source: log.source || '-', id: log.id })); renderDataGrid('#log_export_window', logList, cols.log, 'id', true); }); } if ((entry.action === 'System' && entry.detail === 'Engine Initialization Complete') || (entry.level === 'WARN' && entry.action === 'init_engine_thread')) { closeStartupOverlay(); if (window.onEngineReady) window.onEngineReady(); } }; window.triggerContextScan = (targets) => { if (appState.scanning) return; switchPage('scan_window'); if (window.pywebview) { appState.scanning = true; appState.virusMap.clear(); appState.virusResults = []; renderDataGrid('#scan_window', appState.virusResults, cols.virus, 'path', true); document.getElementById('scan_method_select')?.classList.add('hidden'); const stopBtn = document.getElementById('stop_btn'); if (stopBtn) { stopBtn.classList.remove('hidden'); stopBtn.disabled = false; delete stopBtn.dataset.stopping; } changeScanSelectMode('scan'); const title = document.querySelector('#scan_window .section-title'); const text = document.getElementById('progress_text'); if (title) { title.setAttribute('data-i18n', 'status_scanning'); title.textContent = getMsg('status_scanning'); } if (text) { text.setAttribute('data-i18n', 'msg_init'); text.removeAttribute('data-dynamic-msg'); text.textContent = getMsg('msg_init'); } const paths = Array.isArray(targets) ? targets : [targets]; window.pywebview.api.start_scan(paths); } }; window.revertSwitch = () => {}; document.querySelectorAll('[data-target]').forEach(btn => btn.addEventListener('click', (e) => switchPage(e.currentTarget.getAttribute('data-target')))); document.getElementById('quick_scan_btn')?.addEventListener('click', () => { if (!appState.scanning) { switchPage('scan_window'); triggerScan('smart'); }}); document.getElementById('stop_btn')?.addEventListener('click', async (e) => { const stopBtn = e.currentTarget; if (!window.pywebview || stopBtn.dataset.stopping === '1') return; stopBtn.dataset.stopping = '1'; try { await window.pywebview.api.stop_scan(); } finally { delete stopBtn.dataset.stopping; } }); document.getElementById('theme_select')?.addEventListener('change', (e) => { applyTheme(e.target.value); window.pywebview?.api.update_config('theme', e.target.value); }); document.getElementById('lang_select')?.addEventListener('change', (e) => { translateText(e.target.value); window.pywebview?.api.update_config('language', e.target.value); }); document.getElementById('scan_method_select')?.addEventListener('change', (e) => { const val = e.target.value; if (val === 'none' || appState.scanning) return; if (['smart', 'file', 'path', 'full'].includes(val)) triggerScan(val); else if (['delete', 'ignore', 'quarantine', 'whitelist'].includes(val)) handleVirusActions(val); }); document.querySelectorAll('.toggle-switch input').forEach((toggle, index) => { toggle.addEventListener('change', async (e) => { const switchMap = ["process_switch", "suspend_switch", "load_switch", "document_switch", "system_switch", "network_switch", "driver_switch", "sensitive_switch", "extension_switch", "cloud_switch", "suffix_switch", "autostart_switch", "context_switch"]; const key = switchMap[index]; if (window.pywebview && key) { toggle.disabled = true; try { const result = await window.pywebview.api.update_config(key, e.target.checked); if (result !== undefined && result !== null) toggle.checked = result; } catch { toggle.checked = !e.target.checked; } finally { toggle.disabled = false; } } }); }); document.getElementById('taskmgr_select')?.addEventListener('change', (e) => { const val = e.target.value; const checkedCbs = document.querySelectorAll('#taskmgr_window .manage-list-item input[type="checkbox"]:checked'); if (checkedCbs.length === 0 || !window.pywebview) return updateCustomSelectUI('taskmgr_select', 'none'); if (val === 'view') { checkedCbs.forEach(cb => { if (cb.dataset.path && cb.dataset.path !== "path_unknown") window.pywebview.api.open_file_location(cb.dataset.path); }); } else if (val === 'end') { window.pywebview.api.show_confirm(getMsg("msg_end_proc_title"), getMsg("msg_end_proc_confirm")).then(res => { if (res) Promise.all(Array.from(checkedCbs).map(cb => window.pywebview.api.kill_process(parseInt(cb.value)))).then(() => fetchProcs()); }); } else if (val === 'quarantine' || val === 'whitelist') { const paths = Array.from(checkedCbs).map(cb => cb.dataset.path).filter(p => p && p !== "path_unknown"); if (paths.length > 0) { const listKey = val === 'quarantine' ? 'quarantine' : 'white_list'; window.pywebview.api.manage_named_list(listKey, paths, 'add').then(() => { Promise.all(Array.from(checkedCbs).map(cb => window.pywebview.api.kill_process(parseInt(cb.value)))).then(() => fetchProcs()); }); } } updateCustomSelectUI('taskmgr_select', 'none'); }); document.getElementById('netmon_select')?.addEventListener('change', (e) => { const val = e.target.value; const checkedCbs = document.querySelectorAll('#netmon_window .manage-list-item input[type="checkbox"]:checked'); if (checkedCbs.length === 0 || !window.pywebview) return updateCustomSelectUI('netmon_select', 'none'); if (val === 'view') { checkedCbs.forEach(cb => { if (cb.dataset.path && cb.dataset.path !== "path_unknown") window.pywebview.api.open_file_location(cb.dataset.path); }); } else if (val === 'end') { window.pywebview.api.show_confirm(getMsg("msg_end_proc_title"), getMsg("msg_end_proc_confirm")).then(res => { if (res) Promise.all(Array.from(checkedCbs).map(cb => window.pywebview.api.kill_process(parseInt(cb.value)))).then(() => fetchNetmon()); }); } updateCustomSelectUI('netmon_select', 'none'); }); document.getElementById('startup_select')?.addEventListener('change', (e) => { const val = e.target.value; const checked = getCheckedValues('#startup_window'); if (checked.length > 0 && window.pywebview) { if (val === 'enable' || val === 'disable') { const widget = document.querySelector('#startup_window .manage-widget'); if (widget && widget.gridState && widget.gridState.lastDisplayData) { widget.gridState.lastDisplayData.forEach(item => { if (checked.includes(item.id)) item.status = val === 'enable' ? 'status_enabled' : 'status_disabled'; }); widget.renderData(); } } window.pywebview.api.manage_startup(checked, val).then(fetchStartup); } updateCustomSelectUI('startup_select', 'none'); }); document.getElementById('junk_select')?.addEventListener('change', (e) => { const val = e.target.value; const checked = getCheckedValues('#junk_window'); if (!window.pywebview) return; if (val === 'view') { checked.forEach(path => window.pywebview.api.open_file_location(path)); } else if (val === 'delete') { if (checked.length > 0) { window.pywebview.api.clean_system_junk(checked).then(size => { window.pywebview.api.show_alert(getMsg("title_prompt"), `${getMsg("msg_cleaned")}${formatSize(size)}`, "info"); appState.junkList = appState.junkList.filter(item => !checked.includes(item.path)); renderDataGrid('#junk_window', appState.junkList, cols.junk, 'path', true); }); } } updateCustomSelectUI('junk_select', 'none'); }); document.getElementById('repair_select')?.addEventListener('change', (e) => { const val = e.target.value; if (val === 'repair') { const checked = getCheckedValues('#repair_window'); if (checked.length > 0 && window.pywebview) { window.pywebview.api.execute_system_repair(checked).then(() => { window.pywebview.api.show_alert(getMsg("title_prompt"), getMsg("msg_repaired"), "info"); renderDataGrid('#repair_window', [], cols.repair, 'id', true); }); } } updateCustomSelectUI('repair_select', 'none'); }); ['whitelist_window', 'quarantine_window', 'popup_window', 'custom_protect_window'].forEach(id => { const listKeyMap = { 'popup_window': 'block_list', 'custom_protect_window': 'custom_rule', 'whitelist_window': 'white_list', 'quarantine_window': 'quarantine' }; const listKey = listKeyMap[id]; const selectId = id.replace('_window', '_select'); document.getElementById(selectId)?.addEventListener('change', (e) => { const val = e.target.value; if (!window.pywebview) return; if (val === 'add') { if (id === 'popup_window') { const select = document.getElementById(selectId); if (select.dataset.capturing === 'true') return; select.dataset.capturing = 'true'; const triggerText = select.nextElementSibling?.querySelector('.custom-select-text'); const originalI18n = triggerText?.getAttribute('data-i18n'); const originalText = triggerText?.textContent; if (triggerText) { triggerText.setAttribute('data-i18n', 'msg_click_target'); triggerText.textContent = getMsg('msg_click_target'); } let failureMessage = 'popup_capture_failed'; Promise.resolve().then(() => window.pywebview.api.capture_popup_window()).then(async rule => { if (rule?.error) throw new Error(rule.error); if (!rule) return; failureMessage = 'popup_add_failed'; const ok = await window.pywebview.api.add_popup_rule(rule); if (!ok) throw new Error('Popup rule was not saved'); await refreshConfigLists(); }).catch(error => { console.error('Popup capture/add failed', error); return window.pywebview.api.show_alert(getMsg('title_prompt'), getMsg(failureMessage), 'warning'); }).catch(error => console.error('Popup error notification failed', error)).finally(() => { select.dataset.capturing = 'false'; if (triggerText) { triggerText.setAttribute('data-i18n', originalI18n || 'btn_select'); triggerText.textContent = originalText; } updateCustomSelectUI(selectId, 'none'); }); return; } else { const fTypes = id === 'custom_protect_window' ? ['JSON Files (*.json)'] : null; const currentListKey = id === 'custom_protect_window' ? 'custom_rule' : listKey; window.pywebview.api.select_files(fTypes).then(files => { if (files?.length > 0) window.pywebview.api.manage_named_list(currentListKey, files, 'add').then(refreshConfigLists); }); } } else if (val === 'add_file') { const fTypes = id === 'custom_protect_window' ? ['JSON Files (*.json)'] : null; const currentListKey = id === 'custom_protect_window' ? 'custom_rule' : listKey; window.pywebview.api.select_files(fTypes).then(files => { if (files?.length > 0) window.pywebview.api.manage_named_list(currentListKey, files, 'add').then(refreshConfigLists); }); } else if (val === 'add_path') { window.pywebview.api.select_folder().then(folder => { if (folder?.length > 0) window.pywebview.api.manage_named_list(listKey, folder, 'add').then(refreshConfigLists); }); } else if (val === 'extract') { const checked = getCheckedValues(`#${id}`); if (checked.length > 0) { window.pywebview.api.select_folder().then(folder => { if (folder?.length > 0) { window.pywebview.api.extract_list_items(checked, folder[0]).then(success => { if (success) window.pywebview.api.show_alert(getMsg("title_prompt"), getMsg("msg_extract_success"), "info"); }); } }); } } else if (val === 'remove') { const checked = getCheckedValues(`#${id}`); if (checked.length > 0) { const remove = id === 'popup_window' ? window.pywebview.api.remove_popup_rules(checked) : window.pywebview.api.remove_list_items(listKey, checked); remove.then(refreshConfigLists); } } else if (val === 'quarantine' || val === 'whitelist') { const checked = getCheckedValues(`#${id}`); if (checked.length > 0) { const targetKey = val === 'quarantine' ? 'quarantine' : 'white_list'; window.pywebview.api.manage_named_list(targetKey, checked, 'add').then(() => refreshConfigLists()); } } updateCustomSelectUI(selectId, 'none'); }); }); document.getElementById('log_export_select')?.addEventListener('change', (e) => { const val = e.target.value; const checked = getCheckedValues('#log_export_window'); if (val === 'export') { if (checked.length === 0) { window.pywebview?.api.show_alert(getMsg("title_prompt"), getMsg("msg_sel_log_export"), "warning"); } else { window.pywebview?.api.export_logs(checked).then(res => res && window.pywebview.api.show_alert(getMsg("title_prompt"), getMsg("msg_export_success"), "info")); } } else if (val === 'delete') { if (checked.length > 0 && window.pywebview) { window.pywebview.api.show_confirm(getMsg("title_prompt"), getMsg("msg_del_log_confirm")).then(res => { if (res) window.pywebview.api.clear_logs(checked).then(() => { window.pywebview.api.get_logs().then(logs => renderDataGrid('#log_export_window', logs.reverse().map(log => ({ time_str: `[${log.time_str}]`, level: log.level, action: log.action, source: log.source || '-', id: log.id })), cols.log, 'id', true)); window.pywebview.api.show_alert(getMsg("title_prompt"), getMsg("msg_log_deleted"), "info"); }); }); } } updateCustomSelectUI('log_export_select', 'none'); }); document.getElementById('minimize_button')?.addEventListener('click', () => window.pywebview?.api.minimize()); document.getElementById('close_button')?.addEventListener('click', () => window.pywebview?.api.hide_window()); const contextMenu = document.createElement('div'); contextMenu.className = 'custom-context-menu'; contextMenu.innerHTML = `
    `; document.body.appendChild(contextMenu); let ctxTarget = null; let ctxCellText = null; const copyTextToClipboard = async (text) => { if (navigator.clipboard?.writeText) { try { await navigator.clipboard.writeText(text); return; } catch (e) {} } const textarea = document.createElement('textarea'); textarea.value = text; textarea.style.position = 'fixed'; textarea.style.opacity = '0'; textarea.style.pointerEvents = 'none'; document.body.appendChild(textarea); textarea.focus(); textarea.select(); document.execCommand('copy'); textarea.remove(); }; document.addEventListener('contextmenu', (e) => { const li = e.target.closest('.manage-list-item'); const rowCol = e.target.closest('.row-col'); if (li && rowCol) { const cb = li.querySelector('input[type="checkbox"]'); if (cb && cb.dataset.path && cb.dataset.path !== "path_unknown") { e.preventDefault(); ctxTarget = cb.dataset.path; ctxCellText = rowCol.textContent; contextMenu.querySelector('#ctx_open_location span').textContent = getMsg('ctx_open_loc'); contextMenu.querySelector('#ctx_copy_cell span').textContent = getMsg('ctx_copy_cell'); contextMenu.style.left = `${e.clientX}px`; contextMenu.style.top = `${e.clientY}px`; contextMenu.classList.add('show'); const rect = contextMenu.getBoundingClientRect(); if (rect.right > window.innerWidth) contextMenu.style.left = `${window.innerWidth - rect.width - 5}px`; if (rect.bottom > window.innerHeight) contextMenu.style.top = `${window.innerHeight - rect.height - 5}px`; return; } } contextMenu.classList.remove('show'); }); document.addEventListener('click', (e) => { if (!e.target.closest('.custom-context-menu')) contextMenu.classList.remove('show'); }); document.addEventListener('scroll', () => contextMenu.classList.remove('show'), true); document.getElementById('ctx_open_location').addEventListener('click', () => { if (window.pywebview && ctxTarget) window.pywebview.api.open_file_location(ctxTarget); contextMenu.classList.remove('show'); }); document.getElementById('ctx_copy_cell').addEventListener('click', async () => { const text = ctxCellText; contextMenu.classList.remove('show'); if (text !== null) await copyTextToClipboard(text); }); initI18nKeys(); const bindSettingAction = (key, action) => { const item = Array.from(document.querySelectorAll('#about_window .list-item, #setting_window .list-item')).find(el => { const h2 = el.querySelector('h2'); return h2 && h2.getAttribute('data-i18n') === key; }); if (item) { const btn = item.querySelector('button'); const isAboutItem = item.closest('#about_window') !== null; if (isAboutItem) { item.style.cursor = 'pointer'; item.dataset.action = key; item.addEventListener('click', action); return; } if (btn) { btn.addEventListener('click', action); } else { item.style.cursor = 'pointer'; item.dataset.action = key; item.addEventListener('click', action); } } }; bindSettingAction('key_product', async () => { if (!window.pywebview) return; try { const cfg = await window.pywebview.api.get_config(); if (cfg && cfg.api_key && cfg.api_key.trim() !== "") { window.pywebview.api.show_alert(getMsg("title_prompt"), getMsg("msg_key_activated"), "info"); } } catch (e) {} }); bindSettingAction('web_official', () => window.pywebview?.api.open_website()); bindSettingAction('update_check', () => { window.pywebview?.api.check_update().then(res => { if (res.error) window.pywebview.api.show_alert(getMsg("title_error"), getMsg("msg_update_fail"), "error"); else if (res.has_update) window.pywebview.api.show_confirm(getMsg("title_prompt"), `${getMsg("msg_new_version")} ${res.latest}\n(${res.current} -> ${res.latest})`).then(ok => ok && window.pywebview.api.open_url(res.url)); else window.pywebview.api.show_alert(getMsg("title_prompt"), `${getMsg("msg_latest_version")} ${res.current}`, "info"); }); }); bindSettingAction('opt_reset', () => { window.pywebview?.api.show_confirm(getMsg("title_prompt"), getMsg("opt_reset_desc")).then(res => { if (res) window.pywebview.api.reset_config().then(() => window.pywebview.api.show_alert(getMsg("title_prompt"), getMsg("msg_reset_success"), "info")); }); }); document.querySelectorAll('select.modern-select').forEach(buildCustomSelectElement); renderDataGrid('#scan_window', appState.virusResults, cols.virus, 'path', true); renderDataGrid('#taskmgr_window', [], cols.process, 'pid', false); renderDataGrid('#startup_window', [], cols.startup, 'id', false); renderDataGrid('#junk_window', [], cols.junk, 'path', true); renderDataGrid('#repair_window', [], cols.repair, 'id', true); renderDataGrid('#whitelist_window', [], cols.fileList, 'path', false); renderDataGrid('#quarantine_window', [], cols.fileList, 'path', false); renderDataGrid('#custom_protect_window', [], cols.fileList, 'path', false); renderDataGrid('#popup_window', [], cols.popup, 'value', false); renderDataGrid('#log_export_window', [], cols.log, 'id', true); renderDataGrid('#netmon_window', [], cols.netmon, 'pid', false); let pywebviewInitStarted = false; let pywebviewInitReady = false; let pywebviewInitAttempts = 0; let pywebviewInitTimer = null; const pywebviewInitDeadline = Date.now() + 30000; const reportStartupError = (stage, message) => { const text = String(message); console.error(`PYAS startup ${stage}:`, text); if (window.pywebview?.api?.report_ui_error) { window.pywebview.api.report_ui_error(stage, text).catch(() => {}); } }; const schedulePywebviewInit = (delay) => { clearTimeout(pywebviewInitTimer); pywebviewInitTimer = setTimeout(initializePywebview, delay); }; const initializePywebview = async () => { if (pywebviewInitStarted || pywebviewInitReady || pywebviewInitAttempts >= 3) return; if (!window.pywebview?.api) { if (Date.now() < pywebviewInitDeadline) schedulePywebviewInit(250); else showStartupError("The Python interface bridge is unavailable. PYAS will attempt recovery."); return; } pywebviewInitStarted = true; pywebviewInitAttempts += 1; clearTimeout(pywebviewInitTimer); let timeoutId; let timedOut = false; let stage = 'get_config'; const timeout = new Promise((_, reject) => { timeoutId = setTimeout(() => { timedOut = true; reject(new Error("Python interface initialization timed out")); }, 8000); }); try { for (const error of (window.pyasStartupErrors || []).splice(0)) { reportStartupError(error.stage, error.message); } await Promise.race([(async () => { const cfg = await window.pywebview.api.get_config(); if (timedOut) return; if (!cfg || typeof cfg !== 'object') throw new Error("Invalid configuration response"); stage = 'ui_apply'; const theme = cfg.theme || "white_switch"; const lang = cfg.language || "english_switch"; appState.firstLaunch = cfg.first_launch; applyTheme(theme); translateText(lang); updateCustomSelectUI('theme_select', theme); updateCustomSelectUI('lang_select', lang); const switchMap = ["process_switch", "suspend_switch", "load_switch", "document_switch", "system_switch", "network_switch", "driver_switch", "sensitive_switch", "extension_switch", "cloud_switch", "suffix_switch", "autostart_switch", "context_switch"]; document.querySelectorAll('.toggle-switch input').forEach((toggle, index) => { if (appState.firstLaunch) { toggle.checked = false; toggle.disabled = true; } else if (switchMap[index]) { toggle.checked = !!cfg[switchMap[index]]; } }); stage = 'init_ui_ready'; await window.pywebview.api.init_ui_ready(); })(), timeout]); pywebviewInitReady = true; closeStartupOverlay(); } catch (err) { reportStartupError(stage, err?.message || err); showStartupError(`Interface initialization failed (${stage}): ${err?.message || err}`); if (pywebviewInitAttempts < 3) schedulePywebviewInit(500 * pywebviewInitAttempts); } finally { clearTimeout(timeoutId); pywebviewInitStarted = false; } }; window.addEventListener('pywebviewready', initializePywebview); initializePywebview(); window.onEngineReady = async () => { if (!appState.firstLaunch) return; await window.pywebview.api.update_config("first_launch", false); const switchMap = ["process_switch", "suspend_switch", "load_switch", "document_switch", "system_switch", "network_switch", "driver_switch", "sensitive_switch", "extension_switch", "cloud_switch", "suffix_switch", "autostart_switch", "context_switch"]; const seq = ["autostart_switch", "suffix_switch", "process_switch", "suspend_switch", "load_switch", "document_switch", "system_switch", "network_switch", "driver_switch", "context_switch"]; for (const key of seq) { const index = switchMap.indexOf(key); if (index !== -1) { const toggle = document.querySelectorAll('.toggle-switch input')[index]; if (toggle) { toggle.checked = true; try { const res = await window.pywebview.api.update_config(key, true); if (res !== undefined && res !== null) toggle.checked = res; } catch { toggle.checked = false; } finally { toggle.disabled = false; } await new Promise(r => setTimeout(r, 100)); } } } document.querySelectorAll('.toggle-switch input').forEach(t => t.disabled = false); appState.firstLaunch = false; }; });