@keycloakify/keycloak-admin-ui
Version:
Repackaged Keycloak Admin UI
176 lines • 15 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { KeycloakSelect, SelectVariant, TextControl, } from "../ui-shared";
import { CodeEditor, Language } from "@patternfly/react-code-editor";
import { ActionGroup, Button, Chip, ChipGroup, DatePicker, Flex, FlexItem, Form, FormGroup, Modal, ModalVariant, SelectOption, } from "@patternfly/react-core";
import { Table, TableVariant, Tbody, Td, Th, Thead, Tr, cellWidth, } from "@patternfly/react-table";
import { pickBy } from "lodash-es";
import { useMemo, useState } from "react";
import { Controller, FormProvider, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { useAdminClient } from "../admin-client";
import DropdownPanel from "../components/dropdown-panel/DropdownPanel";
import { ListEmptyState } from "../ui-shared";
import { KeycloakDataTable } from "../ui-shared";
import { useRealm } from "../context/realm-context/RealmContext";
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
import { prettyPrintJSON } from "../util";
import useFormatDate, { FORMAT_DATE_AND_TIME } from "../utils/useFormatDate";
import { CellResourceLinkRenderer } from "../events/ResourceLinks";
import "./events.css";
const defaultValues = {
resourceTypes: [],
operationTypes: [],
resourcePath: "",
dateFrom: "",
dateTo: "",
authClient: "",
authUser: "",
authRealm: "",
authIpAddress: "",
};
const DisplayDialog = ({ titleKey, onClose, children, }) => {
const { t } = useTranslation();
return (_jsx(Modal, { variant: ModalVariant.medium, title: t(titleKey), isOpen: true, onClose: onClose, children: children }));
};
export const AdminEvents = () => {
var _a, _b, _c, _d, _e, _f;
const { adminClient } = useAdminClient();
const { t } = useTranslation();
const { realm } = useRealm();
const serverInfo = useServerInfo();
const formatDate = useFormatDate();
const resourceTypes = (_a = serverInfo.enums) === null || _a === void 0 ? void 0 : _a["resourceType"];
const operationTypes = (_b = serverInfo.enums) === null || _b === void 0 ? void 0 : _b["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({});
const [authEvent, setAuthEvent] = useState();
const [representationEvent, setRepresentationEvent] = useState();
const filterLabels = {
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 form = useForm({
mode: "onChange",
defaultValues,
});
const { getValues, reset, formState: { isDirty }, control, } = form;
function loader(first, max) {
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,
realm,
first,
max,
});
}
function submitSearch() {
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 code = useMemo(() => (representationEvent === null || representationEvent === void 0 ? void 0 : representationEvent.representation)
? prettyPrintJSON(JSON.parse(representationEvent.representation))
: "", [representationEvent === null || representationEvent === void 0 ? void 0 : representationEvent.representation]);
return (_jsxs(_Fragment, { children: [authEvent && (_jsx(DisplayDialog, { titleKey: "auth", onClose: () => setAuthEvent(undefined), children: _jsxs(Table, { "aria-label": "authData", "data-testid": "auth-dialog", variant: TableVariant.compact, children: [_jsx(Thead, { children: _jsxs(Tr, { children: [_jsx(Th, { children: t("attribute") }), _jsx(Th, { children: t("value") })] }) }), _jsxs(Tbody, { children: [_jsxs(Tr, { children: [_jsx(Td, { children: t("realm") }), _jsx(Td, { children: (_c = authEvent.authDetails) === null || _c === void 0 ? void 0 : _c.realmId })] }), _jsxs(Tr, { children: [_jsx(Td, { children: t("client") }), _jsx(Td, { children: (_d = authEvent.authDetails) === null || _d === void 0 ? void 0 : _d.clientId })] }), _jsxs(Tr, { children: [_jsx(Td, { children: t("user") }), _jsx(Td, { children: (_e = authEvent.authDetails) === null || _e === void 0 ? void 0 : _e.userId })] }), _jsxs(Tr, { children: [_jsx(Td, { children: t("ipAddress") }), _jsx(Td, { children: (_f = authEvent.authDetails) === null || _f === void 0 ? void 0 : _f.ipAddress })] })] })] }) })), representationEvent && (_jsx(DisplayDialog, { titleKey: "representation", "data-testid": "representation-dialog", onClose: () => setRepresentationEvent(undefined), children: _jsx(CodeEditor, { isLineNumbersVisible: true, isReadOnly: true, code: code, language: Language.json, height: "8rem" }) })), _jsx(KeycloakDataTable, { loader: loader, isPaginated: true, ariaLabelKey: "adminEvents", toolbarItem: _jsx(FormProvider, { ...form, children: _jsxs(Flex, { direction: { default: "column" }, spaceItems: { default: "spaceItemsNone" }, children: [_jsx(FlexItem, { children: _jsx(DropdownPanel, { buttonText: t("searchForAdminEvent"), setSearchDropdownOpen: setSearchDropdownOpen, searchDropdownOpen: searchDropdownOpen, marginRight: "2.5rem", width: "15vw", children: _jsxs(Form, { isHorizontal: true, className: "keycloak__events_search__form", "data-testid": "searchForm", children: [_jsx(FormGroup, { label: t("resourceTypes"), fieldId: "kc-resourceTypes", className: "keycloak__events_search__form_label", children: _jsx(Controller, { name: "resourceTypes", control: control, render: ({ field }) => (_jsx(KeycloakSelect, { className: "keycloak__events_search__type_select", "data-testid": "resource-types-searchField", chipGroupProps: {
numChips: 1,
expandedText: t("hide"),
collapsedText: t("showRemaining"),
}, variant: SelectVariant.typeaheadMulti, typeAheadAriaLabel: "Select", onToggle: (isOpen) => setSelectResourceTypesOpen(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: selectResourceTypesOpen, "aria-labelledby": "resourceTypes", chipGroupComponent: _jsx(ChipGroup, { children: field.value.map((chip) => (_jsx(Chip, { onClick: (resource) => {
resource.stopPropagation();
field.onChange(field.value.filter((val) => val !== chip));
}, children: chip }, chip))) }), children: resourceTypes === null || resourceTypes === void 0 ? void 0 : resourceTypes.map((option) => (_jsx(SelectOption, { value: option, children: option }, option))) })) }) }), _jsx(FormGroup, { label: t("operationTypes"), fieldId: "kc-operationTypes", className: "keycloak__events_search__form_label", children: _jsx(Controller, { name: "operationTypes", control: control, render: ({ field }) => (_jsx(KeycloakSelect, { className: "keycloak__events_search__type_select", "data-testid": "operation-types-searchField", chipGroupProps: {
numChips: 1,
expandedText: t("hide"),
collapsedText: t("showRemaining"),
}, variant: SelectVariant.typeaheadMulti, typeAheadAriaLabel: "Select", onToggle: (isOpen) => setSelectOperationTypesOpen(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: selectOperationTypesOpen, "aria-labelledby": "operationTypes", chipGroupComponent: _jsx(ChipGroup, { children: field.value.map((chip) => (_jsx(Chip, { onClick: (operation) => {
operation.stopPropagation();
field.onChange(field.value.filter((val) => val !== chip));
}, children: chip }, chip))) }), children: operationTypes === null || operationTypes === void 0 ? void 0 : operationTypes.map((option) => (_jsx(SelectOption, { value: option, children: option }, option.toString()))) })) }) }), _jsx(TextControl, { name: "resourcePath", label: t("resourcePath") }), _jsx(TextControl, { name: "authRealm", label: t("realm") }), _jsx(TextControl, { name: "authClient", label: t("client") }), _jsx(TextControl, { name: "authUser", label: t("user") }), _jsx(TextControl, { name: "authIpAddress", label: t("ipAddress") }), _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" } })) }) }), _jsxs(ActionGroup, { children: [_jsx(Button, { variant: "primary", onClick: submitSearch, "data-testid": "search-events-btn", isDisabled: !isDirty, children: t("searchAdminEventsBtn") }), _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: entry }, entry)))) }, key));
}) })) })] }) }), actions: [
{
title: t("auth"),
onRowClick: (event) => setAuthEvent(event),
},
{
title: t("representation"),
onRowClick: (event) => setRepresentationEvent(event),
},
], columns: [
{
name: "time",
displayKey: "time",
cellRenderer: (row) => formatDate(new Date(row.time), FORMAT_DATE_AND_TIME),
},
{
name: "resourcePath",
displayKey: "resourcePath",
cellRenderer: CellResourceLinkRenderer,
},
{
name: "resourceType",
displayKey: "resourceType",
},
{
name: "operationType",
displayKey: "operationType",
transforms: [cellWidth(10)],
},
{
name: "",
displayKey: "user",
cellRenderer: (event) => { var _a; return ((_a = event.authDetails) === null || _a === void 0 ? void 0 : _a.userId) || ""; },
},
], emptyState: _jsx(ListEmptyState, { message: t("emptyAdminEvents"), instructions: t("emptyAdminEventsInstructions") }), isSearching: Object.keys(activeFilters).length > 0 }, key)] }));
};
//# sourceMappingURL=AdminEvents.js.map