From 45292a08b556d21278fe73ec83e97af5b76082cf Mon Sep 17 00:00:00 2001 From: Jillian Date: Mon, 28 Oct 2024 14:53:48 -0700 Subject: [PATCH 01/36] Access user guide: collapse everything but current accordion on click (#173) --- src/pages/Home.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 5caca6a..e4237b2 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -119,13 +119,19 @@ interface AccordionMakerProps { } function AccordionMaker(props: AccordionMakerProps) { + const [expanded, setExpanded] = React.useState(false); + + const handleChange = (panel: string) => (event: React.SyntheticEvent, newExpanded: boolean) => { + setExpanded(newExpanded ? panel : false); + }; + return ( <> {sections[props.which][0]} {Object.entries(guide[props.which]).map(([key, value]: [string, string]) => ( - + } aria-controls={'panel-content' + key} From 66b3d65b38bad39f48851ef995180d3d39738ed2 Mon Sep 17 00:00:00 2001 From: Jill Date: Mon, 28 Oct 2024 11:53:18 -0700 Subject: [PATCH 02/36] break out app group header component for readability --- src/App.tsx | 2 +- src/pages/apps/app_detail/app_header.tsx | 76 +++++++++++++++ .../apps/{Read.tsx => app_detail/index.tsx} | 92 +++++-------------- 3 files changed, 102 insertions(+), 68 deletions(-) create mode 100644 src/pages/apps/app_detail/app_header.tsx rename src/pages/apps/{Read.tsx => app_detail/index.tsx} (88%) diff --git a/src/App.tsx b/src/App.tsx index e8df44e..ef269a5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -31,7 +31,7 @@ import ListTags from './pages/tags/List'; import ListUsers from './pages/users/List'; import NavItems from './components/NavItems'; import NotFound from './pages/NotFound'; -import ReadApp from './pages/apps/Read'; +import {ReadApp} from './pages/apps/app_detail'; import ReadGroup from './pages/groups/Read'; import ReadTag from './pages/tags/Read'; import ReadUser from './pages/users/Read'; diff --git a/src/pages/apps/app_detail/app_header.tsx b/src/pages/apps/app_detail/app_header.tsx new file mode 100644 index 0000000..5440ec1 --- /dev/null +++ b/src/pages/apps/app_detail/app_header.tsx @@ -0,0 +1,76 @@ +import {Grid, Paper, Typography, Box, Chip, Stack, Tooltip} from '@mui/material'; +import {grey} from '@mui/material/colors'; +import CreateUpdateApp from '../CreateUpdate'; +import DeleteApp from '../Delete'; +import {App} from '../../../api/apiSchemas'; +import {useNavigate} from 'react-router-dom'; + +import TagIcon from '@mui/icons-material/LocalOffer'; +import {useCurrentUser} from '../../../authentication'; +import {MoveTooltip} from '.'; + +interface AppHeaderProps { + app: App; + moveTooltip: MoveTooltip; +} + +const AppHeader: React.FC = ({app, moveTooltip}) => { + const currentUser = useCurrentUser(); + const navigate = useNavigate(); + + const classNames = `app-detail-header app-detail-header ${app.name}`; + + return ( + + + + {app.name} + + + {app.description} + + {app.active_app_tags ? ( + + {app.active_app_tags.map((tagMap) => ( + navigate(`/tags/${tagMap.active_tag!.name}`)} + icon={} + sx={{ + margin: '2px', + marginTop: '5px', + bgcolor: tagMap.active_tag!.enabled ? 'primary' : grey[500], + }} + /> + ))} + + ) : null} + + +
+ +
+
+ +
+ +
+
+
+
+
+ ); +}; + +export default AppHeader; diff --git a/src/pages/apps/Read.tsx b/src/pages/apps/app_detail/index.tsx similarity index 88% rename from src/pages/apps/Read.tsx rename to src/pages/apps/app_detail/index.tsx index 118ba49..7eadba4 100644 --- a/src/pages/apps/Read.tsx +++ b/src/pages/apps/app_detail/index.tsx @@ -19,22 +19,19 @@ import TableRow from '@mui/material/TableRow'; import Tooltip from '@mui/material/Tooltip'; import Typography from '@mui/material/Typography'; -import TagIcon from '@mui/icons-material/LocalOffer'; +import Ending from '../../../components/Ending'; +import {groupBy, displayUserName} from '../../../helpers'; +import {isAccessAdmin, isAppOwnerGroupOwner} from '../../../authorization'; +import {useGetAppById} from '../../../api/apiComponents'; +import {App, OktaUserGroupMember} from '../../../api/apiSchemas'; -import {grey} from '@mui/material/colors'; - -import Ending from '../../components/Ending'; -import {groupBy, displayUserName} from '../../helpers'; -import {isAccessAdmin, isAppOwnerGroupOwner} from '../../authorization'; -import {useGetAppById} from '../../api/apiComponents'; -import {App, OktaUserGroupMember} from '../../api/apiSchemas'; - -import {useCurrentUser} from '../../authentication'; -import CreateUpdateGroup from '../groups/CreateUpdate'; -import CreateUpdateApp from './CreateUpdate'; -import DeleteApp from './Delete'; -import NotFound from '../NotFound'; -import Loading from '../../components/Loading'; +import {useCurrentUser} from '../../../authentication'; +import CreateUpdateGroup from '../../groups/CreateUpdate'; +import CreateUpdateApp from '../CreateUpdate'; +import DeleteApp from '../Delete'; +import NotFound from '../../NotFound'; +import Loading from '../../../components/Loading'; +import AppHeader from './app_header'; function sortGroupMembers( [aUserId, aUsers]: [string, Array], @@ -51,7 +48,16 @@ function groupMemberships( return groupBy(memberships ?? [], 'active_user.id'); } -export default function ReadApp() { +export type Modifier = { + name: string; + options: {}; +}; + +export type MoveTooltip = { + modifiers: Modifier[]; +}; + +export const ReadApp = () => { const currentUser = useCurrentUser(); const {id} = useParams(); @@ -71,61 +77,13 @@ export default function ReadApp() { const app = data ?? ({} as App); - const moveTooltip = {modifiers: [{name: 'offset', options: {offset: [0, -10]}}]}; + const moveTooltip: MoveTooltip = {modifiers: [{name: 'offset', options: {offset: [0, -10]}}]}; return ( - - - - {app.name} - - - {app.description} - - {app.active_app_tags ? ( - - {app.active_app_tags.map((tagMap) => ( - navigate(`/tags/${tagMap.active_tag!.name}`)} - icon={} - sx={{ - margin: '2px', - marginTop: '5px', - bgcolor: tagMap.active_tag!.enabled ? 'primary' : grey[500], - }} - /> - ))} - - ) : null} - - -
- -
-
- -
- -
-
-
-
-
+ {isAccessAdmin(currentUser) || isAppOwnerGroupOwner(currentUser, app.id ?? '') ? (
); -} +}; From 0ed08eea74c6025eac0e1e97f22e4a8045461b57 Mon Sep 17 00:00:00 2001 From: Jill Date: Mon, 28 Oct 2024 13:39:02 -0700 Subject: [PATCH 03/36] update paths, set min width on header to prevent icon overlap on small screens --- src/App.tsx | 4 ++-- .../{app_detail/app_header.tsx => detail/header.tsx} | 9 +++++---- src/pages/apps/{app_detail => detail}/index.tsx | 4 ++-- 3 files changed, 9 insertions(+), 8 deletions(-) rename src/pages/apps/{app_detail/app_header.tsx => detail/header.tsx} (92%) rename src/pages/apps/{app_detail => detail}/index.tsx (99%) diff --git a/src/App.tsx b/src/App.tsx index ef269a5..e95cff2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -31,7 +31,7 @@ import ListTags from './pages/tags/List'; import ListUsers from './pages/users/List'; import NavItems from './components/NavItems'; import NotFound from './pages/NotFound'; -import {ReadApp} from './pages/apps/app_detail'; +import {ReadApp} from './pages/apps/detail'; import ReadGroup from './pages/groups/Read'; import ReadTag from './pages/tags/Read'; import ReadUser from './pages/users/Read'; @@ -95,7 +95,7 @@ function Dashboard() { }; return ( - + = ({app, moveTooltip}) => { +const AppsHeader: React.FC = ({app, moveTooltip}) => { const currentUser = useCurrentUser(); const navigate = useNavigate(); @@ -25,7 +25,8 @@ const AppHeader: React.FC = ({app, moveTooltip}) => { = ({app, moveTooltip}) => { ); }; -export default AppHeader; +export default AppsHeader; diff --git a/src/pages/apps/app_detail/index.tsx b/src/pages/apps/detail/index.tsx similarity index 99% rename from src/pages/apps/app_detail/index.tsx rename to src/pages/apps/detail/index.tsx index 7eadba4..1786678 100644 --- a/src/pages/apps/app_detail/index.tsx +++ b/src/pages/apps/detail/index.tsx @@ -31,7 +31,7 @@ import CreateUpdateApp from '../CreateUpdate'; import DeleteApp from '../Delete'; import NotFound from '../../NotFound'; import Loading from '../../../components/Loading'; -import AppHeader from './app_header'; +import AppsHeader from './header'; function sortGroupMembers( [aUserId, aUsers]: [string, Array], @@ -83,7 +83,7 @@ export const ReadApp = () => { - + {isAccessAdmin(currentUser) || isAppOwnerGroupOwner(currentUser, app.id ?? '') ? ( Date: Mon, 28 Oct 2024 13:59:21 -0700 Subject: [PATCH 04/36] Break out admin action button group into sub component on apps page, part 0 of breaking down the group tables into accordions --- .../apps/detail/AppsAdminActionGroup.tsx | 23 ++++++++++++++ .../detail/{header.tsx => AppsHeader.tsx} | 7 ++--- src/pages/apps/detail/index.tsx | 30 +++++-------------- 3 files changed, 34 insertions(+), 26 deletions(-) create mode 100644 src/pages/apps/detail/AppsAdminActionGroup.tsx rename src/pages/apps/detail/{header.tsx => AppsHeader.tsx} (93%) diff --git a/src/pages/apps/detail/AppsAdminActionGroup.tsx b/src/pages/apps/detail/AppsAdminActionGroup.tsx new file mode 100644 index 0000000..caece40 --- /dev/null +++ b/src/pages/apps/detail/AppsAdminActionGroup.tsx @@ -0,0 +1,23 @@ +import {Grid, Paper} from '@mui/material'; +import CreateUpdateGroup from '../../groups/CreateUpdate'; +import {OktaUser, App} from '../../../api/apiSchemas'; + +interface AppsAdminActionGroupProps { + currentUser: OktaUser; + app: App; +} + +export const AppsAdminActionGroup: React.FC = ({currentUser, app}) => { + return ( + + + + + + ); +}; diff --git a/src/pages/apps/detail/header.tsx b/src/pages/apps/detail/AppsHeader.tsx similarity index 93% rename from src/pages/apps/detail/header.tsx rename to src/pages/apps/detail/AppsHeader.tsx index d7f763d..9eb0de6 100644 --- a/src/pages/apps/detail/header.tsx +++ b/src/pages/apps/detail/AppsHeader.tsx @@ -2,20 +2,19 @@ import {Grid, Paper, Typography, Box, Chip, Stack, Tooltip} from '@mui/material' import {grey} from '@mui/material/colors'; import CreateUpdateApp from '../CreateUpdate'; import DeleteApp from '../Delete'; -import {App} from '../../../api/apiSchemas'; +import {App, OktaUser} from '../../../api/apiSchemas'; import {useNavigate} from 'react-router-dom'; import TagIcon from '@mui/icons-material/LocalOffer'; -import {useCurrentUser} from '../../../authentication'; import {MoveTooltip} from '.'; interface AppsHeaderProps { app: App; moveTooltip: MoveTooltip; + currentUser: OktaUser; } -const AppsHeader: React.FC = ({app, moveTooltip}) => { - const currentUser = useCurrentUser(); +const AppsHeader: React.FC = ({app, moveTooltip, currentUser}) => { const navigate = useNavigate(); const classNames = `app-detail-header app-detail-header ${app.name}`; diff --git a/src/pages/apps/detail/index.tsx b/src/pages/apps/detail/index.tsx index 1786678..a545bdc 100644 --- a/src/pages/apps/detail/index.tsx +++ b/src/pages/apps/detail/index.tsx @@ -1,5 +1,5 @@ import React, {useEffect, useState} from 'react'; -import {Link as RouterLink, useNavigate, useParams} from 'react-router-dom'; +import {Link as RouterLink, useParams} from 'react-router-dom'; import Box from '@mui/material/Box'; import Chip from '@mui/material/Chip'; @@ -26,12 +26,10 @@ import {useGetAppById} from '../../../api/apiComponents'; import {App, OktaUserGroupMember} from '../../../api/apiSchemas'; import {useCurrentUser} from '../../../authentication'; -import CreateUpdateGroup from '../../groups/CreateUpdate'; -import CreateUpdateApp from '../CreateUpdate'; -import DeleteApp from '../Delete'; import NotFound from '../../NotFound'; import Loading from '../../../components/Loading'; -import AppsHeader from './header'; +import AppsHeader from './AppsHeader'; +import {AppsAdminActionGroup} from './AppsAdminActionGroup'; function sortGroupMembers( [aUserId, aUsers]: [string, Array], @@ -61,7 +59,6 @@ export const ReadApp = () => { const currentUser = useCurrentUser(); const {id} = useParams(); - const navigate = useNavigate(); const {data, isError, isLoading} = useGetAppById({ pathParams: {appId: id ?? ''}, @@ -83,22 +80,11 @@ export const ReadApp = () => { - - {isAccessAdmin(currentUser) || isAppOwnerGroupOwner(currentUser, app.id ?? '') ? ( - - - - - - ) : null} + + {isAccessAdmin(currentUser) || + (isAppOwnerGroupOwner(currentUser, app.id ?? '') && ( + + ))} {app.active_owner_app_groups?.map((appGroup) => ( From 2f7c44db792373525280b75ee513a1c04ea5e1af Mon Sep 17 00:00:00 2001 From: Jill Date: Tue, 29 Oct 2024 10:56:26 -0700 Subject: [PATCH 05/36] App detail: remove duplicate components, replace with EmptyListEntry component --- src/pages/apps/detail/index.tsx | 40 ++++++++++++++------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/src/pages/apps/detail/index.tsx b/src/pages/apps/detail/index.tsx index a545bdc..71fe954 100644 --- a/src/pages/apps/detail/index.tsx +++ b/src/pages/apps/detail/index.tsx @@ -55,6 +55,18 @@ export type MoveTooltip = { modifiers: Modifier[]; }; +export const EmptyListEntry = () => { + return ( + + + + None + + + + ); +}; + export const ReadApp = () => { const currentUser = useCurrentUser(); @@ -162,13 +174,7 @@ export const ReadApp = () => { )) ) : ( - - - - None - - - + )} @@ -252,13 +258,7 @@ export const ReadApp = () => { )) ) : ( - - - - None - - - + )} @@ -356,7 +356,7 @@ export const ReadApp = () => { )} - + @@ -436,17 +436,11 @@ export const ReadApp = () => { )) ) : ( - - - - None - - - + )} - + From f63e77d6d4eab42f66e3b850f342e764be30f715 Mon Sep 17 00:00:00 2001 From: Jill Date: Tue, 29 Oct 2024 11:08:28 -0700 Subject: [PATCH 06/36] replace duplicate empty list entries with emptylistentry component --- src/components/EmptyListEntry.tsx | 17 +++++++++++++++++ src/pages/apps/detail/index.tsx | 15 ++------------- src/pages/groups/Read.tsx | 9 ++------- src/pages/requests/Read.tsx | 9 ++------- src/pages/tags/Read.tsx | 31 +++++++------------------------ src/pages/users/Read.tsx | 9 ++------- 6 files changed, 32 insertions(+), 58 deletions(-) create mode 100644 src/components/EmptyListEntry.tsx diff --git a/src/components/EmptyListEntry.tsx b/src/components/EmptyListEntry.tsx new file mode 100644 index 0000000..045f0c1 --- /dev/null +++ b/src/components/EmptyListEntry.tsx @@ -0,0 +1,17 @@ +import {TableRow, TableCell, Typography, TableCellProps} from '@mui/material'; + +interface EmptyListEntryProps { + cellProps?: TableCellProps; +} + +export const EmptyListEntry: React.FC = ({cellProps}) => { + return ( + + + + None + + + + ); +}; diff --git a/src/pages/apps/detail/index.tsx b/src/pages/apps/detail/index.tsx index 71fe954..4424de1 100644 --- a/src/pages/apps/detail/index.tsx +++ b/src/pages/apps/detail/index.tsx @@ -30,6 +30,7 @@ import NotFound from '../../NotFound'; import Loading from '../../../components/Loading'; import AppsHeader from './AppsHeader'; import {AppsAdminActionGroup} from './AppsAdminActionGroup'; +import {EmptyListEntry} from '../../../components/EmptyListEntry'; function sortGroupMembers( [aUserId, aUsers]: [string, Array], @@ -55,18 +56,6 @@ export type MoveTooltip = { modifiers: Modifier[]; }; -export const EmptyListEntry = () => { - return ( - - - - None - - - - ); -}; - export const ReadApp = () => { const currentUser = useCurrentUser(); @@ -262,7 +251,7 @@ export const ReadApp = () => { )} - + diff --git a/src/pages/groups/Read.tsx b/src/pages/groups/Read.tsx index 2dc2f53..19f5be5 100644 --- a/src/pages/groups/Read.tsx +++ b/src/pages/groups/Read.tsx @@ -59,6 +59,7 @@ import { GroupMember, } from '../../api/apiSchemas'; import {canManageGroup} from '../../authorization'; +import {EmptyListEntry} from '../../components/EmptyListEntry'; function sortGroupMembers( [aUserId, aUsers]: [string, Array], @@ -437,13 +438,7 @@ export default function ReadGroup() { )) ) : ( - - - - None - - - + )} diff --git a/src/pages/requests/Read.tsx b/src/pages/requests/Read.tsx index 0efb29b..ef3c292 100644 --- a/src/pages/requests/Read.tsx +++ b/src/pages/requests/Read.tsx @@ -72,6 +72,7 @@ import { import NotFound from '../NotFound'; import Loading from '../../components/Loading'; +import {EmptyListEntry} from '../../components/EmptyListEntry'; dayjs.extend(RelativeTime); dayjs.extend(IsSameOrBefore); @@ -775,13 +776,7 @@ export default function ReadRequest() { )) ) : ( - - - - None - - - + )} diff --git a/src/pages/tags/Read.tsx b/src/pages/tags/Read.tsx index 323a531..e2c695f 100644 --- a/src/pages/tags/Read.tsx +++ b/src/pages/tags/Read.tsx @@ -38,6 +38,7 @@ import CreateUpdateTag from './CreateUpdate'; import NotFound from '../NotFound'; import Loading from '../../components/Loading'; import DeleteTag from './Delete'; +import {EmptyListEntry} from '../../components/EmptyListEntry'; export default function ReadTag() { const currentUser = useCurrentUser(); @@ -218,17 +219,11 @@ export default function ReadTag() { )) ) : ( - - - - None - - - + )} - + @@ -295,17 +290,11 @@ export default function ReadTag() { )) ) : ( - - - - None - - - + )} - + @@ -398,17 +387,11 @@ export default function ReadTag() { )) ) : ( - - - - None - - - + )} - + diff --git a/src/pages/users/Read.tsx b/src/pages/users/Read.tsx index fd642e2..753a539 100644 --- a/src/pages/users/Read.tsx +++ b/src/pages/users/Read.tsx @@ -38,6 +38,7 @@ import RemoveGroupsDialog, {RemoveGroupsDialogParameters} from '../roles/RemoveG import RemoveOwnDirectAccessDialog, {RemoveOwnDirectAccessDialogParameters} from '../groups/RemoveOwnDirectAccess'; import {groupBy, displayUserName, displayGroupType} from '../../helpers'; import {canManageGroup, isGroupOwner} from '../../authorization'; +import {EmptyListEntry} from '../../components/EmptyListEntry'; function sortUserGroups( [aGroupId, aGroups]: [string, Array], @@ -235,13 +236,7 @@ function OwnerTable({user, ownerships, onClickRemoveGroupFromRole, onClickRemove )) ) : ( - - - - None - - - + )} From d4a6ec070e01cf4b9acafb17cc96f5e147461c81 Mon Sep 17 00:00:00 2001 From: Jill Date: Tue, 29 Oct 2024 11:51:07 -0700 Subject: [PATCH 07/36] Add props for adding custom empty list text --- src/components/EmptyListEntry.tsx | 5 +++-- src/pages/apps/detail/index.tsx | 2 +- src/pages/users/Read.tsx | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/EmptyListEntry.tsx b/src/components/EmptyListEntry.tsx index 045f0c1..c00f2c3 100644 --- a/src/components/EmptyListEntry.tsx +++ b/src/components/EmptyListEntry.tsx @@ -2,14 +2,15 @@ import {TableRow, TableCell, Typography, TableCellProps} from '@mui/material'; interface EmptyListEntryProps { cellProps?: TableCellProps; + customText?: string; } -export const EmptyListEntry: React.FC = ({cellProps}) => { +export const EmptyListEntry: React.FC = ({cellProps, customText}) => { return ( - None + {customText || 'None'} diff --git a/src/pages/apps/detail/index.tsx b/src/pages/apps/detail/index.tsx index 4424de1..421124b 100644 --- a/src/pages/apps/detail/index.tsx +++ b/src/pages/apps/detail/index.tsx @@ -167,7 +167,7 @@ export const ReadApp = () => { )} - + diff --git a/src/pages/users/Read.tsx b/src/pages/users/Read.tsx index 753a539..a2c6425 100644 --- a/src/pages/users/Read.tsx +++ b/src/pages/users/Read.tsx @@ -240,7 +240,7 @@ function OwnerTable({user, ownerships, onClickRemoveGroupFromRole, onClickRemove )} - + From 1719825c72b6a5d6ee5c1aaf5f6ac9c86491a8e4 Mon Sep 17 00:00:00 2001 From: Jill Date: Tue, 29 Oct 2024 13:50:20 -0700 Subject: [PATCH 08/36] App page: initial demo accordion, fix missing admin group button --- src/components/AccordionListGroup.tsx | 114 +++++++++++++++++++++ src/pages/apps/detail/index.tsx | 140 ++++++++++++++------------ 2 files changed, 189 insertions(+), 65 deletions(-) create mode 100644 src/components/AccordionListGroup.tsx diff --git a/src/components/AccordionListGroup.tsx b/src/components/AccordionListGroup.tsx new file mode 100644 index 0000000..9febe87 --- /dev/null +++ b/src/components/AccordionListGroup.tsx @@ -0,0 +1,114 @@ +import {Accordion, AccordionDetails, AccordionSummary} from '@mui/material'; +import {AppGroup, OktaUserGroupMember} from '../api/apiSchemas'; + +interface AccordionListGroupProps { + group_name: string; + owner_group: AppGroup; + member_group: AppGroup; +} + +export const AccordionListGroup: React.FC = ({group_name, owner_group, member_group}) => { + return null; +}; +/* + {app.active_owner_app_groups?.map((appGroup) => ( + + + + + }> + + + + + + + App Owners + + + + Can manage app and implicitly own all app groups + + + + + + + Total Owners: {Object.keys(groupMemberships(appGroup.active_user_ownerships)).length} + + + +
+
+ + + + + + + + + Name + Email + Ending + + + + {Object.keys(groupMemberships(appGroup.active_user_ownerships)).length > 0 ? ( + Object.entries(groupMemberships(appGroup.active_user_ownerships)) + .sort(sortGroupMembers) + .map(([userId, users]: [string, Array]) => ( + + + + {displayUserName(users[0].active_user)} + + + + + {users[0].active_user?.email.toLowerCase()} + + + + + + + )) + ) : ( + + )} + + + + + +
+ +
+
+
) +} +*/ diff --git a/src/pages/apps/detail/index.tsx b/src/pages/apps/detail/index.tsx index 421124b..8b9dcde 100644 --- a/src/pages/apps/detail/index.tsx +++ b/src/pages/apps/detail/index.tsx @@ -1,6 +1,7 @@ -import React, {useEffect, useState} from 'react'; +import React from 'react'; import {Link as RouterLink, useParams} from 'react-router-dom'; +import {Accordion, AccordionDetails, AccordionSummary} from '@mui/material'; import Box from '@mui/material/Box'; import Chip from '@mui/material/Chip'; import Container from '@mui/material/Container'; @@ -31,6 +32,7 @@ import Loading from '../../../components/Loading'; import AppsHeader from './AppsHeader'; import {AppsAdminActionGroup} from './AppsAdminActionGroup'; import {EmptyListEntry} from '../../../components/EmptyListEntry'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; function sortGroupMembers( [aUserId, aUsers]: [string, Array], @@ -65,6 +67,12 @@ export const ReadApp = () => { pathParams: {appId: id ?? ''}, }); + const [expanded, setExpanded] = React.useState(false); + + const handleChange = (panel: string) => (event: React.SyntheticEvent, isExpanded: boolean) => { + setExpanded(isExpanded ? panel : false); + }; + if (isError) { return ; } @@ -73,7 +81,7 @@ export const ReadApp = () => { return ; } - const app = data ?? ({} as App); + const app: App = data ?? ({} as App); const moveTooltip: MoveTooltip = {modifiers: [{name: 'offset', options: {offset: [0, -10]}}]}; @@ -82,16 +90,15 @@ export const ReadApp = () => { - {isAccessAdmin(currentUser) || - (isAppOwnerGroupOwner(currentUser, app.id ?? '') && ( - - ))} + {(isAccessAdmin(currentUser) || isAppOwnerGroupOwner(currentUser, app.id ?? '')) && ( + + )} {app.active_owner_app_groups?.map((appGroup) => ( - - - + + }> +
@@ -103,7 +110,7 @@ export const ReadApp = () => { color: 'inherit', }} component={RouterLink}> - App Owners + {appGroup.name} @@ -123,54 +130,63 @@ export const ReadApp = () => { - - Name - Email - Ending - - - - {Object.keys(groupMemberships(appGroup.active_user_ownerships)).length > 0 ? ( - Object.entries(groupMemberships(appGroup.active_user_ownerships)) - .sort(sortGroupMembers) - .map(([userId, users]: [string, Array]) => ( - - - - {displayUserName(users[0].active_user)} - - - - - {users[0].active_user?.email.toLowerCase()} - - - - - - - )) - ) : ( - - )} - - - - -
-
+ +
+ + + + + + Name + Email + Ending + + + + {Object.keys(groupMemberships(appGroup.active_user_ownerships)).length > 0 ? ( + Object.entries(groupMemberships(appGroup.active_user_ownerships)) + .sort(sortGroupMembers) + .map(([userId, users]: [string, Array]) => ( + + + + {displayUserName(users[0].active_user)} + + + + + {users[0].active_user?.email.toLowerCase()} + + + + + + + )) + ) : ( + + )} + + + + + +
+
+
+
@@ -335,13 +351,7 @@ export const ReadApp = () => { )) ) : ( - - - - All app owners are implicitly app group owners - - - + )} From 813fcd484f6c633a9d77a85c03961a2f165d8d7f Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Tue, 29 Oct 2024 15:05:42 -0700 Subject: [PATCH 09/36] [feat] dark mode and theme toggle (#174) --- src/App.tsx | 107 +++++++++++++++++++++++-- src/components/Breadcrumbs.tsx | 2 +- src/components/BulkRenewalDataGrid.tsx | 44 ++++++++++ src/components/DateRange.tsx | 29 ++++--- src/components/NumberInput.tsx | 62 ++++---------- src/index.tsx | 53 +----------- src/mui.d.ts | 19 +++++ src/pages/ComingSoon.tsx | 2 +- src/pages/Error.tsx | 2 +- src/pages/Home.tsx | 8 +- src/pages/NotFound.tsx | 2 +- src/pages/apps/List.tsx | 2 +- src/pages/apps/Read.tsx | 29 ++++--- src/pages/groups/AddRoles.tsx | 4 +- src/pages/groups/AddUsers.tsx | 4 +- src/pages/groups/Audit.tsx | 10 ++- src/pages/groups/BulkRenewal.tsx | 49 +---------- src/pages/groups/Expiring.tsx | 9 +-- src/pages/groups/List.tsx | 2 +- src/pages/groups/Read.tsx | 25 +++--- src/pages/requests/Create.tsx | 2 +- src/pages/requests/List.tsx | 10 ++- src/pages/requests/Read.tsx | 14 ++-- src/pages/roles/AddGroups.tsx | 4 +- src/pages/roles/Audit.tsx | 8 +- src/pages/roles/BulkRenewal.tsx | 51 ++---------- src/pages/roles/Expiring.tsx | 9 +-- src/pages/roles/List.tsx | 2 +- src/pages/tags/List.tsx | 2 +- src/pages/tags/Read.tsx | 17 ++-- src/pages/users/Audit.tsx | 8 +- src/pages/users/List.tsx | 2 +- src/pages/users/Read.tsx | 8 +- tsconfig.json | 2 +- 34 files changed, 304 insertions(+), 299 deletions(-) create mode 100644 src/components/BulkRenewalDataGrid.tsx create mode 100644 src/mui.d.ts diff --git a/src/App.tsx b/src/App.tsx index e8df44e..e5aa55a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import {Link as RouterLink, Route, Routes} from 'react-router-dom'; -import {styled} from '@mui/material/styles'; +import {createTheme, styled, ThemeProvider} from '@mui/material/styles'; import Link from '@mui/material/Link'; import MuiDrawer from '@mui/material/Drawer'; import Box from '@mui/material/Box'; @@ -37,6 +37,19 @@ import ReadTag from './pages/tags/Read'; import ReadUser from './pages/users/Read'; import {useCurrentUser} from './authentication'; import ReadRequest from './pages/requests/Read'; +import { + alpha, + CssBaseline, + PaletteMode, + Stack, + ToggleButton, + ToggleButtonGroup, + Tooltip, + useMediaQuery, + useTheme, +} from '@mui/material'; +import {DarkMode, LightMode} from '@mui/icons-material'; +import {lightGreen, red, yellow} from '@mui/material/colors'; const drawerWidth: number = 240; @@ -88,7 +101,33 @@ const Drawer = styled(MuiDrawer, { }, })); -function Dashboard() { +function ThemeToggle({setThemeMode}: {setThemeMode: (theme: PaletteMode) => void}) { + const theme = useTheme(); + return ( + + + setThemeMode('light')} + aria-label="Light mode"> + + + + + setThemeMode('dark')} + aria-label="Dark mode"> + + + + + ); +} + +function Dashboard({setThemeMode}: {setThemeMode: (theme: PaletteMode) => void}) { const [open, setOpen] = React.useState(true); const toggleDrawer = () => { setOpen(!open); @@ -137,7 +176,7 @@ function Dashboard() { textDecoration: 'none', }}> - + ACCESS @@ -149,12 +188,15 @@ function Dashboard() { + + + - theme.palette.mode === 'light' ? theme.palette.grey[100] : theme.palette.grey[900], + theme.palette.mode === 'light' ? theme.palette.grey[200] : theme.palette.grey[800], flexGrow: 1, height: '100vh', overflow: 'auto', @@ -189,6 +231,61 @@ function Dashboard() { } export default function App() { + const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)'); + const initialMode = prefersDarkMode ? 'dark' : 'light'; + const [mode, setMode] = React.useState(initialMode); + + // See https://discord.com/branding + let theme = React.useMemo(() => { + const base = createTheme({ + palette: { + mode, + primary: { + main: '#5865F2', + light: '#A5B2FF', + }, + secondary: { + main: '#EB459E', + }, + error: { + main: '#ED4245', + }, + warning: { + main: '#FEE75C', + }, + success: { + main: '#57F287', + }, + text: { + accent: mode === 'light' ? '#5865F2' : '#A5B2FF', + }, + }, + }); + return createTheme(base, { + palette: { + highlight: { + success: base.palette.augmentColor({ + color: {main: mode === 'light' ? lightGreen[100] : alpha(lightGreen[500], 0.3)}, + name: 'success', + }), + warning: base.palette.augmentColor({ + color: {main: mode === 'light' ? yellow[100] : alpha(yellow[500], 0.3)}, + name: 'warning', + }), + danger: base.palette.augmentColor({ + color: {main: mode === 'light' ? red[100] : alpha(red[500], 0.3)}, + name: 'danger', + }), + }, + }, + }); + }, [mode]); + useCurrentUser(); - return ; + return ( + + + + + ); } diff --git a/src/components/Breadcrumbs.tsx b/src/components/Breadcrumbs.tsx index 2f60a8b..c199dce 100644 --- a/src/components/Breadcrumbs.tsx +++ b/src/components/Breadcrumbs.tsx @@ -1,7 +1,7 @@ import Breadcrumbs from '@mui/material/Breadcrumbs'; import Typography from '@mui/material/Typography'; -import Link, {LinkProps} from '@mui/material/Link'; +import Link from '@mui/material/Link'; import {Link as RouterLink, useLocation} from 'react-router-dom'; export default function Crumbs() { diff --git a/src/components/BulkRenewalDataGrid.tsx b/src/components/BulkRenewalDataGrid.tsx new file mode 100644 index 0000000..94c11be --- /dev/null +++ b/src/components/BulkRenewalDataGrid.tsx @@ -0,0 +1,44 @@ +import {darken, lighten, PaletteColor, styled} from '@mui/material'; +import {DataGrid} from '@mui/x-data-grid'; + +const getHoverBackgroundColor = (color: PaletteColor, mode: string) => (mode === 'dark' ? color.dark : color.light); + +const getSelectedBackgroundColor = (color: PaletteColor, mode: string) => + mode === 'dark' ? darken(color.dark, 0.5) : lighten(color.light, 0.5); + +const getSelectedHoverBackgroundColor = (color: PaletteColor, mode: string) => + mode === 'dark' ? darken(color.dark, 0.4) : lighten(color.light, 0.4); + +const BulkRenewalDataGrid = styled(DataGrid)( + ({ + theme: { + palette: {highlight, mode}, + }, + }) => ({ + '& .super-app-theme--Expired': { + backgroundColor: highlight.danger.main, + '&:hover': { + backgroundColor: getHoverBackgroundColor(highlight.danger, mode), + }, + '&.Mui-selected': { + backgroundColor: getSelectedBackgroundColor(highlight.danger, mode), + '&:hover': { + backgroundColor: getSelectedHoverBackgroundColor(highlight.danger, mode), + }, + }, + }, + '& .super-app-theme--Soon': { + backgroundColor: highlight.warning.main, + '&:hover': { + backgroundColor: getHoverBackgroundColor(highlight.warning, mode), + }, + '&.Mui-selected': { + backgroundColor: getSelectedBackgroundColor(highlight.warning, mode), + '&:hover': { + backgroundColor: getSelectedHoverBackgroundColor(highlight.warning, mode), + }, + }, + }, + }), +); +export default BulkRenewalDataGrid; diff --git a/src/components/DateRange.tsx b/src/components/DateRange.tsx index 852627c..ec22390 100644 --- a/src/components/DateRange.tsx +++ b/src/components/DateRange.tsx @@ -10,12 +10,10 @@ import EventIcon from '@mui/icons-material/Event'; import {Dayjs} from 'dayjs'; import {DatePicker, DatePickerProps} from '@mui/x-date-pickers/DatePicker'; -import {PickersDay, PickersDayProps, pickersDayClasses} from '@mui/x-date-pickers/PickersDay'; +import {PickersDay, PickersDayProps} from '@mui/x-date-pickers/PickersDay'; import {UseDateFieldProps} from '@mui/x-date-pickers/DateField'; import {BaseSingleInputFieldProps, DateValidationError, FieldSection} from '@mui/x-date-pickers/models'; -import {grey} from '@mui/material/colors'; - function HighlightDay(props: PickersDayProps & {startDate?: Dayjs; endDate?: Dayjs; rangeSelected: boolean}) { const {startDate, endDate, rangeSelected, ...rest} = props; @@ -35,22 +33,26 @@ function HighlightDay(props: PickersDayProps & {startDate?: Dayjs; endDat let selectedClass = ''; let style = {}; + let dayStyle = {}; if (rangeSelected && props.day.isSame(endDate!, 'day')) { selectedClass = 'Mui-selected'; } if (isSelected) { - style = {backgroundColor: 'primary_extra_light.main'}; + style = {backgroundColor: theme.palette.primary.light}; + dayStyle = {color: theme.palette.common.black}; } else if (start) { - style = {background: `linear-gradient(90deg, white 50%, ${theme.palette.primary_extra_light.main} 50%)`}; + style = { + background: `linear-gradient(90deg, transparent 50%, ${theme.palette.primary.light} 50%)`, + }; } else if (end) { - style = {background: `linear-gradient(90deg, ${theme.palette.primary_extra_light.main} 50%, white 50%)`}; + style = {background: `linear-gradient(90deg, ${theme.palette.primary.light} 50%, transparent 50%)`}; } return ( - + ); } @@ -78,6 +80,7 @@ function ButtonField(props: ButtonFieldProps) { inputProps: {'aria-label': ariaLabel} = {}, } = props; + const theme = useTheme(); let displayString = ''; if (rangeSelected) { @@ -97,8 +100,8 @@ function ButtonField(props: ButtonFieldProps) { aria-label={ariaLabel} onClick={() => setOpen?.((prev) => !prev)} sx={{ - color: grey[600], - borderColor: grey[300], + color: theme.palette.text.secondary, + borderColor: theme.palette.action.disabled, height: '48.5px', minWidth: '245px', fontSize: '15px', @@ -106,17 +109,19 @@ function ButtonField(props: ButtonFieldProps) { zIndex: '1', padding: '0 8px', }}> - {displayString} + {displayString} ` font-family: 'IBM Plex Sans', sans-serif; font-weight: 400; - border-radius: 8px; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; - background: ${theme.palette.mode === 'dark' ? grey[900] : '#fff'}; - border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - box-shadow: 0px 2px 4px ${theme.palette.mode === 'dark' ? 'rgba(0,0,0, 0.5)' : 'rgba(0,0,0, 0.05)'}; + border-radius: 4px; + border: 1px solid ${theme.palette.action.disabled}; display: grid; grid-template-columns: auto 1fr auto 19px; grid-template-rows: 1fr 1fr; @@ -86,12 +82,12 @@ const InputRoot = styled('div')( padding: 4px; &.${numberInputClasses.focused} { - border-color: ${theme.palette.primary.main}; - box-shadow: 0 0 0 3px ${theme.palette.primary_extra_light.main}; + border: 1px solid transparent; + outline: 2px solid ${theme.palette.primary.main}; } - &:hover { - border-color: ${theme.palette.primary.main}; + &:hover:not(.${numberInputClasses.focused}) { + border-color: ${theme.palette.action.active}; } // firefox @@ -108,7 +104,7 @@ const InputElement = styled('input')( font-weight: 400; line-height: 1.5; grid-row: 1/3; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; + color: ${theme.palette.text.primary}; background: inherit; border: none; border-radius: inherit; @@ -131,58 +127,30 @@ const Button = styled('button')( font-size: 0.875rem; line-height: 1; box-sizing: border-box; - background: ${theme.palette.mode === 'dark' ? grey[900] : 'white'}; - border: 0; - color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]}; + border: 1px solid ${theme.palette.action.disabled}; transition-property: all; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 120ms; + color: ${theme.palette.text.primary}; + background-color: transparent; + grid-column: 4/5; &:hover { - background: ${theme.palette.mode === 'dark' ? grey[800] : grey[50]}; - border-color: ${theme.palette.mode === 'dark' ? grey[600] : grey[300]}; + background: ${theme.palette.action.hover}; cursor: pointer; } &.${numberInputClasses.incrementButton} { - grid-column: 4/5; grid-row: 1/2; border-top-left-radius: 4px; border-top-right-radius: 4px; - border: 1px solid; - border-bottom: 0; - border-color: ${grey[200]}; - background: ${theme.palette.mode === 'dark' ? grey[900] : undefined}; - color: ${theme.palette.mode === 'dark' ? grey[200] : grey[600]}; - - &:hover { - cursor: pointer; - color: #FFF; - background: ${theme.palette.primary.main}; - border-color: ${theme.palette.primary.main}; - } + border-bottom: 0px; } &.${numberInputClasses.decrementButton} { - grid-column: 4/5; grid-row: 2/3; border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; - border: 1px solid; - border-color: ${theme.palette.mode === 'dark' ? grey[700] : grey[200]}; - background: ${theme.palette.mode === 'dark' ? grey[900] : undefined}; - color: ${theme.palette.mode === 'dark' ? grey[200] : grey[600]}; - - &:hover { - cursor: pointer; - color: #FFF; - background: ${theme.palette.primary.main}; - border-color: ${theme.palette.primary.main}; - } - } - - & .arrow { - transform: translateY(-1px); } & .arrow { diff --git a/src/index.tsx b/src/index.tsx index 070fb55..5ec4ccc 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,8 +2,6 @@ import * as React from 'react'; import {QueryClient, QueryClientProvider} from '@tanstack/react-query'; import {createRoot} from 'react-dom/client'; import {BrowserRouter} from 'react-router-dom'; -import {ThemeProvider, createTheme} from '@mui/material/styles'; -import CssBaseline from '@mui/material/CssBaseline'; import {AdapterDayjs} from '@mui/x-date-pickers/AdapterDayjs'; import {LocalizationProvider} from '@mui/x-date-pickers'; import * as Sentry from '@sentry/react'; @@ -17,46 +15,6 @@ const queryClient = new QueryClient({ }, }); -declare module '@mui/material/styles' { - interface Palette { - primary_extra_light: Palette['primary']; - } - - interface PaletteOptions { - primary_extra_light?: PaletteOptions['primary']; - } -} - -declare module '@mui/material/Button' { - interface ButtonPropsColorOverrides { - primary_extra_light: true; - } -} - -// See https://discord.com/branding -let theme = createTheme({ - palette: { - primary: { - main: '#5865F2', - }, - secondary: { - main: '#EB459E', - }, - error: { - main: '#ED4245', - }, - warning: { - main: '#FEE75C', - }, - success: { - main: '#57F287', - }, - primary_extra_light: { - main: '#A5B2FF', - }, - }, -}); - if (['production', 'staging'].includes(process.env.NODE_ENV)) { // Use a placeholder DSN as we'll be using the tunnel to proxy all Sentry React errors Sentry.init({ @@ -74,14 +32,9 @@ createRoot(document.getElementById('root')!).render( } showDialog> - - - - - - - - + + + diff --git a/src/mui.d.ts b/src/mui.d.ts new file mode 100644 index 0000000..376f39e --- /dev/null +++ b/src/mui.d.ts @@ -0,0 +1,19 @@ +import '@mui/material/styles'; + +declare module '@mui/material/styles' { + interface Palette { + highlight: { + [variant: string]: Palette['primary']; + }; + } + + interface PaletteOptions { + highlight?: { + [variant: string]: PaletteOptions['primary']; + }; + } + + interface TypeText { + accent: string; + } +} diff --git a/src/pages/ComingSoon.tsx b/src/pages/ComingSoon.tsx index 8a17c3d..304bd1b 100644 --- a/src/pages/ComingSoon.tsx +++ b/src/pages/ComingSoon.tsx @@ -26,7 +26,7 @@ export default function ComingSoon() { - + Coming Soon! diff --git a/src/pages/Error.tsx b/src/pages/Error.tsx index 767baef..b359077 100644 --- a/src/pages/Error.tsx +++ b/src/pages/Error.tsx @@ -24,7 +24,7 @@ export default function NotFound() { - + An Unrecoverable Error Occurred diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index e4237b2..9f981c0 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -18,8 +18,6 @@ import PeopleLeadIcon from '@mui/icons-material/ContentPaste'; import FAQIcon from '@mui/icons-material/TipsAndUpdates'; import UserIcon from '@mui/icons-material/AccountBox'; -import {grey} from '@mui/material/colors'; - const sections: Record = { // section shorthand --> [guide title, button title, icon] general: ['Welcome to Access!', 'Overview', ], @@ -127,7 +125,7 @@ function AccordionMaker(props: AccordionMakerProps) { return ( <> - + {sections[props.which][0]} {Object.entries(guide[props.which]).map(([key, value]: [string, string]) => ( @@ -136,7 +134,7 @@ function AccordionMaker(props: AccordionMakerProps) { expandIcon={} aria-controls={'panel-content' + key} id={'panel-header' + key} - sx={{fontWeight: 500, color: grey[900]}}> + sx={{fontWeight: 500}}> {key} @@ -174,7 +172,7 @@ export default function Home() { - + Access User Guides diff --git a/src/pages/NotFound.tsx b/src/pages/NotFound.tsx index 3f98f00..9232d01 100644 --- a/src/pages/NotFound.tsx +++ b/src/pages/NotFound.tsx @@ -26,7 +26,7 @@ export default function NotFound() { - + Not Found diff --git a/src/pages/apps/List.tsx b/src/pages/apps/List.tsx index f7bd69e..8b6f604 100644 --- a/src/pages/apps/List.tsx +++ b/src/pages/apps/List.tsx @@ -111,7 +111,7 @@ export default function ListApps() { - + Applications diff --git a/src/pages/apps/Read.tsx b/src/pages/apps/Read.tsx index 118ba49..70991c4 100644 --- a/src/pages/apps/Read.tsx +++ b/src/pages/apps/Read.tsx @@ -21,8 +21,6 @@ import Typography from '@mui/material/Typography'; import TagIcon from '@mui/icons-material/LocalOffer'; -import {grey} from '@mui/material/colors'; - import Ending from '../../components/Ending'; import {groupBy, displayUserName} from '../../helpers'; import {isAccessAdmin, isAppOwnerGroupOwner} from '../../authorization'; @@ -106,7 +104,8 @@ export default function ReadApp() { sx={{ margin: '2px', marginTop: '5px', - bgcolor: tagMap.active_tag!.enabled ? 'primary' : grey[500], + bgcolor: (theme) => + tagMap.active_tag!.enabled ? 'primary' : theme.palette.action.disabledBackground, }} /> ))} @@ -150,7 +149,7 @@ export default function ReadApp() { - + - + Can manage app and implicitly own all app groups @@ -220,7 +219,7 @@ export default function ReadApp() { ) : ( - + None @@ -240,7 +239,7 @@ export default function ReadApp() { - + - + Members of Owners Okta Group @@ -310,7 +309,7 @@ export default function ReadApp() { ) : ( - + None @@ -334,7 +333,7 @@ export default function ReadApp() { - + - + Can manage membership of Group @@ -404,7 +403,7 @@ export default function ReadApp() { ) : ( - + All app owners are implicitly app group owners @@ -424,7 +423,7 @@ export default function ReadApp() { - + - + Members of App Okta Group @@ -494,7 +493,7 @@ export default function ReadApp() { ) : ( - + None diff --git a/src/pages/groups/AddRoles.tsx b/src/pages/groups/AddRoles.tsx index 223a2b8..606aec3 100644 --- a/src/pages/groups/AddRoles.tsx +++ b/src/pages/groups/AddRoles.tsx @@ -246,7 +246,7 @@ function AddRolesDialog(props: AddRolesDialogProps) { onSuccess={(formData) => submit(formData)}> Add {addRolesText} - + {timeLimit ? (props.owner ? 'Ownership of ' : 'Membership to ') + 'this group is limited to ' + @@ -254,7 +254,7 @@ function AddRolesDialog(props: AddRolesDialogProps) { ' days.' : null} - + {disallowOwnerAdd && !isAccessAdmin(currentUser) ? 'Certain roles cannot be added to this group due to tag constraints.' : null} diff --git a/src/pages/groups/AddUsers.tsx b/src/pages/groups/AddUsers.tsx index 94bad87..bcf773d 100644 --- a/src/pages/groups/AddUsers.tsx +++ b/src/pages/groups/AddUsers.tsx @@ -248,7 +248,7 @@ function AddUsersDialog(props: AddUsersDialogProps) { onSuccess={(formData) => submit(formData)}> Add {addUsersText} - + {timeLimit ? (props.owner ? 'Ownership of ' : 'Membership to ') + 'this group is limited to ' + @@ -256,7 +256,7 @@ function AddUsersDialog(props: AddUsersDialogProps) { ' days.' : null} - + {disallow_owner_add && !isAccessAdmin(props.currentUser) ? 'Owners may not add themselves as ' + (props.owner ? 'owners' : 'members') + ' of this group.' : null} diff --git a/src/pages/groups/Audit.tsx b/src/pages/groups/Audit.tsx index 6bccc00..00b1431 100644 --- a/src/pages/groups/Audit.tsx +++ b/src/pages/groups/Audit.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useMemo} from 'react'; import {Link as RouterLink, useParams, useSearchParams, useNavigate} from 'react-router-dom'; import Link from '@mui/material/Link'; @@ -22,7 +22,6 @@ import TextField from '@mui/material/TextField'; import Autocomplete from '@mui/material/Autocomplete'; import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; import ToggleButton from '@mui/material/ToggleButton'; -import {lightGreen, red} from '@mui/material/colors'; import dayjs from 'dayjs'; @@ -199,7 +198,7 @@ export default function AuditGroup() { - + {(group.deleted_at ?? null) != null ? ( + Object.values(row.ended_at == null || dayjs().isBefore(dayjs(row.ended_at))) + ? palette.highlight.success.main + : palette.highlight.danger.main, }}> {(row.user?.deleted_at ?? null) != null ? ( diff --git a/src/pages/groups/BulkRenewal.tsx b/src/pages/groups/BulkRenewal.tsx index 7425416..2e5eedb 100644 --- a/src/pages/groups/BulkRenewal.tsx +++ b/src/pages/groups/BulkRenewal.tsx @@ -17,14 +17,11 @@ import Select from '@mui/material/Select'; import Tooltip from '@mui/material/Tooltip'; import Typography from '@mui/material/Typography'; -import {red, yellow} from '@mui/material/colors'; -import {darken, lighten, styled} from '@mui/material/styles'; - import AccessRequestIcon from '@mui/icons-material/MoreTime'; import {FormContainer, DatePickerElement, TextFieldElement} from 'react-hook-form-mui'; -import {DataGrid, GridColDef, GridRowSelectionModel} from '@mui/x-data-grid'; +import {GridColDef, GridRowSelectionModel} from '@mui/x-data-grid'; import dayjs, {Dayjs} from 'dayjs'; @@ -32,6 +29,7 @@ import {displayUserName, minTagTimeGroups, requiredReasonGroups} from '../../hel import {usePutGroupMembersById, PutGroupMembersByIdError, PutGroupMembersByIdVariables} from '../../api/apiComponents'; import {GroupMember, OktaUserGroupMember, PolymorphicGroup, RoleGroupMap, RoleGroup} from '../../api/apiSchemas'; +import BulkRenewalDataGrid from '../../components/BulkRenewalDataGrid'; interface Data { id: number; @@ -92,45 +90,6 @@ const UNTIL_OPTIONS = Object.entries(UNTIL_ID_TO_LABELS).map(([id, label], index const RFC822_FORMAT = 'ddd, DD MMM YYYY HH:mm:ss ZZ'; -const getBackgroundColor = (color: string, mode: string) => - mode === 'dark' ? darken(color, 0.7) : lighten(color, 0.7); - -const getHoverBackgroundColor = (color: string, mode: string) => - mode === 'dark' ? darken(color, 0.6) : lighten(color, 0.6); - -const getSelectedBackgroundColor = (color: string, mode: string) => - mode === 'dark' ? darken(color, 0.5) : lighten(color, 0.5); - -const getSelectedHoverBackgroundColor = (color: string, mode: string) => - mode === 'dark' ? darken(color, 0.4) : lighten(color, 0.4); - -const StyledDataGrid = styled(DataGrid)(({theme}) => ({ - '& .super-app-theme--Expired': { - backgroundColor: getBackgroundColor(red[200], theme.palette.mode), - '&:hover': { - backgroundColor: getHoverBackgroundColor(red[200], theme.palette.mode), - }, - '&.Mui-selected': { - backgroundColor: getSelectedBackgroundColor(red[200], theme.palette.mode), - '&:hover': { - backgroundColor: getSelectedHoverBackgroundColor(red[200], theme.palette.mode), - }, - }, - }, - '& .super-app-theme--Soon': { - backgroundColor: getBackgroundColor(yellow[200], theme.palette.mode), - '&:hover': { - backgroundColor: getHoverBackgroundColor(yellow[200], theme.palette.mode), - }, - '&.Mui-selected': { - backgroundColor: getSelectedBackgroundColor(yellow[200], theme.palette.mode), - '&:hover': { - backgroundColor: getSelectedHoverBackgroundColor(yellow[200], theme.palette.mode), - }, - }, - }, -})); - interface BulkRenewalDialogProps { setOpen(open: boolean): any; rows: OktaUserGroupMember[]; @@ -380,7 +339,7 @@ function BulkRenewalDialog(props: BulkRenewalDialogProps) { onSuccess={(formData) => submit(formData)}> Bulk Renew Group Access - + {timeLimit ? 'Access to one or more selected groups is limited to ' + Math.floor(timeLimit / 86400) + ' days.' : null} @@ -445,7 +404,7 @@ function BulkRenewalDialog(props: BulkRenewalDialogProps) { ) : null} - createData(row))} rowHeight={40} columns={columns} diff --git a/src/pages/groups/Expiring.tsx b/src/pages/groups/Expiring.tsx index 946483a..58e665d 100644 --- a/src/pages/groups/Expiring.tsx +++ b/src/pages/groups/Expiring.tsx @@ -19,7 +19,6 @@ import TextField from '@mui/material/TextField'; import ToggleButton from '@mui/material/ToggleButton'; import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; import Typography from '@mui/material/Typography'; -import {red, yellow} from '@mui/material/colors'; import dayjs, {Dayjs} from 'dayjs'; @@ -212,7 +211,7 @@ export default function ExpiringGroups() { - + Expiring Groups @@ -306,11 +305,11 @@ export default function ExpiringGroups() { dayjs(row.ended_at).isAfter(dayjs()) && dayjs(row.ended_at).isBefore(dayjs().add(7, 'day')) - ? yellow[100] + ? highlight.warning.main : dayjs(row.ended_at).isBefore(dayjs()) - ? red[100] + ? highlight.danger.main : null, }}> diff --git a/src/pages/groups/List.tsx b/src/pages/groups/List.tsx index 83dc096..2c269b8 100644 --- a/src/pages/groups/List.tsx +++ b/src/pages/groups/List.tsx @@ -110,7 +110,7 @@ export default function ListGroups() { - + Groups diff --git a/src/pages/groups/Read.tsx b/src/pages/groups/Read.tsx index 2dc2f53..2dd89ac 100644 --- a/src/pages/groups/Read.tsx +++ b/src/pages/groups/Read.tsx @@ -32,8 +32,6 @@ import DeleteIcon from '@mui/icons-material/Close'; import GroupIcon from '@mui/icons-material/People'; import TagIcon from '@mui/icons-material/LocalOffer'; -import {grey} from '@mui/material/colors'; - import {useCurrentUser} from '../../authentication'; import CreateUpdateGroup from './CreateUpdate'; import DeleteGroup from './Delete'; @@ -258,7 +256,8 @@ export default function ReadGroup() { icon={} sx={{ margin: '10px 2px 0 2px', - bgcolor: tagMap.active_tag!.enabled ? 'primary' : grey[500], + bgcolor: (theme) => + tagMap.active_tag!.enabled ? 'primary' : theme.palette.action.disabledBackground, }} /> ))} @@ -369,7 +368,7 @@ export default function ReadGroup() { - + Groups Owned by Role Membership @@ -439,7 +438,7 @@ export default function ReadGroup() { ) : ( - + None @@ -458,7 +457,7 @@ export default function ReadGroup() { - + Groups with Members granted by Role Membership @@ -528,7 +527,7 @@ export default function ReadGroup() { ) : ( - + None @@ -550,10 +549,10 @@ export default function ReadGroup() { - + {group.type == 'role_group' ? 'Role Owners' : 'Group Owners'} - + {group.type == 'role_group' ? 'Can manage description and membership of Role' : 'Can manage description and membership of Group'} @@ -714,7 +713,7 @@ export default function ReadGroup() { ) : ( - + None @@ -734,10 +733,10 @@ export default function ReadGroup() { - + {group.type == 'role_group' ? 'Role Members' : 'Group Members'} - + {group.type == 'role_group' ? 'Members of Okta Group for Role' : 'Members of Okta Group'} @@ -884,7 +883,7 @@ export default function ReadGroup() { ) : ( - + None diff --git a/src/pages/requests/Create.tsx b/src/pages/requests/Create.tsx index db55ce1..e0bf49f 100644 --- a/src/pages/requests/Create.tsx +++ b/src/pages/requests/Create.tsx @@ -330,7 +330,7 @@ function CreateRequestContainer(props: CreateRequestContainerProps) { Request - + {timeLimit ? (owner ? 'Ownership of ' : 'Membership to ') + 'this group is limited to ' + diff --git a/src/pages/requests/List.tsx b/src/pages/requests/List.tsx index 8335333..e3cddc5 100644 --- a/src/pages/requests/List.tsx +++ b/src/pages/requests/List.tsx @@ -18,7 +18,6 @@ import TextField from '@mui/material/TextField'; import Box from '@mui/material/Box'; import Grid from '@mui/material/Grid'; import Autocomplete from '@mui/material/Autocomplete'; -import {lightGreen, red} from '@mui/material/colors'; import dayjs from 'dayjs'; import RelativeTime from 'dayjs/plugin/relativeTime'; @@ -131,7 +130,7 @@ export default function ListRequests() { - + Access Requests @@ -196,7 +195,12 @@ export default function ListRequests() { + row.status == 'APPROVED' + ? highlight.success.main + : row.status == 'REJECTED' + ? highlight.danger.main + : 'inherit', }}> {(row.requester?.deleted_at ?? null) != null ? ( diff --git a/src/pages/requests/Read.tsx b/src/pages/requests/Read.tsx index 0efb29b..8927044 100644 --- a/src/pages/requests/Read.tsx +++ b/src/pages/requests/Read.tsx @@ -604,7 +604,7 @@ export default function ReadRequest() { - + {timeLimit ? (accessRequest.request_ownership ? 'Ownership of ' : 'Membership to ') + 'this group is limited to ' + @@ -720,7 +720,7 @@ export default function ReadRequest() { - + {accessRequest.requested_group?.name}{' '} {accessRequest.requested_group?.type == 'role_group' ? 'Owners' @@ -777,7 +777,7 @@ export default function ReadRequest() { ) : ( - + None @@ -793,7 +793,7 @@ export default function ReadRequest() { - + {((accessRequest.requested_group ?? {}) as AppGroup).app?.name} {' App Owners'} @@ -848,7 +848,7 @@ export default function ReadRequest() { ) : ( - + None @@ -865,7 +865,7 @@ export default function ReadRequest() { - + {ACCESS_APP_RESERVED_NAME} {' Admins'} @@ -920,7 +920,7 @@ export default function ReadRequest() { ) : ( - + None diff --git a/src/pages/roles/AddGroups.tsx b/src/pages/roles/AddGroups.tsx index 164e4eb..38d67c9 100644 --- a/src/pages/roles/AddGroups.tsx +++ b/src/pages/roles/AddGroups.tsx @@ -233,7 +233,7 @@ function AddGroupsDialog(props: AddGroupsDialogProps) { onSuccess={(formData) => submit(formData)}> Add {addGroupsText} - + {timeLimit ? (props.owner ? 'Ownership of ' : 'Membership to ') + 'one or more selected groups is limited to ' + @@ -241,7 +241,7 @@ function AddGroupsDialog(props: AddGroupsDialogProps) { ' days.' : null} - + {disallowedGroups.length != 0 && !isAccessAdmin(currentUser) ? 'Some groups may not be added due to group tag constraints.' : null} diff --git a/src/pages/roles/Audit.tsx b/src/pages/roles/Audit.tsx index f3a1a82..ab11885 100644 --- a/src/pages/roles/Audit.tsx +++ b/src/pages/roles/Audit.tsx @@ -18,7 +18,6 @@ import TextField from '@mui/material/TextField'; import Autocomplete from '@mui/material/Autocomplete'; import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; import ToggleButton from '@mui/material/ToggleButton'; -import {lightGreen, red} from '@mui/material/colors'; import dayjs from 'dayjs'; @@ -194,7 +193,7 @@ export default function AuditRole() { - + {(role.deleted_at ?? null) != null ? ( + row.ended_at == null || dayjs().isBefore(dayjs(row.ended_at)) + ? highlight.success.main + : highlight.danger.main, }}> {(row.group?.deleted_at ?? null) != null ? ( diff --git a/src/pages/roles/BulkRenewal.tsx b/src/pages/roles/BulkRenewal.tsx index 1256166..fc24c36 100644 --- a/src/pages/roles/BulkRenewal.tsx +++ b/src/pages/roles/BulkRenewal.tsx @@ -17,14 +17,11 @@ import Select from '@mui/material/Select'; import Tooltip from '@mui/material/Tooltip'; import Typography from '@mui/material/Typography'; -import {red, yellow} from '@mui/material/colors'; -import {darken, lighten, styled} from '@mui/material/styles'; - import AccessRequestIcon from '@mui/icons-material/MoreTime'; import {FormContainer, DatePickerElement, TextFieldElement} from 'react-hook-form-mui'; -import {DataGrid, GridColDef, GridRowParams, GridRowSelectionModel} from '@mui/x-data-grid'; +import {GridColDef, GridRowParams, GridRowSelectionModel} from '@mui/x-data-grid'; import dayjs, {Dayjs} from 'dayjs'; @@ -35,6 +32,7 @@ import {useCurrentUser} from '../../authentication'; import {usePutRoleMembersById, PutRoleMembersByIdError, PutRoleMembersByIdVariables} from '../../api/apiComponents'; import {RoleMember, RoleGroupMap, OktaGroup, AppGroup} from '../../api/apiSchemas'; import {isAccessAdmin} from '../../authorization'; +import BulkRenewalDataGrid from '../../components/BulkRenewalDataGrid'; interface Data { id: number; @@ -95,45 +93,6 @@ const UNTIL_OPTIONS = Object.entries(UNTIL_ID_TO_LABELS).map(([id, label], index const RFC822_FORMAT = 'ddd, DD MMM YYYY HH:mm:ss ZZ'; -const getBackgroundColor = (color: string, mode: string) => - mode === 'dark' ? darken(color, 0.7) : lighten(color, 0.7); - -const getHoverBackgroundColor = (color: string, mode: string) => - mode === 'dark' ? darken(color, 0.6) : lighten(color, 0.6); - -const getSelectedBackgroundColor = (color: string, mode: string) => - mode === 'dark' ? darken(color, 0.5) : lighten(color, 0.5); - -const getSelectedHoverBackgroundColor = (color: string, mode: string) => - mode === 'dark' ? darken(color, 0.4) : lighten(color, 0.4); - -const StyledDataGrid = styled(DataGrid)(({theme}) => ({ - '& .super-app-theme--Expired': { - backgroundColor: getBackgroundColor(red[200], theme.palette.mode), - '&:hover': { - backgroundColor: getHoverBackgroundColor(red[200], theme.palette.mode), - }, - '&.Mui-selected': { - backgroundColor: getSelectedBackgroundColor(red[200], theme.palette.mode), - '&:hover': { - backgroundColor: getSelectedHoverBackgroundColor(red[200], theme.palette.mode), - }, - }, - }, - '& .super-app-theme--Soon': { - backgroundColor: getBackgroundColor(yellow[200], theme.palette.mode), - '&:hover': { - backgroundColor: getHoverBackgroundColor(yellow[200], theme.palette.mode), - }, - '&.Mui-selected': { - backgroundColor: getSelectedBackgroundColor(yellow[200], theme.palette.mode), - '&:hover': { - backgroundColor: getSelectedHoverBackgroundColor(yellow[200], theme.palette.mode), - }, - }, - }, -})); - interface BulkRenewalDialogProps { setOpen(open: boolean): any; rows: RoleGroupMap[]; @@ -427,12 +386,12 @@ function BulkRenewalDialog(props: BulkRenewalDialogProps) { onSuccess={(formData) => submit(formData)}> Bulk Renew Role Access - + {timeLimit ? 'Access to one or more selected groups is limited to ' + Math.floor(timeLimit / 86400) + ' days.' : null} - + {display_owner_add_constraint ? 'Due to group constraints, some roles may not be renewed since you are both a member of the role and an owner of the group. Please reach out to another group owner to renew the role membership to the group.' : null} @@ -497,7 +456,7 @@ function BulkRenewalDialog(props: BulkRenewalDialogProps) { ) : null} - createData(row))} rowHeight={40} columns={columns} diff --git a/src/pages/roles/Expiring.tsx b/src/pages/roles/Expiring.tsx index 3fc2c41..1acbb5b 100644 --- a/src/pages/roles/Expiring.tsx +++ b/src/pages/roles/Expiring.tsx @@ -18,7 +18,6 @@ import ToggleButton from '@mui/material/ToggleButton'; import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; import Tooltip from '@mui/material/Tooltip'; import Typography from '@mui/material/Typography'; -import {red, yellow} from '@mui/material/colors'; import dayjs, {Dayjs} from 'dayjs'; @@ -216,7 +215,7 @@ export default function ExpiringRoless() { - + Expiring Roles @@ -310,11 +309,11 @@ export default function ExpiringRoless() { dayjs(row.ended_at).isAfter(dayjs()) && dayjs(row.ended_at).isBefore(dayjs().add(7, 'day')) - ? yellow[100] + ? highlight.warning.main : dayjs(row.ended_at).isBefore(dayjs()) - ? red[100] + ? highlight.danger.main : null, }}> diff --git a/src/pages/roles/List.tsx b/src/pages/roles/List.tsx index ac5a783..8fbbbb5 100644 --- a/src/pages/roles/List.tsx +++ b/src/pages/roles/List.tsx @@ -111,7 +111,7 @@ export default function ListRoles() { - + Roles diff --git a/src/pages/tags/List.tsx b/src/pages/tags/List.tsx index f105bcf..97a2577 100644 --- a/src/pages/tags/List.tsx +++ b/src/pages/tags/List.tsx @@ -109,7 +109,7 @@ export default function ListTags() { - + Tags diff --git a/src/pages/tags/Read.tsx b/src/pages/tags/Read.tsx index 323a531..f1a011c 100644 --- a/src/pages/tags/Read.tsx +++ b/src/pages/tags/Read.tsx @@ -25,8 +25,6 @@ import DeleteIcon from '@mui/icons-material/Close'; import Disabled from '@mui/icons-material/PauseCircle'; import Enabled from '@mui/icons-material/TaskAlt'; -import {grey} from '@mui/material/colors'; - import {useGetTagById, usePutGroupById, usePutAppById} from '../../api/apiComponents'; import {App, AppGroup, PolymorphicGroup, Tag} from '../../api/apiSchemas'; @@ -144,12 +142,11 @@ export default function ReadTag() { ) : ( } label="Disabled" sx={{ marginTop: '10px', - bgcolor: grey[500], + bgcolor: (theme) => theme.palette.action.disabledBackground, }} /> @@ -175,7 +172,7 @@ export default function ReadTag() { - + Tag Constraints @@ -220,7 +217,7 @@ export default function ReadTag() { ) : ( - + None @@ -240,7 +237,7 @@ export default function ReadTag() { - + Apps with Tag @@ -297,7 +294,7 @@ export default function ReadTag() { ) : ( - + None @@ -317,7 +314,7 @@ export default function ReadTag() { - + Groups with Tag @@ -400,7 +397,7 @@ export default function ReadTag() { ) : ( - + None diff --git a/src/pages/users/Audit.tsx b/src/pages/users/Audit.tsx index c9ea075..f3339e0 100644 --- a/src/pages/users/Audit.tsx +++ b/src/pages/users/Audit.tsx @@ -20,7 +20,6 @@ import TextField from '@mui/material/TextField'; import Autocomplete from '@mui/material/Autocomplete'; import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; import ToggleButton from '@mui/material/ToggleButton'; -import {lightGreen, red} from '@mui/material/colors'; import dayjs from 'dayjs'; @@ -196,7 +195,7 @@ export default function AuditUser() { - + {(user.deleted_at ?? null) != null ? ( + row.ended_at == null || dayjs().isBefore(dayjs(row.ended_at)) + ? highlight.success.main + : highlight.danger.main, }}> {(row.group?.deleted_at ?? null) != null ? ( diff --git a/src/pages/users/List.tsx b/src/pages/users/List.tsx index f49ae1a..fb13120 100644 --- a/src/pages/users/List.tsx +++ b/src/pages/users/List.tsx @@ -108,7 +108,7 @@ export default function ListUsers() { - + Users diff --git a/src/pages/users/Read.tsx b/src/pages/users/Read.tsx index fd642e2..ca8085e 100644 --- a/src/pages/users/Read.tsx +++ b/src/pages/users/Read.tsx @@ -140,7 +140,7 @@ function OwnerTable({user, ownerships, onClickRemoveGroupFromRole, onClickRemove - + Owner of Group or Roles @@ -237,7 +237,7 @@ function OwnerTable({user, ownerships, onClickRemoveGroupFromRole, onClickRemove ) : ( - + None @@ -283,7 +283,7 @@ function MemberTable({user, memberships, onClickRemoveGroupFromRole, onClickRemo - + Member of Groups or Roles @@ -380,7 +380,7 @@ function MemberTable({user, memberships, onClickRemoveGroupFromRole, onClickRemo ) : ( - + None diff --git a/tsconfig.json b/tsconfig.json index b3c8740..c39ec14 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,7 +24,7 @@ // relative to your source root folder. "sourceRoot": "/" }, - "include": ["src/**/*"], + "include": ["src/**/*", "mui.d.ts"], "paths": { "@mui/styled-engine": ["./node_modules/@mui/styled-engine-sc"] } From 3173e6137219950c80bd1d37cb158ed2e80d1ba1 Mon Sep 17 00:00:00 2001 From: amyjchen Date: Tue, 29 Oct 2024 16:45:13 -0700 Subject: [PATCH 10/36] make theme toggle minimize when nav is minimized --- src/App.tsx | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index e5aa55a..085b3fa 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -101,28 +101,32 @@ const Drawer = styled(MuiDrawer, { }, })); -function ThemeToggle({setThemeMode}: {setThemeMode: (theme: PaletteMode) => void}) { +function ThemeToggle({setThemeMode, condensed}: {setThemeMode: (theme: PaletteMode) => void; condensed: boolean}) { const theme = useTheme(); return ( - - setThemeMode('light')} - aria-label="Light mode"> - - - - - setThemeMode('dark')} - aria-label="Dark mode"> - - - + {(theme.palette.mode != 'light' || !condensed) && ( + + setThemeMode('light')} + aria-label="Light mode"> + + + + )} + {(theme.palette.mode != 'dark' || !condensed) && ( + + setThemeMode('dark')} + aria-label="Dark mode"> + + + + )} ); } @@ -189,7 +193,7 @@ function Dashboard({setThemeMode}: {setThemeMode: (theme: PaletteMode) => void}) - + Date: Tue, 29 Oct 2024 18:49:47 -0700 Subject: [PATCH 11/36] figure out why table rows are not 100% width with accordion component --- src/components/AccordionListGroup.tsx | 114 ----------- .../apps/detail/AppsAccordionListGroup.tsx | 179 ++++++++++++++++++ src/pages/apps/detail/index.tsx | 5 +- 3 files changed, 182 insertions(+), 116 deletions(-) delete mode 100644 src/components/AccordionListGroup.tsx create mode 100644 src/pages/apps/detail/AppsAccordionListGroup.tsx diff --git a/src/components/AccordionListGroup.tsx b/src/components/AccordionListGroup.tsx deleted file mode 100644 index 9febe87..0000000 --- a/src/components/AccordionListGroup.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import {Accordion, AccordionDetails, AccordionSummary} from '@mui/material'; -import {AppGroup, OktaUserGroupMember} from '../api/apiSchemas'; - -interface AccordionListGroupProps { - group_name: string; - owner_group: AppGroup; - member_group: AppGroup; -} - -export const AccordionListGroup: React.FC = ({group_name, owner_group, member_group}) => { - return null; -}; -/* - {app.active_owner_app_groups?.map((appGroup) => ( - - - - - }> - - - - - - - App Owners - - - - Can manage app and implicitly own all app groups - - - - - - - Total Owners: {Object.keys(groupMemberships(appGroup.active_user_ownerships)).length} - - - -
-
- - - - - - - - - Name - Email - Ending - - - - {Object.keys(groupMemberships(appGroup.active_user_ownerships)).length > 0 ? ( - Object.entries(groupMemberships(appGroup.active_user_ownerships)) - .sort(sortGroupMembers) - .map(([userId, users]: [string, Array]) => ( - - - - {displayUserName(users[0].active_user)} - - - - - {users[0].active_user?.email.toLowerCase()} - - - - - - - )) - ) : ( - - )} - - - - - -
- -
-
-
) -} -*/ diff --git a/src/pages/apps/detail/AppsAccordionListGroup.tsx b/src/pages/apps/detail/AppsAccordionListGroup.tsx new file mode 100644 index 0000000..e6e3f38 --- /dev/null +++ b/src/pages/apps/detail/AppsAccordionListGroup.tsx @@ -0,0 +1,179 @@ +import { + Accordion, + AccordionDetails, + AccordionSummary, + Box, + Divider, + Grid, + Link, + Paper, + Stack, + Table, + TableBody, + TableCell, + TableContainer, + TableFooter, + TableHead, + TableRow, + Typography, +} from '@mui/material'; +import {App, AppGroup, OktaUserGroupMember} from '../../../api/apiSchemas'; +import React from 'react'; +import {displayUserName} from '../../../helpers'; +import {EmptyListEntry} from '../../../components/EmptyListEntry'; +import Ending from '../../../components/Ending'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import {Link as RouterLink, useParams} from 'react-router-dom'; + +interface AppAccordionListGroupProps { + app_group?: AppGroup[]; +} + +export const AppsAccordionListGroup: React.FC = ({app_group}) => { + const [expanded, setExpanded] = React.useState(false); + + const handleChange = (panel: string) => (event: React.SyntheticEvent, newExpanded: boolean) => { + setExpanded(newExpanded ? panel : false); + }; + return ( + + {app_group && + app_group?.map((appGroup) => ( + + + + + + + + + + {appGroup.name} + + + + Can manage app and implicitly own all app groups + + + + + + + Total Owners: {appGroup.active_user_ownerships?.length || 0} + + + + + + + + }>OwnerList + Owners list goes here + + + + + }>MemberList + Members list goes here + + + +
+
+
+ ))} +
+ ); + /* + return ( + + + + + + + + + + + {appGroup.name} Group Owners + + + + Can manage membership of Group + + + + + + + Total Owners: {Object.keys(groupMemberships(appGroup.active_user_ownerships)).length} + + + + + Name + Email + Ending + + + + {Object.keys(groupMemberships(appGroup.active_user_ownerships)).length > 0 ? ( + Object.entries(groupMemberships(appGroup.active_user_ownerships)) + .sort(sortGroupMembers) + .map(([userId, users]: [string, Array]) => ( + + + + {displayUserName(users[0].active_user)} + + + + + {users[0].active_user?.email.toLowerCase()} + + + + + + + + + + ) + */ +}; diff --git a/src/pages/apps/detail/index.tsx b/src/pages/apps/detail/index.tsx index 8b9dcde..2f43efa 100644 --- a/src/pages/apps/detail/index.tsx +++ b/src/pages/apps/detail/index.tsx @@ -3,7 +3,6 @@ import {Link as RouterLink, useParams} from 'react-router-dom'; import {Accordion, AccordionDetails, AccordionSummary} from '@mui/material'; import Box from '@mui/material/Box'; -import Chip from '@mui/material/Chip'; import Container from '@mui/material/Container'; import Divider from '@mui/material/Divider'; import Grid from '@mui/material/Grid'; @@ -17,7 +16,6 @@ import TableContainer from '@mui/material/TableContainer'; import TableFooter from '@mui/material/TableFooter'; import TableHead from '@mui/material/TableHead'; import TableRow from '@mui/material/TableRow'; -import Tooltip from '@mui/material/Tooltip'; import Typography from '@mui/material/Typography'; import Ending from '../../../components/Ending'; @@ -33,6 +31,7 @@ import AppsHeader from './AppsHeader'; import {AppsAdminActionGroup} from './AppsAdminActionGroup'; import {EmptyListEntry} from '../../../components/EmptyListEntry'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import {AppsAccordionListGroup} from './AppsAccordionListGroup'; function sortGroupMembers( [aUserId, aUsers]: [string, Array], @@ -93,6 +92,8 @@ export const ReadApp = () => { {(isAccessAdmin(currentUser) || isAppOwnerGroupOwner(currentUser, app.id ?? '')) && ( )} + + {app.active_owner_app_groups?.map((appGroup) => ( From 082cbd10158c147c44ea753a9fe4004d0fbdcf88 Mon Sep 17 00:00:00 2001 From: Jill Date: Tue, 29 Oct 2024 19:12:24 -0700 Subject: [PATCH 12/36] full-width accordion list --- .../apps/detail/AppsAccordionListGroup.tsx | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/pages/apps/detail/AppsAccordionListGroup.tsx b/src/pages/apps/detail/AppsAccordionListGroup.tsx index e6e3f38..d36950f 100644 --- a/src/pages/apps/detail/AppsAccordionListGroup.tsx +++ b/src/pages/apps/detail/AppsAccordionListGroup.tsx @@ -39,9 +39,9 @@ export const AppsAccordionListGroup: React.FC = ({ap {app_group && app_group?.map((appGroup) => ( - + -
+
@@ -77,16 +77,18 @@ export const AppsAccordionListGroup: React.FC = ({ap - - }>OwnerList - Owners list goes here - - - - - }>MemberList - Members list goes here - + + + + }>OwnerList + Owners list goes here + + + }>MemberList + Members list goes here + + +
From 7367f5f96790174cb3a1d8ca1080c5a61e860a5f Mon Sep 17 00:00:00 2001 From: Jill Date: Tue, 29 Oct 2024 19:48:06 -0700 Subject: [PATCH 13/36] add demo of owner group card for accordion list --- .../apps/detail/AppsAccordionListGroup.tsx | 105 ++++++++- src/pages/apps/detail/index.tsx | 208 +++--------------- 2 files changed, 122 insertions(+), 191 deletions(-) diff --git a/src/pages/apps/detail/AppsAccordionListGroup.tsx b/src/pages/apps/detail/AppsAccordionListGroup.tsx index d36950f..3c6a31f 100644 --- a/src/pages/apps/detail/AppsAccordionListGroup.tsx +++ b/src/pages/apps/detail/AppsAccordionListGroup.tsx @@ -28,12 +28,89 @@ import {Link as RouterLink, useParams} from 'react-router-dom'; interface AppAccordionListGroupProps { app_group?: AppGroup[]; } +interface GroupDetailListProps { + member_list: OktaUserGroupMember[]; +} + +const GroupDetailList: React.FC = ({member_list}) => { + const sortGroupMembers = ( + [aUserId, aUsers]: [string, Array], + [bUserId, bUsers]: [string, Array], + ): number => { + let aEmail = aUsers[0].active_user?.email ?? ''; + let bEmail = bUsers[0].active_user?.email ?? ''; + return aEmail.localeCompare(bEmail); + }; + + return ( + + + + + Name + Email + Ending + + + + {member_list.length > 0 ? ( + member_list.map((member: OktaUserGroupMember) => ( + + + + {displayUserName(member.active_user)} + + + + + {member.active_user?.email.toLowerCase()} + + + + + + + )) + ) : ( + + )} + + + + + +
+
+ ); +}; export const AppsAccordionListGroup: React.FC = ({app_group}) => { - const [expanded, setExpanded] = React.useState(false); + const [groupExpanded, setGroupExpanded] = React.useState(false); + const [memberExpanded, setMemberExpanded] = React.useState(false); const handleChange = (panel: string) => (event: React.SyntheticEvent, newExpanded: boolean) => { - setExpanded(newExpanded ? panel : false); + switch (panel) { + case 'members': + setMemberExpanded(!memberExpanded); + break; + case 'owners': + setGroupExpanded(!groupExpanded); + break; + default: + break; + } }; return ( @@ -79,13 +156,25 @@ export const AppsAccordionListGroup: React.FC = ({ap - - }>OwnerList - Owners list goes here + + }> + + Group Owners + + + + + - - }>MemberList - Members list goes here + + }> + + Group Members + + + + + diff --git a/src/pages/apps/detail/index.tsx b/src/pages/apps/detail/index.tsx index 2f43efa..5195071 100644 --- a/src/pages/apps/detail/index.tsx +++ b/src/pages/apps/detail/index.tsx @@ -87,194 +87,36 @@ export const ReadApp = () => { return ( - + {(isAccessAdmin(currentUser) || isAppOwnerGroupOwner(currentUser, app.id ?? '')) && ( )} - + + + theme.palette.mode === 'light' ? theme.palette.grey[200] : theme.palette.grey[800], + }}> + + Owner Group + + + - {app.active_owner_app_groups?.map((appGroup) => ( - - - - }> - - - - - - - {appGroup.name} - - - - Can manage app and implicitly own all app groups - - - - - - - Total Owners: {Object.keys(groupMemberships(appGroup.active_user_ownerships)).length} - - - -
-
- - - - - - Name - Email - Ending - - - - {Object.keys(groupMemberships(appGroup.active_user_ownerships)).length > 0 ? ( - Object.entries(groupMemberships(appGroup.active_user_ownerships)) - .sort(sortGroupMembers) - .map(([userId, users]: [string, Array]) => ( - - - - {displayUserName(users[0].active_user)} - - - - - {users[0].active_user?.email.toLowerCase()} - - - - - - - )) - ) : ( - - )} - - - - - -
-
-
-
-
- - - - - - - - - - App Owners Group Members - - - - Members of Owners Okta Group - - - - - - - Total Members: {Object.keys(groupMemberships(appGroup.active_user_memberships)).length} - - - - - Name - Email - Ending - - - - {Object.keys(groupMemberships(appGroup.active_user_memberships)).length > 0 ? ( - Object.entries(groupMemberships(appGroup.active_user_memberships)) - .sort(sortGroupMembers) - .map(([userId, users]: [string, Array]) => ( - - - - {displayUserName(users[0].active_user)} - - - - - {users[0].active_user?.email.toLowerCase()} - - - - - - - )) - ) : ( - - )} - - - - -
-
-
-
- ))} + + + theme.palette.mode === 'light' ? theme.palette.grey[200] : theme.palette.grey[800], + }}> + + App Groups + + + {app.active_non_owner_app_groups?.map((appGroup) => ( From 3c511420dbb8efa7ea402c00670fcf5a52978d4b Mon Sep 17 00:00:00 2001 From: Jillian Date: Wed, 30 Oct 2024 11:42:24 -0700 Subject: [PATCH 14/36] [Group Detail] Update the groups member page to add spacing and wrap to roles for users (#176) --- src/pages/groups/Read.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/pages/groups/Read.tsx b/src/pages/groups/Read.tsx index 2dd89ac..d0ea52a 100644 --- a/src/pages/groups/Read.tsx +++ b/src/pages/groups/Read.tsx @@ -820,7 +820,13 @@ export default function ReadGroup() {
{group.type != 'role_group' ? ( - + {users.sort(sortOktaUserGroupMembers).map((user) => user.active_role_group_mapping == null ? ( Date: Wed, 30 Oct 2024 13:07:40 -0700 Subject: [PATCH 15/36] [feat] Table Top Bar Component (#177) --- src/components/DateRange.tsx | 40 ++-- src/components/TableTopBar.tsx | 75 ++++++ src/pages/apps/List.tsx | 147 +++++------- src/pages/groups/Audit.tsx | 409 +++++++++++++++------------------ src/pages/groups/Expiring.tsx | 389 +++++++++++++++---------------- src/pages/groups/List.tsx | 156 ++++++------- src/pages/requests/List.tsx | 324 ++++++++++++-------------- src/pages/roles/Audit.tsx | 329 ++++++++++++-------------- src/pages/roles/Expiring.tsx | 368 ++++++++++++++--------------- src/pages/roles/List.tsx | 153 +++++------- src/pages/tags/List.tsx | 139 +++++------ src/pages/users/Audit.tsx | 379 ++++++++++++++---------------- src/pages/users/List.tsx | 175 ++++++-------- 13 files changed, 1427 insertions(+), 1656 deletions(-) create mode 100644 src/components/TableTopBar.tsx diff --git a/src/components/DateRange.tsx b/src/components/DateRange.tsx index ec22390..c0a76f5 100644 --- a/src/components/DateRange.tsx +++ b/src/components/DateRange.tsx @@ -91,27 +91,24 @@ function ButtonField(props: ButtonFieldProps) { return ( - - - + Ending Date Range diff --git a/src/components/TableTopBar.tsx b/src/components/TableTopBar.tsx new file mode 100644 index 0000000..1532201 --- /dev/null +++ b/src/components/TableTopBar.tsx @@ -0,0 +1,75 @@ +import {Launch} from '@mui/icons-material'; +import {Autocomplete, AutocompleteProps, Box, Grid, IconButton, Stack, TextField, Typography} from '@mui/material'; + +import * as React from 'react'; +import {useNavigate} from 'react-router-dom'; + +export function renderUserOption(props: React.HTMLAttributes, option: any) { + const [displayName, email] = option.split(';'); + return ( +
  • + + + {displayName} + + {email} + + + +
  • + ); +} + +export function TableTopBarAutocomplete({ + defaultValue, + filterOptions = (x) => x, + ...restProps +}: Omit, 'renderInput'>) { + return ( + } + {...restProps} + /> + ); +} + +interface TableTopBarProps { + title: string; + link?: string; + children?: React.ReactNode; +} + +export default function TableTopBar({title, link, children}: TableTopBarProps) { + const navigate = useNavigate(); + return ( + + + + {title} + + {link != null && ( + navigate(link)}> + + + )} + + + {children} + + + ); +} diff --git a/src/pages/apps/List.tsx b/src/pages/apps/List.tsx index 8b6f604..6e92f2d 100644 --- a/src/pages/apps/List.tsx +++ b/src/pages/apps/List.tsx @@ -24,6 +24,7 @@ import CreateUpdateApp from './CreateUpdate'; import {perPage} from '../../helpers'; import {useGetApps} from '../../api/apiComponents'; import TablePaginationActions from '../../components/actions/TablePaginationActions'; +import TableTopBar, {TableTopBarAutocomplete} from '../../components/TableTopBar'; export default function ListApps() { const navigate = useNavigate(); @@ -105,96 +106,70 @@ export default function ListApps() { }; return ( - - - - - + + + + + row.name)} + onChange={handleSearchSubmit} + onInputChange={(event, newInputValue) => setSearchInput(newInputValue)} + defaultValue={searchQuery} + /> + +
    + + + Name + Description + + + + {rows.map((row) => ( + - - Applications - + + {row.name} + - - - - - - - - - x} - options={searchRows.map((row) => row.name)} - onChange={handleSearchSubmit} - onInputChange={(event, newInputValue) => setSearchInput(newInputValue)} - defaultValue={searchQuery} - key={searchQuery} - renderInput={(params) => } - /> - + + + {(row.description ?? '').length > 115 + ? row.description?.substring(0, 114) + '...' + : row.description ?? ''} + - - Name - Description - - - - {rows.map((row) => ( - - - - {row.name} - - - - - {(row.description ?? '').length > 115 - ? row.description?.substring(0, 114) + '...' - : row.description ?? ''} - - - - ))} - {emptyRows > 0 && ( - - - - )} - - - - + ))} + {emptyRows > 0 && ( + + - -
    -
    -
    + )} + + + + + + + +
    ); } diff --git a/src/pages/groups/Audit.tsx b/src/pages/groups/Audit.tsx index 00b1431..4e4369d 100644 --- a/src/pages/groups/Audit.tsx +++ b/src/pages/groups/Audit.tsx @@ -34,6 +34,7 @@ import Loading from '../../components/Loading'; import Started from '../../components/Started'; import Ending from '../../components/Ending'; import TablePaginationActions from '../../components/actions/TablePaginationActions'; +import TableTopBar, {renderUserOption, TableTopBarAutocomplete} from '../../components/TableTopBar'; type OrderBy = 'moniker' | 'created_at' | 'ended_at'; type OrderDirection = 'asc' | 'desc'; @@ -192,251 +193,207 @@ export default function AuditGroup() { }; return ( - - - - - - - - {(group.deleted_at ?? null) != null ? ( - - {group.name} - - ) : ( - - {group.name} - - )}{' '} - Group Audit - - + + + + Member + Owner + + + Active + Inactive + + displayUserName(row) + ';' + row.email.toLowerCase())} + onChange={handleSearchSubmit} + onInputChange={(event, newInputValue) => { + setSearchInput(newInputValue?.split(';')[0] ?? ''); + }} + defaultValue={searchQuery} + renderOption={renderUserOption} + /> + +
    + + + + + User Name + + + User Email + Member or Owner + Direct or via Role + + + Started + + + Added by + + + Ending + + + Removed by + Access Request + Justification + + + + {rows.map((row) => ( + + Object.values(row.ended_at == null || dayjs().isBefore(dayjs(row.ended_at))) + ? palette.highlight.success.main + : palette.highlight.danger.main, + }}> - - Member - Owner - + {(row.user?.deleted_at ?? null) != null ? ( + + {displayUserName(row.user)} + + ) : ( + + {displayUserName(row.user)} + + )} - - - Active - Inactive - + + {(row.user?.deleted_at ?? null) != null ? ( + + {row.user?.email.toLowerCase()} + + ) : ( + + {row.user?.email.toLowerCase()} + + )} - - x} - options={searchRows.map((row) => displayUserName(row) + ';' + row.email.toLowerCase())} - onChange={handleSearchSubmit} - onInputChange={(event, newInputValue) => { - setSearchInput(newInputValue?.split(';')[0] ?? ''); - }} - defaultValue={searchQuery} - key={searchQuery} - renderInput={(params) => } - renderOption={(props, option, state) => { - const [displayName, email] = option.split(';'); - return ( -
  • - - - {displayName} - - {email} - - - -
  • - ); - }} - /> + {row.is_owner ? 'Owner' : 'Member'} + + {row.role_group_mapping == null ? ( + + ) : ( + navigate(`/roles/${row.role_group_mapping?.role_group?.name}`)} + /> + )} -
    - - - User Name - + - User Email - Member or Owner - Direct or via Role - - Started - + {(row.created_actor?.deleted_at ?? null) != null ? ( + + {displayUserName(row.created_actor)} + + ) : ( + + {displayUserName(row.created_actor)} + + )} - Added by - - Ending - + - Removed by - Access Request - Justification - - - - {rows.map((row) => ( - - Object.values(row.ended_at == null || dayjs().isBefore(dayjs(row.ended_at))) - ? palette.highlight.success.main - : palette.highlight.danger.main, - }}> - - {(row.user?.deleted_at ?? null) != null ? ( - - {displayUserName(row.user)} - - ) : ( - - {displayUserName(row.user)} - - )} - - - {(row.user?.deleted_at ?? null) != null ? ( - - {row.user?.email.toLowerCase()} - - ) : ( - - {row.user?.email.toLowerCase()} - - )} - - {row.is_owner ? 'Owner' : 'Member'} - - {row.role_group_mapping == null ? ( - - ) : ( - navigate(`/roles/${row.role_group_mapping?.role_group?.name}`)} - /> - )} - - - - - - {(row.created_actor?.deleted_at ?? null) != null ? ( + + {row.ended_at != null && dayjs().isAfter(dayjs(row.ended_at)) ? ( + (row.ended_actor?.deleted_at ?? null) != null ? ( - {displayUserName(row.created_actor)} + {displayUserName(row.ended_actor)} ) : ( - {displayUserName(row.created_actor)} + {displayUserName(row.ended_actor)} - )} - - - - - - {row.ended_at != null && dayjs().isAfter(dayjs(row.ended_at)) ? ( - (row.ended_actor?.deleted_at ?? null) != null ? ( - - {displayUserName(row.ended_actor)} - - ) : ( - - {displayUserName(row.ended_actor)} - - ) - ) : ( - '' - )} - - - {row.access_request != null ? ( - - ) : null} - - - {row.created_reason ? : null} - - - ))} - {emptyRows > 0 && ( - - - - )} - - - - + ) + ) : ( + '' + )} + + + {row.access_request != null ? ( + + ) : null} + + + {row.created_reason ? : null} + + + ))} + {emptyRows > 0 && ( + + - -
    -
    -
    + )} + + + + + + + + ); } diff --git a/src/pages/groups/Expiring.tsx b/src/pages/groups/Expiring.tsx index 58e665d..3e97b9c 100644 --- a/src/pages/groups/Expiring.tsx +++ b/src/pages/groups/Expiring.tsx @@ -34,6 +34,7 @@ import Loading from '../../components/Loading'; import Started from '../../components/Started'; import TablePaginationActions from '../../components/actions/TablePaginationActions'; import {displayUserName, perPage} from '../../helpers'; +import TableTopBar, {renderUserOption, TableTopBarAutocomplete} from '../../components/TableTopBar'; type OrderBy = 'moniker' | 'ended_at'; type OrderDirection = 'asc' | 'desc'; @@ -205,229 +206,197 @@ export default function ExpiringGroups() { }; return ( - - - - - + + + canManageGroup(currentUser, row.group))} + ownAccess={userId == '@me' || userId == currentUser.id} + /> + + Active + Inactive + + , + }} + /> + displayUserName(row) + ';' + row.email.toLowerCase())} + onChange={handleSearchSubmit} + onInputChange={(event, newInputValue) => { + setSearchInput(newInputValue?.split(';')[0] ?? ''); + }} + defaultValue={searchQuery} + renderOption={renderUserOption} + /> + +
    + + + + + User Name + + + User Email + Group Name + Member or Owner + + Started + + Added by + + + Ending + + + + + + {rows.map((row) => ( + + dayjs(row.ended_at).isAfter(dayjs()) && dayjs(row.ended_at).isBefore(dayjs().add(7, 'day')) + ? highlight.warning.main + : dayjs(row.ended_at).isBefore(dayjs()) + ? highlight.danger.main + : null, + }}> - - Expiring Groups - + {(row.user?.deleted_at ?? null) != null ? ( + + {displayUserName(row.user)} + + ) : ( + + {displayUserName(row.user)} + + )} - canManageGroup(currentUser, row.group))} - ownAccess={userId == '@me' || userId == currentUser.id} - /> + {(row.user?.deleted_at ?? null) != null ? ( + + {row.user?.email.toLowerCase()} + + ) : ( + + {row.user?.email.toLowerCase()} + + )} - - Active - Inactive - - - - , - }} - /> - - - x} - options={searchRows.map((row) => displayUserName(row) + ';' + row.email.toLowerCase())} - onChange={handleSearchSubmit} - onInputChange={(event, newInputValue) => { - setSearchInput(newInputValue?.split(';')[0] ?? ''); - }} - defaultValue={searchQuery} - key={searchQuery} - renderInput={(params) => } - renderOption={(props, option, state) => { - const [displayName, email] = option.split(';'); - return ( -
  • - - - {displayName} - - {email} - - - -
  • - ); - }} - /> + {(row.group?.deleted_at ?? null) != null ? ( + + {row.group?.name ?? ''} + + ) : ( + + {row.group?.name ?? ''} + + )}
    -
    - + {row.is_owner ? 'Owner' : 'Member'} - - User Name - + - User Email - Group Name - Member or Owner - Started + {(row.created_actor?.deleted_at ?? null) != null ? ( + + {displayUserName(row.created_actor)} + + ) : ( + + {displayUserName(row.created_actor)} + + )} - Added by - - - Ending - + + - - - - {rows.map((row) => ( - - dayjs(row.ended_at).isAfter(dayjs()) && dayjs(row.ended_at).isBefore(dayjs().add(7, 'day')) - ? highlight.warning.main - : dayjs(row.ended_at).isBefore(dayjs()) - ? highlight.danger.main - : null, - }}> - - {(row.user?.deleted_at ?? null) != null ? ( - - {displayUserName(row.user)} - - ) : ( - - {displayUserName(row.user)} - - )} - - - {(row.user?.deleted_at ?? null) != null ? ( - - {row.user?.email.toLowerCase()} - - ) : ( - - {row.user?.email.toLowerCase()} - - )} - - - {(row.group?.deleted_at ?? null) != null ? ( - - {row.group?.name ?? ''} - - ) : ( - - {row.group?.name ?? ''} - - )} + {userId == '@me' || currentUser.id == row.user.id ? ( + + - {row.is_owner ? 'Owner' : 'Member'} - - + ) : ownerId == '@me' || canManageGroup(currentUser, row.group) ? ( + + canManageGroup(currentUser, row.group))} select={row.id} /> - - {(row.created_actor?.deleted_at ?? null) != null ? ( - - {displayUserName(row.created_actor)} - - ) : ( - - {displayUserName(row.created_actor)} - - )} - - - - - {userId == '@me' || currentUser.id == row.user.id ? ( - - - - ) : ownerId == '@me' || canManageGroup(currentUser, row.group) ? ( - - canManageGroup(currentUser, row.group))} select={row.id} /> - - ) : ( - - )} - - ))} - {emptyRows > 0 && ( - - - - )} - - - - + ) : ( + + )} + + ))} + {emptyRows > 0 && ( + + - -
    -
    -
    + )} + + + + + + + + ); } diff --git a/src/pages/groups/List.tsx b/src/pages/groups/List.tsx index 2c269b8..131937e 100644 --- a/src/pages/groups/List.tsx +++ b/src/pages/groups/List.tsx @@ -16,13 +16,13 @@ import TableHead from '@mui/material/TableHead'; import TablePagination from '@mui/material/TablePagination'; import TableRow from '@mui/material/TableRow'; import TextField from '@mui/material/TextField'; -import Typography from '@mui/material/Typography'; import {useCurrentUser} from '../../authentication'; import CreateUpdateGroup from './CreateUpdate'; import {displayGroupType, perPage} from '../../helpers'; import {useGetGroups} from '../../api/apiComponents'; import TablePaginationActions from '../../components/actions/TablePaginationActions'; +import TableTopBar, {TableTopBarAutocomplete} from '../../components/TableTopBar'; export default function ListGroups() { const navigate = useNavigate(); @@ -104,102 +104,76 @@ export default function ListGroups() { }; return ( - - - - - + + + + + row.name)} + onChange={handleSearchSubmit} + onInputChange={(event, newInputValue) => setSearchInput(newInputValue)} + defaultValue={searchQuery} + /> + +
    + + + Name + Type + Description + + + + {rows.map((row) => ( + - - Groups - + + {row.name} + - + + {displayGroupType(row)} + - - + + + {(row.description?.length ?? 0) > 115 + ? row.description?.substring(0, 114) + '...' ?? '' + : row.description} + - - x} - options={searchRows.map((row) => row.name)} - onChange={handleSearchSubmit} - onInputChange={(event, newInputValue) => setSearchInput(newInputValue)} - defaultValue={searchQuery} - key={searchQuery} - renderInput={(params) => } - /> - - - - Name - Type - Description - - - {rows.map((row) => ( - - - - {row.name} - - - - - {displayGroupType(row)} - - - - - {(row.description?.length ?? 0) > 115 - ? row.description?.substring(0, 114) + '...' ?? '' - : row.description} - - - - ))} - {emptyRows > 0 && ( - - - - )} - - - - + ))} + {emptyRows > 0 && ( + + - -
    -
    -
    + )} + + + + + + + + ); } diff --git a/src/pages/requests/List.tsx b/src/pages/requests/List.tsx index e3cddc5..153fedc 100644 --- a/src/pages/requests/List.tsx +++ b/src/pages/requests/List.tsx @@ -27,6 +27,7 @@ import CreateRequest from './Create'; import {useGetRequests} from '../../api/apiComponents'; import {displayUserName, perPage} from '../../helpers'; import TablePaginationActions from '../../components/actions/TablePaginationActions'; +import TableTopBar, {TableTopBarAutocomplete} from '../../components/TableTopBar'; dayjs.extend(RelativeTime); @@ -124,189 +125,168 @@ export default function ListRequests() { }; return ( - - - - - + + + + + row.id + + ';' + + displayUserName(row.requester) + + ';' + + row.request_ownership + + ';' + + (row.requested_group?.name ?? '') + + ';' + + (row.status ?? '') + + ';' + + displayUserName(row.resolver), + )} + onChange={handleSearchSubmit} + onInputChange={(event, newInputValue) => { + setSearchInput(newInputValue?.split(';')[0] ?? ''); + }} + defaultValue={searchQuery} + renderOption={(props, option, state) => { + const [id, displayName, ownership, group, status, resolver] = option.split(';'); + return ( +
  • + + + + {displayName} {ownership == 'true' ? 'ownership of' : 'membership to'} {group} + + + {status} {status == 'PENDING' || resolver == '' ? '' : 'by ' + resolver} + + + +
  • + ); + }} + /> +
    +
    + + + Requester + Request + Resolver + Status + Created + + + + {rows.map((row) => ( + + row.status == 'APPROVED' + ? highlight.success.main + : row.status == 'REJECTED' + ? highlight.danger.main + : 'inherit', + }}> - - Access Requests - + {(row.requester?.deleted_at ?? null) != null ? ( + + {displayUserName(row.requester)} + + ) : ( + + {displayUserName(row.requester)} + + )} - - - - x} - options={searchRows.map( - (row) => - row.id + - ';' + - displayUserName(row.requester) + - ';' + - row.request_ownership + - ';' + - (row.requested_group?.name ?? '') + - ';' + - (row.status ?? '') + - ';' + - displayUserName(row.resolver), - )} - onChange={handleSearchSubmit} - onInputChange={(event, newInputValue) => { - setSearchInput(newInputValue?.split(';')[0] ?? ''); - }} - defaultValue={searchQuery} - key={searchQuery} - renderInput={(params) => } - renderOption={(props, option, state) => { - const [id, displayName, ownership, group, status, resolver] = option.split(';'); - return ( -
  • - - - - {displayName} {ownership == 'true' ? 'ownership of' : 'membership to'} {group} - - - {status} {status == 'PENDING' || resolver == '' ? '' : 'by ' + resolver} - - - -
  • - ); - }} - /> -
    -
    - - Requester - Request - Resolver - Status - Created - - - - {rows.map((row) => ( - - row.status == 'APPROVED' - ? highlight.success.main - : row.status == 'REJECTED' - ? highlight.danger.main - : 'inherit', - }}> - - {(row.requester?.deleted_at ?? null) != null ? ( - - {displayUserName(row.requester)} - - ) : ( - - {displayUserName(row.requester)} - - )} - - - {row.request_ownership ? 'Ownership of ' : 'Membership to '} - {(row.requested_group?.deleted_at ?? null) != null ? ( - - {row.requested_group?.name ?? ''} - - ) : ( - - {row.requested_group?.name ?? ''} - - )} - - - {row.resolver == null && row.status != 'PENDING' ? ( - 'Access' - ) : (row.resolver?.deleted_at ?? null) != null ? ( - - {displayUserName(row.resolver)} - - ) : ( - - {displayUserName(row.resolver)} - - )} - - + {row.request_ownership ? 'Ownership of ' : 'Membership to '} + {(row.requested_group?.deleted_at ?? null) != null ? ( + + {row.requested_group?.name ?? ''} + + ) : ( - {row.status} + {row.requested_group?.name ?? ''} - - + )} + + + {row.resolver == null && row.status != 'PENDING' ? ( + 'Access' + ) : (row.resolver?.deleted_at ?? null) != null ? ( + {displayUserName(row.resolver)} + + ) : ( + - {dayjs(row.created_at).startOf('second').fromNow()} + {displayUserName(row.resolver)} - - - - - - ))} - {emptyRows > 0 && ( - - - - )} - - - - + )} + + + + {row.status} + + + + + {dayjs(row.created_at).startOf('second').fromNow()} + + + + + + + ))} + {emptyRows > 0 && ( + + - -
    -
    -
    + )} + + + + + + + + ); } diff --git a/src/pages/roles/Audit.tsx b/src/pages/roles/Audit.tsx index ab11885..c832f37 100644 --- a/src/pages/roles/Audit.tsx +++ b/src/pages/roles/Audit.tsx @@ -31,6 +31,7 @@ import Loading from '../../components/Loading'; import Started from '../../components/Started'; import Ending from '../../components/Ending'; import TablePaginationActions from '../../components/actions/TablePaginationActions'; +import TableTopBar, {TableTopBarAutocomplete} from '../../components/TableTopBar'; type OrderBy = 'moniker' | 'created_at' | 'ended_at'; type OrderDirection = 'asc' | 'desc'; @@ -187,205 +188,173 @@ export default function AuditRole() { }; return ( - - - - - + + + + Active + Inactive + + + Member + Owner + + row.name)} + onChange={handleSearchSubmit} + onInputChange={(event, newInputValue) => setSearchInput(newInputValue)} + defaultValue={searchQuery} + /> + +
    + + + + + Group Name + + + Group Type + Member or Owner + + + Started + + + Added by + + + Ending + + + Removed by + Justification + + + + {rows.map((row) => ( + + row.ended_at == null || dayjs().isBefore(dayjs(row.ended_at)) + ? highlight.success.main + : highlight.danger.main, + }}> - - {(role.deleted_at ?? null) != null ? ( - - {role.name} - - ) : ( - - {role.name} - - )}{' '} - Role Audit - + {(row.group?.deleted_at ?? null) != null ? ( + + {row.group?.name ?? ''} + + ) : ( + + {row.group?.name ?? ''} + + )} - - Active - Inactive - - - - - Member - Owner - + {(row.group?.deleted_at ?? null) != null ? ( + displayGroupType(row.group) + ) : ( + + {displayGroupType(row.group)} + + )} - - x} - options={searchRows.map((row) => row.name)} - onChange={handleSearchSubmit} - onInputChange={(event, newInputValue) => setSearchInput(newInputValue)} - defaultValue={searchQuery} - key={searchQuery} - renderInput={(params) => } - /> - - - + {row.is_owner ? 'Owner' : 'Member'} - - Group Name - + - Group Type - Member or Owner - - Started - + {(row.created_actor?.deleted_at ?? null) != null ? ( + + {displayUserName(row.created_actor)} + + ) : ( + + {displayUserName(row.created_actor)} + + )} - Added by - - Ending - + - Removed by - Justification - - - - {rows.map((row) => ( - - row.ended_at == null || dayjs().isBefore(dayjs(row.ended_at)) - ? highlight.success.main - : highlight.danger.main, - }}> - - {(row.group?.deleted_at ?? null) != null ? ( - - {row.group?.name ?? ''} - - ) : ( - - {row.group?.name ?? ''} - - )} - - - {(row.group?.deleted_at ?? null) != null ? ( - displayGroupType(row.group) - ) : ( - - {displayGroupType(row.group)} - - )} - - {row.is_owner ? 'Owner' : 'Member'} - - - - - {(row.created_actor?.deleted_at ?? null) != null ? ( + + {row.ended_at != null && dayjs().isAfter(dayjs(row.ended_at)) ? ( + (row.ended_actor?.deleted_at ?? null) != null ? ( - {displayUserName(row.created_actor)} + {displayUserName(row.ended_actor)} ) : ( - {displayUserName(row.created_actor)} + {displayUserName(row.ended_actor)} - )} - - - - - - {row.ended_at != null && dayjs().isAfter(dayjs(row.ended_at)) ? ( - (row.ended_actor?.deleted_at ?? null) != null ? ( - - {displayUserName(row.ended_actor)} - - ) : ( - - {displayUserName(row.ended_actor)} - - ) - ) : ( - '' - )} - - - {row.created_reason ? : null} - - - ))} - {emptyRows > 0 && ( - - - - )} - - - - + ) + ) : ( + '' + )} + + {row.created_reason ? : null} + + ))} + {emptyRows > 0 && ( + + - -
    -
    -
    + )} + + + + + + + + ); } diff --git a/src/pages/roles/Expiring.tsx b/src/pages/roles/Expiring.tsx index 1acbb5b..0005a61 100644 --- a/src/pages/roles/Expiring.tsx +++ b/src/pages/roles/Expiring.tsx @@ -32,6 +32,7 @@ import Loading from '../../components/Loading'; import Started from '../../components/Started'; import TablePaginationActions from '../../components/actions/TablePaginationActions'; import {displayUserName, perPage} from '../../helpers'; +import TableTopBar, {TableTopBarAutocomplete} from '../../components/TableTopBar'; type OrderBy = 'moniker' | 'ended_at'; type OrderDirection = 'asc' | 'desc'; @@ -209,211 +210,192 @@ export default function ExpiringRoless() { }; return ( - - - - - + + + canManageGroup(currentUser, row.group))} /> + + Active + Inactive + + {ownerId ? ( + + + + Default Owner + + + + + All Owned + + + + ) : null} + , + }} + /> + row.name)} + onChange={handleSearchSubmit} + onInputChange={(event, newInputValue) => setSearchInput(newInputValue)} + defaultValue={searchQuery} + /> + +
    + + + Role Name + + + Group Name + + + Group Type + Member or Owner + + Started + + Added by + + + Ending + + + + + + {rows.map((row) => ( + + dayjs(row.ended_at).isAfter(dayjs()) && dayjs(row.ended_at).isBefore(dayjs().add(7, 'day')) + ? highlight.warning.main + : dayjs(row.ended_at).isBefore(dayjs()) + ? highlight.danger.main + : null, + }}> - - Expiring Roles - + {(row.group?.deleted_at ?? null) != null ? ( + + {row.role_group?.name ?? ''} + + ) : ( + + {row.role_group?.name ?? ''} + + )} - canManageGroup(currentUser, row.group))} /> + {(row.group?.deleted_at ?? null) != null ? ( + + {row.group?.name ?? ''} + + ) : ( + + {row.group?.name ?? ''} + + )} - - Active - Inactive - - - {ownerId ? ( - - - - - Default Owner - - - - - All Owned - - - - - ) : null} - - , - }} - /> - - - x} - options={searchRows.map((row) => row.name)} - onChange={handleSearchSubmit} - onInputChange={(event, newInputValue) => setSearchInput(newInputValue)} - defaultValue={searchQuery} - key={searchQuery} - renderInput={(params) => } - /> + {row.group?.type == 'okta_group' ? 'Group' : 'app_group' ? 'App Group' : 'Role Group'} - - - Role Name + {row.is_owner ? 'Owner' : 'Member'} - - Group Name - + - Group Type - Member or Owner - Started + {(row.created_actor?.deleted_at ?? null) != null ? ( + + {displayUserName(row.created_actor)} + + ) : ( + + {displayUserName(row.created_actor)} + + )} - Added by - - - Ending - + + - - - - {rows.map((row) => ( - - dayjs(row.ended_at).isAfter(dayjs()) && dayjs(row.ended_at).isBefore(dayjs().add(7, 'day')) - ? highlight.warning.main - : dayjs(row.ended_at).isBefore(dayjs()) - ? highlight.danger.main - : null, - }}> - - {(row.group?.deleted_at ?? null) != null ? ( - - {row.role_group?.name ?? ''} - - ) : ( - - {row.role_group?.name ?? ''} - - )} - - - {(row.group?.deleted_at ?? null) != null ? ( - - {row.group?.name ?? ''} - - ) : ( - - {row.group?.name ?? ''} - - )} - - - {row.group?.type == 'okta_group' ? 'Group' : 'app_group' ? 'App Group' : 'Role Group'} - - {row.is_owner ? 'Owner' : 'Member'} - - + {ownerId == '@me' || canManageGroup(currentUser, row.group) ? ( + + canManageGroup(currentUser, row.group))} select={row.id} /> - - {(row.created_actor?.deleted_at ?? null) != null ? ( - - {displayUserName(row.created_actor)} - - ) : ( - - {displayUserName(row.created_actor)} - - )} - - - - - {ownerId == '@me' || canManageGroup(currentUser, row.group) ? ( - - canManageGroup(currentUser, row.group))} select={row.id} /> - - ) : ( - - )} - - ))} - {emptyRows > 0 && ( - - - - )} - - - - + ) : ( + + )} + + ))} + {emptyRows > 0 && ( + + - -
    -
    -
    + )} + + + + + + + + ); } diff --git a/src/pages/roles/List.tsx b/src/pages/roles/List.tsx index 8fbbbb5..37607a9 100644 --- a/src/pages/roles/List.tsx +++ b/src/pages/roles/List.tsx @@ -24,6 +24,7 @@ import CreateUpdateGroup from '../groups/CreateUpdate'; import {perPage} from '../../helpers'; import {useGetRoles} from '../../api/apiComponents'; import TablePaginationActions from '../../components/actions/TablePaginationActions'; +import TableTopBar, {TableTopBarAutocomplete} from '../../components/TableTopBar'; export default function ListRoles() { const navigate = useNavigate(); @@ -105,102 +106,70 @@ export default function ListRoles() { }; return ( - - - - - + + + + + row.name)} + onChange={handleSearchSubmit} + onInputChange={(event, newInputValue) => setSearchInput(newInputValue)} + defaultValue={searchQuery} + /> + +
    + + + Name + Description + + + + {rows.map((row) => ( + - - Roles - + + {row.name} + - - - - - - - - - x} - options={searchRows.map((row) => row.name)} - onChange={handleSearchSubmit} - onInputChange={(event, newInputValue) => setSearchInput(newInputValue)} - defaultValue={searchQuery} - key={searchQuery} - renderInput={(params) => } - /> - + + + {(row.description?.length ?? 0) > 115 + ? row.description?.substring(0, 114) + '...' ?? '' + : row.description} + - - Name - Description - - - - {rows.map((row) => ( - - - - {row.name} - - - - - {(row.description?.length ?? 0) > 115 - ? row.description?.substring(0, 114) + '...' ?? '' - : row.description} - - - - ))} - {emptyRows > 0 && ( - - - - )} - - - - + ))} + {emptyRows > 0 && ( + + - -
    -
    -
    + )} + + + + + + + + ); } diff --git a/src/pages/tags/List.tsx b/src/pages/tags/List.tsx index 97a2577..4e9db00 100644 --- a/src/pages/tags/List.tsx +++ b/src/pages/tags/List.tsx @@ -22,6 +22,7 @@ import CreateUpdateTag from './CreateUpdate'; import {perPage} from '../../helpers'; import {useGetTags} from '../../api/apiComponents'; import TablePaginationActions from '../../components/actions/TablePaginationActions'; +import TableTopBar, {TableTopBarAutocomplete} from '../../components/TableTopBar'; export default function ListTags() { const navigate = useNavigate(); @@ -103,91 +104,67 @@ export default function ListTags() { }; return ( - - - - - + + + + row.name)} + onChange={handleSearchSubmit} + onInputChange={(event, newInputValue) => setSearchInput(newInputValue)} + defaultValue={searchQuery} + /> + +
    + + + Name + Description + + + + {rows.map((row) => ( + - - Tags - + + {row.name} + - - - - - - x} - options={searchRows.map((row) => row.name)} - onChange={handleSearchSubmit} - onInputChange={(event, newInputValue) => setSearchInput(newInputValue)} - defaultValue={searchQuery} - key={searchQuery} - renderInput={(params) => } - /> - + + + {(row.description ?? '').length > 115 + ? row.description?.substring(0, 114) + '...' + : row.description ?? ''} + - - Name - Description - - - - {rows.map((row) => ( - - - - {row.name} - - - - - {(row.description ?? '').length > 115 - ? row.description?.substring(0, 114) + '...' - : row.description ?? ''} - - - - ))} - {emptyRows > 0 && ( - - - - )} - - - - + ))} + {emptyRows > 0 && ( + + - -
    -
    -
    + )} + + + + + + + + ); } diff --git a/src/pages/users/Audit.tsx b/src/pages/users/Audit.tsx index f3339e0..595fdca 100644 --- a/src/pages/users/Audit.tsx +++ b/src/pages/users/Audit.tsx @@ -32,6 +32,7 @@ import Loading from '../../components/Loading'; import Started from '../../components/Started'; import Ending from '../../components/Ending'; import TablePaginationActions from '../../components/actions/TablePaginationActions'; +import TableTopBar, {TableTopBarAutocomplete} from '../../components/TableTopBar'; type OrderBy = 'moniker' | 'created_at' | 'ended_at'; type OrderDirection = 'asc' | 'desc'; @@ -189,229 +190,199 @@ export default function AuditUser() { }; return ( - - - - - - - - {(user.deleted_at ?? null) != null ? ( - - {displayUserName(user)} - - ) : ( - - {displayUserName(user)} - - )}{' '} - Audit - - + + + + Member + Owner + + + Active + Inactive + + row.name)} + onChange={handleSearchSubmit} + onInputChange={(event, newInputValue) => setSearchInput(newInputValue)} + defaultValue={searchQuery} + /> + +
    + + + + + Group Name + + + Group Type + Member or Owner + Direct or via Role + + + Started + + + Added by + + + Ending + + + Removed by + Access Request + Justification + + + + {rows.map((row) => ( + + row.ended_at == null || dayjs().isBefore(dayjs(row.ended_at)) + ? highlight.success.main + : highlight.danger.main, + }}> - - Member - Owner - + {(row.group?.deleted_at ?? null) != null ? ( + + {row.group?.name ?? ''} + + ) : ( + + {row.group?.name ?? ''} + + )} - - - Active - Inactive - + + {(row.group?.deleted_at ?? null) != null ? ( + displayGroupType(row.group) + ) : ( + + {displayGroupType(row.group)} + + )} - - x} - options={searchRows.map((row) => row.name)} - onChange={handleSearchSubmit} - onInputChange={(event, newInputValue) => setSearchInput(newInputValue)} - defaultValue={searchQuery} - key={searchQuery} - renderInput={(params) => } - /> + {row.is_owner ? 'Owner' : 'Member'} + + {row.role_group_mapping == null ? ( + + ) : ( + navigate(`/roles/${row.role_group_mapping?.role_group?.name}`)} + /> + )} - - - - Group Name - + - Group Type - Member or Owner - Direct or via Role - - Started - + {(row.created_actor?.deleted_at ?? null) != null ? ( + + {displayUserName(row.created_actor)} + + ) : ( + + {displayUserName(row.created_actor)} + + )} - Added by - - Ending - + - Removed by - Access Request - Justification - - - - {rows.map((row) => ( - - row.ended_at == null || dayjs().isBefore(dayjs(row.ended_at)) - ? highlight.success.main - : highlight.danger.main, - }}> - - {(row.group?.deleted_at ?? null) != null ? ( - - {row.group?.name ?? ''} - - ) : ( - - {row.group?.name ?? ''} - - )} - - - {(row.group?.deleted_at ?? null) != null ? ( - displayGroupType(row.group) - ) : ( - - {displayGroupType(row.group)} - - )} - - {row.is_owner ? 'Owner' : 'Member'} - - {row.role_group_mapping == null ? ( - - ) : ( - navigate(`/roles/${row.role_group_mapping?.role_group?.name}`)} - /> - )} - - - - - - {(row.created_actor?.deleted_at ?? null) != null ? ( + + {row.ended_at != null && dayjs().isAfter(dayjs(row.ended_at)) ? ( + (row.ended_actor?.deleted_at ?? null) != null ? ( - {displayUserName(row.created_actor)} + {displayUserName(row.ended_actor)} ) : ( - {displayUserName(row.created_actor)} + {displayUserName(row.ended_actor)} - )} - - - - - - {row.ended_at != null && dayjs().isAfter(dayjs(row.ended_at)) ? ( - (row.ended_actor?.deleted_at ?? null) != null ? ( - - {displayUserName(row.ended_actor)} - - ) : ( - - {displayUserName(row.ended_actor)} - - ) - ) : ( - '' - )} - - - {row.access_request != null ? ( - - ) : null} - - - {row.created_reason ? : null} - - - ))} - {emptyRows > 0 && ( - - - - )} - - - - + ) + ) : ( + '' + )} + + + {row.access_request != null ? ( + + ) : null} + + + {row.created_reason ? : null} + + + ))} + {emptyRows > 0 && ( + + - -
    -
    -
    + )} + + + + + + + + ); } diff --git a/src/pages/users/List.tsx b/src/pages/users/List.tsx index fb13120..20a04c3 100644 --- a/src/pages/users/List.tsx +++ b/src/pages/users/List.tsx @@ -22,6 +22,8 @@ import {useGetUsers} from '../../api/apiComponents'; import TablePaginationActions from '../../components/actions/TablePaginationActions'; import UserAvatar from './UserAvatar'; import {displayUserName, perPage} from '../../helpers'; +import {Stack} from '@mui/material'; +import TableTopBar, {renderUserOption, TableTopBarAutocomplete} from '../../components/TableTopBar'; export default function ListUsers() { const navigate = useNavigate(); @@ -102,110 +104,83 @@ export default function ListUsers() { }; return ( - - - - - + + + displayUserName(row) + ';' + row.email.toLowerCase())} + onInputChange={(event, newInputValue) => { + setSearchInput(newInputValue?.split(';')[0] ?? ''); + }} + onChange={handleSearchSubmit} + defaultValue={searchQuery} + key={searchQuery} + renderOption={renderUserOption} + /> + +
    + + + + Name + Email + + + + {rows.map((row) => ( + - - Users - + + + - - - x} - options={searchRows.map((row) => displayUserName(row) + ';' + row.email.toLowerCase())} - onInputChange={(event, newInputValue) => { - setSearchInput(newInputValue?.split(';')[0] ?? ''); - }} - onChange={handleSearchSubmit} - defaultValue={searchQuery} - key={searchQuery} - renderInput={(params) => } - renderOption={(props, option, state) => { - const [displayName, email] = option.split(';'); - return ( -
  • - - - {displayName} - - {email} - - - -
  • - ); - }} - /> + + + {displayUserName(row)} + + + + + {row.email.toLowerCase()} +
    - - - Name - Email - - - - {rows.map((row) => ( - - - - - - - - - {displayUserName(row)} - - - - - {row.email.toLowerCase()} - - - - ))} - {emptyRows > 0 && ( - - - - )} - - - - + ))} + {emptyRows > 0 && ( + + - -
    -
    -
    + )} + + + + + + + + ); } From fa37b58cfd6669b571955a5ee83d2f6dde3a64b2 Mon Sep 17 00:00:00 2001 From: Jill Date: Wed, 30 Oct 2024 14:13:22 -0700 Subject: [PATCH 16/36] figure out why this thing hates being full width of the parent component when it's in an accordion element --- .../apps/detail/AppsAccordionListGroup.tsx | 102 +++++++++--------- src/pages/apps/detail/index.tsx | 6 -- 2 files changed, 49 insertions(+), 59 deletions(-) diff --git a/src/pages/apps/detail/AppsAccordionListGroup.tsx b/src/pages/apps/detail/AppsAccordionListGroup.tsx index 3c6a31f..f4b8087 100644 --- a/src/pages/apps/detail/AppsAccordionListGroup.tsx +++ b/src/pages/apps/detail/AppsAccordionListGroup.tsx @@ -112,6 +112,7 @@ export const AppsAccordionListGroup: React.FC = ({ap break; } }; + return ( {app_group && @@ -119,67 +120,62 @@ export const AppsAccordionListGroup: React.FC = ({ap - - - - - - + }> + + + + + + + {appGroup.name} + + + + Can manage app and implicitly own all app groups + + + + + - {appGroup.name} - - - - Can manage app and implicitly own all app groups - - - - - - - Total Owners: {appGroup.active_user_ownerships?.length || 0} - - - - - - - - - - }> + display: 'flex', + justifyContent: 'flex-end', + alignItems: 'right', + }}> + + Total Owners: {appGroup.active_user_ownerships?.length || 0}
    + Total Members: {appGroup.active_user_memberships?.length || 0} + +
    +
    + + + + + + + Group Owners - - - - - - }> - Group Members + Members - - - - - - - - + + + +
    + +
    diff --git a/src/pages/apps/detail/index.tsx b/src/pages/apps/detail/index.tsx index 5195071..0cf5a45 100644 --- a/src/pages/apps/detail/index.tsx +++ b/src/pages/apps/detail/index.tsx @@ -66,12 +66,6 @@ export const ReadApp = () => { pathParams: {appId: id ?? ''}, }); - const [expanded, setExpanded] = React.useState(false); - - const handleChange = (panel: string) => (event: React.SyntheticEvent, isExpanded: boolean) => { - setExpanded(isExpanded ? panel : false); - }; - if (isError) { return ; } From 1aa0aa825a221d2af89fdc71cd706ed95f303399 Mon Sep 17 00:00:00 2001 From: Jill Date: Wed, 30 Oct 2024 16:18:08 -0700 Subject: [PATCH 17/36] update owner group accordion on app group page --- .../apps/detail/AppsAccordionListGroup.tsx | 211 +++++++++--------- 1 file changed, 110 insertions(+), 101 deletions(-) diff --git a/src/pages/apps/detail/AppsAccordionListGroup.tsx b/src/pages/apps/detail/AppsAccordionListGroup.tsx index f4b8087..886d705 100644 --- a/src/pages/apps/detail/AppsAccordionListGroup.tsx +++ b/src/pages/apps/detail/AppsAccordionListGroup.tsx @@ -30,9 +30,10 @@ interface AppAccordionListGroupProps { } interface GroupDetailListProps { member_list: OktaUserGroupMember[]; + title?: string; } -const GroupDetailList: React.FC = ({member_list}) => { +const GroupDetailList: React.FC = ({member_list, title}) => { const sortGroupMembers = ( [aUserId, aUsers]: [string, Array], [bUserId, bUsers]: [string, Array], @@ -43,56 +44,64 @@ const GroupDetailList: React.FC = ({member_list}) => { }; return ( - - - - - Name - Email - Ending - - - - {member_list.length > 0 ? ( - member_list.map((member: OktaUserGroupMember) => ( - - - - {displayUserName(member.active_user)} - - - - - {member.active_user?.email.toLowerCase()} - - - - - - - )) - ) : ( - - )} - + + {title && ( + + {title} + + )} + + +
    + + + Name + Email + Ending + + + + {member_list.length > 0 ? ( + member_list.map((member: OktaUserGroupMember) => ( + + + + {displayUserName(member.active_user)} + + + + + {member.active_user?.email.toLowerCase()} + + + + + + + )) + ) : ( + + )} + - - - -
    -
    + + + + + + ); }; @@ -119,64 +128,64 @@ export const AppsAccordionListGroup: React.FC = ({ap app_group?.map((appGroup) => ( - - - }> - - - - - - - {appGroup.name} - - - - Can manage app and implicitly own all app groups - - - - - - - Total Owners: {appGroup.active_user_ownerships?.length || 0}
    - Total Members: {appGroup.active_user_memberships?.length || 0} -
    -
    -
    -
    -
    - + + }> + + + + + {appGroup.name} + + + + Can manage app and implicitly own all app groups + + + + + Total Owners: {appGroup.active_user_ownerships?.length || 0}
    + Total Members: {appGroup.active_user_memberships?.length || 0} +
    +
    +
    + +
    - - - Group Owners - - - - Members - - + + + - - -
    + + +
    ))} From a50ceaa218298357e76550240b51935108232806 Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Thu, 31 Oct 2024 10:51:41 -0700 Subject: [PATCH 18/36] [fix] update user avatars to have adequate contrast across themes (#178) --- src/pages/users/UserAvatar.tsx | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/src/pages/users/UserAvatar.tsx b/src/pages/users/UserAvatar.tsx index 7ebd176..ac89bab 100644 --- a/src/pages/users/UserAvatar.tsx +++ b/src/pages/users/UserAvatar.tsx @@ -1,24 +1,14 @@ +import {PaletteMode, useTheme} from '@mui/material'; import Avatar from '@mui/material/Avatar'; import Typography from '@mui/material/Typography'; -function stringToColor(string: string) { - let hash = 0; - let i; - - /* eslint-disable no-bitwise */ - for (i = 0; i < string.length; i += 1) { - hash = string.charCodeAt(i) + ((hash << 5) - hash); +function stringToColor(string: string, mode: PaletteMode) { + const hue = string.split('').reduce((acc, curr) => curr.charCodeAt(0) + acc, 0) % 360; + if (mode === 'dark') { + return `hsl(${hue}, 65%, 70%)`; + } else { + return `hsl(${hue}, 65%, 55%)`; } - - let color = '#'; - - for (i = 0; i < 3; i += 1) { - const value = (hash >> (i * 8)) & 0xff; - color += `00${value.toString(16)}`.slice(-2); - } - /* eslint-enable no-bitwise */ - - return color; } interface UserAvatarProps { @@ -28,13 +18,16 @@ interface UserAvatarProps { } export default function UserAvatar(props: UserAvatarProps) { + const { + palette: {mode}, + } = useTheme(); const splitName = props.name.split(' '); return ( Date: Thu, 31 Oct 2024 13:49:28 -0700 Subject: [PATCH 19/36] [fix] dark mode bugs (#179) --- src/App.tsx | 68 +++++++++++++++++++++++++++++++++------ src/pages/apps/Read.tsx | 3 +- src/pages/groups/Read.tsx | 3 +- 3 files changed, 60 insertions(+), 14 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 085b3fa..0f3e6b3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -48,7 +48,7 @@ import { useMediaQuery, useTheme, } from '@mui/material'; -import {DarkMode, LightMode} from '@mui/icons-material'; +import {DarkMode, LightMode, Monitor} from '@mui/icons-material'; import {lightGreen, red, yellow} from '@mui/material/colors'; const drawerWidth: number = 240; @@ -102,26 +102,54 @@ const Drawer = styled(MuiDrawer, { })); function ThemeToggle({setThemeMode, condensed}: {setThemeMode: (theme: PaletteMode) => void; condensed: boolean}) { - const theme = useTheme(); + const [storedTheme, setStoredTheme] = React.useState( + localStorage.getItem('user-set-color-scheme') as 'light' | 'dark' | null, + ); + const currentTheme = useTheme(); + const systemTheme = useMediaQuery('(prefers-color-scheme: dark)') ? 'dark' : 'light'; + + const handleThemeOverride = (theme: PaletteMode) => { + setThemeMode(theme); + localStorage.setItem('user-set-color-scheme', theme); + setStoredTheme(theme); + }; + + const handleSystemDefault = () => { + setThemeMode(systemTheme); + localStorage.removeItem('user-set-color-scheme'); + setStoredTheme(null); + }; + return ( - {(theme.palette.mode != 'light' || !condensed) && ( + {(currentTheme.palette.mode != 'light' || !condensed) && ( setThemeMode('light')} + selected={storedTheme === 'light'} + onClick={() => handleThemeOverride('light')} aria-label="Light mode"> )} - {(theme.palette.mode != 'dark' || !condensed) && ( + {!condensed && ( + + + + + + )} + {(currentTheme.palette.mode != 'dark' || !condensed) && ( setThemeMode('dark')} + selected={storedTheme === 'dark'} + onClick={() => handleThemeOverride('dark')} aria-label="Dark mode"> @@ -235,8 +263,9 @@ function Dashboard({setThemeMode}: {setThemeMode: (theme: PaletteMode) => void}) } export default function App() { - const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)'); - const initialMode = prefersDarkMode ? 'dark' : 'light'; + const storedTheme = localStorage.getItem('user-set-color-scheme') as 'light' | 'dark' | null; + const systemTheme = useMediaQuery('(prefers-color-scheme: dark)') ? 'dark' : 'light'; + const initialMode = storedTheme ?? systemTheme; const [mode, setMode] = React.useState(initialMode); // See https://discord.com/branding @@ -264,6 +293,25 @@ export default function App() { accent: mode === 'light' ? '#5865F2' : '#A5B2FF', }, }, + components: { + MuiChip: { + styleOverrides: { + colorPrimary: ({ownerState, theme}) => ({ + ...(ownerState.variant === 'outlined' && + ownerState.color === 'primary' && { + color: theme.palette.text.accent, + borderColor: theme.palette.text.accent, + }), + }), + deleteIcon: ({ownerState, theme}) => ({ + ...(ownerState.variant === 'outlined' && + ownerState.color === 'primary' && { + color: theme.palette.text.accent, + }), + }), + }, + }, + }, }); return createTheme(base, { palette: { diff --git a/src/pages/apps/Read.tsx b/src/pages/apps/Read.tsx index 70991c4..e6dabbd 100644 --- a/src/pages/apps/Read.tsx +++ b/src/pages/apps/Read.tsx @@ -104,8 +104,7 @@ export default function ReadApp() { sx={{ margin: '2px', marginTop: '5px', - bgcolor: (theme) => - tagMap.active_tag!.enabled ? 'primary' : theme.palette.action.disabledBackground, + bgcolor: (theme) => (tagMap.active_tag!.enabled ? 'primary' : theme.palette.action.disabled), }} /> ))} diff --git a/src/pages/groups/Read.tsx b/src/pages/groups/Read.tsx index d0ea52a..490ead9 100644 --- a/src/pages/groups/Read.tsx +++ b/src/pages/groups/Read.tsx @@ -256,8 +256,7 @@ export default function ReadGroup() { icon={} sx={{ margin: '10px 2px 0 2px', - bgcolor: (theme) => - tagMap.active_tag!.enabled ? 'primary' : theme.palette.action.disabledBackground, + bgcolor: (theme) => (tagMap.active_tag!.enabled ? 'primary' : theme.palette.action.disabled), }} /> ))} From 9015a9c9991301fd20e3c1fc3729063a4f732d7b Mon Sep 17 00:00:00 2001 From: amyjchen Date: Thu, 31 Oct 2024 15:30:21 -0700 Subject: [PATCH 20/36] fix missing key --- src/pages/Home.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 9f981c0..276b60c 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -129,7 +129,7 @@ function AccordionMaker(props: AccordionMakerProps) { {sections[props.which][0]} {Object.entries(guide[props.which]).map(([key, value]: [string, string]) => ( - + } aria-controls={'panel-content' + key} @@ -179,7 +179,7 @@ export default function Home() { {Object.entries(sections).map(([key, [title, buttonTitle, icon]]) => ( - +