Merge branch 'frikky:launch' into launch
This commit is contained in:
+14
-6
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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;
|
||||
@@ -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}
|
||||
|
||||
@@ -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,
|
||||
}}
|
||||
|
||||
@@ -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
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
+1826
-1274
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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
@@ -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>
|
||||
);
|
||||
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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
|
||||
<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
|
||||
<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
|
||||
|
||||
@@ -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>;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
@@ -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,
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user