Merge branch 'frikky:launch' into launch

This commit is contained in:
Jay Gohil
2022-03-03 16:09:25 +05:30
committed by GitHub
48 changed files with 5726 additions and 2447 deletions
+14 -6
View File
@@ -15,7 +15,7 @@ import theme from "./theme";
import Apps from "./views/Apps";
import AppCreator from "./views/AppCreator";
import Dashboard from "./views/Dashboard";
import Dashboard from "./views/Dashboard.jsx";
import AdminSetup from "./views/AdminSetup";
import Admin from "./views/Admin";
import Docs from "./views/Docs";
@@ -49,6 +49,12 @@ if (window.location.port === "3000") {
//globalUrl = "http://localhost:5002"
}
if (globalUrl.includes("githubpreview.dev")) {
//globalUrl = globalUrl.replace("3000", "5001")
globalUrl = "https://frikky-shuffle-5gvr4xx62w64-5001.githubpreview.dev"
}
console.log("global: ", globalUrl)
const App = (message, props) => {
const [userdata, setUserData] = useState({});
@@ -70,7 +76,7 @@ const App = (message, props) => {
checkLogin();
setDataset(true);
}
});
}, []);
if (
isLoaded &&
@@ -78,17 +84,19 @@ const App = (message, props) => {
!window.location.pathname.startsWith("/login") &&
!window.location.pathname.startsWith("/docs") &&
!window.location.pathname.startsWith("/detectionframework") &&
!window.location.pathname.startsWith("/adminsetup")
!window.location.pathname.startsWith("/adminsetup") &&
!window.location.pathname.startsWith("/usecases")
) {
window.location = "/login";
}
const getUserNotifications = () => {
fetch(`${globalUrl}/api/v1/notifications`, {
fetch(`${globalUrl}/api/v1/users/notifications`, {
credentials: "include",
headers: {
"Content-Type": "application/json",
},
cors: "cors",
})
.then((response) => response.json())
.then((responseJson) => {
@@ -109,7 +117,7 @@ const App = (message, props) => {
const checkLogin = () => {
var baseurl = globalUrl;
fetch(baseurl + "/api/v1/users/getinfo", {
fetch(`${globalUrl}/api/v1/getinfo`, {
credentials: "include",
headers: {
"Content-Type": "application/json",
@@ -399,7 +407,7 @@ const App = (message, props) => {
/>
<Route
exact
path="/dashboard"
path="/usecases"
element={
<Dashboard
isLoaded={isLoaded}
@@ -556,7 +556,7 @@ const ConfigureWorkflow = (props) => {
{action.must_activate ? (
<Button
color="primary"
variant="outlined"
variant="contained"
onClick={() => {
console.log("ACTION: ", action)
activateApp(action.action.app_id, action.app_name, action.app_version);
@@ -666,7 +666,7 @@ const ConfigureWorkflow = (props) => {
}
}}
>
Finish setup
Close window
</Button>
</ButtonGroup>
</div>
@@ -1499,7 +1499,7 @@ const Framework = (props) => {
/>
:
<div>
TBD: Coming in 1.0.0.
Coming in 1.0.0. <a style={{ textDecoration: "none", color: "#f85a3e" }} href="https://shuffler.io/register" target="_blank">Register for Shuffle cloud</a> to try an early version now.
</div>
: null}
</div>
+11
View File
@@ -35,6 +35,7 @@ import {
import {
Analytics as AnalyticsIcon,
Lightbulb as LightbulbIcon,
} from "@mui/icons-material";
//import LogoutIcon from '@mui/icons-material/Logout';
import { useAlert } from "react-alert";
@@ -466,6 +467,16 @@ const Header = (props) => {
<AnalyticsIcon style={{marginRight: 5 }}/> Get Started
</Link>
</MenuItem>
<MenuItem
onClick={(event) => {
event.preventDefault();
handleClose();
}}
>
<Link to="/usecases" style={hrefStyle}>
<LightbulbIcon style={{marginRight: 5 }}/> Use Cases
</Link>
</MenuItem>
<MenuItem
onClick={(event) => {
event.preventDefault();
+202
View File
@@ -0,0 +1,202 @@
import React, {useState, useRef, useImperativeHandle} from 'react'
import {makeStyles} from '@material-ui/core/styles'
import Menu, {MenuProps} from '@material-ui/core/Menu'
import MenuItem, {MenuItemProps} from '@material-ui/core/MenuItem'
import ArrowRight from '@material-ui/icons/ArrowRight'
import clsx from 'clsx'
export interface NestedMenuItemProps extends Omit<MenuItemProps, 'button'> {
/**
* Open state of parent `<Menu />`, used to close decendent menus when the
* root menu is closed.
*/
parentMenuOpen: boolean
/**
* Component for the container element.
* @default 'div'
*/
component?: React.ElementType
/**
* Effectively becomes the `children` prop passed to the `<MenuItem/>`
* element.
*/
label?: React.ReactNode
/**
* @default <ArrowRight />
*/
rightIcon?: React.ReactNode
/**
* Props passed to container element.
*/
ContainerProps?: React.HTMLAttributes<HTMLElement> &
React.RefAttributes<HTMLElement | null>
/**
* Props passed to sub `<Menu/>` element
*/
MenuProps?: Omit<MenuProps, 'children'>
/**
* @see https://material-ui.com/api/list-item/
*/
button?: true | undefined
}
const TRANSPARENT = 'rgba(0,0,0,0)'
const useMenuItemStyles = makeStyles((theme) => ({
root: (props: any) => ({
backgroundColor: props.open ? theme.palette.action.hover : TRANSPARENT
})
}))
/**
* Use as a drop-in replacement for `<MenuItem>` when you need to add cascading
* menu elements as children to this component.
*/
const NestedMenuItem = React.forwardRef<
HTMLLIElement | null,
NestedMenuItemProps
>(function NestedMenuItem(props, ref) {
const {
parentMenuOpen,
component = 'div',
label,
rightIcon = <ArrowRight />,
children,
className,
tabIndex: tabIndexProp,
MenuProps = {},
ContainerProps: ContainerPropsProp = {},
...MenuItemProps
} = props
const {ref: containerRefProp, ...ContainerProps} = ContainerPropsProp
const menuItemRef = useRef<HTMLLIElement>(null)
useImperativeHandle(ref, () => menuItemRef.current)
const containerRef = useRef<HTMLDivElement>(null)
useImperativeHandle(containerRefProp, () => containerRef.current)
const menuContainerRef = useRef<HTMLDivElement>(null)
const [isSubMenuOpen, setIsSubMenuOpen] = useState(false)
const handleMouseEnter = (event: React.MouseEvent<HTMLElement>) => {
setIsSubMenuOpen(true)
if (ContainerProps?.onMouseEnter) {
ContainerProps.onMouseEnter(event)
}
}
const handleMouseLeave = (event: React.MouseEvent<HTMLElement>) => {
setIsSubMenuOpen(false)
if (ContainerProps?.onMouseLeave) {
ContainerProps.onMouseLeave(event)
}
}
// Check if any immediate children are active
const isSubmenuFocused = () => {
const active = containerRef.current?.ownerDocument?.activeElement
for (const child of menuContainerRef.current?.children ?? []) {
if (child === active) {
return true
}
}
return false
}
const handleFocus = (event: React.FocusEvent<HTMLElement>) => {
if (event.target === containerRef.current) {
setIsSubMenuOpen(true)
}
if (ContainerProps?.onFocus) {
ContainerProps.onFocus(event)
}
}
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === 'Escape') {
return
}
if (isSubmenuFocused()) {
event.stopPropagation()
}
const active = containerRef.current?.ownerDocument?.activeElement
if (event.key === 'ArrowLeft' && isSubmenuFocused()) {
containerRef.current?.focus()
}
if (
event.key === 'ArrowRight' &&
event.target === containerRef.current &&
event.target === active
) {
const firstChild = menuContainerRef.current?.children[0] as
| HTMLElement
| undefined
firstChild?.focus()
}
}
const open = isSubMenuOpen && parentMenuOpen
const menuItemClasses = useMenuItemStyles({open})
// Root element must have a `tabIndex` attribute for keyboard navigation
let tabIndex
if (!props.disabled) {
tabIndex = tabIndexProp !== undefined ? tabIndexProp : -1
}
return (
<div
{...ContainerProps}
ref={containerRef}
onFocus={handleFocus}
tabIndex={tabIndex}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onKeyDown={handleKeyDown}
>
<MenuItem
{...MenuItemProps}
className={clsx(menuItemClasses.root, className)}
ref={menuItemRef}
>
{label}
{rightIcon}
</MenuItem>
<Menu
// Set pointer events to 'none' to prevent the invisible Popover div
// from capturing events for clicks and hovers
style={{pointerEvents: 'none'}}
anchorEl={menuItemRef.current}
anchorOrigin={{
vertical: 'top',
horizontal: 'right'
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left'
}}
open={open}
autoFocus={false}
disableAutoFocus
disableEnforceFocus
onClose={() => {
setIsSubMenuOpen(false)
}}
>
<div ref={menuContainerRef} style={{pointerEvents: 'auto'}}>
{children}
</div>
</Menu>
</div>
)
})
export default NestedMenuItem
+57 -28
View File
@@ -49,6 +49,8 @@ const MenuProps = {
scrollX: "auto",
},
},
variant: "menu",
getContentAnchorEl: null,
};
const AuthenticationOauth2 = (props) => {
@@ -84,6 +86,7 @@ const AuthenticationOauth2 = (props) => {
const [oauthUrl, setOauthUrl] = React.useState("");
const [buttonClicked, setButtonClicked] = React.useState(false);
const [selectedScopes, setSelectedScopes] = React.useState([]);
const [offlineAccess, setOfflineAccess] = React.useState(true);
const allscopes =
authenticationType.scope !== undefined ? authenticationType.scope : [];
@@ -111,8 +114,19 @@ const AuthenticationOauth2 = (props) => {
setButtonClicked(true);
console.log("SCOPES: ", scopes);
client_id = client_id.trim()
client_secret = client_secret.trim()
oauth_url = oauth_url.trim()
var resources = "";
if (scopes !== undefined && (scopes !== null) & (scopes.length > 0)) {
if (offlineAccess === true && !scopes.includes("offline_access")) {
if (authenticationType.redirect_uri.includes("microsoft")) {
console.log("Appending offline access")
scopes.push("offline_access")
}
}
resources = scopes.join(" ");
//resources = scopes.join(",");
}
@@ -496,34 +510,49 @@ const AuthenticationOauth2 = (props) => {
}}
/>
{allscopes.length === 0 ? null : (
<span style={{marginTop: 10}}>
Scopes
<Select
multiple
value={selectedScopes}
style={{
backgroundColor: theme.palette.inputColor,
color: "white",
padding: 5,
}}
onChange={(e) => {
handleScopeChange(e);
}}
fullWidth
input={<Input id="select-multiple-native" />}
renderValue={(selected) => selected.join(", ")}
MenuProps={MenuProps}
>
{allscopes.map((data, index) => {
return (
<MenuItem key={index} value={data}>
<Checkbox checked={selectedScopes.indexOf(data) > -1} />
<ListItemText primary={data} />
</MenuItem>
);
})}
</Select>
</span>
<div style={{width: "100%", marginTop: 10, display: "flex"}}>
<span>
Scopes
<Select
multiple
value={selectedScopes}
style={{
backgroundColor: theme.palette.inputColor,
color: "white",
padding: 5,
minWidth: 300,
maxWidth: 300,
}}
onChange={(e) => {
handleScopeChange(e)
}}
fullWidth
input={<Input id="select-multiple-native" />}
renderValue={(selected) => selected.join(", ")}
MenuProps={MenuProps}
>
{allscopes.map((data, index) => {
return (
<MenuItem key={index} value={data}>
<Checkbox checked={selectedScopes.indexOf(data) > -1} />
<ListItemText primary={data} />
</MenuItem>
);
})}
</Select>
</span>
<span>
<Tooltip
color="primary"
title={"Automatic Refresh (default: true)"}
placement="top"
>
<Checkbox style={{paddingTop: 20}} color="secondary" checked={offlineAccess} onClick={() => {
setOfflineAccess(!offlineAccess)
}}/>
</Tooltip>
</span>
</div>
)}
</span>
)}
+222 -73
View File
@@ -97,6 +97,30 @@ const OrgHeader = (props) => {
? ""
: selectedOrganization.defaults.notification_workflow
);
const [openidClientId, setOpenidClientId] = React.useState(
selectedOrganization.sso_config === undefined
? ""
: selectedOrganization.sso_config.client_id === undefined ||
selectedOrganization.sso_config.client_id.length === 0
? ""
: selectedOrganization.sso_config.client_id
);
const [openidAuthorization, setOpenidAuthorization] = React.useState(
selectedOrganization.sso_config === undefined
? ""
: selectedOrganization.sso_config.openid_authorization === undefined ||
selectedOrganization.sso_config.openid_authorization.length === 0
? ""
: selectedOrganization.sso_config.openid_authorization
);
const [openidToken, setOpenidToken] = React.useState(
selectedOrganization.sso_config === undefined
? ""
: selectedOrganization.sso_config.openid_token === undefined ||
selectedOrganization.sso_config.openid_token.length === 0
? ""
: selectedOrganization.sso_config.openid_token
)
const [file, setFile] = React.useState("");
const [fileBase64, setFileBase64] = React.useState(
@@ -145,6 +169,7 @@ const OrgHeader = (props) => {
defaults,
sso_config
) => {
const data = {
name: name,
description: description,
@@ -216,6 +241,9 @@ const OrgHeader = (props) => {
{
sso_entrypoint: ssoEntrypoint,
sso_certificate: ssoCertificate,
client_id: openidClientId,
openid_authorization: openidAuthorization,
openid_token: openidToken,
}
)
}
@@ -548,79 +576,200 @@ const OrgHeader = (props) => {
</span>
</Grid>
)}
<Grid item xs={6} style={{}}>
<span>
<Typography>SSO Entrypoint (IdP)</Typography>
<TextField
required
style={{
flex: "1",
marginTop: "5px",
marginRight: "15px",
backgroundColor: theme.palette.inputColor,
}}
fullWidth={true}
type="name"
multiline={true}
rows={2}
disabled={
selectedOrganization.manager_orgs !== undefined &&
selectedOrganization.manager_orgs !== null &&
selectedOrganization.manager_orgs.length > 0
}
id="outlined-with-placeholder"
margin="normal"
variant="outlined"
placeholder="The entrypoint URL from your provider"
value={ssoEntrypoint}
onChange={(e) => {
setSsoEntrypoint(e.target.value);
}}
InputProps={{
classes: {
notchedOutline: classes.notchedOutline,
},
style: {
color: "white",
},
}}
/>
</span>
</Grid>
<Grid item xs={6} style={{}}>
<span>
<Typography>SSO Certificate (X509)</Typography>
<TextField
required
style={{
flex: "1",
marginTop: "5px",
marginRight: "15px",
backgroundColor: theme.palette.inputColor,
}}
fullWidth={true}
type="name"
id="outlined-with-placeholder"
margin="normal"
variant="outlined"
multiline={true}
rows={2}
placeholder="The X509 certificate to use"
value={ssoCertificate}
onChange={(e) => {
setSsoCertificate(e.target.value);
}}
InputProps={{
classes: {
notchedOutline: classes.notchedOutline,
},
style: {
color: "white",
},
}}
/>
</span>
</Grid>
{isCloud ? null :
<Grid item xs={12} style={{marginTop: 50 }}>
<Typography variant="h4" style={{textAlign: "center",}}>OpenID connect</Typography>
<Grid container style={{marginTop: 10, }}>
<Grid item xs={4} style={{}}>
<span>
<Typography>Client ID</Typography>
<TextField
required
style={{
flex: "1",
marginTop: "5px",
marginRight: "15px",
backgroundColor: theme.palette.inputColor,
}}
fullWidth={true}
type="name"
multiline={true}
rows={2}
disabled={
selectedOrganization.manager_orgs !== undefined &&
selectedOrganization.manager_orgs !== null &&
selectedOrganization.manager_orgs.length > 0
}
id="outlined-with-placeholder"
margin="normal"
variant="outlined"
placeholder="The OpenID client ID from the identity provider"
value={openidClientId}
onChange={(e) => {
setOpenidClientId(e.target.value);
}}
InputProps={{
classes: {
notchedOutline: classes.notchedOutline,
},
style: {
color: "white",
},
}}
/>
</span>
</Grid>
<Grid item xs={4} style={{}}>
<span>
<Typography>Authorization URL</Typography>
<TextField
required
style={{
flex: "1",
marginTop: "5px",
marginRight: "15px",
backgroundColor: theme.palette.inputColor,
}}
fullWidth={true}
type="name"
id="outlined-with-placeholder"
margin="normal"
variant="outlined"
multiline={true}
rows={2}
placeholder="The OpenID authorization URL (usually ends with /authorize)"
value={openidAuthorization}
onChange={(e) => {
setOpenidAuthorization(e.target.value)
}}
InputProps={{
classes: {
notchedOutline: classes.notchedOutline,
},
style: {
color: "white",
},
}}
/>
</span>
</Grid>
<Grid item xs={4} style={{}}>
<span>
<Typography>Token URL</Typography>
<TextField
required
style={{
flex: "1",
marginTop: "5px",
marginRight: "15px",
backgroundColor: theme.palette.inputColor,
}}
fullWidth={true}
type="name"
id="outlined-with-placeholder"
margin="normal"
variant="outlined"
multiline={true}
rows={2}
placeholder="The OpenID token URL (usually ends with /token)"
value={openidToken}
onChange={(e) => {
setOpenidToken(e.target.value)
}}
InputProps={{
classes: {
notchedOutline: classes.notchedOutline,
},
style: {
color: "white",
},
}}
/>
</span>
</Grid>
</Grid>
</Grid>
}
{isCloud ? null :
<Grid item xs={12} style={{marginTop: 50,}}>
<Typography variant="h4" style={{textAlign: "center",}}>SAML SSO (v1.1)</Typography>
<Grid container style={{marginTop: 10, }}>
<Grid item xs={6} style={{}}>
<span>
<Typography>SSO Entrypoint (IdP)</Typography>
<TextField
required
style={{
flex: "1",
marginTop: "5px",
marginRight: "15px",
backgroundColor: theme.palette.inputColor,
}}
fullWidth={true}
type="name"
multiline={true}
rows={2}
disabled={
selectedOrganization.manager_orgs !== undefined &&
selectedOrganization.manager_orgs !== null &&
selectedOrganization.manager_orgs.length > 0
}
id="outlined-with-placeholder"
margin="normal"
variant="outlined"
placeholder="The entrypoint URL from your provider"
value={ssoEntrypoint}
onChange={(e) => {
setSsoEntrypoint(e.target.value);
}}
InputProps={{
classes: {
notchedOutline: classes.notchedOutline,
},
style: {
color: "white",
},
}}
/>
</span>
</Grid>
<Grid item xs={6} style={{}}>
<span>
<Typography>SSO Certificate (X509)</Typography>
<TextField
required
style={{
flex: "1",
marginTop: "5px",
marginRight: "15px",
backgroundColor: theme.palette.inputColor,
}}
fullWidth={true}
type="name"
id="outlined-with-placeholder"
margin="normal"
variant="outlined"
multiline={true}
rows={2}
placeholder="The X509 certificate to use"
value={ssoCertificate}
onChange={(e) => {
setSsoCertificate(e.target.value);
}}
InputProps={{
classes: {
notchedOutline: classes.notchedOutline,
},
style: {
color: "white",
},
}}
/>
</span>
</Grid>
</Grid>
</Grid>
}
{/*
<span style={{textAlign: "center"}}>
{expanded ?
@@ -0,0 +1,19 @@
import React, {useState, useEffect, useLayoutEffect} from 'react';
import Draggable from "react-draggable";
import {
Paper
} from "@material-ui/core";
const PaperComponent = (props) => {
return (
<Draggable
handle="#draggable-dialog-title"
cancel={'[class*="MuiDialogContent-root"]'}
>
<Paper {...props} />
</Draggable>
)
}
export default PaperComponent;
+173 -91
View File
@@ -1,11 +1,12 @@
import React, { useState, useEffect, useLayoutEffect } from "react";
import { makeStyles, createStyles } from "@material-ui/core/styles";
import { validateJson, GetIconInfo } from "../views/Workflows.jsx";
import { GetParsedPaths } from "../views/Apps.jsx";
import { GetIconInfo } from "../views/Workflows.jsx";
import { sortByKey } from "../views/AngularWorkflow.jsx";
import { useTheme } from "@material-ui/core/styles";
import NestedMenuItem from "material-ui-nested-menu-item";
import { useAlert } from "react-alert";
import theme from '../theme';
//import NestedMenuItem from "./NestedMenu.jsx";
@@ -168,6 +169,7 @@ const ParsedAction = (props) => {
//const theme = useTheme();
const classes = useStyles();
const alert = useAlert()
const [expansionModalOpen, setExpansionModalOpen] = React.useState(false);
const [hideBody, setHideBody] = React.useState(true);
@@ -178,23 +180,21 @@ const ParsedAction = (props) => {
const [hiddenDescription, setHiddenDescription] = React.useState(true);
useEffect(() => {
//if (data.startsWith("${") && data.endsWith("}")) {
//}
// PARAM FIX - Gonna use the ID field, even though it's a hack
const paramcheck = selectedAction.parameters.find(param => param.name === "body")
console.log("LOADED! Change hideBody based on input? Action: ", selectedAction, paramcheck)
if (paramcheck !== undefined && paramcheck !== null) {
if (paramcheck.id === "TOGGLED"){
setHideBody(false)
setActivateHidingBodyButton(false)
console.log("TOGGLED BODY!")
} else {
setHideBody(true)
if (paramcheck.id === "UNTOGGLED") {
if (selectedAction.parameters !== null && selectedAction.parameters !== undefined) {
const paramcheck = selectedAction.parameters.find(param => param.name === "body")
//console.log("LOADED! Change hideBody based on input? Action: ", selectedAction, paramcheck)
if (paramcheck !== undefined && paramcheck !== null) {
if (paramcheck.id === "TOGGLED"){
setHideBody(false)
setActivateHidingBodyButton(false)
console.log("UNTOGGLED!")
console.log("TOGGLED BODY!")
} else {
setHideBody(true)
if (paramcheck.id === "UNTOGGLED") {
setActivateHidingBodyButton(false)
console.log("UNTOGGLED!")
}
}
}
}
@@ -304,6 +304,8 @@ const ParsedAction = (props) => {
});
};
const defineStartnode = () => {
if (cy === undefined) {
return;
@@ -391,14 +393,46 @@ const ParsedAction = (props) => {
if (actionlist.length === 0) {
// FIXME: Have previous execution values in here
actionlist.push({
type: "Execution Argument",
name: "Execution Argument",
value: "$exec",
highlight: "exec",
autocomplete: "exec",
example: "",
});
if (workflowExecutions.length > 0) {
for (var key in workflowExecutions) {
if (
workflowExecutions[key].execution_argument === undefined ||
workflowExecutions[key].execution_argument === null ||
workflowExecutions[key].execution_argument.length === 0
) {
continue;
}
console.log("EXEC: ", workflowExecutions[key].execution_argument)
const valid = validateJson(workflowExecutions[key].execution_argument)
console.log("VALID: ", valid)
if (valid.valid) {
actionlist.push({
type: "Execution Argument",
name: "Execution Argument",
value: "$exec",
highlight: "exec",
autocomplete: "exec",
example: valid.result,
})
break
}
}
}
if (actionlist.length === 0) {
actionlist.push({
type: "Execution Argument",
name: "Execution Argument",
value: "$exec",
highlight: "exec",
autocomplete: "exec",
example: "",
})
}
actionlist.push({
type: "Shuffle DB",
name: "Shuffle DB",
@@ -454,7 +488,8 @@ const ParsedAction = (props) => {
continue;
}
var exampledata = item.example === undefined ? "" : item.example;
var exampledata = item.example === undefined || item.example === null ? "" : item.example;
console.log("EXAMPLE: ", exampledata)
// Find previous execution and their variables
//exampledata === "" &&
if (workflowExecutions.length > 0) {
@@ -471,55 +506,22 @@ const ParsedAction = (props) => {
var foundResult = workflowExecutions[key].results.find(
(result) => result.action.id === item.id
);
if (foundResult === undefined) {
if (foundResult === undefined || foundResult === null) {
continue;
}
foundResult.result = foundResult.result.trim();
foundResult.result = foundResult.result
.split(" None")
.join(' "None"');
foundResult.result = foundResult.result
.split(" False")
.join(" false");
foundResult.result = foundResult.result
.split(" True")
.join(" true");
if (foundResult.result !== undefined && foundResult.result !== null) {
foundResult = foundResult.result
}
console.log("VALID RESULT: ", foundResult)
var jsonvalid = true;
try {
const tmp = String(JSON.parse(foundResult.result));
if (
!foundResult.result.includes("{") &&
!foundResult.result.includes("[")
) {
jsonvalid = false;
}
} catch (e) {
try {
foundResult.result = foundResult.result
.split("'")
.join('"');
const tmp = String(JSON.parse(foundResult.result));
if (
!foundResult.result.includes("{") &&
!foundResult.result.includes("[")
) {
jsonvalid = false;
}
} catch (e) {
jsonvalid = false;
}
}
// Finds the FIRST json only
if (jsonvalid) {
exampledata = JSON.parse(foundResult.result);
const valid = validateJson(foundResult)
if (valid.valid) {
exampledata = valid.result;
break;
}
//else {
// console.log("Invalid JSON: ", foundResult.result)
//}
} else {
exampledata = foundResult;
}
}
}
@@ -528,6 +530,7 @@ const ParsedAction = (props) => {
item.label === null || item.label === undefined
? ""
: item.label.split(" ").join("_");
const actionvalue = {
type: "action",
id: item.id,
@@ -539,11 +542,65 @@ const ParsedAction = (props) => {
}
}
//console.log("ACTIONLIST: ", actionlist)
setActionlist(actionlist);
}
}
});
const calculateHelpertext = (input_data) => {
var helperText = ""
var looperText = ""
const found = input_data.match(/[$]{1}([a-zA-Z0-9_-]+\.?){1}([a-zA-Z0-9#_-]+\.?){0,}/g)
if (found !== null) {
try {
// When the found array is empty.
for (var i = 0; i < found.length; i++) {
const variableSplit = found[i].split(".#")
if ((variableSplit.length-1) > 1) {
//console.log("Larger than 1: ", variableSplit)
if (looperText.length === 0) {
looperText += "PS: Double looping (.#) may cause problems."
}
}
var foundSlice = false
for (var j = 0; j < actionlist.length; j++) {
//console.log("ACTION: ", found[i], actionlist[j])
//console.log("ACTION :", found[i].split(".")[0].slice(1,).toLowerCase(), actionlist[j].autocomplete.toLowerCase())
if(found[i].split(".")[0].slice(1,).toLowerCase() == actionlist[j].autocomplete.toLowerCase()){
//console.log("Found: ", found[i])
// Validate path?
foundSlice = true
}
}
if (!foundSlice) {
if (!helperText.includes("Invalid variables")) {
helperText+= "Invalid variables: "
}
helperText+= found[i] + ", "
}
}
} catch (e) {
console.log("Parsing error: ", e)
}
}
if (looperText.length > 0) {
if (helperText.length > 0) {
helperText += ". "
}
helperText += looperText
}
return helperText
}
const changeActionParameter = (event, count, data) => {
//console.log("Action change: ", selectedAction, data)
if (data.name.startsWith("${") && data.name.endsWith("}")) {
@@ -1282,21 +1339,27 @@ const ParsedAction = (props) => {
const clickedFieldId = "rightside_field_" + count;
const shufflecode = <ShuffleCodeEditor
fieldCount = {fieldCount}
setFieldCount = {setFieldCount}
actionlist = {actionlist}
changeActionParameterCodeMirror = {changeActionParameterCodeMirror}
codedata={codedata}
setcodedata={setcodedata}
expansionModalOpen={expansionModalOpen}
setExpansionModalOpen={setExpansionModalOpen}
/>
const shufflecode = fieldCount !== count ? null :
(
<ShuffleCodeEditor
fieldCount = {fieldCount}
setFieldCount = {setFieldCount}
actionlist = {actionlist}
changeActionParameterCodeMirror = {changeActionParameterCodeMirror}
codedata={codedata}
setcodedata={setcodedata}
expansionModalOpen={expansionModalOpen}
setExpansionModalOpen={setExpansionModalOpen}
/>
)
//<TextareaAutosize
// <CodeMirror
//fullWidth
var baseHelperText = ""
if (data !== undefined && data !== null && data.value !== undefined && data.value !== null && data.value.length > 0) {
baseHelperText = calculateHelpertext(data.value)
}
var datafield = (
<TextField
@@ -1405,7 +1468,7 @@ const ParsedAction = (props) => {
//changeActionParameterCodemirror(event, count, data)
changeActionParameter(event, count, data);
}}
helperText={
helperText={baseHelperText.length > 0 ? baseHelperText :
selectedApp.generated &&
selectedApp.activated &&
data.name === "body" ? (
@@ -1423,15 +1486,7 @@ const ParsedAction = (props) => {
) : null
}
onBlur={(event) => {
// Super basic check
//if (event.target.value.startsWith("{")) {
// console.log("VALIDATING JSON")
// try {
// JSON.parse(event.target.value)
// } catch (e) {
// alert.error("Failed to parse json: ", e)
// }
//}
baseHelperText = calculateHelpertext(event.target.value)
}}
/>
);
@@ -1536,6 +1591,9 @@ const ParsedAction = (props) => {
datafield = (
<Select
MenuProps={{
disableScrollLock: true,
}}
SelectDisplayProps={{
style: {
marginLeft: 10,
@@ -2036,6 +2094,9 @@ const ParsedAction = (props) => {
Autocomplete
</InputLabel>
<Select
MenuProps={{
disableScrollLock: true,
}}
labelId="action-autocompleter"
SelectDisplayProps={{
style: {
@@ -2333,11 +2394,17 @@ const ParsedAction = (props) => {
selectedApp.versions !== undefined &&
selectedApp.versions.length > 1 ? (
<Select
MenuProps={{
disableScrollLock: true,
}}
defaultValue={selectedAction.app_version}
onChange={(event) => {
console.log("VAL: ", event.target.value)
console.log("App: ", selectedApp)
const newversion = selectedApp.versions.find(
(tmpApp) => tmpApp.version == event.target.value
);
console.log("NEWVERSION: ", newversion);
if (newversion !== undefined && newversion !== null) {
getApp(newversion.id, true);
@@ -2408,6 +2475,7 @@ const ParsedAction = (props) => {
if (param.value.includes(baselabel)) {
//if (param.value.toLowerCase().includes(baselabel)) {
console.log("FOUND: ", param);
workflow.actions[key].parameters[subkey].value.replaceAll(
baselabel,
e.target.value
@@ -2497,6 +2565,9 @@ const ParsedAction = (props) => {
<Typography>Authentication</Typography>
<div style={{ display: "flex" }}>
<Select
MenuProps={{
disableScrollLock: true,
}}
labelId="select-app-auth"
value={
Object.getOwnPropertyNames(
@@ -2508,6 +2579,7 @@ const ParsedAction = (props) => {
SelectDisplayProps={{
style: {
marginLeft: 10,
maxWidth: 250,
},
}}
fullWidth
@@ -2581,6 +2653,7 @@ const ParsedAction = (props) => {
>
<IconButton
color="primary"
variant="outlined"
style={{}}
onClick={() => {
setAuthenticationModalOpen(true);
@@ -2597,6 +2670,9 @@ const ParsedAction = (props) => {
<div style={{ marginTop: "20px" }}>
<Typography>Environment</Typography>
<Select
MenuProps={{
disableScrollLock: true,
}}
value={
selectedActionEnvironment === undefined ||
selectedActionEnvironment.Name === undefined
@@ -2651,6 +2727,9 @@ const ParsedAction = (props) => {
<div style={{ marginTop: "20px" }}>
<Typography>Set execution variable (optional)</Typography>
<Select
MenuProps={{
disableScrollLock: true,
}}
value={
selectedAction.execution_variable !== undefined
? selectedAction.execution_variable.name
@@ -2843,6 +2922,9 @@ const ParsedAction = (props) => {
{/*setNewSelectedAction !== undefined ?
<Select
MenuProps={{
disableScrollLock: true,
}}
value={selectedAction.name}
fullWidth
onChange={setNewSelectedAction}
+12 -1
View File
@@ -23,6 +23,7 @@ import {
import { useTheme } from '@material-ui/core/styles';
import { validateJson } from "../views/Workflows.jsx";
import ReactJson from "react-json-view";
import PaperComponent from "../components/PaperComponent.jsx"
import CodeMirror from '@uiw/react-codemirror';
import 'codemirror/keymap/sublime';
@@ -89,16 +90,24 @@ const CodeEditor = (props) => {
return (
<Dialog
disableEnforceFocus={true}
hideBackdrop={true}
disableBackdropClick={true}
open={expansionModalOpen}
onClose={() => {
//setExpansionModalOpen(false)
console.log("In closer")
changeActionParameterCodeMirror({target: {value: ""}}, fieldCount, localcodedata)
}}
PaperComponent={PaperComponent}
aria-labelledby="draggable-dialog-title"
PaperProps={{
style: {
backgroundColor: theme.palette.surfaceColor,
color: "white",
minWidth: 600,
padding: 25,
border: theme.palette.defaultBorder,
zIndex: 10012,
},
}}
>
@@ -109,7 +118,9 @@ const CodeEditor = (props) => {
>
<div style={{display: "flex"}}>
<DialogTitle
id="draggable-dialog-title"
style={{
cursor: "move",
paddingBottom:20,
paddingLeft: 10,
}}
+287
View File
@@ -0,0 +1,287 @@
import React, { useState, useEffect, useLayoutEffect } from "react";
import theme from '../theme';
import {
Chip,
Typography,
Paper,
Avatar,
Grid,
Tooltip,
} from "@material-ui/core";
import {
AvatarGroup,
} from "@mui/material"
import {
Restore as RestoreIcon,
Edit as EditIcon,
BubbleChart as BubbleChartIcon,
MoreVert as MoreVertIcon,
} from '@material-ui/icons';
import { useNavigate, Link, useParams } from "react-router-dom";
const workflowActionStyle = {
display: "flex",
width: 160,
height: 44,
justifyContent: "space-between",
}
const paperAppStyle = {
minHeight: 130,
maxHeight: 130,
overflow: "hidden",
width: "100%",
color: "white",
backgroundColor: theme.palette.surfaceColor,
padding: "12px 12px 0px 15px",
borderRadius: 5,
display: "flex",
boxSizing: "border-box",
position: "relative",
}
const chipStyle = {
backgroundColor: "#3d3f43",
marginRight: 5,
paddingLeft: 5,
paddingRight: 5,
height: 28,
cursor: "pointer",
borderColor: "#3d3f43",
color: "white",
}
const WorkflowPaper = (props) => {
const { data } = props;
let navigate = useNavigate();
const [open, setOpen] = React.useState(false);
const [anchorEl, setAnchorEl] = React.useState(null);
const appGroup = data.action_references === undefined || data.action_references === null ? [] : data.action_references
//console.log("Workflow: ", data)
var boxColor = "#86c142";
var parsedName = data.name;
if (
parsedName !== undefined &&
parsedName !== null &&
parsedName.length > 20
) {
parsedName = parsedName.slice(0, 21) + "..";
}
const imageStyle = {
width: 24,
height: 24,
marginRight: 10,
border: "1px solid rgba(255,255,255,0.3)",
}
var image = data.creator_info !== undefined && data.creator_info !== null && data.creator_info.image !== undefined && data.creator_info.image !== null && data.creator_info.image.length > 0 ? <Avatar alt={data.creator} src={data.creator_info.image} style={imageStyle}/> : <Avatar alt={"shuffle_image"} src={theme.palette.defaultImage} style={imageStyle}/>
const creatorname = data.creator_info !== undefined && data.creator_info !== null && data.creator_info.username !== undefined && data.creator_info.username !== null && data.creator_info.username.length > 0 ? data.creator_info.username : ""
var orgName = "";
var orgId = "";
if ((data.objectID === undefined || data.objectID === null) && data.id !== undefined && data.id !== null) {
data.objectID = data.id
}
//console.log("IMG: ", data)
return (
<div style={{width: "100%", position: "relative",}}>
<Paper square style={paperAppStyle}>
<div
style={{
position: "absolute",
bottom: 1,
left: 1,
height: 12,
width: 12,
backgroundColor: boxColor,
borderRadius: "0 100px 0 0",
}}
/>
<Grid
item
style={{ display: "flex", flexDirection: "column", width: "100%" }}
>
<Grid item style={{ display: "flex", maxHeight: 34 }}>
<Tooltip title={`${creatorname}`} placement="bottom">
<div
style={{ cursor: data.creator_info !== undefined ? "pointer" : "inherit" }}
onClick={() => {
if (data.creator_info !== undefined) {
navigate("/creators/"+data.creator_info.username)
}
}}
>
{image}
</div>
</Tooltip>
<Tooltip title={`Edit ${data.name}`} placement="bottom">
<Typography
variant="body1"
style={{
marginBottom: 0,
paddingBottom: 0,
maxHeight: 30,
flex: 10,
}}
>
<Link
to={"/workflows/" + data.objectID}
style={{ textDecoration: "none", color: "inherit" }}
>
{parsedName}
</Link>
</Typography>
</Tooltip>
</Grid>
<Grid item style={workflowActionStyle}>
{appGroup.length > 0 ?
<div style={{display: "flex", marginTop: 8, }}>
<AvatarGroup max={4} style={{marginLeft: 5, maxHeight: 24,}}>
{appGroup.map((app, index) => {
return (
<div
key={index}
style={{
height: 24,
width: 24,
filter: "brightness(0.6)",
cursor: "pointer",
}}
onClick={() => {
navigate("/apps/"+app.id)
}}
>
<Tooltip color="primary" title={app.name} placement="bottom">
<Avatar alt={app.name} src={app.image_url} style={{width: 24, height: 24}}/>
</Tooltip>
</div>
)
})}
</AvatarGroup>
</div>
:
<Tooltip color="primary" title="Action amount" placement="bottom">
<span style={{ color: "#979797", display: "flex" }}>
<BubbleChartIcon
style={{ marginTop: "auto", marginBottom: "auto" }}
/>
<Typography
style={{
marginLeft: 5,
marginTop: "auto",
marginBottom: "auto",
}}
>
{data.actions === undefined || data.actions === null ? 1 : data.actions.length}
</Typography>
</span>
</Tooltip>
}
<Tooltip
color="primary"
title="Trigger amount"
placement="bottom"
>
<span
style={{ marginLeft: 15, color: "#979797", display: "flex" }}
>
<RestoreIcon
style={{
color: "#979797",
marginTop: "auto",
marginBottom: "auto",
}}
/>
<Typography
style={{
marginLeft: 5,
marginTop: "auto",
marginBottom: "auto",
}}
>
{data.triggers === undefined || data.triggers === null ? 1 : data.triggers.length}
</Typography>
</span>
</Tooltip>
<Tooltip color="primary" title="Subflows used" placement="bottom">
<span
style={{
marginLeft: 15,
display: "flex",
color: "#979797",
cursor: "pointer",
}}
onClick={() => {
}}
>
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
style={{
color: "#979797",
marginTop: "auto",
marginBottom: "auto",
}}
>
<path
d="M0 0H15V15H0V0ZM16 16H18V18H16V16ZM16 13H18V15H16V13ZM16 10H18V12H16V10ZM16 7H18V9H16V7ZM16 4H18V6H16V4ZM13 16H15V18H13V16ZM10 16H12V18H10V16ZM7 16H9V18H7V16ZM4 16H6V18H4V16Z"
fill="#979797"
/>
</svg>
<Typography
style={{
marginLeft: 5,
marginTop: "auto",
marginBottom: "auto",
}}
>
{0}
</Typography>
</span>
</Tooltip>
</Grid>
<Grid
item
style={{
justifyContent: "left",
overflow: "hidden",
marginTop: 5,
}}
>
{data.tags !== undefined && data.tags !== null
? data.tags.map((tag, index) => {
if (index >= 3) {
return null;
}
return (
<Chip
key={index}
style={chipStyle}
label={tag}
variant="outlined"
color="primary"
/>
);
})
: null}
</Grid>
</Grid>
</Paper>
</div>
)
}
export default WorkflowPaper
+1
View File
@@ -16,6 +16,7 @@ const theme = createMuiTheme({
surfaceColor: "#27292d",
inputColor: "#383B40",
borderRadius: 5,
defaultBorder: "1px solid rgba(255,255,255,0.3)",
jsonTheme: "brewer",
reactJsonStyle: {
borderRadius: 5,
+31 -12
View File
@@ -1154,8 +1154,8 @@ const Admin = (props) => {
})
.then((respdata) => {
if (respdata.length === 0) {
alert.error("Failed getting file");
return;
alert.error("Failed getting file. Is it deleted?");
return;
}
var blob = new Blob([respdata], {
@@ -3101,12 +3101,12 @@ const Admin = (props) => {
{fileNamespaces !== undefined &&
fileNamespaces !== null &&
fileNamespaces.length > 1 ? (
<FormControl>
<InputLabel id="input-namespace-label">Namespace</InputLabel>
<FormControl style={{minWidth: 150, maxWidth: 150,}}>
<InputLabel id="input-namespace-label">File Category</InputLabel>
<Select
labelId="input-namespace-select-label"
id="input-namespace-select-id"
style={{ color: "white", minWidth: 100, float: "right" }}
style={{ color: "white", minWidth: 150, maxWidth: 150, float: "right" }}
value={selectedNamespace}
onChange={(event) => {
console.log("CHANGE NAMESPACE: ", event.target);
@@ -3185,7 +3185,7 @@ const Admin = (props) => {
}
return (
<ListItem key={index} style={{ backgroundColor: bgColor }}>
<ListItem key={index} style={{ backgroundColor: bgColor, maxHeight: 100, overflow: "hidden",}}>
<ListItemText
style={{
maxWidth: 225,
@@ -3637,8 +3637,8 @@ const Admin = (props) => {
style={{ minWidth: 125, maxWidth: 125, overflow: "hidden" }}
/>
<ListItemText
primary="Last Edited"
style={{ minWidth: 225, maxWidth: 225, overflow: "hidden" }}
primary="Created"
style={{ minWidth: 230, maxWidth: 230, overflow: "hidden" }}
/>
<ListItemText primary="Actions" />
</ListItem>
@@ -3650,7 +3650,26 @@ const Admin = (props) => {
bgColor = "#1f2023";
}
console.log("Auth data: ", data)
//console.log("Auth data: ", data)
if (data.type === "oauth2") {
data.fields = [
{
"key": "url",
"value": "Secret. Replaced during app execution!",
},
{
"key": "client_id",
"value": "Secret. Replaced during app execution!",
},
{
"key": "client_secret",
"value": "Secret. Replaced during app execution!",
},
{
"key": "scope",
"value": "Secret. Replaced during app execution!",
}]
}
return (
<ListItem key={index} style={{ backgroundColor: bgColor }}>
@@ -3720,11 +3739,11 @@ const Admin = (props) => {
/>
<ListItemText
style={{
maxWidth: 225,
minWidth: 225,
maxWidth: 230,
minWidth: 230,
overflow: "hidden",
}}
primary={new Date(data.edited * 1000).toISOString()}
primary={new Date(data.created * 1000).toISOString()}
/>
<ListItemText>
<IconButton
File diff suppressed because it is too large Load Diff
+63 -27
View File
@@ -275,7 +275,6 @@ const AppCreator = (defaultprops) => {
const [urlPathQueries, setUrlPathQueries] = useState([]);
const [update, setUpdate] = useState("");
const [urlPathParameters] = useState([]);
const [firstrequest, setFirstrequest] = React.useState(true);
const [basedata, setBasedata] = React.useState({});
const [actions, setActions] = useState([]);
const [filteredActions, setFilteredActions] = useState([]);
@@ -343,16 +342,13 @@ const AppCreator = (defaultprops) => {
window.location.host === "shuffler.io";
useEffect(() => {
if (firstrequest) {
setFirstrequest(false);
if (window.location.pathname.includes("apps/edit")) {
setIsEditing(true);
handleEditApp();
} else {
checkQuery();
}
}
});
if (window.location.pathname.includes("apps/edit")) {
setIsEditing(true);
handleEditApp();
} else {
checkQuery();
}
}, []);
const handleEditApp = () => {
fetch(globalUrl + "/api/v1/apps/" + props.match.params.appid + "/config", {
@@ -484,10 +480,29 @@ const AppCreator = (defaultprops) => {
// Sets the data up as it should be at later points
// This is the data FROM the database, not what's being saved
const parseIncomingOpenapiData = (data) => {
console.log("Data: ", data)
var parsedDecoded = ""
try {
const decoded = base64_decode(data.openapi)
parsedDecoded = decoded
} catch (e) {
console.log("Failed JSON parsing: ", e)
parsedDecoded = data
}
if (data.openapi === null) {
alert.info("Failed to load OpenAPI for app. Please contact support if this persists.")
setIsAppLoaded(true);
return
}
console.log("Decoded: ", parsedDecoded)
const parsedapp =
data.openapi === undefined
data.openapi === undefined || data.openapi === null
? data
: JSON.parse(base64_decode(data.openapi));
: JSON.parse(parsedDecoded);
data = parsedapp.body === undefined ? parsedapp : parsedapp.body;
var jsonvalid = false;
@@ -618,6 +633,7 @@ const AppCreator = (defaultprops) => {
continue;
}
//console.log("METHOD: ", methodvalue)
var tmpname = methodvalue.summary;
if (
methodvalue.operationId !== undefined &&
@@ -628,7 +644,13 @@ const AppCreator = (defaultprops) => {
tmpname = methodvalue.operationId;
}
tmpname = tmpname.replaceAll(".", " ");
if (tmpname !== undefined && tmpname !== null) {
tmpname = tmpname.replaceAll(".", " ");
}
if ((tmpname === undefined || tmpname === null) && methodvalue.description !== undefined && methodvalue.description !== null && methodvalue.description.length > 0) {
tmpname = methodvalue.description.replaceAll(".", " ").replaceAll("_", " ")
}
var newaction = {
name: tmpname,
@@ -739,9 +761,9 @@ const AppCreator = (defaultprops) => {
var newbody = {};
// Can handle default, required, description and type
for (var propkey in retRef.properties) {
const parsedkey = propkey
.replaceAll(" ", "_")
.toLowerCase();
console.log("replace: ", propkey)
const parsedkey = propkey.replaceAll(" ", "_").toLowerCase();
newbody[parsedkey] = "${" + parsedkey + "}";
}
@@ -885,9 +907,8 @@ const AppCreator = (defaultprops) => {
) {
var newbody = {};
for (var propkey in parameter.properties) {
const parsedkey = propkey
.replaceAll(" ", "_")
.toLowerCase();
console.log("propkey2: ", propkey)
const parsedkey = propkey.replaceAll(" ", "_").toLowerCase();
if (parameter.properties[propkey].type === undefined) {
console.log(
"Skipping (4): ",
@@ -1022,9 +1043,8 @@ const AppCreator = (defaultprops) => {
) {
var newbody = {};
for (var propkey in parameter.properties) {
const parsedkey = propkey
.replaceAll(" ", "_")
.toLowerCase();
console.log("propkey3: ", propkey)
const parsedkey = propkey.replaceAll(" ", "_").toLowerCase();
if (
parameter.properties[propkey].type === undefined
) {
@@ -1112,9 +1132,8 @@ const AppCreator = (defaultprops) => {
) {
var newbody = {};
for (var propkey in parameter.properties) {
const parsedkey = propkey
.replaceAll(" ", "_")
.toLowerCase();
console.log("propkey4: ", propkey)
const parsedkey = propkey.replaceAll(" ", "_").toLowerCase();
if (
parameter.properties[propkey].type ===
undefined
@@ -1197,6 +1216,7 @@ const AppCreator = (defaultprops) => {
) {
var newbody = {};
for (var propkey in parameter.properties) {
console.log("propkey5: ", propkey)
const parsedkey = propkey
.replaceAll(" ", "_")
.toLowerCase();
@@ -1606,6 +1626,15 @@ const AppCreator = (defaultprops) => {
id: props.match.params.appid,
};
if (isEditing === false) {
var urlParams = new URLSearchParams(window.location.search);
if (urlParams !== undefined && urlParams !== null && urlParams.has("id")) {
data.id = urlParams.get("id")
}
//id: props.match.params.appid,
}
if (basedata.info !== undefined && basedata.info.contact !== undefined) {
data.info["contact"] = basedata.info.contact;
} else if (contact === "") {
@@ -2068,6 +2097,7 @@ const AppCreator = (defaultprops) => {
return;
}
console.log("Paramname: ", parameterName)
var newparamName = parameterName.replaceAll('"', "");
newparamName = newparamName.replaceAll("'", "");
@@ -2077,6 +2107,9 @@ const AppCreator = (defaultprops) => {
name: newparamName,
description: refreshUrl,
}
console.log("Full auth component: ", data.components.securitySchemes["ApiKeyAuth"])
} else if (authenticationOption === "Bearer auth") {
data.components.securitySchemes["BearerAuth"] = {
type: "http",
@@ -2098,6 +2131,7 @@ const AppCreator = (defaultprops) => {
scheme: "basic",
};
} else if (authenticationOption === "Oauth2") {
console.log("oauth2: ", parameterName)
var newparamName = parameterName.replaceAll('"', "");
newparamName = newparamName.replaceAll("'", "");
@@ -5029,6 +5063,8 @@ const AppCreator = (defaultprops) => {
marginTop: "5px",
marginRight: "15px",
backgroundColor: inputColor,
maxHeight: 250,
overflow: "auto",
}}
fullWidth={true}
type="name"
@@ -5228,7 +5264,7 @@ const AppCreator = (defaultprops) => {
);
const loadedCheck =
isLoaded && isAppLoaded && !firstrequest ? (
isLoaded && isAppLoaded ? (
<div>
<div style={bodyDivStyle}>{landingpageDataBrowser}</div>
{newActionModal}
+28 -15
View File
@@ -225,7 +225,11 @@ const Apps = (props) => {
document.title = "Shuffle - Apps";
if (!isLoggedIn && isLoaded) {
navigate("/login")
if (isCloud) {
navigate("/search?tab=apps")
} else {
navigate("/login")
}
}
setFirstrequest(false);
@@ -287,9 +291,9 @@ const Apps = (props) => {
if (response.status !== 200) {
console.log("Status not 200 for apps :O!");
if (isCloud) {
window.location.pathname = "/search";
}
//if (isCloud) {
// window.location.pathname = "/search";
//}
}
return response.json();
@@ -515,7 +519,11 @@ const Apps = (props) => {
valid = "false";
}
if (data.actions === null || data.actions.length === 0) {
if (data.actions === undefined || data.actions === null) {
data.actions = []
}
if (data === undefined || data.actions === undefined || data.actions === null || data.actions.length === 0) {
valid = "false";
}
@@ -717,6 +725,8 @@ const Apps = (props) => {
</Tooltip>
) : null;
// FIXME: Add /apps/new?id=<PUBLIC> to allow for changes of the original
// Should always reference the original ID.
var editButton =
selectedApp.activated &&
selectedApp.private_id !== undefined &&
@@ -737,10 +747,10 @@ const Apps = (props) => {
) : null;
//var editNewButton = editButton === null ?
var editNewButton = null
/*
<Link to={editUrl} style={{ textDecoration: "none" }}>
<Tooltip title={"Add your version"}>
console.log("User, & genrrated, activate: ", props.userdata, selectedApp.generated, selectedApp.activated)
var editNewButton = selectedApp.generated && selectedApp.activated && props.userdata.id !== selectedApp.owner ?
<Link to={activateUrl} style={{ textDecoration: "none" }}>
<Tooltip title={"Edit this public app to your liking"}>
<Button
variant="contained"
component="label"
@@ -751,9 +761,9 @@ const Apps = (props) => {
</Button>
</Tooltip>
</Link>
*/
: null
const activateButton =
const activateButton =
selectedApp.generated && !selectedApp.activated ? (
<div>
<Link to={activateUrl} style={{ textDecoration: "none" }}>
@@ -993,15 +1003,14 @@ const Apps = (props) => {
) : null}
{activateButton}
{editNewButton}
{(props.userdata !== undefined &&
(props.userdata.role === "admin" ||
props.userdata.id === selectedApp.owner ||
selectedApp.owner === ""
)) ||
!selectedApp.generated ? (
)) || !selectedApp.generated ? (
<div>
{editButton}
{editNewButton}
{downloadButton}
{deleteButton}
</div>
@@ -1817,7 +1826,11 @@ const Apps = (props) => {
if (responseJson.success) {
alert.success("Successfully updated app configuration");
} else {
alert.error("Error updating app configuration");
if (responseJson.reason !== undefined && responseJson.reason !== null) {
alert.error("Error: "+responseJson.reason);
} else {
alert.error("Error updating app configuration");
}
}
})
.catch((error) => {
+524 -185
View File
@@ -1,46 +1,327 @@
import React, { useState } from "react";
import React, { useState, useEffect } from "react";
import { useInterval } from "react-powerhooks";
// nodejs library that concatenates classes
import classNames from "classnames";
import theme from '../theme';
// react plugin used to create charts
import { Line, Bar } from "react-chartjs-2";
//import { Line, Bar } from "react-chartjs-2";
import { useAlert } from "react-alert";
// https://demos.creative-tim.com/black-dashboard-react/?ref=appseed#/admin/dashboard
// reactstrap components
import {
Button,
ButtonGroup,
Card,
CardHeader,
CardBody,
CardTitle,
DropdownToggle,
DropdownMenu,
DropdownItem,
UncontrolledDropdown,
Label,
FormGroup,
Input,
Table,
Row,
Col,
UncontrolledTooltip,
} from "reactstrap";
Tooltip,
IconButton,
Typography,
Grid,
Paper,
Chip,
} from "@material-ui/core";
import {
Close as CloseIcon,
DoneAll as DoneAllIcon,
} from "@material-ui/icons";
import WorkflowPaper from "../components/WorkflowPaper.jsx"
// core components
import {
chartExample1,
chartExample2,
chartExample3,
chartExample4,
} from "../charts.js";
//import {
// chartExample1,
// chartExample2,
// chartExample3,
// chartExample4,
//} from "../charts.js";
import {
RadialBarChart,
RadialAreaChart,
RadialAxis,
StackedBarSeries,
TooltipArea,
ChartTooltip,
TooltipTemplate,
RadialAreaSeries,
RadialPointSeries,
RadialArea,
RadialLine,
TreeMap,
TreeMapSeries,
TreeMapLabel,
TreeMapRect,
} from 'reaviz';
const UsecaseListComponent = ({keys, isCloud}) => {
const [expandedIndex, setExpandedIndex] = useState(-1);
const [expandedItem, setExpandedItem] = useState(-1);
if (keys === undefined || keys === null || keys.length === 0) {
return null
}
return (
<div style={{marginTop: 25, minHeight: 1000,}}>
<Typography variant="h1">
Shuffle usecases
</Typography>
<Typography variant="body1">
Usecases in Shuffle are divided into {keys.length} type{keys.length === 1 ? "" : "s"}.
</Typography>
{keys.map((usecase, index) => {
return (
<div key={index} style={{marginTop: index === 0 ? 50 : 100}}>
<Typography variant="h6">
{usecase.name}
</Typography>
<Grid container spacing={3} style={{marginTop: 25}}>
{usecase.list.map((subcase, subindex) => {
if (subcase.matches === undefined || subcase.matches === null) {
subcase.matches = []
}
const selectedItem = subindex === expandedItem && index === expandedIndex
const finished = subcase.matches.length > 0
//const backgroundColor = selectedItem ? "inherit" : finished ? "inherit" : usecase.color
const backgroundColor = "inherit"
const itemBorder = `${selectedItem ? "3px" : expandedItem >= 0 ? "0px" : "1px"} solid ${usecase.color}`
return (
<Grid item xs={selectedItem ? 12 : 4} key={subindex} style={{minHeight: 110,}} onClick={() => {
if (selectedItem) {
} else {
setExpandedIndex(index)
setExpandedItem(subindex)
}
}}>
<Paper style={{padding: "30px 30px 30px 30px", minHeight: 75, cursor: !selectedItem ? "pointer" : "default", border: itemBorder, backgroundColor: backgroundColor,}} onClick={() => {
console.log("Clicked: ", subcase)
}}>
{!selectedItem ?
<div style={{textAlign: "left", position: "relative",}}>
<Typography variant="h6">
<b>{subcase.name}</b>
</Typography>
{finished ?
<Tooltip
title="A workflow has been assigned"
placement="top"
>
<IconButton
style={{ position: "absolute", top: -20, right: -20}}
onClick={(e) => {
}}
>
<DoneAllIcon style={{ color: usecase.color }} />
</IconButton>
</Tooltip>
: null}
</div>
:
<div style={{textAlign: "left", position: "relative",}}>
<Typography variant="h6">
<b>{subcase.name}</b>
</Typography>
<Typography variant="body2">
Description: {subcase.description}
</Typography>
<Tooltip
title="Close window"
placement="top"
style={{ zIndex: 10011 }}
>
<IconButton
style={{ position: "absolute", top: 0, right: 0}}
onClick={(e) => {
setExpandedItem(-1)
setExpandedIndex(-1)
}}
>
<CloseIcon style={{ color: "white" }} />
</IconButton>
</Tooltip>
<div style={{marginTop: 25, display: "flex", minHeight: 400, maxHeight: 400, }}>
<img
alt={subcase.name}
src={"/images/detectionframework.png"}
style={{
flex: 1,
height: 400,
width: 400,
borderRadius: theme.palette.borderRadius,
border: "1px solid rgba(255,255,255,0.3)",
}}
/>
<div style={{flex: 1, marginLeft: 10, textAlign: "center",}}>
<Typography variant="h6">
Your workflow{subcase.matches.length === 1 ? "" : "s"} ({subcase.matches.length})
</Typography>
{subcase.matches.length > 0 ?
<Grid container xs={3} style={{maxWidth: 325, margin: "auto", marginTop: 10, itemAlign: "center", }}>
{subcase.matches.map((workflow, workflowindex) => {
return (
<Grid index={workflowindex} xs={12}>
<WorkflowPaper key={workflowindex} data={workflow} />
</Grid>
)
})}
</Grid>
:
<div>
<Typography variant="body1" color="textSecondary">
No workflow selected yet.
</Typography>
</div>
}
{isCloud !== false ?
<Typography variant="h6">
Public workflows
</Typography>
: null}
</div>
</div>
</div>
}
</Paper>
</Grid>
)
})}
</Grid>
</div>
)
})}
</div>
)
}
const TreeChart = ({keys}) => {
const [hovered, setHovered] = useState("");
return (
<div style={{cursor: "pointer",}} onClick={() => {
console.log("Click: ", hovered)
}}>
<TreeMap
id="all_categories"
data={keys}
margins={10}
series={
<TreeMapSeries
colorScheme={(info) => {
return info.color
}}
label={
<TreeMapLabel
fontSize="15px"
fill="#ffffff"
wrap={false}
/>
}
rect={
<TreeMapRect
cursor="pointer"
animated={true}
onClick={(event) => {
console.log("Click: ", event)
}}
/>
}
/>
}
/>
</div>
)
//axis={<RadialAxis type="category" />}
}
const RadialChart = ({keys, setSelectedCategory}) => {
const [hovered, setHovered] = useState("");
return (
<div style={{cursor: "pointer",}} onClick={() => {
console.log("Click: ", hovered)
if (setSelectedCategory !== undefined) {
setSelectedCategory(hovered)
}
}}>
<RadialAreaChart
id="workflow_categories"
height={500}
width={500}
data={keys}
axis={<RadialAxis type="category" />}
series={
<RadialAreaSeries
interpolation="smooth"
colorScheme={(colorInput) => {
return '#f86a3e'
}}
animated={false}
id="workflow_series_id"
style={{cursor: "pointer",}}
line={
<RadialLine
color={"#000000"}
data={(data, color) => {
console.log("INFO: ", data, color)
return (
null
)
}}
/>
}
tooltip={
<TooltipArea
color={"#000000"}
style={{
backgroundColor: "red",
}}
isRadial={true}
onValueEnter={(event) => {
if (hovered !== event.value.x) {
setHovered(event.value.x)
}
}}
tooltip={
<ChartTooltip
followCursor={true}
modifiers={{
offset: '5px, 5px'
}}
content={(data, color) => {
return (
<div style={{borderRadius: theme.palette.borderRadius, backgroundColor: theme.palette.inputColor, border: "1px solid rgba(255,255,255,0.3)", color: "white", padding: 5, cursor: "pointer",}}>
<Typography variant="body1">
{data.x}
</Typography>
</div>
)
/*
<TooltipTemplate
color={"#ffffff"}
value={{
x: data.x,
}}
/>
)
*/
}
}
/>
}
/>
}
/>
}
/>
</div>
)
//axis={<RadialAxis type="category" />}
}
// This is the start of a dashboard that can be used.
// What data do we fill in here? Idk
const Dashboard = (props) => {
const { globalUrl } = props;
const { globalUrl, isLoggedIn } = props;
const alert = useAlert();
const [bigChartData, setBgChartData] = useState("data1");
const [dayAmount, setDayAmount] = useState(7);
@@ -48,11 +329,172 @@ const Dashboard = (props) => {
const [stats, setStats] = useState({});
const [changeme, setChangeme] = useState("");
const [statsRan, setStatsRan] = useState(false);
const [keys, setKeys] = useState([])
const [treeKeys, setTreeKeys] = useState([])
document.title = "Shuffle - dashboard";
const [selectedUsecaseCategory, setSelectedUsecaseCategory] = useState("");
const [selectedUsecases, setSelectedUsecases] = useState([]);
const [usecases, setUsecases] = useState([]);
const [workflows, setWorkflows] = useState([]);
const isCloud =
window.location.host === "localhost:3002" ||
window.location.host === "shuffler.io";
const getAvailableWorkflows = () => {
fetch(globalUrl + "/api/v1/workflows", {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
fetchUsecases()
console.log("Status not 200 for workflows :O!: ", response.status);
return;
}
return response.json();
})
.then((responseJson) => {
fetchUsecases(responseJson)
console.log("Resp: ", responseJson)
if (responseJson !== undefined) {
//setWorkflows(responseJson);
//fetchUsecases(responseJson)
}
})
.catch((error) => {
fetchUsecases()
//alert.error(error.toString());
});
}
useEffect(() => {
console.log("Changed: ", selectedUsecaseCategory)
if (selectedUsecaseCategory.length === 0) {
setSelectedUsecases(usecases)
} else {
const foundUsecase = usecases.find(data => data.name === selectedUsecaseCategory)
if (foundUsecase !== undefined && foundUsecase !== null) {
console.log("FOUND: ", foundUsecase)
setSelectedUsecases([foundUsecase])
}
}
}, [selectedUsecaseCategory])
document.title = "Shuffle - usecases";
var dayGraphLabels = [60, 80, 65, 130, 80, 105, 90, 130, 70, 115, 60, 130];
var dayGraphData = [60, 80, 65, 130, 80, 105, 90, 130, 70, 115, 60, 130];
const handleKeysetting = (categorydata) => {
var allCategories = []
var treeCategories = []
for (key in categorydata) {
const category = categorydata[key]
allCategories.push({"key": category.name, "data": category.list.length, "color": category.color})
treeCategories.push({"key": category.name, "data": 100, "color": category.color,})
for (var subkey in category.list) {
treeCategories.push({"key": category.list[subkey].name, "data": 20, "color": category.color})
}
}
setKeys(allCategories)
setTreeKeys(treeCategories)
}
const fetchUsecases = (workflows) => {
fetch(globalUrl + "/api/v1/workflows/usecases", {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for usecases");
}
return response.json();
})
.then((responseJson) => {
if (responseJson.success !== false) {
console.log("Usecases: ", responseJson)
if (workflows !== undefined && workflows !== null && workflows.length > 0) {
console.log("Got workflows: ", workflows)
var categorydata = responseJson
var newcategories = []
for (var key in categorydata) {
var category = categorydata[key]
category.matches = []
for (var subcategorykey in category.list) {
var subcategory = category.list[subcategorykey]
subcategory.matches = []
for (var workflowkey in workflows) {
const workflow = workflows[workflowkey]
if (workflow.usecase_ids !== undefined && workflow.usecase_ids !== null) {
for (var usecasekey in workflow.usecase_ids) {
if (workflow.usecase_ids[usecasekey].toLowerCase() === subcategory.name.toLowerCase()) {
console.log("Got match: ", workflow.usecase_ids[usecasekey])
category.matches.push({
"workflow": workflow.id,
"category": subcategory.name,
})
subcategory.matches.push(workflow)
break
}
}
}
if (subcategory.matches.length > 0) {
break
}
}
}
newcategories.push(category)
}
console.log("Categories: ", newcategories)
if (newcategories !== undefined && newcategories !== null && newcategories.length > 0) {
handleKeysetting(newcategories)
setUsecases(newcategories)
setSelectedUsecases(newcategories)
} else {
handleKeysetting(responseJson)
setUsecases(responseJson)
setSelectedUsecases(responseJson)
}
} else {
handleKeysetting(responseJson)
setUsecases(responseJson)
setSelectedUsecases(responseJson)
}
}
})
.catch((error) => {
//alert.error("ERROR: " + error.toString());
console.log("ERROR: " + error.toString());
});
};
useEffect(() => {
getAvailableWorkflows()
//fetchUsecases()
}, []);
const fetchdata = (stats_id) => {
fetch(globalUrl + "/api/v1/stats/" + stats_id, {
method: "GET",
@@ -76,7 +518,8 @@ const Dashboard = (props) => {
setChangeme(stats_id);
})
.catch((error) => {
alert.error("ERROR: " + error.toString());
//alert.error("ERROR: " + error.toString());
console.log("ERROR: " + error.toString());
});
};
@@ -201,8 +644,8 @@ const Dashboard = (props) => {
if (firstRequest) {
console.log("HELO");
setFirstRequest(false);
start();
runUpdate();
//start();
//runUpdate();
} else if (!statsRan) {
// FIXME: Run this under runUpdate schedule?
// 1. Fix labels in dayGraphy.data
@@ -301,158 +744,54 @@ const Dashboard = (props) => {
) : null;
const data = (
<div className="content">
<div className="content" style={{width: 1000, margin: "auto", paddingBottom: 200, textAlign: "center",}}>
<div style={{width: 500, margin: "auto"}}>
{keys.length > 0 ?
<RadialChart keys={keys} setSelectedCategory={setSelectedUsecaseCategory} />
: null}
</div>
{usecases !== null && usecases !== undefined && usecases.length > 0 ?
<div style={{ display: "flex", marginLeft: 120,}}>
{usecases.map((usecase, index) => {
return (
<Chip
key={usecase.name}
style={{
backgroundColor: selectedUsecaseCategory === usecase.name ? usecase.color : theme.palette.surfaceColor,
marginRight: 10,
paddingLeft: 5,
paddingRight: 5,
height: 28,
cursor: "pointer",
border: `1px solid ${usecase.color}`,
color: "white",
}}
label={`${usecase.name} (${usecase.list.length})`}
onClick={() => {
console.log("Clicked: ", usecase.name)
if (selectedUsecaseCategory === usecase.name) {
setSelectedUsecaseCategory("")
} else {
setSelectedUsecaseCategory(usecase.name)
}
//addFilter(usecase.name.slice(3,usecase.name.length))
}}
variant="outlined"
color="primary"
/>
)
})}
</div>
: null}
<UsecaseListComponent keys={selectedUsecases} isCloud={isCloud} />
{treeKeys.length > 0 ?
<TreeChart keys={treeKeys} />
: null}
{newdata}
<Row>
<Col xs="12">
<div className="chart-area">
<Line data={dayGraph.data} options={dayGraph.options} />
</div>
</Col>
<Col xs="12">
<Card className="card-chart">
<CardHeader>
<Row>
<Col className="text-left" sm="6">
<h5 className="card-category">Total Shipments</h5>
<CardTitle tag="h2">Workflows</CardTitle>
</Col>
<Col sm="6">
<ButtonGroup
className="btn-group-toggle float-right"
data-toggle="buttons"
>
<Button
tag="label"
className={classNames("btn-simple", {
active: bigChartData === "data1",
})}
color="info"
id="0"
size="sm"
onClick={() => setBgChartData("data1")}
>
<input
defaultChecked
className="d-none"
name="options"
type="radio"
/>
<span className="d-none d-sm-block d-md-block d-lg-block d-xl-block">
Accounts
</span>
<span className="d-block d-sm-none">
<i className="tim-icons icon-single-02" />
</span>
</Button>
<Button
color="info"
id="1"
size="sm"
tag="label"
className={classNames("btn-simple", {
active: bigChartData === "data2",
})}
onClick={() => setBgChartData("data2")}
>
<input className="d-none" name="options" type="radio" />
<span className="d-none d-sm-block d-md-block d-lg-block d-xl-block">
Purchases
</span>
<span className="d-block d-sm-none">
<i className="tim-icons icon-gift-2" />
</span>
</Button>
<Button
color="info"
id="2"
size="sm"
tag="label"
className={classNames("btn-simple", {
active: bigChartData === "data3",
})}
onClick={() => setBgChartData("data3")}
>
<input className="d-none" name="options" type="radio" />
<span className="d-none d-sm-block d-md-block d-lg-block d-xl-block">
Sessions
</span>
<span className="d-block d-sm-none">
<i className="tim-icons icon-tap-02" />
</span>
</Button>
</ButtonGroup>
</Col>
</Row>
</CardHeader>
<CardBody>
<div className="chart-area">
<Line
data={chartExample1[bigChartData]}
options={chartExample1.options}
/>
</div>
</CardBody>
</Card>
</Col>
</Row>
<Row>
<Col lg="4">
<Card className="card-chart">
<CardHeader>
<h5 className="card-category">Total Shipments</h5>
<CardTitle tag="h3">
<i className="tim-icons icon-bell-55 text-info" /> 763,215
</CardTitle>
</CardHeader>
<CardBody>
<div className="chart-area">
<Line
data={chartExample2.data}
options={chartExample2.options}
/>
</div>
</CardBody>
</Card>
</Col>
<Col lg="4">
<Card className="card-chart">
<CardHeader>
<h5 className="card-category">Daily Sales</h5>
<CardTitle tag="h3">
<i className="tim-icons icon-delivery-fast text-primary" />{" "}
3,500
</CardTitle>
</CardHeader>
<CardBody>
<div className="chart-area">
<Bar
data={chartExample3.data}
options={chartExample3.options}
/>
</div>
</CardBody>
</Card>
</Col>
<Col lg="4">
<Card className="card-chart">
<CardHeader>
<h5 className="card-category">Completed Tasks</h5>
<CardTitle tag="h3">
<i className="tim-icons icon-send text-success" /> 12,100K
</CardTitle>
</CardHeader>
<CardBody>
<div className="chart-area">
<Line
data={chartExample4.data}
options={chartExample4.options}
/>
</div>
</CardBody>
</Card>
</Col>
</Row>
</div>
);
+9 -3
View File
@@ -102,7 +102,7 @@ const Docs = (defaultprops) => {
maxHeight: "83vh",
overflowX: "hidden",
overflowY: "auto",
zIndex: 10003,
zIndex: 1000,
};
const fetchDocList = () => {
@@ -141,6 +141,11 @@ const Docs = (defaultprops) => {
setData(responseJson.reason);
document.title = "Shuffle " + docId + " documentation";
if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason.includes("404: Not Found")) {
navigate("/docs")
return
}
if (responseJson.meta !== undefined) {
setSelectedMeta(responseJson.meta);
}
@@ -352,7 +357,7 @@ const Docs = (defaultprops) => {
}
function Img(props) {
return <img style={{ maxWidth: "100%" }} alt={props.alt} src={props.src} />;
return <img style={{ borderRadius: theme.palette.borderRadius, maxWidth: "100%", marginTop: 15, marginBottom: 15, }} alt={props.alt} src={props.src} />;
}
function CodeHandler(props) {
@@ -517,7 +522,8 @@ const Docs = (defaultprops) => {
const newname =
item.charAt(0).toUpperCase() +
item.substring(1).split("_").join(" ").split("-").join(" ");
const itemMatching =
const itemMatching = props.match.params.key === undefined ? false :
props.match.params.key.toLowerCase() === item.toLowerCase();
//const [tocLines, setTocLines] = React.useState([]);
return (
+103 -384
View File
@@ -2,6 +2,7 @@ import React, { useEffect, useContext } from "react";
import { makeStyles } from "@material-ui/core/styles";
import { useTheme } from "@material-ui/core/styles";
import ReactGA from 'react-ga';
import SecurityFramework from '../components/SecurityFramework.jsx';
import {
@@ -67,7 +68,7 @@ import { DataGrid, GridToolbar } from "@material-ui/data-grid";
//import JSONPrettyMon from 'react-json-pretty/dist/monikai'
import Dropzone from "../components/Dropzone";
import { Link } from "react-router-dom";
import { useNavigate, Link, useParams } from "react-router-dom";
import { useAlert } from "react-alert";
import ChipInput from "material-ui-chip-input";
import { v4 as uuidv4 } from "uuid";
@@ -109,263 +110,7 @@ const useStyles = makeStyles((theme) => ({
},
}));
// Takes an action in Shuffle and
// Returns information about the icon, the color etc to be used
// This can be used for actions of all types
export const GetIconInfo = (action) => {
// Finds the icon based on the action. Should be verbs.
const iconList = [
{ key: "cache_add", values: ["set_cache"] },
{ key: "cache_get", values: ["get_cache"] },
{ key: "filter", values: ["filter", "route", "router"] },
{ key: "merge", values: ["join", "merge"] },
{
key: "search",
values: ["search", "find", "locate", "index", "analyze", "anal", "match", "check cache", "check", "verify", "validate"],
},
{ key: "list", values: ["list", "head", "options"] },
{
key: "download",
values: [
"capture",
"get",
"download",
"return",
"hello_world",
"curl",
"request",
"export",
"preview",
],
},
{ key: "add", values: ["add", "accept", ] },
{ key: "delete", values: ["delete", "remove", "clear", "clean", "dismiss",] },
{
key: "send",
values: [
"send",
"dispatch",
"mail",
"forward",
"post",
"submit",
"mark",
"set",
"release",
],
},
{
key: "repeat",
values: ["repeat", "retry", "pause", "skip", "copy", "replicat", "demo", ],
},
{ key: "execute", values: ["execute", "run", "play", "raise"] },
{ key: "extract", values: ["extract", "unpack", "decompress", "open"] },
{ key: "inflate", values: ["inflate", "pack", "compress"] },
{
key: "edit",
values: [
"modify",
"update",
"create",
"edit",
"put",
"patch",
"change",
"replace",
"conver",
"map",
"format",
"escape",
"describe",
],
},
{
key: "compare",
values: ["compare", "convert", "to", "filter", "translate", "parse"],
},
{ key: "close", values: ["close", "stop", "cancel", "block"] },
];
var selectedKey = "";
if (action.name === undefined || action.name === null) {
} else {
const actionname = action.name.toLowerCase();
for (var key in iconList) {
//console.log(iconList[key], actionname)
const found = iconList[key].values.find((value) =>
actionname.includes(value)
);
if (found !== null && found !== undefined) {
selectedKey = iconList[key].key;
break;
}
}
}
// Some of these are manually parsed or created instead of material ui
//M8 0C3.58 0 0 1.79 0 4C0 6.21 3.58 8 8 8C12.42 8 16 6.21 16 4C16 1.79 12.42 0 8 0ZM0 6V9C0 11.21 3.58 13 8 13C12.42 13 16 11.21 16 9V6C16 8.21 12.42 10 8 10C3.58 10 0 8.21 0 6ZM0 11V14C0 16.21 3.58 18 8 18C9.41 18 10.79 17.81 12 17.46V14.46C10.79 14.81 9.41 15 8 15C3.58 15 0 13.21 0 11ZM17 11V14H14V16H17V19H19V16H22V14H19V11
//https://www.figma.com/file/uCfnMs5w6wnLx6ehPHEV74/Figma-Material-Design-System-v3_0?node-id=834%3A21
//COLORS: https://www.pinterest.co.uk/pin/326299935499972946/
const defaultColor = "#f76b1c";
const defaultGradient = ["#fad961", "#f76b1c"];
const parsedIcons = {
cache_add: {
icon: "M11 3C6.58 3 3 4.79 3 7C3 9.21 6.58 11 11 11C15.42 11 19 9.21 19 7C19 4.79 15.42 3 11 3ZM3 9V12C3 14.21 6.58 16 11 16C15.42 16 19 14.21 19 12V9C19 11.21 15.42 13 11 13C6.58 13 3 11.21 3 9ZM3 14V17C3 19.21 6.58 21 11 21C12.41 21 13.79 20.81 15 20.46V17.46C13.79 17.81 12.41 18 11 18C6.58 18 3 16.21 3 14ZM20 14V17H17V19H20V22H22V19H25V17H22V14",
iconColor: "white",
iconBackgroundColor: "#8acc3f",
originalIcon: "",
fillGradient: ["#8acc3f", "#459622"],
},
cache_get: {
icon: "M12 2C7.58 2 4 3.79 4 6C4 8.06 7.13 9.74 11.15 9.96C12.45 8.7 14.19 8 16 8C16.8 8 17.59 8.14 18.34 8.41C19.37 7.74 20 6.91 20 6C20 3.79 16.42 2 12 2ZM4 8V11C4 12.68 6.08 14.11 9 14.71C9.06 13.7 9.32 12.72 9.77 11.82C6.44 11.34 4 9.82 4 8ZM15.93 9.94C14.75 9.95 13.53 10.4 12.46 11.46C8.21 15.71 13.71 22.5 18.75 19.17L23.29 23.71L24.71 22.29L20.17 17.75C22.66 13.97 19.47 9.93 15.93 9.94ZM15.9 12C17.47 11.95 19 13.16 19 15C19 15.7956 18.6839 16.5587 18.1213 17.1213C17.5587 17.6839 16.7956 18 16 18C13.33 18 12 14.77 13.88 12.88C14.47 12.29 15.19 12 15.9 12ZM4 13V16C4 18.05 7.09 19.72 11.06 19.95C10.17 19.07 9.54 17.95 9.22 16.74C6.18 16.17 4 14.72 4 13Z",
iconColor: "white",
iconBackgroundColor: "#8acc3f",
originalIcon: "",
fillGradient: ["#8acc3f", "#459622"],
},
repeat: {
icon: "M19 8l-4 4h3c0 3.31-2.69 6-6 6-1.01 0-1.97-.25-2.8-.7l-1.46 1.46C8.97 19.54 10.43 20 12 20c4.42 0 8-3.58 8-8h3l-4-4zM6 12c0-3.31 2.69-6 6-6 1.01 0 1.97.25 2.8.7l1.46-1.46C15.03 4.46 13.57 4 12 4c-4.42 0-8 3.58-8 8H1l4 4 4-4H6z",
iconColor: "white",
iconBackgroundColor: defaultColor,
originalIcon: <CachedIcon />,
},
add: {
icon: "M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z",
iconColor: "white",
iconBackgroundColor: defaultColor,
originalIcon: <AddIcon />,
},
edit: {
icon: "M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.9959.9959 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z",
iconColor: "white",
iconBackgroundColor: defaultColor,
originalIcon: <EditIcon />,
},
filter: {
icon: "M4.25 5.61C6.27 8.2 10 13 10 13v6c0 .55.45 1 1 1h2c.55 0 1-.45 1-1v-6s3.72-4.8 5.74-7.39c.51-.66.04-1.61-.79-1.61H5.04c-.83 0-1.3.95-.79 1.61z",
iconColor: "white",
iconBackgroundColor: "#f5515f",
originalIcon: "",
fillGradient: ["#f5515f", "#a1051d"],
},
merge: {
icon: "M17 20.41 18.41 19 15 15.59 13.59 17 17 20.41zM7.5 8H11v5.59L5.59 19 7 20.41l6-6V8h3.5L12 3.5 7.5 8z",
iconColor: "white",
iconBackgroundColor: "#f5515f",
originalIcon: "",
fillGradient: ["#f5515f", "#a1051d"],
},
compare: {
icon: "M10 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h5v2h2V1h-2v2zm0 15H5l5-6v6zm9-15h-5v2h5v13l-5-6v9h5c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z",
iconColor: "white",
iconBackgroundColor: defaultColor,
originalIcon: <CompareIcon />,
},
extract: {
icon: "M3 3h18v2H3z",
iconColor: "white",
iconBackgroundColor: defaultColor,
originalIcon: <MaximizeIcon />,
},
inflate: {
icon: "M6 19h12v2H6z",
iconColor: "white",
iconBackgroundColor: defaultColor,
originalIcon: <MinimizeIcon />,
},
list: {
icon: "M3 9h14V7H3v2zm0 4h14v-2H3v2zm0 4h14v-2H3v2zm16 0h2v-2h-2v2zm0-10v2h2V7h-2zm0 6h2v-2h-2v2z",
iconColor: "white",
iconBackgroundColor: defaultColor,
originalIcon: <TocIcon />,
},
execute: {
icon: "M8 5v14l11-7z",
iconColor: "white",
iconBackgroundColor: defaultColor,
originalIcon: <PlayArrowIcon />,
},
delete: {
icon: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z",
iconColor: "white",
iconBackgroundColor: "#03030e",
originalIcon: <DeleteIcon />,
fillGradient: ["#03030e", "#205d66"],
},
close: {
icon: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z",
iconColor: "white",
iconBackgroundColor: "#03030e",
originalIcon: <CloseIcon />,
fillGradient: ["#03030e", "#205d66"],
},
send: {
icon: "M2.01 21L23 12 2.01 3 2 10l15 2-15 2z",
iconColor: "white",
iconBackgroundColor: "#0373da",
originalIcon: <SendIcon />,
fillGradient: ["#0bc8bf", "#0373da"],
},
download: {
icon: "M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z",
iconColor: "white",
iconBackgroundColor: "#0373da",
originalIcon: <GetAppIcon />,
fillGradient: ["#0bc8bf", "#0373da"],
},
search: {
icon: "M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z",
iconColor: "white",
iconBackgroundColor: "green",
originalIcon: <SearchIcon />,
},
};
var selectedItem = parsedIcons[selectedKey];
if (selectedItem === undefined || selectedItem === null) {
return {
icon: "",
iconColor: "",
iconBackground: "black",
originalIcon: "",
};
}
if (selectedItem.fillGradient === undefined) {
selectedItem.fillGradient = defaultGradient;
selectedItem.iconBackgroundColor = defaultColor;
}
if (selectedItem.icon === "" || selectedItem.icon === undefined) {
console.log(
`MISSING PATH FOR ${selectedKey} (find in scope): `,
selectedItem.originalIcon.type.type
);
}
if (
(selectedItem.originalIcon === undefined ||
selectedItem.originalIcon === "") &&
selectedItem.icon !== "" &&
selectedItem.icon !== undefined
) {
const svg_pin = (
<svg
width={svgSize}
height={svgSize}
viewBox={`0 0 ${svgSize} ${svgSize}`}
version="1.1"
xmlns="http://www.w3.org/2000/svg"
>
<path d={selectedItem.icon} fill={selectedItem.iconColor}></path>
</svg>
);
selectedItem.originalIcon = svg_pin;
}
return selectedItem;
};
const chipStyle = {
backgroundColor: "#3d3f43",
marginRight: 5,
@@ -377,62 +122,6 @@ const chipStyle = {
color: "white",
};
export const validateJson = (showResult) => {
//showResult = showResult.split(" None").join(" \"None\"")
showResult = showResult.split(" False").join(" false");
showResult = showResult.split(" True").join(" true");
var jsonvalid = true;
try {
if (!showResult.includes("{") && !showResult.includes("[")) {
jsonvalid = false;
}
} catch (e) {
showResult = showResult.split("'").join('"');
try {
if (!showResult.includes("{") && !showResult.includes("[")) {
jsonvalid = false;
}
} catch (e) {
jsonvalid = false;
}
}
var result = showResult;
try {
result = jsonvalid ? JSON.parse(showResult) : showResult;
} catch (e) {
////console.log("Failed parsing JSON even though its valid: ", e)
jsonvalid = false;
}
if (jsonvalid === false) {
if (typeof showResult === 'string') {
showResult = showResult.trim()
}
try {
var newstr = showResult.replaceAll("'", '"')
//console.log("Try replacements and trimming with new value: ", newstr)
result = JSON.parse(newstr)
jsonvalid = true
} catch (e) {
//console.log("Failed parsing JSON even though its valid (2): ", e)
jsonvalid = false
}
}
//console.log("VALID: ", jsonvalid, result)
return {
valid: jsonvalid,
result: result,
};
};
const GettingStarted = (props) => {
const { globalUrl, isLoggedIn, isLoaded, userdata } = props;
@@ -440,6 +129,7 @@ const GettingStarted = (props) => {
const theme = useTheme();
const alert = useAlert();
const classes = useStyles(theme);
let navigate = useNavigate();
const imgSize = 60;
const referenceUrl = globalUrl + "/api/v1/hooks/";
@@ -459,8 +149,8 @@ const GettingStarted = (props) => {
"https://github.com/frikky/shuffle-workflows"
);
const [downloadBranch, setDownloadBranch] = React.useState("master");
const [loadWorkflowsModalOpen, setLoadWorkflowsModalOpen] =
React.useState(false);
const [loadWorkflowsModalOpen, setLoadWorkflowsModalOpen] = React.useState(false);
const [videoViewOpen, setVideoViewOpen] = React.useState(false);
const [exportModalOpen, setExportModalOpen] = React.useState(false);
const [exportData, setExportData] = React.useState("");
@@ -825,6 +515,8 @@ const GettingStarted = (props) => {
credentials: "include",
})
.then((response) => {
setVideoViewOpen(true)
if (response.status !== 200) {
console.log("Status not 200 for workflows :O!: ", response.status);
@@ -879,6 +571,8 @@ const GettingStarted = (props) => {
}
})
.catch((error) => {
setVideoViewOpen(true)
alert.error(error.toString());
});
};
@@ -2446,70 +2140,27 @@ const GettingStarted = (props) => {
maxWidth: 600,
};
const WorkflowView = () => {
/*
if (workflows.length === 0) {
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>
);
}
*/
const WorkflowView = () => {
var workflowDelay = -150
var appDelay = -75
const textSpacingDiff = 8
const textType = "body2"
// Discover <a target="_blank" href="https://shuffler.io/creators" style={{textDecoration: "none", color: "#f86a3e",}}>use-cases made by other creators</a>!
// Discover <a target="_blank" href="https://shuffler.io/search?tab=workflows" style={{textDecoration: "none", color: "#f86a3e",}}>use-cases made by us and other creators</a>!
const steps = [
{
html: (
<Typography variant={textType} style={{marginTop: textSpacingDiff}}>
<Link to="/detectionframework" style={{textDecoration: "none", color: "#f86a3e",}}>Find your integrations</Link> by following our simple detection framework!
<Typography variant={textType} style={{marginTop: textSpacingDiff}} onClick={() => {
if (isCloud) {
ReactGA.event({
category: "getting-started",
action: `integerations_find_click`,
})
}
}}>
<Link to="/detectionframework" style={{textDecoration: "none", color: "#f86a3e",}}>Find relevant apps</Link> and start your automation journey
</Typography>
),
tutorial: "find_integrations",
@@ -2517,31 +2168,50 @@ const GettingStarted = (props) => {
{
html:
<Typography variant={textType} style={{marginTop: textSpacingDiff}}>
Discover <span style={{cursor: "pointer", textDecoration: "none", color: "#f86a3e",}} onClick={() => {
Discover <Link to="/usecases" style={{cursor: "pointer", textDecoration: "none", color: "#f86a3e",}}>Use Case ideas</Link> and&nbsp;
<span style={{cursor: "pointer", textDecoration: "none", color: "#f86a3e",}} onClick={() => {
if (isCloud) {
navigate(`/search?tab=workflows`)
ReactGA.event({
category: "getting-started",
action: `workflow_find_click`,
})
return
} else {
alert.success("TBD: Coming in version 1.0.0");
}
const ele = document.getElementById("shuffle_search_field")
if (ele !== undefined && ele !== null) {
console.log("Found ele: ", ele)
ele.focus()
ele.style.borderColor = "#f86a3e"
ele.style.borderWidth = "2px"
const ele = document.getElementById("shuffle_search_field")
if (ele !== undefined && ele !== null) {
console.log("Found ele: ", ele)
ele.focus()
ele.style.borderColor = "#f86a3e"
ele.style.borderWidth = "2px"
} else {
alert.success("TBD: Coming in version 1.0.0");
}
}}>
use-cases made by other creators</span>!
} else {
//alert.success("TBD: Coming in version 1.0.0");
}
}}>
workflows made by other creators</span>!
</Typography>,
tutorial: "discover_workflows",
},
{
html: (
<Typography variant={textType} style={{marginTop: textSpacingDiff}}>
<Typography variant={textType} style={{marginTop: textSpacingDiff}} onClick={() => {
if (isCloud) {
ReactGA.event({
category: "getting-started",
action: `create_workflow_click`,
})
}
}}>
Learn to use Shuffle by&nbsp;
<span style={{cursor: "pointer", color: "#f86a3e",}} onClick={() => {setModalOpen(true)}}>
creating your first workflow
</span> and <Link to="/docs" style={{textDecoration: "none", color: "#f86a3e",}}>reading the docs.</Link>
</span> and <Link to="/docs/getting_started" style={{textDecoration: "none", color: "#f86a3e",}}>reading the docs.</Link>
</Typography>
),
tutorial: "learn_shuffle",
@@ -2557,6 +2227,55 @@ const GettingStarted = (props) => {
return (
<div style={viewStyle}>
{isCloud ?
<Dialog
open={videoViewOpen}
onClose={() => {
setVideoViewOpen(false)
}}
PaperProps={{
style: {
backgroundColor: surfaceColor,
color: "white",
minWidth: 560,
minHeight: 415,
textAlign: "center",
},
}}
>
<DialogTitle>
Welcome to Shuffle!
</DialogTitle>
<Tooltip
title="Close window"
placement="top"
style={{ zIndex: 10011 }}
>
<IconButton
style={{ zIndex: 5000, position: "absolute", top: 10, right: 34 }}
onClick={(e) => {
e.preventDefault();
setVideoViewOpen(false)
}}
>
<CloseIcon style={{ color: "white" }} />
</IconButton>
</Tooltip>
<iframe
width="560"
height="315"
style={{margin: "0px auto 0px auto", width: 560, height: 315,}}
src="https://www.youtube-nocookie.com/embed/rO7k9q3OgC0"
title="Introduction video"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
>
</iframe>
</Dialog>
: null}
<div style={workflowViewStyle}>
<Typography variant="h1" style={{fontSize: 30, marginTop: 25, }}>
Getting Started with Shuffle
+39 -9
View File
@@ -1,5 +1,5 @@
/* eslint-disable react/no-multi-comp */
import React, { useState } from "react";
import React, { useState, useEffect } from "react";
import { makeStyles } from "@material-ui/styles";
import { useInterval } from "react-powerhooks";
@@ -32,9 +32,6 @@ const useStyles = makeStyles({
});
const LoginDialog = (props) => {
const theme = useTheme();
let navigate = useNavigate();
const {
globalUrl,
isLoaded,
@@ -44,6 +41,11 @@ const LoginDialog = (props) => {
register,
checkLogin,
} = props;
const theme = useTheme();
let navigate = useNavigate();
const classes = useStyles();
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [firstRequest, setFirstRequest] = useState(true);
@@ -54,9 +56,13 @@ const LoginDialog = (props) => {
const [MFAField, setMFAField] = useState(false);
const [MFAValue, setMFAValue] = useState("");
// Used to swap from login to register. True = login, false = register
const classes = useStyles();
useEffect(() => {
checkAdmin()
}, [loginViewLoading])
// Error messages etc
const [loginInfo, setLoginInfo] = useState("");
@@ -143,7 +149,7 @@ const LoginDialog = (props) => {
var baseurl = globalUrl;
if (register) {
var url = baseurl + "/api/v1/users/login";
var url = baseurl + "/api/v1/login";
fetch(url, {
mode: "cors",
method: "POST",
@@ -180,7 +186,8 @@ const LoginDialog = (props) => {
setIsLoggedIn(true);
navigate("/workflows")
//navigate("/workflows")
window.location.href = "/workflows"
}
})
)
@@ -264,6 +271,7 @@ const LoginDialog = (props) => {
}}
/>
</div>
{loginViewLoading ? (
<div style={{ textAlign: "center", marginTop: 50 }}>
<Typography
@@ -466,13 +474,15 @@ const LoginDialog = (props) => {
<div style={{ textAlign: "center", margin: 10 }}>
<Button
fullWidth
id="sso_button"
color="secondary"
variant="outlined"
type="button"
style={{ flex: "1", marginTop: 5 }}
onClick={() => {
console.log("CLICK");
navigate(ssoUrl)
//console.log("CLICK SSO");
window.location.href = ssoUrl
//navigate(ssoUrl)
}}
>
Use SSO
@@ -488,6 +498,26 @@ const LoginDialog = (props) => {
const loadedCheck = isLoaded ? <div>{basedata}</div> : <div></div>;
useEffect(() => {
setTimeout(() => {
if (ssoUrl !== undefined && ssoUrl !== null && ssoUrl.length > 0) {
//id="sso_button"
const ssoBtn = document.getElementById("sso_button");
if (ssoBtn !== undefined && ssoBtn !== null) {
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) {
if (tmpView === "true") {
console.log("Tmp: ", tmpView)
ssoBtn.click()
}
}
}
}
}, 200);
}, [ssoUrl])
return <div>{loadedCheck}</div>;
};
+29 -21
View File
@@ -77,7 +77,7 @@ const Settings = (props) => {
//Returns the value from a storage position at a given address.
const isCloud =
window.location.host === "localhost:3002" ||
window.location.host === "shuffler.io";
window.location.host === "shuffler.io"
const bodyDivStyle = {
margin: "auto",
@@ -257,6 +257,7 @@ const Settings = (props) => {
console.log("Status not 200 for WORKFLOW EXECUTION :O!");
}
return response.json();
})
.then((responseJson) => {
@@ -402,11 +403,12 @@ const Settings = (props) => {
userdata.eth_info.account.length > 0 && userdata.eth_info.parsed_balance !== undefined
// Random names for type & autoComplete. Didn't research :^)
var imageData = file.length > 0 ? file : fileBase64;
imageData =
imageData === undefined || imageData.length === 0
? theme.palette.defaultImage
: imageData;
//var imageData = file.length > 0 ? file : fileBase64;
//imageData = imageData === undefined || imageData.length === 0
// ? theme.palette.defaultImage
// : imageData;
const imageData = userSettings.image === undefined || userSettings.image == null || userSettings.image.length === 0 ? theme.palette.defaultImage : userSettings.image
const imageInfo = (
<img
src={imageData}
@@ -729,17 +731,22 @@ const Settings = (props) => {
<div style={{ display: runFlex ? "flex" : "", width: "100%" }}>
<div>
{isCloud ?
<Button
style={{ height: 40, marginTop: 10 }}
variant="outlined"
color="primary"
fullWidth={true}
onClick={() => {
handleGithubConnection();
}}
>
Connect to Github
</Button>
<span>
<Typography variant="body1" color="textSecondary">
By connecting your Github account, you agree to our <a href="/docs/terms_of_service" target="_blank" style={{ textDecoration: "none", color: "#f86a3e"}}>Terms of Service</a>, and acknowledge that your non-sensitive data will be turned into a <a target="_blank" style={{ textDecoration: "none", color: "#f86a3e"}} href="https://shuffler.io/search?tab=creators">creator account</a>. This enables you to earn a passive income from Shuffle. This IS reversible.
</Typography>
<Button
style={{ height: 40, marginTop: 10 }}
variant="outlined"
color="primary"
fullWidth={true}
onClick={() => {
handleGithubConnection();
}}
>
Connect to Github
</Button>
</span>
: null}
</div>
<div style={{ flex: 1, display: "flex" }}>
@@ -859,7 +866,7 @@ const Settings = (props) => {
handleEthereumConnection();
}}
>
Authenticate
Authenticate Metamask Wallet
</Button>
)}
</div>
@@ -917,11 +924,11 @@ const Settings = (props) => {
};
const handleGithubConnection = () => {
console.log("GITHUB CONNECT WOO")
console.log("GITHUB CONNECT WOO: ", isCloud)
//result = RestClient.post('https://github.com/login/oauth/access_token',
console.log("HOST: ", window.location.host);
console.log("HOST: ", window.location);
console.log("Location: ", window.location);
const redirectUri = isCloud
? window.location.host === "localhost:3002"
? "http%3A%2F%2Flocalhost:3002%2Fset_authentication"
@@ -931,10 +938,11 @@ const Settings = (props) => {
:
`https%3A%2F%2F${window.location.host}%2Fset_authentication`
console.log("redirect: ", redirectUri)
const client_id = "3d272b1b782b100b1e61"
const username = userdata.id;
const scopes = "user:email";
const scopes = "read:user";
const url = `https://github.com/login/oauth/authorize?access_type=offline&prompt=consent&client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${scopes}&state=username%3D${username}%26type%3Dgithub`
+472 -136
View File
@@ -6,12 +6,16 @@ import { Navigate } from "react-router-dom";
import SecurityFramework from '../components/SecurityFramework.jsx';
import { ShepherdTour, ShepherdTourContext } from 'react-shepherd'
import { isMobile } from "react-device-detect"
import {
Badge,
Avatar,
Grid,
InputLabel,
Select,
ListSubheader,
Paper,
Tooltip,
Divider,
@@ -31,8 +35,15 @@ import {
DialogTitle,
DialogActions,
DialogContent,
OutlinedInput,
Checkbox,
ListItemText,
} from "@material-ui/core";
import {
AvatarGroup,
} from "@mui/material"
import {
GridOn as GridOnIcon,
List as ListIcon,
@@ -58,6 +69,8 @@ import {
Publish as PublishIcon,
CloudUpload as CloudUploadIcon,
CloudDownload as CloudDownloadIcon,
ExpandLess as ExpandLessIcon,
ExpandMore as ExpandMoreIcon,
} from "@material-ui/icons";
import NestedMenuItem from "material-ui-nested-menu-item";
@@ -381,9 +394,27 @@ const chipStyle = {
};
export const validateJson = (showResult) => {
//showResult = showResult.split(" None").join(" \"None\"")
showResult = showResult.split(" False").join(" false");
showResult = showResult.split(" True").join(" true");
//console.log("INPUT: ", showResult, typeof showResult)
if (typeof showResult === 'string') {
//showResult = showResult.split(" None").join(" \"None\"")
showResult = showResult.split(" False").join(" false");
showResult = showResult.split(" True").join(" true");
//return {
// valid: false,
// result: showResult,
//};
}
//if (typeof showResult === undefined) {
//}
if (typeof showResult === "object" || typeof showResult === "array") {
return {
valid: true,
result: showResult,
};
}
var jsonvalid = true;
try {
@@ -453,6 +484,8 @@ const Workflows = (props) => {
var upload = "";
const [workflows, setWorkflows] = React.useState([]);
const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove
const [selectedUsecases, setSelectedUsecases] = React.useState([]);
const [filteredWorkflows, setFilteredWorkflows] = React.useState([]);
const [selectedWorkflow, setSelectedWorkflow] = React.useState({});
const [workflowDone, setWorkflowDone] = React.useState(false);
@@ -488,6 +521,8 @@ const Workflows = (props) => {
const [actionImageList, setActionImageList] = React.useState([]);
const [firstLoad, setFirstLoad] = React.useState(true);
const [showMoreClicked, setShowMoreClicked] = React.useState(false);
const [usecases, setUsecases] = React.useState([]);
const isCloud =
window.location.host === "localhost:3002" ||
@@ -834,7 +869,7 @@ const Workflows = (props) => {
console.log("Status not 200 for workflows :O!: ", response.status);
if (isCloud) {
window.location.pathname = "/login";
window.location.pathname = "/search?tab=workflows";
}
alert.info("Failed getting workflows.");
@@ -847,8 +882,10 @@ const Workflows = (props) => {
.then((responseJson) => {
if (responseJson !== undefined) {
setWorkflows(responseJson);
fetchUsecases(responseJson)
if (responseJson !== undefined) {
var actionnamelist = [];
var parsedactionlist = [];
for (var key in responseJson) {
@@ -888,6 +925,83 @@ const Workflows = (props) => {
});
};
const handleKeysetting = (categorydata, workflows) => {
console.log("Workflows: ", workflows)
//workflows[0].category = ["detect"]
//workflows[0].usecase_ids = ["Correlate tickets"]
if (workflows !== undefined && workflows !== null) {
var newcategories = []
for (var key in categorydata) {
var category = categorydata[key]
category.matches = []
for (var subcategorykey in category.list) {
var subcategory = category.list[subcategorykey]
subcategory.matches = []
for (var workflowkey in workflows) {
const workflow = workflows[workflowkey]
if (workflow.usecase_ids !== undefined && workflow.usecase_ids !== null) {
for (var usecasekey in workflow.usecase_ids) {
if (workflow.usecase_ids[usecasekey].toLowerCase() === subcategory.name.toLowerCase()) {
console.log("Got match: ", workflow.usecase_ids[usecasekey])
category.matches.push({
"workflow": workflow.id,
"category": subcategory.name,
})
subcategory.matches.push(workflow.id)
break
}
}
}
if (subcategory.matches.length > 0) {
break
}
}
}
newcategories.push(category)
}
console.log("Categories: ", newcategories)
setUsecases(newcategories)
} else {
setUsecases(categorydata)
}
}
const fetchUsecases = (workflows) => {
fetch(globalUrl + "/api/v1/workflows/usecases", {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for usecases");
}
return response.json();
})
.then((responseJson) => {
if (responseJson.success !== false) {
console.log("Usecases: ", responseJson)
handleKeysetting(responseJson, workflows)
}
})
.catch((error) => {
//alert.error("ERROR: " + error.toString());
console.log("ERROR: " + error.toString());
});
};
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => {
if (workflows.length <= 0) {
@@ -896,7 +1010,6 @@ const Workflows = (props) => {
setView(tmpView);
}
//setFirstrequest(false);
getAvailableWorkflows();
}
}, [])
@@ -905,8 +1018,8 @@ const Workflows = (props) => {
color: "#ffffff",
width: "100%",
display: "flex",
minWidth: 1024,
maxWidth: 1024,
minWidth: isMobile ? "100%" : 1024,
maxWidth: isMobile ? "100%" : 1024,
margin: "auto",
};
@@ -926,6 +1039,7 @@ const Workflows = (props) => {
flexDirection: "column",
};
//flexDirection: !isMobile ? "column" : "row",
const paperAppContainer = {
display: "flex",
flexWrap: "wrap",
@@ -1270,7 +1384,7 @@ const Workflows = (props) => {
};
return (
<Grid item xs={4} style={{ padding: "12px 10px 12px 10px" }}>
<Grid item xs={isMobile ? 12 : 4} style={{ padding: "12px 10px 12px 10px" }}>
<Paper
square
style={setupPaperStyle}
@@ -1292,6 +1406,30 @@ const Workflows = (props) => {
);
};
const getWorkflowAppgroup = (data) => {
if (data.actions === undefined || data.actions === null) {
return []
}
var appsFound = []
for (var key in data.actions) {
const parsedAction = data.actions[key]
if (parsedAction.large_image === undefined || parsedAction.large_image === null || parsedAction.large_image === "") {
continue
}
if (parsedAction.app_name === "Shuffle Tools" || parsedAction.app_id === "bc78f35c6c6351b07a09b7aed5d29652") {
continue
}
if (appsFound.findIndex(data => data.app_name === parsedAction.app_name) < 0){
appsFound.push(parsedAction)
}
}
return appsFound
}
const WorkflowPaper = (props) => {
const { data } = props;
const [open, setOpen] = React.useState(false);
@@ -1321,6 +1459,7 @@ const Workflows = (props) => {
}
const actions = data.actions !== null ? data.actions.length : 0;
const appGroup = getWorkflowAppgroup(data)
const [triggers, subflows] = getWorkflowMeta(data);
const workflowMenuButtons = (
@@ -1345,6 +1484,11 @@ const Workflows = (props) => {
if (data.tags !== undefined && data.tags !== null) {
setNewWorkflowTags(JSON.parse(JSON.stringify(data.tags)));
}
console.log("Editing: ", data)
if (data.usecase_ids !== undefined && data.usecase_ids !== null && data.usecase_ids.length > 0) {
setSelectedUsecases(data.usecase_ids)
}
}}
key={"change"}
>
@@ -1543,22 +1687,49 @@ const Workflows = (props) => {
</Tooltip>
</Grid>
<Grid item style={workflowActionStyle}>
<Tooltip color="primary" title="Action amount" placement="bottom">
<span style={{ color: "#979797", display: "flex" }}>
<BubbleChartIcon
style={{ marginTop: "auto", marginBottom: "auto" }}
/>
<Typography
style={{
marginLeft: 5,
marginTop: "auto",
marginBottom: "auto",
}}
>
{actions}
</Typography>
</span>
</Tooltip>
{appGroup.length > 0 ?
<div style={{display: "flex", marginTop: 8, }}>
<AvatarGroup max={4} style={{marginLeft: 5, maxHeight: 24,}}>
{appGroup.map((data, index) => {
return (
<div
key={index}
style={{
height: 24,
width: 24,
filter: "brightness(0.6)",
cursor: "pointer",
}}
onClick={() => {
addFilter(data.app_name);
}}
>
<Tooltip color="primary" title={data.app_name} placement="bottom">
<Avatar alt={data.app_name} src={data.large_image} style={{width: 24, height: 24}}/>
</Tooltip>
</div>
)
})}
</AvatarGroup>
</div>
:
<Tooltip color="primary" title="Action amount" placement="bottom">
<span style={{ color: "#979797", display: "flex" }}>
<BubbleChartIcon
style={{ marginTop: "auto", marginBottom: "auto" }}
/>
<Typography
style={{
marginLeft: 5,
marginTop: "auto",
marginBottom: "auto",
}}
>
{actions}
</Typography>
</span>
</Tooltip>
}
<Tooltip
color="primary"
title="Trigger amount"
@@ -1661,9 +1832,11 @@ const Workflows = (props) => {
justifyContent: "left",
overflow: "hidden",
marginTop: 5,
maxHeight: 28,
overflow: "hidden",
}}
>
{data.tags !== undefined
{data.tags !== undefined && data.tags !== null
? data.tags.map((tag, index) => {
if (index >= 3) {
return null;
@@ -1709,7 +1882,8 @@ const Workflows = (props) => {
tags,
defaultReturnValue,
editingWorkflow,
redirect
redirect,
currentUsecases,
) => {
var method = "POST";
var extraData = "";
@@ -1736,6 +1910,12 @@ const Workflows = (props) => {
workflowdata["default_return_value"] = defaultReturnValue;
}
if (currentUsecases !== undefined && currentUsecases !== null) {
workflowdata["usecase_ids"] = currentUsecases
//workflows[0].category = ["detect"]
//workflows[0].usecase_ids = ["Correlate tickets"]
}
return fetch(globalUrl + "/api/v1/workflows" + extraData, {
method: method,
headers: {
@@ -1982,30 +2162,54 @@ const Workflows = (props) => {
const data = params.row.record;
const actions = data.actions !== null ? data.actions.length : 0;
let [triggers, subflows] = getWorkflowMeta(data);
const appGroup = getWorkflowAppgroup(data)
return (
<Grid item>
<div style={{ display: "flex" }}>
<Tooltip
color="primary"
title="Action amount"
placement="bottom"
>
<span style={{ color: "#979797", display: "flex" }}>
<BubbleChartIcon
style={{ marginTop: "auto", marginBottom: "auto" }}
/>
<Typography
style={{
marginLeft: 5,
marginTop: "auto",
marginBottom: "auto",
}}
>
{actions}
</Typography>
</span>
</Tooltip>
{appGroup.length > 0 ?
<div style={{display: "flex", marginTop: 3, }}>
<AvatarGroup max={4} style={{marginLeft: 5, maxHeight: 24,}}>
{appGroup.map((data, index) => {
return (
<div
key={index}
style={{
height: 24,
width: 24,
filter: "brightness(0.6)",
cursor: "pointer",
}}
onClick={() => {
addFilter(data.app_name);
}}
>
<Tooltip color="primary" title={data.app_name} placement="bottom">
<Avatar alt={data.app_name} src={data.large_image} style={{width: 24, height: 24}}/>
</Tooltip>
</div>
)
})}
</AvatarGroup>
</div>
:
<Tooltip color="primary" title="Action amount" placement="bottom">
<span style={{ color: "#979797", display: "flex" }}>
<BubbleChartIcon
style={{ marginTop: "auto", marginBottom: "auto" }}
/>
<Typography
style={{
marginLeft: 5,
marginTop: "auto",
marginBottom: "auto",
}}
>
{actions}
</Typography>
</span>
</Tooltip>
}
<Tooltip
color="primary"
title="Trigger amount"
@@ -2189,6 +2393,7 @@ const Workflows = (props) => {
return <div style={gridContainer}>{workflowData}</div>;
};
var total_count = 0
const modalView = modalOpen ? (
<Dialog
open={modalOpen}
@@ -2199,7 +2404,8 @@ const Workflows = (props) => {
style: {
backgroundColor: surfaceColor,
color: "white",
minWidth: "800px",
minWidth: isMobile ? "90%" : "800px",
maxWidth: isMobile ? "90%" : "800px",
},
}}
>
@@ -2231,6 +2437,7 @@ const Workflows = (props) => {
}}
color="primary"
placeholder="Name"
required
margin="dense"
defaultValue={newWorkflowName}
autoFocus
@@ -2246,47 +2453,117 @@ const Workflows = (props) => {
color="primary"
defaultValue={newWorkflowDescription}
placeholder="Description"
rows="3"
multiline
margin="dense"
fullWidth
/>
<ChipInput
style={{ marginTop: 10 }}
InputProps={{
style: {
color: "white",
},
}}
placeholder="Tags"
color="primary"
fullWidth
value={newWorkflowTags}
onAdd={(chip) => {
newWorkflowTags.push(chip);
setNewWorkflowTags(newWorkflowTags);
}}
onDelete={(chip, index) => {
newWorkflowTags.splice(index, 1);
setNewWorkflowTags(newWorkflowTags);
}}
/>
<div style={{display: "flex", marginTop: 10, }}>
<ChipInput
style={{ flex: 1}}
InputProps={{
style: {
color: "white",
},
}}
placeholder="Tags"
color="primary"
fullWidth
value={newWorkflowTags}
onAdd={(chip) => {
newWorkflowTags.push(chip);
setNewWorkflowTags(newWorkflowTags);
}}
onDelete={(chip, index) => {
newWorkflowTags.splice(index, 1);
setNewWorkflowTags(newWorkflowTags);
}}
/>
{usecases !== null && usecases !== undefined && usecases.length > 0 ?
<FormControl style={{flex: 1, marginLeft: 5, }}>
<InputLabel htmlFor="grouped-select-usecase">Usecases</InputLabel>
<Select
defaultValue=""
id="grouped-select"
label="Matching Usecase"
multiple
value={selectedUsecases}
renderValue={(selected) => selected.join(', ')}
onChange={(event) => {
console.log("Changed: ", event)
}}
>
<MenuItem value="">
<em>None</em>
</MenuItem>
{usecases.map((usecase, index) => {
//console.log(usecase)
return (
<span key={index}>
<ListSubheader
style={{color: usecase.color}}
>
{usecase.name}
</ListSubheader>
{usecase.list.map((subcase, subindex) => {
//console.log(subcase)
total_count += 1
return (
<MenuItem key={subindex} value={total_count} onClick={(event) => {
if (selectedUsecases.includes(subcase.name)) {
const itemIndex = selectedUsecases.indexOf(subcase.name)
if (itemIndex > -1) {
selectedUsecases.splice(itemIndex, 1)
}
} else {
selectedUsecases.push(subcase.name)
}
setUpdate(Math.random());
setSelectedUsecases(selectedUsecases)
}}>
<Checkbox style={{color: selectedUsecases.includes(subcase.name) ? usecase.color : theme.palette.inputColor}} checked={selectedUsecases.includes(subcase.name)} />
<ListItemText primary={subcase.name} />
</MenuItem>
)
})}
</span>
)
})}
</Select>
</FormControl>
: null}
</div>
{showMoreClicked ?
<span>
<TextField
onBlur={(event) => setDefaultReturnValue(event.target.value)}
InputProps={{
style: {
color: "white",
},
}}
color="primary"
defaultValue={defaultReturnValue}
placeholder="Default return value (used for Subflows if the subflow fails)"
rows="3"
multiline
margin="dense"
fullWidth
/>
</span>
: null}
<Tooltip color="primary" title={"Add more details"} placement="top">
<IconButton
style={{ color: "white", margin: "auto", marginTop: 10, textAlign: "center", width: 50,}}
onClick={() => {
setShowMoreClicked(!showMoreClicked);
}}
>
{showMoreClicked ? <ExpandLessIcon /> : <ExpandMoreIcon />}
</IconButton>
</Tooltip>
<TextField
onBlur={(event) => setDefaultReturnValue(event.target.value)}
InputProps={{
style: {
color: "white",
},
}}
color="primary"
defaultValue={defaultReturnValue}
placeholder="Default return value (used for Subflows if the subflow fails)"
rows="3"
multiline
margin="dense"
fullWidth
/>
</DialogContent>
<DialogActions>
<Button
@@ -2298,6 +2575,7 @@ const Workflows = (props) => {
setEditingWorkflow({});
setNewWorkflowTags([]);
setModalOpen(false);
setSelectedUsecases([])
}}
color="primary"
>
@@ -2316,8 +2594,10 @@ const Workflows = (props) => {
newWorkflowTags,
defaultReturnValue,
editingWorkflow,
false
false,
selectedUsecases,
);
setNewWorkflowName("");
setDefaultReturnValue("");
setNewWorkflowDescription("");
@@ -2330,11 +2610,13 @@ const Workflows = (props) => {
newWorkflowTags,
defaultReturnValue,
{},
true
true,
selectedUsecases,
);
}
setSubmitLoading(true);
setSelectedUsecases([])
}}
color="primary"
>
@@ -2366,7 +2648,7 @@ const Workflows = (props) => {
{view === "list" && (
<Tooltip color="primary" title={"Grid View"} placement="top">
<Button
color="primary"
color="secondary"
variant="text"
onClick={() => {
localStorage.setItem("view", "grid");
@@ -2380,7 +2662,7 @@ const Workflows = (props) => {
{view === "grid" && (
<Tooltip color="primary" title={"List View"} placement="top">
<Button
color="primary"
color="secondary"
variant="text"
onClick={() => {
localStorage.setItem("view", "list");
@@ -2393,12 +2675,12 @@ const Workflows = (props) => {
)}
<Tooltip color="primary" title={"Import workflows"} placement="top">
{importLoading ? (
<Button color="primary" style={{}} variant="text" onClick={() => {}}>
<Button color="secondary" style={{}} variant="text" onClick={() => {}}>
<CircularProgress style={{ maxHeight: 15, maxWidth: 15 }} />
</Button>
) : (
<Button
color="primary"
color="secondary"
style={{}}
variant="text"
onClick={() => upload.click()}
@@ -2421,7 +2703,7 @@ const Workflows = (props) => {
placement="top"
>
<Button
color="primary"
color="secondary"
style={{}}
variant="text"
onClick={() => {
@@ -2433,9 +2715,9 @@ const Workflows = (props) => {
</Tooltip>
) : null}
{isCloud ? null : (
<Tooltip color="primary" title={"Download workflows"} placement="top">
<Tooltip color="primary" title={"Import workflows to Shuffle"} placement="top">
<Button
color="primary"
color="secondary"
style={{}}
variant="text"
onClick={() => setLoadWorkflowsModalOpen(true)}
@@ -2606,9 +2888,54 @@ const Workflows = (props) => {
return (
<div style={viewStyle}>
<div style={workflowViewStyle}>
<div style={{ display: "flex" }}>
<div style={{ flex: 3 }}>
<h2>Workflows</h2>
<div style={{ display: "flex", marginTop: 25, }}>
<div style={{ flex: 1 }}>
<Typography variant="h1" style={{fontSize: 30}}>
Workflows
</Typography>
</div>
{/*
<div style={{ flex: 1 }}>
<Typography style={{ marginTop: 7, marginBottom: "auto" }}>
<a
rel="noopener noreferrer"
target="_blank"
href="https://shuffler.io/docs/workflows"
style={{ textDecoration: "none", color: "#f85a3e" }}
>
Learn more about Workflows
</a>
</Typography>
</div>
*/}
{isMobile ? null :
<div style={{ display: "flex", margin: "0px 0px 20px 0px" }}>
<div style={{ flex: 1, float: "right" }}>
<ChipInput
style={{}}
InputProps={{
style: {
color: "white",
maxWidth: 275,
minWidth: 275,
},
}}
placeholder="Add Filter"
color="primary"
fullWidth
value={filters}
onAdd={(chip) => {
addFilter(chip);
}}
onDelete={(_, index) => {
removeFilter(index);
}}
/>
</div>
</div>
}
<div style={{ flex: 1, textAlign: "right" }}>
{workflowButtons}
</div>
</div>
{/*
@@ -2658,43 +2985,52 @@ const Workflows = (props) => {
)
}}
*/}
<div style={{ display: "flex", margin: "0px 0px 20px 0px" }}>
<div style={{ flex: 1 }}>
<Typography style={{ marginTop: 7, marginBottom: "auto" }}>
<a
rel="noopener noreferrer"
target="_blank"
href="https://shuffler.io/docs/workflows"
style={{ textDecoration: "none", color: "#f85a3e" }}
>
Learn more about Workflows
</a>
</Typography>
</div>
<div style={{ flex: 1, float: "right" }}>
<ChipInput
style={{}}
InputProps={{
style: {
color: "white",
},
}}
placeholder="Add Filter"
color="primary"
fullWidth
value={filters}
onAdd={(chip) => {
addFilter(chip);
}}
onDelete={(_, index) => {
removeFilter(index);
}}
/>
</div>
<div style={{ float: "right", flex: 1, textAlign: "right" }}>
{workflowButtons}
</div>
</div>
<div style={{width: "100%",}}>
{!isMobile && usecases !== null && usecases !== undefined && usecases.length > 0 ?
<div style={{ display: "flex", }}>
{usecases.map((usecase, index) => {
//console.log(usecase)
return (
<Paper
key={usecase.name}
style={{
flex: 1,
backgroundColor: filters.includes(usecase.name.toLowerCase()) ? usecase.color : theme.palette.surfaceColor,
borderRadius: theme.palette.borderRadius,
marginRight: index === usecases.length-1 ? 0 : 10,
height: 60,
cursor: "pointer",
border: `2px solid ${usecase.color}`,
overflow: "hidden",
padding: 10,
}}
onClick={() => {
console.log("Clicked!")
return
if (filters.includes(usecase.name.toLowerCase())) {
addFilter(usecase.name)
} else {
const foundIndex = filters.indexOf(usecase.name.toLowerCase())
removeFilter(foundIndex)
}
}}
>
<a href={`/usecases?selected=${usecase.name}`} rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", }}>
<Typography variant="body1" color="textPrimary">
{usecase.name}
</Typography>
<Typography variant="body2" color="textSecondary">
In use: {usecase.matches.length}/{usecase.list.length}
</Typography>
</a>
</Paper>
)
})}
</div>
: null}
</div>
<div style={{ marginTop: 15 }} />
{actionImageList !== undefined &&
actionImageList !== null &&
@@ -2805,7 +3141,7 @@ const Workflows = (props) => {
workflowDelay += 75
} else {
return (
<Grid item xs={4} style={{ padding: "12px 10px 12px 10px" }}>
<Grid key={index} item xs={isMobile ? 12 : 4} style={{ padding: "12px 10px 12px 10px" }}>
<WorkflowPaper key={index} data={data} />
</Grid>
)
@@ -2813,7 +3149,7 @@ const Workflows = (props) => {
return (
<Zoom key={index} in={true} style={{ transitionDelay: `${workflowDelay}ms` }}>
<Grid item xs={4} style={{ padding: "12px 10px 12px 10px" }}>
<Grid item xs={isMobile ? 12 : 4} style={{ padding: "12px 10px 12px 10px" }}>
<WorkflowPaper key={index} data={data} />
</Grid>
</Zoom>
@@ -3008,7 +3344,7 @@ const Workflows = (props) => {
}}
color="primary"
>
Submit Submit
Submit
</Button>
</DialogActions>
</Dialog>
@@ -3024,7 +3360,7 @@ const Workflows = (props) => {
*/}
<Dropzone
style={{
maxWidth: window.innerWidth > 1366 ? 1366 : 1200,
maxWidth: window.innerWidth > 1366 ? 1366 : isMobile ? "100%" : 1200,
margin: "auto",
padding: 20,
}}