vorteil/direktiv

View on GitHub
ui/src/util/router/pages.tsx

Summary

Maintainability
F
1 wk
Test Coverage
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 },
      },
    },
  };
};