Bunch of fixes for MSSP feature buildout

This commit is contained in:
Frikky
2024-06-13 18:49:18 +02:00
parent 0455b59bd8
commit fe43b683b9
13 changed files with 2759 additions and 1372 deletions
+3
View File
@@ -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 {
+172 -13
View File
@@ -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>
+16 -7
View File
@@ -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>
+182 -22
View File
@@ -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"
+105 -1
View File
@@ -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",
})
+5
View File
@@ -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
View File
@@ -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>
&nbsp;
<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
+1
View File
@@ -46,6 +46,7 @@ const Body = {
height: "100%",
color: "white",
position: "relative",
paddingTop: 40,
//textAlign: "center",
};
+153 -69
View File
@@ -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>
)
+78 -78
View File
@@ -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