diff --git a/README.md b/README.md index 90fcd4a..0e99e97 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,12 @@ BlueHound supports presenting your data as tables, graphs, bar charts, line char 4. **Easy Customization**: Any custom collection method can be added into BlueHound. Users can even add their own custom parameters or even custom icons for their graphs. ## Getting Started +### ROST ISO +BlueHound can be used as part of the [ROST image](https://zeronetworks.com/ROST-iso.zip), which comes pre-configured with everything you need (BlueHound, Neo4j, BloodHound, and a sample dataset). +
To load ROST, create a new virtual machine, and install it from the ISO like you would for a new Windows host. +### BlueHound Binary +If you already have a Neo4j instance running, you can download a pre-compiled version of BlueHound from our [release page](https://github.com/zeronetworks/BlueHound/releases). Just download the zip file suitable to your OS version, extract it, and run the binary. +### Using BlueHound 1. Connect to your Neo4j server 2. Download [SharpHound](https://github.com/BloodHoundAD/BloodHound/blob/master/Collectors/SharpHound.exe), [ShotHound](https://github.com/zeronetworks/BloodHound-Tools/tree/main/ShotHound) and the [Vulnerability Scanner report parser](https://github.com/zeronetworks/BloodHound-Tools/tree/main/VulnerabilitiesDataImport) 3. Use the **Data Import** section to collect & import data into your Neo4j database. diff --git a/package.json b/package.json index 3493a6e..ebb0176 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "bluehound", "productName": "BlueHound", - "version": "1.0.0", + "version": "1.1.0", "description": "BlueHound", "neo4jDesktop": { "apiVersion": "^1.2.0" @@ -88,7 +88,8 @@ "async-mutex": "^0.3.2", "jszip": "^3.10.0", "file-saver": "^2.0.5", - "react-lottie-player": "^1.4.3" + "react-lottie-player": "^1.4.3", + "dayjs": "^1.11.5" }, "devDependencies": { "@babel/cli": "^7.16.8", diff --git a/public/zero-networks-logo.png b/public/zero-networks-logo.png new file mode 100644 index 0000000..5a098bf Binary files /dev/null and b/public/zero-networks-logo.png differ diff --git a/public/zero-networks-logo.svg b/public/zero-networks-logo.svg new file mode 100644 index 0000000..19e0e7b --- /dev/null +++ b/public/zero-networks-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/application/Application.tsx b/src/application/Application.tsx index bfb9e18..a3b2fc2 100644 --- a/src/application/Application.tsx +++ b/src/application/Application.tsx @@ -101,7 +101,9 @@ const Application = ({ connection, connected, hasCachedDashboard, oldDashboard, createConnection(connection.protocol, connection.url, connection.port, connection.database, connection.username, connection.password); } - + if (window.electron != undefined) { // running in Electron + window.electron.appLoaded(); + } } // Only render the dashboard component if we have an active Neo4j connection. diff --git a/src/application/ApplicationActions.tsx b/src/application/ApplicationActions.tsx index 47fb890..bdf4882 100644 --- a/src/application/ApplicationActions.tsx +++ b/src/application/ApplicationActions.tsx @@ -125,6 +125,12 @@ export const setParallelQueries = (amount: number) => ({ payload: { amount }, }); +export const SET_COLLECTION_HOURS = 'APPLICATION/SET_COLLECTION_HOURS'; +export const setCollectionHours = (amount: number) => ({ + type: SET_COLLECTION_HOURS, + payload: { amount }, +}); + export const SET_SHARPHOUND_UPLOAD_RESULTS = 'APPLICATION/SET_SHARPHOUND_UPLOAD_RESULTS'; export const setSharpHoundUploadResults = (status: boolean) => ({ type: SET_SHARPHOUND_UPLOAD_RESULTS, @@ -191,4 +197,16 @@ export const SET_DASHBOARD_TO_LOAD_AFTER_CONNECTING = 'APPLICATION/SET_DASHBOARD export const setDashboardToLoadAfterConnecting = (id: any) => ({ type: SET_DASHBOARD_TO_LOAD_AFTER_CONNECTING, payload: { id }, +}); + +export const SET_ZERO_ALERT_SHOWN = 'APPLICATION/SET_ZERO_ALERT_SHOWN'; +export const setZeroAlertShown = (alert_state: boolean) => ({ + type: SET_ZERO_ALERT_SHOWN, + payload: { alert_state} +}); + +export const SET_ZERO_ALERT_OPEN = 'APPLICATION/SET_ZERO_ALERT_OPEN'; +export const setZeroAlertOpen = (alert_state: boolean) => ({ + type: SET_ZERO_ALERT_OPEN, + payload: { alert_state} }); \ No newline at end of file diff --git a/src/application/ApplicationReducer.tsx b/src/application/ApplicationReducer.tsx index 34e1f52..c2bea68 100644 --- a/src/application/ApplicationReducer.tsx +++ b/src/application/ApplicationReducer.tsx @@ -13,6 +13,7 @@ import { SET_CONNECTION_MODAL_OPEN, SET_CONNECTION_PROPERTIES, SET_DASHBOARD_TO_LOAD_AFTER_CONNECTING, + SET_ZERO_ALERT_SHOWN, SET_DESKTOP_CONNECTION_PROPERTIES, SET_OLD_DASHBOARD, SET_SHARE_DETAILS_FROM_URL, @@ -31,7 +32,12 @@ import { SET_SHARPHOUND_UPLOAD_RESULTS, SET_SHARPHOUND_CLEAR_RESULTS, SET_FILTER_MODAL_OPEN, - SET_QUERY_MODAL_OPEN, SET_PARALLEL_QUERIES, SET_DB_VERSION, SET_CACHED_QUERIES + SET_QUERY_MODAL_OPEN, + SET_PARALLEL_QUERIES, + SET_DB_VERSION, + SET_CACHED_QUERIES, + SET_ZERO_ALERT_OPEN, + SET_COLLECTION_HOURS } from "./ApplicationActions"; const update = (state, mutations) => @@ -63,9 +69,12 @@ const initialState = standalone: false, toolsParallel: false, parallelQueries: 5, + collectionHours: 0, sharphoundUploadResults: true, sharphoundClearResults: true, - cachedQueries: {} + cachedQueries: {}, + zeroAlertShown: true, + zeroAlertOpen: false } export const applicationReducer = (state = initialState, action: { type: any; payload: any; }) => { @@ -151,6 +160,11 @@ export const applicationReducer = (state = initialState, action: { type: any; pa state = update(state, { parallelQueries: amount }) return state } + case SET_COLLECTION_HOURS: { + const { amount } = payload; + state = update(state, { collectionHours: amount }) + return state + } case SET_SHARPHOUND_UPLOAD_RESULTS: { const { status } = payload; state = update(state, { sharphoundUploadResults: status }) @@ -211,6 +225,16 @@ export const applicationReducer = (state = initialState, action: { type: any; pa state = update(state, { dashboardToLoadAfterConnecting: id }) return state; } + case SET_ZERO_ALERT_SHOWN: { + const { alert_state } = payload; + state = update(state, { zeroAlertShown: alert_state }) + return state; + } + case SET_ZERO_ALERT_OPEN: { + const { alert_state } = payload; + state = update(state, { zeroAlertOpen: alert_state }) + return state; + } case SET_CONNECTION_PROPERTIES: { const { protocol, url, port, database, username, password, successful } = payload; state = update(state, { diff --git a/src/application/ApplicationSelectors.tsx b/src/application/ApplicationSelectors.tsx index 02391ee..52f7fdc 100644 --- a/src/application/ApplicationSelectors.tsx +++ b/src/application/ApplicationSelectors.tsx @@ -121,10 +121,22 @@ export const getParallelQueries = (state: any) => { return state.application.parallelQueries; } +export const getCollectionHours = (state: any) => { + return state.application.collectionHours; +} + export const getSharpHoundUploadResults = (state: any) => { return state.application.sharphoundUploadResults; } export const getSharpHoundClearResults = (state: any) => { return state.application.sharphoundClearResults; +} + +export const getZeroAlertShown = (state: any) => { + return state.application.zeroAlertShown; +} + +export const getZeroAlertOpen = (state: any) => { + return state.application.zeroAlertOpen; } \ No newline at end of file diff --git a/src/chart/GraphChart.tsx b/src/chart/GraphChart.tsx index 7c08691..f45db33 100644 --- a/src/chart/GraphChart.tsx +++ b/src/chart/GraphChart.tsx @@ -23,10 +23,11 @@ const getNodeLabel = (labels) => { for (let bloodhoundLabel of BLOODHOUND_NODE_LABELS) { const foundIndex = labels.indexOf(bloodhoundLabel); if (foundIndex != -1) { - return labels[foundIndex] + return labels[foundIndex]; } } - return labels[-1] + + return labels[labels.length-1]; } const update = (state, mutations) => @@ -283,6 +284,7 @@ const NeoGraphChart = (props: ChartProps) => { const newImage = new Image(); newImage.src = node.lastLabel + '.png'; if (imageExists(newImage)) { + console.log(newImage.src) imgs[node.lastLabel] = newImage; image = newImage; } diff --git a/src/dashboard/Dashboard.tsx b/src/dashboard/Dashboard.tsx index f4b194b..75f06f1 100644 --- a/src/dashboard/Dashboard.tsx +++ b/src/dashboard/Dashboard.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, {useCallback, useEffect} from 'react'; import NeoPage from '../page/Page'; import Container from '@material-ui/core/Container'; import NeoDrawer from './DashboardDrawer'; @@ -12,8 +12,6 @@ import { forceRefreshPage } from '../page/PageActions'; import { getPageNumber } from '../settings/SettingsSelectors'; import { createNotification } from '../application/ApplicationActions'; - - const Dashboard = ({ pagenumber, connection, onConnectionUpdate }) => { const [open, setOpen] = React.useState(false); const driver = createDriver(connection.protocol, connection.url, connection.port, connection.username, connection.password); diff --git a/src/main.ts b/src/main.ts index e8135d4..c90947c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,4 @@ -const {app, BrowserWindow, ipcMain, dialog, shell, Menu } = require('electron'); +const {app, BrowserWindow, ipcMain, dialog, shell, Menu, ipcRenderer } = require('electron'); const path = require('path'); const os = require('os'); const { spawn } = require('child_process'); @@ -6,6 +6,7 @@ const { handleSharpHoundResultsUpload } = require('./collectors/BloodHoundUpload const logMessages = []; let mainWindow; +const gotTheLock = app.requestSingleInstanceLock(); let shellEnvironments; let logLevel = { 0: "verbose", @@ -57,10 +58,45 @@ function createWindow () { }); } -app.on('ready', createWindow); +if (!gotTheLock) { + app.quit(); +} else { + app.on('second-instance', (event, commandLine, workingDirectory, additionalData) => { + if (commandLine.includes('--collection-only')) { + mainWindow.webContents.send('run-all-collection'); + }; + + if (mainWindow) { + if (mainWindow.isMinimized()) mainWindow.restore() + mainWindow.focus() + } + }) + + app.whenReady().then(() => { + createWindow(); + }) +} + +//app.on('ready', createWindow); const runningProcesses = {}; +const isCollectionOnly = () => { + return process.argv.includes('--collection-only'); +} + +ipcMain.handle("app-loaded", async (event) => { + if (isCollectionOnly()) { + dialog.showMessageBox(mainWindow, { + title: 'Collection-only mode', + buttons: ['Dismiss'], + type: 'info', + message: 'BlueHound is running in collection-only mode…', + }); + event.sender.send('run-all-collection', ''); + } +}); + ipcMain.handle("open-dev-tools", async (event) => { await mainWindow.webContents.openDevTools(); }); @@ -154,7 +190,7 @@ const runToolsInSerial = async (event, toolsData) => { result.on('close', (code) => { event.sender.send("tool-data-done", tool.toolId, code, path.dirname(toolPath)) delete runningProcesses[tool.toolId]; - if (toolsData.length > 1) { runToolsInSerial(event, toolsData.slice(1)) }; + if (toolsData.length > 1) { runToolsInSerial(event, toolsData.slice(1)) } ; }); } @@ -195,4 +231,47 @@ ipcMain.handle("kill-process", async (event, toolId) => { ipcMain.handle("upload-sharphound-results", async (event, toolId, resultsPath, connectionProperties, clearResults) => { await handleSharpHoundResultsUpload(event, toolId, resultsPath, connectionProperties, clearResults); +}) + +ipcMain.handle("tools-finished-running", async (event) => { + if (isCollectionOnly()) { + app.quit(); + } +}) + +ipcMain.handle("add-scheduled-task", async (event, scheduleFrequency, dayOfWeek, dayOfMonth, scheduleTime) => { + if (os.platform() != 'win32') { + dialog.showMessageBox(mainWindow, { + title: 'OS not supported', + buttons: ['Dismiss'], + type: 'error', + message: 'Adding scheduled task is currently only supported on Windows.\n\n' + + 'The --collection-only argument can be used to manually schedule BlueHound on non-Windows hosts.', + }); + return; + } + + let args = []; + const taskName = '"BlueHound Collection"'; + const appPathWithArgs = `"'${path.resolve('.', 'BlueHound.exe')}' --collection-only"`; + + if (scheduleFrequency == 'DAILY') { + args = ['/CREATE', '/F', '/SC DAILY', '/TN ' + taskName, '/TR ' + appPathWithArgs, '/ST ' + scheduleTime]; + } else if (scheduleFrequency == 'WEEKLY') { + args = ['/CREATE', '/F', '/SC WEEKLY', '/D ' + dayOfWeek, '/TN ' + taskName, '/TR ' + appPathWithArgs, '/ST ' + scheduleTime]; + } else if (scheduleFrequency == 'MONTHLY') { + args = ['/CREATE', '/F', '/SC MONTHLY', '/D ' + dayOfMonth, '/TN ' + taskName, '/TR ' + appPathWithArgs, '/ST ' + scheduleTime]; + } + + const result = spawn("SCHTASKS", args, { shell: true }); + + result.on('error', function (err) { // needed for catching ENOENT + event.sender.send("tool-notification", err) + }); + + result.on('close', (code) => { + if (code == 0) { + event.sender.send("tool-notification", "Scheduled task added successfully.") + } + }); }) \ No newline at end of file diff --git a/src/modal/AboutModal.tsx b/src/modal/AboutModal.tsx index cbfdd61..36c9238 100644 --- a/src/modal/AboutModal.tsx +++ b/src/modal/AboutModal.tsx @@ -16,7 +16,7 @@ import {applicationGetDebugState} from "../application/ApplicationSelectors"; import JSZip from 'jszip'; import FileSaver from 'file-saver'; -const version = "1.0.0"; +const version = "1.1.0"; if (window.electron != undefined) { window.electron.receive("console-messages", (consoleData) => { @@ -142,35 +142,32 @@ export const NeoAboutModal = ({ open, handleClose, getDebugState }) => { so if you have ideas, don’t hesitate to reach out to us at {email} or via the Github repository.

