From f63fc58e2caa7ac2660fc69cc25a82b0682f3bcb Mon Sep 17 00:00:00 2001 From: IAmTryingIPromise Date: Sat, 31 May 2025 22:50:45 +0300 Subject: [PATCH 01/15] edit1 --- src/_nav.js | 20 ++ src/components/AppSidebar.js | 2 +- src/routes.js | 10 + src/views/overview/MainChart.js | 133 ++++++++++++ src/views/overview/Overview.js | 356 ++++++++++++++++++++++++++++++++ 5 files changed, 520 insertions(+), 1 deletion(-) create mode 100644 src/views/overview/MainChart.js create mode 100644 src/views/overview/Overview.js diff --git a/src/_nav.js b/src/_nav.js index 9f8ca150b..354240336 100644 --- a/src/_nav.js +++ b/src/_nav.js @@ -1,6 +1,10 @@ import React from 'react' import CIcon from '@coreui/icons-react' import { + //mine + cilHome, + + // pre existent cilBell, cilCalculator, cilChartPie, @@ -17,6 +21,22 @@ import { import { CNavGroup, CNavItem, CNavTitle } from '@coreui/react' const _nav = [ + + // mine + { + component: CNavTitle, + name: 'Overview', + }, + + { + component: CNavItem, + name: 'Overview', + to: '/overview', + icon: , + }, + + + // pre existent { component: CNavItem, name: 'Dashboard', diff --git a/src/components/AppSidebar.js b/src/components/AppSidebar.js index 021cb52c3..0b680800c 100644 --- a/src/components/AppSidebar.js +++ b/src/components/AppSidebar.js @@ -37,7 +37,7 @@ const AppSidebar = () => { > - + import('./views/overview/Overview')) + + +// 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')) @@ -53,6 +59,10 @@ const Widgets = React.lazy(() => import('./views/widgets/Widgets')) const routes = [ { path: '/', exact: true, name: 'Home' }, + // mine + { path: '/overview', name: 'Overview', element: Overview}, + + // pre existent { path: '/dashboard', name: 'Dashboard', element: Dashboard }, { path: '/theme', name: 'Theme', element: Colors, exact: true }, { path: '/theme/colors', name: 'Colors', element: Colors }, diff --git a/src/views/overview/MainChart.js b/src/views/overview/MainChart.js new file mode 100644 index 000000000..922c0d021 --- /dev/null +++ b/src/views/overview/MainChart.js @@ -0,0 +1,133 @@ +import React, { useEffect, useRef } from 'react' + +import { CChartLine } from '@coreui/react-chartjs' +import { getStyle } from '@coreui/utils' + +const MainChart = () => { + const chartRef = useRef(null) + + useEffect(() => { + document.documentElement.addEventListener('ColorSchemeChange', () => { + if (chartRef.current) { + setTimeout(() => { + chartRef.current.options.scales.x.grid.borderColor = getStyle( + '--cui-border-color-translucent', + ) + chartRef.current.options.scales.x.grid.color = getStyle('--cui-border-color-translucent') + chartRef.current.options.scales.x.ticks.color = getStyle('--cui-body-color') + chartRef.current.options.scales.y.grid.borderColor = getStyle( + '--cui-border-color-translucent', + ) + chartRef.current.options.scales.y.grid.color = getStyle('--cui-border-color-translucent') + chartRef.current.options.scales.y.ticks.color = getStyle('--cui-body-color') + chartRef.current.update() + }) + } + }) + }, [chartRef]) + + const random = () => Math.round(Math.random() * 100) + + return ( + <> + + + ) +} + +export default MainChart diff --git a/src/views/overview/Overview.js b/src/views/overview/Overview.js new file mode 100644 index 000000000..76d51b5f9 --- /dev/null +++ b/src/views/overview/Overview.js @@ -0,0 +1,356 @@ +import React from 'react' +import classNames from 'classnames' + +import { + CAvatar, + CButton, + CButtonGroup, + CCard, + CCardBody, + CCardFooter, + CCardHeader, + CCol, + CProgress, + CRow, + CTable, + CTableBody, + CTableDataCell, + CTableHead, + CTableHeaderCell, + CTableRow, +} from '@coreui/react' +import CIcon from '@coreui/icons-react' +import { + cibCcAmex, + cibCcApplePay, + cibCcMastercard, + cibCcPaypal, + cibCcStripe, + cibCcVisa, + cibGoogle, + cibFacebook, + cibLinkedin, + cifBr, + cifEs, + cifFr, + cifIn, + cifPl, + cifUs, + cibTwitter, + cilCloudDownload, + cilPeople, + cilUser, + cilUserFemale, +} from '@coreui/icons' + +import avatar1 from 'src/assets/images/avatars/1.jpg' +import avatar2 from 'src/assets/images/avatars/2.jpg' +import avatar3 from 'src/assets/images/avatars/3.jpg' +import avatar4 from 'src/assets/images/avatars/4.jpg' +import avatar5 from 'src/assets/images/avatars/5.jpg' +import avatar6 from 'src/assets/images/avatars/6.jpg' + +import WidgetsBrand from '../widgets/WidgetsBrand' +import WidgetsDropdown from '../widgets/WidgetsDropdown' +import MainChart from './MainChart' + +const Overview = () => { + + const progressGroupExample1 = [ + { title: 'Monday', value1: 34, value2: 78 }, + { title: 'Tuesday', value1: 56, value2: 94 }, + { title: 'Wednesday', value1: 12, value2: 67 }, + { title: 'Thursday', value1: 43, value2: 91 }, + { title: 'Friday', value1: 22, value2: 73 }, + { title: 'Saturday', value1: 53, value2: 82 }, + { title: 'Sunday', value1: 9, value2: 69 }, + ] + + const progressGroupExample2 = [ + { title: 'Male', icon: cilUser, value: 53 }, + { title: 'Female', icon: cilUserFemale, value: 43 }, + ] + + const progressGroupExample3 = [ + { title: 'Organic Search', icon: cibGoogle, percent: 56, value: '191,235' }, + { title: 'Facebook', icon: cibFacebook, percent: 15, value: '51,223' }, + { title: 'Twitter', icon: cibTwitter, percent: 11, value: '37,564' }, + { title: 'LinkedIn', icon: cibLinkedin, percent: 8, value: '27,319' }, + ] + + const tableExample = [ + { + avatar: { src: avatar1, status: 'success' }, + user: { + name: 'Yiorgos Avraamu', + new: true, + registered: 'Jan 1, 2023', + }, + country: { name: 'USA', flag: cifUs }, + usage: { + value: 50, + period: 'Jun 11, 2023 - Jul 10, 2023', + color: 'success', + }, + payment: { name: 'Mastercard', icon: cibCcMastercard }, + activity: '10 sec ago', + }, + { + avatar: { src: avatar2, status: 'danger' }, + user: { + name: 'Avram Tarasios', + new: false, + registered: 'Jan 1, 2023', + }, + country: { name: 'Brazil', flag: cifBr }, + usage: { + value: 22, + period: 'Jun 11, 2023 - Jul 10, 2023', + color: 'info', + }, + payment: { name: 'Visa', icon: cibCcVisa }, + activity: '5 minutes ago', + }, + { + avatar: { src: avatar3, status: 'warning' }, + user: { name: 'Quintin Ed', new: true, registered: 'Jan 1, 2023' }, + country: { name: 'India', flag: cifIn }, + usage: { + value: 74, + period: 'Jun 11, 2023 - Jul 10, 2023', + color: 'warning', + }, + payment: { name: 'Stripe', icon: cibCcStripe }, + activity: '1 hour ago', + }, + { + avatar: { src: avatar4, status: 'secondary' }, + user: { name: 'Enéas Kwadwo', new: true, registered: 'Jan 1, 2023' }, + country: { name: 'France', flag: cifFr }, + usage: { + value: 98, + period: 'Jun 11, 2023 - Jul 10, 2023', + color: 'danger', + }, + payment: { name: 'PayPal', icon: cibCcPaypal }, + activity: 'Last month', + }, + { + avatar: { src: avatar5, status: 'success' }, + user: { + name: 'Agapetus Tadeáš', + new: true, + registered: 'Jan 1, 2023', + }, + country: { name: 'Spain', flag: cifEs }, + usage: { + value: 22, + period: 'Jun 11, 2023 - Jul 10, 2023', + color: 'primary', + }, + payment: { name: 'Google Wallet', icon: cibCcApplePay }, + activity: 'Last week', + }, + { + avatar: { src: avatar6, status: 'danger' }, + user: { + name: 'Friderik Dávid', + new: true, + registered: 'Jan 1, 2023', + }, + country: { name: 'Poland', flag: cifPl }, + usage: { + value: 43, + period: 'Jun 11, 2023 - Jul 10, 2023', + color: 'success', + }, + payment: { name: 'Amex', icon: cibCcAmex }, + activity: 'Last week', + }, + ] + + return ( + <> + + + + + +

+ Traffic +

+
January - July 2023
+
+ + + + + + {['Day', 'Month', 'Year'].map((value) => ( + + {value} + + ))} + + +
+ +
+
+ + + + + Traffic {' & '} Sales + + + + + +
+
New Clients
+
9,123
+
+
+ +
+
+ Recurring Clients +
+
22,643
+
+
+
+
+ {progressGroupExample1.map((item, index) => ( +
+
+ {item.title} +
+
+ + +
+
+ ))} +
+ + + +
+
Pageviews
+
78,623
+
+
+ +
+
Organic
+
49,123
+
+
+
+ +
+ + {progressGroupExample2.map((item, index) => ( +
+
+ + {item.title} + {item.value}% +
+
+ +
+
+ ))} + +
+ + {progressGroupExample3.map((item, index) => ( +
+
+ + {item.title} + + {item.value}{' '} + ({item.percent}%) + +
+
+ +
+
+ ))} +
+
+ +
+ + + + + + + + User + + Country + + Usage + + Payment Method + + Activity + + + + {tableExample.map((item, index) => ( + + + + + +
{item.user.name}
+
+ {item.user.new ? 'New' : 'Recurring'} | Registered:{' '} + {item.user.registered} +
+
+ + + + +
+
{item.usage.value}%
+
+ {item.usage.period} +
+
+ +
+ + + + +
Last login
+
{item.activity}
+
+
+ ))} +
+
+
+
+
+
+ + ) +} + +export default Overview From c34f1aee923dc0ebc5a3081c27dfd68876f6b3c1 Mon Sep 17 00:00:00 2001 From: IAmTryingIPromise Date: Mon, 2 Jun 2025 17:47:17 +0300 Subject: [PATCH 02/15] Overview1 --- src/views/overview/MainChart.js | 133 ---------- src/views/overview/Overview.js | 458 ++++++++++---------------------- 2 files changed, 134 insertions(+), 457 deletions(-) delete mode 100644 src/views/overview/MainChart.js diff --git a/src/views/overview/MainChart.js b/src/views/overview/MainChart.js deleted file mode 100644 index 922c0d021..000000000 --- a/src/views/overview/MainChart.js +++ /dev/null @@ -1,133 +0,0 @@ -import React, { useEffect, useRef } from 'react' - -import { CChartLine } from '@coreui/react-chartjs' -import { getStyle } from '@coreui/utils' - -const MainChart = () => { - const chartRef = useRef(null) - - useEffect(() => { - document.documentElement.addEventListener('ColorSchemeChange', () => { - if (chartRef.current) { - setTimeout(() => { - chartRef.current.options.scales.x.grid.borderColor = getStyle( - '--cui-border-color-translucent', - ) - chartRef.current.options.scales.x.grid.color = getStyle('--cui-border-color-translucent') - chartRef.current.options.scales.x.ticks.color = getStyle('--cui-body-color') - chartRef.current.options.scales.y.grid.borderColor = getStyle( - '--cui-border-color-translucent', - ) - chartRef.current.options.scales.y.grid.color = getStyle('--cui-border-color-translucent') - chartRef.current.options.scales.y.ticks.color = getStyle('--cui-body-color') - chartRef.current.update() - }) - } - }) - }, [chartRef]) - - const random = () => Math.round(Math.random() * 100) - - return ( - <> - - - ) -} - -export default MainChart diff --git a/src/views/overview/Overview.js b/src/views/overview/Overview.js index 76d51b5f9..fa11b6057 100644 --- a/src/views/overview/Overview.js +++ b/src/views/overview/Overview.js @@ -1,16 +1,10 @@ -import React from 'react' +import React, { useState, useEffect } from 'react' import classNames from 'classnames' - import { - CAvatar, - CButton, - CButtonGroup, CCard, CCardBody, - CCardFooter, CCardHeader, CCol, - CProgress, CRow, CTable, CTableBody, @@ -18,333 +12,147 @@ import { CTableHead, CTableHeaderCell, CTableRow, + CBadge, + CSpinner, + CButton } from '@coreui/react' -import CIcon from '@coreui/icons-react' -import { - cibCcAmex, - cibCcApplePay, - cibCcMastercard, - cibCcPaypal, - cibCcStripe, - cibCcVisa, - cibGoogle, - cibFacebook, - cibLinkedin, - cifBr, - cifEs, - cifFr, - cifIn, - cifPl, - cifUs, - cibTwitter, - cilCloudDownload, - cilPeople, - cilUser, - cilUserFemale, -} from '@coreui/icons' - -import avatar1 from 'src/assets/images/avatars/1.jpg' -import avatar2 from 'src/assets/images/avatars/2.jpg' -import avatar3 from 'src/assets/images/avatars/3.jpg' -import avatar4 from 'src/assets/images/avatars/4.jpg' -import avatar5 from 'src/assets/images/avatars/5.jpg' -import avatar6 from 'src/assets/images/avatars/6.jpg' - -import WidgetsBrand from '../widgets/WidgetsBrand' -import WidgetsDropdown from '../widgets/WidgetsDropdown' -import MainChart from './MainChart' +import { useNavigate } from 'react-router-dom' const Overview = () => { - - const progressGroupExample1 = [ - { title: 'Monday', value1: 34, value2: 78 }, - { title: 'Tuesday', value1: 56, value2: 94 }, - { title: 'Wednesday', value1: 12, value2: 67 }, - { title: 'Thursday', value1: 43, value2: 91 }, - { title: 'Friday', value1: 22, value2: 73 }, - { title: 'Saturday', value1: 53, value2: 82 }, - { title: 'Sunday', value1: 9, value2: 69 }, - ] - - const progressGroupExample2 = [ - { title: 'Male', icon: cilUser, value: 53 }, - { title: 'Female', icon: cilUserFemale, value: 43 }, - ] - - const progressGroupExample3 = [ - { title: 'Organic Search', icon: cibGoogle, percent: 56, value: '191,235' }, - { title: 'Facebook', icon: cibFacebook, percent: 15, value: '51,223' }, - { title: 'Twitter', icon: cibTwitter, percent: 11, value: '37,564' }, - { title: 'LinkedIn', icon: cibLinkedin, percent: 8, value: '27,319' }, - ] - - const tableExample = [ - { - avatar: { src: avatar1, status: 'success' }, - user: { - name: 'Yiorgos Avraamu', - new: true, - registered: 'Jan 1, 2023', - }, - country: { name: 'USA', flag: cifUs }, - usage: { - value: 50, - period: 'Jun 11, 2023 - Jul 10, 2023', - color: 'success', - }, - payment: { name: 'Mastercard', icon: cibCcMastercard }, - activity: '10 sec ago', - }, - { - avatar: { src: avatar2, status: 'danger' }, - user: { - name: 'Avram Tarasios', - new: false, - registered: 'Jan 1, 2023', - }, - country: { name: 'Brazil', flag: cifBr }, - usage: { - value: 22, - period: 'Jun 11, 2023 - Jul 10, 2023', - color: 'info', - }, - payment: { name: 'Visa', icon: cibCcVisa }, - activity: '5 minutes ago', - }, - { - avatar: { src: avatar3, status: 'warning' }, - user: { name: 'Quintin Ed', new: true, registered: 'Jan 1, 2023' }, - country: { name: 'India', flag: cifIn }, - usage: { - value: 74, - period: 'Jun 11, 2023 - Jul 10, 2023', - color: 'warning', - }, - payment: { name: 'Stripe', icon: cibCcStripe }, - activity: '1 hour ago', - }, - { - avatar: { src: avatar4, status: 'secondary' }, - user: { name: 'Enéas Kwadwo', new: true, registered: 'Jan 1, 2023' }, - country: { name: 'France', flag: cifFr }, - usage: { - value: 98, - period: 'Jun 11, 2023 - Jul 10, 2023', - color: 'danger', - }, - payment: { name: 'PayPal', icon: cibCcPaypal }, - activity: 'Last month', - }, - { - avatar: { src: avatar5, status: 'success' }, - user: { - name: 'Agapetus Tadeáš', - new: true, - registered: 'Jan 1, 2023', - }, - country: { name: 'Spain', flag: cifEs }, - usage: { - value: 22, - period: 'Jun 11, 2023 - Jul 10, 2023', - color: 'primary', - }, - payment: { name: 'Google Wallet', icon: cibCcApplePay }, - activity: 'Last week', - }, - { - avatar: { src: avatar6, status: 'danger' }, - user: { - name: 'Friderik Dávid', - new: true, - registered: 'Jan 1, 2023', - }, - country: { name: 'Poland', flag: cifPl }, - usage: { - value: 43, - period: 'Jun 11, 2023 - Jul 10, 2023', - color: 'success', - }, - payment: { name: 'Amex', icon: cibCcAmex }, - activity: 'Last week', - }, - ] + const [data, setData] = useState([]) + const [loading, setLoading] = useState(true) + const navgiate = useNavigate() + + // Function to handle navigation to different dashboards + /* + const handleViewDetails = (itemId) => { + switch(item.department) { + case 'Sales': navigate(`/sales-dashboard/${item.id}`); break; + case 'Marketing': navigate(`/marketing-dashboard/${item.id}`); break; + case 'IT': navigate(`/tech-dashboard/${item.id}`); break; + case 'HR': navigate(`/hr-dashboard/${item.id}`); break; + default: navigate(`/general-dashboard/${item.id}`); + } + }*/ + + // Sample data - replace this with your actual data fetching logic + useEffect(() => { + // Simulate API call + const fetchData = async () => { + try { + // Replace this with your actual API call + const sampleData = [ + { id: 1, deviceName: 'Cisco Switch Catalyst', department: 'Sales', numberOfCVEs: 5, riskLevel: 56, info: 'Asset Details' }, + { id: 2, deviceName: 'Cisco Router', department: 'Sales', numberOfCVEs: 14, riskLevel: 40, info: 'Asset Details' }, + { id: 3, deviceName: 'Cisco Switch E4345', department: 'Developement', numberOfCVEs: 2, riskLevel: 90, info: 'Asset Details' }, + { id: 4, deviceName: 'Random Somthing Else', department: 'HR', numberOfCVEs: 8, riskLevel: 12, info: 'Asset Details' }, + { id: 5, deviceName: 'Not The One You Think', department: 'Support', numberOfCVEs: 10, riskLevel: 78, info: 'Asset Details' }, + ] + + // Sort by score in descending order + const sortedData = sampleData.sort((a, b) => b.riskLevel - a.riskLevel) + setData(sortedData) + setLoading(false) + } catch (error) { + console.error('Error fetching data:', error) + setLoading(false) + } + } + + fetchData() + }, []) + + const getCVEBadgeColor = (riskLevel) => { + if (riskLevel >= 12) return 'danger' + if (riskLevel >= 7) return 'warning' + return 'success' + } + + const getRiskLevelBadgeColor = (riskLevel) => { + if (riskLevel >= 90) return 'danger' + if (riskLevel >= 70) return 'warning' + return 'success' + } + + //const getStatusBadgeColor = (status) => { + // return status === 'Active' ? 'success' : 'secondary' + //} + + if (loading) { + return ( + + + + + +

Loading dashboard data...

+
+
+
+
+ ) + } return ( <> - - - - - -

- Traffic -

-
January - July 2023
-
- - - - - - {['Day', 'Month', 'Year'].map((value) => ( - - {value} - - ))} - - -
- -
-
- - + - Traffic {' & '} Sales + + Dashboard Sorted by Score (Descending) + - - - - -
-
New Clients
-
9,123
-
-
- -
-
- Recurring Clients -
-
22,643
-
-
-
-
- {progressGroupExample1.map((item, index) => ( -
-
- {item.title} -
-
- - -
-
- ))} -
- - - -
-
Pageviews
-
78,623
-
-
- -
-
Organic
-
49,123
-
-
-
- -
- - {progressGroupExample2.map((item, index) => ( -
-
- - {item.title} - {item.value}% -
-
- -
-
- ))} - -
- - {progressGroupExample3.map((item, index) => ( -
-
- - {item.title} - - {item.value}{' '} - ({item.percent}%) - -
-
- -
-
- ))} -
-
- -
- - - - - - - - User - - Country - - Usage - - Payment Method - - Activity - - - - {tableExample.map((item, index) => ( - - - - - -
{item.user.name}
-
- {item.user.new ? 'New' : 'Recurring'} | Registered:{' '} - {item.user.registered} -
-
- - - - -
-
{item.usage.value}%
-
- {item.usage.period} -
-
- -
- - - - -
Last login
-
{item.activity}
-
+
+ + + + Device Name + Department + CVE # + Risk Level + Asset Details - ))} - - + + + {data.map((item, index) => ( + + {item.deviceName} + {item.department} + + + {item.numberOfCVEs} + + + + + {item.riskLevel} + + + + handleViewDetails(item.id)} + > + Go to Details + + + + ))} + + +
@@ -354,3 +162,5 @@ const Overview = () => { } export default Overview + +// {index + 1} \ No newline at end of file From 5ce3d200a68452fa32178932a4751983eaff0712 Mon Sep 17 00:00:00 2001 From: IAmTryingIPromise Date: Thu, 26 Jun 2025 18:52:24 +0300 Subject: [PATCH 03/15] added_dynamics_1 added the device and department dynamic vies still dont work --- src/_nav.js | 18 +++ src/components/AppSidebar.js | 250 ++++++++++++++++++++++++++++++++ src/routes.js | 19 ++- src/views/devices/DeviceView.js | 35 +++++ 4 files changed, 321 insertions(+), 1 deletion(-) create mode 100644 src/views/devices/DeviceView.js diff --git a/src/_nav.js b/src/_nav.js index 354240336..8baac1ca9 100644 --- a/src/_nav.js +++ b/src/_nav.js @@ -36,6 +36,8 @@ const _nav = [ }, + + // pre existent { component: CNavItem, @@ -469,3 +471,19 @@ const _nav = [ ] export default _nav + +/* +import React from 'react' +import CIcon from '@coreui/icons-react' +import { cilSpeedometer } from '@coreui/icons' + +const _nav = [ + { + component: 'CNavItem', + name: 'Overview', + to: '/overview', + icon: + } +] + +export default _nav*/ diff --git a/src/components/AppSidebar.js b/src/components/AppSidebar.js index 0b680800c..33affbc95 100644 --- a/src/components/AppSidebar.js +++ b/src/components/AppSidebar.js @@ -57,3 +57,253 @@ const AppSidebar = () => { } export default React.memo(AppSidebar) + +/*import React, { useState } from 'react' +import { useSelector, useDispatch } from 'react-redux' +import { + CSidebar, + CSidebarBrand, + CSidebarNav, + CNavItem, + CNavGroup, + CButton, + CModal, + CModalHeader, + CModalTitle, + CModalBody, + CModalFooter, + CForm, + CFormInput, + CFormLabel, + CFormSelect, + CRow, + CCol +} from '@coreui/react' +import { cilSpeedometer, cilSettings, cilDevices } from '@coreui/icons' +import CIcon from '@coreui/icons-react' + +const AppSidebar = () => { + const dispatch = useDispatch() + const unfoldable = useSelector((state) => state.sidebarUnfoldable) + const sidebarShow = useSelector((state) => state.sidebarShow) + const [departments, setDepartments] = useState([]) + const [showDepartmentModal, setShowDepartmentModal] = useState(false) + const [showDeviceModal, setShowDeviceModal] = useState(false) + const [newDepartmentName, setNewDepartmentName] = useState('') + const [newDeviceName, setNewDeviceName] = useState('') + const [selectedDepartment, setSelectedDepartment] = useState('') + + // Handle adding new department + const handleAddDepartment = () => { + if (newDepartmentName.trim()) { + const newDepartment = { + id: Date.now(), + name: newDepartmentName.trim(), + devices: [] + } + setDepartments([...departments, newDepartment]) + setNewDepartmentName('') + setShowDepartmentModal(false) + } + } + + // Handle adding new device + const handleAddDevice = () => { + if (newDeviceName.trim() && selectedDepartment) { + setDepartments(prevDepartments => + prevDepartments.map(dept => + dept.id === parseInt(selectedDepartment) + ? { + ...dept, + devices: [...dept.devices, { + id: Date.now(), + name: newDeviceName.trim(), + route: `/device/${Date.now()}` + }] + } + : dept + ) + ) + setNewDeviceName('') + setSelectedDepartment('') + setShowDeviceModal(false) + } + } + + // Generate navigation items + const navigation = [ + { + component: CNavItem, + name: 'Overview', + to: '/dashboard', + icon: + }, + // Add departments as nav groups + ...departments.map(department => ({ + component: CNavGroup, + name: department.name, + icon: , + items: department.devices.map(device => ({ + component: CNavItem, + name: device.name, + to: device.route, + icon: + })) + })) + ] + + return ( + <> + { + dispatch({ type: 'set', sidebarShow: visible }) + }} + > + +
+ Your App Name +
+
+ + + //{ Overview Item } + + + Overview + + + //{ Empty Space } +
+ + //{ Action Buttons } +
+ + + setShowDepartmentModal(true)} + > + Add Department + + + + setShowDeviceModal(true)} + > + Add Device + + + +
+ + //{ Dynamic Navigation Groups } + {departments.map(department => ( + + + {department.name} + + } + > + {department.devices.map(device => ( + + + {device.name} + + ))} + + ))} +
+
+ + //{ Add Department Modal } + setShowDepartmentModal(false)}> + + Add New Department + + + +
+ Department Name + setNewDepartmentName(e.target.value)} + placeholder="Enter department name" + /> +
+
+
+ + setShowDepartmentModal(false)}> + Cancel + + + Add Department + + +
+ + //{ Add Device Modal } + setShowDeviceModal(false)}> + + Add New Device + + + +
+ Device Name + setNewDeviceName(e.target.value)} + placeholder="Enter device name" + /> +
+
+ Select Department + setSelectedDepartment(e.target.value)} + > + + {departments.map(dept => ( + + ))} + +
+
+
+ + setShowDeviceModal(false)}> + Cancel + + + Add Device + + +
+ + ) +} + +export default AppSidebar*/ \ No newline at end of file diff --git a/src/routes.js b/src/routes.js index 4ac5f47b7..44529b7bd 100644 --- a/src/routes.js +++ b/src/routes.js @@ -3,7 +3,7 @@ import React from 'react' // mine const Overview = React.lazy(() => import('./views/overview/Overview')) - +const DeviceView = React.lazy(() => import('./views/devices/DeviceView')) // pre existent const Dashboard = React.lazy(() => import('./views/dashboard/Dashboard')) @@ -61,6 +61,7 @@ const routes = [ { path: '/', exact: true, name: 'Home' }, // mine { path: '/overview', name: 'Overview', element: Overview}, + { path: '/device/:id', name: 'Device', element: DeviceView }, // pre existent { path: '/dashboard', name: 'Dashboard', element: Dashboard }, @@ -110,3 +111,19 @@ const routes = [ ] export default routes + +/* +import React from 'react' +import { Navigate } from 'react-router-dom' + +// Lazy loading components +const Overview = React.lazy(() => import('./views/overview/Overview')) +const DeviceView = React.lazy(() => import('./views/devices/DeviceView')) + +const routes = [ + { path: '/', exact: true, name: 'Home', element: }, + { path: '/overview', name: 'Overview', element: }, + { path: '/device/:id', name: 'Device', element: }, +] + +export default routes*/ diff --git a/src/views/devices/DeviceView.js b/src/views/devices/DeviceView.js new file mode 100644 index 000000000..3925a7f7d --- /dev/null +++ b/src/views/devices/DeviceView.js @@ -0,0 +1,35 @@ +import React from 'react' +import { useParams } from 'react-router-dom' +import { + CCard, + CCardBody, + CCardHeader, + CCol, + CContainer, + CRow, +} from '@coreui/react' + +const DeviceView = () => { + const { id } = useParams() + + return ( + + + + + + Device Details + + +

