diff --git a/src/App.js b/src/App.js index f5b22393e..83839fdee 100644 --- a/src/App.js +++ b/src/App.js @@ -1,6 +1,7 @@ import React, { Suspense, useEffect } from 'react' import { HashRouter, Route, Routes } from 'react-router-dom' import { useSelector } from 'react-redux' +import { useNavigation } from './hooks/useNavigation' import { CSpinner, useColorModes } from '@coreui/react' import './scss/style.scss' @@ -20,6 +21,7 @@ const Page500 = React.lazy(() => import('./views/pages/page500/Page500')) const App = () => { const { isColorModeSet, setColorMode } = useColorModes('coreui-free-react-admin-template-theme') const storedTheme = useSelector((state) => state.theme) + const { refreshAllAssets } = useNavigation() useEffect(() => { const urlParams = new URLSearchParams(window.location.href.split('?')[1]) @@ -35,6 +37,16 @@ const App = () => { setColorMode(storedTheme) }, []) // eslint-disable-line react-hooks/exhaustive-deps + // useEffect for background refresh + useEffect(() => { + // Start background refresh after a short delay to allow initial load + const timer = setTimeout(() => { + refreshAllAssets() + }, 2000) + + return () => clearTimeout(timer) + }, [refreshAllAssets]) + return ( , - badge: { - color: 'info', - text: 'NEW', - }, - }, +export const getNavigation = (departments, onAddDepartment, onAddAsset, dynamicNavItems) => [ + // Main Navigation Title { component: CNavTitle, - name: 'Theme', + name: 'Security Dashboard', }, + + // Overview Page { component: CNavItem, - name: 'Colors', - to: '/theme/colors', - icon: , + name: 'Overview', + to: '/overview', + icon: , }, + + // Asset Management Buttons { - component: CNavItem, - name: 'Typography', - to: '/theme/typography', - icon: , + component: DynamicNavButtons, + props: { + onAddDepartment, + onAddAsset, + departments + } }, - { + + // Dynamic Department and Asset Items + ...dynamicNavItems, + + // Management Section Title + ...(dynamicNavItems.length > 0 ? [{ component: CNavTitle, - name: 'Components', - }, - { - component: CNavGroup, - name: 'Base', - to: '/base', - icon: , - items: [ - { - component: CNavItem, - name: 'Accordion', - to: '/base/accordion', - }, - { - component: CNavItem, - name: 'Breadcrumb', - to: '/base/breadcrumbs', - }, - { - component: CNavItem, - name: ( - - {'Calendar'} - - - ), - href: 'https://coreui.io/react/docs/components/calendar/', - badge: { - color: 'danger', - text: 'PRO', - }, - }, - { - component: CNavItem, - name: 'Cards', - to: '/base/cards', - }, - { - component: CNavItem, - name: 'Carousel', - to: '/base/carousels', - }, - { - component: CNavItem, - name: 'Collapse', - to: '/base/collapses', - }, - { - component: CNavItem, - name: 'List group', - to: '/base/list-groups', - }, - { - component: CNavItem, - name: 'Navs & Tabs', - to: '/base/navs', - }, - { - component: CNavItem, - name: 'Pagination', - to: '/base/paginations', - }, - { - component: CNavItem, - name: 'Placeholders', - to: '/base/placeholders', - }, - { - component: CNavItem, - name: 'Popovers', - to: '/base/popovers', - }, - { - component: CNavItem, - name: 'Progress', - to: '/base/progress', - }, - { - component: CNavItem, - name: 'Smart Pagination', - href: 'https://coreui.io/react/docs/components/smart-pagination/', - badge: { - color: 'danger', - text: 'PRO', - }, - }, - { - component: CNavItem, - name: ( - - {'Smart Table'} - - - ), - href: 'https://coreui.io/react/docs/components/smart-table/', - badge: { - color: 'danger', - text: 'PRO', - }, - }, - { - component: CNavItem, - name: 'Spinners', - to: '/base/spinners', - }, - { - component: CNavItem, - name: 'Tables', - to: '/base/tables', - }, - { - component: CNavItem, - name: 'Tabs', - to: '/base/tabs', - }, - { - component: CNavItem, - name: 'Tooltips', - to: '/base/tooltips', - }, - { - component: CNavItem, - name: ( - - {'Virtual Scroller'} - - - ), - href: 'https://coreui.io/react/docs/components/virtual-scroller/', - badge: { - color: 'danger', - text: 'PRO', - }, - }, - ], - }, + name: 'Management', + }] : []), + + // Data Management { - component: CNavGroup, - name: 'Buttons', - to: '/buttons', - icon: , - items: [ - { - component: CNavItem, - name: 'Buttons', - to: '/buttons/buttons', - }, - { - component: CNavItem, - name: 'Buttons groups', - to: '/buttons/button-groups', - }, - { - component: CNavItem, - name: 'Dropdowns', - to: '/buttons/dropdowns', - }, - { - component: CNavItem, - name: ( - - {'Loading Button'} - - - ), - href: 'https://coreui.io/react/docs/components/loading-button/', - badge: { - color: 'danger', - text: 'PRO', - }, - }, - ], + component: CNavItem, + name: 'Data Management', + to: '/datamanagement', + icon: , }, - { - component: CNavGroup, - name: 'Forms', - icon: , - items: [ - { - component: CNavItem, - name: 'Form Control', - to: '/forms/form-control', - }, - { - component: CNavItem, - name: 'Select', - to: '/forms/select', - }, - { - component: CNavItem, - name: ( - - {'Multi Select'} - - - ), - href: 'https://coreui.io/react/docs/forms/multi-select/', - badge: { - color: 'danger', - text: 'PRO', - }, - }, - { - component: CNavItem, - name: 'Checks & Radios', - to: '/forms/checks-radios', - }, - { - component: CNavItem, - name: 'Range', - to: '/forms/range', - }, - { - component: CNavItem, - name: ( - - {'Range Slider'} - - - ), - href: 'https://coreui.io/react/docs/forms/range-slider/', - badge: { - color: 'danger', - text: 'PRO', - }, - }, - { - component: CNavItem, - name: ( - - {'Rating'} - - - ), - href: 'https://coreui.io/react/docs/forms/rating/', - badge: { - color: 'danger', - text: 'PRO', + + // Security Tools Section (if there are assets) + ...(dynamicNavItems.length > 0 ? [ + { + component: CNavTitle, + name: 'Security Tools', + }, + { + component: CNavGroup, + name: 'Vulnerability Management', + icon: , + items: [ + { + component: CNavItem, + name: 'Scan Results', + to: '#', + disabled: true, + badge: { + color: 'secondary', + text: 'COMING SOON', + }, }, - }, - { - component: CNavItem, - name: 'Input Group', - to: '/forms/input-group', - }, - { - component: CNavItem, - name: 'Floating Labels', - to: '/forms/floating-labels', - }, - { - component: CNavItem, - name: ( - - {'Date Picker'} - - - ), - href: 'https://coreui.io/react/docs/forms/date-picker/', - badge: { - color: 'danger', - text: 'PRO', + { + component: CNavItem, + name: 'CVE Database', + to: '#', + disabled: true, + badge: { + color: 'secondary', + text: 'COMING SOON', + }, }, - }, - { - component: CNavItem, - name: 'Date Range Picker', - href: 'https://coreui.io/react/docs/forms/date-range-picker/', - badge: { - color: 'danger', - text: 'PRO', + { + component: CNavItem, + name: 'Risk Assessment', + to: '#', + disabled: true, + badge: { + color: 'secondary', + text: 'COMING SOON', + }, + } + ] + }, + { + component: CNavGroup, + name: 'Security Analytics', + icon: , + items: [ + { + component: CNavItem, + name: 'Threat Intelligence', + to: '#', + disabled: true, + badge: { + color: 'secondary', + text: 'COMING SOON', + }, }, - }, - { - component: CNavItem, - name: ( - - {'Time Picker'} - - - ), - href: 'https://coreui.io/react/docs/forms/time-picker/', - badge: { - color: 'danger', - text: 'PRO', + { + component: CNavItem, + name: 'Attack Patterns', + to: '#', + disabled: true, + badge: { + color: 'secondary', + text: 'COMING SOON', + }, }, - }, - { - component: CNavItem, - name: 'Layout', - to: '/forms/layout', - }, - { - component: CNavItem, - name: 'Validation', - to: '/forms/validation', - }, - ], - }, - { - component: CNavItem, - name: 'Charts', - to: '/charts', - icon: , - }, - { - component: CNavGroup, - name: 'Icons', - icon: , - items: [ - { - component: CNavItem, - name: 'CoreUI Free', - to: '/icons/coreui-icons', - }, - { - component: CNavItem, - name: 'CoreUI Flags', - to: '/icons/flags', - }, - { - component: CNavItem, - name: 'CoreUI Brands', - to: '/icons/brands', - }, - ], - }, - { - component: CNavGroup, - name: 'Notifications', - icon: , - items: [ - { - component: CNavItem, - name: 'Alerts', - to: '/notifications/alerts', - }, - { - component: CNavItem, - name: 'Badges', - to: '/notifications/badges', - }, - { - component: CNavItem, - name: 'Modal', - to: '/notifications/modals', - }, - { - component: CNavItem, - name: 'Toasts', - to: '/notifications/toasts', - }, - ], - }, - { - component: CNavItem, - name: 'Widgets', - to: '/widgets', - icon: , - badge: { - color: 'info', - text: 'NEW', - }, - }, - { - component: CNavTitle, - name: 'Extras', - }, - { - component: CNavGroup, - name: 'Pages', - icon: , - items: [ - { - component: CNavItem, - name: 'Login', - to: '/login', - }, - { - component: CNavItem, - name: 'Register', - to: '/register', - }, - { - component: CNavItem, - name: 'Error 404', - to: '/404', - }, - { - component: CNavItem, - name: 'Error 500', - to: '/500', - }, - ], - }, - { - component: CNavItem, - name: 'Docs', - href: 'https://coreui.io/react/docs/templates/installation/', - icon: , - }, -] - -export default _nav + { + component: CNavItem, + name: 'Security Metrics', + to: '#', + disabled: true, + badge: { + color: 'secondary', + text: 'COMING SOON', + }, + } + ] + } + ] : []) +] \ No newline at end of file diff --git a/src/components/AppBreadcrumb.js b/src/components/AppBreadcrumb.js index d37de8ce9..7328ba8c3 100644 --- a/src/components/AppBreadcrumb.js +++ b/src/components/AppBreadcrumb.js @@ -1,12 +1,15 @@ import React from 'react' -import { useLocation } from 'react-router-dom' - -import routes from '../routes' - +import { useLocation, useParams } from 'react-router-dom' import { CBreadcrumb, CBreadcrumbItem } from '@coreui/react' +import CIcon from '@coreui/icons-react' +import { cilHome, cilFolder, cilDescription, cilSettings } from '@coreui/icons' +import routes from '../routes' +import { useNavigation } from '../hooks/useNavigation' const AppBreadcrumb = () => { const currentLocation = useLocation().pathname + const { assetId } = useParams() + const { assets, departments } = useNavigation() const getRouteName = (pathname, routes) => { const currentRoute = routes.find((route) => route.path === pathname) @@ -15,17 +18,78 @@ const AppBreadcrumb = () => { const getBreadcrumbs = (location) => { const breadcrumbs = [] - location.split('/').reduce((prev, curr, index, array) => { + + // Handle root/overview case - don't add extra breadcrumbs + if (location === '/' || location === '/overview') { + return breadcrumbs // Return empty array, the "Overview" will be shown as base + } + + // Handle special dynamic routes + if (location.startsWith('/asset/')) { + const asset = assets.find(a => a.id === assetId) + if (asset) { + // Add department breadcrumb + breadcrumbs.push({ + pathname: '#', + name: asset.department, + active: false, + icon: cilFolder + }) + // Add asset breadcrumb + breadcrumbs.push({ + pathname: location, + name: asset.name, + active: true, + icon: cilDescription + }) + } + return breadcrumbs + } + + // Handle standard routes + const pathSegments = location.split('/').filter(segment => segment !== '') + + // Skip if this is just the overview page + if (pathSegments.length === 1 && pathSegments[0] === 'overview') { + return breadcrumbs + } + + pathSegments.reduce((prev, curr, index, array) => { const currentPathname = `${prev}/${curr}` - const routeName = getRouteName(currentPathname, routes) - routeName && + let routeName = getRouteName(currentPathname, routes) + let icon = null + + // Set appropriate icons for known routes + switch (currentPathname) { + case '/datamanagement': + icon = cilSettings + routeName = 'Data Management' + break + default: + if (departments.includes(curr)) { + icon = cilFolder + } else { + icon = cilDescription + } + } + + // If route name not found, try to match department or asset names + if (!routeName && departments.includes(curr)) { + routeName = curr + } + + if (routeName) { breadcrumbs.push({ pathname: currentPathname, name: routeName, - active: index + 1 === array.length ? true : false, + active: index + 1 === array.length, + icon: icon }) + } + return currentPathname - }) + }, '') + return breadcrumbs } @@ -33,13 +97,27 @@ const AppBreadcrumb = () => { return ( - Home + {/* Home/Overview breadcrumb - only show if not already on overview */} + {currentLocation !== '/' && currentLocation !== '/overview' && ( + + + Overview + + )} + {breadcrumbs.map((breadcrumb, index) => { return ( + {breadcrumb.icon && ( + + )} {breadcrumb.name} ) @@ -48,4 +126,4 @@ const AppBreadcrumb = () => { ) } -export default React.memo(AppBreadcrumb) +export default React.memo(AppBreadcrumb) \ No newline at end of file diff --git a/src/components/AppFooter.js b/src/components/AppFooter.js index 217c5a04c..d0285fabf 100644 --- a/src/components/AppFooter.js +++ b/src/components/AppFooter.js @@ -1,23 +1,83 @@ import React from 'react' -import { CFooter } from '@coreui/react' +import { CFooter, CBadge } from '@coreui/react' +import CIcon from '@coreui/icons-react' +import { cilShieldAlt, cilCheckCircle } from '@coreui/icons' +import { useNavigation } from '../hooks/useNavigation' const AppFooter = () => { + const { assets } = useNavigation() + + // Calculate security metrics for footer + const securityStatus = React.useMemo(() => { + if (assets.length === 0) return { status: 'unknown', message: 'No assets scanned' } + + const totalVulns = assets.reduce((sum, asset) => + sum + (asset.vulnerabilities?.cves?.length || 0), 0 + ) + + const criticalVulns = assets.reduce((sum, asset) => + sum + (asset.vulnerabilities?.cves?.filter(cve => cve.cvss_score >= 7.0)?.length || 0), 0 + ) + + if (criticalVulns > 0) { + return { status: 'critical', message: `${criticalVulns} critical vulnerabilities detected` } + } else if (totalVulns > 0) { + return { status: 'warning', message: `${totalVulns} vulnerabilities detected` } + } else { + return { status: 'secure', message: 'All systems secure' } + } + }, [assets]) + + const getStatusColor = () => { + switch (securityStatus.status) { + case 'secure': return 'success' + case 'warning': return 'warning' + case 'critical': return 'danger' + default: return 'secondary' + } + } + + const getStatusIcon = () => { + switch (securityStatus.status) { + case 'secure': return cilCheckCircle + default: return cilShieldAlt + } + } + return ( -
- - CoreUI - - © 2025 creativeLabs. +
+ + CyberSec Dashboard + © 2025 Security Operations.
-
- Powered by - - CoreUI React Admin & Dashboard Template - +
+ {/* Security Status Indicator */} +
+ + Status: + + {securityStatus.message} + +
+ + {/* Asset Count */} +
+ Assets: + {assets.length} +
+ + {/* Last Updated */} + + Updated: {new Date().toLocaleTimeString()} +
) } -export default React.memo(AppFooter) +export default React.memo(AppFooter) \ No newline at end of file diff --git a/src/components/AppHeader.js b/src/components/AppHeader.js index b10bd7e12..dbb3249e1 100644 --- a/src/components/AppHeader.js +++ b/src/components/AppHeader.js @@ -1,4 +1,4 @@ -import React, { useEffect, useRef } from 'react' +import React, { useEffect, useRef, useState } from 'react' import { NavLink } from 'react-router-dom' import { useSelector, useDispatch } from 'react-redux' import { @@ -12,29 +12,54 @@ import { CHeaderToggler, CNavLink, CNavItem, + CBadge, + CButton, + CModal, + CModalHeader, + CModalTitle, + CModalBody, + CModalFooter, + CAlert, + CListGroup, + CListGroupItem, + CProgress, useColorModes, } from '@coreui/react' import CIcon from '@coreui/icons-react' import { - cilBell, + cilAnimal, + cilShieldAlt, + cilBug, cilContrast, - cilEnvelopeOpen, - cilList, cilMenu, cilMoon, cilSun, + cilWarning, + cilCheckCircle, + cilTriangle, + cilSettings, + cilReload, + cilChart, + cilBell } from '@coreui/icons' import { AppBreadcrumb } from './index' -import { AppHeaderDropdown } from './header/index' +import { useNavigation } from '../hooks/useNavigation' const AppHeader = () => { const headerRef = useRef() const { colorMode, setColorMode } = useColorModes('coreui-free-react-admin-template-theme') - + const { assets } = useNavigation() + const dispatch = useDispatch() const sidebarShow = useSelector((state) => state.sidebarShow) + // Security Status States + const [securityAlerts, setSecurityAlerts] = useState([]) + const [showAlertsModal, setShowAlertsModal] = useState(false) + const [showSecurityStatus, setShowSecurityStatus] = useState(false) + const [lastScanTime, setLastScanTime] = useState(new Date()) + useEffect(() => { document.addEventListener('scroll', () => { headerRef.current && @@ -42,100 +67,380 @@ const AppHeader = () => { }) }, []) + // Calculate security metrics from assets + const securityMetrics = React.useMemo(() => { + let totalVulns = 0 + let criticalVulns = 0 + let highRiskAssets = 0 + + assets.forEach(asset => { + if (asset.vulnerabilities?.cves) { + totalVulns += asset.vulnerabilities.cves.length + criticalVulns += asset.vulnerabilities.cves.filter(cve => + cve.cvss_score >= 7.0 + ).length + } + if (asset.risk_level >= 7) { + highRiskAssets++ + } + }) + + const overallRisk = assets.length > 0 + ? Math.round(assets.reduce((sum, asset) => sum + (asset.risk_level || 0), 0) / assets.length) + : 0 + + return { + totalAssets: assets.length, + totalVulnerabilities: totalVulns, + criticalVulnerabilities: criticalVulns, + highRiskAssets, + overallRiskScore: overallRisk, + securityStatus: overallRisk <= 3 ? 'good' : overallRisk <= 6 ? 'warning' : 'critical' + } + }, [assets]) + + // Simulate security alerts (in real app, this would come from your backend) + useEffect(() => { + const alerts = [] + + assets.forEach(asset => { + if (asset.vulnerabilities?.cves) { + asset.vulnerabilities.cves.forEach(cve => { + if (cve.cvss >= 7.0) { // Only show high/critical vulnerabilities + alerts.push({ + id: `${asset.id}-${cve.cve_id}`, + type: cve.cvss >= 9.0 ? 'critical' : 'high', + title: `${cve.cve_id} - ${asset.name}`, + message: `CVSS: ${cve.cvss} - ${cve.description?.substring(0, 100)}...`, + timestamp: new Date(asset.last_updated || Date.now()), + assetId: asset.id, + assetName: asset.name, + cveId: cve.cve_id, + cvssScore: cve.cvss + }) + } + }) + } + }) + + // Sort by CVSS score descending, then by timestamp + alerts.sort((a, b) => { + if (b.cvssScore !== a.cvssScore) { + return b.cvssScore - a.cvssScore + } + return b.timestamp - a.timestamp + }) + + setSecurityAlerts(alerts.slice(0, 20)) // Limit to 20 most critical + }, [assets]) + + const getSecurityStatusColor = () => { + switch (securityMetrics.securityStatus) { + case 'good': return 'success' + case 'warning': return 'warning' + case 'critical': return 'danger' + default: return 'secondary' + } + } + + const getSecurityStatusIcon = () => { + switch (securityMetrics.securityStatus) { + case 'good': return cilCheckCircle + case 'warning': return cilTriangle + case 'critical': return cilWarning + default: return cilShieldAlt + } + } + return ( - - - dispatch({ type: 'set', sidebarShow: !sidebarShow })} - style={{ marginInlineStart: '-14px' }} - > - - - - - - Dashboard - - - - Users - - - Settings - - - - - - - - - - - - - - - - - - - - -
  • -
    -
  • - - - {colorMode === 'dark' ? ( - - ) : colorMode === 'auto' ? ( - - ) : ( - - )} - - - setColorMode('light')} - > - Light - - setColorMode('dark')} - > - Dark - - setColorMode('auto')} - > - Auto - - - -
  • -
    -
  • - -
    -
    - - - -
    + <> + + + dispatch({ type: 'set', sidebarShow: !sidebarShow })} + style={{ marginInlineStart: '-14px' }} + > + + + + {/* Main Navigation */} + + + + + Overview + + + + + + Management + + + + + {/* Security Tools */} + + {/* Security Alerts */} + + { + e.preventDefault() + setShowAlertsModal(true) + }} + className="position-relative" + title="Security Alerts" + > + + {securityAlerts.length > 0 && ( + + {securityAlerts.length} + + )} + + + + {/* Security Status Indicator */} + + { + e.preventDefault() + setShowSecurityStatus(true) + }} + className="position-relative" + title="Security Status Overview" + > + + + {securityMetrics.overallRiskScore} + + + + + + {/* Theme Switcher */} + +
  • +
    +
  • + + + {colorMode === 'dark' ? ( + + ) : colorMode === 'auto' ? ( + + ) : ( + + )} + + + setColorMode('light')} + > + Light + + setColorMode('dark')} + > + Dark + + setColorMode('auto')} + > + Auto + + + +
    +
    + + {/* Security Status Bar */} + {securityMetrics.totalAssets > 0 && ( + +
    +
    + Security Overview: +
    + + {securityMetrics.totalAssets} Assets +
    +
    + + {securityMetrics.totalVulnerabilities} Vulnerabilities +
    + {securityMetrics.criticalVulnerabilities > 0 && ( +
    + + {securityMetrics.criticalVulnerabilities} Critical +
    + )} +
    + + Last scan: {lastScanTime.toLocaleTimeString()} + +
    +
    + )} + + {/* Breadcrumb */} + + + +
    + + {/* Security Alerts Modal */} + setShowAlertsModal(false)} size="lg"> + + Security Alerts + + + {securityAlerts.length > 0 ? ( + + {securityAlerts.map(alert => ( + +
    +
    + + {alert.cveId} + on {alert.assetName} +
    +
    + = 9.0 ? 'danger' : 'warning'} + > + CVSS: {alert.cvssScore} + + + {alert.type.toUpperCase()} + +
    +

    {alert.message}

    + + {alert.timestamp.toLocaleString()} + +
    + { + setShowAlertsModal(false) + navigate(`/asset/${alert.assetId}`) + }} + > + View Asset + +
    + ))} +
    + ) : ( + + + No critical or high-risk vulnerabilities found. + + )} +
    + + setShowAlertsModal(false)}> + Close + + +
    + + {/* Security Status Modal */} + setShowSecurityStatus(false)} size="lg"> + + Security Status Dashboard + + +
    +
    +
    + Overall Security Score + + {securityMetrics.overallRiskScore}/10 + +
    + +
    +
    +
    + High Risk Assets + + {securityMetrics.highRiskAssets}/{securityMetrics.totalAssets} + +
    + 0 ? (securityMetrics.highRiskAssets / securityMetrics.totalAssets) * 100 : 0} + /> +
    +
    + +
    +
    +
    {securityMetrics.totalAssets}
    + Total Assets +
    +
    +
    {securityMetrics.totalVulnerabilities}
    + Vulnerabilities +
    +
    +
    {securityMetrics.criticalVulnerabilities}
    + Critical +
    +
    +
    {securityMetrics.highRiskAssets}
    + High Risk +
    +
    +
    + + setShowSecurityStatus(false)}> + Close + + + + Refresh Status + + +
    + ) } -export default AppHeader +export default AppHeader \ No newline at end of file diff --git a/src/components/AppSidebar.js b/src/components/AppSidebar.js index 021cb52c3..10dfc7f81 100644 --- a/src/components/AppSidebar.js +++ b/src/components/AppSidebar.js @@ -1,6 +1,5 @@ import React from 'react' import { useSelector, useDispatch } from 'react-redux' - import { CCloseButton, CSidebar, @@ -8,25 +7,49 @@ import { CSidebarFooter, CSidebarHeader, CSidebarToggler, + CBadge } from '@coreui/react' import CIcon from '@coreui/icons-react' - +import { cilShieldAlt, cilBug } from '@coreui/icons' import { AppSidebarNav } from './AppSidebarNav' - -import { logo } from 'src/assets/brand/logo' -import { sygnet } from 'src/assets/brand/sygnet' - -// sidebar nav config -import navigation from '../_nav' +import { getNavigation } from '../_nav' +import { useNavigation } from '../hooks/useNavigation' const AppSidebar = () => { const dispatch = useDispatch() const unfoldable = useSelector((state) => state.sidebarUnfoldable) const sidebarShow = useSelector((state) => state.sidebarShow) + + // Use our custom navigation hook + const { departments, assets, addDepartment, addAsset, dynamicNavItems } = useNavigation() + + // Get the complete navigation with dynamic items + const navigation = getNavigation(departments, addDepartment, addAsset, dynamicNavItems) + + // Calculate security metrics for sidebar + const securityMetrics = React.useMemo(() => { + let totalVulns = 0 + let criticalVulns = 0 + + assets.forEach(asset => { + if (asset.vulnerabilities?.cves) { + totalVulns += asset.vulnerabilities.cves.length + criticalVulns += asset.vulnerabilities.cves.filter(cve => + cve.cvss_score >= 7.0 + ).length + } + }) + + return { + totalAssets: assets.length, + totalVulnerabilities: totalVulns, + criticalVulnerabilities: criticalVulns + } + }, [assets]) return ( { }} > - - - + + +
    +
    CyberSec
    + Dashboard +
    { onClick={() => dispatch({ type: 'set', sidebarShow: false })} />
    + + {/* Security Metrics Header */} + {securityMetrics.totalAssets > 0 && ( +
    +
    + Security Status +
    +
    +
    +
    +
    {securityMetrics.totalAssets}
    + Assets +
    +
    +
    +
    +
    + {securityMetrics.totalVulnerabilities} +
    + Vulns +
    +
    +
    + {securityMetrics.criticalVulnerabilities > 0 && ( +
    + + + {securityMetrics.criticalVulnerabilities} Critical + +
    + )} +
    + )} + + - dispatch({ type: 'set', sidebarUnfoldable: !unfoldable })} - /> +
    + + v1.0.0 + + dispatch({ type: 'set', sidebarUnfoldable: !unfoldable })} + /> +
    ) } -export default React.memo(AppSidebar) +export default React.memo(AppSidebar) \ No newline at end of file diff --git a/src/components/AppSidebarNav.js b/src/components/AppSidebarNav.js index 7583abf49..e63a9e897 100644 --- a/src/components/AppSidebarNav.js +++ b/src/components/AppSidebarNav.js @@ -1,11 +1,10 @@ import React from 'react' import { NavLink } from 'react-router-dom' import PropTypes from 'prop-types' - import SimpleBar from 'simplebar-react' import 'simplebar-react/dist/simplebar.min.css' - import { CBadge, CNavLink, CSidebarNav } from '@coreui/react' +import DynamicNavButtons from './DynamicNavButtons' export const AppSidebarNav = ({ items }) => { const navLink = (name, icon, badge, indent = false) => { @@ -20,7 +19,7 @@ export const AppSidebarNav = ({ items }) => { )} {name && name} {badge && ( - + {badge.text} )} @@ -31,13 +30,18 @@ export const AppSidebarNav = ({ items }) => { const navItem = (item, index, indent = false) => { const { component, name, badge, icon, ...rest } = item const Component = component + + // Handle our custom DynamicNavButtons component + if (Component === DynamicNavButtons) { + return + } + return ( {rest.to || rest.href ? ( {navLink(name, icon, badge, indent)} @@ -53,7 +57,7 @@ export const AppSidebarNav = ({ items }) => { const Component = component return ( - {items?.map((item, index) => + {item.items?.map((item, index) => item.items ? navGroup(item, index) : navItem(item, index, true), )} diff --git a/src/components/DataManagement.js b/src/components/DataManagement.js new file mode 100644 index 000000000..06d50fa58 --- /dev/null +++ b/src/components/DataManagement.js @@ -0,0 +1,132 @@ +import React, { useState } from 'react' +import { + CButton, + CCard, + CCardBody, + CCardHeader, + CModal, + CModalHeader, + CModalTitle, + CModalBody, + CModalFooter, + CListGroup, + CListGroupItem, + CBadge +} from '@coreui/react' +import { useNavigation } from '../hooks/useNavigation' + +const DataManagement = () => { + const { + departments, + assets, + removeDepartment, + removeAsset, + clearAllData + } = useNavigation() + + const [showClearModal, setShowClearModal] = useState(false) + + const handleClearAll = () => { + clearAllData() + setShowClearModal(false) + } + + return ( + <> + + +

    Data Management

    +
    + +
    +
    Departments {departments.length}
    + + {departments.map((dept, index) => { + const deptAssets = assets.filter(asset => asset.department === dept) + return ( + + + {dept} + + {deptAssets.length} assets + + + removeDepartment(dept)} + > + Remove + + + ) + })} + {departments.length === 0 && ( + No departments created yet + )} + +
    + +
    +
    Assets {assets.length}
    + + {assets.map((asset) => ( + +
    + {asset.name} +
    + + Department: {asset.department} | Vendor: {asset.vendor} + +
    + removeAsset(asset.id)} + > + Remove + +
    + ))} + {assets.length === 0 && ( + No assets created yet + )} +
    +
    + +
    + setShowClearModal(true)} + disabled={departments.length === 0 && assets.length === 0} + > + Clear All Data + +
    +
    +
    + + {/* Clear All Confirmation Modal */} + setShowClearModal(false)}> + setShowClearModal(false)}> + Confirm Clear All Data + + + Are you sure you want to clear all departments and assets? This action cannot be undone. + + + setShowClearModal(false)}> + Cancel + + + Clear All Data + + + + + ) +} + +export default DataManagement \ No newline at end of file diff --git a/src/components/DynamicNavButtons.js b/src/components/DynamicNavButtons.js new file mode 100644 index 000000000..b0fa86404 --- /dev/null +++ b/src/components/DynamicNavButtons.js @@ -0,0 +1,534 @@ +import React, { useState, useEffect, useCallback } from 'react' +import { + CModal, + CModalHeader, + CModalTitle, + CModalBody, + CModalFooter, + CButton, + CRow, + CCol, + CFormLabel, + CFormInput, + CFormSelect, + CCard, + CCardBody, + CListGroup, + CListGroupItem, + CSpinner, + CAlert, + CBadge +} from '@coreui/react'; + +const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { + const [departmentDialogVisible, setDepartmentDialogVisible] = useState(false) + const [assetDialogVisible, setAssetDialogVisible] = useState(false) + const [departmentName, setDepartmentName] = useState('') + + // Asset form states + const [selectedDepartment, setSelectedDepartment] = useState('') + + // OS Family states + const [osFamilies, setOsFamilies] = useState([]); + const [selectedOsFamily, setSelectedOsFamily] = useState(''); + const [osVersion, setOsVersion] = useState(''); + const [isLoadingOsFamilies, setIsLoadingOsFamilies] = useState(false); + + // Search and CPE matching states + const [searchQuery, setSearchQuery] = useState(''); + const [cpeMatches, setCpeMatches] = useState([]); + const [selectedCpeMatch, setSelectedCpeMatch] = useState(null); + const [isSearching, setIsSearching] = useState(false); + const [searchError, setSearchError] = useState(''); + const [showResults, setShowResults] = useState(false); + const [isScanning, setIsScanning] = useState(false); + + // Debounced search function + const debounce = useCallback((func, delay) => { + let timeoutId; + return (...args) => { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => func.apply(null, args), delay); + }; + }, []); + + // API call to search CPE matches + const searchCpeMatches = async (query) => { + if (!query.trim()) { + setCpeMatches([]); + setShowResults(false); + return; + } + + setIsSearching(true); + setSearchError(''); + + try { + const response = await fetch('http://localhost:8000/api/v1/security/cpe-search', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + device_name: query.trim() + }) + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + setCpeMatches(data.matches || []); + setShowResults(true); + } catch (error) { + console.error('CPE search error:', error); + setSearchError('Failed to search devices. Please try again.'); + setCpeMatches([]); + setShowResults(false); + } finally { + setIsSearching(false); + } + }; + + // Debounced search with 500ms delay + const debouncedSearch = useCallback( + debounce(searchCpeMatches, 500), + [debounce] + ); + + // Handle search input change + const handleSearchChange = (e) => { + const query = e.target.value; + setSearchQuery(query); + setSelectedCpeMatch(null); // Clear selection when typing + debouncedSearch(query); + }; + + // Handle CPE match selection + const handleCpeMatchSelect = async (cpeMatch) => { + setSelectedCpeMatch(cpeMatch); + setSearchQuery(cpeMatch.device_name); + setShowResults(false); + setSearchError(''); + + // Fetch OS families for the selected vendor + if (cpeMatch.vendor) { + await getOsFamilies(cpeMatch.vendor) + } + }; + + // API call to scan device by OS information + const scanDeviceByOs = async (deviceName, h_cpe, vendor, model, osFamily, version, department) => { + try { + const response = await fetch('http://localhost:8000/api/v1/security/scan-by-os', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + device_name: deviceName, + h_cpe: h_cpe, + vendor: vendor, + model: model, + os_family: osFamily, + version: version, + department: department + }) + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const scanData = await response.json(); + return scanData; + } catch (error) { + console.error('Error scanning device:', error); + throw error; + } + }; + + // API call to get OS families by vendor + const getOsFamilies = async (vendor) => { + setIsLoadingOsFamilies(true); + try { + const response = await fetch('http://localhost:8000/api/v1/security/get-os-families', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + vendor: vendor + }) + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + setOsFamilies(data.os_families || []); + setSelectedOsFamily(data.default_os_family || ''); + return data; + } catch (error) { + console.error('Error fetching OS families:', error); + setSearchError('Failed to fetch OS families. Please try again.'); + setOsFamilies([]); + setSelectedOsFamily(''); + } finally { + setIsLoadingOsFamilies(false); + } + }; + + const handleAssetConfirm = async () => { + if (!selectedCpeMatch || !selectedDepartment || !selectedOsFamily || !osVersion.trim()) return + + setIsScanning(true) + setSearchError('') + + try { + const scanResults = await scanDeviceByOs( + selectedCpeMatch.device_name, + selectedCpeMatch.cpe, + selectedCpeMatch.vendor, + selectedCpeMatch.model, + selectedOsFamily, + osVersion.trim(), + selectedDepartment + ) + + if (!scanResults.success) { + throw new Error(scanResults.error_message || 'Scan failed') + } + + // Store OS information in the scan results for future refreshes + const enhancedResults = { + ...scanResults, + device: { + ...scanResults.device, + department: selectedDepartment, + os_family: selectedOsFamily, + version: osVersion.trim() + }, + // Store scanning parameters for background refreshes + scan_params: { + device_name: selectedCpeMatch.device_name, + vendor: selectedCpeMatch.vendor, + model: selectedCpeMatch.model, + os_family: selectedOsFamily, + version: osVersion.trim() + } + } + + // Pass the full API response to addAsset + onAddAsset(enhancedResults) + + // Reset form + resetAssetForm() + setAssetDialogVisible(false) + } catch (error) { + setSearchError(`Failed to scan device: ${error.message}`) + } finally { + setIsScanning(false) + } + } + + // Reset asset form function + const resetAssetForm = () => { + setSearchQuery(''); + setCpeMatches([]); + setSelectedCpeMatch(null); + setShowResults(false); + setSearchError(''); + setOsFamilies([]); + setSelectedOsFamily(''); + setOsVersion(''); + }; + + // Handle asset dialog cancel + const handleAssetCancel = () => { + resetAssetForm(); + setAssetDialogVisible(false); + }; + + // Department handlers + const handleAddDepartment = () => { + setDepartmentDialogVisible(true) + } + + const handleAddAsset = () => { + setAssetDialogVisible(true) + } + + const handleDepartmentConfirm = () => { + if (departmentName.trim()) { + onAddDepartment(departmentName.trim()) + setDepartmentName('') + setDepartmentDialogVisible(false) + } + } + + const handleDepartmentCancel = () => { + setDepartmentName('') + setDepartmentDialogVisible(false) + } + + return ( + <> +
    +
    + + Add Dept + + + Add Asset + +
    +
    + + {/* Department Dialog */} + + + Add Department + + + Department Name + setDepartmentName(e.target.value)} + placeholder="Enter department name..." + onKeyDown={(e) => { + if (e.key === 'Enter' && departmentName.trim()) { + handleDepartmentConfirm() + } + }} + /> + + + + Cancel + + + Confirm + + + + + {/* Asset Dialog */} + + + Add Asset - Security Scan + + + {/* Device Search Section */} + + + Search Devices +
    + + {isSearching && ( +
    + +
    + )} +
    + + {/* Search Error */} + {searchError && ( + + {searchError} + + )} + + {/* CPE Match Results */} + {showResults && cpeMatches.length > 0 && ( + + + + {cpeMatches.map((match, index) => ( + handleCpeMatchSelect(match)} + style={{ cursor: 'pointer' }} + className={selectedCpeMatch?.cpe === match.cpe ? 'bg-light' : ''} + > +
    +
    +
    {match.device_name}
    +
    + Vendor: {match.vendor} | Model: {match.model} +
    + +
    + CPE: {match.cpe} +
    + +
    + {/*} + = 80 ? 'success' : match.score >= 60 ? 'warning' : 'secondary'} + className="ms-2" + > + {match.score}% + + */} +
    +
    + ))} +
    +
    +
    + )} + + {/* No Results Message */} + {showResults && cpeMatches.length === 0 && !isSearching && searchQuery.trim() && ( + + No devices found matching your search. Try different keywords. + + )} +
    +
    + + {/* Selected Device Info */} + {selectedCpeMatch && ( + + + + +
    Selected Device:
    +
    Name: {selectedCpeMatch.device_name}
    +
    Vendor: {selectedCpeMatch.vendor}
    +
    Model: {selectedCpeMatch.model}
    +
    + CPE: {selectedCpeMatch.cpe} +
    +
    +
    +
    +
    + )} + + {/* OS Family and Version Selection */} + {selectedCpeMatch && ( + + + Operating System Family * + setSelectedOsFamily(e.target.value)} + disabled={isScanning || isLoadingOsFamilies} + > + + {osFamilies.map((family, index) => ( + + ))} + + {isLoadingOsFamilies && ( +
    + + Loading OS families... +
    + )} +
    + + OS Version * + setOsVersion(e.target.value)} + placeholder="Enter OS version (e.g., 10.1, 2019, 7.0)..." + disabled={isScanning} + /> + +
    + )} + + {/* Department Selection */} + + + Department * + setSelectedDepartment(e.target.value)} + disabled={isScanning} + > + + {departments.map((dept, index) => ( + + ))} + + + + + {/* Scanning Status */} + {isScanning && ( + + + Scanning device for vulnerabilities... This may take a few moments. + + )} +
    + + + Cancel + + + {isScanning ? ( + <> + + Scanning... + + ) : ( + 'Scan & Add Asset' + )} + + +
    + + ) +} + +export default DynamicNavButtons \ No newline at end of file diff --git a/src/hooks/useNavigation.js b/src/hooks/useNavigation.js new file mode 100644 index 000000000..79fa720eb --- /dev/null +++ b/src/hooks/useNavigation.js @@ -0,0 +1,249 @@ +import { useState, useEffect, useMemo } from 'react' +import { CNavGroup, CNavItem } from '@coreui/react' +import { CIcon } from '@coreui/icons-react' +import { cilFolder, cilDescription } from '@coreui/icons' + +const DEPARTMENTS_STORAGE_KEY = 'coreui_departments' +const ASSETS_STORAGE_KEY = 'coreui_assets' + +export const useNavigation = () => { + // Initialize state from localStorage + const [departments, setDepartments] = useState(() => { + try { + const savedDepartments = localStorage.getItem(DEPARTMENTS_STORAGE_KEY) + return savedDepartments ? JSON.parse(savedDepartments) : [] + } catch (error) { + console.error('Error loading departments from localStorage:', error) + return [] + } + }) + + const [assets, setAssets] = useState(() => { + try { + const savedAssets = localStorage.getItem(ASSETS_STORAGE_KEY) + return savedAssets ? JSON.parse(savedAssets) : [] + } catch (error) { + console.error('Error loading assets from localStorage:', error) + return [] + } + }) + + // Save departments to localStorage whenever it changes + useEffect(() => { + try { + localStorage.setItem(DEPARTMENTS_STORAGE_KEY, JSON.stringify(departments)) + } catch (error) { + console.error('Error saving departments to localStorage:', error) + } + }, [departments]) + + // Save assets to localStorage whenever it changes + useEffect(() => { + try { + localStorage.setItem(ASSETS_STORAGE_KEY, JSON.stringify(assets)) + } catch (error) { + console.error('Error saving assets to localStorage:', error) + } + }, [assets]) + + const addDepartment = (departmentName) => { + if (!departments.includes(departmentName)) { + setDepartments(prev => [...prev, departmentName]) + } + } + + const addAsset = (apiResponse) => { + // Transform API response to match your current asset structure + const asset = { + id: apiResponse.device?.id?.toString() || `asset-${Date.now()}`, + name: apiResponse.device?.name || 'Unknown Device', + vendor: apiResponse.device?.vendor || 'Unknown Vendor', + model: apiResponse.device?.model || 'Unknown Model', + version: apiResponse.device?.version || '', + type: apiResponse.device?.type || 'Unknown', + department: apiResponse.device?.department || '', + description: apiResponse.device?.description || '', + risk_level: apiResponse.device?.risk_level || 0, + os_family: apiResponse.device?.os_family || '', + + // Store scan parameters for background refreshes + scan_params: apiResponse.scan_params || {}, + + // Store full API response for detailed view + vulnerabilities: { + cves: apiResponse.cves || [], + cwes: apiResponse.cwes || [], + capecs: apiResponse.capecs || [], + attacks: apiResponse.attacks || [] + }, + statistics: apiResponse.statistics || {}, + scan_time: apiResponse.scan_time || 0, + last_updated: new Date().toISOString(), + + // Keep backward compatibility + deviceType: apiResponse.device?.type || 'Unknown' + } + + setAssets(prev => [...prev, asset]) + } + + // Add new function to update existing asset + const updateAsset = (assetId, apiResponse) => { + setAssets(prev => prev.map(asset => { + if (asset.id === assetId) { + return { + ...asset, + // Update vulnerability data + vulnerabilities: { + cves: apiResponse.cves || [], + cwes: apiResponse.cwes || [], + capecs: apiResponse.capecs || [], + attacks: apiResponse.attacks || [] + }, + statistics: apiResponse.statistics || {}, + scan_time: apiResponse.scan_time || 0, + last_updated: new Date().toISOString(), + // Update device info if provided + ...(apiResponse.device && { + risk_level: apiResponse.device.risk_level, + description: apiResponse.device.description + }) + } + } + return asset + })) + } + + // Add background refresh function + const refreshAllAssets = async () => { + for (const asset of assets) { + try { + // Skip if recently updated (less than 5 minutes ago) + if (asset.last_updated) { + const lastUpdate = new Date(asset.last_updated) + const now = new Date() + const diffMinutes = (now - lastUpdate) / (1000 * 60) + if (diffMinutes < 5) continue + } + + // Use the new scan-by-os endpoint if we have scan parameters + if (asset.scan_params && asset.scan_params.os_family) { + const response = await fetch('http://localhost:8000/api/v1/security/scan-by-os', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + device_name: asset.scan_params.device_name || asset.name, + vendor: asset.scan_params.vendor || asset.vendor, + model: asset.scan_params.model || asset.model, + os_family: asset.scan_params.os_family, + version: asset.scan_params.version || asset.version, + department: asset.department + }) + }) + + if (response.ok) { + const apiResponse = await response.json() + if (apiResponse.success) { + updateAsset(asset.id, apiResponse) + } + } + } + // Fallback to old CPE method if no OS parameters available + else if (asset.cpe) { + const response = await fetch('http://localhost:8000/api/v1/security/scan-by-cpe', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + cpe: asset.cpe, + device_name: asset.name, + department: asset.department + }) + }) + + if (response.ok) { + const apiResponse = await response.json() + if (apiResponse.success) { + updateAsset(asset.id, apiResponse) + } + } + } + } catch (error) { + console.error(`Failed to refresh asset ${asset.name}:`, error) + } + } + } + + // Function to clear all data (useful for testing or reset functionality) + const clearAllData = () => { + setDepartments([]) + setAssets([]) + localStorage.removeItem(DEPARTMENTS_STORAGE_KEY) + localStorage.removeItem(ASSETS_STORAGE_KEY) + } + + // Function to remove a specific department and its assets + const removeDepartment = (departmentName) => { + setDepartments(prev => prev.filter(dept => dept !== departmentName)) + setAssets(prev => prev.filter(asset => asset.department !== departmentName)) + } + + // Function to remove a specific asset + const removeAsset = (assetId) => { + setAssets(prev => prev.filter(asset => asset.id !== assetId)) + } + + const dynamicNavItems = useMemo(() => { + const navItems = [] + + departments.forEach(department => { + const departmentAssets = assets.filter(asset => asset.department === department) + + if (departmentAssets.length > 0) { + navItems.push({ + component: CNavGroup, + name: department, + icon: , + items: departmentAssets.map(asset => ({ + component: CNavItem, + name: asset.name, + to: `/asset/${asset.id}`, + icon: , + })) + }) + } else { + // Show department even if no assets yet + navItems.push({ + component: CNavGroup, + name: department, + icon: , + items: [{ + component: CNavItem, + name: 'No assets yet', + to: '#', + disabled: true, + style: { fontStyle: 'italic', opacity: 0.6 } + }] + }) + } + }) + + return navItems + }, [departments, assets]) + + return { + departments, + assets, + addDepartment, + addAsset, + updateAsset, + refreshAllAssets, + removeDepartment, + removeAsset, + clearAllData, + dynamicNavItems + } +} \ No newline at end of file diff --git a/src/layout/DefaultLayout.js b/src/layout/DefaultLayout.js index 19fbf225f..438a7c510 100644 --- a/src/layout/DefaultLayout.js +++ b/src/layout/DefaultLayout.js @@ -1,3 +1,24 @@ +/*import React from 'react' +import { AppContent, AppSidebar, AppFooter, AppHeader } from '../components/index' + +const DefaultLayout = () => { + + return ( +
    + +
    + +
    + +
    + +
    +
    + ) +} + +export default DefaultLayout*/ + import React from 'react' import { AppContent, AppSidebar, AppFooter, AppHeader } from '../components/index' @@ -16,4 +37,4 @@ const DefaultLayout = () => { ) } -export default DefaultLayout +export default DefaultLayout \ No newline at end of file diff --git a/src/routes.js b/src/routes.js index d2e9d6479..0bfa3fcb7 100644 --- a/src/routes.js +++ b/src/routes.js @@ -1,5 +1,12 @@ +import { element } from 'prop-types' import React from 'react' +// mine +const Overview = React.lazy(() => import('./views/overview/Overview')) +const AssetView = React.lazy(() => import('./views/assets/AssetView')) +const DataManagement = React.lazy(() => import('./components/DataManagement')) + +// pre existent const Dashboard = React.lazy(() => import('./views/dashboard/Dashboard')) const Colors = React.lazy(() => import('./views/theme/colors/Colors')) const Typography = React.lazy(() => import('./views/theme/typography/Typography')) @@ -52,7 +59,13 @@ const Toasts = React.lazy(() => import('./views/notifications/toasts/Toasts')) const Widgets = React.lazy(() => import('./views/widgets/Widgets')) const routes = [ - { path: '/', exact: true, name: 'Home' }, + { path: '/', name: 'Overview', element: Overview }, + // mine + { path: '/overview', name: 'Overview', element: Overview}, + { path: '/asset/:assetId', name: 'Asset', element: AssetView }, + { path: '/datamanagement', name: 'Data Management', element: DataManagement }, + + // pre existent { path: '/dashboard', name: 'Dashboard', element: Dashboard }, { path: '/theme', name: 'Theme', element: Colors, exact: true }, { path: '/theme/colors', name: 'Colors', element: Colors }, @@ -99,4 +112,4 @@ const routes = [ { path: '/widgets', name: 'Widgets', element: Widgets }, ] -export default routes +export default routes \ No newline at end of file diff --git a/src/scss/style.scss b/src/scss/style.scss index 6b8f2b2e5..fe1e843a2 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -65,3 +65,230 @@ body { --cui-footer-bg: var(--cui-body-bg); } } + +// Cybersecurity Theme Enhancements +:root { + --cybersec-primary: #0066cc; + --cybersec-danger: #dc3545; + --cybersec-warning: #fd7e14; + --cybersec-success: #28a745; + --cybersec-dark: #1a1a1a; + --cybersec-light: #f8f9fa; +} + +// Header enhancements +.header { + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + + .navbar-nav .nav-link { + transition: all 0.2s ease; + + &:hover { + background-color: rgba(0, 102, 204, 0.1); + border-radius: 4px; + } + } +} + +// Security status bar styling +.security-status-bar { + background: linear-gradient(135deg, rgba(0, 102, 204, 0.1) 0%, rgba(0, 102, 204, 0.05) 100%); + border-bottom: 1px solid rgba(0, 102, 204, 0.2); +} + +// Sidebar enhancements +.sidebar { + .sidebar-brand { + background: linear-gradient(135deg, #0066cc 0%, #004499 100%); + + .sidebar-brand-full { + font-weight: 600; + letter-spacing: 0.5px; + } + } + + // Security metrics section + .security-metrics-header { + background: rgba(0, 102, 204, 0.1); + backdrop-filter: blur(10px); + + .metric-item { + transition: transform 0.2s ease; + + &:hover { + transform: translateY(-1px); + } + } + } + + // Navigation enhancements + .nav-link { + transition: all 0.2s ease; + + &:hover { + background-color: rgba(255, 255, 255, 0.1); + transform: translateX(2px); + } + + &.active { + background-color: rgba(0, 102, 204, 0.2); + border-left: 3px solid var(--cybersec-primary); + } + } +} + +// Footer enhancements +.footer { + border-top: 1px solid rgba(0, 102, 204, 0.2); + background: linear-gradient(135deg, rgba(248, 249, 250, 0.9) 0%, rgba(248, 249, 250, 0.95) 100%); + backdrop-filter: blur(10px); + + .security-status-indicator { + padding: 4px 8px; + border-radius: 12px; + font-size: 0.75rem; + font-weight: 500; + } +} + +// Security badge animations +.security-badge { + animation: pulse 2s infinite; + + &.critical { + animation: pulse-danger 1.5s infinite; + } +} + +@keyframes pulse { + 0% { opacity: 1; } + 50% { opacity: 0.7; } + 100% { opacity: 1; } +} + +@keyframes pulse-danger { + 0% { + opacity: 1; + box-shadow: 0 0 0 0 rgba(220, 53, 69, 0.7); + } + 50% { + opacity: 0.8; + box-shadow: 0 0 0 4px rgba(220, 53, 69, 0.1); + } + 100% { + opacity: 1; + box-shadow: 0 0 0 0 rgba(220, 53, 69, 0); + } +} + +// Security alert styling +.security-alert { + border-left: 4px solid var(--cybersec-danger); + background: linear-gradient(135deg, rgba(220, 53, 69, 0.1) 0%, rgba(220, 53, 69, 0.05) 100%); + + &.warning { + border-left-color: var(--cybersec-warning); + background: linear-gradient(135deg, rgba(253, 126, 20, 0.1) 0%, rgba(253, 126, 20, 0.05) 100%); + } + + &.success { + border-left-color: var(--cybersec-success); + background: linear-gradient(135deg, rgba(40, 167, 69, 0.1) 0%, rgba(40, 167, 69, 0.05) 100%); + } +} + +// Vulnerability severity indicators +.vuln-severity { + &.critical { + background: linear-gradient(135deg, #dc3545 0%, #c82333 100%); + color: white; + font-weight: 600; + } + + &.high { + background: linear-gradient(135deg, #fd7e14 0%, #e55a00 100%); + color: white; + font-weight: 600; + } + + &.medium { + background: linear-gradient(135deg, #ffc107 0%, #e0a800 100%); + color: #212529; + font-weight: 600; + } + + &.low { + background: linear-gradient(135deg, #28a745 0%, #1e7e34 100%); + color: white; + font-weight: 600; + } +} + +// Dark mode adjustments +@include color-mode(dark) { + .security-status-bar { + background: linear-gradient(135deg, rgba(0, 102, 204, 0.2) 0%, rgba(0, 102, 204, 0.1) 100%); + border-bottom-color: rgba(0, 102, 204, 0.3); + } + + .footer { + background: linear-gradient(135deg, rgba(33, 37, 41, 0.9) 0%, rgba(33, 37, 41, 0.95) 100%); + } + + .security-alert { + &.critical { + background: linear-gradient(135deg, rgba(220, 53, 69, 0.2) 0%, rgba(220, 53, 69, 0.1) 100%); + } + + &.warning { + background: linear-gradient(135deg, rgba(253, 126, 20, 0.2) 0%, rgba(253, 126, 20, 0.1) 100%); + } + + &.success { + background: linear-gradient(135deg, rgba(40, 167, 69, 0.2) 0%, rgba(40, 167, 69, 0.1) 100%); + } + } +} + +// Responsive adjustments +@media (max-width: 768px) { + .header .security-status-bar { + .d-flex { + flex-direction: column; + gap: 0.5rem; + + .d-flex { + flex-direction: row; + } + } + } + + .sidebar .security-metrics-header { + .row { + margin: 0; + + .col-6 { + padding: 0.25rem; + } + } + } +} + +// Loading states +.loading-skeleton { + background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); + background-size: 200% 100%; + animation: loading 1.5s infinite; +} + +@keyframes loading { + 0% { background-position: 200% 0; } + 100% { background-position: -200% 0; } +} + +@include color-mode(dark) { + .loading-skeleton { + background: linear-gradient(90deg, #2a2a2a 25%, #3a3a3a 50%, #2a2a2a 75%); + background-size: 200% 100%; + } +} diff --git a/src/views/assets/AssetView.js b/src/views/assets/AssetView.js new file mode 100644 index 000000000..6750a9a7c --- /dev/null +++ b/src/views/assets/AssetView.js @@ -0,0 +1,735 @@ +import React, { useState, useEffect, useMemo } from 'react' +import { useParams, useNavigate } from 'react-router-dom' +import { + CCard, + CCardBody, + CCardHeader, + CButton, + CBadge, + CContainer, + CRow, + CCol, + CListGroup, + CListGroupItem, + CFormInput, + CTabs, + CTabList, + CTab, + CTabContent, + CTabPanel, + CAlert +} from '@coreui/react' +import { useNavigation } from '../../hooks/useNavigation' + +const AssetDetail = () => { + const { assetId } = useParams() + const navigate = useNavigate() + const { assets } = useNavigation() + const [expandedItems, setExpandedItems] = useState({}) + + const asset = assets.find(a => a.id === assetId) + + // Generate the same random data as in Overview (consistent based on asset ID) + const enrichedAsset = useMemo(() => { + if (!asset) return null + + // Check if we have vulnerability data + const hasVulnerabilities = asset.vulnerabilities && ( + (asset.vulnerabilities.cves && asset.vulnerabilities.cves.length > 0) || + (asset.vulnerabilities.cwes && asset.vulnerabilities.cwes.length > 0) || + (asset.vulnerabilities.capecs && asset.vulnerabilities.capecs.length > 0) || + (asset.vulnerabilities.attacks && asset.vulnerabilities.attacks.length > 0) + ) + + // Generate consistent mock network data based on asset ID + const seed = asset.id.split('').reduce((a, b) => a + b.charCodeAt(0), 0) + const serialNumber = `SN${(seed % 999999).toString().padStart(6, '0')}` + const ipAddress = `192.168.${(seed % 255) + 1}.${((seed * 7) % 255) + 1}` + const macAddress = `00:${((seed % 255).toString(16).padStart(2, '0'))}:${(((seed * 3) % 255).toString(16).padStart(2, '0'))}:${(((seed * 5) % 255).toString(16).padStart(2, '0'))}:${(((seed * 7) % 255).toString(16).padStart(2, '0'))}:${(((seed * 11) % 255).toString(16).padStart(2, '0'))}` + const lastScanDate = asset.last_updated ? new Date(asset.last_updated).toLocaleDateString() : new Date().toLocaleDateString() + const osVersion = asset.version || `${asset.vendor || 'Generic'} OS v${(seed % 5) + 1}.${(seed % 10)}` + + return { + ...asset, + hasVulnerabilities, + serialNumber, + ipAddress, + macAddress, + lastScanDate, + osVersion, + // Calculate risk level based on vulnerabilities if they exist + riskLevel: hasVulnerabilities && asset.vulnerabilities.cves?.length > 0 + ? (() => { + // Fix: Use risk_level instead of riskLevel + const maxRiskLevel = Math.max(...asset.vulnerabilities.cves.map(cve => cve.risk_level || cve.cvss || 0)) + if (maxRiskLevel >= 9.0) return 'Critical' + if (maxRiskLevel >= 7.0) return 'High' + if (maxRiskLevel >= 4.0) return 'Medium' + return 'Low' + })() + : 'Secure' + } + }, [asset]) + + const [activeTab, setActiveTab] = useState('cves') + useEffect(() => { + if(!enrichedAsset) return + const defaultTab = enrichedAsset.vulnerabilities.cves?.length ? 'cves' + : enrichedAsset.vulnerabilities.cwes?.length ? 'cwes' + : enrichedAsset.vulnerabilities.capecs?.length ? 'capecs' + : enrichedAsset.vulnerabilities.attacks?.length ? 'attacks' + : 'cves' + setActiveTab(defaultTab) + }, [enrichedAsset]) + + const [editableFields, setEditableFields] = useState({ + ipAddress: enrichedAsset?.ipAddress || '', + macAddress: enrichedAsset?.macAddress || '', + osVersion: enrichedAsset?.osVersion || '' + }) + const [isEditing, setIsEditing] = useState(false) + + useEffect(() => { + if (enrichedAsset) { + setEditableFields({ + ipAddress: enrichedAsset.ipAddress, + macAddress: enrichedAsset.macAddress, + osVersion: enrichedAsset.osVersion + }) + } + }, [enrichedAsset]) + + const toggleItemExpansion = (type, index) => { + const key = `${type}-${index}` + setExpandedItems(prev => ({ + ...prev, + [key]: !prev[key] + })) + } + + if (!enrichedAsset) { + return ( + + + + + +
    +

    Asset Not Found

    + navigate('/overview')} + > + Back to Overview + +
    +
    + + The requested asset could not be found. + +
    +
    +
    +
    + ) + } + + const getRiskBadgeVariant = (riskLevel) => { + switch (riskLevel) { + case 'Low': return 'success' + case 'Medium': return 'warning' + case 'High': return 'danger' + case 'Critical': return 'dark' + default: return 'secondary' + } + } + + return ( + + + + + +
    +
    +
    +

    {enrichedAsset.name}

    + {enrichedAsset.hasVulnerabilities ? ( + + ⚠️ Vulnerabilities Found + + ) : ( + + ✅ Secure + + )} +
    +
    + {enrichedAsset.department} + {enrichedAsset.type} +
    +
    + navigate('/overview')} + > + Back to Overview + +
    +
    + + {/* Device Information Section */} + + +
    Device Information
    + + + Name: + {enrichedAsset.name} + + + Vendor: + {enrichedAsset.vendor} + + + Model: + {enrichedAsset.model} + + + Department: + {enrichedAsset.department} + + +
    + + +
    +
    Network & System Info
    + { + if (isEditing) { + // Save logic here if needed + setIsEditing(false) + } else { + setIsEditing(true) + } + }} + > + {isEditing ? "Save" : "Edit"} + +
    + + + + IP Address: + {isEditing ? ( + setEditableFields(prev => ({...prev, ipAddress: e.target.value}))} + style={{ width: '150px' }} + /> + ) : ( + {editableFields.ipAddress} + )} + + + MAC Address: + {isEditing ? ( + setEditableFields(prev => ({...prev, macAddress: e.target.value}))} + style={{ width: '150px' }} + /> + ) : ( + {editableFields.macAddress} + )} + + + OS Version: + {isEditing ? ( + setEditableFields(prev => ({...prev, osVersion: e.target.value}))} + style={{ width: '150px' }} + /> + ) : ( + {editableFields.osVersion} + )} + + + Last Scan: + {enrichedAsset.lastScanDate} + + +
    +
    + + {/* Security Status Section */} + + +
    Security Status
    + {enrichedAsset.hasVulnerabilities ? ( + + ⚠️ Vulnerabilities Found - This device has known security vulnerabilities that require attention. + + ) : ( + + ✅ No Vulnerabilities Found - This device appears to be secure with no known critical vulnerabilities. + + )} +
    +
    + + {/* Vulnerability Details Section - Only show if vulnerabilities exist */} + {enrichedAsset.hasVulnerabilities && ( + + +
    Vulnerability Details
    + setActiveTab(newKey)}> + + {enrichedAsset.vulnerabilities.cves?.length > 0 && ( + + CVEs ({enrichedAsset.vulnerabilities.cves.length}) + + )} + {enrichedAsset.vulnerabilities.cwes?.length > 0 && ( + + CWEs ({enrichedAsset.vulnerabilities.cwes.length}) + + )} + {enrichedAsset.vulnerabilities.capecs?.length > 0 && ( + + CAPECs ({enrichedAsset.vulnerabilities.capecs.length}) + + )} + {enrichedAsset.vulnerabilities.attacks?.length > 0 && ( + + ATT&CK ({enrichedAsset.vulnerabilities.attacks.length}) + + )} + + + + {enrichedAsset.vulnerabilities.cves?.length > 0 && ( + +
    + + {enrichedAsset.vulnerabilities.cves.map((cve, index) => { + const isExpanded = expandedItems[`cve-${index}`] + return ( + +
    toggleItemExpansion('cve', index)} + style={{ cursor: 'pointer' }} + > +
    +
    + {cve.cve_id} + + {/* CVSS Score Badge */} + = 9.0 ? 'danger' : + cve.cvss >= 7.0 ? 'warning' : + cve.cvss >= 4.0 ? 'info' : 'secondary' + } + > + CVSS: {cve.cvss || 'N/A'} + + + {/* Risk Level Badge - Fix the field name */} + {(cve.risk_level !== undefined && cve.risk_level !== null) && ( + + Risk: {cve.risk_level.toFixed(2)} + + )} + + {/* EPSS Badge */} + {(cve.epss !== undefined && cve.epss !== null) && ( + + EPSS: {(cve.epss * 100).toFixed(2)}% + + )} + + {/* Impact Score Badge - Fix the field name */} + {(cve.impact_score !== undefined && cve.impact_score !== null) && ( + + Impact: {cve.impact_score.toFixed(2)} + + )} + + {/* Exploitability Score Badge - Add this missing field */} + {(cve.exploitability_score !== undefined && cve.exploitability_score !== null) && ( + + Exploit: {cve.exploitability_score.toFixed(2)} + + )} + + + {isExpanded ? 'Less' : 'More'} + +
    + +

    + {isExpanded + ? cve.description + : `${cve.description?.substring(0, 200)}${cve.description?.length > 200 ? '...' : ''}` + } +

    + + {isExpanded && ( +
    + + + CVE ID: {cve.cve_id}
    + CVSS Score: {cve.cvss || 'N/A'}
    + {/* Fix field names to match backend */} + Risk Level: {cve.risk_level?.toFixed(2) || 'N/A'}
    + EPSS: {cve.epss ? `${(cve.epss * 100).toFixed(2)}%` : 'N/A'}
    + Impact Score: {cve.impact_score?.toFixed(2) || 'N/A'}
    + Exploitability Score: {cve.exploitability_score?.toFixed(2) || 'N/A'}
    + + {cve.cvss_vector && ( + <> + CVSS Vector: {cve.cvss_vector}
    + + )} +
    + + {cve.published_date && ( + <> + Published: {new Date(cve.published_date).toLocaleDateString()}
    + + )} + {cve.last_modified_date && ( + <> + Last Modified: {new Date(cve.last_modified_date).toLocaleDateString()}
    + + )} + {cve.source && ( + <> + Source: {cve.source}
    + + )} +
    +
    + {cve.references && cve.references.length > 0 && ( +
    + References: + +
    + )} +
    + )} +
    +
    +
    + ) + })} +
    +
    +
    + )} + + {enrichedAsset.vulnerabilities.cwes?.length > 0 && ( + +
    + + {enrichedAsset.vulnerabilities.cwes.map((cwe, index) => { + const isExpanded = expandedItems[`cwe-${index}`] + return ( + +
    toggleItemExpansion('cwe', index)} + style={{ cursor: 'pointer' }} + > +
    + {cwe.cwe_id} + {cwe.name} + + {isExpanded ? 'Less' : 'More'} + +
    +

    + {isExpanded + ? cwe.description + : `${cwe.description?.substring(0, 150)}${cwe.description?.length > 150 ? '...' : ''}` + } +

    + + {isExpanded && ( +
    + CWE ID: {cwe.cwe_id}
    + Name: {cwe.name}
    + {cwe.weakness_abstraction && ( + <> + Abstraction: {cwe.weakness_abstraction}
    + + )} + {cwe.status && ( + <> + Status: {cwe.status}
    + + )} +
    + )} +
    +
    + ) + })} +
    +
    +
    + )} + + {enrichedAsset.vulnerabilities.capecs?.length > 0 && ( + +
    + + {enrichedAsset.vulnerabilities.capecs.map((capec, index) => { + const isExpanded = expandedItems[`capec-${index}`] + return ( + +
    toggleItemExpansion('capec', index)} + style={{ cursor: 'pointer' }} + > +
    +
    +
    + {capec.capec_id} + {capec.name} + + {isExpanded ? 'Less' : 'More'} + +
    +
    + + Severity: {capec.typical_severity || 'Unknown'} + + + Likelihood: {capec.likelihood_of_attack || 'Unknown'} + +
    +

    + {isExpanded + ? capec.description + : `${capec.description?.substring(0, 150)}${capec.description?.length > 150 ? '...' : ''}` + } +

    + + {isExpanded && ( +
    + + + CAPEC ID: {capec.capec_id}
    + Name: {capec.name}
    + Abstraction: {capec.abstraction || 'N/A'}
    +
    + + Severity: {capec.typical_severity || 'Unknown'}
    + Likelihood: {capec.likelihood_of_attack || 'Unknown'}
    + Status: {capec.status || 'N/A'}
    +
    +
    +
    + )} +
    +
    +
    +
    + ) + })} +
    +
    +
    + )} + + {enrichedAsset.vulnerabilities.attacks?.length > 0 && ( + +
    + + {enrichedAsset.vulnerabilities.attacks.map((attack, index) => { + const isExpanded = expandedItems[`attack-${index}`] + return ( + +
    toggleItemExpansion('attack', index)} + style={{ cursor: 'pointer' }} + > +
    +
    +
    + {attack.technique_id} + {attack.name} + + {isExpanded ? 'Less' : 'More'} + +
    +
    + {attack.tactics && ( + + {attack.tactics} + + )} + {attack.platforms && ( + + {attack.platforms.length > 20 ? attack.platforms.substring(0, 20) + '...' : attack.platforms} + + )} +
    +

    + {isExpanded + ? attack.description + : `${attack.description?.substring(0, 150)}${attack.description?.length > 150 ? '...' : ''}` + } +

    + + {isExpanded && ( +
    + + + Technique ID: {attack.technique_id}
    + Name: {attack.name}
    + Tactics: {attack.tactics || 'N/A'}
    +
    + + Platforms: {attack.platforms || 'N/A'}
    + {attack.data_sources && ( + <> + Data Sources: {attack.data_sources}
    + + )} + {attack.detection && ( + <> + Detection: {attack.detection.substring(0, 100)}...
    + + )} +
    +
    +
    + )} +
    +
    +
    +
    + ) + })} +
    +
    +
    + )} +
    +
    +
    +
    + )} + + {/* Summary Section */} + + +
    + {enrichedAsset.hasVulnerabilities ? ( + + Action Required: This device has { + (enrichedAsset.vulnerabilities.cves?.length || 0) + + (enrichedAsset.vulnerabilities.cwes?.length || 0) + + (enrichedAsset.vulnerabilities.capecs?.length || 0) + + (enrichedAsset.vulnerabilities.attacks?.length || 0) + } security findings that should be reviewed and addressed. + + ) : ( + + All Clear: No vulnerable CVEs were found for this device. Continue monitoring for new threats. + + )} +
    +
    +
    +
    +
    +
    + {enrichedAsset.vulnerabilities && enrichedAsset.vulnerabilities.cves.length > 0 && ( + <> +
    + + +
    Vulnerability Details
    +
    +
    + + + +
    CVEs ({enrichedAsset.vulnerabilities.cves.length})
    + + {enrichedAsset.vulnerabilities.cves.slice(0, 10).map((cve, index) => ( + +
    +
    + {cve.cve_id} +
    + {cve.description?.substring(0, 100)}... +
    +
    +
    + = 7 ? 'danger' : cve.cvss >= 4 ? 'warning' : 'info'}> + CVSS: {cve.cvss} + +
    +
    +
    + ))} + {enrichedAsset.vulnerabilities.cves.length > 10 && ( + + ... and {enrichedAsset.vulnerabilities.cves.length - 10} more + + )} +
    +
    + + +
    Attack Patterns ({enrichedAsset.vulnerabilities.capecs?.length || 0})
    + + {enrichedAsset.vulnerabilities.capecs?.slice(0, 5).map((capec, index) => ( + +
    + {capec.capec_id}: {capec.name} +
    + Severity: {capec.typical_severity} +
    +
    +
    + )) || No attack patterns found} +
    +
    +
    + + )} +
    + ) +} + +export default AssetDetail \ No newline at end of file diff --git a/src/views/overview/Overview.js b/src/views/overview/Overview.js new file mode 100644 index 000000000..915ec1aef --- /dev/null +++ b/src/views/overview/Overview.js @@ -0,0 +1,375 @@ +import React, { useMemo } from 'react' +import { useNavigate } from 'react-router-dom' +import { + CCard, + CCardBody, + CCardHeader, + CTable, + CTableHead, + CTableRow, + CTableHeaderCell, + CTableBody, + CTableDataCell, + CButton, + CBadge, + CContainer, + CRow, + CCol, + CAlert, + CListGroup, + CListGroupItem, + CCollapse +} from '@coreui/react' +import { useNavigation } from '../../hooks/useNavigation' + +const Overview = () => { + const navigate = useNavigate() + const { assets, departments } = useNavigation() + const [expandedAssets, setExpandedAssets] = React.useState({}) + + // Function to determine risk level based on CVSS score + const getCVSSRiskLevel = (riskLevel) => { + if (riskLevel >= 9.0) return 'Critical' + if (riskLevel >= 7.0) return 'High' + if (riskLevel >= 4.0) return 'Medium' + if (riskLevel >= 0.1) return 'Low' + return 'None' + } + + // Function to get badge color based on risk level + const getRiskBadgeColor = (riskLevel) => { + switch (riskLevel) { + case 'Critical': return 'danger' + case 'High': return 'warning' + case 'Medium': return 'info' + case 'Low': return 'success' + default: return 'secondary' + } + } + + // Function to get CVSS badge color + const getCVSSBadgeColor = (riskLevel) => { + if (riskLevel >= 9.0) return 'danger' + if (riskLevel >= 7.0) return 'warning' + if (riskLevel >= 4.0) return 'info' + return 'success' + } + + // Process assets to include vulnerability summary + const processedAssets = useMemo(() => { + return assets.map(asset => { + const cves = asset.vulnerabilities?.cves || [] + const hasCVEs = cves.length > 0 + + // Calculate highest risk level - Fix field names + let highestRisk = 'None' + let maxRiskLevel = 0 + + if (hasCVEs) { + // Fix: Use risk_level instead of riskLevel, fallback to cvss + maxRiskLevel = Math.max(...cves.map(cve => cve.risk_level || cve.cvss || 0)) + highestRisk = getCVSSRiskLevel(maxRiskLevel) + } + + return { + ...asset, + cveCount: cves.length, + hasCVEs, + highestRisk, + maxRiskLevel, + // Fix: Sort by risk_level instead of riskLevel + cves: cves.sort((a, b) => (b.risk_level || b.cvss || 0) - (a.risk_level || a.cvss || 0)) + } + }) + }, [assets]) + + // Calculate summary statistics + const summary = useMemo(() => { + const total = processedAssets.length + const vulnerable = processedAssets.filter(asset => asset.hasCVEs).length + const safe = total - vulnerable + const critical = processedAssets.filter(asset => asset.highestRisk === 'Critical').length + const high = processedAssets.filter(asset => asset.highestRisk === 'High').length + const medium = processedAssets.filter(asset => asset.highestRisk === 'Medium').length + const low = processedAssets.filter(asset => asset.highestRisk === 'Low').length + + return { total, vulnerable, safe, critical, high, medium, low } + }, [processedAssets]) + + const handleViewAsset = (assetId) => { + navigate(`/asset/${assetId}`) + } + + const toggleAssetExpansion = (assetId) => { + setExpandedAssets(prev => ({ + ...prev, + [assetId]: !prev[assetId] + })) + } +/* + const handleReset = () => { + localStorage.clear() + sessionStorage.clear() + window.location.reload() + } +*/ + return ( + + {/* Summary Dashboard */} + + + + +
    {summary.total}
    +
    Total Assets
    +
    +
    +
    + + + +
    {summary.safe}
    +
    Safe Assets
    +
    +
    +
    + + + +
    {summary.vulnerable}
    +
    Vulnerable
    +
    +
    +
    + + + +
    {summary.critical + summary.high}
    +
    Critical/High
    +
    +
    +
    + + + +
    {summary.medium}
    +
    Medium Risk
    +
    +
    +
    + + + +
    {summary.low}
    +
    Low Risk
    +
    +
    +
    +
    + + {/* Main Assets Table */} + + + + +
    +

    Security Dashboard - Asset Overview

    + + {summary.total} Total Assets + +
    +
    + + {processedAssets.length === 0 ? ( +
    +
    No Assets Found
    +

    Start by adding departments and assets using the sidebar buttons.

    +
    + ) : ( + <> + {/* High Priority Alerts */} + {summary.critical > 0 && ( + + 🚨 Critical Alert: {summary.critical} asset(s) have CRITICAL vulnerabilities that require immediate attention! + + )} + {summary.high > 0 && ( + + ⚠️ High Priority: {summary.high} asset(s) have HIGH risk vulnerabilities. + + )} + + + + + Asset Details + Department + Security Status + Vulnerability Count + Risk Level + Actions + + + + {processedAssets.map((asset) => ( + + + +
    + {asset.name} +
    + + {asset.vendor} {asset.model && `- ${asset.model}`} + +
    +
    + + + {asset.department} + + + + {asset.hasCVEs ? ( + + 🔓 Vulnerable + + ) : ( + + 🔒 Secure + + )} + + + {asset.hasCVEs ? ( +
    + + {asset.cveCount} CVEs + + {asset.cveCount > 0 && ( + toggleAssetExpansion(asset.id)} + > + {expandedAssets[asset.id] ? 'Hide Details' : 'Show Details'} + + )} +
    + ) : ( + No CVEs + )} +
    + + + {asset.highestRisk} + + {asset.maxRiskLevel > 0 && ( + + (CVSS: {asset.maxRiskLevel}) + + )} + + + handleViewAsset(asset.id)} + > + Full Details + + +
    +{/* + + Reset App + +*/} + {/* Expandable CVE Details */} + {asset.hasCVEs && ( + + + +
    +
    CVE Details for {asset.name}:
    + + {asset.cves.slice(0, 10).map((cve, index) => ( + +
    +
    + {cve.cve_id} + + {/* CVSS Badge */} + + CVSS: {cve.cvss || 'N/A'} + + + {/* Risk Level Badge - Fix field name */} + {(cve.risk_level !== undefined && cve.risk_level !== null) && ( + + Risk: {cve.risk_level.toFixed(2)} + + )} + + {/* EPSS Badge */} + {(cve.epss !== undefined && cve.epss !== null) && ( + + EPSS: {(cve.epss * 100).toFixed(2)}% + + )} + + {/* Impact Score Badge */} + {(cve.impact_score !== undefined && cve.impact_score !== null) && ( + + Impact: {cve.impact_score.toFixed(2)} + + )} + + {/* Exploitability Score Badge - Add this missing field */} + {(cve.exploitability_score !== undefined && cve.exploitability_score !== null) && ( + + Exploit: {cve.exploitability_score.toFixed(2)} + + )} + + {/* Overall Risk Level Badge */} + + {getCVSSRiskLevel(cve.risk_level || cve.cvss)} + +
    + + {/* Description */} +
    + {cve.description?.substring(0, 150)} + {cve.description?.length > 150 ? '...' : ''} +
    +
    +
    + ))} + {asset.cves.length > 10 && ( + + ... and {asset.cves.length - 10} more CVEs + + )} +
    +
    +
    +
    +
    + )} +
    + ))} +
    +
    + + )} +
    +
    +
    +
    +
    + ) +} + +export default Overview \ No newline at end of file