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 {
|
||||
const cc = ip.country_code?.trim();
|
||||
const flag = flagEmoji(cc ?? undefined);
|
||||
const short = ip.ip && ip.ip.length > 12 ? ip.ip.slice(0, 8) + '…' : (ip.ip ?? '');
|
||||
return `${flag} ${cc ?? '?'} ${short}`.trim();
|
||||
const addr = ip.ip ?? '';
|
||||
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>
|
||||
|
||||
<div class="mb-6 flex flex-wrap items-center justify-between gap-4">
|
||||
@@ -387,18 +438,48 @@
|
||||
<Card.Description>С GeoIP при включённой базе на шлюзе</Card.Description>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{#each (uniqueIps ?? []).slice(0, 80) as row (row.username)}
|
||||
{#each row.ips ?? [] as ip (`${row.username ?? ''}-${ip.ip ?? ''}`)}
|
||||
<Badge variant="secondary" class="font-normal" title={ip.ip}>
|
||||
{chipLabel(ip)}
|
||||
</Badge>
|
||||
{/each}
|
||||
{/each}
|
||||
{#if (uniqueIps?.length ?? 0) === 0}
|
||||
<span class="text-sm text-muted-foreground">Нет данных</span>
|
||||
{/if}
|
||||
</div>
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.Head>IP со страной</Table.Head>
|
||||
<Table.Head>Клиент</Table.Head>
|
||||
<Table.Head>Сервер</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#if (activeIpServerRows?.length ?? 0) === 0}
|
||||
<Table.Row>
|
||||
<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.Root>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user