initial version of authentication section (#309)
* initial version of authentication section * remove dialog for now should go to detail page * added link and buildin label to first column * added key attributes * removed setTimeout workaround * only delete when not in use * Update src/authentication/messages.json Co-authored-by: Stan Silvert <ssilvert@redhat.com> * Update src/authentication/messages.json Co-authored-by: Stan Silvert <ssilvert@redhat.com> * refresh table on duplicate Co-authored-by: Stan Silvert <ssilvert@redhat.com>
This commit is contained in:
parent
5633101f42
commit
b9de247569
8 changed files with 419 additions and 10 deletions
|
@ -24,7 +24,7 @@
|
||||||
"@patternfly/react-table": "4.19.24",
|
"@patternfly/react-table": "4.19.24",
|
||||||
"file-saver": "^2.0.2",
|
"file-saver": "^2.0.2",
|
||||||
"i18next": "^19.6.2",
|
"i18next": "^19.6.2",
|
||||||
"keycloak-admin": "1.14.4",
|
"keycloak-admin": "1.14.6",
|
||||||
"lodash": "^4.17.20",
|
"lodash": "^4.17.20",
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
"react": "^16.8.5",
|
"react": "^16.8.5",
|
||||||
|
|
|
@ -1,6 +1,254 @@
|
||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
import { WorkInProgress } from "../components/work-in-progress/WorkInProgress";
|
import { Link, useRouteMatch } from "react-router-dom";
|
||||||
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
|
import {
|
||||||
|
AlertVariant,
|
||||||
|
Button,
|
||||||
|
ButtonVariant,
|
||||||
|
Label,
|
||||||
|
PageSection,
|
||||||
|
Popover,
|
||||||
|
Tab,
|
||||||
|
TabTitleText,
|
||||||
|
} from "@patternfly/react-core";
|
||||||
|
import { CheckCircleIcon } from "@patternfly/react-icons";
|
||||||
|
|
||||||
export const AuthenticationSection = () => (
|
import AuthenticationFlowRepresentation from "keycloak-admin/lib/defs/authenticationFlowRepresentation";
|
||||||
<WorkInProgress marvelLink="https://marvelapp.com/prototype/bh91013/screen/75647039" />
|
import { useAdminClient } from "../context/auth/AdminClient";
|
||||||
);
|
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
|
||||||
|
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
|
||||||
|
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
|
||||||
|
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||||
|
import { useRealm } from "../context/realm-context/RealmContext";
|
||||||
|
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||||
|
import { useAlerts } from "../components/alert/Alerts";
|
||||||
|
import { toUpperCase } from "../util";
|
||||||
|
import { DuplicateFlowModal } from "./DuplicateFlowModal";
|
||||||
|
|
||||||
|
import "./authentication-section.css";
|
||||||
|
|
||||||
|
type UsedBy = "client" | "default" | "idp";
|
||||||
|
|
||||||
|
type AuthenticationType = AuthenticationFlowRepresentation & {
|
||||||
|
usedBy: { type?: UsedBy; values: string[] };
|
||||||
|
};
|
||||||
|
|
||||||
|
const realmFlows = [
|
||||||
|
"browserFlow",
|
||||||
|
"registrationFlow",
|
||||||
|
"directGrantFlow",
|
||||||
|
"resetCredentialsFlow",
|
||||||
|
"clientAuthenticationFlow",
|
||||||
|
"dockerAuthenticationFlow",
|
||||||
|
];
|
||||||
|
|
||||||
|
export const AuthenticationSection = () => {
|
||||||
|
const { t } = useTranslation("authentication");
|
||||||
|
const adminClient = useAdminClient();
|
||||||
|
const { realm } = useRealm();
|
||||||
|
const [key, setKey] = useState(0);
|
||||||
|
const refresh = () => setKey(new Date().getTime());
|
||||||
|
const { addAlert } = useAlerts();
|
||||||
|
const { url } = useRouteMatch();
|
||||||
|
|
||||||
|
const [selectedFlow, setSelectedFlow] = useState<AuthenticationType>();
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const loader = async () => {
|
||||||
|
const clients = await adminClient.clients.find();
|
||||||
|
const idps = await adminClient.identityProviders.find();
|
||||||
|
const realmRep = await adminClient.realms.findOne({ realm });
|
||||||
|
const defaultFlows = Object.entries(realmRep)
|
||||||
|
.filter((entry) => realmFlows.includes(entry[0]))
|
||||||
|
.map((entry) => entry[1]);
|
||||||
|
|
||||||
|
const flows = (await adminClient.authenticationManagement.getFlows()) as AuthenticationType[];
|
||||||
|
for (const flow of flows) {
|
||||||
|
flow.usedBy = { values: [] };
|
||||||
|
const client = clients.find(
|
||||||
|
(client) =>
|
||||||
|
client.authenticationFlowBindingOverrides &&
|
||||||
|
(client.authenticationFlowBindingOverrides["direct_grant"] ===
|
||||||
|
flow.id ||
|
||||||
|
client.authenticationFlowBindingOverrides["browser"] === flow.id)
|
||||||
|
);
|
||||||
|
if (client) {
|
||||||
|
flow.usedBy.type = "client";
|
||||||
|
flow.usedBy.values.push(client.clientId!);
|
||||||
|
}
|
||||||
|
|
||||||
|
const idp = idps.find(
|
||||||
|
(idp) =>
|
||||||
|
idp.firstBrokerLoginFlowAlias === flow.alias ||
|
||||||
|
idp.postBrokerLoginFlowAlias === flow.alias
|
||||||
|
);
|
||||||
|
if (idp) {
|
||||||
|
flow.usedBy.type = "idp";
|
||||||
|
flow.usedBy.values.push(idp.alias!);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDefault = defaultFlows.includes(flow.alias);
|
||||||
|
if (isDefault) {
|
||||||
|
flow.usedBy.type = "default";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return flows;
|
||||||
|
};
|
||||||
|
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||||
|
titleKey: "authentication:deleteConfirmFlow",
|
||||||
|
children: (
|
||||||
|
<Trans i18nKey="authentication:deleteConfirmFlowMessage">
|
||||||
|
{" "}
|
||||||
|
<strong>{{ flow: selectedFlow ? selectedFlow.alias : "" }}</strong>.
|
||||||
|
</Trans>
|
||||||
|
),
|
||||||
|
continueButtonLabel: "common:delete",
|
||||||
|
continueButtonVariant: ButtonVariant.danger,
|
||||||
|
onConfirm: async () => {
|
||||||
|
try {
|
||||||
|
await adminClient.authenticationManagement.deleteFlow({
|
||||||
|
flowId: selectedFlow!.id!,
|
||||||
|
});
|
||||||
|
refresh();
|
||||||
|
addAlert(t("deleteFlowSuccess"), AlertVariant.success);
|
||||||
|
} catch (error) {
|
||||||
|
addAlert(t("deleteFlowError", { error }), AlertVariant.danger);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const UsedBy = ({ id, usedBy: { type, values } }: AuthenticationType) => (
|
||||||
|
<>
|
||||||
|
{(type === "idp" || type === "client") && (
|
||||||
|
<Popover
|
||||||
|
key={id}
|
||||||
|
aria-label="Basic popover"
|
||||||
|
bodyContent={
|
||||||
|
<div key={`usedBy-${id}-${values}`}>
|
||||||
|
{t("appliedBy" + (type === "client" ? "Clients" : "Providers"))}{" "}
|
||||||
|
{values.map((used, index) => (
|
||||||
|
<>
|
||||||
|
<strong>{used}</strong>
|
||||||
|
{index < values.length - 1 ? ", " : ""}
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button variant={ButtonVariant.link} key={`button-${id}`}>
|
||||||
|
<CheckCircleIcon
|
||||||
|
className="keycloak_authentication-section__usedby"
|
||||||
|
key={`icon-${id}`}
|
||||||
|
/>{" "}
|
||||||
|
{t("specific" + (type === "client" ? "Clients" : "Providers"))}
|
||||||
|
</Button>
|
||||||
|
</Popover>
|
||||||
|
)}
|
||||||
|
{type === "default" && (
|
||||||
|
<Button key={id} variant={ButtonVariant.link} isDisabled>
|
||||||
|
<CheckCircleIcon
|
||||||
|
className="keycloak_authentication-section__usedby"
|
||||||
|
key={`icon-${id}`}
|
||||||
|
/>{" "}
|
||||||
|
{t("default")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{!type && (
|
||||||
|
<Button key={id} variant={ButtonVariant.link} isDisabled>
|
||||||
|
{t("notInUse")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const AliasRenderer = ({ id, alias, builtIn }: AuthenticationType) => (
|
||||||
|
<>
|
||||||
|
<Link to={`${url}/${id}`} key={`link-{id}`}>
|
||||||
|
{toUpperCase(alias!)}
|
||||||
|
</Link>{" "}
|
||||||
|
{builtIn && <Label key={`label-${id}`}>{t("buildIn")}</Label>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DeleteConfirm />
|
||||||
|
{open && (
|
||||||
|
<DuplicateFlowModal
|
||||||
|
name={selectedFlow ? selectedFlow.alias! : ""}
|
||||||
|
description={selectedFlow?.description!}
|
||||||
|
toggleDialog={() => setOpen(!open)}
|
||||||
|
onComplete={() => {
|
||||||
|
refresh();
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<ViewHeader titleKey="authentication:title" subKey="" />
|
||||||
|
<PageSection variant="light">
|
||||||
|
<KeycloakTabs isBox>
|
||||||
|
<Tab
|
||||||
|
eventKey="flows"
|
||||||
|
title={<TabTitleText>{t("flows")}</TabTitleText>}
|
||||||
|
>
|
||||||
|
<KeycloakDataTable
|
||||||
|
key={key}
|
||||||
|
loader={loader}
|
||||||
|
ariaLabelKey="authentication:title"
|
||||||
|
searchPlaceholderKey="authentication:searchForEvent"
|
||||||
|
actionResolver={({ data }) => {
|
||||||
|
const defaultActions = [
|
||||||
|
{
|
||||||
|
title: t("duplicate"),
|
||||||
|
onClick: () => {
|
||||||
|
setOpen(true);
|
||||||
|
setSelectedFlow(data);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
// remove delete when it's in use or default flow
|
||||||
|
if (data.builtIn || data.usedBy.values.length > 0) {
|
||||||
|
return defaultActions;
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: t("common:delete"),
|
||||||
|
onClick: () => {
|
||||||
|
setSelectedFlow(data);
|
||||||
|
toggleDeleteDialog();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...defaultActions,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
name: "alias",
|
||||||
|
displayKey: "authentication:flowName",
|
||||||
|
cellRenderer: AliasRenderer,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "usedBy",
|
||||||
|
displayKey: "authentication:usedBy",
|
||||||
|
cellRenderer: UsedBy,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "description",
|
||||||
|
displayKey: "common:description",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
emptyState={
|
||||||
|
<ListEmptyState
|
||||||
|
message={t("emptyEvents")}
|
||||||
|
instructions={t("emptyEventsInstructions")}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Tab>
|
||||||
|
</KeycloakTabs>
|
||||||
|
</PageSection>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
123
src/authentication/DuplicateFlowModal.tsx
Normal file
123
src/authentication/DuplicateFlowModal.tsx
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import {
|
||||||
|
AlertVariant,
|
||||||
|
Button,
|
||||||
|
ButtonVariant,
|
||||||
|
Form,
|
||||||
|
FormGroup,
|
||||||
|
Modal,
|
||||||
|
ModalVariant,
|
||||||
|
TextInput,
|
||||||
|
ValidatedOptions,
|
||||||
|
} from "@patternfly/react-core";
|
||||||
|
|
||||||
|
import { useAdminClient } from "../context/auth/AdminClient";
|
||||||
|
import { useAlerts } from "../components/alert/Alerts";
|
||||||
|
|
||||||
|
type DuplicateFlowModalProps = {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
toggleDialog: () => void;
|
||||||
|
onComplete: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DuplicateFlowModal = ({
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
toggleDialog,
|
||||||
|
onComplete,
|
||||||
|
}: DuplicateFlowModalProps) => {
|
||||||
|
const { t } = useTranslation("authentication");
|
||||||
|
const { register, errors, setValue, trigger, getValues } = useForm({
|
||||||
|
shouldUnregister: false,
|
||||||
|
});
|
||||||
|
const adminClient = useAdminClient();
|
||||||
|
const { addAlert } = useAlerts();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setValue("description", description);
|
||||||
|
setValue("name", t("copyOf", { name }));
|
||||||
|
}, [name, description, setValue]);
|
||||||
|
|
||||||
|
const save = async () => {
|
||||||
|
await trigger();
|
||||||
|
const form = getValues();
|
||||||
|
try {
|
||||||
|
await adminClient.authenticationManagement.copyFlow({
|
||||||
|
flow: name,
|
||||||
|
newName: form.name,
|
||||||
|
});
|
||||||
|
if (form.description !== description) {
|
||||||
|
const newFlow = (
|
||||||
|
await adminClient.authenticationManagement.getFlows()
|
||||||
|
).find((flow) => flow.alias === form.name)!;
|
||||||
|
|
||||||
|
newFlow.description = form.description;
|
||||||
|
await adminClient.authenticationManagement.updateFlow(
|
||||||
|
{ flowId: newFlow.id! },
|
||||||
|
newFlow
|
||||||
|
);
|
||||||
|
}
|
||||||
|
addAlert(t("copyFlowSuccess"), AlertVariant.success);
|
||||||
|
} catch (error) {
|
||||||
|
addAlert(t("copyFlowError", { error }), AlertVariant.danger);
|
||||||
|
}
|
||||||
|
onComplete();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={t("duplicateFlow")}
|
||||||
|
isOpen={true}
|
||||||
|
onClose={toggleDialog}
|
||||||
|
variant={ModalVariant.small}
|
||||||
|
actions={[
|
||||||
|
<Button id="modal-confirm" key="confirm" onClick={save}>
|
||||||
|
{t("common:save")}
|
||||||
|
</Button>,
|
||||||
|
<Button
|
||||||
|
id="modal-cancel"
|
||||||
|
key="cancel"
|
||||||
|
variant={ButtonVariant.secondary}
|
||||||
|
onClick={() => {
|
||||||
|
toggleDialog();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("common:cancel")}
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Form isHorizontal>
|
||||||
|
<FormGroup
|
||||||
|
label={t("common:name")}
|
||||||
|
fieldId="kc-name"
|
||||||
|
helperTextInvalid={t("common:required")}
|
||||||
|
validated={
|
||||||
|
errors.name ? ValidatedOptions.error : ValidatedOptions.default
|
||||||
|
}
|
||||||
|
isRequired
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
type="text"
|
||||||
|
id="kc-name"
|
||||||
|
name="name"
|
||||||
|
ref={register({ required: true })}
|
||||||
|
validated={
|
||||||
|
errors.name ? ValidatedOptions.error : ValidatedOptions.default
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup label={t("common:description")} fieldId="kc-description">
|
||||||
|
<TextInput
|
||||||
|
type="text"
|
||||||
|
id="kc-description"
|
||||||
|
name="description"
|
||||||
|
ref={register()}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
3
src/authentication/authentication-section.css
Normal file
3
src/authentication/authentication-section.css
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.keycloak_authentication-section__usedby {
|
||||||
|
color: var(--pf-global--success-color--100);
|
||||||
|
}
|
24
src/authentication/messages.json
Normal file
24
src/authentication/messages.json
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"authentication": {
|
||||||
|
"title": "Authentication",
|
||||||
|
"flows": "Flows",
|
||||||
|
"flowName": "Flow name",
|
||||||
|
"usedBy": "Used by",
|
||||||
|
"buildIn": "Built-in",
|
||||||
|
"appliedByProviders": "Applied by the following providers",
|
||||||
|
"appliedByClients": "Applied by the following clients",
|
||||||
|
"specificProviders": "Specific providers",
|
||||||
|
"specificClients": "Specific clients",
|
||||||
|
"default": "Default",
|
||||||
|
"notInUse": "Not in use",
|
||||||
|
"duplicate": "Duplicate",
|
||||||
|
"deleteConfirmFlow": "Delete flow?",
|
||||||
|
"deleteConfirmFlowMessage": "Are you sure you want to permanently delete the flow \"<1>{{flow}}</1>\".",
|
||||||
|
"deleteFlowSuccess": "Flow successfully deleted",
|
||||||
|
"deleteFlowError": "Could not delete flow: {{error}}",
|
||||||
|
"duplicateFlow": "Duplicate flow",
|
||||||
|
"copyOf": "Copy of {{name}}",
|
||||||
|
"copyFlowSuccess": "Flow successfully duplicated",
|
||||||
|
"copyFlowError": "Could not duplicate flow: {{error}}"
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
IAction,
|
IAction,
|
||||||
IActions,
|
IActions,
|
||||||
|
IActionsResolver,
|
||||||
IFormatter,
|
IFormatter,
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
|
@ -27,6 +28,7 @@ type DataTableProps<T> = {
|
||||||
columns: Field<T>[];
|
columns: Field<T>[];
|
||||||
rows: Row<T>[];
|
rows: Row<T>[];
|
||||||
actions?: IActions;
|
actions?: IActions;
|
||||||
|
actionResolver?: IActionsResolver;
|
||||||
onSelect?: (isSelected: boolean, rowIndex: number) => void;
|
onSelect?: (isSelected: boolean, rowIndex: number) => void;
|
||||||
canSelectAll: boolean;
|
canSelectAll: boolean;
|
||||||
};
|
};
|
||||||
|
@ -35,6 +37,7 @@ function DataTable<T>({
|
||||||
columns,
|
columns,
|
||||||
rows,
|
rows,
|
||||||
actions,
|
actions,
|
||||||
|
actionResolver,
|
||||||
ariaLabelKey,
|
ariaLabelKey,
|
||||||
onSelect,
|
onSelect,
|
||||||
canSelectAll,
|
canSelectAll,
|
||||||
|
@ -54,6 +57,7 @@ function DataTable<T>({
|
||||||
})}
|
})}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
actions={actions}
|
actions={actions}
|
||||||
|
actionResolver={actionResolver}
|
||||||
aria-label={t(ariaLabelKey)}
|
aria-label={t(ariaLabelKey)}
|
||||||
>
|
>
|
||||||
<TableHeader />
|
<TableHeader />
|
||||||
|
@ -82,6 +86,7 @@ export type DataListProps<T> = {
|
||||||
searchPlaceholderKey: string;
|
searchPlaceholderKey: string;
|
||||||
columns: Field<T>[];
|
columns: Field<T>[];
|
||||||
actions?: Action<T>[];
|
actions?: Action<T>[];
|
||||||
|
actionResolver?: IActionsResolver;
|
||||||
toolbarItem?: ReactNode;
|
toolbarItem?: ReactNode;
|
||||||
emptyState?: ReactNode;
|
emptyState?: ReactNode;
|
||||||
};
|
};
|
||||||
|
@ -104,6 +109,7 @@ export type DataListProps<T> = {
|
||||||
* @param {(first?: number, max?: number, search?: string) => Promise<T[]>} props.loader - loader function that will fetch the data to display first, max and search are only applicable when isPaginated = true
|
* @param {(first?: number, max?: number, search?: string) => Promise<T[]>} props.loader - loader function that will fetch the data to display first, max and search are only applicable when isPaginated = true
|
||||||
* @param {Field<T>} props.columns - definition of the columns
|
* @param {Field<T>} props.columns - definition of the columns
|
||||||
* @param {Action[]} props.actions - the actions that appear on the row
|
* @param {Action[]} props.actions - the actions that appear on the row
|
||||||
|
* @param {IActionsResolver} props.actionResolver Resolver for the given action
|
||||||
* @param {ReactNode} props.toolbarItem - Toolbar items that appear on the top of the table {@link ToolbarItem}
|
* @param {ReactNode} props.toolbarItem - Toolbar items that appear on the top of the table {@link ToolbarItem}
|
||||||
* @param {ReactNode} props.emptyState - ReactNode show when the list is empty could be any component but best to use {@link ListEmptyState}
|
* @param {ReactNode} props.emptyState - ReactNode show when the list is empty could be any component but best to use {@link ListEmptyState}
|
||||||
*/
|
*/
|
||||||
|
@ -116,6 +122,7 @@ export function KeycloakDataTable<T>({
|
||||||
loader,
|
loader,
|
||||||
columns,
|
columns,
|
||||||
actions,
|
actions,
|
||||||
|
actionResolver,
|
||||||
toolbarItem,
|
toolbarItem,
|
||||||
emptyState,
|
emptyState,
|
||||||
}: DataListProps<T>) {
|
}: DataListProps<T>) {
|
||||||
|
@ -250,6 +257,7 @@ export function KeycloakDataTable<T>({
|
||||||
canSelectAll={canSelectAll}
|
canSelectAll={canSelectAll}
|
||||||
onSelect={onSelect ? _onSelect : undefined}
|
onSelect={onSelect ? _onSelect : undefined}
|
||||||
actions={convertAction()}
|
actions={convertAction()}
|
||||||
|
actionResolver={actionResolver}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
ariaLabelKey={ariaLabelKey}
|
ariaLabelKey={ariaLabelKey}
|
||||||
|
@ -272,6 +280,7 @@ export function KeycloakDataTable<T>({
|
||||||
canSelectAll={canSelectAll}
|
canSelectAll={canSelectAll}
|
||||||
onSelect={onSelect ? _onSelect : undefined}
|
onSelect={onSelect ? _onSelect : undefined}
|
||||||
actions={convertAction()}
|
actions={convertAction()}
|
||||||
|
actionResolver={actionResolver}
|
||||||
rows={filteredData || rows}
|
rows={filteredData || rows}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
ariaLabelKey={ariaLabelKey}
|
ariaLabelKey={ariaLabelKey}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import sessions from "./sessions/messages.json";
|
||||||
import events from "./events/messages.json";
|
import events from "./events/messages.json";
|
||||||
import realmSettings from "./realm-settings/messages.json";
|
import realmSettings from "./realm-settings/messages.json";
|
||||||
import realmSettingsHelp from "./realm-settings/help.json";
|
import realmSettingsHelp from "./realm-settings/help.json";
|
||||||
|
import authentication from "./authentication/messages.json";
|
||||||
import storybook from "./stories/messages.json";
|
import storybook from "./stories/messages.json";
|
||||||
import userFederation from "./user-federation/messages.json";
|
import userFederation from "./user-federation/messages.json";
|
||||||
import userFederationHelp from "./user-federation/help.json";
|
import userFederationHelp from "./user-federation/help.json";
|
||||||
|
@ -42,6 +43,7 @@ const initOptions = {
|
||||||
...events,
|
...events,
|
||||||
...realmSettings,
|
...realmSettings,
|
||||||
...realmSettingsHelp,
|
...realmSettingsHelp,
|
||||||
|
...authentication,
|
||||||
...storybook,
|
...storybook,
|
||||||
...userFederation,
|
...userFederation,
|
||||||
...userFederationHelp,
|
...userFederationHelp,
|
||||||
|
|
|
@ -13202,10 +13202,10 @@ junk@^3.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1"
|
resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1"
|
||||||
integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==
|
integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==
|
||||||
|
|
||||||
keycloak-admin@1.14.4:
|
keycloak-admin@1.14.6:
|
||||||
version "1.14.4"
|
version "1.14.6"
|
||||||
resolved "https://registry.yarnpkg.com/keycloak-admin/-/keycloak-admin-1.14.4.tgz#67f9a0991e88003f23a3c6a9d87c7a11294afd12"
|
resolved "https://registry.yarnpkg.com/keycloak-admin/-/keycloak-admin-1.14.6.tgz#22ae06bc0db7b041914bae6cf8fd5fd20d3efa9b"
|
||||||
integrity sha512-16/ctPH/Pz+XLL62tQp1d2XziMaPtxjthrfXcr+wfeJYMGH09QQTd1EDJjdFskuhZOhmYlifaAYPanQLwbUFEg==
|
integrity sha512-bjYwXzq5VRJh/uM/gfogf0SsPn53xb7cMd6R3qp5jY4VrdjnCYPvD1db8F+Cr6lxOxYsA3jVMziMLgGKKuGIdw==
|
||||||
dependencies:
|
dependencies:
|
||||||
axios "^0.21.0"
|
axios "^0.21.0"
|
||||||
camelize "^1.0.0"
|
camelize "^1.0.0"
|
||||||
|
|
Loading…
Reference in a new issue