Split realm context into two (#1523)

This commit is contained in:
Jon Koops 2021-11-11 17:04:04 +01:00 committed by GitHub
parent 2b45a83109
commit d7362a97a0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 96 additions and 68 deletions

View file

@ -15,6 +15,7 @@ import { routes, RouteDef } from "./route-config";
import { PageBreadCrumbs } from "./components/bread-crumb/PageBreadCrumbs"; import { PageBreadCrumbs } from "./components/bread-crumb/PageBreadCrumbs";
import { ForbiddenSection } from "./ForbiddenSection"; import { ForbiddenSection } from "./ForbiddenSection";
import { SubGroups } from "./groups/SubGroupsContext"; import { SubGroups } from "./groups/SubGroupsContext";
import { RealmsProvider } from "./context/RealmsContext";
import { RealmContextProvider } from "./context/realm-context/RealmContext"; import { RealmContextProvider } from "./context/realm-context/RealmContext";
import { ErrorRenderer } from "./components/error/ErrorRenderer"; import { ErrorRenderer } from "./components/error/ErrorRenderer";
import { AdminClient } from "./context/auth/AdminClient"; import { AdminClient } from "./context/auth/AdminClient";
@ -34,17 +35,19 @@ const AppContexts: FunctionComponent<AdminClientProps> = ({
<Router> <Router>
<AdminClient.Provider value={adminClient}> <AdminClient.Provider value={adminClient}>
<WhoAmIContextProvider> <WhoAmIContextProvider>
<RealmContextProvider> <RealmsProvider>
<AccessContextProvider> <RealmContextProvider>
<Help> <AccessContextProvider>
<AlertProvider> <Help>
<ServerInfoProvider> <AlertProvider>
<SubGroups>{children}</SubGroups> <ServerInfoProvider>
</ServerInfoProvider> <SubGroups>{children}</SubGroups>
</AlertProvider> </ServerInfoProvider>
</Help> </AlertProvider>
</AccessContextProvider> </Help>
</RealmContextProvider> </AccessContextProvider>
</RealmContextProvider>
</RealmsProvider>
</WhoAmIContextProvider> </WhoAmIContextProvider>
</AdminClient.Provider> </AdminClient.Provider>
</Router> </Router>

View file

@ -47,13 +47,7 @@ export const MockAdminClient: FunctionComponent<{ mock?: object }> = (
} }
> >
<WhoAmIContextProvider> <WhoAmIContextProvider>
<RealmContext.Provider <RealmContext.Provider value={{ realm: "master" }}>
value={{
realm: "master",
realms: [],
refresh: () => Promise.resolve(),
}}
>
<AccessContextProvider>{props.children}</AccessContextProvider> <AccessContextProvider>{props.children}</AccessContextProvider>
</RealmContext.Provider> </RealmContext.Provider>
</WhoAmIContextProvider> </WhoAmIContextProvider>

View file

@ -22,13 +22,7 @@ describe("FormAccess", () => {
whoAmI: new WhoAmI(whoami as WhoAmIRepresentation), whoAmI: new WhoAmI(whoami as WhoAmIRepresentation),
}} }}
> >
<RealmContext.Provider <RealmContext.Provider value={{ realm }}>
value={{
realm,
realms: [],
refresh: () => Promise.resolve(),
}}
>
<AccessContextProvider> <AccessContextProvider>
<FormAccess role="manage-clients"> <FormAccess role="manage-clients">
<FormGroup label="test" fieldId="field"> <FormGroup label="test" fieldId="field">

View file

