added pagination to realm selector (#30219)
* added pagination to realm selector fixes: #29978 Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fix display name for recent and refresh on open Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> --------- Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
This commit is contained in:
parent
d96967682b
commit
08ead04c43
9 changed files with 137 additions and 196 deletions
|
@ -2024,6 +2024,7 @@ eventTypes.REGISTER_ERROR.description=Register error
|
||||||
infoDisabledFeatures=Shows all disabled features.
|
infoDisabledFeatures=Shows all disabled features.
|
||||||
userSession.modelNote.label=User Session Note
|
userSession.modelNote.label=User Session Note
|
||||||
next=Next
|
next=Next
|
||||||
|
previous=Previous
|
||||||
userLabel=User label
|
userLabel=User label
|
||||||
pagination=Pagination
|
pagination=Pagination
|
||||||
changeAuthenticatorConfirm=If you change authenticator to {{clientAuthenticatorType}}, the Keycloak database will be updated and you may need to download a new adapter configuration for this client.
|
changeAuthenticatorConfirm=If you change authenticator to {{clientAuthenticatorType}}, the Keycloak database will be updated and you may need to download a new adapter configuration for this client.
|
||||||
|
|
|
@ -18,7 +18,6 @@ import {
|
||||||
ErrorBoundaryFallback,
|
ErrorBoundaryFallback,
|
||||||
ErrorBoundaryProvider,
|
ErrorBoundaryProvider,
|
||||||
} from "./context/ErrorBoundary";
|
} from "./context/ErrorBoundary";
|
||||||
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 { RealmContextProvider } from "./context/realm-context/RealmContext";
|
import { RealmContextProvider } from "./context/realm-context/RealmContext";
|
||||||
|
@ -33,15 +32,13 @@ const AppContexts = ({ children }: PropsWithChildren) => (
|
||||||
<ServerInfoProvider>
|
<ServerInfoProvider>
|
||||||
<RealmContextProvider>
|
<RealmContextProvider>
|
||||||
<WhoAmIContextProvider>
|
<WhoAmIContextProvider>
|
||||||
<RealmsProvider>
|
<RecentRealmsProvider>
|
||||||
<RecentRealmsProvider>
|
<AccessContextProvider>
|
||||||
<AccessContextProvider>
|
<AlertProvider>
|
||||||
<AlertProvider>
|
<SubGroups>{children}</SubGroups>
|
||||||
<SubGroups>{children}</SubGroups>
|
</AlertProvider>
|
||||||
</AlertProvider>
|
</AccessContextProvider>
|
||||||
</AccessContextProvider>
|
</RecentRealmsProvider>
|
||||||
</RecentRealmsProvider>
|
|
||||||
</RealmsProvider>
|
|
||||||
</WhoAmIContextProvider>
|
</WhoAmIContextProvider>
|
||||||
</RealmContextProvider>
|
</RealmContextProvider>
|
||||||
</ServerInfoProvider>
|
</ServerInfoProvider>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { NetworkError } from "@keycloak/keycloak-admin-client";
|
||||||
import { label } from "@keycloak/keycloak-ui-shared";
|
import { label } from "@keycloak/keycloak-ui-shared";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
@ -15,19 +16,28 @@ import {
|
||||||
Stack,
|
Stack,
|
||||||
StackItem,
|
StackItem,
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
import { CheckIcon } from "@patternfly/react-icons";
|
import {
|
||||||
import { Fragment, useMemo, useState } from "react";
|
AngleLeftIcon,
|
||||||
|
AngleRightIcon,
|
||||||
|
CheckIcon,
|
||||||
|
} from "@patternfly/react-icons";
|
||||||
|
import { debounce } from "lodash-es";
|
||||||
|
import { Fragment, useCallback, useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import { useRealms } from "../../context/RealmsContext";
|
import { useAdminClient } from "../../admin-client";
|
||||||
import { useRecentRealms } from "../../context/RecentRealms";
|
import { useRecentRealms } from "../../context/RecentRealms";
|
||||||
|
import { fetchAdminUI } from "../../context/auth/admin-ui-endpoint";
|
||||||
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 { toAddRealm } from "../../realm/routes/AddRealm";
|
import { toAddRealm } from "../../realm/routes/AddRealm";
|
||||||
|
import { useFetch } from "../../utils/useFetch";
|
||||||
|
|
||||||
import "./realm-selector.css";
|
import "./realm-selector.css";
|
||||||
|
|
||||||
|
const MAX_RESULTS = 10;
|
||||||
|
|
||||||
type AddRealmProps = {
|
type AddRealmProps = {
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
};
|
};
|
||||||
|
@ -80,49 +90,67 @@ const RealmText = ({ name, displayName, showIsRecent }: RealmTextProps) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type RealmNameRepresentation = {
|
||||||
|
name: string;
|
||||||
|
displayName?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export const RealmSelector = () => {
|
export const RealmSelector = () => {
|
||||||
const { realm } = useRealm();
|
const { realm } = useRealm();
|
||||||
const { realms } = useRealms();
|
const { adminClient } = useAdminClient();
|
||||||
const { whoAmI } = useWhoAmI();
|
const { whoAmI } = useWhoAmI();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [search, setSearch] = useState("");
|
const [realms, setRealms] = useState<RealmNameRepresentation[]>([]);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const recentRealms = useRecentRealms();
|
const recentRealms = useRecentRealms();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const all = useMemo(
|
const [search, setSearch] = useState("");
|
||||||
() =>
|
const [first, setFirst] = useState(0);
|
||||||
realms
|
|
||||||
.map((realm) => {
|
const debounceFn = useCallback(
|
||||||
const used = recentRealms.some((name) => name === realm.name);
|
debounce((value: string) => {
|
||||||
return { realm, used };
|
setFirst(0);
|
||||||
})
|
setSearch(value);
|
||||||
.sort((r1, r2) => {
|
}, 1000),
|
||||||
if (r1.used == r2.used) return 0;
|
[],
|
||||||
if (r1.used) return -1;
|
|
||||||
if (r2.used) return 1;
|
|
||||||
return 0;
|
|
||||||
}),
|
|
||||||
[recentRealms, realm, realms],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const filteredItems = useMemo(() => {
|
useFetch(
|
||||||
const normalizedSearch = search.trim().toLowerCase();
|
async () => {
|
||||||
|
try {
|
||||||
if (normalizedSearch.length === 0) {
|
return await fetchAdminUI<RealmNameRepresentation[]>(
|
||||||
return all;
|
adminClient,
|
||||||
}
|
"ui-ext/realms/names",
|
||||||
|
{ first: `${first}`, max: `${MAX_RESULTS + 1}`, search },
|
||||||
return search.trim() === ""
|
|
||||||
? all
|
|
||||||
: all.filter(
|
|
||||||
(r) =>
|
|
||||||
r.realm.name.toLowerCase().includes(normalizedSearch) ||
|
|
||||||
label(t, r.realm.displayName)
|
|
||||||
?.toLowerCase()
|
|
||||||
.includes(normalizedSearch),
|
|
||||||
);
|
);
|
||||||
}, [search, all]);
|
} catch (error) {
|
||||||
|
if (error instanceof NetworkError && error.response.status < 500) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setRealms,
|
||||||
|
[open, first, search],
|
||||||
|
);
|
||||||
|
|
||||||
|
const sortedRealms = useMemo(
|
||||||
|
() => [
|
||||||
|
...(first === 0 && !search
|
||||||
|
? recentRealms.reduce((acc, name) => {
|
||||||
|
const realm = realms.find((r) => r.name === name);
|
||||||
|
if (realm) {
|
||||||
|
acc.push(realm);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, [] as RealmNameRepresentation[])
|
||||||
|
: []),
|
||||||
|
...realms.filter((r) => !recentRealms.includes(r.name)),
|
||||||
|
],
|
||||||
|
[recentRealms, realms, first, search],
|
||||||
|
);
|
||||||
|
|
||||||
const realmDisplayName = useMemo(
|
const realmDisplayName = useMemo(
|
||||||
() => realms.find((r) => r.name === realm)?.displayName,
|
() => realms.find((r) => r.name === realm)?.displayName,
|
||||||
|
@ -148,13 +176,13 @@ export const RealmSelector = () => {
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<DropdownList>
|
<DropdownList>
|
||||||
{realms.length > 5 && (
|
{(realms.length > 5 || search || first !== 0) && (
|
||||||
<>
|
<>
|
||||||
<DropdownGroup>
|
<DropdownGroup>
|
||||||
<DropdownList>
|
<DropdownList>
|
||||||
<SearchInput
|
<SearchInput
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(_, value) => setSearch(value)}
|
onChange={(_, value) => debounceFn(value)}
|
||||||
onClear={() => setSearch("")}
|
onClear={() => setSearch("")}
|
||||||
/>
|
/>
|
||||||
</DropdownList>
|
</DropdownList>
|
||||||
|
@ -163,25 +191,51 @@ export const RealmSelector = () => {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{(realms.length !== 0
|
{(realms.length !== 0
|
||||||
? filteredItems.map((i) => (
|
? [
|
||||||
<DropdownItem
|
first !== 0 ? (
|
||||||
key={i.realm.name}
|
<DropdownItem onClick={() => setFirst(first - MAX_RESULTS)}>
|
||||||
onClick={() => {
|
<AngleLeftIcon /> {t("previous")}
|
||||||
navigate(toDashboard({ realm: i.realm.name }));
|
</DropdownItem>
|
||||||
setOpen(false);
|
) : (
|
||||||
}}
|
[]
|
||||||
>
|
),
|
||||||
<RealmText
|
...sortedRealms.map((realm) => (
|
||||||
{...i.realm}
|
<DropdownItem
|
||||||
showIsRecent={realms.length > 5 && i.used}
|
key={realm.name}
|
||||||
/>
|
onClick={() => {
|
||||||
</DropdownItem>
|
navigate(toDashboard({ realm: realm.name }));
|
||||||
))
|
setOpen(false);
|
||||||
: [
|
setSearch("");
|
||||||
<DropdownItem key="loader">
|
}}
|
||||||
<Spinner size="sm" /> {t("loadingRealms")}
|
>
|
||||||
</DropdownItem>,
|
<RealmText
|
||||||
|
{...realm}
|
||||||
|
showIsRecent={
|
||||||
|
realms.length > 5 && recentRealms.includes(realm.name)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</DropdownItem>
|
||||||
|
)),
|
||||||
|
realms.length > MAX_RESULTS ? (
|
||||||
|
<DropdownItem onClick={() => setFirst(first + MAX_RESULTS)}>
|
||||||
|
<AngleRightIcon />
|
||||||
|
{t("next")}
|
||||||
|
</DropdownItem>
|
||||||
|
) : (
|
||||||
|
[]
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
: !search
|
||||||
|
? [
|
||||||
|
<DropdownItem key="loader">
|
||||||
|
<Spinner size="sm" /> {t("loadingRealms")}
|
||||||
|
</DropdownItem>,
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
<DropdownItem key="no-results">
|
||||||
|
{t("noResultsFound")}
|
||||||
|
</DropdownItem>,
|
||||||
|
]
|
||||||
).concat([
|
).concat([
|
||||||
<Fragment key="add-realm">
|
<Fragment key="add-realm">
|
||||||
{whoAmI.canCreateRealm() && (
|
{whoAmI.canCreateRealm() && (
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
import { NetworkError } from "@keycloak/keycloak-admin-client";
|
|
||||||
import {
|
|
||||||
createNamedContext,
|
|
||||||
label,
|
|
||||||
useEnvironment,
|
|
||||||
useRequiredContext,
|
|
||||||
} from "@keycloak/keycloak-ui-shared";
|
|
||||||
import { PropsWithChildren, useCallback, useMemo, useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useAdminClient } from "../admin-client";
|
|
||||||
import { useFetch } from "../utils/useFetch";
|
|
||||||
import useLocaleSort from "../utils/useLocaleSort";
|
|
||||||
import { fetchAdminUI } from "./auth/admin-ui-endpoint";
|
|
||||||
|
|
||||||
type RealmsContextProps = {
|
|
||||||
/** A list of all the realms. */
|
|
||||||
realms: RealmNameRepresentation[];
|
|
||||||
/** Refreshes the realms with the latest information. */
|
|
||||||
refresh: () => Promise<void>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface RealmNameRepresentation {
|
|
||||||
name: string;
|
|
||||||
displayName?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const RealmsContext = createNamedContext<RealmsContextProps | undefined>(
|
|
||||||
"RealmsContext",
|
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
|
|
||||||
export const RealmsProvider = ({ children }: PropsWithChildren) => {
|
|
||||||
const { keycloak } = useEnvironment();
|
|
||||||
const { adminClient } = useAdminClient();
|
|
||||||
|
|
||||||
const [realms, setRealms] = useState<RealmNameRepresentation[]>([]);
|
|
||||||
const [refreshCount, setRefreshCount] = useState(0);
|
|
||||||
const localeSort = useLocaleSort();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
function updateRealms(realms: RealmNameRepresentation[]) {
|
|
||||||
setRealms(localeSort(realms, (r) => label(t, r.displayName, r.name)));
|
|
||||||
}
|
|
||||||
|
|
||||||
useFetch(
|
|
||||||
async () => {
|
|
||||||
try {
|
|
||||||
return await fetchAdminUI<RealmNameRepresentation[]>(
|
|
||||||
adminClient,
|
|
||||||
"ui-ext/realms/names",
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof NetworkError && error.response.status < 500) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(realms) => updateRealms(realms),
|
|
||||||
[refreshCount],
|
|
||||||
);
|
|
||||||
|
|
||||||
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 keycloak.updateToken(Number.MAX_VALUE);
|
|
||||||
setRefreshCount((count) => count + 1);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const value = useMemo<RealmsContextProps>(
|
|
||||||
() => ({ realms, refresh }),
|
|
||||||
[realms, refresh],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<RealmsContext.Provider value={value}>{children}</RealmsContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useRealms = () => useRequiredContext(RealmsContext);
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { PropsWithChildren, useEffect, useMemo } from "react";
|
import { PropsWithChildren, useEffect } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
createNamedContext,
|
createNamedContext,
|
||||||
|
@ -6,7 +6,6 @@ import {
|
||||||
useStoredState,
|
useStoredState,
|
||||||
} from "@keycloak/keycloak-ui-shared";
|
} from "@keycloak/keycloak-ui-shared";
|
||||||
import { useRealm } from "./realm-context/RealmContext";
|
import { useRealm } from "./realm-context/RealmContext";
|
||||||
import { RealmNameRepresentation, useRealms } from "./RealmsContext";
|
|
||||||
|
|
||||||
const MAX_REALMS = 4;
|
const MAX_REALMS = 4;
|
||||||
|
|
||||||
|
@ -16,7 +15,6 @@ export const RecentRealmsContext = createNamedContext<string[] | undefined>(
|
||||||
);
|
);
|
||||||
|
|
||||||
export const RecentRealmsProvider = ({ children }: PropsWithChildren) => {
|
export const RecentRealmsProvider = ({ children }: PropsWithChildren) => {
|
||||||
const { realms } = useRealms();
|
|
||||||
const { realm } = useRealm();
|
const { realm } = useRealm();
|
||||||
const [storedRealms, setStoredRealms] = useStoredState(
|
const [storedRealms, setStoredRealms] = useStoredState(
|
||||||
localStorage,
|
localStorage,
|
||||||
|
@ -24,36 +22,16 @@ export const RecentRealmsProvider = ({ children }: PropsWithChildren) => {
|
||||||
[realm],
|
[realm],
|
||||||
);
|
);
|
||||||
|
|
||||||
const recentRealms = useMemo(
|
|
||||||
() => filterRealmNames(realms, storedRealms),
|
|
||||||
[realms, storedRealms],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const newRealms = [...new Set([realm, ...recentRealms])];
|
const newRealms = [...new Set([realm, ...storedRealms])];
|
||||||
setStoredRealms(newRealms.slice(0, MAX_REALMS));
|
setStoredRealms(newRealms.slice(0, MAX_REALMS));
|
||||||
}, [realm]);
|
}, [realm]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RecentRealmsContext.Provider value={recentRealms}>
|
<RecentRealmsContext.Provider value={storedRealms}>
|
||||||
{children}
|
{children}
|
||||||
</RecentRealmsContext.Provider>
|
</RecentRealmsContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useRecentRealms = () => useRequiredContext(RecentRealmsContext);
|
export const useRecentRealms = () => useRequiredContext(RecentRealmsContext);
|
||||||
|
|
||||||
function filterRealmNames(
|
|
||||||
realms: RealmNameRepresentation[],
|
|
||||||
storedRealms: string[],
|
|
||||||
) {
|
|
||||||
// If no realms have been set yet we can't filter out any non-existent realm names.
|
|
||||||
if (realms.length === 0) {
|
|
||||||
return storedRealms;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only keep realm names that actually still exist.
|
|
||||||
return storedRealms.filter((realm) => {
|
|
||||||
return realms.some((r) => r.name === realm);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
|
@ -26,7 +26,6 @@ import {
|
||||||
useRoutableTab,
|
useRoutableTab,
|
||||||
} from "../components/routable-tabs/RoutableTabs";
|
} from "../components/routable-tabs/RoutableTabs";
|
||||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||||
import { useRealms } from "../context/RealmsContext";
|
|
||||||
import { useAccess } from "../context/access/Access";
|
import { useAccess } from "../context/access/Access";
|
||||||
import { useRealm } from "../context/realm-context/RealmContext";
|
import { useRealm } from "../context/realm-context/RealmContext";
|
||||||
import { toDashboard } from "../dashboard/routes/Dashboard";
|
import { toDashboard } from "../dashboard/routes/Dashboard";
|
||||||
|
@ -78,7 +77,6 @@ const RealmSettingsHeader = ({
|
||||||
const { adminClient } = useAdminClient();
|
const { adminClient } = useAdminClient();
|
||||||
const { environment } = useEnvironment<Environment>();
|
const { environment } = useEnvironment<Environment>();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { refresh: refreshRealms } = useRealms();
|
|
||||||
const { addAlert, addError } = useAlerts();
|
const { addAlert, addError } = useAlerts();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [partialImportOpen, setPartialImportOpen] = useState(false);
|
const [partialImportOpen, setPartialImportOpen] = useState(false);
|
||||||
|
@ -105,7 +103,6 @@ const RealmSettingsHeader = ({
|
||||||
try {
|
try {
|
||||||
await adminClient.realms.del({ realm: realmName });
|
await adminClient.realms.del({ realm: realmName });
|
||||||
addAlert(t("deletedSuccessRealmSetting"), AlertVariant.success);
|
addAlert(t("deletedSuccessRealmSetting"), AlertVariant.success);
|
||||||
await refreshRealms();
|
|
||||||
navigate(toDashboard({ realm: environment.masterRealm }));
|
navigate(toDashboard({ realm: environment.masterRealm }));
|
||||||
refresh();
|
refresh();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -179,7 +176,6 @@ export const RealmSettingsTabs = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { addAlert, addError } = useAlerts();
|
const { addAlert, addError } = useAlerts();
|
||||||
const { realm: realmName, realmRepresentation: realm, refresh } = useRealm();
|
const { realm: realmName, realmRepresentation: realm, refresh } = useRealm();
|
||||||
const { refresh: refreshRealms } = useRealms();
|
|
||||||
const combinedLocales = useLocale();
|
const combinedLocales = useLocale();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const isFeatureEnabled = useIsFeatureEnabled();
|
const isFeatureEnabled = useIsFeatureEnabled();
|
||||||
|
@ -271,7 +267,6 @@ export const RealmSettingsTabs = () => {
|
||||||
|
|
||||||
const isRealmRenamed = realmName !== (r.realm || realm?.realm);
|
const isRealmRenamed = realmName !== (r.realm || realm?.realm);
|
||||||
if (isRealmRenamed) {
|
if (isRealmRenamed) {
|
||||||
await refreshRealms();
|
|
||||||
navigate(toRealmSettings({ realm: r.realm!, tab: "general" }));
|
navigate(toRealmSettings({ realm: r.realm!, tab: "general" }));
|
||||||
}
|
}
|
||||||
refresh();
|
refresh();
|
||||||
|
|
|
@ -11,7 +11,6 @@ import { useAlerts } from "../../components/alert/Alerts";
|
||||||
import { FormAccess } from "../../components/form/FormAccess";
|
import { FormAccess } from "../../components/form/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 { 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 { convertFormValuesToObject, convertToFormValues } from "../../util";
|
import { convertFormValuesToObject, convertToFormValues } from "../../util";
|
||||||
|
@ -22,7 +21,6 @@ export default function NewRealmForm() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { refresh, whoAmI } = useWhoAmI();
|
const { refresh, whoAmI } = useWhoAmI();
|
||||||
const { refresh: refreshRealms } = useRealms();
|
|
||||||
const { addAlert, addError } = useAlerts();
|
const { addAlert, addError } = useAlerts();
|
||||||
const [realm, setRealm] = useState<RealmRepresentation>();
|
const [realm, setRealm] = useState<RealmRepresentation>();
|
||||||
|
|
||||||
|
@ -47,7 +45,6 @@ export default function NewRealmForm() {
|
||||||
addAlert(t("saveRealmSuccess"));
|
addAlert(t("saveRealmSuccess"));
|
||||||
|
|
||||||
refresh();
|
refresh();
|
||||||
await refreshRealms();
|
|
||||||
navigate(toDashboard({ realm: fields.realm }));
|
navigate(toDashboard({ realm: fields.realm }));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
addError("saveRealmError", error);
|
addError("saveRealmError", error);
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
package org.keycloak.admin.ui.rest;
|
package org.keycloak.admin.ui.rest;
|
||||||
|
|
||||||
import static org.keycloak.utils.StreamsUtil.throwIfEmpty;
|
|
||||||
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import jakarta.ws.rs.ForbiddenException;
|
import jakarta.ws.rs.DefaultValue;
|
||||||
import jakarta.ws.rs.GET;
|
import jakarta.ws.rs.GET;
|
||||||
import jakarta.ws.rs.Path;
|
import jakarta.ws.rs.Path;
|
||||||
import jakarta.ws.rs.Produces;
|
import jakarta.ws.rs.Produces;
|
||||||
|
import jakarta.ws.rs.QueryParam;
|
||||||
import jakarta.ws.rs.core.MediaType;
|
import jakarta.ws.rs.core.MediaType;
|
||||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||||
import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
|
import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
|
||||||
|
@ -51,19 +50,16 @@ public class UIRealmsResource {
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
)
|
)
|
||||||
public Stream<RealmNameRepresentation> getRealms() {
|
public Stream<RealmNameRepresentation> getRealms(@QueryParam("first") @DefaultValue("0") int first,
|
||||||
|
@QueryParam("max") @DefaultValue("10") int max,
|
||||||
|
@QueryParam("search") @DefaultValue("") String search) {
|
||||||
final RealmsPermissionEvaluator eval = AdminPermissions.realms(session, auth.adminAuth());
|
final RealmsPermissionEvaluator eval = AdminPermissions.realms(session, auth.adminAuth());
|
||||||
|
|
||||||
Stream<RealmNameRepresentation> realms = session.realms().getRealmsStream()
|
return session.realms().getRealmsStream()
|
||||||
.filter(realm -> {
|
.filter(realm -> eval.canView(realm) || eval.isAdmin(realm))
|
||||||
return eval.canView(realm) || eval.isAdmin(realm);
|
.filter(realm -> search.isEmpty() || realm.getName().toLowerCase().contains(search.trim().toLowerCase()))
|
||||||
})
|
.skip(first)
|
||||||
.map((RealmModel realm) -> {
|
.limit(max)
|
||||||
RealmNameRepresentation realmNameRep = new RealmNameRepresentation();
|
.map((RealmModel realm) -> new RealmNameRepresentation(realm.getName(), realm.getDisplayName()));
|
||||||
realmNameRep.setDisplayName(realm.getDisplayName());
|
|
||||||
realmNameRep.setName(realm.getName());
|
|
||||||
return realmNameRep;
|
|
||||||
});
|
|
||||||
return throwIfEmpty(realms, new ForbiddenException());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,11 @@ public class RealmNameRepresentation {
|
||||||
private String name;
|
private String name;
|
||||||
private String displayName;
|
private String displayName;
|
||||||
|
|
||||||
|
public RealmNameRepresentation(String name, String displayName) {
|
||||||
|
this.name = name;
|
||||||
|
this.displayName = displayName;
|
||||||
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return this.name;
|
return this.name;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue