fix: bound and configure tunnel quality probes (#533)

Closes #528 and #532.
This commit is contained in:
sagit
2026-07-31 15:37:26 +08:00
committed by GitHub
parent 11a27d3c67
commit e112d81697
13 changed files with 574 additions and 62 deletions
+72 -4
View File
@@ -43,6 +43,14 @@ import { BackIcon, SettingsIcon } from "@/components/icons";
import { ThemeSettings } from "@/components/theme-settings";
import { isAdmin } from "@/utils/auth";
import { getCachedConfigs, configCache, updateSiteConfig } from "@/config/site";
import {
DEFAULT_TUNNEL_QUALITY_INTERVAL_SEC,
MAX_TUNNEL_QUALITY_INTERVAL_SEC,
MIN_TUNNEL_QUALITY_INTERVAL_SEC,
parseTunnelQualityIntervalSeconds,
TUNNEL_QUALITY_INTERVAL_CONFIG_KEY,
validateTunnelQualityInterval,
} from "@/config/tunnel-quality";
import {
type UpdateReleaseChannel,
getUpdateReleaseChannel,
@@ -157,6 +165,16 @@ const CONFIG_ITEMS: ConfigItem[] = [
"关闭后,前端停止自动刷新,后端停止实时隧道质量探测(全局配置)",
type: "switch",
},
{
key: TUNNEL_QUALITY_INTERVAL_CONFIG_KEY,
label: "隧道质量探测间隔",
placeholder: String(DEFAULT_TUNNEL_QUALITY_INTERVAL_SEC),
description:
"设置实时隧道质量检测的执行频率,单位为秒;允许 1–3600 秒,默认 1 秒。",
type: "input",
dependsOn: "monitor_tunnel_quality_enabled",
dependsValue: "true",
},
{
key: "monitor_retention_days",
label: "监控数据保留天数",
@@ -239,6 +257,7 @@ const getInitialConfigs = (): Record<string, string> => {
"cloudflare_secret_key",
"forward_compact_mode",
"monitor_tunnel_quality_enabled",
TUNNEL_QUALITY_INTERVAL_CONFIG_KEY,
"monitor_retention_days",
"ip",
"panel_domain",
@@ -622,6 +641,19 @@ export default function ConfigPage() {
// 保存配置
const handleSave = async () => {
const intervalValue = configs[TUNNEL_QUALITY_INTERVAL_CONFIG_KEY];
const intervalChanged =
intervalValue !== originalConfigs[TUNNEL_QUALITY_INTERVAL_CONFIG_KEY];
const intervalError = intervalChanged
? validateTunnelQualityInterval(intervalValue || "")
: null;
if (intervalError) {
toast.error(intervalError);
return;
}
setSaving(true);
try {
const changedKeys = Object.keys(configs).filter(
@@ -667,12 +699,22 @@ export default function ConfigPage() {
}),
);
// 如果隧道质量检测开关变更,通知 tunnel-monitor-view
if (changedKeys.includes("monitor_tunnel_quality_enabled")) {
// 如果隧道质量检测配置变更,通知 tunnel-monitor-view
if (
changedKeys.some((key) =>
[
"monitor_tunnel_quality_enabled",
TUNNEL_QUALITY_INTERVAL_CONFIG_KEY,
].includes(key),
)
) {
window.dispatchEvent(
new CustomEvent("monitorTunnelQualityEnabledChanged", {
detail: {
enabled: configs["monitor_tunnel_quality_enabled"] === "true",
intervalSec: parseTunnelQualityIntervalSeconds(
configs[TUNNEL_QUALITY_INTERVAL_CONFIG_KEY],
),
},
}),
);
@@ -1075,11 +1117,21 @@ export default function ConfigPage() {
case "bg_image":
return renderBgImageUploader();
case "input":
case "input": {
if (isBrandPreviewKey(item.key)) {
return renderBrandAssetUploader(item.key, isChanged);
}
const isTunnelQualityInterval =
item.key === TUNNEL_QUALITY_INTERVAL_CONFIG_KEY;
const intervalValue = isTunnelQualityInterval
? (configs[item.key] ?? String(DEFAULT_TUNNEL_QUALITY_INTERVAL_SEC))
: (configs[item.key] ?? "");
const intervalError =
isTunnelQualityInterval && configs[item.key] !== undefined
? validateTunnelQualityInterval(intervalValue)
: null;
return (
<Input
classNames={{
@@ -1091,14 +1143,30 @@ export default function ConfigPage() {
description={
isCommercialDisabled ? "需商业版授权才能修改此项" : undefined
}
endContent={isTunnelQualityInterval ? "秒" : undefined}
errorMessage={intervalError || undefined}
isDisabled={isCommercialDisabled}
isInvalid={Boolean(intervalError)}
max={
isTunnelQualityInterval
? MAX_TUNNEL_QUALITY_INTERVAL_SEC
: undefined
}
min={
isTunnelQualityInterval
? MIN_TUNNEL_QUALITY_INTERVAL_SEC
: undefined
}
placeholder={item.placeholder}
size="md"
value={configs[item.key] || ""}
step={isTunnelQualityInterval ? 1 : undefined}
type={isTunnelQualityInterval ? "number" : "text"}
value={intervalValue}
variant="bordered"
onChange={(e) => handleConfigChange(item.key, e.target.value)}
/>
);
}
case "switch":
return (