- - - - - -
- - - - v{version} -
+
+ + + v{version} + + + +
diff --git a/src/modal/CollectionModal.tsx b/src/modal/CollectionModal.tsx index f6dcd4f..ba62d11 100644 --- a/src/modal/CollectionModal.tsx +++ b/src/modal/CollectionModal.tsx @@ -1,4 +1,4 @@ -import React, {useEffect} from 'react'; +import React, {useCallback, useEffect} from 'react'; import Dialog from '@material-ui/core/Dialog'; import Button from '@mui/material/Button'; import LoadingButton from '@mui/lab/LoadingButton'; @@ -41,11 +41,21 @@ import {Cancel, Stop} from "@material-ui/icons"; import {FormControl, InputLabel, MenuItem, Select} from "@material-ui/core" import { cloneDeep } from 'lodash'; import { makeStyles } from "@material-ui/core/styles"; -import {Autocomplete, Checkbox, FormControlLabel, FormGroup, Stack, Switch} from "@mui/material"; +import {Autocomplete, Checkbox, FormControlLabel, FormGroup, Input, Stack, Switch} from "@mui/material"; +import { Dayjs } from 'dayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { TimePicker } from '@mui/x-date-pickers/TimePicker'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import AlarmIcon from '@mui/icons-material/Alarm'; import { alpha, styled } from '@mui/material/styles'; import { green } from '@mui/material/colors'; import {handleRefreshClick} from "./RefreshModal"; +import {ee} from "../report/Report"; +import debounce from 'lodash/debounce'; +export const RUN_COLLECTION_EVENT = 'runCollection' +import {Mutex} from 'async-mutex'; +const mutex = new Mutex(); export enum ToolType { Binary, @@ -66,11 +76,23 @@ const neo4jConnectionParameters = ["url", "port", "username", "password"] let toolsOutputsRefs = []; +function allToolsFinished(toolsRunning) { + for (const key in toolsRunning) { + if (toolsRunning[key] == true) { return false } + } + return true; +} + function setToolNotRunning(toolId) { const state = store.getState(); let newData = cloneDeep(getToolsRunning(state)); newData[toolId] = false; store.dispatch(setToolsRunning(newData)); + + if (allToolsFinished(newData) && window.electron != undefined) { + console.log('tools finished running') + window.electron.toolsFinishedRunning(); + } } const toolIdToToolType = ((toolId) => { @@ -129,6 +151,14 @@ if (window.electron != undefined) { window.electron.receive("tool-killed", (toolId) => { setToolNotRunning(toolId); }); + + window.electron.receive('run-all-collection', (unused) => { + ee.emit(RUN_COLLECTION_EVENT); + }); + + window.electron.receive('tool-notification', (message) => { + store.dispatch(createNotification("BlueHound", message)); + }); } const sharphoundOutputDirectory = (sharphoundDirectory) => { @@ -227,6 +257,41 @@ export const NeoCollectionModal = ({ open, handleClose, dashboard, setDashboard, const [dialogToolId, setDialogToolId] = React.useState(null); const [checked, setChecked] = React.useState(sharphoundUploadResults != undefined ? sharphoundUploadResults : true); const [clearChecked, setClearChecked] = React.useState(sharphoundClearResults != undefined ? sharphoundClearResults : true); + const [scheduleDialogOpen, setScheduleDialogOpen] = React.useState(false); + const [scheduleFrequency, setScheduleFrequency] = React.useState('WEEKLY'); + const [dayOfWeek, setDayOfWeek] = React.useState('MON'); + const [dayOfMonth, setDayOfMonth] = React.useState(1); + const [timeValue, setTimeValue] = React.useState('2022-01-01 00:00'); + + const daysInMonth = Array.from(Array(31).keys()).map(x => x + 1); + + const handleFrequencyChange = (event) => { + setScheduleFrequency(event.target.value as string); + }; + + const handleDayOfWeekChange = (event) => { + setDayOfWeek(event.target.value as string); + }; + + const handleDayOfMonthChange = (event) => { + setDayOfMonth(event.target.value as string); + }; + + const padTime = (num) => { + return String(num).padStart(2, '0'); + } + + const createScheduleTask = () => { + if (window.electron != undefined) { // running in Electron + let scheduleTime = "00:00"; + if (typeof timeValue != 'string') { + scheduleTime = padTime(timeValue.hour()) + ":" + padTime(timeValue.minute()); + } + window.electron.addScheduledTask(scheduleFrequency, dayOfWeek, dayOfMonth, scheduleTime); + } else { + alert("Available only in Electron") + } + } if (toolsOutputsRefs.length == 0) { Object.keys(toolsParameters).map((element, index) => { @@ -322,42 +387,45 @@ export const NeoCollectionModal = ({ open, handleClose, dashboard, setDashboard, function handleRunAllClick() { if (window.electron != undefined) { // running in Electron - //const enabledTools = toolsParameters.find(tool => tool.enabled); - const enabledTools = []; - toolsParameters.map((tool, index) => { - if (tool.enabled) { - tool.toolId = index; - enabledTools.push(tool); - } - }) + mutex.runExclusive(() => { + if (isAnyReportRunning()) return; + + const enabledTools = []; + toolsParameters.map((tool, index) => { + if (tool.enabled) { + tool.toolId = index; + enabledTools.push(tool); + } + }) - const toolWithMissingPath = enabledTools.find(tool => !tool["path"]) - if (toolWithMissingPath) { - createNotification("Error", "Path is missing for " + toolWithMissingPath["name"] ); - return - } + const toolWithMissingPath = enabledTools.find(tool => !tool["path"]) + if (toolWithMissingPath) { + createNotification("Error", "Path is missing for " + toolWithMissingPath["name"]); + return + } - setToolsOutput({}); + setToolsOutput({}); - if (!toolsParallel) { - window.electron.runToolsInSerial(enabledTools); - } + if (!toolsParallel) { + window.electron.runToolsInSerial(enabledTools); + } - let updatedLoadingObject = {} - for (let i = 0; i < toolsParameters.length; i++) { - if (!toolsParameters[i].enabled) continue; + let updatedLoadingObject = {} + for (let i = 0; i < toolsParameters.length; i++) { + if (!toolsParameters[i].enabled) continue; - updatedLoadingObject[i] = true; - if (toolsParallel) { - if (toolsParameters[i]["toolType"] == ToolType.Python) { - window.electron.runPython(i, toolsParameters[i]["path"], insertNeo4jArgs(toolsParameters[i]["args"])); - } else { - window.electron.runTool(i, toolsParameters[i]["path"], insertNeo4jArgs(toolsParameters[i]["args"])); + updatedLoadingObject[i] = true; + if (toolsParallel) { + if (toolsParameters[i]["toolType"] == ToolType.Python) { + window.electron.runPython(i, toolsParameters[i]["path"], insertNeo4jArgs(toolsParameters[i]["args"])); + } else { + window.electron.runTool(i, toolsParameters[i]["path"], insertNeo4jArgs(toolsParameters[i]["args"])); + } } } - } - setToolsRunning(updatedLoadingObject); - setExpanded(enabledTools[0].toolId); + setToolsRunning(updatedLoadingObject); + setExpanded(enabledTools[0].toolId); + }); } else { alert("Available only in Electron") } @@ -644,6 +712,13 @@ export const NeoCollectionModal = ({ open, handleClose, dashboard, setDashboard, ); } + const debouncedHandleRunAllClick = useCallback( + debounce(handleRunAllClick, 500), + [], + ); + + ee.addListener(RUN_COLLECTION_EVENT, debouncedHandleRunAllClick); + return (
@@ -673,6 +748,11 @@ export const NeoCollectionModal = ({ open, handleClose, dashboard, setDashboard, +
+ + + + Scheduling Options +
+ Frequency: + +
+ {scheduleFrequency == "WEEKLY" ? +
+ Day: + +
+ : <>} + {scheduleFrequency == "MONTHLY" ? +
+ Day: + +
+ : <>} +
+ Start Time: + + { + setTimeValue(newValue); + }} + renderInput={(params) => } + style={{width: 200}} + /> + +
+
+
+ + + + +
); } diff --git a/src/page/Page.tsx b/src/page/Page.tsx index fbdea43..1748f5f 100644 --- a/src/page/Page.tsx +++ b/src/page/Page.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, {useCallback, useEffect} from 'react'; import { connect } from 'react-redux'; import NeoAddCard from '../card/CardAddButton'; import NeoCard from '../card/Card'; @@ -7,7 +7,7 @@ import { removeReportRequest, shiftReportLeftRequest, shiftReportRightRequest } import Grid from '@material-ui/core/Grid'; import { getDashboardIsEditable } from '../settings/SettingsSelectors'; import { getDashboardSettings } from '../dashboard/DashboardSelectors'; - +import NeoPageAlert from './PageAlert' /** * A component responsible for rendering the page, a collection of reports. @@ -26,6 +26,7 @@ export const NeoPage = ( const loadingMessage =
Loading card...
; const content = (
+ {reports.map((report, index) => { // @ts-ignore diff --git a/src/page/PageAlert.tsx b/src/page/PageAlert.tsx new file mode 100644 index 0000000..7f63f18 --- /dev/null +++ b/src/page/PageAlert.tsx @@ -0,0 +1,61 @@ +import React, {useCallback} from "react"; +import {ee} from "../report/Report"; +import {RUN_QUERY_EVENT} from "../modal/QueryModal"; +import debounce from 'lodash/debounce'; +import Collapse from "@mui/material/Collapse"; +import Alert from "@mui/material/Alert"; +import IconButton from "@mui/material/IconButton"; +import CloseIcon from "@mui/icons-material/Close"; +import Box from "@mui/material/Box"; +import {connect} from "react-redux"; +import {getZeroAlertOpen, getZeroAlertShown} from "../application/ApplicationSelectors"; +import {setZeroAlertOpen, setZeroAlertShown} from "../application/ApplicationActions"; + +export const NeoPageAlert = ({zeroAlertOpen, zeroAlertShown, setZeroAlertOpen, setZeroAlertShown}) => { + + const handleOpenAlert = () => { + if (!zeroAlertShown) { + setZeroAlertOpen(true); + setZeroAlertShown(true); + } + } + + const debouncedOpenAlert = useCallback( + debounce(handleOpenAlert), + [], + ); + + ee.addListener(RUN_QUERY_EVENT, debouncedOpenAlert); + + return ( + + + { + setZeroAlertOpen(false); + }}> + + + }> + Want to see how Zero Networks can help you eliminated these paths? Click here to find out! + + + ) +} + +const mapStateToProps = state => ({ + zeroAlertOpen: getZeroAlertOpen(state), + zeroAlertShown: getZeroAlertShown(state), +}) + +const mapDispatchToProps = dispatch => ({ + setZeroAlertShown: (alert_state) => dispatch(setZeroAlertShown(alert_state)), + setZeroAlertOpen: (alert_state) => dispatch(setZeroAlertOpen(alert_state)) +}) + +export default connect(mapStateToProps, mapDispatchToProps)(NeoPageAlert); \ No newline at end of file diff --git a/src/preload.js b/src/preload.js index 91410e1..4dc8bf0 100644 --- a/src/preload.js +++ b/src/preload.js @@ -1,6 +1,7 @@ const { contextBridge, ipcRenderer } = require('electron'); contextBridge.exposeInMainWorld('electron', { + appLoaded: async () => ipcRenderer.invoke('app-loaded'), openDevTools: async () => ipcRenderer.invoke('open-dev-tools'), getConsoleMessages: async () => ipcRenderer.invoke('get-console-messages'), browseFile: async (index) => ipcRenderer.invoke('browse-file', index), @@ -10,6 +11,8 @@ contextBridge.exposeInMainWorld('electron', { runPython: async (toolId, path, args) => ipcRenderer.invoke('run-python', toolId, path, args), uploadSharpHoundResults: async (toolId, path, connectionProperties, clearResults) => ipcRenderer.invoke('upload-sharphound-results', toolId, path, connectionProperties, clearResults), killProcess: async (toolId) => ipcRenderer.invoke('kill-process', toolId), + toolsFinishedRunning: async () => ipcRenderer.invoke('tools-finished-running'), + addScheduledTask: async (scheduleFrequency, dayOfWeek, dayOfMonth, scheduleTime) => ipcRenderer.invoke('add-scheduled-task', scheduleFrequency, dayOfWeek, dayOfMonth, scheduleTime), send: (channel, data) => { ipcRenderer.send(channel, data); },