From 0d27125453efed6518f4b0ee9ff4fefcdeef86f2 Mon Sep 17 00:00:00 2001 From: 87owo <85057800+87owo@users.noreply.github.com> Date: Sun, 12 Jul 2026 11:50:19 +0800 Subject: [PATCH] Add files via upload --- Interface/static/js/main.js | 2275 ++++++++++++++++++----------------- 1 file changed, 1146 insertions(+), 1129 deletions(-) diff --git a/Interface/static/js/main.js b/Interface/static/js/main.js index 39a4f27..641c870 100644 --- a/Interface/static/js/main.js +++ b/Interface/static/js/main.js @@ -1,1130 +1,1147 @@ -['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); - } - }); - }; - - document.addEventListener('click', () => { - document.querySelectorAll('.custom-select-wrapper').forEach(w => w.classList.remove('open')); - }); - - const getMsg = (key) => (dict[appState.lang] || dict["english_switch"])[key] || key; - - 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", "--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": "245, 245, 247", "--text-secondary": "134, 134, 139", "--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", "--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", "--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", "--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", "--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: '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); - - wrapper.classList.remove('open'); - 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'); - document.querySelectorAll('.custom-select-wrapper').forEach(w => w.classList.remove('open')); - if (!isOpen) wrapper.classList.add('open'); - }); - }; - - const updateCustomSelectUI = (selectId, val) => { - const select = document.getElementById(selectId); - if (!select) return; - select.value = val; - const wrapper = select.nextElementSibling; - 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 = 2000; - 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) => 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; - 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'); - stopBtn.classList.remove('hidden'); - stopBtn.disabled = false; - - 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 => ({ exe: item.exe || '*', class: item.class || '*', title: item.title || '*', value: item.exe || item.title })); - 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); - } - } - - 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); - } - } - document.getElementById('stop_btn')?.classList.add('hidden'); - 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 > 20000) val = val.substring(val.indexOf('\n', val.length - 15000) + 1); - 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')) { - 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'); - if (window.onEngineReady) window.onEngineReady(); - }, 400); - } - } - }; - - 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; } - 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', (e) => { if (window.pywebview) window.pywebview.api.stop_scan(); e.target.disabled = true; }); - 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 wrapper = document.getElementById(selectId).nextElementSibling; - const triggerText = wrapper.querySelector('.custom-select-text'); - const originalI18n = triggerText.getAttribute('data-i18n'); - const originalText = triggerText.textContent; - - triggerText.setAttribute('data-i18n', 'msg_click_target'); - triggerText.textContent = getMsg('msg_click_target'); - - window.pywebview.api.capture_popup_window().then(rule => { - triggerText.setAttribute('data-i18n', originalI18n || 'btn_select'); - triggerText.textContent = originalText; - if (rule) window.pywebview.api.add_popup_rule(rule).then(ok => ok && refreshConfigLists()); - }); - } 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) window.pywebview.api.remove_list_items(listKey, checked).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; - - document.addEventListener('contextmenu', (e) => { - const li = e.target.closest('.manage-list-item'); - if (li) { - const cb = li.querySelector('input[type="checkbox"]'); - if (cb && cb.dataset.path && cb.dataset.path !== "path_unknown") { - e.preventDefault(); - ctxTarget = cb.dataset.path; - const span = contextMenu.querySelector('span'); - span.textContent = getMsg('ctx_open_loc'); - 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'); - }); - - 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'); - - 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); - - window.addEventListener('pywebviewready', () => { - window.pywebview.api.get_config().then(cfg => { - 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]]; - } - }); - - window.pywebview.api.init_ui_ready(); - }).catch(err => { - console.error("Config load failed:", err); - window.pywebview.api.init_ui_ready(); - }); - }); - - 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 = ["cloud_switch", "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; - }; +['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); + } + }); + }; + + document.addEventListener('click', () => { + document.querySelectorAll('.custom-select-wrapper').forEach(w => w.classList.remove('open')); + }); + + const getMsg = (key) => (dict[appState.lang] || dict["english_switch"])[key] || key; + + 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", "--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": "245, 245, 247", "--text-secondary": "134, 134, 139", "--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", "--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", "--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", "--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", "--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: '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); + + wrapper.classList.remove('open'); + 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'); + document.querySelectorAll('.custom-select-wrapper').forEach(w => w.classList.remove('open')); + if (!isOpen) wrapper.classList.add('open'); + }); + }; + + const updateCustomSelectUI = (selectId, val) => { + const select = document.getElementById(selectId); + if (!select) return; + select.value = val; + const wrapper = select.nextElementSibling; + 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 = 2000; + 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) => 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; + 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 => ({ exe: item.exe || '*', class: item.class || '*', title: item.title || '*', value: item.exe || item.title })); + 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); + } + } + + 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 > 20000) val = val.substring(val.indexOf('\n', val.length - 15000) + 1); + 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')) { + 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'); + if (window.onEngineReady) window.onEngineReady(); + }, 400); + } + } + }; + + 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 wrapper = document.getElementById(selectId).nextElementSibling; + const triggerText = wrapper.querySelector('.custom-select-text'); + const originalI18n = triggerText.getAttribute('data-i18n'); + const originalText = triggerText.textContent; + + triggerText.setAttribute('data-i18n', 'msg_click_target'); + triggerText.textContent = getMsg('msg_click_target'); + + window.pywebview.api.capture_popup_window().then(rule => { + triggerText.setAttribute('data-i18n', originalI18n || 'btn_select'); + triggerText.textContent = originalText; + if (rule) window.pywebview.api.add_popup_rule(rule).then(ok => ok && refreshConfigLists()); + }); + } 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) window.pywebview.api.remove_list_items(listKey, checked).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; + + document.addEventListener('contextmenu', (e) => { + const li = e.target.closest('.manage-list-item'); + if (li) { + const cb = li.querySelector('input[type="checkbox"]'); + if (cb && cb.dataset.path && cb.dataset.path !== "path_unknown") { + e.preventDefault(); + ctxTarget = cb.dataset.path; + const span = contextMenu.querySelector('span'); + span.textContent = getMsg('ctx_open_loc'); + 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'); + }); + + 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'); + + 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); + + window.addEventListener('pywebviewready', () => { + window.pywebview.api.get_config().then(cfg => { + 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]]; + } + }); + + window.pywebview.api.init_ui_ready(); + }).catch(err => { + console.error("Config load failed:", err); + window.pywebview.api.init_ui_ready(); + }); + }); + + 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 = ["cloud_switch", "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; + }; }); \ No newline at end of file