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, {