Enhance Mihomo proxy groups component with new delay management features
- 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:
@@ -19,13 +19,22 @@
|
|||||||
/** Увеличьте, чтобы принудительно перезагрузить список снаружи. */
|
/** Увеличьте, чтобы принудительно перезагрузить список снаружи. */
|
||||||
refreshNonce = 0,
|
refreshNonce = 0,
|
||||||
/** Состояние загрузки для индикатора у родителя. */
|
/** Состояние загрузки для индикатора у родителя. */
|
||||||
onLoadingChange
|
onLoadingChange,
|
||||||
|
/** Увеличьте — проверить задержку для всех групп (флот: кнопка в шапке карточки). */
|
||||||
|
delayAllNonce = 0,
|
||||||
|
/** Не показывать «Все задержки» внутри группы (кнопка на родителе). */
|
||||||
|
hideCompactPerGroupDelayButton = false,
|
||||||
|
/** Идёт проверка задержек по всем группам. */
|
||||||
|
onDelayAllChange
|
||||||
}: {
|
}: {
|
||||||
alias: string;
|
alias: string;
|
||||||
compact?: boolean;
|
compact?: boolean;
|
||||||
hideToolbar?: boolean;
|
hideToolbar?: boolean;
|
||||||
refreshNonce?: number;
|
refreshNonce?: number;
|
||||||
onLoadingChange?: (loading: boolean) => void;
|
onLoadingChange?: (loading: boolean) => void;
|
||||||
|
delayAllNonce?: number;
|
||||||
|
hideCompactPerGroupDelayButton?: boolean;
|
||||||
|
onDelayAllChange?: (busy: boolean) => void;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
const MIHOMO_DELAY_DEFAULT_URL = 'https://www.gstatic.com/generate_204';
|
const MIHOMO_DELAY_DEFAULT_URL = 'https://www.gstatic.com/generate_204';
|
||||||
@@ -150,6 +159,23 @@
|
|||||||
testingGroup = null;
|
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>
|
</script>
|
||||||
|
|
||||||
{#if !hideToolbar}
|
{#if !hideToolbar}
|
||||||
@@ -202,11 +228,11 @@
|
|||||||
{#each selectableGroups as [gName, group] (gName)}
|
{#each selectableGroups as [gName, group] (gName)}
|
||||||
{#if compact}
|
{#if compact}
|
||||||
<div
|
<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"
|
role="group"
|
||||||
aria-label="Группа {gName}"
|
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">
|
<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">
|
<Badge variant="outline" class="h-5 px-1.5 font-mono text-[10px] uppercase tracking-wide">
|
||||||
{gName}
|
{gName}
|
||||||
@@ -215,15 +241,17 @@
|
|||||||
<span class="text-foreground/90 font-medium">{group.now ?? '—'}</span>
|
<span class="text-foreground/90 font-medium">{group.now ?? '—'}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
{#if !hideCompactPerGroupDelayButton}
|
||||||
variant="outline"
|
<Button
|
||||||
size="sm"
|
variant="outline"
|
||||||
class="h-7 shrink-0 px-2 text-xs"
|
size="sm"
|
||||||
onclick={() => pingGroup(gName)}
|
class="h-7 shrink-0 px-2 text-xs"
|
||||||
disabled={testingGroup === gName}
|
onclick={() => pingGroup(gName)}
|
||||||
>
|
disabled={testingGroup === gName}
|
||||||
{testingGroup === gName ? '…' : 'Все delay'}
|
>
|
||||||
</Button>
|
{testingGroup === gName ? '…' : 'Все задержки'}
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap gap-1.5" role="list">
|
<div class="flex flex-wrap gap-1.5" role="list">
|
||||||
{#each group.all ?? [] as nodeName (nodeName)}
|
{#each group.all ?? [] as nodeName (nodeName)}
|
||||||
@@ -232,10 +260,10 @@
|
|||||||
{@const active = group.now === nodeName}
|
{@const active = group.now === nodeName}
|
||||||
<div
|
<div
|
||||||
class={cn(
|
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
|
active
|
||||||
? 'border-primary bg-accent/25'
|
? 'border-primary/60 bg-muted/30'
|
||||||
: 'border-border hover:border-border/80 hover:bg-muted/40'
|
: 'border-border/60 hover:bg-muted/25'
|
||||||
)}
|
)}
|
||||||
role="listitem"
|
role="listitem"
|
||||||
>
|
>
|
||||||
@@ -298,7 +326,7 @@
|
|||||||
onclick={() => pingGroup(gName)}
|
onclick={() => pingGroup(gName)}
|
||||||
disabled={testingGroup === gName}
|
disabled={testingGroup === gName}
|
||||||
>
|
>
|
||||||
{testingGroup === gName ? 'Проверка…' : 'Проверить все'}
|
{testingGroup === gName ? 'Проверка…' : 'Все задержки'}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
|
|||||||
@@ -51,12 +51,18 @@
|
|||||||
let pollTimer: ReturnType<typeof setInterval> | null = null;
|
let pollTimer: ReturnType<typeof setInterval> | null = null;
|
||||||
/** Принудительная перезагрузка /proxies для карточки ноды. */
|
/** Принудительная перезагрузка /proxies для карточки ноды. */
|
||||||
let proxyRefreshNonce = $state<Record<string, number>>({});
|
let proxyRefreshNonce = $state<Record<string, number>>({});
|
||||||
|
let proxyDelayAllNonce = $state<Record<string, number>>({});
|
||||||
let nodeProxyLoading = $state<Record<string, boolean>>({});
|
let nodeProxyLoading = $state<Record<string, boolean>>({});
|
||||||
|
let nodeDelayAllBusy = $state<Record<string, boolean>>({});
|
||||||
|
|
||||||
function bumpProxyRefresh(a: string) {
|
function bumpProxyRefresh(a: string) {
|
||||||
proxyRefreshNonce = { ...proxyRefreshNonce, [a]: (proxyRefreshNonce[a] ?? 0) + 1 };
|
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 withMihomo = $derived(rows.filter((r) => r.metaStatus === 'ok').length);
|
||||||
let aliveCount = $derived(rows.filter((r) => r.metaStatus === 'ok' && r.alive).length);
|
let aliveCount = $derived(rows.filter((r) => r.metaStatus === 'ok' && r.alive).length);
|
||||||
let sumConn = $derived(
|
let sumConn = $derived(
|
||||||
@@ -334,8 +340,8 @@
|
|||||||
<div>
|
<div>
|
||||||
<h2 class="text-base font-semibold tracking-tight">Прокси по нодам</h2>
|
<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 class="text-muted-foreground mt-0.5 max-w-2xl text-xs leading-relaxed">
|
||||||
Автозагрузка групп. Клик по узлу — выбор; иконка справа — проверка задержки. Обновление списка — в
|
Автозагрузка групп. В шапке: «Все задержки» и обновление списка. Клик по узлу — выбор; справа —
|
||||||
шапке карточки.
|
проверка одного узла.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -358,13 +364,22 @@
|
|||||||
{r.meta.controller_base}
|
{r.meta.controller_base}
|
||||||
</Card.Description>
|
</Card.Description>
|
||||||
{/if}
|
{/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
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
class="size-8 shrink-0"
|
class="size-8 shrink-0"
|
||||||
aria-label="Обновить прокси для {r.alias}"
|
aria-label="Обновить прокси для {r.alias}"
|
||||||
disabled={!!nodeProxyLoading[r.alias]}
|
disabled={!!nodeProxyLoading[r.alias] || !!nodeDelayAllBusy[r.alias]}
|
||||||
onclick={() => bumpProxyRefresh(r.alias)}
|
onclick={() => bumpProxyRefresh(r.alias)}
|
||||||
>
|
>
|
||||||
<RefreshCwIcon
|
<RefreshCwIcon
|
||||||
@@ -383,10 +398,15 @@
|
|||||||
alias={r.alias}
|
alias={r.alias}
|
||||||
compact={true}
|
compact={true}
|
||||||
hideToolbar={true}
|
hideToolbar={true}
|
||||||
|
hideCompactPerGroupDelayButton={true}
|
||||||
refreshNonce={proxyRefreshNonce[r.alias] ?? 0}
|
refreshNonce={proxyRefreshNonce[r.alias] ?? 0}
|
||||||
|
delayAllNonce={proxyDelayAllNonce[r.alias] ?? 0}
|
||||||
onLoadingChange={(v) => {
|
onLoadingChange={(v) => {
|
||||||
nodeProxyLoading = { ...nodeProxyLoading, [r.alias]: v };
|
nodeProxyLoading = { ...nodeProxyLoading, [r.alias]: v };
|
||||||
}}
|
}}
|
||||||
|
onDelayAllChange={(v) => {
|
||||||
|
nodeDelayAllBusy = { ...nodeDelayAllBusy, [r.alias]: v };
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
|
|||||||
Reference in New Issue
Block a user