diff --git a/Interface/static/js/i18n.js b/Interface/static/js/i18n.js index d0a898e..12483ab 100644 --- a/Interface/static/js/i18n.js +++ b/Interface/static/js/i18n.js @@ -35,6 +35,11 @@ window.AppI18n = { "repair_sys_desc": "此選項可以修復系統登錄檔項目", "block_popup": "彈窗攔截", "block_popup_desc": "此選項可以選擇指定窗口攔截", + "popup_adaptive": "視窗特徵匹配", + "popup_reselect": "請重新選取", + "popup_fallback": "標題/所選視窗", + "popup_capture_failed": "無法取得所選視窗,請確認它仍開啟且權限允許存取,再重試。", + "popup_add_failed": "添加規則失敗,請重試並查看日誌。", "list_white": "白名單", "list_white_desc": "此選項可以查看並管理白名單", "list_quarantine": "隔離區", @@ -120,9 +125,9 @@ window.AppI18n = { "col_class": "類別", "col_title": "標題", "col_repair": "修復項目", - "btn_select_all": "全選", - "ctx_open_loc": "打開檔案所在位置", - "ctx_copy_cell": "複製當前欄位文字", + "btn_select_all": "全選", + "ctx_open_loc": "打開檔案所在位置", + "ctx_copy_cell": "複製當前欄位文字", "msg_too_many": "(資料過多,僅顯示前 {0} 筆以確保效能流暢)", "res_scan_comp": "掃描完成", "res_virus_det": "發現病毒", @@ -225,6 +230,11 @@ window.AppI18n = { "repair_sys_desc": "此选项可以修复系统注册表项目", "block_popup": "弹窗拦截", "block_popup_desc": "此选项可以选择指定窗口拦截", + "popup_adaptive": "窗口特征匹配", + "popup_reselect": "请重新选择", + "popup_fallback": "标题/所选窗口", + "popup_capture_failed": "无法取得所选窗口,请确认它仍打开且权限允许访问,再重试。", + "popup_add_failed": "添加规则失败,请重试并查看日志。", "list_white": "白名单", "list_white_desc": "此选项可以查看并管理白名单", "list_quarantine": "隔离区", @@ -310,9 +320,9 @@ window.AppI18n = { "col_class": "类别", "col_title": "标题", "col_repair": "修复项目", - "btn_select_all": "全选", - "ctx_open_loc": "打开文件所在位置", - "ctx_copy_cell": "复制当前字段文本", + "btn_select_all": "全选", + "ctx_open_loc": "打开文件所在位置", + "ctx_copy_cell": "复制当前字段文本", "msg_too_many": "(数据过多,仅显示前 {0} 条以确保性能流畅)", "res_scan_comp": "扫描完成", "res_virus_det": "发现病毒", @@ -415,6 +425,11 @@ window.AppI18n = { "repair_sys_desc": "Repair system registry items", "block_popup": "Popup Blocker", "block_popup_desc": "Block specific popup windows", + "popup_adaptive": "Window matching", + "popup_reselect": "Reselect window", + "popup_fallback": "Title / selected window", + "popup_capture_failed": "Cannot access the selected window. Check that it is still open and access is allowed, then retry.", + "popup_add_failed": "Could not save the popup rule. Retry and check the logs.", "list_white": "Whitelist", "list_white_desc": "View and manage the whitelist", "list_quarantine": "Quarantine", @@ -500,9 +515,9 @@ window.AppI18n = { "col_class": "Class", "col_title": "Title", "col_repair": "Repair Items", - "btn_select_all": "Select All", - "ctx_open_loc": "Open file location", - "ctx_copy_cell": "Copy current field text", + "btn_select_all": "Select All", + "ctx_open_loc": "Open file location", + "ctx_copy_cell": "Copy current field text", "msg_too_many": "(Too much data, showing only the top {0} records for performance)", "res_scan_comp": "Scan Completed", "res_virus_det": "Virus Detected", @@ -605,6 +620,11 @@ window.AppI18n = { "repair_sys_desc": "システムレジストリ項目を修復します", "block_popup": "ポップアップブロック", "block_popup_desc": "特定のポップアップウィンドウをブロックします", + "popup_adaptive": "ウィンドウ照合", + "popup_reselect": "再選択が必要", + "popup_fallback": "タイトル/選択ウィンドウ", + "popup_capture_failed": "選択したウィンドウにアクセスできません。開いていることと権限を確認し、再試行してください。", + "popup_add_failed": "ルールを保存できませんでした。再試行してログを確認してください。", "list_white": "ホワイトリスト", "list_white_desc": "ホワイトリストを表示および管理します", "list_quarantine": "隔離", @@ -690,9 +710,9 @@ window.AppI18n = { "col_class": "クラス", "col_title": "タイトル", "col_repair": "修復項目", - "btn_select_all": "すべて選択", - "ctx_open_loc": "ファイルの場所を開く", - "ctx_copy_cell": "現在のフィールドのテキストをコピー", + "btn_select_all": "すべて選択", + "ctx_open_loc": "ファイルの場所を開く", + "ctx_copy_cell": "現在のフィールドのテキストをコピー", "msg_too_many": "(データが多すぎるため、パフォーマンスを確保するために上位 {0} 件のみを表示します)", "res_scan_comp": "スキャン完了", "res_virus_det": "ウイルスを検出", @@ -795,6 +815,11 @@ window.AppI18n = { "repair_sys_desc": "시스템 레지스트리 항목을 복구합니다", "block_popup": "팝업 차단", "block_popup_desc": "특정 팝업 창을 차단합니다", + "popup_adaptive": "창 특성 일치", + "popup_reselect": "다시 선택 필요", + "popup_fallback": "제목 / 선택한 창", + "popup_capture_failed": "선택한 창에 접근할 수 없습니다. 창이 열려 있고 접근 권한이 있는지 확인하고 다시 시도하세요.", + "popup_add_failed": "규칙을 저장하지 못했습니다. 다시 시도하고 로그를 확인하세요.", "list_white": "화이트리스트", "list_white_desc": "화이트리스트를 보고 관리합니다", "list_quarantine": "격리소", @@ -880,9 +905,9 @@ window.AppI18n = { "col_class": "클래스", "col_title": "제목", "col_repair": "복구 항목", - "btn_select_all": "전체 선택", - "ctx_open_loc": "파일 위치 열기", - "ctx_copy_cell": "현재 필드 텍스트 복사", + "btn_select_all": "전체 선택", + "ctx_open_loc": "파일 위치 열기", + "ctx_copy_cell": "현재 필드 텍스트 복사", "msg_too_many": "(데이터가 너무 많아 성능 유지를 위해 상위 {0}개만 표시합니다)", "res_scan_comp": "스캔 완료", "res_virus_det": "바이러스 발견", @@ -985,6 +1010,11 @@ window.AppI18n = { "repair_sys_desc": "Répare les éléments du registre système", "block_popup": "Bloqueur de fenêtres contextuelles", "block_popup_desc": "Bloque les fenêtres contextuelles spécifiques", + "popup_adaptive": "Correspondance fenêtre", + "popup_reselect": "Resélection requise", + "popup_fallback": "Titre / fenêtre sélectionnée", + "popup_capture_failed": "Impossible d’accéder à la fenêtre sélectionnée. Vérifiez qu’elle est ouverte et accessible, puis réessayez.", + "popup_add_failed": "Impossible d’enregistrer la règle. Réessayez et consultez les journaux.", "list_white": "Liste blanche", "list_white_desc": "Afficher et gérer la liste blanche", "list_quarantine": "Quarantaine", @@ -1070,9 +1100,9 @@ window.AppI18n = { "col_class": "Classe", "col_title": "Titre", "col_repair": "Éléments de réparation", - "btn_select_all": "Tout sélectionner", - "ctx_open_loc": "Ouvrir l'emplacement du fichier", - "ctx_copy_cell": "Copier le texte du champ actuel", + "btn_select_all": "Tout sélectionner", + "ctx_open_loc": "Ouvrir l'emplacement du fichier", + "ctx_copy_cell": "Copier le texte du champ actuel", "msg_too_many": "(Trop de données, affichage des {0} premiers enregistrements pour des raisons de performances)", "res_scan_comp": "Analyse terminée", "res_virus_det": "Virus détecté", @@ -1175,6 +1205,11 @@ window.AppI18n = { "repair_sys_desc": "Repara elementos del registro del sistema", "block_popup": "Bloqueador de ventanas emergentes", "block_popup_desc": "Bloquea ventanas emergentes específicas", + "popup_adaptive": "Coincidencia de ventana", + "popup_reselect": "Seleccionar de nuevo", + "popup_fallback": "Título / ventana seleccionada", + "popup_capture_failed": "No se puede acceder a la ventana seleccionada. Compruebe que sigue abierta y tiene acceso, y reinténtelo.", + "popup_add_failed": "No se pudo guardar la regla. Reinténtelo y consulte los registros.", "list_white": "Lista blanca", "list_white_desc": "Ver y administrar la lista blanca", "list_quarantine": "Cuarentena", @@ -1260,9 +1295,9 @@ window.AppI18n = { "col_class": "Clase", "col_title": "Título", "col_repair": "Elementos de reparación", - "btn_select_all": "Seleccionar todo", - "ctx_open_loc": "Abrir ubicación de archivo", - "ctx_copy_cell": "Copiar texto del campo actual", + "btn_select_all": "Seleccionar todo", + "ctx_open_loc": "Abrir ubicación de archivo", + "ctx_copy_cell": "Copiar texto del campo actual", "msg_too_many": "(Demasiados datos, mostrando solo los primeros {0} registros para mantener el rendimiento)", "res_scan_comp": "Escaneo completado", "res_virus_det": "Virus detectado", @@ -1365,6 +1400,11 @@ window.AppI18n = { "repair_sys_desc": "सिस्टम रजिस्ट्री आइटम की मरम्मत करें", "block_popup": "पॉपअप ब्लॉकर", "block_popup_desc": "विशिष्ट पॉपअप विंडो ब्लॉक करें", + "popup_adaptive": "विंडो मिलान", + "popup_reselect": "दोबारा चुनें", + "popup_fallback": "शीर्षक / चयनित विंडो", + "popup_capture_failed": "चयनित विंडो तक पहुँच नहीं है। विंडो खुली है और अनुमति है, यह जाँचकर फिर प्रयास करें।", + "popup_add_failed": "नियम सहेजा नहीं जा सका। फिर प्रयास करें और लॉग देखें।", "list_white": "श्वेतसूची", "list_white_desc": "श्वेतसूची देखें और प्रबंधित करें", "list_quarantine": "संगरोध", @@ -1450,9 +1490,9 @@ window.AppI18n = { "col_class": "वर्ग", "col_title": "शीर्षक", "col_repair": "मरम्मत आइटम", - "btn_select_all": "सभी चुनें", - "ctx_open_loc": "फ़ाइल स्थान खोलें", - "ctx_copy_cell": "वर्तमान फ़ील्ड का टेक्स्ट कॉपी करें", + "btn_select_all": "सभी चुनें", + "ctx_open_loc": "फ़ाइल स्थान खोलें", + "ctx_copy_cell": "वर्तमान फ़ील्ड का टेक्स्ट कॉपी करें", "msg_too_many": "(बहुत अधिक डेटा, प्रदर्शन सुनिश्चित करने के लिए केवल शीर्ष {0} रिकॉर्ड दिखा रहा है)", "res_scan_comp": "स्कैन पूरा हुआ", "res_virus_det": "वायरस का पता चला", @@ -1555,6 +1595,11 @@ window.AppI18n = { "repair_sys_desc": "إصلاح عناصر سجل النظام", "block_popup": "حظر النوافذ المنبثقة", "block_popup_desc": "حظر نوافذ منبثقة معينة", + "popup_adaptive": "مطابقة النافذة", + "popup_reselect": "أعد اختيار النافذة", + "popup_fallback": "العنوان / النافذة المحددة", + "popup_capture_failed": "تعذر الوصول إلى النافذة المحددة. تأكد من أنها مفتوحة وأن الوصول مسموح ثم أعد المحاولة.", + "popup_add_failed": "تعذر حفظ القاعدة. أعد المحاولة وتحقق من السجلات.", "list_white": "القائمة البيضاء", "list_white_desc": "عرض وإدارة القائمة البيضاء", "list_quarantine": "الحجر الصحي", @@ -1640,9 +1685,9 @@ window.AppI18n = { "col_class": "الفئة", "col_title": "العنوان", "col_repair": "عناصر الإصلاح", - "btn_select_all": "تحديد الكل", - "ctx_open_loc": "فتح موقع الملف", - "ctx_copy_cell": "نسخ نص الحقل الحالي", + "btn_select_all": "تحديد الكل", + "ctx_open_loc": "فتح موقع الملف", + "ctx_copy_cell": "نسخ نص الحقل الحالي", "msg_too_many": "(بيانات كثيرة جدًا، يتم عرض أول {0} سجل فقط لضمان الأداء)", "res_scan_comp": "اكتمل الفحص", "res_virus_det": "تم اكتشاف فيروس", @@ -1745,6 +1790,11 @@ window.AppI18n = { "repair_sys_desc": "Восстановление элементов системного реестра", "block_popup": "Блокировщик всплывающих окон", "block_popup_desc": "Блокировка определенных всплывающих окон", + "popup_adaptive": "Сопоставление окон", + "popup_reselect": "Выберите заново", + "popup_fallback": "Заголовок / выбранное окно", + "popup_capture_failed": "Нет доступа к выбранному окну. Проверьте, что оно открыто и доступ разрешён, и повторите попытку.", + "popup_add_failed": "Не удалось сохранить правило. Повторите попытку и проверьте журнал.", "list_white": "Белый список", "list_white_desc": "Просмотр и управление белым списком", "list_quarantine": "Карантин", @@ -1830,9 +1880,9 @@ window.AppI18n = { "col_class": "Класс", "col_title": "Заголовок", "col_repair": "Элементы восстановления", - "btn_select_all": "Выбрать все", - "ctx_open_loc": "Открыть расположение файла", - "ctx_copy_cell": "Копировать текст текущего поля", + "btn_select_all": "Выбрать все", + "ctx_open_loc": "Открыть расположение файла", + "ctx_copy_cell": "Копировать текст текущего поля", "msg_too_many": "(Слишком много данных, показаны только первые {0} записей для обеспечения производительности)", "res_scan_comp": "Сканирование завершено", "res_virus_det": "Обнаружен вирус", @@ -1935,6 +1985,11 @@ window.AppI18n = { "repair_sys_desc": "Popravlja elemente sistemskega registra", "block_popup": "Blokator pojavnih oken", "block_popup_desc": "Blokira določena pojavna okna", + "popup_adaptive": "Ujemanje oken", + "popup_reselect": "Izberite znova", + "popup_fallback": "Naslov / izbrano okno", + "popup_capture_failed": "Do izbranega okna ni mogoče dostopati. Preverite, da je odprto in dostop dovoljen, ter poskusite znova.", + "popup_add_failed": "Pravila ni bilo mogoče shraniti. Poskusite znova in preverite dnevnike.", "list_white": "Seznam dovoljenih", "list_white_desc": "Ogled in upravljanje seznama dovoljenih", "list_quarantine": "Karantena", @@ -2020,9 +2075,9 @@ window.AppI18n = { "col_class": "Razred", "col_title": "Naslov", "col_repair": "Predmeti za popravilo", - "btn_select_all": "Izberi vse", - "ctx_open_loc": "Odpri mesto datoteke", - "ctx_copy_cell": "Kopiraj besedilo trenutnega polja", + "btn_select_all": "Izberi vse", + "ctx_open_loc": "Odpri mesto datoteke", + "ctx_copy_cell": "Kopiraj besedilo trenutnega polja", "msg_too_many": "(Preveč podatkov, prikazanih je samo prvih {0} zapisov za zagotavljanje zmogljivosti)", "res_scan_comp": "Skeniranje končano", "res_virus_det": "Zaznan virus", @@ -2104,4 +2159,4 @@ window.AppI18n = { russian_switch: "ru", slovenian_switch: "sl" } -}; +}; diff --git a/Interface/static/js/main.js b/Interface/static/js/main.js index f1d8b07..8ce5129 100644 --- a/Interface/static/js/main.js +++ b/Interface/static/js/main.js @@ -172,7 +172,7 @@ document.addEventListener('DOMContentLoaded', () => { 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}], + popup: [{key: 'mode', label: 'col_status', flex: 0.7, isI18n: true}, {key: 'exe', label: 'col_prog', flex: 1}, {key: 'class', label: 'col_class', flex: 1}, {key: 'title', label: 'col_title', flex: 2}], repair: [{key: 'display', label: 'col_repair', flex: 1, isI18n: true}, {key: 'path', label: 'col_path', flex: 3}], netmon: [{key: 'name', label: 'col_name', flex: 1.5}, {key: 'pid', label: 'col_pid', flex: 0.5}, {key: 'downStr', label: 'col_down_speed', flex: 1}, {key: 'upStr', label: 'col_up_speed', flex: 1}, {key: 'connCount', label: 'col_conn_count', flex: 0.8}] }; @@ -468,7 +468,7 @@ document.addEventListener('DOMContentLoaded', () => { selectAllCb.indeterminate = (checkedCount > 0 && checkedCount < cData.length); }; - const MAX_ITEMS = 2000; + const MAX_ITEMS = 1000; const limitData = displayData.slice(0, MAX_ITEMS); let htmlContent = limitData.map(item => { @@ -499,7 +499,13 @@ document.addEventListener('DOMContentLoaded', () => { 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 getCheckedValues = (selector) => { + if (selector === '#log_export_window') { + const widget = document.querySelector(`${selector} .manage-widget`); + if (widget?.gridState) return Array.from(widget.gridState.checkedSet); + } + return Array.from(document.querySelectorAll(`${selector} .manage-list-item input[type="checkbox"]:checked`)).map(cb => cb.value); + }; const formatSize = (bytes) => { if (bytes === 0) return '0 B'; const k = 1024, sizes = ['B', 'KB', 'MB', 'GB'], i = Math.floor(Math.log(bytes) / Math.log(k)); @@ -704,7 +710,7 @@ document.addEventListener('DOMContentLoaded', () => { 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 })); + const popupRules = (cfg.block_list || []).map((item, index) => ({ mode: item.schema_version === 2 ? (item.match_mode === 'title' ? 'popup_fallback' : 'popup_adaptive') : 'popup_fallback', exe: item.exe || '*', class: item.class || '*', title: item.title || '*', value: item.id || `legacy:${index}` })); renderDataGrid('#popup_window', popupRules, cols.popup, 'value'); }); }; @@ -819,7 +825,10 @@ document.addEventListener('DOMContentLoaded', () => { 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); + if (val.length > 100000) { + const newline = val.indexOf('\n', val.length - 90000); + val = newline >= 0 && newline < val.length - 1 ? val.slice(newline + 1) : val.slice(-90000); + } logWidget.value = val; logWidget.scrollTop = logWidget.scrollHeight; } @@ -1008,19 +1017,36 @@ document.addEventListener('DOMContentLoaded', () => { 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()); + const select = document.getElementById(selectId); + if (select.dataset.capturing === 'true') return; + select.dataset.capturing = 'true'; + const triggerText = select.nextElementSibling?.querySelector('.custom-select-text'); + const originalI18n = triggerText?.getAttribute('data-i18n'); + const originalText = triggerText?.textContent; + if (triggerText) { + triggerText.setAttribute('data-i18n', 'msg_click_target'); + triggerText.textContent = getMsg('msg_click_target'); + } + let failureMessage = 'popup_capture_failed'; + Promise.resolve().then(() => window.pywebview.api.capture_popup_window()).then(async rule => { + if (rule?.error) throw new Error(rule.error); + if (!rule) return; + failureMessage = 'popup_add_failed'; + const ok = await window.pywebview.api.add_popup_rule(rule); + if (!ok) throw new Error('Popup rule was not saved'); + await refreshConfigLists(); + }).catch(error => { + console.error('Popup capture/add failed', error); + return window.pywebview.api.show_alert(getMsg('title_prompt'), getMsg(failureMessage), 'warning'); + }).catch(error => console.error('Popup error notification failed', error)).finally(() => { + select.dataset.capturing = 'false'; + if (triggerText) { + triggerText.setAttribute('data-i18n', originalI18n || 'btn_select'); + triggerText.textContent = originalText; + } + updateCustomSelectUI(selectId, 'none'); }); + return; } else { const fTypes = id === 'custom_protect_window' ? ['JSON Files (*.json)'] : null; const currentListKey = id === 'custom_protect_window' ? 'custom_rule' : listKey; @@ -1051,7 +1077,10 @@ document.addEventListener('DOMContentLoaded', () => { } } else if (val === 'remove') { const checked = getCheckedValues(`#${id}`); - if (checked.length > 0) window.pywebview.api.remove_list_items(listKey, checked).then(refreshConfigLists); + if (checked.length > 0) { + const remove = id === 'popup_window' ? window.pywebview.api.remove_popup_rules(checked) : window.pywebview.api.remove_list_items(listKey, checked); + remove.then(refreshConfigLists); + } } else if (val === 'quarantine' || val === 'whitelist') { const checked = getCheckedValues(`#${id}`); if (checked.length > 0) { @@ -1220,51 +1249,92 @@ document.addEventListener('DOMContentLoaded', () => { renderDataGrid('#netmon_window', [], cols.netmon, 'pid', false); let pywebviewInitStarted = false; + let pywebviewInitReady = false; + let pywebviewInitAttempts = 0; + let pywebviewInitTimer = null; + const pywebviewInitDeadline = Date.now() + 30000; - const initializePywebview = () => { - if (pywebviewInitStarted || !window.pywebview?.api) return; + const reportStartupError = (stage, message) => { + const text = String(message); + console.error(`PYAS startup ${stage}:`, text); + if (window.pywebview?.api?.report_ui_error) { + window.pywebview.api.report_ui_error(stage, text).catch(() => {}); + } + }; + + const schedulePywebviewInit = (delay) => { + clearTimeout(pywebviewInitTimer); + pywebviewInitTimer = setTimeout(initializePywebview, delay); + }; + + const initializePywebview = async () => { + if (pywebviewInitStarted || pywebviewInitReady || pywebviewInitAttempts >= 3) return; + if (!window.pywebview?.api) { + if (Date.now() < pywebviewInitDeadline) schedulePywebviewInit(250); + else showStartupError("The Python interface bridge is unavailable. PYAS will attempt recovery."); + return; + } pywebviewInitStarted = true; - - const configTimeout = new Promise((_, reject) => { - setTimeout(() => reject(new Error("Configuration request timed out")), 10000); + pywebviewInitAttempts += 1; + clearTimeout(pywebviewInitTimer); + let timeoutId; + let timedOut = false; + let stage = 'get_config'; + const timeout = new Promise((_, reject) => { + timeoutId = setTimeout(() => { + timedOut = true; + reject(new Error("Python interface initialization timed out")); + }, 8000); }); - - Promise.race([window.pywebview.api.get_config(), configTimeout]).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]]; - } - }); - + try { + for (const error of (window.pyasStartupErrors || []).splice(0)) { + reportStartupError(error.stage, error.message); + } + await Promise.race([(async () => { + const cfg = await window.pywebview.api.get_config(); + if (timedOut) return; + if (!cfg || typeof cfg !== 'object') throw new Error("Invalid configuration response"); + stage = 'ui_apply'; + const theme = cfg.theme || "white_switch"; + const lang = cfg.language || "english_switch"; + appState.firstLaunch = cfg.first_launch; + applyTheme(theme); + translateText(lang); + updateCustomSelectUI('theme_select', theme); + updateCustomSelectUI('lang_select', lang); + const switchMap = ["process_switch", "suspend_switch", "load_switch", "document_switch", "system_switch", "network_switch", "driver_switch", "sensitive_switch", "extension_switch", "cloud_switch", "suffix_switch", "autostart_switch", "context_switch"]; + document.querySelectorAll('.toggle-switch input').forEach((toggle, index) => { + if (appState.firstLaunch) { + toggle.checked = false; + toggle.disabled = true; + } else if (switchMap[index]) { + toggle.checked = !!cfg[switchMap[index]]; + } + }); + stage = 'init_ui_ready'; + await window.pywebview.api.init_ui_ready(); + })(), timeout]); + pywebviewInitReady = true; closeStartupOverlay(); - window.pywebview.api.init_ui_ready(); - }).catch(err => { + } catch (err) { + reportStartupError(stage, err?.message || err); + showStartupError(`Interface initialization failed (${stage}): ${err?.message || err}`); + if (pywebviewInitAttempts < 3) schedulePywebviewInit(500 * pywebviewInitAttempts); + } finally { + clearTimeout(timeoutId); pywebviewInitStarted = false; - console.error("Config load failed:", err); - showStartupError(`Configuration failed to load: ${err?.message || err}`); - }); + } }; window.addEventListener('pywebviewready', initializePywebview); initializePywebview(); + window.onEngineReady = async () => { if (!appState.firstLaunch) return; await window.pywebview.api.update_config("first_launch", false); const switchMap = ["process_switch", "suspend_switch", "load_switch", "document_switch", "system_switch", "network_switch", "driver_switch", "sensitive_switch", "extension_switch", "cloud_switch", "suffix_switch", "autostart_switch", "context_switch"]; - const seq = ["cloud_switch", "autostart_switch", "suffix_switch", "process_switch", "suspend_switch", "load_switch", "document_switch", "system_switch", "network_switch", "driver_switch", "context_switch"]; + const seq = ["autostart_switch", "suffix_switch", "process_switch", "suspend_switch", "load_switch", "document_switch", "system_switch", "network_switch", "driver_switch", "context_switch"]; for (const key of seq) { const index = switchMap.indexOf(key); diff --git a/Interface/templates/index.html b/Interface/templates/index.html index e974968..0c425d0 100644 --- a/Interface/templates/index.html +++ b/Interface/templates/index.html @@ -5,18 +5,34 @@ PYAS Security + - - + @@ -67,20 +83,20 @@

此裝置已受到防護

-
-
-
- -
-

智能掃描

此選項可以快速掃描裝置安全性

-
+
+
+
+ +
+

智能掃描

此選項可以快速掃描裝置安全性

+
-
-
- -
-

日誌導出

此選項可以篩選日誌並匯出存檔

-
+
+
+ +
+

日誌導出

此選項可以篩選日誌並匯出存檔

+
@@ -197,9 +213,9 @@

垃圾清理

此選項可以清理暫存垃圾檔案

- -
- +
+
+

系統修復

此選項可以修復系統登錄檔項目

@@ -257,7 +273,7 @@

雲端服務

此選項可以自動上報可疑檔案

- +
@@ -271,7 +287,7 @@

開機啟動

此選項可以設定開機自動執行

- +
@@ -327,14 +343,14 @@
- +

產品金鑰

此選項可以啟用產品金鑰

- +

官方網站

此選項可以前往官方網站

@@ -621,4 +637,4 @@ IMPORTANT NOTICE: THIS LICENSE IS "SOURCE AVAILABLE" AND IS NOT AN OPEN SOURCE L
- +