2a6f526bc9
侧栏浏览器页 iframe 预览 http(s) 站点;服务器列表「站点」由 domain 跳转,禁止嵌入时可新标签打开。 Co-authored-by: Cursor <cursoragent@cursor.com>
74 lines
3.5 KiB
TypeScript
74 lines
3.5 KiB
TypeScript
import { createRouter, createWebHashHistory } from 'vue-router'
|
|
import { useAuthStore } from '@/stores/auth'
|
|
|
|
const routes = [
|
|
{ path: '/', name: 'Dashboard', component: () => import('@/pages/DashboardPage.vue') },
|
|
{ path: '/servers', name: 'Servers', component: () => import('@/pages/ServersPage.vue') },
|
|
{ path: '/terminal', name: 'Terminal', component: () => import('@/pages/TerminalPage.vue') },
|
|
{ path: '/browser', name: 'Browser', component: () => import('@/pages/BrowserPage.vue') },
|
|
{ path: '/files', name: 'Files', component: () => import('@/pages/FilesPage.vue') },
|
|
{ path: '/push', name: 'Push', component: () => import('@/pages/PushPage.vue') },
|
|
{ path: '/scripts', name: 'Scripts', component: () => import('@/pages/ScriptsPage.vue') },
|
|
{ path: '/script-runs', name: 'ScriptRuns', component: () => import('@/pages/ScriptRunsPage.vue') },
|
|
{ path: '/script-runs/:id', name: 'ScriptRunDetail', component: () => import('@/pages/ScriptRunsPage.vue') },
|
|
{ path: '/executions', name: 'Executions', component: () => import('@/pages/ScriptRunsPage.vue') },
|
|
{ path: '/executions/:id', name: 'ExecutionDetail', component: () => import('@/pages/ScriptRunsPage.vue') },
|
|
{ path: '/credentials', name: 'Credentials',component: () => import('@/pages/CredentialsPage.vue') },
|
|
{ path: '/schedules', name: 'Schedules', component: () => import('@/pages/SchedulesPage.vue') },
|
|
{ path: '/retries', name: 'Retries', component: () => import('@/pages/RetriesPage.vue') },
|
|
{ path: '/commands', name: 'Commands', component: () => import('@/pages/CommandsPage.vue') },
|
|
{ path: '/alerts', name: 'Alerts', component: () => import('@/pages/AlertsPage.vue') },
|
|
{ path: '/audit', name: 'Audit', component: () => import('@/pages/AuditPage.vue') },
|
|
{ path: '/settings', name: 'Settings', component: () => import('@/pages/SettingsPage.vue') },
|
|
{ path: '/login', name: 'Login', component: () => import('@/pages/LoginPage.vue'), meta: { public: true } },
|
|
]
|
|
|
|
const router = createRouter({ history: createWebHashHistory(), routes })
|
|
|
|
const CHUNK_RELOAD_FLAG = 'nexus_chunk_reload_v1'
|
|
|
|
function isChunkLoadError(message: string): boolean {
|
|
return (
|
|
message.includes('Failed to fetch dynamically imported module')
|
|
|| message.includes('Importing a module script failed')
|
|
|| message.includes('error loading dynamically imported module')
|
|
)
|
|
}
|
|
|
|
/** 懒加载 chunk 缺失时静默 reload 一次(部署后旧标签页兜底);登录由 refresh cookie 恢复 */
|
|
router.onError((err, to) => {
|
|
const msg = err?.message || ''
|
|
if (!isChunkLoadError(msg)) return
|
|
|
|
const w = window as Window & { $snackbar?: (text: string, color?: string) => void }
|
|
if (!sessionStorage.getItem(CHUNK_RELOAD_FLAG)) {
|
|
sessionStorage.setItem(CHUNK_RELOAD_FLAG, '1')
|
|
window.location.reload()
|
|
return
|
|
}
|
|
w.$snackbar?.(
|
|
`页面「${String(to.name || to.path)}」资源已更新,请关闭此标签后重新打开后台`,
|
|
'error',
|
|
)
|
|
})
|
|
|
|
router.afterEach(() => {
|
|
sessionStorage.removeItem(CHUNK_RELOAD_FLAG)
|
|
})
|
|
|
|
// Auth guard — restore session from HttpOnly refresh cookie when needed
|
|
router.beforeEach(async (to) => {
|
|
const auth = useAuthStore()
|
|
if (!auth.bootstrapped) {
|
|
await auth.tryRestoreSession()
|
|
}
|
|
if (!to.meta.public && !auth.isLoggedIn) {
|
|
return { name: 'Login', query: { redirect: to.fullPath } }
|
|
}
|
|
if (to.name === 'Login' && auth.isLoggedIn) {
|
|
return { name: 'Dashboard' }
|
|
}
|
|
})
|
|
|
|
export default router
|