Localization: Realm Overrides Fixes (#26169)
* resolved conflict Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com> * improvements Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com> * improved tests Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com> * feedback Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com> * test fix Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com> * test fix Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com> * resolved conflict Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com> * fixed test Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com> --------- Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com> Co-authored-by: Agnieszka Gancarczyk <agancarc@redhat.com>
This commit is contained in:
parent
e83cc12664
commit
33651c396c
7 changed files with 43 additions and 675 deletions
|
@ -206,7 +206,8 @@ describe("Realm settings tabs tests", () => {
|
|||
.contains("td", "123")
|
||||
.should("be.visible");
|
||||
|
||||
cy.findByTestId("realmOverrides-deleteKebabToggle").click();
|
||||
cy.get('td.pf-c-table__action button[aria-label="Actions"]').click();
|
||||
cy.contains("button", "Delete").click();
|
||||
cy.findByTestId("confirm").click();
|
||||
masthead.checkNotificationMessage(
|
||||
"Successfully removed message(s) from the bundle.",
|
||||
|
@ -253,7 +254,8 @@ describe("Realm settings tabs tests", () => {
|
|||
.contains("td", "def")
|
||||
.should("be.visible");
|
||||
|
||||
cy.findByTestId("realmOverrides-deleteKebabToggle").click();
|
||||
cy.get('td.pf-c-table__action button[aria-label="Actions"]').click();
|
||||
cy.contains("button", "Delete").click();
|
||||
cy.findByTestId("confirm").click();
|
||||
|
||||
masthead.checkNotificationMessage(
|
||||
|
@ -343,7 +345,7 @@ describe("Realm settings tabs tests", () => {
|
|||
it("Check a11y violations on localization realm overrides sub tab/ adding message bundle", () => {
|
||||
realmSettingsPage.goToLocalizationTab();
|
||||
realmSettingsPage.goToLocalizationRealmOverridesSubTab();
|
||||
cy.findByTestId("add-bundle-button").click();
|
||||
cy.findByTestId("add-translationBtn").click();
|
||||
cy.checkA11y();
|
||||
modalUtils.cancelModal();
|
||||
});
|
||||
|
|
|
@ -95,7 +95,7 @@ export default class RealmSettingsPage extends CommonPage {
|
|||
testConnectionButton = "test-connection-button";
|
||||
modalTestConnectionButton = "modal-test-connection-button";
|
||||
emailAddressInput = "email-address-input";
|
||||
addBundleButton = "add-bundle-button";
|
||||
addBundleButton = "add-translationBtn";
|
||||
confirmAddBundle = "add-bundle-confirm-button";
|
||||
keyInput = "key-input";
|
||||
valueInput = "value-input";
|
||||
|
|
|
@ -964,7 +964,7 @@ deleteMappingConfirm=Are you sure you want to delete this mapping?
|
|||
createClientProfileSuccess=New client profile created
|
||||
eventTypes.CLIENT_LOGIN_ERROR.description=Client login error
|
||||
explainBearerOnly=This is a special OIDC type. This client only allows bearer token requests and cannot participate in browser logins.
|
||||
noMessageBundlesInstructions=Add a message bundle to get started.
|
||||
noTranslationsInstructions=Add a translation to get started.
|
||||
clearFile=Clear this file
|
||||
allowCreate=Allow create
|
||||
providerUpdatedError=Could not update client policy due to {{error}}
|
||||
|
@ -2632,7 +2632,7 @@ minus=Minus
|
|||
groupsHelp=Groups where the user has membership. To leave a group, select it and click Leave.
|
||||
includeGroupsAndRoles=Include groups and roles
|
||||
groupsPermissionsHint=Determines if fine grained permissions are enabled for managing this role. Disabling will delete all current permissions that have been set up.
|
||||
searchForMessageBundle=Search for message bundle
|
||||
searchForTranslation=Search for translation
|
||||
offlineSessionMaxHelp=Max time before an offline session is expired regardless of activity.
|
||||
resourceSaveError=Could not persist resource due to {{error}}
|
||||
clientsClientScopesHelp=The scopes associated with this resource.
|
||||
|
@ -2673,7 +2673,7 @@ policyType.totp=Time based
|
|||
addAttribute=Add {{label}}
|
||||
clientScopeSearch.protocol=Protocol
|
||||
initialAccessTokenDetails=Initial access token details
|
||||
noMessageBundles=No message bundles
|
||||
noTranslations=No translations
|
||||
deleteProvider=Delete provider?
|
||||
inputTypeSize=Input size
|
||||
createAttributeSubTitle=Create a new attribute
|
||||
|
@ -2984,3 +2984,5 @@ joinCommunity=Join community
|
|||
readBlog=Read blog
|
||||
customValue=Custom value
|
||||
termsAndConditionsUserAttribute=Terms and conditions accepted timestamp
|
||||
realmOverridesDescription= Realm overrides allow you to specify translations that will take effect for the entire realm. These translations will override any translation specified by a theme.
|
||||
addTranslation=Add translation
|
|
@ -951,7 +951,7 @@ deleteMappingConfirm=Czy na pewno chcesz usunąć to odwzorowanie?
|
|||
createClientProfileSuccess=Utworzono nowy profil klienta
|
||||
eventTypes.CLIENT_LOGIN_ERROR.description=Błąd logowania klienta
|
||||
explainBearerOnly=To jest specjalny typ OIDC. Ten klient pozwala tylko na żądania tokenów dostępu i nie może uczestniczyć w logowaniach przeglądarki.
|
||||
noMessageBundlesInstructions=Dodaj pakiet wiadomości, aby rozpocząć.
|
||||
noTranslationsInstructions=Dodaj tłumaczenie, aby rozpocząć.
|
||||
clearFile=Wyczyść ten plik
|
||||
allowCreate=Zezwól na tworzenie
|
||||
providerUpdatedError=Nie można zaktualizować polityki klienta z powodu błędu: {{error}}
|
||||
|
@ -2644,7 +2644,7 @@ policyType.totp=Oparte na czasie
|
|||
addAttribute=Dodaj atrybut
|
||||
clientScopeSearch.protocol=Protokół
|
||||
initialAccessTokenDetails=Informacje o tokenie początkowym dostępu
|
||||
noMessageBundles=Brak pakietów wiadomości
|
||||
noTranslations=Brak tłuamczeń
|
||||
deleteProvider=Usunąć dostawcę?
|
||||
inputTypeSize=Rozmiar pola wejściowego
|
||||
createAttributeSubTitle=Utwórz nowy atrybut
|
||||
|
@ -2916,3 +2916,4 @@ invalidEmailMessage='{{0}}': Nieprawidłowy adres e-mail.
|
|||
missingLastNameMessage='{{0}}': Proszę podać nazwisko.
|
||||
missingEmailMessage='{{0}}': Proszę podać adres e-mail.
|
||||
missingPasswordMessage='{{0}}': Proszę podać hasło.
|
||||
addTranslation=Dodaj tłumaczenie
|
||||
|
|
|
@ -1,625 +0,0 @@
|
|||
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
||||
import {
|
||||
ActionGroup,
|
||||
AlertVariant,
|
||||
Button,
|
||||
Divider,
|
||||
FormGroup,
|
||||
PageSection,
|
||||
Select,
|
||||
SelectGroup,
|
||||
SelectOption,
|
||||
SelectVariant,
|
||||
Switch,
|
||||
TextContent,
|
||||
ToolbarItem,
|
||||
} from "@patternfly/react-core";
|
||||
import { SearchIcon } from "@patternfly/react-icons";
|
||||
import {
|
||||
EditableTextCell,
|
||||
IEditableTextCell,
|
||||
IRow,
|
||||
IRowCell,
|
||||
RowEditType,
|
||||
RowErrors,
|
||||
Table,
|
||||
TableBody,
|
||||
TableHeader,
|
||||
TableVariant,
|
||||
applyCellEdits,
|
||||
cancelCellEdits,
|
||||
validateCellEdits,
|
||||
} from "@patternfly/react-table";
|
||||
import { cloneDeep, isEqual, uniqWith } from "lodash-es";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Controller, useForm, useWatch } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FormPanel, HelpItem } from "ui-shared";
|
||||
import { adminClient } from "../admin-client";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import { FormAccess } from "../components/form/FormAccess";
|
||||
import type { KeyValueType } from "../components/key-value-form/key-value-convert";
|
||||
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
|
||||
import { PaginatingTableToolbar } from "../components/table-toolbar/PaginatingTableToolbar";
|
||||
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 { convertToFormValues, localeToDisplayName } from "../util";
|
||||
import { useFetch } from "../utils/useFetch";
|
||||
import useLocaleSort, { mapByKey } from "../utils/useLocaleSort";
|
||||
import { AddMessageBundleModal } from "./AddMessageBundleModal";
|
||||
|
||||
type LocalizationTabProps = {
|
||||
save: (realm: RealmRepresentation) => void;
|
||||
refresh: () => void;
|
||||
realm: RealmRepresentation;
|
||||
};
|
||||
|
||||
type LocaleSpecificEntry = {
|
||||
key: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export enum RowEditAction {
|
||||
Save = "save",
|
||||
Cancel = "cancel",
|
||||
Edit = "edit",
|
||||
Delete = "delete",
|
||||
}
|
||||
|
||||
export type BundleForm = {
|
||||
key: string;
|
||||
value: string;
|
||||
messageBundle: KeyValueType;
|
||||
};
|
||||
|
||||
export const LocalizationTab = ({ save, realm }: LocalizationTabProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [addMessageBundleModalOpen, setAddMessageBundleModalOpen] =
|
||||
useState(false);
|
||||
|
||||
const [supportedLocalesOpen, setSupportedLocalesOpen] = useState(false);
|
||||
const [defaultLocaleOpen, setDefaultLocaleOpen] = useState(false);
|
||||
const [filterDropdownOpen, setFilterDropdownOpen] = useState(false);
|
||||
const [selectMenuLocale, setSelectMenuLocale] = useState(DEFAULT_LOCALE);
|
||||
|
||||
const { setValue, getValues, control, handleSubmit, formState } = useForm();
|
||||
const [selectMenuValueSelected, setSelectMenuValueSelected] = useState(false);
|
||||
const [messageBundles, setMessageBundles] = useState<LocaleSpecificEntry[]>(
|
||||
[],
|
||||
);
|
||||
const [tableRows, setTableRows] = useState<IRow[]>([]);
|
||||
|
||||
const themeTypes = useServerInfo().themes!;
|
||||
const allLocales = useMemo(() => {
|
||||
const locales = Object.values(themeTypes).flatMap((theme) =>
|
||||
theme.flatMap(({ locales }) => (locales ? locales : [])),
|
||||
);
|
||||
return Array.from(new Set(locales));
|
||||
}, [themeTypes]);
|
||||
const bundleForm = useForm<BundleForm>({ mode: "onChange" });
|
||||
const { addAlert, addError } = useAlerts();
|
||||
const { realm: currentRealm } = useRealm();
|
||||
const { whoAmI } = useWhoAmI();
|
||||
const localeSort = useLocaleSort();
|
||||
|
||||
const defaultSupportedLocales = realm.supportedLocales?.length
|
||||
? realm.supportedLocales
|
||||
: [DEFAULT_LOCALE];
|
||||
|
||||
const setupForm = () => {
|
||||
convertToFormValues(realm, setValue);
|
||||
setValue("supportedLocales", defaultSupportedLocales);
|
||||
};
|
||||
|
||||
useEffect(setupForm, []);
|
||||
|
||||
const watchSupportedLocales: string[] = useWatch({
|
||||
control,
|
||||
name: "supportedLocales",
|
||||
defaultValue: defaultSupportedLocales,
|
||||
});
|
||||
const internationalizationEnabled = useWatch({
|
||||
control,
|
||||
name: "internationalizationEnabled",
|
||||
defaultValue: realm.internationalizationEnabled,
|
||||
});
|
||||
|
||||
const [tableKey, setTableKey] = useState(0);
|
||||
const [max, setMax] = useState(10);
|
||||
const [first, setFirst] = useState(0);
|
||||
const [filter, setFilter] = useState("");
|
||||
|
||||
const refreshTable = () => {
|
||||
setTableKey(tableKey + 1);
|
||||
};
|
||||
|
||||
useFetch(
|
||||
async () => {
|
||||
let result = await adminClient.realms
|
||||
.getRealmLocalizationTexts({
|
||||
first,
|
||||
max,
|
||||
realm: realm.realm!,
|
||||
selectedLocale:
|
||||
selectMenuLocale ||
|
||||
getValues("defaultLocale") ||
|
||||
whoAmI.getLocale(),
|
||||
})
|
||||
// prevents server error in dev mode due to snowpack
|
||||
.catch(() => []);
|
||||
|
||||
const searchInBundles = (idx: number) => {
|
||||
return Object.entries(result).filter((i) => i[idx].includes(filter));
|
||||
};
|
||||
|
||||
if (filter) {
|
||||
const filtered = uniqWith(
|
||||
searchInBundles(0).concat(searchInBundles(1)),
|
||||
isEqual,
|
||||
);
|
||||
|
||||
result = Object.fromEntries(filtered);
|
||||
}
|
||||
|
||||
return { result };
|
||||
},
|
||||
({ result }) => {
|
||||
const bundles = localeSort(
|
||||
Object.entries(result).map<LocaleSpecificEntry>(([key, value]) => ({
|
||||
key,
|
||||
value,
|
||||
})),
|
||||
mapByKey("key"),
|
||||
).slice(first, first + max + 1);
|
||||
|
||||
setMessageBundles(bundles);
|
||||
|
||||
const updatedRows = bundles.map<IRow>((messageBundle) => ({
|
||||
rowEditBtnAriaLabel: () =>
|
||||
t("rowEditBtnAriaLabel", {
|
||||
messageBundle: messageBundle.value,
|
||||
}),
|
||||
rowSaveBtnAriaLabel: () =>
|
||||
t("rowSaveBtnAriaLabel", {
|
||||
messageBundle: messageBundle.value,
|
||||
}),
|
||||
rowCancelBtnAriaLabel: () =>
|
||||
t("rowCancelBtnAriaLabel", {
|
||||
messageBundle: messageBundle.value,
|
||||
}),
|
||||
cells: [
|
||||
{
|
||||
title: (value, rowIndex, cellIndex, props) => (
|
||||
<EditableTextCell
|
||||
value={value!}
|
||||
rowIndex={rowIndex!}
|
||||
cellIndex={cellIndex!}
|
||||
props={props}
|
||||
isDisabled
|
||||
handleTextInputChange={handleTextInputChange}
|
||||
inputAriaLabel={messageBundle.key}
|
||||
/>
|
||||
),
|
||||
props: {
|
||||
value: messageBundle.key,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: (value, rowIndex, cellIndex, props) => (
|
||||
<EditableTextCell
|
||||
value={value!}
|
||||
rowIndex={rowIndex!}
|
||||
cellIndex={cellIndex!}
|
||||
props={props}
|
||||
handleTextInputChange={handleTextInputChange}
|
||||
inputAriaLabel={messageBundle.value}
|
||||
/>
|
||||
),
|
||||
props: {
|
||||
value: messageBundle.value,
|
||||
},
|
||||
},
|
||||
],
|
||||
}));
|
||||
setTableRows(updatedRows);
|
||||
|
||||
return bundles;
|
||||
},
|
||||
[tableKey, filter, first, max],
|
||||
);
|
||||
|
||||
const handleTextInputChange = (
|
||||
newValue: string,
|
||||
evt: any,
|
||||
rowIndex: number,
|
||||
cellIndex: number,
|
||||
) => {
|
||||
setTableRows((prev) => {
|
||||
const newRows = cloneDeep(prev);
|
||||
const textCell = newRows[rowIndex]?.cells?.[
|
||||
cellIndex
|
||||
] as IEditableTextCell;
|
||||
textCell.props.editableValue = newValue;
|
||||
return newRows;
|
||||
});
|
||||
};
|
||||
|
||||
const updateEditableRows = async (
|
||||
type: RowEditType,
|
||||
rowIndex?: number,
|
||||
validationErrors?: RowErrors,
|
||||
) => {
|
||||
if (rowIndex === undefined) {
|
||||
return;
|
||||
}
|
||||
const newRows = cloneDeep(tableRows);
|
||||
let newRow: IRow;
|
||||
const invalid =
|
||||
!!validationErrors && Object.keys(validationErrors).length > 0;
|
||||
|
||||
if (invalid) {
|
||||
newRow = validateCellEdits(newRows[rowIndex], type, validationErrors);
|
||||
} else if (type === RowEditAction.Cancel) {
|
||||
newRow = cancelCellEdits(newRows[rowIndex]);
|
||||
} else {
|
||||
newRow = applyCellEdits(newRows[rowIndex], type);
|
||||
}
|
||||
newRows[rowIndex] = newRow;
|
||||
|
||||
// Update the copy of the retrieved data set so we can save it when the user saves changes
|
||||
|
||||
if (!invalid && type === RowEditAction.Save) {
|
||||
const key = (newRow.cells?.[0] as IRowCell).props.value;
|
||||
const value = (newRow.cells?.[1] as IRowCell).props.value;
|
||||
|
||||
// We only have one editable value, otherwise we'd need to save each
|
||||
try {
|
||||
await adminClient.realms.addLocalization(
|
||||
{
|
||||
realm: realm.realm!,
|
||||
selectedLocale:
|
||||
selectMenuLocale || getValues("defaultLocale") || DEFAULT_LOCALE,
|
||||
key,
|
||||
},
|
||||
value,
|
||||
);
|
||||
addAlert(t("updateMessageBundleSuccess"), AlertVariant.success);
|
||||
} catch (error) {
|
||||
addAlert(t("updateMessageBundleError"), AlertVariant.danger);
|
||||
}
|
||||
}
|
||||
setTableRows(newRows);
|
||||
};
|
||||
|
||||
const handleModalToggle = () => {
|
||||
setAddMessageBundleModalOpen(!addMessageBundleModalOpen);
|
||||
};
|
||||
|
||||
const options = [
|
||||
<SelectGroup label={t("defaultLocale")} key="group1">
|
||||
<SelectOption key={DEFAULT_LOCALE} value={DEFAULT_LOCALE}>
|
||||
{localeToDisplayName(DEFAULT_LOCALE, whoAmI.getLocale())}
|
||||
</SelectOption>
|
||||
</SelectGroup>,
|
||||
<Divider key="divider" />,
|
||||
<SelectGroup label={t("supportedLocales")} key="group2">
|
||||
{watchSupportedLocales.map((locale) => (
|
||||
<SelectOption key={locale} value={locale}>
|
||||
{localeToDisplayName(locale, whoAmI.getLocale())}
|
||||
</SelectOption>
|
||||
))}
|
||||
</SelectGroup>,
|
||||
];
|
||||
|
||||
const addKeyValue = async (pair: KeyValueType): Promise<void> => {
|
||||
try {
|
||||
await adminClient.realms.addLocalization(
|
||||
{
|
||||
realm: currentRealm!,
|
||||
selectedLocale:
|
||||
selectMenuLocale || getValues("defaultLocale") || DEFAULT_LOCALE,
|
||||
key: pair.key,
|
||||
},
|
||||
pair.value,
|
||||
);
|
||||
|
||||
adminClient.setConfig({
|
||||
realmName: currentRealm!,
|
||||
});
|
||||
refreshTable();
|
||||
addAlert(t("addMessageBundleSuccess"), AlertVariant.success);
|
||||
} catch (error) {
|
||||
addError(t("addMessageBundleError"), error);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteKey = async (key: string) => {
|
||||
try {
|
||||
await adminClient.realms.deleteRealmLocalizationTexts({
|
||||
realm: currentRealm!,
|
||||
selectedLocale: selectMenuLocale,
|
||||
key,
|
||||
});
|
||||
refreshTable();
|
||||
addAlert(t("deleteMessageBundleSuccess"));
|
||||
} catch (error) {
|
||||
addError("deleteMessageBundleError", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{addMessageBundleModalOpen && (
|
||||
<AddMessageBundleModal
|
||||
handleModalToggle={handleModalToggle}
|
||||
save={(pair: any) => {
|
||||
addKeyValue(pair);
|
||||
handleModalToggle();
|
||||
}}
|
||||
form={bundleForm}
|
||||
/>
|
||||
)}
|
||||
<PageSection variant="light">
|
||||
<FormAccess
|
||||
isHorizontal
|
||||
role="manage-realm"
|
||||
className="pf-u-mt-lg"
|
||||
onSubmit={handleSubmit(save)}
|
||||
>
|
||||
<FormGroup
|
||||
label={t("internationalization")}
|
||||
fieldId="kc-internationalization"
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText={t("internationalizationHelp")}
|
||||
fieldLabelId="internationalization"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Controller
|
||||
name="internationalizationEnabled"
|
||||
control={control}
|
||||
defaultValue={realm.internationalizationEnabled}
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
id="kc-l-internationalization"
|
||||
label={t("enabled")}
|
||||
labelOff={t("disabled")}
|
||||
isChecked={field.value}
|
||||
data-testid={
|
||||
field.value
|
||||
? "internationalization-enabled"
|
||||
: "internationalization-disabled"
|
||||
}
|
||||
onChange={field.onChange}
|
||||
aria-label={t("internationalization")}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
{internationalizationEnabled && (
|
||||
<>
|
||||
<FormGroup
|
||||
label={t("supportedLocales")}
|
||||
fieldId="kc-l-supported-locales"
|
||||
>
|
||||
<Controller
|
||||
name="supportedLocales"
|
||||
control={control}
|
||||
defaultValue={defaultSupportedLocales}
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
toggleId="kc-l-supported-locales"
|
||||
onToggle={(open) => {
|
||||
setSupportedLocalesOpen(open);
|
||||
}}
|
||||
onSelect={(_, v) => {
|
||||
const option = v as string;
|
||||
if (field.value.includes(option)) {
|
||||
field.onChange(
|
||||
field.value.filter(
|
||||
(item: string) => item !== option,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
field.onChange([...field.value, option]);
|
||||
}
|
||||
}}
|
||||
onClear={() => {
|
||||
field.onChange([]);
|
||||
}}
|
||||
selections={field.value}
|
||||
variant={SelectVariant.typeaheadMulti}
|
||||
aria-label={t("supportedLocales")}
|
||||
isOpen={supportedLocalesOpen}
|
||||
placeholderText={t("selectLocales")}
|
||||
>
|
||||
{allLocales.map((locale) => (
|
||||
<SelectOption
|
||||
selected={field.value.includes(locale)}
|
||||
key={locale}
|
||||
value={locale}
|
||||
>
|
||||
{localeToDisplayName(locale, whoAmI.getLocale())}
|
||||
</SelectOption>
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("defaultLocale")}
|
||||
fieldId="kc-l-default-locale"
|
||||
>
|
||||
<Controller
|
||||
name="defaultLocale"
|
||||
control={control}
|
||||
defaultValue={DEFAULT_LOCALE}
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
toggleId="kc-default-locale"
|
||||
onToggle={() => setDefaultLocaleOpen(!defaultLocaleOpen)}
|
||||
onSelect={(_, value) => {
|
||||
field.onChange(value as string);
|
||||
setDefaultLocaleOpen(false);
|
||||
}}
|
||||
selections={
|
||||
field.value
|
||||
? localeToDisplayName(field.value, whoAmI.getLocale())
|
||||
: realm.defaultLocale !== ""
|
||||
? localeToDisplayName(
|
||||
realm.defaultLocale || DEFAULT_LOCALE,
|
||||
whoAmI.getLocale(),
|
||||
)
|
||||
: t("placeholderText")
|
||||
}
|
||||
variant={SelectVariant.single}
|
||||
aria-label={t("defaultLocale")}
|
||||
isOpen={defaultLocaleOpen}
|
||||
placeholderText={t("placeholderText")}
|
||||
data-testid="select-default-locale"
|
||||
>
|
||||
{watchSupportedLocales.map((locale, idx) => (
|
||||
<SelectOption
|
||||
key={`default-locale-${idx}`}
|
||||
value={locale}
|
||||
>
|
||||
{localeToDisplayName(locale, whoAmI.getLocale())}
|
||||
</SelectOption>
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
</>
|
||||
)}
|
||||
<ActionGroup>
|
||||
<Button
|
||||
variant="primary"
|
||||
isDisabled={!formState.isDirty}
|
||||
type="submit"
|
||||
data-testid="localization-tab-save"
|
||||
>
|
||||
{t("save")}
|
||||
</Button>
|
||||
<Button variant="link" onClick={setupForm}>
|
||||
{t("revert")}
|
||||
</Button>
|
||||
</ActionGroup>
|
||||
</FormAccess>
|
||||
|
||||
<FormPanel className="kc-message-bundles" title="Edit message bundles">
|
||||
<TextContent className="messageBundleDescription">
|
||||
{t("messageBundleDescription")}
|
||||
</TextContent>
|
||||
<div className="tableBorder">
|
||||
<PaginatingTableToolbar
|
||||
count={messageBundles.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("searchForMessageBundle")}
|
||||
toolbarItem={
|
||||
<Button
|
||||
data-testid="add-bundle-button"
|
||||
onClick={() => setAddMessageBundleModalOpen(true)}
|
||||
>
|
||||
{t("addMessageBundle")}
|
||||
</Button>
|
||||
}
|
||||
searchTypeComponent={
|
||||
<ToolbarItem>
|
||||
<Select
|
||||
width={180}
|
||||
data-testid="filter-by-locale-select"
|
||||
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")
|
||||
}
|
||||
>
|
||||
{options}
|
||||
</Select>
|
||||
</ToolbarItem>
|
||||
}
|
||||
>
|
||||
{messageBundles.length === 0 && !filter && (
|
||||
<ListEmptyState
|
||||
hasIcon
|
||||
message={t("noMessageBundles")}
|
||||
instructions={t("noMessageBundlesInstructions")}
|
||||
onPrimaryAction={handleModalToggle}
|
||||
/>
|
||||
)}
|
||||
{messageBundles.length === 0 && filter && (
|
||||
<ListEmptyState
|
||||
hasIcon
|
||||
icon={SearchIcon}
|
||||
isSearchVariant
|
||||
message={t("noSearchResults")}
|
||||
instructions={t("noSearchResultsInstructions")}
|
||||
/>
|
||||
)}
|
||||
{messageBundles.length !== 0 && (
|
||||
<Table
|
||||
aria-label={t("editableRowsTable")}
|
||||
data-testid="editable-rows-table"
|
||||
variant={TableVariant.compact}
|
||||
cells={[t("key"), t("value")]}
|
||||
rows={tableRows}
|
||||
onRowEdit={(_, type, _b, rowIndex, validation) =>
|
||||
updateEditableRows(type, rowIndex, validation)
|
||||
}
|
||||
actions={[
|
||||
{
|
||||
title: t("delete"),
|
||||
onClick: (_, row) =>
|
||||
deleteKey(
|
||||
(tableRows[row].cells?.[0] as IRowCell).props.value,
|
||||
),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<TableHeader />
|
||||
<TableBody />
|
||||
</Table>
|
||||
)}
|
||||
</PaginatingTableToolbar>
|
||||
</div>
|
||||
</FormPanel>
|
||||
</PageSection>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -231,17 +231,7 @@ export const LocalizationTab = ({ save, realm }: LocalizationTabProps) => {
|
|||
<Tab
|
||||
id="realm-overrides"
|
||||
eventKey={1}
|
||||
title={
|
||||
<TabTitleText>
|
||||
{t("realmOverrides")}{" "}
|
||||
<HelpItem
|
||||
fieldLabelId="realm-overrides"
|
||||
helpText={t("realmOverridesHelp")}
|
||||
noVerticalAlign={false}
|
||||
unWrap
|
||||
/>
|
||||
</TabTitleText>
|
||||
}
|
||||
title={<TabTitleText>{t("realmOverrides")} </TabTitleText>}
|
||||
data-testid="rs-localization-realm-overrides-tab"
|
||||
>
|
||||
<RealmOverrides
|
||||
|
@ -253,17 +243,7 @@ export const LocalizationTab = ({ save, realm }: LocalizationTabProps) => {
|
|||
<Tab
|
||||
id="effective-message-bundles"
|
||||
eventKey={2}
|
||||
title={
|
||||
<TabTitleText>
|
||||
{t("effectiveMessageBundles")}
|
||||
<HelpItem
|
||||
fieldLabelId="effective-message-bundles"
|
||||
helpText={t("effectiveMessageBundlesHelp")}
|
||||
noVerticalAlign={false}
|
||||
unWrap
|
||||
/>
|
||||
</TabTitleText>
|
||||
}
|
||||
title={<TabTitleText>{t("effectiveMessageBundles")}</TabTitleText>}
|
||||
data-testid="rs-localization-effective-message-bundles-tab"
|
||||
>
|
||||
<EffectiveMessageBundles
|
||||
|
|
|
@ -12,7 +12,10 @@ import {
|
|||
SelectGroup,
|
||||
SelectOption,
|
||||
SelectVariant,
|
||||
TextContent,
|
||||
Text,
|
||||
ToolbarItem,
|
||||
TextVariants,
|
||||
} from "@patternfly/react-core";
|
||||
import {
|
||||
CheckIcon,
|
||||
|
@ -21,6 +24,7 @@ import {
|
|||
TimesIcon,
|
||||
} from "@patternfly/react-icons";
|
||||
import {
|
||||
ActionsColumn,
|
||||
IRow,
|
||||
IRowCell,
|
||||
Table,
|
||||
|
@ -323,6 +327,11 @@ export const RealmOverrides = ({
|
|||
form={bundleForm}
|
||||
/>
|
||||
)}
|
||||
<TextContent>
|
||||
<Text className="pf-u-mt-lg pf-u-ml-md" component={TextVariants.p}>
|
||||
{t("realmOverridesDescription")}
|
||||
</Text>
|
||||
</TextContent>
|
||||
<PaginatingTableToolbar
|
||||
count={messageBundles.length}
|
||||
first={first}
|
||||
|
@ -339,14 +348,14 @@ export const RealmOverrides = ({
|
|||
setFirst(0);
|
||||
setMax(10);
|
||||
}}
|
||||
inputGroupPlaceholder={t("searchForMessageBundle")}
|
||||
inputGroupPlaceholder={t("searchForTranslation")}
|
||||
toolbarItem={
|
||||
<>
|
||||
<Button
|
||||
data-testid="add-bundle-button"
|
||||
data-testid="add-translationBtn"
|
||||
onClick={() => setAddMessageBundleModalOpen(true)}
|
||||
>
|
||||
{t("addMessageBundle")}
|
||||
{t("addTranslation")}
|
||||
</Button>
|
||||
<ToolbarItem>
|
||||
<Dropdown
|
||||
|
@ -408,8 +417,8 @@ export const RealmOverrides = ({
|
|||
{messageBundles.length === 0 && !filter && (
|
||||
<ListEmptyState
|
||||
hasIcon
|
||||
message={t("noMessageBundles")}
|
||||
instructions={t("noMessageBundlesInstructions")}
|
||||
message={t("noTranslations")}
|
||||
instructions={t("noTranslationsInstructions")}
|
||||
onPrimaryAction={handleModalToggle}
|
||||
/>
|
||||
)}
|
||||
|
@ -544,20 +553,19 @@ export const RealmOverrides = ({
|
|||
</Form>
|
||||
</Td>
|
||||
<Td isActionCell>
|
||||
<Dropdown
|
||||
toggle={
|
||||
<KebabToggle
|
||||
className="pf-m-plain"
|
||||
data-testid="realmOverrides-deleteKebabToggle"
|
||||
/>
|
||||
}
|
||||
onClick={() => {
|
||||
setSelectedRowKeys([
|
||||
(row.cells?.[0] as IRowCell).props.value,
|
||||
]);
|
||||
toggleDeleteDialog();
|
||||
setKebabOpen(false);
|
||||
}}
|
||||
<ActionsColumn
|
||||
items={[
|
||||
{
|
||||
title: t("delete"),
|
||||
onClick: () => {
|
||||
setSelectedRowKeys([
|
||||
(row.cells?.[0] as IRowCell).props.value,
|
||||
]);
|
||||
toggleDeleteDialog();
|
||||
setKebabOpen(false);
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
|
|
Loading…
Reference in a new issue