Device ID: {id}

+

This is where you'll display device-specific information.

+

You can customize this component based on the device data you want to show.

+
+
+
+
+
+ ) +} + +export default DeviceView \ No newline at end of file From 07103d50edc989ed12972db850fc328df37973b8 Mon Sep 17 00:00:00 2001 From: IAmTryingIPromise Date: Sat, 28 Jun 2025 21:06:49 +0300 Subject: [PATCH 04/15] Almost Complete, needs backend for information --- src/_nav.js | 77 +++++- src/components/AppSidebar.js | 69 +++++- src/components/AppSidebarNav.js | 80 +++++- src/components/DataManagement.js | 132 ++++++++++ src/components/DynamicNavButtons.js | 210 ++++++++++++++++ src/hooks/useNavigation.js | 126 ++++++++++ src/layout/DefaultLayout.js | 23 +- src/routes.js | 6 +- src/views/assets/AssetView.js | 365 ++++++++++++++++++++++++++++ src/views/devices/DeviceView.js | 35 --- src/views/overview/Overview.js | 213 +++++++++++++++- 11 files changed, 1285 insertions(+), 51 deletions(-) create mode 100644 src/components/DataManagement.js create mode 100644 src/components/DynamicNavButtons.js create mode 100644 src/hooks/useNavigation.js create mode 100644 src/views/assets/AssetView.js delete mode 100644 src/views/devices/DeviceView.js diff --git a/src/_nav.js b/src/_nav.js index 8baac1ca9..bfd4c4ca3 100644 --- a/src/_nav.js +++ b/src/_nav.js @@ -1,4 +1,4 @@ -import React from 'react' +/*import React from 'react' import CIcon from '@coreui/icons-react' import { //mine @@ -19,8 +19,9 @@ import { cilStar, } from '@coreui/icons' import { CNavGroup, CNavItem, CNavTitle } from '@coreui/react' +import DynamicNavButtons from './components/DynamicNavButtons' -const _nav = [ +export const _nav = (departments, onAddDepartment, onAddAsset, dynamicNavItems) => [ // mine { @@ -35,8 +36,42 @@ const _nav = [ icon: , }, + { + component: DynamicNavButtons, + props: { + onAddDepartment, + onAddAsset, + departments + } + }, - + + { + component: () => ( +
+
+ console.log('Button 1')} + > + Btn 1 + + console.log('Button 2')} + > + Btn 2 + +
+
+ ) + }, // pre existent { @@ -468,9 +503,10 @@ const _nav = [ href: 'https://coreui.io/react/docs/templates/installation/', icon: , }, -] + ...dynamicNavItems +]*/ -export default _nav +//export default _nav /* import React from 'react' @@ -487,3 +523,34 @@ const _nav = [ ] export default _nav*/ + +import React from 'react' +import { CNavItem } from '@coreui/react' +import { CIcon } from '@coreui/icons-react' +import { cilHome, cilSettings } from '@coreui/icons' +import DynamicNavButtons from './components/DynamicNavButtons' + +export const getNavigation = (departments, onAddDepartment, onAddAsset, dynamicNavItems) => [ + { + component: CNavItem, + name: 'Overview', + to: '/overview', + icon: , + }, + { + component: DynamicNavButtons, + props: { + onAddDepartment, + onAddAsset, + departments + } + }, + ...dynamicNavItems, + + { + component: CNavItem, + name: 'Data Management', + to: '/datamanagement', + icon: , + } +] \ No newline at end of file diff --git a/src/components/AppSidebar.js b/src/components/AppSidebar.js index 33affbc95..6ebc45f7f 100644 --- a/src/components/AppSidebar.js +++ b/src/components/AppSidebar.js @@ -1,4 +1,4 @@ -import React from 'react' +/*import React from 'react' import { useSelector, useDispatch } from 'react-redux' import { @@ -58,7 +58,8 @@ const AppSidebar = () => { export default React.memo(AppSidebar) -/*import React, { useState } from 'react' + +import React, { useState } from 'react' import { useSelector, useDispatch } from 'react-redux' import { CSidebar, @@ -306,4 +307,66 @@ const AppSidebar = () => { ) } -export default AppSidebar*/ \ No newline at end of file +export default AppSidebar*/ + +import React from 'react' +import { useSelector, useDispatch } from 'react-redux' +import { + CCloseButton, + CSidebar, + CSidebarBrand, + CSidebarFooter, + CSidebarHeader, + CSidebarToggler, + CSidebarNav, +} from '@coreui/react' +import { AppSidebarNav } from './AppSidebarNav' +import { logo } from 'src/assets/brand/logo' +import { sygnet } from 'src/assets/brand/sygnet' +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, addDepartment, addAsset, dynamicNavItems } = useNavigation() + + // Get the complete navigation with dynamic items + const navigation = getNavigation(departments, addDepartment, addAsset, dynamicNavItems) + + return ( + { + dispatch({ type: 'set', sidebarShow: visible }) + }} + > + + + logo + logo + + dispatch({ type: 'set', sidebarShow: false })} + /> + + + + dispatch({ type: 'set', sidebarUnfoldable: !unfoldable })} + /> + + + ) +} + +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..038e96f2d 100644 --- a/src/components/AppSidebarNav.js +++ b/src/components/AppSidebarNav.js @@ -1,4 +1,4 @@ -import React from 'react' +/*import React from 'react' import { NavLink } from 'react-router-dom' import PropTypes from 'prop-types' @@ -68,6 +68,84 @@ export const AppSidebarNav = ({ items }) => { ) } +AppSidebarNav.propTypes = { + items: PropTypes.arrayOf(PropTypes.any).isRequired, +}*/ + +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) => { + return ( + <> + {icon + ? icon + : indent && ( + + + + )} + {name && name} + {badge && ( + + {badge.text} + + )} + + ) + } + + 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)} + + ) : ( + navLink(name, icon, badge, indent) + )} + + ) + } + + const navGroup = (item, index) => { + const { component, name, icon, items, to, ...rest } = item + const Component = component + return ( + + {item.items?.map((item, index) => + item.items ? navGroup(item, index) : navItem(item, index, true), + )} + + ) + } + + return ( + + {items && + items.map((item, index) => (item.items ? navGroup(item, index) : navItem(item, index)))} + + ) +} + AppSidebarNav.propTypes = { items: PropTypes.arrayOf(PropTypes.any).isRequired, } 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..292e6dc3f --- /dev/null +++ b/src/components/DynamicNavButtons.js @@ -0,0 +1,210 @@ +import React, { useState } from 'react' +import { + CButton, + CModal, + CModalHeader, + CModalTitle, + CModalBody, + CModalFooter, + CFormInput, + CFormLabel, + CFormSelect, + CRow, + CCol +} 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 [vendor, setVendor] = useState('') + const [deviceType, setDeviceType] = useState('') + const [model, setModel] = useState('') + const [selectedDepartment, setSelectedDepartment] = useState('') + + const handleAddDepartment = () => { + setDepartmentDialogVisible(true) + } + + const handleAddAsset = () => { + setAssetDialogVisible(true) + } + + const handleDepartmentConfirm = () => { + if (departmentName.trim()) { + onAddDepartment(departmentName.trim()) + setDepartmentName('') + setDepartmentDialogVisible(false) + } + } + + const handleAssetConfirm = () => { + if (vendor.trim() && deviceType.trim() && model.trim() && selectedDepartment) { + const asset = { + vendor: vendor.trim(), + deviceType: deviceType.trim(), + model: model.trim(), + department: selectedDepartment, + name: `${vendor} ${deviceType} - ${model}`, + id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}` + } + + onAddAsset(asset) + + // Reset form + setVendor('') + setDeviceType('') + setModel('') + setSelectedDepartment('') + setAssetDialogVisible(false) + } + } + + const handleDepartmentCancel = () => { + setDepartmentName('') + setDepartmentDialogVisible(false) + } + + const handleAssetCancel = () => { + setVendor('') + setDeviceType('') + setModel('') + setSelectedDepartment('') + setAssetDialogVisible(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 + + + + + Vendor + setVendor(e.target.value)} + placeholder="Enter vendor name..." + /> + + + Device Type + setDeviceType(e.target.value)} + placeholder="Enter device type..." + /> + + + + + Model + setModel(e.target.value)} + placeholder="Enter model..." + /> + + + Department + setSelectedDepartment(e.target.value)} + > + + {departments.map((dept, index) => ( + + ))} + + + + + + + Cancel + + + Confirm + + + + + ) +} + +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..087aabc48 --- /dev/null +++ b/src/hooks/useNavigation.js @@ -0,0 +1,126 @@ +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 = (asset) => { + setAssets(prev => [...prev, asset]) + } + + // 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, + 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 44529b7bd..20c7b9d22 100644 --- a/src/routes.js +++ b/src/routes.js @@ -3,7 +3,8 @@ import React from 'react' // mine const Overview = React.lazy(() => import('./views/overview/Overview')) -const DeviceView = React.lazy(() => import('./views/devices/DeviceView')) +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')) @@ -61,7 +62,8 @@ const routes = [ { path: '/', exact: true, name: 'Home' }, // mine { path: '/overview', name: 'Overview', element: Overview}, - { path: '/device/:id', name: 'Device', element: DeviceView }, + { path: '/asset/:assetId', name: 'Asset', element: AssetView }, + { path: '/datamanagement', name: 'Data Management', element: DataManagement }, // pre existent { path: '/dashboard', name: 'Dashboard', element: Dashboard }, diff --git a/src/views/assets/AssetView.js b/src/views/assets/AssetView.js new file mode 100644 index 000000000..c89e2d44e --- /dev/null +++ b/src/views/assets/AssetView.js @@ -0,0 +1,365 @@ +/*import React from 'react' +import { useParams } from 'react-router-dom' +import { CCard, CCardBody, CCardHeader } from '@coreui/react' + +const AssetView = ({ assets }) => { + const { assetId } = useParams() + const asset = assets?.find(a => a.id === assetId) + + if (!asset) { + return ( + + Asset Not Found + The requested asset could not be found. + + ) + } + + return ( + + +

