mirror of
https://github.com/fosrl/pangolin.git
synced 2026-07-08 23:24:54 +02:00
Merge pull request #3263 from Fredkiss3/feat/command-bar
feat: Command Palette for searching accross the dashboard
This commit is contained in:
@@ -12,7 +12,7 @@ import UserProvider from "@app/providers/UserProvider";
|
||||
import { Layout } from "@app/components/Layout";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { pullEnv } from "@app/lib/pullEnv";
|
||||
import { orgNavSections } from "@app/app/navigation";
|
||||
import { commandBarNavSections, orgNavSections } from "@app/app/navigation";
|
||||
import { getCachedOrgUser } from "@app/lib/api/getCachedOrgUser";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
@@ -82,6 +82,9 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
|
||||
navItems={orgNavSections(env, {
|
||||
isPrimaryOrg: primaryOrg
|
||||
})}
|
||||
commandNavItems={commandBarNavSections(env, {
|
||||
isPrimaryOrg: primaryOrg
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</Layout>
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
import PrivateResourcesBanner from "@app/components/PrivateResourcesBanner";
|
||||
import type { InternalResourceRow } from "@app/components/PrivateResourcesTable";
|
||||
import PrivateResourcesTable from "@app/components/PrivateResourcesTable";
|
||||
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
||||
import PrivateResourcesBanner from "@app/components/PrivateResourcesBanner";
|
||||
import { internal } from "@app/lib/api";
|
||||
import { authCookieHeader } from "@app/lib/api/cookies";
|
||||
import { getCachedOrg } from "@app/lib/api/getCachedOrg";
|
||||
import OrgProvider from "@app/providers/OrgProvider";
|
||||
import type { ListResourcesResponse } from "@server/routers/resource";
|
||||
import { GetSiteResponse } from "@server/routers/site/getSite";
|
||||
import type { ListAllSiteResourcesByOrgResponse } from "@server/routers/siteResource";
|
||||
import type ResponseT from "@server/types/Response";
|
||||
import type { AxiosResponse } from "axios";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import type { Metadata } from "next";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -24,13 +21,6 @@ export interface ClientResourcesPageProps {
|
||||
searchParams: Promise<Record<string, string>>;
|
||||
}
|
||||
|
||||
function parsePositiveInt(s: string | undefined): number | undefined {
|
||||
if (!s) return undefined;
|
||||
const n = Number(s);
|
||||
if (!Number.isInteger(n) || n <= 0) return undefined;
|
||||
return n;
|
||||
}
|
||||
|
||||
export default async function ClientResourcesPage(
|
||||
props: ClientResourcesPageProps
|
||||
) {
|
||||
@@ -39,7 +29,7 @@ export default async function ClientResourcesPage(
|
||||
const searchParams = new URLSearchParams(await props.searchParams);
|
||||
|
||||
let siteResources: ListAllSiteResourcesByOrgResponse["siteResources"] = [];
|
||||
let pagination: ListResourcesResponse["pagination"] = {
|
||||
let pagination: ListAllSiteResourcesByOrgResponse["pagination"] = {
|
||||
total: 0,
|
||||
page: 1,
|
||||
pageSize: 20
|
||||
@@ -56,34 +46,6 @@ export default async function ClientResourcesPage(
|
||||
pagination = responseData.pagination;
|
||||
} catch (e) {}
|
||||
|
||||
const siteIdParam = parsePositiveInt(
|
||||
searchParams.get("siteId") ?? undefined
|
||||
);
|
||||
|
||||
let initialFilterSite: {
|
||||
siteId: number;
|
||||
name: string;
|
||||
type: string;
|
||||
} | null = null;
|
||||
if (siteIdParam) {
|
||||
try {
|
||||
const siteRes = await internal.get(
|
||||
`/site/${siteIdParam}`,
|
||||
await authCookieHeader()
|
||||
);
|
||||
const s = (siteRes.data as ResponseT<GetSiteResponse>).data;
|
||||
if (s && s.orgId === params.orgId) {
|
||||
initialFilterSite = {
|
||||
siteId: s.siteId,
|
||||
name: s.name,
|
||||
type: s.type
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
// leave null
|
||||
}
|
||||
}
|
||||
|
||||
let org = null;
|
||||
try {
|
||||
const res = await getCachedOrg(params.orgId);
|
||||
@@ -154,7 +116,6 @@ export default async function ClientResourcesPage(
|
||||
pageIndex: pagination.page - 1,
|
||||
pageSize: pagination.pageSize
|
||||
}}
|
||||
initialFilterSite={initialFilterSite}
|
||||
/>
|
||||
</OrgProvider>
|
||||
</>
|
||||
|
||||
@@ -341,3 +341,235 @@ export const adminNavSections = (env?: Env): SidebarNavSection[] => [
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
export type CommandBarNavSection = {
|
||||
// Added from 'dev' branch
|
||||
heading: string;
|
||||
items: CommandBarNavItem[];
|
||||
};
|
||||
|
||||
export type CommandBarNavItem = {
|
||||
href?: string;
|
||||
title: string;
|
||||
icon?: React.ReactNode;
|
||||
showEE?: boolean;
|
||||
isBeta?: boolean;
|
||||
};
|
||||
|
||||
export const commandBarNavSections = (
|
||||
env?: Env,
|
||||
options?: OrgNavSectionsOptions
|
||||
): CommandBarNavSection[] => [
|
||||
{
|
||||
heading: "network",
|
||||
items: [
|
||||
{
|
||||
title: "commandSites",
|
||||
href: "/{orgId}/settings/sites",
|
||||
icon: <Plug className="size-4 flex-none" />
|
||||
},
|
||||
{
|
||||
title: "commandProxyResources",
|
||||
href: "/{orgId}/settings/resources/public",
|
||||
icon: <Globe className="size-4 flex-none" />
|
||||
},
|
||||
{
|
||||
title: "commandClientResources",
|
||||
href: "/{orgId}/settings/resources/private",
|
||||
icon: <GlobeLock className="size-4 flex-none" />
|
||||
},
|
||||
{
|
||||
href: "/{orgId}/settings/clients/user",
|
||||
title: "commandUserDevices",
|
||||
icon: <Laptop className="size-4 flex-none" />
|
||||
},
|
||||
{
|
||||
href: "/{orgId}/settings/clients/machine",
|
||||
title: "commandMachineClients",
|
||||
icon: <Server className="size-4 flex-none" />
|
||||
},
|
||||
...(build === "saas"
|
||||
? [
|
||||
{
|
||||
title: "commandRemoteExitNodes",
|
||||
href: "/{orgId}/settings/remote-exit-nodes",
|
||||
icon: <Server className="size-4 flex-none" />
|
||||
}
|
||||
]
|
||||
: [])
|
||||
]
|
||||
},
|
||||
{
|
||||
heading: "commandTeam",
|
||||
items: [
|
||||
{
|
||||
title: "commandUsers",
|
||||
href: "/{orgId}/settings/access/users",
|
||||
icon: <User className="size-4 flex-none" />
|
||||
},
|
||||
{
|
||||
title: "commandRoles",
|
||||
href: "/{orgId}/settings/access/roles",
|
||||
icon: <Users className="size-4 flex-none" />
|
||||
},
|
||||
{
|
||||
title: "commandInvitations",
|
||||
href: "/{orgId}/settings/access/invitations",
|
||||
icon: <TicketCheck className="size-4 flex-none" />
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
heading: "accessControl",
|
||||
items: [
|
||||
...(!env?.flags.disableEnterpriseFeatures
|
||||
? [
|
||||
{
|
||||
title: "commandResourcePolicies",
|
||||
href: "/{orgId}/settings/policies/resources/public",
|
||||
icon: <ShieldIcon className="size-4 flex-none" />
|
||||
}
|
||||
]
|
||||
: []),
|
||||
// PaidFeaturesAlert
|
||||
...((build === "oss" && !env?.flags.disableEnterpriseFeatures) ||
|
||||
build === "saas" ||
|
||||
env?.app.identityProviderMode === "org" ||
|
||||
(env?.app.identityProviderMode === undefined && build !== "oss")
|
||||
? [
|
||||
{
|
||||
title: "commandIdentityProviders",
|
||||
href: "/{orgId}/settings/idp",
|
||||
icon: <Fingerprint className="size-4 flex-none" />
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...(!env?.flags.disableEnterpriseFeatures
|
||||
? [
|
||||
{
|
||||
title: "commandApprovals",
|
||||
href: "/{orgId}/settings/access/approvals",
|
||||
icon: <UserCog className="size-4 flex-none" />
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
title: "commandShareableLinks",
|
||||
href: "/{orgId}/settings/share-links",
|
||||
icon: <LinkIcon className="size-4 flex-none" />
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
heading: "commandLogsAndAnalytics",
|
||||
items: [
|
||||
{
|
||||
title: "commandLogsAnalytics",
|
||||
href: "/{orgId}/settings/logs/analytics",
|
||||
icon: <ChartLine className="size-4 flex-none" />
|
||||
},
|
||||
{
|
||||
title: "commandLogsRequest",
|
||||
href: "/{orgId}/settings/logs/request",
|
||||
icon: <SquareMousePointer className="size-4 flex-none" />
|
||||
},
|
||||
...(!env?.flags.disableEnterpriseFeatures
|
||||
? [
|
||||
{
|
||||
title: "commandLogsAccess",
|
||||
href: "/{orgId}/settings/logs/access",
|
||||
icon: <ScanEye className="size-4 flex-none" />
|
||||
},
|
||||
{
|
||||
title: "commandLogsAction",
|
||||
href: "/{orgId}/settings/logs/action",
|
||||
icon: <Logs className="size-4 flex-none" />
|
||||
},
|
||||
{
|
||||
title: "commandLogsConnection",
|
||||
href: "/{orgId}/settings/logs/connection",
|
||||
icon: <Cable className="size-4 flex-none" />
|
||||
},
|
||||
{
|
||||
title: "commandLogsStreaming",
|
||||
href: "/{orgId}/settings/logs/streaming",
|
||||
icon: <Unplug className="size-4 flex-none" />
|
||||
}
|
||||
]
|
||||
: [])
|
||||
]
|
||||
},
|
||||
{
|
||||
heading: "commandManagement",
|
||||
items: [
|
||||
...(!env?.flags.disableEnterpriseFeatures
|
||||
? [
|
||||
{
|
||||
title: "commandAlerting",
|
||||
href: "/{orgId}/settings/alerting",
|
||||
icon: <BellRing className="size-4 flex-none" />
|
||||
},
|
||||
{
|
||||
title: "commandProvisioning",
|
||||
href: "/{orgId}/settings/provisioning",
|
||||
icon: <Boxes className="size-4 flex-none" />
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
title: "commandBluePrints",
|
||||
href: "/{orgId}/settings/blueprints",
|
||||
icon: <ReceiptText className="size-4 flex-none" />
|
||||
},
|
||||
{
|
||||
title: "commandApiKeys",
|
||||
href: "/{orgId}/settings/api-keys",
|
||||
icon: <KeyRound className="size-4 flex-none" />
|
||||
},
|
||||
...(!env?.flags.disableEnterpriseFeatures
|
||||
? [
|
||||
{
|
||||
title: "labels",
|
||||
href: "/{orgId}/settings/labels",
|
||||
icon: <TagIcon className="size-4 flex-none" />
|
||||
}
|
||||
]
|
||||
: [])
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
heading: "commandOrganization",
|
||||
items: [
|
||||
{
|
||||
title: "commandSettings",
|
||||
href: "/{orgId}/settings/general",
|
||||
icon: <Settings className="size-4 flex-none" />
|
||||
},
|
||||
{
|
||||
title: "commandDomains",
|
||||
href: "/{orgId}/settings/domains",
|
||||
icon: <Globe className="size-4 flex-none" />
|
||||
}
|
||||
]
|
||||
},
|
||||
...(build === "saas" && options?.isPrimaryOrg
|
||||
? [
|
||||
{
|
||||
heading: "commandBillingAndLicenses",
|
||||
items: [
|
||||
{
|
||||
title: "commandBilling",
|
||||
href: "/{orgId}/settings/billing",
|
||||
icon: <CreditCard className="size-4 flex-none" />
|
||||
},
|
||||
{
|
||||
title: "commandEnterpriseLicenses",
|
||||
href: "/{orgId}/settings/license",
|
||||
icon: <TicketCheck className="size-4 flex-none" />
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
: [])
|
||||
];
|
||||
|
||||
+3
-1
@@ -21,9 +21,11 @@ export default async function Page(props: {
|
||||
searchParams: Promise<{
|
||||
redirect: string | undefined;
|
||||
t: string | undefined;
|
||||
orgs?: string | undefined;
|
||||
}>;
|
||||
}) {
|
||||
const params = await props.searchParams; // this is needed to prevent static optimization
|
||||
const showOrgPicker = params.orgs === "1";
|
||||
|
||||
const env = pullEnv();
|
||||
|
||||
@@ -106,7 +108,7 @@ export default async function Page(props: {
|
||||
}
|
||||
}
|
||||
|
||||
if (targetOrgId) {
|
||||
if (targetOrgId && !showOrgPicker) {
|
||||
const targetOrg = orgs.find((org) => org.orgId === targetOrgId);
|
||||
return (
|
||||
<RedirectToOrg
|
||||
|
||||
Reference in New Issue
Block a user