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", Accept: "application/json",
}, },
credentials: "include", credentials: "include",
}).catch((error) => {
console.log("Error getting workflow stats: " + error);
return workflow
}) })
if (response.status !== 200) { if (response.status !== 200) {
@@ -428,7 +428,6 @@ const ConfigureWorkflow = (props) => {
console.log("Found webhook: ", trigger) console.log("Found webhook: ", trigger)
if (trigger.app_association !== undefined && trigger.app_association.name !== null && trigger.app_association.name !== "") { 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 findapp = trigger.app_association.name.toLowerCase()
const foundindex = newactions.findIndex(action => action.app_name.toLowerCase() === findapp) const foundindex = newactions.findIndex(action => action.app_name.toLowerCase() === findapp)
@@ -449,9 +448,6 @@ const ConfigureWorkflow = (props) => {
newactions[foundindex].show_steps = true newactions[foundindex].show_steps = true
console.log("CHANGED ACTION: ", newactions[foundindex])
//console.log("Index: ", newactions[foundindex])
continue continue
} }
} }
@@ -1321,7 +1317,6 @@ const ConfigureWorkflow = (props) => {
} }
if (step.type === "authenticate") { if (step.type === "authenticate") {
console.log("AUTH STEP: ", step)
if (data.must_authenticate === true ) { if (data.must_authenticate === true ) {
filled = false filled = false
} else { } else {
+169 -10
View File
@@ -78,7 +78,6 @@ const EditWorkflow = (props) => {
const [name, setName] = React.useState(workflow.name !== undefined ? workflow.name : "") 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')) 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 [inputQuestions, setInputQuestions] = React.useState(workflow.input_questions !== undefined && workflow.input_questions !== null ? JSON.parse(JSON.stringify(workflow.input_questions)) : [])
const classes = useStyles(); const classes = useStyles();
@@ -235,7 +234,7 @@ const EditWorkflow = (props) => {
</div> </div>
</DialogTitle> </DialogTitle>
<FormControl> <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 <Button
style={{}} style={{}}
@@ -315,7 +314,7 @@ const EditWorkflow = (props) => {
}} }}
color="primary" color="primary"
> >
{submitLoading ? <CircularProgress color="secondary" /> : "Done"} {submitLoading ? <CircularProgress color="secondary" /> : "Save Changes"}
</Button> </Button>
</div> </div>
@@ -565,12 +564,20 @@ const EditWorkflow = (props) => {
fullWidth fullWidth
/> />
<Typography variant="body2" style={{marginTop: 50, }}> <Typography variant="body1" style={{marginTop: 50, }}>
MSSP Suborg Distribution (beta - contact support@shuffler.io) MSSP Suborg Distribution (beta - contact support@shuffler.io for more info)
</Typography> </Typography>
{userdata !== undefined && userdata !== null && userdata.orgs !== undefined && userdata.orgs !== null && userdata.orgs.length > 0 ? {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 ? userdata.orgs.filter(org => org.creator_org === userdata.active_org.id).length === 0 ?
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)"}}> <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. You can only distribute to suborgs from a parent org.
</Typography> </Typography>
: :
@@ -584,8 +591,6 @@ const EditWorkflow = (props) => {
newvalue = newvalue.filter(value => value !== "none") newvalue = newvalue.filter(value => value !== "none")
} }
console.log("NEWVALUE: ", newvalue)
if (newvalue.includes("none")) { if (newvalue.includes("none")) {
newvalue = ["none"] newvalue = ["none"]
} else if (newvalue.includes("all")) { } else if (newvalue.includes("all")) {
@@ -775,18 +780,172 @@ const EditWorkflow = (props) => {
> >
<AddIcon style={{}} /> <AddIcon style={{}} />
</Button> </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> </span>
: null} : null}
<Tooltip color="primary" title={"Add more details"} placement="top"> <Tooltip color="primary" title={"Add more details"} placement="top">
<IconButton <Button
style={{ color: "white", margin: "auto", textAlign: "center", width: 50, marginTop: 50, }} style={{ margin: "auto", marginTop: 50, textAlign: "center", }}
variant="outlined"
onClick={() => { onClick={() => {
setShowMoreClicked(!showMoreClicked); setShowMoreClicked(!showMoreClicked);
}} }}
> >
{showMoreClicked ? <ExpandLessIcon /> : <ExpandMoreIcon/>} {showMoreClicked ? <ExpandLessIcon /> : <ExpandMoreIcon/>}
</IconButton> {showMoreClicked ? "Collapse": "Expand"}
</Button>
</Tooltip> </Tooltip>
</div> </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 { toast } from "react-toastify";
import theme from "../theme.jsx"; import theme from "../theme.jsx";
import { BrowserView, MobileView } from "react-device-detect"; import { BrowserView, MobileView } from "react-device-detect";
@@ -84,6 +84,15 @@ const Header = (props) => {
setAnchorEl(event.currentTarget); setAnchorEl(event.currentTarget);
}; };
useEffect(() => {
const topbar = localStorage.getItem("topbar_closed")
if (topbar === "true") {
setShowTopbar(false)
} else {
setShowTopbar(true)
}
}, [])
const handleClose = () => { const handleClose = () => {
setAnchorEl(null); setAnchorEl(null);
setAnchorElAvatar(null); setAnchorElAvatar(null);
@@ -1590,17 +1599,17 @@ const Header = (props) => {
navigate("/training") 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)" }}> }} style={{ cursor: "pointer", textDecoration: "none", color: "rgba(255,255,255,0.8)" }}>
Public Training! Public Training!
</span> </span>
</u> </u>
</Typography> </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 /> <CloseIcon />
</IconButton> </IconButton>
</div> </div>
+182 -22
View File
@@ -55,6 +55,10 @@ const OrgHeaderexpanded = (props) => {
globalUrl, globalUrl,
isCloud, isCloud,
adminTab, adminTab,
selectedStatus,
setSelectedStatus,
isEditOrgTab
} = props; } = props;
const classes = useStyles(); const classes = useStyles();
@@ -169,6 +173,11 @@ const OrgHeaderexpanded = (props) => {
: selectedOrganization.sso_config.openid_token : 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 [workflows, setWorkflows] = React.useState([])
const [workflow, setWorkflow] = React.useState({}) const [workflow, setWorkflow] = React.useState({})
@@ -299,7 +308,6 @@ const OrgHeaderexpanded = (props) => {
client_secret: openidClientSecret, client_secret: openidClientSecret,
openid_authorization: openidAuthorization, openid_authorization: openidAuthorization,
openid_token: openidToken, openid_token: openidToken,
SSORequired: SSORequired
} }
) )
} }
@@ -311,16 +319,7 @@ const OrgHeaderexpanded = (props) => {
); );
const toggleBetweenRequiredOrOptional = (event) => { const toggleBetweenRequiredOrOptional = (event) => {
if (ssoEntrypoint === "" && openidAuthorization === "" && openidToken === "") { setSSORequired(event.target.checked);
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)
}; };
return ( return (
@@ -512,16 +511,164 @@ const OrgHeaderexpanded = (props) => {
/> />
</span> </span>
</Grid> </Grid>
{/* {isCloud ? null : */}
<Typography variant="h4" style={{ textAlign: "left", marginTop: 75, marginLeft: 20, }}>Single Signon - SAML/OpenID</Typography> <Grid item xs={12} style={{ marginTop: 50, }}>
<div style={{ display: 'flex', flexDirection: 'column', marginTop: 20, marginLeft: 10, width: '100%', borderBottom: '1px solid #414347' }}> <Typography variant="h4" style={{ textAlign: "left", }}>Workflow Backup Repository</Typography>
<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> <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> <div>
<Switch <Switch
checked={SSORequired} checked={SSORequired}
onChange={(e) => { onChange={toggleBetweenRequiredOrOptional}
toggleBetweenRequiredOrOptional(e)
}}
name="onOffSwitch" name="onOffSwitch"
color="primary" color="primary"
title="Make SAML SSO or OpenID Authentication Required or Optional for Your Organization" title="Make SAML SSO or OpenID Authentication Required or Optional for Your Organization"
@@ -529,10 +676,8 @@ const OrgHeaderexpanded = (props) => {
{SSORequired ? 'Required' : 'Optional'} {SSORequired ? 'Required' : 'Optional'}
</div> </div>
</div> </div>
<div>
</div>
<Grid item xs={12} style={{}}> <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 container style={{ marginTop: 10, }}>
<Grid item xs={6} style={{}}> <Grid item xs={6} style={{}}>
<span> <span>
@@ -549,6 +694,11 @@ const OrgHeaderexpanded = (props) => {
type="name" type="name"
multiline={true} multiline={true}
rows={2} rows={2}
disabled={
selectedOrganization.manager_orgs !== undefined &&
selectedOrganization.manager_orgs !== null &&
selectedOrganization.manager_orgs.length > 0
}
id="outlined-with-placeholder" id="outlined-with-placeholder"
margin="normal" margin="normal"
variant="outlined" variant="outlined"
@@ -583,6 +733,11 @@ const OrgHeaderexpanded = (props) => {
type="name" type="name"
multiline={true} multiline={true}
rows={2} rows={2}
disabled={
selectedOrganization.manager_orgs !== undefined &&
selectedOrganization.manager_orgs !== null &&
selectedOrganization.manager_orgs.length > 0
}
id="outlined-with-placeholder" id="outlined-with-placeholder"
margin="normal" margin="normal"
variant="outlined" variant="outlined"
@@ -677,7 +832,7 @@ const OrgHeaderexpanded = (props) => {
{/* } */} {/* } */}
{/*isCloud ? null : */} {/*isCloud ? null : */}
<Grid item xs={12} style={{ marginTop: 50, }}> <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 container style={{ marginTop: 20, }}>
<Grid item xs={6} style={{}}> <Grid item xs={6} style={{}}>
<span> <span>
@@ -694,6 +849,11 @@ const OrgHeaderexpanded = (props) => {
type="name" type="name"
multiline={true} multiline={true}
rows={2} rows={2}
disabled={
selectedOrganization.manager_orgs !== undefined &&
selectedOrganization.manager_orgs !== null &&
selectedOrganization.manager_orgs.length > 0
}
id="outlined-with-placeholder" id="outlined-with-placeholder"
margin="normal" margin="normal"
variant="outlined" variant="outlined"
+105 -1
View File
@@ -167,6 +167,7 @@ const ParsedAction = (props) => {
listCache, listCache,
authGroups,
apps, apps,
setEditorData, setEditorData,
setcodedata, setcodedata,
@@ -1123,6 +1124,65 @@ const ParsedAction = (props) => {
return helperText 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 // FIXME: Issue #40 - selectedActionParameters not reset
if (Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters.length > 0) { if (Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters.length > 0) {
@@ -1310,6 +1370,7 @@ const ParsedAction = (props) => {
</Button> </Button>
</Tooltip> </Tooltip>
} }
{selectedAction.template === true && selectedAction.matching_actions !== undefined && selectedAction.matching_actions !== null && selectedAction.matching_actions.length > 0 ? {selectedAction.template === true && selectedAction.matching_actions !== undefined && selectedAction.matching_actions !== null && selectedAction.matching_actions.length > 0 ?
<div> <div>
<Typography variant="body1"> <Typography variant="body1">
@@ -1441,6 +1502,8 @@ const ParsedAction = (props) => {
</div> </div>
) : null} ) : null}
{suggestionInfo()}
{selectedActionParameters.map((data, count) => { {selectedActionParameters.map((data, count) => {
if (data.variant === "") { if (data.variant === "") {
data.variant = "STATIC_VALUE"; data.variant = "STATIC_VALUE";
@@ -3606,6 +3669,7 @@ const ParsedAction = (props) => {
}} }}
labelId="select-app-auth" labelId="select-app-auth"
value={ value={
selectedAction.authentication_id === "authgroups" ? "authgroups" :
Object.getOwnPropertyNames(selectedAction.selectedAuthentication).length === 0 Object.getOwnPropertyNames(selectedAction.selectedAuthentication).length === 0
? "No selection" ? "No selection"
: selectedAction.selectedAuthentication : selectedAction.selectedAuthentication
@@ -3617,6 +3681,8 @@ const ParsedAction = (props) => {
}} }}
fullWidth fullWidth
onChange={(e) => { onChange={(e) => {
console.log("AUTH CHANGE: ", e.target.value)
if (e.target.value === "No selection") { if (e.target.value === "No selection") {
selectedAction.selectedAuthentication = {}; selectedAction.selectedAuthentication = {};
selectedAction.authentication_id = ""; selectedAction.authentication_id = "";
@@ -3628,13 +3694,36 @@ const ParsedAction = (props) => {
} }
} }
setSelectedAction(selectedAction); 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 { } else {
selectedAction.selectedAuthentication = e.target.value; selectedAction.selectedAuthentication = e.target.value;
selectedAction.authentication_id = e.target.value.id; selectedAction.authentication_id = e.target.value.id;
setSelectedAction(selectedAction); setSelectedAction(selectedAction);
setUpdate(Math.random()); setUpdate(Math.random());
} }
}} }}
style={{ style={{
backgroundColor: theme.palette.inputColor, backgroundColor: theme.palette.inputColor,
@@ -3653,6 +3742,7 @@ const ParsedAction = (props) => {
> >
<em>No selection</em> <em>No selection</em>
</MenuItem> </MenuItem>
{selectedAction.authentication.map((data) => { {selectedAction.authentication.map((data) => {
if (data.last_modified === true) { if (data.last_modified === true) {
//console.log("LAST MODIFIED: ", data.label) //console.log("LAST MODIFIED: ", data.label)
@@ -3689,6 +3779,20 @@ const ParsedAction = (props) => {
</MenuItem> </MenuItem>
); );
})} })}
<Divider style={{marginTop: 10, marginBottom: 10, }}/>
<MenuItem
style={{
backgroundColor: theme.palette.inputColor,
color: "white",
}}
value="authgroups"
>
<em>Auth Groups</em>
</MenuItem>
</Select> </Select>
{/* {/*
@@ -627,8 +627,8 @@ const CodeEditor = (props) => {
newMarkers.push({ newMarkers.push({
startRow: i, startRow: i,
startCol: startCh, startCol: startCh,
endRow: i+1, endRow: i,
endCol: endCh+1, endCol: endCh,
className: correctVariable ? "good-marker" : "bad-marker", className: correctVariable ? "good-marker" : "bad-marker",
type: "text", type: "text",
}) })
+5
View File
@@ -133,6 +133,11 @@ const data = [
"background-gradient-stop-colors": "data(fillGradient)", "background-gradient-stop-colors": "data(fillGradient)",
}, },
}, },
{
selector: `node[?parent_controlled]`,
css: {
},
},
{ {
selector: `node[app_name="Testing"]`, selector: `node[app_name="Testing"]`,
css: { css: {
+245 -112
View File
@@ -166,7 +166,9 @@ const Admin = (props) => {
//console.log("Selected: ", selectedOrganization) //console.log("Selected: ", selectedOrganization)
const [appAuthenticationGroupModalOpen , setAppAuthenticationGroupModalOpen] = React.useState(false); const [appAuthenticationGroupModalOpen , setAppAuthenticationGroupModalOpen] = React.useState(false);
const [appsForAppAuthGroup, setAppsForAppAuthGroup] = React.useState([]); const [appsForAppAuthGroup, setAppsForAppAuthGroup] = React.useState([]);
const [appAuthenticationGroupId, setAppAuthenticationGroupId] = React.useState("");
const [appAuthenticationGroupName, setAppAuthenticationGroupName] = React.useState(""); const [appAuthenticationGroupName, setAppAuthenticationGroupName] = React.useState("");
const [appAuthenticationGroupEnvironment, setAppAuthenticationGroupEnvironment] = React.useState("");
const [appAuthenticationGroupDescription, setAppAuthenticationGroupDescription] = React.useState(""); const [appAuthenticationGroupDescription, setAppAuthenticationGroupDescription] = React.useState("");
const [appAuthenticationGroups, setAppAuthenticationGroups] = React.useState([]); const [appAuthenticationGroups, setAppAuthenticationGroups] = React.useState([]);
const [organizationFeatures, setOrganizationFeatures] = 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) => { let app_auths = appAuthIds.map((appAuthId) => {
return { id: 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", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Accept: "application/json", Accept: "application/json",
}, },
credentials: "include", credentials: "include",
body: JSON.stringify({ body: JSON.stringify(parsedAppGroup),
label: name,
description: description,
app_auths: app_auths
}),
}) })
.then((response) => { .then((response) => {
if (response.status !== 200) { if (response.status !== 200) {
@@ -457,8 +498,15 @@ const Admin = (props) => {
return response.json(); return response.json();
}) })
.then((responseJson) => { .then((responseJson) => {
// getAppAuthenticationGroups(); if (responseJson.success === false) {
toast("App authentication group created"); 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) => { .catch((error) => {
toast(error.toString()); 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) => { .then((responseJson) => {
setWebHooks(responseJson.webhooks || []); // Handling the case where the result is null or undefined setWebHooks(responseJson.webhooks || []); // Handling the case where the result is null or undefined
setAllSchedules(responseJson.schedules || []); setAllSchedules(responseJson.schedules || []);
setPipelines(responseJson.pipelines || []); // setPipelines(responseJson.pipelines || []);
}) })
.catch((error) => { .catch((error) => {
// toast(error.toString()); // 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 = { const data = {
command: pipeline.command,
name: pipeline.name, name: pipeline.name,
type: state, type: state,
environment: pipeline.environment, environment: pipeline.environment,
workflow_id: pipeline.workflow_id, workflow_id: pipeline.workflow_id,
trigger_id: pipeline.trigger_id, trigger_id: pipeline.trigger_id,
start_node: pipeline.start_node,
}; };
if (state === "start") toast("starting the pipeline"); 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"); if (state === "start") toast("Successfully created pipeline");
else toast("Sucessfully stopped the pipeline"); else toast("Sucessfully stopped the pipeline");
} }
setTimeout(handleGetAllTriggers, 1000);
}) })
.catch((error) => { .catch((error) => {
//toast(error.toString()); //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 = () => { const getAppAuthenticationGroups = () => {
console.log("DEBUG: Skipping app auth group loading") //console.log("DEBUG: Skipping app auth group loading")
return //return
fetch(globalUrl + "/api/v1/apps/authentication/group", { fetch(globalUrl + "/api/v1/authentication/group", {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json", "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> <ListItemText>
<Button <Button
style={{ marginLeft: "140px" }} style={{ marginLeft: "18%" }}
variant={ variant={
webhook.status === "running" ? "contained" : "outlined" 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> </List>
)} )}
<div style={{ marginTop: 20, marginBottom: 20 }}> {/* <div style={{ marginTop: 20, marginBottom: 20 }}>
<h2 style={{ display: "inline" }}>Tenzir Pipelines</h2> <h2 style={{ display: "inline" }}>Tenzir Pipelines</h2>
<span style={{ marginLeft: 25 }}> <span style={{ marginLeft: 25 }}>
Controls the Tenzir pipeline operations.{" "} Controls a pipeline to run things.{" "}
<a <a
target="_blank" target="_blank"
rel="noopener noreferrer" 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" primary="Workflow"
style={{ maxWidth: 315, minWidth: 315 }} style={{ maxWidth: 315, minWidth: 315 }}
/> />
<ListItemText primary="Actions" style={{ marginLeft: '120px' }} /> <ListItemText primary="Actions" />
</ListItem> </ListItem>
{pipelines.map((pipeline, index) => { {pipelines.map((pipeline, index) => {
var bgColor = "#27292d"; 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> </List>
)} )}*/}
</div> </div>
) : null; ) : 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) => { const handleAppAuthGroupCheckbox = (data) => {
let appOrginal = data.app //let groupApp = data.app.id
if (appsForAppAuthGroup.includes(appOrginal.id)) { var newappauth = appsForAppAuthGroup
return; if (appsForAppAuthGroup.includes(data.app.id)) {
newappauth = newappauth.filter((item) => item !== data.app.id)
} }
setAppsForAppAuthGroup([...appsForAppAuthGroup, data.id]); if (appsForAppAuthGroup.includes(data.id)) {
console.log("Apps for app auth group: ", appsForAppAuthGroup); // 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 = const authenticationView =
curTab === 2 ? ( curTab === 2 ? (
<>
<div>
{/* (appAuthenticationGroupModalOpen : { */} {/* (appAuthenticationGroupModalOpen : { */}
{appAuthenticationGroupModalOpen && ( {appAuthenticationGroupModalOpen && (
<Dialog <Dialog
open={appAuthenticationGroupModalOpen} open={appAuthenticationGroupModalOpen}
onClose={() => { onClose={() => {
setAppAuthenticationGroupModalOpen(false); setAppAuthenticationGroupModalOpen(false);
setAppAuthenticationGroupId("")
setAppAuthenticationGroupName("")
setAppAuthenticationGroupEnvironment("")
setAppAuthenticationGroupDescription("")
setAppsForAppAuthGroup([])
}} }}
PaperProps={{ PaperProps={{
style: { style: {
backgroundColor: theme.palette.surfaceColor, backgroundColor: theme.palette.surfaceColor,
color: "white", color: "white",
minWidth: "1200px", minWidth: "1000px",
minHeight: "320px", minHeight: "320px",
padding: 25,
paddingLeft: 50,
}, },
}} }}
> >
@@ -5202,10 +5279,15 @@ 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> <span style={{ color: "white" }}>App Authentication Groups</span>
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent style={{marginLeft: 0, paddingLeft: 0, }}>
<div> <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 <TextField
color="primary" color="primary"
label="Name"
style={{ backgroundColor: theme.palette.inputColor }} style={{ backgroundColor: theme.palette.inputColor }}
autoFocus autoFocus
InputProps={{ InputProps={{
@@ -5221,53 +5303,88 @@ If you're interested, please let me know a time that works for you, or set up a
id="namefield" id="namefield"
margin="normal" margin="normal"
variant="outlined" variant="outlined"
defaultValue={appAuthenticationGroupName}
onChange={(event) => { onChange={(event) => {
setAppAuthenticationGroupName(event.target.value); setAppAuthenticationGroupName(event.target.value);
}} }}
/> />
</div> </div>
<div> <div style={{marginRight: 50, }}>
<TextField <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 style={{marginTop: 65, }}>
<Button
style={{}}
disabled={appAuthenticationGroupName === "" || appAuthenticationGroupEnvironment === "" || appsForAppAuthGroup.length === 0}
variant="contained"
color="primary" color="primary"
style={{ backgroundColor: theme.palette.inputColor }} onClick={() => {
autoFocus createAppAuthenticationGroup(
InputProps={{ appAuthenticationGroupName,
style: { appAuthenticationGroupEnvironment,
height: "50px", appAuthenticationGroupDescription,
color: "white", appsForAppAuthGroup,
fontSize: "1em", );
},
}} }}
required >
fullWidth={true} Set Group
placeholder="Description" </Button>
id="descriptionfield" </div>
margin="normal"
variant="outlined"
onChange={(event) => {
setAppAuthenticationGroupDescription(event.target.value);
}}
/>
</div> </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 */} {/* Show a check box list of all app authentications to add to the auth group */}
<div> <div>
{authentication.map((data, index) => ( {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}> <div key={index}>
<FormControlLabel <FormControlLabel
control={ control={
<Tooltip <Tooltip
title={data.app.name} title={data.app.name}
placement="left"
> >
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '10px', marginLeft: '5px' }}> <div style={{ display: 'flex', alignItems: 'center', marginBottom: '10px', marginLeft: '5px', }}>
<img <img
src={data.app.large_image ? data.app.large_image : '/images/no_image.png'} src={data.app.large_image ? data.app.large_image : '/images/no_image.png'}
alt="" alt=""
style={{ width: '50px', height: '50px', marginRight: '10px' }} style={{ borderRadius: theme.palette.borderRadius, width: 50, height: 50, marginRight: 10 }}
/> />
<Checkbox <Checkbox
checked={data.checked} checked={checked}
onChange={(event) => { onChange={(event) => {
handleAppAuthGroupCheckbox(data) handleAppAuthGroupCheckbox(data)
}} }}
@@ -5280,28 +5397,12 @@ If you're interested, please let me know a time that works for you, or set up a
label={data.label} label={data.label}
/> />
</div> </div>
))} )
})}
</div> </div>
</div> </div>
<div>
<Button
style={{}}
variant="contained"
color="primary"
onClick={() => {
createAppAuthenticationGroup(
appAuthenticationGroupName,
appAuthenticationGroupDescription,
appsForAppAuthGroup
);
}}
>
Create
</Button>
</div>
</DialogContent> </DialogContent>
</Dialog> </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 }}> <div style={{ marginTop: 20, marginBottom: 20 }}>
<h2 style={{ display: "inline" }}>App Authentication</h2> <h2 style={{ display: "inline" }}>App Authentication</h2>
<span style={{ marginLeft: 25 }}> <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> </span>
&nbsp; &nbsp;
<a <a
@@ -5587,10 +5688,18 @@ If you're interested, please let me know a time that works for you, or set up a
</List> </List>
</div> </div>
{/* <div> <Divider
style={{
marginTop: 20,
marginBottom: 20,
backgroundColor: theme.palette.inputColor,
}}
/>
<div style={{marginTop: 50, }}>
<div style={{ marginTop: 20, marginBottom: 20 }}> <div style={{ marginTop: 20, marginBottom: 20 }}>
<h2 style={{ display: "inline" }}>App Authentication Groups</h2> <h2 style={{ }}>App Authentication Groups</h2>
<span style={{ marginLeft: 25 }}> <span style={{ marginLeft: 0 }}>
Groups of authentication options for subflows.{" "} Groups of authentication options for subflows.{" "}
<a <a
target="_blank" target="_blank"
@@ -5602,25 +5711,35 @@ If you're interested, please let me know a time that works for you, or set up a
</a> </a>
</span> </span>
<Divider <br />
style={{ <Button
marginTop: 20, style={{ marginTop: 20 }}
marginBottom: 20, variant="contained"
backgroundColor: theme.palette.inputColor, 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> <ListItem>
<ListItemText <ListItemText
primary="Label" primary="Label"
style={{ minWidth: 150, maxWidth: 150 }}
/>
<ListItemText
primary="Description"
style={{ minWidth: 250, maxWidth: 250}} style={{ minWidth: 250, maxWidth: 250}}
/> />
<ListItemText <ListItemText
primary="Apps" primary="Environment"
style={{ minWidth: 150, maxWidth: 150 }}
/>
<ListItemText
primary="App Auth"
style={{ minWidth: 250, maxWidth: 250 }} style={{ minWidth: 250, maxWidth: 250 }}
/> />
<ListItemText <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) { if (index % 2 === 0) {
bgColor = "#1f2023"; bgColor = "#1f2023";
} }
if (data.app_auths === undefined || data.app_auths === null) {
data.app_auths = []
}
return ( return (
<ListItem key={index} style={{ backgroundColor: bgColor }}> <ListItem key={index} style={{ backgroundColor: bgColor }}>
<ListItemText <ListItemText
primary={data.label} primary={data.label}
style={{ minWidth: 150, maxWidth: 150 }} style={{ minWidth: 250, maxWidth: 250, }}
/> />
<ListItemText <ListItemText
primary={data.description} primary={data.environment}
style={{ minWidth: 250, maxWidth: 250 }} style={{ minWidth: 150, maxWidth: 150, }}
/> />
<ListItemText <ListItemText
primary={ primary={
<div style={{ display: 'flex' }}> <div style={{ display: 'flex' }}>
{data.app_auths.map((appAuth, index) => ( {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 <Tooltip
title={appAuth.app.name} title={tooltip}
> >
<img <img
key={index} key={index}
src={appAuth.app.large_image} src={appAuth.app.large_image}
alt={appAuth.app.name} alt={appAuth.app.name}
style={{ width: '24px', height: '24px', marginRight: '5px' }} style={{ width: 30, height: 30, marginRight: 5 }}
/> />
</Tooltip> </Tooltip>
))} )
})}
</div> </div>
} }
style={{ minWidth: 250, maxWidth: 250 }} 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' }}> <div style={{ display: 'flex' }}>
<IconButton <IconButton
onClick={() => { 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 /> <EditIcon />
</IconButton> </IconButton>
<IconButton <IconButton
onClick={() => { onClick={() => {
// deleteAppAuthenticationGroup(data); deleteAppAuthenticationGroup(data.id)
}} }}
disabled={true}
> >
<DeleteIcon /> <DeleteIcon />
</IconButton> </IconButton>
@@ -5700,20 +5843,10 @@ If you're interested, please let me know a time that works for you, or set up a
)} )}
</List> </List>
<Button
style={{ marginLeft: 10 }}
variant="contained"
color="primary"
onClick={() => {
setAppAuthenticationGroupModalOpen(true);
}}
>
Add Group
</Button>
</div> </div>
</div> */} </div>
</> </div>
) : null; ) : null;
const getLogs = async (ip, userId) => { 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%", height: "100%",
color: "white", color: "white",
position: "relative", position: "relative",
paddingTop: 40,
//textAlign: "center", //textAlign: "center",
}; };
+151 -67
View File
@@ -74,6 +74,7 @@ import {
ArrowLeft as ArrowLeftIcon, ArrowLeft as ArrowLeftIcon,
ArrowRight as ArrowRightIcon, ArrowRight as ArrowRightIcon,
QueryStats as QueryStatsIcon, QueryStats as QueryStatsIcon,
Visibility as VisibilityIcon,
} from "@mui/icons-material"; } from "@mui/icons-material";
import { DataGrid, GridToolbar } from "@mui/x-data-grid"; import { DataGrid, GridToolbar } from "@mui/x-data-grid";
@@ -614,6 +615,7 @@ const Workflows = (props) => {
const [videoViewOpen, setVideoViewOpen] = React.useState(false) const [videoViewOpen, setVideoViewOpen] = React.useState(false)
const [gettingStartedItems, setGettingStartedItems] = React.useState([]) const [gettingStartedItems, setGettingStartedItems] = React.useState([])
const [selectedWorkflowIndexes, setSelectedWorkflowIndexes] = React.useState([]) const [selectedWorkflowIndexes, setSelectedWorkflowIndexes] = React.useState([])
const [highlightIds, setHighlightIds] = React.useState([])
const [apps, setApps] = React.useState([]); const [apps, setApps] = React.useState([]);
@@ -1213,7 +1215,24 @@ const Workflows = (props) => {
} }
setFirstLoad(false) 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 { } else {
if (isLoggedIn) { if (isLoggedIn) {
@@ -1637,20 +1656,24 @@ const Workflows = (props) => {
}); });
}; };
const copyWorkflow = (data) => { const duplicateWorkflow = (data) => {
data = JSON.parse(JSON.stringify(data)); //data = JSON.parse(JSON.stringify(data));
toast("Copying workflow " + data.name); toast("Copying workflow '" + data.name + "'. The new workflow will load in and be highlighted.");
data.id = ""; //data.id = "";
data.name = data.name + "_copy"; //data.name = data.name + "_copy";
data = deduplicateIds(data, true); //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", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Accept: "application/json", Accept: "application/json",
}, },
body: JSON.stringify(data), body: JSON.stringify(duplicateData),
credentials: "include", credentials: "include",
}) })
.then((response) => { .then((response) => {
@@ -1660,7 +1683,20 @@ const Workflows = (props) => {
} }
return response.json(); 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(() => { setTimeout(() => {
getAvailableWorkflows(); getAvailableWorkflows();
}, 1000); }, 1000);
@@ -1689,19 +1725,72 @@ 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 storagewf = localStorage.getItem("workflows")
const storageWorkflows = JSON.parse(storagewf) const storageWorkflows = JSON.parse(storagewf)
if (storageWorkflows === null || storageWorkflows === undefined || storageWorkflows.length === 0) { if (storageWorkflows === null || storageWorkflows === undefined || storageWorkflows.length === 0) {
} else { } else {
for (var i = 0; i < storageWorkflows.length; i++) { for (var i = 0; i < storageWorkflows.length; i++) {
if (storageWorkflows[i].id === id) { if (storageWorkflows[i].id === id) {
if (storageWorkflows[i].image !== "") { if (storageWorkflows[i].image !== "" && storageWorkflows[i].image !== undefined && storageWorkflows[i].image !== null) {
if (action === undefined || action === null || action === "") {
console.log("RETURNING")
return return
} }
} }
} }
} }
}
fetch(globalUrl + "/api/v1/workflows/" + id, { fetch(globalUrl + "/api/v1/workflows/" + id, {
method: "GET", method: "GET",
@@ -1718,8 +1807,16 @@ const Workflows = (props) => {
return response.json() return response.json()
}) })
.then((responseJson) => { .then((responseJson) => {
if (openEdit) { if (responseJson.success !== false && responseJson.id !== undefined) {
if (action === "edit") {
setEditing(responseJson) 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++) { for (var i = 0; i < storageWorkflows.length; i++) {
@@ -1730,8 +1827,9 @@ const Workflows = (props) => {
} }
} }
setWorkflows(storageWorkflows) //setWorkflows(storageWorkflows)
//setFilteredWorkflows(storageWorkflows) setFilteredWorkflows(storageWorkflows)
//setUpdate(Math.random())
}) })
.catch((error) => { .catch((error) => {
console.log(error.toString()) console.log(error.toString())
@@ -1878,7 +1976,9 @@ const Workflows = (props) => {
const appGroup = getWorkflowAppgroup(data) const appGroup = getWorkflowAppgroup(data)
const [triggers, subflows] = getWorkflowMeta(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 = ( const workflowMenuButtons = (
<Menu <Menu
id="long-menu" id="long-menu"
@@ -1890,8 +1990,20 @@ const Workflows = (props) => {
setAnchorEl(null); setAnchorEl(null);
}} }}
> >
{isDistributed ?
<MenuItem <MenuItem
style={{ backgroundColor: theme.palette.inputColor, color: "white" }} 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) => { onClick={(event) => {
event.stopPropagation() event.stopPropagation()
if (data.actions !== undefined && data.actions !== null && data.actions.length > 0 && data.image !== "") { if (data.actions !== undefined && data.actions !== null && data.actions.length > 0 && data.image !== "") {
@@ -1899,7 +2011,9 @@ const Workflows = (props) => {
} else { } else {
//toast("Need to side-load workflow to be edited properly") //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"} key={"change"}
@@ -1909,9 +2023,11 @@ const Workflows = (props) => {
</MenuItem> </MenuItem>
<MenuItem <MenuItem
style={{ backgroundColor: theme.palette.inputColor, color: "white" }} style={{ backgroundColor: theme.palette.inputColor, color: "white" }}
disabled={isDistributed}
onClick={() => { onClick={() => {
setSelectedWorkflow(data); sideloadWorkflow(data.id, "publish")
setPublishModalOpen(true);
toast.info("Loading full workflow for publishing. Please wait...")
}} }}
key={"publish"} key={"publish"}
> >
@@ -1920,9 +2036,10 @@ const Workflows = (props) => {
</MenuItem> </MenuItem>
<MenuItem <MenuItem
style={{ backgroundColor: theme.palette.inputColor, color: "white" }} style={{ backgroundColor: theme.palette.inputColor, color: "white" }}
disabled={isDistributed}
onClick={() => { onClick={() => {
copyWorkflow(data); duplicateWorkflow(data)
setOpen(false); setOpen(false)
}} }}
key={"duplicate"} key={"duplicate"}
> >
@@ -1931,52 +2048,11 @@ const Workflows = (props) => {
</MenuItem> </MenuItem>
<MenuItem <MenuItem
style={{ backgroundColor: theme.palette.inputColor, color: "white" }} style={{ backgroundColor: theme.palette.inputColor, color: "white" }}
disabled={isDistributed}
onClick={() => { onClick={() => {
setExportModalOpen(true); sideloadWorkflow(data.id, "export", setOpen)
if (data.triggers !== null && data.triggers !== undefined) { toast.info("Loading full workflow to be exported. Please wait...")
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);
}} }}
key={"export"} key={"export"}
> >
@@ -1985,6 +2061,7 @@ const Workflows = (props) => {
</MenuItem> </MenuItem>
<MenuItem <MenuItem
style={{ backgroundColor: theme.palette.inputColor, color: "white" }} style={{ backgroundColor: theme.palette.inputColor, color: "white" }}
disabled={isDistributed}
onClick={() => { onClick={() => {
setDeleteModalOpen(true); setDeleteModalOpen(true);
setSelectedWorkflowId(data.id); setSelectedWorkflowId(data.id);
@@ -2076,7 +2153,7 @@ const Workflows = (props) => {
} }
return ( 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}> <Paper square style={paperAppStyle}>
{selectedCategory !== "" ? {selectedCategory !== "" ?
<Tooltip title={`Usecase Category: ${selectedCategory}`} placement="bottom"> <Tooltip title={`Usecase Category: ${selectedCategory}`} placement="bottom">
@@ -2462,6 +2539,8 @@ const Workflows = (props) => {
} }
const reader = new FileReader(); const reader = new FileReader();
var workflowids = []
// Waits for the read // Waits for the read
reader.addEventListener("load", (event) => { reader.addEventListener("load", (event) => {
var data = reader.result; var data = reader.result;
@@ -2498,6 +2577,7 @@ const Workflows = (props) => {
data.org = [] data.org = []
data.execution_org = {} data.execution_org = {}
workflowids.push(data.id)
// Actually create it // Actually create it
setNewWorkflow( setNewWorkflow(
@@ -2528,6 +2608,10 @@ const Workflows = (props) => {
} }
setLoadWorkflowsModalOpen(false); setLoadWorkflowsModalOpen(false);
if (workflowids.length > 0) {
setHighlightIds(workflowids)
}
}; };
const getWorkflowMeta = (data) => { const getWorkflowMeta = (data) => {
@@ -3682,7 +3766,7 @@ const Workflows = (props) => {
workflowDelay += 75 workflowDelay += 75
} else { } else {
return ( 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} /> <WorkflowPaper key={index} data={data} />
</Grid> </Grid>
) )