@ -16,6 +16,7 @@ import { useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom";
import { useRealm } from "../../context/realm-context/RealmContext"; import { useRealm } from "../../context/realm-context/RealmContext";
import { useRealms } from "../../context/RealmsContext";
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 { toAddRealm } from "../../realm/routes/AddRealm"; import { toAddRealm } from "../../realm/routes/AddRealm";
@ -25,7 +26,8 @@ import { RecentUsed } from "./recent-used";
import "./realm-selector.css"; import "./realm-selector.css";
export const RealmSelector = () => { export const RealmSelector = () => {
const { realm, realms } = useRealm(); const { realm } = useRealm();
const { realms } = useRealms();
const { whoAmI } = useWhoAmI(); const { whoAmI } = useWhoAmI();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");

View file

@ -0,0 +1,58 @@
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import { sortBy } from "lodash";
import React, {
createContext,
FunctionComponent,
useCallback,
useMemo,
useState,
} from "react";
import { RecentUsed } from "../components/realm-selector/recent-used";
import useRequiredContext from "../utils/useRequiredContext";
import { useAdminClient, useFetch } from "./auth/AdminClient";
type RealmsContextProps = {
/** A list of all the realms. */
realms: RealmRepresentation[];
/** Refreshes the realms with the latest information. */
refresh: () => Promise<void>;
};
export const RealmsContext = createContext<RealmsContextProps | undefined>(
undefined
);
export const RealmsProvider: FunctionComponent = ({ children }) => {
const adminClient = useAdminClient();
const [realms, setRealms] = useState<RealmRepresentation[]>([]);
const recentUsed = useMemo(() => new RecentUsed(), []);
function updateRealms(realms: RealmRepresentation[]) {
setRealms(sortBy(realms, "realm"));
recentUsed.clean(realms.map(({ realm }) => realm!));
}
useFetch(
() => adminClient.realms.find(),
(realms) => updateRealms(realms),
[]
);
const refresh = useCallback(async () => {
//this is needed otherwise the realm find function will not return
//new or renamed realms because of the cached realms in the token (perhaps?)
await adminClient.keycloak?.updateToken(Number.MAX_VALUE);
updateRealms(await adminClient.realms.find());
}, []);
const value = useMemo<RealmsContextProps>(
() => ({ realms, refresh }),
[realms, refresh]
);
return (
<RealmsContext.Provider value={value}>{children}</RealmsContext.Provider>
);
};
export const useRealms = () => useRequiredContext(RealmsContext);

View file

@ -1,6 +1,4 @@
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation"; import React, { FunctionComponent, useEffect, useMemo } from "react";
import _ from "lodash";
import React, { FunctionComponent, useEffect, useMemo, useState } from "react";
import { useRouteMatch } from "react-router-dom"; import { useRouteMatch } from "react-router-dom";
import { RecentUsed } from "../../components/realm-selector/recent-used"; import { RecentUsed } from "../../components/realm-selector/recent-used";
import { import {
@ -9,12 +7,10 @@ import {
} from "../../dashboard/routes/Dashboard"; } from "../../dashboard/routes/Dashboard";
import environment from "../../environment"; import environment from "../../environment";
import useRequiredContext from "../../utils/useRequiredContext"; import useRequiredContext from "../../utils/useRequiredContext";
import { useAdminClient, useFetch } from "../auth/AdminClient"; import { useAdminClient } from "../auth/AdminClient";
type RealmContextType = { type RealmContextType = {
realm: string; realm: string;
realms: RealmRepresentation[];
refresh: () => Promise<void>;
}; };
export const RealmContext = React.createContext<RealmContextType | undefined>( export const RealmContext = React.createContext<RealmContextType | undefined>(
@ -22,6 +18,8 @@ export const RealmContext = React.createContext<RealmContextType | undefined>(
); );
export const RealmContextProvider: FunctionComponent = ({ children }) => { export const RealmContextProvider: FunctionComponent = ({ children }) => {
const adminClient = useAdminClient();
const recentUsed = useMemo(() => new RecentUsed(), []);
const routeMatch = useRouteMatch<DashboardParams>(DashboardRoute.path); const routeMatch = useRouteMatch<DashboardParams>(DashboardRoute.path);
const realmParam = routeMatch?.params.realm; const realmParam = routeMatch?.params.realm;
const realm = useMemo( const realm = useMemo(
@ -29,43 +27,16 @@ export const RealmContextProvider: FunctionComponent = ({ children }) => {
[realmParam] [realmParam]
); );
const [realms, setRealms] = useState<RealmRepresentation[]>([]);
const adminClient = useAdminClient();
const recentUsed = new RecentUsed();
const updateRealmsList = (realms: RealmRepresentation[]) => {
setRealms(_.sortBy(realms, "realm"));
recentUsed.clean(realms.map((r) => r.realm!));
};
useFetch(
() => adminClient.realms.find(),
(realms) => updateRealmsList(realms),
[]
);
// Configure admin client to use selected realm when it changes. // Configure admin client to use selected realm when it changes.
useEffect(() => adminClient.setConfig({ realmName: realm }), [realm]); useEffect(() => adminClient.setConfig({ realmName: realm }), [realm]);
// Keep track of recently used realms when selected realm changes. // Keep track of recently used realms when selected realm changes.
useEffect(() => recentUsed.setRecentUsed(realm), [realm]); useEffect(() => recentUsed.setRecentUsed(realm), [realm]);
const value = useMemo(() => ({ realm }), [realm]);
return ( return (
<RealmContext.Provider <RealmContext.Provider value={value}>{children}</RealmContext.Provider>
value={{
realm,
realms,
refresh: async () => {
//this is needed otherwise the realm find function will not return
//new or renamed realms because of the cached realms in the token (perhaps?)
await adminClient.keycloak?.updateToken(Number.MAX_VALUE);
const list = await adminClient.realms.find();
updateRealmsList(list);
},
}}
>
{children}
</RealmContext.Provider>
); );
}; };

View file

@ -19,6 +19,7 @@ import type ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/de
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs"; import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
import { useRealm } from "../context/realm-context/RealmContext"; import { useRealm } from "../context/realm-context/RealmContext";
import { useRealms } from "../context/RealmsContext";
import { ViewHeader } from "../components/view-header/ViewHeader"; import { ViewHeader } from "../components/view-header/ViewHeader";
import { useAdminClient } from "../context/auth/AdminClient"; import { useAdminClient } from "../context/auth/AdminClient";
import { useServerInfo } from "../context/server-info/ServerInfoProvider"; import { useServerInfo } from "../context/server-info/ServerInfoProvider";
@ -48,6 +49,8 @@ import { toRealmSettings } from "./routes/RealmSettings";
import { LocalizationTab } from "./LocalizationTab"; import { LocalizationTab } from "./LocalizationTab";
import { HelpItem } from "../components/help-enabler/HelpItem"; import { HelpItem } from "../components/help-enabler/HelpItem";
import { DEFAULT_LOCALE } from "../i18n"; import { DEFAULT_LOCALE } from "../i18n";
import { toDashboard } from "../dashboard/routes/Dashboard";
import environment from "../environment";
type RealmSettingsHeaderProps = { type RealmSettingsHeaderProps = {
onChange: (value: boolean) => void; onChange: (value: boolean) => void;
@ -66,6 +69,7 @@ const RealmSettingsHeader = ({
}: RealmSettingsHeaderProps) => { }: RealmSettingsHeaderProps) => {
const { t } = useTranslation("realm-settings"); const { t } = useTranslation("realm-settings");
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const { refresh: refreshRealms } = useRealms();
const { addAlert, addError } = useAlerts(); const { addAlert, addError } = useAlerts();
const history = useHistory(); const history = useHistory();
const [partialImportOpen, setPartialImportOpen] = useState(false); const [partialImportOpen, setPartialImportOpen] = useState(false);
@ -90,7 +94,8 @@ const RealmSettingsHeader = ({
try { try {
await adminClient.realms.del({ realm: realmName }); await adminClient.realms.del({ realm: realmName });
addAlert(t("deletedSuccess"), AlertVariant.success); addAlert(t("deletedSuccess"), AlertVariant.success);
history.push("/master/"); await refreshRealms();
history.push(toDashboard({ realm: environment.masterRealm }));
refresh(); refresh();
} catch (error) { } catch (error) {
addError("realm-settings:deleteError", error); addError("realm-settings:deleteError", error);
@ -163,7 +168,8 @@ export const RealmSettingsTabs = ({
const { t } = useTranslation("realm-settings"); const { t } = useTranslation("realm-settings");
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const { addAlert, addError } = useAlerts(); const { addAlert, addError } = useAlerts();
const { realm: realmName, refresh: refreshRealm } = useRealm(); const { realm: realmName } = useRealm();
const { refresh: refreshRealms } = useRealms();
const history = useHistory(); const history = useHistory();
const kpComponentTypes = const kpComponentTypes =
@ -215,7 +221,7 @@ export const RealmSettingsTabs = ({
setupForm(realm); setupForm(realm);
const isRealmRenamed = realmName !== realm.realm; const isRealmRenamed = realmName !== realm.realm;
if (isRealmRenamed) { if (isRealmRenamed) {
await refreshRealm(); await refreshRealms();
history.push(toRealmSettings({ realm: realm.realm! })); history.push(toRealmSettings({ realm: realm.realm! }));
} }
addAlert(t("saveSuccess"), AlertVariant.success); addAlert(t("saveSuccess"), AlertVariant.success);

View file

@ -17,7 +17,7 @@ import { FormAccess } from "../../components/form-access/FormAccess";
import { JsonFileUpload } from "../../components/json-file-upload/JsonFileUpload"; import { JsonFileUpload } from "../../components/json-file-upload/JsonFileUpload";
import { ViewHeader } from "../../components/view-header/ViewHeader"; import { ViewHeader } from "../../components/view-header/ViewHeader";
import { useAdminClient } from "../../context/auth/AdminClient"; import { useAdminClient } from "../../context/auth/AdminClient";
import { useRealm } from "../../context/realm-context/RealmContext"; import { useRealms } from "../../context/RealmsContext";
import { useWhoAmI } from "../../context/whoami/WhoAmI"; import { useWhoAmI } from "../../context/whoami/WhoAmI";
import { toDashboard } from "../../dashboard/routes/Dashboard"; import { toDashboard } from "../../dashboard/routes/Dashboard";
@ -25,7 +25,7 @@ export default function NewRealmForm() {
const { t } = useTranslation("realm"); const { t } = useTranslation("realm");
const history = useHistory(); const history = useHistory();
const { refresh } = useWhoAmI(); const { refresh } = useWhoAmI();
const { refresh: realmRefresh } = useRealm(); const { refresh: refreshRealms } = useRealms();
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const { addAlert, addError } = useAlerts(); const { addAlert, addError } = useAlerts();
@ -45,7 +45,7 @@ export default function NewRealmForm() {
addAlert(t("saveRealmSuccess"), AlertVariant.success); addAlert(t("saveRealmSuccess"), AlertVariant.success);
refresh(); refresh();
await realmRefresh(); await refreshRealms();
history.push(toDashboard({ realm: realm.realm })); history.push(toDashboard({ realm: realm.realm }));
} catch (error) { } catch (error) {
addError("realm:saveRealmError", error); addError("realm:saveRealmError", error);