UNPKG

@keycloakify/keycloak-admin-ui

Version:
282 lines 19 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; import { getI18n } from "react-i18next"; import { KeycloakSelect, ListEmptyState, PaginatingTableToolbar, SelectVariant, useAlerts, } from "../../ui-shared"; import { AlertVariant, Button, ButtonVariant, Divider, Dropdown, DropdownItem, DropdownList, Form, FormGroup, MenuToggle, SelectGroup, SelectOption, Text, TextContent, TextInput, TextVariants, ToolbarItem, } from "@patternfly/react-core"; import { CheckIcon, EllipsisVIcon, PencilAltIcon, SearchIcon, TimesIcon, } from "@patternfly/react-icons"; import { ActionsColumn, Table, Tbody, Td, Th, Thead, Tr, } from "@patternfly/react-table"; import { cloneDeep, isEqual, uniqWith } from "lodash-es"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { useAdminClient } from "../../admin-client"; import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog"; import { useRealm } from "../../context/realm-context/RealmContext"; import { useWhoAmI } from "../../context/whoami/WhoAmI"; import { DEFAULT_LOCALE } from "../../i18n/i18n"; import { localeToDisplayName } from "../../util"; import { AddTranslationModal } from "../../realm-settings/AddTranslationModal"; export var RowEditAction; (function (RowEditAction) { RowEditAction["Save"] = "save"; RowEditAction["Cancel"] = "cancel"; RowEditAction["Edit"] = "edit"; RowEditAction["Delete"] = "delete"; })(RowEditAction || (RowEditAction = {})); export const RealmOverrides = ({ internationalizationEnabled, watchSupportedLocales, realm, tableData, }) => { const { adminClient } = useAdminClient(); const { t } = useTranslation(); const [addTranslationModalOpen, setAddTranslationModalOpen] = useState(false); const [filterDropdownOpen, setFilterDropdownOpen] = useState(false); const [translations, setTranslations] = useState([]); const [selectMenuLocale, setSelectMenuLocale] = useState(DEFAULT_LOCALE); const [kebabOpen, setKebabOpen] = useState(false); const { getValues, handleSubmit } = useForm(); const [selectMenuValueSelected, setSelectMenuValueSelected] = useState(false); const [tableRows, setTableRows] = useState([]); const [tableKey, setTableKey] = useState(0); const [max, setMax] = useState(10); const [first, setFirst] = useState(0); const [filter, setFilter] = useState(""); const translationForm = useForm({ mode: "onChange" }); const { addAlert, addError } = useAlerts(); const { realm: currentRealm } = useRealm(); const { whoAmI } = useWhoAmI(); const [selectedRowKeys, setSelectedRowKeys] = useState([]); const [areAllRowsSelected, setAreAllRowsSelected] = useState(false); const [editStates, setEditStates] = useState({}); const [formValue, setFormValue] = useState(""); const refreshTable = () => { setTableKey(tableKey + 1); }; useEffect(() => { const fetchLocalizationTexts = async () => { try { let result = await adminClient.realms.getRealmLocalizationTexts({ first, max, realm: realm.realm, selectedLocale: selectMenuLocale || getValues("defaultLocale") || whoAmI.getLocale(), }); setTranslations(Object.entries(result)); if (filter) { const searchInTranslations = (idx) => { return Object.entries(result).filter((i) => i[idx].includes(filter)); }; const filtered = uniqWith(searchInTranslations(0).concat(searchInTranslations(1)), isEqual); result = Object.fromEntries(filtered); } return Object.entries(result).slice(first, first + max); } catch (_a) { return []; } }; fetchLocalizationTexts().then((translations) => { const updatedRows = translations.map((translation) => ({ rowEditBtnAriaLabel: () => t("rowEditBtnAriaLabel", { translation: translation[1], }), rowSaveBtnAriaLabel: () => t("rowSaveBtnAriaLabel", { translation: translation[1], }), rowCancelBtnAriaLabel: () => t("rowCancelBtnAriaLabel", { translation: translation[1], }), cells: [ { title: translation[0], props: { value: translation[0], }, }, { title: translation[1], props: { value: translation[1], }, }, ], })); setTableRows(updatedRows); }); }, [tableKey, tableData, first, max, filter]); const handleModalToggle = () => { setAddTranslationModalOpen(!addTranslationModalOpen); }; const options = [ _jsx(SelectGroup, { label: t("defaultLocale"), children: _jsx(SelectOption, { value: DEFAULT_LOCALE, children: localeToDisplayName(DEFAULT_LOCALE, whoAmI.getDisplayName()) }, DEFAULT_LOCALE) }, "group1"), _jsx(Divider, {}, "divider"), _jsx(SelectGroup, { label: t("supportedLocales"), children: watchSupportedLocales.map((locale) => (_jsx(SelectOption, { value: locale, children: localeToDisplayName(locale, whoAmI.getLocale()) }, locale))) }, "group2"), ]; const addKeyValue = async (pair) => { try { await adminClient.realms.addLocalization({ realm: currentRealm, selectedLocale: selectMenuLocale || getValues("defaultLocale") || DEFAULT_LOCALE, key: pair.key, }, pair.value); adminClient.setConfig({ realmName: currentRealm, }); refreshTable(); translationForm.setValue("key", ""); translationForm.setValue("value", ""); getI18n().reloadResources(); addAlert(t("addTranslationSuccess"), AlertVariant.success); } catch (error) { addError("addTranslationError", error); } }; const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ titleKey: "deleteConfirmTranslationTitle", messageKey: t("translationDeleteConfirmDialog", { count: selectedRowKeys.length, }), continueButtonLabel: "delete", continueButtonVariant: ButtonVariant.danger, onCancel: () => { setSelectedRowKeys([]); setAreAllRowsSelected(false); }, onConfirm: async () => { try { for (const key of selectedRowKeys) { delete getI18n().store.data[whoAmI.getLocale()][currentRealm][key]; await adminClient.realms.deleteRealmLocalizationTexts({ realm: currentRealm, selectedLocale: selectMenuLocale, key: key, }); } setAreAllRowsSelected(false); setSelectedRowKeys([]); refreshTable(); addAlert(t("deleteAllTranslationsSuccess"), AlertVariant.success); } catch (error) { addError("deleteAllTranslationsError", error); } }, }); const handleRowSelect = (event, rowIndex) => { var _a; const selectedKey = ((_a = tableRows[rowIndex].cells) === null || _a === void 0 ? void 0 : _a[0]).props .value; if (event.target.checked) { setSelectedRowKeys((prevSelected) => [...prevSelected, selectedKey]); } else { setSelectedRowKeys((prevSelected) => prevSelected.filter((key) => key !== selectedKey)); } setAreAllRowsSelected(tableRows.length === selectedRowKeys.length + (event.target.checked ? 1 : -1)); }; const toggleSelectAllRows = () => { if (areAllRowsSelected) { setSelectedRowKeys([]); } else { setSelectedRowKeys(tableRows.map((row) => { var _a; return ((_a = row.cells) === null || _a === void 0 ? void 0 : _a[0]).props.value; })); } setAreAllRowsSelected(!areAllRowsSelected); }; const isRowSelected = (key) => { return selectedRowKeys.includes(key); }; const onSubmit = async (inputValue, rowIndex) => { var _a, _b, _c; const newRows = cloneDeep(tableRows); const newRow = cloneDeep(newRows[rowIndex]); ((_a = newRow.cells) === null || _a === void 0 ? void 0 : _a[1]).props.value = inputValue; newRows[rowIndex] = newRow; try { const key = ((_b = newRow.cells) === null || _b === void 0 ? void 0 : _b[0]).props.value; const value = ((_c = newRow.cells) === null || _c === void 0 ? void 0 : _c[1]).props.value; await adminClient.realms.addLocalization({ realm: realm.realm, selectedLocale: selectMenuLocale || getValues("defaultLocale") || DEFAULT_LOCALE, key, }, value); getI18n().reloadResources(); addAlert(t("updateTranslationSuccess"), AlertVariant.success); setTableRows(newRows); } catch (error) { addError("updateTranslationError", error); } setEditStates((prevEditStates) => ({ ...prevEditStates, [rowIndex]: false, })); }; return (_jsxs(_Fragment, { children: [_jsx(DeleteConfirm, {}), addTranslationModalOpen && (_jsx(AddTranslationModal, { handleModalToggle: handleModalToggle, save: (pair) => { addKeyValue(pair); handleModalToggle(); }, form: translationForm })), _jsx(TextContent, { children: _jsx(Text, { className: "pf-v5-u-mt-lg pf-v5-u-ml-md", component: TextVariants.p, children: t("realmOverridesDescription") }) }), _jsxs(PaginatingTableToolbar, { count: translations.length, first: first, max: max, onNextClick: setFirst, onPreviousClick: setFirst, onPerPageSelect: (first, max) => { setFirst(first); setMax(max); }, inputGroupName: "search", inputGroupOnEnter: (search) => { setFilter(search); setFirst(0); setMax(10); }, inputGroupPlaceholder: t("searchForTranslation"), toolbarItem: _jsxs(_Fragment, { children: [_jsx(Button, { "data-testid": "add-translationBtn", onClick: () => { setAddTranslationModalOpen(true); setAreAllRowsSelected(false); setSelectedRowKeys([]); }, children: t("addTranslation") }), _jsx(ToolbarItem, { children: _jsx(Dropdown, { onOpenChange: (isOpen) => setKebabOpen(isOpen), toggle: (ref) => (_jsx(MenuToggle, { ref: ref, onClick: () => setKebabOpen(!kebabOpen), variant: "plain", isExpanded: kebabOpen, "data-testid": "toolbar-deleteBtn", "aria-label": "kebab", children: _jsx(EllipsisVIcon, {}) })), isOpen: kebabOpen, isPlain: true, children: _jsx(DropdownList, { children: _jsx(DropdownItem, { component: "button", "data-testid": "delete-selected-TranslationBtn", isDisabled: translations.length === 0 || selectedRowKeys.length === 0, onClick: () => { toggleDeleteDialog(); setKebabOpen(false); }, children: t("delete") }, "action") }) }) })] }), searchTypeComponent: _jsx(ToolbarItem, { children: _jsx(KeycloakSelect, { width: 180, isOpen: filterDropdownOpen, className: "kc-filter-by-locale-select", variant: SelectVariant.single, isDisabled: !internationalizationEnabled, onToggle: (isExpanded) => setFilterDropdownOpen(isExpanded), onSelect: (value) => { setSelectMenuLocale(value.toString()); setSelectMenuValueSelected(true); refreshTable(); setFilterDropdownOpen(false); }, selections: selectMenuValueSelected ? localeToDisplayName(selectMenuLocale, whoAmI.getLocale()) : realm.defaultLocale !== "" ? localeToDisplayName(DEFAULT_LOCALE, whoAmI.getLocale()) : t("placeholderText"), children: options }) }), children: [translations.length === 0 && !filter && (_jsx(ListEmptyState, { hasIcon: true, message: t("noTranslations"), instructions: t("noTranslationsInstructions"), onPrimaryAction: handleModalToggle })), translations.length === 0 && filter && (_jsx(ListEmptyState, { hasIcon: true, icon: SearchIcon, isSearchVariant: true, message: t("noSearchResults"), instructions: t("noRealmOverridesSearchResultsInstructions") })), translations.length !== 0 && (_jsxs(Table, { "aria-label": t("editableRowsTable"), "data-testid": "editable-rows-table", children: [_jsx(Thead, { children: _jsxs(Tr, { children: [_jsx(Th, { className: "pf-v5-u-px-lg", children: _jsx("input", { type: "checkbox", "aria-label": t("selectAll"), checked: areAllRowsSelected, onChange: toggleSelectAllRows, "data-testid": "selectAll" }) }), _jsx(Th, { className: "pf-v5-u-py-lg", children: t("key") }), _jsx(Th, { className: "pf-v5-u-py-lg", children: t("value") }), _jsx(Th, { "aria-hidden": "true" })] }) }), _jsx(Tbody, { children: tableRows.map((row, rowIndex) => { var _a, _b, _c, _d; return (_jsxs(Tr, { children: [_jsx(Td, { className: "pf-v5-u-px-lg", select: { rowIndex, onSelect: (event) => handleRowSelect(event, rowIndex), isSelected: isRowSelected(((_a = row.cells) === null || _a === void 0 ? void 0 : _a[0]).props.value), } }), _jsx(Td, { className: "pf-m-sm pf-v5-u-px-sm", dataLabel: t("key"), children: ((_b = row.cells) === null || _b === void 0 ? void 0 : _b[0]).props.value }), _jsx(Td, { className: "pf-m-sm pf-v5-u-px-sm", dataLabel: t("value"), children: _jsx(Form, { isHorizontal: true, className: "kc-form-translationValue", onSubmit: handleSubmit(() => { onSubmit(formValue, rowIndex); }), children: _jsx(FormGroup, { fieldId: "kc-translationValue", className: "pf-v5-u-display-inline-block", children: editStates[rowIndex] ? (_jsxs(_Fragment, { children: [_jsx(TextInput, { "aria-label": t("editTranslationValue"), type: "text", className: "pf-v5-u-w-initial", "data-testid": `editTranslationValueInput-${rowIndex}`, value: formValue, onChange: (event, value) => { setFormValue(value); } }, `edit-input-${rowIndex}`), _jsx(Button, { variant: "link", className: "pf-m-plain", "data-testid": `editTranslationAcceptBtn-${rowIndex}`, type: "submit", "aria-label": t("acceptBtn"), icon: _jsx(CheckIcon, {}) }), _jsx(Button, { variant: "link", className: "pf-m-plain", "data-testid": `editTranslationCancelBtn-${rowIndex}`, icon: _jsx(TimesIcon, {}), "aria-label": t("cancelBtn"), onClick: () => { setEditStates((prevEditStates) => ({ ...prevEditStates, [rowIndex]: false, })); } })] })) : (_jsxs(_Fragment, { children: [_jsx("span", { children: ((_c = row.cells) === null || _c === void 0 ? void 0 : _c[1]).props.value }), _jsx(Button, { onClick: () => { var _a; const currentValue = ((_a = tableRows[rowIndex].cells) === null || _a === void 0 ? void 0 : _a[1]).props.value; setFormValue(currentValue); setEditStates((prevState) => ({ ...prevState, [rowIndex]: true, })); }, "aria-label": t("editBtn"), variant: "link", className: "pf-m-plain", "data-testid": `editTranslationBtn-${rowIndex}`, children: _jsx(PencilAltIcon, {}) }, `edit-button-${rowIndex}`)] })) }) }) }, rowIndex), _jsx(Td, { isActionCell: true, children: _jsx(ActionsColumn, { items: [ { title: t("delete"), onClick: () => { var _a; setSelectedRowKeys([ ((_a = row.cells) === null || _a === void 0 ? void 0 : _a[0]).props.value, ]); if (translations.length === 1) { setAreAllRowsSelected(true); } toggleDeleteDialog(); setKebabOpen(false); }, }, ] }) })] }, ((_d = row.cells) === null || _d === void 0 ? void 0 : _d[0]).props.value)); }) })] }))] })] })); }; //# sourceMappingURL=RealmOverrides.js.map