diff --git a/apps/account-ui/public/locales/en/translation.json b/apps/account-ui/public/locales/en/translation.json index 5db85fb805..bcdfaac96b 100644 --- a/apps/account-ui/public/locales/en/translation.json +++ b/apps/account-ui/public/locales/en/translation.json @@ -19,6 +19,7 @@ "description": "Description", "device-activity": "Device activity", "deviceActivity": "Device activity", + "directMembership": "Direct membership", "doDeny": "Deny", "done": "Done", "doSignOut": "Sign out", @@ -29,6 +30,8 @@ "filterByName": "Filter By Name ...", "firstName": "First name", "fullName": "{{givenName}} {{familyName}}", + "groupDescriptionLabel": "View groups that you are associated with", + "groupLabel": "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", @@ -47,6 +50,8 @@ "manageAccount": "Manage account", "myResources": "My Resources", "name": "Name", + "noGroups": "No groups", + "noGroupsText": "You are not joined in any group", "notInUse": "Not in use", "notSetUp": "{{0}} is not set up.", "offlineAccess": "Offline access", @@ -55,6 +60,7 @@ "password-display-name": "Password", "password-help-text": "Sign in by entering your password.", "password": "My password", + "path": "Path", "permissionRequest": "Permission requests - {{0}}", "permissionRequests": "Permission requests", "permissions": "Permissions", diff --git a/apps/account-ui/src/account-security/LinkedAccounts.tsx b/apps/account-ui/src/account-security/LinkedAccounts.tsx index 379da42448..f13aec9320 100644 --- a/apps/account-ui/src/account-security/LinkedAccounts.tsx +++ b/apps/account-ui/src/account-security/LinkedAccounts.tsx @@ -1,15 +1,10 @@ -import { - DataList, - PageSection, - Stack, - StackItem, - Title, -} from "@patternfly/react-core"; +import { DataList, Stack, StackItem, Title } from "@patternfly/react-core"; import { useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { getLinkedAccounts } from "../api/methods"; import { LinkedAccountRepresentation } from "../api/representations"; import { EmptyRow } from "../components/datalist/EmptyRow"; +import { Page } from "../components/page/Page"; import { usePromise } from "../utils/usePromise"; import { AccountRow } from "./AccountRow"; @@ -33,7 +28,10 @@ const LinkedAccounts = () => { ); return ( - + @@ -73,7 +71,7 @@ const LinkedAccounts = () => { </DataList> </StackItem> </Stack> - </PageSection> + </Page> ); }; diff --git a/apps/account-ui/src/api/methods.ts b/apps/account-ui/src/api/methods.ts index 9733725edd..b0ac4547a2 100644 --- a/apps/account-ui/src/api/methods.ts +++ b/apps/account-ui/src/api/methods.ts @@ -6,6 +6,7 @@ import { CredentialContainer, CredentialRepresentation, DeviceRepresentation, + Group, LinkedAccountRepresentation, Permission, UserRepresentation, @@ -103,3 +104,10 @@ export async function linkAccount(account: LinkedAccountRepresentation) { }); return parseResponse<{ accountLinkUri: string }>(response); } + +export async function getGroups({ signal }: CallOptions) { + const response = await request("/groups", { + signal, + }); + return parseResponse<Group[]>(response); +} diff --git a/apps/account-ui/src/api/representations.ts b/apps/account-ui/src/api/representations.ts index 8a79605594..047e23d40e 100644 --- a/apps/account-ui/src/api/representations.ts +++ b/apps/account-ui/src/api/representations.ts @@ -202,3 +202,9 @@ export interface Permissions { permissions: Permission[]; row?: number; } + +export interface Group { + id?: string; + name: string; + path: string; +} diff --git a/apps/account-ui/src/groups/Groups.tsx b/apps/account-ui/src/groups/Groups.tsx index a86b2ece3f..acddaecbfc 100644 --- a/apps/account-ui/src/groups/Groups.tsx +++ b/apps/account-ui/src/groups/Groups.tsx @@ -1,5 +1,133 @@ -import { PageSection } from "@patternfly/react-core"; +import { + Checkbox, + DataList, + DataListCell, + DataListItem, + DataListItemCells, + DataListItemRow, +} from "@patternfly/react-core"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { getGroups } from "../api/methods"; +import { Group } from "../api/representations"; +import { Page } from "../components/page/Page"; +import { usePromise } from "../utils/usePromise"; -const Groups = () => <PageSection>This is the groups page.</PageSection>; +const Groups = () => { + const { t } = useTranslation(); + + const [groups, setGroups] = useState<Group[]>([]); + const [directMembership, setDirectMembership] = useState(false); + + usePromise( + (signal) => getGroups({ signal }), + (groups) => { + if (directMembership) { + groups.forEach((el) => + getParents( + el, + groups, + groups.map(({ path }) => path) + ) + ); + } + setGroups(groups); + }, + [directMembership] + ); + + const getParents = (el: Group, groups: Group[], groupsPaths: string[]) => { + const parentPath = el.path.slice(0, el.path.lastIndexOf("/")); + if (parentPath && !groupsPaths.includes(parentPath)) { + el = { + name: parentPath.slice(parentPath.lastIndexOf("/") + 1), + path: parentPath, + }; + groups.push(el); + groupsPaths.push(parentPath); + + getParents(el, groups, groupsPaths); + } + }; + + return ( + <Page title={t("groupLabel")} description={t("groupDescriptionLabel")}> + <DataList id="groups-list" aria-label={t("groupLabel")} isCompact> + <DataListItem id="groups-list-header" aria-labelledby="Columns names"> + <DataListItemRow> + <DataListItemCells + dataListCells={[ + <DataListCell key="directMembership-header"> + <Checkbox + label={t("directMembership")} + id="directMembership-checkbox" + isChecked={directMembership} + onChange={(checked) => setDirectMembership(checked)} + /> + </DataListCell>, + ]} + /> + </DataListItemRow> + </DataListItem> + <DataListItem id="groups-list-header" aria-labelledby="Columns names"> + <DataListItemRow> + <DataListItemCells + dataListCells={[ + <DataListCell key="group-name-header" width={2}> + <strong>{t("name")}</strong> + </DataListCell>, + <DataListCell key="group-path-header" width={2}> + <strong>{t("path")}</strong> + </DataListCell>, + <DataListCell key="group-direct-membership-header" width={2}> + <strong>{t("directMembership")}</strong> + </DataListCell>, + ]} + /> + </DataListItemRow> + </DataListItem> + {groups.map((group, appIndex) => ( + <DataListItem + id={`${appIndex}-group`} + key={"group-" + appIndex} + aria-labelledby="groups-list" + > + <DataListItemRow> + <DataListItemCells + dataListCells={[ + <DataListCell + id={`${appIndex}-group-name`} + width={2} + key={"name-" + appIndex} + > + {group.name} + </DataListCell>, + <DataListCell + id={`${appIndex}-group-path`} + width={2} + key={"path-" + appIndex} + > + {group.path} + </DataListCell>, + <DataListCell + id={`${appIndex}-group-directMembership`} + width={2} + key={"directMembership-" + appIndex} + > + <Checkbox + id={`${appIndex}-checkbox-directMembership`} + isChecked={group.id != null} + isDisabled={true} + /> + </DataListCell>, + ]} + /> + </DataListItemRow> + </DataListItem> + ))} + </DataList> + </Page> + ); +}; export default Groups;