Fixing UXD design review (#560)
* Fixing UXD design review * Removed divider * Adding type field * Hide "Consent screen text" field when display consent screen is off * Fixed cancel button * fixed type save * type fix
This commit is contained in:
parent
9c82353f37
commit
0f6ce35687
8 changed files with 191 additions and 78 deletions
|
@ -12,7 +12,6 @@ import {
|
|||
ToolbarItem,
|
||||
} from "@patternfly/react-core";
|
||||
import { cellWidth } from "@patternfly/react-table";
|
||||
import type ClientScopeRepresentation from "keycloak-admin/lib/defs/clientScopeRepresentation";
|
||||
|
||||
import { useAdminClient } from "../context/auth/AdminClient";
|
||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||
|
@ -24,58 +23,14 @@ import {
|
|||
CellDropdown,
|
||||
ClientScope,
|
||||
AllClientScopes,
|
||||
AllClientScopeType,
|
||||
ClientScopeDefaultOptionalType,
|
||||
changeScope,
|
||||
removeScope,
|
||||
} from "../components/client-scope/ClientScopeTypes";
|
||||
import type KeycloakAdminClient from "keycloak-admin";
|
||||
import { ChangeTypeDialog } from "./ChangeTypeDialog";
|
||||
|
||||
import "./client-scope.css";
|
||||
|
||||
type ClientScopeDefaultOptionalType = ClientScopeRepresentation & {
|
||||
type: AllClientScopeType;
|
||||
};
|
||||
|
||||
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 = () => {
|
||||
const { t } = useTranslation("client-scopes");
|
||||
const history = useHistory();
|
||||
|
@ -128,6 +83,7 @@ export const ClientScopesSection = () => {
|
|||
onConfirm: async () => {
|
||||
try {
|
||||
for (const scope of selectedScopes) {
|
||||
await removeScope(adminClient, scope);
|
||||
await adminClient.clientScopes.del({ id: scope.id! });
|
||||
}
|
||||
addAlert(t("deletedSuccess"), AlertVariant.success);
|
||||
|
@ -162,9 +118,14 @@ export const ClientScopesSection = () => {
|
|||
</>
|
||||
);
|
||||
|
||||
const ClientScopeDetailLink = (clientScope: ClientScopeRepresentation) => (
|
||||
const ClientScopeDetailLink = (
|
||||
clientScope: ClientScopeDefaultOptionalType
|
||||
) => (
|
||||
<>
|
||||
<Link key={clientScope.id} to={`${url}/${clientScope.id}/settings`}>
|
||||
<Link
|
||||
key={clientScope.id}
|
||||
to={`${url}/${clientScope.id}/${clientScope.type}/settings`}
|
||||
>
|
||||
{clientScope.name}
|
||||
</Link>
|
||||
</>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { useHistory, useParams } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { Controller, useForm, useWatch } from "react-hook-form";
|
||||
import {
|
||||
Form,
|
||||
FormGroup,
|
||||
|
@ -16,23 +16,37 @@ import {
|
|||
} from "@patternfly/react-core";
|
||||
|
||||
import type ClientScopeRepresentation from "keycloak-admin/lib/defs/clientScopeRepresentation";
|
||||
import {
|
||||
clientScopeTypesSelectOptions,
|
||||
allClientScopeTypes,
|
||||
ClientScopeDefaultOptionalType,
|
||||
} from "../../components/client-scope/ClientScopeTypes";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { useLoginProviders } from "../../context/server-info/ServerInfoProvider";
|
||||
import { convertToFormValues } from "../../util";
|
||||
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||
|
||||
type ScopeFormProps = {
|
||||
clientScope: ClientScopeRepresentation;
|
||||
save: (clientScope: ClientScopeRepresentation) => void;
|
||||
save: (clientScope: ClientScopeDefaultOptionalType) => void;
|
||||
};
|
||||
|
||||
export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
|
||||
const { t } = useTranslation("client-scopes");
|
||||
const { register, control, handleSubmit, errors, setValue } = useForm();
|
||||
const history = useHistory();
|
||||
const { realm } = useRealm();
|
||||
|
||||
const providers = useLoginProviders();
|
||||
const [open, isOpen] = useState(false);
|
||||
const [openType, setOpenType] = useState(false);
|
||||
const { id } = useParams<{ id: string }>();
|
||||
|
||||
const displayOnConsentScreen = useWatch({
|
||||
control,
|
||||
name: "attributes.display-on-consent-screen",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
Object.entries(clientScope).map((entry) => {
|
||||
if (entry[0] === "attributes") {
|
||||
|
@ -99,6 +113,38 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
|
|||
name="description"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("type")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="client-scopes-help:type"
|
||||
forLabel={t("type")}
|
||||
forID="type"
|
||||
/>
|
||||
}
|
||||
fieldId="type"
|
||||
>
|
||||
<Controller
|
||||
name="type"
|
||||
defaultValue=""
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<Select
|
||||
id="type"
|
||||
variant={SelectVariant.single}
|
||||
isOpen={openType}
|
||||
selections={value}
|
||||
onToggle={() => setOpenType(!openType)}
|
||||
onSelect={(_, value) => {
|
||||
onChange(value);
|
||||
setOpenType(false);
|
||||
}}
|
||||
>
|
||||
{clientScopeTypesSelectOptions(t, allClientScopeTypes)}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
{!id && (
|
||||
<FormGroup
|
||||
label={t("protocol")}
|
||||
|
@ -168,6 +214,7 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
|
|||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
{displayOnConsentScreen === "true" && (
|
||||
<FormGroup
|
||||
label={t("consentScreenText")}
|
||||
labelIcon={
|
||||
|
@ -186,6 +233,7 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
|
|||
name="attributes.consent-screen-text"
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
<FormGroup
|
||||
hasNoPaddingTop
|
||||
label={t("includeInTokenScope")}
|
||||
|
@ -246,7 +294,10 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
|
|||
<Button variant="primary" type="submit">
|
||||
{t("common:save")}
|
||||
</Button>
|
||||
<Button variant="link" onClick={() => history.push("/client-scopes/")}>
|
||||
<Button
|
||||
variant="link"
|
||||
onClick={() => history.push(`/${realm}/client-scopes`)}
|
||||
>
|
||||
{t("common:cancel")}
|
||||
</Button>
|
||||
</ActionGroup>
|
||||
|
|
|
@ -3,13 +3,14 @@ import { useParams } from "react-router-dom";
|
|||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
AlertVariant,
|
||||
ButtonVariant,
|
||||
DropdownItem,
|
||||
PageSection,
|
||||
Spinner,
|
||||
Tab,
|
||||
TabTitleText,
|
||||
} from "@patternfly/react-core";
|
||||
|
||||
import type ClientScopeRepresentation from "keycloak-admin/lib/defs/clientScopeRepresentation";
|
||||
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
|
||||
import { KeycloakTabs } from "../../components/keycloak-tabs/KeycloakTabs";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
|
@ -17,16 +18,24 @@ import { ViewHeader } from "../../components/view-header/ViewHeader";
|
|||
import { convertFormValuesToObject } from "../../util";
|
||||
import { MapperList } from "../details/MapperList";
|
||||
import { ScopeForm } from "../details/ScopeForm";
|
||||
import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog";
|
||||
import { RoleMapping, Row } from "../../components/role-mapping/RoleMapping";
|
||||
import type { RoleMappingPayload } from "keycloak-admin/lib/defs/roleRepresentation";
|
||||
import {
|
||||
AllClientScopes,
|
||||
changeScope,
|
||||
ClientScopeDefaultOptionalType,
|
||||
} from "../../components/client-scope/ClientScopeTypes";
|
||||
|
||||
export const ClientScopeForm = () => {
|
||||
const { t } = useTranslation("client-scopes");
|
||||
const [clientScope, setClientScope] = useState<ClientScopeRepresentation>();
|
||||
const [clientScope, setClientScope] = useState<
|
||||
ClientScopeDefaultOptionalType
|
||||
>();
|
||||
const [hide, setHide] = useState(false);
|
||||
|
||||
const adminClient = useAdminClient();
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const { id, type } = useParams<{ id: string; type: AllClientScopes }>();
|
||||
|
||||
const { addAlert } = useAlerts();
|
||||
|
||||
|
@ -36,7 +45,10 @@ export const ClientScopeForm = () => {
|
|||
useFetch(
|
||||
async () => {
|
||||
if (id) {
|
||||
return await adminClient.clientScopes.findOne({ id });
|
||||
return {
|
||||
...(await adminClient.clientScopes.findOne({ id })),
|
||||
type,
|
||||
} as ClientScopeDefaultOptionalType;
|
||||
}
|
||||
},
|
||||
(clientScope) => {
|
||||
|
@ -83,7 +95,7 @@ export const ClientScopeForm = () => {
|
|||
];
|
||||
};
|
||||
|
||||
const save = async (clientScopes: ClientScopeRepresentation) => {
|
||||
const save = async (clientScopes: ClientScopeDefaultOptionalType) => {
|
||||
try {
|
||||
clientScopes.attributes = convertFormValuesToObject(
|
||||
clientScopes.attributes!
|
||||
|
@ -91,8 +103,21 @@ export const ClientScopeForm = () => {
|
|||
|
||||
if (id) {
|
||||
await adminClient.clientScopes.update({ id }, clientScopes);
|
||||
changeScope(
|
||||
adminClient,
|
||||
{ ...clientScopes, id, type },
|
||||
clientScopes.type
|
||||
);
|
||||
} else {
|
||||
await adminClient.clientScopes.create(clientScopes);
|
||||
const scope = await adminClient.clientScopes.findOneByName({
|
||||
name: clientScopes.name!,
|
||||
});
|
||||
changeScope(
|
||||
adminClient,
|
||||
{ ...clientScopes, id: scope.id },
|
||||
clientScopes.type
|
||||
);
|
||||
}
|
||||
addAlert(t((id ? "update" : "create") + "Success"), AlertVariant.success);
|
||||
} catch (error) {
|
||||
|
@ -103,6 +128,24 @@ export const ClientScopeForm = () => {
|
|||
}
|
||||
};
|
||||
|
||||
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||
titleKey: t("deleteClientScope", {
|
||||
count: 1,
|
||||
name: clientScope?.name,
|
||||
}),
|
||||
messageKey: "client-scopes:deleteConfirm",
|
||||
continueButtonLabel: "common:delete",
|
||||
continueButtonVariant: ButtonVariant.danger,
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
await adminClient.clientScopes.del({ id });
|
||||
addAlert(t("deletedSuccess"), AlertVariant.success);
|
||||
} catch (error) {
|
||||
addAlert(t("deleteError", { error }), AlertVariant.danger);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const assignRoles = async (rows: Row[]) => {
|
||||
try {
|
||||
const realmRoles = rows
|
||||
|
@ -149,11 +192,16 @@ export const ClientScopeForm = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<DeleteConfirm />
|
||||
<ViewHeader
|
||||
titleKey={
|
||||
clientScope ? clientScope.name! : "client-scopes:createClientScope"
|
||||
}
|
||||
subKey="client-scopes:clientScopeExplain"
|
||||
dropdownItems={[
|
||||
<DropdownItem key="delete" onClick={() => toggleDeleteDialog()}>
|
||||
{t("common:delete")}
|
||||
</DropdownItem>,
|
||||
]}
|
||||
badge={clientScope ? clientScope.protocol : undefined}
|
||||
divider={!id}
|
||||
/>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
"name": "Name of the client scope. Must be unique in the realm. Name should not contain space characters as it is used as value of scope parameter",
|
||||
"description": "Description of the client scope",
|
||||
"protocol": "Which SSO protocol configuration is being supplied by this client scope",
|
||||
"type": "Client scopes, which will be added as default scopes to each created client",
|
||||
"displayOnConsentScreen": "If on, and this client scope is added to some client with consent required, the text specified by 'Consent Screen Text' will be displayed on consent screen. If off, this client scope will not be displayed on the consent screen",
|
||||
"consentScreenText": "Text that will be shown on the consent screen when this client scope is added to some client with consent required. Defaults to name of client scope if it is not filled",
|
||||
"includeInTokenScope": "If on, the name of this client scope will be added to the access token property 'scope' as well as to the Token Introspection Endpoint response. If off, this client scope will be omitted from the token and from the Token Introspection Endpoint response.",
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"searchFor": "Search for client scope",
|
||||
"protocol": "Protocol",
|
||||
"displayOrder": "Display order",
|
||||
"type": "Type",
|
||||
"deleteClientScope": "Delete client scope {{name}}",
|
||||
"deleteClientScope_plural": "Delete {{count}} client scopes",
|
||||
"deleteConfirm": "Are you sure you want to delete this client scope",
|
||||
|
|
|
@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
|
|||
import { DropdownItem, Select, SelectOption } from "@patternfly/react-core";
|
||||
|
||||
import type ClientScopeRepresentation from "keycloak-admin/lib/defs/clientScopeRepresentation";
|
||||
import type KeycloakAdminClient from "keycloak-admin";
|
||||
|
||||
export enum ClientScope {
|
||||
default = "default",
|
||||
|
@ -81,3 +82,48 @@ export const CellDropdown = ({
|
|||
</Select>
|
||||
);
|
||||
};
|
||||
|
||||
export type ClientScopeDefaultOptionalType = ClientScopeRepresentation & {
|
||||
type: AllClientScopeType;
|
||||
};
|
||||
|
||||
export const changeScope = async (
|
||||
adminClient: KeycloakAdminClient,
|
||||
clientScope: ClientScopeDefaultOptionalType,
|
||||
changeTo: AllClientScopeType
|
||||
) => {
|
||||
await removeScope(adminClient, clientScope);
|
||||
await addScope(adminClient, clientScope, changeTo);
|
||||
};
|
||||
|
||||
const castAdminClient = (adminClient: KeycloakAdminClient) =>
|
||||
(adminClient.clientScopes as unknown) as {
|
||||
[index: string]: Function;
|
||||
};
|
||||
|
||||
export 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!,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -406,7 +406,12 @@ export function KeycloakDataTable<T>({
|
|||
{loading && <Loading />}
|
||||
</PaginatingTableToolbar>
|
||||
)}
|
||||
<>{!loading && rows?.length === 0 && search === "" && emptyState}</>
|
||||
<>
|
||||
{!loading &&
|
||||
(filteredData || rows)?.length === 0 &&
|
||||
search === "" &&
|
||||
emptyState}
|
||||
</>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -109,7 +109,7 @@ export const routes: RoutesFn = (t: TFunction) => [
|
|||
access: "view-clients",
|
||||
},
|
||||
{
|
||||
path: "/:realm/client-scopes/:id/:tab",
|
||||
path: "/:realm/client-scopes/:id/:type/:tab",
|
||||
component: ClientScopeForm,
|
||||
breadcrumb: t("client-scopes:clientScopeDetails"),
|
||||
access: "view-clients",
|
||||
|
|
Loading…
Reference in a new issue