feat: improve UI layout and responsiveness in module and operations pages. Refactor component structures for better alignment and spacing, ensuring a more consistent user experience across different screen sizes. Enhance button placements and text wrapping for improved readability.

This commit is contained in:
Denozordec
2026-04-06 18:48:20 +07:00
parent 4ba5b03f0b
commit 233f29b437
2 changed files with 42 additions and 37 deletions
+21 -21
View File
@@ -498,14 +498,14 @@
{:else if mod} {:else if mod}
<div class="space-y-6"> <div class="space-y-6">
<!-- Header --> <!-- Header -->
<div class="flex items-start justify-between"> <div class="flex flex-col gap-3 xl:flex-row xl:items-start xl:justify-between">
<div class="flex items-start gap-3"> <div class="flex min-w-0 items-start gap-3">
<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> <div class="min-w-0">
<div class="flex items-center gap-2"> <div class="flex flex-wrap items-center gap-2">
<h1 class="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>
<Badge variant="outline">{mod.type}</Badge> <Badge variant="outline">{mod.type}</Badge>
{#if mod.enabled} {#if mod.enabled}
<Badge variant="default" class="text-xs">вкл</Badge> <Badge variant="default" class="text-xs">вкл</Badge>
@@ -513,10 +513,10 @@
<Badge variant="secondary" class="text-xs">выкл</Badge> <Badge variant="secondary" class="text-xs">выкл</Badge>
{/if} {/if}
</div> </div>
<p class="text-muted-foreground mt-0.5 text-sm font-mono text-xs">{mod.id}</p> <p class="text-muted-foreground mt-0.5 break-all font-mono text-xs">{mod.id}</p>
</div> </div>
</div> </div>
<div class="flex gap-2"> <div class="flex flex-wrap gap-2">
<Button variant="outline" size="sm" onclick={refreshMod} disabled={refreshing}> <Button variant="outline" size="sm" onclick={refreshMod} disabled={refreshing}>
<RefreshCw class={refreshing ? 'animate-spin' : ''} /> <RefreshCw class={refreshing ? 'animate-spin' : ''} />
Refresh Refresh
@@ -533,7 +533,7 @@
</div> </div>
<!-- Module metadata --> <!-- Module metadata -->
<div class="grid grid-cols-2 gap-3 sm: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="p-3">
<p class="text-muted-foreground text-xs">Приоритет</p> <p class="text-muted-foreground text-xs">Приоритет</p>
<p class="font-semibold">{mod.priority}</p> <p class="font-semibold">{mod.priority}</p>
@@ -550,21 +550,21 @@
</Card> </Card>
<Card class="p-3"> <Card class="p-3">
<p class="text-muted-foreground text-xs">Community по умолч.</p> <p class="text-muted-foreground text-xs">Community по умолч.</p>
<p class="font-semibold text-sm">{communityLabel(mod.default_community_id)}</p> <p class="font-semibold text-sm break-all">{communityLabel(mod.default_community_id)}</p>
</Card> </Card>
</div> </div>
<!-- Entries by type --> <!-- Entries by type -->
{#if mod.type === 'AS_PREFIXES'} {#if mod.type === 'AS_PREFIXES'}
<Card> <Card>
<CardHeader class="flex flex-row items-center justify-between pb-2"> <CardHeader class="flex flex-col gap-3 pb-2 sm:flex-row sm:items-center sm:justify-between">
<div> <div class="min-w-0 flex-1">
<CardTitle class="text-base">AS-записи</CardTitle> <CardTitle class="text-base">AS-записи</CardTitle>
<CardDescription> <CardDescription>
Номер AS и community; имя, число префиксов и дата обновляются при успешном обновлении модуля (RIPEstat) Номер AS и community; имя, число префиксов и дата обновляются при успешном обновлении модуля (RIPEstat)
</CardDescription> </CardDescription>
</div> </div>
<Button size="sm" onclick={openAsCreate}><Plus />Добавить</Button> <Button size="sm" class="shrink-0 self-start sm:self-auto" onclick={openAsCreate}><Plus />Добавить</Button>
</CardHeader> </CardHeader>
<CardContent class="p-0"> <CardContent class="p-0">
<Table> <Table>
@@ -615,12 +615,12 @@
</Card> </Card>
{:else if mod.type === 'CDN_CIDRS'} {:else if mod.type === 'CDN_CIDRS'}
<Card> <Card>
<CardHeader class="flex flex-row items-center justify-between pb-2"> <CardHeader class="flex flex-col gap-3 pb-2 sm:flex-row sm:items-center sm:justify-between">
<div> <div class="min-w-0 flex-1">
<CardTitle class="text-base">CDN-источники</CardTitle> <CardTitle class="text-base">CDN-источники</CardTitle>
<CardDescription>URL источников для скачивания списков CIDR</CardDescription> <CardDescription>URL источников для скачивания списков CIDR</CardDescription>
</div> </div>
<Button size="sm" onclick={openCdnCreate}><Plus />Добавить</Button> <Button size="sm" class="shrink-0 self-start sm:self-auto" onclick={openCdnCreate}><Plus />Добавить</Button>
</CardHeader> </CardHeader>
<CardContent class="p-0"> <CardContent class="p-0">
<Table> <Table>
@@ -667,12 +667,12 @@
</Card> </Card>
{:else if mod.type === 'DOMAINS'} {:else if mod.type === 'DOMAINS'}
<Card> <Card>
<CardHeader class="flex flex-row items-center justify-between pb-2"> <CardHeader class="flex flex-col gap-3 pb-2 sm:flex-row sm:items-center sm:justify-between">
<div> <div class="min-w-0 flex-1">
<CardTitle class="text-base">Домены</CardTitle> <CardTitle class="text-base">Домены</CardTitle>
<CardDescription>FQDN для резолвинга через DoH</CardDescription> <CardDescription>FQDN для резолвинга через DoH</CardDescription>
</div> </div>
<Button size="sm" onclick={openDomainCreate}><Plus />Добавить</Button> <Button size="sm" class="shrink-0 self-start sm:self-auto" onclick={openDomainCreate}><Plus />Добавить</Button>
</CardHeader> </CardHeader>
<CardContent class="p-0"> <CardContent class="p-0">
<Table> <Table>
@@ -706,12 +706,12 @@
</Card> </Card>
{:else if mod.type === 'IP_RANGES'} {:else if mod.type === 'IP_RANGES'}
<Card> <Card>
<CardHeader class="flex flex-row items-center justify-between pb-2"> <CardHeader class="flex flex-col gap-3 pb-2 sm:flex-row sm:items-center sm:justify-between">
<div> <div class="min-w-0 flex-1">
<CardTitle class="text-base">IP-диапазоны</CardTitle> <CardTitle class="text-base">IP-диапазоны</CardTitle>
<CardDescription>Статические CIDR для анонса</CardDescription> <CardDescription>Статические CIDR для анонса</CardDescription>
</div> </div>
<Button size="sm" onclick={openIpCreate}><Plus />Добавить</Button> <Button size="sm" class="shrink-0 self-start sm:self-auto" onclick={openIpCreate}><Plus />Добавить</Button>
</CardHeader> </CardHeader>
<CardContent class="p-0"> <CardContent class="p-0">
<Table> <Table>
+21 -16
View File
@@ -446,24 +446,29 @@
<!-- Quick actions + локальный статус BIRD (хост API, если задан EVOBGP_BIRDC_SOCKET) --> <!-- Quick actions + локальный статус BIRD (хост API, если задан EVOBGP_BIRDC_SOCKET) -->
<div class="grid gap-3 sm:grid-cols-2 xl:grid-cols-3"> <div class="grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
<Card class="p-4"> <Card class="p-4">
<div class="flex items-center justify-between"> <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div> <div class="min-w-0 flex-1">
<p class="font-semibold">Apply all speakers</p> <p class="font-semibold">Apply all speakers</p>
<p class="text-muted-foreground text-sm">Применить текущую конфигурацию на всех спикерах</p> <p class="text-muted-foreground text-sm">Применить текущую конфигурацию на всех спикерах</p>
</div> </div>
<Button onclick={() => (applyConfirm = true)} disabled={applying}> <Button class="shrink-0 self-start sm:self-auto" onclick={() => (applyConfirm = true)} disabled={applying}>
<Play /> <Play />
Apply Apply
</Button> </Button>
</div> </div>
</Card> </Card>
<Card class="p-4"> <Card class="p-4">
<div class="flex items-center justify-between"> <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div> <div class="min-w-0 flex-1">
<p class="font-semibold">BIRD Reload</p> <p class="font-semibold">BIRD Reload</p>
<p class="text-muted-foreground text-sm">Перезагрузить конфигурацию BIRD на всех спикерах</p> <p class="text-muted-foreground text-sm">Перезагрузить конфигурацию BIRD на всех спикерах</p>
</div> </div>
<Button variant="outline" onclick={() => (reloadConfirm = true)} disabled={reloading}> <Button
variant="outline"
class="shrink-0 self-start sm:self-auto"
onclick={() => (reloadConfirm = true)}
disabled={reloading}
>
<RotateCcw /> <RotateCcw />
Reload Reload
</Button> </Button>
@@ -526,12 +531,12 @@
<!-- Revisions --> <!-- Revisions -->
<TabsContent value="revisions" class="mt-4"> <TabsContent value="revisions" class="mt-4">
<Card> <Card>
<CardHeader class="flex flex-row items-center justify-between pb-2"> <CardHeader class="flex flex-col gap-3 pb-2 sm:flex-row sm:items-center sm:justify-between">
<div> <div class="min-w-0 flex-1">
<CardTitle class="text-base">История ревизий</CardTitle> <CardTitle class="text-base">История ревизий</CardTitle>
<CardDescription>Создаются задачей module_refresh (CDN / IP / AS и т.д.); в превью есть полный текст BIRD с инклюдами</CardDescription> <CardDescription>Создаются задачей module_refresh (CDN / IP / AS и т.д.); в превью есть полный текст BIRD с инклюдами</CardDescription>
</div> </div>
<Button variant="outline" size="sm" onclick={loadRevisions} disabled={revLoading}> <Button variant="outline" size="sm" class="shrink-0 self-start sm:self-auto" onclick={loadRevisions} disabled={revLoading}>
<RefreshCw class={revLoading ? 'animate-spin' : ''} /> <RefreshCw class={revLoading ? 'animate-spin' : ''} />
</Button> </Button>
</CardHeader> </CardHeader>
@@ -585,20 +590,20 @@
<CardDescription>Выберите ID двух ревизий для сравнения</CardDescription> <CardDescription>Выберите ID двух ревизий для сравнения</CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div class="flex gap-2"> <div class="flex flex-col gap-2 sm:flex-row">
<select bind:value={diffRevA} class="border-border bg-background h-8 flex-1 rounded-md border px-2 text-sm"> <select bind:value={diffRevA} class="border-border bg-background h-8 min-w-0 flex-1 rounded-md border px-2 text-sm">
<option value="">Ревизия A</option> <option value="">Ревизия A</option>
{#each revisions as rev} {#each revisions as rev}
<option value={rev.id}>{rev.id.slice(0, 8)} ({formatDate(rev.created_at)})</option> <option value={rev.id}>{rev.id.slice(0, 8)} ({formatDate(rev.created_at)})</option>
{/each} {/each}
</select> </select>
<select bind:value={diffRevB} class="border-border bg-background h-8 flex-1 rounded-md border px-2 text-sm"> <select bind:value={diffRevB} class="border-border bg-background h-8 min-w-0 flex-1 rounded-md border px-2 text-sm">
<option value="">Ревизия B</option> <option value="">Ревизия B</option>
{#each revisions as rev} {#each revisions as rev}
<option value={rev.id}>{rev.id.slice(0, 8)} ({formatDate(rev.created_at)})</option> <option value={rev.id}>{rev.id.slice(0, 8)} ({formatDate(rev.created_at)})</option>
{/each} {/each}
</select> </select>
<Button size="sm" onclick={loadDiff} disabled={diffLoading}> <Button size="sm" class="shrink-0 self-start sm:self-auto" onclick={loadDiff} disabled={diffLoading}>
{diffLoading ? 'Загрузка…' : 'Сравнить'} {diffLoading ? 'Загрузка…' : 'Сравнить'}
</Button> </Button>
</div> </div>
@@ -633,12 +638,12 @@
<!-- Jobs --> <!-- Jobs -->
<TabsContent value="jobs" class="mt-4"> <TabsContent value="jobs" class="mt-4">
<Card> <Card>
<CardHeader class="flex flex-row items-center justify-between pb-2"> <CardHeader class="flex flex-col gap-3 pb-2 sm:flex-row sm:items-center sm:justify-between">
<div> <div class="min-w-0 flex-1">
<CardTitle class="text-base">Задачи</CardTitle> <CardTitle class="text-base">Задачи</CardTitle>
<CardDescription>Фоновые задачи (ingest, apply, refresh)</CardDescription> <CardDescription>Фоновые задачи (ingest, apply, refresh)</CardDescription>
</div> </div>
<Button variant="outline" size="sm" onclick={loadJobs} disabled={jobsLoading}> <Button variant="outline" size="sm" class="shrink-0 self-start sm:self-auto" onclick={loadJobs} disabled={jobsLoading}>
<RefreshCw class={jobsLoading ? 'animate-spin' : ''} /> <RefreshCw class={jobsLoading ? 'animate-spin' : ''} />
</Button> </Button>
</CardHeader> </CardHeader>