From f6c7939ddadc98998967b79c796bed8b946356c7 Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 14 Nov 2023 15:20:31 +0100 Subject: [PATCH] 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" && (