import { ActionGroup, Button, Chip, ChipGroup, Dropdown, DropdownToggle, Flex, FlexItem, Form, FormGroup, Modal, ModalVariant, Select, SelectOption, SelectVariant, TextInput, } from "@patternfly/react-core"; import { cellWidth, Table, TableBody, TableHeader, TableVariant, } from "@patternfly/react-table"; import { CodeEditor, Language } from "@patternfly/react-code-editor"; import type AdminEventRepresentation from "@keycloak/keycloak-admin-client/lib/defs/adminEventRepresentation"; import moment from "moment"; import React, { FunctionComponent, useMemo, useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { pickBy } from "lodash-es"; import { ListEmptyState } from "../components/list-empty-state/ListEmptyState"; import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable"; import { useAdminClient } from "../context/auth/AdminClient"; import { useRealm } from "../context/realm-context/RealmContext"; import { useServerInfo } from "../context/server-info/ServerInfoProvider"; import { prettyPrintJSON } from "../util"; import { CellResourceLinkRenderer } from "./ResourceLinks"; import "./events.css"; type DisplayDialogProps = { titleKey: string; onClose: () => void; }; type AdminEventSearchForm = { resourceTypes: string[]; operationTypes: string[]; resourcePath: string; dateFrom: string; dateTo: string; authClient: string; authUser: string; authRealm: string; authIpAddress: string; }; const defaultValues: AdminEventSearchForm = { resourceTypes: [], operationTypes: [], resourcePath: "", dateFrom: "", dateTo: "", authClient: "", authUser: "", authRealm: "", authIpAddress: "", }; const DisplayDialog: FunctionComponent = ({ titleKey, onClose, children, }) => { const { t } = useTranslation("events"); return ( {children} ); }; export const AdminEvents = () => { const { t } = useTranslation("events"); const adminClient = useAdminClient(); const { realm } = useRealm(); const serverInfo = useServerInfo(); const resourceTypes = serverInfo.enums?.["resourceType"]; const operationTypes = serverInfo.enums?.["operationType"]; const [key, setKey] = useState(0); const [searchDropdownOpen, setSearchDropdownOpen] = useState(false); const [selectResourceTypesOpen, setSelectResourceTypesOpen] = useState(false); const [selectOperationTypesOpen, setSelectOperationTypesOpen] = useState(false); const [activeFilters, setActiveFilters] = useState< Partial >({}); const [authEvent, setAuthEvent] = useState(); const [representationEvent, setRepresentationEvent] = useState(); const filterLabels: Record = { resourceTypes: t("resourceTypes"), operationTypes: t("operationTypes"), resourcePath: t("resourcePath"), dateFrom: t("dateFrom"), dateTo: t("dateTo"), authClient: t("client"), authUser: t("userId"), authRealm: t("realm"), authIpAddress: t("ipAddress"), }; const { getValues, register, reset, formState: { isDirty }, control, } = useForm({ shouldUnregister: false, mode: "onChange", defaultValues, }); function loader(first?: number, max?: number) { return adminClient.realms.findAdminEvents({ // 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 removeFilter(key: keyof AdminEventSearchForm) { const formValues: AdminEventSearchForm = { ...getValues() }; delete formValues[key]; reset({ ...defaultValues, ...formValues }); commitFilters(); } function removeFilterValue( key: keyof AdminEventSearchForm, valueToRemove: string ) { 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 adminEventSearchFormDisplay = () => { return ( setSearchDropdownOpen(isOpen)} className="keycloak__events_search_selector_dropdown__toggle" > {t("searchForAdminEvent")} } isOpen={searchDropdownOpen} >
void; value: string[]; }) => ( )} /> void; value: string[]; }) => ( )} />
{Object.entries(activeFilters).length > 0 && (
{Object.entries(activeFilters).map((filter) => { const [key, value] = filter as [ keyof AdminEventSearchForm, string | string[] ]; return ( removeFilter(key)} > {typeof value === "string" ? ( {value} ) : ( value.map((entry) => ( removeFilterValue(key, entry)} > {entry} )) )} ); })}
)}
); }; const rows = [ [t("realm"), authEvent?.authDetails?.realmId], [t("client"), authEvent?.authDetails?.clientId], [t("user"), authEvent?.authDetails?.userId], [t("ipAddress"), authEvent?.authDetails?.ipAddress], ]; const code = useMemo( () => representationEvent?.representation ? prettyPrintJSON(JSON.parse(representationEvent.representation)) : "", [representationEvent?.representation] ); return ( <> {authEvent && ( setAuthEvent(undefined)}>
)} {representationEvent && ( setRepresentationEvent(undefined)} > )} setAuthEvent(event), }, { title: t("representation"), onRowClick: (event) => setRepresentationEvent(event), }, ]} columns={[ { name: "time", displayKey: "events:time", cellRenderer: (row) => moment(row.time).format("LLL"), }, { name: "resourcePath", displayKey: "events:resourcePath", cellRenderer: CellResourceLinkRenderer, }, { name: "resourceType", displayKey: "events:resourceType", }, { name: "operationType", displayKey: "events:operationType", transforms: [cellWidth(10)], }, { name: "", displayKey: "events:user", cellRenderer: (event) => event.authDetails?.userId, }, ]} emptyState={ } isSearching={Object.keys(activeFilters).length > 0} /> ); };