@keycloakify/keycloak-admin-ui
Version:
Repackaged Keycloak Admin UI
208 lines • 15.8 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { KeycloakSelect, SelectVariant } from "../../ui-shared";
import { ActionGroup, Button, Chip, ChipGroup, Divider, Flex, FlexItem, Form, FormGroup, SelectOption, Text, TextContent, TextInput, TextVariants, } from "@patternfly/react-core";
import { pickBy } from "lodash-es";
import { useMemo, useState } from "react";
import { Controller, 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 { useWhoAmI } from "../../context/whoami/WhoAmI";
import { DEFAULT_LOCALE } from "../../i18n/i18n";
import { localeToDisplayName } from "../../util";
import useLocaleSort, { mapByKey } from "../../utils/useLocaleSort";
const defaultValues = {
theme: "",
themeType: "",
locale: "",
hasWords: [],
};
export const EffectiveMessageBundles = ({ defaultSupportedLocales, defaultLocales, }) => {
const { adminClient } = useAdminClient();
const { t } = useTranslation();
const { realm } = useRealm();
const serverInfo = useServerInfo();
const { whoAmI } = useWhoAmI();
const localeSort = useLocaleSort();
const [searchDropdownOpen, setSearchDropdownOpen] = useState(false);
const [searchPerformed, setSearchPerformed] = useState(false);
const [selectThemesOpen, setSelectThemesOpen] = useState(false);
const [selectThemeTypeOpen, setSelectThemeTypeOpen] = useState(false);
const [selectLanguageOpen, setSelectLanguageOpen] = useState(false);
const [activeFilters, setActiveFilters] = useState({});
const [key, setKey] = useState(0);
const themes = serverInfo.themes;
const themeTypes = useMemo(() => {
if (!themes) {
return [];
}
return localeSort(Object.keys(themes), (key) => key);
}, [themes]);
const themeNames = useMemo(() => {
if (!themes) {
return [];
}
return localeSort(Object.values(themes)
.flatMap((theme) => theme.map((item) => item.name))
.filter((value, index, self) => self.indexOf(value) === index), (name) => name);
}, [themes]);
const combinedLocales = useMemo(() => {
return Array.from(new Set([...defaultLocales, ...defaultSupportedLocales]));
}, [defaultLocales, defaultSupportedLocales]);
const filterLabels = {
theme: t("theme"),
themeType: t("themeType"),
locale: t("language"),
hasWords: t("hasWords"),
};
const { getValues, reset, formState: { isDirty, isValid }, control, } = useForm({
mode: "onChange",
defaultValues,
});
const loader = async () => {
try {
const filter = getValues();
const requiredKeys = ["theme", "themeType", "locale"];
const shouldReturnEmpty = requiredKeys.some((key) => !filter[key]);
if (shouldReturnEmpty) {
return [];
}
const messages = await adminClient.serverInfo.findEffectiveMessageBundles({
realm,
...filter,
locale: filter.locale || DEFAULT_LOCALE,
source: true,
});
const filteredMessages = filter.hasWords.length > 0
? messages.filter((m) => filter.hasWords.some((f) => m.value.includes(f) || m.key.includes(f)))
: messages;
const sortedMessages = localeSort([...filteredMessages], mapByKey("key"));
return sortedMessages;
}
catch (_a) {
return [];
}
};
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 effectiveMessageBunldesSearchFormDisplay = () => {
return (_jsxs(Flex, { direction: { default: "column" }, spaceItems: { default: "spaceItemsNone" }, children: [_jsx(FlexItem, { children: _jsx(TextContent, { children: _jsx(Text, { className: "pf-v5-u-mb-md pf-v5-u-mt-0 pf-v5-u-mr-md", component: TextVariants.p, children: t("effectiveMessageBundlesDescription") }) }) }), _jsx(FlexItem, { children: _jsx(DropdownPanel, { buttonText: t("searchForEffectiveMessageBundles"), setSearchDropdownOpen: setSearchDropdownOpen, searchDropdownOpen: searchDropdownOpen, marginRight: "2.5rem", width: "15vw", children: _jsxs(Form, { isHorizontal: true, className: "pf-v5-u-w-25vw", "data-testid": "effectiveMessageBundlesSearchForm", onSubmit: (e) => e.preventDefault(), children: [_jsx(FormGroup, { label: t("theme"), fieldId: "kc-theme", isRequired: true, children: _jsx(Controller, { name: "theme", control: control, defaultValue: "", rules: {
validate: (value) => (value || "").length > 0,
}, render: ({ field }) => (_jsx(KeycloakSelect, { "data-testid": "effective_message_bundles-theme-searchField", chipGroupProps: {
numChips: 1,
expandedText: t("hide"),
collapsedText: t("showRemaining"),
}, variant: SelectVariant.single, typeAheadAriaLabel: "Select", onToggle: (val) => setSelectThemesOpen(val), selections: field.value, onSelect: (selectedValue) => {
field.onChange(selectedValue.toString());
setSelectThemesOpen(false);
}, onClear: () => {
field.onChange("");
}, isOpen: selectThemesOpen, "aria-label": t("selectTheme"), chipGroupComponent: _jsx(ChipGroup, { children: _jsx(Chip, { onClick: (theme) => {
theme.stopPropagation();
field.onChange("");
}, children: field.value }, field.value) }), children: [
_jsx(SelectOption, { label: t("selectTheme"), isDisabled: true, children: t("selectTheme") }, "theme_placeholder"),
].concat(themeNames.map((option) => (_jsx(SelectOption, { value: option, children: option }, option)))) })) }) }), _jsx(FormGroup, { label: t("themeType"), fieldId: "kc-themeType", isRequired: true, children: _jsx(Controller, { name: "themeType", control: control, defaultValue: "", rules: {
validate: (value) => (value || "").length > 0,
}, render: ({ field }) => (_jsx(KeycloakSelect, { "data-testid": "effective-message-bundles-feature-searchField", chipGroupProps: {
numChips: 1,
expandedText: t("hide"),
collapsedText: t("showRemaining"),
}, variant: SelectVariant.single, typeAheadAriaLabel: "Select", onToggle: (val) => setSelectThemeTypeOpen(val), selections: field.value, onSelect: (selectedValue) => {
field.onChange(selectedValue.toString());
setSelectThemeTypeOpen(false);
}, onClear: () => {
field.onChange("");
}, isOpen: selectThemeTypeOpen, "aria-label": t("selectThemeType"), chipGroupComponent: _jsx(ChipGroup, { children: _jsx(Chip, { onClick: (themeType) => {
themeType.stopPropagation();
field.onChange("");
}, children: field.value }, field.value) }), children: [
_jsx(SelectOption, { label: t("selectThemeType"), isDisabled: true, children: t("selectThemeType") }, "themeType_placeholder"),
].concat(themeTypes.map((option) => (_jsx(SelectOption, { value: option, children: option }, option)))) })) }) }), _jsx(FormGroup, { label: t("language"), fieldId: "kc-language", isRequired: true, children: _jsx(Controller, { name: "locale", control: control, defaultValue: "", rules: {
validate: (value) => (value || "").length > 0,
}, render: ({ field }) => (_jsx(KeycloakSelect, { "data-testid": "effective-message-bundles-language-searchField", chipGroupProps: {
numChips: 1,
expandedText: t("hide"),
collapsedText: t("showRemaining"),
}, variant: SelectVariant.single, typeAheadAriaLabel: "Select", onToggle: (val) => setSelectLanguageOpen(val), selections: field.value, onSelect: (selectedValue) => {
field.onChange(selectedValue.toString());
setSelectLanguageOpen(false);
}, onClear: () => {
field.onChange("");
}, isOpen: selectLanguageOpen, "aria-label": t("selectLanguage"), chipGroupComponent: _jsx(ChipGroup, { children: field.value ? (_jsx(Chip, { onClick: (language) => {
language.stopPropagation();
field.onChange("");
}, children: localeToDisplayName(field.value, whoAmI.getLocale()) }, field.value)) : null }), children: [
_jsx(SelectOption, { label: t("selectLanguage"), isDisabled: true, children: t("selectLanguage") }, "language_placeholder"),
].concat(combinedLocales.map((option) => (_jsx(SelectOption, { value: option, children: localeToDisplayName(option, whoAmI.getLocale()) }, option)))) })) }) }), _jsx(FormGroup, { label: t("hasWords"), fieldId: "kc-hasWords", children: _jsx(Controller, { name: "hasWords", control: control, render: ({ field }) => (_jsxs("div", { children: [_jsx(TextInput, { id: "kc-hasWords", "data-testid": "effective-message-bundles-hasWords-searchField", value: field.value.join(" "), onChange: (e) => {
const target = e.target;
const input = target.value;
if (input.trim().length === 0) {
field.onChange([]);
}
else {
const words = input
.split(" ")
.map((word) => word.trim());
field.onChange(words);
}
} }), _jsx(ChipGroup, { children: field.value.map((word, index) => (_jsx(Chip, { onClick: (e) => {
e.stopPropagation();
const newWords = field.value.filter((_, i) => i !== index);
field.onChange(newWords);
}, children: word }, index))) })] })) }) }), _jsxs(ActionGroup, { className: "pf-v5-u-mt-sm", children: [_jsx(Button, { variant: "primary", onClick: () => {
setSearchPerformed(true);
submitSearch();
}, "data-testid": "search-effective-message-bundles-btn", isDisabled: !isValid, children: t("search") }), _jsx(Button, { variant: "secondary", onClick: resetSearch, "data-testid": "reset-search-effective-message-bundles-btn", isDisabled: !isDirty, children: t("reset") })] })] }) }) }), _jsx(FlexItem, { children: Object.entries(activeFilters).length > 0 && (_jsx(_Fragment, { children: Object.entries(activeFilters).map((filter) => {
var _a;
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: key === "locale"
? (_a = localeToDisplayName(value, whoAmI.getLocale())) === null || _a === void 0 ? void 0 : _a.toLowerCase()
: value })) : (value.map((entry) => (_jsx(Chip, { onClick: () => removeFilterValue(key, entry), children: entry }, entry)))) }, key));
}) })) })] }));
};
if (!searchPerformed) {
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "pf-v5-u-py-lg pf-v5-u-pl-md", children: effectiveMessageBunldesSearchFormDisplay() }), _jsx(Divider, {}), _jsx(ListEmptyState, { message: t("emptyEffectiveMessageBundles"), instructions: t("emptyEffectiveMessageBundlesInstructions"), isSearchVariant: true })] }));
}
return (_jsx(KeycloakDataTable, { loader: loader, ariaLabelKey: "effectiveMessageBundles", toolbarItem: effectiveMessageBunldesSearchFormDisplay(), columns: [
{
name: "key",
displayKey: "key",
},
{
name: "value",
displayKey: "value",
},
], emptyState: _jsx(ListEmptyState, { message: t("noSearchResults"), instructions: t("noSearchResultsInstructions") }), isSearching: Object.keys(activeFilters).length > 0 }, key));
};
//# sourceMappingURL=EffectiveMessageBundles.js.map