From 9d8440e31801921e0059cce061bcb61c0ccc9455 Mon Sep 17 00:00:00 2001 From: Dekel Paz Date: Thu, 20 Oct 2022 18:17:38 +0300 Subject: [PATCH] added scheduling for data collection --- README.md | 6 + package.json | 5 +- public/zero-networks-logo.png | Bin 0 -> 37369 bytes public/zero-networks-logo.svg | 1 + src/application/Application.tsx | 4 +- src/application/ApplicationActions.tsx | 18 ++ src/application/ApplicationReducer.tsx | 28 +++- src/application/ApplicationSelectors.tsx | 12 ++ src/chart/GraphChart.tsx | 6 +- src/dashboard/Dashboard.tsx | 4 +- src/main.ts | 85 +++++++++- src/modal/AboutModal.tsx | 57 +++---- src/modal/CollectionModal.tsx | 199 +++++++++++++++++++---- src/page/Page.tsx | 5 +- src/page/PageAlert.tsx | 61 +++++++ src/preload.js | 3 + 16 files changed, 418 insertions(+), 76 deletions(-) create mode 100644 public/zero-networks-logo.png create mode 100644 public/zero-networks-logo.svg create mode 100644 src/page/PageAlert.tsx 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 0000000000000000000000000000000000000000..5a098bf58b234f176dbada048e0dec14781541f0 GIT binary patch literal 37369 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 literal 0 HcmV?d00001 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); },