Enhance Mihomo proxy groups component with improved data handling and UI updates
Publish telemt-api gateway Docker image / test (push) Successful in 28s
Publish telemt-api gateway Docker image / build-and-push (push) Successful in 2m14s

- Introduced a stable order for selectable proxy groups to minimize DOM changes during JSON updates.
- Added a refresh icon to the update button for better user feedback during loading states.
- Enhanced error handling to prevent UI flickering when fetching proxy data.
- Updated layout in the Mihomo page to utilize a grid system for better responsiveness and organization.
This commit is contained in:
Denozordec
2026-03-31 17:18:23 +07:00
parent 398c776238
commit 55354d3270
2 changed files with 26 additions and 5 deletions
@@ -6,6 +6,7 @@
import DataQueryState from '$lib/components/fleet/data-query-state.svelte';
import { Skeleton } from '$lib/components/ui/skeleton/index.js';
import { cn } from '$lib/utils.js';
import RefreshCwIcon from '@lucide/svelte/icons/refresh-cw';
let {
alias,
@@ -39,8 +40,8 @@
try {
proxiesData = await fetchMihomoJson<MihomoProxiesResponse>(a, 'proxies');
} catch (e) {
proxiesData = null;
proxiesErr = e instanceof ApiError ? e.message : String(e);
// не затираем успешный снимок при сбое обновления — без мерцания UI
} finally {
proxiesLoading = false;
}
@@ -59,6 +60,15 @@
return false;
}
/** Стабильный порядок групп — меньше скачков DOM при обновлении JSON. */
let selectableGroups = $derived.by((): [string, MihomoProxyEntry][] => {
const p = proxiesData?.proxies;
if (!p) return [];
return Object.entries(p)
.filter(([_, v]) => isSelectableGroup(v))
.sort(([a], [b]) => a.localeCompare(b));
});
function lastDelay(p: MihomoProxyEntry): number | null {
const h = p.history;
if (!h?.length) return null;
@@ -139,11 +149,20 @@
onclick={() => loadProxies()}
disabled={proxiesLoading || !alias}
>
<RefreshCwIcon
class={cn('mr-1 size-3.5 shrink-0', proxiesLoading && 'animate-spin')}
aria-hidden="true"
/>
Обновить
</Button>
</div>
<DataQueryState err={proxiesErr} showSkeleton={proxiesLoading} isEmpty={false}>
<DataQueryState
err={proxiesErr}
hasStaleData={proxiesData != null && proxiesErr != null}
showSkeleton={proxiesLoading && proxiesData === null}
isEmpty={false}
>
{#snippet skeleton()}
{#if compact}
<div class="flex flex-wrap gap-1">
@@ -162,7 +181,7 @@
{#snippet children()}
{#if proxiesData?.proxies}
<div class={cn('flex flex-col', compact ? 'gap-3' : 'gap-8')}>
{#each Object.entries(proxiesData.proxies).filter(([_, v]) => isSelectableGroup(v)) as [gName, group] (gName)}
{#each selectableGroups as [gName, group] (gName)}
{#if compact}
<div class="bg-muted/20 rounded-lg border px-2 py-2 sm:px-3">
<div class="mb-1.5 flex flex-wrap items-center justify-between gap-x-2 gap-y-1">
+4 -2
View File
@@ -329,12 +329,14 @@
Список групп подгружается автоматически. Клик по ячейке — выбор узла; «ping» — задержка.
</p>
</div>
<div class="flex flex-col gap-3">
<div
class="grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-3"
>
{#each rows.filter((r) => r.metaStatus === 'ok') as r (r.alias)}
<Card.Root
id="mihomo-proxies-{encodeURIComponent(r.alias)}"
data-size="sm"
class="border-l-4 border-l-primary/30 scroll-mt-20 shadow-sm"
class="border-l-4 border-l-primary/30 min-h-0 min-w-0 scroll-mt-20 shadow-sm"
>
<Card.Header class="space-y-0 pb-1 pt-3">
<Card.Title class="text-sm font-semibold">{r.alias}</Card.Title>