Merge branch '2.0.0' of github.com:Shuffle/Shuffle into 2.0.0
This commit is contained in:
@@ -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).
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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' : ''*/}
|
||||
|
||||
+1191
-729
File diff suppressed because it is too large
Load Diff
@@ -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 {
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
+1701
-1641
File diff suppressed because it is too large
Load Diff
@@ -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"]);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -133,6 +133,11 @@ const data = [
|
||||
"background-gradient-stop-colors": "data(fillGradient)",
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: `node[?parent_controlled]`,
|
||||
css: {
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: `node[app_name="Testing"]`,
|
||||
css: {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+759
-402
File diff suppressed because it is too large
Load Diff
+2731
-1478
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
|
||||
@@ -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%",
|
||||
}}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 += "¬e=" + executionArgument
|
||||
} else {
|
||||
url += "¬e=" + JSON.stringify(executionArgument)
|
||||
}
|
||||
} catch (e) {
|
||||
url += "¬e=" + 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, }}>
|
||||
or
|
||||
</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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 && (
|
||||
<>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user