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 db6e275127cc..928e6fe5a71b 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 @@ -3302,4 +3302,6 @@ managedMembership=Managed membership filterByMembershipType=Filter by Membership Type organizationsMembersListError=Could not fetch organization members\: {{error}} MANAGED=Managed -UNMANAGED=Unmanaged \ No newline at end of file +UNMANAGED=Unmanaged +membershipEvents=Membership events +childGroupEvents=Child group events diff --git a/js/apps/admin-ui/src/client-scopes/EditClientScope.tsx b/js/apps/admin-ui/src/client-scopes/EditClientScope.tsx index 89f4fa64420a..6810c44e9268 100644 --- a/js/apps/admin-ui/src/client-scopes/EditClientScope.tsx +++ b/js/apps/admin-ui/src/client-scopes/EditClientScope.tsx @@ -42,13 +42,15 @@ import { } from "./routes/ClientScope"; import { toClientScopes } from "./routes/ClientScopes"; import { toMapper } from "./routes/Mapper"; +import { useAccess } from "../context/access/Access"; +import { AdminEvents } from "../events/AdminEvents"; export default function EditClientScope() { const { adminClient } = useAdminClient(); const { t } = useTranslation(); const navigate = useNavigate(); - const { realm } = useRealm(); + const { realm, realmRepresentation } = useRealm(); const { id } = useParams(); const { addAlert, addError } = useAlerts(); const { enabled } = useHelp(); @@ -56,6 +58,7 @@ export default function EditClientScope() { useState(); const [key, setKey] = useState(0); const refresh = () => setKey(key + 1); + const { hasAccess } = useAccess(); useFetch( async () => { @@ -108,6 +111,7 @@ export default function EditClientScope() { const settingsTab = useTab("settings"); const mappersTab = useTab("mappers"); const scopeTab = useTab("scope"); + const eventsTab = useTab("events"); const onSubmit = async (formData: ClientScopeDefaultOptionalType) => { const clientScope = convertFormValuesToObject({ @@ -289,6 +293,16 @@ export default function EditClientScope() { save={assignRoles} /> + {realmRepresentation?.adminEventsEnabled && + hasAccess("view-events") && ( + {t("adminEvents")}} + {...eventsTab} + > + + + )} diff --git a/js/apps/admin-ui/src/client-scopes/routes/ClientScope.tsx b/js/apps/admin-ui/src/client-scopes/routes/ClientScope.tsx index baf4dbf21045..58d2b684afd6 100644 --- a/js/apps/admin-ui/src/client-scopes/routes/ClientScope.tsx +++ b/js/apps/admin-ui/src/client-scopes/routes/ClientScope.tsx @@ -3,7 +3,7 @@ import type { Path } from "react-router-dom"; import { generateEncodedPath } from "../../utils/generateEncodedPath"; import type { AppRouteObject } from "../../routes"; -export type ClientScopeTab = "settings" | "mappers" | "scope"; +export type ClientScopeTab = "settings" | "mappers" | "scope" | "events"; export type ClientScopeParams = { realm: string; diff --git a/js/apps/admin-ui/src/clients/ClientDetails.tsx b/js/apps/admin-ui/src/clients/ClientDetails.tsx index d53039dfeff9..5724d2c55fad 100644 --- a/js/apps/admin-ui/src/clients/ClientDetails.tsx +++ b/js/apps/admin-ui/src/clients/ClientDetails.tsx @@ -8,6 +8,7 @@ import { Label, PageSection, Tab, + Tabs, TabTitleText, Tooltip, } from "@patternfly/react-core"; @@ -73,6 +74,7 @@ import { EvaluateScopes } from "./scopes/EvaluateScopes"; import { ServiceAccount } from "./service-account/ServiceAccount"; import { getProtocolName, isRealmClient } from "./utils"; import { UserEvents } from "../events/UserEvents"; +import { AdminEvents } from "../events/AdminEvents"; type ClientDetailHeaderProps = { onChange: (value: boolean) => void; @@ -243,7 +245,9 @@ export default function ClientDetails() { const sessionsTab = useRoutableTab(tab("sessions")); const permissionsTab = useRoutableTab(tab("permissions")); const advancedTab = useRoutableTab(tab("advanced")); - const userEventsTab = useRoutableTab(tab("user-events")); + const eventsTab = useRoutableTab(tab("events")); + + const [activeEventsTab, setActiveEventsTab] = useState("userEvents"); const clientScopesTabRoute = (tab: ClientScopesTab) => toClientScopesTab({ @@ -675,15 +679,37 @@ export default function ClientDetails() { > - {hasAccess("view-events") && realmRepresentation?.eventsEnabled && ( - {t("events")}} - {...userEventsTab} - > - - - )} + {hasAccess("view-events") && + (realmRepresentation?.adminEventsEnabled || + realmRepresentation?.eventsEnabled) && ( + {t("events")}} + {...eventsTab} + > + setActiveEventsTab(key as string)} + > + {realmRepresentation?.eventsEnabled && ( + {t("userEvents")}} + > + + + )} + {realmRepresentation?.adminEventsEnabled && ( + {t("adminEvents")}} + > + + + )} + + + )} diff --git a/js/apps/admin-ui/src/clients/routes/Client.tsx b/js/apps/admin-ui/src/clients/routes/Client.tsx index 871f95e934a8..693385768fa6 100644 --- a/js/apps/admin-ui/src/clients/routes/Client.tsx +++ b/js/apps/admin-ui/src/clients/routes/Client.tsx @@ -15,7 +15,7 @@ export type ClientTab = | "serviceAccount" | "permissions" | "sessions" - | "user-events"; + | "events"; export type ClientParams = { realm: string; diff --git a/js/apps/admin-ui/src/events/AdminEvents.tsx b/js/apps/admin-ui/src/events/AdminEvents.tsx index 59cc86017460..53d824287f4b 100644 --- a/js/apps/admin-ui/src/events/AdminEvents.tsx +++ b/js/apps/admin-ui/src/events/AdminEvents.tsx @@ -66,18 +66,6 @@ type AdminEventSearchForm = { authIpAddress: string; }; -const defaultValues: AdminEventSearchForm = { - resourceTypes: [], - operationTypes: [], - resourcePath: "", - dateFrom: "", - dateTo: "", - authClient: "", - authUser: "", - authRealm: "", - authIpAddress: "", -}; - const DisplayDialog = ({ titleKey, onClose, @@ -114,7 +102,12 @@ const DetailCell = (event: AdminEventRepresentation) => ( ); -export const AdminEvents = () => { + +type AdminEventsProps = { + resourcePath?: string; +}; + +export const AdminEvents = ({ resourcePath }: AdminEventsProps) => { const { adminClient } = useAdminClient(); const { t } = useTranslation(); @@ -131,7 +124,21 @@ export const AdminEvents = () => { useState(false); const [activeFilters, setActiveFilters] = useState< Partial - >({}); + >({ + ...(resourcePath && { resourcePath }), + }); + + const defaultValues: AdminEventSearchForm = { + resourceTypes: [], + operationTypes: [], + resourcePath: resourcePath ? resourcePath : "", + dateFrom: "", + dateTo: "", + authClient: "", + authUser: "", + authRealm: "", + authIpAddress: "", + }; const [authEvent, setAuthEvent] = useState(); const [representationEvent, setRepresentationEvent] = @@ -436,10 +443,10 @@ export const AdminEvents = () => { )} /> - + />)} @@ -514,7 +521,6 @@ export const AdminEvents = () => { className="pf-v5-u-mt-md pf-v5-u-mr-md" key={key} categoryName={filterLabels[key]} - isClosable onClick={() => removeFilter(key)} > {typeof value === "string" ? ( diff --git a/js/apps/admin-ui/src/groups/GroupsSection.tsx b/js/apps/admin-ui/src/groups/GroupsSection.tsx index 7867ecd088b1..d4a542ba67d2 100644 --- a/js/apps/admin-ui/src/groups/GroupsSection.tsx +++ b/js/apps/admin-ui/src/groups/GroupsSection.tsx @@ -40,6 +40,7 @@ import { getId, getLastId } from "./groupIdUtils"; import { toGroups } from "./routes/Groups"; import "./GroupsSection.css"; +import { AdminEvents } from "../events/AdminEvents"; export default function GroupsSection() { const { adminClient } = useAdminClient(); @@ -48,7 +49,7 @@ export default function GroupsSection() { const [activeTab, setActiveTab] = useState(0); const { subGroups, setSubGroups, currentGroup } = useSubGroups(); - const { realm } = useRealm(); + const { realm, realmRepresentation } = useRealm(); const [rename, setRename] = useState(); const [deleteOpen, toggleDeleteOpen] = useToggle(); @@ -77,6 +78,8 @@ export default function GroupsSection() { currentGroup()?.access?.viewMembers || currentGroup()?.access?.manageMembers; + const [activeEventsTab, setActiveEventsTab] = useState("adminEvents"); + useFetch( async () => { const ids = getId(location.pathname); @@ -234,6 +237,54 @@ export default function GroupsSection() { )} + {realmRepresentation?.adminEventsEnabled && + hasAccess("view-events") && ( + {t("adminEvents")}} + > + + setActiveEventsTab(key as string) + } + > + {t("adminEvents")} + } + > + + + + {t("membershipEvents")} + + } + > + + + + {t("childGroupEvents")} + + } + > + + + + + )} )} {subGroups.length === 0 && } diff --git a/js/apps/admin-ui/src/identity-providers/add/DetailSettings.tsx b/js/apps/admin-ui/src/identity-providers/add/DetailSettings.tsx index d34aca7da3a6..4f4b0bdc4d54 100644 --- a/js/apps/admin-ui/src/identity-providers/add/DetailSettings.tsx +++ b/js/apps/admin-ui/src/identity-providers/add/DetailSettings.tsx @@ -42,6 +42,7 @@ import { useRoutableTab, } from "../../components/routable-tabs/RoutableTabs"; import { ViewHeader } from "../../components/view-header/ViewHeader"; +import { useAccess } from "../../context/access/Access"; import { useRealm } from "../../context/realm-context/RealmContext"; import { useServerInfo } from "../../context/server-info/ServerInfoProvider"; import { toUpperCase } from "../../util"; @@ -64,6 +65,7 @@ import { OIDCAuthentication } from "./OIDCAuthentication"; import { OIDCGeneralSettings } from "./OIDCGeneralSettings"; import { ReqAuthnConstraints } from "./ReqAuthnConstraintsSettings"; import { SamlGeneralSettings } from "./SamlGeneralSettings"; +import { AdminEvents } from "../../events/AdminEvents"; type HeaderProps = { onChange: (value: boolean) => void; @@ -278,9 +280,10 @@ export default function DetailSettings() { const { addAlert, addError } = useAlerts(); const navigate = useNavigate(); - const { realm } = useRealm(); + const { realm, realmRepresentation } = useRealm(); const [key, setKey] = useState(0); const refresh = () => setKey(key + 1); + const { hasAccess } = useAccess(); useFetch( () => adminClient.identityProviders.findOne({ alias }), @@ -322,6 +325,7 @@ export default function DetailSettings() { const settingsTab = useTab("settings"); const mappersTab = useTab("mappers"); const permissionsTab = useTab("permissions"); + const eventsTab = useTab("events"); const save = async (savedProvider?: IdentityProviderRepresentation) => { const p = savedProvider || getValues(); @@ -616,6 +620,18 @@ export default function DetailSettings() { )} + {realmRepresentation?.adminEventsEnabled && + hasAccess("view-events") && ( + {t("adminEvents")}} + {...eventsTab} + > + + + )} diff --git a/js/apps/admin-ui/src/identity-providers/routes/IdentityProvider.tsx b/js/apps/admin-ui/src/identity-providers/routes/IdentityProvider.tsx index 31090bd15ead..8a49542085cf 100644 --- a/js/apps/admin-ui/src/identity-providers/routes/IdentityProvider.tsx +++ b/js/apps/admin-ui/src/identity-providers/routes/IdentityProvider.tsx @@ -3,7 +3,11 @@ import type { Path } from "react-router-dom"; import { generateEncodedPath } from "../../utils/generateEncodedPath"; import type { AppRouteObject } from "../../routes"; -export type IdentityProviderTab = "settings" | "mappers" | "permissions"; +export type IdentityProviderTab = + | "settings" + | "mappers" + | "permissions" + | "events"; export type IdentityProviderParams = { realm: string; diff --git a/js/apps/admin-ui/src/organizations/DetailOrganization.tsx b/js/apps/admin-ui/src/organizations/DetailOrganization.tsx index d16eab590d9b..4073ebad3321 100644 --- a/js/apps/admin-ui/src/organizations/DetailOrganization.tsx +++ b/js/apps/admin-ui/src/organizations/DetailOrganization.tsx @@ -8,6 +8,7 @@ import { Button, PageSection, Tab, + Tabs, TabTitleText, } from "@patternfly/react-core"; import { FormProvider, useForm } from "react-hook-form"; @@ -35,12 +36,15 @@ import { OrganizationTab, toEditOrganization, } from "./routes/EditOrganization"; +import { useAccess } from "../context/access/Access"; +import { AdminEvents } from "../events/AdminEvents"; +import { useState } from "react"; export default function DetailOrganization() { const { adminClient } = useAdminClient(); const { addAlert, addError } = useAlerts(); - const { realm } = useRealm(); + const { realm, realmRepresentation } = useRealm(); const { id } = useParams(); const { t } = useTranslation(); @@ -84,6 +88,10 @@ export default function DetailOrganization() { const attributesTab = useTab("attributes"); const membersTab = useTab("members"); const identityProvidersTab = useTab("identityProviders"); + const eventsTab = useTab("events"); + + const { hasAccess } = useAccess(); + const [activeEventsTab, setActiveEventsTab] = useState("adminEvents"); return ( @@ -161,6 +169,32 @@ export default function DetailOrganization() { > + {realmRepresentation?.adminEventsEnabled && + hasAccess("view-events") && ( + {t("adminEvents")}} + {...eventsTab} + > + setActiveEventsTab(key as string)} + > + {t("adminEvents")}} + > + + + {t("membershipEvents")}} + > + + + + + )} diff --git a/js/apps/admin-ui/src/organizations/routes/EditOrganization.tsx b/js/apps/admin-ui/src/organizations/routes/EditOrganization.tsx index 2387de5917e2..31f363f0d490 100644 --- a/js/apps/admin-ui/src/organizations/routes/EditOrganization.tsx +++ b/js/apps/admin-ui/src/organizations/routes/EditOrganization.tsx @@ -7,7 +7,8 @@ export type OrganizationTab = | "settings" | "attributes" | "members" - | "identityProviders"; + | "identityProviders" + | "events"; export type EditOrganizationParams = { realm: string; diff --git a/js/apps/admin-ui/src/realm-roles/RealmRoleTabs.tsx b/js/apps/admin-ui/src/realm-roles/RealmRoleTabs.tsx index 9f70a8db1e3c..f107e106376b 100644 --- a/js/apps/admin-ui/src/realm-roles/RealmRoleTabs.tsx +++ b/js/apps/admin-ui/src/realm-roles/RealmRoleTabs.tsx @@ -52,6 +52,7 @@ import { useParams } from "../utils/useParams"; import { UsersInRoleTab } from "./UsersInRoleTab"; import { RealmRoleRoute, RealmRoleTab, toRealmRole } from "./routes/RealmRole"; import { toRealmRoles } from "./routes/RealmRoles"; +import { AdminEvents } from "../events/AdminEvents"; export default function RealmRoleTabs() { const { adminClient } = useAdminClient(); @@ -197,6 +198,7 @@ export default function RealmRoleTabs() { const attributesTab = useTab("attributes"); const usersInRoleTab = useTab("users-in-role"); const permissionsTab = useTab("permissions"); + const eventsTab = useTab("events"); const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ titleKey: "roleDeleteConfirm", @@ -345,6 +347,15 @@ export default function RealmRoleTabs() { )} + {realm?.adminEventsEnabled && hasAccess("view-events") && ( + {t("adminEvents")}} + {...eventsTab} + > + + + )} diff --git a/js/apps/admin-ui/src/realm-roles/routes/RealmRole.tsx b/js/apps/admin-ui/src/realm-roles/routes/RealmRole.tsx index 19ec0a502a85..60f1d2dc5310 100644 --- a/js/apps/admin-ui/src/realm-roles/routes/RealmRole.tsx +++ b/js/apps/admin-ui/src/realm-roles/routes/RealmRole.tsx @@ -9,7 +9,8 @@ export type RealmRoleTab = | "associated-roles" | "attributes" | "users-in-role" - | "permissions"; + | "permissions" + | "events"; export type RealmRoleParams = { realm: string; diff --git a/js/apps/admin-ui/src/user/EditUser.tsx b/js/apps/admin-ui/src/user/EditUser.tsx index 857ad75188e4..56991592152b 100644 --- a/js/apps/admin-ui/src/user/EditUser.tsx +++ b/js/apps/admin-ui/src/user/EditUser.tsx @@ -15,6 +15,7 @@ import { Label, PageSection, Tab, + Tabs, TabTitleText, Tooltip, } from "@patternfly/react-core"; @@ -60,6 +61,7 @@ import { toUsers } from "./routes/Users"; import { isLightweightUser } from "./utils"; import "./user-section.css"; +import { AdminEvents } from "../events/AdminEvents"; export default function EditUser() { const { adminClient } = useAdminClient(); @@ -102,6 +104,8 @@ export default function EditUser() { tab, }); + const [activeEventsTab, setActiveEventsTab] = useState("userEvents"); + const settingsTab = useRoutableTab(toTab("settings")); const attributesTab = useRoutableTab(toTab("attributes")); const credentialsTab = useRoutableTab(toTab("credentials")); @@ -113,7 +117,7 @@ export default function EditUser() { toTab("identity-provider-links"), ); const sessionsTab = useRoutableTab(toTab("sessions")); - const userEventsTab = useRoutableTab(toTab("user-events")); + const eventsTab = useRoutableTab(toTab("events")); useFetch( async () => @@ -426,15 +430,38 @@ export default function EditUser() { > - {hasAccess("view-events") && realm?.eventsEnabled && ( - {t("events")}} - {...userEventsTab} - > - - - )} + {hasAccess("view-events") && + (realm?.adminEventsEnabled || realm?.eventsEnabled) && ( + {t("events")}} + {...eventsTab} + > + setActiveEventsTab(key as string)} + > + {realm.eventsEnabled && ( + {t("userEvents")}} + > + + + )} + {realm.adminEventsEnabled && ( + {t("adminEvents")} + } + > + + + )} + + + )} diff --git a/js/apps/admin-ui/src/user/routes/User.tsx b/js/apps/admin-ui/src/user/routes/User.tsx index 5bc9c7d9be5e..c5acc4bb66f3 100644 --- a/js/apps/admin-ui/src/user/routes/User.tsx +++ b/js/apps/admin-ui/src/user/routes/User.tsx @@ -13,7 +13,7 @@ export type UserTab = | "credentials" | "role-mapping" | "identity-provider-links" - | "user-events"; + | "events"; export type UserParams = { realm: string;