import { ActionGroup, Button, Chip, ChipGroup, Dropdown, DropdownToggle, Flex, FlexItem, Form, FormGroup, Modal, ModalVariant, Select, SelectOption, SelectVariant, TextInput, Tooltip, } from "@patternfly/react-core"; import { cellWidth, Table, TableBody, TableHeader, TableVariant, } from "@patternfly/react-table"; import type AdminEventRepresentation from "@keycloak/keycloak-admin-client/lib/defs/adminEventRepresentation"; import moment from "moment"; import React, { FunctionComponent, useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; import { pickBy } from "lodash"; 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 "./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} ); }; const MAX_TEXT_LENGTH = 38; const Truncate = ({ text, children, }: { text?: string; children: (text: string) => any; }) => { const definedText = text || ""; const needsTruncation = definedText.length > MAX_TEXT_LENGTH; const truncatedText = definedText.substr(0, MAX_TEXT_LENGTH); return ( <> {needsTruncation && ( {children(truncatedText + "...")} )} {!needsTruncation && <>{children(definedText)}} ); }; 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) ); console.log(newFilters); setActiveFilters(newFilters); setKey(key + 1); } function refresh() { commitFilters(); } const LinkResource = (row: AdminEventRepresentation) => ( {(text) => ( <> {row.resourceType !== "COMPONENT" && ( {text} )} {row.resourceType === "COMPONENT" && {text}} )} ); 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} )) )} ); })}
)}
); }; return ( <> {authEvent && ( setAuthEvent(undefined)}>
)} {representationEvent && ( setRepresentationEvent(undefined)} > some json from the changed values )} 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: LinkResource, }, { name: "resourceType", displayKey: "events:resourceType", }, { name: "operationType", displayKey: "events:operationType", transforms: [cellWidth(10)], }, { name: "", displayKey: "events:user", cellRenderer: (event) => event.authDetails?.userId, }, ]} emptyState={ } /> ); };