Merge pull request #585 from jenny-s51/rsKeyProviders
Realm settings(keys): add providers sub-tab
This commit is contained in:
commit
0c6e1620fd
7 changed files with 341 additions and 209 deletions
|
@ -47,6 +47,7 @@ type DataTableProps<T> = {
|
|||
onSelect?: (isSelected: boolean, rowIndex: number) => void;
|
||||
onCollapse?: (isOpen: boolean, rowIndex: number) => void;
|
||||
canSelectAll: boolean;
|
||||
isNotCompact?: boolean;
|
||||
};
|
||||
|
||||
function DataTable<T>({
|
||||
|
@ -58,13 +59,14 @@ function DataTable<T>({
|
|||
onSelect,
|
||||
onCollapse,
|
||||
canSelectAll,
|
||||
isNotCompact,
|
||||
...props
|
||||
}: DataTableProps<T>) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Table
|
||||
{...props}
|
||||
variant={TableVariant.compact}
|
||||
variant={isNotCompact ? undefined : TableVariant.compact}
|
||||
onSelect={
|
||||
onSelect
|
||||
? (_, isSelected, rowIndex) => onSelect(isSelected, rowIndex)
|
||||
|
@ -124,6 +126,7 @@ export type DataListProps<T> = {
|
|||
toolbarItem?: ReactNode;
|
||||
emptyState?: ReactNode;
|
||||
icon?: React.ComponentClass<SVGIconProps>;
|
||||
isNotCompact?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -155,6 +158,7 @@ export function KeycloakDataTable<T>({
|
|||
isPaginated = false,
|
||||
onSelect,
|
||||
canSelectAll = false,
|
||||
isNotCompact,
|
||||
detailColumns,
|
||||
isRowDisabled,
|
||||
loader,
|
||||
|
@ -383,6 +387,7 @@ export function KeycloakDataTable<T>({
|
|||
actionResolver={actionResolver}
|
||||
rows={filteredData || rows}
|
||||
columns={columns}
|
||||
isNotCompact={isNotCompact}
|
||||
ariaLabelKey={ariaLabelKey}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -14,6 +14,7 @@ import { cellWidth } from "@patternfly/react-table";
|
|||
|
||||
type KeyData = KeyMetadataRepresentation & {
|
||||
provider?: string;
|
||||
type?: string;
|
||||
};
|
||||
|
||||
type KeysTabInnerProps = {
|
||||
|
@ -62,30 +63,36 @@ export const KeysTabInner = ({ keys }: KeysTabInnerProps) => {
|
|||
return <>{provider}</>;
|
||||
};
|
||||
|
||||
const renderPublicKeyButton = (publicKey: string) => {
|
||||
return (
|
||||
<Button
|
||||
onClick={() => {
|
||||
togglePublicKeyDialog();
|
||||
setPublicKey(publicKey!);
|
||||
}}
|
||||
variant="secondary"
|
||||
id="kc-public-key"
|
||||
>
|
||||
{t("realm-settings:publicKeys").slice(0, -1)}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const ButtonRenderer = ({ provider, publicKey, certificate }: KeyData) => {
|
||||
if (provider === "ecdsa-generated") {
|
||||
return <>{renderPublicKeyButton(publicKey!)}</>;
|
||||
}
|
||||
if (provider === "rsa-generated" || provider === "fallback-RS256") {
|
||||
const ButtonRenderer = ({ type, publicKey, certificate }: KeyData) => {
|
||||
if (type === "EC") {
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
{renderPublicKeyButton(publicKey!)}
|
||||
<Button
|
||||
onClick={() => {
|
||||
togglePublicKeyDialog();
|
||||
setPublicKey(publicKey!);
|
||||
}}
|
||||
variant="secondary"
|
||||
id="kc-public-key"
|
||||
>
|
||||
{t("realm-settings:publicKeys").slice(0, -1)}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
} else if (type === "RSA") {
|
||||
return (
|
||||
<>
|
||||
<div className="button-wrapper">
|
||||
<Button
|
||||
onClick={() => {
|
||||
togglePublicKeyDialog();
|
||||
setPublicKey(publicKey!);
|
||||
}}
|
||||
variant="secondary"
|
||||
id="kc-rsa-public-key"
|
||||
>
|
||||
{t("realm-settings:publicKeys").slice(0, -1)}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
toggleCertificateDialog();
|
||||
|
@ -109,6 +116,7 @@ export const KeysTabInner = ({ keys }: KeysTabInnerProps) => {
|
|||
<CertificateDialog />
|
||||
<KeycloakDataTable
|
||||
key={key}
|
||||
isNotCompact={true}
|
||||
loader={loader}
|
||||
ariaLabelKey="realm-settings:keysList"
|
||||
searchPlaceholderKey="realm-settings:searchKey"
|
||||
|
|
247
src/realm-settings/KeysProvidersTab.tsx
Normal file
247
src/realm-settings/KeysProvidersTab.tsx
Normal file
|
@ -0,0 +1,247 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Button,
|
||||
ButtonVariant,
|
||||
DataList,
|
||||
DataListCell,
|
||||
DataListControl,
|
||||
DataListDragButton,
|
||||
DataListItem,
|
||||
DataListItemCells,
|
||||
DataListItemRow,
|
||||
Dropdown,
|
||||
DropdownToggle,
|
||||
InputGroup,
|
||||
PageSection,
|
||||
TextInput,
|
||||
Toolbar,
|
||||
ToolbarGroup,
|
||||
ToolbarItem,
|
||||
} from "@patternfly/react-core";
|
||||
import type { KeyMetadataRepresentation } from "keycloak-admin/lib/defs/keyMetadataRepresentation";
|
||||
import type ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
|
||||
|
||||
import "./RealmSettingsSection.css";
|
||||
import type ComponentTypeRepresentation from "keycloak-admin/lib/defs/componentTypeRepresentation";
|
||||
import { SearchIcon } from "@patternfly/react-icons";
|
||||
|
||||
type ComponentData = KeyMetadataRepresentation & {
|
||||
providerDescription?: string;
|
||||
name?: string;
|
||||
};
|
||||
|
||||
type KeysTabInnerProps = {
|
||||
components: ComponentData[];
|
||||
realmComponents: ComponentRepresentation[];
|
||||
keyProviderComponentTypes: ComponentTypeRepresentation[];
|
||||
};
|
||||
|
||||
export const KeysTabInner = ({ components }: KeysTabInnerProps) => {
|
||||
const { t } = useTranslation("roles");
|
||||
|
||||
const [id, setId] = useState("");
|
||||
const [searchVal, setSearchVal] = useState("");
|
||||
const [filteredComponents, setFilteredComponents] = useState<ComponentData[]>(
|
||||
[]
|
||||
);
|
||||
|
||||
const itemIds = components.map((item, idx) => "data" + idx);
|
||||
|
||||
const [itemOrder, setItemOrder] = useState<string[]>([]);
|
||||
|
||||
const [liveText, setLiveText] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
setItemOrder(["data", ...itemIds]);
|
||||
}, [components, searchVal]);
|
||||
|
||||
const onDragStart = (id: string) => {
|
||||
setLiveText(t("onDragStart", { id }));
|
||||
setId(id);
|
||||
};
|
||||
|
||||
const onDragMove = () => {
|
||||
setLiveText(t("onDragMove", { id }));
|
||||
};
|
||||
|
||||
const onDragCancel = () => {
|
||||
setLiveText(t("onDragCancel"));
|
||||
};
|
||||
|
||||
const onDragFinish = (itemOrder: string[]) => {
|
||||
setItemOrder(["data", ...itemOrder.filter((i) => i !== "data")]);
|
||||
setLiveText(t("onDragCancel"));
|
||||
};
|
||||
|
||||
const onSearch = () => {
|
||||
if (searchVal !== "") {
|
||||
setSearchVal(searchVal);
|
||||
const x = components.filter((v) => {
|
||||
return v.name?.includes(searchVal) || v.providerId?.includes(searchVal);
|
||||
});
|
||||
setFilteredComponents(x);
|
||||
} else {
|
||||
setSearchVal("");
|
||||
setFilteredComponents(components);
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: any) => {
|
||||
if (e.key === "Enter") {
|
||||
onSearch();
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputChange = (value: string) => {
|
||||
setSearchVal(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageSection variant="light" padding={{ default: "noPadding" }}>
|
||||
<Toolbar>
|
||||
<>
|
||||
<ToolbarGroup className="providers-toolbar">
|
||||
<ToolbarItem>
|
||||
<InputGroup>
|
||||
<TextInput
|
||||
name={"inputGroupName"}
|
||||
id={"inputGroupName"}
|
||||
type="search"
|
||||
aria-label={t("common:search")}
|
||||
placeholder={t("common:search")}
|
||||
onChange={handleInputChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
<Button
|
||||
variant={ButtonVariant.control}
|
||||
aria-label={t("common:search")}
|
||||
>
|
||||
<SearchIcon />
|
||||
</Button>
|
||||
</InputGroup>
|
||||
</ToolbarItem>
|
||||
<ToolbarItem>
|
||||
<Dropdown
|
||||
data-testid="addProviderDropdown"
|
||||
className="add-provider-dropdown"
|
||||
onSelect={() => {}}
|
||||
toggle={
|
||||
<DropdownToggle isPrimary>
|
||||
{t("realm-settings:addProvider")}
|
||||
</DropdownToggle>
|
||||
}
|
||||
/>
|
||||
</ToolbarItem>
|
||||
</ToolbarGroup>
|
||||
</>
|
||||
</Toolbar>
|
||||
|
||||
<DataList
|
||||
aria-label={t("groups")}
|
||||
onDragFinish={onDragFinish}
|
||||
onDragStart={onDragStart}
|
||||
onDragMove={onDragMove}
|
||||
onDragCancel={onDragCancel}
|
||||
itemOrder={itemOrder}
|
||||
isCompact
|
||||
>
|
||||
<DataListItem aria-labelledby={"aria"} id="data" key="data">
|
||||
<DataListItemRow className="test" data-testid={"data-list-row"}>
|
||||
<DataListDragButton
|
||||
className="header-drag-button"
|
||||
aria-label="Reorder"
|
||||
aria-labelledby="simple-item"
|
||||
aria-describedby="Press space or enter to begin dragging, and use the arrow keys to navigate up or down. Press enter to confirm the drag, or any other key to cancel the drag operation."
|
||||
aria-pressed="false"
|
||||
isDisabled
|
||||
/>
|
||||
<DataListItemCells
|
||||
className="test2"
|
||||
dataListCells={[
|
||||
<DataListCell className="name" key={"1"}>
|
||||
<>{t("realm-settings:name")}</>
|
||||
</DataListCell>,
|
||||
<DataListCell className="provider" key={"2"}>
|
||||
<>{t("realm-settings:provider")}</>
|
||||
</DataListCell>,
|
||||
<DataListCell className="provider-description" key={"3"}>
|
||||
<>{t("realm-settings:providerDescription")}</>
|
||||
</DataListCell>,
|
||||
]}
|
||||
/>
|
||||
</DataListItemRow>
|
||||
</DataListItem>
|
||||
{(filteredComponents.length === 0
|
||||
? components
|
||||
: filteredComponents
|
||||
).map((component, idx) => (
|
||||
<DataListItem
|
||||
draggable
|
||||
aria-labelledby={"aria"}
|
||||
key={`data${idx}`}
|
||||
id={`data${idx}`}
|
||||
>
|
||||
<DataListItemRow data-testid={"data-list-row"}>
|
||||
<DataListControl>
|
||||
<DataListDragButton
|
||||
className="row-drag-button"
|
||||
aria-label="Reorder"
|
||||
aria-labelledby="simple-item2"
|
||||
aria-describedby="Press space or enter to begin dragging, and use the arrow keys to navigate up or down. Press enter to confirm the drag, or any other key to cancel the drag operation."
|
||||
aria-pressed="false"
|
||||
/>
|
||||
</DataListControl>
|
||||
<DataListItemCells
|
||||
dataListCells={[
|
||||
<DataListCell key={"4"}>
|
||||
<>
|
||||
<Button variant="link">{component.providerId}</Button>
|
||||
</>
|
||||
</DataListCell>,
|
||||
<DataListCell key={"5"}>
|
||||
<>{component.name}</>
|
||||
</DataListCell>,
|
||||
<DataListCell key={"6"}>
|
||||
<>{component.providerDescription}</>
|
||||
</DataListCell>,
|
||||
]}
|
||||
/>
|
||||
</DataListItemRow>
|
||||
</DataListItem>
|
||||
))}
|
||||
</DataList>
|
||||
<div className="pf-screen-reader" aria-live="assertive">
|
||||
{liveText}
|
||||
</div>
|
||||
</PageSection>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
type KeysProps = {
|
||||
components: ComponentRepresentation[];
|
||||
realmComponents: ComponentRepresentation[];
|
||||
keyProviderComponentTypes: ComponentTypeRepresentation[];
|
||||
};
|
||||
|
||||
export const KeysProviderTab = ({
|
||||
components,
|
||||
keyProviderComponentTypes,
|
||||
...props
|
||||
}: KeysProps) => {
|
||||
return (
|
||||
<KeysTabInner
|
||||
components={components?.map((component) => {
|
||||
const provider = keyProviderComponentTypes.find(
|
||||
(componentType: ComponentTypeRepresentation) =>
|
||||
component.providerId === componentType.id
|
||||
);
|
||||
return { ...component, providerDescription: provider?.helpText };
|
||||
})}
|
||||
keyProviderComponentTypes={keyProviderComponentTypes}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -1,185 +0,0 @@
|
|||
import React, { useState } from "react";
|
||||
import { useHistory, useRouteMatch } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button, ButtonVariant, PageSection } from "@patternfly/react-core";
|
||||
import type { KeyMetadataRepresentation } from "keycloak-admin/lib/defs/keyMetadataRepresentation";
|
||||
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
|
||||
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
|
||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||
import { emptyFormatter } from "../util";
|
||||
import type ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
|
||||
|
||||
import "./RealmSettingsSection.css";
|
||||
import { cellWidth } from "@patternfly/react-table";
|
||||
|
||||
type KeyData = KeyMetadataRepresentation & {
|
||||
provider?: string;
|
||||
};
|
||||
|
||||
type KeysTabInnerProps = {
|
||||
keys: KeyData[];
|
||||
};
|
||||
|
||||
export const KeysTabInner = ({ keys }: KeysTabInnerProps) => {
|
||||
const { t } = useTranslation("roles");
|
||||
const history = useHistory();
|
||||
const { url } = useRouteMatch();
|
||||
const [key, setKey] = useState(0);
|
||||
const refresh = () => setKey(new Date().getTime());
|
||||
|
||||
const [publicKey, setPublicKey] = useState("");
|
||||
const [certificate, setCertificate] = useState("");
|
||||
|
||||
const loader = async () => {
|
||||
return keys;
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
refresh();
|
||||
}, [keys]);
|
||||
|
||||
const [togglePublicKeyDialog, PublicKeyDialog] = useConfirmDialog({
|
||||
titleKey: t("realm-settings:publicKeys").slice(0, -1),
|
||||
messageKey: publicKey,
|
||||
continueButtonLabel: "common:close",
|
||||
continueButtonVariant: ButtonVariant.primary,
|
||||
noCancelButton: true,
|
||||
onConfirm: async () => {},
|
||||
});
|
||||
|
||||
const [toggleCertificateDialog, CertificateDialog] = useConfirmDialog({
|
||||
titleKey: t("realm-settings:certificate"),
|
||||
messageKey: certificate,
|
||||
continueButtonLabel: "common:close",
|
||||
continueButtonVariant: ButtonVariant.primary,
|
||||
noCancelButton: true,
|
||||
onConfirm: async () => {},
|
||||
});
|
||||
|
||||
const goToCreate = () => history.push(`${url}/add-role`);
|
||||
|
||||
const ProviderRenderer = ({ provider }: KeyData) => {
|
||||
return <>{provider}</>;
|
||||
};
|
||||
|
||||
const ButtonRenderer = ({ provider, publicKey, certificate }: KeyData) => {
|
||||
if (provider === "ecdsa-generated") {
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
onClick={() => {
|
||||
togglePublicKeyDialog();
|
||||
setPublicKey(publicKey!);
|
||||
}}
|
||||
variant="secondary"
|
||||
id="kc-public-key"
|
||||
>
|
||||
{t("realm-settings:publicKeys").slice(0, -1)}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
if (provider === "rsa-generated" || provider === "fallback-RS256") {
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
onClick={() => {
|
||||
togglePublicKeyDialog();
|
||||
setPublicKey(publicKey!);
|
||||
}}
|
||||
variant="secondary"
|
||||
id="kc-rsa-public-key"
|
||||
>
|
||||
{t("realm-settings:publicKeys").slice(0, -1)}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
toggleCertificateDialog();
|
||||
setCertificate(certificate!);
|
||||
}}
|
||||
variant="secondary"
|
||||
id="kc-certificate"
|
||||
>
|
||||
{t("realm-settings:certificate")}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageSection variant="light" padding={{ default: "noPadding" }}>
|
||||
<PublicKeyDialog />
|
||||
<CertificateDialog />
|
||||
<KeycloakDataTable
|
||||
key={key}
|
||||
loader={loader}
|
||||
ariaLabelKey="realm-settings:keysList"
|
||||
searchPlaceholderKey="realm-settings:searchKey"
|
||||
canSelectAll
|
||||
columns={[
|
||||
{
|
||||
name: "algorithm",
|
||||
displayKey: "realm-settings:algorithm",
|
||||
cellFormatters: [emptyFormatter()],
|
||||
transforms: [cellWidth(15)],
|
||||
},
|
||||
{
|
||||
name: "type",
|
||||
displayKey: "realm-settings:type",
|
||||
cellFormatters: [emptyFormatter()],
|
||||
transforms: [cellWidth(10)],
|
||||
},
|
||||
{
|
||||
name: "kid",
|
||||
displayKey: "realm-settings:kid",
|
||||
cellFormatters: [emptyFormatter()],
|
||||
},
|
||||
{
|
||||
name: "provider",
|
||||
displayKey: "realm-settings:provider",
|
||||
cellRenderer: ProviderRenderer,
|
||||
cellFormatters: [emptyFormatter()],
|
||||
},
|
||||
{
|
||||
name: "publicKeys",
|
||||
displayKey: "realm-settings:publicKeys",
|
||||
cellRenderer: ButtonRenderer,
|
||||
cellFormatters: [],
|
||||
},
|
||||
]}
|
||||
emptyState={
|
||||
<ListEmptyState
|
||||
hasIcon={true}
|
||||
message={t("noRoles")}
|
||||
instructions={t("noRolesInstructions")}
|
||||
primaryActionText={t("createRole")}
|
||||
onPrimaryAction={goToCreate}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</PageSection>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
type KeysProps = {
|
||||
keys: KeyMetadataRepresentation[];
|
||||
realmComponents: ComponentRepresentation[];
|
||||
};
|
||||
|
||||
export const KeysTab = ({ keys, realmComponents, ...props }: KeysProps) => {
|
||||
return (
|
||||
<KeysTabInner
|
||||
keys={keys?.map((key) => {
|
||||
const provider = realmComponents.find(
|
||||
(component: ComponentRepresentation) =>
|
||||
component.id === key.providerId
|
||||
);
|
||||
return { ...key, provider: provider?.providerId };
|
||||
})}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -16,6 +16,39 @@ div.pf-c-card__body.kc-form-panel__body {
|
|||
padding-left: 0px;
|
||||
padding-bottom: var(--pf-global--spacer--2xl);
|
||||
}
|
||||
|
||||
button#kc-certificate.pf-c-button.pf-m-secondary {
|
||||
margin-left: var(--pf-global--spacer--md);
|
||||
}
|
||||
|
||||
.pf-c-data-list__item-row.test {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.pf-c-data-list__item-content.test2 {
|
||||
margin-left: var(--pf-global--spacer--xl);
|
||||
}
|
||||
|
||||
button.pf-c-data-list__item-draggable-button.pf-m-disabled.header-drag-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
button.pf-c-data-list__item-draggable-button.row-drag-button {
|
||||
padding-top: var(--pf-global--spacer--md);
|
||||
}
|
||||
|
||||
.pf-c-data-list__item-control {
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
button.pf-c-button.pf-m-link.add-provider {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.pf-c-toolbar__group.providers-toolbar {
|
||||
padding-left: var(--pf-c-toolbar__content--PaddingLeft);
|
||||
}
|
||||
|
||||
.button-wrapper {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
|
|
@ -29,6 +29,8 @@ import { RealmSettingsEmailTab } from "./EmailTab";
|
|||
import { KeysListTab } from "./KeysListTab";
|
||||
import type { KeyMetadataRepresentation } from "keycloak-admin/lib/defs/keyMetadataRepresentation";
|
||||
import type ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
|
||||
import { KeysProviderTab } from "./KeysProvidersTab";
|
||||
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
|
||||
|
||||
type RealmSettingsHeaderProps = {
|
||||
onChange: (value: boolean) => void;
|
||||
|
@ -134,6 +136,10 @@ export const RealmSettingsSection = () => {
|
|||
ComponentRepresentation[]
|
||||
>([]);
|
||||
|
||||
const kpComponentTypes = useServerInfo().componentTypes![
|
||||
"org.keycloak.keys.KeyProvider"
|
||||
];
|
||||
|
||||
useFetch(
|
||||
() => adminClient.realms.findOne({ realm: realmName }),
|
||||
(realm) => {
|
||||
|
@ -243,6 +249,17 @@ export const RealmSettingsSection = () => {
|
|||
>
|
||||
<KeysListTab keys={keys} realmComponents={realmComponents} />
|
||||
</Tab>
|
||||
<Tab
|
||||
id="evaluate"
|
||||
eventKey={1}
|
||||
title={<TabTitleText>{t("providers")}</TabTitleText>}
|
||||
>
|
||||
<KeysProviderTab
|
||||
components={realmComponents}
|
||||
realmComponents={realmComponents}
|
||||
keyProviderComponentTypes={kpComponentTypes}
|
||||
/>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Tab>
|
||||
</KeycloakTabs>
|
||||
|
|
|
@ -36,8 +36,11 @@
|
|||
"providers": "Providers",
|
||||
"algorithm": "Algorithm",
|
||||
"type": "Type",
|
||||
"name": "Name",
|
||||
"kid": "Kid",
|
||||
"provider": "Provider",
|
||||
"providerDescription": "Provider description",
|
||||
"addProvider": "Add provider",
|
||||
"publicKeys": "Public keys",
|
||||
"certificate": "Certificate",
|
||||
"userRegistration": "User registration",
|
||||
|
@ -102,5 +105,9 @@
|
|||
"partial-import": {
|
||||
"partialImportHeaderText": "Partial import allows you to import users, clients, and resources from a previously exported json file.",
|
||||
"import": "Import"
|
||||
}
|
||||
},
|
||||
"onDragStart": "Dragging started for item {{id}}",
|
||||
"onDragMove": "Dragging item {{id}}",
|
||||
"onDragCancel": "Dragging cancelled. List is unchanged.",
|
||||
"onDragFinish": "Dragging finished {{list}}"
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue