mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-21 20:36:37 +00:00
Merge branch 'dev' into feat/show-newt-install-command
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
import { ApprovalFeed } from "@app/components/ApprovalFeed";
|
||||
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
|
||||
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
||||
import ApprovalsBanner from "@app/components/ApprovalsBanner";
|
||||
import { internal } from "@app/lib/api";
|
||||
import { authCookieHeader } from "@app/lib/api/cookies";
|
||||
import { getCachedOrg } from "@app/lib/api/getCachedOrg";
|
||||
import type { ApprovalItem } from "@app/lib/queries";
|
||||
import OrgProvider from "@app/providers/OrgProvider";
|
||||
import type { GetOrgResponse } from "@server/routers/org";
|
||||
import type { ListRolesResponse } from "@server/routers/role";
|
||||
import type { AxiosResponse } from "axios";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
|
||||
@@ -35,6 +37,21 @@ export default async function ApprovalFeedPage(props: ApprovalFeedPageProps) {
|
||||
org = orgRes.data.data;
|
||||
}
|
||||
|
||||
// Fetch roles to check if approvals are enabled
|
||||
let hasApprovalsEnabled = false;
|
||||
const rolesRes = await internal
|
||||
.get<AxiosResponse<ListRolesResponse>>(
|
||||
`/org/${params.orgId}/roles`,
|
||||
await authCookieHeader()
|
||||
)
|
||||
.catch((e) => {});
|
||||
|
||||
if (rolesRes && rolesRes.status === 200) {
|
||||
hasApprovalsEnabled = rolesRes.data.data.roles.some(
|
||||
(role) => role.requireDeviceApproval === true
|
||||
);
|
||||
}
|
||||
|
||||
const t = await getTranslations();
|
||||
|
||||
return (
|
||||
@@ -44,11 +61,16 @@ export default async function ApprovalFeedPage(props: ApprovalFeedPageProps) {
|
||||
description={t("accessApprovalsDescription")}
|
||||
/>
|
||||
|
||||
<ApprovalsBanner />
|
||||
|
||||
<PaidFeaturesAlert />
|
||||
|
||||
<OrgProvider org={org}>
|
||||
<div className="container mx-auto max-w-12xl">
|
||||
<ApprovalFeed orgId={params.orgId} />
|
||||
<ApprovalFeed
|
||||
orgId={params.orgId}
|
||||
hasApprovalsEnabled={hasApprovalsEnabled}
|
||||
/>
|
||||
</div>
|
||||
</OrgProvider>
|
||||
</>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { internal } from "@app/lib/api";
|
||||
import { authCookieHeader } from "@app/lib/api/cookies";
|
||||
import { getUserDisplayName } from "@app/lib/getUserDisplayName";
|
||||
import { ListUsersResponse } from "@server/routers/user";
|
||||
import { AxiosResponse } from "axios";
|
||||
import UsersTable, { UserRow } from "../../../../../components/UsersTable";
|
||||
@@ -73,7 +74,11 @@ export default async function UsersPage(props: UsersPageProps) {
|
||||
return {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
displayUsername: user.email || user.name || user.username,
|
||||
displayUsername: getUserDisplayName({
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
username: user.username
|
||||
}),
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
type: user.type,
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
GetLoginPageResponse
|
||||
} from "@server/routers/loginPage/types";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export interface AuthPageProps {
|
||||
params: Promise<{ orgId: string }>;
|
||||
@@ -18,6 +19,12 @@ export interface AuthPageProps {
|
||||
|
||||
export default async function AuthPage(props: AuthPageProps) {
|
||||
const orgId = (await props.params).orgId;
|
||||
|
||||
// custom auth branding is only available in enterprise and saas
|
||||
if (build === "oss") {
|
||||
redirect(`/${orgId}/settings/general/`);
|
||||
}
|
||||
|
||||
let subscriptionStatus: GetOrgTierResponse | null = null;
|
||||
try {
|
||||
const subRes = await getCachedSubscription(orgId);
|
||||
|
||||
@@ -40,6 +40,7 @@ import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
||||
import { useResourceContext } from "@app/hooks/useResourceContext";
|
||||
import { toast } from "@app/hooks/useToast";
|
||||
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
||||
import { getUserDisplayName } from "@app/lib/getUserDisplayName";
|
||||
import { orgQueries, resourceQueries } from "@app/lib/queries";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { build } from "@server/build";
|
||||
@@ -154,7 +155,10 @@ export default function ResourceAuthenticationPage() {
|
||||
const allUsers = useMemo(() => {
|
||||
return orgUsers.map((user) => ({
|
||||
id: user.id.toString(),
|
||||
text: `${user.email || user.username}${user.type !== UserType.Internal ? ` (${user.idpName})` : ""}`
|
||||
text: `${getUserDisplayName({
|
||||
email: user.email,
|
||||
username: user.username
|
||||
})}${user.type !== UserType.Internal ? ` (${user.idpName})` : ""}`
|
||||
}));
|
||||
}, [orgUsers]);
|
||||
|
||||
@@ -229,7 +233,10 @@ export default function ResourceAuthenticationPage() {
|
||||
"users",
|
||||
resourceUsers.map((i) => ({
|
||||
id: i.userId.toString(),
|
||||
text: `${i.email || i.username}${i.type !== UserType.Internal ? ` (${i.idpName})` : ""}`
|
||||
text: `${getUserDisplayName({
|
||||
email: i.email,
|
||||
username: i.username
|
||||
})}${i.type !== UserType.Internal ? ` (${i.idpName})` : ""}`
|
||||
}))
|
||||
);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { internal } from "@app/lib/api";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { redirect } from "next/navigation";
|
||||
import { authCookieHeader } from "@app/lib/api/cookies";
|
||||
import { getUserDisplayName } from "@app/lib/getUserDisplayName";
|
||||
import { AdminGetUserResponse } from "@server/routers/user/adminGetUser";
|
||||
import { HorizontalTabs } from "@app/components/HorizontalTabs";
|
||||
import { cache } from "react";
|
||||
@@ -44,7 +45,15 @@ export default async function UserLayoutProps(props: UserLayoutProps) {
|
||||
return (
|
||||
<>
|
||||
<SettingsSectionTitle
|
||||
title={`${user?.email || user?.name || user?.username}`}
|
||||
title={
|
||||
user
|
||||
? getUserDisplayName({
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
username: user.username
|
||||
})
|
||||
: ""
|
||||
}
|
||||
description={t("userDescription2")}
|
||||
/>
|
||||
<HorizontalTabs items={navItems}>{children}</HorizontalTabs>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { verifySession } from "@app/lib/auth/verifySession";
|
||||
import { redirect } from "next/navigation";
|
||||
import DeviceLoginForm from "@/components/DeviceLoginForm";
|
||||
import { getUserDisplayName } from "@app/lib/getUserDisplayName";
|
||||
import { cache } from "react";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
@@ -24,7 +25,12 @@ export default async function DeviceLoginPage({ searchParams }: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
const userName = user?.name || user?.username || "";
|
||||
const userName = user
|
||||
? getUserDisplayName({
|
||||
name: user.name,
|
||||
username: user.username
|
||||
})
|
||||
: "";
|
||||
|
||||
return (
|
||||
<DeviceLoginForm
|
||||
|
||||
@@ -61,15 +61,13 @@ export const orgNavSections = (env?: Env): SidebarNavSection[] => [
|
||||
{
|
||||
title: "sidebarClientResources",
|
||||
href: "/{orgId}/settings/resources/client",
|
||||
icon: <GlobeLock className="size-4 flex-none" />,
|
||||
isBeta: true
|
||||
icon: <GlobeLock className="size-4 flex-none" />
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "sidebarClients",
|
||||
icon: <MonitorUp className="size-4 flex-none" />,
|
||||
isBeta: true,
|
||||
items: [
|
||||
{
|
||||
href: "/{orgId}/settings/clients/user",
|
||||
|
||||
Reference in New Issue
Block a user