mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-25 12:06:37 +00:00
Connection log page working
This commit is contained in:
@@ -581,6 +581,7 @@ export type SubnetProxyTargetV2 = {
|
|||||||
max: number;
|
max: number;
|
||||||
protocol: "tcp" | "udp";
|
protocol: "tcp" | "udp";
|
||||||
}[];
|
}[];
|
||||||
|
resourceId?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function generateSubnetProxyTargetV2(
|
export function generateSubnetProxyTargetV2(
|
||||||
@@ -617,7 +618,8 @@ export function generateSubnetProxyTargetV2(
|
|||||||
sourcePrefixes: [],
|
sourcePrefixes: [],
|
||||||
destPrefix: destination,
|
destPrefix: destination,
|
||||||
portRange,
|
portRange,
|
||||||
disableIcmp
|
disableIcmp,
|
||||||
|
resourceId: siteResource.siteResourceId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -628,7 +630,8 @@ export function generateSubnetProxyTargetV2(
|
|||||||
destPrefix: `${siteResource.aliasAddress}/32`,
|
destPrefix: `${siteResource.aliasAddress}/32`,
|
||||||
rewriteTo: destination,
|
rewriteTo: destination,
|
||||||
portRange,
|
portRange,
|
||||||
disableIcmp
|
disableIcmp,
|
||||||
|
resourceId: siteResource.siteResourceId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else if (siteResource.mode == "cidr") {
|
} else if (siteResource.mode == "cidr") {
|
||||||
@@ -636,7 +639,8 @@ export function generateSubnetProxyTargetV2(
|
|||||||
sourcePrefixes: [],
|
sourcePrefixes: [],
|
||||||
destPrefix: siteResource.destination,
|
destPrefix: siteResource.destination,
|
||||||
portRange,
|
portRange,
|
||||||
disableIcmp
|
disableIcmp,
|
||||||
|
resourceId: siteResource.siteResourceId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
siteResources,
|
siteResources,
|
||||||
sites,
|
sites,
|
||||||
clients,
|
clients,
|
||||||
|
users,
|
||||||
primaryDb
|
primaryDb
|
||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import { registry } from "@server/openApi";
|
import { registry } from "@server/openApi";
|
||||||
@@ -193,6 +194,13 @@ async function enrichWithDetails(
|
|||||||
.filter((id): id is number => id !== null && id !== undefined)
|
.filter((id): id is number => id !== null && id !== undefined)
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
|
const userIds = [
|
||||||
|
...new Set(
|
||||||
|
logs
|
||||||
|
.map((log) => log.userId)
|
||||||
|
.filter((id): id is string => id !== null && id !== undefined)
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
// Fetch resource details from main database
|
// Fetch resource details from main database
|
||||||
const resourceMap = new Map<
|
const resourceMap = new Map<
|
||||||
@@ -235,18 +243,46 @@ async function enrichWithDetails(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetch client details from main database
|
// Fetch client details from main database
|
||||||
const clientMap = new Map<number, { name: string }>();
|
const clientMap = new Map<
|
||||||
|
number,
|
||||||
|
{ name: string; niceId: string; type: string }
|
||||||
|
>();
|
||||||
if (clientIds.length > 0) {
|
if (clientIds.length > 0) {
|
||||||
const clientDetails = await primaryDb
|
const clientDetails = await primaryDb
|
||||||
.select({
|
.select({
|
||||||
clientId: clients.clientId,
|
clientId: clients.clientId,
|
||||||
name: clients.name
|
name: clients.name,
|
||||||
|
niceId: clients.niceId,
|
||||||
|
type: clients.type
|
||||||
})
|
})
|
||||||
.from(clients)
|
.from(clients)
|
||||||
.where(inArray(clients.clientId, clientIds));
|
.where(inArray(clients.clientId, clientIds));
|
||||||
|
|
||||||
for (const c of clientDetails) {
|
for (const c of clientDetails) {
|
||||||
clientMap.set(c.clientId, { name: c.name });
|
clientMap.set(c.clientId, {
|
||||||
|
name: c.name,
|
||||||
|
niceId: c.niceId,
|
||||||
|
type: c.type
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch user details from main database
|
||||||
|
const userMap = new Map<
|
||||||
|
string,
|
||||||
|
{ email: string | null }
|
||||||
|
>();
|
||||||
|
if (userIds.length > 0) {
|
||||||
|
const userDetails = await primaryDb
|
||||||
|
.select({
|
||||||
|
userId: users.userId,
|
||||||
|
email: users.email
|
||||||
|
})
|
||||||
|
.from(users)
|
||||||
|
.where(inArray(users.userId, userIds));
|
||||||
|
|
||||||
|
for (const u of userDetails) {
|
||||||
|
userMap.set(u.userId, { email: u.email });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,6 +303,15 @@ async function enrichWithDetails(
|
|||||||
: null,
|
: null,
|
||||||
clientName: log.clientId
|
clientName: log.clientId
|
||||||
? clientMap.get(log.clientId)?.name ?? null
|
? clientMap.get(log.clientId)?.name ?? null
|
||||||
|
: null,
|
||||||
|
clientNiceId: log.clientId
|
||||||
|
? clientMap.get(log.clientId)?.niceId ?? null
|
||||||
|
: null,
|
||||||
|
clientType: log.clientId
|
||||||
|
? clientMap.get(log.clientId)?.type ?? null
|
||||||
|
: null,
|
||||||
|
userEmail: log.userId
|
||||||
|
? userMap.get(log.userId)?.email ?? null
|
||||||
: null
|
: null
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -290,10 +335,111 @@ async function queryUniqueFilterAttributes(
|
|||||||
.from(connectionAuditLog)
|
.from(connectionAuditLog)
|
||||||
.where(baseConditions);
|
.where(baseConditions);
|
||||||
|
|
||||||
|
// Get unique destination addresses
|
||||||
|
const uniqueDestAddrs = await logsDb
|
||||||
|
.selectDistinct({
|
||||||
|
destAddr: connectionAuditLog.destAddr
|
||||||
|
})
|
||||||
|
.from(connectionAuditLog)
|
||||||
|
.where(baseConditions);
|
||||||
|
|
||||||
|
// Get unique client IDs
|
||||||
|
const uniqueClients = await logsDb
|
||||||
|
.selectDistinct({
|
||||||
|
clientId: connectionAuditLog.clientId
|
||||||
|
})
|
||||||
|
.from(connectionAuditLog)
|
||||||
|
.where(baseConditions);
|
||||||
|
|
||||||
|
// Get unique resource IDs
|
||||||
|
const uniqueResources = await logsDb
|
||||||
|
.selectDistinct({
|
||||||
|
siteResourceId: connectionAuditLog.siteResourceId
|
||||||
|
})
|
||||||
|
.from(connectionAuditLog)
|
||||||
|
.where(baseConditions);
|
||||||
|
|
||||||
|
// Get unique user IDs
|
||||||
|
const uniqueUsers = await logsDb
|
||||||
|
.selectDistinct({
|
||||||
|
userId: connectionAuditLog.userId
|
||||||
|
})
|
||||||
|
.from(connectionAuditLog)
|
||||||
|
.where(baseConditions);
|
||||||
|
|
||||||
|
// Enrich client IDs with names from main database
|
||||||
|
const clientIds = uniqueClients
|
||||||
|
.map((row) => row.clientId)
|
||||||
|
.filter((id): id is number => id !== null);
|
||||||
|
|
||||||
|
let clientsWithNames: Array<{ id: number; name: string }> = [];
|
||||||
|
if (clientIds.length > 0) {
|
||||||
|
const clientDetails = await primaryDb
|
||||||
|
.select({
|
||||||
|
clientId: clients.clientId,
|
||||||
|
name: clients.name
|
||||||
|
})
|
||||||
|
.from(clients)
|
||||||
|
.where(inArray(clients.clientId, clientIds));
|
||||||
|
|
||||||
|
clientsWithNames = clientDetails.map((c) => ({
|
||||||
|
id: c.clientId,
|
||||||
|
name: c.name
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enrich resource IDs with names from main database
|
||||||
|
const resourceIds = uniqueResources
|
||||||
|
.map((row) => row.siteResourceId)
|
||||||
|
.filter((id): id is number => id !== null);
|
||||||
|
|
||||||
|
let resourcesWithNames: Array<{ id: number; name: string | null }> = [];
|
||||||
|
if (resourceIds.length > 0) {
|
||||||
|
const resourceDetails = await primaryDb
|
||||||
|
.select({
|
||||||
|
siteResourceId: siteResources.siteResourceId,
|
||||||
|
name: siteResources.name
|
||||||
|
})
|
||||||
|
.from(siteResources)
|
||||||
|
.where(inArray(siteResources.siteResourceId, resourceIds));
|
||||||
|
|
||||||
|
resourcesWithNames = resourceDetails.map((r) => ({
|
||||||
|
id: r.siteResourceId,
|
||||||
|
name: r.name
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enrich user IDs with emails from main database
|
||||||
|
const userIdsList = uniqueUsers
|
||||||
|
.map((row) => row.userId)
|
||||||
|
.filter((id): id is string => id !== null);
|
||||||
|
|
||||||
|
let usersWithEmails: Array<{ id: string; email: string | null }> = [];
|
||||||
|
if (userIdsList.length > 0) {
|
||||||
|
const userDetails = await primaryDb
|
||||||
|
.select({
|
||||||
|
userId: users.userId,
|
||||||
|
email: users.email
|
||||||
|
})
|
||||||
|
.from(users)
|
||||||
|
.where(inArray(users.userId, userIdsList));
|
||||||
|
|
||||||
|
usersWithEmails = userDetails.map((u) => ({
|
||||||
|
id: u.userId,
|
||||||
|
email: u.email
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
protocols: uniqueProtocols
|
protocols: uniqueProtocols
|
||||||
.map((row) => row.protocol)
|
.map((row) => row.protocol)
|
||||||
.filter((protocol): protocol is string => protocol !== null)
|
.filter((protocol): protocol is string => protocol !== null),
|
||||||
|
destAddrs: uniqueDestAddrs
|
||||||
|
.map((row) => row.destAddr)
|
||||||
|
.filter((addr): addr is string => addr !== null),
|
||||||
|
clients: clientsWithNames,
|
||||||
|
resources: resourcesWithNames,
|
||||||
|
users: usersWithEmails
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,7 +488,7 @@ export async function queryConnectionAuditLogs(
|
|||||||
|
|
||||||
const logsRaw = await baseQuery.limit(data.limit).offset(data.offset);
|
const logsRaw = await baseQuery.limit(data.limit).offset(data.offset);
|
||||||
|
|
||||||
// Enrich with resource, site, and client details
|
// Enrich with resource, site, client, and user details
|
||||||
const log = await enrichWithDetails(logsRaw);
|
const log = await enrichWithDetails(logsRaw);
|
||||||
|
|
||||||
const totalCountResult = await countConnectionQuery(data);
|
const totalCountResult = await countConnectionQuery(data);
|
||||||
|
|||||||
@@ -277,6 +277,8 @@ export const handleConnectionLogMessage: MessageHandler = async (context) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.debug(`Sessions: ${JSON.stringify(sessions)}`)
|
||||||
|
|
||||||
// Build a map from sourceAddr → { clientId, userId } by querying clients
|
// Build a map from sourceAddr → { clientId, userId } by querying clients
|
||||||
// whose subnet field matches exactly. Client subnets are stored with the
|
// whose subnet field matches exactly. Client subnets are stored with the
|
||||||
// org's CIDR suffix (e.g. "100.90.128.5/16"), so we reconstruct that from
|
// org's CIDR suffix (e.g. "100.90.128.5/16"), so we reconstruct that from
|
||||||
@@ -295,9 +297,15 @@ export const handleConnectionLogMessage: MessageHandler = async (context) => {
|
|||||||
if (uniqueSourceAddrs.size > 0) {
|
if (uniqueSourceAddrs.size > 0) {
|
||||||
// Construct the exact subnet strings as stored in the DB
|
// Construct the exact subnet strings as stored in the DB
|
||||||
const subnetQueries = Array.from(uniqueSourceAddrs).map(
|
const subnetQueries = Array.from(uniqueSourceAddrs).map(
|
||||||
(addr) => `${addr}${cidrSuffix}`
|
(addr) => {
|
||||||
|
// Strip port if present (e.g. "100.90.128.1:38004" → "100.90.128.1")
|
||||||
|
const ip = addr.includes(":") ? addr.split(":")[0] : addr;
|
||||||
|
return `${ip}${cidrSuffix}`;
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
logger.debug(`Subnet queries: ${JSON.stringify(subnetQueries)}`);
|
||||||
|
|
||||||
const matchedClients = await db
|
const matchedClients = await db
|
||||||
.select({
|
.select({
|
||||||
clientId: clients.clientId,
|
clientId: clients.clientId,
|
||||||
@@ -314,6 +322,7 @@ export const handleConnectionLogMessage: MessageHandler = async (context) => {
|
|||||||
|
|
||||||
for (const c of matchedClients) {
|
for (const c of matchedClients) {
|
||||||
const ip = c.subnet.split("/")[0];
|
const ip = c.subnet.split("/")[0];
|
||||||
|
logger.debug(`Client ${c.clientId} subnet ${c.subnet} matches ${ip}`);
|
||||||
ipToClient.set(ip, { clientId: c.clientId, userId: c.userId });
|
ipToClient.set(ip, { clientId: c.clientId, userId: c.userId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -346,7 +355,10 @@ export const handleConnectionLogMessage: MessageHandler = async (context) => {
|
|||||||
// Match the source address to a client. The sourceAddr is the
|
// Match the source address to a client. The sourceAddr is the
|
||||||
// client's IP on the WireGuard network, which corresponds to the IP
|
// client's IP on the WireGuard network, which corresponds to the IP
|
||||||
// portion of the client's subnet CIDR (e.g. "100.90.128.5/24").
|
// portion of the client's subnet CIDR (e.g. "100.90.128.5/24").
|
||||||
const clientInfo = ipToClient.get(session.sourceAddr) ?? null;
|
// Strip port if present (e.g. "100.90.128.1:38004" → "100.90.128.1")
|
||||||
|
const sourceIp = session.sourceAddr.includes(":") ? session.sourceAddr.split(":")[0] : session.sourceAddr;
|
||||||
|
const clientInfo = ipToClient.get(sourceIp) ?? null;
|
||||||
|
|
||||||
|
|
||||||
buffer.push({
|
buffer.push({
|
||||||
sessionId: session.sessionId,
|
sessionId: session.sessionId,
|
||||||
|
|||||||
@@ -112,6 +112,9 @@ export type QueryConnectionAuditLogResponse = {
|
|||||||
siteName: string | null;
|
siteName: string | null;
|
||||||
siteNiceId: string | null;
|
siteNiceId: string | null;
|
||||||
clientName: string | null;
|
clientName: string | null;
|
||||||
|
clientNiceId: string | null;
|
||||||
|
clientType: string | null;
|
||||||
|
userEmail: string | null;
|
||||||
}[];
|
}[];
|
||||||
pagination: {
|
pagination: {
|
||||||
total: number;
|
total: number;
|
||||||
@@ -120,5 +123,18 @@ export type QueryConnectionAuditLogResponse = {
|
|||||||
};
|
};
|
||||||
filterAttributes: {
|
filterAttributes: {
|
||||||
protocols: string[];
|
protocols: string[];
|
||||||
|
destAddrs: string[];
|
||||||
|
clients: {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}[];
|
||||||
|
resources: {
|
||||||
|
id: number;
|
||||||
|
name: string | null;
|
||||||
|
}[];
|
||||||
|
users: {
|
||||||
|
id: string;
|
||||||
|
email: string | null;
|
||||||
|
}[];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
import { Button } from "@app/components/ui/button";
|
||||||
import { ColumnFilter } from "@app/components/ColumnFilter";
|
import { ColumnFilter } from "@app/components/ColumnFilter";
|
||||||
import { DateTimeValue } from "@app/components/DateTimePicker";
|
import { DateTimeValue } from "@app/components/DateTimePicker";
|
||||||
import { LogDataTable } from "@app/components/LogDataTable";
|
import { LogDataTable } from "@app/components/LogDataTable";
|
||||||
@@ -14,7 +15,8 @@ import { build } from "@server/build";
|
|||||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { Cable, Monitor, Server } from "lucide-react";
|
import { ArrowUpRight, Laptop, User } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useParams, useRouter, useSearchParams } from "next/navigation";
|
import { useParams, useRouter, useSearchParams } from "next/navigation";
|
||||||
import { useEffect, useState, useTransition } from "react";
|
import { useEffect, useState, useTransition } from "react";
|
||||||
@@ -57,15 +59,31 @@ export default function ConnectionLogsPage() {
|
|||||||
const [isExporting, startTransition] = useTransition();
|
const [isExporting, startTransition] = useTransition();
|
||||||
const [filterAttributes, setFilterAttributes] = useState<{
|
const [filterAttributes, setFilterAttributes] = useState<{
|
||||||
protocols: string[];
|
protocols: string[];
|
||||||
|
destAddrs: string[];
|
||||||
|
clients: { id: number; name: string }[];
|
||||||
|
resources: { id: number; name: string | null }[];
|
||||||
|
users: { id: string; email: string | null }[];
|
||||||
}>({
|
}>({
|
||||||
protocols: []
|
protocols: [],
|
||||||
|
destAddrs: [],
|
||||||
|
clients: [],
|
||||||
|
resources: [],
|
||||||
|
users: []
|
||||||
});
|
});
|
||||||
|
|
||||||
// Filter states - unified object for all filters
|
// Filter states - unified object for all filters
|
||||||
const [filters, setFilters] = useState<{
|
const [filters, setFilters] = useState<{
|
||||||
protocol?: string;
|
protocol?: string;
|
||||||
|
destAddr?: string;
|
||||||
|
clientId?: string;
|
||||||
|
siteResourceId?: string;
|
||||||
|
userId?: string;
|
||||||
}>({
|
}>({
|
||||||
protocol: searchParams.get("protocol") || undefined
|
protocol: searchParams.get("protocol") || undefined,
|
||||||
|
destAddr: searchParams.get("destAddr") || undefined,
|
||||||
|
clientId: searchParams.get("clientId") || undefined,
|
||||||
|
siteResourceId: searchParams.get("siteResourceId") || undefined,
|
||||||
|
userId: searchParams.get("userId") || undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
// Pagination state
|
// Pagination state
|
||||||
@@ -211,9 +229,7 @@ export default function ConnectionLogsPage() {
|
|||||||
endDate: DateTimeValue,
|
endDate: DateTimeValue,
|
||||||
page: number = currentPage,
|
page: number = currentPage,
|
||||||
size: number = pageSize,
|
size: number = pageSize,
|
||||||
filtersParam?: {
|
filtersParam?: typeof filters
|
||||||
protocol?: string;
|
|
||||||
}
|
|
||||||
) => {
|
) => {
|
||||||
console.log("Date range changed:", { startDate, endDate, page, size });
|
console.log("Date range changed:", { startDate, endDate, page, size });
|
||||||
if (!isPaidUser(tierMatrix.connectionLogs)) {
|
if (!isPaidUser(tierMatrix.connectionLogs)) {
|
||||||
@@ -411,9 +427,41 @@ export default function ConnectionLogsPage() {
|
|||||||
{
|
{
|
||||||
accessorKey: "resourceName",
|
accessorKey: "resourceName",
|
||||||
header: ({ column }) => {
|
header: ({ column }) => {
|
||||||
return t("resource");
|
return (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span>{t("resource")}</span>
|
||||||
|
<ColumnFilter
|
||||||
|
options={filterAttributes.resources.map((res) => ({
|
||||||
|
value: res.id.toString(),
|
||||||
|
label: res.name || "Unnamed Resource"
|
||||||
|
}))}
|
||||||
|
selectedValue={filters.siteResourceId}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
handleFilterChange("siteResourceId", value)
|
||||||
|
}
|
||||||
|
searchPlaceholder="Search..."
|
||||||
|
emptyMessage="None found"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
|
if (row.original.resourceName && row.original.resourceNiceId) {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href={`/${row.original.orgId}/settings/resources/proxy/${row.original.resourceNiceId}`}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="text-xs h-6"
|
||||||
|
>
|
||||||
|
{row.original.resourceName}
|
||||||
|
<ArrowUpRight className="ml-2 h-3 w-3" />
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<span className="whitespace-nowrap">
|
<span className="whitespace-nowrap">
|
||||||
{row.original.resourceName ?? "—"}
|
{row.original.resourceName ?? "—"}
|
||||||
@@ -421,6 +469,86 @@ export default function ConnectionLogsPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "clientName",
|
||||||
|
header: ({ column }) => {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span>{t("client")}</span>
|
||||||
|
<ColumnFilter
|
||||||
|
options={filterAttributes.clients.map((c) => ({
|
||||||
|
value: c.id.toString(),
|
||||||
|
label: c.name
|
||||||
|
}))}
|
||||||
|
selectedValue={filters.clientId}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
handleFilterChange("clientId", value)
|
||||||
|
}
|
||||||
|
searchPlaceholder="Search..."
|
||||||
|
emptyMessage="None found"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const clientType = row.original.clientType === "olm" ? "machine" : "user";
|
||||||
|
if (row.original.clientName && row.original.clientNiceId) {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href={`/${row.original.orgId}/settings/clients/${clientType}/${row.original.clientNiceId}`}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="text-xs h-6"
|
||||||
|
>
|
||||||
|
<Laptop className="mr-1 h-3 w-3" />
|
||||||
|
{row.original.clientName}
|
||||||
|
<ArrowUpRight className="ml-2 h-3 w-3" />
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span className="whitespace-nowrap">
|
||||||
|
{row.original.clientName ?? "—"}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "userEmail",
|
||||||
|
header: ({ column }) => {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span>{t("user")}</span>
|
||||||
|
<ColumnFilter
|
||||||
|
options={filterAttributes.users.map((u) => ({
|
||||||
|
value: u.id,
|
||||||
|
label: u.email || u.id
|
||||||
|
}))}
|
||||||
|
selectedValue={filters.userId}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
handleFilterChange("userId", value)
|
||||||
|
}
|
||||||
|
searchPlaceholder="Search..."
|
||||||
|
emptyMessage="None found"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
cell: ({ row }) => {
|
||||||
|
if (row.original.userEmail || row.original.userId) {
|
||||||
|
return (
|
||||||
|
<span className="flex items-center gap-1 whitespace-nowrap">
|
||||||
|
<User className="h-4 w-4" />
|
||||||
|
{row.original.userEmail ?? row.original.userId}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <span>—</span>;
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "sourceAddr",
|
accessorKey: "sourceAddr",
|
||||||
header: ({ column }) => {
|
header: ({ column }) => {
|
||||||
@@ -437,7 +565,23 @@ export default function ConnectionLogsPage() {
|
|||||||
{
|
{
|
||||||
accessorKey: "destAddr",
|
accessorKey: "destAddr",
|
||||||
header: ({ column }) => {
|
header: ({ column }) => {
|
||||||
return t("destinationAddress");
|
return (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span>{t("destinationAddress")}</span>
|
||||||
|
<ColumnFilter
|
||||||
|
options={filterAttributes.destAddrs.map((addr) => ({
|
||||||
|
value: addr,
|
||||||
|
label: addr
|
||||||
|
}))}
|
||||||
|
selectedValue={filters.destAddr}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
handleFilterChange("destAddr", value)
|
||||||
|
}
|
||||||
|
searchPlaceholder="Search..."
|
||||||
|
emptyMessage="None found"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return (
|
return (
|
||||||
@@ -470,10 +614,9 @@ export default function ConnectionLogsPage() {
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-xs">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-xs">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-1 font-semibold text-sm mb-1">
|
{/*<div className="flex items-center gap-1 font-semibold text-sm mb-1">
|
||||||
<Cable className="h-4 w-4" />
|
|
||||||
Connection Details
|
Connection Details
|
||||||
</div>
|
</div>*/}
|
||||||
<div>
|
<div>
|
||||||
<strong>Session ID:</strong>{" "}
|
<strong>Session ID:</strong>{" "}
|
||||||
<span className="font-mono">
|
<span className="font-mono">
|
||||||
@@ -518,10 +661,9 @@ export default function ConnectionLogsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-1 font-semibold text-sm mb-1">
|
{/*<div className="flex items-center gap-1 font-semibold text-sm mb-1">
|
||||||
<Server className="h-4 w-4" />
|
|
||||||
Resource & Site
|
Resource & Site
|
||||||
</div>
|
</div>*/}
|
||||||
<div>
|
<div>
|
||||||
<strong>Resource:</strong>{" "}
|
<strong>Resource:</strong>{" "}
|
||||||
{row.resourceName ?? "—"}
|
{row.resourceName ?? "—"}
|
||||||
@@ -548,10 +690,9 @@ export default function ConnectionLogsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-1 font-semibold text-sm mb-1">
|
{/*<div className="flex items-center gap-1 font-semibold text-sm mb-1">
|
||||||
<Monitor className="h-4 w-4" />
|
|
||||||
Client & Transfer
|
Client & Transfer
|
||||||
</div>
|
</div>*/}
|
||||||
<div>
|
<div>
|
||||||
<strong>Client:</strong> {row.clientName ?? "—"}
|
<strong>Client:</strong> {row.clientName ?? "—"}
|
||||||
{row.clientId && (
|
{row.clientId && (
|
||||||
@@ -561,7 +702,8 @@ export default function ConnectionLogsPage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<strong>User ID:</strong> {row.userId ?? "—"}
|
<strong>User:</strong>{" "}
|
||||||
|
{row.userEmail ?? row.userId ?? "—"}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<strong>Bytes Sent (TX):</strong>{" "}
|
<strong>Bytes Sent (TX):</strong>{" "}
|
||||||
|
|||||||
Reference in New Issue
Block a user