diff --git a/messages/en-US.json b/messages/en-US.json index af0c9546e..5d3b891c6 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1519,7 +1519,7 @@ "navbar": "Navigation Menu", "navbarDescription": "Main navigation menu for the application", "navbarDocsLink": "Documentation", - "commandPaletteTitle": "Command palette", + "commandPaletteTitle": "Command Palette", "commandPaletteDescription": "Search for pages, organizations, resources, and actions", "commandPaletteSearchPlaceholder": "Search pages, resources, actions...", "commandPaletteNoResults": "No results found.", @@ -1529,17 +1529,18 @@ "commandPaletteSites": "Sites", "commandPaletteResources": "Resources", "commandPaletteUsers": "Users", - "commandPaletteClients": "Machine clients", + "commandPaletteClients": "Machine Clients", "commandPaletteActions": "Actions", - "commandPaletteCreateSite": "Create site", - "commandPaletteCreateProxyResource": "Create public resource", - "commandPaletteCreateUser": "Create user", - "commandPaletteCreateApiKey": "Create API key", - "commandPaletteCreateMachineClient": "Create machine client", - "commandPaletteCreateAlertRule": "Create alert rule", - "commandPaletteCreateIdentityProvider": "Create identity provider", - "commandPaletteToggleTheme": "Toggle theme", - "commandPaletteChooseOrganization": "Choose organization", + "commandPaletteCreateSite": "Create Site", + "commandPaletteCreateProxyResource": "Create Public Resource", + "commandPaletteCreatePrivateResource": "Create Private Resource", + "commandPaletteCreateUser": "Create User", + "commandPaletteCreateApiKey": "Create API Key", + "commandPaletteCreateMachineClient": "Create Machine Client", + "commandPaletteCreateAlertRule": "Create Alert Rule", + "commandPaletteCreateIdentityProvider": "Create Identity Provider", + "commandPaletteToggleTheme": "Toggle Theme", + "commandPaletteChooseOrganization": "Choose Organization", "commandPaletteShortcutMac": "⌘K", "commandPaletteShortcutWindows": "Ctrl K", "otpErrorEnable": "Unable to enable 2FA", @@ -1620,7 +1621,7 @@ "sidebarBillingAndLicenses": "Billing & Licenses", "sidebarLogsAnalytics": "Analytics", "commandSites": "Sites", - "commandActionModeInfo": "Type \">\" to open action mode", + "commandActionModeInfo": "Type \">\" To Open Action Mode", "commandResources": "Resources", "commandProxyResources": "Public Resources", "commandClientResources": "Private Resources", @@ -1655,6 +1656,8 @@ "commandBilling": "Billing", "commandEnterpriseLicenses": "Licenses", "commandSettings": "Settings", + "commandLauncher": "Launcher", + "commandResourceLauncher": "Resource Launcher", "commandSearchResults": "Search Results", "alertingTitle": "Alerting", "alertingDescription": "Define sources, triggers, and actions for notifications", @@ -3262,7 +3265,7 @@ "editInternalResourceDialogAddUsers": "Add Users", "editInternalResourceDialogAddClients": "Add Clients", "editInternalResourceDialogDestinationLabel": "Destination", - "editInternalResourceDialogDestinationDescription": "Choose where this resource runs and how clients reach it. Selecting multiple sites will create a high availability resource that can be accessed from any of the selected sites.", + "editInternalResourceDialogDestinationDescription": "Choose where this resource runs and how clients reach it", "internalResourceFormMultiSiteRoutingHelp": "Selecting multiple sites enables resilient routing and failover for high availability.", "internalResourceFormMultiSiteRoutingHelpLearnMore": "Learn more", "editInternalResourceDialogPortRestrictionsDescription": "Restrict access to specific TCP/UDP ports or allow/block all ports.", diff --git a/src/app/[orgId]/page.tsx b/src/app/[orgId]/page.tsx index d543b2752..614db095d 100644 --- a/src/app/[orgId]/page.tsx +++ b/src/app/[orgId]/page.tsx @@ -1,9 +1,11 @@ import { Layout } from "@app/components/Layout"; import ResourceLauncher from "@app/components/resource-launcher/ResourceLauncher"; +import { commandBarNavSections } from "@app/app/navigation"; import { internal } from "@app/lib/api"; import { authCookieHeader } from "@app/lib/api/cookies"; import { fetchLauncherPageData } from "@app/lib/launcherServerData"; import { verifySession } from "@app/lib/auth/verifySession"; +import { pullEnv } from "@app/lib/pullEnv"; import UserProvider from "@app/providers/UserProvider"; import { ListUserOrgsResponse } from "@server/routers/org"; import { GetOrgOverviewResponse } from "@server/routers/org/getOrgOverview"; @@ -57,6 +59,8 @@ export default async function OrgPage(props: OrgPageProps) { } catch (e) {} const isAdminOrOwner = Boolean(overview?.isAdmin || overview?.isOwner); + const env = pullEnv(); + const primaryOrg = orgs.find((o) => o.orgId === orgId)?.isPrimaryOrg; const searchParams = new URLSearchParams(await props.searchParams); const launcherData = overview @@ -73,6 +77,9 @@ export default async function OrgPage(props: OrgPageProps) { orgId={orgId} orgs={orgs} navItems={[]} + commandNavItems={commandBarNavSections(env, { + isPrimaryOrg: primaryOrg + })} showSidebar={false} launcherMode showViewAsAdmin={isAdminOrOwner} diff --git a/src/app/navigation.tsx b/src/app/navigation.tsx index 04c5ee684..b0650ce05 100644 --- a/src/app/navigation.tsx +++ b/src/app/navigation.tsx @@ -15,6 +15,7 @@ import { GlobeLock, KeyRound, Laptop, + LayoutGrid, Link as LinkIcon, Logs, MonitorUp, @@ -49,7 +50,7 @@ export const orgLangingNavItems: SidebarNavItem[] = [ { title: "sidebarAccount", href: "/{orgId}", - icon: + icon: } ]; @@ -360,6 +361,16 @@ export const commandBarNavSections = ( env?: Env, options?: OrgNavSectionsOptions ): CommandBarNavSection[] => [ + { + heading: "commandLauncher", + items: [ + { + title: "commandResourceLauncher", + href: "/{orgId}", + icon: + } + ] + }, { heading: "network", items: [ diff --git a/src/components/LayoutHeader.tsx b/src/components/LayoutHeader.tsx index cd130f5fe..2440d2494 100644 --- a/src/components/LayoutHeader.tsx +++ b/src/components/LayoutHeader.tsx @@ -105,8 +105,11 @@ export function LayoutHeader({ {showTopBar && (
- +
)} diff --git a/src/components/LayoutMobileMenu.tsx b/src/components/LayoutMobileMenu.tsx index 52536820e..ba6ae8f75 100644 --- a/src/components/LayoutMobileMenu.tsx +++ b/src/components/LayoutMobileMenu.tsx @@ -17,7 +17,7 @@ import { import { useUserContext } from "@app/hooks/useUserContext"; import { cn } from "@app/lib/cn"; import { ListUserOrgsResponse } from "@server/routers/org"; -import { Menu, Server, Settings, SquareMousePointer } from "lucide-react"; +import { Menu, Server, Settings, LayoutGrid } from "lucide-react"; import { useTranslations } from "next-intl"; import Link from "next/link"; import { usePathname } from "next/navigation"; @@ -151,7 +151,7 @@ export function LayoutMobileMenu({ } > - + {t( @@ -207,7 +207,11 @@ export function LayoutMobileMenu({ {showTopBar && (
- +
diff --git a/src/components/LayoutSidebar.tsx b/src/components/LayoutSidebar.tsx index c42e2c1fa..df1eb45d3 100644 --- a/src/components/LayoutSidebar.tsx +++ b/src/components/LayoutSidebar.tsx @@ -21,9 +21,9 @@ import { ListUserOrgsResponse } from "@server/routers/org"; import { ArrowRight, ExternalLink, + LayoutGrid, PanelRightOpen, - Server, - SquareMousePointer + Server } from "lucide-react"; import { useTranslations } from "next-intl"; import dynamic from "next/dynamic"; @@ -186,7 +186,7 @@ export function LayoutSidebar({ )} > - + @@ -195,7 +195,9 @@ export function LayoutSidebar({ sideOffset={8} >

- {t("resourceSidebarLauncherTitle")} + {t( + "resourceSidebarLauncherTitle" + )}

@@ -208,7 +210,7 @@ export function LayoutSidebar({ )} > - + {t("resourceSidebarLauncherTitle")} diff --git a/src/components/command-palette/CommandPalette.tsx b/src/components/command-palette/CommandPalette.tsx index 514ef6e1a..8b0a044d2 100644 --- a/src/components/command-palette/CommandPalette.tsx +++ b/src/components/command-palette/CommandPalette.tsx @@ -13,7 +13,9 @@ import { CommandList, CommandSeparator } from "@app/components/ui/command"; +import { Button } from "@app/components/ui/button"; import { cn } from "@app/lib/cn"; +import { useMediaQuery } from "@app/hooks/useMediaQuery"; import { ListUserOrgsResponse } from "@server/routers/org"; import { ChevronRightIcon, @@ -22,7 +24,8 @@ import { LaptopIcon, PlugIcon, ServerIcon, - UserIcon + UserIcon, + X } from "lucide-react"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; @@ -34,6 +37,7 @@ import React, { useMemo, useState } from "react"; +import { useCanUseCommandPalette } from "./useCanUseCommandPalette"; import { useCommandPaletteActions } from "./useCommandPaletteActions"; import { useCommandPaletteNavigation } from "./useCommandPaletteNavigation"; import { useCommandPaletteSearch } from "./useCommandPaletteSearch"; @@ -57,6 +61,7 @@ export function CommandPalette({ orgId, orgs, navItems }: CommandPaletteProps) { const router = useRouter(); const { open, setOpen } = useCommandPalette(); const [search, setSearch] = useState(""); + const isDesktop = useMediaQuery("(min-width: 768px)"); const isActionMode = search.startsWith(">"); @@ -134,6 +139,20 @@ export function CommandPalette({ orgId, orgs, navItems }: CommandPaletteProps) { value={search} onValueChange={setSearch} isLoading={!isActionMode && shouldSearch && isLoading} + trailing={ + !isDesktop ? ( + + ) : undefined + } /> {t("commandPaletteNoResults")} @@ -427,13 +446,14 @@ function isEditableTarget(target: EventTarget | null) { ); } -export function CommandPaletteProvider({ +function CommandPaletteProviderInner({ children, orgId, orgs, navItems }: CommandPaletteProviderProps) { const [open, setOpen] = useState(false); + const canUseCommandPalette = useCanUseCommandPalette(orgId, orgs); const toggle = useCallback(() => { setOpen((current) => !current); @@ -449,6 +469,10 @@ export function CommandPaletteProvider({ ); useEffect(() => { + if (!canUseCommandPalette) { + return; + } + function onKeyDown(event: KeyboardEvent) { if ( event.key.toLowerCase() !== "k" || @@ -467,12 +491,18 @@ export function CommandPaletteProvider({ document.addEventListener("keydown", onKeyDown); return () => document.removeEventListener("keydown", onKeyDown); - }, [open, toggle]); + }, [canUseCommandPalette, open, toggle]); return ( {children} - + {canUseCommandPalette ? ( + + ) : null} ); } + +export function CommandPaletteProvider(props: CommandPaletteProviderProps) { + return ; +} diff --git a/src/components/command-palette/CommandPaletteTrigger.tsx b/src/components/command-palette/CommandPaletteTrigger.tsx index 785013ae2..02913dda7 100644 --- a/src/components/command-palette/CommandPaletteTrigger.tsx +++ b/src/components/command-palette/CommandPaletteTrigger.tsx @@ -6,11 +6,15 @@ import { cn } from "@app/lib/cn"; import { Search } from "lucide-react"; import { useEffect, useState } from "react"; import { useTranslations } from "next-intl"; +import { ListUserOrgsResponse } from "@server/routers/org"; import { useCommandPalette } from "./CommandPalette"; +import { useCanUseCommandPalette } from "./useCanUseCommandPalette"; type CommandPaletteTriggerProps = { variant?: "header" | "mobile"; className?: string; + orgId?: string; + orgs?: ListUserOrgsResponse["orgs"]; }; function useIsMac() { @@ -25,11 +29,18 @@ function useIsMac() { export function CommandPaletteTrigger({ variant = "header", - className + className, + orgId, + orgs }: CommandPaletteTriggerProps) { const t = useTranslations(); const { setOpen } = useCommandPalette(); const isMac = useIsMac(); + const canUseCommandPalette = useCanUseCommandPalette(orgId, orgs); + + if (!canUseCommandPalette) { + return null; + } if (variant === "mobile") { return ( @@ -49,13 +60,13 @@ export function CommandPaletteTrigger({