+ // backgroundColor: "#213243",
+ // This is a mess hahahah
+ return (
+
+
+
+
+ {includedData}
+
+
+
+
+ );
};
export default App;
diff --git a/frontend/src/__test__/appdata.js b/frontend/src/__test__/appdata.js
index 82772dbb..c454c44b 100644
--- a/frontend/src/__test__/appdata.js
+++ b/frontend/src/__test__/appdata.js
@@ -1,3 +1,1350 @@
-const Data = [{"name":"hive","is_valid":false,"id":"02828a90-658b-41c4-8726-e31da7e02fe9","id_":"02828a90-658b-41c4-8726-e31da7e02fe9","link":"","app_version":"1.0.0","description":"The Hive app allows for walkoff to generate or close cases in TheHive","environment":"cloud","contact_info":{"name":"FORGE Cyber","url":"https://github.com/"},"actions":[{"description":"creates a hive case","id_":"eb84008d-b0e0-41c6-a9b3-d7a51866bcd4","name":"create_case","node_type":"ACTION","environment":"cloud","parameters":[{"description":"log data to generate custom fields","id_":"b484aba8-0787-42e9-a97a-882a59e02f8f","name":"log_data","required":true,"schema":{"type":"string"}},{"description":"URL of TheHive","id_":"deb0a68d-479a-4472-b547-1eb82fb3888d","name":"url","required":true,"schema":{"type":"string"}},{"description":"API key to access TheHive","id_":"5116b359-c8da-41cf-80a4-4292a1a3c90b","name":"api_key","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"b040890c-5462-458b-8f54-054e6ce2a146","schema":{"type":"object"}}},{"description":"Updates a case data as well as severity","id_":"b7f64744-04e3-4166-990e-70106edc88cf","name":"update_case","node_type":"ACTION","environment":"cloud","parameters":[{"description":"json from trigger","id_":"ac37d451-ff45-4740-b5a7-de389afcb2b1","name":"input","required":true,"schema":{"type":"object"}},{"description":"id for case","id_":"69613390-78c9-4b5d-aa71-b6c4a4750f8f","name":"id","required":true,"schema":{"type":"string"}},{"description":"severity of change","id_":"b04523b0-e74e-44a3-9c51-055c1e53167a","name":"severity","required":true,"schema":{"type":"integer"}},{"description":"URL of TheHive","id_":"e415c1f9-d502-4a5d-9c8a-8cdefe0ebf88","name":"url","required":true,"schema":{"type":"string"}},{"description":"API key to access TheHive","id_":"2aa8e834-b889-467a-9172-373930b013cf","name":"api_key","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"","schema":{"type":""}}},{"description":"Closes a case in TheHive","id_":"240f26ef-5045-4711-94a5-af7a947ac5a4","name":"close_case","node_type":"ACTION","environment":"cloud","parameters":[{"description":"ID of case to close","id_":"71eb8b33-56dc-416f-8ae9-6bee56321f9b","name":"case_id","required":true,"schema":{"type":"string"}},{"description":"URL of TheHive","id_":"a3bbb859-d6fb-45ec-a08f-8895c8f8b0d5","name":"url","required":true,"schema":{"type":"string"}},{"description":"API key to access TheHive","id_":"660ba026-886f-413c-809a-8a48961d9228","name":"api_key","required":true,"schema":{"type":"string"}},{"description":"Resolution status of the case to close.","id_":"6b9604d8-7358-4362-b0f4-0d570a366724","name":"resolution_status","required":true,"schema":{"type":"string"}},{"description":"Impact status of the case to close. The impact status is only captured when resolution status is TruePositive","id_":"11651f4f-4521-4f1b-a2c7-66078f9be886","name":"impact_status","required":true,"schema":{"type":"string"}},{"description":"Tags to add to the case once closed. Comma separated string.","id_":"01a0951f-92f8-485a-b8a2-0664a7e03862","name":"tags","required":true,"schema":{"type":"string"}},{"description":"Explanation of why the case was closed.","id_":"fa67a17b-b6e0-42e6-8988-03a16484c4b0","name":"summary","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"ebba5506-32c8-4bd2-a004-f32c8ec9cdf6","schema":{"type":"object"}}}]},{"name":"walk_off","is_valid":false,"id":"1f52e17e-c11b-4cf4-abdb-a216211a0d27","id_":"1f52e17e-c11b-4cf4-abdb-a216211a0d27","link":"","app_version":"1.0.0","description":"An example of a Walkoff App specification","environment":"cloud","contact_info":{"name":"Walkoff Team","url":"https://github.com/nsacyber/walkoff"},"actions":[{"description":"Connect to Walkoff","id_":"a9926e7f-18f9-4b93-b805-27cdb5a232ff","name":"connect","node_type":"ACTION","environment":"cloud","parameters":[{"description":"Timeout on the request (in seconds)","id_":"300136af-3c30-4e2b-babe-272f845942a0","name":"timeout","required":true,"schema":{"type":"number"}},{"description":"username","id_":"896de900-9ca1-4108-a034-016e9c6ffe54","name":"username","required":true,"schema":{"type":"string"}},{"description":"password","id_":"e75e75d4-8a6b-4c24-8ffd-61b435683edb","name":"password","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"de121927-243f-4f09-9331-c48ca25e85d1","schema":{"type":"string"}}},{"description":"Disconnect from Walkoff","id_":"acd81f9b-353c-4749-a33a-2091da2d307c","name":"disconnect","node_type":"ACTION","environment":"cloud","parameters":[{"description":"Timeout on the request (in seconds)","id_":"76819a03-5272-4421-99cd-1e53f95963f0","name":"timeout","required":true,"schema":{"type":"number"}},{"description":"refresh token","id_":"b583836e-eff4-4e7c-8430-286bfa81e43f","name":"refresh_token","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"d6cc2470-2dc9-43f9-a2f9-fb123c590426","schema":{"type":"string"}}},{"description":"Gets a list of all the users loaded on the system","id_":"6610a111-687e-45f5-a60d-26048594cdb1","name":"get_all_users","node_type":"ACTION","environment":"cloud","parameters":[{"description":"Timeout on the request (in seconds)","id_":"89b9a459-f756-40d8-b2c0-1a7c57987fee","name":"timeout","required":true,"schema":{"type":"number"}},{"description":"Access Token","id_":"496c7956-964b-4977-88ed-27db81a94cfe","name":"access_token","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"642ecea4-e747-4dfe-9ca7-4f290847c0ed","schema":{"type":"string"}}},{"description":"Gets a list of all the workflows loaded on the system","id_":"7cb34d71-2619-40b1-a565-17274fea924e","name":"get_all_workflows","node_type":"ACTION","environment":"cloud","parameters":[{"description":"Timeout on the request (in seconds)","id_":"e4553287-018d-465a-8599-a605fdb5fb71","name":"timeout","required":true,"schema":{"type":"number"}},{"description":"Access Token","id_":"f9193b36-cc5b-4a5f-babf-7df321837f57","name":"access_token","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"684de06e-b048-4332-88c5-9555ca41e2a1","schema":{"type":"string"}}},{"description":"Executes a workflow","id_":"9aaf7af4-fa4b-45d1-ac99-5722f0487aef","name":"execute_workflow","node_type":"ACTION","environment":"cloud","parameters":[{"description":"ID of the workflow","id_":"2f9c0aaf-82ea-4fab-9c26-99268cc9735e","name":"workflow_id","required":true,"schema":{"type":"string"}},{"description":"Timeout on the request (in seconds)","id_":"35fc2b33-0e03-4b48-9e1c-73aa8a0b4ad7","name":"timeout","required":true,"schema":{"type":"number"}},{"description":"Access Token","id_":"463425b6-ead9-4783-ad40-e04e6d30bc6e","name":"access_token","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"fda59148-a2b1-4180-a34d-8beef5d68929","schema":{"type":"string"}}},{"description":"Log out of Walkoff","id_":"160693dc-38c4-4a7c-a36b-bd529e69118b","name":"shutdown","node_type":"ACTION","environment":"cloud","parameters":[{"description":"refresh Token","id_":"a24a2395-7696-48cc-a1b8-b71c881c1180","name":"refresh_token","required":true,"schema":{"type":"string"}},{"description":"Timeout on the request (in seconds)","id_":"4b0b1d88-203b-4ba3-956f-822def41d0cb","name":"timeout","required":true,"schema":{"type":"number"}}],"returns":{"description":"","id_":"7836185e-786b-4d13-8c9b-b29d89d61155","schema":{"type":"string"}}}]},{"name":"ip_addr_utils","is_valid":false,"id":"4f00d85c-1fc8-4db2-a1d2-fab1eff5d02e","id_":"4f00d85c-1fc8-4db2-a1d2-fab1eff5d02e","link":"","app_version":"1.0.0","description":"An IP address app that will allow users to specify ip addresses and will format them correctly","environment":"cloud","contact_info":{"name":"Walkoff Team","url":"https://github.com/nsacyber/walkoff"},"actions":[{"description":"Sets the timestamp fro which scipt outputs will be filed under","id_":"5404d09b-08d8-469b-9087-e6b22892188b","name":"set_timestamp","node_type":"ACTION","environment":"cloud","parameters":[],"returns":{"description":"","id_":"8945dbff-0920-4b04-8682-e9086bcae5a4","schema":{"type":"string"}}},{"description":"Converts ip address from CIDR notation to individual IP's for easier integration with other apps.","id_":"d6c39b8b-63b7-4198-82d2-b8a4f3ed95f2","name":"cidr_to_array","node_type":"ACTION","environment":"cloud","parameters":[{"description":"list of hosts to execute on","id_":"14a43bde-2a88-4cf7-987c-e7bb62c202c0","name":"ip_array","required":true,"schema":{"type":"array"}}],"returns":{"description":"","id_":"22c7aac2-321e-451a-a9f4-61b9c6656cb7","schema":{"type":"array"}}}]},{"name":"power_shell","is_valid":false,"id":"5b570c0b-4f31-4f2e-93ac-da39b78502bb","id_":"5b570c0b-4f31-4f2e-93ac-da39b78502bb","link":"","app_version":"1.0.0","description":"A power shell app that can run commands on a remote host.","environment":"cloud","contact_info":{"name":"Walkoff Team","url":"https://github.com/nsacyber/walkoff"},"actions":[{"description":"Sets the timestamp fro which scipt outputs will be filed under","id_":"8f0cb2b7-1cd1-4284-8790-f8234ec9a1f9","name":"set_timestamp","node_type":"ACTION","environment":"cloud","parameters":[],"returns":{"description":"","id_":"b58197d0-42fc-40bc-a40f-b1bea1c44b01","schema":{"type":"string"}}},{"description":"Executes powershell scripts on remote devices (Scripts located in \"scripts\" directory within app).","id_":"cb1fb39a-9cd9-485d-8c2a-e4fe138035e8","name":"exec_command_prompt_from_file","node_type":"ACTION","environment":"cloud","parameters":[{"description":"list of hosts to execute on","id_":"13fd6552-5b2b-4081-89f9-8e9dad5bdea2","name":"hosts","required":true,"schema":{"type":"array"}},{"description":"filename in which scripts will be located","id_":"7a51a4e2-2c7d-4c7f-9aca-dc2d1316f2db","name":"local_file_name","required":true,"schema":{"type":"string"}},{"description":"Username for remote host","id_":"6ea5d033-308b-453e-84ff-416011b459d7","name":"username","required":true,"schema":{"type":"string"}},{"description":"Password for remote host user","id_":"b71eb882-4b22-456e-980d-84cbba55216b","name":"password","required":true,"schema":{"type":"string"}},{"description":"transport type","id_":"78c824c8-97f9-46ea-ba2a-ada98127555b","name":"transport","required":true,"schema":{"type":"string"}},{"description":"whether server certificate should be validated","id_":"ca847f0c-26de-4289-a13b-0aca5ac55d1c","name":"server_cert_validation","required":true,"schema":{"type":"boolean"}},{"description":"Will encrypt the WinRM messages if set to True and \"transport auth\" supports message encryption","id_":"2747d198-2fd2-47a0-8854-4a3dfc5baa77","name":"message_encryption","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"","schema":{"type":""}}},{"description":"Executes the powershell command on remote devices.","id_":"70717155-5377-40d1-989b-3db7b90c5975","name":"exec_command_prompt","node_type":"ACTION","environment":"cloud","parameters":[{"description":"list of hosts to execute on","id_":"b151934c-dea4-4ba9-a993-1e527093b6e4","name":"hosts","required":true,"schema":{"type":"array"}},{"description":"list of commands to execute","id_":"fa6386e4-0e45-453e-8510-6d4affdb7e31","name":"commands","required":true,"schema":{"type":"array"}},{"description":"Username for remote host","id_":"a2c56d5d-48eb-455f-8e4b-155855ac4fa4","name":"username","required":true,"schema":{"type":"string"}},{"description":"Password for remote host user","id_":"66b4b42e-bd81-4180-8ff8-84fae5e590f2","name":"password","required":true,"schema":{"type":"string"}},{"description":"transport type","id_":"d0a7b275-8a0f-4421-9b75-7eb3dbc6ff38","name":"transport","required":true,"schema":{"type":"string"}},{"description":"whether server certificate should be validated","id_":"5df9e652-9e3c-437d-b81f-92e8e0702312","name":"server_cert_validation","required":true,"schema":{"type":"boolean"}},{"description":"Will encrypt the WinRM messages if set to True and the transport auth supports message encryption","id_":"64f64521-5ded-4f0b-8590-f9c6395cd98f","name":"message_encryption","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"","schema":{"type":""}}},{"description":"Executes the powershell script on remote devices based on script file passed in.","id_":"d0fabf12-9db6-4a0e-8785-5248b1035051","name":"exec_powershell_script_from_file","node_type":"ACTION","environment":"cloud","parameters":[{"description":"list of hosts to execute on","id_":"09299ae2-38a0-4fb3-b064-fd3597424d67","name":"hosts","required":true,"schema":{"type":"array"}},{"description":"type of shell you want to execute","id_":"3d076cba-93d2-4f58-abfd-1475b354444d","name":"shell_type","required":true,"schema":{"type":"string"}},{"description":"filename in which scripts will be located","id_":"b82484c2-861c-4193-925a-e23bd882938c","name":"local_file_name","required":true,"schema":{"type":"string"}},{"description":"Username for remote host","id_":"79f42b59-2218-4d41-b13e-154b2ed89105","name":"username","required":true,"schema":{"type":"string"}},{"description":"Password for remote host user","id_":"35ac706c-d375-45ab-add5-40d9b846100c","name":"password","required":true,"schema":{"type":"string"}},{"description":"transport type","id_":"aa9a2b80-7836-4810-82fa-3c270d9e18f6","name":"transport","required":true,"schema":{"type":"string"}},{"description":"whether server certificate should be validated","id_":"39cbd4d7-a0cf-4b9c-99bc-5b7c46cfc8df","name":"server_cert_validation","required":true,"schema":{"type":"boolean"}},{"description":"Will encrypt the WinRM messages if set to True and the transport auth supports message encryption","id_":"c7c161e7-c5db-49b8-a7de-b10a203c551b","name":"message_encryption","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"","schema":{"type":""}}},{"description":"Executes the powershell command/script on remote devices.","id_":"98c0cd0f-a05a-4b26-809c-5dbf3ecb791f","name":"exec_powershell_script","node_type":"ACTION","environment":"cloud","parameters":[{"description":"list of hosts to execute on","id_":"9e69e9b0-4639-43dd-9b4c-e4cb710ce63c","name":"hosts","required":true,"schema":{"type":"array"}},{"description":"type of shell you want to execute","id_":"6dce7ac6-b0ac-4d48-a79a-e9db795d751b","name":"shell_type","required":true,"schema":{"type":"string"}},{"description":"script in the form of array commands","id_":"700bb930-582e-4cbe-85e9-f5656461ecd5","name":"arguments","required":true,"schema":{"type":"array"}},{"description":"Username for remote host","id_":"22f0f58f-7e3c-43f6-afca-8e6800a6052e","name":"username","required":true,"schema":{"type":"string"}},{"description":"Password for remote host user","id_":"5b53cb8d-eaa4-4d31-8142-31151406100c","name":"password","required":true,"schema":{"type":"string"}},{"description":"transport type","id_":"635e350e-7a4d-4b94-aa0e-3e0a57ba5577","name":"transport","required":true,"schema":{"type":"string"}},{"description":"whether server certificate should be validated","id_":"07ec9b94-f626-42e7-a5a8-6e9b68b7bf7d","name":"server_cert_validation","required":true,"schema":{"type":"boolean"}},{"description":"Will encrypt the WinRM messages if set to True and the transport auth supports message encryption","id_":"1ec509eb-0444-4f08-971e-1da23983d588","name":"message_encryption","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"762934c3-8de3-4261-850f-38b12effc90a","schema":{"type":"string"}}}]},{"name":"ssh","is_valid":false,"id":"68bfeb11-4e8f-4d46-9cf5-5f681be05858","id_":"68bfeb11-4e8f-4d46-9cf5-5f681be05858","link":"","app_version":"1.0.0","description":"Executes ssh shell commands via SSH","environment":"cloud","contact_info":{"name":"Walkoff Team","url":"https://github.com/nsacyber/walkoff"},"actions":[{"description":"Execute command on remote server with SSH client","id_":"2ec132a9-1c32-42eb-8260-bcfac47901fb","name":"exec_command","node_type":"ACTION","environment":"cloud","parameters":[{"description":"hosts or hostnames of the remote server","id_":"d94d2878-9208-445a-a9b9-852d05ef5b0c","name":"hosts","required":true,"schema":{"type":"array"}},{"description":"port number","id_":"022cd027-b0f2-49ee-a512-da8a07ed2bfc","name":"port","required":true,"schema":{"type":"integer"}},{"description":"json array of arguments","id_":"2d8dff25-dbc9-4656-9de3-73280015eaaf","name":"args","required":true,"schema":{"type":"array"}},{"description":"username to login with","id_":"89145276-3837-4e48-b9ab-0aa76500ba72","name":"username","required":true,"schema":{"type":"string"}},{"description":"password to login with","id_":"564fb3d7-89ff-41df-b3ed-6ba23d7b3f3e","name":"password","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"1395434e-52c2-4428-a1ac-966a315af6e0","schema":{"type":"string"}}},{"description":"Run a local bash command","id_":"baa63db8-c8d6-4dea-b774-81fe4b8dddfb","name":"exec_local_command","node_type":"ACTION","environment":"cloud","parameters":[{"description":"source path of the file to copy","id_":"fe99ea27-1176-4ca5-8de7-a5754f429784","name":"command","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"ac58f084-4234-4380-b438-a2c9239344f2","schema":{"type":"string"}}},{"description":"Copy remote file to remote host using sftp","id_":"87f34206-3b72-4d01-b64a-9bacdf822526","name":"sftp_copy","node_type":"ACTION","environment":"cloud","parameters":[{"description":"source path of the file to copy","id_":"43f07eb3-7e1b-4ea3-8684-862111011394","name":"src_path","required":true,"schema":{"type":"string"}},{"description":"remote path of the file destination","id_":"1a53170b-15b7-4cf8-9dff-9309d56bdacf","name":"dest_path","required":true,"schema":{"type":"string"}},{"description":"host or hostname of the remote server","id_":"4d78b34e-09ae-48c3-9eeb-f5042903bc04","name":"src_host","required":true,"schema":{"type":"string"}},{"description":"port number","id_":"29ddd615-05d3-4531-9869-23ca7b366133","name":"src_port","required":true,"schema":{"type":"integer"}},{"description":"username to login with","id_":"f9db153e-0c9f-4808-9814-9277f56b2f47","name":"src_username","required":true,"schema":{"type":"string"}},{"description":"password to login with","id_":"a6337fa3-0704-49ca-8920-a49229bed77e","name":"src_password","required":true,"schema":{"type":"string"}},{"description":"host or hostname of the remote server","id_":"04399d82-0ef6-4bdc-b563-344df67219a2","name":"dest_host","required":true,"schema":{"type":"string"}},{"description":"port number","id_":"262df217-34fa-4165-a753-e71488099729","name":"dest_port","required":true,"schema":{"type":"integer"}},{"description":"username to login with","id_":"4fde41af-112b-4b33-88dc-ce3219b444f3","name":"dest_username","required":true,"schema":{"type":"string"}},{"description":"password to login with","id_":"d9382c2a-b64c-4ad8-b7ca-488aa8e440fb","name":"dest_password","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"9b484424-f7d7-42ef-b553-f40ec1805806","schema":{"type":"string"}}},{"description":"runs the specified shell script on the remote server(s)","id_":"bb32494e-b808-4d9b-8f9a-f366a9bfd21e","name":"run_shell_script_file","node_type":"ACTION","environment":"cloud","parameters":[{"description":"local path of the shell script to run","id_":"41df393d-3435-4e0c-9c7e-dd6531fdc0a6","name":"local_file_name","required":true,"schema":{"type":"string"}},{"description":"hosts of the remote server","id_":"f2bd124e-1dbb-4b87-a12f-ef7f25d6eb2d","name":"hosts","required":true,"schema":{"type":"array"}},{"description":"port number","id_":"eb2a8834-ea05-4d54-8900-e723e2056132","name":"port","required":true,"schema":{"type":"integer"}},{"description":"username to login with","id_":"ba961ec7-2200-420e-9142-7b237c046809","name":"username","required":true,"schema":{"type":"string"}},{"description":"password to login with","id_":"65b6d98a-5b23-4d5d-ae57-022057038bae","name":"password","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"94e55d3b-ef12-4114-8c60-07e3e6417df8","schema":{"type":"string"}}}]},{"name":"Builtin","is_valid":false,"id":"e34f0c67-83e9-443a-b9e7-7b152b4b16f6","id_":"e34f0c67-83e9-443a-b9e7-7b152b4b16f6","link":"","app_version":"1.0.0","description":"Walkoff built-in functions useful in workflow development.","environment":"cloud","contact_info":{"name":"Walkoff Team","url":"https://github.com/nsacyber/walkoff"},"actions":[{"description":"Takes input from an API Call and triggers the rest of the workflow to beign executing again.","id_":"c3e959c6-2c97-48bb-bf00-4082bf812a5d","name":"Trigger","node_type":"TRIGGER","environment":"cloud","parameters":[],"returns":{"description":"","id_":"6265f75b-e9a7-4d0d-b0e8-7c4da5ac5e65","schema":{"type":"string"}}},{"description":"Takes input from a previous action and chooses which branch to take according to your logic.","id_":"8b260c29-dd20-405d-b1a6-29d2e67d43e5","name":"Condition","node_type":"CONDITION","environment":"cloud","parameters":[],"returns":{"description":"","id_":"a4c44cdd-fd3b-46ef-b151-51c1f686fa40","schema":{"type":"string"}}}]},{"name":"hello_world","is_valid":false,"id":"e66d38eb-19b4-4801-abf2-38248b3b2786","id_":"e66d38eb-19b4-4801-abf2-38248b3b2786","link":"","app_version":"1.0.0","description":"An example of a Walkoff App specification","environment":"cloud","contact_info":{"name":"Walkoff Team","url":"https://github.com/nsacyber/walkoff"},"actions":[{"description":"Returns Hello World from the hostname the action is run on","id_":"ac0e2250-20b1-46a3-93ec-1718f9973cc4","name":"hello_world","node_type":"ACTION","environment":"cloud","parameters":[],"returns":{"description":"","id_":"66f4cd0e-7a97-4185-a167-3956dcf3f627","schema":{"type":"string"}}},{"description":"Returns a random float between 0.0 and 1.0","id_":"3817fd8b-1370-4ce8-b874-a526e3c204de","name":"random_number","node_type":"ACTION","environment":"cloud","parameters":[],"returns":{"description":"","id_":"c788404e-c637-4704-bf9e-1b861060e97e","schema":{"type":"number"}}},{"description":"returns the outputs from the trigger data if it's in Json format.","id_":"7fae3591-ab32-402d-8dc4-26e8b802c661","name":"repeat_trigger_as_json","node_type":"ACTION","environment":"cloud","parameters":[{"description":"message to hold output from","id_":"0f0a76ba-b590-4150-ae03-02d0e797f7e4","name":"call","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"1ab56d07-0ce1-4fe5-be01-d70337d9d589","schema":{"type":"object"}}},{"description":"Repeats the call parameter","id_":"23984f43-7593-4c7b-81b6-77852e498add","name":"repeat_back_to_me","node_type":"ACTION","environment":"cloud","parameters":[{"description":"message to repeat","id_":"bedf6d97-958c-4acd-b8a9-a72e19c5d54b","name":"call","required":true,"schema":{"type":"string"}}],"returns":{"description":"","id_":"78708871-00b5-49e6-a4b4-d5e265d89f36","schema":{"type":"string"}}},{"description":"Increments the number parameter by 1","id_":"86624c25-bb66-4fc3-b66f-dc8d394f09bd","name":"return_plus_one","node_type":"ACTION","environment":"cloud","parameters":[{"description":"number to increment","id_":"45af1005-e7f0-46c3-ac09-28748f022a17","name":"number","required":true,"schema":{"type":"number"}}],"returns":{"description":"","id_":"c84fc303-4d27-4091-9500-cb8cd83d6f05","schema":{"type":"number"}}},{"description":"Pause execution by the seconds parameter","id_":"bf787802-6039-4010-bc98-2d6d1dbdce21","name":"pause","node_type":"ACTION","environment":"cloud","parameters":[{"description":"seconds to pause for","id_":"9adcc6f4-0559-47c2-9496-02c8fb3fd58a","name":"seconds","required":true,"schema":{"type":"number"}}],"returns":{"description":"","id_":"","schema":{"type":""}}},{"description":"Echo the data parameter","id_":"0a9fec6a-5bf5-4266-a264-dae7fe52c0d5","name":"echo_array","node_type":"ACTION","environment":"cloud","parameters":[{"description":"array to echo","id_":"8ee260ee-f181-40b3-9d10-f1821f03228c","name":"data","required":true,"schema":{"type":"array"}}],"returns":{"description":"","id_":"bde96402-3d66-43d8-a418-8c0859cb4d01","schema":{"type":"array"}}},{"description":"echos the given JSON object","id_":"e0927a9c-0e6c-429a-965b-40d0804c13f3","name":"echo_json","node_type":"ACTION","environment":"cloud","parameters":[{"description":"The data to echo","id_":"ba719062-90a0-42dd-8ef3-eaa304a57667","name":"data","required":true,"schema":{"type":"object"}}],"returns":{"description":"","id_":"fd4f9c20-e4a0-43df-9a14-7bf0046f391f","schema":{"type":"object"}}}]},{"name":"nmap","is_valid":false,"id":"fc3d231c-9437-4ba9-8fe8-8ba199626197","id_":"fc3d231c-9437-4ba9-8fe8-8ba199626197","link":"","app_version":"1.0.0","description":"A simple app to interact with map","environment":"cloud","contact_info":{"name":"Walkoff Team","url":"https://github.com/nsacyber/walkoff"},"actions":[{"description":"looks into xml nmap for osfamily","id_":"09840ebb-f72a-43e4-b49e-ab5a10d56d96","name":"parse_xml_for_windows_from_file","node_type":"ACTION","environment":"cloud","parameters":[{"description":"nmap output as xml filename","id_":"2bfccd6f-bd75-4ad0-b8df-5ff74aa64761","name":"nmap_file","required":true,"schema":{"type":"string"}}],"returns":{"description":"os","id_":"9c6900a5-7e20-4033-9f3e-61ce8b95ab5f","schema":{"type":"array"}}},{"description":"transforms xml nmap results into json","id_":"150e28cb-3333-49ad-859c-b61e2b758ff7","name":"xml_to_json","node_type":"ACTION","environment":"cloud","parameters":[{"description":"nmap output either as xml filename or string","id_":"5fb18897-fe0e-473f-a83d-3c11f3d8b2a1","name":"nmap_out","required":true,"schema":{"type":"string"}},{"description":"whether the previous parameter is a filename or string","id_":"f87d9bd6-e85a-4f6d-8300-902e3ba29347","name":"is_file","required":true,"schema":{"type":"boolean"}}],"returns":{"description":"xml string on nmap output","id_":"1cd901cc-2101-4df1-9759-bf74fcdb7b9b","schema":{"type":"string"}}},{"description":"retrieves the hosts and ports from an nmap scan for use with OpenVAS","id_":"f743dfa4-2b06-4d1b-bbc7-55d34b3ce499","name":"ports_and_hosts_from_json","node_type":"ACTION","environment":"cloud","parameters":[{"description":"json string or filename","id_":"ca686f90-c947-4473-9d39-abc8bc4895fd","name":"nmap_json","required":true,"schema":{"type":"string"}},{"description":"whether or not first input is a filename or not","id_":"e605313e-f9c5-4335-8ce7-d46abd422e68","name":"is_file","required":true,"schema":{"type":"boolean"}}],"returns":{"description":"","id_":"040f32fc-d020-469f-9f4c-47928b076688","schema":{"type":"string"}}},{"description":"Runs an nmap scan, returns results as string or filename","id_":"ada798da-fff9-4ccf-85f2-24e2edb7722a","name":"run_scan","node_type":"ACTION","environment":"cloud","parameters":[{"description":"The target(s) to scan, comma separated values, CIDR supported","id_":"72704c22-a9c9-457f-ae33-e7c17e758e7d","name":"targets","required":true,"schema":{"type":"array"}},{"description":"see nmap manpage -- some options require root","id_":"51ea8e70-adae-47a1-9241-87674b4712c1","name":"options","required":true,"schema":{"type":"string"}}],"returns":{"description":"xml string on nmap output","id_":"9c18b2a2-c023-4102-bd46-ddeb31a5430c","schema":{"type":"array"}}},{"description":"Gets the list of active hosts on a network from an nmap scan","id_":"48b4814f-47ea-4c66-a5db-cacc9cd305b6","name":"get_hosts_from_scan","node_type":"ACTION","environment":"cloud","parameters":[{"description":"The target (or targets in CIDR notation) to scan","id_":"e48d60f7-4590-4dd2-98ce-cd112ab9df2f","name":"targets","required":true,"schema":{"type":"array"}},{"description":"","id_":"2eb29670-701e-4622-99f2-80e4b51cb06e","name":"options","required":true,"schema":{"type":"string"}}],"returns":{"description":"xml string on nmap output","id_":"1f710e7d-ef6d-4a28-8ceb-8cbc49f6e197","schema":{"type":"string"}}},{"description":"looks into xml nmap for osfamily to match Linux","id_":"f5425571-2de1-4a84-9221-3829d118617a","name":"parse_xml_for_linux","node_type":"ACTION","environment":"cloud","parameters":[{"description":"nmap output as xml array","id_":"e2e7d0c7-13e8-49f9-85f2-da8bfc73308e","name":"nmap_arr","required":true,"schema":{"type":"array"}}],"returns":{"description":"os","id_":"785f146f-095d-4008-a068-0c3146fdf4f0","schema":{"type":"array"}}},{"description":"looks into xml nmap for osfamily to match Windows","id_":"cf35f64c-43b4-4f23-ae75-70d912f4c1d5","name":"parse_xml_for_windows","node_type":"ACTION","environment":"cloud","parameters":[{"description":"nmap output as xml array","id_":"ef1ce558-2d2a-4d30-8e96-c4ad4c18fae9","name":"nmap_arr","required":true,"schema":{"type":"array"}}],"returns":{"description":"os","id_":"6d21bd12-c076-459e-970d-cd2f39545efb","schema":{"type":"array"}}},{"description":"looks into xml nmap for osfamily","id_":"2baa37be-59cb-4e8a-a67b-1abae654ce4b","name":"parse_xml_for_linux_from_file","node_type":"ACTION","environment":"cloud","parameters":[{"description":"nmap output as xml filename","id_":"aa6324a9-efd2-49c7-a181-90f67b39deb9","name":"nmap_file","required":true,"schema":{"type":"string"}}],"returns":{"description":"os","id_":"96b98b54-2272-4065-b427-2eadf140cb12","schema":{"type":"array"}}}]}]
+const Data = [
+ {
+ name: "hive",
+ is_valid: false,
+ id: "02828a90-658b-41c4-8726-e31da7e02fe9",
+ id_: "02828a90-658b-41c4-8726-e31da7e02fe9",
+ link: "",
+ app_version: "1.0.0",
+ description:
+ "The Hive app allows for walkoff to generate or close cases in TheHive",
+ environment: "cloud",
+ contact_info: { name: "FORGE Cyber", url: "https://github.com/" },
+ actions: [
+ {
+ description: "creates a hive case",
+ id_: "eb84008d-b0e0-41c6-a9b3-d7a51866bcd4",
+ name: "create_case",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "log data to generate custom fields",
+ id_: "b484aba8-0787-42e9-a97a-882a59e02f8f",
+ name: "log_data",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "URL of TheHive",
+ id_: "deb0a68d-479a-4472-b547-1eb82fb3888d",
+ name: "url",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "API key to access TheHive",
+ id_: "5116b359-c8da-41cf-80a4-4292a1a3c90b",
+ name: "api_key",
+ required: true,
+ schema: { type: "string" },
+ },
+ ],
+ returns: {
+ description: "",
+ id_: "b040890c-5462-458b-8f54-054e6ce2a146",
+ schema: { type: "object" },
+ },
+ },
+ {
+ description: "Updates a case data as well as severity",
+ id_: "b7f64744-04e3-4166-990e-70106edc88cf",
+ name: "update_case",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "json from trigger",
+ id_: "ac37d451-ff45-4740-b5a7-de389afcb2b1",
+ name: "input",
+ required: true,
+ schema: { type: "object" },
+ },
+ {
+ description: "id for case",
+ id_: "69613390-78c9-4b5d-aa71-b6c4a4750f8f",
+ name: "id",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "severity of change",
+ id_: "b04523b0-e74e-44a3-9c51-055c1e53167a",
+ name: "severity",
+ required: true,
+ schema: { type: "integer" },
+ },
+ {
+ description: "URL of TheHive",
+ id_: "e415c1f9-d502-4a5d-9c8a-8cdefe0ebf88",
+ name: "url",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "API key to access TheHive",
+ id_: "2aa8e834-b889-467a-9172-373930b013cf",
+ name: "api_key",
+ required: true,
+ schema: { type: "string" },
+ },
+ ],
+ returns: { description: "", id_: "", schema: { type: "" } },
+ },
+ {
+ description: "Closes a case in TheHive",
+ id_: "240f26ef-5045-4711-94a5-af7a947ac5a4",
+ name: "close_case",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "ID of case to close",
+ id_: "71eb8b33-56dc-416f-8ae9-6bee56321f9b",
+ name: "case_id",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "URL of TheHive",
+ id_: "a3bbb859-d6fb-45ec-a08f-8895c8f8b0d5",
+ name: "url",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "API key to access TheHive",
+ id_: "660ba026-886f-413c-809a-8a48961d9228",
+ name: "api_key",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "Resolution status of the case to close.",
+ id_: "6b9604d8-7358-4362-b0f4-0d570a366724",
+ name: "resolution_status",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description:
+ "Impact status of the case to close. The impact status is only captured when resolution status is TruePositive",
+ id_: "11651f4f-4521-4f1b-a2c7-66078f9be886",
+ name: "impact_status",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description:
+ "Tags to add to the case once closed. Comma separated string.",
+ id_: "01a0951f-92f8-485a-b8a2-0664a7e03862",
+ name: "tags",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "Explanation of why the case was closed.",
+ id_: "fa67a17b-b6e0-42e6-8988-03a16484c4b0",
+ name: "summary",
+ required: true,
+ schema: { type: "string" },
+ },
+ ],
+ returns: {
+ description: "",
+ id_: "ebba5506-32c8-4bd2-a004-f32c8ec9cdf6",
+ schema: { type: "object" },
+ },
+ },
+ ],
+ },
+ {
+ name: "walk_off",
+ is_valid: false,
+ id: "1f52e17e-c11b-4cf4-abdb-a216211a0d27",
+ id_: "1f52e17e-c11b-4cf4-abdb-a216211a0d27",
+ link: "",
+ app_version: "1.0.0",
+ description: "An example of a Walkoff App specification",
+ environment: "cloud",
+ contact_info: {
+ name: "Walkoff Team",
+ url: "https://github.com/nsacyber/walkoff",
+ },
+ actions: [
+ {
+ description: "Connect to Walkoff",
+ id_: "a9926e7f-18f9-4b93-b805-27cdb5a232ff",
+ name: "connect",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "Timeout on the request (in seconds)",
+ id_: "300136af-3c30-4e2b-babe-272f845942a0",
+ name: "timeout",
+ required: true,
+ schema: { type: "number" },
+ },
+ {
+ description: "username",
+ id_: "896de900-9ca1-4108-a034-016e9c6ffe54",
+ name: "username",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "password",
+ id_: "e75e75d4-8a6b-4c24-8ffd-61b435683edb",
+ name: "password",
+ required: true,
+ schema: { type: "string" },
+ },
+ ],
+ returns: {
+ description: "",
+ id_: "de121927-243f-4f09-9331-c48ca25e85d1",
+ schema: { type: "string" },
+ },
+ },
+ {
+ description: "Disconnect from Walkoff",
+ id_: "acd81f9b-353c-4749-a33a-2091da2d307c",
+ name: "disconnect",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "Timeout on the request (in seconds)",
+ id_: "76819a03-5272-4421-99cd-1e53f95963f0",
+ name: "timeout",
+ required: true,
+ schema: { type: "number" },
+ },
+ {
+ description: "refresh token",
+ id_: "b583836e-eff4-4e7c-8430-286bfa81e43f",
+ name: "refresh_token",
+ required: true,
+ schema: { type: "string" },
+ },
+ ],
+ returns: {
+ description: "",
+ id_: "d6cc2470-2dc9-43f9-a2f9-fb123c590426",
+ schema: { type: "string" },
+ },
+ },
+ {
+ description: "Gets a list of all the users loaded on the system",
+ id_: "6610a111-687e-45f5-a60d-26048594cdb1",
+ name: "get_all_users",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "Timeout on the request (in seconds)",
+ id_: "89b9a459-f756-40d8-b2c0-1a7c57987fee",
+ name: "timeout",
+ required: true,
+ schema: { type: "number" },
+ },
+ {
+ description: "Access Token",
+ id_: "496c7956-964b-4977-88ed-27db81a94cfe",
+ name: "access_token",
+ required: true,
+ schema: { type: "string" },
+ },
+ ],
+ returns: {
+ description: "",
+ id_: "642ecea4-e747-4dfe-9ca7-4f290847c0ed",
+ schema: { type: "string" },
+ },
+ },
+ {
+ description: "Gets a list of all the workflows loaded on the system",
+ id_: "7cb34d71-2619-40b1-a565-17274fea924e",
+ name: "get_all_workflows",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "Timeout on the request (in seconds)",
+ id_: "e4553287-018d-465a-8599-a605fdb5fb71",
+ name: "timeout",
+ required: true,
+ schema: { type: "number" },
+ },
+ {
+ description: "Access Token",
+ id_: "f9193b36-cc5b-4a5f-babf-7df321837f57",
+ name: "access_token",
+ required: true,
+ schema: { type: "string" },
+ },
+ ],
+ returns: {
+ description: "",
+ id_: "684de06e-b048-4332-88c5-9555ca41e2a1",
+ schema: { type: "string" },
+ },
+ },
+ {
+ description: "Executes a workflow",
+ id_: "9aaf7af4-fa4b-45d1-ac99-5722f0487aef",
+ name: "execute_workflow",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "ID of the workflow",
+ id_: "2f9c0aaf-82ea-4fab-9c26-99268cc9735e",
+ name: "workflow_id",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "Timeout on the request (in seconds)",
+ id_: "35fc2b33-0e03-4b48-9e1c-73aa8a0b4ad7",
+ name: "timeout",
+ required: true,
+ schema: { type: "number" },
+ },
+ {
+ description: "Access Token",
+ id_: "463425b6-ead9-4783-ad40-e04e6d30bc6e",
+ name: "access_token",
+ required: true,
+ schema: { type: "string" },
+ },
+ ],
+ returns: {
+ description: "",
+ id_: "fda59148-a2b1-4180-a34d-8beef5d68929",
+ schema: { type: "string" },
+ },
+ },
+ {
+ description: "Log out of Walkoff",
+ id_: "160693dc-38c4-4a7c-a36b-bd529e69118b",
+ name: "shutdown",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "refresh Token",
+ id_: "a24a2395-7696-48cc-a1b8-b71c881c1180",
+ name: "refresh_token",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "Timeout on the request (in seconds)",
+ id_: "4b0b1d88-203b-4ba3-956f-822def41d0cb",
+ name: "timeout",
+ required: true,
+ schema: { type: "number" },
+ },
+ ],
+ returns: {
+ description: "",
+ id_: "7836185e-786b-4d13-8c9b-b29d89d61155",
+ schema: { type: "string" },
+ },
+ },
+ ],
+ },
+ {
+ name: "ip_addr_utils",
+ is_valid: false,
+ id: "4f00d85c-1fc8-4db2-a1d2-fab1eff5d02e",
+ id_: "4f00d85c-1fc8-4db2-a1d2-fab1eff5d02e",
+ link: "",
+ app_version: "1.0.0",
+ description:
+ "An IP address app that will allow users to specify ip addresses and will format them correctly",
+ environment: "cloud",
+ contact_info: {
+ name: "Walkoff Team",
+ url: "https://github.com/nsacyber/walkoff",
+ },
+ actions: [
+ {
+ description:
+ "Sets the timestamp fro which scipt outputs will be filed under",
+ id_: "5404d09b-08d8-469b-9087-e6b22892188b",
+ name: "set_timestamp",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [],
+ returns: {
+ description: "",
+ id_: "8945dbff-0920-4b04-8682-e9086bcae5a4",
+ schema: { type: "string" },
+ },
+ },
+ {
+ description:
+ "Converts ip address from CIDR notation to individual IP's for easier integration with other apps.",
+ id_: "d6c39b8b-63b7-4198-82d2-b8a4f3ed95f2",
+ name: "cidr_to_array",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "list of hosts to execute on",
+ id_: "14a43bde-2a88-4cf7-987c-e7bb62c202c0",
+ name: "ip_array",
+ required: true,
+ schema: { type: "array" },
+ },
+ ],
+ returns: {
+ description: "",
+ id_: "22c7aac2-321e-451a-a9f4-61b9c6656cb7",
+ schema: { type: "array" },
+ },
+ },
+ ],
+ },
+ {
+ name: "power_shell",
+ is_valid: false,
+ id: "5b570c0b-4f31-4f2e-93ac-da39b78502bb",
+ id_: "5b570c0b-4f31-4f2e-93ac-da39b78502bb",
+ link: "",
+ app_version: "1.0.0",
+ description: "A power shell app that can run commands on a remote host.",
+ environment: "cloud",
+ contact_info: {
+ name: "Walkoff Team",
+ url: "https://github.com/nsacyber/walkoff",
+ },
+ actions: [
+ {
+ description:
+ "Sets the timestamp fro which scipt outputs will be filed under",
+ id_: "8f0cb2b7-1cd1-4284-8790-f8234ec9a1f9",
+ name: "set_timestamp",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [],
+ returns: {
+ description: "",
+ id_: "b58197d0-42fc-40bc-a40f-b1bea1c44b01",
+ schema: { type: "string" },
+ },
+ },
+ {
+ description:
+ 'Executes powershell scripts on remote devices (Scripts located in "scripts" directory within app).',
+ id_: "cb1fb39a-9cd9-485d-8c2a-e4fe138035e8",
+ name: "exec_command_prompt_from_file",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "list of hosts to execute on",
+ id_: "13fd6552-5b2b-4081-89f9-8e9dad5bdea2",
+ name: "hosts",
+ required: true,
+ schema: { type: "array" },
+ },
+ {
+ description: "filename in which scripts will be located",
+ id_: "7a51a4e2-2c7d-4c7f-9aca-dc2d1316f2db",
+ name: "local_file_name",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "Username for remote host",
+ id_: "6ea5d033-308b-453e-84ff-416011b459d7",
+ name: "username",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "Password for remote host user",
+ id_: "b71eb882-4b22-456e-980d-84cbba55216b",
+ name: "password",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "transport type",
+ id_: "78c824c8-97f9-46ea-ba2a-ada98127555b",
+ name: "transport",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "whether server certificate should be validated",
+ id_: "ca847f0c-26de-4289-a13b-0aca5ac55d1c",
+ name: "server_cert_validation",
+ required: true,
+ schema: { type: "boolean" },
+ },
+ {
+ description:
+ 'Will encrypt the WinRM messages if set to True and "transport auth" supports message encryption',
+ id_: "2747d198-2fd2-47a0-8854-4a3dfc5baa77",
+ name: "message_encryption",
+ required: true,
+ schema: { type: "string" },
+ },
+ ],
+ returns: { description: "", id_: "", schema: { type: "" } },
+ },
+ {
+ description: "Executes the powershell command on remote devices.",
+ id_: "70717155-5377-40d1-989b-3db7b90c5975",
+ name: "exec_command_prompt",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "list of hosts to execute on",
+ id_: "b151934c-dea4-4ba9-a993-1e527093b6e4",
+ name: "hosts",
+ required: true,
+ schema: { type: "array" },
+ },
+ {
+ description: "list of commands to execute",
+ id_: "fa6386e4-0e45-453e-8510-6d4affdb7e31",
+ name: "commands",
+ required: true,
+ schema: { type: "array" },
+ },
+ {
+ description: "Username for remote host",
+ id_: "a2c56d5d-48eb-455f-8e4b-155855ac4fa4",
+ name: "username",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "Password for remote host user",
+ id_: "66b4b42e-bd81-4180-8ff8-84fae5e590f2",
+ name: "password",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "transport type",
+ id_: "d0a7b275-8a0f-4421-9b75-7eb3dbc6ff38",
+ name: "transport",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "whether server certificate should be validated",
+ id_: "5df9e652-9e3c-437d-b81f-92e8e0702312",
+ name: "server_cert_validation",
+ required: true,
+ schema: { type: "boolean" },
+ },
+ {
+ description:
+ "Will encrypt the WinRM messages if set to True and the transport auth supports message encryption",
+ id_: "64f64521-5ded-4f0b-8590-f9c6395cd98f",
+ name: "message_encryption",
+ required: true,
+ schema: { type: "string" },
+ },
+ ],
+ returns: { description: "", id_: "", schema: { type: "" } },
+ },
+ {
+ description:
+ "Executes the powershell script on remote devices based on script file passed in.",
+ id_: "d0fabf12-9db6-4a0e-8785-5248b1035051",
+ name: "exec_powershell_script_from_file",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "list of hosts to execute on",
+ id_: "09299ae2-38a0-4fb3-b064-fd3597424d67",
+ name: "hosts",
+ required: true,
+ schema: { type: "array" },
+ },
+ {
+ description: "type of shell you want to execute",
+ id_: "3d076cba-93d2-4f58-abfd-1475b354444d",
+ name: "shell_type",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "filename in which scripts will be located",
+ id_: "b82484c2-861c-4193-925a-e23bd882938c",
+ name: "local_file_name",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "Username for remote host",
+ id_: "79f42b59-2218-4d41-b13e-154b2ed89105",
+ name: "username",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "Password for remote host user",
+ id_: "35ac706c-d375-45ab-add5-40d9b846100c",
+ name: "password",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "transport type",
+ id_: "aa9a2b80-7836-4810-82fa-3c270d9e18f6",
+ name: "transport",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "whether server certificate should be validated",
+ id_: "39cbd4d7-a0cf-4b9c-99bc-5b7c46cfc8df",
+ name: "server_cert_validation",
+ required: true,
+ schema: { type: "boolean" },
+ },
+ {
+ description:
+ "Will encrypt the WinRM messages if set to True and the transport auth supports message encryption",
+ id_: "c7c161e7-c5db-49b8-a7de-b10a203c551b",
+ name: "message_encryption",
+ required: true,
+ schema: { type: "string" },
+ },
+ ],
+ returns: { description: "", id_: "", schema: { type: "" } },
+ },
+ {
+ description:
+ "Executes the powershell command/script on remote devices.",
+ id_: "98c0cd0f-a05a-4b26-809c-5dbf3ecb791f",
+ name: "exec_powershell_script",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "list of hosts to execute on",
+ id_: "9e69e9b0-4639-43dd-9b4c-e4cb710ce63c",
+ name: "hosts",
+ required: true,
+ schema: { type: "array" },
+ },
+ {
+ description: "type of shell you want to execute",
+ id_: "6dce7ac6-b0ac-4d48-a79a-e9db795d751b",
+ name: "shell_type",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "script in the form of array commands",
+ id_: "700bb930-582e-4cbe-85e9-f5656461ecd5",
+ name: "arguments",
+ required: true,
+ schema: { type: "array" },
+ },
+ {
+ description: "Username for remote host",
+ id_: "22f0f58f-7e3c-43f6-afca-8e6800a6052e",
+ name: "username",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "Password for remote host user",
+ id_: "5b53cb8d-eaa4-4d31-8142-31151406100c",
+ name: "password",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "transport type",
+ id_: "635e350e-7a4d-4b94-aa0e-3e0a57ba5577",
+ name: "transport",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "whether server certificate should be validated",
+ id_: "07ec9b94-f626-42e7-a5a8-6e9b68b7bf7d",
+ name: "server_cert_validation",
+ required: true,
+ schema: { type: "boolean" },
+ },
+ {
+ description:
+ "Will encrypt the WinRM messages if set to True and the transport auth supports message encryption",
+ id_: "1ec509eb-0444-4f08-971e-1da23983d588",
+ name: "message_encryption",
+ required: true,
+ schema: { type: "string" },
+ },
+ ],
+ returns: {
+ description: "",
+ id_: "762934c3-8de3-4261-850f-38b12effc90a",
+ schema: { type: "string" },
+ },
+ },
+ ],
+ },
+ {
+ name: "ssh",
+ is_valid: false,
+ id: "68bfeb11-4e8f-4d46-9cf5-5f681be05858",
+ id_: "68bfeb11-4e8f-4d46-9cf5-5f681be05858",
+ link: "",
+ app_version: "1.0.0",
+ description: "Executes ssh shell commands via SSH",
+ environment: "cloud",
+ contact_info: {
+ name: "Walkoff Team",
+ url: "https://github.com/nsacyber/walkoff",
+ },
+ actions: [
+ {
+ description: "Execute command on remote server with SSH client",
+ id_: "2ec132a9-1c32-42eb-8260-bcfac47901fb",
+ name: "exec_command",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "hosts or hostnames of the remote server",
+ id_: "d94d2878-9208-445a-a9b9-852d05ef5b0c",
+ name: "hosts",
+ required: true,
+ schema: { type: "array" },
+ },
+ {
+ description: "port number",
+ id_: "022cd027-b0f2-49ee-a512-da8a07ed2bfc",
+ name: "port",
+ required: true,
+ schema: { type: "integer" },
+ },
+ {
+ description: "json array of arguments",
+ id_: "2d8dff25-dbc9-4656-9de3-73280015eaaf",
+ name: "args",
+ required: true,
+ schema: { type: "array" },
+ },
+ {
+ description: "username to login with",
+ id_: "89145276-3837-4e48-b9ab-0aa76500ba72",
+ name: "username",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "password to login with",
+ id_: "564fb3d7-89ff-41df-b3ed-6ba23d7b3f3e",
+ name: "password",
+ required: true,
+ schema: { type: "string" },
+ },
+ ],
+ returns: {
+ description: "",
+ id_: "1395434e-52c2-4428-a1ac-966a315af6e0",
+ schema: { type: "string" },
+ },
+ },
+ {
+ description: "Run a local bash command",
+ id_: "baa63db8-c8d6-4dea-b774-81fe4b8dddfb",
+ name: "exec_local_command",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "source path of the file to copy",
+ id_: "fe99ea27-1176-4ca5-8de7-a5754f429784",
+ name: "command",
+ required: true,
+ schema: { type: "string" },
+ },
+ ],
+ returns: {
+ description: "",
+ id_: "ac58f084-4234-4380-b438-a2c9239344f2",
+ schema: { type: "string" },
+ },
+ },
+ {
+ description: "Copy remote file to remote host using sftp",
+ id_: "87f34206-3b72-4d01-b64a-9bacdf822526",
+ name: "sftp_copy",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "source path of the file to copy",
+ id_: "43f07eb3-7e1b-4ea3-8684-862111011394",
+ name: "src_path",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "remote path of the file destination",
+ id_: "1a53170b-15b7-4cf8-9dff-9309d56bdacf",
+ name: "dest_path",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "host or hostname of the remote server",
+ id_: "4d78b34e-09ae-48c3-9eeb-f5042903bc04",
+ name: "src_host",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "port number",
+ id_: "29ddd615-05d3-4531-9869-23ca7b366133",
+ name: "src_port",
+ required: true,
+ schema: { type: "integer" },
+ },
+ {
+ description: "username to login with",
+ id_: "f9db153e-0c9f-4808-9814-9277f56b2f47",
+ name: "src_username",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "password to login with",
+ id_: "a6337fa3-0704-49ca-8920-a49229bed77e",
+ name: "src_password",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "host or hostname of the remote server",
+ id_: "04399d82-0ef6-4bdc-b563-344df67219a2",
+ name: "dest_host",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "port number",
+ id_: "262df217-34fa-4165-a753-e71488099729",
+ name: "dest_port",
+ required: true,
+ schema: { type: "integer" },
+ },
+ {
+ description: "username to login with",
+ id_: "4fde41af-112b-4b33-88dc-ce3219b444f3",
+ name: "dest_username",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "password to login with",
+ id_: "d9382c2a-b64c-4ad8-b7ca-488aa8e440fb",
+ name: "dest_password",
+ required: true,
+ schema: { type: "string" },
+ },
+ ],
+ returns: {
+ description: "",
+ id_: "9b484424-f7d7-42ef-b553-f40ec1805806",
+ schema: { type: "string" },
+ },
+ },
+ {
+ description: "runs the specified shell script on the remote server(s)",
+ id_: "bb32494e-b808-4d9b-8f9a-f366a9bfd21e",
+ name: "run_shell_script_file",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "local path of the shell script to run",
+ id_: "41df393d-3435-4e0c-9c7e-dd6531fdc0a6",
+ name: "local_file_name",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "hosts of the remote server",
+ id_: "f2bd124e-1dbb-4b87-a12f-ef7f25d6eb2d",
+ name: "hosts",
+ required: true,
+ schema: { type: "array" },
+ },
+ {
+ description: "port number",
+ id_: "eb2a8834-ea05-4d54-8900-e723e2056132",
+ name: "port",
+ required: true,
+ schema: { type: "integer" },
+ },
+ {
+ description: "username to login with",
+ id_: "ba961ec7-2200-420e-9142-7b237c046809",
+ name: "username",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "password to login with",
+ id_: "65b6d98a-5b23-4d5d-ae57-022057038bae",
+ name: "password",
+ required: true,
+ schema: { type: "string" },
+ },
+ ],
+ returns: {
+ description: "",
+ id_: "94e55d3b-ef12-4114-8c60-07e3e6417df8",
+ schema: { type: "string" },
+ },
+ },
+ ],
+ },
+ {
+ name: "Builtin",
+ is_valid: false,
+ id: "e34f0c67-83e9-443a-b9e7-7b152b4b16f6",
+ id_: "e34f0c67-83e9-443a-b9e7-7b152b4b16f6",
+ link: "",
+ app_version: "1.0.0",
+ description: "Walkoff built-in functions useful in workflow development.",
+ environment: "cloud",
+ contact_info: {
+ name: "Walkoff Team",
+ url: "https://github.com/nsacyber/walkoff",
+ },
+ actions: [
+ {
+ description:
+ "Takes input from an API Call and triggers the rest of the workflow to beign executing again.",
+ id_: "c3e959c6-2c97-48bb-bf00-4082bf812a5d",
+ name: "Trigger",
+ node_type: "TRIGGER",
+ environment: "cloud",
+ parameters: [],
+ returns: {
+ description: "",
+ id_: "6265f75b-e9a7-4d0d-b0e8-7c4da5ac5e65",
+ schema: { type: "string" },
+ },
+ },
+ {
+ description:
+ "Takes input from a previous action and chooses which branch to take according to your logic.",
+ id_: "8b260c29-dd20-405d-b1a6-29d2e67d43e5",
+ name: "Condition",
+ node_type: "CONDITION",
+ environment: "cloud",
+ parameters: [],
+ returns: {
+ description: "",
+ id_: "a4c44cdd-fd3b-46ef-b151-51c1f686fa40",
+ schema: { type: "string" },
+ },
+ },
+ ],
+ },
+ {
+ name: "hello_world",
+ is_valid: false,
+ id: "e66d38eb-19b4-4801-abf2-38248b3b2786",
+ id_: "e66d38eb-19b4-4801-abf2-38248b3b2786",
+ link: "",
+ app_version: "1.0.0",
+ description: "An example of a Walkoff App specification",
+ environment: "cloud",
+ contact_info: {
+ name: "Walkoff Team",
+ url: "https://github.com/nsacyber/walkoff",
+ },
+ actions: [
+ {
+ description:
+ "Returns Hello World from the hostname the action is run on",
+ id_: "ac0e2250-20b1-46a3-93ec-1718f9973cc4",
+ name: "hello_world",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [],
+ returns: {
+ description: "",
+ id_: "66f4cd0e-7a97-4185-a167-3956dcf3f627",
+ schema: { type: "string" },
+ },
+ },
+ {
+ description: "Returns a random float between 0.0 and 1.0",
+ id_: "3817fd8b-1370-4ce8-b874-a526e3c204de",
+ name: "random_number",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [],
+ returns: {
+ description: "",
+ id_: "c788404e-c637-4704-bf9e-1b861060e97e",
+ schema: { type: "number" },
+ },
+ },
+ {
+ description:
+ "returns the outputs from the trigger data if it's in Json format.",
+ id_: "7fae3591-ab32-402d-8dc4-26e8b802c661",
+ name: "repeat_trigger_as_json",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "message to hold output from",
+ id_: "0f0a76ba-b590-4150-ae03-02d0e797f7e4",
+ name: "call",
+ required: true,
+ schema: { type: "string" },
+ },
+ ],
+ returns: {
+ description: "",
+ id_: "1ab56d07-0ce1-4fe5-be01-d70337d9d589",
+ schema: { type: "object" },
+ },
+ },
+ {
+ description: "Repeats the call parameter",
+ id_: "23984f43-7593-4c7b-81b6-77852e498add",
+ name: "repeat_back_to_me",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "message to repeat",
+ id_: "bedf6d97-958c-4acd-b8a9-a72e19c5d54b",
+ name: "call",
+ required: true,
+ schema: { type: "string" },
+ },
+ ],
+ returns: {
+ description: "",
+ id_: "78708871-00b5-49e6-a4b4-d5e265d89f36",
+ schema: { type: "string" },
+ },
+ },
+ {
+ description: "Increments the number parameter by 1",
+ id_: "86624c25-bb66-4fc3-b66f-dc8d394f09bd",
+ name: "return_plus_one",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "number to increment",
+ id_: "45af1005-e7f0-46c3-ac09-28748f022a17",
+ name: "number",
+ required: true,
+ schema: { type: "number" },
+ },
+ ],
+ returns: {
+ description: "",
+ id_: "c84fc303-4d27-4091-9500-cb8cd83d6f05",
+ schema: { type: "number" },
+ },
+ },
+ {
+ description: "Pause execution by the seconds parameter",
+ id_: "bf787802-6039-4010-bc98-2d6d1dbdce21",
+ name: "pause",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "seconds to pause for",
+ id_: "9adcc6f4-0559-47c2-9496-02c8fb3fd58a",
+ name: "seconds",
+ required: true,
+ schema: { type: "number" },
+ },
+ ],
+ returns: { description: "", id_: "", schema: { type: "" } },
+ },
+ {
+ description: "Echo the data parameter",
+ id_: "0a9fec6a-5bf5-4266-a264-dae7fe52c0d5",
+ name: "echo_array",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "array to echo",
+ id_: "8ee260ee-f181-40b3-9d10-f1821f03228c",
+ name: "data",
+ required: true,
+ schema: { type: "array" },
+ },
+ ],
+ returns: {
+ description: "",
+ id_: "bde96402-3d66-43d8-a418-8c0859cb4d01",
+ schema: { type: "array" },
+ },
+ },
+ {
+ description: "echos the given JSON object",
+ id_: "e0927a9c-0e6c-429a-965b-40d0804c13f3",
+ name: "echo_json",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "The data to echo",
+ id_: "ba719062-90a0-42dd-8ef3-eaa304a57667",
+ name: "data",
+ required: true,
+ schema: { type: "object" },
+ },
+ ],
+ returns: {
+ description: "",
+ id_: "fd4f9c20-e4a0-43df-9a14-7bf0046f391f",
+ schema: { type: "object" },
+ },
+ },
+ ],
+ },
+ {
+ name: "nmap",
+ is_valid: false,
+ id: "fc3d231c-9437-4ba9-8fe8-8ba199626197",
+ id_: "fc3d231c-9437-4ba9-8fe8-8ba199626197",
+ link: "",
+ app_version: "1.0.0",
+ description: "A simple app to interact with map",
+ environment: "cloud",
+ contact_info: {
+ name: "Walkoff Team",
+ url: "https://github.com/nsacyber/walkoff",
+ },
+ actions: [
+ {
+ description: "looks into xml nmap for osfamily",
+ id_: "09840ebb-f72a-43e4-b49e-ab5a10d56d96",
+ name: "parse_xml_for_windows_from_file",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "nmap output as xml filename",
+ id_: "2bfccd6f-bd75-4ad0-b8df-5ff74aa64761",
+ name: "nmap_file",
+ required: true,
+ schema: { type: "string" },
+ },
+ ],
+ returns: {
+ description: "os",
+ id_: "9c6900a5-7e20-4033-9f3e-61ce8b95ab5f",
+ schema: { type: "array" },
+ },
+ },
+ {
+ description: "transforms xml nmap results into json",
+ id_: "150e28cb-3333-49ad-859c-b61e2b758ff7",
+ name: "xml_to_json",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "nmap output either as xml filename or string",
+ id_: "5fb18897-fe0e-473f-a83d-3c11f3d8b2a1",
+ name: "nmap_out",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description:
+ "whether the previous parameter is a filename or string",
+ id_: "f87d9bd6-e85a-4f6d-8300-902e3ba29347",
+ name: "is_file",
+ required: true,
+ schema: { type: "boolean" },
+ },
+ ],
+ returns: {
+ description: "xml string on nmap output",
+ id_: "1cd901cc-2101-4df1-9759-bf74fcdb7b9b",
+ schema: { type: "string" },
+ },
+ },
+ {
+ description:
+ "retrieves the hosts and ports from an nmap scan for use with OpenVAS",
+ id_: "f743dfa4-2b06-4d1b-bbc7-55d34b3ce499",
+ name: "ports_and_hosts_from_json",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "json string or filename",
+ id_: "ca686f90-c947-4473-9d39-abc8bc4895fd",
+ name: "nmap_json",
+ required: true,
+ schema: { type: "string" },
+ },
+ {
+ description: "whether or not first input is a filename or not",
+ id_: "e605313e-f9c5-4335-8ce7-d46abd422e68",
+ name: "is_file",
+ required: true,
+ schema: { type: "boolean" },
+ },
+ ],
+ returns: {
+ description: "",
+ id_: "040f32fc-d020-469f-9f4c-47928b076688",
+ schema: { type: "string" },
+ },
+ },
+ {
+ description: "Runs an nmap scan, returns results as string or filename",
+ id_: "ada798da-fff9-4ccf-85f2-24e2edb7722a",
+ name: "run_scan",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description:
+ "The target(s) to scan, comma separated values, CIDR supported",
+ id_: "72704c22-a9c9-457f-ae33-e7c17e758e7d",
+ name: "targets",
+ required: true,
+ schema: { type: "array" },
+ },
+ {
+ description: "see nmap manpage -- some options require root",
+ id_: "51ea8e70-adae-47a1-9241-87674b4712c1",
+ name: "options",
+ required: true,
+ schema: { type: "string" },
+ },
+ ],
+ returns: {
+ description: "xml string on nmap output",
+ id_: "9c18b2a2-c023-4102-bd46-ddeb31a5430c",
+ schema: { type: "array" },
+ },
+ },
+ {
+ description:
+ "Gets the list of active hosts on a network from an nmap scan",
+ id_: "48b4814f-47ea-4c66-a5db-cacc9cd305b6",
+ name: "get_hosts_from_scan",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "The target (or targets in CIDR notation) to scan",
+ id_: "e48d60f7-4590-4dd2-98ce-cd112ab9df2f",
+ name: "targets",
+ required: true,
+ schema: { type: "array" },
+ },
+ {
+ description: "",
+ id_: "2eb29670-701e-4622-99f2-80e4b51cb06e",
+ name: "options",
+ required: true,
+ schema: { type: "string" },
+ },
+ ],
+ returns: {
+ description: "xml string on nmap output",
+ id_: "1f710e7d-ef6d-4a28-8ceb-8cbc49f6e197",
+ schema: { type: "string" },
+ },
+ },
+ {
+ description: "looks into xml nmap for osfamily to match Linux",
+ id_: "f5425571-2de1-4a84-9221-3829d118617a",
+ name: "parse_xml_for_linux",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "nmap output as xml array",
+ id_: "e2e7d0c7-13e8-49f9-85f2-da8bfc73308e",
+ name: "nmap_arr",
+ required: true,
+ schema: { type: "array" },
+ },
+ ],
+ returns: {
+ description: "os",
+ id_: "785f146f-095d-4008-a068-0c3146fdf4f0",
+ schema: { type: "array" },
+ },
+ },
+ {
+ description: "looks into xml nmap for osfamily to match Windows",
+ id_: "cf35f64c-43b4-4f23-ae75-70d912f4c1d5",
+ name: "parse_xml_for_windows",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "nmap output as xml array",
+ id_: "ef1ce558-2d2a-4d30-8e96-c4ad4c18fae9",
+ name: "nmap_arr",
+ required: true,
+ schema: { type: "array" },
+ },
+ ],
+ returns: {
+ description: "os",
+ id_: "6d21bd12-c076-459e-970d-cd2f39545efb",
+ schema: { type: "array" },
+ },
+ },
+ {
+ description: "looks into xml nmap for osfamily",
+ id_: "2baa37be-59cb-4e8a-a67b-1abae654ce4b",
+ name: "parse_xml_for_linux_from_file",
+ node_type: "ACTION",
+ environment: "cloud",
+ parameters: [
+ {
+ description: "nmap output as xml filename",
+ id_: "aa6324a9-efd2-49c7-a181-90f67b39deb9",
+ name: "nmap_file",
+ required: true,
+ schema: { type: "string" },
+ },
+ ],
+ returns: {
+ description: "os",
+ id_: "96b98b54-2272-4065-b427-2eadf140cb12",
+ schema: { type: "array" },
+ },
+ },
+ ],
+ },
+];
export default Data;
diff --git a/frontend/src/__test__/environmentdata.js b/frontend/src/__test__/environmentdata.js
index 7551141b..706d41ce 100644
--- a/frontend/src/__test__/environmentdata.js
+++ b/frontend/src/__test__/environmentdata.js
@@ -1,3 +1,6 @@
-const data = [{"name": "cloud", "type": "cloud"}, {"name": "onprem", "type": "onprem"}]
+const data = [
+ { name: "cloud", type: "cloud" },
+ { name: "onprem", type: "onprem" },
+];
-export default data;
+export default data;
diff --git a/frontend/src/__test__/scheduledata.js b/frontend/src/__test__/scheduledata.js
index 49e05e90..73f5d5ae 100644
--- a/frontend/src/__test__/scheduledata.js
+++ b/frontend/src/__test__/scheduledata.js
@@ -1,30 +1,29 @@
const Data = {
- "src": {
- "name": "Get Tickets",
- "description": "Get tickets",
- "outputparameters": [{
- "name": "SymptomDescription",
- "schema": {"type": "string"}},
- {"name": "DetailedDescription",
- "schema": {"type": "string"}},
- {"name": "EventSource",
- "schema": {"type": "string"}
- }]
- },
- "dst": {
- "name": "Create alert",
- "description": "Create alert in TheHive",
- "inputparameters": [{
- "name": "title",
- "required": true,
- "schema": {"type": "string"}},
- {"name": "description",
- "required": true,
- "schema": {"type": "string"}},
- {"name": "source",
- "required": true,
- "schema": {"type": "string"}
- }]}
+ src: {
+ name: "Get Tickets",
+ description: "Get tickets",
+ outputparameters: [
+ {
+ name: "SymptomDescription",
+ schema: { type: "string" },
+ },
+ { name: "DetailedDescription", schema: { type: "string" } },
+ { name: "EventSource", schema: { type: "string" } },
+ ],
+ },
+ dst: {
+ name: "Create alert",
+ description: "Create alert in TheHive",
+ inputparameters: [
+ {
+ name: "title",
+ required: true,
+ schema: { type: "string" },
+ },
+ { name: "description", required: true, schema: { type: "string" } },
+ { name: "source", required: true, schema: { type: "string" } },
+ ],
+ },
};
export default Data;
diff --git a/frontend/src/__test__/webhookdata.js b/frontend/src/__test__/webhookdata.js
index 1e3a8c55..2142a3f5 100644
--- a/frontend/src/__test__/webhookdata.js
+++ b/frontend/src/__test__/webhookdata.js
@@ -1,15 +1,15 @@
const data = {
- "id":"8ccf0bec1fde018771ab685d2a40bd52",
- "info":{
- "url":"",
- "name":"testing",
- "description":"wut"
- },
- "transforms":{},
- "actions": {},
- "type":"webhook",
- "status":"uninitialized",
- "running":false
-}
+ id: "8ccf0bec1fde018771ab685d2a40bd52",
+ info: {
+ url: "",
+ name: "testing",
+ description: "wut",
+ },
+ transforms: {},
+ actions: {},
+ type: "webhook",
+ status: "uninitialized",
+ running: false,
+};
export default data;
diff --git a/frontend/src/__test__/workflowdata.js b/frontend/src/__test__/workflowdata.js
index 2e2d0254..d1df8ea8 100644
--- a/frontend/src/__test__/workflowdata.js
+++ b/frontend/src/__test__/workflowdata.js
@@ -1,3 +1,169 @@
-const data = {"actions":[{"app_name":"hello_world","app_version":"1.0.0","errors":null,"id":"70574332-da82-cf17-c723-75fa7b8493c2","is_valid":true,"label":"hello_world","environment":"onprem","name":"hello_world","parameters":null,"position":{"x":353.7438792397648,"y":260.6717930890377},"priority":0},{"app_name":"hello_world","app_version":"1.0.0","errors":null,"id":"30522433-56ed-53c3-575d-766e282e1d3e","is_valid":true,"label":"random_number","environment":"cloud","name":"random_number","parameters":null,"position":{"x":458.30040774503794,"y":104.27580103487651},"priority":0},{"app_name":"hello_world","app_version":"1.0.0","errors":null,"id":"5b7ac5b5-9514-02b9-ebe0-998c0843b104","is_valid":false,"label":"hello_world_2","environment":"onprem","name":"hello_world","parameters":null,"position":{"x":414.7256019053981,"y":-140.46450482659628},"priority":0},{"app_name":"hello_world","app_version":"1.0.0","errors":null,"id":"7e6e7a19-4636-cebc-91c4-052a3769a18b","is_valid":true,"label":"hello_world_3","environment":"cloud","name":"hello_world","parameters":null,"position":{"x":83.59752786243806,"y":50.232317715020734},"priority":0},{"app_name":"hello_world","app_version":"1.0.0","errors":null,"id":"edbf927d-5a00-2405-28ed-47982cdf5110","is_valid":true,"label":"hello_world_4","environment":"cloud","name":"hello_world","parameters":null,"position":{"x":-147.30681300186404,"y":89.16690830150289},"priority":0},{"app_name":"hello_world","app_version":"1.0.0","errors":null,"id":"4844a855-1e2b-669d-fc72-5f398321ac5d","is_valid":false,"label":"hello_world_5","environment":"onprem","name":"hello_world","parameters":null,"position":{"x":130.24982593523967,"y":233.8325632286361},"priority":0},{"app_name":"hello_world","app_version":"1.0.0","errors":null,"id":"6d1d3f8a-1ac9-3db4-0e0f-2fe32e9d3c09","is_valid":true,"label":"hello_world_6","environment":"cloud","name":"hello_world","parameters":null,"position":{"x":83.551088005629,"y":-105.15867327274223},"priority":0},{"app_name":"hello_world","app_version":"1.0.0","errors":null,"id":"469d8c2b-52ac-e397-9a29-becccd04aed8","is_valid":true,"label":"hello_world_7","environment":"cloud","name":"hello_world","parameters":null,"position":{"x":314.4987657226086,"y":10.167183586257954},"priority":0}],"branches":[{"destination_id":"30522433-56ed-53c3-575d-766e282e1d3e","id":"4bcb9795-94e6-7d5f-2074-0d5b27784e0b","source_id":"70574332-da82-cf17-c723-75fa7b8493c2"},{"destination_id":"5b7ac5b5-9514-02b9-ebe0-998c0843b104","id":"fe0ab8e4-a535-61cd-3c09-8fd3d8e40769","source_id":"30522433-56ed-53c3-575d-766e282e1d3e"},{"destination_id":"469d8c2b-52ac-e397-9a29-becccd04aed8","id":"8b9ee9bc-b0ab-0bb6-af61-46d4594b2663","source_id":"30522433-56ed-53c3-575d-766e282e1d3e"},{"destination_id":"6d1d3f8a-1ac9-3db4-0e0f-2fe32e9d3c09","id":"c204d5ef-9cc1-d906-9988-86a624c57783","source_id":"469d8c2b-52ac-e397-9a29-becccd04aed8"},{"destination_id":"6d1d3f8a-1ac9-3db4-0e0f-2fe32e9d3c09","id":"1ffb3934-60ec-8f80-5cee-3ddc0a37fdb6","source_id":"5b7ac5b5-9514-02b9-ebe0-998c0843b104"},{"destination_id":"edbf927d-5a00-2405-28ed-47982cdf5110","id":"9c7fb048-9d0d-cb84-9ba0-be729af9b4d1","source_id":"6d1d3f8a-1ac9-3db4-0e0f-2fe32e9d3c09"},{"destination_id":"edbf927d-5a00-2405-28ed-47982cdf5110","id":"e3ab104e-fc8b-3af5-8daa-bfa57bcf9690","source_id":"7e6e7a19-4636-cebc-91c4-052a3769a18b"},{"destination_id":"7e6e7a19-4636-cebc-91c4-052a3769a18b","id":"b6626081-22dd-3af3-b899-480f60d886ca","source_id":"30522433-56ed-53c3-575d-766e282e1d3e"},{"destination_id":"4844a855-1e2b-669d-fc72-5f398321ac5d","id":"4275cf97-0447-bbda-0c80-ab20d389de1a","source_id":"edbf927d-5a00-2405-28ed-47982cdf5110"}],"conditions":[],"triggers":[],"transforms":[],"description":"asd","id":"2f299808-0f1b-4ae0-97fc-ac17483dfcf7","id":"2f299808-0f1b-4ae0-97fc-ac17483dfcf7","is_valid":true,"name":"test2","start":"70574332-da82-cf17-c723-75fa7b8493c2","owner":{"username":"","id":"","orgs":""},"execution_org":{"name":"","org":"","users":null,"id":""},"workflow_variables":null}
+const data = {
+ actions: [
+ {
+ app_name: "hello_world",
+ app_version: "1.0.0",
+ errors: null,
+ id: "70574332-da82-cf17-c723-75fa7b8493c2",
+ is_valid: true,
+ label: "hello_world",
+ environment: "onprem",
+ name: "hello_world",
+ parameters: null,
+ position: { x: 353.7438792397648, y: 260.6717930890377 },
+ priority: 0,
+ },
+ {
+ app_name: "hello_world",
+ app_version: "1.0.0",
+ errors: null,
+ id: "30522433-56ed-53c3-575d-766e282e1d3e",
+ is_valid: true,
+ label: "random_number",
+ environment: "cloud",
+ name: "random_number",
+ parameters: null,
+ position: { x: 458.30040774503794, y: 104.27580103487651 },
+ priority: 0,
+ },
+ {
+ app_name: "hello_world",
+ app_version: "1.0.0",
+ errors: null,
+ id: "5b7ac5b5-9514-02b9-ebe0-998c0843b104",
+ is_valid: false,
+ label: "hello_world_2",
+ environment: "onprem",
+ name: "hello_world",
+ parameters: null,
+ position: { x: 414.7256019053981, y: -140.46450482659628 },
+ priority: 0,
+ },
+ {
+ app_name: "hello_world",
+ app_version: "1.0.0",
+ errors: null,
+ id: "7e6e7a19-4636-cebc-91c4-052a3769a18b",
+ is_valid: true,
+ label: "hello_world_3",
+ environment: "cloud",
+ name: "hello_world",
+ parameters: null,
+ position: { x: 83.59752786243806, y: 50.232317715020734 },
+ priority: 0,
+ },
+ {
+ app_name: "hello_world",
+ app_version: "1.0.0",
+ errors: null,
+ id: "edbf927d-5a00-2405-28ed-47982cdf5110",
+ is_valid: true,
+ label: "hello_world_4",
+ environment: "cloud",
+ name: "hello_world",
+ parameters: null,
+ position: { x: -147.30681300186404, y: 89.16690830150289 },
+ priority: 0,
+ },
+ {
+ app_name: "hello_world",
+ app_version: "1.0.0",
+ errors: null,
+ id: "4844a855-1e2b-669d-fc72-5f398321ac5d",
+ is_valid: false,
+ label: "hello_world_5",
+ environment: "onprem",
+ name: "hello_world",
+ parameters: null,
+ position: { x: 130.24982593523967, y: 233.8325632286361 },
+ priority: 0,
+ },
+ {
+ app_name: "hello_world",
+ app_version: "1.0.0",
+ errors: null,
+ id: "6d1d3f8a-1ac9-3db4-0e0f-2fe32e9d3c09",
+ is_valid: true,
+ label: "hello_world_6",
+ environment: "cloud",
+ name: "hello_world",
+ parameters: null,
+ position: { x: 83.551088005629, y: -105.15867327274223 },
+ priority: 0,
+ },
+ {
+ app_name: "hello_world",
+ app_version: "1.0.0",
+ errors: null,
+ id: "469d8c2b-52ac-e397-9a29-becccd04aed8",
+ is_valid: true,
+ label: "hello_world_7",
+ environment: "cloud",
+ name: "hello_world",
+ parameters: null,
+ position: { x: 314.4987657226086, y: 10.167183586257954 },
+ priority: 0,
+ },
+ ],
+ branches: [
+ {
+ destination_id: "30522433-56ed-53c3-575d-766e282e1d3e",
+ id: "4bcb9795-94e6-7d5f-2074-0d5b27784e0b",
+ source_id: "70574332-da82-cf17-c723-75fa7b8493c2",
+ },
+ {
+ destination_id: "5b7ac5b5-9514-02b9-ebe0-998c0843b104",
+ id: "fe0ab8e4-a535-61cd-3c09-8fd3d8e40769",
+ source_id: "30522433-56ed-53c3-575d-766e282e1d3e",
+ },
+ {
+ destination_id: "469d8c2b-52ac-e397-9a29-becccd04aed8",
+ id: "8b9ee9bc-b0ab-0bb6-af61-46d4594b2663",
+ source_id: "30522433-56ed-53c3-575d-766e282e1d3e",
+ },
+ {
+ destination_id: "6d1d3f8a-1ac9-3db4-0e0f-2fe32e9d3c09",
+ id: "c204d5ef-9cc1-d906-9988-86a624c57783",
+ source_id: "469d8c2b-52ac-e397-9a29-becccd04aed8",
+ },
+ {
+ destination_id: "6d1d3f8a-1ac9-3db4-0e0f-2fe32e9d3c09",
+ id: "1ffb3934-60ec-8f80-5cee-3ddc0a37fdb6",
+ source_id: "5b7ac5b5-9514-02b9-ebe0-998c0843b104",
+ },
+ {
+ destination_id: "edbf927d-5a00-2405-28ed-47982cdf5110",
+ id: "9c7fb048-9d0d-cb84-9ba0-be729af9b4d1",
+ source_id: "6d1d3f8a-1ac9-3db4-0e0f-2fe32e9d3c09",
+ },
+ {
+ destination_id: "edbf927d-5a00-2405-28ed-47982cdf5110",
+ id: "e3ab104e-fc8b-3af5-8daa-bfa57bcf9690",
+ source_id: "7e6e7a19-4636-cebc-91c4-052a3769a18b",
+ },
+ {
+ destination_id: "7e6e7a19-4636-cebc-91c4-052a3769a18b",
+ id: "b6626081-22dd-3af3-b899-480f60d886ca",
+ source_id: "30522433-56ed-53c3-575d-766e282e1d3e",
+ },
+ {
+ destination_id: "4844a855-1e2b-669d-fc72-5f398321ac5d",
+ id: "4275cf97-0447-bbda-0c80-ab20d389de1a",
+ source_id: "edbf927d-5a00-2405-28ed-47982cdf5110",
+ },
+ ],
+ conditions: [],
+ triggers: [],
+ transforms: [],
+ description: "asd",
+ id: "2f299808-0f1b-4ae0-97fc-ac17483dfcf7",
+ id: "2f299808-0f1b-4ae0-97fc-ac17483dfcf7",
+ is_valid: true,
+ name: "test2",
+ start: "70574332-da82-cf17-c723-75fa7b8493c2",
+ owner: { username: "", id: "", orgs: "" },
+ execution_org: { name: "", org: "", users: null, id: "" },
+ workflow_variables: null,
+};
-export default data;
+export default data;
diff --git a/frontend/src/charts.js b/frontend/src/charts.js
index 4751047f..56690e80 100644
--- a/frontend/src/charts.js
+++ b/frontend/src/charts.js
@@ -23,7 +23,7 @@
let chart1_2_options = {
maintainAspectRatio: false,
legend: {
- display: false
+ display: false,
},
tooltips: {
backgroundColor: "#f5f5f5",
@@ -33,7 +33,7 @@ let chart1_2_options = {
xPadding: 12,
mode: "nearest",
intersect: 0,
- position: "nearest"
+ position: "nearest",
},
responsive: true,
scales: {
@@ -43,15 +43,15 @@ let chart1_2_options = {
gridLines: {
drawBorder: false,
color: "rgba(29,140,248,0.0)",
- zeroLineColor: "transparent"
+ zeroLineColor: "transparent",
},
ticks: {
suggestedMin: 60,
suggestedMax: 125,
padding: 20,
- fontColor: "#9a9a9a"
- }
- }
+ fontColor: "#9a9a9a",
+ },
+ },
],
xAxes: [
{
@@ -59,22 +59,22 @@ let chart1_2_options = {
gridLines: {
drawBorder: false,
color: "rgba(29,140,248,0.1)",
- zeroLineColor: "transparent"
+ zeroLineColor: "transparent",
},
ticks: {
padding: 20,
- fontColor: "#9a9a9a"
- }
- }
- ]
- }
+ fontColor: "#9a9a9a",
+ },
+ },
+ ],
+ },
};
// #########################################
// // // used inside src/views/Dashboard.js
// #########################################
let chartExample1 = {
- data1: canvas => {
+ data1: (canvas) => {
let ctx = canvas.getContext("2d");
let gradientStroke = ctx.createLinearGradient(0, 230, 0, 50);
@@ -96,7 +96,7 @@ let chartExample1 = {
"SEP",
"OCT",
"NOV",
- "DEC"
+ "DEC",
],
datasets: [
{
@@ -114,12 +114,12 @@ let chartExample1 = {
pointHoverRadius: 4,
pointHoverBorderWidth: 15,
pointRadius: 4,
- data: [100, 70, 90, 70, 85, 60, 75, 60, 90, 80, 110, 100]
- }
- ]
+ data: [100, 70, 90, 70, 85, 60, 75, 60, 90, 80, 110, 100],
+ },
+ ],
};
},
- data2: canvas => {
+ data2: (canvas) => {
let ctx = canvas.getContext("2d");
let gradientStroke = ctx.createLinearGradient(0, 230, 0, 50);
@@ -141,7 +141,7 @@ let chartExample1 = {
"SEP",
"OCT",
"NOV",
- "DEC"
+ "DEC",
],
datasets: [
{
@@ -159,12 +159,12 @@ let chartExample1 = {
pointHoverRadius: 4,
pointHoverBorderWidth: 15,
pointRadius: 4,
- data: [80, 120, 105, 110, 95, 105, 90, 100, 80, 95, 70, 120]
- }
- ]
+ data: [80, 120, 105, 110, 95, 105, 90, 100, 80, 95, 70, 120],
+ },
+ ],
};
},
- data3: canvas => {
+ data3: (canvas) => {
let ctx = canvas.getContext("2d");
let gradientStroke = ctx.createLinearGradient(0, 230, 0, 50);
@@ -186,7 +186,7 @@ let chartExample1 = {
"SEP",
"OCT",
"NOV",
- "DEC"
+ "DEC",
],
datasets: [
{
@@ -204,19 +204,19 @@ let chartExample1 = {
pointHoverRadius: 4,
pointHoverBorderWidth: 15,
pointRadius: 4,
- data: [60, 80, 65, 130, 80, 105, 90, 130, 70, 115, 60, 130]
- }
- ]
+ data: [60, 80, 65, 130, 80, 105, 90, 130, 70, 115, 60, 130],
+ },
+ ],
};
},
- options: chart1_2_options
+ options: chart1_2_options,
};
// #########################################
// // // used inside src/views/Dashboard.js
// #########################################
let chartExample2 = {
- data: canvas => {
+ data: (canvas) => {
let ctx = canvas.getContext("2d");
let gradientStroke = ctx.createLinearGradient(0, 230, 0, 50);
@@ -243,19 +243,19 @@ let chartExample2 = {
pointHoverRadius: 4,
pointHoverBorderWidth: 15,
pointRadius: 4,
- data: [80, 100, 70, 80, 120, 80]
- }
- ]
+ data: [80, 100, 70, 80, 120, 80],
+ },
+ ],
};
},
- options: chart1_2_options
+ options: chart1_2_options,
};
// #########################################
// // // used inside src/views/Dashboard.js
// #########################################
let chartExample3 = {
- data: canvas => {
+ data: (canvas) => {
let ctx = canvas.getContext("2d");
let gradientStroke = ctx.createLinearGradient(0, 230, 0, 50);
@@ -276,15 +276,15 @@ let chartExample3 = {
borderWidth: 2,
borderDash: [],
borderDashOffset: 0.0,
- data: [53, 20, 10, 80, 100, 45]
- }
- ]
+ data: [53, 20, 10, 80, 100, 45],
+ },
+ ],
};
},
options: {
maintainAspectRatio: false,
legend: {
- display: false
+ display: false,
},
tooltips: {
backgroundColor: "#f5f5f5",
@@ -294,7 +294,7 @@ let chartExample3 = {
xPadding: 12,
mode: "nearest",
intersect: 0,
- position: "nearest"
+ position: "nearest",
},
responsive: true,
scales: {
@@ -303,38 +303,38 @@ let chartExample3 = {
gridLines: {
drawBorder: false,
color: "rgba(225,78,202,0.1)",
- zeroLineColor: "transparent"
+ zeroLineColor: "transparent",
},
ticks: {
suggestedMin: 60,
suggestedMax: 120,
padding: 20,
- fontColor: "#9e9e9e"
- }
- }
+ fontColor: "#9e9e9e",
+ },
+ },
],
xAxes: [
{
gridLines: {
drawBorder: false,
color: "rgba(225,78,202,0.1)",
- zeroLineColor: "transparent"
+ zeroLineColor: "transparent",
},
ticks: {
padding: 20,
- fontColor: "#9e9e9e"
- }
- }
- ]
- }
- }
+ fontColor: "#9e9e9e",
+ },
+ },
+ ],
+ },
+ },
};
// #########################################
// // // used inside src/views/Dashboard.js
// #########################################
const chartExample4 = {
- data: canvas => {
+ data: (canvas) => {
let ctx = canvas.getContext("2d");
let gradientStroke = ctx.createLinearGradient(0, 230, 0, 50);
@@ -361,15 +361,15 @@ const chartExample4 = {
pointHoverRadius: 4,
pointHoverBorderWidth: 15,
pointRadius: 4,
- data: [90, 27, 60, 12, 80]
- }
- ]
+ data: [90, 27, 60, 12, 80],
+ },
+ ],
};
},
options: {
maintainAspectRatio: false,
legend: {
- display: false
+ display: false,
},
tooltips: {
@@ -380,7 +380,7 @@ const chartExample4 = {
xPadding: 12,
mode: "nearest",
intersect: 0,
- position: "nearest"
+ position: "nearest",
},
responsive: true,
scales: {
@@ -390,15 +390,15 @@ const chartExample4 = {
gridLines: {
drawBorder: false,
color: "rgba(29,140,248,0.0)",
- zeroLineColor: "transparent"
+ zeroLineColor: "transparent",
},
ticks: {
suggestedMin: 50,
suggestedMax: 125,
padding: 20,
- fontColor: "#9e9e9e"
- }
- }
+ fontColor: "#9e9e9e",
+ },
+ },
],
xAxes: [
@@ -407,21 +407,21 @@ const chartExample4 = {
gridLines: {
drawBorder: false,
color: "rgba(0,242,195,0.1)",
- zeroLineColor: "transparent"
+ zeroLineColor: "transparent",
},
ticks: {
padding: 20,
- fontColor: "#9e9e9e"
- }
- }
- ]
- }
- }
+ fontColor: "#9e9e9e",
+ },
+ },
+ ],
+ },
+ },
};
module.exports = {
chartExample1, // in src/views/Dashboard.js
chartExample2, // in src/views/Dashboard.js
chartExample3, // in src/views/Dashboard.js
- chartExample4 // in src/views/Dashboard.js
+ chartExample4, // in src/views/Dashboard.js
};
diff --git a/frontend/src/components/AlertPopup.js b/frontend/src/components/AlertPopup.js
index 67b54596..340d03e2 100644
--- a/frontend/src/components/AlertPopup.js
+++ b/frontend/src/components/AlertPopup.js
@@ -1,26 +1,19 @@
-import React, { useEffect} from 'react';
+import React, { useEffect } from "react";
const Popup = (props) => {
- const { data } = props;
+ const { data } = props;
- const popupStyle = {
- position: "fixed",
- width: "300px",
- height: "50px",
- backgroundColor: "black",
- color: "white",
- }
+ const popupStyle = {
+ position: "fixed",
+ width: "300px",
+ height: "50px",
+ backgroundColor: "black",
+ color: "white",
+ };
- const popupData =
-
- HEY
-
+ const popupData =
HEY
;
- return (
-
- {popupData}
-
- )
-}
+ return
{popupData}
;
+};
-export default Popup
+export default Popup;
diff --git a/frontend/src/components/AlertTemplate.js b/frontend/src/components/AlertTemplate.js
index 66f4b1c7..4dfa06d0 100644
--- a/frontend/src/components/AlertTemplate.js
+++ b/frontend/src/components/AlertTemplate.js
@@ -1,46 +1,48 @@
-import React from 'react'
-import InfoIcon from '@material-ui/icons/Info';
-import CheckIcon from '@material-ui/icons/Check';
-import ErrorOutlineIcon from '@material-ui/icons/ErrorOutline';
-import CloseIcon from '@material-ui/icons/Close';
-import Typography from '@material-ui/core/Typography';
+import React from "react";
+import InfoIcon from "@material-ui/icons/Info";
+import CheckIcon from "@material-ui/icons/Check";
+import ErrorOutlineIcon from "@material-ui/icons/ErrorOutline";
+import CloseIcon from "@material-ui/icons/Close";
+import Typography from "@material-ui/core/Typography";
const alertStyle = {
- backgroundColor: 'rgba(0,0,0,0.9)',
- color: 'white',
+ backgroundColor: "rgba(0,0,0,0.9)",
+ color: "white",
padding: 15,
- textTransform: 'uppercase',
- borderRadius: '3px',
- display: 'flex',
- justifyContent: 'space-between',
- alignItems: 'center',
- boxShadow: '0px 2px 2px 2px rgba(0, 0, 0, 0.03)',
+ textTransform: "uppercase",
+ borderRadius: "3px",
+ display: "flex",
+ justifyContent: "space-between",
+ alignItems: "center",
+ boxShadow: "0px 2px 2px 2px rgba(0, 0, 0, 0.03)",
width: 300,
- boxSizing: 'border-box',
- zIndex: 100001,
- overflow: "hidden",
-}
+ boxSizing: "border-box",
+ zIndex: 100001,
+ overflow: "hidden",
+};
const buttonStyle = {
- marginLeft: '20px',
- border: 'none',
- backgroundColor: 'transparent',
- cursor: 'pointer',
- color: '#FFFFFF'
-}
+ marginLeft: "20px",
+ border: "none",
+ backgroundColor: "transparent",
+ cursor: "pointer",
+ color: "#FFFFFF",
+};
const AlertTemplate = ({ message, options, style, close }) => {
return (
- {options.type === 'info' && }
- {options.type === 'success' && }
- {options.type === 'error' && }
- {message}
+ {options.type === "info" && }
+ {options.type === "success" && }
+ {options.type === "error" && (
+
+ )}
+ {message}
- )
-}
+ );
+};
-export default AlertTemplate
+export default AlertTemplate;
diff --git a/frontend/src/components/ConfigureWorkflow.jsx b/frontend/src/components/ConfigureWorkflow.jsx
index 43b93d33..fbd7eb79 100644
--- a/frontend/src/components/ConfigureWorkflow.jsx
+++ b/frontend/src/components/ConfigureWorkflow.jsx
@@ -1,267 +1,372 @@
-import React, {useState} from 'react';
+import React, { useState } from "react";
-import { InputAdornment, Tooltip, TextField, CircularProgress, ButtonGroup, Button, Avatar, ListItemAvatar, Typography, List, ListItem, ListItemText} from '@material-ui/core';
-import {FavoriteBorder as FavoriteBorderIcon} from '@material-ui/icons';
+import {
+ InputAdornment,
+ Tooltip,
+ TextField,
+ CircularProgress,
+ ButtonGroup,
+ Button,
+ Avatar,
+ ListItemAvatar,
+ Typography,
+ List,
+ ListItem,
+ ListItemText,
+} from "@material-ui/core";
+import { FavoriteBorder as FavoriteBorderIcon } from "@material-ui/icons";
import { FixName } from "../views/Apps.jsx";
// Handles workflow updates on first open to highlight the issues of the workflow
// Variables
-// Action (exists, missing fields)
+// Action (exists, missing fields)
// Action auth
// Triggers
//
// Specifically used for UNSAVED workflows only?
const ConfigureWorkflow = (props) => {
- const { globalUrl, theme, workflow, appAuthentication, setSelectedAction, setAuthenticationModalOpen, setSelectedApp, apps, selectedAction,setConfigureWorkflowModalOpen, saveWorkflow, newWebhook, submitSchedule, referenceUrl, isCloud, setAuthenticationType, alert, } = props
- const [requiredActions, setRequiredActions] = React.useState([])
- const [requiredVariables, setRequiredVariables] = React.useState([])
- const [requiredTriggers, setRequiredTriggers] = React.useState([])
- const [previousAuth, setPreviousAuth] = React.useState(appAuthentication)
- const [firstLoad, setFirstLoad] = React.useState("")
- const [itemChanged, setItemChanged] = React.useState(false)
- var finished = false
+ const {
+ globalUrl,
+ theme,
+ workflow,
+ appAuthentication,
+ setSelectedAction,
+ setAuthenticationModalOpen,
+ setSelectedApp,
+ apps,
+ selectedAction,
+ setConfigureWorkflowModalOpen,
+ saveWorkflow,
+ newWebhook,
+ submitSchedule,
+ referenceUrl,
+ isCloud,
+ setAuthenticationType,
+ alert,
+ } = props;
+ const [requiredActions, setRequiredActions] = React.useState([]);
+ const [requiredVariables, setRequiredVariables] = React.useState([]);
+ const [requiredTriggers, setRequiredTriggers] = React.useState([]);
+ const [previousAuth, setPreviousAuth] = React.useState(appAuthentication);
+ const [firstLoad, setFirstLoad] = React.useState("");
+ const [itemChanged, setItemChanged] = React.useState(false);
+ var finished = false;
- if (workflow === undefined || workflow === null) {
- return null
- }
+ if (workflow === undefined || workflow === null) {
+ return null;
+ }
- if (apps === undefined || apps === null) {
- return null
- }
+ if (apps === undefined || apps === null) {
+ return null;
+ }
- if (appAuthentication === undefined || appAuthentication === null) {
- return null
- }
+ if (appAuthentication === undefined || appAuthentication === null) {
+ return null;
+ }
- const getApp = (actionId, appId) => {
- fetch(globalUrl+"/api/v1/apps/"+appId+"/config?openapi=false", {
- headers: {
- 'Accept': 'application/json',
- },
- credentials: "include",
- })
- .then((response) => {
- if (response.status === 200) {
- //alert.success("Successfully GOT app "+appId)
- } else {
- alert.error("Failed getting app")
- }
+ const getApp = (actionId, appId) => {
+ fetch(globalUrl + "/api/v1/apps/" + appId + "/config?openapi=false", {
+ headers: {
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status === 200) {
+ //alert.success("Successfully GOT app "+appId)
+ } else {
+ alert.error("Failed getting app");
+ }
- return response.json()
- })
- .then((responseJson) => {
- console.log("ACTION: ", responseJson)
- if (responseJson.actions !== undefined && responseJson.actions !== null) {
- }
- })
- .catch(error => {
- alert.error(error.toString())
- });
- }
+ return response.json();
+ })
+ .then((responseJson) => {
+ console.log("ACTION: ", responseJson);
+ if (
+ responseJson.actions !== undefined &&
+ responseJson.actions !== null
+ ) {
+ }
+ })
+ .catch((error) => {
+ alert.error(error.toString());
+ });
+ };
- if (firstLoad.length === 0 || firstLoad !== workflow.id) {
- if (finished) {
- setConfigureWorkflowModalOpen(false)
- return null
- }
+ if (firstLoad.length === 0 || firstLoad !== workflow.id) {
+ if (finished) {
+ setConfigureWorkflowModalOpen(false);
+ return null;
+ }
- if (apps === undefined || apps === null || apps.length === 0) {
- console.log("No apps loaded: ", apps)
- setConfigureWorkflowModalOpen(false)
- return null
- }
+ if (apps === undefined || apps === null || apps.length === 0) {
+ console.log("No apps loaded: ", apps);
+ setConfigureWorkflowModalOpen(false);
+ return null;
+ }
- setFirstLoad(workflow.id)
- const newactions = []
- for (var key in workflow.actions) {
- const action = workflow.actions[key]
- var newaction = {
- "large_image": action.large_image,
- "app_name": action.app_name,
- "app_version": action.app_version,
- "activation_done": false,
- "must_activate": false,
- "must_authenticate": false,
- "auth_done": false,
- "action_ids": [],
- "action": action,
- "app": {},
- }
+ setFirstLoad(workflow.id);
+ const newactions = [];
+ for (var key in workflow.actions) {
+ const action = workflow.actions[key];
+ var newaction = {
+ large_image: action.large_image,
+ app_name: action.app_name,
+ app_version: action.app_version,
+ activation_done: false,
+ must_activate: false,
+ must_authenticate: false,
+ auth_done: false,
+ action_ids: [],
+ action: action,
+ update_version: action.app_version,
+ app: {},
+ };
- const app = apps.find(app => app.name === action.app_name && (app.app_version === action.app_version || (app.loop_versions !== null && app.loop_versions.includes(action.app_version))))
- if (app === undefined || app === null) {
- console.log("App not found: ", action.app_name)
-
- newaction.must_activate = true
- } else {
- if (action.authentication_id === "" && app.authentication.required === true) {
- // Check if configuration is filled or not
- var filled = true
- for (var key in action.parameters) {
- if (action.parameters[key].configuration) {
- //console.log("Found config: ", action.parameters[key])
- if (action.parameters[key].value === null || action.parameters[key].value.length === 0) {
- filled = false
- break
- }
- }
- }
-
- if (!filled) {
- newaction.must_authenticate = true
- newaction.action_ids.push(action.id)
- }
+ const app = apps.find(
+ (app) =>
+ app.name === action.app_name &&
+ (app.app_version === action.app_version ||
+ (app.loop_versions !== null &&
+ app.loop_versions.includes(action.app_version)))
+ );
+ if (app === undefined || app === null) {
+ //console.log("App not found: ", action.app_name);
+
+ const subapp = apps.find(app => app.name === action.app_name)
+ if (subapp !== undefined && subapp !== null) {
+ newaction.update_version = "1.1.0"
}
- newaction.app = app
- }
- if (action.errors !== undefined && action.errors !== null && action.errors.length > 0) {
- //console.log("Node has errors!: ", action.errors)
- }
+ newaction.must_activate = true;
+ } else {
+ if (
+ action.authentication_id === "" &&
+ app.authentication.required === true
+ ) {
+ // Check if configuration is filled or not
+ var filled = true;
+ for (var key in action.parameters) {
+ if (action.parameters[key].configuration) {
+ //console.log("Found config: ", action.parameters[key])
+ if (
+ action.parameters[key].value === null ||
+ action.parameters[key].value.length === 0
+ ) {
+ filled = false;
+ break;
+ }
+ }
+ }
+
+ if (!filled) {
+ newaction.must_authenticate = true;
+ newaction.action_ids.push(action.id);
+ }
+ } else if (action.authentication_id !== "" && app.authentication.required === true) {
+ console.log("Should verify authentication ID ", action.authentication_id)
- if (newaction.must_authenticate) {
- var authenticationOptions = []
- for (var key in appAuthentication) {
- const auth = appAuthentication[key]
- if (auth.app.name === app.name && auth.active) {
- //console.log("Found auth: ", auth)
- authenticationOptions.push(auth)
- newaction.authenticationId = auth.id
- break
- }
}
- if (newaction.authenticationId === null || newaction.authenticationId === undefined || newaction.authenticationId.length === "") {
- //console.log("FAILED to authenticate node!")
+ newaction.app = app;
+ }
- if (newactions.find(tmpaction => tmpaction.app_id === newaction.app_id && tmpaction.app_name === newaction.app_name) !== undefined) {
- console.log("Action already found.")
- } else {
- newactions.push(newaction)
- }
- } else {
- //console.log("Skipping node as it's already authenticated.")
- newaction.authentication = authenticationOptions
- workflow.actions[key] = newaction
- }
- } else if (newaction.must_activate) {
+ if (
+ action.errors !== undefined &&
+ action.errors !== null &&
+ action.errors.length > 0
+ ) {
+ //console.log("Node has errors!: ", action.errors)
+ }
- if (newactions.find(tmpaction => tmpaction.app_id === newaction.app_id && tmpaction.app_name === newaction.app_name) !== undefined) {
- console.log("Action already found.")
- } else {
- newactions.push(newaction)
- }
- }
- }
+ if (newaction.must_authenticate) {
+ var authenticationOptions = [];
+ for (var key in appAuthentication) {
+ const auth = appAuthentication[key];
+ if (auth.app.name === app.name && auth.active) {
+ //console.log("Found auth: ", auth)
+ authenticationOptions.push(auth);
+ newaction.authenticationId = auth.id;
+ break;
+ }
+ }
- for (var key in workflow.workflow_variables) {
- const variable = workflow.workflow_variables[key]
- if (variable.value === undefined || variable.value === undefined || variable.value.length < 2) {
- variable.value = ""
- variable.index = key
- requiredVariables.push(variable)
- }
- }
+ if (
+ newaction.authenticationId === null ||
+ newaction.authenticationId === undefined ||
+ newaction.authenticationId.length === ""
+ ) {
+ //console.log("FAILED to authenticate node!")
- for (var key in workflow.triggers) {
- var trigger = workflow.triggers[key]
- trigger.index = key
+ if (
+ newactions.find(
+ (tmpaction) =>
+ tmpaction.app_id === newaction.app_id &&
+ tmpaction.app_name === newaction.app_name
+ ) !== undefined
+ ) {
+ //console.log("Action already found.");
+ } else {
+ newactions.push(newaction);
+ }
+ } else {
+ //console.log("Skipping node as it's already authenticated.")
+ newaction.authentication = authenticationOptions;
+ workflow.actions[key] = newaction;
+ }
+ } else if (newaction.must_activate) {
+ if (
+ newactions.find(
+ (tmpaction) =>
+ tmpaction.app_id === newaction.app_id &&
+ tmpaction.app_name === newaction.app_name
+ ) !== undefined
+ ) {
+ console.log("Action already found.");
+ } else {
+ newactions.push(newaction);
+ }
+ }
+ }
- if (trigger.status === "running") {
- continue
- }
+ for (var key in workflow.workflow_variables) {
+ const variable = workflow.workflow_variables[key];
+ if (
+ variable.value === undefined ||
+ variable.value === undefined ||
+ variable.value.length < 2
+ ) {
+ variable.value = "";
+ variable.index = key;
+ requiredVariables.push(variable);
+ }
+ }
- if (trigger.trigger_type === "SUBFLOW" || trigger.trigger_type === "USERINPUT") {
- continue
- }
-
- requiredTriggers.push(trigger)
- }
+ for (var key in workflow.triggers) {
+ var trigger = workflow.triggers[key];
+ trigger.index = key;
- if (requiredTriggers.length === 0 && requiredVariables.length === 0 && newactions.length === 0) {
- setConfigureWorkflowModalOpen(false)
- }
+ if (trigger.status === "running") {
+ continue;
+ }
- setRequiredTriggers(requiredTriggers)
- setRequiredVariables(requiredVariables)
- setRequiredActions(newactions)
- }
+ if (
+ trigger.trigger_type === "SUBFLOW" ||
+ trigger.trigger_type === "USERINPUT"
+ ) {
+ continue;
+ }
- if (appAuthentication.length !== previousAuth.length) {
- var newactions = []
- for (var actionkey in requiredActions) {
- var newaction = requiredActions[actionkey]
- const app = newaction.app
+ requiredTriggers.push(trigger);
+ }
- for (var key in appAuthentication) {
- const auth = appAuthentication[key]
- if (auth.app.name === app.name && auth.active) {
- newaction.auth_done = true
- break
- }
- }
+ if (
+ requiredTriggers.length === 0 &&
+ requiredVariables.length === 0 &&
+ newactions.length === 0
+ ) {
+ setConfigureWorkflowModalOpen(false);
+ }
- newactions.push(newaction)
- }
+ setRequiredTriggers(requiredTriggers);
+ setRequiredVariables(requiredVariables);
+ setRequiredActions(newactions);
+ }
- setRequiredActions(newactions)
- setPreviousAuth(appAuthentication)
- // Set auth done to true
- //"auth_done": false
- }
+ if (appAuthentication.length !== previousAuth.length) {
+ var newactions = [];
+ for (var actionkey in requiredActions) {
+ var newaction = requiredActions[actionkey];
+ const app = newaction.app;
- const TriggerSection = (props) => {
- const {trigger} = props
+ for (var key in appAuthentication) {
+ const auth = appAuthentication[key];
+ if (auth.app.name === app.name && auth.active) {
+ newaction.auth_done = true;
+ break;
+ }
+ }
- return (
-
-
-
-
-
-
-
- {trigger.trigger_type === "WEBHOOK" && trigger.status !== "running" ?
- {
- workflow.triggers[trigger.index].status = "running"
- if (workflow.triggers[trigger.index].parameters === null) {
- workflow.triggers[trigger.index].parameters = [
- {"name": "url", "value": referenceUrl+"webhook_"+trigger.id},
- {"name": "tmp", "value": "webhook_"+trigger.id},
- ]
- }
+ newactions.push(newaction);
+ }
- newWebhook(workflow.triggers[trigger.index])
- saveWorkflow(workflow)
- setItemChanged(true)
- }}>
- {trigger.status !== "running" ? "Start" : "Running"}
-
- :
- trigger.trigger_type === "SCHEDULE" && trigger.status !== "running" ?
- {
- workflow.triggers[trigger.index].status = "running"
- if (workflow.triggers[trigger.index].parameters === null) {
- workflow.triggers[trigger.index].parameters = [
- {"name": "cron", "value": isCloud ? "*/15 * * * *" : "120"},
- {"name": "execution_argument", "value": '{"example": {"json": "is cool"}}'},
- ]
- }
+ setRequiredActions(newactions);
+ setPreviousAuth(appAuthentication);
+ // Set auth done to true
+ //"auth_done": false
+ }
- submitSchedule(workflow.triggers[trigger.index], trigger.index)
- saveWorkflow(workflow)
- setItemChanged(true)
- }}>
- {trigger.status !== "running" ? "Start" : "Running"}
-
- :
- null}
- {/*
+ const TriggerSection = (props) => {
+ const { trigger } = props;
+
+ return (
+
+
+
+
+
+
+
+ {trigger.trigger_type === "WEBHOOK" && trigger.status !== "running" ? (
+ {
+ workflow.triggers[trigger.index].status = "running";
+ if (workflow.triggers[trigger.index].parameters === null) {
+ workflow.triggers[trigger.index].parameters = [
+ {
+ name: "url",
+ value: referenceUrl + "webhook_" + trigger.id,
+ },
+ { name: "tmp", value: "webhook_" + trigger.id },
+ ];
+ }
+
+ newWebhook(workflow.triggers[trigger.index]);
+ saveWorkflow(workflow);
+ setItemChanged(true);
+ }}
+ >
+ {trigger.status !== "running" ? "Start" : "Running"}
+
+ ) : trigger.trigger_type === "SCHEDULE" &&
+ trigger.status !== "running" ? (
+ {
+ workflow.triggers[trigger.index].status = "running";
+ if (workflow.triggers[trigger.index].parameters === null) {
+ workflow.triggers[trigger.index].parameters = [
+ { name: "cron", value: isCloud ? "*/15 * * * *" : "120" },
+ {
+ name: "execution_argument",
+ value: '{"example": {"json": "is cool"}}',
+ },
+ ];
+ }
+
+ submitSchedule(workflow.triggers[trigger.index], trigger.index);
+ saveWorkflow(workflow);
+ setItemChanged(true);
+ }}
+ >
+ {trigger.status !== "running" ? "Start" : "Running"}
+
+ ) : null}
+ {/*
{
style={{}}
/>
*/}
-
- )
- }
+
+ );
+ };
- const VariableSection = (props) => {
- const {variable} = props
+ const VariableSection = (props) => {
+ const { variable } = props;
- //
Name: {variable.name} - {variable.value}.
- return (
-
-
-
-
-
-
-
-
-
- )
- }}
- fullWidth
- color="primary"
- type={"text"}
- placeholder={`New value for ${variable.name}`}
- onChange={(event) => {
- console.log("NEW VALUE ON INDEX", variable.index, variable.value)
- }}
- onBlur={(event) => {
- workflow.workflow_variables[variable.index].value = event.target.value
- }}
- />
+ //Name: {variable.name} - {variable.value}.
+ return (
+
+
+
+
+
+
+
+ ,
+ }}
+ fullWidth
+ color="primary"
+ type={"text"}
+ placeholder={`New value for ${variable.name}`}
+ onChange={(event) => {
+ console.log(
+ "NEW VALUE ON INDEX",
+ variable.index,
+ variable.value
+ );
+ }}
+ onBlur={(event) => {
+ workflow.workflow_variables[variable.index].value =
+ event.target.value;
+ }}
+ />
+ }
+ style={{}}
+ />
+
+ );
+ };
+
+ const activateApp = (app_id, app_name, app_version) => {
+ fetch(
+ `${globalUrl}/api/v1/apps/${app_id}/activate?app_name=${app_name}&app_version=${app_version}`,
+ {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ }
+ )
+ .then((response) => {
+ if (response.status !== 200) {
+ //window.location.pathname = "/search"
+ //alert.error("Failed to find this app. Is it public?")
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (responseJson.success === false) {
+ if (responseJson.reason !== undefined) {
+ alert.error("Failed to activate the app: "+responseJson.reason);
+ } else {
+ alert.error("Failed to activate the app");
}
- style={{}}
- />
-
- )
- }
+ } else {
+ alert.success("App activated for your organization!");
+ }
+ })
+ .catch((error) => {
+ alert.error(error.toString());
+ });
+ };
- const AppSection = (props) => {
- const {action} = props
- return (
-
-
-
-
-
-
-
- {action.must_authenticate ?
- action.auth_done ?
- {}}>
- Authenticated
-
- :
- selectedAction.app_name === action.app_name ?
-
- :
- {
- setAuthenticationType(action.app.authentication.type === "oauth2" && action.app.authentication.redirect_uri !== undefined && action.app.authentication.redirect_uri !== null ?
- {
- "type": "oauth2",
- "redirect_uri": action.app.authentication.redirect_uri,
- "token_uri": action.app.authentication.token_uri,
- "scope": action.app.authentication.scope,
- } : {
- "type": ""
+
+ const AppSection = (props) => {
+ const { action } = props;
+
+ return (
+
+
+
+
+
+
+
+ {action.must_authenticate ? (
+ action.auth_done ? (
+ {}}>
+ Authenticated
+
+ ) : selectedAction.app_name === action.app_name ? (
+
+ ) : (
+ {
+ setAuthenticationType(
+ action.app.authentication.type === "oauth2" &&
+ action.app.authentication.redirect_uri !== undefined &&
+ action.app.authentication.redirect_uri !== null
+ ? {
+ type: "oauth2",
+ redirect_uri: action.app.authentication.redirect_uri,
+ token_uri: action.app.authentication.token_uri,
+ scope: action.app.authentication.scope,
+ }
+ : {
+ type: "",
+ }
+ );
+
+ setItemChanged(true);
+ setSelectedAction(action.action);
+ setSelectedApp(action.app);
+ setAuthenticationModalOpen(true);
+ }}
+ >
+ Authenticate
+
+ )
+ ) : null}
+ {action.must_activate ? (
+ {
+ console.log("ACTION: ", action)
+ activateApp(action.action.app_id, action.app_name, action.app_version);
+ setItemChanged(true);
+ }}
+ >
+ Activate
+
+ ) : null}
+ {action.update_version !== action.app_version ? (
+ {
+ console.log("Set version to: ", action.update_version)
+
+ if (workflow.actions !== null) {
+ //console.log(workflow.actions)
+ alert.info("Setting action to version "+action.update_version)
+ for (var key in workflow.actions) {
+ if (workflow.actions[key].app_name === action.app_name && workflow.actions[key].app_version === action.app_version) {
+ workflow.actions[key].app_version = action.update_version
+
+ if (!itemChanged) {
+ setItemChanged(true)
}
- )
+ }
+ }
- setItemChanged(true)
- setSelectedAction(action.action)
- setSelectedApp(action.app)
- setAuthenticationModalOpen(true)
- }}>
- Authenticate
-
- :
- null}
- {action.must_activate ?
- {
- activateApp(action.app_id, action.app_name, action.app_version)
- setItemChanged(true)
- }}>
- Activate
-
- : null}
-
- )
- }
+ action.must_activate = false
+ action.update_version = action.app_version
+ }
+ }}
+ >
+ {action.update_version}
+
+ ) : null}
+
+ );
+ };
- const activateApp = (app_id, app_name, app_version) => {
- fetch(`${globalUrl}/api/v1/apps/app_id/activate?app_name=${app_name}&app_version=${app_version}`, {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- },
- credentials: "include",
- })
- .then((response) => {
- if (response.status !== 200) {
- //window.location.pathname = "/search"
- //alert.error("Failed to find this app. Is it public?")
- }
+ return (
+
+
{workflow.name}
+
+ The following configuration makes the workflow ready immediately.
+
+ {requiredActions.length > 0 ? (
+
+
+ Actions
+
+
+ {requiredActions.map((data, index) => {
+ return ;
+ })}
+
+
+ ) : null}
- return response.json()
- })
- .then((responseJson) => {
- if (responseJson.success === false) {
- alert.error("Failed to activate the app")
- } else {
- alert.success("App activated for your organization!")
- }
- })
- .catch(error => {
- alert.error(error.toString())
- });
- }
+ {requiredVariables.length > 0 ? (
+
+
+ Variables
+
+
+ {requiredVariables.map((data, index) => {
+ return ;
+ })}
+
+
+ ) : null}
- return (
-
-
{workflow.name}
-
- The following configuration makes the workflow ready immediately.
-
- {requiredActions.length > 0 ?
-
- Actions
-
- {requiredActions.map((data, index) => {
- return (
-
- )
- })}
-
-
- : null}
-
- {requiredVariables.length > 0 ?
-
- Variables
-
- {requiredVariables.map((data, index) => {
- return (
-
- )
- })}
-
-
- : null}
-
-
- {requiredTriggers.length > 0 ?
-
- Triggers
-
- {requiredTriggers.map((data, index) => {
- return (
-
- )
- })}
-
-
- : null }
-
-
- {/*
+ {requiredTriggers.length > 0 ? (
+
+
+ Triggers
+
+
+ {requiredTriggers.map((data, index) => {
+ return ;
+ })}
+
+
+ ) : null}
+
+
+ {/*
{
setConfigureWorkflowModalOpen(false)
@@ -490,21 +653,25 @@ const ConfigureWorkflow = (props) => {
Skip
*/}
- {
- if (itemChanged) {
- saveWorkflow(workflow)
- window.location.reload()
- } else {
- setConfigureWorkflowModalOpen(false)
- }
- }}>
- Finish setup
-
-
-
-
- )
-}
+
{
+ if (itemChanged) {
+ saveWorkflow(workflow);
+ window.location.reload();
+ } else {
+ setConfigureWorkflowModalOpen(false);
+ }
+ }}
+ >
+ Close window
+
+
+
+
+ );
+};
-export default ConfigureWorkflow
+export default ConfigureWorkflow;
diff --git a/frontend/src/components/DetectionFramework.jsx b/frontend/src/components/DetectionFramework.jsx
new file mode 100644
index 00000000..6dba209a
--- /dev/null
+++ b/frontend/src/components/DetectionFramework.jsx
@@ -0,0 +1,1652 @@
+import React, { useState, useEffect } from 'react';
+
+import { securityFramework } from "./LandingpageUsecases.jsx";
+import CytoscapeComponent from 'react-cytoscapejs';
+import frameworkStyle from '../frameworkStyle.jsx';
+import WorkflowSearch from './Workflowsearch.jsx';
+import { v4 as uuidv4 } from "uuid";
+import theme from '../theme';
+import { useAlert } from "react-alert";
+
+import {
+ Paper,
+ Typography,
+ Divider,
+ IconButton,
+ Badge,
+ CircularProgress,
+ Tooltip,
+} from "@material-ui/core";
+
+import {
+ Button
+} from '@material-ui/core';
+
+import {
+ Close as CloseIcon,
+ Delete as DeleteIcon,
+} from "@material-ui/icons";
+
+import * as edgehandles from "cytoscape-edgehandles";
+import * as cytoscape from "cytoscape";
+
+cytoscape.use(edgehandles);
+
+
+const svgSize = 23
+const parsedDatatypeImages = {
+ "Cases": encodeURI(`data:image/svg+xml;utf-8,
`),
+ "EDR & AV": encodeURI(`data:image/svg+xml;utf-8,
`),
+}
+
+export const usecases = {
+ "None": {
+ "manual": [],
+ "automated": [],
+ },
+ "Phishing": {
+ "manual": [],
+ "automated": [
+ {
+ "source": "BOTTOM_LEFT",
+ "target": "COMMS",
+ "description": "Email received",
+ "human": false,
+ },
+ {
+ "source": "COMMS",
+ "target": "SHUFFLE",
+ "human": false,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "CASES",
+ "human": false,
+ },
+ {
+ "source": "INTEL",
+ "target": "SHUFFLE",
+ "human": false,
+ },
+ {
+ "source": "ASSETS",
+ "target": "SHUFFLE",
+ "human": false,
+ },
+ {
+ "source": "SIEM",
+ "target": "SHUFFLE",
+ "human": false,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "INTEL",
+ "human": false,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "COMMS",
+ "human": false,
+ },
+ {
+ "source": "CASES",
+ "target": "EDR & AV",
+ "human": true,
+ },
+ ]},
+ "Ransomware": {
+ "manual": [],
+ "automated": [
+ {
+ "source": "BOTTOM_LEFT",
+ "target": "EDR & AV",
+ "description": "EDR & AV alert",
+ "human": false,
+ },
+ {
+ "source": "EDR & AV",
+ "target": "SHUFFLE",
+ "description": "",
+ "human": false,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "EDR & AV",
+ "human": false,
+ "description": "isolate",
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "IAM",
+ "human": false,
+ "description": "Block access",
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "COMMS",
+ "description": "Notify oncall and affected user",
+ "human": false,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "CASES",
+ "description": "Create enriched alert",
+ "human": false,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "CASES",
+ "human": false,
+ },
+ {
+ "source": "CASES",
+ "target": "EDR & AV",
+ "description": "Validate alert",
+ "human": true,
+ },
+ ]
+ },
+ "Exploits": {
+ "manual": [],
+ "automated": [
+ {
+ "source": "TOP_LEFT",
+ "target": "NETWORK",
+ "description": "Exploit",
+ "human": false,
+ },
+ {
+ "source": "NETWORK",
+ "target": "SIEM",
+ "description": "WAF alert",
+ "human": false,
+ },
+ {
+ "source": "SIEM",
+ "target": "SHUFFLE",
+ "human": false,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "CASES",
+ "human": false,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "COMMS",
+ "human": false,
+ },
+ {
+ "source": "INTEL",
+ "target": "SHUFFLE",
+ "human": false,
+ },
+ {
+ "source": "ASSETS",
+ "target": "SHUFFLE",
+ "human": false,
+ },
+ {
+ "source": "IAM",
+ "target": "SHUFFLE",
+ "human": false,
+ },
+ {
+ "source": "CASES",
+ "target": "EDR & AV",
+ "human": true,
+ },
+ ]
+ },
+ "AWS S3 honeypots": {
+ "manual": [],
+ "automated": [
+ {
+ "source": "TOP_LEFT",
+ "target": "SIEM",
+ "description": "S3 logs",
+ "human": false,
+ },
+
+ {
+ "source": "SIEM",
+ "target": "SHUFFLE",
+ "human": false,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "INTEL",
+ "description": "Add sighting",
+ "human": false,
+ },
+ {
+ "source": "INTEL",
+ "target": "SHUFFLE",
+ "human": false,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "CASES",
+ "description": "Create case",
+ "human": false,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "NETWORK",
+ "description": "Block IP",
+ "human": false,
+ },
+ ]
+ },
+ "SIEM alerts": {
+ "manual": [],
+ "automated": [
+ {
+ "source": "TOP_LEFT",
+ "target": "SIEM",
+ "description": "Syslog",
+ "human": false,
+ },
+ {
+ "source": "SIEM",
+ "target": "SHUFFLE",
+ "description": "Alerts",
+ "human": false,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "INTEL",
+ "description": "Enrich",
+ "human": false,
+ },
+ {
+ "source": "INTEL",
+ "target": "SHUFFLE",
+ "human": false,
+ },
+ {
+ "source": "IAM",
+ "target": "SHUFFLE",
+ "human": false,
+ "description": "enrich",
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "IAM",
+ "human": false,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "CASES",
+ "human": false,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "COMMS",
+ "human": false,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "EDR & AV",
+ "human": false,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "EDR & AV",
+ "human": true,
+ },
+ ]
+ },
+ "New Detections": {
+ "manual": [],
+ "automated": [
+ {
+ "source": "TOP_LEFT",
+ "target": "SIEM",
+ "description": "Hypothesis",
+ "human": true,
+ },
+ {
+ "source": "SIEM",
+ "target": "SIEM",
+ "description": "Create rule",
+ "human": true,
+ },
+ {
+ "source": "SIEM",
+ "target": "NETWORK",
+ "description": "Create rule",
+ "human": true,
+ },
+ {
+ "source": "NETWORK",
+ "target": "EDR & AV",
+ "description": "Create rule",
+ "human": true,
+ },
+ {
+ "source": "SIEM",
+ "target": "SHUFFLE",
+ "description": "Send alert",
+ "human": false,
+ },
+ {
+ "source": "NETWORK",
+ "target": "SHUFFLE",
+ "description": "Send alert",
+ "human": false,
+ },
+ {
+ "source": "EDR & AV",
+ "target": "SHUFFLE",
+ "description": "Send alert",
+ "human": false,
+ },
+ {
+ "source": "INTEL",
+ "target": "SHUFFLE",
+ "description": "Enrich IOCs",
+ "human": false,
+ },
+ {
+ "source": "ASSETS",
+ "target": "SHUFFLE",
+ "description": "Enrich hostnames etc.",
+ "human": false,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "CASES",
+ "description": "Create enriched alert",
+ "human": false,
+ },
+ ]
+ },
+ "Vulnerabilities": {
+ "manual": [],
+ "automated": [
+ {
+ "source": "TOP_RIGHT",
+ "target": "ASSETS",
+ "description": "New vuln",
+ "human": false,
+ },
+ {
+ "source": "ASSETS",
+ "target": "SHUFFLE",
+ "description": "Get vuln",
+ "human": false,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "CASES",
+ "description": "Raise ticket",
+ "human": false,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "COMMS",
+ "description": "Notify owner",
+ "human": false,
+ },
+ {
+ "source": "COMMS",
+ "target": "SHUFFLE",
+ "description": "",
+ "human": true,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "ASSETS",
+ "description": "Auto-patch",
+ "human": false,
+ },
+ ]
+ },
+ "Approvals": {
+ "manual": [],
+ "automated": [
+ {
+ "source": "TOP_LEFT",
+ "target": "CASES",
+ "description": "New inquiry",
+ "human": false,
+ },
+ {
+ "source": "CASES",
+ "target": "SHUFFLE",
+ "description": "Get tickets",
+ "human": false,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "COMMS",
+ "description": "Ask for approval",
+ "human": false,
+ },
+ {
+ "source": "COMMS",
+ "target": "SHUFFLE",
+ "human": true,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "ASSETS",
+ "description": "Add to user",
+ "human": false,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "IAM",
+ "description": "Approve access",
+ "human": false,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "CASES",
+ "human": false,
+ },
+ ]
+ },
+ "Enrichment": {
+ "manual": [],
+ "automated": [
+ {
+ "source": "TOP_LEFT",
+ "target": "CASES",
+ "description": "Case updated",
+ "human": false,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "CASES",
+ "description": "Get and enrich ticket",
+ "human": false,
+ },
+ {
+ "source": "IAM",
+ "target": "SHUFFLE",
+ "description": "Get access rights",
+ "human": false,
+ },
+ {
+ "source": "ASSETS",
+ "target": "SHUFFLE",
+ "description": "Get relevant assets",
+ "human": false,
+ },
+ {
+ "source": "INTEL",
+ "target": "SHUFFLE",
+ "description": "Get relevant IPs",
+ "human": false,
+ },
+ {
+ "source": "COMMS",
+ "target": "SHUFFLE",
+ "description": "Find relevant mails & chats",
+ "human": false,
+ },
+ {
+ "source": "EDR & AV",
+ "target": "SHUFFLE",
+ "description": "Find incidents for host",
+ "human": false,
+ },
+ {
+ "source": "SIEM",
+ "target": "SHUFFLE",
+ "description": "Find info about hostname and user",
+ "human": false,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "SHUFFLE",
+ "description": "Format info",
+ "human": false,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "CASES",
+ "description": "",
+ "human": false,
+ },
+ ]
+ },
+ "Draw": {
+ }
+}
+
+const Framework = (props) => {
+ const {globalUrl, isLoaded, showOptions, selectedOption, rolling, frameworkData, size, inputUsecase, isLoggedIn, } = props;
+ const [cy, setCy] = React.useState()
+ const [edgesStarted, setEdgesStarted] = React.useState(false)
+ const [graphDone, setGraphDone] = React.useState(false)
+ const [cyDone, setCyDone] = React.useState(false)
+ const [discoveryData, setDiscoveryData] = React.useState({})
+ const [selectionOpen, setSelectionOpen] = React.useState(true)
+ const [newSelectedApp, setNewSelectedApp] = React.useState({})
+ const [defaultSearch, setDefaultSearch] = React.useState("")
+ const [animationStarted, setAnimationStarted] = React.useState(false)
+ const scale = size === undefined ? 1 : size > 5 ? 3 : size
+
+ const alert = useAlert()
+
+ const setUsecaseItem = (inputUsecase) => {
+ var parsedUsecase = inputUsecase
+ const edges = cy.edges().jsons()
+
+ parsedUsecase.process = []
+ for (var key in edges) {
+ const edge = edges[key]
+ console.log("Edge: ", edge)
+ parsedUsecase.process.push(edge.data)
+ }
+
+ fetch(globalUrl + "/api/v1/workflows/usecases", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ body: JSON.stringify(parsedUsecase),
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for framework!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (responseJson.success === false) {
+ if (responseJson.reason !== undefined) {
+ alert.error("Failed updating: " + responseJson.reason)
+ } else {
+ alert.error("Failed to update framework for your org.")
+ }
+ } else {
+ alert.info("Updated usecase.")
+ }
+ })
+ .catch((error) => {
+ alert.error(error.toString());
+ //setFrameworkLoaded(true)
+ })
+ }
+
+ const setFrameworkItem = (data) => {
+ fetch(globalUrl + "/api/v1/apps/frameworkConfiguration", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ body: JSON.stringify(data),
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for framework!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (responseJson.success === false) {
+ if (responseJson.reason !== undefined) {
+ alert.error("Failed updating: " + responseJson.reason)
+ } else {
+ alert.error("Failed to update framework for your org.")
+
+ }
+ }
+
+ //setFrameworkLoaded(true)
+ //setFrameworkData(responseJson)
+ })
+ .catch((error) => {
+ alert.error(error.toString());
+ //setFrameworkLoaded(true)
+ })
+ }
+
+ useEffect(() => {
+ if (discoveryData.id === undefined) {
+ return
+ }
+
+ if (newSelectedApp.objectID === undefined || newSelectedApp.objectID === undefined || newSelectedApp.objectID.length === 0) {
+ return
+ }
+
+ const submitValue = {
+ "type": discoveryData.id,
+ "name": newSelectedApp.name,
+ "id": newSelectedApp.objectID,
+ }
+
+ const foundelement = cy.getElementById(discoveryData.id)
+ if (foundelement !== undefined && foundelement !== null) {
+ foundelement.data("large_image", newSelectedApp.image_url)
+ foundelement.data("margin_x", "0px")
+ foundelement.data("margin_y", "0px")
+ foundelement.data("text_margin_y", `${60*scale}px`)
+ foundelement.data("width", `${85*scale}px`)
+ foundelement.data("height", `${85*scale}px`)
+ }
+
+ setFrameworkItem(submitValue)
+
+ }, [newSelectedApp])
+
+
+ const isCloud =
+ window.location.host === "localhost:3002" ||
+ window.location.host === "shuffler.io";
+
+ const imgSize = 50;
+ var parsedFrameworkData = frameworkData
+
+ if (frameworkData !== undefined) {
+ if (frameworkData.cases !== undefined) {
+ frameworkData.Cases = frameworkData.cases
+ }
+ if (frameworkData.siem !== undefined) {
+ frameworkData.SIEM = frameworkData.siem
+ }
+ if (frameworkData.assets !== undefined) {
+ frameworkData.Assets = frameworkData.assets
+ }
+ if (frameworkData.intel !== undefined) {
+ frameworkData.Intel = frameworkData.intel
+ }
+ if (frameworkData.communication !== undefined) {
+ frameworkData.Comms = frameworkData.communication
+ }
+ if (frameworkData.network !== undefined) {
+ frameworkData.Network = frameworkData.network
+ }
+ if (frameworkData.iam !== undefined) {
+ frameworkData.IAM = frameworkData.iam
+ }
+ if (frameworkData.edr !== undefined) {
+ frameworkData["EDR & AV"] = frameworkData.edr
+ }
+
+ parsedFrameworkData = frameworkData
+ } else {
+ console.log("No frameworkdata: ")
+ parsedFrameworkData = {
+ "Cases": {},
+ "SIEM": {},
+ "Assets": {},
+ "IAM": {},
+ "Intel": {},
+ "Comms": {},
+ "Network": {},
+ "EDR & AV": {},
+ }
+ }
+
+ // 0 = automated, 1 = manual
+ const [usecaseType, setUsecaseType] = React.useState(0)
+ const [selectedUsecase, setSelectedUsecase] = React.useState(selectedOption !== undefined ? selectedOption : "Phishing")
+
+ const elements = []
+ const surfaceColor = "#27292D"
+
+
+ const changeUsecase = (value, type) => {
+ //console.log("Value: ", value)
+ if (value === "Draw" && !edgesStarted) {
+ setEdgesStarted(true)
+ cy.edgehandles({
+ handleNodes: (el) => {
+ if (el.isNode() &&
+ !el.data("isButton") &&
+ !el.data("isDescriptor") &&
+ el.data("type") !== "COMMENT") {
+ return true
+ }
+
+ return false
+ },
+ preview: true,
+ toggleOffOnLeave: true,
+ loopAllowed: function (node) {
+ return false;
+ },
+ })
+
+ }
+
+ setSelectedUsecase(value)
+
+ const edges = cy.edges()
+ if (edges !== undefined) {
+ const allEdges = edges.jsons()
+ for (var key in allEdges) {
+ const newedge = allEdges[key]
+ const foundelement = cy.getElementById(newedge.data.id)
+ if (foundelement !== undefined && foundelement !== null) {
+ foundelement.remove()
+ }
+ }
+ }
+
+ var found = false
+
+ var parsedType = type === 1 ? "manual" : "automated"
+ if (usecases === undefined || usecases === null) {
+ return
+ }
+
+ const newedges = usecases[value][parsedType]
+ for (var key in newedges) {
+ newedges[key].label = parseInt(key)+1
+
+ if (newedges[key].description !== undefined && newedges[key].description !== null && newedges[key].description.length > 0) {
+ newedges[key].label = (parseInt(key)+1)+" "+newedges[key].description
+ }
+
+ cy.add({
+ group: "edges",
+ data: newedges[key],
+ })
+ }
+
+ setGraphDone(true)
+ }
+
+ const animationDuration = 150;
+ const nodeAddDuration = 2500;
+
+ if (cy !== undefined && cyDone && !animationStarted) {
+ const foundelement = cy.getElementById("CASES")
+
+ if (foundelement !== undefined && foundelement !== null) {
+ setAnimationStarted(true)
+
+ var parsedStyle = {
+ "ghost": "yes",
+ "border-color": "#f86a3e",
+ "border-width": "10px",
+ "border-opacity": ".7",
+ }
+
+ var parsedStyle2 = {
+ "ghost": "yes",
+ "border-width": "7px",
+ }
+
+ /*
+ // Animation
+ for (var i = 0; i < 10; i++) {
+ foundelement.animate(
+ {
+ style: parsedStyle,
+ },
+ {
+ duration: nodeAddDuration,
+ }
+ ).delay(1000)
+
+ foundelement.animate(
+ {
+ style: parsedStyle2,
+ },
+ {
+ duration: nodeAddDuration,
+ }
+ )
+ }
+ */
+ }
+ }
+
+ const onNodeAdded = (event) => {
+ //if (event.target.data("animate") === true) {
+ //}
+ }
+
+ const onNodeHover = (event) => {
+ const nodedata = event.target.data();
+
+
+ const cytoscapeElement = document.getElementById("cytoscape_view")
+ if (cytoscapeElement !== undefined && cytoscapeElement !== null) {
+ cytoscapeElement.style.cursor = "pointer"
+ }
+
+ var parsedStyle = {
+ "border-width": "7px",
+ "border-opacity": ".7",
+ }
+
+ //if (nodedata.type !== "COMMENT") {
+ // parsedStyle.color = "white";
+ //}
+
+ if (event.target !== undefined && event.target !== null) {
+ event.target.animate(
+ {
+ style: parsedStyle,
+ },
+ {
+ duration: animationDuration,
+ }
+ );
+ }
+ }
+
+ const onNodeHoverOut = (event) => {
+ const nodedata = event.target.data();
+ //console.log("Node OUT: ", nodedata)
+
+ const cytoscapeElement = document.getElementById("cytoscape_view")
+ if (cytoscapeElement !== undefined && cytoscapeElement !== null) {
+ cytoscapeElement.style.cursor = "default"
+ }
+
+ var parsedStyle = {
+ "border-width": "3px",
+ //"cursor": "default",
+ }
+
+ event.target.animate(
+ {
+ style: parsedStyle,
+ },
+ {
+ duration: animationDuration,
+ }
+ )
+ }
+
+
+ const onNodeSelect = (event) => {
+ const data = event.target.data();
+ console.log("Node: ", data)
+ if (data.id === "SHUFFLE") {
+ event.target.unselect()
+ return
+ }
+
+ setDiscoveryData(data)
+ setSelectionOpen(true)
+ setNewSelectedApp({})
+
+ setDefaultSearch(data.label.charAt(0).toUpperCase()+(data.label.substring(1)).toLowerCase())
+ }
+
+ const onEdgeAdded = (event) => {
+ //event.target.data("human", false)
+ }
+
+ const onEdgeSelect = (event) => {
+ console.log(event)
+
+ if (event.target.data("human") === undefined || event.target.data("human") === false) {
+ event.target.data("human", true)
+ } else if (event.target.data("human") === true) {
+ event.target.remove()
+ return
+ //event.target.data("human", false)
+ }
+
+ console.log("Edge selected!", event.target.data())
+ event.target.unselect()
+ }
+
+ if (graphDone && cyDone === false) {
+ cy.fit(null, 200);
+ cy.on("select", "edge", (e) => onEdgeSelect(e));
+ cy.on("add", "edge", (e) => onEdgeAdded(e));
+
+ cy.on("select", "node", (e) => {
+ onNodeSelect(e)
+ })
+
+ cy.on("mouseover", "node", (e) => {onNodeHover(e)})
+ cy.on("mouseout", "node", (e) => onNodeHoverOut(e));
+ cy.on("add", "node", (e) => onNodeAdded(e));
+
+ setCyDone(true)
+ }
+
+ if (cy !== undefined && cy.elements().length === 0 && parsedFrameworkData !== undefined) {
+ //'background-image': 'data(small_image)',
+ const shiftradius = 115*scale
+ const baselocationX = 285*scale
+ const baselocationY = 50*scale
+ const shiftmodifier = 3*scale
+ //const svgSize = `${40*scale}px`
+ const svgSize = `${40}px`
+ console.log("Size: ", svgSize)
+
+
+ console.log("Framework: ", parsedFrameworkData)
+ const fontSize = `${12*scale}px`
+ const defaultSize = `${85*scale}px`
+ const iconSize = `${45*scale}px`
+ const textMarginDefault = `${14*scale}px`
+ const textMarginImage = `${60*scale}px`
+ const nodes = [
+ {
+ group: "nodes",
+ data: {
+ boxwidth: defaultSize,
+ boxheight: defaultSize,
+ font_size: fontSize,
+ is_valid: true,
+ isValid: true,
+ errors: [],
+ name: parsedFrameworkData.Cases.name === undefined ? "" : parsedFrameworkData.Cases.name,
+ description: parsedFrameworkData.Cases.description === undefined ? "" : parsedFrameworkData.Cases.description,
+ app_id: parsedFrameworkData.Cases.id === undefined ? "" : parsedFrameworkData.Cases.id,
+ text_margin_y: parsedFrameworkData.Cases.large_image === undefined ? textMarginDefault : textMarginImage,
+ margin_x: parsedFrameworkData.Cases.large_image === undefined ? `${32*scale}px` : "0px",
+ margin_y: parsedFrameworkData.Cases.large_image === undefined ? `${19*scale}px` : `0px`,
+ width: parsedFrameworkData.Cases.large_image === undefined ? iconSize : defaultSize,
+ height: parsedFrameworkData.Cases.large_image === undefined ? iconSize : defaultSize,
+ large_image: parsedFrameworkData.Cases.large_image === undefined ? encodeURI(`data:image/svg+xml;utf-8,
+
+ `) : parsedFrameworkData.Cases.large_image,
+ label: securityFramework[0].text.toUpperCase(),
+ id: securityFramework[0].text.toUpperCase(),
+ animate: true,
+ },
+ renderedPosition: {
+ x: baselocationX,
+ y: baselocationY,
+ }
+ },
+ {
+ group: "nodes",
+ data: {
+ font_size: fontSize,
+ boxwidth: defaultSize,
+ boxheight: defaultSize,
+ is_valid: true,
+ isValid: true,
+ errors: [],
+ name: parsedFrameworkData.IAM.name === undefined ? "" : parsedFrameworkData.IAM.name,
+ description: parsedFrameworkData.IAM.description === undefined ? "" : parsedFrameworkData.IAM.description,
+ app_id: parsedFrameworkData.IAM.id === undefined ? "" : parsedFrameworkData.IAM.id,
+ text_margin_y: parsedFrameworkData.IAM.large_image === undefined ? textMarginDefault : textMarginImage,
+ margin_x: parsedFrameworkData.IAM.large_image === undefined ? `${32*scale}px` : "0px",
+ margin_y: parsedFrameworkData.IAM.large_image === undefined ? `${19*scale}px` : `0px`,
+ width: parsedFrameworkData.IAM.large_image === undefined ? iconSize : defaultSize,
+ height: parsedFrameworkData.IAM.large_image === undefined ? iconSize : defaultSize,
+ large_image: parsedFrameworkData.IAM.large_image === undefined ? encodeURI(`data:image/svg+xml;utf-8,
+ ,
+ `) : parsedFrameworkData.IAM.large_image,
+ label: securityFramework[3].text.toUpperCase(),
+ id: securityFramework[3].text.toUpperCase(),
+ animate: false,
+ },
+ renderedPosition: {
+ x: baselocationX+shiftradius+(shiftradius/shiftmodifier),
+ y: baselocationY+shiftradius-(shiftradius/shiftmodifier),
+ }
+ },
+ {
+ group: "nodes",
+ data: {
+ font_size: fontSize,
+ boxwidth: defaultSize,
+ boxheight: defaultSize,
+ is_valid: true,
+ isValid: true,
+ errors: [],
+ name: parsedFrameworkData.Assets.name === undefined ? "" : parsedFrameworkData.Assets.name,
+ description: parsedFrameworkData.Assets.description === undefined ? "" : parsedFrameworkData.Assets.description,
+ app_id: parsedFrameworkData.Assets.id === undefined ? "" : parsedFrameworkData.Assets.id,
+ text_margin_y: parsedFrameworkData.Assets.large_image === undefined ? textMarginDefault : textMarginImage,
+ margin_x: parsedFrameworkData.Assets.large_image === undefined ? `${32*scale}px` : "0px",
+ margin_y: parsedFrameworkData.Assets.large_image === undefined ? `${19*scale}px` : `0px`,
+ width: parsedFrameworkData.Assets.large_image === undefined ? iconSize : defaultSize,
+ height: parsedFrameworkData.Assets.large_image === undefined ? iconSize : defaultSize,
+ large_image: parsedFrameworkData.Assets.large_image === undefined ? encodeURI(`data:image/svg+xml;utf-8,
+ ,
+ `) : parsedFrameworkData.Assets.large_image,
+ label: securityFramework[2].text.toUpperCase(),
+ id: securityFramework[2].text.toUpperCase(),
+ animate: false,
+ },
+ renderedPosition: {
+ x: baselocationX+shiftradius*2,
+ y: baselocationY+shiftradius*2,
+ }
+ },
+ {
+ group: "nodes",
+ data: {
+ font_size: fontSize,
+ boxwidth: defaultSize,
+ boxheight: defaultSize,
+ is_valid: true,
+ isValid: true,
+ errors: [],
+ name: parsedFrameworkData.Intel.name === undefined ? "" : parsedFrameworkData.Intel.name,
+ description: parsedFrameworkData.Intel.description === undefined ? "" : parsedFrameworkData.Intel.description,
+ app_id: parsedFrameworkData.Intel.id === undefined ? "" : parsedFrameworkData.Intel.id,
+ text_margin_y: parsedFrameworkData.Intel.large_image === undefined ? textMarginDefault : textMarginImage,
+ margin_x: parsedFrameworkData.Intel.large_image === undefined ? `${32*scale}px` : "0px",
+ margin_y: parsedFrameworkData.Intel.large_image === undefined ? `${19*scale}px` : `0px`,
+ width: parsedFrameworkData.Intel.large_image === undefined ? iconSize : defaultSize,
+ height: parsedFrameworkData.Intel.large_image === undefined ? iconSize : defaultSize,
+ large_image: parsedFrameworkData.Intel.large_image === undefined ? encodeURI(`data:image/svg+xml;utf-8,
+ ,
+ `): parsedFrameworkData.Intel.large_image,
+ label: securityFramework[4].text.toUpperCase(),
+ id: securityFramework[4].text.toUpperCase(),
+ animate: false,
+ },
+ renderedPosition: {
+ x: baselocationX+shiftradius+(shiftradius/shiftmodifier),
+ y: baselocationY+shiftradius*3+(shiftradius/shiftmodifier),
+ }
+ },
+ {
+ group: "nodes",
+ data: {
+ font_size: fontSize,
+ boxwidth: defaultSize,
+ boxheight: defaultSize,
+ is_valid: true,
+ isValid: true,
+ errors: [],
+ name: parsedFrameworkData.Comms.name === undefined ? "" : parsedFrameworkData.Comms.name,
+ description: parsedFrameworkData.Comms.description === undefined ? "" : parsedFrameworkData.Comms.description,
+ app_id: parsedFrameworkData.Comms.id === undefined ? "" : parsedFrameworkData.Comms.id,
+ text_margin_y: parsedFrameworkData.Comms.large_image === undefined ? textMarginDefault : textMarginImage,
+ margin_x: parsedFrameworkData.Comms.large_image === undefined ? `${32*scale}px` : "0px",
+ margin_y: parsedFrameworkData.Comms.large_image === undefined ? `${19*scale}px` : `0px`,
+ width: parsedFrameworkData.Comms.large_image === undefined ? iconSize : defaultSize,
+ height: parsedFrameworkData.Comms.large_image === undefined ? iconSize : defaultSize,
+ large_image: parsedFrameworkData.Comms.large_image === undefined ? encodeURI(`data:image/svg+xml;utf-8,
+ ,
+ `) : parsedFrameworkData.Comms.large_image,
+ label: securityFramework[5].text.toUpperCase(),
+ id: securityFramework[5].text.toUpperCase(),
+ animate: false,
+ },
+ renderedPosition: {
+ x: baselocationX,
+ y: baselocationY+shiftradius*4,
+ }
+ },
+ {
+ group: "nodes",
+ data: {
+ font_size: fontSize,
+ boxwidth: defaultSize,
+ boxheight: defaultSize,
+ is_valid: true,
+ isValid: true,
+ errors: [],
+ name: parsedFrameworkData["EDR & AV"].name === undefined ? "" : parsedFrameworkData["EDR & AV"].name,
+ description: parsedFrameworkData["EDR & AV"].description === undefined ? "" : parsedFrameworkData["EDR & AV"].description,
+ app_id: parsedFrameworkData["EDR & AV"].id === undefined ? "" : parsedFrameworkData["EDR & AV"].id,
+ text_margin_y: parsedFrameworkData["EDR & AV"].large_image === undefined ? textMarginDefault : textMarginImage,
+ margin_x: parsedFrameworkData["EDR & AV"].large_image === undefined ? `${32*scale}px` : "0px",
+ margin_y: parsedFrameworkData["EDR & AV"].large_image === undefined ? `${19*scale}px` : `0px`,
+ width: parsedFrameworkData["EDR & AV"].large_image === undefined ? iconSize : defaultSize,
+ height: parsedFrameworkData["EDR & AV"].large_image === undefined ? iconSize : defaultSize,
+ large_image: parsedFrameworkData["EDR & AV"].large_image === undefined ? encodeURI(`data:image/svg+xml;utf-8,
+ ,
+ `) : parsedFrameworkData["EDR & AV"].large_image,
+ label: securityFramework[7].text.toUpperCase(),
+ id: securityFramework[7].text.toUpperCase(),
+ animate: false,
+ },
+ renderedPosition: {
+ x: baselocationX-shiftradius-(shiftradius/shiftmodifier),
+ y: baselocationY+shiftradius*3+(shiftradius/shiftmodifier),
+ }
+ },
+ {
+ group: "nodes",
+ data: {
+ font_size: fontSize,
+ boxwidth: defaultSize,
+ boxheight: defaultSize,
+ is_valid: true,
+ isValid: true,
+ errors: [],
+ name: parsedFrameworkData.Network.name === undefined ? "" : parsedFrameworkData.Network.name,
+ description: parsedFrameworkData.Network.description === undefined ? "" : parsedFrameworkData.Network.description,
+ app_id: parsedFrameworkData.Network.id === undefined ? "" : parsedFrameworkData.Network.id,
+ text_margin_y: parsedFrameworkData.Network.large_image === undefined ? textMarginDefault : textMarginImage,
+ margin_x: parsedFrameworkData.Network.large_image === undefined ? `${32*scale}px` : "0px",
+ margin_y: parsedFrameworkData.Network.large_image === undefined ? `${19*scale}px` : `0px`,
+ width: parsedFrameworkData.Network.large_image === undefined ? iconSize : defaultSize,
+ height: parsedFrameworkData.Network.large_image === undefined ? iconSize : defaultSize,
+ large_image: parsedFrameworkData.Network.large_image === undefined ? encodeURI(`data:image/svg+xml;utf-8,
+ ,
+ `) : parsedFrameworkData.Network.large_image,
+ label: securityFramework[6].text.toUpperCase(),
+ id: securityFramework[6].text.toUpperCase(),
+ animate: false,
+ },
+ renderedPosition: {
+ x: baselocationX-shiftradius*2,
+ y: baselocationY+shiftradius*2,
+ }
+ },
+ {
+ group: "nodes",
+ data: {
+ font_size: fontSize,
+ boxwidth: defaultSize,
+ boxheight: defaultSize,
+ is_valid: true,
+ isValid: true,
+ errors: [],
+ name: parsedFrameworkData.SIEM.name === undefined ? "" : parsedFrameworkData.SIEM.name,
+ description: parsedFrameworkData.SIEM.description === undefined ? "" : parsedFrameworkData.SIEM.description,
+ app_id: parsedFrameworkData.SIEM.id === undefined ? "" : parsedFrameworkData.SIEM.id,
+ text_margin_y: parsedFrameworkData.SIEM.large_image === undefined ? textMarginDefault : textMarginImage,
+ margin_x: parsedFrameworkData.SIEM.large_image === undefined ? `${32*scale}px` : "0px",
+ margin_y: parsedFrameworkData.SIEM.large_image === undefined ? `${19*scale}px` : `0px`,
+ width: parsedFrameworkData.SIEM.large_image === undefined ? iconSize : defaultSize,
+ height: parsedFrameworkData.SIEM.large_image === undefined ? iconSize : defaultSize,
+ large_image: parsedFrameworkData.SIEM.large_image === undefined ? encodeURI(`data:image/svg+xml;utf-8,
+ ,
+ `) : parsedFrameworkData.SIEM.large_image,
+ label: securityFramework[1].text.toUpperCase(),
+ id: securityFramework[1].text.toUpperCase(),
+ animate: false,
+ },
+ renderedPosition: {
+ x: baselocationX-shiftradius-(shiftradius/shiftmodifier),
+ y: baselocationY+shiftradius-(shiftradius/shiftmodifier),
+ }
+ },
+ ]
+
+ // Middlenode
+ nodes.push({
+ group: "nodes",
+ data: {
+ font_size: fontSize,
+ width: defaultSize,
+ height: defaultSize,
+ id: "SHUFFLE",
+ is_valid: true,
+ isValid: true,
+ errors: [],
+ middle_node: true,
+ },
+ renderedPosition: {
+ x: baselocationX,
+ y: baselocationY+shiftradius*2,
+ }
+ })
+
+ // Extra nodes
+ nodes.push({
+ group: "nodes",
+ data: {
+ is_valid: true,
+ isValid: true,
+ errors: [],
+ large_image: encodeURI(`data:image/svg+xml;utf-8,
+ ,
+ `),
+ id: "TOP_LEFT",
+ invisible: true,
+ },
+ renderedPosition: {
+ x: baselocationX-shiftradius*2.5,
+ y: baselocationY-50,
+ }
+ })
+ nodes.push({
+ group: "nodes",
+ data: {
+ is_valid: true,
+ isValid: true,
+ errors: [],
+ large_image: encodeURI(`data:image/svg+xml;utf-8,
+ ,
+ `),
+ id: "BOTTOM_LEFT",
+ invisible: true,
+ },
+ renderedPosition: {
+ x: baselocationX-shiftradius*2.5-10,
+ y: baselocationY+shiftradius*4-10,
+ }
+ })
+ nodes.push({
+ group: "nodes",
+ data: {
+ is_valid: true,
+ isValid: true,
+ errors: [],
+ large_image: encodeURI(`data:image/svg+xml;utf-8,
+ ,
+ `),
+ id: "BOTTOM_RIGHT",
+ invisible: true,
+ },
+ renderedPosition: {
+ x: baselocationX+shiftradius*2+50,
+ y: baselocationY+shiftradius*4+50,
+ }
+ })
+ nodes.push({
+ group: "nodes",
+ data: {
+ is_valid: true,
+ isValid: true,
+ errors: [],
+ large_image: encodeURI(`data:image/svg+xml;utf-8,
+ ,
+ `),
+ id: "TOP_RIGHT",
+ invisible: true,
+ },
+ renderedPosition: {
+ x: baselocationX+shiftradius*2,
+ y: baselocationY-150,
+ }
+ })
+
+ //console.log("NODES: " , nodes)
+ for (var key in nodes) {
+ cy.add(nodes[key]).lock()
+ }
+
+ changeUsecase(selectedUsecase, usecaseType)
+
+ if (inputUsecase !== undefined && inputUsecase !== null) {
+ console.log("Got usecase: ", inputUsecase)
+
+ for (var key in inputUsecase.process) {
+ if (inputUsecase.process[key].source === "" || inputUsecase.process[key].target === "") {
+ continue
+ }
+
+ console.log("Edge: ", inputUsecase.process[key])
+ inputUsecase.process[key].label = parseInt(key)+1
+ inputUsecase.process[key].id = uuidv4();
+
+ cy.add({
+ group: "edges",
+ data: inputUsecase.process[key],
+ })
+ }
+ //changeUsecase(inputUsecase, 0)
+ //setSelectedUsecase(value)
+ }
+ }
+
+ if (selectedOption !== undefined && selectedUsecase !== selectedOption) {
+ setSelectedUsecase(selectedOption)
+ changeUsecase(selectedOption, usecaseType)
+ }
+
+ const UsecaseHandler = (props) => {
+ const { data, index, diff } = props
+
+ const leftImage = data.left_image !== undefined ? parsedDatatypeImages[data.left_image] : undefined
+ const rightImage = data.right_image !== undefined ? parsedDatatypeImages[data.right_image] : undefined
+
+ if (leftImage === undefined) {
+ console.log("LEFT MISSING: ", leftImage)
+ }
+
+ if (rightImage === undefined) {
+ console.log("Right MISSING: ", rightImage)
+ }
+
+ const parsedRightImage =
+ const parsedRightText =
{data.right_text}
+
+ const parsedLeftImage =
+ const parsedLeftText =
{data.left_text}
+
+ const svgSize = 20
+ var svgIcon =
+
+
+
+
+
+
+
+
+ if (data.direction === "right") {
+ svgIcon =
+
+
+
+
+ } else if (data.direction === "left") {
+ svgIcon =
+
+
+
+
+
+ }
+
+ //
+ //
+ const handleHover = () => {
+ //console.log("Hover IN: ", data.automated)
+
+ const allEdges = cy.edges().jsons()
+ for (var key in allEdges) {
+ const newedge = allEdges[key]
+ const foundelement = cy.getElementById(newedge.data.id)
+ if (foundelement !== undefined && foundelement !== null) {
+ foundelement.remove()
+ }
+ }
+
+ //const newedges = usecases[value][parsedType]
+ for (var key in data.automated) {
+ data.automated[key].label = parseInt(key)+1
+
+ if (data.automated[key].description !== undefined && data.automated[key].description !== null && data.automated[key].description.length > 0) {
+ data.automated[key].label = (parseInt(key)+1)+" "+data.automated[key].description
+ }
+
+ cy.add({
+ group: "edges",
+ data: data.automated[key],
+ })
+ }
+ }
+
+ const handleHoverOut = () => {
+ //console.log("Hover out: ", data.automated)
+ //const allEdges = cy.edges().jsons()
+ //for (var key in allEdges) {
+ // const newedge = allEdges[key]
+ // const foundelement = cy.getElementById(newedge.data.id)
+ // if (foundelement !== undefined && foundelement !== null) {
+ // foundelement.remove()
+ // }
+ //}
+ }
+
+ return (
+
+
+ {data.name}
+
+
+
+ {parsedLeftImage}
+ {parsedLeftText}
+
+
+ {svgIcon}
+
+
+ {parsedRightImage}
+ {parsedRightText}
+
+
+
+
+ )
+ }
+
+ const selectedUsecases = [
+ {
+ "name": "Sync tickets",
+ "description": "Do cool shit weeeee - this just ensure they are syncing",
+ "left_text": "Cases",
+ "right_text": "Cases",
+ "left_image": "Cases",
+ "right_image": "Cases",
+ "direction": "both",
+ "manual": [],
+ "automated": [
+ {
+ "source": "CASES",
+ "target": "SHUFFLE",
+ "description": "Get cases",
+ "human": false,
+ },
+ {
+ "source": "SHUFFLE",
+ "target": "CASES",
+ "description": "Update cases",
+ "human": false,
+ },
+ ]
+ },
+ {
+ "name": "Forward alerts with enrichment",
+ "description": "Do cool shit weeeee - this just ensure they are syncing",
+ "left_text": "EDR & AV",
+ "right_text": "Cases",
+ "left_image": "EDR & AV",
+ "right_image": "Cases",
+ "direction": "right",
+ "manual": [],
+ "automated": [
+ {
+ "source": "BOTTOM_LEFT",
+ "target": "COMMS",
+ "description": "Email received",
+ "human": false,
+ },
+ ]
+ }
+ ]
+
+ //autounselectify={true}
+ var usecasediff = -100
+ return (
+
+ {showOptions === false ? null :
+
+ {Object.keys(usecases).map((data, index) => {
+ return(
+ {
+ changeUsecase(data, usecaseType)
+ }}>
+ {data}
+
+ )
+ })}
+
+ }
+ {/*
+ Object.getOwnPropertyNames(discoveryData).length > 0 ?
+
+
+ Use-cases
+
+ {selectedUsecases.map((data, index) => {
+ usecasediff += 175
+
+ return (
+
+ )
+ })}
+
+ : null*/}
+
+ {
+ Object.getOwnPropertyNames(discoveryData).length > 0 ?
+
+
+ {
+ //cy.elements().unselectify();
+ cy.elements().unselect()
+
+ e.preventDefault();
+ setDiscoveryData({})
+ setDefaultSearch("")
+ }}
+ >
+
+
+
+ {/* {/*Causes errors in Cytoscape. Removing for now.}
+
+ {
+ e.preventDefault();
+ setDiscoveryData({
+ "id": discoveryData.id,
+ "label": discoveryData.label,
+ "name": ""
+ })
+ setNewSelectedApp({
+ "image_url": "",
+ "name": "",
+ "id": "",
+ "objectID": "remove",
+ })
+ setSelectionOpen(true)
+ setDefaultSearch("")
+
+ const foundelement = cy.getElementById(discoveryData.id)
+ if (foundelement !== undefined && foundelement !== null) {
+ console.log("element: ", foundelement)
+ foundelement.data("large_image", discoveryData.large_image)
+ foundelement.data("text_margin_y", "14px")
+ foundelement.data("margin_x", "32px")
+ foundelement.data("margin_y", "19x")
+ foundelement.data("width", "45px")
+ foundelement.data("height", "45px")
+ }
+
+ setTimeout(() => {
+ setDiscoveryData({})
+ setNewSelectedApp({})
+ }, 1000)
+ }}
+ >
+
+
+
+ */}
+
+ {discoveryData.name !== undefined && discoveryData.name !== null && discoveryData.name.length > 0 ?
+
+
+
0 ? newSelectedApp.image_url : discoveryData.large_image} style={{height: 40, width: 40, margin: "auto",}}/>
+
+ :
+
+ }
+
+ {discoveryData.name !== undefined && discoveryData.name !== null && discoveryData.name.length > 0 ?
+ discoveryData.name
+ :
+ newSelectedApp.name !== undefined && newSelectedApp.name !== null && newSelectedApp.name.length > 0 ?
+ newSelectedApp.name
+ :
+ `No ${discoveryData.label} app chosen`
+ }
+
+
+
+ {discoveryData !== undefined && discoveryData.name !== undefined && discoveryData.name !== null && discoveryData.name.length > 0 ?
+
+
+ {discoveryData.description}
+
+ {/*isCloud && defaultSearch !== undefined && defaultSearch.length > 0 ?
+ { }
+ :
+ null
+ */}
+
+ :
+ selectionOpen
+ ?
+
+
+ Click an app below to select it
+
+
+ :
+ {
+ setSelectionOpen(true)
+ setDefaultSearch(discoveryData.label)
+ }}
+ >
+ Choose {discoveryData.label} app
+
+ }
+
+
+ {selectionOpen ?
+ isCloud && defaultSearch !== undefined && defaultSearch.length > 0 ?
+
+ :
+
+ : null}
+
+
+ : null
+ }
+
{
+ // FIXME: There's something specific loading when
+ // you do the first hover of a node. Why is this different?
+ //console.log("CY: ", incy)
+ setCy(incy)
+ }}
+ />
+ {isCloud && inputUsecase !== undefined && inputUsecase !== null && isLoggedIn === true && Object.keys(inputUsecase).length > 0 ?
+ {
+ setUsecaseItem(inputUsecase)
+ }}>
+ Save
+
+ : null}
+
+ )
+}
+
+export default Framework;
diff --git a/frontend/src/components/Dropzone.js b/frontend/src/components/Dropzone.js
index 621e74b5..6ea9f0de 100644
--- a/frontend/src/components/Dropzone.js
+++ b/frontend/src/components/Dropzone.js
@@ -1,19 +1,19 @@
-import React, { useRef, useState } from 'react';
-import { useEffect } from 'react';
-import BackupIcon from '@material-ui/icons/Backup';
+import React, { useRef, useState } from "react";
+import { useEffect } from "react";
+import BackupIcon from "@material-ui/icons/Backup";
const dragOverStyle = {
- backgroundColor: 'rgba(0,0,0,0.8)',
- border: '5px dashed white',
- borderRadius: '8px',
- width: '100%',
- height: '100%',
- position: 'absolute',
- overflow: 'hidden',
+ backgroundColor: "rgba(0,0,0,0.8)",
+ border: "5px dashed white",
+ borderRadius: "8px",
+ width: "100%",
+ height: "100%",
+ position: "absolute",
+ overflow: "hidden",
zIndex: 100,
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center'
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
};
const Dropzone = ({ children, style, onDrop }) => {
@@ -56,21 +56,21 @@ const Dropzone = ({ children, style, onDrop }) => {
useEffect(() => {
if (!dropzoneRef.current) return;
- dropzoneRef.current.addEventListener('dragover', handleDragOver);
- dropzoneRef.current.addEventListener('dragenter', handleDragEnter);
- dropzoneRef.current.addEventListener('dragleave', handleDragLeave);
- dropzoneRef.current.addEventListener('drop', handleDrop);
+ dropzoneRef.current.addEventListener("dragover", handleDragOver);
+ dropzoneRef.current.addEventListener("dragenter", handleDragEnter);
+ dropzoneRef.current.addEventListener("dragleave", handleDragLeave);
+ dropzoneRef.current.addEventListener("drop", handleDrop);
return () => {
- dropzoneRef.current.removeEventListener('dragover', handleDragOver);
- dropzoneRef.current.removeEventListener('dragenter', handleDragEnter);
- dropzoneRef.current.removeEventListener('dragleave', handleDragLeave);
- dropzoneRef.current.removeEventListener('drop', handleDrop);
+ dropzoneRef.current.removeEventListener("dragover", handleDragOver);
+ dropzoneRef.current.removeEventListener("dragenter", handleDragEnter);
+ dropzoneRef.current.removeEventListener("dragleave", handleDragLeave);
+ dropzoneRef.current.removeEventListener("drop", handleDrop);
};
}, [dropzoneRef]);
return (
-
+
{dragging && (
diff --git a/frontend/src/components/FAQ.jsx b/frontend/src/components/FAQ.jsx
new file mode 100644
index 00000000..ad3aab2c
--- /dev/null
+++ b/frontend/src/components/FAQ.jsx
@@ -0,0 +1,23 @@
+import React, {useState} from 'react';
+
+
+const FAQItem = (props) => {
+ const { question, answer } = props
+
+ const [isExpanded, setIsExpanded] = useState(false)
+
+ return (
+
{
+ setIsExpanded(!isExpanded)
+ }}>
+
+ {question}
+
+
+ {answer}
+
+
+ )
+}
+
+export default FAQItem;
diff --git a/frontend/src/components/FooterNew.js b/frontend/src/components/FooterNew.js
index aa68d486..482c45b7 100644
--- a/frontend/src/components/FooterNew.js
+++ b/frontend/src/components/FooterNew.js
@@ -1,54 +1,54 @@
-import React from 'react';
+import React from "react";
//import List from '@material-ui/core/List';
//import ListItem from '@material-ui/core/ListItem';
//borderTop: "1px solid #385F71"
const FooterStyle = {
- right: "0",
- left: "0",
- bottom: "0",
- height: "130px",
- backgroundColor: 'rgba(15, 14, 31, 1)',
+ right: "0",
+ left: "0",
+ bottom: "0",
+ height: "130px",
+ backgroundColor: "rgba(15, 14, 31, 1)",
};
const FooterInfo = {
- maxWidth: '1150px',
- minWidth: '768px',
- textAlign: 'center',
- margin: 'auto',
+ maxWidth: "1150px",
+ minWidth: "768px",
+ textAlign: "center",
+ margin: "auto",
};
const hrefStyle = {
- color: "#bdbdbd",
- textDecoration: "none"
-}
-
-const Footer = props => {
- return (
-
- );
+ color: "#bdbdbd",
+ textDecoration: "none",
};
-const Box = props => {
- return(
-
- );
+const Footer = (props) => {
+ return (
+
+ );
+};
+
+const Box = (props) => {
+ return (
+
+ );
};
export default Footer;
diff --git a/frontend/src/components/FrameworkData.jsx b/frontend/src/components/FrameworkData.jsx
new file mode 100644
index 00000000..456ea24f
--- /dev/null
+++ b/frontend/src/components/FrameworkData.jsx
@@ -0,0 +1,28 @@
+const data = {
+ "Cases": {
+ "large_image": "data:image/png;base64,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",
+ },
+ "IAM": {
+ "large_image": undefined,
+ },
+ "SIEM": {
+ "large_image": "data:image/png;base64,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",
+ },
+ "Assets": {
+ "large_image": "data:image/png;base64,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",
+ },
+ "Intel": {
+ "large_image": "data:image/jpg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4gKgSUNDX1BST0ZJTEUAAQEAAAKQbGNtcwQwAABtbnRyUkdCIFhZWiAH4AADAB4ACAAYABphY3NwQVBQTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWxjbXMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtkZXNjAAABCAAAADhjcHJ0AAABQAAAAE53dHB0AAABkAAAABRjaGFkAAABpAAAACxyWFlaAAAB0AAAABRiWFlaAAAB5AAAABRnWFlaAAAB+AAAABRyVFJDAAACDAAAACBnVFJDAAACLAAAACBiVFJDAAACTAAAACBjaHJtAAACbAAAACRtbHVjAAAAAAAAAAEAAAAMZW5VUwAAABwAAAAcAHMAUgBHAEIAIABiAHUAaQBsAHQALQBpAG4AAG1sdWMAAAAAAAAAAQAAAAxlblVTAAAAMgAAABwATgBvACAAYwBvAHAAeQByAGkAZwBoAHQALAAgAHUAcwBlACAAZgByAGUAZQBsAHkAAAAAWFlaIAAAAAAAAPbWAAEAAAAA0y1zZjMyAAAAAAABDEoAAAXj///zKgAAB5sAAP2H///7ov///aMAAAPYAADAlFhZWiAAAAAAAABvlAAAOO4AAAOQWFlaIAAAAAAAACSdAAAPgwAAtr5YWVogAAAAAAAAYqUAALeQAAAY3nBhcmEAAAAAAAMAAAACZmYAAPKnAAANWQAAE9AAAApbcGFyYQAAAAAAAwAAAAJmZgAA8qcAAA1ZAAAT0AAACltwYXJhAAAAAAADAAAAAmZmAADypwAADVkAABPQAAAKW2Nocm0AAAAAAAMAAAAAo9cAAFR7AABMzQAAmZoAACZmAAAPXP/bAEMABQMEBAQDBQQEBAUFBQYHDAgHBwcHDwsLCQwRDxISEQ8RERMWHBcTFBoVEREYIRgaHR0fHx8TFyIkIh4kHB4fHv/bAEMBBQUFBwYHDggIDh4UERQeHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHv/AABEIAGQAZAMBIgACEQEDEQH/xAAcAAEAAQUBAQAAAAAAAAAAAAAABgEEBQcIAgP/xAA7EAABAwMDAgUBAwkJAQAAAAABAgMEAAURBgchEjETFCJBUTIIFdEXUlRhcYGRlKEWIzZiY3SEk6Ky/8QAGwEBAQADAAMAAAAAAAAAAAAAAAECBQYDBAf/xAAqEQACAQMDAwMDBQAAAAAAAAAAAQIDBBEFElEhMWETIjIGFLFCYoGRof/aAAwDAQACEQMRAD8A7LpSlAKUpQClKGgFKpmmaArSqZqtAKUpQClKUApSlAKUpQA9q03uJufebfqSVarM3HYbiL8NbjrfWpxXvgdgK3Grsa5g3DCDuDegtRSgzlBSgM4HGTitzolvTrVpeos4Rz31HdVrehH0pYbeP8Mx+VbWX6VD/lU/jT8q2sv0qH/Kp/GsfuFa9LW1+CnTV1VOQ40S/wD3gc6Txg5GME8+n2xXrcC1aVtrNuVpq7KnrdbJkArC+ngYPA9JPPprfQo2U9uKXyz+njng5ipcahT35rfHGfdzxz5L4br6yyD5qGf+KPxrce2+o3NUaYbuT7CWX0uKZdSj6SpOOR+ogitHa4telLfbLU7p67KmyHkZkpLgVjgckY9JzkYqRaX1JcNL7SIuFtDCnVXhbag6kqBSUZ9iPivRvrOjWoRlQhtbeO2OTY6bqFxb3Mo3NTdFRy+ueDelKtLLM+8LPDnlHR5lhDvTnPT1JBx/WruuXaaeGdtGSkk0KUpUKKUpkfIoBSlMj5FAUV2rmfdOFLia7uy5MdxtD8gutKKfStJAwQfeumMg9jUZ3B007qqFEtvmxFipkB2QpKcrUADgJ9u57n4962Wl3ita26XZ9GafW9Ple2+2PddV5OZCQO5A/aadSfzh/GuiLrpGTZtOpj6FRFizULCnDIbS4qQnHYqWDg+47D9lRTy+8P6LD/6ov4V0VLV41U3Hal5lh/hnJVtDnQajPc3+2OV/eV+DUXUD7j+NTu4xZEXZGIJLK2S9eFOthYwVI6CAcfHBqRCPvDnIiw8j/Si/hUU1IjXOodQxLBffEM4lQjtOhLTZ4yVAj0ngd+fis3cevOPuilF5eJZfT+EYK1+2hL2zzJbVmOF1x5ZvvRv+EbP/ALFn/wCBWVqzscQ2+ywoKlhZjx0NFQ7EpSBn+lXlcXUac20fRKMXGnFPhClKVgeQVzTvprvV9k3MuNttOoJkOG20yUNNhGElTYJ7pPua6Wrkv7QSEu72TWljKF+USoZxkFCAaIMnN23rK9pGH4j6W9TvnyjgSBlpSQOp8D4IwR/mOPY1Y7A621XftYTYl4vsuawi2Ouobc6cBYUgA8Ac8mo+NobgzuDdLVJS79zQI7k3zWMeKzhRbQD+cSMH46SfivP2YDnXE4nubO8f/SKpDObDa71ZetwRDvV+mTYYhPuqaWE4ynpweAOe9RW56/1rrXVKmY+pRZGHVqMdozfKMNpHYKX7qI9z3PxX2+za+zF3M8zIcS0yzbpLji1dkpASST+4V8L4razUV4nyYsu7aZ63lKaV5YSYzoJ+sJThbee/TyBn91AbN2WY3UZ1QWL9c1ybE2yXFvPPJlIeJ4Slp1JJznk5PAHI5FayTqLcW9a6k2Gy6nuxkOzX247XnShICVLOMngABNfLZe4XO1bqWyBYpzr8SRM8F4NpUluQzz1LKD2wkdXPIxWNtMW+zN1Ho2mZHlrsu4SfLO9YR0nLhPJBx6c+1ATPS+4mvdH67ZsGrZj8xoSG2JbEkpcWgLxhaFjk8KB7kEV0vIiRpKmlPsNuKZWFtFSclCh7j4NaL0TsvqB/VrWo9cXRmQpt9L6mkOl1x9acFPWogAJyBwM9scVvumcdhhPuBSlKhRSlKAVqXejSmnozr+t5mm5F2caSlyWtF0VH8MN9IQQnBCv6dq21WL1XZIuo9PTbHOW83HmN+G4pogLAyDwSCPagIfc9fsQpqrTq20rtbL9rXLdWy6qT0ILnh89COB0nqKuwrE6bt21mib9dF21c2PKgxS1OcWX3GmmlJSsZUQU5UOnp5yTwKmeoNFWu9SJb8p+WhUu0KtLnhrAAZKuokZH1Z9+36q8SdCWWTBv0OQqU4zfPCMkeIAUFtCUJKCBx9CTznn+FCEN0datstNOu6msUa6+YjqVDfbKH3HGSpIUQtsjKcpAIJGPirWdZ9qtT6wiW9rTTrirlCE5qfBbdabWFKwMhGAPfKiMA8HmpWvbCzOQmI65szqZliUFpZjoCleGUYUhLYQoYJ5KSQeQRV9YdBWyySbPIt0+5NKtcMwkguIIfZK+vpcBTz6ucjBqlIm1/ZTbrWDNjsGmYLMyRGQ4qZNuIYU6lSynobU4D1qGMlII9q+emWNuYO5EsCxqt16j3ZcWNJ8R9xDzq2uskn6EqUFrASfjiprrPRkbVJUzcLtc0QXEoS9CaU34TnSrqB9SCpJPuUkdqOaHtTk1csvzAtV5ReCAsY8ZLfhhPb6ce3fPvQh5O4GmUJuPmJMmMq3xzKfQ/EcbV4IV0lYBGVDPHFWzm4llalyFvqWxbo9u88t91l1DnT4vh58MoyUHghWefjHNY+HtJp6NFmR0zbmtEq3uW9RKmwoNLcDhOQgFSsj6lZOO9ZrUmhrTfpEp6Y/MQqTbU25fhLSAG0uBwEZB9WR3+PaoAnX+mVRFyBKkEpmpg+B5R3xy8pPUEhvp6jlPqzjtzWY07erdqC1N3S1Pl+I4paErKFJyUqKTwQD3BqLap0MH3pN0s63fvR+5NTwtcvwQ0tDPhehQbX3TwQQc5Pbisxtxp5/S+kIlnlSESJDanHHXEZ6SpbilkDPJA6sZPJxmhSRUpSgFKUoBSlKAUpSgFKUoBSlKAUpSgFKUoBSlKAUpSgFKUoBSlKAUpSgFKUoBSlKA//9k=",
+ },
+ "Comms": {
+ "large_image": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCACuAK4DASIAAhEBAxEB/8QAHAABAAIDAQEBAAAAAAAAAAAAAAYHAQUIAgQD/8QASRAAAQMCAwAKDAwGAgMAAAAAAQACAwQFBgcREhMhMTJBUWFxwQgUFyI2dIGhsbLC0RY1N0JUVVaRk5Si0hUjUmJz8HKSJFNj/8QAGgEAAwEBAQEAAAAAAAAAAAAAAAQFBgMBAv/EADYRAAEEAAIECwgDAQEAAAAAAAEAAgMEBRE0cYGxEhMVITEyUVJhkdEUIjVBU3Kh4ULB8DMG/9oADAMBAAIRAxEAPwDlRERCEREQhEREIRF7jikldsYo3vdyNGpWypsPXWo02uhmA5XjY+ldGRPk6jSVzfKyPruAWqRSulwRcpNDO+GEf8tkfMttT4Dp26ds1kr+ZjQ306pyPC7Un8MtfMk5MUqs/nnq51XyK1KbCVog01p3SnlkeStpT26iph/IpII+drBr96djwGU9dwH5ST8eiHUaT+FTLmuadHNIPOF5Ukx+AMQOA/8AU3rUbUixFxMro888jkq9eXjomyZZZjNERFxXZEREIRERCEREQhZAJOg3SvrprXX1Ona9HUSc7YzopfldGx8lxL2NcWiPQka6cJWBvbyt0sIFmISudln8slBvYya0phazPL55+GaqamwfeJtNlTtiH/0eB6FtaXAE50NVWxNHJG0nznRWGUVWPBazenM7fRSpMctP6MhqHrmopTYHtcWm3OnmPO7QeZbSnw9aabTaqGHUcbhsj59VtisFOx04I+qweSSku2JOs8+a/OOKOJuxiY1jeRo0C9LKwmAMlwzzWCvJXoryV6vUQohQhVhmB4Qu/wATetRtSTMDwhd/ib1qNrCX9Jk1lbuho0eoIiIlE2iIiEIiIhCIiIQp7lXwrn0R+0p8VAcq+Fc+iP2lPitphGiM27ysPjGmP2bgsFEKKkpqwVgrJWCherCwsrCF6sFeSvRXkoX0iFEKEKsMwPCF3+JvWo2rvdgwXw9uGz1VZr3m2xskI3OLvdxY7mjfs3X/AIUyzFrCpJZnPD2857f0tNVxaOKFrCx3MOz9qkUV3dzRv2br/wAKZO5o37N1/wCFMuHI0vfb5/pd+Wou47yHqqRRXd3NG/Zuv/CmTuaN+zdf+FMjkaXvt8/0jlqLuO8h6qkUV3dzRv2br/wpk7mjfs3X/hTI5Gl77fP9I5ai7jvIeqpFFcVzy+pKaEdtWmqotnuNe8SN1PNstwqqbtROt1yqKR52RidpryjfB+5K2qElUBziCD2JupiEVpxa0EEdqmOVfCufRH7SnxUByr4Vz6I/aU+K02EaIzbvKyuMaY/ZuCwUQoqSmrBWCt/hHCtyxVcRS22LvW7sszuBGOc9Suuy5LWGlib/ABSeprpvnaO2tnkA3fOkrOIQVjwXnn7An6uHT2hwmDm7SudFhdO1eUWEp4i2Kknp3cT453E/qJVT5iZX1uF4XV1DK6utgPfO2Oj4v+QG+Odcq+K153cAHI+K62MJsV28MjMeCrkryV6K8lUlPRCiFCF0zkB8nsfjMvUrIVb5AfJ7H4zL1KyFhL+kyayt3Q0aPUERESibRERCEXiomip4XzVEjIomDZOe9wa1o5STvL4r9eaGw22Wuuc7YYGDjO648gHGVz9f8SX/ADQvbbVaIZIrfstRCDuAa8OR3V6U7UpOsZuJyaOkpG3eZWyaBm49AX7Z2Y1t+I5qW3WrWWGjkc51Tr3r3EaaN5RzrlPG/hRXdLPUauqMz8D0WDsL2kQuM9fNO4T1B3Nlo3eA4guV8b+FFd0s9RqpXuK9hYIerwvVTaPGm+8zdbg+ikGVfCufRH7SnxUByr4Vz6I/aU+Kq4RojNu8qPjGmP2bgsFfRbaKe419PR0jNnPO8RsbyklfOVZ+QFpbW4umrpBq2hhLm7nz3d6PNqm7M3EROk7AlKsPHzNi7Srxwfh2kwxY4LfRtGrRrLJpuyP43H/d5fFizHViwu8RXOqJqSNRBC3Zv05SN4eUhbDFt5bh/DdfdHtDu149k1pOmycTo0feQuQrlW1Fxrp6yskdLUTPL3vdvklZjD6JvOdLKebeVqsRvig1sUI59wXUuFcxMPYlqxS0NRJFVngw1DNg53Rvg9GuqlssbJonxysa+N4LXNcNQQeIhcUQTSU88c0D3RyxuD2OadC0jdBC64y/vpxHhK33F+5M9mwlH97dwny6a+VGJ4cKmUkZ5j+F7heJG3nHIPeH5C5zzWwsMLYpkhp2kUFQNup9eIHfb5D5tFCyujeyGtbarCNPcAP5lFOBrp81+4fOGrnIq/htg2K7XO6RzFQMSrivYc1vQecIhRCn0gumcgPk9j8Zl6lZC5iy1zDrcHwNpqqmdU2iZ5cGjvXMdxlp03ecehdGWG9UF+t0dda6hk8D+Mb7TyEcRWMxSrJFM6Qj3SelbPC7UcsLY2n3gOhbFERTFTRRvG+MLbhK3GeteH1LwdppmnvpD1DnUXzDzUosPvkoLQ1ldcgCHODv5cJ5+U8yh2EsurtjKs/juMKmaOGVwftbxpLMPYb/ALzqnXota3jrR4LPyVMsXnOdxFUcJ/4Gtaiio8SZsYg2+qe6K3RO0c8AiKBvI0cbv9KvvCuGrbhi2to7XAGN33yO3XyHlcVsLbQUtsooqSggZBTRDRkbBoAvpXK3eM/uMHBYOgeq6U6LYPfeeE89J9FT3ZI/Etm8Yf6q4pxv4UV3Sz1GrtbskfiWzeMP9VcU438KK7pZ6jU1L8NZ93qlYvicn2+ikGVfCufRH7SnxUByr4Vz6I/aU+KuYRojNu8qBjGmP2bgsFXl2NsY7Vvsnzi+FvmeqNKu3sbqkD+OUxPfHapAOjZA+kIxYE1H5eG8L3ByBbZn47ipTnzK6PL6Zrd6SoiaejUnqXM5XUedVE+ty8uBjGydA5k2gHEHDXzEnyLlwrhgZHs5Hif6TGOgiyCewf2sLo7sepHPwPM07zKx4H/Vp61ziul8haN9LgGOR407ZnklHONxvsr6xsgVuftC8wME2ebsK2OczA/LS9A8TYnDySsXKZXUWelS2ny4r4ydHVEkUbf+4d6GlculfGBAiudZ3BdMdINgav7KIUQq0oq6Aypw3bsTZVCjukIeDUy7CQDv4zubrTxKF1dDiTKa/tqad7prfI7QPGu1Tt/pcOJ3+hWbkB8nsfjMvUp/cqGmuVFLSV0LJ6aVuxex41BCycl50FmRjxwmEnMei1jKLZ68cjDwXgDI+q0ODsa2nFFrdVU0zYZYW7KoglcA6LlJ5W86rPMjM+a5TOsmEDK4SO2t9VFrspDvbGPj05/uXzYjyXukNyecO1MMlDJvNmkLHsB4juboVg5cZd0WEoRUTltVdnjR82nex8zPfvlegUa549p4XY3s1/7zXznesDiHDg9ru3V/vJR7LHKxludHdcSxsmrT30dM7vmxHldyu9CtxEUuxZksv4chVStWjrM4EYRERcEwqe7JH4ls3jD/AFVxTjfworulnqNXa3ZI/Etm8Yf6q4pxv4UV3Sz1GqxL8NZ93qo0XxOT7fRSDKvhXPoj9pT4qA5V8K59EftKfFXMI0Rm3eVAxjTH7NwWCprlBfm2HGlK6d+xpaoGnlJO4NeCfIdPOoUUB0Oo309NEJmGN3QUjDKYZGyN6Qu1qqniqqaWnqI2yQytLHscNQ4HfC5lx1lreLDcZXW+kmrba5xMUsTdm5o/pcBugjl41ZuUmYkF5pIbReJhHdIm7GOR50E4G9u/1elWishFNPhkpYR+/ELZSwQYpEHg/rwK5Xwdl1fMQXGNk1HPRUIcNtnnYWaDjAB3SV0/bKGntlvp6KjjEdPAwRsaOIBfSolmDjahwhbXOlc2W4SNO0U4O6Tyu5GryxbmxF4YBqAX1WqQ4cxzydZKrPsir+2etobHA/XtfWeoAPziO9HkGp8oVLlfZda+oulwqK6tkMlTO8ve48ZK+MrV1K4rwtjHy3rJ27BszOkPz3IhRCmUuumcgPk9j8Zl6lZCrfID5PY/GZepWQsJf0mTWVu6GjR6giIiUTaIiIQiIiEKnuyR+JbN4w/1VxTjfworulnqNXa3ZI/Etm8Yf6q4pxv4UV3Sz1GqxL8NZ93qo0XxOT7fRSDKvhXPoj9pT4qA5V8K59EftKfFXMI0Rm3eVAxjTH7NwWCiFFSU1Y1IcCCQRvEKXWjMjFNphbFBdHywt3Aydok08pGvnURKwVzkiZKMngHWuscr4jnG4jUpzXZrYtq4TGLgyAHcJhha0/fpueRQmrqp6yofPVzSTTPOrnyOLnHylfmsLyOCOL/m0DUvqSeSX/o4nWVgryV6K8ldV8IhRChC6ZyA+T2PxmXqVkKj8oceYdw9g5lDdq50FUJ5H7AQvfuHTTdAIU27rGDvrR/5aX9qxl2pO6w9zWEjM/IrZ0rcDa7GueAch8wp0igvdYwd9aP/AC0v7U7rGDvrR/5aX9qV9isfTPkUz7bX+oPMKdIoL3WMHfWj/wAtL+1O6xg760f+Wl/aj2Kx9M+RR7bX+oPMKdIoL3WMHfWj/wAtL+1O6xg760f+Wl/aj2Kx9M+RR7bX+oPMKMdkj8S2bxh/qrinG/hRXdLPUautc6sZWPE1stsNlq3VEkMznvBiezQFunzgFyTjVwdieuLTqNWj7mgKjYY6PDmNeMjwvntU6s9smIvcw5jg/LYpDlXwrn0R+0p8VAcq+Fc+iP2lPirWEaIzbvKhYxpj9m4LBRCipKasFYKyVgoXqwsLKwherBXkr0V5KF9IhRChCiWJMVVNouZpYYIXsDA7V+uu6tV8Pa36JTfq96+LMDwhd/ib1qNrIXMQsxzva1+QBK11PD60kDHOZmSApj8Pa36JTfq96fD2t+iU36veociX5Ttd8pnkyr3B+VMfh7W/RKb9XvT4e1v0Sm/V71DkRyna75RyZV7g/KmPw9rfolN+r3p8Pa36JTfq96hyI5Ttd8o5Mq9wflS6fHVwkjLY4aeJx+cASR95UUlkfNK+SVxc952TnHfJXhEvNZlny4x2eSYhqxQZ8U3LNSHCWIG2J9SZKd0zZtjwXaEaa+9TGmxzapdNsE8J/uZqPMSqtRM1sTnrtDGEZDwStnCq9l5keDmewq56a/2qp02qvp9TxOdsT51sI5GSDWN7XDladVRC/WGomgcDDK+Mjja4hUY8fcOuzyKmyf8Anm/wf5hXoVgqoabFF4p9NjWveBxSAO9K2tNjuvZoJ6eCUc2rSnY8bru62YSUmBWW9XI/7xVkLChtNj2lfp2xSSxn+xwd7ltKbFlnn0/8razySMI8+8nI8QrSdV43b0m/D7MfWYd+5b0ryV+FPX0lTp2vUwya8TXglfuU21wcMwc0sWlpyIyRCiFerxVhmB4Qu/xN61G1JMwPCF3+JvWo2sJf0mTWVu6GjR6giIiUTaIiIQiIiEIiIhCIiIQiIiEIiIhCIiIQstcWnVpIPMvuprvcKbTaa2doHFsyR9xXwIvpr3M52nJfLmNeMnDNSOmxjdodNlJFMOSRnu0W0p8eSDQVNE088b9PSoQicjxK1H0PO3n3pN+G1X9LBs5ty22JrnHdrmamFj2NLGt0fprqFqURKSSOleXu6Sm442xMDG9ARERfC+0REQhEREIRERCF/9k=",
+ },
+ "Network": {
+ "large_image": "data:image/png;base64,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",
+ },
+ "EDR & AV": {
+ "large_image": undefined,
+ },
+}
+
+export default data;
diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js
index 449c057b..7a7ae609 100644
--- a/frontend/src/components/Header.js
+++ b/frontend/src/components/Header.js
@@ -1,250 +1,317 @@
-import React, {useState} from 'react';
-import {BrowserView, MobileView} from "react-device-detect";
+import React, { useState } from "react";
+import { BrowserView, MobileView } from "react-device-detect";
-import {Link} from 'react-router-dom';
+import { Link } from "react-router-dom";
-import { useTheme } from '@material-ui/core/styles';
+import { useTheme } from "@material-ui/core/styles";
-import { Chip, Badge, Typography, Paper, Tooltip, List, Avatar, Menu, ListItem, MenuItem, Select, Button, IconButton, Grid } from '@material-ui/core';
-import { MeetingRoom as MeetingRoomIcon, Settings as SettingsIcon, Notifications as NotificationsIcon, Home as HomeIcon, Polymer as PolymerIcon, Apps as AppsIcon, Description as DescriptionIcon} from '@material-ui/icons';
+import {
+ Chip,
+ Badge,
+ Typography,
+ Paper,
+ Tooltip,
+ List,
+ Avatar,
+ Menu,
+ ListItem,
+ MenuItem,
+ Select,
+ Button,
+ IconButton,
+ Grid,
+} from "@material-ui/core";
+
+import {
+ MeetingRoom as MeetingRoomIcon,
+ Settings as SettingsIcon,
+ Notifications as NotificationsIcon,
+ Home as HomeIcon,
+ Polymer as PolymerIcon,
+ Apps as AppsIcon,
+ Description as DescriptionIcon,
+ HelpOutline as HelpOutlineIcon,
+} from "@material-ui/icons";
+
+import {
+ Analytics as AnalyticsIcon,
+ Lightbulb as LightbulbIcon,
+} from "@mui/icons-material";
//import LogoutIcon from '@mui/icons-material/Logout';
import { useAlert } from "react-alert";
-const hoverColor = "#f85a3e"
-const hoverOutColor = "#e8eaf6"
+const hoverColor = "#f85a3e";
+const hoverOutColor = "#e8eaf6";
-const Header = props => {
- const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, homePage, isLoaded, userdata, cookies } = props;
- const theme = useTheme();
+const Header = (props) => {
+ const {
+ globalUrl,
+ setNotifications,
+ notifications,
+ isLoggedIn,
+ removeCookie,
+ homePage,
+ isLoaded,
+ userdata,
+ cookies,
+ } = props;
+ const theme = useTheme();
- const [HomeHoverColor, setHomeHoverColor] = useState(hoverOutColor);
- const [SoarHoverColor, setSoarHoverColor] = useState(hoverOutColor);
- const [LoginHoverColor, setLoginHoverColor] = useState(hoverOutColor);
- const [DocsHoverColor, setDocsHoverColor] = useState(hoverOutColor);
+ const [HomeHoverColor, setHomeHoverColor] = useState(hoverOutColor);
+ const [SoarHoverColor, setSoarHoverColor] = useState(hoverOutColor);
+ const [LoginHoverColor, setLoginHoverColor] = useState(hoverOutColor);
+ const [DocsHoverColor, setDocsHoverColor] = useState(hoverOutColor);
const [HelpHoverColor, setHelpHoverColor] = useState(hoverOutColor);
const [anchorEl, setAnchorEl] = React.useState(null);
const [anchorElAvatar, setAnchorElAvatar] = React.useState(null);
- const alert = useAlert()
+ const alert = useAlert();
- const hrefStyle = {
- color: hoverOutColor,
- textDecoration: "none",
- }
+ const hrefStyle = {
+ color: hoverOutColor,
+ textDecoration: "none",
+ };
const handleClose = () => {
setAnchorEl(null);
setAnchorElAvatar(null);
};
- const clearNotifications = () => {
- // Don't really care about the logout
+ const clearNotifications = () => {
+ // Don't really care about the logout
fetch(`${globalUrl}/api/v1/notifications/clear`, {
- credentials: "include",
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- },
- })
- .then(function(response) {
- if (response.status !== 200) {
- console.log("Error in response")
- }
+ credentials: "include",
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ })
+ .then(function (response) {
+ if (response.status !== 200) {
+ console.log("Error in response");
+ }
- return response.json();
- }).then(function(responseJson) {
- if (responseJson.success === true) {
- setNotifications([])
- handleClose()
- } else {
- alert.error("Failed dismissing notifications. Please try again later.")
- }
- })
- .catch(error => {
- console.log("error in notification dismissal: ", error)
- //removeCookie("session_token", {path: "/"})
- })
- }
+ return response.json();
+ })
+ .then(function (responseJson) {
+ if (responseJson.success === true) {
+ setNotifications([]);
+ handleClose();
+ } else {
+ alert.error(
+ "Failed dismissing notifications. Please try again later."
+ );
+ }
+ })
+ .catch((error) => {
+ console.log("error in notification dismissal: ", error);
+ //removeCookie("session_token", {path: "/"})
+ });
+ };
- const dismissNotification = (alert_id) => {
- // Don't really care about the logout
+ const dismissNotification = (alert_id) => {
+ // Don't really care about the logout
fetch(`${globalUrl}/api/v1/notifications/${alert_id}/markasread`, {
- credentials: "include",
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- },
- })
- .then(function(response) {
- if (response.status !== 200) {
- console.log("Error in response")
- }
+ credentials: "include",
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ })
+ .then(function (response) {
+ if (response.status !== 200) {
+ console.log("Error in response");
+ }
- return response.json();
- }).then(function(responseJson) {
- if (responseJson.success === true) {
- const newNotifications = notifications.filter(data => data.id !== alert_id)
- console.log("NEW NOTIFICATIONS: ", newNotifications)
- setNotifications(newNotifications)
- } else {
- alert.error("Failed dismissing notification. Please try again later.")
- }
- })
- .catch(error => {
- console.log("error in notification dismissal: ", error)
- //removeCookie("session_token", {path: "/"})
- })
- }
+ return response.json();
+ })
+ .then(function (responseJson) {
+ if (responseJson.success === true) {
+ const newNotifications = notifications.filter(
+ (data) => data.id !== alert_id
+ );
+ console.log("NEW NOTIFICATIONS: ", newNotifications);
+ setNotifications(newNotifications);
+ } else {
+ alert.error(
+ "Failed dismissing notification. Please try again later."
+ );
+ }
+ })
+ .catch((error) => {
+ console.log("error in notification dismissal: ", error);
+ //removeCookie("session_token", {path: "/"})
+ });
+ };
- // DEBUG HERE
- const handleClickLogout = () => {
- console.log("COOKIES: ", cookies, "Remover: ", removeCookie)
- // Don't really care about the logout
- fetch(globalUrl+"/api/v1/logout", {
- credentials: "include",
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- })
- .then(() => {
- // Log out anyway
- //cookies.remove("session_token")
- //window.location.pathname = "/"
- console.log("Should've logged out")
- removeCookie("session_token", {path: "/"})
- removeCookie("session_token", {path: "/workflows"})
- window.location.reload()
- })
- .catch(error => {
- console.log("Error in logout: ", error)
- removeCookie("session_token", {path: "/"})
- window.location.reload()
- //removeCookie("session_token", {path: "/"})
- })
- }
+ // DEBUG HERE
+ const handleClickLogout = () => {
+ console.log("COOKIES: ", cookies, "Remover: ", removeCookie);
+ // Don't really care about the logout
+ fetch(globalUrl + "/api/v1/logout", {
+ credentials: "include",
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ })
+ .then(() => {
+ // Log out anyway
+ //cookies.remove("session_token")
+ //window.location.pathname = "/"
+ console.log("Should've logged out");
+ removeCookie("session_token", { path: "/" });
+ removeCookie("session_token", { path: "/workflows" });
+ window.location.reload();
+ })
+ .catch((error) => {
+ console.log("Error in logout: ", error);
+ removeCookie("session_token", { path: "/" });
+ window.location.reload();
+ //removeCookie("session_token", {path: "/"})
+ });
+ };
- const handleClickChangeOrg = (orgId) => {
- // Don't really care about the logout
- //name: org.name,
- //orgId = "asd"
- const data = {
- org_id: orgId,
- }
+ const handleClickChangeOrg = (orgId) => {
+ // Don't really care about the logout
+ //name: org.name,
+ //orgId = "asd"
+ const data = {
+ org_id: orgId,
+ };
fetch(`${globalUrl}/api/v1/orgs/${orgId}/change`, {
- mode: 'cors',
- method: 'POST',
- body: JSON.stringify(data),
- credentials: 'include',
- crossDomain: true,
- withCredentials: true,
- headers: {
- 'Content-Type': 'application/json; charset=utf-8',
- },
- })
- .then(function(response) {
- if (response.status !== 200) {
- console.log("Error in response")
- }
+ mode: "cors",
+ method: "POST",
+ body: JSON.stringify(data),
+ credentials: "include",
+ crossDomain: true,
+ withCredentials: true,
+ headers: {
+ "Content-Type": "application/json; charset=utf-8",
+ },
+ })
+ .then(function (response) {
+ if (response.status !== 200) {
+ console.log("Error in response");
+ }
- return response.json();
- }).then(function(responseJson) {
- if (responseJson.success !== undefined && responseJson.success) {
- setTimeout(() => {
- window.location.reload()
- }, 2000)
- alert.success("Successfully changed active organization - refreshing!")
- } else {
- alert.error("Failed changing org: ", responseJson.reason)
- }
- })
- .catch(error => {
- console.log("error changing: ", error)
- //removeCookie("session_token", {path: "/"})
- })
- }
+ return response.json();
+ })
+ .then(function (responseJson) {
+ if (responseJson.success !== undefined && responseJson.success) {
+ setTimeout(() => {
+ window.location.reload();
+ }, 2000);
+ alert.success(
+ "Successfully changed active organization - refreshing!"
+ );
+ } else {
+ alert.error("Failed changing org: ", responseJson.reason);
+ }
+ })
+ .catch((error) => {
+ console.log("error changing: ", error);
+ //removeCookie("session_token", {path: "/"})
+ });
+ };
- // Rofl this is weird
- const handleDocsHover = () => {
- setDocsHoverColor(hoverColor)
- }
+ // Rofl this is weird
+ const handleDocsHover = () => {
+ setDocsHoverColor(hoverColor);
+ };
- const handleDocsHoverOut = () => {
- setDocsHoverColor(hoverOutColor)
- }
+ const handleDocsHoverOut = () => {
+ setDocsHoverColor(hoverOutColor);
+ };
- const handleHomeHover = () => {
- setHomeHoverColor(hoverColor)
- }
+ const handleHomeHover = () => {
+ setHomeHoverColor(hoverColor);
+ };
- const handleHelpHover = () => {
- setHelpHoverColor(hoverColor)
- }
+ const handleHelpHover = () => {
+ setHelpHoverColor(hoverColor);
+ };
- const handleHelpHoverOut = () => {
- setHelpHoverColor(hoverOutColor)
- }
-
- const handleSoarHover = () => {
- setSoarHoverColor(hoverColor)
- }
+ const handleHelpHoverOut = () => {
+ setHelpHoverColor(hoverOutColor);
+ };
- const handleSoarHoverOut = () => {
- setSoarHoverColor(hoverOutColor)
- }
+ const handleSoarHover = () => {
+ setSoarHoverColor(hoverColor);
+ };
- const handleHomeHoverOut = () => {
- setHomeHoverColor(hoverOutColor)
- }
+ const handleSoarHoverOut = () => {
+ setSoarHoverColor(hoverOutColor);
+ };
- const handleLoginHover = () => {
- setLoginHoverColor(hoverColor)
- }
+ const handleHomeHoverOut = () => {
+ setHomeHoverColor(hoverOutColor);
+ };
- const handleLoginHoverOut = () => {
- setLoginHoverColor(hoverOutColor)
- }
+ const handleLoginHover = () => {
+ setLoginHoverColor(hoverColor);
+ };
+ const handleLoginHoverOut = () => {
+ setLoginHoverColor(hoverOutColor);
+ };
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
+ const chipStyle = {
+ backgroundColor: "#3d3f43",
+ height: 30,
+ marginRight: 5,
+ paddingLeft: 5,
+ paddingRight: 5,
+ height: 28,
+ cursor: "pointer",
+ borderColor: "#3d3f43",
+ color: "white",
+ };
- const chipStyle = {
- backgroundColor: "#3d3f43", height: 30, marginRight: 5, paddingLeft: 5, paddingRight: 5, height: 28, cursor: "pointer", borderColor: "#3d3f43", color: "white",
- }
+ const notificationWidth = 350;
+ const NotificationItem = (props) => {
+ const { data } = props;
- const notificationWidth = 350
- const NotificationItem = (props) => {
- const {data} = props
-
- return (
-
- {/*
+ return (
+
+ {/*
{new Date(data.updated_at).toISOString()}
*/}
- {data.reference_url !== undefined && data.reference_url !== null && data.reference_url.length > 0 ?
-
-
- {data.title}
-
-
- :
-
- {data.title}
-
- }
+ {data.reference_url !== undefined &&
+ data.reference_url !== null &&
+ data.reference_url.length > 0 ? (
+
+ {data.title}
+
+ ) : (
+ {data.title}
+ )}
- {data.image !== undefined && data.image !== null && data.image.length > 0 ?
-
- :
- null
- }
-
- {data.description}
-
- {/*data.tags !== undefined && data.tags !== null && data.tags.length > 0 ?
+ {data.image !== undefined &&
+ data.image !== null &&
+ data.image.length > 0 ? (
+
+ ) : null}
+ {data.description}
+ {/*data.tags !== undefined && data.tags !== null && data.tags.length > 0 ?
data.tags.map((tag, index) => {
return (
{
)
})
: null */}
- {data.read === false ?
- {
- dismissNotification(data.id)
- }}>
- Dismiss
-
- : null}
-
- )
- }
+ {data.read === false ? (
+ {
+ dismissNotification(data.id);
+ }}
+ >
+ Dismiss
+
+ ) : null}
+
+ );
+ };
- const notificationMenu =
-
- {
- setAnchorEl(event.currentTarget);
- }}>
-
-
-
-
-
-
+ const notificationMenu = (
+
+ {
+ setAnchorEl(event.currentTarget);
+ }}
+ >
+
+
+
+
+
+
+ );
- // Should be based on some path
- const avatarMenu =
-
- {
- setAnchorElAvatar(event.currentTarget);
- }}>
-
-
-
-
+ // Should be based on some path
+ const avatarMenu = (
+
+ {
+ setAnchorElAvatar(event.currentTarget);
+ }}
+ >
+
+
+
+
+ );
-
- // Handle top bar or something
- const logoCheck = !homePage ? null : null
- //
- const loginTextBrowser = !isLoggedIn ?
-
-
-
-
-
- About
-
-
-
-
-
-
- :
-
-
-
-
-
-
-
-
-
-
-
-
-
- {/*
+ // Handle top bar or something
+ const logoCheck = !homePage ? null : null;
+ //
+ const loginTextBrowser = !isLoggedIn ? (
+
+
+
+
+
+ About
+
+
+
+
+
+
+
+
+
+ Login
+
+
+
+
+
+
+ ) : (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/*
Dashboard
*/}
-
-
-
-
- Docs
-
-
-
- {/*
+
+
+
+
+ Docs
+
+
+
+ {/*
@@ -425,146 +610,259 @@ const Header = props => {
*/}
- {/*
+ {/*
Configure
*/}
-
-
-
- {avatarMenu}
- {notificationMenu}
- {userdata === undefined || userdata.admin === undefined || userdata.admin === null || !userdata.admin ? null :
-
-
- Admin
-
-
- }
- {userdata === undefined || userdata.orgs === undefined || userdata.orgs === null || userdata.orgs.length <= 1 ? null :
- {
- handleClickChangeOrg(e.target.value)
- }}
- >
- {userdata.orgs.map((data, index) => {
- if (data.name === undefined || data.name === null || data.name.length === 0) {
- return null
- }
+
+
+
+ {avatarMenu}
+ {notificationMenu}
+ {userdata === undefined ||
+ userdata.admin === undefined ||
+ userdata.admin === null ||
+ !userdata.admin ? null : (
+
+
+ Admin
+
+
+ )}
+ {userdata === undefined ||
+ userdata.orgs === undefined ||
+ userdata.orgs === null ||
+ userdata.orgs.length <= 1 ? null : (
+
{
+ handleClickChangeOrg(e.target.value);
+ }}
+ >
+ {userdata.orgs.map((data, index) => {
+ if (
+ data.name === undefined ||
+ data.name === null ||
+ data.name.length === 0
+ ) {
+ return null;
+ }
- const imagesize = 22
- const imageStyle = {width: imagesize, height: imagesize, pointerEvents: "none", marginRight: 10, marginLeft: data.creator_org !== undefined && data.creator_org.length > 0 ? 20 : 0}
- const image = data.image === "" ?
-
- :
-
+ const imagesize = 22
- return (
-
+ //if (data.creator_org !== undefined && data.creator_org !== null && data.creator_org.length > 0 && data.fixed !== true) {
+ var skipOrg = false
+ if (data.creator_org !== undefined && data.creator_org !== null && data.creator_org.length > 0) {
+ // Finds the parent org
+ for (var key in userdata.child_orgs) {
+ if (data.child_orgs[key].id === data.creator_org) {
+ skipOrg = true
+ break
+ }
+ }
-
-
- {image} {data.name}
-
-
-
- )
- })}
-
- }
-
-
+ if (skipOrg) {
+ return null
+ }
+ }
- //console.log("USR: ", userdata.orgs)
+ // Reordering to have suborgs with access under original org
+ if (data.child_orgs !== undefined && data.child_orgs !== null) {
+ var cnt = 0
+ for (var key in data.child_orgs) {
+ const childorg = data.child_orgs[key]
+ const foundIndex = userdata.orgs.findIndex(item => item.id === childorg.id)
+ if (foundIndex !== -1) {
+ const newindex = parseInt(index)+parseInt(cnt)
- const loginTextMobile = !isLoggedIn ?
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- About
-
-
-
-
-
- :
-
-
-
-
-
- Shuffle
-
-
-
-
- Workflows
-
-
-
-
- Apps
-
-
- {/*
+ var newitem = userdata.orgs[foundIndex]
+ newitem.fixed = true
+ userdata.orgs.splice(newindex+1, 0, newitem)
+ userdata.orgs.splice(foundIndex+1, 1)
+ } else {
+ console.log("ORG NOT FOUND IN LIST: ", childorg)
+
+ }
+
+ // This is stupid :)
+ cnt += 1
+ }
+ }
+
+ //console.log("ORG: ", data)
+ const imageStyle = {
+ width: imagesize,
+ height: imagesize,
+ pointerEvents: "none",
+ marginRight: 10,
+ marginLeft: data.creator_org !== undefined && data.creator_org !== null && data.creator_org.length > 0 ? 20 : 0,
+ }
+
+ const parsedTitle = data.creator_org !== undefined && data.creator_org !== null && data.creator_org.length > 0 ? `Suborg of ${data.creator_org}` : ""
+
+ const image = data.image === "" ?
+
+ :
+
+
+ return (
+
+
+
+
+ {image} {data.name}
+
+
+
+ )
+ })}
+
+ )}
+
+
+ );
+
+ //console.log("USR: ", userdata.orgs)
+
+ const loginTextMobile = !isLoggedIn ? (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ About
+
+
+
+
+
+ ) : (
+
+
+
+
+
+
+ Shuffle
+
+
+
+
+
+
+ Workflows
+
+
+
+
+
+
+ Apps
+
+
+
+ {/*
Configure
*/}
-
-
-
- {avatarMenu}
-
-
+
+
+
+ {avatarMenu}
+
+
+ );
- //
- const loadedCheck =
-
-
- {loginTextBrowser}
-
-
- {loginTextMobile}
-
-
- //
- return (
-
- {loadedCheck}
-
- )
-}
+ //
+ const loadedCheck = (
+
+ {loginTextBrowser}
+ {loginTextMobile}
+
+ );
+ //
+ return (
+
+ {loadedCheck}
+
+ );
+};
export default Header;
diff --git a/frontend/src/components/LandingpageUsecases.jsx b/frontend/src/components/LandingpageUsecases.jsx
new file mode 100644
index 00000000..2f4e2478
--- /dev/null
+++ b/frontend/src/components/LandingpageUsecases.jsx
@@ -0,0 +1,245 @@
+import React, { useState, useEffect } from 'react';
+import {isMobile} from "react-device-detect";
+import DetectionFramework, { usecases } from "../components/DetectionFramework.jsx";
+import {Link} from 'react-router-dom';
+import ReactGA from 'react-ga';
+
+import { Button, LinearProgress, Typography } from '@material-ui/core';
+
+export const securityFramework = [
+ {
+ image:
,
+ text: "Cases",
+ description: "Case management"
+ },
+ {
+ image:
+
,
+ text: "SIEM",
+ description: "Case management"
+ },
+ {
+ image:
+
,
+ text: "Assets",
+ description: "Case management"
+ },
+ {
+ image:
+
,
+ text: "IAM",
+ description: "Case management"
+ },
+ {
+ image:
,
+ text: "Intel",
+ description: "Case management"
+ },
+ {
+ image:
+
,
+ text: "Comms",
+ description: "Case management"
+ },
+ {
+ image:
+
,
+ text: "Network",
+ description: "Case management"
+ },
+ {
+ image:
+
,
+ text: "EDR & AV",
+ description: "Case management"
+ },
+]
+
+const LandingpageUsecases = (props) => {
+ const [selectedUsecase, setSelectedUsecase] = useState("Phishing")
+ const usecasekeys = usecases === undefined || usecases === null ? [] : Object.keys(usecases)
+ const buttonBackground = "linear-gradient(to right, #f86a3e, #f34079)"
+ const buttonStyle = {borderRadius: 25, height: 50, width: 260, margin: isMobile ? "15px auto 15px auto" : 20, fontSize: 18, backgroundImage: buttonBackground}
+
+ const HandleTitle = (props) => {
+ const { usecases, selectedUsecase, setSelecedUsecase } = props
+ const [progress, setProgress] = useState(0)
+
+ useEffect(() => {
+ const timer = setInterval(() => {
+ setProgress((oldProgress) => {
+ if (oldProgress >= 105) {
+ const foundIndex = usecasekeys.findIndex(key => key === selectedUsecase)
+ var newitem = usecasekeys[foundIndex+1]
+ if (newitem === undefined || newitem === 0) {
+ newitem = usecasekeys[1]
+ }
+
+ setSelectedUsecase(newitem)
+ return -18
+ }
+
+ if (oldProgress >= 65) {
+ return oldProgress + 3
+ }
+
+ if (oldProgress >= 80) {
+ return oldProgress + 1
+ }
+
+ return oldProgress + 6
+ })
+ }, 165)
+
+ return () => {
+ clearInterval(timer)
+ }
+ }, [])
+
+ if (usecases === null || usecases === undefined || usecases.length === 0) {
+ return null
+ }
+
+ const modifier = isMobile ? 17 : 22
+ return (
+
+ Handle
+
+ {selectedUsecase}
+
+
+ with confidence
+
+ )
+ }
+
+ const parsedWidth = isMobile ? "100%" : 1100
+ return (
+
+
+
+
+
+
+
+ {/*Security Automation is Hard */}
+
+
+ Connecting your everchanging environment is hard. We get it! That's why we built Shuffle, where you can use and share your security workflows to everyones benefit.
+ {/*Shuffle is an automation platform where you don't need to be an expert to automate. Get access to our large pool of security playbooks, apps and people.*/}
+
+
+ {isMobile ? null :
+
+ {
+ ReactGA.event({
+ category: "landingpage",
+ action: "click_main_pricing",
+ label: "",
+ })
+ }}
+ style={{
+ borderRadius: 25, height: 40, width: 175, margin: "15px 0px 15px 0px", fontSize: 14, color: "white", backgroundImage: buttonBackground, marginRight: 10,
+ }}>
+ See Pricing
+
+
+ }
+ {isMobile ? null :
+
+ {
+ ReactGA.event({
+ category: "landingpage",
+ action: "click_main_try_it_out",
+ label: "",
+ })
+ }}
+ style={{
+ borderRadius: 25, height: 40, width: 175, margin: "15px 0px 15px 0px", fontSize: 14, color: "white", backgroundImage: buttonBackground,
+ }}>
+ Start for free
+
+
+ }
+
+
+ {isMobile ? null :
+
+
+
+ }
+ {isMobile ? null :
+
+ }
+
+
+ {isMobile ?
+
+ {
+ ReactGA.event({
+ category: "landingpage",
+ action: "click_main_pricing",
+ label: "",
+ })
+ }}
+ >
+ See pricing
+
+
+ : null
+ }
+ {/*isMobile ?
+
+ {
+ ReactGA.event({
+ category: "landingpage",
+ action: "click_main_features",
+ label: "",
+ })
+ }}
+ color="secondary"
+ style={buttonStyle}>
+ Features
+
+
+ : null*/}
+
+ {isMobile ? null :
+
+ {securityFramework.map((data, index) => {
+ return (
+
+
+
+ {data.image}
+
+
+
+ {data.text}
+
+
+ )
+ })}
+
+ }
+
+ )
+}
+
+export default LandingpageUsecases;
diff --git a/frontend/src/components/LoginPopup.js b/frontend/src/components/LoginPopup.js
index 232b4976..1807ae9a 100644
--- a/frontend/src/components/LoginPopup.js
+++ b/frontend/src/components/LoginPopup.js
@@ -1,139 +1,176 @@
/* eslint-disable react/no-multi-comp */
-import React, {useState} from 'react';
+import React, { useState } from "react";
-import DialogTitle from '@material-ui/core/DialogTitle';
-import Dialog from '@material-ui/core/Dialog';
-import TextField from '@material-ui/core/TextField';
-import Button from '@material-ui/core/Button';
+import DialogTitle from "@material-ui/core/DialogTitle";
+import Dialog from "@material-ui/core/Dialog";
+import TextField from "@material-ui/core/TextField";
+import Button from "@material-ui/core/Button";
-const LoginDialog = props => {
- const { classes, onClose, open, globalUrl, isLoggedIn, setIsLoggedIn, ...other } = props;
+const LoginDialog = (props) => {
+ const {
+ classes,
+ onClose,
+ open,
+ globalUrl,
+ isLoggedIn,
+ setIsLoggedIn,
+ ...other
+ } = props;
- const [username, setUsername] = useState("");
- const [password, setPassword] = useState("");
- //const [selectedValue, setSelectedValue] = useState(false);
+ const [username, setUsername] = useState("");
+ const [password, setPassword] = useState("");
+ //const [selectedValue, setSelectedValue] = useState(false);
- // Used to swap from login to register. True = login, false = register
- const [loginCheck, setLoginCheck] = useState(true);
+ // Used to swap from login to register. True = login, false = register
+ const [loginCheck, setLoginCheck] = useState(true);
- // Error messages etc
- const [loginInfo, setLoginInfo] = useState("");
+ // Error messages etc
+ const [loginInfo, setLoginInfo] = useState("");
- const handleValidateForm = () => {
- return (username.length > 1 && password.length > 8);
- }
+ const handleValidateForm = () => {
+ return username.length > 1 && password.length > 8;
+ };
- const onSubmit = (e) => {
- e.preventDefault()
+ const onSubmit = (e) => {
+ e.preventDefault();
- // Just use this one?
- var data = '{"username": "' + username + '", "password": "' + password + '"}';
- var baseurl = globalUrl
- if (loginCheck) {
- var url = baseurl+'/login';
- fetch(url, {
- method: 'POST',
- body: data,
- headers: {
- 'Content-Type': 'application/json',
- },
- })
- .then(response =>
- response.json().then(responseJson => {
- console.log(responseJson)
- //console.log(e)
- if (responseJson["success"] === false) {
- setLoginInfo(responseJson["reason"])
- } else {
- setLoginInfo("Successful login :)")
- onClose()
- setIsLoggedIn(true)
- }
- }),
- )
- .catch(error => {
- setLoginInfo("Error in userdata")
- });
- } else {
- url = baseurl+'/register';
- fetch(url, {
- method: 'POST',
- body: data,
- headers: {
- 'Content-Type': 'application/json',
- },
- })
- .then(response =>
- response.json().then(responseJson => {
- if (responseJson["success"] === false) {
- setLoginInfo(responseJson["reason"])
- } else {
- setLoginInfo("Successful register :)")
- onClose()
- setIsLoggedIn(true)
- }
- }),
- )
- .catch(error => {
- setLoginInfo("Error in userdata")
- });
- }
- }
+ // Just use this one?
+ var data =
+ '{"username": "' + username + '", "password": "' + password + '"}';
+ var baseurl = globalUrl;
+ if (loginCheck) {
+ var url = baseurl + "/login";
+ fetch(url, {
+ method: "POST",
+ body: data,
+ headers: {
+ "Content-Type": "application/json",
+ },
+ })
+ .then((response) =>
+ response.json().then((responseJson) => {
+ console.log(responseJson);
+ //console.log(e)
+ if (responseJson["success"] === false) {
+ setLoginInfo(responseJson["reason"]);
+ } else {
+ setLoginInfo("Successful login :)");
+ onClose();
+ setIsLoggedIn(true);
+ }
+ })
+ )
+ .catch((error) => {
+ setLoginInfo("Error in userdata");
+ });
+ } else {
+ url = baseurl + "/register";
+ fetch(url, {
+ method: "POST",
+ body: data,
+ headers: {
+ "Content-Type": "application/json",
+ },
+ })
+ .then((response) =>
+ response.json().then((responseJson) => {
+ if (responseJson["success"] === false) {
+ setLoginInfo(responseJson["reason"]);
+ } else {
+ setLoginInfo("Successful register :)");
+ onClose();
+ setIsLoggedIn(true);
+ }
+ })
+ )
+ .catch((error) => {
+ setLoginInfo("Error in userdata");
+ });
+ }
+ };
- const onChangeUser = (e) => {
- setUsername(e.target.value)
- }
+ const onChangeUser = (e) => {
+ setUsername(e.target.value);
+ };
- const onChangePass = (e) => {
- setPassword(e.target.value)
- }
+ const onChangePass = (e) => {
+ setPassword(e.target.value);
+ };
- const onClickRegister = () => {
- setLoginCheck(!loginCheck)
- }
+ const onClickRegister = () => {
+ setLoginCheck(!loginCheck);
+ };
- //var loginChange = loginCheck ? (
Want to register? Click here.
) : (
Go back to login? Click here.
);
- var formtitle = loginCheck ?
Login
:
Register
- var formButton = loginCheck ?
Click to Register
:
Click to Login
+ //var loginChange = loginCheck ? (
Want to register? Click here.
) : (
Go back to login? Click here.
);
+ var formtitle = loginCheck ?
Login
:
Register
;
+ var formButton = loginCheck ? (
+
Click to Register
+ ) : (
+
Click to Login
+ );
- return (
-
- {formtitle}
-
-
- {formButton}
-
-
- );
-}
+ return (
+
+ {formtitle}
+
+
+
+ {formButton}
+
+
+
+ );
+};
export default LoginDialog;
diff --git a/frontend/src/components/NestedMenu.jsx b/frontend/src/components/NestedMenu.jsx
index cabf1b24..35d770a5 100644
--- a/frontend/src/components/NestedMenu.jsx
+++ b/frontend/src/components/NestedMenu.jsx
@@ -1,9 +1,9 @@
-import React, {useState, useRef, useImperativeHandle} from 'react'
-import {makeStyles} from '@material-ui/core/styles'
-import Menu, {MenuProps} from '@material-ui/core/Menu'
-import MenuItem, {MenuItemProps} from '@material-ui/core/MenuItem'
-import ArrowRight from '@material-ui/icons/ArrowRight'
-import clsx from 'clsx'
+import React, { useState, useRef, useImperativeHandle } from "react";
+import { makeStyles } from "@material-ui/core/styles";
+import Menu, { MenuProps } from "@material-ui/core/Menu";
+import MenuItem, { MenuItemProps } from "@material-ui/core/MenuItem";
+import ArrowRight from "@material-ui/icons/ArrowRight";
+import clsx from "clsx";
//
@@ -39,15 +39,15 @@ import clsx from 'clsx'
// /**
// * @see https://material-ui.com/api/list-item/
// */
-// button: true;
+// button: true;
//}
-const TRANSPARENT = 'rgba(0,0,0,0)'
+const TRANSPARENT = "rgba(0,0,0,0)";
const useMenuItemStyles = makeStyles((theme) => ({
root: (props: any) => ({
- backgroundColor: props.open ? theme.palette.action.hover : TRANSPARENT
- })
-}))
+ backgroundColor: props.open ? theme.palette.action.hover : TRANSPARENT,
+ }),
+}));
/**
* Use as a drop-in replacement for `` when you need to add cascading
@@ -55,11 +55,11 @@ const useMenuItemStyles = makeStyles((theme) => ({
*/
//const NestedMenuItem = React.forwardRef(
const NestedMenuItem = (props, ref) => {
- console.log(props, ref)
- //function NestedMenuItem(props, ref) {
+ console.log(props, ref);
+ //function NestedMenuItem(props, ref) {
const {
parentMenuOpen,
- component = 'div',
+ component = "div",
label,
rightIcon = ,
children,
@@ -68,94 +68,100 @@ const NestedMenuItem = (props, ref) => {
MenuProps = {},
ContainerProps: ContainerPropsProp = {},
...MenuItemProps
- } = props
+ } = props;
- const [isSubMenuOpen, setIsSubMenuOpen] = useState(false)
+ const [isSubMenuOpen, setIsSubMenuOpen] = useState(false);
- const {ref: containerRefProp, ...ContainerProps} = ContainerPropsProp
+ const { ref: containerRefProp, ...ContainerProps } = ContainerPropsProp;
+ const menuItemRef = useRef < HTMLLIElement > null;
+ useImperativeHandle(ref, () => menuItemRef.current);
+ const containerRef = useRef < HTMLDivElement > null;
+ useImperativeHandle(containerRefProp, () => containerRef.current);
+ const menuContainerRef = useRef < HTMLDivElement > null;
- const menuItemRef = useRef(null)
- useImperativeHandle(ref, () => menuItemRef.current)
- const containerRef = useRef(null)
- useImperativeHandle(containerRefProp, () => containerRef.current)
- const menuContainerRef = useRef(null)
-
- console.log("PAST THIS: ", containerRefProp, menuItemRef, containerRef, menuContainerRef, ContainerProps)
+ console.log(
+ "PAST THIS: ",
+ containerRefProp,
+ menuItemRef,
+ containerRef,
+ menuContainerRef,
+ ContainerProps
+ );
const handleMouseEnter = (event: React.MouseEvent) => {
- setIsSubMenuOpen(true)
+ setIsSubMenuOpen(true);
if (ContainerProps?.onMouseEnter) {
- ContainerProps.onMouseEnter(event)
+ ContainerProps.onMouseEnter(event);
}
- }
+ };
const handleMouseLeave = (event: React.MouseEvent) => {
- setIsSubMenuOpen(false)
+ setIsSubMenuOpen(false);
if (ContainerProps?.onMouseLeave) {
- ContainerProps.onMouseLeave(event)
+ ContainerProps.onMouseLeave(event);
}
- }
+ };
// Check if any immediate children are active
const isSubmenuFocused = () => {
- const active = containerRef.current?.ownerDocument?.activeElement
+ const active = containerRef.current?.ownerDocument?.activeElement;
for (const child of menuContainerRef.current?.children ?? []) {
if (child === active) {
- return true
+ return true;
}
}
- return false
- }
+ return false;
+ };
const handleFocus = (event: React.FocusEvent) => {
if (event.target === containerRef.current) {
- setIsSubMenuOpen(true)
+ setIsSubMenuOpen(true);
}
if (ContainerProps?.onFocus) {
- ContainerProps.onFocus(event)
+ ContainerProps.onFocus(event);
}
- }
+ };
const handleKeyDown = (event: React.KeyboardEvent) => {
- if (event.key === 'Escape') {
- return
+ if (event.key === "Escape") {
+ return;
}
if (isSubmenuFocused()) {
- event.stopPropagation()
+ event.stopPropagation();
}
- const active = containerRef.current?.ownerDocument?.activeElement
+ const active = containerRef.current?.ownerDocument?.activeElement;
- if (event.key === 'ArrowLeft' && isSubmenuFocused()) {
- containerRef.current?.focus()
+ if (event.key === "ArrowLeft" && isSubmenuFocused()) {
+ containerRef.current?.focus();
}
if (
- event.key === 'ArrowRight' &&
+ event.key === "ArrowRight" &&
event.target === containerRef.current &&
event.target === active
) {
- console.log("MENU: ", menuContainerRef)
- const firstChild = menuContainerRef.current.children[0]
- console.log("FIRST: ", firstChild)
- firstChild.focus()
+ console.log("MENU: ", menuContainerRef);
+ const firstChild = menuContainerRef.current.children[0];
+ console.log("FIRST: ", firstChild);
+ firstChild.focus();
}
- }
+ };
- const open = isSubMenuOpen && parentMenuOpen
- const menuItemClasses = useMenuItemStyles({open})
+ const open = isSubMenuOpen && parentMenuOpen;
+ const menuItemClasses = useMenuItemStyles({ open });
// Root element must have a `tabIndex` attribute for keyboard navigation
- let tabIndex
+ let tabIndex;
if (!props.disabled) {
- tabIndex = tabIndexProp !== undefined ? tabIndexProp : -1
+ tabIndex = tabIndexProp !== undefined ? tabIndexProp : -1;
}
- console.log("PAST 2! ", tabIndex)
+ console.log("PAST 2! ", tabIndex);
return (
{
{
- setIsSubMenuOpen(false)
+ setIsSubMenuOpen(false);
}}
>
-
- )
-}
+ );
+};
-export default NestedMenuItem
+export default NestedMenuItem;
diff --git a/frontend/src/components/NestedMenuItem.jsx b/frontend/src/components/NestedMenuItem.jsx
new file mode 100644
index 00000000..e48c2988
--- /dev/null
+++ b/frontend/src/components/NestedMenuItem.jsx
@@ -0,0 +1,202 @@
+import React, {useState, useRef, useImperativeHandle} from 'react'
+import {makeStyles} from '@material-ui/core/styles'
+import Menu, {MenuProps} from '@material-ui/core/Menu'
+import MenuItem, {MenuItemProps} from '@material-ui/core/MenuItem'
+import ArrowRight from '@material-ui/icons/ArrowRight'
+import clsx from 'clsx'
+
+export interface NestedMenuItemProps extends Omit {
+ /**
+ * Open state of parent ` `, used to close decendent menus when the
+ * root menu is closed.
+ */
+ parentMenuOpen: boolean
+ /**
+ * Component for the container element.
+ * @default 'div'
+ */
+ component?: React.ElementType
+ /**
+ * Effectively becomes the `children` prop passed to the ` `
+ * element.
+ */
+ label?: React.ReactNode
+ /**
+ * @default
+ */
+ rightIcon?: React.ReactNode
+ /**
+ * Props passed to container element.
+ */
+ ContainerProps?: React.HTMLAttributes &
+ React.RefAttributes
+ /**
+ * Props passed to sub ` ` element
+ */
+ MenuProps?: Omit
+ /**
+ * @see https://material-ui.com/api/list-item/
+ */
+ button?: true | undefined
+}
+
+const TRANSPARENT = 'rgba(0,0,0,0)'
+const useMenuItemStyles = makeStyles((theme) => ({
+ root: (props: any) => ({
+ backgroundColor: props.open ? theme.palette.action.hover : TRANSPARENT
+ })
+}))
+
+/**
+ * Use as a drop-in replacement for `` when you need to add cascading
+ * menu elements as children to this component.
+ */
+const NestedMenuItem = React.forwardRef<
+ HTMLLIElement | null,
+ NestedMenuItemProps
+>(function NestedMenuItem(props, ref) {
+ const {
+ parentMenuOpen,
+ component = 'div',
+ label,
+ rightIcon = ,
+ children,
+ className,
+ tabIndex: tabIndexProp,
+ MenuProps = {},
+ ContainerProps: ContainerPropsProp = {},
+ ...MenuItemProps
+ } = props
+
+ const {ref: containerRefProp, ...ContainerProps} = ContainerPropsProp
+
+ const menuItemRef = useRef(null)
+ useImperativeHandle(ref, () => menuItemRef.current)
+
+ const containerRef = useRef(null)
+ useImperativeHandle(containerRefProp, () => containerRef.current)
+
+ const menuContainerRef = useRef(null)
+
+ const [isSubMenuOpen, setIsSubMenuOpen] = useState(false)
+
+ const handleMouseEnter = (event: React.MouseEvent) => {
+ setIsSubMenuOpen(true)
+
+ if (ContainerProps?.onMouseEnter) {
+ ContainerProps.onMouseEnter(event)
+ }
+ }
+ const handleMouseLeave = (event: React.MouseEvent) => {
+ setIsSubMenuOpen(false)
+
+ if (ContainerProps?.onMouseLeave) {
+ ContainerProps.onMouseLeave(event)
+ }
+ }
+
+ // Check if any immediate children are active
+ const isSubmenuFocused = () => {
+ const active = containerRef.current?.ownerDocument?.activeElement
+ for (const child of menuContainerRef.current?.children ?? []) {
+ if (child === active) {
+ return true
+ }
+ }
+ return false
+ }
+
+ const handleFocus = (event: React.FocusEvent) => {
+ if (event.target === containerRef.current) {
+ setIsSubMenuOpen(true)
+ }
+
+ if (ContainerProps?.onFocus) {
+ ContainerProps.onFocus(event)
+ }
+ }
+
+ const handleKeyDown = (event: React.KeyboardEvent) => {
+ if (event.key === 'Escape') {
+ return
+ }
+
+ if (isSubmenuFocused()) {
+ event.stopPropagation()
+ }
+
+ const active = containerRef.current?.ownerDocument?.activeElement
+
+ if (event.key === 'ArrowLeft' && isSubmenuFocused()) {
+ containerRef.current?.focus()
+ }
+
+ if (
+ event.key === 'ArrowRight' &&
+ event.target === containerRef.current &&
+ event.target === active
+ ) {
+ const firstChild = menuContainerRef.current?.children[0] as
+ | HTMLElement
+ | undefined
+ firstChild?.focus()
+ }
+ }
+
+ const open = isSubMenuOpen && parentMenuOpen
+ const menuItemClasses = useMenuItemStyles({open})
+
+ // Root element must have a `tabIndex` attribute for keyboard navigation
+ let tabIndex
+ if (!props.disabled) {
+ tabIndex = tabIndexProp !== undefined ? tabIndexProp : -1
+ }
+
+ return (
+
+
+ {label}
+ {rightIcon}
+
+
{
+ setIsSubMenuOpen(false)
+ }}
+ >
+
+ {children}
+
+
+
+ )
+})
+
+export default NestedMenuItem
diff --git a/frontend/src/components/Oauth2Auth.jsx b/frontend/src/components/Oauth2Auth.jsx
index 4fd89ac7..77fc31f5 100644
--- a/frontend/src/components/Oauth2Auth.jsx
+++ b/frontend/src/components/Oauth2Auth.jsx
@@ -1,216 +1,343 @@
-import React, {useRef, useState, useEffect, useLayoutEffect} from 'react';
-import { useTheme } from '@material-ui/core/styles';
+import React, { useRef, useState, useEffect, useLayoutEffect } from "react";
+import { useTheme } from "@material-ui/core/styles";
-import { v4 as uuidv4 } from 'uuid';
-import { ListItemText, TextField, Drawer, Button, Paper, Grid, Tabs, InputAdornment, Tab, ButtonBase, Tooltip, Select, MenuItem, Divider, Dialog, Modal, DialogActions, DialogTitle, InputLabel, DialogContent, FormControl, IconButton, Menu, Input, FormGroup, FormControlLabel, Typography, Checkbox, Breadcrumbs, CircularProgress, Switch, Fade } from '@material-ui/core';
-import { LockOpen as LockOpenIcon } from '@material-ui/icons';
+import { v4 as uuidv4 } from "uuid";
+import {
+ ListItemText,
+ TextField,
+ Drawer,
+ Button,
+ Paper,
+ Grid,
+ Tabs,
+ InputAdornment,
+ Tab,
+ ButtonBase,
+ Tooltip,
+ Select,
+ MenuItem,
+ Divider,
+ Dialog,
+ Modal,
+ DialogActions,
+ DialogTitle,
+ InputLabel,
+ DialogContent,
+ FormControl,
+ IconButton,
+ Menu,
+ Input,
+ FormGroup,
+ FormControlLabel,
+ Typography,
+ Checkbox,
+ Breadcrumbs,
+ CircularProgress,
+ Switch,
+ Fade,
+} from "@material-ui/core";
+import { LockOpen as LockOpenIcon } from "@material-ui/icons";
-const ITEM_HEIGHT = 55
-const ITEM_PADDING_TOP = 8
+const ITEM_HEIGHT = 55;
+const ITEM_PADDING_TOP = 8;
const MenuProps = {
PaperProps: {
style: {
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
- minWidth: 500,
+ minWidth: 500,
maxWidth: 500,
- scrollX: "auto",
+ scrollX: "auto",
},
},
-}
+ variant: "menu",
+ getContentAnchorEl: null,
+};
const AuthenticationOauth2 = (props) => {
- const { saveWorkflow, selectedApp, workflow, selectedAction, authenticationType, getAppAuthentication, appAuthentication, setSelectedAction, setNewAppAuth, setAuthenticationModalOpen} = props;
- const theme = useTheme();
+ const {
+ saveWorkflow,
+ selectedApp,
+ workflow,
+ selectedAction,
+ authenticationType,
+ getAppAuthentication,
+ appAuthentication,
+ setSelectedAction,
+ setNewAppAuth,
+ setAuthenticationModalOpen,
+ } = props;
+ const theme = useTheme();
- //const [update, setUpdate] = React.useState("|")
- const [defaultConfigSet, setDefaultConfigSet] = React.useState(authenticationType.client_id !== undefined && authenticationType.client_id !== null && authenticationType.client_id.length > 0 && authenticationType.client_secret !== undefined && authenticationType.client_secret !== null && authenticationType.client_secret.length > 0)
- const [clientId, setClientId] = React.useState(defaultConfigSet ? authenticationType.client_id : "")
- const [clientSecret, setClientSecret] = React.useState(defaultConfigSet ? authenticationType.client_secret : "")
- const [oauthUrl, setOauthUrl] = React.useState("")
- const [buttonClicked, setButtonClicked] = React.useState(false)
- const [selectedScopes, setSelectedScopes] = React.useState([])
- const allscopes = authenticationType.scope !== undefined ? authenticationType.scope: []
+ //const [update, setUpdate] = React.useState("|")
+ const [defaultConfigSet, setDefaultConfigSet] = React.useState(
+ authenticationType.client_id !== undefined &&
+ authenticationType.client_id !== null &&
+ authenticationType.client_id.length > 0 &&
+ authenticationType.client_secret !== undefined &&
+ authenticationType.client_secret !== null &&
+ authenticationType.client_secret.length > 0
+ );
+ const [clientId, setClientId] = React.useState(
+ defaultConfigSet ? authenticationType.client_id : ""
+ );
+ const [clientSecret, setClientSecret] = React.useState(
+ defaultConfigSet ? authenticationType.client_secret : ""
+ );
+ const [oauthUrl, setOauthUrl] = React.useState("");
+ const [buttonClicked, setButtonClicked] = React.useState(false);
+ const [selectedScopes, setSelectedScopes] = React.useState([]);
+ const [offlineAccess, setOfflineAccess] = React.useState(true);
+ const allscopes =
+ authenticationType.scope !== undefined ? authenticationType.scope : [];
- const [manuallyConfigure, setManuallyConfigure] = React.useState(defaultConfigSet ? false : true)
- const [authenticationOption, setAuthenticationOptions] = React.useState({
- app: JSON.parse(JSON.stringify(selectedApp)),
- fields: {},
- label: "",
- usage: [{
- workflow_id: workflow.id,
- }],
- id: uuidv4(),
- active: true,
- })
+ const [manuallyConfigure, setManuallyConfigure] = React.useState(
+ defaultConfigSet ? false : true
+ );
+ const [authenticationOption, setAuthenticationOptions] = React.useState({
+ app: JSON.parse(JSON.stringify(selectedApp)),
+ fields: {},
+ label: "",
+ usage: [
+ {
+ workflow_id: workflow.id,
+ },
+ ],
+ id: uuidv4(),
+ active: true,
+ });
- if (selectedApp.authentication === undefined) {
- return null
- }
+ if (selectedApp.authentication === undefined) {
+ return null;
+ }
- const handleOauth2Request = (client_id, client_secret, oauth_url, scopes) => {
- setButtonClicked(true)
- console.log("SCOPES: ", scopes)
+ const handleOauth2Request = (client_id, client_secret, oauth_url, scopes) => {
+ setButtonClicked(true);
+ console.log("SCOPES: ", scopes);
- var resources = ""
- if (scopes !== undefined && scopes !== null & scopes.length > 0) {
- resources = scopes.join(",")
- }
+ client_id = client_id.trim()
+ client_secret = client_secret.trim()
+ oauth_url = oauth_url.trim()
- const authentication_url = authenticationType.token_uri
-
- console.log("SCOPES2: ", resources)
- const redirectUri = `${window.location.protocol}//${window.location.host}/set_authentication`
- var state = `workflow_id%3D${workflow.id}%26reference_action_id%3d${selectedAction.app_id}%26app_name%3d${selectedAction.app_name}%26app_id%3d${selectedAction.app_id}%26app_version%3d${selectedAction.app_version}%26authentication_url%3d${authentication_url}%26scope%3d${resources}%26client_id%3d${client_id}%26client_secret%3d${client_secret}`
- if (oauth_url !== undefined && oauth_url !== null && oauth_url.length > 0) {
- state += `%26oauth_url%3d${oauth_url}`
- console.log("ADDING OAUTH2 URL: ", state)
- }
- const url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${resources}&prompt=consent&state=${state}`
-
- //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`
- console.log("Full URI: ", url)
- console.log("Redirect Uri: ", redirectUri)
- // &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=800,height=600")
- //console.log(newwin)
-
- var open = true
- const timer = setInterval(() => {
- if (newwin.closed) {
- setButtonClicked(false)
- clearInterval(timer);
- //alert('"Secure Payment" window closed!');
-
- getAppAuthentication(true, true)
- }
- }, 1000);
- //do {
- // setTimeout(() => {
- // console.log(newwin)
- // console.log("CLOSED", newwin.closed)
- // if (newwin.closed) {
-
- // open = false
- // }
- // }, 1000)
- //}
- //while(open === true)
- } catch (e) {
- alert.error("Failed authentication - probably bad credentials. Try again")
- setButtonClicked(false)
- }
-
- return
- //do {
- //} while (
- }
-
-
- authenticationOption.app.actions = []
-
- for (var key in selectedApp.authentication.parameters) {
- if (authenticationOption.fields[selectedApp.authentication.parameters[key].name] === undefined) {
- authenticationOption.fields[selectedApp.authentication.parameters[key].name] = ""
- }
- }
-
- const handleSubmitCheck = () => {
- console.log("NEW AUTH: ", authenticationOption)
- if (authenticationOption.label.length === 0) {
- authenticationOption.label = `Auth for ${selectedApp.name}`
- //alert.info("Label can't be empty")
- //return
- }
-
- // Automatically mapping fields that already exist (predefined).
- // Warning if fields are NOT filled
- for (var key in selectedApp.authentication.parameters) {
- if (authenticationOption.fields[selectedApp.authentication.parameters[key].name].length === 0) {
- if (selectedApp.authentication.parameters[key].value !== undefined && selectedApp.authentication.parameters[key].value !== null && selectedApp.authentication.parameters[key].value.length > 0) {
- authenticationOption.fields[selectedApp.authentication.parameters[key].name] = selectedApp.authentication.parameters[key].value
- } else {
- if (selectedApp.authentication.parameters[key].schema.type === "bool") {
- authenticationOption.fields[selectedApp.authentication.parameters[key].name] = "false"
- } else {
- alert.info("Field "+selectedApp.authentication.parameters[key].name+" can't be empty")
- return
- }
+ var resources = "";
+ if (scopes !== undefined && (scopes !== null) & (scopes.length > 0)) {
+ if (offlineAccess === true && !scopes.includes("offline_access")) {
+ if (authenticationType.redirect_uri.includes("microsoft")) {
+ console.log("Appending offline access")
+ scopes.push("offline_access")
}
- }
- }
+ }
- console.log("Action: ", selectedAction)
- selectedAction.authentication_id = authenticationOption.id
- selectedAction.selectedAuthentication = authenticationOption
- if (selectedAction.authentication === undefined || selectedAction.authentication === null) {
- selectedAction.authentication = [authenticationOption]
- } else {
- selectedAction.authentication.push(authenticationOption)
- }
+ resources = scopes.join(" ");
+ //resources = scopes.join(",");
+ }
- setSelectedAction(selectedAction)
+ const authentication_url = authenticationType.token_uri;
+ //console.log("AUTH: ", authenticationType)
+ //console.log("SCOPES2: ", resources)
+ const redirectUri = `${window.location.protocol}//${window.location.host}/set_authentication`;
+ var state = `workflow_id%3D${workflow.id}%26reference_action_id%3d${selectedAction.app_id}%26app_name%3d${selectedAction.app_name}%26app_id%3d${selectedAction.app_id}%26app_version%3d${selectedAction.app_version}%26authentication_url%3d${authentication_url}%26scope%3d${resources}%26client_id%3d${client_id}%26client_secret%3d${client_secret}`;
+ if (oauth_url !== undefined && oauth_url !== null && oauth_url.length > 0) {
+ state += `%26oauth_url%3d${oauth_url}`;
+ console.log("ADDING OAUTH2 URL: ", state);
+ }
- var newAuthOption = JSON.parse(JSON.stringify(authenticationOption))
- var newFields = []
- for (const key in newAuthOption.fields) {
- const value = newAuthOption.fields[key]
- newFields.push({
- key: key,
- value: value,
- })
- }
+ if (
+ authenticationType.refresh_uri !== undefined &&
+ authenticationType.refresh_uri !== null &&
+ authenticationType.refresh_uri.length > 0
+ ) {
+ state += `%26refresh_uri%3d${authenticationType.refresh_uri}`;
+ } else {
+ state += `%26refresh_uri%3d${authentication_url}`;
+ }
- console.log("FIELDS: ", newFields)
- newAuthOption.fields = newFields
- setNewAppAuth(newAuthOption)
- //appAuthentication.push(newAuthOption)
- //setAppAuthentication(appAuthentication)
- //
-
- //if (configureWorkflowModalOpen) {
- // setSelectedAction({})
- //}
- //setUpdate(authenticationOption.id)
+ const url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${resources}&prompt=consent&state=${state}&access_type=offline`;
- /*
+ //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`
+ //console.log("Full URI: ", url)
+ //console.log("Redirect Uri: ", redirectUri)
+ // &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=800,height=600");
+ //console.log(newwin)
+
+ var open = true;
+ const timer = setInterval(() => {
+ if (newwin.closed) {
+ setButtonClicked(false);
+ clearInterval(timer);
+ //alert('"Secure Payment" window closed!');
+
+ getAppAuthentication(true, true);
+ }
+ }, 1000);
+ //do {
+ // setTimeout(() => {
+ // console.log(newwin)
+ // console.log("CLOSED", newwin.closed)
+ // if (newwin.closed) {
+
+ // open = false
+ // }
+ // }, 1000)
+ //}
+ //while(open === true)
+ } catch (e) {
+ alert.error(
+ "Failed authentication - probably bad credentials. Try again"
+ );
+ setButtonClicked(false);
+ }
+
+ return;
+ //do {
+ //} while (
+ };
+
+ authenticationOption.app.actions = [];
+
+ for (var key in selectedApp.authentication.parameters) {
+ if (
+ authenticationOption.fields[
+ selectedApp.authentication.parameters[key].name
+ ] === undefined
+ ) {
+ authenticationOption.fields[
+ selectedApp.authentication.parameters[key].name
+ ] = "";
+ }
+ }
+
+ const handleSubmitCheck = () => {
+ console.log("NEW AUTH: ", authenticationOption);
+ if (authenticationOption.label.length === 0) {
+ authenticationOption.label = `Auth for ${selectedApp.name}`;
+ //alert.info("Label can't be empty")
+ //return
+ }
+
+ // Automatically mapping fields that already exist (predefined).
+ // Warning if fields are NOT filled
+ for (var key in selectedApp.authentication.parameters) {
+ if (
+ authenticationOption.fields[
+ selectedApp.authentication.parameters[key].name
+ ].length === 0
+ ) {
+ if (
+ selectedApp.authentication.parameters[key].value !== undefined &&
+ selectedApp.authentication.parameters[key].value !== null &&
+ selectedApp.authentication.parameters[key].value.length > 0
+ ) {
+ authenticationOption.fields[
+ selectedApp.authentication.parameters[key].name
+ ] = selectedApp.authentication.parameters[key].value;
+ } else {
+ if (
+ selectedApp.authentication.parameters[key].schema.type === "bool"
+ ) {
+ authenticationOption.fields[
+ selectedApp.authentication.parameters[key].name
+ ] = "false";
+ } else {
+ alert.info(
+ "Field " +
+ selectedApp.authentication.parameters[key].name +
+ " can't be empty"
+ );
+ return;
+ }
+ }
+ }
+ }
+
+ console.log("Action: ", selectedAction);
+ selectedAction.authentication_id = authenticationOption.id;
+ selectedAction.selectedAuthentication = authenticationOption;
+ if (
+ selectedAction.authentication === undefined ||
+ selectedAction.authentication === null
+ ) {
+ selectedAction.authentication = [authenticationOption];
+ } else {
+ selectedAction.authentication.push(authenticationOption);
+ }
+
+ setSelectedAction(selectedAction);
+
+ var newAuthOption = JSON.parse(JSON.stringify(authenticationOption));
+ var newFields = [];
+ for (const key in newAuthOption.fields) {
+ const value = newAuthOption.fields[key];
+ newFields.push({
+ key: key,
+ value: value,
+ });
+ }
+
+ console.log("FIELDS: ", newFields);
+ newAuthOption.fields = newFields;
+ setNewAppAuth(newAuthOption);
+ //appAuthentication.push(newAuthOption)
+ //setAppAuthentication(appAuthentication)
+ //
+
+ //if (configureWorkflowModalOpen) {
+ // setSelectedAction({})
+ //}
+ //setUpdate(authenticationOption.id)
+
+ /*
{selectedAction.authentication.map(data => (
*/
-
- }
+ };
const handleScopeChange = (event) => {
const {
target: { value },
- } = event;
+ } = event;
- console.log("VALUE: ", value)
+ console.log("VALUE: ", value);
// On autofill we get a the stringified value.
- setSelectedScopes(typeof value === 'string' ? value.split(',') : value)
+ setSelectedScopes(typeof value === "string" ? value.split(",") : value);
+ };
+
+ if (
+ authenticationOption.label === null ||
+ authenticationOption.label === undefined
+ ) {
+ authenticationOption.label = selectedApp.name + " authentication";
}
-
- if (authenticationOption.label === null || authenticationOption.label === undefined) {
- authenticationOption.label = selectedApp.name+" authentication"
- }
-
- //console.log(
- return (
-
-
Authentication for {selectedApp.name}
-
-
- Oauth2 requires a client ID and secret to authenticate. This is usually made in the remote system.
- Learn more about Oauth2 with Shuffle
-
- {/*
+
+
+ Authentication for {selectedApp.name}
+
+
+
+
+ Oauth2 requires a client ID and secret to authenticate, defined in the remote system. Your redirect URL is https://shuffler.io/set_authentication .
+
+ {" "}
+ Learn more about Oauth2 with Shuffle
+
+
+
+ {/* {
*/}
- {!manuallyConfigure ? null :
-
- {selectedApp.authentication.parameters.map((data, index) => {
- //console.log(data, index)
- if (data.name === "client_id" || data.name === "client_secret") {
- return null
- }
+ {!manuallyConfigure ? null : (
+
+ {selectedApp.authentication.parameters.map((data, index) => {
+ //console.log(data, index)
+ if (data.name === "client_id" || data.name === "client_secret") {
+ return null;
+ }
- if (data.name !== "url") {
- return null
- }
+ if (data.name !== "url") {
+ return null;
+ }
- if (oauthUrl.length === 0) {
- setOauthUrl(data.value)
- }
+ if (oauthUrl.length === 0) {
+ setOauthUrl(data.value);
+ }
- return (
-
-
-
{data.name}
+ return (
+
+
+ {data.name}
- {data.schema !== undefined && data.schema !== null && data.schema.type === "bool" ?
- {
- console.log("Value: ", e.target.value)
- authenticationOption.fields[data.name] = e.target.value
- }}
- style={{backgroundColor: theme.palette.surfaceColor, color: "white", height: 50}}
- >
-
- false
-
-
- true
-
-
- :
- {
- authenticationOption.fields[data.name] = event.target.value
- console.log("Setting oauth url")
- setOauthUrl(event.target.value)
- //const [oauthUrl, setOauthUrl] = React.useState("")
- }}
- />
- }
-
- )
- })}
- {allscopes.length === 0 ? null :
-
{
- handleScopeChange(e)
- }}
- fullWidth
- input={ }
- renderValue={(selected) => selected.join(', ')}
- MenuProps={MenuProps}
- >
- {allscopes.map((data, index) => {
- return (
-
- -1} />
-
-
- )
- })}
-
- }
-
{
- setClientId(event.target.value)
- //authenticationOption.label = event.target.value
- }}
- />
- {
- setClientSecret(event.target.value)
- //authenticationOption.label = event.target.value
- }}
- />
-
- }
- {
- handleOauth2Request(clientId, clientSecret, oauthUrl, selectedScopes)
- }}
- color="primary"
- >
- {buttonClicked ?
-
- :
- "Oauth2 request"
- }
-
+ {data.schema !== undefined &&
+ data.schema !== null &&
+ data.schema.type === "bool" ? (
+ {
+ console.log("Value: ", e.target.value);
+ authenticationOption.fields[data.name] = e.target.value;
+ }}
+ style={{
+ backgroundColor: theme.palette.surfaceColor,
+ color: "white",
+ height: 50,
+ }}
+ >
+
+ false
+
+
+ true
+
+
+ ) : (
+ {
+ authenticationOption.fields[data.name] =
+ event.target.value;
+ console.log("Setting oauth url");
+ setOauthUrl(event.target.value);
+ //const [oauthUrl, setOauthUrl] = React.useState("")
+ }}
+ />
+ )}
+
+ );
+ })}
+ {
+ setClientId(event.target.value);
+ //authenticationOption.label = event.target.value
+ }}
+ />
+ {
+ setClientSecret(event.target.value);
+ //authenticationOption.label = event.target.value
+ }}
+ />
+ {allscopes.length === 0 ? null : (
+
+
+ Scopes
+ {
+ handleScopeChange(e)
+ }}
+ fullWidth
+ input={ }
+ renderValue={(selected) => selected.join(", ")}
+ MenuProps={MenuProps}
+ >
+ {allscopes.map((data, index) => {
+ return (
+
+ -1} />
+
+
+ );
+ })}
+
+
+
+
+ {
+ setOfflineAccess(!offlineAccess)
+ }}/>
+
+
+
+ )}
+
+ )}
+ {
+ handleOauth2Request(
+ clientId,
+ clientSecret,
+ oauthUrl,
+ selectedScopes
+ );
+ }}
+ color="primary"
+ >
+ {buttonClicked ? (
+
+ ) : (
+ "Oauth2 request"
+ )}
+
- {defaultConfigSet ?
-
- ... or
- {
- setManuallyConfigure(!manuallyConfigure)
+ {defaultConfigSet ? (
+
+ ... or
+ {
+ setManuallyConfigure(!manuallyConfigure);
- if (manuallyConfigure) {
- setClientId(authenticationType.client_id)
- setClientSecret(authenticationType.client_secret)
- } else {
- setClientId("")
- setClientSecret("")
- }
- }}
- color="primary"
- >
- {manuallyConfigure ? "Use auto-config" : "Manually configure Oauth2"}
-
-
- :
- null
- }
-
-
- )
-}
+ if (manuallyConfigure) {
+ setClientId(authenticationType.client_id);
+ setClientSecret(authenticationType.client_secret);
+ } else {
+ setClientId("");
+ setClientSecret("");
+ }
+ }}
+ color="primary"
+ >
+ {manuallyConfigure
+ ? "Use auto-config"
+ : "Manually configure Oauth2"}
+
+
+ ) : null}
+
+
+ );
+};
-export default AuthenticationOauth2
+export default AuthenticationOauth2;
diff --git a/frontend/src/components/OrgHeader.js b/frontend/src/components/OrgHeader.js
deleted file mode 100644
index 65dd6e97..00000000
--- a/frontend/src/components/OrgHeader.js
+++ /dev/null
@@ -1,432 +0,0 @@
-import React, { useEffect} from 'react';
-import { makeStyles } from '@material-ui/styles';
-import { useTheme } from '@material-ui/core/styles';
-
-import Tooltip from '@material-ui/core/Tooltip';
-import Grid from '@material-ui/core/Grid';
-import Button from '@material-ui/core/Button';
-import TextField from '@material-ui/core/TextField';
-import Typography from '@material-ui/core/Typography';
-import { useAlert } from "react-alert";
-import IconButton from '@material-ui/core/IconButton';
-import ExpandLessIcon from '@material-ui/icons/ExpandLess';
-import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
-import SaveIcon from '@material-ui/icons/Save';
-
-const useStyles = makeStyles({
- notchedOutline: {
- borderColor: "#f85a3e !important"
- },
-})
-
-const defaultImage = "data:image/png;base64,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"
-
-const OrgHeader = (props) => {
- const {userdata, selectedOrganization, setSelectedOrganization, globalUrl} = props
-
- const theme = useTheme()
- const alert = useAlert()
- const classes = useStyles()
-
- var upload = ""
- const defaultBranch = "master"
- const [orgName, setOrgName] = React.useState(selectedOrganization.name)
- const [orgDescription, setOrgDescription] = React.useState(selectedOrganization.description)
- const [appDownloadUrl, setAppDownloadUrl] = React.useState(selectedOrganization.defaults === undefined ? "https://github.com/frikky/shuffle-apps" : selectedOrganization.defaults.app_download_repo === undefined || selectedOrganization.defaults.app_download_repo.length === 0 ? "https://github.com/frikky/shuffle-apps" : selectedOrganization.defaults.app_download_repo)
- const [appDownloadBranch, setAppDownloadBranch] = React.useState(selectedOrganization.defaults === undefined ? defaultBranch : selectedOrganization.defaults.app_download_branch === undefined || selectedOrganization.defaults.app_download_branch.length === 0 ? defaultBranch : selectedOrganization.defaults.app_download_branch)
- const [workflowDownloadUrl, setWorkflowDownloadUrl] = React.useState(selectedOrganization.defaults === undefined ? "https://github.com/frikky/shuffle-apps" : selectedOrganization.defaults.workflow_download_repo === undefined || selectedOrganization.defaults.workflow_download_repo.length === 0 ? "https://github.com/frikky/shuffle-workflows" : selectedOrganization.defaults.workflow_download_repo)
- const [workflowDownloadBranch, setWorkflowDownloadBranch] = React.useState(selectedOrganization.defaults === undefined ? defaultBranch : selectedOrganization.defaults.workflow_download_branch === undefined || selectedOrganization.defaults.workflow_download_branch.length === 0 ? defaultBranch : selectedOrganization.defaults.workflow_download_branch)
- const [ssoEntrypoint, setSsoEntrypoint] = React.useState(selectedOrganization.sso_config === undefined ? "" : selectedOrganization.sso_config.sso_entrypoint === undefined || selectedOrganization.sso_config.sso_entrypoint.length === 0 ? "" : selectedOrganization.sso_config.sso_entrypoint)
- const [ssoCertificate, setSsoCertificate] = React.useState(selectedOrganization.sso_config === undefined ? "" : selectedOrganization.sso_config.sso_certificate === undefined || selectedOrganization.sso_config.sso_certificate.length === 0 ? "" : selectedOrganization.sso_config.sso_certificate)
-
- const [file, setFile] = React.useState("")
- const [fileBase64, setFileBase64] = React.useState(selectedOrganization.image)
- const [expanded, setExpanded] = React.useState(false)
-
- if (file !== "") {
- const img = document.getElementById('logo')
- var canvas = document.createElement('canvas')
- canvas.width = 174
- canvas.height = 174
- var ctx = canvas.getContext('2d')
-
- img.onload = function() {
- // img, x, y, width, height
- //ctx.drawImage(img, 174, 174)
- //console.log("IMG natural: ", img.naturalWidth, img.naturalHeight)
- //ctx.drawImage(img, 0, 0, 174, 174)
- ctx.drawImage(img,
- 0, 0, img.width, img.height,
- 0, 0, canvas.width, canvas.height
- )
-
- const canvasUrl = canvas.toDataURL()
- if (canvasUrl !== fileBase64) {
- setFileBase64(canvasUrl)
- selectedOrganization.image = canvasUrl
- setSelectedOrganization(selectedOrganization)
- }
- }
- }
-
- const handleEditOrg = (name, description, orgId, image, defaults, sso_config) => {
- const data = {
- "name": name,
- "description": description,
- "org_id": orgId,
- "image": image,
- "defaults": defaults,
- "sso_config": sso_config,
- }
-
- 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 => {
- if (responseJson["success"] === false) {
- alert.error("Failed updating org: ", responseJson.reason)
- } else {
- alert.success("Successfully edited org!")
- }
- }),
- )
- .catch(error => {
- alert.error("Err: " + error.toString())
- });
- }
-
- var image = ""
- const editHeaderImage = (event) => {
- const file = event.target.value
- const actualFile = event.target.files[0]
- const fileObject = URL.createObjectURL(actualFile)
- setFile(fileObject)
- }
-
- console.log("USER: ", userdata)
- const orgSaveButton =
-
- handleEditOrg(orgName, orgDescription, selectedOrganization.id, selectedOrganization.image, {
- "app_download_repo": appDownloadUrl,
- "app_download_branch": appDownloadBranch,
- "workflow_download_repo": workflowDownloadUrl,
- "workflow_download_branch": workflowDownloadBranch,
- },
- {
- "sso_entrypoint": ssoEntrypoint,
- "sso_certificate": ssoCertificate,
- })}
- >
-
-
-
-
- var imageData = file.length > 0 ? file : fileBase64
- imageData = imageData === undefined || imageData.length === 0 ? defaultImage : imageData
- const imageInfo =
- return (
-
-
-
- 0 ? null : "1px solid #f85a3e", cursor: "pointer", backgroundColor: imageData !== undefined && imageData.length > 0 ? null : theme.palette.inputColor, maxWidth: 174, maxHeight: 174}} onClick={() => {upload.click()}}>
- upload = ref} onChange={editHeaderImage} />
- {imageInfo}
-
-
-
-
- Name
-
{
- const invalid = ["#", ":", "."]
- for (var key in invalid) {
- if (e.target.value.includes(invalid[key])) {
- alert.error("Can't use "+invalid[key]+" in name")
- return
- }
- }
-
- if (e.target.value.length > 100) {
- alert.error("Choose a shorter name.")
- return
- }
-
- setOrgName(e.target.value)
- }}
- color="primary"
- InputProps={{
- style:{
- color: "white",
- height: "50px",
- fontSize: "1em",
- },
- classes: {
- notchedOutline: classes.notchedOutline,
- },
- }}
- />
-
- Description
-
-
{
- setOrgDescription(e.target.value)
- }}
- InputProps={{
- classes: {
- notchedOutline: classes.notchedOutline,
- },
- style:{
- color: "white",
- },
- }}
- />
-
- {orgSaveButton}
-
-
-
-
-
- {
- setExpanded(!expanded)
- }}>
- {expanded ?
-
- :
-
- }
-
- {expanded ?
-
-
-
-
- App Download URL
-
- {
- setAppDownloadUrl(e.target.value)
- }}
- InputProps={{
- classes: {
- notchedOutline: classes.notchedOutline,
- },
- style:{
- color: "white",
- },
- }}
- />
-
-
-
-
-
- App Download Branch
-
- {
- setAppDownloadBranch(e.target.value)
- }}
- InputProps={{
- classes: {
- notchedOutline: classes.notchedOutline,
- },
- style:{
- color: "white",
- },
- }}
- />
-
-
-
-
-
- Workflow Download URL
-
- {
- setWorkflowDownloadUrl(e.target.value)
- }}
- InputProps={{
- classes: {
- notchedOutline: classes.notchedOutline,
- },
- style:{
- color: "white",
- },
- }}
- />
-
-
-
-
-
- Workflow Download Branch
-
- {
- setWorkflowDownloadBranch(e.target.value)
- }}
- InputProps={{
- classes: {
- notchedOutline: classes.notchedOutline,
- },
- style:{
- color: "white",
- },
- }}
- />
-
-
-
-
-
- SSO Entrypoint (IdP)
-
- 0}
- id="outlined-with-placeholder"
- margin="normal"
- variant="outlined"
- placeholder="The entrypoint URL from your provider"
- value={ssoEntrypoint}
- onChange={e => {
- setSsoEntrypoint(e.target.value)
- }}
- InputProps={{
- classes: {
- notchedOutline: classes.notchedOutline,
- },
- style:{
- color: "white",
- },
- }}
- />
-
-
-
-
-
- SSO Certificate (X509)
-
- {
- setSsoCertificate(e.target.value)
- }}
- InputProps={{
- classes: {
- notchedOutline: classes.notchedOutline,
- },
- style:{
- color: "white",
- },
- }}
- />
-
-
- {/*
-
- {expanded ?
-
- :
-
- }
-
- */}
-
- :
- null
- }
-
-
- )
-}
-
-export default OrgHeader
diff --git a/frontend/src/components/OrgHeader.jsx b/frontend/src/components/OrgHeader.jsx
new file mode 100644
index 00000000..a370af93
--- /dev/null
+++ b/frontend/src/components/OrgHeader.jsx
@@ -0,0 +1,789 @@
+import React, { useEffect } from "react";
+import { makeStyles } from "@material-ui/styles";
+import { useTheme } from "@material-ui/core/styles";
+
+import Tooltip from "@material-ui/core/Tooltip";
+import Grid from "@material-ui/core/Grid";
+import Button from "@material-ui/core/Button";
+import TextField from "@material-ui/core/TextField";
+import Typography from "@material-ui/core/Typography";
+import { useAlert } from "react-alert";
+import IconButton from "@material-ui/core/IconButton";
+import ExpandLessIcon from "@material-ui/icons/ExpandLess";
+import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
+import SaveIcon from "@material-ui/icons/Save";
+
+const useStyles = makeStyles({
+ notchedOutline: {
+ borderColor: "#f85a3e !important",
+ },
+});
+
+const defaultImage =
+ "data:image/png;base64,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";
+
+const OrgHeader = (props) => {
+ const {
+ userdata,
+ selectedOrganization,
+ setSelectedOrganization,
+ globalUrl,
+ isCloud,
+ } = props;
+
+ const theme = useTheme();
+ const alert = useAlert();
+ const classes = useStyles();
+
+ var upload = "";
+ const defaultBranch = "master";
+ const [orgName, setOrgName] = React.useState(selectedOrganization.name);
+ const [orgDescription, setOrgDescription] = React.useState(
+ selectedOrganization.description
+ );
+ const [appDownloadUrl, setAppDownloadUrl] = React.useState(
+ selectedOrganization.defaults === undefined
+ ? "https://github.com/frikky/shuffle-apps"
+ : selectedOrganization.defaults.app_download_repo === undefined ||
+ selectedOrganization.defaults.app_download_repo.length === 0
+ ? "https://github.com/frikky/shuffle-apps"
+ : selectedOrganization.defaults.app_download_repo
+ );
+ const [appDownloadBranch, setAppDownloadBranch] = React.useState(
+ selectedOrganization.defaults === undefined
+ ? defaultBranch
+ : selectedOrganization.defaults.app_download_branch === undefined ||
+ selectedOrganization.defaults.app_download_branch.length === 0
+ ? defaultBranch
+ : selectedOrganization.defaults.app_download_branch
+ );
+ const [workflowDownloadUrl, setWorkflowDownloadUrl] = React.useState(
+ selectedOrganization.defaults === undefined
+ ? "https://github.com/frikky/shuffle-apps"
+ : selectedOrganization.defaults.workflow_download_repo === undefined ||
+ selectedOrganization.defaults.workflow_download_repo.length === 0
+ ? "https://github.com/frikky/shuffle-workflows"
+ : selectedOrganization.defaults.workflow_download_repo
+ );
+ const [workflowDownloadBranch, setWorkflowDownloadBranch] = React.useState(
+ selectedOrganization.defaults === undefined
+ ? defaultBranch
+ : selectedOrganization.defaults.workflow_download_branch === undefined ||
+ selectedOrganization.defaults.workflow_download_branch.length === 0
+ ? defaultBranch
+ : selectedOrganization.defaults.workflow_download_branch
+ );
+ const [ssoEntrypoint, setSsoEntrypoint] = React.useState(
+ selectedOrganization.sso_config === undefined
+ ? ""
+ : selectedOrganization.sso_config.sso_entrypoint === undefined ||
+ selectedOrganization.sso_config.sso_entrypoint.length === 0
+ ? ""
+ : selectedOrganization.sso_config.sso_entrypoint
+ );
+ const [ssoCertificate, setSsoCertificate] = React.useState(
+ selectedOrganization.sso_config === undefined
+ ? ""
+ : selectedOrganization.sso_config.sso_certificate === undefined ||
+ selectedOrganization.sso_config.sso_certificate.length === 0
+ ? ""
+ : selectedOrganization.sso_config.sso_certificate
+ );
+ const [notificationWorkflow, setNotificationWorkflow] = React.useState(
+ selectedOrganization.defaults === undefined
+ ? ""
+ : selectedOrganization.defaults.notification_workflow === undefined ||
+ selectedOrganization.defaults.notification_workflow.length === 0
+ ? ""
+ : selectedOrganization.defaults.notification_workflow
+ );
+ const [openidClientId, setOpenidClientId] = React.useState(
+ selectedOrganization.sso_config === undefined
+ ? ""
+ : selectedOrganization.sso_config.client_id === undefined ||
+ selectedOrganization.sso_config.client_id.length === 0
+ ? ""
+ : selectedOrganization.sso_config.client_id
+ );
+ const [openidAuthorization, setOpenidAuthorization] = React.useState(
+ selectedOrganization.sso_config === undefined
+ ? ""
+ : selectedOrganization.sso_config.openid_authorization === undefined ||
+ selectedOrganization.sso_config.openid_authorization.length === 0
+ ? ""
+ : selectedOrganization.sso_config.openid_authorization
+ );
+ const [openidToken, setOpenidToken] = React.useState(
+ selectedOrganization.sso_config === undefined
+ ? ""
+ : selectedOrganization.sso_config.openid_token === undefined ||
+ selectedOrganization.sso_config.openid_token.length === 0
+ ? ""
+ : selectedOrganization.sso_config.openid_token
+ )
+
+ const [file, setFile] = React.useState("");
+ const [fileBase64, setFileBase64] = React.useState(
+ selectedOrganization.image
+ );
+ const [expanded, setExpanded] = React.useState(false);
+
+ if (file !== "") {
+ const img = document.getElementById("logo");
+ var canvas = document.createElement("canvas");
+ canvas.width = 174;
+ canvas.height = 174;
+ var ctx = canvas.getContext("2d");
+
+ img.onload = function () {
+ // img, x, y, width, height
+ //ctx.drawImage(img, 174, 174)
+ //console.log("IMG natural: ", img.naturalWidth, img.naturalHeight)
+ //ctx.drawImage(img, 0, 0, 174, 174)
+ ctx.drawImage(
+ img,
+ 0,
+ 0,
+ img.width,
+ img.height,
+ 0,
+ 0,
+ canvas.width,
+ canvas.height
+ );
+
+ const canvasUrl = canvas.toDataURL();
+ if (canvasUrl !== fileBase64) {
+ setFileBase64(canvasUrl);
+ selectedOrganization.image = canvasUrl;
+ setSelectedOrganization(selectedOrganization);
+ }
+ };
+ }
+
+ const handleEditOrg = (
+ name,
+ description,
+ orgId,
+ image,
+ defaults,
+ sso_config
+ ) => {
+
+ const data = {
+ name: name,
+ description: description,
+ org_id: orgId,
+ image: image,
+ defaults: defaults,
+ sso_config: sso_config,
+ };
+
+ 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) => {
+ if (responseJson["success"] === false) {
+ alert.error("Failed updating org: ", responseJson.reason);
+ } else {
+ alert.success("Successfully edited org!");
+ }
+ })
+ )
+ .catch((error) => {
+ alert.error("Err: " + error.toString());
+ });
+ };
+
+ var image = "";
+ const editHeaderImage = (event) => {
+ const file = event.target.value;
+ const actualFile = event.target.files[0];
+ const fileObject = URL.createObjectURL(actualFile);
+ setFile(fileObject);
+ };
+
+ //console.log("USER: ", userdata)
+ const orgSaveButton = (
+
+
+ handleEditOrg(
+ orgName,
+ orgDescription,
+ selectedOrganization.id,
+ selectedOrganization.image,
+ {
+ app_download_repo: appDownloadUrl,
+ app_download_branch: appDownloadBranch,
+ workflow_download_repo: workflowDownloadUrl,
+ workflow_download_branch: workflowDownloadBranch,
+ notification_workflow: notificationWorkflow,
+ },
+ {
+ sso_entrypoint: ssoEntrypoint,
+ sso_certificate: ssoCertificate,
+ client_id: openidClientId,
+ openid_authorization: openidAuthorization,
+ openid_token: openidToken,
+ }
+ )
+ }
+ >
+
+
+
+ );
+
+ var imageData = file.length > 0 ? file : fileBase64;
+ imageData =
+ imageData === undefined || imageData.length === 0
+ ? defaultImage
+ : imageData;
+ const imageInfo = (
+
+ );
+ return (
+
+
+
+ 0
+ ? null
+ : "1px solid #f85a3e",
+ cursor: "pointer",
+ backgroundColor:
+ imageData !== undefined && imageData.length > 0
+ ? null
+ : theme.palette.inputColor,
+ maxWidth: 174,
+ maxHeight: 174,
+ }}
+ onClick={() => {
+ upload.click();
+ }}
+ >
+ (upload = ref)}
+ onChange={editHeaderImage}
+ />
+ {imageInfo}
+
+
+
+
+ Name
+
{
+ const invalid = ["#", ":", "."];
+ for (var key in invalid) {
+ if (e.target.value.includes(invalid[key])) {
+ alert.error("Can't use " + invalid[key] + " in name");
+ return;
+ }
+ }
+
+ if (e.target.value.length > 100) {
+ alert.error("Choose a shorter name.");
+ return;
+ }
+
+ setOrgName(e.target.value);
+ }}
+ color="primary"
+ InputProps={{
+ style: {
+ color: "white",
+ height: "50px",
+ fontSize: "1em",
+ },
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ }}
+ />
+
+ Description
+
+
{
+ setOrgDescription(e.target.value);
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+ {orgSaveButton}
+
+
+
+
+
+ {
+ setExpanded(!expanded);
+ }}
+ >
+ {expanded ? : }
+
+ {expanded ? (
+
+
+
+ Notification Workflow ID
+ {
+ setNotificationWorkflow(e.target.value);
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+
+ {isCloud ? null : (
+
+
+ App Download URL
+ {
+ setAppDownloadUrl(e.target.value);
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+
+ )}
+ {isCloud ? null : (
+
+
+ App Download Branch
+ {
+ setAppDownloadBranch(e.target.value);
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+
+ )}
+ {isCloud ? null : (
+
+
+ Workflow Download URL
+ {
+ setWorkflowDownloadUrl(e.target.value);
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+
+ )}
+ {isCloud ? null : (
+
+
+ Workflow Download Branch
+ {
+ setWorkflowDownloadBranch(e.target.value);
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+
+ )}
+ {isCloud ? null :
+
+ OpenID connect
+
+
+
+ Client ID
+ 0
+ }
+ id="outlined-with-placeholder"
+ margin="normal"
+ variant="outlined"
+ placeholder="The OpenID client ID from the identity provider"
+ value={openidClientId}
+ onChange={(e) => {
+ setOpenidClientId(e.target.value);
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+
+
+
+ Authorization URL
+ {
+ setOpenidAuthorization(e.target.value)
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+
+
+
+ Token URL
+ {
+ setOpenidToken(e.target.value)
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+
+
+
+ }
+ {isCloud ? null :
+
+ SAML SSO (v1.1)
+
+
+
+ SSO Entrypoint (IdP)
+ 0
+ }
+ id="outlined-with-placeholder"
+ margin="normal"
+ variant="outlined"
+ placeholder="The entrypoint URL from your provider"
+ value={ssoEntrypoint}
+ onChange={(e) => {
+ setSsoEntrypoint(e.target.value);
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+
+
+
+ SSO Certificate (X509)
+ {
+ setSsoCertificate(e.target.value);
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+
+
+
+ }
+ {/*
+
+ {expanded ?
+
+ :
+
+ }
+
+ */}
+
+ ) : null}
+
+
+ );
+};
+
+export default OrgHeader;
diff --git a/frontend/src/components/PaperComponent.jsx b/frontend/src/components/PaperComponent.jsx
new file mode 100644
index 00000000..76fb56ef
--- /dev/null
+++ b/frontend/src/components/PaperComponent.jsx
@@ -0,0 +1,19 @@
+import React, {useState, useEffect, useLayoutEffect} from 'react';
+
+import Draggable from "react-draggable";
+import {
+ Paper
+} from "@material-ui/core";
+
+const PaperComponent = (props) => {
+ return (
+
+
+
+ )
+}
+
+export default PaperComponent;
diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx
index ef635b6f..ed1481ed 100644
--- a/frontend/src/components/ParsedAction.jsx
+++ b/frontend/src/components/ParsedAction.jsx
@@ -1,47 +1,123 @@
-import React, {useState, useEffect, useLayoutEffect} from 'react';
-import { makeStyles, createStyles } from '@material-ui/core/styles';
+import React, { useState, useEffect, useLayoutEffect } from "react";
+import { makeStyles, createStyles } from "@material-ui/core/styles";
+import { validateJson, GetIconInfo } from "../views/Workflows.jsx";
import { GetParsedPaths } from "../views/Apps.jsx";
-import { GetIconInfo } from "../views/Workflows.jsx";
import { sortByKey } from "../views/AngularWorkflow.jsx";
-import { useTheme } from '@material-ui/core/styles';
+import { useTheme } from "@material-ui/core/styles";
import NestedMenuItem from "material-ui-nested-menu-item";
+import { useAlert } from "react-alert";
+import theme from '../theme';
//import NestedMenuItem from "./NestedMenu.jsx";
-import {Popper, TextField, TextareaAutosize, Drawer, Button, Paper, Grid, Tabs, InputAdornment, Tab, ButtonBase, Tooltip, Select, MenuItem, Divider, Dialog, Modal, DialogActions, DialogTitle, InputLabel, DialogContent, FormControl, IconButton, Menu, Input, FormGroup, FormControlLabel, Typography, Checkbox, Breadcrumbs, CircularProgress, Switch, Fade} from '@material-ui/core';
-import {HelpOutline as HelpOutlineIcon, Description as DescriptionIcon, GetApp as GetAppIcon, Search as SearchIcon, ArrowUpward as ArrowUpwardIcon, Visibility as VisibilityIcon, Done as DoneIcon, Close as CloseIcon, Error as ErrorIcon, FindReplace as FindreplaceIcon, ArrowLeft as ArrowLeftIcon, Cached as CachedIcon, DirectionsRun as DirectionsRunIcon, Add as AddIcon, Polymer as PolymerIcon, FormatListNumbered as FormatListNumberedIcon, Create as CreateIcon, PlayArrow as PlayArrowIcon, AspectRatio as AspectRatioIcon, MoreVert as MoreVertIcon, Apps as AppsIcon, Schedule as ScheduleIcon, FavoriteBorder as FavoriteBorderIcon, Pause as PauseIcon, Delete as DeleteIcon, AddCircleOutline as AddCircleOutlineIcon, Save as SaveIcon, KeyboardArrowLeft as KeyboardArrowLeftIcon, KeyboardArrowRight as KeyboardArrowRightIcon, ArrowBack as ArrowBackIcon, Settings as SettingsIcon, LockOpen as LockOpenIcon, ExpandMore as ExpandMoreIcon, VpnKey as VpnKeyIcon} from '@material-ui/icons';
-import Autocomplete from '@material-ui/lab/Autocomplete';
+import {
+ ButtonGroup,
+ Popper,
+ TextField,
+ TextareaAutosize,
+ Drawer,
+ Button,
+ Paper,
+ Tabs,
+ InputAdornment,
+ Tab,
+ ButtonBase,
+ Tooltip,
+ Select,
+ MenuItem,
+ Divider,
+ Dialog,
+ Modal,
+ DialogActions,
+ DialogTitle,
+ InputLabel,
+ DialogContent,
+ FormControl,
+ IconButton,
+ Menu,
+ Input,
+ FormGroup,
+ FormControlLabel,
+ Typography,
+ Checkbox,
+ Breadcrumbs,
+ CircularProgress,
+ Switch,
+ Fade,
+} from "@material-ui/core";
-import CodeMirror from '@uiw/react-codemirror';
-import 'codemirror/keymap/sublime';
-import 'codemirror/theme/gruvbox-dark.css';
+import {
+ HelpOutline as HelpOutlineIcon,
+ Description as DescriptionIcon,
+ GetApp as GetAppIcon,
+ Search as SearchIcon,
+ ArrowUpward as ArrowUpwardIcon,
+ Visibility as VisibilityIcon,
+ Done as DoneIcon,
+ Close as CloseIcon,
+ Error as ErrorIcon,
+ FindReplace as FindreplaceIcon,
+ ArrowLeft as ArrowLeftIcon,
+ Cached as CachedIcon,
+ DirectionsRun as DirectionsRunIcon,
+ Add as AddIcon,
+ Polymer as PolymerIcon,
+ FormatListNumbered as FormatListNumberedIcon,
+ Create as CreateIcon,
+ PlayArrow as PlayArrowIcon,
+ AspectRatio as AspectRatioIcon,
+ MoreVert as MoreVertIcon,
+ Apps as AppsIcon,
+ Schedule as ScheduleIcon,
+ FavoriteBorder as FavoriteBorderIcon,
+ Pause as PauseIcon,
+ Delete as DeleteIcon,
+ AddCircleOutline as AddCircleOutlineIcon,
+ Circle as CircleIcon,
+ Save as SaveIcon,
+ KeyboardArrowLeft as KeyboardArrowLeftIcon,
+ KeyboardArrowRight as KeyboardArrowRightIcon,
+ ArrowBack as ArrowBackIcon,
+ Settings as SettingsIcon,
+ LockOpen as LockOpenIcon,
+ ExpandMore as ExpandMoreIcon,
+ VpnKey as VpnKeyIcon,
+ AutoFixHigh as AutoFixHighIcon,
+} from '@mui/icons-material';
+//} from "@material-ui/icons";
+import Autocomplete from "@material-ui/lab/Autocomplete";
+
+import CodeMirror from "@uiw/react-codemirror";
+import "codemirror/keymap/sublime";
+import "codemirror/theme/gruvbox-dark.css";
+import ShuffleCodeEditor from "../components/ShuffleCodeEditor.jsx";
const useStyles = makeStyles({
- notchedOutline: {
- borderColor: "#f85a3e !important"
- },
- root: {
- "& .MuiAutocomplete-listbox": {
- border: "2px solid grey",
- color: "white",
- fontSize: 18,
- "& li:nth-child(even)": {
- backgroundColor: "#CCC"
- },
- "& li:nth-child(odd)": {
- backgroundColor: "#FFF"
- }
- }
- },
- inputRoot: {
- color: "white",
- // This matches the specificity of the default styles at https://github.com/mui-org/material-ui/blob/v4.11.3/packages/material-ui-lab/src/Autocomplete/Autocomplete.js#L90
- "&:hover .MuiOutlinedInput-notchedOutline": {
- borderColor: "#f86a3e"
- },
- }
-})
+ notchedOutline: {
+ borderColor: "#f85a3e !important",
+ },
+ root: {
+ "& .MuiAutocomplete-listbox": {
+ border: "2px solid grey",
+ color: "white",
+ fontSize: 18,
+ "& li:nth-child(even)": {
+ backgroundColor: "#CCC",
+ },
+ "& li:nth-child(odd)": {
+ backgroundColor: "#FFF",
+ },
+ },
+ },
+ inputRoot: {
+ color: "white",
+ // This matches the specificity of the default styles at https://github.com/mui-org/material-ui/blob/v4.11.3/packages/material-ui-lab/src/Autocomplete/Autocomplete.js#L90
+ "&:hover .MuiOutlinedInput-notchedOutline": {
+ borderColor: "#f86a3e",
+ },
+ },
+});
//const useStyles = makeStyles((theme) =>
// createStyles({
// notchedOutline: {
@@ -49,304 +125,676 @@ const useStyles = makeStyles({
// },
//)
+const openApiFieldDesc = "Generated by OpenAPI body example";
const ParsedAction = (props) => {
- const {workflow, setWorkflow, setAction, setSelectedAction, setUpdate, appActionArguments, selectedApp, workflowExecutions, setSelectedResult, selectedAction, setSelectedApp, setSelectedTrigger, setSelectedEdge, setCurrentView, cy, setAuthenticationModalOpen,setVariablesModalOpen, setCodeModalOpen, selectedNameChange, rightsidebarStyle, showEnvironment, selectedActionEnvironment, environments, setNewSelectedAction, appApiViewStyle, globalUrl, setSelectedActionEnvironment, requiresAuthentication, hideExtraTypes, scrollConfig, setScrollConfig, authenticationType, appAuthentication, getAppAuthentication } = props
+ const {
+ workflow,
+ setWorkflow,
+ setAction,
+ setSelectedAction,
+ setUpdate,
+ appActionArguments,
+ selectedApp,
+ workflowExecutions,
+ setSelectedResult,
+ selectedAction,
+ setSelectedApp,
+ setSelectedTrigger,
+ setSelectedEdge,
+ setCurrentView,
+ cy,
+ setAuthenticationModalOpen,
+ setVariablesModalOpen,
+ setCodeModalOpen,
+ selectedNameChange,
+ rightsidebarStyle,
+ showEnvironment,
+ selectedActionEnvironment,
+ environments,
+ setNewSelectedAction,
+ appApiViewStyle,
+ globalUrl,
+ setSelectedActionEnvironment,
+ requiresAuthentication,
+ hideExtraTypes,
+ scrollConfig,
+ setScrollConfig,
+ authenticationType,
+ appAuthentication,
+ getAppAuthentication,
+ actionDelayChange,
+ getParents,
+ isCloud,
+ lastSaved,
+ setLastSaved,
+ } = props;
- const theme = useTheme();
- const classes = useStyles()
+ //const theme = useTheme();
+ const classes = useStyles();
+ const alert = useAlert()
- const [expansionModalOpen, setExpansionModalOpen] = React.useState(false);
+ const [expansionModalOpen, setExpansionModalOpen] = React.useState(false);
+ const [hideBody, setHideBody] = React.useState(true);
+ const [activateHidingBodyButton, setActivateHidingBodyButton] = React.useState(false);
- const keywords = ["len(", "lower(", "upper(", "trim(", "split(", "length(", "number(", "parse(", "join("]
- const getParents = (action) => {
- if (cy === undefined) {
- return []
- }
+ const [codedata, setcodedata] = React.useState("");
+ const [fieldCount, setFieldCount] = React.useState(0);
+ const [hiddenDescription, setHiddenDescription] = React.useState(true);
- var allkeys = [action.id]
- var handled = []
- var results = []
-
- while(true) {
- for (var key in allkeys) {
- var currentnode = cy.getElementById(allkeys[key])
- if (currentnode === undefined) {
- continue
- }
-
- if (handled.includes(currentnode.data("id"))) {
- continue
+ useEffect(() => {
+ if (selectedAction.parameters !== null && selectedAction.parameters !== undefined) {
+ const paramcheck = selectedAction.parameters.find(param => param.name === "body")
+ //console.log("LOADED! Change hideBody based on input? Action: ", selectedAction, paramcheck)
+ if (paramcheck !== undefined && paramcheck !== null) {
+ if (paramcheck.id === "TOGGLED"){
+ setHideBody(false)
+ setActivateHidingBodyButton(false)
+ console.log("TOGGLED BODY!")
} else {
- // Get the name / label here too?
- handled.push(currentnode.data("id"))
- results.push(currentnode.data())
- }
+ setHideBody(true)
- if (currentnode.length === 0) {
- continue
- }
-
- const incomingEdges = currentnode.incomers('edge')
- if (incomingEdges.length === 0) {
- continue
- }
-
- for (var i = 0; i < incomingEdges.length; i++) {
- var tmp = incomingEdges[i]
- if (!allkeys.includes(tmp.data().source)) {
- allkeys.push(tmp.data().source)
+ if (paramcheck.id === "UNTOGGLED") {
+ setActivateHidingBodyButton(false)
+ console.log("UNTOGGLED!")
}
}
}
- if (results.length === allkeys.length) {
- break
- }
}
+ }, [])
- // Some obscure bug made this have to be done.. zz
- results = results.filter(data => data !== undefined && action !== undefined && data.id !== action.id)
- results = results.filter(data => data !== undefined && data.type !== "TRIGGER")
- results.push({"label": "Execution Argument", "type": "INTERNAL"})
- return results
- }
+ const keywords = [
+ "len(",
+ "lower(",
+ "upper(",
+ "trim(",
+ "split(",
+ "length(",
+ "number(",
+ "parse(",
+ "join(",
+ ];
+
+ const getApp = (appId, setApp) => {
+ fetch(globalUrl + "/api/v1/apps/" + appId + "/config?openapi=false", {
+ headers: {
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status === 200) {
+ //alert.success("Successfully GOT app "+appId)
+ } else {
+ alert.error("Failed getting app");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ console.log("RESPONSE: ", responseJson);
+
+ const parsedapp =
+ responseJson.app !== undefined && responseJson.app !== null
+ ? JSON.parse(atob(responseJson.app))
+ : {};
+ console.log("PARSED: ", parsedapp);
+ //data = parsedapp.body === undefined ? parsedapp : parsedapp.body
+
+ if (
+ setApp &&
+ parsedapp.actions !== undefined &&
+ parsedapp.actions !== null
+ ) {
+ console.log("Inside first if");
+ if (
+ selectedApp.versions !== undefined &&
+ selectedApp.versions !== null
+ ) {
+ parsedapp.versions = selectedApp.versions;
+ }
+
+ if (
+ selectedApp.loop_versions !== undefined &&
+ selectedApp.loop_versions !== null
+ ) {
+ parsedapp.loop_versions = selectedApp.loop_versions;
+ }
+
+ // Find authentication, and if it works?
+ // If authentication has less OR more fields, it has to change
+ //console.log(selected
+
+ console.log("Inside first if2");
+ var foundAction = parsedapp.actions.find(
+ (action) =>
+ action.name.toLowerCase() === selectedAction.name.toLowerCase()
+ );
+ console.log("FOUNDACTION: ", foundAction);
+ if (foundAction !== null && foundAction !== undefined) {
+ var foundparams = [];
+ for (var paramkey in foundAction.parameters) {
+ const param = foundAction.parameters[paramkey];
+
+ const foundParam = selectedAction.parameters.find(
+ (item) => item.name.toLowerCase() === param.name.toLowerCase()
+ );
+ if (foundParam === undefined) {
+ console.log("COULDNT find Param: ", param);
+ } else {
+ foundAction.parameters[paramkey] = foundParam;
+ }
+ //foundparams.push(param.name)
+ }
+ } else {
+ alert.error("Couldn't find action " + selectedAction.name);
+ }
+
+ selectedAction.errors = [];
+ selectedAction.is_valid = true;
+
+ // Updating params for the new action
+ selectedAction.parameters = foundAction.parameters;
+ selectedAction.app_id = appId;
+ selectedAction.app_version = parsedapp.app_version;
+
+ setSelectedAction(selectedAction);
+ setSelectedApp(parsedapp);
+ }
+ })
+ .catch((error) => {
+ alert.error(error.toString());
+ });
+ };
- const getApp = (appId, setApp) => {
- fetch(globalUrl+"/api/v1/apps/"+appId+"/config?openapi=false", {
- headers: {
- 'Accept': 'application/json',
- },
- credentials: "include",
- })
- .then((response) => {
- if (response.status === 200) {
- //alert.success("Successfully GOT app "+appId)
- } else {
- alert.error("Failed getting app")
- }
- return response.json()
- })
- .then((responseJson) => {
- console.log("RESPONSE: ", responseJson)
+ const defineStartnode = () => {
+ if (cy === undefined) {
+ return;
+ }
- const parsedapp = responseJson.app !== undefined && responseJson.app !== null ? JSON.parse(atob(responseJson.app)) : {}
- console.log("PARSED: ", parsedapp)
- //data = parsedapp.body === undefined ? parsedapp : parsedapp.body
+ var oldstartnode = cy.getElementById(workflow.start);
+ if (oldstartnode.length > 0) {
+ oldstartnode[0].data("isStartNode", false);
+ var oldnodecnt = workflow.actions.findIndex(
+ (a) => a.id === workflow.start
+ );
+ if (workflow.actions[oldnodecnt] !== undefined) {
+ workflow.actions[oldnodecnt].isStartNode = false;
+ }
+ }
- if (setApp && parsedapp.actions !== undefined && parsedapp.actions !== null) {
- console.log("Inside first if")
- if (selectedApp.versions !== undefined && selectedApp.versions !== null) {
- parsedapp.versions = selectedApp.versions
- }
+ var newstartnode = cy.getElementById(selectedAction.id);
+ if (newstartnode.length > 0) {
+ newstartnode[0].data("isStartNode", true);
+ var newnodecnt = workflow.actions.findIndex(
+ (a) => a.id === selectedAction.id
+ );
+ console.log("NEW NODE CNT: ", newnodecnt);
+ if (workflow.actions[newnodecnt] !== undefined) {
+ workflow.actions[newnodecnt].isStartNode = true;
+ console.log(workflow.actions[newnodecnt]);
+ }
+ }
- if (selectedApp.loop_versions !== undefined && selectedApp.loop_versions !== null) {
- parsedapp.loop_versions = selectedApp.loop_versions
- }
+ // Find branches with triggers as source nodes
+ // Move these targets to be the new node
+ // Set arrows pointing to new startnode with errors
+ //for (var key in workflow.branches) {
+ // var item = workflow.branches[key]
+ // if (item.destination_id === oldstartnode[0].data()["id"]) {
+ // var curbranch = cy.getElementById(item.id)
+ // if (curbranch.length > 0) {
+ // //console.log(curbranch[0].data())
+ // //curbranch[0].data("target", selectedAction.id)
+ // //curbranch[0].data("hasErrors", true)
+ // //workflow.branches[key].destination_id = selectedAction.id
+ // //console.log(curbranch[0].data())
+ // }
+ // }
+ //}
- // Find authentication, and if it works?
- // If authentication has less OR more fields, it has to change
- //console.log(selected
+ setUpdate("start_node" + selectedAction.id);
+ workflow.start = selectedAction.id;
+ setWorkflow(workflow);
+ //setStartNode(selectedAction.id)
+ };
- console.log("Inside first if2")
- var foundAction = parsedapp.actions.find(action => action.name.toLowerCase() === selectedAction.name.toLowerCase())
- console.log("FOUNDACTION: ", foundAction)
- if (foundAction !== null && foundAction !== undefined) {
- var foundparams = []
- for (var paramkey in foundAction.parameters) {
- const param = foundAction.parameters[paramkey]
+ const AppActionArguments = (props) => {
+ const [selectedActionParameters, setSelectedActionParameters] = React.useState([]);
+ const [selectedVariableParameter, setSelectedVariableParameter] = React.useState("");
+ const [actionlist, setActionlist] = React.useState([]);
+ const [jsonList, setJsonList] = React.useState([]);
+ const [showDropdown, setShowDropdown] = React.useState(false);
+ const [showDropdownNumber, setShowDropdownNumber] = React.useState(0);
+ const [showAutocomplete, setShowAutocomplete] = React.useState(false);
+ const [menuPosition, setMenuPosition] = useState(null);
- const foundParam = selectedAction.parameters.find(item => item.name.toLowerCase() === param.name.toLowerCase())
- if (foundParam === undefined) {
- console.log("COULDNT find Param: ", param)
- } else {
- foundAction.parameters[paramkey] = foundParam
- }
- //foundparams.push(param.name)
- }
- } else {
- alert.error("Couldn't find action "+selectedAction.name)
- }
+ useEffect(() => {
+ if (
+ selectedActionParameters !== null &&
+ selectedActionParameters.length === 0
+ ) {
+ if (
+ selectedAction.parameters !== null &&
+ selectedAction.parameters.length > 0
+ ) {
+ setSelectedActionParameters(selectedAction.parameters);
+ }
+ }
+ if (
+ (selectedVariableParameter === null ||
+ selectedVariableParameter === undefined) &&
+ workflow.workflow_variables !== null &&
+ workflow.workflow_variables.length > 0
+ ) {
+ // FIXME - this is the bad thing
+ setSelectedVariableParameter(workflow.workflow_variables[0].name);
+ }
- selectedAction.errors = []
- selectedAction.is_valid = true
-
- // Updating params for the new action
- selectedAction.parameters = foundAction.parameters
- selectedAction.app_id = appId
- selectedAction.app_version = parsedapp.app_version
-
- setSelectedAction(selectedAction)
- setSelectedApp(parsedapp)
- }
- })
- .catch(error => {
- alert.error(error.toString())
- });
- }
-
- const defineStartnode = () => {
- if (cy === undefined) {
- return
- }
-
- var oldstartnode = cy.getElementById(workflow.start)
- if (oldstartnode.length > 0) {
- oldstartnode[0].data("isStartNode", false)
- var oldnodecnt = workflow.actions.findIndex(a => a.id === workflow.start)
- if (workflow.actions[oldnodecnt] !== undefined) {
- workflow.actions[oldnodecnt].isStartNode = false
- }
- }
-
- var newstartnode = cy.getElementById(selectedAction.id)
- if (newstartnode.length > 0) {
- newstartnode[0].data("isStartNode", true)
- var newnodecnt = workflow.actions.findIndex(a => a.id === selectedAction.id)
- console.log("NEW NODE CNT: ", newnodecnt)
- if (workflow.actions[newnodecnt] !== undefined) {
- workflow.actions[newnodecnt].isStartNode = true
- console.log(workflow.actions[newnodecnt])
- }
- }
-
- // Find branches with triggers as source nodes
- // Move these targets to be the new node
- // Set arrows pointing to new startnode with errors
- //for (var key in workflow.branches) {
- // var item = workflow.branches[key]
- // if (item.destination_id === oldstartnode[0].data()["id"]) {
- // var curbranch = cy.getElementById(item.id)
- // if (curbranch.length > 0) {
- // //console.log(curbranch[0].data())
- // //curbranch[0].data("target", selectedAction.id)
- // //curbranch[0].data("hasErrors", true)
- // //workflow.branches[key].destination_id = selectedAction.id
- // //console.log(curbranch[0].data())
- // }
- // }
- //}
-
- setUpdate("start_node"+selectedAction.id)
- workflow.start = selectedAction.id
- setWorkflow(workflow)
- //setStartNode(selectedAction.id)
- }
-
- const AppActionArguments = (props) => {
- const [selectedActionParameters, setSelectedActionParameters] = React.useState([])
- const [selectedVariableParameter, setSelectedVariableParameter] = React.useState("")
- const [actionlist, setActionlist] = React.useState([])
- const [jsonList, setJsonList] = React.useState([])
- const [showDropdown, setShowDropdown] = React.useState(false)
- const [showDropdownNumber, setShowDropdownNumber] = React.useState(0)
- const [showAutocomplete, setShowAutocomplete] = React.useState(false)
- const [menuPosition, setMenuPosition] = useState(null)
-
- useEffect(() => {
- if (selectedActionParameters !== null && selectedActionParameters.length === 0) {
- if (selectedAction.parameters !== null && selectedAction.parameters.length > 0) {
- setSelectedActionParameters(selectedAction.parameters)
- }
- }
-
- if ((selectedVariableParameter === null || selectedVariableParameter === undefined) && (workflow.workflow_variables !== null && workflow.workflow_variables.length > 0)) {
- // FIXME - this is the bad thing
- setSelectedVariableParameter(workflow.workflow_variables[0].name)
- }
-
- if (actionlist.length === 0) {
- // FIXME: Have previous execution values in here
- actionlist.push({"type": "Execution Argument", "name": "Execution Argument", "value": "$exec", "highlight": "exec", "autocomplete": "exec", "example": "hello"})
- if (workflow.workflow_variables !== null && workflow.workflow_variables !== undefined && workflow.workflow_variables.length > 0) {
- for (var key in workflow.workflow_variables) {
- const item = workflow.workflow_variables[key]
- actionlist.push({"type": "workflow_variable", "name": item.name, "value": item.value, "id": item.id, "autocomplete": `${item.name.split(" ").join("_")}`, "example": item.value})
- }
- }
-
- // FIXME: Add values from previous executions if they exist
- if (workflow.execution_variables !== null && workflow.execution_variables !== undefined && workflow.execution_variables.length > 0) {
- for (var key in workflow.execution_variables) {
- const item = workflow.execution_variables[key]
- actionlist.push({"type": "execution_variable", "name": item.name, "value": item.value, "id": item.id, "autocomplete": `${item.name.split(" ").join("_")}`, "example": ""})
- }
- }
-
- // Loops parent nodes' old results to fix autocomplete
- var parents = getParents(selectedAction)
- if (parents.length > 1) {
- for (var key in parents) {
- const item = parents[key]
- if (item.label === "Execution Argument") {
- continue
+ if (actionlist.length === 0) {
+ // FIXME: Have previous execution values in here
+ if (workflowExecutions.length > 0) {
+ for (var key in workflowExecutions) {
+ if (
+ workflowExecutions[key].execution_argument === undefined ||
+ workflowExecutions[key].execution_argument === null ||
+ workflowExecutions[key].execution_argument.length === 0
+ ) {
+ continue;
}
- var exampledata = item.example === undefined ? "" : item.example
- // Find previous execution and their variables
- //exampledata === "" &&
- if (workflowExecutions.length > 0) {
- // Look for the ID
- const found = false
- for (var key in workflowExecutions) {
- if (workflowExecutions[key].results === undefined || workflowExecutions[key].results === null) {
- continue
- }
+ const valid = validateJson(workflowExecutions[key].execution_argument)
+ if (valid.valid) {
+ actionlist.push({
+ type: "Execution Argument",
+ name: "Execution Argument",
+ value: "$exec",
+ highlight: "exec",
+ autocomplete: "exec",
+ example: valid.result,
+ })
+ break
+ }
+ }
- var foundResult = workflowExecutions[key].results.find(result => result.action.id === item.id)
- if (foundResult === undefined) {
- continue
- }
+ }
- foundResult.result = foundResult.result.trim()
- foundResult.result = foundResult.result.split(" None").join(" \"None\"")
- foundResult.result = foundResult.result.split(" False").join(" false")
- foundResult.result = foundResult.result.split(" True").join(" true")
+ if (actionlist.length === 0) {
+ actionlist.push({
+ type: "Execution Argument",
+ name: "Execution Argument",
+ value: "$exec",
+ highlight: "exec",
+ autocomplete: "exec",
+ example: "",
+ })
+ }
- var jsonvalid = true
- try {
- const tmp = String(JSON.parse(foundResult.result))
- if (!foundResult.result.includes("{") && !foundResult.result.includes("[")) {
- jsonvalid = false
+ actionlist.push({
+ type: "Shuffle DB",
+ name: "Shuffle DB",
+ value: "$shuffle_cache",
+ highlight: "shuffle_cache",
+ autocomplete: "shuffle_cache",
+ example: "",
+ });
+ if (
+ workflow.workflow_variables !== null &&
+ workflow.workflow_variables !== undefined &&
+ workflow.workflow_variables.length > 0
+ ) {
+ for (var key in workflow.workflow_variables) {
+ const item = workflow.workflow_variables[key];
+ actionlist.push({
+ type: "workflow_variable",
+ name: item.name,
+ value: item.value,
+ id: item.id,
+ autocomplete: `${item.name.split(" ").join("_")}`,
+ example: item.value,
+ });
+ }
+ }
+
+ // FIXME: Add values from previous executions if they exist
+ if (
+ workflow.execution_variables !== null &&
+ workflow.execution_variables !== undefined &&
+ workflow.execution_variables.length > 0
+ ) {
+ for (var key in workflow.execution_variables) {
+ const item = workflow.execution_variables[key];
+ actionlist.push({
+ type: "execution_variable",
+ name: item.name,
+ value: item.value,
+ id: item.id,
+ autocomplete: `${item.name.split(" ").join("_")}`,
+ example: "",
+ });
+ }
+ }
+
+ // Loops parent nodes' old results to fix autocomplete
+ if (getParents !== undefined) {
+ var parents = getParents(selectedAction);
+ if (parents.length > 1) {
+ for (var key in parents) {
+ const item = parents[key];
+ if (item.label === "Execution Argument") {
+ continue;
+ }
+
+ var exampledata = item.example === undefined || item.example === null ? "" : item.example;
+ // Find previous execution and their variables
+ //exampledata === "" &&
+ if (workflowExecutions.length > 0) {
+ // Look for the ID
+ const found = false;
+ for (var key in workflowExecutions) {
+ if (
+ workflowExecutions[key].results === undefined ||
+ workflowExecutions[key].results === null
+ ) {
+ continue;
+ }
+
+ var foundResult = workflowExecutions[key].results.find(
+ (result) => result.action.id === item.id
+ );
+ if (foundResult === undefined || foundResult === null) {
+ continue;
+ }
+
+ if (foundResult.result !== undefined && foundResult.result !== null) {
+ foundResult = foundResult.result
}
- } catch (e) {
- try {
- foundResult.result = foundResult.result.split("\'").join("\"")
- const tmp = String(JSON.parse(foundResult.result))
- if (!foundResult.result.includes("{") && !foundResult.result.includes("[")) {
- jsonvalid = false
- }
- } catch (e) {
- jsonvalid = false
- }
- }
- // Finds the FIRST json only
- if (jsonvalid) {
- exampledata = JSON.parse(foundResult.result)
- break
- }
- //else {
- // console.log("Invalid JSON: ", foundResult.result)
- //}
+ const valid = validateJson(foundResult)
+ if (valid.valid) {
+ exampledata = valid.result;
+ break;
+ } else {
+ exampledata = foundResult;
+ }
+ }
+ }
+
+ // 1. Take
+ const itemlabelComplete =
+ item.label === null || item.label === undefined
+ ? ""
+ : item.label.split(" ").join("_");
+
+ const actionvalue = {
+ type: "action",
+ id: item.id,
+ name: item.label,
+ autocomplete: itemlabelComplete,
+ example: exampledata,
+ };
+ actionlist.push(actionvalue);
+ }
+ }
+
+ //console.log("ACTIONLIST: ", actionlist)
+ setActionlist(actionlist);
+ }
+ }
+ });
+
+
+ const calculateHelpertext = (input_data) => {
+ var helperText = ""
+ var looperText = ""
+ const found = input_data.match(/[$]{1}([a-zA-Z0-9_-]+\.?){1}([a-zA-Z0-9#_-]+\.?){0,}/g)
+
+ if (found !== null) {
+ try {
+ // When the found array is empty.
+ for (var i = 0; i < found.length; i++) {
+ const variableSplit = found[i].split(".#")
+ if ((variableSplit.length-1) > 1) {
+ //console.log("Larger than 1: ", variableSplit)
+ if (looperText.length === 0) {
+ looperText += "PS: Double looping (.#) may cause problems."
}
}
- // 1. Take
- const itemlabelComplete = item.label === null || item.label === undefined ? "" : item.label.split(" ").join("_")
- const actionvalue = {"type": "action", "id": item.id, "name": item.label, "autocomplete": itemlabelComplete, "example": exampledata}
- actionlist.push(actionvalue)
+ var foundSlice = false
+ for (var j = 0; j < actionlist.length; j++) {
+ //console.log("ACTION: ", found[i], actionlist[j])
+ //console.log("ACTION :", found[i].split(".")[0].slice(1,).toLowerCase(), actionlist[j].autocomplete.toLowerCase())
+ if(found[i].split(".")[0].slice(1,).toLowerCase() == actionlist[j].autocomplete.toLowerCase()){
+ //console.log("Found: ", found[i])
+ // Validate path?
+
+ foundSlice = true
+ }
+ }
+
+ if (!foundSlice) {
+ if (!helperText.includes("Invalid variables")) {
+ helperText+= "Invalid variables: "
+ }
+ helperText+= found[i] + ", "
+ }
}
+ } catch (e) {
+ console.log("Parsing error: ", e)
+ }
+ }
+
+ if (looperText.length > 0) {
+ if (helperText.length > 0) {
+ helperText += ". "
}
- setActionlist(actionlist)
+ helperText += looperText
}
- })
- const changeActionParameter = (event, count, data) => {
- //console.log(event)
- if (data.name.startsWith("${") && data.name.endsWith("}")) {
+ return helperText
+ }
+
+ const changeActionParameter = (event, count, data) => {
+ //console.log("Action change: ", selectedAction, data)
+ if (data.name.startsWith("${") && data.name.endsWith("}")) {
+ // PARAM FIX - Gonna use the ID field, even though it's a hack
+ const paramcheck = selectedAction.parameters.find(
+ (param) => param.name === "body"
+ );
+ if (paramcheck !== undefined) {
+ // Escapes all double quotes
+ const toReplace = event.target.value
+ .trim()
+ .replaceAll('\\"', '"')
+ .replaceAll('"', '\\"');
+
+ console.log("REPLACE WITH: ", toReplace);
+ if (
+ paramcheck["value_replace"] === undefined ||
+ paramcheck["value_replace"] === null
+ ) {
+ paramcheck["value_replace"] = [
+ {
+ key: data.name,
+ value: toReplace,
+ },
+ ];
+
+ console.log("IN IF: ", paramcheck);
+ } else {
+ const subparamindex = paramcheck["value_replace"].findIndex(
+ (param) => param.key === data.name
+ );
+ if (subparamindex === -1) {
+ paramcheck["value_replace"].push({
+ key: data.name,
+ value: toReplace,
+ });
+ } else {
+ paramcheck["value_replace"][subparamindex]["value"] = toReplace;
+ }
+
+ console.log("IN ELSE: ", paramcheck);
+ }
+ //console.log("PARAM: ", paramcheck)
+ //if (paramcheck.id === undefined) {
+ // console.log("Normal paramcheck")
+ //} else {
+ // selectedActionParameters[count]["value_replace"] = paramcheck
+ // selectedAction.parameters[count]["value_replace"] = paramcheck
+ //}
+
+ if (paramcheck["value_replace"] === undefined) {
+ selectedActionParameters[count]["value_replace"] = paramcheck;
+ selectedAction.parameters[count]["value_replace"] = paramcheck;
+ } else {
+ selectedActionParameters[count]["value_replace"] =
+ paramcheck["value_replace"];
+ selectedAction.parameters[count]["value_replace"] =
+ paramcheck["value_replace"];
+ }
+ console.log("RESULT: ", selectedAction);
+ setSelectedAction(selectedAction);
+ //setUpdate(Math.random())
+ return;
+ }
+ }
+
+
+ if (event.target.value[event.target.value.length - 1] === "$") {
+ if (!showDropdown) {
+ setShowAutocomplete(false);
+ setShowDropdown(true);
+ setShowDropdownNumber(count);
+ }
+ } else {
+ if (showDropdown) {
+ setShowDropdown(false);
+ }
+ }
+
+ // bad detection mechanism probably
+ if (
+ event.target.value[event.target.value.length - 1] === "." &&
+ actionlist.length > 0
+ ) {
+ console.log("GET THE LAST ARGUMENT FOR NODE!");
+ // THIS IS AN EXAMPLE OF SHOWING IT
+ /*
+
+ const inputdata = {"data": "1.2.3.4", "dataType": "4.5.6.6"}
+ setJsonList(GetParsedPaths(inputdata, ""))
+ if (!showDropdown) {
+ setShowAutocomplete(false)
+ setShowDropdown(true)
+ setShowDropdownNumber(count)
+ }
+ console.log(jsonList)
+ */
+
+ // Search for the item backwards
+ // 1. Reverse search backwards from . -> $
+ // 2. Search the actionlist for the item
+ // 3. Find the data for the specific item
+
+ var curstring = "";
+ var record = false;
+ for (var key in selectedActionParameters[count].value) {
+ const item = selectedActionParameters[count].value[key];
+ if (record) {
+ curstring += item;
+ }
+
+ if (item === "$") {
+ record = true;
+ curstring = "";
+ }
+ }
+
+ //console.log("CURSTRING: ", curstring)
+ if (curstring.length > 0 && actionlist !== null) {
+ // Search back in the action list
+ curstring = curstring.split(" ").join("_").toLowerCase();
+ var actionItem = actionlist.find(
+ (data) =>
+ data.autocomplete.split(" ").join("_").toLowerCase() === curstring
+ );
+ if (actionItem !== undefined) {
+ console.log("Found item: ", actionItem);
+
+ //actionItem.example = actionItem.example.trim()
+ //actionItem.example = actionItem.example.split(" None").join(" \"None\"")
+ //actionItem.example = actionItem.example.split("\'").join("\"")
+
+ var jsonvalid = true;
+ try {
+ const tmp = String(JSON.parse(actionItem.example));
+ if (
+ !actionItem.example.includes("{") &&
+ !actionItem.example.includes("[")
+ ) {
+ jsonvalid = false;
+ }
+ } catch (e) {
+ jsonvalid = false;
+ }
+
+ if (jsonvalid) {
+ setJsonList(GetParsedPaths(JSON.parse(actionItem.example), ""));
+
+ if (!showDropdown) {
+ setShowAutocomplete(false);
+ setShowDropdown(true);
+ setShowDropdownNumber(count);
+ }
+ }
+ }
+ }
+ } else {
+ if (jsonList.length > 0) {
+ setJsonList([]);
+ }
+ }
+
+ //console.log("CHANGING ACTION COUNT !")
+ selectedActionParameters[count].value = event.target.value;
+ selectedAction.parameters[count].value = event.target.value;
+
+ var forceUpdate = false
+ if (selectedAction.app_name === "Shuffle Tools" && selectedAction.name === "filter_list" && data.name === "input_list") {
+ //console.log("FILTER LIST!: ", event, count, data)
+ const parsedvalue = event.target.value
+ if (parsedvalue.includes("#")) {
+ const splitparsed = parsedvalue.split(".#.")
+ //console.log("Cant contain #: ", splitparsed)
+ if (splitparsed.length > 1) {
+ data.value = splitparsed[0]
+
+ selectedActionParameters[count].value = splitparsed[0]
+ selectedAction.parameters[count].value = splitparsed[0]
+
+ //changeActionParameter({target: {value: splitparsed[1]}},
+ selectedActionParameters[1].value = splitparsed[1]
+ selectedAction.parameters[1].value = splitparsed[1]
+ forceUpdate = true
+ }
+ }
+ }
+
+ setSelectedAction(selectedAction);
+ if (forceUpdate) {
+ setUpdate(Math.random())
+ }
+ //setUpdate(event.target.value)
+ };
+
+
+ const changeActionParameterCodeMirror = (event, count, data) => {
+ if (data.startsWith("${") && data.endsWith("}")) {
// PARAM FIX - Gonna use the ID field, even though it's a hack
const paramcheck = selectedAction.parameters.find(param => param.name === "body")
if (paramcheck !== undefined) {
@@ -481,838 +929,1144 @@ const ParsedAction = (props) => {
}
}
- //console.log("CHANGING ACTION COUNT !")
- selectedActionParameters[count].value = event.target.value
- selectedAction.parameters[count].value = event.target.value
+ selectedActionParameters[count].value = data
+ selectedAction.parameters[count].value = data
setSelectedAction(selectedAction)
//setUpdate(Math.random())
//setUpdate(event.target.value)
}
- const changeActionParameterCodemirror = (event, count, data) => {
- console.log(event)
- if (data.name.startsWith("${") && data.name.endsWith("}")) {
- // PARAM FIX - Gonna use the ID field, even though it's a hack
- const paramcheck = selectedAction.parameters.find(param => param.name === "body")
- if (paramcheck !== undefined) {
- // Escapes all double quotes
- const toReplace = event.target.value.trim().replaceAll("\\\"", "\"").replaceAll("\"", "\\\"");
- console.log("REPLACE WITH: ", toReplace)
- if (paramcheck["value_replace"] === undefined || paramcheck["value_replace"] === null) {
- paramcheck["value_replace"] = [{
- "key": data.name,
- "value": toReplace,
- }]
- console.log("IN IF: ", paramcheck)
+ const changeActionParameterVariable = (fieldvalue, count) => {
+ //console.log("CALLED THIS ONE WITH VALUE!", fieldvalue)
+ //if (selectedVariableParameter === fieldvalue) {
+ // return
+ //}
- } else {
- const subparamindex = paramcheck["value_replace"].findIndex(param => param.key === data.name)
- if (subparamindex === -1) {
- paramcheck["value_replace"].push({
- "key": data.name,
- "value": toReplace,
- })
- } else {
- paramcheck["value_replace"][subparamindex]["value"] = toReplace
- }
+ setSelectedVariableParameter(fieldvalue);
- console.log("IN ELSE: ", paramcheck)
- }
- //console.log("PARAM: ", paramcheck)
- //if (paramcheck.id === undefined) {
- // console.log("Normal paramcheck")
- //} else {
- // selectedActionParameters[count]["value_replace"] = paramcheck
- // selectedAction.parameters[count]["value_replace"] = paramcheck
- //}
+ selectedActionParameters[count].action_field = fieldvalue;
+ selectedAction.parameters = selectedActionParameters;
- if (paramcheck["value_replace"] === undefined) {
- selectedActionParameters[count]["value_replace"] = paramcheck
- selectedAction.parameters[count]["value_replace"] = paramcheck
- } else {
- selectedActionParameters[count]["value_replace"] = paramcheck["value_replace"]
- selectedAction.parameters[count]["value_replace"] = paramcheck["value_replace"]
- }
- console.log("RESULT: ", selectedAction)
- setSelectedAction(selectedAction)
- //setUpdate(Math.random())
- return
+ setSelectedApp(selectedApp);
+ setSelectedAction(selectedAction);
+ setUpdate(fieldvalue);
+ };
+
+ // Sets ACTION_RESULT things
+ const changeActionParameterActionResult = (fieldvalue, count) => {
+ //cy.nodes().forEach(function( ele ) {
+ // if (ele.data()["label"] === fieldvalue) {
+ // selectedActionParameters[count].action_field = ele.id()
+ // return
+ // }
+ //});
+
+ selectedActionParameters[count].action_field = fieldvalue;
+ selectedAction.parameters = selectedActionParameters;
+
+ // FIXME - check if startnode
+
+ // Set value
+ setSelectedApp(selectedApp);
+
+ setSelectedAction(selectedAction);
+ setUpdate(Math.random());
+ };
+
+ const changeActionParameterVariant = (data, count) => {
+ selectedActionParameters[count].variant = data;
+ selectedActionParameters[count].value = "";
+
+ if (data === "ACTION_RESULT" && getParents !== undefined) {
+ var parents = getParents(selectedAction);
+ if (parents.length > 0) {
+ selectedActionParameters[count].action_field = parents[0].label;
+ } else {
+ selectedActionParameters[count].action_field = "";
+ }
+ } else if (data === "WORKFLOW_VARIABLE") {
+ if (
+ workflow.workflow_variables !== null &&
+ workflow.workflow_variables !== undefined &&
+ workflow.workflow_variables.length > 0
+ ) {
+ selectedActionParameters[count].action_field =
+ workflow.workflow_variables[0].name;
+ }
+ }
+
+ selectedAction.parameters = selectedActionParameters;
+
+ // This is a stupid workaround to make it refresh rofl
+ setSelectedAction({});
+
+ if (setSelectedTrigger !== undefined) {
+ setSelectedTrigger({});
+ setSelectedApp({});
+ setSelectedEdge({});
+ }
+ // FIXME - check if startnode
+
+ // Set value
+ setSelectedApp(selectedApp);
+ setSelectedAction(selectedAction);
+ setUpdate(Math.random());
+ };
+
+
+ const returnHelperText = (name, value) => {
+ if (name === undefined || value === undefined) {
+ return ""
+ }
+
+ var helperText = ""
+ //console.log("DATA: ", name, value)
+ if (name.includes("url")) {
+ if (value.includes("localhost") || value.includes("127.0.0.1")) {
+ helperText = "Can't use localhost. Please change to an external IP or hostname."
}
}
- if (event.display.maxLine.text[event.display.maxLine.text.length-1] === "$") {
- if (!showDropdown) {
- setShowAutocomplete(false)
- setShowDropdown(true)
- setShowDropdownNumber(count)
- }
- } else {
- if (showDropdown) {
- setShowDropdown(false)
- }
- }
-
- // bad detection mechanism probably
- if (event.display.maxLine.text[event.display.maxLine.text.length-1] === "." && actionlist.length > 0) {
- console.log("GET THE LAST ARGUMENT FOR NODE!")
- // THIS IS AN EXAMPLE OF SHOWING IT
- /*
- const inputdata = {"data": "1.2.3.4", "dataType": "4.5.6.6"}
- setJsonList(GetParsedPaths(inputdata, ""))
- if (!showDropdown) {
- setShowAutocomplete(false)
- setShowDropdown(true)
- setShowDropdownNumber(count)
- }
- console.log(jsonList)
- */
-
- // Search for the item backwards
- // 1. Reverse search backwards from . -> $
- // 2. Search the actionlist for the item
- // 3. Find the data for the specific item
-
- var curstring = ""
- var record = false
- for (var key in selectedActionParameters[count].value) {
- const item = selectedActionParameters[count].value[key]
- if (record) {
- curstring += item
- }
-
- if (item === "$") {
- record = true
- curstring = ""
- }
- }
-
- //console.log("CURSTRING: ", curstring)
- if (curstring.length > 0 && actionlist !== null) {
- // Search back in the action list
- curstring = curstring.split(" ").join("_").toLowerCase()
- var actionItem = actionlist.find(data => data.autocomplete.split(" ").join("_").toLowerCase() === curstring)
- if (actionItem !== undefined) {
- console.log("Found item: ", actionItem)
-
- //actionItem.example = actionItem.example.trim()
- //actionItem.example = actionItem.example.split(" None").join(" \"None\"")
- //actionItem.example = actionItem.example.split("\'").join("\"")
-
- var jsonvalid = true
- try {
- const tmp = String(JSON.parse(actionItem.example))
- if (!actionItem.example.includes("{") && !actionItem.example.includes("[")) {
- jsonvalid = false
- }
- } catch (e) {
- jsonvalid = false
- }
-
- if (jsonvalid) {
- setJsonList(GetParsedPaths(JSON.parse(actionItem.example), ""))
-
- if (!showDropdown) {
- setShowAutocomplete(false)
- setShowDropdown(true)
- setShowDropdownNumber(count)
- }
- }
- }
- }
- } else {
- if (jsonList.length > 0) {
- setJsonList([])
- }
- }
-
- selectedActionParameters[count].value = event.display.maxLine.text
- selectedAction.parameters[count].value = event.display.maxLine.text
- setSelectedAction(selectedAction)
- //setUpdate(Math.random())
- //setUpdate(event.target.value)
+ return helperText
}
- const changeActionParameterVariable = (fieldvalue, count) => {
- //console.log("CALLED THIS ONE WITH VALUE!", fieldvalue)
- //if (selectedVariableParameter === fieldvalue) {
- // return
- //}
-
- setSelectedVariableParameter(fieldvalue)
-
- selectedActionParameters[count].action_field = fieldvalue
- selectedAction.parameters = selectedActionParameters
-
- setSelectedApp(selectedApp)
- setSelectedAction(selectedAction)
- setUpdate(fieldvalue)
- }
-
- // Sets ACTION_RESULT things
- const changeActionParameterActionResult = (fieldvalue, count) => {
- //cy.nodes().forEach(function( ele ) {
- // if (ele.data()["label"] === fieldvalue) {
- // selectedActionParameters[count].action_field = ele.id()
- // return
- // }
- //});
-
- selectedActionParameters[count].action_field = fieldvalue
- selectedAction.parameters = selectedActionParameters
-
- // FIXME - check if startnode
-
- // Set value
- setSelectedApp(selectedApp)
-
- setSelectedAction(selectedAction)
- setUpdate(Math.random())
- }
-
- const changeActionParameterVariant = (data, count) => {
- selectedActionParameters[count].variant = data
- selectedActionParameters[count].value = ""
-
- if (data === "ACTION_RESULT") {
- var parents = getParents(selectedAction)
- if (parents.length > 0) {
- selectedActionParameters[count].action_field = parents[0].label
- } else {
- selectedActionParameters[count].action_field = ""
- }
- } else if (data === "WORKFLOW_VARIABLE") {
- if (workflow.workflow_variables !== null && workflow.workflow_variables !== undefined && workflow.workflow_variables.length > 0) {
- selectedActionParameters[count].action_field = workflow.workflow_variables[0].name
- }
- }
-
- selectedAction.parameters = selectedActionParameters
-
- // This is a stupid workaround to make it refresh rofl
- setSelectedAction({})
-
- if (setSelectedTrigger !== undefined) {
- setSelectedTrigger({})
- setSelectedApp({})
- setSelectedEdge({})
- }
- // FIXME - check if startnode
-
- // Set value
- setSelectedApp(selectedApp)
- setSelectedAction(selectedAction)
- setUpdate(Math.random())
- }
-
- // FIXME: Issue #40 - selectedActionParameters not reset
- if (Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters.length > 0) {
- var authWritten = false
+ //console.log("AUTH: ", authenticationType)
+ if (authenticationType !== undefined && authenticationType !== null && authenticationType.type === "oauth2") {
+ /*
return (
-
-
Parameters
- {selectedActionParameters.map((data, count) => {
- if (data.variant === "") {
- data.variant = "STATIC_VALUE"
- }
+
+ You must authenticate before using oauth2 apps.
+
+ )
+ */
+ }
+ // FIXME: Issue #40 - selectedActionParameters not reset
+ if (
+ Object.getOwnPropertyNames(selectedAction).length > 0 &&
+ selectedActionParameters.length > 0
+ ) {
+ var authWritten = false;
+ return (
+
+
+ {
+ setHiddenDescription(!hiddenDescription)
+ }}
+ >
+ Parameters
+
+
+ {selectedAction.description !== undefined && selectedAction.description !== null && selectedAction.description.length > 0 && hiddenDescription === false ? (
+
+
+ Description
+
+
+ {selectedAction.description}
+
+
+ ) : null}
+ {selectedActionParameters.map((data, count) => {
+ if (data.variant === "") {
+ data.variant = "STATIC_VALUE";
+ }
- // selectedAction.selectedAuthentication = e.target.value
- // selectedAction.authentication_id = e.target.value.id
- if (!selectedAction.auth_not_required && selectedAction.selectedAuthentication !== undefined && selectedAction.selectedAuthentication.fields !== undefined && selectedAction.selectedAuthentication.fields[data.name] !== undefined) {
- // This sets the placeholder in the frontend. (Replaced in backend)
- selectedActionParameters[count].value = selectedAction.selectedAuthentication.fields[data.name]
- selectedAction.parameters[count].value = selectedAction.selectedAuthentication.fields[data.name]
- setSelectedAction(selectedAction)
- //setUpdate(Math.random())
+ // selectedAction.selectedAuthentication = e.target.value
+ // selectedAction.authentication_id = e.target.value.id
+ if (
+ !selectedAction.auth_not_required &&
+ selectedAction.selectedAuthentication !== undefined &&
+ selectedAction.selectedAuthentication.fields !== undefined &&
+ selectedAction.selectedAuthentication.fields[data.name] !==
+ undefined
+ ) {
+ // This sets the placeholder in the frontend. (Replaced in backend)
+ selectedActionParameters[count].value =
+ selectedAction.selectedAuthentication.fields[data.name];
+ selectedAction.parameters[count].value =
+ selectedAction.selectedAuthentication.fields[data.name];
+ setSelectedAction(selectedAction);
+ //setUpdate(Math.random())
+
+ if (authWritten) {
+ return null;
+ }
+
+ authWritten = true;
+ return (
+
+ Authentication fields are hidden
+
+ );
+ }
+
+ var staticcolor = "inherit";
+ var actioncolor = "inherit";
+ var varcolor = "inherit";
+ var multiline;
+ if (
+ data.multiline !== undefined &&
+ data.multiline !== null &&
+ data.multiline === true
+ ) {
+ multiline = true;
+ }
+
+ if (
+ data.value !== undefined &&
+ data.value !== null &&
+ data.value.startsWith("{") &&
+ data.value.endsWith("}")
+ ) {
+ multiline = true;
+ }
+
+ var placeholder = "Value";
+ if (
+ data.example !== undefined &&
+ data.example !== null &&
+ data.example.length > 0
+ ) {
+ placeholder = data.example;
+
+ if (data.name === "url" && data.value !== undefined && data.value !== null && data.value.length === 0) {
+ data.value = data.example;
+ }
+ }
+
+ if (data.name.startsWith("${") && data.name.endsWith("}")) {
+ const paramcheck = selectedAction.parameters.find(
+ (param) => param.name === "body"
+ );
+ if (paramcheck !== undefined && paramcheck !== null) {
+ if (
+ paramcheck["value_replace"] !== undefined &&
+ paramcheck["value_replace"] !== null
+ ) {
+ //console.log("IN THE VALUE REPLACE: ", paramcheck["value_replace"])
+ const subparamindex = paramcheck["value_replace"].findIndex(
+ (param) => param.key === data.name
+ );
+ if (subparamindex !== -1) {
+ data.value =
+ paramcheck["value_replace"][subparamindex]["value"];
+ }
+ }
+ }
+ }
+
+ var disabled = false;
+ var rows = "5";
+ var openApiHelperText = "This is an OpenAPI specific field";
+ /*
+ if (
+ selectedApp.generated &&
+ data.name === "url" &&
+ data.required &&
+ data.configuration
+ ) {
+ //&&
+ //hideExtraTypes
- if (authWritten) {
- return null
- }
+ //console.log("GENERATED WITH DATA: ", data);
+ return null;
+ }
+ */
- authWritten = true
- return (
-
- Authentication fields are hidden
-
- )
+ if (selectedApp.generated && data.name === "headers") {
+ //console.log("HEADER: ", data)
+ //if (data.value.length === 0) {
+ //}
+ //setSelectedActionParameters(selectedActionParameters)
+ }
+
+ var hideBodyButton = "";
+ const hideBodyButtonValue = (
+
+
+ {
+ var tag = "TOGGLED"
+ if (hideBody) {
+ tag = "UNTOGGLED"
+ }
+
+ setHideBody(!hideBody);
+
+ for (var key in selectedActionParameters) {
+ var currentItem = selectedActionParameters[key];
+ if (currentItem.name === "ssl_verify") {
+
+ }
+
+ if (currentItem.name === "body") {
+ // FIXME: Workaround for toggling, as actions don't have IDs.
+ // May screw up something in the future.
+ currentItem.id = tag
+ }
+
+ if (currentItem.description === openApiFieldDesc) {
+ currentItem.field_active = !hideBody;
+ console.log("Changing", currentItem);
+ }
+ }
+ }}
+ name="requires_unique"
+ />
+ }
+ label={"Automatically fix body"}
+ />
+
+
+ )
+
+ if (selectedApp.generated && data.name === "body") {
+ const regex = /\${(\w+)}/g;
+ const found = placeholder.match(regex);
+
+ hideBodyButton = hideBodyButtonValue;
+ if (found === null || !hideBody) {
+ console.log("Should hide body? ", found)
+ //
+ if (found === null) {
+ setActivateHidingBodyButton(true);
+ }
+ } else {
+ //console.log("SHOW BUTTON");
+
+ rows = "1";
+ disabled = true;
+ openApiHelperText = "OpenAPI spec: fill the following fields.";
+ //console.log("SHOULD ADD TO selectedActionParameters!: ", found, selectedActionParameters)
+ var changed = false;
+ for (var specKey in found) {
+ const tmpitem = found[specKey];
+ var skip = false;
+ for (var innerkey in selectedActionParameters) {
+ if (selectedActionParameters[innerkey].name === tmpitem) {
+ skip = true;
+ break;
+ }
+ }
+
+ if (skip) {
+ //console.log("SKIPPING ", tmpitem)
+ continue;
+ }
+
+ changed = true;
+ selectedActionParameters.push({
+ action_field: "",
+ configuration: false,
+ description: openApiFieldDesc,
+ example: "",
+ id: "",
+ multiline: false,
+ name: tmpitem,
+ options: null,
+ required: false,
+ schema: { type: "string" },
+ skip_multicheck: false,
+ tags: null,
+ value: "",
+ variant: "STATIC_VALUE",
+ field_active: true,
+ });
+ }
+
+ if (changed) {
+ setSelectedActionParameters(selectedActionParameters);
+ }
+
+ return hideBodyButton;
+ }
+ }
+
+ if (activateHidingBodyButton === true) {
+ hideBodyButton = "";
+ }
+
+ const clickedFieldId = "rightside_field_" + count;
+
+ const shufflecode = fieldCount !== count ? null :
+ (
+
+ )
+
+ //
0) {
+ baseHelperText = calculateHelpertext(data.value)
}
+
+ var datafield = (
+
+
+
+ {
+ event.preventDefault()
+ setFieldCount(count)
+ setcodedata(data.value)
+ setExpansionModalOpen(true)
+ }}
+ />
+
+
+ {
+ event.preventDefault()
+ setMenuPosition({
+ top: event.pageY + 10,
+ left: event.pageX + 10,
+ });
+ setShowDropdownNumber(count);
+ setShowDropdown(true);
+ setShowAutocomplete(true);
+ }}
+ />
+
+
+
+ ),
+ }}
+ multiline={multiline}
+ helperText={returnHelperText(data.name, data.value)}
+ onClick={() => {
+ /*
+ console.log("Clicked field: ", clickedFieldId);
+ setExpansionModalOpen(false);
+ if (
+ setScrollConfig !== undefined &&
+ scrollConfig !== null &&
+ scrollConfig !== undefined &&
+ scrollConfig.selected !== clickedFieldId
+ ) {
+ scrollConfig.selected = clickedFieldId;
+ setScrollConfig(scrollConfig);
+ //console.log("Change field id!")
+ }
+ */
- var staticcolor = "inherit"
- var actioncolor = "inherit"
- var varcolor = "inherit"
- var multiline
- if (data.multiline !== undefined && data.multiline !== null && data.multiline === true) {
- multiline = true
- }
-
- if (data.value !== undefined && data.value !== null && data.value.startsWith("{") && data.value.endsWith("}")) {
- multiline = true
- }
-
- var placeholder = "Static value"
- if (data.example !== undefined && data.example !== null && data.example.length > 0) {
- placeholder = data.example
-
- if (data.name === "url" && data.value.length === 0) {
- data.value = data.example
- }
- }
-
- if (data.name.startsWith("${") && data.name.endsWith("}")) {
- const paramcheck = selectedAction.parameters.find(param => param.name === "body")
- if (paramcheck !== undefined && paramcheck !== null) {
- if (paramcheck["value_replace"] !== undefined && paramcheck["value_replace"] !== null) {
- //console.log("IN THE VALUE REPLACE: ", paramcheck["value_replace"])
- const subparamindex = paramcheck["value_replace"].findIndex(param => param.key === data.name)
- if (subparamindex !== -1) {
- data.value = paramcheck["value_replace"][subparamindex]["value"]
- }
- }
- }
- }
-
- var disabled = false
- var rows = "5"
- var openApiHelperText = "This is an OpenAPI specific field"
- if (selectedApp.generated && data.name === "url" && data.required && data.configuration && hideExtraTypes) {
- console.log("GENERATED WITH DATA: ", data)
- return null
- }
-
- if (selectedApp.generated && data.name === "headers") {
- //console.log("HEADER: ", data)
- //if (data.value.length === 0) {
-
- //}
- //setSelectedActionParameters(selectedActionParameters)
- }
-
- if (selectedApp.generated && data.name === "body") {
- const regex = /\${(\w+)}/g
- const found = placeholder.match(regex)
- if (found === null) {
- //setExtraBodyFields([])
- } else {
- rows = "1"
- disabled = true
- openApiHelperText = "OpenAPI spec: fill the following fields."
- //console.log("SHOULD ADD TO selectedActionParameters!: ", found, selectedActionParameters)
- var changed = false
- for (var specKey in found) {
- const tmpitem = found[specKey]
- var skip = false
- for (var innerkey in selectedActionParameters) {
- if (selectedActionParameters[innerkey].name === tmpitem) {
- skip = true
- break
- }
- }
-
- if (skip) {
- //console.log("SKIPPING ", tmpitem)
- continue
- }
-
- changed = true
- selectedActionParameters.push({
- action_field: "",
- configuration: false,
- description: "Generated by OpenAPI body example",
- example: "",
- id: "",
- multiline: false,
- name: tmpitem,
- options: null,
- required: false,
- schema: {type: "string"},
- skip_multicheck: false,
- tags: null,
- value: "",
- variant: "STATIC_VALUE",
- })
- }
-
- if (changed) {
- setSelectedActionParameters(selectedActionParameters)
- }
-
- return
- }
- }
-
- const clickedFieldId = "rightside_field_"+count
- //
-
- {
- setMenuPosition({
- top: event.pageY+10,
- left: event.pageX+10,
- })
- setShowDropdownNumber(count)
- setShowDropdown(true)
- setShowAutocomplete(true)
- }}/>
-
-
-
- )
- }}
- fullWidth
- multiline={multiline}
- onClick={() => {
console.log("Clicked field: ", clickedFieldId)
- setExpansionModalOpen(false)
if (setScrollConfig !== undefined && scrollConfig !== null && scrollConfig !== undefined && scrollConfig.selected !== clickedFieldId) {
scrollConfig.selected = clickedFieldId
setScrollConfig(scrollConfig)
//console.log("Change field id!")
}
- }}
- id={clickedFieldId}
- rows={rows}
- color="primary"
- defaultValue={data.value}
- //value={data.value}
- //options={{
- // theme: 'gruvbox-dark',
- // keyMap: 'sublime',
- // mode: 'python',
- //}}
- //height={multiline ? 50 : 150}
+ }}
+ id={clickedFieldId}
+ rows={rows}
+ color="primary"
+ defaultValue={data.value}
+ //value={data.value}
+ //options={{
+ // theme: 'gruvbox-dark',
+ // keyMap: 'sublime',
+ // mode: 'python',
+ //}}
+ //height={multiline ? 50 : 150}
- type={placeholder.includes("***") || (data.configuration && (data.name.toLowerCase().includes("api") || data.name.toLowerCase().includes("key") || data.name.toLowerCase().includes("pass"))) ? "password" : "text"}
- placeholder={placeholder}
-
- onChange={(event) => {
- //changeActionParameterCodemirror(event, count, data)
- changeActionParameter(event, count, data)
- }}
-
- helperText={selectedApp.generated && selectedApp.activated && data.name === "body" ?
-
- {openApiHelperText}
-
- :
- data.name.startsWith("${") && data.name.endsWith("}") ?
-
- OpenAPI helperfield
-
- :
- null
- }
- onBlur={(event) => {
- // Super basic check
- //if (event.target.value.startsWith("{")) {
- // console.log("VALIDATING JSON")
- // try {
- // JSON.parse(event.target.value)
- // } catch (e) {
- // alert.error("Failed to parse json: ", e)
- // }
- //}
- }}
- />
+ type={
+ placeholder.includes("***") ||
+ (data.configuration &&
+ (data.name.toLowerCase().includes("api") ||
+ data.name.toLowerCase().includes("key") ||
+ data.name.toLowerCase().includes("pass")))
+ ? "password"
+ : "text"
+ }
+ placeholder={placeholder}
+ onChange={(event) => {
+ //changeActionParameterCodemirror(event, count, data)
+ changeActionParameter(event, count, data);
+ }}
+ helperText={baseHelperText.length > 0 ? baseHelperText :
+ selectedApp.generated &&
+ selectedApp.activated &&
+ data.name === "body" ? (
+
+ {openApiHelperText}
+
+ ) : data.name.startsWith("${") && data.name.endsWith("}") ? (
+
+ OpenAPI helperfield
+
+ ) : null
+ }
+ onBlur={(event) => {
+ baseHelperText = calculateHelpertext(event.target.value)
+ if (setLastSaved !== undefined) {
+ setLastSaved(false)
+ }
+ }}
+ />
+ );
- //console.log("FIELD VALUE: ", data.value)
+ //console.log("FIELD VALUE: ", data.value)
//const regexp = new RegExp("\W+\.", "g")
- //let match
- //while ((match = regexp.exec(data.value)) !== null) {
- // console.log(`Found ${match[0]} start=${match.index} end=${regexp.lastIndex}.`);
- //}
+ //let match
+ //while ((match = regexp.exec(data.value)) !== null) {
+ // console.log(`Found ${match[0]} start=${match.index} end=${regexp.lastIndex}.`);
+ //}
- //const str = = data.value.search(submatch)
- //console.log("FOUND? ", n)
- //for (var key in keywords) {
- // const keyword = keywords[key]
- // if (data.value.includes(keyword)) {
- // console.log("INCLUDED: ", keyword)
- // }
- //}
+ //const str = = data.value.search(submatch)
+ //console.log("FOUND? ", n)
+ //for (var key in keywords) {
+ // const keyword = keywords[key]
+ // if (data.value.includes(keyword)) {
+ // console.log("INCLUDED: ", keyword)
+ // }
+ //}
- //const keywords = ["len", "lower", "upper", "trim", "split", "length", "number", "parse", "join"]
- if (selectedActionParameters[count].schema !== undefined && selectedActionParameters[count].schema !== null && selectedActionParameters[count].schema.type === "file") {
- datafield =
-
-
- {
- setMenuPosition({
- top: event.pageY+10,
- left: event.pageX+10,
- })
- setShowDropdownNumber(count)
- setShowDropdown(true)
- setShowAutocomplete(true)
- }}/>
-
-
- )
- }}
- fullWidth
- multiline={multiline}
- rows="5"
- color="primary"
- defaultValue={data.value}
- type={"text"}
- placeholder={"The file ID to get"}
- id={"rightside_field_"+count}
- onChange={(event) => {
- changeActionParameter(event, count, data)
- }}
- onBlur={(event) => {
- }}
- />
- //const fileId = "6daabec1-892b-469c-b603-c902e47223a9"
- //datafield = `SHOW FILES FROM OTHER NODES? Filename: ${selectedActionParameters[count].value}`
- /*
+ // Basic helpertext
+
+ //const keywords = ["len", "lower", "upper", "trim", "split", "length", "number", "parse", "join"]
+ if (
+ selectedActionParameters[count].schema !== undefined &&
+ selectedActionParameters[count].schema !== null &&
+ selectedActionParameters[count].schema.type === "file"
+ ) {
+ datafield = (
+
+
+ {
+ setMenuPosition({
+ top: event.pageY + 10,
+ left: event.pageX + 10,
+ });
+ setShowDropdownNumber(count);
+ setShowDropdown(true);
+ setShowAutocomplete(true);
+ }}
+ />
+
+
+ ),
+ }}
+ helperText={returnHelperText(data.name, data.value)}
+ fullWidth
+ multiline={multiline}
+ rows="5"
+ color="primary"
+ defaultValue={data.value}
+ type={"text"}
+ placeholder={"The file ID to get"}
+ id={"rightside_field_" + count}
+ onChange={(event) => {
+ changeActionParameter(event, count, data);
+ }}
+ onBlur={(event) => {}}
+ />
+ );
+ //const fileId = "6daabec1-892b-469c-b603-c902e47223a9"
+ //datafield = `SHOW FILES FROM OTHER NODES? Filename: ${selectedActionParameters[count].value}`
+ /*
if (selectedActionParameters[count].value != fileId) {
changeActionParameter(fileId, count, data)
setUpdate(Math.random())
}
*/
- } else if (selectedActionParameters[count].options !== undefined && selectedActionParameters[count].options !== null && selectedActionParameters[count].options.length > 0) {
- if (selectedActionParameters[count].value === "" && selectedActionParameters[count].required) {
- // Rofl, dirty workaround :)
- const e = {
- target: {
- value: selectedActionParameters[count].options[0],
- }
- }
+ } else if (
+ selectedActionParameters[count].options !== undefined &&
+ selectedActionParameters[count].options !== null &&
+ selectedActionParameters[count].options.length > 0
+ ) {
+ if (selectedActionParameters[count].value === "") {
+ // && selectedActionParameters[count].required) {
+ // Rofl, dirty workaround :)
+ const e = {
+ target: {
+ value: selectedActionParameters[count].options[0],
+ },
+ };
- changeActionParameter(e, count, data)
- }
+ changeActionParameter(e, count, data);
+ }
- datafield =
- {
- changeActionParameter(e, count, data)
- setUpdate(Math.random())
- }}
- style={{backgroundColor: theme.palette.surfaceColor, color: "white", height: "50px", borderRadius: theme.palette.borderRadius,}}
- >
- {selectedActionParameters[count].options.map((data, index) => {
- const split_data = data.split("||")
- var viewed_data = data
- if (split_data.length > 1) {
- viewed_data = split_data[0]
- }
+ SelectDisplayProps={{
+ style: {
+ marginLeft: 10,
+ },
+ }}
+ value={selectedActionParameters[count].value}
+ fullWidth
+ id={"rightside_field_" + count}
+ onChange={(e) => {
+ changeActionParameter(e, count, data);
+ setUpdate(Math.random());
+ }}
+ style={{
+ backgroundColor: theme.palette.surfaceColor,
+ color: "white",
+ height: "50px",
+ borderRadius: theme.palette.borderRadius,
+ }}
+ >
+ {selectedActionParameters[count].options.map(
+ (data, index) => {
+ const split_data = data.split("||");
+ var viewed_data = data;
+ if (split_data.length > 1) {
+ viewed_data = split_data[0];
+ }
- return (
-
- {viewed_data}
-
- )
- })}
-
+ return (
+
+ {viewed_data}
+
+ );
+ }
+ )}
+
+ );
+ } else if (data.variant === "STATIC_VALUE") {
+ staticcolor = "#f85a3e";
+ }
- } else if (data.variant === "STATIC_VALUE") {
- staticcolor = "#f85a3e"
- }
+ if (data.field_active === false) {
+ return null;
+ }
- // Shows nested list of nodes > their JSON lists
- const ActionlistWrapper = (props) => {
+ // Shows nested list of nodes > their JSON lists
+ const ActionlistWrapper = (props) => {
+ const handleMenuClose = () => {
+ setShowAutocomplete(false);
- const handleMenuClose = () => {
- setShowAutocomplete(false)
+ if (
+ !selectedActionParameters[count].value[
+ selectedActionParameters[count].value.length - 1
+ ] === "$"
+ ) {
+ setShowDropdown(false);
+ }
- if (!selectedActionParameters[count].value[selectedActionParameters[count].value.length-1] === "$") {
- setShowDropdown(false)
- }
+ setUpdate(Math.random());
+ setMenuPosition(null);
+ };
- setUpdate(Math.random())
- setMenuPosition(null)
- }
+ const handleItemClick = (values) => {
+ if (
+ values === undefined ||
+ values === null ||
+ values.length === 0
+ ) {
+ return;
+ }
- const handleItemClick = (values) => {
- if (values === undefined || values === null || values.length === 0) {
- return
- }
+ console.log("AUTOCOMPLETE1: ", values);
- console.log("AUTOCOMPLETE1: ", values)
+ var toComplete = selectedActionParameters[count].value.trim()
+ .endsWith("$")
+ ? values[0].autocomplete
+ : "$" + values[0].autocomplete;
+ toComplete = toComplete.toLowerCase().replaceAll(" ", "_");
+ console.log("AUTOCOMPLETE: ", toComplete);
+ for (var key in values) {
+ if (key == 0 || values[key].autocomplete.length === 0) {
+ continue;
+ }
- var toComplete = selectedActionParameters[count].value.trim().endsWith("$") ? values[0].autocomplete : "$"+values[0].autocomplete
- toComplete = toComplete.toLowerCase().replaceAll(" ", "_")
- console.log("AUTOCOMPLETE: ", toComplete)
- for (var key in values) {
- if (key == 0 || values[key].autocomplete.length === 0) {
- continue
- }
+ toComplete += values[key].autocomplete;
+ }
- toComplete += values[key].autocomplete
- }
+ // Handles the fields under OpenAPI body to be parsed.
+ if (data.name.startsWith("${") && data.name.endsWith("}")) {
+ console.log("INSIDE VALUE REPLACE: ", data.name, toComplete);
+ // PARAM FIX - Gonna use the ID field, even though it's a hack
+ const paramcheck = selectedAction.parameters.find(
+ (param) => param.name === "body"
+ );
+ if (paramcheck !== undefined) {
+ if (
+ paramcheck["value_replace"] === undefined ||
+ paramcheck["value_replace"] === null
+ ) {
+ paramcheck["value_replace"] = [
+ {
+ key: data.name,
+ value: toComplete,
+ },
+ ];
+ } else {
+ const subparamindex = paramcheck[
+ "value_replace"
+ ].findIndex((param) => param.key === data.name);
+ if (subparamindex === -1) {
+ paramcheck["value_replace"].push({
+ key: data.name,
+ value: toComplete,
+ });
+ } else {
+ paramcheck["value_replace"][subparamindex]["value"] +=
+ toComplete;
+ }
+ }
- // Handles the fields under OpenAPI body to be parsed.
- if (data.name.startsWith("${") && data.name.endsWith("}")) {
- console.log("INSIDE VALUE REPLACE: ", data.name, toComplete)
- // PARAM FIX - Gonna use the ID field, even though it's a hack
- const paramcheck = selectedAction.parameters.find(param => param.name === "body")
- if (paramcheck !== undefined) {
- if (paramcheck["value_replace"] === undefined || paramcheck["value_replace"] === null) {
- paramcheck["value_replace"] = [{
- "key": data.name,
- "value": toComplete,
- }]
+ selectedActionParameters[count]["value_replace"] =
+ paramcheck;
+ selectedAction.parameters[count]["value_replace"] =
+ paramcheck;
+ setSelectedAction(selectedAction);
+ setUpdate(Math.random());
- } else {
- const subparamindex = paramcheck["value_replace"].findIndex(param => param.key === data.name)
- if (subparamindex === -1) {
- paramcheck["value_replace"].push({
- "key": data.name,
- "value": toComplete,
- })
- } else {
- paramcheck["value_replace"][subparamindex]["value"] += toComplete
- }
- }
+ setShowDropdown(false);
+ setMenuPosition(null);
+ return;
+ }
+ }
-
- selectedActionParameters[count]["value_replace"] = paramcheck
- selectedAction.parameters[count]["value_replace"] = paramcheck
- setSelectedAction(selectedAction)
- setUpdate(Math.random())
+ selectedActionParameters[count].value += toComplete;
+ selectedAction.parameters[count].value =
+ selectedActionParameters[count].value;
+ setSelectedAction(selectedAction);
+ setUpdate(Math.random());
- setShowDropdown(false)
- setMenuPosition(null)
- return
- }
- }
+ setShowDropdown(false);
+ setMenuPosition(null);
+ };
- selectedActionParameters[count].value += toComplete
- selectedAction.parameters[count].value = selectedActionParameters[count].value
- setSelectedAction(selectedAction)
- setUpdate(Math.random())
+ const iconStyle = {
+ marginRight: 15,
+ };
- setShowDropdown(false)
- setMenuPosition(null)
- }
+ return (
+ {
+ handleMenuClose();
+ }}
+ open={!!menuPosition}
+ style={{
+ color: "white",
+ marginTop: 2,
+ maxHeight: 650,
+ }}
+ >
+ {actionlist.map((innerdata) => {
+ const icon =
+ innerdata.type === "action" ? (
+
+ ) : innerdata.type === "workflow_variable" ||
+ innerdata.type === "execution_variable" ? (
+
+ ) : (
+
+ );
- const iconStyle = {
- marginRight: 15,
- }
+ const handleExecArgumentHover = (inside) => {
+ var exec_text_field = document.getElementById(
+ "execution_argument_input_field"
+ );
+ if (exec_text_field !== null) {
+ if (inside) {
+ exec_text_field.style.border = "2px solid #f85a3e";
+ } else {
+ exec_text_field.style.border = "";
+ }
+ }
- return (
- {
- handleMenuClose()
- }}
- open={!!menuPosition}
- style={{
- color: "white",
- marginTop: 2,
- maxHeight: 650,
- }}
- >
- {actionlist.map(innerdata => {
- const icon = innerdata.type === "action" ? : innerdata.type === "workflow_variable" || innerdata.type === "execution_variable" ? :
+ // Also doing arguments
+ if (
+ workflow.triggers !== undefined &&
+ workflow.triggers !== null &&
+ workflow.triggers.length > 0
+ ) {
+ for (var key in workflow.triggers) {
+ const item = workflow.triggers[key];
- const handleExecArgumentHover = (inside) => {
- var exec_text_field = document.getElementById("execution_argument_input_field")
- if (exec_text_field !== null) {
- if (inside) {
- exec_text_field.style.border = "2px solid #f85a3e"
- } else {
- exec_text_field.style.border = ""
- }
- }
+ if (cy !== undefined) {
+ var node = cy.getElementById(item.id);
+ if (node.length > 0) {
+ if (inside) {
+ node.addClass("shuffle-hover-highlight");
+ } else {
+ node.removeClass("shuffle-hover-highlight");
+ }
+ }
+ }
+ }
+ }
+ };
- // Also doing arguments
- if (workflow.triggers !== undefined && workflow.triggers !== null && workflow.triggers.length > 0) {
- for (var key in workflow.triggers) {
- const item = workflow.triggers[key]
+ const handleActionHover = (inside, actionId) => {
+ if (cy !== undefined) {
+ var node = cy.getElementById(actionId);
+ if (node.length > 0) {
+ if (inside) {
+ node.addClass("shuffle-hover-highlight");
+ } else {
+ node.removeClass("shuffle-hover-highlight");
+ }
+ }
+ }
+ };
- if (cy !== undefined) {
- var node = cy.getElementById(item.id)
- if (node.length > 0) {
- if (inside) {
- node.addClass('shuffle-hover-highlight')
- } else {
- node.removeClass('shuffle-hover-highlight')
- }
- }
- }
- }
- }
- }
+ const handleMouseover = () => {
+ if (innerdata.type === "Execution Argument") {
+ handleExecArgumentHover(true);
+ } else if (innerdata.type === "action") {
+ handleActionHover(true, innerdata.id);
+ }
+ };
- const handleActionHover = (inside, actionId) => {
- if (cy !== undefined) {
- var node = cy.getElementById(actionId)
- if (node.length > 0) {
- if (inside) {
- node.addClass('shuffle-hover-highlight')
- } else {
- node.removeClass('shuffle-hover-highlight')
- }
- }
- }
- }
+ const handleMouseOut = () => {
+ if (innerdata.type === "Execution Argument") {
+ handleExecArgumentHover(false);
+ } else if (innerdata.type === "action") {
+ handleActionHover(false, innerdata.id);
+ }
+ };
- const handleMouseover = () => {
- if (innerdata.type === "Execution Argument") {
- handleExecArgumentHover(true)
- } else if (innerdata.type === "action") {
- handleActionHover(true, innerdata.id)
- }
- }
-
- const handleMouseOut = () => {
- if (innerdata.type === "Execution Argument") {
- handleExecArgumentHover(false)
- } else if (innerdata.type === "action") {
- handleActionHover(false, innerdata.id)
- }
- }
+ var parsedPaths = [];
+ if (typeof innerdata.example === "object") {
+ parsedPaths = GetParsedPaths(innerdata.example, "");
+ }
- var parsedPaths = []
- if (typeof(innerdata.example) === "object") {
- parsedPaths = GetParsedPaths(innerdata.example, "")
- }
+ const coverColor = "#82ccc3"
+ return parsedPaths.length > 0 ? (
+
+ {icon} {innerdata.name}
+
+ }
+ parentMenuOpen={!!menuPosition}
+ style={{
+ backgroundColor: theme.palette.inputColor,
+ color: "white",
+ minWidth: 250,
+ maxWidth: 250,
+ maxHeight: 650,
+ scrollX: "",
+ }}
+ //PaperProps={{
+ // style: {
+ // maxHeight: 400,
+ // width: 250,
+ // }
+ //}}
+ onClick={() => {
+ console.log("CLICKED: ", innerdata);
+ console.log(innerdata.example)
+ handleItemClick([innerdata]);
+ }}
+ >
+
{
+ //console.log("HOVER: ", pathdata);
+ }}
+ onClick={() => {
+ handleItemClick([innerdata]);
+ }}
+ >
+
+ {innerdata.name}
+
+
+ {parsedPaths.map((pathdata, index) => {
+ // FIXME: Should be recursive in here
+ //
+ const icon =
+ pathdata.type === "value" ? (
+
+ ) : pathdata.type === "list" ? (
+
+ ) : (
+
+ );
+ //
- return (
- parsedPaths.length > 0 ?
-
- {icon} {innerdata.name}
-
- }
- parentMenuOpen={!!menuPosition}
- style={{backgroundColor: theme.palette.inputColor, color: "white", minWidth: 250, maxWidth: 250, maxHeight: 650, scrollX: "", }}
- //PaperProps={{
- // style: {
- // maxHeight: 400,
- // width: 250,
- // }
- //}}
- onClick={() => {
- console.log("CLICKED: ", innerdata)
- handleItemClick([innerdata])
- }}
- >
- {parsedPaths.map((pathdata, index) => {
- // FIXME: Should be recursive in here
- const icon = pathdata.type === "value" ? : pathdata.type === "list" ? :
- return (
- {console.log("HOVER: ", pathdata)}}
- onClick={() => {
- handleItemClick([innerdata, pathdata])
- }}
- >
-
-
- {icon} {pathdata.name}
-
-
-
- )
+ console.log("Path: ", pathdata)
+ const indentation_count = (pathdata.name.match(/\./g) || []).length+1
+ const baseIndent =
+ //const boxPadding = pathdata.type === "object" ? "10px 0px 0px 0px" : 0
+ const boxPadding = 0
+ const namesplit = pathdata.name.split(".")
+ const newname = namesplit[namesplit.length-1]
+ console.log(newname)
+ return (
+ {
+ //console.log("HOVER: ", pathdata);
+ }}
+ onClick={() => {
+ handleItemClick([innerdata, pathdata]);
+ }}
+ >
+
+
+ {Array(indentation_count).fill().map((subdata, subindex) => {
+ return (
+ baseIndent
+ )
+ })}
+ {icon} {newname}
+
+
+
+ );
+ })}
+
+ ) : (
+ handleMouseover()}
+ onMouseOut={() => {
+ handleMouseOut();
+ }}
+ onClick={() => {
+ handleItemClick([innerdata]);
+ }}
+ >
+
+
+ {icon} {innerdata.name}
+
+
+
+ );
+ })}
+
+ );
+ };
- })}
-
- :
- handleMouseover()} onMouseOut={() => {handleMouseOut()}}
- onClick={() => {
- handleItemClick([innerdata])
- }}
- >
-
-
- {icon} {innerdata.name}
-
-
-
-
- )
- })}
-
- )
- }
+ var tmpitem = data.name.valueOf();
+ if (data.name.startsWith("${") && data.name.endsWith("}")) {
+ tmpitem = tmpitem.slice(2, data.name.length - 1);
+ }
+ tmpitem = (
+ tmpitem.charAt(0).toUpperCase() + tmpitem.substring(1)
+ ).replaceAll("_", " ");
- var tmpitem = data.name.valueOf()
- if (data.name.startsWith("${") && data.name.endsWith("}")) {
- tmpitem = tmpitem.slice(2, data.name.length-1)
- }
-
- tmpitem = (tmpitem.charAt(0).toUpperCase()+tmpitem.substring(1)).replaceAll("_", " ")
+ if (tmpitem === "Username basic") {
+ tmpitem = "Username"
+ } else if (tmpitem === "Password basic") {
+ tmpitem = "Password"
+ }
- if (tmpitem === "Username basic") {
- tmpitem = "Username"
- } else if (tmpitem === "Password basic") {
- tmpitem = "Password"
- }
+
+ const description =
+ data.description === undefined ? "" : data.description;
+ const tooltipDescription = (
+
+
+ - Required:{" "}
+ {data.required === true || data.configuration === true
+ ? "True"
+ : "False"}
+
+
+ - Example: {data.example}
+
+
+ - Description: {description}
+
+
+ );
- const description = data.description === undefined ? "" : data.description
- const tooltipDescription =
-
- - Required: {data.required === true || data.configuration === true ? "True" : "False"}
- - Example: {data.example}
- - Description: {description}
-
+ //var itemColor = "#f85a3e"
+ //if (!data.required) {
+ // itemColor = "#ffeb3b"
+ //}
+ {
+ /*
*/
+ }
+ return (
+
+ {hideBodyButton}
+
+ {data.configuration === true ? (
+
+ {
+ setAuthenticationModalOpen(true);
+ }}
+ />
+
+ ) : null}
- //var itemColor = "#f85a3e"
- //if (!data.required) {
- // itemColor = "#ffeb3b"
- //}
- {/*
*/}
- return (
-
-
- {data.configuration === true ?
-
- {
- setAuthenticationModalOpen(true)
- }}/>
-
- :
- null
- }
-
-
- {tmpitem}
-
-
+
+
+ {tmpitem}
+
+
- {/*selectedActionParameters[count].options !== undefined && selectedActionParameters[count].options !== null && selectedActionParameters[count].options.length > 0 ? null :
+ {/*selectedActionParameters[count].options !== undefined && selectedActionParameters[count].options !== null && selectedActionParameters[count].options.length > 0 ? null :
{
@@ -1342,7 +2096,7 @@ const ParsedAction = (props) => {
*/}
- {/*(selectedActionParameters[count].options !== undefined && selectedActionParameters[count].options !== null && selectedActionParameters[count].options.length > 0 && selectedActionParameters[count].required === true && selectedActionParameters[count].unique_toggled !== undefined) || hideExtraTypes ? null :
+ {/*(selectedActionParameters[count].options !== undefined && selectedActionParameters[count].options !== null && selectedActionParameters[count].options.length > 0 && selectedActionParameters[count].required === true && selectedActionParameters[count].unique_toggled !== undefined) || hideExtraTypes ? null :
{}}>
@@ -1355,7 +2109,7 @@ const ParsedAction = (props) => {
onChange={(event) => {
//console.log("CHECKED!: ", selectedActionParameters[count])
selectedActionParameters[count].unique_toggled = !selectedActionParameters[count].unique_toggled
- selectedAction.parameters[count].unique_toggled = selectedActionParameters[count].unique_toggled
+ selectedAction.parameters[count].unique_toggled = selectedActionParameters[count].unique_toggled
setSelectedActionParameters(selectedActionParameters)
setSelectedAction(selectedAction)
setUpdate(Math.random())
@@ -1366,158 +2120,280 @@ const ParsedAction = (props) => {
*/}
-
- {datafield}
- {showDropdown && showDropdownNumber === count && data.variant === "STATIC_VALUE" && jsonList.length > 0 ?
-
- Autocomplete
-
+ {datafield}
+ {shufflecode}
+ {showDropdown &&
+ showDropdownNumber === count &&
+ data.variant === "STATIC_VALUE" &&
+ jsonList.length > 0 ? (
+
+
+ Autocomplete
+
+ {
+ setShowAutocomplete(false);
+
+ if (
+ !selectedActionParameters[count].value[
+ selectedActionParameters[count].value.length - 1
+ ] === "."
+ ) {
+ setShowDropdown(false);
+ }
+
+ setUpdate(Math.random());
+ }}
+ onClick={() => setShowAutocomplete(true)}
+ fullWidth
+ open={showAutocomplete}
+ style={{
+ color: "white",
+ height: 50,
+ marginTop: 2,
+ borderRadius: theme.palette.borderRadius,
+ }}
+ onChange={(e) => {
+ if (
+ selectedActionParameters[count].value[
+ selectedActionParameters[count].value.length - 1
+ ] === "."
+ ) {
+ e.target.value.autocomplete =
+ e.target.value.autocomplete.slice(
+ 1,
+ e.target.value.autocomplete.length
+ );
+ }
+
+ selectedActionParameters[count].value +=
+ e.target.value.autocomplete;
+ selectedAction.parameters[count].value =
+ selectedActionParameters[count].value;
+ setSelectedAction(selectedAction);
+ setUpdate(Math.random());
+
+ setShowDropdown(false);
+ }}
+ >
+ {jsonList.map((data) => {
+ const iconStyle = {
+ marginRight: 15,
+ };
+
+ const icon =
+ data.type === "value" ? (
+
+ ) : data.type === "list" ? (
+
+ ) : (
+
+ );
+ return (
+ {}}
+ >
+
+
+ {icon} {data.name}
+
+
+
+ );
+ })}
+
+
+ ) : null}
+ {showDropdown &&
+ showDropdownNumber === count &&
+ data.variant === "STATIC_VALUE" &&
+ jsonList.length === 0 ? (
+
+ ) : null}
+
+ );
+ })}
+
+ );
+ }
+ return null;
+ };
+
+ //const CustomPopper = function (props) {
+ // const classes = useStyles()
+ // return
+ //}
+ //console.log("env: ", selectedActionEnvironment)
+
+ const baselabel = selectedAction.label;
+ return (
+
+
+ {hideExtraTypes === true ? null : (
+
+
+
+
+ {(
+ selectedAction.app_name.charAt(0).toUpperCase() +
+ selectedAction.app_name.substring(1)
+ ).replaceAll("_", " ")}
+
+
+
{
+ console.log("FIND EXAMPLE RESULTS FOR ", selectedAction);
+ if (workflowExecutions.length > 0) {
+ // Look for the ID
+ const found = false;
+ for (var key in workflowExecutions) {
+ if (
+ workflowExecutions[key].results === undefined ||
+ workflowExecutions[key].results === null
+ ) {
+ continue;
+ }
+
+ var foundResult = workflowExecutions[key].results.find(
+ (result) => result.action.id === selectedAction.id
+ );
+ if (foundResult === undefined || foundResult === null) {
+ continue;
+ }
+
+ setSelectedResult(foundResult);
+
+ if (setCodeModalOpen !== undefined) {
+ setCodeModalOpen(true);
+ }
+ break;
+ }
+ }
+ }}
+ >
+
+
+
+
+
{
+ setAuthenticationModalOpen(true);
+ }}
+ >
+
+
+
+
+
{}}
+ >
+
+
+
+
+
+
+
{
+ //setAuthenticationModalOpen(true);
+ console.log("Should enable/disable magic!")
+ console.log("Action: ", selectedAction)
+ if (selectedAction.run_magic_output === undefined) {
+ selectedAction.run_magic_output = true
+ } else {
+ if (selectedAction.run_magic_output === true) {
+ selectedAction.run_magic_output = false
+ } else {
+ selectedAction.run_magic_output = true
}
- }}
- onClose={() => {
- setShowAutocomplete(false)
-
- if (!selectedActionParameters[count].value[selectedActionParameters[count].value.length-1] === ".") {
- setShowDropdown(false)
- }
-
- setUpdate(Math.random())
- }}
- onClick={() => setShowAutocomplete(true)}
- fullWidth
- open={showAutocomplete}
- style={{color: "white", height: 50, marginTop: 2, borderRadius: theme.palette.borderRadius,}}
- onChange={(e) => {
- if (selectedActionParameters[count].value[selectedActionParameters[count].value.length-1] === ".") {
- e.target.value.autocomplete = e.target.value.autocomplete.slice(1, e.target.value.autocomplete.length)
- }
-
- selectedActionParameters[count].value += e.target.value.autocomplete
- selectedAction.parameters[count].value = selectedActionParameters[count].value
- setSelectedAction(selectedAction)
- setUpdate(Math.random())
-
- setShowDropdown(false)
- }}
- >
- {jsonList.map(data => {
- const iconStyle = {
- marginRight: 15,
- }
-
- const icon = data.type === "value" ? : data.type === "list" ? :
- return (
- {}}>
-
-
- {icon} {data.name}
-
-
-
- )
- })}
-
-
- : null}
- {showDropdown && showDropdownNumber === count && data.variant === "STATIC_VALUE" && jsonList.length === 0 ?
-
- : null}
-
-
-
- )})}
-
- )
- }
- return null
- }
-
- const expansionModal =
-
{
- setExpansionModalOpen(false)
- }}
- PaperProps={{
- style: {
- backgroundColor: theme.palette.surfaceColor,
- color: "white",
- minWidth: 600,
- padding: 50,
- },
- }}
- >
- Workflow Variable
-
- Hello
-
-
-
- //const CustomPopper = function (props) {
- // const classes = useStyles()
- // return
- //}
-
- const baselabel = selectedAction.label
- return (
-
- {expansionModal}
- {hideExtraTypes === true ? null :
-
-
-
-
{(selectedAction.app_name.charAt(0).toUpperCase()+selectedAction.app_name.substring(1)).replaceAll("_", " ")}
-
-
{
- console.log("FIND EXAMPLE RESULTS FOR ", selectedAction)
- if (workflowExecutions.length > 0) {
- // Look for the ID
- const found = false
- for (var key in workflowExecutions) {
- if (workflowExecutions[key].results === undefined || workflowExecutions[key].results === null) {
- continue
}
- var foundResult = workflowExecutions[key].results.find(result => result.action.id === selectedAction.id)
- if (foundResult === undefined || foundResult === null) {
- continue
- }
+ setSelectedAction(selectedAction)
+ setUpdate(Math.random());
+ }}
+ >
+
+
+
+
- setSelectedResult(foundResult)
+
+
+
+ {/*selectedAction.id === workflow.start ? null :
- if (setCodeModalOpen !== undefined) {
- setCodeModalOpen(true)
- }
- break
- }
- }
- }}>
-
-
-
-
-
{
- setAuthenticationModalOpen(true)
- }}>
-
-
-
-
-
{}}>
-
-
-
-
-
-
-
-
-
- {/*selectedAction.id === workflow.start ? null :
{
defineStartnode(e)
@@ -1526,139 +2402,255 @@ const ParsedAction = (props) => {
*/}
- {selectedApp.versions !== null && selectedApp.versions !== undefined && selectedApp.versions.length > 1 ?
- {
- const newversion = selectedApp.versions.find(tmpApp => tmpApp.version == event.target.value)
- console.log("NEWVERSION: ", newversion)
- if (newversion !== undefined && newversion !== null) {
- getApp(newversion.id, true)
- }
- }}
- style={{marginTop: 10, backgroundColor: theme.palette.surfaceColor, backgroundColor: theme.palette.inputColor, color: "white", height: 35, marginleft: 10, borderRadius: theme.palette.borderRadius,}}
- SelectDisplayProps={{
- style: {
- marginLeft: 10,
- }
- }}
- >
- {selectedApp.versions.map((data, index) => {
- return (
-
- {data.version}
+ {selectedApp.versions !== null &&
+ selectedApp.versions !== undefined &&
+ selectedApp.versions.length > 1 ? (
+ {
+ console.log("VAL: ", event.target.value)
+ console.log("App: ", selectedApp)
+ const newversion = selectedApp.versions.find(
+ (tmpApp) => tmpApp.version == event.target.value
+ );
-
- )
- })}
-
- : null }
-
-
-
-
- Name
-
-
{
- const name = e.target.value
- console.log("CHANGED FROM2: ", baselabel)
- console.log("CHANGED TO: ", name)
- for (var key in workflow.actions) {
- for (var subkey in workflow.actions[key].parameters) {
- const param = workflow.actions[key].parameters[subkey]
- if (param.value.includes(baselabel)) {
- //if (param.value.toLowerCase().includes(baselabel)) {
- console.log("FOUND: ", param)
- workflow.actions[key].parameters[subkey].value.replaceAll(baselabel, e.target.value)
- }
- }
- }
-
- console.log("DID REPLACE ACTUALLY WORK?? - Something is buggy.")
- setWorkflow(workflow)
- }}
- />
-
- }
- {selectedApp.name !== undefined && selectedAction.authentication !== null && selectedAction.authentication !== undefined && selectedAction.authentication.length === 0 && requiresAuthentication ?
-
-
-
- {
- console.log(authenticationType)
- //if (authenticationType.type === "oauth2" && authenticationType.redirect_uri !== undefined && authenticationType.redirect_uri !== null) {
- // return null
- //}
-
- setAuthenticationModalOpen(true)
- }}>
- Authenticate {selectedApp.name}
-
-
-
-
- : null}
- {selectedAction.authentication !== undefined && selectedAction.authentication !== null && selectedAction.authentication.length > 0 ?
-
- Authentication
+ console.log("NEWVERSION: ", newversion);
+ if (newversion !== undefined && newversion !== null) {
+ getApp(newversion.id, true);
+ }
+ }}
+ style={{
+ marginTop: 10,
+ backgroundColor: theme.palette.surfaceColor,
+ backgroundColor: theme.palette.inputColor,
+ color: "white",
+ height: 35,
+ marginleft: 10,
+ borderRadius: theme.palette.borderRadius,
+ }}
+ SelectDisplayProps={{
+ style: {
+ marginLeft: 10,
+ },
+ }}
+ >
+ {selectedApp.versions.map((data, index) => {
+ return (
+
+ {data.version}
+
+ );
+ })}
+
+ ) : null}
+
+
+
-
{
- if (e.target.value === "No selection") {
- selectedAction.selectedAuthentication = {}
- selectedAction.authentication_id = ""
+
+ Name
+ {
+ const name = e.target.value;
+ console.log("CHANGED FROM2: ", baselabel);
+ console.log("CHANGED TO: ", name);
+ for (var key in workflow.actions) {
+ for (var subkey in workflow.actions[key].parameters) {
+ const param = workflow.actions[key].parameters[subkey];
+ if (param.value.includes(baselabel)) {
+ //if (param.value.toLowerCase().includes(baselabel)) {
+ console.log("FOUND: ", param);
- for (var key in selectedAction.parameters) {
- //console.log(selectedAction.parameters[key])
- if (selectedAction.parameters[key].configuration) {
- selectedAction.parameters[key].value = ""
+ workflow.actions[key].parameters[subkey].value.replaceAll(
+ baselabel,
+ e.target.value
+ );
+ }
}
}
- setSelectedAction(selectedAction)
- setUpdate(Math.random())
- } else {
- //console.log("CHOSE AN AUTHENTICATION OPTION: ", e.target.value)
- selectedAction.selectedAuthentication = e.target.value
- selectedAction.authentication_id = e.target.value.id
- setSelectedAction(selectedAction)
- setUpdate(Math.random())
- }
+
+ console.log("DID REPLACE ACTUALLY WORK?? - Something is buggy.");
+ setWorkflow(workflow);
+ }}
+ />
+
+ {/*!isCloud ? null :*/}
+
+
+
+ Delay
+ {
+ if (actionDelayChange !== undefined) {
+ actionDelayChange(event)
+ }
+ }}
+ />
+
+
+
+ {/**/}
+
+
+ )}
+ {selectedApp.name !== undefined &&
+ selectedAction.authentication !== null &&
+ selectedAction.authentication !== undefined &&
+ selectedAction.authentication.length === 0 &&
+ requiresAuthentication ? (
+
+
+
+ {
+ console.log(authenticationType);
+ //if (authenticationType.type === "oauth2" && authenticationType.redirect_uri !== undefined && authenticationType.redirect_uri !== null) {
+ // return null
+ //}
+
+ setAuthenticationModalOpen(true);
+ }}
+ >
+ Authenticate{" "}
+ {selectedApp.name}
+
+
+
+
+ ) : null}
+ {selectedAction.authentication !== undefined &&
+ selectedAction.authentication !== null &&
+ selectedAction.authentication.length > 0 ? (
+
+
Authentication
+
+
+ labelId="select-app-auth"
+ value={
+ Object.getOwnPropertyNames(
+ selectedAction.selectedAuthentication
+ ).length === 0
+ ? "No selection"
+ : selectedAction.selectedAuthentication
+ }
+ SelectDisplayProps={{
+ style: {
+ marginLeft: 10,
+ maxWidth: 250,
+ },
+ }}
+ fullWidth
+ onChange={(e) => {
+ if (e.target.value === "No selection") {
+ selectedAction.selectedAuthentication = {};
+ selectedAction.authentication_id = "";
-
- No selection
-
- {selectedAction.authentication.map(data => {
- //console.log("AUTH DATA: ", data)
- return(
-
- {data.label} - ({data.app.app_version})
-
- )
- })}
-
+ for (var key in selectedAction.parameters) {
+ //console.log(selectedAction.parameters[key])
+ if (selectedAction.parameters[key].configuration) {
+ selectedAction.parameters[key].value = "";
+ }
+ }
+ setSelectedAction(selectedAction);
+ setUpdate(Math.random());
+ } else {
+ //console.log("CHOSE AN AUTHENTICATION OPTION: ", e.target.value)
+ selectedAction.selectedAuthentication = e.target.value;
+ selectedAction.authentication_id = e.target.value.id;
+ setSelectedAction(selectedAction);
+ setUpdate(Math.random());
+ }
+ }}
+ style={{
+ backgroundColor: theme.palette.inputColor,
+ color: "white",
+ height: 50,
+ maxWidth: rightsidebarStyle.maxWidth - 80,
+ borderRadius: theme.palette.borderRadius,
+ }}
+ >
+
+ No selection
+
+ {selectedAction.authentication.map((data) => {
+ //console.log("AUTH DATA: ", data)
+ return (
+
+ {data.label} - ({data.app.app_version})
+
+ );
+ })}
+
- {/*
+ {/*
setAuthenticationModalOpen(true)}>
AUTHENTICATE
@@ -1666,164 +2658,287 @@ const ParsedAction = (props) => {
curaction.authentication = authenticationOptions
if (curaction.selectedAuthentication === null || curaction.selectedAuthentication === undefined || curaction.selectedAuthentication.length === "")
*/}
-
- {
- setAuthenticationModalOpen(true)
- }}>
-
-
-
-
-
- : null}
+
+ {
+ setAuthenticationModalOpen(true);
+ }}
+ >
+
+
+
+
+
+ ) : null}
- {showEnvironment !== undefined && showEnvironment ?
-
-
- Environment
-
-
1 ? (
+
+ Environment
+ {
+ const env = environments.find((a) => a.Name === e.target.value);
+ setSelectedActionEnvironment(env);
+ selectedAction.environment = env.Name;
+ setSelectedAction(selectedAction);
+ }}
+ style={{
+ backgroundColor: theme.palette.inputColor,
+ color: "white",
+ height: "50px",
+ borderRadius: theme.palette.borderRadius,
+ }}
+ >
+ {environments.map((data, index) => {
+ if (data.archived === true) {
+ return null;
+ }
- }
- }}
- fullWidth
- onChange={(e) => {
- const env = environments.find(a => a.Name === e.target.value)
- setSelectedActionEnvironment(env)
- selectedAction.environment = env.Name
- setSelectedAction(selectedAction)
- }}
- style={{backgroundColor: theme.palette.inputColor, color: "white", height: "50px", borderRadius: theme.palette.borderRadius,}}
- >
- {environments.map(data => {
- if (data.archived) {
- return null
- }
-
- return (
-
- {data.Name}
-
- )
- })}
-
-
- : null}
+ return (
+
+ {data.Name}
+
+ );
+ })}
+
+
+ ) : null}
- {workflow.execution_variables !== undefined && workflow.execution_variables !== null && workflow.execution_variables.length > 0 ?
-
-
- Set execution variable (optional)
-
-
0 ? (
+
+
Execution variable (optional)
+
{
- if (e.target.value === "No selection") {
- selectedAction.execution_variable = {"name": "No selection"}
- } else {
- const value = workflow.execution_variables.find(a => a.name === e.target.value)
- selectedAction.execution_variable = value
- }
- setSelectedAction(selectedAction)
- setUpdate(Math.random())
- }}
- style={{backgroundColor: theme.palette.inputColor, color: "white", height: "50px", borderRadius: theme.palette.borderRadius,}}
- >
-
- No selection
-
-
- {workflow.execution_variables.map(data => (
-
- {data.name}
-
- ))}
-
-
- : null}
+ value={
+ selectedAction.execution_variable !== undefined
+ && selectedAction.execution_variable !== null
+ && selectedAction.execution_variable.name !== undefined
+ && selectedAction.execution_variable.name !== null
+ && selectedAction.execution_variable.name.length > 0
+ ? selectedAction.execution_variable.name
+ : "No selection"
+ }
+ SelectDisplayProps={{
+ style: {
+ marginLeft: 10,
+ },
+ }}
+ fullWidth
+ onChange={(e) => {
+ if (e.target.value === "No selection") {
+ selectedAction.execution_variable = { name: "No selection" };
+ } else {
+ const value = workflow.execution_variables.find(
+ (a) => a.name === e.target.value
+ );
+ selectedAction.execution_variable = value;
+ }
+ setSelectedAction(selectedAction);
+ setUpdate(Math.random());
+ }}
+ style={{
+ backgroundColor: theme.palette.inputColor,
+ color: "white",
+ height: "50px",
+ borderRadius: theme.palette.borderRadius,
+ }}
+ >
+
+ No selection
+
+
+ {workflow.execution_variables.map((data) => (
+
+ {data.name}
+
+ ))}
+
+
+ ) : null}
-
-
- {/*hideExtraTypes ? null :
+
+
+ {/*hideExtraTypes ? null :
Actions
*/}
- {setNewSelectedAction !== undefined ?
-
{
- if (option === undefined || option === null || option.name === undefined || option.name === undefined) {
- return null
+ {setNewSelectedAction !== undefined ? (
+ {
+ if (
+ option === undefined ||
+ option === null ||
+ option.name === undefined ||
+ option.name === null
+ ) {
+ return null;
+ }
+
+ const newname = (
+ option.name.charAt(0).toUpperCase() + option.name.substring(1)
+ ).replaceAll("_", " ");
+ return newname;
+ }}
+ options={sortByKey(selectedApp.actions, "label")}
+ fullWidth
+ style={{
+ backgroundColor: theme.palette.inputColor,
+ height: 50,
+ borderRadius: theme.palette.borderRadius,
+ }}
+ onChange={(event, newValue) => {
+ // Workaround with event lol
+ if (newValue !== undefined && newValue !== null) {
+ setNewSelectedAction({ target: { value: newValue.name } });
+ }
+ }}
+ renderOption={(data) => {
+ var newActionname = data.name;
+ if (
+ data.label !== undefined &&
+ data.label !== null &&
+ data.label.length > 0
+ ) {
+ newActionname = data.label;
+ }
+
+ var newActiondescription = data.description;
+ //console.log("DESC: ", newActiondescription)
+ if (
+ data.description === undefined || data.description === null
+ ) {
+ newActiondescription = "Description: No description defined for this action"
+ } else {
+ newActiondescription = "Description: "+newActiondescription
}
- const newname = (option.name.charAt(0).toUpperCase()+option.name.substring(1)).replaceAll("_", " ")
- return newname
- }}
- options={sortByKey(selectedApp.actions, "label")}
- fullWidth
- style={{backgroundColor: theme.palette.inputColor, height: 50, borderRadius: theme.palette.borderRadius,}}
- onChange={(event, newValue) => {
- // Workaround with event lol
- if (newValue !== undefined && newValue !== null) {
- setNewSelectedAction({"target": {"value": newValue.name}})
- }
+ const iconInfo = GetIconInfo({ name: data.name });
+ const useIcon = iconInfo.originalIcon;
- }}
- renderOption={(data) => {
- var newActionname = data.name
- if (data.label !== undefined && data.label !== null && data.label.length > 0) {
- newActionname = data.label
+ newActionname = (
+ newActionname.charAt(0).toUpperCase() +
+ newActionname.substring(1)
+ ).replaceAll("_", " ");
+
+ return (
+
+
+
+ {useIcon}
+
+ {newActionname}
+
+
+ );
+ }}
+ renderInput={(params) => {
+ if (params.inputProps !== undefined && params.inputProps !== null && params.inputProps.value !== undefined && params.inputProps.value !== null) {
+ const prefixes = ["Post", "Put", "Patch"]
+ for (var key in prefixes) {
+ if (params.inputProps.value.startsWith(prefixes[key])) {
+ params.inputProps.value = params.inputProps.value.replace(prefixes[key]+" ", "", -1)
+ if (params.inputProps.value.length > 1) {
+ params.inputProps.value = params.inputProps.value.charAt(0).toUpperCase()+params.inputProps.value.substring(1)
+ }
+ break
+ }
+ }
}
- const iconInfo = GetIconInfo({"name": data.name})
- const useIcon = iconInfo.originalIcon
+ return (
+
+ );
+ }}
+ />
+ ) : null}
- newActionname = (newActionname.charAt(0).toUpperCase()+newActionname.substring(1)).replaceAll("_", " ")
-
- return (
-
- {useIcon}
- {newActionname}
-
- )
- }}
- renderInput={(params) => {
- return (
-
- )
- }}
- />
-
- : null}
-
- {/*setNewSelectedAction !== undefined ?
+ {/*setNewSelectedAction !== undefined ?
{
: null*/}
- {selectedAction.description !== undefined && selectedAction.description.length > 0 && hideExtraTypes !== true ?
-
- {selectedAction.description}
-
: null}
-
-
-
-
- )
- }
-
+
+
+
+ );
+};
export default ParsedAction;
diff --git a/frontend/src/components/RenderCytoscape.js b/frontend/src/components/RenderCytoscape.js
index 3a88dcf8..18c9815c 100644
--- a/frontend/src/components/RenderCytoscape.js
+++ b/frontend/src/components/RenderCytoscape.js
@@ -1,79 +1,82 @@
-import React, {useState, useEffect, useLayoutEffect} from 'react';
-import * as cytoscape from 'cytoscape';
-import CytoscapeComponent from 'react-cytoscapejs';
-import cystyle from '../defaultCytoscapeStyle';
+import React, { useState, useEffect, useLayoutEffect } from "react";
+import * as cytoscape from "cytoscape";
+import CytoscapeComponent from "react-cytoscapejs";
+import cystyle from "../defaultCytoscapeStyle";
-const surfaceColor = "#27292D"
+const surfaceColor = "#27292D";
const CytoscapeWrapper = (props) => {
const { globalUrl, inworkflow } = props;
- const [elements, setElements] = useState([])
- const [workflow, setWorkflow] = useState(inworkflow)
- const [cy, setCy] = React.useState()
- const bodyWidth = 200
- const bodyHeight = 150
+ const [elements, setElements] = useState([]);
+ const [workflow, setWorkflow] = useState(inworkflow);
+ const [cy, setCy] = React.useState();
+ const bodyWidth = 200;
+ const bodyHeight = 150;
- const setupGraph = () => {
- const actions = workflow.actions.map(action => {
- const node = {}
- node.position = action.position
- node.data = action
+ const setupGraph = () => {
+ const actions = workflow.actions.map((action) => {
+ const node = {};
+ node.position = action.position;
+ node.data = action;
- node.data._id = action["id"]
- node.data.type = "ACTION"
- node.isStartNode = action["id"] === workflow.start
+ node.data._id = action["id"];
+ node.data.type = "ACTION";
+ node.isStartNode = action["id"] === workflow.start;
+ var example = "";
+ if (
+ action.example !== undefined &&
+ action.example !== null &&
+ action.example.length > 0
+ ) {
+ example = action.example;
+ }
- var example = ""
- if (action.example !== undefined && action.example !== null && action.example.length > 0) {
- example = action.example
- }
+ node.data.example = example;
+ return node;
+ });
- node.data.example = example
- return node;
- })
+ const triggers = workflow.triggers.map((trigger) => {
+ const node = {};
+ node.position = trigger.position;
+ node.data = trigger;
- const triggers = workflow.triggers.map(trigger => {
- const node = {}
- node.position = trigger.position
- node.data = trigger
+ node.data._id = trigger["id"];
+ node.data.type = "TRIGGER";
- node.data._id = trigger["id"]
- node.data.type = "TRIGGER"
+ return node;
+ });
- return node;
- })
+ // FIXME - tmp branch update
+ var insertedNodes = [].concat(actions, triggers);
+ const edges = workflow.branches.map((branch, index) => {
+ //workflow.branches[index].conditions = [{
- // FIXME - tmp branch update
- var insertedNodes = [].concat(actions, triggers)
- const edges = workflow.branches.map((branch, index) => {
- //workflow.branches[index].conditions = [{
+ const edge = {};
+ var conditions = workflow.branches[index].conditions;
+ if (conditions === undefined || conditions === null) {
+ conditions = [];
+ }
- const edge = { };
- var conditions = workflow.branches[index].conditions
- if (conditions === undefined || conditions === null) {
- conditions = []
- }
+ var label = "";
+ if (conditions.length === 1) {
+ label = conditions.length + " condition";
+ } else if (conditions.length > 1) {
+ label = conditions.length + " conditions";
+ }
- var label = ""
- if (conditions.length === 1) {
- label = conditions.length+" condition"
- } else if (conditions.length > 1) {
- label = conditions.length+" conditions"
- }
+ edge.data = {
+ id: branch.id,
+ _id: branch.id,
+ source: branch.source_id,
+ target: branch.destination_id,
+ label: label,
+ conditions: conditions,
+ hasErrors: branch.has_errors,
+ };
- edge.data = {
- id: branch.id,
- _id: branch.id,
- source: branch.source_id,
- target: branch.destination_id,
- label: label,
- conditions: conditions,
- hasErrors: branch.has_errors
- };
-
- // This is an attempt at prettier edges. The numbers are weird to work with.
- /*
+ // This is an attempt at prettier edges. The numbers are weird to work with.
+ /*
//http://manual.graphspace.org/projects/graphspace-python/en/latest/demos/edge-types.html
const sourcenode = actions.find(node => node.data._id === branch.source_id)
const destinationnode = actions.find(node => node.data._id === branch.destination_id)
@@ -96,51 +99,59 @@ const CytoscapeWrapper = (props) => {
}
*/
- return edge;
- })
+ return edge;
+ });
- setWorkflow(workflow)
+ setWorkflow(workflow);
- // Verifies if a branch is valid and skips others
- var newedges = []
- for (var key in edges) {
- var item = edges[key]
+ // Verifies if a branch is valid and skips others
+ var newedges = [];
+ for (var key in edges) {
+ var item = edges[key];
- const sourcecheck = insertedNodes.find(data => data.data.id === item.data.source)
- const destcheck = insertedNodes.find(data => data.data.id === item.data.target)
- if (sourcecheck === undefined || destcheck === undefined) {
- continue
- }
+ const sourcecheck = insertedNodes.find(
+ (data) => data.data.id === item.data.source
+ );
+ const destcheck = insertedNodes.find(
+ (data) => data.data.id === item.data.target
+ );
+ if (sourcecheck === undefined || destcheck === undefined) {
+ continue;
+ }
- newedges.push(item)
- }
+ newedges.push(item);
+ }
- insertedNodes = insertedNodes.concat(newedges)
- setElements(insertedNodes)
- }
+ insertedNodes = insertedNodes.concat(newedges);
+ setElements(insertedNodes);
+ };
- if (elements.length === 0) {
- setupGraph()
- }
+ if (elements.length === 0) {
+ setupGraph();
+ }
- return (
- {
- // FIXME: There's something specific loading when
- // you do the first hover of a node. Why is this different?
- //console.log("CY: ", incy)
- setCy(incy)
- }}
- />
- )
-}
+ return (
+ {
+ // FIXME: There's something specific loading when
+ // you do the first hover of a node. Why is this different?
+ //console.log("CY: ", incy)
+ setCy(incy);
+ }}
+ />
+ );
+};
-export default CytoscapeWrapper
+export default CytoscapeWrapper;
diff --git a/frontend/src/components/ScrollToTop.jsx b/frontend/src/components/ScrollToTop.jsx
index 88d2d158..f166cf1f 100644
--- a/frontend/src/components/ScrollToTop.jsx
+++ b/frontend/src/components/ScrollToTop.jsx
@@ -1,25 +1,30 @@
-import { useEffect } from 'react';
-import { withRouter } from 'react-router-dom';
+import { useEffect } from "react";
+//import { withRouter } from "react-router-dom";
+import { useLocation } from "react-router-dom";
+
+function ScrollToTop({ getUserNotifications, setCurpath, history }) {
+ let location = useLocation();
-function ScrollToTop({getUserNotifications, setCurpath, history }) {
useEffect(() => {
- const unlisten = history.listen(() => {
- window.scroll({
- top: 0,
- left: 0,
- behavior: "smooth",
- });
+ //const unlisten = history.listen(() => {
+ window.scroll({
+ top: 0,
+ left: 0,
+ behavior: "smooth",
+ });
- setCurpath(window.location.pathname)
- getUserNotifications()
- });
- return () => {
- unlisten();
- }
- }, []);
+ setCurpath(window.location.pathname);
+ getUserNotifications();
+ //});
+ //return () => {
+ // unlisten();
+ //};
+ }, [location]);
- return (null);
+ return null;
}
// https://stackoverflow.com/questions/36904185/react-router-scroll-to-top-on-every-transition
-export default withRouter(ScrollToTop);
+//export default withRouter(ScrollToTop);
+// https://v5.reactrouter.com/web/api/Hooks/uselocation
+export default ScrollToTop;
diff --git a/frontend/src/components/SecurityFramework.jsx b/frontend/src/components/SecurityFramework.jsx
new file mode 100644
index 00000000..cfe5196d
--- /dev/null
+++ b/frontend/src/components/SecurityFramework.jsx
@@ -0,0 +1,245 @@
+import React, {useState } from 'react';
+import {isMobile} from "react-device-detect";
+import DetectionFramework, { usecases } from "../components/DetectionFramework.jsx";
+import {Link} from 'react-router-dom';
+import ReactGA from 'react-ga';
+
+import { Button, LinearProgress, Typography } from '@material-ui/core';
+
+export const securityFramework = [
+ {
+ image: ,
+ text: "Cases",
+ description: "Case management"
+ },
+ {
+ image:
+ ,
+ text: "SIEM",
+ description: "Case management"
+ },
+ {
+ image:
+ ,
+ text: "Assets",
+ description: "Case management"
+ },
+ {
+ image:
+ ,
+ text: "IAM",
+ description: "Case management"
+ },
+ {
+ image: ,
+ text: "Intel",
+ description: "Case management"
+ },
+ {
+ image:
+ ,
+ text: "Comms",
+ description: "Case management"
+ },
+ {
+ image:
+ ,
+ text: "Network",
+ description: "Case management"
+ },
+ {
+ image:
+ ,
+ text: "EDR & AV",
+ description: "Case management"
+ },
+]
+
+const LandingpageUsecases = (props) => {
+ const [selectedUsecase, setSelectedUsecase] = useState("Phishing")
+ const usecasekeys = usecases === undefined || usecases === null ? [] : Object.keys(usecases)
+ const buttonBackground = "linear-gradient(to right, #f86a3e, #f34079)"
+ const buttonStyle = {borderRadius: 25, height: 50, width: 260, margin: isMobile ? "15px auto 15px auto" : 20, fontSize: 18, backgroundImage: buttonBackground}
+
+ const HandleTitle = (props) => {
+ const { usecases, selectedUsecase, setSelecedUsecase } = props
+ const [progress, setProgress] = useState(0)
+
+ React.useEffect(() => {
+ const timer = setInterval(() => {
+ setProgress((oldProgress) => {
+ if (oldProgress >= 105) {
+ const foundIndex = usecasekeys.findIndex(key => key === selectedUsecase)
+ var newitem = usecasekeys[foundIndex+1]
+ if (newitem === undefined || newitem === 0) {
+ newitem = usecasekeys[1]
+ }
+
+ setSelectedUsecase(newitem)
+ return -18
+ }
+
+ if (oldProgress >= 65) {
+ return oldProgress + 3
+ }
+
+ if (oldProgress >= 80) {
+ return oldProgress + 1
+ }
+
+ return oldProgress + 6
+ })
+ }, 165)
+
+ return () => {
+ clearInterval(timer)
+ }
+ }, [])
+
+ if (usecases === null || usecases === undefined || usecases.length === 0) {
+ return null
+ }
+
+ const modifier = isMobile ? 17 : 22
+ return (
+
+ Handle
+
+ {selectedUsecase}
+
+
+ with confidence
+
+ )
+ }
+
+ const parsedWidth = isMobile ? "100%" : 1100
+ return (
+
+
+
+
+
+
+
+ {/*Security Automation is Hard */}
+
+
+ Connecting your everchanging environment is hard. We get it! That's why we built Shuffle, where you can use and share your security workflows to everyones benefit.
+ {/*Shuffle is an automation platform where you don't need to be an expert to automate. Get access to our large pool of security playbooks, apps and people.*/}
+
+
+ {isMobile ? null :
+
+ {
+ ReactGA.event({
+ category: "landingpage",
+ action: "click_main_pricing",
+ label: "",
+ })
+ }}
+ style={{
+ borderRadius: 25, height: 40, width: 175, margin: "15px 0px 15px 0px", fontSize: 14, color: "white", backgroundImage: buttonBackground, marginRight: 10,
+ }}>
+ See Pricing
+
+
+ }
+ {isMobile ? null :
+
+ {
+ ReactGA.event({
+ category: "landingpage",
+ action: "click_main_try_it_out",
+ label: "",
+ })
+ }}
+ style={{
+ borderRadius: 25, height: 40, width: 175, margin: "15px 0px 15px 0px", fontSize: 14, color: "white", backgroundImage: buttonBackground,
+ }}>
+ Start for free
+
+
+ }
+
+
+ {isMobile ? null :
+
+
+
+ }
+ {isMobile ? null :
+
+ }
+
+
+ {isMobile ?
+
+ {
+ ReactGA.event({
+ category: "landingpage",
+ action: "click_main_pricing",
+ label: "",
+ })
+ }}
+ >
+ See pricing
+
+
+ : null
+ }
+ {/*isMobile ?
+
+ {
+ ReactGA.event({
+ category: "landingpage",
+ action: "click_main_features",
+ label: "",
+ })
+ }}
+ color="secondary"
+ style={buttonStyle}>
+ Features
+
+
+ : null*/}
+
+ {isMobile ? null :
+
+ {securityFramework.map((data, index) => {
+ return (
+
+
+
+ {data.image}
+
+
+
+ {data.text}
+
+
+ )
+ })}
+
+ }
+
+ )
+}
+
+export default LandingpageUsecases;
diff --git a/frontend/src/components/SettingsPopup.js b/frontend/src/components/SettingsPopup.js
index f493b1a5..3eb38aa5 100644
--- a/frontend/src/components/SettingsPopup.js
+++ b/frontend/src/components/SettingsPopup.js
@@ -1,141 +1,177 @@
-import React, {useState} from 'react';
+import React, { useState } from "react";
-import DialogTitle from '@material-ui/core/DialogTitle';
-import Dialog from '@material-ui/core/Dialog';
-import TextField from '@material-ui/core/TextField';
-import Button from '@material-ui/core/Button';
-import Divider from '@material-ui/core/Divider';
+import DialogTitle from "@material-ui/core/DialogTitle";
+import Dialog from "@material-ui/core/Dialog";
+import TextField from "@material-ui/core/TextField";
+import Button from "@material-ui/core/Button";
+import Divider from "@material-ui/core/Divider";
+const SettingsDialog = (props) => {
+ const {
+ classes,
+ onClose,
+ settingsOpen,
+ settingsData,
+ globalUrl,
+ isLoggedIn,
+ setIsLoggedIn,
+ ...other
+ } = props;
-const SettingsDialog = props => {
- const { classes, onClose, settingsOpen, settingsData, globalUrl, isLoggedIn, setIsLoggedIn, ...other } = props;
-
const [password1, setPassword1] = useState("");
const [password2, setPassword2] = useState("");
const [password3, setPassword3] = useState("");
- const handleValidateForm = () => {
- var passlength = 10
- if (password1 === password2 && password1.length >= passlength && password3.length >= passlength) {
- return true
- }
+ const handleValidateForm = () => {
+ var passlength = 10;
+ if (
+ password1 === password2 &&
+ password1.length >= passlength &&
+ password3.length >= passlength
+ ) {
+ return true;
+ }
- return false
- }
+ return false;
+ };
- const onChangePass1 = (e) => {
- setPassword1(e.target.value)
- }
+ const onChangePass1 = (e) => {
+ setPassword1(e.target.value);
+ };
- const onChangePass2 = (e) => {
- setPassword2(e.target.value)
- }
+ const onChangePass2 = (e) => {
+ setPassword2(e.target.value);
+ };
- const onChangePass3 = (e) => {
- setPassword3(e.target.value)
- }
+ const onChangePass3 = (e) => {
+ setPassword3(e.target.value);
+ };
- const onSubmitPassReset = () => {
- console.log("Should change password")
- // Rofl, this can't possibly be typesafe
- var data = '{"password1": "'+password1+'", "password2": "'+password2+'", "password3": "'+password3+'"}'
+ const onSubmitPassReset = () => {
+ console.log("Should change password");
+ // Rofl, this can't possibly be typesafe
+ var data =
+ '{"password1": "' +
+ password1 +
+ '", "password2": "' +
+ password2 +
+ '", "password3": "' +
+ password3 +
+ '"}';
- fetch(globalUrl+"/passwordreset", {
- body: data,
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
+ fetch(globalUrl + "/passwordreset", {
+ body: data,
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
})
- .then((response) => response.json())
- .then((responseJson) => {
- console.log(responseJson)
- if (responseJson.status === true) {
- console.log("SUCCESS")
- }
- })
- .catch(error => {
- console.log(error)
- });
- }
-
- //PaperProps={{style: {minWidth: "500px"}}
- return(
- onClose()} {...other}>
- Settings
-
-
-
- Username
-
- {settingsData.username}
-
-
-
- ApiKey
-
-
-
-
-
-
- );
-}
+ .then((response) => response.json())
+ .then((responseJson) => {
+ console.log(responseJson);
+ if (responseJson.status === true) {
+ console.log("SUCCESS");
+ }
+ })
+ .catch((error) => {
+ console.log(error);
+ });
+ };
+
+ //PaperProps={{style: {minWidth: "500px"}}
+ return (
+ onClose()} {...other}>
+ Settings
+
+
+
Username
+ {settingsData.username}
+
+
+
ApiKey
+
+
+
+
+
+ );
+};
export default SettingsDialog;
diff --git a/frontend/src/components/ShuffleCodeEditor.jsx b/frontend/src/components/ShuffleCodeEditor.jsx
new file mode 100644
index 00000000..5afb4bd3
--- /dev/null
+++ b/frontend/src/components/ShuffleCodeEditor.jsx
@@ -0,0 +1,689 @@
+import React, {useState, useEffect, useLayoutEffect} from 'react';
+import {
+ IconButton,
+ Dialog,
+ Modal,
+ Tooltip,
+ DialogTitle,
+ DialogContent,
+ Typography,
+ Paper
+} from '@material-ui/core';
+
+import Checkbox from '@mui/material/Checkbox';
+import { orange } from '@mui/material/colors';
+import { isMobile } from "react-device-detect"
+
+import {
+ FullscreenExit as FullscreenExitIcon,
+} from "@material-ui/icons";
+
+import {
+ AutoFixHigh as AutoFixHighIcon, CompressOutlined,
+} from '@mui/icons-material';
+
+import { useTheme } from '@material-ui/core/styles';
+import { validateJson } from "../views/Workflows.jsx";
+import ReactJson from "react-json-view";
+import PaperComponent from "../components/PaperComponent.jsx";
+
+import CodeMirror from '@uiw/react-codemirror';
+import 'codemirror/keymap/sublime';
+import 'codemirror/addon/selection/mark-selection.js'
+import 'codemirror/theme/gruvbox-dark.css';
+import 'codemirror/theme/duotone-light.css';
+import { padding, textAlign } from '@mui/system';
+
+const CodeEditor = (props) => {
+ const { fieldCount, setFieldCount, actionlist, changeActionParameterCodeMirror, expansionModalOpen, setExpansionModalOpen, codedata, setcodedata } = props
+ const [localcodedata, setlocalcodedata] = React.useState(codedata === undefined || codedata === null || codedata.length === 0 ? "" : codedata);
+ // const {codelang, setcodelang} = props
+ const theme = useTheme();
+ const [validation, setValidation] = React.useState(false);
+ const [expOutput, setExpOutput] = React.useState(" ");
+ const [linewrap, setlinewrap] = React.useState(true);
+ const [codeTheme, setcodeTheme] = React.useState("gruvbox-dark");
+ const [editorPopupOpen, setEditorPopupOpen] = React.useState(false);
+
+ const [currentCharacter, setCurrentCharacter] = React.useState(-1);
+ const [currentLine, setCurrentLine] = React.useState(-1);
+
+ const [variableOccurences, setVariableOccurences] = React.useState([]);
+ const [currentLocation, setCurrentLocation] = React.useState([]);
+ const [currentVariable, setCurrentVariable] = React.useState("");
+ // useEffect(() => {
+ // console.log(currentLocation)
+ // }, [currentLocation])
+
+ // const [allVariable, setAllVariable] = React.useState([]);
+ var allVariable = []
+ var mainVariables = []
+
+ // console.log(actionlist.length)
+ for(var i=0; i {
+ // console.log(data)
+ // console.log(actionlist.length)
+ // console.log(data.autocomplete)
+ // allVariable.push('$'+data.autocomplete.substring(0, 25))
+ // return (
+ //
+ // {
+ // replaceVariables(data.autocomplete)
+ // // console.log(currentCharacter, currentLine)
+ // }}
+ // style={{
+ // backgroundColor: 'transparent',
+ // color: 'white',
+ // border: 'none',
+ // padding: 7.5,
+ // cursor: 'pointer',
+ // width: '100%',
+ // textAlign: 'left'
+ // }}
+ // >
+ // ${data.autocomplete.substring(0, 25)}
+ // {Object.keys(data.example).forEach(key => key)}
+ //
+ //
+ // )
+ // })}
+
+ const autoFormat = (input) => {
+ if (validation !== true) {
+ return
+ }
+
+ try {
+ input = JSON.stringify(JSON.parse(input), null, 4)
+ } catch (e) {
+ console.log("Failed magic JSON stringification: ", e)
+ }
+
+ if (input !== localcodedata) {
+ setlocalcodedata(input)
+ }
+ }
+
+ function findIndex(line, loc) {
+ // var temp_arr = []
+ // for(var i=0; i action.autocomplete.toLowerCase() === variable_occurence[occ].slice(1,).toLowerCase())
+ var correctVariable = allVariable.includes(variable_occurence[occ].toLowerCase())
+ // console.log(actionlist)
+ if(!correctVariable) {
+ value.markText({line:i, ch:dollar_occurence[occ]}, {line:i, ch:dollar_occurence_len[occ]+dollar_occurence[occ]}, {"css": "background-color: rgb(248, 106, 62, 0.9); padding-top: 2px; padding-bottom: 2px; color: white"})
+ }
+ else{
+ value.markText({line:i, ch:dollar_occurence[occ]}, {line:i, ch:dollar_occurence_len[occ]+dollar_occurence[occ]}, {"css": "background-color: #8b8e26; padding-top: 2px; padding-bottom: 2px; color: white"})
+ }
+ // console.log(correctVariables)
+ }
+ } catch (e) {}
+ }
+ }
+
+ // function findlocation(arr) {
+ // for(var i=0; i {
+ console.log("In closer")
+ changeActionParameterCodeMirror({target: {value: ""}}, fieldCount, localcodedata)
+ //setExpansionModalOpen(false)
+ }}
+ PaperComponent={PaperComponent}
+ aria-labelledby="draggable-dialog-title"
+ PaperProps={{
+ style: {
+ backgroundColor: theme.palette.surfaceColor,
+ color: "white",
+ minWidth: isMobile ? "100%" : 600,
+ padding: isMobile ? "25px 10px 25px 10px" : 25,
+ border: theme.palette.defaultBorder,
+ zIndex: 10012,
+ },
+ }}
+ >
+
+
+
+ Code Editor
+
+
{
+ autoFormat(localcodedata)
+ }}
+ >
+
+
+
+
+
+
+
+
+ {
+ // console.log(value.getCursor())
+ setCurrentCharacter(value.getCursor().ch)
+ setCurrentLine(value.getCursor().line)
+ // console.log(value.getCursor().ch, value.getCursor().line)
+ findIndex(value.getCursor().line, value.getCursor().ch)
+ highlight_variables(value)
+ }}
+ onChange={(value) => {
+ setlocalcodedata(value.getValue())
+ expectedOutput(value.getValue())
+ // console.log(allVariable)
+ // console.log(value.getValue().split('\n')[value.getCursor().line])
+ // console.log(value.getCursor())
+ // console.log(value)
+ // console.log(value.getValue().indexOf('$'))
+ // console.log(value.display.input.prevInput)
+ if(value.display.input.prevInput.startsWith('$') || value.display.input.prevInput.endsWith('$')){
+ setEditorPopupOpen(true)
+ // console.log(findIndex(value.getValue()))
+ // console.log(findlocation(findIndex(value.getValue())))
+ // setCurrentLocation(findlocation(findIndex(value.getValue())))
+ // console.log(currentLocation)
+ // console.log(findVariables(findlocation(findIndex(value.getValue())), value.getValue()))
+ // setCurrentVariable(findVariables(findlocation(findIndex(value.getValue())), value.getValue()))
+ // console.log(actionlist)
+ }
+ // setVariableOccurences(findIndex(value.getValue()))
+ // setCurrentVariable(findVariables(value.getValue()))
+ // console.log(currentLocation)
+ // console.log(currentVariable)
+ // console.log(findIndex(value.getValue()))
+ // highlight_variables(value)
+ }}
+ options={{
+ styleSelectedText: true,
+ theme: codeTheme,
+ keyMap: 'sublime',
+ mode: 'javascript',
+ lineWrapping: linewrap,
+ // mode: {codelang},
+ }}
+ />
+
+ {editorPopupOpen ?
+
+ {mainVariables.map((data, index) => {
+ // console.log(data)
+ return (
+
+ {
+ replaceVariables(data.substring(1,))
+ // console.log(currentCharacter, currentLine)
+ }}
+ style={{
+ backgroundColor: 'transparent',
+ color: 'white',
+ border: 'none',
+ padding: 7.5,
+ cursor: 'pointer',
+ width: '100%',
+ textAlign: 'left'
+ }}
+ >
+ {data.substring(0, 25)}
+ {/* {Object.keys(data.example).forEach(key => key)} */}
+
+
+ )
+ })}
+
+ : null}
+
+
+ {/*
+
+ Line Wrap
+ {
+ if (linewrap) {
+ setlinewrap(false)
+ }
+ if (!linewrap){
+ setlinewrap(true)
+ }
+ }}
+ defaultChecked
+ size="small"
+ sx={{
+ color: orange[600],
+ '&.Mui-checked': {
+ color: orange[800],
+ },
+ }}
+ />
+
+
+
+ Dark Theme
+ {
+ if (codeTheme === "gruvbox-dark") {
+ setcodeTheme("duotone-light")
+ }
+ if (codeTheme === "duotone-light"){
+ setcodeTheme("gruvbox-dark")
+ }
+ }}
+ defaultChecked
+ size="small"
+ sx={{
+ color: orange[600],
+ '&.Mui-checked': {
+ color: orange[800],
+ },
+ }}
+ />
+
+ */}
+
+
+
+
+ {isMobile ? null :
+
+
+ Output
+
+
+ }
+ {isMobile ? null :
+ validation === true ?
+
{
+ //handleReactJsonClipboard(copy);
+ }}
+ displayDataTypes={false}
+ onSelect={(select) => {
+ //HandleJsonCopy(validate.result, select, "exec");
+ }}
+ name={"JSON autocompletion"}
+ />
+ :
+
+ {expOutput}
+
+ }
+
+ JSON Validation: {validation ? "Correct" : "Incorrect"}
+
+
+
+
+ {
+ setExpansionModalOpen(false)
+ }}
+ >
+ Cancel
+
+ {
+ // console.log(codedata)
+ // console.log(fieldCount)
+ changeActionParameterCodeMirror(event, fieldCount, localcodedata)
+ setExpansionModalOpen(false)
+ setcodedata(localcodedata)
+ }}
+ >
+ Done
+
+
+ )
+}
+
+export default CodeEditor;
diff --git a/frontend/src/components/WorkflowPaper.jsx b/frontend/src/components/WorkflowPaper.jsx
new file mode 100644
index 00000000..5d3b68cb
--- /dev/null
+++ b/frontend/src/components/WorkflowPaper.jsx
@@ -0,0 +1,287 @@
+import React, { useState, useEffect, useLayoutEffect } from "react";
+import theme from '../theme';
+
+import {
+ Chip,
+ Typography,
+ Paper,
+ Avatar,
+ Grid,
+ Tooltip,
+} from "@material-ui/core";
+
+import {
+ AvatarGroup,
+} from "@mui/material"
+
+import {
+ Restore as RestoreIcon,
+ Edit as EditIcon,
+ BubbleChart as BubbleChartIcon,
+ MoreVert as MoreVertIcon,
+} from '@material-ui/icons';
+
+import { useNavigate, Link, useParams } from "react-router-dom";
+
+const workflowActionStyle = {
+ display: "flex",
+ width: 160,
+ height: 44,
+ justifyContent: "space-between",
+}
+
+const paperAppStyle = {
+ minHeight: 130,
+ maxHeight: 130,
+ overflow: "hidden",
+ width: "100%",
+ color: "white",
+ backgroundColor: theme.palette.surfaceColor,
+ padding: "12px 12px 0px 15px",
+ borderRadius: 5,
+ display: "flex",
+ boxSizing: "border-box",
+ position: "relative",
+}
+
+const chipStyle = {
+ backgroundColor: "#3d3f43",
+ marginRight: 5,
+ paddingLeft: 5,
+ paddingRight: 5,
+ height: 28,
+ cursor: "pointer",
+ borderColor: "#3d3f43",
+ color: "white",
+}
+
+const WorkflowPaper = (props) => {
+ const { data } = props;
+ let navigate = useNavigate();
+
+ const [open, setOpen] = React.useState(false);
+ const [anchorEl, setAnchorEl] = React.useState(null);
+ const appGroup = data.action_references === undefined || data.action_references === null ? [] : data.action_references
+
+ //console.log("Workflow: ", data)
+ var boxColor = "#86c142";
+
+ var parsedName = data.name;
+ if (
+ parsedName !== undefined &&
+ parsedName !== null &&
+ parsedName.length > 20
+ ) {
+ parsedName = parsedName.slice(0, 21) + "..";
+ }
+
+
+ const imageStyle = {
+ width: 24,
+ height: 24,
+ marginRight: 10,
+ border: "1px solid rgba(255,255,255,0.3)",
+ }
+ var image = data.creator_info !== undefined && data.creator_info !== null && data.creator_info.image !== undefined && data.creator_info.image !== null && data.creator_info.image.length > 0 ? :
+ const creatorname = data.creator_info !== undefined && data.creator_info !== null && data.creator_info.username !== undefined && data.creator_info.username !== null && data.creator_info.username.length > 0 ? data.creator_info.username : ""
+ var orgName = "";
+ var orgId = "";
+ if ((data.objectID === undefined || data.objectID === null) && data.id !== undefined && data.id !== null) {
+ data.objectID = data.id
+ }
+
+ //console.log("IMG: ", data)
+
+ return (
+
+
+
+
+
+
+ {
+ if (data.creator_info !== undefined) {
+ navigate("/creators/"+data.creator_info.username)
+ }
+ }}
+ >
+ {image}
+
+
+
+
+
+ {parsedName}
+
+
+
+
+
+ {appGroup.length > 0 ?
+
+
+ {appGroup.map((app, index) => {
+ return (
+ {
+ navigate("/apps/"+app.id)
+ }}
+ >
+
+
+
+
+ )
+ })}
+
+
+ :
+
+
+
+
+ {data.actions === undefined || data.actions === null ? 1 : data.actions.length}
+
+
+
+ }
+
+
+
+
+ {data.triggers === undefined || data.triggers === null ? 1 : data.triggers.length}
+
+
+
+
+ {
+ }}
+ >
+
+
+
+
+ {0}
+
+
+
+
+
+ {data.tags !== undefined && data.tags !== null
+ ? data.tags.map((tag, index) => {
+ if (index >= 3) {
+ return null;
+ }
+
+ return (
+
+ );
+ })
+ : null}
+
+
+
+
+ )
+ }
+
+export default WorkflowPaper
diff --git a/frontend/src/components/Workflowsearch.jsx b/frontend/src/components/Workflowsearch.jsx
new file mode 100644
index 00000000..e69de29b
diff --git a/frontend/src/components/history.jsx b/frontend/src/components/history.jsx
index 71ce4bd5..b26d4bad 100644
--- a/frontend/src/components/history.jsx
+++ b/frontend/src/components/history.jsx
@@ -1,8 +1,8 @@
-import { createBrowserHistory } from 'history';
+import { createBrowserHistory } from "history";
-var localExport
-if (typeof window !== 'undefined') {
- localExport = createBrowserHistory({forceRefresh: true});
+var localExport;
+if (typeof window !== "undefined") {
+ localExport = createBrowserHistory({ forceRefresh: true });
}
-export default localExport
+export default localExport;
diff --git a/frontend/src/css/nunito.css b/frontend/src/css/nunito.css
index bbf07d1d..7a9e5b2a 100644
--- a/frontend/src/css/nunito.css
+++ b/frontend/src/css/nunito.css
@@ -1,40 +1,45 @@
/* cyrillic-ext */
@font-face {
- font-family: 'Nunito Sans';
+ font-family: "Nunito Sans";
font-style: normal;
font-weight: 400;
- src: url('./font1.woff2') format('woff2');
- unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
+ src: url("./font1.woff2") format("woff2");
+ unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F,
+ U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
- font-family: 'Nunito Sans';
+ font-family: "Nunito Sans";
font-style: normal;
font-weight: 400;
- src: url('./font2.woff2') format('woff2');
+ src: url("./font2.woff2") format("woff2");
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
- font-family: 'Nunito Sans';
+ font-family: "Nunito Sans";
font-style: normal;
font-weight: 400;
- src: url('./font3.woff2') format('woff2');
- unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
+ src: url("./font3.woff2") format("woff2");
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
+ U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
- font-family: 'Nunito Sans';
+ font-family: "Nunito Sans";
font-style: normal;
font-weight: 400;
- src: url('./font4.woff2') format('woff2');
- unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
+ src: url("./font4.woff2") format("woff2");
+ unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
+ U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
- font-family: 'Nunito Sans';
+ font-family: "Nunito Sans";
font-style: normal;
font-weight: 400;
- src: url('./font5.woff2') format('woff2');
- unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+ src: url("./font5.woff2") format("woff2");
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
+ U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
+ U+FEFF, U+FFFD;
}
diff --git a/frontend/src/defaultCytoscapeStyle.js b/frontend/src/defaultCytoscapeStyle.js
index 48642d60..ba7891ea 100644
--- a/frontend/src/defaultCytoscapeStyle.js
+++ b/frontend/src/defaultCytoscapeStyle.js
@@ -1,357 +1,411 @@
-const data = [{
- selector: 'node',
- css: {
- 'label': 'data(label)',
- 'text-valign': 'center',
- 'font-family': 'Segoe UI, Tahoma, Geneva, Verdana, sans-serif, sans-serif',
- 'font-weight': 'lighter',
- 'margin-right': '10px',
- 'font-size': '18px',
- 'width': '80px',
- 'height': '80px',
- 'color': 'white',
- 'padding': '10px',
- 'margin': '5px',
- 'border-width': '1px',
- 'text-margin-x': '10px',
- }
+const data = [
+ {
+ selector: "node",
+ css: {
+ label: "data(label)",
+ "text-valign": "center",
+ "font-family":
+ "Segoe UI, Tahoma, Geneva, Verdana, sans-serif, sans-serif",
+ "font-weight": "lighter",
+ "margin-right": "10px",
+ "font-size": "18px",
+ width: "80px",
+ height: "80px",
+ color: "white",
+ padding: "10px",
+ margin: "5px",
+ "border-width": "1px",
+ "text-margin-x": "10px",
+ "z-index": 5001,
+ },
+ },
+ {
+ selector: "edge",
+ css: {
+ "target-arrow-shape": "triangle",
+ "target-arrow-color": "grey",
+ "curve-style": "unbundled-bezier",
+ label: "data(label)",
+ "text-margin-y": "-15px",
+ width: "5px",
+ color: "white",
+ "line-fill": "linear-gradient",
+ "line-gradient-stop-positions": ["0.0", "100"],
+ "line-gradient-stop-colors": ["grey", "grey"],
+ "z-index": 5001,
+ },
+ },
+ {
+ selector: `node[type="ACTION"]`,
+ css: {
+ shape: "roundrectangle",
+ "background-color": "#213243",
+ "border-color": "#81c784",
+ "background-width": "100%",
+ "background-height": "100%",
+ "border-radius": "5px",
+ "z-index": 5001,
+ },
+ },
+ {
+ selector: `node[type="COMMENT"]`,
+ css: {
+ shape: "roundrectangle",
+ color: "data(color)",
+ width: "data(width)",
+ height: "data(height)",
+ padding: "0px",
+ margin: "0px",
+ "background-color": "data(backgroundcolor)",
+ "border-color": "#ffffff",
+ "text-margin-x": "0px",
+ "z-index": 4999,
+ "border-radius": "5px",
+ "background-opacity": "0.5",
+ "text-wrap": "wrap",
+ },
+ },
+ {
+ selector: `node[app_name="Shuffle Tools"]`,
+ css: {
+ width: "30px",
+ height: "30px",
+ "z-index": 5000,
+ "font-size": "0px",
+ "background-width": "75%",
+ "background-height": "75%",
+ "background-color": "data(iconBackground)",
+ "background-fill": "data(fillstyle)",
+ "background-gradient-direction": "to-right",
+ "background-gradient-stop-colors": "data(fillGradient)",
+ },
+ },
+ {
+ selector: `node[app_name="Testing"]`,
+ css: {
+ width: "30px",
+ height: "30px",
+ "z-index": 5000,
+ "font-size": "0px",
+ },
+ },
+ {
+ selector: `node[?small_image]`,
+ css: {
+ "background-image": "data(small_image)",
+ "text-halign": "right",
+ },
+ },
+ {
+ selector: `node[?large_image]`,
+ css: {
+ "background-image": "data(large_image)",
+ "text-halign": "right",
+ },
+ },
+ {
+ selector: `node[type="CONDITION"]`,
+ css: {
+ shape: "diamond",
+ "border-color": "##FFEB3B",
+ padding: "30px",
+ },
+ },
+ {
+ selector: `node[type="eventAction"]`,
+ css: {
+ "background-color": "#edbd21",
+ },
+ },
+ {
+ selector: `node[type="TRIGGER"]`,
+ css: {
+ shape: "octagon",
+ "border-radius": "5px",
+ "border-color": "orange",
+ "background-color": "#213243",
+ "background-width": "100%",
+ "background-height": "100%",
+ },
+ },
+ {
+ selector: `node[status="running"]`,
+ css: {
+ "border-color": "#81c784",
+ },
+ },
+ {
+ selector: `node[status="stopped"]`,
+ css: {
+ "border-color": "orange",
+ },
+ },
+ {
+ selector: 'node[type="mq"]',
+ css: {
+ "background-color": "#edbd21",
+ },
+ },
+ {
+ selector: "node[?isButton]",
+ css: {
+ shape: "ellipse",
+ width: "15px",
+ height: "15px",
+ "z-index": "5002",
+ "font-size": "0px",
+ border: "1px solid rgba(255,255,255,0.9)",
+ "background-image": "data(icon)",
+ "background-color": "data(iconBackground)",
+ },
+ },
+ {
+ selector: "node[?isSuggestion]",
+ css: {
+ shape: "ellipse",
+ width: "30px",
+ height: "30px",
+ "z-index": "5002",
+ "font-size": "0px",
+ border: "1px solid rgba(255,255,255,0.9)",
+ "background-image": "data(large_image)",
+ "background-color": "data(iconBackground)",
+ label: "data(label)",
+ },
+ },
+ {
+ selector: "node[?isDescriptor]",
+ css: {
+ shape: "ellipse",
+ "border-color": "#80deea",
+ width: "5px",
+ height: "5px",
+ "z-index": "5002",
+ "font-size": "10px",
+ "text-valign": "center",
+ "text-halign": "center",
+ border: "1px solid black",
+ "margin-right": "0px",
+ "text-margin-x": "0px",
+ "background-color": "data(imageColor)",
+ "background-image": "data(image)",
+ },
+ },
+ {
+ selector: "node[?isStartNode]",
+ css: {
+ shape: "ellipse",
+ "border-color": "#80deea",
+ width: "80px",
+ height: "80px",
+ "font-size": "18px",
+ "background-width": "100%",
+ "background-height": "100%",
+ },
+ },
+ {
+ selector: "node[!is_valid]",
+ css: {
+ "border-color": "red",
+ "border-width": "10px",
+ },
+ },
+ {
+ selector: ":selected",
+ css: {
+ "background-color": "#77b0d0",
+ "border-color": "#77b0d0",
+ "border-width": "20px",
+ },
+ },
+ {
+ selector: ".skipped-highlight",
+ css: {
+ "background-color": "grey",
+ "border-color": "grey",
+ "border-width": "8px",
+ "transition-property": "background-color",
+ "transition-duration": "0.5s",
+ },
+ },
+ {
+ selector: ".success-highlight",
+ css: {
+ "background-color": "#41dcab",
+ "border-color": "#41dcab",
+ "border-width": "5px",
+ "transition-property": "background-color",
+ "transition-duration": "0.5s",
+ },
+ },
+ {
+ selector: ".hover-highlight",
+ css: {
+ "background-color": "#5f9265",
+ "border-color": "#5f9265",
+ "border-width": "5px",
+ "transition-property": "background-color",
+ "transition-duration": "0.5s",
+ },
+ },
+ {
+ selector: ".failure-highlight",
+ css: {
+ "background-color": "#8e3530",
+ "border-color": "#8e3530",
+ "border-width": "5px",
+ "transition-property": "background-color",
+ "transition-duration": "0.5s",
+ },
+ },
+ {
+ selector: ".not-executing-highlight",
+ css: {
+ "background-color": "grey",
+ "border-color": "grey",
+ "border-width": "5px",
+ "transition-property": "#ffef47",
+ "transition-duration": "0.25s",
+ },
+ },
+ {
+ selector: ".executing-highlight",
+ css: {
+ "background-color": "#ffef47",
+ "border-color": "#ffef47",
+ "border-width": "8px",
+ "transition-property": "border-width",
+ "transition-duration": "0.25s",
+ },
+ },
+ {
+ selector: ".awaiting-data-highlight",
+ css: {
+ "background-color": "#f4ad42",
+ "border-color": "#f4ad42",
+ "border-width": "5px",
+ "transition-property": "border-color",
+ "transition-duration": "0.5s",
+ },
+ },
+ {
+ selector: ".shuffle-hover-highlight",
+ css: {
+ "background-color": "#f85a3e",
+ "border-color": "#f85a3e",
+ "border-width": "12px",
+ "transition-property": "border-width",
+ "transition-duration": "0.25s",
+ label: "data(label)",
+ "font-size": "18px",
+ color: "white",
+ },
+ },
+ {
+ selector: "$node > node",
+ css: {
+ "padding-top": "10px",
+ "padding-left": "10px",
+ "padding-bottom": "10px",
+ "padding-right": "10px",
+ },
+ },
+ {
+ selector: "edge.executing-highlight",
+ css: {
+ width: "5px",
+ "target-arrow-color": "#ffef47",
+ "line-color": "#ffef47",
+ "transition-property": "line-color, width",
+ "transition-duration": "0.25s",
+ },
+ },
+ {
+ selector: `edge[?decorator]`,
+ css: {
+ width: "1px",
+ "line-style": "dashed",
+ "line-fill": "linear-gradient",
+ "target-arrow-color": "#f34079",
+ "line-gradient-stop-positions": ["0.0", "100"],
+ "line-gradient-stop-colors": ["#f86a3e", "#f34079"],
+ },
+ },
+ {
+ selector: "edge.success-highlight",
+ css: {
+ width: "5px",
+ "target-arrow-color": "#41dcab",
+ "line-color": "#41dcab",
+ "transition-property": "line-color, width",
+ "transition-duration": "0.5s",
+ "line-fill": "linear-gradient",
+ "line-gradient-stop-positions": ["0.0", "100"],
+ "line-gradient-stop-colors": ["#41dcab", "#41dcab"],
+ },
+ },
+ {
+ selector: ".eh-handle",
+ style: {
+ "background-color": "#337ab7",
+ width: "1px",
+ height: "1px",
+ shape: "circle",
+ "border-width": "1px",
+ "border-color": "black",
+ },
+ },
+ {
+ selector: ".eh-source",
+ style: {
+ "border-width": "3",
+ "border-color": "#337ab7",
+ },
+ },
+ {
+ selector: ".eh-target",
+ style: {
+ "border-width": "3",
+ "border-color": "#337ab7",
+ },
+ },
+ {
+ selector: ".eh-preview, .eh-ghost-edge",
+ style: {
+ "background-color": "#337ab7",
+ "line-color": "#337ab7",
+ "target-arrow-color": "#337ab7",
+ "source-arrow-color": "#337ab7",
+ },
+ },
+ {
+ selector: "edge:selected",
+ css: {
+ "target-arrow-color": "#f85a3e",
+ },
+ },
+ {
+ selector: `edge[?source_workflow]`,
+ css: {
+ "background-opacity": "1",
+ "font-size": "0px",
+ },
+ },
+ {
+ selector: `node[?source_workflow]`,
+ css: {
+ "background-opacity": "0",
+ "font-size": "0px",
+ },
+ },
+ {
+ selector: "node:selected",
+ css: {
+ "border-color": "#f86a3e",
+ "border-width": "7px",
},
- {
- selector: 'edge',
- css: {
- 'target-arrow-shape': 'triangle',
- 'target-arrow-color': 'grey',
- 'curve-style': 'unbundled-bezier',
- 'label': 'data(label)',
- 'text-margin-y': '-15px',
- 'width': '5px',
- "color": "white",
- "line-fill": "linear-gradient",
- "line-gradient-stop-positions": ["0.0", "100"],
- "line-gradient-stop-colors": ["grey", "grey"],
- },
- },
- {
- selector: `node[type="ACTION"]`,
- css: {
- 'shape': 'square',
- 'background-color': '#213243',
- 'border-color': '#81c784',
- 'background-width': '100%',
- 'background-height': '100%',
- 'border-radius': '5px',
- 'z-index': 5001,
- },
- },
- {
- selector: `node[app_name="Shuffle Tools"]`,
- css: {
- 'width': '30px',
- 'height': '30px',
- 'z-index': 5000,
- 'font-size': '0px',
- 'background-width': '75%',
- 'background-height': '75%',
- 'background-color': 'data(iconBackground)',
- 'background-fill': 'data(fillstyle)',
- 'background-gradient-direction': 'to-right',
- 'background-gradient-stop-colors': 'data(fillGradient)',
- }
- },
- {
- selector: `node[app_name="Testing"]`,
- css: {
- 'width': '30px',
- 'height': '30px',
- 'z-index': 5000,
- 'font-size': '0px',
- },
- },
- {
- selector: `node[?small_image]`,
- css: {
- 'background-image': 'data(small_image)',
- 'text-halign': 'right',
- },
- },
- {
- selector: `node[?large_image]`,
- css: {
- 'background-image': 'data(large_image)',
- 'text-halign': 'right',
- },
- },
- {
- selector: `node[type="CONDITION"]`,
- css: {
- 'shape': 'diamond',
- 'border-color': '##FFEB3B',
- 'padding': '30px'
- },
- },
- {
- selector: `node[type="eventAction"]`,
- css: {
- 'background-color': '#edbd21',
- },
- },
- {
- selector: `node[type="TRIGGER"]`,
- css: {
- 'shape': 'octagon',
- 'border-color': 'orange',
- 'background-color': '#213243',
- 'background-width': '100%',
- 'background-height': '100%',
- },
- },
- {
- selector: `node[status="running"]`,
- css: {
- 'border-color': '#81c784',
- },
- },
- {
- selector: `node[status="stopped"]`,
- css: {
- 'border-color': 'orange',
- },
- },
- {
- selector: 'node[type="mq"]',
- css: {
- 'background-color': '#edbd21',
- },
- },
- {
- selector: 'node[?isButton]',
- css: {
- 'shape': 'ellipse',
- 'width': '15px',
- 'height': '15px',
- 'z-index': '5002',
- 'font-size': '0px',
- 'border': '1px solid rgba(255,255,255,0.9)',
- 'background-image': 'data(icon)',
- 'background-color': 'data(iconBackground)',
- },
- },
- {
- selector: 'node[?isDescriptor]',
- css: {
- 'shape': 'ellipse',
- 'border-color': '#80deea',
- 'width': '5px',
- 'height': '5px',
- 'z-index': '5002',
- 'font-size': '10px',
- 'text-valign': 'center',
- 'text-halign': 'center',
- 'border': '1px solid black',
- 'margin-right': '0px',
- 'text-margin-x': '0px',
- 'background-color': 'data(imageColor)',
- 'background-image': 'data(image)',
- },
- },
- {
- selector: 'node[?isStartNode]',
- css: {
- 'shape': 'ellipse',
- 'border-color': '#80deea',
- 'width': '80px',
- 'height': '80px',
- 'font-size': '18px',
- 'background-width': '100%',
- 'background-height': '100%',
- },
- },
- {
- selector: "node[!is_valid]",
- css: {
- 'border-color': 'red',
- 'border-width': '10px',
- },
- },
- {
- selector: ':selected',
- css: {
- 'background-color': '#77b0d0',
- 'border-color': '#77b0d0',
- 'border-width': '20px',
- },
- },
- {
- selector: '.skipped-highlight',
- css: {
- 'background-color': 'grey',
- 'border-color': 'grey',
- 'border-width': '8px',
- 'transition-property': 'background-color',
- 'transition-duration': '0.5s',
- },
- },
- {
- selector: '.success-highlight',
- css: {
- 'background-color': '#41dcab',
- 'border-color': '#41dcab',
- 'border-width': '5px',
- 'transition-property': 'background-color',
- 'transition-duration': '0.5s',
- },
- },
- {
- selector: '.failure-highlight',
- css: {
- 'background-color': '#8e3530',
- 'border-color': '#8e3530',
- 'border-width': '5px',
- 'transition-property': 'background-color',
- 'transition-duration': '0.5s',
- },
- },
- {
- selector: '.not-executing-highlight',
- css: {
- 'background-color': 'grey',
- 'border-color': 'grey',
- 'border-width': '5px',
- 'transition-property': '#ffef47',
- 'transition-duration': '0.25s',
- },
- },
- {
- selector: '.executing-highlight',
- css: {
- 'background-color': '#ffef47',
- 'border-color': '#ffef47',
- 'border-width': '8px',
- 'transition-property': 'border-width',
- 'transition-duration': '0.25s',
- },
- },
- {
- selector: '.awaiting-data-highlight',
- css: {
- 'background-color': '#f4ad42',
- 'border-color': '#f4ad42',
- 'border-width': '5px',
- 'transition-property': 'border-color',
- 'transition-duration': '0.5s',
- },
- },
- {
- selector: '.shuffle-hover-highlight',
- css: {
- 'background-color': "#f85a3e",
- 'border-color': '#f85a3e',
- 'border-width': '12px',
- 'transition-property': 'border-width',
- 'transition-duration': '0.25s',
- 'label': 'data(label)',
- 'font-size': '18px',
- 'color': 'white',
- },
- },
- {
- selector: '$node > node',
- css: {
- 'padding-top': '10px',
- 'padding-left': '10px',
- 'padding-bottom': '10px',
- 'padding-right': '10px',
- },
- },
- {
- selector: 'edge.executing-highlight',
- css: {
- 'width': '5px',
- 'target-arrow-color': '#ffef47',
- 'line-color': '#ffef47',
- 'transition-property': 'line-color, width',
- 'transition-duration': '0.25s',
- },
- },
- {
- selector: `edge[?decorator]`,
- css: {
- 'width': '1px',
- 'line-style': 'dashed',
- "line-fill": "linear-gradient",
- 'target-arrow-color': '#f34079',
- "line-gradient-stop-positions": ["0.0", "100"],
- "line-gradient-stop-colors": ["#f86a3e", "#f34079"],
- },
- },
- {
- selector: 'edge.success-highlight',
- css: {
- 'width': '5px',
- 'target-arrow-color': '#41dcab',
- 'line-color': '#41dcab',
- 'transition-property': 'line-color, width',
- 'transition-duration': '0.5s',
- "line-fill": "linear-gradient",
- "line-gradient-stop-positions": ["0.0", "100"],
- "line-gradient-stop-colors": ["#41dcab", "#41dcab"],
- },
- },
- {
- selector: '.eh-handle',
- style: {
- 'background-color': '#337ab7',
- 'width': '1px',
- 'height': '1px',
- 'shape': 'circle',
- 'border-width': '1px',
- 'border-color': 'black'
- }
- },
- {
- selector: '.eh-source',
- style: {
- 'border-width': '3',
- 'border-color': '#337ab7'
- }
- },
- {
- selector: '.eh-target',
- style: {
- 'border-width': '3',
- 'border-color': '#337ab7'
- }
- },
- {
- selector: '.eh-preview, .eh-ghost-edge',
- style: {
- 'background-color': '#337ab7',
- 'line-color': '#337ab7',
- 'target-arrow-color': '#337ab7',
- 'source-arrow-color': '#337ab7'
- }
- },
- {
- selector: 'edge:selected',
- css: {
- 'target-arrow-color': '#f85a3e',
- },
- },
- {
- selector: `edge[?source_workflow]`,
- css: {
- "background-opacity": "1",
- 'font-size': '0px',
- },
- },
- {
- selector: `node[?source_workflow]`,
- css: {
- "background-opacity": "0",
- 'font-size': '0px',
- },
- },
-]
+ },
+];
//{
// selector: 'edge[?hasErrors]',
@@ -365,5 +419,4 @@ const data = [{
// },
//},
-
-export default data
+export default data;
diff --git a/frontend/src/frameworkStyle.jsx b/frontend/src/frameworkStyle.jsx
new file mode 100644
index 00000000..0dbc1fdc
--- /dev/null
+++ b/frontend/src/frameworkStyle.jsx
@@ -0,0 +1,112 @@
+const data = [{
+ selector: 'node',
+ css: {
+ 'label': 'data(label)',
+ 'text-valign': 'center',
+ 'font-family': 'Segoe UI, Tahoma, Geneva, Verdana, sans-serif, sans-serif',
+ 'font-weight': 'lighter',
+ 'font-size': 'data(font_size)',
+ 'text-algin': 'center',
+ 'border-width': '3px',
+ 'border-color': '#8a8a8a',
+ 'color': '#f85a3e',
+ 'text-margin-x': '0px',
+ 'text-margin-y': 'data(text_margin_y)',
+ 'background-color': '#27292d',
+ 'background-image': 'data(large_image)',
+ 'background-position-x': 'data(margin_x)',
+ 'background-position-y': 'data(margin_y)',
+ 'background-clip': "node",
+ 'background-width': 'data(width)',
+ 'background-height': 'data(width)',
+ 'width': 'data(boxwidth)',
+ 'height': 'data(boxheight)',
+ }
+ },
+ {
+ selector: 'edge',
+ css: {
+ 'target-arrow-shape': 'triangle',
+ 'target-arrow-color': '#8a8a8a',
+ 'curve-style': 'bezier',
+ 'label': 'data(label)',
+ 'text-wrap': 'wrap',
+ 'text-max-width': '120px',
+ "color": "rgba(255,255,255,0.7)",
+ 'line-style': 'dashed',
+ "line-fill": "linear-gradient",
+ "line-gradient-stop-positions": ["0.0", "100"],
+ "line-gradient-stop-colors": ["#8a8a8a", "#8a8a8a"],
+ 'width': '1px',
+ 'z-compound-depth': 'top',
+ 'font-size': '13px',
+ },
+ },
+ {
+ selector: `node[!is_valid]`,
+ css: {
+ 'height': '20px',
+ 'width': '20px',
+ 'background-color': '#6d9eeb',
+ 'border-color': '#4c6ea4',
+ 'border-width': '1px',
+ },
+ },
+ {
+ selector: `edge[?human]`,
+ css: {
+ 'target-arrow-color': '#6d9eeb',
+ 'line-style': 'solid',
+ "line-gradient-stop-positions": ["0.0", "100"],
+ "line-gradient-stop-colors": ["#6d9eeb", "#6d9eeb"],
+ }
+ },
+ {
+ selector: `node[?middle_node]`,
+ css: {
+ 'background-image': '/images/Shuffle_logo.png',
+ 'height': 'data(width)',
+ 'width': 'data(height)',
+ 'background-width': 'data(width)',
+ 'background-height': 'data(height)',
+ 'background-position-x': '0px',
+ 'background-position-y': '0px',
+ 'border-width': '2px',
+ },
+ },
+ {
+ selector: `node[?invisible]`,
+ css: {
+ 'height': '10x',
+ 'width': '10px',
+ 'background-position-x': '0px',
+ 'background-position-y': '0px',
+ 'border-width': '0px',
+ 'font-size': '0px',
+ },
+ },
+ {
+ selector: `node[?font_size]`,
+ css: {
+ 'font-size': 'data(font_size)',
+ },
+ },
+ {
+ selector: ".eh-preview, .eh-ghost-edge",
+ style: {
+ "background-color": "#337ab7",
+ "line-color": "#337ab7",
+ "target-arrow-color": "#337ab7",
+ "source-arrow-color": "#337ab7",
+ },
+ },
+ {
+ selector: "node:selected",
+ css: {
+ "border-color": "#f86a3e",
+ "border-width": "3px",
+ },
+ },
+ ]
+
+export default data
diff --git a/frontend/src/index.css b/frontend/src/index.css
index 793c42e5..4188b885 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -1,9 +1,9 @@
-@import url('./css/nunito.css');
+@import url("./css/nunito.css");
body {
margin: 0;
padding: 0;
- font-family: "Nunito Sans", sans-serif;
+ font-family: "Nunito Sans", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@@ -12,3 +12,22 @@ code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}
+
+.cm-tab::before{
+ content: "———";
+ margin-right: -13px;
+}
+
+/* .cm-string{
+ z-index: -1;
+}
+
+.CodeMirror-selected{
+ background-color: #007500 !important;
+ z-index: 100 !important;
+} */
+
+.CodeMirror-selectedtext{
+ background-color: rgba(28, 47, 69, 0.6) !important;
+ color: rgb(255, 255, 255) !important;
+}
\ No newline at end of file
diff --git a/frontend/src/index.js b/frontend/src/index.js
index 5c4e400f..781cb802 100644
--- a/frontend/src/index.js
+++ b/frontend/src/index.js
@@ -1,13 +1,10 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-import './index.css';
-import App from './App';
-import * as serviceWorker from './serviceWorker';
+import React from "react";
+import ReactDOM from "react-dom";
+import "./index.css";
+import App from "./App";
+import * as serviceWorker from "./serviceWorker";
-
-ReactDOM.render(
-
- , document.getElementById('root'));
+ReactDOM.render( , document.getElementById("root"));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
diff --git a/frontend/src/serviceWorker.js b/frontend/src/serviceWorker.js
index 8859a0c6..5133f741 100644
--- a/frontend/src/serviceWorker.js
+++ b/frontend/src/serviceWorker.js
@@ -9,9 +9,9 @@
// This link also includes instructions on opting out of this behavior.
const isLocalhost = Boolean(
- window.location.hostname === 'localhost' ||
+ window.location.hostname === "localhost" ||
// [::1] is the IPv6 localhost address.
- window.location.hostname === '[::1]' ||
+ window.location.hostname === "[::1]" ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
@@ -19,7 +19,7 @@ const isLocalhost = Boolean(
);
export function register(config) {
- if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
+ if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
if (publicUrl.origin !== window.location.origin) {
@@ -29,7 +29,7 @@ export function register(config) {
return;
}
- window.addEventListener('load', () => {
+ window.addEventListener("load", () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
@@ -40,8 +40,8 @@ export function register(config) {
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
- 'This web app is being served cache-first by a service ' +
- 'worker. To learn more, visit https://goo.gl/SC7cgQ'
+ "This web app is being served cache-first by a service " +
+ "worker. To learn more, visit https://goo.gl/SC7cgQ"
);
});
} else {
@@ -55,17 +55,17 @@ export function register(config) {
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
- .then(registration => {
+ .then((registration) => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
installingWorker.onstatechange = () => {
- if (installingWorker.state === 'installed') {
+ if (installingWorker.state === "installed") {
if (navigator.serviceWorker.controller) {
// At this point, the old content will have been purged and
// the fresh content will have been added to the cache.
// It's the perfect time to display a "New content is
// available; please refresh." message in your web app.
- console.log('New content is available; please refresh.');
+ console.log("New content is available; please refresh.");
// Execute callback
if (config.onUpdate) {
@@ -75,7 +75,7 @@ function registerValidSW(swUrl, config) {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
- console.log('Content is cached for offline use.');
+ console.log("Content is cached for offline use.");
// Execute callback
if (config.onSuccess) {
@@ -86,22 +86,22 @@ function registerValidSW(swUrl, config) {
};
};
})
- .catch(error => {
- console.error('Error during service worker registration:', error);
+ .catch((error) => {
+ console.error("Error during service worker registration:", error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
- .then(response => {
+ .then((response) => {
// Ensure service worker exists, and that we really are getting a JS file.
if (
response.status === 404 ||
- response.headers.get('content-type').indexOf('javascript') === -1
+ response.headers.get("content-type").indexOf("javascript") === -1
) {
// No service worker found. Probably a different app. Reload the page.
- navigator.serviceWorker.ready.then(registration => {
+ navigator.serviceWorker.ready.then((registration) => {
registration.unregister().then(() => {
window.location.reload();
});
@@ -113,14 +113,14 @@ function checkValidServiceWorker(swUrl, config) {
})
.catch(() => {
console.log(
- 'No internet connection found. App is running in offline mode.'
+ "No internet connection found. App is running in offline mode."
);
});
}
export function unregister() {
- if ('serviceWorker' in navigator) {
- navigator.serviceWorker.ready.then(registration => {
+ if ("serviceWorker" in navigator) {
+ navigator.serviceWorker.ready.then((registration) => {
registration.unregister();
});
}
diff --git a/frontend/src/theme.js b/frontend/src/theme.js
index 92371aad..73d23933 100644
--- a/frontend/src/theme.js
+++ b/frontend/src/theme.js
@@ -1,65 +1,101 @@
-import React from 'react';
-import { createMuiTheme } from '@material-ui/core/styles';
+import React from "react";
+import { createMuiTheme } from "@material-ui/core/styles";
const theme = createMuiTheme({
- palette: {
- primary: {
- main: "#f85a3e"
+ palette: {
+ primary: {
+ main: "#f85a3e",
},
secondary: {
- main: '#e8eaf6',
+ main: "#e8eaf6",
},
- text: {
- secondary: "rgba(255,255,255,0.7)",
- },
- type: 'dark',
- surfaceColor: "#27292d",
- inputColor: "#383B40",
- borderRadius: 5,
- textFieldStyle: {
- backgroundColor: "#383B40",
+ text: {
+ secondary: "rgba(255,255,255,0.7)",
+ },
+ type: "dark",
+ surfaceColor: "#27292d",
+ inputColor: "#383B40",
+ borderRadius: 5,
+ defaultBorder: "1px solid rgba(255,255,255,0.3)",
+ jsonTheme: "brewer",
+ reactJsonStyle: {
borderRadius: 5,
+ border: "1px solid rgba(255,255,255,0.7)",
+ padding: 5,
},
- innerTextfieldStyle: {
- color: "white",
- minHeight: 50,
- marginLeft: "5px",
- maxWidth: "95%",
- fontSize: "1em",
- borderRadius: 5,
+ textFieldStyle: {
+ backgroundColor: "#383B40",
+ borderRadius: 5,
+ },
+ innerTextfieldStyle: {
+ color: "white",
+ minHeight: 50,
+ marginLeft: "5px",
+ maxWidth: "95%",
+ fontSize: "1em",
+ borderRadius: 5,
+ },
+ tooltip: {
+ backgroundColor: "white",
+ color: "rgba(0, 0, 0, 0.87)",
+ boxShadow: 1,
+ fontSize: 11,
+ },
+ defaultImage:
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAK4AAACuCAYAAACvDDbuAAAgAElEQVR4Xu19e9CvV1Xe3r/vnJOcBEhBSgMEBaoUK9POCOVmAuP0HwcUCNYZSUsh9xv3hGl1qNippQRE20IFEhQoBJiaKVpEgQRnhD+0QHSmRkAsxE4doBZQTs71u/zezruv6/Ksvffvkn/qd8bBfN/3XvZe+1nPevbaa+/XuxX/Tbe6C/ece8qOd5dNk3um9+7Jk/OPds49zE3uyPy4ST5zCr/y4f+sfxO4j16vHsqfmV7gpgm8Yzm/lP9+km1B9+W2zn/zzk2sDeR55ffy3enn1Lf5J/1e3bb42kX43/Do1nt9fUdpLrSbbFt8vvls+idlm/qsaJPWuNK/TfvOLU44577pnPuSc9PvT95/9szu3p9c/IH/c2oVKDbeyB8z/Yz7noNd9zzn3U+5hX+6m9wjnXM7GqXCHpbR0+PnjudGxEtBkzRo02vFtRB83rkAXPqPGD4MnmWGGa3CDqp9+pp4Bwd2dCwPzIXur6D1xaGRXaQz8nf4Kfix1/3joDXtDm0jHVbYspirbZdkj4Npcv/XO/8F59xdfv/o7zz0A1/9yxEAd4E7/by74OCke5Fb+Bvd5J7unDvGHmyCKiMzX40ByW+XQCxvyoSafgGMBT0fgTayXmA/kykr8PI1kS0JIOMPRiSJ7ctOWfuI+mcw4Uj7lO25Y0TQoyHGbKsjJbqXR5HoKijK6nuz3bPhokEX+f4956Z7nXP/8cz+w//bxR/4H00GbgJ3ep170sHC/WxgWecvUJ5AemrLA4NF2cCAa4BDSCbjYVR2RYN2ZXlAOgxZqcXUDLh9toyvSkybopAJ+KBbOpHADOEjoO1EkVYU6LB0HbPUDtaN8N7TzrmPOnfwry9631/8mcW+JnD3Xu+eu3DubZNzT1Xhu8eyxaXw47lWBIzcAW2fGSpoM1WPgzYNWqSRwqjsnSYokgMyvW5JnzWZNrQpC9tC+ZVZW1Ek9GbBQK8ZuS99mrrW1sPh7XXsTeDmqHrvYvKve+j77/8MAi9E1v6t7vnOu7c7555gaM4SuiHGOsajcV8xmeEUMSDFf4yJQLiUmpYNTg906iVCl7b6ltu4gi5lTMs6Z4RpwLQsVMP+IZaNTtYngRoNI1uOzwdYhJxtEhqKAFsdvoI0XHy/m3ZeedH7v/ZxCV7Vir1b3HP8wr3PBC1HDsog5IkJxSdDHAyBlRqBgyHwAANuI3sg3j4G+shSHASQaRlbZtDW8AmiT7b3TLJD8qACrWrwkclYg2l70qACWow5IZvQ9lHQUmeZ7p+8v/Lh7/3z36NDw6w7a9rljvvw5NwPWykOOuEono5YT8IvXQNBK5yhGnwFVoCgZYPYZgvSBtnGMMkJnR2ZrIh3Fga3mGoR9YgpL2xAjbGlBC2YTEGWJu01J3mkrwYGuFP25EF1XMFj9y6Wiyse9p+/9hXl2il78G7n3T9radomMVphpMdGUG9wAJRLYJ4Wz2CZ73QmUpkxa9jlA2fmKvNz0414Fm+Abw6b+U8D4Km+xfsb0l5E99Z+S4mApIHhaEz2bCAPSmMkaNMzW4Av94ac3vtPHdu96TG3f32evFXhuH+ru8J5f4dzrmYPVGiKN7Bfsx/syVj+C07AA3WQmlYBa4TR+bpWnhYCgjyLtN+OBrbezPdAwNcQCjq4EAsaCEAS8BF4PNIZoBKhWdo9PMO0DX5vbmGTRMi41SgyxrQlTcmsVQB+2jt/3UXv/9qdxVLTre5RBwv/m25yz+Qspe2NQWt5ZEQ5x79kMgzaoTC4DM9SuoqDvcEWSqKsNlnJHYvvQ5oWvRutiInrijdwALGBLZ003sEcMl7DSEdHoGTHyoQ4eqTxaqymzdmYuq40wrS1D5A80i+985/1u0de/LAPf+Vb4Y7917uXO+dud84fRcClIOKdVwPGQZRuNBuj6DujMHakCUCgadUsubeMS1A/vhRLBxZEgR6g8juLbUzAE1tWe8wslv9hh9HyQPknZNpEAsUQaJWPDgpttyCjsDxuTMQMwAOiUoTknNv1frrmovfd/wE/1x4cTP4jbuF+3AItwFe81ACGXm0SAC/5UTYGoSBAdwCwmZE9GAqhInmvBr8JPMmAo0zLB7G5YmdMALkTy/fOz5+NonO0VaJl0Bmyh3jDakzL2xL6JnLF8dGjk8xKDBWPtc3euY+dWT7kJX73VveMhfe/4Zy7ONOcgDpnP+a+iC1WLpZJ7RPhArLCPD7snZSVpnmGkgMZTdIrA5BIwOoHBkDLHWs1eRBMZ/Wr2FWGTcFmGQSMZYhjAInAB1C2WQIlrBGUi1JtUaJ8DHrtVIJtwyjZ6bxKjA3Q1n590y8Wl/uDW9wt08LfFgpmCCgx81FroU40KqhUvNLPYu+0NBSYiBUckjDHxtX0eCJJeqBN7cfh2WazKa/FK4MK+wGmHV/t40wbmyoBj8erYDQ5VGYC1VzFmBZpjUzEjGIjhREO5NS2A+f9v/T7t7q7nPc/qZPbYuhZeJdGL6lIMgFQ12gsJUBxwObLxP1KHujO+8nQZT15ANiOsrRcooZVXjDEyyovAzypbDKNQRifdSZiFXTI9m2JQCNBZcAcv+x7w9iF/zEACwlLZEd6ZZiFmWI7vJvu8gevd/dNk/+hPFDa02rwtXOZdZij8axlUoldwnilAYaRDKa1c5ixizxMVqfAEcUIo122RW2OCwv8/aPSotolLn5Y+nBkRWxk4aQuqGu7GOMxE5nPtQcN0DLggvGGYySjRR3L1L77/P6t/tvOuUc0gdvSZb0qL+hxtbSw/tkGma49SB3J2sl4R0SOBqNm+AYbFW9Hz0KgqIPIgWsCgHhzvaZ9rwYsjgKyzZiJMVmNVoiNsi3oWxi3BlkUwshMW7JO35mBu+dc3LmgwNvUfZVNasfH5AHVX/kOvHNBDzbXfX3jZkxnThliwSwtSswcLQDng7haFVUEWb99umAm6+5iyzA0yDbcntDmhQSwo2U5EvPzI3pWOzzDC/OaLtMm0Pm9GbhVpkD0hheX9hZwpxdWPYQ6iiqMweCgUMizB7nBYtXOeKeo0optXC1Mg8WFagMzAtV62mpKwCiC4miBdcCNSRg2aOnQNSu4Amn5MAi2LGyzoF2aaDtLflfCS2kDtBPRtBVf+cr4Dr+XgMs7Tn9CA86uNkDBr4k/AY0jQ0XRsoIdUqV9NXjLUXrywAJ8Yj2LBYrXovsrqMwI1Mhlhn6xUWq/I9tzTKsT1hNO15dNkqV5VFHAE7bTzpGep/6Afi8jXXWMBnBB2CeGXVUeaNAaYV5kD3IYLP5mFpTwlFSkx9VraWv1m+UYNqB46AL2Q6yf2lgr0HRoDX1PBTk1EliEgkO8dPhcLs+Ba9ybTRsuXmVFLI6Cwgoihh7gBcFx4JohKoli8nAJqGhczbIZQIWsynXCSA2mHQ6Don0YuHYY5HvEWsArMKs6by2Wthml9pkzeWwVilwI8KQPrdJEU/rI+xtsa/SfQwKQ1RRlKL1OLwpph6rAtUEbH5z+Xkm3LyFoOMtXwwkLBO1g8XgeSADa7COlzWqASHhKS0RU0rAwqJwyLrOGCUrD4XFKrtqusDRMefEVsfyaVR0ya17AK7GLrWXmsoxGioOKYXB4p0SjCA7ZyqpFNh3KTRG4LbZIf4vMKXa66hYKyh2ZJfNl3Poe6hhW9iCxDGkjBF6nAJwHi5EQT1hwrrBYYatOjkDM5I3FCxrJxmt9OaDs2oP2ZCqMd2jo6osL7SiJbcww1okCfu+WzNK5mXUjXjZafmAc4Lb2ZSzF1IOlafnzqIcaOw9qc0jnKhD6LMByu8Tbx3KhYiAt0ALDZ9uRqUJiAhm9UPagioTS15ZDkrqIEu0U0WBNW5+fbwDABflXjhMiXSxihDZK76x/I4+t7U3ARR1AtbRCR5mxR4TC1myarYhx7RaWcNkoU8t3KrXsMGOsqInoEJqCnA2F7x5Lz88m9m9FOMpu5LqxsksepfBuaquuAsiXYG5QT6vG3SCf1nVCMpSgGTqKHYqN/t4tCxN+sAjcvjopDvnSRiJcLOOW9fkm6LAnjw0sdryxe9Hyqnhetmyr5hSDNqGarLqxsxWkYxhEkydtyX6mz7P2aaerTVwNtENRT/Rfw6mzcJJu8BC4Bf55JEjnGsDV9bRGI1TdQZUgVVdZUUCyeSsKGJ7bOvfALD6viwsVEONOysOvBTxZTxtTekWDC5aSsixcR5weArcxEVt1qw1lxnVAW/ycSRhgU3Xhwmng1haE7BCuV2XSO/0AKn4QyMWKWKQaOYlrhArmVEL3mQOrwR0v7YX4PBlNxwT1nm+ExiwS2gfEVdCOti0afgTYluyR0my9ZVwbtNl17D1szPk6EsSF1FmYg00KuP0TCTVo1SY3GOo7RyL1QFFop2afMmzDrTgEF6eqbGSE93CBdphcT8tlUx/wehuR9V5UBL7i3rfEtPhQPQu08ffBqQoZrFZ7wHGWbGKNAxnfdeUB1dsMuCZoFTC0hGiHQnz4XGaMuN1jLOVVo8YIeMigRT/lk6Tig3bZIHcKW3pwdxZyxnTK0doDBHoJFM6erD1W9kFJwnFNC8HHcMLbB6NIa9LO5EP+obavAheE4HIvCvkpTKk/WUaqupY4ee6cAkSKrqjzOuzTkIkA1HRIw3iFaUHRi9K4lkRoRhEN2jjuKzpkM7cenpWVChnOFKuK1h/L08J0ngU+I11GMWUeu8pAi5eY/d7rYlahT/scEklbsf1JJNSmx3lv1dLyk1usMBqZsFpesEoFhhqcov1aEgJKmrktqQi8KT9Im0UYDMAeBC1tuLFVR/SNs/mcMsQBsV/WCEoTK2GAvvdDPHI6MfGuIVMcR4XvtRY/AHDJAyDTijBYrCZZU8oDYGATOCTE03BOHawJjDpoOERZuUyUORgJ0/Wa0qyGU2ikVU1rH/ckB9bapmT1TUeq8Xpa0L9evW/zFHZDcil5YGtuv5sYV64mSX6tPyPWMxqS5AFlzAIkU37USZJkWsYsEBi54ECwUiy1EA2XbRY7F0zH6NScNlkayYPVJ2J27QGaIOWRk7JrTB5Qs9UdvzpLwPCSbIBroBtEwAa4OVGc/O7rdjiENKAKfnRDRhcXJNs2JmJQG5FoaTKZZFlgIBOMaHFhtYlYn2lTaWIa4dijUdDSvnhwdBMBJyCEUqheAIWWcG25xh85tkAQMSijd4dpw4uMsknO8EPAZTWVZWJiMd78brAiVjyydboMaZxmaTLi1pLgWgcqI00LDGy2O4GvKV309nG9WGNXaWVp0ZY9DeCx72yMgraSTZ2M2hKOTpJUerSXPSi2a0QBIeQr47ZCt4yy/cxBsnUFQJywII/LwxK3k0BJkn8PU2ZEFrTAAx2NL7HGd1ttxEzDmRbd3znzoHS4x2RygYeYKnh5I3wXpkWM1ulXfk2LqBioQI2xAJ2yMwRudhfcvgHgjoR5fGJiZs32ipHWZYpZWvKAGAUyknnvINOG53Mwjy24EJAQh1w53RWEZXw/Zz7iJIp0eIiOfwY5WgbK/AOYRCvGtLT06KZSKSEa7TP6FoELdVE1TDUYQH/v8LkVWXAcfJxpYxtRiDdYkBJWKWiR90MtHs6ibUsmDdqsaRX5dEoTY8pq6Ms2iSdQBDJAKyJclWc0PYpAZrM0cy7xvQoWTcPybe78yMJHHcfQzt3XsslZ+F02Mg+DABQDp4CbeUbx0T71ThF+CM7Sfw5MxkxtJSdj4xOxvjQIoC0YaO5h6xS4w2382C4MtDzSjTOt5i+bWa0l9vAMk6ykEwwtMUOHlMCtoMjvR40IBwNyeTAM9hz00nOLkUswTE1oMnWs07WPn7dzmSx3GZkWnOaNBmx04+Wgpm2A1pJWbZbX8sDnhRTm9YwtCyj4OCAZojUnjY7ZYtGksI65MmwZ2xHg4igqgIvSMyDU9j4SEhpmTsRYgTaUBsVrbSas7IA6hu5DedoRphWar+VQWUsSpxypQNM7MizbNRiQrO02t9soWl1B0zJNrO8L6b1YeCIC5IimTZgR8oU+iLI5AS7RRqWBRgPsExPjBKKlbZTIG89lhtCpPl7XCk3cGNUmwtM7oa1ffGQ7yur3jjK7ZjU88dMTn0wWsdWD28eFBFD4r2RFkCvsYjJt2yELHEmUSsAFxmqlnhrbbTqnqLCiCNvQmAnVqYlHLnDuyBG1LV6xeJMlyeF0s4XgZNPKsZKBN5gsGt1c0RMn8zSS7wk4/DWAWFQ7ePRjNRTLAzftnSWk1l8RG4+QCLQj0kBEa8HA+ccM3OAl5Rqkv4ayBw15AItBUOcMlk+TuYrBhTvyYz/jdn7wR51bHojQxH+EYyl1n4oEzUcO/tGwR79BHEyDb5NBWr+mtsf7hdv/8/vcubtuc9PuWTsPLB6igTtQzBNsO764kAu45nSK5Sj+3GuP6P2IaM/U2l+24WyFG2ItAWttVO73O+7Yle91O0990eCwHl4mLbD/p59zp3/5ajedPSl0KdKk4XeC4AQ7lhdIQhqti0DRATu/333tkbkxMWuzxopYjHV9pi29hsciGZMkaml5EsshcDf2xP0vf86d/vfjwGXkC8d9BXkAIxxzmFD2iIOTd4Fxqw6TtkgPEpqWd6ABWlF7AAGuDCAmibV1fIHhELgPAnAx02YJUse9tzwdyAx8/jU1OeQy29utcEqQSJ1zryHAlbqPAHZM2+QHoMUBJLo7TNsqmjkE7vaAe+ZU4hQJ3Phzn2nR2M6/G1uxY4G1FATJd/PMg4/ANSZEDLhi2bFRLRU6S9JW0bmwUbT1a9ledUpw72LWuL92qHE3gG+QCkHjEuDW2S+oBBiYiJX74TIumT8C0jKPstLj78+95iiXERGsheYz0+Zbw8+t0kSRPVCgLS6MnUWfvmIAfmbcqw6BuwFuHQcuZzRddjk+gR5jWj03wsVE5b0J9LGdArh6GZezcaMAPDNqcgOmh1i8seVBdAoiN+SoUIc5BO4mmA33RuBeo7IKxt438T40EWvIA2Ns65gT/crkCcUc07iUcSVwx8O7jiujx4RWT1dgz6ZCM9igcX/N7TztMB22LoI1cKuuZHMn40gpLoDXWFxIDcd12gmwoSGa7Py51xyb9NfHI413GTCSdnw9ERyR0/un05Ql3LywRJ9XRkM/P152JAH3heuO29/4+2bgnvrla5wLeVxRy5HHQuWjENOOTMK0NIi4MWp4W9F3GaQCB67MHsQKLBTes7vUBunMQ3vHadMxSg4GvXsRvhcbGfcQuOt6YAZumJyhCfpaoMVkFtuoQM9rm+E19Z7cnPk3ALiV4eCsnjKh6NhK1f1EBkDAmx6fvPsQuOvitdxXgBvSYRJUePLMJeGaK2I5IjP8kMhagAdIa5mW78696li5fZWUVepA4MVVmTZLi+pBoxVi5NyDxSHjborcANxfmidnBLhoPqGZMr0abbw0AE8Ij0fa/AcqCfkzGL5TzYzPwB2bSeIwMF7lxfXqaoCvVVxx08cipMOOHEqFtfEbgXttzCpkxh2aiM2vHNluwyf3OLKiOYwBXHLSZwKuqMlU2obYhnQMZgGgxybAs9NNbLHOR0IXgIduJeAeaty1cev2v/x5zrhxQPPsgmjStD8s/H297IG9YVYCV8uDQoxkQcyffdV5BKbjB3WsxpYo84Bmp8ZELK3E1SGa17oX7ryrfnWzydlcDjmBU6bXx8L6d9KZB30KgVGB1chbWD4L3LCz4/a/+N/dqf9wQ5QKa+9a6JwHkV4NU16S3Y0a8GAaMUwVuCZT8swAZNnsp3lCxewUwdj8hhjeo8THqbw4gTsw7q9uJBX2P/NBt//Hdzvnd8TIGlkUGG2MyJGf2FhlzMerBozBKDeWzSmvYvATs3H5fL9w04nvuP2v/qFzB7KemYd4u2BmbAkYnoBEGw1wU5JK8NO4ziXgdlbESKfXYloCbLyF3GbajF713i0A99ydP+v2Pv0O5xdHe6dVJjvSyJEmisBJNTMm52WHKKM+06S7dggyixaF3yR8s6+kCPBmgBR2nyOXdNogB8TGxpWLwIk9ObbY5lRAlgWwgWXtiZ4/+6rz4bkKVe3wF+vMA2ScWtKWjISFuRVmyHYaybQZKNsA7kfe4Pbv+RXnFkcLVzFi6hQSxUhisaI16UhDM3Y2rdKbdGDtzxw0xqwbCSTbjh7ZBByts/0+NgVMxKztU+Raf/aV5xtTMdn5uBrGVEFrIrYFeWB++G5+71wddtV7NpIK5wJw35mAK8v3DEZMA58nG2nRT1yMQBsHqSCxOPTIZKfBnErLcuCpw5hDA0bqaRv73xRiBPhauGD3oolYm2kLb2Hgoh2kwjtaTKM+Kd+fiMUBHWDa7DlzrcLVmwN3LwGXj//IwBrn05rhr2r9qi7QbmM0mPF3+S8l6iEQgN/1o4hk2SAXmAhCS/v2xgC52qo0s3g2IQ2oaTURAMaNg1ZDEjr+xy5xK/dZIR7+nuhF677S1awDF9sB7t3vdG5nlgrFl9WuYXnuQf5ZhypsF8p69SMo48Dg77EkSA4F8f9nabaSQ5Ybx9tGEbhZygsxLRkTFuoXTgBXG0WHGkvTJb3aK5hRo03ytOYZXgBUs8a9es4qvEB57+gvzn3oDW7v0xS4IzWnKGwnPrRCaJa1pX8UGIjtKr9C0LJBRPcLSYePouJba8qLRqSLiL6pPbimRWIK4Ccv46LzglURV2wfAS7WtKU/JhOSgbS+hlhoWDaa7Etqnm0LALU14L7LuZ35bAbLIanhJSAIyw0fgTrCZhq4kTmlHTDo9WmSqG9Ivo20zXiWkofSmQ09mw/0s3aRM8+tkTkBV4NWV/J0BrZ4HfZGrYdEOqkl2hGo5rLGq+/YjHE/8ga3d/e7yuRMM7UuYuZ6UYQy9gCuac1zvBh7WkzbYPSq6dipjkUi9E6DDG1GB093okjxWS/OcEPhHWMny5m0uFDmrVpPE8Dmpb2zrzwe7s8J8OLZTLwAMOa/q3pK5Mn8d7HB/GuN8L2g1rd0KkzONgRukAoIuDKKWBMxzD5kBFJN81geFIXakvJCMgR8kagMSwyoA3o9AVd5bT/Er6tpS1daEzHm0DJn7mepcLzkcfGBxRZoeceK90jW0TMY8DmmTigjMqX+544776o73JF/tInG/VdR44Y8bp6Pyn41Io2haQNwm3laBCokQyR4CJuRd8txi5UFDVnB0D1amogJSZlgRtN8fHD4Q4NpG4sLBULhGXihpwAXAg/qWq6rasPHmTY0rCUNwt+VHha3bB243NDJ8NDvOkxWl2/HQaHfg0Ar5Yd1mLU8twAAiICCk+34Mm6RI+UBtM0GaAcWFyo+xLluJAL7s69IUmHVCvho6UQufdCqrSGmR9qGK4M7/8diS8Cd87ghHYZDIyO2sp9JOxVTVi1Q2E6biRpuhwK7B4ydKWPAG8vTctaMmjL+DoIWEh1dc7E+jRvsGRWWsJ0ixvQLf+YVxye9ImaHbl4sI/aWQaaMQ1oOVG4yrWaZ2Bu5fy3uOTvv6tu3IBVmjXuEgSWHXrL4l3CJQZG7FM0vWUKEdzXiI0X0KMqNkIUI18X266W8eqfLNORBdMrBxQW9/y05CsGOP/OKC+qP2GPqoKHywrU0LdI/NijCWEvA+53tAPeeOR12lEoXcEJ5Z+8ca9s4KKJTgu8tIOcmYwPly0j2YF15kDSz/owtGUcdQUsE8Uke6HYjohqzHwCuwbbprYoBqb6BFpWrYsZkpxo+BwMSP9LginMVtsG4u/e8y/mdo8kxrLZhRwunby9RPW/HCQsJH6nzGHXSjwQFYe4MdvV+zszxMrmZjLRt/tMcbZpRsO7EXUEe1JVXrmmzr4L5hJ6ItTIqFbjaY9TDY/+6wCtQDl8gJ4CPf2D3hzkorBBS+5zEe7fCuD/ndu95p/M7x0S9MAkjFpNNS7d4zJPdzt9/bu3T7KOwLh2Twf6f/r47+F/3haL4pKdY/MrprCpFqGSa3M6jv98decpzNDbRGJEFgtAav3DLb3/d7f3R7zq3v8+iKm3EWPUb719AZ1oNaxaoZ2UbXggmYooI63v8mZtnqWCAkcyelUhuMi0qlgGgbzgLazNcgNiGVJiB++6kcQVmgvUbNRkH++7YpVe48696q3MLq5JKPFP8eOZDv+B2P/HuWiuBpJp1ntbBgTv67Be7C69/i3M7qKa2/e75r/tf+oI7+bYbnIM7IKwNBCgSVPwwnGgnVuWusZVGbXMjEvgzN18IAjzXHqsxLZcG+Ulzco+ZEoKWh7bmh/22xrgWcO0JaiCKg3139NIr3PFNgHvnL7jdT94RMiT1H9J98XcsVG8FuJ93J992o3Nye3oam6YmDY0BTDs3NLCttB+aTFbQVvLV/VdFTnOMJsCt+kM1qK17uW8v2qtweQigHqahEL0zh9RlYMnzrn73hlkFi3E7Kbm57csDd/TSl2wZuEqjJq0IjrNaLt2xZ7/YXXD9bRsw7ufdyV+8MTJu2eWbVYsBPBQVEgCqRGg7fdXUekVMbEXRGj3PgCDjqu81IG+RjYvyIDhiU+y3S//aEwWiBefNktfM6bCf6MdE44qzd/6c25ulwlxkk/91qvZL17YOXA3aktaksixT0/JgY+Dufenz7tTMuEQqzJo2t4RzC2VCDcxqlzHQsu/NjdieXBNwVoFbG1YAaIh8rImTrl0RtPFd8pwyoIdpYXM6V+H8DYF77s7EuAW4/bLG8g2xgz139LJZKrxlfY175791u5+4vTqOsJ0KStSpZscJGndzxp13+YZV2qGUGp4PBWnQ+E6IzbJ56UFKBPAeUhdTgFuS7ubhutb+MKJTVi1NHP4ehJvcNH/KkhLjwm0HuDNwZo3ZTmGpAvCDvenopVf441dvCbggBFcCSY5MkbwVxv2CO/WLN6ZjRkcKgYxJPNS0gnxK20HBzFSSOlrrV/FbF9YK47ZOzGvSONq5gNhyYP9aaKBhGPSpoa1JhdtxVoHurxNGDyYJk2tz7OsAAB/vSURBVLOXuO0CN/afARZOYn18/49sxrh7X4rAnaXCqlvIWfYgTci4IpMMamQOGDABdkQFXH6vP3PTQ8J/K+8ugM3/oTUte6dmjPhIAMbxw/FQnWjacBiAO0/ONtW4beDmEKc02ZaAe+4TdzifsgqqOi9EgTpnrsCYgXvgjv7I5RtJhQxcfSCI1NvAoUJjVpMHHKMD2YPUYVjuGYBL8ixjZ4jFnQsFq+ZMEzeueE1TWiQJIq4pTfWzVNgUuG+Mk7NQq1AdlEwyZ7fzcCKxJeDufuIONy129A5qsxY5te5BAy4GLZNpiWFZDUkGsrqwSJDQ8Dj2CBeSbWuUVlp/ttbpxLhwwhXuIIdDhJeinQtISAPaR3uKoDzQK0nqPIfFgwFcvn08GszQfvPkbAtSYWZc7jjJbnq0+KpjAO4sFd68QTrsCyEdNp09neBmT5BUc3A9bVwJJUTIC2YGACtIkL+3gjkBdyTdRQYxOT37Liz1tMZEJ1xmMnRhWRIfa5gqr5j/ujXgzlIhTc5KDUB+U6PgYzlr3J/eWOMOAldvbJyzChsCN0iFtybgijwuIrIwbO0KrwhbMidQErTnkOnvWjZxh/ZnbnooOLbK8gyZ8mrk7EgDV9K0XAixmWSEU92efv6179pQ477R7d1N0lEJryXlxZxRhLitAfc9fOVMTcZQfncG0NIdffbl7sIb1mfcCNybuufjlqE0vufMCEVEqQqD7jaihHFAVEBe+NM3PZT7AAvd6XjJ1Jj2woIV4kAtLayN0BOx5jljs8bdMnCzCIOaVkaRWeNeNmcVbtsgj/smFxk3Lfm2zqbN6IjyLWUVtgxc5TTVc+OhlpKokFPpjY2McMojUZSv72CA52QWoo8ALvKKGi5tabBd0DIBb0mLLQF3lzJuBgViWhHi5lqFY5sC94MUuEb0YrGWSJeQDtsicK1PmI7kaMsYEaxIgDL7YdByojIiTWLfClzzXIFYexAKpUyPLNICa9NWo8XWaAXaYhTR2ZBVeKc78vQXGDWxCn3qF2c/9PNu9573kHQU0LQgdIdfHextEbg0q5GbifOgpRNbA+7N9HxcMn5pKmIc86kKX6BE6KS8yNhq6du+NwIXApJQPllNYxNGqjkJLAZPdIT7+bEexmFq5wee5fwjH08OZ7bCTz2SiKJ3ef8fuYNvfCWFwLHK+zxgoTrs2T/ljr/830SpACcdDedZLNzZD93mzt39PlEdJvpqRYEHB7iswaY8KExGL+8WzOBa7snn3WDkYf19c/70jQ9TJs8aL8CgtQQsdJ+tS1AY5Cmv6Opy/1WjdmC+YVq6sAuBnQkb+m9W/nPy3zHOh0XSR0wapsktHn6xWzz2B5PBUR/xlp/c14Nv/E+3/NZfiAHtMG0miwLcf7d2OmyenJ18682yHjcCqdQd0H4hYjDkgULViDwgTtuM0s5h4M5LrIVarQHBQr28r/wHuF+xSAVFeS0I0ZwOeEhlK4MwgtR2FNk4pUNJupqW31suD1tn5AilvliGp3aZdz6UMuU2KGoT03VbAu6pt97sptOnnMulIDMXhNEfmYjNrZJVgQZexNYkKAnNRRcWzmOqmDJu2GqjcpmlIVX/GFVEbKwa8kOsL/OcdQvwbEeGNlBse1sbccdCwG3v5I0mJO+WABU/g3DWnp3nMYISgbRtTseFydlmjHvqLTe7aS4kX+Ro58O2myI/ZX/LH7g0iBaZK8wkCyBbrfE1SYZLAdyyKmbStAWKKHrCbU3gSS1Iwm+hQQEMaDgRtoutbNCGx4+cLsNHrOSRi0Pn9nQZNQ0kG8d+2aSbyEGA9F5JBFsE7vLM6Tj0wymvyLQQ3K0QX8IikITMYREG+OnvgXEZ05bWIMq3j/WpTGaFCg7arPO4g7ZFeQQfB20FFAItZkedp+2UNBZ/bLN5LcgxnE+xEWpfo7yQ3r8/55G3wbivcMszJ/mh2tLZGEJr7UHdrNifTEWUA8IJg9o/LkrWCvvTN/4tHuFb+tDwJh5+EXA7tQeQxfSg4vNX03UtUORPcaDySKPgw1xybEUVYTtuWIMIGNMMgnYOyftzHvlFG0uFk295RZQKQ5qWfCTRlGW5Q9LJUZTsEEF6lC5wD1IhAreGUmDgTUsTxWDzAe3sps0mhTXDuWf2RGLdc7zyE2Nbx5jW3Gbf1X2tXcIGAAJwZ8Z902ZZhRWBq+RBq28getfLB2RTmtNoTkrAfXBWxDTLRpiBjX9wCZgP2Nhp19rbY6cNYMBdqsbEwRogoemVUw4yGYvO5YeGNJuBe+nl7sIbtw1c5KTcfpHpLEnIJ2M4cqH79fNaW4n8qRuyVLBZi0646MCYX8UJHQOdRYPY1X0MSPnqmK5p3FvCCARu47yEpr4jjhFFespzG4PYqz0IjRxZrROyaX7trHEvvdw9ZKvARRmAdfO087hlIxGjhgpndu4UlinWCefpUf7UDQ8Hw6+32kTT1Y41WZrkgWvTpcbphIrQcMurka6Vnj7OtDGixfvbkz09sLgQSAMtRxvtbBS4Y5ovtHcrwL3XBY0753HpsRcFEegAv4GJWJJ1Cp+43JWhOyaR+1EPALc/O69s1piIJX1DNWLpCA4zbLUrhwndeStMcaeK9wk2E5OnAiZJAErTin6mgWX61wzvQr4U3bfaEnO5LdZGuWl/PtfhhZsx7hcTcOmBIAC0tfed3djFLiPRmzt3G1MKk9MgcEd1aUp5FbShIzQbbFRGx2LbwRAPswdtXcXDToNVBPhVuGrJAwIKrWn7bJuLnIJ5Z+BetiFwv3SvO3kbyirIxQUEWDGOsW98S1fTmTm4cQTHhBHezKTCUPZglGUB5ff27TfrIpA8qMbralriFONMa7ElYfciLwZmySZwkbYUAyvD7IMGXJny6kc4Kn/gbmHm3Qg/0XZas9p2qcCNoCJ6A7GsxZYogzBSMENYJtCI8ZGQrD5Vz7g8MPeHgfsMI8X+lz9iTZt/S+ZncpZtHB00lt1QOVWk97cB3CIV8p4zuRo2NvunCwt1REc20/Lns3qTKteSmTngE3DlZAwclQ71YZoACRT0vU47gK1pG2F7marDMjHmdhjfHGPNbNQf11qKaCwO0NGJF2CWhsbO1VjxXZbEEoXkz7ncPeTmDbbuBOC+Mi1AiOyBOd5AvxLD1v8cm8TJoaPRsHUclz91wyPA19NX1LTp7XqyMhY+zexB03gLt/P3nuUWj3qiqsfVbNrKO1K1icJY7yw0cr/Z3ngNGlTK3mzQtAhOD8mctnRHvv8fuvN+9PK1tw7tAeAW3+8dElO8mcum0uyhVOBIsQ0niowxf+r6R5Bxrg9KGx7SL+SA6lpauL0dpz/YKALQVrliHqocP15y/rXvcEef8eMEuNZo///6+zknip1tpMczcB8gjFvm1D3QBS9EgEUTTD4PiVegKjIRyTAJhM8czM8gwK1hkdODMRlT4UHOAC2D5t9bjcfF19Qxwgx0seOOX/ef3NGnP39kjA6vARYowD09H3qX9G1vAl1ChwRuf4IZNVej9LExh4mvjRGcABeB1gKenIiN3is7ZkzEWjpwbnzu3CFwN3bGCtwzjT2FGJDG5Nb8nkQsfB2v16WdK6qEOJU/df338DlL+cmQB0ysjeZpqdbtZg6KzMqarzY8d2feObBwx69/xyHjbgDfCNxX15WzFuMJQuGXdspRy8fJwcQut589kF+HahYKcMt9kPHQ/rCkVZjhOnvEkr7hE5X0gEaICsAtuZKUZzwE7gaQjbcG4N726phVYJkYnBExT3TshPiYo1XPjCnDwkpC45beYUkZgNsG7fyEWgSeZ3XQak1RbyWZBzRtQTrZanMI3O0CNwIrwUjOVxBJjeZ4O9+DMCN8axK3cP5klgoDTFsEsjRZR5fm7e9aFyUvszw2r6aEIxNFwcchcB8M4Or9dDwnzjdSDkymmh/1E7KTqoaQ4VBEWHPN/uR1WeOi8BAuzJE6PXcke0BmnKnaB8sDOzyE64s2AitOM3Cvm9Nhh1mFdRFcpEKpDuMTsUi/YvKd5V5Dk4ZLzOq+fvbBXowqkX/yJ697JCBClKcFIOukTnKJGvWkYuSBe+MbQWndfO9iBu7bD4G7Lmqpxj192ihr7JcX2vn7+cSLxlctgSwpMlQhkhBX+hsGLqmuMjVtL9/XqqfV0qJoK5pBiAlxWZqYPPYQuBtANt4aGfc1sB63eeAgmTixRoTBG015oWiL7tWgDXdqxm1sIS/UiXK86+ZpReojhRnItLRO9hC4WwDuH8asQpYKhFBUuktpPYyBsXMVNGj1uXQzYOf98hK48b0EuPZO3Mh86V+Lac2Om/fy4vFinIHdrmFydigVNkHv3hclcOVqGAFYayKW/ja+L1Bo6QCwsXWD3F8O3NIAROP9ukxW6Kws2ikCL4YZAG1oXlqAeMbzNhm7v9H3BuC+ec7jzmWNqxe8yPmKwnY4WD/pXJH2yj+GSrgBTcuW/CvjysM6UFmjLQ/Ce9MXCWHKC6bLonOklEVKIIxuZ5lv3UmMewjcdb1vBu6JN7/aOQjcNN4dps27eHNMZYeENOTFKhMxtO3fn7z2UbF2wUpvNEBX1IN5jb24QCdh8TnDoI2zVb9wFwSpcAjczYD7mnIgSJWE/ZRVwGRvi7qBKRu0AgcN6RmBW3K1eMWEG0auIxuyIkposB+j5gXjQf3W2bJop3ES5uHR3h37x1e6nSc9DZyYWFsMI4BigtbQo0hDrjdf0LlPvLI4cvl9inrm81GbV3inX7j9//1Vd/Y33++m3V3yMMS0+rm5UgtiwyLBzGOQ6FT2IJlE6u48OYvAxVvBe3WZ4ECN0JEeS5cjkcaZFgt/ckynFdIMbVXb2dDUJaQYgOjaZ5UyPvAOfUhfncwa381lDjCSsmShdhC0LaYtDcA2w6fao5SX4SxpTBLjVhasHlQYLzelLAdGXYruyUrHSJUkULMshZWnVYRinPAnAbv2hk8UbdBhHbbWz85Qm8RCrmIQpd0YGOV7UPQiX0EKl4/pUnMi1tKzaexKmGfjI9+7yvjb8oAHRm5L/8C1fycMNWvzgKfKWR4AvIBeAnprGRcydWlw9JcsQWqvOCAAw/L+dfZClft72Q3ECLmKDYCuGYmSqXofCmGDJPaIhed3nCq2obC2crBGiI9OKckKOTtxIIIALC2a516QI/bpe1IxeQFuk+IZ2o3UyUCVVxMU7WqjYSMn8Ofrc8ubmqwYuKG3ufuDY4NybYX8poE2ugwmwfTLRjpKvXtkN25+S2FDnTPXJJADcRFJNfUzuoVcO23SoqLbTdCGHsOT0ZPzBMYtT+zWD4A0Wbm5X0wcL0XhV3upmcxuhbOWntUTxTqQPZbtMVFiI9W0DIyATJzHLhFEaVZiE/Jg9n0OOjfp2qUCipFATx6UXSfCASm0KQYEPNdg2kju1KnyL4gNI3DbIZoc1oAFdy97UHWIBC3SZMjAeBCZRiRM25cGIJxB8LbbV8ZuKC3Ebcfw0vrMKGHbsQOpcd/GisDB+LZ2+7YcepoZE+3kbDOtiqyl/7xt/oFrMuMaHpVutLeQG3oujWr3fFrGJo0Dz1i4lNKlhkXNeiOatqdnRdilrNLdxo21cHhEAqxyNPb8GqWKmjOjnLBLKnipcon0A4YHfn/zIG1jPMpjIRl25YFm2hTNBJE7/8A1F4Mu0A5IQU4eMTMNt3rNPLCnji4uoHc1WI9IgzHACjYKN422TTNZ6/zWGu/sKBW/I0b/YYek8qACo00YuHjfdnjZEnQKOLtGyLISfQygWcUy7D50OqchXwFwA0OFlTyac1Ne29XDuUkjmtZyDgO0gnrW0sPBwAi0CaCNMBj+1DrzoTJibmlRS6FH6SMh0NnYSNZJWOWHAflCSkohm7dCvLkiJhwFMKpdq4JLE3larvv8aMv0XgFcoi9N3TaWdomdGGWzkYJlwnjJ8Guddg21rGA9hShp1JEMALGlpDOlaS0mlCmvEdnTOenSiJDFX/KsnaVIUfvqPsfqazjXrKKKkIfx7zrKS7MFvihZBSoVClgNBoThgQ9q1rRmPa0AhQYf8jwNWjOP3FvNCr1HTsWYLDElCPOwQF4OrL4vPBDmaVV/p/q1PLptCjGtHPD4s8nkHabNk2x+f5vh87W4ymtuSv7yH0+X1qBpSB4gYPOhiPMd/oFrHl0vWW3XQnKI2jH+Lsq2yhNDu8eq7PHgFPzVsGzWRZRLWlGAi63ctxrq6945khi326bat+I3xCog4oJBs0CbpKwUoYKQrrfbrH0kUsEAbx+ahAlbpTsZBqwon3CZ+8aBS8IvoujxlNlIyqt2ooLdCpc6TGvQosUP9LyR7MEoy7ZBy9o4mO6i0qrYBQJvhGkHGDqFaO0U6Pn8d7V9KLdv1x4Qh4yRv6TMjHrt1H9JwJVxm2wLBpPuww/3rjbRWSmcEYkC70NhhWkmJA2yyxuhqjCCpemlfBGSKXs/BG26l7e7hFXmzEN9Q9KuHeJrFBqRFlj64AnqGhOxVo2FOPC5sO4sFXDFTnsJN8X69P8MYIwUvMBGE6OX2DdUZKO1aRl4EAkUKAD4WqG2OlSRFJxpUXi0IovczTwAPMFG8Q5iJ9Y/g3zUSeDyvei+egAdj84StMiZAVEwsZoB0d506R+4+jHhNjWGAyFqlcWF+I5sWrAOTVi1yM1STs9BW7A8NBFbLRLQd0cYGA5TjKYHJ7bPp7QXHdoUDpkQBSmv3kocfzfW3C2nJJqRj33oS0yGln/1PzNLmSWmtKsEP9mrY5OovaztXDHKtaJrAO5KoE0fr4olZauBYizMa4/nHYdhVqRTwjX6OPtsuB7T9pwWOFkBfH6HtbgA5AH/1YA2Je/v34s0ewMUzKnKvRGz4X87KS9hG9i+YizM5vYnFeq7/YnEuIzyjeR69bh89Xi9KvQ6jWRwYLAd4hArVO2G5Mto+G2cd9UDbSt7oPoL5AGyidLrnAVjnwf6BhcXkHTRNsfFMgAHUB6OMm0FZjUDllYAuI0kN9NDI9mD+NLciLi1jRReIBC0PHY4hIa2ZV+p+B5e7QM6jMVUNLC19oCRgAJd/Kt50Am7WbeD6gI1uAz0oI2t/rfunQtmwuG2vGetZVyTafPOX4O02gxdHaACN4dHFiq0J8cHj6SV8ApOWEk2NFQ1DDvSPw50oy6Cecfgd3vRztFWlRt/B2A8xbTZkJIJQS2tKrkUoAPSpUaw/kdCYqTkJMJY2nDKONaWdLFTXuxxQ5q2UevLQnW1iwYu8vgm06bOKQ8ywhnybK4j9P63XmV/eaZRFyFOHIyjIcGxWlqIRBE3rZCnpWGgAKNhu+gwDWnAUaIK3MOjW5NYk2mj45knJrIP9RrSQmLJkEH1cEN6Q9t5/YmrHwu+uiPYsgkMrEEboSK1jmuXOKDohHNDwCsmGK+LoObJA6sCjXo+b0cBbm8ZV9iO2iW+UzKm1nTr1tKaac6OHi5jAYGW7Fz+xp2q2pHay1pcWD0K5LEDwK0PC40wQWtV9MuVFOQ5PARV+wCQ9lJe4eYx6aJZdmQSRtpKhXMALP+bFX5nPZt7phzaYrz0ewpaBQqTLfO4rTYWNYokDLDnr1lLC48oWC+K0MDsT1z1WOFXtdHRUKOgiIPIoz7SR5hRIOP1QmgIg6PtE4zZ+kq3ofmyt+McrdCDpEMZuNU2I1VeiI0szTkie9S9Kl1YzK1OJWqDlvWLhbNGtBRjq0jAZPT4TAHcCrT6XCN7QAaXhnkOQAlcBdry0eJ8XzwkZORbEtlCxooYawgIZ0O6DxveXFxQepO3DYfudkQaZOiMnWiU1m7cFkuPTMSgZpbRBxEWQfSwZscROHQ2Mi6XB/E06YZm7IGicYJNNGxRKoMrdmhw12daXR2FmMwA7cBELKJI1tKO1THTnQv9KKSjSO5bm0C0/MEZlb48yPKI+cNgLTeTiL3oKivEMnDDfeXMA6QbLS8aKQLnTJvDAjVu/O9+agfX0iaF0tPDvS8agvBU5QHcQp6YTh4XhTTtWN9wcXznKKs0dmZtc4NlC49AsJGJWBgg4SiWxlcgrHUvhpSs8tp4pjzbzp+46pKI2fIyYxm3AQoVzpBGJBetVYeL9SwEDpqE5ZOyNYvJAeHRh8qg/KHoGvSkM4O6gzwQLUZpRCBcoM2lT8jHkE+PMyZDY8FqU8K9ot5hlGll9snQtC1p0LBLQbOqW/BBKuy5yR+J/VsNtNqx2qmdzLRUv+dQAyoqRE7SOqwjhb2WdjM/oEKBZ0mDOWkhC2aUVmeZjbYu5WE6a1JoS0r3xWjCscKNqO2N+UUB7np1B2U+wirR1gCt6p98hlkhtu9PXPW4b7tpekRT0zKkNcIF8h7GtMjIIzNsJF0QYPXzYzgxjFqkifH3/pFIOfIV4Cq2Y4wn2iekCTNfb3m6PHcctKSxkSda8kA8H0bVAjwLtLy/MdLSVrD5jviDPMaUveM7/q+vvOSPvVs8BbKgYXTaibJo0GC8ONlDE6yG0Zk3GsU8LO6DZ/U+WWSyVXw5XhEjg0HkFQcFmg+g/sdnKUD1loDTuJgLEx0mGzpdxgSu7EefaSuMUKQyMJBsC4nATX/iT1x5ya9PbvFPOHA1C+Iw3xggAWRFxpss4w5M4rqrRg0mDm1dhuR9Z39ZjAR9aQAijZglF/t3J5irTGKN6KgGo69p4fg39ojRgvaIJp8OLGQCGy9RJ80OAR/Gxt3lT1z5fbdObnqzc24nGs8GYx98yKMQ23aYdorZ3FW2tzOSaR7rn9+NBrWeeaAjEGZaFZTW3tHRtn3tHxofdG/pXyX0FGH4OKI8eLxFRgK2JG9Fq1aUUwDSUWggihwsJ/cv/Hdf/vhnOr/8qHPu4hgf5ek0cjWsYaTc01KUk79LSJiLe2m6gwLeAm1nwNLIdpmWHLNJwVls2tuNG2yEKrySXTqSiebM6/uxw4PsCGcuTjRCI0rHRPZr11PX4VxNHkA93LJLrv6DbMGwMV/5zQO38yL/zZf+gwuPH/nrDzvnf0Ks1zIaV1oDNYTpkrZ2yTgrnzRkz1uvYGbsXIDarvzKIJUhYC0WHFlcwJqe+DYZprYmDu3s1SIb1VosxFsSofzekBalpfZWm6ipetucgGRKztfbfl9OsHHut/z+4qdDS7/78u99qXP+Pc65Yxz0sSNDoE254AACMRELv1tb044zLawuY6Gbgza2Nf8z9ogpLSCWcY1CccaWBBiQjdQ7BIBSWM6/tccDkYVle1lPq+/lZx5YoENLzOJawzGi5VEtLopA4Xf7y+Xy2ks+8bvvCz+duO5Jj5x2z/5X5/xl5WEEwbbXElAVFEiwDyx1lo6tybRVV/FwaUx0FPHMxhs6gC5OxBjghx3SIAEGWgC8BFrYZnBvNUBiP4tljfcqp8g4MBh/nJCAY8C5iARt/dk7/wd+37/w4k996i/Lb7/7su/7p5N3t3vnLmBbnLmO0uHNMF69cN08rel1LCjEMArYoLPSZ8sDFOLn5wNduwJo+8BDfYjRqzoKsom+rxBNC7Qj42bZlkYpJg9J9Oo45CDgaQXb6YMDd+3jPnnPh1iPv37dYy64cPfYrzjnXtY3MtZk6r6B1I5Re4DBSIuByASQIVmFbhQCE2u2CmaE5t460ypjaYdpzrAb4Xc8T8ulU462SkJpQJQQr1ROpwA/v6OpaUHfls5/cLFz/vWP+djH5s9gcjF64p8/8UnLxXJG9FOZZGANbzMhCzWtwWnJgx6TldoKCUo7zJSIlw6fy8u4ZmgMI1KlQR2gxrI29SCyvq4Y07Jnjm6TONpARj0hy1jfeCERkU60YCZlqFuTqdBhrJlRJVlvgSk0hETGYXKMfb/3YOFe8rjfuvvPBKdXi//VlY9/rp+m9zq3eEL4bRO03GOLPQeAZzItrgnlH97oz7Dh+n3oymD2II64XFxQg5mAgR1GsfTAwgkFhXKqBuCH5IHB0uVe5gHtmmgOPBSBgYQhwBVREnhqtqn/2r7zV37vb3/qM/Qe6FLffdkTnz/56e1uchG81OMZmEX6I4xU6TDxdjmwjVpaajz53sJIwp8kI4msBgPQQD1tbAKo9OpJnzSaYwdN48kKH1Bh+x7wFI3JzIEGUxxO2pb26TJqeMzic/7MzLgQ8AJT5B33T37nlZd8/JMfl3axYoH7q5c94TneTb/knH8qWk0bK020JhNrFIH3wowxqNUIMXOg9JsaCSYPkr2xpmfGFO9XA9Rhy1iW2JM+OQbWsynCYyGTCdACeaGYlhCDciBgf72ShhyDE5mKIsz+DOz3Huy4Wx73sbt/DzmzCdz54u++/Ik/sJzcG72bLnfOX0B2Log1/E7Kiw1ae7WmNrKChXYWAg+AgodplO4iRi73V2nQ1qVigIDTMFA0QFsYT7Fley5RSQp981fWHuhJWPwNjZhGuE+AVc0bmoQlO1kH62HQnnaT//WlX77pkt/+9FdwBMKFnOzasLK2OPmCaXKvnpz/Ye/cUcbspm6JhRWZIzbZiavDGQqhIPTmy1bMHnBjDUzGoESAQNDnma37OabmMmmZiCX/1cDlQ29p1M7ignbIzBfRhKqQSIwRd/hdN/nPueX0Tnfs+G/k7MHawM03PvDSv/uofeee57z/Se+mp03O/W3n/E63AJywmQIE8wAGxmKAsXwfN0h5ZW8ixt4PFhd65YUEsCoSaIrSS+jDs3YOvPCuXi0t6xvhD1R0bk2m4a6KyqIqOrIBbh8TmoB94Jz/lp/856Zp+V+Wx93vXPLRT3/bAiv9fVMqoAcEBvYP/NByWlzm3OJZzk1Pds5f7Nx0kXPuCF7qNHbiio6q9yWDKr0IBkXei2sPLI9HW25GmXbFLfmFiRIALBsA+VG6rYEGvrWAn6/8ySxNRCwtnmmtpqVFk8i4lZC8c3uTcw+45fQN5xdfnvzyD5bTkc8e3Xf3XfypT50aAWy+5v8BUrIHNHvQF7oAAAAASUVORK5CYII=",
+ },
+ typography: {
+ fontFamily: `"Roboto", "Helvetica", "Arial", sans-serif`,
+ useNextVariants: true,
+ h1: {
+ fontSize: 40,
+ },
+ h4: {
+ fontSize: 30,
+ fontWeight: 500,
+ },
+ h6: {
+ fontSize: 22,
+ },
+ body1: {
+ fontSize: 18,
+ },
+ },
+ overrides: {
+ MuiMenu: {
+ list: {
+ backgroundColor: "#383B40",
+ },
+ },
+ MuiCssBaseline: {
+ '@global': {
+ /*
+ scrollbarColor: "#6b6b6b #2b2b2b",
+ "&::-webkit-scrollbar, & *::-webkit-scrollbar": {
+ backgroundColor: "#2b2b2b",
+ },
+ "&::-webkit-scrollbar-thumb, & *::-webkit-scrollbar-thumb": {
+ borderRadius: 8,
+ backgroundColor: "#6b6b6b",
+ minHeight: 24,
+ border: "3px solid #2b2b2b",
+ },
+ "&::-webkit-scrollbar-thumb:focus, & *::-webkit-scrollbar-thumb:focus": {
+ backgroundColor: "#959595",
+ },
+ "&::-webkit-scrollbar-thumb:active, & *::-webkit-scrollbar-thumb:active": {
+ backgroundColor: "#959595",
+ },
+ "&::-webkit-scrollbar-thumb:hover, & *::-webkit-scrollbar-thumb:hover": {
+ backgroundColor: "#959595",
+ },
+ "&::-webkit-scrollbar-corner, & *::-webkit-scrollbar-corner": {
+ backgroundColor: "#2b2b2b",
+ },
+ */
+ }
},
- tooltip: {
- backgroundColor: "white",
- color: 'rgba(0, 0, 0, 0.87)',
- boxShadow: 1,
- fontSize: 11,
- },
- defaultImage: "data:image/png;base64,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",
- },
- typography: {
- "fontFamily": `"Roboto", "Helvetica", "Arial", sans-serif`,
- useNextVariants: true,
- h1: {
- fontSize: 40,
- },
- h4: {
- fontSize: 30,
- fontWeight: 500,
- },
- h6: {
- fontSize: 22,
- },
- body1: {
- fontSize: 18,
- },
- },
- overrides: {
- MuiMenu: {
- list: {
- backgroundColor: "#383B40",
- },
- },
- },
+ },
});
export default theme;
diff --git a/frontend/src/views/About.jsx b/frontend/src/views/About.jsx
index 9c4044f9..12447fb9 100644
--- a/frontend/src/views/About.jsx
+++ b/frontend/src/views/About.jsx
@@ -1,49 +1,74 @@
-import React from 'react';
+import React from "react";
const hrefStyle = {
- color: "#f85a3e",
- textDecoration: "none"
-}
+ color: "#f85a3e",
+ textDecoration: "none",
+};
const About = () => {
+ return (
+
+
About
- return (
-
-
About
+
+ Endao was started as a project in late 2018 as a free service to analyze
+ APK (and soon IPA) files for vulnerabilities. The project was started
+ after I,
+
+ @frikkylikeme
+
+ , found multiple vulnerabilities in IoT devices based purely on their
+ apps. As I wanted to learn more about these kind of vulnerabilities, I
+ looked for solutions that work for my purpose, but didn't find any good,
+ free and easy to use service - hence this site was born.
+
-
- Endao was started as a project in late 2018 as a free service to analyze APK (and soon IPA) files for vulnerabilities. The project was started after I,
- @frikkylikeme
- , found multiple vulnerabilities in IoT devices based purely on their apps. As I wanted to learn more about these kind of vulnerabilities, I looked for solutions that work for my purpose, but didn't find any good, free and easy to use service - hence this site was born.
-
+
+ My personal goal has and will always be to make the internet safer. As
+ the IoT sphere grows, I want to be able to add ways of finding possible
+ vulnerabilities fast to this website. This will hopefully include
+ blogposts when I get around to it, as well as actual implementations.
+ The vulnerability discovery field is in no way new, but I'll try my best
+ to add whatever I can to it. As a disclaimer, I'm an "Ops" person, and I
+ had never done frontend before creating this site. This is as much of a
+ learning project within web development as it is in vulnerability
+ discovery.
+
-
- My personal goal has and will always be to make the internet safer. As the IoT sphere grows, I want to be able to add ways of finding possible vulnerabilities fast to this website. This will hopefully include blogposts when I get around to it, as well as actual implementations. The vulnerability discovery field is in no way new, but I'll try my best to add whatever I can to it. As a disclaimer, I'm an "Ops" person, and I had never done frontend before creating this site. This is as much of a learning project within web development as it is in vulnerability discovery.
-
+
This site currently uses the following projects
+
-
- This site currently uses the following projects
-
-
+
Hopefully it is of use to some people :)
-
Hopefully it is of use to some people :)
+
Thanks
+
Thanks to Andy for the initial frontend help :)
-
Thanks
-
- Thanks to Andy for the initial frontend help :)
-
-
-
Regards
-
- @frikkylikeme
-
-
- )
-}
+
Regards
+
+
+ @frikkylikeme
+
+
+
+ );
+};
export default About;
diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx
index e0169439..11315745 100644
--- a/frontend/src/views/Admin.jsx
+++ b/frontend/src/views/Admin.jsx
@@ -1,120 +1,227 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect } from "react";
-import { makeStyles } from '@material-ui/styles';
-import { useTheme } from '@material-ui/core/styles';
-import {Link} from 'react-router-dom';
+import { makeStyles } from "@material-ui/styles";
+import { useTheme } from "@material-ui/core/styles";
+import { useNavigate, Link } from "react-router-dom";
-import {FormControl, InputLabel, Paper, Card, Tooltip, FormControlLabel, Typography, Switch, Select, MenuItem, Divider, TextField, Button, Tabs, Tab, Grid, List, ListItem, ListItemText, ListItemAvatar, ListItemSecondaryAction, IconButton, Avatar, Zoom, Dialog, DialogTitle, DialogActions, DialogContent, CircularProgress } from '@material-ui/core';
+import {
+ FormControl,
+ InputLabel,
+ Paper,
+ OutlinedInput,
+ Checkbox,
+ Card,
+ Tooltip,
+ FormControlLabel,
+ Typography,
+ Switch,
+ Select,
+ MenuItem,
+ Divider,
+ TextField,
+ Button,
+ Tabs,
+ Tab,
+ Grid,
+ List,
+ ListItem,
+ ListItemText,
+ ListItemAvatar,
+ ListItemSecondaryAction,
+ IconButton,
+ Avatar,
+ Zoom,
+ Dialog,
+ DialogTitle,
+ DialogActions,
+ DialogContent,
+ CircularProgress,
+} from "@material-ui/core";
-import {Edit as EditIcon, FileCopy as FileCopyIcon, Publish as PublishIcon, SelectAll as SelectAllIcon, OpenInNew as OpenInNewIcon, CloudDownload as CloudDownloadIcon, Description as DescriptionIcon, Polymer as PolymerIcon, CheckCircle as CheckCircleIcon, Close as CloseIcon, Apps as AppsIcon, Image as ImageIcon, Delete as DeleteIcon, Cached as CachedIcon, AccessibilityNew as AccessibilityNewIcon, Lock as LockIcon, Eco as EcoIcon, Schedule as ScheduleIcon, Cloud as CloudIcon, Business as BusinessIcon} from '@material-ui/icons';
+import {
+ Edit as EditIcon,
+ FileCopy as FileCopyIcon,
+ Publish as PublishIcon,
+ SelectAll as SelectAllIcon,
+ OpenInNew as OpenInNewIcon,
+ CloudDownload as CloudDownloadIcon,
+ Description as DescriptionIcon,
+ Polymer as PolymerIcon,
+ CheckCircle as CheckCircleIcon,
+ Close as CloseIcon,
+ Apps as AppsIcon,
+ Image as ImageIcon,
+ Delete as DeleteIcon,
+ Cached as CachedIcon,
+ AccessibilityNew as AccessibilityNewIcon,
+ Lock as LockIcon,
+ Eco as EcoIcon,
+ Schedule as ScheduleIcon,
+ Cloud as CloudIcon,
+ Business as BusinessIcon,
+} from "@material-ui/icons";
import { useAlert } from "react-alert";
-import Dropzone from '../components/Dropzone';
-import HandlePayment from './HandlePayment'
-import OrgHeader from '../components/OrgHeader'
+import Dropzone from "../components/Dropzone";
+import HandlePayment from "./HandlePayment";
+import OrgHeader from "../components/OrgHeader.jsx";
const useStyles = makeStyles({
- notchedOutline: {
- borderColor: "#f85a3e !important"
- },
-})
+ notchedOutline: {
+ borderColor: "#f85a3e !important",
+ },
+});
+
+const ITEM_HEIGHT = 48;
+const ITEM_PADDING_TOP = 8;
+const MenuProps = {
+ PaperProps: {
+ style: {
+ maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
+ width: 500,
+ },
+ },
+ getContentAnchorEl: () => null,
+}
+
const Admin = (props) => {
- const { globalUrl, userdata } = props;
+ const { globalUrl, userdata, serverside} = props;
- var upload = ""
- var to_be_copied = ""
- const theme = useTheme();
- const classes = useStyles();
- const [firstRequest, setFirstRequest] = React.useState(true);
- const [orgRequest, setOrgRequest] = React.useState(true);
- const [modalUser, setModalUser] = React.useState({});
- const [orgName, setOrgName] = React.useState("")
- const [modalOpen, setModalOpen] = React.useState(false);
+ var upload = "";
+ var to_be_copied = "";
+ const theme = useTheme();
+ const classes = useStyles();
+ let navigate = useNavigate();
- const [cloudSyncModalOpen, setCloudSyncModalOpen] = React.useState(false);
- const [cloudSyncApikey, setCloudSyncApikey] = React.useState("");
- const [loading, setLoading] = React.useState(false);
+ const [firstRequest, setFirstRequest] = React.useState(true);
+ const [orgRequest, setOrgRequest] = React.useState(true);
+ const [modalUser, setModalUser] = React.useState({});
+ const [orgName, setOrgName] = React.useState("");
+ const [modalOpen, setModalOpen] = React.useState(false);
- const [selectedOrganization, setSelectedOrganization] = React.useState({});
- const [organizationFeatures, setOrganizationFeatures] = React.useState({});
- const [loginInfo, setLoginInfo] = React.useState("");
- const [curTab, setCurTab] = React.useState(0);
- const [users, setUsers] = React.useState([]);
- const [organizations, setOrganizations] = React.useState([]);
- const [orgSyncResponse, setOrgSyncResponse] = React.useState("");
- const [userSettings, setUserSettings] = React.useState({});
+ const [cloudSyncModalOpen, setCloudSyncModalOpen] = React.useState(false);
+ const [cloudSyncApikey, setCloudSyncApikey] = React.useState("");
+ const [loading, setLoading] = React.useState(false);
- const [environments, setEnvironments] = React.useState([]);
- const [authentication, setAuthentication] = React.useState([]);
- const [schedules, setSchedules] = React.useState([])
- const [files, setFiles] = React.useState([])
- const [selectedNamespace, setSelectedNamespace] = React.useState("default")
- const [fileNamespaces, setFileNamespaces] = React.useState([]);
- const [selectedUser, setSelectedUser] = React.useState({})
- const [newUsername, setNewUsername] = React.useState("");
- const [newPassword, setNewPassword] = React.useState("");
- const [selectedUserModalOpen, setSelectedUserModalOpen] = React.useState(false)
- const [selectedAuthentication, setSelectedAuthentication] = React.useState({})
- const [selectedAuthenticationModalOpen, setSelectedAuthenticationModalOpen] = React.useState(false)
- const [authenticationFields, setAuthenticationFields] = React.useState([])
- const [showArchived, setShowArchived] = React.useState(false)
- const [isDropzone, setIsDropzone] = React.useState(false);
+ const [selectedOrganization, setSelectedOrganization] = React.useState({});
+ //console.log("Selected: ", selectedOrganization)
+ const [organizationFeatures, setOrganizationFeatures] = React.useState({});
+ const [loginInfo, setLoginInfo] = React.useState("");
+ const [curTab, setCurTab] = React.useState(0);
+ const [users, setUsers] = React.useState([]);
+ const [organizations, setOrganizations] = React.useState([]);
+ const [orgSyncResponse, setOrgSyncResponse] = React.useState("");
+ const [userSettings, setUserSettings] = React.useState({});
+ const [matchingOrganizations, setMatchingOrganizations] = React.useState([]);
- useEffect(() => {
- if (isDropzone) {
- //redirectOpenApi();
- setIsDropzone(false);
- }
+ const [environments, setEnvironments] = React.useState([]);
+ const [authentication, setAuthentication] = React.useState([]);
+ const [schedules, setSchedules] = React.useState([]);
+ const [files, setFiles] = React.useState([]);
+ const [selectedNamespace, setSelectedNamespace] = React.useState("default");
+ const [fileNamespaces, setFileNamespaces] = React.useState([]);
+ const [selectedUser, setSelectedUser] = React.useState({});
+ const [newUsername, setNewUsername] = React.useState("");
+ const [newPassword, setNewPassword] = React.useState("");
+ const [selectedUserModalOpen, setSelectedUserModalOpen] =
+ React.useState(false);
+ const [selectedAuthentication, setSelectedAuthentication] = React.useState(
+ {}
+ );
+ const [selectedAuthenticationModalOpen, setSelectedAuthenticationModalOpen] =
+ React.useState(false);
+ const [authenticationFields, setAuthenticationFields] = React.useState([]);
+ const [showArchived, setShowArchived] = React.useState(false);
+ const [isDropzone, setIsDropzone] = React.useState(false);
+
+ const [image2FA, setImage2FA] = React.useState("");
+ const [value2FA, setValue2FA] = React.useState("");
+ const [secret2FA, setSecret2FA] = React.useState("");
+ const [show2faSetup, setShow2faSetup] = useState(false);
+
+ useEffect(() => {
+ if (isDropzone) {
+ //redirectOpenApi();
+ setIsDropzone(false);
+ }
}, [isDropzone]);
- const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"
- const getApps = () => {
- fetch(globalUrl+"/api/v1/apps", {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- },
- credentials: "include",
- })
- .then((response) => {
- if (response.status !== 200) {
- console.log("Status not 200 for apps :O!")
- }
+ const isCloud =
+ window.location.host === "localhost:3002" ||
+ window.location.host === "shuffler.io";
- return response.json()
- })
- .then((responseJson) => {
- console.log("apps: ", responseJson)
- //setApps(responseJson)
- //setFilteredApps(responseJson)
- //if (responseJson.length > 0) {
- // setSelectedApp(responseJson[0])
- // if (responseJson[0].actions !== null && responseJson[0].actions.length > 0) {
- // setSelectedAction(responseJson[0].actions[0])
- // } else {
- // setSelectedAction({})
- // }
- //}
+ const get2faCode = (userId) => {
+ fetch(`${globalUrl}/api/v1/users/${userId}/get2fa`, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
})
- .catch(error => {
- alert.error(error.toString())
- });
- }
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for apps :O!");
+ }
- const categories = [
- {
- "name": "Ticketing",
- "apps": [
- "TheHive",
- "Service-Now",
- "SecureWorks",
- ],
- "categories": ["tickets", "ticket", "ticketing"]
- },
- ]
- /*
+ return response.json();
+ })
+ .then((responseJson) => {
+ //console.log("RESPONSE: ", responseJson)
+ if (responseJson.success === true) {
+ //alert.info(responseJson.reason)
+ setImage2FA(responseJson.reason);
+ setSecret2FA(responseJson.extra);
+ }
+ })
+ .catch((error) => {
+ alert.error(error.toString());
+ });
+ };
+
+ const getApps = () => {
+ fetch(globalUrl + "/api/v1/apps", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for apps :O!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ console.log("apps: ", responseJson);
+ //setApps(responseJson)
+ //setFilteredApps(responseJson)
+ //if (responseJson.length > 0) {
+ // setSelectedApp(responseJson[0])
+ // if (responseJson[0].actions !== null && responseJson[0].actions.length > 0) {
+ // setSelectedAction(responseJson[0].actions[0])
+ // } else {
+ // setSelectedAction({})
+ // }
+ //}
+ })
+ .catch((error) => {
+ alert.error(error.toString());
+ });
+ };
+
+ const categories = [
+ {
+ name: "Ticketing",
+ apps: ["TheHive", "Service-Now", "SecureWorks"],
+ categories: ["tickets", "ticket", "ticketing"],
+ },
+ ];
+ /*
"SIEM",
"Active Directory",
"Firewalls",
@@ -134,2045 +241,2790 @@ const Admin = (props) => {
]
*/
- const alert = useAlert()
-
- const deleteAuthentication = (data) => {
- alert.info("Deleting auth " + data.label)
-
- // Just use this one?
- const url = globalUrl + '/api/v1/apps/authentication/' + data.id
- console.log("URL: ", url)
- fetch(url, {
- method: 'DELETE',
- credentials: "include",
- headers: {
- 'Content-Type': 'application/json',
- },
- })
- .then(response =>
- response.json().then(responseJson => {
- console.log("RESP: ", responseJson)
- if (responseJson["success"] === false) {
- alert.error("Failed deleting auth")
- } else {
- // Need to wait because query in ES is too fast
- setTimeout(() => {
- getAppAuthentication()
- }, 1000)
- //alert.success("Successfully deleted authentication!")
- }
- }),
- )
- .catch(error => {
- console.log("Error in userdata: ", error)
- });
- }
-
- const deleteSchedule = (data) => {
- // FIXME - add some check here ROFL
- console.log("INPUT: ", data)
-
- // Just use this one?
- const url = globalUrl + '/api/v1/workflows/' + data["workflow_id"] + "/schedule/" + data.id
- console.log("URL: ", url)
- fetch(url, {
- method: 'DELETE',
- credentials: "include",
- headers: {
- 'Content-Type': 'application/json',
- },
- })
- .then(response =>
- response.json().then(responseJson => {
- console.log("RESP: ", responseJson)
- if (responseJson["success"] === false) {
- alert.error("Failed stopping schedule")
- } else {
- setTimeout(() => {
- getSchedules()
- }, 1500)
- //alert.success("Successfully stopped schedule!")
- }
- }),
- )
- .catch(error => {
- console.log("Error in userdata: ", error)
- });
- }
-
- const handleStopOrgSync = (org_id) => {
- if (org_id === undefined || org_id === null) {
- alert.error("Couldn't get org "+org_id)
- return
- }
-
- const data = {}
-
- const url = globalUrl + '/api/v1/orgs/' + org_id + "/stop_sync";
- 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 => {
- if (response.status === 200) {
- console.log("Cloud sync success?")
- alert.success("Successfully stopped cloud sync")
- } else {
- console.log("Cloud sync fail?")
- alert.error("Failed stopping sync. Try again, and contact support if this persists.")
- }
-
- return response.json()
- })
- .then((responseJson) => {
- setTimeout(() => {
- handleGetOrg(org_id)
- }, 1000)
- })
- .catch(error => {
- alert.error("Err: " + error.toString())
- })
- }
-
- const enableCloudSync = (apikey, organization, disableSync) => {
- setOrgSyncResponse("")
-
- const data = {
- apikey: apikey,
- organization: organization,
- disable: disableSync,
- }
-
- const url = globalUrl + '/api/v1/cloud/setup';
- 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 => {
- setLoading(false)
- if (response.status === 200) {
- console.log("Cloud sync success?")
- } else {
- console.log("Cloud sync fail?")
- }
-
- return response.json()
- //setTimeout(() => {
- //}, 1000)
- })
- .then((responseJson) => {
- console.log("RESP: ", responseJson)
- if (responseJson.success === false && responseJson.reason !== undefined) {
- setOrgSyncResponse(responseJson.reason)
- alert.error("Failed to handle sync: "+responseJson.reason)
- } else if (!responseJson.success) {
- alert.error("Failed to handle sync.")
- } else {
- getOrgs()
- if (disableSync) {
- alert.success("Successfully disabled sync!")
- setOrgSyncResponse("Successfully disabled syncronization")
- } else {
- alert.success("Cloud Syncronization successfully set up!")
- setOrgSyncResponse("Successfully started syncronization. Cloud features you now have access to can be seen below.")
- }
-
- selectedOrganization.cloud_sync = !selectedOrganization.cloud_sync
- setSelectedOrganization(selectedOrganization)
- setCloudSyncApikey("")
-
- handleGetOrg(userdata.active_org.id)
- }
- })
- .catch(error => {
- setLoading(false)
- alert.error("Err: " + error.toString())
- })
- }
-
- const saveAuthentication = (authentication) => {
- const data = authentication
- const url = globalUrl + '/api/v1/apps/authentication';
-
- fetch(url, {
- mode: 'cors',
- method: 'PUT',
- body: JSON.stringify(data),
- credentials: 'include',
- crossDomain: true,
- withCredentials: true,
- headers: {
- 'Content-Type': 'application/json; charset=utf-8',
- },
- })
- .then(response =>
- response.json().then(responseJson => {
- if (responseJson["success"] === false) {
- alert.error("Failed changing authentication")
- } else {
- //alert.success("Successfully password!")
- setSelectedUserModalOpen(false)
- getAppAuthentication()
- }
- }),
- )
- .catch(error => {
- alert.error("Err: " + error.toString())
- });
- }
-
- const editAuthenticationConfig = (id) => {
- const data = {
- "id": id,
- "action": "assign_everywhere",
- }
- const url = globalUrl + '/api/v1/apps/authentication/'+id+"/config";
-
- 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 => {
- if (responseJson["success"] === false) {
- alert.error("Failed overwriting appauth in workflows")
- } else {
- alert.success("Successfully updated auth everywhere!")
- setSelectedUserModalOpen(false)
- setTimeout(() => {
- getAppAuthentication()
- }, 1000)
- }
- }),
- )
- .catch(error => {
- alert.error("Err: " + error.toString())
- });
- }
-
- const createSubOrg = (currentOrgId, name) => {
- const data = { "name": name, "org_id": currentOrgId}
- console.log(data)
- const url = globalUrl + `/api/v1/orgs/${currentOrgId}/create_sub_org`
-
- 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 => {
- if (responseJson["success"] === false) {
- if (responseJson.reason !== undefined) {
- alert.error(responseJson.reason)
- } else {
- alert.error("Failed creating suborg")
- }
- } else {
- alert.success("Successfully created suborg!")
- setSelectedUserModalOpen(false)
- }
-
- setOrgName("")
- setModalOpen(false)
- }),
- )
- .catch(error => {
- alert.error("Err: " + error.toString())
- });
-
- }
-
- const onPasswordChange = () => {
- const data = { "username": selectedUser.username, "newpassword": newPassword }
- const url = globalUrl + '/api/v1/users/passwordchange';
-
- 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 => {
- if (responseJson["success"] === false) {
- if (responseJson.reason !== undefined) {
- alert.error(responseJson.reason)
- } else {
- alert.error("Failed setting new password")
- }
- } else {
- alert.success("Successfully updated password!")
- setSelectedUserModalOpen(false)
- }
- }),
- )
- .catch(error => {
- alert.error("Err: " + error.toString())
- });
- }
-
- const deleteUser = (data) => {
- // Just use this one?
- const userId = data.id
-
- const url = globalUrl + '/api/v1/users/' + userId
- fetch(url, {
- method: 'DELETE',
- credentials: "include",
- headers: {
- 'Content-Type': 'application/json',
- },
- })
- .then(response => {
- if (response.status === 200) {
- getUsers()
- }
-
- return response.json()
- })
- .then((responseJson) => {
- if (!responseJson.success && responseJson.reason !== undefined) {
- alert.error("Failed to deactivate user: "+responseJson.reason)
- } else {
- alert.success("Deactivated user "+data.id)
- }
- })
-
- .catch(error => {
- console.log("Error in userdata: ", error)
- });
- }
-
- const handleGetOrg = (orgId) => {
- if (orgId.length === 0) {
- alert.error("Organization ID not defined. Please contact us on https://shuffler.io if this persists logout.")
- return
- }
-
- // Just use this one?
- var baseurl = globalUrl
- const url = baseurl + '/api/v1/orgs/'+orgId
- fetch(url, {
- method: 'GET',
- credentials: "include",
- headers: {
- 'Content-Type': 'application/json',
- },
- })
- .then(response => {
- if (response.status === 401) {
- }
-
- return response.json()
- })
- .then(responseJson => {
- if (responseJson["success"] === false) {
- alert.error("Failed getting org: ", responseJson.readon)
- } else {
- if (responseJson.sync_features === undefined || responseJson.sync_features === null) {
- responseJson.sync_features = {}
-
- }
- setSelectedOrganization(responseJson)
- var lists = {
- "active": {
- "triggers": [],
- "features": [],
- "sync": [],
- },
- "inactive": {
- "triggers": [],
- "features": [],
- "sync": [],
- },
- }
-
-
- // FIXME: Set up features
- //Object.keys(responseJson.sync_features).map(function(key, index) {
- // //console.log(responseJson.sync_features[key])
- //})
-
- //setOrgName(responseJson.name)
- //setOrgDescription(responseJson.description)
- setOrganizationFeatures(lists)
- }
- })
- .catch(error => {
- console.log("Error getting org: ", error)
- alert.error("Error getting current organization")
- });
- }
-
- const inviteUser = (data) => {
- console.log("INPUT: ", data)
- setLoginInfo("")
-
- // Just use this one?
- var data = { "username": data.Username, "type": "invite", "org_id": selectedOrganization.id}
- var baseurl = globalUrl
- const url = baseurl + '/api/v1/users/register_org';
-
- fetch(url, {
- method: 'POST',
- credentials: "include",
- body: JSON.stringify(data),
- headers: {
- 'Content-Type': 'application/json',
- },
- })
- .then(response =>
- response.json().then(responseJson => {
- if (responseJson["success"] === false) {
- setLoginInfo("Error: " + responseJson.reason)
- } else {
- setLoginInfo("")
- setModalOpen(false)
- setTimeout(() => {
- getUsers()
- }, 1000)
- }
- }),
- )
- .catch(error => {
- console.log("Error in userdata: ", error)
- });
- }
-
- const submitUser = (data) => {
- console.log("INPUT: ", data)
- setLoginInfo("")
-
- // Just use this one?
- var data = { "username": data.Username, "password": data.Password }
- var baseurl = globalUrl
- const url = baseurl + '/api/v1/users/register';
-
- fetch(url, {
- method: 'POST',
- credentials: "include",
- body: JSON.stringify(data),
- headers: {
- 'Content-Type': 'application/json',
- },
- })
- .then(response =>
- response.json().then(responseJson => {
- if (responseJson["success"] === false) {
- setLoginInfo("Error: " + responseJson.reason)
- } else {
- setLoginInfo("")
- setModalOpen(false)
- setTimeout(() => {
- getUsers()
- }, 1000)
- }
- }),
- )
- .catch(error => {
- console.log("Error in userdata: ", error)
- });
- }
-
- // Horrible frontend fix for environments
- const setDefaultEnvironment = (environment) => {
- // FIXME - add more checks to this
- alert.info("Setting default env to " + environment.name)
- var newEnv = []
- for (var key in environments) {
- if (environments[key].id == environment.id) {
- if (environments[key].archived) {
- alert.error("Can't set archived to default")
- return
- }
-
- environments[key].default = true
- } else if (environments[key].default == true && environments[key].id !== environment.id) {
- environments[key].default = false
- }
-
- newEnv.push(environments[key])
- }
-
- // Just use this one?
- const url = globalUrl + '/api/v1/setenvironments';
- fetch(url, {
- method: 'PUT',
- credentials: "include",
- body: JSON.stringify(newEnv),
- headers: {
- 'Content-Type': 'application/json',
- },
- })
- .then(response =>
- response.json().then(responseJson => {
- if (responseJson["success"] === false) {
- alert.error(responseJson.reason)
- setTimeout(() => {
- getEnvironments()
- }, 1500)
- } else {
- setLoginInfo("")
- setModalOpen(false)
- setTimeout(() => {
- getEnvironments()
- }, 1500)
- }
- }),
- )
- .catch(error => {
- console.log("Error in backend data: ", error)
- })
- }
-
- const flushQueue = (name) => {
- // Just use this one?
- const url = globalUrl + '/api/v1/flush_queue';
- fetch(url, {
- method: 'DELETE',
- credentials: "include",
- headers: {
- 'Content-Type': 'application/json',
- },
- })
- .then(response =>
- response.json().then(responseJson => {
- if (responseJson["success"] === false) {
- alert.error(responseJson.reason)
- getEnvironments()
- } else {
- setLoginInfo("")
- setModalOpen(false)
- getEnvironments()
- }
- }),
- )
- .catch(error => {
- console.log("Error when deleting: ", error)
- })
- }
-
- const deleteEnvironment = (environment) => {
- // FIXME - add some check here ROFL
- //const name = environment.name
-
- //alert.info("Modifying environment " + name)
- //var newEnv = []
- //for (var key in environments) {
- // if (environments[key].Name == name) {
- // if (environments[key].default) {
- // alert.error("Can't modify the default environment")
- // return
- // }
-
- // if (environments[key].type === "cloud" && !environments[key].archived) {
- // alert.error("Can't modify cloud environments")
- // return
- // }
-
- // environments[key].archived = !environments[key].archived
- // }
-
- // newEnv.push(environments[key])
- //}
- const id = environment.id
-
- //alert.info("Modifying environment " + environment.Name)
- var newEnv = []
- for (var key in environments) {
- if (environments[key].id == id) {
- if (environments[key].default) {
- alert.error("Can't modify the default environment")
- return
- }
-
- if (environments[key].type === "cloud" && !environments[key].archived) {
- alert.error("Can't modify cloud environments")
- return
- }
-
- environments[key].archived = !environments[key].archived
- }
-
- newEnv.push(environments[key])
- }
-
- // Just use this one?
- const url = globalUrl + '/api/v1/setenvironments';
- fetch(url, {
- method: 'PUT',
- credentials: "include",
- body: JSON.stringify(newEnv),
- headers: {
- 'Content-Type': 'application/json',
- },
- })
- .then(response =>
- response.json().then(responseJson => {
- if (responseJson["success"] === false) {
- alert.error(responseJson.reason)
- getEnvironments()
- } else {
- setLoginInfo("")
- setModalOpen(false)
- getEnvironments()
- }
- }),
- )
- .catch(error => {
- console.log("Error when deleting: ", error)
- })
- }
-
- const submitEnvironment = (data) => {
- // FIXME - add some check here ROFL
- environments.push({
- "name": data.environment,
- "type": "onprem",
- })
-
- // Just use this one?
- var baseurl = globalUrl
- const url = baseurl + '/api/v1/setenvironments';
- fetch(url, {
- method: 'PUT',
- credentials: "include",
- body: JSON.stringify(environments),
- headers: {
- 'Content-Type': 'application/json',
- },
- })
- .then(response =>
- response.json().then(responseJson => {
- if (responseJson["success"] === false) {
- setLoginInfo("Error in input: " + responseJson.reason)
- getEnvironments()
- } else {
- setLoginInfo("")
- setModalOpen(false)
- getEnvironments()
- }
- }),
- )
- .catch(error => {
- console.log("Error in userdata: ", error)
- });
- }
-
- const handleFileUpload = (file_id, file) => {
- //console.log("FILE: ", file_id, file)
- fetch(`${globalUrl}/api/v1/files/${file_id}/upload`, {
- method: 'POST',
- credentials: "include",
- body: file,
- })
- .then((response) => {
- if (response.status !== 200) {
- console.log("Status not 200 for apps :O!")
- return
- }
-
- return response.json()
- })
- .then((responseJson) => {
- //console.log("RESPONSE: ", responseJson)
- //setFiles(responseJson)
- })
- .catch(error => {
- //alert.error(error.toString())
- });
- }
-
- const handleCreateFile = (filename, file) => {
- const data = {
- "filename": filename,
- "org_id": selectedOrganization.id,
- "workflow_id": "global",
- }
-
- fetch(globalUrl + "/api/v1/files/create", {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- },
- credentials: "include",
- body: JSON.stringify(data),
- })
- .then((response) => {
- if (response.status !== 200) {
- console.log("Status not 200 for apps :O!")
- return
- }
-
- return response.json()
- })
- .then((responseJson) => {
- //console.log("RESP: ", responseJson)
- if (responseJson.success) {
- handleFileUpload(responseJson.id, file)
- } else {
- alert.error("Failed to upload file ", filename)
- }
- })
- .catch(error => {
- alert.error(error.toString())
- });
- }
-
- const getFiles = () => {
- fetch(globalUrl + "/api/v1/files", {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- },
- credentials: "include",
- })
- .then((response) => {
- if (response.status !== 200) {
- console.log("Status not 200 for apps :O!")
- return
- }
-
- return response.json()
- })
- .then((responseJson) => {
- if (responseJson.files !== undefined && responseJson.files !== null) {
- setFiles(responseJson.files)
- } else {
- setFiles([])
- }
-
- console.log("NAMESPACES: ", responseJson.namespaces)
- if (responseJson.namespaces !== undefined && responseJson.namespaces !== null) {
- setFileNamespaces(responseJson.namespaces)
- }
- })
- .catch(error => {
- alert.error(error.toString())
- });
- }
-
- const downloadFile = (file) => {
- fetch(globalUrl + "/api/v1/files/"+file.id+"/content", {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- },
- credentials: "include",
- })
- .then((response) => {
- if (response.status !== 200) {
- console.log("Status not 200 for apps :O!")
- return ""
- }
-
- return response.text()
- })
- .then((respdata) => {
- if (respdata.length === 0) {
- alert.error("Failed getting file")
- return
- }
-
- var blob = new Blob( [ respdata ], {
- type: 'application/octet-stream'
- })
-
- var url = URL.createObjectURL( blob )
- var link = document.createElement( 'a' )
- link.setAttribute( 'href', url )
- link.setAttribute( 'download', `${file.filename}` )
- var event = document.createEvent( 'MouseEvents' )
- event.initMouseEvent( 'click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null)
- link.dispatchEvent( event )
-
- //return response.json()
- })
- .then((responseJson) => {
- //console.log(responseJson)
- //setSchedules(responseJson)
- })
- .catch(error => {
- alert.error(error.toString())
- });
- }
-
- const getSchedules = () => {
- fetch(globalUrl + "/api/v1/workflows/schedules", {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- },
- credentials: "include",
- })
- .then((response) => {
- if (response.status !== 200) {
- console.log("Status not 200 for apps :O!")
- return
- }
-
- return response.json()
- })
- .then((responseJson) => {
- setSchedules(responseJson)
- })
- .catch(error => {
- alert.error(error.toString())
- });
- }
-
- const getAppAuthentication = () => {
- fetch(globalUrl + "/api/v1/apps/authentication", {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- },
- credentials: "include",
- })
- .then((response) => {
- if (response.status !== 200) {
- console.log("Status not 200 for apps :O!")
- return
- }
-
- return response.json()
- })
- .then((responseJson) => {
- if (responseJson.success) {
- //console.log(responseJson.data)
- //console.log(responseJson)
- setAuthentication(responseJson.data)
- } else {
- alert.error("Failed getting authentications")
- }
- })
- .catch(error => {
- alert.error(error.toString())
- });
- }
-
- const getEnvironments = () => {
- fetch(globalUrl + "/api/v1/getenvironments", {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- },
- credentials: "include",
- })
- .then((response) => {
- if (response.status !== 200) {
- console.log("Status not 200 for apps :O!")
- return
- }
-
- return response.json()
- })
- .then((responseJson) => {
- setEnvironments(responseJson)
- })
- .catch(error => {
- alert.error(error.toString())
- });
- }
-
- const getOrgs = () => {
- // API no longer in use, as it's in handleInfo request
- return
-
- fetch(globalUrl + "/api/v1/orgs", {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- },
- credentials: "include",
- })
- .then((response) => {
- if (response.status !== 200) {
- console.log("Status not 200 for apps :O!")
- return
- }
-
- return response.json()
- })
- .then((responseJson) => {
- setOrganizations(responseJson)
- })
- .catch(error => {
- alert.error(error.toString())
- });
- }
-
- const getUsers = () => {
- fetch(globalUrl + "/api/v1/getusers", {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- },
- credentials: "include",
- })
- .then((response) => {
- if (response.status !== 200) {
- // Ahh, this happens because they're not admin
- // window.location.pathname = "/workflows"
- return
- }
-
- return response.json()
- })
- .then((responseJson) => {
- setUsers(responseJson)
- })
- .catch(error => {
- alert.error(error.toString())
- });
- }
-
- const getSettings = () => {
- fetch(globalUrl+"/api/v1/getsettings", {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- },
- credentials: "include",
- })
- .then((response) => {
- if (response.status !== 200) {
- console.log("Status not 200 when getting settings :O!")
- }
-
- return response.json()
- })
- .then((responseJson) => {
- setUserSettings(responseJson)
+ const alert = useAlert();
+
+ const deleteAuthentication = (data) => {
+ alert.info("Deleting auth " + data.label);
+
+ // Just use this one?
+ const url = globalUrl + "/api/v1/apps/authentication/" + data.id;
+ console.log("URL: ", url);
+ fetch(url, {
+ method: "DELETE",
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ },
})
- .catch(error => {
- console.log(error)
- });
- }
+ .then((response) =>
+ response.json().then((responseJson) => {
+ console.log("RESP: ", responseJson);
+ if (responseJson["success"] === false) {
+ alert.error("Failed deleting auth");
+ } else {
+ // Need to wait because query in ES is too fast
+ setTimeout(() => {
+ getAppAuthentication();
+ }, 1000);
+ //alert.success("Successfully deleted authentication!")
+ }
+ })
+ )
+ .catch((error) => {
+ console.log("Error in userdata: ", error);
+ });
+ };
- const views = {
- 0: "organization",
- 1: "users",
- 2: "app_auth",
- 3: "files",
- 4: "schedules",
- 5: "environments",
- 6: "categories",
- }
- const setConfig = (event, newValue) => {
- //console.log("Value: ", newValue)
+ const deleteSchedule = (data) => {
+ // FIXME - add some check here ROFL
+ console.log("INPUT: ", data);
- setCurTab(parseInt(newValue))
- if (newValue === 1) {
- document.title = "Shuffle - admin - users"
- getUsers()
- } else if (newValue === 2) {
- document.title = "Shuffle - admin - app authentication"
- getAppAuthentication()
- } else if (newValue === 3) {
- document.title = "Shuffle - admin - files"
- getFiles()
- } else if (newValue === 4) {
- document.title = "Shuffle - admin - schedules"
- getSchedules()
- } else if (newValue === 5) {
- document.title = "Shuffle - admin - environments"
- getEnvironments()
- } else if (newValue === 6) {
- document.title = "Shuffle - admin - orgs"
- getOrgs()
- } else {
- document.title = "Shuffle - admin"
- }
+ // Just use this one?
+ const url =
+ globalUrl +
+ "/api/v1/workflows/" +
+ data["workflow_id"] +
+ "/schedule/" +
+ data.id;
+ console.log("URL: ", url);
+ fetch(url, {
+ method: "DELETE",
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ })
+ .then((response) =>
+ response.json().then((responseJson) => {
+ console.log("RESP: ", responseJson);
+ if (responseJson["success"] === false) {
+ alert.error("Failed stopping schedule");
+ } else {
+ setTimeout(() => {
+ getSchedules();
+ }, 1500);
+ //alert.success("Successfully stopped schedule!")
+ }
+ })
+ )
+ .catch((error) => {
+ console.log("Error in userdata: ", error);
+ });
+ };
- if (newValue === 6) {
- console.log("Should get apps for categories.")
- }
+ const handleVerify2FA = (userId, code) => {
+ const data = {
+ code: code,
+ user_id: userId,
+ };
+ fetch(`${globalUrl}/api/v1/users/${userId}/set2fa`, {
+ mode: "cors",
+ method: "POST",
+ body: JSON.stringify(data),
+ credentials: "include",
+ crossDomain: true,
+ withCredentials: true,
+ headers: {
+ "Content-Type": "application/json; charset=utf-8",
+ },
+ })
+ .then((response) => {
+ if (response.status === 200) {
+ } else {
+ //alert.info("Wrong code sent.")
+ //alert.info("Wrong code sent. Please try again.")
+ }
- //var theURL = window.location.pathname
- //FIXME: Add url edits
- //var theURL = window.location
- //theURL.replace(`/${views[curTab]}`, `/${views[newValue]}`)
- //window.history.pushState({"html":response.html,"pageTitle":response.pageTitle},"", urlPath);
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (responseJson.success === true) {
+ alert.info("Successfully enabled 2fa");
- //console.log(newpath)
- //window.location.pathame = newpath
+ setTimeout(() => {
+ getUsers();
- setModalUser({})
- }
+ setImage2FA("");
+ setValue2FA("");
+ setSecret2FA("");
+ setShow2faSetup(false);
+ setSelectedUserModalOpen(false);
+ }, 1000);
+ } else {
+ alert.info("Wrong code sent. Please try again.");
+ //alert.error("Failed setting 2fa: ", responseJson.reason)
+ }
+ })
+ .catch((error) => {
+ alert.info("Wrong code sent. Please try again.");
+ //alert.error("Err: " + error.toString())
+ });
+ };
+ const handleStopOrgSync = (org_id) => {
+ if (org_id === undefined || org_id === null) {
+ alert.error("Couldn't get org " + org_id);
+ return;
+ }
- if (firstRequest) {
- setFirstRequest(false)
- document.title = "Shuffle - admin"
- if (!isCloud) {
- getUsers()
- } else {
- getSettings()
- }
+ const data = {};
- if (props.match.params.key !== undefined) {
- //const tmpitem = views[props.match.params.key]
- setConfig("", props.match.params.key)
- }
- }
+ const url = globalUrl + "/api/v1/orgs/" + org_id + "/stop_sync";
+ 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) => {
+ if (response.status === 200) {
+ console.log("Cloud sync success?");
+ alert.success("Successfully stopped cloud sync");
+ } else {
+ console.log("Cloud sync fail?");
+ alert.error(
+ "Failed stopping sync. Try again, and contact support if this persists."
+ );
+ }
- if (selectedOrganization.id === undefined && userdata !== undefined && userdata.active_org !== undefined && orgRequest) {
- setOrgRequest(false)
- handleGetOrg(userdata.active_org.id)
- }
+ return response.json();
+ })
+ .then((responseJson) => {
+ setTimeout(() => {
+ handleGetOrg(org_id);
+ }, 1000);
+ })
+ .catch((error) => {
+ alert.error("Err: " + error.toString());
+ });
+ };
- const paperStyle = {
- maxWidth: 1250,
- margin: "auto",
- color: "white",
- backgroundColor: theme.palette.surfaceColor,
- marginBottom: 10,
- padding: 20,
- }
+ const enableCloudSync = (apikey, organization, disableSync) => {
+ setOrgSyncResponse("");
- const changeModalData = (field, value) => {
- modalUser[field] = value
- }
+ const data = {
+ apikey: apikey,
+ organization: organization,
+ disable: disableSync,
+ };
- const setUser = (userId, field, value) => {
- const data = { "user_id": userId }
- data[field] = value
+ const url = globalUrl + "/api/v1/cloud/setup";
+ 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) => {
+ setLoading(false);
+ if (response.status === 200) {
+ console.log("Cloud sync success?");
+ } else {
+ console.log("Cloud sync fail?");
+ }
- fetch(globalUrl + "/api/v1/users/updateuser", {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- },
- body: JSON.stringify(data),
- credentials: "include",
- })
- .then((response) => {
- if (response.status !== 200) {
- console.log("Status not 200 for WORKFLOW EXECUTION :O!")
- } else {
- getUsers()
+ return response.json();
+ //setTimeout(() => {
+ //}, 1000)
+ })
+ .then((responseJson) => {
+ console.log("RESP: ", responseJson);
+ if (
+ responseJson.success === false &&
+ responseJson.reason !== undefined
+ ) {
+ setOrgSyncResponse(responseJson.reason);
+ alert.error("Failed to handle sync: " + responseJson.reason);
+ } else if (!responseJson.success) {
+ alert.error("Failed to handle sync.");
+ } else {
+ getOrgs();
+ if (disableSync) {
+ alert.success("Successfully disabled sync!");
+ setOrgSyncResponse("Successfully disabled syncronization");
+ } else {
+ alert.success("Cloud Syncronization successfully set up!");
+ setOrgSyncResponse(
+ "Successfully started syncronization. Cloud features you now have access to can be seen below."
+ );
+ }
+
+ selectedOrganization.cloud_sync = !selectedOrganization.cloud_sync;
+ setSelectedOrganization(selectedOrganization);
+ setCloudSyncApikey("");
+
+ handleGetOrg(userdata.active_org.id);
+ }
+ })
+ .catch((error) => {
+ setLoading(false);
+ alert.error("Err: " + error.toString());
+ });
+ };
+
+ const saveAuthentication = (authentication) => {
+ const data = authentication;
+ const url = globalUrl + "/api/v1/apps/authentication";
+
+ fetch(url, {
+ mode: "cors",
+ method: "PUT",
+ body: JSON.stringify(data),
+ credentials: "include",
+ crossDomain: true,
+ withCredentials: true,
+ headers: {
+ "Content-Type": "application/json; charset=utf-8",
+ },
+ })
+ .then((response) =>
+ response.json().then((responseJson) => {
+ if (responseJson["success"] === false) {
+ alert.error("Failed changing authentication");
+ } else {
+ //alert.success("Successfully password!")
+ setSelectedUserModalOpen(false);
+ getAppAuthentication();
+ }
+ })
+ )
+ .catch((error) => {
+ alert.error("Err: " + error.toString());
+ });
+ };
+
+ const editAuthenticationConfig = (id) => {
+ const data = {
+ id: id,
+ action: "assign_everywhere",
+ };
+ const url = globalUrl + "/api/v1/apps/authentication/" + id + "/config";
+
+ 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) => {
+ if (responseJson["success"] === false) {
+ alert.error("Failed overwriting appauth in workflows");
+ } else {
+ alert.success("Successfully updated auth everywhere!");
+ setSelectedUserModalOpen(false);
+ setTimeout(() => {
+ getAppAuthentication();
+ }, 1000);
+ }
+ })
+ )
+ .catch((error) => {
+ alert.error("Err: " + error.toString());
+ });
+ };
+
+ const createSubOrg = (currentOrgId, name) => {
+ const data = { name: name, org_id: currentOrgId };
+ console.log(data);
+ const url = globalUrl + `/api/v1/orgs/${currentOrgId}/create_sub_org`;
+
+ 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) => {
+ if (responseJson["success"] === false) {
+ if (responseJson.reason !== undefined) {
+ alert.error(responseJson.reason);
+ } else {
+ alert.error("Failed creating suborg. Please try again");
+ }
+ } else {
+ alert.success("Successfully created suborg. Reloading in 3 seconds!");
+ setSelectedUserModalOpen(false);
+
+ setTimeout(() => {
+ window.location.reload()
+ }, 2500);
+ }
+
+ setOrgName("");
+ setModalOpen(false);
+ })
+ )
+ .catch((error) => {
+ alert.error("Err: " + error.toString());
+ });
+ };
+
+ const onPasswordChange = () => {
+ const data = { username: selectedUser.username, newpassword: newPassword };
+ const url = globalUrl + "/api/v1/users/passwordchange";
+
+ 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) => {
+ if (responseJson["success"] === false) {
+ if (responseJson.reason !== undefined) {
+ alert.error(responseJson.reason);
+ } else {
+ alert.error("Failed setting new password");
+ }
+ } else {
+ alert.success("Successfully updated password!");
+ setSelectedUserModalOpen(false);
+ }
+ })
+ )
+ .catch((error) => {
+ alert.error("Err: " + error.toString());
+ });
+ };
+
+ const deleteUser = (data) => {
+ // Just use this one?
+ const userId = data.id;
+
+ const url = globalUrl + "/api/v1/users/" + userId;
+ fetch(url, {
+ method: "DELETE",
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ })
+ .then((response) => {
+ if (response.status === 200) {
+ getUsers();
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (!responseJson.success && responseJson.reason !== undefined) {
+ alert.error("Failed to deactivate user: " + responseJson.reason);
+ } else {
+ alert.success("Changed activation for user " + data.id);
+ }
+ })
+
+ .catch((error) => {
+ console.log("Error in userdata: ", error);
+ });
+ };
+
+ const handleGetOrg = (orgId) => {
+ if (orgId.length === 0) {
+ alert.error(
+ "Organization ID not defined. Please contact us on https://shuffler.io if this persists logout."
+ );
+ return;
+ }
+
+ // Just use this one?
+ var baseurl = globalUrl;
+ const url = baseurl + "/api/v1/orgs/" + orgId;
+ fetch(url, {
+ method: "GET",
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ })
+ .then((response) => {
+ if (response.status === 401) {
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (responseJson["success"] === false) {
+ alert.error("Failed getting org: ", responseJson.readon);
+ } else {
+ if (
+ responseJson.sync_features === undefined ||
+ responseJson.sync_features === null
+ ) {
+ responseJson.sync_features = {};
+ }
+ setSelectedOrganization(responseJson);
+ var lists = {
+ active: {
+ triggers: [],
+ features: [],
+ sync: [],
+ },
+ inactive: {
+ triggers: [],
+ features: [],
+ sync: [],
+ },
+ };
+
+ // FIXME: Set up features
+ //Object.keys(responseJson.sync_features).map(function(key, index) {
+ // //console.log(responseJson.sync_features[key])
+ //})
+
+ //setOrgName(responseJson.name)
+ //setOrgDescription(responseJson.description)
+ setOrganizationFeatures(lists);
+ }
+ })
+ .catch((error) => {
+ console.log("Error getting org: ", error);
+ alert.error("Error getting current organization");
+ });
+ };
+
+ const inviteUser = (data) => {
+ console.log("INPUT: ", data);
+ setLoginInfo("");
+
+ // Just use this one?
+ var data = {
+ username: data.Username,
+ type: "invite",
+ org_id: selectedOrganization.id,
+ };
+ var baseurl = globalUrl;
+ const url = baseurl + "/api/v1/users/register_org";
+
+ fetch(url, {
+ method: "POST",
+ credentials: "include",
+ body: JSON.stringify(data),
+ headers: {
+ "Content-Type": "application/json",
+ },
+ })
+ .then((response) =>
+ response.json().then((responseJson) => {
+ if (responseJson["success"] === false) {
+ setLoginInfo("Error: " + responseJson.reason);
+ } else {
+ setLoginInfo("");
+ setModalOpen(false);
+ setTimeout(() => {
+ getUsers();
+ }, 1000);
+ }
+ })
+ )
+ .catch((error) => {
+ console.log("Error in userdata: ", error);
+ });
+ };
+
+ const submitUser = (data) => {
+ console.log("INPUT: ", data);
+ setLoginInfo("");
+
+ // Just use this one?
+ var data = { username: data.Username, password: data.Password };
+ var baseurl = globalUrl;
+ const url = baseurl + "/api/v1/users/register";
+
+ fetch(url, {
+ method: "POST",
+ credentials: "include",
+ body: JSON.stringify(data),
+ headers: {
+ "Content-Type": "application/json",
+ },
+ })
+ .then((response) =>
+ response.json().then((responseJson) => {
+ if (responseJson["success"] === false) {
+ setLoginInfo("Error: " + responseJson.reason);
+ } else {
+ setLoginInfo("");
+ setModalOpen(false);
+ setTimeout(() => {
+ getUsers();
+ }, 1000);
+ }
+ })
+ )
+ .catch((error) => {
+ console.log("Error in userdata: ", error);
+ });
+ };
+
+ // Horrible frontend fix for environments
+ const setDefaultEnvironment = (environment) => {
+ // FIXME - add more checks to this
+ alert.info("Setting default env to " + environment.name);
+ var newEnv = [];
+ for (var key in environments) {
+ if (environments[key].id == environment.id) {
+ if (environments[key].archived) {
+ alert.error("Can't set archived to default");
+ return;
+ }
+
+ environments[key].default = true;
+ } else if (
+ environments[key].default == true &&
+ environments[key].id !== environment.id
+ ) {
+ environments[key].default = false;
+ }
+
+ newEnv.push(environments[key]);
+ }
+
+ // Just use this one?
+ const url = globalUrl + "/api/v1/setenvironments";
+ fetch(url, {
+ method: "PUT",
+ credentials: "include",
+ body: JSON.stringify(newEnv),
+ headers: {
+ "Content-Type": "application/json",
+ },
+ })
+ .then((response) =>
+ response.json().then((responseJson) => {
+ if (responseJson["success"] === false) {
+ alert.error(responseJson.reason);
+ setTimeout(() => {
+ getEnvironments();
+ }, 1500);
+ } else {
+ setLoginInfo("");
+ setModalOpen(false);
+ setTimeout(() => {
+ getEnvironments();
+ }, 1500);
+ }
+ })
+ )
+ .catch((error) => {
+ console.log("Error in backend data: ", error);
+ });
+ };
+
+ const flushQueue = (name) => {
+ // Just use this one?
+ const url = globalUrl + "/api/v1/flush_queue";
+ fetch(url, {
+ method: "DELETE",
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ })
+ .then((response) =>
+ response.json().then((responseJson) => {
+ if (responseJson["success"] === false) {
+ alert.error(responseJson.reason);
+ getEnvironments();
+ } else {
+ setLoginInfo("");
+ setModalOpen(false);
+ getEnvironments();
+ }
+ })
+ )
+ .catch((error) => {
+ console.log("Error when deleting: ", error);
+ });
+ };
+
+ const abortEnvironmentWorkflows = (environment) => {
+ //console.log("Aborting all workflows started >10 minutes ago, not finished");
+
+ fetch(`${globalUrl}/api/v1/environments/${environment.id}/stop?deleteall=true`, {
+ method: "GET",
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for apps :O!");
+ alert.error("Failed aborting dangling workflows")
+ return;
+ } else {
+ alert.info("Aborted all dangling workflows")
}
- return response.json()
- })
- .then((responseJson) => {
- if (!responseJson.success && responseJson.reason !== undefined) {
- alert.error("Failed setting user: " + responseJson.reason)
- } else {
- alert.success("Set the user field " + field + " to " + value)
- setSelectedUserModalOpen(false)
+ return response.json();
+ })
+ .then((responseJson) => {
+ console.log("Got response for execution: ", responseJson);
+ //console.log("RESPONSE: ", responseJson)
+ //setFiles(responseJson)
+ })
+ .catch((error) => {
+ //alert.error(error.toString())
+ });
+ };
+
+ const deleteEnvironment = (environment) => {
+ // FIXME - add some check here ROFL
+ //const name = environment.name
+
+ //alert.info("Modifying environment " + name)
+ //var newEnv = []
+ //for (var key in environments) {
+ // if (environments[key].Name == name) {
+ // if (environments[key].default) {
+ // alert.error("Can't modify the default environment")
+ // return
+ // }
+
+ // if (environments[key].type === "cloud" && !environments[key].archived) {
+ // alert.error("Can't modify cloud environments")
+ // return
+ // }
+
+ // environments[key].archived = !environments[key].archived
+ // }
+
+ // newEnv.push(environments[key])
+ //}
+ const id = environment.id;
+
+ //alert.info("Modifying environment " + environment.Name)
+ var newEnv = [];
+ for (var key in environments) {
+ if (environments[key].id == id) {
+ if (environments[key].default) {
+ alert.error("Can't modify the default environment");
+ return;
+ }
+
+ if (environments[key].type === "cloud" && !environments[key].archived) {
+ alert.error("Can't modify cloud environments");
+ return;
+ }
+
+ environments[key].archived = !environments[key].archived;
+ }
+
+ newEnv.push(environments[key]);
+ }
+
+ // Just use this one?
+ const url = globalUrl + "/api/v1/setenvironments";
+ fetch(url, {
+ method: "PUT",
+ credentials: "include",
+ body: JSON.stringify(newEnv),
+ headers: {
+ "Content-Type": "application/json",
+ },
+ })
+ .then((response) =>
+ response.json().then((responseJson) => {
+ if (responseJson["success"] === false) {
+ alert.error(responseJson.reason);
+ getEnvironments();
+ } else {
+ setLoginInfo("");
+ setModalOpen(false);
+ getEnvironments();
+ }
+ })
+ )
+ .catch((error) => {
+ console.log("Error when deleting: ", error);
+ });
+ };
+
+ const submitEnvironment = (data) => {
+ // FIXME - add some check here ROFL
+ environments.push({
+ name: data.environment,
+ type: "onprem",
+ });
+
+ // Just use this one?
+ var baseurl = globalUrl;
+ const url = baseurl + "/api/v1/setenvironments";
+ fetch(url, {
+ method: "PUT",
+ credentials: "include",
+ body: JSON.stringify(environments),
+ headers: {
+ "Content-Type": "application/json",
+ },
+ })
+ .then((response) =>
+ response.json().then((responseJson) => {
+ if (responseJson["success"] === false) {
+ setLoginInfo("Error in input: " + responseJson.reason);
+ getEnvironments();
+ } else {
+ setLoginInfo("");
+ setModalOpen(false);
+ getEnvironments();
+ }
+ })
+ )
+ .catch((error) => {
+ console.log("Error in userdata: ", error);
+ });
+ };
+
+ const handleFileUpload = (file_id, file) => {
+ //console.log("FILE: ", file_id, file)
+ fetch(`${globalUrl}/api/v1/files/${file_id}/upload`, {
+ method: "POST",
+ credentials: "include",
+ body: file,
+ })
+ .then((response) => {
+ if (response.status !== 200 && response.status !== 201) {
+ console.log("Status not 200 for apps :O!");
+ alert.error("File was created, but failed to upload.")
+ return;
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ //console.log("RESPONSE: ", responseJson)
+ //setFiles(responseJson)
+ })
+ .catch((error) => {
+ alert.error(error.toString())
+ });
+ };
+
+ const handleCreateFile = (filename, file) => {
+ var data = {
+ filename: filename,
+ org_id: selectedOrganization.id,
+ workflow_id: "global",
+ };
+
+ if (selectedNamespace !== undefined && selectedNamespace !== null && selectedNamespace.length > 0 && selectedNamespace !== "default") {
+ data.namespace = selectedNamespace
+ }
+
+ fetch(globalUrl + "/api/v1/files/create", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ body: JSON.stringify(data),
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for apps :O!");
+ return;
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ //console.log("RESP: ", responseJson)
+ if (responseJson.success === true) {
+ handleFileUpload(responseJson.id, file);
+ } else {
+ alert.error("Failed to upload file ", filename);
+ }
+ })
+ .catch((error) => {
+ alert.error("Failed to upload file ", filename)
+ console.log(error.toString());
+ });
+ };
+
+ const getFiles = () => {
+ fetch(globalUrl + "/api/v1/files", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for apps :O!");
+ return;
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (responseJson.files !== undefined && responseJson.files !== null) {
+ setFiles(responseJson.files);
+ } else {
+ setFiles([]);
+ }
+
+ console.log("NAMESPACES: ", responseJson.namespaces);
+ if (
+ responseJson.namespaces !== undefined &&
+ responseJson.namespaces !== null
+ ) {
+ setFileNamespaces(responseJson.namespaces);
+ }
+ })
+ .catch((error) => {
+ alert.error(error.toString());
+ });
+ };
+
+ const deleteFile = (file) => {
+ fetch(globalUrl + "/api/v1/files/" + file.id, {
+ method: "DELETE",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for file delete :O!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (responseJson.success) {
+ alert.info("Successfully deleted file "+file.name)
+
+ } else if (responseJson.reason !== undefined && responseJson.reason !== null) {
+ alert.error("Failed to delete file: " + responseJson.reason)
+
}
- })
- .catch(error => {
- console.log(error)
- });
- }
+ setTimeout(() => {
+ getFiles();
+ }, 1500);
+ console.log(responseJson)
+ })
+ .catch((error) => {
+ alert.error(error.toString());
+ });
+ };
+ const downloadFile = (file) => {
+ fetch(globalUrl + "/api/v1/files/" + file.id + "/content", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for apps :O!");
+ return "";
+ }
- const generateApikey = (user) => {
- const userId = user.id
- const data = { "user_id": userId }
+ return response.text();
+ })
+ .then((respdata) => {
+ if (respdata.length === 0) {
+ alert.error("Failed getting file. Is it deleted?");
+ return;
+ }
- fetch(globalUrl + "/api/v1/generateapikey", {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- },
- body: JSON.stringify(data),
- credentials: "include",
- })
- .then((response) => {
- if (response.status !== 200) {
- console.log("Status not 200 for WORKFLOW EXECUTION :O!")
- } else {
- getUsers()
- }
+ var blob = new Blob([respdata], {
+ type: "application/octet-stream",
+ });
- return response.json()
- })
- .then((responseJson) => {
- console.log("RESP: ", responseJson)
- if (!responseJson.success && responseJson.reason !== undefined) {
- alert.error("Failed getting new: " + responseJson.reason)
- } else {
- alert.success("Got new API key")
- }
- })
- .catch(error => {
- console.log(error)
- });
- }
+ var url = URL.createObjectURL(blob);
+ var link = document.createElement("a");
+ link.setAttribute("href", url);
+ link.setAttribute("download", `${file.filename}`);
+ var event = document.createEvent("MouseEvents");
+ event.initMouseEvent(
+ "click",
+ true,
+ true,
+ window,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ false,
+ false,
+ false,
+ false,
+ 0,
+ null
+ );
+ link.dispatchEvent(event);
- const editAuthenticationModal = selectedAuthenticationModalOpen ?
- { setSelectedAuthenticationModalOpen(false) }}
- PaperProps={{
- style: {
- backgroundColor: theme.palette.surfaceColor,
- color: "white",
- minWidth: "800px",
- minHeight: "320px",
- },
- }}
- >
- Edit authentication for {selectedAuthentication.app.name} ({selectedAuthentication.label})
-
- {selectedAuthentication.fields.map((data, index) => {
- console.log("DATA: ", data, selectedAuthentication)
- return (
-
- {data.key}
- {
- authenticationFields[index].value = e.target.value
- setAuthenticationFields(authenticationFields)
- }}
- />
-
- )
- })}
-
-
- setSelectedAuthenticationModalOpen(false)} color="primary">
- Cancel
-
- {
- var error = false
- for (var key in authenticationFields) {
- const item = authenticationFields[key]
- if (item.value.length === 0) {
- console.log("ITEM: ", item)
- //var currentnode = cy.getElementById(data.id)
- var textfield = document.getElementById(`authentication-${key}`)
- if (textfield !== null && textfield !== undefined) {
- console.log("HANDLE ERROR FOR KEY ", key)
- }
- error = true
- }
+ //return response.json()
+ })
+ .then((responseJson) => {
+ //console.log(responseJson)
+ //setSchedules(responseJson)
+ })
+ .catch((error) => {
+ alert.error(error.toString());
+ });
+ };
+
+ const getSchedules = () => {
+ fetch(globalUrl + "/api/v1/workflows/schedules", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for apps :O!");
+ return;
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ setSchedules(responseJson);
+ })
+ .catch((error) => {
+ alert.error(error.toString());
+ });
+ };
+
+ const getAppAuthentication = () => {
+ fetch(globalUrl + "/api/v1/apps/authentication", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for apps :O!");
+ return;
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (responseJson.success) {
+ //console.log(responseJson.data)
+ //console.log(responseJson)
+ setAuthentication(responseJson.data);
+ } else {
+ alert.error("Failed getting authentications");
+ }
+ })
+ .catch((error) => {
+ alert.error(error.toString());
+ });
+ };
+
+ const getEnvironments = () => {
+ fetch(globalUrl + "/api/v1/getenvironments", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for apps :O!");
+ return;
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ setEnvironments(responseJson);
+ })
+ .catch((error) => {
+ alert.error(error.toString());
+ });
+ };
+
+ const getOrgs = () => {
+ // API no longer in use, as it's in handleInfo request
+ return;
+
+ fetch(globalUrl + "/api/v1/orgs", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for apps :O!");
+ return;
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ setOrganizations(responseJson);
+ })
+ .catch((error) => {
+ alert.error(error.toString());
+ });
+ };
+
+ const getUsers = () => {
+ fetch(globalUrl + "/api/v1/getusers", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ // Ahh, this happens because they're not admin
+ // window.location.pathname = "/workflows"
+ return;
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ setUsers(responseJson);
+ })
+ .catch((error) => {
+ alert.error(error.toString());
+ });
+ };
+
+ const getSettings = () => {
+ fetch(globalUrl + "/api/v1/getsettings", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 when getting settings :O!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ setUserSettings(responseJson);
+ })
+ .catch((error) => {
+ console.log(error);
+ });
+ };
+
+ const views = {
+ 0: "organization",
+ 1: "users",
+ 2: "app_auth",
+ 3: "files",
+ 4: "schedules",
+ 5: "environments",
+ 6: "suborgs",
+ };
+ const setConfig = (event, inputValue) => {
+ const newValue = parseInt(inputValue)
+
+ setCurTab(newValue)
+ if (newValue === 1) {
+ document.title = "Shuffle - admin - users";
+ getUsers();
+ } else if (newValue === 2) {
+ document.title = "Shuffle - admin - app authentication";
+ getAppAuthentication();
+ } else if (newValue === 3) {
+ document.title = "Shuffle - admin - files";
+ getFiles();
+ } else if (newValue === 4) {
+ document.title = "Shuffle - admin - schedules";
+ getSchedules();
+ } else if (newValue === 5) {
+ document.title = "Shuffle - admin - environments";
+ getEnvironments();
+ } else if (newValue === 6) {
+ document.title = "Shuffle - admin - orgs";
+ getOrgs();
+ } else {
+ document.title = "Shuffle - admin";
+ }
+
+ console.log("NEWVALUE: ", newValue)
+
+ if (newValue === 6) {
+ console.log("Should get apps for categories.");
+ }
+
+ console.log("PROPS: ", props)
+
+ navigate(`/admin?tab=${views[newValue]}`)
+
+ setModalUser({});
+ };
+
+ if (firstRequest) {
+ setFirstRequest(false);
+ document.title = "Shuffle - admin";
+ if (!isCloud) {
+ getUsers();
+ } else {
+ getSettings();
+ }
+
+ if (serverside !== true && window.location.search !== undefined && window.location.search !== null) {
+ const urlSearchParams = new URLSearchParams(window.location.search)
+ const params = Object.fromEntries(urlSearchParams.entries())
+ const foundTab = params["tab"]
+ if (foundTab !== null && foundTab !== undefined) {
+ for (var key in Object.keys(views)) {
+ const value = views[key]
+ console.log(key, value)
+ if (value === foundTab) {
+ setConfig("", key)
+ break
}
-
- if (error) {
- alert.error("All fields must have a new value")
- } else {
- alert.success("Saving new version of this authentication")
- selectedAuthentication.fields = authenticationFields
- saveAuthentication(selectedAuthentication)
- setSelectedAuthentication({})
- setSelectedAuthenticationModalOpen(false)
- }
- }} color="primary">
- Submit
-
-
-
- : null
-
- const editUserModal =
- { setSelectedUserModalOpen(false) }}
- PaperProps={{
- style: {
- backgroundColor: theme.palette.surfaceColor,
- color: "white",
- minWidth: "800px",
- minHeight: "320px",
- },
- }}
- >
- Editing {selectedUser.username}
-
- {isCloud ?
- null
- :
-
- {
- setNewUsername(e.target.value)
- }}
- />
- {
- setUser(selectedUser.id, "username", newUsername)
- }}
- >
- Submit
-
-
}
- {isCloud ?
- null
- :
-
- setNewPassword(e.target.value)}
- />
- onPasswordChange()}
- >
- Submit
-
-
- }
-
- deleteUser(selectedUser)}
- >
- {selectedUser.active ? "Deactivate" : "Activate"}
-
- generateApikey(selectedUser)}
- >
- Get new API key
-
-
-
-
- const GridItem = (props) => {
- const [expanded, setExpanded] = React.useState(false)
-
- const primary = props.data.primary
- const secondary = props.data.secondary
- const primaryIcon = props.data.icon
- const secondaryIcon = props.data.active ?
-
- :
-
-
- return (
- {
- setExpanded(!expanded)
- }}>
-
-
-
-
- {primaryIcon}
-
-
-
- {secondaryIcon}
-
- {expanded ?
-
-
- Usage: {props.data.limit === 0 ? "Infinite" : {props.data.usage} / {props.data.limit} }
-
-
- Data sharing: {props.data.data_collection}
-
-
- Description: {secondary}
-
-
- : null}
-
-
- )
- }
-
- const itemColor = "black"
- var syncList = [
- {
- "primary": "Workflows",
- "secondary": "",
- "active": true,
- "icon": ,
- },
- {
- "primary": "Apps",
- "secondary": "",
- "active": true,
- "icon": ,
- },
- {
- "primary": "Organization",
- "secondary": "",
- "active": true,
- "icon": ,
- },
- ]
-
- const cloudSyncModal =
- { setCloudSyncModalOpen(false) }}
- PaperProps={{
- style: {
- backgroundColor: theme.palette.surfaceColor,
- color: "white",
- minWidth: "800px",
- minHeight: "320px",
- },
- }}
- >
-
- Enable cloud features
-
-
- What does cloud sync do?
-
- {
- setCloudSyncApikey(event.target.value)
- }}
- />
- {
- setLoading(true)
- enableCloudSync(
- cloudSyncApikey,
- selectedOrganization,
- selectedOrganization.cloud_sync,
- )
- }} color="primary">
- {selectedOrganization.cloud_sync ?
- "Stop sync"
- :
- "Start sync"
- }
-
-
- {orgSyncResponse.length > 0 ?
-
- Error: {orgSyncResponse}
-
- : null
- }
-
-
- {syncList.map((data, index) => {
- return (
-
- )
- })}
-
-
- * New triggers (userinput, hotmail realtime)
- * Execute in the cloud rather than onprem
- * Apps can be built in the cloud
- * Easily share apps and workflows
- * Access to powerful cloud search
-
-
-
- const cancelSubscriptions = (subscription_id) => {
- console.log(selectedOrganization)
- const orgId = selectedOrganization.id
- const data = {
- "subscription_id": subscription_id,
- "action": "cancel",
- "org_id": selectedOrganization.id,
- }
-
- const url = globalUrl + `/api/v1/orgs/${orgId}`;
- 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(function(response) {
- if (response.status !== 200) {
- console.log("Error in response")
}
-
- handleGetOrg(selectedOrganization.id)
- return response.json();
- }).then(function(responseJson) {
- if (responseJson.success !== undefined && responseJson.success) {
- alert.success("Successfully stopped subscription!")
-
-
- } else {
- alert.error("Failed stopping subscription. Please contact us.")
- }
- })
- .catch(function(error) {
- console.log("Error: ", error)
- alert.error("Failed stopping subscription. Please contact us.")
- })
+ }
}
- const organizationView = curTab === 0 && selectedOrganization.id !== undefined ?
-
-
-
Organization overview
-
- On this page you can configure individual parts of your organization. Learn more
-
-
- {selectedOrganization.id === undefined ?
-
-
-
- Loading Organization
-
-
- :
-
-
- {
- const elementName = "copy_element_shuffle"
- const org_id = selectedOrganization.id
- var copyText = document.getElementById(elementName);
- if (copyText !== null && copyText !== undefined) {
- const clipboard = navigator.clipboard
- if (clipboard === undefined) {
- alert.error("Can only copy over HTTPS (port 3443)")
- return
- }
+ if (
+ selectedOrganization.id === undefined &&
+ userdata !== undefined &&
+ userdata.active_org !== undefined &&
+ orgRequest
+ ) {
+ setOrgRequest(false);
+ handleGetOrg(userdata.active_org.id);
+ }
- navigator.clipboard.writeText(org_id)
- copyText.select();
- copyText.setSelectionRange(0, 99999); /* For mobile devices */
+ const paperStyle = {
+ maxWidth: 1250,
+ margin: "auto",
+ color: "white",
+ backgroundColor: theme.palette.surfaceColor,
+ marginBottom: 10,
+ padding: 20,
+ };
- /* Copy the text inside the text field */
- document.execCommand("copy");
+ const changeModalData = (field, value) => {
+ modalUser[field] = value;
+ };
- alert.info(org_id + " copied to clipboard")
- }
- }}>
-
-
-
- {selectedOrganization.name.length > 0 ?
-
- :
-
-
-
- Loading Organization
-
-
+ const setUser = (userId, field, value) => {
+ const data = { user_id: userId };
+ data[field] = value;
+
+ fetch(globalUrl + "/api/v1/users/updateuser", {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ body: JSON.stringify(data),
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for WORKFLOW EXECUTION :O!");
+ } else {
+ getUsers();
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (!responseJson.success && responseJson.reason !== undefined) {
+ alert.error("Failed setting user: " + responseJson.reason);
+ } else {
+ alert.success("Set the user field " + field + " to " + value);
+
+ if (field !== "suborgs") {
+ setSelectedUserModalOpen(false);
}
-
- Cloud syncronization
- What does cloud sync do? Cloud syncronization is a way of getting more out of Shuffle. Shuffle will ALWAYS make every option open source, but features relying on other users can't be done without a collaborative approach.
+ }
+ })
+ .catch((error) => {
+ console.log(error);
+ });
+ };
- {isCloud ?
-
-
-
- Currently syncronizing: {selectedOrganization.cloud_sync_active === true ? "True" : "False"}
-
- {selectedOrganization.cloud_sync_active ?
-
- Syncronization interval: {selectedOrganization.sync_config.interval === 0 ? "60" : selectedOrganization.sync_config.interval}
-
- :
- null
- }
-
- Your Apikey
-
-
-
- {selectedOrganization.cloud_sync_active ?
- {
- handleStopOrgSync(selectedOrganization.id)
- }}
- >
- Stop Sync
-
- : null}
-
-
-
- :
-
-
- {
- setCloudSyncApikey(event.target.value)
- }}
- />
- {
- setLoading(true)
- enableCloudSync(
- cloudSyncApikey,
- selectedOrganization,
- selectedOrganization.cloud_sync,
- )
- }} color="primary">
- {selectedOrganization.cloud_sync ?
- "Stop sync"
- :
- "Start sync"
- }
-
-
- {orgSyncResponse.length > 0 ?
-
- Message from Shuffle Cloud: {orgSyncResponse}
-
- : null
- }
-
- }
- Cloud sync features
-
- {selectedOrganization.sync_features === undefined || selectedOrganization.sync_features === null ? null : Object.keys(selectedOrganization.sync_features).map(function(key, index) {
- if (key === "schedule") {
- return null
- }
+ const generateApikey = (user) => {
+ const userId = user.id;
+ const data = { user_id: userId };
- const item = selectedOrganization.sync_features[key]
- const newkey = key.replaceAll("_", " ")
- const griditem = {
- "primary": newkey,
- "secondary": item.description === undefined || item.description === null || item.description.length === 0 ? "Not defined yet" : item.description,
- "limit": item.limit,
- "usage": 0,
- "data_collection": "None",
- "active": item.active,
- "icon": ,
- }
+ fetch(globalUrl + "/api/v1/generateapikey", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ body: JSON.stringify(data),
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for WORKFLOW EXECUTION :O!");
+ } else {
+ getUsers();
+ }
- return (
-
-
-
- )
- })}
-
-
- {isCloud && selectedOrganization.subscriptions !== undefined && selectedOrganization.subscriptions !== null && selectedOrganization.subscriptions.length > 0 ?
-
-
- Your subscription{selectedOrganization.subscriptions.length > 1 ? "s" : ""}
-
-
- {selectedOrganization.subscriptions.reverse().map((sub, index) => {
- return (
-
-
- Type : {sub.level}
- Recurrence : {sub.recurrence}
- {sub.active ?
-
-
Started : {new Date(sub.startdate*1000).toISOString()}
-
{
- cancelSubscriptions(sub.reference)
- }}>
- Cancel subscription
-
-
- :
-
-
Cancelled : {new Date(sub.cancellationdate*1000).toISOString()}
-
- Status : Deactivated
-
-
- }
-
-
- )
- })}
-
-
-
- : null
- }
-
+ return response.json();
+ })
+ .then((responseJson) => {
+ console.log("RESP: ", responseJson);
+ if (!responseJson.success && responseJson.reason !== undefined) {
+ alert.error("Failed getting new: " + responseJson.reason);
+ } else {
+ alert.success("Got new API key");
+ }
+ })
+ .catch((error) => {
+ console.log(error);
+ });
+ };
+
+ const editAuthenticationModal = selectedAuthenticationModalOpen ? (
+
{
+ setSelectedAuthenticationModalOpen(false);
+ }}
+ PaperProps={{
+ style: {
+ backgroundColor: theme.palette.surfaceColor,
+ color: "white",
+ minWidth: "800px",
+ minHeight: "320px",
+ },
+ }}
+ >
+
+
+ Edit authentication for {selectedAuthentication.app.name} (
+ {selectedAuthentication.label})
+
+
+
+ {selectedAuthentication.fields.map((data, index) => {
+ //console.log("DATA: ", data, selectedAuthentication)
+ return (
+
+
+ {data.key}
+
+ {
+ authenticationFields[index].value = e.target.value;
+ setAuthenticationFields(authenticationFields);
+ }}
+ />
+
+ );
+ })}
+
+
+ setSelectedAuthenticationModalOpen(false)}
+ color="primary"
+ >
+ Cancel
+
+ {
+ var error = false;
+ for (var key in authenticationFields) {
+ const item = authenticationFields[key];
+ if (item.value.length === 0) {
+ console.log("ITEM: ", item);
+ //var currentnode = cy.getElementById(data.id)
+ var textfield = document.getElementById(
+ `authentication-${key}`
+ );
+ if (textfield !== null && textfield !== undefined) {
+ console.log("HANDLE ERROR FOR KEY ", key);
+ }
+ error = true;
+ }
+ }
+
+ if (error) {
+ alert.error("All fields must have a new value");
+ } else {
+ alert.success("Saving new version of this authentication");
+ selectedAuthentication.fields = authenticationFields;
+ saveAuthentication(selectedAuthentication);
+ setSelectedAuthentication({});
+ setSelectedAuthenticationModalOpen(false);
+ }
+ }}
+ color="primary"
+ >
+ Submit
+
+
+
+ ) : null;
+
+ const handleOrgEditChange = (event) => {
+ if (userdata.id === selectedUser.id) {
+ alert.info("Can't remove orgs from yourself")
+ return
+ }
+
+ console.log("event: ", event.target.value)
+ setMatchingOrganizations(event.target.value)
+ // Workaround for empty orgs
+ if (event.target.value.length === 0) {
+ event.target.value.push("REMOVE")
+ }
+
+ setUser(selectedUser.id, "suborgs", event.target.value)
+ //setUser(selectedUser.id, "suborgs", matchingOrganizations)
+ }
+
+ const userOrgEdit = selectedUser.id !== undefined && selectedUser.orgs !== undefined && selectedUser.orgs !== null && selectedOrganization.child_orgs !== undefined && selectedOrganization.child_orgs !== null && selectedOrganization.child_orgs.length > 0 ?
+
+ Accessible Sub-Organizations ({selectedUser.orgs? selectedUser.orgs.length-1 : 0})
+
}
-
-
-
-
-
+ renderValue={(selected) => {
+ return selected.join(', ')
+ }}
+ MenuProps={MenuProps}
+ >
+ {selectedOrganization.child_orgs.map((org, index) => (
+
+ -1} />
+
+
+ ))}
+
+
: null
- const modalView =
- { setModalOpen(false) }}
- PaperProps={{
- style: {
- backgroundColor: theme.palette.surfaceColor,
- color: "white",
- minWidth: "800px",
- minHeight: "320px",
- },
- }}
- >
-
- {curTab === 1 ? "Add user" : curTab === 6 ? "Add Sub-Organization" : "Add environment"}
-
-
- {curTab === 1 && isCloud ?
-
- We'll send an email to invite them to your organization.
-
- :
- curTab === 6 ?
-
- The organization created will become a child of your current organization, and be available to you.
-
- :
- null }
- {curTab === 1 ?
-
- Username
- changeModalData("Username", event.target.value)}
- />
- {isCloud ? null :
-
- Password
- changeModalData("Password", event.target.value)}
- />
-
- }
-
- : curTab === 6 ?
-
- Name
- {
- setOrgName(event.target.value)
- }}
- />
-
- : curTab === 5 ?
-
- Environment Name
- changeModalData("environment", event.target.value)}
- />
-
- : null}
- {loginInfo}
-
-
- setModalOpen(false)} color="primary">
- Cancel
-
- {
- if (curTab === 1) {
- if (isCloud) {
- inviteUser(modalUser)
- } else {
- submitUser(modalUser)
- }
- } else if (curTab === 6) {
- createSubOrg(selectedOrganization.id, orgName)
- } else if (curTab === 5) {
- submitEnvironment(modalUser)
- }
- }} color="primary">
- Submit
-
-
-
+ const editUserModal = (
+ {
+ setSelectedUserModalOpen(false);
+ setImage2FA("");
+ setSecret2FA("");
+ }}
+ PaperProps={{
+ style: {
+ backgroundColor: theme.palette.surfaceColor,
+ color: "white",
+ minWidth: "800px",
+ minHeight: "320px",
+ },
+ }}
+ >
+
+
+ Editing {selectedUser.username}
+
+
+
+ {isCloud ? null : (
+
+ {
+ setNewUsername(e.target.value);
+ }}
+ />
+ {
+ setUser(selectedUser.id, "username", newUsername);
+ }}
+ >
+ Submit
+
+
+ )}
- const usersView = curTab === 1 ?
-
-
-
User management
-
Add, edit, block or change passwords. Learn more
-
-
-
setModalOpen(true)}
- >
- Add user
-
-
getUsers()}
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {users === undefined || users === null ? null : users.map((data, index) => {
- var bgColor = "#27292d"
- if (index % 2 === 0) {
- bgColor = "#1f2023"
- }
+ {isCloud ? null : (
+
+ setNewPassword(e.target.value)}
+ />
+ onPasswordChange()}
+ >
+ Submit
+
+
+ )}
- return (
-
-
-
-
- {
- const elementName = "copy_element_shuffle"
- var copyText = document.getElementById(elementName);
- if (copyText !== null && copyText !== undefined) {
- const clipboard = navigator.clipboard
- if (clipboard === undefined) {
- alert.error("Can only copy over HTTPS (port 3443)")
- return
- }
-
- navigator.clipboard.writeText(data.apikey)
- copyText.select();
- copyText.setSelectionRange(0, 99999); /* For mobile devices */
-
- /* Copy the text inside the text field */
- document.execCommand("copy");
-
- alert.info("Apikey copied to clipboard")
- }
- }}>
-
-
-
- }/>
-
- {
- console.log("VALUE: ", e.target.value)
- setUser(data.id, "role", e.target.value)
- }}
- style={{ backgroundColor: theme.palette.surfaceColor, color: "white", height: "50px" }}
- >
-
- Admin
-
-
- User
-
-
- }
- style ={{ minWidth: 135, maxWidth: 135, marginRight: 15,}}
- />
-
-
-
- {
- setSelectedUserModalOpen(true)
- setSelectedUser(data)
+ {userOrgEdit}
+
+
+ {
+ deleteUser(selectedUser)
+ setSelectedUserModalOpen(false);
}}
- >
-
-
-
+ {selectedUser.active ? "Delete from org" : "Delete from org"}
+
+ generateApikey(selectedUser)}
+ >
+ Renew API-key
+
+ {
+ run2FASetup(userdata);
+ }}
+ disabled={
+ (selectedUser.role === "admin" && selectedUser.username !== userdata.username)
+ }
+ variant="outlined"
+ color="primary"
+ >
+ { selectedUser.mfa_info !== undefined &&
+ selectedUser.mfa_info !== null &&
+ selectedUser.mfa_info.active === true
+ ? "Disable 2FA"
+ : "Enable 2FA"}
+
+
+ {show2faSetup ? (
+
+ {/*
*/}
+
+ {secret2FA !== undefined &&
+ secret2FA !== null &&
+ secret2FA.length > 0 ? (
+
+
+ Scan the image below with the two-factor authentication app on
+ your phone. If you can’t use a QR code, use the code{" "}
+ {secret2FA} instead.
+
+
+ ) : null}
+ {image2FA !== undefined &&
+ image2FA !== null &&
+ image2FA.length > 0 ? (
+
+ ) : (
+
+ )}
+
+
+ After scanning the QR code image, the app will display a code that
+ you can enter below.
+
+
+ {
+ if (event.target.value.length > 6) {
+ return;
+ }
+
+ setValue2FA(event.target.value);
+ }}
+ />
+ {
+ handleVerify2FA(userdata.id, value2FA);
+ }}
+ color="primary"
+ >
+ Submit
+
+
+
+ ) : null}
+
+
+ );
+
+ const GridItem = (props) => {
+ const [expanded, setExpanded] = React.useState(false);
+
+ const primary = props.data.primary;
+ const secondary = props.data.secondary;
+ const primaryIcon = props.data.icon;
+ const secondaryIcon = props.data.active ? (
+
+ ) : (
+
+ );
+
+ return (
+ {
+ setExpanded(!expanded);
+ }}
+ >
+
+
+
+ {primaryIcon}
+
+
+ {secondaryIcon}
+
+ {expanded ? (
+
+
+ Usage:{" "}
+ {props.data.limit === 0 ? (
+ "Infinite"
+ ) : (
+
+ {props.data.usage} / {props.data.limit}
+
+ )}
+
+
+ Data sharing: {props.data.data_collection}
+
+ Description: {secondary}
+
+ ) : null}
+
+
+ );
+ };
+
+ const itemColor = "black";
+ var syncList = [
+ {
+ primary: "Workflows",
+ secondary: "",
+ active: true,
+ icon: ,
+ },
+ {
+ primary: "Apps",
+ secondary: "",
+ active: true,
+ icon: ,
+ },
+ {
+ primary: "Organization",
+ secondary: "",
+ active: true,
+ icon: ,
+ },
+ ];
+
+ const cloudSyncModal = (
+ {
+ setCloudSyncModalOpen(false);
+ }}
+ PaperProps={{
+ style: {
+ backgroundColor: theme.palette.surfaceColor,
+ color: "white",
+ minWidth: "800px",
+ minHeight: "320px",
+ },
+ }}
+ >
+
+ Enable cloud features
+
+
+ What does{" "}
+
+ cloud sync
+ {" "}
+ do?
+
+ {
+ setCloudSyncApikey(event.target.value);
+ }}
+ />
+ {
+ setLoading(true);
+ enableCloudSync(
+ cloudSyncApikey,
+ selectedOrganization,
+ selectedOrganization.cloud_sync
+ );
+ }}
+ color="primary"
+ variant={
+ selectedOrganization.cloud_sync === true
+ ? "outlined"
+ : "contained"
+ }
+ >
+ {selectedOrganization.cloud_sync ? "Stop sync" : "Start sync"}
+
+
+ {orgSyncResponse.length > 0 ? (
+
+ Error: {orgSyncResponse}
+
+ ) : null}
+
+ {syncList.map((data, index) => {
+ return ;
+ })}
+
+ * New triggers (userinput, hotmail realtime)
+
+ * Execute in the cloud rather than onprem
+
+ * Apps can be built in the cloud
+
+ * Easily share apps and workflows
+
* Access to powerful cloud search
+
+
+ );
+
+ const cancelSubscriptions = (subscription_id) => {
+ console.log(selectedOrganization);
+ const orgId = selectedOrganization.id;
+ const data = {
+ subscription_id: subscription_id,
+ action: "cancel",
+ org_id: selectedOrganization.id,
+ };
+
+ const url = globalUrl + `/api/v1/orgs/${orgId}`;
+ 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(function (response) {
+ if (response.status !== 200) {
+ console.log("Error in response");
+ }
+
+ handleGetOrg(selectedOrganization.id);
+ return response.json();
+ })
+ .then(function (responseJson) {
+ if (responseJson.success !== undefined && responseJson.success) {
+ alert.success("Successfully stopped subscription!");
+ } else {
+ alert.error("Failed stopping subscription. Please contact us.");
+ }
+ })
+ .catch(function (error) {
+ console.log("Error: ", error);
+ alert.error("Failed stopping subscription. Please contact us.");
+ });
+ };
+
+ const organizationView =
+ curTab === 0 && selectedOrganization.id !== undefined ? (
+
+
+
Organization overview
+
+ On this page you can configure individual parts of your
+ organization.{" "}
+
+ Learn more
+
+
+
+ {selectedOrganization.id === undefined ? (
+
+
+ Loading Organization
+
+ ) : (
+
+
+ {
+ const elementName = "copy_element_shuffle";
+ const org_id = selectedOrganization.id;
+ var copyText = document.getElementById(elementName);
+ if (copyText !== null && copyText !== undefined) {
+ const clipboard = navigator.clipboard;
+ if (clipboard === undefined) {
+ alert.error("Can only copy over HTTPS (port 3443)");
+ return;
+ }
+
+ navigator.clipboard.writeText(org_id);
+ copyText.select();
+ copyText.setSelectionRange(
+ 0,
+ 99999
+ ); /* For mobile devices */
+
+ /* Copy the text inside the text field */
+ document.execCommand("copy");
+
+ alert.info(org_id + " copied to clipboard");
+ }
+ }}
+ >
+
+
+
+ {selectedOrganization.name.length > 0 ? (
+
+ ) : (
+
+
+ Loading Organization
+
+ )}
+
+
+ Cloud syncronization
+
+ What does{" "}
+
+ cloud sync
+ {" "}
+ do? Cloud syncronization is a way of getting more out of Shuffle.
+ Shuffle will
ALWAYS make every option open source, but
+ features relying on other users can't be done without a
+ collaborative approach.
+ {isCloud ? (
+
+
+
+ Currently syncronizing:{" "}
+ {selectedOrganization.cloud_sync_active === true
+ ? "True"
+ : "False"}
+
+ {selectedOrganization.cloud_sync_active ? (
+
+ Syncronization interval:{" "}
+ {selectedOrganization.sync_config.interval === 0
+ ? "60"
+ : selectedOrganization.sync_config.interval}
+
+ ) : null}
+
+ Your Apikey
+
+
+
+ {selectedOrganization.cloud_sync_active ? (
+ {
+ handleStopOrgSync(selectedOrganization.id);
+ }}
+ >
+ Stop Sync
+
+ ) : null}
+
+
+
+ ) : (
+
+
+ {
+ setCloudSyncApikey(event.target.value);
+ }}
+ />
+ {
+ setLoading(true);
+ enableCloudSync(
+ cloudSyncApikey,
+ selectedOrganization,
+ selectedOrganization.cloud_sync
+ );
+ }}
+ color="primary"
+ variant={
+ selectedOrganization.cloud_sync === true
+ ? "outlined"
+ : "contained"
+ }
+ >
+ {selectedOrganization.cloud_sync
+ ? "Stop sync"
+ : "Start sync"}
+
+
+ {orgSyncResponse.length > 0 ? (
+
+ Message from Shuffle Cloud: {orgSyncResponse}
+
+ ) : null}
+
+ )}
+
+ Cloud sync features
+
+
+ {selectedOrganization.sync_features === undefined ||
+ selectedOrganization.sync_features === null
+ ? null
+ : Object.keys(selectedOrganization.sync_features).map(function (
+ key,
+ index
+ ) {
+ if (key === "schedule") {
+ return null;
+ }
+
+ const item = selectedOrganization.sync_features[key];
+ const newkey = key.replaceAll("_", " ");
+ const griditem = {
+ primary: newkey,
+ secondary:
+ item.description === undefined ||
+ item.description === null ||
+ item.description.length === 0
+ ? "Not defined yet"
+ : item.description,
+ limit: item.limit,
+ usage: 0,
+ data_collection: "None",
+ active: item.active,
+ icon: ,
+ };
+
+ return (
+
+
+
+ );
+ })}
+
+
+ {isCloud &&
+ selectedOrganization.subscriptions !== undefined &&
+ selectedOrganization.subscriptions !== null &&
+ selectedOrganization.subscriptions.length > 0 ? (
+
+
+ Your subscription
+ {selectedOrganization.subscriptions.length > 1 ? "s" : ""}
+
+
+ {selectedOrganization.subscriptions
+ .reverse()
+ .map((sub, index) => {
+ return (
+
+
+ Type : {sub.level}
+
+ Recurrence : {sub.recurrence}
+
+ {sub.active ? (
+
+
Started :{" "}
+ {new Date(sub.startdate * 1000).toISOString()}
+
+
{
+ cancelSubscriptions(sub.reference);
+ }}
+ >
+ Cancel subscription
+
+
+ ) : (
+
+
Cancelled :{" "}
+ {new Date(
+ sub.cancellationdate * 1000
+ ).toISOString()}
+
+
+ Status : Deactivated
+
+
+ )}
+
+
+ );
+ })}
+
+
+
+ ) : null}
+
+ )}
+
+
+
+
+
+ ) : null;
+
+ const modalView = (
+ {
+ setModalOpen(false);
+ }}
+ PaperProps={{
+ style: {
+ backgroundColor: theme.palette.surfaceColor,
+ color: "white",
+ minWidth: "800px",
+ minHeight: "320px",
+ },
+ }}
+ >
+
+
+ {curTab === 1
+ ? "Add user"
+ : curTab === 6
+ ? "Add Sub-Organization"
+ : "Add environment"}
+
+
+
+ {curTab === 1 && isCloud ? (
+
+ We'll send an email to invite them to your organization.
+
+ ) : curTab === 6 ? (
+
+ The organization created will become a child of your current
+ organization, and be available to you.
+
+ ) : null}
+ {curTab === 1 ? (
+
+ Username
+
+ changeModalData("Username", event.target.value)
+ }
+ />
+ {isCloud ? null : (
+
+ Password
+
+ changeModalData("Password", event.target.value)
+ }
+ />
+
+ )}
+
+ ) : curTab === 6 ? (
+
+ Name
+ {
+ setOrgName(event.target.value);
+ }}
+ />
+
+ ) : curTab === 5 ? (
+
+ Environment Name
+
+ changeModalData("environment", event.target.value)
+ }
+ />
+
+ ) : null}
+ {loginInfo}
+
+
+ setModalOpen(false)}
+ color="primary"
+ >
+ Cancel
+
+ {
+ if (curTab === 1) {
+ if (isCloud) {
+ inviteUser(modalUser);
+ } else {
+ submitUser(modalUser);
+ }
+ } else if (curTab === 6) {
+ createSubOrg(selectedOrganization.id, orgName);
+ } else if (curTab === 5) {
+ submitEnvironment(modalUser);
+ }
+ }}
+ color="primary"
+ >
+ Submit
+
+
+
+ );
+
+
+
+
+ const usersView =
+ curTab === 1 ? (
+
+
+
User management
+
+ Add, edit, block or change passwords.{" "}
+
+ Learn more
+
+
+
+
+
setModalOpen(true)}
+ >
+ Add user
+
+
getUsers()}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {selectedOrganization.child_orgs !== undefined && selectedOrganization.child_orgs !== null && selectedOrganization.child_orgs.length > 0 ?
+
+ : null}
+
+
+ {users === undefined || users === null
+ ? null
+ : users.map((data, index) => {
+ var bgColor = "#27292d";
+ if (index % 2 === 0) {
+ bgColor = "#1f2023";
+ }
+
+ return (
+
+
+
+
+ {
+ const elementName = "copy_element_shuffle";
+ var copyText =
+ document.getElementById(elementName);
+ if (
+ copyText !== null &&
+ copyText !== undefined
+ ) {
+ const clipboard = navigator.clipboard;
+ if (clipboard === undefined) {
+ alert.error(
+ "Can only copy over HTTPS (port 3443)"
+ );
+ return;
+ }
+
+ navigator.clipboard.writeText(data.apikey);
+ copyText.select();
+ copyText.setSelectionRange(
+ 0,
+ 99999
+ ); /* For mobile devices */
+
+ /* Copy the text inside the text field */
+ document.execCommand("copy");
+
+ alert.info("Apikey copied to clipboard");
+ }
+ }}
+ >
+
+
+
+ )
+ }
+ />
+
+ {
+ console.log("VALUE: ", e.target.value);
+ setUser(data.id, "role", e.target.value);
+ }}
+ style={{
+ backgroundColor: theme.palette.surfaceColor,
+ color: "white",
+ height: "50px",
+ }}
+ >
+
+ Org Admin
+
+
+ Org User
+
+
+ Org Reader
+
+
+ }
+ style={{ minWidth: 135, maxWidth: 135, marginRight: 15 }}
+ />
+
+
+
+ {selectedOrganization.child_orgs !== undefined && selectedOrganization.child_orgs !== null && selectedOrganization.child_orgs.length > 0 ?
+
+ : null}
+
+ {
+ setSelectedUserModalOpen(true);
+ setSelectedUser(data);
+
+ // Find matching orgs between current org and current user's access to those orgs
+ if (userdata.orgs !== undefined && userdata.orgs !== null && userdata.orgs.length > 0 && selectedOrganization.child_orgs !== undefined && selectedOrganization.child_orgs !== null && selectedOrganization.child_orgs.length > 0) {
+ console.log("In here?")
+ var active = []
+ for (var key in userdata.orgs) {
+ console.log("ORG: ", userdata.orgs[key])
+ const found = selectedOrganization.child_orgs.find(item => item.id === userdata.orgs[key].id)
+ if (found !== null && found !== undefined) {
+
+ if (data.orgs === undefined || data.orgs === null) {
+ continue
+ }
+
+ const subfound = data.orgs.find(item => item === found.id)
+ if (subfound !== null && subfound !== undefined) {
+ active.push(subfound)
+ }
+ }
+ }
+
+ setMatchingOrganizations(active)
+ }
+ }}
+ >
+
+
+ {/* {
generateApikey(data)
}}
+ disabled={data.role === "admin" && data.username !== userdata.username}
variant="outlined"
color="primary"
>
New apikey
-
-
-
- )
- })}
-
-
- : null
+ */}
+
+
+ );
+ })}
+
+
+ ) : null;
- const uploadFiles = (files) => {
- for (var key in files) {
- try {
- const filename = files[key].name
- var filedata = new FormData()
- filedata.append('shuffle_file', files[key])
+ const run2FASetup = (data) => {
+ console.log("2fa: ", data, show2faSetup);
+ if (!show2faSetup) {
+ get2faCode(data.id);
+ } else {
+ // Should remove?
+ setImage2FA("");
+ setSecret2FA("");
+ }
- if (typeof(files[key]) === "object") {
- handleCreateFile(filename, filedata)
- }
+ setShow2faSetup(!show2faSetup);
+ //setShow2faSetup(true);
+ };
- /*
+ const uploadFiles = (files) => {
+ for (var key in files) {
+ try {
+ const filename = files[key].name;
+ var filedata = new FormData();
+ filedata.append("shuffle_file", files[key]);
+
+ if (typeof files[key] === "object") {
+ handleCreateFile(filename, filedata);
+ }
+
+ /*
reader.addEventListener('load', (e) => {
var data = e.target.result;
setIsDropzone(false)
@@ -2182,810 +3034,1272 @@ const Admin = (props) => {
})
reader.readAsText(files[key])
*/
- } catch (e) {
- console.log("Error in dropzone: ", e)
- }
- }
+ } catch (e) {
+ console.log("Error in dropzone: ", e);
+ }
+ }
- setTimeout(() => {
- getFiles()
- }, 2500)
- }
+ setTimeout(() => {
+ getFiles();
+ }, 2500);
+ };
+
+ const uploadFile = (e) => {
+ const isDropzone =
+ e.dataTransfer === undefined ? false : e.dataTransfer.files.length > 0;
+ const files = isDropzone ? e.dataTransfer.files : e.target.files;
- const uploadFile = (e) => {
- const isDropzone = e.dataTransfer === undefined ? false : e.dataTransfer.files.length > 0;
- const files = isDropzone ? e.dataTransfer.files : e.target.files;
-
//const reader = new FileReader();
- //alert.info("Starting fileupload")
- uploadFiles(files)
- }
+ //alert.info("Starting fileupload")
+ uploadFiles(files);
+ };
- const filesView = curTab === 3 ?
- 1366 ? 1366 : 1200, margin: "auto", padding: 20 }} onDrop={uploadFile}>
-
-
-
{
- upload.click()
- }}>
-
- Upload files
-
-
upload = ref} onChange={(event) => {
- //const file = event.target.value
- //const fileObject = URL.createObjectURL(actualFile)
- //setFile(fileObject)
- //const files = event.target.files[0]
- uploadFiles(event.target.files)
- }} />
-
getFiles()}
- >
-
-
+ const filesView =
+ curTab === 3 ? (
+
1366 ? 1366 : 1200,
+ margin: "auto",
+ padding: 20,
+ }}
+ onDrop={uploadFile}
+ >
+
+
+
{
+ upload.click();
+ }}
+ >
+ Upload files
+
+
(upload = ref)}
+ onChange={(event) => {
+ //const file = event.target.value
+ //const fileObject = URL.createObjectURL(actualFile)
+ //setFile(fileObject)
+ //const files = event.target.files[0]
+ uploadFiles(event.target.files);
+ }}
+ />
+
getFiles()}
+ >
+
+
- {fileNamespaces !== undefined && fileNamespaces !== null && fileNamespaces.length > 1 ?
-
- Namespace
- {
- console.log("CHANGE NAMESPACE: ", event.target)
- setSelectedNamespace(event.target.value)
- }}
- >
- {fileNamespaces.map((data, index) => {
- return (
- {data}
- )
- })}
-
-
-
- : null}
+ {fileNamespaces !== undefined &&
+ fileNamespaces !== null &&
+ fileNamespaces.length > 1 ? (
+
+ File Category
+ {
+ console.log("CHANGE NAMESPACE: ", event.target);
+ setSelectedNamespace(event.target.value);
+ }}
+ >
+ {fileNamespaces.map((data, index) => {
+ return (
+
+ {data}
+
+ );
+ })}
+
+
+ ) : null}
-
-
-
-
-
-
-
-
-
-
-
-
- {files === undefined || files === null || files.length === 0 ? null : files.map((file, index) => {
- if (file.namespace === "") {
- file.namespace = "default"
- }
+
+
+
+
+
+
+
+
+
+
+
+ {files === undefined || files === null || files.length === 0
+ ? null
+ : files.map((file, index) => {
+ if (file.namespace === "") {
+ file.namespace = "default";
+ }
- if (file.namespace !== selectedNamespace) {
- return null
- }
+ if (file.namespace !== selectedNamespace) {
+ return null;
+ }
- var bgColor = "#27292d"
- if (index % 2 === 0) {
- bgColor = "#1f2023"
- }
+ var bgColor = "#27292d";
+ if (index % 2 === 0) {
+ bgColor = "#1f2023";
+ }
- return (
-
-
-
-
-
-
- :
-
-
-
-
-
-
-
+ return (
+
+
+
+
+
+
+ ) : (
+
+
+
+
+
+
+
+
+
+ )
+ }
+ style={{
+ minWidth: 100,
+ maxWidth: 100,
+ overflow: "hidden",
+ }}
+ />
+
+
+
+
+
+
+ {
+ downloadFile(file);
+ }}
+ >
+
+
+
+
+
+
+ {
+ deleteFile(file);
+ }}
+ >
+
+
+
+
+
+ {
+ const elementName = "copy_element_shuffle";
+ var copyText = document.getElementById(elementName);
+ if (copyText !== null && copyText !== undefined) {
+ const clipboard = navigator.clipboard;
+ if (clipboard === undefined) {
+ alert.error(
+ "Can only copy over HTTPS (port 3443)"
+ );
+ return;
+ }
+
+ navigator.clipboard.writeText(file.id);
+ copyText.select();
+ copyText.setSelectionRange(
+ 0,
+ 99999
+ ); /* For mobile devices */
+
+ /* Copy the text inside the text field */
+ document.execCommand("copy");
+
+ alert.info(file.id + " copied to clipboard");
+ }
+ }}
+ >
+
+
+
-
- }
- style={{minWidth: 100, maxWidth: 100, overflow: "hidden"}}
- />
-
-
-
-
-
- {
- downloadFile(file)
- }}>
-
-
-
-
- style={{minWidth: 75, maxWidth: 75, overflow: "hidden"}}
- />
- {/*
-
- deleteSchedule(file)}
- >
- Stop schedule
-
-
- */}
- {
- const elementName = "copy_element_shuffle"
- var copyText = document.getElementById(elementName);
- if (copyText !== null && copyText !== undefined) {
- const clipboard = navigator.clipboard
- if (clipboard === undefined) {
- alert.error("Can only copy over HTTPS (port 3443)")
- return
- }
+ style={{
+ minWidth: 150,
+ maxWidth: 150,
+ overflow: "hidden",
+ }}
+ />
+
+ );
+ })}
+
+
+
+ ) : null;
- navigator.clipboard.writeText(file.id)
- copyText.select();
- copyText.setSelectionRange(0, 99999); /* For mobile devices */
+ const schedulesView =
+ curTab === 4 ? (
+
+
+
Schedules
+
+ Schedules used in Workflows. Makes locating and control easier.{" "}
+
+ Learn more
+
+
+
+
+
+
+
+
+
+
+
+
+ {schedules === undefined || schedules === null
+ ? null
+ : schedules.map((schedule, index) => {
+ var bgColor = "#27292d";
+ if (index % 2 === 0) {
+ bgColor = "#1f2023";
+ }
- /* Copy the text inside the text field */
- document.execCommand("copy");
+ return (
+
+ {schedule.seconds} seconds
+ )
+ }
+ />
+
+
+ {schedule.workflow_id}
+
+ }
+ />
+
+
+ deleteSchedule(schedule)}
+ >
+ Stop schedule
+
+
+
+ );
+ })}
+
+
+ ) : null;
- alert.info(file.id + " copied to clipboard")
- }
- }}>
-
-
- />
-
- )
- })}
-
-
-
- : null
+ const appCategoryView =
+ curTab === 7 ? (
+
+
+
Categories
+
+ Categories are the categories supported by Shuffle, which are mapped
+ to apps and workflows
+
+
+
+
+
+
+
+
+
+
+
+ {categories.map((data, index) => {
+ if (data.apps.length === 0) {
+ return null;
+ }
- const schedulesView = curTab === 4 ?
-
-
-
Schedules
-
Schedules used in Workflows. Makes locating and control easier. Learn more
-
-
-
-
-
-
-
-
-
-
- {schedules === undefined || schedules === null ? null : schedules.map((schedule, index) => {
- var bgColor = "#27292d"
- if (index % 2 === 0) {
- bgColor = "#1f2023"
- }
+ return (
+
+
+
+
+
+
+ {
+ console.log("Show apps with this category");
+ }}
+ >
+ Find app ({data.apps === null ? 0 : data.apps.length})
+
+
+
+ );
+ })}
+
+
+ ) : null;
- return (
-
- {schedule.seconds} seconds}
- />
-
- {schedule.workflow_id}}
- />
-
-
- deleteSchedule(schedule)}
- >
- Stop schedule
-
-
-
- )
- })}
-
-
- : null
+ const updateAppAuthentication = (field) => {
+ setSelectedAuthenticationModalOpen(true);
+ setSelectedAuthentication(field);
+ //{selectedAuthentication.fields.map((data, index) => {
+ var newfields = [];
+ for (var key in field.fields) {
+ newfields.push({
+ key: field.fields[key].key,
+ value: "",
+ });
+ }
+ setAuthenticationFields(newfields);
+ };
- const appCategoryView = curTab === 7 ?
-
-
-
Categories
-
- Categories are the categories supported by Shuffle, which are mapped to apps and workflows
-
-
-
-
-
-
-
-
-
-
-
- {categories.map((data, index) => {
- if (data.apps.length === 0) {
- return null
- }
+ const authenticationView =
+ curTab === 2 ? (
+
+
+
App Authentication
+
+ Control the authentication options for individual apps.{" "}
+ PS: Actions performed here can be destructive!
+
+
+
+ Learn more about authentication
+
+
+
+
+
+
+
+
+ {/* */}
+
+ {/*
+
+ */}
+
+
+
+
+ {authentication === undefined || authentication === null
+ ? null
+ : authentication.map((data, index) => {
+ var bgColor = "#27292d";
+ if (index % 2 === 0) {
+ bgColor = "#1f2023";
+ }
- return (
-
-
-
-
-
-
- {
- console.log("Show apps with this category")
- }}
- >
- Find app ({data.apps === null ? 0 : data.apps.length})
-
-
-
- )
- })}
-
-
- : null
-
- const updateAppAuthentication = (field) => {
- setSelectedAuthenticationModalOpen(true)
- setSelectedAuthentication(field)
- //{selectedAuthentication.fields.map((data, index) => {
- var newfields = []
- for (var key in field.fields) {
- newfields.push({
- "key": field.fields[key].key,
- "value": "",
- })
- }
- setAuthenticationFields(newfields)
- }
-
- const authenticationView = curTab === 2 ?
-
-
-
App Authentication
-
Control the authentication options for individual apps. Actions can be destructive!
-
Learn more
-
-
-
-
-
-
-
-
-
-
-
-
-
- {authentication === undefined || authentication === null ? null : authentication.map((data, index) => {
- var bgColor = "#27292d"
- if (index % 2 === 0) {
- bgColor = "#1f2023"
- }
-
-
- return (
-
-
- style={{minWidth: 75, maxWidth: 75}}
- />
-
-
-
-
-
- {return data.key}).join(", ")}
- style={{minWidth: 200, maxWidth: 200, overflow: "hidden"}}
- />
-
- {
- updateAppAuthentication(data)
- }}
- >
-
-
- {data.defined ?
-
- {
- editAuthenticationConfig(data.id)
- }}
- >
-
-
-
- :
-
- {
- }}
- >
-
-
-
+ //console.log("Auth data: ", data)
+ if (data.type === "oauth2") {
+ data.fields = [
+ {
+ "key": "url",
+ "value": "Secret. Replaced during app execution!",
+ },
+ {
+ "key": "client_id",
+ "value": "Secret. Replaced during app execution!",
+ },
+ {
+ "key": "client_secret",
+ "value": "Secret. Replaced during app execution!",
+ },
+ {
+ "key": "scope",
+ "value": "Secret. Replaced during app execution!",
+ }]
}
- {
- deleteAuthentication(data)
- }}
- >
-
-
-
-
- )
- })}
-
-
- : null
- const environmentView = curTab === 5 ?
-
-
-
Environments
-
Decides what Orborus environment to execute an action in a workflow in. Learn more
-
-
setModalOpen(true)}
- >
- Add environment
-
-
getEnvironments()}
- >
-
-
-
{setShowArchived(!showArchived)}} /> Show archived
-
-
-
-
-
-
-
-
-
-
- {environments === undefined || environments === null ? null : environments.map((environment, index)=> {
- if (!showArchived && environment.archived) {
- return null
- }
+ return (
+
+
+ style={{ minWidth: 75, maxWidth: 75 }}
+ />
+
+
+ {/*
+
+ */}
+
+ {/*
+
+ */}
+ {
+ return data.key;
+ })
+ .join(", ")
+ }
+ style={{
+ minWidth: 125,
+ maxWidth: 125,
+ overflow: "hidden",
+ }}
+ />
+
+
+ {
+ updateAppAuthentication(data);
+ }}
+ >
+
+
+ {data.defined ? (
+
+ {
+ editAuthenticationConfig(data.id);
+ }}
+ >
+
+
+
+ ) : (
+
+ {}}
+ >
+
+
+
+ )}
+ {
+ deleteAuthentication(data);
+ }}
+ >
+
+
+
+
+ );
+ })}
+
+
+ ) : null;
- if (environment.archived === undefined) {
- getEnvironments()
- return null
- }
+ const environmentView =
+ curTab === 5 ? (
+
+
+
Environments
+
+ Decides what Orborus environment to execute an action in a workflow
+ in.{" "}
+
+ Learn more
+
+
+
+
setModalOpen(true)}
+ >
+ Add environment
+
+
getEnvironments()}
+ >
+
+
+
{
+ setShowArchived(!showArchived);
+ }}
+ />{" "}
+ Show disabled
+
+
+
+
+
+
+
+
+
+
+
+ {environments === undefined || environments === null
+ ? null
+ : environments.map((environment, index) => {
+ if (!showArchived && environment.archived) {
+ return null;
+ }
- //var bgColor = "#27292d"
- //if (index % 2 === 0) {
- // bgColor = "#1f2023"
- //}
+ if (environment.archived === undefined) {
+ getEnvironments();
+ return null;
+ }
- return (
-
-
-
-
-
- {environment.default ?
- null
- :
- setDefaultEnvironment(environment)} color="primary">Set default
- }
-
-
- deleteEnvironment(environment)} color="primary">{environment.archived ? "Activate" : "Disable"}
- {/* flushQueue(environment.Name)} color="primary">Flush Queue */}
-
-
-
- )
- })}
-
-
- : null
+ var bgColor = "#27292d";
+ if (index % 2 === 0) {
+ bgColor = "#1f2023";
+ }
- const organizationsTab = curTab === 6 ?
-
-
-
Organizations
- Global admin: control organizations
-
-
{
- setModalOpen(true)
- }}
- >
- Add suborganization
-
-
-
-
-
-
-
-
-
-
-
- {userdata.orgs !== undefined && userdata.orgs !== null && userdata.orgs.length > 0 ?
-
- {userdata.orgs.map((data, index) => {
- const isSelected = props.userdata.active_org.id === undefined ? "False" : props.userdata.active_org.id === data.id ? "True" : "False"
+ return (
+
+
+
+
+
+ {environment.default ? null : (
+ setDefaultEnvironment(environment)}
+ color="primary"
+ >
+ Make default
+
+ )}
+
+
+
+
+
+ deleteEnvironment(environment)}
+ color="primary"
+ >
+ {environment.archived ? "Activate" : "Disable"}
+
+ {
+ console.log("Should clear executions for: ", environment)
+ abortEnvironmentWorkflows(environment)
+ }} color="primary">Clear executions
+
+
+
+ );
+ })}
+
+
+ ) : null;
- const imagesize = 40
- const imageStyle = {width: imagesize, height: imagesize, pointerEvents: "none", }
- const image = data.image === "" ?
-
- :
-
+ const organizationsTab =
+ curTab === 6 ? (
+
+
+
Organizations
+
+ Global admin: control organizations
+
+
+
{
+ setModalOpen(true);
+ }}
+ >
+ Add suborganization
+
+
+
+
+
+
+
+
+
+
+
+ {userdata.orgs !== undefined &&
+ userdata.orgs !== null &&
+ userdata.orgs.length > 0 ? (
+
+ {userdata.orgs.map((data, index) => {
+ const isSelected =
+ props.userdata.active_org.id === undefined
+ ? "False"
+ : props.userdata.active_org.id === data.id
+ ? "True"
+ : "False";
- var bgColor = "#27292d"
- if (index % 2 === 0) {
- bgColor = "#1f2023"
- }
+ const imagesize = 40;
+ const imageStyle = {
+ width: imagesize,
+ height: imagesize,
+ pointerEvents: "none",
+ };
+ const image =
+ data.image === "" ? (
+
+ ) : (
+
+ );
- return (
-
-
-
-
-
-
- {
- setCloudSyncModalOpen(true)
- setSelectedOrganization(data)
- console.log("INVERT CLOUD SYNC")
- }} />
- style={{minWidth: 150, maxWidth: 150}}
- />
-
- )
- })}
-
- : null}
-
-
- : null
+ var bgColor = "#27292d";
+ if (index % 2 === 0) {
+ bgColor = "#1f2023";
+ }
- const hybridTab = curTab === 7 ?
-
-
-
Hybrid
-
-
-
-
-
-
+
+
+
+
+
+ {
+ setCloudSyncModalOpen(true);
+ setSelectedOrganization(data);
+ console.log("INVERT CLOUD SYNC");
+ }}
+ />
+ style={{ minWidth: 150, maxWidth: 150 }}
+ />
+
+ );
+ })}
+
+ ) : null}
+
+
+ ) : null;
+
+ const hybridTab =
+ curTab === 7 ? (
+
+
+
Hybrid
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ console.log("INVERT");
+ }}
+ />
+ style={{ minWidth: 150, maxWidth: 150 }}
+ />
+
+
+
+ ) : null;
+
+ // primary={environment.Registered ? "true" : "false"}
+
+ const iconStyle = { marginRight: 10 };
+ const data = (
+
+
+
+
+ Organization
+
+ />
+
+
+ Users
+
+ />
+
+
+ App Authentication
+
+ />
+
+
+ Files
+
+ />
+
+
+ Schedules
+
+ />
+
+
+ Environments
+
/>
-
+ Organizations
+
/>
-
-
-
-
-
- {console.log("INVERT")}} />
- style={{minWidth: 150, maxWidth: 150}}
- />
-
-
-
- : null
+ {/*window.location.protocol == "http:" && window.location.port === "3000" ? Hybrid/> : null*/}
+ {/*window.location.protocol === "http:" && window.location.port === "3000" ? Categories/> : null*/}
+
+
+
+ {organizationView}
+ {authenticationView}
+ {usersView}
+ {environmentView}
+ {schedulesView}
+ {filesView}
+ {hybridTab}
+ {organizationsTab}
+ {appCategoryView}
+
+
+
+ );
- // primary={environment.Registered ? "true" : "false"}
+ return (
+
+ {modalView}
+ {cloudSyncModal}
+ {editUserModal}
+ {editAuthenticationModal}
+ {data}
+
+
+ );
+};
-
-
- const iconStyle = {marginRight: 10}
- const data =
-
-
-
- Organization/>
- Users />
- App Authentication/>
- Files />
- Schedules />
- {isCloud ? null : Environments/>}
- {isCloud ? null : Organizations/>}
- {/*window.location.protocol == "http:" && window.location.port === "3000" ? Hybrid/> : null*/}
- {/*window.location.protocol === "http:" && window.location.port === "3000" ? Categories/> : null*/}
-
-
-
- {organizationView}
- {authenticationView}
- {usersView}
- {environmentView}
- {schedulesView}
- {filesView}
- {hybridTab}
- {organizationsTab}
- {appCategoryView}
-
-
-
-
- return (
-
- {modalView}
- {cloudSyncModal}
- {editUserModal}
- {editAuthenticationModal}
- {data}
-
-
- )
-}
-
-export default Admin
+export default Admin;
diff --git a/frontend/src/views/AdminSetup.jsx b/frontend/src/views/AdminSetup.jsx
index 7f0e3a9a..df2355bc 100644
--- a/frontend/src/views/AdminSetup.jsx
+++ b/frontend/src/views/AdminSetup.jsx
@@ -1,223 +1,233 @@
/* eslint-disable react/no-multi-comp */
-import React, {useState} from 'react';
-import { makeStyles } from '@material-ui/styles';
+import React, { useState } from "react";
+import { makeStyles } from "@material-ui/styles";
-import {CircularProgress, TextField, Button, Paper, Typography} from '@material-ui/core'
+import {
+ CircularProgress,
+ TextField,
+ Button,
+ Paper,
+ Typography,
+} from "@material-ui/core";
const bodyDivStyle = {
- margin: "auto",
- marginTop: "100px",
- width: "500px",
-}
+ margin: "auto",
+ marginTop: "100px",
+ width: "500px",
+};
-const surfaceColor = "#27292D"
-const inputColor = "#383B40"
+const surfaceColor = "#27292D";
+const inputColor = "#383B40";
const boxStyle = {
- paddingLeft: "30px",
- paddingRight: "30px",
- paddingBottom: "30px",
- paddingTop: "30px",
- backgroundColor: surfaceColor,
-}
+ paddingLeft: "30px",
+ paddingRight: "30px",
+ paddingBottom: "30px",
+ paddingTop: "30px",
+ backgroundColor: surfaceColor,
+};
const useStyles = makeStyles({
- notchedOutline: {
- borderColor: "#f85a3e !important"
- },
+ notchedOutline: {
+ borderColor: "#f85a3e !important",
+ },
});
-const AdminAccount = props => {
- const { globalUrl, isLoaded, isLoggedIn, } = props;
+const AdminAccount = (props) => {
+ const { globalUrl, isLoaded, isLoggedIn } = props;
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [firstRequest, setFirstRequest] = useState(true);
const [loginLoading, setLoginLoading] = useState(false);
- // Used to swap from login to register. True = login, false = register
- const register = true
+ // Used to swap from login to register. True = login, false = register
+ const register = true;
- const classes = useStyles();
- // Error messages etc
- const [loginInfo, setLoginInfo] = useState("");
+ const classes = useStyles();
+ // Error messages etc
+ const [loginInfo, setLoginInfo] = useState("");
- const handleValidateForm = () => {
- return (username.length > 1 && password.length > 1);
- }
+ const handleValidateForm = () => {
+ return username.length > 1 && password.length > 1;
+ };
- if (isLoggedIn === true) {
- window.location.pathname = "/workflows"
- }
+ if (isLoggedIn === true) {
+ window.location.pathname = "/workflows";
+ }
- const checkAdmin = () => {
- const url = globalUrl+'/api/v1/checkusers';
- fetch(url, {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- },
- })
- .then(response =>
- response.json().then(responseJson => {
- if (responseJson["success"] === false) {
- setLoginInfo(responseJson["reason"])
- } else {
- if (responseJson.reason === "redirect") {
- window.location.pathname = "/login"
- }
- }
- }),
- )
- .catch(error => {
- setLoginInfo("Error in userdata: ", error)
- })
- }
+ const checkAdmin = () => {
+ const url = globalUrl + "/api/v1/checkusers";
+ fetch(url, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ })
+ .then((response) =>
+ response.json().then((responseJson) => {
+ if (responseJson["success"] === false) {
+ setLoginInfo(responseJson["reason"]);
+ } else {
+ if (responseJson.reason === "redirect") {
+ window.location.pathname = "/login";
+ }
+ }
+ })
+ )
+ .catch((error) => {
+ setLoginInfo("Error in userdata: ", error);
+ });
+ };
- if (firstRequest) {
- setFirstRequest(false)
- checkAdmin()
- }
+ if (firstRequest) {
+ setFirstRequest(false);
+ checkAdmin();
+ }
- const onSubmit = (e) => {
- setLoginLoading(true)
- e.preventDefault()
- // FIXME - add some check here ROFL
+ const onSubmit = (e) => {
+ setLoginLoading(true);
+ e.preventDefault();
+ // FIXME - add some check here ROFL
- // Just use this one?
- var data = {"username": username, "password": password}
- var baseurl = globalUrl
- const url = baseurl+'/api/v1/register';
- fetch(url, {
- method: 'POST',
- body: JSON.stringify(data),
- headers: {
- 'Content-Type': 'application/json',
- },
- })
- .then(response =>
- response.json().then(responseJson => {
- setLoginLoading(false)
- if (responseJson["success"] === false) {
- setLoginInfo(responseJson["reason"])
- } else {
- setLoginInfo("Successful register :)")
- window.location.pathname = "/login"
- }
- }),
- )
- .catch(error => {
- setLoginLoading(false)
- setLoginInfo("Error in userdata: ", error)
- });
- }
+ // Just use this one?
+ var data = { username: username, password: password };
+ var baseurl = globalUrl;
+ const url = baseurl + "/api/v1/register";
+ fetch(url, {
+ method: "POST",
+ body: JSON.stringify(data),
+ headers: {
+ "Content-Type": "application/json",
+ },
+ })
+ .then((response) =>
+ response.json().then((responseJson) => {
+ setLoginLoading(false);
+ if (responseJson["success"] === false) {
+ setLoginInfo(responseJson["reason"]);
+ } else {
+ setLoginInfo("Successful register :)");
+ window.location.pathname = "/login";
+ }
+ })
+ )
+ .catch((error) => {
+ setLoginLoading(false);
+ setLoginInfo("Error in userdata: ", error);
+ });
+ };
- const onChangeUser = (e) => {
- setUsername(e.target.value)
- }
+ const onChangeUser = (e) => {
+ setUsername(e.target.value);
+ };
- const onChangePass = (e) => {
- setPassword(e.target.value)
- }
+ const onChangePass = (e) => {
+ setPassword(e.target.value);
+ };
- //const onClickRegister = () => {
- // if (props.location.pathname === "/login") {
- // window.location.pathname = "/register"
- // } else {
- // window.location.pathname = "/login"
- // }
+ //const onClickRegister = () => {
+ // if (props.location.pathname === "/login") {
+ // window.location.pathname = "/register"
+ // } else {
+ // window.location.pathname = "/login"
+ // }
- // setLoginCheck(!register)
- //}
+ // setLoginCheck(!register)
+ //}
- //var loginChange = register ? (Want to register? Click here.
) : (Go back to login? Click here.
);
- var formtitle = register ? Login
: Register
+ //var loginChange = register ? (Want to register? Click here.
) : (Go back to login? Click here.
);
+ var formtitle = register ? Login
: Register
;
- formtitle = "Create administrator account"
-
- const basedata =
-
+ formtitle = "Create administrator account";
- const loadedCheck = isLoaded ?
-
- {basedata}
-
- :
-
-
+ const basedata = (
+
+ );
- return (
-
- {loadedCheck}
-
- )
-}
+ const loadedCheck = isLoaded ? {basedata}
:
;
+
+ return {loadedCheck}
;
+};
export default AdminAccount;
diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx
index 4d5beeb1..70b74038 100644
--- a/frontend/src/views/AngularWorkflow.jsx
+++ b/frontend/src/views/AngularWorkflow.jsx
@@ -1,42 +1,119 @@
-import React, {useRef, useState, useEffect, useLayoutEffect} from 'react';
-import { useInterval } from 'react-powerhooks';
-import { useTheme } from '@material-ui/core/styles';
+import React, { useState, useEffect, useLayoutEffect } from "react";
+import ReactDOM from "react-dom"
+import { useInterval } from "react-powerhooks";
+import { makeStyles, useTheme } from "@material-ui/core/styles";
-import { v4 as uuidv4 } from 'uuid';
-import {Link} from 'react-router-dom';
-import { Prompt } from 'react-router'
-import { useBeforeunload } from 'react-beforeunload';
-import ReactJson from 'react-json-view'
+import { v4 as uuidv4 } from "uuid";
+import { useNavigate, Link, useParams } from "react-router-dom";
+// import { Prompt } from "react-router"; // FIXME
+import { useBeforeunload } from "react-beforeunload";
+import ReactJson from "react-json-view";
import NestedMenuItem from "material-ui-nested-menu-item";
-import ReactMarkdown from 'react-markdown';
-
-
-import {TextField, Drawer, Button, Paper, Grid, Tabs, InputAdornment, Tab, ButtonBase, Tooltip, Select, MenuItem, Divider, Dialog, Modal, DialogActions, DialogTitle, InputLabel, DialogContent, FormControl, IconButton, Menu, Input, FormGroup, FormControlLabel, Typography, Checkbox, Breadcrumbs, CircularProgress, Switch, Fade} from '@material-ui/core';
-import {OpenInNew as OpenInNewIcon,Undo as UndoIcon, FileCopy as FileCopyIcon, GetApp as GetAppIcon, Search as SearchIcon, ArrowUpward as ArrowUpwardIcon, Visibility as VisibilityIcon, Done as DoneIcon, Close as CloseIcon, Error as ErrorIcon, FindReplace as FindreplaceIcon, ArrowLeft as ArrowLeftIcon, Cached as CachedIcon, DirectionsRun as DirectionsRunIcon, Add as AddIcon, Polymer as PolymerIcon, FormatListNumbered as FormatListNumberedIcon, Create as CreateIcon, PlayArrow as PlayArrowIcon, AspectRatio as AspectRatioIcon, MoreVert as MoreVertIcon, Apps as AppsIcon, Schedule as ScheduleIcon, FavoriteBorder as FavoriteBorderIcon, Pause as PauseIcon, Delete as DeleteIcon, AddCircleOutline as AddCircleOutlineIcon, Save as SaveIcon, KeyboardArrowLeft as KeyboardArrowLeftIcon, KeyboardArrowRight as KeyboardArrowRightIcon, ArrowBack as ArrowBackIcon, Settings as SettingsIcon, LockOpen as LockOpenIcon, ExpandMore as ExpandMoreIcon, VpnKey as VpnKeyIcon} from '@material-ui/icons';
-
-import * as cytoscape from 'cytoscape';
-import * as edgehandles from 'cytoscape-edgehandles';
-import * as clipboard from 'cytoscape-clipboard';
-import CytoscapeComponent from 'react-cytoscapejs';
-import undoRedo from 'cytoscape-undo-redo';
-import Draggable from 'react-draggable';
-
-import cytoscapestyle from '../defaultCytoscapeStyle';
-import cxtmenu from 'cytoscape-cxtmenu';
-
-import { w3cwebsocket as W3CWebSocket } from "websocket";
+import ReactMarkdown from "react-markdown";
import { useAlert } from "react-alert";
+import theme from '../theme';
+import { isMobile } from "react-device-detect"
+
+import {
+ Zoom,
+ Avatar,
+ Popover,
+ TextField,
+ Drawer,
+ Button,
+ Paper,
+ Grid,
+ Tabs,
+ InputAdornment,
+ Tab,
+ ButtonBase,
+ Tooltip,
+ Select,
+ MenuItem,
+ Divider,
+ Dialog,
+ DialogActions,
+ DialogTitle,
+ InputLabel,
+ DialogContent,
+ FormControl,
+ IconButton,
+ Menu,
+ Input,
+ Fade,
+ FormGroup,
+ FormControlLabel,
+ Typography,
+ Checkbox,
+ Breadcrumbs,
+ CircularProgress,
+ SwipeableDrawer,
+ Switch,
+ Chip,
+} from "@material-ui/core";
+
+import {
+ AvatarGroup,
+} from "@mui/material"
+
+import {
+ OpenInNew as OpenInNewIcon,
+ Undo as UndoIcon,
+ GetApp as GetAppIcon,
+ Search as SearchIcon,
+ Visibility as VisibilityIcon,
+ Done as DoneIcon,
+ Close as CloseIcon,
+ Error as ErrorIcon,
+ ArrowLeft as ArrowLeftIcon,
+ Cached as CachedIcon,
+ DirectionsRun as DirectionsRunIcon,
+ Polymer as PolymerIcon,
+ FormatListNumbered as FormatListNumberedIcon,
+ PlayArrow as PlayArrowIcon,
+ AspectRatio as AspectRatioIcon,
+ MoreVert as MoreVertIcon,
+ Apps as AppsIcon,
+ Schedule as ScheduleIcon,
+ FavoriteBorder as FavoriteBorderIcon,
+ Pause as PauseIcon,
+ Delete as DeleteIcon,
+ AddCircleOutline as AddCircleOutlineIcon,
+ Save as SaveIcon,
+ KeyboardArrowRight as KeyboardArrowRightIcon,
+ ArrowBack as ArrowBackIcon,
+ Settings as SettingsIcon,
+ LockOpen as LockOpenIcon,
+ ExpandMore as ExpandMoreIcon,
+ VpnKey as VpnKeyIcon,
+ AddComment as AddCommentIcon,
+ Edit as EditIcon,
+} from "@material-ui/icons";
+
+import {
+ Preview as PreviewIcon,
+} from '@mui/icons-material';
+
+import Autocomplete from "@material-ui/lab/Autocomplete";
+import * as cytoscape from "cytoscape";
+import * as edgehandles from "cytoscape-edgehandles";
+import * as clipboard from "cytoscape-clipboard";
+import CytoscapeComponent from "react-cytoscapejs";
+import undoRedo from "cytoscape-undo-redo";
+import Draggable from "react-draggable";
+
+import cytoscapestyle from "../defaultCytoscapeStyle";
+import cxtmenu from "cytoscape-cxtmenu";
+
import { validateJson, GetIconInfo } from "./Workflows.jsx";
import { GetParsedPaths } from "./Apps.jsx";
-import ConfigureWorkflow from '../components/ConfigureWorkflow.jsx';
+import ConfigureWorkflow from "../components/ConfigureWorkflow.jsx";
import AuthenticationOauth2 from "../components/Oauth2Auth.jsx";
-import ParsedAction from '../components/ParsedAction.jsx';
-//import Scroll from 'react-scroll'
-//import { Element as ScrollElement, animateScroll as scroll, scrollSpy, scroller } from 'react-scroll'
+import ParsedAction from "../components/ParsedAction.jsx";
+import PaperComponent from "../components/PaperComponent.jsx"
-const surfaceColor = "#27292D"
-const inputColor = "#383B40"
-const appSorter = "favorite_apps"
+const surfaceColor = "#27292D";
+const inputColor = "#383B40";
// http://apps.cytoscape.org/apps/yfileslayoutalgorithms
cytoscape.use(edgehandles);
@@ -48,1476 +125,1738 @@ cytoscape.use(cxtmenu);
//import popper from 'cytoscape-popper';
//cytoscape.use(popper);
-
// https://stackoverflow.com/questions/19014250/rerender-view-on-browser-resize-with-react
function useWindowSize() {
- const [size, setSize] = useState([0, 0]);
- useLayoutEffect(() => {
- function updateSize() {
- setSize([window.innerWidth, window.innerHeight]);
- }
- window.addEventListener('resize', updateSize);
- updateSize();
- return () => window.removeEventListener('resize', updateSize);
- }, []);
- return size;
+ const [size, setSize] = useState([0, 0]);
+ useLayoutEffect(() => {
+ function updateSize() {
+ setSize([window.innerWidth, window.innerHeight]);
+ }
+ window.addEventListener("resize", updateSize);
+ updateSize();
+ return () => window.removeEventListener("resize", updateSize);
+ }, []);
+ return size;
}
export function sortByKey(array, key) {
- if (array === undefined) {
- return []
- }
+ if (array === undefined) {
+ return [];
+ }
- if (key.startsWith("-") && key.length > 2) {
- key = key.slice(1, key.length)
- return array.sort(function(a, b) {
- var x = a[key]; var y = b[key]
- return ((x < y) ? -1 : ((x > y) ? 1 : 0))
- }).reverse()
- }
+ if (key.startsWith("-") && key.length > 2) {
+ key = key.slice(1, key.length);
+ return array
+ .sort(function (a, b) {
+ var x = a[key];
+ var y = b[key];
+ return x < y ? -1 : x > y ? 1 : 0;
+ })
+ .reverse();
+ }
- if (array === undefined || array === null) {
- return []
- }
+ if (array === undefined || array === null) {
+ return [];
+ }
- return array.sort(function(a, b) {
- var x = a[key]; var y = b[key]
- return ((x < y) ? -1 : ((x > y) ? 1 : 0))
- })
-}
-
-function removeParam(key, sourceURL) {
- if (sourceURL === undefined) {
- return
- }
-
- var rtn = sourceURL.split("?")[0],
- param,
- params_arr = [],
- queryString = (sourceURL.indexOf("?") !== -1) ? sourceURL.split("?")[1] : "";
-
- if (queryString !== "") {
- params_arr = queryString.split("&");
- for (var i = params_arr.length - 1; i >= 0; i -= 1) {
- param = params_arr[i].split("=")[0];
- if (param === key) {
- params_arr.splice(i, 1);
- }
- }
- rtn = rtn + "?" + params_arr.join("&");
- }
-
- if (rtn === "?") {
- return ""
- }
-
- return rtn;
-}
-
-const splitter = "|~|"
-const svgSize = 24
-//const referenceUrl = "https://shuffler.io/functions/webhooks/"
-//const referenceUrl = window.location.origin+"/api/v1/hooks/"
-
-function useTraceUpdate(props) {
- const prev = useRef(props)
- useEffect(() => {
- const changedProps = Object.entries(props).reduce((ps, [k, v]) => {
- if (prev.current[k] !== v) {
- ps[k] = [prev.current[k], v];
- }
- return ps;
- }, {});
- if (Object.keys(changedProps).length > 0) {
- console.log('Changed props:', changedProps);
- }
- prev.current = props;
+ return array.sort(function (a, b) {
+ var x = a[key];
+ var y = b[key];
+ return x < y ? -1 : x > y ? 1 : 0;
});
}
-const AngularWorkflow = (props) => {
- const { globalUrl, isLoggedIn, isLoaded, userdata } = props;
- const referenceUrl = globalUrl+"/api/v1/hooks/"
- const alert = useAlert()
- const theme = useTheme();
- const green = "#86c142"
- const yellow = "#FECC00"
+function removeParam(key, sourceURL) {
+ if (sourceURL === undefined) {
+ return;
+ }
- const [bodyWidth, bodyHeight] = useWindowSize();
+ var rtn = sourceURL.split("?")[0],
+ param,
+ params_arr = [],
+ queryString = sourceURL.indexOf("?") !== -1 ? sourceURL.split("?")[1] : "";
- var to_be_copied = ""
- const [cystyle, ] = useState(cytoscapestyle)
- const [cy, setCy] = React.useState()
-
- const [currentView, setCurrentView] = React.useState(0)
- const [triggerAuthentication, setTriggerAuthentication] = React.useState({})
- const [triggerFolders, setTriggerFolders] = React.useState([])
- const [workflows, setWorkflows] = React.useState([])
- const [showEnvironment, setShowEnvironment] = React.useState(false)
+ if (queryString !== "") {
+ params_arr = queryString.split("&");
+ for (var i = params_arr.length - 1; i >= 0; i -= 1) {
+ param = params_arr[i].split("=")[0];
+ if (param === key) {
+ params_arr.splice(i, 1);
+ }
+ }
+ rtn = rtn + "?" + params_arr.join("&");
+ }
- const [workflow, setWorkflow] = React.useState({});
- const [userSettings, setUserSettings] = React.useState({});
- const [subworkflow, setSubworkflow] = React.useState({});
- const [subworkflowStartnode, setSubworkflowStartnode] = React.useState("");
- const [leftViewOpen, setLeftViewOpen] = React.useState(true);
- const [leftBarSize, setLeftBarSize] = React.useState(350)
- const [executionText, setExecutionText] = React.useState("");
- const [executionRequestStarted, setExecutionRequestStarted] = React.useState(false);
- const [scrollConfig, setScrollConfig] = React.useState({
- top: 0,
- left: 0,
- selected: "",
- })
+ if (rtn === "?") {
+ return "";
+ }
- const [history, setHistory] = React.useState([])
- const [historyIndex, setHistoryIndex] = React.useState(history.length)
+ return rtn;
+}
- const [appAuthentication, setAppAuthentication] = React.useState([]);
- const [variablesModalOpen, setVariablesModalOpen] = React.useState(false);
- const [executionVariablesModalOpen, setExecutionVariablesModalOpen] = React.useState(false);
- const [authenticationModalOpen, setAuthenticationModalOpen] = React.useState(false);
- const [conditionsModalOpen, setConditionsModalOpen] = React.useState(false);
- const [newVariableName, setNewVariableName] = React.useState("");
- const [authenticationType, setAuthenticationType] = React.useState("");
- const [newVariableDescription, setNewVariableDescription] = React.useState("");
- const [newVariableValue, setNewVariableValue] = React.useState("");
- const [workflowDone, setWorkflowDone] = React.useState(false)
- const [authLoaded, setAuthLoaded] = React.useState(false)
- const [localFirstrequest, setLocalFirstrequest] = React.useState(true)
- const [requiresAuthentication, setRequiresAuthentication] = React.useState(false)
- const [rightSideBarOpen, setRightSideBarOpen] = React.useState(false)
- const [showSkippedActions, setShowSkippedActions] = React.useState(false)
- const [lastExecution, setLastExecution] = React.useState("")
- const [configureWorkflowModalOpen, setConfigureWorkflowModalOpen] = React.useState(false)
- const [curpath, setCurpath] = useState(typeof window === 'undefined' || window.location === undefined ? "" : window.location.pathname)
+const useStyles = makeStyles({
+ notchedOutline: {
+ borderColor: "#f85a3e !important",
+ },
+ root: {
+ "& .MuiAutocomplete-listbox": {
+ border: "2px solid #f85a3e",
+ color: "white",
+ fontSize: 18,
+ "& li:nth-child(even)": {
+ backgroundColor: "#CCC",
+ },
+ "& li:nth-child(odd)": {
+ backgroundColor: "#FFF",
+ },
+ },
+ },
+ inputRoot: {
+ color: "white",
+ // This matches the specificity of the default styles at https://github.com/mui-org/material-ui/blob/v4.11.3/packages/material-ui-lab/src/Autocomplete/Autocomplete.js#L90
+ "&:hover .MuiOutlinedInput-notchedOutline": {
+ borderColor: "#f86a3e",
+ },
+ },
+});
- // 0 = normal, 1 = just done, 2 = normal
- const [savingState, setSavingState] = React.useState(0)
+const splitter = "|~|";
+const svgSize = 24;
+//const referenceUrl = "https://shuffler.io/functions/webhooks/"
+//const referenceUrl = window.location.origin+"/api/v1/hooks/"
- const [selectedResult, setSelectedResult] = React.useState({})
- const [codeModalOpen, setCodeModalOpen] = React.useState(false);
+const AngularWorkflow = (defaultprops) => {
+ const { globalUrl, isLoggedIn, isLoaded, userdata } = defaultprops;
+ const referenceUrl = globalUrl + "/api/v1/hooks/";
+ const alert = useAlert()
+ let navigate = useNavigate();
- const [variableAnchorEl, setVariableAnchorEl] = React.useState(null)
+ const params = useParams();
+ var props = JSON.parse(JSON.stringify(defaultprops))
+ props.match = {}
+ props.match.params = params
- const [sourceValue, setSourceValue] = React.useState({})
- const [destinationValue, setDestinationValue] = React.useState({})
- const [conditionValue, setConditionValue] = React.useState({})
- const [dragging, setDragging] = React.useState(false)
- const [dragPosition, setDragPosition] = React.useState({
- x: 0,
- y: 0,
- })
+ const green = "#86c142";
+ const yellow = "#FECC00";
+ //const theme = useTheme();
- // Trigger stuff
- const [selectedTrigger, setSelectedTrigger] = React.useState({});
- const [selectedTriggerIndex, setSelectedTriggerIndex] = React.useState({});
- const [selectedEdge, setSelectedEdge] = React.useState({});
- const [selectedEdgeIndex, setSelectedEdgeIndex] = React.useState({});
+ const [bodyWidth, bodyHeight] = useWindowSize();
+
+ var to_be_copied = "";
+ const [firstrequest, setFirstrequest] = React.useState(true);
+ const [cystyle] = useState(cytoscapestyle);
+ const [cy, setCy] = React.useState();
+
+ const [currentView, setCurrentView] = React.useState(0);
+ const [triggerAuthentication, setTriggerAuthentication] = React.useState({});
+ const [triggerFolders, setTriggerFolders] = React.useState([]);
+ const [workflows, setWorkflows] = React.useState([]);
+ const [showEnvironment, setShowEnvironment] = React.useState(false);
+ const [editWorkflowDetails, setEditWorkflowDetails] = React.useState(false);
+
+ const [workflow, setWorkflow] = React.useState({});
+ const [userSettings, setUserSettings] = React.useState({});
+ const [subworkflow, setSubworkflow] = React.useState({});
+ const [subworkflowStartnode, setSubworkflowStartnode] = React.useState("");
+ const [leftViewOpen, setLeftViewOpen] = React.useState(isMobile ? false : true);
+ const [leftBarSize, setLeftBarSize] = React.useState(isMobile ? 0 : 350);
+ const [creatorProfile, setCreatorProfile] = React.useState({});
+ const [appGroup, setAppGroup] = React.useState([]);
+ const [triggerGroup, setTriggerGroup] = React.useState([]);
+ const [executionText, setExecutionText] = React.useState("");
+ const [executionRequestStarted, setExecutionRequestStarted] =
+ React.useState(false);
+ const [scrollConfig, setScrollConfig] = React.useState({
+ top: 0,
+ left: 0,
+ selected: "",
+ });
+
+ const [history, setHistory] = React.useState([]);
+ const [historyIndex, setHistoryIndex] = React.useState(history.length);
+ const [variableInfo, setVariableInfo] = React.useState({})
+
+ const [appAuthentication, setAppAuthentication] = React.useState([]);
+ const [variablesModalOpen, setVariablesModalOpen] = React.useState(false);
+ const [executionVariablesModalOpen, setExecutionVariablesModalOpen] =
+ React.useState(false);
+ const [authenticationModalOpen, setAuthenticationModalOpen] =
+ React.useState(false);
+ const [conditionsModalOpen, setConditionsModalOpen] = React.useState(false);
+ const [authenticationType, setAuthenticationType] = React.useState("");
- const [visited, setVisited] = React.useState([]);
- //const [workflow, setWorkflow] = React.useState(workflowdata);
-
- const [apps, setApps] = React.useState([]);
- const [filteredApps, setFilteredApps] = React.useState([]);
- const [prioritizedApps, setPrioritizedApps] = React.useState([]);
- const [firstrequest, setFirstrequest] = React.useState(true)
- //const [apps, setApps] = React.useState(appdata);
- //const [filteredApps, setFilteredApps] = React.useState();
-
- const [environments, setEnvironments] = React.useState([]);
- const [established, setEstablished] = React.useState(false);
+ const [workflowDone, setWorkflowDone] = React.useState(false);
+ const [authLoaded, setAuthLoaded] = React.useState(false);
+ const [localFirstrequest, setLocalFirstrequest] = React.useState(true);
+ const [requiresAuthentication, setRequiresAuthentication] =
+ React.useState(false);
+ const [rightSideBarOpen, setRightSideBarOpen] = React.useState(false);
+ const [showSkippedActions, setShowSkippedActions] = React.useState(false);
+ const [lastExecution, setLastExecution] = React.useState("");
+ const [configureWorkflowModalOpen, setConfigureWorkflowModalOpen] =
+ React.useState(false);
- const [graphSetup, setGraphSetup] = React.useState(false);
+ const curpath =
+ typeof window === "undefined" || window.location === undefined
+ ? ""
+ : window.location.pathname;
- const [selectedApp, setSelectedApp] = React.useState({});
- const [selectedAction, setSelectedAction] = React.useState({});
- const [selectedActionEnvironment, setSelectedActionEnvironment] = React.useState({});
+ // 0 = normal, 1 = just done, 2 = normal
+ const [savingState, setSavingState] = React.useState(0);
- const [executionRequest, setExecutionRequest] = React.useState({})
+ const [selectedResult, setSelectedResult] = React.useState({});
+ const [codeModalOpen, setCodeModalOpen] = React.useState(false);
- const [, setExecutingNodes] = React.useState([])
- const [executionRunning, setExecutionRunning] = React.useState(false)
- const [executionModalOpen, setExecutionModalOpen] = React.useState(false)
- const [executionModalView, setExecutionModalView] = React.useState(0)
- const [executionData, setExecutionData] = React.useState({})
+ const [variableAnchorEl, setVariableAnchorEl] = React.useState(null);
- const [lastSaved, setLastSaved] = React.useState(true)
+ const [sourceValue, setSourceValue] = React.useState({});
+ const [destinationValue, setDestinationValue] = React.useState({});
+ const [conditionValue, setConditionValue] = React.useState({});
+ const [dragging, setDragging] = React.useState(false);
+ const [dragPosition, setDragPosition] = React.useState({
+ x: 0,
+ y: 0,
+ });
- const [appAdded, setAppAdded] = useState(false)
- const [update, setUpdate] = useState("");
- const [workflowExecutions, setWorkflowExecutions] = React.useState([]);
- const [defaultEnvironmentIndex, setDefaultEnvironmentIndex] = React.useState(0)
+ // Trigger stuff
+ const [selectedComment, setSelectedComment] = React.useState({});
+ const [selectedTrigger, setSelectedTrigger] = React.useState({});
+ const [selectedTriggerIndex, setSelectedTriggerIndex] = React.useState({});
+ const [selectedEdge, setSelectedEdge] = React.useState({});
+ const [selectedEdgeIndex, setSelectedEdgeIndex] = React.useState({});
- // This should all be set once, not on every iteration
- // Use states and don't update lol
- const cloudSyncEnabled = props.userdata !== undefined && props.userdata.active_org !== null && props.userdata.active_org !== undefined ? props.userdata.active_org.cloud_sync === true : false
- //const triggerEnvironments = cloudSyncEnabled ? ["cloud", "onprem"] : environments
- const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"
- const appBarSize = isCloud ? 75 : 60
- const triggerEnvironments = isCloud ? ["cloud"] : ["onprem", "cloud"]
- const unloadText = 'Are you sure you want to leave without saving (CTRL+S)?'
+ const [visited, setVisited] = React.useState([]);
- useBeforeunload(() => {
- if (!lastSaved) {
- return unloadText
- }
- })
+ const [apps, setApps] = React.useState([]);
+ const [filteredApps, setFilteredApps] = React.useState([]);
+ const [prioritizedApps, setPrioritizedApps] = React.useState([]);
- const [elements, setElements] = useState([])
- // No point going as fast, as the nodes aren't realtime anymore, but bulk updated.
- // Set it from 2500 to 6000 to reduce overall load
- const { start, stop } = useInterval({
- duration: 3000,
- startImmediate: false,
- callback: () => {
- fetchUpdates()
- }
- })
+ const [environments, setEnvironments] = React.useState([]);
+ const [established, setEstablished] = React.useState(false);
- const getAvailableWorkflows = (trigger_index) => {
- fetch(globalUrl+"/api/v1/workflows", {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- },
- credentials: "include",
+ const [graphSetup, setGraphSetup] = React.useState(false);
+
+ const [selectedApp, setSelectedApp] = React.useState({});
+ const [selectedAction, setSelectedAction] = React.useState({});
+ const [selectedActionEnvironment, setSelectedActionEnvironment] =
+ React.useState({});
+
+ const [executionRequest, setExecutionRequest] = React.useState({});
+
+ const [executionRunning, setExecutionRunning] = React.useState(false);
+ const [executionModalOpen, setExecutionModalOpen] = React.useState(false);
+ const [executionModalView, setExecutionModalView] = React.useState(0);
+ const [executionData, setExecutionData] = React.useState({});
+ const [appsLoaded, setAppsLoaded] = React.useState(false);
+
+ const [lastSaved, setLastSaved] = React.useState(true);
+
+ // eslint-disable-next-line no-unused-vars
+ const [_, setUpdate] = useState(""); // Used for rendring, don't remove
+
+ const [workflowExecutions, setWorkflowExecutions] = React.useState([]);
+ const [defaultEnvironmentIndex, setDefaultEnvironmentIndex] = React.useState(0);
+
+ // This should all be set once, not on every iteration
+ // Use states and don't update lol
+ const cloudSyncEnabled =
+ props.userdata !== undefined &&
+ props.userdata.active_org !== null &&
+ props.userdata.active_org !== undefined
+ ? props.userdata.active_org.cloud_sync === true
+ : false;
+ const isCloud =
+ window.location.host === "localhost:3002" ||
+ window.location.host === "shuffler.io";
+ const appBarSize = isCloud ? 75 : 60;
+ const triggerEnvironments = isCloud ? ["cloud"] : ["onprem", "cloud"];
+ const unloadText = "Are you sure you want to leave without saving (CTRL+S)?";
+ const classes = useStyles();
+ const cytoscapeWidth = isMobile ? bodyWidth - leftBarSize : bodyWidth - leftBarSize - 25
+
+
+ const [elements, setElements] = useState([]);
+ // No point going as fast, as the nodes aren't realtime anymore, but bulk updated.
+ // Set it from 2500 to 6000 to reduce overall load
+ const { start, stop } = useInterval({
+ duration: 3000,
+ startImmediate: false,
+ callback: () => {
+ fetchUpdates();
+ },
+ });
+
+ const getAvailableWorkflows = (trigger_index) => {
+ fetch(globalUrl + "/api/v1/workflows", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
})
- .then((response) => {
- if (response.status !== 200) {
- console.log("Status not 200 for workflows :O!")
- return
- }
- return response.json()
- })
- .then((responseJson) => {
- if (responseJson !== undefined) {
- setWorkflows(responseJson)
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for workflows :O!");
+ return;
+ }
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (responseJson !== undefined) {
+ setWorkflows(responseJson);
- // Sets up subflow trigger with the right info
- if (trigger_index > -1) {
- var outersub = {}
- const trigger = workflow.triggers[trigger_index]
- if (trigger.parameters.length >= 3) {
- for (var key in trigger.parameters) {
- const param = trigger.parameters[key]
- if (param.name === "workflow") {
- if (param.value === workflow.id) {
- setSubworkflow(workflow)
- outersub = workflow
- } else {
- const sub = responseJson.find(data => data.id === param.value)
- if (sub !== undefined && subworkflow.id !== sub.id) {
- setSubworkflow(sub)
- outersub = sub
+ // Sets up subflow trigger with the right info
+ if (trigger_index > -1) {
+ var baseSubflow = {}
+ const trigger = workflow.triggers[trigger_index];
+ if (trigger.parameters.length >= 3) {
+ for (var key in trigger.parameters) {
+ const param = trigger.parameters[key];
+
+ if (param.name === "workflow") {
+ if (param.value === workflow.id) {
+ setSubworkflow(workflow);
+ baseSubflow = workflow
+ } else {
+ const sub = responseJson.find(
+ (data) => data.id === param.value
+ );
+ if (sub !== undefined && subworkflow.id !== sub.id) {
+ baseSubflow = sub
+ setSubworkflow(sub);
+ }
+ }
+ }
+
+ if (
+ param.name === "startnode" &&
+ param.value !== undefined &&
+ param.value !== null
+ ) {
+ if (Object.getOwnPropertyNames(baseSubflow).length > 0) {
+ const foundAction = baseSubflow.actions.find(action => action.id === param.value)
+ if (foundAction !== null && foundAction !== undefined) {
+ setSubworkflowStartnode(foundAction);
+ }
+ } else {
+ setSubworkflowStartnode(param.value);
}
- }
- }
+ }
+ }
+ }
+ }
+ }
+ })
+ .catch((error) => {
+ alert.error(error.toString());
+ });
+ };
- console.log("PARAM: ", param)
- //console.log("PARAMVAL: ", param.value)
- if (param.name === "startnode" && param.value !== undefined && param.value !== null) {
- //console.log("SHOULD SET STARTNODE IN SUBFLOW SELECTION: ", outersub)
- //const innernode = outersub.actions.find(action => action.id === param.value)
- //console.log("FOUND NODE: ", innernode)
- //if (innernode !== undefined && subworkflowStartnode.id !== innernode.id) {
- //setSubworkflowStartnode(JSON.parse(JSON.stringify(param.value)))
- setSubworkflowStartnode(param.value)
- //}
- /*
- const sub = responseJson.find(data => data.id === param.value)
- setSubworkflow(sub)
- }
- */
- }
- }
- }
+ function OuterLink(props) {
+ if (props.href.includes("http") || props.href.includes("mailto")) {
+ return (
+
+ {props.children}
+
+ );
+ }
+ return (
+
+ {props.children}
+
+ );
+ }
+
+ function Img(props) {
+ return ;
+ }
+
+ function CodeHandler(props) {
+ return (
+
+ {props.value}
+
+ );
+ }
+
+ function Heading(props) {
+ const element = React.createElement(
+ `h${props.level}`,
+ { style: { marginTop: 40 } },
+ props.children
+ );
+ return (
+
+ {props.level !== 1 ? (
+
+ ) : null}
+ {element}
+
+ );
+ }
+
+ const generateApikey = () => {
+ fetch(globalUrl + "/api/v1/generateapikey", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for APIKEY gen :O!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ setUserSettings(responseJson);
+ })
+ .catch((error) => {
+ console.log(error);
+ });
+ };
+
+ const getSettings = () => {
+ fetch(globalUrl + "/api/v1/getsettings", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for get settings :O!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (
+ responseJson.success === true &&
+ (responseJson.apikey === undefined ||
+ responseJson.apikey.length === 0 ||
+ responseJson.apikey === null)
+ ) {
+ generateApikey();
+ }
+
+ if (responseJson.success === true) {
+ setUserSettings(responseJson)
}
- }
+ })
+ .catch((error) => {
+ console.log(error);
+ });
+ };
+
+ const setNewAppAuth = (appAuthData) => {
+ console.log("DAta: ", appAuthData);
+ fetch(globalUrl + "/api/v1/apps/authentication", {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ body: JSON.stringify(appAuthData),
+ credentials: "include",
})
- .catch(error => {
- alert.error(error.toString())
- });
- }
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for setting app auth :O!");
+ }
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (!responseJson.success) {
+ alert.error("Failed to set app auth: " + responseJson.reason);
+ } else {
+ getAppAuthentication(true, false);
+ setAuthenticationModalOpen(false);
- function OuterLink(props) {
- if (props.href.includes("http") || props.href.includes("mailto")) {
- return {props.children}
- }
- return {props.children}
- }
+ // Needs a refresh with the new authentication..
+ //alert.success("Successfully saved new app auth")
+ }
+ })
+ .catch((error) => {
+ alert.error(error.toString());
+ });
+ };
- function Img(props) {
- return
- }
+ const getWorkflowExecution = (id, execution_id) => {
+ fetch(globalUrl + "/api/v1/workflows/" + id + "/executions", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for WORKFLOW EXECUTION :O!");
+ }
- function CodeHandler(props) {
- return (
-
-
- {props.value}
-
-
- )
- }
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (responseJson.length > 0) {
+ // FIXME: Sort this by time
- function Heading(props) {
- const element = React.createElement(`h${props.level}`, {style: {marginTop: 40}}, props.children)
- return (
-
- {props.level !== 1 ? : null}
- {element}
-
- )
- }
+ // - means it's opposite
+ const newkeys = sortByKey(responseJson, "-started_at");
+ setWorkflowExecutions(newkeys);
- const generateApikey = () => {
- fetch(globalUrl+"/api/v1/generateapikey", {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- },
- credentials: "include",
- })
+ const cursearch =
+ typeof window === "undefined" || window.location === undefined
+ ? ""
+ : window.location.search;
+
+ var tmpView = new URLSearchParams(cursearch).get("execution_id");
+ if (
+ execution_id !== undefined &&
+ execution_id !== null &&
+ execution_id.length > 0 &&
+ (tmpView === undefined || tmpView === null || tmpView.length === 0)
+ ) {
+ tmpView = execution_id;
+ }
+
+ if (tmpView !== undefined && tmpView !== null && tmpView.length > 0) {
+ const execution = responseJson.find(
+ (data) => data.execution_id === tmpView
+ );
+
+ if (execution !== null && execution !== undefined) {
+ setExecutionData(execution);
+ setExecutionModalView(1);
+ start();
+
+ setExecutionRequest({
+ execution_id: execution.execution_id,
+ authorization: execution.authorization,
+ });
+
+ const newitem = removeParam("execution_id", cursearch);
+ navigate(curpath + newitem)
+ //props.history.push(curpath + newitem);
+ } else {
+ console.log("Couldn't find execution for execution ID. Retrying as user to get ", tmpView)
+
+ //setExecutionRequestStarted(true);
+ const cur_execution = {
+ execution_id: tmpView,
+ //authorization: data.authorization,
+ }
+ setExecutionModalView(1);
+ setExecutionRequest(cur_execution);
+ start();
+
+ const newitem = removeParam("execution_id", cursearch);
+ navigate(curpath + newitem)
+
+ setTimeout(() => {
+ stop()
+ }, 5000);
+ }
+ }
+ }
+ })
+ .catch((error) => {
+ alert.error(error.toString());
+ });
+ };
+
+ const fetchUpdates = () => {
+ fetch(globalUrl + "/api/v1/streams/results", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ body: JSON.stringify(executionRequest),
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for stream results :O!");
+ stop();
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ //console.log("RESPONSE: ", responseJson)
+ handleUpdateResults(responseJson, executionRequest);
+ })
+ .catch((error) => {
+ console.log("Error: ", error);
+ stop();
+ });
+ };
+
+ const abortExecution = () => {
+ setExecutionRunning(false);
+
+ fetch(
+ globalUrl +
+ "/api/v1/workflows/" +
+ props.match.params.key +
+ "/executions/" +
+ executionRequest.execution_id +
+ "/abort",
+ {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ }
+ )
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for ABORT EXECUTION :O!");
+ }
+
+ return response.json();
+ })
+ .catch((error) => {
+ alert.error(error.toString());
+ });
+ };
+
+ // Controls the colors and direction of execution results.
+ // Style is in defaultCytoscapeStyle.js
+ const handleUpdateResults = (responseJson, executionRequest) => {
+ //console.log(responseJson)
+ // Loop nodes and find results
+ // Update on every interval? idk
+
+ ReactDOM.unstable_batchedUpdates(() => {
+ if (JSON.stringify(responseJson) !== JSON.stringify(executionData)) {
+ // FIXME: If another is selected, don't edit..
+ // Doesn't work because this is some async garbage
+ if (
+ executionData.execution_id === undefined ||
+ (responseJson.execution_id === executionData.execution_id &&
+ responseJson.results !== undefined &&
+ responseJson.results !== null)
+ ) {
+ if (
+ executionData.status !== responseJson.status ||
+ executionData.result !== responseJson.result ||
+ executionData.results.length !== responseJson.results.length
+ ) {
+ setExecutionData(responseJson);
+ } else {
+ console.log("NOT updating state.");
+ }
+ }
+ }
+
+ if (responseJson.execution_id !== executionRequest.execution_id) {
+ cy.elements().removeClass(
+ "success-highlight failure-highlight executing-highlight"
+ );
+ return;
+ }
+
+ if (responseJson.results !== null && responseJson.results.length > 0) {
+ for (var key in responseJson.results) {
+ var item = responseJson.results[key];
+ var currentnode = cy.getElementById(item.action.id);
+ if (currentnode.length === 0) {
+ continue;
+ }
+
+ currentnode = currentnode[0];
+ const outgoingEdges = currentnode.outgoers("edge");
+ const incomingEdges = currentnode.incomers("edge");
+
+ switch (item.status) {
+ case "EXECUTING":
+ currentnode.removeClass("not-executing-highlight");
+ currentnode.removeClass("success-highlight");
+ currentnode.removeClass("failure-highlight");
+ currentnode.removeClass("shuffle-hover-highlight");
+ currentnode.removeClass("awaiting-data-highlight");
+ incomingEdges.addClass("success-highlight");
+ currentnode.addClass("executing-highlight");
+ break;
+ case "SKIPPED":
+ currentnode.removeClass("not-executing-highlight");
+ currentnode.removeClass("success-highlight");
+ currentnode.removeClass("failure-highlight");
+ currentnode.removeClass("shuffle-hover-highlight");
+ currentnode.removeClass("awaiting-data-highlight");
+ currentnode.removeClass("executing-highlight");
+ currentnode.addClass("skipped-highlight");
+ break;
+ case "WAITING":
+ currentnode.removeClass("not-executing-highlight");
+ currentnode.removeClass("success-highlight");
+ currentnode.removeClass("failure-highlight");
+ currentnode.removeClass("shuffle-hover-highlight");
+ currentnode.removeClass("awaiting-data-highlight");
+ currentnode.addClass("executing-highlight");
+
+ if (!visited.includes(item.action.label)) {
+ if (executionRunning) {
+ visited.push(item.action.label);
+ setVisited(visited);
+ }
+ }
+
+ // FIXME - add outgoing nodes to executing
+ //const outgoingNodes = outgoingEdges.find().data().target
+ if (outgoingEdges.length > 0) {
+ outgoingEdges.addClass("success-highlight");
+ }
+ break;
+ case "SUCCESS":
+ currentnode.removeClass("not-executing-highlight");
+ currentnode.removeClass("executing-highlight");
+ currentnode.removeClass("failure-highlight");
+ currentnode.removeClass("shuffle-hover-highlight");
+ currentnode.removeClass("awaiting-data-highlight");
+ currentnode.addClass("success-highlight");
+ incomingEdges.addClass("success-highlight");
+ outgoingEdges.addClass("success-highlight");
+
+ if (
+ visited !== undefined &&
+ visited !== null &&
+ !visited.includes(item.action.label)
+ ) {
+ if (executionRunning) {
+ visited.push(item.action.label);
+ setVisited(visited);
+ }
+ }
+
+ // FIXME - add outgoing nodes to executing
+ //const outgoingNodes = outgoingEdges.find().data().target
+ if (outgoingEdges.length > 0) {
+ for (var i = 0; i < outgoingEdges.length; i++) {
+ const edge = outgoingEdges[i];
+ const targetnode = cy.getElementById(edge.data().target);
+ if (
+ targetnode !== undefined &&
+ !targetnode.classes().includes("success-highlight") &&
+ !targetnode.classes().includes("failure-highlight")
+ ) {
+ targetnode.removeClass("not-executing-highlight");
+ targetnode.removeClass("success-highlight");
+ targetnode.removeClass("shuffle-hover-highlight");
+ targetnode.removeClass("failure-highlight");
+ targetnode.removeClass("awaiting-data-highlight");
+ targetnode.addClass("executing-highlight");
+ }
+ }
+ }
+ break;
+ case "FAILURE":
+ //When status comes as failure, allow user to start workflow execution
+ if (executionRunning) {
+ setExecutionRunning(false);
+ }
+
+ currentnode.removeClass("not-executing-highlight");
+ currentnode.removeClass("executing-highlight");
+ currentnode.removeClass("success-highlight");
+ currentnode.removeClass("awaiting-data-highlight");
+ currentnode.removeClass("shuffle-hover-highlight");
+ currentnode.addClass("failure-highlight");
+
+ if (!visited.includes(item.action.label)) {
+ if (
+ item.action.result !== undefined &&
+ item.action.result !== null &&
+ !item.action.result.includes("failed condition")
+ ) {
+ alert.error(
+ "Error for " +
+ item.action.label +
+ " with result " +
+ item.result
+ );
+ }
+ visited.push(item.action.label);
+ setVisited(visited);
+ }
+ break;
+ case "AWAITING_DATA":
+ currentnode.removeClass("not-executing-highlight");
+ currentnode.removeClass("executing-highlight");
+ currentnode.removeClass("success-highlight");
+ currentnode.removeClass("failure-highlight");
+ currentnode.removeClass("shuffle-hover-highlight");
+ currentnode.addClass("awaiting-data-highlight");
+ break;
+ default:
+ console.log("DEFAULT?");
+ break;
+ }
+ }
+ }
+
+ if (
+ responseJson.status === "ABORTED" ||
+ responseJson.status === "STOPPED" ||
+ responseJson.status === "FAILURE" ||
+ responseJson.status === "WAITING"
+ ) {
+ stop();
+
+ if (executionRunning) {
+ setExecutionRunning(false);
+ }
+
+ var curelements = cy.elements();
+ for (var i = 0; i < curelements.length; i++) {
+ if (curelements[i].classes().includes("executing-highlight")) {
+ curelements[i].removeClass("executing-highlight");
+ curelements[i].addClass("failure-highlight");
+ }
+ }
+
+ getWorkflowExecution(props.match.params.key, "");
+ } else if (responseJson.status === "FINISHED") {
+ setExecutionRunning(false);
+ stop();
+ getWorkflowExecution(props.match.params.key, "");
+ setUpdate(Math.random());
+ }
+ })
+ };
+
+ const sendStreamRequest = (body) => {
+ console.log("Stream not activated yet.")
+ return
+ // Session may be important here huh
+ body.user_id = userdata.id
+
+ fetch(globalUrl + "/api/v1/workflows/" + props.match.params.key + "/stream", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ body: JSON.stringify(body),
+ credentials: "include",
+ })
.then((response) => {
+ setSavingState(0);
if (response.status !== 200) {
- console.log("Status not 200 for APIKEY gen :O!")
+ console.log("Status not 200 for setting workflows :O!");
}
- return response.json()
+ return response.json();
})
.then((responseJson) => {
- setUserSettings(responseJson)
- })
- .catch(error => {
- console.log(error)
- });
- }
-
- const getSettings = () => {
- fetch(globalUrl+"/api/v1/getsettings", {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- },
- credentials: "include",
- })
- .then((response) => {
- if (response.status !== 200) {
- console.log("Status not 200 for get settings :O!")
- }
-
- return response.json()
+ console.log("RESP: ", responseJson)
})
- .then((responseJson) => {
- if (responseJson.apikey === undefined || responseJson.apikey.length === 0 || responseJson.apikey === null) {
- generateApikey()
- }
- setUserSettings(responseJson)
- })
- .catch(error => {
- console.log(error)
- });
+ .catch((error) => {
+ console.log("Stream error: ", error.toString())
+ //alert.error(error.toString());
+ })
+
}
- const setNewAppAuth = (appAuthData) => {
- console.log("DAta: ", appAuthData)
- fetch(globalUrl+"/api/v1/apps/authentication", {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- },
- body: JSON.stringify(appAuthData),
- credentials: "include",
+ const saveWorkflow = (curworkflow, executionArgument, startNode) => {
+ var success = false;
+
+ if (isCloud && !isLoggedIn) {
+ console.log("Should redirect to register with redirect.");
+ window.location.href = `/register?view=/workflows/${props.match.params.key}&message=You need sign up to use workflows with Shuffle`;
+ return;
+ }
+
+ setSavingState(2);
+
+ // This might not be the right course of action, but seems logical, as items could be running already
+ // Makes it possible to update with a version in current render
+ stop();
+ var useworkflow = workflow;
+ if (curworkflow !== undefined) {
+ useworkflow = curworkflow;
+ }
+
+ var cyelements = cy.elements();
+ var newActions = [];
+ var newTriggers = [];
+ var newBranches = [];
+ var newVBranches = [];
+ var newComments = [];
+ for (var key in cyelements) {
+ if (cyelements[key].data === undefined) {
+ continue;
+ }
+
+ var type = cyelements[key].data()["type"];
+ if (type === undefined) {
+ if (
+ cyelements[key].data().source === undefined ||
+ cyelements[key].data().target === undefined
+ ) {
+ continue;
+ }
+
+ var parsedElement = {
+ id: cyelements[key].data().id,
+ source_id: cyelements[key].data().source,
+ destination_id: cyelements[key].data().target,
+ conditions: cyelements[key].data().conditions,
+ decorator: cyelements[key].data().decorator,
+ };
+
+ if (parsedElement.decorator) {
+ newVBranches.push(parsedElement);
+ } else {
+ newBranches.push(parsedElement);
+ }
+ } else {
+ if (type === "ACTION") {
+ const cyelement = cyelements[key].data();
+ const elementid =
+ cyelement.id === undefined || cyelement.id === null
+ ? cyelement["_id"]
+ : cyelement.id;
+
+ var curworkflowAction = useworkflow.actions.find(
+ (a) =>
+ a !== undefined &&
+ (a["id"] === elementid || a["_id"] === elementid)
+ );
+ if (curworkflowAction === undefined) {
+ curworkflowAction = cyelements[key].data();
+ }
+
+ curworkflowAction.position = cyelements[key].position();
+
+ // workaround to fix some edgecases
+ if (
+ curworkflowAction.parameters === "" ||
+ curworkflowAction.parameters === null
+ ) {
+ curworkflowAction.parameters = [];
+ }
+
+ if (
+ curworkflowAction.example === undefined ||
+ curworkflowAction.example === "" ||
+ curworkflowAction.example === null
+ ) {
+ if (cyelements[key].data().example !== undefined) {
+ curworkflowAction.example = cyelements[key].data().example;
+ }
+ }
+
+ // Override just in this place
+ curworkflowAction.errors = [];
+ curworkflowAction.isValid = true;
+
+ // Cleans up OpenAPI items
+ var newparams = [];
+ for (var key in curworkflowAction.parameters) {
+ const thisitem = curworkflowAction.parameters[key];
+ if (thisitem.name.startsWith("${") && thisitem.name.endsWith("}")) {
+ continue;
+ }
+
+ newparams.push(thisitem);
+ }
+
+ curworkflowAction.parameters = newparams;
+ newActions.push(curworkflowAction);
+ } else if (type === "TRIGGER") {
+ if (useworkflow.triggers === undefined || useworkflow.triggers === null) {
+ useworkflow.triggers = [];
+ }
+
+ var curworkflowTrigger = useworkflow.triggers.find(
+ (a) => a.id === cyelements[key].data()["id"]
+ );
+ if (curworkflowTrigger === undefined) {
+ curworkflowTrigger = cyelements[key].data();
+ }
+
+ curworkflowTrigger.position = cyelements[key].position();
+
+ newTriggers.push(curworkflowTrigger);
+ } else if (type === "COMMENT") {
+ if (useworkflow.comments === undefined || useworkflow.comments === null) {
+ useworkflow.comments = [];
+ }
+
+ var curworkflowComment = useworkflow.comments.find(
+ (a) => a.id === cyelements[key].data()["id"]
+ )
+
+ if (curworkflowComment === undefined) {
+ curworkflowComment = cyelements[key].data();
+ try {
+ curworkflowComment.position.x = parseInt(curworkflowComment.position.x)
+ } catch (e) {
+ console.log("Failed to parse position Y of comment: ", curworkflowComment.position.x)
+ }
+
+ try {
+ curworkflowComment.position.y = parseInt(curworkflowComment.position.y)
+ } catch (e) {
+ console.log("Failed to parse position Y of comment: ", curworkflowComment.position.y)
+ }
+ }
+
+ const parsedHeight = parseInt(curworkflowComment["height"])
+ if (!isNaN(parsedHeight)) {
+ curworkflowComment.height = parsedHeight
+ } else {
+ curworkflowComment.width = 150
+ }
+
+ const parsedWidth = parseInt(curworkflowComment["width"])
+ if (!isNaN(parsedWidth)) {
+ curworkflowComment.width = parsedWidth
+ } else {
+ curworkflowComment.width = 200
+ }
+
+ curworkflowComment.position = cyelements[key].position();
+ //console.log(curworkflowComment)
+
+ newComments.push(curworkflowComment);
+ } else {
+ alert.info("No handler for type: " + type);
+ }
+ }
+ }
+
+ useworkflow.actions = newActions;
+ useworkflow.triggers = newTriggers;
+ useworkflow.branches = newBranches;
+ useworkflow.comments = newComments;
+ useworkflow.visual_branches = newVBranches;
+
+ // Errors are backend defined
+ useworkflow.errors = [];
+ useworkflow.previously_saved = true;
+
+ if (cy !== undefined) {
+ // scale: 0.3,
+ // bg: "#27292d",
+ const cyImageData = cy.png({
+ output: "base64uri",
+ maxWidth: 480,
+ maxHeight: 270,
+ })
+
+ if (cyImageData !== undefined && cyImageData !== null && cyImageData.length > 0) {
+ useworkflow.image = cyImageData
+ }
+ }
+
+ setLastSaved(true);
+ fetch(globalUrl + "/api/v1/workflows/" + props.match.params.key, {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ body: JSON.stringify(useworkflow),
+ credentials: "include",
})
+ .then((response) => {
+ setSavingState(0);
+ if (response.status !== 200) {
+ console.log("Status not 200 for setting workflows :O!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (executionArgument !== undefined && startNode !== undefined) {
+ //console.log("Running execution AFTER saving");
+ executeWorkflow(executionArgument, startNode, true);
+ return;
+ }
+
+ if (!responseJson.success) {
+ console.log(responseJson);
+ if (responseJson.reason !== undefined && responseJson.reason !== null) {
+ alert.error("Failed to save: " + responseJson.reason);
+ } else {
+ alert.error("Failed to save. Please contact your admin if this is unexpected.")
+ }
+ } else {
+ if (
+ responseJson.new_id !== undefined &&
+ responseJson.new_id !== null
+ ) {
+ window.location.pathname = "/workflows/" + responseJson.new_id;
+ }
+
+ success = true;
+ if (responseJson.errors !== undefined) {
+ workflow.errors = responseJson.errors;
+ if (responseJson.errors.length === 0) {
+ workflow.isValid = true;
+ workflow.is_valid = true;
+
+ const cyelements = cy.elements();
+
+ for (var i = 0; i < cyelements.length; i++) {
+ //cyelements[i].removeStyle();
+ cyelements[i].data().is_valid = true;
+ cyelements[i].data().errors = [];
+ }
+
+ for (var key in workflow.actions) {
+ workflow.actions[key].is_valid = true;
+ workflow.actions[key].errors = [];
+ }
+ }
+
+ for (var key in workflow.errors) {
+ alert.info(workflow.errors[key]);
+ }
+
+ setWorkflow(workflow);
+ }
+
+ setSavingState(1);
+ setTimeout(() => {
+ setSavingState(0);
+ }, 1500);
+ }
+ })
+ .catch((error) => {
+ setSavingState(0);
+ alert.error(error.toString());
+ });
+
+ return success;
+ };
+
+ const monitorUpdates = () => {
+ var firstnode = cy.getElementById(workflow.start);
+ if (firstnode.length === 0) {
+ var found = false;
+ for (var key in workflow.actions) {
+ if (workflow.actions[key].isStartNode) {
+ console.log("Updating startnode");
+ workflow.start = workflow.actions[key].id;
+ firstnode = cy.getElementById(workflow.actions[key].id);
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ return false;
+ }
+ }
+
+ cy.elements().removeClass(
+ "success-highlight failure-highlight executing-highlight"
+ );
+ firstnode[0].addClass("executing-highlight");
+
+ return true;
+ };
+
+ const executeWorkflow = (executionArgument, startNode, hasSaved) => {
+ ReactDOM.unstable_batchedUpdates(() => {
+ if (hasSaved === false) {
+ setExecutionRequestStarted(true);
+ saveWorkflow(workflow, executionArgument, startNode);
+ console.log("FIXME: Might have forgotten to save before executing.");
+ return;
+ }
+
+ if (workflow.public) {
+ alert.info("Save it to get a new version");
+ }
+
+ var returncheck = monitorUpdates();
+ if (!returncheck) {
+ alert.error("No startnode set.");
+ return;
+ }
+
+ setVisited([]);
+ setExecutionRequest({});
+ stop();
+
+ var curelements = cy.elements();
+ for (var i = 0; i < curelements.length; i++) {
+ curelements[i].addClass("not-executing-highlight");
+ }
+
+ const data = { execution_argument: executionArgument, start: startNode };
+ fetch(
+ globalUrl + "/api/v1/workflows/" + props.match.params.key + "/execute",
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ body: JSON.stringify(data),
+ }
+ )
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for WORKFLOW EXECUTION :O!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (!responseJson.success) {
+ alert.error("Failed to start: " + responseJson.reason);
+ setExecutionRunning(false);
+ setExecutionRequestStarted(false);
+ stop();
+
+ for (var i = 0; i < curelements.length; i++) {
+ curelements[i].removeClass("not-executing-highlight");
+ }
+ return;
+ } else {
+ setExecutionRunning(true);
+ setExecutionRequestStarted(false);
+ }
+
+ if (
+ responseJson.execution_id === "" ||
+ responseJson.execution_id === undefined ||
+ responseJson.authorization === "" ||
+ responseJson.authorization === undefined
+ ) {
+ alert.error("Something went wrong during execution startup");
+ console.log("BAD RESPONSE FOR EXECUTION: ", responseJson);
+ setExecutionRunning(false);
+ setExecutionRequestStarted(false);
+ stop();
+
+ for (var i = 0; i < curelements.length; i++) {
+ curelements[i].removeClass("not-executing-highlight");
+ }
+ return;
+ }
+
+ setExecutionRequest({
+ execution_id: responseJson.execution_id,
+ authorization: responseJson.authorization,
+ });
+ setExecutionData({});
+ setExecutionModalOpen(true);
+ setExecutionModalView(1);
+ start();
+ })
+ .catch((error) => {
+ alert.error(error.toString());
+ });
+ })
+ };
+
+ // This can be used to only show prioritzed ones later
+ // Right now, it can prioritize authenticated ones
+ //"Testing",
+ const internalIds = ["Shuffle Tools", "http", "email"];
+
+ const getAppAuthentication = (reset, updateAction) => {
+ fetch(globalUrl + "/api/v1/apps/authentication", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for app auth :O!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (responseJson.success) {
+ var newauth = [];
+ for (var key in responseJson.data) {
+ if (responseJson.data[key].defined === false) {
+ continue;
+ }
+
+ newauth.push(responseJson.data[key]);
+ }
+
+ if (cy !== undefined) {
+ console.log("NEW AUTH = reset cy's onnodeselect");
+
+ // Remove the old listener for select, run with new one
+ cy.removeListener("select");
+ cy.on("select", "node", (e) => onNodeSelect(e, newauth));
+ cy.on("select", "edge", (e) => onEdgeSelect(e));
+ }
+
+ setAppAuthentication(newauth);
+ setAuthLoaded(true);
+
+ if (updateAction === true) {
+ if (selectedApp.authentication.required) {
+ // Setup auth here :)
+ var appUpdates = false;
+ const authenticationOptions = [];
+
+ var tmpAuth = JSON.parse(JSON.stringify(responseJson.data));
+ var latest = 0;
+ for (var key in tmpAuth) {
+ var item = tmpAuth[key];
+
+ const newfields = {};
+ for (var filterkey in item.fields) {
+ newfields[item.fields[filterkey].key] =
+ item.fields[filterkey].value;
+ }
+
+ item.fields = newfields;
+ if (item.app.name === selectedApp.name) {
+ authenticationOptions.push(item);
+
+ // Always becoming the last one
+ if (item.edited > latest) {
+ latest = item.edited;
+ selectedAction.selectedAuthentication = item;
+
+ for (var key in workflow.actions) {
+ if (workflow.actions[key].app_name === selectedApp.name) {
+ workflow.actions[key].selectedAuthentication = item;
+ workflow.actions[key].authentication_id = item.id;
+ appUpdates = true;
+ }
+ }
+ }
+ }
+ }
+
+ selectedAction.authentication = authenticationOptions;
+ if (
+ selectedAction.selectedAuthentication === null ||
+ selectedAction.selectedAuthentication === undefined ||
+ selectedAction.selectedAuthentication.length === ""
+ ) {
+ selectedAction.selectedAuthentication = {};
+ }
+
+ if (appUpdates === true) {
+ setAuthenticationModalOpen(false);
+ setSelectedAction(selectedAction);
+ setWorkflow(workflow);
+ saveWorkflow(workflow);
+ alert.info("Added and updated authentication!");
+ } else {
+ alert.error("Failed to find new authentication - did it work?");
+ }
+ } else {
+ alert.info("No authentication to update");
+ }
+ }
+ } else {
+ setAuthLoaded(true);
+ }
+ })
+ .catch((error) => {
+ setAuthLoaded(true);
+ alert.error("Auth loading error: " + error.toString());
+ });
+ };
+
+ const getApps = () => {
+ fetch(globalUrl + "/api/v1/apps", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ setAppsLoaded(true)
+ if (response.status !== 200) {
+ console.log("Status not 200 for apps :O!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (responseJson === null) {
+ console.log("No response")
+ const pretend_apps = [{
+ "name": "TBD",
+ "app_name": "TBD",
+ "app_version": "TBD",
+ "description": "TBD",
+ "version": "TBD",
+ "large_image": "",
+ }]
+ setApps(pretend_apps)
+ setFilteredApps(pretend_apps)
+ setPrioritizedApps(pretend_apps);
+ return
+ }
+
+ if (responseJson.success === false) {
+ return
+ }
+
+ // FIXME - handle versions on left bar
+ //handleAppVersioning(responseJson)
+ //var tmpapps = []
+ //tmpapps = tmpapps.concat(getExtraApps())
+ //tmpapps = tmpapps.concat(responseJson)
+ setApps(responseJson);
+
+ if (isCloud) {
+ setFilteredApps(
+ responseJson.filter((app) => !internalIds.includes(app.name))
+ );
+ setPrioritizedApps(
+ responseJson.filter((app) => internalIds.includes(app.name))
+ );
+ } else {
+ //setFilteredApps(
+ // responseJson.filter(
+ // (app) =>
+ // !internalIds.includes(app.name) &&
+ // !(!app.activated && app.generated)
+ // )
+ //);
+
+ var tmpFiltered = responseJson.filter((app) => !internalIds.includes(app.name))
+ //tmpFiltered = sortByKey(tmpFiltered, "activated")
+ setFilteredApps(tmpFiltered)
+
+ //!(!app.activated && app.generated)
+ setPrioritizedApps(
+ responseJson.filter((app) => internalIds.includes(app.name))
+ );
+ }
+ })
+ .catch((error) => {
+ setAppsLoaded(true)
+ alert.error("App loading error: "+error.toString());
+ });
+ };
+
+ // Searhc by username, userId, workflow, appId should all work
+ const getUserProfile = (username) => {
+ fetch(`${globalUrl}/api/v1/users/creators/${username}`, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
.then((response) => {
if (response.status !== 200) {
- console.log("Status not 200 for setting app auth :O!")
- }
-
- return response.json()
- })
- .then((responseJson) => {
- if (!responseJson.success) {
- alert.error("Failed to set app auth: "+responseJson.reason)
- } else {
- getAppAuthentication(true, false)
- setAuthenticationModalOpen(false)
-
- // Needs a refresh with the new authentication..
- //alert.success("Successfully saved new app auth")
- }
- })
- .catch(error => {
- alert.error(error.toString())
- })
- }
-
- const getWorkflowExecution = (id, execution_id) => {
- fetch(globalUrl+"/api/v1/workflows/"+id+"/executions", {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- },
- credentials: "include",
- })
- .then((response) => {
- if (response.status !== 200) {
- console.log("Status not 200 for WORKFLOW EXECUTION :O!")
+ console.log("Status not 200 for WORKFLOW EXECUTION :O!");
}
- return response.json()
+
+ return response.json();
})
.then((responseJson) => {
- if (responseJson.length > 0) {
- // FIXME: Sort this by time
+ if (responseJson.success !== false) {
+ console.log("Found creator: ", responseJson)
+ setCreatorProfile(responseJson)
+ }
+ })
+ .catch((error) => {
+ console.log(error);
+ })
+ }
+
+ const getWorkflow = (workflow_id, sourcenode) => {
+ console.log(
+ //`Getting workflow ${workflow_id} with append value ${sourcenode}`
+ );
+
+ fetch(globalUrl + "/api/v1/workflows/" + workflow_id, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for workflows :O!");
+ window.location.pathname = "/workflows";
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ // Not sure why this is necessary.
+ if (responseJson.isValid === undefined) {
+ responseJson.isValid = true;
+ }
+
+ if (responseJson.errors === undefined) {
+ responseJson.errors = [];
+ }
+
+ if (responseJson.actions === undefined || responseJson.actions === null) {
+ responseJson.actions = [];
+ }
+
+ if (responseJson.triggers === undefined || responseJson.triggers === null) {
+ responseJson.triggers = [];
+ }
+
+ if (responseJson.public) {
+ alert.info("This workflow is public. Save the workflow to use it in your organization.");
- // - means it's opposite
- const newkeys = sortByKey(responseJson, "-started_at")
- setWorkflowExecutions(newkeys)
- //console.log("NEWKEYS: ", newkeys)
- //setWorkflowExecutions(responseJson)
-
- const cursearch = typeof window === 'undefined' || window.location === undefined ? "" : window.location.search
- var tmpView = new URLSearchParams(cursearch).get("execution_id")
- if (execution_id !== undefined && execution_id !== null && execution_id.length > 0 && (tmpView === undefined || tmpView === null || tmpView.length === 0)) {
- tmpView = execution_id
- }
-
- if (tmpView !== undefined && tmpView !== null && tmpView.length > 0) {
- const execution = responseJson.find(data => data.execution_id === tmpView)
- if (execution !== null && execution !== undefined) {
- setExecutionData(execution)
- setExecutionModalView(1)
- start()
-
- setExecutionRequest({
- "execution_id": execution.execution_id,
- "authorization": execution.authorization,
- })
-
- const newitem = removeParam("execution_id", cursearch)
- props.history.push(curpath+newitem)
- }
- }
- }
-
- //alert.info("Loaded executions")
- //setWorkflowExecutions(responseJson)
- })
- .catch(error => {
- alert.error(error.toString())
- });
- }
-
- const debugView = workflowExecutions.length > 0 ?
-
-
- {workflowExecutions.slice(0,15).map((data, index) => {
- return (
-
- {new Date(data.started_at*1000).toISOString()}
- , {data.status}
- {data.result.length > 0 ? ", "+data.result : ", "}
- {data.execution_argument.length > 0 ? ", "+data.execution_argument : ", "}
-
-
- )
- return
- })}
-
-
- : null
-
- const fetchUpdates = () => {
- fetch(globalUrl+"/api/v1/streams/results", {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- },
- body: JSON.stringify(executionRequest),
- credentials: "include",
- })
- .then((response) => {
- if (response.status !== 200) {
- console.log("Status not 200 for stream results :O!")
- stop()
- }
-
- return response.json()
- })
- .then((responseJson) => {
- handleUpdateResults(responseJson, executionRequest)
- })
- .catch(error => {
- console.log("Error: ", error)
- //alert.error(error.toString())
- stop()
- })
- }
-
- const abortExecution = () => {
- setExecutionRunning(false)
-
- //alert.info("Aborting execution")
- fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key+"/executions/"+executionRequest.execution_id+"/abort", {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- },
- credentials: "include",
- })
- .then((response) => {
- if (response.status !== 200) {
- console.log("Status not 200 for ABORT EXECUTION :O!")
- } else {
- //alert.success("Execution aborted")
- }
-
- return response.json()
- })
- .catch(error => {
- alert.error(error.toString())
- });
- }
-
- // Controls the colors and direction of execution results.
- // Style is in defaultCytoscapeStyle.js
- const handleUpdateResults = (responseJson, executionRequest) => {
- //console.log(responseJson)
- // Loop nodes and find results
- // Update on every interval? idk
-
- if (JSON.stringify(responseJson) !== JSON.stringify(executionData)) {
- // FIXME: If another is selected, don't edit..
- // Doesn't work because this is some async garbage
- if (executionData.execution_id === undefined || (responseJson.execution_id === executionData.execution_id && responseJson.results !== undefined && responseJson.results !== null)) {
-
- if (executionData.status !== responseJson.status || executionData.result !== responseJson.result || executionData.results.length !== responseJson.results.length) {
- //console.log("Updated state with this data:")
- //console.log(responseJson)
- //console.log(executionData)
- setExecutionData(responseJson)
- } else {
- console.log("NOT updating state.")
- }
- }
- } else {
- //console.log("JSON is same")
- }
-
- //console.log("PRE LOOPING RESULTS: !", responseJson.execution_id, executionRequest.execution_id)
-
- if (responseJson.execution_id !== executionRequest.execution_id) {
- cy.elements().removeClass('success-highlight failure-highlight executing-highlight')
- return
- }
-
- //console.log("LOOPING RESULTS!")
-
- if (responseJson.results !== null && responseJson.results.length > 0) {
- for (var key in responseJson.results) {
- var item = responseJson.results[key]
- var currentnode = cy.getElementById(item.action.id)
- if (currentnode.length === 0) {
- continue
- }
-
- currentnode = currentnode[0]
- const outgoingEdges = currentnode.outgoers('edge')
- const incomingEdges = currentnode.incomers('edge')
-
- //currentnode.removeClass('success-highlight failure-highlight executing-highlight')
- switch (item.status) {
- case "EXECUTING":
- currentnode.removeClass('not-executing-highlight')
- currentnode.removeClass('success-highlight')
- currentnode.removeClass('failure-highlight')
- currentnode.removeClass('shuffle-hover-highlight')
- currentnode.removeClass('awaiting-data-highlight')
- incomingEdges.addClass('success-highlight')
- currentnode.addClass('executing-highlight')
- break
- case "SKIPPED":
- currentnode.removeClass('not-executing-highlight')
- currentnode.removeClass('success-highlight')
- currentnode.removeClass('failure-highlight')
- currentnode.removeClass('shuffle-hover-highlight')
- currentnode.removeClass('awaiting-data-highlight')
- currentnode.removeClass('executing-highlight')
- currentnode.addClass('skipped-highlight')
- break
- case "WAITING":
- currentnode.removeClass('not-executing-highlight')
- currentnode.removeClass('success-highlight')
- currentnode.removeClass('failure-highlight')
- currentnode.removeClass('shuffle-hover-highlight')
- currentnode.removeClass('awaiting-data-highlight')
- currentnode.addClass('executing-highlight')
-
- if (!visited.includes(item.action.label)) {
- if (executionRunning) {
- //alert.show("WAITING FOR "+item.action.label+" with result "+item.result)
- visited.push(item.action.label)
- setVisited(visited)
- }
- }
-
- // FIXME - add outgoing nodes to executing
- //const outgoingNodes = outgoingEdges.find().data().target
- if (outgoingEdges.length > 0) {
- outgoingEdges.addClass('success-highlight')
- }
- break
- case "SUCCESS":
- currentnode.removeClass('not-executing-highlight')
- currentnode.removeClass('executing-highlight')
- currentnode.removeClass('failure-highlight')
- currentnode.removeClass('shuffle-hover-highlight')
- currentnode.removeClass('awaiting-data-highlight')
- currentnode.addClass('success-highlight')
- incomingEdges.addClass('success-highlight')
- outgoingEdges.addClass('success-highlight')
-
- if (visited !== undefined && visited !== null && !visited.includes(item.action.label)) {
- if (executionRunning) {
- //alert.show("Success in node "+item.action.label)
- //+" with result "+item.result)
- visited.push(item.action.label)
- setVisited(visited)
- }
- }
-
- // FIXME - add outgoing nodes to executing
- //const outgoingNodes = outgoingEdges.find().data().target
- if (outgoingEdges.length > 0) {
- for (var i = 0; i < outgoingEdges.length; i++) {
- const edge = outgoingEdges[i]
- const targetnode = cy.getElementById(edge.data().target)
- if (targetnode !== undefined && !targetnode.classes().includes("success-highlight") && !targetnode.classes().includes("failure-highlight")) {
- targetnode.removeClass('not-executing-highlight')
- targetnode.removeClass('success-highlight')
- targetnode.removeClass('shuffle-hover-highlight')
- targetnode.removeClass('failure-highlight')
- targetnode.removeClass('awaiting-data-highlight')
- targetnode.addClass('executing-highlight')
- }
- }
-
- // const outgoingEdges = currentnode.outgoers('edge')
- }
- break
- case "FAILURE":
- //When status comes as failure, allow user to start workflow execution
- if (executionRunning) {
- setExecutionRunning(false)
- }
-
- currentnode.removeClass('not-executing-highlight')
- currentnode.removeClass('executing-highlight')
- currentnode.removeClass('success-highlight')
- currentnode.removeClass('awaiting-data-highlight')
- currentnode.removeClass('shuffle-hover-highlight')
- currentnode.addClass('failure-highlight')
-
- if (!visited.includes(item.action.label)) {
- if (item.action.result !== undefined && item.action.result !== null && !item.action.result.includes("failed condition")) {
- alert.error("Error for "+item.action.label+" with result "+item.result)
- }
- visited.push(item.action.label)
- setVisited(visited)
- }
- break
- case "AWAITING_DATA":
- currentnode.removeClass('not-executing-highlight')
- currentnode.removeClass('executing-highlight')
- currentnode.removeClass('success-highlight')
- currentnode.removeClass('failure-highlight')
- currentnode.removeClass('shuffle-hover-highlight')
- currentnode.addClass('awaiting-data-highlight')
- break
- default:
- console.log("DEFAULT?")
- break
- }
- }
- }
-
- if (responseJson.status === "ABORTED" || responseJson.status === "STOPPED" || responseJson.status === "FAILURE" || responseJson.status == "WAITING") {
- stop()
-
- if (executionRunning) {
- setExecutionRunning(false)
- }
-
- var curelements = cy.elements()
- for (var i = 0; i < curelements.length; i++) {
- if (curelements[i].classes().includes("executing-highlight")) {
- curelements[i].removeClass("executing-highlight")
- curelements[i].addClass("failure-highlight")
- } else {
- //curelements[i].removeClass('not-executing-highlight')
- //curelements[i].removeClass('executing-highlight')
- //curelements[i].removeClass('success-highlight')
- //curelements[i].removeClass('awaiting-data-highlight')
- //curelements[i].removeClass('failure-highlight')
- }
- }
-
- getWorkflowExecution(props.match.params.key, "")
- } else if (responseJson.status === "FINISHED") {
- //console.log("STOPPING BECAUSE ITS OVAH!")
- setExecutionRunning(false)
- stop()
- getWorkflowExecution(props.match.params.key, "")
- setUpdate(Math.random())
- } else {
- //console.log("Nothing to update")
- }
- }
-
- const saveWorkflow = (curworkflow, executionArgument, startNode) => {
- var success = false
-
-
- if (isCloud && !isLoggedIn) {
- console.log("Should redirect to register with redirect.")
- window.location.href = `/register?view=/workflows/${props.match.params.key}&message=You need sign up to use workflows with Shuffle`
- return
- }
-
- setSavingState(2)
-
- // This might not be the right course of action, but seems logical, as items could be running already
- // Makes it possible to update with a version in current render
- stop()
- var useworkflow = workflow
- if (curworkflow !== undefined) {
- useworkflow = curworkflow
- } else {
- //alert.info("Saving workflow")
- }
-
- var cyelements = cy.elements()
- var newActions = []
- var newTriggers = []
- var newBranches = []
- var newVBranches = []
- for (var key in cyelements) {
- if (cyelements[key].data === undefined) {
- continue
- }
-
- var type = cyelements[key].data()["type"]
- if (type === undefined) {
- if (cyelements[key].data().source === undefined ||
- cyelements[key].data().target === undefined) {
- continue
- }
-
- var parsedElement = {
- id: cyelements[key].data().id,
- source_id: cyelements[key].data().source,
- destination_id: cyelements[key].data().target,
- conditions: cyelements[key].data().conditions,
- decorator: cyelements[key].data().decorator,
- }
-
- if (parsedElement.decorator) {
- newVBranches.push(parsedElement)
- } else {
- newBranches.push(parsedElement)
- }
- } else {
- if (type === "ACTION") {
- const cyelement = cyelements[key].data()
- const elementid = cyelement.id === undefined || cyelement.id === null ? cyelement["_id"] : cyelement.id
-
- var curworkflowAction = useworkflow.actions.find(a => a !== undefined && (a["id"] === elementid || a["_id"] === elementid))
- if (curworkflowAction === undefined) {
- curworkflowAction = cyelements[key].data()
- }
-
- curworkflowAction.position = cyelements[key].position()
-
- // workaround to fix some edgecases
- if (curworkflowAction.parameters === "" || curworkflowAction.parameters === null) {
- curworkflowAction.parameters = []
+ console.log("RESP: ", responseJson)
+ if (Object.getOwnPropertyNames(creatorProfile).length === 0) {
+ //getUserProfile("frikky")
+ getUserProfile(responseJson.id)
}
- if (curworkflowAction.example === undefined || curworkflowAction.example === "" || curworkflowAction.example === null) {
- if (cyelements[key].data().example !== undefined) {
- curworkflowAction.example = cyelements[key].data().example
- }
- }
-
- // Override just in this place
- curworkflowAction.errors = []
- curworkflowAction.isValid = true
-
- // Cleans up OpenAPI items
- var newparams = []
- for (var key in curworkflowAction.parameters) {
- const thisitem = curworkflowAction.parameters[key]
- if (thisitem.name.startsWith("${") && thisitem.name.endsWith("}")) {
+ //{appGroup.map((data, index) => {
+ //const [appGroup, setAppGroup] = React.useState([]);
+ var appsFound = []
+ for (var key in responseJson.actions) {
+ const parsedAction = responseJson.actions[key]
+ if (parsedAction.large_image === undefined || parsedAction.large_image === null || parsedAction.large_image === "") {
continue
}
-
- newparams.push(thisitem)
- }
-
- curworkflowAction.parameters = newparams
- newActions.push(curworkflowAction)
- } else if (type === "TRIGGER") {
- //console.log("TRIGGER")
- var curworkflowTrigger = useworkflow.triggers.find(a => a.id === cyelements[key].data()["id"])
- if (curworkflowTrigger === undefined) {
- curworkflowTrigger = cyelements[key].data()
- }
-
- curworkflowTrigger.position = cyelements[key].position()
- //console.log(curworkflowTrigger)
-
- newTriggers.push(curworkflowTrigger)
- }
- }
- }
-
- useworkflow.actions = newActions
- useworkflow.triggers = newTriggers
- useworkflow.branches = newBranches
- useworkflow.visual_branches = newVBranches
-
- // Errors are backend defined
- useworkflow.errors = []
- useworkflow.previously_saved = true
-
- setLastSaved(true)
- fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key, {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- },
- body: JSON.stringify(useworkflow),
- credentials: "include",
- })
- .then((response) => {
- setSavingState(0)
- if (response.status !== 200) {
- console.log("Status not 200 for setting workflows :O!")
- }
-
- return response.json()
- })
- .then((responseJson) => {
- if (executionArgument !== undefined && startNode !== undefined) {
- console.log("Running execution AFTER saving")
- executeWorkflow(executionArgument, startNode, true)
- return
- }
-
- if (!responseJson.success) {
- console.log(responseJson)
- alert.error("Failed to save: "+responseJson.reason)
- } else {
- if (responseJson.new_id !== undefined && responseJson.new_id !== null) {
- window.location.pathname = "/workflows/"+responseJson.new_id
- }
-
- success = true
- if (responseJson.errors !== undefined) {
- //console.log(responseJson)
- workflow.errors = responseJson.errors
- if (responseJson.errors.length === 0) {
- workflow.isValid = true
- workflow.is_valid = true
-
- //console.log("ELEMENTS: ", cy.elements())
- //const setupGraph = () => {
- const cyelements = cy.elements()
- for (var i = 0; i < cyelements.length; i++) {
- cyelements[i].removeStyle()
- cyelements[i].data().is_valid = true
- cyelements[i].data().errors = []
- }
-
- for (var key in workflow.actions) {
- workflow.actions[key].is_valid = true
- workflow.actions[key].errors = []
+ if (appsFound.findIndex(data => data.app_name === parsedAction.app_name) < 0){
+ appsFound.push(parsedAction)
}
}
- for (var key in workflow.errors) {
- //console.log("Error: ", workflow.errors[key])
- alert.info(workflow.errors[key])
+ setAppGroup(appsFound)
+
+ appsFound = []
+ for (var key in responseJson.triggers) {
+ const parsedAction = responseJson.triggers[key]
+ if (appsFound.findIndex(data => data.app_name === parsedAction.app_name) < 0){
+ appsFound.push(parsedAction)
+ }
}
- setWorkflow(workflow)
- }
+ setTriggerGroup(appsFound)
+ }
- //alert.success("Successfully saved workflow")
- setSavingState(1)
- setTimeout(() => {
- setSavingState(0)
- }, 1500);
- }
- })
- .catch(error => {
- setSavingState(0)
- alert.error(error.toString())
- });
- return success
- }
+ // Appends SUBFLOWS. Does NOT run during normal grabbing of workflows.
+ if (sourcenode.id !== undefined) {
+ console.log("WORKFLOW: ", responseJson);
- const monitorUpdates = () => {
- var firstnode = cy.getElementById(workflow.start)
- if (firstnode.length === 0) {
- var found = false
- for (var key in workflow.actions) {
- if (workflow.actions[key].isStartNode) {
- console.log("Updating startnode")
- workflow.start = workflow.actions[key].id
- firstnode = cy.getElementById(workflow.actions[key].id)
- found = true
- break
- }
- }
+ var nodefound = false;
+ const target = sourcenode.parameters.find(
+ (item) => item.name === "startnode"
+ );
+ console.log(sourcenode.parameters);
+ console.log(target);
+ const target_id = target === undefined ? "" : target.value;
+ const actions = responseJson.actions.map((action) => {
+ const node = {
+ group: "nodes",
+ };
- if (!found) {
- return false
- }
- }
+ // Set it dynamically?
+ node.position = {
+ x: sourcenode.position.x + action.position.x,
+ y: sourcenode.position.y + action.position.y,
+ };
- cy.elements().removeClass('success-highlight failure-highlight executing-highlight')
- firstnode[0].addClass('executing-highlight')
+ node.data = action;
- return true
- }
+ node.data._id = action["id"];
+ node.data.type = "ACTION";
+ node.data.source_workflow = responseJson.id;
+ if (action.id === target_id) {
+ nodefound = true;
+ }
- const executeWorkflow = (executionArgument, startNode, hasSaved) => {
- if (hasSaved === false) {
- //alert.error("You might have forgotten to save before executing.")
- //const saveWorkflow = (curworkflow) => {
- //setExecutionRunning(true)
- setExecutionRequestStarted(true)
- saveWorkflow(workflow, executionArgument, startNode)
- console.log("FIXME: Might have forgotten to save before executing.")
- return
- }
+ var example = "";
+ if (
+ action.example !== undefined &&
+ action.example !== null &&
+ action.example.length > 0
+ ) {
+ example = action.example;
+ }
- if (workflow.public) {
- alert.info("Save it to get a new version")
- }
+ node.data.example = example;
+ return node;
+ });
- var returncheck = monitorUpdates()
- if (!returncheck) {
- alert.error("No startnode set.")
- return
- }
+ var edges = responseJson.branches.map((branch, index) => {
+ const edge = {};
+ var conditions = responseJson.branches[index].conditions;
+ if (conditions === undefined || conditions === null) {
+ conditions = [];
+ }
- setVisited([])
- setExecutionRequest({})
- stop()
+ var label = "";
+ if (conditions.length === 1) {
+ label = conditions.length + " condition";
+ } else if (conditions.length > 1) {
+ label = conditions.length + " conditions";
+ }
- var curelements = cy.elements()
- for (var i = 0; i < curelements.length; i++) {
- curelements[i].addClass("not-executing-highlight")
- }
+ const sourceFound = actions.findIndex(
+ (action) => action.data.id === branch.source_id
+ );
+ if (sourceFound < 0) {
+ return null;
+ }
- if (executionArgument !== undefined && executionArgument !== null && executionArgument.length > 0) {
- //alert.success("Starting execution WITH an execution argument")
- } else {
- //alert.success("Starting execution")
- }
+ const destinationFound = actions.findIndex(
+ (action) => action.data.id === branch.destination_id
+ );
+ if (destinationFound < 0) {
+ return null;
+ }
- const data = {"execution_argument": executionArgument, "start": startNode}
- fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key+"/execute", {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- },
- credentials: "include",
- body: JSON.stringify(data),
- })
- .then((response) => {
- if (response.status !== 200) {
- console.log("Status not 200 for WORKFLOW EXECUTION :O!")
- }
+ edge.data = {
+ id: branch.id,
+ _id: branch.id,
+ source: branch.source_id,
+ target: branch.destination_id,
+ label: label,
+ conditions: conditions,
+ hasErrors: branch.has_errors,
+ decorator: false,
+ source_workflow: responseJson.id,
+ };
- return response.json()
- })
- .then((responseJson) => {
- if (!responseJson.success) {
- alert.error("Failed to start: "+responseJson.reason)
- setExecutionRunning(false)
- setExecutionRequestStarted(false)
- stop()
+ return edge;
+ });
- for (var i = 0; i < curelements.length; i++) {
- curelements[i].removeClass("not-executing-highlight")
- }
- return
- } else {
- setExecutionRunning(true)
- setExecutionRequestStarted(false)
- }
+ edges = edges.filter((edge) => edge !== null);
+ cy.removeListener("add");
+ cy.add(actions);
+ cy.add(edges);
- if (responseJson.execution_id === "" || responseJson.execution_id === undefined || responseJson.authorization === "" || responseJson.authorization === undefined ) {
- alert.error("Something went wrong during execution startup")
- console.log("BAD RESPONSE FOR EXECUTION: ", responseJson)
- setExecutionRunning(false)
- setExecutionRequestStarted(false)
- stop()
+ if (nodefound === true) {
+ const newId = uuidv4();
+ cy.add({
+ group: "edges",
+ data: {
+ id: newId,
+ _id: newId,
+ source: sourcenode.id,
+ target: target_id,
+ label: "Subflow",
+ decorator: true,
+ source_workflow: responseJson.id,
+ },
+ });
+ }
- for (var i = 0; i < curelements.length; i++) {
- curelements[i].removeClass("not-executing-highlight")
- }
- return
- }
+ cy.fit(null, 100);
+ cy.on("add", "node", (e) => onNodeAdded(e));
+ cy.on("add", "edge", (e) => onEdgeAdded(e));
+ } else {
+ setWorkflow(responseJson);
+ setWorkflowDone(true);
- setExecutionRequest({
- "execution_id": responseJson.execution_id,
- "authorization": responseJson.authorization,
- })
- setExecutingNodes([workflow.start])
- setExecutionData({})
- setExecutionModalOpen(true)
- setExecutionModalView(1)
- start()
- })
- .catch(error => {
- alert.error(error.toString())
- });
- }
+ // Add error checks
+ console.log("Workflow: ", responseJson);
+ if (!responseJson.public) {
+ if (
+ !responseJson.previously_saved ||
+ !responseJson.is_valid ||
+ responseJson.errors !== undefined ||
+ responseJson.errors !== null ||
+ responseJson.errors !== // what
+ responseJson.errors.length > 0
+ ) {
+ setConfigureWorkflowModalOpen(true);
+ }
+ }
+ }
+ })
+ .catch((error) => {
+ alert.error(error.toString());
+ });
+ };
- //const handleAppVersioning = (apps) => {
- // var newapps = []
- // for (var key in apps) {
- // var item = apps[key]
- // const previtem = newapps.findIndex(data => data.name === item.name)
- // if (previtem === -1) {
- // item["versions"] = [item.app_version]
- // newapps.push(item)
- // continue
- // }
+ const onUnselect = (event) => {
+ const nodedata = event.target.data();
+ console.log("UNSELECT: ", nodedata);
+ //if (nodedata.type === "ACTION") {
+ // setLastSelected(nodedata)
+ //}
+ //
- // // THere might be duplicates for some reason..
- // if (!newapps[previtem]["versions"].includes(item.app_version)) {
- // newapps[previtem]["versions"].push(item.app_version)
- // }
- // }
- //
- // // FIXME - handle this, as we can't have more than one of each :)
- // //setVersionedApps(newapps)
- //}
-
- // Builtin actions that should ran in Worker and not apps
- const getExtraApps = () => {
- const data = [{
- name: "Filter",
- is_valid: true,
- id: "0ca8887e-b4af-4e3e-887c-87e9d3bc3d3e",
- link: "https://shuffler.io",
- app_version: "1.0.0",
- generated: true,
- downloaded: false,
- sharing: false,
- verified: false,
- tested: false,
- owner: "",
- private_id: "",
- description: "Filter",
- environment: "Shuffle",
- small_image: "",
- large_image: "",
- contact_info: {name: "", url: ""},
- authentication: {required: false, parameters: [],},
- actions: [{
- description: "Filter cases",
- id: "",
- name: "filter_cases",
- node_type: "action",
- environment: "Shuffle",
- sharing: false,
- private_id: "",
- app_id: "",
- authentication: null,
- tested: true,
- parameters: [{
- description: "",
- id: "",
- name: "Field to look for",
- example: "$testing_1.#.id",
- value: "",
- multiline: true,
- action_field: "",
- variant: "",
- required: true,
- schema: {type: "string"},
- }]
- }],
- }]
-
- return data
- }
-
- // This can be used to only show prioritzed ones later
- // Right now, it can prioritize authenticated ones
- //"Testing",
- const internalIds = [
- "Shuffle Tools",
- "http",
- ]
-
- const getAppAuthentication = (reset, updateAction) => {
- fetch(globalUrl+"/api/v1/apps/authentication", {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- },
- credentials: "include",
- })
- .then((response) => {
- if (response.status !== 200) {
- console.log("Status not 200 for apps :O!")
- }
-
- return response.json()
- })
- .then((responseJson) => {
- if (responseJson.success) {
- var newauth = []
- for (var key in responseJson.data) {
- if (responseJson.data[key].defined === false) {
- continue
- }
-
- newauth.push(responseJson.data[key])
- }
-
- //{selectedAction.authentication.map(data => {
-
- if (cy !== undefined) {
- console.log("NEW AUTH = reset cy's onnodeselect")
-
- // Remove the old listener for select, run with new one
- cy.removeListener('select')
- cy.on('select', 'node', (e) => onNodeSelect(e, newauth))
- cy.on('select', 'edge', (e) => onEdgeSelect(e))
- }
-
- setAppAuthentication(newauth)
- setAuthLoaded(true)
-
- if (updateAction === true) {
- //console.log("Should update authentication for selectedAction!!")
- //console.log(responseJson)
-
- if (selectedApp.authentication.required) {
- //console.log("App requires auth!!")
- // Setup auth here :)
- var appUpdates = false
- const authenticationOptions = []
- var findAuthId = ""
- if (selectedAction.authentication_id !== null && selectedAction.authentication_id !== undefined && selectedAction.authentication_id.length > 0) {
- findAuthId = selectedAction.authentication_id
- }
-
- var tmpAuth = JSON.parse(JSON.stringify(responseJson.data))
- //console.log("FOUND AUTH: ", tmpAuth)
-
- //console.log("Checking authentication: ", tmpAuth)
- var latest = 0
- for (var key in tmpAuth) {
- var item = tmpAuth[key]
-
- const newfields = {}
- for (var filterkey in item.fields) {
- newfields[item.fields[filterkey].key] = item.fields[filterkey].value
- }
-
- item.fields = newfields
- if (item.app.name === selectedApp.name) {
- authenticationOptions.push(item)
-
- // Always becoming the last one
- if (item.edited > latest) {
- latest = item.edited
- selectedAction.selectedAuthentication = item
-
- for (var key in workflow.actions) {
- console.log(workflow.actions[key].app_name)
- if (workflow.actions[key].app_name == selectedApp.name) {
- console.log("Setting auth at: ", workflow.actions[key], item.id)
- workflow.actions[key].selectedAuthentication = item
- workflow.actions[key].authentication_id = item.id
- appUpdates = true
- //if (workflow.actions[key].selectedAuthentication === undefined || workflow.actions[key].selectedAuthentication === null || workflow.actions[key].selectedAuthentication.length === 0) {
- // console.log("Setting inner auth: ", workflow.actions[key])
- //}
- }
- }
- }
- }
- }
-
- //if (item.id === findAuthId) {
- // selectedAction.selectedAuthentication = item
- //}
- //console.log("ACTION: ", selectedAction)
-
- //console.log("OPTIONS: ", authenticationOptions)
- selectedAction.authentication = authenticationOptions
- //console.log("Authentication: ", authenticationOptions)
- if (selectedAction.selectedAuthentication === null || selectedAction.selectedAuthentication === undefined || selectedAction.selectedAuthentication.length === "") {
- selectedAction.selectedAuthentication = {}
- }
+ // Wait for new node to possibly be selected
+ //setTimeout(() => {
+ const typeIds = cy.elements('node:selected').jsons();
+ console.log("Found: ", typeIds)
+ for (var idkey in typeIds) {
+ const item = typeIds[idkey]
+ console.log("items: ", item)
+ if (item.data.isButton === true) {
+ console.log("Reselect old node & return - or just return?")
-
- if (appUpdates === true) {
- setAuthenticationModalOpen(false)
- setSelectedAction(selectedAction)
- setWorkflow(workflow)
- saveWorkflow(workflow)
- alert.info("Added and updated authentication!")
- } else {
- alert.error("Failed to find new authentication - did it work?")
- }
- } else {
- alert.info("No authentication to update")
- }
+ if (item.data.buttonType === "delete" && item.data.attachedTo === nodedata.id) {
+ console.log("delete of same node!")
}
- } else {
- setAuthLoaded(true)
- //alert.error("Failed getting authentications")
+ return
}
- })
- .catch(error => {
- setAuthLoaded(true)
- alert.error("Auth loading error: "+error.toString())
- })
- }
-
-
- const getApps = () => {
- fetch(globalUrl+"/api/v1/apps", {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- },
- credentials: "include",
- })
- .then((response) => {
- if (response.status !== 200) {
- console.log("Status not 200 for apps :O!")
- }
-
- return response.json()
- })
- .then((responseJson) => {
- // FIXME - handle versions on left bar
- //handleAppVersioning(responseJson)
- //var tmpapps = []
- //tmpapps = tmpapps.concat(getExtraApps())
- //tmpapps = tmpapps.concat(responseJson)
- console.log("APPS: ", responseJson)
- setApps(responseJson)
-
- if (isCloud) {
- setFilteredApps(responseJson.filter(app => !internalIds.includes(app.name)))
- setPrioritizedApps(responseJson.filter(app => internalIds.includes(app.name)))
- } else {
- setFilteredApps(responseJson.filter(app => !internalIds.includes(app.name) && !(!app.activated && app.generated)))
- setPrioritizedApps(responseJson.filter(app => internalIds.includes(app.name)))
- }
- })
- .catch(error => {
- alert.error(error.toString())
- });
- }
-
- const getWorkflow = (workflow_id, sourcenode) => {
- console.log(`Getting workflow ${workflow_id} with append value ${sourcenode}`)
-
- fetch(globalUrl+"/api/v1/workflows/"+workflow_id, {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- },
- credentials: "include",
- })
- .then((response) => {
- if (response.status !== 200) {
- console.log("Status not 200 for workflows :O!")
- window.location.pathname = "/workflows"
- }
-
- return response.json()
- })
- .then((responseJson) => {
- // Not sure why this is necessary.
- if (responseJson.isValid === undefined) {
- responseJson.isValid = true
- }
-
- if (responseJson.errors === undefined) {
- responseJson.errors = []
- }
-
- if (responseJson.public) {
- alert.info("This workflow is public. You will have to save it to make it your own!")
- //setLastSaved(false)
- }
-
- // Appends SUBFLOWS. Does NOT run during normal grabbing of workflows.
- if (sourcenode.id !== undefined) {
- console.log("WORKFLOW: ", responseJson)
-
- var nodefound = false
- const target = sourcenode.parameters.find(item => item.name === "startnode")
- console.log(sourcenode.parameters)
- console.log(target)
- const target_id = target === undefined ? "" : target.value
- const actions = responseJson.actions.map(action => {
- const node = {
- group: "nodes",
- }
-
- // Set it dynamically?
- node.position = {
- x: sourcenode.position.x+action.position.x,
- y: sourcenode.position.y+action.position.y,
- }
-
- node.data = action
-
- node.data._id = action["id"]
- node.data.type = "ACTION"
- node.data.source_workflow = responseJson.id
- if (action.id === target_id) {
- nodefound = true
- }
-
- var example = ""
- if (action.example !== undefined && action.example !== null && action.example.length > 0) {
- example = action.example
- }
-
- node.data.example = example
- return node;
- })
-
- var edges = responseJson.branches.map((branch, index) => {
- const edge = { };
- var conditions = responseJson.branches[index].conditions
- if (conditions === undefined || conditions === null) {
- conditions = []
- }
-
- var label = ""
- if (conditions.length === 1) {
- label = conditions.length+" condition"
- } else if (conditions.length > 1) {
- label = conditions.length+" conditions"
- }
-
- const sourceFound = actions.findIndex(action => action.data.id === branch.source_id)
- if (sourceFound < 0) {
- return null
- }
-
- const destinationFound = actions.findIndex(action => action.data.id === branch.destination_id)
- if (destinationFound < 0) {
- return null
- }
-
- edge.data = {
- id: branch.id,
- _id: branch.id,
- source: branch.source_id,
- target: branch.destination_id,
- label: label,
- conditions: conditions,
- hasErrors: branch.has_errors,
- decorator: false,
- source_workflow: responseJson.id,
- }
-
- return edge;
- })
-
-
- //console.log("Adding node: ", node)
- //cy.on('add', 'node', (e) => onNodeAdded(e))
- edges = edges.filter(edge => edge !== null)
- cy.removeListener('add')
- cy.add(actions)
- cy.add(edges)
-
- if (nodefound === true) {
- const newId = uuidv4()
- cy.add({
- group: "edges",
- data: {
- id: newId,
- _id: newId,
- source: sourcenode.id,
- target: target_id,
- label: "Subflow",
- decorator: true,
- source_workflow: responseJson.id,
- }
- })
- }
-
- cy.fit(null, 100)
- //cy.zoom(2.0)
- cy.on('add', 'node', (e) => onNodeAdded(e))
- cy.on('add', 'edge', (e) => onEdgeAdded(e))
-
- //for (var key in
- } else {
- setWorkflow(responseJson)
- setWorkflowDone(true)
-
- //console.log(responseJson)
- // Add error checks
- console.log("Workflow: ", responseJson)
- if (!responseJson.public) {
- if ((!responseJson.previously_saved || (!responseJson.is_valid || (responseJson.errors !== undefined || responseJson.errors !== null || responseJson.errors !== responseJson.errors.length > 0)))) {
- setConfigureWorkflowModalOpen(true)
- }
- }
- }
- })
- .catch(error => {
- alert.error(error.toString())
- });
- }
-
- const onUnselect = (event) => {
- console.time("UNSELECT")
-
- const nodedata = event.target.data()
- if (nodedata.app_name === undefined && nodedata.source === undefined) {
- return
}
- event.target.removeClass("selected")
+ //if (nodedata.app_name === undefined && nodedata.source === undefined) {
+ // return;
+ //}
+ //event.target.removeClass("selected");
+ //
+
+
+
+
+
+ //// If button is clicked, select current node
// Attempt at rewrite of name in other actions in following nodes.
// Should probably be done in the onBlur for the textfield instead
@@ -1554,1029 +1893,1202 @@ const AngularWorkflow = (props) => {
}
*/
+ //cy.removeListener("select");
+ //cy.on("select", "node", (e) => onNodeSelect(e, appAuthentication));
+ //cy.on("select", "edge", (e) => onEdgeSelect(e));
+
+
// FIXME - check if they have value before overriding like this for no reason.
// Would save a lot of time (400~ ms -> 30ms)
//console.log("ACTION: ", selectedAction)
//console.log("APP: ", selectedApp)
- setSelectedAction({})
- setSelectedApp({})
- setSelectedTrigger({})
- //setSelectedEdge({})
- // setSelectedTriggerIndex(-1)
- //setSelectedActionEnvironment({})
- setSelectedEdge({})
- //setTriggerAuthentication({})
- //setSelectedTriggerIndex(-1)
- //setTriggerFolders([])
+ ReactDOM.unstable_batchedUpdates(() => {
+ setSelectedAction({});
+ setSelectedApp({});
+ setSelectedTrigger({});
+ setSelectedComment({})
+ setSelectedEdge({});
- // Can be used for right side view
- setRightSideBarOpen(false)
- setScrollConfig({
- top: 0,
- left: 0,
- selected: "",
+ setSelectedEdge({})
+ setSelectedActionEnvironment({})
+ setTriggerAuthentication({})
+ setSelectedTriggerIndex(-1)
+ setTriggerFolders([])
+ setSubworkflow({})
+
+ // Can be used for right side view
+ setRightSideBarOpen(false);
+ setScrollConfig({
+ top: 0,
+ left: 0,
+ selected: "",
+ });
+ //console.timeEnd("UNSELECT");
})
- console.timeEnd("UNSELECT")
- }
+ //}, 150)
+ };
- const onEdgeSelect = (event) => {
- setRightSideBarOpen(true)
- setLastSaved(false)
+ const onEdgeSelect = (event) => {
+ ReactDOM.unstable_batchedUpdates(() => {
+ setRightSideBarOpen(true);
+ setLastSaved(false);
- /*
- // Used to not be able to edit trigger-based branches.
- const triggercheck = workflow.triggers.find(trigger => trigger.id === event.target.data()["source"])
- if (triggercheck === undefined) {
- */
- //console.log(event.target.data())
- if (event.target.data().decorator) {
- alert.info("This edge can't be edited.")
- } else {
- setSelectedEdgeIndex(workflow.branches.findIndex(data => data.id === event.target.data()["id"]))
- setSelectedEdge(event.target.data())
- }
+ /*
+ // Used to not be able to edit trigger-based branches.
+ const triggercheck = workflow.triggers.find(trigger => trigger.id === event.target.data()["source"])
+ if (triggercheck === undefined) {
+ */
+ if (
+ event.target.data("type") !== "COMMENT" &&
+ event.target.data().decorator
+ ) {
+ alert.info("This edge can't be edited.");
+ } else {
+ //console.log("DATA: ", event.target.data())
+ const destinationId = event.target.data("target");
+ //console.log("DATA: ", event.target.data())
+ const curaction = workflow.actions.find((a) => a.id === destinationId);
+ //console.log("ACTION: ", curaction)
+ if (curaction !== undefined && curaction !== null) {
+ if (
+ curaction.app_name === "Shuffle Tools" &&
+ curaction.name === "router"
+ ) {
+ alert.info("Router action can't have incoming conditions");
+ event.target.unselect();
+ return;
+ }
+ }
- /*
- } else {
- //alert.info("Can't edit branches from triggers")
- console.log("IN HERE: !", triggercheck)
- }
- */
+ setSelectedEdgeIndex(
+ workflow.branches.findIndex(
+ (data) => data.id === event.target.data()["id"]
+ )
+ );
+ setSelectedEdge(event.target.data());
+ }
- setSelectedAction({})
- setSelectedTrigger({})
- }
-
- // Comparing locations between nodes and setting views
- var styledElements = []
- var originalLocation = {
- x: 0,
- y: 0,
- }
-
-
- const onCtxTap = (event) => {
- const nodedata = event.target.data()
- console.log(nodedata)
- if (nodedata.type === "TRIGGER" && nodedata.app_name === "Shuffle Workflow") {
- if (nodedata.parameters === null) {
- alert.error("Set a workflow first")
- return
- }
-
- const workflow_id = nodedata.parameters.find(param => param.name === "workflow")
- if (workflow.id === workflow_id.valu) {
- return
- }
-
- cy.animation({
- zoom: 0,
- center: {
- eles: event.target,
- },
- })
- .play()
- .promise()
- .then( () => {
- console.log("DONE: ", workflow_id)
- //cy.elements().remove()
- getWorkflow(workflow_id.value, nodedata)
- cy.fit(null, 50)
- //props.match.params.key
- //cy.animation({
- // zoom: 500,
- // center: {
- // eles: cy.nodes(),
- // },
- //})
- //.play()
- //.promise()
- })
-
- //const animationDuration = 150
- //style: {
- //maxZoom={2.00}
- //cy.animate({
- // fit: 100,
- //}, {
- // duration: 500,
- //})
- }
- }
- //cy.on('cxttap', "node", (e) => onCtxTap(e))
-
- var hiddenNodes = []
- const onNodeDragStop = (event, selectedAction) => {
- const nodedata = event.target.data()
- //console.log("IN NODE DRAG STOP: ", nodedata)
- if (nodedata.id === selectedAction.id) {
- return
- }
-
- if (nodedata.finished === false) {
- return
- }
-
- //console.log("Drag: ", nodedata)
- //return
-
- //console.log("DRAGGED NODE: ", nodedata)
- //console.log("TARGET NODE: ", selectedAction)
- if (styledElements.length === 1) {
- console.log("Should reset location and autofill: ", styledElements, selectedAction)
- //event.target.position = originalLocation
- //curworkflowTrigger.position = cyelements[key].position()
- if (originalLocation.x !== 0 || originalLocation.y !== 0) {
- const currentnode = cy.getElementById(nodedata.id)
- if (currentnode !== null && currentnode !== undefined) {
- //currentnode.position = originalLocation
- currentnode.position("x", originalLocation.x)
- currentnode.position("y", originalLocation.y)
- }
-
- originalLocation = {x: 0, y: 0}
- }
-
- const curElement = document.getElementById(styledElements[0])
- if (curElement !== null && curElement !== undefined) {
- //console.log("ELE: ", curElement)
- curElement.style.border = curElement.style.original_border
- var newValue = "$"+nodedata.label.toLowerCase().replaceAll(" ", "_")
- var paramname = ""
- var idnumber = -1
- if (curElement.id.startsWith("rightside_field_")) {
- console.log("FOUND FIELD WITH NUMBER: ", curElement.id)
- const idsplit = curElement.id.split("_")
- console.log(idsplit)
- if (idsplit.length === 3 && !isNaN(idsplit[2])) {
- console.log("ADDING TO PARAM ", idsplit[2])
- console.log("PARAM: ", selectedAction)
-
- selectedAction.parameters[idsplit[2]].value = newValue
- paramname = selectedAction.parameters[idsplit[2]].name
- idnumber = idsplit[2]
- }
- }
-
- if (idnumber >= 0 && paramname.length > 0) {
- const exampledata = GetExampleResult(nodedata)
- const parsedname = paramname.toLowerCase().trim().replaceAll("_", " ")
-
- const foundresult = GetParamMatch(parsedname, exampledata, "")
- if (foundresult.length > 0) {
- console.log("FOUND RESULT: ", paramname, foundresult)
- newValue = `${newValue}${foundresult}`
- }
-
- selectedAction.parameters[idnumber].value = newValue
- }
-
- curElement.value = newValue
- }
- }
-
- const skipnames = []
- if (nodedata.app_name !== undefined && ((
- nodedata.app_name !== "Shuffle Tools" &&
- nodedata.app_name !== "Testing" &&
- nodedata.app_name !== "Shuffle Workflow" &&
- nodedata.app_name !== "User Input" &&
- nodedata.app_name !== "Webhook" &&
- nodedata.app_name !== "Schedule" &&
- nodedata.app_name !== "Email") || nodedata.isStartNode)
- ) {
- const allNodes = cy.nodes().jsons()
- var found = false
- for (var key in allNodes) {
- const currentNode = allNodes[key]
- if (currentNode.data.attachedTo === nodedata.id && currentNode.data.isDescriptor) {
- found = true
- console.log("FOUND THE NODE!")
- break
- }
- }
-
- // Readding the icon after moving the node
- if (!found) {
- //console.log("Node wasn't found")
- const iconInfo = GetIconInfo(nodedata)
- const svg_pin = ` `
- const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin)
-
- const offset = nodedata.isStartNode ? 36 : 44
- //console.log(event.target.position())
- const decoratorNode = {
- position: {
- x: event.target.position().x+offset,
- y: event.target.position().y+offset,
- },
- locked: true,
- data: {
- "isDescriptor": true,
- "isValid": true,
- "is_valid": true,
- "label": "",
- "image": svgpin_Url,
- "imageColor": iconInfo.iconBackgroundColor,
- "attachedTo": nodedata.id,
- },
- }
-
- cy.add(decoratorNode).unselectify()
- } else {
- console.log("Node already exists - don't add descriptor node")
- }
- } else {
- //console.log("Shouldnt re-add info? ")
- }
-
- originalLocation = {
- x: 0,
- y: 0,
- }
- }
-
- const onNodeDrag = (event, selectedAction) => {
- //if (Object.getOwnPropertyNames(selectedAction).length === 0) {
- // return
- //}
- const nodedata = event.target.data()
- //console.log("Dragging: ", nodedata)
- if (nodedata.finished === false) {
- return
- }
-
- //console.log("Dragging node!!")
- if (nodedata.app_name == "Shuffle Tools" || nodedata.app_name == "Testing") {
- //console.log("NODE: ",
- //selector: `node[app_name="Shuffle Tools"]`,
- //console.log(event.target)
-
- // 1. Find location of node
- // 2. Check if it's within view of another node (inside)
- // 3. If it is, then hide text
- }
-
- if (nodedata.app_name !== undefined) {
- //console.log("Trying to remove friendly nodes")
- const allNodes = cy.nodes().jsons()
- for (var key in allNodes) {
- const currentNode = allNodes[key]
- if (currentNode.data.attachedTo === nodedata.id) {
- cy.getElementById(currentNode.data.id).remove()
- }
- }
- } else {
- console.log("No appid? ", nodedata)
- }
-
- if (nodedata.id === selectedAction.id) {
- return
- }
-
- if (originalLocation.x === 0 && originalLocation.y === 0 && nodedata.position !== undefined) {
- //console.log("Updating location!: ", nodedata)
- originalLocation.x = nodedata.position.x
- originalLocation.y = nodedata.position.y
- }
-
- // Part of autocomplete. Styles elements in frontend to indicate
- // what and where we may input data for the user.
- const onMouseUpdate = (e) => {
- const x = e.pageX;
- const y = e.pageY;
-
- const elementMouseIsOver = document.elementFromPoint(x, y)
- if (elementMouseIsOver !== undefined && elementMouseIsOver !== null) {
- // Color for #f85a3e translated to rgb
- const newBorder = "3px solid rgb(248, 90, 62)"
- if (elementMouseIsOver.style.border != newBorder && elementMouseIsOver.id.includes("rightside")) {
- //console.log(elementMouseIsOver.style.border)
- if (elementMouseIsOver.style.border !== undefined) {
- elementMouseIsOver.style.original_border = elementMouseIsOver.style.border
- } else {
- elementMouseIsOver.style.original_border = ""
- }
-
- elementMouseIsOver.style.border = newBorder
- console.log("STYLED: ", styledElements)
- for (var key in styledElements) {
- const curElement = document.getElementById(styledElements[key])
- if (curElement !== null && curElement !== undefined) {
- curElement.style.border = curElement.style.original_border
- }
- }
-
- styledElements = []
- styledElements.push(elementMouseIsOver.id)
- } else if (elementMouseIsOver.id === "cytoscape_view" || elementMouseIsOver.id === "") {
- for (var key in styledElements) {
- const curElement = document.getElementById(styledElements[key])
- if (curElement !== null && curElement !== undefined) {
- curElement.style.border = curElement.style.original_border
- }
- }
-
- styledElements = []
- }
- }
-
- // Ensure it only happens once
- document.removeEventListener('mousemove', onMouseUpdate, false)
- }
-
- document.addEventListener('mousemove', onMouseUpdate, false);
-
- /*
- event.target.animate({
- style: {
- "border-width": "12px",
- "border-opacity": ".7",
- }
- }, {
- duration: animationDuration,
+ setSelectedAction({});
+ setSelectedTrigger({});
})
- event.target.animate({
- style: {
- "border-width": "12px",
- "border-opacity": ".7",
- }
- }, {
- duration: animationDuration,
- })
- */
- }
+ };
- // Nodeselectbatching:
- // https://stackoverflow.com/questions/16677856/cy-onselect-callback-only-once
- const onNodeSelect = (event, newAppAuth) => {
- const data = event.target.data()
- //console.log("NEW NODE ID: ", data.id)
- if (data.isButton) {
- //console.log("BUTTON CLICKED: ", data)
- if (data.buttonType === "delete") {
- //console.log("DELETE!")
- const parentNode = cy.getElementById(data.attachedTo)
- if (parentNode !== null && parentNode !== undefined) {
- parentNode.remove()
- }
+ // Comparing locations between nodes and setting views
+ var styledElements = [];
+ var originalLocation = {
+ x: 0,
+ y: 0,
+ };
- //for (var key in allNodes) {
- // const currentNode = allNodes[key]
- // if (currentNode.data.attachedTo === data.attachedTo) {
- // cy.getElementById(currentNode.data.id).remove()
- // }
- //}
- } else if (data.buttonType === "set_startnode" && data.type !== "TRIGGER") {
- const parentNode = cy.getElementById(data.attachedTo)
- if (parentNode !== null && parentNode !== undefined) {
- var oldstartnode = cy.getElementById(workflow.start)
- if (oldstartnode !== null && oldstartnode !== undefined && oldstartnode.length > 0) {
- try {
- oldstartnode[0].data("isStartNode", false)
- } catch (e) {
- console.log("Startnode error: ", e)
- }
- }
+ const onCtxTap = (event) => {
+ const nodedata = event.target.data();
+ console.log(nodedata);
+ if (
+ nodedata.type === "TRIGGER" &&
+ nodedata.app_name === "Shuffle Workflow"
+ ) {
+ if (nodedata.parameters === null) {
+ alert.error("Set a workflow first");
+ return;
+ }
- workflow.start = parentNode.data('id')
- parentNode.data('isStartNode', true)
- }
+ const workflow_id = nodedata.parameters.find(
+ (param) => param.name === "workflow"
+ );
+ if (workflow.id === workflow_id.valu) {
+ return;
+ }
- } else if (data.buttonType === "copy") {
- console.log("COPY!")
- // 1. Find parent
- // 2. Find branches for parent
- // 3. Make a new node that's moved a little bit
- const parentNode = cy.getElementById(data.attachedTo)
- if (parentNode !== null && parentNode !== undefined) {
- //parentNode.data()
- var newNodeData = JSON.parse(JSON.stringify(parentNode.data()))
- newNodeData.id = uuidv4.v4()
- if (newNodeData.position !== undefined) {
- newNodeData.position = {
- "x": newNodeData.position.x+100,
- "y": newNodeData.position.y+100,
- }
- }
+ cy.animation({
+ zoom: 0,
+ center: {
+ eles: event.target,
+ },
+ })
+ .play()
+ .promise()
+ .then(() => {
+ console.log("DONE: ", workflow_id);
+ getWorkflow(workflow_id.value, nodedata);
+ cy.fit(null, 50);
+ });
+ }
+ };
- newNodeData.isStartNode = false
- newNodeData.errors = []
- newNodeData.is_valid = true
- newNodeData.isValid = true
- newNodeData.label = parentNode.data("label")+"_copy"
+ const onNodeDragStop = (event, selectedAction) => {
+ const nodedata = event.target.data();
+ if (nodedata.id === selectedAction.id) {
+ return;
+ }
- cy.add({
- group: 'nodes',
- data: newNodeData,
- position: newNodeData.position,
- })
+ if (nodedata.finished === false) {
+ return;
+ }
- // Readding the icon after moving the node
- //console.log("Node wasn't found")
- if (newNodeData.app_name !== "Testing" || newNodeData.app_name !== "Shuffle Workflow") {
- } else {
- const iconInfo = GetIconInfo(newNodeData)
- const svg_pin = ` `
- const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin)
+ const connected = event.target.connectedEdges().jsons()
+ for (var key in connected) {
+ const edge = connected[key]
+ //console.log("EDGE:", edge)
- const offset = newNodeData.isStartNode ? 36 : 44
- //console.log(event.target.position())
- const decoratorNode = {
- position: {
- x: newNodeData.position.x+offset,
- y: newNodeData.position.y+offset,
- },
- locked: true,
- data: {
- "isDescriptor": true,
- "isValid": true,
- "is_valid": true,
- "label": "",
- "image": svgpin_Url,
- "imageColor": iconInfo.iconBackgroundColor,
- "attachedTo": newNodeData.id,
- },
- }
+ //const edge = edgeBase.json()
- cy.add(decoratorNode).unselectify()
- }
-
- workflow.actions.push(newNodeData)
-
- const sourcebranches = workflow.branches.filter(foundbranch => foundbranch.source_id === parentNode.data("id"))
- const destinationbranches = workflow.branches.filter(foundbranch => foundbranch.destination_id === parentNode.data("id"))
-
- //for (var key in sourcebranches) {
- // var newbranch = JSON.parse(JSON.stringify(sourcebranches[key]))
- // newbranch.id = uuidv4()
- // newbranch.source_id = newNodeData.id
- // cy.add({
- // group: "edges",
- // data: newbranch,
- // })
- //}
-
- for (var key in sourcebranches) {
- var newbranch = JSON.parse(JSON.stringify(sourcebranches[key]))
- newbranch.id = uuidv4()
- newbranch.source_id = newNodeData.id
-
- newbranch._id = newbranch.id
- newbranch.source = newbranch.source_id
- newbranch.target = newbranch.destination_id
- cy.add({
- group: "edges",
- data: newbranch,
- })
- }
-
- for (var key in destinationbranches) {
- var newbranch = JSON.parse(JSON.stringify(destinationbranches[key]))
- newbranch.id = uuidv4()
- newbranch.destination_id = newNodeData.id
-
- newbranch._id = newbranch.id
- newbranch.source = newbranch.source_id
- newbranch.target = newbranch.destination_id
- cy.add({
- group: "edges",
- data: newbranch,
- })
- }
- }
- }
-
- event.target.unselect()
- return
- } else if (data.isDescriptor) {
- console.log("Can't select descriptor")
- event.target.unselect()
- return
- }
-
- //const node = cy.getElementById(data.id)
- //if (node.length > 0) {
- // node.addClass('shuffle-hover-highlight')
- //}
-
- //const branch = workflow.branches.filter(branch => branch.source_id === data.id || branch.destination_id === data.id)
- //console.log("APPAUTH: ", newAppAuth)
- //console.log("BRANCHES: ", branch)
- //console.log("CLICK: ", data)
-
- if (data.type === "ACTION") {
- //setSelectedApp({})
- //setSelectedAction({})
- var curaction = workflow.actions.find(a => a.id === data.id)
- //console.log("PARAMS: ", curaction.parameters[0])
- //console.log("INSIDE CURACTION: ", curaction)
- if (!curaction || curaction === undefined) {
- //event.target.unselect()
- alert.error("Action not found. Please remake it.")
- return
- }
-
-
- const curapp = apps.find(a => a.name === curaction.app_name && ((a.app_version === curaction.app_version || (a.loop_versions !== null && a.loop_versions.includes(curaction.app_version)))))
- //console.log("APP: ", curapp)
- if (!curapp || curapp === undefined) {
- alert.error(`App ${curaction.app_name}:${curaction.app_version} not found. Is it activated?`)
-
- const tmpapp = {
- name: curaction.app_name,
- app_name: curaction.app_name,
- app_version: curaction.app_version,
- id: curaction.app_id,
- actions: [curaction],
- }
-
- //console.log(tmpapp)
- //console.log(curaction)
- setSelectedApp(tmpapp)
- //setSelectedAction(JSON.parse(JSON.stringify(curaction)))
- setSelectedAction(curaction)
- //return
- } else {
- //console.log("AUTHENTICATION: ", curapp.authentication)
- //console.log(curapp.authentication)
- setAuthenticationType(curapp.authentication.type === "oauth2" && curapp.authentication.redirect_uri !== undefined && curapp.authentication.redirect_uri !== null ?
- {
- "type": "oauth2",
- "redirect_uri": curapp.authentication.redirect_uri,
- "refresh_uri": curapp.authentication.refresh_uri,
- "token_uri": curapp.authentication.token_uri,
- "scope": curapp.authentication.scope,
- "client_id": curapp.authentication.client_id,
- "client_secret": curapp.authentication.client_secret,
- } : {
- "type": ""
- }
- )
-
- const requiresAuth = curapp.authentication.required //&& ((curapp.authentication.parameters !== undefined && curapp.authentication.parameters !== null) || (curapp.authentication.type === "oauth2" && curapp.authentication.redirect_uri !== undefined && curapp.authentication.redirect_uri !== null))
- //console.log("AUTHCHECK: ", requiresAuth)
- setRequiresAuthentication(requiresAuth)
- if (curapp.authentication.required) {
- //console.log("App requires auth.")
- // Setup auth here :)
- const authenticationOptions = []
- var findAuthId = ""
- if (curaction.authentication_id !== null && curaction.authentication_id !== undefined && curaction.authentication_id.length > 0) {
- findAuthId = curaction.authentication_id
- }
-
- var tmpAuth = JSON.parse(JSON.stringify(newAppAuth))
- //console.log("FOUND AUTH: ", tmpAuth)
-
- //console.log("Checking authentication: ", tmpAuth)
- for (var key in tmpAuth) {
- var item = tmpAuth[key]
-
- const newfields = {}
- for (var filterkey in item.fields) {
- newfields[item.fields[filterkey].key] = item.fields[filterkey].value
- }
-
- item.fields = newfields
- if (item.app.name === curapp.name) {
- authenticationOptions.push(item)
- if (item.id === findAuthId) {
- curaction.selectedAuthentication = item
- }
- }
- }
-
- //console.log("OPTIONS: ", authenticationOptions)
- curaction.authentication = authenticationOptions
- //console.log("Authentication: ", authenticationOptions)
- if (curaction.selectedAuthentication === null || curaction.selectedAuthentication === undefined || curaction.selectedAuthentication.length === "") {
- curaction.selectedAuthentication = {}
- }
- } else {
- curaction.authentication = []
- curaction.authentication_id = ""
- curaction.selectedAuthentication = {}
- }
-
- //setSelectedAction(JSON.parse(JSON.stringify(curaction)))
- //console.log("CURAPP: ", curapp, selectedApp)
- //console.log("ACTION: ", curaction)
- if (curaction.parameters !== undefined && curaction.parameters !== null && curaction.parameters.length > 0) {
- //console.log("params: ", curaction.parameters)
- for (var key in curaction.parameters) {
- if (curaction.parameters[key].options !== undefined && curaction.parameters[key].options !== null && curaction.parameters[key].options.length > 0 && curaction.parameters[key].value == "") {
- curaction.parameters[key].value = curaction.parameters[key].options[0]
- }
- }
- }
-
- setSelectedApp(curapp)
- setSelectedAction(curaction)
-
- cy.removeListener('drag')
- cy.removeListener('free')
- cy.on('drag', 'node', (e) => onNodeDrag(e, curaction))
- cy.on('free', 'node', (e) => onNodeDragStop(e, curaction))
- }
-
- if (environments !== undefined && environments !== null) {
- var env = environments.find(a => a.Name === curaction.environment)
- if (!env || env === undefined) {
- env = environments[defaultEnvironmentIndex]
- }
-
- setSelectedActionEnvironment(env)
- }
-
- } else if (data.type === "TRIGGER") {
- //console.log("Should handle trigger "+data.triggertype)
- //console.log(data)
- const trigger_index = workflow.triggers.findIndex(a => a.id === data.id)
-
- if (data.app_name === "Shuffle Workflow") {
- getAvailableWorkflows(trigger_index)
- getSettings()
- } else if (data.app_name === "Webhook") {
- if (workflow.triggers[trigger_index].parameters !== undefined) {
- workflow.triggers[trigger_index].parameters[0] = {"name": "url", "value": referenceUrl+"webhook_"+selectedTrigger.id}
- }
- }
-
- setSelectedTriggerIndex(trigger_index)
- setSelectedTrigger(data)
- setSelectedActionEnvironment(data.env)
- } else {
- alert.error("Can't handle "+data.type)
- }
-
- //console.log("BAR: ", rightSideBarOpen, "SAVE: ", lastSaved)
- setRightSideBarOpen(true)
- setLastSaved(false)
-
- // Refresh listeners
- //cy.removeListener('select')
- //cy.on('select', 'node', (e) => onNodeSelect(e, newAppAuth, curapp, rightSideBarOpen, lastSaved))
- //cy.on('select', 'edge', (e) => onEdgeSelect(e))
-
- setScrollConfig({
- top: 0,
- left: 0,
- selected: "",
- })
- }
-
- const GetExampleResult = (item) => {
- var exampledata = item.example === undefined ? "" : item.example
- if (workflowExecutions.length > 0) {
- // Look for the ID
- const found = false
- for (var key in workflowExecutions) {
- if (workflowExecutions[key].results === undefined || workflowExecutions[key].results === null) {
- continue
- }
-
- var foundResult = {"result": ""}
- if (item.id === "exec") {
- //console.log("EXEC: ", workflowExecutions[key].execution_argument)
- if (workflowExecutions[key].execution_argument !== undefined && workflowExecutions[key].execution_argument !== null && workflowExecutions[key].execution_argument.length > 0) {
- foundResult.result = workflowExecutions[key].execution_argument
- } else {
- continue
- }
- } else {
- foundResult = workflowExecutions[key].results.find(result => result.action.id === item.id)
- if (foundResult === undefined) {
- continue
- }
- }
-
- foundResult.result = foundResult.result.trim()
- foundResult.result = foundResult.result.split(" None").join(" \"None\"")
- foundResult.result = foundResult.result.split(" False").join(" false")
- foundResult.result = foundResult.result.split(" True").join(" true")
-
- var jsonvalid = true
- try {
- const tmp = String(JSON.parse(foundResult.result))
- if (!foundResult.result.includes("{") && !foundResult.result.includes("[")) {
- jsonvalid = false
- }
- } catch (e) {
- try {
- foundResult.result = foundResult.result.split("\'").join("\"")
- const tmp = String(JSON.parse(foundResult.result))
- if (!foundResult.result.includes("{") && !foundResult.result.includes("[")) {
- jsonvalid = false
- }
- } catch (e) {
- jsonvalid = false
- }
- }
-
- // Finds the FIRST json only
- if (jsonvalid) {
- //console.log("VALID!")
- exampledata = JSON.parse(foundResult.result)
- break
- } else {
- //console.log("INVALID: ", foundResult.result)
- }
- }
- }
-
- //console.log("EXAMPLE: ", exampledata)
- return exampledata
- }
-
- const GetParamMatch = (paramname, exampledata, basekey) => {
- const splitkey = "."
- //console.log(typeof(exampledata))
- //console.log("MATCHING WITH: ", exampledata)
- if (typeof(exampledata) !== "object") {
- return ""
- }
-
- if (exampledata === null) {
- return ""
- }
-
- // Basically just a stupid if-else :)
- const synonyms = {
- "id": ["id", "ref", "sourceref", "reference", "sourcereference", "alert id", "case id", "incident id", "service id", "sid", "uid", "uuid", "team id"],
- "title": ["title", "name", "message"],
- "description": ["description", "explanation", "story", "details",],
- "email": ["mail", "email", "sender", "receiver", "recipient"],
- "data": ["data", "ip", "domain", "url", "hash", "md5", "sha2", "sha256", "value", "item",],
- }
-
- // 1. Find the right synonym
- // 2.
- var selectedsynonyms = [paramname]
- for (const [key, value] of Object.entries(synonyms)) {
- if (key === paramname || value.includes(paramname)) {
- if (!value.includes(key)) {
- value.push(key.toLowerCase())
- }
-
- selectedsynonyms = value
- break
- }
- }
- //console.log("SELECTED: ", selectedsynonyms)
-
- //console.log("SYNONYMS FOR ", paramname, selectedsynonyms)
- var toreturn = ""
-
- for (const [key, value] of Object.entries(exampledata)) {
- // Check if loop or JSON
- const extra = basekey.length > 0 ? splitkey : ""
- const basekeyname = `${basekey.slice(1, basekey.length).split(".").join(splitkey)}${extra}${key}`
-
- // Handle direct loop!
- //if (!isNaN(key) && basekey === "") {
- // //console.log("Handling direct loop: ", key, value)
- // //parsedValues.push({"type": "object", "name": "Node", "autocomplete": `${basekey}`})
- // //parsedValues.push({"type": "list", "name": `${splitkey}list`, "autocomplete": `${basekey}.#`})
- // //for (var subkey in returnValues) {
- // // parsedValues.push(returnValues[subkey])
- // //}
-
- // toreturn = GetParsedPaths(paramname, value, `${basekey}.#`)
- // console.log("LIST, TORETURN: ", value, toreturn)
- // if (toreturn.length > 0) {
- // break
- // }
- //}
-
- //console.log("KEY: ", key, "VALUE: ", value, "BASEKEY: ", basekeyname)
- if (typeof(value) === 'object') {
- if (Array.isArray(value)) {
- //console.log("LIST!!: ", value, key)
- var selectedkey = ""
- if (isNaN(key)) {
- selectedkey = `.${key}`
- }
-
- for (var subitem in value) {
- toreturn = GetParamMatch(paramname, value[subitem], `${basekey}${selectedkey}.#`)
- if (toreturn.length > 0) {
- break
- }
- }
-
- if (toreturn.length > 0) {
- break
- }
- } else {
- var selectedkey = ""
- if (isNaN(key)) {
- selectedkey = `.${key}`
- }
-
- toreturn = GetParamMatch(paramname, value, `${basekey}${selectedkey}`)
- //console.log("OBJECT: ", value, toreturn, key)
- if (toreturn.length > 0) {
- break
- }
- }
- //console.log("VALUE IS OBJECT: ", key, value)
- } else {
- //console.log("SINGLE ITEM: ", key)
- if (selectedsynonyms.includes(key.toLowerCase())) {
- //parsedValues.push({"type": "value", "name": basekeyname, "autocomplete": `${basekey}.${key}`, "value": value,})
- //console.log("STRING: ", key, value)
- toreturn = `${basekey}.${key}`
- //toreturn = basekeyname
- break
- }
- }
- }
-
- return toreturn
- }
-
- // Takes an action as input, then runs through and updates the relevant fields
- // based on previous actions'
- const RunAutocompleter = (dstdata) => {
- // **PS: The right action should already be set here**
- // 1. Check execution argument
- // 2. Check parents in order
- var exampledata = GetExampleResult({"id": "exec", "name": "exec",})
- //console.log("EXAMPLE RETURN: ", exampledata)
- var parentlabel = "exec"
- for (var paramkey in dstdata.parameters) {
- const param = dstdata.parameters[paramkey]
- // Skip authentication params
- if (param.configuration) {
+ const sourcenode = cy.getElementById(edge.data.source)
+ const destinationnode = cy.getElementById(edge.data.target)
+ if (sourcenode === undefined || sourcenode === null || destinationnode === undefined || destinationnode === null) {
continue
}
- const paramname = param.name.toLowerCase().trim().replaceAll("_", " ")
- //console.log("PARAM: ", param)
- //console.log("PARAMNAME: ", paramname)
-
- const foundresult = GetParamMatch(paramname, exampledata, "")
- if (foundresult.length > 0) {
- //console.log("FOUND: ", paramname, foundresult)
-
- if (dstdata.parameters[paramkey].value.length === 0) {
- dstdata.parameters[paramkey].value = `$${parentlabel}${foundresult}`
- } else {
- //console.log("Skipping ", dstdata.parameters[paramkey], " because it already has a value")
- }
+ const edgeCurve = calculateEdgeCurve(sourcenode.position(), destinationnode.position())
+ const currentedge = cy.getElementById(edge.data.id)
+ if (currentedge !== undefined && currentedge !== null) {
+ currentedge.style('control-point-distance', edgeCurve.distance)
+ currentedge.style('control-point-weight', edgeCurve.weight)
}
}
-
- var parents = getParents(dstdata)
- //console.log("PARENTS: ", parents)
- if (parents.length > 1) {
- for (var key in parents) {
- const item = parents[key]
- if (item.label === "Execution Argument") {
- continue
- }
- parentlabel = item.label === undefined ? "" : item.label.toLowerCase().trim().replaceAll(" ", "_")
- exampledata = GetExampleResult(item)
- for (var paramkey in dstdata.parameters) {
- const param = dstdata.parameters[paramkey]
- // Skip authentication params
- if (param.configuration) {
- continue
- }
+ if (styledElements.length === 1) {
+ console.log(
+ "Should reset location and autofill: ",
+ styledElements,
+ selectedAction
+ );
+ if (originalLocation.x !== 0 || originalLocation.y !== 0) {
+ const currentnode = cy.getElementById(nodedata.id);
+ if (currentnode !== null && currentnode !== undefined) {
+ currentnode.position("x", originalLocation.x);
+ currentnode.position("y", originalLocation.y);
+ }
- const paramname = param.name.toLowerCase().trim().replaceAll("_", " ")
- //console.log("PARAM: ", param)
- //console.log("PARAMNAME: ", paramname)
+ originalLocation = { x: 0, y: 0 };
+ }
- const foundresult = GetParamMatch(paramname, exampledata, "")
- if (foundresult.length > 0) {
- //console.log("FOUND: ", paramname, foundresult)
-
- if (dstdata.parameters[paramkey].value.length === 0) {
- dstdata.parameters[paramkey].value = `$${parentlabel}${foundresult}`
- } else {
- dstdata.parameters[paramkey].value = `$${parentlabel}${foundresult}`
- //console.log("Skipping ", dstdata.parameters[paramkey], " because it already has a value")
- }
+ const curElement = document.getElementById(styledElements[0]);
+ if (curElement !== null && curElement !== undefined) {
+ curElement.style.border = curElement.style.original_border;
+ var newValue = "$" + nodedata.label.toLowerCase().replaceAll(" ", "_");
+ if (nodedata.type === "TRIGGER") {
+ if (nodedata.trigger_type === "WEBHOOK" || nodedata.trigger_type === "SCHEDULE" || nodedata.trigger_type === "EMAIL") {
+ var newValue = "$exec"
}
}
- // Check agains every param
- }
- }
+ var paramname = "";
+ var idnumber = -1;
+ if (curElement.id.startsWith("rightside_field_")) {
+ console.log("FOUND FIELD WITH NUMBER: ", curElement.id);
- return dstdata
- }
- //const FixNameUpdater = (sourcenode) => {
- //}
+ // Find exact position to put the text
- // Checks for errors in edges when they're added
- const onEdgeAdded = (event) => {
- const edge = event.target.data()
- console.log("EDGE ADDED: ", edge)
- //setLastSaved(false)
- var targetnode = workflow.triggers.findIndex(data => data.id === edge.target)
- if (targetnode !== -1) {
- console.log("TARGETNODE: ", targetnode)
- if (workflow.triggers[targetnode].app_name === "User Input" || workflow.triggers[targetnode].app_name === "Shuffle Workflow") {
- } else {
- alert.error("Can't have triggers as target of branch")
- event.target.remove()
- }
- }
+ const idsplit = curElement.id.split("_");
+ console.log(idsplit);
+ if (idsplit.length === 3 && !isNaN(idsplit[2])) {
+ console.log("ADDING TO PARAM ", idsplit[2]);
- console.log("TARGET: ", event.target.target().data())
- if (event.target.target().data("isButton") === true || event.target.target().data("isDescriptor") === true) {
- event.target.remove()
+ selectedAction.parameters[idsplit[2]].value += newValue;
+ paramname = selectedAction.parameters[idsplit[2]].name;
+ idnumber = idsplit[2];
+ }
+ }
+
+ if (idnumber >= 0 && paramname.length > 0) {
+ const exampledata = GetExampleResult(nodedata);
+ const parsedname = paramname
+ .toLowerCase()
+ .trim()
+ .replaceAll("_", " ");
+
+ const foundresult = GetParamMatch(parsedname, exampledata, "");
+ if (foundresult.length > 0) {
+ console.log("FOUND RESULT: ", paramname, foundresult);
+ newValue = `${newValue}${foundresult}`;
+ }
+
+ selectedAction.parameters[idnumber].value = newValue;
+ }
+
+ curElement.value = newValue;
+ }
+ }
+
+ if (
+ nodedata.app_name !== undefined &&
+ ((nodedata.app_name !== "Shuffle Tools" &&
+ nodedata.app_name !== "Testing" &&
+ nodedata.app_name !== "Shuffle Workflow" &&
+ nodedata.app_name !== "User Input" &&
+ nodedata.app_name !== "Webhook" &&
+ nodedata.app_name !== "Schedule" &&
+ nodedata.app_name !== "Email") ||
+ nodedata.isStartNode)
+ ) {
+ const allNodes = cy.nodes().jsons();
+ var found = false;
+ for (var key in allNodes) {
+ const currentNode = allNodes[key];
+ if (
+ currentNode.data.attachedTo === nodedata.id &&
+ currentNode.data.isDescriptor
+ ) {
+ found = true;
+ console.log("FOUND THE NODE!");
+ break;
+ }
+ }
+
+ // Readding the icon after moving the node
+ if (!found) {
+ const iconInfo = GetIconInfo(nodedata);
+ const svg_pin = ` `;
+ const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
+
+ const offset = nodedata.isStartNode ? 36 : 44;
+ const decoratorNode = {
+ position: {
+ x: event.target.position().x + offset,
+ y: event.target.position().y + offset,
+ },
+ locked: true,
+ data: {
+ isDescriptor: true,
+ isValid: true,
+ is_valid: true,
+ label: "",
+ image: svgpin_Url,
+ imageColor: iconInfo.iconBackgroundColor,
+ attachedTo: nodedata.id,
+ },
+ };
+
+ cy.add(decoratorNode).unselectify();
+ } else {
+ console.log("Node already exists - don't add descriptor node");
+ }
+ }
+ originalLocation = {
+ x: 0,
+ y: 0,
+ };
+
+ sendStreamRequest({
+ "item": "node",
+ "type": "move",
+ "id": nodedata.id,
+ "location": {"x": event.target.position("x"), "y": event.target.position("y")}
+ })
+ };
+
+ const onNodeDrag = (event, selectedAction) => {
+ const nodedata = event.target.data();
+ if (nodedata.finished === false) {
+ console.log("NOT FINISHED - ADD EXAMPLE BRANCHES TO CLOSEST!!")
return
- }
+ }
- targetnode = -1
- var sourcenode = workflow.triggers.findIndex(data => data.id === edge.source)
- console.log("SOURCENODE: ", sourcenode)
- if (sourcenode !== -1) {
- if (workflow.triggers[sourcenode].app_name === "User Input" || workflow.triggers[sourcenode].app_name === "Shuffle Workflow") {
- //console.log("NORMAL TRIGGER")
- } else {
- //var currentnode = cy.getElementById(workflow.triggers[sourcenode].id)
- //console.log("NODE: ", currentnode)
- //if (currentnode !== null && currentnode !== undefined) {
- // console.log("SHOULD CHECK IF TRIGGER HAS MULTIPLE EDGES: ", currentnode)
- //if (workflow.branches !== undefined && workflow.branches !== null) {
- // const found_branches = workflow.branches.filter(branch => branch.source == workflow.triggers[sourcenode].id)
- // console.log("FOUND BRANCHES: ", found_branches)
- // if (found_branches.length > 0) {
- // alert.error("Can't have multiple branches from this trigger")
- // event.target.remove()
- // }
+ if (nodedata.app_name !== undefined) {
+ const allNodes = cy.nodes().jsons();
+ for (var key in allNodes) {
+ const currentNode = allNodes[key];
+ if (currentNode.data.attachedTo === nodedata.id) {
+ cy.getElementById(currentNode.data.id).remove();
+ }
+
+ // Calculate location
+ //currentNode.position.x >
+ //if (nodedata.position.x > 0 && nodedata.position.y > 0) {
+ // console.log("Positive both")
//}
- //if (cy.edges().size() === 1) {
- // https://js.cytoscape.org/#edges.connectedNodes
- //console.log("CURRENTNODE: ", currentnode)
- //console.log("EDGES: ", currentnode.connectedEdges(`node[id=${workflow.triggers[sourcenode].id}]`))
- //console.log("EDGES2: ", currentnode.connectedEdges())
- //currentnode.connectedEdges().animate({style: {lineColor: "red"}})
- //console.log("OUTGOERS: ", currentnode.outgoers())
+ //console.log(currentNode.position)
+ //console.log(nodedata.position)
+ }
+ } else {
+ //console.log("No appid? ", nodedata)
+ }
- //console.log("LEN2: ", currentnode.edges().length)
- //if (currentnode.connectedNodes().length > 0) {
- // alert.error("Can't have multiple branches from this trigger")
- // event.target.remove()
- //}
- }
+ if (nodedata.id === selectedAction.id) {
+ return;
+ }
+
+
+
+ /*
+ // Tried looking for the closest node by position. aStar path not working entirely.
+ console.log("NODE: ", event.target)
+ const closestNode = cy.elements().aStar({
+ root: nodedata.id,
+ goal: 'node',
+ directed: false,
+ })
+
+ if (closestNode.found) {
+ console.log("No closest node found for: ", nodedata.id)
+ } else {
+ console.log("Closest: ", closestNode)
+ }
+ */
+
+ if (
+ originalLocation.x === 0 &&
+ originalLocation.y === 0 &&
+ nodedata.position !== undefined
+ ) {
+ originalLocation.x = nodedata.position.x;
+ originalLocation.y = nodedata.position.y;
+ }
+
+ // Part of autocomplete. Styles elements in frontend to indicate
+ // what and where we may input data for the user.
+ const onMouseUpdate = (e) => {
+ const x = e.pageX;
+ const y = e.pageY;
+
+ const elementMouseIsOver = document.elementFromPoint(x, y);
+ if (elementMouseIsOver !== undefined && elementMouseIsOver !== null) {
+ // Color for #f85a3e translated to rgb
+ const newBorder = "3px solid rgb(248, 90, 62)";
+ if (
+ elementMouseIsOver.style.border !== newBorder &&
+ elementMouseIsOver.id.includes("rightside")
+ ) {
+ if (elementMouseIsOver.style.border !== undefined) {
+ elementMouseIsOver.style.original_border =
+ elementMouseIsOver.style.border;
+ } else {
+ elementMouseIsOver.style.original_border = "";
+ }
+
+ elementMouseIsOver.style.border = newBorder;
+ console.log("STYLED: ", styledElements);
+ for (var key in styledElements) {
+ const curElement = document.getElementById(styledElements[key]);
+ if (curElement !== null && curElement !== undefined) {
+ curElement.style.border = curElement.style.original_border;
+ }
+ }
+
+ styledElements = [];
+ styledElements.push(elementMouseIsOver.id);
+ } else if (
+ elementMouseIsOver.id === "cytoscape_view" ||
+ elementMouseIsOver.id === ""
+ ) {
+ for (var key in styledElements) {
+ const curElement = document.getElementById(styledElements[key]);
+ if (curElement !== null && curElement !== undefined) {
+ curElement.style.border = curElement.style.original_border;
+ }
+ }
+
+ styledElements = [];
+ }
+ }
+
+ // Ensure it only happens once
+ document.removeEventListener("mousemove", onMouseUpdate, false);
+ };
+
+ document.addEventListener("mousemove", onMouseUpdate, false);
+ };
+
+
+ useBeforeunload(() => {
+ if (!lastSaved) {
+ return unloadText;
+ } else {
+ if (workflow.public === false) {
+ //document.removeEventListener("mousemove", onMouseUpdate, true);
+ document.removeEventListener("keydown", handleKeyDown, true);
+ document.removeEventListener("paste", handlePaste, true);
+ }
+ }
+ });
+
+ // Nodeselectbatching:
+ // https://stackoverflow.com/questions/16677856/cy-onselect-callback-only-once
+ // onNodeClick
+ const onNodeSelect = (event, newAppAuth) => {
+ // Otherwise everything is SUPER slow
+ ReactDOM.unstable_batchedUpdates(() => {
+ const data = event.target.data();
+ if (data.isButton) {
+ if (data.buttonType === "delete") {
+ const parentNode = cy.getElementById(data.attachedTo);
+ if (parentNode !== null && parentNode !== undefined) {
+ removeNode(data.attachedTo)
+ //parentNode.remove()
+ }
+
+ return
+ } else if (data.buttonType === "set_startnode" && data.type !== "TRIGGER") {
+ const parentNode = cy.getElementById(data.attachedTo);
+ if (parentNode !== null && parentNode !== undefined) {
+ var oldstartnode = cy.getElementById(workflow.start);
+ if (
+ oldstartnode !== null &&
+ oldstartnode !== undefined &&
+ oldstartnode.length > 0
+ ) {
+ try {
+ oldstartnode[0].data("isStartNode", false);
+ } catch (e) {
+ console.log("Startnode error: ", e);
+ }
+ }
+
+ workflow.start = parentNode.data("id");
+ setLastSaved(false);
+ parentNode.data("isStartNode", true);
+ }
+
+ //event.target.unselect();
+ setRightSideBarOpen(true);
+ return
+ } else if (data.buttonType === "copy") {
+ console.log("COPY!");
+
+ // 1. Find parent
+ // 2. Find branches for parent
+ // 3. Make a new node that's moved a little bit
+ const parentNode = cy.getElementById(data.attachedTo);
+ if (parentNode !== null && parentNode !== undefined) {
+ var newNodeData = JSON.parse(JSON.stringify(parentNode.data()));
+ newNodeData.id = uuidv4();
+ if (newNodeData.position !== undefined) {
+ newNodeData.position = {
+ x: newNodeData.position.x + 100,
+ y: newNodeData.position.y + 100,
+ };
+ }
+
+ newNodeData.isStartNode = false;
+ newNodeData.errors = [];
+ newNodeData.is_valid = true;
+ newNodeData.isValid = true;
+ newNodeData.label = parentNode.data("label") + "_copy";
+
+ cy.add({
+ group: "nodes",
+ data: newNodeData,
+ position: newNodeData.position,
+ });
+
+ // Readding the icon after moving the node
+ if (
+ newNodeData.app_name !== "Testing" ||
+ newNodeData.app_name !== "Shuffle Workflow"
+ ) {
+ } else {
+ const iconInfo = GetIconInfo(newNodeData);
+ const svg_pin = ` `;
+ const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
+
+ const offset = newNodeData.isStartNode ? 36 : 44;
+ const decoratorNode = {
+ position: {
+ x: newNodeData.position.x + offset,
+ y: newNodeData.position.y + offset,
+ },
+ locked: true,
+ data: {
+ isDescriptor: true,
+ isValid: true,
+ is_valid: true,
+ label: "",
+ image: svgpin_Url,
+ imageColor: iconInfo.iconBackgroundColor,
+ attachedTo: newNodeData.id,
+ },
+ };
+
+ cy.add(decoratorNode).unselectify();
+ }
+
+ workflow.actions.push(newNodeData);
+
+ const sourcebranches = workflow.branches.filter(
+ (foundbranch) => foundbranch.source_id === parentNode.data("id")
+ );
+ const destinationbranches = workflow.branches.filter(
+ (foundbranch) =>
+ foundbranch.destination_id === parentNode.data("id")
+ );
+
+ for (var key in sourcebranches) {
+ var newbranch = JSON.parse(JSON.stringify(sourcebranches[key]));
+ newbranch.id = uuidv4();
+ newbranch.source_id = newNodeData.id;
+
+ newbranch._id = newbranch.id;
+ newbranch.source = newbranch.source_id;
+ newbranch.target = newbranch.destination_id;
+ cy.add({
+ group: "edges",
+ data: newbranch,
+ });
+ }
+
+ for (var key in destinationbranches) {
+ var newbranch = JSON.parse(
+ JSON.stringify(destinationbranches[key])
+ );
+ newbranch.id = uuidv4();
+ newbranch.destination_id = newNodeData.id;
+
+ newbranch._id = newbranch.id;
+ newbranch.source = newbranch.source_id;
+ newbranch.target = newbranch.destination_id;
+ cy.add({
+ group: "edges",
+ data: newbranch,
+ });
+ }
+
+ //event.target.unselect();
+ return
+ }
+ }
+
+ return;
+ } else if (data.isDescriptor) {
+ console.log("Can't select descriptor");
+ event.target.unselect();
+ return;
+ }
+
+ if (data.type === "ACTION") {
+ setSelectedComment({})
+ //var curaction = JSON.parse(JSON.stringify(data))
+ // FIXME: Trust it to just work?
+ //event.target.data()
+ var curaction = workflow.actions.find((a) => a.id === data.id);
+ if (!curaction || curaction === undefined) {
+ console.log("NOT FOUND DATA: ", event.target.data())
+ if (data.id !== undefined && data.app_name !== undefined) {
+ workflow.actions.push(data)
+ setWorkflow(workflow)
+ curaction = data
+ } else {
+ if (workflow.public !== true) {
+ alert.error("Action not found. Please remake it.");
+ }
+
+ event.target.remove();
+ return;
+ }
+ }
+
+ //var newapps = JSON.parse(JSON.stringify(apps))
+ var newapps = apps
+ if (apps === null || apps === undefined || apps.length === 0) {
+ newapps = filteredApps
+ }
+
+ const curapp = newapps.find(
+ (a) =>
+ a.name === curaction.app_name &&
+ (a.app_version === curaction.app_version ||
+ (a.loop_versions !== null &&
+ a.loop_versions.includes(curaction.app_version)))
+ );
+ if (!curapp || curapp === undefined) {
+ console.log("APPS: ", newapps)
+ //alert.error(`App ${curaction.app_name}:${curaction.app_version} not found. Is it activated?`);
+
+ const tmpapp = {
+ name: curaction.app_name,
+ app_name: curaction.app_name,
+ app_version: curaction.app_version,
+ id: curaction.app_id,
+ actions: [curaction],
+ };
+
+ setSelectedApp(tmpapp);
+ setSelectedAction(curaction);
+ } else {
+ setAuthenticationType(
+ curapp.authentication.type === "oauth2" &&
+ curapp.authentication.redirect_uri !== undefined &&
+ curapp.authentication.redirect_uri !== null
+ ? {
+ type: "oauth2",
+ redirect_uri: curapp.authentication.redirect_uri,
+ refresh_uri: curapp.authentication.refresh_uri,
+ token_uri: curapp.authentication.token_uri,
+ scope: curapp.authentication.scope,
+ client_id: curapp.authentication.client_id,
+ client_secret: curapp.authentication.client_secret,
+ }
+ : {
+ type: "",
+ }
+ );
+
+ const requiresAuth = curapp.authentication.required; //&& ((curapp.authentication.parameters !== undefined && curapp.authentication.parameters !== null) || (curapp.authentication.type === "oauth2" && curapp.authentication.redirect_uri !== undefined && curapp.authentication.redirect_uri !== null))
+ setRequiresAuthentication(requiresAuth);
+ if (curapp.authentication.required) {
+ //console.log("App requires auth.")
+ // Setup auth here :)
+ const authenticationOptions = [];
+ var findAuthId = "";
+ if (
+ curaction.authentication_id !== null &&
+ curaction.authentication_id !== undefined &&
+ curaction.authentication_id.length > 0
+ ) {
+ findAuthId = curaction.authentication_id;
+ }
+
+ var tmpAuth = JSON.parse(JSON.stringify(newAppAuth));
+
+ for (var key in tmpAuth) {
+ var item = tmpAuth[key];
+
+ const newfields = {};
+ for (var filterkey in item.fields) {
+ newfields[item.fields[filterkey].key] =
+ item.fields[filterkey].value;
+ }
+
+ item.fields = newfields;
+ if (item.app.name === curapp.name) {
+ authenticationOptions.push(item);
+ if (item.id === findAuthId) {
+ curaction.selectedAuthentication = item;
+ }
+ }
+ }
+
+ curaction.authentication = authenticationOptions;
+ if (
+ curaction.selectedAuthentication === null ||
+ curaction.selectedAuthentication === undefined ||
+ curaction.selectedAuthentication.length === ""
+ ) {
+ curaction.selectedAuthentication = {};
+ }
+ } else {
+ curaction.authentication = [];
+ curaction.authentication_id = "";
+ curaction.selectedAuthentication = {};
+ }
+
+ if (
+ curaction.parameters !== undefined &&
+ curaction.parameters !== null &&
+ curaction.parameters.length > 0
+ ) {
+ for (var key in curaction.parameters) {
+ if (
+ curaction.parameters[key].options !== undefined &&
+ curaction.parameters[key].options !== null &&
+ curaction.parameters[key].options.length > 0 &&
+ curaction.parameters[key].value === ""
+ ) {
+ curaction.parameters[key].value =
+ curaction.parameters[key].options[0];
+ }
+ }
+ }
+
+ console.log("ACTION: ", curaction)
+ setSelectedApp(curapp);
+ setSelectedAction(curaction);
+
+ cy.removeListener("drag");
+ cy.removeListener("free");
+ cy.on("drag", "node", (e) => onNodeDrag(e, curaction));
+ cy.on("free", "node", (e) => onNodeDragStop(e, curaction));
+ }
+
+ console.log("Object: ", environments)
+ if (environments !== undefined && environments !== null && (typeof environments === "array" || typeof environments === "object")) {
+ var parsedenv = environments
+ if (typeof environments === "object") {
+ parsedenv = [environments]
+ }
+
+ var env = parsedenv.find((a) => a.Name === curaction.environment);
+ if (!env || env === undefined) {
+ env = parsedenv[defaultEnvironmentIndex];
+ }
+
+ setSelectedActionEnvironment(env);
+ }
+ } else if (data.type === "TRIGGER") {
+ setSelectedComment({})
+ if (workflow.triggers === null) {
+ workflow.triggers = []
+ }
+
+ var trigger_index = workflow.triggers.findIndex(
+ (a) => a.id === data.id
+ );
+
+ //console.log("Trigger: ", data, trigger_index)
+ if (trigger_index === -1) {
+ workflow.triggers.push(data)
+ trigger_index = workflow.triggers.length-1
+ setWorkflow(workflow)
+ }
+
+ //console.log("Trigger2: ", data, trigger_index)
+ //if (data.id !== undefined && data.app_name !== undefined) {
+ // //newapps.push(data)
+ // workflow.actions.push(data)
+ // curaction = data
+ //} else {
+ // alert.error("Action not found. Please remake it.");
+ // event.target.remove();
+ // return;
+ //}
+
+ if (data.app_name === "Shuffle Workflow") {
+ getAvailableWorkflows(trigger_index);
+ getSettings();
+ } else if (data.app_name === "Webhook") {
+ if (workflow.triggers[trigger_index].parameters !== undefined) {
+ workflow.triggers[trigger_index].parameters[0] = {
+ name: "url",
+ value: referenceUrl + "webhook_" + workflow.triggers[trigger_index].id,
+ };
+ }
+ }
+
+ console.log("DATA: ", data)
+ setSelectedTriggerIndex(trigger_index);
+ setSelectedTrigger(data);
+ setSelectedActionEnvironment(data.env);
+ } else if (data.type === "COMMENT") {
+ setSelectedComment(data);
+ } else {
+ alert.error("Can't handle " + data.type);
+ return;
+ }
+
+ setRightSideBarOpen(true);
+ setLastSaved(false);
+ setScrollConfig({
+ top: 0,
+ left: 0,
+ selected: "",
+ });
+ })
+ }
+
+ const activateApp = (appid) => {
+ fetch(globalUrl+"/api/v1/apps/"+appid+"/activate", {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json',
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Failed to activate")
+ }
+
+ return response.json()
+ })
+ .then((responseJson) => {
+ if (responseJson.success === false) {
+ alert.error("Failed to activate the app")
+ } else {
+ alert.success("App activated for your organization!")
+ }
+ })
+ .catch(error => {
+ alert.error(error.toString())
+ });
+ }
+
+ const GetExampleResult = (item) => {
+ var exampledata = item.example === undefined ? "" : item.example;
+ if (workflowExecutions.length > 0) {
+ // Look for the ID
+ for (var key in workflowExecutions) {
+ if (
+ workflowExecutions[key].results === undefined ||
+ workflowExecutions[key].results === null
+ ) {
+ continue;
+ }
+
+ var foundResult = { result: "" };
+ if (item.id === "exec") {
+ if (
+ workflowExecutions[key].execution_argument !== undefined &&
+ workflowExecutions[key].execution_argument !== null &&
+ workflowExecutions[key].execution_argument.length > 0
+ ) {
+ foundResult.result = workflowExecutions[key].execution_argument;
+ } else {
+ continue;
+ }
+ } else {
+ foundResult = workflowExecutions[key].results.find(
+ (result) => result.action.id === item.id
+ );
+ if (foundResult === undefined) {
+ continue;
+ }
+ }
+
+ foundResult.result = foundResult.result.trim();
+ foundResult.result = foundResult.result.split(" None").join(' "None"');
+ foundResult.result = foundResult.result.split(" False").join(" false");
+ foundResult.result = foundResult.result.split(" True").join(" true");
+
+ var jsonvalid = true;
+ try {
+ if (
+ !foundResult.result.includes("{") &&
+ !foundResult.result.includes("[")
+ ) {
+ jsonvalid = false;
+ }
+ } catch (e) {
+ try {
+ foundResult.result = foundResult.result.split("'").join('"');
+ if (
+ !foundResult.result.includes("{") &&
+ !foundResult.result.includes("[")
+ ) {
+ jsonvalid = false;
+ }
+ } catch (e) {
+ jsonvalid = false;
+ }
+ }
+
+ // Finds the FIRST json only
+ if (jsonvalid) {
+ try {
+ exampledata = JSON.parse(foundResult.result)
+ } catch (e) {
+ console.log("Result: ", exampledata)
+
+ }
+
+ break;
+ }
+ }
+ }
+
+ return exampledata;
+ };
+
+ const GetParamMatch = (paramname, exampledata, basekey) => {
+ if (typeof exampledata !== "object") {
+ return "";
+ }
+
+ if (exampledata === null) {
+ return "";
+ }
+
+ console.log("NOT REPLACING ON PURPOSE!!")
+ return ""
+
+ // Basically just a stupid if-else :)
+ const synonyms = {
+ id: [
+ "id",
+ "ref",
+ "sourceref",
+ "reference",
+ "sourcereference",
+ "alert id",
+ "case id",
+ "incident id",
+ "service id",
+ "sid",
+ "uid",
+ "uuid",
+ "team id",
+ ],
+ title: ["title", "name", "message"],
+ description: ["description", "explanation", "story", "details"],
+ email: ["mail", "email", "sender", "receiver", "recipient"],
+ data: [
+ "data",
+ "ip",
+ "domain",
+ "url",
+ "hash",
+ "md5",
+ "sha2",
+ "sha256",
+ "value",
+ "item",
+ ],
+ };
+
+ // 1. Find the right synonym
+ // 2.
+ var selectedsynonyms = [paramname];
+ for (const [key, value] of Object.entries(synonyms)) {
+ if (key === paramname || value.includes(paramname)) {
+ if (!value.includes(key)) {
+ value.push(key.toLowerCase());
+ }
+
+ selectedsynonyms = value;
+ break;
+ }
+ }
+
+ var toreturn = "";
+
+ for (const [key, value] of Object.entries(exampledata)) {
+ // Check if loop or JSON
+
+ if (typeof value === "object") {
+ if (Array.isArray(value)) {
+ var selectedkey = "";
+ if (isNaN(key)) {
+ selectedkey = `.${key}`;
+ }
+
+ for (var subitem in value) {
+ toreturn = GetParamMatch(
+ paramname,
+ value[subitem],
+ `${basekey}${selectedkey}.#`
+ );
+ if (toreturn.length > 0) {
+ break;
+ }
+ }
+
+ if (toreturn.length > 0) {
+ break;
+ }
+ } else {
+ var selectedkey = "";
+ if (isNaN(key)) {
+ selectedkey = `.${key}`;
+ }
+
+ toreturn = GetParamMatch(
+ paramname,
+ value,
+ `${basekey}${selectedkey}`
+ );
+ if (toreturn.length > 0) {
+ break;
+ }
+ }
+ } else {
+ if (selectedsynonyms.includes(key.toLowerCase())) {
+ toreturn = `${basekey}.${key}`;
+ break;
+ }
+ }
+ }
+
+ return toreturn;
+ };
+
+ // Takes an action as input, then runs through and updates the relevant fields
+ // based on previous actions'
+ // Uses lots of synonyms
+ // autocomplete
+ const RunAutocompleter = (dstdata) => {
+ // **PS: The right action should already be set here**
+ // 1. Check execution argument
+ // 2. Check parents in order
+ var exampledata = GetExampleResult({ id: "exec", name: "exec" });
+ var parentlabel = "exec";
+ for (var paramkey in dstdata.parameters) {
+ const param = dstdata.parameters[paramkey];
+ // Skip authentication params
+ if (param.configuration) {
+ continue;
+ }
+
+ const paramname = param.name.toLowerCase().trim().replaceAll("_", " ");
+
+ const foundresult = GetParamMatch(paramname, exampledata, "");
+ if (foundresult.length > 0) {
+ if (dstdata.parameters[paramkey].value.length === 0) {
+ dstdata.parameters[paramkey].value = `$${parentlabel}${foundresult}`;
+ }
+ }
+ }
+
+ var parents = getParents(dstdata);
+ if (parents.length > 1) {
+ for (var key in parents) {
+ const item = parents[key];
+ if (item.label === "Execution Argument") {
+ continue;
+ }
+
+ parentlabel =
+ item.label === undefined
+ ? ""
+ : item.label.toLowerCase().trim().replaceAll(" ", "_");
+ exampledata = GetExampleResult(item);
+ for (var paramkey in dstdata.parameters) {
+ const param = dstdata.parameters[paramkey];
+ // Skip authentication params
+ if (param.configuration) {
+ continue;
+ }
+
+ const paramname = param.name
+ .toLowerCase()
+ .trim()
+ .replaceAll("_", " ");
+
+ const foundresult = GetParamMatch(paramname, exampledata, "");
+ if (foundresult.length > 0) {
+ if (dstdata.parameters[paramkey].value.length === 0) {
+ dstdata.parameters[
+ paramkey
+ ].value = `$${parentlabel}${foundresult}`;
+ } else {
+ dstdata.parameters[
+ paramkey
+ ].value = `$${parentlabel}${foundresult}`;
+ }
+ }
+ }
+ // Check agains every param
+ }
+ }
+
+ return dstdata;
+ };
+
+ // Checks for errors in edges when they're added
+ const onEdgeAdded = (event) => {
+ setLastSaved(false);
+ const edge = event.target.data();
+
+ const sourcenode = cy.getElementById(edge.source)
+ const destinationnode = cy.getElementById(edge.target)
+ if (sourcenode === undefined || sourcenode === null || destinationnode === undefined || destinationnode === null) {
+ } else {
+ const edgeCurve = calculateEdgeCurve(sourcenode.position(), destinationnode.position())
+ const currentedge = cy.getElementById(edge.id)
+ if (currentedge !== undefined && currentedge !== null) {
+ currentedge.style('control-point-distance', edgeCurve.distance)
+ currentedge.style('control-point-weight', edgeCurve.weight)
+ }
}
+ var targetnode = workflow.triggers.findIndex(
+ (data) => data.id === edge.target
+ );
+ if (targetnode !== -1) {
+ console.log("TARGETNODE: ", targetnode);
+ if (
+ workflow.triggers[targetnode].app_name === "User Input" ||
+ workflow.triggers[targetnode].app_name === "Shuffle Workflow"
+ ) {
+ } else {
+ alert.error("Can't have triggers as target of branch");
+ event.target.remove();
+ }
+ }
- //console.log(workflow.branches)
+ const eventTarget = event.target.target()
+ console.log("BUTTON! Find parent from: ", eventTarget)
+ if (eventTarget.data("isButton") === true) {
+ console.log("ACTUALLY A BUTTON!")
+ const parentNode = cy.getElementById(eventTarget.data("attachedTo"))
+ event.target.remove()
+ console.log("Setting it to parentnode: ", parentNode.data())
+ if (parentNode !== undefined && parentNode !== null) {
+ //event.target.data("target", eventTarget.data("attachedTo"))
- // Check if:
- // dest == source && source == dest
- // dest == dest && source == source
- // backend: check all children? to stop recursion
- var found = false
- for (var key in workflow.branches) {
- if (workflow.branches[key].destination_id === edge.source && workflow.branches[key].source_id === edge.target) {
- alert.error("A branch in the opposite direction already exists")
- event.target.remove()
- found = true
- break
- } else if (workflow.branches[key].destination_id === edge.target && workflow.branches[key].source_id === edge.source) {
- console.log(edge.source)
- alert.error("That branch already exists")
- event.target.remove()
- found = true
- break
- } else if (edge.target === workflow.start) {
- targetnode = workflow.triggers.findIndex(data => data.id === edge.source)
- if (targetnode === -1) {
- alert.error("Can't make arrow to starting node")
- event.target.remove()
- found = true
- break
- }
- } else if (edge.source === workflow.branches[key].source_id) {
- // FIXME: Verify multi-target for triggers
- // 1. Check if destination exists
- // 2. Check if source is a trigger
- // targetnode = workflow.triggers.findIndex(data => data.id === edge.source)
- // console.log("Destination: ", edge.target)
- // console.log("CHECK SOURCE IF ITS A TRIGGER: ", targetnode)
- // if (targetnode !== -1) {
- // alert.error("Triggers can only target one target (startnode)")
- // event.target.remove()
- // found = true
- // break
- // }
- } else {
- //console.log("INSIDE LAST CHECK: ", edge)
+ const newEdgeUuid = uuidv4()
+ const newcybranch = {
+ source: event.target.data("source"),
+ target: eventTarget.data("attachedTo"),
+ _id: newEdgeUuid,
+ id: newEdgeUuid,
+ hasErrors: event.target.data("hasErrors"),
+ };
- // Find the targetnode and check if its a trigger
- // FIXME - do this for both actions and other types?
- /*
+ const edgeToBeAdded = {
+ group: "edges",
+ data: newcybranch,
+ }
+
+ cy.add(edgeToBeAdded);
+ }
+ }
+
+ if (
+ eventTarget.data("isDescriptor") === true ||
+ eventTarget.data("type") === "COMMENT"
+ ) {
+ console.log("Removing because of descriptor or comment")
+ event.target.remove();
+ return;
+ }
+
+ targetnode = -1;
+
+ // Check if:
+ // dest == source && source == dest
+ // dest == dest && source == source
+ // backend: check all children? to stop recursion
+ var found = false;
+ for (var key in workflow.branches) {
+ if (
+ workflow.branches[key].destination_id === edge.source &&
+ workflow.branches[key].source_id === edge.target
+ ) {
+ alert.error("A branch in the opposite direction already exists");
+ event.target.remove();
+ found = true;
+ break;
+ } else if (
+ workflow.branches[key].destination_id === edge.target &&
+ workflow.branches[key].source_id === edge.source
+ ) {
+ console.log(edge.source);
+ alert.error("That branch already exists");
+ event.target.remove();
+ found = true;
+ break;
+ } else if (edge.target === workflow.start) {
+ targetnode = workflow.triggers.findIndex(
+ (data) => data.id === edge.source
+ );
+ if (targetnode === -1) {
+ if (targetnode.type !== "TRIGGER") {
+ alert.error("Can't make arrow to starting node");
+ event.target.remove();
+ break;
+ }
+
+ found = true;
+ }
+ } else if (edge.source === workflow.branches[key].source_id) {
+ // FIXME: Verify multi-target for triggers
+ // 1. Check if destination exists
+ // 2. Check if source is a trigger
+ // targetnode = workflow.triggers.findIndex(data => data.id === edge.source)
+ // console.log("Destination: ", edge.target)
+ // console.log("CHECK SOURCE IF ITS A TRIGGER: ", targetnode)
+ // if (targetnode !== -1) {
+ // alert.error("Triggers can only target one target (startnode)")
+ // event.target.remove()
+ // found = true
+ // break
+ // }
+ } else {
+ //console.log("INSIDE LAST CHECK: ", edge)
+ // Find the targetnode and check if its a trigger
+ // FIXME - do this for both actions and other types?
+ /*
targetnode = workflow.triggers.findIndex(data => data.id === edge.target)
if (targetnode !== -1) {
console.log("TARGETNODE: ", targetnode)
@@ -2589,4108 +3101,5736 @@ const AngularWorkflow = (props) => {
}
}
*/
+ }
+ }
-
- }
- }
+ // 1. Guess what the next node's action should be
+ // 2. Get result from previous nodes (if any)
+ // 3. TRY to automatically map them in based on synonyms
+ const newsource = cy.getElementById(edge.source);
+ const newdst = cy.getElementById(edge.target);
+ if (
+ newsource !== undefined &&
+ newsource !== null &&
+ newdst !== undefined &&
+ newdst !== null
+ ) {
+ const dstdata = RunAutocompleter(newdst.data());
+ console.log("DST: ", dstdata);
+ }
+
+ var newbranch = {
+ source_id: edge.source,
+ destination_id: edge.target,
+ id_: edge.id,
+ id: edge.id,
+ hasErrors: false,
+ decorator: false,
+ };
+
+ if (!found) {
+ newbranch["hasErrors"] = false;
+
+ workflow.branches.push(newbranch);
+ setWorkflow(workflow);
+ }
+
+ history.push({
+ type: "edge",
+ action: "added",
+ data: edge,
+ });
+ setHistory(history);
+ setHistoryIndex(history.length);
+ };
+
+ const onNodeAdded = (event) => {
+ const node = event.target;
+ const nodedata = event.target.data();
+
+ if (nodedata.finished === false || (nodedata.id !== undefined && nodedata.is_valid === undefined)
+ ) {
+ //if (nodedata.app_id === undefined) {
+ console.log("Returning because node is not valid: ", nodedata)
+ return;
+ }
- // 1. Guess what the next node's action should be
- // 2. Get result from previous nodes (if any)
- // 3. TRY to automatically map them in based on synonyms
- const newsource = cy.getElementById(edge.source)
- const newdst = cy.getElementById(edge.target)
- if (newsource !== undefined && newsource !== null && newdst !== undefined && newdst !== null) {
- //const srcdata = newsource.data()
- //console.log("EDGE: ", edge)
- const dstdata = RunAutocompleter(newdst.data())
- console.log("DST: ", dstdata)
- }
+ // DONT MOVE THIS LINE RIGHT HERE v
+ setLastSaved(false)
+ // Dont move the line above. May break stuff.
- var newbranch = {
- "source_id": edge.source,
- "destination_id": edge.target,
- "id_": edge.id,
- "id": edge.id,
- "hasErrors": false,
- "decorator": false,
- }
- if (!found) {
- newbranch["hasErrors"] = false
+ if (node.isNode() && cy.nodes().size() === 1) {
+ workflow.start = node.data("id");
+ nodedata.isStartNode = true;
+ } else {
+ if (workflow.actions === null) {
+ console.log("Returning because node has no value")
+ return;
+ }
- workflow.branches.push(newbranch)
- setWorkflow(workflow)
- }
+ // Remove bad startnode
+ for (var key in workflow.actions) {
+ const action = workflow.actions[key];
+ if (action.isStartNode && workflow.start !== action.id) {
+ action.isStartNode = false;
+ }
+ }
+ }
- history.push({
- "type": "edge",
- "action": "added",
- "data": edge,
- })
- setHistory(history)
- setHistoryIndex(history.length)
- }
-
- const onNodeAdded = (event) => {
- const node = event.target
- const nodedata = event.target.data()
- if (nodedata.finished === false || (nodedata.id !== undefined && nodedata.is_valid === undefined)) {
- //console.log("RETURNING (NOT ADDING) NODE ADD FOR: ", nodedata)
- return
- }
-
- //console.log("IS IT ADDED TO THE WORKFLOW?: ", nodedata)
- if (node.isNode() && cy.nodes().size() === 1) {
- //setStartNode(node.data('id'))
- workflow.start = node.data('id')
- nodedata.isStartNode = true
- } else {
- if (workflow.actions === null) {
- return
- }
-
- // Remove bad startnode
- const startnode_exists = false
- for (var key in workflow.actions) {
- const action = workflow.actions[key]
- if (action.isStartNode && workflow.start !== action.id) {
- action.isStartNode = false
+ if (nodedata.type === "ACTION") {
+ /*
+ var curaction = workflow.actions.find((a) => a.id === nodedata.id);
+ if (curaction === null || curaction === undefined) {
+ alert.error("Node not found. Please remake it.")
+ event.target.remove();
}
- }
- }
+ */
+ if (
+ workflow.actions.length === 1 &&
+ workflow.actions[0].id === workflow.start
+ ) {
+ const newEdgeUuid = uuidv4();
+ const newcybranch = {
+ source: workflow.start,
+ target: nodedata.id,
+ _id: newEdgeUuid,
+ id: newEdgeUuid,
+ hasErrors: false,
+ };
- if (nodedata.type === "ACTION") {
- /*
- const tmpView = localStorage.getItem(appSorter)
- const nodekey = nodedata.id
- if (tmpView === null || tmpView === undefined) {
- const basedata = {}
- basedata[nodekey] = 1
- localStorage.setItem(appSorter, JSON.stringify(basedata))
- } else {
- try {
- var parsed = JSON.parse(tmpView)
- try {
- parsed[nodekey] += 1
- } catch {
- parsed[nodekey] = 1
+ const edgeToBeAdded = {
+ group: "edges",
+ data: newcybranch,
+ };
+
+ console.log("SHOULD STITCH WITH STARTNODE");
+ cy.add(edgeToBeAdded);
+ }
+
+ if (nodedata.app_name === "Shuffle Tools") {
+ const iconInfo = GetIconInfo(nodedata);
+ const svg_pin = ` `;
+ const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
+ nodedata.large_image = svgpin_Url;
+ nodedata.fillGradient = iconInfo.fillGradient;
+ nodedata.fillstyle = "solid";
+ if (
+ nodedata.fillGradient !== undefined &&
+ nodedata.fillGradient !== null &&
+ nodedata.fillGradient.length > 0
+ ) {
+ nodedata.fillstyle = "linear-gradient";
+ } else {
+ nodedata.iconBackground = iconInfo.iconBackgroundColor;
+ }
+ }
+
+
+ if (
+ nodedata.parameters !== undefined &&
+ nodedata.parameters !== null &&
+ !nodedata.label.endsWith("_copy")
+ ) {
+ var newparameters = [];
+
+ for (var subkey in nodedata.parameters) {
+ var newparam = JSON.parse(
+ JSON.stringify(nodedata.parameters[subkey])
+ );
+ newparam.id = uuidv4();
+
+ if (newparam.value === undefined || newparam.value === null) {
+ newparam.value = "";
+ } else {
+ newparam.value = newparam.value;
}
- localStorage.setItem(appSorter, JSON.stringify(parsed))
- } catch {
- console.log("Bad data in tmpView: ", tmpView)
- }
- }
- console.log("FAVORITeS: ", tmpView)
- */
+ newparameters.push(newparam);
+ }
- if (workflow.actions.length === 1 && workflow.actions[0].id === workflow.start) {
- const newEdgeUuid = uuidv4()
- const newcybranch = {
- "source": workflow.start,
- "target": nodedata.id,
- "_id": newEdgeUuid,
- "id": newEdgeUuid,
- "hasErrors": false,
- }
+ nodedata.parameters = newparameters;
+ }
- const edgeToBeAdded = {
- group: "edges",
- data: newcybranch,
- }
+ if (workflow.actions === undefined || workflow.actions === null) {
+ workflow.actions = [nodedata];
+ } else {
+ workflow.actions.push(nodedata);
+ }
- console.log("SHOULD STITCH WITH STARTNODE")
- cy.add(edgeToBeAdded)
- }
-
- if (nodedata.app_name === "Shuffle Tools") {
- const iconInfo = GetIconInfo(nodedata)
- const svg_pin = ` `
- const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin)
- nodedata.large_image = svgpin_Url
- nodedata.fillGradient = iconInfo.fillGradient
- nodedata.fillstyle = "solid"
- if (nodedata.fillGradient !== undefined && nodedata.fillGradient !== null && nodedata.fillGradient.length > 0) {
- nodedata.fillstyle = 'linear-gradient'
- } else {
- nodedata.iconBackground = iconInfo.iconBackgroundColor
- }
- }
+ setWorkflow(workflow);
+ } else if (nodedata.type === "TRIGGER") {
+ if (nodedata.is_valid === false) {
+ alert.info("This trigger is not available to you");
+ node.remove();
+ return;
+ }
+
+ if (workflow.triggers === undefined) {
+ workflow.triggers = [nodedata];
+ } else {
+ workflow.triggers.push(nodedata);
+ }
+
+ const newEdgeUuid = uuidv4();
+ const newcybranch = {
+ source: nodedata.id,
+ target: workflow.start,
+ source_id: nodedata.id,
+ destination_id: workflow.start,
+ _id: newEdgeUuid,
+ id: newEdgeUuid,
+ hasErrors: false,
+ decorator: false,
+ };
+
+ const edgeToBeAdded = {
+ group: "edges",
+ data: newcybranch,
+ };
+
+ if (
+ nodedata.name !== "User Input" &&
+ nodedata.name !== "Shuffle Workflow"
+ ) {
+ if (
+ workflow.actions !== undefined &&
+ workflow.actions !== null &&
+ workflow.actions.length > 0
+ ) {
+ cy.add(edgeToBeAdded);
+ }
+ }
+
+ setWorkflow(workflow);
+ }
+
+ if (nodedata.app_name !== undefined) {
+ history.push({
+ type: "node",
+ action: "added",
+ data: nodedata,
+ });
+ setHistory(history);
+ setHistoryIndex(history.length);
+ }
+ };
+
+ const onEdgeRemoved = (event) => {
+ setLastSaved(false);
+
+
+ const edge = event.target;
+ if (edge.data("decorator") === true) {
+ return;
+ }
+
+ workflow.branches = workflow.branches.filter(
+ (a) => a.id !== edge.data().id
+ );
+ setWorkflow(workflow);
+ event.target.remove();
+
+ // trigger as source check
+ const indexcheck = workflow.triggers.findIndex(
+ (data) => edge.data()["source"] === data.id
+ );
+ if (indexcheck !== -1) {
+ console.log("Shouldnt remove edge from trigger");
+ }
+
+ if (edge.data().source !== undefined) {
+ history.push({
+ type: "edge",
+ action: "removed",
+ data: edge.data(),
+ });
+
+ setHistory(history);
+ setHistoryIndex(history.length);
+ }
+ };
+
+ const onNodeRemoved = (event) => {
+ const node = event.target;
+ const data = node.data();
+
+ if (data.finished === false) {
+ return
+ }
+
+ workflow.actions = workflow.actions.filter((a) => a.id !== data.id);
+ workflow.triggers = workflow.triggers.filter((a) => a.id !== data.id);
+ if (workflow.start === data.id && workflow.actions.length > 0) {
+ // FIXME - should check branches connected to startnode, as picking random
+ // is just confusing
+ if (workflow.actions[0].id !== data.id) {
+ const ele = cy.getElementById(workflow.actions[0].id);
+ if (ele !== undefined && ele !== null) {
+ ele.data("isStartNode", true);
+ workflow.start = ele.id();
+ }
+ } else {
+ if (workflow.actions.length > 1) {
+ const ele = cy.getElementById(workflow.actions[1].id);
+ if (ele !== undefined && ele !== null) {
+ ele.data("isStartNode", true);
+ workflow.start = ele.id();
+ }
+ }
+ }
+ }
+
+ if (data.app_name !== undefined) {
+ const allNodes = cy.nodes().jsons();
+ for (var key in allNodes) {
+ const currentNode = allNodes[key];
+ if (currentNode.data.attachedTo === data.id) {
+ cy.getElementById(currentNode.data.id).remove();
+ }
+ }
+
+ history.push({
+ type: "node",
+ action: "removed",
+ data: data,
+ })
+
+ //console.log("REMOVED: ", data)
+ setHistory(history);
+ setHistoryIndex(history.length);
+ }
+
+ setWorkflow(workflow);
+ if (data.type === "TRIGGER") {
+ saveWorkflow(workflow);
+ }
+ };
+
+ //var previouskey = 0
+ const handleKeyDown = (event) => {
+ switch (event.keyCode) {
+ case 27:
+ if (configureWorkflowModalOpen === true) {
+ setConfigureWorkflowModalOpen(false);
+ }
+ break;
+ case 46:
+ console.log("DELETE");
+ break;
+ case 38:
+ //console.log("UP");
+ break;
+ case 37:
+ //console.log("LEFT");
+ break;
+ case 40:
+ //console.log("DOWN");
+ break;
+ case 39:
+ //console.log("RIGHT");
+ break;
+ case 90:
+ if (event.ctrlKey) {
+ console.log("CTRL+Z");
+ }
+
+ break;
+ case 67:
+ if (event.ctrlKey && !event.shiftKey) {
+ if (
+ event.path !== undefined &&
+ event.path !== null &&
+ event.path.length > 0
+ ) {
+ if (event.path[0].localName !== "body") {
+ console.log("Skipping because body is not targeted");
+ return;
+ }
+ }
+
+ if (
+ event.target !== undefined &&
+ event.target !== null
+ ) {
+ if (event.target.localName !== "body") {
+ console.log("Skipping because body is not targeted")
+ return;
+ }
+ }
+
+ console.log("CTRL+C");
+ if (cy !== undefined) {
+ var cydata = cy.$(":selected").jsons();
+ if (cydata !== undefined && cydata !== null && cydata.length > 0) {
+ console.log(cydata);
+
+ const elementName = "copy_element_shuffle";
+ var copyText = document.getElementById(elementName);
+ if (copyText !== null && copyText !== undefined) {
+ const clipboard = navigator.clipboard;
+ if (clipboard === undefined) {
+ alert.error("Can only copy over HTTPS (port 3443)");
+ return;
+ }
+
+ navigator.clipboard.writeText(JSON.stringify(cydata));
+ copyText.select();
+ copyText.setSelectionRange(0, 99999); /* For mobile devices */
+ document.execCommand("copy");
+ alert.success(`Copied ${cydata.length} element(s)`);
+ }
+ }
+ }
+ }
+ break;
+ case 86:
+ if (event.ctrlKey) {
+ //console.log("CTRL+V")
+ // The below parts are handled in the function handlePaste()
+ /*
+ const clipboard = navigator.clipboard
+ if (clipboard === undefined || window === undefined || window === null) {
+ alert.error("Can only use cliboard over HTTPS (port 3443)")
+ return
+ }
+
+ console.log("CLIPBOARD: ", window.clipboardData)
+ const pastedData = window.clipboardData.getData('Text');
+ console.log("PASTED: ", pastedData)
- if (nodedata.parameters !== undefined && nodedata.parameters !== null && !nodedata.label.endsWith("_copy")) {
- var newparameters = []
- for (var subkey in nodedata.parameters) {
- var newparam = JSON.parse(JSON.stringify(nodedata.parameters[subkey]))
- newparam.id = uuidv4()
- newparam.value = ""
- newparameters.push(newparam)
- }
+ //var tmpAuth = JSON.parse(JSON.stringify(appAuthentication))
+ var jsonvalid = true
+ var parsedjson = []
+ */
+ }
+ break;
+ case 88:
+ if (event.ctrlKey) {
+ console.log("CTRL+X");
+ }
+ break;
+ case 83:
+ break;
+ case 70:
+ break;
+ case 65:
+ // As a poweruser myself, I found myself hitting this a few
+ // too many times to just edit text. Need a better bind, which does NOT work while inside a field
+ break;
+ default:
+ break;
+ }
- nodedata.parameters = newparameters
- }
+ //previouskey = event.keyCode
+ };
- if (workflow.actions === undefined || workflow.actions === null) {
- workflow.actions = [nodedata]
- } else {
- workflow.actions.push(nodedata)
- }
-
- setWorkflow(workflow)
- } else if (nodedata.type === "TRIGGER") {
- if (nodedata.is_valid === false) {
- alert.info("This trigger is not available to you")
- node.remove()
- return
- }
-
- if (workflow.triggers === undefined) {
- workflow.triggers = [nodedata]
- } else {
- workflow.triggers.push(nodedata)
- }
-
- const newEdgeUuid = uuidv4()
- const newcybranch = {
- "source": nodedata.id,
- "target": workflow.start,
- "source_id": nodedata.id,
- "destination_id": workflow.start,
- "_id": newEdgeUuid,
- "id": newEdgeUuid,
- "hasErrors": false,
- "decorator": false,
- }
-
- const edgeToBeAdded = {
- group: "edges",
- data: newcybranch,
- }
-
- if (nodedata.name !== "User Input" && nodedata.name !== "Shuffle Workflow") {
- //workflow.branches.push(newbranch)
- if (workflow.actions !== undefined && workflow.actions !== null && workflow.actions.length > 0) {
- cy.add(edgeToBeAdded)
- }
- }
-
- //if (data.trigger_type === "WEBHOOK") {
- // newWebhook(newAppData)
- // saveWorkflow(workflow)
- //}
-
- setWorkflow(workflow)
+ const handlePaste = (event) => {
+ //console.log("EV: ", event)
+ if (
+ event.path !== undefined &&
+ event.path !== null &&
+ event.path.length > 0
+ ) {
+ //console.log("PATH: ", event.path[0])
+ if (event.path[0].localName !== "body") {
+ //console.log("Skipping because body is not targeted")
+ return;
+ }
+ }
+
+ //console.log("PATH2: ", event.target)
+ if (
+ event.target !== undefined &&
+ event.target !== null
+ ) {
+ if (event.target.localName !== "body") {
+ //console.log("Skipping because body is not targeted")
+ return;
+ }
}
- if (nodedata.app_name !== undefined) {
- history.push({
- "type": "node",
- "action": "added",
- "data": nodedata,
- })
- setHistory(history)
- setHistoryIndex(history.length)
- }
+ event.preventDefault();
+ const clipboard = (event.originalEvent || event).clipboardData.getData(
+ "text/plain"
+ );
+ //console.log("Text: ", clipboard)
+ //window.document.execCommand('insertText', false, text);
+ //
+ try {
+ const parsedjson = JSON.parse(clipboard);
+ //console.log("Parsed: ", parsedjson)
- //if (nodedata.app_name !== undefined && ((
- // nodedata.app_name !== "Shuffle Tools" &&
- // nodedata.app_name !== "Testing" &&
- // nodedata.app_name !== "Shuffle Workflow" &&
- // nodedata.app_name !== "User Input" &&
- // nodedata.app_name !== "Webhook" &&
- // nodedata.app_name !== "Schedule" &&
- // nodedata.app_name !== "Email") || nodedata.isStartNode)
- //) {
- // const allNodes = cy.nodes().jsons()
- // var found = false
- // for (var key in allNodes) {
- // const currentNode = allNodes[key]
- // if (currentNode.data.attachedTo === nodedata.id && currentNode.data.isDescriptor) {
- // found = true
- // console.log("FOUND THE NODE!")
- // break
- // }
- // }
+ for (var key in parsedjson) {
+ const item = parsedjson[key];
+ console.log("Adding: ", item);
+ item.data.id = uuidv4()
- // // Readding the icon after moving the node
- // if (!found) {
- // console.log("Node wasn't found")
- // const iconInfo = GetIconInfo(nodedata)
- // const svg_pin = ` `
- // const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin)
+ cy.add({
+ group: item.group,
+ data: item.data,
+ position: item.position,
+ });
+ }
+ } catch (e) {
+ console.log("Error pasting: ", e);
+ //alert.info("Failed parsing clipboard: ", e)
+ }
+ };
- // const offset = nodedata.isStartNode ? 36 : 44
- // const decoratorNode = {
- // position: {
- // x: event.target.position().x+offset,
- // y: event.target.position().y+offset,
- // },
- // locked: true,
- // data: {
- // "isDescriptor": true,
- // "isValid": true,
- // "is_valid": true,
- // "label": "",
- // "image": svgpin_Url,
- // "imageColor": iconInfo.iconBackgroundColor,
- // "attachedTo": nodedata.id,
- // },
- // }
+ const registerKeys = () => {
+ document.addEventListener("keydown", handleKeyDown);
+ document.addEventListener("paste", handlePaste);
+ };
- // cy.add(decoratorNode)
- // } else {
- // console.log("Node already exists - don't add descriptor node")
- // }
- //} else {
- // //console.log("Shouldnt re-add info? ")
- //}
- }
-
- const onEdgeRemoved = (event) => {
- setLastSaved(false)
- const edge = event.target
-
- workflow.branches = workflow.branches.filter(a => a.id !== edge.data().id)
- setWorkflow(workflow)
- event.target.remove()
-
- // trigger as source check
- const indexcheck = workflow.triggers.findIndex(data => edge.data()["source"] === data.id)
- if (indexcheck !== -1) {
- //alert.error("Can't remove edge from a trigger")
- console.log("Shouldnt remove edge from trigger")
- //const edgeToBeAdded = {
- // group: "edges",
- // data: newcybranch,
- //}
- }
-
- if (edge.data().source !== undefined) {
- history.push({
- "type": "edge",
- "action": "removed",
- "data": edge.data().source,
- })
- setHistory(history)
- setHistoryIndex(history.length)
- }
- }
-
- const onNodeRemoved = (event) => {
- const node = event.target
- const data = node.data()
-
- if (data.finished === false) {
- return
- }
-
- //setLastSaved(false)
-
- workflow.actions = workflow.actions.filter(a => a.id !== data.id)
- workflow.triggers = workflow.triggers.filter(a => a.id !== data.id)
- if (workflow.start === data.id && workflow.actions.length > 0) {
- // FIXME - should check branches connected to startnode, as picking random
- // is just confusing
- if (workflow.actions[0].id !== data.id) {
- const ele = cy.getElementById(workflow.actions[0].id)
- if (ele !== undefined && ele !== null) {
- ele.data("isStartNode", true)
- workflow.start = ele.id()
- }
- } else {
- if (workflow.actions.length > 1) {
- const ele = cy.getElementById(workflow.actions[1].id)
- if (ele !== undefined && ele !== null) {
- ele.data("isStartNode", true)
- workflow.start = ele.id()
- }
- }
- }
-
- //cy.nodes().some(function( ele ) {
- // if (ele.id() !== workflow.start && ele.data()["label"] !== undefined) {
- // //alert.success("Changed startnode to "+ele.data()["label"])
- // ele.data("isStartNode", true)
- // workflow.start = ele.id()
- // throw BreakException
- // return false
- // }
- //})
- }
-
- if (data.app_name !== undefined) {
- //console.log("Trying to remove friendly nodes")
- const allNodes = cy.nodes().jsons()
- //console.log("NOT UNDEFINED IN HOVEROUT!", allNodes)
- for (var key in allNodes) {
- const currentNode = allNodes[key]
- if (currentNode.data.attachedTo === data.id) {
- cy.getElementById(currentNode.data.id).remove()
- }
- }
-
- history.push({
- "type": "node",
- "action": "removed",
- "data": data,
- })
- setHistory(history)
- setHistoryIndex(history.length)
- }
-
-
- setWorkflow(workflow)
- if (data.type === "TRIGGER") {
- saveWorkflow(workflow)
- }
- }
-
- var previouskey = 0
- const handleKeyDown = (event) => {
- // SHIFT = 16
- // CTRL = 17
- //console.log(event.keyCode)
- switch( event.keyCode ) {
- case 27:
- //console.log("ESCAPE")
- if (configureWorkflowModalOpen === true) {
- setConfigureWorkflowModalOpen(false)
- }
- break;
- case 46:
- //removeNode()
- console.log("DELETE")
- break;
- case 38:
- console.log("UP")
- break;
- case 37:
- console.log("LEFT")
- break;
- case 40:
- console.log("DOWN")
- break;
- case 39:
- console.log("RIGHT")
- break;
- case 90:
- if (previouskey === 17) {
- console.log("CTRL+Z")
- //handleHistoryUndo()
- }
-
- break;
- case 67:
- if (previouskey === 17) {
- console.log("CTRL+C")
- //const filteredelements = cy.filter(function(element, i){
- // return element.hasClass('selected')
- //})
-
- //for (var key in filteredelements) {
- //}
-
- //var copyText = document.getElementById("copy_element_shuffle")
- //if (copyText !== undefined && copyText !== null) {
- // const clipboard = navigator.clipboard
- // if (clipboard === undefined) {
- // alert.error("Can only copy over HTTPS (port 3443)")
- // return
- // }
- //}
- //console.log("FILTERED: ", filteredelements)
- }
- break;
- case 86:
- if (previouskey === 17) {
- console.log("CTRL+V")
- }
- break;
- case 88:
- if (previouskey === 17) {
- console.log("CTRL+V")
- }
- break;
- case 83:
- //if (previouskey === 17) {
- // event.preventDefault()
- // saveWorkflow()
- //}
- break;
- case 70:
- //if (previouskey === 17) {
- // event.preventDefault()
- // cy.fit(null, 50)
- //}
- //break;
- case 65:
- // As a poweruser myself, I found myself hitting this a few
- // too many times to just edit text. Need a better bind
- //
- //if (previouskey === 17) {
- // event.preventDefault()
- // if (executionRunning || executionRequestStarted) {
- // abortExecution()
- // } else {
- // executeWorkflow()
- // }
- // cy.fit(null, 50)
- //}
- break;
- default:
- //console.log(event.keyCode)
- break;
- }
-
- previouskey = event.keyCode
- }
-
- const registerKeys = () => {
- document.addEventListener("keydown", handleKeyDown);
- }
-
- const getEnvironments = () => {
- fetch(globalUrl+"/api/v1/getenvironments", {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- },
- credentials: "include",
+ const getEnvironments = () => {
+ fetch(globalUrl + "/api/v1/getenvironments", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
})
- .then((response) => {
- if (response.status !== 200) {
- console.log("Status not 200 for apps :O!")
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for envs :O!");
+ if (isCloud) {
+ setEnvironments([{ Name: "Cloud", Type: "cloud" }]);
+ } else {
+ setEnvironments([{ Name: "Onprem", Type: "onprem" }]);
+ }
+
+ return;
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ var found = false;
+ var showEnvCnt = 0;
+ for (var key in responseJson) {
+ if (responseJson[key].default) {
+ setDefaultEnvironmentIndex(key);
+ found = true;
+ }
+
+ if (responseJson[key].archived === false) {
+ showEnvCnt += 1;
+ }
+ }
+
+ if (showEnvCnt > 1) {
+ setShowEnvironment(true);
+ }
+
+ if (!found) {
+ for (var key in responseJson) {
+ if (!responseJson[key].archived) {
+ setDefaultEnvironmentIndex(key);
+ break;
+ }
+ }
+ }
+
+ // FIXME: Don't allow multiple in cloud yet. Cloud -> Onprem isn't stable.
if (isCloud) {
- setEnvironments({"name": "Cloud", "type": "cloud"})
- } else {
- setEnvironments({"name": "Onprem", "type": "onprem"})
- }
-
- return
- }
-
- return response.json()
- })
- .then((responseJson) => {
- var found = false
- var showEnvCnt = 0
- for (var key in responseJson) {
- if (responseJson[key].default) {
- setDefaultEnvironmentIndex(key)
- found = true
- }
-
- if (responseJson[key].archived === false) {
- showEnvCnt += 1
- }
- }
-
- if (showEnvCnt > 1) {
- setShowEnvironment(true)
- }
-
- if (!found) {
- for (var key in responseJson) {
- if (!responseJson[key].archived) {
- setDefaultEnvironmentIndex(key)
- break
+ console.log("Envs: ", responseJson)
+ if (responseJson !== undefined && responseJson !== null && responseJson.length > 0) {
+ setEnvironments(responseJson);
+ } else {
+ setEnvironments([{ Name: "Cloud", Type: "cloud" }]);
}
- }
- }
-
- setEnvironments(responseJson)
- })
- .catch(error => {
- alert.error(error.toString())
- });
- }
-
-
- if (!firstrequest && graphSetup && established && props.match.params.key !== workflow.id && workflow.id !== undefined && workflow.id !== null && workflow.id.length > 0) {
- //console.log(props.match.params.key, workflow.id)
- //getWorkflow()
- //setCy()
- //getWorkflowExecution(props.match.params.key, "")
- //setEstablished(false)
- //setGraphSetup(false)
- window.location.pathname = "/workflows/"+props.match.params.key
- }
-
- useEffect(() => {
- if (firstrequest) {
- setFirstrequest(false)
- getWorkflow(props.match.params.key, {})
- getApps()
- getAppAuthentication()
- getEnvironments()
- getWorkflowExecution(props.match.params.key, "")
- getAvailableWorkflows(-1)
- getSettings()
-
- const cursearch = typeof window === 'undefined' || window.location === undefined ? "" : window.location.search
- const tmpView = new URLSearchParams(cursearch).get("view")
- if (tmpView !== undefined && tmpView !== null && tmpView === "executions") {
- setExecutionModalOpen(true)
-
- const newitem = removeParam("view", cursearch)
- props.history.push(curpath+newitem)
- }
- return
- }
-
-
- // App length necessary cus of cy initialization
- //console.log("PRE ELEMENTS: !", workflow.actions, graphSetup, apps, authLoaded, cy)
- if (elements.length === 0 && workflow.actions !== undefined && !graphSetup && Object.getOwnPropertyNames(workflow).length > 0) {
- setGraphSetup(true)
- setupGraph()
-
- //console.log("IN ELEMENT CHECK!")
- } else if (!established && cy !== undefined && apps !== null && apps !== undefined && apps.length > 0 && Object.getOwnPropertyNames(workflow).length > 0 && authLoaded){
- //This part has to load LAST, as it's kind of not async.
- //This means we need everything else to happen first.
- //
- //console.log("IN THIS PART AGAIN")
-
- //console.log("IN ESTABLISHED!")
-
- setEstablished(true)
- // Validate if the node is just a node lol
- //console.log("CY: ", cy)
- //console.log("CY: ", cy.edgehandles())
- //try {
- cy.edgehandles({
- handleNodes: (el) => el.isNode() && !el.data("isButton") && !el.data("isDescriptor"),
- preview: false,
- toggleOffOnLeave: false,
- loopAllowed: function( node ){
- return false;
- },
- })
- //} catch (e) {
- // console.log("Error in edgehandles: ", e)
- //}
-
- cy.fit(null, 200)
-
- cy.on('boxselect', 'node', (e) => {
- if (e.target.data("isButton") || e.target.data("isDescriptor")) {
- e.target.unselect()
- }
-
- e.target.addClass("selected")
- })
-
- cy.on('boxstart', (e) => {
- console.log("START")
- cy.removeListener('select')
- //onNodeSelect(e, appAuthentication)
- })
- cy.on('boxend', (e) => {
- console.log("END")
- cy.removeListener('select')
- //onNodeSelect(e, appAuthentication)
- })
-
- cy.on('select', 'node', (e) => {
- onNodeSelect(e, appAuthentication)
- })
- cy.on('select', 'edge', (e) => onEdgeSelect(e))
-
- cy.on('unselect', (e) => onUnselect(e))
-
- cy.on('add', 'node', (e) => onNodeAdded(e))
- cy.on('add', 'edge', (e) => onEdgeAdded(e))
- cy.on('remove', 'node', (e) => onNodeRemoved(e))
- cy.on('remove', 'edge', (e) => onEdgeRemoved(e))
-
- cy.on('mouseover', 'edge', (e) => onEdgeHover(e))
- cy.on('mouseout', 'edge', (e) => onEdgeHoverOut(e))
- cy.on('mouseover', 'node', (e) => onNodeHover(e))
- cy.on('mouseout', 'node', (e) => onNodeHoverOut(e))
-
- // Handles dragging
- cy.on('drag', 'node', (e) => onNodeDrag(e, selectedAction))
- cy.on('free', 'node', (e) => onNodeDragStop(e, selectedAction))
-
- cy.on('cxttap', "node", (e) => onCtxTap(e))
-
- //let popper2 = cy.popper({
- // content: () => {
- // let div = document.createElement('div');
- //
- // div.innerHTML = 'Popper content'
- //
- // document.body.appendChild(div)
- //
- // return div
- // },
- // renderedPosition: () => ({ x: 100, y: 200 }),
- // popper: {} // my popper options here
- //});
-
- //let popper1 = cy.nodes()[0].popper({
- // content: () => {
- // let div = document.createElement('div')
- //
- // div.innerHTML = 'Popper content'
- // document.body.appendChild(div)
- //
- // return div
- // },
- // popper: {} // my popper options here
- //})
- //let update = () => {
- // popper.update()
- //}
-
- //let node = cy.nodes().first()
- //node.on('position', update)
-
-
-
-
-
- //cy.on('mouseover', 'node', () => $(targetElement).addClass('mouseover'));
-
- //cy.on('cxttapstart', 'node', (e) => edgeHandler.start(e.target))
- //cy.on('cxttapend', 'node', (e) => edgeHandler.stop())
- //cy.on('cxtdragover', 'node', (e) => edgeHandler.preview(e.target))
- //cy.on('cxtdragout', 'node', (e) => edgeHandler.unpreview(e.target))
-
- document.title = "Workflow - "+workflow.name
- registerKeys()
- //setStartNode(workflow.start)
- } else if (established) {
- //console.log("established - should fix colors of things")
- //console.log(cy.elements())
- }
- })
-
- //}, [selectedAction])
-
- var previousnodecolor = ""
- //var previousedgecolor = ""
- const animationDuration = 150
- const onNodeHoverOut = (event) => {
- const nodedata = event.target.data()
- if (nodedata.app_name !== undefined) {
- const allNodes = cy.nodes().jsons()
- for (var key in allNodes) {
- const currentNode = allNodes[key]
- if (currentNode.data.isButton && currentNode.data.attachedTo !== nodedata.id) {
- cy.getElementById(currentNode.data.id).remove()
- }
- }
- }
-
- var parsedStyle = {
- "border-width": "1px",
- 'font-size': '18px',
- }
-
- if ((nodedata.app_name === "Testing" || nodedata.app_name === "Shuffle Tools") && !nodedata.isStartNode) {
- parsedStyle = {
- "border-width": "1px",
- 'font-size': '0px',
- }
- }
-
- event.target.animate({
- style: parsedStyle,
- }, {
- duration: animationDuration,
- })
- }
-
- const buttonColor = "rgba(255,255,255,0.9)"
- const buttonBackgroundColor = "#1f2023"
- const addStartnodeButton = (event) => {
- var parentNode = cy.$('#' + event.target.data("id"));
- if (parentNode.data('isButton') || parentNode.data('buttonId'))
- return
-
- if (parentNode.data("isStartNode")) {
- return
- }
-
- //parentNode.lock()
- const px = parentNode.position('x') - 65
- const py = parentNode.position('y') - 45
- const circleId = newNodeId = uuidv4()
-
- parentNode.data('circleId', circleId)
-
- const iconInfo = {
- "icon": "M9.4 2H15V12H8L7.6 10H2V17H0V0H9L9.4 2ZM9 10H11V8H13V6H11V4H9V6L8 4V2H6V4H4V2H2V4H4V6H2V8H4V6H6V8H8V6L9 8V10ZM6 6V4H8V6H6ZM9 6H11V8H9V6Z",
- "iconColor": buttonColor,
- "iconBackgroundColor": buttonBackgroundColor,
- }
-
- const svg_pin = ` `
- const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin)
-
- cy.add({
- group: 'nodes',
- data: {
- weight: 30,
- id: circleId,
- name: "TEEEXT",
- isButton: true,
- buttonType: "set_startnode",
- attachedTo: event.target.data("id"),
- icon: svgpin_Url,
- iconBackground: iconInfo.iconBackgroundColor,
- is_valid: true,
- },
- position: { x: px, y: py },
- locked: true
- })
- //.unselectify()
- }
-
- const addCopyButton = (event) => {
- var parentNode = cy.$('#' + event.target.data("id"));
- if (parentNode.data('isButton') || parentNode.data('buttonId'))
- return
-
- //parentNode.lock()
- const px = parentNode.position('x') - 65
- const py = parentNode.position('y') - 5
- const circleId = newNodeId = uuidv4()
-
- parentNode.data('circleId', circleId)
-
- const iconInfo = {
- "icon": "M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z",
- "iconColor": buttonColor,
- "iconBackgroundColor": buttonBackgroundColor,
- }
-
- const svg_pin = ` `
- const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin)
-
- cy.add({
- group: 'nodes',
- data: {
- weight: 30,
- id: circleId,
- name: "TEEEXT",
- isButton: true,
- buttonType: "copy",
- attachedTo: event.target.data("id"),
- icon: svgpin_Url,
- iconBackground: iconInfo.iconBackgroundColor,
- is_valid: true,
- },
- position: { x: px, y: py },
- locked: true
- })
- //.unselectify()
- }
-
- const addDeleteButton = (event) => {
- var parentNode = cy.$('#' + event.target.data("id"));
- if (parentNode.data('isButton') || parentNode.data('buttonId'))
- return
-
- //parentNode.lock()
- const px = parentNode.position('x') - 65
- const py = parentNode.position('y') + 35
- const circleId = newNodeId = uuidv4()
-
- parentNode.data('circleId', circleId)
-
- const iconInfo = {
- "icon": "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z",
- "iconColor": buttonColor,
- "iconBackgroundColor": buttonBackgroundColor,
- }
- const svg_pin = ` `
- const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin)
-
- cy.add({
- group: 'nodes',
- data: {
- weight: 30,
- id: circleId,
- name: "TEEEXT",
- isButton: true,
- buttonType: "delete",
- attachedTo: event.target.data("id"),
- icon: svgpin_Url,
- iconBackground: iconInfo.iconBackgroundColor,
- is_valid: true,
- },
- position: { x: px, y: py },
- locked: true
- })
- //.unselectify()
- }
-
- const onNodeHover = (event) => {
- //console.log("TAR: ", event.target)
- const nodedata = event.target.data()
- if (nodedata.finished === false) {
- return
- }
-
- var parentNode = cy.$('#' + event.target.data("id"));
- if (parentNode.data('isButton') || parentNode.data('buttonId'))
- return
-
- if (nodedata.app_name !== undefined) {
- const allNodes = cy.nodes().jsons()
-
- var found = false
- for (var key in allNodes) {
- const currentNode = allNodes[key]
- if (currentNode.data.isButton && currentNode.data.attachedTo !== nodedata.id) {
- cy.getElementById(currentNode.data.id).remove()
- }
-
- if (currentNode.data.isButton && currentNode.data.attachedTo === nodedata.id) {
- found = true
- }
- }
-
- if (!found) {
- addDeleteButton(event)
- addCopyButton(event)
- addStartnodeButton(event)
- }
- }
-
-
- const parsedStyle = {
- "border-width": "7px",
- "border-opacity": ".7",
- 'font-size': '25px',
- 'color': 'white',
- }
-
- event.target.animate({
- style: parsedStyle
- }, {
- duration: animationDuration,
- })
-
- previousnodecolor = event.target.style("border-color")
- }
-
- const onEdgeHoverOut = (event) => {
- if (event === null || event === undefined) {
- event.target.removeStyle()
- return
- }
-
- const edgeData = event.target.data()
- if (edgeData.decorator === true) {
- return
- }
-
- event.target.removeStyle()
- }
-
- // This is here to have a proper transition for lines
- const onEdgeHover = (event) => {
- if (event === null || event === undefined) {
- return
- }
-
- const edgeData = event.target.data()
- if (edgeData.decorator === true) {
- return
- }
-
- const sourcecolor = cy.getElementById(event.target.data("source")).style("border-color")
- const targetcolor = cy.getElementById(event.target.data("target")).style("border-color")
- if (sourcecolor !== null && sourcecolor !== undefined && targetcolor !== null && targetcolor !== undefined) {
- event.target.animate({
- style: {
- 'target-arrow-color': targetcolor,
- "line-fill": "linear-gradient",
- "line-gradient-stop-colors": [sourcecolor, targetcolor],
- "line-gradient-stop-positions": [0, 1],
- },
- duration: 0,
- })
- }
- }
-
- const setupGraph = () => {
- const actions = workflow.actions.map(action => {
- const node = {}
-
- if (!action.isStartNode && action.app_name === "Shuffle Tools") {
- const iconInfo = GetIconInfo(action)
- const svg_pin = ` `
- const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin)
- action.large_image = svgpin_Url
- action.fillGradient = iconInfo.fillGradient
- action.fillstyle = "solid"
- if (action.fillGradient !== undefined && action.fillGradient !== null && action.fillGradient.length > 0) {
- action.fillstyle = 'linear-gradient'
- //console.log("GRADIENT!: ", action)
- //action.fillstyle =
- //'background-fill': 'data(fillstyle)',
} else {
- action.iconBackground = iconInfo.iconBackgroundColor
+ setEnvironments(responseJson);
}
- //console.log("FOUND NODE INFO: ", action)
- }
+ })
+ .catch((error) => {
+ alert.error(error.toString());
+ });
+ };
- node.position = action.position
- node.data = action
+ if (
+ !firstrequest &&
+ graphSetup &&
+ established &&
+ props.match.params.key !== workflow.id &&
+ workflow.id !== undefined &&
+ workflow.id !== null &&
+ workflow.id.length > 0
+ ) {
+ window.location.pathname = "/workflows/" + props.match.params.key;
+ }
- node.data._id = action["id"]
- node.data.type = "ACTION"
- node.isStartNode = action["id"] === workflow.start
+ const animationDuration = 150;
+ const onNodeHoverOut = (event) => {
+ const nodedata = event.target.data();
+ if (nodedata.app_name !== undefined) {
+ const allNodes = cy.nodes().jsons();
+ for (var key in allNodes) {
+ const currentNode = allNodes[key];
+ if (
+ currentNode.data.isButton &&
+ currentNode.data.attachedTo !== nodedata.id
+ ) {
+ cy.getElementById(currentNode.data.id).remove();
+ }
+ }
+ }
+ const cytoscapeElement = document.getElementById("cytoscape_view")
+ if (cytoscapeElement !== undefined && cytoscapeElement !== null) {
+ cytoscapeElement.style.cursor = "default"
+ }
- var example = ""
- if (action.example !== undefined && action.example !== null && action.example.length > 0) {
- example = action.example
- }
-
- node.data.example = example
-
- return node;
- })
-
- const decoratorNodes = workflow.actions.map(action => {
- if (!action.isStartNode) {
- if (action.app_name === "Testing") {
- return null
- } else if (action.app_name === "Shuffle Tools") {
- return null
+ // Skipping node editing if it's the selected one
+ if (cy !== undefined) {
+ const typeIds = cy.elements('node:selected').jsons();
+ for (var idkey in typeIds) {
+ const item = typeIds[idkey]
+ if (item.data.id === nodedata.id) {
+ console.log("items: ", item.data.id, nodedata.id)
+ return
}
}
+ }
+ //if (nodedata.id === selectedAction.id || nodedata.id === selectedTrigger.id) {
+ // return
+ //}
- const iconInfo = GetIconInfo(action)
- const svg_pin = ` `
- const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin)
+ var parsedStyle = {
+ "border-width": "1px",
+ "font-size": "18px",
+ //"cursor": "default",
+ }
- const offset = action.isStartNode ? 36 : 44
- const decoratorNode = {
- position: {
- x: action.position.x+offset,
- y: action.position.y+offset,
- },
- locked: true,
- data: {
- "isDescriptor": true,
- "isValid": true,
- "is_valid": true,
- "label": "",
- "image": svgpin_Url,
- "imageColor": iconInfo.iconBackgroundColor,
- "attachedTo": action.id,
+ if ((nodedata.app_name === "Testing" || nodedata.app_name === "Shuffle Tools") && !nodedata.isStartNode) {
+ parsedStyle = {
+ "border-width": "1px",
+ "font-size": "0px",
+ };
+ }
+
+ event.target.animate(
+ {
+ style: parsedStyle,
+ },
+ {
+ duration: animationDuration,
+ }
+ );
+
+ const outgoingEdges = event.target.outgoers("edge");
+ const incomingEdges = event.target.incomers("edge");
+ if (outgoingEdges.length > 0) {
+ outgoingEdges.removeClass("hover-highlight");
+ }
+
+ if (incomingEdges.length > 0) {
+ outgoingEdges.removeClass("hover-highlight");
+ }
+ };
+
+ const buttonColor = "rgba(255,255,255,0.9)";
+ const buttonBackgroundColor = "#1f2023";
+ const addStartnodeButton = (event) => {
+ var parentNode = cy.$("#" + event.target.data("id"));
+ if (parentNode.data("isButton") || parentNode.data("buttonId")) return;
+
+ if (parentNode.data("isStartNode")) {
+ return;
+ }
+
+ const px = parentNode.position("x") - 65;
+ const py = parentNode.position("y") - 45;
+ const circleId = (newNodeId = uuidv4());
+
+ parentNode.data("circleId", circleId);
+
+ const iconInfo = {
+ icon: "M9.4 2H15V12H8L7.6 10H2V17H0V0H9L9.4 2ZM9 10H11V8H13V6H11V4H9V6L8 4V2H6V4H4V2H2V4H4V6H2V8H4V6H6V8H8V6L9 8V10ZM6 6V4H8V6H6ZM9 6H11V8H9V6Z",
+ iconColor: buttonColor,
+ iconBackgroundColor: buttonBackgroundColor,
+ };
+
+ const svg_pin = ` `;
+ const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
+
+ cy.add({
+ group: "nodes",
+ data: {
+ weight: 30,
+ id: circleId,
+ name: "TEEEXT",
+ isButton: true,
+ buttonType: "set_startnode",
+ attachedTo: event.target.data("id"),
+ icon: svgpin_Url,
+ iconBackground: iconInfo.iconBackgroundColor,
+ is_valid: true,
+ },
+ position: { x: px, y: py },
+ locked: true,
+ });
+ };
+
+ const addCopyButton = (event) => {
+ var parentNode = cy.$("#" + event.target.data("id"));
+ if (parentNode.data("isButton") || parentNode.data("buttonId")) return;
+
+ const px = parentNode.position("x") - 65;
+ const py = parentNode.position("y") - 5;
+ const circleId = (newNodeId = uuidv4());
+
+ parentNode.data("circleId", circleId);
+
+ const iconInfo = {
+ icon: "M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z",
+ iconColor: buttonColor,
+ iconBackgroundColor: buttonBackgroundColor,
+ };
+
+ const svg_pin = ` `;
+ const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
+
+ cy.add({
+ group: "nodes",
+ data: {
+ weight: 30,
+ id: circleId,
+ name: "TEEEXT",
+ isButton: true,
+ buttonType: "copy",
+ attachedTo: event.target.data("id"),
+ icon: svgpin_Url,
+ iconBackground: iconInfo.iconBackgroundColor,
+ is_valid: true,
+ },
+ position: { x: px, y: py },
+ locked: true,
+ });
+ };
+
+ const addSuggestionButtons = (event) => {
+ var parentNode = cy.$("#" + event.target.data("id"));
+ if (parentNode.data("isButton") || parentNode.data("buttonId")) return;
+
+ const px = parentNode.position("x") + 300;
+ const py = parentNode.position("y") + 0;
+ const circleId = (newNodeId = uuidv4());
+
+ parentNode.data("circleId", circleId);
+
+ var appid = "1234"
+ var suggestions = [{
+ app_name: "TheHive",
+ app_version: "1.1.0",
+ app_id: appid,
+ sharing: false,
+ private_id: false,
+ isStartNode: false,
+ label: "Suggestion 1",
+ large_image: "data:image/png;base64,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",
+ finished: false,
+ is_valid: true,
+ isSuggestion: true,
+ attachedTo: event.target.data("id"),
+ }]
+ //isButton: true,
+
+ cy.add({
+ group: "nodes",
+ data: suggestions[0],
+ position: { x: px, y: py },
+ locked: true,
+ });
+ }
+
+ const addDeleteButton = (event) => {
+ var parentNode = cy.$("#" + event.target.data("id"));
+ if (parentNode.data("isButton") || parentNode.data("buttonId")) return;
+
+ const px = parentNode.position("x") - 65;
+ const py = parentNode.position("y") + 35;
+ const circleId = (newNodeId = uuidv4());
+
+ parentNode.data("circleId", circleId);
+
+ const iconInfo = {
+ icon: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z",
+ iconColor: buttonColor,
+ iconBackgroundColor: buttonBackgroundColor,
+ };
+ const svg_pin = ` `;
+ const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
+
+ cy.add({
+ group: "nodes",
+ data: {
+ weight: 30,
+ id: circleId,
+ name: "TEEEXT",
+ isButton: true,
+ buttonType: "delete",
+ attachedTo: event.target.data("id"),
+ icon: svgpin_Url,
+ iconBackground: iconInfo.iconBackgroundColor,
+ is_valid: true,
+ },
+ position: { x: px, y: py },
+ locked: true,
+ });
+ };
+
+ const onNodeHover = (event) => {
+ const nodedata = event.target.data();
+
+ if (nodedata.finished === false) {
+ console.log("NODE: ", nodedata)
+ return;
+ }
+
+ const cytoscapeElement = document.getElementById("cytoscape_view")
+ if (cytoscapeElement !== undefined && cytoscapeElement !== null) {
+ cytoscapeElement.style.cursor = "pointer"
+ }
+
+ //var parentNode = cy.$("#" + event.target.data("id"));
+ //if (parentNode.data("isButton") || parentNode.data("buttonId")) return;
+
+ if (nodedata.app_name !== undefined && !workflow.public === true) {
+ const allNodes = cy.nodes().jsons();
+
+ var found = false;
+ for (var key in allNodes) {
+ const currentNode = allNodes[key];
+ if (
+ currentNode.data.isButton &&
+ currentNode.data.attachedTo !== nodedata.id
+ ) {
+ cy.getElementById(currentNode.data.id).remove();
+ }
+
+ if (
+ currentNode.data.isButton &&
+ currentNode.data.attachedTo === nodedata.id
+ ) {
+ found = true;
+ }
+ }
+
+ if (!found) {
+ addDeleteButton(event);
+
+ if (nodedata.type === "TRIGGER") {
+ if (nodedata.trigger_type === "SUBFLOW" || nodedata.trigger_type === "USERINPUT") {
+ addCopyButton(event);
+ }
+ } else {
+ addCopyButton(event);
+ addStartnodeButton(event);
+ }
+
+ //addSuggestionButtons(event)
+ }
+ }
+
+ var parsedStyle = {
+ "border-width": "7px",
+ "border-opacity": ".7",
+ "font-size": "25px",
+ //"cursor": "pointer",
+ }
+
+ const typeIds = cy.elements('node:selected').jsons();
+ for (var idkey in typeIds) {
+ const item = typeIds[idkey]
+ if (item.data.id === nodedata.id) {
+ //console.log("items: ", item.data.id, nodedata.id)
+ parsedStyle["border-width"] = "12px"
+ break
+ }
+ }
+
+ if (nodedata.type !== "COMMENT") {
+ parsedStyle.color = "white";
+ }
+
+ if (event.target !== undefined && event.target !== null) {
+ event.target.animate(
+ {
+ style: parsedStyle,
},
+ {
+ duration: animationDuration,
+ }
+ );
+ }
+
+ const outgoingEdges = event.target.outgoers("edge");
+ const incomingEdges = event.target.incomers("edge");
+ if (outgoingEdges.length > 0) {
+ outgoingEdges.addClass("hover-highlight");
+ }
+
+ if (incomingEdges.length > 0) {
+ outgoingEdges.addClass("hover-highlight");
+ }
+ };
+
+ const onEdgeHoverOut = (event) => {
+ if (event === null || event === undefined) {
+ event.target.removeStyle();
+ return;
+ }
+
+ const edgeData = event.target.data();
+ if (edgeData.decorator === true) {
+ return;
+ }
+
+ //event.target.removeStyle();
+ };
+
+ // This is here to have a proper transition for lines
+ const onEdgeHover = (event) => {
+ if (event === null || event === undefined) {
+ return;
+ }
+
+ const edgeData = event.target.data();
+ if (edgeData.decorator === true) {
+ return;
+ }
+
+ const sourcecolor = cy
+ .getElementById(event.target.data("source"))
+ .style("border-color");
+ const targetcolor = cy
+ .getElementById(event.target.data("target"))
+ .style("border-color");
+
+ //console.log(sourcecolor, targetcolor)
+ if (
+ sourcecolor !== null &&
+ sourcecolor !== undefined &&
+ targetcolor !== null &&
+ targetcolor !== undefined &&
+ !sourcecolor.includes("rgb") &&
+ !targetcolor.includes("rgb")
+ ) {
+ console.log(sourcecolor)
+ console.log(targetcolor)
+
+ if (event.target !== null && event.target.value !== null) {
+ event.target.animate({
+ style: {
+ "target-arrow-color": targetcolor,
+ "line-fill": "linear-gradient",
+ "line-gradient-stop-colors": [sourcecolor, targetcolor],
+ "line-gradient-stop-positions": [0, 1],
+ },
+ duration: animationDuration,
+ })
+ } else {
+ event.target.animate({
+ style: {
+ "target-arrow-color": targetcolor,
+ "line-fill": "linear-gradient",
+ "line-gradient-stop-colors": ["#41dcab", "#41dcab"],
+ "line-gradient-stop-positions": [0, 1],
+ },
+ duration: animationDuration,
+ })
+
}
- return decoratorNode
- return null
- })
+ }
+ }
- const triggers = workflow.triggers.map(trigger => {
- const node = {}
- node.position = trigger.position
- node.data = trigger
+ // Thanks :)
+ // https://codepen.io/guillaumethomas/pen/xxbbBKO
+ const calculateEdgeCurve = (sourcenodePosition, destinationnodePosition) => {
+ const xParsed = destinationnodePosition.x - sourcenodePosition.x
+ const yParsed = destinationnodePosition.y - sourcenodePosition.y
- node.data._id = trigger["id"]
- node.data.type = "TRIGGER"
+ const z = Math.sqrt(xParsed * xParsed + yParsed * yParsed);
+ const costheta = xParsed / z;
+ const alpha = 0.25;
+ var controlPointDistance = [-alpha * yParsed * costheta, alpha * yParsed * costheta];
+ var controlPointWeight = [alpha, 1-alpha]
- return node;
- })
+ //'control-point-weight': ['0.33', '0.66'],
+ //var controlPointWeight = ["0.33", "0.66"]
+ //var controlPointDistance = ["33%", "-66%"]
+ //var controlPointWeight = ["0.00", "1.00"]
+ /*
+ if (yParsed !== 0) {
+ //const degreeFound = Math.atan2(xParsed / yParsed)
+ const degreeFound = Math.atan2(xParsed, yParsed) * 180 / Math.PI
- // FIXME - tmp branch update
- var insertedNodes = [].concat(actions, triggers, decoratorNodes)
- insertedNodes = insertedNodes.filter(node => node !== null)
+ if (degreeFound > 90 && degreeFound < 180) {
+ console.log("TOPRIGHT")
+ } else if (degreeFound < 90 && degreeFound > 0) {
+ console.log("BOTTOMRIGHT")
+ } else if (degreeFound < 0 && degreeFound > -90) {
+ console.log("BOTTOMLEFT")
+ //controlPointWeight = ["0.20", "0.80"]
+ //controlPointWeight = "0.7"
+ //controlPointDistance = "50%"
- var edges = workflow.branches.map((branch, index) => {
- //workflow.branches[index].conditions = [{
-
- const edge = { };
- var conditions = workflow.branches[index].conditions
- if (conditions === undefined || conditions === null) {
- conditions = []
+ } else if (degreeFound < -90 && degreeFound > -180) {
+ console.log("TOPLEFT")
+ } else {
+ console.log("STRAIGHT!")
}
+ }
+ */
- var label = ""
- if (conditions.length === 1) {
- label = conditions.length+" condition"
- } else if (conditions.length > 1) {
- label = conditions.length+" conditions"
- }
+ return {
+ "distance": controlPointDistance,
+ "weight": controlPointWeight,
+ }
+ }
- edge.data = {
- id: branch.id,
- _id: branch.id,
- source: branch.source_id,
- target: branch.destination_id,
- label: label,
- conditions: conditions,
- hasErrors: branch.has_errors,
- decorator: false,
- }
+ const setupGraph = () => {
+ const actions = workflow.actions.map((action) => {
+ const node = {};
- // This is an attempt at prettier edges. The numbers are weird to work with.
- /*
+ if (!action.isStartNode && action.app_name === "Shuffle Tools") {
+ const iconInfo = GetIconInfo(action);
+ const svg_pin = ` `;
+ const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
+ action.large_image = svgpin_Url;
+ action.fillGradient = iconInfo.fillGradient;
+ action.fillstyle = "solid";
+ if (
+ action.fillGradient !== undefined &&
+ action.fillGradient !== null &&
+ action.fillGradient.length > 0
+ ) {
+ action.fillstyle = "linear-gradient";
+ } else {
+ action.iconBackground = iconInfo.iconBackgroundColor;
+ }
+ }
+
+ node.position = action.position;
+ node.data = action;
+
+ node.data._id = action["id"];
+ node.data.type = "ACTION";
+ node.isStartNode = action["id"] === workflow.start;
+
+ var example = "";
+ if (
+ action.example !== undefined &&
+ action.example !== null &&
+ action.example.length > 0
+ ) {
+ example = action.example;
+ }
+
+ node.data.example = example;
+
+ return node;
+ });
+
+ const decoratorNodes = workflow.actions.map((action) => {
+ if (!action.isStartNode) {
+ if (action.app_name === "Testing") {
+ return null;
+ } else if (action.app_name === "Shuffle Tools") {
+ return null;
+ }
+ }
+
+ const iconInfo = GetIconInfo(action);
+ const svg_pin = ` `;
+ const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
+
+ const offset = action.isStartNode ? 36 : 44;
+ const decoratorNode = {
+ position: {
+ x: action.position.x + offset,
+ y: action.position.y + offset,
+ },
+ locked: true,
+ data: {
+ isDescriptor: true,
+ isValid: true,
+ is_valid: true,
+ label: "",
+ image: svgpin_Url,
+ imageColor: iconInfo.iconBackgroundColor,
+ attachedTo: action.id,
+ },
+ };
+ return decoratorNode;
+ });
+
+ const triggers = workflow.triggers.map((trigger) => {
+ const node = {};
+ node.position = trigger.position;
+ node.data = trigger;
+
+ node.data._id = trigger["id"];
+ node.data.type = "TRIGGER";
+
+ return node;
+ });
+
+ var comments = [];
+ if (
+ workflow.comments !== undefined &&
+ workflow.comments !== null &&
+ workflow.comments.length > 0
+ ) {
+ comments = workflow.comments.map((comment) => {
+ const node = {};
+ node.position = comment.position;
+ node.data = comment;
+
+ node.data._id = comment["id"];
+ node.data.type = "COMMENT";
+
+ return node;
+ });
+ }
+
+ // FIXME - tmp branch update
+ var insertedNodes = [].concat(actions, triggers, decoratorNodes, comments);
+ insertedNodes = insertedNodes.filter((node) => node !== null);
+
+ var edges = workflow.branches.map((branch, index) => {
+ const edge = {};
+ var conditions = workflow.branches[index].conditions;
+ if (conditions === undefined || conditions === null) {
+ conditions = [];
+ }
+
+ var label = "";
+ if (conditions.length === 1) {
+ label = conditions.length + " condition";
+ } else if (conditions.length > 1) {
+ label = conditions.length + " conditions";
+ }
+
+ edge.data = {
+ id: branch.id,
+ _id: branch.id,
+ source: branch.source_id,
+ target: branch.destination_id,
+ label: label,
+ conditions: conditions,
+ hasErrors: branch.has_errors,
+ decorator: false,
+ };
+
+ // This is an attempt at prettier edges. The numbers are weird to work with.
+ // Bezier curves
//http://manual.graphspace.org/projects/graphspace-python/en/latest/demos/edge-types.html
const sourcenode = actions.find(node => node.data._id === branch.source_id)
const destinationnode = actions.find(node => node.data._id === branch.destination_id)
if (sourcenode !== undefined && destinationnode !== undefined && branch.source_id !== branch.destination_id) {
//node.data._id = action["id"]
- console.log("SOURCE: ", sourcenode.position)
- console.log("DESTINATIONNODE: ", destinationnode.position)
-
- var opposite = true
- if (sourcenode.position.x > destinationnode.position.x) {
- opposite = false
- } else {
- opposite = true
- }
+ //console.log("SOURCE: ", sourcenode.position)
+ //console.log("DESTINATIONNODE: ", destinationnode.position)
+ const edgeCurve = calculateEdgeCurve(sourcenode.position, destinationnode.position)
edge.style = {
- 'control-point-distance': opposite ? ["25%", "-75%"] : ["-10%", "90%"],
- 'control-point-weight': ['0.3', '0.7'],
- }
- }
- */
-
- return edge;
- })
-
- if (workflow.visual_branches !== undefined && workflow.visual_branches !== null && workflow.visual_branches.length > 0) {
- const visualedges = workflow.visual_branches.map((branch, index) => {
- const edge = { };
-
- if (workflow.branches[index] === undefined) {
- return {}
- }
-
- var conditions = workflow.branches[index].conditions
- if (conditions === undefined || conditions === null) {
- conditions = []
- }
-
- const label = "Subflow"
- edge.data = {
- id: branch.id,
- _id: branch.id,
- source: branch.source_id,
- target: branch.destination_id,
- label: label,
- decorator: true,
- }
-
- return edge;
- })
-
- edges = edges.concat(visualedges)
- }
-
- setWorkflow(workflow)
-
- // Verifies if a branch is valid and skips others
- var newedges = []
- for (var key in edges) {
- var item = edges[key]
- if (item.data === undefined) {
- continue
- }
-
- const sourcecheck = insertedNodes.find(data => data.data.id === item.data.source)
- const destcheck = insertedNodes.find(data => data.data.id === item.data.target)
- if (sourcecheck === undefined || destcheck === undefined) {
- continue
- }
-
- newedges.push(item)
- }
-
- insertedNodes = insertedNodes.concat(newedges)
- setElements(insertedNodes)
- }
-
- const removeNode = () => {
- setSelectedApp({})
- setSelectedAction({})
-
- const selectedNode = cy.$(':selected')
- if (selectedNode.data() === undefined) {
- return
- }
-
- if (selectedNode.data().type === "TRIGGER") {
- console.log("Should remove trigger!")
- console.log(selectedNode.data())
- const triggerindex = workflow.triggers.findIndex(data => data.id === selectedNode.data().id)
- setSelectedTriggerIndex(triggerindex)
- if (selectedNode.data().trigger_type === "SCHEDULE") {
- setSelectedTrigger(selectedNode.data())
- stopSchedule(selectedNode.data(), triggerindex)
- } else if (selectedNode.data().trigger_type === "WEBHOOK") {
- setSelectedTrigger(selectedNode.data())
- deleteWebhook(selectedNode.data(), triggerindex)
- } else if (selectedNode.data().trigger_type === "EMAIL") {
- setSelectedTrigger(selectedNode.data())
- stopMailSub(selectedTrigger, triggerindex)
- }
-
- }
-
- if (selectedNode.data().decorator === true) {
- alert.info("This edge can't be deleted.")
- } else {
- selectedNode.remove()
- }
-
- setSelectedTrigger({})
- setSelectedTriggerIndex({})
- }
-
- const stopSchedule = (trigger, triggerindex) => {
- //alert.info("Stopping schedule")
- fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key+"/schedule/"+trigger.id, {
- method: 'DELETE',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- },
- credentials: "include",
- })
- .then((response) => {
- if (response.status !== 200) {
- console.log("Status not 200 for stream results :O!")
- }
-
- return response.json()
- })
- .then((responseJson) => {
- // No matter what, it's being stopped.
- if (!responseJson.success) {
- if (responseJson.reason !== undefined) {
- alert.error("Failed to stop schedule: " + responseJson.reason)
+ 'control-point-distance': edgeCurve.distance,
+ 'control-point-weight': edgeCurve.weight,
}
} else {
- alert.success("Successfully stopped schedule")
+ console.log("FAILED node curve handling")
}
- workflow.triggers[triggerindex].status = "stopped"
- trigger.status = "stopped"
- setSelectedTrigger(trigger)
- setWorkflow(workflow)
- saveWorkflow(workflow)
- })
- .catch(error => {
- alert.error(error.toString())
- });
- }
+ return edge;
+ });
- //const submitSchedule = (id, name, frequency, executionArg) => {
- const submitSchedule = (trigger, triggerindex) => {
- //const cronSplit = workflow.triggers[triggerindex].parameters[0].value.split("*")
- //if (cronSplit.length <= 5 || cronSplit.length > 6) {
- // alert.error("Error: Bad cron, example run every 1 minute: */1 * * * *")
- // return
+ if (
+ workflow.visual_branches !== undefined &&
+ workflow.visual_branches !== null &&
+ workflow.visual_branches.length > 0
+ ) {
+ const visualedges = workflow.visual_branches.map((branch, index) => {
+ const edge = {};
+
+ if (workflow.branches[index] === undefined) {
+ return {};
+ }
+
+ var conditions = workflow.branches[index].conditions;
+ if (conditions === undefined || conditions === null) {
+ conditions = [];
+ }
+
+ const label = "Subflow";
+ edge.data = {
+ id: branch.id,
+ _id: branch.id,
+ source: branch.source_id,
+ target: branch.destination_id,
+ label: label,
+ decorator: true,
+ };
+
+ return edge;
+ });
+
+ edges = edges.concat(visualedges);
+ }
+
+ setWorkflow(workflow);
+
+ // Verifies if a branch is valid and skips others
+ var newedges = [];
+ for (var key in edges) {
+ var item = edges[key];
+ if (item.data === undefined) {
+ continue;
+ }
+
+ const sourcecheck = insertedNodes.find(
+ (data) => data.data.id === item.data.source
+ );
+ const destcheck = insertedNodes.find(
+ (data) => data.data.id === item.data.target
+ );
+ if (sourcecheck === undefined || destcheck === undefined) {
+ continue;
+ }
+
+ newedges.push(item);
+ }
+
+ insertedNodes = insertedNodes.concat(newedges);
+ setElements(insertedNodes);
+ };
+
+ const removeNode = (nodeId) => {
+ const selectedNode = cy.getElementById(nodeId);
+ if (selectedNode.data() === undefined) {
+ console.log("No node to remove")
+ return;
+ }
+
+ //console.log("Removing node: ", selectedNode.data("id"), "Action: ", selectedAction.id)
+
+ // Get selected node
+
+ if (selectedNode.data().type === "TRIGGER") {
+ console.log("Should remove trigger!");
+ console.log(selectedNode.data());
+ const triggerindex = workflow.triggers.findIndex(
+ (data) => data.id === selectedNode.data().id
+ );
+ setSelectedTriggerIndex(triggerindex);
+ if (selectedNode.data().trigger_type === "SCHEDULE") {
+ setSelectedTrigger(selectedNode.data());
+ stopSchedule(selectedNode.data(), triggerindex);
+ } else if (selectedNode.data().trigger_type === "WEBHOOK") {
+ setSelectedTrigger(selectedNode.data());
+ deleteWebhook(selectedNode.data(), triggerindex);
+ } else if (selectedNode.data().trigger_type === "EMAIL") {
+ setSelectedTrigger(selectedNode.data());
+ stopMailSub(selectedTrigger, triggerindex);
+ }
+ }
+
+ //if (selectedNode.data("id") === selectedAction.id) {
+ // setSelectedApp({});
+ // setSelectedAction({});
+ //setSelectedTrigger({});
+ //setSelectedTriggerIndex({});
//}
-
- if (trigger.name.length <= 0) {
- alert.error("Error: name can't be empty")
- return
- }
-
- alert.info("Attempting to create schedule with name " + trigger.name)
- const data = {
- "name": trigger.name,
- "frequency": workflow.triggers[triggerindex].parameters[0].value,
- "execution_argument": workflow.triggers[triggerindex].parameters[1].value,
- "environment": workflow.triggers[triggerindex].environment,
- "id": trigger.id,
- }
-
- fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key+"/schedule", {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- },
- body: JSON.stringify(data),
- credentials: "include",
- })
- .then((response) => {
- if (response.status !== 200) {
- console.log("Status not 200 for stream results :O!")
- }
-
- return response.json()
- })
- .then((responseJson) => {
- if (!responseJson.success) {
- alert.error("Failed to set schedule: " + responseJson.reason)
- } else {
- alert.success("Successfully created schedule")
- workflow.triggers[triggerindex].status = "running"
- trigger.status = "running"
- setSelectedTrigger(trigger)
- setWorkflow(workflow)
- console.log("Should set the status to running and save")
- saveWorkflow(workflow)
- }
- })
- .catch(error => {
- alert.error(error.toString())
- });
- }
-
- const appViewStyle = {
- marginLeft: 5,
- marginRight: 5,
- display: "flex",
- flexDirection: "column",
- height: "100%",
- }
-
-
- const paperAppStyle = {
- borderRadius: theme.palette.borderRadius,
- minHeight: 100,
- maxHeight: 100,
- minWidth: "100%",
- maxWidth: "100%",
- marginTop: "5px",
- color: "white",
- backgroundColor: surfaceColor,
- cursor: "pointer",
- display: "flex",
- }
-
- const paperVariableStyle = {
- borderRadius: theme.palette.borderRadius,
- minHeight: 50,
- maxHeight: 50,
- minWidth: "100%",
- maxWidth: "100%",
- marginTop: "5px",
- color: "white",
- backgroundColor: surfaceColor,
- cursor: "pointer",
- display: "flex",
- }
-
- const VariablesView = () => {
- const [open, setOpen] = React.useState(false);
- const [anchorEl, setAnchorEl] = React.useState(null);
-
- const menuClick = (event) => {
- setOpen(!open)
- setAnchorEl(event.currentTarget);
- }
-
- const deleteVariable = (variableName) => {
- console.log("Delete:" ,variableName)
- workflow.workflow_variables = workflow.workflow_variables.filter(data => data.name !== variableName)
- setWorkflow(workflow)
- }
-
- const deleteExecutionVariable = (variableName) => {
- workflow.execution_variables = workflow.execution_variables.filter(data => data.name !== variableName)
- setWorkflow(workflow)
- }
-
- const variableScrollStyle = {
- margin: 15,
- overflow: "scroll",
- height: "66vh",
- overflowX: "auto",
- overflowY: "auto",
- flex: "10",
- }
-
- return (
-
-
- What are
WORKFLOW variables?
- {workflow.workflow_variables === null ?
- null : workflow.workflow_variables.map((variable, index) => {
- return (
-
-
{
- }}>
-
-
-
{
- setNewVariableName(variable.name)
- setNewVariableDescription(variable.description)
- setNewVariableValue(variable.value)
- setVariablesModalOpen(true)}}>
- Name: {variable.name}
-
-
-
-
-
-
-
-
-
-
- )
- })}
-
- {
- setVariablesModalOpen(true)
- setLastSaved(false)
- }}>New workflow variable
-
-
- What are
EXECUTION variables?
- {workflow.execution_variables === null || workflow.execution_variables === undefined ?
- null : workflow.execution_variables.map(variable=> {
- return (
-
-
{
- }}>
-
-
-
{
- setNewVariableName(variable.name)
- setExecutionVariablesModalOpen(true)
- }}>
- Name: {variable.name}
-
-
-
-
-
-
-
-
-
-
- )
- })}
-
- {
- setExecutionVariablesModalOpen(true)
- setLastSaved(false)
- }}>New execution variable
-
-
-
- )
- }
-
- const curTab = 0
- const handleSetTab = (event, newValue) => {
- setCurrentView(newValue)
- }
-
- const HandleLeftView = () => {
- // Defaults to apps.
- var thisview =
- if (currentView === 1) {
- thisview =
- } else if (currentView === 2) {
- thisview =
- }
-
- const tabStyle = {
- maxWidth: leftBarSize/3,
- minWidth: leftBarSize/3,
- flex: 1,
- textTransform: "none",
- }
-
- const iconStyle = {
- marginTop: 3,
- marginRight: 5,
- }
-
- return(
-
-
- {thisview}
-
-
-
-
-
-
-
-
- Apps
-
-
- } style={tabStyle} />
-
-
-
-
-
- Triggers
-
-
- } style={tabStyle} />
-
-
-
-
-
- Variables
-
-
- }style={tabStyle} />
-
-
- )
- }
-
- const triggers = [{
- "name": "Webhook",
- "type": "TRIGGER",
- "status": "uninitialized",
- "trigger_type": "WEBHOOK",
- "errors": null,
- "large_image": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAAAAB3RJTUUH4wYNAxEP4A5uKQAAGipJREFUeNrtXHt4lNWZf8853zf3SSZDEgIJJtxCEnLRLSkXhSKgTcEL6yLK1hZWWylVbO1q7SKsSu3TsvVZqF2g4haoT2m9PIU+gJVHtFa5NQRD5FICIUAumBAmc81cvss5Z/845MtkAskEDJRu3r8Y8n3nfc/vvOe9zyDOOQxScoRvtAA3Ew2C1Q8aBKsfNAhWP2gQrH7QIFj9oEGw+kGDYPWDBsHqBw2C1Q+SbrQAPSg+/ULoRkvTjf4uwOKMAeeAEMI4AaBuf7rRhG5kIs05Zxxh1AUQ5yymUkVFgLBFxhZzbw///wGLUyZ2zikLn2oIVJ3o+NtZ5Xyb5u/gmgYAyCTLLqdlRKajaFRqeZFtTA7C+BJk5MZo2Y0Ai3EOHGGshyIX393btnNv5FQjjSoIYyQRRDBgdOkxyriuc8aJzeIozMu4d2rG16YQm4UzhtANULHrDRZnDGHMGW/b9lHzxh3RxlZslrHFjDAG4JxziBcHAUIIAHHGWFRhqmYblZ3z7bmZc24HAM75dTZk1xUsThkiWPn84umVv/btrSF2K7aYOGPA+pYBYQQIs5hCo8qQGRNGP/9vpky3WPAfECyxseCntSef+6Xq8UupDk4Z9Jc7QohgzR+yDMsY9/OlzpIx1xOv6wSW2JJvb03tM78AxrHVzHWaiAJGAMA5F4p2KYzgnHMG3WVEEqGRGDbJhWt+kFpedN3wuh5gCTsVPHzyb9/9L845Nkmcsm5CEMw0yiIxzhg2ycgkAQemqFyniBBiMyOJXOYVRcNmuXjDMntBnmBx84PFOSCktvmOLHxR9fiJ1Ry/bYQxZ0wPhk3pLtek4tQJhda84cRpA8Y0bzBS3xz4tDZYXav5QlKKXTwcjxcNxyy3ZJVufkFKtQtG/whg1f77Lzy7K+U0Z/ztQwTTiIJN0rAFdw97+G5TRlrihjkAAuVzT8tbu1ve3s01SmzdsZaI5g1m/cudY158/Doo18CCJTbg2V158plfXLLocUjpoYhtdE7+T5bYx+WKaJNR2mW8GOMciERESBWuPXdq2brIuRbJYU3QTRqOFq37oWtSyUDjNZBHwQFhzCk9v3knkqV4Iy2QcpaMKfnf5fZxuZxSRhlgREwykSVMCCaEyLJkkhHGlFKuU3tBXvH/LncU5Okd0QRzzjk/v2kngAjKBpAGULPEOXv/8umJ7/+3lGLvUgeMuKKZhrpLNv6nKcPFKeUIYYxVVa2srKyurm5ra+Ocp6enl5WVTZo0yW63M8YQ40giyueeo4+u1HwhJEtG2IEwohFl/Gv/kTqhcECVa8CrDhd3HUhw/MCBUzbquYXxSFVVVb322mv19fWcc0IIAFBKt2/fnp2dvWjRorvuuosBA52ah6ePfPYbtc++KpnkrmNGiGm65739qRMKYSAt8ICBxTnCWPd3hGrqsNXEO2N0RLAeDKffNTHtjjLOmEBq+/bta9askSQpJSUFRKjVeafa29tXrlx57ty5b3/72wwYMDZkZrl76q3eTw5LDptwjpxxYjEFDp2gkRixWQbOLQ6UxooNh+sa1Yu++CvDOUeEDJ03AwAYYxjjysrKNWvW2Gw2i8VCKaWUMsYYY+LfJpPJ7Xa/8cYbW7duxRgzygAga96M7k6TI5OstHgi9ecN1jcTWOI6hOuamKp12V2EWEyz5g1LuTUfAIgkqaq6YcMGSZIwxoyxnssI4FJSUjZv3nz+/HkiSxwg5bYCS3YGUzUDMoQRjamR+maD9U0FFgAAKOfb4j8ijLiq2gvzsNlENR0A/vrXv545c8ZqtV4WqUuwcy5JUiAQePfddwGA6TpxWO1jb2GKGuf+EHDeye6m0ywEAKB6At19E+KM20ZmCwwA4NChQ8ncGsaY2WyuqakBAIwwAFhGZHLGoOsuckBIC3R08b6ZwAIEACyqAEJxJ80BITnNCSJPBmhpaSGEJIMXIcTr9YZCIRFkSSkO4Im4cI32uc7fJ1gCMdTjUvD4/I5Smnwk2R3TnvhybJYHdDcDBxYHAOKwivwu/r/VNp+xc5fL1Yu1iidKqdvtdjgcwBgA6MEIksilAjQAAAIO8pDUK+D4dw4WBwAwZ6bF6xFwQBIJ1zaI3QFAfn5+Msol4vuioiKEEKUMAMKnGvVAB4sqAIAIRhghgq25WWAsfTOBBQAA1lHZ8QER54xYzKEjdbHzF4kkAcC0adNSU1P7xIsxJsvytGnToNPYDX/kazmP3WcdORwY1/whzRfEZpM9PxcGMkMcqAheSOwoHNmtSMAByUT1Bi5s+yj3yflU1YYPH37fffdt3rw5IyND07TLLoUxjkQixcXFZWVlAIAJBoC020vTbi/llEbPtgQ/O+X75DAgZM0bBgBxd/MmAUtIbBuTYxuT03H8LLaZRbGYMyY5bK1v7U6/a5J93C3A2KJFi86ePfvxxx+73W6EEO8kYyURZ/l8Pr/f73K5OOcIIc4YcECECBZZ/zIjsU49AERefPHFAVpatFH1QNi3t4ZYLV1FAkJoROk4Vp9RMRmbTRjgjqlTFUU5fvx4OBxmjBFCJEmKx0uW5ZaWFoTQhAkTRJKEuspeHBgDQNehDD+AYCEEgJBleMbFXQeYonRFp5xjsxxrbus4cTZ9ZjkyyRjQpMmTJk2a5HA4CCHBYDAYDJrNXb17zrnZbD516tTkyZPdbrdQrk4uCGE80JWsAQdLtOYlp412RPz7jxK7pas/yDmxWiKnmwNVf3NNKpZTHUyn6RkZEyZMqKiomDlzJqX02LFjstwVNxFCOjo6gsHgV77ylXiwricNJFidymUvHOn96FPNF8Iy6YqBOCdWS6y5zbPrgGVYhn3sCM454xwB2Gy2iRMnyrJ84MABi8Ui7iPn3GKxnD59urCwMCcnh4kO/j8SWIAQZ4xYTObsjIvv7sMmU7euKufEYqJR5eJ7+yOnmx35t5jcKUh08TkvLS2tra09d+6c2Ww2Klyapn3++ecVFRX4RkwgDXyvDWPOmHvabTmL7lG9ASSR+L9yypAkSU57+wdVNQ8tC59sAIRwZ1S5cOFCWe6qiDLG7Hb70aNH33vvPQCgdMDd3/UGS+AFnOc+9VBGxRTN40+skXMOCBBBriml9nG5wAEwEuWtwsLCmTNnhkIhUWgWeFkslt///vfBYDDJDPwmA6sTMzT25e+4Z5TTmJI43kcZtphzn3jwEnaXHkcAsGDBApfLpeu6+CjcYlNT0zvvvCOwS2DSM0z7uwNLVIF7ExEhTimxmrMeuJPTbrYZEaIHw8Mevts2dgSnzIi/EUKU0pycnHvuuaejo8MwUowxh8Oxffv2pqYmQohRiTbsmiDOuZAqyUR9wMHinAuMMMaEkN7dEyKEa3rz5h0ovm6DEI0ptlHZ2YvuATFXFC8cxgDw4IMPZmdnK4piKJckScFg8Le//a1AhxAiwlRKaSwWi8ViQhOFVBhjQ85rBOvq0x3hvAkhuq7X1tZ++OGHxcXFM2fOFBF2IqyUIYJb3v4gWH1STnMa2SLCiCvaiMUPSE5bz2EYsf/U1NSHHnpo9erVZrNZGHVKqcPh+Oijj2bNmjV06NCqqqqzZ8+2trYGAgFFUcRVTU1NzcrKys/PLykpycvLEwbusrIlT1fTZBVGAWOsKMr777+/Y8eOc+fOeb3emTNnrlq16jICcQ4IKa3tRx75T70jiiQiDJPoS6fdXlr0Pz/svX+l6/rSpUtPnz4dX60XKqZpWjgcRgiJrodgLVRJ3EGbzTZ69OhZs2bNmjXL4XCI168Osn6DZSB18ODB1157ra6uzmw2WywW4dc3bNhg5LpdrzCGMD790uutf/hIdjl5nMvnjJX8eoWjaOSlSeTLkUB///79K1asEN3pS6IjZGi3IVjXxhASMlBKFUVRVTU7O3vBggWzZ88mhFydivXvBYECY2z9+vXPPfdcY2Ojy+Uym82Ct8fjOXnyJHSv/wqkAlV/a9uxV0qxG0hdsuvzZjqKRl6aXL6SiBhzzqdMmTJ58uR4S28cSbyNN8joPAKA1Wp1uVxer/fnP//5s88+29zcjDG+ijCtH2AJ4SKRyIoVK7Zs2eJwOERb1HBDlFLRgOl2whhzxhrXvgPxCQpCLKZYc4flPHYf9LDrl2UNAN/85jeFCvd3kwI4WZbdbndNTc3SpUurqqqEJx0QsARSsVhs+fLl+/btGzJkiGh/xj9gsVg+++wzADBiSGHIW9/5MFBdSzq77QIdqqgjFv+z5HJyyvrstosYNT8/v6KioqOjw1i/60jifJ+4gD0dNOdc13Wn0xmLxZ5//vmPP/64v3j17xquWrWqqqoqLS1N1/WEzXDOI5GI1+v1+/1CMuAcEay2+Zp/vZ3YrF1ICbs+pSzz3qnimWRYGzGqqKkaKAhQdF0PhUJ+vz8cDiuKEovFxEdVVRMgEyomy/JPfvKTQ4cOCfuV5PaTMvDCJG3ZsuVXv/qV2+1OQIoQEo1GAWDOnDmPPPJIenr6pWImZYjg+pc3try9W3aldLPrlJX8erlj/Khe7HpPopQSQt56661169aJthBCKBqNapqWlZVVXFxcVFSUk5PjcDgope3t7bW1tZWVlWfPnrVarbIsx4MiOiB2u/2Xv/zl8OHDk6z59A2WQOrUqVPf+973hJLHvyJqdaNGjXr66adLSkqEQgHnopETrD55bPFPsVmOL5NqvmD2wntGPvP1/k4Ziy0pivLEE080NzcDgKIo48ePv/fee6dMmeJ0Onu+oqrqn//8502bNnk8HgFivOShUKi8vHzVqlVJgtW3rGKVTZs2xWKxhNwVYxwIBO666661a9eWlJRQTeeMI4wRJqK60LD2HR7fuUGIKYr1lqycbyVl13tKIvr43/jGN/x+//Dhw1944YVXX331q1/9ak+kdF3XNE2W5YqKivXr1992220i9zYeoJQ6nc7Kyspdu3aJlfsWoHfNEmpVXV397LPP2my2BE0OBoMPP/zwkiVLOOeMUiJJwHnH8TO+/UeiDa1Ki6fj+JluI3oEa/6O/Je/k3nftGsZXldVdffu3VOnTk1JSaGUCn1vbW09d+5cOBx2OBy5ublZWVnQOYQjYteXXnpp37594hUDfVVVc3Jy1q9fbzKZ+tSvpNKdnTt3JgBPCAkEAnPnzl2yZAljDBgnktRx4lzDq28Gqk4wRUUYIUnCVnPcMCPWO6JpU0oy75uWvF2/LMmyPGfOHM650J36+vrNmzfX1NSIfgfG2OFwFBUVPfzww7feeit0th2XL1/+gx/8oK6uzkgDhAc/c+bM/v37p0+f3idYvUksIvW2trbDhw8b5V2hU+FwuLS0dOmTS4WRwhK5+O7eo4te8h84hq0mOS1FSnUQmxl6qO2wf60A0ZK5NtJ1Xdd1WZb37Nnz5JNP7tmzR6QQTqfTbrfrun7w4MGnn3769ddfN3Jsi8Xy/e9/32QyJUQ8CKEPP/wwGaa9gSUWPXLkiNfrja9YiqTs8ccfl2SJ6RQT4v3o01PPr0cESyl2YJxTyinrhghCTNflNKejIA/6b60SSKQ4Aqkf//jHACDmK1knIYQcDofD4di0adPatWtF2EUp7RmpCeWqra31er0iALpKsAQdO3as2wsYh8Ph8vLykpISRimRJc0XPLPqDWySESH8ijEeRwhxnTJNv/yfe6WEhzVNa2xsXLt27cqVKyVJkiSpZ2wpUov09PS33nrrk08+MZz47Nmz7Xa78bw4eK/XW1dXB32NWPZms0QW1tDQEN/yFI7jjjvuABGgE3Lhjx/Hmi/IQ1J76wlzQBLR/aHQkTpLdgZw0HTtpZdeunDhgqGz8ZoLcSMLRj3PCFxCodCFCxcikYhwgldyZAJok8n05ptvTps2Texi9OjRY8eOPXbsmOGvEEK6rp85c2bixIlXCZYR1Hi93viIgVJqs9ny8/MBQMQH/n2fYbOczHcGOQf/viMZX5sCCBBAQ0NDY2OjyMMNXC4rSYJUGGNZlsVESe8cRc3+9OnTtbW1BQUFlFJJkvLz82tqarpVaxFqaWnpU/4+vGEsFotGo0aiLyyl3W53uVwAgDGm4ZjS6kXdu+1XQh+bpGhDi1iIcS5JktVqFT4brnwFeiIoVCbJtE7U3c6ePVtQUCBOJTs7O1EwjEWWdk2hA6XUaBYYS4uzvfSRMZ5kbsUBEKLRGNN0LEuqoookySif94JyUuv3SqFQyPh3zwhWdCT7BKsPA08Iib+D4hCi0WhHR4f4KDltl8rEffo3BMA5sVmQLAFAIBgIh8M9HRBOmvrVkbbZbMa/e842iX31uUgfmmWxWGw2WyAQiIcvHA6fP38+JyeH6TqRZcf40aGj9cRm4dDbvUAIMVW3jc4RW2xtbY1EIglZAQBEo9FkWvPC5ffp7MWTsizn5nbNuXk8noS3OOd2ux3iCor9A0v4HbPZ7Ha7m5ubDdcrzNaRI0cmTpwoBhIzKiZf+MOfk/i2M0IYDZn5ZfHh5MmT8ZUWQ+hx48bFB8BXIoxxXV2dqMD08rDwUSNGjCgoKIDOQlt9fX38W8K/Z2RkwLWEDmJUatSoUdXV1cauGGMmk+ngwYOLFi2SZZkzlvJP49Lvnti2Y4+c7uJXCKOQLGntAff0L6XdUSbKMtXV1fGBrrAamZmZr7zyitVq7f2ERU6za9eun/70p737REJIJBKZO3euLMuiwhMMBk+cOGHMTxjcher1cUJ9PlFaWhpflhH6X19ff+DAAQBgjAPAyB9+016Qp3mDSJa6RecIxE9baN6AbXTOmBWPcgCEUV1d3fHjx+NrxMJnFRUVWa1WsfleYlShCxUVFXPnzvV4PKKv01OnJElqb2+/884777//fuP/9+3b19raGn9OIhgaM2YMXIuBFxKUlZVlZmYmXBlRC1RVlUiEMyanOYvW/tBVXqRe9NGoIhwfIMQp18NRzRtMu71s/Gv/Ycp0i2+hbNu2LRqNxhdMBARixBbiAtFeiHP+1FNPPfDAA+3t7bFYTJRMBWGMNU3zeDzTp09ftmyZWJ8QEovFtm7dmqDRqqrm5uaOHDkS+mqR9TZyJA7QarU2NzcfO3ZM3A7oHDg4f/68pmnl5eWMMQQgOW0Zs6eYh6Vr7UE9GGaKyhmT7NaU0rF5Tz2Uu/QhyWnTNU2S5YMHD77++uvxpt0olSxevFiSJKOL1QsZKnb77bePGDGiqanJ4/FEIhHRkWaMDRs27Fvf+tbixYvFRJy4uZs2bfrLX/5idA+h01/df//9ZWVlotrTC9Ok6lmnT59eunRpwkIY446OjieeeGLevHmMMc4YxgRhxClTWjxaewAINg8dYspwAQCjjDEqyXJTU9Mzzzzj9/vjj5cQ4vf7n3zyyfnz5wvL0jtSRtdPhKaiXFVbW1tfX9/R0WGz2UaOHFlYWGhcc1HS2r1796pVq4wjN0CXJGn9+vXJFJeTLSuvXr1627ZtLpcrvnIGAOFweMGCBY899pjwL1TTESGks1bFAZiuIwBECELoxIkTK1eu9Hg88dbKMO3r1693OBy9SywagoSQ999/v7m5+ZFHHjFKLglnKXA0ksrdu3e/8sorwrolHNK8efOWLl2aTNu1b7CE9F6v97vf/a7P54uvBwlRgsFgUVHRwoULy8vLeyqFeP3ixYtbt27dtm2bqAvHx1aijvjCCy/MmDGjd4kNULZs2bJx40Zd10ePHj1//vzp06dbLBYDSsFRHJ5o3/3mN795++23E+IykT87nc5169YZTZZrBctQrn379okGekLZRLhnSunYsWPLy8sLCgoyMzOtVquu636/v6Gh4bPPPqupqfH5fA6HI+FLmGLAfc6cOc8991zvSInNqKq6Zs2anTt3ulwukUsoipKXlzdt2rSJEyfm5eWJ2FLI3NLScuDAgR07djQ0NIgUJ0HsQCCwbNmyu+++O8lufrKzDmK53/3ud+vWrXO73QkJneAUi8UURcEYm81mSZIYY6qqappGCLFarT2rTuIrl+PHj+8zthJ/8vl8K1eurK6uNqyBuGLCqJvN5vT09IyMDNHF8Xq9LS0twWDQYrGIznkC6/b29vnz5yd5AfsHloHXhg0b3njjjbS0tJ5lOSF6fMXOqED1fFiSJL/fn5+fv2rVqoTR9ssiFYlEHn/88aampiFDhqiq2pMvY0zTNF3XjWkRWZbFmfVk3d7ePmvWrBUrViTjeQ3qx7SyiCQmTJhgNpsPHDggiko9k6wEX9MTJoGg1+udMGHCyy+/LPS0l7MVcJtMJrvdXl1dLZQoIaMULAghpk4yRmsSWAOAz+ebPXv2j370I/FM8mD1e+RIuPa9e/euXr3a4/E4nU5xqsmsI2AKh8MA8OCDDz722GOiUZzMLRD6dfz48Z/97GeNjY0pKSn9moQRrCORCAAsWrTo61//ekI9dkDAMvDyeDwbN2784IMPFEWx2Wwi9rvs3RQC6bouClhlZWWPPvpoaWmpuC/JiytgDYVCGzdu/NOf/iT678LrXbZUD3GWQdjT4uLiJUuWFBcX95f11YMFnTOSCKFTp0798Y9/rKysbG9vFwGeMcoCnbM+uq5zzlNSUkpLS++9994vf/nLQhmvQlzjrZMnT7755ptVVVWhUEiWZXHv4hcUcZamaaqqSpI0ZsyYBx54YMaMGcKKXafJP4PEYRpW4PDhw0ePHhXzkiKSEG4xLS1txIgR48eP/9KXvjRs2LCEF6+Rb1NT0549ew4dOtTY2BgMBjVNM7YjSZLNZhs6dGhxcfHUqVNLS0tFw+JaWF/rD/f0jJ5VVY1EIrquY4ytVqvVau3l4S+Kr8/na2lpuXjxomhKWywWt9udlZU1dOhQw9KL0P9amH4xv3IkRIFOO9pzY+I8v/CvJiWzskh6vpAT+uJ/Eqqngf9i178S0wQbb1RyvkAuN/S3lW82uvE/hX0T0SBY/aBBsPpBg2D1gwbB6gcNgtUPGgSrHzQIVj9oEKx+0CBY/aD/A/ORNiwv2PAfAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE5LTA2LTEzVDAzOjE3OjE2LTA0OjAwj3mANAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxOS0wNi0xM1QwMzoxNzoxNS0wNDowMM/MIhUAAAAASUVORK5CYII=',
- "is_valid": true,
- "label": "Webhook",
- "environment": "onprem",
- "description": "Simple HTTP webhook",
- "long_description": "Execute a workflow with an unauthicated POST request",
- },
- {
- "name": "Schedule",
- "type": "TRIGGER",
- "status": "uninitialized",
- "description": "Schedule execution time",
- "trigger_type": "SCHEDULE",
- "errors": null,
- "large_image": "data:image/png;base64,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",
- "label": "Schedule",
- "is_valid": true,
- "environment": "onprem",
- "long_description": "Create a schedule based on cron",
- },
- {
- "name": "Shuffle Workflow",
- "type": "TRIGGER",
- "status": "uninitialized",
- "trigger_type": "SUBFLOW",
- "errors": null,
- "large_image": 'data:image/png;base64,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',
- "is_valid": true,
- "label": "Subflow",
- "environment": "onprem",
- "description": "Control another workflow",
- "long_description": "Execute another workflow from this workflow",
- },
- {
- "name": "User Input",
- "type": "TRIGGER",
- "status": "running",
- "large_image": "data:image/png;base64,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",
- "description": "Wait for user input",
- "trigger_type": "USERINPUT",
- "errors": null,
- "is_valid": cloudSyncEnabled || isCloud ? true : false,
- "label": "User input",
- "environment": "cloud",
- "long_description": "Take user input to continue execution",
- },
- {
- "name": "Office365",
- "type": "TRIGGER",
- "status": "uninitialized",
- "description": "Starts upon O365 email",
- "trigger_type": "EMAIL",
- "errors": null,
- "is_valid": cloudSyncEnabled || isCloud ? true : false,
- "label": "Email",
- "environment": "cloud",
- "large_image": 'data:image/jpg;base64,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',
- "long_description": "Execute a workflow when you get an email",
- },
- {
- "name": "Gmail",
- "type": "TRIGGER",
- "status": "uninitialized",
- "description": "Trigger based on Gmail",
- "trigger_type": "EMAIL",
- "errors": null,
- "is_valid": cloudSyncEnabled || isCloud ? true : false,
- "label": "Email",
- "environment": "cloud",
- "large_image": 'data:image/jpg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/hAzFodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQ1IDc5LjE2MzQ5OSwgMjAxOC8wOC8xMy0xNjo0MDoyMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTkgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QTIyMjgyMEYwMDJDMTFFQkJBOEE5OUJBM0MzMTA2RDIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QTIyMjgyMTAwMDJDMTFFQkJBOEE5OUJBM0MzMTA2RDIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDozQTMwMDQxRTAwMEUxMUVCQkE4QTk5QkEzQzMxMDZEMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpBMjIyODIwRTAwMkMxMUVCQkE4QTk5QkEzQzMxMDZEMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv/bAEMAAwICAwICAwMCAwMDAwMEBwUEBAQECQYHBQcKCQsLCgkKCgwNEQ4MDBAMCgoOFA8QERITExMLDhQWFBIWERITEv/bAEMBAwMDBAQECAUFCBIMCgwSEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEv/AABEIAK4ArgMBEQACEQEDEQH/xAAcAAEAAQUBAQAAAAAAAAAAAAAABgEFBwgJBAP/xABBEAABAwIDBAUGCwgDAAAAAAAAAQIEAwUGBxESITFBCDdRYXUTFDJScbMYIiM2QmJ0gcHD0QkzNVSRlbHCcpLw/8QAHAEBAAIDAQEBAAAAAAAAAAAAAAYHBAUIAwIB/8QAQBEAAgECAgYGBwUHBAMAAAAAAAECAwQFEQYhMUFRYQcSMjRxciI1UqGx0fATM4GRshQWYpLBwuFCotLxFRck/9oADAMBAAIRAxEAPwDpgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiarom9V4AGo+enTspYNxBMw/lZbYN5k2+o6jLus57ljtqtXRzKTGKi1NldUVyuRNUXRF4kevscVKbhRWeW97PwLf0X6LpXtvG6xGbgpLNRjl1stzk3nlnwyz45EOy9/aGXmldqNHNGxW2VbKrkSpLs9N9GvQT1vJuc5tRE7EVq9irwMa30gn1sq0VlyNzi/RHaui5YdWkprdPJp8s0k1460bpYUxbZ8cWGLesJXGNdLXMbrRkx3atVebVTi1ycFaqIqc0JNSqwqwU4PNMpO/sLmwuJW9zBwnHan9a1wa1Mu56GGAAAAAAACz4kxVBwxG8pOftVnp8lHZvfU/RO9SJ6V6Z4Zo5b/AGl1LOb7MF2pfJcZPVwzeo2mF4Rc4hU6tJaltb2L5vkY8rZvXV1faoRYNOlrupua5y6f8tU/wULX6ccdlX61KhTjDg1Jv8ZdZe5Im8NDLJQylOTfHUvdkTXB+OI+KmPpOp+bTaTdp9Ha1RzfWavZ2pyLi0F6Q7TSaMqTj9nXis3HPNNe1F8OKetc1rInjWAVcOakn1oPfwfB/WskxYhoAAAAAAAAAAAACN5l3SvY8ucV3GA5WSoNjm16L0X0XtoPVq/cqIp43MnGjOS2pP4GywahCviVvSnslOCfg5I47s12G6qqrspqq8ytzsp7Sp+n4ZW6PWbOIsqsTyZWGZa+bV6SOmW+squjytHInx28nacHpo5PZuPahf1rOSnTfitz+uJocf0Zw/HLf7K6jrXZku1HwfDinqfvOi2U+dNgzat21Z6ixLrQZtS7XXenlaXa5vrs+sn3oik1w/FKF5H0NUt63/5RzZpPohiGA1sqy61N9ma2Pk/ZfJ/g2T82RFQAAAiarom9V7ADAWdvSkt+CvObNgN0e7X5urK0n040F3PXTdUqJ6qfFTmvIjuKY9ChnToelLjuXzZamh/RtcYl1brEM6dHalslP/jHnte5byOWi6zL5aIFwu8irLmy4lKrXrVF1c9ysRVVf68E3HFukt1XucXualablLry1t57G0vwS2LcSuta0bWrOjQiowi2kluWZ6zRnmX7Ald8fF9rWmqpt10pu72uRUVCcdG9xUoaU2UoPLOfVfhJNP3M02kFOM8MrJ7ln+Wszuh2winQAAAAAAAAAAACKZsx3y8q8ZUaOi1K2H5zGIq6JqtB6IP2Wpdf/PS7U/RW7W9S95m4be0bG9o3dbsU5RlLJZvKLTeS36lsOQdeJWg1VoS6b6VWmiI5jk0VCvb/AA+6sLiVtdU3CpHant/64NanuOv8NxSyxO1heWVVVKU9alF5p/JrenrW9HzMQzySYE/icj7P/sh4V+yj6iZBtd1mWS4x59nlV4U2I9H0JFB6sfTd2oqf+U8KdSdOSlB5NHnc21G5oyo1oKUJamms0zb3JPpVw8T+b2XMmpHtt3doyhctEpx5a8ER/Kk9f+q/V4E1wvSCFXKncapcdz+T9xQWmHRnWsutd4WnOltcNso+HtL/AHLntNiSTFRnivN6gYdtci5X2ZHgQIjNuvIrv2WMT29vYib15HnVqwpQc5vJIybSzuLuvGhbwc5y2Ja2/rjsW807zs6Us/GSSLLgB0i02J2rK0z0JM1vNO2nTXsT4y81TgQnFMenXzp0NUeO9/Je86C0P6NbfDurdYjlUrbVHbGP/KXPYty3mv8Apo3RNyIhHEWtvNp8J/Naz/YKHu0OYMc9Z3Pnl+plSYh3ur5n8S6mrMQ9mGb1Dt2N8OxpddrZE64U6dCkm9z1XXfp2d5YPRnhV3d6RWtalDOFOacnuWXPi9y2kU0rxuxsrR29eolUq+jCO9t8uC3vZ+JsSnA7PRV4AAAAAAAAAAABHcxur3E/g0v3LjaYJ6zt/PH9SMPEe51fK/gzmxecPxL9FayYzSo1vydZvpM/VO4vfSnRDDdIaH2d1HKa7M12o/NcYvU+T1kP0L08xjRS6+2sZ5wl26cuxPxW6XCS1rmtRjK+4cl2Cvsym7dFy/J12p8V36L3HKelWhuJaO1+pcxzpvszXZl8pfwv8M1rO3dCOkHB9LLbr2curVivTpy7Uef8UeElq45PUe/An8TkfZ/9kIZX7KJ5Em5jH2OKaLwUAzdk70oLxl3GbasS0q9/sdJipGYtVEkRVRNzWPdxZru2XcOS8jfYbj1W1XUqLrR3cV/grbSzo4s8Xn+0WrVKs3r1ejLi2lslzW3fxITmlnDiDNm6JXv9ZKECg5Vh22g5UoR+/wCu/teu/s0TcYF/iVe8nnUepbFuX+eZJNG9FMPwGh1LeOc32pvtS+S5LVxzesg5gElC8F9gBtNhP5rWf7BQ92hzBjnrO588/wBTKlxDvdXzP4kZxrmfGsXlIdl8nLuCao52utOgvf2u7v69hNtEuj25xPq3N7nTo7l/ql4cFze3ct5TGm/Sla4R1rTD8qlfY3tjDx9qX8K2b3uIllBcJN0zrwlKuNapIkVbxSV1R66qvHd3J3IdE4JY29lKjb20FGEXqS+tb4t62c+YfiF1iGO0bm6qOdSU1m39aktyWpbjfxOCE9LkKgAAAAAAAAAAAjuY3V7ifwaX7lxtME9Z2/nj+pGHiPc6vlfwZzrb6LfYh0+9pTZ85EalLoPoyqbKtKomjmPTVFMW8sre9oSt7mCnCWpprNP6/NbjMw/ELvD7mF1aVHTqQealF5NP62rY9jLfhXKe6S7hdpWEote4x4EHziTQpptVaNPbaiuROL0TXfpvRO05b6R+jh4Ild2MutRk8uq9covLPb/qjz2rfntOx+jDplo47lYYslTuEtU1qhPdr9iXLsvdlsPjx4bynS/QAAAAAD2WizzsQXKPbrHEkTp8x+xQj0GK99R3cn+V4JzPulTnUmoQWbZ4XV1QtaMq9eajCOtt6kjIuKsa3O2RW4ZjsdAqWmmkKc9r0V76tNNh7WuTcjdUVNU3qRjCej23tsQq3t/lObnJqO2Mdbaz9p+5c9pwj0k9Ktxf3lxZYW3TpdaSc9k5a3s9mP8AufLYQMsQowm+SPXBg7xel+JlWPeqfibjR/1rb+ZHQNOCEzLwKgAAAAAAAAAAAjuY3V7ifwaX7lxtME9Z2/nj+pGHiPc6vlfwZzrb6LfYh0+9pTZUAz50N92Pr4qblSzfnsK26TfV1Hz/ANrJZof3up5f6oyFnZ0X7bjvzi8YKSPaMQu1fVpabEac76yJ+7evrpuX6ScznTFMBp3GdSj6M/c/k+f5nSmh/SPc4X1bW+zqUNie2UPD2o8nrW57jTa/YfuWF7tItmIYUi33CK7Zqx67dlzexexUXkqaovJSD1qNSjNwqLJo6Gsr62vaEbi2mpwlsa+tvFPWi3nmZQAJpljlJiDNa7LFw5HRkWi5EmXCuipQjIvav0ndjE3r3JvM6xw+veT6tNat73L64Ed0j0ow/AqH2l1L0n2YrtS8OC4t6l46jeLKnJrD+U1uSlY6SybjXaiS7nIanlq/cnqM1+in36rvJ9h+GULKOUFm973v5Lkc06S6W4hj1brV31aa7MF2Vz5vm/wyRpFmV1jYq8al++cRq6+/n4v4nL2K9/r+aXxZGzwMAm+SPXBg7xel+JlWPeqfibjR/wBa2/mR0DTghMy8CoAAAAAAAAAAAI7mN1e4n8Gl+5cbTBPWdv54/qRh4j3Or5X8Gc62+i32IdPvaU2VAM+dDj5+33wb89hW3Sb6uo+f+1ks0P75U8v9UbclKliEMzNylw/mtaUiYkjqyVRaqQ7hQRErxVX1V5t7WLuXuXeYN9h1C8h1ai17nvX1wJFo7pRiGBV/tLWXovtRfZl48Hwa1rw1Gj2a2TV/ykuPk75SSRbKz1SJdKDV8jW7l9R+nFq/cqpvIBiGGV7KWU9cdz3f4fI6W0Z0tw/HqPWt3lUXag+0vmua/HJk8yT6L1yx15vecbpItGH3aPpUdNiTOb9VF/dsX1l3r9FOZscLwGpcZVK3ow97+S5/kRfTDpItsL61rYZVK+xvbGHj7UuS1Le9xuTYbBbsL2mPbMPQo9vt8RuzRj0GbLW9q96rzVdVXmpOKNGnRgoU1kkc83t9c3teVxczc5y2t7f+uCWpFwb6Se09DFOc+ZXWNirxqX75xCbr7+fi/iUNivf6/ml8WRs8DAJvkj1wYO8XpfiZVj3qn4m40f8AWtv5kdA04ITMvAqAAAAAAAAAAACO5jdXuJ/BpfuXG0wT1nb+eP6kYeI9zq+V/BnOtvot9iHT72lNlQDPnQ4+ft98G/PYVt0m+rqPn/tZLND++VPL/VG3JSpYgAPjMhR7jHdHuEehKoPVFdSr00qMcqLqiq1UVNyoiofMoxkspLNHpSrVKM1OnJxa3p5P80fZV1XVd6n0eYAKt9JPaAc58yusbFXjUv3ziE3X38/F/EobFe/1/NL4sjZ4GATfJHrgwd4vS/EyrHvVPxNxo/61t/MjoGnBCZl4FQAAAAAAAAAAAWbGlvrXbB19gwm7ciZbJNGi31nupORqfeqohnYXWhRvqNWeyMot+CaMa9pyqW1SEdri17jnHsuZ8V7Va5u5zVTRUVNyovedS5p60UwADPnQ4+ft98G/PYVt0m+rqPn/ALWSzQ/vlTy/1RtyUqWIAAAAAAVb6Se0A5z5ldY2KvGpfvnEJuvv5+L+JQ2K9/r+aXxZGzwMAyBkFbZFzzjwq2HTc9Y05JNVUTcynTarnOXu4J7VQzMPi5XUMuJvNG6UqmK0FFbHm/BbTftOCExLsAAAAAAAAAAAAABjvFWQGB8YXSrcrtaH0psh21XqwpL4/lXc3Oa3cq9+mq8yT4fpjjFjRVGlVzitiklLLks9eRprrALC5qOpOGt7cm1mWb4K2Xn8jdf7rUM//wBg477cf5EY37rYb7L/AJmSbAeTWF8t7lJn4UjTaMmVH8hUWvMdWRWbSO3IvBdUTeanF9J8RxWlGldSTinmsopa8sjOscGtLKbnRTTay1vMm5HzaAAAAAABF0XUAxVd+jLgO+XWZcbhDubpU+Q+RXcy5Paive5XO0TkmqruNfPC7acnJp5vmRqtonhlarKpOLzk236T2s8nwUcu/wCRu391qHx/4m14P8zz/c3CfZl/MybYGyvwzlxSrNwjbGRKshEStIe91WtUROCK9yqunPRNEMuha0aH3ayNvh+EWdgn+zwyb2va/wA2SoyDZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH//2Q==',
- "long_description": "Execute a workflow when you get an email",
- }
- ]
-
- const TriggersView = () => {
- const triggersViewStyle = {
- marginLeft: "10px",
- marginRight: "10px",
- display: "flex",
- flexDirection: "column",
- }
-
- // Predefined hurr
-
- return (
-
-
- {triggers.map((trigger, index) => {
- var imageline = trigger.large_image.length === 0 ?
-
- :
-
-
- const color = trigger.is_valid ? green : yellow
- return(
-
{handleTriggerDrag(e, trigger)}}
- onStop={(e) => {
- handleDragStop(e)
- }}
- dragging={false}
- position={{
- x: 0,
- y: 0,
- }}
- >
- {}}>
-
-
-
-
-
- {imageline}
-
-
-
-
- {trigger.name}
-
-
- {trigger.description}
-
-
-
-
-
- )
- })}
-
-
- )
- }
-
- var newNodeId = ""
- var parsedApp = {}
- const handleTriggerDrag = (e, data) => {
- const cycontainer = cy.container()
- // Chrome lol
- //if (e.srcElement !== undefined && e.srcElement.localName === "canvas") {
- if (e.pageX > cycontainer.offsetLeft && e.pageX < cycontainer.offsetLeft+cycontainer.offsetWidth && e.pageY > cycontainer.offsetTop && e.pageY < cycontainer.offsetTop+cycontainer.offsetHeight) {
- if (newNodeId.length > 0) {
- var currentnode = cy.getElementById(newNodeId)
- if (currentnode.length === 0) {
- return
- }
-
- currentnode[0].renderedPosition("x", e.pageX-cycontainer.offsetLeft)
- currentnode[0].renderedPosition("y", e.pageY-cycontainer.offsetTop)
- } else{
- if (workflow.start === "" || workflow.start === undefined) {
- alert.error("Define a starting action first.")
- return
- }
-
- //if (data.is_valid === false) {
- // alert.error(data.name+" requires hybrid version of Shuffle")
- // return
- //}
-
- const triggerLabel = getNextActionName(data.name)
-
- newNodeId = uuidv4()
- const newposition = {
- "x": e.pageX-cycontainer.offsetLeft,
- "y": e.pageY-cycontainer.offsetTop,
- }
-
- const newAppData = {
- app_name: data.name,
- app_version: "1.0.0",
- environment: isCloud ? "cloud" : data.environment,
- description: data.description,
- long_description: data.long_description,
- errors: [],
- id_: newNodeId,
- _id_: newNodeId,
- id: newNodeId,
- finished: false,
- label: triggerLabel,
- type: data.type,
- is_valid: true,
- trigger_type: data.trigger_type,
- large_image: data.large_image,
- status: "uninitialized",
- name: data.name,
- isStartNode: false,
- position: newposition,
- }
-
- //if (data.trigger_type === "WEBHOOK") {
- // newAppData.status = "running"
- //}
-
- // Can all the data be in here? hmm
- const nodeToBeAdded = {
- group: "nodes",
- data: newAppData,
- renderedPosition: newposition,
- }
-
- cy.add(nodeToBeAdded)
- parsedApp = nodeToBeAdded
- return
- }
- }
- }
-
- const handleDragStop = (e, app) => {
- //console.log("STOP!: ", e)
- //console.log("APP!: ", parsedApp)
- //const onNodeAdded = (event) => {
- //const node = event.target
- //const nodedata = event.target.data()
- var currentnode = cy.getElementById(newNodeId)
- if (currentnode === undefined || currentnode === null || currentnode.length === 0) {
- return
- }
-
- // Using remove & replace, as this triggers the function
- // onNodeAdded() with this node after it's added
-
- currentnode.remove()
- parsedApp.data.finished = true
- parsedApp.data.position = currentnode.renderedPosition()
- parsedApp.position = currentnode.renderedPosition()
- parsedApp.renderedPosition = currentnode.renderedPosition()
-
- var newAppData = parsedApp.data
- if (newAppData.type === "ACTION") {
- // AUTHENTICATION
- if (app.authentication.required) {
- // Setup auth here :)
- const authenticationOptions = []
- var findAuthId = ""
- if (newAppData.authentication_id !== null && newAppData.authentication_id !== undefined && newAppData.authentication_id.length > 0) {
- findAuthId = newAppData.authentication_id
- }
-
- var tmpAuth = JSON.parse(JSON.stringify(appAuthentication))
- for (var key in tmpAuth) {
- var item = tmpAuth[key]
-
- const newfields = {}
- for (var filterkey in item.fields) {
- newfields[item.fields[filterkey].key] = item.fields[filterkey].value
- }
-
- item.fields = newfields
- if (item.app.name === app.name) {
- authenticationOptions.push(item)
- if (item.id === findAuthId) {
- newAppData.selectedAuthentication = item
- }
- }
- }
-
- if (authenticationOptions !== undefined && authenticationOptions !== null && authenticationOptions.length > 0) {
- for (var key in authenticationOptions) {
- const option = authenticationOptions[key]
- if (option.active) {
- newAppData.selectedAuthentication = option
- newAppData.authentication_id = option.id
- break
- }
- }
- }
-
- //newAppData.authentication = authenticationOptions
- //if (newAppData.selectedAuthentication === null || newAppData.selectedAuthentication === undefined || newAppData.selectedAuthentication.length === "") {
- // newAppData.selectedAuthentication = {}
- //} else {
- // console.log("CAN WE SELECT AUTH?: ", authenticationOptions)
- //}
- //
- //
-
- //console.log(parsedApp)
- } else {
- newAppData.authentication = []
- newAppData.authentication_id = ""
- newAppData.selectedAuthentication = {}
- }
-
- parsedApp.data = newAppData
- cy.add(parsedApp)
- } else if (newAppData.type === "TRIGGER") {
- cy.add(parsedApp)
-
- }
-
- newNodeId = ""
- parsedApp = {}
- }
-
- const appScrollStyle = {
- overflow: "scroll",
- maxHeight: bodyHeight-appBarSize-55-50,
- minHeight: bodyHeight-appBarSize-55-50,
- marginTop: 1,
- overflowY: "auto",
- overflowX: "hidden",
- }
-
- const handleAppDrag = (e, app) => {
- const cycontainer = cy.container()
-
- // Chrome lol
- //if (e.srcElement !== undefined && e.srcElement.localName === "canvas") {
- if (e.pageX > cycontainer.offsetLeft && e.pageX < cycontainer.offsetLeft+cycontainer.offsetWidth && e.pageY > cycontainer.offsetTop && e.pageY < cycontainer.offsetTop+cycontainer.offsetHeight) {
- if (newNodeId.length > 0) {
- var currentnode = cy.getElementById(newNodeId)
- if (currentnode === undefined || currentnode === null || currentnode.length === 0) {
- return
- }
-
- currentnode[0].renderedPosition("x", e.pageX-cycontainer.offsetLeft)
- currentnode[0].renderedPosition("y", e.pageY-cycontainer.offsetTop)
- } else {
- if (workflow.public) {
- console.log("workflow is public - not adding")
- return
- }
-
- if (app.actions === undefined || app.actions === null || app.actions.length === 0) {
- alert.error("App "+app.name+" currently has no actions to perform. Please go to https://shuffler.io/apps to edit it.")
- return
- }
-
- newNodeId = uuidv4()
- const actionType = "ACTION"
- const actionLabel = getNextActionName(app.name)
- var parameters = null
- var example = ""
-
- if (app.actions[0].parameters !== null && app.actions[0].parameters.length > 0) {
- parameters = app.actions[0].parameters
- }
- if (app.actions[0].returns.example !== undefined && app.actions[0].returns.example !== null && app.actions[0].returns.example.length > 0) {
- example = app.actions[0].returns.example
- }
-
- const parsedEnvironments = environments === null || environments === [] ? "cloud" : environments[defaultEnvironmentIndex] === undefined ? "cloud" : environments[defaultEnvironmentIndex].Name
- const newAppData = {
- app_name: app.name,
- app_version: app.app_version,
- app_id: app.id,
- sharing: app.sharing,
- private_id: app.private_id,
- environment: parsedEnvironments,
- errors: [],
- id_: newNodeId,
- _id_: newNodeId,
- id: newNodeId,
- is_valid: true,
- label: actionLabel,
- type: actionType,
- name: app.actions[0].name,
- parameters: parameters,
- isStartNode: false,
- large_image: app.large_image,
- authentication: [],
- execution_variable: undefined,
- example: example,
- category: app.categories !== null && app.categories !== undefined && app.categories.length > 0 ? app.categories[0] : "",
- authentication_id: "",
- finished: false,
- }
-
- // FIXME: overwrite category if the ACTION chosen has a different category
-
- // const image = "url("+app.large_image+")"
- // FIXME - find the cytoscape offset position
- // Can this be done with zoom calculations?
- const nodeToBeAdded = {
- group: "nodes",
- data: newAppData,
- renderedPosition: {
- //x: e.layerX,
- //y: e.layerY,
- x: e.pageX-cycontainer.offsetLeft,
- y: e.pageY-cycontainer.offsetTop,
- }
- }
-
- parsedApp = nodeToBeAdded
- cy.add(nodeToBeAdded)
- return
- }
- }
- }
-
- const AppView = (props) => {
- const { allApps, prioritizedApps, filteredApps } = props;
- const [visibleApps, setVisibleApps] = React.useState(prioritizedApps.concat(filteredApps.filter(innerapp => !internalIds.includes(innerapp.id))))
-
- const ParsedAppPaper = (props) => {
- const app = props.app
- const [hover, setHover] = React.useState(false)
-
- const maxlen = 24
- var newAppname = app.name
- newAppname = newAppname.charAt(0).toUpperCase()+newAppname.substring(1)
- if (newAppname.length > maxlen) {
- newAppname = newAppname.slice(0, maxlen)+".."
- }
- newAppname = newAppname.replaceAll("_", " ")
-
- //const image = "url("+app.large_image+")"
- const image = app.large_image
- const newAppStyle = JSON.parse(JSON.stringify(paperAppStyle))
- const pixelSize = !hover ? "2px" : "4px"
- newAppStyle.borderLeft = app.is_valid ? `${pixelSize} solid ${green}` : `${pixelSize} solid ${yellow}`
-
- return (
- {handleAppDrag(e, app)}}
- onStop={(e) => {
- handleDragStop(e, app)
- }}
- key={app.id}
- dragging={false}
- position={{
- x: 0,
- y: 0,
- }}
- >
- {setHover(true)}} onMouseOut={() => {setHover(false)}}>
-
-
-
-
-
-
-
- {newAppname}
-
-
-
-
- Version: {app.app_version}
-
-
-
-
- {app.description}
-
-
-
-
-
-
- )
- }
-
- const runSearch = (value) => {
- if (value.length > 0) {
- var newApps = allApps.filter(app => (app.name.toLowerCase().includes(value.trim().toLowerCase() || app.description.toLowerCase().includes(value.trim().toLowerCase()))) && !(!app.activated && app.generated))
-
- // Extend search
- if (newApps.length === 0) {
- const searchvalue = value.trim().toLowerCase()
- newApps = allApps.filter(app => {
- for (var key in app.actions) {
- const inneraction = app.actions[key]
- if (inneraction.name.toLowerCase().includes(searchvalue)) {
- return true
- }
- }
-
- return false
- })
- }
-
- //setFilteredApps(responseJson.filter(app => !internalIds.includes(app.name) && !(!app.activated && app.generated)))
- //console.log("FOUND: ", newApps)
- setVisibleApps(newApps)
- } else {
- setVisibleApps(prioritizedApps.concat(filteredApps.filter(innerapp => !internalIds.includes(innerapp.id))))
- }
- }
-
- return(
-
-
-
-
-
-
-
- )
- }}
- fullWidth
- color="primary"
- placeholder={"Search Active Apps"}
- id="appsearch"
- onKeyPress={(event) => {
- if (event.key === "Enter") {
- event.target.blur(event)
- }
- }}
- onBlur={(event) => {
- console.log("BLUR: ", event.target.value)
- runSearch(event.target.value)
- }}
- />
- {visibleApps.length > 0 ?
-
- {visibleApps.map((app, index) => {
- if (app.invalid) {
- return null
- }
-
- return(
-
- )
- })}
-
- :
- apps.length > 0 ?
-
-
- Couldn't find app. Is it active?
-
-
- :
-
-
-
- Loading apps
-
-
- }
-
-
- )
- }
-
- const getNextActionName = (appName) => {
- var highest = ""
- //label = name + _number
- const allitems = workflow.actions.concat(workflow.triggers)
- for (var key in allitems) {
- const item = allitems[key]
- if (item.app_name === appName && item.label !== undefined && item.label !== null) {
- var number = item.label.split("_")
- if (isNaN(number[-1]) && parseInt(number[number.length-1]) > highest) {
- highest = number[number.length-1]
- }
- }
- }
-
- if (highest) {
- return appName+"_"+(parseInt(highest)+1)
- } else {
- return appName+"_"+1
- }
- }
-
- const setNewSelectedAction = (e) => {
- const newaction = selectedApp.actions.find(a => a.name === e.target.value)
- if (newaction === undefined || newaction === null) {
- alert.error("Failed to find the action")
- return
- }
-
- // Does this one find the wrong one?
- var newSelectedAction = selectedAction
- newSelectedAction.name = newaction.name
- newSelectedAction.parameters = JSON.parse(JSON.stringify(newaction.parameters))
- newSelectedAction.errors = []
- newSelectedAction.isValid = true
- newSelectedAction.is_valid = true
-
- if (newSelectedAction.app_name === "Shuffle Tools") {
- const iconInfo = GetIconInfo(newSelectedAction)
- console.log("ICONINFO: ", iconInfo)
- const svg_pin = ` `
- const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin)
- newSelectedAction.large_image = svgpin_Url
- newSelectedAction.fillGradient = iconInfo.fillGradient
- newSelectedAction.fillstyle = "solid"
- if (newSelectedAction.fillGradient !== undefined && newSelectedAction.fillGradient !== null && newSelectedAction.fillGradient.length > 0) {
- newSelectedAction.fillstyle = 'linear-gradient'
- console.log("GRADIENT!: ", newSelectedAction)
- //action.fillstyle =
- //'background-fill': 'data(fillstyle)',
- } else {
- newSelectedAction.iconBackground = iconInfo.iconBackgroundColor
- }
-
- const foundnode = cy.getElementById(newSelectedAction.id)
- if (foundnode !== null && foundnode !== undefined) {
- console.log("UPDATING NODE!")
- foundnode.data(newSelectedAction)
- }
- }
-
- // Takes an action as input, then runs through and updates the relevant fields
- // based on previous actions'
- newSelectedAction = RunAutocompleter(newSelectedAction)
-
- console.log("ACTION: ", newSelectedAction)
-
- if (newaction.returns.example !== undefined && newaction.returns.example !== null && newaction.returns.example.length > 0) {
- newSelectedAction.example = newaction.returns.example
- }
-
- // FIXME - this is broken sometimes lol
- //var env = environments.find(a => a.Name === newaction.environment)
- //if ((!env || env === undefined) && selectedAction.environment === undefined ) {
- // env = environments[defaultEnvironmentIndex]
- //}
- //setSelectedActionEnvironment(env)
-
- setSelectedAction(newSelectedAction)
- setUpdate(Math.random())
-
- // FIXME - should change icon-node (descriptor) as well
- const allNodes = cy.nodes().jsons()
- for (var key in allNodes) {
- const currentNode = allNodes[key]
- if (currentNode.data.attachedTo === selectedAction.id && currentNode.data.isDescriptor) {
- const foundnode = cy.getElementById(currentNode.data.id)
- if (foundnode !== null && foundnode !== undefined) {
- const iconInfo = GetIconInfo(newaction)
- const svg_pin = ` `
- const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin)
- foundnode.data("image", svgpin_Url)
- foundnode.data("imageColor", iconInfo.iconBackgroundColor)
- }
-
- break
- }
- }
- }
-
- // APPSELECT at top
- // appname & version
- // description
- // ACTION select
- //
- const selectedNameChange = (event) => {
- //console.log("OLDNAME: ", selectedAction.name)
- event.target.value = event.target.value.replaceAll("(", "")
- event.target.value = event.target.value.replaceAll(")", "")
- event.target.value = event.target.value.replaceAll("]", "")
- event.target.value = event.target.value.replaceAll("[", "")
- event.target.value = event.target.value.replaceAll("{", "")
- event.target.value = event.target.value.replaceAll("}", "")
- event.target.value = event.target.value.replaceAll("*", "")
- event.target.value = event.target.value.replaceAll("!", "")
- event.target.value = event.target.value.replaceAll("@", "")
- event.target.value = event.target.value.replaceAll("#", "")
- event.target.value = event.target.value.replaceAll("$", "")
- event.target.value = event.target.value.replaceAll("%", "")
- event.target.value = event.target.value.replaceAll("&", "")
- event.target.value = event.target.value.replaceAll("#", "")
- event.target.value = event.target.value.replaceAll(".", "")
- event.target.value = event.target.value.replaceAll(",", "")
- event.target.value = event.target.value.replaceAll(" ", "_")
- selectedAction.label = event.target.value
- setSelectedAction(selectedAction)
-
- //console.log("SHOULD CHANGE NAME EVERYWHERE ITS USED TOO BASED ON OLD NAME!")
-
+ const parsedSelection = cy.$(":selected");
+ if (selectedNode.data().decorator === true && selectedNode.data("type") !== "COMMENT") {
+ alert.info("This node can't be deleted.");
+ } else {
+ selectedNode.remove();
+ }
+
+ // An attempt at NOT unselecting when removing
/*
- if (nodeaction.label !== curaction.label) {
- console.log("BEACH!")
+ setTimeout(() => {
+ if (parsedSelection.data() !== undefined) {
+ if (parsedSelection.data("id") !== selectedNode.data("id")) {
+ console.log("SHOULD SELECT SINCE ID IS DIFFERENT")
- var params = []
- const fixedName = "$"+curaction.label.toLowerCase().replace(" ", "_")
- for (var actionkey in workflow.actions) {
- if (workflow.actions[actionkey].id === curaction.id) {
- continue
- }
-
- for (var paramkey in workflow.actions[actionkey].parameters) {
- const param = workflow.actions[actionkey].parameters[paramkey]
- if (param.value === null || param.value === undefined || !param.value.includes("$")) {
- continue
- }
-
- const innername = param.value.toLowerCase().replace(" ", "_")
- if (innername.includes(fixedName)) {
- //workflow.actions[actionkey].parameters[paramkey].replace(
- //console.log("FOUND!: ", innername)
- }
+ parsedSelection.select()
}
}
- }
+
+ console.log("Parsed: ", parsedSelection.data("id"), selectedNode.data("id"))
+ }, 2500)
*/
- }
+ };
- const selectedTriggerChange = (event) => {
- selectedTrigger.label = event.target.value
- setSelectedTrigger(selectedTrigger)
- }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ //useEffect(() => {
+ if (firstrequest) {
+ setFirstrequest(false);
+ getWorkflow(props.match.params.key, {});
+ getApps();
+ getAppAuthentication();
+ getEnvironments();
+ getWorkflowExecution(props.match.params.key, "");
+ getAvailableWorkflows(-1);
+ getSettings();
- // Starts on current node and climbs UP the tree to the root object.
- // Sends back everything in it's path
- const getParents = (action) => {
- var allkeys = [action.id]
- var handled = []
- var results = []
+ const cursearch =
+ typeof window === "undefined" || window.location === undefined
+ ? ""
+ : window.location.search;
- // maxiter = max amount of parent nodes to loop
- // also handles breaks if there are issues
- var iterations = 0
- var maxiter = 10
- while(true) {
- for (var key in allkeys) {
- var currentnode = cy.getElementById(allkeys[key])
- if (currentnode === undefined || currentnode === null) {
- continue
- }
-
- if (currentnode.data() === undefined) {
- //console.log("Node doesn't have any data (getParents)! Probably a trigger.")
- handled.push(allkeys[key])
- results.push({"id": allkeys[key], "type": "TRIGGER"})
- } else {
- if (handled.includes(currentnode.data().id)) {
- continue
- } else {
- handled.push(currentnode.data().id)
- results.push(currentnode.data())
+ // FIXME: Don't check specific one here
+ const tmpExec = new URLSearchParams(cursearch).get("execution_highlight");
+ if (
+ tmpExec !== undefined &&
+ tmpExec !== null &&
+ tmpExec === "executions"
+ ) {
+ setExecutionModalOpen(true)
+ const newitem = removeParam("execution_highlight", cursearch);
+ navigate(curpath + newitem)
+ //props.history.push(curpath + newitem);
+ }
+
+ const tmpView = new URLSearchParams(cursearch).get("view");
+ if (
+ tmpView !== undefined &&
+ tmpView !== null &&
+ tmpView === "executions"
+ ) {
+ setExecutionModalOpen(true);
+
+ const newitem = removeParam("view", cursearch);
+ navigate(curpath + newitem)
+ //navigate(`?execution_highlight=${parsed_url}`)
+ //props.history.push(curpath + newitem);
+ }
+ return;
+ }
+
+ // App length necessary cus of cy initialization
+ if (
+ // First load - gets the workflow
+ elements.length === 0 &&
+ workflow.actions !== undefined &&
+ !graphSetup &&
+ Object.getOwnPropertyNames(workflow).length > 0
+ ) {
+
+ setGraphSetup(true);
+ setupGraph();
+ console.log("In graph setup")
+ } else if (
+ // 2nd load - configures cytoscape
+ //
+ !established &&
+ cy !== undefined &&
+ ((apps !== null &&
+ apps !== undefined &&
+ apps.length > 0) || workflow.public === true) &&
+ Object.getOwnPropertyNames(workflow).length > 0 &&
+ authLoaded
+ ) {
+
+ console.log("In POST graph setup!")
+ //This part has to load LAST, as it's kind of not async.
+ //This means we need everything else to happen first.
+
+ setEstablished(true);
+ // Validate if the node is just a node lol
+ cy.edgehandles({
+ handleNodes: (el) => {
+ if (el.isNode() &&
+ !el.data("isButton") &&
+ !el.data("isDescriptor") &&
+ !el.data("isSuggestion") &&
+ el.data("type") !== "COMMENT") {
+ return true
}
- }
- // Get the name / label here too?
- if (currentnode.length === 0) {
- continue
- }
-
- const incomingEdges = currentnode.incomers('edge')
- if (incomingEdges.length === 0) {
- continue
- }
-
- for (var i = 0; i < incomingEdges.length; i++) {
- var tmp = incomingEdges[i]
- if (!allkeys.includes(tmp.data().source)) {
- allkeys.push(tmp.data().source)
- }
- }
- }
-
- if (results.length === allkeys.length || iterations === maxiter) {
- break
- }
-
- iterations += 1
- }
-
- // Remove self
- results = results.filter(data => data.id !== action.id)
- results = results.filter(data => data.type !== "TRIGGER")
- results.push({"label": "Execution Argument", "type": "INTERNAL"})
- return results
- }
-
- // BOLD name: type: required?
- // FORM
- // Dropdown -> static, action, local env, global env
- // VALUE (JSON)
- // {data.name}, {data.description}, {data.required}, {data.schema.type}
-
- //height: "100%",
- const appApiViewStyle = {
- display: "flex",
- flexDirection: "column",
- backgroundColor: "#1F2023",
- color: "white",
- paddingRight: 15,
- paddingLeft: 15,
- minHeight: "100%",
- zIndex: 1000,
- resize: "vertical",
- overflow: "auto",
- }
-
- // Old static one
- //var rightsidebarStyle = {
- // position: "fixed",
- // right: 0,
- // top: appBarSize+1,
- // height: "100%",
- // bottom: 0,
- // minWidth: 350,
- // maxWidth: 350,
- // borderLeft: "1px solid rgb(91, 96, 100)",
- // overflow: "scroll",
- // overflowX: "auto",
- // overflowY: "auto",
- // zIndex: 1000,
- //}
-
- var rightsidebarStyle = {
- position: "fixed",
- top: appBarSize+25,
- right: 25,
- height: "80vh",
- width: 350,
- minWidth: 200,
- maxWidth: 600,
- maxHeight: "100vh",
- border: "1px solid rgb(91, 96, 100)",
- zIndex: 1000,
- borderRadius: theme.palette.borderRadius,
- resize: "both",
- overflow: "auto",
- }
-
- const setTriggerFolderWrapperMulti = event => {
- const { options } = event.target
- const value = []
- for (let i = 0, l = options.length; i < l; i += 1) {
- if (options[i].selected) {
- value.push(options[i].value)
- }
- }
-
- if (selectedTrigger.parameters === null) {
- selectedTrigger.parameters = [[]]
- workflow.triggers[selectedTriggerIndex].parameters = [[]]
- }
-
- // This is a dirty workaround for the static values in the go backend and datastore db
- const fixedValue = value.join(splitter)
- selectedTrigger.parameters[0] = {"value": fixedValue, "name": "outlookfolder"}
- workflow.triggers[selectedTriggerIndex].parameters[0] = {"value": fixedValue, "name": "outlookfolder"}
-
- // This resets state for some reason (:
- setSelectedAction({})
- setSelectedTrigger({})
- setSelectedApp({})
- setSelectedEdge({})
-
- // Set value
- setSelectedTrigger(selectedTrigger)
- setWorkflow(workflow)
- };
-
- //const setTriggerFolderWrapper = (event) => {
- // if (selectedTrigger.parameters === null) {
- // selectedTrigger.parameters = []
- // workflow.triggers[selectedTriggerIndex].parameters = []
- // }
-
- // const folder = triggerFolders.find(a => a.displayName === event.target.value)
- // console.log(event.target.value)
- // console.log(folder)
- //
- // if (folder !== undefined) {
- // workflow.triggers[selectedTriggerIndex].parameters[0] = {"value": folder.displayName, "name": "outlookfolder", "id": folder.id}
- // selectedTrigger.parameters[0] = {"value": folder.displayName, "name": "outlookfolder", "id": folder.id}
- // setWorkflow(workflow)
-
- // // This resets state for some reason (:
- // setSelectedActionEnvironment({})
- // setSelectedAction({})
- // setSelectedTrigger({})
- // setSelectedApp({})
- // setSelectedEdge({})
-
- // // Set value
- // setSelectedTrigger(selectedTrigger)
-
- // } else {
- // alert.error("Some error occurred with folder "+event.target.value)
- // }
- //}
-
- const setTriggerCronWrapper = (value) => {
- if (selectedTrigger.parameters === null) {
- selectedTrigger.parameters = []
- }
-
- workflow.triggers[selectedTriggerIndex].parameters[0] = {"value": value, "name": "cron"}
- setWorkflow(workflow)
- }
-
- const setTriggerOptionsWrapper = (value) => {
- if (selectedTrigger.parameters === null) {
- selectedTrigger.parameters = []
- }
-
- const splitItems = workflow.triggers[selectedTriggerIndex].parameters[2].value.split(",")
- console.log(splitItems)
- if (splitItems.includes(value)) {
- for( var i = 0; i < splitItems.length; i++){
- if (splitItems[i] === value) {
- splitItems.splice(i, 1)
- }
- }
-
- } else {
- splitItems.push(value)
- }
-
- for( var i = 0; i < splitItems.length; i++){
- if (splitItems[i] === "") {
- splitItems.splice(i, 1)
- }
- }
-
- workflow.triggers[selectedTriggerIndex].parameters[2].value = splitItems.join(",")
-
- console.log(splitItems)
- setWorkflow(workflow)
- setLocalFirstrequest(!localFirstrequest)
- }
-
- const setTriggerTextInformationWrapper = (value) => {
- if (selectedTrigger.parameters === null) {
- selectedTrigger.parameters = []
- }
-
- workflow.triggers[selectedTriggerIndex].parameters[0] = {"value": value, "name": "alertinfo"}
- setWorkflow(workflow)
- }
-
- const setTriggerBodyWrapper = (value) => {
- if (selectedTrigger.parameters === null) {
- selectedTrigger.parameters = []
- workflow.triggers[selectedTriggerIndex].parameters[0] = {"value": value, "name": "cron"}
- }
-
- workflow.triggers[selectedTriggerIndex].parameters[1] = {"value": value, "name": "execution_argument"}
- setWorkflow(workflow)
- }
-
- const AppConditionHandler = (props) => {
- const { tmpdata, type } = props
- const [data, ] = useState(tmpdata)
- const [multiline, setMultiline] = useState(false)
- const [showAutocomplete, setShowAutocomplete] = React.useState(false)
- const [actionlist, setActionlist] = React.useState([])
-
- if (tmpdata === undefined) {
- return tmpdata
- }
-
- if (data.variant === "") {
- data.variant = "STATIC_VALUE"
- }
-
-
- // Set actions based on NEXT node, since it should be able to involve those two
- //console.log("IN APPACTIONARG: ", selectedEdge)
- if (actionlist.length === 0) {
- // FIXME: Have previous execution values in here
- actionlist.push({"type": "Execution Argument", "name": "Execution Argument", "value": "$exec", "highlight": "exec", "autocomplete": "exec", "example": "hello"})
- //actionlist.push({"type": "Key:Value store", "name": "Shuffle KV store", "value": "$shuffle_cache", "highlight": "shuffle_cache", "autocomplete": "shuffle_cache", "example": ""})
-
- if (workflow.workflow_variables !== null && workflow.workflow_variables !== undefined && workflow.workflow_variables.length > 0) {
- for (var key in workflow.workflow_variables) {
- const item = workflow.workflow_variables[key]
- actionlist.push({"type": "workflow_variable", "name": item.name, "value": item.value, "id": item.id, "autocomplete": `${item.name.split(" ").join("_")}`, "example": item.value})
- }
- }
-
- // FIXME: Add values from previous executions if they exist
- if (workflow.execution_variables !== null && workflow.execution_variables !== undefined && workflow.execution_variables.length > 0) {
- for (var key in workflow.execution_variables) {
- const item = workflow.execution_variables[key]
- actionlist.push({"type": "execution_variable", "name": item.name, "value": item.value, "id": item.id, "autocomplete": `${item.name.split(" ").join("_")}`, "example": ""})
- }
- }
-
- const destAction = cy.getElementById(selectedEdge.target)
- //console.log(destAction.data())
- var parents = getParents(destAction.data())
- if (parents.length > 1) {
- for (var key in parents) {
- const item = parents[key]
- if (item.label === "Execution Argument") {
- continue
- }
-
- // 1. Take
- const actionvalue = {"type": "action", "id": item.id, "name": item.label, "autocomplete": `${item.label.split(" ").join("_")}`, "example": item.example === undefined ? "" : item.example}
- actionlist.push(actionvalue)
- }
- }
-
- setActionlist(actionlist)
- }
-
- var staticcolor = "inherit"
- var actioncolor = "inherit"
- var varcolor = "inherit"
- if (data.multiline !== undefined && data.multiline !== null && data.multiline === true) {
- setMultiline(true)
- }
-
- var placeholder = "Static value"
- if (data.example !== undefined && data.example !== null && data.example.length > 0) {
- placeholder = data.example
- }
-
- var datafield =
-
- Use "Shuffle Tools" app with "Filter List" action to handle loops
-
- : null
- }
- onClick={() => {
- //console.log("CHANGE FIELD")
- }}
- onBlur={(e) => {
- changeActionVariable(data.action_field, e.target.value)
- setUpdate(Math.random())
- }}
- />
-
- const changeActionVariable = (variable, value) => {
- // set the name
- data.value = value
- data.action_field = variable
-
- //setConditionValue({})
-
- if (type === "source") {
- setSourceValue(data)
- //setDestinationValue(destinationValue)
- } else if (type === "destination") {
- setDestinationValue(data)
- //setSourceValue(sourceValue)
- }
- }
-
- const changeActionParameterVariant = (variant) => {
- if (data.variant === variant) {
- return
- }
-
- data.variant = variant
- data.value = ""
-
- if (variant === "ACTION_RESULT") {
- console.log("SHOULD FIND PARENTS OF EDGE")
-
- // Uses the target's parents, as the target should be executing the checks (I think)
- var parents = getParents(workflow.actions.find(a => a.id === selectedEdge["target"]))
- if (parents.length > 0) {
- data.action_field = parents[0].label
- } else {
- data.action_field = ""
- }
- } else if (variant === "WORKFLOW_VARIABLE") {
- if (workflow.workflow_variables !== null && workflow.workflow_variables !== undefined && workflow.workflow_variables.length > 0) {
- data.action_field = workflow.workflow_variables[0].name
- }
- }
-
- setSourceValue({})
- setConditionValue({})
- setDestinationValue({})
-
- if (type === "source") {
- setSourceValue(data)
- setDestinationValue(destinationValue)
- } else if (type === "destination") {
- setDestinationValue(data)
- setSourceValue(sourceValue)
- }
- }
-
- return (
-
-
- {datafield}
- {actionlist.length === 0 ? null :
-
- Autocomplete
- {
- setShowAutocomplete(false)
-
- //if (!selectedActionParameters[count].value[selectedActionParameters[count].value.length-1] === "$") {
- // setShowDropdown(false)
- //}
-
- setUpdate(Math.random())
- }}
- onClick={() => {
- setShowAutocomplete(true)
- }}
- open={showAutocomplete}
- fullWidth
- style={{borderBottom: `1px solid #f85a3e`, color: "white", height: 50, marginTop: 2,}}
- onChange={(e) => {
- const autocomplete = e.target.value.autocomplete
- const newValue = autocomplete.startsWith("$") ? data.value+autocomplete : `${data.value}$${autocomplete}`
-
- changeActionVariable(data.action_field, newValue)
- }}
- >
- {actionlist.map(data => {
- const icon = data.type === "action" ? : data.type === "workflow_variable" || data.type === "execution_variable" ? :
-
- const handleExecArgumentHover = (inside) => {
- var exec_text_field = document.getElementById("execution_argument_input_field")
- if (exec_text_field !== null) {
- if (inside) {
- exec_text_field.style.border = "2px solid #f85a3e"
- } else {
- exec_text_field.style.border = ""
- }
- }
-
- // Also doing arguments
- if (workflow.triggers !== undefined && workflow.triggers !== null && workflow.triggers.length > 0) {
- for (var key in workflow.triggers) {
- const item = workflow.triggers[key]
-
- var node = cy.getElementById(item.id)
- if (node.length > 0) {
- if (inside) {
- node.addClass('shuffle-hover-highlight')
- } else {
- node.removeClass('shuffle-hover-highlight')
- }
- }
-
- }
- }
- }
-
- const handleActionHover = (inside, actionId) => {
- var node = cy.getElementById(actionId)
- if (node.length > 0) {
- if (inside) {
- node.addClass('shuffle-hover-highlight')
- } else {
- node.removeClass('shuffle-hover-highlight')
- }
- }
- }
-
- return (
- {
- if (data.type === "Execution Argument") {
- handleExecArgumentHover(true)
- } else if (data.type === "action") {
- handleActionHover(true, data.id)
- }
- }} onMouseOut={() => {
- if (data.type === "Execution Argument") {
- handleExecArgumentHover(false)
- } else if (data.type === "action") {
- handleActionHover(false, data.id)
- }
- }}>
-
-
- {icon} {data.name}
-
-
-
- )
- })}
-
-
- }
-
- )
- }
-
-
- const menuItemStyle = {
- color: "white",
- backgroundColor: inputColor
- }
-
- const conditionsModal =
- {
- setConditionsModalOpen(false)
- setSourceValue({})
- setConditionValue({})
- setDestinationValue({})
- }}
- >
-
- Conditions can't be used for loops [ .# ] Learn more
-
-
- Condition
-
-
+ preview: true,
+ toggleOffOnLeave: true,
+ loopAllowed: function (node) {
+ return false;
+ },
+ });
-
-
-
- {
- conditionValue.configuration = !conditionValue.configuration
- setConditionValue(conditionValue)
- setUpdate(Math.random())
- }}>
- {conditionValue.configuration ? "!" : "="}
-
-
-
-
-
- {setVariableAnchorEl(e.currentTarget)}}>
- {conditionValue.value}
-
-
-
-
+ cy.on("boxselect", "node", (e) => {
+ if (e.target.data("isButton") || e.target.data("isDescriptor") || e.target.data("isSuggestion")) {
+ e.target.unselect();
+ }
-
-
-
- {
- setSelectedEdge({})
+ e.target.addClass("selected");
+ });
+ cy.on("boxstart", (e) => {
+ console.log("START");
+ cy.removeListener("select");
+ });
- var data = {
- condition: conditionValue,
- source: sourceValue,
- destination: destinationValue,
- }
-
- setConditionsModalOpen(false)
- if (selectedEdge.conditions === undefined) {
- selectedEdge.conditions = [data]
- } else {
- const curedgeindex = selectedEdge.conditions.findIndex(data => data.source.id === sourceValue.id)
- if (curedgeindex < 0) {
- selectedEdge.conditions.push(data)
- } else {
- selectedEdge.conditions[curedgeindex] = data
- }
- }
+ cy.on("boxend", (e) => {
+ console.log("END: ", cy)
+ var cydata = cy.$(":selected").jsons();
+ if (cydata !== undefined && cydata !== null && cydata.length > 0) {
+ alert.success(`Selected ${cydata.length} element(s). CTRL+C to copy them.`);
+ }
+ });
- var label = ""
- if (selectedEdge.conditions.length === 1) {
- label = selectedEdge.conditions.length+" condition"
- } else if (selectedEdge.conditions.length > 1) {
- label = selectedEdge.conditions.length+" conditions"
- }
+ cy.on("select", "node", (e) => {
+ onNodeSelect(e, appAuthentication);
+ });
+ cy.on("select", "edge", (e) => onEdgeSelect(e));
- var currentedge = cy.getElementById(selectedEdge.id)
- if (currentedge !== undefined && currentedge !== null) {
- currentedge.data().label = label
- }
+ cy.on("unselect", (e) => onUnselect(e));
- setSelectedEdge(selectedEdge)
- workflow.branches[selectedEdgeIndex] = selectedEdge
- setWorkflow(workflow)
- }} color="primary">
- Submit
-
-
-
-
+ cy.on("add", "node", (e) => onNodeAdded(e));
+ cy.on("add", "edge", (e) => onEdgeAdded(e));
+ cy.on("remove", "node", (e) => onNodeRemoved(e));
+ cy.on("remove", "edge", (e) => onEdgeRemoved(e));
- const EdgeSidebar = () => {
- const ConditionHandler = (condition, index) => {
- const [open, setOpen] = React.useState(false)
- const [anchorEl, setAnchorEl] = React.useState(null)
+ cy.on("mouseover", "edge", (e) => onEdgeHover(e));
+ cy.on("mouseout", "edge", (e) => onEdgeHoverOut(e));
+ cy.on("mouseover", "node", (e) => onNodeHover(e));
+ cy.on("mouseout", "node", (e) => onNodeHoverOut(e));
- const duplicateCondition = (conditionIndex) => {
-
- var newEdge = JSON.parse(JSON.stringify(selectedEdge.conditions[conditionIndex]))
- const newUuid = uuidv4()
- newEdge.condition.id = newUuid
- newEdge.source.id = newUuid
- newEdge.destination.id = newUuid
- selectedEdge.conditions.push(newEdge)
+ // Handles dragging
+ cy.on("drag", "node", (e) => onNodeDrag(e, selectedAction));
+ cy.on("free", "node", (e) => onNodeDragStop(e, selectedAction));
- setUpdate(Math.random())
+ cy.on("cxttap", "node", (e) => onCtxTap(e));
+
+ document.title = "Workflow - " + workflow.name;
+ registerKeys();
+ }
+ //})
+
+ const stopSchedule = (trigger, triggerindex) => {
+ fetch(
+ globalUrl +
+ "/api/v1/workflows/" +
+ props.match.params.key +
+ "/schedule/" +
+ trigger.id,
+ {
+ method: "DELETE",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ }
+ )
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for stream results :O!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ // No matter what, it's being stopped.
+ if (!responseJson.success) {
+ if (responseJson.reason !== undefined) {
+ alert.error("Failed to stop schedule: " + responseJson.reason);
+ }
+ } else {
+ alert.success("Successfully stopped schedule");
+ }
+
+ workflow.triggers[triggerindex].status = "stopped";
+ trigger.status = "stopped";
+ setSelectedTrigger(trigger);
+ setWorkflow(workflow);
+ saveWorkflow(workflow);
+ })
+ .catch((error) => {
+ alert.error(error.toString());
+ });
+ };
+
+ const submitSchedule = (trigger, triggerindex) => {
+ if (trigger.name.length <= 0) {
+ alert.error("Error: name can't be empty");
+ return;
+ }
+
+ alert.info("Attempting to create schedule with name " + trigger.name);
+ const data = {
+ name: trigger.name,
+ frequency: workflow.triggers[triggerindex].parameters[0].value,
+ execution_argument: workflow.triggers[triggerindex].parameters[1].value,
+ environment: workflow.triggers[triggerindex].environment,
+ id: trigger.id,
+ };
+
+ fetch(
+ globalUrl + "/api/v1/workflows/" + props.match.params.key + "/schedule",
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ body: JSON.stringify(data),
+ credentials: "include",
+ }
+ )
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for stream results :O!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (!responseJson.success) {
+ alert.error("Failed to set schedule: " + responseJson.reason);
+ } else {
+ alert.success("Successfully created schedule");
+ workflow.triggers[triggerindex].status = "running";
+ trigger.status = "running";
+ setSelectedTrigger(trigger);
+ setWorkflow(workflow);
+ console.log("Should set the status to running and save");
+ saveWorkflow(workflow);
+ }
+ })
+ .catch((error) => {
+ alert.error(error.toString());
+ });
+ };
+
+ const appViewStyle = {
+ marginLeft: 5,
+ marginRight: 5,
+ display: "flex",
+ flexDirection: "column",
+ minHeight: isMobile ? bodyHeight-appBarSize*4 : "100%",
+ maxHeight: isMobile ? bodyHeight-appBarSize*4 : "100%",
+ };
+
+ const paperAppStyle = {
+ borderRadius: theme.palette.borderRadius,
+ minHeight: isMobile ? 50 : 100,
+ maxHeight: isMobile ? 50 : 100,
+ minWidth: isMobile ? 50 : "100%",
+ maxWidth: isMobile ? 50 : "100%",
+ marginTop: "5px",
+ color: "white",
+ backgroundColor: surfaceColor,
+ cursor: "pointer",
+ display: "flex",
+ };
+
+ const paperVariableStyle = {
+ borderRadius: theme.palette.borderRadius,
+ minHeight: 50,
+ maxHeight: 50,
+ minWidth: "100%",
+ maxWidth: "100%",
+ marginTop: "5px",
+ color: "white",
+ backgroundColor: surfaceColor,
+ cursor: "pointer",
+ display: "flex",
+ };
+
+ const VariablesView = () => {
+ const [open, setOpen] = React.useState(false);
+ const [anchorEl, setAnchorEl] = React.useState(null);
+
+ const menuClick = (event) => {
+ setOpen(!open);
+ setAnchorEl(event.currentTarget);
+ };
+
+ const deleteVariable = (variableIndex) => {
+ //console.log("Delete:", variableName);
+ if (workflow.workflow_variables !== undefined && workflow.workflow_variables !== null && workflow.workflow_variables.length > variableIndex) {
+ workflow.workflow_variables.splice(variableIndex, 1)
}
- const deleteCondition = (conditionIndex) => {
- console.log(selectedEdge)
- if (selectedEdge.conditions.length === 1) {
- selectedEdge.conditions = []
- } else {
- selectedEdge.conditions.splice(conditionIndex, 1)
- }
+ //workflow.workflow_variables = workflow.workflow_variables.filter(
+ // (data) => data.name !== variableName
+ //);
+ setWorkflow(workflow);
+ };
- setSelectedEdge(selectedEdge)
- setOpen(false)
- setUpdate(Math.random())
+ const deleteExecutionVariable = (variableIndex) => {
+ if (workflow.execution_variables !== undefined && workflow.execution_variables !== null && workflow.execution_variables.length > variableIndex) {
+ workflow.execution_variables.splice(variableIndex, 1)
}
- const paperVariableStyle = {
- minHeight: 75,
- maxHeight: 75,
- minWidth: "100%",
- maxWidth: "100%",
- marginTop: "5px",
- color: "white",
- backgroundColor: surfaceColor,
- cursor: "pointer",
- display: "flex",
- }
+ //workflow.execution_variables = workflow.execution_variables.filter(
+ // (data) => data.name !== variableName
+ //);
+ setWorkflow(workflow);
+ };
- const menuClick = (event) => {
- console.log("MENU CLICK")
- setOpen(!open)
- setAnchorEl(event.currentTarget)
- }
+ const variableScrollStyle = {
+ margin: 15,
+ overflow: "scroll",
+ height: isMobile ? "100%" : "66vh",
+ overflowX: "auto",
+ overflowY: "auto",
+ flex: "10",
+ };
- return (
- {}}>
-
-
-
{
- setSourceValue(condition.source)
- setConditionValue(condition.condition)
- setDestinationValue(condition.destination)
- setConditionsModalOpen(true)
- }}>
-
- {condition.source.value}
-
-
-
{}}>
- {condition.condition.value}
-
-
-
- {condition.destination.value}
-
-
-
-
-
-
-
-
-
-
- )
- }
+ return (
+
+
+ What are{" "}
+
+ WORKFLOW variables?
+
+ {workflow.workflow_variables === null
+ ? null
+ : workflow.workflow_variables.map((variable, index) => {
+ return (
+
+
{}}>
+
+
+
{
+ setVariableInfo({
+ "name": variable.name,
+ "description": variable.description,
+ "value": variable.value,
+ "index": index,
+ })
+ setVariablesModalOpen(true);
+ }}
+ >
+ Name: {variable.name}
+
+
+
+
+
+
+
+
+
+
+ );
+ })}
+
+ {
+ setVariablesModalOpen(true);
+ setLastSaved(false);
+ }}
+ >
+ New workflow variable
+
+
+
+ What are{" "}
+
+ EXECUTION variables?
+
+ {workflow.execution_variables === null ||
+ workflow.execution_variables === undefined
+ ? null
+ : workflow.execution_variables.map((variable, index) => {
+ return (
+
+
{}}>
+
+
+
{
+ //setNewVariableName(variable.name);
+ setVariableInfo({
+ "name": variable.name,
+ "description": variable.description,
+ "value": variable.value,
+ "index": index,
+ })
+ setExecutionVariablesModalOpen(true);
+ }}
+ >
+ Name: {variable.name}
+
+
+
+
+
+
+
+
+
+
+ );
+ })}
+
+ {
+ setExecutionVariablesModalOpen(true);
+ setLastSaved(false);
+ }}
+ >
+ New execution variable
+
+
+
+
+ );
+ };
- var injectedData =
-
-
+ const handleSetTab = (event, newValue) => {
+ setCurrentView(newValue);
+ };
- if (selectedEdge.conditions !== undefined && selectedEdge.conditions !== null && selectedEdge.conditions.length > 0) {
- injectedData = selectedEdge.conditions.map((condition, index) => {
- return ConditionHandler(condition, index)
- })
- }
+ const HandleLeftView = () => {
+ // Defaults to apps.
+ var thisview = (
+
+ );
+ if (currentView === 1) {
+ thisview = ;
+ } else if (currentView === 2) {
+ thisview = ;
+ }
- // FIXME - remove index
- const conditionId = uuidv4()
- return(
-
-
-
-
- Conditions
-
- {injectedData}
-
-
{
- setSourceValue({"name": "source", "value": "", "variant": "STATIC_VALUE", "action_field": "", "id": conditionId})
- setConditionValue({"name": "condition", "value": "equals", "id": conditionId})
- setDestinationValue({"name": "destination", "value": "", "variant": "STATIC_VALUE", "action_field": "", "id": conditionId})
- setConditionsModalOpen(true)
- }} fullWidth>New condition
-
- )
- }
+ const tabStyle = {
+ maxWidth: isMobile ? leftBarSize : leftBarSize / 3,
+ minWidth: isMobile ? leftBarSize : leftBarSize / 3,
+ flex: 1,
+ textTransform: "none",
+ };
- // 1. GET the trigger authentication data
- // 2. Parse the fields that are used (outlook & gmail)
- // 3. Parse the folders that are selected
- // 4. Start / stop
- const EmailSidebar = () => {
- if (Object.getOwnPropertyNames(selectedTrigger).length === 0) {
- return null
- }
+ const iconStyle = {
+ marginTop: 3,
+ marginRight: 5,
+ };
- if (workflow.triggers[selectedTriggerIndex] === undefined) {
- return null
- }
+ const parsedHeight = isMobile ? bodyHeight - appBarSize*4 : bodyHeight - appBarSize - 50
+ return (
+
+
+ {thisview}
+
+
+
+
+
+
+
+ {isMobile ? null : Apps }
+
+ }
+ style={tabStyle}
+ />
+
+
+
+
+ {isMobile ? null : Triggers }
+
+ }
+ style={tabStyle}
+ />
+
+
+
+
+ {isMobile ? null : Variables }
+
+ }
+ style={tabStyle}
+ />
+
+
+ );
+ };
- if (workflow.triggers[selectedTriggerIndex].parameters === undefined || workflow.triggers[selectedTriggerIndex].parameters === null || workflow.triggers[selectedTriggerIndex].parameters.length === 0) {
- workflow.triggers[selectedTriggerIndex].parameters = [{"value": "No folders selected yet", "name": "outlookfolder"}]
- selectedTrigger.parameters = [{"value": "No folders selected yet", "name": "outlookfolder"}]
- setWorkflow(workflow)
- setSelectedTrigger(selectedTrigger)
- }
+ const triggers = [
+ {
+ name: "Webhook",
+ type: "TRIGGER",
+ status: "uninitialized",
+ trigger_type: "WEBHOOK",
+ errors: null,
+ large_image:
+ "data:image/png;base64,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",
+ is_valid: true,
+ label: "Webhook",
+ environment: "onprem",
+ description: "Simple HTTP webhook",
+ long_description: "Execute a workflow with an unauthicated POST request",
+ },
+ {
+ name: "Schedule",
+ type: "TRIGGER",
+ status: "uninitialized",
+ description: "Schedule execution time",
+ trigger_type: "SCHEDULE",
+ errors: null,
+ large_image:
+ "data:image/png;base64,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",
+ label: "Schedule",
+ is_valid: true,
+ environment: "onprem",
+ long_description: "Create a schedule based on cron",
+ },
+ {
+ name: "Shuffle Workflow",
+ type: "TRIGGER",
+ status: "uninitialized",
+ trigger_type: "SUBFLOW",
+ errors: null,
+ large_image:
+ "data:image/png;base64,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",
+ is_valid: true,
+ label: "Subflow",
+ environment: "onprem",
+ description: "Control another workflow",
+ long_description: "Execute another workflow from this workflow",
+ },
+ {
+ name: "User Input",
+ type: "TRIGGER",
+ status: "running",
+ large_image:
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAkFBMVEX///8VfvsAevsAePsIe/sAdvvO4f5Tl/x4rfxwp/wAdPu20v6ZwP3l7/+Ouf2sy/33+//V5v7z+f/s9P/e6/4eg/vC2v5cnvymyf2Tvf3r9P+nxf3X5/4oiPuFtfzL3/5Ekvslhfs5jPtXm/u61P1ko/zB2/5/sPxwqftRlPo4j/wAcPvP3v6syP2Gtvyew/08Qum3AAAKdUlEQVR4nO2d6XqqOhSGhSTaxglRtLqL4lS03bve/90dS1YYo4I1Ax6+P30YtHlNsoYkhFarUaNGjRo1atSoUaNGjcyV0x4O9oHvWZ73uX05TRe6C/RQOe1BQClBGGPrLIxtRKj32n0WylHoUxShZWUTvO26ukv3e+0CJMJjwsRbjnWX8Hfa+fQiHhMiYY0ZR8dbfBEjPuku6L36zjZPfDYvTITYmSskGOku6z2a+ChDZ+17u3bfddzx+8cw/DxTpi/3dBe3urokqSaEO//6uevO9MVL3UK3jpZi3q8BSdqgN8/jMTm7bdJP0UZ8k6l6jQGR9XbF541mMSP2JurK92vFgBh937h15Sf31gfxwAtNgvfbdw+4ycW4LohxH6TLUvevPI7o18P5dykHHGYvrP68dILNJtgvu9nKGm/AsdhHheW8WxPK29wqddZ9C87JhY0jnV2/FWac/AwQyYvi0t4hZ8ObXDs5OQltkovfEA26qY/tAZHulJe4qr6hE5KkBp3QRlZRmHx+JJ/rQIjjmZ5PjQCQJhU09UR8jPEQRzLOmlUyOmgpd3kFUM7Eii7pBb7oPj+2ORNoxqQt/GJTtGM4OIjP7FOANssuSCrnSNmjIcl/1kT5rOw0rplt3EIxtV7fPibj/qg7CGhyGsWdcca6Ip3qKXspdVk1kAE/wU3kGWSWMiutRS/pnIj7jQU7hT/VFrqSjqwK19x+xH2QdArh28nibsXjkcwcmd4TR1DCNzieckD0R3B3/wh21+7AGQeOXxWVt7pCRrgGl+ZCvInRhUrhETrlPwlUomVqeOp4rMLmcAzAV1KGEBAxII2hTXcvfUCz2qxR2pCs93k4faVbQTyKQjh+jcwp3ksv630aRMXFPD+AKiSpscLV9+zYOQyT0XwXIhkLTk2hUg0N3VjMTSBncsH5d+LrQy+aurAJeo07GiDxlu2yhk7NtKYOQ6LQSN9Y2WPn726T6Aah2KszN499fhj9SoYOLkI39OCQRah4BofuZyb+jo1JG34IcPtv0V22mR2RxZXcmbkQv/FIZptLMBAPAfxMrbGfKa5Ss7RE6aJCB1vDxWE+w4ghTijdXZ0v9sOoLnwp7ZmhgSSdFdzm2Z5fmKLh2fw7WCS4EWc6s1liHY93qENkQRBEKyuSB7TwFj7HMgqyEH2LWWLVRMERdNgRZH/fojQf4vNPVvfAxIwp+Sh+v375rC6AkKUZBFrbXjTLDbYGmOC3YHVPjByQWrNyAyGrGQSEHQEhj8dZpEbAQ76wIyMjU1aHCAhZf7KfixAXW2kFQiGvWWLtkkdps6qEk/SddFX8fv3aZqwg8/8VCMGysrZOjZyFgh4EHpANSpUn5MEP8/9fRqZPLIrhY8Hv1erQhimZfjbCMUssEo0HdL1KhNwB7rJfYpbg5ydwGDXa0oQImuUy0xBMEys4H7KO8qCyhHFCCOMERrpDbjJ4j4q8R1lCHrNNIAw3dDgRplZ4kv8z212SMO53PWRwN2y1xjC4xuORcyWWJEx/5OfKrSUq2sTimDjxOxvXcoR26hPRFSOzwx/B1FM8iH9ApQgR4gOoLCwyePIJRjtjw+isSRnCr3jcjfkbZPB60yX0RB43T2gJwhkf0odeaCGDlykuYLKJB5mtjxKEfOKpdfqKltyYa2d+xOcqBrnz1whjTf/+3Z1eN6Y6QyYXRpxQLr8rRcjkGDmQmOgECypyC38qEBovsBZ2kDn7TIQj3k5n6bPPRNiaw/A2Ss8fPRVhvJISdRK/9lyEDp+FSS1aK0HY7w7CcD41coAmr368qtniq4RvEk72+OztbUS9ZR0Y3zEHKhO1/aiH4idokGXkaHBOE45YJrc465CZP82vDzdSE9+uQDjPTRDnIyIjNQ5KjwjD0GpK2Dc4u0gU0pIjUXxdaUrx2j+ztfNQKcJ+cX4Yb7Jf5U7ny2XPPE8yPtAyOf6wOMcfz5VH6ocWRWdRKzTu6e/2Z2bOTEy4FMzxx8twzhriZDmxZehQcZm5p6xSg96DrCcxdQznfsL8UiNTH6q5m3BRuOSZOchxN2FY6KQoPwZkhu4ldARWaJ0NB5z2aTA4fej2JPcSfggeKcqsCHN7PniSF72e5F7CP4I6JKnAvJ088Y4srQH7vYQDAWFqAfEu81QjnQv+syrJIRzlrupMu+QQFhatIn19UQphYd2xlMcz3Wl3uLrtg6UQFtcdx89tPI7vxaKEUHK8sPOFVEJB1vXwFX/9NfwTjPDr1ZlpGYRTQdaFHmxO16ly22h/pYXIIBTllXYo/v93KvcvbDK/OLyijPCxOxbs80Ujn5cG6mtKeCwUGxcmf+tNKCo2CYRWtaaEoqKdzaooCa8p4ZvgX/yUTmCxa0pYGKvmxZsVbq0poTBsisrg511jXQl7Fzct8XJuo66Ei4vblmCcDRArE4K5mmsmFFtTpmw2WpXQfvn356x/M9HnVBK+i60pQ0yb1KqE5zA3kqijKyUsBm4pkdQKvMqEV6SWMD9SkkVM/l19Ca9WokXiTUpqTNi/0hNTiDUmFM7+FRGFhJlnoisQntQSuteLAxGciBAPhpGKSdh14T37nOiXkUEo/CnTiPtLhBYikSoC/mShkYRORsquhDfqgBwuEUqQHMLR9UqMEOtNKJjFzCGGdSd0bv1fMtjWm5DvJXhZivjkEQrWbWmSNMLiiglNkkbIH7PQLnmE/DEL3ZJIOLllbNRIImF6I32NkknoGNFOZRKKtoJSL6mEN4O3+hO2PP3tVDLhVL89lUzYOmhvp7IJ3bXudiqbEHaXeWbCZP/upyV0NTdT+YS626kCwuvD/E9B2NcJqITw1gjxExAqGznURyha9/lchDoHbRQR3prJeALCS4ulnocwfunT8xJqS4bVEbY1VaI6Ql2DNgoJYeu2JybUlGSoJNQz46aUcKGjJyol1BK8occ+M3NLGoI3xRsQX124KIlQ8YOy6p2i8m1fVAOqfy+G8pkMonx/XuVOUfnGmX3F5nStfvOInlKnqGUj8I3KWlQb0oBWKo2Nnv34VU5HES2vNhmrA7QsHYAqI/D4LW+qpczYaHvTl7JhKX2buSuaU9T45suxGnOqcyu3kxKnqCFkS6Qi3df7giEV6b6td0N++XM12t+wK31B2Jful9Fdeyj6EdJehcl++5KEdFfhWR2Zfl/xULBYUpMMT/fel5EkjryZ8iq6gSzE+KVf2jWT5PixEW30R46cVNGkd+v2ZTxseWmDMT2SkA0jYzohk2i3w1/JvBcpdB+LiD3j9tqP3t75OEDLgGitoAciYvSum0aoKX6QRcXqZ9NKqv2YlX3INxWw1VpsHhDdkKOZLxFgcva/7oxUx1RaFb0JtyorLVvvJvOl9B78wjPSo4leoqCecJ+gMhVIere/3Qj1t9X3UPrZRWlmrg0taHqkVTfCIsHH7e81SbugCiOmgaGv0rmm9p6Ws6sY0U4dXjon0OL0ad+CxAj5gxr1v4Imva11sSqxTVCwrO17S2ONV/OZR6N3c0Nofv5rI0LJJuzWufayctvdXjjrbNaeZXl+MHs5dc1MkBo1atSoUaNGjRo1avQ/138T4Kq0F8t+uwAAAABJRU5ErkJggg==",
+ description: "Wait for user input",
+ trigger_type: "USERINPUT",
+ errors: null,
+ is_valid: cloudSyncEnabled || isCloud ? true : false,
+ label: "User input",
+ environment: "cloud",
+ long_description: "Take user input to continue execution",
+ },
+ {
+ name: "Office365",
+ type: "TRIGGER",
+ status: "uninitialized",
+ description: "Starts upon O365 email",
+ trigger_type: "EMAIL",
+ errors: null,
+ is_valid: cloudSyncEnabled || isCloud ? true : false,
+ label: "Email",
+ environment: "cloud",
+ large_image:
+ "data:image/jpg;base64,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",
+ long_description: "Execute a workflow when you get an email",
+ },
+ {
+ name: "Gmail",
+ type: "TRIGGER",
+ status: "uninitialized",
+ description: "Trigger based on Gmail",
+ trigger_type: "EMAIL",
+ errors: null,
+ is_valid: cloudSyncEnabled || isCloud ? true : false,
+ label: "Email",
+ environment: "cloud",
+ large_image:
+ "data:image/jpg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/hAzFodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQ1IDc5LjE2MzQ5OSwgMjAxOC8wOC8xMy0xNjo0MDoyMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTkgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QTIyMjgyMEYwMDJDMTFFQkJBOEE5OUJBM0MzMTA2RDIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QTIyMjgyMTAwMDJDMTFFQkJBOEE5OUJBM0MzMTA2RDIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDozQTMwMDQxRTAwMEUxMUVCQkE4QTk5QkEzQzMxMDZEMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpBMjIyODIwRTAwMkMxMUVCQkE4QTk5QkEzQzMxMDZEMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv/bAEMAAwICAwICAwMCAwMDAwMEBwUEBAQECQYHBQcKCQsL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",
+ long_description: "Execute a workflow when you get an email",
+ },
+ ];
- const setGmailFolders = () => {
- console.log("In set gmail folders")
- fetch(globalUrl+"/api/v1/triggers/gmail/getFolders?trigger_id="+selectedTrigger.id, {
- method: "GET",
- headers: {"content-type": "application/json"},
- credentials: "include",
- })
- .then((response) => {
- if (response.status !== 200) {
- throw new Error("No folders :o!")
- }
+ const TriggersView = () => {
+ const triggersViewStyle = {
+ marginLeft: "10px",
+ marginRight: "10px",
+ display: "flex",
+ flexDirection: "column",
+ };
- return response.json()
- })
- .then((responseJson) => {
- if (responseJson !== undefined && responseJson !== null && responseJson.success !== false && responseJson.length > 0) {
- setTriggerFolders(responseJson)
- }
+ // Predefined hurr
- if (workflow.triggers[selectedTriggerIndex].parameters.length === 0 && responseJson.length > 0) {
- workflow.triggers[selectedTriggerIndex].parameters = [{"value": responseJson[0].displayName, "name": "outlookfolder", "id": responseJson[0].id}]
- selectedTrigger.parameters = [{"value": responseJson[0].displayName, "name": "outlookfolder", "id": responseJson[0].id}]
- setWorkflow(workflow)
- setSelectedTrigger(selectedTrigger)
- }
- })
- .catch(error => {
- console.log(error.toString())
- })
- }
+ return (
+
+
+ {triggers.map((trigger, index) => {
+ var imageline =
+ trigger.large_image.length === 0 ? (
+
+ ) : (
+
+ );
- const setOutlookFolders = () => {
- fetch(globalUrl+"/api/v1/triggers/outlook/getFolders?trigger_id="+selectedTrigger.id, {
- method: "GET",
- headers: {"content-type": "application/json"},
- credentials: "include",
- })
- .then((response) => {
- if (response.status !== 200) {
- throw new Error("No folders :o!")
- }
+ const color = trigger.is_valid ? green : yellow;
+ return (
+
{
+ handleTriggerDrag(e, trigger);
+ }}
+ onStop={(e) => {
+ handleDragStop(e);
+ }}
+ dragging={false}
+ position={{
+ x: 0,
+ y: 0,
+ }}
+ >
+ {}}>
+
+
+
+ {imageline}
+
+ {isMobile ? null :
+
+
+
+ {trigger.name}
+
+
+
+ {trigger.description}
+
+
+ }
+
+
+
+ );
+ })}
+
+
+ );
+ };
- return response.json()
- })
- .then((responseJson) => {
- if (responseJson !== null && responseJson.success !== false && responseJson.length > 0) {
- setTriggerFolders(responseJson)
- }
+ var newNodeId = "";
+ var parsedApp = {};
+ const handleTriggerDrag = (e, data) => {
+ const cycontainer = cy.container();
+ // Chrome lol
+ if (
+ e.pageX > cycontainer.offsetLeft &&
+ e.pageX < cycontainer.offsetLeft + cycontainer.offsetWidth &&
+ e.pageY > cycontainer.offsetTop &&
+ e.pageY < cycontainer.offsetTop + cycontainer.offsetHeight
+ ) {
+ if (newNodeId.length > 0) {
+ var currentnode = cy.getElementById(newNodeId);
+ if (currentnode.length === 0) {
+ return;
+ }
- if (workflow.triggers[selectedTriggerIndex].parameters.length === 0 && responseJson.length > 0) {
- workflow.triggers[selectedTriggerIndex].parameters = [{"value": responseJson[0].displayName, "name": "outlookfolder", "id": responseJson[0].id}]
- selectedTrigger.parameters = [{"value": responseJson[0].displayName, "name": "outlookfolder", "id": responseJson[0].id}]
- setWorkflow(workflow)
- setSelectedTrigger(selectedTrigger)
- }
- })
- .catch(error => {
- console.log(error.toString())
- })
- }
+ currentnode[0].renderedPosition("x", e.pageX - cycontainer.offsetLeft);
+ currentnode[0].renderedPosition("y", e.pageY - cycontainer.offsetTop);
+ } else {
+ if (workflow.start === "" || workflow.start === undefined) {
+ alert.error("Define a starting action first.");
+ return;
+ }
- const getTriggerAuth = () => {
- fetch(globalUrl+"/api/v1/triggers/outlook/"+selectedTrigger.id, {
- method: "GET",
- headers: {"content-type": "application/json"},
- credentials: "include",
- })
- .then((response) => {
- if (response.status !== 200) {
- throw new Error("No trigger info :o!")
- }
+ const triggerLabel = getNextActionName(data.name);
- return response.json()
- })
- .then((responseJson) => {
- setTriggerAuthentication(responseJson)
- })
- .catch(error => {
- console.log(error.toString())
- })
- }
+ newNodeId = uuidv4();
+ const newposition = {
+ x: e.pageX - cycontainer.offsetLeft,
+ y: e.pageY - cycontainer.offsetTop,
+ };
- // Getting the triggers and the folders if they exist
- // This is horrible hahah
- if (localFirstrequest) {
- getTriggerAuth()
- setOutlookFolders()
- setGmailFolders()
- setLocalFirstrequest(false)
- }
+ const newAppData = {
+ app_name: data.name,
+ app_version: "1.0.0",
+ environment: isCloud ? "cloud" : data.environment,
+ description: data.description,
+ long_description: data.long_description,
+ errors: [],
+ id_: newNodeId,
+ _id_: newNodeId,
+ id: newNodeId,
+ finished: false,
+ label: triggerLabel,
+ type: data.type,
+ is_valid: true,
+ trigger_type: data.trigger_type,
+ large_image: data.large_image,
+ status: "uninitialized",
+ name: data.name,
+ isStartNode: false,
+ position: newposition,
+ };
- const handleButtonRequest = () => {
+ // Can all the data be in here? hmm
+ const nodeToBeAdded = {
+ group: "nodes",
+ data: newAppData,
+ renderedPosition: newposition,
+ };
- }
+ cy.add(nodeToBeAdded);
+ parsedApp = nodeToBeAdded;
+ return;
+ }
+ }
+ };
- const gmailButton = selectedTrigger.name !== "Gmail" ? null :
- {
- //const redirectUri = isCloud ? "https%3A%2F%2Fshuffler.io%2Fapi%2Fv1%2Ftriggers%2Foutlook%2Fregister" : "http%3A%2F%2Flocalhost:5001%2Fapi%2Fv1%2Ftriggers%2Fgmail%2Fregister"
- console.log("HOST: ", window.location.host)
- const redirectUri = isCloud ?
- window.location.host === "localhost:3002" ?
- "http%3A%2F%2Flocalhost:5002%2Fapi%2Fv1%2Ftriggers%2Fgmail%2Fregister"
- : "https%3A%2F%2Fshuffler.io%2Fapi%2Fv1%2Ftriggers%2Fgmail%2Fregister"
- : "http%3A%2F%2Flocalhost:5001%2Fapi%2Fv1%2Ftriggers%2Fgmail%2Fregister"
+ const handleDragStop = (e, app) => {
+ var currentnode = cy.getElementById(newNodeId);
+ if (
+ currentnode === undefined ||
+ currentnode === null ||
+ currentnode.length === 0
+ ) {
+ return;
+ }
- const client_id = "253565968129-c0a35knic7q1pdk6i6qk9gdkvr07ci49.apps.googleusercontent.com"
- //const username = isCloud ? userdata.username : userdata.id
- const username = userdata.id
- console.log(redirectUri)
- console.log("USER: ", username, userdata)
+ // Using remove & replace, as this triggers the function
+ // onNodeAdded() with this node after it's added
- const branch = workflow.branches.find(branch => branch.source_id === selectedTrigger.id)
- if (branch === undefined || branch === null) {
- alert.error("No startnode connected to node. Connect it to an action.")
- return
- }
+ currentnode.remove();
+ parsedApp.data.finished = true;
+ parsedApp.data.position = currentnode.renderedPosition();
+ parsedApp.position = currentnode.renderedPosition();
+ parsedApp.renderedPosition = currentnode.renderedPosition();
- console.log("BRANCH: ", branch)
- const startnode = branch.destination_id
- const scopes = "https://www.googleapis.com/auth/gmail.readonly"
- const url = `https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent&client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${scopes}&state=workflow_id%3D${props.match.params.key}%26trigger_id%3D${selectedTrigger.id}%26username%3D${username}%26type%3Dgmail%26start%3d${startnode}`
- console.log("URL: ", url)
-
- var newwin = window.open(url, "", "width=800,height=600")
-
- // Check whether we got a callback somewhere
- var id = setInterval(function () {
- fetch(globalUrl+"/api/v1/triggers/gmail/"+selectedTrigger.id, {
- method: "GET",
- headers: {"content-type": "application/json"},
- credentials: "include",
- })
- .then((response) => {
- if (response.status !== 200) {
- throw new Error("No trigger info :o!")
- }
-
- return response.json()
- })
- .then((responseJson) => {
- console.log("RESPONSE: ")
- setTriggerAuthentication(responseJson)
- clearInterval(id)
- newwin.close()
- setGmailFolders()
- })
- .catch(error => {
- console.log(error.toString())
- })
- }, 2500)
-
- saveWorkflow(workflow)
- }} >
- Sign in with Google
-
-
- const outlookButton = selectedTrigger.name !== "Office365" ? null :
- {
- //const redirectUri = isCloud ? "https%3A%2F%2Fshuffler.io%2Fapi%2Fv1%2Ftriggers%2Foutlook%2Fregister" : "http%3A%2F%2Flocalhost:5001%2Fapi%2Fv1%2Ftriggers%2Foutlook%2Fregister"
- //const redirectUri = isCloud ? "http%3A%2F%2Flocalhost:5002%2Fapi%2Fv1%2Ftriggers%2Foutlook%2Fregister" : "http%3A%2F%2Flocalhost:5001%2Fapi%2Fv1%2Ftriggers%2Foutlook%2Fregister"
- const redirectUri = isCloud ?
- window.location.host === "localhost:3002" ?
- "http%3A%2F%2Flocalhost:5002%2Fapi%2Fv1%2Ftriggers%2Foutlook%2Fregister"
- : "https%3A%2F%2Fshuffler.io%2Fapi%2Fv1%2Ftriggers%2Foutlook%2Fregister"
- : "http%3A%2F%2Flocalhost:5001%2Fapi%2Fv1%2Ftriggers%2Foutlook%2Fregister"
-
- //const client_id = "fd55c175-aa30-4fa6-b303-09a29fb3f750"
- const client_id = "bb4bff85-0d0b-4f5d-8a69-3cee8029b11a"
+ var newAppData = parsedApp.data;
+ if (newAppData.type === "ACTION") {
- //isCloud ? userdata.username :
- const username = userdata.id
- console.log(redirectUri)
- console.log("USER: ", username, userdata)
+ //const activateApp = (appid) => {
+ if (newAppData.activated === false) {
+ console.log("SHOULD ACTIVATE!")
+ activateApp(newAppData.app_id)
+ }
- const branch = workflow.branches.find(branch => branch.source_id === selectedTrigger.id)
- if (branch === undefined || branch === null) {
- alert.error("No startnode connected to node. Connect it to an action.")
- return
- }
+ // AUTHENTICATION
+ if (app.authentication.required) {
+ console.log("App auth is required!")
- //console.log("SELECTED: ", selectedTrigger.
+ // Setup auth here :)
+ const authenticationOptions = [];
+ var findAuthId = "";
+ if (
+ newAppData.authentication_id !== null &&
+ newAppData.authentication_id !== undefined &&
+ newAppData.authentication_id.length > 0
+ ) {
+ findAuthId = newAppData.authentication_id;
+ }
- console.log("BRANCH: ", branch)
- const startnode = branch.destination_id
- const url = `https://login.microsoftonline.com/common/oauth2/authorize?access_type=offline&client_id=${client_id}&redirect_uri=${redirectUri}&resource=https%3A%2F%2Fgraph.microsoft.com&response_type=code&scope=Mail.Read+User.Read+https%3A%2F%2Foutlook.office.com%2Fmail.read&prompt=login&state=workflow_id%3D${props.match.params.key}%26trigger_id%3D${selectedTrigger.id}%26username%3D${username}%26type%3Doutlook%26start%3d${startnode}`
- //const url = `https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent&client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${scopes}&state=workflow_id%3D${props.match.params.key}%26trigger_id%3D${selectedTrigger.id}%26username%3D${username}%26type%3Dgmail%26start%3d${startnode}`
+ console.log("Found auth: ", findAuthId)
+ var tmpAuth = JSON.parse(JSON.stringify(appAuthentication));
+ for (var key in tmpAuth) {
+ var item = tmpAuth[key];
- //const scopes = "https://www.googleapis.com/auth/gmail.readonly"
- //const url = `https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent&client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${scopes}&state=workflow_id%3D${props.match.params.key}%26trigger_id%3D${selectedTrigger.id}%26username%3D${username}%26type%3Dgmail%26start%3d${startnode}`
+ const newfields = {};
+ for (var filterkey in item.fields) {
+ newfields[item.fields[filterkey].key] =
+ item.fields[filterkey].value;
+ }
+ item.fields = newfields;
+ if (item.app.name === app.name) {
+ authenticationOptions.push(item);
+ if (item.id === findAuthId) {
+ newAppData.selectedAuthentication = item;
+ }
+ }
+ }
- console.log("URL: ", url)
+ if (
+ authenticationOptions !== undefined &&
+ authenticationOptions !== null &&
+ authenticationOptions.length > 0
+ ) {
+ for (var key in authenticationOptions) {
+ const option = authenticationOptions[key];
+ if (option.active) {
+ newAppData.selectedAuthentication = option;
+ newAppData.authentication_id = option.id;
+ break;
+ }
+ }
+ }
+ } else {
+ newAppData.authentication = [];
+ newAppData.authentication_id = "";
+ newAppData.selectedAuthentication = {};
+ }
- var newwin = window.open(url, "", "width=800,height=600")
+ parsedApp.data = newAppData;
+ cy.add(parsedApp);
+ } else if (newAppData.type === "TRIGGER") {
+ cy.add(parsedApp);
+ }
+
+ newNodeId = "";
+ parsedApp = {};
+ };
+
+ const appScrollStyle = {
+ overflow: "scroll",
+ maxHeight: isMobile ? bodyHeight-appBarSize*4 : bodyHeight - appBarSize - 55 - 50,
+ minHeight: isMobile ? bodyHeight-appBarSize*4 : bodyHeight - appBarSize - 55 - 50,
+ marginTop: 1,
+ overflowY: "auto",
+ overflowX: "hidden",
+ };
+
+ const handleAppDrag = (e, app) => {
+ const cycontainer = cy.container();
+
+ //console.log("e: ", e)
+ //console.log("Offset: ", cycontainer)
+
+ // Chrome lol
+ if (
+ e.pageX > cycontainer.offsetLeft &&
+ e.pageX < cycontainer.offsetLeft + cycontainer.offsetWidth &&
+ e.pageY > cycontainer.offsetTop &&
+ e.pageY < cycontainer.offsetTop + cycontainer.offsetHeight
+ ) {
+ if (newNodeId.length > 0) {
+ var currentnode = cy.getElementById(newNodeId);
+ if (
+ currentnode === undefined ||
+ currentnode === null ||
+ currentnode.length === 0
+ ) {
+ return;
+ }
+
+ currentnode[0].renderedPosition("x", e.pageX - cycontainer.offsetLeft);
+ currentnode[0].renderedPosition("y", e.pageY - cycontainer.offsetTop);
+ } else {
+ if (workflow.public) {
+ console.log("workflow is public - not adding");
+ return;
+ }
+
+ if (
+ app.actions === undefined ||
+ app.actions === null ||
+ app.actions.length === 0
+ ) {
+ alert.error(
+ "App " +
+ app.name +
+ " currently has no actions to perform. Please go to https://shuffler.io/apps to edit it."
+ );
+ return;
+ }
+
+ newNodeId = uuidv4();
+ const actionType = "ACTION";
+ const actionLabel = getNextActionName(app.name);
+ var parameters = null;
+ var example = "";
+ var description = ""
+
+ if (
+ app.actions[0].parameters !== null &&
+ app.actions[0].parameters.length > 0
+ ) {
+ parameters = app.actions[0].parameters;
+ }
+
+ if (
+ app.actions[0].returns.example !== undefined &&
+ app.actions[0].returns.example !== null &&
+ app.actions[0].returns.example.length > 0
+ ) {
+ example = app.actions[0].returns.example;
+ }
+
+ if (
+ app.actions[0].description !== undefined &&
+ app.actions[0].description !== null &&
+ app.actions[0].description.length > 0
+ ) {
+ description = app.actions[0].description
+ }
+
+ const parsedEnvironments =
+ environments === null || environments === []
+ ? "cloud"
+ : environments[defaultEnvironmentIndex] === undefined
+ ? "cloud"
+ : environments[defaultEnvironmentIndex].Name;
+
+ // activated: app.generated === true ? app.activated === false ? false : true : true,
+ const newAppData = {
+ app_name: app.name,
+ app_version: app.app_version,
+ app_id: app.id,
+ sharing: app.sharing,
+ private_id: app.private_id,
+ description: description,
+ environment: parsedEnvironments,
+ errors: [],
+ finished: false,
+ id_: newNodeId,
+ _id_: newNodeId,
+ id: newNodeId,
+ is_valid: true,
+ label: actionLabel,
+ type: actionType,
+ name: app.actions[0].name,
+ parameters: parameters,
+ isStartNode: false,
+ large_image: app.large_image,
+ run_magic_output: false,
+ authentication: [],
+ execution_variable: undefined,
+ example: example,
+ category:
+ app.categories !== null &&
+ app.categories !== undefined &&
+ app.categories.length > 0
+ ? app.categories[0]
+ : "",
+ authentication_id: "",
+ finished: false,
+ };
+
+ // FIXME: overwrite category if the ACTION chosen has a different category
+
+ // const image = "url("+app.large_image+")"
+ // FIXME - find the cytoscape offset position
+ // Can this be done with zoom calculations?
+ const nodeToBeAdded = {
+ group: "nodes",
+ data: newAppData,
+ renderedPosition: {
+ x: e.pageX - cycontainer.offsetLeft,
+ y: e.pageY - cycontainer.offsetTop,
+ },
+ };
+
+ parsedApp = nodeToBeAdded;
+ cy.add(nodeToBeAdded);
+ return;
+ }
+ }
+ };
+
+ const AppView = (props) => {
+ const { allApps, prioritizedApps, filteredApps } = props;
+ const [visibleApps, setVisibleApps] = React.useState(
+ prioritizedApps.concat(
+ filteredApps.filter((innerapp) => !internalIds.includes(innerapp.id))
+ )
+ );
+
+ var delay = -75
+ var runDelay = false
+
+ const ParsedAppPaper = (props) => {
+ const app = props.app;
+ const [hover, setHover] = React.useState(false);
+
+ const maxlen = 24;
+ var newAppname = app.name;
+ newAppname = newAppname.charAt(0).toUpperCase() + newAppname.substring(1);
+ if (newAppname.length > maxlen) {
+ newAppname = newAppname.slice(0, maxlen) + "..";
+ }
+
+ newAppname = newAppname.replaceAll("_", " ");
+
+ const image = app.large_image;
+ const newAppStyle = JSON.parse(JSON.stringify(paperAppStyle));
+ const pixelSize = !hover ? "2px" : "4px";
+ //newAppStyle.borderLeft = app.is_valid && app.actions !== null && app.actions !== undefined && app.actions.length > 0 && !(app.activated && app.generated)
+ newAppStyle.borderLeft = app.is_valid && app.actions !== null && app.actions !== undefined && app.actions.length > 0
+ ? `${pixelSize} solid ${green}`
+ : `${pixelSize} solid ${yellow}`;
+
+ if (!app.activated && app.generated) {
+ newAppStyle.borderLeft = `${pixelSize} solid ${yellow}`;
+ }
+
+ return (
+ {
+ handleAppDrag(e, app);
+ }}
+ onStop={(e) => {
+ handleDragStop(e, app);
+ }}
+ key={app.id}
+ dragging={false}
+ position={{
+ x: 0,
+ y: 0,
+ }}
+ >
+ {
+ setHover(true);
+ }}
+ onMouseOut={() => {
+ setHover(false);
+ }}
+ onClick={() => {
+ if (isMobile) {
+ newNodeId = uuidv4();
+ const actionType = "ACTION";
+ const actionLabel = getNextActionName(app.name);
+ var parameters = null;
+ var example = "";
+ var description = ""
+
+ if (
+ app.actions[0].parameters !== null &&
+ app.actions[0].parameters.length > 0
+ ) {
+ parameters = app.actions[0].parameters;
+ }
+
+ if (
+ app.actions[0].returns.example !== undefined &&
+ app.actions[0].returns.example !== null &&
+ app.actions[0].returns.example.length > 0
+ ) {
+ example = app.actions[0].returns.example;
+ }
+
+ if (
+ app.actions[0].description !== undefined &&
+ app.actions[0].description !== null &&
+ app.actions[0].description.length > 0
+ ) {
+ description = app.actions[0].description
+ }
+
+ const parsedEnvironments =
+ environments === null || environments === []
+ ? "cloud"
+ : environments[defaultEnvironmentIndex] === undefined
+ ? "cloud"
+ : environments[defaultEnvironmentIndex].Name;
+
+ // activated: app.generated === true ? app.activated === false ? false : true : true,
+ const newAppData = {
+ app_name: app.name,
+ app_version: app.app_version,
+ app_id: app.id,
+ sharing: app.sharing,
+ private_id: app.private_id,
+ description: description,
+ environment: parsedEnvironments,
+ errors: [],
+ finished: false,
+ id_: newNodeId,
+ _id_: newNodeId,
+ id: newNodeId,
+ is_valid: true,
+ label: actionLabel,
+ type: actionType,
+ name: app.actions[0].name,
+ parameters: parameters,
+ isStartNode: false,
+ large_image: app.large_image,
+ run_magic_output: false,
+ authentication: [],
+ execution_variable: undefined,
+ example: example,
+ category:
+ app.categories !== null &&
+ app.categories !== undefined &&
+ app.categories.length > 0
+ ? app.categories[0]
+ : "",
+ authentication_id: "",
+ finished: false,
+ };
+
+ const nodeToBeAdded = {
+ group: "nodes",
+ data: newAppData,
+ renderedPosition: {
+ x: 100,
+ y: 100,
+ },
+ };
+
+ parsedApp = nodeToBeAdded;
+ cy.add(nodeToBeAdded);
- // Check whether we got a callback somewhere
- var id = setInterval(function () {
- fetch(globalUrl+"/api/v1/triggers/outlook/"+selectedTrigger.id, {
- method: "GET",
- headers: {"content-type": "application/json"},
- credentials: "include",
- })
- .then((response) => {
- if (response.status !== 200) {
- throw new Error("No trigger info :o!")
}
-
- return response.json()
- })
- .then((responseJson) => {
- setTriggerAuthentication(responseJson)
- clearInterval(id)
- newwin.close()
- setOutlookFolders()
- })
- .catch(error => {
- console.log(error.toString())
- })
- }, 2500)
-
- saveWorkflow(workflow)
- }} >
- Sign in with Office365
-
-
-
- // FIXME - set everything in here to multifolder etc
- var triggerInfo = "SET UP BUT NO TYPE :)"
- if (Object.getOwnPropertyNames(triggerAuthentication).length > 0) {
- // Should get the folders if they don't already exist
-
- if (triggerAuthentication.type === "outlook" || triggerAuthentication.type === "gmail" ) {
- triggerInfo =
- {selectedTrigger.status === "running" ? null :
-
-
- {outlookButton}
- {gmailButton}
-
- }
-
- {triggerFolders === undefined || triggerFolders === null ?
- null :
-
-
-
+
+
+
+
+ {isMobile ? null :
+ {
- //setTriggerFolderWrapper(e)
- setTriggerFolderWrapperMulti(e)
- }}
- fullWidth
- input={ }
- key={selectedTrigger}
>
- {triggerFolders.map(folder => {
- //console.log("FOLDER: ", folder)
- //var folderItem =
- var folderItem =
-
- {folder.displayName}
-
+
+
+ {newAppname}
+
+
+
+
+ Version: {app.app_version}
+
+
+
+
+ {app.description}
+
+
+
+ }
+
+
+
+ );
+ };
- if (folder.childFolderCount > 0) {
- // Here to handle subfolders sometime later
- folderItem =
-
- {folder.displayName}
-
- }
+ const runSearch = (value) => {
+ if (value.length > 0) {
+ var newApps = allApps.filter(
+ (app) =>
+ app.name
+ .toLowerCase()
+ .includes(
+ value.trim().toLowerCase() ||
+ app.description
+ .toLowerCase()
+ .includes(value.trim().toLowerCase())
+ ) && !(!app.activated && app.generated)
+ );
- return folderItem
- })}
-
-
- }
-
- } else if (triggerAuthentication.type === "gmail") {
- console.log("AUTH: ", triggerAuthentication)
- triggerInfo = "SPECIAL GMAIL"
+ // Extend search
+ if (newApps.length === 0) {
+ const searchvalue = value.trim().toLowerCase();
+ newApps = allApps.filter((app) => {
+ for (var key in app.actions) {
+ const inneraction = app.actions[key];
+ if (inneraction.name.toLowerCase().includes(searchvalue)) {
+ return true;
+ }
+ }
+
+ return false;
+ });
+ }
+
+ setVisibleApps(newApps);
+ } else {
+ setVisibleApps(
+ prioritizedApps.concat(
+ filteredApps.filter(
+ (innerapp) => !internalIds.includes(innerapp.id)
+ )
+ )
+ );
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+ ),
+ }}
+ fullWidth
+ color="primary"
+ placeholder={"Search Active Apps"}
+ id="appsearch"
+ onKeyPress={(event) => {
+ if (event.key === "Enter") {
+ event.target.blur(event);
+ }
+ }}
+ onBlur={(event) => {
+ console.log("BLUR: ", event.target.value);
+ runSearch(event.target.value);
+ }}
+ />
+ {visibleApps.length > 0 ? (
+
+ {visibleApps.map((app, index) => {
+ if (app.invalid) {
+ return null;
+ }
+
+ var extraMessage = ""
+ if (index == 2) {
+ extraMessage =
+ }
+
+ delay += 75
+ return (
+ runDelay ?
+
+
+
+ :
+
+ )
+ })}
+
+ ) : apps.length > 0 ? (
+
+
+ Couldn't find app. Is it active?
+
+
+ ) : (
+
+
+
+ Loading Apps
+
+
+ )}
+
+
+ );
+ };
+
+ const getNextActionName = (appName) => {
+ var highest = "";
+ const allitems = workflow.actions.concat(workflow.triggers);
+ for (var key in allitems) {
+ const item = allitems[key];
+ if (
+ item.app_name === appName &&
+ item.label !== undefined &&
+ item.label !== null
+ ) {
+ var number = item.label.split("_");
+ if (
+ isNaN(number[-1]) &&
+ parseInt(number[number.length - 1]) > highest
+ ) {
+ highest = number[number.length - 1];
+ }
+ }
+ }
+
+ if (highest) {
+ return appName + "_" + (parseInt(highest) + 1);
+ } else {
+ return appName + "_" + 1;
+ }
+ };
+
+ const setNewSelectedAction = (e) => {
+ if (selectedApp.actions === undefined || selectedApp.actions === null) {
+ return
+ }
+
+ const newaction = selectedApp.actions.find(
+ (a) => a.name === e.target.value
+ );
+
+ if (newaction === undefined || newaction === null) {
+ alert.error("Failed to find the action");
+ return;
+ }
+
+ if (workflow.actions !== undefined && workflow.actions !== null) {
+ const foundInfo = workflow.actions.find(ac => ac.id === selectedAction.id)
+ console.log("aigo: ", foundInfo)
+ }
+
+ console.log("PRe: ", selectedAction)
+
+ // Does this one find the wrong one?
+ //var newSelectedAction = JSON.parse(JSON.stringify(selectedAction))
+ var newSelectedAction = selectedAction
+ newSelectedAction.name = newaction.name;
+ newSelectedAction.parameters = JSON.parse(JSON.stringify(newaction.parameters))
+ newSelectedAction.errors = [];
+ newSelectedAction.isValid = true;
+ newSelectedAction.is_valid = true;
+ //console.log(newSelectedAction)
+
+ // Simmple action swap autocompleter
+ if (selectedAction.parameters !== undefined && newSelectedAction.parameters !== undefined && selectedAction.id === newSelectedAction.id) {
+ console.log("OLD: ", selectedAction, "NEW: ", newSelectedAction)
+ for (var paramkey in selectedAction.parameters) {
+ const param = selectedAction.parameters[paramkey];
+
+ if (param.value === null || param.value === undefined || param.value.length === 0) {
+ continue
+ }
+
+ if (param.name === "body") {
+ //console.log("Param: ", param)
+ continue
+ }
+
+ if (param.name === "headers") {
+ console.log("Swap header?")
+ //newSelectedAction.parameters[newParamIndex].value = param.value
+ }
+
+ const newParamIndex = newSelectedAction.parameters.findIndex(paramdata => paramdata.name === param.name)
+ if (newParamIndex < 0) {
+ continue
+ }
+
+ newSelectedAction.parameters[newParamIndex].value = param.value
}
}
-
- // Check
- const argumentView = Object.getOwnPropertyNames(triggerAuthentication).length > 0 ?
-
- {triggerInfo}
-
- :
-
-
- {outlookButton}
- {gmailButton}
-
-
- return(
-
-
-
-
- Name
-
-
+ if (newSelectedAction.app_name === "Shuffle Tools") {
+ const iconInfo = GetIconInfo(newSelectedAction);
+ console.log("ICONINFO: ", iconInfo);
+ const svg_pin = `
`;
+ const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
+ newSelectedAction.large_image = svgpin_Url;
+ newSelectedAction.fillGradient = iconInfo.fillGradient;
+ newSelectedAction.fillstyle = "solid";
+ if (
+ newSelectedAction.fillGradient !== undefined &&
+ newSelectedAction.fillGradient !== null &&
+ newSelectedAction.fillGradient.length > 0
+ ) {
+ newSelectedAction.fillstyle = "linear-gradient";
+ console.log("GRADIENT!: ", newSelectedAction);
+ } else {
+ newSelectedAction.iconBackground = iconInfo.iconBackgroundColor;
+ }
-
- Environment:
-
-
-
- {argumentView}
-
-
-
-
- {
- startMailSub(selectedTrigger, selectedTriggerIndex)
- }} color="primary">
- Start
-
- {
- stopMailSub(selectedTrigger, selectedTriggerIndex)
- }} color="primary">
- Stop
-
-
-
-
-
- )
+ const foundnode = cy.getElementById(newSelectedAction.id);
+ if (foundnode !== null && foundnode !== undefined) {
+ console.log("UPDATING NODE!");
+ foundnode.data(newSelectedAction);
+ }
+ }
+
+
+ // Takes an action as input, then runs through and updates the relevant fields
+ // based on previous actions'
+ newSelectedAction = RunAutocompleter(newSelectedAction);
+
+ if (
+ newaction.returns.example !== undefined &&
+ newaction.returns.example !== null &&
+ newaction.returns.example.length > 0
+ ) {
+ newSelectedAction.example = newaction.returns.example;
+ }
+
+ if (
+ newaction.description !== undefined &&
+ newaction.description !== null &&
+ newaction.description.length > 0
+ ) {
+ newSelectedAction.description = newaction.description
+ }
+
+ // FIXME - this is broken sometimes lol
+ //var env = environments.find(a => a.Name === newaction.environment)
+ //if ((!env || env === undefined) && selectedAction.environment === undefined ) {
+ // env = environments[defaultEnvironmentIndex]
+ //}
+ //setSelectedActionEnvironment(env)
+
+
+ console.log("NEW ACTION: ", newSelectedAction);
+ setSelectedAction(newSelectedAction);
+ if (workflow.actions !== undefined && workflow.actions !== null && workflow.actions.length > 0) {
+ const foundActionIndex = workflow.actions.findIndex(actiondata => actiondata.id === newSelectedAction.id)
+ console.log("Found action on index ", foundActionIndex)
+ if (foundActionIndex >= 0) {
+ workflow.actions[foundActionIndex] = newSelectedAction
+ setWorkflow(workflow)
+ }
+ }
+ setUpdate(Math.random());
+
+ // FIXME - should change icon-node (descriptor) as well
+ const allNodes = cy.nodes().jsons();
+ for (var key in allNodes) {
+ const currentNode = allNodes[key];
+ if (
+ currentNode.data.attachedTo === selectedAction.id &&
+ currentNode.data.isDescriptor
+ ) {
+ const foundnode = cy.getElementById(currentNode.data.id);
+ if (foundnode !== null && foundnode !== undefined) {
+ const iconInfo = GetIconInfo(newaction);
+ const svg_pin = ` `;
+ const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
+ foundnode.data("image", svgpin_Url);
+ foundnode.data("imageColor", iconInfo.iconBackgroundColor);
+ }
+
+ break;
+ }
+ }
+ };
+
+ // APPSELECT at top
+ // appname & version
+ // description
+ // ACTION select
+ const selectedNameChange = (event) => {
+ event.target.value = event.target.value.replaceAll("(", "");
+ event.target.value = event.target.value.replaceAll(")", "");
+ event.target.value = event.target.value.replaceAll("]", "");
+ event.target.value = event.target.value.replaceAll("[", "");
+ event.target.value = event.target.value.replaceAll("{", "");
+ event.target.value = event.target.value.replaceAll("}", "");
+ event.target.value = event.target.value.replaceAll("*", "");
+ event.target.value = event.target.value.replaceAll("!", "");
+ event.target.value = event.target.value.replaceAll("@", "");
+ event.target.value = event.target.value.replaceAll("#", "");
+ event.target.value = event.target.value.replaceAll("$", "");
+ event.target.value = event.target.value.replaceAll("%", "");
+ event.target.value = event.target.value.replaceAll("&", "");
+ event.target.value = event.target.value.replaceAll("#", "");
+ event.target.value = event.target.value.replaceAll(".", "");
+ event.target.value = event.target.value.replaceAll(",", "");
+ event.target.value = event.target.value.replaceAll(" ", "_");
+
+ selectedAction.label = event.target.value;
+ setSelectedAction(selectedAction);
+ };
+
+ const actionDelayChange = (event) => {
+ if (isNaN(event.target.value)) {
+ console.log("NAN: ", event.target.value)
+ return
+ }
+
+ const parsedNumber = parseInt(event.target.value)
+ if (parsedNumber > 86400) {
+ console.log("Max number is 1 day (86400)")
+ return
+ }
+
+ selectedAction.execution_delay = parsedNumber
+ setSelectedAction(selectedAction)
}
- const SubflowSidebar = () => {
- const [menuPosition, setMenuPosition] = useState(null)
- const [showDropdown, setShowDropdown] = React.useState(false)
- const [showDropdownNumber, setShowDropdownNumber] = React.useState(0)
- const [showAutocomplete, setShowAutocomplete] = React.useState(false)
- const [actionlist, setActionlist] = React.useState([])
+ const selectedTriggerChange = (event) => {
+ selectedTrigger.label = event.target.value;
+ setSelectedTrigger(selectedTrigger);
+ };
- if (actionlist.length === 0) {
- // FIXME: Have previous execution values in here
- actionlist.push({"type": "Execution Argument", "name": "Execution Argument", "value": "$exec", "highlight": "exec", "autocomplete": "exec", "example": "hello"})
- if (workflow.workflow_variables !== null && workflow.workflow_variables !== undefined && workflow.workflow_variables.length > 0) {
- for (var key in workflow.workflow_variables) {
- const item = workflow.workflow_variables[key]
- actionlist.push({"type": "workflow_variable", "name": item.name, "value": item.value, "id": item.id, "autocomplete": `${item.name.split(" ").join("_")}`, "example": item.value})
- }
- }
+ // Starts on current node and climbs UP the tree to the root object.
+ // Sends back everything in it's path
+ const getParents = (action) => {
+ if (action === undefined || action === null) {
+ return []
+ }
- // FIXME: Add values from previous executions if they exist
- if (workflow.execution_variables !== null && workflow.execution_variables !== undefined && workflow.execution_variables.length > 0) {
- for (var key in workflow.execution_variables) {
- const item = workflow.execution_variables[key]
- actionlist.push({"type": "execution_variable", "name": item.name, "value": item.value, "id": item.id, "autocomplete": `${item.name.split(" ").join("_")}`, "example": ""})
- }
- }
+ var allkeys = [action.id];
+ var handled = [];
+ var results = [];
- var parents = getParents(selectedAction)
- if (parents.length > 1) {
- for (var key in parents) {
- const item = parents[key]
- if (item.label === "Execution Argument") {
+ // maxiter = max amount of parent nodes to loop
+ // also handles breaks if there are issues
+ var iterations = 0;
+ var maxiter = 10;
+ while (true) {
+ for (var key in allkeys) {
+ var currentnode = cy.getElementById(allkeys[key]);
+ if (currentnode === undefined || currentnode === null) {
+ continue;
+ }
+
+ if (currentnode.data() === undefined) {
+ handled.push(allkeys[key]);
+ results.push({ id: allkeys[key], type: "TRIGGER" });
+ } else {
+ if (handled.includes(currentnode.data().id)) {
+ continue;
+ } else {
+ handled.push(currentnode.data().id);
+ results.push(currentnode.data());
+ }
+ }
+
+ // Get the name / label here too?
+ if (currentnode.length === 0) {
+ continue;
+ }
+
+ const incomingEdges = currentnode.incomers("edge");
+ if (incomingEdges.length === 0) {
+ continue;
+ }
+
+ for (var i = 0; i < incomingEdges.length; i++) {
+ var tmp = incomingEdges[i];
+ if (tmp.data("decorator")) {
continue
}
- var exampledata = item.example === undefined ? "" : item.example
- // Find previous execution and their variables
- //exampledata === "" &&
- if (workflowExecutions.length > 0) {
- // Look for the ID
- const found = false
- for (var key in workflowExecutions) {
- if (workflowExecutions[key].results === undefined || workflowExecutions[key].results === null) {
- continue
- }
+ if (!allkeys.includes(tmp.data("source"))) {
+ allkeys.push(tmp.data("source"));
+ }
+ }
+ }
- var foundResult = workflowExecutions[key].results.find(result => result.action.id === item.id)
- if (foundResult === undefined) {
- continue
- }
+ if (results.length === allkeys.length || iterations === maxiter) {
+ break;
+ }
- foundResult.result = foundResult.result.trim()
- foundResult.result = foundResult.result.split(" None").join(" \"None\"")
- foundResult.result = foundResult.result.split(" False").join(" false")
- foundResult.result = foundResult.result.split(" True").join(" true")
+ iterations += 1;
+ }
- var jsonvalid = true
- try {
- const tmp = String(JSON.parse(foundResult.result))
- if (!foundResult.result.includes("{") && !foundResult.result.includes("[")) {
- jsonvalid = false
- }
- } catch (e) {
- try {
- foundResult.result = foundResult.result.split("\'").join("\"")
- const tmp = String(JSON.parse(foundResult.result))
- if (!foundResult.result.includes("{") && !foundResult.result.includes("[")) {
- jsonvalid = false
- }
- } catch (e) {
- jsonvalid = false
- }
- }
+ // Remove on the end as we don't want to remove everything
+ results = results.filter((data) => data.id !== action.id);
+ results = results.filter((data) => data.type === "ACTION" || data.app_name === "Shuffle Workflow" || data.app_name === "User Input");
+ results.push({ label: "Execution Argument", type: "INTERNAL" });
+ return results;
+ };
- // Finds the FIRST json only
- if (jsonvalid) {
- exampledata = JSON.parse(foundResult.result)
- break
- }
- //else {
- // console.log("Invalid JSON: ", foundResult.result)
- //}
- }
- }
+ // BOLD name: type: required?
+ // FORM
+ // Dropdown -> static, action, local env, global env
+ // VALUE (JSON)
+ // {data.name}, {data.description}, {data.required}, {data.schema.type}
- // 1. Take
- const actionvalue = {"type": "action", "id": item.id, "name": item.label, "autocomplete": `${item.label.split(" ").join("_")}`, "example": exampledata}
- actionlist.push(actionvalue)
- }
+ //height: "100%",
+ const appApiViewStyle = {
+ display: "flex",
+ flexDirection: "column",
+ backgroundColor: "#1F2023",
+ color: "white",
+ paddingRight: 15,
+ paddingLeft: 15,
+ minHeight: "100%",
+ zIndex: 1000,
+ resize: "vertical",
+ overflow: "auto",
+ };
- }
+ var rightsidebarStyle = {
+ position: "fixed",
+ top: appBarSize + 25,
+ right: 25,
+ height: "80vh",
+ width: isMobile ? "100%" : 365,
+ minWidth: 200,
+ maxWidth: 600,
+ maxHeight: "100vh",
+ border: "1px solid rgb(91, 96, 100)",
+ zIndex: 1000,
+ borderRadius: theme.palette.borderRadius,
+ resize: "both",
+ overflow: "auto",
+ };
- //console.log("ACTIONS: ", actionlist)
- setActionlist(actionlist)
+ const setTriggerFolderWrapperMulti = (event) => {
+ const { options } = event.target;
+ var value = [];
+ for (let i = 0, l = options.length; i < l; i += 1) {
+ if (options[i].selected) {
+ value.push(options[i].value);
+ }
+ }
+
+ if (selectedTrigger.parameters === null) {
+ selectedTrigger.parameters = [[]];
+ workflow.triggers[selectedTriggerIndex].parameters = [[]];
+ }
+
+ // Max 1 folder for office for some reason. MailFolders('MAILBOX_ID') in resource
+ // Can't parse URL with multiple folders.
+ if (selectedTrigger.name === "Office365" & value !== undefined && value !== null && value.length > 1) {
+ alert.info("Max 1 folder at a time allowed for Office365")
+ console.log("VALUE: ", value)
+ value = [value[0]]
}
- // Shows nested list of nodes > their JSON lists
- const ActionlistWrapper = (props) => {
- const { data } = props;
+ // This is a dirty workaround for the static values in the go backend and datastore db
+ const fixedValue = value.join(splitter);
+ selectedTrigger.parameters[0] = {
+ value: fixedValue,
+ name: "outlookfolder",
+ };
+ workflow.triggers[selectedTriggerIndex].parameters[0] = {
+ value: fixedValue,
+ name: "outlookfolder",
+ };
- const handleMenuClose = () => {
- setShowAutocomplete(false)
+ // This resets state for some reason (:
+ setSelectedAction({});
+ setSelectedTrigger({});
+ setSelectedApp({});
+ setSelectedEdge({});
- //if (!selectedActionParameters[count].value[selectedActionParameters[count].value.length-1] === "$") {
- // setShowDropdown(false)
- //}
+ // Set value
+ setSelectedTrigger(selectedTrigger);
+ setWorkflow(workflow);
+ };
- setUpdate(Math.random())
- setMenuPosition(null)
- }
+ const setTriggerCronWrapper = (value) => {
+ if (selectedTrigger.parameters === null) {
+ selectedTrigger.parameters = [];
+ }
- const handleItemClick = (values) => {
- if (values === undefined || values === null || values.length === 0) {
+ workflow.triggers[selectedTriggerIndex].parameters[0] = {
+ value: value,
+ name: "cron",
+ };
+ setWorkflow(workflow);
+ };
+
+ const setTriggerOptionsWrapper = (value) => {
+ if (selectedTrigger.parameters === null) {
+ selectedTrigger.parameters = [];
+ }
+
+ const splitItems =
+ workflow.triggers[selectedTriggerIndex].parameters[2].value.split(",");
+ console.log(splitItems);
+ if (splitItems.includes(value)) {
+ for (var i = 0; i < splitItems.length; i++) {
+ if (splitItems[i] === value) {
+ splitItems.splice(i, 1);
+ }
+ }
+ } else {
+ splitItems.push(value);
+ }
+
+ for (var i = 0; i < splitItems.length; i++) {
+ if (splitItems[i] === "") {
+ splitItems.splice(i, 1);
+ }
+ }
+
+ workflow.triggers[selectedTriggerIndex].parameters[2].value =
+ splitItems.join(",");
+
+ console.log(splitItems);
+ setWorkflow(workflow);
+ setLocalFirstrequest(!localFirstrequest);
+ };
+
+ const setTriggerTextInformationWrapper = (value) => {
+ if (selectedTrigger.parameters === null) {
+ selectedTrigger.parameters = [];
+ }
+
+ workflow.triggers[selectedTriggerIndex].parameters[0] = {
+ value: value,
+ name: "alertinfo",
+ };
+ setWorkflow(workflow);
+ };
+
+ const setTriggerBodyWrapper = (value) => {
+ if (selectedTrigger.parameters === null) {
+ selectedTrigger.parameters = [];
+ workflow.triggers[selectedTriggerIndex].parameters[0] = {
+ value: value,
+ name: "cron",
+ };
+ }
+
+ workflow.triggers[selectedTriggerIndex].parameters[1] = {
+ value: value,
+ name: "execution_argument",
+ };
+ setWorkflow(workflow);
+ };
+
+ const AppConditionHandler = (props) => {
+ const { tmpdata, type } = props;
+ const [data] = useState(tmpdata);
+ const [multiline, setMultiline] = useState(false);
+ const [showAutocomplete, setShowAutocomplete] = React.useState(false);
+ const [actionlist, setActionlist] = React.useState([]);
+
+ if (tmpdata === undefined) {
+ return tmpdata;
+ }
+
+ if (data.variant === "") {
+ data.variant = "STATIC_VALUE";
+ }
+
+ // Set actions based on NEXT node, since it should be able to involve those two
+ if (actionlist.length === 0) {
+ // FIXME: Have previous execution values in here
+ actionlist.push({
+ type: "Execution Argument",
+ name: "Execution Argument",
+ value: "$exec",
+ highlight: "exec",
+ autocomplete: "exec",
+ example: "tmp",
+ })
+ actionlist.push({
+ type: "Shuffle DB",
+ name: "Shuffle DB",
+ value: "$shuffle_cache",
+ highlight: "shuffle_cache",
+ autocomplete: "shuffle_cache",
+ example: "tmp",
+ })
+
+ if (
+ workflow.workflow_variables !== null &&
+ workflow.workflow_variables !== undefined &&
+ workflow.workflow_variables.length > 0
+ ) {
+ for (var key in workflow.workflow_variables) {
+ const item = workflow.workflow_variables[key];
+ actionlist.push({
+ type: "workflow_variable",
+ name: item.name,
+ value: item.value,
+ id: item.id,
+ autocomplete: `${item.name.split(" ").join("_")}`,
+ example: item.value,
+ });
+ }
+ }
+
+ // FIXME: Add values from previous executions if they exist
+ if (
+ workflow.execution_variables !== null &&
+ workflow.execution_variables !== undefined &&
+ workflow.execution_variables.length > 0
+ ) {
+ for (var key in workflow.execution_variables) {
+ const item = workflow.execution_variables[key];
+ actionlist.push({
+ type: "execution_variable",
+ name: item.name,
+ value: item.value,
+ id: item.id,
+ autocomplete: `${item.name.split(" ").join("_")}`,
+ example: "",
+ });
+ }
+ }
+
+ const destAction = cy.getElementById(selectedEdge.target);
+ var parents = getParents(destAction.data());
+ if (parents.length > 1) {
+ for (var key in parents) {
+ const item = parents[key];
+ if (item.label === "Execution Argument") {
+ continue;
+ }
+
+ // 1. Take
+ const actionvalue = {
+ type: "action",
+ id: item.id,
+ name: item.label,
+ autocomplete: `${item.label.split(" ").join("_")}`,
+ example: item.example === undefined ? "" : item.example,
+ };
+ actionlist.push(actionvalue);
+ }
+ }
+
+ setActionlist(actionlist);
+ }
+
+ if (
+ data.multiline !== undefined &&
+ data.multiline !== null &&
+ data.multiline === true
+ ) {
+ setMultiline(true);
+ }
+
+ var placeholder = "Static value";
+ if (
+ data.example !== undefined &&
+ data.example !== null &&
+ data.example.length > 0
+ ) {
+ placeholder = data.example;
+ }
+
+ var datafield = (
+
+ Use "Shuffle Tools" app with "Filter List" action to handle loops
+
+ ) : null
+ }
+ onBlur={(e) => {
+ changeActionVariable(data.action_field, e.target.value);
+ setUpdate(Math.random());
+ }}
+ />
+ );
+
+ const changeActionVariable = (variable, value) => {
+ // set the name
+ data.value = value;
+ data.action_field = variable;
+
+ if (type === "source") {
+ setSourceValue(data);
+ } else if (type === "destination") {
+ setDestinationValue(data);
+ }
+ };
+
+ return (
+
+
+ {datafield}
+ {actionlist.length === 0 ? null : (
+
+
+ Autocomplete
+
+ {
+ setShowAutocomplete(false);
+
+ setUpdate(Math.random());
+ }}
+ onClick={() => {
+ setShowAutocomplete(true);
+ }}
+ open={showAutocomplete}
+ fullWidth
+ style={{
+ borderBottom: `1px solid #f85a3e`,
+ color: "white",
+ height: 50,
+ marginTop: 2,
+ }}
+ onChange={(e) => {
+ const autocomplete = e.target.value.autocomplete;
+ const newValue = autocomplete.startsWith("$")
+ ? data.value + autocomplete
+ : `${data.value}$${autocomplete}`;
+ changeActionVariable(data.action_field, newValue);
+ }}
+ >
+ {actionlist.map((data) => {
+ const icon =
+ data.type === "action" ? (
+
+ ) : data.type === "workflow_variable" ||
+ data.type === "execution_variable" ? (
+
+ ) : (
+
+ );
+
+ const handleExecArgumentHover = (inside) => {
+ var exec_text_field = document.getElementById(
+ "execution_argument_input_field"
+ );
+ if (exec_text_field !== null) {
+ if (inside) {
+ exec_text_field.style.border = "2px solid #f85a3e";
+ } else {
+ exec_text_field.style.border = "";
+ }
+ }
+
+ // Also doing arguments
+ if (
+ workflow.triggers !== undefined &&
+ workflow.triggers !== null &&
+ workflow.triggers.length > 0
+ ) {
+ for (var key in workflow.triggers) {
+ const item = workflow.triggers[key];
+
+ var node = cy.getElementById(item.id);
+ if (node.length > 0) {
+ if (inside) {
+ node.addClass("shuffle-hover-highlight");
+ } else {
+ node.removeClass("shuffle-hover-highlight");
+ }
+ }
+ }
+ }
+ };
+
+ const handleActionHover = (inside, actionId) => {
+ var node = cy.getElementById(actionId);
+ if (node.length > 0) {
+ if (inside) {
+ node.addClass("shuffle-hover-highlight");
+ } else {
+ node.removeClass("shuffle-hover-highlight");
+ }
+ }
+ };
+
+ return (
+ {
+ if (data.type === "Execution Argument") {
+ handleExecArgumentHover(true);
+ } else if (data.type === "action") {
+ handleActionHover(true, data.id);
+ }
+ }}
+ onMouseOut={() => {
+ if (data.type === "Execution Argument") {
+ handleExecArgumentHover(false);
+ } else if (data.type === "action") {
+ handleActionHover(false, data.id);
+ }
+ }}
+ >
+
+
+ {icon} {data.name}
+
+
+
+ );
+ })}
+
+
+ )}
+
+ );
+ };
+
+ const menuItemStyle = {
+ color: "white",
+ backgroundColor: inputColor,
+ };
+
+ const conditionsModal = (
+ {
+ }}
+ >
+
+ Conditions can't be used for loops [ .# ]{" "}
+
+ Learn more
+
+
+
+
+ Condition
+
+
+
+
+
+ {
+ conditionValue.configuration =
+ !conditionValue.configuration;
+ setConditionValue(conditionValue);
+ setUpdate(Math.random());
+ }}
+ >
+ {conditionValue.configuration ? "!" : "="}
+
+
+
+
+
+ {
+ setVariableAnchorEl(e.currentTarget);
+ }}
+ >
+ {conditionValue.value}
+
+
+
+
+
+
+
+ {
+ setConditionsModalOpen(false);
+ setSourceValue({});
+ setConditionValue({});
+ setDestinationValue({});
+ }}
+ color="secondary"
+ >
+ Cancel
+
+ {
+ setSelectedEdge({});
+
+ var data = {
+ condition: conditionValue,
+ source: sourceValue,
+ destination: destinationValue,
+ };
+
+ setConditionsModalOpen(false);
+ if (selectedEdge.conditions === undefined) {
+ selectedEdge.conditions = [data];
+ } else {
+ const curedgeindex = selectedEdge.conditions.findIndex(
+ (data) => data.source.id === sourceValue.id
+ );
+ if (curedgeindex < 0) {
+ selectedEdge.conditions.push(data);
+ } else {
+ selectedEdge.conditions[curedgeindex] = data;
+ }
+ }
+
+ var label = "";
+ if (selectedEdge.conditions.length === 1) {
+ label = selectedEdge.conditions.length + " condition";
+ } else if (selectedEdge.conditions.length > 1) {
+ label = selectedEdge.conditions.length + " conditions";
+ }
+
+ var currentedge = cy.getElementById(selectedEdge.id);
+ if (currentedge !== undefined && currentedge !== null) {
+ currentedge.data().label = label;
+ }
+
+ setSelectedEdge(selectedEdge);
+ workflow.branches[selectedEdgeIndex] = selectedEdge;
+ setWorkflow(workflow);
+ }}
+ color="primary"
+ >
+ Submit
+
+
+
+
+ );
+
+ const EdgeSidebar = () => {
+ const ConditionHandler = (condition, index) => {
+ const [open, setOpen] = React.useState(false);
+ const [anchorEl, setAnchorEl] = React.useState(null);
+
+ const duplicateCondition = (conditionIndex) => {
+ var newEdge = JSON.parse(
+ JSON.stringify(selectedEdge.conditions[conditionIndex])
+ );
+ const newUuid = uuidv4();
+ newEdge.condition.id = newUuid;
+ newEdge.source.id = newUuid;
+ newEdge.destination.id = newUuid;
+ selectedEdge.conditions.push(newEdge);
+
+ setUpdate(Math.random());
+ };
+
+ const deleteCondition = (conditionIndex) => {
+ console.log(selectedEdge);
+ if (selectedEdge.conditions.length === 1) {
+ selectedEdge.conditions = [];
+ } else {
+ selectedEdge.conditions.splice(conditionIndex, 1);
+ }
+
+ setSelectedEdge(selectedEdge);
+ setOpen(false);
+ setUpdate(Math.random());
+ };
+
+ const paperVariableStyle = {
+ minHeight: 75,
+ maxHeight: 75,
+ minWidth: "100%",
+ maxWidth: "100%",
+ marginTop: "5px",
+ color: "white",
+ backgroundColor: surfaceColor,
+ cursor: "pointer",
+ display: "flex",
+ };
+
+ const menuClick = (event) => {
+ console.log("MENU CLICK");
+ setOpen(!open);
+ setAnchorEl(event.currentTarget);
+ };
+
+ return (
+ {}}
+ >
+
+
+
{
+ setSourceValue(condition.source);
+ setConditionValue(condition.condition);
+ setDestinationValue(condition.destination);
+ setConditionsModalOpen(true);
+ }}
+ >
+
+ {condition.source.value}
+
+
+
{}}
+ >
+ {condition.condition.value}
+
+
+
+ {condition.destination.value}
+
+
+
+
+
+
+
+
+
+
+ );
+ };
+
+ var injectedData =
;
+
+ if (
+ selectedEdge.conditions !== undefined &&
+ selectedEdge.conditions !== null &&
+ selectedEdge.conditions.length > 0
+ ) {
+ injectedData = selectedEdge.conditions.map((condition, index) => {
+ return ConditionHandler(condition, index);
+ });
+ }
+
+ // FIXME - remove index
+ const conditionId = uuidv4();
+ return (
+
+
+
+
Conditions
+ {injectedData}
+
+
{
+ if (conditionsModalOpen) {
+ return
+ }
+
+ setSourceValue({
+ name: "source",
+ value: "",
+ variant: "STATIC_VALUE",
+ action_field: "",
+ id: conditionId,
+ });
+ setConditionValue({
+ name: "condition",
+ value: "equals",
+ id: conditionId,
+ });
+ setDestinationValue({
+ name: "destination",
+ value: "",
+ variant: "STATIC_VALUE",
+ action_field: "",
+ id: conditionId,
+ });
+
+ setConditionsModalOpen(true);
+ }}
+ fullWidth
+ >
+ New condition
+
+
+ );
+ };
+
+ // 1. GET the trigger authentication data
+ // 2. Parse the fields that are used (outlook & gmail)
+ // 3. Parse the folders that are selected
+ // 4. Start / stop
+ const EmailSidebar = () => {
+ if (Object.getOwnPropertyNames(selectedTrigger).length === 0) {
+ return null;
+ }
+
+ if (workflow.triggers[selectedTriggerIndex] === undefined) {
+ return null;
+ }
+
+ if (
+ workflow.triggers[selectedTriggerIndex].parameters === undefined ||
+ workflow.triggers[selectedTriggerIndex].parameters === null ||
+ workflow.triggers[selectedTriggerIndex].parameters.length === 0
+ ) {
+ workflow.triggers[selectedTriggerIndex].parameters = [
+ { value: "No folders selected yet", name: "outlookfolder" },
+ ];
+ selectedTrigger.parameters = [
+ { value: "No folders selected yet", name: "outlookfolder" },
+ ];
+ setWorkflow(workflow);
+ setSelectedTrigger(selectedTrigger);
+ }
+
+ const setGmailFolders = () => {
+ console.log("In set gmail folders");
+ fetch(
+ globalUrl +
+ "/api/v1/triggers/gmail/getFolders?trigger_id=" +
+ selectedTrigger.id,
+ {
+ method: "GET",
+ headers: { "content-type": "application/json" },
+ credentials: "include",
+ }
+ )
+ .then((response) => {
+ if (response.status !== 200) {
+ throw new Error("No folders :o!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (
+ responseJson !== undefined &&
+ responseJson !== null &&
+ responseJson.success !== false &&
+ responseJson.length > 0
+ ) {
+ setTriggerFolders(responseJson);
+ }
+
+ if (
+ workflow.triggers[selectedTriggerIndex].parameters.length === 0 &&
+ responseJson.length > 0
+ ) {
+ workflow.triggers[selectedTriggerIndex].parameters = [
+ {
+ value: responseJson[0].displayName,
+ name: "outlookfolder",
+ id: responseJson[0].id,
+ },
+ ];
+ selectedTrigger.parameters = [
+ {
+ value: responseJson[0].displayName,
+ name: "outlookfolder",
+ id: responseJson[0].id,
+ },
+ ];
+ setWorkflow(workflow);
+ setSelectedTrigger(selectedTrigger);
+ }
+ })
+ .catch((error) => {
+ console.log(error.toString());
+ });
+ };
+
+ const setOutlookFolders = () => {
+ fetch(
+ globalUrl +
+ "/api/v1/triggers/outlook/getFolders?trigger_id=" +
+ selectedTrigger.id,
+ {
+ method: "GET",
+ headers: { "content-type": "application/json" },
+ credentials: "include",
+ }
+ )
+ .then((response) => {
+ if (response.status !== 200) {
+ throw new Error("No folders :o!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (
+ responseJson !== null &&
+ responseJson.success !== false &&
+ responseJson.length > 0
+ ) {
+ setTriggerFolders(responseJson);
+ }
+
+ if (
+ workflow.triggers[selectedTriggerIndex].parameters.length === 0 &&
+ responseJson.length > 0
+ ) {
+ workflow.triggers[selectedTriggerIndex].parameters = [
+ {
+ value: responseJson[0].displayName,
+ name: "outlookfolder",
+ id: responseJson[0].id,
+ },
+ ];
+ selectedTrigger.parameters = [
+ {
+ value: responseJson[0].displayName,
+ name: "outlookfolder",
+ id: responseJson[0].id,
+ },
+ ];
+ setWorkflow(workflow);
+ setSelectedTrigger(selectedTrigger);
+ }
+ })
+ .catch((error) => {
+ console.log(error.toString());
+ });
+ };
+
+ const getTriggerAuth = () => {
+ fetch(globalUrl + "/api/v1/triggers/outlook/" + selectedTrigger.id, {
+ method: "GET",
+ headers: { "content-type": "application/json" },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ throw new Error("No trigger info :o!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ setTriggerAuthentication(responseJson);
+ })
+ .catch((error) => {
+ console.log(error.toString());
+ });
+ };
+
+ // Getting the triggers and the folders if they exist
+ // This is horrible hahah
+ if (localFirstrequest) {
+ getTriggerAuth();
+ setOutlookFolders();
+ setGmailFolders();
+ setLocalFirstrequest(false);
+ }
+
+ const gmailButton =
+ selectedTrigger.name !== "Gmail" ? null : (
+ {
+ console.log("HOST: ", window.location.host);
+ console.log("HOST: ", window.location);
+ const redirectUri = isCloud
+ ? window.location.host === "localhost:3002"
+ ? "http%3A%2F%2Flocalhost:5002%2Fapi%2Fv1%2Ftriggers%2Fgmail%2Fregister"
+ : "https%3A%2F%2Fshuffler.io%2Fapi%2Fv1%2Ftriggers%2Fgmail%2Fregister"
+ : window.location.protocol === "http:" ?
+ `http%3A%2F%2F${window.location.host}%2Fapi%2Fv1%2Ftriggers%2Fgmail%2Fregister`
+ :
+ `https%3A%2F%2F${window.location.host}%2Fapi%2Fv1%2Ftriggers%2Fgmail%2Fregister`
+
+ const client_id =
+ "253565968129-c0a35knic7q1pdk6i6qk9gdkvr07ci49.apps.googleusercontent.com";
+ const username = userdata.id;
+ console.log(redirectUri);
+ console.log("USER: ", username, userdata);
+
+ const branch = workflow.branches.find(
+ (branch) => branch.source_id === selectedTrigger.id
+ );
+ if (branch === undefined || branch === null) {
+ alert.error(
+ "No startnode connected to node. Connect it to an action."
+ );
+ return;
+ }
+
+ console.log("BRANCH: ", branch);
+ const startnode = branch.destination_id;
+ const scopes = "https://www.googleapis.com/auth/gmail.readonly";
+ const url = `https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent&client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${scopes}&state=workflow_id%3D${props.match.params.key}%26trigger_id%3D${selectedTrigger.id}%26username%3D${username}%26type%3Dgmail%26start%3d${startnode}`;
+ console.log("URL: ", url);
+
+ var newwin = window.open(url, "", "width=800,height=600");
+
+ // Check whether we got a callback somewhere
+ var id = setInterval(function () {
+ fetch(
+ globalUrl + "/api/v1/triggers/gmail/" + selectedTrigger.id,
+ {
+ method: "GET",
+ headers: { "content-type": "application/json" },
+ credentials: "include",
+ }
+ )
+ .then((response) => {
+ if (response.status !== 200) {
+ throw new Error("No trigger info :o!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ //console.log("RESPONSE: ");
+ setTriggerAuthentication(responseJson);
+ clearInterval(id);
+ newwin.close();
+ setGmailFolders();
+ })
+ .catch((error) => {
+ console.log(error.toString());
+ });
+ }, 2500);
+
+ saveWorkflow(workflow);
+ }}
+ >
+
+
+ Sign in with Google
+
+
+ );
+
+ const outlookButton =
+ selectedTrigger.name !== "Office365" ? null : (
+ {
+ console.log(window.location)
+ const redirectUri = isCloud
+ ? window.location.host === "localhost:3002"
+ ? "http%3A%2F%2Flocalhost:5002%2Fapi%2Fv1%2Ftriggers%2Foutlook%2Fregister"
+ : "https%3A%2F%2Fshuffler.io%2Fapi%2Fv1%2Ftriggers%2Foutlook%2Fregister"
+ : window.location.protocol === "http:" ?
+ `http%3A%2F%2F${window.location.host}%2Fapi%2Fv1%2Ftriggers%2Foutlook%2Fregister`
+ :
+ `https%3A%2F%2F${window.location.host}%2Fapi%2Fv1%2Ftriggers%2Foutlook%2Fregister`
+
+ //const client_id = "fd55c175-aa30-4fa6-b303-09a29fb3f750"
+ const client_id = "bb4bff85-0d0b-4f5d-8a69-3cee8029b11a";
+
+ const username = userdata.id;
+ console.log(redirectUri);
+ console.log("USER: ", username, userdata);
+
+ const branch = workflow.branches.find(
+ (branch) => branch.source_id === selectedTrigger.id
+ );
+ if (branch === undefined || branch === null) {
+ alert.error(
+ "No startnode connected to node. Connect it to an action."
+ );
+ return;
+ }
+
+ console.log("BRANCH: ", branch);
+ const startnode = branch.destination_id;
+ const url = `https://login.microsoftonline.com/common/oauth2/authorize?access_type=offline&client_id=${client_id}&redirect_uri=${redirectUri}&resource=https%3A%2F%2Fgraph.microsoft.com&response_type=code&scope=Mail.Read+User.Read+https%3A%2F%2Foutlook.office.com%2Fmail.read&prompt=login&state=workflow_id%3D${props.match.params.key}%26trigger_id%3D${selectedTrigger.id}%26username%3D${username}%26type%3Doutlook%26start%3d${startnode}`;
+ //const url = `https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent&client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${scopes}&state=workflow_id%3D${props.match.params.key}%26trigger_id%3D${selectedTrigger.id}%26username%3D${username}%26type%3Dgmail%26start%3d${startnode}`
+
+ //const scopes = "https://www.googleapis.com/auth/gmail.readonly"
+ //const url = `https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent&client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${scopes}&state=workflow_id%3D${props.match.params.key}%26trigger_id%3D${selectedTrigger.id}%26username%3D${username}%26type%3Dgmail%26start%3d${startnode}`
+
+ console.log("URL: ", url);
+
+ var newwin = window.open(url, "", "width=800,height=600");
+
+ // Check whether we got a callback somewhere
+ var id = setInterval(function () {
+ fetch(
+ globalUrl + "/api/v1/triggers/outlook/" + selectedTrigger.id,
+ {
+ method: "GET",
+ headers: { "content-type": "application/json" },
+ credentials: "include",
+ }
+ )
+ .then((response) => {
+ if (response.status !== 200) {
+ throw new Error("No trigger info :o!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ setTriggerAuthentication(responseJson);
+ clearInterval(id);
+ newwin.close();
+ setOutlookFolders();
+ })
+ .catch((error) => {
+ console.log(error.toString());
+ });
+ }, 2500);
+
+ saveWorkflow(workflow);
+ }}
+ >
+
+
+ Sign in with Microsoft
+
+
+ );
+
+ // FIXME - set everything in here to multifolder etc
+ var triggerInfo = "SET UP BUT NO TYPE :)";
+ if (Object.getOwnPropertyNames(triggerAuthentication).length > 0) {
+ // Should get the folders if they don't already exist
+
+ if (
+ triggerAuthentication.type === "outlook" ||
+ triggerAuthentication.type === "gmail"
+ ) {
+ triggerInfo = (
+
+ {selectedTrigger.status === "running" ? null : (
+
+
+ {outlookButton}
+ {gmailButton}
+
+ If you have trouble using this trigger, please contact us to get access
+
+
+ )}
+
+ {triggerFolders === undefined || triggerFolders === null ? null : (
+
+
+
+
+ Select {triggerAuthentication.type === "gmail" ? "labels" : "folders"} (CTRL+click)
+
+
+ {
+ setTriggerFolderWrapperMulti(e)
+ }}
+ fullWidth
+ input={ }
+ key={selectedTrigger}
+ >
+ {triggerFolders.map((folder) => {
+ var folderItem = (
+
+ {folder.displayName}
+
+ );
+
+ if (folder.childFolderCount > 0) {
+ // Here to handle subfolders sometime later
+ folderItem = (
+
+ {folder.displayName}
+
+ );
+ }
+
+ return folderItem;
+ })}
+
+
+ )}
+
+ );
+ } else if (triggerAuthentication.type === "gmail") {
+ console.log("AUTH: ", triggerAuthentication);
+ triggerInfo = "SPECIAL GMAIL";
+ }
+ }
+
+ // Check
+ const argumentView =
+ Object.getOwnPropertyNames(triggerAuthentication).length > 0 ? (
+ {triggerInfo}
+ ) : (
+
+
+ {outlookButton}
+ {gmailButton}
+
+ );
+
+ return (
+
+
+
+
Name
+
+
+
+ Environment:
+
+
+
+ {argumentView}
+
+
+
+
+ {
+ startMailSub(selectedTrigger, selectedTriggerIndex);
+ }}
+ color="primary"
+ >
+ Start
+
+ {
+ stopMailSub(selectedTrigger, selectedTriggerIndex);
+ }}
+ color="primary"
+ >
+ Stop
+
+
+
+
+
+ );
+ };
+
+ const SubflowSidebar = () => {
+ const [menuPosition, setMenuPosition] = useState(null);
+ const [showDropdown, setShowDropdown] = React.useState(false);
+ const [actionlist, setActionlist] = React.useState([]);
+
+ if (actionlist.length === 0) {
+ // FIXME: Have previous execution values in here
+ actionlist.push({
+ type: "Execution Argument",
+ name: "Execution Argument",
+ value: "$exec",
+ highlight: "exec",
+ autocomplete: "exec",
+ example: "hello",
+ })
+ actionlist.push({
+ type: "Shuffle Database",
+ name: "Shuffle Database",
+ value: "$shuffle_cache",
+ highlight: "shuffle_db",
+ autocomplete: "shuffle_cache",
+ example: "hello",
+ })
+ if (
+ workflow.workflow_variables !== null &&
+ workflow.workflow_variables !== undefined &&
+ workflow.workflow_variables.length > 0
+ ) {
+ for (var key in workflow.workflow_variables) {
+ const item = workflow.workflow_variables[key];
+ actionlist.push({
+ type: "workflow_variable",
+ name: item.name,
+ value: item.value,
+ id: item.id,
+ autocomplete: `${item.name.split(" ").join("_")}`,
+ example: item.value,
+ });
+ }
+ }
+
+ // FIXME: Add values from previous executions if they exist
+ if (
+ workflow.execution_variables !== null &&
+ workflow.execution_variables !== undefined &&
+ workflow.execution_variables.length > 0
+ ) {
+ for (var key in workflow.execution_variables) {
+ const item = workflow.execution_variables[key];
+ actionlist.push({
+ type: "execution_variable",
+ name: item.name,
+ value: item.value,
+ id: item.id,
+ autocomplete: `${item.name.split(" ").join("_")}`,
+ example: "",
+ });
+ }
+ }
+
+ var parents = getParents(selectedTrigger);
+ if (parents.length > 1) {
+ for (var key in parents) {
+ const item = parents[key];
+ if (item.label === "Execution Argument") {
+ continue;
+ }
+
+ var exampledata = item.example === undefined ? "" : item.example;
+ // Find previous execution and their variables
+ if (workflowExecutions.length > 0) {
+ // Look for the ID
+ for (var key in workflowExecutions) {
+ if (
+ workflowExecutions[key].results === undefined ||
+ workflowExecutions[key].results === null
+ ) {
+ continue;
+ }
+
+ var foundResult = workflowExecutions[key].results.find(
+ (result) => result.action.id === item.id
+ );
+ if (foundResult === undefined) {
+ continue;
+ }
+
+ const validated = validateJson(foundResult.result)
+ if (validated.valid) {
+ exampledata = validateJson.result
+ break
+ }
+ }
+ }
+
+ // 1. Take
+ const actionvalue = {
+ type: "action",
+ id: item.id,
+ name: item.label,
+ autocomplete: `${item.label.split(" ").join("_")}`,
+ example: exampledata,
+ }
+ actionlist.push(actionvalue);
+ }
+ }
+
+ setActionlist(actionlist);
+ }
+
+ // Shows nested list of nodes > their JSON lists
+ const ActionlistWrapper = (props) => {
+ const { data } = props;
+
+ const handleMenuClose = () => {
+ setUpdate(Math.random());
+ setMenuPosition(null);
+ };
+
+ const handleItemClick = (values) => {
+ console.log("VALUES: ", values)
+ if (values === undefined || values === null || values.length === 0) {
+ return;
+ }
+
+
+ /*
+ workflow.triggers[selectedTriggerIndex].parameters[1].value
+ .trim()
+ .endsWith("$")
+ ? values[0].autocomplete
+ : "$" + values[0].autocomplete;
+
+ for (var key in values) {
+ if (key === 0 || values[key].autocomplete.length === 0) {
+ continue;
+ }
+
+ toComplete += values[key].autocomplete
+ }
+ */
+
+ console.log("SELECTED TRIGGER: ", selectedTrigger)
+ if (selectedTrigger.name === "Shuffle Workflow") {
+ const toComplete = selectedTrigger.parameters[1].value + "$" + values[0].autocomplete
+ selectedTrigger.parameters[1].value = toComplete
+ setSelectedTrigger(selectedTrigger)
+ }
+
+ setUpdate(Math.random());
+ setShowDropdown(false);
+ setMenuPosition(null);
+ };
+
+ const iconStyle = {
+ marginRight: 15,
+ };
+
+ return (
+ {
+ handleMenuClose();
+ }}
+ open={!!menuPosition}
+ style={{
+ border: `2px solid #f85a3e`,
+ color: "white",
+ marginTop: 2,
+ }}
+ >
+ {actionlist.map((innerdata) => {
+ const icon =
+ innerdata.type === "action" ? (
+
+ ) : innerdata.type === "workflow_variable" ||
+ innerdata.type === "execution_variable" ? (
+
+ ) : (
+
+ );
+
+ const handleExecArgumentHover = (inside) => {
+ var exec_text_field = document.getElementById(
+ "execution_argument_input_field"
+ );
+ if (exec_text_field !== null) {
+ if (inside) {
+ exec_text_field.style.border = "2px solid #f85a3e";
+ } else {
+ exec_text_field.style.border = "";
+ }
+ }
+
+ // Also doing arguments
+ if (
+ workflow.triggers !== undefined &&
+ workflow.triggers !== null &&
+ workflow.triggers.length > 0
+ ) {
+ for (var key in workflow.triggers) {
+ const item = workflow.triggers[key];
+
+ if (cy !== undefined) {
+ var node = cy.getElementById(item.id);
+ if (node.length > 0) {
+ if (inside) {
+ node.addClass("shuffle-hover-highlight");
+ } else {
+ node.removeClass("shuffle-hover-highlight");
+ }
+ }
+ }
+ }
+ }
+ }
+
+ const handleActionHover = (inside, actionId) => {
+ if (cy !== undefined) {
+ var node = cy.getElementById(actionId);
+ if (node.length > 0) {
+ if (inside) {
+ node.addClass("shuffle-hover-highlight");
+ } else {
+ node.removeClass("shuffle-hover-highlight");
+ }
+ }
+ }
+ };
+
+ const handleMouseover = () => {
+ if (innerdata.type === "Execution Argument") {
+ handleExecArgumentHover(true);
+ } else if (innerdata.type === "action") {
+ handleActionHover(true, innerdata.id);
+ }
+ };
+
+ const handleMouseOut = () => {
+ if (innerdata.type === "Execution Argument") {
+ handleExecArgumentHover(false);
+ } else if (innerdata.type === "action") {
+ handleActionHover(false, innerdata.id);
+ }
+ };
+
+ var parsedPaths = [];
+ if (typeof innerdata.example === "object") {
+ parsedPaths = GetParsedPaths(innerdata.example, "");
+ }
+
+ return parsedPaths.length > 0 ? (
+
+ {icon} {innerdata.name}
+
+ }
+ parentMenuOpen={!!menuPosition}
+ style={{
+ backgroundColor: theme.palette.inputColor,
+ color: "white",
+ minWidth: 250,
+ }}
+ onClick={() => {
+ handleItemClick([innerdata]);
+ }}
+ >
+ {parsedPaths.map((pathdata, index) => {
+ // FIXME: Should be recursive in here
+ const icon =
+ pathdata.type === "value" ? (
+
+ ) : pathdata.type === "list" ? (
+
+ ) : (
+
+ )
+
+ return (
+
{}}
+ onClick={() => {
+ handleItemClick([innerdata, pathdata]);
+ }}
+ >
+
+
+ {icon} {pathdata.name}
+
+
+
+ );
+ })}
+
+ ) : (
+
handleMouseover()}
+ onMouseOut={() => {
+ handleMouseOut();
+ }}
+ onClick={() => {
+ handleItemClick([innerdata]);
+ }}
+ >
+
+
+ {icon} {innerdata.name}
+
+
+
+ );
+ })}
+
+ );
+ };
+
+ if (Object.getOwnPropertyNames(selectedTrigger).length > 0) {
+ if (workflow.triggers[selectedTriggerIndex] === undefined) {
+ return null;
+ }
+
+ if (
+ workflow.triggers[selectedTriggerIndex].parameters === undefined ||
+ workflow.triggers[selectedTriggerIndex].parameters === null ||
+ workflow.triggers[selectedTriggerIndex].parameters.length === 0
+ ) {
+ workflow.triggers[selectedTriggerIndex].parameters = [];
+ workflow.triggers[selectedTriggerIndex].parameters[0] = {
+ name: "workflow",
+ value: "",
+ };
+ workflow.triggers[selectedTriggerIndex].parameters[1] = {
+ name: "argument",
+ value: "",
+ };
+ workflow.triggers[selectedTriggerIndex].parameters[2] = {
+ name: "user_apikey",
+ value: "",
+ };
+ workflow.triggers[selectedTriggerIndex].parameters[3] = {
+ name: "startnode",
+ value: "",
+ };
+ workflow.triggers[selectedTriggerIndex].parameters[4] = {
+ name: "check_result",
+ value: "false",
+ };
+
+ /*
+ // API-key has been replaced by auth key for the execution.
+ // Parents can now automatically execute children without auth from a user, as long as the subflow in question is owned by the same org and the subflow is actually referencing it during checkin.
+ console.log("SETTINGS: ", userSettings);
+ if (
+ userSettings !== undefined &&
+ userSettings !== null &&
+ userSettings.apikey !== null &&
+ userSettings.apikey !== undefined &&
+ userSettings.apikey.length > 0
+ ) {
+ workflow.triggers[selectedTriggerIndex].parameters[2] = {
+ name: "user_apikey",
+ value: userSettings.apikey,
+ };
+ }
+ */
+ }
+
+ const handleSubflowStartnodeSelection = (e) => {
+ setSubworkflowStartnode(e.target.value);
+
+ if (e.target.value === null || e.target.value === undefined) {
return
}
- var toComplete = workflow.triggers[selectedTriggerIndex].parameters[1].value.trim().endsWith("$") ? values[0].autocomplete : "$"+values[0].autocomplete
- for (var key in values) {
- if (key == 0 || values[key].autocomplete.length === 0) {
- continue
- }
+ const branchId = uuidv4();
+ const newbranch = {
+ source_id: workflow.triggers[selectedTriggerIndex].id,
+ destination_id: e.target.value.id,
+ source: workflow.triggers[selectedTriggerIndex].id,
+ target: e.target.value.id,
+ has_errors: false,
+ id: branchId,
+ _id: branchId,
+ label: "Subflow",
+ decorator: true,
+ };
- toComplete += values[key].autocomplete
- }
-
- // Handles the fields under OpenAPI body to be parsed.
- if (data.name.startsWith("${") && data.name.endsWith("}")) {
- console.log("INSIDE VALUE REPLACE: ", data.name, toComplete)
- // PARAM FIX - Gonna use the ID field, even though it's a hack
- const paramcheck = selectedAction.parameters.find(param => param.name === "body")
- if (paramcheck !== undefined) {
- if (paramcheck["value_replace"] === undefined || paramcheck["value_replace"] === null) {
- paramcheck["value_replace"] = [{
- "key": data.name,
- "value": toComplete,
- }]
-
- } else {
- const subparamindex = paramcheck["value_replace"].findIndex(param => param.key === data.name)
- if (subparamindex === -1) {
- paramcheck["value_replace"].push({
- "key": data.name,
- "value": toComplete,
- })
- } else {
- paramcheck["value_replace"][subparamindex]["value"] += toComplete
+ if (workflow.visual_branches !== undefined) {
+ if (workflow.visual_branches === null) {
+ workflow.visual_branches = [newbranch];
+ } else if (workflow.visual_branches.length === 0) {
+ workflow.visual_branches.push(newbranch);
+ } else {
+ const foundIndex = workflow.visual_branches.findIndex(
+ (branch) => branch.source_id === newbranch.source_id
+ );
+ if (foundIndex !== -1) {
+ const currentEdge = cy.getElementById(
+ workflow.visual_branches[foundIndex].id
+ );
+ if (
+ currentEdge !== undefined &&
+ currentEdge !== null
+ ) {
+ currentEdge.remove();
}
}
-
- workflow.triggers[selectedTriggerIndex].parameters[1]["value_replace"] = paramcheck
- setSelectedAction(selectedAction)
- setUpdate(Math.random())
- setShowDropdown(false)
- setMenuPosition(null)
- return
+ workflow.visual_branches.splice(foundIndex, 1);
+ workflow.visual_branches.push(newbranch);
}
}
- //workflow.triggers[selectedTriggerIndex].parameters[1].value = selectedActionParameters[count].value
- setSelectedAction(selectedAction)
- setUpdate(Math.random())
+ if (workflow.id === subworkflow.id) {
+ const cybranch = {
+ group: "edges",
+ source: newbranch.source_id,
+ target: newbranch.destination_id,
+ id: branchId,
+ data: newbranch,
+ };
- setShowDropdown(false)
- setMenuPosition(null)
+ cy.add(cybranch);
+ }
+
+ console.log("Value to be set: ", e.target.value);
+ try {
+ workflow.triggers[
+ selectedTriggerIndex
+ ].parameters[3].value = e.target.value.id;
+ } catch {
+ workflow.triggers[selectedTriggerIndex].parameters[3] =
+ {
+ name: "startnode",
+ value: e.target.value.id,
+ };
+ }
+
+ setWorkflow(workflow);
}
- const iconStyle = {
- marginRight: 15,
+ const handleWorkflowSelectionUpdate = (e) => {
+ setUpdate(Math.random());
+
+ if (e.target.value === undefined || e.target.value === null || e.target.value.id === undefined) {
+ return null
+ }
+
+ workflow.triggers[
+ selectedTriggerIndex
+ ].parameters[0].value = e.target.value.id;
+ setSubworkflow(e.target.value);
+
+ // Sets the startnode
+ if (e.target.value.id !== workflow.id) {
+ console.log("WORKFLOW: ", e.target.value);
+
+ const startnode = e.target.value.actions.find(
+ (action) => action.id === e.target.value.start
+ );
+ if (startnode !== undefined && startnode !== null) {
+ console.log("STARTNODE: ", startnode);
+ setSubworkflowStartnode(startnode);
+
+ try {
+ workflow.triggers[
+ selectedTriggerIndex
+ ].parameters[3].value = startnode.id;
+ } catch {
+ workflow.triggers[
+ selectedTriggerIndex
+ ].parameters[3] = {
+ name: "startnode",
+ value: startnode.id,
+ };
+ }
+
+ setWorkflow(workflow);
+ }
+ console.log("STARTNODE: ", startnode);
+ } else {
+ console.log("WORKFLOW: ", workflow);
+ }
+
+ setWorkflow(workflow);
}
- return (
-
{
- handleMenuClose()
- }}
- open={!!menuPosition}
- style={{
- border: `2px solid #f85a3e`,
- color: "white",
- marginTop: 2,
- }}
- >
- {actionlist.map(innerdata => {
- const icon = innerdata.type === "action" ? : innerdata.type === "workflow_variable" || innerdata.type === "execution_variable" ? :
-
- const handleExecArgumentHover = (inside) => {
- var exec_text_field = document.getElementById("execution_argument_input_field")
- if (exec_text_field !== null) {
- if (inside) {
- exec_text_field.style.border = "2px solid #f85a3e"
- } else {
- exec_text_field.style.border = ""
- }
- }
-
- // Also doing arguments
- if (workflow.triggers !== undefined && workflow.triggers !== null && workflow.triggers.length > 0) {
- for (var key in workflow.triggers) {
- const item = workflow.triggers[key]
-
- if (cy !== undefined) {
- var node = cy.getElementById(item.id)
- if (node.length > 0) {
- if (inside) {
- node.addClass('shuffle-hover-highlight')
- } else {
- node.removeClass('shuffle-hover-highlight')
- }
- }
- }
- }
- }
- }
-
- const handleActionHover = (inside, actionId) => {
- if (cy !== undefined) {
- var node = cy.getElementById(actionId)
- if (node.length > 0) {
- if (inside) {
- node.addClass('shuffle-hover-highlight')
- } else {
- node.removeClass('shuffle-hover-highlight')
- }
- }
- }
- }
-
- const handleMouseover = () => {
- if (innerdata.type === "Execution Argument") {
- handleExecArgumentHover(true)
- } else if (innerdata.type === "action") {
- handleActionHover(true, innerdata.id)
- }
- }
-
- const handleMouseOut = () => {
- if (innerdata.type === "Execution Argument") {
- handleExecArgumentHover(false)
- } else if (innerdata.type === "action") {
- handleActionHover(false, innerdata.id)
- }
- }
-
- var parsedPaths = []
- if (typeof(innerdata.example) === "object") {
- parsedPaths = GetParsedPaths(innerdata.example, "")
- }
-
- return (
- parsedPaths.length > 0 ?
-
- {icon} {innerdata.name}
-
- }
- parentMenuOpen={!!menuPosition}
- style={{backgroundColor: theme.palette.inputColor, color: "white", minWidth: 250,}}
- onClick={() => {
- handleItemClick([innerdata])
+ return (
+
+
+
+
+
+
Name
+
- {parsedPaths.map((pathdata, index) => {
- // FIXME: Should be recursive in here
- const icon = pathdata.type === "value" ? : pathdata.type === "list" ? :
- return (
- {}}
- onClick={() => {
- handleItemClick([innerdata, pathdata])
- }}
- >
-
-
- {icon} {pathdata.name}
-
-
-
- )
-
- })}
-
- :
- handleMouseover()} onMouseOut={() => {handleMouseOut()}}
- onClick={() => {
- handleItemClick([innerdata])
+ InputProps={{
+ style: {
+ color: "white",
+ marginLeft: "5px",
+ maxWidth: "95%",
+ height: 50,
+ fontSize: "1em",
+ },
}}
- >
-
-
- {icon} {innerdata.name}
-
-
-
-
- )
- })}
-
- )
- }
-
- if (Object.getOwnPropertyNames(selectedTrigger).length > 0) {
- if (workflow.triggers[selectedTriggerIndex] === undefined) {
- return null
- }
-
- if (workflow.triggers[selectedTriggerIndex].parameters === undefined || workflow.triggers[selectedTriggerIndex].parameters === null || workflow.triggers[selectedTriggerIndex].parameters.length === 0) {
- workflow.triggers[selectedTriggerIndex].parameters = []
- workflow.triggers[selectedTriggerIndex].parameters[0] = {"name": "workflow", "value": ""}
- workflow.triggers[selectedTriggerIndex].parameters[1] = {"name": "argument", "value": ""}
- workflow.triggers[selectedTriggerIndex].parameters[2] = {"name": "user_apikey", "value": ""}
- workflow.triggers[selectedTriggerIndex].parameters[3] = {"name": "startnode", "value": ""}
- workflow.triggers[selectedTriggerIndex].parameters[4] = {"name": "check_result", "value": "false"}
-
- console.log("SETTINGS: ", userSettings)
- if (userSettings !== undefined && userSettings !== null && userSettings.apikey !== null && userSettings.apikey !== undefined && userSettings.apikey.length > 0) {
- workflow.triggers[selectedTriggerIndex].parameters[2] = {"name": "user_apikey", "value": userSettings.apikey}
- }
- }
-
- return(
-
-
-
-
- Name
-
-
-
{
- const newvalue = workflow.triggers[selectedTriggerIndex].parameters[4] === undefined || workflow.triggers[selectedTriggerIndex].parameters[4].value === "false" ? "true" : "false"
- workflow.triggers[selectedTriggerIndex].parameters[4] = {
- "name": "check_result",
- "value": newvalue,
- }
-
- setWorkflow(workflow)
- setUpdate(Math.random())
- }}
+ fullWidth
color="primary"
- value="Wait for results"
+ placeholder={selectedTrigger.label}
+ onChange={selectedTriggerChange}
/>
- }
- style={{marginTop: 10}}
- label={Wait for results
}
- />
-
-
+
-
Parameters
-
-
-
- Select a workflow to execute
-
-
- {workflows === undefined || workflows === null || workflows.length === 0 ? null :
-
{
- setUpdate(Math.random())
- workflow.triggers[selectedTriggerIndex].parameters[0].value = e.target.value.id
- setSubworkflow(e.target.value)
-
- // Sets the startnode
- if (e.target.value.id !== workflow.id) {
- console.log("WORKFLOW: ", e.target.value)
-
- const startnode = e.target.value.actions.find(action => action.id === e.target.value.start)
- if (startnode !== undefined && startnode !== null) {
- console.log("STARTNODE: ", startnode)
- setSubworkflowStartnode(startnode)
-
- try {
- workflow.triggers[selectedTriggerIndex].parameters[3].value = startnode.id
- } catch {
- workflow.triggers[selectedTriggerIndex].parameters[3] = {
- "name": "startnode",
- "value": startnode.id,
- }
+
+
+
+ Delay
+ {
+ if (isNaN(event.target.value)) {
+ console.log("NAN: ", event.target.value)
+ return
}
- setWorkflow(workflow)
- }
- console.log("STARTNODE: ", startnode)
- } else {
- console.log("WORKFLOW: ", workflow)
- }
+ const parsedNumber = parseInt(event.target.value)
+ if (parsedNumber > 86400) {
+ console.log("Max number is 1 day (86400)")
+ return
+ }
- setWorkflow(workflow)
+ selectedTrigger.execution_delay = parseInt(event.target.value)
+ setSelectedTrigger(selectedTrigger)
+ }}
+ />
+
+
+
+
+
+ {
+ const newvalue =
+ workflow.triggers[selectedTriggerIndex].parameters[4] ===
+ undefined ||
+ workflow.triggers[selectedTriggerIndex].parameters[4]
+ .value === "false"
+ ? "true"
+ : "false";
+ workflow.triggers[selectedTriggerIndex].parameters[4] = {
+ name: "check_result",
+ value: newvalue,
+ };
+
+ setWorkflow(workflow);
+ setUpdate(Math.random());
+ }}
+ color="primary"
+ value="Wait for results"
+ />
+ }
+ style={{ marginTop: 10 }}
+ label={Wait for results
}
+ />
+
+
+
+
+
- {workflows.map((data, index) => {
- if (data.id === workflow.id) {
- //return null
- data = workflow
- }
+
+ Select a workflow to execute
+
+
+ {workflow.triggers[selectedTriggerIndex].parameters[0].value
+ .length === 0 ? null : workflow.triggers[selectedTriggerIndex]
+ .parameters[0].value === props.match.params.key ? null : (
+
+ )}
+
+ {workflows === undefined ||
+ workflows === null ||
+ workflows.length === 0 ? null : (
+
option.id === value.id}
+ getOptionLabel={(option) => {
+ if (
+ option === undefined ||
+ option === null ||
+ option.name === undefined ||
+ option.name === null
+ ) {
+ return "No Workflow Selected";
+ }
- return (
-
+ const newname = (
+ option.name.charAt(0).toUpperCase() + option.name.substring(1)
+ ).replaceAll("_", " ");
+ return newname;
+ }}
+ options={workflows}
+ fullWidth
+ style={{
+ backgroundColor: theme.palette.inputColor,
+ height: 50,
+ borderRadius: theme.palette.borderRadius,
+ }}
+ onChange={(event, newValue) => {
+ handleWorkflowSelectionUpdate({ target: { value: newValue} })
+ }}
+ renderOption={(data, index) => {
+ if (data.id === workflow.id) {
+ data = workflow;
+ }
+
+ //key={index}
+ return (
+
+ {data.image !== undefined && data.image !== null && data.image.length > 0 ?
+
+ : null}
+
+ Choose {data.name}
+
+
+ } placement="bottom">
+
{data.name}
- )
- })}
-
- }
- {workflow.triggers[selectedTriggerIndex].parameters[0].value.length === 0 ? null :
- workflow.triggers[selectedTriggerIndex].parameters[0].value === props.match.params.key ? null :
- Explore selected workflow
- }
-
- {subworkflow === undefined || subworkflow === null || subworkflow.id === undefined || subworkflow.actions === null || subworkflow.actions === undefined || subworkflow.actions.length === 0 ? null :
-
-
-
-
- Select the Startnode
-
-
- {
- setSubworkflowStartnode(e.target.value)
- //console.log("WF: ", workflow)
-
- const branchId = uuidv4()
- const newbranch = {
- "source_id": workflow.triggers[selectedTriggerIndex].id,
- "destination_id": e.target.value.id,
- "source": workflow.triggers[selectedTriggerIndex].id,
- "target": e.target.value.id,
- "has_errors": false,
- "id": branchId,
- "_id": branchId,
- "label": "Subflow",
- "decorator": true,
- }
-
- if (workflow.visual_branches !== undefined) {
- if (workflow.visual_branches === null) {
- workflow.visual_branches = [newbranch]
- } else if (workflow.visual_branches.length === 0) {
- workflow.visual_branches.push(newbranch)
- } else {
- const foundIndex = workflow.visual_branches.findIndex(branch => branch.source_id === newbranch.source_id)
- if (foundIndex !== -1) {
- const currentEdge = cy.getElementById(workflow.visual_branches[foundIndex].id)
- if (currentEdge !== undefined && currentEdge !== null) {
- currentEdge.remove()
- }
- }
-
- workflow.visual_branches.splice(foundIndex, 1)
- workflow.visual_branches.push(newbranch)
- }
- }
-
- if (workflow.id === subworkflow.id) {
- const cybranch = {
- group: "edges",
- source: newbranch.source_id,
- target: newbranch.destination_id,
- id: branchId,
- data: newbranch,
- }
-
- cy.add(cybranch)
- }
-
- console.log("Value to be set: ", e.target.value)
- try {
- workflow.triggers[selectedTriggerIndex].parameters[3].value = e.target.value.id
- } catch {
- workflow.triggers[selectedTriggerIndex].parameters[3] = {
- "name": "startnode",
- "value": e.target.value.id,
- }
- }
-
- setWorkflow(workflow)
- //setUpdate(Math.random())
- }}
- style={{backgroundColor: inputColor, color: "white", height: 50}}
- >
- {subworkflow.actions.map((action, index) => {
- //console.log(action)
- const isParent = getParents(selectedTrigger).find(parent => parent.id === action.id)
- return (
-
- {action.label}
-
- )
- })}
-
-
- }
-
-
-
- Execution Argument
-
-
-
-
- {
- setMenuPosition({
- top: event.pageY+10,
- left: event.pageX+10,
- })
- //setShowDropdownNumber(3)
- setShowDropdown(true)
- setShowAutocomplete(true)
- }}/>
-
-
+
)
- }}
- rows="6"
- multiline
- fullWidth
- color="primary"
- placeholder="Some execution data"
- defaultValue={workflow.triggers[selectedTriggerIndex].parameters[1].value}
- onBlur={(e) => {
- console.log("DATA: ", e.target.value)
- workflow.triggers[selectedTriggerIndex].parameters[1].value = e.target.value
- setWorkflow(workflow)
- }}
- />
- {showDropdown ?
-
- : null}
-
- {
- workflow.triggers[selectedTriggerIndex].parameters[2].value = e.target.value
- setWorkflow(workflow)
- }}
- />
-
-
-
- )
- }
+ }}
+ renderInput={(params) => {
+ return (
+
+ );
+ }}
+ />
+ )}
- return null
- }
+ {subworkflow === undefined ||
+ subworkflow === null ||
+ subworkflow.id === undefined ||
+ subworkflow.actions === null ||
+ subworkflow.actions === undefined ||
+ subworkflow.actions.length === 0 ? null : (
+
+
+
+ Select the Startnode
+
+
+ option.id === value.id}
+ getOptionLabel={(option) => {
+ if (
+ option === undefined ||
+ option === null ||
+ option.label === undefined ||
+ option.label === null
+ ) {
+ if (option.length === 36) {
- const WebhookSidebar = () => {
- if (Object.getOwnPropertyNames(selectedTrigger).length > 0) {
+ }
+
+ return "TMP";
+ }
+
+ const newname = (
+ option.label.charAt(0).toUpperCase() + option.label.substring(1)
+ ).replaceAll("_", " ");
+ return newname;
+ }}
+ options={subworkflow.actions}
+ fullWidth
+ style={{
+ backgroundColor: theme.palette.inputColor,
+ height: 50,
+ borderRadius: theme.palette.borderRadius,
+ }}
+ onChange={(event, newValue) => {
+ handleSubflowStartnodeSelection({ target: { value: newValue} })
+ }}
+ renderOption={(action) => {
+ const isParent = getParents(selectedTrigger).find(
+ (parent) => parent.id === action.id
+ )
+
+ return (
+ {
+ if (subworkflow.id === workflow.id) {
+ handleActionHover(true, action.id)
+ }
+ }}
+ onMouseOut={() => {
+ if (subworkflow.id === workflow.id) {
+ handleActionHover(false, action.id)
+ }
+ }}
+ disabled={isCloud && isParent}
+ style={{
+ backgroundColor: theme.palette.inputColor,
+ color: isParent ? "red" : "white",
+ }}
+ value={action}
+ >
+ {action.label}
+
+ );
+ }}
+ renderInput={(params) => {
+ return (
+
+ );
+ }}
+ />
+
+ )}
+
+
+ Execution Argument
+
+
+
+
+ {
+ setMenuPosition({
+ top: event.pageY + 10,
+ left: event.pageX + 10,
+ });
+ //setShowDropdownNumber(3)
+ setShowDropdown(true);
+ }}
+ />
+
+
+ ),
+ }}
+ rows="6"
+ multiline
+ fullWidth
+ color="primary"
+ placeholder="Some execution data"
+ defaultValue={
+ workflow.triggers[selectedTriggerIndex].parameters[1].value
+ }
+ onBlur={(e) => {
+ console.log("DATA: ", e.target.value);
+ workflow.triggers[selectedTriggerIndex].parameters[1].value =
+ e.target.value;
+ setWorkflow(workflow);
+ }}
+ />
+ {showDropdown ? (
+
+ ) : null}
+ {/*
+
+ {
+ workflow.triggers[selectedTriggerIndex].parameters[2].value =
+ e.target.value;
+ setWorkflow(workflow);
+ }}
+ />
+ */}
+
+
+
+ );
+ }
+
+ return null;
+ };
+
+ const CommentSidebar = () => {
+ if (Object.getOwnPropertyNames(selectedComment).length > 0) {
+ /*
if (workflow.triggers[selectedTriggerIndex] === undefined) {
return null
}
@@ -6702,2060 +8842,3998 @@ const AngularWorkflow = (props) => {
workflow.triggers[selectedTriggerIndex].parameters[2] = {"name": "auth_headers", "value": ""}
setWorkflow(workflow)
} else {
- // Always update
- const newUrl = referenceUrl+"webhook_"+selectedTrigger.id
- console.log("Validating webhook url: ", newUrl)
- if (newUrl !== workflow.triggers[selectedTriggerIndex].parameters[0].value) {
- console.log("Url is wrong - updating")
- workflow.triggers[selectedTriggerIndex].parameters[0].value = newUrl
- setWorkflow(workflow)
+ if (selectedTrigger.environment !== "cloud") {
+ const newUrl = referenceUrl+"webhook_"+selectedTrigger.id
+ if (newUrl !== workflow.triggers[selectedTriggerIndex].parameters[0].value) {
+ console.log("Url is wrong - should update. This functionality is temporarily disabled.")
+ //workflow.triggers[selectedTriggerIndex].parameters[0].value = newUrl
+ //setWorkflow(workflow)
+ }
}
}
const trigger_header_auth = workflow.triggers[selectedTriggerIndex].parameters.length > 2 ? workflow.triggers[selectedTriggerIndex].parameters[2].value : ""
- //const cronValue = "*/15 * * * *"
+ */
- return(
-
-
-
-
- Name
-
-
-
-
- Environment
-
-
+
+
+ Name
+ {
+ selectedComment.label = event.target.value;
+ setSelectedComment(selectedComment);
+ }}
+ />
+
+
+
Height
+
{
+ selectedComment.height = event.target.value;
+ setSelectedComment(selectedComment);
+ }}
+ />
+
+
+
Width
+
{
+ selectedComment.width = event.target.value;
+ setSelectedComment(selectedComment);
+ }}
+ />
+
+
+
+
+
Background
+
{
+ selectedComment.backgroundcolor = event.target.value;
+ setSelectedComment(selectedComment);
+ }}
+ />
+
+
+
Text Color
+
{
+ selectedComment.color = event.target.value;
+ setSelectedComment(selectedComment);
+ }}
+ />
+
+
+
+ );
+ }
- }
- }}
- fullWidth
- onChange={(e) => {
- selectedTrigger.environment = e.target.value
- setSelectedTrigger(selectedTrigger)
- if (e.target.value === "cloud") {
- var tmpvalue = workflow.triggers[selectedTriggerIndex].parameters[0].value.split("/")
- const urlpath = tmpvalue.slice(3, tmpvalue.length)
- const newurl = "https://shuffler.io/"+urlpath.join("/")
- workflow.triggers[selectedTriggerIndex].parameters[0].value = newurl
- } else {
- var tmpvalue = workflow.triggers[selectedTriggerIndex].parameters[0].value.split("/")
- const urlpath = tmpvalue.slice(3, tmpvalue.length)
- const newurl = window.location.origin+"/"+urlpath.join("/")
- workflow.triggers[selectedTriggerIndex].parameters[0].value = newurl
- }
+ return null;
+ };
- setWorkflow(workflow)
- setUpdate(Math.random())
- }}
- style={{backgroundColor: inputColor, color: "white", height: 50}}
- >
- {triggerEnvironments.map(data => {
- if (data.archived) {
- return null
- }
-
- return (
-
- {data}
-
- )
- })}
-
-
-
-
-
-
Parameters
-
-
{
- var copyText = document.getElementById("webhook_uri_field")
- if (copyText !== undefined && copyText !== null) {
- console.log("NAVIGATOR: ", navigator)
- const clipboard = navigator.clipboard
- if (clipboard === undefined) {
- alert.error("Can only copy over HTTPS (port 3443)")
- return
- }
+ const WebhookSidebar = () => {
+ if (Object.getOwnPropertyNames(selectedTrigger).length > 0) {
+ if (workflow.triggers[selectedTriggerIndex] === undefined) {
+ return null;
+ }
- navigator.clipboard.writeText(copyText.value)
- copyText.select()
- copyText.setSelectionRange(0, 99999) /* For mobile devices */
+ if (
+ workflow.triggers[selectedTriggerIndex].parameters === undefined ||
+ workflow.triggers[selectedTriggerIndex].parameters === null ||
+ workflow.triggers[selectedTriggerIndex].parameters.length === 0
+ ) {
+ workflow.triggers[selectedTriggerIndex].parameters = [];
+ workflow.triggers[selectedTriggerIndex].parameters[0] = {
+ name: "url",
+ value: referenceUrl + "webhook_" + selectedTrigger.id,
+ };
+ workflow.triggers[selectedTriggerIndex].parameters[1] = {
+ name: "tmp",
+ value: "webhook_" + selectedTrigger.id,
+ };
+ workflow.triggers[selectedTriggerIndex].parameters[2] = {
+ name: "auth_headers",
+ value: "",
+ };
+ workflow.triggers[selectedTriggerIndex].parameters[3] = {
+ name: "custom_response_body",
+ value: "",
+ };
+ setWorkflow(workflow);
+ } else {
+ // Always update
+ const newUrl = referenceUrl + "webhook_" + selectedTrigger.id;
+ //console.log("Validating webhook url: ", newUrl);
+ if (selectedTrigger.environment !== "cloud") {
+ if (newUrl !== workflow.triggers[selectedTriggerIndex].parameters[0].value) {
+ console.log("Url is wrong. NOT updating because of hybrid.");
+ //workflow.triggers[selectedTriggerIndex].parameters[0].value = newUrl;
+ //setWorkflow(workflow);
+ }
+ }
+ }
- /* Copy the text inside the text field */
- document.execCommand("copy")
- alert.success("Copied Webhook URL")
- } else {
- console.log("Couldn't find webhook URI field: ", copyText)
- }
- }}
- helperText={workflow.triggers[selectedTriggerIndex].parameters[0].value !== undefined && workflow.triggers[selectedTriggerIndex].parameters[0].value !== null && (workflow.triggers[selectedTriggerIndex].parameters[0].value.includes("localhost") || workflow.triggers[selectedTriggerIndex].parameters[0].value.includes("127.0.0.1"))?
-
- PS: This does NOT work with localhost. Use your local IP instead.
-
- : null
- }
- InputProps={{
- style:{
- color: "white",
- height: 50,
- marginLeft: "5px",
- maxWidth: "95%",
- fontSize: "1em",
- },
- }}
- fullWidth
- disabled
- defaultValue={workflow.triggers[selectedTriggerIndex].parameters[0].value}
- color="primary"
- placeholder="defaultValue"
- onBlur={(e) => {
- setTriggerCronWrapper(e.target.value)
- }}
- />
-
-
-
- Required headers
-
-
-
-
-
-
- {
- newWebhook(workflow.triggers[selectedTriggerIndex])
- }} color="primary">
- Start
-
- {
- deleteWebhook(selectedTrigger, selectedTriggerIndex)
- }} color="primary">
- Stop
-
-
-
-
-
- )
- }
+ return (
+
+
+
+
Name
+
+
+ Environment
+ {
+ selectedTrigger.environment = e.target.value;
+ if (e.target.value === "cloud") {
+ const tmpvalue = workflow.triggers[selectedTriggerIndex].parameters[0].value.split("/");
+ const urlpath = tmpvalue.slice(3, tmpvalue.length);
+ const newurl = "https://shuffler.io/" + urlpath.join("/");
+ workflow.triggers[selectedTriggerIndex].parameters[0].value = newurl;
+ } else {
+ const tmpvalue = workflow.triggers[selectedTriggerIndex].parameters[0].value.split("/");
+ const urlpath = tmpvalue.slice(3, tmpvalue.length);
+ const newurl = window.location.origin + "/" + urlpath.join("/");
+ workflow.triggers[selectedTriggerIndex].parameters[0].value = newurl;
+ }
- return null
- }
-
- //const getTriggerAuth = (trigger_id) => {
- // fetch(globalUrl+"/api/v1/triggers/"+trigger_id, {
- // method: "GET",
- // headers: {"content-type": "application/json"},
- // credentials: "include",
- // })
- // .then((response) => response.json())
- // .then((responseJson) => {
- // if (responseJson.success) {
- // console.log("SUCCESS")
- // console.log(responseJson)
- // } else {
- // console.log("FAIL")
- // }
- // })
- // .catch(error => {
- // console.log(error.toString())
- // })
- //}
+ console.log("New value: ", workflow.triggers[selectedTriggerIndex].parameters[0])
+ selectedTrigger.parameters[0] = workflow.triggers[selectedTriggerIndex].parameters[0]
+ setSelectedTrigger(selectedTrigger);
+ setWorkflow(workflow);
+ setUpdate(Math.random());
+ }}
+ style={{
+ backgroundColor: inputColor,
+ color: "white",
+ height: 50,
+ }}
+ >
+ {triggerEnvironments.map((data) => {
+ if (data.archived) {
+ return null;
+ }
- const stopMailSub = (trigger, triggerindex) => {
- // DELETE
- if (trigger.id === undefined) {
- return
- }
+ return (
+
+ {data}
+
+ );
+ })}
+
+
+
+
+
+
Parameters
+
+
{
+ var copyText = document.getElementById("webhook_uri_field");
+ if (copyText !== undefined && copyText !== null) {
+ console.log("NAVIGATOR: ", navigator);
+ const clipboard = navigator.clipboard;
+ if (clipboard === undefined) {
+ alert.error("Can only copy over HTTPS (port 3443)");
+ return;
+ }
- alert.info("Stopping mail trigger")
- const requesttype = triggerAuthentication.type
- fetch(`${globalUrl}/api/v1/workflows/${props.match.params.key}/${requesttype}/${trigger.id}`, {
- method: 'DELETE',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- },
- credentials: "include",
- })
- .then((response) => {
- if (response.status !== 200) {
- throw new Error("Status not 200 for stream results :O!")
- }
+ navigator.clipboard.writeText(copyText.value);
+ copyText.select();
+ copyText.setSelectionRange(
+ 0,
+ 99999
+ ); /* For mobile devices */
- return response.json()
- })
- .then((responseJson) => {
- if (responseJson.success) {
- alert.success("Successfully stopped trigger")
- // Set the status
- workflow.triggers[triggerindex].status = "stopped"
- trigger.status = "stopped"
- setWorkflow(workflow)
- setSelectedTrigger(trigger)
- saveWorkflow(workflow)
- } else {
- alert.error("Failed stopping trigger: "+responseJson.reason)
- }
- })
- .catch(error => {
- alert.error(error.toString())
- })
- }
+ /* Copy the text inside the text field */
+ document.execCommand("copy");
+ alert.success("Copied Webhook URL");
+ } else {
+ console.log("Couldn't find webhook URI field: ", copyText);
+ }
+ }}
+ helperText={
+ workflow.triggers[selectedTriggerIndex].parameters[0].value !== undefined &&
+ workflow.triggers[selectedTriggerIndex].parameters[0].value !== null &&
+ (workflow.triggers[
+ selectedTriggerIndex
+ ].parameters[0].value.includes("localhost") ||
+ workflow.triggers[
+ selectedTriggerIndex
+ ].parameters[0].value.includes("127.0.0.1")) ? (
+
+ PS: This does NOT work with localhost. Use your local IP
+ instead.
+
+ ) : null
+ }
+ InputProps={{
+ style: {
+ color: "white",
+ height: 50,
+ marginLeft: "5px",
+ maxWidth: "95%",
+ fontSize: "1em",
+ },
+ }}
+ fullWidth
+ disabled
+ value={
+ workflow.triggers[selectedTriggerIndex].parameters[0].value
+ }
+ color="primary"
+ placeholder="defaultValue"
+ onBlur={(e) => {
+ setTriggerCronWrapper(e.target.value);
+ }}
+ />
+
+ {
+ newWebhook(workflow.triggers[selectedTriggerIndex]);
+ }}
+ color="primary"
+ >
+ Start
+
+ {
+ deleteWebhook(selectedTrigger, selectedTriggerIndex);
+ }}
+ color="primary"
+ >
+ Stop
+
+
+
+
+
+
+ Authentication headers
+
+
+
+
+
+
+
+ Custom Response
+
+
+
+
+
+
+
+ );
+ }
+
+ return null;
+ };
+
+ const stopMailSub = (trigger, triggerindex) => {
+ // DELETE
+ if (trigger.id === undefined) {
+ return;
+ }
+
+ alert.info("Stopping mail trigger");
+ const requesttype = triggerAuthentication.type;
+ fetch(
+ `${globalUrl}/api/v1/workflows/${props.match.params.key}/${requesttype}/${trigger.id}`,
+ {
+ method: "DELETE",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ }
+ )
+ .then((response) => {
+ if (response.status !== 200) {
+ throw new Error("Status not 200 for stream results :O!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (responseJson.success) {
+ alert.success("Successfully stopped trigger");
+ // Set the status
+ workflow.triggers[triggerindex].status = "stopped";
+ trigger.status = "stopped";
+ setWorkflow(workflow);
+ setSelectedTrigger(trigger);
+ saveWorkflow(workflow);
+ } else {
+ alert.error("Failed stopping trigger: " + responseJson.reason);
+ }
+ })
+ .catch((error) => {
+ alert.error(error.toString());
+ });
+ };
+
+ const startMailSub = (trigger, triggerindex) => {
+ var folders = [];
+
+ if (triggerFolders === null || triggerFolders === undefined) {
+ return null;
+ }
+
+ const splitItem =
+ workflow.triggers[selectedTriggerIndex].parameters[0].value.split(
+ splitter
+ );
+ for (var key in splitItem) {
+ const item = splitItem[key];
+ const curfolder = triggerFolders.find((a) => a.displayName === item);
+ if (curfolder === undefined) {
+ alert.error("Something went wrong with folder selection: " + item);
+ return;
+ }
+
+ folders.push(curfolder.id);
+ }
+
+ const data = {
+ name: trigger.name,
+ folders: folders,
+ id: trigger.id,
+ };
+
+ const requesttype = triggerAuthentication.type;
+ alert.info(
+ "Creating " + requesttype + " subscription with name " + trigger.name
+ );
+
+ fetch(
+ globalUrl +
+ "/api/v1/workflows/" +
+ props.match.params.key +
+ "/" +
+ requesttype,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ body: JSON.stringify(data),
+ credentials: "include",
+ }
+ )
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for stream results :O!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (!responseJson.success) {
+ alert.error("Failed to start trigger: " + responseJson.reason);
+ } else {
+ alert.success(
+ "Successfully started folder subscription trigger. Test it by sending yoursend an email"
+ );
+
+ workflow.triggers[triggerindex].status = "running";
+ trigger.status = "running";
+ setWorkflow(workflow);
+ setSelectedTrigger(trigger);
+ saveWorkflow(workflow);
+ }
+ })
+ .catch((error) => {
+ alert.error(error.toString());
+ });
+ };
+
+ const newWebhook = (trigger) => {
+ const hookname = trigger.label;
+ if (hookname.length === 0) {
+ alert.error("Missing name");
+ return;
+ }
+
+ if (trigger.id.length !== 36) {
+ alert.error("Missing id");
+ return;
+ }
+
+ // Check the node it's connected to
+ var startNode = workflow.start;
+ const branch = workflow.branches.find(
+ (branch) => branch.source_id === trigger.id
+ );
+ if (
+ branch === undefined &&
+ (workflow.start === undefined ||
+ workflow.start === null ||
+ workflow.start.length === 0)
+ ) {
+ alert.error("No webhook node defined");
+ }
+
+ alert.info("Starting webhook");
+ if (branch !== undefined) {
+ startNode = branch.destination_id;
+ }
+
+ const param = trigger.parameters.find(
+ (param) => param.name === "auth_headers"
+ );
+ var auth = "";
+ if (param !== undefined && param !== null) {
+ auth = param.value;
+ }
+
+ const customRespParam = trigger.parameters.find(
+ (param) => param.name === "custom_response_body"
+ )
+ var custom_response = "";
+ if (customRespParam !== undefined && customRespParam !== null) {
+ custom_response = customRespParam.value;
+ }
+
+ console.log("TRIG: ", trigger);
+ const data = {
+ name: hookname,
+ type: "webhook",
+ id: trigger.id,
+ workflow: workflow.id,
+ start: startNode,
+ environment: trigger.environment,
+ auth: auth,
+ custom_response: custom_response,
+ };
+
+ fetch(globalUrl + "/api/v1/hooks/new", {
+ method: "POST",
+ headers: { "content-type": "application/json" },
+ body: JSON.stringify(data),
+ credentials: "include",
+ })
+ .then((response) => response.json())
+ .then((responseJson) => {
+ if (responseJson.success) {
+ // Set the status
+ alert.success("Successfully started webhook");
+ trigger.status = "running";
+ setSelectedTrigger(trigger);
+ workflow.triggers[selectedTriggerIndex].status = "running";
+ setWorkflow(workflow);
+ saveWorkflow(workflow);
+ } else {
+ alert.error("Failed starting webhook: " + responseJson.reason);
+ }
+ })
+ .catch((error) => {
+ console.log(error.toString());
+ });
+ };
+
+ const deleteWebhook = (trigger, triggerindex) => {
+ if (trigger.id === undefined) {
+ return;
+ }
+
+ fetch(globalUrl + "/api/v1/hooks/" + trigger.id + "/delete", {
+ method: "DELETE",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for stream results :O!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (workflow.triggers[triggerindex] !== undefined) {
+ workflow.triggers[triggerindex].status = "stopped";
+ }
+
+ if (responseJson.success) {
+ // Set the status
+ saveWorkflow(workflow);
+ } else {
+ if (responseJson.reason !== undefined) {
+ alert.error("Failed stopping webhook: " + responseJson.reason);
+ }
+ }
+
+ trigger.status = "stopped";
+ setWorkflow(workflow);
+ setSelectedTrigger(trigger);
+ })
+ .catch((error) => {
+ alert.error(error.toString());
+ });
+ };
+
+ const UserinputSidebar = () => {
+ if (
+ Object.getOwnPropertyNames(selectedTrigger).length > 0 &&
+ workflow.triggers[selectedTriggerIndex] !== undefined
+ ) {
+ if (
+ workflow.triggers[selectedTriggerIndex].parameters === undefined ||
+ workflow.triggers[selectedTriggerIndex].parameters === null ||
+ workflow.triggers[selectedTriggerIndex].parameters.length === 0
+ ) {
+ workflow.triggers[selectedTriggerIndex].parameters = [];
+ workflow.triggers[selectedTriggerIndex].parameters[0] = {
+ name: "alertinfo",
+ value: "hello this is an alert",
+ };
+
+ // boolean,
+ workflow.triggers[selectedTriggerIndex].parameters[1] = {
+ name: "options",
+ value: "boolean",
+ };
+
+ // email,sms,app ...
+ workflow.triggers[selectedTriggerIndex].parameters[2] = {
+ name: "type",
+ value: "email",
+ };
+
+ workflow.triggers[selectedTriggerIndex].parameters[3] = {
+ name: "email",
+ value: "test@test.com",
+ };
+ workflow.triggers[selectedTriggerIndex].parameters[4] = {
+ name: "sms",
+ value: "0000000",
+ };
+ workflow.triggers[selectedTriggerIndex].parameters[5] = {
+ name: "subflow",
+ value: "",
+ };
+
+ setWorkflow(workflow);
+ }
+
+ return (
+
+
+
+
Name
+
+
+
+ Environment:
+
+
+
+
+
Parameters
+
+
{
+ setTriggerTextInformationWrapper(e.target.value);
+ }}
+ />
+
+
+
+ Contact options
+
+
+
+ {
+ setTriggerOptionsWrapper("subflow");
+ }}
+ color="primary"
+ value="subflow"
+ disabled
+ />
+ }
+ label={Subflow
}
+ />
+ {
+ setTriggerOptionsWrapper("email");
+ }}
+ color="primary"
+ value="email"
+ />
+ }
+ label={Email
}
+ />
+ {
+ setTriggerOptionsWrapper("sms");
+ }}
+ color="primary"
+ value="sms"
+ />
+ }
+ label={SMS
}
+ />
+
+ {workflow.triggers[selectedTriggerIndex].parameters[2] !==
+ undefined &&
+ workflow.triggers[
+ selectedTriggerIndex
+ ].parameters[2].value.includes("email") ? (
+ {
+ workflow.triggers[selectedTriggerIndex].parameters[3].value =
+ event.target.value;
+ setWorkflow(workflow);
+ setUpdate(Math.random());
+ }}
+ />
+ ) : null}
+ {workflow.triggers[selectedTriggerIndex].parameters[2] !==
+ undefined &&
+ workflow.triggers[
+ selectedTriggerIndex
+ ].parameters[2].value.includes("sms") ? (
+ {
+ workflow.triggers[selectedTriggerIndex].parameters[4].value =
+ event.target.value;
+ setWorkflow(workflow);
+ setUpdate(Math.random());
+ }}
+ />
+ ) : null}
+ {workflow.triggers[selectedTriggerIndex].parameters[2] !==
+ undefined &&
+ workflow.triggers[
+ selectedTriggerIndex
+ ].parameters[2].value.includes("subflow") ? (
+ {
+ workflow.triggers[selectedTriggerIndex].parameters[5].value =
+ event.target.value;
+ setWorkflow(workflow);
+ setUpdate(Math.random());
+ }}
+ />
+ ) : null}
+
+
+ );
+ }
+
+ return null;
+ };
+
+ const ScheduleSidebar = () => {
+ if (
+ Object.getOwnPropertyNames(selectedTrigger).length > 0 &&
+ workflow.triggers[selectedTriggerIndex] !== undefined
+ ) {
+ if (
+ workflow.triggers[selectedTriggerIndex].parameters === undefined ||
+ workflow.triggers[selectedTriggerIndex].parameters === null ||
+ workflow.triggers[selectedTriggerIndex].parameters.length === 0
+ ) {
+ workflow.triggers[selectedTriggerIndex].parameters = [];
+ workflow.triggers[selectedTriggerIndex].parameters[0] = {
+ name: "cron",
+ value: isCloud ? "*/25 * * * *" : "60",
+ };
+ workflow.triggers[selectedTriggerIndex].parameters[1] = {
+ name: "execution_argument",
+ value: '{"example": {"json": "is cool"}}',
+ };
+ setWorkflow(workflow);
+ }
+
+ return (
+
+
+
+
Name
+
+
+ Environment
+ {
+ selectedTrigger.environment = e.target.value;
+ setSelectedTrigger(selectedTrigger);
+ if (e.target.value === "cloud") {
+ console.log("Set cloud config");
+ workflow.triggers[selectedTriggerIndex].parameters[0].value =
+ "*/25 * * * *";
+ } else {
+ console.log("Set cloud config");
+
+ workflow.triggers[selectedTriggerIndex].parameters[0].value =
+ "60";
+ }
+
+ setWorkflow(workflow);
+ setUpdate(Math.random());
+ }}
+ style={{
+ backgroundColor: inputColor,
+ color: "white",
+ height: 50,
+ }}
+ >
+ {triggerEnvironments.map((data) => {
+ if (data.archived) {
+ return null;
+ }
+
+ return (
+
+ {data}
+
+ );
+ })}
+
+
+
+
+
+
Parameters
+
+
{
+ setTriggerCronWrapper(e.target.value);
+ }}
+ />
+
+
+
+ Execution argument:
+
+
+ {
+ setTriggerBodyWrapper(e.target.value);
+ }}
+ />
+
+
+ {
+ submitSchedule(selectedTrigger, selectedTriggerIndex);
+ }}
+ color="primary"
+ >
+ Start
+
+ {
+ stopSchedule(selectedTrigger, selectedTriggerIndex);
+ }}
+ color="primary"
+ >
+ Stop
+
+
+
+
+
+ );
+ }
+
+ return null;
+ };
+
+ const cytoscapeViewWidths = isMobile ? 50 : 850;
+ const bottomBarStyle = {
+ position: "fixed",
+ right: isMobile ? 20 : 20,
+ bottom: isMobile ? undefined : 0,
+ top: isMobile ? appBarSize + 55 : undefined,
+ left: isMobile ? undefined : leftBarSize,
+ minWidth: cytoscapeViewWidths,
+ maxWidth: cytoscapeViewWidths,
+ marginLeft: 20,
+ marginBottom: 20,
+ zIndex: 10,
+ };
+
+ const topBarStyle = {
+ position: "fixed",
+ right: 0,
+ left: isMobile ? 20 : leftBarSize + 20,
+ top: isMobile ? 30 : appBarSize + 20,
+ };
+
+ const TopCytoscapeBar = (props) => {
+ if (workflow.public === true) {
return null
}
- const splitItem = workflow.triggers[selectedTriggerIndex].parameters[0].value.split(splitter)
- for (var key in splitItem) {
- const item = splitItem[key]
- const curfolder = triggerFolders.find(a => a.displayName === item)
- if (curfolder === undefined) {
- alert.error("Something went wrong with folder selection: "+item)
- return
- }
+ return (
+
+
+
+
+
+
+ Workflows
+
+
+ {workflow.name}
+
+
+
+ );
+ };
- folders.push(curfolder.id)
- }
+ const WorkflowMenu = () => {
+ const [newAnchor, setNewAnchor] = React.useState(null);
+ const [showShuffleMenu, setShowShuffleMenu] = React.useState(false);
- const data = {
- "name": trigger.name,
- "folders": folders,
- "id": trigger.id,
- }
+ return (
+
+
-
-
-
- {
- setShowShuffleMenu(!showShuffleMenu)
- setNewAnchor(event.currentTarget)
- }}>
-
-
-
-
-
- )
- }
-
- const handleHistoryUndo = () => {
- console.log("history: ", history, "index: ", historyIndex)
- var item = history[historyIndex-1]
- if (historyIndex === 0) {
- item = history[historyIndex]
- }
-
- if (item === undefined) {
- console.log("Couldn't find the action you're looking for")
- return
- }
-
- console.log("HANDLE: ", item)
- if (item.type === "node" && item.action === "removed") {
- // Re-add the node
-
- cy.add({
- group: 'nodes',
- data: item.data,
- position: item.data.position,
- })
- } else if (item.action === "added") {
- console.log("Should remove item!")
- const currentitem = cy.getElementById(item.data.id)
- if (currentitem !== undefined && currentitem !== null) {
- currentitem.remove()
- }
- }
-
- if (historyIndex > 0) {
- setHistoryIndex(historyIndex-1)
- }
- }
-
- const BottomCytoscapeBar = () => {
- const [anchorEl, setAnchorEl] = React.useState(null)
-
- const boxSize = 100
- const executionButton = executionRunning ?
-
-
- {
- abortExecution()
- }}>
-
-
-
-
- :
-
-
- {
- executeWorkflow(executionText, workflow.start, lastSaved)
- }}>
-
-
-
-
-
- return(
-
- {executionButton}
-
- {workflow.public ? null :
-
- Skip Notifications
}
+ control={
+
{
+ workflow.configuration.skip_notifications =
+ !workflow.configuration.skip_notifications;
+ setWorkflow(workflow);
+ setUpdate(
+ "skip_notifications" +
+ workflow.configuration.skip_notification
+ ? "true"
+ : "false"
+ );
+ }}
+ />
+ }
+ />
+ Exit on Error }
+ control={
+
{
+ workflow.configuration.exit_on_error =
+ !workflow.configuration.exit_on_error;
+ setWorkflow(workflow);
+ setUpdate(
+ "exit_on_error_" + workflow.configuration.exit_on_error
+ ? "true"
+ : "false"
+ );
+ }}
+ />
+ }
+ />
+ Start from top }
+ control={
+
{
+ workflow.configuration.start_from_top =
+ !workflow.configuration.start_from_top;
+ setWorkflow(workflow);
+ setUpdate(
+ "start_from_top_" + workflow.configuration.start_from_top
+ ? "true"
+ : "false"
+ );
+ }}
+ />
+ }
+ />
+
+
+
+
+ {
+ setShowShuffleMenu(!showShuffleMenu);
+ setNewAnchor(event.currentTarget);
+ }}
+ >
+
+
+
+
+ {isMobile ?
+
+
+ {
+ console.log("Show apps!")
+ setLeftBarSize(leftViewOpen ? 0 : 60)
+ setLeftViewOpen(!leftViewOpen)
}}
- color="secondary"
- placeholder={"Execution Argument"}
- defaultValue={executionText}
- onBlur={(e) => {
- setExecutionText(e.target.value)
- }}
- />
-
- }
-
-
- saveWorkflow()}>
- {savingState === 2 ? : savingState === 1 ? : }
-
-
-
- {workflow.public ?
-
-
- {
- //setExecutionModalOpen(true)
- //getWorkflowExecution(props.match.params.key, "")
- //data = sanitizeWorkflow(workflow)
- const data = workflow
- let exportFileDefaultName = data.name+'.json';
-
- let dataStr = JSON.stringify(data)
- let dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr)
- let linkElement = document.createElement('a')
- linkElement.setAttribute('href', dataUri)
- linkElement.setAttribute('download', exportFileDefaultName)
- linkElement.click()
- }}>
-
+ >
+
-
-
- : null}
-
-
- cy.fit(null, 50)}>
-
-
-
-
- {
- handleHistoryUndo(history)
- }}>
-
-
-
-
-
-
- {
- removeNode()
- }}>
-
-
-
-
-
-
- {
- setExecutionModalOpen(true)
- getWorkflowExecution(props.match.params.key, "")
- }}>
-
-
-
-
- {workflow.configuration !== null && workflow.configuration !== undefined && workflow.configuration.exit_on_error !== undefined ?
: null}
-
+ : null}
- )
- }
+ );
+ };
- const RightSideBar = (props) => {
- const {workflow, setWorkflow, setAction, setSelectedAction, setUpdate, appActionArguments, selectedApp, workflowExecutions, setSelectedResult, selectedAction, setSelectedApp, setSelectedTrigger, setSelectedEdge, setCurrentView, cy, setAuthenticationModalOpen,setVariablesModalOpen, setCodeModalOpen, selectedNameChange, rightsidebarStyle, showEnvironment, selectedActionEnvironment, environments, setNewSelectedAction, appApiViewStyle, globalUrl, setSelectedActionEnvironment, requiresAuthentication, hideExtraTypes, scrollConfig, setScrollConfig } = props
-
- if (!rightSideBarOpen) {
- return null
- }
-
- if (Object.getOwnPropertyNames(selectedAction).length > 0) {
- if (Object.getOwnPropertyNames(selectedAction).length === 0) {
- return null
- }
-
- return (
-
- )
-
- } else if (Object.getOwnPropertyNames(selectedTrigger).length > 0) {
- if (selectedTrigger.trigger_type === "SCHEDULE") {
- //console.log("SCHEDULE")
- return(
-
- )
- } else if (selectedTrigger.trigger_type === "WEBHOOK") {
- return(
-
- )
- } else if (selectedTrigger.trigger_type === "SUBFLOW") {
- return(
-
- )
- } else if (selectedTrigger.trigger_type === "EMAIL") {
- return(
-
- )
- } else if (selectedTrigger.trigger_type === "USERINPUT") {
- return(
-
- )
- } else if (selectedTrigger.trigger_type === undefined) {
- return null
- } else {
- console.log("Unable to handle invalid trigger type "+selectedTrigger.trigger_type)
- return null
- }
- } else if (Object.getOwnPropertyNames(selectedEdge).length > 0) {
- return(
-
- )
- }
-
- return(
- null
- )
- }
-
- // This can execute a workflow with firestore. Used for test, as datastore is old and stuff
- // Too much work to move everything over alone, so won't touch it for now
- //
-
- const executionPaperStyle = {
- minWidth: "95%",
- maxWidth: "95%",
- marginTop: "5px",
- color: "white",
- marginBottom: 10,
- padding: 5,
- backgroundColor: surfaceColor,
- cursor: "pointer",
- display: "flex",
- minHeight: 40,
- maxHeight: 40,
- }
-
- const parsedExecutionArgument = () => {
- var showResult = executionData.execution_argument.trim()
- const validate = validateJson(showResult)
-
-
- if (validate.valid) {
- if (typeof(validate.result) === "string") {
- try {
- validate.result = JSON.parse(validate.result)
- } catch(e) {
- console.log("Error: ", e)
- validate.valid = false
- }
- }
-
- return (
-