@keycloakify/keycloak-admin-ui
Version:
Repackaged Keycloak Admin UI
282 lines • 19 kB
JavaScript
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