Skip to content

Commit

Permalink
use globally stacked modals when navigating between different resources
Browse files Browse the repository at this point in the history
  • Loading branch information
manV committed Oct 9, 2024
1 parent 39a02da commit 2c84037
Show file tree
Hide file tree
Showing 20 changed files with 517 additions and 574 deletions.
45 changes: 30 additions & 15 deletions deepfence_frontend/apps/dashboard/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,51 @@
import './index.css';

import { QueryClientProvider } from '@tanstack/react-query';
import { useState } from 'react';
import { RouterProvider } from 'react-router-dom';
import { Toaster } from 'sonner';

import {
GlobalModalStackContext,
GlobalModalStackItem,
} from '@/components/detail-modal-stack';
import { queryClient } from '@/queries/client';
import { router } from '@/routes';
import { ThemeProvider, useThemeMode } from '@/theme/ThemeContext';
import { useDocumentTitle } from '@/utils/useDocumentTitle';

function App() {
const { setMode, userSelectedMode, mode } = useThemeMode();
const [globalModalStackItems, setGlobalModalStackItems] = useState<
GlobalModalStackItem[]
>([]);
useDocumentTitle();
return (
<QueryClientProvider client={queryClient}>
<ThemeProvider value={{ setMode, mode, userSelectedMode }}>
<Toaster
theme={mode === 'dark' ? 'dark' : 'light'}
toastOptions={{
unstyled: true,
classNames: {
toast:
'rounded-sm flex items-center gap-2 w-full bg-bg-top-header text-white text-p1 px-4 py-2 shadow-md ',
success: '!bg-status-success',
error: '!bg-status-error',
info: '!bg-status-info',
warning: '!bg-status-warning',
default: '!bg-bg-tooltip',
},
<GlobalModalStackContext.Provider
value={{
items: globalModalStackItems,
setItems: setGlobalModalStackItems,
}}
/>
<RouterProvider router={router} />
>
<Toaster
theme={mode === 'dark' ? 'dark' : 'light'}
toastOptions={{
unstyled: true,
classNames: {
toast:
'rounded-sm flex items-center gap-2 w-full bg-bg-top-header text-white text-p1 px-4 py-2 shadow-md ',
success: '!bg-status-success',
error: '!bg-status-error',
info: '!bg-status-info',
warning: '!bg-status-warning',
default: '!bg-bg-tooltip',
},
}}
/>
<RouterProvider router={router} />
</GlobalModalStackContext.Provider>
</ThemeProvider>
</QueryClientProvider>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import React, { createContext, useContext } from 'react';
import { SlidingModal, SlidingModalCloseButton } from 'ui-components';

import { CloudService } from '@/features/topology/data-components/node-details/CloudService';
import { Container } from '@/features/topology/data-components/node-details/Container';
import { ContainerImage } from '@/features/topology/data-components/node-details/ContainerImage';
import { Host } from '@/features/topology/data-components/node-details/Host';
import { Pod } from '@/features/topology/data-components/node-details/Pod';
import { Process } from '@/features/topology/data-components/node-details/Process';

interface HostNodeDetailModalStackItem {
kind: 'host';
nodeId: string;
tab?: string;
}

interface ContainerDetailModalStackItem {
kind: 'container';
nodeId: string;
tab?: string;
}

interface ContainerImageDetailModalStackItem {
kind: 'container_image';
nodeId: string;
tab?: string;
}

interface ProcessDetailModalStackItem {
kind: 'process';
nodeId: string;
tab?: string;
}

interface PodDetailModalStackItem {
kind: 'pod';
nodeId: string;
tab?: string;
}

interface CloudServiceDetailModalStackItem {
kind: 'cloud_service';
nodeType: string;
region: string;
}

export type GlobalModalStackItem =
| HostNodeDetailModalStackItem
| ContainerDetailModalStackItem
| ContainerImageDetailModalStackItem
| ProcessDetailModalStackItem
| PodDetailModalStackItem
| CloudServiceDetailModalStackItem;

export const GlobalModalStackContext = createContext<{
items: GlobalModalStackItem[];
setItems: React.Dispatch<React.SetStateAction<GlobalModalStackItem[]>>;
}>({
items: [],
setItems: () => {
/**noop */
},
});

export function useGlobalModalStack() {
const { items, setItems } = useContext(GlobalModalStackContext);
const addGlobalModal = (item: GlobalModalStackItem) => {
setItems((prev) => {
return [...prev, item];
});
};
const removeCurrentModal = () => {
setItems((prev) => {
return prev.slice(0, prev.length - 1);
});
};

return { addGlobalModal, removeCurrentModal, globalModalStack: items };
}

export function GlobalModals() {
const { globalModalStack, removeCurrentModal } = useGlobalModalStack();

return globalModalStack.map((modalInfo, index) => {
const kind = modalInfo.kind;
switch (kind) {
case 'host': {
return (
<SlidingModal
size="xxl"
open={true}
onOpenChange={() => removeCurrentModal()}
key={modalInfo.kind + modalInfo.nodeId + index}
>
<SlidingModalCloseButton />
<Host nodeId={modalInfo.nodeId} defaultTab={modalInfo.tab} />
</SlidingModal>
);
}
case 'container': {
return (
<SlidingModal
size="xxl"
open={true}
onOpenChange={() => removeCurrentModal()}
key={modalInfo.kind + modalInfo.nodeId + index}
>
<SlidingModalCloseButton />
<Container nodeId={modalInfo.nodeId} defaultTab={modalInfo.tab} />
</SlidingModal>
);
}
case 'container_image': {
return (
<SlidingModal size="xxl" open={true} onOpenChange={() => removeCurrentModal()}>
<SlidingModalCloseButton />
<ContainerImage
nodeId={modalInfo.nodeId}
defaultTab={modalInfo.tab}
key={modalInfo.kind + modalInfo.nodeId + index}
/>
</SlidingModal>
);
}
case 'process': {
return (
<SlidingModal
size="xxl"
open={true}
onOpenChange={() => removeCurrentModal()}
key={modalInfo.kind + modalInfo.nodeId + index}
>
<SlidingModalCloseButton />
<Process nodeId={modalInfo.nodeId} defaultTab={modalInfo.tab} />
</SlidingModal>
);
}
case 'pod': {
return (
<SlidingModal
size="xxl"
open={true}
onOpenChange={() => removeCurrentModal()}
key={modalInfo.kind + modalInfo.nodeId + index}
>
<SlidingModalCloseButton />
<Pod nodeId={modalInfo.nodeId} defaultTab={modalInfo.tab} />
</SlidingModal>
);
}
case 'cloud_service': {
return (
<SlidingModal
size="xxl"
open={true}
onOpenChange={() => removeCurrentModal()}
key={modalInfo.kind + modalInfo.nodeType + modalInfo.region + index}
>
<SlidingModalCloseButton />
<CloudService nodeType={modalInfo.nodeType} region={modalInfo.region} />
</SlidingModal>
);
}
}

const exhaustiveCheck: never = kind;
throw new Error(`Unhandled node type: ${exhaustiveCheck}`);
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Outlet } from 'react-router-dom';
import { cn } from 'tailwind-preset';

import { AppHeader } from '@/components/AppHeader';
import { GlobalModals } from '@/components/detail-modal-stack';
import { getSideNavigationState, SideNavigation } from '@/components/SideNavigation';
import { UserInfoGuard } from '@/components/UserInfoGuard';

Expand All @@ -27,6 +28,7 @@ export const RootLayout = () => {
<Outlet />
</main>
</div>
<GlobalModals />
<UserInfoGuard />
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from 'ui-components';

import { CopyButton, useCopyToClipboardState } from '@/components/CopyToClipboard';
import { useGlobalModalStack } from '@/components/detail-modal-stack';
import { DFLink } from '@/components/DFLink';
import { CopyLineIcon } from '@/components/icons/common/CopyLine';
import { InfoStandardIcon } from '@/components/icons/common/InfoStandard';
Expand Down Expand Up @@ -131,6 +132,7 @@ const DetailsComponent = ({
const {
data: { data: malwares },
} = useGetMalwareDetails();
const { addGlobalModal } = useGlobalModalStack();

const [showResourceModal, setShowResourceModal] = useState({
resource: '',
Expand Down Expand Up @@ -285,6 +287,18 @@ const DetailsComponent = ({
target="_blank"
rel="noreferrer"
className="text-p2 flex items-center gap-3"
onClick={(e) => {
if (
resource.node_type === 'container' ||
resource.node_type === 'host'
) {
e.preventDefault();
addGlobalModal({
kind: resource.node_type,
nodeId: resource.node_id,
});
}
}}
>
<span className="h-4 w-4 shrink-0">
<PopOutIcon />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from 'ui-components';

import { CopyButton, useCopyToClipboardState } from '@/components/CopyToClipboard';
import { useGlobalModalStack } from '@/components/detail-modal-stack';
import { DFLink } from '@/components/DFLink';
import { CopyLineIcon } from '@/components/icons/common/CopyLine';
import { PopOutIcon } from '@/components/icons/common/PopOut';
Expand Down Expand Up @@ -113,6 +114,7 @@ const DetailsComponent = ({
const {
data: { data: secrets },
} = useGetSecretDetails();
const { addGlobalModal } = useGlobalModalStack();

const [showResourceModal, setShowResourceModal] = useState({
resource: '',
Expand Down Expand Up @@ -247,6 +249,18 @@ const DetailsComponent = ({
target="_blank"
rel="noreferrer"
className="text-p2 flex items-center gap-3"
onClick={(e) => {
if (
resource.node_type === 'container' ||
resource.node_type === 'host'
) {
e.preventDefault();
addGlobalModal({
kind: resource.node_type,
nodeId: resource.node_id,
});
}
}}
>
<span className="h-4 w-4 shrink-0">
<PopOutIcon />
Expand Down
Loading

0 comments on commit 2c84037

Please sign in to comment.