From 2da24013666f31624be7cedce2498d4ea930ab98 Mon Sep 17 00:00:00 2001 From: Chris Brown <1731074+ccbrown@users.noreply.github.com> Date: Mon, 12 May 2025 10:35:22 -0400 Subject: [PATCH] add basic admin control panel --- frontend/src/app/(user-area)/Header.tsx | 11 +++ .../(user-area)/control-panel/TabLayout.tsx | 23 ++++++ .../app/(user-area)/control-panel/layout.tsx | 21 +++++ .../app/(user-area)/control-panel/page.tsx | 7 ++ .../control-panel/teams/[teamId]/page.tsx | 82 +++++++++++++++++++ .../(user-area)/control-panel/teams/page.tsx | 50 +++++++++++ frontend/src/models/teams.ts | 7 ++ 7 files changed, 201 insertions(+) create mode 100644 frontend/src/app/(user-area)/control-panel/TabLayout.tsx create mode 100644 frontend/src/app/(user-area)/control-panel/layout.tsx create mode 100644 frontend/src/app/(user-area)/control-panel/page.tsx create mode 100644 frontend/src/app/(user-area)/control-panel/teams/[teamId]/page.tsx create mode 100644 frontend/src/app/(user-area)/control-panel/teams/page.tsx diff --git a/frontend/src/app/(user-area)/Header.tsx b/frontend/src/app/(user-area)/Header.tsx index 90ac4ec0..1d67f5ac 100644 --- a/frontend/src/app/(user-area)/Header.tsx +++ b/frontend/src/app/(user-area)/Header.tsx @@ -5,12 +5,14 @@ import { ArrowRightStartOnRectangleIcon, ChevronDownIcon, CogIcon, + ComputerDesktopIcon, UserCircleIcon, UserGroupIcon, } from '@heroicons/react/24/outline'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; +import { UserRole } from '@/generated/api'; import { Logo } from '@/components'; import { useCurrentUser } from '@/hooks'; import { useDispatch } from '@/store'; @@ -68,6 +70,15 @@ export const Header = (props: Props) => { Teams + {currentUser?.role === UserRole.Administrator && ( + + + Control Panel + + )}
signOut()} diff --git a/frontend/src/app/(user-area)/control-panel/TabLayout.tsx b/frontend/src/app/(user-area)/control-panel/TabLayout.tsx new file mode 100644 index 00000000..0d882481 --- /dev/null +++ b/frontend/src/app/(user-area)/control-panel/TabLayout.tsx @@ -0,0 +1,23 @@ +'use client'; + +import { UserGroupIcon } from '@heroicons/react/24/outline'; + +import { TabLayout as TabLayoutImpl } from '@/components'; + +const TABS = [ + { + title: 'Teams', + path: '/control-panel/teams', + icon: UserGroupIcon, + }, +]; + +interface Props { + children?: React.ReactNode; +} + +export const TabLayout = (props: Props) => ( + Control Panel} tabs={TABS}> + {props.children} + +); diff --git a/frontend/src/app/(user-area)/control-panel/layout.tsx b/frontend/src/app/(user-area)/control-panel/layout.tsx new file mode 100644 index 00000000..43ac6ab2 --- /dev/null +++ b/frontend/src/app/(user-area)/control-panel/layout.tsx @@ -0,0 +1,21 @@ +import type { Metadata } from 'next'; + +import { Header } from '../Header'; +import { TabLayout } from './TabLayout'; + +export const metadata: Metadata = { + title: 'Control Panel', +}; + +export default function Layout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( +
+
+ {children} +
+ ); +} diff --git a/frontend/src/app/(user-area)/control-panel/page.tsx b/frontend/src/app/(user-area)/control-panel/page.tsx new file mode 100644 index 00000000..eebfada6 --- /dev/null +++ b/frontend/src/app/(user-area)/control-panel/page.tsx @@ -0,0 +1,7 @@ +import { redirect } from 'next/navigation'; + +const Page = () => { + redirect('control-panel/teams'); +}; + +export default Page; diff --git a/frontend/src/app/(user-area)/control-panel/teams/[teamId]/page.tsx b/frontend/src/app/(user-area)/control-panel/teams/[teamId]/page.tsx new file mode 100644 index 00000000..c4ba7ef1 --- /dev/null +++ b/frontend/src/app/(user-area)/control-panel/teams/[teamId]/page.tsx @@ -0,0 +1,82 @@ +'use client'; + +import { useParams } from 'next/navigation'; +import { useEffect } from 'react'; + +import { Tooltip } from '@/components'; +import { useDispatch, useSelector } from '@/store'; + +const Page = () => { + const { teamId } = useParams<{ teamId: string }>(); + + const dispatch = useDispatch(); + + useEffect(() => { + dispatch.teams.fetch(teamId); + }, [dispatch, teamId]); + + const team = useSelector((state) => state.teams.teams[teamId]); + + useEffect(() => { + if (team) { + dispatch.aws.fetchIntegrationsByTeamId(team.id); + } + }, [dispatch, team]); + + const awsIntegrationIds = useSelector((state) => state.aws.teamIntegrationIds[teamId]); + const allAwsIntegrations = useSelector((state) => state.aws.integrations); + + if (!team) { + return
Loading...
; + } + + return ( +
+

{team.name}

+
+ Entitlements:{' '} + {team.entitlements.teamFeatures + ? 'Team Features' + : team.entitlements.individualFeatures + ? 'Individual Features' + : 'None'} +
+

AWS Integrations

+ + + + + + + + + + + {awsIntegrationIds + ?.map((integrationId) => allAwsIntegrations[integrationId]) + .filter((integration) => integration) + .map((integration) => ( + + + + + + + ))} + +
NameOrgsSCPsCloudTrail Trail
{integration.name}{integration.getAccountNamesFromOrganizations ? 'Yes' : 'No'}{integration.manageScps ? 'Yes' : 'No'} + {integration.cloudtrailTrail ? ( + + Yes + + ) : ( + 'No' + )} +
+
+ ); +}; + +export default Page; diff --git a/frontend/src/app/(user-area)/control-panel/teams/page.tsx b/frontend/src/app/(user-area)/control-panel/teams/page.tsx new file mode 100644 index 00000000..84b3def0 --- /dev/null +++ b/frontend/src/app/(user-area)/control-panel/teams/page.tsx @@ -0,0 +1,50 @@ +'use client'; + +import Link from 'next/link'; +import { useEffect } from 'react'; + +import { useDispatch, useSelector } from '@/store'; + +const Page = () => { + const dispatch = useDispatch(); + + useEffect(() => { + dispatch.teams.fetchAll(); + }, [dispatch]); + + const teams = useSelector((state) => state.teams.teams); + + return ( + <> +

Teams

+ + + + + + + + + {Object.values(teams).map((team) => ( + + + + + ))} + +
NameEntitlements
+ + {team.name} + + + {team.entitlements.teamFeatures + ? 'Team Features' + : team.entitlements.individualFeatures + ? 'Individual Features' + : 'None'} +
+ + ); +}; + +export default Page; diff --git a/frontend/src/models/teams.ts b/frontend/src/models/teams.ts index 0242cc8e..aee88b06 100644 --- a/frontend/src/models/teams.ts +++ b/frontend/src/models/teams.ts @@ -139,6 +139,13 @@ export const teams = createModel()({ }); dispatch.teams.put(resp); }, + async fetchAll(_payload: void, state) { + const api = new TeamApi(apiConfiguration(state.api)); + const resp = await api.getTeams(); + resp.forEach((team) => { + dispatch.teams.put(team); + }); + }, async fetchBillingProfile(id: string, state) { const api = new TeamApi(apiConfiguration(state.api)); try {