ui/src/util/router/pages.tsx
import {
ActivitySquare,
BadgeCheck,
Boxes,
FolderTree,
GitCompare,
Layers,
LucideIcon,
Network,
PlaySquare,
Radio,
Settings,
} from "lucide-react";
import { useMatches, useParams, useSearchParams } from "react-router-dom";
import ConsumerEditorPage from "~/pages/namespace/Explorer/Consumer";
import EndpointEditorPage from "~/pages/namespace/Explorer/Endpoint";
import ErrorPage from "./ErrorPage";
import EventsPage from "~/pages/namespace/Events";
import ExplorerPage from "~/pages/namespace/Explorer";
import GatewayConsumersPage from "~/pages/namespace/Gateway/Consumers";
import GatewayPage from "~/pages/namespace/Gateway";
import GatewayRoutesPage from "~/pages/namespace/Gateway/Routes";
import GroupsPage from "~/pages/namespace/Permissions/Groups";
import History from "~/pages/namespace/Events/History";
import InstancesPage from "~/pages/namespace/Instances";
import InstancesPageDetail from "~/pages/namespace/Instances/Detail";
import InstancesPageList from "~/pages/namespace/Instances/List";
import JqPlaygroundPage from "~/pages/namespace/JqPlayground";
import Listeners from "~/pages/namespace/Events/Listeners";
import Logs from "~/pages/namespace/Mirror/Detail/Sync";
import MirrorDetail from "~/pages/namespace/Mirror/Detail";
import MirrorPage from "~/pages/namespace/Mirror";
import MonitoringPage from "~/pages/namespace/Monitoring";
import PermissionsPage from "~/pages/namespace/Permissions";
import PolicyPage from "~/pages/namespace/Permissions/Policy";
import type { RouteObject } from "react-router-dom";
import RoutesPageDetail from "~/pages/namespace/Gateway/Routes/Detail";
import ServiceDetailPage from "~/pages/namespace/Services/Detail";
import ServiceEditorPage from "~/pages/namespace/Explorer/Service";
import ServicesListPage from "~/pages/namespace/Services/List";
import ServicesPage from "~/pages/namespace/Services";
import SettingsPage from "~/pages/namespace/Settings";
import TokensPage from "~/pages/namespace/Permissions/Tokens";
import TreePage from "~/pages/namespace/Explorer/Tree";
import WorkflowPage from "~/pages/namespace/Explorer/Workflow";
import WorkflowPageActive from "~/pages/namespace/Explorer/Workflow/Edit";
import WorkflowPageOverview from "~/pages/namespace/Explorer/Workflow/Overview";
import WorkflowPageServices from "~/pages/namespace/Explorer/Workflow/Services";
import WorkflowPageSettings from "~/pages/namespace/Explorer/Workflow/Settings";
import { checkHandlerInMatcher as checkHandler } from "./utils";
import { isEnterprise } from "~/config/env/utils";
import { removeLeadingSlash } from "~/api/files/utils";
type PageBase = {
name: string;
icon: LucideIcon;
route: RouteObject;
};
type KeysWithNoPathParams = "monitoring" | "settings" | "jqPlayground";
type DefaultPageSetup = Record<
KeysWithNoPathParams,
PageBase & { createHref: (params: { namespace: string }) => string }
>;
export type ExplorerSubpages =
| "workflow"
| "workflow-overview"
| "workflow-settings"
| "workflow-services"
| "service"
| "endpoint"
| "consumer";
type ExplorerSubpagesParams =
| {
subpage?: Exclude<ExplorerSubpages, "workflow-services">;
}
// workflow-services must has an optional serviceId param
| {
subpage: "workflow-services";
serviceId?: string;
};
type ExplorerPageSetup = Record<
"explorer",
PageBase & {
createHref: (
params: {
namespace: string;
path?: string; // if no subpage is provided, it opens the tree view
} & ExplorerSubpagesParams
) => string;
useParams: () => {
namespace: string | undefined;
path: string | undefined;
isExplorerPage: boolean;
isTreePage: boolean;
isWorkflowPage: boolean;
isWorkflowEditorPage: boolean;
isWorkflowOverviewPage: boolean;
isWorkflowSettingsPage: boolean;
isWorkflowServicesPage: boolean;
isServicePage: boolean;
isEndpointPage: boolean;
isConsumerPage: boolean;
serviceId: string | undefined;
};
}
>;
type InstancesPageSetup = Record<
"instances",
PageBase & {
createHref: (params: { namespace: string; instance?: string }) => string;
useParams: () => {
namespace: string | undefined;
instance: string | undefined;
isInstancePage: boolean;
isInstanceListPage: boolean;
isInstanceDetailPage: boolean;
};
}
>;
type ServicesPageSetup = Record<
"services",
PageBase & {
createHref: (params: { namespace: string; service?: string }) => string;
useParams: () => {
namespace: string | undefined;
service: string | undefined;
isServicePage: boolean;
isServiceListPage: boolean;
isServiceDetailPage: boolean;
};
}
>;
type EventsPageSetup = Record<
"events",
PageBase & {
createHref: (params: {
namespace: string;
subpage?: "eventlisteners";
}) => string;
useParams: () => {
isEventsHistoryPage: boolean;
isEventsListenersPage: boolean;
};
}
>;
type MirrorPageSetup = Record<
"mirror",
PageBase & {
createHref: (params: { namespace: string; sync?: string }) => string;
useParams: () => {
sync?: string;
isMirrorPage: boolean;
isSyncDetailPage: boolean;
};
}
>;
type MonitoringPageSetup = Record<
"monitoring",
PageBase & {
useParams: () => {
isMonitoringPage: boolean;
};
}
>;
type SettingsPageSetup = Record<
"settings",
PageBase & {
useParams: () => {
isSettingsPage: boolean;
};
}
>;
type JqPlaygroundPageSetup = Record<
"jqPlayground",
PageBase & {
useParams: () => {
isJqPlaygroundPage: boolean;
};
}
>;
type GatewayPageSetup = Record<
"gateway",
PageBase & {
createHref: (
params: { namespace: string } & (
| { subpage?: "consumers" }
| {
subpage: "routeDetail";
routePath: string;
}
)
) => string;
useParams: () => {
isGatewayPage: boolean;
isGatewayRoutesPage: boolean;
isGatewayRoutesDetailPage: boolean;
isGatewayConsumerPage: boolean;
routePath?: string;
};
}
>;
type PageType = DefaultPageSetup &
ExplorerPageSetup &
InstancesPageSetup &
ServicesPageSetup &
EventsPageSetup &
MonitoringPageSetup &
SettingsPageSetup &
GatewayPageSetup &
JqPlaygroundPageSetup &
MirrorPageSetup;
type PermissionsPageSetup = Partial<
Record<
"permissions",
PageBase & {
createHref: (params: {
namespace: string;
subpage?: "tokens" | "groups"; // policy is the default page
}) => string;
useParams: () => {
isPermissionsPage: boolean;
isPermissionsPolicyPage: boolean;
isPermissionsTokenPage: boolean;
isPermissionsGroupPage: boolean;
};
}
>
>;
type EnterprisePageType = PermissionsPageSetup;
// these are the direct child pages that live in the /:namespace folder
// the main goal of this abstraction is to make the router as typesafe as
// possible and to globally manage and change the url structure
// entries with no name and icon will not be rendered in the navigation
export const usePages = (): PageType & EnterprisePageType => {
const enterprisePages: EnterprisePageType = isEnterprise()
? {
permissions: {
name: "components.mainMenu.permissions",
icon: BadgeCheck,
createHref: (params) => {
let subpage = "";
if (params.subpage === "groups") {
subpage = "/groups";
}
if (params.subpage === "tokens") {
subpage = "/tokens";
}
return `/n/${params.namespace}/permissions${subpage}`;
},
useParams: () => {
const [, secondLevel, thirdLevel] = useMatches(); // first level is namespace level
const isPermissionsPage = checkHandler(
secondLevel,
"isPermissionsPage"
);
const isPermissionsPolicyPage = checkHandler(
thirdLevel,
"isPermissionsPolicyPage"
);
const isPermissionsTokenPage = checkHandler(
thirdLevel,
"isPermissionsTokenPage"
);
const isPermissionsGroupPage = checkHandler(
thirdLevel,
"isPermissionsGroupPage"
);
return {
isPermissionsPage,
isPermissionsPolicyPage,
isPermissionsTokenPage,
isPermissionsGroupPage,
};
},
route: {
path: "permissions",
element: <PermissionsPage />,
handle: { permissions: true, isPermissionsPage: true },
children: [
{
path: "",
element: <PolicyPage />,
handle: { isPermissionsPolicyPage: true },
},
{
path: "tokens",
element: <TokensPage />,
handle: { isPermissionsTokenPage: true },
},
{
path: "groups",
element: <GroupsPage />,
handle: { isPermissionsGroupPage: true },
},
],
},
},
}
: {};
return {
explorer: {
name: "components.mainMenu.explorer",
icon: FolderTree,
createHref: (params) => {
let path = "";
if (params.path) {
path = params.path.startsWith("/") ? params.path : `/${params.path}`;
}
const subfolder: Record<ExplorerSubpages, string> = {
workflow: "workflow/edit",
"workflow-overview": "workflow/overview",
"workflow-settings": "workflow/settings",
"workflow-services": "workflow/services",
endpoint: "endpoint",
consumer: "consumer",
service: "service",
};
let searchParamsObj;
if (params.subpage === "workflow-services" && params.serviceId) {
searchParamsObj = {
serviceId: params.serviceId,
};
}
const searchParams = new URLSearchParams(searchParamsObj);
const subpage = params.subpage ? subfolder[params.subpage] : "tree";
const searchParamsString = searchParams.toString();
const urlParams = searchParamsString ? `?${searchParamsString}` : "";
return `/n/${params.namespace}/explorer/${subpage}${path}${urlParams}`;
},
useParams: () => {
const { "*": path, namespace } = useParams();
const [, , thirdLvl, fourthLvl] = useMatches(); // first level is namespace level
const [searchParams] = useSearchParams();
// explorer.useParams() can also be called on pages that are not
// the explorer page and some params might accidentally match as
// well (like wildcards). To prevent that we use custom handles that
// we injected in the route objects
const isTreePage = checkHandler(thirdLvl, "isTreePage");
const isWorkflowPage = checkHandler(thirdLvl, "isWorkflowPage");
const isServicePage = checkHandler(thirdLvl, "isServicePage");
const isEndpointPage = checkHandler(thirdLvl, "isEndpointPage");
const isConsumerPage = checkHandler(thirdLvl, "isConsumerPage");
const isExplorerPage =
isTreePage ||
isWorkflowPage ||
isServicePage ||
isEndpointPage ||
isConsumerPage;
const isWorkflowEditorPage = checkHandler(fourthLvl, "isEditorPage");
const isWorkflowOverviewPage = checkHandler(
fourthLvl,
"isOverviewPage"
);
const isWorkflowSettingsPage = checkHandler(
fourthLvl,
"isSettingsPage"
);
const isWorkflowServicesPage = checkHandler(
fourthLvl,
"isServicesPage"
);
return {
path: isExplorerPage ? path : undefined,
namespace: isExplorerPage ? namespace : undefined,
isExplorerPage,
isTreePage,
isWorkflowPage,
isWorkflowEditorPage,
isWorkflowOverviewPage,
isWorkflowSettingsPage,
isWorkflowServicesPage,
isServicePage,
isEndpointPage,
isConsumerPage,
serviceId: searchParams.get("serviceId") ?? undefined,
};
},
route: {
path: "explorer/",
errorElement: <ErrorPage className="h-full" />,
element: <ExplorerPage />,
handle: { explorer: true },
children: [
{
path: "tree/*",
element: <TreePage />,
handle: { isTreePage: true },
},
{
path: "workflow/",
element: <WorkflowPage />,
handle: { isWorkflowPage: true },
children: [
{
path: "edit/*",
element: <WorkflowPageActive />,
handle: { isEditorPage: true },
},
{
path: "overview/*",
element: <WorkflowPageOverview />,
handle: { isOverviewPage: true },
},
{
path: "settings/*",
element: <WorkflowPageSettings />,
handle: { isSettingsPage: true },
},
{
path: "services/*",
element: <WorkflowPageServices />,
handle: { isServicesPage: true },
},
],
},
{
path: "service/*",
element: <ServiceEditorPage />,
handle: { isServicePage: true },
},
{
path: "endpoint/*",
element: <EndpointEditorPage />,
handle: { isEndpointPage: true },
},
{
path: "consumer/*",
element: <ConsumerEditorPage />,
handle: { isConsumerPage: true },
},
],
},
},
monitoring: {
name: "components.mainMenu.monitoring",
icon: ActivitySquare,
createHref: (params) => `/n/${params.namespace}/monitoring`,
useParams: () => {
const [, secondLevel] = useMatches(); // first level is namespace level
const isMonitoringPage = checkHandler(secondLevel, "isMonitoringPage");
return { isMonitoringPage };
},
route: {
path: "monitoring",
element: <MonitoringPage />,
handle: { monitoring: true, isMonitoringPage: true },
},
},
instances: {
name: "components.mainMenu.instances",
icon: Boxes,
createHref: (params) =>
`/n/${params.namespace}/instances${
params.instance ? `/${params.instance}` : ""
}`,
useParams: () => {
const { namespace, instance } = useParams();
const [, , thirdLvl] = useMatches(); // first level is namespace level
const isInstanceListPage = checkHandler(thirdLvl, "isInstanceListPage");
const isInstanceDetailPage = checkHandler(
thirdLvl,
"isInstanceDetailPage"
);
const isInstancePage = isInstanceListPage || isInstanceDetailPage;
return {
namespace: isInstancePage ? namespace : undefined,
instance: isInstancePage ? instance : undefined,
isInstancePage,
isInstanceListPage,
isInstanceDetailPage,
};
},
route: {
path: "instances",
element: <InstancesPage />,
handle: { instances: true },
children: [
{
path: "",
element: <InstancesPageList />,
handle: { isInstanceListPage: true },
},
{
path: ":instance",
element: <InstancesPageDetail />,
handle: { isInstanceDetailPage: true },
},
],
},
},
events: {
name: "components.mainMenu.events",
icon: Radio,
createHref: (params) =>
`/n/${params.namespace}/events/${
params?.subpage === "eventlisteners" ? `listeners` : "history"
}`,
useParams: () => {
const [, , thirdLevel] = useMatches(); // first level is namespace level
const isEventsHistoryPage = checkHandler(
thirdLevel,
"isEventHistoryPage"
);
const isEventsListenersPage = checkHandler(
thirdLevel,
"isEventListenersPage"
);
return { isEventsHistoryPage, isEventsListenersPage };
},
route: {
path: "events",
element: <EventsPage />,
handle: { events: true },
children: [
{
path: "history",
element: <History />,
handle: { isEventHistoryPage: true },
},
{
path: "listeners",
element: <Listeners />,
handle: { isEventListenersPage: true },
},
],
},
},
gateway: {
name: "components.mainMenu.gateway",
icon: Network,
createHref: (params) => {
let subpage = "routes";
if (params.subpage === "routeDetail") {
subpage = `routes/${removeLeadingSlash(params.routePath)}`;
}
if (params.subpage === "consumers") {
subpage = "consumers";
}
return `/n/${params.namespace}/gateway/${subpage}`;
},
useParams: () => {
const { "*": path } = useParams();
const [, secondLevel, thirdLevel] = useMatches(); // first level is namespace level
const isGatewayPage = checkHandler(secondLevel, "isGatewayPage");
const isGatewayRoutesPage = checkHandler(
thirdLevel,
"isGatewayRoutesPage"
);
const isGatewayConsumerPage = checkHandler(
thirdLevel,
"isGatewayConsumerPage"
);
const isGatewayRoutesDetailPage = checkHandler(
thirdLevel,
"isGatewayRoutesDetailPage"
);
return {
isGatewayPage,
isGatewayRoutesPage,
isGatewayConsumerPage,
isGatewayRoutesDetailPage,
routePath: isGatewayRoutesDetailPage ? path : undefined,
};
},
route: {
path: "gateway",
element: <GatewayPage />,
handle: { gateway: true, isGatewayPage: true },
children: [
{
path: "routes",
element: <GatewayRoutesPage />,
handle: { isGatewayRoutesPage: true },
},
{
path: "routes/*",
element: <RoutesPageDetail />,
handle: { isGatewayRoutesDetailPage: true },
},
{
path: "consumers",
element: <GatewayConsumersPage />,
handle: { isGatewayConsumerPage: true },
},
],
},
},
services: {
name: "components.mainMenu.services",
icon: Layers,
createHref: (params) =>
`/n/${params.namespace}/services${
params.service ? `/${params.service}` : ""
}`,
useParams: () => {
const { namespace, service } = useParams();
const [, , thirdLvl] = useMatches(); // first level is namespace level
const isServiceListPage = checkHandler(thirdLvl, "isServiceListPage");
const isServiceDetailPage = checkHandler(
thirdLvl,
"isServiceDetailPage"
);
const isServicePage = isServiceListPage || isServiceDetailPage;
return {
namespace: isServicePage ? namespace : undefined,
service: isServiceDetailPage ? service : undefined,
isServicePage,
isServiceListPage,
isServiceDetailPage,
};
},
route: {
path: "services",
element: <ServicesPage />,
handle: { services: true },
children: [
{
path: "",
element: <ServicesListPage />,
handle: { isServiceListPage: true },
},
{
path: ":service",
element: <ServiceDetailPage />,
handle: { isServiceDetailPage: true },
},
],
},
},
mirror: {
name: "components.mainMenu.mirror",
icon: GitCompare,
createHref: (params) =>
`/n/${params.namespace}/mirror/${
params?.sync ? `logs/${params.sync}` : ""
}`,
useParams: () => {
const { sync } = useParams();
const [, secondLevel, thirdLevel] = useMatches(); // first level is namespace level
const isMirrorPage = checkHandler(secondLevel, "isMirrorPage");
const isSyncDetailPage = checkHandler(thirdLevel, "isMirrorLogsPage");
return {
isMirrorPage,
isSyncDetailPage,
sync: isSyncDetailPage ? sync : undefined,
};
},
route: {
path: "mirror",
element: <MirrorPage />,
handle: { mirror: true, isMirrorPage: true },
children: [
{
path: "",
element: <MirrorDetail />,
handle: { isMirrorDetailPage: true },
},
{
path: "logs/:sync",
element: <Logs />,
handle: { isMirrorLogsPage: true },
},
],
},
},
...enterprisePages,
settings: {
name: "components.mainMenu.settings",
icon: Settings,
createHref: (params) => `/n/${params.namespace}/settings`,
useParams: () => {
const [, secondLevel] = useMatches(); // first level is namespace level
const isSettingsPage = checkHandler(secondLevel, "isSettingsPage");
return { isSettingsPage };
},
route: {
path: "settings",
element: <SettingsPage />,
handle: { settings: true, isSettingsPage: true },
},
},
jqPlayground: {
name: "components.mainMenu.jqPlayground",
icon: PlaySquare,
createHref: (params) => `/n/${params.namespace}/jq`,
useParams: () => {
const [, secondLevel] = useMatches(); // first level is namespace level
const isJqPlaygroundPage = checkHandler(
secondLevel,
"isJqPlaygroundPage"
);
return { isJqPlaygroundPage };
},
route: {
path: "jq",
element: <JqPlaygroundPage />,
handle: { jqPlayground: true, isJqPlaygroundPage: true },
},
},
};
};