Merge branch '2.0.0' of github.com:Shuffle/Shuffle into 2.0.0

This commit is contained in:
Aditya
2024-06-28 19:20:15 +05:30
38 changed files with 8491 additions and 5523 deletions
+2
View File
@@ -11,11 +11,13 @@ Shuffle Automation
[Shuffle](https://shuffler.io) is an automation platform for and by the community, focusing on accessibility for anyone to automate. Security operations is complex, but it doesn't have to be.
[ Get training ](https://shuffler.io/training)
[_Key Features_](https://shuffler.io/docs/features) —
[_Community & Support_](https://discord.gg/B2CBzUm) —
[_Documentation_](https://shuffler.io/docs) —
[_Getting Started_](https://shuffler.io/docs/getting_started) —
[_Development_](https://github.com/shuffle/Shuffle/blob/master/.github/CONTRIBUTING.md)
[ Set up a demo call ](https://shuffler.io/contact)
Follow us on Twitter at [@shuffleio](https://twitter.com/shuffleio).
+12
View File
@@ -97,6 +97,7 @@ def md5_base64(a):
a = str(a)
foundhash = hashlib.md5(a.encode('utf-8')).hexdigest()
return base64.b64encode(foundhash.encode('utf-8'))
@shuffle_filters.register
def base64_encode(a):
@@ -107,6 +108,17 @@ def base64_encode(a):
except:
return base64.b64encode(a).decode()
@shuffle_filters.register
def random_element(a):
# Choose a random item from an array
a = list(a)
if len(a) == 0:
return ""
return random.choice(a)
@shuffle_filters.register
def base64_decode(a):
a = str(a)
+7 -5
View File
@@ -20,15 +20,15 @@ require (
github.com/gorilla/mux v1.8.1
github.com/h2non/filetype v1.1.3
github.com/satori/go.uuid v1.2.0
github.com/shuffle/shuffle-shared v0.6.40
github.com/shuffle/shuffle-shared v0.6.50
golang.org/x/crypto v0.22.0
google.golang.org/api v0.176.1
google.golang.org/grpc v1.63.2
gopkg.in/src-d/go-git.v4 v4.13.1
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.30.0
k8s.io/apimachinery v0.30.0
k8s.io/client-go v0.30.0
k8s.io/api v0.30.2
k8s.io/apimachinery v0.30.2
k8s.io/client-go v0.30.2
)
require (
@@ -58,7 +58,7 @@ require (
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/frikky/schemaless v0.0.11 // indirect
github.com/frikky/schemaless v0.0.13 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
@@ -100,6 +100,8 @@ require (
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/sashabaranov/go-openai v1.19.2 // indirect
github.com/sendgrid/rest v2.6.9+incompatible // indirect
github.com/sendgrid/sendgrid-go v3.14.0+incompatible // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.2.2 // indirect
+20
View File
@@ -160,6 +160,8 @@ github.com/frikky/schemaless v0.0.9 h1:RzNLPkJq5c4nlm5iLiTndFcbeQxdMGJIj266wSGt2
github.com/frikky/schemaless v0.0.9/go.mod h1:mooDxY+D6weHjhKvjy3+IE9S7P4g4cpNnidkdRv/cHQ=
github.com/frikky/schemaless v0.0.11 h1:c4r6CJX30XI+SoJdT9RlUd9qYSQlx6hvwGRtsypu+uM=
github.com/frikky/schemaless v0.0.11/go.mod h1:mooDxY+D6weHjhKvjy3+IE9S7P4g4cpNnidkdRv/cHQ=
github.com/frikky/schemaless v0.0.13 h1:ARiN9V7wr2VZXAr9JK5wvTbyPgpGrgeiL1VhR5MlgaQ=
github.com/frikky/schemaless v0.0.13/go.mod h1:mooDxY+D6weHjhKvjy3+IE9S7P4g4cpNnidkdRv/cHQ=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsouza/go-dockerclient v1.11.0 h1:4ZAk6W7rPAtPXm7198EFqA5S68rwnNQORxlOA5OurCA=
@@ -303,6 +305,8 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
@@ -427,6 +431,10 @@ github.com/sashabaranov/go-openai v1.19.2 h1:+dkuCADSnwXV02YVJkdphY8XD9AyHLUWwk6
github.com/sashabaranov/go-openai v1.19.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0=
github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
github.com/sendgrid/sendgrid-go v3.14.0+incompatible h1:KDSasSTktAqMJCYClHVE94Fcif2i7P7wzISv1sU6DUA=
github.com/sendgrid/sendgrid-go v3.14.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
@@ -443,6 +451,12 @@ github.com/shuffle/shuffle-shared v0.6.27 h1:q4qZD6bGZFIvZ5Y10unGr3N3rZ7OryWyvva
github.com/shuffle/shuffle-shared v0.6.27/go.mod h1:rWkh1eWdIx7OqQzJ1+JzF3Hck1X/Ty1WkUtjLrp+CU4=
github.com/shuffle/shuffle-shared v0.6.31 h1:MK1SW1pwjIP7hznq+mMlTPM1R3LIOfi1/bUL5xFSg/8=
github.com/shuffle/shuffle-shared v0.6.31/go.mod h1:rWkh1eWdIx7OqQzJ1+JzF3Hck1X/Ty1WkUtjLrp+CU4=
github.com/shuffle/shuffle-shared v0.6.46 h1:v/IXc+4V8DCWflGKGgaI37uuGnsylMjqKRylwNoXVrA=
github.com/shuffle/shuffle-shared v0.6.46/go.mod h1:rWkh1eWdIx7OqQzJ1+JzF3Hck1X/Ty1WkUtjLrp+CU4=
github.com/shuffle/shuffle-shared v0.6.47 h1:EOalfIIBX97uGkgRRPsdUApts6yCCcXf1iSphm6UoE0=
github.com/shuffle/shuffle-shared v0.6.47/go.mod h1:XVIcR2/GyIk+6qmlpOG3NvIao6kCOma36EdMym4AeSY=
github.com/shuffle/shuffle-shared v0.6.50 h1:MBeGAiBNkw9Eg+3YTJIlBOuskWntGvT0uefFUYOBhbY=
github.com/shuffle/shuffle-shared v0.6.50/go.mod h1:RAJiSFjmuKmijKTbbEf9A6Ojb+3/te7g71lED7JjPus=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
@@ -977,10 +991,16 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/api v0.30.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA=
k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE=
k8s.io/api v0.30.2 h1:+ZhRj+28QT4UOH+BKznu4CBgPWgkXO7XAvMcMl0qKvI=
k8s.io/api v0.30.2/go.mod h1:ULg5g9JvOev2dG0u2hig4Z7tQ2hHIuS+m8MNZ+X6EmI=
k8s.io/apimachinery v0.30.0 h1:qxVPsyDM5XS96NIh9Oj6LavoVFYff/Pon9cZeDIkHHA=
k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
k8s.io/apimachinery v0.30.2 h1:fEMcnBj6qkzzPGSVsAZtQThU62SmQ4ZymlXRC5yFSCg=
k8s.io/apimachinery v0.30.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
k8s.io/client-go v0.30.0 h1:sB1AGGlhY/o7KCyCEQ0bPWzYDL0pwOZO4vAtTSh/gJQ=
k8s.io/client-go v0.30.0/go.mod h1:g7li5O5256qe6TYdAMyX/otJqMhIiGgTapdLchhmOaY=
k8s.io/client-go v0.30.2 h1:sBIVJdojUNPDU/jObC+18tXWcTJVcwyqS9diGdWHk50=
k8s.io/client-go v0.30.2/go.mod h1:JglKSWULm9xlJLx4KCkfLLQ7XwtlbflV6uFFSHTMgVs=
k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
+1 -1
View File
@@ -3616,7 +3616,7 @@ func remoteOrgJobHandler(org shuffle.Org, interval int) error {
}
if org.SyncConfig.WorkflowBackup {
workflows, err := shuffle.GetAllWorkflowsByQuery(ctx, foundUser)
workflows, err := shuffle.GetAllWorkflowsByQuery(ctx, foundUser, 250, "")
if err != nil {
log.Printf("[ERROR] Failed getting backup workflows for org %s: %s", org.Id, err)
} else {
+3 -1
View File
@@ -1076,7 +1076,7 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request
// return workflowExecution, fmt.Sprintf("%s", err), nil
} else {
log.Printf("[ERROR] Failed in prepareExecution: '%s'", err)
return shuffle.WorkflowExecution{}, fmt.Sprintf("Failed starting workflow: %s", err), err
return shuffle.WorkflowExecution{}, fmt.Sprintf("Failed running: %s", err), err
}
}
@@ -3382,6 +3382,8 @@ func executeSingleAction(resp http.ResponseWriter, request *http.Request) {
return
}
debugUrl := fmt.Sprintf("/workflows/%s?execution_id=%s", workflowExecution.Workflow.ID, workflowExecution.ExecutionId)
resp.Header().Add("X-Debug-Url", debugUrl)
workflowExecution.Priority = 11
environments, err := shuffle.GetEnvironments(ctx, user.ActiveOrg.Id)
+10
View File
@@ -232,6 +232,11 @@ const AppGrid = (props) => {
removeQuery("q");
refine(event.currentTarget.value);
}}
onKeyDown={(event) => {
if(event.key === "Enter") {
event.preventDefault();
}
}}
limit={5}
/>
{/*isSearchStalled ? 'My search is stalled' : ''*/}
@@ -994,6 +999,11 @@ const AppGrid = (props) => {
onChange={(event) => {
setSearchQuery(event.currentTarget.value);
}}
onKeyDown={(event) => {
if(event.key === "Enter") {
event.preventDefault();
}
}}
limit={5}
/>
{/*isSearchStalled ? 'My search is stalled' : ''*/}
File diff suppressed because it is too large Load Diff
+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 {
+6
View File
@@ -136,6 +136,11 @@ const CreatorGrid = props => {
removeQuery("q")
refine(event.currentTarget.value)
}}
onKeyDown={(event) => {
if(event.key === "Enter") {
event.preventDefault();
}
}}
/>
{/*isSearchStalled ? 'My search is stalled' : ''*/}
</form>
@@ -147,6 +152,7 @@ const CreatorGrid = props => {
flexWrap: "wrap",
alignContent: "space-between",
marginTop: 5,
padding:"0px 180px",
}
const Hits = ({ hits }) => {
+7 -2
View File
@@ -75,7 +75,12 @@ const DiscordChat = props => {
fullWidth
value={currentRefinement}
onChange={(event) => refine(event.currentTarget.value)}
placeholder="Search Discord Chats"
onKeyDown={(event) => {
if(event.key === "Enter") {
event.preventDefault();
}
}}
placeholder="Search Discord Chats..."
style={{ backgroundColor: theme.palette.inputColor, borderRadius: borderRadius, margin: 10, width: "100%", }}
InputProps={{
style: {
@@ -143,7 +148,7 @@ const DiscordChat = props => {
const CustomHits = connectHits(Hits);
return (
<div style={{ width: "100%", textAlign:"center", position: "relative", height: "100%", }}>
<div style={{textAlign:"center", position: "relative", height: "100%", padding:"0px 240px" }}>
<InstantSearch searchClient={searchClient} indexName="discord_chat">
<div style={{ maxWidth: 450, margin: "auto", marginTop: 15, marginBottom: 5, }}>
<CustomSearchBox />
+6 -1
View File
@@ -124,6 +124,11 @@ const DocsGrid = props => {
removeQuery("q")
refine(event.currentTarget.value)
}}
onKeyDown={(event) => {
if(event.key === "Enter") {
event.preventDefault();
}
}}
limit={5}
/>
{/*isSearchStalled ? 'My search is stalled' : ''*/}
@@ -274,7 +279,7 @@ const DocsGrid = props => {
</Button>
</div>
*/}
<div style={{width: "100%", position: "relative", height: "100%",}}>
<div style={{width: "100%", position: "relative", height: "100%", padding: "0px 180px"}}>
<InstantSearch searchClient={searchClient} indexName="documentation">
<div style={{maxWidth: 450, margin: "auto", marginTop: 15, marginBottom: 15, }}>
<CustomSearchBox />
+218 -46
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>
@@ -339,26 +338,6 @@ const EditWorkflow = (props) => {
autoFocus
fullWidth
/>
<div style={{display: "flex", }}>
<TextField
onBlur={(event) => {
setDescription(event.target.value)
}}
InputProps={{
style: {
color: "white",
},
}}
maxRows={4}
color="primary"
defaultValue={innerWorkflow.description}
placeholder="Description"
multiline
label="Description"
margin="dense"
fullWidth
/>
</div>
<div style={{display: "flex", marginTop: 10, }}>
{usecases !== null && usecases !== undefined && usecases.length > 0 ?
<FormControl style={{flex: 1, marginRight: 5,}}>
@@ -426,27 +405,56 @@ const EditWorkflow = (props) => {
fullWidth
value={newWorkflowTags}
onChange={(chip) => {
console.log("Chip: ", chip)
//newWorkflowTags.push(chip);
setNewWorkflowTags(chip);
}}
onBlur={(event) => {
if (event.target.value.length === 0) {
return
}
if (newWorkflowTags.includes(event.target.value)) {
return
}
newWorkflowTags.push(event.target.value)
setNewWorkflowTags(newWorkflowTags)
setUpdate(Math.random())
}}
onAdd={(chip) => {
newWorkflowTags.push(chip);
setNewWorkflowTags(newWorkflowTags);
newWorkflowTags.push(chip)
setNewWorkflowTags(newWorkflowTags)
}}
onDelete={(chip, index) => {
console.log("Deleting: ", chip, index)
newWorkflowTags.splice(index, 1);
setNewWorkflowTags(newWorkflowTags);
setUpdate(Math.random());
newWorkflowTags.splice(index, 1)
setNewWorkflowTags(newWorkflowTags)
setUpdate(Math.random())
}}
/>
</div>
{showMoreClicked === true ?
<span style={{marginTop: 25, }}>
<div style={{marginTop: 50, }}>
<TextField
onBlur={(event) => {
setDescription(event.target.value)
}}
InputProps={{
style: {
color: "white",
},
}}
multiLine
rows={3}
color="primary"
defaultValue={innerWorkflow.description}
placeholder="Description"
multiline
label="Description"
margin="dense"
fullWidth
/>
<div style={{display: "flex"}}>
<FormControl style={{marginTop: 15, }}>
@@ -565,14 +573,24 @@ const EditWorkflow = (props) => {
fullWidth
/>
<Typography variant="body2" style={{marginTop: 50, }}>
MSSP Suborg Distribution (beta - contact support@shuffler.io)
<Divider style={{marginTop: 20, marginBottom: 20, }} />
<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 +602,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")) {
@@ -664,12 +680,13 @@ const EditWorkflow = (props) => {
</Link>
}
<Divider style={{marginTop: 20, marginBottom: 20, }} />
<Typography variant="h6" style={{marginTop: 50, }}>
Input fields
</Typography>
<Typography variant="body1" color="textSecondary" style={{marginBottom: 20, }}>
Input fields are fields that will be used during the startup of the workflow. These will be formatted in JSON and is most commonly used from the <a href={`/workflows/${workflow.id}/run`} rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>workflow run page</a>.
<Typography variant="body2" color="textSecondary" style={{marginBottom: 20, }}>
Input fields are fields that will be used during the startup of the workflow. These will be formatted in JSON and is most commonly used from the <a href={`/workflows/${workflow.id}/run`} rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>workflow run page</a>. If chosen in the User Input node, these will be required fields.
</Typography>
@@ -775,18 +792,173 @@ const EditWorkflow = (props) => {
>
<AddIcon style={{}} />
</Button>
</span>
<Divider style={{marginTop: 20, marginBottom: 20, }} />
<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>
</div>
: 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>
+98 -62
View File
@@ -44,9 +44,9 @@ const Files = (props) => {
const { globalUrl, userdata, serverside, selectedOrganization, isCloud,isSelectedFiles } = props;
const [files, setFiles] = React.useState([]);
const [selectedNamespace, setSelectedNamespace] = React.useState("default");
const [selectedCategory, setSelectedCategory] = React.useState("default");
const [openFileId, setOpenFileId] = React.useState(false);
const [fileNamespaces, setFileNamespaces] = React.useState([]);
const [fileCategories, setFileCategories] = React.useState([]);
const [fileContent, setFileContent] = React.useState("");
const [openEditor, setOpenEditor] = React.useState(false);
const [renderTextBox, setRenderTextBox] = React.useState(false);
@@ -57,6 +57,7 @@ const Files = (props) => {
const [downloadUrl, setDownloadUrl] = React.useState("https://github.com/shuffle/standards")
const [downloadBranch, setDownloadBranch] = React.useState("main");
const [downloadFolder, setDownloadFolder] = React.useState("translation_standards");
const [contentLoading, setContentLoading] = React.useState(false)
//const alert = useAlert();
const allowedFileTypes = ["txt", "py", "yaml", "yml","json", "html", "js", "csv", "log", "eml", "msg", "md", "xml", "sh", "bat", "ps1", "psm1", "psd1", "ps1xml", "pssc", "psc1", "response"]
@@ -67,8 +68,8 @@ const Files = (props) => {
console.log('do validate')
console.log("new namespace name->",event.target.value);
fileNamespaces.push(event.target.value);
setSelectedNamespace(event.target.value);
fileCategories.push(event.target.value);
setSelectedCategory(event.target.value);
setRenderTextBox(false);
}
@@ -107,12 +108,13 @@ const Files = (props) => {
const getFiles = (namespace) => {
var parsedurl = `${globalUrl}/api/v1/files`
if (namespace === undefined || namespace === "default") {
if (namespace === undefined || namespace === null || namespace === "default") {
} else if (namespace !== undefined && namespace !== null && namespace !== "") {
parsedurl = `${globalUrl}/api/v1/files/namespaces/${namespace}?ids=true`
} else if (selectedNamespace !== undefined && selectedNamespace !== null && selectedNamespace !== "default" && selectedNamespace !== "") {
parsedurl = `${globalUrl}/api/v1/files/namespaces/${selectedNamespace}?ids=true`
} else if (selectedCategory !== undefined && selectedCategory !== null && selectedCategory !== "default" && selectedCategory !== "") {
parsedurl = `${globalUrl}/api/v1/files/namespaces/${selectedCategory}?ids=true`
}
fetch(parsedurl, {
@@ -149,9 +151,11 @@ const Files = (props) => {
setFiles([]);
}
if (responseJson.namespaces !== undefined && responseJson.namespaces !== null && (fileNamespaces.length === 0 || responseJson.namespaces.length > fileNamespaces.length)) {
setFileNamespaces(responseJson.namespaces);
}
if (namespace === undefined || namespace === null || namespace === "default") {
if (responseJson.namespaces !== undefined && responseJson.namespaces !== null && (fileCategories.length === 0 || responseJson.namespaces.length > fileCategories.length)) {
setFileCategories(responseJson.namespaces)
}
}
})
.catch((error) => {
toast(error.toString());
@@ -159,7 +163,21 @@ const Files = (props) => {
};
useEffect(() => {
getFiles(selectedNamespace)
getFiles("default")
setTimeout(() => {
var category = selectedCategory
if (window.location.search.includes("category=")) {
const urlParams = new URLSearchParams(window.location.search)
category = urlParams.get("category")
}
if (category !== undefined && category !== null && category.length > 0 && category !== "default") {
setSelectedCategory(category)
}
getFiles(category)
}, 1000)
}, []);
const importStandardsFromUrl = (url, folder) => {
@@ -392,8 +410,9 @@ const Files = (props) => {
) {
toast("Failed to delete file: " + responseJson.reason);
}
setTimeout(() => {
getFiles();
getFiles(selectedCategory)
}, 1500);
})
.catch((error) => {
@@ -402,6 +421,8 @@ const Files = (props) => {
};
const readFileData = (file) => {
setContentLoading(true)
fetch(globalUrl + "/api/v1/files/" + file.id + "/content", {
method: "GET",
headers: {
@@ -411,6 +432,7 @@ const Files = (props) => {
credentials: "include",
})
.then((response) => {
setContentLoading(false)
if (response.status !== 200) {
console.log("Status not 200 for file :O!");
return "";
@@ -428,12 +450,12 @@ const Files = (props) => {
return respdata
})
.then((responseData) => {
setFileContent(responseData);
//console.log("filecontent state ",fileContent);
setFileContent(responseData);
//console.log("filecontent state ",fileContent);
})
.catch((error) => {
toast(error.toString());
setContentLoading(false)
toast(error.toString())
});
};
@@ -505,12 +527,12 @@ const Files = (props) => {
};
if (
selectedNamespace !== undefined &&
selectedNamespace !== null &&
selectedNamespace.length > 0 &&
selectedNamespace !== "default"
selectedCategory !== undefined &&
selectedCategory !== null &&
selectedCategory.length > 0 &&
selectedCategory !== "default"
) {
data.namespace = selectedNamespace;
data.namespace = selectedCategory;
}
fetch(globalUrl + "/api/v1/files/create", {
@@ -676,22 +698,23 @@ const Files = (props) => {
//setFile(fileObject)
//const files = event.target.files[0]
uploadFiles(event.target.files);
}}
/>
<Button
style={{ marginLeft: 5, marginRight: 15, backgroundColor:isSelectedFiles?"#2F2F2F":null,borderRadius:isSelectedFiles?200:null, width:isSelectedFiles?81:null, height:isSelectedFiles?40:null, boxShadow: isSelectedFiles?'none':null, }}
variant="contained"
color="primary"
onClick={() => getFiles()}
onClick={() => getFiles(selectedCategory)}
>
<CachedIcon />
</Button>
{fileNamespaces !== undefined &&
fileNamespaces !== null &&
fileNamespaces.length > 1 ? (
{fileCategories !== undefined &&
fileCategories !== null &&
fileCategories.length > 1 ? (
<FormControl style={{ minWidth: 150, maxWidth: 150 }}>
<InputLabel id="input-namespace-label">File Category</InputLabel>
<Select
@@ -703,18 +726,26 @@ const Files = (props) => {
maxWidth: 150,
float: "right",
}}
value={selectedNamespace}
value={selectedCategory}
onChange={(event) => {
setSelectedNamespace(event.target.value);
setSelectedCategory(event.target.value)
if (event.target.value === "all" || event.target.value === "default") {
getFiles()
} else {
getFiles(event.target.value)
}
// Add it to the url as a query
if (window.location.search.includes("category=")) {
const newurl = window.location.href.replace(/category=[^&]+/, `category=${event.target.value}`)
window.history.pushState({ path: newurl }, "", newurl)
} else {
window.history.pushState({ path: window.location.href }, "", `${window.location.href}&category=${event.target.value}`)
}
}}
>
{fileNamespaces.map((data, index) => {
{fileCategories.map((data, index) => {
return (
<MenuItem
key={index}
@@ -731,31 +762,31 @@ const Files = (props) => {
<div style={{display: "inline-flex", position:"relative"}}>
{renderTextBox ?
<Tooltip title={"Close"} style={{}} aria-label={""}>
<Button
style={{ marginLeft: 5, marginRight: 15 }}
color="primary"
onClick={() => {
setRenderTextBox(false);
console.log(" close clicked")
}}
>
<ClearIcon/>
</Button>
</Tooltip>
:
<Tooltip title={"Add new file category"} style={{}} aria-label={""}>
<Button
style={{ marginLeft: 5, marginRight: 15 }}
color="primary"
onClick={() => {
setRenderTextBox(true);
}}
>
<AddIcon/>
</Button>
</Tooltip> }
<Tooltip title={"Close"} style={{}} aria-label={""}>
<Button
style={{ marginLeft: 5, marginRight: 15 }}
color="primary"
onClick={() => {
setRenderTextBox(false);
console.log(" close clicked")
}}
>
<ClearIcon/>
</Button>
</Tooltip>
:
<Tooltip title={"Add new file category"} style={{}} aria-label={""}>
<Button
style={{ marginLeft: 5, marginRight: 15 }}
color="primary"
onClick={() => {
setRenderTextBox(true);
}}
>
<AddIcon/>
</Button>
</Tooltip>
}
{renderTextBox && <TextField
onKeyPress={(event)=>{
@@ -783,6 +814,7 @@ const Files = (props) => {
isFileEditor = {true}
key = {fileContent} //https://reactjs.org/docs/reconciliation.html#recursing-on-children
runUpdateText = {runUpdateText}
contentLoading = {contentLoading}
/>
{isSelectedFiles?null:
<Divider
@@ -795,15 +827,17 @@ const Files = (props) => {
<List style={{borderRadius: isSelectedFiles?8:null, border:isSelectedFiles?"1px solid #494949":null, marginTop:isSelectedFiles?24:null}}>
<ListItem style={{width:isSelectedFiles?"100%":null, borderBottom:isSelectedFiles?"1px solid #494949":null}}>
{/*
<ListItemText
primary="Updated"
style={{ maxWidth: 185, minWidth: 185 }}
/>
*/}
<ListItemText
primary="Name"
style={{
maxWidth: 150,
minWidth: 150,
maxWidth: 250,
minWidth: 250,
overflow: "hidden",
marginLeft: 10,
}}
@@ -814,7 +848,7 @@ const Files = (props) => {
/>
<ListItemText
primary="Md5"
style={{ minWidth: isSelectedFiles?210:250, maxWidth: isSelectedFiles?210:250, overflow: "hidden" }}
style={{ minWidth: 300, maxWidth: 300, overflow: "hidden" }}
/>
<ListItemText
primary="Status"
@@ -832,7 +866,7 @@ const Files = (props) => {
file.namespace = "default";
}
if (file.namespace !== selectedNamespace) {
if (file.namespace !== selectedCategory) {
return null;
}
@@ -853,6 +887,7 @@ const Files = (props) => {
overflow: "hidden",
}}
>
{/*
<ListItemText
style={{
maxWidth: isSelectedFiles ? 170:225,
@@ -861,10 +896,11 @@ const Files = (props) => {
}}
primary={new Date(file.updated_at * 1000).toISOString()}
/>
*/}
<ListItemText
style={{
maxWidth: 150,
minWidth: 150,
maxWidth: 250,
minWidth: 250,
overflow: "hidden",
marginLeft: 10,
}}
@@ -928,8 +964,8 @@ const Files = (props) => {
<ListItemText
primary={file.md5_sum}
style={{
minWidth: isSelectedFiles?200:300,
maxWidth: isSelectedFiles?200:300,
minWidth: 300,
maxWidth: 300,
marginLeft:isSelectedFiles? 15:null,
overflow: isSelectedFiles?"auto":"hidden",
}}
@@ -1076,7 +1112,7 @@ const Files = (props) => {
disabled={file.status !== "active"}
style = {{padding: "6px"}}
onClick={() => {
deleteFile(file);
deleteFile(file)
}}
>
<DeleteIcon
+19 -13
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);
@@ -537,7 +546,7 @@ const Header = (props) => {
rel="noopener noreferrer"
target="_blank"
>
<Tooltip color="primary" title={"Join the Shuffle Automation Community"} placement="left">
<Tooltip color="primary" title={"Shuffle is Open Source, and has a thriving Discord Automation Community"} placement="left">
<IconButton
color="primary"
style={{}}
@@ -545,11 +554,8 @@ const Header = (props) => {
aria-haspopup="true"
onClick={(event) => { }}
>
<img
alt="Discord Community Join"
src={"/images/social/discord.png"}
style={{ height: 30, width: 30 }}
/>
<svg viewBox="0 0 256 199" width="256" height="199" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid" style={{height: 30, width: 30, }}><path d="M216.856 16.597A208.502 208.502 0 0 0 164.042 0c-2.275 4.113-4.933 9.645-6.766 14.046-19.692-2.961-39.203-2.961-58.533 0-1.832-4.4-4.55-9.933-6.846-14.046a207.809 207.809 0 0 0-52.855 16.638C5.618 67.147-3.443 116.4 1.087 164.956c22.169 16.555 43.653 26.612 64.775 33.193A161.094 161.094 0 0 0 79.735 175.3a136.413 136.413 0 0 1-21.846-10.632 108.636 108.636 0 0 0 5.356-4.237c42.122 19.702 87.89 19.702 129.51 0a131.66 131.66 0 0 0 5.355 4.237 136.07 136.07 0 0 1-21.886 10.653c4.006 8.02 8.638 15.67 13.873 22.848 21.142-6.58 42.646-16.637 64.815-33.213 5.316-56.288-9.08-105.09-38.056-148.36ZM85.474 135.095c-12.645 0-23.015-11.805-23.015-26.18s10.149-26.2 23.015-26.2c12.867 0 23.236 11.804 23.015 26.2.02 14.375-10.148 26.18-23.015 26.18Zm85.051 0c-12.645 0-23.014-11.805-23.014-26.18s10.148-26.2 23.014-26.2c12.867 0 23.236 11.804 23.015 26.2 0 14.375-10.148 26.18-23.015 26.18Z" fill="#dadae1"/></svg>
{/*#f865f2*/}
</IconButton>
</Tooltip>
</a>
@@ -1590,17 +1596,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>
+12 -19
View File
@@ -471,32 +471,26 @@ const AuthenticationOauth2 = (props) => {
state += `%26refresh_uri%3d${authentication_url}`;
}
// No prompt forcing
//var url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=login&scope=${resources}&state=${state}&access_type=offline`;
// FIXME: Should this be =consent?
var defaultPrompt = "login"
if (prompt !== undefined && prompt !== null && prompt.length > 0) {
defaultPrompt = prompt
}
defaultPrompt = prompt
}
var url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=${defaultPrompt}&scope=${resources}&state=${state}&access_type=offline`;
var url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=${defaultPrompt}&scope=${resources}&state=${state}&access_type=offline`;
if (admin_consent === true) {
console.log("Running Oauth2 WITH admin consent")
//url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=consent&scope=${resources}&state=${state}&access_type=offline`;
url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=admin_consent&scope=${resources}&state=${state}&access_type=offline`;
}
if (admin_consent === true) {
console.log("Running Oauth2 WITH admin consent")
//url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=consent&scope=${resources}&state=${state}&access_type=offline`;
url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&prompt=admin_consent&scope=${resources}&state=${state}&access_type=offline`;
}
console.log("OAUTH2 URL: ", url)
// Force new consent
// Force new consent
//const url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${resources}&prompt=consent&state=${state}&access_type=offline`;
// Admin consent
// Admin consent
//const url = `https://accounts.zoho.com/oauth/v2/auth?response_type=code&client_id=${client_id}&scope=AaaServer.profile.Read&redirect_uri=${redirectUri}&prompt=consent`
// &resource=https%3A%2F%2Fgraph.microsoft.com&
// FIXME: Awful, but works for prototyping
// How can we get a callback properly realtime?
// How can we properly try-catch without breaks on error?
try {
var newwin = window.open(url, "", "width=582,height=700");
//console.log(newwin)
@@ -997,7 +991,6 @@ const AuthenticationOauth2 = (props) => {
color: "white",
padding: 5,
minWidth: 300,
maxWidth: 300,
}}
onChange={(e, value) => {
//handleScopeChange(e)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+18 -2
View File
@@ -27,8 +27,9 @@ const Priorities = (props) => {
const [selectedWorkflow, setSelectedWorkflow] = React.useState("NO HIGHLIGHT");
const [selectedExecutionId, setSelectedExecutionId] = React.useState("NO HIGHLIGHT");
let navigate = useNavigate();
const [highlightKMS, setHighlightKMS] = React.useState(false)
let navigate = useNavigate();
useEffect(() => {
getFramework()
@@ -36,6 +37,12 @@ const Priorities = (props) => {
const urlParams = new URLSearchParams(window.location.search)
const workflow = urlParams.get("workflow")
const execution_id = urlParams.get("execution_id")
const kms = urlParams.get("kms")
if (kms !== null && kms !== undefined && kms.length > 0 && kms === "true") {
toast.info("KMS-related notifications are highlighted.")
setHighlightKMS(true)
}
if (execution_id !== null) {
setSelectedExecutionId(execution_id)
@@ -217,7 +224,16 @@ const Priorities = (props) => {
var orgId = "";
const highlighted = selectedExecutionId === "" && selectedWorkflow === "" ? false : data.reference_url === undefined || data.reference_url === null || data.reference_url.length === 0 ? false : data.reference_url.includes(selectedExecutionId) || data.reference_url.includes(selectedWorkflow)
var highlighted = selectedExecutionId === "" && selectedWorkflow === "" ? false : data.reference_url === undefined || data.reference_url === null || data.reference_url.length === 0 ? false : data.reference_url.includes(selectedExecutionId) || data.reference_url.includes(selectedWorkflow)
if (!highlighted && highlightKMS) {
if (data.title !== undefined && data.title !== null && data.title.toLowerCase().includes("kms")) {
highlighted = true
} else if (data.description !== undefined && data.description !== null && data.description.toLowerCase().includes("kms")) {
highlighted = true
}
}
if (userdata.orgs !== undefined) {
const foundOrg = userdata.orgs.find((org) => org.id === data["org_id"]);
+2 -2
View File
@@ -88,12 +88,12 @@ const SearchData = props => {
const textFieldRef = useRef(null);
const keyPressHandler = (e) => {
if (e.which === 13) {
if (e.key === "Enter") {
e.preventDefault();
// navigate(`/search?q=${currentRefinement}`, { state: value, replace: true });
// setModalOpen(false);
const trimmedValue = inputValue.trim();
if (trimmedValue !== '') {
e.preventDefault();
navigate(`/search?q=${trimmedValue}`, { state: trimmedValue, replace: true });
setModalOpen(false);
}
+18 -7
View File
@@ -107,10 +107,9 @@ const CodeEditor = (props) => {
getParents,
fieldname,
contentLoading,
} = props
const [localcodedata, setlocalcodedata] = React.useState(codedata === undefined || codedata === null || codedata.length === 0 ? "" : codedata);
//const { setContainer } = useCodeMirror({
@@ -627,8 +626,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",
})
@@ -989,6 +988,17 @@ const CodeEditor = (props) => {
},
}}
>
{contentLoading === true ?
<Tooltip
color="primary"
title={`The File content is loading. Please wait a moment.`}
placement="top"
>
<CircularProgress style={{position: "absolute", right: 106, top: 6, }}/>
</Tooltip>
: null}
<Tooltip
color="primary"
title={`Move window`}
@@ -1571,7 +1581,8 @@ const CodeEditor = (props) => {
>
<div>
<span style={{color: "white"}}>
Expected Output
{selectedAction === undefined ? "" : selectedAction.name === "execute_python" ? "Code to run" : "Expected Output"}
</span>
</div>
@@ -1588,7 +1599,7 @@ const CodeEditor = (props) => {
border: `1px solid ${theme.palette.primary.main}`,
position: "absolute",
top: 24,
right: 65,
right: 100,
maxHeight: 35,
minWidth: 70,
}}
@@ -1599,7 +1610,7 @@ const CodeEditor = (props) => {
{executing ?
<CircularProgress style={{height: 18, width: 18, }} />
:
<span>Try it <PlayArrowIcon style={{height: 18, width: 18, marginBottom: -4, marginLeft: 5, }} /> </span>
<span>{selectedAction === undefined ? "" : selectedAction.name === "execute_python" ? "Run Python Code" : "Try it"}<PlayArrowIcon style={{height: 18, width: 18, marginBottom: -4, marginLeft: 5, }} /> </span>
}
</Button>
</Tooltip>
+10
View File
@@ -204,6 +204,9 @@ const AppGrid = props => {
style={{backgroundColor: theme.palette.inputColor, borderRadius: borderRadius, margin: 10, width: "100%",}}
InputProps={{
style:{
color: "white",
fontSize: "1em",
height: 50,
},
startAdornment: (
<InputAdornment position="start">
@@ -221,6 +224,11 @@ const AppGrid = props => {
removeQuery("q")
refine(event.currentTarget.value)
}}
onKeyDown={(event) => {
if(event.key === "Enter") {
event.preventDefault();
}
}}
limit={5}
/>
: null}
@@ -233,6 +241,8 @@ const AppGrid = props => {
flexWrap: "wrap",
alignContent: "space-between",
marginTop: 5,
padding: "0px 180px",
width:"auto"
}
var workflowDelay = -50
+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: {
+2 -4
View File
@@ -18,11 +18,9 @@ code {
margin-right: -13px;
}
.toc:hover {
background-color: gray;
color: white;
table th, table td {
border: 1px solid;
}
/* .cm-string{
z-index: -1;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+13 -8
View File
@@ -955,7 +955,6 @@ const Apps = (props) => {
</Link>
: null
console.log("Sharing config: ", sharingConfiguration);
const activateButton =
selectedApp.generated && !selectedApp.activated ? (
<div>
@@ -1281,10 +1280,15 @@ const Apps = (props) => {
{isCloud && !internalIds.includes(selectedApp.name.toLowerCase()) ?
<Tooltip title="Deactivates this app for the current organisation. This means the app will not be usable again until you re-activate it." placement="top">
<Button
variant="contained"
variant={selectedApp.reference_org === userdata.active_org.id ? "outlined" : "contained"}
component="label"
color="primary"
onClick={() => {
if (selectedApp.reference_org === userdata.active_org.id) {
toast.info("Can't deactivate apps made in this org. Please contact support if you want to deactivate this app.")
return
}
const tmpurl = new URL(window.location.href);
const searchParams = tmpurl.searchParams;
const queryID = searchParams.get("queryID");
@@ -1316,7 +1320,7 @@ const Apps = (props) => {
console.log("No query to handle when activating");
}
activateApp(selectedApp.id, true)
activateApp(selectedApp.id, true, true)
}}
style={{ height: 35, marginTop: 0, marginLeft: 10, }}
>
@@ -1845,9 +1849,12 @@ const Apps = (props) => {
)
}
const activateApp = (appid, refresh) => {
const activateApp = (appid, refresh, deactivate) => {
const appExists = userdata.active_apps !== undefined && userdata.active_apps !== null && userdata.active_apps.includes(appid)
const url = appExists ? `${globalUrl}/api/v1/apps/${appid}/deactivate` : `${globalUrl}/api/v1/apps/${appid}/activate`
const url = deactivate === true ?
`${globalUrl}/api/v1/apps/${appid}/deactivate`
:
appExists ? `${globalUrl}/api/v1/apps/${appid}/deactivate` : `${globalUrl}/api/v1/apps/${appid}/activate`
fetch(url, {
method: 'GET',
@@ -1876,7 +1883,7 @@ const Apps = (props) => {
if (appExists) {
toast("App deactivated for your organization! Existing workflows with the app will continue to work.")
} else {
toast("App activated for your organization!")
toast("App activation changed for your organization!")
}
if (refresh === true) {
@@ -2021,8 +2028,6 @@ const Apps = (props) => {
userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id,
}
])
} else {
console.log("No query to handle when activating")
}
activateApp(hit.objectID, true)
+13 -8
View File
@@ -1,15 +1,13 @@
import React, { useEffect, useLayoutEffect, useRef, useState } from "react"
import { toast } from 'react-toastify';
import Markdown from 'react-markdown'
import theme from '../theme.jsx';
import ReactJson from "react-json-view";
import { isMobile } from "react-device-detect";
import { BrowserView, MobileView } from "react-device-detect";
import { useParams, useNavigate, Link } from "react-router-dom";
import { validateJson, GetIconInfo } from "../views/Workflows.jsx";
import remarkGfm from 'remark-gfm'
import {
Grid,
TextField,
@@ -26,7 +24,6 @@ import {
ListItemButton,
ListItemText
} from "@mui/material";
import {
Link as LinkIcon,
Edit as EditIcon,
@@ -35,7 +32,6 @@ import {
FileCopy as FileCopyIcon
} from "@mui/icons-material";
import { fontGrid } from "@mui/material/styles/cssUtils.js";
import { active } from "d3";
const Body = {
//maxWidth: 1000,
@@ -46,6 +42,7 @@ const Body = {
height: "100%",
color: "white",
position: "relative",
paddingTop: 40,
//textAlign: "center",
};
@@ -155,9 +152,16 @@ export const OuterLink = (props) => {
export const Img = (props) => {
return <img style={{ borderRadius: theme.palette.borderRadius, width: 750, maxWidth: "100%", marginTop: 15, marginBottom: 15, }} alt={props.alt} src={props.src} />;
return(
<img
style={{border: "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, width: 750, maxWidth: "100%", marginTop: 15, marginBottom: 15, }}
alt={props.alt}
src={props.src}
/>
)
}
export const CodeHandler = (props) => {
const propvalue = props.value !== undefined && props.value !== null ? props.value : props.children !== undefined && props.children !== null && props.children.length > 0 ? props.children[0] : ""
@@ -369,8 +373,7 @@ const Docs = (defaultprops) => {
hash = hash.split('?')[0]
}
if (hash) {
console.log("HASH: ", hash)
const element = document.getElementById(hash)
const element = document.getElementById(hash.toLowerCase())
if (element) {
element.scrollIntoView({
behavior: "instant",
@@ -1022,8 +1025,10 @@ const Docs = (defaultprops) => {
<Markdown
components={markdownComponents}
id="markdown_wrapper"
className={"style.reactMarkdown"}
escapeHtml={false}
skipHtml={false}
remarkPlugins={[remarkGfm]}
style={{
maxWidth: "100%", minWidth: "100%",
}}
+1 -1
View File
@@ -166,7 +166,7 @@ const LoginDialog = (props) => {
} else {
if (responseJson["reason"] === "MFA_REDIRECT") {
setLoginInfo(
"MFA required. Please the 6-digit code from your authenticator"
"MFA required. Please enter the 6-digit code from your authenticator"
);
setMFAField(true);
return;
+191 -54
View File
@@ -2,6 +2,7 @@
import React, {useState, useEffect} from 'react';
import ReactDOM from "react-dom"
import { ToastContainer, toast } from "react-toastify"
import { useInterval } from "react-powerhooks";
import { makeStyles } from '@mui/material/styles';
import { useNavigate, Link, useParams } from "react-router-dom";
@@ -43,7 +44,8 @@ const bodyDivStyle = {
const RunWorkflow = (defaultprops) => {
const { globalUrl, isLoaded, isLoggedIn, setIsLoggedIn, setCookie, register, serverside } = defaultprops;
let navigate = useNavigate();
let navigate = useNavigate();
const [_, setUpdate] = useState(""); // Used to force rendring, don't remove
const [message, setMessage] = useState("");
const [workflow, setWorkflow] = React.useState({});
const [executionRequest, setExecutionRequest] = React.useState({});
@@ -55,6 +57,7 @@ const RunWorkflow = (defaultprops) => {
const [selectedOrganization, setSelectedOrganization] = React.useState(undefined);
const [apps, setApps] = React.useState([]);
const [buttonClicked, setButtonClicked] = React.useState("");
const [foundSourcenode, setFoundSourcenode] = React.useState(undefined);
const boxStyle = {
color: "white",
@@ -340,6 +343,8 @@ const RunWorkflow = (defaultprops) => {
event.preventDefault()
}
console.log("ONSUBMIT: ", event, execution_id, authorization, answer)
stop()
setMessage("")
setExecutionLoading(true)
@@ -389,12 +394,27 @@ const RunWorkflow = (defaultprops) => {
url += `?reference_execution=${execution_id}&authorization=${authorization}&answer=${answer}`
data = {}
fetchBody.method = "GET"
if (executionArgument !== undefined && executionArgument !== null) {
try {
if (typeof executionArgument === "string") {
url += "&note=" + executionArgument
} else {
url += "&note=" + JSON.stringify(executionArgument)
}
} catch (e) {
url += "&note=" + executionArgument
}
}
} else {
fetchBody.method = "POST"
fetchBody.body = JSON.stringify(data)
}
console.log("Pre request: ", url, fetchBody)
// IF there is an execution argument, we should use it
console.log("FULL URL: ", url)
fetch(url, fetchBody)
.then((response) => {
if (response.status !== 200 && response.status !== 201) {
@@ -408,16 +428,26 @@ const RunWorkflow = (defaultprops) => {
})
start();
return
return response.json()
}
}
return response.json();
return response.json()
})
.then(responseJson => {
setExecutionLoading(false)
if (responseJson["success"] === false) {
if (responseJson.success === false) {
console.log("Failed sending execution request")
if (responseJson.reason !== undefined && responseJson.reason !== null) {
toast.warn(responseJson.reason)
}
stop()
setMessage("")
setExecutionData({})
setExecutionInfo("")
setExecutionRunning(false)
setExecutionRequest({})
} else {
console.log("Started execution")
@@ -426,17 +456,24 @@ const RunWorkflow = (defaultprops) => {
} else {
setExecutionRunning(true);
setExecutionRequest(responseJson)
start();
start()
}
}
})
.catch(error => {
//setExecutionInfo("Error in workflow startup: " + error)
toast.warn("Error in workflow startup: " + error)
stop()
setMessage("")
setExecutionData({})
setExecutionInfo("")
setExecutionLoading(false)
})
}
const getWorkflow = (workflow_id) => {
const getWorkflow = (workflow_id, selectedNode) => {
fetch(globalUrl + "/api/v1/workflows/" + workflow_id, {
method: "GET",
headers: {
@@ -480,6 +517,48 @@ const RunWorkflow = (defaultprops) => {
setExecutionArgument(newexec)
}
if (selectedNode !== undefined && selectedNode !== null && selectedNode.length > 0) {
var found = false
for (var actionkey in responseJson.actions) {
if (responseJson.actions[actionkey].id === selectedNode) {
found = true
setFoundSourcenode(responseJson.actions[actionkey])
break
}
}
if (!found) {
for (var triggerkey in responseJson.triggers) {
if (responseJson.triggers[triggerkey].id !== selectedNode) {
continue
}
setFoundSourcenode(responseJson.triggers[triggerkey])
if (responseJson.input_questions !== undefined && responseJson.input_questions !== null && responseJson.input_questions.length > 0 && responseJson.triggers[triggerkey].trigger_type === "USERINPUT") {
// Look for input questions param
for (var paramkey in responseJson.triggers[triggerkey].parameters) {
if (responseJson.triggers[triggerkey].parameters[paramkey].name === "input_questions") {
var relevantquestions = []
for (var questionkey in responseJson.input_questions) {
if (responseJson.triggers[triggerkey].parameters[paramkey].value.includes(responseJson.input_questions[questionkey].name)) {
relevantquestions.push(responseJson.input_questions[questionkey])
}
}
responseJson.input_questions = relevantquestions
}
}
}
break
}
}
}
handleGetOrg(responseJson.org_id)
setWorkflow(responseJson);
})
@@ -489,7 +568,7 @@ const RunWorkflow = (defaultprops) => {
};
const { start, stop } = useInterval({
duration: 3000,
duration: 1500,
startImmediate: true,
callback: () => {
fetchUpdates(executionRequest.execution_id, executionRequest.authorization)
@@ -526,15 +605,11 @@ const RunWorkflow = (defaultprops) => {
for (var key in responseJson.results) {
if (responseJson.results[key].status === "WAITING") {
console.log("Found: ", responseJson.results[key])
const validate = validateJson(responseJson.results[key].result)
console.log("Validate: ", validate)
if (validate.valid && typeof validate.result === "string") {
validate.result = JSON.parse(validate.result)
}
console.log("Newresult: ", validate.result)
if (validate.result["information"] !== undefined && validate.result["information"] !== null) {
setWorkflowQuestion(validate.result["information"])
}
@@ -623,15 +698,15 @@ const RunWorkflow = (defaultprops) => {
return
}
fetch(globalUrl + "/api/v1/streams/results", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(innerRequest),
credentials: "include",
})
fetch(globalUrl + "/api/v1/streams/results", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(innerRequest),
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for stream results :O!");
@@ -651,11 +726,14 @@ const RunWorkflow = (defaultprops) => {
});
};
const answer = new URLSearchParams(window.location.search).get("answer")
const execution_id = new URLSearchParams(window.location.search).get("reference_execution")
const authorization = new URLSearchParams(window.location.search).get("authorization")
const searchParams = new URLSearchParams(window.location.search)
const answer = searchParams.get("answer")
const execution_id = searchParams.get("reference_execution")
const authorization = searchParams.get("authorization")
const sourceNode = searchParams.get("source_node")
useEffect(() => {
getWorkflow(props.match.params.key)
getWorkflow(props.match.params.key, sourceNode)
if (execution_id !== undefined && execution_id !== null && authorization !== undefined && authorization !== null) {
console.log("Get execution: ", execution_id)
fetchUpdates(execution_id, authorization, true)
@@ -667,13 +745,60 @@ const RunWorkflow = (defaultprops) => {
}, [])
useEffect(() => {
if (executionData === undefined || executionData === null || executionData === {}) {
return
}
if (foundSourcenode === undefined || foundSourcenode === null || foundSourcenode === {}) {
return
}
if (foundSourcenode.trigger_type !== "USERINPUT") {
return
}
if (executionData.results === undefined || executionData.results === null || executionData.results.length === 0) {
return
}
for (var resultkey in executionData.results) {
const result = executionData.results[resultkey]
if (result.action.id !== foundSourcenode.id) {
continue
}
var parsedresult = result.result
try {
parsedresult = JSON.parse(parsedresult)
} catch (e) {
console.log("Error parsing result: ", e)
}
if (result.status !== "WAITING") {
if (parsedresult.click_info !== undefined && parsedresult.click_info !== null) {
if (parsedresult.click_info.user !== undefined && parsedresult.click_info.user !== null && parsedresult.click_info.user.length > 0) {
setMessage("Already answered by " + parsedresult.click_info.user)
}
} else {
setMessage("Answered.")
}
}
break
}
}, [executionData, foundSourcenode])
const buttonBackground = "linear-gradient(to right, #f86a3e, #f34079)"
const buttonStyle = {borderRadius: 25, height: 50, fontSize: 18, backgroundImage: handleValidateForm(executionArgument) || executionLoading ? buttonBackground : "grey", color: "white"}
//console.log("execdata: ", executionData)
const disabledButtons = message.length > 0 || executionData.status === "FINISHED" || executionData.status === "ABORTED"
//{disabledButtons ? null :
const organization = selectedOrganization !== undefined && selectedOrganization !== null ? selectedOrganization.name : "Unknown"
const contact = selectedOrganization !== undefined && selectedOrganization !== null && selectedOrganization.org !== undefined && selectedOrganization.org !== null? selectedOrganization.org : "support@shuffler.io"
//const contact = selectedOrganization !== undefined && selectedOrganization !== null && selectedOrganization.contact !== undefined && selectedOrganization.contact !== null? selectedOrganization.contact : "support@shuffler.io"
@@ -724,49 +849,54 @@ const RunWorkflow = (defaultprops) => {
<Typography variant="h6" style={{marginBottom: 10, marginTop: 50, textAlign: "center", }}>
{organization}
</Typography>
<Typography variant="body1" color="textSecondary" style={{marginBottom: 15, marginTop: 0, textAlign: "center",}}>
{contact}
</Typography>
<Divider style={{marginTop: 20, marginBottom: 20, }}/>
<Typography color="textSecondary">{message}</Typography>
{disabledButtons && message.length > 0 ? null :
<Typography color="textSecondary" style={{textAlign: "center", }}>
{message}
</Typography>
}
{answer !== undefined && answer !== null ? null :
<Typography variant="h6" style={{marginBottom: 15, }}><b>{workflow.name}</b></Typography>
<Typography variant="h6" style={{marginBottom: 15, textAlign: "center", }}><b>{workflow.name}</b></Typography>
}
{workflowQuestion.length > 0 ?
<Typography variant="body1" style={{ marginBottom: 35, marginTop: 30, marginRight: 15, textAlign: "center", whiteSpace: "pre-line", }}>
{workflowQuestion}
</Typography>
<div style={{
backgroundColor: theme.palette.inputColor,
padding: 20,
borderRadius: theme.palette.borderRadius,
marginBottom: 35,
marginTop: 30,
}}>
<Typography variant="body1" style={{ marginRight: 15, textAlign: "center", whiteSpace: "pre-line", }}>
{workflowQuestion}
</Typography>
</div>
: null}
{workflow.input_questions !== undefined && workflow.input_questions !== null && workflow.input_questions.length > 0 ?
<div style={{marginBottom: 5, }}>
{workflow.input_questions.map((question, index) => {
return (
<div style={{marginBottom: 5}}>
{question.name}
<TextField
color="primary"
style={{backgroundColor: theme.palette.inputColor, marginTop: 5, }}
multiLine
maxRows={2}
InputProps={{
style:{
height: "50px",
color: "white",
fontSize: "1em",
},
}}
label={question.value}
required
disabled={disabledButtons}
fullWidth={true}
placeholder=""
id="emailfield"
margin="normal"
variant="outlined"
onChange={(e) => {
onBlur={(e) => {
//setExecutionArgument(e.target.value)
executionArgument[question.value] = e.target.value
setUpdate(Math.random())
}}
/>
</div>
@@ -813,20 +943,27 @@ const RunWorkflow = (defaultprops) => {
</Typography>
: null}
</span>
:
answer !== undefined && answer !== null ?
((answer !== undefined && answer !== null) || (foundSourcenode !== undefined && foundSourcenode !== null)) ?
<span style={{marginTop: 20, }}>
<Typography variant="body1" style={{textAlign: "center", marginTop: 30, marginBottom: 20, }}>
{disabledButtons ? "Already answered. Nothing to do." : ""}
</Typography>
{disabledButtons && message.length > 0 ?
<Typography variant="body1" style={{textAlign: "center", marginTop: 30, marginBottom: 20, }}>
{message}. You may close this window.
</Typography>
:
<Typography variant="body1" style={{textAlign: "center", marginTop: 30, marginBottom: 20, }}>
{disabledButtons ? "Answered. You may close this window." : ""}
</Typography>
}
{disabledButtons ? null :
<Typography variant="body2" color="textSecondary" style={{textAlign: "center", marginTop: 10, }}>
What do you want to do?
</Typography>
}
<div fullWidth style={{width: "100%", marginTop: 10, marginBottom: 10, display: "flex", }}>
<Button fullWidth id="continue_execution" variant="contained" disabled={disabledButtons} color="primary" style={{border: answer === "true" ? "2px solid rgba(255,255,255,0.6)" : null, flex: 1,}} onClick={() => {
<Button fullWidth id="continue_execution" variant="contained" disabled={disabledButtons} color="primary" style={{flex: 1,}} onClick={() => {
onSubmit(null, execution_id, authorization, true)
setButtonClicked("FINISHED")
@@ -837,7 +974,7 @@ const RunWorkflow = (defaultprops) => {
<Typography variant="body1" style={{marginLeft: 3, marginRight: 3, marginTop: 3, }}>
&nbsp;or&nbsp;
</Typography>
<Button fullWidth id="abort_execution" variant="contained" color="primary" disabled={disabledButtons} style={{border: answer !== "true" ? "2px solid rgba(255,255,255,0.6)" : null, flex: 1, }} onClick={() => {
<Button fullWidth id="abort_execution" variant="contained" color="primary" disabled={disabledButtons} style={{ flex: 1, }} onClick={() => {
onSubmit(null, execution_id, authorization, false)
setButtonClicked("ABORTED")
@@ -856,7 +993,7 @@ const RunWorkflow = (defaultprops) => {
</div>
}
{buttonClicked !== undefined && buttonClicked !== null && buttonClicked !== "finished" && buttonClicked.length > 0 ?
{/*buttonClicked !== undefined && buttonClicked !== null && buttonClicked !== "finished" && buttonClicked.length > 0 ?
<img id="finalize_gif" src="/images/finalize.gif" alt="finalize workflow animation" style={{width: 150, marginLeft: 125, borderRadius: theme.palette.borderRadius, }}
onLoad={() => {
console.log("Img loaded.")
@@ -868,7 +1005,7 @@ const RunWorkflow = (defaultprops) => {
}}
/>
: null}
: null*/}
<div style={{marginTop: "10px"}}>
{executionInfo}
+8 -7
View File
@@ -43,11 +43,10 @@ const Search = (props) => {
const params = Object.fromEntries(urlSearchParams.entries());
const foundTab = params["tab"];
if (foundTab !== null && foundTab !== undefined) {
for (var key in Object.keys(views)) {
for (let key in views) {
const value = views[key];
console.log(key, value);
if (value === foundTab) {
setConfig("", key);
setConfig(null, key);
break;
}
}
@@ -130,7 +129,7 @@ const Search = (props) => {
display: "flex",
flexDirection: "column",
overflowX: "hidden",
minHeight: 400,
minHeight: 400
};
const views = {
@@ -138,7 +137,9 @@ const Search = (props) => {
1: "workflows",
2: "docs",
3: "creators",
4: "discord"
};
const setConfig = (event, inputValue) => {
const newValue = parseInt(inputValue);
@@ -216,7 +217,7 @@ const Search = (props) => {
<div style={boxStyle}>
<Tabs
style={{
width: 741,
width: 757,
margin: isHeader ? null : "auto",
marginTop: hidemargins === true ? 0 : isHeader ? null : 25,
backgroundColor: "rgba(33, 33, 33, 1)",
@@ -227,8 +228,8 @@ const Search = (props) => {
textColor="secondary"
onChange={setConfig}
aria-label="disabled tabs example"
variant="scrollable"
scrollButtons="auto"
// variant="scrollable"
// scrollButtons="auto"
classes={{ indicator: classes.hideIndicator, root: classes.customTab }}
>
<StyledTab
+86 -2
View File
@@ -10,6 +10,7 @@ import {
Divider,
TextField,
Modal,
Switch,
} from "@mui/material";
//import { useAlert
import { ToastContainer, toast } from "react-toastify";
@@ -19,6 +20,9 @@ import { FileCopy, Visibility, VisibilityOff } from "@mui/icons-material";
import IconButton from "@mui/material/IconButton";
import { Tooltip } from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
import { Box } from "@mui/system";
import CircularProgress from "@mui/material/CircularProgress";
const Settings = (props) => {
const { globalUrl, isLoaded, userdata, setUserData } = props;
@@ -32,6 +36,11 @@ const Settings = (props) => {
const [currentPassword, setCurrentPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
const [newPassword2, setNewPassword2] = useState("");
const [selectedOrganization, setSelectedOrganization] = useState({});
const [MFARequired, setMFARequired] = React.useState(false);
const [image2FA, setImage2FA] = React.useState("");
const [value2FA, setValue2FA] = React.useState("");
// const [file, setFile] = React.useState("");
// const [fileBase64, setFileBase64] = React.useState(
// userdata.image === undefined || userdata.image === null
@@ -87,6 +96,82 @@ const Settings = (props) => {
flexDirection: "column",
};
useEffect(() => {
if (userdata.active_org === undefined || userdata.active_org === null || userdata.active_org.id === undefined || userdata.active_org.id === null) {
return;
}
fetch(`${globalUrl}/api/v1/orgs/${userdata.active_org.id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
console.log("Received response:", response);
if (response.status !== 200) {
console.error("Status not 200:", response.status);
throw new Error(`Status not 200: ${response.status}`);
}
return response.json();
})
.then((responseJson) => {
setSelectedOrganization(responseJson);
setMFARequired(responseJson.mfa_required);
})
.catch((error) => {
console.error("Error fetching organization:", error);
});
}, [userdata]);
const UpdateMFAInUserOrg = (org_id) => {
if (MFARequired === false) {
toast("Making MFA required for your organization. Please wait...");
} else {
toast("Making MFA optional for your organization. Please wait...");
}
const data = {
mfa_required: !selectedOrganization.mfa_required,
org_id: selectedOrganization.id,
}
const url = globalUrl + `/api/v1/orgs/${selectedOrganization.id}`;
fetch(url, {
mode: "cors",
method: "POST",
body: JSON.stringify(data),
credentials: "include",
crossDomain: true,
withCredentials: true,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
})
.then((response) =>
response.json().then((responseJson) => {
console.log(responseJson)
if (responseJson["success"] === false) {
toast.error("Failed updating org: ", responseJson.reason);
} else {
if (MFARequired === false) {
setMFARequired(true)
toast.success("Successfully make MFA required for your organization!");
} else {
setMFARequired(false)
toast.success("Successfully make MFA optional for your organization!")
}
}
}),
)
.catch((error) => {
toast("Err: " + error.toString());
});
}
const checkOwner = (data, userdata) => {
var currentOwner = false;
if (data.owner.address === userdata.eth_info.account) {
@@ -938,8 +1023,7 @@ const Settings = (props) => {
>
Submit password change
</Button>
<h3>{passwordFormMessage}</h3>
<h3>{passwordFormMessage}</h3>
{isCloud && (
<>
+205 -88
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";
@@ -129,7 +130,6 @@ export const GetIconInfo = (action) => {
// Finds the icon based on the action. Should be verbs.
const iconList = [
{ key: "cases", values: ["cases"] },
{ key: "communication", values: ["communication", "comms", "email",] },
{ key: "cache_add", values: ["set_cache"] },
{ key: "cache_get", values: ["get_cache"] },
{ key: "filter", values: ["filter"] },
@@ -199,6 +199,7 @@ export const GetIconInfo = (action) => {
values: ["compare", "convert", "to", "filter", "translate", "parse"],
},
{ key: "close", values: ["close", "stop", "cancel", "block"] },
{ key: "communication", values: ["communication", "comms", "email", "mail",] },
];
var selectedKey = ""
@@ -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([]);
@@ -974,15 +976,16 @@ const Workflows = (props) => {
}, 1000);
} else if (selectedWorkflowIndexes.length > 0) {
// Do backwards so it doesn't change
toast("Starting deletion of workflows. This might take a while.")
for (var i = selectedWorkflowIndexes.length - 1; i >= 0; i--) {
const workflow = filteredWorkflows[selectedWorkflowIndexes[i]-1]
if (workflow !== undefined && workflow !== null && workflow.id !== undefined && workflow.id !== null) {
deleteWorkflow(workflow.id);
deleteWorkflow(workflow.id, true)
}
}
setTimeout(() => {
getAvailableWorkflows();
getAvailableWorkflows()
}, 1000);
setSelectedWorkflowIndexes([]);
@@ -1113,7 +1116,7 @@ const Workflows = (props) => {
})
}
const getAvailableWorkflows = () => {
const getAvailableWorkflows = (amount) => {
var storageWorkflows = []
try {
const storagewf = localStorage.getItem("workflows")
@@ -1130,7 +1133,12 @@ const Workflows = (props) => {
//console.log("Failed to get workflows from localstorage: ", e)
}
fetch(globalUrl + "/api/v1/workflows", {
var url = `${globalUrl}/api/v1/workflows`
if (amount !== undefined && amount !== null) {
url += `?top=${amount}`
}
fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
@@ -1154,10 +1162,11 @@ const Workflows = (props) => {
})
.then((responseJson) => {
if (responseJson !== undefined) {
var newarray = []
for (var key in responseJson) {
const wf = responseJson[key]
if (wf.public === true) {
for (var wfkey in responseJson) {
const wf = responseJson[wfkey]
if (wf.public === true || wf.hidden === true) {
continue
}
@@ -1170,9 +1179,9 @@ const Workflows = (props) => {
var parsedactionlist = [];
for (var key in newarray) {
const workflow = newarray[key]
if (workflow.status === "production") {
setProdFilter = true
}
//if (workflow.status === "production") {
// setProdFilter = true
//}
for (var actionkey in newarray[key].actions) {
const action = newarray[key].actions[actionkey];
@@ -1213,7 +1222,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) {
@@ -1319,8 +1345,21 @@ const Workflows = (props) => {
setView(tmpView);
}
const urlSearchParams = new URLSearchParams(window.location.search);
const params = Object.fromEntries(urlSearchParams.entries());
const foundTab = params["top"];
if (foundTab !== null && foundTab !== undefined) {
// Check if it's a number
if (isNaN(foundTab)) {
getAvailableWorkflows()
} else {
getAvailableWorkflows(foundTab)
}
} else {
getAvailableWorkflows()
}
getApps()
getAvailableWorkflows();
getFramework()
}
}, [])
@@ -1637,20 +1676,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 +1703,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 +1745,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 +1827,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,15 +1847,16 @@ const Workflows = (props) => {
}
}
setWorkflows(storageWorkflows)
//setFilteredWorkflows(storageWorkflows)
//setWorkflows(storageWorkflows)
setFilteredWorkflows(storageWorkflows)
//setUpdate(Math.random())
})
.catch((error) => {
console.log(error.toString())
})
}
const deleteWorkflow = (id) => {
const deleteWorkflow = (id, bulk) => {
fetch(globalUrl + "/api/v1/workflows/" + id, {
method: "DELETE",
headers: {
@@ -1752,15 +1870,19 @@ const Workflows = (props) => {
console.log("Status not 200 for setting workflows :O!");
toast("Failed deleting workflow. Do you have access?");
} else {
toast("Deleted workflow " + id);
if (bulk !== true) {
toast("Deleted workflow " + id);
}
}
return response.json();
})
.then(() => {
setTimeout(() => {
getAvailableWorkflows();
}, 1000);
if (bulk !== true) {
setTimeout(() => {
getAvailableWorkflows();
}, 1000);
}
})
.catch((error) => {
toast(error.toString());
@@ -1878,7 +2000,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 +2014,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 +2035,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 +2047,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 +2060,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 +2072,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 +2085,7 @@ const Workflows = (props) => {
</MenuItem>
<MenuItem
style={{ backgroundColor: theme.palette.inputColor, color: "white" }}
disabled={isDistributed}
onClick={() => {
setDeleteModalOpen(true);
setSelectedWorkflowId(data.id);
@@ -2076,7 +2177,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: 320, position: "relative", border: highlightIds.includes(data.id) ? "2px solid #f85a3e" : isDistributed || hasSuborgs ? "1px solid #40E0D0" : "inherit", borderRadius: theme.palette.borderRadius, }}>
<Paper square style={paperAppStyle}>
{selectedCategory !== "" ?
<Tooltip title={`Usecase Category: ${selectedCategory}`} placement="bottom">
@@ -2129,8 +2230,17 @@ const Workflows = (props) => {
<Typography>
Edit '{data.name}'
</Typography>
<br />
{isDistributed || hasSuborgs ?
<Typography>
This is a parentorg-controlled workflow.
</Typography>
: null}
</div>
} placement="right">
<Typography
variant="body1"
style={{
@@ -2462,6 +2572,8 @@ const Workflows = (props) => {
}
const reader = new FileReader();
var workflowids = []
// Waits for the read
reader.addEventListener("load", (event) => {
var data = reader.result;
@@ -2497,7 +2609,8 @@ const Workflows = (props) => {
data.org_id = userdata.active_org.id
data.org = []
data.execution_org = {}
workflowids.push(data.id)
// Actually create it
setNewWorkflow(
@@ -2528,6 +2641,10 @@ const Workflows = (props) => {
}
setLoadWorkflowsModalOpen(false);
if (workflowids.length > 0) {
setHighlightIds(workflowids)
}
};
const getWorkflowMeta = (data) => {
@@ -2869,7 +2986,7 @@ const Workflows = (props) => {
className={classes.datagrid}
rows={rows}
columns={columns}
pageSize={25}
pageSize={100}
checkboxSelection
autoHeight
density="standard"
@@ -3682,7 +3799,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>
)
+9 -7
View File
@@ -4,16 +4,16 @@ go 1.22.0
toolchain go1.22.2
replace github.com/shuffle/shuffle-shared => ../../../../shuffle-shared
//replace github.com/shuffle/shuffle-shared => ../../../../shuffle-shared
require (
github.com/docker/docker v26.1.0+incompatible
github.com/docker/go-connections v0.5.0
github.com/satori/go.uuid v1.2.0
github.com/shuffle/shuffle-shared v0.6.37
k8s.io/api v0.30.0
k8s.io/apimachinery v0.30.0
k8s.io/client-go v0.30.0
github.com/shuffle/shuffle-shared v0.6.50
k8s.io/api v0.30.2
k8s.io/apimachinery v0.30.2
k8s.io/client-go v0.30.2
)
require (
@@ -31,7 +31,7 @@ require (
github.com/algolia/algoliasearch-client-go/v3 v3.18.1 // indirect
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect
github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 // indirect
github.com/cloudflare/circl v1.3.3 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
@@ -41,7 +41,7 @@ require (
github.com/emirpasic/gods v1.18.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/frikky/kin-openapi v0.41.0 // indirect
github.com/frikky/schemaless v0.0.11 // indirect
github.com/frikky/schemaless v0.0.13 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.5.0 // indirect
@@ -82,6 +82,8 @@ require (
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/sashabaranov/go-openai v1.19.2 // indirect
github.com/sendgrid/rest v2.6.9+incompatible // indirect
github.com/sendgrid/sendgrid-go v3.14.0+incompatible // indirect
github.com/sergi/go-diff v1.1.0 // indirect
github.com/skeema/knownhosts v1.2.1 // indirect
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
+16
View File
@@ -97,6 +97,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ=
@@ -139,6 +141,8 @@ github.com/frikky/schemaless v0.0.9 h1:RzNLPkJq5c4nlm5iLiTndFcbeQxdMGJIj266wSGt2
github.com/frikky/schemaless v0.0.9/go.mod h1:mooDxY+D6weHjhKvjy3+IE9S7P4g4cpNnidkdRv/cHQ=
github.com/frikky/schemaless v0.0.11 h1:c4r6CJX30XI+SoJdT9RlUd9qYSQlx6hvwGRtsypu+uM=
github.com/frikky/schemaless v0.0.11/go.mod h1:mooDxY+D6weHjhKvjy3+IE9S7P4g4cpNnidkdRv/cHQ=
github.com/frikky/schemaless v0.0.13 h1:ARiN9V7wr2VZXAr9JK5wvTbyPgpGrgeiL1VhR5MlgaQ=
github.com/frikky/schemaless v0.0.13/go.mod h1:mooDxY+D6weHjhKvjy3+IE9S7P4g4cpNnidkdRv/cHQ=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
@@ -385,6 +389,10 @@ github.com/sashabaranov/go-openai v1.19.2 h1:+dkuCADSnwXV02YVJkdphY8XD9AyHLUWwk6
github.com/sashabaranov/go-openai v1.19.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0=
github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
github.com/sendgrid/sendgrid-go v3.14.0+incompatible h1:KDSasSTktAqMJCYClHVE94Fcif2i7P7wzISv1sU6DUA=
github.com/sendgrid/sendgrid-go v3.14.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shuffle/shuffle-shared v0.6.16 h1:dQBDRmb2Wgl3pEuewqjDvN6v6nUKr+1EvGSEja9zG6s=
@@ -395,6 +403,8 @@ github.com/shuffle/shuffle-shared v0.6.27 h1:q4qZD6bGZFIvZ5Y10unGr3N3rZ7OryWyvva
github.com/shuffle/shuffle-shared v0.6.27/go.mod h1:rWkh1eWdIx7OqQzJ1+JzF3Hck1X/Ty1WkUtjLrp+CU4=
github.com/shuffle/shuffle-shared v0.6.37 h1:IB8tJqubJmJwwpLYbXNMVWck5jgVO9SKcKo/BBu+wTc=
github.com/shuffle/shuffle-shared v0.6.37/go.mod h1:rWkh1eWdIx7OqQzJ1+JzF3Hck1X/Ty1WkUtjLrp+CU4=
github.com/shuffle/shuffle-shared v0.6.50 h1:MBeGAiBNkw9Eg+3YTJIlBOuskWntGvT0uefFUYOBhbY=
github.com/shuffle/shuffle-shared v0.6.50/go.mod h1:RAJiSFjmuKmijKTbbEf9A6Ojb+3/te7g71lED7JjPus=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
@@ -908,10 +918,16 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/api v0.30.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA=
k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE=
k8s.io/api v0.30.2 h1:+ZhRj+28QT4UOH+BKznu4CBgPWgkXO7XAvMcMl0qKvI=
k8s.io/api v0.30.2/go.mod h1:ULg5g9JvOev2dG0u2hig4Z7tQ2hHIuS+m8MNZ+X6EmI=
k8s.io/apimachinery v0.30.0 h1:qxVPsyDM5XS96NIh9Oj6LavoVFYff/Pon9cZeDIkHHA=
k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
k8s.io/apimachinery v0.30.2 h1:fEMcnBj6qkzzPGSVsAZtQThU62SmQ4ZymlXRC5yFSCg=
k8s.io/apimachinery v0.30.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
k8s.io/client-go v0.30.0 h1:sB1AGGlhY/o7KCyCEQ0bPWzYDL0pwOZO4vAtTSh/gJQ=
k8s.io/client-go v0.30.0/go.mod h1:g7li5O5256qe6TYdAMyX/otJqMhIiGgTapdLchhmOaY=
k8s.io/client-go v0.30.2 h1:sBIVJdojUNPDU/jObC+18tXWcTJVcwyqS9diGdWHk50=
k8s.io/client-go v0.30.2/go.mod h1:JglKSWULm9xlJLx4KCkfLLQ7XwtlbflV6uFFSHTMgVs=
k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
+1 -1
View File
@@ -1452,7 +1452,7 @@ func main() {
if isKubernetes == "true" {
clientset, _, err := shuffle.GetKubernetesClient()
if err != nil {
log.Printf("[ERROR] Error getting kubernetes client:", err)
log.Printf("[ERROR] Error getting kubernetes client: %s", err)
os.Exit(1)
}
+79 -79
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
@@ -2090,7 +2090,7 @@ func runWorkflowExecutionTransaction(ctx context.Context, attempts int64, workfl
*/
}
}
if setExecution || workflowExecution.Status == "FINISHED" || workflowExecution.Status == "ABORTED" || workflowExecution.Status == "FAILURE" {
log.Printf("[DEBUG][%s] Running setexec with status %s and %d/%d results", workflowExecution.ExecutionId, workflowExecution.Status, len(workflowExecution.Results), len(workflowExecution.Workflow.Actions))
//result(s)", workflowExecution.ExecutionId, workflowExecution.Status, len(workflowExecution.Results))