接入六个新页面,外壳加内核状态心跳
导航新增「规则」项;设置、配置、规则页接 saved 事件重读配置,仪表盘只 读传 data。心跳每 10 秒探一次内核在不在:kernel.online 原本只在 load() 里更新,用户停在任何页面期间它都是静态快照,内核重启一次,请求页和 仪表盘会锁死在「内核未连接」,各页等它回来的 watch 永远不可达。恢复 在线的那一次顺带拉新运行时数据。
This commit is contained in:
+49
-3
@@ -1,11 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { ApiError, api, clearToken, getToken, setToken } from './api'
|
||||
import type { ConfigSnapshot } from './api'
|
||||
import { NAV } from './nav'
|
||||
import { density, toggleDensity } from './prefs'
|
||||
import { kernel, reloadRuntime } from './runtime'
|
||||
import { kernel, refreshKernel, reloadRuntime } from './runtime'
|
||||
import ConfigPage from './views/ConfigPage.vue'
|
||||
import ConnectionsPage from './views/ConnectionsPage.vue'
|
||||
import DashboardPage from './views/DashboardPage.vue'
|
||||
import RequestsPage from './views/RequestsPage.vue'
|
||||
import RouterPage from './views/RouterPage.vue'
|
||||
import RulesPage from './views/RulesPage.vue'
|
||||
import SettingsPage from './views/SettingsPage.vue'
|
||||
import TokenGate from './views/TokenGate.vue'
|
||||
import IconFold from '~icons/ri/sidebar-fold-line'
|
||||
import IconUnfold from '~icons/ri/sidebar-unfold-line'
|
||||
@@ -55,6 +61,24 @@ onMounted(() => {
|
||||
if (!needsToken.value) void load()
|
||||
})
|
||||
|
||||
/**
|
||||
* 内核状态的心跳。kernel.online 原本只在 load() 里更新,用户停在任何页面期间它都是
|
||||
* 静态快照:内核重启一次,请求页和仪表盘会锁死在「内核未连接」,各页等它回来的 watch
|
||||
* 永远不会触发。10 秒探一次(/api/kernel 是个 5 秒超时的轻量探测),恢复在线的那一次
|
||||
* 顺带把运行时数据拉新,路由页的组和线路也跟着回来。
|
||||
*/
|
||||
let kernelTimer = 0
|
||||
onMounted(() => {
|
||||
kernelTimer = window.setInterval(() => {
|
||||
if (needsToken.value || !snapshot.value) return
|
||||
const wasOnline = kernel.value.online
|
||||
void refreshKernel().then(() => {
|
||||
if (!wasOnline && kernel.value.online) void reloadRuntime()
|
||||
})
|
||||
}, 10_000)
|
||||
})
|
||||
onBeforeUnmount(() => window.clearInterval(kernelTimer))
|
||||
|
||||
const problems = computed(
|
||||
() => snapshot.value?.diagnostics.filter((d) => d.severity === 'error') ?? [],
|
||||
)
|
||||
@@ -120,13 +144,35 @@ const current = computed(() => NAV.find((n) => n.key === active.value))
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<DashboardPage v-if="active === 'dashboard'" :data="snapshot.data" />
|
||||
|
||||
<RouterPage
|
||||
v-if="active === 'router'"
|
||||
v-else-if="active === 'router'"
|
||||
:data="snapshot.data"
|
||||
:revision="snapshot.revision"
|
||||
@saved="load"
|
||||
/>
|
||||
|
||||
<ConfigPage
|
||||
v-else-if="active === 'config'"
|
||||
:data="snapshot.data"
|
||||
:revision="snapshot.revision"
|
||||
@saved="load"
|
||||
/>
|
||||
|
||||
<RulesPage
|
||||
v-else-if="active === 'rules'"
|
||||
:data="snapshot.data"
|
||||
:revision="snapshot.revision"
|
||||
@saved="load"
|
||||
/>
|
||||
|
||||
<RequestsPage v-else-if="active === 'requests'" />
|
||||
|
||||
<ConnectionsPage v-else-if="active === 'connections'" />
|
||||
|
||||
<SettingsPage v-else-if="active === 'settings'" @saved="load" />
|
||||
|
||||
<div v-else class="note">
|
||||
<component :is="current?.icon" class="note-icon" />
|
||||
<p>{{ current?.label }} 尚未实现</p>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import IconRoute from '~icons/ri/route-line'
|
||||
import IconDashboard from '~icons/ri/dashboard-line'
|
||||
import IconConfig from '~icons/ri/file-list-3-line'
|
||||
import IconRules from '~icons/ri/filter-3-line'
|
||||
import IconRequests from '~icons/ri/pulse-line'
|
||||
import IconConnections from '~icons/ri/links-line'
|
||||
import IconSettings from '~icons/ri/settings-3-line'
|
||||
@@ -21,6 +22,7 @@ export const NAV: NavItem[] = [
|
||||
{ key: 'dashboard', label: '仪表盘', icon: IconDashboard },
|
||||
{ key: 'router', label: '路由', icon: IconRoute },
|
||||
{ key: 'config', label: '配置', icon: IconConfig },
|
||||
{ key: 'rules', label: '规则', icon: IconRules },
|
||||
{ key: 'requests', label: '请求', icon: IconRequests },
|
||||
{ key: 'connections', label: '连接', icon: IconConnections },
|
||||
{ key: 'settings', label: '设置', icon: IconSettings },
|
||||
|
||||
+6
-1
@@ -32,6 +32,11 @@ export const lineProvider = ref<Record<string, string>>({})
|
||||
|
||||
export const kernel = ref<{ online: boolean; version?: string }>({ online: false })
|
||||
|
||||
/** 只探测内核在不在线,不拉运行时数据。给外壳的定时心跳用,各页等内核回来的 watch 靠它可达。 */
|
||||
export async function refreshKernel(): Promise<void> {
|
||||
kernel.value = await api.kernel().catch(() => ({ online: false }))
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新读一遍运行时状态。
|
||||
*
|
||||
@@ -42,7 +47,7 @@ export const kernel = ref<{ online: boolean; version?: string }>({ online: false
|
||||
* 最需要这个界面的时候。
|
||||
*/
|
||||
export async function reloadRuntime(): Promise<void> {
|
||||
kernel.value = await api.kernel().catch(() => ({ online: false }))
|
||||
await refreshKernel()
|
||||
if (!kernel.value.online) {
|
||||
runtime.value = {}
|
||||
providers.value = {}
|
||||
|
||||
Reference in New Issue
Block a user