Refactor initalization of Keycloak JS in Admin Console (#20100)
This commit is contained in:
parent
75ea22bad2
commit
228da0e29a
10 changed files with 72 additions and 79 deletions
|
@ -1,6 +1,4 @@
|
||||||
import type KeycloakAdminClient from "@keycloak/keycloak-admin-client";
|
|
||||||
import { Page } from "@patternfly/react-core";
|
import { Page } from "@patternfly/react-core";
|
||||||
import type Keycloak from "keycloak-js";
|
|
||||||
import { PropsWithChildren, Suspense } from "react";
|
import { PropsWithChildren, Suspense } from "react";
|
||||||
import { ErrorBoundary } from "react-error-boundary";
|
import { ErrorBoundary } from "react-error-boundary";
|
||||||
import { Outlet } from "react-router-dom";
|
import { Outlet } from "react-router-dom";
|
||||||
|
@ -15,7 +13,7 @@ import { KeycloakSpinner } from "./components/keycloak-spinner/KeycloakSpinner";
|
||||||
import { RealmsProvider } from "./context/RealmsContext";
|
import { RealmsProvider } from "./context/RealmsContext";
|
||||||
import { RecentRealmsProvider } from "./context/RecentRealms";
|
import { RecentRealmsProvider } from "./context/RecentRealms";
|
||||||
import { AccessContextProvider } from "./context/access/Access";
|
import { AccessContextProvider } from "./context/access/Access";
|
||||||
import { AdminClientContext } from "./context/auth/AdminClient";
|
import { AdminClientProvider } from "./context/auth/AdminClient";
|
||||||
import { RealmContextProvider } from "./context/realm-context/RealmContext";
|
import { RealmContextProvider } from "./context/realm-context/RealmContext";
|
||||||
import { ServerInfoProvider } from "./context/server-info/ServerInfoProvider";
|
import { ServerInfoProvider } from "./context/server-info/ServerInfoProvider";
|
||||||
import { WhoAmIContextProvider } from "./context/whoami/WhoAmI";
|
import { WhoAmIContextProvider } from "./context/whoami/WhoAmI";
|
||||||
|
@ -24,17 +22,8 @@ import { AuthWall } from "./root/AuthWall";
|
||||||
|
|
||||||
export const mainPageContentId = "kc-main-content-page-container";
|
export const mainPageContentId = "kc-main-content-page-container";
|
||||||
|
|
||||||
export type AdminClientProps = {
|
const AppContexts = ({ children }: PropsWithChildren) => (
|
||||||
keycloak: Keycloak;
|
<AdminClientProvider>
|
||||||
adminClient: KeycloakAdminClient;
|
|
||||||
};
|
|
||||||
|
|
||||||
const AppContexts = ({
|
|
||||||
children,
|
|
||||||
keycloak,
|
|
||||||
adminClient,
|
|
||||||
}: PropsWithChildren<AdminClientProps>) => (
|
|
||||||
<AdminClientContext.Provider value={{ keycloak, adminClient }}>
|
|
||||||
<WhoAmIContextProvider>
|
<WhoAmIContextProvider>
|
||||||
<RealmsProvider>
|
<RealmsProvider>
|
||||||
<RealmContextProvider>
|
<RealmContextProvider>
|
||||||
|
@ -50,12 +39,12 @@ const AppContexts = ({
|
||||||
</RealmContextProvider>
|
</RealmContextProvider>
|
||||||
</RealmsProvider>
|
</RealmsProvider>
|
||||||
</WhoAmIContextProvider>
|
</WhoAmIContextProvider>
|
||||||
</AdminClientContext.Provider>
|
</AdminClientProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const App = ({ keycloak, adminClient }: AdminClientProps) => {
|
export const App = () => {
|
||||||
return (
|
return (
|
||||||
<AppContexts keycloak={keycloak} adminClient={adminClient}>
|
<AppContexts>
|
||||||
<Page
|
<Page
|
||||||
header={<Header />}
|
header={<Header />}
|
||||||
isManagedSidebar
|
isManagedSidebar
|
||||||
|
|
|
@ -15,16 +15,16 @@ import { HelpIcon } from "@patternfly/react-icons";
|
||||||
import { ReactNode, useState } from "react";
|
import { ReactNode, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { HelpHeader } from "./components/help-enabler/HelpHeader";
|
|
||||||
import { useHelp } from "ui-shared";
|
import { useHelp } from "ui-shared";
|
||||||
import { useAdminClient } from "./context/auth/AdminClient";
|
|
||||||
|
import { HelpHeader } from "./components/help-enabler/HelpHeader";
|
||||||
import { useRealm } from "./context/realm-context/RealmContext";
|
import { useRealm } from "./context/realm-context/RealmContext";
|
||||||
import { useWhoAmI } from "./context/whoami/WhoAmI";
|
import { useWhoAmI } from "./context/whoami/WhoAmI";
|
||||||
import { toDashboard } from "./dashboard/routes/Dashboard";
|
import { toDashboard } from "./dashboard/routes/Dashboard";
|
||||||
import environment from "./environment";
|
import environment from "./environment";
|
||||||
|
import { keycloak } from "./keycloak";
|
||||||
|
|
||||||
const ManageAccountDropdownItem = () => {
|
const ManageAccountDropdownItem = () => {
|
||||||
const { keycloak } = useAdminClient();
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
|
@ -38,7 +38,6 @@ const ManageAccountDropdownItem = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const SignOutDropdownItem = () => {
|
const SignOutDropdownItem = () => {
|
||||||
const { keycloak } = useAdminClient();
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
|
@ -134,8 +133,7 @@ export const Header = () => {
|
||||||
const { realm } = useRealm();
|
const { realm } = useRealm();
|
||||||
|
|
||||||
const headerTools = () => {
|
const headerTools = () => {
|
||||||
const adminClient = useAdminClient();
|
const picture = keycloak.tokenParsed?.picture;
|
||||||
const picture = adminClient.keycloak.tokenParsed?.picture;
|
|
||||||
return (
|
return (
|
||||||
<PageHeaderTools>
|
<PageHeaderTools>
|
||||||
<PageHeaderToolsGroup
|
<PageHeaderToolsGroup
|
||||||
|
|
20
js/apps/admin-ui/src/admin-client.ts
Normal file
20
js/apps/admin-ui/src/admin-client.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import KeycloakAdminClient from "@keycloak/keycloak-admin-client";
|
||||||
|
|
||||||
|
import environment from "./environment";
|
||||||
|
import { keycloak } from "./keycloak";
|
||||||
|
|
||||||
|
export const adminClient = new KeycloakAdminClient();
|
||||||
|
|
||||||
|
adminClient.setConfig({ realmName: environment.loginRealm });
|
||||||
|
adminClient.baseUrl = environment.authUrl;
|
||||||
|
adminClient.registerTokenProvider({
|
||||||
|
async getAccessToken() {
|
||||||
|
try {
|
||||||
|
await keycloak.updateToken(5);
|
||||||
|
} catch (error) {
|
||||||
|
keycloak.login();
|
||||||
|
}
|
||||||
|
|
||||||
|
return keycloak.token;
|
||||||
|
},
|
||||||
|
});
|
|
@ -2,8 +2,9 @@ import { NetworkError } from "@keycloak/keycloak-admin-client";
|
||||||
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
||||||
import { sortBy } from "lodash-es";
|
import { sortBy } from "lodash-es";
|
||||||
import { PropsWithChildren, useCallback, useMemo, useState } from "react";
|
import { PropsWithChildren, useCallback, useMemo, useState } from "react";
|
||||||
|
|
||||||
import { createNamedContext, useRequiredContext } from "ui-shared";
|
import { createNamedContext, useRequiredContext } from "ui-shared";
|
||||||
|
|
||||||
|
import { keycloak } from "../keycloak";
|
||||||
import { useAdminClient, useFetch } from "./auth/AdminClient";
|
import { useAdminClient, useFetch } from "./auth/AdminClient";
|
||||||
|
|
||||||
type RealmsContextProps = {
|
type RealmsContextProps = {
|
||||||
|
@ -19,7 +20,7 @@ export const RealmsContext = createNamedContext<RealmsContextProps | undefined>(
|
||||||
);
|
);
|
||||||
|
|
||||||
export const RealmsProvider = ({ children }: PropsWithChildren) => {
|
export const RealmsProvider = ({ children }: PropsWithChildren) => {
|
||||||
const { keycloak, adminClient } = useAdminClient();
|
const { adminClient } = useAdminClient();
|
||||||
const [realms, setRealms] = useState<RealmRepresentation[]>([]);
|
const [realms, setRealms] = useState<RealmRepresentation[]>([]);
|
||||||
const [refreshCount, setRefreshCount] = useState(0);
|
const [refreshCount, setRefreshCount] = useState(0);
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,24 @@
|
||||||
import KeycloakAdminClient from "@keycloak/keycloak-admin-client";
|
import KeycloakAdminClient from "@keycloak/keycloak-admin-client";
|
||||||
import Keycloak from "keycloak-js";
|
import { DependencyList, PropsWithChildren, useEffect } from "react";
|
||||||
import { DependencyList, useEffect } from "react";
|
|
||||||
import { useErrorHandler } from "react-error-boundary";
|
import { useErrorHandler } from "react-error-boundary";
|
||||||
|
|
||||||
import environment from "../../environment";
|
|
||||||
import { createNamedContext, useRequiredContext } from "ui-shared";
|
import { createNamedContext, useRequiredContext } from "ui-shared";
|
||||||
|
|
||||||
|
import { adminClient } from "../../admin-client";
|
||||||
|
|
||||||
export type AdminClientProps = {
|
export type AdminClientProps = {
|
||||||
keycloak: Keycloak;
|
|
||||||
adminClient: KeycloakAdminClient;
|
adminClient: KeycloakAdminClient;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AdminClientContext = createNamedContext<
|
const AdminClientContext = createNamedContext<AdminClientProps | undefined>(
|
||||||
AdminClientProps | undefined
|
"AdminClientContext",
|
||||||
>("AdminClientContext", undefined);
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
export const AdminClientProvider = ({ children }: PropsWithChildren) => (
|
||||||
|
<AdminClientContext.Provider value={{ adminClient }}>
|
||||||
|
{children}
|
||||||
|
</AdminClientContext.Provider>
|
||||||
|
);
|
||||||
|
|
||||||
export const useAdminClient = () => useRequiredContext(AdminClientContext);
|
export const useAdminClient = () => useRequiredContext(AdminClientContext);
|
||||||
|
|
||||||
|
@ -58,33 +63,3 @@ export function useFetch<T>(
|
||||||
return () => controller.abort();
|
return () => controller.abort();
|
||||||
}, deps);
|
}, deps);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function initAdminClient() {
|
|
||||||
const keycloak = new Keycloak({
|
|
||||||
url: environment.authServerUrl,
|
|
||||||
realm: environment.loginRealm,
|
|
||||||
clientId: environment.isRunningAsTheme
|
|
||||||
? "security-admin-console"
|
|
||||||
: "security-admin-console-v2",
|
|
||||||
});
|
|
||||||
|
|
||||||
await keycloak.init({ onLoad: "check-sso", pkceMethod: "S256" });
|
|
||||||
|
|
||||||
const adminClient = new KeycloakAdminClient();
|
|
||||||
|
|
||||||
adminClient.setConfig({ realmName: environment.loginRealm });
|
|
||||||
adminClient.baseUrl = environment.authUrl;
|
|
||||||
adminClient.registerTokenProvider({
|
|
||||||
async getAccessToken() {
|
|
||||||
try {
|
|
||||||
await keycloak.updateToken(5);
|
|
||||||
} catch (error) {
|
|
||||||
keycloak.login();
|
|
||||||
}
|
|
||||||
|
|
||||||
return keycloak.token;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return { keycloak, adminClient };
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,22 +1,20 @@
|
||||||
import { init, use, InitOptions, TOptions } from "i18next";
|
import { InitOptions, TOptions, init, use } from "i18next";
|
||||||
import HttpBackend, { LoadPathOption } from "i18next-http-backend";
|
import HttpBackend, { LoadPathOption } from "i18next-http-backend";
|
||||||
import { initReactI18next } from "react-i18next";
|
import { initReactI18next } from "react-i18next";
|
||||||
import type KeycloakAdminClient from "@keycloak/keycloak-admin-client";
|
|
||||||
|
|
||||||
|
import { adminClient } from "./admin-client";
|
||||||
import environment from "./environment";
|
import environment from "./environment";
|
||||||
import { getAuthorizationHeaders } from "./utils/getAuthorizationHeaders";
|
|
||||||
import { addTrailingSlash } from "./util";
|
import { addTrailingSlash } from "./util";
|
||||||
|
import { getAuthorizationHeaders } from "./utils/getAuthorizationHeaders";
|
||||||
|
|
||||||
export const DEFAULT_LOCALE = "en";
|
export const DEFAULT_LOCALE = "en";
|
||||||
|
|
||||||
export async function initI18n(adminClient: KeycloakAdminClient) {
|
export async function initI18n() {
|
||||||
const options = await initOptions(adminClient);
|
const options = await initOptions();
|
||||||
await init(options);
|
await init(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
const initOptions = async (
|
const initOptions = async (): Promise<InitOptions> => {
|
||||||
adminClient: KeycloakAdminClient
|
|
||||||
): Promise<InitOptions> => {
|
|
||||||
const constructLoadPath: LoadPathOption = (_, namespaces) => {
|
const constructLoadPath: LoadPathOption = (_, namespaces) => {
|
||||||
if (namespaces[0] === "overrides") {
|
if (namespaces[0] === "overrides") {
|
||||||
return `${addTrailingSlash(adminClient.baseUrl)}admin/realms/${
|
return `${addTrailingSlash(adminClient.baseUrl)}admin/realms/${
|
||||||
|
|
11
js/apps/admin-ui/src/keycloak.ts
Normal file
11
js/apps/admin-ui/src/keycloak.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import Keycloak from "keycloak-js";
|
||||||
|
|
||||||
|
import environment from "./environment";
|
||||||
|
|
||||||
|
export const keycloak = new Keycloak({
|
||||||
|
url: environment.authServerUrl,
|
||||||
|
realm: environment.loginRealm,
|
||||||
|
clientId: environment.isRunningAsTheme
|
||||||
|
? "security-admin-console"
|
||||||
|
: "security-admin-console-v2",
|
||||||
|
});
|
|
@ -5,10 +5,16 @@ import { StrictMode } from "react";
|
||||||
import { render } from "react-dom";
|
import { render } from "react-dom";
|
||||||
import { createHashRouter, RouterProvider } from "react-router-dom";
|
import { createHashRouter, RouterProvider } from "react-router-dom";
|
||||||
|
|
||||||
|
import { initI18n } from "./i18n";
|
||||||
|
import { keycloak } from "./keycloak";
|
||||||
import { RootRoute } from "./routes";
|
import { RootRoute } from "./routes";
|
||||||
|
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
|
// Initialize required components before rendering app.
|
||||||
|
await keycloak.init({ onLoad: "check-sso", pkceMethod: "S256" });
|
||||||
|
await initI18n();
|
||||||
|
|
||||||
const router = createHashRouter([RootRoute]);
|
const router = createHashRouter([RootRoute]);
|
||||||
const container = document.getElementById("app");
|
const container = document.getElementById("app");
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,6 @@ import type { AccessType } from "@keycloak/keycloak-admin-client/lib/defs/whoAmI
|
||||||
import type { TFunction } from "i18next";
|
import type { TFunction } from "i18next";
|
||||||
import type { ComponentType } from "react";
|
import type { ComponentType } from "react";
|
||||||
import type { NonIndexRouteObject, RouteObject } 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 { App } from "./App";
|
||||||
import { PageNotFoundSection } from "./PageNotFoundSection";
|
import { PageNotFoundSection } from "./PageNotFoundSection";
|
||||||
|
@ -56,12 +54,8 @@ export const routes: AppRouteObject[] = [
|
||||||
NotFoundRoute,
|
NotFoundRoute,
|
||||||
];
|
];
|
||||||
|
|
||||||
const { keycloak, adminClient } = await initAdminClient();
|
|
||||||
|
|
||||||
await initI18n(adminClient);
|
|
||||||
|
|
||||||
export const RootRoute: RouteObject = {
|
export const RootRoute: RouteObject = {
|
||||||
path: "/",
|
path: "/",
|
||||||
element: <App keycloak={keycloak} adminClient={adminClient} />,
|
element: <App />,
|
||||||
children: routes,
|
children: routes,
|
||||||
};
|
};
|
||||||
|
|
|
@ -24,6 +24,7 @@ import {
|
||||||
import { useAdminClient } from "../context/auth/AdminClient";
|
import { useAdminClient } from "../context/auth/AdminClient";
|
||||||
import { useRealm } from "../context/realm-context/RealmContext";
|
import { useRealm } from "../context/realm-context/RealmContext";
|
||||||
import { useWhoAmI } from "../context/whoami/WhoAmI";
|
import { useWhoAmI } from "../context/whoami/WhoAmI";
|
||||||
|
import { keycloak } from "../keycloak";
|
||||||
import { toUser } from "../user/routes/User";
|
import { toUser } from "../user/routes/User";
|
||||||
import useFormatDate from "../utils/useFormatDate";
|
import useFormatDate from "../utils/useFormatDate";
|
||||||
|
|
||||||
|
@ -78,7 +79,7 @@ export default function SessionsTable({
|
||||||
const { realm } = useRealm();
|
const { realm } = useRealm();
|
||||||
const { whoAmI } = useWhoAmI();
|
const { whoAmI } = useWhoAmI();
|
||||||
const { t } = useTranslation("sessions");
|
const { t } = useTranslation("sessions");
|
||||||
const { keycloak, adminClient } = useAdminClient();
|
const { adminClient } = useAdminClient();
|
||||||
const { addError } = useAlerts();
|
const { addError } = useAlerts();
|
||||||
const formatDate = useFormatDate();
|
const formatDate = useFormatDate();
|
||||||
const [key, setKey] = useState(0);
|
const [key, setKey] = useState(0);
|
||||||
|
|
Loading…
Reference in a new issue