From 0793e8d200fa09b796b55d1c3083c9bb039689e8 Mon Sep 17 00:00:00 2001 From: Darla Maestas <dnm66@drexel.edu> Date: Fri, 3 May 2024 16:26:13 -0400 Subject: [PATCH] darla final push --- backend/db.sqlite3 | Bin 561152 -> 561152 bytes backend/requirements.txt | 3 +- backend/watchstone_backend/urls.py | 3 +- frontend/src/_nav.js | 60 +------ .../components/header/AppHeaderDropdown.js | 47 ----- frontend/src/scss/_custom.scss | 41 +++++ frontend/src/scss/_variables.scss | 13 +- frontend/src/scss/style.scss | 5 + .../NetworkScanner/RecommendationComponent.js | 46 ++--- frontend/src/views/dashboard/CVEUpdates.js | 35 ++-- frontend/src/views/emails/BreachesEmails.js | 72 +++++--- frontend/src/views/pages/login/Login.js | 4 +- frontend/src/views/pages/register/Register.js | 2 +- .../src/views/passwords/BreachesPasswords.js | 25 ++- frontend/src/views/shodan/shodanscan.js | 169 +++++++++--------- frontend/src/views/widgets/WidgetsDropdown.js | 69 +++++-- 16 files changed, 335 insertions(+), 259 deletions(-) diff --git a/backend/db.sqlite3 b/backend/db.sqlite3 index 2640fc9a9b2d3bcc229cdaa25a55f92addba7a93..35040fd9bd153f9ce7316d0ce69219812facd16b 100644 GIT binary patch delta 925 zcmZozpwzHHX@WGP;Y1l{Rzn88vd)bu?0Sp>lXLXy8D%EF(KBTWk>_J}W}IfnEX}4W z&CP7hIGxX)S-d%3e|x+><I)QPY<ve9_|Njs;LqSU<om&QaI;{;T0S;YRTgVUhUphB zFp5hV85o)98kp)D7%Lc>TNxNz85`&s7?@j{nr#2U$HWuJ#m1k^!2g23o<DgzPXbdY zKZ`WT8jxN_)$R59OsxvcGTi&8vrk|$W#N9#y??WzLOpkYnhXnrtYb(~scDIEiXyTl zmR1IaR;Ffp#%31ghDK`AAax~p)EOF?SQr^>=byv$)tFg^Z_9M{3rw;yd|UY4_%HJB z;$Of&kw1%HnEwku2Y(gcLH_mpt(yfKO8BIBm}MDZ_RD}cFiWJjpS#CoCdkXm=+D4? zmZO%nh`FC}yPyD*9V1tZZ3sJqwW4N+G&poLj0_CTbqx%44Ge=a%TkM+GfOIcQ&Tb% zJ@XX267xzEiz*dBN_3`Y|7X$+G&e9YGBdC+F)%gK&nPJ=D7Mnq&rM9uPE9RHEz(QQ z&(%-L$xqiW$S*F@H#WA^EiOpR%}p&zPb^8*Ey+mLO-szl(alfOP0Z6x%`M0*N}X=F ziAm8dQ6V?6xHz*cRiUIPzbsWDKTRPeu_RF;ttdZN0qiaXki!(fE>kE^ELKR%FU?C) zNUbQyFHTKS$j{5k%uAi_*u^9q43R0xS4hs!D@n}EQ%KCo%`YxdFw`?qNXpE~$;{7F zC`wJvFG?v^$jnR5DNV`DOIJwEP0Y+uNX$!7@C;B$Oi3w9EiO(i)=|hy%uOwx&MU(t z8JC!o19Dh-MrLvb*bSv1s}qYA3KEM-Kr)GW3aNSdMY;K<#R?#gE94iX7A2Ns=I7}t z_~#|3Dx{?9rj!=sWF{w;q^9U7lw_n9r7Dzz2xH6X2LG58mC7?qGBWc(GH}0v0z$7j zAs7^EvTeEGSlccp&2*TNlOu?o!B|nU+jF|$MMmN6{a=|%xtIm`*{8BI%d+r`^0RLi yY`D)S!y~}LAj<(uDLnij9xSP}EAunAEAz9oEAz9qEAz9pEAz8&SLWxq+W-KJg%ZsG delta 260 zcmZozpwzHHX@WGP)<hX+RxJj-aIcLi?0SrRlXLXy8Kow_(KBTWlILS~W}IfnEX}4O z&CP7hIGxX)S-d%3e|x+><I)R!tb9Kh_|Njs;LqSU<omH%u;Cfs_8)vqJb@gn{Phg{ zFZk=X^Cd8a@=tb9klS9L&(x~GEXBh=oqYn6>1IQP%iP=f=P-RWW|rbRH=X?gldJ^4 z8~;WAUHl99C-P_U3-f>B=ism6U(esVS+HRe-}ZC&n9KyZm|2S$xX*IbvKDPNR7hdv zYU2oEXE0Wj?Dm{4c#%<fd;eFaQZ8mbe&wm`%(9yW8~*dPEAunAEAz9oEAz9qEAz9p OEAz8&SLWxq+W-J7xJbbO diff --git a/backend/requirements.txt b/backend/requirements.txt index e5169064d..278b0d548 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -14,4 +14,5 @@ shodan openai==1.25.0 requests netifaces -python-nmap \ No newline at end of file +python-nmap +feedparser \ No newline at end of file diff --git a/backend/watchstone_backend/urls.py b/backend/watchstone_backend/urls.py index e8a725864..eee0c2e8b 100644 --- a/backend/watchstone_backend/urls.py +++ b/backend/watchstone_backend/urls.py @@ -13,6 +13,7 @@ urlpatterns = [ path('haveibeenpwned/passwords/<str:identifier>/', breached_password_info, name='breached_password_info'), path('', include('shodan_scan.urls')), path('api/networkscanner/', include('network_scanner.urls')), - path('', include('rssfeeds.urls')), # Include the rssfeeds app URLs + path('', include('rssfeeds.urls')), + # Include the rssfeeds app URLs # Add other URL patterns as needed ] diff --git a/frontend/src/_nav.js b/frontend/src/_nav.js index fa233e420..5360df4df 100644 --- a/frontend/src/_nav.js +++ b/frontend/src/_nav.js @@ -9,11 +9,14 @@ import { cilDrop, cilNotes, cilPencil, + cilSearch, cilPuzzle, cilSpeedometer, cilStar, cilShieldAlt, cilEnvelopeLetter, + cilLibraryAdd, + cilEnvelopeOpen, } from '@coreui/icons' import { CNavGroup, CNavItem, CNavTitle } from '@coreui/react' @@ -25,7 +28,7 @@ const _nav = [ icon: <CIcon icon={cilSpeedometer} customClassName="nav-icon" />, badge: { color: 'info', - text: 'NEW', + text: '', }, }, { @@ -36,13 +39,13 @@ const _nav = [ component: CNavItem, name: 'Add Email', to: '/email-breaches/add', - icon: <CIcon icon={cilShieldAlt} customClassName="nav-icon" />, + icon: <CIcon icon={cilLibraryAdd} customClassName="nav-icon" />, }, { component: CNavItem, name: 'Tracked Emails', to: '/email-breaches/view', - icon: <CIcon icon={cilShieldAlt} customClassName="nav-icon" />, + icon: <CIcon icon={cilSearch} customClassName="nav-icon" />, }, { component: CNavTitle, @@ -52,13 +55,13 @@ const _nav = [ component: CNavItem, name: 'Add Password', to: '/password-breaches/add', - icon: <CIcon icon={cilShieldAlt} customClassName="nav-icon" />, + icon: <CIcon icon={cilLibraryAdd} customClassName="nav-icon" />, }, { component: CNavItem, name: 'Tracked Passwords', to: '/password-breaches/view', - icon: <CIcon icon={cilShieldAlt} customClassName="nav-icon" />, + icon: <CIcon icon={cilSearch} customClassName="nav-icon" />, }, { component: CNavTitle, @@ -80,53 +83,6 @@ const _nav = [ to: '/recommendations', icon: <CIcon icon={cilEnvelopeLetter} customClassName="nav-icon" />, }, - { - component: CNavItem, - name: 'Widgets', - to: '/widgets', - icon: <CIcon icon={cilCalculator} customClassName="nav-icon" />, - badge: { - color: 'info', - text: 'NEW', - }, - }, - { - component: CNavTitle, - name: 'Extras', - }, - { - component: CNavGroup, - name: 'Pages', - icon: <CIcon icon={cilStar} customClassName="nav-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: <CIcon icon={cilDescription} customClassName="nav-icon" />, - }, ] export default _nav diff --git a/frontend/src/components/header/AppHeaderDropdown.js b/frontend/src/components/header/AppHeaderDropdown.js index 12dc4f0e9..3c4d65c06 100644 --- a/frontend/src/components/header/AppHeaderDropdown.js +++ b/frontend/src/components/header/AppHeaderDropdown.js @@ -30,58 +30,11 @@ const AppHeaderDropdown = () => { <CAvatar src={avatar8} size="md" /> </CDropdownToggle> <CDropdownMenu className="pt-0" placement="bottom-end"> - <CDropdownHeader className="bg-light fw-semibold py-2">Account</CDropdownHeader> - <CDropdownItem href="#"> - <CIcon icon={cilBell} className="me-2" /> - Updates - <CBadge color="info" className="ms-2"> - 42 - </CBadge> - </CDropdownItem> - <CDropdownItem href="#"> - <CIcon icon={cilEnvelopeOpen} className="me-2" /> - Messages - <CBadge color="success" className="ms-2"> - 42 - </CBadge> - </CDropdownItem> - <CDropdownItem href="#"> - <CIcon icon={cilTask} className="me-2" /> - Tasks - <CBadge color="danger" className="ms-2"> - 42 - </CBadge> - </CDropdownItem> - <CDropdownItem href="#"> - <CIcon icon={cilCommentSquare} className="me-2" /> - Comments - <CBadge color="warning" className="ms-2"> - 42 - </CBadge> - </CDropdownItem> <CDropdownHeader className="bg-light fw-semibold py-2">Settings</CDropdownHeader> <CDropdownItem href="#/profile"> <CIcon icon={cilUser} className="me-2" /> Profile </CDropdownItem> - <CDropdownItem href="#"> - <CIcon icon={cilSettings} className="me-2" /> - Settings - </CDropdownItem> - <CDropdownItem href="#"> - <CIcon icon={cilCreditCard} className="me-2" /> - Payments - <CBadge color="secondary" className="ms-2"> - 42 - </CBadge> - </CDropdownItem> - <CDropdownItem href="#"> - <CIcon icon={cilFile} className="me-2" /> - Projects - <CBadge color="primary" className="ms-2"> - 42 - </CBadge> - </CDropdownItem> <CDropdownDivider /> <CDropdownItem href="#"> <CIcon icon={cilLockLocked} className="me-2" /> diff --git a/frontend/src/scss/_custom.scss b/frontend/src/scss/_custom.scss index 8b1378917..759402d69 100644 --- a/frontend/src/scss/_custom.scss +++ b/frontend/src/scss/_custom.scss @@ -1 +1,42 @@ +.w-15{ + width:15%!important; +} +.table-responsive-1 { + overflow: scroll!important; +} + +.scroll-hint { + display: none; + } + +::-webkit-scrollbar { + height: 4px; /* height of horizontal scrollbar ← You're missing this */ + width: 4px; /* width of vertical scrollbar */ + border: 1px solid #d5d5d5; + } + + @media (max-width: 787px) { + .scroll-hint { + display: block; + text-align: center; + padding: 10px; + background-color: #f8f9fa; + } + + + } + + .tertiary-background { + background-color: $tertiary!important; + } + + .input-group-text { + color:$tertiary!important; + } + + + .form-control:focus { +border-color: $tertiary!important; +box-shadow: 0 0 0 0.25rem rgb(142 82 245 / 22%); + } \ No newline at end of file diff --git a/frontend/src/scss/_variables.scss b/frontend/src/scss/_variables.scss index 961322c7b..0fc31e5ae 100644 --- a/frontend/src/scss/_variables.scss +++ b/frontend/src/scss/_variables.scss @@ -76,15 +76,18 @@ // fusv-disable // $primary-dark: #1f1498 !default; -// $primary-base: #321fdb !default; +$primary-base: #122C5A !default; // $primary-50: #988fed !default; // $primary-25: #ccc7f6 !default; // $secondary-dark: #212233 !default; -// $secondary-base: #9da5b1 !default; +$secondary-base: #00C0A1 !default; // $secondary-50: #9da5b1 !default; // $secondary-25: #ced2d8 !default; +$tertiary: #8E52F5 !default; + + // $success-dark: #1b9e3e !default; // $success-base: #2eb85c !default; // $success-50: #96dbad !default; @@ -119,6 +122,7 @@ // scss-docs-start theme-color-variables // $primary: $primary-base !default; // $secondary: $secondary-base !default; + // $success: $success-base !default; // $info: $info-base !default; // $warning: $warning-base !default; @@ -131,6 +135,7 @@ // $theme-colors: ( // "primary": $primary, // "secondary": $secondary, + // "success": $success, // "info": $info, // "warning": $warning, @@ -1789,3 +1794,7 @@ $sidebar-bg: #122C5A; // $kbd-bg: $gray-900 !default; // $pre-color: unset !default; + + + + diff --git a/frontend/src/scss/style.scss b/frontend/src/scss/style.scss index 02f199c40..7736af273 100644 --- a/frontend/src/scss/style.scss +++ b/frontend/src/scss/style.scss @@ -15,3 +15,8 @@ $enable-rtl: true; // If you want to add custom CSS you can put it here. @import "custom"; + +// style.scss + + + diff --git a/frontend/src/views/NetworkScanner/RecommendationComponent.js b/frontend/src/views/NetworkScanner/RecommendationComponent.js index 9ccdf2e6a..a2dd970b9 100644 --- a/frontend/src/views/NetworkScanner/RecommendationComponent.js +++ b/frontend/src/views/NetworkScanner/RecommendationComponent.js @@ -1,8 +1,10 @@ import React, { useState, useEffect } from 'react' import axios from 'axios' +import { useNavigate } from 'react-router-dom' const RecommendationsPage = () => { const [recommendations, setRecommendations] = useState([]) + const navigate = useNavigate() useEffect(() => { const fetchRecommendations = async () => { @@ -25,36 +27,40 @@ const RecommendationsPage = () => { setRecommendations(response.data) } catch (error) { console.error('Error fetching recommendations:', error) - if (error.response) { - console.error('Error status:', error.response.status) + if (error.response && error.response.status === 401) { + console.error('Unauthorized access to the API') + navigate('/login') // Redirect to login page } } } fetchRecommendations() - }, []) + }, [navigate]) return ( <div> <h2>Network Security Recommendations</h2> - <table className="table table-striped table-responsive mt-3"> - <thead> - <tr> - <th>IP Address</th> - <th>Scan Date</th> - <th>Recommendation</th> - </tr> - </thead> - <tbody> - {recommendations.map((rec) => ( - <tr key={rec.id}> - <td>{rec.ip_address}</td> - <td>{rec.scan_date}</td> - <td style={{ whiteSpace: 'pre-wrap' }}>{rec.recommendation_text}</td> + <div className="scroll-hint">Scroll horizontally to view the columns</div> + <div className="table-responsive overflow-x-auto"> + <table className="table table-striped mt-3"> + <thead> + <tr> + <th>IP Address</th> + <th className="w-15">Scan Date</th> + <th>Recommendation</th> </tr> - ))} - </tbody> - </table> + </thead> + <tbody> + {recommendations.map((rec) => ( + <tr key={rec.id}> + <td>{rec.ip_address}</td> + <td>{rec.scan_date}</td> + <td style={{ whiteSpace: 'pre-wrap' }}>{rec.recommendation_text}</td> + </tr> + ))} + </tbody> + </table> + </div> </div> ) } diff --git a/frontend/src/views/dashboard/CVEUpdates.js b/frontend/src/views/dashboard/CVEUpdates.js index 6df254345..186659af1 100644 --- a/frontend/src/views/dashboard/CVEUpdates.js +++ b/frontend/src/views/dashboard/CVEUpdates.js @@ -21,23 +21,30 @@ const CVEUpdates = () => { return ( <div> {cveItems.length > 0 ? ( - <div> - {cveItems.map((cveItem, index) => ( - <CCard key={index} className="mb-3"> - <CCardBody> - <CCardTitle>ID: {cveItem.cve.id}</CCardTitle> - <p>Date: {cveItem.cve.published}</p> - <p>Summary: {cveItem.cve.descriptions[0].value}</p> - <p> - Details:{' '} + cveItems.map((cveItem, index) => ( + <CCard key={index} className="mb-3"> + <CCardBody> + <CCardTitle>ID: {cveItem.cve?.id}</CCardTitle> + <p>Date: {cveItem.cve?.published}</p> + <p> + Summary:{' '} + {cveItem.cve?.descriptions && cveItem.cve.descriptions.length > 0 + ? cveItem.cve.descriptions[0].value + : 'No summary available'} + </p> + <p> + Details:{' '} + {cveItem.cve?.references && cveItem.cve.references.length > 0 ? ( <a href={cveItem.cve.references[0].url} target="_blank" rel="noopener noreferrer"> View More </a> - </p> - </CCardBody> - </CCard> - ))} - </div> + ) : ( + 'No details available' + )} + </p> + </CCardBody> + </CCard> + )) ) : ( <p>No CVE data available.</p> )} diff --git a/frontend/src/views/emails/BreachesEmails.js b/frontend/src/views/emails/BreachesEmails.js index 11eff4d26..99b445d9d 100644 --- a/frontend/src/views/emails/BreachesEmails.js +++ b/frontend/src/views/emails/BreachesEmails.js @@ -1,10 +1,12 @@ -// BreachesPasswords.js import React, { useState, useEffect } from 'react' import { useNavigate } from 'react-router-dom' const BreachesEmails = () => { const [breachResults, setBreachResults] = useState([]) + const [sortOrder, setSortOrder] = useState('asc') // Default sorting order + const [sortedColumn, setSortedColumn] = useState('breach_date') // Default sorted column const navigate = useNavigate() + useEffect(() => { fetchBreachResultsData() }, []) @@ -31,34 +33,60 @@ const BreachesEmails = () => { } } + const handleSort = (column) => { + if (sortedColumn === column) { + // Toggle sorting order if the same column is clicked again + setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc') + } else { + // Default to ascending order for a new column + setSortOrder('asc') + setSortedColumn(column) + } + } + + const sortedResults = [...breachResults].sort((a, b) => { + // Custom sorting logic based on column and sortOrder + if (sortOrder === 'asc') { + return a[sortedColumn].localeCompare(b[sortedColumn]) + } else { + return b[sortedColumn].localeCompare(a[sortedColumn]) + } + }) + return ( <div> <h2>List of Breached Emails</h2> - <table className="table table-striped table-responsive mt-3"> - <thead> - <tr> - <th>Email</th> - <th>Breach Name</th> - <th>Breach Date</th> - <th>Description</th> - </tr> - </thead> - <tbody> - {breachResults.map((result) => ( - <tr key={result.id}> - <td>{result.email}</td> - <td>{result.name}</td> - <td>{result.breach_date}</td> - <td style={{ padding: '10px' }}> - <div dangerouslySetInnerHTML={{ __html: result.description }} /> - </td> + <div className="scroll-hint">Scroll horizontally to view the columns</div> + <div className="table-responsive overflow-x-auto"> + <table className="table table-striped mt-3"> + <thead> + <tr> + <th onClick={() => handleSort('email')}>Email</th> + <th onClick={() => handleSort('name')}>Breach Name</th> + <th className="w-15" onClick={() => handleSort('breach_date')}> + Breach Date {sortedColumn === 'breach_date' && (sortOrder === 'asc' ? '↑' : '↓')} + </th> + <th>Description</th> </tr> - ))} - </tbody> - </table> + </thead> + <tbody> + {sortedResults.map((result) => ( + <tr key={result.id}> + <td>{result.email}</td> + <td>{result.name}</td> + <td>{result.breach_date}</td> + <td style={{ padding: '10px' }}> + <div dangerouslySetInnerHTML={{ __html: result.description }} /> + </td> + </tr> + ))} + </tbody> + </table> + </div> </div> ) } + const countUniqueEmails = (results) => { const uniqueEmails = new Set(results.map((result) => result.email)) return uniqueEmails.size diff --git a/frontend/src/views/pages/login/Login.js b/frontend/src/views/pages/login/Login.js index 8d08d6122..1d6be7c0d 100644 --- a/frontend/src/views/pages/login/Login.js +++ b/frontend/src/views/pages/login/Login.js @@ -97,13 +97,13 @@ const Login = () => { </CForm> </CCardBody> </CCard> - <CCard className="text-white bg-primary py-5" style={{ width: '44%' }}> + <CCard className="text-white bg-primary py-5"> <CCardBody className="text-center"> <div> <h2>New here?</h2> <p>Create an account to begin your journey of guarding your data.</p> <Link to="/register"> - <CButton color="primary" className="mt-3" active tabIndex={-1}> + <CButton color="secondary" className="mt-3 text-white" active tabIndex={-1}> Register Now! </CButton> </Link> diff --git a/frontend/src/views/pages/register/Register.js b/frontend/src/views/pages/register/Register.js index 8a6fe6105..8263011e9 100644 --- a/frontend/src/views/pages/register/Register.js +++ b/frontend/src/views/pages/register/Register.js @@ -122,7 +122,7 @@ const Register = () => { /> </CInputGroup> <div className="d-grid"> - <CButton color="success" type="submit"> + <CButton color="primary" type="submit"> Create Account </CButton> </div> diff --git a/frontend/src/views/passwords/BreachesPasswords.js b/frontend/src/views/passwords/BreachesPasswords.js index 9b6882575..bded33aec 100644 --- a/frontend/src/views/passwords/BreachesPasswords.js +++ b/frontend/src/views/passwords/BreachesPasswords.js @@ -1,13 +1,17 @@ -// BreachesPasswords.js import React, { useState, useEffect } from 'react' +import PropTypes from 'prop-types' +import { useNavigate } from 'react-router-dom' -const BreachesPasswords = () => { +const BreachesPasswords = ({ onCountChange }) => { const [breachResults, setBreachResults] = useState([]) + const [breachedPasswordsCount, setBreachedPasswordsCount] = useState(0) useEffect(() => { fetchBreachResultsData() }, []) + const navigate = useNavigate() + const fetchBreachResultsData = async () => { try { const token = localStorage.getItem('token') @@ -19,11 +23,19 @@ const BreachesPasswords = () => { if (response.status === 401) { console.error('Unauthorized access to the API') + navigate('/login') // Redirect to login page return } const data = await response.json() setBreachResults(data) + + // Calculate count of breached passwords with more than 0 breaches + const breachedCount = data.filter((result) => result.count > 0).length + setBreachedPasswordsCount(breachedCount) + + // Pass the count to the parent component + onCountChange(breachedCount) } catch (error) { console.error('Error fetching breach results:', error) } @@ -51,10 +63,15 @@ const BreachesPasswords = () => { </div> ) } + +BreachesPasswords.propTypes = { + onCountChange: PropTypes.func.isRequired, +} + const countUniquePasswords = (results) => { - return results.filter((result) => result.count === 0).length + return results.filter((result) => result.count > 0).length } -export { countUniquePasswords } // Correct export statement +export { countUniquePasswords } export default BreachesPasswords diff --git a/frontend/src/views/shodan/shodanscan.js b/frontend/src/views/shodan/shodanscan.js index 8b35c2774..cb15ea72f 100644 --- a/frontend/src/views/shodan/shodanscan.js +++ b/frontend/src/views/shodan/shodanscan.js @@ -43,88 +43,95 @@ const ShodanScanner = () => { } return ( - <CRow> - <CCol lg={12}> - <CCard className={'mb-4'}> - <CCardHeader>Shodan Scanner</CCardHeader> - <CCardBody> - <CForm> - {ipAddresses.map((ip, index) => ( - <CInputGroup key={index} className="mb-3"> - <CFormInput - type="text" - placeholder="Enter IP Address" - value={ip.address} - onChange={(e) => { - const newIpAddresses = [...ipAddresses] - newIpAddresses[index].address = e.target.value - setIpAddresses(newIpAddresses) - }} - /> - <CButton - type="button" - color="danger" - variant="outline" - onClick={() => handleRemoveIpAddress(index)} - > - <CIcon icon={cilTrash} /> - </CButton> - </CInputGroup> - ))} - <CButton color="primary" className="mb-3" onClick={handleAddIpAddress}> - <CIcon icon={cilPlus} /> Add Another IP Address - </CButton> - - <div className="d-grid gap-2"> - <CButton color="info" onClick={handleScan}> - Scan - </CButton> - </div> - </CForm> - {scanResults.length > 0 && ( - <div className="scan-results"> - <h3>Scan Results</h3> - {scanResults.map((result, index) => ( - <div key={index} className="result"> - <h4>IP Address: {result.ip_address}</h4> - <div> - Geolocation: {result.geolocation.country}, {result.geolocation.city} - </div> - <div> - Latitude: {result.geolocation.latitude}, Longitude: {''} - {result.geolocation.longitude} - </div> - <div> - <h5>Open Ports</h5> - {result.open_ports.map((port, portIndex) => ( - <p key={portIndex}> - {port.port}/{port.protocol} - {port.banner} - </p> - ))} - </div> - <div> - <h5>Vulnerabilities</h5> - {result.vulnerabilities.map((vuln, vulnIndex) => ( - <p key={vulnIndex}> - <a - href={`https://nvd.nist.gov/vuln/detail/${vuln.cve}`} - target="_blank" - rel="noopener noreferrer" - > - {vuln.cve} - </a> - {vuln.cve}: {vuln.summary} (CVSS: {vuln.cvss || 'N/A'}) - </p> - ))} - </div> - </div> + <div> + <CRow> + <CCol lg={12}> + <CCard className="mb-4"> + <CCardHeader>Shodan Scanner</CCardHeader> + <CCardBody> + <CForm> + {ipAddresses.map((ip, index) => ( + <CInputGroup key={index} className="mb-3"> + <CFormInput + type="text" + placeholder="Enter IP Address" + value={ip.address} + onChange={(e) => { + const newIpAddresses = [...ipAddresses] + newIpAddresses[index].address = e.target.value + setIpAddresses(newIpAddresses) + }} + /> + <CButton + type="button" + color="danger" + variant="outline" + onClick={() => handleRemoveIpAddress(index)} + > + <CIcon icon={cilTrash} /> + </CButton> + </CInputGroup> ))} - </div> - )} - </CCardBody> - </CCard> - </CCol> - </CRow> + <CButton color="primary" className="mb-3" onClick={handleAddIpAddress}> + <CIcon icon={cilPlus} /> Add Another IP Address + </CButton> + + <div className="d-grid gap-2"> + <CButton color="info" onClick={handleScan}> + Scan + </CButton> + </div> + </CForm> + {scanResults.length > 0 && ( + <CCard className="mt-4" style={{ backgroundColor: '#f8f9fa' }}> + <CCardHeader>Scan Results</CCardHeader> + <CCardBody className="p-0"> + {scanResults.map((result, index) => ( + <div key={index} className="result"> + <div className="bg-light p-3"> + <h4>IP Address: {result.ip_address}</h4> + <div> + Geolocation: {result.geolocation.country}, {result.geolocation.city} + </div> + <div> + Latitude: {result.geolocation.latitude}, Longitude: + {result.geolocation.longitude} + </div> + </div> + <div className="bg-white p-3"> + <h5>Open Ports</h5> + {result.open_ports.map((port, portIndex) => ( + <p key={portIndex}> + {port.port}/{port.protocol} - {port.banner} + </p> + ))} + </div> + <div className="bg-light p-3"> + <h5>Vulnerabilities</h5> + {result.vulnerabilities.map((vuln, vulnIndex) => ( + <p key={vulnIndex}> + <a + href={`https://nvd.nist.gov/vuln/detail/${vuln.cve}`} + target="_blank" + rel="noopener noreferrer" + > + {vuln.cve} + </a> + <br></br> + {vuln.cve}: {vuln.summary} (CVSS: {vuln.cvss || 'N/A'}) + </p> + ))} + </div> + </div> + ))} + </CCardBody> + </CCard> + )} + </CCardBody> + </CCard> + </CCol> + </CRow> + </div> ) } diff --git a/frontend/src/views/widgets/WidgetsDropdown.js b/frontend/src/views/widgets/WidgetsDropdown.js index 4914227d6..c7fe5917e 100644 --- a/frontend/src/views/widgets/WidgetsDropdown.js +++ b/frontend/src/views/widgets/WidgetsDropdown.js @@ -10,11 +10,14 @@ import { CWidgetStatsA, } from '@coreui/react' import CIcon from '@coreui/icons-react' -import { cilArrowTop, cilOptions } from '@coreui/icons' +import { cilOptions } from '@coreui/icons' +import { countUniqueEmails } from '../../views/emails/BreachesEmails' +import { countUniquePasswords } from '../../views/passwords/BreachesPasswords' const WidgetsDropdown = () => { const [breachResults, setBreachResults] = useState([]) const [recommendationCount, setRecommendationCount] = useState(0) + const [breachedPasswordCount, setBreachedPasswordCount] = useState(0) const navigate = useNavigate() useEffect(() => { @@ -47,6 +50,19 @@ const WidgetsDropdown = () => { ) const recommendationsData = await recommendationsResponse.json() setRecommendationCount(recommendationsData.total_recommendations) + + // Fetch breached passwords count + const breachedPasswordsResponse = await fetch( + 'http://localhost:8000/api/breaches/passwords/details/', + { + headers: { + Authorization: `Bearer ${token}`, + }, + }, + ) + const breachedPasswordsData = await breachedPasswordsResponse.json() + const breachedPasswordsCount = countUniquePasswords(breachedPasswordsData) + setBreachedPasswordCount(breachedPasswordsCount) } catch (error) { console.error('Error fetching data:', error) } @@ -56,20 +72,31 @@ const WidgetsDropdown = () => { }, [navigate]) const handleViewBreachesClick = () => { - window.location.href = '/breaches-emails#/view' + window.location.href = '#/email-breaches/view' } const handleAddBreachesClick = () => { - window.location.href = '/breaches-emails#/add' + window.location.href = '#/email-breaches/add' + } + + const handleViewPasswordsClick = () => { + window.location.href = '#/password-breaches/view' + } + const handleAddPasswordsClick = () => { + window.location.href = '#/password-breaches/add' + } + + const handleViewRecommendationsClick = () => { + window.location.href = '#/recommendations' } return ( <CRow> - <CCol sm={6} lg={3}> + <CCol sm={6} lg={4}> <CWidgetStatsA className="my-4 pb-4" - color="primary" - value={<>{breachResults.length}</>} + color="secondary" + value={<>{countUniqueEmails(breachResults)}</>} title="Emails Breached" action={ <CDropdown alignment="end"> @@ -84,11 +111,30 @@ const WidgetsDropdown = () => { } /> </CCol> - <CCol sm={6} lg={3}> + <CCol sm={6} lg={4}> + <CWidgetStatsA + className="my-4 pb-4 tertiary-background" + color="tertiary" + value={breachedPasswordCount} + title="Breached Passwords" + action={ + <CDropdown alignment="end"> + <CDropdownToggle color="transparent" caret={false} className="p-0"> + <CIcon icon={cilOptions} className="text-high-emphasis-inverse" /> + </CDropdownToggle> + <CDropdownMenu> + <CDropdownItem onClick={handleViewPasswordsClick}>View Passwords</CDropdownItem> + <CDropdownItem onClick={handleAddPasswordsClick}>Add Password</CDropdownItem> + </CDropdownMenu> + </CDropdown> + } + /> + </CCol> + <CCol sm={6} lg={4}> <CWidgetStatsA className="my-4 pb-4" color="info" - value={<> {recommendationCount}</>} + value={recommendationCount} title="Total Network Recommendations" action={ <CDropdown alignment="end"> @@ -96,10 +142,9 @@ const WidgetsDropdown = () => { <CIcon icon={cilOptions} className="text-high-emphasis-inverse" /> </CDropdownToggle> <CDropdownMenu> - <CDropdownItem>Action</CDropdownItem> - <CDropdownItem>Another action</CDropdownItem> - <CDropdownItem>Something else here...</CDropdownItem> - <CDropdownItem disabled>Disabled action</CDropdownItem> + <CDropdownItem onClick={handleViewRecommendationsClick}> + View Recommendations + </CDropdownItem> </CDropdownMenu> </CDropdown> } -- GitLab