{asset.name}

+
+ +
+
+

Vendor: {asset.vendor}

+

Device Type: {asset.deviceType}

+
+
+

Model: {asset.model}

+

Department: {asset.department}

+
+
+ { Add more asset details here } +
+
+ ) +} + +export default AssetView*/ + +/* +import React from 'react' +import { useParams } from 'react-router-dom' +import { + CCard, + CCardBody, + CCardHeader, + CRow, + CCol, + CBreadcrumb, + CBreadcrumbItem, + CAlert +} from '@coreui/react' + +const AssetDetail = () => { + const { assetId } = useParams() + + // Get asset data from localStorage (for now) + const getAssetData = () => { + try { + const savedAssets = localStorage.getItem('coreui_assets') + const assets = savedAssets ? JSON.parse(savedAssets) : [] + return assets.find(asset => asset.id === assetId) + } catch (error) { + console.error('Error loading asset data:', error) + return null + } + } + + const asset = getAssetData() + + if (!asset) { + return ( +
+ + Home + Asset Not Found + + + +

Asset Not Found

+

The requested asset could not be found or may have been removed.

+
+
+ ) + } + + return ( +
+ + Home + {asset.name} + + + + + + +

{asset.name}

+ Asset Details +
+ + + +
+ Vendor: +
{asset.vendor}
+
+
+ Device Type: +
{asset.deviceType}
+
+
+ +
+ Model: +
{asset.model}
+
+
+ Department: +
{asset.department}
+
+
+
+ + { Add more sections as needed } + + + + +
Additional Information
+
+ +

Add more asset-specific content here such as:

+
    +
  • Serial numbers
  • +
  • Purchase date
  • +
  • Warranty information
  • +
  • Location details
  • +
  • Maintenance history
  • +
  • Usage statistics
  • +
+
+
+
+
+
+
+
+
+
+ ) +} + +export default AssetDetail*/ + + +import React, { useMemo } from 'react' +import { useParams, useNavigate } from 'react-router-dom' +import { + CCard, + CCardBody, + CCardHeader, + CButton, + CBadge, + CContainer, + CRow, + CCol, + CListGroup, + CListGroupItem +} from '@coreui/react' +import { useNavigation } from '../../hooks/useNavigation' + +const AssetDetail = () => { + const { assetId } = useParams() + const navigate = useNavigate() + const { assets } = useNavigation() + + 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 + + const seed = asset.id.split('').reduce((a, b) => a + b.charCodeAt(0), 0) + const cveYear = 2020 + (seed % 5) + const cveNumber = (seed % 9999) + 1000 + const cve = `CVE-${cveYear}-${cveNumber}` + + const riskLevels = ['Low', 'Medium', 'High', 'Critical'] + const riskIndex = seed % 4 + const riskLevel = riskLevels[riskIndex] + + // Generate additional mock data for detailed view + 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 = new Date(Date.now() - (seed % 30) * 24 * 60 * 60 * 1000).toLocaleDateString() + const osVersion = `OS v${(seed % 5) + 1}.${(seed % 10)}.${(seed % 100)}` + + return { + ...asset, + cve, + riskLevel, + serialNumber, + ipAddress, + macAddress, + lastScanDate, + osVersion + } + }, [asset]) + + 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.department} +
+ navigate('/overview')} + > + Back to Overview + +
+
+ + + +
Basic Information
+ + + Vendor: + {enrichedAsset.vendor} + + + Device Type: + {enrichedAsset.deviceType} + + + Model: + {enrichedAsset.model} + + + Department: + {enrichedAsset.department} + + + Serial Number: + {enrichedAsset.serialNumber} + + +
+ + +
Network Information
+ + + IP Address: + {enrichedAsset.ipAddress} + + + MAC Address: + {enrichedAsset.macAddress} + + + OS Version: + {enrichedAsset.osVersion} + + + Last Scan: + {enrichedAsset.lastScanDate} + + +
+
+ +
+ + + +
Security Information
+ + + CVE Number: + {enrichedAsset.cve} + + + Risk Level: + + {enrichedAsset.riskLevel} + + + +
+ + +
Actions
+
+ + Run Security Scan + + + Update Asset Info + + + Generate Report + +
+
+
+
+
+
+
+
+ ) +} + +export default AssetDetail \ No newline at end of file diff --git a/src/views/devices/DeviceView.js b/src/views/devices/DeviceView.js deleted file mode 100644 index 3925a7f7d..000000000 --- a/src/views/devices/DeviceView.js +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react' -import { useParams } from 'react-router-dom' -import { - CCard, - CCardBody, - CCardHeader, - CCol, - CContainer, - CRow, -} from '@coreui/react' - -const DeviceView = () => { - const { id } = useParams() - - return ( - - - - - - Device Details - - -

Device ID: {id}

-

This is where you'll display device-specific information.

-

You can customize this component based on the device data you want to show.

-
-
-
-
-
- ) -} - -export default DeviceView \ No newline at end of file diff --git a/src/views/overview/Overview.js b/src/views/overview/Overview.js index fa11b6057..f457c393b 100644 --- a/src/views/overview/Overview.js +++ b/src/views/overview/Overview.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react' +/*import React, { useState, useEffect } from 'react' import classNames from 'classnames' import { CCard, @@ -24,7 +24,7 @@ const Overview = () => { const navgiate = useNavigate() // Function to handle navigation to different dashboards - /* + const handleViewDetails = (itemId) => { switch(item.department) { case 'Sales': navigate(`/sales-dashboard/${item.id}`); break; @@ -33,7 +33,7 @@ const Overview = () => { case 'HR': navigate(`/hr-dashboard/${item.id}`); break; default: navigate(`/general-dashboard/${item.id}`); } - }*/ + } // Sample data - replace this with your actual data fetching logic useEffect(() => { @@ -163,4 +163,209 @@ const Overview = () => { export default Overview -// {index + 1} \ No newline at end of file +// {index + 1}*/ + +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 +} from '@coreui/react' +import { useNavigation } from '../../hooks/useNavigation' + +const Overview = () => { + const navigate = useNavigate() + const { assets } = useNavigation() + + // Generate random CVE numbers and risk levels for each asset + const enrichedAssets = useMemo(() => { + return assets.map(asset => { + // Generate a consistent random CVE based on asset ID (so it doesn't change on re-render) + const seed = asset.id.split('').reduce((a, b) => a + b.charCodeAt(0), 0) + const cveYear = 2020 + (seed % 5) // CVE years between 2020-2024 + const cveNumber = (seed % 9999) + 1000 // CVE numbers between 1000-9999 + const cve = `CVE-${cveYear}-${cveNumber}` + + // Generate consistent risk level + const riskLevels = ['Low', 'Medium', 'High', 'Critical'] + const riskColors = ['success', 'warning', 'danger', 'dark'] + const riskIndex = seed % 4 + const riskLevel = riskLevels[riskIndex] + const riskColor = riskColors[riskIndex] + + return { + ...asset, + cve, + riskLevel, + riskColor + } + }) + }, [assets]) + + const handleViewAsset = (assetId) => { + navigate(`/asset/${assetId}`) + } + + 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 ( + + + + + +
+

Assets Overview

