diff --git a/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties b/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties
index 3249e1aa2e..c27ba0a6a3 100644
--- a/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties
+++ b/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties
@@ -3286,3 +3286,13 @@ somethingWentWrong=Something went wrong
somethingWentWrongDescription=Sorry, an unexpected error has occurred.
tryAgain=Try again
errorSavingTranslations=Error saving translations\: '{{error}}'
+clearCachesTitle=Clear Caches
+realmCache=Realm Cache
+userCache=User Cache
+keysCache=Keys Cache
+clearButtonTitle=Clear
+clearRealmCacheHelp=This will clear entries for all realms.
+clearUserCacheHelp=This will clear entries for all realms.
+clearKeysCacheHelp=Clears all entries from the cache of external public keys. These are keys of external clients or identity providers. This will clear all entries for all realms.
+clearCacheSuccess=Cache cleared successfully
+clearCacheError=Could not clear cache\: {{error}}
diff --git a/js/apps/admin-ui/src/PageHeader.tsx b/js/apps/admin-ui/src/PageHeader.tsx
index 7efbd719c9..02bea87cf1 100644
--- a/js/apps/admin-ui/src/PageHeader.tsx
+++ b/js/apps/admin-ui/src/PageHeader.tsx
@@ -23,6 +23,9 @@ import { HelpHeader } from "./components/help-enabler/HelpHeader";
import { useRealm } from "./context/realm-context/RealmContext";
import { useWhoAmI } from "./context/whoami/WhoAmI";
import { toDashboard } from "./dashboard/routes/Dashboard";
+import useToggle from "./utils/useToggle";
+import { PageHeaderClearCachesModal } from "./PageHeaderClearCachesModal";
+import { useAccess } from "./context/access/Access";
const ManageAccountDropdownItem = () => {
const { keycloak } = useEnvironment();
@@ -67,6 +70,20 @@ const ServerInfoDropdownItem = () => {
);
};
+const ClearCachesDropdownItem = () => {
+ const { t } = useTranslation();
+ const [open, toggleModal] = useToggle();
+
+ return (
+ <>
+ toggleModal()}>
+ {t("clearCachesTitle")}
+
+ {open && toggleModal()} />}
+ >
+ );
+};
+
const HelpDropdownItem = () => {
const { t } = useTranslation();
const { enabled, toggleHelp } = useHelp();
@@ -81,23 +98,34 @@ const HelpDropdownItem = () => {
);
};
-const kebabDropdownItems = [
+const kebabDropdownItems = (isMasterRealm: boolean, isManager: boolean) => [
,
,
+ ...(isMasterRealm && isManager
+ ? []
+ : []),
,
,
,
];
-const userDropdownItems = [
+const userDropdownItems = (isMasterRealm: boolean, isManager: boolean) => [
,
,
+ ...(isMasterRealm && isManager
+ ? []
+ : []),
,
,
];
const KebabDropdown = () => {
const [isDropdownOpen, setDropdownOpen] = useState(false);
+ const { realm } = useRealm();
+ const { hasAccess } = useAccess();
+
+ const isMasterRealm = realm === "master";
+ const isManager = hasAccess("manage-realm");
return (
{
)}
isOpen={isDropdownOpen}
>
- {kebabDropdownItems}
+
+ {kebabDropdownItems(isMasterRealm, isManager)}
+
);
};
@@ -124,6 +154,11 @@ const KebabDropdown = () => {
const UserDropdown = () => {
const { whoAmI } = useWhoAmI();
const [isDropdownOpen, setDropdownOpen] = useState(false);
+ const { realm } = useRealm();
+ const { hasAccess } = useAccess();
+
+ const isMasterRealm = realm === "master";
+ const isManager = hasAccess("manage-realm");
return (
{
)}
>
- {userDropdownItems}
+ {userDropdownItems(isMasterRealm, isManager)}
);
};
diff --git a/js/apps/admin-ui/src/PageHeaderClearCachesModal.tsx b/js/apps/admin-ui/src/PageHeaderClearCachesModal.tsx
new file mode 100644
index 0000000000..fdfbd7ba00
--- /dev/null
+++ b/js/apps/admin-ui/src/PageHeaderClearCachesModal.tsx
@@ -0,0 +1,101 @@
+import {
+ AlertVariant,
+ Button,
+ Flex,
+ FlexItem,
+ List,
+ ListItem,
+ Modal,
+ ModalVariant,
+} from "@patternfly/react-core";
+import { useRealm } from "./context/realm-context/RealmContext";
+import { useAdminClient } from "./admin-client";
+import { useTranslation } from "react-i18next";
+import { HelpItem, useAlerts } from "@keycloak/keycloak-ui-shared";
+
+export type ClearCachesModalProps = {
+ onClose: () => void;
+};
+export const PageHeaderClearCachesModal = ({
+ onClose,
+}: ClearCachesModalProps) => {
+ const { realm: realmName } = useRealm();
+ const { t } = useTranslation();
+ const { adminClient } = useAdminClient();
+ const { addError, addAlert } = useAlerts();
+
+ const clearCache =
+ (clearCacheFn: typeof adminClient.cache.clearRealmCache) =>
+ async (realm: string) => {
+ try {
+ await clearCacheFn({ realm });
+ addAlert(t("clearCacheSuccess"), AlertVariant.success);
+ } catch (error) {
+ addError("clearCacheError", error);
+ }
+ };
+ const clearRealmCache = clearCache(adminClient.cache.clearRealmCache);
+ const clearUserCache = clearCache(adminClient.cache.clearUserCache);
+ const clearKeysCache = clearCache(adminClient.cache.clearKeysCache);
+
+ return (
+ e.stopPropagation()}
+ >
+
+
+
+
+ {t("realmCache")}{" "}
+
+
+
+
+
+
+
+
+
+
+ {t("userCache")}{" "}
+
+
+
+
+
+
+
+
+
+
+ {t("keysCache")}{" "}
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/js/apps/keycloak-server/README.md b/js/apps/keycloak-server/README.md
index 40e7412951..9a389034b6 100644
--- a/js/apps/keycloak-server/README.md
+++ b/js/apps/keycloak-server/README.md
@@ -35,7 +35,7 @@ Or if you just want to clear the data so you can start fresh without downloading
pnpm delete-data
```
-If you want to run with a local Quarkus distribution of Keycloak for development purposes, you can do so by running this command instead:
+If you want to run with a local Quarkus distribution of Keycloak for development purposes, you can do so by running this command instead:
```sh
pnpm start --local
diff --git a/js/libs/keycloak-admin-client/src/resources/cache.ts b/js/libs/keycloak-admin-client/src/resources/cache.ts
index ce6baf81d5..820bdf5464 100644
--- a/js/libs/keycloak-admin-client/src/resources/cache.ts
+++ b/js/libs/keycloak-admin-client/src/resources/cache.ts
@@ -6,6 +6,14 @@ export class Cache extends Resource<{ realm?: string }> {
method: "POST",
path: "/clear-user-cache",
});
+ public clearKeysCache = this.makeRequest<{}, void>({
+ method: "POST",
+ path: "/clear-keys-cache",
+ });
+ public clearRealmCache = this.makeRequest<{}, void>({
+ method: "POST",
+ path: "/clear-realm-cache",
+ });
constructor(client: KeycloakAdminClient) {
super(client, {