Bunch of fixes for MSSP feature buildout
This commit is contained in:
@@ -134,6 +134,9 @@ const AppStats = (defaultprops) => {
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
}).catch((error) => {
|
||||
console.log("Error getting workflow stats: " + error);
|
||||
return workflow
|
||||
})
|
||||
|
||||
if (response.status !== 200) {
|
||||
|
||||
@@ -428,7 +428,6 @@ const ConfigureWorkflow = (props) => {
|
||||
console.log("Found webhook: ", trigger)
|
||||
|
||||
if (trigger.app_association !== undefined && trigger.app_association.name !== null && trigger.app_association.name !== "") {
|
||||
console.log("Actions: ", newactions)
|
||||
const findapp = trigger.app_association.name.toLowerCase()
|
||||
const foundindex = newactions.findIndex(action => action.app_name.toLowerCase() === findapp)
|
||||
|
||||
@@ -449,9 +448,6 @@ const ConfigureWorkflow = (props) => {
|
||||
|
||||
newactions[foundindex].show_steps = true
|
||||
|
||||
console.log("CHANGED ACTION: ", newactions[foundindex])
|
||||
//console.log("Index: ", newactions[foundindex])
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -1321,7 +1317,6 @@ const ConfigureWorkflow = (props) => {
|
||||
}
|
||||
|
||||
if (step.type === "authenticate") {
|
||||
console.log("AUTH STEP: ", step)
|
||||
if (data.must_authenticate === true ) {
|
||||
filled = false
|
||||
} else {
|
||||
|
||||
@@ -78,7 +78,6 @@ const EditWorkflow = (props) => {
|
||||
const [name, setName] = React.useState(workflow.name !== undefined ? workflow.name : "")
|
||||
const [dueDate, setDueDate] = React.useState(workflow.due_date !== undefined && workflow.due_date !== null && workflow.due_date !== 0 ? dayjs(workflow.due_date*1000) : dayjs().subtract(1, 'day'))
|
||||
|
||||
console.log("WORKFLOW: ", workflow)
|
||||
const [inputQuestions, setInputQuestions] = React.useState(workflow.input_questions !== undefined && workflow.input_questions !== null ? JSON.parse(JSON.stringify(workflow.input_questions)) : [])
|
||||
|
||||
const classes = useStyles();
|
||||
@@ -235,7 +234,7 @@ const EditWorkflow = (props) => {
|
||||
</div>
|
||||
</DialogTitle>
|
||||
<FormControl>
|
||||
<div style={{width: 600, position: "fixed", left: 0, bottom: 0, zIndex: 1002, backgroundColor: "rgba(53,53,53,1)", height: 75, paddingTop: 20, paddingLeft: 75, }}>
|
||||
<div style={{borderTop: "1px solid rgba(255,255,255,0.5)", width: 600, position: "fixed", left: 0, bottom: 0, zIndex: 1002, backgroundColor: "rgba(53,53,53,1)", height: 75, paddingTop: 20, paddingLeft: 75, }}>
|
||||
{/*
|
||||
<Button
|
||||
style={{}}
|
||||
@@ -315,7 +314,7 @@ const EditWorkflow = (props) => {
|
||||
}}
|
||||
color="primary"
|
||||
>
|
||||
{submitLoading ? <CircularProgress color="secondary" /> : "Done"}
|
||||
{submitLoading ? <CircularProgress color="secondary" /> : "Save Changes"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -565,14 +564,22 @@ const EditWorkflow = (props) => {
|
||||
fullWidth
|
||||
/>
|
||||
|
||||
<Typography variant="body2" style={{marginTop: 50, }}>
|
||||
MSSP Suborg Distribution (beta - contact support@shuffler.io)
|
||||
<Typography variant="body1" style={{marginTop: 50, }}>
|
||||
MSSP Suborg Distribution (beta - contact support@shuffler.io for more info)
|
||||
</Typography>
|
||||
{userdata !== undefined && userdata !== null && userdata.orgs !== undefined && userdata.orgs !== null && userdata.orgs.length > 0 ?
|
||||
userdata.orgs.filter(org => org.creator_org === userdata.active_org.id).length === 0 ?
|
||||
<Typography variant="body2" style={{marginTop: 10, color: "rgba(255,255,255,0.7)"}}>
|
||||
You can only distribute to suborgs from a parent org.
|
||||
</Typography>
|
||||
userdata.active_org.creator_org === undefined || userdata.active_org.creator_org === null || userdata.active_org.creator_org === "" ?
|
||||
<Typography variant="body2" style={{marginTop: 10, color: "rgba(255,255,255,0.7)"}}>
|
||||
Your organization does not have any suborgs yet. Please <a href="/admin?tab=suborgs" style={{textDecoration: "none", color: "#f86a3e"}} target="_blank">make one</a>, then try again.
|
||||
</Typography>
|
||||
:
|
||||
<Typography variant="body2" style={{marginTop: 10, color: "rgba(255,255,255,0.7)"}}>
|
||||
{innerWorkflow.parentorg_workflow !== undefined && innerWorkflow.parentorg_workflow !== null && innerWorkflow.parentorg_workflow.length > 0 ? <span>This workflow is distributed from <a href={`/workflows/${innerWorkflow.parentorg_workflow}`} style={{textDecoration: "none", color: "#f86a3e"}} target="_blank">your parent workflow</a> (you may not have access).</span> : null}
|
||||
<br />
|
||||
<br />
|
||||
You can only distribute to suborgs from a parent org.
|
||||
</Typography>
|
||||
:
|
||||
<Select
|
||||
multiple
|
||||
@@ -584,8 +591,6 @@ const EditWorkflow = (props) => {
|
||||
newvalue = newvalue.filter(value => value !== "none")
|
||||
}
|
||||
|
||||
console.log("NEWVALUE: ", newvalue)
|
||||
|
||||
if (newvalue.includes("none")) {
|
||||
newvalue = ["none"]
|
||||
} else if (newvalue.includes("all")) {
|
||||
@@ -775,18 +780,172 @@ const EditWorkflow = (props) => {
|
||||
>
|
||||
<AddIcon style={{}} />
|
||||
</Button>
|
||||
|
||||
|
||||
<Typography variant="body1" style={{marginTop: 50, }}>
|
||||
Git Backup Repository
|
||||
</Typography>
|
||||
<Typography variant="body2" style={{ textAlign: "left", marginTop: 5, }} color="textSecondary">
|
||||
Decide where this workflow is backed up in a Git repository. Will create logs and notifications if upload fails. <b>The repository and branch must already have been initialized</b>. Files will show up in the root folder in the format 'orgid/workflow status/workflow id.json' without images. Overrides the <a href="/admin?admin_tab=organization" style={{textDecoration: "none", color: "#f86a3e"}} target="_blank">default backup repository</a> your org has chosen. All fields must be filled in for the backup to work.
|
||||
<br />
|
||||
PS: This is a beta feature, and might not work as expected. Credentials are NOT encrypted.
|
||||
</Typography>
|
||||
<Grid container style={{ marginTop: 10, }} spacing={2}>
|
||||
<Grid item xs={6} style={{}}>
|
||||
<span>
|
||||
<Typography>Workflow Backup Repository</Typography>
|
||||
<TextField
|
||||
required
|
||||
style={{
|
||||
flex: "1",
|
||||
marginTop: "5px",
|
||||
marginRight: "15px",
|
||||
}}
|
||||
fullWidth={true}
|
||||
type="name"
|
||||
multiline={true}
|
||||
rows={1}
|
||||
id="outlined-with-placeholder"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
placeholder="github/com/shuffle/workflowbackup "
|
||||
defaultValue={innerWorkflow.backup_config === undefined || innerWorkflow.backup_config.upload_repo === undefined || innerWorkflow.backup_config.upload_repo === null || innerWorkflow.backup_config.upload_repo === "" ? "" : innerWorkflow.backup_config.upload_repo}
|
||||
onChange={(e) => {
|
||||
//setUploadRepo(e.target.value);
|
||||
innerWorkflow.backup_config.upload_repo = e.target.value
|
||||
setInnerWorkflow(innerWorkflow)
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: classes.notchedOutline,
|
||||
},
|
||||
style: {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</Grid>
|
||||
<Grid item xs={6} style={{}}>
|
||||
<span>
|
||||
<Typography>Branch</Typography>
|
||||
<TextField
|
||||
required
|
||||
style={{
|
||||
flex: "1",
|
||||
marginTop: "5px",
|
||||
marginRight: "15px",
|
||||
}}
|
||||
fullWidth={true}
|
||||
type="name"
|
||||
id="outlined-with-placeholder"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
multiline={true}
|
||||
rows={1}
|
||||
placeholder="The branch to use"
|
||||
defaultValue={innerWorkflow.backup_config === undefined || innerWorkflow.backup_config.upload_branch === undefined || innerWorkflow.backup_config.upload_branch === null || innerWorkflow.backup_config.upload_branch === "" ? "" : innerWorkflow.backup_config.upload_branch}
|
||||
onChange={(e) => {
|
||||
innerWorkflow.backup_config.upload_branch = e.target.value
|
||||
setInnerWorkflow(innerWorkflow)
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: classes.notchedOutline,
|
||||
},
|
||||
style: {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container style={{ }} spacing={2}>
|
||||
<Grid item xs={6} style={{}}>
|
||||
<span>
|
||||
<Typography>Username</Typography>
|
||||
<TextField
|
||||
required
|
||||
style={{
|
||||
flex: "1",
|
||||
marginTop: "5px",
|
||||
marginRight: "15px",
|
||||
}}
|
||||
fullWidth={true}
|
||||
type="name"
|
||||
multiline={true}
|
||||
rows={1}
|
||||
id="outlined-with-placeholder"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
placeholder="The username to use"
|
||||
defaultValue={innerWorkflow.backup_config === undefined || innerWorkflow.backup_config.upload_username === undefined || innerWorkflow.backup_config.upload_username === null || innerWorkflow.backup_config.upload_username === "" ? "" : innerWorkflow.backup_config.upload_username}
|
||||
onChange={(e) => {
|
||||
innerWorkflow.backup_config.upload_username = e.target.value
|
||||
setInnerWorkflow(innerWorkflow)
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: classes.notchedOutline,
|
||||
},
|
||||
style: {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</Grid>
|
||||
<Grid item xs={6} style={{}}>
|
||||
<span>
|
||||
<Typography>Git token/password</Typography>
|
||||
<TextField
|
||||
required
|
||||
style={{
|
||||
flex: "1",
|
||||
marginTop: "5px",
|
||||
marginRight: "15px",
|
||||
}}
|
||||
fullWidth={true}
|
||||
id="outlined-with-placeholder"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
multiline={true}
|
||||
rows={1}
|
||||
placeholder="The API token to use"
|
||||
defaultValue={innerWorkflow.backup_config === undefined || innerWorkflow.backup_config.upload_token === undefined || innerWorkflow.backup_config.upload_token === null || innerWorkflow.backup_config.upload_token === "" ? "" : innerWorkflow.backup_config.upload_token}
|
||||
onChange={(e) => {
|
||||
innerWorkflow.backup_config.upload_token = e.target.value
|
||||
setInnerWorkflow(innerWorkflow)
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: classes.notchedOutline,
|
||||
},
|
||||
style: {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
type="password"
|
||||
/>
|
||||
</span>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</span>
|
||||
: null}
|
||||
|
||||
|
||||
<Tooltip color="primary" title={"Add more details"} placement="top">
|
||||
<IconButton
|
||||
style={{ color: "white", margin: "auto", textAlign: "center", width: 50, marginTop: 50, }}
|
||||
<Button
|
||||
style={{ margin: "auto", marginTop: 50, textAlign: "center", }}
|
||||
variant="outlined"
|
||||
onClick={() => {
|
||||
setShowMoreClicked(!showMoreClicked);
|
||||
}}
|
||||
>
|
||||
{showMoreClicked ? <ExpandLessIcon /> : <ExpandMoreIcon/>}
|
||||
</IconButton>
|
||||
{showMoreClicked ? "Collapse": "Expand"}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { toast } from "react-toastify";
|
||||
import theme from "../theme.jsx";
|
||||
import { BrowserView, MobileView } from "react-device-detect";
|
||||
@@ -84,6 +84,15 @@ const Header = (props) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const topbar = localStorage.getItem("topbar_closed")
|
||||
if (topbar === "true") {
|
||||
setShowTopbar(false)
|
||||
} else {
|
||||
setShowTopbar(true)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
setAnchorElAvatar(null);
|
||||
@@ -1590,17 +1599,17 @@ const Header = (props) => {
|
||||
|
||||
navigate("/training")
|
||||
|
||||
//if (window.drift !== undefined) {
|
||||
// window.drift.api.startInteraction({ interactionId: 341911 })
|
||||
//} else {
|
||||
// console.log("Couldn't find drift in window.drift and not .drift-open-chat with querySelector: ", window.drift)
|
||||
//}
|
||||
}} style={{ cursor: "pointer", textDecoration: "none", color: "rgba(255,255,255,0.8)" }}>
|
||||
Public Training!
|
||||
</span>
|
||||
</u>
|
||||
</Typography>
|
||||
<IconButton color="secondary" style={{ position: "absolute", top: -3, right: 20, }} onClick={(event) => { setShowTopbar(false) }}>
|
||||
<IconButton color="secondary" style={{ position: "absolute", top: -3, right: 20, }} onClick={(event) => {
|
||||
setShowTopbar(false)
|
||||
|
||||
// Set storage that it's clicked
|
||||
localStorage.setItem("topbar_closed", "true")
|
||||
}}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
|
||||
@@ -55,6 +55,10 @@ const OrgHeaderexpanded = (props) => {
|
||||
globalUrl,
|
||||
isCloud,
|
||||
adminTab,
|
||||
|
||||
selectedStatus,
|
||||
setSelectedStatus,
|
||||
isEditOrgTab
|
||||
} = props;
|
||||
|
||||
const classes = useStyles();
|
||||
@@ -169,6 +173,11 @@ const OrgHeaderexpanded = (props) => {
|
||||
: selectedOrganization.sso_config.openid_token
|
||||
)
|
||||
|
||||
const [uploadRepo, setUploadRepo] = React.useState(selectedOrganization.defaults === undefined ? "" : selectedOrganization.defaults.workflow_upload_repo === undefined || selectedOrganization.defaults.workflow_upload_repo.length === 0 ? "" : selectedOrganization.defaults.workflow_upload_repo)
|
||||
const [uploadBranch, setUploadBranch] = React.useState(selectedOrganization.defaults === undefined ? defaultBranch : selectedOrganization.defaults.workflow_upload_branch === undefined || selectedOrganization.defaults.workflow_upload_branch.length === 0 ? defaultBranch : selectedOrganization.defaults.workflow_upload_branch)
|
||||
const [uploadUsername, setUploadUsername] = React.useState(selectedOrganization.defaults === undefined ? "" : selectedOrganization.defaults.workflow_upload_username === undefined || selectedOrganization.defaults.workflow_upload_username.length === 0 ? "" : selectedOrganization.defaults.workflow_upload_username)
|
||||
const [uploadToken, setUploadToken] = React.useState(selectedOrganization.defaults === undefined ? "" : selectedOrganization.defaults.workflow_upload_token === undefined || selectedOrganization.defaults.workflow_upload_token.length === 0 ? "" : selectedOrganization.defaults.workflow_upload_token)
|
||||
|
||||
const [workflows, setWorkflows] = React.useState([])
|
||||
const [workflow, setWorkflow] = React.useState({})
|
||||
|
||||
@@ -299,7 +308,6 @@ const OrgHeaderexpanded = (props) => {
|
||||
client_secret: openidClientSecret,
|
||||
openid_authorization: openidAuthorization,
|
||||
openid_token: openidToken,
|
||||
SSORequired: SSORequired
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -311,16 +319,7 @@ const OrgHeaderexpanded = (props) => {
|
||||
);
|
||||
|
||||
const toggleBetweenRequiredOrOptional = (event) => {
|
||||
if (ssoEntrypoint === "" && openidAuthorization === "" && openidToken === "") {
|
||||
if (!SSORequired) {
|
||||
toast.error("Please fill in fields for either OpenID connect or SSO before continuing. ")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
toast.info("Toggled SSO. Remember to save.")
|
||||
}
|
||||
|
||||
setSSORequired(event.target.checked)
|
||||
setSSORequired(event.target.checked);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -512,16 +511,164 @@ const OrgHeaderexpanded = (props) => {
|
||||
/>
|
||||
</span>
|
||||
</Grid>
|
||||
{/* {isCloud ? null : */}
|
||||
<Typography variant="h4" style={{ textAlign: "left", marginTop: 75, marginLeft: 20, }}>Single Signon - SAML/OpenID</Typography>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', marginTop: 20, marginLeft: 10, width: '100%', borderBottom: '1px solid #414347' }}>
|
||||
<Typography variant="body1" style={{ margin: '5px 0px 5px 10px' }}>Make SAML SSO or OpenID Authentication required/optional for your organization. Tip: Do not make it required until you have tested the URL directly.</Typography>
|
||||
|
||||
<Grid item xs={12} style={{ marginTop: 50, }}>
|
||||
<Typography variant="h4" style={{ textAlign: "left", }}>Workflow Backup Repository</Typography>
|
||||
<Typography variant="body2" style={{ textAlign: "left", marginTop: 5, }} color="textSecondary">
|
||||
Decide where workflows are backed up in a Git repository. Will create logs and notifications if upload fails. The repository and branch must already have been initialized. Files will show up in the root folder in the format 'status_workflowid.json'
|
||||
</Typography>
|
||||
<Grid container style={{ marginTop: 10, }} spacing={2}>
|
||||
<Grid item xs={6} style={{}}>
|
||||
<span>
|
||||
<Typography>Repository for workflow backup</Typography>
|
||||
<TextField
|
||||
required
|
||||
style={{
|
||||
flex: "1",
|
||||
marginTop: "5px",
|
||||
marginRight: "15px",
|
||||
backgroundColor: isEditOrgTab ? "rgba(33, 33, 33, 1)" : theme.palette.inputColor,
|
||||
}}
|
||||
fullWidth={true}
|
||||
type="name"
|
||||
multiline={true}
|
||||
rows={1}
|
||||
id="outlined-with-placeholder"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
placeholder="Ex: github/com/shuffle/workflowbackup "
|
||||
value={uploadRepo}
|
||||
onChange={(e) => {
|
||||
setUploadRepo(e.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: isEditOrgTab ? null : classes.notchedOutline,
|
||||
},
|
||||
style: {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</Grid>
|
||||
<Grid item xs={6} style={{}}>
|
||||
<span>
|
||||
<Typography>Branch</Typography>
|
||||
<TextField
|
||||
required
|
||||
style={{
|
||||
flex: "1",
|
||||
marginTop: "5px",
|
||||
marginRight: "15px",
|
||||
backgroundColor: isEditOrgTab ? "rgba(33, 33, 33, 1)" : theme.palette.inputColor,
|
||||
}}
|
||||
fullWidth={true}
|
||||
type="name"
|
||||
id="outlined-with-placeholder"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
multiline={true}
|
||||
rows={1}
|
||||
placeholder="The branch to use for backup of workflows"
|
||||
value={uploadBranch}
|
||||
onChange={(e) => {
|
||||
setUploadBranch(e.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: isEditOrgTab ? null : classes.notchedOutline,
|
||||
},
|
||||
style: {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container style={{ marginTop: 10, }} spacing={2}>
|
||||
<Grid item xs={6} style={{}}>
|
||||
<span>
|
||||
<Typography>Username</Typography>
|
||||
<TextField
|
||||
required
|
||||
style={{
|
||||
flex: "1",
|
||||
marginTop: "5px",
|
||||
marginRight: "15px",
|
||||
backgroundColor: isEditOrgTab ? "rgba(33, 33, 33, 1)" : theme.palette.inputColor,
|
||||
}}
|
||||
fullWidth={true}
|
||||
type="name"
|
||||
multiline={true}
|
||||
rows={1}
|
||||
id="outlined-with-placeholder"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
placeholder="The username to use for backup of workflows"
|
||||
value={uploadUsername}
|
||||
onChange={(e) => {
|
||||
setUploadUsername(e.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: isEditOrgTab ? null : classes.notchedOutline,
|
||||
},
|
||||
style: {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</Grid>
|
||||
<Grid item xs={6} style={{}}>
|
||||
<span>
|
||||
<Typography>Git token/password</Typography>
|
||||
<TextField
|
||||
required
|
||||
style={{
|
||||
flex: "1",
|
||||
marginTop: "5px",
|
||||
marginRight: "15px",
|
||||
backgroundColor: isEditOrgTab ? "rgba(33, 33, 33, 1)" : theme.palette.inputColor,
|
||||
}}
|
||||
fullWidth={true}
|
||||
id="outlined-with-placeholder"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
multiline={true}
|
||||
rows={1}
|
||||
placeholder="The token to use for backup of workflows. PS: This will be stored in cleartext in the database for now."
|
||||
value={uploadToken}
|
||||
onChange={(e) => {
|
||||
setUploadToken(e.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: isEditOrgTab ? null : classes.notchedOutline,
|
||||
},
|
||||
style: {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
type="password"
|
||||
/>
|
||||
</span>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Typography variant="h4" style={{ marginLeft: 20, paddingTop: 100, borderTop: "1px solid rgba(255, 255, 255, 0.12)", width: "100%", marginTop: 50, }}>
|
||||
SSO Configuration
|
||||
</Typography>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', marginLeft: 10, width: '100%', }}>
|
||||
<Typography variant="body2" color="textSecondary" style={{ margin: '5px 0px 5px 10px' }}>Make SAML SSO or OpenID Authentication Required or Optional for Your Organization.</Typography>
|
||||
<div>
|
||||
<Switch
|
||||
checked={SSORequired}
|
||||
onChange={(e) => {
|
||||
toggleBetweenRequiredOrOptional(e)
|
||||
}}
|
||||
onChange={toggleBetweenRequiredOrOptional}
|
||||
name="onOffSwitch"
|
||||
color="primary"
|
||||
title="Make SAML SSO or OpenID Authentication Required or Optional for Your Organization"
|
||||
@@ -529,10 +676,8 @@ const OrgHeaderexpanded = (props) => {
|
||||
{SSORequired ? 'Required' : 'Optional'}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
</div>
|
||||
<Grid item xs={12} style={{}}>
|
||||
<Typography variant="h6" style={{ textAlign: "left", }}>OpenID connect</Typography>
|
||||
<Typography variant="h6" style={{ textAlign: "center", }}>OpenID connect</Typography>
|
||||
<Grid container style={{ marginTop: 10, }}>
|
||||
<Grid item xs={6} style={{}}>
|
||||
<span>
|
||||
@@ -549,6 +694,11 @@ const OrgHeaderexpanded = (props) => {
|
||||
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"
|
||||
@@ -583,6 +733,11 @@ const OrgHeaderexpanded = (props) => {
|
||||
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"
|
||||
@@ -677,7 +832,7 @@ const OrgHeaderexpanded = (props) => {
|
||||
{/* } */}
|
||||
{/*isCloud ? null : */}
|
||||
<Grid item xs={12} style={{ marginTop: 50, }}>
|
||||
<Typography variant="h4" style={{ textAlign: "center", }}>SAML SSO (v1.1)</Typography>
|
||||
<Typography variant="h6" style={{ textAlign: "center", }}>SAML SSO (v1.1)</Typography>
|
||||
<Grid container style={{ marginTop: 20, }}>
|
||||
<Grid item xs={6} style={{}}>
|
||||
<span>
|
||||
@@ -694,6 +849,11 @@ const OrgHeaderexpanded = (props) => {
|
||||
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"
|
||||
|
||||
@@ -167,6 +167,7 @@ const ParsedAction = (props) => {
|
||||
|
||||
listCache,
|
||||
|
||||
authGroups,
|
||||
apps,
|
||||
setEditorData,
|
||||
setcodedata,
|
||||
@@ -1123,6 +1124,65 @@ const ParsedAction = (props) => {
|
||||
return helperText
|
||||
}
|
||||
|
||||
const analyzeFields = () => {
|
||||
|
||||
if (selectedAction === undefined || selectedAction === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (selectedActionParameters === undefined || selectedActionParameters === null || selectedActionParameters.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Only shuffle tools for now
|
||||
if (selectedAction.app_name !== "Shuffle Tools") {
|
||||
return null
|
||||
}
|
||||
|
||||
// Custom rules
|
||||
if (selectedAction.name === "set_cache_value") {
|
||||
var actionKey = ""
|
||||
var actionValue = ""
|
||||
for (let [key,keyval] in Object.entries(selectedActionParameters)) {
|
||||
const param = selectedActionParameters[key]
|
||||
if (param.name === "key") {
|
||||
actionKey = param.value
|
||||
}
|
||||
|
||||
if (param.name === "value") {
|
||||
actionValue = param.value
|
||||
}
|
||||
}
|
||||
|
||||
if (actionKey === "" || actionValue === "") {
|
||||
return null
|
||||
}
|
||||
|
||||
if (!actionKey.includes(".#") && actionValue.includes(".#")) {
|
||||
return <span>When the key ({actionKey}) is static, but the value is a list ({actionValue}), it will overwrite the list. You may be looking for the <span onClick={() => {}} style={{cursor: "pointer", color: "#f85a3e", }}>Check Cache Contains</span> action instead.</span>
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
|
||||
}
|
||||
|
||||
const suggestionInfo = () => {
|
||||
const suggestionText = analyzeFields()
|
||||
if (suggestionText === undefined || suggestionText === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (selectedAction.errors === undefined || selectedAction.errors === null || selectedAction.errors.length === 0) {
|
||||
selectedAction.errors = ["Suggestion: " + suggestionText]
|
||||
}
|
||||
|
||||
return <Paper style={{padding: 10, backgroundColor: theme.palette.surfaceColor, border: "1px solid red",}}>
|
||||
<Typography variant="body" style={{color: "white", }}>
|
||||
<b>Tip:</b> {suggestionText}
|
||||
</Typography>
|
||||
</Paper>
|
||||
}
|
||||
|
||||
// FIXME: Issue #40 - selectedActionParameters not reset
|
||||
if (Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters.length > 0) {
|
||||
@@ -1310,6 +1370,7 @@ const ParsedAction = (props) => {
|
||||
</Button>
|
||||
</Tooltip>
|
||||
}
|
||||
|
||||
{selectedAction.template === true && selectedAction.matching_actions !== undefined && selectedAction.matching_actions !== null && selectedAction.matching_actions.length > 0 ?
|
||||
<div>
|
||||
<Typography variant="body1">
|
||||
@@ -1441,6 +1502,8 @@ const ParsedAction = (props) => {
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{suggestionInfo()}
|
||||
|
||||
{selectedActionParameters.map((data, count) => {
|
||||
if (data.variant === "") {
|
||||
data.variant = "STATIC_VALUE";
|
||||
@@ -3606,6 +3669,7 @@ const ParsedAction = (props) => {
|
||||
}}
|
||||
labelId="select-app-auth"
|
||||
value={
|
||||
selectedAction.authentication_id === "authgroups" ? "authgroups" :
|
||||
Object.getOwnPropertyNames(selectedAction.selectedAuthentication).length === 0
|
||||
? "No selection"
|
||||
: selectedAction.selectedAuthentication
|
||||
@@ -3617,6 +3681,8 @@ const ParsedAction = (props) => {
|
||||
}}
|
||||
fullWidth
|
||||
onChange={(e) => {
|
||||
console.log("AUTH CHANGE: ", e.target.value)
|
||||
|
||||
if (e.target.value === "No selection") {
|
||||
selectedAction.selectedAuthentication = {};
|
||||
selectedAction.authentication_id = "";
|
||||
@@ -3628,13 +3694,36 @@ const ParsedAction = (props) => {
|
||||
}
|
||||
}
|
||||
setSelectedAction(selectedAction);
|
||||
setUpdate(Math.random());
|
||||
setUpdate(Math.random())
|
||||
|
||||
} else if (e.target.value === "authgroups") {
|
||||
if (authGroups !== undefined && authGroups !== null && authGroups.length === 0) {
|
||||
toast("No auth groups created. Opening window to create one")
|
||||
|
||||
setTimeout(() => {
|
||||
window.open("/admin?tab=app_auth", "_blank")
|
||||
}, 2500)
|
||||
} else {
|
||||
selectedAction.selectedAuthentication = {};
|
||||
selectedAction.authentication_id = "authgroups"
|
||||
|
||||
for (let [key,keyval] in Object.entries(selectedAction.parameters)) {
|
||||
//console.log(selectedAction.parameters[key])
|
||||
if (selectedAction.parameters[key].configuration) {
|
||||
selectedAction.parameters[key].value = "authgroup controlled"
|
||||
}
|
||||
}
|
||||
|
||||
setSelectedAction(selectedAction)
|
||||
setUpdate(Math.random())
|
||||
}
|
||||
} else {
|
||||
selectedAction.selectedAuthentication = e.target.value;
|
||||
selectedAction.authentication_id = e.target.value.id;
|
||||
setSelectedAction(selectedAction);
|
||||
setUpdate(Math.random());
|
||||
}
|
||||
|
||||
}}
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
@@ -3653,6 +3742,7 @@ const ParsedAction = (props) => {
|
||||
>
|
||||
<em>No selection</em>
|
||||
</MenuItem>
|
||||
|
||||
{selectedAction.authentication.map((data) => {
|
||||
if (data.last_modified === true) {
|
||||
//console.log("LAST MODIFIED: ", data.label)
|
||||
@@ -3689,6 +3779,20 @@ const ParsedAction = (props) => {
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
|
||||
|
||||
<Divider style={{marginTop: 10, marginBottom: 10, }}/>
|
||||
|
||||
<MenuItem
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
color: "white",
|
||||
}}
|
||||
value="authgroups"
|
||||
>
|
||||
<em>Auth Groups</em>
|
||||
</MenuItem>
|
||||
|
||||
</Select>
|
||||
|
||||
{/*
|
||||
|
||||
@@ -627,8 +627,8 @@ const CodeEditor = (props) => {
|
||||
newMarkers.push({
|
||||
startRow: i,
|
||||
startCol: startCh,
|
||||
endRow: i+1,
|
||||
endCol: endCh+1,
|
||||
endRow: i,
|
||||
endCol: endCh,
|
||||
className: correctVariable ? "good-marker" : "bad-marker",
|
||||
type: "text",
|
||||
})
|
||||
|
||||
@@ -133,6 +133,11 @@ const data = [
|
||||
"background-gradient-stop-colors": "data(fillGradient)",
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: `node[?parent_controlled]`,
|
||||
css: {
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: `node[app_name="Testing"]`,
|
||||
css: {
|
||||
|
||||
+296
-163
@@ -166,7 +166,9 @@ const Admin = (props) => {
|
||||
//console.log("Selected: ", selectedOrganization)
|
||||
const [appAuthenticationGroupModalOpen , setAppAuthenticationGroupModalOpen] = React.useState(false);
|
||||
const [appsForAppAuthGroup, setAppsForAppAuthGroup] = React.useState([]);
|
||||
const [appAuthenticationGroupId, setAppAuthenticationGroupId] = React.useState("");
|
||||
const [appAuthenticationGroupName, setAppAuthenticationGroupName] = React.useState("");
|
||||
const [appAuthenticationGroupEnvironment, setAppAuthenticationGroupEnvironment] = React.useState("");
|
||||
const [appAuthenticationGroupDescription, setAppAuthenticationGroupDescription] = React.useState("");
|
||||
const [appAuthenticationGroups, setAppAuthenticationGroups] = React.useState([]);
|
||||
const [organizationFeatures, setOrganizationFeatures] = React.useState({});
|
||||
@@ -431,23 +433,62 @@ const Admin = (props) => {
|
||||
});
|
||||
};
|
||||
|
||||
const createAppAuthenticationGroup = (name, description, appAuthIds) => {
|
||||
const deleteAppAuthenticationGroup = (appAuthGroupId) => {
|
||||
const url = `${globalUrl}/api/v1/authentication/group/${appAuthGroupId}`
|
||||
fetch(url, {
|
||||
method: "DELETE",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for deleting app auth group");
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (responseJson.success === false) {
|
||||
toast("Failed to delete app authentication group");
|
||||
} else {
|
||||
toast("App authentication group deleted")
|
||||
getAppAuthenticationGroups()
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
toast(error.toString())
|
||||
})
|
||||
}
|
||||
|
||||
const createAppAuthenticationGroup = (name, environment, description, appAuthIds) => {
|
||||
// Makes list of ids into a full-on list of auth, but just with the ID
|
||||
// The backend fills in the rest
|
||||
console.log("INput auth: ", appAuthIds)
|
||||
let app_auths = appAuthIds.map((appAuthId) => {
|
||||
return { id: appAuthId };
|
||||
})
|
||||
|
||||
fetch(globalUrl + "/api/v1/apps/authentication/group", {
|
||||
var parsedAppGroup = {
|
||||
label: name,
|
||||
environment: environment,
|
||||
description: description,
|
||||
app_auths: app_auths
|
||||
}
|
||||
|
||||
if (appAuthenticationGroupId !== undefined && appAuthenticationGroupId !== null && appAuthenticationGroupId !== "") {
|
||||
parsedAppGroup.id = appAuthenticationGroupId
|
||||
}
|
||||
|
||||
fetch(globalUrl + "/api/v1/authentication/group", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
body: JSON.stringify({
|
||||
label: name,
|
||||
description: description,
|
||||
app_auths: app_auths
|
||||
}),
|
||||
body: JSON.stringify(parsedAppGroup),
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
@@ -457,8 +498,15 @@ const Admin = (props) => {
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
// getAppAuthenticationGroups();
|
||||
toast("App authentication group created");
|
||||
if (responseJson.success === false) {
|
||||
toast("Failed to create. Please try again, or contact support@shuffler.io")
|
||||
} else {
|
||||
// Close the modal
|
||||
setAppAuthenticationGroupModalOpen(false)
|
||||
|
||||
toast("App authentication group created")
|
||||
getAppAuthenticationGroups()
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
toast(error.toString());
|
||||
@@ -805,7 +853,7 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
.then((responseJson) => {
|
||||
setWebHooks(responseJson.webhooks || []); // Handling the case where the result is null or undefined
|
||||
setAllSchedules(responseJson.schedules || []);
|
||||
setPipelines(responseJson.pipelines || []);
|
||||
// setPipelines(responseJson.pipelines || []);
|
||||
})
|
||||
.catch((error) => {
|
||||
// toast(error.toString());
|
||||
@@ -990,13 +1038,11 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
}
|
||||
|
||||
const data = {
|
||||
command: pipeline.command,
|
||||
name: pipeline.name,
|
||||
type: state,
|
||||
environment: pipeline.environment,
|
||||
workflow_id: pipeline.workflow_id,
|
||||
trigger_id: pipeline.trigger_id,
|
||||
start_node: pipeline.start_node,
|
||||
};
|
||||
|
||||
if (state === "start") toast("starting the pipeline");
|
||||
@@ -1027,7 +1073,6 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
if (state === "start") toast("Successfully created pipeline");
|
||||
else toast("Sucessfully stopped the pipeline");
|
||||
}
|
||||
setTimeout(handleGetAllTriggers, 1000);
|
||||
})
|
||||
.catch((error) => {
|
||||
//toast(error.toString());
|
||||
@@ -2093,10 +2138,10 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
};
|
||||
|
||||
const getAppAuthenticationGroups = () => {
|
||||
console.log("DEBUG: Skipping app auth group loading")
|
||||
return
|
||||
//console.log("DEBUG: Skipping app auth group loading")
|
||||
//return
|
||||
|
||||
fetch(globalUrl + "/api/v1/apps/authentication/group", {
|
||||
fetch(globalUrl + "/api/v1/authentication/group", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -4941,7 +4986,7 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
|
||||
<ListItemText>
|
||||
<Button
|
||||
style={{ marginLeft: "140px" }}
|
||||
style={{ marginLeft: "18%" }}
|
||||
variant={
|
||||
webhook.status === "running" ? "contained" : "outlined"
|
||||
}
|
||||
@@ -4963,10 +5008,10 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
</List>
|
||||
)}
|
||||
|
||||
<div style={{ marginTop: 20, marginBottom: 20 }}>
|
||||
{/* <div style={{ marginTop: 20, marginBottom: 20 }}>
|
||||
<h2 style={{ display: "inline" }}>Tenzir Pipelines</h2>
|
||||
<span style={{ marginLeft: 25 }}>
|
||||
Controls the Tenzir pipeline operations.{" "}
|
||||
Controls a pipeline to run things.{" "}
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
@@ -5013,7 +5058,7 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
primary="Workflow"
|
||||
style={{ maxWidth: 315, minWidth: 315 }}
|
||||
/>
|
||||
<ListItemText primary="Actions" style={{ marginLeft: '120px' }} />
|
||||
<ListItemText primary="Actions" />
|
||||
</ListItem>
|
||||
{pipelines.map((pipeline, index) => {
|
||||
var bgColor = "#27292d";
|
||||
@@ -5066,7 +5111,7 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
)}
|
||||
)}*/}
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
@@ -5170,31 +5215,63 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
|
||||
|
||||
const handleAppAuthGroupCheckbox = (data) => {
|
||||
let appOrginal = data.app
|
||||
if (appsForAppAuthGroup.includes(appOrginal.id)) {
|
||||
return;
|
||||
}
|
||||
//let groupApp = data.app.id
|
||||
var newappauth = appsForAppAuthGroup
|
||||
if (appsForAppAuthGroup.includes(data.app.id)) {
|
||||
newappauth = newappauth.filter((item) => item !== data.app.id)
|
||||
}
|
||||
|
||||
setAppsForAppAuthGroup([...appsForAppAuthGroup, data.id]);
|
||||
console.log("Apps for app auth group: ", appsForAppAuthGroup);
|
||||
};
|
||||
if (appsForAppAuthGroup.includes(data.id)) {
|
||||
// Remove app from app auth group
|
||||
newappauth = newappauth.filter((item) => item !== data.id)
|
||||
setAppsForAppAuthGroup(newappauth)
|
||||
return
|
||||
}
|
||||
|
||||
for (var i = 0; i < authentication.length; i++) {
|
||||
if (authentication[i].id === data.id) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (!appsForAppAuthGroup.includes(authentication[i].id)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (authentication[i].app.id === data.app.id) {
|
||||
// Remove app from app auth group
|
||||
newappauth = newappauth.filter((item) => item !== authentication[i].id)
|
||||
toast(`App ${data.app.name} is already in this group`)
|
||||
}
|
||||
}
|
||||
|
||||
setAppsForAppAuthGroup(newappauth.concat(data.id))
|
||||
}
|
||||
|
||||
const authenticationView =
|
||||
curTab === 2 ? (
|
||||
<>
|
||||
|
||||
<div>
|
||||
{/* (appAuthenticationGroupModalOpen : { */}
|
||||
{appAuthenticationGroupModalOpen && (
|
||||
<Dialog
|
||||
open={appAuthenticationGroupModalOpen}
|
||||
onClose={() => {
|
||||
setAppAuthenticationGroupModalOpen(false);
|
||||
|
||||
setAppAuthenticationGroupId("")
|
||||
setAppAuthenticationGroupName("")
|
||||
setAppAuthenticationGroupEnvironment("")
|
||||
setAppAuthenticationGroupDescription("")
|
||||
setAppsForAppAuthGroup([])
|
||||
}}
|
||||
PaperProps={{
|
||||
style: {
|
||||
backgroundColor: theme.palette.surfaceColor,
|
||||
color: "white",
|
||||
minWidth: "1200px",
|
||||
minWidth: "1000px",
|
||||
minHeight: "320px",
|
||||
padding: 25,
|
||||
paddingLeft: 50,
|
||||
},
|
||||
}}
|
||||
>
|
||||
@@ -5202,106 +5279,130 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
<span style={{ color: "white" }}>App Authentication Groups</span>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent>
|
||||
<div>
|
||||
<TextField
|
||||
color="primary"
|
||||
style={{ backgroundColor: theme.palette.inputColor }}
|
||||
autoFocus
|
||||
InputProps={{
|
||||
style: {
|
||||
height: "50px",
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
required
|
||||
fullWidth={true}
|
||||
placeholder="Name"
|
||||
id="namefield"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
onChange={(event) => {
|
||||
setAppAuthenticationGroupName(event.target.value);
|
||||
}}
|
||||
/>
|
||||
<DialogContent style={{marginLeft: 0, paddingLeft: 0, }}>
|
||||
<div style={{display: "flex", position: "sticky", top: 0, zIndex: 1, backgroundColor: theme.palette.surfaceColor, borderRadius: theme.palette.borderRadius, padding: 20, marginBottom: 10, }}>
|
||||
<div style={{marginRight: 50, minWidth: 250, }}>
|
||||
<Typography style={{marginTop: 10, }}>
|
||||
Name
|
||||
</Typography>
|
||||
<TextField
|
||||
color="primary"
|
||||
label="Name"
|
||||
style={{ backgroundColor: theme.palette.inputColor }}
|
||||
autoFocus
|
||||
InputProps={{
|
||||
style: {
|
||||
height: "50px",
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
required
|
||||
fullWidth={true}
|
||||
placeholder="Name"
|
||||
id="namefield"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
defaultValue={appAuthenticationGroupName}
|
||||
onChange={(event) => {
|
||||
setAppAuthenticationGroupName(event.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div style={{marginRight: 50, }}>
|
||||
<Typography style={{marginTop: 10, marginBottom: 10}}>
|
||||
Evironment
|
||||
</Typography>
|
||||
{environments !== undefined && environments !== null && environments.length > 0 ?
|
||||
<Select
|
||||
defaultValue={appAuthenticationGroupEnvironment === "" ? environments[0].Name : appAuthenticationGroupEnvironment}
|
||||
onChange={(e) => {
|
||||
setAppAuthenticationGroupEnvironment(e.target.value);
|
||||
}}
|
||||
>
|
||||
{environments.map((env, index) => {
|
||||
return (
|
||||
<MenuItem key={index} value={env.Name}>
|
||||
{env.Name}
|
||||
</MenuItem>
|
||||
)
|
||||
})}
|
||||
</Select>
|
||||
:
|
||||
<Typography>
|
||||
Environments failed to load. Please try again
|
||||
</Typography>
|
||||
}
|
||||
</div>
|
||||
<div>
|
||||
<TextField
|
||||
color="primary"
|
||||
style={{ backgroundColor: theme.palette.inputColor }}
|
||||
autoFocus
|
||||
InputProps={{
|
||||
style: {
|
||||
height: "50px",
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
required
|
||||
fullWidth={true}
|
||||
placeholder="Description"
|
||||
id="descriptionfield"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
onChange={(event) => {
|
||||
setAppAuthenticationGroupDescription(event.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{/* Show a check box list of all app authentications to add to the auth group */}
|
||||
<div>
|
||||
{authentication.map((data, index) => (
|
||||
<div key={index}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Tooltip
|
||||
title={data.app.name}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '10px', marginLeft: '5px' }}>
|
||||
<img
|
||||
src={data.app.large_image ? data.app.large_image : '/images/no_image.png'}
|
||||
alt=""
|
||||
style={{ width: '50px', height: '50px', marginRight: '10px' }}
|
||||
/>
|
||||
<Checkbox
|
||||
checked={data.checked}
|
||||
onChange={(event) => {
|
||||
handleAppAuthGroupCheckbox(data)
|
||||
}}
|
||||
name={data.label}
|
||||
disabled={data.app.id in appsForAppAuthGroup}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
label={data.label}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<div style={{marginTop: 65, }}>
|
||||
<Button
|
||||
style={{}}
|
||||
disabled={appAuthenticationGroupName === "" || appAuthenticationGroupEnvironment === "" || appsForAppAuthGroup.length === 0}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
createAppAuthenticationGroup(
|
||||
appAuthenticationGroupName,
|
||||
appAuthenticationGroupEnvironment,
|
||||
appAuthenticationGroupDescription,
|
||||
appsForAppAuthGroup
|
||||
appsForAppAuthGroup,
|
||||
);
|
||||
}}
|
||||
>
|
||||
Create
|
||||
Set Group
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider style={{marginTop: 10, marginBottom: 10, }}/>
|
||||
|
||||
<div style={{marginLeft: 25, }}>
|
||||
{/* Show a check box list of all app authentications to add to the auth group */}
|
||||
<div>
|
||||
{authentication.map((data, index) => {
|
||||
var checked = data.checked
|
||||
if (checked === undefined || checked === null) {
|
||||
checked = false
|
||||
}
|
||||
|
||||
if (appsForAppAuthGroup.includes(data.id)) {
|
||||
checked = true
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={index}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Tooltip
|
||||
title={data.app.name}
|
||||
placement="left"
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '10px', marginLeft: '5px', }}>
|
||||
<img
|
||||
src={data.app.large_image ? data.app.large_image : '/images/no_image.png'}
|
||||
alt=""
|
||||
style={{ borderRadius: theme.palette.borderRadius, width: 50, height: 50, marginRight: 10 }}
|
||||
/>
|
||||
<Checkbox
|
||||
checked={checked}
|
||||
onChange={(event) => {
|
||||
handleAppAuthGroupCheckbox(data)
|
||||
}}
|
||||
name={data.label}
|
||||
disabled={data.app.id in appsForAppAuthGroup}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
label={data.label}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
@@ -5311,7 +5412,7 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
<div style={{ marginTop: 20, marginBottom: 20 }}>
|
||||
<h2 style={{ display: "inline" }}>App Authentication</h2>
|
||||
<span style={{ marginLeft: 25 }}>
|
||||
Control the authentication options for individual apps.
|
||||
Control the authentication options for individual apps. App Groups are farther down on this page.
|
||||
</span>
|
||||
|
||||
<a
|
||||
@@ -5587,10 +5688,18 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
</List>
|
||||
</div>
|
||||
|
||||
{/* <div>
|
||||
<Divider
|
||||
style={{
|
||||
marginTop: 20,
|
||||
marginBottom: 20,
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
}}
|
||||
/>
|
||||
|
||||
<div style={{marginTop: 50, }}>
|
||||
<div style={{ marginTop: 20, marginBottom: 20 }}>
|
||||
<h2 style={{ display: "inline" }}>App Authentication Groups</h2>
|
||||
<span style={{ marginLeft: 25 }}>
|
||||
<h2 style={{ }}>App Authentication Groups</h2>
|
||||
<span style={{ marginLeft: 0 }}>
|
||||
Groups of authentication options for subflows.{" "}
|
||||
<a
|
||||
target="_blank"
|
||||
@@ -5602,29 +5711,39 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
</a>
|
||||
</span>
|
||||
|
||||
<Divider
|
||||
style={{
|
||||
marginTop: 20,
|
||||
marginBottom: 20,
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
<br />
|
||||
<Button
|
||||
style={{ marginTop: 20 }}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
|
||||
if (environments !== undefined && environments !== null && environments.length > 0) {
|
||||
setAppAuthenticationGroupEnvironment(environments[0].Name)
|
||||
}
|
||||
|
||||
setAppAuthenticationGroupModalOpen(true)
|
||||
}}
|
||||
/>
|
||||
<List>
|
||||
>
|
||||
Add Group
|
||||
</Button>
|
||||
|
||||
<List style={{marginTop: 25, }}>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary="Label"
|
||||
style={{ minWidth: 250, maxWidth: 250}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Environment"
|
||||
style={{ minWidth: 150, maxWidth: 150 }}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Description"
|
||||
primary="App Auth"
|
||||
style={{ minWidth: 250, maxWidth: 250 }}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Apps"
|
||||
style={{ minWidth: 250, maxWidth: 250 }}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="CreatedAt"
|
||||
primary="Created At"
|
||||
style={{ minWidth: 150, maxWidth: 150 }}
|
||||
/>
|
||||
<ListItemText
|
||||
@@ -5638,31 +5757,49 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
if (index % 2 === 0) {
|
||||
bgColor = "#1f2023";
|
||||
}
|
||||
|
||||
if (data.app_auths === undefined || data.app_auths === null) {
|
||||
data.app_auths = []
|
||||
}
|
||||
|
||||
return (
|
||||
<ListItem key={index} style={{ backgroundColor: bgColor }}>
|
||||
<ListItemText
|
||||
primary={data.label}
|
||||
style={{ minWidth: 150, maxWidth: 150 }}
|
||||
style={{ minWidth: 250, maxWidth: 250, }}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={data.description}
|
||||
style={{ minWidth: 250, maxWidth: 250 }}
|
||||
primary={data.environment}
|
||||
style={{ minWidth: 150, maxWidth: 150, }}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={
|
||||
<div style={{ display: 'flex' }}>
|
||||
{data.app_auths.map((appAuth, index) => (
|
||||
<Tooltip
|
||||
title={appAuth.app.name}
|
||||
>
|
||||
<img
|
||||
key={index}
|
||||
src={appAuth.app.large_image}
|
||||
alt={appAuth.app.name}
|
||||
style={{ width: '24px', height: '24px', marginRight: '5px' }}
|
||||
/>
|
||||
</Tooltip>
|
||||
))}
|
||||
{data.app_auths.map((appAuth, index) => {
|
||||
if (appAuth.app.large_image === undefined || appAuth.app.large_image === null || appAuth.app.large_image === "") {
|
||||
const foundImage = authentication.find((auth) => auth.app.id === appAuth.app.id)
|
||||
if (foundImage !== undefined) {
|
||||
appAuth.app.large_image = foundImage.app.large_image
|
||||
|
||||
appAuth.app.name = foundImage.app.name
|
||||
}
|
||||
}
|
||||
|
||||
const tooltip = `${appAuth.app.name.replaceAll("_", " ")} (authname: ${appAuth.label})`
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
title={tooltip}
|
||||
>
|
||||
<img
|
||||
key={index}
|
||||
src={appAuth.app.large_image}
|
||||
alt={appAuth.app.name}
|
||||
style={{ width: 30, height: 30, marginRight: 5 }}
|
||||
/>
|
||||
</Tooltip>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
style={{ minWidth: 250, maxWidth: 250 }}
|
||||
@@ -5676,16 +5813,22 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
<div style={{ display: 'flex' }}>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setAppAuthenticationGroupId(data.id)
|
||||
|
||||
setAppAuthenticationGroupName(data.label)
|
||||
setAppAuthenticationGroupDescription(data.description)
|
||||
|
||||
setAppsForAppAuthGroup(data.app_auths.map((appAuth) => appAuth.id))
|
||||
setAppAuthenticationGroupEnvironment(data.environment)
|
||||
setAppAuthenticationGroupModalOpen(true)
|
||||
}}
|
||||
disabled={true}
|
||||
>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
// deleteAppAuthenticationGroup(data);
|
||||
deleteAppAuthenticationGroup(data.id)
|
||||
}}
|
||||
disabled={true}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
@@ -5700,20 +5843,10 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
)}
|
||||
</List>
|
||||
|
||||
<Button
|
||||
style={{ marginLeft: 10 }}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setAppAuthenticationGroupModalOpen(true);
|
||||
}}
|
||||
>
|
||||
Add Group
|
||||
</Button>
|
||||
|
||||
</div>
|
||||
</div> */}
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
const getLogs = async (ip, userId) => {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -46,6 +46,7 @@ const Body = {
|
||||
height: "100%",
|
||||
color: "white",
|
||||
position: "relative",
|
||||
paddingTop: 40,
|
||||
//textAlign: "center",
|
||||
};
|
||||
|
||||
|
||||
@@ -74,6 +74,7 @@ import {
|
||||
ArrowLeft as ArrowLeftIcon,
|
||||
ArrowRight as ArrowRightIcon,
|
||||
QueryStats as QueryStatsIcon,
|
||||
Visibility as VisibilityIcon,
|
||||
} from "@mui/icons-material";
|
||||
|
||||
import { DataGrid, GridToolbar } from "@mui/x-data-grid";
|
||||
@@ -614,6 +615,7 @@ const Workflows = (props) => {
|
||||
const [videoViewOpen, setVideoViewOpen] = React.useState(false)
|
||||
const [gettingStartedItems, setGettingStartedItems] = React.useState([])
|
||||
const [selectedWorkflowIndexes, setSelectedWorkflowIndexes] = React.useState([])
|
||||
const [highlightIds, setHighlightIds] = React.useState([])
|
||||
|
||||
const [apps, setApps] = React.useState([]);
|
||||
|
||||
@@ -1213,7 +1215,24 @@ const Workflows = (props) => {
|
||||
}
|
||||
|
||||
setFirstLoad(false)
|
||||
}, 100)
|
||||
}, 250)
|
||||
|
||||
/*
|
||||
setTimeout(() => {
|
||||
var timeout = 0
|
||||
for (var key in newarray) {
|
||||
const wf = newarray[key]
|
||||
if (wf.actions === undefined || wf.actions === null || wf.actions.length === 0) {
|
||||
setTimeout(() => {
|
||||
sideloadWorkflow(wf.id, false)
|
||||
}, timeout)
|
||||
|
||||
timeout += 1000
|
||||
}
|
||||
|
||||
}
|
||||
}, 1000)
|
||||
*/
|
||||
|
||||
} else {
|
||||
if (isLoggedIn) {
|
||||
@@ -1637,20 +1656,24 @@ const Workflows = (props) => {
|
||||
});
|
||||
};
|
||||
|
||||
const copyWorkflow = (data) => {
|
||||
data = JSON.parse(JSON.stringify(data));
|
||||
toast("Copying workflow " + data.name);
|
||||
data.id = "";
|
||||
data.name = data.name + "_copy";
|
||||
data = deduplicateIds(data, true);
|
||||
const duplicateWorkflow = (data) => {
|
||||
//data = JSON.parse(JSON.stringify(data));
|
||||
toast("Copying workflow '" + data.name + "'. The new workflow will load in and be highlighted.");
|
||||
//data.id = "";
|
||||
//data.name = data.name + "_copy";
|
||||
//data = deduplicateIds(data, true);
|
||||
|
||||
fetch(globalUrl + "/api/v1/workflows", {
|
||||
const duplicateData = {
|
||||
name: data.name + "_copy",
|
||||
}
|
||||
|
||||
fetch(`${globalUrl}/api/v1/workflows/${data.id}/duplicate`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
body: JSON.stringify(duplicateData),
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
@@ -1660,7 +1683,20 @@ const Workflows = (props) => {
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(() => {
|
||||
.then((responseJson) => {
|
||||
if (responseJson.success === false) {
|
||||
if (responseJson.reason !== undefined) {
|
||||
toast("Failed copying workflow: " + responseJson.reason)
|
||||
} else {
|
||||
toast("Failed copying workflow")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (responseJson.id !== undefined) {
|
||||
setHighlightIds([responseJson.id])
|
||||
}
|
||||
setTimeout(() => {
|
||||
getAvailableWorkflows();
|
||||
}, 1000);
|
||||
@@ -1689,15 +1725,68 @@ const Workflows = (props) => {
|
||||
})
|
||||
}
|
||||
|
||||
const sideloadWorkflow = (id, openEdit) => {
|
||||
const exportSingleWorkflow = (data, setOpen) => {
|
||||
setExportModalOpen(true)
|
||||
|
||||
if (data.triggers !== null && data.triggers !== undefined) {
|
||||
var newSubflows = [];
|
||||
for (var key in data.triggers) {
|
||||
const trigger = data.triggers[key];
|
||||
|
||||
if (
|
||||
trigger.parameters !== null &&
|
||||
trigger.parameters !== undefined
|
||||
) {
|
||||
for (var subkey in trigger.parameters) {
|
||||
const param = trigger.parameters[subkey];
|
||||
if (
|
||||
param.name === "workflow" &&
|
||||
param.value !== data.id &&
|
||||
!newSubflows.includes(param.value)
|
||||
) {
|
||||
newSubflows.push(param.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var parsedworkflows = [];
|
||||
for (var key in newSubflows) {
|
||||
const foundWorkflow = workflows.find(
|
||||
(workflow) => workflow.id === newSubflows[key]
|
||||
);
|
||||
if (foundWorkflow !== undefined && foundWorkflow !== null) {
|
||||
parsedworkflows.push(foundWorkflow);
|
||||
}
|
||||
}
|
||||
|
||||
if (parsedworkflows.length > 0) {
|
||||
console.log(
|
||||
"Appending subflows during export: ",
|
||||
parsedworkflows.length
|
||||
);
|
||||
data.subflows = parsedworkflows;
|
||||
}
|
||||
}
|
||||
|
||||
setExportData(data)
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const sideloadWorkflow = (id, action, setOpen) => {
|
||||
|
||||
const storagewf = localStorage.getItem("workflows")
|
||||
const storageWorkflows = JSON.parse(storagewf)
|
||||
if (storageWorkflows === null || storageWorkflows === undefined || storageWorkflows.length === 0) {
|
||||
} else {
|
||||
for (var i = 0; i < storageWorkflows.length; i++) {
|
||||
if (storageWorkflows[i].id === id) {
|
||||
if (storageWorkflows[i].image !== "") {
|
||||
return
|
||||
if (storageWorkflows[i].image !== "" && storageWorkflows[i].image !== undefined && storageWorkflows[i].image !== null) {
|
||||
|
||||
if (action === undefined || action === null || action === "") {
|
||||
console.log("RETURNING")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1718,8 +1807,16 @@ const Workflows = (props) => {
|
||||
return response.json()
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (openEdit) {
|
||||
setEditing(responseJson)
|
||||
if (responseJson.success !== false && responseJson.id !== undefined) {
|
||||
if (action === "edit") {
|
||||
setEditing(responseJson)
|
||||
} else if (action === "publish") {
|
||||
setPublishModalOpen(true)
|
||||
setSelectedWorkflow(responseJson)
|
||||
} else if (action === "export") {
|
||||
exportSingleWorkflow(responseJson, setOpen)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for (var i = 0; i < storageWorkflows.length; i++) {
|
||||
@@ -1730,8 +1827,9 @@ const Workflows = (props) => {
|
||||
}
|
||||
}
|
||||
|
||||
setWorkflows(storageWorkflows)
|
||||
//setFilteredWorkflows(storageWorkflows)
|
||||
//setWorkflows(storageWorkflows)
|
||||
setFilteredWorkflows(storageWorkflows)
|
||||
//setUpdate(Math.random())
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error.toString())
|
||||
@@ -1878,7 +1976,9 @@ const Workflows = (props) => {
|
||||
const appGroup = getWorkflowAppgroup(data)
|
||||
const [triggers, subflows] = getWorkflowMeta(data)
|
||||
|
||||
const isDistributed = data.suborg_distribution !== undefined && data.suborg_distribution !== null && data.suborg_distribution.includes(userdata.active_org.id)
|
||||
const hasSuborgs = data.suborg_distribution !== undefined && data.suborg_distribution !== null && data.suborg_distribution.length > 0
|
||||
const isDistributed = (data.parentorg_workflow !== undefined && data.parentorg_workflow !== null && data.parentorg_workflow.length > 0) //|| (data.org_id !== userdata.active_org.id && data.org_id !== undefined && data.org_id !== null && data.org_id.length > 0)
|
||||
|
||||
const workflowMenuButtons = (
|
||||
<Menu
|
||||
id="long-menu"
|
||||
@@ -1890,8 +1990,20 @@ const Workflows = (props) => {
|
||||
setAnchorEl(null);
|
||||
}}
|
||||
>
|
||||
{isDistributed ?
|
||||
<MenuItem
|
||||
style={{ backgroundColor: theme.palette.inputColor, color: "white" }}
|
||||
onClick={() => {
|
||||
navigate(`/workflows/${data.id}`)
|
||||
}}
|
||||
>
|
||||
<VisibilityIcon style={{ marginLeft: 0, marginRight: 8 }} />
|
||||
Explore Workflow
|
||||
</MenuItem>
|
||||
: null}
|
||||
<MenuItem
|
||||
style={{ backgroundColor: theme.palette.inputColor, color: "white" }}
|
||||
disabled={isDistributed}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
if (data.actions !== undefined && data.actions !== null && data.actions.length > 0 && data.image !== "") {
|
||||
@@ -1899,7 +2011,9 @@ const Workflows = (props) => {
|
||||
|
||||
} else {
|
||||
//toast("Need to side-load workflow to be edited properly")
|
||||
sideloadWorkflow(data.id, true)
|
||||
sideloadWorkflow(data.id, "edit")
|
||||
|
||||
toast.info("Loading full workflow for editing. Please wait...")
|
||||
}
|
||||
}}
|
||||
key={"change"}
|
||||
@@ -1909,9 +2023,11 @@ const Workflows = (props) => {
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
style={{ backgroundColor: theme.palette.inputColor, color: "white" }}
|
||||
disabled={isDistributed}
|
||||
onClick={() => {
|
||||
setSelectedWorkflow(data);
|
||||
setPublishModalOpen(true);
|
||||
sideloadWorkflow(data.id, "publish")
|
||||
|
||||
toast.info("Loading full workflow for publishing. Please wait...")
|
||||
}}
|
||||
key={"publish"}
|
||||
>
|
||||
@@ -1920,9 +2036,10 @@ const Workflows = (props) => {
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
style={{ backgroundColor: theme.palette.inputColor, color: "white" }}
|
||||
disabled={isDistributed}
|
||||
onClick={() => {
|
||||
copyWorkflow(data);
|
||||
setOpen(false);
|
||||
duplicateWorkflow(data)
|
||||
setOpen(false)
|
||||
}}
|
||||
key={"duplicate"}
|
||||
>
|
||||
@@ -1931,52 +2048,11 @@ const Workflows = (props) => {
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
style={{ backgroundColor: theme.palette.inputColor, color: "white" }}
|
||||
disabled={isDistributed}
|
||||
onClick={() => {
|
||||
setExportModalOpen(true);
|
||||
sideloadWorkflow(data.id, "export", setOpen)
|
||||
|
||||
if (data.triggers !== null && data.triggers !== undefined) {
|
||||
var newSubflows = [];
|
||||
for (var key in data.triggers) {
|
||||
const trigger = data.triggers[key];
|
||||
|
||||
if (
|
||||
trigger.parameters !== null &&
|
||||
trigger.parameters !== undefined
|
||||
) {
|
||||
for (var subkey in trigger.parameters) {
|
||||
const param = trigger.parameters[subkey];
|
||||
if (
|
||||
param.name === "workflow" &&
|
||||
param.value !== data.id &&
|
||||
!newSubflows.includes(param.value)
|
||||
) {
|
||||
newSubflows.push(param.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var parsedworkflows = [];
|
||||
for (var key in newSubflows) {
|
||||
const foundWorkflow = workflows.find(
|
||||
(workflow) => workflow.id === newSubflows[key]
|
||||
);
|
||||
if (foundWorkflow !== undefined && foundWorkflow !== null) {
|
||||
parsedworkflows.push(foundWorkflow);
|
||||
}
|
||||
}
|
||||
|
||||
if (parsedworkflows.length > 0) {
|
||||
console.log(
|
||||
"Appending subflows during export: ",
|
||||
parsedworkflows.length
|
||||
);
|
||||
data.subflows = parsedworkflows;
|
||||
}
|
||||
}
|
||||
|
||||
setExportData(data);
|
||||
setOpen(false);
|
||||
toast.info("Loading full workflow to be exported. Please wait...")
|
||||
}}
|
||||
key={"export"}
|
||||
>
|
||||
@@ -1985,6 +2061,7 @@ const Workflows = (props) => {
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
style={{ backgroundColor: theme.palette.inputColor, color: "white" }}
|
||||
disabled={isDistributed}
|
||||
onClick={() => {
|
||||
setDeleteModalOpen(true);
|
||||
setSelectedWorkflowId(data.id);
|
||||
@@ -2076,7 +2153,7 @@ const Workflows = (props) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{width: "100%", minWidth: 321, position: "relative", border: isDistributed ? "2px solid #40E0D0" : "inherit", borderRadius: theme.palette.borderRadius, }}>
|
||||
<div style={{width: "100%", minWidth: 321, position: "relative", border: highlightIds.includes(data.id) ? "2px solid #f85a3e" : isDistributed || hasSuborgs ? "2px solid #40E0D0" : "inherit", borderRadius: theme.palette.borderRadius, }}>
|
||||
<Paper square style={paperAppStyle}>
|
||||
{selectedCategory !== "" ?
|
||||
<Tooltip title={`Usecase Category: ${selectedCategory}`} placement="bottom">
|
||||
@@ -2462,6 +2539,8 @@ const Workflows = (props) => {
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
var workflowids = []
|
||||
|
||||
// Waits for the read
|
||||
reader.addEventListener("load", (event) => {
|
||||
var data = reader.result;
|
||||
@@ -2498,6 +2577,7 @@ const Workflows = (props) => {
|
||||
data.org = []
|
||||
data.execution_org = {}
|
||||
|
||||
workflowids.push(data.id)
|
||||
|
||||
// Actually create it
|
||||
setNewWorkflow(
|
||||
@@ -2528,6 +2608,10 @@ const Workflows = (props) => {
|
||||
}
|
||||
|
||||
setLoadWorkflowsModalOpen(false);
|
||||
|
||||
if (workflowids.length > 0) {
|
||||
setHighlightIds(workflowids)
|
||||
}
|
||||
};
|
||||
|
||||
const getWorkflowMeta = (data) => {
|
||||
@@ -3682,7 +3766,7 @@ const Workflows = (props) => {
|
||||
workflowDelay += 75
|
||||
} else {
|
||||
return (
|
||||
<Grid key={index} item xs={isMobile ? 12 : 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>
|
||||
)
|
||||
|
||||
@@ -1897,87 +1897,87 @@ func buildEnvVars(envMap map[string]string) []corev1.EnvVar {
|
||||
}
|
||||
|
||||
func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) {
|
||||
if request.Body == nil {
|
||||
log.Printf("[WARNING] (2) No body in request for workflowqueue")
|
||||
resp.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
defer request.Body.Close()
|
||||
body, err := ioutil.ReadAll(request.Body)
|
||||
if err != nil {
|
||||
log.Printf("[WARNING] (3) Failed reading body for workflowqueue")
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
|
||||
return
|
||||
}
|
||||
|
||||
var actionResult shuffle.ActionResult
|
||||
err = json.Unmarshal(body, &actionResult)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed shuffle.ActionResult unmarshaling (2): %s", err)
|
||||
//resp.WriteHeader(401)
|
||||
//resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
|
||||
//return
|
||||
}
|
||||
|
||||
if len(actionResult.ExecutionId) == 0 {
|
||||
log.Printf("[ERROR] No workflow execution id in action result. Data: %s", string(body))
|
||||
resp.WriteHeader(400)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "No workflow execution id in action result"}`)))
|
||||
return
|
||||
}
|
||||
|
||||
// 1. Get the shuffle.WorkflowExecution(ExecutionId) from the database
|
||||
// 2. if shuffle.ActionResult.Authentication != shuffle.WorkflowExecution.Authentication -> exit
|
||||
// 3. Add to and update actionResult in workflowExecution
|
||||
// 4. Push to db
|
||||
// IF FAIL: Set executionstatus: abort or cancel
|
||||
ctx := context.Background()
|
||||
workflowExecution, err := shuffle.GetWorkflowExecution(ctx, actionResult.ExecutionId)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR][%s] Failed getting execution (workflowqueue) %s: %s", actionResult.ExecutionId, actionResult.ExecutionId, err)
|
||||
resp.WriteHeader(500)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed getting execution ID %s because it doesn't exist locally."}`, actionResult.ExecutionId)))
|
||||
return
|
||||
}
|
||||
|
||||
if workflowExecution.Authorization != actionResult.Authorization {
|
||||
log.Printf("[ERROR][%s] Bad authorization key when updating node (workflowQueue). Want: %s, Have: %s", actionResult.ExecutionId, workflowExecution.Authorization, actionResult.Authorization)
|
||||
resp.WriteHeader(403)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad authorization key"}`)))
|
||||
return
|
||||
}
|
||||
|
||||
if workflowExecution.Status == "FINISHED" {
|
||||
log.Printf("[DEBUG][%s] Workflowexecution is already FINISHED. No further action can be taken", workflowExecution.ExecutionId)
|
||||
resp.WriteHeader(200)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Workflowexecution is already finished because it has status %s. Lastnode: %s"}`, workflowExecution.Status, workflowExecution.LastNode)))
|
||||
return
|
||||
}
|
||||
|
||||
if workflowExecution.Status == "ABORTED" || workflowExecution.Status == "FAILURE" {
|
||||
log.Printf("[WARNING][%s] Workflowexecution already has status %s. No further action can be taken", workflowExecution.ExecutionId, workflowExecution.Status)
|
||||
resp.WriteHeader(200)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Workflowexecution is aborted because of %s with result %s and status %s"}`, workflowExecution.LastNode, workflowExecution.Result, workflowExecution.Status)))
|
||||
return
|
||||
}
|
||||
|
||||
retries := 0
|
||||
retry, retriesok := request.URL.Query()["retries"]
|
||||
if retriesok && len(retry) > 0 {
|
||||
val, err := strconv.Atoi(retry[0])
|
||||
if err == nil {
|
||||
retries = val
|
||||
if request.Body == nil {
|
||||
log.Printf("[WARNING] (2) No body in request for workflowqueue")
|
||||
resp.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG][%s] Action: Received, Label: '%s', Action: '%s', Status: %s, Run status: %s, Extra=Retry:%d", workflowExecution.ExecutionId, actionResult.Action.Label, actionResult.Action.AppName, actionResult.Status, workflowExecution.Status, retries)
|
||||
defer request.Body.Close()
|
||||
body, err := ioutil.ReadAll(request.Body)
|
||||
if err != nil {
|
||||
log.Printf("[WARNING] (3) Failed reading body for workflowqueue")
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
|
||||
return
|
||||
}
|
||||
|
||||
//results = append(results, actionResult)
|
||||
//log.Printf("[INFO][%s] Time to execute %s (%s) with app %s:%s, function %s, env %s with %d parameters.", workflowExecution.ExecutionId, action.ID, action.Label, action.AppName, action.AppVersion, action.Name, action.Environment, len(action.Parameters))
|
||||
//log.Printf("[DEBUG][%s] In workflowQueue with transaction", workflowExecution.ExecutionId)
|
||||
runWorkflowExecutionTransaction(ctx, 0, workflowExecution.ExecutionId, actionResult, resp)
|
||||
var actionResult shuffle.ActionResult
|
||||
err = json.Unmarshal(body, &actionResult)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed shuffle.ActionResult unmarshaling (2): %s", err)
|
||||
//resp.WriteHeader(401)
|
||||
//resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
|
||||
//return
|
||||
}
|
||||
|
||||
if len(actionResult.ExecutionId) == 0 {
|
||||
log.Printf("[ERROR] No workflow execution id in action result. Data: %s", string(body))
|
||||
resp.WriteHeader(400)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "No workflow execution id in action result"}`)))
|
||||
return
|
||||
}
|
||||
|
||||
// 1. Get the shuffle.WorkflowExecution(ExecutionId) from the database
|
||||
// 2. if shuffle.ActionResult.Authentication != shuffle.WorkflowExecution.Authentication -> exit
|
||||
// 3. Add to and update actionResult in workflowExecution
|
||||
// 4. Push to db
|
||||
// IF FAIL: Set executionstatus: abort or cancel
|
||||
ctx := context.Background()
|
||||
workflowExecution, err := shuffle.GetWorkflowExecution(ctx, actionResult.ExecutionId)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR][%s] Failed getting execution (workflowqueue) %s: %s", actionResult.ExecutionId, actionResult.ExecutionId, err)
|
||||
resp.WriteHeader(500)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed getting execution ID %s because it doesn't exist locally."}`, actionResult.ExecutionId)))
|
||||
return
|
||||
}
|
||||
|
||||
if workflowExecution.Authorization != actionResult.Authorization {
|
||||
log.Printf("[ERROR][%s] Bad authorization key when updating node (workflowQueue). Want: %s, Have: %s", actionResult.ExecutionId, workflowExecution.Authorization, actionResult.Authorization)
|
||||
resp.WriteHeader(403)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad authorization key"}`)))
|
||||
return
|
||||
}
|
||||
|
||||
if workflowExecution.Status == "FINISHED" {
|
||||
log.Printf("[DEBUG][%s] Workflowexecution is already FINISHED. No further action can be taken", workflowExecution.ExecutionId)
|
||||
resp.WriteHeader(200)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Workflowexecution is already finished because it has status %s. Lastnode: %s"}`, workflowExecution.Status, workflowExecution.LastNode)))
|
||||
return
|
||||
}
|
||||
|
||||
if workflowExecution.Status == "ABORTED" || workflowExecution.Status == "FAILURE" {
|
||||
log.Printf("[WARNING][%s] Workflowexecution already has status %s. No further action can be taken", workflowExecution.ExecutionId, workflowExecution.Status)
|
||||
resp.WriteHeader(200)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Workflowexecution is aborted because of %s with result %s and status %s"}`, workflowExecution.LastNode, workflowExecution.Result, workflowExecution.Status)))
|
||||
return
|
||||
}
|
||||
|
||||
retries := 0
|
||||
retry, retriesok := request.URL.Query()["retries"]
|
||||
if retriesok && len(retry) > 0 {
|
||||
val, err := strconv.Atoi(retry[0])
|
||||
if err == nil {
|
||||
retries = val
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG][%s] Action: Received, Label: '%s', Action: '%s', Status: %s, Run status: %s, Extra=Retry:%d", workflowExecution.ExecutionId, actionResult.Action.Label, actionResult.Action.AppName, actionResult.Status, workflowExecution.Status, retries)
|
||||
|
||||
//results = append(results, actionResult)
|
||||
//log.Printf("[INFO][%s] Time to execute %s (%s) with app %s:%s, function %s, env %s with %d parameters.", workflowExecution.ExecutionId, action.ID, action.Label, action.AppName, action.AppVersion, action.Name, action.Environment, len(action.Parameters))
|
||||
//log.Printf("[DEBUG][%s] In workflowQueue with transaction", workflowExecution.ExecutionId)
|
||||
runWorkflowExecutionTransaction(ctx, 0, workflowExecution.ExecutionId, actionResult, resp)
|
||||
}
|
||||
|
||||
// Will make sure transactions are always ran for an execution. This is recursive if it fails. Allowed to fail up to 5 times
|
||||
|
||||
Reference in New Issue
Block a user