+ + {assets.length} Total Assets + +
+
+ + {enrichedAssets.length === 0 ? ( +
+
No Assets Found
+

Start by adding departments and assets using the sidebar buttons.

+
+ ) : ( + + + + Device Name + Department + CVE # + Risk Level + Asset Details + + + + {enrichedAssets.map((asset) => ( + + +
+ {asset.name} +
+ + {asset.vendor} - {asset.deviceType} + +
+
+ + + {asset.department} + + + + {asset.cve} + + + + {asset.riskLevel} + + + + handleViewAsset(asset.id)} + > + View Details + + +
+ ))} +
+
+ )} +
+
+
+
+ + {/* Optional: Summary cards */} + {enrichedAssets.length > 0 && ( + + + + +
+
+
+ {enrichedAssets.filter(a => a.riskLevel === 'Low').length} +
+
Low Risk
+
+
+
+
+
+ + + +
+
+
+ {enrichedAssets.filter(a => a.riskLevel === 'Medium').length} +
+
Medium Risk
+
+
+
+
+
+ + + +
+
+
+ {enrichedAssets.filter(a => a.riskLevel === 'High').length} +
+
High Risk
+
+
+
+
+
+ + + +
+
+
+ {enrichedAssets.filter(a => a.riskLevel === 'Critical').length} +
+
Critical Risk
+
+
+
+
+
+
+ )} +
+ ) +} + +export default Overview \ No newline at end of file From 642070262993ea0aa10a72655d357d3de0fef6db Mon Sep 17 00:00:00 2001 From: IAmTryingIPromise Date: Fri, 15 Aug 2025 19:08:22 +0300 Subject: [PATCH 05/15] more complete asset searching with searching bar and api calls- not finished --- src/components/DynamicNavButtons.js | 362 +++++++++++++++++++++------- src/routes.js | 2 +- src/views/overview/Overview.js | 12 +- 3 files changed, 288 insertions(+), 88 deletions(-) diff --git a/src/components/DynamicNavButtons.js b/src/components/DynamicNavButtons.js index 292e6dc3f..6267fe9d6 100644 --- a/src/components/DynamicNavButtons.js +++ b/src/components/DynamicNavButtons.js @@ -1,17 +1,23 @@ -import React, { useState } from 'react' -import { - CButton, - CModal, - CModalHeader, - CModalTitle, - CModalBody, +import React, { useState, useEffect, useCallback } from 'react' +import { + CModal, + CModalHeader, + CModalTitle, + CModalBody, CModalFooter, - CFormInput, + CButton, + CRow, + CCol, CFormLabel, + CFormInput, CFormSelect, - CRow, - CCol -} from '@coreui/react' + CCard, + CCardBody, + CListGroup, + CListGroupItem, + CSpinner, + CAlert +} from '@coreui/react'; const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { const [departmentDialogVisible, setDepartmentDialogVisible] = useState(false) @@ -23,6 +29,168 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { const [deviceType, setDeviceType] = useState('') const [model, setModel] = useState('') const [selectedDepartment, setSelectedDepartment] = useState('') + // New + const [searchQuery, setSearchQuery] = useState(''); + const [searchResults, setSearchResults] = useState([]); + const [selectedAsset, setSelectedAsset] = useState(null); + const [isSearching, setIsSearching] = useState(false); + const [searchError, setSearchError] = useState(''); + const [showResults, setShowResults] = useState(false); + + // Everything about asset searching and API calls + // 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 assets + const searchAssets = async (query) => { + if (!query.trim()) { + setSearchResults([]); + setShowResults(false); + return; + } + + setIsSearching(true); + setSearchError(''); + + try { + const response = await fetch(`YOUR_API_ENDPOINT/assets/search`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer YOUR_TOKEN`, // Replace with your auth token + }, + body: JSON.stringify({ + query: query.trim(), + limit: 10 + }) + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + setSearchResults(data.results || []); // Assuming API returns { results: [...] } + setShowResults(true); + } catch (error) { + console.error('Search error:', error); + setSearchError('Failed to search assets. Please try again.'); + setSearchResults([]); + setShowResults(false); + } finally { + setIsSearching(false); + } + }; + + // Debounced search with 400ms delay + const debouncedSearch = useCallback( + debounce(searchAssets, 400), + [debounce] + ); + + // Handle search input change + const handleSearchChange = (e) => { + const query = e.target.value; + setSearchQuery(query); + debouncedSearch(query); + }; + + // Handle asset selection from search results + const handleAssetSelect = (asset) => { + setSelectedAsset(asset); + setSearchQuery(asset.name || `${asset.vendor} ${asset.model}`); // Display selected asset + setShowResults(false); + }; + + // API call to get full asset details + const getAssetDetails = async (assetId) => { + try { + const response = await fetch(`YOUR_API_ENDPOINT/assets/${assetId}`, { + method: 'GET', + headers: { + 'Authorization': `Bearer YOUR_TOKEN`, // Replace with your auth token + } + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const assetDetails = await response.json(); + return assetDetails; + } catch (error) { + console.error('Error fetching asset details:', error); + throw error; + } + }; + + // Enhanced confirm handler + const handleAssetConfirm = async () => { + if (!selectedAsset || !selectedDepartment) return; + + try { + const assetDetails = await getAssetDetails(selectedAsset.id); + + const asset = { + ...assetDetails, + department: selectedDepartment, + name: assetDetails.name || `${assetDetails.vendor} ${assetDetails.deviceType} - ${assetDetails.model}`, + id: assetDetails.id || `${Date.now()}-${Math.random().toString(36).substr(2, 9)}` + } + + onAddAsset(asset); + + // Reset form + setSearchQuery(''); + setSelectedAsset(null); +// setSelectedDepartment(''); + setSearchResults([]); + setShowResults(false); + setSearchError(''); + setAssetDialogVisible(false); + } catch (error) { + setSearchError('Failed to get asset details. Please try again.'); + } + }; + + // Reset form function + const resetForm = () => { + setVendor(''); + setDeviceType(''); + setModel(''); +// setSelectedDepartment(''); + setSearchQuery(''); + setSearchResults([]); + setSelectedAsset(null); + setShowResults(false); + setSearchError(''); + }; + + // Reset form when modal opens/closes + //useEffect(() => { + // if (!assetDialogVisible) { + // resetForm(); + // } + //}, [assetDialogVisible]); + + // Handle cancel + const handleAssetCancel = () => { + setSearchQuery(''); + setSelectedAsset(null); +// setSelectedDepartment(''); + setSearchResults([]); + setShowResults(false); + setSearchError(''); + setAssetDialogVisible(false); + }; + + const handleAddDepartment = () => { setDepartmentDialogVisible(true) @@ -39,7 +207,7 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { setDepartmentDialogVisible(false) } } - +/* const handleAssetConfirm = () => { if (vendor.trim() && deviceType.trim() && model.trim() && selectedDepartment) { const asset = { @@ -61,12 +229,12 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { setAssetDialogVisible(false) } } - +*/ const handleDepartmentCancel = () => { setDepartmentName('') setDepartmentDialogVisible(false) } - +/* const handleAssetCancel = () => { setVendor('') setDeviceType('') @@ -74,7 +242,7 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { setSelectedDepartment('') setAssetDialogVisible(false) } - +*/ return ( <>
@@ -133,76 +301,108 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { - - {/* Asset Dialog */} + + {/* Asset Dialog*/} - - Add Asset - - - - - Vendor - setVendor(e.target.value)} - placeholder="Enter vendor name..." - /> - - - Device Type - setDeviceType(e.target.value)} - placeholder="Enter device type..." - /> - - - - - Model + + Add Asset + + + {/* Search Section */} + + + Search Assets +
setModel(e.target.value)} - placeholder="Enter model..." + value={searchQuery} + onChange={handleSearchChange} + placeholder="Start typing to search for assets..." /> - - - Department - setSelectedDepartment(e.target.value)} - > - - {departments.map((dept, index) => ( - - ))} - - - - - - - Cancel - - - Confirm - - - + {isSearching && ( +
+ +
+ )} +
+ + {/* Search Error */} + {searchError && ( + + {searchError} + + )} + + {/* Search Results */} + {showResults && searchResults.length > 0 && ( + + + {searchResults.map((asset, index) => ( + handleAssetSelect(asset)} + style={{ cursor: 'pointer' }} + > +
+ {asset.name || `${asset.vendor} ${asset.model}`} + {asset.vendor &&
Vendor: {asset.vendor}
} + {asset.deviceType &&
Type: {asset.deviceType}
} + {asset.model &&
Model: {asset.model}
} +
+
+ ))} +
+
+ )} + + {/* No Results Message */} + {showResults && searchResults.length === 0 && !isSearching && ( + + No assets found matching your search. + + )} +
+
+ + {/* Department Selection */} + + + Department + setSelectedDepartment(e.target.value)} + > + + {departments.map((dept, index) => ( + + ))} + + + +
+ + + Cancel + + + Confirm + + +
) } diff --git a/src/routes.js b/src/routes.js index 20c7b9d22..faffedae9 100644 --- a/src/routes.js +++ b/src/routes.js @@ -59,7 +59,7 @@ 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 }, diff --git a/src/views/overview/Overview.js b/src/views/overview/Overview.js index f457c393b..277f4debc 100644 --- a/src/views/overview/Overview.js +++ b/src/views/overview/Overview.js @@ -235,16 +235,16 @@ const Overview = () => {
-

Assets Overview

+

Vulnerabilities Overview

- {assets.length} Total Assets + {assets.length} Total Vulnerabilies
{enrichedAssets.length === 0 ? (
-
No Assets Found
+
No Vulnerable Assets Found

Start by adding departments and assets using the sidebar buttons.

) : ( @@ -253,9 +253,9 @@ const Overview = () => { Device Name Department - CVE # - Risk Level - Asset Details + CVE ID + CVE Risk Level + More Details From 488805c2733804bf8f45b43feaaf38486d159417 Mon Sep 17 00:00:00 2001 From: IAmTryingIPromise Date: Thu, 21 Aug 2025 20:51:23 +0300 Subject: [PATCH 06/15] updated API calls 1 - 21/08/2025 --- src/components/DynamicNavButtons.js | 124 ++++++++++++++++------------ 1 file changed, 72 insertions(+), 52 deletions(-) diff --git a/src/components/DynamicNavButtons.js b/src/components/DynamicNavButtons.js index 6267fe9d6..3cf3b90db 100644 --- a/src/components/DynamicNavButtons.js +++ b/src/components/DynamicNavButtons.js @@ -48,45 +48,46 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { }, []); // API call to search assets - const searchAssets = async (query) => { - if (!query.trim()) { - setSearchResults([]); - setShowResults(false); - return; - } - - setIsSearching(true); - setSearchError(''); + const searchCPEMatches = async (deviceName) => { + if (!deviceName.trim()) { + setSearchResults([]); + setShowResults(false); + return; + } - try { - const response = await fetch(`YOUR_API_ENDPOINT/assets/search`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer YOUR_TOKEN`, // Replace with your auth token - }, - body: JSON.stringify({ - query: query.trim(), - limit: 10 - }) - }); + setIsSearching(true); + setSearchError(''); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } + try { + const response = await fetch(`YOUR_API_ENDPOINT/security/cpe-search`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer YOUR_TOKEN`, // Replace with your auth token + }, + body: JSON.stringify({ + device_name: deviceName.trim() + }) + }); - const data = await response.json(); - setSearchResults(data.results || []); // Assuming API returns { results: [...] } - setShowResults(true); - } catch (error) { - console.error('Search error:', error); - setSearchError('Failed to search assets. Please try again.'); - setSearchResults([]); - setShowResults(false); - } finally { - setIsSearching(false); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); } - }; + + const data = await response.json(); + // Backend returns: { device_name, matches: [CPEMatch], total_matches } + // CPEMatch: { device_name, vendor, model, cpe, score } + setSearchResults(data.matches || []); + setShowResults(true); + } catch (error) { + console.error('CPE search error:', error); + setSearchError('Failed to search for device matches. Please try again.'); + setSearchResults([]); + setShowResults(false); + } finally { + setIsSearching(false); + } +}; // Debounced search with 400ms delay const debouncedSearch = useCallback( @@ -109,26 +110,45 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { }; // API call to get full asset details - const getAssetDetails = async (assetId) => { - try { - const response = await fetch(`YOUR_API_ENDPOINT/assets/${assetId}`, { - method: 'GET', - headers: { - 'Authorization': `Bearer YOUR_TOKEN`, // Replace with your auth token - } - }); + const scanDeviceByCPE = async (cpe, deviceName = null, department = "Unknown") => { + try { + const response = await fetch(`YOUR_API_ENDPOINT/security/scan-by-cpe`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer YOUR_TOKEN`, // Replace with your auth token + }, + body: JSON.stringify({ + cpe: cpe, + device_name: deviceName, + department: department + }) + }); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } - const assetDetails = await response.json(); - return assetDetails; - } catch (error) { - console.error('Error fetching asset details:', error); - throw error; + const scanResults = await response.json(); + /* Backend returns FullScanResponse: + { + success: boolean, + error_message: string | null, + scan_time: float, + device: AssetInfo | null, + cves: CVEInfo[], + cwes: CWEInfo[], + capecs: CAPECInfo[], + attacks: AttackInfo[], + statistics: { cves: int, cwes: int, capecs: int, attacks: int } } - }; + */ + return scanResults; + } catch (error) { + console.error('Error scanning device by CPE:', error); + throw error; + } +}; // Enhanced confirm handler const handleAssetConfirm = async () => { From 114cda0d25c9759a856dd5aa8616b66ddb4d8fa4 Mon Sep 17 00:00:00 2001 From: IAmTryingIPromise Date: Sat, 23 Aug 2025 19:07:06 +0300 Subject: [PATCH 07/15] 23/08/2025 - device api testing needed --- src/components/DynamicNavButtons.js | 470 +++++++++++++++++++++++++++- 1 file changed, 454 insertions(+), 16 deletions(-) diff --git a/src/components/DynamicNavButtons.js b/src/components/DynamicNavButtons.js index 3cf3b90db..6fa58a9ea 100644 --- a/src/components/DynamicNavButtons.js +++ b/src/components/DynamicNavButtons.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback } from 'react' +/*import React, { useState, useEffect, useCallback } from 'react' import { CModal, CModalHeader, @@ -63,7 +63,7 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { method: 'POST', headers: { 'Content-Type': 'application/json', - 'Authorization': `Bearer YOUR_TOKEN`, // Replace with your auth token + 'Authorization': '8b9ca364-443f-9920-b8a94c54', }, body: JSON.stringify({ device_name: deviceName.trim() @@ -116,7 +116,7 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { method: 'POST', headers: { 'Content-Type': 'application/json', - 'Authorization': `Bearer YOUR_TOKEN`, // Replace with your auth token + 'Authorization': '8b9ca364-443f-9920-b8a94c54', }, body: JSON.stringify({ cpe: cpe, @@ -130,7 +130,7 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { } const scanResults = await response.json(); - /* Backend returns FullScanResponse: + Backend returns FullScanResponse: { success: boolean, error_message: string | null, @@ -142,7 +142,7 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { attacks: AttackInfo[], statistics: { cves: int, cwes: int, capecs: int, attacks: int } } - */ + return scanResults; } catch (error) { console.error('Error scanning device by CPE:', error); @@ -155,7 +155,7 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { if (!selectedAsset || !selectedDepartment) return; try { - const assetDetails = await getAssetDetails(selectedAsset.id); + const assetDetails = await scanDeviceByCPE(selectedAsset.cpe); const asset = { ...assetDetails, @@ -249,12 +249,12 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { setAssetDialogVisible(false) } } -*/ + const handleDepartmentCancel = () => { setDepartmentName('') setDepartmentDialogVisible(false) } -/* + const handleAssetCancel = () => { setVendor('') setDeviceType('') @@ -262,7 +262,7 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { setSelectedDepartment('') setAssetDialogVisible(false) } -*/ + return ( <>
@@ -288,7 +288,7 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => {
- {/* Department Dialog */} + {/* Department Dialog } Add Department @@ -322,13 +322,13 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { - {/* Asset Dialog*/} + {/* Asset Dialog} Add Asset - {/* Search Section */} + {/* Search Section } Search Assets @@ -352,14 +352,14 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { )} - {/* Search Error */} + {/* Search Error } {searchError && ( {searchError} )} - {/* Search Results */} + {/* Search Results } {showResults && searchResults.length > 0 && ( @@ -382,7 +382,7 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { )} - {/* No Results Message */} + {/* No Results Message } {showResults && searchResults.length === 0 && !isSearching && ( No assets found matching your search. @@ -391,7 +391,7 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { - {/* Department Selection */} + {/* Department Selection } Department @@ -427,4 +427,442 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { ) } +export default DynamicNavButtons*/ +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('') + + // 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('/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 = (cpeMatch) => { + setSelectedCpeMatch(cpeMatch); + setSearchQuery(cpeMatch.device_name); + setShowResults(false); + setSearchError(''); + }; + + // API call to scan device by CPE and get full vulnerability data + const scanDeviceByCpe = async (cpe, deviceName, department) => { + try { + const response = await fetch('/api/v1/security/scan-by-cpe', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + cpe: cpe, + device_name: deviceName, + 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; + } + }; + + // Enhanced confirm handler + const handleAssetConfirm = async () => { + if (!selectedCpeMatch || !selectedDepartment) return; + + setIsScanning(true); + setSearchError(''); + + try { + const scanResults = await scanDeviceByCpe( + selectedCpeMatch.cpe, + selectedCpeMatch.device_name, + selectedDepartment + ); + + if (!scanResults.success) { + throw new Error(scanResults.error_message || 'Scan failed'); + } + + // Create asset object with scan results + const asset = { + id: scanResults.device?.id || `scan-${Date.now()}`, + name: scanResults.device?.name || selectedCpeMatch.device_name, + vendor: scanResults.device?.vendor || selectedCpeMatch.vendor, + model: scanResults.device?.model || selectedCpeMatch.model, + type: scanResults.device?.type || 'Unknown', + department: selectedDepartment, + risk_level: scanResults.device?.risk_level || 0, + cpe: selectedCpeMatch.cpe, + vulnerabilities: { + cves: scanResults.cves || [], + cwes: scanResults.cwes || [], + capecs: scanResults.capecs || [], + attacks: scanResults.attacks || [] + }, + statistics: scanResults.statistics || {}, + scan_time: scanResults.scan_time || 0 + }; + + onAddAsset(asset); + + // 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(''); + }; + + // 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} +
+
+
+
+
+ )} + + {/* 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 From bb7ce2ab32130879d579755b84385f8ab5863caa Mon Sep 17 00:00:00 2001 From: IAmTryingIPromise Date: Sun, 24 Aug 2025 18:06:49 +0300 Subject: [PATCH 08/15] api testing works fine so far - needs asset screen implementation --- src/components/DynamicNavButtons.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/DynamicNavButtons.js b/src/components/DynamicNavButtons.js index 6fa58a9ea..47c6ee51f 100644 --- a/src/components/DynamicNavButtons.js +++ b/src/components/DynamicNavButtons.js @@ -488,7 +488,7 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { setSearchError(''); try { - const response = await fetch('/api/v1/security/cpe-search', { + const response = await fetch('http://localhost:8000/api/v1/security/cpe-search', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -540,7 +540,7 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { // API call to scan device by CPE and get full vulnerability data const scanDeviceByCpe = async (cpe, deviceName, department) => { try { - const response = await fetch('/api/v1/security/scan-by-cpe', { + const response = await fetch('http://localhost:8000/api/v1/security/scan-by-cpe', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -767,16 +767,20 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => {
Vendor: {match.vendor} | Model: {match.model}
+
CPE: {match.cpe}
+ + {/*} = 80 ? 'success' : match.score >= 60 ? 'warning' : 'secondary'} className="ms-2" > {match.score}% + */} ))} From 77a3c7aff98f537f8416d8bf6527b6195e20a3c8 Mon Sep 17 00:00:00 2001 From: IAmTryingIPromise Date: Tue, 26 Aug 2025 19:03:11 +0300 Subject: [PATCH 09/15] Asset creation 26/08/2025 --- src/components/DynamicNavButtons.js | 430 ---------------------------- src/views/assets/AssetView.js | 158 ---------- 2 files changed, 588 deletions(-) diff --git a/src/components/DynamicNavButtons.js b/src/components/DynamicNavButtons.js index 47c6ee51f..7b0660e05 100644 --- a/src/components/DynamicNavButtons.js +++ b/src/components/DynamicNavButtons.js @@ -1,433 +1,3 @@ -/*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 -} 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 [vendor, setVendor] = useState('') - const [deviceType, setDeviceType] = useState('') - const [model, setModel] = useState('') - const [selectedDepartment, setSelectedDepartment] = useState('') - // New - const [searchQuery, setSearchQuery] = useState(''); - const [searchResults, setSearchResults] = useState([]); - const [selectedAsset, setSelectedAsset] = useState(null); - const [isSearching, setIsSearching] = useState(false); - const [searchError, setSearchError] = useState(''); - const [showResults, setShowResults] = useState(false); - - // Everything about asset searching and API calls - // 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 assets - const searchCPEMatches = async (deviceName) => { - if (!deviceName.trim()) { - setSearchResults([]); - setShowResults(false); - return; - } - - setIsSearching(true); - setSearchError(''); - - try { - const response = await fetch(`YOUR_API_ENDPOINT/security/cpe-search`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': '8b9ca364-443f-9920-b8a94c54', - }, - body: JSON.stringify({ - device_name: deviceName.trim() - }) - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); - // Backend returns: { device_name, matches: [CPEMatch], total_matches } - // CPEMatch: { device_name, vendor, model, cpe, score } - setSearchResults(data.matches || []); - setShowResults(true); - } catch (error) { - console.error('CPE search error:', error); - setSearchError('Failed to search for device matches. Please try again.'); - setSearchResults([]); - setShowResults(false); - } finally { - setIsSearching(false); - } -}; - - // Debounced search with 400ms delay - const debouncedSearch = useCallback( - debounce(searchAssets, 400), - [debounce] - ); - - // Handle search input change - const handleSearchChange = (e) => { - const query = e.target.value; - setSearchQuery(query); - debouncedSearch(query); - }; - - // Handle asset selection from search results - const handleAssetSelect = (asset) => { - setSelectedAsset(asset); - setSearchQuery(asset.name || `${asset.vendor} ${asset.model}`); // Display selected asset - setShowResults(false); - }; - - // API call to get full asset details - const scanDeviceByCPE = async (cpe, deviceName = null, department = "Unknown") => { - try { - const response = await fetch(`YOUR_API_ENDPOINT/security/scan-by-cpe`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': '8b9ca364-443f-9920-b8a94c54', - }, - body: JSON.stringify({ - cpe: cpe, - device_name: deviceName, - department: department - }) - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const scanResults = await response.json(); - Backend returns FullScanResponse: - { - success: boolean, - error_message: string | null, - scan_time: float, - device: AssetInfo | null, - cves: CVEInfo[], - cwes: CWEInfo[], - capecs: CAPECInfo[], - attacks: AttackInfo[], - statistics: { cves: int, cwes: int, capecs: int, attacks: int } - } - - return scanResults; - } catch (error) { - console.error('Error scanning device by CPE:', error); - throw error; - } -}; - - // Enhanced confirm handler - const handleAssetConfirm = async () => { - if (!selectedAsset || !selectedDepartment) return; - - try { - const assetDetails = await scanDeviceByCPE(selectedAsset.cpe); - - const asset = { - ...assetDetails, - department: selectedDepartment, - name: assetDetails.name || `${assetDetails.vendor} ${assetDetails.deviceType} - ${assetDetails.model}`, - id: assetDetails.id || `${Date.now()}-${Math.random().toString(36).substr(2, 9)}` - } - - onAddAsset(asset); - - // Reset form - setSearchQuery(''); - setSelectedAsset(null); -// setSelectedDepartment(''); - setSearchResults([]); - setShowResults(false); - setSearchError(''); - setAssetDialogVisible(false); - } catch (error) { - setSearchError('Failed to get asset details. Please try again.'); - } - }; - - // Reset form function - const resetForm = () => { - setVendor(''); - setDeviceType(''); - setModel(''); -// setSelectedDepartment(''); - setSearchQuery(''); - setSearchResults([]); - setSelectedAsset(null); - setShowResults(false); - setSearchError(''); - }; - - // Reset form when modal opens/closes - //useEffect(() => { - // if (!assetDialogVisible) { - // resetForm(); - // } - //}, [assetDialogVisible]); - - // Handle cancel - const handleAssetCancel = () => { - setSearchQuery(''); - setSelectedAsset(null); -// setSelectedDepartment(''); - setSearchResults([]); - setShowResults(false); - setSearchError(''); - setAssetDialogVisible(false); - }; - - - - const handleAddDepartment = () => { - setDepartmentDialogVisible(true) - } - - const handleAddAsset = () => { - setAssetDialogVisible(true) - } - - const handleDepartmentConfirm = () => { - if (departmentName.trim()) { - onAddDepartment(departmentName.trim()) - setDepartmentName('') - setDepartmentDialogVisible(false) - } - } -/* - const handleAssetConfirm = () => { - if (vendor.trim() && deviceType.trim() && model.trim() && selectedDepartment) { - const asset = { - vendor: vendor.trim(), - deviceType: deviceType.trim(), - model: model.trim(), - department: selectedDepartment, - name: `${vendor} ${deviceType} - ${model}`, - id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}` - } - - onAddAsset(asset) - - // Reset form - setVendor('') - setDeviceType('') - setModel('') - setSelectedDepartment('') - setAssetDialogVisible(false) - } - } - - const handleDepartmentCancel = () => { - setDepartmentName('') - setDepartmentDialogVisible(false) - } - - const handleAssetCancel = () => { - setVendor('') - setDeviceType('') - setModel('') - setSelectedDepartment('') - setAssetDialogVisible(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 - - - {/* Search Section } - - - Search Assets -
- - {isSearching && ( -
- -
- )} -
- - {/* Search Error } - {searchError && ( - - {searchError} - - )} - - {/* Search Results } - {showResults && searchResults.length > 0 && ( - - - {searchResults.map((asset, index) => ( - handleAssetSelect(asset)} - style={{ cursor: 'pointer' }} - > -
- {asset.name || `${asset.vendor} ${asset.model}`} - {asset.vendor &&
Vendor: {asset.vendor}
} - {asset.deviceType &&
Type: {asset.deviceType}
} - {asset.model &&
Model: {asset.model}
} -
-
- ))} -
-
- )} - - {/* No Results Message } - {showResults && searchResults.length === 0 && !isSearching && ( - - No assets found matching your search. - - )} -
-
- - {/* Department Selection } - - - Department - setSelectedDepartment(e.target.value)} - > - - {departments.map((dept, index) => ( - - ))} - - - -
- - - Cancel - - - Confirm - - -
- - ) -} - -export default DynamicNavButtons*/ import React, { useState, useEffect, useCallback } from 'react' import { CModal, diff --git a/src/views/assets/AssetView.js b/src/views/assets/AssetView.js index c89e2d44e..fb5febb30 100644 --- a/src/views/assets/AssetView.js +++ b/src/views/assets/AssetView.js @@ -1,161 +1,3 @@ -/*import React from 'react' -import { useParams } from 'react-router-dom' -import { CCard, CCardBody, CCardHeader } from '@coreui/react' - -const AssetView = ({ assets }) => { - const { assetId } = useParams() - const asset = assets?.find(a => a.id === assetId) - - if (!asset) { - return ( - - Asset Not Found - The requested asset could not be found. - - ) - } - - return ( - - -

{asset.name}

-
- -
-
-

Vendor: {asset.vendor}

-

Device Type: {asset.deviceType}

-
-
-

Model: {asset.model}

-

Department: {asset.department}

-
-
- { Add more asset details here } -
-
- ) -} - -export default AssetView*/ - -/* -import React from 'react' -import { useParams } from 'react-router-dom' -import { - CCard, - CCardBody, - CCardHeader, - CRow, - CCol, - CBreadcrumb, - CBreadcrumbItem, - CAlert -} from '@coreui/react' - -const AssetDetail = () => { - const { assetId } = useParams() - - // Get asset data from localStorage (for now) - const getAssetData = () => { - try { - const savedAssets = localStorage.getItem('coreui_assets') - const assets = savedAssets ? JSON.parse(savedAssets) : [] - return assets.find(asset => asset.id === assetId) - } catch (error) { - console.error('Error loading asset data:', error) - return null - } - } - - const asset = getAssetData() - - if (!asset) { - return ( -
- - Home - Asset Not Found - - - -

Asset Not Found

-

The requested asset could not be found or may have been removed.

-
-
- ) - } - - return ( -
- - Home - {asset.name} - - - - - - -

{asset.name}

- Asset Details -
- - - -
- Vendor: -
{asset.vendor}
-
-
- Device Type: -
{asset.deviceType}
-
-
- -
- Model: -
{asset.model}
-
-
- Department: -
{asset.department}
-
-
-
- - { Add more sections as needed } - - - - -
Additional Information
-
- -

Add more asset-specific content here such as:

-
    -
  • Serial numbers
  • -
  • Purchase date
  • -
  • Warranty information
  • -
  • Location details
  • -
  • Maintenance history
  • -
  • Usage statistics
  • -
-
-
-
-
-
-
-
-
-
- ) -} - -export default AssetDetail*/ - - import React, { useMemo } from 'react' import { useParams, useNavigate } from 'react-router-dom' import { From 4f124d752589911c1cbc3b0ac0631a2d365d8693 Mon Sep 17 00:00:00 2001 From: IAmTryingIPromise Date: Tue, 26 Aug 2025 20:17:01 +0300 Subject: [PATCH 10/15] Almost complete AssetView - needs more testing - 26/08/2025 --- src/App.js | 12 + src/components/DynamicNavButtons.js | 42 ++- src/hooks/useNavigation.js | 97 +++++- src/views/assets/AssetView.js | 445 +++++++++++++++++++++++----- 4 files changed, 527 insertions(+), 69 deletions(-) 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 ( { }; // Enhanced confirm handler + const handleAssetConfirm = async () => { + if (!selectedCpeMatch || !selectedDepartment) return + + setIsScanning(true) + setSearchError('') + + try { + const scanResults = await scanDeviceByCpe( + selectedCpeMatch.cpe, + selectedCpeMatch.device_name, + selectedDepartment + ) + + if (!scanResults.success) { + throw new Error(scanResults.error_message || 'Scan failed') + } + + // Store CPE in the scan results for future refreshes + const enhancedResults = { + ...scanResults, + device: { + ...scanResults.device, + department: selectedDepartment // Ensure department is set + }, + cpe: selectedCpeMatch.cpe // Store CPE for background refreshes + } + + // 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) + } + } + /* const handleAssetConfirm = async () => { if (!selectedCpeMatch || !selectedDepartment) return; @@ -182,7 +222,7 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { } finally { setIsScanning(false); } - }; + };*/ // Reset asset form function const resetAssetForm = () => { diff --git a/src/hooks/useNavigation.js b/src/hooks/useNavigation.js index 087aabc48..096c3a110 100644 --- a/src/hooks/useNavigation.js +++ b/src/hooks/useNavigation.js @@ -52,10 +52,103 @@ export const useNavigation = () => { } } - const addAsset = (asset) => { + 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, + + // Store CPE for background refreshes + cpe: apiResponse.cpe || '', + + // 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 + } + + 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 || '', // You might need to store CPE in the asset + 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([]) @@ -118,6 +211,8 @@ export const useNavigation = () => { assets, addDepartment, addAsset, + updateAsset, + refreshAllAssets, removeDepartment, removeAsset, clearAllData, diff --git a/src/views/assets/AssetView.js b/src/views/assets/AssetView.js index fb5febb30..5172dfb23 100644 --- a/src/views/assets/AssetView.js +++ b/src/views/assets/AssetView.js @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react' +import React, { useState, useEffect, useMemo } from 'react' import { useParams, useNavigate } from 'react-router-dom' import { CCard, @@ -10,7 +10,14 @@ import { CRow, CCol, CListGroup, - CListGroupItem + CListGroupItem, + CFormInput, + CTabs, + CTabList, + CTab, + CTabContent, + CTabPanel, + CAlert } from '@coreui/react' import { useNavigation } from '../../hooks/useNavigation' @@ -25,34 +32,60 @@ const AssetDetail = () => { const enrichedAsset = useMemo(() => { if (!asset) return null - const seed = asset.id.split('').reduce((a, b) => a + b.charCodeAt(0), 0) - const cveYear = 2020 + (seed % 5) - const cveNumber = (seed % 9999) + 1000 - const cve = `CVE-${cveYear}-${cveNumber}` - - const riskLevels = ['Low', 'Medium', 'High', 'Critical'] - const riskIndex = seed % 4 - const riskLevel = riskLevels[riskIndex] + // 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 additional mock data for detailed view + // 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 = new Date(Date.now() - (seed % 30) * 24 * 60 * 60 * 1000).toLocaleDateString() - const osVersion = `OS v${(seed % 5) + 1}.${(seed % 10)}.${(seed % 100)}` + 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, - cve, - riskLevel, + hasVulnerabilities, serialNumber, ipAddress, macAddress, lastScanDate, - osVersion + osVersion, + // Calculate risk level based on vulnerabilities if they exist + riskLevel: hasVulnerabilities && asset.vulnerabilities.cves?.length > 0 + ? (() => { + const maxCvss = Math.max(...asset.vulnerabilities.cves.map(cve => cve.cvss || 0)) + if (maxCvss >= 9.0) return 'Critical' + if (maxCvss >= 7.0) return 'High' + if (maxCvss >= 4.0) return 'Medium' + return 'Low' + })() + : 'Secure' } }, [asset]) + 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]) + if (!enrichedAsset) { return ( @@ -99,8 +132,22 @@ const AssetDetail = () => {
-

{enrichedAsset.name}

- {enrichedAsset.department} +
+

{enrichedAsset.name}

+ {enrichedAsset.hasVulnerabilities ? ( + + ⚠️ Vulnerabilities Found + + ) : ( + + ✅ Secure + + )} +
+
+ {enrichedAsset.department} + {enrichedAsset.type} +
{
- + {/* Device Information Section */} + -
Basic Information
+
Device Information
- Vendor: - {enrichedAsset.vendor} + Name: + {enrichedAsset.name} - Device Type: - {enrichedAsset.deviceType} + Vendor: + {enrichedAsset.vendor} Model: @@ -132,27 +180,68 @@ const AssetDetail = () => { Department: {enrichedAsset.department} - - Serial Number: - {enrichedAsset.serialNumber} -
-
Network Information
+
+
Network & System Info
+ { + if (isEditing) { + // Save logic here if needed + setIsEditing(false) + } else { + setIsEditing(true) + } + }} + > + {isEditing ? "Save" : "Edit"} + +
+ - + IP Address: - {enrichedAsset.ipAddress} + {isEditing ? ( + setEditableFields(prev => ({...prev, ipAddress: e.target.value}))} + style={{ width: '150px' }} + /> + ) : ( + {editableFields.ipAddress} + )} - + MAC Address: - {enrichedAsset.macAddress} + {isEditing ? ( + setEditableFields(prev => ({...prev, macAddress: e.target.value}))} + style={{ width: '150px' }} + /> + ) : ( + {editableFields.macAddress} + )} - + OS Version: - {enrichedAsset.osVersion} + {isEditing ? ( + setEditableFields(prev => ({...prev, osVersion: e.target.value}))} + style={{ width: '150px' }} + /> + ) : ( + {editableFields.osVersion} + )} Last Scan: @@ -161,45 +250,267 @@ const AssetDetail = () => {
- -
- - - -
Security Information
- - - CVE Number: - {enrichedAsset.cve} - - - Risk Level: - - {enrichedAsset.riskLevel} - - - + + {/* Security Status Section */} + + +
Security Status
+ {enrichedAsset.hasVulnerabilities ? ( + + ⚠️ Vulnerabilities Found - This device has known security vulnerabilities that require attention. + + ) : ( + + ✅ No Vulnerable CVEs Found - This device appears to be secure with no known critical vulnerabilities. + + )}
- - -
Actions
-
- - Run Security Scan - - - Update Asset Info - - - Generate Report - -
+
+ + {/* Vulnerability Details Section - Only show if vulnerabilities exist */} + {enrichedAsset.hasVulnerabilities && ( + + +
Vulnerability Details
+ + + {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) => ( + +
+
+
+ {cve.cve_id} + = 9.0 ? 'danger' : + cve.cvss >= 7.0 ? 'warning' : + cve.cvss >= 4.0 ? 'info' : 'secondary' + } + > + CVSS: {cve.cvss || 'N/A'} + + {cve.epss && ( + + EPSS: {(cve.epss * 100).toFixed(1)}% + + )} +
+

+ {cve.description?.substring(0, 200)} + {cve.description?.length > 200 && '...'} +

+
+
+
+ ))} +
+
+
+ )} + + {enrichedAsset.vulnerabilities.cwes?.length > 0 && ( + +
+ + {enrichedAsset.vulnerabilities.cwes.map((cwe, index) => ( + +
+ {cwe.cwe_id} + {cwe.name} +
+

+ {cwe.description?.substring(0, 150)} + {cwe.description?.length > 150 && '...'} +

+
+ ))} +
+
+
+ )} + + {enrichedAsset.vulnerabilities.capecs?.length > 0 && ( + +
+ + {enrichedAsset.vulnerabilities.capecs.map((capec, index) => ( + +
+
+
+ {capec.capec_id} + {capec.name} +
+
+ + Severity: {capec.typical_severity || 'Unknown'} + + + Likelihood: {capec.likelihood_of_attack || 'Unknown'} + +
+

+ {capec.description?.substring(0, 150)} + {capec.description?.length > 150 && '...'} +

+
+
+
+ ))} +
+
+
+ )} + + {enrichedAsset.vulnerabilities.attacks?.length > 0 && ( + +
+ + {enrichedAsset.vulnerabilities.attacks.map((attack, index) => ( + +
+
+
+ {attack.technique_id} + {attack.name} +
+
+ {attack.tactics && ( + + {attack.tactics} + + )} + {attack.platforms && ( + + {attack.platforms.length > 20 ? attack.platforms.substring(0, 20) + '...' : attack.platforms} + + )} +
+

+ {attack.description?.substring(0, 150)} + {attack.description?.length > 150 && '...'} +

+
+
+
+ ))} +
+
+
+ )} +
+
+
+
+ )} + + {/* 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} +
+
+
+ + )} ) } From d742e8dbf5ce3e80bb175086c195eae130896ae8 Mon Sep 17 00:00:00 2001 From: IAmTryingIPromise Date: Tue, 2 Sep 2025 16:31:58 +0300 Subject: [PATCH 11/15] post asset creation - QOL changes - 02/09/2025 --- src/_nav.js | 601 +++++--------------------------- src/components/AppBreadcrumb.js | 92 ++++- src/components/AppFooter.js | 84 ++++- src/components/AppHeader.js | 478 ++++++++++++++++++++----- src/components/AppSidebar.js | 395 ++++----------------- src/scss/style.scss | 227 ++++++++++++ 6 files changed, 914 insertions(+), 963 deletions(-) diff --git a/src/_nav.js b/src/_nav.js index bfd4c4ca3..aff57b0c0 100644 --- a/src/_nav.js +++ b/src/_nav.js @@ -1,41 +1,34 @@ -/*import React from 'react' -import CIcon from '@coreui/icons-react' -import { - //mine - cilHome, - - // pre existent - cilBell, - cilCalculator, - cilChartPie, - cilCursor, - cilDescription, - cilDrop, - cilExternalLink, - cilNotes, - cilPencil, - cilPuzzle, +import React from 'react' +import { CNavItem, CNavTitle, CNavGroup } from '@coreui/react' +import { CIcon } from '@coreui/icons-react' +import { + cilHome, + cilSettings, + cilAnimal, + cilChart, cilSpeedometer, - cilStar, + cilFolder, + cilDescription, + cilBug } from '@coreui/icons' -import { CNavGroup, CNavItem, CNavTitle } from '@coreui/react' import DynamicNavButtons from './components/DynamicNavButtons' -export const _nav = (departments, onAddDepartment, onAddAsset, dynamicNavItems) => [ - - // mine +export const getNavigation = (departments, onAddDepartment, onAddAsset, dynamicNavItems) => [ + // Main Navigation Title { component: CNavTitle, - name: 'Overview', + name: 'Security Dashboard', }, + // Overview Page { component: CNavItem, name: 'Overview', to: '/overview', - icon: , + icon: , }, + // Asset Management Buttons { component: DynamicNavButtons, props: { @@ -45,512 +38,76 @@ export const _nav = (departments, onAddDepartment, onAddAsset, dynamicNavItems) } }, - - { - component: () => ( -
-
- console.log('Button 1')} - > - Btn 1 - - console.log('Button 2')} - > - Btn 2 - -
-
- ) - }, + // Dynamic Department and Asset Items + ...dynamicNavItems, - // pre existent - { - component: CNavItem, - name: 'Dashboard', - to: '/dashboard', - icon: , - badge: { - color: 'info', - text: 'NEW', - }, - }, - { + // Management Section Title + ...(dynamicNavItems.length > 0 ? [{ component: CNavTitle, - name: 'Theme', - }, - { - component: CNavItem, - name: 'Colors', - to: '/theme/colors', - icon: , - }, + name: 'Management', + }] : []), + + // Data Management { component: CNavItem, - name: 'Typography', - to: '/theme/typography', - icon: , - }, - { - 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', - }, - }, - ], - }, - { - 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', - }, - }, - ], + 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: '/vulnerabilities/scan-results', + badge: { + color: 'danger', + text: 'NEW', + }, }, - }, - { - 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: '/vulnerabilities/cve-database', }, - }, - { - 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: '/vulnerabilities/risk-assessment', + } + ] + }, + { + component: CNavGroup, + name: 'Security Analytics', + icon: , + items: [ + { + component: CNavItem, + name: 'Threat Intelligence', + to: '/analytics/threat-intelligence', }, - }, - { - 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: '/analytics/attack-patterns', }, - }, - { - 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: , - }, - ...dynamicNavItems -]*/ - -//export default _nav - -/* -import React from 'react' -import CIcon from '@coreui/icons-react' -import { cilSpeedometer } from '@coreui/icons' - -const _nav = [ - { - component: 'CNavItem', - name: 'Overview', - to: '/overview', - icon: - } -] - -export default _nav*/ - -import React from 'react' -import { CNavItem } from '@coreui/react' -import { CIcon } from '@coreui/icons-react' -import { cilHome, cilSettings } from '@coreui/icons' -import DynamicNavButtons from './components/DynamicNavButtons' - -export const getNavigation = (departments, onAddDepartment, onAddAsset, dynamicNavItems) => [ - { - component: CNavItem, - name: 'Overview', - to: '/overview', - icon: , - }, - { - component: DynamicNavButtons, - props: { - onAddDepartment, - onAddAsset, - departments + { + component: CNavItem, + name: 'Security Metrics', + to: '/analytics/security-metrics', + } + ] } - }, - ...dynamicNavItems, - - { - component: CNavItem, - name: 'Data Management', - to: '/datamanagement', - icon: , - } + ] : []) ] \ No newline at end of file diff --git a/src/components/AppBreadcrumb.js b/src/components/AppBreadcrumb.js index d37de8ce9..3c48b8fb2 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,70 @@ const AppBreadcrumb = () => { const getBreadcrumbs = (location) => { const breadcrumbs = [] - location.split('/').reduce((prev, curr, index, array) => { + + // 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 !== '') + + 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 '/overview': + icon = cilHome + break + case '/datamanagement': + icon = cilSettings + 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 +89,25 @@ const AppBreadcrumb = () => { return ( - Home + {/* Home/Overview breadcrumb */} + + + Overview + + {breadcrumbs.map((breadcrumb, index) => { return ( + {breadcrumb.icon && ( + + )} {breadcrumb.name} ) @@ -48,4 +116,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..f107a3fb6 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 { cilAnimal, 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 cilAnimal + } + } + 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..a3f57c972 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,361 @@ 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 = [ + { + id: 1, + type: 'critical', + title: 'Critical Vulnerability Detected', + message: 'CVE-2024-1234 found in 3 assets', + timestamp: new Date(Date.now() - 30 * 60 * 1000) + }, + { + id: 2, + type: 'warning', + title: 'Outdated Software Detected', + message: '5 assets need security updates', + timestamp: new Date(Date.now() - 2 * 60 * 60 * 1000) + } + ] + setSecurityAlerts(alerts) + }, [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 cilAnimal + } + } + return ( - - - dispatch({ type: 'set', sidebarShow: !sidebarShow })} - style={{ marginInlineStart: '-14px' }} - > - - - - - - Dashboard - - - - Users - - - Settings - - - - - - - - - - - - - - - - - - - - -
  • -
    -
  • - - - {colorMode === 'dark' ? ( - - ) : colorMode === 'auto' ? ( - - ) : ( - - )} - - - setColorMode('light')} + <> + + + dispatch({ type: 'set', sidebarShow: !sidebarShow })} + style={{ marginInlineStart: '-14px' }} + > + + + + {/* Main Navigation */} + + + + + Overview + + + + + + Management + + + + + {/* Security Tools */} + + {/* Security Alerts */} + + { + e.preventDefault() + setShowAlertsModal(true) + }} + className="position-relative" > - Light - - setColorMode('dark')} + + {securityAlerts.length > 0 && ( + + {securityAlerts.length} + + )} + + + + {/* Vulnerability Scanner */} + + { + e.preventDefault() + // Implement scan functionality + setLastScanTime(new Date()) + alert('Vulnerability scan initiated...') + }} + title="Run Vulnerability Scan" > - Dark - - setColorMode('auto')} + + + + + {/* Security Status Indicator */} + + { + e.preventDefault() + setShowSecurityStatus(true) + }} + className="position-relative" > - Auto - - - -
  • -
    -
  • - -
    -
    - - - -
    + + + {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.title} +
    +

    {alert.message}

    + + {alert.timestamp.toLocaleString()} + +
    + + {alert.type} + +
    + ))} +
    + ) : ( + + + No security alerts at this time. + + )} +
    + + 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 6ebc45f7f..4283e6ed4 100644 --- a/src/components/AppSidebar.js +++ b/src/components/AppSidebar.js @@ -1,314 +1,3 @@ -/*import React from 'react' -import { useSelector, useDispatch } from 'react-redux' - -import { - CCloseButton, - CSidebar, - CSidebarBrand, - CSidebarFooter, - CSidebarHeader, - CSidebarToggler, -} from '@coreui/react' -import CIcon from '@coreui/icons-react' - -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' - -const AppSidebar = () => { - const dispatch = useDispatch() - const unfoldable = useSelector((state) => state.sidebarUnfoldable) - const sidebarShow = useSelector((state) => state.sidebarShow) - - return ( - { - dispatch({ type: 'set', sidebarShow: visible }) - }} - > - - - - - - dispatch({ type: 'set', sidebarShow: false })} - /> - - - - dispatch({ type: 'set', sidebarUnfoldable: !unfoldable })} - /> - - - ) -} - -export default React.memo(AppSidebar) - - -import React, { useState } from 'react' -import { useSelector, useDispatch } from 'react-redux' -import { - CSidebar, - CSidebarBrand, - CSidebarNav, - CNavItem, - CNavGroup, - CButton, - CModal, - CModalHeader, - CModalTitle, - CModalBody, - CModalFooter, - CForm, - CFormInput, - CFormLabel, - CFormSelect, - CRow, - CCol -} from '@coreui/react' -import { cilSpeedometer, cilSettings, cilDevices } from '@coreui/icons' -import CIcon from '@coreui/icons-react' - -const AppSidebar = () => { - const dispatch = useDispatch() - const unfoldable = useSelector((state) => state.sidebarUnfoldable) - const sidebarShow = useSelector((state) => state.sidebarShow) - const [departments, setDepartments] = useState([]) - const [showDepartmentModal, setShowDepartmentModal] = useState(false) - const [showDeviceModal, setShowDeviceModal] = useState(false) - const [newDepartmentName, setNewDepartmentName] = useState('') - const [newDeviceName, setNewDeviceName] = useState('') - const [selectedDepartment, setSelectedDepartment] = useState('') - - // Handle adding new department - const handleAddDepartment = () => { - if (newDepartmentName.trim()) { - const newDepartment = { - id: Date.now(), - name: newDepartmentName.trim(), - devices: [] - } - setDepartments([...departments, newDepartment]) - setNewDepartmentName('') - setShowDepartmentModal(false) - } - } - - // Handle adding new device - const handleAddDevice = () => { - if (newDeviceName.trim() && selectedDepartment) { - setDepartments(prevDepartments => - prevDepartments.map(dept => - dept.id === parseInt(selectedDepartment) - ? { - ...dept, - devices: [...dept.devices, { - id: Date.now(), - name: newDeviceName.trim(), - route: `/device/${Date.now()}` - }] - } - : dept - ) - ) - setNewDeviceName('') - setSelectedDepartment('') - setShowDeviceModal(false) - } - } - - // Generate navigation items - const navigation = [ - { - component: CNavItem, - name: 'Overview', - to: '/dashboard', - icon: - }, - // Add departments as nav groups - ...departments.map(department => ({ - component: CNavGroup, - name: department.name, - icon: , - items: department.devices.map(device => ({ - component: CNavItem, - name: device.name, - to: device.route, - icon: - })) - })) - ] - - return ( - <> - { - dispatch({ type: 'set', sidebarShow: visible }) - }} - > - -
    - Your App Name -
    -
    - - - //{ Overview Item } - - - Overview - - - //{ Empty Space } -
    - - //{ Action Buttons } -
    - - - setShowDepartmentModal(true)} - > - Add Department - - - - setShowDeviceModal(true)} - > - Add Device - - - -
    - - //{ Dynamic Navigation Groups } - {departments.map(department => ( - - - {department.name} - - } - > - {department.devices.map(device => ( - - - {device.name} - - ))} - - ))} -
    -
    - - //{ Add Department Modal } - setShowDepartmentModal(false)}> - - Add New Department - - - -
    - Department Name - setNewDepartmentName(e.target.value)} - placeholder="Enter department name" - /> -
    -
    -
    - - setShowDepartmentModal(false)}> - Cancel - - - Add Department - - -
    - - //{ Add Device Modal } - setShowDeviceModal(false)}> - - Add New Device - - - -
    - Device Name - setNewDeviceName(e.target.value)} - placeholder="Enter device name" - /> -
    -
    - Select Department - setSelectedDepartment(e.target.value)} - > - - {departments.map(dept => ( - - ))} - -
    -
    -
    - - setShowDeviceModal(false)}> - Cancel - - - Add Device - - -
    - - ) -} - -export default AppSidebar*/ - import React from 'react' import { useSelector, useDispatch } from 'react-redux' import { @@ -318,11 +7,11 @@ import { CSidebarFooter, CSidebarHeader, CSidebarToggler, - CSidebarNav, + CBadge } from '@coreui/react' +import CIcon from '@coreui/icons-react' +import { cilAnimal, cilBug } from '@coreui/icons' import { AppSidebarNav } from './AppSidebarNav' -import { logo } from 'src/assets/brand/logo' -import { sygnet } from 'src/assets/brand/sygnet' import { getNavigation } from '../_nav' import { useNavigation } from '../hooks/useNavigation' @@ -332,11 +21,32 @@ const AppSidebar = () => { const sidebarShow = useSelector((state) => state.sidebarShow) // Use our custom navigation hook - const { departments, addDepartment, addAsset, dynamicNavItems } = useNavigation() + 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 ( { }} > - - logo - logo + + +
    +
    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 })} + /> +
    ) 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%; + } +} From 1e2d6b98db4031681c7343b5767423858b6e94ef Mon Sep 17 00:00:00 2001 From: IAmTryingIPromise Date: Sat, 6 Sep 2025 17:33:39 +0300 Subject: [PATCH 12/15] before fw/os version modifications --- src/components/AppFooter.js | 6 +- src/components/AppHeader.js | 4 +- src/components/AppSidebar.js | 4 +- src/components/AppSidebarNav.js | 74 ----- src/routes.js | 18 +- src/views/overview/Overview.js | 502 +++++++++++++++++++++----------- 6 files changed, 340 insertions(+), 268 deletions(-) diff --git a/src/components/AppFooter.js b/src/components/AppFooter.js index f107a3fb6..d0285fabf 100644 --- a/src/components/AppFooter.js +++ b/src/components/AppFooter.js @@ -1,7 +1,7 @@ import React from 'react' import { CFooter, CBadge } from '@coreui/react' import CIcon from '@coreui/icons-react' -import { cilAnimal, cilCheckCircle } from '@coreui/icons' +import { cilShieldAlt, cilCheckCircle } from '@coreui/icons' import { useNavigation } from '../hooks/useNavigation' const AppFooter = () => { @@ -40,14 +40,14 @@ const AppFooter = () => { const getStatusIcon = () => { switch (securityStatus.status) { case 'secure': return cilCheckCircle - default: return cilAnimal + default: return cilShieldAlt } } return (
    - + CyberSec Dashboard © 2025 Security Operations.
    diff --git a/src/components/AppHeader.js b/src/components/AppHeader.js index a3f57c972..a22f1d065 100644 --- a/src/components/AppHeader.js +++ b/src/components/AppHeader.js @@ -134,7 +134,7 @@ const AppHeader = () => { case 'good': return cilCheckCircle case 'warning': return cilTriangle case 'critical': return cilWarning - default: return cilAnimal + default: return cilShieldAlt } } @@ -288,7 +288,7 @@ const AppHeader = () => {
    Security Overview:
    - + {securityMetrics.totalAssets} Assets
    diff --git a/src/components/AppSidebar.js b/src/components/AppSidebar.js index 4283e6ed4..10dfc7f81 100644 --- a/src/components/AppSidebar.js +++ b/src/components/AppSidebar.js @@ -10,7 +10,7 @@ import { CBadge } from '@coreui/react' import CIcon from '@coreui/icons-react' -import { cilAnimal, cilBug } from '@coreui/icons' +import { cilShieldAlt, cilBug } from '@coreui/icons' import { AppSidebarNav } from './AppSidebarNav' import { getNavigation } from '../_nav' import { useNavigation } from '../hooks/useNavigation' @@ -60,7 +60,7 @@ const AppSidebar = () => { > - +
    CyberSec
    Dashboard diff --git a/src/components/AppSidebarNav.js b/src/components/AppSidebarNav.js index 038e96f2d..e63a9e897 100644 --- a/src/components/AppSidebarNav.js +++ b/src/components/AppSidebarNav.js @@ -1,77 +1,3 @@ -/*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' - -export const AppSidebarNav = ({ items }) => { - const navLink = (name, icon, badge, indent = false) => { - return ( - <> - {icon - ? icon - : indent && ( - - - - )} - {name && name} - {badge && ( - - {badge.text} - - )} - - ) - } - - const navItem = (item, index, indent = false) => { - const { component, name, badge, icon, ...rest } = item - const Component = component - return ( - - {rest.to || rest.href ? ( - - {navLink(name, icon, badge, indent)} - - ) : ( - navLink(name, icon, badge, indent) - )} - - ) - } - - const navGroup = (item, index) => { - const { component, name, icon, items, to, ...rest } = item - const Component = component - return ( - - {items?.map((item, index) => - item.items ? navGroup(item, index) : navItem(item, index, true), - )} - - ) - } - - return ( - - {items && - items.map((item, index) => (item.items ? navGroup(item, index) : navItem(item, index)))} - - ) -} - -AppSidebarNav.propTypes = { - items: PropTypes.arrayOf(PropTypes.any).isRequired, -}*/ - import React from 'react' import { NavLink } from 'react-router-dom' import PropTypes from 'prop-types' diff --git a/src/routes.js b/src/routes.js index faffedae9..0bfa3fcb7 100644 --- a/src/routes.js +++ b/src/routes.js @@ -112,20 +112,4 @@ const routes = [ { path: '/widgets', name: 'Widgets', element: Widgets }, ] -export default routes - -/* -import React from 'react' -import { Navigate } from 'react-router-dom' - -// Lazy loading components -const Overview = React.lazy(() => import('./views/overview/Overview')) -const DeviceView = React.lazy(() => import('./views/devices/DeviceView')) - -const routes = [ - { path: '/', exact: true, name: 'Home', element: }, - { path: '/overview', name: 'Overview', element: }, - { path: '/device/:id', name: 'Device', element: }, -] - -export default routes*/ +export default routes \ No newline at end of file diff --git a/src/views/overview/Overview.js b/src/views/overview/Overview.js index 277f4debc..8e0156065 100644 --- a/src/views/overview/Overview.js +++ b/src/views/overview/Overview.js @@ -1,171 +1,4 @@ -/*import React, { useState, useEffect } from 'react' -import classNames from 'classnames' -import { - CCard, - CCardBody, - CCardHeader, - CCol, - CRow, - CTable, - CTableBody, - CTableDataCell, - CTableHead, - CTableHeaderCell, - CTableRow, - CBadge, - CSpinner, - CButton -} from '@coreui/react' -import { useNavigate } from 'react-router-dom' - -const Overview = () => { - const [data, setData] = useState([]) - const [loading, setLoading] = useState(true) - const navgiate = useNavigate() - - // Function to handle navigation to different dashboards - - const handleViewDetails = (itemId) => { - switch(item.department) { - case 'Sales': navigate(`/sales-dashboard/${item.id}`); break; - case 'Marketing': navigate(`/marketing-dashboard/${item.id}`); break; - case 'IT': navigate(`/tech-dashboard/${item.id}`); break; - case 'HR': navigate(`/hr-dashboard/${item.id}`); break; - default: navigate(`/general-dashboard/${item.id}`); - } - } - - // Sample data - replace this with your actual data fetching logic - useEffect(() => { - // Simulate API call - const fetchData = async () => { - try { - // Replace this with your actual API call - const sampleData = [ - { id: 1, deviceName: 'Cisco Switch Catalyst', department: 'Sales', numberOfCVEs: 5, riskLevel: 56, info: 'Asset Details' }, - { id: 2, deviceName: 'Cisco Router', department: 'Sales', numberOfCVEs: 14, riskLevel: 40, info: 'Asset Details' }, - { id: 3, deviceName: 'Cisco Switch E4345', department: 'Developement', numberOfCVEs: 2, riskLevel: 90, info: 'Asset Details' }, - { id: 4, deviceName: 'Random Somthing Else', department: 'HR', numberOfCVEs: 8, riskLevel: 12, info: 'Asset Details' }, - { id: 5, deviceName: 'Not The One You Think', department: 'Support', numberOfCVEs: 10, riskLevel: 78, info: 'Asset Details' }, - ] - - // Sort by score in descending order - const sortedData = sampleData.sort((a, b) => b.riskLevel - a.riskLevel) - setData(sortedData) - setLoading(false) - } catch (error) { - console.error('Error fetching data:', error) - setLoading(false) - } - } - - fetchData() - }, []) - - const getCVEBadgeColor = (riskLevel) => { - if (riskLevel >= 12) return 'danger' - if (riskLevel >= 7) return 'warning' - return 'success' - } - - const getRiskLevelBadgeColor = (riskLevel) => { - if (riskLevel >= 90) return 'danger' - if (riskLevel >= 70) return 'warning' - return 'success' - } - - //const getStatusBadgeColor = (status) => { - // return status === 'Active' ? 'success' : 'secondary' - //} - - if (loading) { - return ( - - - - - -

    Loading dashboard data...

    -
    -
    -
    -
    - ) - } - - return ( - <> - - - - - Dashboard Sorted by Score (Descending) - - -
    - - - - Device Name - Department - CVE # - Risk Level - Asset Details - - - - {data.map((item, index) => ( - - {item.deviceName} - {item.department} - - - {item.numberOfCVEs} - - - - - {item.riskLevel} - - - - handleViewDetails(item.id)} - > - Go to Details - - - - ))} - - -
    -
    -
    -
    -
    - - ) -} - -export default Overview - -// {index + 1}*/ - -import React, { useMemo } from 'react' +/*import React, { useMemo } from 'react' import { useNavigate } from 'react-router-dom' import { CCard, @@ -302,8 +135,8 @@ const Overview = () => { - - {/* Optional: Summary cards */} + */ + {/* Optional: Summary cards */}/* {enrichedAssets.length > 0 && ( @@ -368,4 +201,333 @@ const Overview = () => { ) } +export default Overview*/ + +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 } = useNavigation() + const [expandedAssets, setExpandedAssets] = React.useState({}) + + // Function to determine risk level based on CVSS score + const getCVSSRiskLevel = (cvssScore) => { + if (cvssScore >= 9.0) return 'Critical' + if (cvssScore >= 7.0) return 'High' + if (cvssScore >= 4.0) return 'Medium' + if (cvssScore >= 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 = (cvssScore) => { + if (cvssScore >= 9.0) return 'danger' + if (cvssScore >= 7.0) return 'warning' + if (cvssScore >= 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 + let highestRisk = 'None' + let maxCvss = 0 + + if (hasCVEs) { + maxCvss = Math.max(...cves.map(cve => cve.cvss || 0)) + highestRisk = getCVSSRiskLevel(maxCvss) + } + + return { + ...asset, + cveCount: cves.length, + hasCVEs, + highestRisk, + maxCvss, + cves: cves.sort((a, b) => (b.cvss || 0) - (a.cvss || 0)) // Sort by CVSS descending + } + }) + }, [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] + })) + } + + 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 + Highest Risk + 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.maxCvss > 0 && ( + + (CVSS: {asset.maxCvss}) + + )} + + + handleViewAsset(asset.id)} + > + Full Details + + +
    + + {/* Expandable CVE Details */} + {asset.hasCVEs && ( + + + +
    +
    CVE Details for {asset.name}:
    + + {asset.cves.slice(0, 10).map((cve, index) => ( + +
    + {cve.cve_id} + + CVSS: {cve.cvss || 'N/A'} + + {cve.epss && ( + + EPSS: {(cve.epss * 100).toFixed(1)}% + + )} + + {getCVSSRiskLevel(cve.cvss)} + +
    +
    + ))} + {asset.cves.length > 10 && ( + + ... and {asset.cves.length - 10} more CVEs + + )} +
    +
    +
    +
    +
    + )} +
    + ))} +
    +
    + + )} +
    +
    +
    +
    +
    + ) +} + export default Overview \ No newline at end of file From b8fb20627b6a248c8214fc8da923fae766142840 Mon Sep 17 00:00:00 2001 From: IAmTryingIPromise Date: Wed, 17 Sep 2025 20:29:49 +0300 Subject: [PATCH 13/15] Final changes 1 --- src/components/DynamicNavButtons.js | 176 +++++++++++++++-------- src/hooks/useNavigation.js | 60 +++++--- src/views/assets/AssetView.js | 31 ++-- src/views/overview/Overview.js | 215 ++-------------------------- 4 files changed, 189 insertions(+), 293 deletions(-) diff --git a/src/components/DynamicNavButtons.js b/src/components/DynamicNavButtons.js index 4287d36a4..b0fa86404 100644 --- a/src/components/DynamicNavButtons.js +++ b/src/components/DynamicNavButtons.js @@ -27,6 +27,12 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { // 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(''); @@ -100,24 +106,33 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { }; // Handle CPE match selection - const handleCpeMatchSelect = (cpeMatch) => { + 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 CPE and get full vulnerability data - const scanDeviceByCpe = async (cpe, deviceName, department) => { + // 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-cpe', { + const response = await fetch('http://localhost:8000/api/v1/security/scan-by-os', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ - cpe: cpe, device_name: deviceName, + h_cpe: h_cpe, + vendor: vendor, + model: model, + os_family: osFamily, + version: version, department: department }) }); @@ -134,17 +149,52 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { } }; - // Enhanced confirm handler + // 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) return + if (!selectedCpeMatch || !selectedDepartment || !selectedOsFamily || !osVersion.trim()) return setIsScanning(true) setSearchError('') try { - const scanResults = await scanDeviceByCpe( - selectedCpeMatch.cpe, + const scanResults = await scanDeviceByOs( selectedCpeMatch.device_name, + selectedCpeMatch.cpe, + selectedCpeMatch.vendor, + selectedCpeMatch.model, + selectedOsFamily, + osVersion.trim(), selectedDepartment ) @@ -152,14 +202,23 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { throw new Error(scanResults.error_message || 'Scan failed') } - // Store CPE in the scan results for future refreshes + // Store OS information in the scan results for future refreshes const enhancedResults = { ...scanResults, device: { ...scanResults.device, - department: selectedDepartment // Ensure department is set + department: selectedDepartment, + os_family: selectedOsFamily, + version: osVersion.trim() }, - cpe: selectedCpeMatch.cpe // Store CPE for background refreshes + // 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 @@ -174,55 +233,6 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { setIsScanning(false) } } - /* - const handleAssetConfirm = async () => { - if (!selectedCpeMatch || !selectedDepartment) return; - - setIsScanning(true); - setSearchError(''); - - try { - const scanResults = await scanDeviceByCpe( - selectedCpeMatch.cpe, - selectedCpeMatch.device_name, - selectedDepartment - ); - - if (!scanResults.success) { - throw new Error(scanResults.error_message || 'Scan failed'); - } - - // Create asset object with scan results - const asset = { - id: scanResults.device?.id || `scan-${Date.now()}`, - name: scanResults.device?.name || selectedCpeMatch.device_name, - vendor: scanResults.device?.vendor || selectedCpeMatch.vendor, - model: scanResults.device?.model || selectedCpeMatch.model, - type: scanResults.device?.type || 'Unknown', - department: selectedDepartment, - risk_level: scanResults.device?.risk_level || 0, - cpe: selectedCpeMatch.cpe, - vulnerabilities: { - cves: scanResults.cves || [], - cwes: scanResults.cwes || [], - capecs: scanResults.capecs || [], - attacks: scanResults.attacks || [] - }, - statistics: scanResults.statistics || {}, - scan_time: scanResults.scan_time || 0 - }; - - onAddAsset(asset); - - // Reset form - resetAssetForm(); - setAssetDialogVisible(false); - } catch (error) { - setSearchError(`Failed to scan device: ${error.message}`); - } finally { - setIsScanning(false); - } - };*/ // Reset asset form function const resetAssetForm = () => { @@ -231,6 +241,9 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { setSelectedCpeMatch(null); setShowResults(false); setSearchError(''); + setOsFamilies([]); + setSelectedOsFamily(''); + setOsVersion(''); }; // Handle asset dialog cancel @@ -427,6 +440,45 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => {
    )} + {/* 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 */} @@ -462,7 +514,7 @@ const DynamicNavButtons = ({ onAddDepartment, onAddAsset, departments }) => { {isScanning ? ( <> diff --git a/src/hooks/useNavigation.js b/src/hooks/useNavigation.js index 096c3a110..79fa720eb 100644 --- a/src/hooks/useNavigation.js +++ b/src/hooks/useNavigation.js @@ -64,9 +64,10 @@ export const useNavigation = () => { department: apiResponse.device?.department || '', description: apiResponse.device?.description || '', risk_level: apiResponse.device?.risk_level || 0, + os_family: apiResponse.device?.os_family || '', - // Store CPE for background refreshes - cpe: apiResponse.cpe || '', + // Store scan parameters for background refreshes + scan_params: apiResponse.scan_params || {}, // Store full API response for detailed view vulnerabilities: { @@ -125,22 +126,49 @@ export const useNavigation = () => { if (diffMinutes < 5) continue } - 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 || '', // You might need to store CPE in the asset - device_name: asset.name, - department: asset.department + // 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) + if (response.ok) { + const apiResponse = await response.json() + if (apiResponse.success) { + updateAsset(asset.id, apiResponse) + } } } } catch (error) { diff --git a/src/views/assets/AssetView.js b/src/views/assets/AssetView.js index 5172dfb23..85d0579e2 100644 --- a/src/views/assets/AssetView.js +++ b/src/views/assets/AssetView.js @@ -69,6 +69,17 @@ const AssetDetail = () => { } }, [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 || '', @@ -261,7 +272,7 @@ const AssetDetail = () => { ) : ( - ✅ No Vulnerable CVEs Found - This device appears to be secure with no known critical vulnerabilities. + ✅ No Vulnerabilities Found - This device appears to be secure with no known critical vulnerabilities. )} @@ -272,25 +283,25 @@ const AssetDetail = () => {
    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}) )} @@ -298,7 +309,7 @@ const AssetDetail = () => { {enrichedAsset.vulnerabilities.cves?.length > 0 && ( - +
    {enrichedAsset.vulnerabilities.cves.map((cve, index) => ( @@ -336,7 +347,7 @@ const AssetDetail = () => { )} {enrichedAsset.vulnerabilities.cwes?.length > 0 && ( - +
    {enrichedAsset.vulnerabilities.cwes.map((cwe, index) => ( @@ -357,7 +368,7 @@ const AssetDetail = () => { )} {enrichedAsset.vulnerabilities.capecs?.length > 0 && ( - +
    {enrichedAsset.vulnerabilities.capecs.map((capec, index) => ( @@ -390,7 +401,7 @@ const AssetDetail = () => { )} {enrichedAsset.vulnerabilities.attacks?.length > 0 && ( - +
    {enrichedAsset.vulnerabilities.attacks.map((attack, index) => ( diff --git a/src/views/overview/Overview.js b/src/views/overview/Overview.js index 8e0156065..73da56816 100644 --- a/src/views/overview/Overview.js +++ b/src/views/overview/Overview.js @@ -1,208 +1,3 @@ -/*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 -} from '@coreui/react' -import { useNavigation } from '../../hooks/useNavigation' - -const Overview = () => { - const navigate = useNavigate() - const { assets } = useNavigation() - - // Generate random CVE numbers and risk levels for each asset - const enrichedAssets = useMemo(() => { - return assets.map(asset => { - // Generate a consistent random CVE based on asset ID (so it doesn't change on re-render) - const seed = asset.id.split('').reduce((a, b) => a + b.charCodeAt(0), 0) - const cveYear = 2020 + (seed % 5) // CVE years between 2020-2024 - const cveNumber = (seed % 9999) + 1000 // CVE numbers between 1000-9999 - const cve = `CVE-${cveYear}-${cveNumber}` - - // Generate consistent risk level - const riskLevels = ['Low', 'Medium', 'High', 'Critical'] - const riskColors = ['success', 'warning', 'danger', 'dark'] - const riskIndex = seed % 4 - const riskLevel = riskLevels[riskIndex] - const riskColor = riskColors[riskIndex] - - return { - ...asset, - cve, - riskLevel, - riskColor - } - }) - }, [assets]) - - const handleViewAsset = (assetId) => { - navigate(`/asset/${assetId}`) - } - - 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 ( - - - - - -
    -

    Vulnerabilities Overview

    - - {assets.length} Total Vulnerabilies - -
    -
    - - {enrichedAssets.length === 0 ? ( -
    -
    No Vulnerable Assets Found
    -

    Start by adding departments and assets using the sidebar buttons.

    -
    - ) : ( - - - - Device Name - Department - CVE ID - CVE Risk Level - More Details - - - - {enrichedAssets.map((asset) => ( - - -
    - {asset.name} -
    - - {asset.vendor} - {asset.deviceType} - -
    -
    - - - {asset.department} - - - - {asset.cve} - - - - {asset.riskLevel} - - - - handleViewAsset(asset.id)} - > - View Details - - -
    - ))} -
    -
    - )} -
    -
    -
    -
    - */ - {/* Optional: Summary cards */}/* - {enrichedAssets.length > 0 && ( - - - - -
    -
    -
    - {enrichedAssets.filter(a => a.riskLevel === 'Low').length} -
    -
    Low Risk
    -
    -
    -
    -
    -
    - - - -
    -
    -
    - {enrichedAssets.filter(a => a.riskLevel === 'Medium').length} -
    -
    Medium Risk
    -
    -
    -
    -
    -
    - - - -
    -
    -
    - {enrichedAssets.filter(a => a.riskLevel === 'High').length} -
    -
    High Risk
    -
    -
    -
    -
    -
    - - - -
    -
    -
    - {enrichedAssets.filter(a => a.riskLevel === 'Critical').length} -
    -
    Critical Risk
    -
    -
    -
    -
    -
    -
    - )} -
    - ) -} - -export default Overview*/ - import React, { useMemo } from 'react' import { useNavigate } from 'react-router-dom' import { @@ -310,6 +105,12 @@ const Overview = () => { })) } + const handleReset = () => { + localStorage.clear() + sessionStorage.clear() + window.location.reload() + } + return ( {/* Summary Dashboard */} @@ -478,6 +279,10 @@ const Overview = () => { + + + Reset App + {/* Expandable CVE Details */} {asset.hasCVEs && ( From 5a9c4e50ae9756c8ea5b3878404f0c44ec59491c Mon Sep 17 00:00:00 2001 From: IAmTryingIPromise Date: Mon, 22 Sep 2025 15:32:08 +0300 Subject: [PATCH 14/15] Final changes 2 --- src/_nav.js | 42 +++- src/components/AppBreadcrumb.js | 26 ++- src/components/AppHeader.js | 189 +++++++++-------- src/views/assets/AssetView.js | 355 ++++++++++++++++++++++++-------- src/views/overview/Overview.js | 46 ++--- 5 files changed, 445 insertions(+), 213 deletions(-) diff --git a/src/_nav.js b/src/_nav.js index aff57b0c0..1e470c3b6 100644 --- a/src/_nav.js +++ b/src/_nav.js @@ -69,21 +69,32 @@ export const getNavigation = (departments, onAddDepartment, onAddAsset, dynamicN { component: CNavItem, name: 'Scan Results', - to: '/vulnerabilities/scan-results', + to: '#', + disabled: true, badge: { - color: 'danger', - text: 'NEW', + color: 'secondary', + text: 'COMING SOON', }, }, { component: CNavItem, name: 'CVE Database', - to: '/vulnerabilities/cve-database', + to: '#', + disabled: true, + badge: { + color: 'secondary', + text: 'COMING SOON', + }, }, { component: CNavItem, name: 'Risk Assessment', - to: '/vulnerabilities/risk-assessment', + to: '#', + disabled: true, + badge: { + color: 'secondary', + text: 'COMING SOON', + }, } ] }, @@ -95,17 +106,32 @@ export const getNavigation = (departments, onAddDepartment, onAddAsset, dynamicN { component: CNavItem, name: 'Threat Intelligence', - to: '/analytics/threat-intelligence', + to: '#', + disabled: true, + badge: { + color: 'secondary', + text: 'COMING SOON', + }, }, { component: CNavItem, name: 'Attack Patterns', - to: '/analytics/attack-patterns', + to: '#', + disabled: true, + badge: { + color: 'secondary', + text: 'COMING SOON', + }, }, { component: CNavItem, name: 'Security Metrics', - to: '/analytics/security-metrics', + to: '#', + disabled: true, + badge: { + color: 'secondary', + text: 'COMING SOON', + }, } ] } diff --git a/src/components/AppBreadcrumb.js b/src/components/AppBreadcrumb.js index 3c48b8fb2..7328ba8c3 100644 --- a/src/components/AppBreadcrumb.js +++ b/src/components/AppBreadcrumb.js @@ -19,6 +19,11 @@ const AppBreadcrumb = () => { const getBreadcrumbs = (location) => { const breadcrumbs = [] + // 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) @@ -44,6 +49,11 @@ const AppBreadcrumb = () => { // 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}` let routeName = getRouteName(currentPathname, routes) @@ -51,11 +61,9 @@ const AppBreadcrumb = () => { // Set appropriate icons for known routes switch (currentPathname) { - case '/overview': - icon = cilHome - break case '/datamanagement': icon = cilSettings + routeName = 'Data Management' break default: if (departments.includes(curr)) { @@ -89,11 +97,13 @@ const AppBreadcrumb = () => { return ( - {/* Home/Overview breadcrumb */} - - - Overview - + {/* Home/Overview breadcrumb - only show if not already on overview */} + {currentLocation !== '/' && currentLocation !== '/overview' && ( + + + Overview + + )} {breadcrumbs.map((breadcrumb, index) => { return ( diff --git a/src/components/AppHeader.js b/src/components/AppHeader.js index a22f1d065..dbb3249e1 100644 --- a/src/components/AppHeader.js +++ b/src/components/AppHeader.js @@ -101,23 +101,37 @@ const AppHeader = () => { // Simulate security alerts (in real app, this would come from your backend) useEffect(() => { - const alerts = [ - { - id: 1, - type: 'critical', - title: 'Critical Vulnerability Detected', - message: 'CVE-2024-1234 found in 3 assets', - timestamp: new Date(Date.now() - 30 * 60 * 1000) - }, - { - id: 2, - type: 'warning', - title: 'Outdated Software Detected', - message: '5 assets need security updates', - timestamp: new Date(Date.now() - 2 * 60 * 60 * 1000) + 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 } - ] - setSecurityAlerts(alerts) + return b.timestamp - a.timestamp + }) + + setSecurityAlerts(alerts.slice(0, 20)) // Limit to 20 most critical }, [assets]) const getSecurityStatusColor = () => { @@ -166,72 +180,58 @@ const AppHeader = () => { {/* Security Tools */} - - {/* Security Alerts */} - - { - e.preventDefault() - setShowAlertsModal(true) - }} - className="position-relative" - > - - {securityAlerts.length > 0 && ( + + {/* 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" + > + - {securityAlerts.length} + {securityMetrics.overallRiskScore} - )} - - - - {/* Vulnerability Scanner */} - - { - e.preventDefault() - // Implement scan functionality - setLastScanTime(new Date()) - alert('Vulnerability scan initiated...') - }} - title="Run Vulnerability Scan" - > - - - - - {/* Security Status Indicator */} - - { - e.preventDefault() - setShowSecurityStatus(true) - }} - className="position-relative" - > - - - {securityMetrics.overallRiskScore} - - - - + + + {/* Theme Switcher */} @@ -331,23 +331,42 @@ const AppHeader = () => { icon={alert.type === 'critical' ? cilWarning : cilTriangle} className={alert.type === 'critical' ? 'text-danger' : 'text-warning'} /> - {alert.title} + {alert.cveId} + on {alert.assetName}
    -

    {alert.message}

    +
    + = 9.0 ? 'danger' : 'warning'} + > + CVSS: {alert.cvssScore} + + + {alert.type.toUpperCase()} + +
    +

    {alert.message}

    {alert.timestamp.toLocaleString()}
    - - {alert.type} - + { + setShowAlertsModal(false) + navigate(`/asset/${alert.assetId}`) + }} + > + View Asset + ))}
    ) : ( - No security alerts at this time. + No critical or high-risk vulnerabilities found. )} diff --git a/src/views/assets/AssetView.js b/src/views/assets/AssetView.js index 85d0579e2..b533a6cf4 100644 --- a/src/views/assets/AssetView.js +++ b/src/views/assets/AssetView.js @@ -25,6 +25,7 @@ const AssetDetail = () => { const { assetId } = useParams() const navigate = useNavigate() const { assets } = useNavigation() + const [expandedItems, setExpandedItems] = useState({}) const asset = assets.find(a => a.id === assetId) @@ -59,10 +60,10 @@ const AssetDetail = () => { // Calculate risk level based on vulnerabilities if they exist riskLevel: hasVulnerabilities && asset.vulnerabilities.cves?.length > 0 ? (() => { - const maxCvss = Math.max(...asset.vulnerabilities.cves.map(cve => cve.cvss || 0)) - if (maxCvss >= 9.0) return 'Critical' - if (maxCvss >= 7.0) return 'High' - if (maxCvss >= 4.0) return 'Medium' + const mexRiskLevel = Math.max(...asset.vulnerabilities.cves.map(cve => cve.riskLevel || 0)) + if (mexRiskLevel >= 9.0) return 'Critical' + if (mexRiskLevel >= 7.0) return 'High' + if (mexRiskLevel >= 4.0) return 'Medium' return 'Low' })() : 'Secure' @@ -97,6 +98,14 @@ const AssetDetail = () => { } }, [enrichedAsset]) + const toggleItemExpansion = (type, index) => { + const key = `${type}-${index}` + setExpandedItems(prev => ({ + ...prev, + [key]: !prev[key] + })) + } + if (!enrichedAsset) { return ( @@ -312,35 +321,102 @@ const AssetDetail = () => {
    - {enrichedAsset.vulnerabilities.cves.map((cve, index) => ( - -
    -
    -
    - {cve.cve_id} - = 9.0 ? 'danger' : - cve.cvss >= 7.0 ? 'warning' : - cve.cvss >= 4.0 ? 'info' : 'secondary' - } - > - CVSS: {cve.cvss || 'N/A'} - - {cve.epss && ( - - EPSS: {(cve.epss * 100).toFixed(1)}% + {enrichedAsset.vulnerabilities.cves.map((cve, index) => { + const isExpanded = expandedItems[`cve-${index}`] + return ( + +
    toggleItemExpansion('cve', index)} + style={{ cursor: 'pointer' }} + > +
    +
    + {cve.cve_id} + = 9.0 ? 'danger' : + cve.cvss >= 7.0 ? 'warning' : + cve.cvss >= 4.0 ? 'info' : 'secondary' + } + > + CVSS: {cve.cvss || 'N/A'} + {cve.epss && ( + + EPSS: {cve.impactscore} + + )} + + {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'}
    + {cve.cvss_vector && ( + <> + CVSS Vector: {cve.cvss_vector}
    + + )} + {cve.epss && ( + <> + Risk Level: {(cve.epss).toFixed(2)}%
    + + )} +
    + + {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: + +
    + )} +
    )}
    -

    - {cve.description?.substring(0, 200)} - {cve.description?.length > 200 && '...'} -

    -
    - - ))} + + ) + })}
    @@ -350,18 +426,49 @@ const AssetDetail = () => {
    - {enrichedAsset.vulnerabilities.cwes.map((cwe, index) => ( - -
    - {cwe.cwe_id} - {cwe.name} -
    -

    - {cwe.description?.substring(0, 150)} - {cwe.description?.length > 150 && '...'} -

    -
    - ))} + {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}
    + + )} +
    + )} +
    +
    + ) + })}
    @@ -371,30 +478,61 @@ const AssetDetail = () => {
    - {enrichedAsset.vulnerabilities.capecs.map((capec, index) => ( - -
    -
    -
    - {capec.capec_id} - {capec.name} + {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'}
    +
    +
    +
    + )} +
    -
    - - Severity: {capec.typical_severity || 'Unknown'} - - - Likelihood: {capec.likelihood_of_attack || 'Unknown'} - -
    -

    - {capec.description?.substring(0, 150)} - {capec.description?.length > 150 && '...'} -

    -
    - - ))} + + ) + })}
    @@ -404,34 +542,73 @@ const AssetDetail = () => {
    - {enrichedAsset.vulnerabilities.attacks.map((attack, index) => ( - -
    -
    -
    - {attack.technique_id} - {attack.name} + {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)}...
    + + )} +
    +
    +
    + )} +
    -
    - {attack.tactics && ( - - {attack.tactics} - - )} - {attack.platforms && ( - - {attack.platforms.length > 20 ? attack.platforms.substring(0, 20) + '...' : attack.platforms} - - )} -
    -

    - {attack.description?.substring(0, 150)} - {attack.description?.length > 150 && '...'} -

    -
    - - ))} + + ) + })}
    diff --git a/src/views/overview/Overview.js b/src/views/overview/Overview.js index 73da56816..0f492ca3d 100644 --- a/src/views/overview/Overview.js +++ b/src/views/overview/Overview.js @@ -28,11 +28,11 @@ const Overview = () => { const [expandedAssets, setExpandedAssets] = React.useState({}) // Function to determine risk level based on CVSS score - const getCVSSRiskLevel = (cvssScore) => { - if (cvssScore >= 9.0) return 'Critical' - if (cvssScore >= 7.0) return 'High' - if (cvssScore >= 4.0) return 'Medium' - if (cvssScore >= 0.1) return 'Low' + 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' } @@ -48,10 +48,10 @@ const Overview = () => { } // Function to get CVSS badge color - const getCVSSBadgeColor = (cvssScore) => { - if (cvssScore >= 9.0) return 'danger' - if (cvssScore >= 7.0) return 'warning' - if (cvssScore >= 4.0) return 'info' + const getCVSSBadgeColor = (riskLevel) => { + if (riskLevel >= 9.0) return 'danger' + if (riskLevel >= 7.0) return 'warning' + if (riskLevel >= 4.0) return 'info' return 'success' } @@ -63,11 +63,11 @@ const Overview = () => { // Calculate highest risk level let highestRisk = 'None' - let maxCvss = 0 + let maxRiskLevel = 0 if (hasCVEs) { - maxCvss = Math.max(...cves.map(cve => cve.cvss || 0)) - highestRisk = getCVSSRiskLevel(maxCvss) + maxRiskLevel = Math.max(...cves.map(cve => cve.riskLevel || 0)) + highestRisk = getCVSSRiskLevel(maxRiskLevel) } return { @@ -75,8 +75,8 @@ const Overview = () => { cveCount: cves.length, hasCVEs, highestRisk, - maxCvss, - cves: cves.sort((a, b) => (b.cvss || 0) - (a.cvss || 0)) // Sort by CVSS descending + maxRiskLevel, + cves: cves.sort((a, b) => (b.riskLevel || 0) - (a.riskLevel || 0)) // Sort by Risk Level descending } }) }, [assets]) @@ -104,13 +104,13 @@ const Overview = () => { [assetId]: !prev[assetId] })) } - +/* const handleReset = () => { localStorage.clear() sessionStorage.clear() window.location.reload() } - +*/ return ( {/* Summary Dashboard */} @@ -204,7 +204,7 @@ const Overview = () => { Department Security Status Vulnerability Count - Highest Risk + Risk Level Actions @@ -262,9 +262,9 @@ const Overview = () => { {asset.highestRisk} - {asset.maxCvss > 0 && ( + {asset.maxRiskLevel > 0 && ( - (CVSS: {asset.maxCvss}) + (CVSS: {asset.maxRiskLevel}) )} @@ -279,11 +279,11 @@ const Overview = () => { - +{/* Reset App - +*/} {/* Expandable CVE Details */} {asset.hasCVEs && ( @@ -299,9 +299,9 @@ const Overview = () => { CVSS: {cve.cvss || 'N/A'} - {cve.epss && ( + {cve.riskLevel && ( - EPSS: {(cve.epss * 100).toFixed(1)}% + Risk Level: {cve.riskLevel} )} From b64a9f84ecb328962ac148a68f37635d071f884e Mon Sep 17 00:00:00 2001 From: IAmTryingIPromise Date: Tue, 23 Sep 2025 17:09:24 +0300 Subject: [PATCH 15/15] Final changes 3 --- src/views/assets/AssetView.js | 51 ++++++++++++++++++----- src/views/overview/Overview.js | 75 +++++++++++++++++++++++++--------- 2 files changed, 96 insertions(+), 30 deletions(-) diff --git a/src/views/assets/AssetView.js b/src/views/assets/AssetView.js index b533a6cf4..6750a9a7c 100644 --- a/src/views/assets/AssetView.js +++ b/src/views/assets/AssetView.js @@ -60,10 +60,11 @@ const AssetDetail = () => { // Calculate risk level based on vulnerabilities if they exist riskLevel: hasVulnerabilities && asset.vulnerabilities.cves?.length > 0 ? (() => { - const mexRiskLevel = Math.max(...asset.vulnerabilities.cves.map(cve => cve.riskLevel || 0)) - if (mexRiskLevel >= 9.0) return 'Critical' - if (mexRiskLevel >= 7.0) return 'High' - if (mexRiskLevel >= 4.0) return 'Medium' + // 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' @@ -333,6 +334,8 @@ const AssetDetail = () => {
    {cve.cve_id} + + {/* CVSS Score Badge */} = 9.0 ? 'danger' : @@ -342,15 +345,40 @@ const AssetDetail = () => { > CVSS: {cve.cvss || 'N/A'} - {cve.epss && ( + + {/* 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) && ( - EPSS: {cve.impactscore} + Exploit: {cve.exploitability_score.toFixed(2)} )} + {isExpanded ? 'Less' : 'More'}
    +

    {isExpanded ? cve.description @@ -364,16 +392,17 @@ const AssetDetail = () => { 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.epss && ( - <> - Risk Level: {(cve.epss).toFixed(2)}%
    - - )}
    {cve.published_date && ( diff --git a/src/views/overview/Overview.js b/src/views/overview/Overview.js index 0f492ca3d..915ec1aef 100644 --- a/src/views/overview/Overview.js +++ b/src/views/overview/Overview.js @@ -24,7 +24,7 @@ import { useNavigation } from '../../hooks/useNavigation' const Overview = () => { const navigate = useNavigate() - const { assets } = useNavigation() + const { assets, departments } = useNavigation() const [expandedAssets, setExpandedAssets] = React.useState({}) // Function to determine risk level based on CVSS score @@ -61,12 +61,13 @@ const Overview = () => { const cves = asset.vulnerabilities?.cves || [] const hasCVEs = cves.length > 0 - // Calculate highest risk level + // Calculate highest risk level - Fix field names let highestRisk = 'None' let maxRiskLevel = 0 if (hasCVEs) { - maxRiskLevel = Math.max(...cves.map(cve => cve.riskLevel || 0)) + // 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) } @@ -76,7 +77,8 @@ const Overview = () => { hasCVEs, highestRisk, maxRiskLevel, - cves: cves.sort((a, b) => (b.riskLevel || 0) - (a.riskLevel || 0)) // Sort by Risk Level descending + // 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]) @@ -216,7 +218,7 @@ const Overview = () => {

    {asset.name}
    - + {asset.vendor} {asset.model && `- ${asset.model}`}
    @@ -263,7 +265,7 @@ const Overview = () => { {asset.highestRisk} {asset.maxRiskLevel > 0 && ( - + (CVSS: {asset.maxRiskLevel}) )} @@ -293,20 +295,55 @@ const Overview = () => {
    CVE Details for {asset.name}:
    {asset.cves.slice(0, 10).map((cve, index) => ( - -
    - {cve.cve_id} - - CVSS: {cve.cvss || 'N/A'} - - {cve.riskLevel && ( - - Risk Level: {cve.riskLevel} + +
    +
    + {cve.cve_id} + + {/* CVSS Badge */} + + CVSS: {cve.cvss || 'N/A'} - )} - - {getCVSSRiskLevel(cve.cvss)} - + + {/* 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 ? '...' : ''} +
    ))}