From 2583d867c32809c75769327319e7a21a83d81229 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=9A=E5=88=9A?= <225284228a@gmail.com> Date: Sat, 10 Oct 2026 04:04:22 +1300 Subject: [PATCH] fix(web): keep refresh buttons steady during polling (#2647) Co-authored-by: 225284228a-droid <239500008+225284228a-droid@users.noreply.github.com> --- .../frontend/src/components/Dashboard.vue | 29 ++++++++++++------- .../frontend/src/components/DeviceList.vue | 14 +++++---- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/easytier-web/frontend/src/components/Dashboard.vue b/easytier-web/frontend/src/components/Dashboard.vue index 91687de7..3627709f 100644 --- a/easytier-web/frontend/src/components/Dashboard.vue +++ b/easytier-web/frontend/src/components/Dashboard.vue @@ -12,17 +12,24 @@ const devices = ref(); const networks = ref(); const loadError = ref(false); const refreshing = ref(false); +let requestInFlight = false; -const loadSummary = async () => { - if (refreshing.value) return; - refreshing.value = true; - const results = await Promise.allSettled([ - props.api.get_summary().then(value => { summary.value = value; }), - props.api.list_machines().then(value => { devices.value = value.map(Utils.buildDeviceInfo); }), - ...(props.centralEnabled ? [props.api.list_networks().then(value => { networks.value = value; })] : []), - ]); - loadError.value = results.some(result => result.status === 'rejected'); - refreshing.value = false; +const loadSummary = async (manual = false) => { + // A manual refresh can show progress for an already running background request. + if (manual) refreshing.value = true; + if (requestInFlight) return; + requestInFlight = true; + try { + const results = await Promise.allSettled([ + props.api.get_summary().then(value => { summary.value = value; }), + props.api.list_machines().then(value => { devices.value = value.map(Utils.buildDeviceInfo); }), + ...(props.centralEnabled ? [props.api.list_networks().then(value => { networks.value = value; })] : []), + ]); + loadError.value = results.some(result => result.status === 'rejected'); + } finally { + requestInFlight = false; + refreshing.value = false; + } }; watch(() => props.centralEnabled, () => loadSummary()); const periodFunc = new Utils.PeriodicTask(loadSummary, 2000); @@ -45,7 +52,7 @@ const stats = computed(() => [

{{ t('web.main.dashboard') }}

-
{{ t('web.console.load_failed') }}
diff --git a/easytier-web/frontend/src/components/DeviceList.vue b/easytier-web/frontend/src/components/DeviceList.vue index f0375db8..f70defe5 100644 --- a/easytier-web/frontend/src/components/DeviceList.vue +++ b/easytier-web/frontend/src/components/DeviceList.vue @@ -27,6 +27,7 @@ const deviceList = ref | undefined>(undefined); const search = ref(''); const loadError = ref(false); const refreshing = ref(false); +let requestInFlight = false; const expandedRows = ref({}); // 桌面端可在表格与卡片视图之间切换,选择持久化;移动端始终使用紧凑列表 @@ -57,9 +58,11 @@ const copyEnrollCommand = async () => { } }; -const loadDevices = async () => { - if (refreshing.value) return; - refreshing.value = true; +const loadDevices = async (manual = false) => { + // A manual refresh can show progress for an already running background request. + if (manual) refreshing.value = true; + if (requestInFlight) return; + requestInFlight = true; try { const resp = await api?.list_machines(); deviceList.value = (resp || []).map(Utils.buildDeviceInfo); @@ -68,6 +71,7 @@ const loadDevices = async () => { loadError.value = true; console.error(e); } finally { + requestInFlight = false; refreshing.value = false; } }; @@ -214,7 +218,7 @@ const locationText = (device: Utils.DeviceInfo) => {
@@ -223,7 +227,7 @@ const locationText = (device: Utils.DeviceInfo) => {

{{ t('web.console.load_failed') }}

-