From 9b3193f512bebee849bde1ac2bd345ab247af8f0 Mon Sep 17 00:00:00 2001 From: Jeremy Foster Date: Wed, 17 Jan 2024 10:50:55 -0800 Subject: [PATCH] HOSTSD-227 HSB user pages (#53) --- .../client/admin/organizations/[id]/page.tsx | 16 -- .../app/client/admin/organizations/page.tsx | 3 - src/dashboard/src/app/client/admin/page.tsx | 6 + .../src/app/client/admin/users/page.tsx | 6 + .../src/app/client/dashboard/[id]/page.tsx | 16 -- .../src/app/client/dashboard/page.tsx | 28 +-- src/dashboard/src/app/client/servers/page.tsx | 19 ++ .../src/app/hsb/admin/groups/[id]/page.tsx | 16 -- .../src/app/hsb/admin/groups/page.tsx | 3 - .../src/app/hsb/admin/organizations/page.tsx | 6 + .../src/app/hsb/admin/users/page.tsx | 202 +++++++++--------- .../src/app/hsb/dashboard/Page.module.scss | 11 - .../src/app/hsb/dashboard/[id]/page.tsx | 16 -- src/dashboard/src/app/hsb/dashboard/page.tsx | 28 +-- src/dashboard/src/app/hsb/servers/page.tsx | 19 ++ .../allocationTable/AllocationTable.tsx | 24 ++- .../constants/OperatingSystems.ts | 12 -- .../charts/allocationTable/constants/index.ts | 1 - .../hooks/useAllocationByOS.ts | 9 +- .../charts/allocationTable/utils/getLabel.ts | 26 +-- .../bar/allocationByOS/AllocationByOS.tsx | 15 +- .../AllocationByStorageVolume.tsx | 22 +- .../allocationByVolume/AllocationByVolume.tsx | 42 ++++ .../bar/allocationByVolume/defaultData.ts | 46 ++++ .../charts/bar/allocationByVolume/index.ts | 1 + .../src/components/charts/bar/index.ts | 1 + .../segmentedBarChart/SegmentedBarChart.tsx | 7 +- .../allOrganizations/AllOrganizations.tsx | 18 +- .../hooks/useAllOrganizationsDoughnutChart.ts | 6 +- .../doughnut/totalStorage/TotalStorage.tsx | 11 +- .../hooks/useTotalStorageDoughnutChart.ts | 6 +- .../src/components/dashboard/Dashboard.tsx | 112 ++++++++++ .../src/components/dashboard/index.ts | 1 + .../src/components/filter/Filter.tsx | 43 +++- .../src/components/header/Header.tsx | 10 +- src/dashboard/src/components/index.ts | 1 + src/dashboard/src/hooks/dashboard/index.ts | 4 + .../useDashboardOperatingSystemItems.ts | 15 ++ .../dashboard/useDashboardOrganizations.ts | 15 ++ .../dashboard/useDashboardServerItems.ts | 15 ++ .../hooks/dashboard/useDashboardTenants.ts | 15 ++ .../filter/useFilteredOperatingSystemItems.ts | 4 +- src/dashboard/src/hooks/index.ts | 1 + src/dashboard/src/hooks/useAuth.ts | 19 +- src/dashboard/src/hooks/useSecureRoute.ts | 16 ++ src/dashboard/src/store/useDashboard.ts | 18 ++ 46 files changed, 617 insertions(+), 314 deletions(-) delete mode 100644 src/dashboard/src/app/client/admin/organizations/[id]/page.tsx delete mode 100644 src/dashboard/src/app/client/admin/organizations/page.tsx delete mode 100644 src/dashboard/src/app/client/dashboard/[id]/page.tsx create mode 100644 src/dashboard/src/app/client/servers/page.tsx delete mode 100644 src/dashboard/src/app/hsb/admin/groups/[id]/page.tsx delete mode 100644 src/dashboard/src/app/hsb/admin/groups/page.tsx delete mode 100644 src/dashboard/src/app/hsb/dashboard/Page.module.scss delete mode 100644 src/dashboard/src/app/hsb/dashboard/[id]/page.tsx create mode 100644 src/dashboard/src/app/hsb/servers/page.tsx delete mode 100644 src/dashboard/src/components/charts/allocationTable/constants/OperatingSystems.ts delete mode 100644 src/dashboard/src/components/charts/allocationTable/constants/index.ts create mode 100644 src/dashboard/src/components/charts/bar/allocationByVolume/AllocationByVolume.tsx create mode 100644 src/dashboard/src/components/charts/bar/allocationByVolume/defaultData.ts create mode 100644 src/dashboard/src/components/charts/bar/allocationByVolume/index.ts create mode 100644 src/dashboard/src/components/dashboard/Dashboard.tsx create mode 100644 src/dashboard/src/components/dashboard/index.ts create mode 100644 src/dashboard/src/hooks/dashboard/useDashboardOperatingSystemItems.ts create mode 100644 src/dashboard/src/hooks/dashboard/useDashboardOrganizations.ts create mode 100644 src/dashboard/src/hooks/dashboard/useDashboardServerItems.ts create mode 100644 src/dashboard/src/hooks/dashboard/useDashboardTenants.ts create mode 100644 src/dashboard/src/hooks/useSecureRoute.ts diff --git a/src/dashboard/src/app/client/admin/organizations/[id]/page.tsx b/src/dashboard/src/app/client/admin/organizations/[id]/page.tsx deleted file mode 100644 index 0a95f4c8..00000000 --- a/src/dashboard/src/app/client/admin/organizations/[id]/page.tsx +++ /dev/null @@ -1,16 +0,0 @@ -'use client'; - -import { Button } from '@/components'; - -interface IPageProps { - params: { id: string; searchParams: any }; -} - -export default function Page({ params }: IPageProps) { - return ( -
- {params.id}:{params.searchParams} - -
- ); -} diff --git a/src/dashboard/src/app/client/admin/organizations/page.tsx b/src/dashboard/src/app/client/admin/organizations/page.tsx deleted file mode 100644 index 8d1e0561..00000000 --- a/src/dashboard/src/app/client/admin/organizations/page.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function Page() { - return
Client Organization Admin
; -} diff --git a/src/dashboard/src/app/client/admin/page.tsx b/src/dashboard/src/app/client/admin/page.tsx index c3af1a88..40b5f874 100644 --- a/src/dashboard/src/app/client/admin/page.tsx +++ b/src/dashboard/src/app/client/admin/page.tsx @@ -1,3 +1,9 @@ +'use client'; + +import { useSecureRoute } from '@/hooks'; + export default function Page() { + useSecureRoute((state) => state.isOrganizationAdmin, '/'); + return
Client Admin
; } diff --git a/src/dashboard/src/app/client/admin/users/page.tsx b/src/dashboard/src/app/client/admin/users/page.tsx index 28abd3f1..5b5f2801 100644 --- a/src/dashboard/src/app/client/admin/users/page.tsx +++ b/src/dashboard/src/app/client/admin/users/page.tsx @@ -1,3 +1,9 @@ +'use client'; + +import { useSecureRoute } from '@/hooks'; + export default function Page() { + useSecureRoute((state) => state.isOrganizationAdmin, '/'); + return
Client User Admin
; } diff --git a/src/dashboard/src/app/client/dashboard/[id]/page.tsx b/src/dashboard/src/app/client/dashboard/[id]/page.tsx deleted file mode 100644 index 0a95f4c8..00000000 --- a/src/dashboard/src/app/client/dashboard/[id]/page.tsx +++ /dev/null @@ -1,16 +0,0 @@ -'use client'; - -import { Button } from '@/components'; - -interface IPageProps { - params: { id: string; searchParams: any }; -} - -export default function Page({ params }: IPageProps) { - return ( -
- {params.id}:{params.searchParams} - -
- ); -} diff --git a/src/dashboard/src/app/client/dashboard/page.tsx b/src/dashboard/src/app/client/dashboard/page.tsx index c28c7201..0cb098e9 100644 --- a/src/dashboard/src/app/client/dashboard/page.tsx +++ b/src/dashboard/src/app/client/dashboard/page.tsx @@ -1,24 +1,10 @@ -import { - AllOrganizations, - AllocationByOS, - AllocationByStorageVolume, - AllocationTable, - SegmentedBarChart, - StorageTrendsChart, - TotalStorage, -} from '@/components/charts'; -import { OperatingSystems } from '@/components/charts/allocationTable/constants'; +'use client'; + +import { Dashboard } from '@/components'; +import { useSecureRoute } from '@/hooks'; export default function Page() { - return ( - <> - - - - - - - - - ); + useSecureRoute((state) => state.isClient, '/'); + + return ; } diff --git a/src/dashboard/src/app/client/servers/page.tsx b/src/dashboard/src/app/client/servers/page.tsx new file mode 100644 index 00000000..7cf50222 --- /dev/null +++ b/src/dashboard/src/app/client/servers/page.tsx @@ -0,0 +1,19 @@ +'use client'; + +import { AllocationByStorageVolume, Col } from '@/components'; +import { useSecureRoute } from '@/hooks'; +import { useFilteredOrganizations, useFilteredServerItems } from '@/hooks/filter'; + +export default function Page() { + useSecureRoute((state) => state.isClient, '/'); + + const { organizations } = useFilteredOrganizations(); + const { serverItems } = useFilteredServerItems(); + + return ( + +

All Servers + + + ); +} diff --git a/src/dashboard/src/app/hsb/admin/groups/[id]/page.tsx b/src/dashboard/src/app/hsb/admin/groups/[id]/page.tsx deleted file mode 100644 index 0a95f4c8..00000000 --- a/src/dashboard/src/app/hsb/admin/groups/[id]/page.tsx +++ /dev/null @@ -1,16 +0,0 @@ -'use client'; - -import { Button } from '@/components'; - -interface IPageProps { - params: { id: string; searchParams: any }; -} - -export default function Page({ params }: IPageProps) { - return ( -
- {params.id}:{params.searchParams} - -
- ); -} diff --git a/src/dashboard/src/app/hsb/admin/groups/page.tsx b/src/dashboard/src/app/hsb/admin/groups/page.tsx deleted file mode 100644 index 71cd18de..00000000 --- a/src/dashboard/src/app/hsb/admin/groups/page.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function Page() { - return
HSB Group Admin
; -} diff --git a/src/dashboard/src/app/hsb/admin/organizations/page.tsx b/src/dashboard/src/app/hsb/admin/organizations/page.tsx index 0690184c..5012603a 100644 --- a/src/dashboard/src/app/hsb/admin/organizations/page.tsx +++ b/src/dashboard/src/app/hsb/admin/organizations/page.tsx @@ -1,3 +1,9 @@ +'use client'; + +import { useSecureRoute } from '@/hooks'; + export default function Page() { + useSecureRoute((state) => state.isSystemAdmin, '/'); + return
HSB Organization Admin
; } diff --git a/src/dashboard/src/app/hsb/admin/users/page.tsx b/src/dashboard/src/app/hsb/admin/users/page.tsx index a513a500..30d16477 100644 --- a/src/dashboard/src/app/hsb/admin/users/page.tsx +++ b/src/dashboard/src/app/hsb/admin/users/page.tsx @@ -2,25 +2,15 @@ import styles from './Users.module.scss'; -import { - Button, - Checkbox, - Info, - Overlay, - Row, - Select, - Sheet, - Spinner, - Table, - Text, -} from '@/components'; -import { IUserModel } from '@/hooks'; +import { Button, Checkbox, Info, Overlay, Select, Sheet, Spinner, Table, Text } from '@/components'; +import { IUserModel, useSecureRoute } from '@/hooks'; import { useApiUsers } from '@/hooks/api/admin'; import { useGroups, useUsers } from '@/hooks/data'; import React from 'react'; import { IUserForm } from './IUserForm'; export default function Page() { + useSecureRoute((state) => state.isSystemAdmin, '/'); const { isReady: isReadyUsers, users } = useUsers({ includeGroups: true }); const { isReady: isReadyGroups, groups, options: groupOptions } = useGroups(); const { update: updateUser } = useApiUsers(); @@ -81,104 +71,104 @@ export default function Page() { }, [updateUser, records]); return ( - +
- {loading && ( - - - - )} -
- - Find a user by name, username, or email. Click checkbox to enable user access to dashboard - for selected group(s). - -
- setFilter(e.target.value)} - onKeyDown={(e) => { - if (e.code === 'Enter') handleSearch(); + {loading && ( + + + + )} +
+ + Find a user by name, username, or email. Click checkbox to enable user access to + dashboard for selected group(s). + +
+ setFilter(e.target.value)} + onKeyDown={(e) => { + if (e.code === 'Enter') handleSearch(); + }} + /> + +
+
+
+ +
Username
+
Email
+
Name
+
Enabled
+
Groups
+ + } + > + {({ data }) => { + return ( + <> +
{data.username}
+
{data.email}
+
{data.displayName}
+
+ { + setRecords((records) => + records.map((r) => + r.id === data.id + ? { ...data, isEnabled: e.target.checked, isDirty: true } + : r, + ), + ); + }} + /> +
+
+
+
+
+
-
- -
Username
-
Email
-
Name
-
Enabled
-
Groups
- - } - > - {({ data }) => { - return ( - <> -
{data.username}
-
{data.email}
-
{data.displayName}
-
- { - setRecords((records) => - records.map((r) => - r.id === data.id - ? { ...data, isEnabled: e.target.checked, isDirty: true } - : r, - ), - ); - }} - /> -
-
-
-
-
- -
-
); } diff --git a/src/dashboard/src/app/hsb/dashboard/Page.module.scss b/src/dashboard/src/app/hsb/dashboard/Page.module.scss deleted file mode 100644 index dd5bf4ef..00000000 --- a/src/dashboard/src/app/hsb/dashboard/Page.module.scss +++ /dev/null @@ -1,11 +0,0 @@ -@import '@/styles/utils.scss'; - -.page { - >div:first-child { - gap: 2rem; - - >div { - flex: 1; - } - } -} \ No newline at end of file diff --git a/src/dashboard/src/app/hsb/dashboard/[id]/page.tsx b/src/dashboard/src/app/hsb/dashboard/[id]/page.tsx deleted file mode 100644 index 0a95f4c8..00000000 --- a/src/dashboard/src/app/hsb/dashboard/[id]/page.tsx +++ /dev/null @@ -1,16 +0,0 @@ -'use client'; - -import { Button } from '@/components'; - -interface IPageProps { - params: { id: string; searchParams: any }; -} - -export default function Page({ params }: IPageProps) { - return ( -
- {params.id}:{params.searchParams} - -
- ); -} diff --git a/src/dashboard/src/app/hsb/dashboard/page.tsx b/src/dashboard/src/app/hsb/dashboard/page.tsx index fd769408..52f6cf32 100644 --- a/src/dashboard/src/app/hsb/dashboard/page.tsx +++ b/src/dashboard/src/app/hsb/dashboard/page.tsx @@ -1,24 +1,10 @@ -import { - AllOrganizations, - AllocationByOS, - AllocationByStorageVolume, - AllocationTable, - StorageTrendsChart, - TotalStorage, - SegmentedBarChart -} from '@/components/charts'; -import { OperatingSystems } from '@/components/charts/allocationTable/constants'; +'use client'; + +import { Dashboard } from '@/components'; +import { useSecureRoute } from '@/hooks'; export default function Page() { - return ( - <> - - - - - - - - - ); + useSecureRoute((state) => state.isHSB, '/'); + + return ; } diff --git a/src/dashboard/src/app/hsb/servers/page.tsx b/src/dashboard/src/app/hsb/servers/page.tsx new file mode 100644 index 00000000..a3e90a91 --- /dev/null +++ b/src/dashboard/src/app/hsb/servers/page.tsx @@ -0,0 +1,19 @@ +'use client'; + +import { AllocationTable, Col } from '@/components'; +import { useSecureRoute } from '@/hooks'; +import { useFilteredOrganizations, useFilteredServerItems } from '@/hooks/filter'; + +export default function Page() { + useSecureRoute((state) => state.isHSB, '/'); + + const { organizations } = useFilteredOrganizations(); + const { serverItems } = useFilteredServerItems(); + + return ( + +

All Servers

+ + + ); +} diff --git a/src/dashboard/src/components/charts/allocationTable/AllocationTable.tsx b/src/dashboard/src/components/charts/allocationTable/AllocationTable.tsx index c768ecb2..ca570e57 100644 --- a/src/dashboard/src/components/charts/allocationTable/AllocationTable.tsx +++ b/src/dashboard/src/components/charts/allocationTable/AllocationTable.tsx @@ -2,7 +2,7 @@ import { Button } from '@/components/buttons'; import { Text } from '@/components/forms/text'; -import { useServerItems } from '@/hooks/data'; +import { IServerItemModel } from '@/hooks'; import classNames from 'classnames'; import { debounce } from 'lodash'; import React from 'react'; @@ -10,17 +10,21 @@ import styles from './AllocationTable.module.scss'; import { Dropdown } from './Dropdown'; import { ITableRowData } from './ITableRowData'; import { TableRow } from './TableRow'; -import { OperatingSystems } from './constants'; import { useAllocationByOS } from './hooks'; import { getColumns, getLabel } from './utils'; export interface IAllocationTableProps { /** Filter servers by their OS */ - operatingSystem: OperatingSystems; + operatingSystem?: string; + serverItems: IServerItemModel[]; + loading?: boolean; } -export const AllocationTable = ({ operatingSystem }: IAllocationTableProps) => { - const { serverItems } = useServerItems(); +export const AllocationTable = ({ + operatingSystem, + serverItems, + loading, +}: IAllocationTableProps) => { const getServerItems = useAllocationByOS(operatingSystem); const [keyword, setKeyword] = React.useState(''); @@ -34,7 +38,9 @@ export const AllocationTable = ({ operatingSystem }: IAllocationTableProps) => { serverItems, (si) => !filter || - si.name.toLocaleLowerCase().includes(filter.toLocaleLowerCase()) || + (si.name.length + ? si.name.toLocaleLowerCase().includes(filter.toLocaleLowerCase()) + : '[NO NAME]'.toLocaleLowerCase().includes(filter.toLocaleLowerCase())) || (!!si.operatingSystemItem && si.operatingSystemItem.name.toLocaleLowerCase().includes(filter.toLocaleLowerCase())), sorting[0] as keyof ITableRowData, @@ -53,7 +59,11 @@ export const AllocationTable = ({ operatingSystem }: IAllocationTableProps) => { return (
-

Allocation by Storage Volume - All {getLabel(operatingSystem)}

+

+ {operatingSystem + ? `Allocation by Storage Volume - All ${getLabel(operatingSystem)}` + : 'All Servers'} +

{ +export const useAllocationByOS = (operatingSystem?: string) => { const { operatingSystemItems } = useOperatingSystemItems(); const { tenants } = useTenants(); @@ -28,11 +27,11 @@ export const useAllocationByOS = (operatingSystem: OperatingSystems) => { })) .filter((si) => { const className = si.operatingSystemItem?.rawData.u_class; - return className === operatingSystem && filter(si); + return (!operatingSystem || className === operatingSystem) && filter(si); }) .map((si) => { return { - server: si.name, + server: si.name.length ? si.name : '[NO NAME]', os: si.operatingSystemItem?.name ?? '', tenant: si.tenant?.name ?? '', capacity: si.capacity ?? 0, @@ -52,7 +51,7 @@ export const useAllocationByOS = (operatingSystem: OperatingSystems) => { } else if (sort === 'available') { return (a.available < b.available ? -1 : a.available > b.available ? 1 : 0) * order; } - return a.server < b.server ? -1 : a.server > b.server ? 1 : 0; + return a.server < b.server ? -1 : a.server > b.server ? 1 : 0 * order; }); return data; diff --git a/src/dashboard/src/components/charts/allocationTable/utils/getLabel.ts b/src/dashboard/src/components/charts/allocationTable/utils/getLabel.ts index 4647df74..96f1141a 100644 --- a/src/dashboard/src/components/charts/allocationTable/utils/getLabel.ts +++ b/src/dashboard/src/components/charts/allocationTable/utils/getLabel.ts @@ -1,25 +1,25 @@ -import { OperatingSystems } from '../constants'; - -export const getLabel = (operatingSystem: OperatingSystems) => { - switch (operatingSystem) { - case OperatingSystems.AIX: +export const getLabel = (className: string) => { + switch (className) { + case 'cmdb_ci_aix_server': return 'AIX Servers'; - case OperatingSystems.ESX: + case 'cmdb_ci_esx_server': return 'ESX Servers'; - case OperatingSystems.Server: + case 'cmdb_ci_server': return 'Servers'; - case OperatingSystems.Windows: + case 'cmdb_ci_win_server': return 'Windows Servers'; - case OperatingSystems.Linux: + case 'cmdb_ci_linux_server': return 'Linux Servers'; - case OperatingSystems.Unix: + case 'cmdb_ci_unix_server': return 'Unix Servers'; - case OperatingSystems.Solaris: + case 'cmdb_ci_solaris_server': return 'Solaris Servers'; - case OperatingSystems.PCHardware: + case 'cmdb_ci_pc_hardware': return 'PC Hardware'; - case OperatingSystems.VMS: + case 'u_openvms': return 'VMS'; + case 'u_cmdb_ci_appliance': + return 'Appliance'; default: return 'Unknown'; } diff --git a/src/dashboard/src/components/charts/bar/allocationByOS/AllocationByOS.tsx b/src/dashboard/src/components/charts/bar/allocationByOS/AllocationByOS.tsx index 2ca2e7a2..40d071a8 100644 --- a/src/dashboard/src/components/charts/bar/allocationByOS/AllocationByOS.tsx +++ b/src/dashboard/src/components/charts/bar/allocationByOS/AllocationByOS.tsx @@ -1,15 +1,22 @@ 'use client'; -import { useOperatingSystemItems, useServerItems } from '@/hooks/data'; +import { IOperatingSystemItemModel, IServerItemModel } from '@/hooks'; import Link from 'next/link'; import { BarRow, SmallBarChart } from '../smallBar'; import defaultData from './defaultData'; import { groupByOS } from './utils'; -export const AllocationByOS: React.FC = () => { - const { serverItems } = useServerItems(); - const { operatingSystemItems } = useOperatingSystemItems(); +export interface IAllocationByOSProps { + serverItems: IServerItemModel[]; + operatingSystemItems: IOperatingSystemItemModel[]; + loading?: boolean; +} +export const AllocationByOS = ({ + serverItems, + operatingSystemItems, + loading, +}: IAllocationByOSProps) => { return ( { - const { isReady: organizationsReady, organizations } = useOrganizations(); - const { isReady: serverItemsReady, serverItems } = useServerItems(); +export interface IAllocationByStorageVolumeProps { + organizations: IOrganizationModel[]; + serverItems: IServerItemModel[]; + loading?: boolean; +} +export const AllocationByStorageVolume = ({ + organizations, + serverItems, + loading, +}: IAllocationByStorageVolumeProps) => { const [sortOption, setSortOption] = React.useState(0); const [search, setSearch] = React.useState(''); const [items, setItems] = React.useState( @@ -26,10 +30,8 @@ export const AllocationByStorageVolume: React.FC = () => { ); React.useEffect(() => { - if (organizationsReady && serverItemsReady) { - setItems(calcOrganizationStorage(organizations, serverItems, sortOption)); - } - }, [organizations, organizationsReady, serverItems, serverItemsReady, sortOption]); + setItems(calcOrganizationStorage(organizations, serverItems, sortOption)); + }, [organizations, serverItems, sortOption]); const filterOrganizations = React.useCallback( ( diff --git a/src/dashboard/src/components/charts/bar/allocationByVolume/AllocationByVolume.tsx b/src/dashboard/src/components/charts/bar/allocationByVolume/AllocationByVolume.tsx new file mode 100644 index 00000000..5b545abc --- /dev/null +++ b/src/dashboard/src/components/charts/bar/allocationByVolume/AllocationByVolume.tsx @@ -0,0 +1,42 @@ +'use client'; + +import { IFileSystemItemModel } from '@/hooks'; +import Link from 'next/link'; +import { BarRow, SmallBarChart } from '../smallBar'; +import { IBarChartRowData } from '../smallBar/IBarChartRowData'; +import defaultData from './defaultData'; + +export interface IAllocationByVolumeProps { + fileSystemItems: IFileSystemItemModel[]; + loading?: boolean; +} + +export const AllocationByVolume = ({ fileSystemItems, loading }: IAllocationByVolumeProps) => { + return ( + ((fsi) => ({ + key: fsi.name, + label: fsi.name, + capacity: fsi.capacity, + available: fsi.availableSpace, + })), + }} + exportDisabled={true} + onExport={() => {}} + > + {(data) => { + return data.datasets.map((os) => ( + {os.label}} + capacity={os.capacity} + available={os.available} + /> + )); + }} + + ); +}; diff --git a/src/dashboard/src/components/charts/bar/allocationByVolume/defaultData.ts b/src/dashboard/src/components/charts/bar/allocationByVolume/defaultData.ts new file mode 100644 index 00000000..35183336 --- /dev/null +++ b/src/dashboard/src/components/charts/bar/allocationByVolume/defaultData.ts @@ -0,0 +1,46 @@ +import { IBarChartData } from '../smallBar/IBarChartData'; +import { IBarChartRowData } from '../smallBar/IBarChartRowData'; + +const defaultData: IBarChartData = { + labels: ['Drive', 'Allocated', 'Used', 'Unused', 'Percentage Used'], + datasets: [ + { + key: 'Drive 1', + label: 'Drive 1', + capacity: 12, + available: 4.8, + }, + { + key: 'Drive 2', + label: 'Drive 2', + capacity: 12, + available: 1.6, + }, + { + key: 'Drive 3', + label: 'Drive 3', + capacity: 12, + available: 4, + }, + { + key: 'Drive 4', + label: 'Drive 4', + capacity: 12, + available: 1, + }, + { + key: 'Drive 5', + label: 'Drive 5', + capacity: 12, + available: 3.2, + }, + { + key: 'Drive 6', + label: 'Drive 6', + capacity: 12, + available: 0.5, + }, + ], +}; + +export default defaultData; diff --git a/src/dashboard/src/components/charts/bar/allocationByVolume/index.ts b/src/dashboard/src/components/charts/bar/allocationByVolume/index.ts new file mode 100644 index 00000000..493edf61 --- /dev/null +++ b/src/dashboard/src/components/charts/bar/allocationByVolume/index.ts @@ -0,0 +1 @@ +export * from './AllocationByVolume'; diff --git a/src/dashboard/src/components/charts/bar/index.ts b/src/dashboard/src/components/charts/bar/index.ts index 5d30a2b9..c1877bce 100644 --- a/src/dashboard/src/components/charts/bar/index.ts +++ b/src/dashboard/src/components/charts/bar/index.ts @@ -1,4 +1,5 @@ export * from './allocationByOS'; export * from './allocationByStorageVolume'; +export * from './allocationByVolume'; export * from './segmentedBarChart'; export * from './smallBar'; diff --git a/src/dashboard/src/components/charts/bar/segmentedBarChart/SegmentedBarChart.tsx b/src/dashboard/src/components/charts/bar/segmentedBarChart/SegmentedBarChart.tsx index b68a6281..b9d5236a 100644 --- a/src/dashboard/src/components/charts/bar/segmentedBarChart/SegmentedBarChart.tsx +++ b/src/dashboard/src/components/charts/bar/segmentedBarChart/SegmentedBarChart.tsx @@ -4,8 +4,8 @@ import { Button } from '@/components/buttons'; import { Bar } from 'react-chartjs-2'; import styles from './SegmentedBarChart.module.scss'; +import { IServerItemModel } from '@/hooks'; import { useDashboardFileSystemHistoryItems } from '@/hooks/dashboard'; -import { useFiltered } from '@/store'; import { BarElement, CategoryScale, Chart as ChartJS, Legend, Title, Tooltip } from 'chart.js'; import React from 'react'; import { defaultOptions } from './defaultOptions'; @@ -15,11 +15,12 @@ import { extractVolumeName } from './utils'; ChartJS.register(CategoryScale, BarElement, Title, Tooltip, Legend); export interface ISegmentedBarChart { + serverItem: IServerItemModel; maxVolumes?: number; + loading?: boolean; } -export const SegmentedBarChart = ({ maxVolumes = 4 }: ISegmentedBarChart) => { - const serverItem = useFiltered((state) => state.serverItem); +export const SegmentedBarChart = ({ serverItem, maxVolumes = 4, loading }: ISegmentedBarChart) => { const { findFileSystemHistoryItems } = useDashboardFileSystemHistoryItems(); const data = useStorageTrends(1, maxVolumes); diff --git a/src/dashboard/src/components/charts/doughnut/allOrganizations/AllOrganizations.tsx b/src/dashboard/src/components/charts/doughnut/allOrganizations/AllOrganizations.tsx index caee7660..1bd52d0f 100644 --- a/src/dashboard/src/components/charts/doughnut/allOrganizations/AllOrganizations.tsx +++ b/src/dashboard/src/components/charts/doughnut/allOrganizations/AllOrganizations.tsx @@ -3,17 +3,25 @@ import styles from './AllOrganizations.module.scss'; import { Button } from '@/components/buttons'; -import { useOrganizations } from '@/hooks/data'; +import { IOrganizationModel, IServerItemModel } from '@/hooks'; import { ArcElement, Chart as ChartJS, Tooltip } from 'chart.js'; -import React from 'react'; import { Doughnut } from 'react-chartjs-2'; import { useAllOrganizationsDoughnutChart } from './hooks'; ChartJS.register(ArcElement, Tooltip); -export const AllOrganizations: React.FC = () => { - const { organizations } = useOrganizations(); - const data = useAllOrganizationsDoughnutChart(); +export interface IAllOrganizationsProps { + organizations: IOrganizationModel[]; + serverItems: IServerItemModel[]; + loading?: boolean; +} + +export const AllOrganizations = ({ + organizations, + serverItems, + loading, +}: IAllOrganizationsProps) => { + const data = useAllOrganizationsDoughnutChart(serverItems); return (
diff --git a/src/dashboard/src/components/charts/doughnut/allOrganizations/hooks/useAllOrganizationsDoughnutChart.ts b/src/dashboard/src/components/charts/doughnut/allOrganizations/hooks/useAllOrganizationsDoughnutChart.ts index 4e9b6695..de2a86c0 100644 --- a/src/dashboard/src/components/charts/doughnut/allOrganizations/hooks/useAllOrganizationsDoughnutChart.ts +++ b/src/dashboard/src/components/charts/doughnut/allOrganizations/hooks/useAllOrganizationsDoughnutChart.ts @@ -1,12 +1,10 @@ -import { useServerItems } from '@/hooks/data'; +import { IServerItemModel } from '@/hooks'; import React from 'react'; import { IDoughnutStats } from '../..'; import { defaultData } from '../defaultData'; import { generateDoughnutChart } from './generateDoughnutChart'; -export const useAllOrganizationsDoughnutChart = () => { - const { serverItems } = useServerItems(); - +export const useAllOrganizationsDoughnutChart = (serverItems: IServerItemModel[]) => { const [data, setData] = React.useState(defaultData); React.useEffect(() => { diff --git a/src/dashboard/src/components/charts/doughnut/totalStorage/TotalStorage.tsx b/src/dashboard/src/components/charts/doughnut/totalStorage/TotalStorage.tsx index a44e83dc..fd918861 100644 --- a/src/dashboard/src/components/charts/doughnut/totalStorage/TotalStorage.tsx +++ b/src/dashboard/src/components/charts/doughnut/totalStorage/TotalStorage.tsx @@ -1,16 +1,21 @@ 'use client'; +import { IServerItemModel } from '@/hooks'; import { convertToStorageSize } from '@/utils'; import { ArcElement, Chart as ChartJS, Tooltip } from 'chart.js'; -import React from 'react'; import { Doughnut } from 'react-chartjs-2'; import styles from './TotalStorage.module.scss'; import { useTotalStorageDoughnutChart } from './hooks'; ChartJS.register(ArcElement, Tooltip); -export const TotalStorage: React.FC = () => { - const data = useTotalStorageDoughnutChart(); +export interface ITotalStorageProps { + serverItems: IServerItemModel[]; + loading?: boolean; +} + +export const TotalStorage = ({ serverItems, loading }: ITotalStorageProps) => { + const data = useTotalStorageDoughnutChart(serverItems); return (
diff --git a/src/dashboard/src/components/charts/doughnut/totalStorage/hooks/useTotalStorageDoughnutChart.ts b/src/dashboard/src/components/charts/doughnut/totalStorage/hooks/useTotalStorageDoughnutChart.ts index e38af863..b6082a7c 100644 --- a/src/dashboard/src/components/charts/doughnut/totalStorage/hooks/useTotalStorageDoughnutChart.ts +++ b/src/dashboard/src/components/charts/doughnut/totalStorage/hooks/useTotalStorageDoughnutChart.ts @@ -1,12 +1,10 @@ -import { useDashboard } from '@/store'; +import { IServerItemModel } from '@/hooks'; import React from 'react'; import { IDoughnutStats } from '../..'; import { defaultData } from '../defaultData'; import { generateDoughnutChart } from './generateDoughnutChart'; -export const useTotalStorageDoughnutChart = () => { - const serverItems = useDashboard((state) => state.serverItems); - +export const useTotalStorageDoughnutChart = (serverItems: IServerItemModel[]) => { const [data, setData] = React.useState(defaultData); React.useEffect(() => { diff --git a/src/dashboard/src/components/dashboard/Dashboard.tsx b/src/dashboard/src/components/dashboard/Dashboard.tsx new file mode 100644 index 00000000..5b0c3817 --- /dev/null +++ b/src/dashboard/src/components/dashboard/Dashboard.tsx @@ -0,0 +1,112 @@ +'use client'; + +import { + AllOrganizations, + AllocationByOS, + AllocationByStorageVolume, + AllocationByVolume, + AllocationTable, + SegmentedBarChart, + StorageTrendsChart, + TotalStorage, +} from '@/components/charts'; +import { + useDashboardOperatingSystemItems, + useDashboardOrganizations, + useDashboardServerItems, +} from '@/hooks/dashboard'; +import { useOperatingSystemItems, useOrganizations, useServerItems } from '@/hooks/data'; +import { useFilteredFileSystemItems } from '@/hooks/filter'; + +/** + * Dashboard component displays different charts depending on what data has been stored in the dashboard state. + * The Filter component updates the dashboard state when the Update button is clicked. + * @returns Component + */ +export const Dashboard = () => { + const { isReady: isReadyOrganizations, organizations } = useOrganizations(); + const { isReady: isReadyOperatingSystemItems, operatingSystemItems } = useOperatingSystemItems(); + const { isReady: isReadyServerItems, serverItems } = useServerItems(); + const { organizations: dashboardOrganizations } = useDashboardOrganizations(); + const { operatingSystemItems: dashboardOperatingSystemItems } = + useDashboardOperatingSystemItems(); + const { serverItems: dashboardServerItems } = useDashboardServerItems(); + const { fileSystemItems } = useFilteredFileSystemItems(); + + const selectedOrganizations = dashboardOrganizations.length + ? dashboardOrganizations + : organizations; + const selectedOperatingSystemItems = dashboardOperatingSystemItems.length + ? dashboardOperatingSystemItems + : operatingSystemItems; + const selectedServerItems = dashboardServerItems.length ? dashboardServerItems : serverItems; + + const showTotalStorage = + selectedServerItems.length === 1 || + (selectedOrganizations.length === 1 && selectedOperatingSystemItems.length > 1); + const showAllocationByOS = + selectedOrganizations.length === 1 && selectedOperatingSystemItems.length > 1; + const showAllocationByVolume = selectedServerItems.length === 1; + const showAllOrganizations = + selectedOrganizations.length > 1 && + selectedOperatingSystemItems.length > 1 && + selectedServerItems.length > 1; + const showAllocationByStorageVolume = + selectedOrganizations.length > 1 && + selectedOperatingSystemItems.length > 1 && + selectedServerItems.length > 1; + const showAllocationTable = + selectedOperatingSystemItems.length === 1 && selectedServerItems.length > 1; + const showSegmentedBarChart = selectedServerItems.length === 1; + + return ( + <> + {/* Single Organization total storage */} + {showTotalStorage && } + {/* Multiple OS */} + {showAllocationByOS && ( + + )} + {/* One Server Selected */} + {showAllocationByVolume && ( + + )} + {/* Multiple Organizations */} + {showAllOrganizations && ( + + )} + + {showAllocationByStorageVolume && ( + + )} + {showAllocationTable && ( + + )} + {showSegmentedBarChart && ( + + )} + + ); +}; diff --git a/src/dashboard/src/components/dashboard/index.ts b/src/dashboard/src/components/dashboard/index.ts new file mode 100644 index 00000000..19bd377d --- /dev/null +++ b/src/dashboard/src/components/dashboard/index.ts @@ -0,0 +1 @@ +export * from './Dashboard'; diff --git a/src/dashboard/src/components/filter/Filter.tsx b/src/dashboard/src/components/filter/Filter.tsx index 3b9f5e03..7ffb37c4 100644 --- a/src/dashboard/src/components/filter/Filter.tsx +++ b/src/dashboard/src/components/filter/Filter.tsx @@ -1,7 +1,7 @@ 'use client'; import { Button, DateRangePicker, Select } from '@/components'; -import { IOperatingSystemItemModel, IOrganizationModel, ITenantModel } from '@/hooks'; +import { IOperatingSystemItemModel, IOrganizationModel, ITenantModel, useAuth } from '@/hooks'; import { useDashboardServerHistoryItems } from '@/hooks/dashboard'; import { useOperatingSystemItems, @@ -10,6 +10,7 @@ import { useTenants, } from '@/hooks/data'; import { + useFilteredFileSystemItems, useFilteredOperatingSystemItems, useFilteredOrganizations, useFilteredServerItems, @@ -21,6 +22,7 @@ import React from 'react'; import styles from './Filter.module.scss'; export const Filter: React.FC = () => { + const { isHSB } = useAuth(); const { isReady: tenantsReady, tenants } = useTenants(); const { isReady: organizationsReady, organizations } = useOrganizations(); const { isReady: operatingSystemItemsReady, operatingSystemItems } = useOperatingSystemItems(); @@ -32,7 +34,7 @@ export const Filter: React.FC = () => { const filteredTenant = useFiltered((state) => state.tenant); const setFilteredTenant = useFiltered((state) => state.setTenant); const setFilteredTenants = useFiltered((state) => state.setTenants); - const { options: filteredTenantOptions } = useFilteredTenants(); + const { options: filteredTenantOptions, tenants: filteredTenants } = useFilteredTenants(); const filteredOrganization = useFiltered((state) => state.organization); const setFilteredOrganization = useFiltered((state) => state.setOrganization); @@ -46,8 +48,11 @@ export const Filter: React.FC = () => { const filteredOperatingSystemItem = useFiltered((state) => state.operatingSystemItem); const setFilteredOperatingSystemItem = useFiltered((state) => state.setOperatingSystemItem); const setFilteredOperatingSystemItems = useFiltered((state) => state.setOperatingSystemItems); - const { options: filteredOperatingSystemItemOptions, findOperatingSystemItems } = - useFilteredOperatingSystemItems(); + const { + options: filteredOperatingSystemItemOptions, + findOperatingSystemItems, + operatingSystemItems: filteredOperatingSystemItems, + } = useFilteredOperatingSystemItems(); const filteredServerItem = useFiltered((state) => state.serverItem); const filteredServerItems = useFiltered((state) => state.serverItems); @@ -56,18 +61,26 @@ export const Filter: React.FC = () => { const { options: filteredServerItemOptions, findServerItems } = useFilteredServerItems(); const setDashboardDateRange = useDashboard((state) => state.setDateRange); + const setDashboardTenants = useDashboard((state) => state.setTenants); const setDashboardOrganizations = useDashboard((state) => state.setOrganizations); + const setDashboardOperatingSystemItems = useDashboard((state) => state.setOperatingSystemItems); const setDashboardServerItems = useDashboard((state) => state.setServerItems); const { isReady: serverHistoryItemsReady, findServerHistoryItems } = useDashboardServerHistoryItems(); + const { findFileSystemItems } = useFilteredFileSystemItems(); + + const enableTenants = isHSB || tenants.length > 1; + const enableOrganizations = isHSB || organizations.length > 1; React.useEffect(() => { + if (tenants.length === 1) setFilteredTenant(tenants[0]); setFilteredTenants(tenants); - }, [setFilteredTenants, tenants]); + }, [setFilteredTenant, setFilteredTenants, tenants]); React.useEffect(() => { + if (organizations.length === 1) setFilteredOrganization(organizations[0]); setFilteredOrganizations(organizations); - }, [setFilteredOrganizations, organizations]); + }, [setFilteredOrganizations, organizations, setFilteredOrganization]); React.useEffect(() => { setFilteredOperatingSystemItems(operatingSystemItems); @@ -110,7 +123,7 @@ export const Filter: React.FC = () => { options={filteredTenantOptions} placeholder="Select tenant" value={filteredTenant?.id ?? ''} - disabled={!tenantsReady} + disabled={!tenantsReady || !enableTenants || !serverItemsReady} loading={!tenantsReady} onChange={async (value) => { const tenant = tenants.find((t) => t.id == value); @@ -148,7 +161,7 @@ export const Filter: React.FC = () => { options={filteredOrganizationOptions} placeholder="Select organization" value={filteredOrganization?.id ?? ''} - disabled={!organizationsReady} + disabled={!organizationsReady || !enableOrganizations || !serverItemsReady} loading={!organizationsReady} onChange={async (value) => { const organization = organizations.find((o) => o.id == value); @@ -181,7 +194,7 @@ export const Filter: React.FC = () => { options={filteredOperatingSystemItemOptions} placeholder="Select OS" value={filteredOperatingSystemItem?.id ?? ''} - disabled={!operatingSystemItemsReady} + disabled={!operatingSystemItemsReady || !serverItemsReady} loading={!operatingSystemItemsReady} onChange={async (value) => { const operatingSystemItem = operatingSystemItems.find((o) => o.id == value); @@ -237,14 +250,26 @@ export const Filter: React.FC = () => { } loading={!serverHistoryItemsReady} onClick={async () => { + if (filteredTenant) setDashboardTenants([filteredTenant]); + else setDashboardTenants(filteredTenants); + if (filteredOrganization) setDashboardOrganizations([filteredOrganization]); else setDashboardOrganizations(filteredOrganizations); + if (filteredOperatingSystemItem) + setDashboardOperatingSystemItems([filteredOperatingSystemItem]); + else setDashboardOperatingSystemItems(filteredOperatingSystemItems); + if (filteredServerItem) setDashboardServerItems([filteredServerItem]); else setDashboardServerItems(filteredServerItems); setDashboardDateRange(filteredDateRange); + if (filteredServerItem) + await findFileSystemItems({ + serverItemServiceNowKey: filteredServerItem.serviceNowKey, + }); + await findServerHistoryItems({ startDate: filteredDateRange[0] ? filteredDateRange[0] : undefined, endDate: filteredDateRange[1] ? filteredDateRange[1] : undefined, diff --git a/src/dashboard/src/components/header/Header.tsx b/src/dashboard/src/components/header/Header.tsx index 233ed1b1..f57a11fc 100644 --- a/src/dashboard/src/components/header/Header.tsx +++ b/src/dashboard/src/components/header/Header.tsx @@ -32,12 +32,16 @@ export const Header: React.FC = () => { const isLogin = path.includes('/login'); const rootPath = isHSB ? 'hsb' : 'client'; const infoIcon = false; - const isDashboardView = path.includes('/dashboard'); + const isDashboardView = path.includes('/dashboard'); const isAdminView = path.includes('/admin'); return ( <> -
+
{ )} {isDashboardView && ( - + See all servers )} diff --git a/src/dashboard/src/components/index.ts b/src/dashboard/src/components/index.ts index 4025d9b4..0985e943 100644 --- a/src/dashboard/src/components/index.ts +++ b/src/dashboard/src/components/index.ts @@ -1,6 +1,7 @@ export * from './auth'; export * from './buttons'; export * from './charts'; +export * from './dashboard'; export * from './flex'; export * from './footer'; export * from './forms'; diff --git a/src/dashboard/src/hooks/dashboard/index.ts b/src/dashboard/src/hooks/dashboard/index.ts index 9b66e8f7..2fc84d7e 100644 --- a/src/dashboard/src/hooks/dashboard/index.ts +++ b/src/dashboard/src/hooks/dashboard/index.ts @@ -1,2 +1,6 @@ export * from './useDashboardFileSystemHistoryItems'; +export * from './useDashboardOperatingSystemItems'; +export * from './useDashboardOrganizations'; export * from './useDashboardServerHistoryItems'; +export * from './useDashboardServerItems'; +export * from './useDashboardTenants'; diff --git a/src/dashboard/src/hooks/dashboard/useDashboardOperatingSystemItems.ts b/src/dashboard/src/hooks/dashboard/useDashboardOperatingSystemItems.ts new file mode 100644 index 00000000..28162c96 --- /dev/null +++ b/src/dashboard/src/hooks/dashboard/useDashboardOperatingSystemItems.ts @@ -0,0 +1,15 @@ +import { useDashboard } from '@/store'; +import React from 'react'; + +export const useDashboardOperatingSystemItems = () => { + const operatingSystemItems = useDashboard((state) => state.operatingSystemItems); + const setOperatingSystemItems = useDashboard((state) => state.setOperatingSystemItems); + + return React.useMemo( + () => ({ + operatingSystemItems, + setOperatingSystemItems, + }), + [operatingSystemItems, setOperatingSystemItems], + ); +}; diff --git a/src/dashboard/src/hooks/dashboard/useDashboardOrganizations.ts b/src/dashboard/src/hooks/dashboard/useDashboardOrganizations.ts new file mode 100644 index 00000000..a749251b --- /dev/null +++ b/src/dashboard/src/hooks/dashboard/useDashboardOrganizations.ts @@ -0,0 +1,15 @@ +import { useDashboard } from '@/store'; +import React from 'react'; + +export const useDashboardOrganizations = () => { + const organizations = useDashboard((state) => state.organizations); + const setOrganizations = useDashboard((state) => state.setOrganizations); + + return React.useMemo( + () => ({ + organizations, + setOrganizations, + }), + [organizations, setOrganizations], + ); +}; diff --git a/src/dashboard/src/hooks/dashboard/useDashboardServerItems.ts b/src/dashboard/src/hooks/dashboard/useDashboardServerItems.ts new file mode 100644 index 00000000..7a2f8c5b --- /dev/null +++ b/src/dashboard/src/hooks/dashboard/useDashboardServerItems.ts @@ -0,0 +1,15 @@ +import { useDashboard } from '@/store'; +import React from 'react'; + +export const useDashboardServerItems = () => { + const serverItems = useDashboard((state) => state.serverItems); + const setServerItems = useDashboard((state) => state.setServerItems); + + return React.useMemo( + () => ({ + serverItems, + setServerItems, + }), + [serverItems, setServerItems], + ); +}; diff --git a/src/dashboard/src/hooks/dashboard/useDashboardTenants.ts b/src/dashboard/src/hooks/dashboard/useDashboardTenants.ts new file mode 100644 index 00000000..8ce128d6 --- /dev/null +++ b/src/dashboard/src/hooks/dashboard/useDashboardTenants.ts @@ -0,0 +1,15 @@ +import { useDashboard } from '@/store'; +import React from 'react'; + +export const useDashboardTenants = () => { + const tenants = useDashboard((state) => state.tenants); + const setTenants = useDashboard((state) => state.setTenants); + + return React.useMemo( + () => ({ + tenants, + setTenants, + }), + [tenants, setTenants], + ); +}; diff --git a/src/dashboard/src/hooks/filter/useFilteredOperatingSystemItems.ts b/src/dashboard/src/hooks/filter/useFilteredOperatingSystemItems.ts index c84aa38a..84abf31d 100644 --- a/src/dashboard/src/hooks/filter/useFilteredOperatingSystemItems.ts +++ b/src/dashboard/src/hooks/filter/useFilteredOperatingSystemItems.ts @@ -9,6 +9,7 @@ import { export const useFilteredOperatingSystemItems = () => { const { find } = useApiOperatingSystemItems(); + const operatingSystemItem = useFiltered((state) => state.operatingSystemItem); const operatingSystemItems = useFiltered((state) => state.operatingSystemItems); const setOperatingSystemItems = useFiltered((state) => state.setOperatingSystemItems); @@ -39,7 +40,8 @@ export const useFilteredOperatingSystemItems = () => { findOperatingSystemItems: fetch, options, operatingSystemItems, + operatingSystemItem, }), - [operatingSystemItems, fetch, options], + [fetch, options, operatingSystemItems, operatingSystemItem], ); }; diff --git a/src/dashboard/src/hooks/index.ts b/src/dashboard/src/hooks/index.ts index 002fba72..807db401 100644 --- a/src/dashboard/src/hooks/index.ts +++ b/src/dashboard/src/hooks/index.ts @@ -1,3 +1,4 @@ export * from './api'; export * from './constants'; export * from './useAuth'; +export * from './useSecureRoute'; diff --git a/src/dashboard/src/hooks/useAuth.ts b/src/dashboard/src/hooks/useAuth.ts index 7a07b009..da84471a 100644 --- a/src/dashboard/src/hooks/useAuth.ts +++ b/src/dashboard/src/hooks/useAuth.ts @@ -1,7 +1,21 @@ +import { Session } from 'next-auth'; import { useSession } from 'next-auth/react'; import React from 'react'; import { RoleName } from './constants'; +export interface IAuthState { + status: string; + session: Session | null; + isLoading: boolean; + isAuthenticated: boolean; + isAuthorized: boolean; + isSystemAdmin: boolean; + isHSB: boolean; + isClient: boolean; + isOrganizationAdmin: boolean; + roles: string[]; +} + export const useAuth = () => { const { data: session, status } = useSession(); @@ -9,7 +23,8 @@ export const useAuth = () => { // This provides a way to manually override authentication/authorization. const roles = process.env.NEXT_PUBLIC_AUTH_ROLES?.split(',') ?? session?.user.roles ?? []; const oStatus = process.env.NEXT_PUBLIC_AUTH_STATUS ?? status; - return { + + const state: IAuthState = { status: oStatus, session, isLoading: oStatus === 'loading', @@ -21,5 +36,7 @@ export const useAuth = () => { isOrganizationAdmin: roles.includes(RoleName.OrganizationAdmin), roles, }; + + return state; }, [session, status]); }; diff --git a/src/dashboard/src/hooks/useSecureRoute.ts b/src/dashboard/src/hooks/useSecureRoute.ts new file mode 100644 index 00000000..e3be0a78 --- /dev/null +++ b/src/dashboard/src/hooks/useSecureRoute.ts @@ -0,0 +1,16 @@ +import { redirect } from 'next/navigation'; +import { useLayoutEffect } from 'react'; +import { IAuthState, useAuth } from '.'; + +/** + * Hook to redirect a user to another route if they don't pass the `predicate`. + * @param predicate Function to validate the current user can access the current route. + * @param redirectTo If user is not allowed on current route, then `redirectTo`. + */ +export const useSecureRoute = (predicate: (state: IAuthState) => boolean, redirectTo: string) => { + const state = useAuth(); + + useLayoutEffect(() => { + if (!predicate(state)) redirect(redirectTo); + }, [predicate, redirectTo, state]); +}; diff --git a/src/dashboard/src/store/useDashboard.ts b/src/dashboard/src/store/useDashboard.ts index daa91895..15624ad4 100644 --- a/src/dashboard/src/store/useDashboard.ts +++ b/src/dashboard/src/store/useDashboard.ts @@ -1,9 +1,11 @@ import { IFileSystemHistoryItemModel, IFileSystemItemModel, + IOperatingSystemItemModel, IOrganizationModel, IServerHistoryItemModel, IServerItemModel, + ITenantModel, } from '@/hooks/api'; import { create } from 'zustand'; @@ -12,10 +14,18 @@ export interface IDashboardState { dateRange: string[]; setDateRange: (value?: string[]) => void; + // Tenants + tenants: ITenantModel[]; + setTenants: (values: ITenantModel[]) => void; + // Organizations organizations: IOrganizationModel[]; setOrganizations: (values: IOrganizationModel[]) => void; + // Operating System Items + operatingSystemItems: IOperatingSystemItemModel[]; + setOperatingSystemItems: (values: IOperatingSystemItemModel[]) => void; + // Server Items serverItemsReady?: boolean; setServerItemsReady: (value?: boolean) => void; @@ -44,10 +54,18 @@ export const useDashboard = create((set) => ({ dateRange: [], setDateRange: (values) => set((state) => ({ dateRange: values })), + // Tenants + tenants: [], + setTenants: (values) => set((state) => ({ tenants: values })), + // Organizations organizations: [], setOrganizations: (values) => set((state) => ({ organizations: values })), + // Operating System Items + operatingSystemItems: [], + setOperatingSystemItems: (values) => set((state) => ({ operatingSystemItems: values })), + // Server Items serverItemsReady: true, setServerItemsReady: (value) => set((state) => ({ serverItemsReady: value })),