* fixed issues described in #468 fixing: #468 * fixed type * fixed column size and order
This commit is contained in:
parent
83a8f2baa7
commit
4d52871fc2
10 changed files with 405 additions and 88 deletions
71
src/client-scopes/ChangeTypeDialog.tsx
Normal file
71
src/client-scopes/ChangeTypeDialog.tsx
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
ButtonVariant,
|
||||||
|
Form,
|
||||||
|
Modal,
|
||||||
|
Radio,
|
||||||
|
} from "@patternfly/react-core";
|
||||||
|
import {
|
||||||
|
AllClientScopes,
|
||||||
|
AllClientScopeType,
|
||||||
|
allClientScopeTypes,
|
||||||
|
} from "../components/client-scope/ClientScopeTypes";
|
||||||
|
|
||||||
|
type ChangeTypeDialogProps = {
|
||||||
|
selectedClientScopes: number;
|
||||||
|
onConfirm: (scope: AllClientScopeType) => void;
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ChangeTypeDialog = ({
|
||||||
|
selectedClientScopes,
|
||||||
|
onConfirm,
|
||||||
|
onClose,
|
||||||
|
}: ChangeTypeDialogProps) => {
|
||||||
|
const { t } = useTranslation("client-scopes");
|
||||||
|
const [value, setValue] = useState<AllClientScopeType>(AllClientScopes.none);
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={t("changeType")}
|
||||||
|
isOpen={true}
|
||||||
|
onClose={onClose}
|
||||||
|
variant="small"
|
||||||
|
description={t("changeTypeIntro", { count: selectedClientScopes })}
|
||||||
|
actions={[
|
||||||
|
<Button
|
||||||
|
data-testid="change-scope-dialog-confirm"
|
||||||
|
key="confirm"
|
||||||
|
onClick={() => onConfirm(value)}
|
||||||
|
>
|
||||||
|
{t("common:continue")}
|
||||||
|
</Button>,
|
||||||
|
<Button
|
||||||
|
key="cancel"
|
||||||
|
variant={ButtonVariant.secondary}
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
{t("common:cancel")}
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Form isHorizontal>
|
||||||
|
{allClientScopeTypes.map((scope) => (
|
||||||
|
<Radio
|
||||||
|
key={scope}
|
||||||
|
isChecked={scope === value}
|
||||||
|
name={`radio-${scope}`}
|
||||||
|
onChange={(_val, event) => {
|
||||||
|
const { value } = event.currentTarget;
|
||||||
|
setValue(value as AllClientScopeType);
|
||||||
|
}}
|
||||||
|
label={t(`common:clientScope.${scope}`)}
|
||||||
|
id={`radio-${scope}`}
|
||||||
|
value={scope}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,13 +1,80 @@
|
||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link, useHistory, useRouteMatch } from "react-router-dom";
|
import { Link, useHistory, useRouteMatch } from "react-router-dom";
|
||||||
import { AlertVariant, Button, PageSection } from "@patternfly/react-core";
|
import {
|
||||||
|
AlertVariant,
|
||||||
|
Button,
|
||||||
|
ButtonVariant,
|
||||||
|
Dropdown,
|
||||||
|
DropdownItem,
|
||||||
|
KebabToggle,
|
||||||
|
PageSection,
|
||||||
|
ToolbarItem,
|
||||||
|
} from "@patternfly/react-core";
|
||||||
|
import { cellWidth } from "@patternfly/react-table";
|
||||||
import ClientScopeRepresentation from "keycloak-admin/lib/defs/clientScopeRepresentation";
|
import ClientScopeRepresentation from "keycloak-admin/lib/defs/clientScopeRepresentation";
|
||||||
|
|
||||||
import { useAdminClient } from "../context/auth/AdminClient";
|
import { useAdminClient } from "../context/auth/AdminClient";
|
||||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||||
import { useAlerts } from "../components/alert/Alerts";
|
import { useAlerts } from "../components/alert/Alerts";
|
||||||
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
|
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
|
||||||
|
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||||
|
import { emptyFormatter } from "../util";
|
||||||
|
import {
|
||||||
|
CellDropdown,
|
||||||
|
ClientScope,
|
||||||
|
AllClientScopes,
|
||||||
|
AllClientScopeType,
|
||||||
|
} from "../components/client-scope/ClientScopeTypes";
|
||||||
|
|
||||||
|
type ClientScopeDefaultOptionalType = ClientScopeRepresentation & {
|
||||||
|
type: AllClientScopeType;
|
||||||
|
};
|
||||||
|
|
||||||
|
import "./client-scope.css";
|
||||||
|
import KeycloakAdminClient from "keycloak-admin";
|
||||||
|
import { ChangeTypeDialog } from "./ChangeTypeDialog";
|
||||||
|
|
||||||
|
const castAdminClient = (adminClient: KeycloakAdminClient) =>
|
||||||
|
(adminClient.clientScopes as unknown) as {
|
||||||
|
[index: string]: Function;
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeScope = async (
|
||||||
|
adminClient: KeycloakAdminClient,
|
||||||
|
clientScope: ClientScopeDefaultOptionalType,
|
||||||
|
changeTo: AllClientScopeType
|
||||||
|
) => {
|
||||||
|
await removeScope(adminClient, clientScope);
|
||||||
|
await addScope(adminClient, clientScope, changeTo);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeScope = async (
|
||||||
|
adminClient: KeycloakAdminClient,
|
||||||
|
clientScope: ClientScopeDefaultOptionalType
|
||||||
|
) => {
|
||||||
|
if (clientScope.type !== AllClientScopes.none)
|
||||||
|
await castAdminClient(adminClient)[
|
||||||
|
`delDefault${
|
||||||
|
clientScope.type === ClientScope.optional ? "Optional" : ""
|
||||||
|
}ClientScope`
|
||||||
|
]({
|
||||||
|
id: clientScope.id!,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const addScope = async (
|
||||||
|
adminClient: KeycloakAdminClient,
|
||||||
|
clientScope: ClientScopeDefaultOptionalType,
|
||||||
|
type: AllClientScopeType
|
||||||
|
) => {
|
||||||
|
if (type !== AllClientScopes.none)
|
||||||
|
await castAdminClient(adminClient)[
|
||||||
|
`addDefault${type === ClientScope.optional ? "Optional" : ""}ClientScope`
|
||||||
|
]({
|
||||||
|
id: clientScope.id!,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const ClientScopesSection = () => {
|
export const ClientScopesSection = () => {
|
||||||
const { t } = useTranslation("client-scopes");
|
const { t } = useTranslation("client-scopes");
|
||||||
|
@ -17,7 +84,83 @@ export const ClientScopesSection = () => {
|
||||||
const adminClient = useAdminClient();
|
const adminClient = useAdminClient();
|
||||||
const { addAlert } = useAlerts();
|
const { addAlert } = useAlerts();
|
||||||
|
|
||||||
const loader = async () => await adminClient.clientScopes.find();
|
const [key, setKey] = useState(0);
|
||||||
|
const refresh = () => setKey(new Date().getTime());
|
||||||
|
|
||||||
|
const [kebabOpen, setKebabOpen] = useState(false);
|
||||||
|
const [changeTypeOpen, setChangeTypeOpen] = useState(false);
|
||||||
|
const [selectedScopes, setSelectedScopes] = useState<
|
||||||
|
ClientScopeDefaultOptionalType[]
|
||||||
|
>([]);
|
||||||
|
|
||||||
|
const loader = async () => {
|
||||||
|
const defaultScopes = await adminClient.clientScopes.listDefaultClientScopes();
|
||||||
|
const optionalScopes = await adminClient.clientScopes.listDefaultOptionalClientScopes();
|
||||||
|
|
||||||
|
const clientScopes = (await adminClient.clientScopes.find()).map(
|
||||||
|
(scope) => {
|
||||||
|
return {
|
||||||
|
...scope,
|
||||||
|
type: defaultScopes.find(
|
||||||
|
(defaultScope) => defaultScope.name === scope.name
|
||||||
|
)
|
||||||
|
? ClientScope.default
|
||||||
|
: optionalScopes.find(
|
||||||
|
(optionalScope) => optionalScope.name === scope.name
|
||||||
|
)
|
||||||
|
? ClientScope.optional
|
||||||
|
: AllClientScopes.none,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return clientScopes;
|
||||||
|
};
|
||||||
|
|
||||||
|
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||||
|
titleKey: t("deleteClientScope", {
|
||||||
|
count: selectedScopes.length,
|
||||||
|
name: selectedScopes[0]?.name,
|
||||||
|
}),
|
||||||
|
messageKey: "client-scopes:deleteConfirm",
|
||||||
|
continueButtonLabel: "common:delete",
|
||||||
|
continueButtonVariant: ButtonVariant.danger,
|
||||||
|
onConfirm: async () => {
|
||||||
|
try {
|
||||||
|
for (const scope of selectedScopes) {
|
||||||
|
await adminClient.clientScopes.del({ id: scope.id! });
|
||||||
|
}
|
||||||
|
addAlert(t("deletedSuccess"), AlertVariant.success);
|
||||||
|
refresh();
|
||||||
|
} catch (error) {
|
||||||
|
addAlert(
|
||||||
|
t("deleteError", {
|
||||||
|
error: error.response?.data?.errorMessage || error,
|
||||||
|
}),
|
||||||
|
AlertVariant.danger
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const TypeSelector = (scope: ClientScopeDefaultOptionalType) => (
|
||||||
|
<>
|
||||||
|
<CellDropdown
|
||||||
|
clientScope={scope}
|
||||||
|
type={scope.type}
|
||||||
|
all
|
||||||
|
onSelect={async (value) => {
|
||||||
|
try {
|
||||||
|
await changeScope(adminClient, scope, value);
|
||||||
|
addAlert(t("clientScopeSuccess"), AlertVariant.success);
|
||||||
|
refresh();
|
||||||
|
} catch (error) {
|
||||||
|
addAlert(t("clientScopeError", { error }), AlertVariant.danger);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
const ClientScopeDetailLink = (clientScope: ClientScopeRepresentation) => (
|
const ClientScopeDetailLink = (clientScope: ClientScopeRepresentation) => (
|
||||||
<>
|
<>
|
||||||
|
@ -28,19 +171,79 @@ export const ClientScopesSection = () => {
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<DeleteConfirm />
|
||||||
|
{changeTypeOpen && (
|
||||||
|
<ChangeTypeDialog
|
||||||
|
selectedClientScopes={selectedScopes.length}
|
||||||
|
onConfirm={(type) => {
|
||||||
|
selectedScopes.map(async (scope) => {
|
||||||
|
try {
|
||||||
|
await changeScope(adminClient, scope, type);
|
||||||
|
addAlert(t("clientScopeSuccess"), AlertVariant.success);
|
||||||
|
refresh();
|
||||||
|
} catch (error) {
|
||||||
|
addAlert(t("clientScopeError", { error }), AlertVariant.danger);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setChangeTypeOpen(false);
|
||||||
|
}}
|
||||||
|
onClose={() => setChangeTypeOpen(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<ViewHeader
|
<ViewHeader
|
||||||
titleKey="clientScopes"
|
titleKey="clientScopes"
|
||||||
subKey="client-scopes:clientScopeExplain"
|
subKey="client-scopes:clientScopeExplain"
|
||||||
/>
|
/>
|
||||||
<PageSection variant="light" className="pf-u-p-0">
|
<PageSection variant="light" className="pf-u-p-0">
|
||||||
<KeycloakDataTable
|
<KeycloakDataTable
|
||||||
|
key={key}
|
||||||
loader={loader}
|
loader={loader}
|
||||||
ariaLabelKey="client-scopes:clientScopeList"
|
ariaLabelKey="client-scopes:clientScopeList"
|
||||||
searchPlaceholderKey="client-scopes:searchFor"
|
searchPlaceholderKey="client-scopes:searchFor"
|
||||||
|
onSelect={(clientScopes) => setSelectedScopes([...clientScopes])}
|
||||||
|
canSelectAll
|
||||||
toolbarItem={
|
toolbarItem={
|
||||||
<Button onClick={() => history.push(`${url}/new`)}>
|
<>
|
||||||
{t("createClientScope")}
|
<ToolbarItem>
|
||||||
</Button>
|
<Button onClick={() => history.push(`${url}/new`)}>
|
||||||
|
{t("createClientScope")}
|
||||||
|
</Button>
|
||||||
|
</ToolbarItem>
|
||||||
|
<ToolbarItem>
|
||||||
|
<Dropdown
|
||||||
|
toggle={
|
||||||
|
<KebabToggle onToggle={() => setKebabOpen(!kebabOpen)} />
|
||||||
|
}
|
||||||
|
isOpen={kebabOpen}
|
||||||
|
isPlain
|
||||||
|
dropdownItems={[
|
||||||
|
<DropdownItem
|
||||||
|
key="changeType"
|
||||||
|
component="button"
|
||||||
|
isDisabled={selectedScopes.length === 0}
|
||||||
|
onClick={() => {
|
||||||
|
setChangeTypeOpen(true);
|
||||||
|
setKebabOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("changeType")}
|
||||||
|
</DropdownItem>,
|
||||||
|
|
||||||
|
<DropdownItem
|
||||||
|
key="action"
|
||||||
|
component="button"
|
||||||
|
isDisabled={selectedScopes.length === 0}
|
||||||
|
onClick={() => {
|
||||||
|
toggleDeleteDialog();
|
||||||
|
setKebabOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("common:delete")}
|
||||||
|
</DropdownItem>,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</ToolbarItem>
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
actions={[
|
actions={[
|
||||||
{
|
{
|
||||||
|
@ -49,20 +252,9 @@ export const ClientScopesSection = () => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("common:delete"),
|
title: t("common:delete"),
|
||||||
onRowClick: async (clientScope) => {
|
onRowClick: (clientScope) => {
|
||||||
try {
|
setSelectedScopes([clientScope]);
|
||||||
await adminClient.clientScopes.del({ id: clientScope.id! });
|
toggleDeleteDialog();
|
||||||
addAlert(t("deletedSuccess"), AlertVariant.success);
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
addAlert(
|
|
||||||
t("deleteError", {
|
|
||||||
error: error.response?.data?.errorMessage || error,
|
|
||||||
}),
|
|
||||||
AlertVariant.danger
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
@ -71,11 +263,18 @@ export const ClientScopesSection = () => {
|
||||||
name: "name",
|
name: "name",
|
||||||
cellRenderer: ClientScopeDetailLink,
|
cellRenderer: ClientScopeDetailLink,
|
||||||
},
|
},
|
||||||
{ name: "description" },
|
{ name: "description", cellFormatters: [emptyFormatter()] },
|
||||||
|
{ name: "type", cellRenderer: TypeSelector },
|
||||||
{
|
{
|
||||||
name: "protocol",
|
name: "protocol",
|
||||||
displayKey: "client-scopes:protocol",
|
displayKey: "client-scopes:protocol",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "attributes['gui.order']",
|
||||||
|
displayKey: "client-scopes:displayOrder",
|
||||||
|
cellFormatters: [emptyFormatter()],
|
||||||
|
transforms: [cellWidth(20)],
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</PageSection>
|
</PageSection>
|
||||||
|
|
4
src/client-scopes/client-scope.css
Normal file
4
src/client-scopes/client-scope.css
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
.keycloak__client-scope__none > button .pf-c-select__toggle-text {
|
||||||
|
color: var(--pf-global--Color--400);
|
||||||
|
}
|
|
@ -6,6 +6,14 @@
|
||||||
"clientScopeExplain": "Client scopes allow you to define a common set of protocol mappers and roles, which are shared between multiple clients",
|
"clientScopeExplain": "Client scopes allow you to define a common set of protocol mappers and roles, which are shared between multiple clients",
|
||||||
"searchFor": "Search for client scope",
|
"searchFor": "Search for client scope",
|
||||||
"protocol": "Protocol",
|
"protocol": "Protocol",
|
||||||
|
"displayOrder": "Display order",
|
||||||
|
"deleteClientScope": "Delete client scope {{name}}",
|
||||||
|
"deleteClientScope_plural": "Delete {{count}} client scopes",
|
||||||
|
"deleteConfirm": "Are you sure you want to delete this client scope",
|
||||||
|
"changeType": "Change type",
|
||||||
|
"changeTypeIntro": "{{count}} selected client scopes will be changed to",
|
||||||
|
"clientScopeSuccess": "Scope mapping updated",
|
||||||
|
"clientScopeError": "Could not update scope mapping {{error}}",
|
||||||
"deletedSuccess": "The client scope has been deleted",
|
"deletedSuccess": "The client scope has been deleted",
|
||||||
"deleteError": "Could not delete client scope: {{error}}",
|
"deleteError": "Could not delete client scope: {{error}}",
|
||||||
"includeInTokenScope": "Include in token scope",
|
"includeInTokenScope": "Include in token scope",
|
||||||
|
|
|
@ -30,10 +30,6 @@
|
||||||
"evaluate": "Evaluate",
|
"evaluate": "Evaluate",
|
||||||
"changeTypeTo": "Change type to",
|
"changeTypeTo": "Change type to",
|
||||||
"assignRole": "Assign role",
|
"assignRole": "Assign role",
|
||||||
"clientScope": {
|
|
||||||
"default": "Default",
|
|
||||||
"optional": "Optional"
|
|
||||||
},
|
|
||||||
"clientScopeSearch": {
|
"clientScopeSearch": {
|
||||||
"client": "Client scope",
|
"client": "Client scope",
|
||||||
"assigned": "Assigned type"
|
"assigned": "Assigned type"
|
||||||
|
|
|
@ -18,7 +18,10 @@ import {
|
||||||
} from "@patternfly/react-table";
|
} from "@patternfly/react-table";
|
||||||
import ClientScopeRepresentation from "keycloak-admin/lib/defs/clientScopeRepresentation";
|
import ClientScopeRepresentation from "keycloak-admin/lib/defs/clientScopeRepresentation";
|
||||||
|
|
||||||
import { ClientScopeType, clientScopeTypesDropdown } from "./ClientScopeTypes";
|
import {
|
||||||
|
ClientScopeType,
|
||||||
|
clientScopeTypesDropdown,
|
||||||
|
} from "../../components/client-scope/ClientScopeTypes";
|
||||||
|
|
||||||
export type AddScopeDialogProps = {
|
export type AddScopeDialogProps = {
|
||||||
clientScopes: ClientScopeRepresentation[];
|
clientScopes: ClientScopeRepresentation[];
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import { TFunction } from "i18next";
|
|
||||||
import { DropdownItem, SelectOption } from "@patternfly/react-core";
|
|
||||||
|
|
||||||
export enum ClientScope {
|
|
||||||
default = "default",
|
|
||||||
optional = "optional",
|
|
||||||
}
|
|
||||||
export type ClientScopeType = ClientScope.default | ClientScope.optional;
|
|
||||||
const clientScopeTypes = Object.keys(ClientScope);
|
|
||||||
|
|
||||||
export const clientScopeTypesSelectOptions = (t: TFunction) =>
|
|
||||||
clientScopeTypes.map((type) => (
|
|
||||||
<SelectOption key={type} value={type}>
|
|
||||||
{t(`clientScope.${type}`)}
|
|
||||||
</SelectOption>
|
|
||||||
));
|
|
||||||
|
|
||||||
export const clientScopeTypesDropdown = (
|
|
||||||
t: TFunction,
|
|
||||||
onClick: (scope: ClientScopeType) => void
|
|
||||||
) =>
|
|
||||||
clientScopeTypes.map((type) => (
|
|
||||||
<DropdownItem key={type} onClick={() => onClick(type as ClientScopeType)}>
|
|
||||||
{t(`clientScope.${type}`)}
|
|
||||||
</DropdownItem>
|
|
||||||
));
|
|
|
@ -22,7 +22,8 @@ import {
|
||||||
clientScopeTypesSelectOptions,
|
clientScopeTypesSelectOptions,
|
||||||
ClientScopeType,
|
ClientScopeType,
|
||||||
ClientScope,
|
ClientScope,
|
||||||
} from "./ClientScopeTypes";
|
CellDropdown,
|
||||||
|
} from "../../components/client-scope/ClientScopeTypes";
|
||||||
import { useAlerts } from "../../components/alert/Alerts";
|
import { useAlerts } from "../../components/alert/Alerts";
|
||||||
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
|
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
|
||||||
|
|
||||||
|
@ -78,41 +79,8 @@ const addScope = async (
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
type CellDropdownProps = {
|
|
||||||
clientScope: ClientScopeRepresentation;
|
|
||||||
type: ClientScopeType;
|
|
||||||
onSelect: (value: ClientScopeType) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const CellDropdown = ({ clientScope, type, onSelect }: CellDropdownProps) => {
|
|
||||||
const { t } = useTranslation("clients");
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Select
|
|
||||||
key={clientScope.id}
|
|
||||||
onToggle={() => setOpen(!open)}
|
|
||||||
isOpen={open}
|
|
||||||
selections={[type]}
|
|
||||||
onSelect={(_, value) => {
|
|
||||||
onSelect(value as ClientScopeType);
|
|
||||||
setOpen(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{clientScopeTypesSelectOptions(t)}
|
|
||||||
</Select>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
type SearchType = "client" | "assigned";
|
type SearchType = "client" | "assigned";
|
||||||
|
|
||||||
type TableRow = {
|
|
||||||
selected: boolean;
|
|
||||||
clientScope: ClientScopeRepresentation;
|
|
||||||
type: ClientScopeType;
|
|
||||||
cells: (string | undefined)[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ClientScopes = ({ clientId, protocol }: ClientScopesProps) => {
|
export const ClientScopes = ({ clientId, protocol }: ClientScopesProps) => {
|
||||||
const { t } = useTranslation("clients");
|
const { t } = useTranslation("clients");
|
||||||
const adminClient = useAdminClient();
|
const adminClient = useAdminClient();
|
||||||
|
@ -178,7 +146,13 @@ export const ClientScopes = ({ clientId, protocol }: ClientScopesProps) => {
|
||||||
type={scope.type}
|
type={scope.type}
|
||||||
onSelect={async (value) => {
|
onSelect={async (value) => {
|
||||||
try {
|
try {
|
||||||
await changeScope(adminClient, clientId, scope, scope.type, value);
|
await changeScope(
|
||||||
|
adminClient,
|
||||||
|
clientId,
|
||||||
|
scope,
|
||||||
|
scope.type,
|
||||||
|
value as ClientScope
|
||||||
|
);
|
||||||
addAlert(t("clientScopeSuccess"), AlertVariant.success);
|
addAlert(t("clientScopeSuccess"), AlertVariant.success);
|
||||||
refresh();
|
refresh();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -55,6 +55,12 @@
|
||||||
"unexpectedError": "An unexpected error occurred: '{{error}}'",
|
"unexpectedError": "An unexpected error occurred: '{{error}}'",
|
||||||
"retry": "Retry",
|
"retry": "Retry",
|
||||||
|
|
||||||
|
"clientScope": {
|
||||||
|
"default": "Default",
|
||||||
|
"optional": "Optional",
|
||||||
|
"none": "None"
|
||||||
|
},
|
||||||
|
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
"manage": "Manage",
|
"manage": "Manage",
|
||||||
"clients": "Clients",
|
"clients": "Clients",
|
||||||
|
|
83
src/components/client-scope/ClientScopeTypes.tsx
Normal file
83
src/components/client-scope/ClientScopeTypes.tsx
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
|
||||||
|
import { TFunction } from "i18next";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { DropdownItem, Select, SelectOption } from "@patternfly/react-core";
|
||||||
|
|
||||||
|
import ClientScopeRepresentation from "keycloak-admin/lib/defs/clientScopeRepresentation";
|
||||||
|
|
||||||
|
export enum ClientScope {
|
||||||
|
default = "default",
|
||||||
|
optional = "optional",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum AllClientScopes {
|
||||||
|
none = "none",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ClientScopeType = ClientScope;
|
||||||
|
export type AllClientScopeType = ClientScope | AllClientScopes;
|
||||||
|
|
||||||
|
const clientScopeTypes = Object.keys(ClientScope);
|
||||||
|
export const allClientScopeTypes = Object.keys({
|
||||||
|
...AllClientScopes,
|
||||||
|
...ClientScope,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const clientScopeTypesSelectOptions = (
|
||||||
|
t: TFunction,
|
||||||
|
scopeTypes: string[] | undefined = clientScopeTypes
|
||||||
|
) =>
|
||||||
|
scopeTypes.map((type) => (
|
||||||
|
<SelectOption key={type} value={type}>
|
||||||
|
{t(`common:clientScope.${type}`)}
|
||||||
|
</SelectOption>
|
||||||
|
));
|
||||||
|
|
||||||
|
export const clientScopeTypesDropdown = (
|
||||||
|
t: TFunction,
|
||||||
|
onClick: (scope: ClientScopeType) => void
|
||||||
|
) =>
|
||||||
|
clientScopeTypes.map((type) => (
|
||||||
|
<DropdownItem key={type} onClick={() => onClick(type as ClientScopeType)}>
|
||||||
|
{t(`common:clientScope.${type}`)}
|
||||||
|
</DropdownItem>
|
||||||
|
));
|
||||||
|
|
||||||
|
type CellDropdownProps = {
|
||||||
|
clientScope: ClientScopeRepresentation;
|
||||||
|
type: ClientScopeType | AllClientScopeType;
|
||||||
|
all?: boolean;
|
||||||
|
onSelect: (value: ClientScopeType | AllClientScopeType) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CellDropdown = ({
|
||||||
|
clientScope,
|
||||||
|
type,
|
||||||
|
onSelect,
|
||||||
|
all = false,
|
||||||
|
}: CellDropdownProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
className={`keycloak__client-scope__${type}`}
|
||||||
|
key={clientScope.id}
|
||||||
|
onToggle={() => setOpen(!open)}
|
||||||
|
isOpen={open}
|
||||||
|
selections={[type]}
|
||||||
|
onSelect={(_, value) => {
|
||||||
|
onSelect(
|
||||||
|
all ? (value as ClientScopeType) : (value as AllClientScopeType)
|
||||||
|
);
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{clientScopeTypesSelectOptions(
|
||||||
|
t,
|
||||||
|
all ? allClientScopeTypes : clientScopeTypes
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
};
|
Loading…
Reference in a new issue