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:
@@ -2489,6 +2489,8 @@
|
|||||||
"logIn": "Log In",
|
"logIn": "Log In",
|
||||||
"deviceInformation": "Device Information",
|
"deviceInformation": "Device Information",
|
||||||
"deviceInformationDescription": "Information about the device and agent",
|
"deviceInformationDescription": "Information about the device and agent",
|
||||||
|
"deviceSecurity": "Device Security",
|
||||||
|
"deviceSecurityDescription": "Device security posture information",
|
||||||
"platform": "Platform",
|
"platform": "Platform",
|
||||||
"macosVersion": "macOS Version",
|
"macosVersion": "macOS Version",
|
||||||
"windowsVersion": "Windows Version",
|
"windowsVersion": "Windows Version",
|
||||||
@@ -2501,6 +2503,17 @@
|
|||||||
"hostname": "Hostname",
|
"hostname": "Hostname",
|
||||||
"firstSeen": "First Seen",
|
"firstSeen": "First Seen",
|
||||||
"lastSeen": "Last Seen",
|
"lastSeen": "Last Seen",
|
||||||
|
"biometricsEnabled": "Biometrics Enabled",
|
||||||
|
"diskEncrypted": "Disk Encrypted",
|
||||||
|
"firewallEnabled": "Firewall Enabled",
|
||||||
|
"autoUpdatesEnabled": "Auto Updates Enabled",
|
||||||
|
"tpmAvailable": "TPM Available",
|
||||||
|
"windowsDefenderEnabled": "Windows Defender Enabled",
|
||||||
|
"macosSipEnabled": "System Integrity Protection (SIP)",
|
||||||
|
"macosGatekeeperEnabled": "Gatekeeper",
|
||||||
|
"macosFirewallStealthMode": "Firewall Stealth Mode",
|
||||||
|
"linuxAppArmorEnabled": "AppArmor",
|
||||||
|
"linuxSELinuxEnabled": "SELinux",
|
||||||
"deviceSettingsDescription": "View device information and settings",
|
"deviceSettingsDescription": "View device information and settings",
|
||||||
"devicePendingApprovalDescription": "This device is waiting for approval",
|
"devicePendingApprovalDescription": "This device is waiting for approval",
|
||||||
"deviceBlockedDescription": "This device is currently blocked. It won't be able to connect to any resources unless unblocked.",
|
"deviceBlockedDescription": "This device is currently blocked. It won't be able to connect to any resources unless unblocked.",
|
||||||
|
|||||||
51
server/private/middlewares/verifyValidSubscription.ts
Normal file
51
server/private/middlewares/verifyValidSubscription.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of a proprietary work.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 Fossorial, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This file is licensed under the Fossorial Commercial License.
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
||||||
|
*
|
||||||
|
* This file is not licensed under the AGPLv3.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Request, Response, NextFunction } from "express";
|
||||||
|
import createHttpError from "http-errors";
|
||||||
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
import { build } from "@server/build";
|
||||||
|
import { getOrgTierData } from "#private/lib/billing";
|
||||||
|
import { TierId } from "@server/lib/billing/tiers";
|
||||||
|
|
||||||
|
export async function verifyValidLicense(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (build != "saas") {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { tier, active } = await getOrgTierData(orgId);
|
||||||
|
const subscribed = tier === TierId.STANDARD;
|
||||||
|
if (!subscribed) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.FORBIDDEN,
|
||||||
|
"This organization's current plan does not support this feature."
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return next();
|
||||||
|
} catch (e) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
|
"Error verifying subscription"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import stoi from "@server/lib/stoi";
|
|||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { getUserDeviceName } from "@server/db/names";
|
import { getUserDeviceName } from "@server/db/names";
|
||||||
|
import { build } from "@server/build";
|
||||||
|
|
||||||
const getClientSchema = z.strictObject({
|
const getClientSchema = z.strictObject({
|
||||||
clientId: z
|
clientId: z
|
||||||
@@ -51,6 +52,106 @@ async function query(clientId?: number, niceId?: string, orgId?: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PostureData = {
|
||||||
|
biometricsEnabled?: boolean | null;
|
||||||
|
diskEncrypted?: boolean | null;
|
||||||
|
firewallEnabled?: boolean | null;
|
||||||
|
autoUpdatesEnabled?: boolean | null;
|
||||||
|
tpmAvailable?: boolean | null;
|
||||||
|
windowsDefenderEnabled?: boolean | null;
|
||||||
|
macosSipEnabled?: boolean | null;
|
||||||
|
macosGatekeeperEnabled?: boolean | null;
|
||||||
|
macosFirewallStealthMode?: boolean | null;
|
||||||
|
linuxAppArmorEnabled?: boolean | null;
|
||||||
|
linuxSELinuxEnabled?: boolean | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getPlatformPostureData(
|
||||||
|
platform: string | null | undefined,
|
||||||
|
fingerprint: typeof currentFingerprint.$inferSelect | null
|
||||||
|
): PostureData | null {
|
||||||
|
if (!fingerprint) return null;
|
||||||
|
|
||||||
|
const normalizedPlatform = platform?.toLowerCase() || "unknown";
|
||||||
|
const posture: PostureData = {};
|
||||||
|
|
||||||
|
// Windows: Hard drive encryption, Firewall, Auto updates, TPM availability, Windows Defender
|
||||||
|
if (normalizedPlatform === "windows") {
|
||||||
|
if (fingerprint.diskEncrypted !== null && fingerprint.diskEncrypted !== undefined) {
|
||||||
|
posture.diskEncrypted = fingerprint.diskEncrypted;
|
||||||
|
}
|
||||||
|
if (fingerprint.firewallEnabled !== null && fingerprint.firewallEnabled !== undefined) {
|
||||||
|
posture.firewallEnabled = fingerprint.firewallEnabled;
|
||||||
|
}
|
||||||
|
if (fingerprint.autoUpdatesEnabled !== null && fingerprint.autoUpdatesEnabled !== undefined) {
|
||||||
|
posture.autoUpdatesEnabled = fingerprint.autoUpdatesEnabled;
|
||||||
|
}
|
||||||
|
if (fingerprint.tpmAvailable !== null && fingerprint.tpmAvailable !== undefined) {
|
||||||
|
posture.tpmAvailable = fingerprint.tpmAvailable;
|
||||||
|
}
|
||||||
|
if (fingerprint.windowsDefenderEnabled !== null && fingerprint.windowsDefenderEnabled !== undefined) {
|
||||||
|
posture.windowsDefenderEnabled = fingerprint.windowsDefenderEnabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// macOS: Hard drive encryption, Biometric configuration, Firewall, System Integrity Protection (SIP), Gatekeeper, Firewall stealth mode
|
||||||
|
else if (normalizedPlatform === "macos") {
|
||||||
|
if (fingerprint.diskEncrypted !== null && fingerprint.diskEncrypted !== undefined) {
|
||||||
|
posture.diskEncrypted = fingerprint.diskEncrypted;
|
||||||
|
}
|
||||||
|
if (fingerprint.biometricsEnabled !== null && fingerprint.biometricsEnabled !== undefined) {
|
||||||
|
posture.biometricsEnabled = fingerprint.biometricsEnabled;
|
||||||
|
}
|
||||||
|
if (fingerprint.firewallEnabled !== null && fingerprint.firewallEnabled !== undefined) {
|
||||||
|
posture.firewallEnabled = fingerprint.firewallEnabled;
|
||||||
|
}
|
||||||
|
if (fingerprint.macosSipEnabled !== null && fingerprint.macosSipEnabled !== undefined) {
|
||||||
|
posture.macosSipEnabled = fingerprint.macosSipEnabled;
|
||||||
|
}
|
||||||
|
if (fingerprint.macosGatekeeperEnabled !== null && fingerprint.macosGatekeeperEnabled !== undefined) {
|
||||||
|
posture.macosGatekeeperEnabled = fingerprint.macosGatekeeperEnabled;
|
||||||
|
}
|
||||||
|
if (fingerprint.macosFirewallStealthMode !== null && fingerprint.macosFirewallStealthMode !== undefined) {
|
||||||
|
posture.macosFirewallStealthMode = fingerprint.macosFirewallStealthMode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Linux: Hard drive encryption, Firewall, AppArmor, SELinux, TPM availability
|
||||||
|
else if (normalizedPlatform === "linux") {
|
||||||
|
if (fingerprint.diskEncrypted !== null && fingerprint.diskEncrypted !== undefined) {
|
||||||
|
posture.diskEncrypted = fingerprint.diskEncrypted;
|
||||||
|
}
|
||||||
|
if (fingerprint.firewallEnabled !== null && fingerprint.firewallEnabled !== undefined) {
|
||||||
|
posture.firewallEnabled = fingerprint.firewallEnabled;
|
||||||
|
}
|
||||||
|
if (fingerprint.linuxAppArmorEnabled !== null && fingerprint.linuxAppArmorEnabled !== undefined) {
|
||||||
|
posture.linuxAppArmorEnabled = fingerprint.linuxAppArmorEnabled;
|
||||||
|
}
|
||||||
|
if (fingerprint.linuxSELinuxEnabled !== null && fingerprint.linuxSELinuxEnabled !== undefined) {
|
||||||
|
posture.linuxSELinuxEnabled = fingerprint.linuxSELinuxEnabled;
|
||||||
|
}
|
||||||
|
if (fingerprint.tpmAvailable !== null && fingerprint.tpmAvailable !== undefined) {
|
||||||
|
posture.tpmAvailable = fingerprint.tpmAvailable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// iOS: Biometric configuration
|
||||||
|
else if (normalizedPlatform === "ios") {
|
||||||
|
if (fingerprint.biometricsEnabled !== null && fingerprint.biometricsEnabled !== undefined) {
|
||||||
|
posture.biometricsEnabled = fingerprint.biometricsEnabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Android: Screen lock, Biometric configuration, Hard drive encryption
|
||||||
|
else if (normalizedPlatform === "android") {
|
||||||
|
if (fingerprint.biometricsEnabled !== null && fingerprint.biometricsEnabled !== undefined) {
|
||||||
|
posture.biometricsEnabled = fingerprint.biometricsEnabled;
|
||||||
|
}
|
||||||
|
if (fingerprint.diskEncrypted !== null && fingerprint.diskEncrypted !== undefined) {
|
||||||
|
posture.diskEncrypted = fingerprint.diskEncrypted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only return if we have at least one posture field
|
||||||
|
return Object.keys(posture).length > 0 ? posture : null;
|
||||||
|
}
|
||||||
|
|
||||||
export type GetClientResponse = NonNullable<
|
export type GetClientResponse = NonNullable<
|
||||||
Awaited<ReturnType<typeof query>>
|
Awaited<ReturnType<typeof query>>
|
||||||
>["clients"] & {
|
>["clients"] & {
|
||||||
@@ -69,6 +170,7 @@ export type GetClientResponse = NonNullable<
|
|||||||
firstSeen: number | null;
|
firstSeen: number | null;
|
||||||
lastSeen: number | null;
|
lastSeen: number | null;
|
||||||
} | null;
|
} | null;
|
||||||
|
posture: PostureData | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
registry.registerPath({
|
registry.registerPath({
|
||||||
@@ -152,13 +254,23 @@ export async function getClient(
|
|||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
// Build posture data if available (platform-specific)
|
||||||
|
let postureData: PostureData | null = null;
|
||||||
|
if (build !== "oss") {
|
||||||
|
postureData = getPlatformPostureData(
|
||||||
|
client.currentFingerprint?.platform || null,
|
||||||
|
client.currentFingerprint
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const data: GetClientResponse = {
|
const data: GetClientResponse = {
|
||||||
...client.clients,
|
...client.clients,
|
||||||
name: clientName,
|
name: clientName,
|
||||||
olmId: client.olms ? client.olms.olmId : null,
|
olmId: client.olms ? client.olms.olmId : null,
|
||||||
agent: client.olms?.agent || null,
|
agent: client.olms?.agent || null,
|
||||||
olmVersion: client.olms?.version || null,
|
olmVersion: client.olms?.version || null,
|
||||||
fingerprint: fingerprintData
|
fingerprint: fingerprintData,
|
||||||
|
posture: postureData
|
||||||
};
|
};
|
||||||
|
|
||||||
return response<GetClientResponse>(res, {
|
return response<GetClientResponse>(res, {
|
||||||
|
|||||||
@@ -22,12 +22,13 @@ import {
|
|||||||
import { Badge } from "@app/components/ui/badge";
|
import { Badge } from "@app/components/ui/badge";
|
||||||
import { Button } from "@app/components/ui/button";
|
import { Button } from "@app/components/ui/button";
|
||||||
import ActionBanner from "@app/components/ActionBanner";
|
import ActionBanner from "@app/components/ActionBanner";
|
||||||
|
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
|
||||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
||||||
import { toast } from "@app/hooks/useToast";
|
import { toast } from "@app/hooks/useToast";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useState, useEffect, useTransition } from "react";
|
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 { useParams } from "next/navigation";
|
||||||
import { FaApple, FaWindows, FaLinux } from "react-icons/fa";
|
import { FaApple, FaWindows, FaLinux } from "react-icons/fa";
|
||||||
import { SiAndroid } from "react-icons/si";
|
import { SiAndroid } from "react-icons/si";
|
||||||
@@ -111,18 +112,12 @@ function getPlatformFieldConfig(
|
|||||||
kernelVersion: { show: false, labelKey: "kernelVersion" },
|
kernelVersion: { show: false, labelKey: "kernelVersion" },
|
||||||
arch: { show: true, labelKey: "architecture" },
|
arch: { show: true, labelKey: "architecture" },
|
||||||
deviceModel: { show: true, labelKey: "deviceModel" },
|
deviceModel: { show: true, labelKey: "deviceModel" },
|
||||||
serialNumber: { show: true, labelKey: "serialNumber" },
|
|
||||||
username: { show: true, labelKey: "username" },
|
|
||||||
hostname: { show: true, labelKey: "hostname" }
|
|
||||||
},
|
},
|
||||||
android: {
|
android: {
|
||||||
osVersion: { show: true, labelKey: "androidVersion" },
|
osVersion: { show: true, labelKey: "androidVersion" },
|
||||||
kernelVersion: { show: true, labelKey: "kernelVersion" },
|
kernelVersion: { show: true, labelKey: "kernelVersion" },
|
||||||
arch: { show: true, labelKey: "architecture" },
|
arch: { show: true, labelKey: "architecture" },
|
||||||
deviceModel: { show: true, labelKey: "deviceModel" },
|
deviceModel: { show: true, labelKey: "deviceModel" },
|
||||||
serialNumber: { show: true, labelKey: "serialNumber" },
|
|
||||||
username: { show: true, labelKey: "username" },
|
|
||||||
hostname: { show: true, labelKey: "hostname" }
|
|
||||||
},
|
},
|
||||||
unknown: {
|
unknown: {
|
||||||
osVersion: { show: true, labelKey: "osVersion" },
|
osVersion: { show: true, labelKey: "osVersion" },
|
||||||
@@ -138,6 +133,7 @@ function getPlatformFieldConfig(
|
|||||||
return configs[normalizedPlatform] || configs.unknown;
|
return configs[normalizedPlatform] || configs.unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default function GeneralPage() {
|
export default function GeneralPage() {
|
||||||
const { client, updateClient } = useClientContext();
|
const { client, updateClient } = useClientContext();
|
||||||
const { isPaidUser } = usePaidStatus();
|
const { isPaidUser } = usePaidStatus();
|
||||||
@@ -152,6 +148,20 @@ export default function GeneralPage() {
|
|||||||
|
|
||||||
const showApprovalFeatures = build !== "oss" && isPaidUser;
|
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
|
// Fetch approval ID for this client if pending
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
@@ -407,13 +417,13 @@ export default function GeneralPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{client.fingerprint.osVersion &&
|
{client.fingerprint.osVersion &&
|
||||||
fieldConfig.osVersion.show && (
|
fieldConfig.osVersion?.show && (
|
||||||
<InfoSection>
|
<InfoSection>
|
||||||
<InfoSectionTitle>
|
<InfoSectionTitle>
|
||||||
{t(
|
{t(
|
||||||
fieldConfig
|
fieldConfig
|
||||||
.osVersion
|
.osVersion
|
||||||
.labelKey
|
?.labelKey || "osVersion"
|
||||||
)}
|
)}
|
||||||
</InfoSectionTitle>
|
</InfoSectionTitle>
|
||||||
<InfoSectionContent>
|
<InfoSectionContent>
|
||||||
@@ -426,7 +436,7 @@ export default function GeneralPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{client.fingerprint.kernelVersion &&
|
{client.fingerprint.kernelVersion &&
|
||||||
fieldConfig.kernelVersion.show && (
|
fieldConfig.kernelVersion?.show && (
|
||||||
<InfoSection>
|
<InfoSection>
|
||||||
<InfoSectionTitle>
|
<InfoSectionTitle>
|
||||||
{t("kernelVersion")}
|
{t("kernelVersion")}
|
||||||
@@ -456,7 +466,7 @@ export default function GeneralPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{client.fingerprint.deviceModel &&
|
{client.fingerprint.deviceModel &&
|
||||||
fieldConfig.deviceModel.show && (
|
fieldConfig.deviceModel?.show && (
|
||||||
<InfoSection>
|
<InfoSection>
|
||||||
<InfoSectionTitle>
|
<InfoSectionTitle>
|
||||||
{t("deviceModel")}
|
{t("deviceModel")}
|
||||||
@@ -486,7 +496,7 @@ export default function GeneralPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{client.fingerprint.username &&
|
{client.fingerprint.username &&
|
||||||
fieldConfig.username.show && (
|
fieldConfig.username?.show && (
|
||||||
<InfoSection>
|
<InfoSection>
|
||||||
<InfoSectionTitle>
|
<InfoSectionTitle>
|
||||||
{t("username")}
|
{t("username")}
|
||||||
@@ -501,7 +511,7 @@ export default function GeneralPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{client.fingerprint.hostname &&
|
{client.fingerprint.hostname &&
|
||||||
fieldConfig.hostname.show && (
|
fieldConfig.hostname?.show && (
|
||||||
<InfoSection>
|
<InfoSection>
|
||||||
<InfoSectionTitle>
|
<InfoSectionTitle>
|
||||||
{t("hostname")}
|
{t("hostname")}
|
||||||
@@ -548,6 +558,218 @@ export default function GeneralPage() {
|
|||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
</SettingsSection>
|
</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>
|
</SettingsContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,10 +145,10 @@ function CollapsibleNavItem({
|
|||||||
<div className="flex items-center gap-1.5 flex-shrink-0 ml-2">
|
<div className="flex items-center gap-1.5 flex-shrink-0 ml-2">
|
||||||
{notificationCount !== undefined &&
|
{notificationCount !== undefined &&
|
||||||
notificationCount > 0 && (
|
notificationCount > 0 && (
|
||||||
<Badge
|
<Badge variant="secondary">
|
||||||
variant="secondary"
|
{notificationCount > 99
|
||||||
>
|
? "99+"
|
||||||
{notificationCount > 99 ? "99+" : notificationCount}
|
: notificationCount}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
{build === "enterprise" &&
|
{build === "enterprise" &&
|
||||||
@@ -321,9 +321,7 @@ export function SidebarNav({
|
|||||||
<div className="flex items-center gap-1.5 flex-shrink-0">
|
<div className="flex items-center gap-1.5 flex-shrink-0">
|
||||||
{notificationCount !== undefined &&
|
{notificationCount !== undefined &&
|
||||||
notificationCount > 0 && (
|
notificationCount > 0 && (
|
||||||
<Badge
|
<Badge variant="secondary">
|
||||||
variant="secondary"
|
|
||||||
>
|
|
||||||
{notificationCount > 99
|
{notificationCount > 99
|
||||||
? "99+"
|
? "99+"
|
||||||
: notificationCount}
|
: notificationCount}
|
||||||
@@ -346,8 +344,8 @@ export function SidebarNav({
|
|||||||
notificationCount !== undefined &&
|
notificationCount !== undefined &&
|
||||||
notificationCount > 0 && (
|
notificationCount > 0 && (
|
||||||
<Badge
|
<Badge
|
||||||
variant="default"
|
variant="secondary"
|
||||||
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"
|
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}
|
{notificationCount > 99 ? "99+" : notificationCount}
|
||||||
</Badge>
|
</Badge>
|
||||||
@@ -379,7 +377,7 @@ export function SidebarNav({
|
|||||||
{notificationCount !== undefined &&
|
{notificationCount !== undefined &&
|
||||||
notificationCount > 0 && (
|
notificationCount > 0 && (
|
||||||
<Badge
|
<Badge
|
||||||
variant="default"
|
variant="secondary"
|
||||||
className="flex-shrink-0 bg-primary text-primary-foreground"
|
className="flex-shrink-0 bg-primary text-primary-foreground"
|
||||||
>
|
>
|
||||||
{notificationCount > 99
|
{notificationCount > 99
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export function formatFingerprintInfo(
|
|||||||
): string {
|
): string {
|
||||||
if (!fingerprint) return "";
|
if (!fingerprint) return "";
|
||||||
const parts: string[] = [];
|
const parts: string[] = [];
|
||||||
|
const normalizedPlatform = fingerprint.platform?.toLowerCase() || "unknown";
|
||||||
|
|
||||||
if (fingerprint.platform) {
|
if (fingerprint.platform) {
|
||||||
parts.push(
|
parts.push(
|
||||||
@@ -53,14 +54,17 @@ export function formatFingerprintInfo(
|
|||||||
if (fingerprint.arch) {
|
if (fingerprint.arch) {
|
||||||
parts.push(`${t("architecture")}: ${fingerprint.arch}`);
|
parts.push(`${t("architecture")}: ${fingerprint.arch}`);
|
||||||
}
|
}
|
||||||
if (fingerprint.hostname) {
|
|
||||||
parts.push(`${t("hostname")}: ${fingerprint.hostname}`);
|
if (normalizedPlatform !== "ios" && normalizedPlatform !== "android") {
|
||||||
}
|
if (fingerprint.hostname) {
|
||||||
if (fingerprint.username) {
|
parts.push(`${t("hostname")}: ${fingerprint.hostname}`);
|
||||||
parts.push(`${t("username")}: ${fingerprint.username}`);
|
}
|
||||||
}
|
if (fingerprint.username) {
|
||||||
if (fingerprint.serialNumber) {
|
parts.push(`${t("username")}: ${fingerprint.username}`);
|
||||||
parts.push(`${t("serialNumber")}: ${fingerprint.serialNumber}`);
|
}
|
||||||
|
if (fingerprint.serialNumber) {
|
||||||
|
parts.push(`${t("serialNumber")}: ${fingerprint.serialNumber}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return parts.join("\n");
|
return parts.join("\n");
|
||||||
|
|||||||
Reference in New Issue
Block a user