Use new React Router API for the Admin UI (#19361)
This commit is contained in:
parent
7f85453ac8
commit
f0057157da
4 changed files with 90 additions and 65 deletions
|
@ -3,25 +3,24 @@ import { Page } from "@patternfly/react-core";
|
|||
import type Keycloak from "keycloak-js";
|
||||
import { PropsWithChildren, Suspense } from "react";
|
||||
import { ErrorBoundary } from "react-error-boundary";
|
||||
import { HashRouter as Router, Route, Routes } from "react-router-dom";
|
||||
import { Outlet } from "react-router-dom";
|
||||
import { Help } from "ui-shared";
|
||||
|
||||
import { Header } from "./PageHeader";
|
||||
import { PageNav } from "./PageNav";
|
||||
import { AlertProvider } from "./components/alert/Alerts";
|
||||
import { PageBreadCrumbs } from "./components/bread-crumb/PageBreadCrumbs";
|
||||
import { ErrorRenderer } from "./components/error/ErrorRenderer";
|
||||
import { Help } from "ui-shared";
|
||||
import { KeycloakSpinner } from "./components/keycloak-spinner/KeycloakSpinner";
|
||||
import { AccessContextProvider, useAccess } from "./context/access/Access";
|
||||
import { AdminClientContext } from "./context/auth/AdminClient";
|
||||
import { RealmContextProvider } from "./context/realm-context/RealmContext";
|
||||
import { RealmsProvider } from "./context/RealmsContext";
|
||||
import { RecentRealmsProvider } from "./context/RecentRealms";
|
||||
import { AccessContextProvider } from "./context/access/Access";
|
||||
import { AdminClientContext } from "./context/auth/AdminClient";
|
||||
import { RealmContextProvider } from "./context/realm-context/RealmContext";
|
||||
import { ServerInfoProvider } from "./context/server-info/ServerInfoProvider";
|
||||
import { WhoAmIContextProvider } from "./context/whoami/WhoAmI";
|
||||
import { ForbiddenSection } from "./ForbiddenSection";
|
||||
import { SubGroups } from "./groups/SubGroupsContext";
|
||||
import { Header } from "./PageHeader";
|
||||
import { PageNav } from "./PageNav";
|
||||
import { AppRouteObject, routes } from "./routes";
|
||||
import { AuthWall } from "./root/AuthWall";
|
||||
|
||||
export const mainPageContentId = "kc-main-content-page-container";
|
||||
|
||||
|
@ -35,43 +34,25 @@ const AppContexts = ({
|
|||
keycloak,
|
||||
adminClient,
|
||||
}: PropsWithChildren<AdminClientProps>) => (
|
||||
<Router>
|
||||
<AdminClientContext.Provider value={{ keycloak, adminClient }}>
|
||||
<WhoAmIContextProvider>
|
||||
<RealmsProvider>
|
||||
<RealmContextProvider>
|
||||
<RecentRealmsProvider>
|
||||
<AccessContextProvider>
|
||||
<Help>
|
||||
<AlertProvider>
|
||||
<SubGroups>{children}</SubGroups>
|
||||
</AlertProvider>
|
||||
</Help>
|
||||
</AccessContextProvider>
|
||||
</RecentRealmsProvider>
|
||||
</RealmContextProvider>
|
||||
</RealmsProvider>
|
||||
</WhoAmIContextProvider>
|
||||
</AdminClientContext.Provider>
|
||||
</Router>
|
||||
<AdminClientContext.Provider value={{ keycloak, adminClient }}>
|
||||
<WhoAmIContextProvider>
|
||||
<RealmsProvider>
|
||||
<RealmContextProvider>
|
||||
<RecentRealmsProvider>
|
||||
<AccessContextProvider>
|
||||
<Help>
|
||||
<AlertProvider>
|
||||
<SubGroups>{children}</SubGroups>
|
||||
</AlertProvider>
|
||||
</Help>
|
||||
</AccessContextProvider>
|
||||
</RecentRealmsProvider>
|
||||
</RealmContextProvider>
|
||||
</RealmsProvider>
|
||||
</WhoAmIContextProvider>
|
||||
</AdminClientContext.Provider>
|
||||
);
|
||||
|
||||
// If someone tries to go directly to a route they don't
|
||||
// have access to, show forbidden page.
|
||||
type SecuredRouteProps = { route: AppRouteObject };
|
||||
const SecuredRoute = ({ route }: SecuredRouteProps) => {
|
||||
const { hasAccess } = useAccess();
|
||||
const accessAllowed =
|
||||
route.handle.access instanceof Array
|
||||
? hasAccess(...route.handle.access)
|
||||
: hasAccess(route.handle.access);
|
||||
|
||||
if (accessAllowed)
|
||||
return <Suspense fallback={<KeycloakSpinner />}>{route.element}</Suspense>;
|
||||
|
||||
return <ForbiddenSection permissionNeeded={route.handle.access} />;
|
||||
};
|
||||
|
||||
export const App = ({ keycloak, adminClient }: AdminClientProps) => {
|
||||
return (
|
||||
<AppContexts keycloak={keycloak} adminClient={adminClient}>
|
||||
|
@ -90,15 +71,11 @@ export const App = ({ keycloak, adminClient }: AdminClientProps) => {
|
|||
}
|
||||
>
|
||||
<ServerInfoProvider>
|
||||
<Routes>
|
||||
{routes.map((route, i) => (
|
||||
<Route
|
||||
key={i}
|
||||
path={route.path}
|
||||
element={<SecuredRoute route={route} />}
|
||||
/>
|
||||
))}
|
||||
</Routes>
|
||||
<Suspense fallback={<KeycloakSpinner />}>
|
||||
<AuthWall>
|
||||
<Outlet />
|
||||
</AuthWall>
|
||||
</Suspense>
|
||||
</ServerInfoProvider>
|
||||
</ErrorBoundary>
|
||||
</Page>
|
||||
|
|
|
@ -1,24 +1,20 @@
|
|||
import "@patternfly/patternfly/patternfly-addons.css";
|
||||
import "@patternfly/react-core/dist/styles/base.css";
|
||||
import "@patternfly/patternfly/patternfly-addons.css";
|
||||
|
||||
import { StrictMode } from "react";
|
||||
import { render } from "react-dom";
|
||||
import { createHashRouter, RouterProvider } from "react-router-dom";
|
||||
|
||||
import { App } from "./App";
|
||||
import { initAdminClient } from "./context/auth/AdminClient";
|
||||
import { initI18n } from "./i18n";
|
||||
import { RootRoute } from "./routes";
|
||||
|
||||
import "./index.css";
|
||||
|
||||
const { keycloak, adminClient } = await initAdminClient();
|
||||
|
||||
await initI18n(adminClient);
|
||||
|
||||
const router = createHashRouter([RootRoute]);
|
||||
const container = document.getElementById("app");
|
||||
|
||||
render(
|
||||
<StrictMode>
|
||||
<App keycloak={keycloak} adminClient={adminClient} />
|
||||
<RouterProvider router={router} />
|
||||
</StrictMode>,
|
||||
container
|
||||
);
|
||||
|
|
39
js/apps/admin-ui/src/root/AuthWall.tsx
Normal file
39
js/apps/admin-ui/src/root/AuthWall.tsx
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { AccessType } from "@keycloak/keycloak-admin-client/lib/defs/whoAmIRepresentation";
|
||||
import { useMatches } from "react-router";
|
||||
|
||||
import { ForbiddenSection } from "../ForbiddenSection";
|
||||
import { useAccess } from "../context/access/Access";
|
||||
|
||||
function hasProp<K extends PropertyKey>(
|
||||
data: object,
|
||||
prop: K
|
||||
): data is Record<K, unknown> {
|
||||
return prop in data;
|
||||
}
|
||||
|
||||
export const AuthWall = ({ children }: any) => {
|
||||
const matches = useMatches();
|
||||
const { hasAccess } = useAccess();
|
||||
|
||||
const permissionNeeded = matches.flatMap(({ handle }) => {
|
||||
if (
|
||||
typeof handle !== "object" ||
|
||||
handle === null ||
|
||||
!hasProp(handle, "access")
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (Array.isArray(handle.access)) {
|
||||
return handle.access as AccessType[];
|
||||
}
|
||||
|
||||
return [handle.access] as AccessType[];
|
||||
});
|
||||
|
||||
return hasAccess(...permissionNeeded) ? (
|
||||
children
|
||||
) : (
|
||||
<ForbiddenSection permissionNeeded={permissionNeeded} />
|
||||
);
|
||||
};
|
|
@ -1,8 +1,12 @@
|
|||
import type { AccessType } from "@keycloak/keycloak-admin-client/lib/defs/whoAmIRepresentation";
|
||||
import type { TFunction } from "i18next";
|
||||
import type { ComponentType } from "react";
|
||||
import type { NonIndexRouteObject } from "react-router";
|
||||
import type { NonIndexRouteObject, RouteObject } from "react-router";
|
||||
import { initAdminClient } from "./context/auth/AdminClient";
|
||||
import { initI18n } from "./i18n";
|
||||
|
||||
import { App } from "./App";
|
||||
import { PageNotFoundSection } from "./PageNotFoundSection";
|
||||
import authenticationRoutes from "./authentication/routes";
|
||||
import clientScopesRoutes from "./client-scopes/routes";
|
||||
import clientRoutes from "./clients/routes";
|
||||
|
@ -10,7 +14,6 @@ import dashboardRoutes from "./dashboard/routes";
|
|||
import eventRoutes from "./events/routes";
|
||||
import groupsRoutes from "./groups/routes";
|
||||
import identityProviders from "./identity-providers/routes";
|
||||
import { PageNotFoundSection } from "./PageNotFoundSection";
|
||||
import realmRoleRoutes from "./realm-roles/routes";
|
||||
import realmSettingRoutes from "./realm-settings/routes";
|
||||
import realmRoutes from "./realm/routes";
|
||||
|
@ -28,7 +31,7 @@ export interface AppRouteObject extends NonIndexRouteObject {
|
|||
handle: AppRouteObjectHandle;
|
||||
}
|
||||
|
||||
const NotFoundRoute: AppRouteObject = {
|
||||
export const NotFoundRoute: AppRouteObject = {
|
||||
path: "*",
|
||||
element: <PageNotFoundSection />,
|
||||
handle: {
|
||||
|
@ -52,3 +55,13 @@ export const routes: AppRouteObject[] = [
|
|||
...dashboardRoutes,
|
||||
NotFoundRoute,
|
||||
];
|
||||
|
||||
const { keycloak, adminClient } = await initAdminClient();
|
||||
|
||||
await initI18n(adminClient);
|
||||
|
||||
export const RootRoute: RouteObject = {
|
||||
path: "/",
|
||||
element: <App keycloak={keycloak} adminClient={adminClient} />,
|
||||
children: routes,
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue