@keycloakify/keycloak-admin-ui
Version:
Repackaged Keycloak Admin UI
164 lines • 13.5 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { KeycloakDataTable, KeycloakSelect, ListEmptyState, SelectVariant, TextControl, useFetch, } from "../ui-shared";
import { ActionGroup, Button, Chip, ChipGroup, DatePicker, DescriptionList, DescriptionListDescription, DescriptionListGroup, DescriptionListTerm, Flex, FlexItem, Form, FormGroup, Icon, PageSection, SelectOption, Tab, TabTitleText, Tooltip, } from "@patternfly/react-core";
import { CheckCircleIcon, WarningTriangleIcon } from "@patternfly/react-icons";
import { cellWidth } from "@patternfly/react-table";
import { pickBy } from "lodash-es";
import { useState } from "react";
import { Controller, FormProvider, useForm } from "react-hook-form";
import { Trans, useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { useAdminClient } from "../admin-client";
import DropdownPanel from "../components/dropdown-panel/DropdownPanel";
import { RoutableTabs, useRoutableTab, } from "../components/routable-tabs/RoutableTabs";
import { ViewHeader } from "../components/view-header/ViewHeader";
import { useRealm } from "../context/realm-context/RealmContext";
import helpUrls from "../help-urls";
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 "../events/AdminEvents";
import { toEvents } from "../events/routes/Events";
import "./events.css";
const defaultValues = {
client: "",
dateFrom: "",
dateTo: "",
user: "",
type: [],
ipAddress: "",
};
const StatusRow = (event) => !event.error ? (_jsxs("span", { children: [_jsx(Icon, { status: "success", children: _jsx(CheckCircleIcon, {}) }), event.type] })) : (_jsx(Tooltip, { content: event.error, children: _jsxs("span", { children: [_jsx(Icon, { status: "warning", children: _jsx(WarningTriangleIcon, {}) }), event.type] }) }));
const DetailCell = (event) => (_jsxs(DescriptionList, { isHorizontal: true, className: "keycloak_eventsection_details", children: [event.details &&
Object.entries(event.details).map(([key, value]) => (_jsxs(DescriptionListGroup, { children: [_jsx(DescriptionListTerm, { children: key }), _jsx(DescriptionListDescription, { children: value })] }, key))), event.error && (_jsxs(DescriptionListGroup, { children: [_jsx(DescriptionListTerm, { children: "error" }), _jsx(DescriptionListDescription, { children: event.error })] }, "error"))] }));
const UserDetailLink = (event) => {
const { t } = useTranslation();
const { realm } = useRealm();
return (_jsxs(_Fragment, { children: [event.userId && (_jsx(Link, { to: toUser({
realm,
id: event.userId,
tab: "settings",
}), children: event.userId }, `link-${event.time}-${event.type}`)), !event.userId && t("noUserDetails")] }));
};
export default function EventsSection() {
const { adminClient } = useAdminClient();
const { t } = useTranslation();
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({});
const filterLabels = {
client: t("client"),
dateFrom: t("dateFrom"),
dateTo: t("dateTo"),
user: t("userId"),
type: t("eventType"),
ipAddress: t("ipAddress"),
};
const form = useForm({
mode: "onChange",
defaultValues,
});
const { getValues, reset, formState: { isDirty }, control, handleSubmit, } = form;
useFetch(() => adminClient.realms.getConfigEvents({ realm }), (events) => setEvents(events), []);
function loader(first, max) {
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,
realm,
first,
max,
});
}
const useTab = (tab) => useRoutableTab(toEvents({ realm, tab }));
const userEventsTab = useTab("user-events");
const adminEventsTab = useTab("admin-events");
function onSubmit() {
setSearchDropdownOpen(false);
commitFilters();
}
function resetSearch() {
reset();
commitFilters();
}
function removeFilter(key) {
const formValues = { ...getValues() };
delete formValues[key];
reset({ ...defaultValues, ...formValues });
commitFilters();
}
function removeFilterValue(key, valueToRemove) {
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 = pickBy(getValues(), (value) => value !== "" || (Array.isArray(value) && value.length > 0));
setActiveFilters(newFilters);
setKey(key + 1);
}
const userEventSearchFormDisplay = () => {
return (_jsx(FormProvider, { ...form, children: _jsxs(Flex, { direction: { default: "column" }, spaceItems: { default: "spaceItemsNone" }, children: [_jsx(FlexItem, { children: _jsx(DropdownPanel, { buttonText: t("searchForUserEvent"), setSearchDropdownOpen: setSearchDropdownOpen, searchDropdownOpen: searchDropdownOpen, marginRight: "2.5rem", width: "15vw", children: _jsxs(Form, { "data-testid": "searchForm", className: "keycloak__events_search__form", onSubmit: handleSubmit(onSubmit), isHorizontal: true, children: [_jsx(TextControl, { name: "user", label: t("userId"), "data-testid": "userId-searchField" }), _jsx(FormGroup, { label: t("eventType"), fieldId: "kc-eventType", className: "keycloak__events_search__form_label", children: _jsx(Controller, { name: "type", control: control, render: ({ field }) => {
var _a;
return (_jsx(KeycloakSelect, { className: "keycloak__events_search__type_select", "data-testid": "event-type-searchField", chipGroupProps: {
numChips: 1,
expandedText: t("hide"),
collapsedText: t("showRemaining"),
}, variant: SelectVariant.typeaheadMulti, typeAheadAriaLabel: "Select", onToggle: (isOpen) => setSelectOpen(isOpen), selections: field.value, onSelect: (selectedValue) => {
const option = selectedValue.toString();
const changedValue = field.value.includes(option)
? field.value.filter((item) => item !== option)
: [...field.value, option];
field.onChange(changedValue);
}, onClear: () => {
field.onChange([]);
}, isOpen: selectOpen, "aria-labelledby": "eventType", chipGroupComponent: _jsx(ChipGroup, { children: field.value.map((chip) => (_jsx(Chip, { onClick: (event) => {
event.stopPropagation();
field.onChange(field.value.filter((val) => val !== chip));
}, children: t(`eventTypes.${chip}.name`) }, chip))) }), children: (_a = events === null || events === void 0 ? void 0 : events.enabledEventTypes) === null || _a === void 0 ? void 0 : _a.map((option) => (_jsx(SelectOption, { value: option, children: t(`eventTypes.${option}.name`) }, option))) }));
} }) }), _jsx(TextControl, { name: "client", label: t("client"), "data-testid": "client-searchField" }), _jsx(FormGroup, { label: t("dateFrom"), fieldId: "kc-dateFrom", className: "keycloak__events_search__form_label", children: _jsx(Controller, { name: "dateFrom", control: control, render: ({ field }) => (_jsx(DatePicker, { className: "pf-v5-u-w-100", value: field.value, onChange: (_, value) => field.onChange(value), inputProps: { id: "kc-dateFrom" } })) }) }), _jsx(FormGroup, { label: t("dateTo"), fieldId: "kc-dateTo", className: "keycloak__events_search__form_label", children: _jsx(Controller, { name: "dateTo", control: control, render: ({ field }) => (_jsx(DatePicker, { className: "pf-v5-u-w-100", value: field.value, onChange: (_, value) => field.onChange(value), inputProps: { id: "kc-dateTo" } })) }) }), _jsx(TextControl, { name: "ipAddress", label: t("ipAddress"), "data-testid": "ipAddress-searchField" }), _jsxs(ActionGroup, { children: [_jsx(Button, { "data-testid": "search-events-btn", variant: "primary", type: "submit", isDisabled: !isDirty, children: t("searchUserEventsBtn") }), _jsx(Button, { variant: "secondary", onClick: resetSearch, isDisabled: !isDirty, children: t("resetBtn") })] })] }) }) }), _jsx(FlexItem, { children: Object.entries(activeFilters).length > 0 && (_jsx("div", { className: "keycloak__searchChips pf-v5-u-ml-md", children: Object.entries(activeFilters).map((filter) => {
const [key, value] = filter;
return (_jsx(ChipGroup, { className: "pf-v5-u-mt-md pf-v5-u-mr-md", categoryName: filterLabels[key], isClosable: true, onClick: () => removeFilter(key), children: typeof value === "string" ? (_jsx(Chip, { isReadOnly: true, children: value })) : (value.map((entry) => (_jsx(Chip, { onClick: () => removeFilterValue(key, entry), children: t(`eventTypes.${entry}.name`) }, entry)))) }, key));
}) })) })] }) }));
};
return (_jsxs(_Fragment, { children: [_jsx(ViewHeader, { titleKey: "titleEvents", subKey: _jsxs(Trans, { i18nKey: "eventExplain", children: ["If you want to configure user events, Admin events or Event listeners, please enter", _jsx(Link, { to: toRealmSettings({ realm, tab: "events" }), children: t("eventConfig") }), "page realm settings to configure."] }), helpUrl: helpUrls.eventsUrl, divider: false }), _jsx(PageSection, { variant: "light", className: "pf-v5-u-p-0", children: _jsxs(RoutableTabs, { isBox: true, defaultLocation: toEvents({ realm, tab: "user-events" }), children: [_jsx(Tab, { title: _jsx(TabTitleText, { children: t("userEvents") }), ...userEventsTab, children: _jsx("div", { className: "keycloak__events_table", children: _jsx(KeycloakDataTable, { loader: loader, detailColumns: [
{
name: "details",
enabled: (event) => event.details !== undefined,
cellRenderer: DetailCell,
},
], isPaginated: true, ariaLabelKey: "titleEvents", toolbarItem: userEventSearchFormDisplay(), columns: [
{
name: "time",
displayKey: "time",
cellRenderer: (row) => formatDate(new Date(row.time), FORMAT_DATE_AND_TIME),
},
{
name: "userId",
displayKey: "user",
cellRenderer: UserDetailLink,
},
{
name: "type",
displayKey: "eventType",
cellRenderer: StatusRow,
},
{
name: "ipAddress",
displayKey: "ipAddress",
transforms: [cellWidth(10)],
},
{
name: "clientId",
displayKey: "client",
},
], emptyState: _jsx(ListEmptyState, { message: t("emptyUserEvents"), instructions: t("emptyUserEventsInstructions") }), isSearching: Object.keys(activeFilters).length > 0 }, key) }) }), _jsx(Tab, { title: _jsx(TabTitleText, { children: t("adminEvents") }), "data-testid": "admin-events-tab", ...adminEventsTab, children: _jsx(AdminEvents, {}) })] }) })] }));
}
//# sourceMappingURL=EventsSection.js.map