Implement active IP server rows display in user traffic table
- Introduced a new derived state to calculate and display active IP-server pairs for each user, enhancing the visibility of user connections. - Replaced the previous badge display with a structured table format, improving data organization and readability. - Updated the UI to conditionally render active connections, providing clearer feedback when no active connections are present.
This commit is contained in:
+95
-14
@@ -205,9 +205,60 @@
|
|||||||
function chipLabel(ip: components['schemas']['IPAssignments']): string {
|
function chipLabel(ip: components['schemas']['IPAssignments']): string {
|
||||||
const cc = ip.country_code?.trim();
|
const cc = ip.country_code?.trim();
|
||||||
const flag = flagEmoji(cc ?? undefined);
|
const flag = flagEmoji(cc ?? undefined);
|
||||||
const short = ip.ip && ip.ip.length > 12 ? ip.ip.slice(0, 8) + '…' : (ip.ip ?? '');
|
const addr = ip.ip ?? '';
|
||||||
return `${flag} ${cc ?? '?'} ${short}`.trim();
|
return `${flag} ${cc ?? '?'} ${addr}`.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ActiveIpServerRow = {
|
||||||
|
username: string;
|
||||||
|
ip: string;
|
||||||
|
country_code: string | null | undefined;
|
||||||
|
server: string;
|
||||||
|
activeLabel: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
let activeIpServerRows = $derived.by((): ActiveIpServerRow[] => {
|
||||||
|
const out: ActiveIpServerRow[] = [];
|
||||||
|
const seen = new Set<string>();
|
||||||
|
const maxUniqueIpRows = 80;
|
||||||
|
const maxOutRows = 2000;
|
||||||
|
|
||||||
|
for (const ur of uniqueIps?.slice(0, maxUniqueIpRows) ?? []) {
|
||||||
|
const username = ur.username ?? '';
|
||||||
|
if (!username) continue;
|
||||||
|
|
||||||
|
for (const ipa of ur.ips ?? []) {
|
||||||
|
const ip = ipa.ip ?? '';
|
||||||
|
if (!ip) continue;
|
||||||
|
|
||||||
|
const servers = ipa.active_on_servers ?? [];
|
||||||
|
if (servers.length === 0) continue;
|
||||||
|
|
||||||
|
for (const server of servers) {
|
||||||
|
if (!server) continue;
|
||||||
|
const key = `${username}|${ip}|${server}`;
|
||||||
|
if (seen.has(key)) continue;
|
||||||
|
seen.add(key);
|
||||||
|
|
||||||
|
out.push({
|
||||||
|
username,
|
||||||
|
ip,
|
||||||
|
country_code: ipa.country_code,
|
||||||
|
server,
|
||||||
|
activeLabel: chipLabel(ipa)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (out.length >= maxOutRows) return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.sort(
|
||||||
|
(a, b) =>
|
||||||
|
a.ip.localeCompare(b.ip) || a.server.localeCompare(b.server) || a.username.localeCompare(b.username)
|
||||||
|
);
|
||||||
|
return out;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="mb-6 flex flex-wrap items-center justify-between gap-4">
|
<div class="mb-6 flex flex-wrap items-center justify-between gap-4">
|
||||||
@@ -387,18 +438,48 @@
|
|||||||
<Card.Description>С GeoIP при включённой базе на шлюзе</Card.Description>
|
<Card.Description>С GeoIP при включённой базе на шлюзе</Card.Description>
|
||||||
</Card.Header>
|
</Card.Header>
|
||||||
<Card.Content>
|
<Card.Content>
|
||||||
<div class="flex flex-wrap gap-2">
|
<Table.Root>
|
||||||
{#each (uniqueIps ?? []).slice(0, 80) as row (row.username)}
|
<Table.Header>
|
||||||
{#each row.ips ?? [] as ip (`${row.username ?? ''}-${ip.ip ?? ''}`)}
|
<Table.Row>
|
||||||
<Badge variant="secondary" class="font-normal" title={ip.ip}>
|
<Table.Head>IP со страной</Table.Head>
|
||||||
{chipLabel(ip)}
|
<Table.Head>Клиент</Table.Head>
|
||||||
</Badge>
|
<Table.Head>Сервер</Table.Head>
|
||||||
{/each}
|
</Table.Row>
|
||||||
{/each}
|
</Table.Header>
|
||||||
{#if (uniqueIps?.length ?? 0) === 0}
|
<Table.Body>
|
||||||
<span class="text-sm text-muted-foreground">Нет данных</span>
|
{#if (activeIpServerRows?.length ?? 0) === 0}
|
||||||
{/if}
|
<Table.Row>
|
||||||
</div>
|
<Table.Cell colspan="3" class="p-4 text-center text-muted-foreground">
|
||||||
|
Нет активных подключений
|
||||||
|
</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
{:else}
|
||||||
|
{#each activeIpServerRows as r (r.username + '|' + r.ip + '|' + r.server)}
|
||||||
|
<Table.Row>
|
||||||
|
<Table.Cell class="font-mono text-sm">
|
||||||
|
{r.activeLabel}
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell>
|
||||||
|
<a
|
||||||
|
href="/users/{encodeURIComponent(r.username)}"
|
||||||
|
class="text-primary hover:underline"
|
||||||
|
>
|
||||||
|
{r.username}
|
||||||
|
</a>
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell>
|
||||||
|
<a
|
||||||
|
href="/servers/{encodeURIComponent(r.server)}"
|
||||||
|
class="text-primary hover:underline"
|
||||||
|
>
|
||||||
|
{r.server}
|
||||||
|
</a>
|
||||||
|
</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</Table.Body>
|
||||||
|
</Table.Root>
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
</Card.Root>
|
</Card.Root>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user