Added statistics to both backend and frontend + cloud sync

This commit is contained in:
Frikky
2023-10-17 02:40:32 +02:00
parent 0579f60b58
commit 595c217a7c
32 changed files with 4035 additions and 1753 deletions
-2
View File
@@ -1856,8 +1856,6 @@ const AppFramework = (props) => {
//autounselectify={true}
var usecasediff = -100
const bgColor = color === undefined || color === null || color.length === 0 ? theme.palette.surfaceColor : color
console.log("Background: ", bgColor)
return (
<div style={{margin: "auto", backgroundColor: bgColor, position: "relative", }}>
<div style={{position: "absolute"}}>
+1 -1
View File
@@ -200,7 +200,7 @@ const Appsearch = props => {
const CustomHits = connectHits(InputHits)
return (
<div style={{width: "100%", textAlign: "center", position: "relative", height: "100%",}}>
<div style={{width: 287, height: 295, padding: "16px 16px 267px 16px", alignItems: "center", gap: 138,}}>
<InstantSearch searchClient={searchClient} indexName="appsearch">
{/* showSearch === false ? null :
<div style={{maxWidth: 450, margin: "auto", }}>
@@ -18,7 +18,6 @@ import {
Grid,
Paper,
Typography,
TextField,
Zoom,
} from "@mui/material";
@@ -294,9 +294,6 @@ const AuthenticationData = (props) => {
InputProps={{
style: {
color: "white",
marginLeft: "5px",
maxWidth: "95%",
height: 50,
fontSize: "1em",
},
disableUnderline: true,
@@ -386,7 +383,6 @@ const AuthenticationData = (props) => {
PaperProps={{
style: {
pointerEvents: "auto",
backgroundColor: theme.palette.surfaceColor,
color: "white",
minWidth: 600,
minHeight: 600,
@@ -441,9 +437,6 @@ const AuthenticationData = (props) => {
InputProps={{
style: {
color: "white",
marginLeft: "5px",
maxWidth: "95%",
height: 50,
fontSize: "1em",
},
}}
File diff suppressed because it is too large Load Diff
+280
View File
@@ -0,0 +1,280 @@
import React, { useState, useEffect } from 'react';
import classNames from "classnames";
import theme from '../theme.jsx';
import {
Tooltip,
TextField,
IconButton,
Button,
Typography,
Grid,
Paper,
Chip,
Checkbox,
} from "@mui/material";
import {
BarChart,
RadialBarChart,
RadialAreaChart,
RadialAxis,
StackedBarSeries,
TooltipArea,
ChartTooltip,
TooltipTemplate,
RadialAreaSeries,
RadialPointSeries,
RadialArea,
RadialLine,
TreeMap,
TreeMapSeries,
TreeMapLabel,
TreeMapRect,
Line,
LineChart,
LineSeries,
LinearYAxis,
LinearXAxis,
LinearYAxisTickSeries,
LinearXAxisTickSeries,
Area,
AreaChart,
AreaSeries,
AreaSparklineChart,
PointSeries,
GridlineSeries,
Gridline,
Stripes,
Gradient,
GradientStop,
LinearXAxisTickLabel,
} from 'reaviz';
const LineChartWrapper = ({keys, inputname, height, width}) => {
const [hovered, setHovered] = useState("");
const inputdata = keys.data === undefined ? keys : keys.data
return (
<div style={{color: "white", border: "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, padding: 30, marginTop: 15, }}>
<Typography variant="h6" style={{marginBotton: 15, }}>
{inputname}
</Typography>
<BarChart
width={"100%"}
height={height}
data={inputdata}
gridlines={
<GridlineSeries line={<Gridline direction="all" />} />
}
/>
</div>
)
}
const AppStats = (defaultprops) => {
const { globalUrl, selectedOrganization, userdata, } = defaultprops;
const [keys, setKeys] = useState([])
const [searches, setSearches] = useState([]);
const [clickData, setClickData] = useState(undefined);
const [conversionData, setConversionData] = useState(undefined);
const [statistics, setStatistics] = useState(undefined);
const [appRuns, setAppruns] = useState(undefined);
const [workflowRuns, setWorkflowRuns] = useState(undefined);
const [subflowRuns, setSubflowRuns] = useState(undefined);
const handleDataSetting = (inputdata, grouping) => {
if (inputdata === undefined || inputdata === null) {
return
}
const dailyStats = inputdata.daily_statistics
if (dailyStats === undefined || dailyStats === null) {
return
}
console.log("Looking at daily data: ", inputdata)
var appRuns = {
"key": "App Runs",
"data": []
}
var workflowRuns = {
"key": "Workflow Runs (includes subflows)",
"data": []
}
var subflowRuns = {
"key": "Subflow Runs",
"data": []
}
for (let key in dailyStats) {
// Always skips first one as it has accumulated data in it
if (key === 0) {
continue
}
const item = dailyStats[key]
if (item["date"] === undefined) {
console.log("No date: ", item)
continue
}
// Check if app_executions key in item
if (item["app_executions"] !== undefined && item["app_executions"] !== null) {
appRuns["data"].push({
key: new Date(item["date"]),
data: item["app_executions"]
})
}
// Check if workflow_executions key in item
if (item["workflow_executions"] !== undefined && item["workflow_executions"] !== null) {
workflowRuns["data"].push({
key: new Date(item["date"]),
data: item["workflow_executions"]
})
}
if (item["subflow_executions"] !== undefined && item["subflow_executions"] !== null) {
subflowRuns["data"].push({
key: new Date(item["date"]),
data: item["subflow_executions"]
})
}
}
// Adds data for today
console.log("Inputdata: ", inputdata)
if (inputdata["daily_app_executions"] !== undefined && inputdata["daily_app_executions"] !== null) {
appRuns["data"].push({
key: new Date(),
data: inputdata["daily_app_executions"]
})
}
if (inputdata["daily_workflow_executions"] !== undefined && inputdata["daily_workflow_executions"] !== null) {
workflowRuns["data"].push({
key: new Date(),
data: inputdata["daily_workflow_executions"]
})
}
if (inputdata["daily_subflow_executions"] !== undefined && inputdata["daily_subflow_executions"] !== null) {
subflowRuns["data"].push({
key: new Date(),
data: inputdata["daily_subflow_executions"]
})
}
setSubflowRuns(subflowRuns)
setWorkflowRuns(workflowRuns)
setAppruns(appRuns)
}
const getStats = () => {
fetch(`${globalUrl}/api/v1/orgs/${selectedOrganization.id}/stats`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for workflows :O!: ", response.status);
return;
}
return response.json();
})
.then((responseJson) => {
if (responseJson["success"] === false) {
return
}
setStatistics(responseJson)
handleDataSetting(responseJson, "day")
})
.catch((error) => {
console.log("error: ", error)
});
}
useEffect(() => {
getStats()
}, [])
const paperStyle = {
textAlign: "center",
padding: 40,
margin: 5,
backgroundColor: theme.palette.surfaceColor,
maxWidth: 300,
}
const data = (
<div className="content" style={{width: "100%", margin: "auto", }}>
<Typography variant="body1" style={{margin: "auto", marginLeft: 10, marginBottom: 20, }}>
All Stat widgets are monthly and gathered from <a
href={`${globalUrl}/api/v1/orgs/${selectedOrganization.id}/stats`}
target="_blank"
style={{ textDecoration: "none", color: "#f85a3e",}}
>Your Organization Statistics. </a>
This is a feature to help give you more insight into Shuffle, and will be populating over time.
</Typography>
{statistics !== undefined ?
<div style={{display: "flex", textAlign: "center",}}>
<Paper style={paperStyle}>
<Typography variant="h4">
{statistics.monthly_workflow_executions}
</Typography>
<Typography variant="h6">
Workflow Runs
</Typography>
</Paper>
<Paper style={paperStyle}>
<Typography variant="h4">
{statistics.monthly_app_executions}
</Typography>
<Typography variant="h6">
App Runs
</Typography>
</Paper>
</div>
: null}
{appRuns === undefined ?
null
:
<LineChartWrapper keys={appRuns} height={300} width={"100%"} inputname={"Daily App Runs"}/>
}
{workflowRuns === undefined ?
null
:
<LineChartWrapper keys={workflowRuns} height={300} width={"100%"} inputname={"Daily Workflow Runs (including subflows)"}/>
}
{subflowRuns === undefined ?
null
:
<LineChartWrapper keys={subflowRuns} height={300} width={"100%"} inputname={"Subflow Runs"}/>
}
</div>
)
const dataWrapper = (
<div style={{ maxWidth: 1366, margin: "auto" }}>{data}</div>
);
return dataWrapper;
}
export default AppStats;
File diff suppressed because it is too large Load Diff
+38 -7
View File
@@ -5,6 +5,7 @@ import { MuiChipsInput } from "mui-chips-input";
import UsecaseSearch from "../components/UsecaseSearch.jsx"
import WorkflowGrid from "../components/WorkflowGrid.jsx"
import dayjs from 'dayjs';
import WorkflowTemplatePopup from "./WorkflowTemplatePopup.jsx";
import {
Badge,
@@ -57,7 +58,7 @@ import {
} from "@mui/icons-material";
const EditWorkflow = (props) => {
const { globalUrl, workflow, setWorkflow, modalOpen, setModalOpen, showUpload, usecases, setNewWorkflow, appFramework, isEditing, userdata, } = props
const { globalUrl, workflow, setWorkflow, modalOpen, setModalOpen, showUpload, usecases, setNewWorkflow, appFramework, isEditing, userdata, apps, } = props
const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove
@@ -143,7 +144,9 @@ const EditWorkflow = (props) => {
return null
}
const newWorkflow = isEditing === true ? false : true
const newWorkflow = isEditing === true ? false : true
const priority = userdata === undefined || userdata === null ? null : userdata.priorities.find(prio => prio.type === "usecase" && prio.active === true)
console.log("PRIO: ", priority)
var upload = "";
var total_count = 0
@@ -161,6 +164,7 @@ const EditWorkflow = (props) => {
maxWidth: isMobile ? "90%" : 650,
minHeight: 400,
paddingTop: 25,
paddingLeft: 50,
//minWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550,
//maxWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550,
},
@@ -170,7 +174,7 @@ const EditWorkflow = (props) => {
<div style={{display: "flex"}}>
<div style={{flex: 1, color: "rgba(255,255,255,0.9)" }}>
<div style={{display: "flex"}}>
<Typography variant="h6" style={{flex: 9, }}>
<Typography variant="h4" style={{flex: 9, }}>
{newWorkflow ? "New" : "Editing"} workflow
</Typography>
{newWorkflow === true ? null :
@@ -193,7 +197,7 @@ const EditWorkflow = (props) => {
</div>
}
</div>
<Typography variant="body2" color="textSecondary" style={{maxWidth: 440,}}>
<Typography variant="body2" color="textSecondary" style={{marginTop: 20, maxWidth: 440,}}>
Workflows can be built from scratch, or from templates. <a href="/usecases" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>Usecases</a> can help you discover next steps, and you can <a href="/search?tab=workflows" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>search</a> for them directly. <a href="/docs/workflows" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>Learn more</a>
</Typography>
{showUpload === true ?
@@ -480,7 +484,7 @@ const EditWorkflow = (props) => {
</DialogContent>
<DialogActions style={{paddingRight: 20, }}>
<DialogActions style={{paddingRight: 100, }}>
<Button
style={{}}
onClick={() => {
@@ -542,11 +546,38 @@ const EditWorkflow = (props) => {
}}
color="primary"
>
{submitLoading ? <CircularProgress color="secondary" /> : "Submit"}
{submitLoading ? <CircularProgress color="secondary" /> : "Done"}
</Button>
</DialogActions>
{newWorkflow === true ?
<span style={{marginTop: 30, }}>
<Typography variant="h6" style={{marginLeft: 30, paddingBottom: 0, }}>
Relevant Workflows
</Typography>
{newWorkflow === true && name.length > 5 ?
{priority === null || priority === undefined ? null :
<div style={{marginLeft: 30, }}>
<WorkflowTemplatePopup
userdata={userdata}
globalUrl={globalUrl}
srcapp={priority.description.split("&").length > 2 ? priority.description.split("&")[0] : ""}
img1={priority.description.split("&").length > 2 ? priority.description.split("&")[1] : ""}
dstapp={priority.description.split("&").length > 3 ? priority.description.split("&")[2] : ""}
img2={priority.description.split("&").length > 3 ? priority.description.split("&")[3] : ""}
title={priority.name}
description={priority.description.split("&").length > 4 ? priority.description.split("&")[4] : ""}
apps={apps}
/>
</div>
}
</span>
: null}
{newWorkflow === true && name.length > 2 ?
<div style={{marginLeft: 30, }}>
<WorkflowGrid
maxRows={1}
+324
View File
@@ -0,0 +1,324 @@
import React, { useState, useEffect } from "react";
import ReactGA from 'react-ga4';
import 'react-alice-carousel/lib/alice-carousel.css';
import TrendingFlatIcon from '@mui/icons-material/TrendingFlat';
import theme from '../theme.jsx';
import CheckBoxSharpIcon from '@mui/icons-material/CheckBoxSharp';
import {
Checkbox,
Button,
Collapse,
IconButton,
FormGroup,
FormControl,
InputLabel,
FormLabel,
FormControlLabel,
Select,
MenuItem,
Grid,
Paper,
Typography,
TextField,
Zoom,
List,
ListItem,
ListItemText,
Divider,
Tooltip,
Chip,
ButtonGroup,
Dialog,
DialogTitle,
DialogActions,
DialogContent,
} from "@mui/material";
import { useNavigate, Link } from "react-router-dom";
import WorkflowTemplatePopup from "../components/WorkflowTemplatePopup.jsx";
const ExploreWorkflow = (props) => {
const { userdata, globalUrl } = props
const [activeUsecases, setActiveUsecases] = useState(0);
const [modalOpen, setModalOpen] = React.useState(false);
const [suggestedUsecases, setSuggestedUsecases] = useState([])
const [usecasesSet, setUsecasesSet] = useState(false)
const [apps, setApps] = useState([])
const sizing = 475
let navigate = useNavigate();
const imagestyle = {
height: 40,
borderRadius: 40,
//border: "2px solid rgba(255,255,255,0.3)",
}
const loadApps = () => {
fetch(`${globalUrl}/api/v1/apps`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
return response.json();
})
.then((responseJson) => {
if (responseJson === null) {
console.log("null-response from server")
const pretend_apps = [{
"name": "TBD",
"app_name": "TBD",
"app_version": "TBD",
"description": "TBD",
"version": "TBD",
"large_image": "",
}]
setApps(pretend_apps)
return
}
if (responseJson.success === false) {
console.log("error loading apps: ", responseJson)
return
}
setApps(responseJson);
})
.catch((error) => {
console.log("App loading error: " + error.toString());
})
}
// Find priorities in userdata.priorities and check if the item.type === "usecase"
// If so, set the item.isActive to true
if (usecasesSet === false && userdata.priorities !== undefined && userdata.priorities !== null && userdata.priorities.length > 0 && suggestedUsecases.length === 0) {
var tmpUsecases = []
for (let i = 0; i < userdata.priorities.length; i++) {
if (userdata.priorities[i].type !== "usecase" || userdata.priorities[i].active === false) {
continue
}
tmpUsecases.push(userdata.priorities[i])
}
setSuggestedUsecases(tmpUsecases)
setUsecasesSet(true)
loadApps()
}
const modalView = (
// console.log("key:", dataValue.key),
//console.log("value:",dataValue.value),
<Dialog
open={modalOpen}
onClose={() => {
setModalOpen(false);
}}
PaperProps={{
style: {
backgroundColor: theme.palette.surfaceColor,
color: "white",
minWidth: "800px",
minHeight: "320px",
},
}}
>
<DialogTitle style={{}}>
<div style={{ color: "white", display: "flex", alignItems: "center", justifyContent: "center" }}>
<CheckBoxSharpIcon sx={{ borderRadius: 4, color: "rgba(255, 132, 68, 1)" }} style={{ width: 24 }} />
<span style={{ marginLeft: 8, color: "rgba(255, 132, 68, 1)", fontSize: 16, width: 60 }}>Sign Up</span>
<div style={{ borderTop: "1px solid rgba(255, 132, 68, 1)", width: 85, marginLeft: 8, marginRight: 8 }} />
<CheckBoxSharpIcon sx={{ borderRadius: 4, color: "rgba(255, 132, 68, 1)" }} style={{ width: 24 }} />
<span style={{ marginLeft: 8, color: "rgba(255, 132, 68, 1)", fontSize: 16, width: 60 }}>Setup</span>
<div style={{ borderTop: "1px solid rgba(255, 132, 68, 1)", width: 85, marginRight: 8 }} />
<CheckBoxSharpIcon sx={{ borderRadius: 4, color: "rgba(255, 132, 68, 1)" }} style={{ width: 24 }} />
<span style={{ marginLeft: 8, color: "rgba(255, 132, 68, 1)", fontSize: 16, width: 60 }}>Explore</span>
</div>
</DialogTitle>
<Typography style={{ fontSize: 16, width: 252, marginLeft: 167 }}>
Heres a recommended workflow:
</Typography>
{/* <div style={{ marginTop: 0, maxWidth: 700, minWidth: 700, margin: "auto", minHeight: sizing, maxHeight: sizing, }}>
<div style={{ marginTop: 0, }}>
<div className="thumbs" style={{ display: "flex" }}>
<Tooltip title={"Previous usecase"}>
<IconButton
style={{
// backgroundColor: thumbIndex === 0 ? "inherit" : "white",
zIndex: 5000,
minHeight: 50,
maxHeight: 50,
color: "grey",
marginTop: 150,
borderRadius: 50,
border: "1px solid rgba(255,255,255,0.3)",
}}
onClick={() => {
slidePrev()
}}
>
<ArrowBackIosNewIcon />
</IconButton>
</Tooltip>
<div style={{ minWidth: 554, maxWidth: 554, borderRadius: theme.palette.borderRadius, }}>
<AliceCarousel
style={{ backgroundColor: theme.palette.surfaceColor, minHeight: 750, maxHeight: 750, }}
items={formattedCarousel}
activeIndex={thumbIndex}
infiniteLoop
mouseTracking={false}
responsive={responsive}
// activeIndex={activeIndex}
controlsStrategy="responsive"
autoPlay={false}
infinite={true}
animationType="fadeout"
animationDuration={800}
disableButtonsControls
/>
</div>
<Tooltip title={"Next usecase"}>
<IconButton
style={{
backgroundColor: thumbIndex === usecaseButtons.length - 1 ? "inherit" : "white",
zIndex: 5000,
minHeight: 50,
maxHeight: 50,
color: "grey",
marginTop: 150,
borderRadius: 50,
border: "1px solid rgba(255,255,255,0.3)",
}}
onClick={() => {
slideNext()
}}
>
<ArrowForwardIosIcon />
</IconButton>
</Tooltip>
</div>
</div>
</div> */}
<DialogActions style={{ paddingLeft: "30px", paddingRight: '30px' }}>
<Button
style={{ borderRadius: "0px" }}
onClick={() => setModalOpen(false)}
color="primary"
>
Cancel
</Button>
<Button
variant="contained"
style={{ borderRadius: "0px" }}
onClick={() => {
console.log("hello")
}}
color="primary"
>
Submit
</Button>
</DialogActions>
</Dialog>
);
return (
<div style={{ marginTop: 0, margin: "auto", minHeight: sizing, maxHeight: sizing, }}>
{modalView}
<Typography variant="h4" style={{ marginLeft: 8, marginTop: 40, marginRight: 30, marginBottom: 0, }} color="rgba(241, 241, 241, 1)">
Start using workflows
</Typography>
<Typography variant="body2" style={{ marginLeft: 8, marginTop: 10, marginRight: 30, marginBottom: 40, }} color="rgba(158, 158, 158, 1)">
Based on what you selected heres our recommendations!
</Typography>
<div style={{ marginTop: 0, }}>
<div className="thumbs" style={{ display: "flex" }}>
<div style={{ minWidth: 554, maxWidth: 554, borderRadius: theme.palette.borderRadius, }}>
<Grid item xs={11} style={{}}>
{suggestedUsecases.length === 0 && usecasesSet ?
<Typography variant="h6" style={{ marginTop: 30, marginBottom: 50, }} color="rgba(158, 158, 158, 1)">
All Workflows are already added for your current apps!
</Typography>
:
suggestedUsecases.map((priority, index) => {
const srcapp = priority.description.split("&")[0]
var image1 = priority.description.split("&")[1]
var image2 = ""
var dstapp = ""
if (priority.description.split("&").length > 3) {
dstapp = priority.description.split("&")[2]
image2 = priority.description.split("&")[3]
}
const name = priority.name.replace("Suggested Usecase: ", "")
var description = ""
if (priority.description.split("&").length > 4) {
description = priority.description[4]
}
// FIXME: Should have a proper description
description = ""
return (
<WorkflowTemplatePopup
userdata={userdata}
globalUrl={globalUrl}
img1={image1}
srcapp={srcapp}
img2={image2}
dstapp={dstapp}
title={name}
description={description}
apps={apps}
/>
)
})}
</Grid>
<div>
<div style={{ marginTop: 32 }}>
<Typography variant="body2" style={{ fontSize: 16, marginTop: 24 }} color="rgba(158, 158, 158, 1)">
<Button variant="contained" type="submit"
fullWidth style={{
borderRadius: 200,
height: 51,
width: 464,
fontSize: 16,
padding: "16px 24px",
margin: "auto",
itemAlign: "center",
background: activeUsecases === 0 ? "rgba(47, 47, 47, 1)" : "linear-gradient(90deg, #F86744 0%, #F34475 100%)",
color: activeUsecases === 0? "rgba(158, 158, 158, 1)" : "rgba(241, 241, 241, 1)",
border: activeUsecases === 0 ? "1px solid rgba(158, 158, 158, 1)" : "none",
}}
onClick={() => {
navigate("/workflows?message="+activeUsecases+" workflows added")
}}>
Continue to workflows
</Button>
</Typography>
</div>
<Typography variant="body2" style={{ fontSize: 16, marginTop: 24 }} color="rgba(158, 158, 158, 1)">
<Link style={{ color: "#f86a3e", marginLeft: 145 }} to="/usecases" className="btn btn-primary">
Explore usecases
</Link>
</Typography>
</div>
</div>
</div>
</div>
</div>
)
}
export default ExploreWorkflow
+4 -3
View File
@@ -212,7 +212,8 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
const notificationWidth = 300
const imagesize = 22;
const boxColor = "#86c142";
const boxColor = "#86c142";
const NotificationItem = (props) => {
const {data} = props
@@ -263,7 +264,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
{data.reference_url !== undefined && data.reference_url !== null && data.reference_url.length > 0 ?
<Link to={data.reference_url} style={{color: "#f86a3e", textDecoration: "none",}}>
<Typography variant="body1">
{data.title}
{data.title} ({data.amount})
</Typography >
</Link>
:
@@ -277,7 +278,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
:
null
}
<Typography variant="body2">
<Typography variant="body2" style={{maxHeight: 200, overflowX: "hidden", overflowY: "auto", }}>
{data.description}
</Typography >
{/*data.tags !== undefined && data.tags !== null && data.tags.length > 0 ?
+24 -24
View File
@@ -2278,30 +2278,30 @@ const ParsedAction = (props) => {
handleItemClick([innerdata]);
}}
>
<Paper style={{minHeight: 500, maxHeight: 500, minWidth: 275, maxWidth: 275, position: "fixed", top: menuPosition1.top-200, left: menuPosition1.left-455, padding: "10px 0px 10px 10px", backgroundColor: theme.palette.inputColor, overflow: "hidden", overflowY: "auto", border: "1px solid rgba(255,255,255,0.3)",}}>
<MenuItem
key={innerdata.name}
style={{
backgroundColor: theme.palette.inputColor,
marginLeft: 15,
color: "white",
minWidth: 250,
maxWidth: 250,
padding: 0,
position: "relative",
}}
value={innerdata}
onMouseOver={() => {
//console.log("HOVER: ", pathdata);
}}
onClick={() => {
handleItemClick([innerdata]);
}}
>
<Typography variant="h6" style={{paddingBottom: 5}}>
{innerdata.name}
</Typography>
</MenuItem>
<Paper style={{minHeight: 500, maxHeight: 500, minWidth: 275, maxWidth: 275, position: "fixed", top: menuPosition1.top-200, left: menuPosition1.left-455, padding: "10px 0px 10px 10px", overflow: "hidden", overflowY: "auto", border: "1px solid rgba(255,255,255,0.3)",}}>
<MenuItem
key={innerdata.name}
style={{
backgroundColor: theme.palette.inputColor,
marginLeft: 15,
color: "white",
minWidth: 250,
maxWidth: 250,
padding: 0,
position: "relative",
}}
value={innerdata}
onMouseOver={() => {
//console.log("HOVER: ", pathdata);
}}
onClick={() => {
handleItemClick([innerdata]);
}}
>
<Typography variant="h6" style={{paddingBottom: 5}}>
{innerdata.name}
</Typography>
</MenuItem>
{parsedPaths.map((pathdata, index) => {
// FIXME: Should be recursive in here
//<VpnKeyIcon style={iconStyle} />
+1 -1
View File
@@ -69,7 +69,7 @@ const Priority = (props) => {
return (
<div style={{border: priority.active === false ? "1px solid #000000" : priority.severity === 1 ? "1px solid #f85a3e" : "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, marginTop: 10, marginBottom: 10, padding: 15, textAlign: "center", minHeight: 100, maxHeight: 100, textAlign: "left", backgroundColor: theme.palette.surfaceColor, display: "flex", }}>
<div style={{border: priority.active === false ? "1px solid #000000" : priority.severity === 1 ? "1px solid #f85a3e" : "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, marginTop: 10, marginBottom: 10, padding: 15, textAlign: "center", minHeight: isCloud ? 70 : 100, maxHeight: isCloud ? 70 : 100, textAlign: "left", backgroundColor: theme.palette.surfaceColor, display: "flex", }}>
<div style={{flex: 2, overflow: "hidden",}}>
<span style={{display: "flex", }}>
{priority.type === "usecase" || priority.type == "apps" ? <AutoFixHighIcon style={{height: 19, width: 19, marginLeft: 3, marginRight: 10, }}/> : null}
+27 -15
View File
@@ -26,22 +26,21 @@ import { SetJsonDotnotation } from "../views/AngularWorkflow.jsx";
import {
FullscreenExit as FullscreenExitIcon,
Extension as ExtensionIcon,
Apps as AppsIcon,
FavoriteBorder as FavoriteBorderIcon,
Schedule as ScheduleIcon,
FormatListNumbered as FormatListNumberedIcon,
Apps as AppsIcon,
FavoriteBorder as FavoriteBorderIcon,
Schedule as ScheduleIcon,
FormatListNumbered as FormatListNumberedIcon,
SquareFoot as SquareFootIcon,
Circle as CircleIcon,
Add as AddIcon,
Circle as CircleIcon,
Add as AddIcon,
PlayArrow as PlayArrowIcon,
} from '@mui/icons-material';
import {
AutoFixHigh as AutoFixHighIcon,
Close as CloseIcon,
CompressOutlined,
QrCodeScannerOutlined,
} from '@mui/icons-material';
import { validateJson } from "../views/Workflows.jsx";
import ReactJson from "react-json-view";
import PaperComponent from "../components/PaperComponent.jsx";
@@ -823,10 +822,6 @@ const CodeEditor = (props) => {
}
const executeSingleAction = (inputdata) => {
//if (serverside === true) {
// return
//}
if (validation === true) {
inputdata = JSON.stringify(inputdata)
}
@@ -834,7 +829,10 @@ const CodeEditor = (props) => {
// Shuffle Tools 1.2.0 (in most cases?)
const appid = toolsAppId !== undefined && toolsAppId !== null && toolsAppId.length > 0 ? toolsAppId : "3e2bdf9d5069fe3f4746c29d68785a6a"
const actiondata = {"description":"Repeats the call parameter","id":"","name":"repeat_back_to_me","label":"","node_type":"","environment":"","sharing":false,"private_id":"","public_id":"","app_id": appid,"tags":null,"authentication":[],"tested":false,"parameters":[{"description":"The message to repeat","id":"","name":"call","example":"REPEATING: Hello world","value":inputdata,"multiline":true,"options":null,"action_field":"","variant":"STATIC_VALUE","required":true,"configuration":false,"tags":null,"schema":{"type":"string"},"skip_multicheck":false,"value_replace":null,"unique_toggled":false,"autocompleted":false}],"execution_variable":{"description":"","id":"","name":"","value":""},"returns":{"description":"","example":"","id":"","schema":{"type":"string"}},"authentication_id":"","example":"","auth_not_required":false,"source_workflow":"","run_magic_output":false,"run_magic_input":false,"execution_delay":0,"app_name":"Shuffle Tools","app_version":"1.2.0","selectedAuthentication":{}}
const actionname = selectedAction.name === "execute_python" && !inputdata.replaceAll(" ", "").includes("{%python%}") ? "execute_python" : "repeat_back_to_me"
const params = actionname === "execute_python" ? [{"name": "code", "value":inputdata}] : [{"name":"call", "value": inputdata}]
const actiondata = {"description":"Repeats the call parameter","id":"","name":actionname,"label":"","node_type":"","environment":"","sharing":false,"private_id":"","public_id":"","app_id": appid,"tags":null,"authentication":[],"tested":false,"parameters": params, "execution_variable":{"description":"","id":"","name":"","value":""},"returns":{"description":"","example":"","id":"","schema":{"type":"string"}},"authentication_id":"","example":"","auth_not_required":false,"source_workflow":"","run_magic_output":false,"run_magic_input":false,"execution_delay":0,"app_name":"Shuffle Tools","app_version":"1.2.0","selectedAuthentication":{}}
setExecutionResult({
"valid": false,
@@ -924,6 +922,20 @@ const CodeEditor = (props) => {
},
}}
>
<IconButton
style={{
zIndex: 5000,
position: "absolute",
top: 14,
right: 18,
color: "grey",
}}
onClick={() => {
setExpansionModalOpen(false)
}}
>
<CloseIcon />
</IconButton>
<div style={{display: "flex"}}>
<div style={{flex: 1, }}>
{ isFileEditor ?
@@ -1555,7 +1567,7 @@ const CodeEditor = (props) => {
<IconButton disabled={executing} color="primary" style={{border: `1px solid ${theme.palette.primary.main}`, marginLeft: 100, padding: 8}} variant="contained" onClick={() => {
executeSingleAction(expOutput)
}}>
<Tooltip title="Try it! This runs the Shuffle Tools 'repeat back to me' action with what you see in the expected output window. Commonly used to test your Python scripts or Liquid filters, not requiring the full workflow to run again." placement="top">
<Tooltip title="Try it! This runs the Shuffle Tools 'repeat back to me' or 'execute python' action with what you see in the expected output window. Commonly used to test your Python scripts or Liquid filters, not requiring the full workflow to run again." placement="top">
{executing ? <CircularProgress style={{height: 18, width: 18, }} /> : <PlayArrowIcon style={{height: 18, width: 18, }} /> }
</Tooltip>
File diff suppressed because it is too large Load Diff
+2 -5
View File
@@ -154,7 +154,6 @@ const AppGrid = props => {
})
.then((responseJson) => {
if (responseJson.success !== false) {
console.log("Usecases: ", responseJson)
//handleKeysetting(responseJson, workflows)
}
})
@@ -187,10 +186,10 @@ const AppGrid = props => {
}, [])
if (localMessage !== inputsearch && inputsearch !== undefined && inputsearch !== null && inputsearch.length > 0) {
console.log("In refinement: ", inputsearch)
//setLocalMessage(inputsearch)
refine(inputsearch)
defaultSearch = inputsearch
return null
} else if (onlyResults === true) {
// Don't return anything unless refinement works
return null
@@ -244,9 +243,7 @@ const AppGrid = props => {
return (
<div>
{onlyResults === true && hits.length > 0 ?
<Typography variant="h6" style={{paddingBottom: 0, }}>
Relevant Workflows
</Typography>
null
: null}
<Grid container spacing={4} style={paperAppContainer}>
{hits.map((data, index) => {
+1 -1
View File
@@ -277,7 +277,7 @@ const WorkflowPaper = (props) => {
>
{data.tags !== undefined && data.tags !== null
? data.tags.map((tag, index) => {
if (index >= 3) {
if (index >= 2) {
return null;
}
@@ -0,0 +1,382 @@
import React, { useState, useEffect } from "react";
import { toast } from "react-toastify"
import theme from '../theme.jsx';
import {
Button,
Typography,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Drawer,
CircularProgress,
IconButton,
} from "@mui/material";
import {
Check as CheckIcon,
TrendingFlat as TrendingFlatIcon,
Close as CloseIcon,
} from '@mui/icons-material';
import WorkflowTemplatePopup2 from "./WorkflowTemplatePopup.jsx";
import ConfigureWorkflow from "../components/ConfigureWorkflow.jsx";
const WorkflowTemplatePopup = (props) => {
const { userdata, globalUrl, img1, srcapp, img2, dstapp, title, description, visualOnly, apps } = props;
const [isActive, setIsActive] = useState(false);
const [isHovered, setIsHovered] = useState(false);
const [modalOpen, setModalOpen] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
const [workflowLoading, setWorkflowLoading] = useState(false);
const [workflow, setWorkflow] = useState({});
const [appAuthentication, setAppAuthentication] = React.useState(undefined);
const imagestyle = {
height: 40,
width: 40,
borderRadius: 40,
}
if (title === undefined || title === null || title === "") {
console.log("No title for workflow template popup!");
return null
}
const getWorkflow = (workflowId) => {
fetch(`${globalUrl}/api/v1/workflows/${workflowId}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for framework!");
}
return response.json();
})
.then((responseJson) => {
if (responseJson.success === false) {
console.log("Error in workflow loading for ID ", workflowId)
} else {
setWorkflow(responseJson)
}
})
.catch((error) => {
console.log("err in framework: ", error.toString());
setWorkflowLoading(false)
})
}
const loadAppAuth = () => {
fetch(`${globalUrl}/api/v1/apps/authentication`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for setting app auth :O!");
}
return response.json();
})
.then((responseJson) => {
if (!responseJson.success) {
toast("Failed to get app auth: " + responseJson.reason);
return
}
var newauth = [];
for (let authkey in responseJson.data) {
if (responseJson.data[authkey].defined === false) {
continue;
}
newauth.push(responseJson.data[authkey]);
}
setAppAuthentication(newauth);
})
.catch((error) => {
//toast(error.toString());
console.log("New auth error: ", error.toString());
});
}
const getGeneratedWorkflow = () => {
// POST
// https://shuffler.io/api/v1/workflows/merge
// destination: {app_id: "b9c2feaf99b6309dabaeaa8518c61d3d", app_name: "Servicenow_API", app_version: "",…}
// id: ""
// middle:[]
// name: "Email analysis"
// source:{app_id: "accdaaf2eeba6a6ed43b2efc0112032d", app_name
setWorkflowLoading(true)
// FIXME: Remove hardcoding here after testing, and user srcapp/dstapp
const newsrcapp = srcapp
const newdstapp = dstapp
const mergedata = {
name: title,
id: "",
source: {
app_name: newsrcapp,
},
middle: [],
destination: {
app_name: newdstapp,
},
}
fetch(globalUrl + "/api/v1/workflows/merge", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
body: JSON.stringify(mergedata),
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for framework!");
}
setWorkflowLoading(false)
return response.json();
})
.then((responseJson) => {
if (responseJson.success === false) {
console.log("Error in workflow template: ", responseJson.error);
setErrorMessage("Failed to generate workflow for these tools - the Shuffle team has been notified. Click out of this window to continue. Contact support@shuffler.io for further assistance.")
setIsActive(true)
//setTimeout(() => {
// setModalOpen(false)
//}, 5000)
} else {
console.log("Success in workflow template: ", responseJson);
setIsActive(true)
if (responseJson.workflow_id === "") {
console.log("Failed to build workflow for these tools. Closing in 3 seconds.")
return
}
getWorkflow(responseJson.workflow_id)
}
})
.catch((error) => {
console.log("err in framework: ", error.toString());
setWorkflowLoading(false)
})
}
const isFinished = () => {
// Look for configuration fields being done in the current modal
// 1. Start by finding the modal
const template = document.getElementById("workflow-template")
if (template === null || template == undefined) {
return true
}
// Find item in template with id app-config
const appconfig = template.getElementsByClassName("app-config")
if (appconfig === null || appconfig == undefined) {
return true
}
console.log("APPCONFIG: ", appconfig)
return false
}
const ModalView = () => {
return (
<Drawer
anchor={"left"}
open={modalOpen}
onClose={() => {
setModalOpen(false);
}}
PaperProps={{
style: {
backgroundColor: "black",
color: "white",
minWidth: 700,
maxWidth: 700,
paddingTop: 75,
itemAlign: "center",
},
}}
>
<IconButton
style={{
zIndex: 5000,
position: "absolute",
top: 14,
right: 14,
color: "white",
}}
onClick={() => {
setModalOpen(false);
}}
>
<CloseIcon />
</IconButton>
<DialogContent style={{marginTop: 0, marginLeft: 75, maxWidth: 470, }}>
<Typography variant="h4">
<b>Configure Workflow</b>
</Typography>
<Typography variant="body2" color="textSecondary" style={{marginTop: 25, }}>
Selected Workflow:
</Typography>
<div style={{marginBottom: 0, }} id="workflow-template">
<WorkflowTemplatePopup2
globalUrl={globalUrl}
img1={img1}
srcapp={srcapp}
img2={img2}
dstapp={dstapp}
title={title}
description={description}
visualOnly={true}
/>
</div>
{workflowLoading ?
<div style={{marginTop: 75, textAlign: "center", }}>
<Typography variant="h4"> Generating the Workflow...
</Typography>
<CircularProgress style={{marginLeft: 125, marginTop: 10, }}/>
</div>
:
<div>
<Typography variant="h6" style={{marginTop: 75, }}>
{errorMessage !== "" ? errorMessage : ""}
</Typography>
</div>
}
<ConfigureWorkflow
userdata={userdata}
theme={theme}
globalUrl={globalUrl}
workflow={workflow}
appAuthentication={appAuthentication}
setAppAuthentication={setAppAuthentication}
apps={apps}
/>
{errorMessage === "" ?
<Button
style={{marginTop: 50, }}
variant={isFinished() ? "contained" : "outlined"}
>
Done
</Button>
: null}
</DialogContent>
</Drawer>
)
}
var parsedTitle = title
const maxlength = 30
if (title.length > maxlength) {
parsedTitle = title.substring(0, maxlength) + "..."
}
parsedTitle.replaceAll("_", " ")
return (
<div style={{ display: "flex", maxWidth: 470, minWidth: 470, height: 78, borderRadius: 8 }}>
<ModalView />
<div
// variant={isActive === 1 ? "contained" : "outlined"}
color="secondary"
disabled={visualOnly === true}
style={{
margin: 4,
width: "100%",
borderRadius: 8,
textTransform: "none",
backgroundColor: theme.palette.inputColor,
border: isActive ? errorMessage !== "" ? "1px solid red" : `2px solid ${theme.palette.green}` : isHovered ? "1px solid #f85a3e" : "1px solid rgba(33, 33, 33, 1)",
cursor: isActive ? errorMessage !== "" ? "not-allowed" : "pointer" : "pointer",
padding: "10px 20px 10px 20px",
position: "relative",
}}
onMouseEnter={() => {
setIsHovered(true)
}}
onMouseLeave={() => {
setIsHovered(false)
}}
onClick={() => {
if (visualOnly === true) {
console.log("Not showing more than visuals.")
return
}
//setIsActive(!isActive)
if (errorMessage !== "") {
toast("Already failed to generate workflow for these apps. Please try again later or contact support@shuffler.io.")
} else if (isActive) {
toast("Workflow already generated. Please try another template, or continue to the next page.")
// FIXME: Remove these?
loadAppAuth()
setModalOpen(true)
//getGeneratedWorkflow()
} else {
loadAppAuth()
setModalOpen(true)
getGeneratedWorkflow()
}
}}
>
<div style={{ display: "flex", itemAlign: "left", textAlign: "left", }}>
<div style={{display: "flex", flex: 1, marginTop: 3, }}>
<img src={img1} style={imagestyle} />
{img2 !== "" ?
<span style={{display: "flex", }}>
<TrendingFlatIcon style={{ marginTop: 7 }} />
<img src={img2} style={imagestyle} />
</span>
:
<span style={{width: 50, }} />
}
</div>
<div style={{ flex: 3, marginLeft: 20, }}>
<Typography variant="body1" style={{ marginTop: description.length === 0 ? 10 : 0, }} color="rgba(241, 241, 241, 1)">
{parsedTitle}
</Typography>
<Typography variant="body2" color="textSecondary" style={{ marginTop: 0, marginRight: 0, maxHeight: 16, overflow: "hidden",}} color="rgba(158, 158, 158, 1)">
{description}
</Typography>
</div>
</div>
<div>
{isActive === true && errorMessage === "" ?
<CheckIcon color="primary" sx={{ borderRadius: 4 }} style={{ position: "absolute", color: theme.palette.green, top: 10, right: 10, }} />
: ""}
</div>
</div>
</div>
)
}
export default WorkflowTemplatePopup
+3 -6
View File
@@ -18,10 +18,12 @@ const theme = createTheme(adaptV4Theme({
secondary: "rgba(255,255,255,0.7)",
},
type: "dark",
inputColor: "#383B40",
inputColor: "rgba(39,41,45,1)",
//inputColor: "#383B40",
surfaceColor: "#27292d",
platformColor: "#1c1c1d",
backgroundColor: "#1a1a1a",
green: "#5cc879",
borderRadius: 5,
defaultBorder: "1px solid rgba(255,255,255,0.3)",
jsonTheme: "brewer",
@@ -70,11 +72,6 @@ const theme = createTheme(adaptV4Theme({
},
},
overrides: {
MuiPaper: {
root: {
backgroundColor: "#1c1c1d",
},
},
MuiMenu: {
list: {
backgroundColor: "#27292d",
+19 -11
View File
@@ -1692,15 +1692,24 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user
const userId = user.id;
const data = { user_id: userId };
fetch(globalUrl + "/api/v1/generateapikey", {
console.log(user, userdata)
var fetchdata = {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(data),
credentials: "include",
})
}
if (userId === userdata.id) {
fetchdata.method = "GET"
} else {
fetchdata.body = JSON.stringify(data)
}
fetch(globalUrl + "/api/v1/generateapikey", fetchdata)
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for WORKFLOW EXECUTION :O!");
@@ -1889,8 +1898,11 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user
open={selectedUserModalOpen}
onClose={() => {
setSelectedUserModalOpen(false);
setImage2FA("");
setSecret2FA("");
setImage2FA("");
setValue2FA("");
setSecret2FA("");
setShow2faSetup(false);
}}
PaperProps={{
style: {
@@ -2156,7 +2168,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user
<Card
style={{
margin: 4,
backgroundColor: theme.palette.inputColor,
backgroundColor: theme.palette.surfaceColor,
color: "white",
minHeight: expanded ? 250 : "inherit",
maxHeight: expanded ? 250 : "inherit",
@@ -2515,9 +2527,8 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user
</span>
/>
<Tab
disabled={!isCloud}
label=<span>
Billing
Billing (Beta)
</span>
/>
<Tab
@@ -3246,10 +3257,8 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user
selectedOrganization.child_orgs !== null &&
selectedOrganization.child_orgs.length > 0
) {
console.log("In here?");
var active = [];
for (var key in userdata.orgs) {
console.log("ORG: ", userdata.orgs[key]);
const found =
selectedOrganization.child_orgs.find(
(item) => item.id === userdata.orgs[key].id
@@ -3299,7 +3308,6 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user
) : null;
const run2FASetup = (data) => {
console.log("2fa: ", data, show2faSetup);
if (!show2faSetup) {
get2faCode(data.id);
} else {
+45 -13
View File
@@ -580,15 +580,39 @@ const AngularWorkflow = (defaultprops) => {
const cytoscapeWidth = isMobile ? bodyWidth - leftBarSize : bodyWidth - leftBarSize - 25
const [elements, setElements] = useState([]);
const [loopRunning, setLoopRunning] = useState(false)
const stop = () => {
setLoopRunning(false)
}
const start = () => {
setLoopRunning(true)
}
useEffect(() => {
console.log("In useeffect for loopRunning: ", loopRunning)
if (loopRunning) {
const intervalId = setInterval(() => {
if (!loopRunning) {
clearInterval(intervalId);
}
fetchUpdates()
}, 3000)
return () => clearInterval(intervalId);
}
}, [loopRunning])
// No point going as fast, as the nodes aren't realtime anymore, but bulk updated.
// Set it from 2500 to 6000 to reduce overall load
const { start, stop } = useInterval({
duration: 3000,
startImmediate: false,
callback: () => {
fetchUpdates();
},
});
//const { start, stop } = useInterval({
// duration: 3000,
// startImmediate: false,
// callback: () => {
// fetchUpdates();
// },
//});
const getAppDocs = (appname, location, version) => {
fetch(`${globalUrl}/api/v1/docs/${appname}?location=${location}&version=${version}`, {
@@ -924,7 +948,7 @@ const AngularWorkflow = (defaultprops) => {
};
const getWorkflowExecution = (id, execution_id) => {
fetch(`${globalUrl}/api/v1/workflows/${id}/executions`, {
fetch(`${globalUrl}/api/v2/workflows/${id}/executions`, {
method: "GET",
headers: {
"Content-Type": "application/json",
@@ -940,11 +964,10 @@ const AngularWorkflow = (defaultprops) => {
return response.json();
})
.then((responseJson) => {
if (responseJson !== undefined && responseJson !== null && responseJson.length > 0) {
// FIXME: Sort this by time
if (responseJson !== undefined && responseJson !== null && responseJson.executions !== undefined && responseJson.executions !== null && responseJson.executions.length > 0) {
// - means it's opposite
const newkeys = sortByKey(responseJson, "-started_at");
const newkeys = sortByKey(responseJson.executions, "-started_at");
setWorkflowExecutions(newkeys);
const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search;
@@ -1021,6 +1044,7 @@ const AngularWorkflow = (defaultprops) => {
},
body: JSON.stringify(executionRequest),
credentials: "include",
cors: "no-cors",
})
.then((response) => {
if (response.status !== 200) {
@@ -1279,7 +1303,7 @@ const AngularWorkflow = (defaultprops) => {
};
const sendStreamRequest = (body) => {
console.log("Stream not activated yet.")
//console.log("Stream not activated yet.")
return
// Session may be important here huh
@@ -15302,6 +15326,10 @@ const AngularWorkflow = (defaultprops) => {
overflow: "hidden",
}}
onMouseOver={() => {
if (cy == undefined || cy == null) {
return
}
var currentnode = cy.getElementById(data.action.id);
if (currentnode !== undefined && currentnode !== null && currentnode.length !== 0) {
currentnode.addClass("shuffle-hover-highlight");
@@ -15314,6 +15342,10 @@ const AngularWorkflow = (defaultprops) => {
//)
}}
onMouseOut={() => {
if (cy == undefined || cy == null) {
return
}
var currentnode = cy.getElementById(data.action.id);
if (currentnode.length !== 0) {
currentnode.removeClass("shuffle-hover-highlight");
+2 -1
View File
@@ -1354,7 +1354,8 @@ const Apps = (props) => {
border: hover ? "1px solid #f85a3e" : "1px solid rgba(255,255,255,0.3)",
cursor: hover ? "pointer" : "default",
textAlign: "center",
height: 125,
minHeight: 150,
maxHeight: 150,
}}
>
{icon}
+181 -121
View File
@@ -6,6 +6,7 @@ import { makeStyles, } from "@mui/styles";
import classNames from "classnames";
import theme from '../theme.jsx';
import { useNavigate, Link, useParams } from "react-router-dom";
import WorkflowTemplatePopup from "../components/WorkflowTemplatePopup.jsx";
// react plugin used to create charts
//import { Line, Bar } from "react-chartjs-2";
@@ -90,7 +91,7 @@ const useStyles = makeStyles({
},
});
const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLoggedIn, workflows, setWorkflows}) => {
const UsecaseListComponent = ({userdata, keys, isCloud, globalUrl, frameworkData, isLoggedIn, workflows, setWorkflows}) => {
const [expandedIndex, setExpandedIndex] = useState(-1);
const [expandedItem, setExpandedItem] = useState(-1);
const [inputUsecase, setInputUsecase] = useState({});
@@ -103,17 +104,69 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
const [selectedWorkflows, setSelectedWorkflows] = useState([])
const [firstLoad, setFirstLoad] = useState(true)
const [apps, setApps] = useState([])
const classes = useStyles();
const classes = useStyles();
let navigate = useNavigate();
const [mitreTags, setMitreTags] = useState([]);
const loadApps = () => {
fetch(`${globalUrl}/api/v1/apps`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
return response.json();
})
.then((responseJson) => {
if (responseJson === null) {
console.log("null-response from server")
const pretend_apps = [{
"name": "TBD",
"app_name": "TBD",
"app_version": "TBD",
"description": "TBD",
"version": "TBD",
"large_image": "",
}]
setApps(pretend_apps)
return
}
if (responseJson.success === false) {
console.log("error loading apps: ", responseJson)
return
}
setApps(responseJson);
})
.catch((error) => {
console.log("App loading error: " + error.toString());
})
}
useEffect(() => {
loadApps()
}, [])
if (keys === undefined || keys === null || keys.length === 0) {
return null
}
const getUsecase = (name, index, subindex) => {
fetch(`${globalUrl}/api/v1/workflows/usecases/${escape(name.replaceAll(" ", "_"))}`, {
const parseUsecase = (parsedUsecase, subcase) => {
console.log("parseUsecase: ", parsedUsecase, subcase)
}
const getUsecase = (subcase, index, subindex) => {
console.log(subcase)
fetch(`${globalUrl}/api/v1/workflows/usecases/${escape(subcase.name.replaceAll(" ", "_"))}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
@@ -129,14 +182,26 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
return response.json();
})
.then((responseJson) => {
var parsedUsecase = responseJson
if (responseJson.success === false) {
setInputUsecase({
"name": name,
})
parsedUsecase = subcase
//img1={inputUsecase.srcimg}
//srcapp={inputUsecase.srcapp}
//img2={inputUsecase.dstimg}
//dstapp={inputUsecase.dstapp}
//title={inputUsecase.name}
parsedUsecase = parseUsecase(parsedUsecase, subcase)
} else {
setInputUsecase(responseJson)
parsedUsecase = responseJson
parsedUsecase = parseUsecase(parsedUsecase, responseJson)
}
// Look for the type of app and fill in img1, srcapp...
setInputUsecase(parsedUsecase)
setExpandedIndex(index)
setExpandedItem(subindex)
@@ -289,6 +354,7 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
if (selectedItem && subcase.matches.length > 0 && selectedWorkflows.length === 0 && firstLoad === true) {
setFirstLoad(false)
setSelectedWorkflows(subcase.matches)
}
}
@@ -316,10 +382,8 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
}
}
//const backgroundColor = selectedItem ? "inherit" : finished ? "inherit" : usecase.color
const finished = subcase.matches.length > 0
const backgroundColor = theme.palette.surfaceColor
//"inherit"
const itemBorder = `${selectedItem ? "3px" : expandedItem >= 0 ? "0px" : "1px"} solid ${usecase.color}`
const fixedName = subcase.name.toLowerCase().replace("_", " ")
@@ -330,14 +394,14 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
//setSelectedWorkflows([])
if (selectedItem) {
} else {
getUsecase(subcase.name, index, subindex)
getUsecase(subcase, index, subindex)
navigate(`/usecases?selected_object=${fixedName}`)
//const newitem = removeParam("selected_object", cursearch);
//navigate(curpath + newitem)
}
}}>
<Paper style={{padding: 25, minHeight: 75, cursor: !selectedItem ? "pointer" : "default", border: itemBorder, backgroundColor: backgroundColor,}} onClick={() => {
<Paper style={{padding: 25, minHeight: isCloud ? 75 : 122, cursor: !selectedItem ? "pointer" : "default", border: itemBorder, backgroundColor: backgroundColor,}} onClick={() => {
}}>
{!selectedItem ?
<div style={{textAlign: "left", position: "relative",}}>
@@ -649,12 +713,12 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
: null}
{workflows !== undefined && workflows !== null && workflows.length > 0 ?
<Autocomplete
multiple
<Autocomplete
multiple
id="workflow_matching"
options={workflows}
autoHighlight
value={selectedWorkflows}
value={selectedWorkflows}
classes={{ inputRoot: classes.inputRoot }}
ListboxProps={{
style: {
@@ -662,7 +726,7 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
color: "white",
},
}}
getOptionSelected={(option, value) => option.id === value.id}
getOptionSelected={(option, value) => option.id === value.id}
getOptionLabel={(option) => {
if (
@@ -685,120 +749,120 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
borderRadius: theme.palette.borderRadius,
}}
onChange={(event, newValue) => {
console.log("CLICK: ", newValue)
//handleWorkflowSelectionUpdate({ target: { value: newValue} })
//setSelectedWorkflows=
//var newvalue = []
//for (var key in newValue) {
// if (newValue[key].id !== undefined) {
// newvalue.push(newValue[key].id)
// }
//}
console.log("CLICK: ", newValue)
//handleWorkflowSelectionUpdate({ target: { value: newValue} })
//setSelectedWorkflows=
//var newvalue = []
//for (var key in newValue) {
// if (newValue[key].id !== undefined) {
// newvalue.push(newValue[key].id)
// }
//}
// Doing this way as you may want to remove some too
for (var key in workflows) {
if (!newValue.find(data => data.id === workflows[key].id)) {
// Check if it has the one in it
if (workflows[key]["usecase_ids"] !== undefined && workflows[key]["usecase_ids"] !== null && workflows[key]["usecase_ids"].includes(subcase.name)) {
const filtered = workflows[key]["usecase_ids"].filter(data => data !== subcase.name)
if (filtered !== undefined && filtered !== null) {
//console.log("Removing: ", workflows[key].name, workflows[key])
workflows[key]["usecase_ids"] = filtered
setWorkflow(workflows[key])
}
}
// Doing this way as you may want to remove some too
for (var key in workflows) {
if (!newValue.find(data => data.id === workflows[key].id)) {
// Check if it has the one in it
if (workflows[key]["usecase_ids"] !== undefined && workflows[key]["usecase_ids"] !== null && workflows[key]["usecase_ids"].includes(subcase.name)) {
const filtered = workflows[key]["usecase_ids"].filter(data => data !== subcase.name)
if (filtered !== undefined && filtered !== null) {
//console.log("Removing: ", workflows[key].name, workflows[key])
workflows[key]["usecase_ids"] = filtered
setWorkflow(workflows[key])
}
}
continue
}
continue
}
if (workflows[key]["usecase_ids"] === undefined || workflows[key]["usecase_ids"] === null) {
workflows[key]["usecase_ids"] = [subcase.name]
console.log("Setting: ", workflows[key].name)
setWorkflow(workflows[key])
if (workflows[key]["usecase_ids"] === undefined || workflows[key]["usecase_ids"] === null) {
workflows[key]["usecase_ids"] = [subcase.name]
console.log("Setting: ", workflows[key].name)
setWorkflow(workflows[key])
} else if (!workflows[key]["usecase_ids"].includes(subcase.name)) {
workflows[key]["usecase_ids"].push(subcase.name)
console.log("Adding: ", workflows[key].name)
setWorkflow(workflows[key])
} else if (!workflows[key]["usecase_ids"].includes(subcase.name)) {
workflows[key]["usecase_ids"].push(subcase.name)
console.log("Adding: ", workflows[key].name)
setWorkflow(workflows[key])
}
}
}
}
setWorkflows(workflows)
console.log("New: ", newValue)
setSelectedWorkflows(newValue)
setWorkflows(workflows)
console.log("New: ", newValue)
setSelectedWorkflows(newValue)
//setUpdate(Math.random())
}}
renderOption={(props, option) => {
//console.log("In options?: ", props, option)
renderOption={(props, data, state) => {
var newname = data.name
if (newname === undefined || newname === null) {
newname = "placeholder"
}
var newname = props.name
if (newname === undefined || newname === null) {
newname = "placeholder"
}
if (newname.length > 2) {
newname = newname.charAt(0).toUpperCase() + newname.substring(1)
}
return (
<li {...props}>
<Tooltip arrow placement="left" title={
<span style={{}}>
{props.image !== undefined && props.image !== null && props.image.length > 0 ?
<img src={props.image} alt={newname} style={{backgroundColor: theme.palette.surfaceColor, maxHeight: 200, minHeigth: 200, borderRadius: theme.palette.borderRadius, }} />
: null}
<Typography>
Choose {newname}
</Typography>
</span>
} placement="bottom">
<span>
<Checkbox
icon={<CheckBoxOutlineBlankIcon fontSize="small" />}
checkedIcon={<CheckBoxIcon fontSize="small" />}
style={{ marginRight: 8 }}
checked={option.selected}
/>
{newname}
</span>
</Tooltip>
</li>
)
}}
if (newname.length > 2) {
newname = newname.charAt(0).toUpperCase() + newname.substring(1)
}
return (
<li {...props}>
<Tooltip arrow placement="left" title={
<span style={{}}>
{data.image !== undefined && data.image !== null && data.image.length > 0 ?
<img src={data.image} alt={newname} style={{backgroundColor: theme.palette.surfaceColor, maxHeight: 200, minHeigth: 200, borderRadius: theme.palette.borderRadius, }} />
: null}
<Typography>
Choose {newname}
</Typography>
</span>
} placement="bottom">
<span>
<Checkbox
icon={<CheckBoxOutlineBlankIcon fontSize="small" />}
checkedIcon={<CheckBoxIcon fontSize="small" />}
style={{ marginRight: 8 }}
checked={selectedWorkflows.find(wf => wf.id === data.id) !== undefined}
/>
{newname}
</span>
</Tooltip>
</li>
)
}}
renderInput={(params) => {
return (
<TextField
style={{
backgroundColor: theme.palette.inputColor,
borderRadius: theme.palette.borderRadius,
}}
{...params}
label="Find your workflows"
variant="outlined"
<TextField
style={{
backgroundColor: theme.palette.inputColor,
borderRadius: theme.palette.borderRadius,
}}
{...params}
label="Find your workflows"
variant="outlined"
/>
);
}}
/>
: null}
: null}
<span style={{top: 30, position: "relative",}}>
<Typography variant="body1" style={{marginTop: 0,}} onClick={() => {}}>
Try it out
</Typography>
<WorkflowTemplatePopup
userdata={userdata}
globalUrl={globalUrl}
img1={inputUsecase.srcimg}
srcapp={inputUsecase.srcapp}
img2={inputUsecase.dstimg}
dstapp={inputUsecase.dstapp}
title={inputUsecase.name}
description={inputUsecase.description}
apps={apps}
/>
</span>
{/*
{/*subcase.matches.length > 0 ?
<Grid container style={{maxWidth: 325, marginTop: 10, }}>
{subcase.matches.map((workflow, workflowindex) => {
return (
<Grid key={workflowindex} item index={workflowindex} xs={12}>
<WorkflowPaper key={workflowindex} data={workflow} />
</Grid>
)
})}
</Grid>
:
<div>
<Typography variant="body1" color="textSecondary">
No workflow selected yet.
</Typography>
</div>
*/}
{subcase.extra_buttons !== undefined && subcase.extra_buttons !== null && subcase.extra_buttons.length > 0 ?
<div style={{marginTop: 25, }}>
@@ -852,15 +916,9 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
<Typography variant="body1" style={{marginTop: 15, cursor: "pointer",}} onClick={() => {}}>
See other Public Workflows for {} <OpenInNewIcon style={{marginTop: 5, marginLeft: 15, }}/>
</Typography>
{/*
<div>
<Typography variant="body1" color="textSecondary">
No workflows yet.
</Typography>
</div>
*/}
</a>
</div>
*/}
</div>
}
<div style={{
@@ -868,6 +926,8 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
width: 350,
borderRadius: theme.palette.borderRadius,
border: "1px solid rgba(255,255,255,0.3)",
padding: 5,
backgroundColor: theme.palette.backgroundColor,
}}>
<AppFramework
inputUsecase={inputUsecase}
+13 -6
View File
@@ -305,7 +305,6 @@ const LoginDialog = (props) => {
paddingBottom: "30px",
paddingTop: "30px",
position: "relative",
backgroundColor: theme.palette.inputColor,
textAlign: "left",
marginTop: 15,
}}
@@ -331,17 +330,25 @@ const LoginDialog = (props) => {
variant="body2"
style={{ marginBottom: 20, color: "white" }}
>
<b>1.</b> Make sure shuffle-database folder has correct access:{" "}
<b>1.</b> Make sure shuffle-database folder has correct access, and that you have a minimum of <b>2Gb of RAM available</b>:{" "}
<br />
<br />
sudo chown 1000:1000 -R shuffle-database
</Typography>
<Typography
variant="body2"
style={{ marginBottom: 20, color: "white" }}
>
<b>2</b>. Restart docker-compose:
<b>2.</b> Disable memory swap on the host
<br />
<br />
sudo swapoff -a
</Typography>
<Typography
variant="body2"
style={{ marginBottom: 20, color: "white" }}
>
<b>3</b>. Restart docker-compose:
<br />
<br />
sudo docker-compose restart
@@ -513,7 +520,7 @@ const LoginDialog = (props) => {
//id="sso_button"
const ssoBtn = document.getElementById("sso_button");
if (ssoBtn !== undefined && ssoBtn !== null) {
console.log("SSO BTN: ", ssoBtn)
//console.log("SSO BTN: ", ssoBtn)
const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search;
var tmpView = new URLSearchParams(cursearch).get("autologin");
if (tmpView !== undefined && tmpView !== null) {
@@ -527,7 +534,7 @@ const LoginDialog = (props) => {
}, 200);
}, [ssoUrl])
return <div>{loadedCheck}</div>;
return <div style={{paddingBottom: 150, }}>{loadedCheck}</div>;
};
export default LoginDialog;
+17 -17
View File
@@ -26,7 +26,7 @@ const SetAuthentication = (props) => {
//const alert = useAlert();
const parseIncomingOpenapiData = (data) => {
if (data.app === undefined || data.app === null) {
if (data.app === undefined || data.app === null) {
return
}
@@ -43,11 +43,11 @@ const SetAuthentication = (props) => {
parsedapp.name = parsedapp.name.replaceAll("_", " ");
setApp(parsedapp);
document.title = parsedapp.name + " App Auth";
document.title = parsedapp.name + " App Auth";
}
console.log("App: ", app)
console.log("App: ", app)
const getApp = (appid) => {
if (serverside === true) {
@@ -64,21 +64,21 @@ const SetAuthentication = (props) => {
})
.then((response) => {
if (response.status !== 200) {
if (isCloud) {
ReactGA.event({
category: "appauth",
action: `app_not_found`,
label: appid,
});
}
if (isCloud) {
ReactGA.event({
category: "appauth",
action: `app_not_found`,
label: appid,
});
}
} else {
if (isCloud) {
ReactGA.event({
category: "appauth",
action: `app_found`,
label: appid,
});
}
if (isCloud) {
ReactGA.event({
category: "appauth",
action: `app_found`,
label: appid,
});
}
}
return response.json();
+48 -36
View File
@@ -23,6 +23,7 @@ import {
} from '@mui/material';
import theme from '../theme.jsx';
import { useNavigate, Link } from "react-router-dom";
import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
import Drift from "react-driftjs";
const Welcome = (props) => {
@@ -301,44 +302,47 @@ const Welcome = (props) => {
flex: 1,
padding: 0,
textAlign: "center",
maxWidth: 300,
minWidth: 300,
maxWidth: 275,
minWidth: 275,
backgroundColor: theme.palette.surfaceColor,
color: "white",
borderRadius: theme.palette.borderRadius,
}
const actionObject = {
padding: "35px",
maxHeight: 300,
minHeight: 300,
padding: "25px",
maxHeight: 280,
minHeight: 280,
borderRadius: theme.palette.borderRadius,
}
const imageStyle = {
width: 150,
width: 70,
// height: 150,
// margin: "auto",
// marginTop: 10,
borderRadius: 75,
marginBottom: 18,
// borderRadius: 75,
objectFit: "scale-down",
}
const buttonStyle = {
borderRadius: 8,
borderRadius: 200,
height: 51,
width: 464,
fontSize: 16,
background: "linear-gradient(89.83deg, #FF8444 0.13%, #F2643B 99.84%)",
// background: "linear-gradient(89.83deg, #FF8444 0.13%, #F2643B 99.84%)",
background: "linear-gradient(90deg, #F86744 0%, #F34475 100%)",
padding: "16px 24px",
top: 75,
top: 105,
margin: "auto",
itemAlign: "center",
marginLeft: "65px",
}
const defaultImage = "/images/experienced.png"
const experienced_image = userdata !== undefined && userdata !== null && userdata.active_org !== undefined && userdata.active_org.image !== undefined && userdata.active_org.image !== null && userdata.active_org.image !== "" ? userdata.active_org.image : defaultImage
return (
<div style={{width: 1000, margin: "auto", paddingBottom: 150, minHeight: 1500, marginTop: 50, }}>
<div style={{ margin: "auto", paddingBottom: 150, minHeight: 1500, marginTop: 50, }}>
{/*
<div style={{position: "fixed", bottom: 110, right: 110, display: "flex", }}>
<img src="/images/Arrow.png" style={{width: 250, height: "100%",}} />
@@ -346,7 +350,7 @@ const Welcome = (props) => {
*/}
{showWelcome === true ?
<div>
<div style={{minWidth: 500, maxWidth: 500, margin: "auto", marginTop: isCloud ? "auto" : 20, }}>
{/* <div style={{minWidth: 500, maxWidth: 500, margin: "auto", marginTop: isCloud ? "auto" : 20, }}>
<Stepper
activeStep={activeStep}
color="primary"
@@ -379,8 +383,8 @@ const Welcome = (props) => {
)
})}
</Stepper>
</div>
<Grid container spacing={2} style={{ padding: 0, maxWidth: 1000, minWidth: 1000, margin: "auto", }}>
</div> */}
<Grid container spacing={1} style={{ padding: 0, maxWidth: 500, minWidth: 500, marginRight : "auto" , marginLeft: "auto" }}>
<Grid item xs={window.location.href.includes("tab=2") ? 6 : 12}>
<div>
{/*
@@ -414,7 +418,7 @@ const Welcome = (props) => {
/>
</div>
</Grid>
{frameworkData === undefined || window.location.href.includes("tab=1") || window.location.href.includes("tab=3") ? null :
{/* {frameworkData === undefined || window.location.href.includes("tab=1") || window.location.href.includes("tab=3") ? null :
<div style={{marginTop: 25, }}>
<Typography variant="h6" style={{textAlign: "center", marginBottom: 25, }}>
App Framework
@@ -439,32 +443,35 @@ const Welcome = (props) => {
/>
</Fade>
</div>
}
} */}
</Grid>
</div>
:
<Fade in={true}>
<div style={{maxWidth: 700, margin: "auto", marginTop: 50, }}>
{/*
<div style={{display:"flex"}}>
<ArrowBackIosIcon style={{color: "#9E9E9E",}} onClick={() => {
navigate("/login")
<div style={{maxWidth: 590, margin: "auto", marginTop: 50, }}>
{/* <div style={{display:"flex"}}>
<ArrowBackIosIcon style={{color: "#9E9E9E", width: "16px",}} onClick={() => {
navigate("/workflows")
}}/>
<Typography variant="body1" style={{color: "#9E9E9E",textAlign: "center", marginBottom: 50, paddingRight: "366px"}} onClick={() => {
navigate("/login")
<Typography variant="h8" style={{color: "#9E9E9E",textAlign: "center", marginBottom: 20, paddingRight: "366px"}} onClick={() => {
navigate("/workflows")
}}>
Back
</Typography>
</div>
*/}
<Typography variant="h4" style={{color: "#F1F1F1", textAlign: "center", marginTop: 50, }}>
</div> */}
<Typography variant="h4" style={{color: "#F1F1F1", marginTop: 50, fontSize: 32}}>
Help us get to know you
</Typography>
<Typography variant="body1" style={{color: "#9E9E9E", textAlign: "center", marginBottom: 50,}}>
We will use this information to personalize your automation
<Typography variant="body1" style={{color: "#9E9E9E", marginTop: 12 ,marginBottom: 50, fontSize: 16}}>
Let us help you create a smoother journey.
</Typography>
{/* <Typography variant="body1" style={{color: "#9E9E9E", marginTop: 12 ,marginBottom: 50, fontSize: 16}}>
We will use this information to personalize your automation
</Typography> */}
<div style={{display: "flex", marginTop: 70, width: 700, margin: "auto",}}>
<div style={{border: "1px solid #806BFF", borderRadius: theme.palette.borderRadius, }}>
<div style={{border: "2px solid #806BFF", borderRadius: theme.palette.borderRadius, }}>
<Card style={paperObject} onClick={() => {
if (isCloud) {
ReactGA.event({
@@ -506,7 +513,7 @@ const Welcome = (props) => {
navigate("/workflows?message=Skipped intro")
}}>
<CardActionArea style={actionObject}>
<img src={experienced_image} style={{padding: experienced_image === defaultImage ? 38 : 10, objectFit: "scale-down", minHeight: experienced_image === defaultImage ? 40 : 70, maxHeight: experienced_image === defaultImage ? 40 : 70, bordeRadius: theme.palette.borderRadius, }} />
<img src={experienced_image} style={{padding: experienced_image === defaultImage ? 2 : 10, objectFit: "scale-down", minHeight: experienced_image === defaultImage ? 40 : 70, maxHeight: experienced_image === defaultImage ? 40 : 70, bordeRadius: theme.palette.borderRadius, marginBottom: experienced_image === defaultImage ? 24 : 2 }} />
<Typography variant="h4" style={{color: "#F1F1F1"}}>
Experienced
</Typography>
@@ -517,17 +524,22 @@ const Welcome = (props) => {
</Card>
</div>
{/*
<div style={{display: "flex", flexDirection: "row", }}>
<div style={{ flexDirection: "row", }}>
<Button variant="contained" type="submit" fullWidth style={buttonStyle} onClick={() => {
navigate("/workflows?message=Skipped intro continue")
ReactGA.event({
category: "welcome",
action: "click_welcome_continue",
label: "",
})
}}>
Continue
</Button>
</div>
*/}
<div style={{margin: "auto", borderRadius: theme.palette.borderRadius, marginTop: 50, width: 200, overflow: "wrap", padding: 25, cursor: "pointer", }} onClick={() => {
{/* <div style={{margin: "auto", borderRadius: theme.palette.borderRadius, marginTop: 50, width: 200, overflow: "wrap", padding: 25, cursor: "pointer", }} onClick={() => {
if (window.drift !== undefined) {
window.drift.api.startInteraction({ interactionId: 340045 })
} else {
@@ -537,7 +549,7 @@ const Welcome = (props) => {
<Typography variant="body1" style={{margin: "auto", textAlign: "center"}}>
Want a demo instead?
</Typography>
</div>
</div> */}
</div>
</Fade>
}
+127 -137
View File
@@ -11,13 +11,13 @@ import { isMobile } from "react-device-detect"
import {
Badge,
Divider,
Divider,
Avatar,
Drawer,
Grid,
InputLabel,
Select,
ListSubheader,
InputLabel,
Select,
ListSubheader,
Paper,
Tooltip,
Button,
@@ -533,7 +533,6 @@ const Workflows = (props) => {
document.title = "Shuffle - Workflows";
let navigate = useNavigate();
//const alert = useAlert();
const classes = useStyles(theme);
const imgSize = 60;
@@ -590,6 +589,9 @@ const Workflows = (props) => {
const [drawerOpen, setDrawerOpen] = React.useState(false)
const [videoViewOpen, setVideoViewOpen] = React.useState(false)
const [gettingStartedItems, setGettingStartedItems] = React.useState([])
const [selectedWorkflowIndexes, setSelectedWorkflowIndexes] = React.useState([])
const [apps, setApps] = React.useState([]);
const drawerWidth = drawerOpen ? 325 : 0
@@ -725,6 +727,30 @@ const Workflows = (props) => {
}
};
const getApps = () => {
fetch(`${globalUrl}/api/v1/apps`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for apps :O!");
}
return response.json();
})
.then((responseJson) => {
setApps(responseJson);
})
.catch((error) => {
console.log("App loading error: "+error.toString());
});
}
const addFilter = (data) => {
if (data === null || data === undefined) {
console.log("No filter data")
@@ -891,13 +917,15 @@ const Workflows = (props) => {
backgroundColor: theme.palette.surfaceColor,
color: "white",
minWidth: 500,
padding: 50,
},
}}
>
<DialogTitle>
<div style={{ textAlign: "center", color: "rgba(255,255,255,0.9)" }}>
Are you sure you want to delete this workflow? <div />
Other workflows relying on this one may stop working
Are you sure you want to delete {selectedWorkflowId.length > 0 ? filteredWorkflows.find((w) => w.id === selectedWorkflowId).name : `${selectedWorkflowIndexes.length} workflow${selectedWorkflowIndexes.length === 1 ? '' : 's'}`}? <div />
Other workflows relying on {selectedWorkflowIndexes.length > 0 ? "them" : "it"} one will stop working
</div>
</DialogTitle>
<DialogContent
@@ -912,7 +940,21 @@ const Workflows = (props) => {
setTimeout(() => {
getAvailableWorkflows();
}, 1000);
}
} else if (selectedWorkflowIndexes.length > 0) {
// Do backwards so it doesn't change
for (var i = selectedWorkflowIndexes.length - 1; i >= 0; i--) {
const workflow = filteredWorkflows[selectedWorkflowIndexes[i]-1]
if (workflow !== undefined && workflow !== null && workflow.id !== undefined && workflow.id !== null) {
deleteWorkflow(workflow.id);
}
}
setTimeout(() => {
getAvailableWorkflows();
}, 1000);
setSelectedWorkflowIndexes([]);
}
setDeleteModalOpen(false);
}}
color="primary"
@@ -1115,9 +1157,9 @@ const Workflows = (props) => {
// Ensures the zooming happens only once per load
setTimeout(() => {
fetchUsecases(newarray)
setFirstLoad(false)
}, 100)
fetchUsecases(newarray)
setFirstLoad(false)
}, 100)
} else {
if (isLoggedIn) {
@@ -1223,8 +1265,9 @@ const Workflows = (props) => {
setView(tmpView);
}
getApps()
getAvailableWorkflows();
getFramework()
getFramework()
}
}, [])
@@ -1280,6 +1323,7 @@ const Workflows = (props) => {
color: "white",
margin: "10px",
backgroundColor: theme.palette.surfaceColor,
position: "relative",
};
const workflowActionStyle = {
@@ -1708,21 +1752,22 @@ const Workflows = (props) => {
<MenuItem
style={{ backgroundColor: theme.palette.inputColor, color: "white" }}
onClick={(event) => {
event.stopPropagation()
ReactDOM.unstable_batchedUpdates(() => {
setModalOpen(true);
setEditingWorkflow(JSON.parse(JSON.stringify(data)));
setNewWorkflowName(data.name);
setNewWorkflowDescription(data.description);
setDefaultReturnValue(data.default_return_value);
if (data.tags !== undefined && data.tags !== null) {
setNewWorkflowTags(JSON.parse(JSON.stringify(data.tags)));
}
event.stopPropagation()
ReactDOM.unstable_batchedUpdates(() => {
setIsEditing(true)
setModalOpen(true);
setEditingWorkflow(JSON.parse(JSON.stringify(data)));
setNewWorkflowName(data.name);
setNewWorkflowDescription(data.description);
setDefaultReturnValue(data.default_return_value);
if (data.tags !== undefined && data.tags !== null) {
setNewWorkflowTags(JSON.parse(JSON.stringify(data.tags)));
}
if (data.usecase_ids !== undefined && data.usecase_ids !== null && data.usecase_ids.length > 0) {
setSelectedUsecases(data.usecase_ids)
}
})
if (data.usecase_ids !== undefined && data.usecase_ids !== null && data.usecase_ids.length > 0) {
setSelectedUsecases(data.usecase_ids)
}
})
}}
key={"change"}
>
@@ -2654,6 +2699,7 @@ const Workflows = (props) => {
renderCell: (params) => {},
},
];
let rows = [];
rows = filteredWorkflows.map((data, index) => {
let obj = {
@@ -2671,17 +2717,55 @@ const Workflows = (props) => {
className={classes.datagrid}
rows={rows}
columns={columns}
pageSize={20}
pageSize={25}
checkboxSelection
autoHeight
density="standard"
onSelectionModelChange={(newSelection) => {
//setSelectedWorkflows(newSelection.selectionModel);
console.log(newSelection)
setSelectedWorkflowIndexes(newSelection)
}}
selectionModel={selectedWorkflowIndexes}
components={{
Toolbar: GridToolbar,
}}
/>
);
}
return <div style={gridContainer}>{workflowData}</div>;
return (
<div style={gridContainer}>
<Tooltip title={`New Workflow`} placement="bottom">
<IconButton
style={{position: "absolute", top: 10, right: 50, zIndex: 1000}}
onClick={() => {
setModalOpen(true)
setIsEditing(false)
}}
>
<AddCircleIcon style={{}} />
</IconButton>
</Tooltip>
{filteredWorkflows.length === 0 ? null :
<IconButton
style={{position: "absolute", top: 10, right: 10, zIndex: 1000}}
disabled={selectedWorkflowIndexes.length === 0}
onClick={() => {
setDeleteModalOpen(true)
}}
>
<Tooltip
title={`Delete ${selectedWorkflowIndexes.length} workflows`}
placement="top"
>
<DeleteIcon />
</Tooltip>
</IconButton>
}
{workflowData}
</div>
)
};
var total_count = 0
@@ -3152,70 +3236,20 @@ const Workflows = (props) => {
const WorkflowView = () => {
if (workflows.length === 0) {
// Not going there yet
//if ((userdata.tutorials !== undefined && userdata.tutorials !== null && !userdata.tutorials.includes("getting-started")) || userdata.tutorials === null) {
// return <Navigate to="/getting-started" replace />;
//}
//return (
// <div style={emptyWorkflowStyle}>
// <Paper style={boxStyle}>
// <div>
// <h2>Welcome to Shuffle</h2>
// </div>
// <div>
// <p>
// <b>Shuffle</b> is a flexible, easy to use, automation platform
// allowing users to integrate their services and devices freely.
// It's made to significantly reduce the amount of manual labor,
// and is focused on security applications.{" "}
// <a
// href="/docs/about"
// style={{ textDecoration: "none", color: "#f85a3e" }}
// >
// Click here to learn more.
// </a>
// </p>
// </div>
// <div>
// If you want to jump straight into it, click here to create your
// first workflow:
// </div>
// <div style={{ display: "flex" }}>
// <Button
// id="second-step"
// color="primary"
// style={{ marginTop: "20px" }}
// variant="outlined"
// onClick={() => setModalOpen(true)}
// >
// New workflow
// </Button>
// <span style={{ paddingTop: 20, display: "flex" }}>
// <Typography
// style={{ marginTop: 5, marginLeft: 30, marginRight: 15 }}
// >
// ..OR
// </Typography>
// {workflowButtons}
// </span>
// </div>
// </Paper>
// </div>
//)
}
var workflowDelay = -150
var appDelay = -75
var workflowDelay = -150
var appDelay = -75
const foundPriority = userdata === undefined || userdata === null ? null : userdata.priorities.find(prio => prio.type === "usecase" && prio.active === true)
const foundPriority = userdata === undefined || userdata === null ? null : userdata.priorities.find(prio => prio.type === "usecase" && prio.active === true)
return (
<div style={viewStyle}>
<div style={workflowViewStyle}>
<div style={{ display: "flex", marginTop: 25, }}>
<div style={{ flex: 1 }}>
<Typography variant="h1" style={{fontSize: 30}}>
Workflows
</Typography>
<Typography variant="h1" style={{fontSize: 30}}>
Workflows
</Typography>
</div>
{/*
<div style={{ flex: 1 }}>
@@ -3243,6 +3277,7 @@ const Workflows = (props) => {
minWidth: 275,
},
}}
rows={1}
placeholder="Filter Workflows"
color="primary"
fullWidth
@@ -3268,53 +3303,6 @@ const Workflows = (props) => {
{workflowButtons}
</div>
</div>
{/*
<div style={flexContainerStyle}>
<div style={{...flexBoxStyle, ...activeWorkflowStyle}}>
<div style={flexContentStyle}>
<div><img src={mobileImage} style={iconStyle} /></div>
<div style={ blockRightStyle }>
<div style={counterStyle}>{workflows.length}</div>
<div style={fontSize_16}>ACTIVE WORKFLOWS</div>
</div>
</div>
</div>
<div style={{...flexBoxStyle, ...availableWorkflowStyle}}>
<div style={flexContentStyle}>
<div><img src={bookImage} style={iconStyle} /></div>
<div style={ blockRightStyle }>
<div style={counterStyle}>{workflows.length}</div>
<div style={fontSize_16}>AVAILABE WORKFLOWS</div>
</div>
</div>
</div>
<div style={{...flexBoxStyle, ...notificationStyle}}>
<div style={flexContentStyle}>
<div><img src={bagImage} style={iconStyle} /></div>
<div style={ blockRightStyle }>
<div style={counterStyle}>{workflows.length}</div>
<div style={fontSize_16}>NOTIFICATIONS</div>
</div>
</div>
</div>
</div>
*/}
{/*
chipRenderer={({ value, isFocused, isDisabled, handleClick, handleRequestDelete }, key) => {
console.log("VALUE: ", value)
return (
<Chip
key={key}
style={chipStyle}
>
{value}
</Chip>
)
}}
*/}
<div style={{width: "100%", minHeight: isMobile ? 0 : 51, maxHeight: isMobile ? 0 : 51, marginTop: 10, }}>
{!isMobile && usecases !== null && usecases !== undefined && usecases.length > 0 ?
@@ -3772,12 +3760,12 @@ const Workflows = (props) => {
}
const gettingStartedDrawer =
<Drawer
anchor={"right"}
open={drawerOpen}
variant="persistent"
keepMounted={true}
PaperProps={{
<Drawer
anchor={"right"}
open={drawerOpen}
variant="persistent"
keepMounted={true}
PaperProps={{
style: {
resize: "both",
overflow: "auto",
@@ -3928,6 +3916,7 @@ const Workflows = (props) => {
{modalOpen === true ?
<EditWorkflow
globalUrl={globalUrl}
userdata={userdata}
workflow={editingWorkflow}
setWorkflow={setEditingWorkflow}
modalOpen={modalOpen}
@@ -3938,6 +3927,7 @@ const Workflows = (props) => {
isEditing={isEditing}
workflows={workflows}
apps={apps}
setWorkflows={setWorkflows}
/>
: null}