Add applications page to account ui (#4254)
This commit is contained in:
parent
e65a1effda
commit
d241f63a22
3 changed files with 302 additions and 5 deletions
|
@ -1,12 +1,18 @@
|
||||||
{
|
{
|
||||||
"accept": "Accept",
|
"accept": "Accept",
|
||||||
|
"accessGrantedOn": "Access granted on",
|
||||||
"accountSecurity": "Account security",
|
"accountSecurity": "Account security",
|
||||||
"add": "Add",
|
"add": "Add",
|
||||||
"application": "Application",
|
"application": "Application",
|
||||||
|
"applicationDetails": "Application details",
|
||||||
"applications": "Applications",
|
"applications": "Applications",
|
||||||
|
"applicationsIntroMessage": "Track and manage your app permission to access your account",
|
||||||
|
"applicationType": "Application type",
|
||||||
"avatar": "Avatar",
|
"avatar": "Avatar",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
|
"client": "Client",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
|
"description": "Description",
|
||||||
"deviceActivity": "Device activity",
|
"deviceActivity": "Device activity",
|
||||||
"doDeny": "Deny",
|
"doDeny": "Deny",
|
||||||
"done": "Done",
|
"done": "Done",
|
||||||
|
@ -16,16 +22,26 @@
|
||||||
"firstName": "First name",
|
"firstName": "First name",
|
||||||
"fullName": "{{givenName}} {{familyName}}",
|
"fullName": "{{givenName}} {{familyName}}",
|
||||||
"groups": "Groups",
|
"groups": "Groups",
|
||||||
|
"infoMessage": "By clicking Remove Access, you will remove granted permissions of this application. This application will no longer use your information.",
|
||||||
|
"internalApp": "Internal",
|
||||||
|
"inUse": "In use",
|
||||||
"lastName": "Last name",
|
"lastName": "Last name",
|
||||||
"linkedAccounts": "Linked accounts",
|
"linkedAccounts": "Linked accounts",
|
||||||
"logo": "Logo",
|
"logo": "Logo",
|
||||||
"manageAccount": "Manage account",
|
"manageAccount": "Manage account",
|
||||||
"myResources": "My Resources",
|
"myResources": "My Resources",
|
||||||
|
"name": "Name",
|
||||||
|
"notInUse": "Not in use",
|
||||||
|
"offlineAccess": "Offline access",
|
||||||
"permissionRequest": "Permission requests - {{0}}",
|
"permissionRequest": "Permission requests - {{0}}",
|
||||||
"permissionRequests": "Permission requests",
|
"permissionRequests": "Permission requests",
|
||||||
"permissions": "Permissions",
|
"permissions": "Permissions",
|
||||||
"personalInfo": "Personal info",
|
"personalInfo": "Personal info",
|
||||||
"personalInfoDescription": "Manage your basic information",
|
"personalInfoDescription": "Manage your basic information",
|
||||||
|
"privacyPolicy": "Privacy policy",
|
||||||
|
"removeButton": "Remove access",
|
||||||
|
"removeModalMessage": "This will remove the currently granted access permission for {{0}}. You will need to grant access again if you want to use this app.",
|
||||||
|
"removeModalTitle": "Remove access",
|
||||||
"requestor": "Requestor",
|
"requestor": "Requestor",
|
||||||
"required": "Required",
|
"required": "Required",
|
||||||
"resourceAlreadyShared": "Resource is already shared with this user.",
|
"resourceAlreadyShared": "Resource is already shared with this user.",
|
||||||
|
@ -44,6 +60,9 @@
|
||||||
"signOut": "Sign out",
|
"signOut": "Sign out",
|
||||||
"somethingWentWrong": "Something went wrong",
|
"somethingWentWrong": "Something went wrong",
|
||||||
"somethingWentWrongDescription": "Sorry, an unexpected error has occurred.",
|
"somethingWentWrongDescription": "Sorry, an unexpected error has occurred.",
|
||||||
|
"status": "Status",
|
||||||
|
"termsOfService": "Terms of service",
|
||||||
|
"thirdPartyApp": "Third-party",
|
||||||
"tryAgain": "Try again",
|
"tryAgain": "Try again",
|
||||||
"unknownUser": "Anonymous",
|
"unknownUser": "Anonymous",
|
||||||
"unShare": "Unshare all",
|
"unShare": "Unshare all",
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
import { parseResponse } from "./parse-response";
|
import { parseResponse } from "./parse-response";
|
||||||
import { Permission, UserRepresentation } from "./representations";
|
import {
|
||||||
|
ClientRepresentation,
|
||||||
|
Permission,
|
||||||
|
UserRepresentation,
|
||||||
|
} from "./representations";
|
||||||
import { request } from "./request";
|
import { request } from "./request";
|
||||||
|
|
||||||
export type CallOptions = {
|
export type CallOptions = {
|
||||||
|
@ -29,3 +33,14 @@ export async function getPermissionRequests(
|
||||||
|
|
||||||
return parseResponse<Permission[]>(response);
|
return parseResponse<Permission[]>(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getApplications({ signal }: CallOptions = {}): Promise<
|
||||||
|
ClientRepresentation[]
|
||||||
|
> {
|
||||||
|
const response = await request("/applications", { signal });
|
||||||
|
return parseResponse<ClientRepresentation[]>(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteConsent(id: string) {
|
||||||
|
return request(`/applications/${id}/consent`, { method: "DELETE" });
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,270 @@
|
||||||
import { PageSection } from "@patternfly/react-core";
|
import {
|
||||||
|
Button,
|
||||||
|
DataList,
|
||||||
|
DataListCell,
|
||||||
|
DataListContent,
|
||||||
|
DataListItem,
|
||||||
|
DataListItemCells,
|
||||||
|
DataListItemRow,
|
||||||
|
DataListToggle,
|
||||||
|
DescriptionList,
|
||||||
|
DescriptionListDescription,
|
||||||
|
DescriptionListGroup,
|
||||||
|
DescriptionListTerm,
|
||||||
|
Grid,
|
||||||
|
GridItem,
|
||||||
|
Spinner,
|
||||||
|
} from "@patternfly/react-core";
|
||||||
|
import {
|
||||||
|
CheckIcon,
|
||||||
|
ExternalLinkAltIcon,
|
||||||
|
InfoAltIcon,
|
||||||
|
} from "@patternfly/react-icons";
|
||||||
|
import { TFuncKey } from "i18next";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { deleteConsent, getApplications } from "../api/methods";
|
||||||
|
import { ClientRepresentation } from "../api/representations";
|
||||||
|
import { useAlerts } from "../components/alerts/Alerts";
|
||||||
|
import { ContinueCancelModal } from "../components/continue-cancel/ContinueCancelModel";
|
||||||
|
import { Page } from "../components/page/Page";
|
||||||
|
import { usePromise } from "../utils/usePromise";
|
||||||
|
|
||||||
const Applications = () => (
|
type Application = ClientRepresentation & {
|
||||||
<PageSection>This is the applications page.</PageSection>
|
open: boolean;
|
||||||
);
|
};
|
||||||
|
|
||||||
|
const Applications = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { addAlert, addError } = useAlerts();
|
||||||
|
|
||||||
|
const [applications, setApplications] = useState<Application[]>();
|
||||||
|
const [key, setKey] = useState(1);
|
||||||
|
const refresh = () => setKey(key + 1);
|
||||||
|
|
||||||
|
usePromise(
|
||||||
|
(signal) => getApplications({ signal }),
|
||||||
|
(clients) => setApplications(clients.map((c) => ({ ...c, open: false }))),
|
||||||
|
[key]
|
||||||
|
);
|
||||||
|
|
||||||
|
const toggleOpen = (clientId: string) => {
|
||||||
|
setApplications([
|
||||||
|
...applications!.map((a) =>
|
||||||
|
a.clientId === clientId ? { ...a, open: !a.open } : a
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeConsent = async (id: string) => {
|
||||||
|
try {
|
||||||
|
await deleteConsent(id);
|
||||||
|
refresh();
|
||||||
|
addAlert("removeConsentSuccess");
|
||||||
|
} catch (error) {
|
||||||
|
addError("removeConsentError", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!applications) {
|
||||||
|
return <Spinner />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Page title={t("application")} description={t("applicationsIntroMessage")}>
|
||||||
|
<DataList id="applications-list" aria-label={t("application")}>
|
||||||
|
<DataListItem
|
||||||
|
id="applications-list-header"
|
||||||
|
aria-labelledby="Columns names"
|
||||||
|
>
|
||||||
|
<DataListItemRow>
|
||||||
|
<span style={{ visibility: "hidden", height: 55 }}>
|
||||||
|
<DataListToggle
|
||||||
|
id="applications-list-header-invisible-toggle"
|
||||||
|
aria-controls="hidden"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<DataListItemCells
|
||||||
|
dataListCells={[
|
||||||
|
<DataListCell
|
||||||
|
key="applications-list-client-id-header"
|
||||||
|
width={2}
|
||||||
|
className="pf-u-pt-md"
|
||||||
|
>
|
||||||
|
<strong>{t("name")}</strong>
|
||||||
|
</DataListCell>,
|
||||||
|
<DataListCell
|
||||||
|
key="applications-list-app-type-header"
|
||||||
|
width={2}
|
||||||
|
className="pf-u-pt-md"
|
||||||
|
>
|
||||||
|
<strong>{t("applicationType")}</strong>
|
||||||
|
</DataListCell>,
|
||||||
|
<DataListCell
|
||||||
|
key="applications-list-status"
|
||||||
|
width={2}
|
||||||
|
className="pf-u-pt-md"
|
||||||
|
>
|
||||||
|
<strong>{t("status")}</strong>
|
||||||
|
</DataListCell>,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</DataListItemRow>
|
||||||
|
</DataListItem>
|
||||||
|
{applications.map((application) => (
|
||||||
|
<DataListItem
|
||||||
|
key={application.clientId}
|
||||||
|
aria-labelledby="applications-list"
|
||||||
|
isExpanded={application.open}
|
||||||
|
>
|
||||||
|
<DataListItemRow className="pf-u-align-items-center">
|
||||||
|
<DataListToggle
|
||||||
|
onClick={() => toggleOpen(application.clientId)}
|
||||||
|
isExpanded={application.open}
|
||||||
|
id={`toggle-${application.clientId}`}
|
||||||
|
/>
|
||||||
|
<DataListItemCells
|
||||||
|
className="pf-u-align-items-center"
|
||||||
|
dataListCells={[
|
||||||
|
<DataListCell width={2} key={`client${application.clientId}`}>
|
||||||
|
<Button
|
||||||
|
className="pf-u-pl-0 title-case"
|
||||||
|
component="a"
|
||||||
|
variant="link"
|
||||||
|
onClick={() => window.open(application.effectiveUrl)}
|
||||||
|
>
|
||||||
|
{application.clientName || application.clientId}{" "}
|
||||||
|
<ExternalLinkAltIcon />
|
||||||
|
</Button>
|
||||||
|
</DataListCell>,
|
||||||
|
<DataListCell
|
||||||
|
width={2}
|
||||||
|
key={`internal${application.clientId}`}
|
||||||
|
>
|
||||||
|
{application.userConsentRequired
|
||||||
|
? t("thirdPartyApp")
|
||||||
|
: t("internalApp")}
|
||||||
|
{application.offlineAccess ? ", " + t("offlineAccess") : ""}
|
||||||
|
</DataListCell>,
|
||||||
|
<DataListCell width={2} key={`status${application.clientId}`}>
|
||||||
|
{application.inUse ? t("inUse") : t("notInUse")}
|
||||||
|
</DataListCell>,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</DataListItemRow>
|
||||||
|
|
||||||
|
<DataListContent
|
||||||
|
className="pf-u-pl-4xl"
|
||||||
|
aria-label={t("applicationDetails")}
|
||||||
|
isHidden={!application.open}
|
||||||
|
>
|
||||||
|
<DescriptionList>
|
||||||
|
<DescriptionListGroup>
|
||||||
|
<DescriptionListTerm>{t("client")}</DescriptionListTerm>
|
||||||
|
<DescriptionListDescription>
|
||||||
|
{application.clientId}
|
||||||
|
</DescriptionListDescription>
|
||||||
|
</DescriptionListGroup>
|
||||||
|
{application.description && (
|
||||||
|
<DescriptionListGroup>
|
||||||
|
<DescriptionListTerm>
|
||||||
|
{t("description")}
|
||||||
|
</DescriptionListTerm>
|
||||||
|
<DescriptionListDescription>
|
||||||
|
{application.description}
|
||||||
|
</DescriptionListDescription>
|
||||||
|
</DescriptionListGroup>
|
||||||
|
)}
|
||||||
|
{application.effectiveUrl && (
|
||||||
|
<DescriptionListGroup>
|
||||||
|
<DescriptionListTerm>URL</DescriptionListTerm>
|
||||||
|
<DescriptionListDescription>
|
||||||
|
{application.effectiveUrl.split('"')}
|
||||||
|
</DescriptionListDescription>
|
||||||
|
</DescriptionListGroup>
|
||||||
|
)}
|
||||||
|
{application.consent && (
|
||||||
|
<>
|
||||||
|
<DescriptionListGroup>
|
||||||
|
<DescriptionListTerm>Has access to</DescriptionListTerm>
|
||||||
|
{application.consent.grantedScopes.map((scope) => (
|
||||||
|
<DescriptionListDescription key={`scope${scope.id}`}>
|
||||||
|
<CheckIcon /> {t(scope.name as TFuncKey)}
|
||||||
|
</DescriptionListDescription>
|
||||||
|
))}
|
||||||
|
</DescriptionListGroup>
|
||||||
|
{application.tosUri && (
|
||||||
|
<DescriptionListGroup>
|
||||||
|
<DescriptionListTerm>
|
||||||
|
{t("termsOfService")}
|
||||||
|
</DescriptionListTerm>
|
||||||
|
<DescriptionListDescription>
|
||||||
|
{application.tosUri}
|
||||||
|
</DescriptionListDescription>
|
||||||
|
</DescriptionListGroup>
|
||||||
|
)}
|
||||||
|
{application.policyUri && (
|
||||||
|
<DescriptionListGroup>
|
||||||
|
<DescriptionListTerm>
|
||||||
|
{t("privacyPolicy")}
|
||||||
|
</DescriptionListTerm>
|
||||||
|
<DescriptionListDescription>
|
||||||
|
{application.policyUri}
|
||||||
|
</DescriptionListDescription>
|
||||||
|
</DescriptionListGroup>
|
||||||
|
)}
|
||||||
|
{application.logoUri && (
|
||||||
|
<DescriptionListGroup>
|
||||||
|
<DescriptionListTerm>{t("logo")}</DescriptionListTerm>
|
||||||
|
<DescriptionListDescription>
|
||||||
|
<img src={application.logoUri} />
|
||||||
|
</DescriptionListDescription>
|
||||||
|
</DescriptionListGroup>
|
||||||
|
)}
|
||||||
|
<DescriptionListGroup>
|
||||||
|
<DescriptionListTerm>
|
||||||
|
{t("accessGrantedOn") + ": "}
|
||||||
|
</DescriptionListTerm>
|
||||||
|
<DescriptionListDescription>
|
||||||
|
{new Intl.DateTimeFormat("en", {
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
hour: "numeric",
|
||||||
|
minute: "numeric",
|
||||||
|
second: "numeric",
|
||||||
|
}).format(application.consent.createdDate)}
|
||||||
|
</DescriptionListDescription>
|
||||||
|
</DescriptionListGroup>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</DescriptionList>
|
||||||
|
{(application.consent || application.offlineAccess) && (
|
||||||
|
<Grid hasGutter>
|
||||||
|
<hr />
|
||||||
|
<GridItem>
|
||||||
|
<ContinueCancelModal
|
||||||
|
buttonTitle="removeButton"
|
||||||
|
buttonVariant="secondary"
|
||||||
|
modalTitle="removeModalTitle"
|
||||||
|
modalMessage={t("removeModalMessage", [
|
||||||
|
application.clientId,
|
||||||
|
])}
|
||||||
|
continueLabel="confirmButton"
|
||||||
|
onContinue={() => removeConsent(application.clientId)} // required
|
||||||
|
/>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem>
|
||||||
|
<InfoAltIcon /> {t("infoMessage")}
|
||||||
|
</GridItem>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
</DataListContent>
|
||||||
|
</DataListItem>
|
||||||
|
))}
|
||||||
|
</DataList>
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default Applications;
|
export default Applications;
|
||||||
|
|
Loading…
Reference in a new issue