mirror of
https://github.com/fosrl/pangolin.git
synced 2026-07-08 07:05:08 +02:00
🚧 wip: Use command bar nav items
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>
|
||||
|
||||
@@ -340,3 +340,253 @@ export const adminNavSections = (env?: Env): SidebarNavSection[] => [
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
export const commandBarNavSections = (
|
||||
env?: Env,
|
||||
options?: OrgNavSectionsOptions
|
||||
): SidebarNavSection[] => [
|
||||
{
|
||||
heading: "network",
|
||||
items: [
|
||||
{
|
||||
title: "sidebarSites",
|
||||
href: "/{orgId}/settings/sites",
|
||||
icon: <Plug className="size-4 flex-none" />
|
||||
},
|
||||
{
|
||||
title: "sidebarResources",
|
||||
icon: <Waypoints className="size-4 flex-none" />,
|
||||
items: [
|
||||
{
|
||||
title: "sidebarProxyResources",
|
||||
href: "/{orgId}/settings/resources/public",
|
||||
icon: <Globe className="size-4 flex-none" />
|
||||
},
|
||||
{
|
||||
title: "sidebarClientResources",
|
||||
href: "/{orgId}/settings/resources/private",
|
||||
icon: <GlobeLock className="size-4 flex-none" />
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "sidebarClients",
|
||||
icon: <MonitorUp className="size-4 flex-none" />,
|
||||
items: [
|
||||
{
|
||||
href: "/{orgId}/settings/clients/user",
|
||||
title: "sidebarUserDevices",
|
||||
icon: <Laptop className="size-4 flex-none" />
|
||||
},
|
||||
{
|
||||
href: "/{orgId}/settings/clients/machine",
|
||||
title: "sidebarMachineClients",
|
||||
icon: <Server className="size-4 flex-none" />
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "sidebarDomains",
|
||||
href: "/{orgId}/settings/domains",
|
||||
icon: <Globe className="size-4 flex-none" />
|
||||
},
|
||||
...(build === "saas"
|
||||
? [
|
||||
{
|
||||
title: "sidebarRemoteExitNodes",
|
||||
href: "/{orgId}/settings/remote-exit-nodes",
|
||||
icon: <Server className="size-4 flex-none" />
|
||||
}
|
||||
]
|
||||
: [])
|
||||
]
|
||||
},
|
||||
{
|
||||
heading: "accessControl",
|
||||
items: [
|
||||
{
|
||||
title: "sidebarTeam",
|
||||
icon: <Users className="size-4 flex-none" />,
|
||||
items: [
|
||||
{
|
||||
title: "sidebarUsers",
|
||||
href: "/{orgId}/settings/access/users",
|
||||
icon: <User className="size-4 flex-none" />
|
||||
},
|
||||
{
|
||||
title: "sidebarRoles",
|
||||
href: "/{orgId}/settings/access/roles",
|
||||
icon: <Users className="size-4 flex-none" />
|
||||
},
|
||||
{
|
||||
title: "sidebarInvitations",
|
||||
href: "/{orgId}/settings/access/invitations",
|
||||
icon: <TicketCheck className="size-4 flex-none" />
|
||||
}
|
||||
]
|
||||
},
|
||||
...(!env?.flags.disableEnterpriseFeatures
|
||||
? [
|
||||
{
|
||||
title: "sidebarPolicies",
|
||||
|
||||
icon: <ShieldIcon className="size-4 flex-none" />,
|
||||
items: [
|
||||
{
|
||||
title: "sidebarResourcePolicies",
|
||||
href: "/{orgId}/settings/policies/resources/public",
|
||||
icon: (
|
||||
<GlobeIcon className="size-4 flex-none" />
|
||||
)
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
: []),
|
||||
// PaidFeaturesAlert
|
||||
...((build === "oss" && !env?.flags.disableEnterpriseFeatures) ||
|
||||
build === "saas" ||
|
||||
env?.app.identityProviderMode === "org" ||
|
||||
(env?.app.identityProviderMode === undefined && build !== "oss")
|
||||
? [
|
||||
{
|
||||
title: "sidebarIdentityProviders",
|
||||
href: "/{orgId}/settings/idp",
|
||||
icon: <Fingerprint className="size-4 flex-none" />
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...(!env?.flags.disableEnterpriseFeatures
|
||||
? [
|
||||
{
|
||||
title: "sidebarApprovals",
|
||||
href: "/{orgId}/settings/access/approvals",
|
||||
icon: <UserCog className="size-4 flex-none" />
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
title: "sidebarShareableLinks",
|
||||
href: "/{orgId}/settings/share-links",
|
||||
icon: <LinkIcon className="size-4 flex-none" />
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
heading: "sidebarOrganization",
|
||||
items: [
|
||||
{
|
||||
title: "sidebarLogsAndAnalytics",
|
||||
icon: <ChartLine className="size-4 flex-none" />,
|
||||
items: [
|
||||
{
|
||||
title: "sidebarLogsAnalytics",
|
||||
href: "/{orgId}/settings/logs/analytics",
|
||||
icon: <ChartLine className="size-4 flex-none" />
|
||||
},
|
||||
{
|
||||
title: "sidebarLogsRequest",
|
||||
href: "/{orgId}/settings/logs/request",
|
||||
icon: (
|
||||
<SquareMousePointer className="size-4 flex-none" />
|
||||
)
|
||||
},
|
||||
...(!env?.flags.disableEnterpriseFeatures
|
||||
? [
|
||||
{
|
||||
title: "sidebarLogsAccess",
|
||||
href: "/{orgId}/settings/logs/access",
|
||||
icon: <ScanEye className="size-4 flex-none" />
|
||||
},
|
||||
{
|
||||
title: "sidebarLogsAction",
|
||||
href: "/{orgId}/settings/logs/action",
|
||||
icon: <Logs className="size-4 flex-none" />
|
||||
},
|
||||
{
|
||||
title: "sidebarLogsConnection",
|
||||
href: "/{orgId}/settings/logs/connection",
|
||||
icon: <Cable className="size-4 flex-none" />
|
||||
},
|
||||
{
|
||||
title: "sidebarLogsStreaming",
|
||||
href: "/{orgId}/settings/logs/streaming",
|
||||
icon: <Unplug className="size-4 flex-none" />
|
||||
}
|
||||
]
|
||||
: [])
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "sidebarManagement",
|
||||
icon: <Building2 className="size-4 flex-none" />,
|
||||
items: [
|
||||
...(!env?.flags.disableEnterpriseFeatures
|
||||
? [
|
||||
{
|
||||
title: "sidebarAlerting",
|
||||
href: "/{orgId}/settings/alerting",
|
||||
icon: (
|
||||
<BellRing className="size-4 flex-none" />
|
||||
)
|
||||
},
|
||||
{
|
||||
title: "sidebarProvisioning",
|
||||
href: "/{orgId}/settings/provisioning",
|
||||
icon: <Boxes className="size-4 flex-none" />
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
title: "sidebarBluePrints",
|
||||
href: "/{orgId}/settings/blueprints",
|
||||
icon: <ReceiptText className="size-4 flex-none" />
|
||||
},
|
||||
{
|
||||
title: "sidebarApiKeys",
|
||||
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" />
|
||||
}
|
||||
]
|
||||
: [])
|
||||
]
|
||||
},
|
||||
...(build === "saas" && options?.isPrimaryOrg
|
||||
? [
|
||||
{
|
||||
title: "sidebarBillingAndLicenses",
|
||||
icon: <CreditCard className="size-4 flex-none" />,
|
||||
items: [
|
||||
{
|
||||
title: "sidebarBilling",
|
||||
href: "/{orgId}/settings/billing",
|
||||
icon: (
|
||||
<CreditCard className="size-4 flex-none" />
|
||||
)
|
||||
},
|
||||
{
|
||||
title: "sidebarEnterpriseLicenses",
|
||||
href: "/{orgId}/settings/license",
|
||||
icon: (
|
||||
<TicketCheck className="size-4 flex-none" />
|
||||
)
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
title: "sidebarSettings",
|
||||
href: "/{orgId}/settings/general",
|
||||
icon: <Settings className="size-4 flex-none" />
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@@ -13,6 +13,7 @@ interface LayoutProps {
|
||||
orgId?: string;
|
||||
orgs?: ListUserOrgsResponse["orgs"];
|
||||
navItems?: SidebarNavSection[];
|
||||
commandNavItems?: SidebarNavSection[];
|
||||
showSidebar?: boolean;
|
||||
showHeader?: boolean;
|
||||
showTopBar?: boolean;
|
||||
@@ -24,6 +25,7 @@ export async function Layout({
|
||||
orgId,
|
||||
orgs,
|
||||
navItems = [],
|
||||
commandNavItems = [],
|
||||
showSidebar = true,
|
||||
showHeader = true,
|
||||
showTopBar = true,
|
||||
@@ -38,7 +40,11 @@ export async function Layout({
|
||||
(sidebarStateCookie !== "expanded" && defaultSidebarCollapsed);
|
||||
|
||||
return (
|
||||
<CommandPaletteProvider orgId={orgId} orgs={orgs} navItems={navItems}>
|
||||
<CommandPaletteProvider
|
||||
orgId={orgId}
|
||||
orgs={orgs}
|
||||
navItems={commandNavItems}
|
||||
>
|
||||
<div className="flex h-screen-safe overflow-hidden">
|
||||
{/* Desktop Sidebar */}
|
||||
{showSidebar && (
|
||||
|
||||
@@ -113,13 +113,6 @@ export default function SitesTable({
|
||||
const api = createApiClient(useEnvContext());
|
||||
const t = useTranslations();
|
||||
|
||||
// useEffect(() => {
|
||||
// const interval = setInterval(() => {
|
||||
// router.refresh();
|
||||
// }, 30_000);
|
||||
// return () => clearInterval(interval);
|
||||
// }, []);
|
||||
|
||||
const booleanSearchFilterSchema = z
|
||||
.enum(["true", "false"])
|
||||
.optional()
|
||||
|
||||
@@ -14,7 +14,7 @@ import { Badge } from "@app/components/ui/badge";
|
||||
import { ListUserOrgsResponse } from "@server/routers/org";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import {
|
||||
import React, {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
@@ -27,6 +27,7 @@ import { useCommandPaletteActions } from "./useCommandPaletteActions";
|
||||
import { useCommandPaletteNavigation } from "./useCommandPaletteNavigation";
|
||||
import { useCommandPaletteOrganizations } from "./useCommandPaletteOrganizations";
|
||||
import { useCommandPaletteSearch } from "./useCommandPaletteSearch";
|
||||
import { useUserContext } from "@app/hooks/useUserContext";
|
||||
|
||||
type CommandPaletteProps = {
|
||||
orgId?: string;
|
||||
@@ -34,6 +35,12 @@ type CommandPaletteProps = {
|
||||
navItems: SidebarNavSection[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Plan for command bar:
|
||||
* - the nav items should be custom items instead of all of the ones in the sidebar
|
||||
* - actions should be triggered by using `>` (like in Github)
|
||||
*/
|
||||
|
||||
export function CommandPalette({ orgId, orgs, navItems }: CommandPaletteProps) {
|
||||
const t = useTranslations();
|
||||
const router = useRouter();
|
||||
@@ -41,7 +48,7 @@ export function CommandPalette({ orgId, orgs, navItems }: CommandPaletteProps) {
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const navigationGroups = useCommandPaletteNavigation(navItems);
|
||||
const organizations = useCommandPaletteOrganizations(orgs);
|
||||
// const organizations = useCommandPaletteOrganizations(orgs);
|
||||
const actions = useCommandPaletteActions(orgId, orgs);
|
||||
const { shouldSearch, sites, resources, users, machineClients, isLoading } =
|
||||
useCommandPaletteSearch({
|
||||
@@ -69,45 +76,58 @@ export function CommandPalette({ orgId, orgs, navItems }: CommandPaletteProps) {
|
||||
[setOpen]
|
||||
);
|
||||
|
||||
const hasEntityResults =
|
||||
sites.length > 0 ||
|
||||
resources.length > 0 ||
|
||||
users.length > 0 ||
|
||||
machineClients.length > 0;
|
||||
// const hasEntityResults =
|
||||
// sites.length > 0 ||
|
||||
// resources.length > 0 ||
|
||||
// users.length > 0 ||
|
||||
// machineClients.length > 0;
|
||||
|
||||
return (
|
||||
<CommandDialog
|
||||
open={open}
|
||||
open //={open}
|
||||
onOpenChange={handleOpenChange}
|
||||
title={t("commandPaletteTitle")}
|
||||
description={t("commandPaletteDescription")}
|
||||
className="max-w-2xl **:data-[slot=command-input-wrapper]:h-15"
|
||||
>
|
||||
<CommandInput
|
||||
placeholder={t("commandPaletteSearchPlaceholder")}
|
||||
value={search}
|
||||
onValueChange={setSearch}
|
||||
/>
|
||||
<CommandList>
|
||||
<CommandList className="max-h-125 min-h-0 h-auto">
|
||||
<CommandEmpty>{t("commandPaletteNoResults")}</CommandEmpty>
|
||||
|
||||
{navigationGroups.map((group) => (
|
||||
<CommandGroup key={group.heading} heading={group.heading}>
|
||||
{group.items.map((item) => (
|
||||
<CommandItem
|
||||
key={item.id}
|
||||
value={`${item.title} ${group.heading}`}
|
||||
onSelect={() =>
|
||||
runCommand(() => router.push(item.href))
|
||||
}
|
||||
>
|
||||
{item.icon}
|
||||
<span>{item.title}</span>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
<CommandGroup
|
||||
heading='Type ">" to open action mode'
|
||||
className="[&_[cmdk-group-heading]]:text-sm"
|
||||
/>
|
||||
|
||||
{navigationGroups.map((group, idx) => (
|
||||
<React.Fragment key={group.heading}>
|
||||
{idx > 0 && <CommandSeparator />}
|
||||
<CommandGroup
|
||||
heading={group.heading}
|
||||
className="[&_[cmdk-group-heading]]:py-2 [&_[cmdk-group-heading]]:text-sm pb-2.5"
|
||||
>
|
||||
{group.items.map((item) => (
|
||||
<CommandItem
|
||||
key={item.id}
|
||||
value={`${item.title} ${group.heading}`}
|
||||
onSelect={() =>
|
||||
runCommand(() => router.push(item.href))
|
||||
}
|
||||
className="h-9"
|
||||
>
|
||||
{item.icon}
|
||||
<span>{item.title}</span>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</React.Fragment>
|
||||
))}
|
||||
|
||||
{organizations.length > 1 && (
|
||||
{/* {organizations.length > 1 && (
|
||||
<>
|
||||
<CommandSeparator />
|
||||
<CommandGroup
|
||||
@@ -137,9 +157,9 @@ export function CommandPalette({ orgId, orgs, navItems }: CommandPaletteProps) {
|
||||
))}
|
||||
</CommandGroup>
|
||||
</>
|
||||
)}
|
||||
)} */}
|
||||
|
||||
{shouldSearch && orgId && (
|
||||
{/* {shouldSearch && orgId && (
|
||||
<>
|
||||
<CommandSeparator />
|
||||
{isLoading && !hasEntityResults ? (
|
||||
@@ -243,12 +263,15 @@ export function CommandPalette({ orgId, orgs, navItems }: CommandPaletteProps) {
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
)} */}
|
||||
|
||||
{actions.length > 0 && (
|
||||
{/* {actions.length > 0 && (
|
||||
<>
|
||||
<CommandSeparator />
|
||||
<CommandGroup heading={t("commandPaletteActions")}>
|
||||
<CommandGroup
|
||||
heading={t("commandPaletteActions")}
|
||||
className="pb-2.5"
|
||||
>
|
||||
{actions.map((action) => (
|
||||
<CommandItem
|
||||
key={action.id}
|
||||
@@ -269,7 +292,7 @@ export function CommandPalette({ orgId, orgs, navItems }: CommandPaletteProps) {
|
||||
))}
|
||||
</CommandGroup>
|
||||
</>
|
||||
)}
|
||||
)} */}
|
||||
</CommandList>
|
||||
</CommandDialog>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user