refactor: enhance module display and input handling in module pages
CI / changes (push) Successful in 6s
CI / openapi (push) Has been skipped
CI / go (push) Has been skipped
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, , evobgp-web) (push) Successful in 1m8s
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, evobgp-all, evobgp-web-all) (push) Successful in 1m7s
CI / docker-bird (push) Has been skipped
CI / bird2 (push) Has been skipped
CI / docker-go-prime (push) Has been skipped
CI / docker-go (deploy/docker/evobgp-agent/Dockerfile, , evobgp-agent) (push) Has been skipped
CI / docker-go (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, , evobgp-all) (push) Has been skipped
CI / docker-go (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, , evobgp-api) (push) Has been skipped
CI / docker-go (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, , evobgp-deploy) (push) Has been skipped
CI / docker-go (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, , evobgp-ingest) (push) Has been skipped
CI / docker-go (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, , evobgp-node) (push) Has been skipped
CI / docker-go (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, , evobgp-render) (push) Has been skipped
CI / docker-go (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, , evobgp-scheduler) (push) Has been skipped
CI / changes (push) Successful in 6s
CI / openapi (push) Has been skipped
CI / go (push) Has been skipped
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, , evobgp-web) (push) Successful in 1m8s
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, evobgp-all, evobgp-web-all) (push) Successful in 1m7s
CI / docker-bird (push) Has been skipped
CI / bird2 (push) Has been skipped
CI / docker-go-prime (push) Has been skipped
CI / docker-go (deploy/docker/evobgp-agent/Dockerfile, , evobgp-agent) (push) Has been skipped
CI / docker-go (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, , evobgp-all) (push) Has been skipped
CI / docker-go (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, , evobgp-api) (push) Has been skipped
CI / docker-go (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, , evobgp-deploy) (push) Has been skipped
CI / docker-go (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, , evobgp-ingest) (push) Has been skipped
CI / docker-go (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, , evobgp-node) (push) Has been skipped
CI / docker-go (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, , evobgp-render) (push) Has been skipped
CI / docker-go (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, , evobgp-scheduler) (push) Has been skipped
Updated the module display logic to improve the handling of nullable values for refresh intervals and community selections. Introduced new utility functions for managing nullable select values, ensuring better user experience and data integrity. Additionally, enhanced the visual presentation of module metadata with updated icons and styling, improving overall clarity and usability in the module pages.
This commit is contained in:
@@ -237,7 +237,9 @@
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell class="text-muted-foreground">{m.priority}</TableCell>
|
<TableCell class="text-muted-foreground">{m.priority}</TableCell>
|
||||||
<TableCell class="text-muted-foreground font-mono text-xs">
|
<TableCell class="text-muted-foreground font-mono text-xs">
|
||||||
{m.cron_expr ?? (m.refresh_interval_sec ? `${m.refresh_interval_sec}с` : '—')}
|
{m.cron_expr ?? (m.refresh_interval_sec !== null && m.refresh_interval_sec !== undefined
|
||||||
|
? `${m.refresh_interval_sec}с`
|
||||||
|
: '—')}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{#if m.enabled}
|
{#if m.enabled}
|
||||||
|
|||||||
@@ -74,6 +74,11 @@
|
|||||||
import Save from '@lucide/svelte/icons/save';
|
import Save from '@lucide/svelte/icons/save';
|
||||||
import Upload from '@lucide/svelte/icons/upload';
|
import Upload from '@lucide/svelte/icons/upload';
|
||||||
import Download from '@lucide/svelte/icons/download';
|
import Download from '@lucide/svelte/icons/download';
|
||||||
|
import Blocks from '@lucide/svelte/icons/blocks';
|
||||||
|
import ArrowDownUp from '@lucide/svelte/icons/arrow-down-up';
|
||||||
|
import Timer from '@lucide/svelte/icons/timer';
|
||||||
|
import ShieldCheck from '@lucide/svelte/icons/shield-check';
|
||||||
|
import Network from '@lucide/svelte/icons/network';
|
||||||
import { moduleTypeRu } from '$lib/ui-labels.js';
|
import { moduleTypeRu } from '$lib/ui-labels.js';
|
||||||
|
|
||||||
const moduleId = $derived(page.params.moduleId);
|
const moduleId = $derived(page.params.moduleId);
|
||||||
@@ -150,6 +155,7 @@
|
|||||||
let csvImporting = $state(false);
|
let csvImporting = $state(false);
|
||||||
let csvExporting = $state(false);
|
let csvExporting = $state(false);
|
||||||
let csvFileInput = $state<HTMLInputElement | null>(null);
|
let csvFileInput = $state<HTMLInputElement | null>(null);
|
||||||
|
const NONE_OPTION = '__none__';
|
||||||
const selectedAsCount = $derived(selectedAsIds.size);
|
const selectedAsCount = $derived(selectedAsIds.size);
|
||||||
const selectedCdnCount = $derived(selectedCdnIds.size);
|
const selectedCdnCount = $derived(selectedCdnIds.size);
|
||||||
const selectedDomainCount = $derived(selectedDomainIds.size);
|
const selectedDomainCount = $derived(selectedDomainIds.size);
|
||||||
@@ -716,6 +722,14 @@
|
|||||||
return t || c.community;
|
return t || c.community;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function nullableSelectValue(value: string | null | undefined): string {
|
||||||
|
return value ?? NONE_OPTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromNullableSelect(value: string): string | null {
|
||||||
|
return value === NONE_OPTION ? null : value;
|
||||||
|
}
|
||||||
|
|
||||||
const activeTab = $derived.by(() => {
|
const activeTab = $derived.by(() => {
|
||||||
if (!mod) return 'entries';
|
if (!mod) return 'entries';
|
||||||
switch (mod.type) {
|
switch (mod.type) {
|
||||||
@@ -749,6 +763,12 @@
|
|||||||
<Button variant="ghost" size="icon-sm" href={resolve('/modules')}>
|
<Button variant="ghost" size="icon-sm" href={resolve('/modules')}>
|
||||||
<ArrowLeft class="size-4" />
|
<ArrowLeft class="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
<div
|
||||||
|
class="bg-chart-2/15 text-chart-2 flex size-11 shrink-0 items-center justify-center rounded-xl"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<Blocks class="size-6" />
|
||||||
|
</div>
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
<h1 class="min-w-0 break-words text-2xl font-semibold tracking-tight">{mod.name}</h1>
|
<h1 class="min-w-0 break-words text-2xl font-semibold tracking-tight">{mod.name}</h1>
|
||||||
@@ -780,22 +800,22 @@
|
|||||||
|
|
||||||
<!-- Module metadata -->
|
<!-- Module metadata -->
|
||||||
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-4">
|
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-4">
|
||||||
<Card class="p-3">
|
<Card class="bg-chart-3/10 p-3">
|
||||||
<p class="text-muted-foreground text-xs">Приоритет</p>
|
<p class="text-muted-foreground flex items-center gap-1.5 text-xs"><ArrowDownUp class="size-3.5" />Приоритет</p>
|
||||||
<p class="font-semibold">{mod.priority}</p>
|
<p class="font-semibold">{mod.priority}</p>
|
||||||
</Card>
|
</Card>
|
||||||
<Card class="p-3">
|
<Card class="bg-chart-5/10 p-3">
|
||||||
<p class="text-muted-foreground text-xs">Интервал</p>
|
<p class="text-muted-foreground flex items-center gap-1.5 text-xs"><Timer class="size-3.5" />Интервал</p>
|
||||||
<p class="font-semibold font-mono text-sm">
|
<p class="font-semibold font-mono text-sm">
|
||||||
{mod.cron_expr ?? (mod.refresh_interval_sec ? `${mod.refresh_interval_sec}с` : '—')}
|
{mod.cron_expr ?? (mod.refresh_interval_sec !== null && mod.refresh_interval_sec !== undefined ? `${mod.refresh_interval_sec}с` : '—')}
|
||||||
</p>
|
</p>
|
||||||
</Card>
|
</Card>
|
||||||
<Card class="p-3">
|
<Card class="bg-chart-4/10 p-3">
|
||||||
<p class="text-muted-foreground text-xs">DoH профиль</p>
|
<p class="text-muted-foreground flex items-center gap-1.5 text-xs"><Network class="size-3.5" />DoH профиль</p>
|
||||||
<p class="font-semibold text-sm">{mod.doh_profile_id ? mod.doh_profile_id.slice(0, 8) + '…' : '—'}</p>
|
<p class="font-semibold text-sm">{mod.doh_profile_id ? mod.doh_profile_id.slice(0, 8) + '…' : '—'}</p>
|
||||||
</Card>
|
</Card>
|
||||||
<Card class="p-3">
|
<Card class="bg-chart-2/10 p-3">
|
||||||
<p class="text-muted-foreground text-xs">Community по умолч.</p>
|
<p class="text-muted-foreground flex items-center gap-1.5 text-xs"><ShieldCheck class="size-3.5" />Community по умолч.</p>
|
||||||
<p class="font-semibold text-sm break-all">{communityLabel(mod.default_community_id)}</p>
|
<p class="font-semibold text-sm break-all">{communityLabel(mod.default_community_id)}</p>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
@@ -1032,7 +1052,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell class="text-muted-foreground text-sm">{communityLabel(src.community_id)}</TableCell>
|
<TableCell class="text-muted-foreground text-sm">{communityLabel(src.community_id)}</TableCell>
|
||||||
<TableCell class="text-muted-foreground text-sm">{src.refresh_interval_sec ? `${src.refresh_interval_sec}с` : '—'}</TableCell>
|
<TableCell class="text-muted-foreground text-sm">
|
||||||
|
{src.refresh_interval_sec !== null && src.refresh_interval_sec !== undefined
|
||||||
|
? `${src.refresh_interval_sec}с`
|
||||||
|
: '—'}
|
||||||
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div class="flex gap-1">
|
<div class="flex gap-1">
|
||||||
<Button variant="ghost" size="icon-sm" onclick={() => openCdnEdit(src)}><Pencil class="size-3.5" /></Button>
|
<Button variant="ghost" size="icon-sm" onclick={() => openCdnEdit(src)}><Pencil class="size-3.5" /></Button>
|
||||||
@@ -1252,12 +1276,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<Label for="e-comm">Community по умолчанию</Label>
|
<Label for="e-comm">Community по умолчанию</Label>
|
||||||
<Select type="single" value={editForm.default_community_id ?? ''} onValueChange={(v) => { editForm.default_community_id = v || null; }}>
|
<Select
|
||||||
|
type="single"
|
||||||
|
value={nullableSelectValue(editForm.default_community_id)}
|
||||||
|
onValueChange={(v) => {
|
||||||
|
editForm.default_community_id = fromNullableSelect(v);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<SelectTrigger id="e-comm" class="w-full">
|
<SelectTrigger id="e-comm" class="w-full">
|
||||||
{editForm.default_community_id ? communityLabel(editForm.default_community_id) : 'Не выбрано'}
|
{editForm.default_community_id ? communityLabel(editForm.default_community_id) : 'Не выбрано'}
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="">Не выбрано</SelectItem>
|
<SelectItem value={NONE_OPTION}>Не выбрано</SelectItem>
|
||||||
{#each communities as c (c.id)}
|
{#each communities as c (c.id)}
|
||||||
<SelectItem value={c.id}>{communityOptionLabel(c)}</SelectItem>
|
<SelectItem value={c.id}>{communityOptionLabel(c)}</SelectItem>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -1266,12 +1296,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<Label for="e-doh">DoH профиль</Label>
|
<Label for="e-doh">DoH профиль</Label>
|
||||||
<Select type="single" value={editForm.doh_profile_id ?? ''} onValueChange={(v) => { editForm.doh_profile_id = v || null; }}>
|
<Select
|
||||||
|
type="single"
|
||||||
|
value={nullableSelectValue(editForm.doh_profile_id)}
|
||||||
|
onValueChange={(v) => {
|
||||||
|
editForm.doh_profile_id = fromNullableSelect(v);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<SelectTrigger id="e-doh" class="w-full">
|
<SelectTrigger id="e-doh" class="w-full">
|
||||||
{editForm.doh_profile_id ? (dohProfiles.find(d => d.id === editForm.doh_profile_id)?.url ?? editForm.doh_profile_id) : 'Не выбрано'}
|
{editForm.doh_profile_id ? (dohProfiles.find(d => d.id === editForm.doh_profile_id)?.url ?? editForm.doh_profile_id) : 'Не выбрано'}
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="">Не выбрано</SelectItem>
|
<SelectItem value={NONE_OPTION}>Не выбрано</SelectItem>
|
||||||
{#each dohProfiles as d (d.id)}
|
{#each dohProfiles as d (d.id)}
|
||||||
<SelectItem value={d.id}>{d.url}</SelectItem>
|
<SelectItem value={d.id}>{d.url}</SelectItem>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -1334,12 +1370,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<Label for="as-comm">Community</Label>
|
<Label for="as-comm">Community</Label>
|
||||||
<Select type="single" value={asForm.community_id ?? ''} onValueChange={(v) => { asForm.community_id = v || null; }}>
|
<Select
|
||||||
|
type="single"
|
||||||
|
value={nullableSelectValue(asForm.community_id)}
|
||||||
|
onValueChange={(v) => {
|
||||||
|
asForm.community_id = fromNullableSelect(v);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<SelectTrigger id="as-comm" class="w-full">
|
<SelectTrigger id="as-comm" class="w-full">
|
||||||
{asForm.community_id ? communityLabel(asForm.community_id) : 'Не выбрано'}
|
{asForm.community_id ? communityLabel(asForm.community_id) : 'Не выбрано'}
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="">Не выбрано</SelectItem>
|
<SelectItem value={NONE_OPTION}>Не выбрано</SelectItem>
|
||||||
{#each communities as c (c.id)}
|
{#each communities as c (c.id)}
|
||||||
<SelectItem value={c.id}>{communityOptionLabel(c)}</SelectItem>
|
<SelectItem value={c.id}>{communityOptionLabel(c)}</SelectItem>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -1431,12 +1473,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<Label for="cdn-comm">Community</Label>
|
<Label for="cdn-comm">Community</Label>
|
||||||
<Select type="single" value={cdnForm.community_id ?? ''} onValueChange={(v) => { cdnForm.community_id = v || null; }}>
|
<Select
|
||||||
|
type="single"
|
||||||
|
value={nullableSelectValue(cdnForm.community_id)}
|
||||||
|
onValueChange={(v) => {
|
||||||
|
cdnForm.community_id = fromNullableSelect(v);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<SelectTrigger id="cdn-comm" class="w-full">
|
<SelectTrigger id="cdn-comm" class="w-full">
|
||||||
{cdnForm.community_id ? communityLabel(cdnForm.community_id) : 'Не выбрано'}
|
{cdnForm.community_id ? communityLabel(cdnForm.community_id) : 'Не выбрано'}
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="">Не выбрано</SelectItem>
|
<SelectItem value={NONE_OPTION}>Не выбрано</SelectItem>
|
||||||
{#each communities as c (c.id)}
|
{#each communities as c (c.id)}
|
||||||
<SelectItem value={c.id}>{communityOptionLabel(c)}</SelectItem>
|
<SelectItem value={c.id}>{communityOptionLabel(c)}</SelectItem>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -1518,12 +1566,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<Label for="dom-comm">Community</Label>
|
<Label for="dom-comm">Community</Label>
|
||||||
<Select type="single" value={domainForm.community_id ?? ''} onValueChange={(v) => { domainForm.community_id = v || null; }}>
|
<Select
|
||||||
|
type="single"
|
||||||
|
value={nullableSelectValue(domainForm.community_id)}
|
||||||
|
onValueChange={(v) => {
|
||||||
|
domainForm.community_id = fromNullableSelect(v);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<SelectTrigger id="dom-comm" class="w-full">
|
<SelectTrigger id="dom-comm" class="w-full">
|
||||||
{domainForm.community_id ? communityLabel(domainForm.community_id) : 'Не выбрано'}
|
{domainForm.community_id ? communityLabel(domainForm.community_id) : 'Не выбрано'}
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="">Не выбрано</SelectItem>
|
<SelectItem value={NONE_OPTION}>Не выбрано</SelectItem>
|
||||||
{#each communities as c (c.id)}
|
{#each communities as c (c.id)}
|
||||||
<SelectItem value={c.id}>{communityOptionLabel(c)}</SelectItem>
|
<SelectItem value={c.id}>{communityOptionLabel(c)}</SelectItem>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
Reference in New Issue
Block a user