评审 P1 修正一批
设置:显式主题补 color-scheme,滚动条和 select 弹层这些 UA 部件不看 变量。连接:断开失败的提示与轮询取数分成两条通道,不再被下一秒的轮询 成功抹掉;排序加 id 决胜,内核快照的遍历序无序,空闲连接下载量普遍 相同,没有决胜键行会在断开按钮下面跳。仪表盘与请求:一条流断了连带 收掉同组连接;断流不等于内核没了(配置重载掐断流而 online 从未翻转), 5 秒自动重连。规则:正则类规则(DOMAIN-REGEX 等三种)按内核的 payloadCanContainComma 语义拆分与禁入结构化编辑,顶层逗号不再把正则 截一半当目标;findRuleIssues 注释里对内核校验的错误断言改正。
This commit is contained in:
@@ -25,7 +25,11 @@ import ConfirmDialog from '../components/ConfirmDialog.vue'
|
||||
import IconClose from '~icons/ri/close-line'
|
||||
|
||||
const snapshot = ref<ConnectionSnapshot | null>(null)
|
||||
/** 断开动作的失败。只被下一次动作覆盖,轮询不碰它 */
|
||||
const failure = ref('')
|
||||
/** 轮询取数的失败。轮询自己成功时清掉 */
|
||||
const pollFailure = ref('')
|
||||
const shownFailure = computed(() => failure.value || pollFailure.value)
|
||||
/** 表格里的持续时间按这一刻算。跟着轮询节拍走,不单独开秒表 */
|
||||
const now = ref(Date.now())
|
||||
const query = ref('')
|
||||
@@ -38,6 +42,10 @@ const askAll = ref(false)
|
||||
let fetching = false
|
||||
let timer: number | undefined
|
||||
|
||||
/**
|
||||
* 轮询的取数失败和断开动作的失败分开放。共用一条通道的话,断开失败的提示
|
||||
* 会被下一秒轮询成功时的清空抹掉,用户只看到那条连接还在、不知道为什么。
|
||||
*/
|
||||
async function refresh() {
|
||||
// 内核不在线时不打这个接口:得不到数据,界面直接说原因
|
||||
if (!kernel.value.online || fetching) return
|
||||
@@ -45,9 +53,9 @@ async function refresh() {
|
||||
try {
|
||||
snapshot.value = await api.connections()
|
||||
now.value = Date.now()
|
||||
failure.value = ''
|
||||
pollFailure.value = ''
|
||||
} catch (error) {
|
||||
failure.value = error instanceof ApiError ? error.message : String(error)
|
||||
pollFailure.value = error instanceof ApiError ? error.message : String(error)
|
||||
} finally {
|
||||
fetching = false
|
||||
}
|
||||
@@ -144,8 +152,8 @@ async function closeAllConfirmed() {
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div v-if="failure" class="failure">
|
||||
<strong>{{ failure }}</strong>
|
||||
<div v-if="shownFailure" class="failure">
|
||||
<strong>{{ shownFailure }}</strong>
|
||||
</div>
|
||||
|
||||
<p v-if="!kernel.online" class="note">内核未连接,看不到当前有哪些连接。</p>
|
||||
|
||||
@@ -51,7 +51,10 @@ function startStreams() {
|
||||
.catch(() => {})
|
||||
// 正常结束也是断了:这两个流本身永不结束
|
||||
.finally(() => {
|
||||
if (!ctrl.signal.aborted) streamFailed.value = true
|
||||
if (ctrl.signal.aborted) return
|
||||
// 一条断了把另一条也收掉:界面已经显示离线,不留一条看不见的活连接
|
||||
ctrl.abort()
|
||||
streamFailed.value = true
|
||||
})
|
||||
|
||||
run('/traffic', (p) => {
|
||||
@@ -84,7 +87,7 @@ onMounted(() => {
|
||||
}
|
||||
})
|
||||
|
||||
// 内核从离线恢复(比如路由页保存配置触发的重载)时重新接上;掉线时收掉
|
||||
// 内核从离线恢复(外壳的心跳每 10 秒探测一次)时重新接上;掉线时收掉
|
||||
watch(
|
||||
() => kernel.value.online,
|
||||
(online) => {
|
||||
@@ -100,7 +103,24 @@ watch(
|
||||
},
|
||||
)
|
||||
|
||||
onBeforeUnmount(stopStreams)
|
||||
// 断流不等于内核没了:配置重载会掐断流,内核几秒内就回来、online 从未翻转。
|
||||
// 稍后重试一次;再失败会重新走到这里,形成 5 秒一次的重试节奏,直到接上或离开页面
|
||||
let retryTimer = 0
|
||||
watch(streamFailed, (failed) => {
|
||||
window.clearTimeout(retryTimer)
|
||||
if (!failed) return
|
||||
retryTimer = window.setTimeout(() => {
|
||||
if (!kernel.value.online) return // 真离线交给上面那个 watch
|
||||
streamFailed.value = false
|
||||
startStreams()
|
||||
void loadConfigs()
|
||||
}, 5000)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
stopStreams()
|
||||
window.clearTimeout(retryTimer)
|
||||
})
|
||||
|
||||
const latest = computed(() => samples.value.at(-1) ?? null)
|
||||
|
||||
|
||||
@@ -77,9 +77,23 @@ function flush() {
|
||||
void scrollToBottom()
|
||||
}
|
||||
|
||||
/**
|
||||
* 断流不等于内核没了:配置重载会掐断流,而内核几秒内就回来、online 从未翻转。
|
||||
* 断了 5 秒后自动重连,失败会再次走到这里形成重试节奏;真离线时交给下面等
|
||||
* online 翻转的 watch。
|
||||
*/
|
||||
let retryTimer = 0
|
||||
function scheduleRetry() {
|
||||
window.clearTimeout(retryTimer)
|
||||
retryTimer = window.setTimeout(() => {
|
||||
if (kernel.value.online) void pump()
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
async function pump() {
|
||||
// 切换级别或内核恢复在线都从这里重连。旧流先停,不然两条流一起往缓冲里倒
|
||||
aborter?.abort()
|
||||
window.clearTimeout(retryTimer)
|
||||
const controller = new AbortController()
|
||||
aborter = controller
|
||||
failure.value = ''
|
||||
@@ -88,10 +102,14 @@ async function pump() {
|
||||
for await (const line of streamLogs(level.value, controller.signal)) {
|
||||
pending.push({ ...line, id: nextId++, time: clock(new Date()) })
|
||||
}
|
||||
if (!controller.signal.aborted) ended.value = true
|
||||
if (!controller.signal.aborted) {
|
||||
ended.value = true
|
||||
scheduleRetry()
|
||||
}
|
||||
} catch (error) {
|
||||
if (controller.signal.aborted) return
|
||||
failure.value = error instanceof Error ? error.message : String(error)
|
||||
scheduleRetry()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +134,7 @@ watch(level, () => {
|
||||
onBeforeUnmount(() => {
|
||||
aborter?.abort()
|
||||
window.clearInterval(timer)
|
||||
window.clearTimeout(retryTimer)
|
||||
})
|
||||
|
||||
const visible = computed(() => {
|
||||
@@ -164,7 +183,7 @@ const visible = computed(() => {
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<p v-if="ended && !failure" class="notice">日志流已中断。切换级别可重新连接。</p>
|
||||
<p v-if="ended && !failure" class="notice">日志流已中断,将自动重连。</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ const COMMON_TYPES = [
|
||||
'DOMAIN',
|
||||
'DOMAIN-SUFFIX',
|
||||
'DOMAIN-KEYWORD',
|
||||
'DOMAIN-REGEX',
|
||||
'GEOSITE',
|
||||
'GEOIP',
|
||||
'IP-CIDR',
|
||||
|
||||
Reference in New Issue
Block a user