mirror of
https://github.com/fosrl/pangolin.git
synced 2026-01-28 22:00:51 +00:00
add display info for device posture
This commit is contained in:
@@ -22,12 +22,13 @@ import {
|
||||
import { Badge } from "@app/components/ui/badge";
|
||||
import { Button } from "@app/components/ui/button";
|
||||
import ActionBanner from "@app/components/ActionBanner";
|
||||
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
|
||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
||||
import { toast } from "@app/hooks/useToast";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState, useEffect, useTransition } from "react";
|
||||
import { Check, Ban, Shield, ShieldOff, Clock } from "lucide-react";
|
||||
import { Check, Ban, Shield, ShieldOff, Clock, CheckCircle2, XCircle } from "lucide-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { FaApple, FaWindows, FaLinux } from "react-icons/fa";
|
||||
import { SiAndroid } from "react-icons/si";
|
||||
@@ -111,18 +112,12 @@ function getPlatformFieldConfig(
|
||||
kernelVersion: { show: false, labelKey: "kernelVersion" },
|
||||
arch: { show: true, labelKey: "architecture" },
|
||||
deviceModel: { show: true, labelKey: "deviceModel" },
|
||||
serialNumber: { show: true, labelKey: "serialNumber" },
|
||||
username: { show: true, labelKey: "username" },
|
||||
hostname: { show: true, labelKey: "hostname" }
|
||||
},
|
||||
android: {
|
||||
osVersion: { show: true, labelKey: "androidVersion" },
|
||||
kernelVersion: { show: true, labelKey: "kernelVersion" },
|
||||
arch: { show: true, labelKey: "architecture" },
|
||||
deviceModel: { show: true, labelKey: "deviceModel" },
|
||||
serialNumber: { show: true, labelKey: "serialNumber" },
|
||||
username: { show: true, labelKey: "username" },
|
||||
hostname: { show: true, labelKey: "hostname" }
|
||||
},
|
||||
unknown: {
|
||||
osVersion: { show: true, labelKey: "osVersion" },
|
||||
@@ -138,6 +133,7 @@ function getPlatformFieldConfig(
|
||||
return configs[normalizedPlatform] || configs.unknown;
|
||||
}
|
||||
|
||||
|
||||
export default function GeneralPage() {
|
||||
const { client, updateClient } = useClientContext();
|
||||
const { isPaidUser } = usePaidStatus();
|
||||
@@ -152,6 +148,20 @@ export default function GeneralPage() {
|
||||
|
||||
const showApprovalFeatures = build !== "oss" && isPaidUser;
|
||||
|
||||
const formatPostureValue = (value: boolean | null | undefined) => {
|
||||
if (value === null || value === undefined) return "-";
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
{value ? (
|
||||
<CheckCircle2 className="h-4 w-4 text-green-600" />
|
||||
) : (
|
||||
<XCircle className="h-4 w-4 text-red-600" />
|
||||
)}
|
||||
<span>{value ? t("enabled") : t("disabled")}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Fetch approval ID for this client if pending
|
||||
useEffect(() => {
|
||||
if (
|
||||
@@ -407,13 +417,13 @@ export default function GeneralPage() {
|
||||
)}
|
||||
|
||||
{client.fingerprint.osVersion &&
|
||||
fieldConfig.osVersion.show && (
|
||||
fieldConfig.osVersion?.show && (
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
{t(
|
||||
fieldConfig
|
||||
.osVersion
|
||||
.labelKey
|
||||
?.labelKey || "osVersion"
|
||||
)}
|
||||
</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
@@ -426,7 +436,7 @@ export default function GeneralPage() {
|
||||
)}
|
||||
|
||||
{client.fingerprint.kernelVersion &&
|
||||
fieldConfig.kernelVersion.show && (
|
||||
fieldConfig.kernelVersion?.show && (
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
{t("kernelVersion")}
|
||||
@@ -456,7 +466,7 @@ export default function GeneralPage() {
|
||||
)}
|
||||
|
||||
{client.fingerprint.deviceModel &&
|
||||
fieldConfig.deviceModel.show && (
|
||||
fieldConfig.deviceModel?.show && (
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
{t("deviceModel")}
|
||||
@@ -486,7 +496,7 @@ export default function GeneralPage() {
|
||||
)}
|
||||
|
||||
{client.fingerprint.username &&
|
||||
fieldConfig.username.show && (
|
||||
fieldConfig.username?.show && (
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
{t("username")}
|
||||
@@ -501,7 +511,7 @@ export default function GeneralPage() {
|
||||
)}
|
||||
|
||||
{client.fingerprint.hostname &&
|
||||
fieldConfig.hostname.show && (
|
||||
fieldConfig.hostname?.show && (
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
{t("hostname")}
|
||||
@@ -548,6 +558,218 @@ export default function GeneralPage() {
|
||||
</SettingsSectionBody>
|
||||
</SettingsSection>
|
||||
)}
|
||||
|
||||
{/* Device Security Section */}
|
||||
{build !== "oss" && (
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
{t("deviceSecurity")}
|
||||
</SettingsSectionTitle>
|
||||
<SettingsSectionDescription>
|
||||
{t("deviceSecurityDescription")}
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
|
||||
<SettingsSectionBody>
|
||||
{client.posture && Object.keys(client.posture).length > 0 ? (
|
||||
<>
|
||||
{!isPaidUser && <PaidFeaturesAlert />}
|
||||
<InfoSections cols={3}>
|
||||
{client.posture.biometricsEnabled !== null &&
|
||||
client.posture.biometricsEnabled !== undefined && (
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
{t("biometricsEnabled")}
|
||||
</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
{isPaidUser
|
||||
? formatPostureValue(
|
||||
client.posture.biometricsEnabled
|
||||
)
|
||||
: "-"}
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
)}
|
||||
|
||||
{client.posture.diskEncrypted !== null &&
|
||||
client.posture.diskEncrypted !== undefined && (
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
{t("diskEncrypted")}
|
||||
</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
{isPaidUser
|
||||
? formatPostureValue(
|
||||
client.posture.diskEncrypted
|
||||
)
|
||||
: "-"}
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
)}
|
||||
|
||||
{client.posture.firewallEnabled !== null &&
|
||||
client.posture.firewallEnabled !== undefined && (
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
{t("firewallEnabled")}
|
||||
</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
{isPaidUser
|
||||
? formatPostureValue(
|
||||
client.posture.firewallEnabled
|
||||
)
|
||||
: "-"}
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
)}
|
||||
|
||||
{client.posture.autoUpdatesEnabled !== null &&
|
||||
client.posture.autoUpdatesEnabled !== undefined && (
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
{t("autoUpdatesEnabled")}
|
||||
</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
{isPaidUser
|
||||
? formatPostureValue(
|
||||
client.posture.autoUpdatesEnabled
|
||||
)
|
||||
: "-"}
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
)}
|
||||
|
||||
{client.posture.tpmAvailable !== null &&
|
||||
client.posture.tpmAvailable !== undefined && (
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
{t("tpmAvailable")}
|
||||
</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
{isPaidUser
|
||||
? formatPostureValue(
|
||||
client.posture.tpmAvailable
|
||||
)
|
||||
: "-"}
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
)}
|
||||
|
||||
{client.posture.windowsDefenderEnabled !== null &&
|
||||
client.posture.windowsDefenderEnabled !== undefined && (
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
{t("windowsDefenderEnabled")}
|
||||
</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
{isPaidUser
|
||||
? formatPostureValue(
|
||||
client.posture
|
||||
.windowsDefenderEnabled
|
||||
)
|
||||
: "-"}
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
)}
|
||||
|
||||
{client.posture.macosSipEnabled !== null &&
|
||||
client.posture.macosSipEnabled !== undefined && (
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
{t("macosSipEnabled")}
|
||||
</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
{isPaidUser
|
||||
? formatPostureValue(
|
||||
client.posture.macosSipEnabled
|
||||
)
|
||||
: "-"}
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
)}
|
||||
|
||||
{client.posture.macosGatekeeperEnabled !== null &&
|
||||
client.posture.macosGatekeeperEnabled !==
|
||||
undefined && (
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
{t("macosGatekeeperEnabled")}
|
||||
</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
{isPaidUser
|
||||
? formatPostureValue(
|
||||
client.posture
|
||||
.macosGatekeeperEnabled
|
||||
)
|
||||
: "-"}
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
)}
|
||||
|
||||
{client.posture.macosFirewallStealthMode !== null &&
|
||||
client.posture.macosFirewallStealthMode !==
|
||||
undefined && (
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
{t("macosFirewallStealthMode")}
|
||||
</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
{isPaidUser
|
||||
? formatPostureValue(
|
||||
client.posture
|
||||
.macosFirewallStealthMode
|
||||
)
|
||||
: "-"}
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
)}
|
||||
|
||||
{client.posture.linuxAppArmorEnabled !== null &&
|
||||
client.posture.linuxAppArmorEnabled !==
|
||||
undefined && (
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
{t("linuxAppArmorEnabled")}
|
||||
</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
{isPaidUser
|
||||
? formatPostureValue(
|
||||
client.posture
|
||||
.linuxAppArmorEnabled
|
||||
)
|
||||
: "-"}
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
)}
|
||||
|
||||
{client.posture.linuxSELinuxEnabled !== null &&
|
||||
client.posture.linuxSELinuxEnabled !==
|
||||
undefined && (
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
{t("linuxSELinuxEnabled")}
|
||||
</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
{isPaidUser
|
||||
? formatPostureValue(
|
||||
client.posture
|
||||
.linuxSELinuxEnabled
|
||||
)
|
||||
: "-"}
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
)}
|
||||
</InfoSections>
|
||||
</>
|
||||
) : (
|
||||
<div className="text-muted-foreground">
|
||||
{t("noData")}
|
||||
</div>
|
||||
)}
|
||||
</SettingsSectionBody>
|
||||
</SettingsSection>
|
||||
)}
|
||||
</SettingsContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -145,10 +145,10 @@ function CollapsibleNavItem({
|
||||
<div className="flex items-center gap-1.5 flex-shrink-0 ml-2">
|
||||
{notificationCount !== undefined &&
|
||||
notificationCount > 0 && (
|
||||
<Badge
|
||||
variant="secondary"
|
||||
>
|
||||
{notificationCount > 99 ? "99+" : notificationCount}
|
||||
<Badge variant="secondary">
|
||||
{notificationCount > 99
|
||||
? "99+"
|
||||
: notificationCount}
|
||||
</Badge>
|
||||
)}
|
||||
{build === "enterprise" &&
|
||||
@@ -321,9 +321,7 @@ export function SidebarNav({
|
||||
<div className="flex items-center gap-1.5 flex-shrink-0">
|
||||
{notificationCount !== undefined &&
|
||||
notificationCount > 0 && (
|
||||
<Badge
|
||||
variant="secondary"
|
||||
>
|
||||
<Badge variant="secondary">
|
||||
{notificationCount > 99
|
||||
? "99+"
|
||||
: notificationCount}
|
||||
@@ -346,8 +344,8 @@ export function SidebarNav({
|
||||
notificationCount !== undefined &&
|
||||
notificationCount > 0 && (
|
||||
<Badge
|
||||
variant="default"
|
||||
className="absolute -top-1 -right-1 h-5 min-w-5 px-1.5 flex items-center justify-center text-xs bg-primary text-primary-foreground"
|
||||
variant="secondary"
|
||||
className="absolute -top-1 -right-1 h-5 min-w-5 px-1.5 flex items-center justify-center text-xs"
|
||||
>
|
||||
{notificationCount > 99 ? "99+" : notificationCount}
|
||||
</Badge>
|
||||
@@ -379,7 +377,7 @@ export function SidebarNav({
|
||||
{notificationCount !== undefined &&
|
||||
notificationCount > 0 && (
|
||||
<Badge
|
||||
variant="default"
|
||||
variant="secondary"
|
||||
className="flex-shrink-0 bg-primary text-primary-foreground"
|
||||
>
|
||||
{notificationCount > 99
|
||||
|
||||
@@ -38,6 +38,7 @@ export function formatFingerprintInfo(
|
||||
): string {
|
||||
if (!fingerprint) return "";
|
||||
const parts: string[] = [];
|
||||
const normalizedPlatform = fingerprint.platform?.toLowerCase() || "unknown";
|
||||
|
||||
if (fingerprint.platform) {
|
||||
parts.push(
|
||||
@@ -53,14 +54,17 @@ export function formatFingerprintInfo(
|
||||
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}`);
|
||||
|
||||
if (normalizedPlatform !== "ios" && normalizedPlatform !== "android") {
|
||||
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");
|
||||
|
||||
Reference in New Issue
Block a user