diff --git a/deepfence_frontend/apps/dashboard/src/assets/df-background.jpg b/deepfence_frontend/apps/dashboard/src/assets/df-background.jpg deleted file mode 100644 index 240bdbdc7d..0000000000 Binary files a/deepfence_frontend/apps/dashboard/src/assets/df-background.jpg and /dev/null differ diff --git a/deepfence_frontend/apps/dashboard/src/assets/logo-deepfence-white.svg b/deepfence_frontend/apps/dashboard/src/assets/logo-deepfence-white.svg deleted file mode 100644 index e72f37243f..0000000000 --- a/deepfence_frontend/apps/dashboard/src/assets/logo-deepfence-white.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/deepfence_frontend/apps/dashboard/src/components/AppHeader.tsx b/deepfence_frontend/apps/dashboard/src/components/AppHeader.tsx index 01e274ca59..3a4e8be383 100644 --- a/deepfence_frontend/apps/dashboard/src/components/AppHeader.tsx +++ b/deepfence_frontend/apps/dashboard/src/components/AppHeader.tsx @@ -1,136 +1,52 @@ import classNames from 'classnames'; -import { IconContext } from 'react-icons'; -import { - HiChevronLeft, - HiLogout, - HiMenu, - HiOutlineDesktopComputer, - HiOutlineMoon, - HiOutlineSun, -} from 'react-icons/hi'; -import { useFetcher } from 'react-router-dom'; -import { - Avatar, - Dropdown, - DropdownItem, - DropdownSeparator, - DropdownSubMenu, - IconButton, -} from 'ui-components'; +import { useFetcher, useRouteLoaderData } from 'react-router-dom'; +import { Dropdown, DropdownItem } from 'ui-components'; +import { DFLink } from '@/components/DFLink'; import { AutoRefresh } from '@/components/header/AutoRefresh'; -import { useTheme } from '@/theme/ThemeContext'; +import { CaretDown } from '@/components/icons/common/CaretDown'; +import { UserLine } from '@/components/icons/common/UserLine'; -export interface DashboardHeaderProps { - sideNavExpanded: boolean; - onSideNavExpandedChange: (expanded: boolean) => void; -} - -const themeSelectedDropdownClassname = 'text-blue-500 dark:text-blue-300'; -const themeDropdownClassname = 'text-gray-700 dark:text-gray-400'; - -export function AppHeader({ - sideNavExpanded, - onSideNavExpandedChange, -}: DashboardHeaderProps) { - const { setMode, userSelectedMode } = useTheme(); +export function AppHeader() { const fetcher = useFetcher(); + const { email } = useRouteLoaderData('root') as { email: string }; return (
-
- { - onSideNavExpandedChange(!sideNavExpanded); +
+ - - - } - size="xs" - /> + > +
+ +
+
+ deepfence +
+
-
+
+ +
+ - - { - setMode('light'); - }} - className={ - userSelectedMode === 'light' - ? themeSelectedDropdownClassname - : themeDropdownClassname - } - > - - Light - - { - setMode('dark'); - }} - className={ - userSelectedMode === 'dark' - ? themeSelectedDropdownClassname - : themeDropdownClassname - } - > - - Dark - - { - setMode(undefined); - }} - className={ - !userSelectedMode - ? themeSelectedDropdownClassname - : themeDropdownClassname - } - > - - Device Theme - - - } - > - e.preventDefault()}> - - - - Theme - - - - { fetcher.submit(null, { @@ -140,18 +56,48 @@ export function AppHeader({ }} className="text-red-700 dark:text-red-500" > - Logout } > -
- -
+
); } + +const DeepfenceLogo = () => { + return ( + + + + + + ); +}; diff --git a/deepfence_frontend/apps/dashboard/src/components/SeverityBadge.tsx b/deepfence_frontend/apps/dashboard/src/components/SeverityBadge.tsx new file mode 100644 index 0000000000..46ef0345f7 --- /dev/null +++ b/deepfence_frontend/apps/dashboard/src/components/SeverityBadge.tsx @@ -0,0 +1,72 @@ +export const SeverityBadge = ({ severity }: { severity: string }) => { + return ( +
+
+ +
{' '} + {severity} +
+ ); +}; + +const SeverityIcon = ({ severity }: { severity: string }) => { + const severities = ['unknown', 'low', 'medium', 'high', 'critical']; + const activeFillClassName = + { + critical: 'dark:fill-status-error', + high: 'dark:fill-chart-orange', + medium: 'dark:fill-status-warning', + low: 'dark:fill-chart-yellow1', + }[severity] ?? ''; + const defaultFillClassName = 'dark:fill-df-gray-700'; + return ( + + 0 ? activeFillClassName : defaultFillClassName + } + /> + 1 ? activeFillClassName : defaultFillClassName + } + /> + 2 ? activeFillClassName : defaultFillClassName + } + /> + 3 ? activeFillClassName : defaultFillClassName + } + /> + + ); +}; diff --git a/deepfence_frontend/apps/dashboard/src/components/SideNavigation.tsx b/deepfence_frontend/apps/dashboard/src/components/SideNavigation.tsx index 3548b01d05..ad2b8176c5 100644 --- a/deepfence_frontend/apps/dashboard/src/components/SideNavigation.tsx +++ b/deepfence_frontend/apps/dashboard/src/components/SideNavigation.tsx @@ -2,12 +2,8 @@ import * as NavigationMenu from '@radix-ui/react-navigation-menu'; import classNames from 'classnames'; import { forwardRef, ReactNode, useEffect } from 'react'; import { NavLink } from 'react-router-dom'; -import { twMerge } from 'tailwind-merge'; import { Tooltip } from 'ui-components'; -import DeepfenceBackground from '@/assets/df-background.jpg'; -import LogoDeepfenceWhite from '@/assets/logo-deepfence-white.svg'; -import { DFLink } from '@/components/DFLink'; import { DashboardIcon } from '@/components/sideNavigation/icons/Dashboard'; import { IntegrationsIcon } from '@/components/sideNavigation/icons/Integrations'; import { MalwareIcon } from '@/components/sideNavigation/icons/Malware'; @@ -18,9 +14,11 @@ import { SettingsIcon } from '@/components/sideNavigation/icons/Settings'; import { ThreatGraphIcon } from '@/components/sideNavigation/icons/ThreatGraph'; import { TopologyIcon } from '@/components/sideNavigation/icons/Topology'; import { VulnerabilityIcon } from '@/components/sideNavigation/icons/Vulnerability'; +import { dfTwMerge } from '@/utils/twmerge'; export interface SideNavigationRootProps { - expanded?: boolean; + expanded: boolean; + onExpandedChange: (expanded: boolean) => void; } const MenuItems: Array<{ @@ -95,62 +93,56 @@ const ItemWrapper = forwardRef( ) => { if (expanded) return
{children}
; return ( - +
{children}
); }, ); -export function SideNavigation({ expanded }: SideNavigationRootProps) { +export function SideNavigation({ expanded, onExpandedChange }: SideNavigationRootProps) { useEffect(() => { setSideNavigationState(expanded ? 'open' : 'closed'); }, [expanded]); return ( - + - - Deefence Logo - + {MenuItems.map((menuItem) => { const linkClass = classNames( - 'text-base font-medium text-gray-100 rounded-xl p-2 block', - 'hover:bg-gray-100/25', - 'flex gap-3 whitespace-nowrap', - 'group', - 'animate-colors', - 'focus:outline-none focus:ring-1 focus:ring-gray-400', - { - ['w-fit']: !expanded, - }, + 'text-h4 dark:text-text-text-and-icon py-3 px-5', + 'dark:hover:bg-bg-breadcrumb-bar', + 'flex items-center gap-5 whitespace-nowrap relative', + 'h-12', ); return ( @@ -160,19 +152,38 @@ export function SideNavigation({ expanded }: SideNavigationRootProps) { - isActive ? twMerge(linkClass, 'bg-gray-100/25') : linkClass + isActive + ? dfTwMerge( + linkClass, + 'dark:bg-bg-active-selection dark:text-text-input-value', + ) + : linkClass } > -
- -
- {expanded && ( -
{menuItem.title}
- )} + {({ isActive }) => { + return ( + <> + {isActive && ( +
+ )} +
+ +
+ {expanded && ( +
{menuItem.title}
+ )} + + ); + }} @@ -194,3 +205,37 @@ export function getSideNavigationState(): SideNavigationState { export function setSideNavigationState(state: SideNavigationState) { localStorage.setItem(storageKey, state); } + +const HamburgerIcon = () => { + return ( + + + + + + ); +}; diff --git a/deepfence_frontend/apps/dashboard/src/components/header/AutoRefresh.tsx b/deepfence_frontend/apps/dashboard/src/components/header/AutoRefresh.tsx index f963249e6f..e98d3ecc5c 100644 --- a/deepfence_frontend/apps/dashboard/src/components/header/AutoRefresh.tsx +++ b/deepfence_frontend/apps/dashboard/src/components/header/AutoRefresh.tsx @@ -1,9 +1,11 @@ import React, { useEffect, useState } from 'react'; -import { HiChevronDown, HiRefresh } from 'react-icons/hi'; +import { HiRefresh } from 'react-icons/hi'; import { useRevalidator } from 'react-router-dom'; import { useInterval } from 'react-use'; import { Dropdown, DropdownItem } from 'ui-components'; +import { CaretDown } from '@/components/icons/common/CaretDown'; + // function that converts seconds to human friendly time // e.g. 300 seconds => 5m // e.g. 3600 seconds => 1h @@ -12,9 +14,9 @@ function secondsToHuman(seconds: number) { if (seconds < 60) { return `${seconds}s`; } else if (seconds < 3600) { - return `${Math.floor(seconds / 60)}m`; + return `${Math.floor(seconds / 60)} min`; } else { - return `${Math.floor(seconds / 3600)}h`; + return `${Math.floor(seconds / 3600)} hour`; } } @@ -42,9 +44,9 @@ export const AutoRefresh = () => { }, [spinning]); return ( -
+
+ + ); +}; + +export const TopologyHeader = ({ nodeCounts }: { nodeCounts: SearchNodeCountResp }) => { + return ( +
+ } + name="Clouds" + type={NodeType.cloud_provider} + count={ + + + {(data: SearchNodeCountResp) => { + return data.cloud_provider; + }} + + + } + /> + } + name="Hosts" + type={NodeType.host} + count={ + + + {(data: SearchNodeCountResp) => { + return data.host; + }} + + + } + /> + } + name="Kubernetes Clusters" + type={NodeType.kubernetes_cluster} + count={ + + + {(data: SearchNodeCountResp) => { + return data.kubernetes_cluster; + }} + + + } + /> + } + name="Containers" + type={NodeType.container} + count={ + + + {(data: SearchNodeCountResp) => { + return data.container; + }} + + + } + /> + } + name="Pods" + type={NodeType.pod} + count={ + + + {(data: SearchNodeCountResp) => { + return data.pod; + }} + + + } + /> +
); }; +// TODO: change this view switcher const ViewSwitcher = () => { const params = useParams(); const location = useLocation(); diff --git a/deepfence_frontend/apps/dashboard/src/routes/private.tsx b/deepfence_frontend/apps/dashboard/src/routes/private.tsx index cc37af1158..497b6c6e8d 100644 --- a/deepfence_frontend/apps/dashboard/src/routes/private.tsx +++ b/deepfence_frontend/apps/dashboard/src/routes/private.tsx @@ -186,6 +186,7 @@ export const privateRoutes: CustomRouteObject[] = [ }, { path: '/', + id: 'root', loader: authenticatedRootLoader, element: , errorElement: , diff --git a/deepfence_frontend/apps/dashboard/src/theme/ThemeContext.tsx b/deepfence_frontend/apps/dashboard/src/theme/ThemeContext.tsx index fe896d75f6..c3b75ce9b1 100644 --- a/deepfence_frontend/apps/dashboard/src/theme/ThemeContext.tsx +++ b/deepfence_frontend/apps/dashboard/src/theme/ThemeContext.tsx @@ -15,23 +15,25 @@ interface ThemeContextProps { const THEME_PREFRENCE_STORAGE_KEY = 'theme'; function getCurrentThemeModeFromStorage(): Mode { - const themePrefrence = localStorage.getItem(THEME_PREFRENCE_STORAGE_KEY); - if (!themePrefrence || ![THEME_LIGHT, THEME_DARK].includes(themePrefrence)) { - // this means user has not set any explicit prefrence, so we use device theme - const deviceTheme = - !!window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches - ? THEME_DARK - : THEME_LIGHT; - return deviceTheme; - } - return themePrefrence as Mode; + return 'dark'; // TODO: remove this and comment code below when we enable light theme + // const themePrefrence = localStorage.getItem(THEME_PREFRENCE_STORAGE_KEY); + // if (!themePrefrence || ![THEME_LIGHT, THEME_DARK].includes(themePrefrence)) { + // // this means user has not set any explicit prefrence, so we use device theme + // const deviceTheme = + // !!window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches + // ? THEME_DARK + // : THEME_LIGHT; + // return deviceTheme; + // } + // return themePrefrence as Mode; } function getUserSelectedModeFromStorage(): Mode | undefined { - const themePrefrence = localStorage.getItem(THEME_PREFRENCE_STORAGE_KEY); - if ([THEME_LIGHT, THEME_DARK].includes(themePrefrence ?? '')) { - return themePrefrence as Mode; - } + return 'dark'; // TODO: remove this and comment code below when we enable light theme + // const themePrefrence = localStorage.getItem(THEME_PREFRENCE_STORAGE_KEY); + // if ([THEME_LIGHT, THEME_DARK].includes(themePrefrence ?? '')) { + // return themePrefrence as Mode; + // } } const saveThemeModeToStorage = (newMode?: Mode) => { diff --git a/deepfence_frontend/apps/dashboard/src/utils/api.ts b/deepfence_frontend/apps/dashboard/src/utils/api.ts index e1ac0beec5..92c384ea6d 100644 --- a/deepfence_frontend/apps/dashboard/src/utils/api.ts +++ b/deepfence_frontend/apps/dashboard/src/utils/api.ts @@ -112,7 +112,7 @@ export function redirectToLogin() { export async function requireLogin() { const auth = storage.getAuth(); - if (auth) return; + if (auth) return auth; storage.clearAuth(); throw redirectToLogin(); } diff --git a/deepfence_frontend/apps/dashboard/src/utils/twmerge.ts b/deepfence_frontend/apps/dashboard/src/utils/twmerge.ts new file mode 100644 index 0000000000..291c81ddd7 --- /dev/null +++ b/deepfence_frontend/apps/dashboard/src/utils/twmerge.ts @@ -0,0 +1,30 @@ +import { extendTailwindMerge, twMerge } from 'tailwind-merge'; + +// https://github.com/dcastil/tailwind-merge/issues/217 +// TODO: make importing tailwind-preset work here and add keys from there +export const dfTwMerge: typeof twMerge = extendTailwindMerge({ + classGroups: { + 'font-size': [ + 'text-h1', + 'text-h2', + 'text-h3', + 'text-h4', + 'text-h5', + 'text-h6', + 'text-p1', + 'text-p2', + 'text-p3', + 'text-p4', + 'text-p5', + 'text-p6', + 'text-p7', + 'text-p8', + 'text-p9', + 'text-t1', + 'text-t2', + 'text-t3', + 'text-t4', + 'text-t5', + ], + }, +}); diff --git a/deepfence_frontend/packages/tailwind-preset/index.js b/deepfence_frontend/packages/tailwind-preset/index.js index 550743aa04..93ba0770ce 100644 --- a/deepfence_frontend/packages/tailwind-preset/index.js +++ b/deepfence_frontend/packages/tailwind-preset/index.js @@ -57,9 +57,9 @@ module.exports = { }, ], h2: [ - '24px', + '22px', { - lineHeight: '36px', + lineHeight: '30px', fontWeight: '600', }, ], @@ -234,7 +234,7 @@ module.exports = { pink2: '#C7527E', pink3: '#E3749E', red: '#F55B47', - orange: '##F57600', + orange: '#F57600', yellow1: '#E5C354', yellow2: '#F6C24F', lime: '#62C655', @@ -273,6 +273,7 @@ module.exports = { 'hover-1': '#3777C2', 'hover-2': '#0E1F33', 'hover-3': '#0140E3', + 'side-panel': '#192C49', }, clarity: { border: '#0F171C',