Add files via upload

This commit is contained in:
87owo authored and GitHub committed 2026-10-09 13:33:08 +08:00
1 parent fb7a68ebbb
commit 6d088d05c2
3 files changed
+256 -115

No files matched your search

+89 -34
View File
@@ -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"
}
};
};
+119 -49
View File
@@ -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);
+48 -32
View File
@@ -5,18 +5,34 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PYAS Security</title>
<link rel="stylesheet" href="/static/css/style.css">
<script>
window.pyasStartupErrors = [];
window.addEventListener('error', function (event) {
var target = event.target;
window.pyasStartupErrors.push({stage: 'javascript-or-resource', message: event.message || (target && (target.src || target.href)) || 'Unknown script error'});
}, true);
window.addEventListener('unhandledrejection', function (event) {
window.pyasStartupErrors.push({stage: 'promise', message: String(event.reason)});
});
window.addEventListener('pywebviewready', function () {
if (!window.pywebview || !window.pywebview.api || !window.pywebview.api.report_ui_error) return;
window.pyasStartupErrors.splice(0).forEach(function (error) {
window.pywebview.api.report_ui_error(error.stage, error.message).catch(function () {});
});
});
</script>
<script defer src="/static/js/i18n.js"></script>
<script defer src="/static/js/main.js"></script>
<script>
window.addEventListener('DOMContentLoaded', function () {
window.setTimeout(function () {
var overlay = document.getElementById('loading_overlay');
if (overlay && !overlay.classList.contains('fade-out') && !overlay.classList.contains('startup-error')) {
overlay.classList.add('startup-error');
overlay.innerHTML = '<div class="startup-error-box"><h1>PYAS failed to open</h1><p>WebView2 initialization timed out. PYAS will retry automatically.</p></div>';
}
}, 20000);
});
<script defer src="/static/js/main.js"></script>
<script>
window.addEventListener('DOMContentLoaded', function () {
window.setTimeout(function () {
var overlay = document.getElementById('loading_overlay');
if (overlay && !overlay.classList.contains('fade-out') && !overlay.classList.contains('startup-error')) {
overlay.classList.add('startup-error');
overlay.innerHTML = '<div class="startup-error-box"><h1>PYAS failed to open</h1><p>WebView2 initialization timed out. PYAS will retry automatically.</p></div>';
}
}, 20000);
});
</script>
</head>
<body>
@@ -67,20 +83,20 @@
<div class="state-widget">
<svg viewBox="0 0 24 24" class="state-icon theme-svg" style="width: 120px; height: 120px;" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path><polyline points="9 12 11 14 15 10"></polyline></svg>
<h2 class="state-title" data-i18n>此裝置已受到防護</h2>
</div>
<div class="quick-actions">
<div class="list-item" style="cursor: pointer;" id="quick_scan_btn" data-action="scan">
<svg class="item-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="10" cy="10" r="6"/><path d="m14.5 14.5 5 5M8 10l1.5 1.5L12 8.5"/></svg>
<div class="item-info">
<h2 data-i18n>智能掃描</h2><p data-i18n>此選項可以快速掃描裝置安全性</p>
</div>
</div>
<div class="quick-actions">
<div class="list-item" style="cursor: pointer;" id="quick_scan_btn" data-action="scan">
<svg class="item-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="10" cy="10" r="6"/><path d="m14.5 14.5 5 5M8 10l1.5 1.5L12 8.5"/></svg>
<div class="item-info">
<h2 data-i18n>智能掃描</h2><p data-i18n>此選項可以快速掃描裝置安全性</p>
</div>
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></button>
</div>
<div class="list-item" style="cursor: pointer;" data-target="log_export_window">
<svg class="item-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M14 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9zM14 3v6h6"/><path d="M8 13h4m-4 4h3m5-5v6m-3-3 3 3 3-3"/></svg>
<div class="item-info">
<h2 data-i18n>日誌導出</h2><p data-i18n>此選項可以篩選日誌並匯出存檔</p>
</div>
</div>
<div class="list-item" style="cursor: pointer;" data-target="log_export_window">
<svg class="item-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M14 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9zM14 3v6h6"/><path d="M8 13h4m-4 4h3m5-5v6m-3-3 3 3 3-3"/></svg>
<div class="item-info">
<h2 data-i18n>日誌導出</h2><p data-i18n>此選項可以篩選日誌並匯出存檔</p>
</div>
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></button>
</div>
</div>
@@ -197,9 +213,9 @@
<h2 data-i18n>垃圾清理</h2><p data-i18n>此選項可以清理暫存垃圾檔案</p>
</div>
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></button>
</div>
<div class="list-item" style="cursor: pointer;" data-target="repair_window">
<svg class="item-icon item-icon-repair" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M14 6a5 5 0 0 0-6 6l-5 5a2.1 2.1 0 0 0 3 3l5-5a5 5 0 0 0 6-6l-3 3-3-3 3-3z"/></svg>
</div>
<div class="list-item" style="cursor: pointer;" data-target="repair_window">
<svg class="item-icon item-icon-repair" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M14 6a5 5 0 0 0-6 6l-5 5a2.1 2.1 0 0 0 3 3l5-5a5 5 0 0 0 6-6l-3 3-3-3 3-3z"/></svg>
<div class="item-info">
<h2 data-i18n>系統修復</h2><p data-i18n>此選項可以修復系統登錄檔項目</p>
</div>
@@ -257,7 +273,7 @@
<div class="item-info">
<h2 data-i18n>雲端服務</h2><p data-i18n>此選項可以自動上報可疑檔案</p>
</div>
<label class="toggle-switch"><input type="checkbox" checked><span class="slider"></span></label>
<label class="toggle-switch"><input type="checkbox" data-config-key="cloud_switch"><span class="slider"></span></label>
</div>
<div class="list-item">
<svg class="item-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M3 4h18l-7 8v7l-4 2v-9L3 4z"/></svg>
@@ -271,7 +287,7 @@
<div class="item-info">
<h2 data-i18n>開機啟動</h2><p data-i18n>此選項可以設定開機自動執行</p>
</div>
<label class="toggle-switch"><input type="checkbox" checked><span class="slider"></span></label>
<label class="toggle-switch"><input type="checkbox" checked data-config-key="autostart_switch"><span class="slider"></span></label>
</div>
<div class="list-item">
<svg class="item-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M7 7h10M7 11h3"/><circle cx="13" cy="14" r="3"/><path d="m15.2 16.2 2.8 2.8"/></svg>
@@ -327,14 +343,14 @@
<section id="about_window" class="page">
<div class="list-widget">
<div class="list-item">
<svg class="item-icon item-icon-compact" aria-hidden="true" focusable="false" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"></path></svg>
<svg class="item-icon item-icon-compact" aria-hidden="true" focusable="false" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"></path></svg>
<div class="item-info">
<h2 data-i18n>產品金鑰</h2><p data-i18n>此選項可以啟用產品金鑰</p>
</div>
<button class="icon-action-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></button>
</div>
<div class="list-item">
<svg class="item-icon item-icon-compact" aria-hidden="true" focusable="false" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg>
<svg class="item-icon item-icon-compact" aria-hidden="true" focusable="false" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg>
<div class="item-info">
<h2 data-i18n>官方網站</h2><p data-i18n>此選項可以前往官方網站</p>
</div>
@@ -621,4 +637,4 @@ IMPORTANT NOTICE: THIS LICENSE IS "SOURCE AVAILABLE" AND IS NOT AN OPEN SOURCE L
</div>
</div>
</body>
</html>
</html>