import { ActionGroup, Button, Chip, ChipGroup, DescriptionList, DescriptionListDescription, DescriptionListGroup, DescriptionListTerm, Dropdown, DropdownToggle, Flex, FlexItem, Form, FormGroup, PageSection, Select, SelectOption, SelectVariant, Tab, TabTitleText, Tooltip, } from "@patternfly/react-core"; import { CheckCircleIcon, WarningTriangleIcon } from "@patternfly/react-icons"; import { cellWidth, expandable } from "@patternfly/react-table"; import type EventRepresentation from "@keycloak/keycloak-admin-client/lib/defs/eventRepresentation"; import type EventType from "@keycloak/keycloak-admin-client/lib/defs/eventTypes"; import type { RealmEventsConfigRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/realmEventsConfigRepresentation"; import { pickBy } from "lodash-es"; import { useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { Trans, useTranslation } from "react-i18next"; import { useHistory } from "react-router-dom"; import { Link } from "react-router-dom-v5-compat"; import { ListEmptyState } from "../components/list-empty-state/ListEmptyState"; import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable"; import { ViewHeader } from "../components/view-header/ViewHeader"; import { KeycloakTextInput } from "../components/keycloak-text-input/KeycloakTextInput"; import { useAdminClient, useFetch } from "../context/auth/AdminClient"; import { useRealm } from "../context/realm-context/RealmContext"; import { toRealmSettings } from "../realm-settings/routes/RealmSettings"; import { toUser } from "../user/routes/User"; import useFormatDate, { FORMAT_DATE_AND_TIME } from "../utils/useFormatDate"; import { AdminEvents } from "./AdminEvents"; import helpUrls from "../help-urls"; import { routableTab, RoutableTabs, } from "../components/routable-tabs/RoutableTabs"; import { EventsTab, toEvents } from "./routes/Events"; import "./events.css"; type UserEventSearchForm = { client: string; dateFrom: string; dateTo: string; user: string; type: EventType[]; }; const defaultValues: UserEventSearchForm = { client: "", dateFrom: "", dateTo: "", user: "", type: [], }; const StatusRow = (event: EventRepresentation) => !event.error ? ( {event.type} ) : ( {event.type} ); const DetailCell = (event: EventRepresentation) => ( {Object.entries(event.details!).map(([key, value]) => ( {key} {value} ))} ); export default function EventsSection() { const { t } = useTranslation("events"); const { adminClient } = useAdminClient(); const { realm } = useRealm(); const formatDate = useFormatDate(); const [key, setKey] = useState(0); const [searchDropdownOpen, setSearchDropdownOpen] = useState(false); const [selectOpen, setSelectOpen] = useState(false); const [events, setEvents] = useState(); const [activeFilters, setActiveFilters] = useState< Partial >({}); const filterLabels: Record = { client: t("client"), dateFrom: t("dateFrom"), dateTo: t("dateTo"), user: t("userId"), type: t("eventType"), }; const { getValues, register, reset, formState: { isDirty }, control, } = useForm({ shouldUnregister: false, mode: "onChange", defaultValues, }); useFetch( () => adminClient.realms.getConfigEvents({ realm }), (events) => setEvents(events), [] ); function loader(first?: number, max?: number) { 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), realm, first, max, }); } function submitSearch() { setSearchDropdownOpen(false); commitFilters(); } function resetSearch() { reset(); commitFilters(); } function removeFilter(key: keyof UserEventSearchForm) { const formValues: UserEventSearchForm = { ...getValues() }; delete formValues[key]; reset({ ...defaultValues, ...formValues }); commitFilters(); } function removeFilterValue( key: keyof UserEventSearchForm, valueToRemove: EventType ) { const formValues = getValues(); const fieldValue = formValues[key]; const newFieldValue = Array.isArray(fieldValue) ? fieldValue.filter((val) => val !== valueToRemove) : fieldValue; reset({ ...formValues, [key]: newFieldValue }); commitFilters(); } function commitFilters() { const newFilters: Partial = pickBy( getValues(), (value) => value !== "" || (Array.isArray(value) && value.length > 0) ); setActiveFilters(newFilters); setKey(key + 1); } function refresh() { commitFilters(); } const UserDetailLink = (event: EventRepresentation) => ( <> {event.userId && ( {event.userId} )} {!event.userId && t("noUserDetails")} ); const userEventSearchFormDisplay = () => { return ( setSearchDropdownOpen(isOpen)} className="keycloak__events_search_selector_dropdown__toggle" > {t("searchForUserEvent")} } isOpen={searchDropdownOpen} >
void; value: EventType[]; }) => ( )} />
{Object.entries(activeFilters).length > 0 && (
{Object.entries(activeFilters).map((filter) => { const [key, value] = filter as [ keyof UserEventSearchForm, string | EventType[] ]; return ( removeFilter(key)} > {typeof value === "string" ? ( {value} ) : ( value.map((entry) => ( removeFilterValue(key, entry)} > {entry} )) )} ); })}
)}
); }; const history = useHistory(); const route = (tab: EventsTab) => routableTab({ to: toEvents({ realm, tab }), history, }); return ( <> If you want to configure user events, Admin events or Event listeners, please enter {t("eventConfig")} page realm settings to configure. } helpUrl={helpUrls.eventsUrl} divider={false} /> {t("userEvents")}} {...route("user-events")} >
event.details !== undefined, cellRenderer: DetailCell, }, ]} isPaginated ariaLabelKey="events:title" toolbarItem={userEventSearchFormDisplay()} columns={[ { name: "time", displayKey: "events:time", cellFormatters: [expandable], cellRenderer: (row) => formatDate(new Date(row.time!), FORMAT_DATE_AND_TIME), }, { name: "userId", displayKey: "events:user", cellRenderer: UserDetailLink, }, { name: "type", displayKey: "events:eventType", cellRenderer: StatusRow, }, { name: "ipAddress", displayKey: "events:ipAddress", transforms: [cellWidth(10)], }, { name: "clientId", displayKey: "events:client", }, ]} emptyState={ } isSearching={Object.keys(activeFilters).length > 0} />
{t("adminEvents")}} data-testid="admin-events-tab" {...route("admin-events")} >
); }