show fingerprint popup and fix policy check errors

This commit is contained in:
miloschwartz
2026-01-18 11:55:24 -08:00
parent 34e2fbefb9
commit 6a45151741
6 changed files with 344 additions and 150 deletions

View File

@@ -27,7 +27,7 @@ import ClientDownloadBanner from "./ClientDownloadBanner";
import { Badge } from "./ui/badge";
import { build } from "@server/build";
import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { t } from "@faker-js/faker/dist/airline-DF6RqYmq";
import { InfoPopup } from "@app/components/ui/info-popup";
export type ClientRow = {
id: number;
@@ -48,6 +48,16 @@ export type ClientRow = {
approvalState: "approved" | "pending" | "denied" | null;
archived?: boolean;
blocked?: boolean;
fingerprint?: {
platform: string | null;
osVersion: string | null;
kernelVersion: string | null;
arch: string | null;
deviceModel: string | null;
serialNumber: string | null;
username: string | null;
hostname: string | null;
} | null;
};
type ClientTableProps = {
@@ -55,10 +65,52 @@ type ClientTableProps = {
orgId: string;
};
function formatPlatform(platform: string | null | undefined): string {
if (!platform) return "-";
const platformMap: Record<string, string> = {
macos: "macOS",
windows: "Windows",
linux: "Linux",
ios: "iOS",
android: "Android",
unknown: "Unknown"
};
return platformMap[platform.toLowerCase()] || platform;
}
export default function UserDevicesTable({ userClients }: ClientTableProps) {
const router = useRouter();
const t = useTranslations();
const formatFingerprintInfo = (fingerprint: ClientRow["fingerprint"]): string => {
if (!fingerprint) return "";
const parts: string[] = [];
if (fingerprint.platform) {
parts.push(`${t("platform")}: ${formatPlatform(fingerprint.platform)}`);
}
if (fingerprint.deviceModel) {
parts.push(`${t("deviceModel")}: ${fingerprint.deviceModel}`);
}
if (fingerprint.osVersion) {
parts.push(`${t("osVersion")}: ${fingerprint.osVersion}`);
}
if (fingerprint.arch) {
parts.push(`${t("architecture")}: ${fingerprint.arch}`);
}
if (fingerprint.hostname) {
parts.push(`${t("hostname")}: ${fingerprint.hostname}`);
}
if (fingerprint.username) {
parts.push(`${t("username")}: ${fingerprint.username}`);
}
if (fingerprint.serialNumber) {
parts.push(`${t("serialNumber")}: ${fingerprint.serialNumber}`);
}
return parts.join("\n");
};
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [selectedClient, setSelectedClient] = useState<ClientRow | null>(
null
@@ -182,7 +234,7 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) {
{
accessorKey: "name",
enableHiding: false,
friendlyName: "Name",
friendlyName: t("name"),
header: ({ column }) => {
return (
<Button
@@ -193,16 +245,31 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) {
)
}
>
Name
{t("name")}
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
},
cell: ({ row }) => {
const r = row.original;
const fingerprintInfo = r.fingerprint
? formatFingerprintInfo(r.fingerprint)
: null;
return (
<div className="flex items-center gap-2">
<span>{r.name}</span>
{fingerprintInfo && (
<InfoPopup>
<div className="space-y-1 text-sm">
<div className="font-semibold mb-2">
{t("deviceInformation")}
</div>
<div className="text-muted-foreground whitespace-pre-line">
{fingerprintInfo}
</div>
</div>
</InfoPopup>
)}
{r.archived && (
<Badge variant="secondary">
{t("archived")}
@@ -250,7 +317,7 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) {
},
{
accessorKey: "userEmail",
friendlyName: "User",
friendlyName: t("users"),
header: ({ column }) => {
return (
<Button
@@ -261,7 +328,7 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) {
)
}
>
User
{t("users")}
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
@@ -284,7 +351,7 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) {
},
{
accessorKey: "online",
friendlyName: "Connectivity",
friendlyName: t("online"),
header: ({ column }) => {
return (
<Button
@@ -295,7 +362,7 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) {
)
}
>
Connectivity
{t("online")}
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
@@ -306,14 +373,14 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) {
return (
<span className="text-green-500 flex items-center space-x-2">
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
<span>Connected</span>
<span>{t("online")}</span>
</span>
);
} else {
return (
<span className="text-neutral-500 flex items-center space-x-2">
<div className="w-2 h-2 bg-gray-500 rounded-full"></div>
<span>Disconnected</span>
<span>{t("offline")}</span>
</span>
);
}
@@ -321,7 +388,7 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) {
},
{
accessorKey: "mbIn",
friendlyName: "Data In",
friendlyName: t("dataIn"),
header: ({ column }) => {
return (
<Button
@@ -332,7 +399,7 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) {
)
}
>
Data In
{t("dataIn")}
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
@@ -340,7 +407,7 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) {
},
{
accessorKey: "mbOut",
friendlyName: "Data Out",
friendlyName: t("dataOut"),
header: ({ column }) => {
return (
<Button
@@ -351,7 +418,7 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) {
)
}
>
Data Out
{t("dataOut")}
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
@@ -399,7 +466,7 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) {
},
{
accessorKey: "subnet",
friendlyName: "Address",
friendlyName: t("address"),
header: ({ column }) => {
return (
<Button
@@ -410,7 +477,7 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) {
)
}
>
Address
{t("address")}
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
@@ -445,8 +512,8 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) {
>
<span>
{clientRow.archived
? "Unarchive"
: "Archive"}
? t("actionUnarchiveClient")
: t("actionArchiveClient")}
</span>
</DropdownMenuItem>
<DropdownMenuItem
@@ -460,8 +527,8 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) {
>
<span>
{clientRow.blocked
? "Unblock"
: "Block"}
? t("actionUnblockClient")
: t("actionBlockClient")}
</span>
</DropdownMenuItem>
{!clientRow.userId && (
@@ -473,7 +540,7 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) {
}}
>
<span className="text-red-500">
Delete
{t("delete")}
</span>
</DropdownMenuItem>
)}
@@ -483,7 +550,7 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) {
href={`/${clientRow.orgId}/settings/clients/user/${clientRow.niceId}`}
>
<Button variant={"outline"}>
View
{t("viewDetails")}
<ArrowRight className="ml-2 w-4 h-4" />
</Button>
</Link>
@@ -510,10 +577,10 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) {
<p>{t("clientMessageRemove")}</p>
</div>
}
buttonText="Confirm Delete Client"
buttonText={t("actionDeleteClient")}
onConfirm={async () => deleteClient(selectedClient!.id)}
string={selectedClient.name}
title="Delete Client"
title={t("actionDeleteClient")}
/>
)}
<ClientDownloadBanner />