Enhance Mihomo proxy groups component with new delay management features
Publish telemt-api gateway Docker image / test (push) Successful in 28s
Publish telemt-api gateway Docker image / build-and-push (push) Successful in 2m23s

- Added new props for managing delay checks across all groups, improving user control over delay monitoring.
- Implemented a loading state for delay checks to provide feedback during asynchronous operations.
- Updated UI elements to conditionally display delay buttons, enhancing the overall user experience and interaction with proxy groups.
This commit is contained in:
Denozordec
2026-03-31 17:59:03 +07:00
parent 56832e525d
commit 5cfbf7f6a1
2 changed files with 68 additions and 20 deletions
@@ -19,13 +19,22 @@
/** Увеличьте, чтобы принудительно перезагрузить список снаружи. */
refreshNonce = 0,
/** Состояние загрузки для индикатора у родителя. */
onLoadingChange
onLoadingChange,
/** Увеличьте — проверить задержку для всех групп (флот: кнопка в шапке карточки). */
delayAllNonce = 0,
/** Не показывать «Все задержки» внутри группы (кнопка на родителе). */
hideCompactPerGroupDelayButton = false,
/** Идёт проверка задержек по всем группам. */
onDelayAllChange
}: {
alias: string;
compact?: boolean;
hideToolbar?: boolean;
refreshNonce?: number;
onLoadingChange?: (loading: boolean) => void;
delayAllNonce?: number;
hideCompactPerGroupDelayButton?: boolean;
onDelayAllChange?: (busy: boolean) => void;
} = $props();
const MIHOMO_DELAY_DEFAULT_URL = 'https://www.gstatic.com/generate_204';
@@ -150,6 +159,23 @@
testingGroup = null;
}
}
$effect(() => {
const a = alias;
const n = delayAllNonce;
if (!a || n <= 0) return;
const groupsSnapshot = untrack(() => [...selectableGroups]);
void (async () => {
untrack(() => onDelayAllChange?.(true));
try {
for (const [gName] of groupsSnapshot) {
await pingGroup(gName);
}
} finally {
untrack(() => onDelayAllChange?.(false));
}
})();
});
</script>
{#if !hideToolbar}
@@ -202,11 +228,11 @@
{#each selectableGroups as [gName, group] (gName)}
{#if compact}
<div
class="bg-card/40 ring-border/60 space-y-2 rounded-lg p-2 ring-1 sm:p-2.5"
class="bg-muted/15 space-y-2 rounded-md border border-border/50 p-2 sm:p-2.5"
role="group"
aria-label="Группа {gName}"
>
<div class="flex flex-wrap items-center justify-between gap-2">
<div class="flex flex-wrap items-center gap-2">
<div class="flex min-w-0 flex-1 flex-wrap items-center gap-x-2 gap-y-1">
<Badge variant="outline" class="h-5 px-1.5 font-mono text-[10px] uppercase tracking-wide">
{gName}
@@ -215,15 +241,17 @@
<span class="text-foreground/90 font-medium">{group.now ?? '—'}</span>
</span>
</div>
<Button
variant="outline"
size="sm"
class="h-7 shrink-0 px-2 text-xs"
onclick={() => pingGroup(gName)}
disabled={testingGroup === gName}
>
{testingGroup === gName ? '…' : 'Все delay'}
</Button>
{#if !hideCompactPerGroupDelayButton}
<Button
variant="outline"
size="sm"
class="h-7 shrink-0 px-2 text-xs"
onclick={() => pingGroup(gName)}
disabled={testingGroup === gName}
>
{testingGroup === gName ? '…' : 'Все задержки'}
</Button>
{/if}
</div>
<div class="flex flex-wrap gap-1.5" role="list">
{#each group.all ?? [] as nodeName (nodeName)}
@@ -232,10 +260,10 @@
{@const active = group.now === nodeName}
<div
class={cn(
'bg-background focus-within:ring-ring flex min-w-0 max-w-full items-stretch overflow-hidden rounded-md border text-left text-xs shadow-sm transition-colors focus-within:ring-2',
'bg-background flex min-w-0 max-w-full items-stretch overflow-hidden rounded-md border text-left text-xs transition-colors',
active
? 'border-primary bg-accent/25'
: 'border-border hover:border-border/80 hover:bg-muted/40'
? 'border-primary/60 bg-muted/30'
: 'border-border/60 hover:bg-muted/25'
)}
role="listitem"
>
@@ -298,7 +326,7 @@
onclick={() => pingGroup(gName)}
disabled={testingGroup === gName}
>
{testingGroup === gName ? 'Проверка…' : 'Проверить все'}
{testingGroup === gName ? 'Проверка…' : 'Все задержки'}
</Button>
</div>
<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
+24 -4
View File
@@ -51,12 +51,18 @@
let pollTimer: ReturnType<typeof setInterval> | null = null;
/** Принудительная перезагрузка /proxies для карточки ноды. */
let proxyRefreshNonce = $state<Record<string, number>>({});
let proxyDelayAllNonce = $state<Record<string, number>>({});
let nodeProxyLoading = $state<Record<string, boolean>>({});
let nodeDelayAllBusy = $state<Record<string, boolean>>({});
function bumpProxyRefresh(a: string) {
proxyRefreshNonce = { ...proxyRefreshNonce, [a]: (proxyRefreshNonce[a] ?? 0) + 1 };
}
function bumpProxyDelayAll(a: string) {
proxyDelayAllNonce = { ...proxyDelayAllNonce, [a]: (proxyDelayAllNonce[a] ?? 0) + 1 };
}
let withMihomo = $derived(rows.filter((r) => r.metaStatus === 'ok').length);
let aliveCount = $derived(rows.filter((r) => r.metaStatus === 'ok' && r.alive).length);
let sumConn = $derived(
@@ -334,8 +340,8 @@
<div>
<h2 class="text-base font-semibold tracking-tight">Прокси по нодам</h2>
<p class="text-muted-foreground mt-0.5 max-w-2xl text-xs leading-relaxed">
Автозагрузка групп. Клик по узлу — выбор; иконка справа — проверка задержки. Обновление списка — в
шапке карточки.
Автозагрузка групп. В шапке: «Все задержки» и обновление списка. Клик по узлу — выбор; справа
проверка одного узла.
</p>
</div>
<div
@@ -358,13 +364,22 @@
{r.meta.controller_base}
</Card.Description>
{/if}
<Card.Action>
<Card.Action class="flex flex-wrap items-center justify-end gap-1">
<Button
variant="outline"
size="sm"
class="h-8 px-2 text-xs"
disabled={!!nodeProxyLoading[r.alias] || !!nodeDelayAllBusy[r.alias]}
onclick={() => bumpProxyDelayAll(r.alias)}
>
{nodeDelayAllBusy[r.alias] ? 'Проверка…' : 'Все задержки'}
</Button>
<Button
variant="outline"
size="icon"
class="size-8 shrink-0"
aria-label="Обновить прокси для {r.alias}"
disabled={!!nodeProxyLoading[r.alias]}
disabled={!!nodeProxyLoading[r.alias] || !!nodeDelayAllBusy[r.alias]}
onclick={() => bumpProxyRefresh(r.alias)}
>
<RefreshCwIcon
@@ -383,10 +398,15 @@
alias={r.alias}
compact={true}
hideToolbar={true}
hideCompactPerGroupDelayButton={true}
refreshNonce={proxyRefreshNonce[r.alias] ?? 0}
delayAllNonce={proxyDelayAllNonce[r.alias] ?? 0}
onLoadingChange={(v) => {
nodeProxyLoading = { ...nodeProxyLoading, [r.alias]: v };
}}
onDelayAllChange={(v) => {
nodeDelayAllBusy = { ...nodeDelayAllBusy, [r.alias]: v };
}}
/>
</div>
</Card.Content>