From f6c7939ddadc98998967b79c796bed8b946356c7 Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 14 Nov 2023 15:20:31 +0100 Subject: [PATCH 01/11] 1.4.0 changes too --- frontend/src/App.jsx | 2 + frontend/src/components/AppSearchButtons.jsx | 398 +++++----- frontend/src/components/AppSelection.jsx | 148 ++-- frontend/src/components/Billing.jsx | 27 + frontend/src/components/CacheView.jsx | 2 +- frontend/src/components/ConfigureWorkflow.jsx | 2 +- frontend/src/components/ExploreWorkflow.jsx | 21 +- frontend/src/components/Files.jsx | 2 +- frontend/src/components/Header.jsx | 98 +-- frontend/src/components/Searchfield.jsx | 741 +++--------------- .../src/components/WorkflowTemplatePopup.jsx | 36 +- frontend/src/views/Admin.jsx | 43 +- frontend/src/views/AngularWorkflow.jsx | 45 +- frontend/src/views/AppCreator.jsx | 12 +- frontend/src/views/Welcome.jsx | 2 +- frontend/src/views/Workflows.jsx | 25 +- 16 files changed, 614 insertions(+), 990 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 4d96250a..8599d420 100755 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -38,6 +38,7 @@ import ScrollToTop from "./components/ScrollToTop"; import AlertTemplate from "./components/AlertTemplate"; import { useAlert, positions, Provider } from "react-alert"; import { isMobile } from "react-device-detect"; +import RuntimeDebugger from "./components/RuntimeDebugger.jsx" import { ToastContainer, toast } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; @@ -346,6 +347,7 @@ const App = (message, props) => { /> } /> + } /> } /> { - const { userdata, globalUrl, appFramework, finishedApps, appType, totalApps, index, onNodeSelect, setDiscoveryData, appName, AppImage, setDefaultSearch, discoveryData, checkLogin, setMissing, } = props + const { userdata, globalUrl, appFramework, moreButton, finishedApps, appType, totalApps, index, onNodeSelect, setDiscoveryData, appName, AppImage, setDefaultSearch, discoveryData, checkLogin, setMissing, } = props const ref = useRef() let navigate = useNavigate(); const [isHover, setIsHover] = useState(false); const [localSearchOpen, setLocalSearchOpen] = useState(false) - const [newSelectedApp, setNewSelectedApp] = useState(undefined) + const [newSelectedApp, setNewSelectedApp] = useState(undefined) - useEffect(() => { - console.log("AppSearchButtons: newSelectedApp: " + JSON.stringify(newSelectedApp)) + useEffect(() => { + console.log("AppSearchButtons: newSelectedApp: " + JSON.stringify(newSelectedApp)) - if (newSelectedApp !== undefined && setMissing != undefined) { - console.log("AppSearchButtons: setMissing is defined!") + if (newSelectedApp !== undefined && setMissing != undefined) { + console.log("AppSearchButtons: setMissing is defined!") - const submitAppFramework = { - "description": newSelectedApp.description, - "id": newSelectedApp.objectID, - "name": newSelectedApp.name, - "type": appType, - "large_image": newSelectedApp.image_url, - } + const submitAppFramework = { + "description": newSelectedApp.description, + "id": newSelectedApp.objectID, + "name": newSelectedApp.name, + "type": appType, + "large_image": newSelectedApp.image_url, + } - setFrameworkItem(submitAppFramework) - setMissing(newSelectedApp) - } - }, [newSelectedApp]) + setFrameworkItem(submitAppFramework) + setMissing(newSelectedApp) + } + }, [newSelectedApp]) - if (appType === undefined) { - console.log("Apptype is required in AppSearchButtons") - return null; - } + if (appType === undefined) { + console.log("Apptype is required in AppSearchButtons") + return null; + } const buttonWidth = 450; const buttonMargin = 10; const handleMouseEnter = () => { setIsHover(true); - }; - - const handleMouseLeave = () => { + }; + + const handleMouseLeave = () => { setIsHover(false); - }; + }; - const foundApp = findSpecificApp(appFramework, appType) - if (foundApp === undefined || foundApp === null) { - console.log("AppSearchButtons: App not found in appFramework: " + appType) - return null - } + const foundApp = findSpecificApp(appFramework, appType) + if (foundApp === undefined || foundApp === null) { + console.log("AppSearchButtons: App not found in appFramework: " + appType) + return null + } - const setFrameworkItem = (data) => { + const setFrameworkItem = (data) => { fetch(globalUrl + "/api/v1/apps/frameworkConfiguration", { method: "POST", @@ -99,187 +99,207 @@ const AppSearchButtons = (props) => { body: JSON.stringify(data), credentials: "include", }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for framework!"); - } + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for framework!"); + } - if (checkLogin !== undefined) { - checkLogin() - } + if (checkLogin !== undefined) { + checkLogin() + } - return response; - }) - .then((responseJson) => { - if (responseJson.success === false) { - if (responseJson.reason !== undefined) { - toast("Failed updating: " + responseJson.reason) - } else { - toast("Failed to update framework for your org.") + return response; + }) + .then((responseJson) => { + if (responseJson.success === false) { + if (responseJson.reason !== undefined) { + toast("Failed updating: " + responseJson.reason) + } else { + toast("Failed to update framework for your org.") - } - } - //setFrameworkLoaded(true) - //setFrameworkData(responseJson) - }) - .catch((error) => { - if (checkLogin !== undefined) { - checkLogin() - } + } + } + //setFrameworkLoaded(true) + //setFrameworkData(responseJson) + }) + .catch((error) => { + if (checkLogin !== undefined) { + checkLogin() + } - toast(error.toString()); - //setFrameworkLoaded(true) - }) + toast(error.toString()); + //setFrameworkLoaded(true) + }) } + const icon = foundApp.large_image + console.log("index:", moreButton) + console.log("totalApps:", totalApps) - const icon = foundApp.large_image let xsValue = 12; - if (index !== undefined && index !== 0) { + if (index === totalApps - 1 || index === totalApps - 2 || index === totalApps - 3 || index === totalApps - 4) { xsValue = 6; - } - - if (index === totalApps - 4 && totalApps % 2 === 1) { + } + if (index === totalApps - 5) { xsValue = 12; } - - if (index === totalApps - 1 || index === totalApps - 2 || index === totalApps - 3) { - xsValue = 4; - } + if (moreButton) { + switch (index) { + case totalApps - 1: + case totalApps - 2: + case totalApps - 3: + xsValue = 4; + break; + case totalApps - 4: + case totalApps - 5: + case totalApps - 6: + case totalApps - 7: + xsValue = 6; + break; + case totalApps - 8: + xsValue = 12; + break; + default: + // Handle other cases if needed + } + } + return ( - - {localSearchOpen ? ( -
-
-
- {discoveryData} -
-
- - { - setLocalSearchOpen(false) - }} - > - - - - - { - e.preventDefault(); - setLocalSearchOpen(false) - setDefaultSearch("") - const submitDeletedApp = { - "description": "", - "id": "remove", - "name": "", - "type": discoveryData - } - setFrameworkItem(submitDeletedApp) - setNewSelectedApp({}) - setTimeout(() => { - setDiscoveryData({}) - setFrameworkItem(submitDeletedApp) - //setNewSelectedApp({}) - }, 1000) - //setAppName(discoveryData.cases.name) - }} - > - - - -
-
-
- -
- ) : null} -
- +
+
@@ -559,21 +557,6 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho - {/* - - - - - - */} {isCloud ? @@ -615,60 +598,33 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
-
- -
+ + - - - - - - - {isCloud ? - - - - - - : null} - - {/* - - {supportMenu} - - */} - - {/* - - - - - - */} + } + }}>Login + {isCloud ? + + + + + : null} +
diff --git a/frontend/src/components/Searchfield.jsx b/frontend/src/components/Searchfield.jsx index 3bafef61..7edef2ca 100644 --- a/frontend/src/components/Searchfield.jsx +++ b/frontend/src/components/Searchfield.jsx @@ -1,34 +1,42 @@ -import React, {useState, useEffect, useRef} from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import theme from '../theme.jsx'; import { useNavigate, Link, useParams } from "react-router-dom"; +import SearchBox from "./SearchData.jsx"; import { - Chip, - IconButton, - TextField, - InputAdornment, - List, - Card, - ListItem, - ListItemAvatar, - ListItemText, - Avatar, + Chip, + IconButton, + TextField, + InputAdornment, + List, + Card, + ListItem, + Button, + Dialog, + ListItemAvatar, + ListItemText, + Avatar, Typography, Tooltip, + Divider, + DialogTitle, + DialogContent, } from '@mui/material'; +import Mousetrap from 'mousetrap'; import { - AvatarGroup, + AvatarGroup, } from "@mui/material" -import {Search as SearchIcon, Close as CloseIcon, Folder as FolderIcon, Code as CodeIcon, LibraryBooks as LibraryBooksIcon} from '@mui/icons-material' - +import { Search as SearchIcon, Close as CloseIcon, Folder as FolderIcon, Code as CodeIcon, LibraryBooks as LibraryBooksIcon } from '@mui/icons-material' +import KeyboardCommandKeyIcon from '@mui/icons-material/KeyboardCommandKey'; import algoliasearch from 'algoliasearch/lite'; import aa from 'search-insights' import { InstantSearch, Configure, connectSearchBox, connectHits, Index } from 'react-instantsearch-dom'; //import { InstantSearch, SearchBox, Hits, connectSearchBox, connectHits, Index } from 'react-instantsearch-dom'; +import { HotKeys } from 'react-hotkeys'; // https://www.algolia.com/doc/api-reference/widgets/search-box/react/ const chipStyle = { backgroundColor: "#3d3f43", height: 30, marginRight: 5, paddingLeft: 5, paddingRight: 5, height: 28, cursor: "pointer", borderColor: "#3d3f43", color: "white", @@ -36,623 +44,120 @@ const chipStyle = { const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const SearchField = props => { - const { serverside, userdata } = props + const { serverside, userdata, isMobile, isLoaded, globalUrl, isHeader, isLoggedIn, small, rounded } = props let navigate = useNavigate(); const borderRadius = 3 const node = useRef() const [searchOpen, setSearchOpen] = useState(false) + const [modalOpen, setModalOpen] = React.useState(false); const [oldPath, setOldPath] = useState("") const [value, setValue] = useState(""); + useEffect(() => { + Mousetrap.bind(['command+k', 'ctrl+k'], () => { + setModalOpen(true); + return false; // Prevent the default action + }); + Mousetrap.bind(['esc'], () => { + setModalOpen(false); + return false; // Prevent the default action + }); - if (serverside === true) { - return null - } - - if (window !== undefined && window.location !== undefined && window.location.pathname === "/search") { - return null - } - - const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; - - if (window.location.pathname !== oldPath) { - setSearchOpen(false) - setOldPath(window.location.pathname) - } - - //useEffect(() => { - // if (searchOpen) { - // var tarfield = document.getElementById("shuffle_search_field") - // tarfield.focus() - // } - //}, searchOpen) - - const SearchBox = ({currentRefinement, refine, isSearchStalled, } ) => { - const keyPressHandler = (e) => { - // e.preventDefault(); - if (e.which === 13) { - // alert("You pressed enter!"); - navigate("/search?q=" + currentRefinement, { state: value, replace: true }); - } + return () => { + Mousetrap.unbind(['command+k', 'ctrl+k']); }; - /* - endAdornment: ( - { - event.preventDefault() - }}> - { - setSearchOpen(false) - }} /> - - ), - */ + }, []); - return ( - - return ( - { - //console.log("CLICK") - setSearchOpen(true) - - aa('init', { - appId: searchClient.appId, - apiKey: searchClient.transporter.queryParameters["x-algolia-api-key"] - }) - - const timestamp = new Date().getTime() - aa('sendEvents', [ - { - eventType: 'click', - eventName: 'Workflow Clicked', - index: 'workflows', - objectIDs: [hit.objectID], - timestamp: timestamp, - queryID: hit.__queryID, - positions: [hit.__position], - userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id, - } - ]) - - if (!isCloud) { - event.preventDefault() - window.open(parsedUrl, '_blank'); - } - }}> - { - setMouseHoverIndex(index) - }}> - - {avatar} - -
- - - {appGroup.map((app, index) => { - // Putting all this in secondary of ListItemText looked weird. - return ( -
{ - navigate("/apps/"+app.id) - }} - > - - - -
- ) - })} -
-
- {/* - - - - - - */} -
- - )}) - } - - {/* - - - - See all workflows - - - - */} - - ) - } - - const AppHits = ({ hits }) => { - const [mouseHoverIndex, setMouseHoverIndex] = useState(0) - - var tmp = searchOpen - if (!searchOpen) { - return null - } - - const positionInfo = document.activeElement.getBoundingClientRect() - const outerlistitemStyle = { - width: "100%", - overflowX: "hidden", - overflowY: "hidden", - borderBottom: "1px solid rgba(255,255,255,0.4)", - } - - if (hits.length > 4) { - hits = hits.slice(0, 4) - } - - var type = "app" - const baseImage = - - return ( - - { - setSearchOpen(false) - }}> - - - - Apps - - - - {hits.length === 0 ? - - console.log(hits)}> - - - - - - - : - hits.map((hit, index) => { - const innerlistitemStyle = { - width: positionInfo.width+35, - overflowX: "hidden", - overflowY: "hidden", - borderBottom: "1px solid rgba(255,255,255,0.4)", - backgroundColor: mouseHoverIndex === index ? "#1f2023" : "inherit", - cursor: "pointer", - marginLeft: 5, - marginRight: 5, - maxHeight: 75, - minHeight: 75, - maxWidth: 420, - minWidth: "100%", - } - - const name = hit.name === undefined ? - hit.filename.charAt(0).toUpperCase() + hit.filename.slice(1).replaceAll("_", " ") + " - " + hit.title : - (hit.name.charAt(0).toUpperCase()+hit.name.slice(1)).replaceAll("_", " ") - - var secondaryText = hit.data !== undefined ? hit.data.slice(0, 40)+"..." : "" - const avatar = hit.image_url === undefined ? - baseImage - : - - - //console.log(hit) - if (hit.categories !== undefined && hit.categories !== null && hit.categories.length > 0) { - secondaryText = hit.categories.slice(0,3).map((data, index) => { - if (index === 0) { - return data - } - - return ", "+data - - /* - { - //handleChipClick - }} - variant="outlined" - color="primary" - /> - */ - }) - } - - var parsedUrl = isCloud ? `/apps/${hit.objectID}` : `https://shuffler.io/apps/${hit.objectID}` - parsedUrl += `?queryID=${hit.__queryID}` - - return ( - { - console.log("CLICK") - setSearchOpen(true) - - aa('init', { - appId: searchClient.appId, - apiKey: searchClient.transporter.queryParameters["x-algolia-api-key"] - }) - - const timestamp = new Date().getTime() - aa('sendEvents', [ - { - eventType: 'click', - eventName: 'App Clicked', - index: 'appsearch', - objectIDs: [hit.objectID], - timestamp: timestamp, - queryID: hit.__queryID, - positions: [hit.__position], - userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id, - } - ]) - - if (!isCloud) { - event.preventDefault() - window.open(parsedUrl, '_blank'); - } - }}> - { - setMouseHoverIndex(index) - }}> - - {avatar} - - - {/* - - - - - - */} - - - )}) - } - - - - - See more - - - - - ) - } - - const DocHits = ({ hits }) => { - const [mouseHoverIndex, setMouseHoverIndex] = useState(0) - - var tmp = searchOpen - if (!searchOpen) { - return null - } - - - const positionInfo = document.activeElement.getBoundingClientRect() - const outerlistitemStyle = { - width: "100%", - overflowX: "hidden", - overflowY: "hidden", - borderBottom: "1px solid rgba(255,255,255,0.4)", - } - - if (hits.length > 4) { - hits = hits.slice(0, 4) - } - - const type = "documentation" - const baseImage = - - //console.log(type, hits.length, hits) - - return ( - - { - setSearchOpen(false) - }}> - - - - Documentation - - {/* - { - setSearchOpen(false) - }}> - - - */} - - {hits.length === 0 ? - - console.log(hits)}> - - - - - - - : - hits.map((hit, index) => { - const innerlistitemStyle = { - width: positionInfo.width+35, - overflowX: "hidden", - overflowY: "hidden", - borderBottom: "1px solid rgba(255,255,255,0.4)", - backgroundColor: mouseHoverIndex === index ? "#1f2023" : "inherit", - cursor: "pointer", - marginLeft: 5, - marginRight: 5, - maxHeight: 75, - minHeight: 75, - maxWidth: 420, - minWidth: "100%", - } - - var name = hit.name === undefined ? - hit.filename.charAt(0).toUpperCase() + hit.filename.slice(1).replaceAll("_", " ") + " - " + hit.title - : - (hit.name.charAt(0).toUpperCase()+hit.name.slice(1)).replaceAll("_", " ") - - if (name.length > 30) { - name = name.slice(0, 30)+"..." - } - const secondaryText = hit.data !== undefined ? hit.data.slice(0, 40)+"..." : "" - const avatar = hit.image_url === undefined ? - baseImage - : - - - var parsedUrl = hit.urlpath !== undefined ? hit.urlpath : "" - parsedUrl += `?queryID=${hit.__queryID}` - if (parsedUrl.includes("/apps/")) { - const extraHash = hit.url_hash === undefined ? "" : `#${hit.url_hash}` - - parsedUrl = `/apps/${hit.filename}` - parsedUrl += `?tab=docs&queryID=${hit.__queryID}${extraHash}` - } - - return ( - { - aa('init', { - appId: searchClient.appId, - apiKey: searchClient.transporter.queryParameters["x-algolia-api-key"] - }) - - const timestamp = new Date().getTime() - aa('sendEvents', [ - { - eventType: 'click', - eventName: 'Document Clicked', - index: 'documentation', - objectIDs: [hit.objectID], - timestamp: timestamp, - queryID: hit.__queryID, - positions: [hit.__position], - userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id, - } - ]) - - console.log("CLICK") - setSearchOpen(true) - }}> - { - setMouseHoverIndex(index) - }}> - - {avatar} - - - {/* - - - - - - */} - - - )}) - } - - {type === "documentation" ? - - - Search by - -
- Algolia logo - - + const fieldWidth = small === true ? 120 : 310 + const modalView = ( + // console.log("key:", dataValue.key), + //console.log("value:",dataValue.value), + { + setModalOpen(false); + }} + PaperProps={{ + style: { + color: "white", + minWidth: 750, + height: 685, + borderRadius: 16, + border: "1px solid var(--Container-Stroke, #494949)", + background: "var(--Container, #000000)", + boxShadow: "0px 16px 24px 8px rgba(0, 0, 0, 0.25)", + zIndex: 13000, + }, + }} + > + {isHeader ?
+ Search Shuffle + +
: null} - - ) - } - - const CustomSearchBox = connectSearchBox(SearchBox) - const CustomAppHits = connectHits(AppHits) - const CustomWorkflowHits = connectHits(WorkflowHits) - const CustomDocHits = connectHits(DocHits) + + + + + +
+ {/* + Discord + */} + + Algolia logo + +
+
+ + Search by + + + Algolia logo + +
+
+
+ ); return ( -
- { - console.log("CLICKED") - }}> - - - - - - - - - - - - +
+ {modalView} + + + + ), + endAdornment: ( + + ) + }} + variant="standard" + autoComplete='off' + color="primary" + placeholder="Search Apps, Workflows, Docs..." + onClick={(event) => { + setModalOpen(true) + }} + limit={5} + />
) } diff --git a/frontend/src/components/WorkflowTemplatePopup.jsx b/frontend/src/components/WorkflowTemplatePopup.jsx index 9b283da9..ff20ad36 100644 --- a/frontend/src/components/WorkflowTemplatePopup.jsx +++ b/frontend/src/components/WorkflowTemplatePopup.jsx @@ -22,6 +22,7 @@ import { Check as CheckIcon, TrendingFlat as TrendingFlatIcon, Close as CloseIcon, + East as EastIcon, } from '@mui/icons-material'; import WorkflowTemplatePopup2 from "./WorkflowTemplatePopup.jsx"; @@ -36,6 +37,7 @@ const WorkflowTemplatePopup = (props) => { const [errorMessage, setErrorMessage] = useState(""); const [workflowLoading, setWorkflowLoading] = useState(false); const [workflow, setWorkflow] = useState({}); + const [showLoginButton, setShowLoginButton] = useState(false); const [appAuthentication, setAppAuthentication] = React.useState(undefined); const [missingSource, setMissingSource] = React.useState(undefined) @@ -119,7 +121,8 @@ const WorkflowTemplatePopup = (props) => { const loadAppAuth = () => { // Check if it exists, and has keys if (userdata === undefined || userdata === null || Object.keys(userdata).length === 0) { - setErrorMessage("You need to be logged in to try usecases. Redirecting in 5 seconds...") + setErrorMessage("You need to be logged in to try the pre-built Workflow Templates.") + setShowLoginButton(true) // Send the user to the login screen after 3 seconds setTimeout(() => { @@ -350,12 +353,35 @@ const WorkflowTemplatePopup = (props) => { {errorMessage !== "" ? errorMessage : ""} + {showLoginButton ? + + + Sign up + + + + : null}
} - {isLoggedIn && (missingSource !== undefined || missingDestination !== undefined) ? - - {"The following app category is required generate this workflow: "} + {(missingSource !== undefined || missingDestination !== undefined) ? + + {"Find relevevant Apps for this Usecase"} : null} @@ -502,7 +528,7 @@ const WorkflowTemplatePopup = (props) => { {parsedTitle} - + {parsedDescription} diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index 5f65e35a..9631fd1e 100755 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -291,8 +291,6 @@ const Admin = (props) => { //const alert = useAlert(); const handleStatusChange = (event) => { const { value } = event.target; - console.log("value: ", value) - setSelectedStatus(value); @@ -331,7 +329,7 @@ const Admin = (props) => { var your_apps = "- Connecting " var subject_add = 0 - var subject = "Want to automate " + var subject = "POC to automate " if (org.security_framework !== undefined && org.security_framework !== null) { if (org.security_framework.cases.name !== undefined && org.security_framework.cases.name !== null && org.security_framework.cases.name !== "") { @@ -400,7 +398,7 @@ const Admin = (props) => { // Remove comma - subject += "?" + //subject += "?" your_apps = your_apps.substring(0, your_apps.length - 2) } @@ -432,12 +430,25 @@ const Admin = (props) => { var admins = "" // Loop users + var lastLogin = 0 for (var i = 0; i < users.length; i++) { + if (users[i].username.includes("shuffler")) { + continue + } + if (users[i].role === "admin") { admins += users[i].username + "," } + + const data = users[i] + for (var i = 0; i < data.login_info.length; i++) { + if (data.login_info[i].timestamp > lastLogin) { + lastLogin = data.login_info[i].timestamp + } + } } + // Remove last comma admins = admins.substring(0, admins.length - 1) @@ -452,15 +463,24 @@ const Admin = (props) => { // Get drift username from userdata.username before @ in email const username = userdata.username.substring(0, userdata.username.indexOf("@")) - var body = `Hey,%0D%0A%0D%0AI saw you trying to use Shuffle, and thought we may be able to help. Right now, it looks like you have ${workflow_amount} workflows made, but it still doesn't look like you are getting the most out of Shuffle. If you're interested, I'd love to set up a quick call to see if we can help you get more out of Shuffle. %0D%0A%0D%0A + // Check if timestamp is more than 2 weeks ago and add "a while back" to the message + const timeComparison = 1209600 + const extra_timestamp_text = lastLogin === 0 ? 0 : (Date.now()/1000 - lastLogin) > timeComparison ? " a while back" : "" + console.log("LAST LOGIN: " + lastLogin, extra_timestamp_text) + + // Check if cloud sync is active, and if so, add a message about it + const cloudSyncInfo = selectedOrganization.cloud_sync === true ? "- Scale your onprem installation" : "" + + var body = `Hey,%0D%0A%0D%0AI noticed you tried to use Shuffle${extra_timestamp_text}, and thought you may be interested in a POC. It looks like you have ${workflow_amount} workflows made, but it still doesn't look like you are getting what you wanted out of Shuffle. If you're interested, I'd love to set up a quick call to see if we can help you get more out of Shuffle. %0D%0A%0D%0A Some of the things we can help with:%0D%0A ${your_apps} - Configuring and authenticating your apps%0D%0A ${usecases} -- Creating special usecases and apps%0D%0A%0D%0A +- Multi-Tenancy and creating special usecases%0D%0A +${cloudSyncInfo}%0D%0A -Let me know if you're interested, or set up a call here: https://drift.me/${username}` +If you're interested, please let me know a time that works for you, or set up a call here: https://drift.me/${username}` return `mailto:${admins}?bcc=frikky@shuffler.io,binu@shuffler.io&subject=${subject}&body=${body}` } @@ -984,6 +1004,10 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user leads.push("old customer") } + if (responseJson.lead_info.old_lead) { + leads.push("old lead") + } + if (responseJson.lead_info.tech_partner) { leads.push("tech partner") } @@ -2428,7 +2452,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user renderValue={(selected) => selected.join(', ')} MenuProps={MenuProps} > - {["contacted", "lead", "demo done", "pov", "customer", "open source", "student", "internal", "old customer", "creator", "tech partner"].map((name) => ( + {["contacted", "lead", "demo done", "pov", "customer", "open source", "student", "internal", "creator", "tech partner", "old customer", "old lead", ].map((name) => ( -1} /> @@ -3124,7 +3148,6 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user } if (loginInfo > 0) { - console.log("SETTING LAST LOGIN: " + loginInfo) lastLogin = new Date(loginInfo * 1000).toISOString().slice(0, 10) + " (" + data.login_info.length + ")" } } @@ -4382,7 +4405,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user - App Authentication + App Auth /> { onClose={() => { setExecutionModalOpen(false) }} - style={{ resize: "both", overflow: "auto", zIndex: 10005 }} + style={{ resize: "both", overflow: "auto", }} hideBackdrop={false} variant="temporary" BackdropProps={{ @@ -14607,7 +14607,6 @@ const AngularWorkflow = (defaultprops) => { maxWidth: isMobile ? "100%" : 420, color: "white", fontSize: 18, - zIndex: 10005, borderLeft: theme.palette.defaultBorder, }, }} @@ -14631,16 +14630,32 @@ const AngularWorkflow = (defaultprops) => { : null} {executionModalView === 0 ? (
- -

- - All Workflow Runs -

-
+
+ +

+ + All Workflow Runs +

+
+ + + + + +
- Value prefix + Prefix { value={refreshUrl} onChange={(e) => { // Just reusing this state - setRefreshUrl(e.target.value); + setRefreshUrl(e.target.value); }} />
@@ -3618,13 +3618,13 @@ const AppCreator = (defaultprops) => { { diff --git a/frontend/src/views/Welcome.jsx b/frontend/src/views/Welcome.jsx index 1b2085f4..991cd898 100644 --- a/frontend/src/views/Welcome.jsx +++ b/frontend/src/views/Welcome.jsx @@ -419,7 +419,7 @@ const Welcome = (props) => { : -
+
Help us get to know you diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx index bf32b52e..2bcd3b38 100755 --- a/frontend/src/views/Workflows.jsx +++ b/frontend/src/views/Workflows.jsx @@ -68,11 +68,12 @@ import { CloudDownload as CloudDownloadIcon, ExpandLess as ExpandLessIcon, ExpandMore as ExpandMoreIcon, - Done as DoneIcon, - CheckCircle as CheckCircleIcon, - RadioButtonUnchecked as RadioButtonUncheckedIcon, + Done as DoneIcon, + CheckCircle as CheckCircleIcon, + RadioButtonUnchecked as RadioButtonUncheckedIcon, ArrowLeft as ArrowLeftIcon, ArrowRight as ArrowRightIcon, + QueryStats as QueryStatsIcon, } from "@mui/icons-material"; import { DataGrid, GridToolbar } from "@mui/x-data-grid"; @@ -3061,6 +3062,18 @@ const Workflows = (props) => { const workflowButtons = ( + + + {view === "list" && ( diff --git a/frontend/src/components/Searchfield.jsx b/frontend/src/components/Searchfield.jsx index 7edef2ca..726a2fa8 100644 --- a/frontend/src/components/Searchfield.jsx +++ b/frontend/src/components/Searchfield.jsx @@ -81,7 +81,7 @@ const SearchField = props => { style: { color: "white", minWidth: 750, - height: 685, + height: 785, borderRadius: 16, border: "1px solid var(--Container-Stroke, #494949)", background: "var(--Container, #000000)", diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index ad643a9b..27ba8f18 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -4593,7 +4593,6 @@ const AngularWorkflow = (defaultprops) => { } } - console.log("NODE: ", nodedata) if (nodedata.decorator !== true && nodedata.attachedTo === undefined) { var newdata = JSON.parse(JSON.stringify(nodedata)) newdata.large_image = "" diff --git a/frontend/src/views/AppCreator.jsx b/frontend/src/views/AppCreator.jsx index 980e10c2..4cd2dfc7 100755 --- a/frontend/src/views/AppCreator.jsx +++ b/frontend/src/views/AppCreator.jsx @@ -374,8 +374,8 @@ const AppCreator = (defaultprops) => { "API key", "Bearer auth", "Basic auth", - "Oauth2", "JWT", + "Oauth2", ]; const apikeySelection = ["Header", "Query"]; @@ -411,6 +411,7 @@ const AppCreator = (defaultprops) => { const [appBuilding, setAppBuilding] = useState(false); const [fileDownloadEnabled, setFileDownloadEnabled] = useState(false); const [actionAmount, setActionAmount] = useState(increaseAmount); + const [oauth2Type, setOauth2Type] = useState("application"); const defaultAuth = { name: "", type: "header", @@ -1678,7 +1679,7 @@ const AppCreator = (defaultprops) => { value.in.length > 0 ) { setParameterName(value.in); - optionset = true + optionset = true } } else if (value.scheme === "bearer") { @@ -1709,24 +1710,24 @@ const AppCreator = (defaultprops) => { newauth.push({ "name": key, "type": value.in.toLowerCase(), - "in": value.in.toLowerCase(), + "in": value.in.toLowerCase(), "example": "", - }) - } else { - newauth.push({ - "name": key, - "type": value.in.toLowerCase(), - "in": value.in.toLowerCase(), - "example": "", - }) - } + }) + } else { + newauth.push({ + "name": key, + "type": value.in.toLowerCase(), + "in": value.in.toLowerCase(), + "example": "", + }) + } if (value.description !== undefined && value.description !== null && value.description.length > 0) { - // Don't want a real description - just the ones we're replacing with - if ((value.description.split(" ").length - 1) <= 2) { - setRefreshUrl(value.description) - } - } + // Don't want a real description - just the ones we're replacing with + if ((value.description.split(" ").length - 1) <= 2) { + setRefreshUrl(value.description) + } + } } else if (value.scheme === "basic") { setAuthenticationOption("Basic auth"); @@ -1734,15 +1735,15 @@ const AppCreator = (defaultprops) => { optionset = true } else if (value.scheme === "oauth2") { - setAuthenticationOption("Oauth2"); - setAuthenticationRequired(true); - optionset = true + setAuthenticationOption("Oauth2"); + setAuthenticationRequired(true); + optionset = true } else if (value.type === "oauth2" || key === "Oauth2" || key === "Oauth2c" || (key !== undefined && key !== null && key.toLowerCase().includes("oauth2"))) { //toast("Can't handle Oauth2 auth yet.") setAuthenticationOption("Oauth2"); setAuthenticationRequired(true); - optionset = true + optionset = true //console.log("FLOW-1: ", value) const flowkey = value.flow === undefined ? "flows" : "flow"; @@ -1754,12 +1755,11 @@ const AppCreator = (defaultprops) => { //console.log("FLOW2: ", value[flowkey][basekey]) if (value[flowkey] !== undefined && value[flowkey][basekey] !== undefined ) { - if ( - value[flowkey][basekey].authorizationUrl !== undefined && - parameterName.length === 0 - ) { - setParameterName(value[flowkey][basekey].authorizationUrl); - } + if (value[flowkey][basekey].authorizationUrl !== undefined && parameterName.length === 0) { + setParameterName(value[flowkey][basekey].authorizationUrl); + } else { + setOauth2Type("application") + } var tokenUrl = ""; if (value[flowkey][basekey].tokenUrl !== undefined) { @@ -2846,7 +2846,6 @@ const AppCreator = (defaultprops) => { style={{ margin: 0, flex: "1", backgroundColor: inputColor }} fullWidth={true} placeholder="/security/user/authenticate" - type="name" id="standard-required" margin="normal" variant="outlined" @@ -2866,6 +2865,40 @@ const AppCreator = (defaultprops) => { }, }} /> + + Optional: Authentication queries + + {/* + + Must use 'key=value&key=value' format + + } + onBlur={(e) => { + //setParameterName(e.target.value) + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + */}
) : null; @@ -2878,56 +2911,64 @@ const AppCreator = (defaultprops) => { color="textSecondary" style={{ marginTop: 10 }} > - Find the Authorization URL, Token URL and scopes in question for the API. Ensure the app in question is pointed at https://shuffler.io/set_authentication + {oauth2Type === "delegated" ? + "Find the Authorization URL, Token URL and scopes in question for the API. Ensure your app in the service uses redirect url https://shuffler.io/set_authentication" + : + "Find the Token URL and scopes in question for the API" + } - - Base Authorization URL for Oauth2 - - setParameterName(e.target.value)} + {oauth2Type === "delegated" ? + + + Base Authorization URL for Oauth2 + + setParameterName(e.target.value)} onBlur={(event) => { - var tmpstring = event.target.value.trim(); + var tmpstring = event.target.value.trim(); - if ( - tmpstring.length > 4 && - !tmpstring.startsWith("http") && - !tmpstring.startsWith("ftp") - ) { - toast("Auth URL must start with http(s)://"); - } + if ( + tmpstring.length > 4 && + !tmpstring.startsWith("http") && + !tmpstring.startsWith("ftp") + ) { + toast("Auth URL must start with http(s)://"); + } - if (tmpstring.includes("?")) { - var newtmp = tmpstring.split("?") - if (tmpstring.length > 1) { - tmpstring = newtmp[0] - } - } + if (tmpstring.includes("?")) { + var newtmp = tmpstring.split("?") + if (tmpstring.length > 1) { + tmpstring = newtmp[0] + } + } - setParameterName(tmpstring) - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style: { - color: "white", - }, - }} - /> + setParameterName(tmpstring) + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + : null} { }, }} /> - - Refresh-token URL for Oauth2 (Optional) - - setRefreshUrl(e.target.value)} - onBlur={(event) => { - var tmpstring = event.target.value.trim(); + {oauth2Type === "delegated" ? + + + Refresh-token URL for Oauth2 (Optional) + + setRefreshUrl(e.target.value)} + onBlur={(event) => { + var tmpstring = event.target.value.trim(); - if ( - tmpstring.length > 4 && - !tmpstring.startsWith("http") && - !tmpstring.startsWith("ftp") - ) { - toast("Refresh URL must start with http(s)://"); - } + if ( + tmpstring.length > 4 && + !tmpstring.startsWith("http") && + !tmpstring.startsWith("ftp") + ) { + toast("Refresh URL must start with http(s)://"); + } - if (tmpstring.includes("?")) { - var newtmp = tmpstring.split("?") - if (tmpstring.length > 1) { - tmpstring = newtmp[0] - } - } + if (tmpstring.includes("?")) { + var newtmp = tmpstring.split("?") + if (tmpstring.length > 1) { + tmpstring = newtmp[0] + } + } - setRefreshUrl(tmpstring) - }} - InputProps={{ - style: { - color: "white", - }, - }} - /> - - Scopes for Oauth2 - - + + : null} + + Scopes for Oauth2 + + { setOauth2Scopes(chips) setUpdate(Math.random()) }} - /> + />
) : null; @@ -5865,6 +5910,7 @@ const AppCreator = (defaultprops) => { }} />
+ Authentication { setParameterLocation("") } - setExtraAuth([]) + setExtraAuth([]) } }} value={authenticationOption} @@ -5910,6 +5956,42 @@ const AppCreator = (defaultprops) => { ))} + {authenticationOption === "Oauth2" ? + + {/* + + - Delegated: The user will get a popup for access their personal data. + - Application: Permissions are set by the app creator in the 3rd party platform. + + */} + + + : null} + + +
{basicAuth} {bearerAuth} From c629864c26b80e2f445a7b574f2937c7628f79e6 Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 15 Nov 2023 12:58:23 +0100 Subject: [PATCH 03/11] Fixed some execution setting bugs --- backend/app_sdk/app_base.py | 2 +- backend/go-app/go.mod | 2 +- backend/go-app/walkoff.go | 13 +++++++++++-- frontend/src/App.jsx | 6 ++++-- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index f19b0f2c..337c58d3 100755 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -3515,7 +3515,7 @@ class AppBase: timeout = 30 # Check if current app is Shuffle Tools, then set to 55 due to certain actions being slow (ioc parser..) - #uu In general, this should be disabled for onprem + # In general, this should be disabled for onprem if self.action["app_name"].lower() == "shuffle tools": timeout = 55 diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index 61f2cde9..ecb9219b 100755 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -1,6 +1,6 @@ module shuffle-shared -//replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared +replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared go 1.19 diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index aa5d5e97..32fb67ef 100755 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -1049,15 +1049,25 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request workflowExecution, execInfo, _, err := shuffle.PrepareWorkflowExecution(ctx, workflow, request, 10) if err != nil { + err = shuffle.SetWorkflowExecution(ctx, workflowExecution, true) + if err != nil { + log.Printf("[ERROR] Failed setting workflow execution during init (2): %s", err) + } + if strings.Contains(fmt.Sprintf("%s", err), "User Input") { // Special for user input callbacks return workflowExecution, fmt.Sprintf("%s", err), nil } else { - log.Printf("[WARNING] Failed in prepareExecution: %s", err) + log.Printf("[ERROR] Failed in prepareExecution: %s", err) return shuffle.WorkflowExecution{}, fmt.Sprintf("Failed starting workflow: %s", err), err } } + err = shuffle.SetWorkflowExecution(ctx, workflowExecution, true) + if err != nil { + log.Printf("[ERROR] Failed setting workflow execution during init (2): %s", err) + } + err = imageCheckBuilder(execInfo.ImageNames) if err != nil { log.Printf("[ERROR] Failed building the required images from %#v: %s", execInfo.ImageNames, err) @@ -1643,7 +1653,6 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request return shuffle.WorkflowExecution{}, "Failed building missing Docker images", err } - //Org []Org `json:"org,omitempty" datastore:"org"` err = shuffle.SetWorkflowExecution(ctx, workflowExecution, true) if err != nil { log.Printf("[WARNING] Error saving workflow execution for updates %s", err) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 8599d420..0daf7b5f 100755 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -8,7 +8,8 @@ import Workflows from "./views/Workflows"; import GettingStarted from "./views/GettingStarted"; import AngularWorkflow from "./views/AngularWorkflow.jsx"; -import Header from "./components/Header.jsx"; +//import Header from "./components/Header.jsx"; +import Header from "./components/NewHeader.jsx"; import theme from "./theme"; import Apps from "./views/Apps"; import AppCreator from "./views/AppCreator"; @@ -347,7 +348,6 @@ const App = (message, props) => { /> } /> - } /> } /> { /> } /> + } /> + } /> Date: Wed, 15 Nov 2023 12:58:58 +0100 Subject: [PATCH 04/11] Added new header --- frontend/src/components/NewHeader.jsx | 1377 ++++++++++++++++++++++++ frontend/src/components/Oauth2Auth.jsx | 4 +- frontend/src/views/AngularWorkflow.jsx | 8 +- frontend/src/views/AppCreator.jsx | 6 +- 4 files changed, 1386 insertions(+), 9 deletions(-) create mode 100644 frontend/src/components/NewHeader.jsx diff --git a/frontend/src/components/NewHeader.jsx b/frontend/src/components/NewHeader.jsx new file mode 100644 index 00000000..7b72f0bd --- /dev/null +++ b/frontend/src/components/NewHeader.jsx @@ -0,0 +1,1377 @@ +import React, { useState } from "react"; +import { toast } from "react-toastify"; +import theme from "../theme.jsx"; +import { BrowserView, MobileView } from "react-device-detect"; + +import { useNavigate, Link } from "react-router-dom"; +import ReactGA from "react-ga4"; +import SearchField from "../components/Searchfield.jsx"; +import { + Paper, + Typography, + Badge, + Tooltip, + List, + ListItem, + Avatar, + Menu, + MenuItem, + Select, + Button, + Grid, + IconButton, + Divider, + LinearProgress, + + AppBar, +} from "@mui/material"; + +import { + MeetingRoom as MeetingRoomIcon, + HelpOutline as HelpOutlineIcon, + Settings as SettingsIcon, + Notifications as NotificationsIcon, + Home as HomeIcon, + Apps as AppsIcon, + Description as DescriptionIcon, + EmojiObjects as EmojiObjectsIcon, + Business as BusinessIcon, + Polyline as PolylineIcon, +} from "@mui/icons-material"; + +import { + Analytics as AnalyticsIcon, + Lightbulb as LightbulbIcon, +} from "@mui/icons-material"; + +const hoverColor = "#f85a3e"; +const hoverOutColor = "#e8eaf6"; + +const Header = (props) => { + const { + globalUrl, + setNotifications, + notifications, + isLoaded, + isLoggedIn, + removeCookie, + homePage, + userdata, + isMobile, + serverside, + } = props; + + const [HomeHoverColor, setHomeHoverColor] = useState(hoverOutColor); + const [SoarHoverColor, setSoarHoverColor] = useState(hoverOutColor); + const [LoginHoverColor, setLoginHoverColor] = useState(hoverOutColor); + const [DocsHoverColor, setDocsHoverColor] = useState(hoverOutColor); + const [HelpHoverColor, setHelpHoverColor] = useState(hoverOutColor); + const [isHeader, setIsHeader] = React.useState(false); + const [anchorEl, setAnchorEl] = React.useState(null); + const [anchorElAvatar, setAnchorElAvatar] = React.useState(null); + const [subAnchorEl, setSubAnchorEl] = React.useState(null); + let navigate = useNavigate(); + + const handleClick = (event) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + setAnchorElAvatar(null); + }; + // Should be based on some path + const logoCheck = !homePage ? null : null + + const hrefStyle = { + color: hoverOutColor, + textDecoration: "none", + }; + + const menuText = { + textTransform: "none", + color: "#FFF", + textAlign: "center", + fontSize: 16, + fontStyle: "normal", + fontWeight: 400, + lineHeight: "normal", + } + + const isCloud = + serverside === true || typeof window === "undefined" + ? true + : window.location.host === "localhost:3002" || + window.location.host === "shuffler.io"; + + const clearNotifications = () => { + // Don't really care about the logout + fetch(`${globalUrl}/api/v1/notifications/clear`, { + credentials: "include", + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }) + .then(function (response) { + if (response.status !== 200) { + console.log("Error in response"); + } + + return response.json(); + }) + .then(function (responseJson) { + if (responseJson.success === true) { + setNotifications([]); + handleClose(); + } else { + toast("Failed dismissing notifications. Please try again later."); + } + }) + .catch((error) => { + console.log("error in notification dismissal: ", error); + //removeCookie("session_token", {path: "/"}) + }); + }; + + const dismissNotification = (alert_id) => { + // Don't really care about the logout + fetch(`${globalUrl}/api/v1/notifications/${alert_id}/markasread`, { + credentials: "include", + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }) + .then(function (response) { + if (response.status !== 200) { + console.log("Error in response"); + } + + return response.json(); + }) + .then(function (responseJson) { + if (responseJson.success === true) { + const newNotifications = notifications.filter( + (data) => data.id !== alert_id + ); + console.log("NEW NOTIFICATIONS: ", newNotifications); + setNotifications(newNotifications); + } else { + toast("Failed dismissing notification. Please try again later."); + } + }) + .catch((error) => { + console.log("error in notification dismissal: ", error); + //removeCookie("session_token", {path: "/"}) + }); + }; + + // DEBUG HERE + const handleClickLogout = () => { + console.log("SHOULD LOG OUT"); + + // Don't really care about the logout + fetch(globalUrl + "/api/v1/logout", { + credentials: "include", + method: "POST", + headers: { + "Content-Type": "application/json", + }, + }) + .then(() => { + // Log out anyway + removeCookie("session_token", { path: "/" }); + removeCookie("session_token", { path: "/" }); + removeCookie("session_token", { path: "/" }); + removeCookie("session_token", { path: "/" }); + window.location.pathname = "/"; + }) + .catch((error) => { + console.log(error); + }); + }; + + // Rofl this is weird + const handleDocsHover = () => { + setDocsHoverColor(hoverColor); + }; + + const handleDocsHoverOut = () => { + setDocsHoverColor(hoverOutColor); + }; + + const handleHomeHover = () => { + setHomeHoverColor(hoverColor); + }; + + const handleHelpHover = () => { + setHelpHoverColor(hoverColor); + }; + + const handleHelpHoverOut = () => { + setHelpHoverColor(hoverOutColor); + }; + + const handleSoarHover = () => { + setSoarHoverColor(hoverColor); + }; + + const handleSoarHoverOut = () => { + setSoarHoverColor(hoverOutColor); + }; + + const handleHomeHoverOut = () => { + setHomeHoverColor(hoverOutColor); + }; + + const handleLoginHover = () => { + setLoginHoverColor(hoverColor); + }; + + const handleLoginHoverOut = () => { + setLoginHoverColor(hoverOutColor); + }; + + const notificationWidth = 300 + const imagesize = 22; + const boxColor = "#86c142"; + + const NotificationItem = (props) => { + const {data} = props + + var image = ""; + var orgName = ""; + var orgId = ""; + if (userdata.orgs !== undefined) { + const foundOrg = userdata.orgs.find((org) => org.id === data["org_id"]); + if (foundOrg !== undefined && foundOrg !== null) { + //position: "absolute", bottom: 5, right: -5, + const imageStyle = { + width: imagesize, + height: imagesize, + pointerEvents: "none", + marginLeft: + data.creator_org !== undefined && data.creator_org.length > 0 + ? 20 + : 0, + borderRadius: 10, + border: + foundOrg.id === userdata.active_org.id + ? `3px solid ${boxColor}` + : null, + cursor: "pointer", + marginRight: 10, + }; + + image = + foundOrg.image === "" ? ( + {foundOrg.name} + ) : ( + {foundOrg.name} {}} + /> + ); + + orgName = foundOrg.name; + orgId = foundOrg.id; + } + } + + return ( + + {/* + {new Date(data.updated_at).toISOString()} + */} + {data.reference_url !== undefined && + data.reference_url !== null && + data.reference_url.length > 0 ? ( + + {data.title} + + ) : ( + + {data.title} + + )} + + {data.image !== undefined && + data.image !== null && + data.image.length > 0 ? ( + {data.title} + ) : null} + {data.description} + {/*data.tags !== undefined && data.tags !== null && data.tags.length > 0 ? + data.tags.map((tag, index) => { + return ( + { + }} + variant="outlined" + color="primary" + /> + ) + }) + : null */} +
+ {data.read === false ? ( + + ) : null} + +
{}} + > + {image} +
+
+
+
+ ); + }; + + const notificationMenu = ( + + { + setAnchorEl(event.currentTarget); + }} + > + + + + + { + handleClose(); + }} + > + +
+ + Your Notifications ({notifications.length}) + + {notifications.length > 1 ? ( + + ) : null} +
+ + Notifications are made by Shuffle to help you discover issues or + improvements. + +
+ {notifications.map((data, index) => { + return ; + })} +
+
+ ); + + const handleClickChangeOrg = (orgId) => { + // Don't really care about the logout + //name: org.name, + //orgId = "asd" + const data = { + org_id: orgId, + }; + + localStorage.setItem("globalUrl", ""); + localStorage.setItem("getting_started_sidebar", "open"); + + fetch(`${globalUrl}/api/v1/orgs/${orgId}/change`, { + mode: "cors", + credentials: "include", + crossDomain: true, + method: "POST", + body: JSON.stringify(data), + withCredentials: true, + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }) + .then(function (response) { + if (response.status !== 200) { + console.log("Error in response"); + } + + return response.json(); + }) + .then(function (responseJson) { + if (responseJson.success === true) { + if ( + responseJson.region_url !== undefined && + responseJson.region_url !== null && + responseJson.region_url.length > 0 + ) { + console.log("Region Change: ", responseJson.region_url); + localStorage.setItem("globalUrl", responseJson.region_url); + //globalUrl = responseJson.region_url + } + + setTimeout(() => { + window.location.reload(); + }, 2000); + toast("Successfully changed active organization - refreshing!"); + } else { + toast("Failed changing org: ", responseJson.reason); + } + }) + .catch((error) => { + console.log("error changing: ", error); + //removeCookie("session_token", {path: "/"}) + }); + }; + + const supportMenu = ( + +
+ + {}} + > + Discord Community Join + + + + + ); + + // Should be based on some path + const parsedAvatar = + userdata.avatar !== undefined && + userdata.avatar !== null && + userdata.avatar.length > 0 + ? userdata.avatar + : ""; + + const avatarMenu = ( + + { + setAnchorElAvatar(event.currentTarget); + }} + > + + + { + handleClose(); + }} + > + + { + handleClose(); + }} + > + Admin + + + + + { + handleClose(); + }} + > + About + + + {/* + + { + handleClose(); + }} + > + Get Started + + + */} + + { + handleClose(); + }} + > + Use Cases + + + + + { + handleClose(); + }} + > + Creator page + + + + { + handleClose(); + }} + > + Settings + + + + { + handleClickLogout(); + event.preventDefault(); + handleClose(); + }} + > +  Logout + + + + ); + + const listItemStyle = { + textAlign: "center", + marginTop: "auto", + marginBottom: "auto", + marginRight: 10, + }; + + // Handle top bar or something + const defaultTop = isCloud ? 0 : 7; + const loginTextBrowser = !isLoggedIn ? ( +
+ + + + { + if (isCloud) { + ReactGA.event({ + category: "header", + action: "home_click", + label: "", + }); + } + }} + > + shuffle logo + + + + + + + + + + + {isCloud ? ( + + + + + + ) : null} + + + + + + + +
+ + + + + + + + + + + +
+ ) : ( +
+
+
+ + + +
+ + + logo + + +
+ + + +
+ {/* + + */} + + Workflows + +
+ +
+ + +
+ {/* + + */} + + Apps + +
+ +
+ {/* + + +
Dashboard
+ +
+ */} + + +
+ {/* + + */} + + Docs + +
+ +
+
+
+
+
+ +
+
+ + + {avatarMenu} + {notificationMenu} + {/*supportMenu*/} + {logoCheck} + + + {userdata === undefined || + userdata.orgs === undefined || + userdata.orgs === null || + userdata.orgs.length <= 1 ? null : ( + + + + )} + + {/* Show on cloud, if not suborg and if not customer/pov/internal */} + {isCloud && + (userdata.org_status === undefined || + userdata.org_status === null || + userdata.org_status.length === 0) ? ( + + + + + + ) : null} + + {userdata === undefined || + userdata.app_execution_limit === undefined || + userdata.app_execution_usage === undefined || + userdata.app_execution_usage < 1000 ? null : ( + +
= + 0.9 + ? "#f86a3e" + : null, + }} + onClick={() => { + console.log( + userdata.appe_execution_usage / + userdata.app_execution_limit + ); + if (window.drift !== undefined) { + window.drift.api.startInteraction({ + interactionId: 326905, + }); + navigate("/pricing"); + } else { + console.log( + "Couldn't find drift in window.drift and not .drift-open-chat with querySelector: ", + window.drift + ); + } + }} + > + + + {( + (userdata.app_execution_usage / + userdata.app_execution_limit) * + 100 + ).toFixed(0)} + % + + + +
+
+ )} +
+
+
+
+ ); + + const loginTextMobile = !isLoggedIn ? ( +
+ + + +
+ + + logo + + +
+ +
+ + +
+ About +
+ +
+ + + + + + + + + + +
+
+ ) : ( +
+
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + +
+
+
+ + +
+ Logout +
+
+ {logoCheck} + +
+
+
+ ); + + // + return !isMobile ? + isLoggedIn ? +
+ + {loginTextBrowser} + +
+ : + + + {loginTextBrowser} + + + : + {loginTextMobile} +}; + +export default Header; diff --git a/frontend/src/components/Oauth2Auth.jsx b/frontend/src/components/Oauth2Auth.jsx index b70e536a..ca51d31a 100755 --- a/frontend/src/components/Oauth2Auth.jsx +++ b/frontend/src/components/Oauth2Auth.jsx @@ -125,7 +125,7 @@ const AuthenticationOauth2 = (props) => { const allscopes = authenticationType.scope !== undefined ? authenticationType.scope : []; - const [selectedScopes, setSelectedScopes] = React.useState(allscopes.length === 1 ? [allscopes[0]] : []) + const [selectedScopes, setSelectedScopes] = React.useState(allscopes.length <= 3 ? allscopes : []) const [manuallyConfigure, setManuallyConfigure] = React.useState( defaultConfigSet ? false : true ); @@ -883,7 +883,7 @@ const AuthenticationOauth2 = (props) => { //authenticationOption.label = event.target.value }} /> - {allscopes.length === 0 ? null : "Scopes"} + {allscopes.length === 0 ? null : "Scopes (access rights)"} {allscopes.length === 0 ? null : (
diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 27ba8f18..bb019f14 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -591,7 +591,7 @@ const AngularWorkflow = (defaultprops) => { } useEffect(() => { - console.log("In useeffect for loopRunning: ", loopRunning) + //console.log("In useeffect for loopRunning: ", loopRunning) if (loopRunning) { const intervalId = setInterval(() => { if (!loopRunning) { @@ -1244,7 +1244,7 @@ const AngularWorkflow = (defaultprops) => { // Doesn't work because this is some async garbage if (executionData.execution_id === undefined || (responseJson.execution_id === executionData.execution_id && responseJson.results !== undefined && responseJson.results !== null)) { if (executionData.status !== responseJson.status || executionData.result !== responseJson.result || (executionData.results !== undefined && responseJson.results !== null && executionData.results.length !== responseJson.results.length)) { - console.log("Updating data!") + //console.log("Updating data!") setExecutionData(responseJson) } else { if (responseJson.status === "ABORTED" || responseJson.status === "STOPPED" || responseJson.status === "FAILURE" || responseJson.status === "WAITING") { @@ -15381,7 +15381,7 @@ const AngularWorkflow = (defaultprops) => { }} onClick={() => { const oldstartnode = cy.getElementById(data.action.id); - console.log("FOUND NODe: ", oldstartnode) + //console.log("FOUND NODe: ", oldstartnode) if (oldstartnode !== undefined && oldstartnode !== null) { const foundname = oldstartnode.data("label") if (foundname !== undefined && foundname !== null) { @@ -15389,7 +15389,7 @@ const AngularWorkflow = (defaultprops) => { } } - console.log("Click data: ", data) + //console.log("Click data: ", data) //data.action.label = "" setSelectedResult(data); setCodeModalOpen(true); diff --git a/frontend/src/views/AppCreator.jsx b/frontend/src/views/AppCreator.jsx index 4cd2dfc7..c2656a95 100755 --- a/frontend/src/views/AppCreator.jsx +++ b/frontend/src/views/AppCreator.jsx @@ -242,7 +242,7 @@ export const appCategories = [ "name": "Eradication", "color": "#FFC107", "icon": "eradication", - "action_labels": ["List Alerts", "Close Alert", "Get Alert", "Create detection", "Block hash", "Search Hosts", "Isolate host", "Unisolate host"], + "action_labels": ["List Alerts", "Close Alert", "Get Alert", "Create detection", "Block hash", "Search Hosts", "Isolate host", "Unisolate host", "Trigger host scan",], }, { "name": "Cases", "color": "#FFC107", @@ -1757,8 +1757,8 @@ const AppCreator = (defaultprops) => { ) { if (value[flowkey][basekey].authorizationUrl !== undefined && parameterName.length === 0) { setParameterName(value[flowkey][basekey].authorizationUrl); - } else { - setOauth2Type("application") + // } else { + // setOauth2Type("application") } var tokenUrl = ""; From e924919fac1f847b2cf2dcdd8f503fcb4db7e10c Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 15 Nov 2023 13:00:59 +0100 Subject: [PATCH 05/11] Added new search engine --- frontend/package.json | 5 +- frontend/src/components/SearchData.jsx | 800 +++++++++++++++++++++++++ 2 files changed, 803 insertions(+), 2 deletions(-) create mode 100644 frontend/src/components/SearchData.jsx diff --git a/frontend/package.json b/frontend/package.json index eb3cfe90..0cb2e764 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -60,6 +60,7 @@ "react-driftjs": "^1.2.2", "react-dropzone": "^14.2.3", "react-ga4": "^2.0.0", + "react-hotkeys": "^2.0.0", "react-instantsearch-dom": "^6.28.0", "react-json-pretty": "^2.2.0", "react-json-view": "^1.21.3", @@ -76,10 +77,10 @@ "shellwords": "^0.1.1", "simplebar": "^4.2.3", "styled-components": "^4.4.0", + "webpack": "^5.88.2", "yaml": "^1.7.2", "yamljs": "^0.3.0", - "zone.js": "^0.13.1", - "webpack": "^5.88.2" + "zone.js": "^0.13.1" }, "scripts": { "start": "HTTPS=false&&PORT=3000 GENERATE_SOURCEMAP=false react-scripts --openssl-legacy-provider start", diff --git a/frontend/src/components/SearchData.jsx b/frontend/src/components/SearchData.jsx new file mode 100644 index 00000000..cc9c1551 --- /dev/null +++ b/frontend/src/components/SearchData.jsx @@ -0,0 +1,800 @@ +import React, { useState, useEffect, useRef } from 'react'; + +import theme from '../theme.jsx'; +import { useNavigate, Link, useParams } from "react-router-dom"; + +import { + Chip, + IconButton, + TextField, + InputAdornment, + List, + Card, + ListItem, + ListItemAvatar, + ListItemText, + Avatar, + Grid, + Typography, + Tooltip, + Divider, + Button, +} from '@mui/material'; +import ArticleIcon from '@mui/icons-material/Article'; +import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; +import ManageSearchIcon from '@mui/icons-material/ManageSearch'; +import { + AvatarGroup, +} from "@mui/material" + +import { Search as SearchIcon, Close as CloseIcon, Folder as FolderIcon, Code as CodeIcon, LibraryBooks as LibraryBooksIcon } from '@mui/icons-material' + +import algoliasearch from 'algoliasearch/lite'; +import aa from 'search-insights' +import { InstantSearch, Configure, connectSearchBox, connectHits, Index } from 'react-instantsearch-dom'; +//import { InstantSearch, SearchBox, Hits, connectSearchBox, connectHits, Index } from 'react-instantsearch-dom'; + +// https://www.algolia.com/doc/api-reference/widgets/search-box/react/ +const chipStyle = { + backgroundColor: "#3d3f43", height: 30, marginRight: 5, paddingLeft: 5, paddingRight: 5, height: 28, cursor: "pointer", borderColor: "#3d3f43", color: "white", +} + +const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") +const SearchData = props => { + const { serverside, userdata, setModalOpen, modalOpen } = props + + let navigate = useNavigate(); + const borderRadius = 3 + const node = useRef() + const [searchOpen, setSearchOpen] = useState(true) + const [value, setValue] = useState(""); + const [userTyped, setUserTyped] = useState(false) + + if (serverside === true) { + return null + } + + //if (window !== undefined && window.location !== undefined && window.location.pathname === "/search") { + // return null + //} + + const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; + + //if (window.location.pathname !== oldPath) { + // setSearchOpen(false) + // setOldPath(window.location.pathname) + //} + + //if (window.location.pathname === "/search") { + // setModalOpen(true) + //} + // if (window.location.pathname === "/docs" || window.location.pathname === "/apps" || window.location.pathname === "/usecases" ) { + // setModalOpen(false) + // } + + //useEffect(() => { + // if (searchOpen) { + // var tarfield = document.getElementById("shuffle_search_field") + // tarfield.focus() + // } + //}, searchOpen) + + const SearchBox = ({ currentRefinement, refine, isSearchStalled, }) => { + const keyPressHandler = (e) => { + // e.preventDefault(); + if (e.which === 13) { + // alert("You pressed enter!"); + navigate("/search?q=" + currentRefinement, { state: value, replace: true }); + setSearchOpen(false) + setModalOpen(false) + return + } + }; + /* + endAdornment: ( + { + event.preventDefault() + }}> + { + setSearchOpen(false) + }} /> + + ), + */ + + return ( +
-
@@ -557,6 +559,21 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho + {/* + + + + + + */} {isCloud ? @@ -598,33 +615,60 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
-
+
- +
+
- - + + + + {isCloud ? + + + + + + : null} + + {/* + + {supportMenu} + + */} + + {/* + + + - {isCloud ? - - - - - : null} - + }}> + Docs + + + + */}
From 6e1a267cd20789693fad64f2258a4f9ee8130ec2 Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 15 Nov 2023 13:08:45 +0100 Subject: [PATCH 09/11] Changed header to fit new searchbar --- frontend/src/components/Header.jsx | 7 +++---- frontend/src/components/Searchfield.jsx | 2 ++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/Header.jsx b/frontend/src/components/Header.jsx index d3137674..2acc229f 100644 --- a/frontend/src/components/Header.jsx +++ b/frontend/src/components/Header.jsx @@ -43,9 +43,8 @@ import { Lightbulb as LightbulbIcon, } from "@mui/icons-material"; -//import { useAlert - import SearchField from '../components/Searchfield.jsx' + const hoverColor = "#f85a3e" const hoverOutColor = "#e8eaf6" @@ -615,7 +614,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
-
From 9e1195caf2eab7dc175a6157b64c087120b2ae3b Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 15 Nov 2023 13:32:09 +0100 Subject: [PATCH 10/11] Using latest shuffle-shared --- backend/go-app/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index 9ba56e7b..d774a438 100755 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -19,7 +19,7 @@ require ( github.com/gorilla/mux v1.8.0 github.com/h2non/filetype v1.1.3 github.com/satori/go.uuid v1.2.0 - github.com/shuffle/shuffle-shared v0.4.78 + github.com/shuffle/shuffle-shared v0.4.79 golang.org/x/crypto v0.9.0 google.golang.org/api v0.125.0 google.golang.org/appengine v1.6.7 From 06593a2ef32d58f1ff4ed606be13e3deaa924f8b Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 15 Nov 2023 13:32:24 +0100 Subject: [PATCH 11/11] Fixed Oauth2 app auth problems --- .../src/components/AuthenticationNormal.jsx | 12 +-- frontend/src/components/Oauth2Auth.jsx | 89 ++++++++----------- frontend/src/views/AngularWorkflow.jsx | 6 +- 3 files changed, 49 insertions(+), 58 deletions(-) diff --git a/frontend/src/components/AuthenticationNormal.jsx b/frontend/src/components/AuthenticationNormal.jsx index 1140de10..271dc59a 100644 --- a/frontend/src/components/AuthenticationNormal.jsx +++ b/frontend/src/components/AuthenticationNormal.jsx @@ -56,13 +56,13 @@ const AuthenticationData = (props) => { if (!responseJson.success) { toast("Failed to set app auth: " + responseJson.reason); } else { - if (getAppAuthentication !== undefined) { - getAppAuthentication() - } + if (getAppAuthentication !== undefined) { + getAppAuthentication() + } - if (setAuthenticationModalOpen !== undefined) { - setAuthenticationModalOpen(false) - } + if (setAuthenticationModalOpen !== undefined) { + setAuthenticationModalOpen(false) + } // Needs a refresh with the new authentication.. //toast("Successfully saved new app auth") diff --git a/frontend/src/components/Oauth2Auth.jsx b/frontend/src/components/Oauth2Auth.jsx index ca51d31a..3e834341 100755 --- a/frontend/src/components/Oauth2Auth.jsx +++ b/frontend/src/components/Oauth2Auth.jsx @@ -125,7 +125,7 @@ const AuthenticationOauth2 = (props) => { const allscopes = authenticationType.scope !== undefined ? authenticationType.scope : []; - const [selectedScopes, setSelectedScopes] = React.useState(allscopes.length <= 3 ? allscopes : []) + const [selectedScopes, setSelectedScopes] = React.useState(allscopes.length > 0 && allscopes.length <= 3 ? [allscopes[0]] : []) const [manuallyConfigure, setManuallyConfigure] = React.useState( defaultConfigSet ? false : true ); @@ -301,58 +301,47 @@ const AuthenticationOauth2 = (props) => { if ((authenticationType.redirect_uri === undefined || authenticationType.redirect_uri === null || authenticationType.redirect_uri.length === 0) && (authenticationType.token_uri !== undefined && authenticationType.token_uri !== null && authenticationType.token_uri.length > 0)) { console.log("No redirect URI found, and token URI found. Assuming client credentials flow and saving directly in the database") - // Find app.configuration=true fields in the app.paramters - var parsedFields = [{ - "key": "client_id", - "value": client_id, - }, - { - "key": "client_secret", - "value": client_secret, - }, - { - "key": "scope", - "value": scopes.join(","), - }, - { - "key": "token_uri", - "value": authenticationType.token_uri, - }] + // Find app.configuration=true fields in the app.paramters + var parsedFields = [{ + "key": "client_id", + "value": client_id, + }, + { + "key": "client_secret", + "value": client_secret, + }, + { + "key": "scope", + "value": scopes.join(","), + }, + { + "key": "token_uri", + "value": authenticationType.token_uri, + }] - // Not necessary yet to do something like this due to not showing the fields anyway - /* - if (selectedApp.parameters !== undefined && selectedApp.parameters !== null && selectedApp.parameters.length > 0) { - - for (var i = 0; i < selectedApp.parameters.length; i++) { - if (selectedApp.parameters[i].configuration !== true) { - continue - } - - console.log("Found configuration field: ", selectedApp.parameters[i].key, " with example: ", selectedApp.parameters[i].example) - parsedFields.push({ - "key": selectedApp.parameters[i].key, - "value": selectedApp.parameters[i].example, - }) - } - } - */ - - - const appAuthData = { - "label": "OAuth2 for " + selectedApp.name, - "app": { - "id": selectedApp.id, - "name": selectedApp.name, - "version": selectedApp.version, - "large_image": selectedApp.large_image, + const appAuthData = { + "label": "OAuth2 for " + selectedApp.name, + "app": { + "id": selectedApp.id, + "name": selectedApp.name, + "version": selectedApp.version, + "large_image": selectedApp.large_image, }, "fields": parsedFields, "type": "oauth2-app", "reference_workflow": workflowId, - } - setNewAppAuth(appAuthData) + } - return + setNewAppAuth(appAuthData) + // Wait 1 second, then get app auth with update + // + if (getAppAuthentication !== undefined) { + setTimeout(() => { + getAppAuthentication(true, true, true); + }, 1000) + } + + return } @@ -463,9 +452,9 @@ const AuthenticationOauth2 = (props) => { //alert('"Secure Payment" window closed!'); // - if (getAppAuthentication !== undefined) { - getAppAuthentication(true, true, true); - } + if (getAppAuthentication !== undefined) { + getAppAuthentication(true, true, true); + } } else { console.log("Not closed") } diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index bb019f14..8133d485 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -1713,7 +1713,9 @@ const AngularWorkflow = (defaultprops) => { }) .then((responseJson) => { if (!responseJson.success) { - toast("Failed to start: " + responseJson.reason); + //toast("Failed to start: " + responseJson.reason); + toast(responseJson.reason); + //toast.error(responseJson.reason); setExecutionRunning(false); setExecutionRequestStarted(false); stop(); @@ -16490,7 +16492,7 @@ const AngularWorkflow = (defaultprops) => { var newAuthOption = JSON.parse(JSON.stringify(authenticationOption)); var newFields = []; - console.log("Fields: ", newAuthOption.fields) + console.log("Fields: ", newAuthOption.fields) for (let authkey in newAuthOption.fields) { const value = newAuthOption.fields[authkey]; newFields.push({