diff --git a/js/apps/admin-ui/cypress/e2e/clients_test.spec.ts b/js/apps/admin-ui/cypress/e2e/clients_test.spec.ts index d912ee9f2bce..2e93a538979a 100644 --- a/js/apps/admin-ui/cypress/e2e/clients_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/clients_test.spec.ts @@ -1221,8 +1221,7 @@ describe("Clients test", () => { .checkTabExists(ClientsDetailsTab.Sessions, true) .checkTabExists(ClientsDetailsTab.Permissions, true) .checkTabExists(ClientsDetailsTab.Advanced, true) - .checkTabExists(ClientsDetailsTab.UserEvents, true) - .checkNumberOfTabsIsEqual(6); + .checkNumberOfTabsIsEqual(5); }); it("Hides the delete action", () => { diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/client_details/ClientDetailsPage.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/client_details/ClientDetailsPage.ts index 06b3dbdd9a96..20b38dd73c10 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/client_details/ClientDetailsPage.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/client_details/ClientDetailsPage.ts @@ -19,7 +19,7 @@ export enum ClientsDetailsTab { ServiceAccountsRoles = "Service accounts roles", Advanced = "Advanced", Scope = "Scope", - UserEvents = "User events", + UserEvents = "Events", } export default class ClientDetailsPage extends CommonPage { 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 c27ba0a6a3b5..4d2084eceaa2 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 @@ -3296,3 +3296,4 @@ 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}} +expandRow=Expand row \ No newline at end of file diff --git a/js/apps/admin-ui/src/clients/ClientDetails.tsx b/js/apps/admin-ui/src/clients/ClientDetails.tsx index 9f3645f6d2ad..d53039dfeff9 100644 --- a/js/apps/admin-ui/src/clients/ClientDetails.tsx +++ b/js/apps/admin-ui/src/clients/ClientDetails.tsx @@ -191,7 +191,7 @@ export default function ClientDetails() { const { t } = useTranslation(); const { addAlert, addError } = useAlerts(); - const { realm } = useRealm(); + const { realm, realmRepresentation } = useRealm(); const { hasAccess } = useAccess(); const isFeatureEnabled = useIsFeatureEnabled(); @@ -226,55 +226,65 @@ export default function ClientDetails() { return sortBy(roles, (role) => role.name?.toUpperCase()); }; - const useTab = (tab: ClientTab) => - useRoutableTab( - toClient({ - realm, - clientId, - tab, - }), - ); - - const settingsTab = useTab("settings"); - const keysTab = useTab("keys"); - const credentialsTab = useTab("credentials"); - const rolesTab = useTab("roles"); - const clientScopesTab = useTab("clientScopes"); - const authorizationTab = useTab("authorization"); - const serviceAccountTab = useTab("serviceAccount"); - const sessionsTab = useTab("sessions"); - const permissionsTab = useTab("permissions"); - const advancedTab = useTab("advanced"); - const userEventsTab = useTab("user-events"); - - const useClientScopesTab = (tab: ClientScopesTab) => - useRoutableTab( - toClientScopesTab({ - realm, - clientId, - tab, - }), - ); - - const clientScopesSetupTab = useClientScopesTab("setup"); - const clientScopesEvaluateTab = useClientScopesTab("evaluate"); + const tab = (tab: ClientTab) => + toClient({ + realm, + clientId, + tab, + }); + + const settingsTab = useRoutableTab(tab("settings")); + const keysTab = useRoutableTab(tab("keys")); + const credentialsTab = useRoutableTab(tab("credentials")); + const rolesTab = useRoutableTab(tab("roles")); + const clientScopesTab = useRoutableTab(tab("clientScopes")); + const authorizationTab = useRoutableTab(tab("authorization")); + const serviceAccountTab = useRoutableTab(tab("serviceAccount")); + const sessionsTab = useRoutableTab(tab("sessions")); + const permissionsTab = useRoutableTab(tab("permissions")); + const advancedTab = useRoutableTab(tab("advanced")); + const userEventsTab = useRoutableTab(tab("user-events")); + + const clientScopesTabRoute = (tab: ClientScopesTab) => + toClientScopesTab({ + realm, + clientId, + tab, + }); + + const clientScopesSetupTab = useRoutableTab(clientScopesTabRoute("setup")); + const clientScopesEvaluateTab = useRoutableTab( + clientScopesTabRoute("evaluate"), + ); - const useAuthorizationTab = (tab: AuthorizationTab) => - useRoutableTab( - toAuthorizationTab({ - realm, - clientId, - tab, - }), - ); + const authorizationTabRoute = (tab: AuthorizationTab) => + toAuthorizationTab({ + realm, + clientId, + tab, + }); - const authorizationSettingsTab = useAuthorizationTab("settings"); - const authorizationResourcesTab = useAuthorizationTab("resources"); - const authorizationScopesTab = useAuthorizationTab("scopes"); - const authorizationPoliciesTab = useAuthorizationTab("policies"); - const authorizationPermissionsTab = useAuthorizationTab("permissions"); - const authorizationEvaluateTab = useAuthorizationTab("evaluate"); - const authorizationExportTab = useAuthorizationTab("export"); + const authorizationSettingsTab = useRoutableTab( + authorizationTabRoute("settings"), + ); + const authorizationResourcesTab = useRoutableTab( + authorizationTabRoute("resources"), + ); + const authorizationScopesTab = useRoutableTab( + authorizationTabRoute("scopes"), + ); + const authorizationPoliciesTab = useRoutableTab( + authorizationTabRoute("policies"), + ); + const authorizationPermissionsTab = useRoutableTab( + authorizationTabRoute("permissions"), + ); + const authorizationEvaluateTab = useRoutableTab( + authorizationTabRoute("evaluate"), + ); + const authorizationExportTab = useRoutableTab( + authorizationTabRoute("export"), + ); const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ titleKey: "clientDeleteConfirmTitle", @@ -665,10 +675,10 @@ export default function ClientDetails() { > - {hasAccess("view-events") && ( + {hasAccess("view-events") && realmRepresentation?.eventsEnabled && ( {t("userEvents")}} + title={{t("events")}} {...userEventsTab} > diff --git a/js/apps/admin-ui/src/events/EventsSection.tsx b/js/apps/admin-ui/src/events/EventsSection.tsx index c404c41147b5..8ec321ec5f9f 100644 --- a/js/apps/admin-ui/src/events/EventsSection.tsx +++ b/js/apps/admin-ui/src/events/EventsSection.tsx @@ -11,7 +11,7 @@ import helpUrls from "../help-urls"; import { toRealmSettings } from "../realm-settings/routes/RealmSettings"; import { AdminEvents } from "./AdminEvents"; import { UserEvents } from "./UserEvents"; -import { EventsTab, toEvents } from "./routes/Events"; +import { toEvents } from "./routes/Events"; import "./events.css"; @@ -19,10 +19,10 @@ export default function EventsSection() { const { t } = useTranslation(); const { realm } = useRealm(); - const useTab = (tab: EventsTab) => useRoutableTab(toEvents({ realm, tab })); - - const userEventsTab = useTab("user-events"); - const adminEventsTab = useTab("admin-events"); + const userEventsTab = useRoutableTab(toEvents({ realm, tab: "user-events" })); + const adminEventsTab = useRoutableTab( + toEvents({ realm, tab: "admin-events" }), + ); return ( <> diff --git a/js/apps/admin-ui/src/events/UserEvents.tsx b/js/apps/admin-ui/src/events/UserEvents.tsx index f11e22cb27d1..5dd19e03124b 100644 --- a/js/apps/admin-ui/src/events/UserEvents.tsx +++ b/js/apps/admin-ui/src/events/UserEvents.tsx @@ -128,10 +128,7 @@ export const UserEvents = ({ user, client }: UserEventsProps) => { const [events, setEvents] = useState(); const [activeFilters, setActiveFilters] = useState< Partial - >({ - ...(user && { user }), - ...(client && { client }), - }); + >({}); const defaultValues: UserEventSearchForm = { client: client ? client : "", @@ -174,6 +171,8 @@ export const UserEvents = ({ user, client }: UserEventsProps) => { return adminClient.realms.findEvents({ // The admin client wants 'dateFrom' and 'dateTo' to be Date objects, however it cannot actually handle them so we need to cast to any. ...(activeFilters as any), + client, + user, realm, first, max, @@ -231,7 +230,7 @@ export const UserEvents = ({ user, client }: UserEventsProps) => { > { onSubmit={handleSubmit(onSubmit)} isHorizontal > - + {!user && ( + + )} { )} /> - + {!client && ( + + )} { string | EventType[], ]; - const disableClose = - (key === "user" && !!user) || - (key === "client" && !!client); - return ( removeFilter(key)} > {typeof value === "string" ? ( @@ -443,11 +439,14 @@ export const UserEvents = ({ user, client }: UserEventsProps) => { cellRenderer: (row) => formatDate(new Date(row.time!), FORMAT_DATE_AND_TIME), }, - { - name: "userId", - displayKey: "user", - cellRenderer: UserDetailLink, - }, + ...(!user + ? [ + { + name: "userId", + cellRenderer: UserDetailLink, + }, + ] + : []), { name: "type", displayKey: "eventType", @@ -458,10 +457,14 @@ export const UserEvents = ({ user, client }: UserEventsProps) => { displayKey: "ipAddress", transforms: [cellWidth(10)], }, - { - name: "clientId", - displayKey: "client", - }, + ...(!client + ? [ + { + name: "clientId", + displayKey: "client", + }, + ] + : []), ]} emptyState={ { }, ); } - refreshRealm(); try { await adminClient.realms.updateConfigEvents( @@ -133,6 +132,8 @@ export const EventsTab = ({ realm }: EventsTabProps) => { error, ); } + + refreshRealm(); }; const addEventTypes = async (eventTypes: EventType[]) => { diff --git a/js/apps/admin-ui/src/user/EditUser.tsx b/js/apps/admin-ui/src/user/EditUser.tsx index b8bf43987a4b..35028e748cd2 100644 --- a/js/apps/admin-ui/src/user/EditUser.tsx +++ b/js/apps/admin-ui/src/user/EditUser.tsx @@ -102,18 +102,18 @@ export default function EditUser() { tab, }); - const useTab = (tab: UserTab) => useRoutableTab(toTab(tab)); - - const settingsTab = useTab("settings"); - const attributesTab = useTab("attributes"); - const credentialsTab = useTab("credentials"); - const roleMappingTab = useTab("role-mapping"); - const groupsTab = useTab("groups"); - const organizationsTab = useTab("organizations"); - const consentsTab = useTab("consents"); - const identityProviderLinksTab = useTab("identity-provider-links"); - const sessionsTab = useTab("sessions"); - const userEventsTab = useTab("user-events"); + const settingsTab = useRoutableTab(toTab("settings")); + const attributesTab = useRoutableTab(toTab("attributes")); + const credentialsTab = useRoutableTab(toTab("credentials")); + const roleMappingTab = useRoutableTab(toTab("role-mapping")); + const groupsTab = useRoutableTab(toTab("groups")); + const organizationsTab = useRoutableTab(toTab("organizations")); + const consentsTab = useRoutableTab(toTab("consents")); + const identityProviderLinksTab = useRoutableTab( + toTab("identity-provider-links"), + ); + const sessionsTab = useRoutableTab(toTab("sessions")); + const userEventsTab = useRoutableTab(toTab("user-events")); useFetch( async () => @@ -426,10 +426,10 @@ export default function EditUser() { > - {hasAccess("view-events") && ( + {hasAccess("view-events") && realm?.eventsEnabled && ( {t("userEvents")}} + title={{t("events")}} {...userEventsTab} > diff --git a/js/libs/ui-shared/src/controls/table/KeycloakDataTable.tsx b/js/libs/ui-shared/src/controls/table/KeycloakDataTable.tsx index b28083ecee2f..ab00d57a4988 100644 --- a/js/libs/ui-shared/src/controls/table/KeycloakDataTable.tsx +++ b/js/libs/ui-shared/src/controls/table/KeycloakDataTable.tsx @@ -77,13 +77,18 @@ type CellRendererProps = { row: IRow; }; -const CellRenderer = ({ row }: CellRendererProps) => { - const isRow = (c: ReactNode | IRowCell): c is IRowCell => - !!c && (c as IRowCell).title !== undefined; - return row.cells!.map((c, i) => ( +const isRow = (c: ReactNode | IRowCell): c is IRowCell => + !!c && (c as IRowCell).title !== undefined; + +const CellRenderer = ({ row }: CellRendererProps) => + row.cells!.map((c, i) => ( {(isRow(c) ? c.title : c) as ReactNode} )); -}; + +const ExpandableRowRenderer = ({ row }: CellRendererProps) => + row.cells!.map((c, i) => ( +
{(isRow(c) ? c.title : c) as ReactNode}
+ )); function DataTable({ columns, @@ -162,10 +167,10 @@ function DataTable({ > - {onCollapse && } + {onCollapse && } {canSelectAll && ( ({ )} {columns.map((column) => ( @@ -243,7 +249,7 @@ function DataTable({ - +