mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-14 17:06:39 +00:00
show fingerprint popup and fix policy check errors
This commit is contained in:
@@ -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 />
|
||||
|
||||
Reference in New Issue
Block a user