Initial version of the client scope tab for clients (#227)
* initial version of client scope tab for clients * initial version evaluate tab * changed to use new adminCliend and merged default and optional * added add dialog * removed "always" and "required" scopes * better seach types dropdown layout * added switch implementation
This commit is contained in:
parent
ad09c883e3
commit
29a1d82c7f
9 changed files with 543 additions and 8 deletions
|
@ -29,6 +29,8 @@ import {
|
||||||
convertToMultiline,
|
convertToMultiline,
|
||||||
toValue,
|
toValue,
|
||||||
} from "../components/multi-line-input/MultiLineInput";
|
} from "../components/multi-line-input/MultiLineInput";
|
||||||
|
import { ClientScopes } from "./scopes/ClientScopes";
|
||||||
|
import { EvaluateScopes } from "./scopes/EvaluateScopes";
|
||||||
|
|
||||||
export const ClientDetails = () => {
|
export const ClientDetails = () => {
|
||||||
const { t } = useTranslation("clients");
|
const { t } = useTranslation("clients");
|
||||||
|
@ -44,7 +46,8 @@ export const ClientDetails = () => {
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState(0);
|
const [activeTab, setActiveTab] = useState(0);
|
||||||
const [name, setName] = useState("");
|
const [activeTab2, setActiveTab2] = useState(30);
|
||||||
|
const [client, setClient] = useState<ClientRepresentation>();
|
||||||
|
|
||||||
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||||
titleKey: "clients:clientDeleteConfirmTitle",
|
titleKey: "clients:clientDeleteConfirmTitle",
|
||||||
|
@ -83,7 +86,7 @@ export const ClientDetails = () => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const fetchedClient = await adminClient.clients.findOne({ id });
|
const fetchedClient = await adminClient.clients.findOne({ id });
|
||||||
if (fetchedClient) {
|
if (fetchedClient) {
|
||||||
setName(fetchedClient.clientId!);
|
setClient(fetchedClient);
|
||||||
setupForm(fetchedClient);
|
setupForm(fetchedClient);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
@ -133,7 +136,7 @@ export const ClientDetails = () => {
|
||||||
<>
|
<>
|
||||||
<DisableConfirm />
|
<DisableConfirm />
|
||||||
<ViewHeader
|
<ViewHeader
|
||||||
titleKey={name}
|
titleKey={client ? client.clientId! : ""}
|
||||||
subKey="clients:clientsExplain"
|
subKey="clients:clientsExplain"
|
||||||
dropdownItems={[
|
dropdownItems={[
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
|
@ -189,6 +192,31 @@ export const ClientDetails = () => {
|
||||||
<Credentials clientId={id} form={form} save={save} />
|
<Credentials clientId={id} form={form} save={save} />
|
||||||
</Tab>
|
</Tab>
|
||||||
)}
|
)}
|
||||||
|
<Tab
|
||||||
|
eventKey={2}
|
||||||
|
title={<TabTitleText>{t("clientScopes")}</TabTitleText>}
|
||||||
|
>
|
||||||
|
<Tabs
|
||||||
|
activeKey={activeTab2}
|
||||||
|
isSecondary
|
||||||
|
onSelect={(_, key) => setActiveTab2(key as number)}
|
||||||
|
>
|
||||||
|
{client && (
|
||||||
|
<Tab
|
||||||
|
eventKey={30}
|
||||||
|
title={<TabTitleText>{t("setup")}</TabTitleText>}
|
||||||
|
>
|
||||||
|
<ClientScopes clientId={id} protocol={client!.protocol!} />
|
||||||
|
</Tab>
|
||||||
|
)}
|
||||||
|
<Tab
|
||||||
|
eventKey={31}
|
||||||
|
title={<TabTitleText>{t("evaluate")}</TabTitleText>}
|
||||||
|
>
|
||||||
|
<EvaluateScopes />
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</PageSection>
|
</PageSection>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -13,6 +13,24 @@
|
||||||
"downloadAdaptorTitle": "Download adaptor configs",
|
"downloadAdaptorTitle": "Download adaptor configs",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"credentials": "Credentials",
|
"credentials": "Credentials",
|
||||||
|
"clientScopes": "Client scopes",
|
||||||
|
"addClientScope": "Add client scope",
|
||||||
|
"addClientScopesTo": "Add client scopes to {{clientId}}",
|
||||||
|
"searchByName": "Search by name",
|
||||||
|
"setup": "Setup",
|
||||||
|
"evaluate": "Evaluate",
|
||||||
|
"changeTypeTo": "Change type to",
|
||||||
|
"clientScope": {
|
||||||
|
"default" : "Default",
|
||||||
|
"optional" : "Optional"
|
||||||
|
},
|
||||||
|
"clientScopeSearch": {
|
||||||
|
"client": "Client scope",
|
||||||
|
"assigned": "Assigned type"
|
||||||
|
},
|
||||||
|
"emptyClientScopes": "This client doesn't have any added client scopes",
|
||||||
|
"emptyClientScopesInstructions": "There are currently no client scopes linked to this client. You can add existing client scopes to this client to share protocol mappers and roles.",
|
||||||
|
"emptyClientScopesPrimaryAction": "Add client scopes",
|
||||||
"details": "Details",
|
"details": "Details",
|
||||||
"clientList": "Clients",
|
"clientList": "Clients",
|
||||||
"clientSettings": "Client details",
|
"clientSettings": "Client details",
|
||||||
|
|
87
src/clients/scopes/AddScopeDialog.tsx
Normal file
87
src/clients/scopes/AddScopeDialog.tsx
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
ButtonVariant,
|
||||||
|
Dropdown,
|
||||||
|
DropdownToggle,
|
||||||
|
Modal,
|
||||||
|
ModalVariant,
|
||||||
|
DropdownDirection,
|
||||||
|
} from "@patternfly/react-core";
|
||||||
|
import { CaretUpIcon } from "@patternfly/react-icons";
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableHeader,
|
||||||
|
TableVariant,
|
||||||
|
} from "@patternfly/react-table";
|
||||||
|
import ClientScopeRepresentation from "keycloak-admin/lib/defs/clientScopeRepresentation";
|
||||||
|
|
||||||
|
import { clientScopeTypesDropdown } from "./ClientScopeTypes";
|
||||||
|
|
||||||
|
export type AddScopeDialogProps = {
|
||||||
|
clientScopes: ClientScopeRepresentation[];
|
||||||
|
open: boolean;
|
||||||
|
toggleDialog: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AddScopeDialog = ({
|
||||||
|
clientScopes,
|
||||||
|
open,
|
||||||
|
toggleDialog,
|
||||||
|
}: AddScopeDialogProps) => {
|
||||||
|
const { t } = useTranslation("clients");
|
||||||
|
const [addToggle, setAddToggle] = useState(false);
|
||||||
|
|
||||||
|
const data = clientScopes.map((scope) => {
|
||||||
|
return { cells: [scope.name, scope.description] };
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
variant={ModalVariant.medium}
|
||||||
|
title={t("addClientScopesTo", { clientId: "test" })}
|
||||||
|
isOpen={open}
|
||||||
|
onClose={toggleDialog}
|
||||||
|
actions={[
|
||||||
|
<Dropdown
|
||||||
|
id="add-dropdown"
|
||||||
|
key="add-dropdown"
|
||||||
|
direction={DropdownDirection.up}
|
||||||
|
isOpen={addToggle}
|
||||||
|
toggle={
|
||||||
|
<DropdownToggle
|
||||||
|
onToggle={() => setAddToggle(!addToggle)}
|
||||||
|
isPrimary
|
||||||
|
toggleIndicator={CaretUpIcon}
|
||||||
|
id="add-scope-toggle"
|
||||||
|
>
|
||||||
|
{t("common:add")}
|
||||||
|
</DropdownToggle>
|
||||||
|
}
|
||||||
|
dropdownItems={clientScopeTypesDropdown(t)}
|
||||||
|
/>,
|
||||||
|
<Button
|
||||||
|
id="modal-cancel"
|
||||||
|
key="cancel"
|
||||||
|
variant={ButtonVariant.secondary}
|
||||||
|
onClick={toggleDialog}
|
||||||
|
>
|
||||||
|
{t("common:cancel")}
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Table
|
||||||
|
variant={TableVariant.compact}
|
||||||
|
cells={[t("name"), t("description")]}
|
||||||
|
onSelect={(_, isSelected, rowIndex) => {}}
|
||||||
|
rows={data}
|
||||||
|
aria-label={t("chooseAMapperType")}
|
||||||
|
>
|
||||||
|
<TableHeader />
|
||||||
|
<TableBody />
|
||||||
|
</Table>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
22
src/clients/scopes/ClientScopeTypes.tsx
Normal file
22
src/clients/scopes/ClientScopeTypes.tsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
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) =>
|
||||||
|
clientScopeTypes.map((type) => (
|
||||||
|
<DropdownItem key={type}>{t(`clientScope.${type}`)}</DropdownItem>
|
||||||
|
));
|
304
src/clients/scopes/ClientScopes.tsx
Normal file
304
src/clients/scopes/ClientScopes.tsx
Normal file
|
@ -0,0 +1,304 @@
|
||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { TFunction } from "i18next";
|
||||||
|
import {
|
||||||
|
IFormatter,
|
||||||
|
IFormatterValueType,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableHeader,
|
||||||
|
TableVariant,
|
||||||
|
} from "@patternfly/react-table";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Dropdown,
|
||||||
|
DropdownItem,
|
||||||
|
DropdownToggle,
|
||||||
|
Select,
|
||||||
|
Spinner,
|
||||||
|
Split,
|
||||||
|
SplitItem,
|
||||||
|
} from "@patternfly/react-core";
|
||||||
|
import { FilterIcon } from "@patternfly/react-icons";
|
||||||
|
import ClientScopeRepresentation from "keycloak-admin/lib/defs/clientScopeRepresentation";
|
||||||
|
import KeycloakAdminClient from "keycloak-admin";
|
||||||
|
|
||||||
|
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||||
|
import { TableToolbar } from "../../components/table-toolbar/TableToolbar";
|
||||||
|
import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState";
|
||||||
|
import { AddScopeDialog } from "./AddScopeDialog";
|
||||||
|
import {
|
||||||
|
clientScopeTypesSelectOptions,
|
||||||
|
ClientScopeType,
|
||||||
|
ClientScope,
|
||||||
|
} from "./ClientScopeTypes";
|
||||||
|
|
||||||
|
export type ClientScopesProps = {
|
||||||
|
clientId: string;
|
||||||
|
protocol: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const firstUpperCase = (name: string) =>
|
||||||
|
name.charAt(0).toUpperCase() + name.slice(1);
|
||||||
|
|
||||||
|
const changeScope = async (
|
||||||
|
adminClient: KeycloakAdminClient,
|
||||||
|
clientId: string,
|
||||||
|
clientScope: ClientScopeRepresentation,
|
||||||
|
type: ClientScopeType,
|
||||||
|
changeTo: ClientScopeType
|
||||||
|
) => {
|
||||||
|
const typeToName = firstUpperCase(type);
|
||||||
|
const changeToName = firstUpperCase(changeTo);
|
||||||
|
|
||||||
|
const indexedAdminClient = (adminClient.clients as unknown) as {
|
||||||
|
[index: string]: Function;
|
||||||
|
};
|
||||||
|
await indexedAdminClient[`del${typeToName}ClientScope`]({
|
||||||
|
id: clientId,
|
||||||
|
clientScopeId: clientScope.id!,
|
||||||
|
});
|
||||||
|
await indexedAdminClient[`add${changeToName}ClientScope`]({
|
||||||
|
id: clientId,
|
||||||
|
clientScopeId: clientScope.id!,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
type CellDropdownProps = {
|
||||||
|
clientId: string;
|
||||||
|
clientScope: ClientScopeRepresentation;
|
||||||
|
type: ClientScopeType;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CellDropdown = ({ clientId, clientScope, type }: CellDropdownProps) => {
|
||||||
|
const adminClient = useAdminClient();
|
||||||
|
const { t } = useTranslation("clients");
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
key={clientScope.id}
|
||||||
|
onToggle={() => setOpen(!open)}
|
||||||
|
isOpen={open}
|
||||||
|
selections={[type]}
|
||||||
|
onSelect={(_, value) => {
|
||||||
|
changeScope(
|
||||||
|
adminClient,
|
||||||
|
clientId,
|
||||||
|
clientScope,
|
||||||
|
type,
|
||||||
|
value as ClientScopeType
|
||||||
|
);
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{clientScopeTypesSelectOptions(t)}
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type SearchType = "client" | "assigned";
|
||||||
|
|
||||||
|
type TableRow = {
|
||||||
|
clientScope: ClientScopeRepresentation;
|
||||||
|
type: ClientScopeType;
|
||||||
|
cells: (string | undefined)[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ClientScopes = ({ clientId, protocol }: ClientScopesProps) => {
|
||||||
|
const { t } = useTranslation("clients");
|
||||||
|
const adminClient = useAdminClient();
|
||||||
|
const [searchToggle, setSearchToggle] = useState(false);
|
||||||
|
const [searchType, setSearchType] = useState<SearchType>("client");
|
||||||
|
const [addToggle, setAddToggle] = useState(false);
|
||||||
|
const [addDialogOpen, setAddDialogOpen] = useState(false);
|
||||||
|
|
||||||
|
const [rows, setRows] = useState<TableRow[]>();
|
||||||
|
const [rest, setRest] = useState<ClientScopeRepresentation[]>();
|
||||||
|
|
||||||
|
const loader = async () => {
|
||||||
|
const defaultClientScopes = await adminClient.clients.listDefaultClientScopes(
|
||||||
|
{ id: clientId }
|
||||||
|
);
|
||||||
|
const optionalClientScopes = await adminClient.clients.listOptionalClientScopes(
|
||||||
|
{ id: clientId }
|
||||||
|
);
|
||||||
|
const clientScopes = await adminClient.clientScopes.find();
|
||||||
|
|
||||||
|
const find = (id: string) =>
|
||||||
|
clientScopes.find((clientScope) => id === clientScope.id)!;
|
||||||
|
|
||||||
|
const optional = optionalClientScopes.map((c) => {
|
||||||
|
const scope = find(c.id!);
|
||||||
|
return {
|
||||||
|
clientScope: c,
|
||||||
|
type: ClientScope.optional,
|
||||||
|
cells: [c.name, c.id, scope.description],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultScopes = defaultClientScopes.map((c) => {
|
||||||
|
const scope = find(c.id!);
|
||||||
|
return {
|
||||||
|
clientScope: c,
|
||||||
|
type: ClientScope.default,
|
||||||
|
cells: [c.name, c.id, scope.description],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
setRows([...optional, ...defaultScopes]);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loader();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (rows) {
|
||||||
|
loadRest(rows);
|
||||||
|
}
|
||||||
|
}, [rows]);
|
||||||
|
|
||||||
|
const loadRest = async (rows: { cells: (string | undefined)[] }[]) => {
|
||||||
|
const clientScopes = await adminClient.clientScopes.find();
|
||||||
|
const names = rows.map((row) => row.cells[0]);
|
||||||
|
|
||||||
|
setRest(
|
||||||
|
clientScopes
|
||||||
|
.filter((scope) => !names.includes(scope.name))
|
||||||
|
.filter((scope) => scope.protocol === protocol)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const dropdown = (): IFormatter => (data?: IFormatterValueType) => {
|
||||||
|
if (!data) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
const row = rows?.find((row) => row.clientScope.id === data.toString())!;
|
||||||
|
return (
|
||||||
|
<CellDropdown
|
||||||
|
clientId={clientId}
|
||||||
|
clientScope={row.clientScope}
|
||||||
|
type={row.type}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterData = () => {};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!rows && (
|
||||||
|
<div className="pf-u-text-align-center">
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{rows && rows.length > 0 && (
|
||||||
|
<>
|
||||||
|
{rest && (
|
||||||
|
<AddScopeDialog
|
||||||
|
clientScopes={rest}
|
||||||
|
open={addDialogOpen}
|
||||||
|
toggleDialog={() => setAddDialogOpen(!addDialogOpen)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<TableToolbar
|
||||||
|
searchTypeComponent={
|
||||||
|
<Dropdown
|
||||||
|
toggle={
|
||||||
|
<DropdownToggle
|
||||||
|
id="toggle-id"
|
||||||
|
onToggle={() => setSearchToggle(!searchToggle)}
|
||||||
|
>
|
||||||
|
<FilterIcon /> {t(`clientScopeSearch.${searchType}`)}
|
||||||
|
</DropdownToggle>
|
||||||
|
}
|
||||||
|
aria-label="Select Input"
|
||||||
|
isOpen={searchToggle}
|
||||||
|
dropdownItems={[
|
||||||
|
<DropdownItem
|
||||||
|
key="client"
|
||||||
|
onClick={() => {
|
||||||
|
setSearchType("client");
|
||||||
|
setSearchToggle(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("clientScopeSearch.client")}
|
||||||
|
</DropdownItem>,
|
||||||
|
<DropdownItem
|
||||||
|
key="assigned"
|
||||||
|
onClick={() => {
|
||||||
|
setSearchType("assigned");
|
||||||
|
setSearchToggle(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("clientScopeSearch.assigned")}
|
||||||
|
</DropdownItem>,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
inputGroupName="clientsScopeToolbarTextInput"
|
||||||
|
inputGroupPlaceholder={t("searchByName")}
|
||||||
|
inputGroupOnChange={filterData}
|
||||||
|
toolbarItem={
|
||||||
|
<Split hasGutter>
|
||||||
|
<SplitItem>
|
||||||
|
<Button onClick={() => setAddDialogOpen(true)}>
|
||||||
|
{t("addClientScope")}
|
||||||
|
</Button>
|
||||||
|
</SplitItem>
|
||||||
|
<SplitItem>
|
||||||
|
<Select
|
||||||
|
id="add-dropdown"
|
||||||
|
key="add-dropdown"
|
||||||
|
isOpen={addToggle}
|
||||||
|
selections={[]}
|
||||||
|
placeholderText={t("changeTypeTo")}
|
||||||
|
onToggle={() => setAddToggle(!addToggle)}
|
||||||
|
onSelect={(_, value) => {
|
||||||
|
console.log(value);
|
||||||
|
setAddToggle(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{clientScopeTypesSelectOptions(t)}
|
||||||
|
</Select>
|
||||||
|
</SplitItem>
|
||||||
|
</Split>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Table
|
||||||
|
onSelect={() => {}}
|
||||||
|
variant={TableVariant.compact}
|
||||||
|
cells={[
|
||||||
|
t("name"),
|
||||||
|
{ title: t("description"), cellFormatters: [dropdown()] },
|
||||||
|
t("protocol"),
|
||||||
|
]}
|
||||||
|
rows={rows}
|
||||||
|
actions={[
|
||||||
|
{
|
||||||
|
title: t("common:remove"),
|
||||||
|
onClick: () => {},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
aria-label={t("clientScopeList")}
|
||||||
|
>
|
||||||
|
<TableHeader />
|
||||||
|
<TableBody />
|
||||||
|
</Table>
|
||||||
|
</TableToolbar>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{rows && rows.length === 0 && (
|
||||||
|
<ListEmptyState
|
||||||
|
message={t("clients:emptyClientScopes")}
|
||||||
|
instructions={t("clients:emptyClientScopesInstructions")}
|
||||||
|
primaryActionText={t("clients:emptyClientScopesPrimaryAction")}
|
||||||
|
onPrimaryAction={() => {}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
64
src/clients/scopes/EvaluateScopes.tsx
Normal file
64
src/clients/scopes/EvaluateScopes.tsx
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import {
|
||||||
|
ClipboardCopy,
|
||||||
|
Form,
|
||||||
|
FormGroup,
|
||||||
|
Select,
|
||||||
|
SelectOption,
|
||||||
|
SelectVariant,
|
||||||
|
Split,
|
||||||
|
SplitItem,
|
||||||
|
} from "@patternfly/react-core";
|
||||||
|
|
||||||
|
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||||
|
import "./evaluate.css";
|
||||||
|
|
||||||
|
export const EvaluateScopes = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
// const [selected]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form isHorizontal>
|
||||||
|
<FormGroup
|
||||||
|
label={t("rootUrl")}
|
||||||
|
fieldId="kc-root-url"
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="client-scopes-help:protocolMapper"
|
||||||
|
forLabel={t("protocolMapper")}
|
||||||
|
forID="protocolMapper"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Split hasGutter>
|
||||||
|
<SplitItem isFilled>
|
||||||
|
<Select
|
||||||
|
variant={SelectVariant.typeaheadMulti}
|
||||||
|
typeAheadAriaLabel="Select a state"
|
||||||
|
onToggle={() => setIsOpen(!isOpen)}
|
||||||
|
isOpen={isOpen}
|
||||||
|
aria-labelledby="test"
|
||||||
|
placeholderText="Select a state"
|
||||||
|
>
|
||||||
|
{/* {this.state.options.map((option, index) => (
|
||||||
|
<SelectOption
|
||||||
|
isDisabled={option.disabled}
|
||||||
|
key={index}
|
||||||
|
value={option.value}
|
||||||
|
{...(option.description && { description: option.description })}
|
||||||
|
/>
|
||||||
|
))} */}
|
||||||
|
</Select>
|
||||||
|
</SplitItem>
|
||||||
|
<SplitItem>
|
||||||
|
<ClipboardCopy className="keycloak__scopes_evaluate__clipboard-copy">
|
||||||
|
{isOpen}
|
||||||
|
</ClipboardCopy>
|
||||||
|
</SplitItem>
|
||||||
|
</Split>
|
||||||
|
</FormGroup>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
3
src/clients/scopes/evaluate.css
Normal file
3
src/clients/scopes/evaluate.css
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.keycloak__scopes_evaluate__clipboard-copy input {
|
||||||
|
display: none;
|
||||||
|
}
|
|
@ -12,6 +12,7 @@
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"continue": "Continue",
|
"continue": "Continue",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
|
"remove": "Remove",
|
||||||
"search": "Search",
|
"search": "Search",
|
||||||
"next": "Next",
|
"next": "Next",
|
||||||
"back": "Back",
|
"back": "Back",
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
import React, { MouseEventHandler, ReactNode } from "react";
|
import React, {
|
||||||
|
FormEvent,
|
||||||
|
Fragment,
|
||||||
|
MouseEventHandler,
|
||||||
|
ReactNode,
|
||||||
|
} from "react";
|
||||||
import {
|
import {
|
||||||
Toolbar,
|
Toolbar,
|
||||||
ToolbarContent,
|
ToolbarContent,
|
||||||
|
@ -14,12 +19,13 @@ import { useTranslation } from "react-i18next";
|
||||||
type TableToolbarProps = {
|
type TableToolbarProps = {
|
||||||
toolbarItem?: ReactNode;
|
toolbarItem?: ReactNode;
|
||||||
toolbarItemFooter?: ReactNode;
|
toolbarItemFooter?: ReactNode;
|
||||||
children: React.ReactNode;
|
children: ReactNode;
|
||||||
|
searchTypeComponent?: ReactNode;
|
||||||
inputGroupName?: string;
|
inputGroupName?: string;
|
||||||
inputGroupPlaceholder?: string;
|
inputGroupPlaceholder?: string;
|
||||||
inputGroupOnChange?: (
|
inputGroupOnChange?: (
|
||||||
newInput: string,
|
newInput: string,
|
||||||
event: React.FormEvent<HTMLInputElement>
|
event: FormEvent<HTMLInputElement>
|
||||||
) => void;
|
) => void;
|
||||||
inputGroupOnClick?: MouseEventHandler;
|
inputGroupOnClick?: MouseEventHandler;
|
||||||
};
|
};
|
||||||
|
@ -28,6 +34,7 @@ export const TableToolbar = ({
|
||||||
toolbarItem,
|
toolbarItem,
|
||||||
toolbarItemFooter,
|
toolbarItemFooter,
|
||||||
children,
|
children,
|
||||||
|
searchTypeComponent,
|
||||||
inputGroupName,
|
inputGroupName,
|
||||||
inputGroupPlaceholder,
|
inputGroupPlaceholder,
|
||||||
inputGroupOnChange,
|
inputGroupOnChange,
|
||||||
|
@ -38,10 +45,11 @@ export const TableToolbar = ({
|
||||||
<>
|
<>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<ToolbarContent>
|
<ToolbarContent>
|
||||||
<React.Fragment>
|
<Fragment>
|
||||||
{inputGroupName && (
|
{inputGroupName && (
|
||||||
<ToolbarItem>
|
<ToolbarItem>
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
|
{searchTypeComponent}
|
||||||
<TextInput
|
<TextInput
|
||||||
name={inputGroupName}
|
name={inputGroupName}
|
||||||
id={inputGroupName}
|
id={inputGroupName}
|
||||||
|
@ -60,7 +68,7 @@ export const TableToolbar = ({
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</ToolbarItem>
|
</ToolbarItem>
|
||||||
)}
|
)}
|
||||||
</React.Fragment>
|
</Fragment>
|
||||||
{toolbarItem}
|
{toolbarItem}
|
||||||
</ToolbarContent>
|
</ToolbarContent>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
|
Loading…
Reference in a new issue