Merge pull request #691 from jenny-s51/consentsUIFixes
Consents UI fixes
This commit is contained in:
commit
04bdf4bfa5
4 changed files with 176 additions and 53 deletions
|
@ -1,7 +1,12 @@
|
||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { PageSection } from "@patternfly/react-core";
|
import {
|
||||||
|
AlertVariant,
|
||||||
|
ButtonVariant,
|
||||||
|
Chip,
|
||||||
|
ChipGroup,
|
||||||
|
} from "@patternfly/react-core";
|
||||||
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
|
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
|
||||||
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
|
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
|
||||||
import { emptyFormatter } from "../util";
|
import { emptyFormatter } from "../util";
|
||||||
|
@ -11,9 +16,16 @@ import _ from "lodash";
|
||||||
import type UserConsentRepresentation from "keycloak-admin/lib/defs/userConsentRepresentation";
|
import type UserConsentRepresentation from "keycloak-admin/lib/defs/userConsentRepresentation";
|
||||||
import { CubesIcon } from "@patternfly/react-icons";
|
import { CubesIcon } from "@patternfly/react-icons";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
|
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||||
|
import { useAlerts } from "../components/alert/Alerts";
|
||||||
|
|
||||||
export const UserConsents = () => {
|
export const UserConsents = () => {
|
||||||
|
const [selectedClient, setSelectedClient] = useState<
|
||||||
|
UserConsentRepresentation
|
||||||
|
>();
|
||||||
const { t } = useTranslation("roles");
|
const { t } = useTranslation("roles");
|
||||||
|
const { addAlert } = useAlerts();
|
||||||
|
const [key, setKey] = useState(0);
|
||||||
|
|
||||||
const adminClient = useAdminClient();
|
const adminClient = useAdminClient();
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
|
@ -21,16 +33,31 @@ export const UserConsents = () => {
|
||||||
return _.sortBy(consentsList, (client) => client.clientId?.toUpperCase());
|
return _.sortBy(consentsList, (client) => client.clientId?.toUpperCase());
|
||||||
};
|
};
|
||||||
|
|
||||||
const loader = async () => {
|
const refresh = () => setKey(new Date().getTime());
|
||||||
const consents = await adminClient.users.listConsents({ id });
|
|
||||||
|
|
||||||
return alphabetize(consents);
|
const loader = async () => {
|
||||||
|
const getConsents = await adminClient.users.listConsents({ id });
|
||||||
|
|
||||||
|
return alphabetize(getConsents);
|
||||||
};
|
};
|
||||||
|
|
||||||
const clientScopesRenderer = ({
|
const clientScopesRenderer = ({
|
||||||
grantedClientScopes,
|
grantedClientScopes,
|
||||||
}: UserConsentRepresentation) => {
|
}: UserConsentRepresentation) => {
|
||||||
return <>{grantedClientScopes!.join(", ")}</>;
|
return (
|
||||||
|
<ChipGroup className="kc-consents-chip-group">
|
||||||
|
{grantedClientScopes!.map((currentChip) => (
|
||||||
|
<Chip
|
||||||
|
key={currentChip}
|
||||||
|
isReadOnly
|
||||||
|
className="kc-consents-chip"
|
||||||
|
id="consents-chip-text"
|
||||||
|
>
|
||||||
|
{currentChip}
|
||||||
|
</Chip>
|
||||||
|
))}
|
||||||
|
</ChipGroup>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const createdRenderer = ({ createDate }: UserConsentRepresentation) => {
|
const createdRenderer = ({ createDate }: UserConsentRepresentation) => {
|
||||||
|
@ -43,52 +70,85 @@ export const UserConsents = () => {
|
||||||
return <>{moment(lastUpdatedDate).format("MM/DD/YY hh:MM A")}</>;
|
return <>{moment(lastUpdatedDate).format("MM/DD/YY hh:MM A")}</>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||||
|
titleKey: "users:revokeClientScopesTitle",
|
||||||
|
messageKey: t("users:revokeClientScopes", {
|
||||||
|
clientId: selectedClient?.clientId,
|
||||||
|
}),
|
||||||
|
continueButtonLabel: "common:delete",
|
||||||
|
continueButtonVariant: ButtonVariant.danger,
|
||||||
|
onConfirm: async () => {
|
||||||
|
try {
|
||||||
|
await adminClient.users.revokeConsent({
|
||||||
|
id,
|
||||||
|
clientId: selectedClient!.clientId!,
|
||||||
|
});
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
|
||||||
|
addAlert(t("deleteGrantsSuccess"), AlertVariant.success);
|
||||||
|
} catch (error) {
|
||||||
|
addAlert(t("deleteGrantsError", { error }), AlertVariant.danger);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageSection variant="light">
|
<DeleteConfirm />
|
||||||
<KeycloakDataTable
|
<KeycloakDataTable
|
||||||
loader={loader}
|
loader={loader}
|
||||||
ariaLabelKey="roles:roleList"
|
key={key}
|
||||||
columns={[
|
ariaLabelKey="roles:roleList"
|
||||||
{
|
searchPlaceholderKey=" "
|
||||||
name: "clientId",
|
columns={[
|
||||||
displayKey: "clients:Client",
|
{
|
||||||
cellFormatters: [emptyFormatter()],
|
name: "clientId",
|
||||||
transforms: [cellWidth(20)],
|
displayKey: "clients:Client",
|
||||||
|
cellFormatters: [emptyFormatter()],
|
||||||
|
transforms: [cellWidth(20)],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "grantedClientScopes",
|
||||||
|
displayKey: "client-scopes:grantedClientScopes",
|
||||||
|
cellFormatters: [emptyFormatter()],
|
||||||
|
cellRenderer: clientScopesRenderer,
|
||||||
|
transforms: [cellWidth(30)],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "createdDate",
|
||||||
|
displayKey: "clients:created",
|
||||||
|
cellFormatters: [emptyFormatter()],
|
||||||
|
cellRenderer: createdRenderer,
|
||||||
|
transforms: [cellWidth(20)],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "lastUpdatedDate",
|
||||||
|
displayKey: "clients:lastUpdated",
|
||||||
|
cellFormatters: [emptyFormatter()],
|
||||||
|
cellRenderer: lastUpdatedRenderer,
|
||||||
|
transforms: [cellWidth(10)],
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
actions={[
|
||||||
|
{
|
||||||
|
title: t("users:revoke"),
|
||||||
|
onRowClick: (client) => {
|
||||||
|
setSelectedClient(client);
|
||||||
|
toggleDeleteDialog();
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
name: "grantedClientScopes",
|
]}
|
||||||
displayKey: "client-scopes:grantedClientScopes",
|
emptyState={
|
||||||
cellFormatters: [emptyFormatter()],
|
<ListEmptyState
|
||||||
cellRenderer: clientScopesRenderer,
|
hasIcon={true}
|
||||||
transforms: [cellWidth(30)],
|
icon={CubesIcon}
|
||||||
},
|
message={t("users:noConsents")}
|
||||||
{
|
instructions={t("users:noConsentsText")}
|
||||||
name: "createdDate",
|
onPrimaryAction={() => {}}
|
||||||
displayKey: "clients:created",
|
/>
|
||||||
cellFormatters: [emptyFormatter()],
|
}
|
||||||
cellRenderer: createdRenderer,
|
/>
|
||||||
transforms: [cellWidth(20)],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "lastUpdatedDate",
|
|
||||||
displayKey: "clients:lastUpdated",
|
|
||||||
cellFormatters: [emptyFormatter()],
|
|
||||||
cellRenderer: lastUpdatedRenderer,
|
|
||||||
transforms: [cellWidth(20)],
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
emptyState={
|
|
||||||
<ListEmptyState
|
|
||||||
hasIcon={true}
|
|
||||||
icon={CubesIcon}
|
|
||||||
message={t("users:noConsents")}
|
|
||||||
instructions={t("users:noConsentsText")}
|
|
||||||
onPrimaryAction={() => {}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</PageSection>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -108,9 +108,7 @@ export const UsersTabs = () => {
|
||||||
data-testid="user-consents-tab"
|
data-testid="user-consents-tab"
|
||||||
title={<TabTitleText>{t("users:consents")}</TabTitleText>}
|
title={<TabTitleText>{t("users:consents")}</TabTitleText>}
|
||||||
>
|
>
|
||||||
<PageSection variant="light">
|
<UserConsents />
|
||||||
<UserConsents />
|
|
||||||
</PageSection>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
</KeycloakTabs>
|
</KeycloakTabs>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -60,6 +60,11 @@
|
||||||
"noConsents": "No consents",
|
"noConsents": "No consents",
|
||||||
"noConsentsText": "The consents will only be recorded when users try to access a client that is configured to require consent. In that case, users will get a consent page which asks them to grant access to the client.",
|
"noConsentsText": "The consents will only be recorded when users try to access a client that is configured to require consent. In that case, users will get a consent page which asks them to grant access to the client.",
|
||||||
"whoWillAppearLinkText": "Who will appear in this group list?",
|
"whoWillAppearLinkText": "Who will appear in this group list?",
|
||||||
"whoWillAppearPopoverText": "Groups are hierarchical. When you select Direct Membership, you see only the child group that the user joined. Ancestor groups are not included."
|
"whoWillAppearPopoverText": "Groups are hierarchical. When you select Direct Membership, you see only the child group that the user joined. Ancestor groups are not included.",
|
||||||
|
"revoke": "Revoke",
|
||||||
|
"revokeClientScopesTitle": "Revoke all granted client scopes?",
|
||||||
|
"revokeClientScopes": "Are you sure you want to revoke all granted client scopes for {{clientId}}?",
|
||||||
|
"deleteGrantsSuccess": "Grants successfully revoked.",
|
||||||
|
"deleteGrantsError": "Error deleting grants."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,3 +45,63 @@ td.pf-c-table__check > input {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
vertical-align: text-top;
|
vertical-align: text-top;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pf-c-toolbar__content-section {
|
||||||
|
margin-bottom: calc(var(--pf-global--spacer--lg) * -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-c-chip.kc-consents-chip::before {
|
||||||
|
padding: 0px;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-c-chip.kc-consents-chip {
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-c-chip-group__list-item {
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#consents-chip-text.pf-c-chip-text {
|
||||||
|
font-size: var(--pf-c-table--cell--FontSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kc-consents-chip > .pf-c-chip__text {
|
||||||
|
font-size: var(--pf-c-table--cell--FontSize);
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-c-chip-group.kc-consents-chip-group
|
||||||
|
> div.pf-c-chip-group__main
|
||||||
|
> ul.pf-c-chip-group__list
|
||||||
|
li:first-child
|
||||||
|
.pf-c-chip__text::before {
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-c-chip-group.kc-consents-chip-group
|
||||||
|
> div.pf-c-chip-group__main
|
||||||
|
> ul.pf-c-chip-group__list
|
||||||
|
.pf-c-chip__text::before {
|
||||||
|
content: ", ";
|
||||||
|
}
|
||||||
|
|
||||||
|
div.pf-c-chip-group.kc-consents-chip-group
|
||||||
|
> div.pf-c-chip-group__main
|
||||||
|
> ul.pf-c-chip-group__list
|
||||||
|
> li.pf-c-chip-group__list-item:last-child
|
||||||
|
> button.pf-c-chip.pf-m-overflow::before {
|
||||||
|
content: "";
|
||||||
|
margin-left: var(--pf-global--spacer--sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
div.pf-c-chip-group.kc-consents-chip-group
|
||||||
|
> div.pf-c-chip-group__main
|
||||||
|
> ul.pf-c-chip-group__list
|
||||||
|
> li.pf-c-chip-group__list-item:last-child
|
||||||
|
> button.pf-c-chip.pf-m-overflow
|
||||||
|
> span::before {
|
||||||
|
content: "";
|
||||||
|
margin-left: var(--pf-global--spacer--sm);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue