Refactor table structure and styling in user and IP pages
Publish telemt-api gateway Docker image / test (push) Successful in 24s
Publish telemt-api gateway Docker image / build-and-push (push) Successful in 2m11s

- Updated the table layout in the user traffic and IP detail pages to enhance readability and organization.
- Improved the display of active connections and server information, ensuring a consistent user experience across different views.
- Added responsive styling for table headers and cells, optimizing the interface for various screen sizes.
This commit is contained in:
Denozordec
2026-03-30 15:02:11 +07:00
parent f2683aab5e
commit ac002bf5b4
3 changed files with 108 additions and 87 deletions
+14 -14
View File
@@ -449,12 +449,13 @@
<Card.Description>С GeoIP при включённой базе на шлюзе</Card.Description> <Card.Description>С GeoIP при включённой базе на шлюзе</Card.Description>
</Card.Header> </Card.Header>
<Card.Content> <Card.Content>
<div class="overflow-x-auto">
<Table.Root> <Table.Root>
<Table.Header> <Table.Header>
<Table.Row> <Table.Row>
<Table.Head>IP со страной</Table.Head> <Table.Head class="text-xs sm:text-sm">IP со страной</Table.Head>
<Table.Head>Клиент</Table.Head> <Table.Head class="text-xs sm:text-sm">Клиент</Table.Head>
<Table.Head>Серверы</Table.Head> <Table.Head class="text-xs sm:text-sm">Серверы</Table.Head>
</Table.Row> </Table.Row>
</Table.Header> </Table.Header>
<Table.Body> <Table.Body>
@@ -467,10 +468,10 @@
{:else} {:else}
{#each activeIpServerRows as r (r.username + '|' + r.ip)} {#each activeIpServerRows as r (r.username + '|' + r.ip)}
<Table.Row> <Table.Row>
<Table.Cell class="font-mono text-sm"> <Table.Cell class="font-mono text-xs sm:text-sm whitespace-normal break-all">
{r.activeLabel} {r.activeLabel}
</Table.Cell> </Table.Cell>
<Table.Cell> <Table.Cell class="text-xs sm:text-sm">
<a <a
href="/users/{encodeURIComponent(r.username)}" href="/users/{encodeURIComponent(r.username)}"
class="text-primary hover:underline" class="text-primary hover:underline"
@@ -478,24 +479,23 @@
{r.username} {r.username}
</a> </a>
</Table.Cell> </Table.Cell>
<Table.Cell> <Table.Cell class="text-xs sm:text-sm whitespace-normal">
{#each r.servers as srv, idx (srv)} <div class="flex flex-wrap items-center gap-1">
<a {#each r.servers as srv (srv)}
href="/servers/{encodeURIComponent(srv)}" <a href="/servers/{encodeURIComponent(srv)}" class="inline-flex">
class="text-primary hover:underline" <Badge variant="secondary" class="font-normal px-2 py-0.5">
>
{srv} {srv}
</Badge>
</a> </a>
{#if idx < r.servers.length - 1}
<span class="text-muted-foreground">, </span>
{/if}
{/each} {/each}
</div>
</Table.Cell> </Table.Cell>
</Table.Row> </Table.Row>
{/each} {/each}
{/if} {/if}
</Table.Body> </Table.Body>
</Table.Root> </Table.Root>
</div>
</Card.Content> </Card.Content>
</Card.Root> </Card.Root>
+17 -5
View File
@@ -84,17 +84,29 @@
<Table.Header> <Table.Header>
<Table.Row> <Table.Row>
<Table.Head>Имя</Table.Head> <Table.Head>Имя</Table.Head>
<Table.Head>Ссылки</Table.Head> <Table.Head class="hidden sm:table-cell">Ссылки</Table.Head>
<Table.Head class="text-right">Трафик</Table.Head> <Table.Head class="text-right">Трафик</Table.Head>
<Table.Head class="text-right">Активных IP</Table.Head> <Table.Head class="text-right">Активных IP</Table.Head>
<Table.Head>Истекает</Table.Head> <Table.Head class="hidden sm:table-cell">Истекает</Table.Head>
</Table.Row> </Table.Row>
</Table.Header> </Table.Header>
<Table.Body> <Table.Body>
{#each rows as row (row.username ?? '')} {#each rows as row (row.username ?? '')}
<Table.Row class="cursor-pointer" onclick={() => openUser(row.username)}> <Table.Row
class="cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40"
tabindex="0"
role="button"
aria-label={`Открыть пользователя ${row.username ?? ''}`}
onclick={() => openUser(row.username)}
onkeydown={(e: KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
openUser(row.username);
}
}}
>
<Table.Cell class="font-medium">{row.username}</Table.Cell> <Table.Cell class="font-medium">{row.username}</Table.Cell>
<Table.Cell> <Table.Cell class="hidden sm:table-cell">
<div class="flex flex-wrap gap-1"> <div class="flex flex-wrap gap-1">
{#each row.links?.tls ?? [] as link (link)} {#each row.links?.tls ?? [] as link (link)}
<Button <Button
@@ -127,7 +139,7 @@
<span class="text-muted-foreground"> / {row.max_unique_ips}</span> <span class="text-muted-foreground"> / {row.max_unique_ips}</span>
{/if} {/if}
</Table.Cell> </Table.Cell>
<Table.Cell class="text-sm text-muted-foreground"> <Table.Cell class="hidden sm:table-cell text-sm text-muted-foreground">
{row.expiration_rfc3339 ? new Date(row.expiration_rfc3339).toLocaleString() : '—'} {row.expiration_rfc3339 ? new Date(row.expiration_rfc3339).toLocaleString() : '—'}
</Table.Cell> </Table.Cell>
</Table.Row> </Table.Row>
+16 -7
View File
@@ -167,26 +167,34 @@
</Alert> </Alert>
{/if} {/if}
<div class="overflow-x-auto">
<Table.Root> <Table.Root>
<Table.Header> <Table.Header>
<Table.Row> <Table.Row>
<Table.Head>IP</Table.Head> <Table.Head class="text-xs sm:text-sm">IP</Table.Head>
<Table.Head>Серверы</Table.Head> <Table.Head class="text-xs sm:text-sm">Серверы</Table.Head>
<Table.Head>Geo / ASN</Table.Head> <Table.Head class="text-xs sm:text-sm">Geo / ASN</Table.Head>
</Table.Row> </Table.Row>
</Table.Header> </Table.Header>
<Table.Body> <Table.Body>
{#if ipsForUser.length === 0} {#if ipsForUser.length === 0}
<Table.Row> <Table.Row>
<Table.Cell colspan="3" class="p-4 text-center text-muted-foreground"> <Table.Cell
colspan="3"
class="p-4 text-center text-muted-foreground whitespace-normal"
>
Нет активных IP Нет активных IP
</Table.Cell> </Table.Cell>
</Table.Row> </Table.Row>
{:else} {:else}
{#each ipsForUser as ip (ip.ip ?? '')} {#each ipsForUser as ip (ip.ip ?? '')}
<Table.Row> <Table.Row>
<Table.Cell class="font-mono text-sm">{ip.ip}</Table.Cell> <Table.Cell
<Table.Cell class="text-sm text-muted-foreground"> class="font-mono text-xs sm:text-sm whitespace-normal break-all"
>
{ip.ip}
</Table.Cell>
<Table.Cell class="text-xs sm:text-sm text-muted-foreground whitespace-normal">
active: {((ip.active_on_servers ?? []).filter(Boolean) as string[]).join(', ') || '—'} active: {((ip.active_on_servers ?? []).filter(Boolean) as string[]).join(', ') || '—'}
{#if (ip.recent_on_servers?.length ?? 0) > 0} {#if (ip.recent_on_servers?.length ?? 0) > 0}
<br /> <br />
@@ -197,7 +205,7 @@
primary: {ip.primary_server} primary: {ip.primary_server}
{/if} {/if}
</Table.Cell> </Table.Cell>
<Table.Cell class="text-sm"> <Table.Cell class="text-xs sm:text-sm whitespace-normal">
<div class="space-y-1"> <div class="space-y-1">
<div> <div>
<span class="mr-1">{flagEmoji(ip.country_code ?? undefined)}</span> <span class="mr-1">{flagEmoji(ip.country_code ?? undefined)}</span>
@@ -221,6 +229,7 @@
{/if} {/if}
</Table.Body> </Table.Body>
</Table.Root> </Table.Root>
</div>
</Card.Content> </Card.Content>
</Card.Root> </Card.Root>
{/if} {/if}