console.log("[INP2P] boot", typeof Vue, Vue && Vue.version); const { createApp, onMounted, onBeforeUnmount, watch } = Vue; createApp({ setup(){ const state = window.INP2P.createState(Vue); const { api, loginByPassword } = window.INP2P.createApi(state); const { actions, refs, setSwitchTab } = window.INP2P.createActions(state, { api, loginByPassword, Vue }); const switchTab = async (next) => { state.tab.value = next; if (!state.loggedIn.value) return; try { if (next === 'tenants') await actions.loadTenants(); else if (next === 'apikeys') await actions.loadKeys(); else if (next === 'users') await actions.loadUsers(); else if (next === 'enroll') await actions.loadEnrolls(); else if (next === 'settings' && state.isAdmin.value) { state.beginTab('settings'); try { state.settings.value = await api('/api/v1/admin/settings'); state.endTabOk('settings'); } catch(_) { state.endTabErr('settings'); } } else if (next === 'audit' && state.isAdmin.value) { await actions.loadAudit(); } else if (next === 'sdwan') { state.beginTab('sdwan'); try { state.sd.value = await api('/api/v1/sdwans'); state.endTabOk('sdwan', !(state.sd.value?.nodes||[]).length); } catch (e) { state.toast('SDWAN 加载失败: ' + (e.message || ''), 'error'); state.endTabErr('sdwan'); } } } catch (e) { state.toast(e.message || '切换页面失败', 'error'); } }; setSwitchTab(switchTab); watch(state.refreshSec, actions.startTimer); watch(state.loggedIn, (v) => { if (v) actions.startTimer(); else actions.stopTimer(); }); watch(state.tenantKeyword, (v) => localStorage.setItem('inp2p_kw_tenant', String(v || ''))); watch(state.keyKeyword, (v) => localStorage.setItem('inp2p_kw_key', String(v || ''))); watch(state.userKeyword, (v) => localStorage.setItem('inp2p_kw_user', String(v || ''))); watch(state.enrollKeyword, (v) => localStorage.setItem('inp2p_kw_enroll', String(v || ''))); watch(state.keepFailedPool, (v) => localStorage.setItem('inp2p_keep_failed_pool', v ? '1' : '0')); onMounted(() => { state.applyTheme && state.applyTheme(); // 全局兜底:前端异常时保留可见提示,避免“瞬间空白” window.addEventListener('error', (ev) => { const m = (ev && ev.message) ? ev.message : 'unknown'; state.toast('前端异常:' + m, 'error'); state.logEvent && state.logEvent('error', { tab:'global', action:'window_error', message:m }); }); window.addEventListener('unhandledrejection', (ev) => { const m = ev && ev.reason && ev.reason.message ? ev.reason.message : String(ev && ev.reason ? ev.reason : 'unknown'); state.toast('请求异常:' + m, 'error'); state.logEvent && state.logEvent('error', { tab:'global', action:'promise_rejection', message:m }); }); if (localStorage.getItem('t')) { state.loggedIn.value = true; actions.refreshAll(); switchTab(state.tab.value); } }); onBeforeUnmount(() => { }); return { buildVersion: state.buildVersion, tab: state.tab, filteredTabs: state.filteredTabs, loggedIn: state.loggedIn, loginBusy: state.loginBusy, globalBusy: state.globalBusy, tabBusy: state.tabBusy, tabState: state.tabState, debugMode: state.debugMode, debugEvents: state.debugEvents, clearDebugEvents: state.clearDebugEvents, role: state.role, roleLabel: state.roleLabel, status: state.status, tokenType: state.tokenType, tenantId: state.tenantId, loginUser: state.loginUser, loginPass: state.loginPass, loginErr: state.loginErr, opSummary: state.opSummary, refreshSec: state.refreshSec, theme: state.theme, health: state.health, stats: state.stats, nodes: state.nodes, nodeKeyword: state.nodeKeyword, filteredNodes: state.filteredNodes, sd: state.sd, connectForm: state.connectForm, nodeEditVisible: state.nodeEditVisible, nodeEditMode: state.nodeEditMode, nodeEditTitle: state.nodeEditTitle, nodeEditNode: state.nodeEditNode, nodeEditValue: state.nodeEditValue, tenants: state.tenants, activeTenant: state.activeTenant, keys: state.keys, users: state.users, enrolls: state.enrolls, selectedKeyIds: state.selectedKeyIds, selectedUserIds: state.selectedUserIds, selectedEnrollIds: state.selectedEnrollIds, bulkFailedKeyIds: state.bulkFailedKeyIds, bulkFailedUserIds: state.bulkFailedUserIds, bulkFailedEnrollIds: state.bulkFailedEnrollIds, keepFailedPool: state.keepFailedPool, auditPreset: state.auditPreset, tenantKeyword: state.tenantKeyword, keyKeyword: state.keyKeyword, userKeyword: state.userKeyword, enrollKeyword: state.enrollKeyword, filteredTenants: state.filteredTenants, filteredKeys: state.filteredKeys, filteredUsers: state.filteredUsers, filteredEnrolls: state.filteredEnrolls, tenantForm: state.tenantForm, keyForm: state.keyForm, userForm: state.userForm, settings: state.settings, tenantFormErr: state.tenantFormErr, keyFormErr: state.keyFormErr, userFormErr: state.userFormErr, sdwanFormErr: state.sdwanFormErr, audit: state.audit, auditTenant: state.auditTenant, auditFilters: state.auditFilters, auditActionOptions: state.auditActionOptions, onlineMap: state.onlineMap, nodeStatus: state.nodeStatus, isOnline: state.isOnline, nodeOnline: state.nodeOnline, nodeStatusTextOf: state.nodeStatusTextOf, nodeStatusClassOf: state.nodeStatusClassOf, natText: state.natText, uptime: state.uptime, fmtTime: state.fmtTime, appPushNode: refs.appPushNode, appPushRaw: refs.appPushRaw, login: actions.login, logout: actions.logout, refreshAll: actions.refreshAll, switchTab, toggleTheme: state.toggleTheme, saveSDWAN: actions.saveSDWAN, addSDWANNode: actions.addSDWANNode, removeSDWANNode: actions.removeSDWANNode, addSubnetProxy: actions.addSubnetProxy, removeSubnetProxy: actions.removeSubnetProxy, autoAssignIPs: actions.autoAssignIPs, kickNode: actions.kickNode, renameNode: actions.renameNode, changeNodeIP: actions.changeNodeIP, closeNodeEdit: actions.closeNodeEdit, submitNodeEdit: actions.submitNodeEdit, openAppManager: actions.openAppManager, pushAppConfigs: actions.pushAppConfigs, openConnect: actions.openConnect, doConnect: actions.doConnect, createTenant: actions.createTenant, loadTenants: actions.loadTenants, setTenantStatus: actions.setTenantStatus, reassignTenantSubnet: actions.reassignTenantSubnet, createKey: actions.createKey, loadKeys: actions.loadKeys, setKeyStatus: actions.setKeyStatus, createUser: actions.createUser, loadUsers: actions.loadUsers, setUserStatus: actions.setUserStatus, resetUserPassword: actions.resetUserPassword, deleteUser: actions.deleteUser, createEnroll: actions.createEnroll, loadEnrolls: actions.loadEnrolls, setEnrollStatus: actions.setEnrollStatus, consumeEnroll: actions.consumeEnroll, saveSettings: actions.saveSettings, loadAudit: actions.loadAudit, clearAuditFilters: actions.clearAuditFilters, applyAuditPreset: actions.applyAuditPreset, exportAuditCsv: actions.exportAuditCsv, exportAuditFailuresCsv: actions.exportAuditFailuresCsv, toggleAllKeys: actions.toggleAllKeys, toggleAllUsers: actions.toggleAllUsers, toggleAllEnrolls: actions.toggleAllEnrolls, bulkSetKeyStatus: actions.bulkSetKeyStatus, bulkSetUserStatus: actions.bulkSetUserStatus, bulkSetEnrollStatus: actions.bulkSetEnrollStatus, retryFailedKeyOps: actions.retryFailedKeyOps, retryFailedUserOps: actions.retryFailedUserOps, retryFailedEnrollOps: actions.retryFailedEnrollOps, clearFailedPool: actions.clearFailedPool, updateCharts: actions.updateCharts }; } }).mount('#app');