From fb3f45d00833f6f9fa4b9b1f478e7ac603f8dc32 Mon Sep 17 00:00:00 2001 From: Isoporhode Date: Sun, 14 Nov 2021 16:45:18 +0100 Subject: [PATCH] Ran prettier on frontend --- frontend/README.md | 1 + frontend/package.json | 1 + frontend/public/index.html | 14 +- frontend/src/App.jsx | 753 +- frontend/src/__test__/appdata.js | 1349 +- frontend/src/__test__/environmentdata.js | 7 +- frontend/src/__test__/scheduledata.js | 51 +- frontend/src/__test__/webhookdata.js | 24 +- frontend/src/__test__/workflowdata.js | 170 +- frontend/src/charts.js | 136 +- frontend/src/components/AlertPopup.js | 33 +- frontend/src/components/AlertTemplate.js | 64 +- frontend/src/components/ConfigureWorkflow.jsx | 959 +- frontend/src/components/Dropzone.js | 44 +- frontend/src/components/FooterNew.js | 74 +- frontend/src/components/Header.js | 1180 +- frontend/src/components/LoginPopup.js | 283 +- frontend/src/components/NestedMenu.jsx | 134 +- frontend/src/components/Oauth2Auth.jsx | 867 +- frontend/src/components/OrgHeader.jsx | 1053 +- frontend/src/components/ParsedAction.jsx | 3983 +-- frontend/src/components/RenderCytoscape.js | 211 +- frontend/src/components/ScrollToTop.jsx | 22 +- frontend/src/components/SettingsPopup.js | 286 +- frontend/src/components/history.jsx | 10 +- frontend/src/css/nunito.css | 33 +- frontend/src/defaultCytoscapeStyle.js | 777 +- frontend/src/index.css | 4 +- frontend/src/index.js | 15 +- frontend/src/serviceWorker.js | 36 +- frontend/src/theme.js | 115 +- frontend/src/views/About.jsx | 99 +- frontend/src/views/Admin.jsx | 6950 +++--- frontend/src/views/AdminSetup.jsx | 388 +- frontend/src/views/AngularWorkflow.jsx | 20205 +++++++++------- frontend/src/views/AppCreator.jsx | 8232 ++++--- frontend/src/views/Apps.jsx | 3619 +-- frontend/src/views/Contact.jsx | 668 +- frontend/src/views/Dashboard.jsx | 799 +- frontend/src/views/Docs.jsx | 1046 +- frontend/src/views/EditSchedule.jsx | 2938 +-- frontend/src/views/EditWebhook.jsx | 677 +- frontend/src/views/EditWorkflow.jsx | 695 +- frontend/src/views/ForgotPassword.jsx | 206 +- frontend/src/views/ForgotPasswordLink.jsx | 214 +- frontend/src/views/HandlePayment.js | 10 +- frontend/src/views/Introduction.jsx | 340 +- frontend/src/views/Landingpage.jsx | 344 +- frontend/src/views/LandingpageLoggedin.jsx | 25 +- frontend/src/views/LandingpageNew.jsx | 828 +- frontend/src/views/LoginPage.jsx | 793 +- frontend/src/views/MyView.jsx | 3493 +-- frontend/src/views/Oauth2.jsx | 12 +- frontend/src/views/Post.jsx | 105 +- frontend/src/views/PrivacyPolicy.jsx | 378 +- frontend/src/views/RegisterLink.jsx | 130 +- frontend/src/views/RegisterPage.jsx | 283 +- frontend/src/views/Schedules.jsx | 412 +- frontend/src/views/SetAuthentication.jsx | 259 +- frontend/src/views/SetAuthenticationSSO.jsx | 241 +- frontend/src/views/SettingsPage.jsx | 1554 +- frontend/src/views/Webhooks.jsx | 559 +- frontend/src/views/Workflows.jsx | 4670 ++-- 63 files changed, 42478 insertions(+), 31383 deletions(-) diff --git a/frontend/README.md b/frontend/README.md index 3bcf7832..2257fc4e 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,4 +1,5 @@ # Certificate: + Creating a localhost certificate: ``` diff --git a/frontend/package.json b/frontend/package.json index aaa5e1c0..f027f35a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -93,6 +93,7 @@ "not op_mini all" ], "devDependencies": { + "prettier": "2.4.1", "promise-window": "^1.2.1" } } diff --git a/frontend/public/index.html b/frontend/public/index.html index 2ece9bc4..748b7a1a 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -1,13 +1,15 @@ - - - - - + + + + + Shuffle -
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index f1192ed3..f1d6cecd 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,9 +1,9 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect } from "react"; -import { Route } from 'react-router'; -import { BrowserRouter } from 'react-router-dom'; -import { CookiesProvider } from 'react-cookie'; -import { removeCookies, useCookies } from 'react-cookie'; +import { Route } from "react-router"; +import { BrowserRouter } from "react-router-dom"; +import { CookiesProvider } from "react-cookie"; +import { removeCookies, useCookies } from "react-cookie"; import EditSchedule from "./views/EditSchedule"; import Schedules from "./views/Schedules"; @@ -12,10 +12,10 @@ import Workflows from "./views/Workflows"; import EditWebhook from "./views/EditWebhook"; import AngularWorkflow from "./views/AngularWorkflow"; -import Header from './components/Header'; -import theme from './theme' -import Apps from './views/Apps'; -import AppCreator from './views/AppCreator'; +import Header from "./components/Header"; +import theme from "./theme"; +import Apps from "./views/Apps"; +import AppCreator from "./views/AppCreator"; import Dashboard from "./views/Dashboard"; import AdminSetup from "./views/AdminSetup"; @@ -31,242 +31,559 @@ import SettingsPage from "./views/SettingsPage"; import MyView from "./views/MyView"; -import { createMuiTheme, MuiThemeProvider } from '@material-ui/core/styles'; +import { createMuiTheme, MuiThemeProvider } from "@material-ui/core/styles"; import ScrollToTop from "./components/ScrollToTop"; import AlertTemplate from "./components/AlertTemplate"; import { positions, Provider } from "react-alert"; -import {isMobile} from "react-device-detect"; +import { isMobile } from "react-device-detect"; -import detectEthereumProvider from '@metamask/detect-provider'; +import detectEthereumProvider from "@metamask/detect-provider"; // Production - backend proxy forwarding in nginx -var globalUrl = window.location.origin +var globalUrl = window.location.origin; // CORS used for testing purposes. Should only happen with specific port and http -if ( window.location.port === "3000") { - globalUrl = "http://localhost:5001" - //globalUrl = "http://localhost:5002" +if (window.location.port === "3000") { + globalUrl = "http://localhost:5001"; + //globalUrl = "http://localhost:5002" } const App = (message, props) => { - const [userdata, setUserData] = useState({}); - const [notifications, setNotifications] = useState([]) - const [cookies, setCookie, removeCookie] = useCookies([]) - const [isLoggedIn, setIsLoggedIn] = useState(false); - const [dataset, setDataset] = useState(false); - const [isLoaded, setIsLoaded] = useState(false); - const [curpath, setCurpath] = useState(typeof window === 'undefined' || window.location === undefined ? "" : window.location.pathname) + const [userdata, setUserData] = useState({}); + const [notifications, setNotifications] = useState([]); + const [cookies, setCookie, removeCookie] = useCookies([]); + const [isLoggedIn, setIsLoggedIn] = useState(false); + const [dataset, setDataset] = useState(false); + const [isLoaded, setIsLoaded] = useState(false); + const [curpath, setCurpath] = useState( + typeof window === "undefined" || window.location === undefined + ? "" + : window.location.pathname + ); - useEffect(() => { - if (dataset === false) { - getUserNotifications() - checkLogin() - setDataset(true) - } - }) + useEffect(() => { + if (dataset === false) { + getUserNotifications(); + checkLogin(); + setDataset(true); + } + }); - if (isLoaded && !isLoggedIn && (!window.location.pathname.startsWith("/login") && (!window.location.pathname.startsWith("/docs") && (!window.location.pathname.startsWith("/adminsetup"))))) { - window.location = "/login" - } + if ( + isLoaded && + !isLoggedIn && + !window.location.pathname.startsWith("/login") && + !window.location.pathname.startsWith("/docs") && + !window.location.pathname.startsWith("/adminsetup") + ) { + window.location = "/login"; + } - const getUserNotifications = () => { - fetch(`${globalUrl}/api/v1/notifications`, { - credentials: "include", - headers: { - 'Content-Type': 'application/json', - }, - }) - .then(response => response.json()) - .then(responseJson => { - if (responseJson.success === true && responseJson.notifications !== null && responseJson.notifications !== undefined && responseJson.notifications.length > 0) { - //console.log("RESP: ", responseJson) - setNotifications(responseJson.notifications) - } - }) - .catch(error => { - console.log("Failed getting notifications for user: ", error) - }); - } + const getUserNotifications = () => { + fetch(`${globalUrl}/api/v1/notifications`, { + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => response.json()) + .then((responseJson) => { + if ( + responseJson.success === true && + responseJson.notifications !== null && + responseJson.notifications !== undefined && + responseJson.notifications.length > 0 + ) { + //console.log("RESP: ", responseJson) + setNotifications(responseJson.notifications); + } + }) + .catch((error) => { + console.log("Failed getting notifications for user: ", error); + }); + }; - const checkLogin = () => { - var baseurl = globalUrl - fetch(baseurl + "/api/v1/users/getinfo", { - credentials: "include", - headers: { - 'Content-Type': 'application/json', - }, - }) - .then(response => response.json()) - .then(responseJson => { - var userInfo = {} - if (responseJson.success === true) { - console.log(responseJson) + const checkLogin = () => { + var baseurl = globalUrl; + fetch(baseurl + "/api/v1/users/getinfo", { + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => response.json()) + .then((responseJson) => { + var userInfo = {}; + if (responseJson.success === true) { + console.log(responseJson); - userInfo = responseJson - setIsLoggedIn(true) - //console.log("Cookies: ", cookies) - // Updating cookie every request - for (var key in responseJson["cookies"]) { - setCookie(responseJson["cookies"][key].key, responseJson["cookies"][key].value, { path: "/" }) - } - } + userInfo = responseJson; + setIsLoggedIn(true); + //console.log("Cookies: ", cookies) + // Updating cookie every request + for (var key in responseJson["cookies"]) { + setCookie( + responseJson["cookies"][key].key, + responseJson["cookies"][key].value, + { path: "/" } + ); + } + } - // Handling Ethereum update - detectEthereumProvider() - .then((provider) => { - if (provider && userInfo.eth_info !== undefined && userInfo.eth_info !== null) { - if (userInfo.eth_info.account !== undefined && userInfo.eth_info.account !== null && userInfo.eth_info.account.length === 0) { - userInfo.eth_info = {} - var method = "eth_requestAccounts" - var params = [] - provider.request({ - method: method, - params, - }) - .then((result) => { - if (result !== undefined && result !== null && result.length > 0) { - userInfo.eth_info.account = result[0] + // Handling Ethereum update + detectEthereumProvider().then((provider) => { + if ( + provider && + userInfo.eth_info !== undefined && + userInfo.eth_info !== null + ) { + if ( + userInfo.eth_info.account !== undefined && + userInfo.eth_info.account !== null && + userInfo.eth_info.account.length === 0 + ) { + userInfo.eth_info = {}; + var method = "eth_requestAccounts"; + var params = []; + provider + .request({ + method: method, + params, + }) + .then((result) => { + if ( + result !== undefined && + result !== null && + result.length > 0 + ) { + userInfo.eth_info.account = result[0]; - // Getting and setting balance for the current user - method = "eth_getBalance" - params = [ - userInfo.eth_info.account, - "latest" - ] - provider.request({ - method: method, - params, - }) - .then((result) => { - if (result !== undefined && result !== null && result.length > 0) { - userInfo.parsed_balance = result/1000000000000000000 - } else { - alert.error("Couldn't find balance: ", result) - } - // The result varies by RPC method. - // For example, this method will return a transaction hash hexadecimal string on success. - }) - .catch((error) => { - // If the request fails, the Promise will reject with an error. - alert.error("Failed getting info from ethereum API: "+error) - }) - } else { - alert.error("Couldn't find any user: ", result) - } - }) - .catch((error) => { - // If the request fails, the Promise will reject with an error. - alert.error("Failed getting info from ethereum API: "+error) - }) - } - - // Register hooks here - provider.on('message', (event) => { - alert.info("Message from MetaMask: ", event) - }) + // Getting and setting balance for the current user + method = "eth_getBalance"; + params = [userInfo.eth_info.account, "latest"]; + provider + .request({ + method: method, + params, + }) + .then((result) => { + if ( + result !== undefined && + result !== null && + result.length > 0 + ) { + userInfo.parsed_balance = + result / 1000000000000000000; + } else { + alert.error("Couldn't find balance: ", result); + } + // The result varies by RPC method. + // For example, this method will return a transaction hash hexadecimal string on success. + }) + .catch((error) => { + // If the request fails, the Promise will reject with an error. + alert.error( + "Failed getting info from ethereum API: " + error + ); + }); + } else { + alert.error("Couldn't find any user: ", result); + } + }) + .catch((error) => { + // If the request fails, the Promise will reject with an error. + alert.error( + "Failed getting info from ethereum API: " + error + ); + }); + } - provider.on('chainChanged', (chainId) => { - console.log("Changed chain to: ", chainId) - - method = "eth_getBalance" - params = [ - userInfo.eth_info.account, - "latest" - ] - provider.request({ - method: method, - params, - }) - .then((result) => { - console.log("Got result: ", result) - if (result !== undefined && result !== null) { - userInfo.eth_info.balance = result - userInfo.eth_info.parsed_balance = result/1000000000000000000 - console.log("INFO: ", userInfo) - setUserData(userInfo) - } else { - alert.error("Couldn't find balance: ", result) - } - }) - .catch((error) => { - // If the request fails, the Promise will reject with an error. - alert.error("Failed getting info from ethereum API: "+error) - }) - }) - } - }) + // Register hooks here + provider.on("message", (event) => { + alert.info("Message from MetaMask: ", event); + }); - if (userInfo.eth_info !== undefined && userInfo.eth_info.balance !== undefined) { - //console.log(userInfo.eth_info.balance) - userInfo.eth_info.parsed_balance = userInfo.eth_info.balance/1000000000000000000 - } + provider.on("chainChanged", (chainId) => { + console.log("Changed chain to: ", chainId); - //console.log("USER: ", userInfo) - setUserData(userInfo) - setIsLoaded(true) + method = "eth_getBalance"; + params = [userInfo.eth_info.account, "latest"]; + provider + .request({ + method: method, + params, + }) + .then((result) => { + console.log("Got result: ", result); + if (result !== undefined && result !== null) { + userInfo.eth_info.balance = result; + userInfo.eth_info.parsed_balance = + result / 1000000000000000000; + console.log("INFO: ", userInfo); + setUserData(userInfo); + } else { + alert.error("Couldn't find balance: ", result); + } + }) + .catch((error) => { + // If the request fails, the Promise will reject with an error. + alert.error( + "Failed getting info from ethereum API: " + error + ); + }); + }); + } + }); - }) - .catch(error => { - setIsLoaded(true) - }); - } + if ( + userInfo.eth_info !== undefined && + userInfo.eth_info.balance !== undefined + ) { + //console.log(userInfo.eth_info.balance) + userInfo.eth_info.parsed_balance = + userInfo.eth_info.balance / 1000000000000000000; + } - // Dumb for content load (per now), but good for making the site not suddenly reload parts (ajax thingies) + //console.log("USER: ", userInfo) + setUserData(userInfo); + setIsLoaded(true); + }) + .catch((error) => { + setIsLoaded(true); + }); + }; - const options = { - timeout: 9000, - position: positions.BOTTOM_LEFT, - }; + // Dumb for content load (per now), but good for making the site not suddenly reload parts (ajax thingies) - const includedData = window.location.pathname === "/home" || window.location.pathname === "/features" ? -
- } /> -
: -
- -
-
- } /> - } /> - } /> - {userdata.id !== undefined ? - } /> - : null} - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - { window.location.pathname = "/docs/about" }} /> - } /> - } /> - } /> - } /> - } /> -
+ const options = { + timeout: 9000, + position: positions.BOTTOM_LEFT, + }; - //
- // backgroundColor: "#213243", - // This is a mess hahahah - return ( - - - - - {includedData} - - - - - ); + const includedData = + window.location.pathname === "/home" || + window.location.pathname === "/features" ? ( +
+ } + /> +
+ ) : ( +
+ +
+
+ ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + {userdata.id !== undefined ? ( + ( + + )} + /> + ) : null} + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + } + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + } + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + { + window.location.pathname = "/docs/about"; + }} + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> +
+ ); + + //
+ // 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..bb85a059 100644 --- a/frontend/src/components/ConfigureWorkflow.jsx +++ b/frontend/src/components/ConfigureWorkflow.jsx @@ -1,267 +1,362 @@ -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, + 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) + 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 - } - } - } + 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) - } - } + if (!filled) { + newaction.must_authenticate = true; + newaction.action_ids.push(action.id); + } + } - newaction.app = app - } + newaction.app = app; + } - if (action.errors !== undefined && action.errors !== null && action.errors.length > 0) { - //console.log("Node has errors!: ", action.errors) - } + if ( + action.errors !== undefined && + action.errors !== null && + action.errors.length > 0 + ) { + //console.log("Node has errors!: ", action.errors) + } - 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.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!") + if ( + newaction.authenticationId === null || + newaction.authenticationId === undefined || + newaction.authenticationId.length === "" + ) { + //console.log("FAILED to authenticate node!") - 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); + } + } 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 (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) - } - } - } + 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); + } + } - 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) - } - } + for (var key in workflow.triggers) { + var trigger = workflow.triggers[key]; + trigger.index = key; - for (var key in workflow.triggers) { - var trigger = workflow.triggers[key] - trigger.index = key + if (trigger.status === "running") { + continue; + } - if (trigger.status === "running") { - continue - } + if ( + trigger.trigger_type === "SUBFLOW" || + trigger.trigger_type === "USERINPUT" + ) { + continue; + } - if (trigger.trigger_type === "SUBFLOW" || trigger.trigger_type === "USERINPUT") { - continue - } - - requiredTriggers.push(trigger) - } + requiredTriggers.push(trigger); + } - if (requiredTriggers.length === 0 && requiredVariables.length === 0 && newactions.length === 0) { - setConfigureWorkflowModalOpen(false) - } + if ( + requiredTriggers.length === 0 && + requiredVariables.length === 0 && + newactions.length === 0 + ) { + setConfigureWorkflowModalOpen(false); + } - setRequiredTriggers(requiredTriggers) - setRequiredVariables(requiredVariables) - setRequiredActions(newactions) - } + setRequiredTriggers(requiredTriggers); + setRequiredVariables(requiredVariables); + setRequiredActions(newactions); + } - if (appAuthentication.length !== previousAuth.length) { - var newactions = [] - for (var actionkey in requiredActions) { - var newaction = requiredActions[actionkey] - const app = newaction.app + if (appAuthentication.length !== previousAuth.length) { + var newactions = []; + for (var actionkey in requiredActions) { + var newaction = requiredActions[actionkey]; + const app = newaction.app; - for (var key in appAuthentication) { - const auth = appAuthentication[key] - if (auth.app.name === app.name && auth.active) { - newaction.auth_done = true - break - } - } + for (var key in appAuthentication) { + const auth = appAuthentication[key]; + if (auth.app.name === app.name && auth.active) { + newaction.auth_done = true; + break; + } + } - newactions.push(newaction) - } + newactions.push(newaction); + } - setRequiredActions(newactions) - setPreviousAuth(appAuthentication) - // Set auth done to true - //"auth_done": false - } + setRequiredActions(newactions); + setPreviousAuth(appAuthentication); + // Set auth done to true + //"auth_done": false + } - const TriggerSection = (props) => { - const {trigger} = props + const TriggerSection = (props) => { + const { trigger } = props; - return ( - - - - {trigger.label} - - - - {trigger.trigger_type === "WEBHOOK" && trigger.status !== "running" ? - - : - trigger.trigger_type === "SCHEDULE" && trigger.status !== "running" ? - + ) : trigger.trigger_type === "SCHEDULE" && + trigger.status !== "running" ? ( + - : - null} - {/* + 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 - }} - /> - } - style={{}} - /> - - ) - } + //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 AppSection = (props) => { - const {action} = props + const AppSection = (props) => { + const { action } = props; - return ( - - - - {action.app_name} - - - - {action.must_authenticate ? - action.auth_done ? - - : - selectedAction.app_name === action.app_name ? - - : - + ) : selectedAction.app_name === action.app_name ? ( + + ) : ( + - : - null} - {action.must_activate ? - - : null} - - ) - } + setItemChanged(true); + setSelectedAction(action.action); + setSelectedApp(action.app); + setAuthenticationModalOpen(true); + }} + > + Authenticate + + ) + ) : null} + {action.must_activate ? ( + + ) : 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?") - } + 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) { - alert.error("Failed to activate the app") - } else { - alert.success("App activated for your organization!") - } - }) - .catch(error => { - alert.error(error.toString()) - }); - } + 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()); + }); + }; - return ( -
- {workflow.name} - - The following configuration makes the workflow ready immediately. - - {requiredActions.length > 0 ? - - Actions - - {requiredActions.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} + {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} +
+ + {/* */} - - -
-
- ) -} + + +
+
+ ); +}; -export default ConfigureWorkflow +export default ConfigureWorkflow; 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/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/Header.js b/frontend/src/components/Header.js index 113ba374..1abddaea 100644 --- a/frontend/src/components/Header.js +++ b/frontend/src/components/Header.js @@ -1,250 +1,310 @@ -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, +} from "@material-ui/icons"; //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 ? - {data.title} - : - null - } - - {data.description} - - {/*data.tags !== undefined && data.tags !== null && data.tags.length > 0 ? + {data.image !== undefined && + data.image !== null && + data.image.length > 0 ? ( + {data.title} + ) : null} + {data.description} + {/*data.tags !== undefined && data.tags !== null && data.tags.length > 0 ? data.tags.map((tag, index) => { return ( { ) }) : null */} - {data.read === false ? - - : null} - - ) - } + {data.read === false ? ( + + ) : null} + + ); + }; - const notificationMenu = - - { - setAnchorEl(event.currentTarget); - }}> - - - - - { - handleClose() - }} - > - -
- - Your Notifications ({notifications.length}) - - {notifications.length > 1 ? - - : null} -
- - Notifications are made by Shuffle to help you discover issues or improvements. - -
- {notifications.map((data, index) => { - return ( - - ) - })} -
-
+ const notificationMenu = ( + + { + setAnchorEl(event.currentTarget); + }} + > + + + + + { + handleClose(); + }} + > + +
+ + Your Notifications ({notifications.length}) + + {notifications.length > 1 ? ( + + ) : null} +
+ + Notifications are made by Shuffle to help you discover issues or + improvements. + +
+ {notifications.map((data, index) => { + return ; + })} +
+
+ ); - // Should be based on some path - const avatarMenu = - - { - setAnchorElAvatar(event.currentTarget); - }}> - - - { - handleClose() - }} - > - { - event.preventDefault() - handleClose() - }}> - - Settings - - - { - event.preventDefault() - handleClose() - handleClickLogout() - }}> -  Logout - - - + // Should be based on some path + const avatarMenu = ( + + { + setAnchorElAvatar(event.currentTarget); + }} + > + + + { + handleClose(); + }} + > + { + event.preventDefault(); + handleClose(); + }} + > + + Settings + + + { + event.preventDefault(); + handleClose(); + handleClickLogout(); + }} + > +  Logout + + + + ); - - // Handle top bar or something - const logoCheck = !homePage ? null : null - //
- const loginTextBrowser = !isLoggedIn ? -
- - - -
- About -
- -
-
-
- - - -
Login
- -
-
-
-
- : -
-
- - - -
- - Workflows -
- -
- - -
- - Apps -
- -
- {/* + // Handle top bar or something + const logoCheck = !homePage ? null : null; + //
+ const loginTextBrowser = !isLoggedIn ? ( +
+ + + +
+ About +
+ +
+
+
+ + + +
+ Login +
+ +
+
+
+
+ ) : ( +
+
+ + + +
+ + Workflows +
+ +
+ + +
+ + Apps +
+ +
+ {/*
Dashboard
*/} - - -
- - Docs -
- -
- {/* + + +
+ + Docs +
+ +
+ {/*
@@ -425,146 +573,236 @@ const Header = props => { */} - {/* + {/*
Configure
*/} - -
-
- {avatarMenu} - {notificationMenu} - {userdata === undefined || userdata.admin === undefined || userdata.admin === null || !userdata.admin ? null : - - - - } - {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 === "" ? - {data.name} - : - {data.name} + 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 === "" ? ( + {data.name} + ) : ( + {data.name} + ); - return ( - + return ( + + +
+ {image} {data.name} +
+
+
+ ); + })} + + )} +
+
+ ); - -
- {image} {data.name} -
-
- - ) - })} - - } -
-
+ //console.log("USR: ", userdata.orgs) - //console.log("USR: ", userdata.orgs) - - const loginTextMobile = !isLoggedIn ? -
- - - -
- - - - - -
- -
- - -
- About -
- -
-
-
- : -
-
- - - -
Shuffle
- -
- - -
Workflows
- -
- - -
Apps
- -
- {/* + 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/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} -
- Username -
- -
- Password -
- -
-
- - - -
- {loginInfo} -
-
- -
-
- ); -} + return ( + + {formtitle} +
+ Username +
+ +
+ Password +
+ +
+
+ + + +
+ {loginInfo} +
+
+ +
+
+ ); +}; 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); }} > -
+
{children}
- ) -} + ); +}; -export default NestedMenuItem +export default NestedMenuItem; diff --git a/frontend/src/components/Oauth2Auth.jsx b/frontend/src/components/Oauth2Auth.jsx index 2d9afa2d..634ccf67 100644 --- a/frontend/src/components/Oauth2Auth.jsx +++ b/frontend/src/components/Oauth2Auth.jsx @@ -1,224 +1,332 @@ -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", }, }, -} +}; 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 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) { - //scopes.push("offline_access") - resources = scopes.join(",") - } + var resources = ""; + if (scopes !== undefined && (scopes !== null) & (scopes.length > 0)) { + //scopes.push("offline_access") + resources = scopes.join(","); + } - 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) - } + 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); + } - 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}` - } + 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}`; + } - 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 = `${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 { + //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& - 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!'); + // 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) - getAppAuthentication(true, true) - } - }, 1000); - //do { - // setTimeout(() => { - // console.log(newwin) - // console.log("CLOSED", newwin.closed) - // if (newwin.closed) { + var open = true; + const timer = setInterval(() => { + if (newwin.closed) { + setButtonClicked(false); + clearInterval(timer); + //alert('"Secure Payment" window closed!'); - // open = false - // } - // }, 1000) - //} - //while(open === true) - } catch (e) { - alert.error("Failed authentication - probably bad credentials. Try again") - setButtonClicked(false) - } + getAppAuthentication(true, true); + } + }, 1000); + //do { + // setTimeout(() => { + // console.log(newwin) + // console.log("CLOSED", newwin.closed) + // if (newwin.closed) { - return - //do { - //} while ( - } + // 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 = [] + 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] = "" - } - } + 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 - } + 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 - } - } - } - } + // 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) - } + 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) + 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, - }) - } + 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) + 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. This is + usually made in the remote system. + + + {" "} + 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" ? - - : - { - 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 : - } - 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 - }} - /> - - } - + {data.schema !== undefined && + data.schema !== null && + data.schema.type === "bool" ? ( + + ) : ( + { + 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 : ( + } + 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 + }} + /> +
+ )} + - {defaultConfigSet ? - - ... or - - - : - 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.jsx b/frontend/src/components/OrgHeader.jsx index 14e32552..49897638 100644 --- a/frontend/src/components/OrgHeader.jsx +++ b/frontend/src/components/OrgHeader.jsx @@ -1,456 +1,627 @@ -import React, { useEffect} from 'react'; -import { makeStyles } from '@material-ui/styles'; -import { useTheme } from '@material-ui/core/styles'; +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 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'; +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" - }, -}) + notchedOutline: { + borderColor: "#f85a3e !important", + }, +}); -const defaultImage = "data:image/png;base64,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" +const defaultImage = + "data:image/png;base64,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"; const OrgHeader = (props) => { - const {userdata, selectedOrganization, setSelectedOrganization, globalUrl, isCloud} = props + const { + userdata, + selectedOrganization, + setSelectedOrganization, + globalUrl, + isCloud, + } = props; - const theme = useTheme() - const alert = useAlert() - const classes = useStyles() + 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) + 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 [file, setFile] = React.useState("") - const [fileBase64, setFileBase64] = React.useState(selectedOrganization.image) - const [expanded, setExpanded] = React.useState(false) + 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') + 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 - ) + 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 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 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()) - }); - } + 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) - } + 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 = - - - + //console.log("USER: ", userdata) + const orgSaveButton = ( + + + + ); - 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 - } - } + 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 - } + 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", - }, - }} - /> - - - } - - - - 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", - }, - }} - /> - - - {/* + 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", + }, + }} + /> + + + )} + + + 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 ? @@ -459,13 +630,11 @@ const OrgHeader = (props) => { } */} - - : - null - } -
-
- ) -} + + ) : null} +
+
+ ); +}; -export default OrgHeader +export default OrgHeader; diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index b6bcbd46..97c5ac90 100644 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -1,47 +1,115 @@ -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 { 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 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 CodeMirror from '@uiw/react-codemirror'; -import 'codemirror/keymap/sublime'; -import 'codemirror/theme/gruvbox-dark.css'; +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 CodeMirror from "@uiw/react-codemirror"; +import "codemirror/keymap/sublime"; +import "codemirror/theme/gruvbox-dark.css"; 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,374 +117,537 @@ const useStyles = makeStyles({ // }, //) -const openApiFieldDesc = "Generated by OpenAPI body example" +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, + } = props; - const theme = useTheme(); - const classes = useStyles() + const theme = useTheme(); + const classes = useStyles(); - const [expansionModalOpen, setExpansionModalOpen] = React.useState(false); - const [hideBody, setHideBody] = React.useState(false) - const [activateHidingBody, setActivateHidingBody] = React.useState(false) + const [expansionModalOpen, setExpansionModalOpen] = React.useState(false); + const [hideBody, setHideBody] = React.useState(false); + const [activateHidingBody, setActivateHidingBody] = React.useState(false); - const keywords = ["len(", "lower(", "upper(", "trim(", "split(", "length(", "number(", "parse(", "join("] - const getParents = (action) => { - if (cy === undefined) { - return [] - } + const keywords = [ + "len(", + "lower(", + "upper(", + "trim(", + "split(", + "length(", + "number(", + "parse(", + "join(", + ]; + const getParents = (action) => { + if (cy === undefined) { + return []; + } - var allkeys = [action.id] - var handled = [] - var results = [] + 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 - } + while (true) { + for (var key in allkeys) { + var currentnode = cy.getElementById(allkeys[key]); + if (currentnode === undefined) { + continue; + } - if (handled.includes(currentnode.data("id"))) { - continue - } else { - // Get the name / label here too? - handled.push(currentnode.data("id")) - results.push(currentnode.data()) - } + if (handled.includes(currentnode.data("id"))) { + continue; + } else { + // Get the name / label here too? + handled.push(currentnode.data("id")); + results.push(currentnode.data()); + } - if (currentnode.length === 0) { - continue - } + if (currentnode.length === 0) { + continue; + } - const incomingEdges = currentnode.incomers('edge') - if (incomingEdges.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) { - break - } - } + 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) { + 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 - } + // 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 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"); + } - 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); - 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 - 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 (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; + } - 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 - // 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]; - 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); + } - 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; - selectedAction.errors = [] - selectedAction.is_valid = true + setSelectedAction(selectedAction); + setSelectedApp(parsedapp); + } + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; - // Updating params for the new action - selectedAction.parameters = foundAction.parameters - selectedAction.app_id = appId - selectedAction.app_version = parsedapp.app_version + const defineStartnode = () => { + if (cy === undefined) { + return; + } - setSelectedAction(selectedAction) - setSelectedApp(parsedapp) - } - }) - .catch(error => { - alert.error(error.toString()) - }); - } + 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; + } + } - const defineStartnode = () => { - if (cy === undefined) { - return - } + 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]); + } + } - 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 - } - } + // 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()) + // } + // } + //} - 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]) - } - } + setUpdate("start_node" + selectedAction.id); + workflow.start = selectedAction.id; + setWorkflow(workflow); + //setStartNode(selectedAction.id) + }; - // 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()) - // } - // } - //} + 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); - setUpdate("start_node"+selectedAction.id) - workflow.start = selectedAction.id - setWorkflow(workflow) - //setStartNode(selectedAction.id) - } + useEffect(() => { + if ( + selectedActionParameters !== null && + selectedActionParameters.length === 0 + ) { + if ( + selectedAction.parameters !== null && + selectedAction.parameters.length > 0 + ) { + setSelectedActionParameters(selectedAction.parameters); + } + } - 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) + 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); + } - 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: "", + }); + actionlist.push({ + type: "Shuffle DB", + name: "Shuffle DB", + value: "$shuffle_cache", + highlight: "shuffle", + autocomplete: "shuffle", + 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, + }); + } + } - 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": ""}) - actionlist.push({"type": "Shuffle DB", "name": "Shuffle DB", "value": "$shuffle_cache", "highlight": "shuffle", "autocomplete": "shuffle", "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: "", + }); + } + } - // 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; + } - // 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 - } + 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; + } - 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 - } + var foundResult = workflowExecutions[key].results.find( + (result) => result.action.id === item.id + ); + if (foundResult === undefined) { + continue; + } - 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"); - 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; + } + } - 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) { + exampledata = JSON.parse(foundResult.result); + break; + } + //else { + // console.log("Invalid JSON: ", foundResult.result) + //} + } + } - // Finds the FIRST json only - if (jsonvalid) { - exampledata = JSON.parse(foundResult.result) - break - } - //else { - // console.log("Invalid JSON: ", foundResult.result) - //} - } - } + // 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); + } + } - // 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) - } - } + setActionlist(actionlist); + } + }); - setActionlist(actionlist) - } - }) + const changeActionParameter = (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, + }, + ]; - const changeActionParameter = (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); + } 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 IF: ", paramcheck) + 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 + //} - } 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 - } + 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; + } + } - 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 (event.target.value[event.target.value.length - 1] === "$") { + if (!showDropdown) { + setShowAutocomplete(false); + setShowDropdown(true); + setShowDropdownNumber(count); + } + } else { + if (showDropdown) { + setShowDropdown(false); + } + } - 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 - /* + // 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) { @@ -427,141 +658,167 @@ const ParsedAction = (props) => { 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 + // 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 - } + 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 = "" - } - } + 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) + //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("\"") + //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 - } + 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 (jsonvalid) { + setJsonList(GetParsedPaths(JSON.parse(actionItem.example), "")); - if (!showDropdown) { - setShowAutocomplete(false) - setShowDropdown(true) - setShowDropdownNumber(count) - } - } - } - } - } else { - if (jsonList.length > 0) { - setJsonList([]) - } - } + 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 - setSelectedAction(selectedAction) - //setUpdate(Math.random()) - //setUpdate(event.target.value) - } + //console.log("CHANGING ACTION COUNT !") + selectedActionParameters[count].value = event.target.value; + selectedAction.parameters[count].value = event.target.value; + 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, - }] + 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) + 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; + } - } 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 + //} - 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 (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.display.maxLine.text[event.display.maxLine.text.length - 1] === + "$" + ) { + if (!showDropdown) { + setShowAutocomplete(false); + setShowDropdown(true); + setShowDropdownNumber(count); + } + } else { + if (showDropdown) { + setShowDropdown(false); + } + } - 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 - /* + // 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) { @@ -572,803 +829,1037 @@ const ParsedAction = (props) => { 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 + // 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 - } + 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 = "" - } - } + 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) + //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("\"") + //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 - } + 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 (jsonvalid) { + setJsonList(GetParsedPaths(JSON.parse(actionItem.example), "")); - if (!showDropdown) { - setShowAutocomplete(false) - setShowDropdown(true) - setShowDropdownNumber(count) - } - } - } - } - } else { - if (jsonList.length > 0) { - setJsonList([]) - } - } + 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) - } + selectedActionParameters[count].value = event.display.maxLine.text; + selectedAction.parameters[count].value = event.display.maxLine.text; + setSelectedAction(selectedAction); + //setUpdate(Math.random()) + //setUpdate(event.target.value) + }; - const changeActionParameterVariable = (fieldvalue, count) => { - //console.log("CALLED THIS ONE WITH VALUE!", fieldvalue) - //if (selectedVariableParameter === fieldvalue) { - // return - //} + const changeActionParameterVariable = (fieldvalue, count) => { + //console.log("CALLED THIS ONE WITH VALUE!", fieldvalue) + //if (selectedVariableParameter === fieldvalue) { + // return + //} - setSelectedVariableParameter(fieldvalue) + setSelectedVariableParameter(fieldvalue); - selectedActionParameters[count].action_field = fieldvalue - selectedAction.parameters = selectedActionParameters + selectedActionParameters[count].action_field = fieldvalue; + selectedAction.parameters = selectedActionParameters; - setSelectedApp(selectedApp) - setSelectedAction(selectedAction) - setUpdate(fieldvalue) - } + 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 - // } - //}); + // 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 + selectedActionParameters[count].action_field = fieldvalue; + selectedAction.parameters = selectedActionParameters; - // FIXME - check if startnode + // FIXME - check if startnode - // Set value - setSelectedApp(selectedApp) + // Set value + setSelectedApp(selectedApp); - setSelectedAction(selectedAction) - setUpdate(Math.random()) - } + setSelectedAction(selectedAction); + setUpdate(Math.random()); + }; - const changeActionParameterVariant = (data, count) => { - selectedActionParameters[count].variant = data - selectedActionParameters[count].value = "" + 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 + 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; + } + } - // This is a stupid workaround to make it refresh rofl - setSelectedAction({}) + selectedAction.parameters = selectedActionParameters; - if (setSelectedTrigger !== undefined) { - setSelectedTrigger({}) - setSelectedApp({}) - setSelectedEdge({}) - } - // FIXME - check if startnode + // This is a stupid workaround to make it refresh rofl + setSelectedAction({}); - // Set value - setSelectedApp(selectedApp) - setSelectedAction(selectedAction) - setUpdate(Math.random()) - } + if (setSelectedTrigger !== undefined) { + setSelectedTrigger({}); + setSelectedApp({}); + setSelectedEdge({}); + } + // FIXME - check if startnode - // FIXME: Issue #40 - selectedActionParameters not reset - if (Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters.length > 0) { - var authWritten = false - return ( -
- Parameters - {selectedActionParameters.map((data, count) => { - if (data.variant === "") { - data.variant = "STATIC_VALUE" - } + // 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; + return ( +
+ Parameters + {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()) - - if (authWritten) { - return null - } + // 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()) - authWritten = true - return ( - - Authentication fields are hidden - - ) - } + if (authWritten) { + return null; + } - var staticcolor = "inherit" - var actioncolor = "inherit" - var varcolor = "inherit" - var multiline - if (data.multiline !== undefined && data.multiline !== null && data.multiline === true) { - multiline = true - } + authWritten = true; + return ( + + Authentication fields are hidden + + ); + } - if (data.value !== undefined && data.value !== null && data.value.startsWith("{") && data.value.endsWith("}")) { - multiline = true - } + var staticcolor = "inherit"; + var actioncolor = "inherit"; + var varcolor = "inherit"; + var multiline; + if ( + data.multiline !== undefined && + data.multiline !== null && + data.multiline === true + ) { + multiline = true; + } - var placeholder = "Static value" - if (data.example !== undefined && data.example !== null && data.example.length > 0) { - placeholder = data.example + if ( + data.value !== undefined && + data.value !== null && + data.value.startsWith("{") && + data.value.endsWith("}") + ) { + multiline = true; + } - if (data.name === "url" && data.value.length === 0) { - data.value = data.example - } - } + var placeholder = "Static value"; + if ( + data.example !== undefined && + data.example !== null && + data.example.length > 0 + ) { + placeholder = 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"] - } - } - } - } + if (data.name === "url" && data.value.length === 0) { + data.value = data.example; + } + } - 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 (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"]; + } + } + } + } - if (selectedApp.generated && data.name === "headers") { - //console.log("HEADER: ", data) - //if (data.value.length === 0) { + 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; + } - //} - //setSelectedActionParameters(selectedActionParameters) - } + if (selectedApp.generated && data.name === "headers") { + //console.log("HEADER: ", data) + //if (data.value.length === 0) { + //} + //setSelectedActionParameters(selectedActionParameters) + } - var hideBodyButton = "" - const hideBodyButtonValue = -
- - { - setHideBody(!hideBody) + var hideBodyButton = ""; + const hideBodyButtonValue = ( +
+ + { + setHideBody(!hideBody); - for (var key in selectedActionParameters) { - var currentItem = selectedActionParameters[key] + for (var key in selectedActionParameters) { + var currentItem = selectedActionParameters[key]; - if (currentItem.description === openApiFieldDesc) { - currentItem.field_active = !hideBody - console.log("Changing", currentItem) - } - } - }} - name="requires_unique" - /> - } - label={"Automatically fix body"} - /> - -
+ 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) + if (selectedApp.generated && data.name === "body") { + const regex = /\${(\w+)}/g; + const found = placeholder.match(regex); - hideBodyButton = hideBodyButtonValue - if (found === null || !hideBody) { - //setExtraBodyFields([]) - // - if (found === null) { - setActivateHidingBody(true) - } - } else { - console.log("SHOW BUTTON") + hideBodyButton = hideBodyButtonValue; + if (found === null || !hideBody) { + //setExtraBodyFields([]) + // + if (found === null) { + setActivateHidingBody(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 - } - } + 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 - } + 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, - }) - } + 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) - } + if (changed) { + setSelectedActionParameters(selectedActionParameters); + } - return hideBodyButton - } - } + return hideBodyButton; + } + } - if (activateHidingBody === true) { - hideBodyButton = "" - } + if (activateHidingBody === true) { + hideBodyButton = ""; + } - 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} + 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} - 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={ + 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) + // } + //} + }} + /> + ); - //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}` - /* + //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}` + /* 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]; + } - 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; + } - if (data.field_active === false) { - return null - } + // Shows nested list of nodes > their JSON lists + const ActionlistWrapper = (props) => { + const handleMenuClose = () => { + setShowAutocomplete(false); - // Shows nested list of nodes > their JSON lists - const ActionlistWrapper = (props) => { + if ( + !selectedActionParameters[count].value[ + selectedActionParameters[count].value.length - 1 + ] === "$" + ) { + setShowDropdown(false); + } - const handleMenuClose = () => { - setShowAutocomplete(false) + setUpdate(Math.random()); + setMenuPosition(null); + }; - if (!selectedActionParameters[count].value[selectedActionParameters[count].value.length-1] === "$") { - setShowDropdown(false) - } + const handleItemClick = (values) => { + if ( + values === undefined || + values === null || + values.length === 0 + ) { + return; + } - setUpdate(Math.random()) - setMenuPosition(null) - } + console.log("AUTOCOMPLETE1: ", values); - const handleItemClick = (values) => { - if (values === undefined || values === null || values.length === 0) { - return - } + 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; + } - console.log("AUTOCOMPLETE1: ", values) + toComplete += values[key].autocomplete; + } - 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 - } + // 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; + } + } - toComplete += values[key].autocomplete - } + selectedActionParameters[count]["value_replace"] = + paramcheck; + selectedAction.parameters[count]["value_replace"] = + paramcheck; + setSelectedAction(selectedAction); + setUpdate(Math.random()); - // 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, - }] + setShowDropdown(false); + setMenuPosition(null); + return; + } + } - } 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 - } - } + selectedActionParameters[count].value += toComplete; + selectedAction.parameters[count].value = + selectedActionParameters[count].value; + setSelectedAction(selectedAction); + setUpdate(Math.random()); - - selectedActionParameters[count]["value_replace"] = paramcheck - selectedAction.parameters[count]["value_replace"] = paramcheck - setSelectedAction(selectedAction) - setUpdate(Math.random()) + setShowDropdown(false); + setMenuPosition(null); + }; - setShowDropdown(false) - setMenuPosition(null) - return - } - } + const iconStyle = { + marginRight: 15, + }; - selectedActionParameters[count].value += toComplete - selectedAction.parameters[count].value = selectedActionParameters[count].value - setSelectedAction(selectedAction) - setUpdate(Math.random()) + 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" ? ( + + ) : ( + + ); - setShowDropdown(false) - setMenuPosition(null) - } + 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 = ""; + } + } - const iconStyle = { - marginRight: 15, - } + // 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]; - 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" ? : + 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 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 = "" - } - } + 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"); + } + } + } + }; - // 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 handleMouseover = () => { + if (innerdata.type === "Execution Argument") { + handleExecArgumentHover(true); + } else if (innerdata.type === "action") { + handleActionHover(true, innerdata.id); + } + }; - 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 handleMouseOut = () => { + if (innerdata.type === "Execution Argument") { + handleExecArgumentHover(false); + } else if (innerdata.type === "action") { + handleActionHover(false, 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') - } - } - } - } + var parsedPaths = []; + if (typeof innerdata.example === "object") { + parsedPaths = GetParsedPaths(innerdata.example, ""); + } - 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) - } - } + 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} +
+
+
+ ); + })} + + ) : ( + handleMouseover()} + onMouseOut={() => { + handleMouseOut(); + }} + onClick={() => { + handleItemClick([innerdata]); + }} + > + +
+ {icon} {innerdata.name} +
+
+
+ ); + })} + + ); + }; - var parsedPaths = [] - if (typeof(innerdata.example) === "object") { - parsedPaths = GetParsedPaths(innerdata.example, "") - } + var tmpitem = data.name.valueOf(); + if (data.name.startsWith("${") && data.name.endsWith("}")) { + tmpitem = tmpitem.slice(2, data.name.length - 1); + } - 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} -
-
-
- ) + tmpitem = ( + tmpitem.charAt(0).toUpperCase() + tmpitem.substring(1) + ).replaceAll("_", " "); - })} - - : - handleMouseover()} onMouseOut={() => {handleMouseOut()}} - onClick={() => { - handleItemClick([innerdata]) - }} - > - -
- {icon} {innerdata.name} -
-
-
- - ) - })} - - ) - } + 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} + + + ); - 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 itemColor = "#f85a3e" + //if (!data.required) { + // itemColor = "#ffeb3b" + //} + { + /*
*/ + } + return ( +
+ {hideBodyButton} +
+ {data.configuration === true ? ( + + { + setAuthenticationModalOpen(true); + }} + /> + + ) : null} - if (tmpitem === "Username basic") { - tmpitem = "Username" - } else if (tmpitem === "Password basic") { - tmpitem = "Password" - } +
+ + {tmpitem} + +
- 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 - } - -
- - {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 :
{ @@ -1398,7 +1889,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 :
{}}> @@ -1422,158 +1913,262 @@ const ParsedAction = (props) => {
*/} -
- {datafield} - {showDropdown && showDropdownNumber === count && data.variant === "STATIC_VALUE" && jsonList.length > 0 ? - - Autocomplete - { + 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()) - }} - 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) - } + 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()) + 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, - } + 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} + 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 + + ); -
- )})} -
- ) - } - return null - } + //const CustomPopper = function (props) { + // const classes = useStyles() + // return + //} - const expansionModal = - { - setExpansionModalOpen(false) - }} - PaperProps={{ - style: { - backgroundColor: theme.palette.surfaceColor, - color: "white", - minWidth: 600, - padding: 50, - }, - }} - > - Workflow Variable - - Hello - - + 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; + } - //const CustomPopper = function (props) { - // const classes = useStyles() - // return - //} + var foundResult = workflowExecutions[key].results.find( + (result) => result.action.id === selectedAction.id + ); + if (foundResult === undefined || foundResult === null) { + continue; + } - 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 - } + setSelectedResult(foundResult); - 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) - }}> - - - - - {}}> - - - - - - -
-
-
- {/*selectedAction.id === workflow.start ? null : + if (setCodeModalOpen !== undefined) { + setCodeModalOpen(true); + } + break; + } + } + }} + > + + + + + { + setAuthenticationModalOpen(true); + }} + > + + + + + {}} + > + + + + + + +
+
+
+ {/*selectedAction.id === workflow.start ? null : */} - {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} + + ); + })} + + ) : 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 + ); + } + } + } - - ) - })} - - : 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 ? ( +
+ + + + + +
+ ) : null} + {selectedAction.authentication !== undefined && + selectedAction.authentication !== null && + selectedAction.authentication.length > 0 ? ( +
+ Authentication +
+ { - if (e.target.value === "No selection") { - selectedAction.selectedAuthentication = {} - selectedAction.authentication_id = "" + 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}) + + ); + })} + - 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}) - - ) - })} - - - {/* + {/*
-
- : null} + + { + setAuthenticationModalOpen(true); + }} + > + + + +
+
+ ) : null} - {showEnvironment !== undefined && showEnvironment ? -
- - 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) => { + if (data.archived) { + 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) - - -
- : null} + {workflow.execution_variables !== undefined && + workflow.execution_variables !== null && + workflow.execution_variables.length > 0 ? ( +
+ Set execution variable (optional) + +
+ ) : 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 === undefined + ) { + 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}}) - } + 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; + } - }} - renderOption={(data) => { - var newActionname = data.name - if (data.label !== undefined && data.label !== null && data.label.length > 0) { - newActionname = data.label - } + const iconInfo = GetIconInfo({ name: data.name }); + const useIcon = iconInfo.originalIcon; - const iconInfo = GetIconInfo({"name": data.name}) - const useIcon = iconInfo.originalIcon + newActionname = ( + newActionname.charAt(0).toUpperCase() + + newActionname.substring(1) + ).replaceAll("_", " "); - newActionname = (newActionname.charAt(0).toUpperCase()+newActionname.substring(1)).replaceAll("_", " ") + return ( +
+ + {useIcon} + + {newActionname} +
+ ); + }} + renderInput={(params) => { + return ( + + ); + }} + /> + ) : null} - return ( -
- {useIcon} - {newActionname} -
- ) - }} - renderInput={(params) => { - return ( - - ) - }} - /> - - : null} - - {/*setNewSelectedAction !== undefined ? + {/*setNewSelectedAction !== undefined ? : null*/} - {selectedAction.description !== undefined && selectedAction.description.length > 0 && hideExtraTypes !== true ? -
- {selectedAction.description} -
: 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..00b8d09c 100644 --- a/frontend/src/components/ScrollToTop.jsx +++ b/frontend/src/components/ScrollToTop.jsx @@ -1,24 +1,24 @@ -import { useEffect } from 'react'; -import { withRouter } from 'react-router-dom'; +import { useEffect } from "react"; +import { withRouter } from "react-router-dom"; -function ScrollToTop({getUserNotifications, setCurpath, history }) { +function ScrollToTop({ getUserNotifications, setCurpath, history }) { useEffect(() => { const unlisten = history.listen(() => { window.scroll({ - top: 0, - left: 0, - behavior: "smooth", - }); + top: 0, + left: 0, + behavior: "smooth", + }); - setCurpath(window.location.pathname) - getUserNotifications() + setCurpath(window.location.pathname); + getUserNotifications(); }); return () => { unlisten(); - } + }; }, []); - return (null); + return null; } // https://stackoverflow.com/questions/36904185/react-router-scroll-to-top-on-every-transition 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 -

- -
- -
-

- Change password -

-
- -
-
- -
-
- -
-
- - -
-
-
- ); -} + .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

+ +
+ +
+

Change password

+
+ +
+
+ +
+
+ +
+
+ + +
+
+
+ ); +}; export default SettingsDialog; 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 b0879d2e..491d9476 100644 --- a/frontend/src/defaultCytoscapeStyle.js +++ b/frontend/src/defaultCytoscapeStyle.js @@ -1,389 +1,391 @@ -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', - 'cursor': 'pointer', - "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", - 'cursor': 'pointer', - "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', - 'background-color': 'data(backgroundcolor)', - 'border-color': '#ffffff', - 'color': 'data(color)', - 'width': 'data(width)', - 'height': 'data(height)', - 'border-radius': '5px', - "background-opacity": "0.5", - 'padding': '0px', - 'margin': '0px', - 'text-margin-x': '0px', - 'z-index': 4999, - }, - }, - { - 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[?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', - }, - }, -] +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", + cursor: "pointer", + "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", + cursor: "pointer", + "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", + "background-color": "data(backgroundcolor)", + "border-color": "#ffffff", + color: "data(color)", + width: "data(width)", + height: "data(height)", + "border-radius": "5px", + "background-opacity": "0.5", + padding: "0px", + margin: "0px", + "text-margin-x": "0px", + "z-index": 4999, + }, + }, + { + 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[?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: 'edge[?hasErrors]', @@ -397,5 +399,4 @@ const data = [{ // }, //}, - -export default data +export default data; diff --git a/frontend/src/index.css b/frontend/src/index.css index 793c42e5..da2c34e8 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; } 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..c6b39a39 100644 --- a/frontend/src/theme.js +++ b/frontend/src/theme.js @@ -1,65 +1,66 @@ -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", - 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,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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", - }, - }, - }, + text: { + secondary: "rgba(255,255,255,0.7)", + }, + type: "dark", + surfaceColor: "#27292d", + inputColor: "#383B40", + 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,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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 20aa37ee..46eb463e 100644 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -1,155 +1,208 @@ -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 { 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, + 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.jsx' +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 Admin = (props) => { - const { globalUrl, userdata } = props; + const { globalUrl, userdata } = 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(); + 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 [cloudSyncModalOpen, setCloudSyncModalOpen] = React.useState(false); - const [cloudSyncApikey, setCloudSyncApikey] = React.useState(""); - const [loading, setLoading] = React.useState(false); + const [cloudSyncModalOpen, setCloudSyncModalOpen] = React.useState(false); + const [cloudSyncApikey, setCloudSyncApikey] = React.useState(""); + const [loading, setLoading] = 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 [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 [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 [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) + 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); - } + useEffect(() => { + if (isDropzone) { + //redirectOpenApi(); + setIsDropzone(false); + } }, [isDropzone]); - const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" + const isCloud = + window.location.host === "localhost:3002" || + window.location.host === "shuffler.io"; - const get2faCode = (userId) => { - fetch(`${globalUrl}/api/v1/users/${userId}/get2fa`, { - 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("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({}) - // } - //} + 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", @@ -169,2178 +222,2588 @@ 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 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.") - } - - return response.json() - }) - .then((responseJson) => { - if (responseJson.success === true) { - alert.info("Successfully enabled 2fa") - - setTimeout(() => { - getUsers() - - 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 - } - - 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("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}/stop`, { - method: 'GET', - credentials: "include", - }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for apps :O!") - return - } - - 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) { - 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." + ); + } - 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) - } - }) - .catch(error => { - console.log(error) - }); - } + 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"; - const generateApikey = (user) => { - const userId = user.id - const data = { "user_id": userId } + 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()); + }); + }; - 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() - } + const editAuthenticationConfig = (id) => { + const data = { + id: id, + action: "assign_everywhere", + }; + const url = globalUrl + "/api/v1/apps/authentication/" + id + "/config"; - 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) - }); - } + 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 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) - }} - /> -
- ) - })} -
- - - - -
- : null + 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); + } - 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) - }} - /> - -
- } - {isCloud ? - null - : -
- setNewPassword(e.target.value)} - /> - -
- } - -
- - - -
- {show2faSetup && isCloud ? -
- {/**/} + setOrgName(""); + setModalOpen(false); + }) + ) + .catch((error) => { + alert.error("Err: " + error.toString()); + }); + }; - {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 ? - {"2 - : - - } + const onPasswordChange = () => { + const data = { username: selectedUser.username, newpassword: newPassword }; + const url = globalUrl + "/api/v1/users/passwordchange"; - - After scanning the QR code image, the app will display a code that you can enter below. - -
- { - if (event.target.value.length > 6) { - return - } + 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()); + }); + }; - setValue2FA(event.target.value) - }} - /> - -
-
- : null} -
-
+ const deleteUser = (data) => { + // Just use this one? + const userId = data.id; - const GridItem = (props) => { - const [expanded, setExpanded] = React.useState(false) + 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(); + } - const primary = props.data.primary - const secondary = props.data.secondary - const primaryIcon = props.data.icon - const secondaryIcon = props.data.active ? - - : - + 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); + } + }) - 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} -
-
- ) - } + .catch((error) => { + console.log("Error in userdata: ", error); + }); + }; - const itemColor = "black" - var syncList = [ - { - "primary": "Workflows", - "secondary": "", - "active": true, - "icon": , - }, - { - "primary": "Apps", - "secondary": "", - "active": true, - "icon": , - }, - { - "primary": "Organization", - "secondary": "", - "active": true, - "icon": , - }, - ] + 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; + } - 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) - }} - /> - -
- {orgSyncResponse.length > 0 ? - - Error: {orgSyncResponse} - - : null - } + // 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) { + } - - {syncList.map((data, index) => { - return ( - - ) - })} - + 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: [], + }, + }; - * 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 - -
+ // FIXME: Set up features + //Object.keys(responseJson.sync_features).map(function(key, index) { + // //console.log(responseJson.sync_features[key]) + //}) - const cancelSubscriptions = (subscription_id) => { - console.log(selectedOrganization) - const orgId = selectedOrganization.id - const data = { - "subscription_id": subscription_id, - "action": "cancel", - "org_id": selectedOrganization.id, - } + //setOrgName(responseJson.name) + //setOrgDescription(responseJson.description) + setOrganizationFeatures(lists); + } + }) + .catch((error) => { + console.log("Error getting org: ", error); + alert.error("Error getting current organization"); + }); + }; - 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") - } + const inviteUser = (data) => { + console.log("INPUT: ", data); + setLoginInfo(""); - handleGetOrg(selectedOrganization.id) - return response.json(); - }).then(function(responseJson) { - if (responseJson.success !== undefined && responseJson.success) { - alert.success("Successfully stopped subscription!") + // 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); + }); + }; - } 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 submitUser = (data) => { + console.log("INPUT: ", data); + setLoginInfo(""); - 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 - } + // Just use this one? + var data = { username: data.Username, password: data.Password }; + var baseurl = globalUrl; + const url = baseurl + "/api/v1/users/register"; - navigator.clipboard.writeText(org_id) - copyText.select(); - copyText.setSelectionRange(0, 99999); /* For mobile devices */ + 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); + }); + }; - /* Copy the text inside the text field */ - document.execCommand("copy"); + // 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; + } - 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. + environments[key].default = true; + } else if ( + environments[key].default == true && + environments[key].id !== environment.id + ) { + environments[key].default = false; + } - {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 ? - - : null} -
-
-
- : -
-
- { - setCloudSyncApikey(event.target.value) - }} - /> - -
- {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 - } + newEnv.push(environments[key]); + } - 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": , - } + // 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); + }); + }; - 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()}
- -
- : -
- Cancelled: {new Date(sub.cancellationdate*1000).toISOString()}
- - Status: Deactivated - -
- } - - - ) - })} - - -
- : null - } -
- } + 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); + }); + }; -
- -
-
- : null + const abortEnvironmentWorkflows = (environment) => { + console.log("Aborting all workflows started >10 minutes ago, not finished"); - 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} -
- - - - -
+ fetch(`${globalUrl}/api/v1/environments/${environment}/stop`, { + method: "GET", + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for apps :O!"); + return; + } - const usersView = curTab === 1 ? -
-
-

User management

- Add, edit, block or change passwords. Learn more -
-
- - - - - - - - - - - - - - - - {users === undefined || users === null ? null : users.map((data, index) => { - var bgColor = "#27292d" - if (index % 2 === 0) { - bgColor = "#1f2023" - } + return response.json(); + }) + .then((responseJson) => { + console.log("Got response for execution: ", responseJson); + //console.log("RESPONSE: ", responseJson) + //setFiles(responseJson) + }) + .catch((error) => { + //alert.error(error.toString()) + }); + }; - 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 - } + const deleteEnvironment = (environment) => { + // FIXME - add some check here ROFL + //const name = environment.name - navigator.clipboard.writeText(data.apikey) - copyText.select(); - copyText.setSelectionRange(0, 99999); /* For mobile devices */ + //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 + // } - /* Copy the text inside the text field */ - document.execCommand("copy"); + // if (environments[key].type === "cloud" && !environments[key].archived) { + // alert.error("Can't modify cloud environments") + // return + // } - 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) - }} - > - - - {/* + + + + ) : 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); + }} + /> + +
+ )} + {isCloud ? null : ( +
+ setNewPassword(e.target.value)} + /> + +
+ )} + +
+ + + +
+ {show2faSetup && isCloud ? ( +
+ {/**/} + + {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 ? ( + {"2 + ) : ( + + )} + + + 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); + }} + /> + +
+
+ ) : 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); + }} + /> + +
+ {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 ? ( + + ) : null} +
+
+
+ ) : ( +
+
+ { + setCloudSyncApikey(event.target.value); + }} + /> + +
+ {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()} +
+ +
+ ) : ( +
+ 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} +
+ + + + +
+ ); + + const usersView = + curTab === 1 ? ( +
+
+

User management

+ + Add, edit, block or change passwords.{" "} + + Learn more + + +
+
+ + + + + + + + + + + + + + + + {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", + }} + > + + Admin + + + User + + + } + style={{ minWidth: 135, maxWidth: 135, marginRight: 15 }} + /> + + + + + { + setSelectedUserModalOpen(true); + setSelectedUser(data); + }} + > + + + {/**/} - - - ) - })} - -
- : null + + + ); + })} + +
+ ) : null; - const run2FASetup = (data) => { - console.log("2fa: ", data) - if (!show2faSetup) { - get2faCode(data.id) - } else { - // Should remove? - } + const run2FASetup = (data) => { + console.log("2fa: ", data); + if (!show2faSetup) { + get2faCode(data.id); + } else { + // Should remove? + } - setShow2faSetup(!show2faSetup) - } + setShow2faSetup(!show2faSetup); + }; - 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 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) - } + if (typeof files[key] === "object") { + handleCreateFile(filename, filedata); + } - /* + /* reader.addEventListener('load', (e) => { var data = e.target.result; setIsDropzone(false) @@ -2390,180 +2853,295 @@ 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}> -
-
-

Files

- Files from Workflows. Learn more -
- - 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) - }} /> - + const filesView = + curTab === 3 ? ( + 1366 ? 1366 : 1200, + margin: "auto", + padding: 20, + }} + onDrop={uploadFile} + > +
+
+

Files

+ + Files from Workflows.{" "} + + Learn more + + +
+ + (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); + }} + /> + - {fileNamespaces !== undefined && fileNamespaces !== null && fileNamespaces.length > 1 ? - - Namespace - - - : null} + {fileNamespaces !== undefined && + fileNamespaces !== null && + fileNamespaces.length > 1 ? ( + + Namespace + + + ) : 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 ( - - - - - - - : - - - - - - - - - - } - style={{minWidth: 100, maxWidth: 100, overflow: "hidden"}} - /> - - - - - - { - downloadFile(file) - }}> - - - - - style={{minWidth: 75, maxWidth: 75, overflow: "hidden"}} - /> - {/* + return ( + + + + + + + ) : ( + + + + + + + + + + ) + } + style={{ + minWidth: 100, + maxWidth: 100, + overflow: "hidden", + }} + /> + + + + + + { + downloadFile(file); + }} + > + + + + + style={{ + minWidth: 75, + maxWidth: 75, + overflow: "hidden", + }} + /> + {/* */} - { - 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 - } + { + 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 */ + navigator.clipboard.writeText(file.id); + copyText.select(); + copyText.setSelectionRange( + 0, + 99999 + ); /* For mobile devices */ - /* Copy the text inside the text field */ - document.execCommand("copy"); + /* Copy the text inside the text field */ + document.execCommand("copy"); - alert.info(file.id + " copied to clipboard") - } - }}> - - - /> - - ) - })} - -
-
- : null + alert.info(file.id + " copied to clipboard"); + } + }} + > + + + /> + + ); + })} + +
+
+ ) : 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" - } + 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 ( - - {schedule.seconds} seconds} - /> - - {schedule.workflow_id}} - /> - - - - - - ) - })} - -
- : null + return ( + + {schedule.seconds} seconds + ) + } + /> + + + {schedule.workflow_id} + + } + /> + + + + + + ); + })} +
+
+ ) : 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 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; + } - return ( - - - - - - - - - - ) - })} - -
- : null + return ( + + + + + + + + + + ); + })} +
+
+ ) : 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 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" - } + 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); + }} + > + + + + ) : ( + + {}} + > + + + + )} + { + deleteAuthentication(data); + }} + > + + + + + ); + })} + +
+ ) : null; - return ( - - - style={{minWidth: 75, maxWidth: 75}} - /> - - - - - - {return data.key}).join(", ")} - style={{minWidth: 200, maxWidth: 200, overflow: "hidden"}} - /> - - { - updateAppAuthentication(data) - }} - > - - - {data.defined ? - - { - editAuthenticationConfig(data.id) - }} - > - - - - : - - { - }} - > - - - - } - { - deleteAuthentication(data) - }} - > - - - - - ) - })} -
-
- : null + const environmentView = + curTab === 5 ? ( +
+
+

Environments

+ + Decides what Orborus environment to execute an action in a workflow + in.{" "} + + Learn more + + +
+ + + { + setShowArchived(!showArchived); + }} + />{" "} + Show archived + + + + + + + + + + + + {environments === undefined || environments === null + ? null + : environments.map((environment, index) => { + if (!showArchived && environment.archived) { + return null; + } - const environmentView = curTab === 5 ? -
-
-

Environments

- Decides what Orborus environment to execute an action in a workflow in. Learn more -
- - - {setShowArchived(!showArchived)}} /> Show archived - - - - - - - - - - - - {environments === undefined || environments === null ? null : environments.map((environment, index)=> { - if (!showArchived && environment.archived) { - return null - } + if (environment.archived === undefined) { + getEnvironments(); + return null; + } - if (environment.archived === undefined) { - getEnvironments() - return null - } + var bgColor = "#27292d"; + if (index % 2 === 0) { + bgColor = "#1f2023"; + } - var bgColor = "#27292d" - if (index % 2 === 0) { - bgColor = "#1f2023" - } - - return ( - - - - - - {environment.default ? - null - : - - } - - -
- - {/* + )} + + +
+ + {/**/} - {/**/} -
-
- - - - ) - })} - -
- : null + {/**/} +
+ + + + + ); + })} +
+
+ ) : null; - const organizationsTab = curTab === 6 ? -
-
-

Organizations

- Global admin: control organizations -
- - - - - - - - - - - - {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" + const organizationsTab = + curTab === 6 ? ( +
+
+

Organizations

+ + Global admin: control organizations + +
+ + + + + + + + + + + + {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"; - const imagesize = 40 - const imageStyle = {width: imagesize, height: imagesize, pointerEvents: "none", } - const image = data.image === "" ? - {data.name} - : - {data.name} + const imagesize = 40; + const imageStyle = { + width: imagesize, + height: imagesize, + pointerEvents: "none", + }; + const image = + data.image === "" ? ( + {data.name} + ) : ( + {data.name} + ); - var bgColor = "#27292d" - if (index % 2 === 0) { - bgColor = "#1f2023" - } + var bgColor = "#27292d"; + if (index % 2 === 0) { + bgColor = "#1f2023"; + } - return ( - - - - - - - { - setCloudSyncModalOpen(true) - setSelectedOrganization(data) - console.log("INVERT CLOUD SYNC") - }} /> - style={{minWidth: 150, maxWidth: 150}} - /> - - ) - })} - - : null} - -
- : null + return ( + + + + + + + { + 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 + const hybridTab = + curTab === 7 ? ( +
+
+

Hybrid

+ +
+ + + + + + + + + + + { + console.log("INVERT"); + }} + /> + style={{ minWidth: 150, maxWidth: 150 }} + /> + + +
+ ) : null; - // primary={environment.Registered ? "true" : "false"} + // primary={environment.Registered ? "true" : "false"} - + 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} +
+
+
+ ); - 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} + +
+ ); +}; - 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}

- Username -
- -
- Password -
- -
-
- - -
-
- {loginInfo} -
-
-
-
+ formtitle = "Create administrator account"; - const loadedCheck = isLoaded ? -
- {basedata} -
- : -
-
+ const basedata = ( +
+ +
+

{formtitle}

+ Username +
+ +
+ Password +
+ +
+
+ +
+
{loginInfo}
+
+
+
+ ); - 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 0e3c8180..f582d3e4 100644 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -1,98 +1,99 @@ -import React, { 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 { useInterval } from "react-powerhooks"; +import { 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 { Link } from "react-router-dom"; +import { Prompt } from "react-router"; +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 ReactMarkdown from "react-markdown"; +import { + TextField, + Drawer, + Button, + Paper, + Grid, + Tabs, + InputAdornment, + Tab, + ButtonBase, + Tooltip, + Select, + MenuItem, + Divider, + Dialog, + DialogActions, + DialogTitle, + InputLabel, + DialogContent, + FormControl, + IconButton, + Menu, + Input, + FormGroup, + FormControlLabel, + Typography, + Checkbox, + Breadcrumbs, + CircularProgress, + Switch, +} from "@material-ui/core"; -import {TextField, - Drawer, - Button, - Paper, - Grid, - Tabs, - InputAdornment, - Tab, - ButtonBase, - Tooltip, - Select, - MenuItem, - Divider, - Dialog, - DialogActions, - DialogTitle, - InputLabel, - DialogContent, - FormControl, - IconButton, - Menu, - Input, - FormGroup, - FormControlLabel, - Typography, - Checkbox, - Breadcrumbs, - CircularProgress, - Switch, - } from '@material-ui/core'; - -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, -} from '@material-ui/icons'; +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, +} 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 * 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 cytoscapestyle from "../defaultCytoscapeStyle"; +import cxtmenu from "cytoscape-cxtmenu"; import { useAlert } from "react-alert"; 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 ParsedAction from "../components/ParsedAction.jsx"; -const surfaceColor = "#27292D" -const inputColor = "#383B40" +const surfaceColor = "#27292D"; +const inputColor = "#383B40"; // http://apps.cytoscape.org/apps/yfileslayoutalgorithms cytoscape.use(edgehandles); @@ -104,1301 +105,1464 @@ 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)) - }) + 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 - } + if (sourceURL === undefined) { + return; + } - var rtn = sourceURL.split("?")[0], - param, - params_arr = [], - queryString = (sourceURL.indexOf("?") !== -1) ? sourceURL.split("?")[1] : ""; + 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 (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 "" - } + if (rtn === "?") { + return ""; + } - return rtn; + return rtn; } -const splitter = "|~|" -const svgSize = 24 +const splitter = "|~|"; +const svgSize = 24; //const referenceUrl = "https://shuffler.io/functions/webhooks/" //const referenceUrl = window.location.origin+"/api/v1/hooks/" 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" + const referenceUrl = globalUrl + "/api/v1/hooks/"; + const alert = useAlert(); + const theme = useTheme(); + const green = "#86c142"; + const yellow = "#FECC00"; - const [bodyWidth, bodyHeight] = useWindowSize(); + const [bodyWidth, bodyHeight] = useWindowSize(); - 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) + var to_be_copied = ""; + const [cystyle] = useState(cytoscapestyle); + const [cy, setCy] = React.useState(); - 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: "", - }) + 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 [history, setHistory] = React.useState([]) - const [historyIndex, setHistoryIndex] = React.useState(history.length) + 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: "", + }); - 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 [history, setHistory] = React.useState([]); + const [historyIndex, setHistoryIndex] = React.useState(history.length); - const curpath = typeof window === 'undefined' || window.location === undefined ? "" : window.location.pathname + 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); - // 0 = normal, 1 = just done, 2 = normal - const [savingState, setSavingState] = React.useState(0) + const curpath = + typeof window === "undefined" || window.location === undefined + ? "" + : window.location.pathname; - const [selectedResult, setSelectedResult] = React.useState({}) - const [codeModalOpen, setCodeModalOpen] = React.useState(false); + // 0 = normal, 1 = just done, 2 = normal + const [savingState, setSavingState] = React.useState(0); - const [variableAnchorEl, setVariableAnchorEl] = React.useState(null) + const [selectedResult, setSelectedResult] = React.useState({}); + const [codeModalOpen, setCodeModalOpen] = React.useState(false); - 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 [variableAnchorEl, setVariableAnchorEl] = React.useState(null); - // 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({}); + 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, + }); + // 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({}); - const [visited, setVisited] = React.useState([]); - - const [apps, setApps] = React.useState([]); - const [filteredApps, setFilteredApps] = React.useState([]); - const [prioritizedApps, setPrioritizedApps] = React.useState([]); - const [firstrequest, setFirstrequest] = React.useState(true) - - const [environments, setEnvironments] = React.useState([]); - const [established, setEstablished] = React.useState(false); + const [visited, setVisited] = React.useState([]); - const [graphSetup, setGraphSetup] = React.useState(false); + const [apps, setApps] = React.useState([]); + const [filteredApps, setFilteredApps] = React.useState([]); + const [prioritizedApps, setPrioritizedApps] = React.useState([]); + const [firstrequest, setFirstrequest] = React.useState(true); - const [selectedApp, setSelectedApp] = React.useState({}); - const [selectedAction, setSelectedAction] = React.useState({}); - const [selectedActionEnvironment, setSelectedActionEnvironment] = React.useState({}); + const [environments, setEnvironments] = React.useState([]); + const [established, setEstablished] = React.useState(false); - const [executionRequest, setExecutionRequest] = React.useState({}) + const [graphSetup, setGraphSetup] = React.useState(false); - const [executionRunning, setExecutionRunning] = React.useState(false) - const [executionModalOpen, setExecutionModalOpen] = React.useState(false) - const [executionModalView, setExecutionModalView] = React.useState(0) - const [executionData, setExecutionData] = React.useState({}) + const [selectedApp, setSelectedApp] = React.useState({}); + const [selectedAction, setSelectedAction] = React.useState({}); + const [selectedActionEnvironment, setSelectedActionEnvironment] = + React.useState({}); - const [lastSaved, setLastSaved] = React.useState(true) + const [executionRequest, setExecutionRequest] = React.useState({}); - // eslint-disable-next-line no-unused-vars - const [_, setUpdate] = useState(""); // Used for rendring, don't remove + const [executionRunning, setExecutionRunning] = React.useState(false); + const [executionModalOpen, setExecutionModalOpen] = React.useState(false); + const [executionModalView, setExecutionModalView] = React.useState(0); + const [executionData, setExecutionData] = React.useState({}); - const [workflowExecutions, setWorkflowExecutions] = React.useState([]); - const [defaultEnvironmentIndex, setDefaultEnvironmentIndex] = React.useState(0) + const [lastSaved, setLastSaved] = React.useState(true); - // 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)?' + // eslint-disable-next-line no-unused-vars + const [_, setUpdate] = useState(""); // Used for rendring, don't remove - useBeforeunload(() => { - if (!lastSaved) { - return unloadText - } - }) + const [workflowExecutions, setWorkflowExecutions] = React.useState([]); + const [defaultEnvironmentIndex, setDefaultEnvironmentIndex] = + React.useState(0); - 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() - } - }) + // 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 getAvailableWorkflows = (trigger_index) => { - fetch(globalUrl+"/api/v1/workflows", { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - credentials: "include", + useBeforeunload(() => { + if (!lastSaved) { + return unloadText; + } + }); + + 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) { - 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) - } else { - const sub = responseJson.find(data => data.id === param.value) - if (sub !== undefined && subworkflow.id !== sub.id) { - setSubworkflow(sub) - } - } - } + // Sets up subflow trigger with the right info + if (trigger_index > -1) { + 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); + } else { + const sub = responseJson.find( + (data) => data.id === param.value + ); + if (sub !== undefined && subworkflow.id !== sub.id) { + setSubworkflow(sub); + } + } + } - console.log("PARAM: ", param) - if (param.name === "startnode" && param.value !== undefined && param.value !== null) { - setSubworkflowStartnode(param.value) - } - } - } - } - } + console.log("PARAM: ", param); + if ( + param.name === "startnode" && + param.value !== undefined && + param.value !== null + ) { + setSubworkflowStartnode(param.value); + } + } + } + } + } + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; + + function OuterLink(props) { + if (props.href.includes("http") || props.href.includes("mailto")) { + return ( + + {props.children} + + ); + } + return ( + + {props.children} + + ); + } + + function Img(props) { + return {props.alt}; + } + + 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", }) - .catch(error => { - alert.error(error.toString()) - }); - } + .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); + }); + }; - function OuterLink(props) { - if (props.href.includes("http") || props.href.includes("mailto")) { - return {props.children} - } - return {props.children} - } - - function Img(props) { - return {props.alt} - } - - 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) + const getSettings = () => { + fetch(globalUrl + "/api/v1/getsettings", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", }) - .catch(error => { - console.log(error) - }); - } + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for get settings :O!"); + } - 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.apikey === undefined || + responseJson.apikey.length === 0 || + responseJson.apikey === null + ) { + generateApikey(); + } + setUserSettings(responseJson); + }) + .catch((error) => { + console.log(error); + }); + }; - return response.json() - }) - .then((responseJson) => { - if (responseJson.apikey === undefined || responseJson.apikey.length === 0 || responseJson.apikey === null) { - generateApikey() - } - setUserSettings(responseJson) + 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 => { - 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", - }) - .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!") - } - - return response.json() - }) - .then((responseJson) => { - if (responseJson.length > 0) { - // FIXME: Sort this by time - - // - means it's opposite - const newkeys = sortByKey(responseJson, "-started_at") - setWorkflowExecutions(newkeys) - - 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) - } - } - } - - }) - .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) => { - 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 - - 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 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") { - 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 = [] - } - - var curworkflowComment = useworkflow.comments.find(a => a.id === cyelements[key].data()["id"]) - if (curworkflowComment === undefined) { - curworkflowComment = cyelements[key].data() - } - - curworkflowComment.position = cyelements[key].position() - - 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 - - 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) { - 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) => { - 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", - ] - - 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 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 apps :O!") - } + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for WORKFLOW EXECUTION :O!"); + } - return response.json() - }) - .then((responseJson) => { - if (responseJson.success) { - var newauth = [] - for (var key in responseJson.data) { - if (responseJson.data[key].defined === false) { - continue - } + return response.json(); + }) + .then((responseJson) => { + if (responseJson.length > 0) { + // FIXME: Sort this by time - newauth.push(responseJson.data[key]) - } - + // - means it's opposite + const newkeys = sortByKey(responseJson, "-started_at"); + setWorkflowExecutions(newkeys); - if (cy !== undefined) { - console.log("NEW AUTH = reset cy's onnodeselect") + 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; + } - // 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)) - } + 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(); - setAppAuthentication(newauth) - setAuthLoaded(true) + setExecutionRequest({ + execution_id: execution.execution_id, + authorization: execution.authorization, + }); - if (updateAction === true) { - - if (selectedApp.authentication.required) { - // Setup auth here :) - var appUpdates = false - const authenticationOptions = [] + const newitem = removeParam("execution_id", cursearch); + props.history.push(curpath + newitem); + } + } + } + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; - 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", + 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 apps :O!") - } + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for stream results :O!"); + stop(); + } - 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) + return response.json(); + }) + .then((responseJson) => { + handleUpdateResults(responseJson, executionRequest); + }) + .catch((error) => { + console.log("Error: ", error); + stop(); + }); + }; - 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))) - } + 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 + + 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 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") { + 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 = []; + } + + var curworkflowComment = useworkflow.comments.find( + (a) => a.id === cyelements[key].data()["id"] + ); + if (curworkflowComment === undefined) { + curworkflowComment = cyelements[key].data(); + } + + curworkflowComment.position = cyelements[key].position(); + + 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; + + 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", }) - .catch(error => { - alert.error(error.toString()) - }); - } - - const getWorkflow = (workflow_id, sourcenode) => { - console.log(`Getting workflow ${workflow_id} with append value ${sourcenode}`) + .then((response) => { + setSavingState(0); + if (response.status !== 200) { + console.log("Status not 200 for setting workflows :O!"); + } - fetch(globalUrl+"/api/v1/workflows/"+workflow_id, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - credentials: "include", + 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) { + 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) => { + 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"]; + + 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 workflows :O!") - window.location.pathname = "/workflows" - } + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for apps :O!"); + } - return response.json() - }) - .then((responseJson) => { - // Not sure why this is necessary. - if (responseJson.isValid === undefined) { - responseJson.isValid = true - } + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success) { + var newauth = []; + for (var key in responseJson.data) { + if (responseJson.data[key].defined === false) { + continue; + } - if (responseJson.errors === undefined) { - responseJson.errors = [] - } + newauth.push(responseJson.data[key]); + } - if (responseJson.public) { - alert.info("This workflow is public. You will have to save it to make it your own!") - } + if (cy !== undefined) { + console.log("NEW AUTH = reset cy's onnodeselect"); - // Appends SUBFLOWS. Does NOT run during normal grabbing of workflows. - if (sourcenode.id !== undefined) { - console.log("WORKFLOW: ", responseJson) + // 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)); + } - 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", - } + setAppAuthentication(newauth); + setAuthLoaded(true); - // Set it dynamically? - node.position = { - x: sourcenode.position.x+action.position.x, - y: sourcenode.position.y+action.position.y, - } + if (updateAction === true) { + if (selectedApp.authentication.required) { + // Setup auth here :) + var appUpdates = false; + const authenticationOptions = []; - node.data = action + var tmpAuth = JSON.parse(JSON.stringify(responseJson.data)); + var latest = 0; + for (var key in tmpAuth) { + var item = tmpAuth[key]; - node.data._id = action["id"] - node.data.type = "ACTION" - node.data.source_workflow = responseJson.id - if (action.id === target_id) { - nodefound = true - } + const newfields = {}; + for (var filterkey in item.fields) { + newfields[item.fields[filterkey].key] = + item.fields[filterkey].value; + } - var example = "" - if (action.example !== undefined && action.example !== null && action.example.length > 0) { - example = action.example - } + item.fields = newfields; + if (item.app.name === selectedApp.name) { + authenticationOptions.push(item); - node.data.example = example - return node; - }) + // Always becoming the last one + if (item.edited > latest) { + latest = item.edited; + selectedAction.selectedAuthentication = item; - var edges = responseJson.branches.map((branch, index) => { - const edge = { }; - var conditions = responseJson.branches[index].conditions - if (conditions === undefined || conditions === null) { - conditions = [] - } + 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; + } + } + } + } + } - var label = "" - if (conditions.length === 1) { - label = conditions.length+" condition" - } else if (conditions.length > 1) { - label = conditions.length+" conditions" - } + selectedAction.authentication = authenticationOptions; + if ( + selectedAction.selectedAuthentication === null || + selectedAction.selectedAuthentication === undefined || + selectedAction.selectedAuthentication.length === "" + ) { + selectedAction.selectedAuthentication = {}; + } - const sourceFound = actions.findIndex(action => action.data.id === branch.source_id) - if (sourceFound < 0) { - return null - } + 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 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; - }) - - 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.on('add', 'node', (e) => onNodeAdded(e)) - cy.on('add', 'edge', (e) => onEdgeAdded(e)) - - } else { - setWorkflow(responseJson) - setWorkflowDone(true) - - // 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) - } - } - } + const getApps = () => { + fetch(globalUrl + "/api/v1/apps", { + 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 onUnselect = (event) => { - console.time("UNSELECT") + 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); - const nodedata = event.target.data() - if (nodedata.app_name === undefined && nodedata.source === undefined) { - return - } - event.target.removeClass("selected") + 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}` + ); - // Attempt at rewrite of name in other actions in following nodes. - // Should probably be done in the onBlur for the textfield instead - /* + 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!" + ); + } + + // 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; + }); + + 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.on("add", "node", (e) => onNodeAdded(e)); + cy.on("add", "edge", (e) => onEdgeAdded(e)); + } else { + setWorkflow(responseJson); + setWorkflowDone(true); + + // 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 onUnselect = (event) => { + console.time("UNSELECT"); + + const nodedata = event.target.data(); + if (nodedata.app_name === undefined && nodedata.source === undefined) { + return; + } + event.target.removeClass("selected"); + + // Attempt at rewrite of name in other actions in following nodes. + // Should probably be done in the onBlur for the textfield instead + /* if (event.target.data().type === "ACTION") { const nodeaction = event.target.data() const curaction = workflow.actions.find(a => a.id === nodeaction.id) @@ -1431,242 +1595,263 @@ const AngularWorkflow = (props) => { } */ - // 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({}) + // 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([]) + // setSelectedTriggerIndex(-1) + //setSelectedActionEnvironment({}) + setSelectedEdge({}); + //setTriggerAuthentication({}) + //setSelectedTriggerIndex(-1) + //setTriggerFolders([]) - // Can be used for right side view - setRightSideBarOpen(false) - setScrollConfig({ - top: 0, - left: 0, - selected: "", - }) - console.timeEnd("UNSELECT") - } + // Can be used for right side view + setRightSideBarOpen(false); + setScrollConfig({ + top: 0, + left: 0, + selected: "", + }); + console.timeEnd("UNSELECT"); + }; - const onEdgeSelect = (event) => { - setRightSideBarOpen(true) - setLastSaved(false) + const onEdgeSelect = (event) => { + 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) { */ - if (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 - } - } + if (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; + } + } - setSelectedEdgeIndex(workflow.branches.findIndex(data => data.id === event.target.data()["id"])) - setSelectedEdge(event.target.data()) - } + setSelectedEdgeIndex( + workflow.branches.findIndex( + (data) => data.id === event.target.data()["id"] + ) + ); + setSelectedEdge(event.target.data()); + } + setSelectedAction({}); + setSelectedTrigger({}); + }; - setSelectedAction({}) - setSelectedTrigger({}) - } + // Comparing locations between nodes and setting views + var styledElements = []; + var originalLocation = { + x: 0, + y: 0, + }; - // 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; + } - 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 - } + 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); + }); + } + }; - const workflow_id = nodedata.parameters.find(param => param.name === "workflow") - if (workflow.id === workflow_id.valu) { - return - } + const onNodeDragStop = (event, selectedAction) => { + const nodedata = event.target.data(); + if (nodedata.id === selectedAction.id) { + return; + } - 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) - }) + if (nodedata.finished === false) { + return; + } - } - } + 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 onNodeDragStop = (event, selectedAction) => { - const nodedata = event.target.data() - if (nodedata.id === selectedAction.id) { - return - } + originalLocation = { x: 0, y: 0 }; + } - if (nodedata.finished === false) { - return - } + const curElement = document.getElementById(styledElements[0]); + if (curElement !== null && curElement !== undefined) { + 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); - 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) - } + selectedAction.parameters[idsplit[2]].value = newValue; + paramname = selectedAction.parameters[idsplit[2]].name; + idnumber = idsplit[2]; + } + } - originalLocation = {x: 0, y: 0} - } + if (idnumber >= 0 && paramname.length > 0) { + const exampledata = GetExampleResult(nodedata); + const parsedname = paramname + .toLowerCase() + .trim() + .replaceAll("_", " "); - const curElement = document.getElementById(styledElements[0]) - if (curElement !== null && curElement !== undefined) { - 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] - } - } + const foundresult = GetParamMatch(parsedname, exampledata, ""); + if (foundresult.length > 0) { + console.log("FOUND RESULT: ", paramname, foundresult); + newValue = `${newValue}${foundresult}`; + } - if (idnumber >= 0 && paramname.length > 0) { - const exampledata = GetExampleResult(nodedata) - const parsedname = paramname.toLowerCase().trim().replaceAll("_", " ") + selectedAction.parameters[idnumber].value = newValue; + } - const foundresult = GetParamMatch(parsedname, exampledata, "") - if (foundresult.length > 0) { - console.log("FOUND RESULT: ", paramname, foundresult) - newValue = `${newValue}${foundresult}` - } + curElement.value = newValue; + } + } - selectedAction.parameters[idnumber].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; + } + } - curElement.value = newValue - } - } + // 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); - 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 - } - } + 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, + }, + }; - // 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) + cy.add(decoratorNode).unselectify(); + } else { + console.log("Node already exists - don't add descriptor node"); + } + } + originalLocation = { + x: 0, + y: 0, + }; + }; - 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 onNodeDrag = (event, selectedAction) => { + const nodedata = event.target.data(); + if (nodedata.finished === false) { + return; + } - cy.add(decoratorNode).unselectify() - } else { - console.log("Node already exists - don't add descriptor node") - } - } - originalLocation = { - x: 0, - y: 0, - } - } + 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(); + } + } + } else { + //console.log("No appid? ", nodedata) + } - const onNodeDrag = (event, selectedAction) => { - const nodedata = event.target.data() - if (nodedata.finished === false) { - return - } + if (nodedata.id === selectedAction.id) { + return; + } - 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() - } - } - } else { - //console.log("No appid? ", nodedata) - } - - 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({ @@ -1682,588 +1867,709 @@ const AngularWorkflow = (props) => { } */ - 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); - - } - - // Nodeselectbatching: - // https://stackoverflow.com/questions/16677856/cy-onselect-callback-only-once - const onNodeSelect = (event, newAppAuth) => { - const data = event.target.data() - if (data.isButton) { - if (data.buttonType === "delete") { - const parentNode = cy.getElementById(data.attachedTo) - if (parentNode !== null && parentNode !== undefined) { - parentNode.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) - } - } - - workflow.start = parentNode.data('id') - setLastSaved(false) - parentNode.data('isStartNode', true) - } - - } 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.v4() - 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 - } else if (data.isDescriptor) { - console.log("Can't select descriptor") - event.target.unselect() - return - } - - - if (data.type === "ACTION") { - var curaction = workflow.actions.find(a => a.id === data.id) - if (!curaction || curaction === undefined) { - 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))))) - 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], - } - - 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] - } - } - } - - 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") { - 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 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 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) { - exampledata = JSON.parse(foundResult.result) - break - } - } - } - - return exampledata - } - - const GetParamMatch = (paramname, exampledata, basekey) => { - 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 - } - } - - 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' - 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() - - 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() - } - } - - if (event.target.target().data("isButton") === true || event.target.target().data("isDescriptor") === true) { - 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) { - 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) - - // Find the targetnode and check if its a trigger - // FIXME - do this for both actions and other types? - /* + 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); + }; + + // Nodeselectbatching: + // https://stackoverflow.com/questions/16677856/cy-onselect-callback-only-once + const onNodeSelect = (event, newAppAuth) => { + const data = event.target.data(); + if (data.isButton) { + if (data.buttonType === "delete") { + const parentNode = cy.getElementById(data.attachedTo); + if (parentNode !== null && parentNode !== undefined) { + parentNode.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); + } + } + + workflow.start = parentNode.data("id"); + setLastSaved(false); + parentNode.data("isStartNode", true); + } + } 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.v4(); + 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; + } else if (data.isDescriptor) { + console.log("Can't select descriptor"); + event.target.unselect(); + return; + } + + if (data.type === "ACTION") { + var curaction = workflow.actions.find((a) => a.id === data.id); + if (!curaction || curaction === undefined) { + 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))) + ); + 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], + }; + + 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]; + } + } + } + + 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") { + 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 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 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) { + exampledata = JSON.parse(foundResult.result); + break; + } + } + } + + return exampledata; + }; + + const GetParamMatch = (paramname, exampledata, basekey) => { + 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; + } + } + + 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' + 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(); + + 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(); + } + } + + if ( + event.target.target().data("isButton") === true || + event.target.target().data("isDescriptor") === true + ) { + 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) { + 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) + // 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) @@ -2276,336 +2582,358 @@ 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, + }; - // 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) - } + if (!found) { + newbranch["hasErrors"] = false; - var newbranch = { - "source_id": edge.source, - "destination_id": edge.target, - "id_": edge.id, - "id": edge.id, - "hasErrors": false, - "decorator": false, - } + workflow.branches.push(newbranch); + setWorkflow(workflow); + } - if (!found) { - newbranch["hasErrors"] = false + history.push({ + type: "edge", + action: "added", + data: edge, + }); + setHistory(history); + setHistoryIndex(history.length); + }; - workflow.branches.push(newbranch) - setWorkflow(workflow) - } + const onNodeAdded = (event) => { + setLastSaved(false); + const node = event.target; + const nodedata = event.target.data(); + if ( + nodedata.finished === false || + (nodedata.id !== undefined && nodedata.is_valid === undefined) + ) { + return; + } - history.push({ - "type": "edge", - "action": "added", - "data": edge, - }) - setHistory(history) - setHistoryIndex(history.length) - } + //console.log("IS IT ADDED TO THE WORKFLOW?: ", nodedata) + if (node.isNode() && cy.nodes().size() === 1) { + workflow.start = node.data("id"); + nodedata.isStartNode = true; + } else { + if (workflow.actions === null) { + return; + } - const onNodeAdded = (event) => { - setLastSaved(false) - const node = event.target - const nodedata = event.target.data() - if (nodedata.finished === false || (nodedata.id !== undefined && nodedata.is_valid === undefined)) { - return - } + // Remove bad startnode + for (var key in workflow.actions) { + const action = workflow.actions[key]; + if (action.isStartNode && workflow.start !== action.id) { + action.isStartNode = false; + } + } + } - //console.log("IS IT ADDED TO THE WORKFLOW?: ", nodedata) - if (node.isNode() && cy.nodes().size() === 1) { - workflow.start = node.data('id') - nodedata.isStartNode = true - } else { - if (workflow.actions === null) { - return - } + if (nodedata.type === "ACTION") { + 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, + }; - // Remove bad startnode - for (var key in workflow.actions) { - const action = workflow.actions[key] - if (action.isStartNode && workflow.start !== action.id) { - action.isStartNode = false - } - } - } + const edgeToBeAdded = { + group: "edges", + data: newcybranch, + }; - if (nodedata.type === "ACTION") { + 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 (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.parameters !== undefined && + nodedata.parameters !== null && + !nodedata.label.endsWith("_copy") + ) { + var newparameters = []; - const edgeToBeAdded = { - group: "edges", - data: newcybranch, - } + for (var subkey in nodedata.parameters) { + var newparam = JSON.parse( + JSON.stringify(nodedata.parameters[subkey]) + ); + newparam.id = uuidv4(); + newparam.value = ""; + newparameters.push(newparam); + } - 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 - } - } + nodedata.parameters = newparameters; + } - 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) - } + if (workflow.actions === undefined || workflow.actions === null) { + workflow.actions = [nodedata]; + } else { + workflow.actions.push(nodedata); + } - nodedata.parameters = newparameters - } + 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.actions === undefined || workflow.actions === null) { - workflow.actions = [nodedata] - } else { - workflow.actions.push(nodedata) - } + if (workflow.triggers === undefined) { + workflow.triggers = [nodedata]; + } else { + workflow.triggers.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 - } + 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, + }; - if (workflow.triggers === undefined) { - workflow.triggers = [nodedata] - } else { - workflow.triggers.push(nodedata) - } + const edgeToBeAdded = { + group: "edges", + data: newcybranch, + }; - 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, - } + if ( + nodedata.name !== "User Input" && + nodedata.name !== "Shuffle Workflow" + ) { + if ( + workflow.actions !== undefined && + workflow.actions !== null && + workflow.actions.length > 0 + ) { + cy.add(edgeToBeAdded); + } + } - const edgeToBeAdded = { - group: "edges", - data: newcybranch, - } + setWorkflow(workflow); + } - if (nodedata.name !== "User Input" && nodedata.name !== "Shuffle Workflow") { - if (workflow.actions !== undefined && workflow.actions !== null && workflow.actions.length > 0) { - cy.add(edgeToBeAdded) - } - } + 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; + workflow.branches = workflow.branches.filter( + (a) => a.id !== edge.data().id + ); + setWorkflow(workflow); + event.target.remove(); - setWorkflow(workflow) - } + // 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().source, + }); + setHistory(history); + setHistoryIndex(history.length); + } + }; - if (nodedata.app_name !== undefined) { - history.push({ - "type": "node", - "action": "added", - "data": nodedata, - }) - setHistory(history) - setHistoryIndex(history.length) - } + const onNodeRemoved = (event) => { + const node = event.target; + const data = node.data(); - } + if (data.finished === false) { + return; + } - const onEdgeRemoved = (event) => { - setLastSaved(false) - const edge = event.target + 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(); + } + } + } + } - workflow.branches = workflow.branches.filter(a => a.id !== edge.data().id) - setWorkflow(workflow) - event.target.remove() + 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(); + } + } - // 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") + history.push({ + type: "node", + action: "removed", + data: data, + }); + setHistory(history); + setHistoryIndex(history.length); + } - } + setWorkflow(workflow); + if (data.type === "TRIGGER") { + saveWorkflow(workflow); + } + }; - if (edge.data().source !== undefined) { - history.push({ - "type": "edge", - "action": "removed", - "data": edge.data().source, - }) - setHistory(history) - setHistoryIndex(history.length) - } - } + //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"); + } - const onNodeRemoved = (event) => { - const node = event.target - const data = node.data() + break; + case 67: + console.log(event); + 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 (data.finished === false) { - 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; + } - 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, - }) - 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: - console.log(event) - 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 - } - } - - 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} elements`) - } - } - } - } - break; - case 86: - if (event.ctrlKey) { - //console.log("CTRL+V") - - // The below parts are handled in the function handlePaste() - /* + navigator.clipboard.writeText(JSON.stringify(cydata)); + copyText.select(); + copyText.setSelectionRange(0, 99999); /* For mobile devices */ + document.execCommand("copy"); + alert.success(`Copied ${cydata.length} elements`); + } + } + } + } + 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)") @@ -2621,612 +2949,676 @@ const AngularWorkflow = (props) => { var jsonvalid = true var parsedjson = [] */ - } - break; - case 88: - if (event.ctrlKey) { - console.log("CTRL+X") - } - break; - case 83: + } + 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; + } - 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; - } + //previouskey = event.keyCode + }; - //previouskey = event.keyCode - } + const handlePaste = (event) => { + //console.log(event) + if ( + event.path !== undefined && + event.path !== null && + event.path.length > 0 + ) { + //console.log(event.path[0]) + if (event.path[0].localName !== "body") { + //console.log("Skipping because body is not targeted") + return; + } + } - const handlePaste = (event) => { - //console.log(event) - if (event.path !== undefined && event.path !== null && event.path.length > 0) { - //console.log(event.path[0]) - if (event.path[0].localName !== "body") { - //console.log("Skipping because body is not targeted") - return - } - } + 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) - 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) + for (var key in parsedjson) { + const item = parsedjson[key]; + console.log("Adding: ", item); - for (var key in parsedjson) { - const item = parsedjson[key] - console.log("Adding: ", item) + cy.add({ + group: item.group, + data: item.data, + position: item.position, + }); + } + } catch (e) { + console.log("Error pasting: ", e); + //alert.info("Failed parsing clipboard: ", e) + } + }; - 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 registerKeys = () => { + document.addEventListener("keydown", handleKeyDown); + document.addEventListener("paste", handlePaste); + }; - const registerKeys = () => { - document.addEventListener("keydown", handleKeyDown); - document.addEventListener('paste', handlePaste) - } - - 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!") - 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 - } - } - } - - 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) { - window.location.pathname = "/workflows/"+props.match.params.key - } - - // 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() - - 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 - if (elements.length === 0 && workflow.actions !== undefined && !graphSetup && Object.getOwnPropertyNames(workflow).length > 0) { - setGraphSetup(true) - setupGraph() - - } 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. - - - setEstablished(true) - // Validate if the node is just a node lol - cy.edgehandles({ - handleNodes: (el) => el.isNode() && !el.data("isButton") && !el.data("isDescriptor") && !el.data("type") === "COMMENT", - preview: false, - toggleOffOnLeave: false, - loopAllowed: function( node ){ - return false; - }, - }) - - 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') - }) - cy.on('boxend', (e) => { - console.log("END") - cy.removeListener('select') - }) - - 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)) - - document.title = "Workflow - "+workflow.name - registerKeys() - } - }) - - - 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 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 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) { - 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) - } - } - - - var parsedStyle = { - "border-width": "7px", - "border-opacity": ".7", - 'font-size': '25px', - } - - if (nodedata.type !== "COMMENT") { - parsedStyle.color = 'white' - } - - 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") - 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' - } 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. - /* + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for apps :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; + } + } + } + + 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 + ) { + window.location.pathname = "/workflows/" + props.match.params.key; + } + + // 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(); + + 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 + if ( + elements.length === 0 && + workflow.actions !== undefined && + !graphSetup && + Object.getOwnPropertyNames(workflow).length > 0 + ) { + setGraphSetup(true); + setupGraph(); + } 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. + + setEstablished(true); + // Validate if the node is just a node lol + cy.edgehandles({ + handleNodes: (el) => + el.isNode() && + !el.data("isButton") && + !el.data("isDescriptor") && + !el.data("type") === "COMMENT", + preview: false, + toggleOffOnLeave: false, + loopAllowed: function (node) { + return false; + }, + }); + + 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"); + }); + cy.on("boxend", (e) => { + console.log("END"); + cy.removeListener("select"); + }); + + 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)); + + document.title = "Workflow - " + workflow.name; + registerKeys(); + } + }); + + 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 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 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) { + 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); + } + } + + var parsedStyle = { + "border-width": "7px", + "border-opacity": ".7", + "font-size": "25px", + }; + + if (nodedata.type !== "COMMENT") { + parsedStyle.color = "white"; + } + + 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"); + 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"; + } 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. + /* //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) @@ -3249,2843 +3641,4025 @@ const AngularWorkflow = (props) => { } */ - 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) => { - 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", - 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} -
-
- - - - { - setOpen(false) - setAnchorEl(null) - }} - > - - { - setOpen(false) - setNewVariableName(variable.name) - setNewVariableDescription(variable.description) - setNewVariableValue(variable.value) - setVariablesModalOpen(true) - }} key={"Edit"}>{"Edit"} - { - deleteVariable(variable.name) - setOpen(false) - }} key={"Delete"}>{"Delete"} - -
-
- -
- ) - })} -
- -
- - 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} -
-
- - - - { - setOpen(false) - setAnchorEl(null) - }} - > - - { - setOpen(false) - setNewVariableName(variable.name) - setExecutionVariablesModalOpen(true) - }} key={"Edit"}>{"Edit"} - { - deleteExecutionVariable(variable.name) - setOpen(false) - }} key={"Delete"}>{"Delete"} - -
-
- -
- ) - })} -
- -
-
-
- ) - } - - 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,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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,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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/bAEMAAwICAwICAwMCAwMDAwMEBwUEBAQECQYHBQcKCQsL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', - "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.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 - } - - - 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, - } - - // 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) => { - 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 - } - } - } - - } 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.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.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 = 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} - - - - - {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 - }) - } - - 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 = "" - 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) - } 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) => { - 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 selectedTriggerChange = (event) => { - selectedTrigger.label = event.target.value - setSelectedTrigger(selectedTrigger) - } - - // 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 = [] - - // 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 (!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", - } - - - var rightsidebarStyle = { - position: "fixed", - top: appBarSize+25, - right: 25, - height: "80vh", - width: 365, - 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 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 - 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": ""}) - } - } - - 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 ( -
-
-
-
- {data.name} -
-
- {datafield} - {actionlist.length === 0 ? null : - - Autocomplete - - - } -
- ) - } - - - const menuItemStyle = { - color: "white", - backgroundColor: inputColor - } - - const conditionsModal = - { - setConditionsModalOpen(false) - setSourceValue({}) - setConditionValue({}) - setDestinationValue({}) - }} - > - - Conditions can't be used for loops [ .# ] Learn more - - - Condition - - - -
- - - - - -
- -
-
- - { - setVariableAnchorEl(null) - }} - > - { - conditionValue.value = "equals" - setConditionValue(conditionValue) - setVariableAnchorEl(null) - }} key={"equals"}>equals - { - conditionValue.value = "does not equal" - setConditionValue(conditionValue) - setVariableAnchorEl(null) - }} key={"does not equal"}>does not equal - { - conditionValue.value = "startswith" - setConditionValue(conditionValue) - setVariableAnchorEl(null) - }} key={"starts with"}>starts with - { - conditionValue.value = "endswith" - setConditionValue(conditionValue) - setVariableAnchorEl(null) - }} key={"ends with"}>ends with - { - conditionValue.value = "contains" - setConditionValue(conditionValue) - setVariableAnchorEl(null) - }} key={"contains"}>contains - { - conditionValue.value = "contains_any_of" - setConditionValue(conditionValue) - setVariableAnchorEl(null) - }} key={"contains_any_of"}>contains any of - { - conditionValue.value = "matches regex" - setConditionValue(conditionValue) - setVariableAnchorEl(null) - }} key={"matches regex"}>matches regex - { - conditionValue.value = "larger than" - setConditionValue(conditionValue) - setVariableAnchorEl(null) - }} key={"larger than"}>larger than - { - conditionValue.value = "less than" - setConditionValue(conditionValue) - setVariableAnchorEl(null) - }} key={"less than"}>less than - -
-
- -
- -
-
- - - -
-
- - 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} -
-
-
- - - - { - setOpen(false) - setAnchorEl(null) - }} - > - { - duplicateCondition(index) - }} key={"Duplicate"}>{"Duplicate"} - { - setOpen(false) - deleteCondition(index) - }} key={"Delete"}>{"Delete"} - -
-
- - ) - } - - 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( -
-
-
-

Branch: Conditions - {selectedEdgeIndex}

- What are conditions? -
-
- -
- Conditions -
- {injectedData} - - -
- ) - } - - // 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 : - - - const outlookButton = selectedTrigger.name !== "Office365" ? null : - - - - // 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 : - -
-
-
- Change auth -
-
- {outlookButton} - {gmailButton} - - } - - {triggerFolders === undefined || triggerFolders === null ? - null : - -
-
-
- Select folders -
-
- } - key={selectedTrigger} - > - {triggerFolders.map(folder => { - var folderItem = - - - if (folder.childFolderCount > 0) { - // Here to handle subfolders sometime later - folderItem = - - } - - return folderItem - })} - - - } -
- } else if (triggerAuthentication.type === "gmail") { - console.log("AUTH: ", triggerAuthentication) - triggerInfo = "SPECIAL GMAIL" - } - } - - - // Check - const argumentView = Object.getOwnPropertyNames(triggerAuthentication).length > 0 ? -
- {triggerInfo} -
- : -
-
-
-
- Login -
-
- {outlookButton} - {gmailButton} -
- - return( -
-
-
-

{selectedTrigger.app_name}: {selectedTrigger.status}

- What are email triggers? -
-
- -
- Name -
- - -
- Environment: - -
- - {argumentView} -
-
- -
- - -
-
-
-
- ) - } - - 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"}) - 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(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 - // 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 - } - - 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) { - exampledata = JSON.parse(foundResult.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) => { - if (values === undefined || values === null || values.length === 0) { - 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 - } - - 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 - } - } - - workflow.triggers[selectedTriggerIndex].parameters[1]["value_replace"] = paramcheck - setSelectedAction(selectedAction) - setUpdate(Math.random()) - - setShowDropdown(false) - setMenuPosition(null) - return - } - } - - setSelectedAction(selectedAction) - 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"} - - 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( -
-
-
-

{selectedTrigger.app_name}

- What are subflows? -
-
- -
- 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()) - }} - color="primary" - value="Wait for results" - /> - } - style={{marginTop: 10}} - label={
Wait for results
} - /> - -
-
- Parameters -
-
-
- Select a workflow to execute -
-
- {workflows === undefined || workflows === null || workflows.length === 0 ? null : - - } - {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 -
-
- - - } -
-
-
- 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} -
-
-
- API-key -
-
- { - workflow.triggers[selectedTriggerIndex].parameters[2].value = e.target.value - setWorkflow(workflow) - }} - /> -
-
-
- ) - } - - return null - } - - const CommentSidebar = () => { - if (Object.getOwnPropertyNames(selectedComment).length > 0) { - /* + 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) => { + 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", + 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} +
+
+ + + + { + setOpen(false); + setAnchorEl(null); + }} + > + { + setOpen(false); + setNewVariableName(variable.name); + setNewVariableDescription(variable.description); + setNewVariableValue(variable.value); + setVariablesModalOpen(true); + }} + key={"Edit"} + > + {"Edit"} + + { + deleteVariable(variable.name); + setOpen(false); + }} + key={"Delete"} + > + {"Delete"} + + +
+
+ +
+ ); + })} +
+ +
+ + 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} +
+
+ + + + { + setOpen(false); + setAnchorEl(null); + }} + > + { + setOpen(false); + setNewVariableName(variable.name); + setExecutionVariablesModalOpen(true); + }} + key={"Edit"} + > + {"Edit"} + + { + deleteExecutionVariable(variable.name); + setOpen(false); + }} + key={"Delete"} + > + {"Delete"} + + +
+
+ +
+ ); + })} +
+ +
+
+
+ ); + }; + + 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,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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,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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/bAEMAAwICAwICAwMCAwMDAwMEBwUEBAQECQYHBQcKCQsL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", + 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.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; + } + + 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, + }; + + // 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) => { + 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; + } + } + } + } 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.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.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 = 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} + + + + + {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; + }); + } + + 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 = ""; + 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); + } 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) => { + 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 selectedTriggerChange = (event) => { + selectedTrigger.label = event.target.value; + setSelectedTrigger(selectedTrigger); + }; + + // 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 = []; + + // 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 (!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", + }; + + var rightsidebarStyle = { + position: "fixed", + top: appBarSize + 25, + right: 25, + height: "80vh", + width: 365, + 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 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 + 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: "", + }); + } + } + + 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 ( +
+
+
+
+ {data.name} +
+
+ {datafield} + {actionlist.length === 0 ? null : ( + + + Autocomplete + + + + )} +
+ ); + }; + + const menuItemStyle = { + color: "white", + backgroundColor: inputColor, + }; + + const conditionsModal = ( + { + setConditionsModalOpen(false); + setSourceValue({}); + setConditionValue({}); + setDestinationValue({}); + }} + > + + Conditions can't be used for loops [ .# ]{" "} + + Learn more + + + + + Condition + + +
+ + + + + +
+ +
+
+ + { + setVariableAnchorEl(null); + }} + > + { + conditionValue.value = "equals"; + setConditionValue(conditionValue); + setVariableAnchorEl(null); + }} + key={"equals"} + > + equals + + { + conditionValue.value = "does not equal"; + setConditionValue(conditionValue); + setVariableAnchorEl(null); + }} + key={"does not equal"} + > + does not equal + + { + conditionValue.value = "startswith"; + setConditionValue(conditionValue); + setVariableAnchorEl(null); + }} + key={"starts with"} + > + starts with + + { + conditionValue.value = "endswith"; + setConditionValue(conditionValue); + setVariableAnchorEl(null); + }} + key={"ends with"} + > + ends with + + { + conditionValue.value = "contains"; + setConditionValue(conditionValue); + setVariableAnchorEl(null); + }} + key={"contains"} + > + contains + + { + conditionValue.value = "contains_any_of"; + setConditionValue(conditionValue); + setVariableAnchorEl(null); + }} + key={"contains_any_of"} + > + contains any of + + { + conditionValue.value = "matches regex"; + setConditionValue(conditionValue); + setVariableAnchorEl(null); + }} + key={"matches regex"} + > + matches regex + + { + conditionValue.value = "larger than"; + setConditionValue(conditionValue); + setVariableAnchorEl(null); + }} + key={"larger than"} + > + larger than + + { + conditionValue.value = "less than"; + setConditionValue(conditionValue); + setVariableAnchorEl(null); + }} + key={"less than"} + > + less than + + +
+
+ +
+
+
+ + + +
+
+ ); + + 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} +
+
+
+ + + + { + setOpen(false); + setAnchorEl(null); + }} + > + { + duplicateCondition(index); + }} + key={"Duplicate"} + > + {"Duplicate"} + + { + setOpen(false); + deleteCondition(index); + }} + key={"Delete"} + > + {"Delete"} + + +
+
+ + ); + }; + + 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 ( +
+
+
+

+ Branch: Conditions - {selectedEdgeIndex} +

+ + What are conditions? + +
+
+ +
Conditions
+ {injectedData} + + +
+ ); + }; + + // 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 : ( + + ); + + const outlookButton = + selectedTrigger.name !== "Office365" ? null : ( + + ); + + // 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 : ( + +
+
+
+ Change auth +
+
+ {outlookButton} + {gmailButton} + + )} + + {triggerFolders === undefined || triggerFolders === null ? null : ( + +
+
+
+ Select folders +
+
+ } + key={selectedTrigger} + > + {triggerFolders.map((folder) => { + var folderItem = ( + + ); + + if (folder.childFolderCount > 0) { + // Here to handle subfolders sometime later + folderItem = ( + + ); + } + + return folderItem; + })} + + + )} +
+ ); + } else if (triggerAuthentication.type === "gmail") { + console.log("AUTH: ", triggerAuthentication); + triggerInfo = "SPECIAL GMAIL"; + } + } + + // Check + const argumentView = + Object.getOwnPropertyNames(triggerAuthentication).length > 0 ? ( +
{triggerInfo}
+ ) : ( +
+
+
+
+ Login +
+
+ {outlookButton} + {gmailButton} +
+ ); + + return ( +
+
+
+

+ {selectedTrigger.app_name}: {selectedTrigger.status} +

+ + What are email triggers? + +
+
+ +
Name
+ + +
+ Environment: + +
+ + {argumentView} +
+
+ +
+ + +
+
+
+
+ ); + }; + + 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", + }); + 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(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; + // 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; + } + + 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) { + exampledata = JSON.parse(foundResult.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) => { + if (values === undefined || values === null || values.length === 0) { + 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; + } + + 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; + } + } + + workflow.triggers[selectedTriggerIndex].parameters[1][ + "value_replace" + ] = paramcheck; + setSelectedAction(selectedAction); + setUpdate(Math.random()); + + setShowDropdown(false); + setMenuPosition(null); + return; + } + } + + setSelectedAction(selectedAction); + 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", + }; + + 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 ( +
+
+
+

+ {selectedTrigger.app_name} +

+ + What are subflows? + +
+
+ +
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()); + }} + color="primary" + value="Wait for results" + /> + } + style={{ marginTop: 10 }} + label={
Wait for results
} + /> + +
+
+ Parameters +
+
+
+ Select a workflow to execute +
+
+ {workflows === undefined || + workflows === null || + workflows.length === 0 ? null : ( + + )} + {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 +
+
+ + + )} +
+
+
+ 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} +
+
+
+ API-key +
+
+ { + 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 } @@ -6110,2793 +7684,4240 @@ const AngularWorkflow = (props) => { const trigger_header_auth = workflow.triggers[selectedTriggerIndex].parameters.length > 2 ? workflow.triggers[selectedTriggerIndex].parameters[2].value : "" */ - return( -
-
-
-

Comment

- What are comments? -
-
- -
- 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) - }} - /> -
-
-
- ) - } - - return null - } - - const WebhookSidebar = () => { - 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": "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": ""} - 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) - } - } - - const trigger_header_auth = workflow.triggers[selectedTriggerIndex].parameters.length > 2 ? workflow.triggers[selectedTriggerIndex].parameters[2].value : "" - - return( -
-
-
-

{selectedTrigger.app_name}: {selectedTrigger.status}

- What are webhooks? -
-
- -
- Name -
- -
- - Environment - - -
- -
-
- Parameters -
-
-
- Webhook URI -
-
- { - 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 - } - - navigator.clipboard.writeText(copyText.value) - copyText.select() - copyText.setSelectionRange(0, 99999) /* For mobile devices */ - - /* 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 -
-
-
- {}} - InputProps={{ - style:{ - color: "white", - marginLeft: "5px", - maxWidth: "95%", - fontSize: "1em", - }, - }} - fullWidth - multiline - rows="4" - defaultValue={trigger_header_auth} - color="primary" - disabled={selectedTrigger.status === "running"} - placeholder={"AUTH_HEADER=AUTH_VALUE1"} - onBlur={(e) => { - const value = e.target.value - if (selectedTrigger.parameters === null) { - selectedTrigger.parameters = [] - } - - workflow.triggers[selectedTriggerIndex].parameters[2] = {"value": value, "name": "auth_headers"} - setWorkflow(workflow) - }} - /> -
- -
- - -
-
-
-
- ) - } - - 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 - } - - console.log("TRIG: ", trigger) - const data = { - "name": hookname, - "type": "webhook", - "id": trigger.id, - "workflow": workflow.id, - "start": startNode, - "environment": trigger.environment, - "auth": auth, - } - - 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"} - setWorkflow(workflow) - } - - return( -
-
-
-

{selectedTrigger.app_name}: {selectedTrigger.status}

- What is the user input trigger? -
-
- -
- Name -
- - -
- Environment: - -
- -
- Parameters -
-
-
- Information: -
-
- { - setTriggerTextInformationWrapper(e.target.value) - }} - /> -
-
-
- Contact options: -
-
- - { - 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 - } -
-
- ) - } - - 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 ? "*/15 * * * *" : "120"} - workflow.triggers[selectedTriggerIndex].parameters[1] = {"name": "execution_argument", "value": '{"example": {"json": "is cool"}}'} - setWorkflow(workflow) - } - - return( -
-
-
-

{selectedTrigger.app_name}: {selectedTrigger.status}

- What are schedules? -
-
- -
- Name -
- -
- - Environment - - -
- -
-
- Parameters -
-
-
- Interval (UTC) -
-
- { - setTriggerCronWrapper(e.target.value) - }} - /> -
-
-
- Execution argument: -
-
- { - setTriggerBodyWrapper(e.target.value) - }} - /> - -
- - -
-
-
-
- ) - } - - return null - } - - const cytoscapeViewWidths = 800 - const bottomBarStyle = { - position: "fixed", - right: 20, - left: leftBarSize, - bottom: 0, - minWidth: cytoscapeViewWidths, - maxWidth: cytoscapeViewWidths, - marginLeft: 20, - marginBottom: 20, - zIndex: 10, - } - - const topBarStyle= { - position: "fixed", - right: 0, - left: leftBarSize+20, - top: appBarSize+20, - - } - - const TopCytoscapeBar = (props) => { - return ( -
-
- - -

- - Workflows -

- -

- {workflow.name} -

- - {workflow.public ? -

- Public Workflow PREVIEW -

- : - null - } -
-
-
- ) - } - - const WorkflowMenu = () => { - const [newAnchor, setNewAnchor] = React.useState(null) - const [showShuffleMenu, setShowShuffleMenu] = React.useState(false) - - return ( -
- { - setShowShuffleMenu(false) - }} - > -
-

This menu is used to control the workflow itself.

- - - 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") - }} /> - } - /> -
- - - - - - -
- ) - } - - 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 = () => { - if (workflow.id === undefined || workflow.id === null || apps.length === 0) { - return null - } - - const boxSize = 100 - const executionButton = executionRunning ? - - - - - - : - - - - - - - return( -
- {executionButton} -
- {workflow.public ? null : - - { - setExecutionText(e.target.value) - }} - /> - - } - - - - - - {workflow.public ? - - - - - - : null} - - - - - - - - - - - - - - - - - - - - - - - - - - {workflow.configuration !== null && workflow.configuration !== undefined && workflow.configuration.exit_on_error !== undefined ? : null} -
-
- ) - } - - const addCommentNode = () => { - const newId = uuidv4() - const position = { - x: 300, - y: 300, - } - cy.add({ - group: "nodes", - data: { - id: newId, - label: "Your comment :)", - type: "COMMENT", - is_valid: true, - decorator: true, - width: 250, - height: 150, - position: position, - backgroundcolor: "#1f2023", - color: "#ffffff", - }, - position: position, - }) - } - - const RightSideBar = (props) => { - const {workflow, setWorkflow, setSelectedAction, setUpdate, - selectedApp, workflowExecutions, setSelectedResult, selectedAction, setSelectedApp, setSelectedTrigger, - setSelectedEdge, setCurrentView, cy, setAuthenticationModalOpen,setVariablesModalOpen, setCodeModalOpen, - selectedNameChange, rightsidebarStyle, showEnvironment, selectedActionEnvironment, environments, setNewSelectedAction, - appApiViewStyle, globalUrl, setSelectedActionEnvironment, requiresAuthentication, 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(selectedComment).length > 0) { - console.log("Returning comment field") - return( -
- -
- ) - } else if (Object.getOwnPropertyNames(selectedTrigger).length > 0) { - if (selectedTrigger.trigger_type === "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 leftView = leftViewOpen ? -
- -
- : -
-
{ - setLeftViewOpen(true) - setLeftBarSize(350) - }}> - - - -
-
- - 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 ( - { - handleReactJsonClipboard(copy) - }} - onSelect={(select) => { - HandleJsonCopy(showResult, select, "exec") - console.log("SELECTED!: ", select) - }} - name={"Execution Argument"} - /> - ) - } - - return ( -
-

Execution Argument

-
- {executionData.execution_argument} -
-
- ) - } - - - const getExecutionSourceImage = (execution) => { - // This is the playbutton at 150x150 - const defaultImage = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAACOCAMAAADkWgEmAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAWlBMVEX4Wj69TDgmKCvkVTwlJyskJiokJikkJSkjJSn4Ykf+6+f5h3L////8xLr5alH/9fT7nYz4Wz/919H5cVn/+vr8qpv4XUL94d35e2X//v38t6v4YUbkVDy8SzcVIzHLAAAAAWJLR0QMgbNRYwAAAAlwSFlzAAARsAAAEbAByCf1VAAAAAd0SU1FB+QGGgsvBZ/GkmwAAAFKSURBVHja7dlrTgMxDEXhFgpTiukL2vLc/zbZQH5N7MmReu4KPmlGN4m9WgGzfhgtaOZxM1rQztNoQDvPowHtTKMB7WxHA2TJkiVLlixIZMmSRYgsWbIIkSVLFiGyZMkiRNZirBcma/eKZEW87ZGsOBxPRFbE+R3Jio/LlciKuH0iWfH1/UNkRSR3RRYruSvyWKldkcjK7IpUVl5X5LLSuiKbldQV6aycrihgZXRFCau/K2pY3V1RxersijJWX1cUsnq6opLV0RW1rNldUc2a2RXlrHldsQBrTlfcLwv5EZm/PLIgkHXKPHyQRzXzYoO8BjIvzcgnBvJBxny+Ih/7zNEIcpDEHLshh5TIkS5zAI5cFzCXK8hVFHNxh1xzQpfC0BV6XWTJkkWILFmyCJElSxYhsmTJIkSWLFmEyJIlixBZsmQB8stk/U3/Yb49pVcDMg4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMjAtMDYtMjZUMTE6NDc6MDUrMDI6MDD8QCPmAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIwLTA2LTI2VDExOjQ3OjA1KzAyOjAwjR2bWgAAAABJRU5ErkJggg==" - const size = 40 - if (execution.execution_source === undefined || execution.execution_source === null || execution.execution_source.length === 0) { - return default - } - - if (execution.execution_source === "webhook") { - return {"webhook"} trigger.trigger_type === "WEBHOOK").large_image} style={{width: size, height: size}} /> - } else if (execution.execution_source === "outlook") { - return {"email"} trigger.trigger_type === "EMAIL").large_image} style={{width: size, height: size}} /> - } else if (execution.execution_source === "schedule") { - return {"schedule"} trigger.trigger_type === "SCHEDULE").large_image} style={{width: size, height: size}} /> - } else if (execution.execution_source === "EMAIL") { - return {"email"} trigger.trigger_type === "EMAIL").large_image} style={{width: size, height: size}} /> - } - - if (execution.execution_parent !== null && execution.execution_parent !== undefined && execution.execution_parent.length > 0) { - return {"parent trigger.trigger_type === "SUBFLOW").large_image} style={{width: size, height: size}} /> - } - - return ( - {execution.execution_source} - ) - } - - const handleReactJsonClipboard = (copy) => { - console.log("COPY: ", copy) - - const elementName = "copy_element_shuffle" - var copyText = document.getElementById(elementName); - if (copyText !== null && copyText !== undefined) { - if (copy.namespace !== undefined && copy.name !== undefined && copy.src !== undefined) { - copy = copy.src - } - - const clipboard = navigator.clipboard - if (clipboard === undefined) { - alert.error("Can only copy over HTTPS (port 3443)") - return - } - - var stringified = JSON.stringify(copy) - if (stringified.startsWith("\"") && stringified.endsWith("\"")) { - stringified = stringified.substring(1, stringified.length-1) - } - - - navigator.clipboard.writeText(stringified) - copyText.select() - copyText.setSelectionRange(0, 99999) /* For mobile devices */ - - /* Copy the text inside the text field */ - document.execCommand("copy") - } else { - console.log("Failed to copy from "+elementName+": ", copyText) - } - } - - const HandleJsonCopy = (base, copy, base_node_name) => { - if (typeof(copy.name) === "string") { - copy.name = copy.name.replaceAll(" ", "_") - } - - console.log("COPY: ", copy) - var newitem = JSON.parse(base) - to_be_copied = "$"+base_node_name.toLowerCase().replaceAll(" ", "_") - for (var key in copy.namespace) { - if (copy.namespace[key].includes("Results for")) { - continue - } - - if (newitem !== undefined && newitem !== null) { - newitem = newitem[copy.namespace[key]] - if (!isNaN(copy.namespace[key])) { - to_be_copied += ".#" - } else { - to_be_copied += "."+copy.namespace[key] - } - } - } - - if (newitem !== undefined && newitem !== null) { - newitem = newitem[copy.name] - if (!isNaN(copy.name)) { - to_be_copied += ".#" - } else { - to_be_copied += "."+copy.name - } - } - - - to_be_copied.replaceAll(" ", "_") - const elementName = "copy_element_shuffle" - var copyText = document.getElementById(elementName) - if (copyText !== null && copyText !== undefined) { - console.log("NAVIGATOR: ", navigator) - const clipboard = navigator.clipboard - if (clipboard === undefined) { - alert.error("Can only copy over HTTPS (port 3443)") - return - } - - navigator.clipboard.writeText(to_be_copied) - copyText.select() - copyText.setSelectionRange(0, 99999); /* For mobile devices */ - - /* Copy the text inside the text field */ - document.execCommand("copy"); - console.log("COPYING!") - } else { - console.log("Couldn't find element ", elementName) - } - - } - - const executionModal = - setExecutionModalOpen(false)} style={{resize: "both", overflow: "auto", zIndex: 10005}} PaperProps={{style: {resize: "both", overflow: "auto", minWidth: 420, maxWidth: 420, backgroundColor: "#1F2023", color: "white", fontSize: 18, zIndex: 10005}}}> - {executionModalView === 0 ? -
- -

- - All Executions -

-
- - - {workflowExecutions.length > 0 ? -
- {workflowExecutions.map((data, index) => { - const statusColor = data.status === "FINISHED" ? green : data.status === "ABORTED" || data.status === "FAILED" ? "red" : yellow - const resultsLength = data.results !== undefined && data.results !== null ? data.results.length : 0 - - const timestamp = new Date(data.started_at*1000).toISOString().split('.')[0].split("T").join(" ") - - var calculatedResult = data.workflow.actions !== undefined && data.workflow.actions !== null ? data.workflow.actions.length : 0 - for (var key in data.workflow.triggers) { - const trigger = data.workflow.triggers[key] - if ((trigger.app_name === "User Input" && trigger.trigger_type === "USERINPUT") || (trigger.app_name === "Shuffle Workflow" && trigger.trigger_type === "SUBFLOW")) { - calculatedResult += 1 - } - } - - return ( - - {}} onMouseOut={() => {}} onClick={() => { - - if ((data.result === undefined || data.result === null || data.result.length === 0) && data.status !== "FINISHED" && data.status !== "ABORTED") { - - start() - setExecutionRunning(true) - setExecutionRequestStarted(false) - } - - const cur_execution = { - "execution_id": data.execution_id, - "authorization": data.authorization, - } - setExecutionRequest(cur_execution) - setExecutionModalView(1) - setExecutionData(data) - handleUpdateResults(data, cur_execution) - }}> -
-
-
- {getExecutionSourceImage(data)} -
-
- {timestamp} -
- {data.workflow.actions !== null ? - -
- {resultsLength}/{calculatedResult} -
-
- : null} -
- - {lastExecution === data.execution_id ? - - : - - } - - - - ) - })} -
- : -
- There are no executions yet -
- } -
- : -
- - { - setExecutionRunning(false) - stop() - getWorkflowExecution(props.match.params.key, "") - setExecutionModalView(0) - setLastExecution(executionData.execution_id) - }}> - {}}> - - -

{ - }}> - See other Executions -

-
-
- -
-

Execution info

- - - - - - {executionData.status === "EXECUTING" ? - - - - - - : null} -
- {executionData.status !== undefined && executionData.status.length > 0 ? -
- - Status    - - - {executionData.status} - -
- : null - } - {executionData.execution_source !== undefined && executionData.execution_source !== null && executionData.execution_source.length > 0 && executionData.execution_source !== "default" ? -
- - Source    - - - {executionData.execution_parent !== null && executionData.execution_parent !== undefined && executionData.execution_parent.length > 0 ? - executionData.execution_source === props.match.params.key ? - - { - getWorkflowExecution(props.match.params.key, executionData.execution_parent) - }}> - Parent Execution - - - : - Parent Workflow - : - executionData.execution_source - } - -
- : null - } - {executionData.started_at !== undefined ? -
- - Started    - - - {new Date(executionData.started_at*1000).toISOString()} - -
- : null - } - {executionData.completed_at !== undefined && executionData.completed_at !== null && executionData.completed_at > 0 ? -
- - Finished   - - - {new Date(executionData.completed_at*1000).toISOString()} - -
- : null - } -
- {executionData.execution_argument !== undefined && executionData.execution_argument.length > 0 ? - parsedExecutionArgument() - : null } - - {executionData.results !== undefined && executionData.results !== null && executionData.results.length > 1 && executionData.results.find(result => result.status === "SKIPPED" || result.status === "FAILURE") ? - Show failed / skipped actions
} - control={ - {setShowSkippedActions(!showSkippedActions)}} /> - } - /> - : - null - } -
- -
- {executionData.status !== undefined && - executionData.status !== "ABORTED" && - executionData.status !== "FINISHED" && - executionData.status !== "FAILURE" && - executionData.status !== "WAITING" && - !(executionData.results === undefined || - executionData.results === null || - (executionData.results.length === 0 && // probably ment to be around the or's - executionData.status === "EXECUTING"))? : null} -
-
- {executionData.results === undefined || executionData.results === null || (executionData.results.length === 0 && executionData.status === "EXECUTING" )? - - : - executionData.results.map((data, index) => { - if (executionData.results.length !== 1 && !showSkippedActions && (data.status === "SKIPPED" || data.status === "FAILURE")) { - return null - } - - // FIXME: The latter replace doens't really work if ' is used in a string - var showResult = data.result.trim() - const validate = validateJson(showResult) - - const curapp = apps.find(a => a.name === data.action.app_name && a.app_version === data.action.app_version) - const imgsize = 50 - const statusColor = data.status === "FINISHED" || data.status === "SUCCESS" ? green : data.status === "ABORTED" || data.status === "FAILURE" ? "red" : yellow - - var imgSrc = curapp === undefined ? "" : curapp.large_image - if (imgSrc.length === 0 && workflow.actions !== undefined && workflow.actions !== null) { - // Look for the node in the workflow - const action = workflow.actions.find(action => action.id === data.action.id) - if (action !== undefined && action !== null) { - imgSrc = action.large_image - } - - } - - var actionimg = curapp === null ? - null : - {data.action.app_name} - - if (triggers.length > 2) { - if (data.action.app_name === "shuffle-subflow") { - const parsedImage = triggers[2].large_image - actionimg = {"Shuffle - } - - if (data.action.app_name === "User Input") { - actionimg = {"Shuffle - } - } - - if (data.action.app_name === "Shuffle Tools" && data.action.id !== undefined && cy !== undefined) { - - const nodedata = cy.getElementById(data.action.id).data() - if (nodedata !== undefined && nodedata !== null && nodedata.fillstyle === "linear-gradient") { - var imgStyle = { - marginRight: 20, - width: imgsize, - height: imgsize, - border: `2px solid ${statusColor}`, - borderRadius: executionData.start === data.action.id ? 25 : 5, - background: `linear-gradient(to right, ${nodedata.fillGradient})` - } - - actionimg = {nodedata.label} - } - } - - - if (validate.valid && typeof(validate.result) === "string") { - validate.result = JSON.parse(validate.result) - } - - if (validate.valid && typeof(validate.result) === "object") { - if (validate.result.result !== undefined && validate.result.result !== null) { - try { - validate.result.result = JSON.parse(validate.result.result) - } catch (e) { - //console.log("ERROR PARSING: ", e) - } - } - } - - return ( -
{ - var currentnode = cy.getElementById(data.action.id) - if (currentnode.length !== 0) { - currentnode.addClass('shuffle-hover-highlight') - } - }} onMouseOut={() => { - var currentnode = cy.getElementById(data.action.id) - if (currentnode.length !== 0) { - currentnode.removeClass('shuffle-hover-highlight') - } - }}> -
-
- { - setSelectedResult(data) - setCodeModalOpen(true) - }}> - - - - - {actionimg} -
-
{data.action.label}
-
- - {data.action.name} - -
-
-
- {data.action.app_name === "shuffle-subflow" && validate.result.success !== undefined && validate.result.success === true ? - - {validate.valid && data.action.parameters !== undefined && data.action.parameters !== null && data.action.parameters.length > 0 ? - data.action.parameters[0].value === props.match.params.key ? - { - getWorkflowExecution(props.match.params.key, validate.result.execution_id) - }}> - See sub-execution - - : - { - }}> - - - : - "" - } - - : null - } -
-
- - Status  - - - {data.status} - -
- {validate.valid ? - { - handleReactJsonClipboard(copy) - }} - displayDataTypes={false} - onSelect={(select) => { - HandleJsonCopy(showResult, select, data.action.label) - console.log("SELECTED!: ", select) - }} - name={"Results for "+data.action.label} - /> - - : -
- - Result  - - - {data.result} - -
- } -
- ) - }) - } -
- } - - - // This sucks :) - const curapp = !codeModalOpen ? {} : selectedResult.action.app_name === "shuffle-subflow" ? triggers[2] : selectedResult.action.app_name === "User Input" ? triggers[3] : apps.find(a => a.name === selectedResult.action.app_name && a.app_version === selectedResult.action.app_version) - const imgsize = 50 - const statusColor = !codeModalOpen ? "red" : selectedResult.status === "FINISHED" || selectedResult.status === "SUCCESS" ? green : selectedResult.status === "ABORTED" || selectedResult.status === "FAILURE" ? "red" : yellow - const validate = !codeModalOpen ? "" : validateJson(selectedResult.result.trim()) - if (validate.valid && typeof(validate.result) === "string") { - validate.result = JSON.parse(validate.result) - } - - var draggingDisabled = false - const codePopoutModal = !codeModalOpen ? null : - { - console.log(event) - console.log(event.srcElement) - if (!dragging) { - console.log("START") - setDragging(true) - } - }} - disabled={draggingDisabled} - onStop={(e) => { - console.log("STOP") - if (!dragging) { - return - } - - setDragging(false) - const newoffsetX = parseInt(dragPosition.x)-parseInt(e.layerX-e.offsetX) - const newoffsetY = parseInt(dragPosition.y)-parseInt(e.layerY-e.offsetY) - if ((newoffsetX <= 40 && newoffsetX >= -40) && (newoffsetY <= 40 && newoffsetY >= -40)) { - console.log("SKIP X & Y") - return - } - - const newPosition = { - x: e.layerX-e.offsetX, - y: e.layerY-e.offsetY, - } - setDragPosition(newPosition) - }} - position={dragPosition} - > - - - - - { - e.preventDefault() - for (var key in workflowExecutions) { - const execution = workflowExecutions[key] - const result = execution.results.find(data => data.status === "SUCCESS" && data.action.id === selectedResult.action.id) - if (result !== undefined) { - setSelectedResult(result) - setUpdate(Math.random()) - break - } - } - }}> - - - - - { - e.preventDefault() - for (var key in workflowExecutions) { - const execution = workflowExecutions[key] - const result = execution.results.find(data => data.action.id === selectedResult.action.id && data.status !== "SUCCESS" && data.status !== "SKIPPED" && data.status !== "WAITING") - if (result !== undefined) { - setSelectedResult(result) - setUpdate(Math.random()) - break - } - } - }}> - - - - - { - e.preventDefault() - const executionIndex = workflowExecutions.findIndex(data => data.execution_id === selectedResult.execution_id) - if (executionIndex !== -1) { - setExecutionModalOpen(true) - setExecutionModalView(1) - setExecutionData(workflowExecutions[executionIndex]) - } - }}> - - - - - { - e.preventDefault() - setCodeModalOpen(false) - }}> - - - - -
-
- {curapp === null ? null : {selectedResult.app_name}} - -
-
{selectedResult.action.label}
-
{selectedResult.action.name}
-
-
-
Status {selectedResult.status}
- {validate.valid ? { - handleReactJsonClipboard(copy) - }} - onSelect={(select) => { - HandleJsonCopy(JSON.stringify(validate.result), select, selectedResult.action.label) - }} - name={"Results for "+selectedResult.action.label} - /> - : -
- Result  - { - console.log("IN HERE TO CLICK") - to_be_copied = selectedResult.result - var copyText = document.getElementById("copy_element_shuffle"); - console.log("PRECOPY: ", to_be_copied) - if (copyText !== null && copyText !== undefined) { - console.log("COPY: ", copyText) - console.log("NAVIGATOR: ", navigator) - const clipboard = navigator.clipboard - if (clipboard === undefined) { - alert.error("Can only copy over HTTPS (port 3443)") - return - } - - navigator.clipboard.writeText(to_be_copied) - - copyText.select(); - copyText.setSelectionRange(0, 99999); /* For mobile devices */ - - /* Copy the text inside the text field */ - document.execCommand("copy"); - } else { - console.log("Failed to copy. copy_element_shuffle is undefined") - } - }}>{selectedResult.result} -
- } -
- {selectedResult.action.parameters !== null && selectedResult.action.parameters !== undefined ? -
- - - Variables - - {selectedResult.action.parameters.map((data, index) => { - if (data.value.length === 0) { - return null - } - - if (data.example !== undefined && data.example !== null && data.example.includes("***")) { - return null - } - - return ( -
- - {data.name}: {data.value} - -
- ) - })} -
- : - null - } -
-
-
-
- - const newView = -
-
- {leftView} - {workflow.id === undefined || workflow.id === null || apps.length === 0 ? -
- - - Loading Workflow - -
- : - { - // 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) - }} - /> - } -
- {executionModal} - - - -
- - const executionVariableModal = executionVariablesModalOpen ? - { - setNewVariableName("") - setExecutionVariablesModalOpen(false) - }} - PaperProps={{ - style: { - backgroundColor: surfaceColor, - color: "white", - }, - }} - > - - Execution Variable - - Execution Variables are TEMPORARY variables that you can ony be set and used during execution. Learn more here - setNewVariableName(event.target.value)} - color="primary" - placeholder="Name" - style={{marginTop: 25}} - InputProps={{ - style:{ - color: "white" - } - }} - margin="dense" - fullWidth - defaultValue={newVariableName} - /> - - - - - - {workflowExecutions.length > 0 ? - - - Values from last 3 executions - {workflowExecutions.slice(0,3).map((execution, index) => { - if (execution.execution_variables === undefined || execution.execution_variables === null || execution.execution_variables === 0) { - return null - } - - const variable = execution.execution_variables.find(data => data.name === newVariableName) - if (variable === undefined || variable.value === undefined) { - return null - } - - return ( -
- {index+1}: {variable.value} -
- ) - })} -
- : null - } -
-
- : null - - const variablesModal = variablesModalOpen ? - { - setNewVariableName("") - setNewVariableDescription("") - setNewVariableValue("") - setVariablesModalOpen(false) - }} - PaperProps={{ - style: { - backgroundColor: surfaceColor, - color: "white", - }, - }} - > - - Workflow Variable - - setNewVariableName(event.target.value)} - color="primary" - placeholder="Name" - InputProps={{ - style:{ - color: "white" - } - }} - margin="dense" - fullWidth - defaultValue={newVariableName} - /> - setNewVariableDescription(event.target.value)} - color="primary" - placeholder="Description" - margin="dense" - fullWidth - InputProps={{ - style:{ - color: "white" - } - }} - defaultValue={newVariableDescription} - /> - setNewVariableValue(event.target.value)} - rows="6" - multiline - color="primary" - placeholder="Value" - margin="dense" - InputProps={{ - style:{ - color: "white" - } - }} - fullWidth - defaultValue={newVariableValue} - /> - - - - - - - - : null - - - - const AuthenticationData = (props) => { - const selectedApp = props.app - - 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 || selectedApp.authentication.parameters === null || selectedApp.authentication.parameters === undefined || selectedApp.authentication.parameters.length === 0) { - return ( - - - {selectedApp.name} does not require authentication - - - ) - } - - 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}` - } - - // 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) - - if (configureWorkflowModalOpen) { - setSelectedAction({}) - } - - setUpdate(authenticationOption.id) - - } - - if (authenticationOption.label === null || authenticationOption.label === undefined) { - authenticationOption.label = selectedApp.name+" authentication" - } - - return ( -
-
Authentication for {selectedApp.name}
- - What is app authentication?
- These are required fields for authenticating with {selectedApp.name} -
- Name - what is this used for? - { - authenticationOption.label = event.target.value - }} - /> - -
- {selectedApp.authentication.parameters.map((data, index) => { - - return ( -
- - {data.name} - - {data.schema !== undefined && data.schema !== null && data.schema.type === "bool" ? - - : - { - authenticationOption.fields[data.name] = event.target.value - }} - /> - } -
- ) - })} - - - - - -
- ) - } - - const configureWorkflowModal = configureWorkflowModalOpen && apps.length !== 0 ? - - { - setConfigureWorkflowModalOpen(false) - }}> - - - - - : null - - - // This whole part is redundant. Made it part of Arguments instead. - const authenticationModal = authenticationModalOpen ? - { - if (configureWorkflowModalOpen) { - setSelectedAction({}) - } - }} - PaperProps={{ - style: { - backgroundColor: surfaceColor, - color: "white", - minWidth: 1100, - minHeight: 700, - maxHeight: 700, - padding: 15, - overflow: "hidden", - zIndex: 10012, - }, - }} - > -
- {selectedApp.reference_info === undefined || selectedApp.reference_info === null || selectedApp.reference_info.github_url === undefined || selectedApp.reference_info.github_url === null || selectedApp.reference_info.github_url.length === 0 ? - - {`Documentation - - : - - {`Documentation - - } -
- { - setAuthenticationModalOpen(false) - if (configureWorkflowModalOpen) { - setSelectedAction({}) - } - }}> - - -
-
- {authenticationType.type === "oauth2" ? - - : - - } -
-
- {selectedApp.documentation === undefined || selectedApp.documentation === null || selectedApp.documentation.length === 0 ? - - - {selectedApp.description} - - - - There is currently no extended documentation available for this app. - - - Want help with this app? Join the Discord! - - - - Want to help change this app? - - {selectedApp.reference_info === undefined || selectedApp.reference_info === null || selectedApp.reference_info.github_url === undefined || selectedApp.reference_info.github_url === null || selectedApp.reference_info.github_url.length === 0 ? - - - Check it out on Github! - - - : - - - Check it out on Github! - - - } - - : - - } -
-
-
: null - - const loadedCheck = isLoaded && workflowDone ? -
- {newView} - {variablesModal} - {executionVariableModal} - {conditionsModal} - {authenticationModal} - {codePopoutModal} - {configureWorkflowModal} - -
- : -
-
- - // Awful way of handling scroll - if (scrollConfig !== undefined && setScrollConfig !== undefined && Object.getOwnPropertyNames(selectedAction).length !== 0) { - const rightSideActionView = document.getElementById("rightside_actions") - if (rightSideActionView !== undefined && rightSideActionView !== null) { - if (scrollConfig.top !== 0 && scrollConfig.top !== undefined && scrollConfig.top !== 0) { - - setTimeout(() => { - - - if (scrollConfig.selected !== undefined && scrollConfig.selected !== null) { - const selectedField = document.getElementById(scrollConfig.selected) - if (selectedField !== undefined && selectedField !== null) { - selectedField.focus() - - } - } - }, 5) - } else { - if (rightSideActionView.scrollTop !== scrollConfig.top) { - setScrollConfig({ - top: rightSideActionView.scrollTop, - left: 0, - selected: "", - }) - } - } - } - } - - return ( -
- - {loadedCheck} -
- ) - -} - -export default AngularWorkflow + return ( +
+
+ +
+ +
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); + }} + /> +
+
+
+ ); + } + + return null; + }; + + const WebhookSidebar = () => { + 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: "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: "", + }; + 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); + } + } + + const trigger_header_auth = + workflow.triggers[selectedTriggerIndex].parameters.length > 2 + ? workflow.triggers[selectedTriggerIndex].parameters[2].value + : ""; + + return ( +
+
+
+

+ {selectedTrigger.app_name}: {selectedTrigger.status} +

+ + What are webhooks? + +
+
+ +
Name
+ +
+ Environment + +
+ +
+
+ Parameters +
+
+
+ Webhook URI +
+
+ { + 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; + } + + navigator.clipboard.writeText(copyText.value); + copyText.select(); + copyText.setSelectionRange( + 0, + 99999 + ); /* For mobile devices */ + + /* 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 +
+
+
+ {}} + InputProps={{ + style: { + color: "white", + marginLeft: "5px", + maxWidth: "95%", + fontSize: "1em", + }, + }} + fullWidth + multiline + rows="4" + defaultValue={trigger_header_auth} + color="primary" + disabled={selectedTrigger.status === "running"} + placeholder={"AUTH_HEADER=AUTH_VALUE1"} + onBlur={(e) => { + const value = e.target.value; + if (selectedTrigger.parameters === null) { + selectedTrigger.parameters = []; + } + + workflow.triggers[selectedTriggerIndex].parameters[2] = { + value: value, + name: "auth_headers", + }; + setWorkflow(workflow); + }} + /> +
+ +
+ + +
+
+
+
+ ); + } + + 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; + } + + console.log("TRIG: ", trigger); + const data = { + name: hookname, + type: "webhook", + id: trigger.id, + workflow: workflow.id, + start: startNode, + environment: trigger.environment, + auth: auth, + }; + + 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", + }; + setWorkflow(workflow); + } + + return ( +
+
+
+

+ {selectedTrigger.app_name}: {selectedTrigger.status} +

+ + What is the user input trigger? + +
+
+ +
Name
+ + +
+ Environment: + +
+ +
+ Parameters +
+
+
+ Information: +
+
+ { + setTriggerTextInformationWrapper(e.target.value); + }} + /> +
+
+
+ Contact options: +
+
+ + { + 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} +
+
+ ); + } + + 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 ? "*/15 * * * *" : "120", + }; + workflow.triggers[selectedTriggerIndex].parameters[1] = { + name: "execution_argument", + value: '{"example": {"json": "is cool"}}', + }; + setWorkflow(workflow); + } + + return ( +
+
+
+

+ {selectedTrigger.app_name}: {selectedTrigger.status} +

+ + What are schedules? + +
+
+ +
Name
+ +
+ Environment + +
+ +
+
+ Parameters +
+
+
+ Interval (UTC) +
+
+ { + setTriggerCronWrapper(e.target.value); + }} + /> +
+
+
+ Execution argument: +
+
+ { + setTriggerBodyWrapper(e.target.value); + }} + /> + +
+ + +
+
+
+
+ ); + } + + return null; + }; + + const cytoscapeViewWidths = 800; + const bottomBarStyle = { + position: "fixed", + right: 20, + left: leftBarSize, + bottom: 0, + minWidth: cytoscapeViewWidths, + maxWidth: cytoscapeViewWidths, + marginLeft: 20, + marginBottom: 20, + zIndex: 10, + }; + + const topBarStyle = { + position: "fixed", + right: 0, + left: leftBarSize + 20, + top: appBarSize + 20, + }; + + const TopCytoscapeBar = (props) => { + return ( +
+
+ + +

+ + Workflows +

+ +

{workflow.name}

+ + {workflow.public ? ( +

Public Workflow PREVIEW

+ ) : null} +
+
+
+ ); + }; + + const WorkflowMenu = () => { + const [newAnchor, setNewAnchor] = React.useState(null); + const [showShuffleMenu, setShowShuffleMenu] = React.useState(false); + + return ( +
+ { + setShowShuffleMenu(false); + }} + > +
+

This menu is used to control the workflow itself.

+ + + 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" + ); + }} + /> + } + /> +
+ + + + + + +
+ ); + }; + + 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 = () => { + if ( + workflow.id === undefined || + workflow.id === null || + apps.length === 0 + ) { + return null; + } + + const boxSize = 100; + const executionButton = executionRunning ? ( + + + + + + ) : ( + + + + + + ); + + return ( +
+ {executionButton} +
+ {workflow.public ? null : ( + + { + setExecutionText(e.target.value); + }} + /> + + )} + + + + + + {workflow.public ? ( + + + + + + ) : null} + + + + + + + + + + + + + + + + + + + + + + + + + + {workflow.configuration !== null && + workflow.configuration !== undefined && + workflow.configuration.exit_on_error !== undefined ? ( + + ) : null} +
+
+ ); + }; + + const addCommentNode = () => { + const newId = uuidv4(); + const position = { + x: 300, + y: 300, + }; + cy.add({ + group: "nodes", + data: { + id: newId, + label: "Your comment :)", + type: "COMMENT", + is_valid: true, + decorator: true, + width: 250, + height: 150, + position: position, + backgroundcolor: "#1f2023", + color: "#ffffff", + }, + position: position, + }); + }; + + const RightSideBar = (props) => { + const { + workflow, + setWorkflow, + setSelectedAction, + setUpdate, + selectedApp, + workflowExecutions, + setSelectedResult, + selectedAction, + setSelectedApp, + setSelectedTrigger, + setSelectedEdge, + setCurrentView, + cy, + setAuthenticationModalOpen, + setVariablesModalOpen, + setCodeModalOpen, + selectedNameChange, + rightsidebarStyle, + showEnvironment, + selectedActionEnvironment, + environments, + setNewSelectedAction, + appApiViewStyle, + globalUrl, + setSelectedActionEnvironment, + requiresAuthentication, + 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(selectedComment).length > 0) { + console.log("Returning comment field"); + return ( +
+ +
+ ); + } else if (Object.getOwnPropertyNames(selectedTrigger).length > 0) { + if (selectedTrigger.trigger_type === "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 leftView = leftViewOpen ? ( +
+ +
+ ) : ( +
+
{ + setLeftViewOpen(true); + setLeftBarSize(350); + }} + > + + + +
+
+ ); + + 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 ( + { + handleReactJsonClipboard(copy); + }} + onSelect={(select) => { + HandleJsonCopy(showResult, select, "exec"); + console.log("SELECTED!: ", select); + }} + name={"Execution Argument"} + /> + ); + } + + return ( +
+

Execution Argument

+
+ {executionData.execution_argument} +
+
+ ); + }; + + const getExecutionSourceImage = (execution) => { + // This is the playbutton at 150x150 + const defaultImage = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAACOCAMAAADkWgEmAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAWlBMVEX4Wj69TDgmKCvkVTwlJyskJiokJikkJSkjJSn4Ykf+6+f5h3L////8xLr5alH/9fT7nYz4Wz/919H5cVn/+vr8qpv4XUL94d35e2X//v38t6v4YUbkVDy8SzcVIzHLAAAAAWJLR0QMgbNRYwAAAAlwSFlzAAARsAAAEbAByCf1VAAAAAd0SU1FB+QGGgsvBZ/GkmwAAAFKSURBVHja7dlrTgMxDEXhFgpTiukL2vLc/zbZQH5N7MmReu4KPmlGN4m9WgGzfhgtaOZxM1rQztNoQDvPowHtTKMB7WxHA2TJkiVLlixIZMmSRYgsWbIIkSVLFiGyZMkiRNZirBcma/eKZEW87ZGsOBxPRFbE+R3Jio/LlciKuH0iWfH1/UNkRSR3RRYruSvyWKldkcjK7IpUVl5X5LLSuiKbldQV6aycrihgZXRFCau/K2pY3V1RxersijJWX1cUsnq6opLV0RW1rNldUc2a2RXlrHldsQBrTlfcLwv5EZm/PLIgkHXKPHyQRzXzYoO8BjIvzcgnBvJBxny+Ih/7zNEIcpDEHLshh5TIkS5zAI5cFzCXK8hVFHNxh1xzQpfC0BV6XWTJkkWILFmyCJElSxYhsmTJIkSWLFmEyJIlixBZsmQB8stk/U3/Yb49pVcDMg4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMjAtMDYtMjZUMTE6NDc6MDUrMDI6MDD8QCPmAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIwLTA2LTI2VDExOjQ3OjA1KzAyOjAwjR2bWgAAAABJRU5ErkJggg=="; + const size = 40; + if ( + execution.execution_source === undefined || + execution.execution_source === null || + execution.execution_source.length === 0 + ) { + return ( + default + ); + } + + if (execution.execution_source === "webhook") { + return ( + {"webhook"} trigger.trigger_type === "WEBHOOK") + .large_image + } + style={{ width: size, height: size }} + /> + ); + } else if (execution.execution_source === "outlook") { + return ( + {"email"} trigger.trigger_type === "EMAIL") + .large_image + } + style={{ width: size, height: size }} + /> + ); + } else if (execution.execution_source === "schedule") { + return ( + {"schedule"} trigger.trigger_type === "SCHEDULE") + .large_image + } + style={{ width: size, height: size }} + /> + ); + } else if (execution.execution_source === "EMAIL") { + return ( + {"email"} trigger.trigger_type === "EMAIL") + .large_image + } + style={{ width: size, height: size }} + /> + ); + } + + if ( + execution.execution_parent !== null && + execution.execution_parent !== undefined && + execution.execution_parent.length > 0 + ) { + return ( + {"parent trigger.trigger_type === "SUBFLOW") + .large_image + } + style={{ width: size, height: size }} + /> + ); + } + + return ( + {execution.execution_source} + ); + }; + + const handleReactJsonClipboard = (copy) => { + console.log("COPY: ", copy); + + const elementName = "copy_element_shuffle"; + var copyText = document.getElementById(elementName); + if (copyText !== null && copyText !== undefined) { + if ( + copy.namespace !== undefined && + copy.name !== undefined && + copy.src !== undefined + ) { + copy = copy.src; + } + + const clipboard = navigator.clipboard; + if (clipboard === undefined) { + alert.error("Can only copy over HTTPS (port 3443)"); + return; + } + + var stringified = JSON.stringify(copy); + if (stringified.startsWith('"') && stringified.endsWith('"')) { + stringified = stringified.substring(1, stringified.length - 1); + } + + navigator.clipboard.writeText(stringified); + copyText.select(); + copyText.setSelectionRange(0, 99999); /* For mobile devices */ + + /* Copy the text inside the text field */ + document.execCommand("copy"); + } else { + console.log("Failed to copy from " + elementName + ": ", copyText); + } + }; + + const HandleJsonCopy = (base, copy, base_node_name) => { + if (typeof copy.name === "string") { + copy.name = copy.name.replaceAll(" ", "_"); + } + + console.log("COPY: ", copy); + var newitem = JSON.parse(base); + to_be_copied = "$" + base_node_name.toLowerCase().replaceAll(" ", "_"); + for (var key in copy.namespace) { + if (copy.namespace[key].includes("Results for")) { + continue; + } + + if (newitem !== undefined && newitem !== null) { + newitem = newitem[copy.namespace[key]]; + if (!isNaN(copy.namespace[key])) { + to_be_copied += ".#"; + } else { + to_be_copied += "." + copy.namespace[key]; + } + } + } + + if (newitem !== undefined && newitem !== null) { + newitem = newitem[copy.name]; + if (!isNaN(copy.name)) { + to_be_copied += ".#"; + } else { + to_be_copied += "." + copy.name; + } + } + + to_be_copied.replaceAll(" ", "_"); + const elementName = "copy_element_shuffle"; + var copyText = document.getElementById(elementName); + if (copyText !== null && copyText !== undefined) { + console.log("NAVIGATOR: ", navigator); + const clipboard = navigator.clipboard; + if (clipboard === undefined) { + alert.error("Can only copy over HTTPS (port 3443)"); + return; + } + + navigator.clipboard.writeText(to_be_copied); + copyText.select(); + copyText.setSelectionRange(0, 99999); /* For mobile devices */ + + /* Copy the text inside the text field */ + document.execCommand("copy"); + console.log("COPYING!"); + } else { + console.log("Couldn't find element ", elementName); + } + }; + + const executionModal = ( + setExecutionModalOpen(false)} + style={{ resize: "both", overflow: "auto", zIndex: 10005 }} + PaperProps={{ + style: { + resize: "both", + overflow: "auto", + minWidth: 420, + maxWidth: 420, + backgroundColor: "#1F2023", + color: "white", + fontSize: 18, + zIndex: 10005, + }, + }} + > + {executionModalView === 0 ? ( +
+ +

+ + All Executions +

+
+ + + {workflowExecutions.length > 0 ? ( +
+ {workflowExecutions.map((data, index) => { + const statusColor = + data.status === "FINISHED" + ? green + : data.status === "ABORTED" || data.status === "FAILED" + ? "red" + : yellow; + const resultsLength = + data.results !== undefined && data.results !== null + ? data.results.length + : 0; + + const timestamp = new Date(data.started_at * 1000) + .toISOString() + .split(".")[0] + .split("T") + .join(" "); + + var calculatedResult = + data.workflow.actions !== undefined && + data.workflow.actions !== null + ? data.workflow.actions.length + : 0; + for (var key in data.workflow.triggers) { + const trigger = data.workflow.triggers[key]; + if ( + (trigger.app_name === "User Input" && + trigger.trigger_type === "USERINPUT") || + (trigger.app_name === "Shuffle Workflow" && + trigger.trigger_type === "SUBFLOW") + ) { + calculatedResult += 1; + } + } + + return ( + + {}} + onMouseOut={() => {}} + onClick={() => { + if ( + (data.result === undefined || + data.result === null || + data.result.length === 0) && + data.status !== "FINISHED" && + data.status !== "ABORTED" + ) { + start(); + setExecutionRunning(true); + setExecutionRequestStarted(false); + } + + const cur_execution = { + execution_id: data.execution_id, + authorization: data.authorization, + }; + setExecutionRequest(cur_execution); + setExecutionModalView(1); + setExecutionData(data); + handleUpdateResults(data, cur_execution); + }} + > +
+
+
+ {getExecutionSourceImage(data)} +
+
+ {timestamp} +
+ {data.workflow.actions !== null ? ( + +
+ {resultsLength}/{calculatedResult} +
+
+ ) : null} +
+ + {lastExecution === data.execution_id ? ( + + ) : ( + + )} + + + + ); + })} +
+ ) : ( +
There are no executions yet
+ )} +
+ ) : ( +
+ + { + setExecutionRunning(false); + stop(); + getWorkflowExecution(props.match.params.key, ""); + setExecutionModalView(0); + setLastExecution(executionData.execution_id); + }} + > + {}} + > + + +

{}} + > + See other Executions +

+
+
+ +
+

Execution info

+ + + + + + {executionData.status === "EXECUTING" ? ( + + + + + + ) : null} +
+ {executionData.status !== undefined && + executionData.status.length > 0 ? ( +
+ + Status    + + + {executionData.status} + +
+ ) : null} + {executionData.execution_source !== undefined && + executionData.execution_source !== null && + executionData.execution_source.length > 0 && + executionData.execution_source !== "default" ? ( +
+ + Source    + + + {executionData.execution_parent !== null && + executionData.execution_parent !== undefined && + executionData.execution_parent.length > 0 ? ( + executionData.execution_source === props.match.params.key ? ( + { + getWorkflowExecution( + props.match.params.key, + executionData.execution_parent + ); + }} + > + Parent Execution + + ) : ( + + Parent Workflow + + ) + ) : ( + executionData.execution_source + )} + +
+ ) : null} + {executionData.started_at !== undefined ? ( +
+ + Started    + + + {new Date(executionData.started_at * 1000).toISOString()} + +
+ ) : null} + {executionData.completed_at !== undefined && + executionData.completed_at !== null && + executionData.completed_at > 0 ? ( +
+ + Finished   + + + {new Date(executionData.completed_at * 1000).toISOString()} + +
+ ) : null} +
+ {executionData.execution_argument !== undefined && + executionData.execution_argument.length > 0 + ? parsedExecutionArgument() + : null} + + {executionData.results !== undefined && + executionData.results !== null && + executionData.results.length > 1 && + executionData.results.find( + (result) => + result.status === "SKIPPED" || result.status === "FAILURE" + ) ? ( + + Show failed / skipped actions +
+ } + control={ + { + setShowSkippedActions(!showSkippedActions); + }} + /> + } + /> + ) : null} +
+
+ {executionData.status !== undefined && + executionData.status !== "ABORTED" && + executionData.status !== "FINISHED" && + executionData.status !== "FAILURE" && + executionData.status !== "WAITING" && + !( + executionData.results === undefined || + executionData.results === null || + (executionData.results.length === 0 && // probably ment to be around the or's + executionData.status === "EXECUTING") + ) ? ( + + ) : null} +
+
+ {executionData.results === undefined || + executionData.results === null || + (executionData.results.length === 0 && + executionData.status === "EXECUTING") ? ( + + ) : ( + executionData.results.map((data, index) => { + if ( + executionData.results.length !== 1 && + !showSkippedActions && + (data.status === "SKIPPED" || data.status === "FAILURE") + ) { + return null; + } + + // FIXME: The latter replace doens't really work if ' is used in a string + var showResult = data.result.trim(); + const validate = validateJson(showResult); + + const curapp = apps.find( + (a) => + a.name === data.action.app_name && + a.app_version === data.action.app_version + ); + const imgsize = 50; + const statusColor = + data.status === "FINISHED" || data.status === "SUCCESS" + ? green + : data.status === "ABORTED" || data.status === "FAILURE" + ? "red" + : yellow; + + var imgSrc = curapp === undefined ? "" : curapp.large_image; + if ( + imgSrc.length === 0 && + workflow.actions !== undefined && + workflow.actions !== null + ) { + // Look for the node in the workflow + const action = workflow.actions.find( + (action) => action.id === data.action.id + ); + if (action !== undefined && action !== null) { + imgSrc = action.large_image; + } + } + + var actionimg = + curapp === null ? null : ( + {data.action.app_name} + ); + + if (triggers.length > 2) { + if (data.action.app_name === "shuffle-subflow") { + const parsedImage = triggers[2].large_image; + actionimg = ( + {"Shuffle + ); + } + + if (data.action.app_name === "User Input") { + actionimg = ( + {"Shuffle + ); + } + } + + if ( + data.action.app_name === "Shuffle Tools" && + data.action.id !== undefined && + cy !== undefined + ) { + const nodedata = cy.getElementById(data.action.id).data(); + if ( + nodedata !== undefined && + nodedata !== null && + nodedata.fillstyle === "linear-gradient" + ) { + var imgStyle = { + marginRight: 20, + width: imgsize, + height: imgsize, + border: `2px solid ${statusColor}`, + borderRadius: + executionData.start === data.action.id ? 25 : 5, + background: `linear-gradient(to right, ${nodedata.fillGradient})`, + }; + + actionimg = ( + {nodedata.label} + ); + } + } + + if (validate.valid && typeof validate.result === "string") { + validate.result = JSON.parse(validate.result); + } + + if (validate.valid && typeof validate.result === "object") { + if ( + validate.result.result !== undefined && + validate.result.result !== null + ) { + try { + validate.result.result = JSON.parse(validate.result.result); + } catch (e) { + //console.log("ERROR PARSING: ", e) + } + } + } + + return ( +
{ + var currentnode = cy.getElementById(data.action.id); + if (currentnode.length !== 0) { + currentnode.addClass("shuffle-hover-highlight"); + } + }} + onMouseOut={() => { + var currentnode = cy.getElementById(data.action.id); + if (currentnode.length !== 0) { + currentnode.removeClass("shuffle-hover-highlight"); + } + }} + > +
+
+ { + setSelectedResult(data); + setCodeModalOpen(true); + }} + > + + + + + {actionimg} +
+
+ {data.action.label} +
+
+ + {data.action.name} + +
+
+
+ {data.action.app_name === "shuffle-subflow" && + validate.result.success !== undefined && + validate.result.success === true ? ( + + {validate.valid && + data.action.parameters !== undefined && + data.action.parameters !== null && + data.action.parameters.length > 0 ? ( + data.action.parameters[0].value === + props.match.params.key ? ( + { + getWorkflowExecution( + props.match.params.key, + validate.result.execution_id + ); + }} + > + See sub-execution + + ) : ( + {}} + > + + + ) + ) : ( + "" + )} + + ) : null} +
+
+ + Status  + + + {data.status} + +
+ {validate.valid ? ( + + { + handleReactJsonClipboard(copy); + }} + displayDataTypes={false} + onSelect={(select) => { + HandleJsonCopy(showResult, select, data.action.label); + console.log("SELECTED!: ", select); + }} + name={"Results for " + data.action.label} + /> + + ) : ( +
+ + Result  + + + {data.result} + +
+ )} +
+ ); + }) + )} +
+ )} + + ); + + // This sucks :) + const curapp = !codeModalOpen + ? {} + : selectedResult.action.app_name === "shuffle-subflow" + ? triggers[2] + : selectedResult.action.app_name === "User Input" + ? triggers[3] + : apps.find( + (a) => + a.name === selectedResult.action.app_name && + a.app_version === selectedResult.action.app_version + ); + const imgsize = 50; + const statusColor = !codeModalOpen + ? "red" + : selectedResult.status === "FINISHED" || + selectedResult.status === "SUCCESS" + ? green + : selectedResult.status === "ABORTED" || selectedResult.status === "FAILURE" + ? "red" + : yellow; + const validate = !codeModalOpen + ? "" + : validateJson(selectedResult.result.trim()); + if (validate.valid && typeof validate.result === "string") { + validate.result = JSON.parse(validate.result); + } + + var draggingDisabled = false; + const codePopoutModal = !codeModalOpen ? null : ( + { + console.log(event); + console.log(event.srcElement); + if (!dragging) { + console.log("START"); + setDragging(true); + } + }} + disabled={draggingDisabled} + onStop={(e) => { + console.log("STOP"); + if (!dragging) { + return; + } + + setDragging(false); + const newoffsetX = + parseInt(dragPosition.x) - parseInt(e.layerX - e.offsetX); + const newoffsetY = + parseInt(dragPosition.y) - parseInt(e.layerY - e.offsetY); + if ( + newoffsetX <= 40 && + newoffsetX >= -40 && + newoffsetY <= 40 && + newoffsetY >= -40 + ) { + console.log("SKIP X & Y"); + return; + } + + const newPosition = { + x: e.layerX - e.offsetX, + y: e.layerY - e.offsetY, + }; + setDragPosition(newPosition); + }} + position={dragPosition} + > + + + + { + e.preventDefault(); + for (var key in workflowExecutions) { + const execution = workflowExecutions[key]; + const result = execution.results.find( + (data) => + data.status === "SUCCESS" && + data.action.id === selectedResult.action.id + ); + if (result !== undefined) { + setSelectedResult(result); + setUpdate(Math.random()); + break; + } + } + }} + > + + + + + { + e.preventDefault(); + for (var key in workflowExecutions) { + const execution = workflowExecutions[key]; + const result = execution.results.find( + (data) => + data.action.id === selectedResult.action.id && + data.status !== "SUCCESS" && + data.status !== "SKIPPED" && + data.status !== "WAITING" + ); + if (result !== undefined) { + setSelectedResult(result); + setUpdate(Math.random()); + break; + } + } + }} + > + + + + + { + e.preventDefault(); + const executionIndex = workflowExecutions.findIndex( + (data) => data.execution_id === selectedResult.execution_id + ); + if (executionIndex !== -1) { + setExecutionModalOpen(true); + setExecutionModalView(1); + setExecutionData(workflowExecutions[executionIndex]); + } + }} + > + + + + + { + e.preventDefault(); + setCodeModalOpen(false); + }} + > + + + + +
+
+ {curapp === null ? null : ( + {selectedResult.app_name} + )} + +
+
+ {selectedResult.action.label} +
+
{selectedResult.action.name}
+
+
+
+ Status {selectedResult.status} +
+ {validate.valid ? ( + { + handleReactJsonClipboard(copy); + }} + onSelect={(select) => { + HandleJsonCopy( + JSON.stringify(validate.result), + select, + selectedResult.action.label + ); + }} + name={"Results for " + selectedResult.action.label} + /> + ) : ( +
+ Result  + { + console.log("IN HERE TO CLICK"); + to_be_copied = selectedResult.result; + var copyText = document.getElementById( + "copy_element_shuffle" + ); + console.log("PRECOPY: ", to_be_copied); + if (copyText !== null && copyText !== undefined) { + console.log("COPY: ", copyText); + console.log("NAVIGATOR: ", navigator); + const clipboard = navigator.clipboard; + if (clipboard === undefined) { + alert.error("Can only copy over HTTPS (port 3443)"); + return; + } + + navigator.clipboard.writeText(to_be_copied); + + copyText.select(); + copyText.setSelectionRange( + 0, + 99999 + ); /* For mobile devices */ + + /* Copy the text inside the text field */ + document.execCommand("copy"); + } else { + console.log( + "Failed to copy. copy_element_shuffle is undefined" + ); + } + }} + > + {selectedResult.result} + +
+ )} +
+ {selectedResult.action.parameters !== null && + selectedResult.action.parameters !== undefined ? ( +
+ + + Variables + + {selectedResult.action.parameters.map((data, index) => { + if (data.value.length === 0) { + return null; + } + + if ( + data.example !== undefined && + data.example !== null && + data.example.includes("***") + ) { + return null; + } + + return ( +
+ + {data.name}: {data.value} + +
+ ); + })} +
+ ) : null} +
+
+
+
+ ); + + const newView = ( +
+
+ {leftView} + {workflow.id === undefined || + workflow.id === null || + apps.length === 0 ? ( +
+ + + Loading Workflow + +
+ ) : ( + { + // 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); + }} + /> + )} +
+ {executionModal} + + + +
+ ); + + const executionVariableModal = executionVariablesModalOpen ? ( + { + setNewVariableName(""); + setExecutionVariablesModalOpen(false); + }} + PaperProps={{ + style: { + backgroundColor: surfaceColor, + color: "white", + }, + }} + > + + + Execution Variable + + + Execution Variables are TEMPORARY variables that you can ony be set + and used during execution. Learn more{" "} + + here + + setNewVariableName(event.target.value)} + color="primary" + placeholder="Name" + style={{ marginTop: 25 }} + InputProps={{ + style: { + color: "white", + }, + }} + margin="dense" + fullWidth + defaultValue={newVariableName} + /> + + + + + + {workflowExecutions.length > 0 ? ( + + + Values from last 3 executions + {workflowExecutions.slice(0, 3).map((execution, index) => { + if ( + execution.execution_variables === undefined || + execution.execution_variables === null || + execution.execution_variables === 0 + ) { + return null; + } + + const variable = execution.execution_variables.find( + (data) => data.name === newVariableName + ); + if (variable === undefined || variable.value === undefined) { + return null; + } + + return ( +
+ {index + 1}: {variable.value} +
+ ); + })} +
+ ) : null} +
+
+ ) : null; + + const variablesModal = variablesModalOpen ? ( + { + setNewVariableName(""); + setNewVariableDescription(""); + setNewVariableValue(""); + setVariablesModalOpen(false); + }} + PaperProps={{ + style: { + backgroundColor: surfaceColor, + color: "white", + }, + }} + > + + + Workflow Variable + + + setNewVariableName(event.target.value)} + color="primary" + placeholder="Name" + InputProps={{ + style: { + color: "white", + }, + }} + margin="dense" + fullWidth + defaultValue={newVariableName} + /> + setNewVariableDescription(event.target.value)} + color="primary" + placeholder="Description" + margin="dense" + fullWidth + InputProps={{ + style: { + color: "white", + }, + }} + defaultValue={newVariableDescription} + /> + setNewVariableValue(event.target.value)} + rows="6" + multiline + color="primary" + placeholder="Value" + margin="dense" + InputProps={{ + style: { + color: "white", + }, + }} + fullWidth + defaultValue={newVariableValue} + /> + + + + + + + + ) : null; + + const AuthenticationData = (props) => { + const selectedApp = props.app; + + 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 || + selectedApp.authentication.parameters === null || + selectedApp.authentication.parameters === undefined || + selectedApp.authentication.parameters.length === 0 + ) { + return ( + + + {selectedApp.name} does not require authentication + + + ); + } + + 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}`; + } + + // 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); + + if (configureWorkflowModalOpen) { + setSelectedAction({}); + } + + setUpdate(authenticationOption.id); + }; + + if ( + authenticationOption.label === null || + authenticationOption.label === undefined + ) { + authenticationOption.label = selectedApp.name + " authentication"; + } + + return ( +
+ +
+ Authentication for {selectedApp.name} +
+
+ + + What is app authentication? + +
+ These are required fields for authenticating with {selectedApp.name} +
+ Name - what is this used for? + { + authenticationOption.label = event.target.value; + }} + /> + +
+ {selectedApp.authentication.parameters.map((data, index) => { + return ( +
+ + {data.name} + + {data.schema !== undefined && + data.schema !== null && + data.schema.type === "bool" ? ( + + ) : ( + { + authenticationOption.fields[data.name] = + event.target.value; + }} + /> + )} +
+ ); + })} + + + + + +
+ ); + }; + + const configureWorkflowModal = + configureWorkflowModalOpen && apps.length !== 0 ? ( + + { + setConfigureWorkflowModalOpen(false); + }} + > + + + + + ) : null; + + // This whole part is redundant. Made it part of Arguments instead. + const authenticationModal = authenticationModalOpen ? ( + { + if (configureWorkflowModalOpen) { + setSelectedAction({}); + } + }} + PaperProps={{ + style: { + backgroundColor: surfaceColor, + color: "white", + minWidth: 1100, + minHeight: 700, + maxHeight: 700, + padding: 15, + overflow: "hidden", + zIndex: 10012, + }, + }} + > +
+ {selectedApp.reference_info === undefined || + selectedApp.reference_info === null || + selectedApp.reference_info.github_url === undefined || + selectedApp.reference_info.github_url === null || + selectedApp.reference_info.github_url.length === 0 ? ( + + {`Documentation + + ) : ( + + {`Documentation + + )} +
+ { + setAuthenticationModalOpen(false); + if (configureWorkflowModalOpen) { + setSelectedAction({}); + } + }} + > + + +
+
+ {authenticationType.type === "oauth2" ? ( + + ) : ( + + )} +
+
+ {selectedApp.documentation === undefined || + selectedApp.documentation === null || + selectedApp.documentation.length === 0 ? ( + + + {selectedApp.description} + + + + There is currently no extended documentation available for this + app. + + + Want help with this app?{" "} + + Join the Discord! + + + + + Want to help change this app? + + {selectedApp.reference_info === undefined || + selectedApp.reference_info === null || + selectedApp.reference_info.github_url === undefined || + selectedApp.reference_info.github_url === null || + selectedApp.reference_info.github_url.length === 0 ? ( + + + + Check it out on Github! + + + + ) : ( + + + + Check it out on Github! + + + + )} + + ) : ( + + )} +
+
+
+ ) : null; + + const loadedCheck = + isLoaded && workflowDone ? ( +
+ {newView} + {variablesModal} + {executionVariableModal} + {conditionsModal} + {authenticationModal} + {codePopoutModal} + {configureWorkflowModal} + +
+ ) : ( +
+ ); + + // Awful way of handling scroll + if ( + scrollConfig !== undefined && + setScrollConfig !== undefined && + Object.getOwnPropertyNames(selectedAction).length !== 0 + ) { + const rightSideActionView = document.getElementById("rightside_actions"); + if (rightSideActionView !== undefined && rightSideActionView !== null) { + if ( + scrollConfig.top !== 0 && + scrollConfig.top !== undefined && + scrollConfig.top !== 0 + ) { + setTimeout(() => { + if ( + scrollConfig.selected !== undefined && + scrollConfig.selected !== null + ) { + const selectedField = document.getElementById( + scrollConfig.selected + ); + if (selectedField !== undefined && selectedField !== null) { + selectedField.focus(); + } + } + }, 5); + } else { + if (rightSideActionView.scrollTop !== scrollConfig.top) { + setScrollConfig({ + top: rightSideActionView.scrollTop, + left: 0, + selected: "", + }); + } + } + } + } + + return ( +
+ + {loadedCheck} +
+ ); +}; + +export default AngularWorkflow; diff --git a/frontend/src/views/AppCreator.jsx b/frontend/src/views/AppCreator.jsx index 5554e204..ab2293f8 100644 --- a/frontend/src/views/AppCreator.jsx +++ b/frontend/src/views/AppCreator.jsx @@ -1,2827 +1,3602 @@ -import React, {useState, useEffect} from 'react'; -import { makeStyles } from '@material-ui/styles'; -import { useTheme } from '@material-ui/core/styles'; -import {BrowserView, MobileView} from "react-device-detect"; +import React, { useState, useEffect } from "react"; +import { makeStyles } from "@material-ui/styles"; +import { useTheme } from "@material-ui/core/styles"; +import { BrowserView, MobileView } from "react-device-detect"; -import {Paper, Typography, FormControlLabel, Button, Divider, Select, MenuItem, FormControl, Switch, Dialog, DialogTitle, DialogContent, DialogActions, TextField, Tooltip, Breadcrumbs, CircularProgress, Chip} from '@material-ui/core'; -import {LockOpen as LockOpenIcon, FileCopy as FileCopyIcon, Delete as DeleteIcon, Remove as RemoveIcon, Add as AddIcon, CheckCircle as CheckCircleIcon, AttachFile as AttachFileIcon, Apps as AppsIcon, ErrorOutline as ErrorOutlineIcon} from '@material-ui/icons'; +import { + Paper, + Typography, + FormControlLabel, + Button, + Divider, + Select, + MenuItem, + FormControl, + Switch, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + TextField, + Tooltip, + Breadcrumbs, + CircularProgress, + Chip, +} from "@material-ui/core"; +import { + LockOpen as LockOpenIcon, + FileCopy as FileCopyIcon, + Delete as DeleteIcon, + Remove as RemoveIcon, + Add as AddIcon, + CheckCircle as CheckCircleIcon, + AttachFile as AttachFileIcon, + Apps as AppsIcon, + ErrorOutline as ErrorOutlineIcon, +} from "@material-ui/icons"; -import { v4 as uuidv4 } from 'uuid'; -import {Link} from 'react-router-dom'; -import YAML from 'yaml' -import ChipInput from 'material-ui-chip-input' +import { v4 as uuidv4 } from "uuid"; +import { Link } from "react-router-dom"; +import YAML from "yaml"; +import ChipInput from "material-ui-chip-input"; import { useAlert } from "react-alert"; -import words from "shellwords" +import words from "shellwords"; -import AvatarEditor from 'react-avatar-editor'; -import AddAPhotoIcon from '@material-ui/icons/AddAPhoto'; -import AddAPhotoOutlinedIcon from '@material-ui/icons/AddAPhotoOutlined'; -import ZoomInOutlinedIcon from '@material-ui/icons/ZoomInOutlined'; -import ZoomOutOutlinedIcon from '@material-ui/icons/ZoomOutOutlined'; -import LoopIcon from '@material-ui/icons/Loop'; -import AddPhotoAlternateIcon from '@material-ui/icons/AddPhotoAlternate'; +import AvatarEditor from "react-avatar-editor"; +import AddAPhotoIcon from "@material-ui/icons/AddAPhoto"; +import AddAPhotoOutlinedIcon from "@material-ui/icons/AddAPhotoOutlined"; +import ZoomInOutlinedIcon from "@material-ui/icons/ZoomInOutlined"; +import ZoomOutOutlinedIcon from "@material-ui/icons/ZoomOutOutlined"; +import LoopIcon from "@material-ui/icons/Loop"; +import AddPhotoAlternateIcon from "@material-ui/icons/AddPhotoAlternate"; -const surfaceColor = "#27292D" -const inputColor = "#383B40" +const surfaceColor = "#27292D"; +const inputColor = "#383B40"; const bodyDivStyle = { - margin: "auto", - width: "900px", -} + margin: "auto", + width: "900px", +}; const actionListStyle = { - paddingLeft: "10px", - paddingRight: "10px", - paddingBottom: "10px", - paddingTop: "10px", - marginTop: "5px", - backgroundColor: inputColor, - display: "flex", - color: "white", -} + paddingLeft: "10px", + paddingRight: "10px", + paddingBottom: "10px", + paddingTop: "10px", + marginTop: "5px", + backgroundColor: inputColor, + display: "flex", + color: "white", +}; const boxStyle = { - color: "white", - flex: "1", - marginLeft: "10px", - marginRight: "10px", - paddingLeft: "30px", - paddingRight: "30px", - paddingBottom: "30px", - paddingTop: "30px", - display: "flex", - flexDirection: "column", - backgroundColor: surfaceColor, -} + color: "white", + flex: "1", + marginLeft: "10px", + marginRight: "10px", + paddingLeft: "30px", + paddingRight: "30px", + paddingBottom: "30px", + paddingTop: "30px", + display: "flex", + flexDirection: "column", + backgroundColor: surfaceColor, +}; -const dividerStyle = { - marginBottom: "10px", - marginTop: "10px", - height: "1px", - width: "100%", - backgroundColor: "grey", -} - -const appIconStyle = { - marginLeft: "5px", -} +const dividerStyle = { + marginBottom: "10px", + marginTop: "10px", + height: "1px", + width: "100%", + backgroundColor: "grey", +}; +const appIconStyle = { + marginLeft: "5px", +}; const useStyles = makeStyles({ - notchedOutline: { - borderColor: "#f85a3e !important" - }, -}) - + notchedOutline: { + borderColor: "#f85a3e !important", + }, +}); const rewrite = (args) => { - return args.reduce(function(args, a){ - if (0 === a.indexOf('-X')) { - args.push('-X') - args.push(a.slice(2)) + return args.reduce(function (args, a) { + if (0 === a.indexOf("-X")) { + args.push("-X"); + args.push(a.slice(2)); } else { - args.push(a) + args.push(a); } - return args - }, []) -} + return args; + }, []); +}; const parseField = (s) => { - return s.split(/: (.+)/) -} + return s.split(/: (.+)/); +}; const isURL = (s) => { - return /^https?:\/\//.test(s) -} + return /^https?:\/\//.test(s); +}; // Parses CURL to a real request const parseCurl = (s) => { - //console.log("CURL: ", s) + //console.log("CURL: ", s) - if (0 != s.indexOf('curl ')) { - console.log("Not curl start") - return "" - } + if (0 != s.indexOf("curl ")) { + console.log("Not curl start"); + return ""; + } - try { - var args = rewrite(words.split(s)) - } catch (e) { - return s - } - - var out = { method: 'GET', header: {} } - var state = '' + try { + var args = rewrite(words.split(s)); + } catch (e) { + return s; + } - args.forEach(function(arg){ + var out = { method: "GET", header: {} }; + var state = ""; + + args.forEach(function (arg) { switch (true) { case isURL(arg): - out.url = arg + out.url = arg; break; - case arg === '-A' || arg === '--user-agent': - state = 'user-agent' + case arg === "-A" || arg === "--user-agent": + state = "user-agent"; break; - case arg === '-H' || arg === '--header': - state = 'header' + case arg === "-H" || arg === "--header": + state = "header"; break; - case arg === '-d' || arg === '--data' || arg === '--data-ascii': - state = 'data' + case arg === "-d" || arg === "--data" || arg === "--data-ascii": + state = "data"; break; - case arg === '-u' || arg === '--user': - state = 'user' + case arg === "-u" || arg === "--user": + state = "user"; break; - case arg === '-I' || arg === '--head': - out.method = 'HEAD' + case arg === "-I" || arg === "--head": + out.method = "HEAD"; break; - case arg === '-X' || arg === '--request': - state = 'method' + case arg === "-X" || arg === "--request": + state = "method"; break; - case arg === '-b' || arg === '--cookie': - state = 'cookie' + case arg === "-b" || arg === "--cookie": + state = "cookie"; break; - case arg === '--compressed': - out.header['Accept-Encoding'] = out.header['Accept-Encoding'] || 'deflate, gzip' + case arg === "--compressed": + out.header["Accept-Encoding"] = + out.header["Accept-Encoding"] || "deflate, gzip"; break; case !!arg: switch (state) { - case 'header': - var field = parseField(arg) - out.header[field[0]] = field[1] - state = '' + case "header": + var field = parseField(arg); + out.header[field[0]] = field[1]; + state = ""; break; - case 'user-agent': - out.header['User-Agent'] = arg - state = '' + case "user-agent": + out.header["User-Agent"] = arg; + state = ""; break; - case 'data': - if (out.method === 'GET' || out.method === 'HEAD') out.method = 'POST' - out.header['Content-Type'] = out.header['Content-Type'] || 'application/x-www-form-urlencoded' - out.body = out.body - ? out.body + '&' + arg - : arg - state = '' + case "data": + if (out.method === "GET" || out.method === "HEAD") + out.method = "POST"; + out.header["Content-Type"] = + out.header["Content-Type"] || "application/x-www-form-urlencoded"; + out.body = out.body ? out.body + "&" + arg : arg; + state = ""; break; - case 'user': - out.header['Authorization'] = 'Basic ' + btoa(arg) - state = '' + case "user": + out.header["Authorization"] = "Basic " + btoa(arg); + state = ""; break; - case 'method': - out.method = arg - state = '' + case "method": + out.method = arg; + state = ""; break; - case 'cookie': - out.header['Set-Cookie'] = arg - state = '' + case "cookie": + out.header["Set-Cookie"] = arg; + state = ""; break; } break; } - }) + }); - return out -} + return out; +}; // Should be different if logged in :| const AppCreator = (props) => { const { globalUrl, isLoaded } = props; - const classes = useStyles(); - const alert = useAlert() - const theme = useTheme(); + const classes = useStyles(); + const alert = useAlert(); + const theme = useTheme(); - var upload = "" - const increaseAmount = 50 - const actionNonBodyRequest = ["GET", "HEAD", "DELETE", "CONNECT"] - const actionBodyRequest = ["POST", "PUT", "PATCH",] - const authenticationOptions = ["No authentication", "API key", "Bearer auth", "Basic auth", "Oauth2", "JWT"] - const apikeySelection = ["Header", "Query",] + var upload = ""; + const increaseAmount = 50; + const actionNonBodyRequest = ["GET", "HEAD", "DELETE", "CONNECT"]; + const actionBodyRequest = ["POST", "PUT", "PATCH"]; + const authenticationOptions = [ + "No authentication", + "API key", + "Bearer auth", + "Basic auth", + "Oauth2", + "JWT", + ]; + const apikeySelection = ["Header", "Query"]; - const [name, setName] = useState(""); - const [contact, setContact] = useState(""); - const [file, setFile] = useState(""); - const [fileBase64, setFileBase64] = useState(""); - const [isAppLoaded, setIsAppLoaded] = useState(false); - const [isEditing, setIsEditing] = useState(false); - const [description, setDescription] = useState(""); - const [updater, setUpdater] = useState("tmp") - const [baseUrl, setBaseUrl] = useState(""); - const [actionsModalOpen, setActionsModalOpen] = useState(false); - const [authenticationRequired, setAuthenticationRequired] = useState(false); - const [authenticationOption, setAuthenticationOption] = useState(authenticationOptions[0]); - const [newWorkflowTags, setNewWorkflowTags] = React.useState([]); - const [newWorkflowCategories, setNewWorkflowCategories] = React.useState([]); - const [parameterName, setParameterName] = useState(""); - const [parameterLocation, setParameterLocation] = useState(apikeySelection.length > 0 ? apikeySelection[0] : ""); - const [refreshUrl, setRefreshUrl] = useState(""); - const [oauth2Scopes, setOauth2Scopes] = useState([]); - const [projectCategories, setProjectCategories] = useState([]); - const [selectedCategory, setSelectedCategory] = useState(""); + const [name, setName] = useState(""); + const [contact, setContact] = useState(""); + const [file, setFile] = useState(""); + const [fileBase64, setFileBase64] = useState(""); + const [isAppLoaded, setIsAppLoaded] = useState(false); + const [isEditing, setIsEditing] = useState(false); + const [description, setDescription] = useState(""); + const [updater, setUpdater] = useState("tmp"); + const [baseUrl, setBaseUrl] = useState(""); + const [actionsModalOpen, setActionsModalOpen] = useState(false); + const [authenticationRequired, setAuthenticationRequired] = useState(false); + const [authenticationOption, setAuthenticationOption] = useState( + authenticationOptions[0] + ); + const [newWorkflowTags, setNewWorkflowTags] = React.useState([]); + const [newWorkflowCategories, setNewWorkflowCategories] = React.useState([]); + const [parameterName, setParameterName] = useState(""); + const [parameterLocation, setParameterLocation] = useState( + apikeySelection.length > 0 ? apikeySelection[0] : "" + ); + const [refreshUrl, setRefreshUrl] = useState(""); + const [oauth2Scopes, setOauth2Scopes] = useState([]); + const [projectCategories, setProjectCategories] = useState([]); + const [selectedCategory, setSelectedCategory] = useState(""); - const [urlPath, setUrlPath] = useState(""); - //const [urlPathQueries, setUrlPathQueries] = useState([{"name": "test", "required": false}]); - const [urlPathQueries, setUrlPathQueries] = useState([]); - const [update, setUpdate] = useState("") - const [urlPathParameters, ] = useState([]); - const [firstrequest, setFirstrequest] = React.useState(true) - const [basedata, setBasedata] = React.useState({}) - const [actions, setActions] = useState([]) - const [filteredActions, setFilteredActions] = useState([]) - const [errorCode, setErrorCode] = useState("") - const [appBuilding, setAppBuilding] = useState(false) - const [extraBodyFields, setExtraBodyFields] = useState([]) - const [fileUploadEnabled, setFileUploadEnabled] = useState(false) - const [actionAmount, setActionAmount] = useState(increaseAmount) - const defaultAuth = { - "name": "", - "type": "header", - "example": "", - } - const [extraAuth, setExtraAuth] = useState([]) + const [urlPath, setUrlPath] = useState(""); + //const [urlPathQueries, setUrlPathQueries] = useState([{"name": "test", "required": false}]); + const [urlPathQueries, setUrlPathQueries] = useState([]); + const [update, setUpdate] = useState(""); + const [urlPathParameters] = useState([]); + const [firstrequest, setFirstrequest] = React.useState(true); + const [basedata, setBasedata] = React.useState({}); + const [actions, setActions] = useState([]); + const [filteredActions, setFilteredActions] = useState([]); + const [errorCode, setErrorCode] = useState(""); + const [appBuilding, setAppBuilding] = useState(false); + const [extraBodyFields, setExtraBodyFields] = useState([]); + const [fileUploadEnabled, setFileUploadEnabled] = useState(false); + const [actionAmount, setActionAmount] = useState(increaseAmount); + const defaultAuth = { + name: "", + type: "header", + example: "", + }; + const [extraAuth, setExtraAuth] = useState([]); + const [app, setApp] = useState({}); + const [appAuthentication, setAppAuthentication] = React.useState([]); + const [selectedAction, setSelectedAction] = useState({}); + const [authLoaded, setAuthLoaded] = useState(false); - const [app, setApp] = useState({}) - const [appAuthentication, setAppAuthentication] = React.useState([]); - const [selectedAction, setSelectedAction] = useState({}) - const [authLoaded, setAuthLoaded] = useState(false) + //const [actions, setActions] = useState([{ + // "name": "Get workflows", + // "description": "Get workflows", + // "url": "/workflows", + // "headers": "", + // "queries": [], + // "paths": [], + // "body": "", + // "errors": ["wutface", "WOAH"], + // "method": actionNonBodyRequest[0], + //}, { + // "name": "Get workflow", + // "description": "Get workflow", + // "url": "/workflows/{id}", + // "headers": "", + // "queries": [], + // "paths": ["id"], + // "body": "", + // "errors": ["wutface", "WOAH"], + // "method": actionNonBodyRequest[0], + //}, + // + //]) - //const [actions, setActions] = useState([{ - // "name": "Get workflows", - // "description": "Get workflows", - // "url": "/workflows", - // "headers": "", - // "queries": [], - // "paths": [], - // "body": "", - // "errors": ["wutface", "WOAH"], - // "method": actionNonBodyRequest[0], - //}, { - // "name": "Get workflow", - // "description": "Get workflow", - // "url": "/workflows/{id}", - // "headers": "", - // "queries": [], - // "paths": ["id"], - // "body": "", - // "errors": ["wutface", "WOAH"], - // "method": actionNonBodyRequest[0], - //}, - // - //]) + const [currentActionMethod, setCurrentActionMethod] = useState( + actionNonBodyRequest[0] + ); + const [currentAction, setCurrentAction] = useState({ + name: "", + file_field: "", + description: "", + url: "", + headers: "", + paths: [], + queries: [], + body: "", + errors: [], + example_response: "", + method: actionNonBodyRequest[0], + }); - const [currentActionMethod, setCurrentActionMethod] = useState(actionNonBodyRequest[0]) - const [currentAction, setCurrentAction] = useState({ - "name": "", - "file_field": "", - "description": "", - "url": "", - "headers": "", - "paths": [], - "queries": [], - "body": "", - "errors": [], - "example_response": "", - "method": actionNonBodyRequest[0], - }); + const isCloud = + window.location.host === "localhost:3002" || + window.location.host === "shuffler.io"; - const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" + useEffect(() => { + if (firstrequest) { + setFirstrequest(false); + if (window.location.pathname.includes("apps/edit")) { + setIsEditing(true); + handleEditApp(); + } else { + checkQuery(); + } + } + }); - - useEffect(() => { - if (firstrequest) { - setFirstrequest(false) - if (window.location.pathname.includes("apps/edit")) { - setIsEditing(true) - handleEditApp() - } else { - checkQuery() - } - } - }) - - const handleEditApp = () => { - fetch(globalUrl+"/api/v1/apps/"+props.match.params.appid+"/config", { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - credentials: "include", + const handleEditApp = () => { + fetch(globalUrl + "/api/v1/apps/" + props.match.params.appid + "/config", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", }) - .then((response) => { - if (response.status !== 200) { - window.location.pathname = "/apps" - } - - return response.json() - }) - .then((responseJson) => { - if (responseJson.success === false) { - alert.error("Failed to get the app") - setIsAppLoaded(true) - window.location.pathname = "/search" - } else { - parseIncomingOpenapiData(responseJson) - } - }) - .catch(error => { - alert.error(error.toString()) - }); - } - - - // Checks if there is an ID in the query, and gets it if it doesn't exist. - const checkQuery = () => { - var urlParams = new URLSearchParams(window.location.search) - if (!urlParams.has("id")) { - setActionAmount(0) - - setIsAppLoaded(true) - return - } - - fetch(globalUrl+"/api/v1/get_openapi/"+urlParams.get("id"), { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - credentials: "include", - }) - .then((response) => { - if (response.status !== 200) { - throw new Error("NOT 200 :O") - } - - return response.json() - }) - .then((responseJson) => { - setIsAppLoaded(true) - if (!responseJson.success) { - alert.error("Failed to verify") - } else{ - parseIncomingOpenapiData(responseJson) - - } - }) - .catch(error => { - console.log("Error: ", error.toString()) - alert.error(error.toString()) - }); - } - - const setFileFromb64 = () => { - //const img = document.getElementById('logo') - //var canvas = document.createElement('canvas') - //var ctx = canvas.getContext('2d') - - //img.onload = function() { - // console.log("LOADED?") - // ctx.drawImage(img, 0, 0) - // const canvasUrl = canvas.toDataURL() - // console.log(canvasUrl) - // setFileBase64(canvasUrl) - //} - } - - - const handleGetRef = (parameter, data) => { - try { - if (parameter === null || parameter["$ref"] === undefined) { - //console.log("$ref not found in getref for: ", parameter) - return parameter - } - } catch (e) { - console.log("Failed getting $ref of ", parameter) - return parameter - } - - const paramsplit = parameter["$ref"].split("/") - if (paramsplit[0] !== "#") { - console.log("Bad param: ", paramsplit) - return parameter - } - - var newitem = data - for (var key in paramsplit) { - var tmpparam = paramsplit[key] - if (tmpparam === "#") { - continue - } - - if (newitem[tmpparam] === undefined) { - return parameter - } - - newitem = newitem[tmpparam] - } - - return newitem - } - - const base64_decode = (str) => { - return decodeURIComponent(atob(str).split('').map(function(c) { - return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); - }).join('')) - } - - // Sets the data up as it should be at later points - // This is the data FROM the database, not what's being saved - const parseIncomingOpenapiData = (data) => { - const parsedapp = data.openapi === undefined ? data : JSON.parse(base64_decode(data.openapi)) - data = parsedapp.body === undefined ? parsedapp : parsedapp.body - - var jsonvalid = false - var tmpvalue = "" - try { - data = JSON.parse(data) - jsonvalid = true - } catch (e) { - console.log("Error JSON: ", e) - } - - if (!jsonvalid) { - try { - data = YAML.parse(data) - jsonvalid = true - } catch(e) { - console.log("Error YAML: ", e) - } - } - - if (!jsonvalid) { - alert.info("OpenAPI data is invalid.") - return - } - - setBasedata(data) - if (data.info !== null && data.info !== undefined) { - if (data.info.title !== undefined && data.info.title !== null) { - if (data.info.title.length > 29) { - setName(data.info.title.slice(0, 29)) - } else { - setName(data.info.title) - } - } - - setDescription(data.info.description) - document.title = "Apps - "+data.info.title - - if (data.info["x-logo"] !== undefined) { - - if (data.info["x-logo"].url !== undefined) { - console.log("PARSED LOGO: ", data.info["x-logo"].url) - setFileBase64(data.info["x-logo"].url) - } else { - setFileBase64(data.info["x-logo"]) - } - console.log("") - console.log("") - console.log("LOGO: ", data.info["x-logo"]) - console.log("") - console.log("") - } - - if (data.info.contact !== undefined) { - setContact(data.info.contact) - } - - if (data.info["x-categories"] !== undefined && data.info["x-categories"].length > 0) { - if (typeof(data.info["x-categories"]) == "array") { - } else { - - } - setNewWorkflowCategories(data.info["x-categories"]) - } - } - - if (data.tags !== undefined && data.tags.length > 0) { - var newtags = [] - for (var key in data.tags) { - if (data.tags[key].name.length > 50) { - console.log("Skipping tag cus it's too long: ", data.tags[key].name.length) - continue - } - - newtags.push(data.tags[key].name) - } - - if (newtags.length > 10) { - newtags = newtags.slice(0,9) - } - - setNewWorkflowTags(newtags) - } - - // This is annoying (: - var securitySchemes = data.components.securityDefinitions - if (securitySchemes === undefined) { - securitySchemes = data.securitySchemes - } - - if (securitySchemes === undefined) { - securitySchemes = data.components.securitySchemes - } - - const allowedfunctions = [ - "GET", - "CONNECT", - "HEAD", - "DELETE", - "POST", - "PATCH", - "PUT", - ] - - var newActions = [] - var wordlist = {} - var all_categories = [] - if (data.paths !== null && data.paths !== undefined) { - for (let [path, pathvalue] of Object.entries(data.paths)) { - for (let [method, methodvalue] of Object.entries(pathvalue)) { - if (methodvalue === null) { - alert.info("Skipped method (null)"+method) - continue - } - - if (!allowedfunctions.includes(method.toUpperCase())) { - // Typical YAML issue - if (method !== "parameters") { - console.log("Invalid method: ", method, "data: ", methodvalue) - alert.info("Skipped method (not allowed): "+method) - } - continue - } - - var tmpname = methodvalue.summary - if (methodvalue.operationId !== undefined && methodvalue.operationId !== null && methodvalue.operationId.length > 0 && (tmpname === undefined || tmpname.length === 0)) { - tmpname = methodvalue.operationId - } - - tmpname = tmpname.replaceAll(".", " ") - - var newaction = { - "name": tmpname, - "description": methodvalue.description, - "url": path, - "file_field": "", - "method": method.toUpperCase(), - "headers": "", - "queries": [], - "paths": [], - "body": "", - "errors": [], - "example_response": "", - } - - // Finding category - if (path.includes("/")) { - const pathsplit = path.split("/") - var categoryindex = -1 - // Stupid way of finding a category/grouping - for (var key in pathsplit) { - if (pathsplit[key].length > 0 && pathsplit[key] !== "v1" && pathsplit[key] !== "v2" && pathsplit[key] !== "api" && pathsplit[key] !== "1.0" && pathsplit[key] !== "apis") { - newaction["category"] = pathsplit[key] - if (!all_categories.includes(pathsplit[key])) { - all_categories.push(pathsplit[key]) - } - break - } - } - } - - - // Typescript? I think not ;) - if (methodvalue["requestBody"] !== undefined) { - //console.log("Handle requestbody: ", methodvalue["requestBody"]) - if (methodvalue["requestBody"]["content"] !== undefined) { - if (methodvalue["requestBody"]["content"]["application/json"] !== undefined) { - newaction["headers"] = "Content-Type=application/json\nAccept=application/json" - if (methodvalue["requestBody"]["content"]["application/json"]["schema"] !== undefined && methodvalue["requestBody"]["content"]["application/json"]["schema"] !== null) { - if (methodvalue["requestBody"]["content"]["application/json"]["schema"]["properties"] !== undefined) { - var tmpobject = {} - for (let [prop, propvalue] of Object.entries(methodvalue["requestBody"]["content"]["application/json"]["schema"]["properties"])) { - tmpobject[prop] = `\$\{${prop}\}` - } - - //console.log("Data: ", data) - for (var subkey in methodvalue["requestBody"]["content"]["application/json"]["schema"]["required"]) { - const tmpitem = methodvalue["requestBody"]["content"]["application/json"]["schema"]["required"][subkey] - tmpobject[tmpitem] = `\$\{${tmpitem}\}` - } - - newaction["body"] = JSON.stringify(tmpobject, null, 2) - } else if (methodvalue["requestBody"]["content"]["application/json"]["schema"]["$ref"] !== undefined && methodvalue["requestBody"]["content"]["application/json"]["schema"]["$ref"] !== null) { - const retRef = handleGetRef(methodvalue["requestBody"]["content"]["application/json"]["schema"], data) - var newbody = {} - // Can handle default, required, description and type - for (var propkey in retRef.properties) { - const parsedkey = propkey.replaceAll(" ", "_").toLowerCase() - newbody[parsedkey] = "${"+parsedkey+"}" - } - - newaction["body"] = JSON.stringify(newbody, null, 2) - } - } - } else if (methodvalue["requestBody"]["content"]["application/xml"] !== undefined) { - console.log("METHOD XML: ", methodvalue) - newaction["headers"] = "Content-Type=application/xml\nAccept=application/xml" - if (methodvalue["requestBody"]["content"]["application/xml"]["schema"] !== undefined && methodvalue["requestBody"]["content"]["application/xml"]["schema"] !== null) { - if (methodvalue["requestBody"]["content"]["application/xml"]["schema"]["properties"] !== undefined) { - var tmpobject = {} - for (let [prop, propvalue] of Object.entries(methodvalue["requestBody"]["content"]["application/xml"]["schema"]["properties"])) { - tmpobject[prop] = `\$\{${prop}\}` - } - - for (var subkey in methodvalue["requestBody"]["content"]["application/xml"]["schema"]["required"]) { - const tmpitem = methodvalue["requestBody"]["content"]["application/xml"]["schema"]["required"][subkey] - tmpobject[tmpitem] = `\$\{${tmpitem}\}` - } - - //console.log("OBJ XML: ", tmpobject) - //newaction["body"] = XML.stringify(tmpobject, null, 2) - } - } - } else { - //console.log("REQUESTBODY: ", methodvalue["requestBody"]["content"]) - if (methodvalue["requestBody"]["content"]["example"] !== undefined) { - if (methodvalue["requestBody"]["content"]["example"]["example"] !== undefined) { - newaction["body"] = methodvalue["requestBody"]["content"]["example"]["example"] - //JSON.stringify(tmpobject, null, 2) - } - } else if (methodvalue["requestBody"]["content"]["multipart/form-data"] !== undefined) { - if (methodvalue["requestBody"]["content"]["multipart/form-data"]["schema"] !== undefined && methodvalue["requestBody"]["content"]["multipart/form-data"]["schema"] !== null) { - if (methodvalue["requestBody"]["content"]["multipart/form-data"]["schema"]["type"] === "object") { - const fieldname = methodvalue["requestBody"]["content"]["multipart/form-data"]["schema"]["properties"]["fieldname"] - if (fieldname !== undefined) { - console.log("FIELDNAME: ", fieldname) - newaction.file_field = fieldname["value"] - } - } - } - } else { - - var schemas = [] - const content = methodvalue["requestBody"]["content"] - if (content !== undefined && content !== null) { - //console.log("CONTENT: ", content) - for (const [subkey, subvalue] of Object.entries(content)) { - if (subvalue["schema"] !== undefined) { - //console.log("SCHEMA: ", subvalue["schema"]) - if (subvalue["schema"]["$ref"] !== undefined) { - //console.log("SCHEMA FOUND REF!") - - if (!schemas.includes(subvalue["schema"]["$ref"])) { - schemas.push(subvalue["schema"]["$ref"]) - } - } - } else { - console.log("ERROR: couldn't find schema for ", subvalue, method) - } - } - } - - if (schemas.length === 1) { - const parameter = handleGetRef({"$ref": schemas[0]}, data) - - if (parameter.properties !== undefined && parameter["type"] === "object") { - var newbody = {} - for (var propkey in parameter.properties) { - const parsedkey = propkey.replaceAll(" ", "_").toLowerCase() - if (parameter.properties[propkey].type === undefined) { - console.log("Skipping (4): ", parameter.properties[propkey]) - continue - } - - if (parameter.properties[propkey].type === "string") { - if (parameter.properties[propkey].description !== undefined) { - newbody[parsedkey] = parameter.properties[propkey].description - } else { - newbody[parsedkey] = "" - } - } else if (parameter.properties[propkey].type.includes("int") || parameter.properties[propkey].type.includes("uint64")) { - newbody[parsedkey] = 0 - } else if (parameter.properties[propkey].type.includes("boolean")) { - newbody[parsedkey] = false - } else if (parameter.properties[propkey].type.includes("array")) { - newbody[parsedkey] = [] - } else { - console.log("CANT HANDLE JSON TYPE (4)", parameter.properties[propkey].type, parameter.properties[propkey]) - newbody[parsedkey] = [] - } - } - - newaction["body"] = JSON.stringify(newbody, null, 2) - } else { - console.log("CANT HANDLE PARAM: (4) ", parameter.properties) - } - } - } - } - } - } - - // HAHAHA wtf is this. - if (methodvalue.responses !== undefined && methodvalue.responses !== null) { - if (methodvalue.responses.default !== undefined) { - if (methodvalue.responses.default.content !== undefined) { - if (methodvalue.responses.default.content["text/plain"] !== undefined) { - if (methodvalue.responses.default.content["text/plain"]["schema"] !== undefined) { - if (methodvalue.responses.default.content["text/plain"]["schema"]["example"] !== undefined) { - newaction.example_response = methodvalue.responses.default.content["text/plain"]["schema"]["example"] - } - } - } - } - } else { - var selectedReturn = "" - if (methodvalue.responses["200"] !== undefined) { - selectedReturn = "200" - } else if (methodvalue.responses["201"] !== undefined) { - selectedReturn = "201" - } - - // Parsing examples. This should be standardized lol - if (methodvalue.responses[selectedReturn] !== undefined) { - const selectedExample = methodvalue.responses[selectedReturn] - if (selectedExample["content"] !== undefined) { - if (selectedExample["content"]["application/json"] !== undefined) { - if (selectedExample["content"]["application/json"]["schema"] !== undefined && selectedExample["content"]["application/json"]["schema"] !== null) { - if (selectedExample["content"]["application/json"]["schema"]["$ref"] !== undefined) { - //console.log("REF EXAMPLE: ", selectedExample["content"]["application/json"]["schema"]) - const parameter = handleGetRef(selectedExample["content"]["application/json"]["schema"], data) - //console.log("GOT REF RETURN AS EXAMPLE: ", parameter) - - if (parameter.properties !== undefined && parameter["type"] === "object") { - var newbody = {} - for (var propkey in parameter.properties) { - const parsedkey = propkey.replaceAll(" ", "_").toLowerCase() - if (parameter.properties[propkey].type === undefined) { - console.log("Skipping (1): ", parameter.properties[propkey]) - continue - } - - if (parameter.properties[propkey].type === "string") { - if (parameter.properties[propkey].description !== undefined) { - newbody[parsedkey] = parameter.properties[propkey].description - } else { - newbody[parsedkey] = "" - } - } else if (parameter.properties[propkey].type.includes("int")) { - newbody[parsedkey] = 0 - } else if (parameter.properties[propkey].type.includes("boolean")) { - newbody[parsedkey] = false - } else if (parameter.properties[propkey].type.includes("array")) { - //console.log("Added empty array. Base is: ", parameter.properties[propkey].type) - - //const parameter = handleGetRef(selectedExample["content"]["application/json"]["schema"], data) - newbody[parsedkey] = [] - } else { - console.log("CANT HANDLE JSON TYPE ", parameter.properties[propkey].type, parameter.properties[propkey]) - newbody[parsedkey] = [] - } - } - newaction.example_response = JSON.stringify(newbody, null, 2) - } else { - console.log("CANT HANDLE PARAM: (1) ", parameter.properties) - } - } else { - // Just selecting the first one. bleh. - if (selectedExample["content"]["application/json"]["schema"]["allOf"] !== undefined) { - //console.log("ALLOF: ", selectedExample["content"]["application/json"]["schema"]["allOf"]) - //console.log("BAD EXAMPLE: (SKIP ALLOF) ", selectedExample["content"]["application/json"]["schema"]["allOf"]) - var selectedComponent = selectedExample["content"]["application/json"]["schema"]["allOf"] - if (selectedComponent.length >= 1) { - selectedComponent = selectedComponent[0] - - const parameter = handleGetRef(selectedComponent, data) - if (parameter.properties !== undefined && parameter["type"] === "object") { - var newbody = {} - for (var propkey in parameter.properties) { - const parsedkey = propkey.replaceAll(" ", "_").toLowerCase() - if (parameter.properties[propkey].type === undefined) { - console.log("Skipping (2): ", parameter.properties[propkey]) - continue - } - - if (parameter.properties[propkey].type === "string") { - if (parameter.properties[propkey].description !== undefined) { - newbody[parsedkey] = parameter.properties[propkey].description - } else { - newbody[parsedkey] = "" - } - } else if (parameter.properties[propkey].type.includes("int")) { - newbody[parsedkey] = 0 - } else if (parameter.properties[propkey].type.includes("boolean")) { - newbody[parsedkey] = false - } else { - console.log("CANT HANDLE JSON TYPE (2) ", parameter.properties[propkey].type) - newbody[parsedkey] = [] - } - } - - newaction.example_response = JSON.stringify(newbody, null, 2) - //newaction.example_response = JSON.stringify(parameter.properties, null, 2) - } else { - //newaction.example_response = parameter.properties - console.log("CANT HANDLE PARAM: (3) ", parameter.properties) - } - } else { - - } - } else if (selectedExample["content"]["application/json"]["schema"]["properties"] !== undefined) { - if (selectedExample["content"]["application/json"]["schema"]["properties"]["data"] !== undefined) { - const parameter = handleGetRef(selectedExample["content"]["application/json"]["schema"]["properties"]["data"], data) - if (parameter.properties !== undefined && parameter["type"] === "object") { - var newbody = {} - for (var propkey in parameter.properties) { - const parsedkey = propkey.replaceAll(" ", "_").toLowerCase() - if (parameter.properties[propkey].type === undefined) { - console.log("Skipping (3): ", parameter.properties[propkey]) - continue - } - - if (parameter.properties[propkey].type === "string") { - if (parameter.properties[propkey].description !== undefined) { - newbody[parsedkey] = parameter.properties[propkey].description - } else { - newbody[parsedkey] = "" - } - console.log(parameter.properties[propkey]) - } else if (parameter.properties[propkey].type.includes("int")) { - newbody[parsedkey] = 0 - } else { - console.log("CANT HANDLE JSON TYPE (3) ", parameter.properties[propkey].type) - newbody[parsedkey] = [] - } - } - - newaction.example_response = JSON.stringify(newbody, null, 2) - //newaction.example_response = JSON.stringify(parameter.properties, null, 2) - } else { - //newaction.example_response = parameter.properties - console.log("CANT HANDLE PARAM: (3) ", parameter.properties) - } - } - } - } - } - } - } - } - } - } - - for (var key in methodvalue.parameters) { - const parameter = handleGetRef(methodvalue.parameters[key], data) - if (parameter.in === "query") { - var tmpaction = { - "description": parameter.description, - "name": parameter.name, - "required": parameter.required, - "in": "query", - } - - if (parameter.required === undefined) { - tmpaction.required = false - } - - newaction.queries.push(tmpaction) - } else if (parameter.in === "path") { - // FIXME - parse this to the URL too - newaction.paths.push(parameter.name) - - // FIXME: This doesn't follow OpenAPI3 exactly. - // https://swagger.io/docs/specification/describing-request-body/ - // https://swagger.io/docs/specification/describing-parameters/ - // Need to split the data. - } else if (parameter.in === "body") { - // FIXME: Add tracking for components - // E.G: https://raw.githubusercontent.com/owentl/Shuffle/master/gosecure.yaml - if (parameter.example !== undefined) { - newaction.body = parameter.example - } - } else if (parameter.in === "header") { - newaction.headers += `${parameter.name}=${parameter.example}\n` - } else { - console.log("WARNING: don't know how to handle this param: ", parameter) - } - } - - - if (newaction.name === "" || newaction.name === undefined) { - // Find a unique part of the string - // FIXME: Looks for length between /, find the one where they differ - // Should find others with the same START to their path - // Make a list of reserved names? Aka things that show up only once - if (Object.getOwnPropertyNames(wordlist).length === 0) { - for (let [newpath, pathvalue] of Object.entries(data.paths)) { - const newpathsplit = newpath.split("/") - for(var key in newpathsplit) { - const pathitem = newpathsplit[key].toLowerCase() - if (wordlist[pathitem] === undefined) { - wordlist[pathitem] = 1 - } else { - wordlist[pathitem] += 1 - } - } - } - } - - //console.log("WORDLIST: ", wordlist) - - // Remove underscores and make it normal with upper case etc - const urlsplit = path.split("/") - if (urlsplit.length > 0) { - var curname = "" - for(var key in urlsplit) { - var subpath = urlsplit[key] - if (wordlist[subpath] > 2 || subpath.length < 1) { - continue - } - - curname = subpath - break - } - - // FIXME: If name exists, - // FIXME: Check if first part of parsedname is verb, otherwise use method - const parsedname = curname.split("_").join(" ").split("-").join(" ").split("{").join(" ").split("}").join(" ").trim() - if (parsedname.length === 0) { - newaction.errors.push("Missing name") - } else { - const newname = method.charAt(0).toUpperCase() + method.slice(1) + " " + parsedname - const searchactions = newActions.find(data => data.name === newname) - console.log("SEARCH: ", searchactions) - if (searchactions !== undefined) { - newaction.errors.push("Missing name") - } else { - newaction.name = newname - } - } - - } else { - newaction.errors.push("Missing name") - } - } - - newActions.push(newaction) - } - } - - if (data.servers !== undefined && data.servers.length > 0) { - var firstUrl = data.servers[0].url - if (firstUrl.includes("{") && firstUrl.includes("}") && data.servers[0].variables !== undefined) { - const regex = /{\w+}/g - const found = firstUrl.match(regex) - if (found !== null) { - for (var key in found) { - const item = found[key].slice(1, found[key].length-1) - const foundVar = data.servers[0].variables[item] - if (foundVar["default"] !== undefined) { - firstUrl = firstUrl.replace(found[key], foundVar["default"]) - } - } - - } - } - - if (firstUrl.endsWith("/")) { - setBaseUrl(firstUrl.slice(0, firstUrl.length-1)) - } else { - setBaseUrl(firstUrl) - } - } - } - - - //console.log("SECURITYSCHEMES: ", securitySchemes) - if (securitySchemes !== undefined) { - // FIXME: Should add Oauth2 (Microsoft) and JWT (Wazuh) - //console.log("SECURITY: ", securitySchemes) - //if (Object.entries(securitySchemes) > 1 && - var newauth = [] - for (const [key, value] of Object.entries(securitySchemes)) { - console.log(key, value) - if (key === "jwt") { - setAuthenticationOption("JWT") - setAuthenticationRequired(true) - - if (value.in !== undefined && value.in !== null && value.in.length > 0) { - setParameterName(value.in) - } - } else if (value.scheme === "bearer") { - setAuthenticationOption("Bearer auth") - setAuthenticationRequired(true) - } else if (key === "Oauth2" || key === "Oauth2c") { - //alert.info("Can't handle Oauth2 auth yet.") - setAuthenticationOption("Oauth2") - setAuthenticationRequired(true) - - //console.log("FLOW-1: ", value) - const flowkey = value.flow === undefined ? "flows" : "flow" - //console.log("FLOW: ", value[flowkey]) - const basekey = value[flowkey].authorizationCode !== undefined ? "authorizationCode" : "implicit" - //console.log("FLOW2: ", value[flowkey][basekey]) - if (value[flowkey] !== undefined && value[flowkey][basekey] !== undefined) { - if (value[flowkey][basekey].authorizationUrl !== undefined && parameterName.length === 0) { - setParameterName(value[flowkey][basekey].authorizationUrl) - } - - var tokenUrl = "" - if (value[flowkey][basekey].tokenUrl !== undefined) { - setParameterLocation(value[flowkey][basekey].tokenUrl) - tokenUrl = value[flowkey][basekey].tokenUrl - } else { - setParameterLocation("") - } - - if (value[flowkey][basekey].refreshUrl !== undefined) { - setRefreshUrl(value[flowkey][basekey].refreshUrl) - } else if (tokenUrl.length > 0) { - setRefreshUrl(tokenUrl) - } - - if (value[flowkey][basekey].scopes !== undefined && value[flowkey][basekey].scopes !== null) { - if (value[flowkey][basekey].scopes.length > 0) { - setOauth2Scopes(value[flowkey][basekey].scopes) - } else { - var newscopes = [] - for (let [scopekey, scopevalue] of Object.entries(value[flowkey][basekey].scopes)) { - if (scopekey.startsWith("http")) { - const scopekeysplit = scopekey.split("/") - if (scopekeysplit.length < 5) { - console.log("Skipping scope: ", scopekey) - alert.info("Skipping scope: "+scopekey) - continue - } - - //console.log("Checking scope for: ", scopekey, scopekeysplit.length) - } - - newscopes.push(scopekey) - } - - setOauth2Scopes(newscopes) - } - } - } else { - console.log("Bad flowkey and basekey for oauth2: ", flowkey, basekey) - } - - } else if (key === "ApiKeyAuth") { - setAuthenticationOption("API key") - - value.in = value.in.charAt(0).toUpperCase() + value.in.slice(1); - setParameterLocation(value.in) - if (!apikeySelection.includes(value.in)) { - console.log("APIKEY SELECT: ", apikeySelection) - alert.error("Might be error in setting up API key authentication") - } - - console.log("PARAM NAME: ", value.name) - setParameterName(value.name) - setAuthenticationRequired(true) - } else if (value.scheme === "basic") { - setAuthenticationOption("Basic auth") - setAuthenticationRequired(true) - } else if (value.scheme === "oauth2") { - setAuthenticationOption("Oauth2") - setAuthenticationRequired(true) - } else { - alert.error("Couldn't handle AUTH type: ", key) - //newauth.push({ - // "name": key, - // "type": value.in, - // "example": "", - //}) - } - } - - if (newauth.length > 0) { - setExtraAuth(newauth) - } - } - - if (newActions.length > increaseAmount-1) { - setActionAmount(increaseAmount) - } else { - setActionAmount(newActions.length) - } - - //const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" - if (newActions.length > 1000 && isCloud) { - alert.error("Cut down actions from "+newActions.length+" to 999 because of limit") - newActions = newActions.slice(0,999) - } - - setProjectCategories(all_categories) - setActions(newActions) - setFilteredActions(newActions) - setIsAppLoaded(true) - } - - // Saving the app that's been configured. - const submitApp = () => { - alert.info("Uploading and building app " + name) - setAppBuilding(true) - setErrorCode("") - - // Format the information - const splitBase = baseUrl.split("/") - const host = splitBase[2] - const schemes = [splitBase[0]] - const basePath = "/"+(splitBase.slice(3, )).join("/") - - const data = { - "openapi": "3.0.0", - "info": { - "title": name, - "description": description, - "version": "1.0", - "x-logo": fileBase64, - }, - "servers": [{"url": baseUrl}], - "host": host, - "basePath": basePath, - "schemes": schemes, - "paths": {}, - "editing": isEditing, - "components": { - "securitySchemes": {}, - }, - "id": props.match.params.appid, - } - - if (basedata.info !== undefined && basedata.info.contact !== undefined) { - data.info["contact"] = basedata.info.contact - } else if (contact === "") { - data.info["contact"] = { - "name": "@frikkylikeme", - "url": "https://twitter.com/frikkylikeme", - "email": "frikky@shuffler.io", - } - } else { - data.info["contact"] = contact - } - - if (newWorkflowTags.length > 0) { - var newtags = [] - for (var key in newWorkflowTags) { - newtags.push({"name": newWorkflowTags[key]}) - } - - data["tags"] = newtags - } - - if (newWorkflowCategories.length > 0) { - data["info"]["x-categories"] = newWorkflowCategories - } - - // Handles actions - for (var key in actions) { - var item = actions[key] - if (item.errors.length > 0) { - alert.error("Saving with error in action "+item.name) - } - - if (item.name === undefined && item.description !== undefined) { - item.name = item.description - } - - if (data.paths[item.url] === null || data.paths[item.url] === undefined) { - data.paths[item.url] = {} - } - - const regex = /[A-Za-z0-9 _]/g; - if (item.name === undefined) { - console.log("Skipping action ", item) - continue - } - - const found = item.name.match(regex); - if (found !== null) { - item.name = found.join("") - } - - data.paths[item.url][item.method.toLowerCase()] = { - "responses": { - "default": { - "description": "default", - "content": { - "text/plain": { - "schema": { - "type": "string", - "example": "", - }, - }, - }, - } - }, - "summary": item.name, - "operationId": item.name.split(" ").join("_"), - "description": item.description, - "parameters": [], - "requestBody": { - "content": { - - } - }, - } - - //console.log("ACTION: ", item) - - if (item.example_response !== undefined && item.example_response.length > 0) { - // FIXME: Shallow copy of the string - var showResult = Object.assign("", item.example_response).trim() - showResult = showResult.split(" None").join(" \"None\"") - showResult = showResult.split("\'").join("\"") - showResult = showResult.split(" False").join(" false") - showResult = showResult.split(" True").join(" true") - - var jsonvalid = true - try { - const tmp = String(JSON.parse(showResult)) - if (!showResult.includes("{") && !showResult.includes("[")) { - jsonvalid = false - } - } catch (e) { - jsonvalid = false - } - - data.paths[item.url][item.method.toLowerCase()].responses["default"]["content"]["text/plain"].schema.type = "string" - if (jsonvalid) { - // FIXME: Add a JSON parser here - don't run it as a string. - data.paths[item.url][item.method.toLowerCase()].responses["default"]["content"]["text/plain"].schema.example = showResult - - } else { - data.paths[item.url][item.method.toLowerCase()].responses["default"]["content"]["text/plain"].schema.example = item.example_response - - } - } - - if (item.queries.length > 0) { - var skipped = false - for (var querykey in item.queries) { - const queryitem = item.queries[querykey] - - if (queryitem.name.toLowerCase() == "url") { - console.log(item.name+" uses a bad query: url") - continue - //skipped = true - //break - } - - if (queryitem.name.toLowerCase() == "url" - || queryitem.name.toLowerCase() == "body" - || queryitem.name.toLowerCase() == "self" - || queryitem.name.toLowerCase() == "ssl_verify" - || queryitem.name.toLowerCase() == "queries" - || queryitem.name.toLowerCase() == "headers" - || queryitem.name.includes("[") - || queryitem.name.includes("]") - || queryitem.name.includes("{") - || queryitem.name.includes("}") - || queryitem.name.includes("(") - || queryitem.name.includes(")") - || queryitem.name.includes("!") - || queryitem.name.includes("@") - || queryitem.name.includes("#") - || queryitem.name.includes("$") - || queryitem.name.includes("%") - || queryitem.name.includes("^") - || queryitem.name.includes("&") - || queryitem.name.includes(":") - || queryitem.name.includes(";") - || queryitem.name.includes("<") - || queryitem.name.includes(">") - || queryitem.name.includes("\"") - || queryitem.name.includes("\'") - ) { - console.log(item.name+" error: uses a bad query - not adding: ", queryitem.name) - continue - } - - var newitem = { - "in": "query", - "name": queryitem.name, - "description": "Generated by shuffler.io OpenAPI", - "required": queryitem.required, - "schema": { - "type": "string", - }, - } - - if (queryitem.description !== undefined) { - newitem.description = queryitem.description - } - - data.paths[item.url][item.method.toLowerCase()].parameters.push(newitem) - //console.log(queryitem) - } - - // Bad code as it doesn't allow for "anything". - if (skipped) { - alert.info("Bad configuration of "+item.name+". Skipping because queries are invalid.") - continue - } - } - //data.paths[item.url][item.method.toLowerCase()].parameters.push(newitem) - - if (item.paths.length > 0) { - for (querykey in item.paths) { - const queryitem = item.paths[querykey] - - if (queryitem.toLowerCase() == "url") { - queryitem = "action_url" - } - - if (queryitem.toLowerCase() == "apikey") { - queryitem = "action_apikey" - } - - newitem = { - "in": "path", - "name": queryitem, - "description": "Generated by shuffler.io OpenAPI", - "required": true, - "schema": { - "type": "string", - }, - } - - if (queryitem.description !== undefined) { - newitem.description = queryitem.description - } - - data.paths[item.url][item.method.toLowerCase()].parameters.push(newitem) - //console.log(queryitem) - } - } else { - // Always goes here if they didn't click anything :/ - const values = getCurrentPaths(item.url) - const paths = values[0] - - for (querykey in paths) { - const queryitem = paths[querykey] - newitem = { - "in": "path", - "name": queryitem, - "description": "Generated by shuffler.io OpenAPI", - "required": true, - "schema": { - "type": "string", - }, - } - - if (queryitem.description !== undefined) { - newitem.description = queryitem.description - } - - data.paths[item.url][item.method.toLowerCase()].parameters.push(newitem) - //console.log(queryitem) - } - } - - if (item.body !== undefined && item.body !== null && item.body.length > 0) { - const required = false - newitem = { - "in": "body", - "name": "body", - "multiline": true, - "description": "Generated by shuffler.io OpenAPI", - "required": required, - "example": item.body, - "schema": { - "type": "string", - }, - } - - // FIXME - add application/json if JSON example? - data.paths[item.url][item.method.toLowerCase()]["requestBody"] = { - "description": "Generated by Shuffler.io", - "required": required, - "content": { - "example": { - "example": item.body, - }, - }, - } - - data.paths[item.url][item.method.toLowerCase()].parameters.push(newitem) - } else if (actionBodyRequest.includes(item.method.toUpperCase())) { - // Appending an empty field - const required = false - newitem = { - "in": "body", - "name": "body", - "multiline": true, - "description": "Generated by shuffler.io OpenAPI", - "required": required, - "example": "", - "schema": { - "type": "string", - }, - } - - // FIXME - add application/json if JSON example? - data.paths[item.url][item.method.toLowerCase()]["requestBody"] = { - "description": "Generated by Shuffler.io", - "required": required, - "content": { - "example": { - "example": "", - }, - }, - } - - data.paths[item.url][item.method.toLowerCase()].parameters.push(newitem) - } else { - //console.log("Nothing to append?") - } - - // https://swagger.io/docs/specification/describing-request-body/file-upload/ - if (item.file_field !== undefined && item.file_field !== null && item.file_field.length > 0) { - console.log("HANDLE FILEFIELD SAVE: ", item.file_field) - data.paths[item.url][item.method.toLowerCase()]["requestBody"]["content"]["multipart/form-data"] = { - - "schema": { - "type": "object", - "properties": { - "fieldname": { - "type": "string", - "value": item.file_field, - }, - }, - }, - } - - console.log(data.paths[item.url][item.method.toLowerCase()]["requestBody"]["content"]["multipart/form-data"]) - } - - if (item.headers.length > 0) { - const required = false - - const headersSplit = item.headers.split("\n") - for (var key in headersSplit) { - const header = headersSplit[key] - var key = "" - var value = "" - if (header.length > 0 && header.includes("= ")) { - const headersplit = header.split("= ") - key = headersplit[0] - value = headersplit[1] - } else if (header.length > 0 && header.includes(" =")) { - const headersplit = header.split(" =") - key = headersplit[0] - value = headersplit[1] - } else if (header.length > 0 && header.includes("=")) { - const headersplit = header.split("=") - key = headersplit[0] - value = headersplit[1] - } else if (header.length > 0 && header.includes(": ")) { - const headersplit = header.split(": ") - key = headersplit[0] - value = headersplit[1] - } else if (header.length > 0 && header.includes(" :")) { - const headersplit = header.split(" :") - key = headersplit[0] - value = headersplit[1] - } else if (header.length > 0 && header.includes(":")) { - const headersplit = header.split(":") - key = headersplit[0] - value = headersplit[1] - } else { - continue - } - - if (key.length > 0 && value.length > 0) { - newitem = { - "in": "header", - "name": key, - "multiline": false, - "description": "Header generated by shuffler.io OpenAPI", - "required": false, - "example": value, - "schema": { - "type": "string", - }, - } - - data.paths[item.url][item.method.toLowerCase()].parameters.push(newitem) - } - } - } - } - - if (authenticationOption === "API key") { - if (parameterName.length === 0) { - alert.error("A field name for the APIkey must be defined") - setAppBuilding(false) - return - } - - var newparamName = parameterName.replaceAll("\"", "") - newparamName = newparamName.replaceAll("\'", "") - - data.components.securitySchemes["ApiKeyAuth"] = { - "type": "apiKey", - "in": parameterLocation.toLowerCase(), - "name": newparamName, - } - } else if (authenticationOption === "Bearer auth") { - data.components.securitySchemes["BearerAuth"] = { - "type": "http", - "scheme": "bearer", - "bearerFormat": "UUID", - } - } else if (authenticationOption === "JWT") { - data.components.securitySchemes["jwt"] = { - "type": "http", - "scheme": "bearer", - "bearerFormat": "JWT", - "in": parameterName, - } - - console.log("SECURITYSCHEMES: ", data.components) - } else if (authenticationOption === "Basic auth") { - data.components.securitySchemes["BasicAuth"] = { - "type": "http", - "scheme": "basic", - } - } else if (authenticationOption === "Oauth2") { - var newparamName = parameterName.replaceAll("\"", "") - newparamName = newparamName.replaceAll("\'", "") - - //parameterName, parameterValue, revocationUrl - data.components.securitySchemes["Oauth2"] = { - "type": "oauth2", - "description": "Oauth2.0 authorizationCode authentication", - "flow": { - "authorizationCode": { - "authorizationUrl": newparamName, - "tokenUrl": parameterLocation, - "refreshUrl": refreshUrl, - "scopes": oauth2Scopes === undefined || oauth2Scopes === null ? [] : oauth2Scopes, - }, - }, - } - } - - if (setExtraAuth.length > 0) { - for (var key in extraAuth) { - const curauth = extraAuth[key] - - if (curauth.name.toLowerCase() == "url") { - alert.error("Can't add extra auth with Name URL") - setAppBuilding(false) - return - } - - data.components.securitySchemes[curauth.name] = { - "type": "apiKey", - "in": curauth.type, - "name": curauth.name, - } - } - } - - fetch(globalUrl+"/api/v1/verify_openapi", { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - body: JSON.stringify(data, null, 4), - credentials: "include", - }) - .then((response) => { - //if (response.status !== 200) { - // setErrorCode("An error occurred during validation") - // throw new Error("NOT 200 :O") - //} - - setAppBuilding(false) - return response.json() - }) - .then((responseJson) => { - if (!responseJson.success) { - if (responseJson.reason !== undefined) { - setErrorCode(responseJson.reason) - alert.error("Failed to verify: "+responseJson.reason) - } - } else { - alert.success("Successfully uploaded openapi") - if (window.location.pathname.includes("/new")) { - if (responseJson.id !== undefined && responseJson.id !== null) { - window.location = `/apps/edit/${responseJson.id}` - } - } - } - }) - .catch(error => { - setAppBuilding(false) - setErrorCode(error.toString()) - alert.error(error.toString()) - }); - } - - const bearerAuth = authenticationOption === "Bearer auth" ? -
-

- - Bearer auth - -

- Users will be required to submit their API as the header "Authorization: Bearer APIKEY" -
- : null - - // Basicauth - const basicAuth = authenticationOption === "Basic auth" ? -
-

- - Basic authentication - -

- Users will be required to submit a valid username and password before using the API -
- : null - - // API key - //const verifyBaseUrl = () => { - // if (baseUrl.startsWith("http://") || baseUrl.startsWith("https://")) { - // return true - // } - - // if (baseUrl.endsWith("/")) { - // return true - // } - // - // return false - //} - - //const verifyApiParameter = () => { - // const notAllowed = ["!","#","$","%","&","'","^","+","-",".","_","~","|","]","+","$",] - // for (var key in notAllowed) { - // if (parameterName.includes(notAllowed[key])) { - // return false - // } - // } - - // return true - //} - - const testAction = (index) => { - console.log("Should test action at index "+index) - console.log(actions[index]) - } - - const addPathQuery = () => { - urlPathQueries.push({"name": "", "required": true}) - if (updater === "addupdater") { - setUpdater("updater") - } else { - setUpdater("addupdater") - } - setUrlPathQueries(urlPathQueries) - } - - const flipRequired = (index) => { - urlPathQueries[index].required = !urlPathQueries[index].required - if (updater === "flipupdater") { - setUpdater("updater") - } else { - setUpdater("flipupdater") - } - setUrlPathQueries(urlPathQueries) - - } - - const deletePathQuery = (index) => { - urlPathQueries.splice(index, 1) - if (updater === "deleteupdater") { - setUpdater("updater") - } else { - setUpdater("deleteupdater") - } - setUrlPathQueries(urlPathQueries) - } - - const duplicateAction = (index) => { - var newAction = JSON.parse(JSON.stringify(actions[index])) - newAction.name = newAction.name+"_copy" - newAction.errors.push("Can't have the same name") - - actions.push(newAction) - - if (actions.length > actionAmount) { - setActionAmount(actions.length) - } - - setActions(actions) - setFilteredActions(actions) - setUpdate(Math.random()) - } - - const deleteAction = (index) => { - actions.splice(index, 1) - setCurrentAction({ - "name": "", - "description": "", - "url": "", - "file_field": "", - "headers": "", - "paths": [], - "queries": [], - "body": "", - "errors": [], - "method": actionNonBodyRequest[0], - }) - - setActions(actions) - setFilteredActions(actions) - setActionAmount(actionAmount-1) - setUpdate(Math.random()) - } - - //console.log("Option: ", authenticationOption) - //console.log("Location: ", parameterLocation) + .then((response) => { + if (response.status !== 200) { + window.location.pathname = "/apps"; + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === false) { + alert.error("Failed to get the app"); + setIsAppLoaded(true); + window.location.pathname = "/search"; + } else { + parseIncomingOpenapiData(responseJson); + } + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; + + // Checks if there is an ID in the query, and gets it if it doesn't exist. + const checkQuery = () => { + var urlParams = new URLSearchParams(window.location.search); + if (!urlParams.has("id")) { + setActionAmount(0); + + setIsAppLoaded(true); + return; + } + + fetch(globalUrl + "/api/v1/get_openapi/" + urlParams.get("id"), { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + throw new Error("NOT 200 :O"); + } + + return response.json(); + }) + .then((responseJson) => { + setIsAppLoaded(true); + if (!responseJson.success) { + alert.error("Failed to verify"); + } else { + parseIncomingOpenapiData(responseJson); + } + }) + .catch((error) => { + console.log("Error: ", error.toString()); + alert.error(error.toString()); + }); + }; + + const setFileFromb64 = () => { + //const img = document.getElementById('logo') + //var canvas = document.createElement('canvas') + //var ctx = canvas.getContext('2d') + //img.onload = function() { + // console.log("LOADED?") + // ctx.drawImage(img, 0, 0) + // const canvasUrl = canvas.toDataURL() + // console.log(canvasUrl) + // setFileBase64(canvasUrl) + //} + }; + + const handleGetRef = (parameter, data) => { + try { + if (parameter === null || parameter["$ref"] === undefined) { + //console.log("$ref not found in getref for: ", parameter) + return parameter; + } + } catch (e) { + console.log("Failed getting $ref of ", parameter); + return parameter; + } + + const paramsplit = parameter["$ref"].split("/"); + if (paramsplit[0] !== "#") { + console.log("Bad param: ", paramsplit); + return parameter; + } + + var newitem = data; + for (var key in paramsplit) { + var tmpparam = paramsplit[key]; + if (tmpparam === "#") { + continue; + } + + if (newitem[tmpparam] === undefined) { + return parameter; + } + + newitem = newitem[tmpparam]; + } + + return newitem; + }; + + const base64_decode = (str) => { + return decodeURIComponent( + atob(str) + .split("") + .map(function (c) { + return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2); + }) + .join("") + ); + }; + + // Sets the data up as it should be at later points + // This is the data FROM the database, not what's being saved + const parseIncomingOpenapiData = (data) => { + const parsedapp = + data.openapi === undefined + ? data + : JSON.parse(base64_decode(data.openapi)); + data = parsedapp.body === undefined ? parsedapp : parsedapp.body; + + var jsonvalid = false; + var tmpvalue = ""; + try { + data = JSON.parse(data); + jsonvalid = true; + } catch (e) { + console.log("Error JSON: ", e); + } + + if (!jsonvalid) { + try { + data = YAML.parse(data); + jsonvalid = true; + } catch (e) { + console.log("Error YAML: ", e); + } + } + + if (!jsonvalid) { + alert.info("OpenAPI data is invalid."); + return; + } + + setBasedata(data); + if (data.info !== null && data.info !== undefined) { + if (data.info.title !== undefined && data.info.title !== null) { + if (data.info.title.length > 29) { + setName(data.info.title.slice(0, 29)); + } else { + setName(data.info.title); + } + } + + setDescription(data.info.description); + document.title = "Apps - " + data.info.title; + + if (data.info["x-logo"] !== undefined) { + if (data.info["x-logo"].url !== undefined) { + console.log("PARSED LOGO: ", data.info["x-logo"].url); + setFileBase64(data.info["x-logo"].url); + } else { + setFileBase64(data.info["x-logo"]); + } + console.log(""); + console.log(""); + console.log("LOGO: ", data.info["x-logo"]); + console.log(""); + console.log(""); + } + + if (data.info.contact !== undefined) { + setContact(data.info.contact); + } + + if ( + data.info["x-categories"] !== undefined && + data.info["x-categories"].length > 0 + ) { + if (typeof data.info["x-categories"] == "array") { + } else { + } + setNewWorkflowCategories(data.info["x-categories"]); + } + } + + if (data.tags !== undefined && data.tags.length > 0) { + var newtags = []; + for (var key in data.tags) { + if (data.tags[key].name.length > 50) { + console.log( + "Skipping tag cus it's too long: ", + data.tags[key].name.length + ); + continue; + } + + newtags.push(data.tags[key].name); + } + + if (newtags.length > 10) { + newtags = newtags.slice(0, 9); + } + + setNewWorkflowTags(newtags); + } + + // This is annoying (: + var securitySchemes = data.components.securityDefinitions; + if (securitySchemes === undefined) { + securitySchemes = data.securitySchemes; + } + + if (securitySchemes === undefined) { + securitySchemes = data.components.securitySchemes; + } + + const allowedfunctions = [ + "GET", + "CONNECT", + "HEAD", + "DELETE", + "POST", + "PATCH", + "PUT", + ]; + + var newActions = []; + var wordlist = {}; + var all_categories = []; + if (data.paths !== null && data.paths !== undefined) { + for (let [path, pathvalue] of Object.entries(data.paths)) { + for (let [method, methodvalue] of Object.entries(pathvalue)) { + if (methodvalue === null) { + alert.info("Skipped method (null)" + method); + continue; + } + + if (!allowedfunctions.includes(method.toUpperCase())) { + // Typical YAML issue + if (method !== "parameters") { + console.log("Invalid method: ", method, "data: ", methodvalue); + alert.info("Skipped method (not allowed): " + method); + } + continue; + } + + var tmpname = methodvalue.summary; + if ( + methodvalue.operationId !== undefined && + methodvalue.operationId !== null && + methodvalue.operationId.length > 0 && + (tmpname === undefined || tmpname.length === 0) + ) { + tmpname = methodvalue.operationId; + } + + tmpname = tmpname.replaceAll(".", " "); + + var newaction = { + name: tmpname, + description: methodvalue.description, + url: path, + file_field: "", + method: method.toUpperCase(), + headers: "", + queries: [], + paths: [], + body: "", + errors: [], + example_response: "", + }; + + // Finding category + if (path.includes("/")) { + const pathsplit = path.split("/"); + var categoryindex = -1; + // Stupid way of finding a category/grouping + for (var key in pathsplit) { + if ( + pathsplit[key].length > 0 && + pathsplit[key] !== "v1" && + pathsplit[key] !== "v2" && + pathsplit[key] !== "api" && + pathsplit[key] !== "1.0" && + pathsplit[key] !== "apis" + ) { + newaction["category"] = pathsplit[key]; + if (!all_categories.includes(pathsplit[key])) { + all_categories.push(pathsplit[key]); + } + break; + } + } + } + + // Typescript? I think not ;) + if (methodvalue["requestBody"] !== undefined) { + //console.log("Handle requestbody: ", methodvalue["requestBody"]) + if (methodvalue["requestBody"]["content"] !== undefined) { + if ( + methodvalue["requestBody"]["content"]["application/json"] !== + undefined + ) { + newaction["headers"] = + "Content-Type=application/json\nAccept=application/json"; + if ( + methodvalue["requestBody"]["content"]["application/json"][ + "schema" + ] !== undefined && + methodvalue["requestBody"]["content"]["application/json"][ + "schema" + ] !== null + ) { + if ( + methodvalue["requestBody"]["content"]["application/json"][ + "schema" + ]["properties"] !== undefined + ) { + var tmpobject = {}; + for (let [prop, propvalue] of Object.entries( + methodvalue["requestBody"]["content"]["application/json"][ + "schema" + ]["properties"] + )) { + tmpobject[prop] = `\$\{${prop}\}`; + } + + //console.log("Data: ", data) + for (var subkey in methodvalue["requestBody"]["content"][ + "application/json" + ]["schema"]["required"]) { + const tmpitem = + methodvalue["requestBody"]["content"][ + "application/json" + ]["schema"]["required"][subkey]; + tmpobject[tmpitem] = `\$\{${tmpitem}\}`; + } + + newaction["body"] = JSON.stringify(tmpobject, null, 2); + } else if ( + methodvalue["requestBody"]["content"]["application/json"][ + "schema" + ]["$ref"] !== undefined && + methodvalue["requestBody"]["content"]["application/json"][ + "schema" + ]["$ref"] !== null + ) { + const retRef = handleGetRef( + methodvalue["requestBody"]["content"]["application/json"][ + "schema" + ], + data + ); + var newbody = {}; + // Can handle default, required, description and type + for (var propkey in retRef.properties) { + const parsedkey = propkey + .replaceAll(" ", "_") + .toLowerCase(); + newbody[parsedkey] = "${" + parsedkey + "}"; + } + + newaction["body"] = JSON.stringify(newbody, null, 2); + } + } + } else if ( + methodvalue["requestBody"]["content"]["application/xml"] !== + undefined + ) { + console.log("METHOD XML: ", methodvalue); + newaction["headers"] = + "Content-Type=application/xml\nAccept=application/xml"; + if ( + methodvalue["requestBody"]["content"]["application/xml"][ + "schema" + ] !== undefined && + methodvalue["requestBody"]["content"]["application/xml"][ + "schema" + ] !== null + ) { + if ( + methodvalue["requestBody"]["content"]["application/xml"][ + "schema" + ]["properties"] !== undefined + ) { + var tmpobject = {}; + for (let [prop, propvalue] of Object.entries( + methodvalue["requestBody"]["content"]["application/xml"][ + "schema" + ]["properties"] + )) { + tmpobject[prop] = `\$\{${prop}\}`; + } + + for (var subkey in methodvalue["requestBody"]["content"][ + "application/xml" + ]["schema"]["required"]) { + const tmpitem = + methodvalue["requestBody"]["content"][ + "application/xml" + ]["schema"]["required"][subkey]; + tmpobject[tmpitem] = `\$\{${tmpitem}\}`; + } + + //console.log("OBJ XML: ", tmpobject) + //newaction["body"] = XML.stringify(tmpobject, null, 2) + } + } + } else { + //console.log("REQUESTBODY: ", methodvalue["requestBody"]["content"]) + if ( + methodvalue["requestBody"]["content"]["example"] !== undefined + ) { + if ( + methodvalue["requestBody"]["content"]["example"][ + "example" + ] !== undefined + ) { + newaction["body"] = + methodvalue["requestBody"]["content"]["example"][ + "example" + ]; + //JSON.stringify(tmpobject, null, 2) + } + } else if ( + methodvalue["requestBody"]["content"][ + "multipart/form-data" + ] !== undefined + ) { + if ( + methodvalue["requestBody"]["content"][ + "multipart/form-data" + ]["schema"] !== undefined && + methodvalue["requestBody"]["content"][ + "multipart/form-data" + ]["schema"] !== null + ) { + if ( + methodvalue["requestBody"]["content"][ + "multipart/form-data" + ]["schema"]["type"] === "object" + ) { + const fieldname = + methodvalue["requestBody"]["content"][ + "multipart/form-data" + ]["schema"]["properties"]["fieldname"]; + if (fieldname !== undefined) { + console.log("FIELDNAME: ", fieldname); + newaction.file_field = fieldname["value"]; + } + } + } + } else { + var schemas = []; + const content = methodvalue["requestBody"]["content"]; + if (content !== undefined && content !== null) { + //console.log("CONTENT: ", content) + for (const [subkey, subvalue] of Object.entries(content)) { + if (subvalue["schema"] !== undefined) { + //console.log("SCHEMA: ", subvalue["schema"]) + if (subvalue["schema"]["$ref"] !== undefined) { + //console.log("SCHEMA FOUND REF!") + + if (!schemas.includes(subvalue["schema"]["$ref"])) { + schemas.push(subvalue["schema"]["$ref"]); + } + } + } else { + console.log( + "ERROR: couldn't find schema for ", + subvalue, + method + ); + } + } + } + + if (schemas.length === 1) { + const parameter = handleGetRef({ $ref: schemas[0] }, data); + + if ( + parameter.properties !== undefined && + parameter["type"] === "object" + ) { + var newbody = {}; + for (var propkey in parameter.properties) { + const parsedkey = propkey + .replaceAll(" ", "_") + .toLowerCase(); + if (parameter.properties[propkey].type === undefined) { + console.log( + "Skipping (4): ", + parameter.properties[propkey] + ); + continue; + } + + if (parameter.properties[propkey].type === "string") { + if ( + parameter.properties[propkey].description !== + undefined + ) { + newbody[parsedkey] = + parameter.properties[propkey].description; + } else { + newbody[parsedkey] = ""; + } + } else if ( + parameter.properties[propkey].type.includes("int") || + parameter.properties[propkey].type.includes("uint64") + ) { + newbody[parsedkey] = 0; + } else if ( + parameter.properties[propkey].type.includes("boolean") + ) { + newbody[parsedkey] = false; + } else if ( + parameter.properties[propkey].type.includes("array") + ) { + newbody[parsedkey] = []; + } else { + console.log( + "CANT HANDLE JSON TYPE (4)", + parameter.properties[propkey].type, + parameter.properties[propkey] + ); + newbody[parsedkey] = []; + } + } + + newaction["body"] = JSON.stringify(newbody, null, 2); + } else { + console.log( + "CANT HANDLE PARAM: (4) ", + parameter.properties + ); + } + } + } + } + } + } + + // HAHAHA wtf is this. + if ( + methodvalue.responses !== undefined && + methodvalue.responses !== null + ) { + if (methodvalue.responses.default !== undefined) { + if (methodvalue.responses.default.content !== undefined) { + if ( + methodvalue.responses.default.content["text/plain"] !== + undefined + ) { + if ( + methodvalue.responses.default.content["text/plain"][ + "schema" + ] !== undefined + ) { + if ( + methodvalue.responses.default.content["text/plain"][ + "schema" + ]["example"] !== undefined + ) { + newaction.example_response = + methodvalue.responses.default.content["text/plain"][ + "schema" + ]["example"]; + } + } + } + } + } else { + var selectedReturn = ""; + if (methodvalue.responses["200"] !== undefined) { + selectedReturn = "200"; + } else if (methodvalue.responses["201"] !== undefined) { + selectedReturn = "201"; + } + + // Parsing examples. This should be standardized lol + if (methodvalue.responses[selectedReturn] !== undefined) { + const selectedExample = methodvalue.responses[selectedReturn]; + if (selectedExample["content"] !== undefined) { + if ( + selectedExample["content"]["application/json"] !== undefined + ) { + if ( + selectedExample["content"]["application/json"][ + "schema" + ] !== undefined && + selectedExample["content"]["application/json"][ + "schema" + ] !== null + ) { + if ( + selectedExample["content"]["application/json"][ + "schema" + ]["$ref"] !== undefined + ) { + //console.log("REF EXAMPLE: ", selectedExample["content"]["application/json"]["schema"]) + const parameter = handleGetRef( + selectedExample["content"]["application/json"][ + "schema" + ], + data + ); + //console.log("GOT REF RETURN AS EXAMPLE: ", parameter) + + if ( + parameter.properties !== undefined && + parameter["type"] === "object" + ) { + var newbody = {}; + for (var propkey in parameter.properties) { + const parsedkey = propkey + .replaceAll(" ", "_") + .toLowerCase(); + if ( + parameter.properties[propkey].type === undefined + ) { + console.log( + "Skipping (1): ", + parameter.properties[propkey] + ); + continue; + } + + if ( + parameter.properties[propkey].type === "string" + ) { + if ( + parameter.properties[propkey].description !== + undefined + ) { + newbody[parsedkey] = + parameter.properties[propkey].description; + } else { + newbody[parsedkey] = ""; + } + } else if ( + parameter.properties[propkey].type.includes("int") + ) { + newbody[parsedkey] = 0; + } else if ( + parameter.properties[propkey].type.includes( + "boolean" + ) + ) { + newbody[parsedkey] = false; + } else if ( + parameter.properties[propkey].type.includes( + "array" + ) + ) { + //console.log("Added empty array. Base is: ", parameter.properties[propkey].type) + + //const parameter = handleGetRef(selectedExample["content"]["application/json"]["schema"], data) + newbody[parsedkey] = []; + } else { + console.log( + "CANT HANDLE JSON TYPE ", + parameter.properties[propkey].type, + parameter.properties[propkey] + ); + newbody[parsedkey] = []; + } + } + newaction.example_response = JSON.stringify( + newbody, + null, + 2 + ); + } else { + console.log( + "CANT HANDLE PARAM: (1) ", + parameter.properties + ); + } + } else { + // Just selecting the first one. bleh. + if ( + selectedExample["content"]["application/json"][ + "schema" + ]["allOf"] !== undefined + ) { + //console.log("ALLOF: ", selectedExample["content"]["application/json"]["schema"]["allOf"]) + //console.log("BAD EXAMPLE: (SKIP ALLOF) ", selectedExample["content"]["application/json"]["schema"]["allOf"]) + var selectedComponent = + selectedExample["content"]["application/json"][ + "schema" + ]["allOf"]; + if (selectedComponent.length >= 1) { + selectedComponent = selectedComponent[0]; + + const parameter = handleGetRef( + selectedComponent, + data + ); + if ( + parameter.properties !== undefined && + parameter["type"] === "object" + ) { + var newbody = {}; + for (var propkey in parameter.properties) { + const parsedkey = propkey + .replaceAll(" ", "_") + .toLowerCase(); + if ( + parameter.properties[propkey].type === + undefined + ) { + console.log( + "Skipping (2): ", + parameter.properties[propkey] + ); + continue; + } + + if ( + parameter.properties[propkey].type === + "string" + ) { + if ( + parameter.properties[propkey] + .description !== undefined + ) { + newbody[parsedkey] = + parameter.properties[propkey].description; + } else { + newbody[parsedkey] = ""; + } + } else if ( + parameter.properties[propkey].type.includes( + "int" + ) + ) { + newbody[parsedkey] = 0; + } else if ( + parameter.properties[propkey].type.includes( + "boolean" + ) + ) { + newbody[parsedkey] = false; + } else { + console.log( + "CANT HANDLE JSON TYPE (2) ", + parameter.properties[propkey].type + ); + newbody[parsedkey] = []; + } + } + + newaction.example_response = JSON.stringify( + newbody, + null, + 2 + ); + //newaction.example_response = JSON.stringify(parameter.properties, null, 2) + } else { + //newaction.example_response = parameter.properties + console.log( + "CANT HANDLE PARAM: (3) ", + parameter.properties + ); + } + } else { + } + } else if ( + selectedExample["content"]["application/json"][ + "schema" + ]["properties"] !== undefined + ) { + if ( + selectedExample["content"]["application/json"][ + "schema" + ]["properties"]["data"] !== undefined + ) { + const parameter = handleGetRef( + selectedExample["content"]["application/json"][ + "schema" + ]["properties"]["data"], + data + ); + if ( + parameter.properties !== undefined && + parameter["type"] === "object" + ) { + var newbody = {}; + for (var propkey in parameter.properties) { + const parsedkey = propkey + .replaceAll(" ", "_") + .toLowerCase(); + if ( + parameter.properties[propkey].type === + undefined + ) { + console.log( + "Skipping (3): ", + parameter.properties[propkey] + ); + continue; + } + + if ( + parameter.properties[propkey].type === + "string" + ) { + if ( + parameter.properties[propkey] + .description !== undefined + ) { + newbody[parsedkey] = + parameter.properties[propkey].description; + } else { + newbody[parsedkey] = ""; + } + console.log(parameter.properties[propkey]); + } else if ( + parameter.properties[propkey].type.includes( + "int" + ) + ) { + newbody[parsedkey] = 0; + } else { + console.log( + "CANT HANDLE JSON TYPE (3) ", + parameter.properties[propkey].type + ); + newbody[parsedkey] = []; + } + } + + newaction.example_response = JSON.stringify( + newbody, + null, + 2 + ); + //newaction.example_response = JSON.stringify(parameter.properties, null, 2) + } else { + //newaction.example_response = parameter.properties + console.log( + "CANT HANDLE PARAM: (3) ", + parameter.properties + ); + } + } + } + } + } + } + } + } + } + } + + for (var key in methodvalue.parameters) { + const parameter = handleGetRef(methodvalue.parameters[key], data); + if (parameter.in === "query") { + var tmpaction = { + description: parameter.description, + name: parameter.name, + required: parameter.required, + in: "query", + }; + + if (parameter.required === undefined) { + tmpaction.required = false; + } + + newaction.queries.push(tmpaction); + } else if (parameter.in === "path") { + // FIXME - parse this to the URL too + newaction.paths.push(parameter.name); + + // FIXME: This doesn't follow OpenAPI3 exactly. + // https://swagger.io/docs/specification/describing-request-body/ + // https://swagger.io/docs/specification/describing-parameters/ + // Need to split the data. + } else if (parameter.in === "body") { + // FIXME: Add tracking for components + // E.G: https://raw.githubusercontent.com/owentl/Shuffle/master/gosecure.yaml + if (parameter.example !== undefined) { + newaction.body = parameter.example; + } + } else if (parameter.in === "header") { + newaction.headers += `${parameter.name}=${parameter.example}\n`; + } else { + console.log( + "WARNING: don't know how to handle this param: ", + parameter + ); + } + } + + if (newaction.name === "" || newaction.name === undefined) { + // Find a unique part of the string + // FIXME: Looks for length between /, find the one where they differ + // Should find others with the same START to their path + // Make a list of reserved names? Aka things that show up only once + if (Object.getOwnPropertyNames(wordlist).length === 0) { + for (let [newpath, pathvalue] of Object.entries(data.paths)) { + const newpathsplit = newpath.split("/"); + for (var key in newpathsplit) { + const pathitem = newpathsplit[key].toLowerCase(); + if (wordlist[pathitem] === undefined) { + wordlist[pathitem] = 1; + } else { + wordlist[pathitem] += 1; + } + } + } + } + + //console.log("WORDLIST: ", wordlist) + + // Remove underscores and make it normal with upper case etc + const urlsplit = path.split("/"); + if (urlsplit.length > 0) { + var curname = ""; + for (var key in urlsplit) { + var subpath = urlsplit[key]; + if (wordlist[subpath] > 2 || subpath.length < 1) { + continue; + } + + curname = subpath; + break; + } + + // FIXME: If name exists, + // FIXME: Check if first part of parsedname is verb, otherwise use method + const parsedname = curname + .split("_") + .join(" ") + .split("-") + .join(" ") + .split("{") + .join(" ") + .split("}") + .join(" ") + .trim(); + if (parsedname.length === 0) { + newaction.errors.push("Missing name"); + } else { + const newname = + method.charAt(0).toUpperCase() + + method.slice(1) + + " " + + parsedname; + const searchactions = newActions.find( + (data) => data.name === newname + ); + console.log("SEARCH: ", searchactions); + if (searchactions !== undefined) { + newaction.errors.push("Missing name"); + } else { + newaction.name = newname; + } + } + } else { + newaction.errors.push("Missing name"); + } + } + + newActions.push(newaction); + } + } + + if (data.servers !== undefined && data.servers.length > 0) { + var firstUrl = data.servers[0].url; + if ( + firstUrl.includes("{") && + firstUrl.includes("}") && + data.servers[0].variables !== undefined + ) { + const regex = /{\w+}/g; + const found = firstUrl.match(regex); + if (found !== null) { + for (var key in found) { + const item = found[key].slice(1, found[key].length - 1); + const foundVar = data.servers[0].variables[item]; + if (foundVar["default"] !== undefined) { + firstUrl = firstUrl.replace(found[key], foundVar["default"]); + } + } + } + } + + if (firstUrl.endsWith("/")) { + setBaseUrl(firstUrl.slice(0, firstUrl.length - 1)); + } else { + setBaseUrl(firstUrl); + } + } + } + + //console.log("SECURITYSCHEMES: ", securitySchemes) + if (securitySchemes !== undefined) { + // FIXME: Should add Oauth2 (Microsoft) and JWT (Wazuh) + //console.log("SECURITY: ", securitySchemes) + //if (Object.entries(securitySchemes) > 1 && + var newauth = []; + for (const [key, value] of Object.entries(securitySchemes)) { + console.log(key, value); + if (key === "jwt") { + setAuthenticationOption("JWT"); + setAuthenticationRequired(true); + + if ( + value.in !== undefined && + value.in !== null && + value.in.length > 0 + ) { + setParameterName(value.in); + } + } else if (value.scheme === "bearer") { + setAuthenticationOption("Bearer auth"); + setAuthenticationRequired(true); + } else if (key === "Oauth2" || key === "Oauth2c") { + //alert.info("Can't handle Oauth2 auth yet.") + setAuthenticationOption("Oauth2"); + setAuthenticationRequired(true); + + //console.log("FLOW-1: ", value) + const flowkey = value.flow === undefined ? "flows" : "flow"; + //console.log("FLOW: ", value[flowkey]) + const basekey = + value[flowkey].authorizationCode !== undefined + ? "authorizationCode" + : "implicit"; + //console.log("FLOW2: ", value[flowkey][basekey]) + if ( + value[flowkey] !== undefined && + value[flowkey][basekey] !== undefined + ) { + if ( + value[flowkey][basekey].authorizationUrl !== undefined && + parameterName.length === 0 + ) { + setParameterName(value[flowkey][basekey].authorizationUrl); + } + + var tokenUrl = ""; + if (value[flowkey][basekey].tokenUrl !== undefined) { + setParameterLocation(value[flowkey][basekey].tokenUrl); + tokenUrl = value[flowkey][basekey].tokenUrl; + } else { + setParameterLocation(""); + } + + if (value[flowkey][basekey].refreshUrl !== undefined) { + setRefreshUrl(value[flowkey][basekey].refreshUrl); + } else if (tokenUrl.length > 0) { + setRefreshUrl(tokenUrl); + } + + if ( + value[flowkey][basekey].scopes !== undefined && + value[flowkey][basekey].scopes !== null + ) { + if (value[flowkey][basekey].scopes.length > 0) { + setOauth2Scopes(value[flowkey][basekey].scopes); + } else { + var newscopes = []; + for (let [scopekey, scopevalue] of Object.entries( + value[flowkey][basekey].scopes + )) { + if (scopekey.startsWith("http")) { + const scopekeysplit = scopekey.split("/"); + if (scopekeysplit.length < 5) { + console.log("Skipping scope: ", scopekey); + alert.info("Skipping scope: " + scopekey); + continue; + } + + //console.log("Checking scope for: ", scopekey, scopekeysplit.length) + } + + newscopes.push(scopekey); + } + + setOauth2Scopes(newscopes); + } + } + } else { + console.log( + "Bad flowkey and basekey for oauth2: ", + flowkey, + basekey + ); + } + } else if (key === "ApiKeyAuth") { + setAuthenticationOption("API key"); + + value.in = value.in.charAt(0).toUpperCase() + value.in.slice(1); + setParameterLocation(value.in); + if (!apikeySelection.includes(value.in)) { + console.log("APIKEY SELECT: ", apikeySelection); + alert.error("Might be error in setting up API key authentication"); + } + + console.log("PARAM NAME: ", value.name); + setParameterName(value.name); + setAuthenticationRequired(true); + } else if (value.scheme === "basic") { + setAuthenticationOption("Basic auth"); + setAuthenticationRequired(true); + } else if (value.scheme === "oauth2") { + setAuthenticationOption("Oauth2"); + setAuthenticationRequired(true); + } else { + alert.error("Couldn't handle AUTH type: ", key); + //newauth.push({ + // "name": key, + // "type": value.in, + // "example": "", + //}) + } + } + + if (newauth.length > 0) { + setExtraAuth(newauth); + } + } + + if (newActions.length > increaseAmount - 1) { + setActionAmount(increaseAmount); + } else { + setActionAmount(newActions.length); + } + + //const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" + if (newActions.length > 1000 && isCloud) { + alert.error( + "Cut down actions from " + + newActions.length + + " to 999 because of limit" + ); + newActions = newActions.slice(0, 999); + } + + setProjectCategories(all_categories); + setActions(newActions); + setFilteredActions(newActions); + setIsAppLoaded(true); + }; + + // Saving the app that's been configured. + const submitApp = () => { + alert.info("Uploading and building app " + name); + setAppBuilding(true); + setErrorCode(""); + + // Format the information + const splitBase = baseUrl.split("/"); + const host = splitBase[2]; + const schemes = [splitBase[0]]; + const basePath = "/" + splitBase.slice(3).join("/"); + + const data = { + openapi: "3.0.0", + info: { + title: name, + description: description, + version: "1.0", + "x-logo": fileBase64, + }, + servers: [{ url: baseUrl }], + host: host, + basePath: basePath, + schemes: schemes, + paths: {}, + editing: isEditing, + components: { + securitySchemes: {}, + }, + id: props.match.params.appid, + }; + + if (basedata.info !== undefined && basedata.info.contact !== undefined) { + data.info["contact"] = basedata.info.contact; + } else if (contact === "") { + data.info["contact"] = { + name: "@frikkylikeme", + url: "https://twitter.com/frikkylikeme", + email: "frikky@shuffler.io", + }; + } else { + data.info["contact"] = contact; + } + + if (newWorkflowTags.length > 0) { + var newtags = []; + for (var key in newWorkflowTags) { + newtags.push({ name: newWorkflowTags[key] }); + } + + data["tags"] = newtags; + } + + if (newWorkflowCategories.length > 0) { + data["info"]["x-categories"] = newWorkflowCategories; + } + + // Handles actions + for (var key in actions) { + var item = actions[key]; + if (item.errors.length > 0) { + alert.error("Saving with error in action " + item.name); + } + + if (item.name === undefined && item.description !== undefined) { + item.name = item.description; + } + + if (data.paths[item.url] === null || data.paths[item.url] === undefined) { + data.paths[item.url] = {}; + } + + const regex = /[A-Za-z0-9 _]/g; + if (item.name === undefined) { + console.log("Skipping action ", item); + continue; + } + + const found = item.name.match(regex); + if (found !== null) { + item.name = found.join(""); + } + + data.paths[item.url][item.method.toLowerCase()] = { + responses: { + default: { + description: "default", + content: { + "text/plain": { + schema: { + type: "string", + example: "", + }, + }, + }, + }, + }, + summary: item.name, + operationId: item.name.split(" ").join("_"), + description: item.description, + parameters: [], + requestBody: { + content: {}, + }, + }; + + //console.log("ACTION: ", item) + + if ( + item.example_response !== undefined && + item.example_response.length > 0 + ) { + // FIXME: Shallow copy of the string + var showResult = Object.assign("", item.example_response).trim(); + showResult = showResult.split(" None").join(' "None"'); + showResult = showResult.split("'").join('"'); + showResult = showResult.split(" False").join(" false"); + showResult = showResult.split(" True").join(" true"); + + var jsonvalid = true; + try { + const tmp = String(JSON.parse(showResult)); + if (!showResult.includes("{") && !showResult.includes("[")) { + jsonvalid = false; + } + } catch (e) { + jsonvalid = false; + } + + data.paths[item.url][item.method.toLowerCase()].responses["default"][ + "content" + ]["text/plain"].schema.type = "string"; + if (jsonvalid) { + // FIXME: Add a JSON parser here - don't run it as a string. + data.paths[item.url][item.method.toLowerCase()].responses["default"][ + "content" + ]["text/plain"].schema.example = showResult; + } else { + data.paths[item.url][item.method.toLowerCase()].responses["default"][ + "content" + ]["text/plain"].schema.example = item.example_response; + } + } + + if (item.queries.length > 0) { + var skipped = false; + for (var querykey in item.queries) { + const queryitem = item.queries[querykey]; + + if (queryitem.name.toLowerCase() == "url") { + console.log(item.name + " uses a bad query: url"); + continue; + //skipped = true + //break + } + + if ( + queryitem.name.toLowerCase() == "url" || + queryitem.name.toLowerCase() == "body" || + queryitem.name.toLowerCase() == "self" || + queryitem.name.toLowerCase() == "ssl_verify" || + queryitem.name.toLowerCase() == "queries" || + queryitem.name.toLowerCase() == "headers" || + queryitem.name.includes("[") || + queryitem.name.includes("]") || + queryitem.name.includes("{") || + queryitem.name.includes("}") || + queryitem.name.includes("(") || + queryitem.name.includes(")") || + queryitem.name.includes("!") || + queryitem.name.includes("@") || + queryitem.name.includes("#") || + queryitem.name.includes("$") || + queryitem.name.includes("%") || + queryitem.name.includes("^") || + queryitem.name.includes("&") || + queryitem.name.includes(":") || + queryitem.name.includes(";") || + queryitem.name.includes("<") || + queryitem.name.includes(">") || + queryitem.name.includes('"') || + queryitem.name.includes("'") + ) { + console.log( + item.name + " error: uses a bad query - not adding: ", + queryitem.name + ); + continue; + } + + var newitem = { + in: "query", + name: queryitem.name, + description: "Generated by shuffler.io OpenAPI", + required: queryitem.required, + schema: { + type: "string", + }, + }; + + if (queryitem.description !== undefined) { + newitem.description = queryitem.description; + } + + data.paths[item.url][item.method.toLowerCase()].parameters.push( + newitem + ); + //console.log(queryitem) + } + + // Bad code as it doesn't allow for "anything". + if (skipped) { + alert.info( + "Bad configuration of " + + item.name + + ". Skipping because queries are invalid." + ); + continue; + } + } + //data.paths[item.url][item.method.toLowerCase()].parameters.push(newitem) + + if (item.paths.length > 0) { + for (querykey in item.paths) { + const queryitem = item.paths[querykey]; + + if (queryitem.toLowerCase() == "url") { + queryitem = "action_url"; + } + + if (queryitem.toLowerCase() == "apikey") { + queryitem = "action_apikey"; + } + + newitem = { + in: "path", + name: queryitem, + description: "Generated by shuffler.io OpenAPI", + required: true, + schema: { + type: "string", + }, + }; + + if (queryitem.description !== undefined) { + newitem.description = queryitem.description; + } + + data.paths[item.url][item.method.toLowerCase()].parameters.push( + newitem + ); + //console.log(queryitem) + } + } else { + // Always goes here if they didn't click anything :/ + const values = getCurrentPaths(item.url); + const paths = values[0]; + + for (querykey in paths) { + const queryitem = paths[querykey]; + newitem = { + in: "path", + name: queryitem, + description: "Generated by shuffler.io OpenAPI", + required: true, + schema: { + type: "string", + }, + }; + + if (queryitem.description !== undefined) { + newitem.description = queryitem.description; + } + + data.paths[item.url][item.method.toLowerCase()].parameters.push( + newitem + ); + //console.log(queryitem) + } + } + + if ( + item.body !== undefined && + item.body !== null && + item.body.length > 0 + ) { + const required = false; + newitem = { + in: "body", + name: "body", + multiline: true, + description: "Generated by shuffler.io OpenAPI", + required: required, + example: item.body, + schema: { + type: "string", + }, + }; + + // FIXME - add application/json if JSON example? + data.paths[item.url][item.method.toLowerCase()]["requestBody"] = { + description: "Generated by Shuffler.io", + required: required, + content: { + example: { + example: item.body, + }, + }, + }; + + data.paths[item.url][item.method.toLowerCase()].parameters.push( + newitem + ); + } else if (actionBodyRequest.includes(item.method.toUpperCase())) { + // Appending an empty field + const required = false; + newitem = { + in: "body", + name: "body", + multiline: true, + description: "Generated by shuffler.io OpenAPI", + required: required, + example: "", + schema: { + type: "string", + }, + }; + + // FIXME - add application/json if JSON example? + data.paths[item.url][item.method.toLowerCase()]["requestBody"] = { + description: "Generated by Shuffler.io", + required: required, + content: { + example: { + example: "", + }, + }, + }; + + data.paths[item.url][item.method.toLowerCase()].parameters.push( + newitem + ); + } else { + //console.log("Nothing to append?") + } + + // https://swagger.io/docs/specification/describing-request-body/file-upload/ + if ( + item.file_field !== undefined && + item.file_field !== null && + item.file_field.length > 0 + ) { + console.log("HANDLE FILEFIELD SAVE: ", item.file_field); + data.paths[item.url][item.method.toLowerCase()]["requestBody"][ + "content" + ]["multipart/form-data"] = { + schema: { + type: "object", + properties: { + fieldname: { + type: "string", + value: item.file_field, + }, + }, + }, + }; + + console.log( + data.paths[item.url][item.method.toLowerCase()]["requestBody"][ + "content" + ]["multipart/form-data"] + ); + } + + if (item.headers.length > 0) { + const required = false; + + const headersSplit = item.headers.split("\n"); + for (var key in headersSplit) { + const header = headersSplit[key]; + var key = ""; + var value = ""; + if (header.length > 0 && header.includes("= ")) { + const headersplit = header.split("= "); + key = headersplit[0]; + value = headersplit[1]; + } else if (header.length > 0 && header.includes(" =")) { + const headersplit = header.split(" ="); + key = headersplit[0]; + value = headersplit[1]; + } else if (header.length > 0 && header.includes("=")) { + const headersplit = header.split("="); + key = headersplit[0]; + value = headersplit[1]; + } else if (header.length > 0 && header.includes(": ")) { + const headersplit = header.split(": "); + key = headersplit[0]; + value = headersplit[1]; + } else if (header.length > 0 && header.includes(" :")) { + const headersplit = header.split(" :"); + key = headersplit[0]; + value = headersplit[1]; + } else if (header.length > 0 && header.includes(":")) { + const headersplit = header.split(":"); + key = headersplit[0]; + value = headersplit[1]; + } else { + continue; + } + + if (key.length > 0 && value.length > 0) { + newitem = { + in: "header", + name: key, + multiline: false, + description: "Header generated by shuffler.io OpenAPI", + required: false, + example: value, + schema: { + type: "string", + }, + }; + + data.paths[item.url][item.method.toLowerCase()].parameters.push( + newitem + ); + } + } + } + } + + if (authenticationOption === "API key") { + if (parameterName.length === 0) { + alert.error("A field name for the APIkey must be defined"); + setAppBuilding(false); + return; + } + + var newparamName = parameterName.replaceAll('"', ""); + newparamName = newparamName.replaceAll("'", ""); + + data.components.securitySchemes["ApiKeyAuth"] = { + type: "apiKey", + in: parameterLocation.toLowerCase(), + name: newparamName, + }; + } else if (authenticationOption === "Bearer auth") { + data.components.securitySchemes["BearerAuth"] = { + type: "http", + scheme: "bearer", + bearerFormat: "UUID", + }; + } else if (authenticationOption === "JWT") { + data.components.securitySchemes["jwt"] = { + type: "http", + scheme: "bearer", + bearerFormat: "JWT", + in: parameterName, + }; + + console.log("SECURITYSCHEMES: ", data.components); + } else if (authenticationOption === "Basic auth") { + data.components.securitySchemes["BasicAuth"] = { + type: "http", + scheme: "basic", + }; + } else if (authenticationOption === "Oauth2") { + var newparamName = parameterName.replaceAll('"', ""); + newparamName = newparamName.replaceAll("'", ""); + + //parameterName, parameterValue, revocationUrl + data.components.securitySchemes["Oauth2"] = { + type: "oauth2", + description: "Oauth2.0 authorizationCode authentication", + flow: { + authorizationCode: { + authorizationUrl: newparamName, + tokenUrl: parameterLocation, + refreshUrl: refreshUrl, + scopes: + oauth2Scopes === undefined || oauth2Scopes === null + ? [] + : oauth2Scopes, + }, + }, + }; + } + + if (setExtraAuth.length > 0) { + for (var key in extraAuth) { + const curauth = extraAuth[key]; + + if (curauth.name.toLowerCase() == "url") { + alert.error("Can't add extra auth with Name URL"); + setAppBuilding(false); + return; + } + + data.components.securitySchemes[curauth.name] = { + type: "apiKey", + in: curauth.type, + name: curauth.name, + }; + } + } + + fetch(globalUrl + "/api/v1/verify_openapi", { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(data, null, 4), + credentials: "include", + }) + .then((response) => { + //if (response.status !== 200) { + // setErrorCode("An error occurred during validation") + // throw new Error("NOT 200 :O") + //} + + setAppBuilding(false); + return response.json(); + }) + .then((responseJson) => { + if (!responseJson.success) { + if (responseJson.reason !== undefined) { + setErrorCode(responseJson.reason); + alert.error("Failed to verify: " + responseJson.reason); + } + } else { + alert.success("Successfully uploaded openapi"); + if (window.location.pathname.includes("/new")) { + if (responseJson.id !== undefined && responseJson.id !== null) { + window.location = `/apps/edit/${responseJson.id}`; + } + } + } + }) + .catch((error) => { + setAppBuilding(false); + setErrorCode(error.toString()); + alert.error(error.toString()); + }); + }; + + const bearerAuth = + authenticationOption === "Bearer auth" ? ( +
+

+ + Bearer auth + +

+ Users will be required to submit their API as the header "Authorization: + Bearer APIKEY" +
+ ) : null; + + // Basicauth + const basicAuth = + authenticationOption === "Basic auth" ? ( +
+

+ + Basic authentication + +

+ Users will be required to submit a valid username and password before + using the API +
+ ) : null; + + // API key + //const verifyBaseUrl = () => { + // if (baseUrl.startsWith("http://") || baseUrl.startsWith("https://")) { + // return true + // } + + // if (baseUrl.endsWith("/")) { + // return true + // } + // + // return false + //} + + //const verifyApiParameter = () => { + // const notAllowed = ["!","#","$","%","&","'","^","+","-",".","_","~","|","]","+","$",] + // for (var key in notAllowed) { + // if (parameterName.includes(notAllowed[key])) { + // return false + // } + // } + + // return true + //} + + const testAction = (index) => { + console.log("Should test action at index " + index); + console.log(actions[index]); + }; + + const addPathQuery = () => { + urlPathQueries.push({ name: "", required: true }); + if (updater === "addupdater") { + setUpdater("updater"); + } else { + setUpdater("addupdater"); + } + setUrlPathQueries(urlPathQueries); + }; + + const flipRequired = (index) => { + urlPathQueries[index].required = !urlPathQueries[index].required; + if (updater === "flipupdater") { + setUpdater("updater"); + } else { + setUpdater("flipupdater"); + } + setUrlPathQueries(urlPathQueries); + }; + + const deletePathQuery = (index) => { + urlPathQueries.splice(index, 1); + if (updater === "deleteupdater") { + setUpdater("updater"); + } else { + setUpdater("deleteupdater"); + } + setUrlPathQueries(urlPathQueries); + }; + + const duplicateAction = (index) => { + var newAction = JSON.parse(JSON.stringify(actions[index])); + newAction.name = newAction.name + "_copy"; + newAction.errors.push("Can't have the same name"); + + actions.push(newAction); + + if (actions.length > actionAmount) { + setActionAmount(actions.length); + } + + setActions(actions); + setFilteredActions(actions); + setUpdate(Math.random()); + }; + + const deleteAction = (index) => { + actions.splice(index, 1); + setCurrentAction({ + name: "", + description: "", + url: "", + file_field: "", + headers: "", + paths: [], + queries: [], + body: "", + errors: [], + method: actionNonBodyRequest[0], + }); + + setActions(actions); + setFilteredActions(actions); + setActionAmount(actionAmount - 1); + setUpdate(Math.random()); + }; + + //console.log("Option: ", authenticationOption) + //console.log("Location: ", parameterLocation) //console.log("Name: ", parameterName) - const extraKeys = -
-
- Extra configuration items - {extraAuth.length === 0 ? - - : } -
- {extraAuth.map((value, index) => { - return ( - - { - extraAuth[index].name = e.target.value - setExtraAuth(extraAuth) - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style:{ - color: "white", - minHeight: 50, - marginLeft: 5, - maxWidth: "95%", - fontSize: "1em", - }, - }} - /> - { - extraAuth[index].example = e.target.value - setExtraAuth(extraAuth) - }} - InputProps={{ - style:{ - color: "white", - minHeight: 50, - marginLeft: 5, - maxWidth: "95%", - fontSize: "1em", - }, - }} - /> - -
- {index === extraAuth.length-1 ? - - : } - -
-
- ) - })} -
+ const extraKeys = ( +
+
+ Extra configuration items + {extraAuth.length === 0 ? ( + + ) : ( + + )} +
+ {extraAuth.map((value, index) => { + return ( + + { + extraAuth[index].name = e.target.value; + setExtraAuth(extraAuth); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + minHeight: 50, + marginLeft: 5, + maxWidth: "95%", + fontSize: "1em", + }, + }} + /> + { + extraAuth[index].example = e.target.value; + setExtraAuth(extraAuth); + }} + InputProps={{ + style: { + color: "white", + minHeight: 50, + marginLeft: 5, + maxWidth: "95%", + fontSize: "1em", + }, + }} + /> + +
+ {index === extraAuth.length - 1 ? ( + + ) : ( + + )} + +
+
+ ); + })} +
+ ); - const jwtAuth = authenticationOption === "JWT" ? -
- JWT authentication - - Authentication path - - Must start with / and be a valid path} - onBlur={e => setParameterName(e.target.value)} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style:{ - color: "white", - }, - }} - /> -
- : - null + const jwtAuth = + authenticationOption === "JWT" ? ( +
+ JWT authentication + + Authentication path + + + Must start with / and be a valid path + + } + onBlur={(e) => setParameterName(e.target.value)} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> +
+ ) : null; - const oauth2Auth = authenticationOption === "Oauth2" ? -
- Oauth2 authentication - - Base Authorization URL - - setParameterName(e.target.value)} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style:{ - color: "white", - }, - }} - /> - - Token URL - - setParameterLocation(e.target.value)} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style:{ - color: "white", - }, - }} - /> - - Refresh-token URL - - setRefreshUrl(e.target.value)} - InputProps={{ - style:{ - color: "white", - }, - }} - /> - - Scopes - - { - oauth2Scopes.push(chip) - console.log(oauth2Scopes) - setOauth2Scopes(oauth2Scopes) - setUpdate(Math.random()) - }} - onDelete={(chip, index) => { - oauth2Scopes.splice(index, 1) - console.log(oauth2Scopes) - setOauth2Scopes(oauth2Scopes) - setUpdate(Math.random()) - }} - /> -
- : null + const oauth2Auth = + authenticationOption === "Oauth2" ? ( +
+ Oauth2 authentication + + Base Authorization URL + + setParameterName(e.target.value)} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + Token URL + + setParameterLocation(e.target.value)} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + Refresh-token URL + + setRefreshUrl(e.target.value)} + InputProps={{ + style: { + color: "white", + }, + }} + /> + + Scopes + + { + oauth2Scopes.push(chip); + console.log(oauth2Scopes); + setOauth2Scopes(oauth2Scopes); + setUpdate(Math.random()); + }} + onDelete={(chip, index) => { + oauth2Scopes.splice(index, 1); + console.log(oauth2Scopes); + setOauth2Scopes(oauth2Scopes); + setUpdate(Math.random()); + }} + /> +
+ ) : null; - const apiKey = authenticationOption === "API key" ? -
- API key authentication - - Add the name of the field used for authentication, e.g. "X-APIKEY" - - Can't be empty. Can't contain any of the following characters: !#$%&'^"+-._~|]+$} - onChange={e => { - setParameterName(e.target.value) - }} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style:{ - color: "white", - }, - }} - /> - Field type - { + setParameterLocation(e.target.value); + }} + value={parameterLocation} + style={{ + borderRadius: 5, + backgroundColor: inputColor, + paddingLeft: "10px", + color: "white", + height: "50px", + }} + inputProps={{ + name: "age", + id: "outlined-age-simple", + }} + > + {apikeySelection.map((data, index) => { + if (data === undefined) { + return null; + } - return ( - - {data} - - )} - )} - -
- : null + return ( + + {data} + + ); + })} + +
+ ) : null; - const loopQueries = urlPathQueries.length === 0 ? - null : -
- - Queries - {urlPathQueries.map((data, index) => { - const requiredColor = data.required === true ? "green" : "red" - //const required = data.required === true ?
{data.required.toString()}
:
{flipRequired(index)}} style={{display: "inline", color: "red", cursor: "pointer"}}>{data.required.toString()}
- return ( - -
-
{flipRequired(index)}}> - Required:
{data.required.toString()}
-
- Click required switch} - onBlur={(e) => { - console.log("IN BLUR: ", e.target.value) - urlPathQueries[index].name = e.target.value.replaceAll("=", "") + const loopQueries = + urlPathQueries.length === 0 ? null : ( +
+ + Queries + {urlPathQueries.map((data, index) => { + const requiredColor = data.required === true ? "green" : "red"; + //const required = data.required === true ?
{data.required.toString()}
:
{flipRequired(index)}} style={{display: "inline", color: "red", cursor: "pointer"}}>{data.required.toString()}
+ return ( + +
+
{ + flipRequired(index); + }} + > + Required:{" "} +
+ {data.required.toString()} +
+
+ + Click required switch + + } + onBlur={(e) => { + console.log("IN BLUR: ", e.target.value); + urlPathQueries[index].name = e.target.value.replaceAll( + "=", + "" + ); - setUrlPathQueries(urlPathQueries) - }} - InputProps={{ - style:{ - color: "white", - }, - }} - /> + setUrlPathQueries(urlPathQueries); + }} + InputProps={{ + style: { + color: "white", + }, + }} + /> +
+
{ + deletePathQuery(index); + }} + > + Delete +
+
+ ); + })} + +
+ ); -
-
{deletePathQuery(index)}}> - Delete -
+ const loopActions = + actions.length === 0 ? null : ( +
+ {filteredActions.slice(0, actionAmount).map((data, index) => { + var error = + data.errors.length > 0 ? ( + + + + ) : ( + + + + ); - - ) - })} - -
+ var bgColor = "#61afee"; + if (data.method === "POST") { + bgColor = "#49cc90"; + } else if (data.method === "PUT") { + bgColor = "#fca130"; + } else if (data.method === "PATCH") { + bgColor = "#50e3c2"; + } else if (data.method === "DELETE") { + bgColor = "#f93e3e"; + } else if (data.method === "HEAD") { + bgColor = "#9012fe"; + } - const loopActions = actions.length === 0 ? - null - : -
- {filteredActions.slice(0,actionAmount).map((data, index) => { - var error = data.errors.length > 0 ? - - - - : - - - - + const url = data.url; + const hasFile = + data["file_field"] !== undefined && + data["file_field"] !== null && + data["file_field"].length > 0; + return ( + + {error} + +
{ + setCurrentAction(data); + setCurrentActionMethod(data.method); + setUrlPathQueries(data.queries); + setUrlPath(data.url); + setActionsModalOpen(true); - var bgColor = "#61afee" - if (data.method === "POST") { - bgColor = "#49cc90" - } else if (data.method === "PUT") { - bgColor = "#fca130" - } else if (data.method === "PATCH") { - bgColor = "#50e3c2" - } else if (data.method === "DELETE") { - bgColor = "#f93e3e" - } else if (data.method === "HEAD") { - bgColor = "#9012fe" - } + if ( + data["body"] !== undefined && + data["body"] !== null && + data["body"].length > 0 + ) { + findBodyParams(data["body"]); + } - const url = data.url - const hasFile = data["file_field"] !== undefined && data["file_field"] !== null && data["file_field"].length > 0 - return ( - - {error} - -
{ - setCurrentAction(data) - setCurrentActionMethod(data.method) - setUrlPathQueries(data.queries) - setUrlPath(data.url) - setActionsModalOpen(true) - - if (data["body"] !== undefined && data["body"] !== null && data["body"].length > 0) { - findBodyParams(data["body"]) - } - - if (hasFile) { - setFileUploadEnabled(true) - } - }}> -
- - - {hasFile ? : null} {url} - {data.name} - - -
-
-
- {/* + if (hasFile) { + setFileUploadEnabled(true); + } + }} + > +
+ + + {hasFile ? ( + + ) : null}{" "} + {url} - {data.name} + +
+
+
+ {/*
{testAction(index)}}> Test
*/} - -
{ - duplicateAction(index) - }}> - -
-
- -
{deleteAction(index)}}> - -
-
-
- ) - })} -
- - const setActionField = (field, value) => { - currentAction[field] = value - setCurrentAction(currentAction) - - //setUrlPathQueries(currentAction.queries) - } - - const findBodyParams = (body) => { - const regex = /\${(\w+)}/g - const found = body.match(regex) - if (found === null) { - setExtraBodyFields([]) - } else { - setExtraBodyFields(found) - } - } - - const HandleIndividualChip = (props) => { - const { chipData, index } = props; - const [chipRequired, setChipRequired] = useState(false) - - return ( - - { - console.log("CLICK: ", chipData) - setChipRequired(!chipRequired) - }} - /> - - ) - } - - const SetExtraBodyField = (props) => { - const { extraBodyFields } = props; - - if (extraBodyFields === undefined || extraBodyFields === null) { - return null - } - - //const parsedlist = extraBodyFields.join(", ") - //console.log("LIST: ", parsedlist) - - return ( - - {extraBodyFields.map((data, index) => { - return ( - - ) - })} - - ) - } - - const bodyInfo = actionBodyRequest.includes(currentActionMethod) ? -
- Request Body: {extraBodyFields.length > 0 ? - - - - : - - {`Add variables with \$\{ variable_name }`} - - } - { - setActionField("body", e.target.value) - findBodyParams(e.target.value) - }} - key={currentAction} - helperText={ - - Shows an example body to the user. ${} creates variables. - - } - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style:{ - color: "white", - }, - }} - /> - -
-
-
- : null - - const exampleResponse = -
- Example success response - setActionField("example_response", e.target.value)} - helperText={ - Helps with autocompletion and understanding of the endpoint - } - key={currentAction} - InputProps={{ - style:{ - color: "white", - }, - }} - /> -
- - const addActionToView = (errors) => { - currentAction.errors = errors - currentAction.queries = urlPathQueries - setUrlPathQueries([]) - - const actionIndex = actions.findIndex(data => data.name === currentAction.name) - if (actionIndex < 0) { - actions.push(currentAction) - } else { - actions[actionIndex] = currentAction - } - - - if (actions.length > actionAmount) { - setActionAmount(actions.length) - } - - setActions(actions) - setFilteredActions(actions) - } - - const getActionErrors = () => { - var errormessage = [] - if (currentAction.name === undefined || currentAction.name.length === 0) { - errormessage.push("Name can't be empty") - } - - // Url verification - //if (currentAction.url.length === 0) { - // errormessage.push("URL path can't be empty.") - if (!currentAction.url.startsWith("/") && baseUrl.length > 0 && currentAction.url.length > 0) { - errormessage.push("URL must start with /") - } - - const check = urlPathQueries.findIndex(data => data.name.length === 0) - if (check >= 0) { - errormessage.push("All queries must have a value") - } - - return errormessage - } - - const getCurrentPaths = (urlPath) => { - var paths = [] - var queries = [] - - if (urlPath.includes("{") && urlPath.includes("}")) { - var tmpWord = "" - var record = false - - var query = false - for (var key in urlPath) { - if (urlPath[key] === "?") { - query = true - } - - if (urlPath[key] === "}") { - if (tmpWord === parameterName) { - tmpWord = "" - record = false - continue - } else if (query) { - queries.push(tmpWord) - } else { - paths.push(tmpWord) - } - - tmpWord = "" - record = false - } - - if (record) { - tmpWord += urlPath[key] - } - - //if (urlPath[key] === "{" && urlPath[key-1] === "/") { - if (urlPath[key] === "{") { - record = true - } - } - } - - if (urlPath.includes("<") && urlPath.includes(">")) { - var tmpWord = "" - var record = false - - var query = false - for (var key in urlPath) { - if (urlPath[key] === "?") { - query = true - } - - if (urlPath[key] === ">") { - if (tmpWord === parameterName) { - tmpWord = "" - record = false - continue - } else if (query) { - queries.push(tmpWord) - } else { - paths.push(tmpWord) - } - - tmpWord = "" - record = false - } - - if (record) { - tmpWord += urlPath[key] - } - - //if (urlPath[key] === "{" && urlPath[key-1] === "/") { - if (urlPath[key] === "<") { - record = true - } - } - } - - return [paths, queries] - } - - const UrlPathParameters = () => { - const values = getCurrentPaths(urlPath) - const paths = values[0] - const queries = values[1] - - if (currentAction.paths !== paths && urlPath.length > 0) { - //console.log("IN PATHS SETTER: !", paths) - setActionField("paths", paths) - } - - var tmpQueries = [] - - // No overlapping of names - for (var key in queries) { - const tmpquery = queries[key] - const found = tmpQueries.find(query => query.name === tmpquery) - if (found === undefined) { - tmpQueries.push({"name": queries[key], required: true}) - } - } - - // FIXME: Frontend isn't updating.. - if (tmpQueries.length > 0 && JSON.stringify(tmpQueries) !== JSON.stringify(urlPathQueries)) { - setUrlPathQueries(tmpQueries) - } - - return paths.length > 0 ? -
- Required parameters: {paths.join(", ")} -
- : null - } - - const newActionModal = - { - setUrlPath("") - setCurrentAction({ - "name": "", - "description": "", - "url": "", - "file_field": "", - "headers": "", - "paths": [], - "queries": [], - "body": "", - "errors": [], - "method": actionNonBodyRequest[0], - }) - setCurrentActionMethod(apikeySelection[0]) - setUrlPathQueries([]) - setActionsModalOpen(false) - setFileUploadEnabled(false) - }} - > - -
New action
- - Learn more about actions -
- Name - { - setActionField("name", e.target.value) - }} - onBlur={e => { - // Fix basic issues in frontend. Python functions run a-zA-Z0-9_ - const regex = /[A-Za-z0-9 _]/g; - const found = e.target.value.match(regex); - if (found !== null) { - setActionField("name", found.join("")) - } - }} - key={currentAction} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style:{ - color: "white", - }, - }} - /> -
- Description - setActionField("description", e.target.value)} - InputProps={{ - style:{ - color: "white", - }, - }} - /> - -

Request

- -
- URL path / Curl statement - { - setActionField("url", e.target.value) - setUrlPath(e.target.value) - console.log(e.target.value) - }} - helperText={The path to use. Must start with /. Use {"{variablename}"} to have path variables} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - input: classes.input, - }, - style:{ - color: "white", - }, - }} - onBlur={event => { - var parsedurl = event.target.value - //console.log("URL: ", parsedurl) - if (parsedurl.includes(" ")) { - parsedurl = parsedurl.replaceAll(" ", " ") - } - - if (parsedurl.includes(" ")) { - parsedurl = parsedurl.replaceAll(" ", " ") - } - - if (parsedurl.includes("[") && parsedurl.includes("]")) { - //console.log("REPLACE1") - parsedurl = parsedurl.replaceAll("[", "{") - parsedurl = parsedurl.replaceAll("]", "}") - } - - if (parsedurl.includes("<") && parsedurl.includes(">")) { - //console.log("REPLACE2") - parsedurl = parsedurl.replaceAll("<", "{") - parsedurl = parsedurl.replaceAll(">", "}") - } - - //console.log("URL2: ", parsedurl) - if (parsedurl.startsWith("PUT ") || parsedurl.startsWith("GET ") ||parsedurl.startsWith("POST ") || parsedurl.startsWith("DELETE ") ||parsedurl.startsWith("PATCH ") || parsedurl.startsWith("CONNECT ")) { - - const tmp = parsedurl.split(" ") - - if (tmp.length > 1) { - parsedurl = tmp[1].trim() - setActionField("url", parsedurl) - - setCurrentActionMethod(tmp[0].toUpperCase()) - setActionField("method", tmp[0].toUpperCase()) - } - - console.log("URL3: ", parsedurl) - - setUpdate(Math.random()) - } else if (parsedurl.startsWith("curl")) { - console.log("URL4: ", parsedurl) - - const request = parseCurl(event.target.value) - if (request !== event.target.value && request.method !== undefined && request.method !== null) { - if (request.method.toUpperCase() !== currentAction.Method) { - setCurrentActionMethod(request.method.toUpperCase()) - setActionField("method", request.method.toUpperCase()) - } - - if (request.header !== undefined && request.header !== null) { - var headers = [] - for (let [key, value] of Object.entries(request.header)) { - if (parameterName !== undefined && key.toLowerCase() === parameterName.toLowerCase()) { - continue - } - - if (key === "Authorization" && authenticationOption === "Bearer auth") { - continue - } - - headers += key+"="+value+"\n" - } - - setActionField("headers", headers) - } - - if (request.body !== undefined && request.body !== null) { - setActionField("body", request.body) - } - - // Parse URL - if (request.url !== undefined) { - parsedurl = request.url - } - } - - console.log("PARSED: ", parsedurl) - if (parsedurl !== undefined) { - if (parsedurl.includes("<") && parsedurl.includes(">")) { - parsedurl = parsedurl.split("<").join("{") - parsedurl = parsedurl.split(">").join("}") - } - - if (parsedurl.startsWith("http") || parsedurl.startsWith("ftp")) { - if (parsedurl !== undefined && parsedurl.includes(parameterName)) { - // Remove <> etc. - // - - console.log("IT HAS THE PARAM NAME!") - const newurl = new URL(encodeURI(parsedurl)) - newurl.searchParams.delete(parameterName) - parsedurl = decodeURI(newurl.href) - } - - // Remove the base URL itself - if (parsedurl !== undefined && baseUrl !== undefined && baseUrl.length > 0 && parsedurl.includes(baseUrl)) { - parsedurl = parsedurl.replace(baseUrl, "") - } - - // Check URL query && headers - //setActionField("url", parsedurl) - } - } - } - - if (event.target.value !== parsedurl) { - setUrlPath(parsedurl) - setActionField("url", parsedurl) - } - //console.log("URL: ", request.url) - }} - /> - - {loopQueries} - - {currentActionMethod === "POST" ? - - : null} - {fileUploadEnabled ? - setActionField("file_field", e.target.value)} - helperText={The File field to interact with} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style:{ - color: "white", - }, - }} - /> - : null} -
- Headers: static for the action - setActionField("headers", e.target.value)} - helperText={Headers that are part of the request. Default: EMPTY} - InputProps={{ - style:{ - color: "white", - }, - }} - /> - {bodyInfo} - - {exampleResponse} - - - - - - -
- - - const categories = [ - "Communication", - "Cases", - "SIEM", - "Assets", - "Intel", - "IAM", - "Network", - "Eradication", - "Other", - ] - - const tagView = -
- {/* + +
{ + duplicateAction(index); + }} + > + +
+
+ +
{ + deleteAction(index); + }} + > + +
+
+ + ); + })} +
+ ); + + const setActionField = (field, value) => { + currentAction[field] = value; + setCurrentAction(currentAction); + + //setUrlPathQueries(currentAction.queries) + }; + + const findBodyParams = (body) => { + const regex = /\${(\w+)}/g; + const found = body.match(regex); + if (found === null) { + setExtraBodyFields([]); + } else { + setExtraBodyFields(found); + } + }; + + const HandleIndividualChip = (props) => { + const { chipData, index } = props; + const [chipRequired, setChipRequired] = useState(false); + + return ( + + { + console.log("CLICK: ", chipData); + setChipRequired(!chipRequired); + }} + /> + + ); + }; + + const SetExtraBodyField = (props) => { + const { extraBodyFields } = props; + + if (extraBodyFields === undefined || extraBodyFields === null) { + return null; + } + + //const parsedlist = extraBodyFields.join(", ") + //console.log("LIST: ", parsedlist) + + return ( + + {extraBodyFields.map((data, index) => { + return ; + })} + + ); + }; + + const bodyInfo = actionBodyRequest.includes(currentActionMethod) ? ( +
+ Request Body:{" "} + {extraBodyFields.length > 0 ? ( + + + + ) : ( + + {`Add variables with \$\{ variable_name }`} + + )} + { + setActionField("body", e.target.value); + findBodyParams(e.target.value); + }} + key={currentAction} + helperText={ + + Shows an example body to the user. ${} creates variables. + + } + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> +
+
+ ) : null; + + const exampleResponse = ( +
+ Example success response + setActionField("example_response", e.target.value)} + helperText={ + + Helps with autocompletion and understanding of the endpoint + + } + key={currentAction} + InputProps={{ + style: { + color: "white", + }, + }} + /> +
+ ); + + const addActionToView = (errors) => { + currentAction.errors = errors; + currentAction.queries = urlPathQueries; + setUrlPathQueries([]); + + const actionIndex = actions.findIndex( + (data) => data.name === currentAction.name + ); + if (actionIndex < 0) { + actions.push(currentAction); + } else { + actions[actionIndex] = currentAction; + } + + if (actions.length > actionAmount) { + setActionAmount(actions.length); + } + + setActions(actions); + setFilteredActions(actions); + }; + + const getActionErrors = () => { + var errormessage = []; + if (currentAction.name === undefined || currentAction.name.length === 0) { + errormessage.push("Name can't be empty"); + } + + // Url verification + //if (currentAction.url.length === 0) { + // errormessage.push("URL path can't be empty.") + if ( + !currentAction.url.startsWith("/") && + baseUrl.length > 0 && + currentAction.url.length > 0 + ) { + errormessage.push("URL must start with /"); + } + + const check = urlPathQueries.findIndex((data) => data.name.length === 0); + if (check >= 0) { + errormessage.push("All queries must have a value"); + } + + return errormessage; + }; + + const getCurrentPaths = (urlPath) => { + var paths = []; + var queries = []; + + if (urlPath.includes("{") && urlPath.includes("}")) { + var tmpWord = ""; + var record = false; + + var query = false; + for (var key in urlPath) { + if (urlPath[key] === "?") { + query = true; + } + + if (urlPath[key] === "}") { + if (tmpWord === parameterName) { + tmpWord = ""; + record = false; + continue; + } else if (query) { + queries.push(tmpWord); + } else { + paths.push(tmpWord); + } + + tmpWord = ""; + record = false; + } + + if (record) { + tmpWord += urlPath[key]; + } + + //if (urlPath[key] === "{" && urlPath[key-1] === "/") { + if (urlPath[key] === "{") { + record = true; + } + } + } + + if (urlPath.includes("<") && urlPath.includes(">")) { + var tmpWord = ""; + var record = false; + + var query = false; + for (var key in urlPath) { + if (urlPath[key] === "?") { + query = true; + } + + if (urlPath[key] === ">") { + if (tmpWord === parameterName) { + tmpWord = ""; + record = false; + continue; + } else if (query) { + queries.push(tmpWord); + } else { + paths.push(tmpWord); + } + + tmpWord = ""; + record = false; + } + + if (record) { + tmpWord += urlPath[key]; + } + + //if (urlPath[key] === "{" && urlPath[key-1] === "/") { + if (urlPath[key] === "<") { + record = true; + } + } + } + + return [paths, queries]; + }; + + const UrlPathParameters = () => { + const values = getCurrentPaths(urlPath); + const paths = values[0]; + const queries = values[1]; + + if (currentAction.paths !== paths && urlPath.length > 0) { + //console.log("IN PATHS SETTER: !", paths) + setActionField("paths", paths); + } + + var tmpQueries = []; + + // No overlapping of names + for (var key in queries) { + const tmpquery = queries[key]; + const found = tmpQueries.find((query) => query.name === tmpquery); + if (found === undefined) { + tmpQueries.push({ name: queries[key], required: true }); + } + } + + // FIXME: Frontend isn't updating.. + if ( + tmpQueries.length > 0 && + JSON.stringify(tmpQueries) !== JSON.stringify(urlPathQueries) + ) { + setUrlPathQueries(tmpQueries); + } + + return paths.length > 0 ? ( +
Required parameters: {paths.join(", ")}
+ ) : null; + }; + + const newActionModal = ( + { + setUrlPath(""); + setCurrentAction({ + name: "", + description: "", + url: "", + file_field: "", + headers: "", + paths: [], + queries: [], + body: "", + errors: [], + method: actionNonBodyRequest[0], + }); + setCurrentActionMethod(apikeySelection[0]); + setUrlPathQueries([]); + setActionsModalOpen(false); + setFileUploadEnabled(false); + }} + > + + +
New action
+
+ + + Learn more about actions + +
+ Name + { + setActionField("name", e.target.value); + }} + onBlur={(e) => { + // Fix basic issues in frontend. Python functions run a-zA-Z0-9_ + const regex = /[A-Za-z0-9 _]/g; + const found = e.target.value.match(regex); + if (found !== null) { + setActionField("name", found.join("")); + } + }} + key={currentAction} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> +
+ Description + setActionField("description", e.target.value)} + InputProps={{ + style: { + color: "white", + }, + }} + /> + +

Request

+ +
+ URL path / Curl statement + { + setActionField("url", e.target.value); + setUrlPath(e.target.value); + console.log(e.target.value); + }} + helperText={ + + The path to use. Must start with /. Use {"{variablename}"} to + have path variables + + } + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + input: classes.input, + }, + style: { + color: "white", + }, + }} + onBlur={(event) => { + var parsedurl = event.target.value; + //console.log("URL: ", parsedurl) + if (parsedurl.includes(" ")) { + parsedurl = parsedurl.replaceAll(" ", " "); + } + + if (parsedurl.includes(" ")) { + parsedurl = parsedurl.replaceAll(" ", " "); + } + + if (parsedurl.includes("[") && parsedurl.includes("]")) { + //console.log("REPLACE1") + parsedurl = parsedurl.replaceAll("[", "{"); + parsedurl = parsedurl.replaceAll("]", "}"); + } + + if (parsedurl.includes("<") && parsedurl.includes(">")) { + //console.log("REPLACE2") + parsedurl = parsedurl.replaceAll("<", "{"); + parsedurl = parsedurl.replaceAll(">", "}"); + } + + //console.log("URL2: ", parsedurl) + if ( + parsedurl.startsWith("PUT ") || + parsedurl.startsWith("GET ") || + parsedurl.startsWith("POST ") || + parsedurl.startsWith("DELETE ") || + parsedurl.startsWith("PATCH ") || + parsedurl.startsWith("CONNECT ") + ) { + const tmp = parsedurl.split(" "); + + if (tmp.length > 1) { + parsedurl = tmp[1].trim(); + setActionField("url", parsedurl); + + setCurrentActionMethod(tmp[0].toUpperCase()); + setActionField("method", tmp[0].toUpperCase()); + } + + console.log("URL3: ", parsedurl); + + setUpdate(Math.random()); + } else if (parsedurl.startsWith("curl")) { + console.log("URL4: ", parsedurl); + + const request = parseCurl(event.target.value); + if ( + request !== event.target.value && + request.method !== undefined && + request.method !== null + ) { + if (request.method.toUpperCase() !== currentAction.Method) { + setCurrentActionMethod(request.method.toUpperCase()); + setActionField("method", request.method.toUpperCase()); + } + + if (request.header !== undefined && request.header !== null) { + var headers = []; + for (let [key, value] of Object.entries(request.header)) { + if ( + parameterName !== undefined && + key.toLowerCase() === parameterName.toLowerCase() + ) { + continue; + } + + if ( + key === "Authorization" && + authenticationOption === "Bearer auth" + ) { + continue; + } + + headers += key + "=" + value + "\n"; + } + + setActionField("headers", headers); + } + + if (request.body !== undefined && request.body !== null) { + setActionField("body", request.body); + } + + // Parse URL + if (request.url !== undefined) { + parsedurl = request.url; + } + } + + console.log("PARSED: ", parsedurl); + if (parsedurl !== undefined) { + if (parsedurl.includes("<") && parsedurl.includes(">")) { + parsedurl = parsedurl.split("<").join("{"); + parsedurl = parsedurl.split(">").join("}"); + } + + if ( + parsedurl.startsWith("http") || + parsedurl.startsWith("ftp") + ) { + if ( + parsedurl !== undefined && + parsedurl.includes(parameterName) + ) { + // Remove <> etc. + // + + console.log("IT HAS THE PARAM NAME!"); + const newurl = new URL(encodeURI(parsedurl)); + newurl.searchParams.delete(parameterName); + parsedurl = decodeURI(newurl.href); + } + + // Remove the base URL itself + if ( + parsedurl !== undefined && + baseUrl !== undefined && + baseUrl.length > 0 && + parsedurl.includes(baseUrl) + ) { + parsedurl = parsedurl.replace(baseUrl, ""); + } + + // Check URL query && headers + //setActionField("url", parsedurl) + } + } + } + + if (event.target.value !== parsedurl) { + setUrlPath(parsedurl); + setActionField("url", parsedurl); + } + //console.log("URL: ", request.url) + }} + /> + + {loopQueries} + + {currentActionMethod === "POST" ? ( + + ) : null} + {fileUploadEnabled ? ( + setActionField("file_field", e.target.value)} + helperText={ + + The File field to interact with + + } + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + ) : null} +
+ Headers: static for the action + setActionField("headers", e.target.value)} + helperText={ + + Headers that are part of the request. Default: EMPTY + + } + InputProps={{ + style: { + color: "white", + }, + }} + /> + {bodyInfo} + + {exampleResponse} + + + + + + +
+ ); + + const categories = [ + "Communication", + "Cases", + "SIEM", + "Assets", + "Intel", + "IAM", + "Network", + "Eradication", + "Other", + ]; + + const tagView = ( +
+ {/* { }} /> */} -

Categories

- -

Tags

- { - newWorkflowTags.push(chip) - setNewWorkflowTags(newWorkflowTags) - setUpdate("added"+chip) - }} - onDelete={(chip, index) => { - newWorkflowTags.splice(index, 1) - setNewWorkflowTags(newWorkflowTags) - setUpdate("delete "+chip) - }} - /> -
+

Categories

+ +

Tags

+ { + newWorkflowTags.push(chip); + setNewWorkflowTags(newWorkflowTags); + setUpdate("added" + chip); + }} + onDelete={(chip, index) => { + newWorkflowTags.splice(index, 1); + setNewWorkflowTags(newWorkflowTags); + setUpdate("delete " + chip); + }} + /> +
+ ); - const ParsedActionHandler = () => { - const passedOrg = {"id": "", "name": ""} - const owner = "" - const passedTags = ["single test"] + const ParsedActionHandler = () => { + const passedOrg = { id: "", name: "" }; + const owner = ""; + const passedTags = ["single test"]; - const [, setUpdate] = useState() - const [authenticationModalOpen, setAuthenticationModalOpen] = useState(false); - const [selectedApp, setSelectedApp] = useState({ - versions: [{ - "id": selectedAction.app_id, - "version": selectedAction.app_version, - }], - loop_versions: [selectedAction.app_version], - id: selectedAction.app_id, - name: selectedAction.app_name, - version: selectedAction.app_version, - }) + const [, setUpdate] = useState(); + const [authenticationModalOpen, setAuthenticationModalOpen] = + useState(false); + const [selectedApp, setSelectedApp] = useState({ + versions: [ + { + id: selectedAction.app_id, + version: selectedAction.app_version, + }, + ], + loop_versions: [selectedAction.app_version], + id: selectedAction.app_id, + name: selectedAction.app_name, + version: selectedAction.app_version, + }); - const [requiresAuthentication, setRequiresAuthentication] = useState(app.authentication !== null && app.authentication !== undefined && app.authentication.required && app.authentication.parameters !== undefined && app.authentication.parameters !== null ? true : false) - const [workflow, setWorkflow] = useState({ - name: "", - description: "", - actions: [selectedAction], - start: selectedAction.id, - tags: passedTags, - execution_org: passedOrg, - org_id: passedOrg.id, - id: uuidv4(), - isValid: true, - owner: owner, - created: Date.now(), - }) + const [requiresAuthentication, setRequiresAuthentication] = useState( + app.authentication !== null && + app.authentication !== undefined && + app.authentication.required && + app.authentication.parameters !== undefined && + app.authentication.parameters !== null + ? true + : false + ); + const [workflow, setWorkflow] = useState({ + name: "", + description: "", + actions: [selectedAction], + start: selectedAction.id, + tags: passedTags, + execution_org: passedOrg, + org_id: passedOrg.id, + id: uuidv4(), + isValid: true, + owner: owner, + created: Date.now(), + }); - const EndpointData = () => { - const [tmpVar, setTmpVar] = React.useState("") + const EndpointData = () => { + const [tmpVar, setTmpVar] = React.useState(""); - return ( -
- The API endpoint to use (URL) - predefined in the app - { - setTmpVar(event.target.value) - }} - onBlur={() => { - selectedApp.link = tmpVar - console.log("LINK: ", selectedApp.link) - setSelectedApp(selectedApp) - }} - /> -
- ) - } + return ( +
+ The API endpoint to use (URL) - predefined in the app + { + setTmpVar(event.target.value); + }} + onBlur={() => { + selectedApp.link = tmpVar; + console.log("LINK: ", selectedApp.link); + setSelectedApp(selectedApp); + }} + /> +
+ ); + }; - const setAppActionAuthentication = (newauth) => { - if (app.authentication.required) { - var findAuthId = "" - if (selectedAction.authentication_id !== null && selectedAction.authentication_id !== undefined && selectedAction.authentication_id.length > 0) { - findAuthId = selectedAction.authentication_id - } + const setAppActionAuthentication = (newauth) => { + if (app.authentication.required) { + var findAuthId = ""; + if ( + selectedAction.authentication_id !== null && + selectedAction.authentication_id !== undefined && + selectedAction.authentication_id.length > 0 + ) { + findAuthId = selectedAction.authentication_id; + } - var baseAuthOptions = [] - for (var key in newauth) { - var item = newauth[key] + var baseAuthOptions = []; + for (var key in newauth) { + var item = newauth[key]; - const newfields = {} - for (var filterkey in item.fields) { - newfields[item.fields[filterkey].key] = item.fields[filterkey].value - } + 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) { - baseAuthOptions.push(item) + item.fields = newfields; + if (item.app.name === app.name) { + baseAuthOptions.push(item); - if (item.id === findAuthId) { - selectedAction.selectedAuthentication = item - } - } - } + if (item.id === findAuthId) { + selectedAction.selectedAuthentication = item; + } + } + } - selectedAction.authentication = baseAuthOptions - //console.log("Authentication: ", authenticationOptions) - if (selectedAction.selectedAuthentication === null || selectedAction.selectedAuthentication === undefined || selectedAction.selectedAuthentication.length === "") { - selectedAction.selectedAuthentication = {} - } - } else { - selectedAction.authentication = [] - selectedAction.authentication_id = "" - selectedAction.selectedAuthentication = {} - } + selectedAction.authentication = baseAuthOptions; + //console.log("Authentication: ", authenticationOptions) + if ( + selectedAction.selectedAuthentication === null || + selectedAction.selectedAuthentication === undefined || + selectedAction.selectedAuthentication.length === "" + ) { + selectedAction.selectedAuthentication = {}; + } + } else { + selectedAction.authentication = []; + selectedAction.authentication_id = ""; + selectedAction.selectedAuthentication = {}; + } - setSelectedAction(selectedAction) - console.log(selectedAction) - } + setSelectedAction(selectedAction); + console.log(selectedAction); + }; - //{selectedAction.authentication !== undefined && selectedAction.authentication.length > 0 ? - 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!") - } + //{selectedAction.authentication !== undefined && selectedAction.authentication.length > 0 ? + 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 response.json() - }) - .then((responseJson) => { - if (responseJson.success && responseJson.data !== undefined && responseJson.data !== null && responseJson.data.length !== 0) { - var newauth = [] - for (var key in responseJson.data) { - if (responseJson.data[key].defined === false) { - continue - } + return response.json(); + }) + .then((responseJson) => { + if ( + responseJson.success && + responseJson.data !== undefined && + responseJson.data !== null && + responseJson.data.length !== 0 + ) { + var newauth = []; + for (var key in responseJson.data) { + if (responseJson.data[key].defined === false) { + continue; + } - newauth.push(responseJson.data[key]) - } + newauth.push(responseJson.data[key]); + } - setAppAuthentication(newauth) - setAppActionAuthentication(newauth) - } else { - if (app.authentication.required) { - const tmpParams = selectedAction.parameters - selectedAction.parameters = [] + setAppAuthentication(newauth); + setAppActionAuthentication(newauth); + } else { + if (app.authentication.required) { + const tmpParams = selectedAction.parameters; + selectedAction.parameters = []; - for (var paramkey in app.authentication.parameters) { - var item = app.authentication.parameters[paramkey] - item.configuration = true + for (var paramkey in app.authentication.parameters) { + var item = app.authentication.parameters[paramkey]; + item.configuration = true; - const found = selectedAction.parameters.find(param => param.name === item.name) - if (found === null || found === undefined) { - selectedAction.parameters.push(item) - } - } + const found = selectedAction.parameters.find( + (param) => param.name === item.name + ); + if (found === null || found === undefined) { + selectedAction.parameters.push(item); + } + } - for (var paramkey in tmpParams) { - var item = tmpParams[paramkey] - //item.configuration = true - - const found = selectedAction.parameters.find(param => param.name === item.name) - if (found === null || found === undefined) { - selectedAction.parameters.push(item) - } - } + for (var paramkey in tmpParams) { + var item = tmpParams[paramkey]; + //item.configuration = true - setSelectedAction(selectedAction) - } + const found = selectedAction.parameters.find( + (param) => param.name === item.name + ); + if (found === null || found === undefined) { + selectedAction.parameters.push(item); + } + } - //alert.error("Failed getting authentications") - } - }) - .catch(error => { - alert.error("Auth loading error: "+error.toString()) - }) - } + setSelectedAction(selectedAction); + } - if (!authLoaded && appAuthentication.length === 0 && selectedAction.id !== undefined) { - setAuthLoaded(true) - getAppAuthentication() - } else if (selectedAction.id === undefined && currentAction.name !== undefined && currentAction.name !== null && currentAction.name.length > 0) { - var methodName = `${currentAction.method}_${currentAction.name}` - if (currentAction.method.toLowerCase() === "custom" || currentAction.name.toLowerCase().startsWith(currentAction.method.toLowerCase())) { - methodName = currentAction.name - } + //alert.error("Failed getting authentications") + } + }) + .catch((error) => { + alert.error("Auth loading error: " + error.toString()); + }); + }; - methodName = methodName.toLowerCase().replaceAll(" ", "_") - if (app.actions !== null && app.actions !== undefined) { - var newselectedaction = app.actions.find(item => item.name.toLowerCase().replaceAll(" ", "_") === methodName) - if (newselectedaction !== undefined && newselectedaction !== null) { - newselectedaction.app_id = app.id - newselectedaction.app_name = app.name - newselectedaction.app_version = app.app_version - newselectedaction.authentication = [] - newselectedaction.authentication_id = "" - newselectedaction.selectedAuthentication = {} - setSelectedAction(newselectedaction) - } - } - } + if ( + !authLoaded && + appAuthentication.length === 0 && + selectedAction.id !== undefined + ) { + setAuthLoaded(true); + getAppAuthentication(); + } else if ( + selectedAction.id === undefined && + currentAction.name !== undefined && + currentAction.name !== null && + currentAction.name.length > 0 + ) { + var methodName = `${currentAction.method}_${currentAction.name}`; + if ( + currentAction.method.toLowerCase() === "custom" || + currentAction.name + .toLowerCase() + .startsWith(currentAction.method.toLowerCase()) + ) { + methodName = currentAction.name; + } - 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", - }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for setting app auth :O!") - } + methodName = methodName.toLowerCase().replaceAll(" ", "_"); + if (app.actions !== null && app.actions !== undefined) { + var newselectedaction = app.actions.find( + (item) => item.name.toLowerCase().replaceAll(" ", "_") === methodName + ); + if (newselectedaction !== undefined && newselectedaction !== null) { + newselectedaction.app_id = app.id; + newselectedaction.app_name = app.name; + newselectedaction.app_version = app.app_version; + newselectedaction.authentication = []; + newselectedaction.authentication_id = ""; + newselectedaction.selectedAuthentication = {}; + setSelectedAction(newselectedaction); + } + } + } - return response.json() - }) - .then((responseJson) => { - if (!responseJson.success) { - alert.error("Failed to set app auth: "+responseJson.reason) - } else { - getAppAuthentication(true) - setAuthenticationModalOpen(false) + 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", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for setting app auth :O!"); + } - // Needs a refresh with the new authentication.. - //alert.success("Successfully saved new app auth") - } - }) - .catch(error => { - alert.error(error.toString()) - }) - } + return response.json(); + }) + .then((responseJson) => { + if (!responseJson.success) { + alert.error("Failed to set app auth: " + responseJson.reason); + } else { + getAppAuthentication(true); + setAuthenticationModalOpen(false); - const AuthenticationData = (props) => { - const selectedApp = props.app - console.log("APP: ", selectedApp) + // Needs a refresh with the new authentication.. + //alert.success("Successfully saved new app auth") + } + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; - const [authenticationOption, setAuthenticationOptions] = React.useState({ - app: JSON.parse(JSON.stringify(selectedApp)), - fields: {}, - label: "", - usage: [{ - workflow_id: workflow.id, - }], - id: uuidv4(), - active: true, - }) + const AuthenticationData = (props) => { + const selectedApp = props.app; + console.log("APP: ", selectedApp); - if (selectedApp.authentication === undefined) { - return null - } + 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.parameters === null || selectedApp.authentication.parameters === undefined || selectedApp.authentication.parameters.length === 0) { - return null - } + if (selectedApp.authentication === undefined) { + return null; + } - authenticationOption.app.actions = [] + if ( + selectedApp.authentication.parameters === null || + selectedApp.authentication.parameters === undefined || + selectedApp.authentication.parameters.length === 0 + ) { + return null; + } - for (var key in selectedApp.authentication.parameters) { - if (authenticationOption.fields[selectedApp.authentication.parameters[key].name] === undefined) { - authenticationOption.fields[selectedApp.authentication.parameters[key].name] = "" - } - } + authenticationOption.app.actions = []; - 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 - } + for (var key in selectedApp.authentication.parameters) { + if ( + authenticationOption.fields[ + selectedApp.authentication.parameters[key].name + ] === undefined + ) { + authenticationOption.fields[ + selectedApp.authentication.parameters[key].name + ] = ""; + } + } - for (var key in selectedApp.authentication.parameters) { - if (authenticationOption.fields[selectedApp.authentication.parameters[key].name].length === 0) { - alert.info("Field "+selectedApp.authentication.parameters[key].name+" can't be empty") - return null - } - } + 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 + } - 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) - } + for (var key in selectedApp.authentication.parameters) { + if ( + authenticationOption.fields[ + selectedApp.authentication.parameters[key].name + ].length === 0 + ) { + alert.info( + "Field " + + selectedApp.authentication.parameters[key].name + + " can't be empty" + ); + return null; + } + } - setSelectedAction(selectedAction) + 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); + } - 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, - }) - } + setSelectedAction(selectedAction); - console.log("FIELDS: ", newFields) - newAuthOption.fields = newFields - setNewAppAuth(newAuthOption) - //appAuthentication.push(newAuthOption) - //setAppAuthentication(appAuthentication) - // - - setUpdate(authenticationOption.id) + 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) + // + + setUpdate(authenticationOption.id); + + /* {selectedAction.authentication.map(data => ( */ + }; - } + 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("PRE RETURN"); + return ( +
+ + + What is this? + +
+ These are required fields for authenticating with {selectedApp.name} +
+ Name - what is this used for? + { + authenticationOption.label = event.target.value; + }} + /> + {selectedApp.link.length > 0 ? ( +
+ +
+ ) : null} + +
+ {selectedApp.authentication.parameters.map((data, index) => { + return ( +
+ + {data.name} + { + authenticationOption.fields[data.name] = + event.target.value; + }} + /> +
+ ); + })} + + + + + +
+ ); + }; + }; - console.log("PRE RETURN") - return ( -
- - What is this?
- These are required fields for authenticating with {selectedApp.name} -
- Name - what is this used for? - { - authenticationOption.label = event.target.value - }} - /> - {selectedApp.link.length > 0 ?
: null} - -
- {selectedApp.authentication.parameters.map((data, index) => { - return ( -
- - {data.name} - { - authenticationOption.fields[data.name] = event.target.value - }} - /> -
- ) - })} - - - - - -
- ) - } - } - - const actionView = -
-
- {actionAmount > 0 && actionAmount < filteredActions.length ? - - : null} -
- Actions {actionAmount > 0 ? ({actionAmount} / {filteredActions.length}) : null} - - Actions are the tasks performed by an app - usually single URL paths for REST API's. - + }} + > + See all actions + + ) : null} +
+ + Actions{" "} + {actionAmount > 0 ? ( + + ({actionAmount} / {filteredActions.length}) + + ) : null} + + + Actions are the tasks performed by an app - usually single URL paths for + REST API's. + - {projectCategories !== undefined && projectCategories !== null && projectCategories.length > 1 ? -
- {projectCategories.map((tag, index) => { - const newname = tag.charAt(0).toUpperCase() + tag.slice(1) - return ( - { - if (selectedCategory === tag) { - setFilteredActions(actions) - setSelectedCategory("") - setActionAmount(50) - return - } + {projectCategories !== undefined && + projectCategories !== null && + projectCategories.length > 1 ? ( +
+ {projectCategories.map((tag, index) => { + const newname = tag.charAt(0).toUpperCase() + tag.slice(1); + return ( + { + if (selectedCategory === tag) { + setFilteredActions(actions); + setSelectedCategory(""); + setActionAmount(50); + return; + } - const foundActions = actions.filter(data => data.category === tag) - setFilteredActions(foundActions) - setSelectedCategory(tag) - if (actionAmount > foundActions.length) { - setActionAmount(foundActions.length) - } + const foundActions = actions.filter( + (data) => data.category === tag + ); + setFilteredActions(foundActions); + setSelectedCategory(tag); + if (actionAmount > foundActions.length) { + setActionAmount(foundActions.length); + } - //{filteredActions.slice(0,actionAmount).map((data, index) => { - - //console.log("Found: ", foundActions) - //{filteredActions.slice(0,actionAmount).map((data, index) => { - //{actions.slice(0,actionAmount).map((data, index) => { - }} - variant={selectedCategory === tag ? "contained " : "outlined"} - color={selectedCategory === tag ? "primary" : "secondary"} - /> - ) - })} -
- : null} -
- {loopActions} -
- - {/* + //{filteredActions.slice(0,actionAmount).map((data, index) => { + + //console.log("Found: ", foundActions) + //{filteredActions.slice(0,actionAmount).map((data, index) => { + //{actions.slice(0,actionAmount).map((data, index) => { + }} + variant={selectedCategory === tag ? "contained " : "outlined"} + color={selectedCategory === tag ? "primary" : "secondary"} + /> + ); + })} +
+ ) : null} +
+ {loopActions} +
+ + {/* {actionAmount} {actions.length} */} -
-
-
+
+
+
+ ); - const testView = -
-

Test

- Test an action to see whether it performs in an expected way. -  TBD: Click here to learn more about testing. -
- Test :) -
-
+ const testView = ( +
+

Test

+ Test an action to see whether it performs in an expected way. + +  TBD: Click here to learn more about testing + + .
Test :)
+
+ ); - var image = "" - const editHeaderImage = (event) => { - const file = event.target.value - const actualFile = event.target.files[0] - const fileObject = URL.createObjectURL(actualFile) - setFile(fileObject) - } + var image = ""; + const editHeaderImage = (event) => { + const file = event.target.value; + const actualFile = event.target.files[0]; + const fileObject = URL.createObjectURL(actualFile); + setFile(fileObject); + }; - if (file !== "") { - const img = document.getElementById('logo') - var canvas = document.createElement('canvas') - canvas.width = 174 - canvas.height = 174 - var ctx = canvas.getContext('2d') + 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 - ) + 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 + ); - try { - const canvasUrl = canvas.toDataURL() - if (canvasUrl !== fileBase64) { - //console.log("SET URL TO: ", canvasUrl) - setFileBase64(canvasUrl) - } - } catch (e) { - alert.error("Failed to parse canvasurl!") - } - } + try { + const canvasUrl = canvas.toDataURL(); + if (canvasUrl !== fileBase64) { + //console.log("SET URL TO: ", canvasUrl) + setFileBase64(canvasUrl); + } + } catch (e) { + alert.error("Failed to parse canvasurl!"); + } + }; - //console.log(img.width) - //console.log(img.width) - //canvas.width = img.width - //canvas.height = img.height - } + //console.log(img.width) + //console.log(img.width) + //canvas.width = img.width + //canvas.height = img.height + } - const [imageUploadError, setImageUploadError] = useState(""); - const [openImageModal, setOpenImageModal] = useState(""); - const [scale, setScale] = useState(1); - const [rotate, setRotatation] = useState(0); - const [disableImageUpload, setDisableImageUpload] = useState(true); + const [imageUploadError, setImageUploadError] = useState(""); + const [openImageModal, setOpenImageModal] = useState(""); + const [scale, setScale] = useState(1); + const [rotate, setRotatation] = useState(0); + const [disableImageUpload, setDisableImageUpload] = useState(true); - let imageData = fileBase64; - let croppedData = file.length > 0 ? file : fileBase64 + let imageData = fileBase64; + let croppedData = file.length > 0 ? file : fileBase64; - const imageInfo = + const imageInfo = ( + + ); - const alternateImg = { - upload.click() - }}/> - - const zoomIn = () => { - console.log("ZOOOMING IN") - setScale(scale+0.1); - } + const alternateImg = ( + { + upload.click(); + }} + /> + ); - const zoomOut = () => { - setScale(scale-0.1); - } - const rotatation = () => { - setRotatation(rotate+10); - } + const zoomIn = () => { + console.log("ZOOOMING IN"); + setScale(scale + 0.1); + }; - const onPositionChange = () => { - setDisableImageUpload(false); - } + const zoomOut = () => { + setScale(scale - 0.1); + }; + const rotatation = () => { + setRotatation(rotate + 10); + }; - const onCancelSaveAppIcon = () => { - setFile(""); - setOpenImageModal(false) - setImageUploadError("") - } + const onPositionChange = () => { + setDisableImageUpload(false); + }; - let editor; - const setEditorRef = (imgEditor) => { editor = imgEditor; } + const onCancelSaveAppIcon = () => { + setFile(""); + setOpenImageModal(false); + setImageUploadError(""); + }; - const onSaveAppIcon = () => { - if(editor){ - try { - setFile(""); - const canvas = editor.getImageScaledToCanvas(); - setFileBase64(canvas.toDataURL()) - setOpenImageModal(false) - setDisableImageUpload(true); - } catch (e) { - alert.error("Failed to set image. Replace it if this persists.") - } - } - } + let editor; + const setEditorRef = (imgEditor) => { + editor = imgEditor; + }; - const errorText = imageUploadError.length > 0 ?
Error: {imageUploadError}
: null - const imageUploadModalView = openImageModal ? - - -
Upload App Icon
- {errorText} - - setRotatation(0)} - /> - - - - - - - - - - - - - - - - - - - - {/**/} -
-
- : null; + const onSaveAppIcon = () => { + if (editor) { + try { + setFile(""); + const canvas = editor.getImageScaledToCanvas(); + setFileBase64(canvas.toDataURL()); + setOpenImageModal(false); + setDisableImageUpload(true); + } catch (e) { + alert.error("Failed to set image. Replace it if this persists."); + } + } + }; - // Random names for type & autoComplete. Didn't research :^) - const landingpageDataBrowser = -
- - -

- - Apps -

- -

- {name} {actions === null || actions === undefined || actions.length === 0 ? null : ({actions.length})} -

-
- {imageUploadModalView} - upload = ref} onChange={editHeaderImage} /> - -

General information

- Click here to learn more about app creation -
- -
{ - /* + const errorText = + imageUploadError.length > 0 ? ( +
Error: {imageUploadError}
+ ) : null; + const imageUploadModalView = openImageModal ? ( + + + +
Upload App Icon
+
+ {errorText} + + setRotatation(0)} + /> + + + + + + + + + + + + + + + + + + + + {/**/} +
+
+ ) : null; + + // Random names for type & autoComplete. Didn't research :^) + const landingpageDataBrowser = ( +
+ + +

+ + Apps +

+ +

+ {name}{" "} + {actions === null || + actions === undefined || + actions.length === 0 ? null : ( + ({actions.length}) + )} +

+
+ {imageUploadModalView} + (upload = ref)} + onChange={editHeaderImage} + /> + +

+ General information +

+ + Click here to learn more about app creation + +
+ +
{ + /* if (fileBase64.length === 0) { upload.click() } */ - setOpenImageModal(true) - }}> - {!imageData && (alternateImg)} - {imageInfo} - upload = ref} onChange={editHeaderImage} /> -
-
-
-
- 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 - } - } + setOpenImageModal(true); + }} + > + {!imageData && alternateImg} + {imageInfo} + (upload = ref)} + onChange={editHeaderImage} + /> +
+ +
+
+ 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 > 29) { - alert.error("Choose a shorter name (max 29).") - return - } + if (e.target.value.length > 29) { + alert.error("Choose a shorter name (max 29)."); + return; + } - //e.target.value.trim() + //e.target.value.trim() - setName(e.target.value) - }} - onBlur={e => { - setName(e.target.value.trim()) - }} - color="primary" - InputProps={{ - style:{ - color: "white", - height: "50px", - fontSize: "1em", - }, - classes: { - notchedOutline: classes.notchedOutline, - }, - }} - /> -
- Description - setDescription(e.target.value)} - InputProps={{ - classes: { - notchedOutline: classes.notchedOutline, - }, - style:{ - color: "white", - }, - }} - /> -
-
- - API information - Base URL - used as suggestion to the user - Must start with http(s):// and CANT end with /. } - placeholder="https://api.example.com" - onChange={e => setBaseUrl(e.target.value)} - onBlur={(event) => { - var tmpstring = event.target.value.trim() - if (tmpstring.endsWith("/")) { - tmpstring = tmpstring.slice(0, -1) - } - if (tmpstring.length > 4 && !tmpstring.startsWith("http") && !tmpstring.startsWith("ftp")) { - alert.error("URL must start with http(s)://") - } + setName(e.target.value); + }} + onBlur={(e) => { + setName(e.target.value.trim()); + }} + color="primary" + InputProps={{ + style: { + color: "white", + height: "50px", + fontSize: "1em", + }, + classes: { + notchedOutline: classes.notchedOutline, + }, + }} + /> +
+ Description + setDescription(e.target.value)} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> +
+
+ + + API information + + + Base URL - used as suggestion to the user + + + Must start with http(s):// and CANT end with /.{" "} + + } + placeholder="https://api.example.com" + onChange={(e) => setBaseUrl(e.target.value)} + onBlur={(event) => { + var tmpstring = event.target.value.trim(); + if (tmpstring.endsWith("/")) { + tmpstring = tmpstring.slice(0, -1); + } + if ( + tmpstring.length > 4 && + !tmpstring.startsWith("http") && + !tmpstring.startsWith("ftp") + ) { + alert.error("URL must start with http(s)://"); + } - //if (authenticationOption === "No authentication" && + //if (authenticationOption === "No authentication" && - setBaseUrl(tmpstring) - }} - /> - - Authentication type - - - {basicAuth} - {bearerAuth} - {apiKey} - {oauth2Auth} - {jwtAuth} - {extraKeys} + setBaseUrl(tmpstring); + }} + /> + + Authentication type + + + {basicAuth} + {bearerAuth} + {apiKey} + {oauth2Auth} + {jwtAuth} + {extraKeys} - {/*authenticationOption === "No authentication" ? null : + {/*authenticationOption === "No authentication" ? null : Authentication required (default true)
@@ -3737,46 +4824,61 @@ const AppCreator = (props) => { setAuthenticationRequired(!authenticationRequired) }} />} />*/} - -
- {actionView} -
+ +
{actionView}
- -
- {tagView} -
- {/* + +
{tagView}
+ {/* {/* {testView} */} - - - {errorCode.length > 0 ? `Error: ${errorCode}` : null} - - -
+ + + {errorCode.length > 0 ? `Error: ${errorCode}` : null} + +
+
+ ); + const loadedCheck = + isLoaded && isAppLoaded && !firstrequest ? ( +
+
{landingpageDataBrowser}
+ {newActionModal} +
+ ) : ( +
+ ); - const loadedCheck = isLoaded && isAppLoaded && !firstrequest ? -
-
{landingpageDataBrowser}
- {newActionModal} -
- : -
-
- - return( -
- {loadedCheck} -
- ) -} + return
{loadedCheck}
; +}; export default AppCreator; diff --git a/frontend/src/views/Apps.jsx b/frontend/src/views/Apps.jsx index b69836cc..f413da97 100644 --- a/frontend/src/views/Apps.jsx +++ b/frontend/src/views/Apps.jsx @@ -1,1651 +1,2185 @@ -import React, { useEffect } from 'react'; +import React, { useEffect } from "react"; -import { useInterval } from 'react-powerhooks'; +import { useInterval } from "react-powerhooks"; -import {IconButton, Typography, Grid, Select, Paper, Divider, ButtonBase, Button, TextField, FormControl, MenuItem, Tooltip, FormControlLabel, Switch, Input, Breadcrumbs, Chip, Dialog, DialogTitle, DialogActions, DialogContent, CircularProgress} from '@material-ui/core'; -import {LockOpen as LockOpenIcon, OpenInNew as OpenInNewIcon,Apps as AppsIcon, Cached as CachedIcon, Publish as PublishIcon, CloudDownload as CloudDownloadIcon, Edit as EditIcon, Delete as DeleteIcon} from '@material-ui/icons'; +import { + IconButton, + Typography, + Grid, + Select, + Paper, + Divider, + ButtonBase, + Button, + TextField, + FormControl, + MenuItem, + Tooltip, + FormControlLabel, + Switch, + Input, + Breadcrumbs, + Chip, + Dialog, + DialogTitle, + DialogActions, + DialogContent, + CircularProgress, +} from "@material-ui/core"; +import { + LockOpen as LockOpenIcon, + OpenInNew as OpenInNewIcon, + Apps as AppsIcon, + Cached as CachedIcon, + Publish as PublishIcon, + CloudDownload as CloudDownloadIcon, + Edit as EditIcon, + Delete as DeleteIcon, +} from "@material-ui/icons"; -import { useTheme } from '@material-ui/core/styles'; +import { useTheme } from "@material-ui/core/styles"; -import YAML from 'yaml' -import {Link} from 'react-router-dom'; +import YAML from "yaml"; +import { Link } from "react-router-dom"; import { useAlert } from "react-alert"; -import Dropzone from '../components/Dropzone'; +import Dropzone from "../components/Dropzone"; -const surfaceColor = "#27292D" -const inputColor = "#383B40" +const surfaceColor = "#27292D"; +const inputColor = "#383B40"; const chipStyle = { - backgroundColor: "#3d3f43", height: 30, marginRight: 5, paddingLeft: 5, paddingRight: 5, height: 28, cursor: "pointer", borderColor: "#3d3f43", color: "white", -} + backgroundColor: "#3d3f43", + height: 30, + marginRight: 5, + paddingLeft: 5, + paddingRight: 5, + height: 28, + cursor: "pointer", + borderColor: "#3d3f43", + color: "white", +}; // Fixes names by making them uppercase and such // Used for labels. A lot of places don't use this yet export const FixName = (name) => { - const newAppname = (name.charAt(0).toUpperCase()+name.substring(1)).replaceAll("_", " ") - return newAppname -} + const newAppname = ( + name.charAt(0).toUpperCase() + name.substring(1) + ).replaceAll("_", " "); + return newAppname; +}; // Parses JSON data into keys that can be used everywhere :) export const GetParsedPaths = (inputdata, basekey) => { - const splitkey = "." - var parsedValues = [] - if (inputdata === undefined || inputdata === null) { - return parsedValues - } + const splitkey = "."; + var parsedValues = []; + if (inputdata === undefined || inputdata === null) { + return parsedValues; + } - if (typeof(inputdata) !== "object") { - return parsedValues - } + if (typeof inputdata !== "object") { + return parsedValues; + } - for (const [key, value] of Object.entries(inputdata)) { - // Check if loop or JSON - const extra = basekey.length > 0 ? splitkey : "" - const basekeyname = `${basekey.slice(1, basekey.length).split(".").join(splitkey)}${extra}${key}` + for (const [key, value] of Object.entries(inputdata)) { + // 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.") - parsedValues.push({"type": "object", "name": "Node", "autocomplete": `${basekey.replaceAll(" ", "_")}`}) - parsedValues.push({"type": "list", "name": `${splitkey}list`, "autocomplete": `${basekey.replaceAll(" ", "_")}.#`}) - const returnValues = GetParsedPaths(value, `${basekey}.#`) - for (var subkey in returnValues) { - parsedValues.push(returnValues[subkey]) - } + // Handle direct loop! + if (!isNaN(key) && basekey === "") { + console.log("Handling direct loop."); + parsedValues.push({ + type: "object", + name: "Node", + autocomplete: `${basekey.replaceAll(" ", "_")}`, + }); + parsedValues.push({ + type: "list", + name: `${splitkey}list`, + autocomplete: `${basekey.replaceAll(" ", "_")}.#`, + }); + const returnValues = GetParsedPaths(value, `${basekey}.#`); + for (var subkey in returnValues) { + parsedValues.push(returnValues[subkey]); + } - return parsedValues - } + return parsedValues; + } - //console.log("KEY: ", key, "VALUE: ", value, "BASEKEY: ", basekeyname) - if (typeof(value) === 'object') { - if (Array.isArray(value)) { - // Check if each item is object - parsedValues.push({"type": "object", "name": basekeyname, "autocomplete": `${basekey}.${key.replaceAll(" ", "_")}`}) - parsedValues.push({"type": "list", "name": `${basekeyname}${splitkey}list`, "autocomplete": `${basekey}.${key.replaceAll(" ", "_")}.#`}) + //console.log("KEY: ", key, "VALUE: ", value, "BASEKEY: ", basekeyname) + if (typeof value === "object") { + if (Array.isArray(value)) { + // Check if each item is object + parsedValues.push({ + type: "object", + name: basekeyname, + autocomplete: `${basekey}.${key.replaceAll(" ", "_")}`, + }); + parsedValues.push({ + type: "list", + name: `${basekeyname}${splitkey}list`, + autocomplete: `${basekey}.${key.replaceAll(" ", "_")}.#`, + }); - // Only check the first. This would be probably be dumb otherwise. - for (var subkey in value) { - if (typeof(value) === 'object') { - const returnValues = GetParsedPaths(value[subkey], `${basekey}.${key}.#`) - for (var subkey in returnValues) { - parsedValues.push(returnValues[subkey]) - } - } + // Only check the first. This would be probably be dumb otherwise. + for (var subkey in value) { + if (typeof value === "object") { + const returnValues = GetParsedPaths( + value[subkey], + `${basekey}.${key}.#` + ); + for (var subkey in returnValues) { + parsedValues.push(returnValues[subkey]); + } + } - // Don't need else as # (all items) is already defined before the loop + // Don't need else as # (all items) is already defined before the loop - break - } - //console.log(key+" is array") - } else { - parsedValues.push({"type": "object", "name": basekeyname, "autocomplete": `${basekey}.${key.replaceAll(" ", "_")}`}) - const returnValues = GetParsedPaths(value, `${basekey}.${key}`) - for (var subkey in returnValues) { - parsedValues.push(returnValues[subkey]) - } - } - } else { - parsedValues.push({"type": "value", "name": basekeyname, "autocomplete": `${basekey}.${key.replaceAll(" ", "_")}`, "value": value,}) - } - } - - return parsedValues -} + break; + } + //console.log(key+" is array") + } else { + parsedValues.push({ + type: "object", + name: basekeyname, + autocomplete: `${basekey}.${key.replaceAll(" ", "_")}`, + }); + const returnValues = GetParsedPaths(value, `${basekey}.${key}`); + for (var subkey in returnValues) { + parsedValues.push(returnValues[subkey]); + } + } + } else { + parsedValues.push({ + type: "value", + name: basekeyname, + autocomplete: `${basekey}.${key.replaceAll(" ", "_")}`, + value: value, + }); + } + } + return parsedValues; +}; const Apps = (props) => { const { globalUrl, isLoggedIn, isLoaded, userdata } = props; - //const [workflows, setWorkflows] = React.useState([]); - const theme = useTheme(); - const baseRepository = "https://github.com/frikky/shuffle-apps" - const alert = useAlert() - const [selectedApp, setSelectedApp] = React.useState({}); - const [firstrequest, setFirstrequest] = React.useState(true) - const [apps, setApps] = React.useState([]) - const [filteredApps, setFilteredApps] = React.useState([]) - const [validation, setValidation] = React.useState(false) - const [isLoading, setIsLoading] = React.useState(true) - const [appSearchLoading, setAppSearchLoading] = React.useState(false) - const [selectedAction, setSelectedAction] = React.useState({}) - const [searchBackend, setSearchBackend] = React.useState(false) - const [searchableApps, setSearchableApps] = React.useState([]) + //const [workflows, setWorkflows] = React.useState([]); + const theme = useTheme(); + const baseRepository = "https://github.com/frikky/shuffle-apps"; + const alert = useAlert(); + const [selectedApp, setSelectedApp] = React.useState({}); + const [firstrequest, setFirstrequest] = React.useState(true); + const [apps, setApps] = React.useState([]); + const [filteredApps, setFilteredApps] = React.useState([]); + const [validation, setValidation] = React.useState(false); + const [isLoading, setIsLoading] = React.useState(true); + const [appSearchLoading, setAppSearchLoading] = React.useState(false); + const [selectedAction, setSelectedAction] = React.useState({}); + const [searchBackend, setSearchBackend] = React.useState(false); + const [searchableApps, setSearchableApps] = React.useState([]); - const [openApi, setOpenApi] = React.useState("") - const [openApiData, setOpenApiData] = React.useState("") - const [appValidation, setAppValidation] = React.useState("") - const [loadAppsModalOpen, setLoadAppsModalOpen] = React.useState(false); - const [deleteModalOpen, setDeleteModalOpen] = React.useState(false); - const [openApiModal, setOpenApiModal] = React.useState(false); - const [openApiModalType, setOpenApiModalType] = React.useState(""); - const [openApiError, setOpenApiError] = React.useState("") - const [field1, setField1] = React.useState("") - const [field2, setField2] = React.useState("") - const [cursearch, setCursearch] = React.useState("") - const [sharingConfiguration, setSharingConfiguration] = React.useState("you") - const [downloadBranch, setDownloadBranch] = React.useState("master") + const [openApi, setOpenApi] = React.useState(""); + const [openApiData, setOpenApiData] = React.useState(""); + const [appValidation, setAppValidation] = React.useState(""); + const [loadAppsModalOpen, setLoadAppsModalOpen] = React.useState(false); + const [deleteModalOpen, setDeleteModalOpen] = React.useState(false); + const [openApiModal, setOpenApiModal] = React.useState(false); + const [openApiModalType, setOpenApiModalType] = React.useState(""); + const [openApiError, setOpenApiError] = React.useState(""); + const [field1, setField1] = React.useState(""); + const [field2, setField2] = React.useState(""); + const [cursearch, setCursearch] = React.useState(""); + const [sharingConfiguration, setSharingConfiguration] = React.useState("you"); + const [downloadBranch, setDownloadBranch] = React.useState("master"); - const [isDropzone, setIsDropzone] = React.useState(false); - const upload = React.useRef(null); - const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" ? true : false - const borderRadius = 3 - const viewWidth = 590 + const [isDropzone, setIsDropzone] = React.useState(false); + const upload = React.useRef(null); + const isCloud = + window.location.host === "localhost:3002" || + window.location.host === "shuffler.io" + ? true + : false; + const borderRadius = 3; + const viewWidth = 590; - const { start, stop } = useInterval({ - duration: 5000, - startImmediate: false, - callback: () => { - getApps() - } - }); + const { start, stop } = useInterval({ + duration: 5000, + startImmediate: false, + callback: () => { + getApps(); + }, + }); - useEffect(() => { - if (apps.length <= 0 && firstrequest) { - document.title = "Shuffle - Apps" + useEffect(() => { + if (apps.length <= 0 && firstrequest) { + document.title = "Shuffle - Apps"; - if (!isLoggedIn && isLoaded) { - window.location = "/login" - } + if (!isLoggedIn && isLoaded) { + window.location = "/login"; + } - setFirstrequest(false) - getApps() - } - }) + setFirstrequest(false); + getApps(); + } + }); - function sortByKey(array, key) { - if (array === undefined || array === null) { - return array - } + function sortByKey(array, key) { + if (array === undefined || array === null) { + return array; + } - return array.sort(function(a, b) { - var x = a[key]; - var y = b[key]; + return array.sort(function (a, b) { + var x = a[key]; + var y = b[key]; - if (typeof x == "string") - { - x = (""+x).toLowerCase(); - } - if (typeof y == "string") - { - y = (""+y).toLowerCase(); - } + if (typeof x == "string") { + x = ("" + x).toLowerCase(); + } + if (typeof y == "string") { + y = ("" + y).toLowerCase(); + } - return ((x < y) ? 1 : ((x > y) ? -1 : 0)); + return x < y ? 1 : x > y ? -1 : 0; }); - } + } - const appViewStyle = { - color: "#ffffff", - width: "100%", - display: "flex", - margin: "auto", - } + const appViewStyle = { + color: "#ffffff", + width: "100%", + display: "flex", + margin: "auto", + }; - const paperAppStyle = { - minHeight: 130, - maxHeight: 130, - minWidth: "100%", - maxWidth: 612.5, - marginBottom: 5, - borderRadius: 5, - color: "white", - backgroundColor: surfaceColor, - cursor: "pointer", - display: "flex", - } + const paperAppStyle = { + minHeight: 130, + maxHeight: 130, + minWidth: "100%", + maxWidth: 612.5, + marginBottom: 5, + borderRadius: 5, + color: "white", + backgroundColor: surfaceColor, + cursor: "pointer", + display: "flex", + }; - const getApps = () => { - fetch(globalUrl+"/api/v1/apps", { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - credentials: "include", - }) - .then((response) => { - setIsLoading(false) - if (response.status !== 200) { - console.log("Status not 200 for apps :O!") - - if (isCloud) { - window.location.pathname = "/search" - } - } - - return response.json() - }) - .then((responseJson) => { - //console.log("Apps: ", responseJson) - //responseJson = sortByKey(responseJson, "large_image") - //responseJson = sortByKey(responseJson, "is_valid") - //setFilteredApps(responseJson.filter(app => !internalIds.includes(app.name) && !(!app.activated && app.generated))) - - var privateapps = [] - var valid = [] - var invalid = [] - for (var key in responseJson) { - const app = responseJson[key] - if (app.is_valid && !(!app.activated && app.generated)) { - privateapps.push(app) - } else if (app.private_id !== undefined && app.private_id.length > 0) { - valid.push(app) - } else { - invalid.push(app) - } - } - - //console.log(privateapps) - //console.log(valid) - //console.log(invalid) - //console.log(privateapps) - //privateapps.reverse() - privateapps.push(...valid) - privateapps.push(...invalid) - - setApps(privateapps) - setCursearch("") - - //handleSearchChange(event.target.value) - //setCursearch(event.target.value) - setFilteredApps(privateapps) - if (privateapps.length > 0) { - if (selectedApp.id === undefined || selectedApp.id === null) { - setSelectedApp(privateapps[0]) - } - - if (privateapps[0].actions !== null && privateapps[0].actions.length > 0) { - setSelectedAction(privateapps[0].actions[0]) - } else { - setSelectedAction({}) - } - } - - //runAppSearch("") + const getApps = () => { + fetch(globalUrl + "/api/v1/apps", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", }) - .catch(error => { - alert.error(error.toString()) - setIsLoading(false) - }); - } + .then((response) => { + setIsLoading(false); + if (response.status !== 200) { + console.log("Status not 200 for apps :O!"); - const downloadApp = (inputdata) => { - const id = inputdata.id + if (isCloud) { + window.location.pathname = "/search"; + } + } - alert.info("Downloading..") - fetch(globalUrl+"/api/v1/apps/"+id+"/config", { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - credentials: "include", - }) - .then((response) => { - if (response.status !== 200) { - window.location.pathname = "/apps" - } + return response.json(); + }) + .then((responseJson) => { + //console.log("Apps: ", responseJson) + //responseJson = sortByKey(responseJson, "large_image") + //responseJson = sortByKey(responseJson, "is_valid") + //setFilteredApps(responseJson.filter(app => !internalIds.includes(app.name) && !(!app.activated && app.generated))) - return response.json() - }) - .then((responseJson) => { - if (!responseJson.success) { - alert.error("Failed to download file") - } else { - console.log(responseJson) - const basedata = atob(responseJson.openapi) - console.log("BASE: ", basedata) - var inputdata = JSON.parse(basedata) - console.log("POST INPUT: ", inputdata) - inputdata = JSON.parse(inputdata.body) + var privateapps = []; + var valid = []; + var invalid = []; + for (var key in responseJson) { + const app = responseJson[key]; + if (app.is_valid && !(!app.activated && app.generated)) { + privateapps.push(app); + } else if ( + app.private_id !== undefined && + app.private_id.length > 0 + ) { + valid.push(app); + } else { + invalid.push(app); + } + } - const newpaths = {} - if (inputdata["paths"] !== undefined) { - Object.keys(inputdata["paths"]).forEach(function(key) { - newpaths[key.split("?")[0]] = inputdata.paths[key] - }) - } + //console.log(privateapps) + //console.log(valid) + //console.log(invalid) + //console.log(privateapps) + //privateapps.reverse() + privateapps.push(...valid); + privateapps.push(...invalid); - inputdata.paths = newpaths - console.log("INPUT: ", inputdata) - var name = inputdata.info.title - name = name.replace(/ /g, "_", -1) - name = name.toLowerCase() + setApps(privateapps); + setCursearch(""); - delete inputdata.id - delete inputdata.editing + //handleSearchChange(event.target.value) + //setCursearch(event.target.value) + setFilteredApps(privateapps); + if (privateapps.length > 0) { + if (selectedApp.id === undefined || selectedApp.id === null) { + setSelectedApp(privateapps[0]); + } - const data = YAML.stringify(inputdata) - var blob = new Blob( [ data ], { - type: 'application/octet-stream' - }) + if ( + privateapps[0].actions !== null && + privateapps[0].actions.length > 0 + ) { + setSelectedAction(privateapps[0].actions[0]); + } else { + setSelectedAction({}); + } + } - var url = URL.createObjectURL( blob ) - var link = document.createElement( 'a' ) - link.setAttribute( 'href', url ) - link.setAttribute( 'download', `${name}.yaml` ) - 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 ) - //link.parentNode.removeChild(link) - } - }) - .catch(error => { - console.log(error) - alert.error(error.toString()) - }); - } + //runAppSearch("") + }) + .catch((error) => { + alert.error(error.toString()); + setIsLoading(false); + }); + }; - // dropdown with copy etc I guess - const appPaper = (data) => { - if (data.name === "" && data.id === "") { - return null - } + const downloadApp = (inputdata) => { + const id = inputdata.id; - var boxWidth = "2px" - if (selectedApp.id === data.id) { - boxWidth = "4px" - } + alert.info("Downloading.."); + fetch(globalUrl + "/api/v1/apps/" + id + "/config", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + window.location.pathname = "/apps"; + } - var boxColor = "orange" - if (data.is_valid) { - boxColor = "green" - } + return response.json(); + }) + .then((responseJson) => { + if (!responseJson.success) { + alert.error("Failed to download file"); + } else { + console.log(responseJson); + const basedata = atob(responseJson.openapi); + console.log("BASE: ", basedata); + var inputdata = JSON.parse(basedata); + console.log("POST INPUT: ", inputdata); + inputdata = JSON.parse(inputdata.body); - if (!data.activated && data.generated) { - boxColor = "orange" - } + const newpaths = {}; + if (inputdata["paths"] !== undefined) { + Object.keys(inputdata["paths"]).forEach(function (key) { + newpaths[key.split("?")[0]] = inputdata.paths[key]; + }); + } - if (data.invalid) { - boxColor = "red" - } + inputdata.paths = newpaths; + console.log("INPUT: ", inputdata); + var name = inputdata.info.title; + name = name.replace(/ /g, "_", -1); + name = name.toLowerCase(); - //
- //
- var imageline = data.large_image === undefined || data.large_image.length === 0 ? - {data.title} - : - {data.title} { - //console.log("IMG LOADED!: ", event.target) - }} /> + delete inputdata.id; + delete inputdata.editing; - var newAppname = data.name - if (newAppname === undefined) { - newAppname = "Undefined" - } else { - newAppname = newAppname.charAt(0).toUpperCase()+newAppname.substring(1) - newAppname = newAppname.replaceAll("_", " ") - } + const data = YAML.stringify(inputdata); + var blob = new Blob([data], { + type: "application/octet-stream", + }); - var sharing = "public" - if (!data.sharing) { - sharing = "private" - } + var url = URL.createObjectURL(blob); + var link = document.createElement("a"); + link.setAttribute("href", url); + link.setAttribute("download", `${name}.yaml`); + 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); + //link.parentNode.removeChild(link) + } + }) + .catch((error) => { + console.log(error); + alert.error(error.toString()); + }); + }; - var valid = "true" - if (!data.valid) { - valid = "false" - } + // dropdown with copy etc I guess + const appPaper = (data) => { + if (data.name === "" && data.id === "") { + return null; + } - if (data.actions === null || data.actions.length === 0) { - valid = "false" - } + var boxWidth = "2px"; + if (selectedApp.id === data.id) { + boxWidth = "4px"; + } - var description = data.description - const maxDescLen = 60 - if (description.length > maxDescLen) { - description = data.description.slice(0, maxDescLen)+"..." - } + var boxColor = "orange"; + if (data.is_valid) { + boxColor = "green"; + } - const version = data.app_version - return ( - { - if (selectedApp.id !== data.id) { - data.name = newAppname - setSelectedApp(data) + if (!data.activated && data.generated) { + boxColor = "orange"; + } - console.log(data) - if (data.actions !== undefined && data.actions !== null && data.actions.length > 0) { - setSelectedAction(data.actions[0]) - } else { - setSelectedAction({}) - } + if (data.invalid) { + boxColor = "red"; + } - if (data.sharing) { - setSharingConfiguration(isCloud ? "public" : "everyone") - } - } - }}> - - - {imageline} - -
- - - - - {newAppname} - - -
- - - {description} - - -
- - {data.tags === null || data.tags === undefined ? null : data.tags.map((tag, index) => { - if (index >= 3) { - return null - } + //
+ //
+ var imageline = + data.large_image === undefined || data.large_image.length === 0 ? ( + {data.title} + ) : ( + {data.title} { + //console.log("IMG LOADED!: ", event.target) + }} + /> + ); - return ( - { - //console.log("SEARCH: ", event.target.value) - handleSearchChange(tag) - setCursearch(tag) - //id="app_search_field" - const searchfield = document.getElementById("app_search_field") - if (searchfield !== null && searchfield !== undefined) { - console.log("SEARCHFIELD: ", searchfield) - searchfield.value = tag - } - }} - /> - ) - })} - - - - + var newAppname = data.name; + if (newAppname === undefined) { + newAppname = "Undefined"; + } else { + newAppname = newAppname.charAt(0).toUpperCase() + newAppname.substring(1); + newAppname = newAppname.replaceAll("_", " "); + } - {data.activated && data.private_id !== undefined && data.private_id.length > 0 && data.generated ? - {downloadApp(data)}}> - - - - - : null} - - ) - } + var sharing = "public"; + if (!data.sharing) { + sharing = "private"; + } - const dividerColor = "rgb(225, 228, 232)" - const uploadViewPaperStyle = { - minWidth: viewWidth, - maxWidth: viewWidth, - color: "white", - borderRadius: 5, - backgroundColor: surfaceColor, - //display: "flex", - marginBottom: 10, - overflow: "hidden", - } + var valid = "true"; + if (!data.valid) { + valid = "false"; + } - const UploadView = () => { - //var imageline = selectedApp.large_image === undefined || selectedApp.large_image.length === 0 ? - // - // : - // PICTURE - // FIXME - add label to apps, as this might be slow with A LOT of apps - var newAppname = selectedApp.name - if (newAppname !== undefined && newAppname.length > 0) { - newAppname = newAppname.replaceAll("_", " ") - newAppname = newAppname.charAt(0).toUpperCase()+newAppname.substring(1) - } else { - newAppname = "" - } + if (data.actions === null || data.actions.length === 0) { + valid = "false"; + } - var description = selectedApp.description + var description = data.description; + const maxDescLen = 60; + if (description.length > maxDescLen) { + description = data.description.slice(0, maxDescLen) + "..."; + } - const editUrl = "/apps/edit/"+selectedApp.id - const activateUrl = "/apps/new?id="+selectedApp.id + const version = data.app_version; + return ( + { + if (selectedApp.id !== data.id) { + data.name = newAppname; + setSelectedApp(data); - var downloadButton = selectedApp.activated && selectedApp.private_id !== undefined && selectedApp.private_id.length > 0 && selectedApp.generated ? - - - - : null + console.log(data); + if ( + data.actions !== undefined && + data.actions !== null && + data.actions.length > 0 + ) { + setSelectedAction(data.actions[0]); + } else { + setSelectedAction({}); + } - var editButton = selectedApp.activated && selectedApp.private_id !== undefined && selectedApp.private_id.length > 0 && selectedApp.generated ? - - - - - : null + if (data.sharing) { + setSharingConfiguration(isCloud ? "public" : "everyone"); + } + } + }} + > + + + {imageline} + +
+ + + + + {newAppname} + + +
+ + + {description} + + +
+ + {data.tags === null || data.tags === undefined + ? null + : data.tags.map((tag, index) => { + if (index >= 3) { + return null; + } - const activateButton = selectedApp.generated && !selectedApp.activated ? -
- - - - - - -
- : null + return ( + { + //console.log("SEARCH: ", event.target.value) + handleSearchChange(tag); + setCursearch(tag); + //id="app_search_field" + const searchfield = + document.getElementById("app_search_field"); + if ( + searchfield !== null && + searchfield !== undefined + ) { + console.log("SEARCHFIELD: ", searchfield); + searchfield.value = tag; + } + }} + /> + ); + })} +
+
+
+ - const deleteButton = ( - (selectedApp.private_id !== undefined && selectedApp.private_id.length > 0 && selectedApp.generated) - || (selectedApp.downloaded !== undefined && selectedApp.downloaded == true) - || (!selectedApp.generated) - ) - && activateButton === null ? - - - - : null + {data.activated && + data.private_id !== undefined && + data.private_id.length > 0 && + data.generated ? ( + { + downloadApp(data); + }} + > + + + + + ) : null} + + ); + }; - var imageline = selectedApp.large_image === undefined || selectedApp.large_image.length === 0 ? - {selectedApp.title} - : - {selectedApp.title} + const dividerColor = "rgb(225, 228, 232)"; + const uploadViewPaperStyle = { + minWidth: viewWidth, + maxWidth: viewWidth, + color: "white", + borderRadius: 5, + backgroundColor: surfaceColor, + //display: "flex", + marginBottom: 10, + overflow: "hidden", + }; - const GetAppExample = () => { - if (selectedAction.returns === undefined) { - return null - } + const UploadView = () => { + //var imageline = selectedApp.large_image === undefined || selectedApp.large_image.length === 0 ? + // + // : + // PICTURE + // FIXME - add label to apps, as this might be slow with A LOT of apps + var newAppname = selectedApp.name; + if (newAppname !== undefined && newAppname.length > 0) { + newAppname = newAppname.replaceAll("_", " "); + newAppname = newAppname.charAt(0).toUpperCase() + newAppname.substring(1); + } else { + newAppname = ""; + } - var showResult = selectedAction.returns.example - if (showResult === undefined || showResult === null || showResult.length === 0) { - return null - } + var description = selectedApp.description; - var jsonvalid = true - try { - const tmp = String(JSON.parse(showResult)) - if (!tmp.includes("{") && !tmp.includes("[")) { - jsonvalid = false - } - } catch (e) { - jsonvalid = false - } + const editUrl = "/apps/edit/" + selectedApp.id; + const activateUrl = "/apps/new?id=" + selectedApp.id; + var downloadButton = + selectedApp.activated && + selectedApp.private_id !== undefined && + selectedApp.private_id.length > 0 && + selectedApp.generated ? ( + + + + ) : null; - // FIXME: In here -> parse the values into a list or something - if (jsonvalid) { - const paths = GetParsedPaths(JSON.parse(showResult), "") - console.log("PATHS: ", paths) + var editButton = + selectedApp.activated && + selectedApp.private_id !== undefined && + selectedApp.private_id.length > 0 && + selectedApp.generated ? ( + + + + + + ) : null; - return ( -
- {paths.map((data, index) => { - const circleSize = 10 - return ( - console.log(data.autocomplete)}> - {data.name} - - ) - })} -
- ) - } + const activateButton = + selectedApp.generated && !selectedApp.activated ? ( +
+ + + + + + +
+ ) : null; - return ( -
- Example return
- {selectedAction.returns.example} -
- ) - } + const deleteButton = + ((selectedApp.private_id !== undefined && + selectedApp.private_id.length > 0 && + selectedApp.generated) || + (selectedApp.downloaded !== undefined && + selectedApp.downloaded == true) || + !selectedApp.generated) && + activateButton === null ? ( + + + + ) : null; - const userRoles = [ - "you", - isCloud ? "public" : "everyone", - ] + var imageline = + selectedApp.large_image === undefined || + selectedApp.large_image.length === 0 ? ( + {selectedApp.title} + ) : ( + {selectedApp.title} + ); - //fetch(globalUrl+"/api/v1/get_openapi/"+urlParams.get("id"), - var baseInfo = newAppname.length > 0 ? -
-
-
- {imageline} -
-
- - {newAppname} - - - Version {selectedApp.app_version} - - - {description} - -
-
- {selectedApp.versions !== null && selectedApp.versions !== undefined && selectedApp.versions.length > 1 ? - - : null } - {isCloud ? - - - - - - : null} + var jsonvalid = true; + try { + const tmp = String(JSON.parse(showResult)); + if (!tmp.includes("{") && !tmp.includes("[")) { + jsonvalid = false; + } + } catch (e) { + jsonvalid = false; + } - {activateButton} - {(props.userdata !== undefined && (props.userdata.role === "admin" || props.userdata.id === selectedApp.owner) || !selectedApp.generated) ? -
- {editButton} - {downloadButton} - {deleteButton} -
- : null} - {selectedApp.tags !== undefined && selectedApp.tags !== null ? -
- {selectedApp.tags.map((tag, index) => { - if (index >= 3) { - return null - } + // FIXME: In here -> parse the values into a list or something + if (jsonvalid) { + const paths = GetParsedPaths(JSON.parse(showResult), ""); + console.log("PATHS: ", paths); - return ( - - ) - })} -
- : null} - {props.userdata !== undefined && props.userdata.id === selectedApp.owner ? -
- {/*

ID: {selectedApp.id}

*/} - Sharing - -
- : null} - {/*

Owner: {selectedApp.owner}

*/} - {selectedApp.privateId !== undefined && selectedApp.privateId.length > 0 ?

PrivateID: {selectedApp.privateId}

: null} - -
- {selectedApp.link.length > 0 ?

URL: {selectedApp.link}

: null} -
- Actions - {selectedApp.actions !== null && selectedApp.actions.length > 0 ? - { + console.log("Changing version to index " + event.target.value); + const newversion = selectedApp.versions.find( + (tmpApp) => tmpApp.version == event.target.value + ); + console.log("New version: ", newversion); + selectedApp.app_version = selectedApp.app_version; + setSelectedApp(selectedApp); - - ) - })} - - : -
- There are no actions defined for this app. -
- } -
+ if (newversion !== undefined && newversion !== null) { + getApp(newversion.id, true); + } + }} + style={{ + position: "absolute", + top: -10, + right: isCloud ? 50 : 0, + backgroundColor: theme.palette.surfaceColor, + backgroundColor: inputColor, + color: "white", + height: 35, + marginleft: 10, + }} + SelectDisplayProps={{ + style: { + marginLeft: 10, + }, + }} + > + {selectedApp.versions.map((data, index) => { + return ( + + {data.version} + + ); + })} + + ) : null} + {isCloud ? ( + + + + + + ) : null} - {selectedAction.parameters !== undefined && selectedAction.parameters !== null ? -
- Parameters - {selectedAction.parameters.map(data => { - var itemColor = "#f85a3e" - if (!data.required) { - itemColor = "#ffeb3b" - } + {activateButton} + {(props.userdata !== undefined && + (props.userdata.role === "admin" || + props.userdata.id === selectedApp.owner)) || + !selectedApp.generated ? ( +
+ {editButton} + {downloadButton} + {deleteButton} +
+ ) : null} + {selectedApp.tags !== undefined && selectedApp.tags !== null ? ( +
+ {selectedApp.tags.map((tag, index) => { + if (index >= 3) { + return null; + } - const circleSize = 10 - return ( - - {data.configuration === true ? - - - - : -
- } - {data.name} - - ) - })} -
- : null} - {selectedAction.description !== undefined && selectedAction.description !== null && selectedAction.description.length > 0 ? -
- Action Description
- {selectedAction.description} -
- : null} - -
-
- : - null + return ( + + ); + })} +
+ ) : null} + {props.userdata !== undefined && + props.userdata.id === selectedApp.owner ? ( +
+ {/*

ID: {selectedApp.id}

*/} + Sharing + +
+ ) : null} + {/*

Owner: {selectedApp.owner}

*/} + {selectedApp.privateId !== undefined && + selectedApp.privateId.length > 0 ? ( +

+ PrivateID: {selectedApp.privateId} +

+ ) : null} + +
+ {selectedApp.link.length > 0 ? ( +

+ URL: {selectedApp.link} +

+ ) : null} +
+ Actions + {selectedApp.actions !== null && + selectedApp.actions.length > 0 ? ( + + ) : ( +
+ There are no actions defined for this app. +
+ )} +
- const searchfield = search.toLowerCase() - var newapps = apps.filter(data => data.name.toLowerCase().includes(searchfield) || data.description.toLowerCase().includes(searchfield) || (data.tags !== null && data.tags.includes(search))) - var tmpapps = searchableApps.filter(data => data.name.toLowerCase().includes(searchfield) || data.description.toLowerCase().includes(searchfield) || (data.tags !== null && data.tags.includes(search))) - newapps.push(...tmpapps) + {selectedAction.parameters !== undefined && + selectedAction.parameters !== null ? ( +
+ Parameters + {selectedAction.parameters.map((data) => { + var itemColor = "#f85a3e"; + if (!data.required) { + itemColor = "#ffeb3b"; + } - //console.log(newapps) - setFilteredApps(newapps) - //if ((newapps.length === 0 || searchBackend) && !appSearchLoading) { + const circleSize = 10; + return ( + + {data.configuration === true ? ( + + + + ) : ( +
+ )} + {data.name} + + ); + })} +
+ ) : null} + {selectedAction.description !== undefined && + selectedAction.description !== null && + selectedAction.description.length > 0 ? ( +
+ Action Description +
+ {selectedAction.description} +
+ ) : null} + +
+
+ ) : null; - // //setAppSearchLoading(true) - // //runAppSearch(searchfield) - //} else { - //} - } + return ( +
+ +
+

App Creator

+ + How it works + +  -{" "} + + Security API's + +  -{" "} + + OpenAPI directory + +  -{" "} + + OpenAPI Validator + +
+ + Apps interact with eachother in workflows. They are created with + the app creator, using OpenAPI specification or manually in + python. The links above are references to OpenAPI tools and other + app repositories. There's thousands of them. + +
+ +
+ +  OR  + + + +
+
+ + +
{baseInfo}
+
+
+ ); + }; + + const handleSearchChange = (search) => { + if (apps === undefined || apps === null || apps.length === 0) { + return; + } + + const searchfield = search.toLowerCase(); + var newapps = apps.filter( + (data) => + data.name.toLowerCase().includes(searchfield) || + data.description.toLowerCase().includes(searchfield) || + (data.tags !== null && data.tags.includes(search)) + ); + var tmpapps = searchableApps.filter( + (data) => + data.name.toLowerCase().includes(searchfield) || + data.description.toLowerCase().includes(searchfield) || + (data.tags !== null && data.tags.includes(search)) + ); + newapps.push(...tmpapps); + + //console.log(newapps) + setFilteredApps(newapps); + //if ((newapps.length === 0 || searchBackend) && !appSearchLoading) { + + // //setAppSearchLoading(true) + // //runAppSearch(searchfield) + //} else { + //} + }; + + 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(); - try { - reader.addEventListener('load', (e) => { - const content = e.target.result; - setOpenApiData(content); - setIsDropzone(isDropzone); - setOpenApiModal(true) - }) - } catch (e) { - console.log("Error in dropzone: ", e) - } + try { + reader.addEventListener("load", (e) => { + const content = e.target.result; + setOpenApiData(content); + setIsDropzone(isDropzone); + setOpenApiModal(true); + }); + } catch (e) { + console.log("Error in dropzone: ", e); + } - try { - reader.readAsText(files[0]); - } catch(error) { - alert.error("Failed to read file") - } + try { + reader.readAsText(files[0]); + } catch (error) { + alert.error("Failed to read file"); + } }; useEffect(() => { if (openApiData.length > 0) { - setOpenApiError(''); + setOpenApiError(""); validateOpenApi(openApiData); - } - }, [openApiData]); - - useEffect(() => { - if (appValidation && isDropzone) { - redirectOpenApi(); - setIsDropzone(false); - } + } + }, [openApiData]); + + useEffect(() => { + if (appValidation && isDropzone) { + redirectOpenApi(); + setIsDropzone(false); + } }, [appValidation, isDropzone]); - const appView = isLoggedIn ? - -
-
- - - - - App upload - - - {selectedApp.activated && selectedApp.private_id !== undefined && selectedApp.private_id.length > 0 && selectedApp.generated ? - - - {selectedApp.name} - - - : null} - -
- -
-
-
-
- - Activated apps ({apps.length+searchableApps.length}) - -
- {isCloud ? null : - - {isLoading ? null : - - - - } - - - - - } -
-
- { - handleSearchChange(event.target.value) - setCursearch(event.target.value) - }} - /> -
-
- {apps.length > 0 ? - filteredApps.length > 0 ? -
- {filteredApps.map(app => { - return ( - appPaper(app) - ) - })} - {cursearch.length > 0 ? null : - searchableApps.map(app => { - return ( - appPaper(app) - ) - }) - } -
- : - - - - - Click here - to search ALL apps, not just your activated ones. - - -
+ const appView = isLoggedIn ? ( + +
+
+ + + + + App upload + + + {selectedApp.activated && + selectedApp.private_id !== undefined && + selectedApp.private_id.length > 0 && + selectedApp.generated ? ( + + {selectedApp.name} + + ) : null} + +
+ +
+
+
+
+ + Activated apps ({apps.length + searchableApps.length}) + +
+ {isCloud ? null : ( + + {isLoading ? null : ( + + + + )} + + + + + )} +
+
+ { + handleSearchChange(event.target.value); + setCursearch(event.target.value); + }} + /> +
+
+ {apps.length > 0 ? ( + filteredApps.length > 0 ? ( +
+ {filteredApps.map((app) => { + return appPaper(app); + })} + {cursearch.length > 0 + ? null + : searchableApps.map((app) => { + return appPaper(app); + })} +
+ ) : ( + + + + + Click here + {" "} + to search ALL apps, not just your activated ones. + + +
- {appSearchLoading ? - - : null - } - - : - isLoading ? - - : - - - No apps have been created, uploaded or downloaded yet. Click "Load existing apps" above to get the baseline. This may take a while as its building docker images. - - - If you're still not able to see any apps, please follow our troubleshooting guide for loading apps! - - - } -
-
-
- - : - null + {appSearchLoading ? ( + + ) : null} + + ) + ) : isLoading ? ( + + ) : ( + + + No apps have been created, uploaded or downloaded yet. Click + "Load existing apps" above to get the baseline. This may take + a while as its building docker images. + + + If you're still not able to see any apps, please follow our{" "} + + troubleshooting guide for loading apps! + + + + )} +
+
+
+ + ) : null; - // Load data e.g. from github - const getSpecificApps = (url, forceUpdate) => { - setValidation(true) + // Load data e.g. from github + const getSpecificApps = (url, forceUpdate) => { + setValidation(true); - setIsLoading(true) - //start() + setIsLoading(true); + //start() - const parsedData = { - "url": url, - "branch": downloadBranch || 'master' - } + const parsedData = { + url: url, + branch: downloadBranch || "master", + }; - if (field1.length > 0) { - parsedData["field_1"] = field1 - } + if (field1.length > 0) { + parsedData["field_1"] = field1; + } - if (field2.length > 0) { - parsedData["field_2"] = field2 - } + if (field2.length > 0) { + parsedData["field_2"] = field2; + } - parsedData["force_update"] = forceUpdate + parsedData["force_update"] = forceUpdate; - alert.success("Getting specific apps from your URL.") - var cors = "cors" - fetch(globalUrl+"/api/v1/apps/get_existing", { - method: "POST", - mode: "cors", - headers: { - 'Accept': 'application/json', - }, - body: JSON.stringify(parsedData), - credentials: "include", - }) - .then((response) => { - if (response.status === 200) { - alert.success("Loaded existing apps!") - } - - //stop() - setIsLoading(false) - setValidation(false) - return response.json() - }) - .then((responseJson) => { - console.log("DATA: ", responseJson) - if (responseJson.reason !== undefined) { - alert.error("Failed loading: "+responseJson.reason) - } - }) - .catch(error => { - console.log("ERROR: ", error.toString()) - alert.error(error.toString()) - - //stop() - setIsLoading(false) - setValidation(false) - }) - } - - // Locally hotloads app from folder - const hotloadApps = () => { - alert.info("Hotloading apps from location in .env") - setIsLoading(true) - fetch(globalUrl+"/api/v1/apps/run_hotload", { - mode: "cors", - headers: { - 'Accept': 'application/json', - }, - credentials: "include", - }) - .then((response) => { - setIsLoading(false) - if (response.status === 200) { - //alert.success("Hotloaded apps!") - getApps() - } - - return response.json() - }) - .then((responseJson) => { - if (responseJson.success === true) { - alert.info("Successfully finished hotload") - } else { - alert.error("Failed hotload: ", responseJson.reason) - //(responseJson.reason !== undefined && responseJson.reason.length > 0) { - } + alert.success("Getting specific apps from your URL."); + var cors = "cors"; + fetch(globalUrl + "/api/v1/apps/get_existing", { + method: "POST", + mode: "cors", + headers: { + Accept: "application/json", + }, + body: JSON.stringify(parsedData), + credentials: "include", }) - .catch(error => { - alert.error(error.toString()) - }); - } + .then((response) => { + if (response.status === 200) { + alert.success("Loaded existing apps!"); + } - // Gets the URL itself (hopefully this works in most cases? - // Will then forward the data to an internal endpoint to validate the api - const validateUrl = () => { - setValidation(true) + //stop() + setIsLoading(false); + setValidation(false); + return response.json(); + }) + .then((responseJson) => { + console.log("DATA: ", responseJson); + if (responseJson.reason !== undefined) { + alert.error("Failed loading: " + responseJson.reason); + } + }) + .catch((error) => { + console.log("ERROR: ", error.toString()); + alert.error(error.toString()); - var cors = "cors" - if (openApi.includes("= localhost")) { - cors = "no-cors" - } + //stop() + setIsLoading(false); + setValidation(false); + }); + }; - fetch(openApi, { - method: "GET", - mode: "cors", - }) - .then((response) => { - response.text().then(function (text) { - validateOpenApi(text) - }) - }) - .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(responseJson) - - if (setApp) { - if (selectedApp.versions !== undefined && selectedApp.versions !== null) { - responseJson.versions = selectedApp.versions - } - - if (selectedApp.loop_versions !== undefined && selectedApp.loop_versions !== null) { - responseJson.loop_versions = selectedApp.loop_versions - } - - //alert.info("Should set app to selected") - if (responseJson.actions !== undefined && responseJson.actions !== null && responseJson.actions.length > 0) { - setSelectedAction(responseJson.actions[0]) - } else { - setSelectedAction({}) - } - setSelectedApp(responseJson) - } - }) - .catch(error => { - alert.error(error.toString()) - }); - } - - const deleteApp = (appId) => { - alert.info("Attempting to delete app") - fetch(globalUrl+"/api/v1/apps/"+appId, { - method: 'DELETE', - headers: { - 'Accept': 'application/json', - }, - credentials: "include", - }) - .then((response) => { - if (response.status === 200) { - alert.success("Successfully deleted app") - setTimeout(() => { - getApps() - }, 1000); - } else { - alert.error("Failed deleting app") - } - }) - .catch(error => { - alert.error(error.toString()) - }); - } - - const updateAppField = (app_id, fieldname, fieldvalue) => { - const data = {} - data[fieldname] = fieldvalue - - console.log("DATA: ", data) - - fetch(globalUrl+"/api/v1/apps/"+app_id, { - method: 'PATCH', - headers: { - 'Accept': 'application/json', - }, - body: JSON.stringify(data), - credentials: "include", - }) - .then((response) => { - //setAppSearchLoading(false) - return response.json() - }) - .then((responseJson) => { - //console.log(responseJson) - //alert.info(responseJson) - if (responseJson.success) { - alert.success("Successfully updated app configuration") - } else { - alert.error("Error updating app configuration") - } + // Locally hotloads app from folder + const hotloadApps = () => { + alert.info("Hotloading apps from location in .env"); + setIsLoading(true); + fetch(globalUrl + "/api/v1/apps/run_hotload", { + mode: "cors", + headers: { + Accept: "application/json", + }, + credentials: "include", }) - .catch(error => { - alert.error(error.toString()) - }); - } + .then((response) => { + setIsLoading(false); + if (response.status === 200) { + //alert.success("Hotloaded apps!") + getApps(); + } - const runAppSearch = (searchterm) => { - const data = {"search": searchterm} + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === true) { + alert.info("Successfully finished hotload"); + } else { + alert.error("Failed hotload: ", responseJson.reason); + //(responseJson.reason !== undefined && responseJson.reason.length > 0) { + } + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; - fetch(globalUrl+"/api/v1/apps/search", { - method: 'POST', - headers: { - 'Accept': 'application/json', - }, - body: JSON.stringify(data), - credentials: "include", - }) - .then((response) => { - setAppSearchLoading(false) - return response.json() - }) - .then((responseJson) => { - if (responseJson.success) { - if (responseJson.reason !== undefined && responseJson.reason.length > 0) { - setSearchableApps(responseJson.reason) - //setFilteredApps(responseJson.reason) - } - } + // Gets the URL itself (hopefully this works in most cases? + // Will then forward the data to an internal endpoint to validate the api + const validateUrl = () => { + setValidation(true); + + var cors = "cors"; + if (openApi.includes("= localhost")) { + cors = "no-cors"; + } + + fetch(openApi, { + method: "GET", + mode: "cors", }) - .catch(error => { - alert.error(error.toString()) - }); - } + .then((response) => { + response.text().then(function (text) { + validateOpenApi(text); + }); + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; - const validateRemote = () => { - setValidation(true) - - fetch(globalUrl+"/api/v1/get_openapi_uri", { - method: 'POST', - headers: { - 'Accept': 'application/json', - }, - body: JSON.stringify(openApi), - credentials: "include", - }) - .then((response) => { - setValidation(false) - if (response.status !== 200) { - return response.json() - } - - return response.text() - }) - .then((responseJson) => { - if (typeof(responseJson) !== "string" && !responseJson.success) { - console.log(responseJson.reason) - if (responseJson.reason !== undefined) { - setOpenApiError(responseJson.reason) - } else { - setOpenApiError("Undefined issue with OpenAPI validation") - } - return - } - - console.log("Validating response!") - validateOpenApi(responseJson) + const getApp = (appId, setApp) => { + fetch(globalUrl + "/api/v1/apps/" + appId + "/config?openapi=false", { + headers: { + Accept: "application/json", + }, + credentials: "include", }) - .catch(error => { - alert.error(error.toString()) - setOpenApiError(error.toString()) - }); - } + .then((response) => { + if (response.status === 200) { + //alert.success("Successfully GOT app "+appId) + } else { + alert.error("Failed getting app"); + } - const escapeApiData = (apidata) => { - //console.log(apidata) - try { - return JSON.stringify(JSON.parse(apidata)) - } catch(error) { - console.log("JSON DECODE ERROR - TRY YAML") - } + return response.json(); + }) + .then((responseJson) => { + console.log(responseJson); + if (setApp) { + if ( + selectedApp.versions !== undefined && + selectedApp.versions !== null + ) { + responseJson.versions = selectedApp.versions; + } - try { - const parsed = YAML.parse(YAML.stringify(apidata)) - //const parsed = YAML.parse(apidata)) - return YAML.stringify(parsed) - } catch(error) { - console.log("YAML DECODE ERROR - TRY SOMETHING ELSE?: "+error) - setOpenApiError("Local error: "+ error.toString()) - } + if ( + selectedApp.loop_versions !== undefined && + selectedApp.loop_versions !== null + ) { + responseJson.loop_versions = selectedApp.loop_versions; + } - return "" - } + //alert.info("Should set app to selected") + if ( + responseJson.actions !== undefined && + responseJson.actions !== null && + responseJson.actions.length > 0 + ) { + setSelectedAction(responseJson.actions[0]); + } else { + setSelectedAction({}); + } + setSelectedApp(responseJson); + } + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; - // Sends the data to backend, which should return a version 3 of the same API - // If 200 - continue, otherwise, there's some issue somewhere - const validateOpenApi = (openApidata) => { - var newApidata = escapeApiData(openApidata) - if (newApidata === "") { - // Used to return here - newApidata = openApidata - return - } + const deleteApp = (appId) => { + alert.info("Attempting to delete app"); + fetch(globalUrl + "/api/v1/apps/" + appId, { + method: "DELETE", + headers: { + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status === 200) { + alert.success("Successfully deleted app"); + setTimeout(() => { + getApps(); + }, 1000); + } else { + alert.error("Failed deleting app"); + } + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; - //console.log(newApidata) + const updateAppField = (app_id, fieldname, fieldvalue) => { + const data = {}; + data[fieldname] = fieldvalue; - setValidation(true) - fetch(globalUrl+"/api/v1/validate_openapi", { - method: 'POST', - headers: { - 'Accept': 'application/json', - }, - body: openApidata, - credentials: "include", - }) - .then((response) => { - setValidation(false) - return response.json() - }) - .then((responseJson) => { - if (responseJson.success) { - setAppValidation(responseJson.id); - } else { - if (responseJson.reason !== undefined) { - setOpenApiError(responseJson.reason) - } - alert.error("An error occurred in the response") - } - }) - .catch(error => { - setValidation(false) - alert.error(error.toString()) - setOpenApiError(error.toString()) - }); - } + console.log("DATA: ", data); - const redirectOpenApi = () => { - window.location.href = "/apps/new?id="+appValidation - } + fetch(globalUrl + "/api/v1/apps/" + app_id, { + method: "PATCH", + headers: { + Accept: "application/json", + }, + body: JSON.stringify(data), + credentials: "include", + }) + .then((response) => { + //setAppSearchLoading(false) + return response.json(); + }) + .then((responseJson) => { + //console.log(responseJson) + //alert.info(responseJson) + if (responseJson.success) { + alert.success("Successfully updated app configuration"); + } else { + alert.error("Error updating app configuration"); + } + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; - const handleGithubValidation = (forceUpdate) => { - getSpecificApps(openApi, forceUpdate) - setLoadAppsModalOpen(false) - } + const runAppSearch = (searchterm) => { + const data = { search: searchterm }; - const deleteModal = deleteModalOpen ? - { - setDeleteModalOpen(false) - }} - PaperProps={{ - style: { - backgroundColor: surfaceColor, - color: "white", - minWidth: 500, - }, - }} - > - -
- Are you sure?
Some workflows may stop working. -
- - - - - - -
- : null + fetch(globalUrl + "/api/v1/apps/search", { + method: "POST", + headers: { + Accept: "application/json", + }, + body: JSON.stringify(data), + credentials: "include", + }) + .then((response) => { + setAppSearchLoading(false); + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success) { + if ( + responseJson.reason !== undefined && + responseJson.reason.length > 0 + ) { + setSearchableApps(responseJson.reason); + //setFilteredApps(responseJson.reason) + } + } + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; - const circularLoader = validation ? : null - const appsModalLoad = loadAppsModalOpen ? - { - setOpenApi("") - setLoadAppsModalOpen(false) - setField1("") - setField2("") - }} - PaperProps={{ - style: { - backgroundColor: surfaceColor, - color: "white", - minWidth: "800px", - minHeight: "320px", - }, - }} - > - -
- Load from github repo -
-
- - Repository (supported: github, gitlab, bitbucket) - setOpenApi(e.target.value)} - placeholder="https://github.com/frikky/shuffle-apps" - fullWidth - /> - Branch (default value is "master"): -
- setDownloadBranch(e.target.value)} - placeholder="master" - fullWidth - /> -
+ const validateRemote = () => { + setValidation(true); - Authentication (optional - private repos etc): -
- setField1(e.target.value)} - type="username" - placeholder="Username / APIkey (optional)" - fullWidth - /> - setField2(e.target.value)} - type="password" - placeholder="Password (optional)" - fullWidth - /> -
-
- - {circularLoader} - - {isCloud ? null : - - } - - -
- : null + fetch(globalUrl + "/api/v1/get_openapi_uri", { + method: "POST", + headers: { + Accept: "application/json", + }, + body: JSON.stringify(openApi), + credentials: "include", + }) + .then((response) => { + setValidation(false); + if (response.status !== 200) { + return response.json(); + } - const errorText = openApiError.length > 0 ?
Error: {openApiError}
: null - const modalView = openApiModal ? - { - setOpenApiModal(false) - }} - PaperProps={{ - style: { - backgroundColor: surfaceColor, - color: "white", - minWidth: "800px", - minHeight: "320px", - }, - }} - > - -
Create a new integration
- - Paste in the URI for the OpenAPI - 0} color="primary" - onClick={() => { - setOpenApiError("") - validateRemote() - }}>Validate - }} - onChange={e => { - setOpenApi(e.target.value) - }} - helperText={Must point to a version 2 or 3 OpenAPI specification.} - placeholder="OpenAPI URI" - fullWidth - /> - {/* + return response.text(); + }) + .then((responseJson) => { + if (typeof responseJson !== "string" && !responseJson.success) { + console.log(responseJson.reason); + if (responseJson.reason !== undefined) { + setOpenApiError(responseJson.reason); + } else { + setOpenApiError("Undefined issue with OpenAPI validation"); + } + return; + } + + console.log("Validating response!"); + validateOpenApi(responseJson); + }) + .catch((error) => { + alert.error(error.toString()); + setOpenApiError(error.toString()); + }); + }; + + const escapeApiData = (apidata) => { + //console.log(apidata) + try { + return JSON.stringify(JSON.parse(apidata)); + } catch (error) { + console.log("JSON DECODE ERROR - TRY YAML"); + } + + try { + const parsed = YAML.parse(YAML.stringify(apidata)); + //const parsed = YAML.parse(apidata)) + return YAML.stringify(parsed); + } catch (error) { + console.log("YAML DECODE ERROR - TRY SOMETHING ELSE?: " + error); + setOpenApiError("Local error: " + error.toString()); + } + + return ""; + }; + + // Sends the data to backend, which should return a version 3 of the same API + // If 200 - continue, otherwise, there's some issue somewhere + const validateOpenApi = (openApidata) => { + var newApidata = escapeApiData(openApidata); + if (newApidata === "") { + // Used to return here + newApidata = openApidata; + return; + } + + //console.log(newApidata) + + setValidation(true); + fetch(globalUrl + "/api/v1/validate_openapi", { + method: "POST", + headers: { + Accept: "application/json", + }, + body: openApidata, + credentials: "include", + }) + .then((response) => { + setValidation(false); + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success) { + setAppValidation(responseJson.id); + } else { + if (responseJson.reason !== undefined) { + setOpenApiError(responseJson.reason); + } + alert.error("An error occurred in the response"); + } + }) + .catch((error) => { + setValidation(false); + alert.error(error.toString()); + setOpenApiError(error.toString()); + }); + }; + + const redirectOpenApi = () => { + window.location.href = "/apps/new?id=" + appValidation; + }; + + const handleGithubValidation = (forceUpdate) => { + getSpecificApps(openApi, forceUpdate); + setLoadAppsModalOpen(false); + }; + + const deleteModal = deleteModalOpen ? ( + { + setDeleteModalOpen(false); + }} + PaperProps={{ + style: { + backgroundColor: surfaceColor, + color: "white", + minWidth: 500, + }, + }} + > + +
+ Are you sure?
+ Some workflows may stop working. +
+ + + + + +
+ ) : null; + + const circularLoader = validation ? ( + + ) : null; + const appsModalLoad = loadAppsModalOpen ? ( + { + setOpenApi(""); + setLoadAppsModalOpen(false); + setField1(""); + setField2(""); + }} + PaperProps={{ + style: { + backgroundColor: surfaceColor, + color: "white", + minWidth: "800px", + minHeight: "320px", + }, + }} + > + +
+ Load from github repo +
+
+ + Repository (supported: github, gitlab, bitbucket) + setOpenApi(e.target.value)} + placeholder="https://github.com/frikky/shuffle-apps" + fullWidth + /> + + Branch (default value is "master"): + +
+ setDownloadBranch(e.target.value)} + placeholder="master" + fullWidth + /> +
+ + Authentication (optional - private repos etc): + +
+ setField1(e.target.value)} + type="username" + placeholder="Username / APIkey (optional)" + fullWidth + /> + setField2(e.target.value)} + type="password" + placeholder="Password (optional)" + fullWidth + /> +
+
+ + {circularLoader} + + {isCloud ? null : ( + + )} + + +
+ ) : null; + + const errorText = + openApiError.length > 0 ? ( +
Error: {openApiError}
+ ) : null; + const modalView = openApiModal ? ( + { + setOpenApiModal(false); + }} + PaperProps={{ + style: { + backgroundColor: surfaceColor, + color: "white", + minWidth: "800px", + minHeight: "320px", + }, + }} + > + + +
+ Create a new integration +
+
+ + Paste in the URI for the OpenAPI + 0} + color="primary" + onClick={() => { + setOpenApiError(""); + validateRemote(); + }} + > + Validate + + ), + }} + onChange={(e) => { + setOpenApi(e.target.value); + }} + helperText={ + + Must point to a version 2 or 3 OpenAPI specification. + + } + placeholder="OpenAPI URI" + fullWidth + /> + {/*
Example:
https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v2.0/json/uber.json */} -

Or upload a YAML or JSON specification

+

Or upload a YAML or JSON specification

{ > Upload - {errorText} - - - {circularLoader} - - - - -
- : null + {errorText} +
+ + {circularLoader} + + + +
+
+ ) : null; - const loadedCheck = isLoaded && !firstrequest ? -
- {appView} - {modalView} - {appsModalLoad} - {deleteModal} -
- : -
-
+ const loadedCheck = + isLoaded && !firstrequest ? ( +
+ {appView} + {modalView} + {appsModalLoad} + {deleteModal} +
+ ) : ( +
+ ); - // Maybe use gridview or something, idk - return loadedCheck -} + // Maybe use gridview or something, idk + return loadedCheck; +}; -export default Apps +export default Apps; diff --git a/frontend/src/views/Contact.jsx b/frontend/src/views/Contact.jsx index 5a34382c..54932023 100644 --- a/frontend/src/views/Contact.jsx +++ b/frontend/src/views/Contact.jsx @@ -1,345 +1,365 @@ -import React, { useState } from 'react'; +import React, { useState } from "react"; import { BrowserView, MobileView } from "react-device-detect"; -import Paper from '@material-ui/core/Paper'; -import Button from '@material-ui/core/Button'; +import Paper from "@material-ui/core/Paper"; +import Button from "@material-ui/core/Button"; -import TextField from '@material-ui/core/TextField'; +import TextField from "@material-ui/core/TextField"; -import { useTheme } from '@material-ui/core/styles'; +import { useTheme } from "@material-ui/core/styles"; const bodyDivStyle = { - margin: "auto", - textAlign: "center", - width: "900px", -} - + margin: "auto", + textAlign: "center", + width: "900px", +}; // Should be different if logged in :| const Contact = (props) => { - const { globalUrl, isLoaded } = props; - - const theme = useTheme(); + const { globalUrl, isLoaded } = props; - const boxStyle = { - flex: "1", - marginLeft: "10px", - marginRight: "10px", - paddingLeft: "30px", - paddingRight: "30px", - paddingBottom: "30px", - paddingTop: "30px", - backgroundColor: theme.palette.surfaceColor, - display: "flex", - flexDirection: "column" - } + const theme = useTheme(); - const bodyTextStyle = { - color: "#ffffff", - } + const boxStyle = { + flex: "1", + marginLeft: "10px", + marginRight: "10px", + paddingLeft: "30px", + paddingRight: "30px", + paddingBottom: "30px", + paddingTop: "30px", + backgroundColor: theme.palette.surfaceColor, + display: "flex", + flexDirection: "column", + }; - const [firstname, setFirstname] = useState(""); - const [lastname, setLastname] = useState(""); - const [title, setTitle] = useState(""); - const [companyname, setCompanyname] = useState(""); - const [email, setEmail] = useState(""); - const [phone, setPhone] = useState(""); - const [message, setMessage] = useState(""); + const bodyTextStyle = { + color: "#ffffff", + }; - const [formMessage, setFormMessage] = useState(""); + const [firstname, setFirstname] = useState(""); + const [lastname, setLastname] = useState(""); + const [title, setTitle] = useState(""); + const [companyname, setCompanyname] = useState(""); + const [email, setEmail] = useState(""); + const [phone, setPhone] = useState(""); + const [message, setMessage] = useState(""); - const submitContact = () => { - const data = { - "firstname": firstname, - "lastname": lastname, - "title": title, - "companyname": companyname, - "email": email, - "phone": phone, - "message": message, - } - console.log(data) + const [formMessage, setFormMessage] = useState(""); - fetch(globalUrl + "/api/v1/contact", { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(data), - }) - .then(response => response.json()) - .then(response => { - if (response.success === true) { - setFormMessage(response.message) - } else { - setFormMessage("Something went wrong. Please contact frikky@shuffler.io.") - } - console.log(response) - }) - .catch(error => { - console.log(error) - }); - } + const submitContact = () => { + const data = { + firstname: firstname, + lastname: lastname, + title: title, + companyname: companyname, + email: email, + phone: phone, + message: message, + }; + console.log(data); - // Random names for type & autoComplete. Didn't research :^) - const landingpageDataBrowser = -
-
-

Contact us

-

Lets talk!

-
-
- -

Contact Details

-
- setFirstname(e.target.value)} - /> - setLastname(e.target.value)} - /> -
-
- setTitle(e.target.value)} - /> - setCompanyname(e.target.value)} - /> -
-
- setEmail(e.target.value)} - /> - setPhone(e.target.value)} - /> -
-
-

Message

-
-
- setMessage(e.target.value)} - /> -
- -

{formMessage}

-
-
-
+ fetch(globalUrl + "/api/v1/contact", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }) + .then((response) => response.json()) + .then((response) => { + if (response.success === true) { + setFormMessage(response.message); + } else { + setFormMessage( + "Something went wrong. Please contact frikky@shuffler.io." + ); + } + console.log(response); + }) + .catch((error) => { + console.log(error); + }); + }; - const landingpageDataMobile = -
-
-

Contact us

-

Lets talk!

-
-
- -

Contact Details

-
- setFirstname(e.target.value)} - /> -
-
- setEmail(e.target.value)} - /> -
-
-

Message

-
-
- setMessage(e.target.value)} - /> -
- -

{formMessage}

-
-
-
+ // Random names for type & autoComplete. Didn't research :^) + const landingpageDataBrowser = ( +
+
+

Contact us

+

Lets talk!

+
+
+ +

Contact Details

+
+ setFirstname(e.target.value)} + /> + setLastname(e.target.value)} + /> +
+
+ setTitle(e.target.value)} + /> + setCompanyname(e.target.value)} + /> +
+
+ setEmail(e.target.value)} + /> + setPhone(e.target.value)} + /> +
+
+

Message

+
+
+ setMessage(e.target.value)} + /> +
+ +

{formMessage}

+
+
+
+ ); + const landingpageDataMobile = ( +
+
+

Contact us

+

Lets talk!

+
+
+ +

Contact Details

+
+ setFirstname(e.target.value)} + /> +
+
+ setEmail(e.target.value)} + /> +
+
+

Message

+
+
+ setMessage(e.target.value)} + /> +
+ +

{formMessage}

+
+
+
+ ); - const loadedCheck = isLoaded ? -
- -
{landingpageDataBrowser}
-
- - {landingpageDataMobile} - -
- : -
-
+ const loadedCheck = isLoaded ? ( +
+ +
{landingpageDataBrowser}
+
+ {landingpageDataMobile} +
+ ) : ( +
+ ); - return ( -
- {loadedCheck} -
- ) -} + return
{loadedCheck}
; +}; export default Contact; diff --git a/frontend/src/views/Dashboard.jsx b/frontend/src/views/Dashboard.jsx index 3ffb560e..d0dc7754 100644 --- a/frontend/src/views/Dashboard.jsx +++ b/frontend/src/views/Dashboard.jsx @@ -1,5 +1,5 @@ -import React, {useState} from 'react'; -import { useInterval } from 'react-powerhooks'; +import React, { useState } from "react"; +import { useInterval } from "react-powerhooks"; // nodejs library that concatenates classes import classNames from "classnames"; // react plugin used to create charts @@ -26,7 +26,7 @@ import { Table, Row, Col, - UncontrolledTooltip + UncontrolledTooltip, } from "reactstrap"; // core components @@ -34,430 +34,433 @@ import { chartExample1, chartExample2, chartExample3, - chartExample4 + chartExample4, } from "../charts.js"; -// This is the start of a dashboard that can be used. +// This is the start of a dashboard that can be used. // What data do we fill in here? Idk const Dashboard = (props) => { const { globalUrl } = props; - const alert = useAlert() - const [bigChartData, setBgChartData] = useState("data1"); - const [dayAmount, setDayAmount] = useState(7); - const [firstRequest, setFirstRequest] = useState(true); - const [stats, setStats] = useState({}) - const [changeme, setChangeme] = useState("") - const [statsRan, setStatsRan] = useState(false) - - document.title = "Shuffle - dashboard" - var dayGraphLabels = [60, 80, 65, 130, 80, 105, 90, 130, 70, 115, 60, 130] - var dayGraphData = [60, 80, 65, 130, 80, 105, 90, 130, 70, 115, 60, 130] + const alert = useAlert(); + const [bigChartData, setBgChartData] = useState("data1"); + const [dayAmount, setDayAmount] = useState(7); + const [firstRequest, setFirstRequest] = useState(true); + const [stats, setStats] = useState({}); + const [changeme, setChangeme] = useState(""); + const [statsRan, setStatsRan] = useState(false); - const fetchdata = (stats_id) => { - fetch(globalUrl+"/api/v1/stats/"+stats_id, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - credentials: "include", + document.title = "Shuffle - dashboard"; + var dayGraphLabels = [60, 80, 65, 130, 80, 105, 90, 130, 70, 115, 60, 130]; + var dayGraphData = [60, 80, 65, 130, 80, 105, 90, 130, 70, 115, 60, 130]; + + const fetchdata = (stats_id) => { + fetch(globalUrl + "/api/v1/stats/" + stats_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 "+stats_id) - } + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for " + stats_id); + } - return response.json() - }) - .then((responseJson) => { - stats[stats_id] = responseJson - setStats(stats) - // Used to force updates - setChangeme(stats_id) - }) - .catch(error => { - alert.error("ERROR: "+error.toString()) - }); - } + return response.json(); + }) + .then((responseJson) => { + stats[stats_id] = responseJson; + setStats(stats); + // Used to force updates + setChangeme(stats_id); + }) + .catch((error) => { + alert.error("ERROR: " + error.toString()); + }); + }; - let chart1_2_options = { - maintainAspectRatio: false, - legend: { - display: false - }, - tooltips: { - backgroundColor: "#f5f5f5", - titleFontColor: "#333", - bodyFontColor: "#666", - bodySpacing: 4, - xPadding: 12, - mode: "nearest", - intersect: 0, - position: "nearest" - }, - responsive: true, - scales: { - yAxes: [ - { - barPercentage: 1.6, - gridLines: { - drawBorder: false, - color: "rgba(29,140,248,0.0)", - zeroLineColor: "transparent" - }, - ticks: { - suggestedMin: 60, - suggestedMax: 125, - padding: 20, - fontColor: "#9a9a9a" - } - } - ], - xAxes: [ - { - barPercentage: 1.6, - gridLines: { - drawBorder: false, - color: "rgba(29,140,248,0.1)", - zeroLineColor: "transparent" - }, - ticks: { - padding: 20, - fontColor: "#9a9a9a" - } - } - ] - } - } + let chart1_2_options = { + maintainAspectRatio: false, + legend: { + display: false, + }, + tooltips: { + backgroundColor: "#f5f5f5", + titleFontColor: "#333", + bodyFontColor: "#666", + bodySpacing: 4, + xPadding: 12, + mode: "nearest", + intersect: 0, + position: "nearest", + }, + responsive: true, + scales: { + yAxes: [ + { + barPercentage: 1.6, + gridLines: { + drawBorder: false, + color: "rgba(29,140,248,0.0)", + zeroLineColor: "transparent", + }, + ticks: { + suggestedMin: 60, + suggestedMax: 125, + padding: 20, + fontColor: "#9a9a9a", + }, + }, + ], + xAxes: [ + { + barPercentage: 1.6, + gridLines: { + drawBorder: false, + color: "rgba(29,140,248,0.1)", + zeroLineColor: "transparent", + }, + ticks: { + padding: 20, + fontColor: "#9a9a9a", + }, + }, + ], + }, + }; - const dayGraph = { - data: canvas => { - let ctx = canvas.getContext("2d"); + const dayGraph = { + data: (canvas) => { + let ctx = canvas.getContext("2d"); - let gradientStroke = ctx.createLinearGradient(0, 230, 0, 50); + let gradientStroke = ctx.createLinearGradient(0, 230, 0, 50); - gradientStroke.addColorStop(1, "rgba(29,140,248,0.2)"); - gradientStroke.addColorStop(0.4, "rgba(29,140,248,0.0)"); - gradientStroke.addColorStop(0, "rgba(29,140,248,0)"); //blue colors + gradientStroke.addColorStop(1, "rgba(29,140,248,0.2)"); + gradientStroke.addColorStop(0.4, "rgba(29,140,248,0.0)"); + gradientStroke.addColorStop(0, "rgba(29,140,248,0)"); //blue colors - return { - labels: dayGraphLabels, - datasets: [ - { - label: "My First dataset", - fill: true, - backgroundColor: gradientStroke, - borderColor: "#1f8ef1", - borderWidth: 2, - borderDash: [], - borderDashOffset: 0.0, - pointBackgroundColor: "#1f8ef1", - pointBorderColor: "rgba(255,255,255,0)", - pointHoverBackgroundColor: "#1f8ef1", - pointBorderWidth: 20, - pointHoverRadius: 4, - pointHoverBorderWidth: 15, - pointRadius: 4, - data: dayGraphData, - } - ] - } - }, - options: chart1_2_options, - } + return { + labels: dayGraphLabels, + datasets: [ + { + label: "My First dataset", + fill: true, + backgroundColor: gradientStroke, + borderColor: "#1f8ef1", + borderWidth: 2, + borderDash: [], + borderDashOffset: 0.0, + pointBackgroundColor: "#1f8ef1", + pointBorderColor: "rgba(255,255,255,0)", + pointHoverBackgroundColor: "#1f8ef1", + pointBorderWidth: 20, + pointHoverRadius: 4, + pointHoverBorderWidth: 15, + pointRadius: 4, + data: dayGraphData, + }, + ], + }; + }, + options: chart1_2_options, + }; - // All these are currently tracked. - const variables = [ - "backend_executions", - "workflow_executions", - "workflow_executions_aborted", - "workflow_executions_success", - "total_apps_created", - "total_apps_loaded", - "openapi_apps_created", - "total_apps_deleted", - "total_webhooks_ran", - "total_workflows", - "total_workflow_actions", - "total_workflow_triggers", - ] + // All these are currently tracked. + const variables = [ + "backend_executions", + "workflow_executions", + "workflow_executions_aborted", + "workflow_executions_success", + "total_apps_created", + "total_apps_loaded", + "openapi_apps_created", + "total_apps_deleted", + "total_webhooks_ran", + "total_workflows", + "total_workflow_actions", + "total_workflow_triggers", + ]; - const runUpdate = () => { - for (var key in variables) { - fetchdata(variables[key]) - } - } + const runUpdate = () => { + for (var key in variables) { + fetchdata(variables[key]); + } + }; - // Refresh every 60 seconds - const autoUpdate = 60000 - const { start, stop } = useInterval({ - duration: autoUpdate, - startImmediate: false, - callback: () => { - runUpdate() - } - }) + // Refresh every 60 seconds + const autoUpdate = 60000; + const { start, stop } = useInterval({ + duration: autoUpdate, + startImmediate: false, + callback: () => { + runUpdate(); + }, + }); - if (firstRequest) { - console.log("HELO") - setFirstRequest(false) - start() - runUpdate() - } else if (!statsRan) { - // FIXME: Run this under runUpdate schedule? - // 1. Fix labels in dayGraphy.data - // 2. Add data to the daygraph - - // Every time there's an update :) + if (firstRequest) { + console.log("HELO"); + setFirstRequest(false); + start(); + runUpdate(); + } else if (!statsRan) { + // FIXME: Run this under runUpdate schedule? + // 1. Fix labels in dayGraphy.data + // 2. Add data to the daygraph - // This should probably be done in the backend.. bleh - if (stats["workflow_executions"] !== undefined && stats["workflow_executions"] !== null && stats["workflow_executions"].data !== undefined) { - setStatsRan(true) - //console.log("NEW DATA?: ", stats) - console.log('SET WORKFLOW: ', stats["workflow_executions"]) - //var curday = startDate.getDate() + // Every time there's an update :) - // Index = what day are we on + // This should probably be done in the backend.. bleh + if ( + stats["workflow_executions"] !== undefined && + stats["workflow_executions"] !== null && + stats["workflow_executions"].data !== undefined + ) { + setStatsRan(true); + //console.log("NEW DATA?: ", stats) + console.log("SET WORKFLOW: ", stats["workflow_executions"]); + //var curday = startDate.getDate() - // 0 = today - var newDayGraphLabels = [] - var newDayGraphData = [] - for (var i = dayAmount; i > 0; i--) { - var enddate = new Date() - enddate.setDate(-i) - enddate.setHours(23,59,59,999) + // Index = what day are we on - var startdate = new Date() - startdate.setDate(-i) - startdate.setHours(0,0,0,0) + // 0 = today + var newDayGraphLabels = []; + var newDayGraphData = []; + for (var i = dayAmount; i > 0; i--) { + var enddate = new Date(); + enddate.setDate(-i); + enddate.setHours(23, 59, 59, 999); - var endtime = enddate.getTime()/1000 - var starttime = startdate.getTime()/1000 + var startdate = new Date(); + startdate.setDate(-i); + startdate.setHours(0, 0, 0, 0); - console.log("START: ", starttime, "END: ", endtime, "Data: ", stats["workflow_executions"]) - for (var key in stats["workflow_executions"].data) { - const item = stats["workflow_executions"]["data"][key] - console.log("ITEM: ", item.timestamp, endtime) - console.log(endtime-starttime) - if (endtime-starttime > endtime-item.timestamp && endtime.timestamp >= 0) { - console.log("HIT? ") - } - console.log(item.timestamp-endtime) - //console.log(item.timestamp-endtime) - break - if (item.timestamp > endtime && item.timestamp < starttime) { - if (newDayGraphData[i-1] === undefined) { - newDayGraphData[i-1] = 1 - } else { - newDayGraphData[i-1] += 1 - } + var endtime = enddate.getTime() / 1000; + var starttime = startdate.getTime() / 1000; - //break - } - } - - newDayGraphLabels.push(i) - } + console.log( + "START: ", + starttime, + "END: ", + endtime, + "Data: ", + stats["workflow_executions"] + ); + for (var key in stats["workflow_executions"].data) { + const item = stats["workflow_executions"]["data"][key]; + console.log("ITEM: ", item.timestamp, endtime); + console.log(endtime - starttime); + if ( + endtime - starttime > endtime - item.timestamp && + endtime.timestamp >= 0 + ) { + console.log("HIT? "); + } + console.log(item.timestamp - endtime); + //console.log(item.timestamp-endtime) + break; + if (item.timestamp > endtime && item.timestamp < starttime) { + if (newDayGraphData[i - 1] === undefined) { + newDayGraphData[i - 1] = 1; + } else { + newDayGraphData[i - 1] += 1; + } - console.log(newDayGraphLabels) - console.log(newDayGraphData) - } - } + //break + } + } - const newdata = Object.getOwnPropertyNames(stats).length > 0 ? -
- Autoupdate every {autoUpdate/1000} seconds - {variables.map(data => { - if (stats[data] === undefined || stats[data] === null) { - return null - } + newDayGraphLabels.push(i); + } - if (stats[data].total === undefined) { - return null - } + console.log(newDayGraphLabels); + console.log(newDayGraphData); + } + } - return ( -
- {data}: {stats[data].total} -
- ) - })} -
- : null + const newdata = + Object.getOwnPropertyNames(stats).length > 0 ? ( +
+ Autoupdate every {autoUpdate / 1000} seconds + {variables.map((data) => { + if (stats[data] === undefined || stats[data] === null) { + return null; + } - const data = -
- {newdata} - - -
- -
- - - - - - -
Total Shipments
- Workflows - - - - - - - - -
-
- -
- -
-
-
- -
- - - - -
Total Shipments
- - {" "} - 763,215 - -
- -
- -
-
-
- - - - -
Daily Sales
- - {" "} - 3,500€ - -
- -
- -
-
-
- - - - -
Completed Tasks
- - 12,100K - -
- -
- -
-
-
- -
-
+ if (stats[data].total === undefined) { + return null; + } - const dataWrapper = -
- {data} -
+ return ( +
+ {data}: {stats[data].total} +
+ ); + })} +
+ ) : null; - return dataWrapper -} + const data = ( +
+ {newdata} + + +
+ +
+ + + + + + +
Total Shipments
+ Workflows + + + + + + + + +
+
+ +
+ +
+
+
+ +
+ + + + +
Total Shipments
+ + 763,215 + +
+ +
+ +
+
+
+ + + + +
Daily Sales
+ + {" "} + 3,500€ + +
+ +
+ +
+
+
+ + + + +
Completed Tasks
+ + 12,100K + +
+ +
+ +
+
+
+ +
+
+ ); -export default Dashboard + const dataWrapper = ( +
{data}
+ ); + + return dataWrapper; +}; + +export default Dashboard; diff --git a/frontend/src/views/Docs.jsx b/frontend/src/views/Docs.jsx index 58ee19a3..6c3df78d 100644 --- a/frontend/src/views/Docs.jsx +++ b/frontend/src/views/Docs.jsx @@ -1,349 +1,453 @@ -import React, {useState,} from 'react'; +import React, { useState } from "react"; -import { useTheme } from '@material-ui/core/styles'; -import ReactMarkdown from 'react-markdown'; -import {BrowserView, MobileView} from "react-device-detect"; -import {Link} from 'react-router-dom'; +import { useTheme } from "@material-ui/core/styles"; +import ReactMarkdown from "react-markdown"; +import { BrowserView, MobileView } from "react-device-detect"; +import { Link } from "react-router-dom"; -import {Tooltip, Divider, Button, Menu, MenuItem, Typography, Paper, List} from '@material-ui/core'; -import {Link as LinkIcon, Edit as EditIcon} from '@material-ui/icons'; +import { + Tooltip, + Divider, + Button, + Menu, + MenuItem, + Typography, + Paper, + List, +} from "@material-ui/core"; +import { Link as LinkIcon, Edit as EditIcon } from "@material-ui/icons"; const Body = { - maxWidth: '1000px', - minWidth: '768px', - margin: 'auto', - display: "flex", - height: "100%", - color: "white", - //textAlign: "center", + maxWidth: "1000px", + minWidth: "768px", + margin: "auto", + display: "flex", + height: "100%", + color: "white", + //textAlign: "center", }; -const dividerColor = "rgb(225, 228, 232)" +const dividerColor = "rgb(225, 228, 232)"; const hrefStyle = { - color: "rgba(255, 255, 255, 0.40)", - textDecoration: "none" -} + color: "rgba(255, 255, 255, 0.40)", + textDecoration: "none", +}; const innerHrefStyle = { - color: "rgba(255, 255, 255, 0.75)", - textDecoration: "none" -} + color: "rgba(255, 255, 255, 0.75)", + textDecoration: "none", +}; const Docs = (props) => { - const { globalUrl, selectedDoc, serverside, isMobile, } = props; + const { globalUrl, selectedDoc, serverside, isMobile } = props; - const theme = useTheme(); - const [mobile, setMobile] = useState(isMobile === true ? true : false); - const [data, setData] = useState(""); - const [firstrequest, setFirstrequest] = useState(true); - const [list, setList] = useState([]); - const [, setListLoaded] = useState(false); - const [anchorEl, setAnchorEl] = React.useState(null); - const [headingSet, setHeadingSet] = React.useState(false); - const [selectedMeta, setSelectedMeta] = React.useState({link: "hello", read_time: 2, }); - const [tocLines, setTocLines] = React.useState([]); - const [baseUrl, setBaseUrl] = React.useState(serverside === true ? "" : window.location.href) + const theme = useTheme(); + const [mobile, setMobile] = useState(isMobile === true ? true : false); + const [data, setData] = useState(""); + const [firstrequest, setFirstrequest] = useState(true); + const [list, setList] = useState([]); + const [, setListLoaded] = useState(false); + const [anchorEl, setAnchorEl] = React.useState(null); + const [headingSet, setHeadingSet] = React.useState(false); + const [selectedMeta, setSelectedMeta] = React.useState({ + link: "hello", + read_time: 2, + }); + const [tocLines, setTocLines] = React.useState([]); + const [baseUrl, setBaseUrl] = React.useState( + serverside === true ? "" : window.location.href + ); function handleClick(event) { - setAnchorEl(event.currentTarget); - } + setAnchorEl(event.currentTarget); + } - function handleClose() { - setAnchorEl(null); - } + function handleClose() { + setAnchorEl(null); + } - const SidebarPaperStyle = { - backgroundColor: theme.palette.surfaceColor, - overflowX: "hidden", - position: "relative", - padding: 30, - paddingTop: 15, - height: "80vh", - marginTop: 15, - } + const SidebarPaperStyle = { + backgroundColor: theme.palette.surfaceColor, + overflowX: "hidden", + position: "relative", + padding: 30, + paddingTop: 15, + height: "80vh", + marginTop: 15, + }; - const SideBar = { - maxWidth: 250, - flex: "1", - position: "fixed", - } + const SideBar = { + maxWidth: 250, + flex: "1", + position: "fixed", + }; - const fetchDocList = () => { - fetch(globalUrl+"/api/v1/docs", { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, + const fetchDocList = () => { + fetch(globalUrl + "/api/v1/docs", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, }) - .then((response) => response.json()) - .then((responseJson) => { - if (responseJson.success) { - setList(responseJson.list) - } else { - setList(["# Error loading documentation. Please contact us if this persists."]) - } - setListLoaded(true) - }) - .catch(error => {}); - } + .then((response) => response.json()) + .then((responseJson) => { + if (responseJson.success) { + setList(responseJson.list); + } else { + setList([ + "# Error loading documentation. Please contact us if this persists.", + ]); + } + setListLoaded(true); + }) + .catch((error) => {}); + }; - const fetchDocs = (docId) => { - fetch(globalUrl+"/api/v1/docs/"+docId, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - }) - .then((response) => response.json()) - .then((responseJson) => { - if (responseJson.success) { - setData(responseJson.reason) - document.title = "Shuffle "+docId+" documentation" + const fetchDocs = (docId) => { + fetch(globalUrl + "/api/v1/docs/" + docId, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + }) + .then((response) => response.json()) + .then((responseJson) => { + if (responseJson.success) { + setData(responseJson.reason); + document.title = "Shuffle " + docId + " documentation"; - if (responseJson.meta !== undefined) { - setSelectedMeta(responseJson.meta) - } + if (responseJson.meta !== undefined) { + setSelectedMeta(responseJson.meta); + } - //console.log("TOC list: ", responseJson.reason) - if (responseJson.reason !== undefined && responseJson.reason !== null) { - const splitkey = responseJson.reason.split("\n") - var innerTocLines = [] - var record = false - for (var key in splitkey) { - const line = splitkey[key] - //console.log("Line: ", line) - if (line.toLowerCase().includes("table of contents")) { - record = true - continue - } + //console.log("TOC list: ", responseJson.reason) + if ( + responseJson.reason !== undefined && + responseJson.reason !== null + ) { + const splitkey = responseJson.reason.split("\n"); + var innerTocLines = []; + var record = false; + for (var key in splitkey) { + const line = splitkey[key]; + //console.log("Line: ", line) + if (line.toLowerCase().includes("table of contents")) { + record = true; + continue; + } - if (record && line.length < 3) { - record = false - } + if (record && line.length < 3) { + record = false; + } - if (record) { - const parsedline = line.split("](") - if (parsedline.length > 1) { - parsedline[0] = parsedline[0].replaceAll("*", "") - parsedline[0] = parsedline[0].replaceAll("[", "") - parsedline[0] = parsedline[0].replaceAll("]", "") - parsedline[0] = parsedline[0].replaceAll("(", "") - parsedline[0] = parsedline[0].replaceAll(")", "") - parsedline[0] = parsedline[0].trim() + if (record) { + const parsedline = line.split("]("); + if (parsedline.length > 1) { + parsedline[0] = parsedline[0].replaceAll("*", ""); + parsedline[0] = parsedline[0].replaceAll("[", ""); + parsedline[0] = parsedline[0].replaceAll("]", ""); + parsedline[0] = parsedline[0].replaceAll("(", ""); + parsedline[0] = parsedline[0].replaceAll(")", ""); + parsedline[0] = parsedline[0].trim(); - parsedline[1] = parsedline[1].replaceAll("*", "") - parsedline[1] = parsedline[1].replaceAll("[", "") - parsedline[1] = parsedline[1].replaceAll("]", "") - parsedline[1] = parsedline[1].replaceAll(")", "") - parsedline[1] = parsedline[1].replaceAll("(", "") - parsedline[1] = parsedline[1].trim() - //console.log(parsedline[0], parsedline[1]) + parsedline[1] = parsedline[1].replaceAll("*", ""); + parsedline[1] = parsedline[1].replaceAll("[", ""); + parsedline[1] = parsedline[1].replaceAll("]", ""); + parsedline[1] = parsedline[1].replaceAll(")", ""); + parsedline[1] = parsedline[1].replaceAll("(", ""); + parsedline[1] = parsedline[1].trim(); + //console.log(parsedline[0], parsedline[1]) - innerTocLines.push({ - "text": parsedline[0], - "link": parsedline[1] - }) - } else { - console.log("Bad line for parsing: ", line) - } - } - } - - setTocLines(innerTocLines) - } - } else { - setData("# Error\nThis page doesn't exist.") - } - }) - .catch(error => {}); - } + innerTocLines.push({ + text: parsedline[0], + link: parsedline[1], + }); + } else { + console.log("Bad line for parsing: ", line); + } + } + } - if (firstrequest) { - setFirstrequest(false) - if (!serverside) { - if (window.innerWidth < 768) { - setMobile(true) - } - } + setTocLines(innerTocLines); + } + } else { + setData("# Error\nThis page doesn't exist."); + } + }) + .catch((error) => {}); + }; - if (selectedDoc !== undefined) { - setData(selectedDoc.reason) - setList(selectedDoc.list) - setListLoaded(true) - } else { - if (!serverside) { - fetchDocList() - fetchDocs(props.match.params.key) - } - } - } + if (firstrequest) { + setFirstrequest(false); + if (!serverside) { + if (window.innerWidth < 768) { + setMobile(true); + } + } - // Handles search-based changes that origin from outside this file - if (serverside !== true && window.location.href !== baseUrl) { - setBaseUrl(window.location.href) - fetchDocs(props.match.params.key) - } + if (selectedDoc !== undefined) { + setData(selectedDoc.reason); + setList(selectedDoc.list); + setListLoaded(true); + } else { + if (!serverside) { + fetchDocList(); + fetchDocs(props.match.params.key); + } + } + } - const parseElementScroll = () => { - const offset = 45 - var parent = document.getElementById("markdown_wrapper_outer") - if (parent !== null) { - //console.log("IN PARENT") - var elements = parent.getElementsByTagName('h2') + // Handles search-based changes that origin from outside this file + if (serverside !== true && window.location.href !== baseUrl) { + setBaseUrl(window.location.href); + fetchDocs(props.match.params.key); + } - const name = window.location.hash.slice(1, window.location.hash.lenth).toLowerCase().split("%20").join(" ").split("_").join(" ").split("-").join(" ") + const parseElementScroll = () => { + const offset = 45; + var parent = document.getElementById("markdown_wrapper_outer"); + if (parent !== null) { + //console.log("IN PARENT") + var elements = parent.getElementsByTagName("h2"); - //console.log(name) - var found = false - for (var key in elements) { - const element = elements[key] - if (element.innerHTML === undefined) { - continue - } + const name = window.location.hash + .slice(1, window.location.hash.lenth) + .toLowerCase() + .split("%20") + .join(" ") + .split("_") + .join(" ") + .split("-") + .join(" "); - // Fix location.. - if (element.innerHTML.toLowerCase() === name) { - //console.log(element.offsetTop) - element.scrollIntoView({behavior: "smooth"}) - //element.scrollTo({ - // top: element.offsetTop+offset, - // behavior: "smooth" - //}) - found = true - //element.scrollTo({ - // top: element.offsetTop-100, - // behavior: "smooth" - //}) - } - } + //console.log(name) + var found = false; + for (var key in elements) { + const element = elements[key]; + if (element.innerHTML === undefined) { + continue; + } - // H# - if (!found) { - elements = parent.getElementsByTagName('h3') - //console.log("NAMe: ", name) - found = false - for (key in elements) { - const element = elements[key] - if (element.innerHTML === undefined) { - continue - } + // Fix location.. + if (element.innerHTML.toLowerCase() === name) { + //console.log(element.offsetTop) + element.scrollIntoView({ behavior: "smooth" }); + //element.scrollTo({ + // top: element.offsetTop+offset, + // behavior: "smooth" + //}) + found = true; + //element.scrollTo({ + // top: element.offsetTop-100, + // behavior: "smooth" + //}) + } + } - // Fix location.. - if (element.innerHTML.toLowerCase() === name) { - element.scrollIntoView({behavior: "smooth"}) - //element.scrollTo({ - // top: element.offsetTop-offset, - // behavior: "smooth" - //}) - found = true - //element.scrollTo({ - // top: element.offsetTop-100, - // behavior: "smooth" - //}) - } - } - } - } - //console.log(element) + // H# + if (!found) { + elements = parent.getElementsByTagName("h3"); + //console.log("NAMe: ", name) + found = false; + for (key in elements) { + const element = elements[key]; + if (element.innerHTML === undefined) { + continue; + } - //console.log("NAME: ", name) - //console.log(document.body.innerHTML) - // parent = document.getElementById(parent); + // Fix location.. + if (element.innerHTML.toLowerCase() === name) { + element.scrollIntoView({ behavior: "smooth" }); + //element.scrollTo({ + // top: element.offsetTop-offset, + // behavior: "smooth" + //}) + found = true; + //element.scrollTo({ + // top: element.offsetTop-100, + // behavior: "smooth" + //}) + } + } + } + } + //console.log(element) - //var descendants = parent.getElementsByTagName(tagname); + //console.log("NAME: ", name) + //console.log(document.body.innerHTML) + // parent = document.getElementById(parent); - // this.scrollDiv.current.scrollIntoView({ behavior: 'smooth' }); + //var descendants = parent.getElementsByTagName(tagname); - //$(".parent").find("h2:contains('Statistics')").parent(); - } + // this.scrollDiv.current.scrollIntoView({ behavior: 'smooth' }); - if (serverside !== true && window.location.hash.length > 0) { - parseElementScroll() - } + //$(".parent").find("h2:contains('Statistics')").parent(); + }; - const markdownStyle = { - color: "rgba(255, 255, 255, 0.65)", - flex: "1", - maxWidth: mobile ? "100%" : 750, - overflow: "hidden", - paddingBottom: 200, - marginLeft: mobile ? 0 : 275, - } + if (serverside !== true && window.location.hash.length > 0) { + parseElementScroll(); + } - function OuterLink(props) { - if (props.href.includes("http") || props.href.includes("mailto")) { - return {props.children} - } - return {props.children} - } + const markdownStyle = { + color: "rgba(255, 255, 255, 0.65)", + flex: "1", + maxWidth: mobile ? "100%" : 750, + overflow: "hidden", + paddingBottom: 200, + marginLeft: mobile ? 0 : 275, + }; - function Img(props) { - return {props.alt} - } + function OuterLink(props) { + if (props.href.includes("http") || props.href.includes("mailto")) { + return ( + + {props.children} + + ); + } + return ( + + {props.children} + + ); + } - function CodeHandler(props) { - return ( -
-				
-					{props.value}
-				
-			
- ) - } + function Img(props) { + return {props.alt}; + } - const Heading = (props) => { - const element = React.createElement(`h${props.level}`, {style: {marginTop: props.level === 1 ? 20 : 50}}, props.children) - const [hover, setHover] = useState(false) + function CodeHandler(props) { + return ( +
+        {props.value}
+      
+ ); + } - var extraInfo = "" - if (props.level === 1) { - extraInfo = -
-
- {mobile ? null : - - - - - - } - {mobile ? null : -
- } - - {selectedMeta.read_time} minute{selectedMeta.read_time === 1 ? "" : "s"} to read - -
-
- {mobile || selectedMeta.contributors === undefined || selectedMeta.contributors === null ? "" : -
- {selectedMeta.contributors.slice(0,7).map((data, index) => { - return ( - - - {data.url} - - - ) - })} -
- } -
-
- } + const Heading = (props) => { + const element = React.createElement( + `h${props.level}`, + { style: { marginTop: props.level === 1 ? 20 : 50 } }, + props.children + ); + const [hover, setHover] = useState(false); - return ( - { - setHover(true) - }} > - {props.level !== 1 ? : null} - {element} - {/*hover ? {setHover(true)}} style={{cursor: "pointer", display: "inline", }} onClick={() => { + var extraInfo = ""; + if (props.level === 1) { + extraInfo = ( +
+
+ {mobile ? null : ( + + + + + + )} + {mobile ? null : ( +
+ )} + + {selectedMeta.read_time} minute + {selectedMeta.read_time === 1 ? "" : "s"} to read + +
+
+ {mobile || + selectedMeta.contributors === undefined || + selectedMeta.contributors === null ? ( + "" + ) : ( +
+ {selectedMeta.contributors.slice(0, 7).map((data, index) => { + return ( + + + {data.url} + + + ); + })} +
+ )} +
+
+ ); + } + + return ( + { + setHover(true); + }} + > + {props.level !== 1 ? ( + + ) : null} + {element} + {/*hover ? {setHover(true)}} style={{cursor: "pointer", display: "inline", }} onClick={() => { window.location.href += "#hello" console.log(window.location) //window.history.pushState('page2', 'Title', '/page2.php'); @@ -351,166 +455,208 @@ const Docs = (props) => { }} /> : "" */} - {extraInfo} - - ) - } - //React.createElement("p", {style: {color: "red", backgroundColor: "blue"}}, this.props.paragraph) + {extraInfo} + + ); + }; + //React.createElement("p", {style: {color: "red", backgroundColor: "blue"}}, this.props.paragraph) + //function unicodeToChar(text) { + // return text.replace(/\\u[\dA-F]{4}/gi, + // function (match) { + // return String.fromCharCode(parseInt(match.replace(/\\u/g, ''), 16)); + // } + // ); + //} - //function unicodeToChar(text) { - // return text.replace(/\\u[\dA-F]{4}/gi, - // function (match) { - // return String.fromCharCode(parseInt(match.replace(/\\u/g, ''), 16)); - // } - // ); - //} + const postDataBrowser = + list === undefined || list === null ? null : ( +
+
+ + + {list.map((data, index) => { + const item = data.name; + if (item === undefined) { + return null; + } - const postDataBrowser = list === undefined || list === null ? null : -
-
- - - {list.map((data, index) => { - const item = data.name - if (item === undefined) { - return null - } + const path = "/docs/" + item; + const newname = + item.charAt(0).toUpperCase() + + item.substring(1).split("_").join(" ").split("-").join(" "); + const itemMatching = + props.match.params.key.toLowerCase() === item.toLowerCase(); + //const [tocLines, setTocLines] = React.useState([]); + return ( +
  • + { + setTocLines([]); + fetchDocs(item); + }} + > + + > {newname} + + + {itemMatching && + tocLines !== null && + tocLines !== undefined && + tocLines.length > 0 ? ( +
    + {tocLines.map((data, index) => { + //console.log(data) - const path = "/docs/"+item - const newname = item.charAt(0).toUpperCase()+item.substring(1).split("_").join(" ").split("-").join(" ") - const itemMatching = props.match.params.key.toLowerCase() === item.toLowerCase() - //const [tocLines, setTocLines] = React.useState([]); - return ( -
  • - { - setTocLines([]) - fetchDocs(item) - }}> - > {newname} - - {itemMatching && tocLines !== null && tocLines !== undefined && tocLines.length > 0 ? -
    - {tocLines.map((data, index) => { - //console.log(data) + return ( + {}} + > + + - {data.text} + + + ); + })} +
    + ) : null} +
  • + ); + })} +
    +
    +
    +
    + +
    +
    + ); - return ( - {}}> - - - {data.text} - - - ) - })} -
    - : null} - - ) - })} - - -
    -
    - -
    -
    + const mobileStyle = { + color: "white", + marginLeft: 25, + marginRight: 25, + paddingBottom: 50, + backgroundColor: "inherit", + display: "flex", + flexDirection: "column", + }; - const mobileStyle = { - color: "white", - marginLeft: 25, - marginRight: 25, - paddingBottom: 50, - backgroundColor: "inherit", - display: "flex", - flexDirection: "column", - } + const postDataMobile = + list === undefined || list === null ? null : ( +
    +
    + + + {list.map((data, index) => { + const item = data.name; + if (item === undefined) { + return null; + } - const postDataMobile = list === undefined || list === null ? null : -
    -
    - - - {list.map((data, index) => { - const item = data.name - if (item === undefined) { - return null - } + const path = "/docs/" + item; + const newname = + item.charAt(0).toUpperCase() + + item.substring(1).split("_").join(" ").split("-").join(" "); + return ( + { + window.location.pathname = path; + }} + > + {newname} + + ); + })} + +
    +
    + +
    + + +
    + ); - const path = "/docs/"+item - const newname = item.charAt(0).toUpperCase()+item.substring(1).split("_").join(" ").split("-").join(" ") - return ( - {window.location.pathname = path}}>{newname} - ) - })} -
    -
    -
    - -
    - - + //const imageModal = + // + // {imageModal} -
    - - - //const imageModal = - // - // {imageModal} - - - const loadedCheck = -
    - - {postDataBrowser} - - - {postDataMobile} - -
    - - return ( -
    - {loadedCheck} -
    - ) -} + const loadedCheck = ( +
    + {postDataBrowser} + {postDataMobile} +
    + ); + return
    {loadedCheck}
    ; +}; export default Docs; diff --git a/frontend/src/views/EditSchedule.jsx b/frontend/src/views/EditSchedule.jsx index c47453e8..ffc3eb1c 100644 --- a/frontend/src/views/EditSchedule.jsx +++ b/frontend/src/views/EditSchedule.jsx @@ -1,1388 +1,1574 @@ -import React, { useState, useEffect} from 'react'; +import React, { useState, useEffect } from "react"; -/* +/* * It works a little something like this: * Choose what source and destination action you want - * Select the source field to be used for a required destination field OR + * Select the source field to be used for a required destination field OR * write a static value for the field (FIXME) OR * run a generator action for the field (FIXME, these should be e.g. run workflow and get result) */ -import Grid from '@material-ui/core/Grid'; -import Dialog from '@material-ui/core/Dialog'; -import DialogTitle from '@material-ui/core/DialogTitle'; -import DialogActions from '@material-ui/core/DialogActions'; -import DialogContent from '@material-ui/core/DialogContent'; +import Grid from "@material-ui/core/Grid"; +import Dialog from "@material-ui/core/Dialog"; +import DialogTitle from "@material-ui/core/DialogTitle"; +import DialogActions from "@material-ui/core/DialogActions"; +import DialogContent from "@material-ui/core/DialogContent"; -import ButtonBase from '@material-ui/core/ButtonBase'; -import TextField from '@material-ui/core/TextField'; -import MenuItem from '@material-ui/core/MenuItem'; -import Paper from '@material-ui/core/Paper'; -import Button from '@material-ui/core/Button'; -import FormControl from '@material-ui/core/FormControl'; -import Select from '@material-ui/core/Select'; -import InputLabel from '@material-ui/core/InputLabel'; -import Table from '@material-ui/core/Table'; -import InputAdornment from '@material-ui/core/InputAdornment'; -import TableBody from '@material-ui/core/TableBody'; -import TableCell from '@material-ui/core/TableCell'; -import TableRow from '@material-ui/core/TableRow'; +import ButtonBase from "@material-ui/core/ButtonBase"; +import TextField from "@material-ui/core/TextField"; +import MenuItem from "@material-ui/core/MenuItem"; +import Paper from "@material-ui/core/Paper"; +import Button from "@material-ui/core/Button"; +import FormControl from "@material-ui/core/FormControl"; +import Select from "@material-ui/core/Select"; +import InputLabel from "@material-ui/core/InputLabel"; +import Table from "@material-ui/core/Table"; +import InputAdornment from "@material-ui/core/InputAdornment"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import TableRow from "@material-ui/core/TableRow"; -import SearchIcon from '@material-ui/icons/Search'; -import DeleteIcon from '@material-ui/icons/Delete'; +import SearchIcon from "@material-ui/icons/Search"; +import DeleteIcon from "@material-ui/icons/Delete"; -import Downshift from 'downshift'; -import deburr from 'lodash/deburr'; +import Downshift from "downshift"; +import deburr from "lodash/deburr"; const EditSchedule = (props) => { - const { globalUrl } = props; - - const [tmpSrcApp, setTmpSrcApp] = useState({}) - const [tmpDstApp, setTmpDstApp] = useState({}) - const [srcApp, setSrcApp] = useState({}) - const [srcAppConfig, setSrcAppConfig] = useState([]) - const [srcAppConfigOpen, setSrcAppConfigOpen] = useState(false) - const [srcAppAction, setSrcAppAction] = useState("") - - const [dstApp, setDstApp] = useState({}) - const [dstAppAction, setDstAppAction] = useState("") - const [dstAppConfig, setDstAppConfig] = useState([]) - const [dstAppConfigOpen, setDstAppConfigOpen] = useState(false) - - // Lets set the real data here - const [selectedSrc, setSelectedSrc] = React.useState(""); - const [, setSelectedSrcData] = React.useState({}); - const [selectedDst, setSelectedDst] = React.useState([]); - - // FIXME - const [suggestions, setSuggestions] = React.useState([]); - const [inputappdata, setInputAppData] = React.useState({}); - - const [scheduleConfig, setScheduleConfig] = React.useState({}) - const [selectedSrcParameters, setSelectedSrcParameters] = React.useState([]) - - const getCurrentSchedule = () => { - fetch(globalUrl+"/api/v1/schedules/"+props.match.params.key, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - }) - .then((response) => response.json()) - .then((responseJson) => { - setScheduleConfig(responseJson) - }) - .catch(error => { - console.log(error) - }); - } - - const loadAppSuggestions = () => { - fetch(globalUrl+"/api/v1/schedules/apps", { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - }) - .then((response) => response.json()) - .then((responseJson) => { - setSuggestions(responseJson.apps) - }) - .catch(error => { - console.log(error) - }); - } - - // FIXME - this is generated from app selection input with required items - useEffect(() => { - if (Object.getOwnPropertyNames(scheduleConfig).length <= 0) { - getCurrentSchedule() - } - - // Load apps if destination or source is - if (suggestions.length === 0) { - loadAppSuggestions() - } - - // Load everything else - if (Object.getOwnPropertyNames(scheduleConfig).length > 0 && Object.getOwnPropertyNames(scheduleConfig.appinfo.sourceapp).length > 0 && Object.getOwnPropertyNames(srcApp).length <= 0) { - if (scheduleConfig.appinfo.sourceapp.name.length > 0) { - setSrcApp(scheduleConfig.appinfo.sourceapp) - setSrcAppAction(scheduleConfig.appinfo.sourceapp.action) - } - } - - - // Use sourceapp.name&version and sourceapp.action and look for name in inputappdata - // Basically fix everything in this one lol - if (suggestions.length > 0 && srcAppAction.length > 0 && Object.getOwnPropertyNames(scheduleConfig).length > 0 && Object.getOwnPropertyNames(inputappdata).length <= 0) { - // Loops all apps and finds current - for (var key in suggestions) { - var curapp = suggestions[key] - if (curapp.name === srcApp.name) { - break - } - } - - setInputAppData(curapp) - - // Loops the apps actions to find current - for (key in curapp.output) { - var curappaction = curapp.output[key] - if (curappaction.name === srcAppAction) { - break - } - } - - setSelectedSrcParameters(curappaction.outputparameters) - if (curappaction.config !== null && curappaction.config !== undefined) { - setSrcAppConfig(curappaction.config) - } - - // FIXME - set src of all translator nodes to curappaction.outputparameters[0] if not defined - //value={scheduleConfig.translator[count].src.name} - - //console.log(scheduleConfig) - var newtranslator = [] - for (key in scheduleConfig.translator) { - var curtranslator = scheduleConfig.translator[key] - - // Overwrite issues - if (curtranslator.src.name === "") { - curtranslator.src = curappaction.outputparameters[0] - } - - newtranslator.push(curtranslator) - } - - scheduleConfig.translator = newtranslator - setScheduleConfig(scheduleConfig) - } - - if (suggestions.length > 0 && dstAppAction.length <= 0 && Object.getOwnPropertyNames(scheduleConfig).length > 0) { - // Loops all apps and finds current - for (key in suggestions) { - curapp = suggestions[key] - if (curapp.name === dstApp.name) { - break - } - } - - const curAction = scheduleConfig.appinfo.destinationapp.action - - // Loops the apps actions to find current - for (key in curapp.output) { - curappaction = curapp.output[key] - if (curappaction.name === curAction) { - break - } - } - - setDstAppAction(curappaction.name) - if (curappaction.config !== null && curappaction.config !== undefined) { - setDstAppConfig(curappaction.config) - } - } - - - }) - - const getSuggestions = (value, type, { showEmpty = false } = {}) => { - const inputValue = deburr(value.trim()).toLowerCase(); - const inputLength = inputValue.length; - let count = 0; - - return inputLength === 0 && !showEmpty - ? [] - : suggestions.filter(suggestion => { - const keep = - count < 5 && - suggestion.types && - suggestion.types.includes(type) && - suggestion.name.slice(0, inputLength).toLowerCase() === inputValue; - - if (keep) { - count += 1; - } - - return keep; - }); - } - - const setSrcAppWrapper = (currentapps) => { - setSrcApp(currentapps[0]) - if (currentapps[0].output.length > 0) { - selectSrcAction(currentapps[0].output[0].name) - } - } - - const setDstAppWrapper = (currentapps) => { - setDstApp(currentapps[0]) - if (currentapps[0].input.length > 0) { - selectInitialDstAction(currentapps) - } - } - - const renderInput = (type, inputProps) => { - const { InputProps, classes, ref, ...other } = inputProps; - - // Sets the srcapp if string is matching exactly for name - if (InputProps["aria-activedescendant"] === null && InputProps["value"].length > 0) { - const currentapps = suggestions.filter(data => data.name === InputProps["value"]) - if (currentapps.length === 1) { - if (type === "output") { - if (currentapps[0].name !== srcApp.name) { - setSrcAppWrapper(currentapps) - } - } else if (type === "input") { - if (currentapps[0].name !== dstApp.name) { - setDstAppWrapper(currentapps) - } - } - } - } - - return ( -
    - - - - ), - }} - {...other} - /> -
    - ); - } - - const renderSuggestion = (suggestionProps) => { - const { suggestion, index, itemProps, highlightedIndex, selectedItem } = suggestionProps; - const isHighlighted = highlightedIndex === index; - const isSelected = (selectedItem || '').indexOf(suggestion.name) > -1; - - return ( - - {suggestion.name} - - ); - } - - const bodyDivStyle = { - marginLeft: "20px", - marginTop: "50px", - marginRight: "20px", - margin: "auto", - width: "1350px", - display: "flex", - } - - const appActionStyle = { - height: "50px", - marginTop: "10px", - } - - // FIXME - set this - //const setRelationshipsFromSource = () => { - - //} - - //// FIXME - set this - //const setRelationshipsFromGenerator = () => { - - //} - - const selectDstAppAction = (event) => { - if (event.target.value === dstAppAction) { - return - } - - setDstAppAction(event.target.value) - refactorTranslations(event.target.value) - } - - const refactorTranslations = (action) => { - // dstapp is chosen - var found = false - for (var key in dstApp.input) { - var curinput = dstApp.input[key] - if (curinput.name === action) { - found = true - break - } - } - - // Should never happen.. - if (!found) { - return - } - - var tmprelationships = [] - for (key in curinput.inputparameters) { - var curRelation = {"dst": curinput.inputparameters[key], "src": {}} - tmprelationships.push(curRelation) - } - - scheduleConfig["translator"] = tmprelationships - setScheduleConfig(scheduleConfig) - } - - const selectInitialDstAction = (value) => { - var action = value[0].input[0].name - setDstAppAction(action) - - var found = false - for (var key in value) { - var curinput = value[0].input[key] - if (curinput.name === action) { - found = true - break - } - } - - // Should never happen.. - if (!found) { - return - } - - var tmprelationships = [] - for (key in curinput.inputparameters) { - var curRelation = {"dst": curinput.inputparameters[key], "src": {}} - tmprelationships.push(curRelation) - } - - scheduleConfig["translator"] = tmprelationships - setScheduleConfig(scheduleConfig) - } - - - const selectSrcAction = (value) => { - // FIXME - load the config for this action - var found = false - - setSrcAppAction(value) - - var curitem = {} - for (var key in inputappdata.output) { - curitem = inputappdata.output[key] - if (curitem.name === value) { - found = true - break - } - } - - if (!found) { - setSelectedSrcParameters([]) - return - } - - // Generate this for every relation? - setSelectedSrcParameters(curitem.outputparameters) - - //FIXME - check if source has the right attribute - var tmprelationships = [] - key = 0 - for (key in scheduleConfig.translator) { - var curRelation = scheduleConfig.translator[key] - curRelation["src"] = curitem.outputparameters[0] - tmprelationships.push(curRelation) - } - - scheduleConfig["translator"] = tmprelationships - setScheduleConfig(scheduleConfig) - } - - // Wrapper to handle event click - const selectSrcActionWrapper = (event) => { - return selectSrcAction(event.target.value) - } - - const srcappaction = Object.getOwnPropertyNames(srcApp).length > 0 && srcApp.output ? -
    - - - Actions - - - -
    - : null - - const dstappaction = Object.getOwnPropertyNames(dstApp).length > 0 ? -
    - - - Actions - - - -
    - : null - - const downshiftStyle = { - //marginLeft: "20px", - //marginTop: "20px", - //marginRight: "20px", - display: "flex", - width: "100%", - } - - const submitDisabled = selectedSrc.length > 0 && selectedDst.length > 0 - const submitButtonClick = () => { - var newtranslations = [] - var tobeadded = [] - var tobechanged = [] - - // Go find the information again in testdata - - //newrelationship["translator"] = {"src": {}, "dst": {}} - //newrelationship["static"] = {} - - // Reformat relationships - // clean up old relationships - // - - if (Object.getOwnPropertyNames(scheduleConfig.Translator).length <= 0) { - return - } - - // Clean up old translations for specified elements - // FIXME - maybe just send a request with relationships and fix in backend? - // FIXME - there is an issue here for some multifield stuff - // Literally have to verify every single one anyway.. - for (var selected in selectedDst) { - var found = false - var index = 0 - for (var key in scheduleConfig["translator"]) { - if (scheduleConfig["translator"][key]["dst"]["name"] === selectedDst[selected]) { - index = key - found = true - break - } - } - - if (!found) { - tobeadded.push(selectedDst[selected]) - } else { - // Dst can be the - tobechanged.push(index) - } - } - - var tmprelationships = scheduleConfig - - key = 0 - for (key in scheduleConfig["translator"]) { - if (tobechanged.includes(key)) { - var tmprel = scheduleConfig["translator"][key] - tmprel["src"] = {"name": selectedSrc} - tmprelationships["translator"].splice(key, 1) - newtranslations.push(tmprel) - } - } - - key = 0 - for (key in tobeadded) { - tmprel = {"src": {"name": selectedSrc}, "dst": {"name": tobeadded[key]}} - newtranslations.push(tmprel) - } - - // delete deletable keys from copy (new) - // - key = 0 - for (key in newtranslations) { - tmprelationships["translator"].push(newtranslations[key]) - } - - setSelectedSrc("") - setSelectedDst([]) - setSelectedSrcData({}) - - // FIXME - does this work? - scheduleConfig["translator"] = tmprelationships["translator"] - setScheduleConfig(scheduleConfig) - } - - // When it's set, need to find the destination in the row and set static - const setStaticValue = (event, row) => { - console.log("HI") - // FIXME - modify the row first - // FIXME - set generator and source to "nothing" - - console.log(row.dst.name) - console.log(row) - console.log(event.target.value) - var newsrcrow = {"name": "static", "description": "Static value set", "type": "static", "value": event.target.value, "schema": {"type": "string"}} - - var relationshipclone = JSON.parse(JSON.stringify(scheduleConfig)); - for (var key in scheduleConfig["translator"]) { - var curItem = scheduleConfig["translator"][key] - if (curItem.dst.name === row.dst.name) { - break - } - } - - relationshipclone["translator"][key]["src"] = newsrcrow - - scheduleConfig["translator"] = relationshipclone["translator"] - console.log(scheduleConfig) - - // Fuck this :( - // DO DIS IN FRONTEND AND HAVE A SUBMIT BUTTON :( - fetch(globalUrl+"/api/v1/schedules/"+props.match.params.key, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - body: JSON.stringify(scheduleConfig), - }) - .then((response) => response.json()) - .then((responseJson) => { - setScheduleConfig({}) - }) - .catch(error => { - console.log(error) - }); - } - - // Rewrites relationships clientside - const setCurrentSrcPropRelation = (event, rowkey) => { - console.log(event, rowkey) - var found = false - - for (var key in selectedSrcParameters) { - var curItem = selectedSrcParameters[key] - if (curItem.name === event.target.value) { - found = true - break - } - } - - // No idea how this would ever happen, but but (: - if (!found) { - return - } - - var row = scheduleConfig.translator[rowkey] - var rowclone = JSON.parse(JSON.stringify(row)); - rowclone.src = curItem - - var relationshipclone = JSON.parse(JSON.stringify(scheduleConfig)); - for (key in scheduleConfig["translator"]) { - curItem = scheduleConfig["translator"][key] - if (curItem.dst.name === row.dst.name) { - break - } - } - - relationshipclone["translator"][key] = rowclone - - scheduleConfig["translator"] = relationshipclone["translator"] - console.log(scheduleConfig) - - // Fuck this :( - // DO DIS IN FRONTEND AND HAVE A SUBMIT BUTTON :( - fetch(globalUrl+"/api/v1/schedules/"+props.match.params.key, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - body: JSON.stringify(scheduleConfig), - }) - .then((response) => response.json()) - .then((responseJson) => { - setScheduleConfig({}) - }) - .catch(error => { - console.log(error) - }); - return - } - - - - const relationshipBody = Object.getOwnPropertyNames(scheduleConfig).length > 0 && scheduleConfig["translator"] !== undefined && scheduleConfig.translator.length > 0 ? - - - - - Action - - - Source Field - - - Destination Field - - - Field Type - - - Required - - - Static - - - Generator - - - Transform - - - {scheduleConfig.translator.map((row, count) => { - var buttonIcon = - if (!row.dst.required) { - buttonIcon = - } - - const srcpropsSelector = (Object.getOwnPropertyNames(selectedSrcParameters).length > 0 || Object.getOwnPropertyNames(scheduleConfig.translator).length > 0) && Object.getOwnPropertyNames(srcApp).length > 0 ? - :
    No defined fields
    - - const placeholderValue = row.src.type === "static" ? row.src.value : "... Set a static value" - - return( - - - {buttonIcon} - - {srcpropsSelector} - {row.dst.name} - {row.dst.schema.type} - {row.dst.required} - - { - if (event.key === 'Enter') { - event.preventDefault(); - console.log(`Pressed keyCode ${event.key}`); - setStaticValue(event, row) - } - }} - /> - - INSERT SCRIPT THINGY - Cortex responder? - - ) - })} -
    -
    : null - - - // FIXME - use this - //const submitRelationships = () => { - // // Make an API-call to the backend for verification - // // Return with failures etc, and mark row issues? - // - // var apiUrl = "http://localhost:5000" - // fetch(apiUrl+"/api/v1/schedules", - // { - // method: "POST", - // headers: {"content-type": "application/json"}, - // body: JSON.stringify(scheduleConfig), - // } - // ) - // .then((response) => response.json()) - // .then((responseJson) => { - // console.log(responseJson) - // }) - // .catch((error) => { - // console.log(error); - // }); - //} - - const executeSchedule = () => { - fetch(globalUrl+"/api/v1/schedules/"+props.match.params.key+"/execute", { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - }) - .then((response) => response.json()) - .then((responseJson) => { - console.log(responseJson) - }) - .catch(error => { - console.log(error) - }); - } - - - const submitButton = -
    - -
    - - // Requires src or dst as input - // FIXME - use this shit to edit an app or something - //const editButtonFix = (app, apptype) => { - // if (apptype === "src") { - - // } else if (apptype === "dst") { - - // } - //} - - const editSrcApp = (event) => { - // if tmpsrcapp, show RESET (can be an X or something too) - // if reset is clicked, set source app back to the original - setTmpSrcApp(scheduleConfig.appinfo.sourceapp) - - var tmpscheduleConfig = scheduleConfig - tmpscheduleConfig.appinfo.sourceapp = {} - - fetch(globalUrl+"/api/v1/schedules/"+props.match.params.key, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - body: JSON.stringify(tmpscheduleConfig), - }) - .then((response) => response.json()) - .then((responseJson) => { - setScheduleConfig({}) - setSrcApp({}) - setSrcAppAction("") - }) - .catch(error => { - console.log(error) - }); - } - - const editDstApp = (event) => { - // if tmpsrcapp, show RESET (can be an X or something too) - // if reset is clicked, set source app back to the original - setTmpDstApp(scheduleConfig.appinfo.destinationapp) - - var tmpscheduleConfig = scheduleConfig - tmpscheduleConfig.appinfo.destinationapp = {} - - fetch(globalUrl+"/api/v1/schedules/"+props.match.params.key, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - body: JSON.stringify(tmpscheduleConfig), - }) - .then((response) => response.json()) - .then((responseJson) => { - setScheduleConfig({}) - setDstApp({}) - setDstAppAction("") - }) - .catch(error => { - console.log(error) - }); - } - - const scheduleApp = (app, actiondata) => { - const editButton = actiondata === "src" ? - - : - - - - - const configureButton = actiondata === "src" ? - - : - - // FIXME - set src vs dst - return( - - - - - - - {splitter} - - - -
    -

    {app.name}

    -
    -
    - {app.description} -
    -
    - - {app.action} - -
    - {splitter} - -
    - {editButton} -
    -
    - {configureButton} -
    -
    -
    -
    - ) - } - - const splitter =
    - - const dstDownshift = - - {({ - getInputProps, - getItemProps, - getMenuProps, - highlightedIndex, - inputValue, - isOpen, - selectedItem, - }) => ( -
    - {renderInput("input", { - fullWidth: true, - InputProps: getInputProps({ - placeholder: 'Search destination apps', - }), - })} - -
    -
    - {isOpen ? ( - - {getSuggestions(inputValue, "input").map((suggestion, index) => - renderSuggestion({ - suggestion, - index, - itemProps: getItemProps({ item: suggestion.name }), - highlightedIndex, - selectedItem, - }), - )} - - ) : null} -
    - {dstappaction} -
    -
    - )} -
    - - - const srcDownshift = - - {({ - getInputProps, - getItemProps, - getMenuProps, - highlightedIndex, - inputValue, - isOpen, - selectedItem, - }) => ( -
    - {renderInput("output", { - fullWidth: true, - InputProps: getInputProps({ - placeholder: 'Search source apps', - }), - })} - -
    -
    - {isOpen ? ( - - {getSuggestions(inputValue, "output").map((suggestion, index) => - renderSuggestion({ - suggestion, - index, - itemProps: getItemProps({ item: suggestion.name }), - highlightedIndex, - selectedItem, - }), - )} - - ) : null} -
    - {srcappaction} -
    -
    - )} -
    - - const submitDstApp = (event) => { - var packagedSrc = { - name: dstApp.name, - id: dstApp.id, - description: dstApp.description, - action: dstAppAction, - } - - var tmpscheduleConfig = scheduleConfig - - tmpscheduleConfig.appinfo.destinationapp = packagedSrc - - // Hmm, do this here? Idk - fetch(globalUrl+"/api/v1/schedules/"+props.match.params.key, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - body: JSON.stringify(tmpscheduleConfig), - }) - .then((response) => response.json()) - .then((responseJson) => { - setScheduleConfig({}) - }) - .catch(error => { - console.log(error) - }); - } - - const submitSrcApp = (event) => { - var packagedSrc = { - name: srcApp.name, - id: srcApp.id, - description: srcApp.description, - action: srcAppAction, - } - - var tmpscheduleConfig = scheduleConfig - - // FIXME - future fred - // - tmpscheduleConfig.appinfo.sourceapp = packagedSrc - - // Hmm, do this here? Idk - fetch(globalUrl+"/api/v1/schedules/"+props.match.params.key, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - body: JSON.stringify(tmpscheduleConfig), - }) - .then((response) => response.json()) - .then((responseJson) => { - setScheduleConfig({}) - }) - .catch(error => { - console.log(error) - }); - } - - const resetDstApp = (event) => { - var tmpscheduleConfig = scheduleConfig - tmpscheduleConfig.appinfo.destinationapp = tmpDstApp - - // Hmm, do this here? Idk - fetch(globalUrl+"/api/v1/schedules/"+props.match.params.key, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - body: JSON.stringify(tmpscheduleConfig), - }) - .then((response) => response.json()) - .then((responseJson) => { - setScheduleConfig({}) - setDstApp(tmpSrcApp) - setDstAppAction(tmpDstApp.action) - setTmpDstApp({}) - }) - .catch(error => { - console.log(error) - }); - - } - - const resetSrcApp = (event) => { - var tmpscheduleConfig = scheduleConfig - tmpscheduleConfig.appinfo.sourceapp = tmpSrcApp - - // Hmm, do this here? Idk - fetch(globalUrl+"/api/v1/schedules/"+props.match.params.key, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - body: JSON.stringify(tmpscheduleConfig), - }) - .then((response) => response.json()) - .then((responseJson) => { - setScheduleConfig({}) - setSrcApp(tmpSrcApp) - setSrcAppAction(tmpSrcApp.action) - setTmpSrcApp({}) - }) - .catch(error => { - console.log(error) - }); - - } - - // Based on some srcthing - const searchGridSrc = - - - Choose Source app (FIXME) - clickable - - {splitter} - - - -
    - {srcDownshift} -
    -
    -
    - {splitter} - -
    - -
    -
    - -
    -
    -
    -
    - - const searchGridDst = - - - Choose Source app (FIXME) - clickable - - {splitter} - - - -
    - {dstDownshift} -
    -
    -
    - {splitter} - -
    - -
    -
    - -
    -
    -
    -
    - - - const srcField = (Object.getOwnPropertyNames(scheduleConfig).length > 0 && Object.getOwnPropertyNames(scheduleConfig.appinfo.sourceapp).length > 0 && scheduleConfig.appinfo.sourceapp.name.length > 0) ? -
    - - {scheduleApp(scheduleConfig.appinfo.sourceapp, "src")} - -
    - : -
    - - {searchGridSrc} - -
    - - const dstField = (Object.getOwnPropertyNames(scheduleConfig).length > 0 && Object.getOwnPropertyNames(scheduleConfig.appinfo.destinationapp).length > 0 && scheduleConfig.appinfo.destinationapp.name.length > 0) ? -
    - - {scheduleApp(scheduleConfig.appinfo.destinationapp, "dst")} - -
    - : -
    - - {searchGridDst} - -
    - - // Have to use array cus of datastore lol (no map[string]string) - const srcModalData = [] - const buildSrcModal = (event, fieldname) => { - var fieldfound = false - for (var key in srcModalData) { - if (srcModalData[key]["key"] === fieldname) { - fieldfound = true - srcModalData[key]["value"] = event.target.value - break - } - } - - if (!fieldfound) { - srcModalData.push({"key": fieldname, "value": event.target.value}) - } - console.log(srcModalData) - } - - // FIXME - verify required fields? - const submitSrcConfig = () => { - scheduleConfig.appinfo.sourceapp.config = srcModalData - setScheduleConfig(scheduleConfig) - setSrcAppConfigOpen(false) - - fetch(globalUrl+"/api/v1/schedules/"+props.match.params.key, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - body: JSON.stringify(scheduleConfig), - }) - .then((response) => response.json()) - .then((responseJson) => { - console.log(responseJson) - }) - .catch(error => { - console.log(error) - }); - } - - const dstModalData = [] - const buildDstModal = (event, fieldname) => { - var fieldfound = false - for (var key in dstModalData) { - if (dstModalData[key]["key"] === fieldname) { - fieldfound = true - dstModalData[key]["value"] = event.target.value - break - } - } - - if (!fieldfound) { - dstModalData.push({"key": fieldname, "value": event.target.value}) - } - console.log(dstModalData) - } - - // FIXME - verify required fields - const submitDstConfig = () => { - scheduleConfig.appinfo.destinationapp.config = dstModalData - setScheduleConfig(scheduleConfig) - setDstAppConfigOpen(false) - - fetch(globalUrl+"/api/v1/schedules/"+props.match.params.key, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - body: JSON.stringify(scheduleConfig), - }) - .then((response) => response.json()) - .then((responseJson) => { - console.log(responseJson) - }) - .catch(error => { - console.log(error) - }); - } - - const dstConfigModal = dstAppConfigOpen ? - {setDstAppConfigOpen(false)}} - > - Source configuration - - Configure {dstApp.name}'s required fields - {dstAppConfig.map(data => ( - {buildDstModal(event, data.name)}} - autofocus - color="primary" - name="searchtext" - placeholder={data.name} - margin="dense" - id={data.name} - label={data.name} - fullWidth - /> - ))} - - - - - - : null - - - // FIXME - load the actual fields! - const srcConfigModal = srcAppConfigOpen ? - {setSrcAppConfigOpen(false)}} - > - Source configuration - - Configure {srcApp.name}'s required fields - {srcAppConfig.map(data => ( - {buildSrcModal(event, data.name)}} - autofocus - color="primary" - name="searchtext" - placeholder={data.name} - margin="dense" - id={data.name} - label={data.name} - fullWidth - /> - ))} - - - - - - : null - - return( -
    - {srcConfigModal} - {dstConfigModal} -
    - {srcField} - {dstField} -
    - -
    - {submitButton} -
    -
    - {relationshipBody} -
    -
    - ) -} - -export default EditSchedule + const { globalUrl } = props; + + const [tmpSrcApp, setTmpSrcApp] = useState({}); + const [tmpDstApp, setTmpDstApp] = useState({}); + const [srcApp, setSrcApp] = useState({}); + const [srcAppConfig, setSrcAppConfig] = useState([]); + const [srcAppConfigOpen, setSrcAppConfigOpen] = useState(false); + const [srcAppAction, setSrcAppAction] = useState(""); + + const [dstApp, setDstApp] = useState({}); + const [dstAppAction, setDstAppAction] = useState(""); + const [dstAppConfig, setDstAppConfig] = useState([]); + const [dstAppConfigOpen, setDstAppConfigOpen] = useState(false); + + // Lets set the real data here + const [selectedSrc, setSelectedSrc] = React.useState(""); + const [, setSelectedSrcData] = React.useState({}); + const [selectedDst, setSelectedDst] = React.useState([]); + + // FIXME + const [suggestions, setSuggestions] = React.useState([]); + const [inputappdata, setInputAppData] = React.useState({}); + + const [scheduleConfig, setScheduleConfig] = React.useState({}); + const [selectedSrcParameters, setSelectedSrcParameters] = React.useState([]); + + const getCurrentSchedule = () => { + fetch(globalUrl + "/api/v1/schedules/" + props.match.params.key, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + }) + .then((response) => response.json()) + .then((responseJson) => { + setScheduleConfig(responseJson); + }) + .catch((error) => { + console.log(error); + }); + }; + + const loadAppSuggestions = () => { + fetch(globalUrl + "/api/v1/schedules/apps", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + }) + .then((response) => response.json()) + .then((responseJson) => { + setSuggestions(responseJson.apps); + }) + .catch((error) => { + console.log(error); + }); + }; + + // FIXME - this is generated from app selection input with required items + useEffect(() => { + if (Object.getOwnPropertyNames(scheduleConfig).length <= 0) { + getCurrentSchedule(); + } + + // Load apps if destination or source is + if (suggestions.length === 0) { + loadAppSuggestions(); + } + + // Load everything else + if ( + Object.getOwnPropertyNames(scheduleConfig).length > 0 && + Object.getOwnPropertyNames(scheduleConfig.appinfo.sourceapp).length > 0 && + Object.getOwnPropertyNames(srcApp).length <= 0 + ) { + if (scheduleConfig.appinfo.sourceapp.name.length > 0) { + setSrcApp(scheduleConfig.appinfo.sourceapp); + setSrcAppAction(scheduleConfig.appinfo.sourceapp.action); + } + } + + // Use sourceapp.name&version and sourceapp.action and look for name in inputappdata + // Basically fix everything in this one lol + if ( + suggestions.length > 0 && + srcAppAction.length > 0 && + Object.getOwnPropertyNames(scheduleConfig).length > 0 && + Object.getOwnPropertyNames(inputappdata).length <= 0 + ) { + // Loops all apps and finds current + for (var key in suggestions) { + var curapp = suggestions[key]; + if (curapp.name === srcApp.name) { + break; + } + } + + setInputAppData(curapp); + + // Loops the apps actions to find current + for (key in curapp.output) { + var curappaction = curapp.output[key]; + if (curappaction.name === srcAppAction) { + break; + } + } + + setSelectedSrcParameters(curappaction.outputparameters); + if (curappaction.config !== null && curappaction.config !== undefined) { + setSrcAppConfig(curappaction.config); + } + + // FIXME - set src of all translator nodes to curappaction.outputparameters[0] if not defined + //value={scheduleConfig.translator[count].src.name} + + //console.log(scheduleConfig) + var newtranslator = []; + for (key in scheduleConfig.translator) { + var curtranslator = scheduleConfig.translator[key]; + + // Overwrite issues + if (curtranslator.src.name === "") { + curtranslator.src = curappaction.outputparameters[0]; + } + + newtranslator.push(curtranslator); + } + + scheduleConfig.translator = newtranslator; + setScheduleConfig(scheduleConfig); + } + + if ( + suggestions.length > 0 && + dstAppAction.length <= 0 && + Object.getOwnPropertyNames(scheduleConfig).length > 0 + ) { + // Loops all apps and finds current + for (key in suggestions) { + curapp = suggestions[key]; + if (curapp.name === dstApp.name) { + break; + } + } + + const curAction = scheduleConfig.appinfo.destinationapp.action; + + // Loops the apps actions to find current + for (key in curapp.output) { + curappaction = curapp.output[key]; + if (curappaction.name === curAction) { + break; + } + } + + setDstAppAction(curappaction.name); + if (curappaction.config !== null && curappaction.config !== undefined) { + setDstAppConfig(curappaction.config); + } + } + }); + + const getSuggestions = (value, type, { showEmpty = false } = {}) => { + const inputValue = deburr(value.trim()).toLowerCase(); + const inputLength = inputValue.length; + let count = 0; + + return inputLength === 0 && !showEmpty + ? [] + : suggestions.filter((suggestion) => { + const keep = + count < 5 && + suggestion.types && + suggestion.types.includes(type) && + suggestion.name.slice(0, inputLength).toLowerCase() === inputValue; + + if (keep) { + count += 1; + } + + return keep; + }); + }; + + const setSrcAppWrapper = (currentapps) => { + setSrcApp(currentapps[0]); + if (currentapps[0].output.length > 0) { + selectSrcAction(currentapps[0].output[0].name); + } + }; + + const setDstAppWrapper = (currentapps) => { + setDstApp(currentapps[0]); + if (currentapps[0].input.length > 0) { + selectInitialDstAction(currentapps); + } + }; + + const renderInput = (type, inputProps) => { + const { InputProps, classes, ref, ...other } = inputProps; + + // Sets the srcapp if string is matching exactly for name + if ( + InputProps["aria-activedescendant"] === null && + InputProps["value"].length > 0 + ) { + const currentapps = suggestions.filter( + (data) => data.name === InputProps["value"] + ); + if (currentapps.length === 1) { + if (type === "output") { + if (currentapps[0].name !== srcApp.name) { + setSrcAppWrapper(currentapps); + } + } else if (type === "input") { + if (currentapps[0].name !== dstApp.name) { + setDstAppWrapper(currentapps); + } + } + } + } + + return ( +
    + + + + ), + }} + {...other} + /> +
    + ); + }; + + const renderSuggestion = (suggestionProps) => { + const { suggestion, index, itemProps, highlightedIndex, selectedItem } = + suggestionProps; + const isHighlighted = highlightedIndex === index; + const isSelected = (selectedItem || "").indexOf(suggestion.name) > -1; + + return ( + + {suggestion.name} + + ); + }; + + const bodyDivStyle = { + marginLeft: "20px", + marginTop: "50px", + marginRight: "20px", + margin: "auto", + width: "1350px", + display: "flex", + }; + + const appActionStyle = { + height: "50px", + marginTop: "10px", + }; + + // FIXME - set this + //const setRelationshipsFromSource = () => { + + //} + + //// FIXME - set this + //const setRelationshipsFromGenerator = () => { + + //} + + const selectDstAppAction = (event) => { + if (event.target.value === dstAppAction) { + return; + } + + setDstAppAction(event.target.value); + refactorTranslations(event.target.value); + }; + + const refactorTranslations = (action) => { + // dstapp is chosen + var found = false; + for (var key in dstApp.input) { + var curinput = dstApp.input[key]; + if (curinput.name === action) { + found = true; + break; + } + } + + // Should never happen.. + if (!found) { + return; + } + + var tmprelationships = []; + for (key in curinput.inputparameters) { + var curRelation = { dst: curinput.inputparameters[key], src: {} }; + tmprelationships.push(curRelation); + } + + scheduleConfig["translator"] = tmprelationships; + setScheduleConfig(scheduleConfig); + }; + + const selectInitialDstAction = (value) => { + var action = value[0].input[0].name; + setDstAppAction(action); + + var found = false; + for (var key in value) { + var curinput = value[0].input[key]; + if (curinput.name === action) { + found = true; + break; + } + } + + // Should never happen.. + if (!found) { + return; + } + + var tmprelationships = []; + for (key in curinput.inputparameters) { + var curRelation = { dst: curinput.inputparameters[key], src: {} }; + tmprelationships.push(curRelation); + } + + scheduleConfig["translator"] = tmprelationships; + setScheduleConfig(scheduleConfig); + }; + + const selectSrcAction = (value) => { + // FIXME - load the config for this action + var found = false; + + setSrcAppAction(value); + + var curitem = {}; + for (var key in inputappdata.output) { + curitem = inputappdata.output[key]; + if (curitem.name === value) { + found = true; + break; + } + } + + if (!found) { + setSelectedSrcParameters([]); + return; + } + + // Generate this for every relation? + setSelectedSrcParameters(curitem.outputparameters); + + //FIXME - check if source has the right attribute + var tmprelationships = []; + key = 0; + for (key in scheduleConfig.translator) { + var curRelation = scheduleConfig.translator[key]; + curRelation["src"] = curitem.outputparameters[0]; + tmprelationships.push(curRelation); + } + + scheduleConfig["translator"] = tmprelationships; + setScheduleConfig(scheduleConfig); + }; + + // Wrapper to handle event click + const selectSrcActionWrapper = (event) => { + return selectSrcAction(event.target.value); + }; + + const srcappaction = + Object.getOwnPropertyNames(srcApp).length > 0 && srcApp.output ? ( +
    + + Actions + + +
    + ) : null; + + const dstappaction = + Object.getOwnPropertyNames(dstApp).length > 0 ? ( +
    + + Actions + + +
    + ) : null; + + const downshiftStyle = { + //marginLeft: "20px", + //marginTop: "20px", + //marginRight: "20px", + display: "flex", + width: "100%", + }; + + const submitDisabled = selectedSrc.length > 0 && selectedDst.length > 0; + const submitButtonClick = () => { + var newtranslations = []; + var tobeadded = []; + var tobechanged = []; + + // Go find the information again in testdata + + //newrelationship["translator"] = {"src": {}, "dst": {}} + //newrelationship["static"] = {} + + // Reformat relationships + // clean up old relationships + // + + if (Object.getOwnPropertyNames(scheduleConfig.Translator).length <= 0) { + return; + } + + // Clean up old translations for specified elements + // FIXME - maybe just send a request with relationships and fix in backend? + // FIXME - there is an issue here for some multifield stuff + // Literally have to verify every single one anyway.. + for (var selected in selectedDst) { + var found = false; + var index = 0; + for (var key in scheduleConfig["translator"]) { + if ( + scheduleConfig["translator"][key]["dst"]["name"] === + selectedDst[selected] + ) { + index = key; + found = true; + break; + } + } + + if (!found) { + tobeadded.push(selectedDst[selected]); + } else { + // Dst can be the + tobechanged.push(index); + } + } + + var tmprelationships = scheduleConfig; + + key = 0; + for (key in scheduleConfig["translator"]) { + if (tobechanged.includes(key)) { + var tmprel = scheduleConfig["translator"][key]; + tmprel["src"] = { name: selectedSrc }; + tmprelationships["translator"].splice(key, 1); + newtranslations.push(tmprel); + } + } + + key = 0; + for (key in tobeadded) { + tmprel = { src: { name: selectedSrc }, dst: { name: tobeadded[key] } }; + newtranslations.push(tmprel); + } + + // delete deletable keys from copy (new) + // + key = 0; + for (key in newtranslations) { + tmprelationships["translator"].push(newtranslations[key]); + } + + setSelectedSrc(""); + setSelectedDst([]); + setSelectedSrcData({}); + + // FIXME - does this work? + scheduleConfig["translator"] = tmprelationships["translator"]; + setScheduleConfig(scheduleConfig); + }; + + // When it's set, need to find the destination in the row and set static + const setStaticValue = (event, row) => { + console.log("HI"); + // FIXME - modify the row first + // FIXME - set generator and source to "nothing" + + console.log(row.dst.name); + console.log(row); + console.log(event.target.value); + var newsrcrow = { + name: "static", + description: "Static value set", + type: "static", + value: event.target.value, + schema: { type: "string" }, + }; + + var relationshipclone = JSON.parse(JSON.stringify(scheduleConfig)); + for (var key in scheduleConfig["translator"]) { + var curItem = scheduleConfig["translator"][key]; + if (curItem.dst.name === row.dst.name) { + break; + } + } + + relationshipclone["translator"][key]["src"] = newsrcrow; + + scheduleConfig["translator"] = relationshipclone["translator"]; + console.log(scheduleConfig); + + // Fuck this :( + // DO DIS IN FRONTEND AND HAVE A SUBMIT BUTTON :( + fetch(globalUrl + "/api/v1/schedules/" + props.match.params.key, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(scheduleConfig), + }) + .then((response) => response.json()) + .then((responseJson) => { + setScheduleConfig({}); + }) + .catch((error) => { + console.log(error); + }); + }; + + // Rewrites relationships clientside + const setCurrentSrcPropRelation = (event, rowkey) => { + console.log(event, rowkey); + var found = false; + + for (var key in selectedSrcParameters) { + var curItem = selectedSrcParameters[key]; + if (curItem.name === event.target.value) { + found = true; + break; + } + } + + // No idea how this would ever happen, but but (: + if (!found) { + return; + } + + var row = scheduleConfig.translator[rowkey]; + var rowclone = JSON.parse(JSON.stringify(row)); + rowclone.src = curItem; + + var relationshipclone = JSON.parse(JSON.stringify(scheduleConfig)); + for (key in scheduleConfig["translator"]) { + curItem = scheduleConfig["translator"][key]; + if (curItem.dst.name === row.dst.name) { + break; + } + } + + relationshipclone["translator"][key] = rowclone; + + scheduleConfig["translator"] = relationshipclone["translator"]; + console.log(scheduleConfig); + + // Fuck this :( + // DO DIS IN FRONTEND AND HAVE A SUBMIT BUTTON :( + fetch(globalUrl + "/api/v1/schedules/" + props.match.params.key, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(scheduleConfig), + }) + .then((response) => response.json()) + .then((responseJson) => { + setScheduleConfig({}); + }) + .catch((error) => { + console.log(error); + }); + return; + }; + + const relationshipBody = + Object.getOwnPropertyNames(scheduleConfig).length > 0 && + scheduleConfig["translator"] !== undefined && + scheduleConfig.translator.length > 0 ? ( + + + + + Action + + + Source Field + + + Destination Field + + + Field Type + + + Required + + + Static + + + Generator + + + Transform + + + {scheduleConfig.translator.map((row, count) => { + var buttonIcon = ; + if (!row.dst.required) { + buttonIcon = ( + + ); + } + + const srcpropsSelector = + (Object.getOwnPropertyNames(selectedSrcParameters).length > 0 || + Object.getOwnPropertyNames(scheduleConfig.translator).length > + 0) && + Object.getOwnPropertyNames(srcApp).length > 0 ? ( + + ) : ( +
    No defined fields
    + ); + + const placeholderValue = + row.src.type === "static" + ? row.src.value + : "... Set a static value"; + + return ( + + + {buttonIcon} + + {srcpropsSelector} + {row.dst.name} + {row.dst.schema.type} + {row.dst.required} + + { + if (event.key === "Enter") { + event.preventDefault(); + console.log(`Pressed keyCode ${event.key}`); + setStaticValue(event, row); + } + }} + /> + + INSERT SCRIPT THINGY - Cortex responder? + + ); + })} +
    +
    + ) : null; + + // FIXME - use this + //const submitRelationships = () => { + // // Make an API-call to the backend for verification + // // Return with failures etc, and mark row issues? + // + // var apiUrl = "http://localhost:5000" + // fetch(apiUrl+"/api/v1/schedules", + // { + // method: "POST", + // headers: {"content-type": "application/json"}, + // body: JSON.stringify(scheduleConfig), + // } + // ) + // .then((response) => response.json()) + // .then((responseJson) => { + // console.log(responseJson) + // }) + // .catch((error) => { + // console.log(error); + // }); + //} + + const executeSchedule = () => { + fetch( + globalUrl + "/api/v1/schedules/" + props.match.params.key + "/execute", + { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + } + ) + .then((response) => response.json()) + .then((responseJson) => { + console.log(responseJson); + }) + .catch((error) => { + console.log(error); + }); + }; + + const submitButton = ( +
    + +
    + ); + + // Requires src or dst as input + // FIXME - use this shit to edit an app or something + //const editButtonFix = (app, apptype) => { + // if (apptype === "src") { + + // } else if (apptype === "dst") { + + // } + //} + + const editSrcApp = (event) => { + // if tmpsrcapp, show RESET (can be an X or something too) + // if reset is clicked, set source app back to the original + setTmpSrcApp(scheduleConfig.appinfo.sourceapp); + + var tmpscheduleConfig = scheduleConfig; + tmpscheduleConfig.appinfo.sourceapp = {}; + + fetch(globalUrl + "/api/v1/schedules/" + props.match.params.key, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(tmpscheduleConfig), + }) + .then((response) => response.json()) + .then((responseJson) => { + setScheduleConfig({}); + setSrcApp({}); + setSrcAppAction(""); + }) + .catch((error) => { + console.log(error); + }); + }; + + const editDstApp = (event) => { + // if tmpsrcapp, show RESET (can be an X or something too) + // if reset is clicked, set source app back to the original + setTmpDstApp(scheduleConfig.appinfo.destinationapp); + + var tmpscheduleConfig = scheduleConfig; + tmpscheduleConfig.appinfo.destinationapp = {}; + + fetch(globalUrl + "/api/v1/schedules/" + props.match.params.key, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(tmpscheduleConfig), + }) + .then((response) => response.json()) + .then((responseJson) => { + setScheduleConfig({}); + setDstApp({}); + setDstAppAction(""); + }) + .catch((error) => { + console.log(error); + }); + }; + + const scheduleApp = (app, actiondata) => { + const editButton = + actiondata === "src" ? ( + + ) : ( + + ); + + const configureButton = + actiondata === "src" ? ( + + ) : ( + + ); + + // FIXME - set src vs dst + return ( + + + + + + + {splitter} + + + +
    +

    {app.name}

    +
    +
    {app.description}
    +
    + {app.action} +
    + {splitter} + +
    {editButton}
    +
    {configureButton}
    +
    +
    +
    + ); + }; + + const splitter = ( +
    + ); + + const dstDownshift = ( + + {({ + getInputProps, + getItemProps, + getMenuProps, + highlightedIndex, + inputValue, + isOpen, + selectedItem, + }) => ( +
    + {renderInput("input", { + fullWidth: true, + InputProps: getInputProps({ + placeholder: "Search destination apps", + }), + })} + +
    +
    + {isOpen ? ( + + {getSuggestions(inputValue, "input").map( + (suggestion, index) => + renderSuggestion({ + suggestion, + index, + itemProps: getItemProps({ item: suggestion.name }), + highlightedIndex, + selectedItem, + }) + )} + + ) : null} +
    + {dstappaction} +
    +
    + )} +
    + ); + + const srcDownshift = ( + + {({ + getInputProps, + getItemProps, + getMenuProps, + highlightedIndex, + inputValue, + isOpen, + selectedItem, + }) => ( +
    + {renderInput("output", { + fullWidth: true, + InputProps: getInputProps({ + placeholder: "Search source apps", + }), + })} + +
    +
    + {isOpen ? ( + + {getSuggestions(inputValue, "output").map( + (suggestion, index) => + renderSuggestion({ + suggestion, + index, + itemProps: getItemProps({ item: suggestion.name }), + highlightedIndex, + selectedItem, + }) + )} + + ) : null} +
    + {srcappaction} +
    +
    + )} +
    + ); + + const submitDstApp = (event) => { + var packagedSrc = { + name: dstApp.name, + id: dstApp.id, + description: dstApp.description, + action: dstAppAction, + }; + + var tmpscheduleConfig = scheduleConfig; + + tmpscheduleConfig.appinfo.destinationapp = packagedSrc; + + // Hmm, do this here? Idk + fetch(globalUrl + "/api/v1/schedules/" + props.match.params.key, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(tmpscheduleConfig), + }) + .then((response) => response.json()) + .then((responseJson) => { + setScheduleConfig({}); + }) + .catch((error) => { + console.log(error); + }); + }; + + const submitSrcApp = (event) => { + var packagedSrc = { + name: srcApp.name, + id: srcApp.id, + description: srcApp.description, + action: srcAppAction, + }; + + var tmpscheduleConfig = scheduleConfig; + + // FIXME - future fred + // + tmpscheduleConfig.appinfo.sourceapp = packagedSrc; + + // Hmm, do this here? Idk + fetch(globalUrl + "/api/v1/schedules/" + props.match.params.key, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(tmpscheduleConfig), + }) + .then((response) => response.json()) + .then((responseJson) => { + setScheduleConfig({}); + }) + .catch((error) => { + console.log(error); + }); + }; + + const resetDstApp = (event) => { + var tmpscheduleConfig = scheduleConfig; + tmpscheduleConfig.appinfo.destinationapp = tmpDstApp; + + // Hmm, do this here? Idk + fetch(globalUrl + "/api/v1/schedules/" + props.match.params.key, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(tmpscheduleConfig), + }) + .then((response) => response.json()) + .then((responseJson) => { + setScheduleConfig({}); + setDstApp(tmpSrcApp); + setDstAppAction(tmpDstApp.action); + setTmpDstApp({}); + }) + .catch((error) => { + console.log(error); + }); + }; + + const resetSrcApp = (event) => { + var tmpscheduleConfig = scheduleConfig; + tmpscheduleConfig.appinfo.sourceapp = tmpSrcApp; + + // Hmm, do this here? Idk + fetch(globalUrl + "/api/v1/schedules/" + props.match.params.key, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(tmpscheduleConfig), + }) + .then((response) => response.json()) + .then((responseJson) => { + setScheduleConfig({}); + setSrcApp(tmpSrcApp); + setSrcAppAction(tmpSrcApp.action); + setTmpSrcApp({}); + }) + .catch((error) => { + console.log(error); + }); + }; + + // Based on some srcthing + const searchGridSrc = ( + + + Choose Source app (FIXME) - clickable + + {splitter} + + + +
    {srcDownshift}
    +
    +
    + {splitter} + +
    + +
    +
    + +
    +
    +
    +
    + ); + + const searchGridDst = ( + + + Choose Source app (FIXME) - clickable + + {splitter} + + + +
    {dstDownshift}
    +
    +
    + {splitter} + +
    + +
    +
    + +
    +
    +
    +
    + ); + + const srcField = + Object.getOwnPropertyNames(scheduleConfig).length > 0 && + Object.getOwnPropertyNames(scheduleConfig.appinfo.sourceapp).length > 0 && + scheduleConfig.appinfo.sourceapp.name.length > 0 ? ( +
    + + {scheduleApp(scheduleConfig.appinfo.sourceapp, "src")} + +
    + ) : ( +
    + + {searchGridSrc} + +
    + ); + + const dstField = + Object.getOwnPropertyNames(scheduleConfig).length > 0 && + Object.getOwnPropertyNames(scheduleConfig.appinfo.destinationapp).length > + 0 && + scheduleConfig.appinfo.destinationapp.name.length > 0 ? ( +
    + + {scheduleApp(scheduleConfig.appinfo.destinationapp, "dst")} + +
    + ) : ( +
    + + {searchGridDst} + +
    + ); + + // Have to use array cus of datastore lol (no map[string]string) + const srcModalData = []; + const buildSrcModal = (event, fieldname) => { + var fieldfound = false; + for (var key in srcModalData) { + if (srcModalData[key]["key"] === fieldname) { + fieldfound = true; + srcModalData[key]["value"] = event.target.value; + break; + } + } + + if (!fieldfound) { + srcModalData.push({ key: fieldname, value: event.target.value }); + } + console.log(srcModalData); + }; + + // FIXME - verify required fields? + const submitSrcConfig = () => { + scheduleConfig.appinfo.sourceapp.config = srcModalData; + setScheduleConfig(scheduleConfig); + setSrcAppConfigOpen(false); + + fetch(globalUrl + "/api/v1/schedules/" + props.match.params.key, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(scheduleConfig), + }) + .then((response) => response.json()) + .then((responseJson) => { + console.log(responseJson); + }) + .catch((error) => { + console.log(error); + }); + }; + + const dstModalData = []; + const buildDstModal = (event, fieldname) => { + var fieldfound = false; + for (var key in dstModalData) { + if (dstModalData[key]["key"] === fieldname) { + fieldfound = true; + dstModalData[key]["value"] = event.target.value; + break; + } + } + + if (!fieldfound) { + dstModalData.push({ key: fieldname, value: event.target.value }); + } + console.log(dstModalData); + }; + + // FIXME - verify required fields + const submitDstConfig = () => { + scheduleConfig.appinfo.destinationapp.config = dstModalData; + setScheduleConfig(scheduleConfig); + setDstAppConfigOpen(false); + + fetch(globalUrl + "/api/v1/schedules/" + props.match.params.key, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(scheduleConfig), + }) + .then((response) => response.json()) + .then((responseJson) => { + console.log(responseJson); + }) + .catch((error) => { + console.log(error); + }); + }; + + const dstConfigModal = dstAppConfigOpen ? ( + { + setDstAppConfigOpen(false); + }} + > + Source configuration + + Configure {dstApp.name}'s required fields + {dstAppConfig.map((data) => ( + { + buildDstModal(event, data.name); + }} + autofocus + color="primary" + name="searchtext" + placeholder={data.name} + margin="dense" + id={data.name} + label={data.name} + fullWidth + /> + ))} + + + + + + + ) : null; + + // FIXME - load the actual fields! + const srcConfigModal = srcAppConfigOpen ? ( + { + setSrcAppConfigOpen(false); + }} + > + Source configuration + + Configure {srcApp.name}'s required fields + {srcAppConfig.map((data) => ( + { + buildSrcModal(event, data.name); + }} + autofocus + color="primary" + name="searchtext" + placeholder={data.name} + margin="dense" + id={data.name} + label={data.name} + fullWidth + /> + ))} + + + + + + + ) : null; + + return ( +
    + {srcConfigModal} + {dstConfigModal} +
    + {srcField} + {dstField} +
    + +
    {submitButton}
    +
    {relationshipBody}
    +
    + ); +}; + +export default EditSchedule; diff --git a/frontend/src/views/EditWebhook.jsx b/frontend/src/views/EditWebhook.jsx index 9ec5879c..e1a5d516 100644 --- a/frontend/src/views/EditWebhook.jsx +++ b/frontend/src/views/EditWebhook.jsx @@ -1,366 +1,393 @@ -import React, {useState, useEffect} from 'react'; +import React, { useState, useEffect } from "react"; -import Button from '@material-ui/core/Button'; -import Paper from '@material-ui/core/Paper'; -import Divider from '@material-ui/core/Divider'; -import Select from '@material-ui/core/Select'; -import MenuItem from '@material-ui/core/MenuItem'; +import Button from "@material-ui/core/Button"; +import Paper from "@material-ui/core/Paper"; +import Divider from "@material-ui/core/Divider"; +import Select from "@material-ui/core/Select"; +import MenuItem from "@material-ui/core/MenuItem"; -import WebhookImage from '../assets/img/webhook.png'; -import KafkaImage from '../assets/img/kafka.png'; +import WebhookImage from "../assets/img/webhook.png"; +import KafkaImage from "../assets/img/kafka.png"; import EditWorkflow from "./EditWorkflow"; const EditWebhook = (props) => { - const { globalUrl, isLoaded } = props; + const { globalUrl, isLoaded } = props; - // FIXME - //const [webhookData, setWebhookData] = useState(webhooktest) - const [webhookData, setWebhookData] = useState({}) - const [workflows, setWorkflows] = useState([]) - const [firstrequest, setFirstrequest] = React.useState(true); + // FIXME + //const [webhookData, setWebhookData] = useState(webhooktest) + const [webhookData, setWebhookData] = useState({}); + const [workflows, setWorkflows] = useState([]); + const [firstrequest, setFirstrequest] = React.useState(true); - const [selectedWorkflows, setSelectedWorkflows] = useState([]) + const [selectedWorkflows, setSelectedWorkflows] = useState([]); - const getWorkflows = () => { - 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 response.json() - }) - .then((responseJson) => { - setWorkflows(responseJson) + const getWorkflows = () => { + 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 response.json(); + }) + .then((responseJson) => { + setWorkflows(responseJson); + }) + .catch((error) => { + console.log(error); + }); + }; - }) - .catch(error => { - console.log(error) - }); - } + const setWebhook = (inputdata) => { + console.log(inputdata); - const setWebhook = (inputdata) => { - console.log(inputdata) + fetch(globalUrl + "/api/v1/hooks/" + props.match.params.key, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + body: JSON.stringify(inputdata), + }) + .then((response) => response.json()) + .then((responseJson) => { + console.log(responseJson); + }) + .catch((error) => { + console.log(error); + }); + }; - fetch(globalUrl+"/api/v1/hooks/"+props.match.params.key, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - credentials: "include", - body: JSON.stringify(inputdata), - }) - .then((response) => response.json()) - .then((responseJson) => { - console.log(responseJson) - }) - .catch(error => { - console.log(error) - }); - } + const getCurrentWebhook = () => { + fetch(globalUrl + "/api/v1/hooks/" + props.match.params.key, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200!"); + window.location.pathname = "webhooks"; + } + return response.json(); + }) + .then((responseJson) => { + if (responseJson.actions === null) { + responseJson.actions = []; + } - const getCurrentWebhook = () => { - fetch(globalUrl+"/api/v1/hooks/"+props.match.params.key, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - credentials: "include", - }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200!") - window.location.pathname = "webhooks" - } - return response.json() - }) - .then((responseJson) => { - if (responseJson.actions === null) { - responseJson.actions = [] - } + if (responseJson.transforms === null) { + responseJson.transforms = []; + } - if (responseJson.transforms === null) { - responseJson.transforms = [] - } + setWebhookData(responseJson); + }) + .catch((error) => { + console.log(error); + //window.location.pathname = "webhooks" + }); + }; - setWebhookData(responseJson) - }) - .catch(error => { - console.log(error) - //window.location.pathname = "webhooks" - }); - } + useEffect(() => { + if (firstrequest) { + setFirstrequest(false); + getCurrentWebhook(); + if (workflows.length <= 0) { + getWorkflows(); + } + } - useEffect(() => { - if (firstrequest) { - setFirstrequest(false) - getCurrentWebhook() - if (workflows.length <= 0) { - getWorkflows() - } - } + // After everything is loaded + if ( + Object.getOwnPropertyNames(webhookData).length > 0 && + webhookData.actions.length > 0 && + workflows.length > 0 && + selectedWorkflows.length === 0 + ) { + // Setting startup actions. making like this in case we want other actions + var tmpActionWorkflows = []; + for (var key in webhookData.actions) { + if (webhookData.actions[key].type === "workflow") { + tmpActionWorkflows.push(webhookData.actions[key]); + } + } - // After everything is loaded - if (Object.getOwnPropertyNames(webhookData).length > 0 && webhookData.actions.length > 0 && workflows.length > 0 && selectedWorkflows.length === 0) { - // Setting startup actions. making like this in case we want other actions - var tmpActionWorkflows = [] - for (var key in webhookData.actions) { - if (webhookData.actions[key].type === "workflow") { - tmpActionWorkflows.push(webhookData.actions[key]) - } - } + // Fix duplicates... Meh + var foundWorkflowIds = []; + var tmpWorkflows = []; + for (key in tmpActionWorkflows) { + if (foundWorkflowIds.includes(tmpActionWorkflows[key].id)) { + continue; + } - // Fix duplicates... Meh - var foundWorkflowIds = [] - var tmpWorkflows = [] - for (key in tmpActionWorkflows) { - if (foundWorkflowIds.includes(tmpActionWorkflows[key].id)) { - continue - } + for (var subkey in workflows) { + if (tmpActionWorkflows[key].id === workflows[subkey]["id_"]) { + console.log(tmpActionWorkflows[key].id, workflows[subkey]["id_"]); + foundWorkflowIds.push(tmpActionWorkflows[key].id); + tmpWorkflows.push(workflows[subkey]); + break; + } + } + } - for (var subkey in workflows) { - if (tmpActionWorkflows[key].id === workflows[subkey]["id_"]) { - console.log(tmpActionWorkflows[key].id, workflows[subkey]["id_"]) - foundWorkflowIds.push(tmpActionWorkflows[key].id) - tmpWorkflows.push(workflows[subkey]) - break - } - } - } + if (tmpWorkflows.length > 0) { + setSelectedWorkflows(tmpWorkflows); + } + } + }); - if (tmpWorkflows.length > 0) { - setSelectedWorkflows(tmpWorkflows) - } - } - }) + const hookPicture = + Object.getOwnPropertyNames(webhookData).length > 0 && + webhookData.type === "webhook" ? ( + webhook + ) : ( + MQ + ); + const executeHook = (action) => { + fetch( + globalUrl + "/api/v1/hooks/" + props.match.params.key + "/" + action, + { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + } + ) + .then((response) => response.json()) + .then((responseJson) => { + setWebhookData({}); + }) + .catch((error) => { + console.log(error); + }); + }; - const hookPicture = Object.getOwnPropertyNames(webhookData).length > 0 && webhookData.type === "webhook" ? - webhook - : - MQ + const headerPaperStyle = { + display: "flex", + maxHeight: "800px", + minHeight: "800px", + margin: "10px 30px 10px 10px", + padding: "10px 5px 5px 5px", + flexDirection: "column", + }; - const executeHook = (action) => { - fetch(globalUrl+"/api/v1/hooks/"+props.match.params.key+"/"+action, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - credentials: "include", - }) - .then((response) => response.json()) - .then((responseJson) => { - setWebhookData({}) - }) - .catch(error => { - console.log(error) - }); - } + // FIXME - add with counter to change the correct one (not just edit) + const addNewWorkflow = (event) => { + // Verify if it already exists in the array. Returns if it exists + for (var key in selectedWorkflows) { + var item = selectedWorkflows[key]; + if (item["id_"] === event.target.value["id_"]) { + return; + } + } - const headerPaperStyle = { - display: "flex", - maxHeight: "800px", - minHeight: "800px", - margin: "10px 30px 10px 10px", - padding: "10px 5px 5px 5px", - flexDirection: "column", - } + // FIXME - make this possible for all accounts + if (selectedWorkflows.length === 0) { + console.log("ADD FIRST ITEM FOR SELECTEDWORKFLOWS"); + console.log(event.target.value); - // FIXME - add with counter to change the correct one (not just edit) - const addNewWorkflow = (event) => { - // Verify if it already exists in the array. Returns if it exists - for (var key in selectedWorkflows) { - var item = selectedWorkflows[key] - if (item["id_"] === event.target.value["id_"]) { - return - } - } + // Cleanup previous actions + var newActions = []; + if (webhookData.actions.length > 0) { + for (key in webhookData.actions) { + if ( + webhookData.actions[key].type === "" || + webhookData.actions[key].type === undefined + ) { + continue; + } - // FIXME - make this possible for all accounts - if (selectedWorkflows.length === 0) { - console.log("ADD FIRST ITEM FOR SELECTEDWORKFLOWS") - console.log(event.target.value) + newActions.push(webhookData.actions[key]); + } + } - // Cleanup previous actions - var newActions = [] - if (webhookData.actions.length > 0) { - for (key in webhookData.actions) { - if (webhookData.actions[key].type === "" || webhookData.actions[key].type === undefined) { - continue - } + // FIXME - how to stringify this better hurr + var formattedWorkflow = { + type: "workflow", + name: event.target.value.name, + id: event.target.value.id_, + field: "", + }; - newActions.push(webhookData.actions[key]) - } - } + // FIXME: patch this n + newActions.push(formattedWorkflow); + console.log(newActions); - // FIXME - how to stringify this better hurr - var formattedWorkflow = { - "type": "workflow", - "name": event.target.value.name, - "id": event.target.value.id_, - "field": "", - } + webhookData.actions = newActions; + setWebhook(webhookData); + } - // FIXME: patch this n - newActions.push(formattedWorkflow) - console.log(newActions) + var tmpSelectedWorkflows = [].concat(selectedWorkflows, [ + event.target.value, + ]); + setSelectedWorkflows(tmpSelectedWorkflows); + }; - webhookData.actions = newActions - setWebhook(webhookData) - } + // FIXME + // Create a list with + button + // For each, choose the new workflow I wanna add + // Current: JUST ONE + const selectedWorkflowIds = selectedWorkflows.map((data) => { + return data["id_"]; + }); + const availableWorkflows = workflows.filter( + (data) => !selectedWorkflowIds.includes(data["id_"]) + ); - var tmpSelectedWorkflows = [].concat(selectedWorkflows, [event.target.value]) - setSelectedWorkflows(tmpSelectedWorkflows) - } + const WorkflowSelect = (counter) => { + if (selectedWorkflows[counter.counter] === undefined) { + return null; + } - // FIXME - // Create a list with + button - // For each, choose the new workflow I wanna add - // Current: JUST ONE - const selectedWorkflowIds = selectedWorkflows.map(data => {return data["id_"]}) - const availableWorkflows = workflows.filter(data => !selectedWorkflowIds.includes(data["id_"])) + console.log(selectedWorkflows[0]); + console.log(selectedWorkflows[0]); + console.log(selectedWorkflows[0]); + console.log(selectedWorkflows[counter.counter]); + console.log(selectedWorkflows[counter.counter].name); + return ( +
    + Workflow select: + +
    + ); + }; - const WorkflowSelect = (counter) => { - if (selectedWorkflows[counter.counter] === undefined) { - return null - } + const extraWorkflow = + workflows.length > 0 && availableWorkflows.length > 0 ? ( + + ) : null; - console.log(selectedWorkflows[0]) - console.log(selectedWorkflows[0]) - console.log(selectedWorkflows[0]) - console.log(selectedWorkflows[counter.counter]) - console.log(selectedWorkflows[counter.counter].name) - return ( -
    - Workflow select: - -
    - ) - } + const multiWorkflowSelect = + workflows.length > 0 && selectedWorkflows.length > 0 ? ( +
    + {selectedWorkflows.map((data, count) => ( + + ))} + {extraWorkflow} +
    + ) : ( + + ); - const extraWorkflow = workflows.length > 0 && availableWorkflows.length > 0 ? - : null + const headerInfo = + Object.getOwnPropertyNames(webhookData).length > 0 ? ( +
    + +
    +
    {hookPicture}
    +
    +
    +

    Name: {webhookData.info.name}

    +
    +
    +
    +
    + Description: {webhookData.info.description} +
    Id: {webhookData.id}
    +
    Url: {webhookData.info.url}
    +
    Type: {webhookData.type}
    +
    Status: {webhookData.status}
    +
    + CHOOSE ACTIONS: + {multiWorkflowSelect} +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    + ) : null; - const multiWorkflowSelect = workflows.length > 0 && selectedWorkflows.length > 0 ? -
    - {selectedWorkflows.map((data, count) => ( - - ))} - {extraWorkflow} -
    - : + // FIXME - needs refresh every time you add a new workflow + const workflowdata = + Object.getOwnPropertyNames(webhookData).length > 0 && + selectedWorkflows.length > 0 ? ( + + ) : null; - const headerInfo = Object.getOwnPropertyNames(webhookData).length > 0 ? -
    - -
    -
    - {hookPicture} -
    -
    -
    -

    Name: {webhookData.info.name}

    -
    -
    -
    -
    - Description: {webhookData.info.description} -
    - Id: {webhookData.id} -
    -
    - Url: {webhookData.info.url} -
    -
    - Type: {webhookData.type} -
    -
    - Status: {webhookData.status} -
    -
    - CHOOSE ACTIONS: - {multiWorkflowSelect} -
    -
    - -
    -
    - -
    -
    - -
    -
    -
    -
    - : null - - // FIXME - needs refresh every time you add a new workflow - const workflowdata = Object.getOwnPropertyNames(webhookData).length > 0 && selectedWorkflows.length > 0 ? - : null + const loadedCheck = isLoaded ? ( +
    +
    {workflowdata}
    +
    {headerInfo}
    +
    + ) : ( +
    + ); - const loadedCheck = isLoaded ? -
    -
    - {workflowdata} -
    -
    - {headerInfo} -
    -
    - : -
    -
    - - // FIXME: Use this for testing - // : null - return ( - -
    - {loadedCheck} -
    - ) -} + // FIXME: Use this for testing + // : null + return
    {loadedCheck}
    ; +}; export default EditWebhook; diff --git a/frontend/src/views/EditWorkflow.jsx b/frontend/src/views/EditWorkflow.jsx index 19ef4bfc..29066fef 100644 --- a/frontend/src/views/EditWorkflow.jsx +++ b/frontend/src/views/EditWorkflow.jsx @@ -1,364 +1,377 @@ -import React, { useState, useEffect} from 'react'; +import React, { useState, useEffect } from "react"; -import * as cytoscape from 'cytoscape'; -import * as edgehandles from 'cytoscape-edgehandles'; -import CytoscapeComponent from 'react-cytoscapejs'; +import * as cytoscape from "cytoscape"; +import * as edgehandles from "cytoscape-edgehandles"; +import CytoscapeComponent from "react-cytoscapejs"; const EditWorkflow = (props) => { - const { inputworkflows, inputtype, inputname} = props; + const { inputworkflows, inputtype, inputname } = props; + const [elements, setElements] = useState([]); + const [loadedWorkflows] = useState(inputworkflows); - const [elements, setElements] = useState([]) - const [loadedWorkflows, ] = useState(inputworkflows) + // Setting cy config + const [cystyle] = useState([ + { + selector: "node", + css: { + label: "data(label)", + "text-halign": "right", + "text-valign": "center", + "font-family": + "Segoe UI, Tahoma, Geneva, Verdana, sans-serif, sans-serif", + "font-weight": "lighter", + "font-size": "15px", + width: "60px", + height: "60px", + padding: "10px", + margin: "5px", + "border-width": "2px", + "background-image": + 'url("data:image/png;base64,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")', + }, + }, + { + selector: `node[type="ACTION"]`, + css: { + shape: "square", + "border-color": "#81c784", + }, + }, + { + selector: `node[type="CONDITION"]`, + css: { + shape: "diamond", + "border-color": "##FFEB3B", + padding: "30px", + }, + }, + { + selector: 'node[type="eventAction"]', + css: { + "background-color": "#edbd21", + }, + }, + { + selector: 'node[type="webhook"]', + css: { + "border-color": "#81c784", + "background-color": "white", + "background-image": + 'url("data:image/png;base64,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")', + }, + }, + { + selector: 'node[type="mq"]', + css: { + "background-color": "#edbd21", + }, + }, + { + selector: "node[?isStartNode]", + css: { + shape: "ellipse", + "border-width": "2px", + "border-color": "#80deea", + }, + }, + { + selector: "node[?hasErrors]", + css: { + color: "#991818", + "font-style": "italic", + }, + }, + { + selector: "node:selected", + css: { + "background-color": "#77b0d0", + }, + }, + { + selector: ".success-highlight", + css: { + "background-color": "#399645", + "transition-property": "background-color", + "transition-duration": "0.5s", + }, + }, + { + selector: ".failure-highlight", + css: { + "background-color": "#8e3530", + "transition-property": "background-color", + "transition-duration": "0.5s", + }, + }, + { + selector: ".executing-highlight", + css: { + "background-color": "#ffef47", + "transition-property": "background-color", + "transition-duration": "0.25s", + }, + }, + { + selector: ".awaiting-data-highlight", + css: { + "background-color": "#f4ad42", + "transition-property": "background-color", + "transition-duration": "0.5s", + }, + }, + { + selector: "$node > node", + css: { + "padding-top": "10px", + "padding-left": "10px", + "padding-bottom": "10px", + "padding-right": "10px", + "text-valign": "top", + "text-halign": "center", + }, + }, + { + selector: "edge", + css: { + "target-arrow-shape": "triangle", + "curve-style": "bezier", + }, + }, + { + 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.success-highlight", + css: { + width: "5px", + "target-arrow-color": "#399645", + "line-color": "#399645", + "transition-property": "line-color, width", + "transition-duration": "0.5s", + }, + }, + { + selector: "edge[?hasErrors]", + css: { + "target-arrow-color": "#991818", + "line-color": "#991818", + "line-style": "dashed", + }, + }, + { + selector: ".eh-handle", + style: { + "background-color": "#337ab7", + width: "1px", + height: "1px", + shape: "triangle", + }, + }, + { + 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", + }, + }, + ]); - // Setting cy config - const [cystyle, ] = useState( - [{ - selector: 'node', - css: { - 'label': 'data(label)', - 'text-halign': 'right', - 'text-valign': 'center', - 'font-family': 'Segoe UI, Tahoma, Geneva, Verdana, sans-serif, sans-serif', - 'font-weight': 'lighter', - 'font-size': '15px', - 'width': '60px', - 'height': '60px', - 'padding': '10px', - 'margin': '5px', - 'border-width': '2px', - 'background-image': 'url("data:image/png;base64,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")', - } - }, - { - selector: `node[type="ACTION"]`, - css: { - 'shape': 'square', - 'border-color': '#81c784', - }, - }, - { - selector: `node[type="CONDITION"]`, - css: { - 'shape': 'diamond', - 'border-color': '##FFEB3B', - 'padding': '30px' - }, - }, - { - selector: 'node[type="eventAction"]', - css: { - 'background-color': '#edbd21', - }, - }, - { - selector: 'node[type="webhook"]', - css: { - 'border-color': '#81c784', - 'background-color': 'white', - 'background-image': 'url("data:image/png;base64,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")', - }, - }, - { - selector: 'node[type="mq"]', - css: { - 'background-color': '#edbd21', - }, - }, - { - selector: 'node[?isStartNode]', - css: { - 'shape': 'ellipse', - 'border-width': '2px', - 'border-color': '#80deea', - }, - }, - { - selector: 'node[?hasErrors]', - css: { - 'color': '#991818', - 'font-style': 'italic', - }, - }, - { - selector: 'node:selected', - css: { - 'background-color': '#77b0d0', - }, - }, - { - selector: '.success-highlight', - css: { - 'background-color': '#399645', - 'transition-property': 'background-color', - 'transition-duration': '0.5s', - }, - }, - { - selector: '.failure-highlight', - css: { - 'background-color': '#8e3530', - 'transition-property': 'background-color', - 'transition-duration': '0.5s', - }, - }, - { - selector: '.executing-highlight', - css: { - 'background-color': '#ffef47', - 'transition-property': 'background-color', - 'transition-duration': '0.25s', - }, - }, - { - selector: '.awaiting-data-highlight', - css: { - 'background-color': '#f4ad42', - 'transition-property': 'background-color', - 'transition-duration': '0.5s', - }, - }, - { - selector: '$node > node', - css: { - 'padding-top': '10px', - 'padding-left': '10px', - 'padding-bottom': '10px', - 'padding-right': '10px', - 'text-valign': 'top', - 'text-halign': 'center', - }, - }, - { - selector: 'edge', - css: { - 'target-arrow-shape': 'triangle', - 'curve-style': 'bezier', - }, - }, - { - 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.success-highlight', - css: { - 'width': '5px', - 'target-arrow-color': '#399645', - 'line-color': '#399645', - 'transition-property': 'line-color, width', - 'transition-duration': '0.5s', - }, - }, - { - selector: 'edge[?hasErrors]', - css: { - 'target-arrow-color': '#991818', - 'line-color': '#991818', - 'line-style': 'dashed' - }, - }, - { - selector: '.eh-handle', - style: { - 'background-color': '#337ab7', - 'width': '1px', - 'height': '1px', - 'shape': 'triangle', - } - }, - { - 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' - } - } -] -) + useEffect(() => { + if (elements.length === 0) { + setupGraph(); + } + const cyDummy = cytoscape(); + if (!cyDummy.edgehandles) { + cytoscape.use(edgehandles); + } + }); - useEffect(() => { - if (elements.length === 0) { - setupGraph() - } + const setupGraph = () => { + // Convert our selection arrays to a string + //if (!this.loadedWorkflow.actions) { this.loadedWorkflow.actions = []; } - const cyDummy = cytoscape(); - if (!cyDummy.edgehandles) { cytoscape.use(edgehandles); } - }) + //setTimeout(() => { + // if (this.consoleArea && this.consoleArea.codeMirror) this.consoleArea.codeMirror.refresh(); + //}); - const setupGraph = () => { - // Convert our selection arrays to a string - //if (!this.loadedWorkflow.actions) { this.loadedWorkflow.actions = []; } + // Create the Cytoscape graph + // http://js.cytoscape.org/#style/labels - //setTimeout(() => { - // if (this.consoleArea && this.consoleArea.codeMirror) this.consoleArea.codeMirror.refresh(); - //}); + // Breaks stuff + //container: document.getElementById('cy'), - // Create the Cytoscape graph - // http://js.cytoscape.org/#style/labels - - // Breaks stuff - //container: document.getElementById('cy'), - - // FIXME - needs refresh - const tmpEdges = loadedWorkflows.map((workflow, count) => { - return workflow.branches.map(branch => { - const edge = { }; - edge.data = { - id: branch.id, - _id: branch.id, - source: branch.source_id, - target: branch.destination_id, - hasErrors: branch.has_errors - }; - return edge; - }); - }) + // FIXME - needs refresh + const tmpEdges = loadedWorkflows.map((workflow, count) => { + return workflow.branches.map((branch) => { + const edge = {}; + edge.data = { + id: branch.id, + _id: branch.id, + source: branch.source_id, + target: branch.destination_id, + hasErrors: branch.has_errors, + }; + return edge; + }); + }); - // Make the actual actions - var edges = [] - for (var key in tmpEdges) { - for (var subkey in tmpEdges[key]) { - edges.push(tmpEdges[key][subkey]) - } - } + // Make the actual actions + var edges = []; + for (var key in tmpEdges) { + for (var subkey in tmpEdges[key]) { + edges.push(tmpEdges[key][subkey]); + } + } - const tmpActions = loadedWorkflows.map((workflow, count) => { - return workflow.actions.map(action => { - const node = { position: {x: action.position.x, y: action.position.y}} - node.data = { - id: action["id_"], - _id: action["id_"], - label: action.name, - isStartNode: action["id_"] === loadedWorkflows[count].start, - hasErrors: action.has_errors, - type: "ACTION" - }; - return node; - }); - }) + const tmpActions = loadedWorkflows.map((workflow, count) => { + return workflow.actions.map((action) => { + const node = { + position: { x: action.position.x, y: action.position.y }, + }; + node.data = { + id: action["id_"], + _id: action["id_"], + label: action.name, + isStartNode: action["id_"] === loadedWorkflows[count].start, + hasErrors: action.has_errors, + type: "ACTION", + }; + return node; + }); + }); - // Make the actual actions - var actions = [] - for (key in tmpActions) { - for (subkey in tmpActions[key]) { - actions.push(tmpActions[key][subkey]) - } - } + // Make the actual actions + var actions = []; + for (key in tmpActions) { + for (subkey in tmpActions[key]) { + actions.push(tmpActions[key][subkey]); + } + } - const tmpConditionals = loadedWorkflows.map((workflow, count) => { - return workflow.conditions.map(condition => { - const node = { position: {x: condition.position.x, y: condition.position.y}} - node.data = { - id: condition.id_, - _id: condition.id_, - label: condition.name, - isStartNode: condition["id_"] === loadedWorkflows[count].start, - hasErrors: condition.has_errors, - type: "CONDITION" - }; - return node; - }); - }) + const tmpConditionals = loadedWorkflows.map((workflow, count) => { + return workflow.conditions.map((condition) => { + const node = { + position: { x: condition.position.x, y: condition.position.y }, + }; + node.data = { + id: condition.id_, + _id: condition.id_, + label: condition.name, + isStartNode: condition["id_"] === loadedWorkflows[count].start, + hasErrors: condition.has_errors, + type: "CONDITION", + }; + return node; + }); + }); - // Make the actual actions - var conditionals = [] - for (key in tmpConditionals) { - for (subkey in tmpConditionals[key]) { - conditionals.push(tmpConditionals[key][subkey]) - } - } + // Make the actual actions + var conditionals = []; + for (key in tmpConditionals) { + for (subkey in tmpConditionals[key]) { + conditionals.push(tmpConditionals[key][subkey]); + } + } - const tmpelements = [].concat(edges, actions, conditionals) + const tmpelements = [].concat(edges, actions, conditionals); - if (inputtype !== undefined && inputname !== undefined ) { - // Find startnode, find the movement location and push elements down: - // FIXME - generate stuff - const locationvar = 200 - const baseylocation = 100 - const hookid = "GENERATEME" - const hookname = inputname - for (key in tmpelements) { - var item = tmpelements[key] - - if (item.data.isStartNode) { - // Append a webhook item to the view - var shiftlength = 0 - if (item.position.y-locationvar < baseylocation) { - shiftlength = item.position.y-locationvar+(-baseylocation) - if (shiftlength < 0) { - shiftlength = -(shiftlength) - } - } - - const tmpdata = {data: {id: hookid, label: hookname, type: inputtype}, position: {x: item.position.x, y: item.position.y-locationvar}} - const newedge = {data: {source: hookid, target: item.data.id}} - tmpelements.push(tmpdata) - tmpelements.push(newedge) - - if (shiftlength !== 0) { - const newelements = [] - for (key in tmpelements) { - // isNaN? - if (tmpelements[key].position === undefined || tmpelements[key].position.isNaN) { - newelements.push(tmpelements) - continue - } - - var newitem = tmpelements[key] - newitem.position.y = newitem.position.y+shiftlength - } - } - - break - } - } - } + if (inputtype !== undefined && inputname !== undefined) { + // Find startnode, find the movement location and push elements down: + // FIXME - generate stuff + const locationvar = 200; + const baseylocation = 100; + const hookid = "GENERATEME"; + const hookname = inputname; + for (key in tmpelements) { + var item = tmpelements[key]; - setElements(tmpelements) - } + if (item.data.isStartNode) { + // Append a webhook item to the view + var shiftlength = 0; + if (item.position.y - locationvar < baseylocation) { + shiftlength = item.position.y - locationvar + -baseylocation; + if (shiftlength < 0) { + shiftlength = -shiftlength; + } + } - // Set some extra stuff? - var cy; - const cytmp = cytoscape() - cytmp.fit(null, 50); + const tmpdata = { + data: { id: hookid, label: hookname, type: inputtype }, + position: { x: item.position.x, y: item.position.y - locationvar }, + }; + const newedge = { data: { source: hookid, target: item.data.id } }; + tmpelements.push(tmpdata); + tmpelements.push(newedge); - return ( -
    - cy = cytmp} - elements={elements} - style={{width: '1000px', height: '1000px'}} - stylesheet={cystyle} - boxSelectionEnabled={false} - autounselectify={false} - wheelSensitivity={0.1} - />; -
    - ) -} + if (shiftlength !== 0) { + const newelements = []; + for (key in tmpelements) { + // isNaN? + if ( + tmpelements[key].position === undefined || + tmpelements[key].position.isNaN + ) { + newelements.push(tmpelements); + continue; + } -export default EditWorkflow; + var newitem = tmpelements[key]; + newitem.position.y = newitem.position.y + shiftlength; + } + } + + break; + } + } + } + + setElements(tmpelements); + }; + + // Set some extra stuff? + var cy; + const cytmp = cytoscape(); + cytmp.fit(null, 50); + + return ( +
    + (cy = cytmp)} + elements={elements} + style={{ width: "1000px", height: "1000px" }} + stylesheet={cystyle} + boxSelectionEnabled={false} + autounselectify={false} + wheelSensitivity={0.1} + /> + ; +
    + ); +}; + +export default EditWorkflow; diff --git a/frontend/src/views/ForgotPassword.jsx b/frontend/src/views/ForgotPassword.jsx index 0385579a..e4c95013 100644 --- a/frontend/src/views/ForgotPassword.jsx +++ b/frontend/src/views/ForgotPassword.jsx @@ -1,122 +1,118 @@ /* eslint-disable react/no-multi-comp */ -import React, {useState} from 'react'; +import React, { useState } from "react"; -import TextField from '@material-ui/core/TextField'; -import Button from '@material-ui/core/Button'; -import Paper from '@material-ui/core/Paper'; +import TextField from "@material-ui/core/TextField"; +import Button from "@material-ui/core/Button"; +import Paper from "@material-ui/core/Paper"; const bodyDivStyle = { - margin: "auto", - marginTop: "100px", - width: "500px", -} + margin: "auto", + marginTop: "100px", + width: "500px", +}; +const ForgotPassword = (props) => { + const { globalUrl, isLoaded, isLoggedIn, surfaceColor, inputColor } = props; -const ForgotPassword = props => { - const { globalUrl, isLoaded, isLoggedIn, surfaceColor, inputColor } = props; + const boxStyle = { + paddingLeft: "30px", + paddingRight: "30px", + paddingBottom: "30px", + paddingTop: "30px", + backgroundColor: surfaceColor, + }; + const [username, setUsername] = useState(""); + const [resetInfo, setResetInfo] = useState( + "You will receive an email with instructions shortly." + ); - const boxStyle = { - paddingLeft: "30px", - paddingRight: "30px", - paddingBottom: "30px", - paddingTop: "30px", - backgroundColor: surfaceColor, - } + const handleValidateForm = () => { + return username.length > 3; + }; - const [username, setUsername] = useState("") - const [resetInfo, setResetInfo] = useState("You will receive an email with instructions shortly.") + if (isLoggedIn === true) { + window.location.pathname = "/"; + } - const handleValidateForm = () => { - return username.length > 3 - } + const onSubmit = (e) => { + e.preventDefault(); + // FIXME - add some check here ROFL - if (isLoggedIn === true) { - window.location.pathname = "/" - } + // Just use this one? + var data = { username: username }; + var baseurl = globalUrl; + var url = baseurl + "/api/v1/passwordresetmail"; + fetch(url, { + method: "POST", + body: JSON.stringify(data), + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }) + .then((response) => + response.json().then((responseJson) => { + if (responseJson["success"] === false) { + setResetInfo(responseJson["reason"]); + } + }) + ) + .catch((error) => { + setResetInfo("Error in userdata: " + error); + }); + }; - const onSubmit = (e) => { - e.preventDefault() - // FIXME - add some check here ROFL + const onChangeUser = (e) => { + setUsername(e.target.value); + }; - // Just use this one? - var data = {"username": username} - var baseurl = globalUrl - var url = baseurl+'/api/v1/passwordresetmail'; - fetch(url, { - method: 'POST', - body: JSON.stringify(data), - headers: { - 'Content-Type': 'application/json; charset=utf-8', - }, - }) - .then(response => - response.json().then(responseJson => { - if (responseJson["success"] === false) { - setResetInfo(responseJson["reason"]) - } - }), - ) - .catch(error => { - setResetInfo("Error in userdata: " + error) - }); - } + const data = ( +
    + +
    +

    Password reset

    +
    + +
    +
    + +
    +
    {resetInfo}
    +
    +
    +
    + ); - const onChangeUser = (e) => { - setUsername(e.target.value) - } + const loadedCheck = isLoaded ?
    {data}
    :
    ; - const data = -
    - -
    -

    Password reset

    -
    - -
    -
    - - -
    -
    - {resetInfo} -
    -
    -
    -
    - - const loadedCheck = isLoaded ? -
    - {data} -
    - : -
    -
    - - return ( -
    - {loadedCheck} -
    - ) -} + return
    {loadedCheck}
    ; +}; export default ForgotPassword; diff --git a/frontend/src/views/ForgotPasswordLink.jsx b/frontend/src/views/ForgotPasswordLink.jsx index 33a5b421..8944deed 100644 --- a/frontend/src/views/ForgotPasswordLink.jsx +++ b/frontend/src/views/ForgotPasswordLink.jsx @@ -1,28 +1,28 @@ -import React, {useState, useEffect} from 'react'; +import React, { useState, useEffect } from "react"; -import Paper from '@material-ui/core/Paper'; -import Button from '@material-ui/core/Button'; +import Paper from "@material-ui/core/Paper"; +import Button from "@material-ui/core/Button"; -import TextField from '@material-ui/core/TextField'; +import TextField from "@material-ui/core/TextField"; const bodyDivStyle = { - margin: "auto", - textAlign: "center", - width: "768px", -} + margin: "auto", + textAlign: "center", + width: "768px", +}; const boxStyle = { - flex: "1", - marginLeft: "10px", - marginRight: "10px", - paddingLeft: "30px", - paddingRight: "30px", - paddingBottom: "30px", - paddingTop: "30px", - backgroundColor: "#e8eaf6", - display: "flex", - flexDirection: "column" -} + flex: "1", + marginLeft: "10px", + marginRight: "10px", + paddingLeft: "30px", + paddingRight: "30px", + paddingBottom: "30px", + paddingTop: "30px", + backgroundColor: "#e8eaf6", + display: "flex", + flexDirection: "column", +}; //const tmpdata = { // "username": "frikky", @@ -38,97 +38,99 @@ const boxStyle = { // FIXME - remove tmpdata // FIXME: Use isLoggedIn :) const Settings = (props) => { - const { globalUrl, isLoaded, } = props; + const { globalUrl, isLoaded } = props; - const [newPassword, setNewPassword] = useState(""); - const [newPassword2, setNewPassword2] = useState(""); - const [passwordFormMessage, setPasswordFormMessage] = useState(""); + const [newPassword, setNewPassword] = useState(""); + const [newPassword2, setNewPassword2] = useState(""); + const [passwordFormMessage, setPasswordFormMessage] = useState(""); - const onPasswordChange = () => { - const data = {"newpassword": newPassword, "newpassword2": newPassword2, "reference": props.match.params.key} - const url = globalUrl+'/api/v1/passwordreset'; - 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) { - setPasswordFormMessage(responseJson["reason"]) - } - }), - ) - .catch(error => { - setPasswordFormMessage("Something went wrong.") - }); - } + const onPasswordChange = () => { + const data = { + newpassword: newPassword, + newpassword2: newPassword2, + reference: props.match.params.key, + }; + const url = globalUrl + "/api/v1/passwordreset"; + 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) { + setPasswordFormMessage(responseJson["reason"]); + } + }) + ) + .catch((error) => { + setPasswordFormMessage("Something went wrong."); + }); + }; - // This should "always" have data - useEffect(() => { - }) + // This should "always" have data + useEffect(() => {}); - // Random names for type & autoComplete. Didn't research :^) - const landingpageData = -
    - -

    Password Reset

    -
    - setNewPassword(e.target.value)} - /> - setNewPassword2(e.target.value)} - /> -
    - -

    {passwordFormMessage}

    -
    -
    + // Random names for type & autoComplete. Didn't research :^) + const landingpageData = ( +
    + +

    Password Reset

    +
    + setNewPassword(e.target.value)} + /> + setNewPassword2(e.target.value)} + /> +
    + +

    {passwordFormMessage}

    +
    +
    + ); -const loadedCheck = isLoaded ? -
    - {landingpageData} -
    - : -
    -
    + const loadedCheck = isLoaded ? ( +
    {landingpageData}
    + ) : ( +
    + ); -return( -
    - {loadedCheck} -
    -) -} + return
    {loadedCheck}
    ; +}; export default Settings; diff --git a/frontend/src/views/HandlePayment.js b/frontend/src/views/HandlePayment.js index 9155753e..c19459f6 100644 --- a/frontend/src/views/HandlePayment.js +++ b/frontend/src/views/HandlePayment.js @@ -1,9 +1,7 @@ -import React from 'react'; +import React from "react"; const HandlePayment = () => { - return ( - null - ) -} + return null; +}; -export default HandlePayment +export default HandlePayment; diff --git a/frontend/src/views/Introduction.jsx b/frontend/src/views/Introduction.jsx index 5fb15287..f6c5a43d 100644 --- a/frontend/src/views/Introduction.jsx +++ b/frontend/src/views/Introduction.jsx @@ -1,181 +1,182 @@ -import React, { useEffect, useState } from 'react'; -import { useTheme } from '@material-ui/core/styles'; +import React, { useEffect, useState } from "react"; +import { useTheme } from "@material-ui/core/styles"; -import Grid from '@material-ui/core/Grid'; -import Card from '@material-ui/core/Card'; -import CardActionArea from '@material-ui/core/CardActionArea'; -import CardContent from '@material-ui/core/CardContent'; -import CardHeader from '@material-ui/core/CardHeader'; -import Typography from '@material-ui/core/Typography'; -import Button from '@material-ui/core/Button'; +import Grid from "@material-ui/core/Grid"; +import Card from "@material-ui/core/Card"; +import CardActionArea from "@material-ui/core/CardActionArea"; +import CardContent from "@material-ui/core/CardContent"; +import CardHeader from "@material-ui/core/CardHeader"; +import Typography from "@material-ui/core/Typography"; +import Button from "@material-ui/core/Button"; const Workflows = (props) => { - const { globalUrl, isLoggedIn, isLoaded, } = props - const theme = useTheme(); - const [curView, setCurView] = useState(0); - const [firstrequest, setFirstrequest] = useState(true) - const [selectedItems, setSelectedItems] = useState([]) + const { globalUrl, isLoggedIn, isLoaded } = props; + const theme = useTheme(); + const [curView, setCurView] = useState(0); + const [firstrequest, setFirstrequest] = useState(true); + const [selectedItems, setSelectedItems] = useState([]); - const viewdata1 = [ - { - "title": "General", - "content": "Learn about our ticketing solutions", - "subitems": [ - { - "name": "Search", - "subtitle": "Search for anything, anywhere", - }, - { - "name": "Message", - "subtitle": "Read and send messages", - }, - { - "name": "Parse emails", - "subtitle": "what", - }, - ], - }, - { - "title": "Ticketing", - "subitems": [ - { - "name": "Search", - "subtitle": "Search for anything, anywhere", - }, - { - "name": "Message", - "subtitle": "Read and send messages", - }, - { - "name": "Parse emails", - "subtitle": "what", - }, - ], - }, - { - "title": "Threat intel", - "subitems": [ - { - "name": "Search", - "subtitle": "Search for anything, anywhere", - }, - { - "name": "Message", - "subtitle": "Read and send messages", - }, - { - "name": "Parse emails", - "subtitle": "what", - }, - ], - }, - ] + const viewdata1 = [ + { + title: "General", + content: "Learn about our ticketing solutions", + subitems: [ + { + name: "Search", + subtitle: "Search for anything, anywhere", + }, + { + name: "Message", + subtitle: "Read and send messages", + }, + { + name: "Parse emails", + subtitle: "what", + }, + ], + }, + { + title: "Ticketing", + subitems: [ + { + name: "Search", + subtitle: "Search for anything, anywhere", + }, + { + name: "Message", + subtitle: "Read and send messages", + }, + { + name: "Parse emails", + subtitle: "what", + }, + ], + }, + { + title: "Threat intel", + subitems: [ + { + name: "Search", + subtitle: "Search for anything, anywhere", + }, + { + name: "Message", + subtitle: "Read and send messages", + }, + { + name: "Parse emails", + subtitle: "what", + }, + ], + }, + ]; - if (firstrequest) { - setFirstrequest(false) - if (props.match.params.key) { - console.log("PROPS: ", props.match.params.key) - const viewitem = viewdata1.find(item => item.title.toLowerCase() === props.match.params.key.toLowerCase()) - if (viewitem !== undefined && viewitem !== null) { - setCurView(1) - //setSelectedItem(viewitem) - } - } - } + if (firstrequest) { + setFirstrequest(false); + if (props.match.params.key) { + console.log("PROPS: ", props.match.params.key); + const viewitem = viewdata1.find( + (item) => + item.title.toLowerCase() === props.match.params.key.toLowerCase() + ); + if (viewitem !== undefined && viewitem !== null) { + setCurView(1); + //setSelectedItem(viewitem) + } + } + } - + const cardContentStyle = { + height: "100%", + width: "100%", + padding: 40, + }; - const cardContentStyle = { - height: "100%", - width: "100%", - padding: 40, - } + const outerGridView = { + width: "100%", + marginTop: 15, + }; - const outerGridView = { - width: "100%", - marginTop: 15, - } + const paperStyle = { + height: 300, + color: "white", + backgroundColor: theme.palette.surfaceColor, + color: "white", + cursor: "pointer", + display: "flex", + textAlign: "center", + }; - const paperStyle = { - height: 300, - color: "white", - backgroundColor: theme.palette.surfaceColor, - color: "white", - cursor: "pointer", - display: "flex", - textAlign: "center", - } + const HandleSelection = (data) => { + const [selected, setSelected] = useState(false); - const HandleSelection = (data) => { - const [selected, setSelected] = useState(false); + var baseStyle = JSON.parse(JSON.stringify(paperStyle)); + if (selected) { + baseStyle.backgroundColor = "white"; + baseStyle.color = "black"; + } - var baseStyle = JSON.parse(JSON.stringify(paperStyle)) - if (selected) { - baseStyle.backgroundColor = "white" - baseStyle.color = "black" - } + return ( + { + console.log(selectedItems); + if (selected) { + const index = selectedItems.findIndex( + (item) => item.title === data.title + ); + if (index >= 0) { + selectedItems.splice(index, 1); + setSelectedItems(selectedItems); + } + } else { + selectedItems.push(data); + setSelectedItems(selectedItems); + } - return ( - { - console.log(selectedItems) - if (selected) { - const index = selectedItems.findIndex(item => item.title === data.title) - if (index >= 0) { - selectedItems.splice(index, 1) - setSelectedItems(selectedItems) - } - } else { - selectedItems.push(data) - setSelectedItems(selectedItems) - } + setSelected(!selected); - setSelected(!selected) - - //setCurView(1) - //setSelectedItem(data) - //window.location.pathname += "/"+data.title.toLowerCase() - }}> - - - - - {data.title} - - - - - - ) - } + //setCurView(1) + //setSelectedItem(data) + //window.location.pathname += "/"+data.title.toLowerCase() + }} + > + + + + {data.title} + + + + + ); + }; - const view1 = curView === 0 ? -
    - - What are you interested in? - - - {viewdata1.map(data => { - return ( - HandleSelection(data) - ) - })} - - {/* + const view1 = + curView === 0 ? ( +
    + What are you interested in? + + {viewdata1.map((data) => { + return HandleSelection(data); + })} + + {/* */} -
    - : null +
    + ) : null; - const view2 = curView === 1 ? -
    - - Step 2. - - {/* + const view2 = + curView === 1 ? ( +
    + Step 2. + {/* {selectedItem.subitems === undefined ? null : selectedItem.subitems.map(data => { @@ -198,20 +199,17 @@ const Workflows = (props) => { })} */} -
    - : null +
    + ) : null; - const baseView = -
    - {view1} - {view2} -
    + const baseView = ( +
    + {view1} + {view2} +
    + ); - return ( -
    - {baseView} -
    - ) -} + return
    {baseView}
    ; +}; -export default Workflows +export default Workflows; diff --git a/frontend/src/views/Landingpage.jsx b/frontend/src/views/Landingpage.jsx index 1dd2f912..ed02060f 100644 --- a/frontend/src/views/Landingpage.jsx +++ b/frontend/src/views/Landingpage.jsx @@ -1,179 +1,205 @@ -import React, {} from 'react'; +import React from "react"; -import Paper from '@material-ui/core/Paper'; -import Button from '@material-ui/core/Button'; -import Divider from '@material-ui/core/Divider'; -import {BrowserView, MobileView} from "react-device-detect"; -import ScheduleIcon from '@material-ui/icons/Schedule'; -import Web from '@material-ui/icons/Web'; -import AccountTree from '@material-ui/icons/AccountTree'; +import Paper from "@material-ui/core/Paper"; +import Button from "@material-ui/core/Button"; +import Divider from "@material-ui/core/Divider"; +import { BrowserView, MobileView } from "react-device-detect"; +import ScheduleIcon from "@material-ui/icons/Schedule"; +import Web from "@material-ui/icons/Web"; +import AccountTree from "@material-ui/icons/AccountTree"; const bodyDivStyle = { - margin: "auto", - marginTop: "75px", - textAlign: "center", - width: "1100px", -} + margin: "auto", + marginTop: "75px", + textAlign: "center", + width: "1100px", +}; -const surfaceColor = "#27292D" +const surfaceColor = "#27292D"; const boxStyle = { - flex: "1", - marginLeft: "10px", - marginRight: "10px", - height: "400px", - //backgroundColor: "#e8eaf6", - backgroundColor: surfaceColor, - textAlign: "center", - display: "flex", - flexDirection: "column", -} + flex: "1", + marginLeft: "10px", + marginRight: "10px", + height: "400px", + //backgroundColor: "#e8eaf6", + backgroundColor: surfaceColor, + textAlign: "center", + display: "flex", + flexDirection: "column", +}; const bodyTextStyle = { - color: "#ffffff", -} + color: "#ffffff", +}; const hrefStyle = { - color: "black", - textDecoration: "none", -} - + color: "black", + textDecoration: "none", +}; // Should be different if logged in :| const LandingPage = (props) => { - const { isLoaded} = props; + const { isLoaded } = props; - const textColor = "#8899A6" - const iconColor = "#1DA1F2" - const iconSize = "8em" - const GridLayout = (header, description, link, icon) => { - return ( - - -
    -

    {header}

    -
    - -
    - {description} -
    -
    - {icon} -
    - -
    -
    - Learn more -
    -
    -
    -
    - ) - } + const textColor = "#8899A6"; + const iconColor = "#1DA1F2"; + const iconSize = "8em"; + const GridLayout = (header, description, link, icon) => { + return ( + + +
    +

    {header}

    +
    + +
    + {description} +
    +
    {icon}
    + +
    +
    Learn more
    +
    +
    +
    + ); + }; - const listitems = [ - GridLayout("Simple integrations", "Easily use others' or create your own integration", "/docs/apps", ), - GridLayout("Workflows", "Access the power of automation within minutes, whether its on premise or in the cloud", "/docs/workflows", ), - GridLayout("Realtime actions", "Beat the clock by leveraging our realtime triggers", "/docs/triggers", ), - ] + const listitems = [ + GridLayout( + "Simple integrations", + "Easily use others' or create your own integration", + "/docs/apps", + + ), + GridLayout( + "Workflows", + "Access the power of automation within minutes, whether its on premise or in the cloud", + "/docs/workflows", + + ), + GridLayout( + "Realtime actions", + "Beat the clock by leveraging our realtime triggers", + "/docs/triggers", + + ), + ]; - // The actual landing page - // {"logo"} - const landingpageDataBrowser = -
    -
    -

    Shuffle

    -

    A general automation solution for Infosec and IT Professionals

    -
    - - - - - - -
    - {listitems.map(item => { - return ( -
    - {item} -
    - ) - })} -
    -
    + // The actual landing page + // {"logo"} + const landingpageDataBrowser = ( +
    +
    +

    Shuffle

    +

    + A general automation solution for Infosec and IT Professionals +

    +
    + + + + + + +
    + {listitems.map((item) => { + return
    {item}
    ; + })} +
    +
    + ); - const landingpageDataMobile = -
    -
    -

    Shuffle

    -

    A general automation solution for Infosec and IT Professionals

    - - - -
    -
    -
    - {listitems[0]} -
    -
    - {listitems[1]} -
    -
    - {listitems[2]} -
    - -
    -
    + const landingpageDataMobile = ( +
    +
    +

    Shuffle

    +

    A general automation solution for Infosec and IT Professionals

    + + + +
    +
    +
    {listitems[0]}
    +
    {listitems[1]}
    +
    + {listitems[2]} +
    + +
    +
    + ); + // Reroute if the user is logged in + // const landingSite = isLoggedIn ? :
    {landingpageData}
    + const landingSite =
    {landingpageDataBrowser}
    ; - // Reroute if the user is logged in - // const landingSite = isLoggedIn ? :
    {landingpageData}
    - const landingSite =
    {landingpageDataBrowser}
    + const loadedCheck = isLoaded ? ( +
    + {landingSite} + {landingpageDataMobile} +
    + ) : ( +
    + ); - const loadedCheck = isLoaded ? -
    - - {landingSite} - - - {landingpageDataMobile} - -
    - : -
    -
    - - return( -
    - {loadedCheck} -
    - ) -} + return
    {loadedCheck}
    ; +}; export default LandingPage; diff --git a/frontend/src/views/LandingpageLoggedin.jsx b/frontend/src/views/LandingpageLoggedin.jsx index 178c5930..c0abb59d 100644 --- a/frontend/src/views/LandingpageLoggedin.jsx +++ b/frontend/src/views/LandingpageLoggedin.jsx @@ -1,21 +1,16 @@ -import React, {} from 'react'; +import React from "react"; const bodyDivStyle = { - transform: "translate(-50%, -50%)", - top: "50%", - left: "50%", - position: "absolute", - width: "500px", - color: "white", -} + transform: "translate(-50%, -50%)", + top: "50%", + left: "50%", + position: "absolute", + width: "500px", + color: "white", +}; // Should be different if logged in :| const LandingPageLoggedin = (props) => { - - return( -
    - TMP landingpage when logged in -
    - ) -} + return
    TMP landingpage when logged in
    ; +}; export default LandingPageLoggedin; diff --git a/frontend/src/views/LandingpageNew.jsx b/frontend/src/views/LandingpageNew.jsx index 4ff6f25e..252d389c 100644 --- a/frontend/src/views/LandingpageNew.jsx +++ b/frontend/src/views/LandingpageNew.jsx @@ -1,349 +1,529 @@ -import React, {useState } from 'react'; +import React, { useState } from "react"; -import Paper from '@material-ui/core/Paper'; -import Card from '@material-ui/core/Card'; -import CardActionArea from '@material-ui/core/CardActionArea'; -import CardMedia from '@material-ui/core/CardMedia'; -import CardContent from '@material-ui/core/CardContent'; -import CardActions from '@material-ui/core/CardActions'; -import Button from '@material-ui/core/Button'; -import Divider from '@material-ui/core/Divider'; -import Grid from '@material-ui/core/Grid'; -import {BrowserView, MobileView} from "react-device-detect"; +import Paper from "@material-ui/core/Paper"; +import Card from "@material-ui/core/Card"; +import CardActionArea from "@material-ui/core/CardActionArea"; +import CardMedia from "@material-ui/core/CardMedia"; +import CardContent from "@material-ui/core/CardContent"; +import CardActions from "@material-ui/core/CardActions"; +import Button from "@material-ui/core/Button"; +import Divider from "@material-ui/core/Divider"; +import Grid from "@material-ui/core/Grid"; +import { BrowserView, MobileView } from "react-device-detect"; -import ScheduleIcon from '@material-ui/icons/Schedule'; -import Web from '@material-ui/icons/Web'; -import AccountTree from '@material-ui/icons/AccountTree'; -import InfoIcon from '@material-ui/icons/Info'; -import ArrowForwardIcon from '@material-ui/icons/ArrowForward'; -import CreateIcon from '@material-ui/icons/Create'; +import ScheduleIcon from "@material-ui/icons/Schedule"; +import Web from "@material-ui/icons/Web"; +import AccountTree from "@material-ui/icons/AccountTree"; +import InfoIcon from "@material-ui/icons/Info"; +import ArrowForwardIcon from "@material-ui/icons/ArrowForward"; +import CreateIcon from "@material-ui/icons/Create"; const bodyDivStyle = { - margin: "auto", -} + margin: "auto", +}; -const surfaceColor = "#27292D" +const surfaceColor = "#27292D"; const boxStyle = { - flex: "1", - marginLeft: "10px", - marginRight: "10px", - height: "400px", - //backgroundColor: "#e8eaf6", - backgroundColor: surfaceColor, - textAlign: "center", - display: "flex", - flexDirection: "column", -} + flex: "1", + marginLeft: "10px", + marginRight: "10px", + height: "400px", + //backgroundColor: "#e8eaf6", + backgroundColor: surfaceColor, + textAlign: "center", + display: "flex", + flexDirection: "column", +}; const bodyTextStyle = { - color: "#ffffff", -} + color: "#ffffff", +}; const hrefStyle = { - color: "inherit", - textDecoration: "none", -} - + color: "inherit", + textDecoration: "none", +}; // Should be different if logged in :| const LandingPage = (props) => { - const { isLoaded} = props; + const { isLoaded } = props; - const textColor = "#8899A6" - const iconColor = "#1DA1F2" - const iconSize = "8em" + const textColor = "#8899A6"; + const iconColor = "#1DA1F2"; + const iconSize = "8em"; - const GridLayout = (header, description, link, icon) => { - return ( - - -
    -

    {header}

    -
    - -
    - {description} -
    -
    - {icon} -
    - -
    -
    - Learn more -
    -
    -
    -
    - ) - } + const GridLayout = (header, description, link, icon) => { + return ( + + +
    +

    {header}

    +
    + +
    + {description} +
    +
    {icon}
    + +
    +
    Learn more
    +
    +
    +
    + ); + }; - const listitems = [ - GridLayout("Simple integrations", "Easily use others' or create your own integration", "/docs/features", ), - GridLayout("Workflows", "Access the power of automation within minutes, whether its on premise or in the cloud", "/docs/features", ), - GridLayout("Realtime actions", "Beat the clock by leveraging our realtime triggers", "/docs/features", ), - ] + const listitems = [ + GridLayout( + "Simple integrations", + "Easily use others' or create your own integration", + "/docs/features", + + ), + GridLayout( + "Workflows", + "Access the power of automation within minutes, whether its on premise or in the cloud", + "/docs/features", + + ), + GridLayout( + "Realtime actions", + "Beat the clock by leveraging our realtime triggers", + "/docs/features", + + ), + ]; - // The actual landing page - // {"logo"} - //We start by understanding your unique environment to help identify the right thing to automate. - const secondaryColor = "rgba(167,46,87,1)" - const primaryColor = "rgba(25, 35, 94, 1)" + // The actual landing page + // {"logo"} + //We start by understanding your unique environment to help identify the right thing to automate. + const secondaryColor = "rgba(167,46,87,1)"; + const primaryColor = "rgba(25, 35, 94, 1)"; - const paperStyle = { - flex: 1, - backgroundColor: "inherit", - cursor: "pointer", - } - - const secondaryItemList = [ - { - primaryText: "No time to waste", - secondaryText: "Bring all your applications into a single view, and make them all work together flawlessly!", - image: "/images/time.jpg", - }, { - primaryText: "Get a better overview", - secondaryText: "Don't know what's happening? We'll help you track and act on your most valuable KPI's!", - image: "/images/overview.jpg", - }, { - primaryText: "Conquer your tasks", - secondaryText: "Get access to powerful tools and pre-made workflows to help you crush your teams daily tasks!", - image: "/images/burnout.jpg", - }, - ] - const [image, setImage] = useState(secondaryItemList[0].image); + const paperStyle = { + flex: 1, + backgroundColor: "inherit", + cursor: "pointer", + }; - const landingpageDataBrowser = -
    -
    - -
    -
    - Shuffle -
    -
    - INFORMATION
    OVERLOAD
    -
    -
    - Everyone run into the same fundamental operational problems. Mailbox chaos, tickets getting out of hand and a constant feeling of being overwhelmed. The good news?
    Shuffle solves them.
    -
    - - - -
    -
    -
    -
    -
    - Automation is just the beginning -
    -
    -
    - {secondaryItemList.map((data, index) => { - const color = image === data.image ? "rgba(255,255,255,1)" : "rgba(255,255,255,0.4)" - return ( -
    setImage(data.image)}> - {data.primaryText} -
    - {data.secondaryText} -
    -
    - ) - })} -
    -
    -
    - -
    -
    -
    -
    - -
    -
    - Learn more about the benefits of Shuffle -
    - -
    -
    -
    -
    -
    - Focus on the work that matters to you -
    -
    - Menial tasks, scattered content, constant copy pasting, waste of talent - there's a smarter way to work. -
    -
    - {window.location.pathname = "/docs/features"}} style={{flex: 1, margin: 10, textAlign: "center"}}> - - - -

    Premade playbooks

    -

    Get your automation done with minimal effort

    -
    -
    - -
    - {window.location.pathname = "/docs/features"}} style={{flex: 1, margin: 10, textAlign: "center"}}> - - - -

    Open frameworks

    -

    Mitre Att&ck, OpenAPI and more!

    -
    -
    - -
    - {window.location.pathname = "/docs/features"}} style={{flex: 1, margin: 10, textAlign: "center"}}> - - - -

    Hundreds of integrations

    -

    Quickly integrate your software applications

    -
    -
    - -
    - {window.location.pathname = "/docs/features"}}> - - - -

    Automated compliance

    -

    Stuck with compliance needs you can't meet?

    -
    -
    - -
    -
    -
    -
    + const secondaryItemList = [ + { + primaryText: "No time to waste", + secondaryText: + "Bring all your applications into a single view, and make them all work together flawlessly!", + image: "/images/time.jpg", + }, + { + primaryText: "Get a better overview", + secondaryText: + "Don't know what's happening? We'll help you track and act on your most valuable KPI's!", + image: "/images/overview.jpg", + }, + { + primaryText: "Conquer your tasks", + secondaryText: + "Get access to powerful tools and pre-made workflows to help you crush your teams daily tasks!", + image: "/images/burnout.jpg", + }, + ]; + const [image, setImage] = useState(secondaryItemList[0].image); - const landingpageDataMobile = -
    -
    -

    Shuffle

    -

    A general automation solution for Infosec and IT Professionals

    - - - -
    -
    -
    - {listitems[0]} -
    -
    - {listitems[1]} -
    -
    - {listitems[2]} -
    - -
    -
    + const landingpageDataBrowser = ( +
    +
    + +
    +
    Shuffle
    +
    + INFORMATION
    OVERLOAD
    +
    +
    + Everyone run into the same fundamental operational problems. Mailbox + chaos, tickets getting out of hand and a constant feeling of being + overwhelmed. The good news?{" "} +
    + Shuffle solves them. +
    +
    + + + +
    +
    +
    +
    +
    + Automation is just the beginning +
    +
    +
    + {secondaryItemList.map((data, index) => { + const color = + image === data.image + ? "rgba(255,255,255,1)" + : "rgba(255,255,255,0.4)"; + return ( +
    setImage(data.image)} + > + {data.primaryText} +
    + {data.secondaryText} +
    +
    + ); + })} +
    +
    +
    + +
    +
    +
    +
    + +
    +
    + Learn more about the benefits of Shuffle +
    + +
    +
    +
    +
    +
    + Focus on the work that matters to you +
    +
    + Menial tasks, scattered content, constant copy pasting, waste of + talent - there's a smarter way to work. +
    +
    + { + window.location.pathname = "/docs/features"; + }} + style={{ flex: 1, margin: 10, textAlign: "center" }} + > + + + +

    Premade playbooks

    +

    Get your automation done with minimal effort

    +
    +
    + +
    + { + window.location.pathname = "/docs/features"; + }} + style={{ flex: 1, margin: 10, textAlign: "center" }} + > + + + +

    Open frameworks

    +

    Mitre Att&ck, OpenAPI and more!

    +
    +
    + +
    + { + window.location.pathname = "/docs/features"; + }} + style={{ flex: 1, margin: 10, textAlign: "center" }} + > + + + +

    Hundreds of integrations

    +

    Quickly integrate your software applications

    +
    +
    + +
    + { + window.location.pathname = "/docs/features"; + }} + > + + + +

    Automated compliance

    +

    Stuck with compliance needs you can't meet?

    +
    +
    + +
    +
    +
    +
    + ); + const landingpageDataMobile = ( +
    +
    +

    Shuffle

    +

    A general automation solution for Infosec and IT Professionals

    + + + +
    +
    +
    {listitems[0]}
    +
    {listitems[1]}
    +
    + {listitems[2]} +
    + +
    +
    + ); - // Reroute if the user is logged in - // const landingSite = isLoggedIn ? :
    {landingpageData}
    - const landingSite =
    {landingpageDataBrowser}
    + // Reroute if the user is logged in + // const landingSite = isLoggedIn ? :
    {landingpageData}
    + const landingSite =
    {landingpageDataBrowser}
    ; - const loadedCheck = isLoaded ? -
    - - {landingSite} - - - {landingpageDataMobile} - -
    - : -
    -
    + const loadedCheck = isLoaded ? ( +
    + {landingSite} + {landingpageDataMobile} +
    + ) : ( +
    + ); - return( -
    - {loadedCheck} -
    - ) -} + return
    {loadedCheck}
    ; +}; export default LandingPage; diff --git a/frontend/src/views/LoginPage.jsx b/frontend/src/views/LoginPage.jsx index 6a6529e3..2a51187e 100644 --- a/frontend/src/views/LoginPage.jsx +++ b/frontend/src/views/LoginPage.jsx @@ -1,391 +1,490 @@ /* eslint-disable react/no-multi-comp */ -import React, { useState } from 'react'; -import { makeStyles } from '@material-ui/styles'; -import { useInterval } from 'react-powerhooks'; - -import {CircularProgress, TextField, Button, Paper, Typography} from '@material-ui/core' -import { useTheme } from '@material-ui/core/styles'; +import React, { useState } from "react"; +import { makeStyles } from "@material-ui/styles"; +import { useInterval } from "react-powerhooks"; +import { + CircularProgress, + TextField, + Button, + Paper, + Typography, +} from "@material-ui/core"; +import { useTheme } from "@material-ui/core/styles"; const hrefStyle = { - color: "white", - textDecoration: "none" -} + color: "white", + textDecoration: "none", +}; const bodyDivStyle = { - margin: "auto", - marginTop: 150, - width: "500px", -} - + margin: "auto", + marginTop: 150, + width: "500px", +}; const useStyles = makeStyles({ - notchedOutline: { - borderColor: "#f85a3e !important" - }, + notchedOutline: { + borderColor: "#f85a3e !important", + }, }); -const LoginDialog = props => { - const theme = useTheme(); +const LoginDialog = (props) => { + const theme = useTheme(); - const { globalUrl, isLoaded, isLoggedIn, setIsLoggedIn, setCookie, register, checkLogin } = props; - const [username, setUsername] = useState(""); - const [password, setPassword] = useState(""); - const [firstRequest, setFirstRequest] = useState(true); + const { + globalUrl, + isLoaded, + isLoggedIn, + setIsLoggedIn, + setCookie, + register, + checkLogin, + } = props; + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [firstRequest, setFirstRequest] = useState(true); const [loginLoading, setLoginLoading] = useState(false); const [loginViewLoading, setLoginViewLoading] = useState(false); - const [ssoUrl, setSSOUrl] = useState("") + const [ssoUrl, setSSOUrl] = useState(""); const [MFAField, setMFAField] = useState(false); const [MFAValue, setMFAValue] = useState(""); - // Used to swap from login to register. True = login, false = register + // Used to swap from login to register. True = login, false = register - 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.sso_url !== undefined && responseJson.sso_url !== null) { - setSSOUrl(responseJson.sso_url) - } + 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.sso_url !== undefined && + responseJson.sso_url !== null + ) { + setSSOUrl(responseJson.sso_url); + } - if (loginViewLoading) { - setLoginViewLoading(false) - checkLogin() - stop() + if (loginViewLoading) { + setLoginViewLoading(false); + checkLogin(); + stop(); - if (responseJson.reason !== undefined && responseJson.reason !== null) { - setLoginInfo(responseJson.reason) - } - } + if ( + responseJson.reason !== undefined && + responseJson.reason !== null + ) { + setLoginInfo(responseJson.reason); + } + } - if (responseJson.reason === "stay") { - window.location.pathname = "/adminsetup" - } - } - }), - ) - .catch(error => { - if (!loginViewLoading) { - setLoginViewLoading(true) - start() - } - }) - } + if (responseJson.reason === "stay") { + window.location.pathname = "/adminsetup"; + } + } + }) + ) + .catch((error) => { + if (!loginViewLoading) { + setLoginViewLoading(true); + start(); + } + }); + }; - const { start, stop } = useInterval({ - duration: 3000, - startImmediate: false, - callback: () => { - checkAdmin() - } - }) + const { start, stop } = useInterval({ + duration: 3000, + startImmediate: false, + callback: () => { + checkAdmin(); + }, + }); - if (firstRequest) { - setFirstRequest(false) - checkAdmin() - } + if (firstRequest) { + setFirstRequest(false); + checkAdmin(); + } - const onSubmit = (e) => { - setLoginLoading(true) - e.preventDefault() - setLoginInfo("") - // FIXME - add some check here ROFL + const onSubmit = (e) => { + setLoginLoading(true); + e.preventDefault(); + setLoginInfo(""); + // FIXME - add some check here ROFL - // Just use this one? - var data = {"username": username, "password": password} - if (MFAValue !== undefined && MFAValue !== null && MFAValue.length > 0) { - data["mfa_code"] = MFAValue - } + // Just use this one? + var data = { username: username, password: password }; + if (MFAValue !== undefined && MFAValue !== null && MFAValue.length > 0) { + data["mfa_code"] = MFAValue; + } - var baseurl = globalUrl - if (register) { - var url = baseurl + '/api/v1/users/login'; - 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 => { - setLoginLoading(false) - if (responseJson["success"] === false) { - setLoginInfo(responseJson["reason"]) - } else { - if (responseJson["reason"] === "MFA_REDIRECT") { - setLoginInfo("MFA required. Please the 6-digit code from your authenticator") - setMFAField(true) - return - } + var baseurl = globalUrl; + if (register) { + var url = baseurl + "/api/v1/users/login"; + 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) => { + setLoginLoading(false); + if (responseJson["success"] === false) { + setLoginInfo(responseJson["reason"]); + } else { + if (responseJson["reason"] === "MFA_REDIRECT") { + setLoginInfo( + "MFA required. Please the 6-digit code from your authenticator" + ); + setMFAField(true); + return; + } - setLoginInfo("Successful login, rerouting") - for (var key in responseJson["cookies"]) { - setCookie(responseJson["cookies"][key].key, responseJson["cookies"][key].value, { path: "/" }) - } + setLoginInfo("Successful login, rerouting"); + for (var key in responseJson["cookies"]) { + setCookie( + responseJson["cookies"][key].key, + responseJson["cookies"][key].value, + { path: "/" } + ); + } - setIsLoggedIn(true) + setIsLoggedIn(true); - window.location.pathname = "/workflows" - } - }), - ) - .catch(error => { - setLoginLoading(false) - setLoginInfo("Error logging in: " + error) - }); - } else { - url = baseurl + '/api/v1/users/register'; - fetch(url, { - method: 'POST', - body: JSON.stringify(data), - headers: { - 'Content-Type': 'application/json', - }, - }) - .then(response => - response.json().then(responseJson => { - if (responseJson["success"] === false) { - setLoginInfo(responseJson["reason"]) - } else { - setLoginInfo("Successful register!") - } - }), - ) - .catch(error => { - setLoginInfo("Error in from backend: ", error) - }); - } - } + window.location.pathname = "/workflows"; + } + }) + ) + .catch((error) => { + setLoginLoading(false); + setLoginInfo("Error logging in: " + error); + }); + } else { + url = baseurl + "/api/v1/users/register"; + fetch(url, { + method: "POST", + body: JSON.stringify(data), + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => + response.json().then((responseJson) => { + if (responseJson["success"] === false) { + setLoginInfo(responseJson["reason"]); + } else { + setLoginInfo("Successful register!"); + } + }) + ) + .catch((error) => { + setLoginInfo("Error in from backend: ", 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
    - const imgsize = 100 - const basedata = -
    - -
    - -
    - {loginViewLoading ? -
    - - Waiting for the Shuffle database to become available. This may take up to a minute. - + //var loginChange = register ? (

    Want to register? Click here.

    ) : (

    Go back to login? Click here.

    ); + var formtitle = register ?
    Login
    :
    Register
    ; + const imgsize = 100; + const basedata = ( +
    + +
    + +
    + {loginViewLoading ? ( +
    + + Waiting for the Shuffle database to become available. This may + take up to a minute. + - {loginInfo === undefined || loginInfo === null || loginInfo.length === 0 ? - null - : -
    - Response: {loginInfo} -
    - } - + {loginInfo === undefined || + loginInfo === null || + loginInfo.length === 0 ? null : ( +
    Response: {loginInfo}
    + )} + + + + + Are you sure Shuffle is{" "} + + installed correctly + + ? + + + + 1. Make sure shuffle-database folder has correct access:{" "} +
    +
    + sudo chown 1000:1000 -R shuffle-database +
    - - - Are you sure Shuffle is installed correctly? - - - 1. Make sure shuffle-database folder has correct access:

    - sudo chown 1000:1000 -R shuffle-database -
    + + 2. Restart docker-compose: +
    +
    + sudo docker-compose restart +
    +
    + + Need help?{" "} + + Join the Discord! + + +
    + ) : ( +
    +

    {formtitle}

    + Username +
    + +
    + Password +
    + +
    + {MFAField === true ? ( +
    + 5-factor code + { + setMFAValue(event.target.value); + }} + /> +
    + ) : null} +
    + +
    +
    {loginInfo}
    + {ssoUrl !== undefined && ssoUrl !== null && ssoUrl.length > 0 ? ( +
    + Or +
    + +
    +
    + ) : null} +
    + )} +
    +
    + ); - - 2. Restart docker-compose:

    - sudo docker-compose restart -
    - - - Need help? Join the Discord! - -
    - : -
    -

    {formtitle}

    - Username -
    - -
    - Password -
    - -
    - {MFAField === true ? -
    - 5-factor code - { - setMFAValue(event.target.value) - }} - /> -
    - : null} -
    - -
    -
    - {loginInfo} -
    - {ssoUrl !== undefined && ssoUrl !== null && ssoUrl.length > 0 ? -
    - - Or - -
    - -
    -
    - : null} -
    - } -
    -
    + const loadedCheck = isLoaded ?
    {basedata}
    :
    ; - const loadedCheck = isLoaded ? -
    - {basedata} -
    - : -
    -
    - - return ( -
    - {loadedCheck} -
    - ) -} + return
    {loadedCheck}
    ; +}; export default LoginDialog; diff --git a/frontend/src/views/MyView.jsx b/frontend/src/views/MyView.jsx index 70927e69..3420a7d8 100644 --- a/frontend/src/views/MyView.jsx +++ b/frontend/src/views/MyView.jsx @@ -1,636 +1,735 @@ -import React, { useEffect} from 'react'; -import { useInterval } from 'react-powerhooks'; +import React, { useEffect } from "react"; +import { useInterval } from "react-powerhooks"; -import Grid from '@material-ui/core/Grid'; -import Paper from '@material-ui/core/Paper'; -import Tooltip from '@material-ui/core/Tooltip'; -import Divider from '@material-ui/core/Divider'; -import Button from '@material-ui/core/Button'; -import TextField from '@material-ui/core/TextField'; -import FormControl from '@material-ui/core/FormControl'; -import IconButton from '@material-ui/core/IconButton'; -import Menu from '@material-ui/core/Menu'; -import MenuItem from '@material-ui/core/MenuItem'; -import FormControlLabel from '@material-ui/core/FormControlLabel'; -import Chip from '@material-ui/core/Chip'; -import Switch from '@material-ui/core/Switch'; -import Typography from '@material-ui/core/Typography'; -import Zoom from '@material-ui/core/Zoom'; +import Grid from "@material-ui/core/Grid"; +import Paper from "@material-ui/core/Paper"; +import Tooltip from "@material-ui/core/Tooltip"; +import Divider from "@material-ui/core/Divider"; +import Button from "@material-ui/core/Button"; +import TextField from "@material-ui/core/TextField"; +import FormControl from "@material-ui/core/FormControl"; +import IconButton from "@material-ui/core/IconButton"; +import Menu from "@material-ui/core/Menu"; +import MenuItem from "@material-ui/core/MenuItem"; +import FormControlLabel from "@material-ui/core/FormControlLabel"; +import Chip from "@material-ui/core/Chip"; +import Switch from "@material-ui/core/Switch"; +import Typography from "@material-ui/core/Typography"; +import Zoom from "@material-ui/core/Zoom"; -import CircularProgress from '@material-ui/core/CircularProgress'; -import CachedIcon from '@material-ui/icons/Cached'; -import GetAppIcon from '@material-ui/icons/GetApp'; -import AppsIcon from '@material-ui/icons/Apps'; -import EditIcon from '@material-ui/icons/Edit'; -import MoreVertIcon from '@material-ui/icons/MoreVert'; -import PlayArrowIcon from '@material-ui/icons/PlayArrow'; -import AddIcon from '@material-ui/icons/Add'; -import PublishIcon from '@material-ui/icons/Publish'; +import CircularProgress from "@material-ui/core/CircularProgress"; +import CachedIcon from "@material-ui/icons/Cached"; +import GetAppIcon from "@material-ui/icons/GetApp"; +import AppsIcon from "@material-ui/icons/Apps"; +import EditIcon from "@material-ui/icons/Edit"; +import MoreVertIcon from "@material-ui/icons/MoreVert"; +import PlayArrowIcon from "@material-ui/icons/PlayArrow"; +import AddIcon from "@material-ui/icons/Add"; +import PublishIcon from "@material-ui/icons/Publish"; //import JSONPretty from 'react-json-pretty'; //import JSONPrettyMon from 'react-json-pretty/dist/monikai' -import ReactJson from 'react-json-view' +import ReactJson from "react-json-view"; -import {Link} from 'react-router-dom'; +import { Link } from "react-router-dom"; import { useAlert } from "react-alert"; -import ChipInput from 'material-ui-chip-input' +import ChipInput from "material-ui-chip-input"; -import Dialog from '@material-ui/core/Dialog'; -import DialogTitle from '@material-ui/core/DialogTitle'; -import DialogActions from '@material-ui/core/DialogActions'; -import DialogContent from '@material-ui/core/DialogContent'; -import CloudDownloadIcon from '@material-ui/icons/CloudDownload'; -import BubbleChartIcon from '@material-ui/icons/BubbleChart'; -import RestoreIcon from '@material-ui/icons/Restore'; +import Dialog from "@material-ui/core/Dialog"; +import DialogTitle from "@material-ui/core/DialogTitle"; +import DialogActions from "@material-ui/core/DialogActions"; +import DialogContent from "@material-ui/core/DialogContent"; +import CloudDownloadIcon from "@material-ui/icons/CloudDownload"; +import BubbleChartIcon from "@material-ui/icons/BubbleChart"; +import RestoreIcon from "@material-ui/icons/Restore"; -import mobileImage from '../assets/img/mobile.svg'; -import bagImage from '../assets/img/bag.svg'; -import bookImage from '../assets/img/book.svg'; +import mobileImage from "../assets/img/mobile.svg"; +import bagImage from "../assets/img/bag.svg"; +import bookImage from "../assets/img/book.svg"; import { - DataGrid, - GridToolbarContainer, - GridDensitySelector, - GridToolbar, - } from '@material-ui/data-grid'; -import { makeStyles } from '@material-ui/core/styles'; + DataGrid, + GridToolbarContainer, + GridDensitySelector, + GridToolbar, +} from "@material-ui/data-grid"; +import { makeStyles } from "@material-ui/core/styles"; -import ListIcon from '@material-ui/icons/List'; -import GridOnIcon from '@material-ui/icons/GridOn'; +import ListIcon from "@material-ui/icons/List"; +import GridOnIcon from "@material-ui/icons/GridOn"; -const inputColor = "#383B40" -const surfaceColor = "#27292D" +const inputColor = "#383B40"; +const surfaceColor = "#27292D"; const flexContainerStyle = { - display: "flex", - flexDirection: "row", - justifyContent: "left", - alignContent: "space-between", -} + display: "flex", + flexDirection: "row", + justifyContent: "left", + alignContent: "space-between", +}; const flexBoxStyle = { - height: 125, - borderRadius: 4, - boxSizing: "border-box", - letterSpacing: "0.4px", - color: "#D6791E", - margin: 10, - flex: 1, -} + height: 125, + borderRadius: 4, + boxSizing: "border-box", + letterSpacing: "0.4px", + color: "#D6791E", + margin: 10, + flex: 1, +}; //const activeWorkflowStyle = {backgroundColor: "#FFF5EE"} //const notificationStyle = {backgroundColor: "#E5F9FF"} //const activeWorkflowStyle = {backgroundColor: "#3d3f43"} -const availableWorkflowStyle = {backgroundColor: "#3d3f43"} -const notificationStyle = {backgroundColor: "#3d3f43"} -const activeWorkflowStyle = {backgroundColor: "#3d3f43"} +const availableWorkflowStyle = { backgroundColor: "#3d3f43" }; +const notificationStyle = { backgroundColor: "#3d3f43" }; +const activeWorkflowStyle = { backgroundColor: "#3d3f43" }; const flexContentStyle = { - display: "flex", - flexDirection: "row" -} + display: "flex", + flexDirection: "row", +}; const iconStyle = { - width: "75px", - height: "75px", - padding: "20px" -} + width: "75px", + height: "75px", + padding: "20px", +}; -const fontSize_16 = {fontSize: "16px",} -const counterStyle = {fontSize: "36px",fontWeight:"bold"} -const blockRightStyle = {textAlign: "right",padding: "20px 20px 0px 0px",width:"100%"} +const fontSize_16 = { fontSize: "16px" }; +const counterStyle = { fontSize: "36px", fontWeight: "bold" }; +const blockRightStyle = { + textAlign: "right", + padding: "20px 20px 0px 0px", + width: "100%", +}; export const validateJson = (showResult) => { - //showResult = showResult.split(" None").join(" \"None\"") - showResult = showResult.split(" False").join(" false") - showResult = showResult.split(" True").join(" true") + //showResult = showResult.split(" None").join(" \"None\"") + showResult = showResult.split(" False").join(" false"); + showResult = showResult.split(" True").join(" true"); - var jsonvalid = true - try { - const tmp = String(JSON.parse(showResult)) - if (!showResult.includes("{") && !showResult.includes("[")) { - jsonvalid = false - } - } catch (e) { - showResult = showResult.split("\'").join("\"") + var jsonvalid = true; + try { + const tmp = String(JSON.parse(showResult)); + if (!showResult.includes("{") && !showResult.includes("[")) { + jsonvalid = false; + } + } catch (e) { + showResult = showResult.split("'").join('"'); - try { - const tmp = String(JSON.parse(showResult)) - if (!showResult.includes("{") && !showResult.includes("[")) { - jsonvalid = false - } - } catch (e) { - jsonvalid = false - } - } + try { + const tmp = String(JSON.parse(showResult)); + if (!showResult.includes("{") && !showResult.includes("[")) { + jsonvalid = false; + } + } catch (e) { + jsonvalid = false; + } + } - const result = jsonvalid ? JSON.parse(showResult) : showResult - //console.log("VALID: ", jsonvalid, result) - return { - "valid": jsonvalid, - "result": result, - } -} + const result = jsonvalid ? JSON.parse(showResult) : showResult; + //console.log("VALID: ", jsonvalid, result) + return { + valid: jsonvalid, + result: result, + }; +}; const MyView = (props) => { - const { globalUrl, isLoggedIn, isLoaded, userdata} = props; - document.title = "Shuffle - Workflows" + const { globalUrl, isLoggedIn, isLoaded, userdata } = props; + document.title = "Shuffle - Workflows"; - const alert = useAlert() + const alert = useAlert(); - var upload = "" - const [file, setFile] = React.useState(""); + var upload = ""; + const [file, setFile] = React.useState(""); - const [workflows, setWorkflows] = React.useState([]); - const [selectedWorkflow, setSelectedWorkflow] = React.useState({}); - const [selectedExecution, setSelectedExecution] = React.useState({}); - const [workflowExecutions, setWorkflowExecutions] = React.useState([]); - const [firstrequest, setFirstrequest] = React.useState(true) - const [workflowDone, setWorkflowDone] = React.useState(false) - const [, setTrackingId] = React.useState("") - const [selectedWorkflowId, setSelectedWorkflowId] = React.useState("") + const [workflows, setWorkflows] = React.useState([]); + const [selectedWorkflow, setSelectedWorkflow] = React.useState({}); + const [selectedExecution, setSelectedExecution] = React.useState({}); + const [workflowExecutions, setWorkflowExecutions] = React.useState([]); + const [firstrequest, setFirstrequest] = React.useState(true); + const [workflowDone, setWorkflowDone] = React.useState(false); + const [, setTrackingId] = React.useState(""); + const [selectedWorkflowId, setSelectedWorkflowId] = React.useState(""); - const [collapseJson, setCollapseJson] = React.useState(false) - const [field1, setField1] = React.useState("") - const [field2, setField2] = React.useState("") - const [downloadUrl, setDownloadUrl] = React.useState("https://github.com/frikky/shuffle-workflows") - const [downloadBranch, setDownloadBranch] = React.useState("master") - const [loadWorkflowsModalOpen, setLoadWorkflowsModalOpen] = React.useState(false) + const [collapseJson, setCollapseJson] = React.useState(false); + const [field1, setField1] = React.useState(""); + const [field2, setField2] = React.useState(""); + const [downloadUrl, setDownloadUrl] = React.useState( + "https://github.com/frikky/shuffle-workflows" + ); + const [downloadBranch, setDownloadBranch] = React.useState("master"); + const [loadWorkflowsModalOpen, setLoadWorkflowsModalOpen] = + React.useState(false); - const [modalOpen, setModalOpen] = React.useState(false); - const [newWorkflowName, setNewWorkflowName] = React.useState(""); - const [newWorkflowDescription, setNewWorkflowDescription] = React.useState(""); - const [newWorkflowTags, setNewWorkflowTags] = React.useState([]); - const [update, setUpdate] = React.useState("test"); - const [deleteModalOpen, setDeleteModalOpen] = React.useState(false); - const [editingWorkflow, setEditingWorkflow] = React.useState({}) - const [executionLoading, setExecutionLoading] = React.useState(false) - const [view, setView] = React.useState("grid") - const { start, stop } = useInterval({ - duration: 5000, - startImmediate: false, - callback: () => { - //getWorkflowExecution(selectedWorkflow.id) - } - }) + const [modalOpen, setModalOpen] = React.useState(false); + const [newWorkflowName, setNewWorkflowName] = React.useState(""); + const [newWorkflowDescription, setNewWorkflowDescription] = + React.useState(""); + const [newWorkflowTags, setNewWorkflowTags] = React.useState([]); + const [update, setUpdate] = React.useState("test"); + const [deleteModalOpen, setDeleteModalOpen] = React.useState(false); + const [editingWorkflow, setEditingWorkflow] = React.useState({}); + const [executionLoading, setExecutionLoading] = React.useState(false); + const [view, setView] = React.useState("grid"); + const { start, stop } = useInterval({ + duration: 5000, + startImmediate: false, + callback: () => { + //getWorkflowExecution(selectedWorkflow.id) + }, + }); - // DEBUG HERE - const handleClickLogout = () => {} + // DEBUG HERE + const handleClickLogout = () => {}; - const deleteModal = deleteModalOpen ? - { - setDeleteModalOpen(false) - setSelectedWorkflowId("") - }} - PaperProps={{ - style: { - backgroundColor: surfaceColor, - color: "white", - minWidth: 500, - }, - }} - > - -
    - Are you sure?
    Other workflows relying on this one may stop working -
    - - - - - - -
    - : null + const deleteModal = deleteModalOpen ? ( + { + setDeleteModalOpen(false); + setSelectedWorkflowId(""); + }} + PaperProps={{ + style: { + backgroundColor: surfaceColor, + color: "white", + minWidth: 500, + }, + }} + > + +
    + Are you sure?
    + Other workflows relying on this one may stop working +
    + + + + + +
    + ) : null; - const getAvailableWorkflows = () => { - fetch(globalUrl+"/api/v1/workflows", { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - credentials: "include", + const getAvailableWorkflows = () => { + 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) => { - setSelectedExecution({}) - setWorkflowExecutions([]) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for workflows :O!"); + return; + } + return response.json(); + }) + .then((responseJson) => { + setSelectedExecution({}); + setWorkflowExecutions([]); - if (responseJson !== undefined) { - setWorkflows(responseJson) - setWorkflowDone(true) - } else { - if (isLoggedIn) { - alert.error("An error occurred while loading workflows") - } else { - handleClickLogout() - } + if (responseJson !== undefined) { + setWorkflows(responseJson); + setWorkflowDone(true); + } else { + if (isLoggedIn) { + alert.error("An error occurred while loading workflows"); + } else { + handleClickLogout(); + } - return - } + return; + } - if (responseJson.length > 0){ - setSelectedWorkflow(responseJson[0]) - //getWorkflowExecution(responseJson[0].id) - } - }) - .catch(error => { - alert.error(error.toString()) - }); - } + if (responseJson.length > 0) { + setSelectedWorkflow(responseJson[0]); + //getWorkflowExecution(responseJson[0].id) + } + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; - useEffect(() => { - if (workflows.length <= 0 && firstrequest) { - setFirstrequest(false) - getAvailableWorkflows() - } - }) + useEffect(() => { + if (workflows.length <= 0 && firstrequest) { + setFirstrequest(false); + getAvailableWorkflows(); + } + }); - const viewStyle = { - color: "#ffffff", - width: "100%", - display: "flex", - minWidth: 1024, - maxWidth: 1024, - margin: "auto", - /*maxHeight: "90vh",*/ - } + const viewStyle = { + color: "#ffffff", + width: "100%", + display: "flex", + minWidth: 1024, + maxWidth: 1024, + margin: "auto", + /*maxHeight: "90vh",*/ + }; - const emptyWorkflowStyle = { - paddingTop: "200px", - width: 1024, - margin: "auto", - } + const emptyWorkflowStyle = { + paddingTop: "200px", + width: 1024, + margin: "auto", + }; - const boxStyle = { - padding: "20px 20px 20px 20px", - width: "100%", - height: "250px", - color: "white", - backgroundColor: surfaceColor, - display: "flex", - flexDirection: "column", - } + const boxStyle = { + padding: "20px 20px 20px 20px", + width: "100%", + height: "250px", + color: "white", + backgroundColor: surfaceColor, + display: "flex", + flexDirection: "column", + }; + const scrollStyle = { + marginTop: "10px", + overflow: "scroll", + height: "90%", + overflowX: "hidden", + overflowY: "auto", + }; - const scrollStyle = { - marginTop: "10px", - overflow: "scroll", - height: "90%", - overflowX: "hidden", - overflowY: "auto", - } + const paperAppContainer = { + display: "flex", + flexWrap: "wrap", + alignContent: "space-between", + }; - const paperAppContainer = { - display: "flex", - flexWrap: 'wrap', - alignContent: "space-between", - } + const paperAppStyle = { + minHeight: 130, + width: "100%", + color: "white", + backgroundColor: surfaceColor, + padding: "12px 12px 0px 15px", + borderRadius: 5, + display: "flex", + boxSizing: "border-box", + position: "relative", + }; - const paperAppStyle = { - minHeight: 130, - width: "100%", - color: "white", - backgroundColor: surfaceColor, - padding: "12px 12px 0px 15px", - borderRadius: 5, - display: "flex", - boxSizing: "border-box", - position: "relative", - } + const gridContainer = { + height: "auto", + color: "white", + margin: "10px", + backgroundColor: surfaceColor, + }; - const gridContainer = { - height: "auto", - color: "white", - margin: "10px", - backgroundColor: surfaceColor, - } + const workflowActionStyle = { + flex: "1", + display: "flex", + width: 150, + height: 44, + justifyContent: "space-between", + overflow: "hidden", + }; - const workflowActionStyle = { - flex: "1", - display: "flex", - width: 150, - height: 44, - justifyContent: "space-between", - overflow: "hidden" - } - - const getWorkflowExecution = (id) => { - setExecutionLoading(true) - fetch(globalUrl+"/api/v1/workflows/"+id+"/executions", { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - credentials: "include", - }) - .then((response) => { - setExecutionLoading(false) - if (response.status !== 200) { - console.log("Status not 200 for WORKFLOW EXECUTION :O!") - } - - return response.json() - }) - .then((responseJson) => { - if (responseJson.success === false) { - alert.error("Failed getting executions") - } else { - if (responseJson.length > 0) { - setSelectedExecution(responseJson[0]) - setWorkflowExecutions(responseJson) - } else { - //alert.info("Couldn't find executions for the workflow") - setSelectedExecution({}) - setWorkflowExecutions([]) - } - } - }) - .catch(error => { - setExecutionLoading(false) - alert.error(error.toString()) - }); - } - - const abortExecution = (workflowid, executionid) => { - alert.success("Aborting execution") - fetch(globalUrl+"/api/v1/workflows/"+workflowid+"/executions/"+executionid+"/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 WORKFLOW EXECUTION :O!") - } - //getWorkflowExecution(workflowid) - - return response.json() - }) - .catch(error => { - alert.error(error.toString()) - }); - } - - const executeWorkflow = (id) => { - alert.show("Executing workflow "+id) - setTrackingId(id) - fetch(globalUrl+"/api/v1/workflows/"+id+"/execute", { - 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!") - } - - return response.json() - }) - .then((responseJson) => { - if (!responseJson.success) { - alert.error(responseJson.reason) - } - }) - .catch(error => { - alert.error(error.toString()) - }); - - if (id === selectedWorkflow.id) { - sleep(2000).then(() => { - stop() - start() - }) - } - } - - function sleep (time) { - return new Promise((resolve) => setTimeout(resolve, time)); - } - - const exportAllWorkflows = () => { - for (var key in workflows) { - exportWorkflow(workflows[key]) - } - } - - const exportWorkflow = (data) => { - console.log("export") - let dataStr = JSON.stringify(data) - - let dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr); - let exportFileDefaultName = data.name+'.json'; - - data["owner"] = "" - for (var key in data.triggers) { - const trigger = data.triggers[key] - if (trigger.app_name === "Shuffle Workflow") { - if (trigger.parameters.length > 2) { - trigger.parameters[2].value = "" - } - } - - if (trigger.status == "running") { - trigger.status = "stopped" - } - } - - for (var key in data.actions) { - data.actions[key].authentication_id = "" - } - - //return - - data["org"] = [] - data["org_id"] = "" - data.execution_org = {"id": ""} - console.log(data) - - let linkElement = document.createElement('a'); - linkElement.setAttribute('href', dataUri); - linkElement.setAttribute('download', exportFileDefaultName); - linkElement.click(); - } - - const copyWorkflow = (data) => { - data = JSON.parse(JSON.stringify(data)) - alert.success("Copying workflow "+data.name) - console.log("data: ", data) - data.id = "" - data.name = data.name+"_copy" - //return - - fetch(globalUrl+"/api/v1/workflows", { - 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 workflows :O!") - return - } - return response.json() - }) - .then((responseJson) => { - getAvailableWorkflows() + const getWorkflowExecution = (id) => { + setExecutionLoading(true); + fetch(globalUrl + "/api/v1/workflows/" + id + "/executions", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", }) - .catch(error => { - alert.error(error.toString()) - }); - } + .then((response) => { + setExecutionLoading(false); + if (response.status !== 200) { + console.log("Status not 200 for WORKFLOW EXECUTION :O!"); + } - const deleteWorkflow = (id) => { - alert.success("Deleted workflow "+id) - fetch(globalUrl+"/api/v1/workflows/"+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 setting workflows :O!") - } + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === false) { + alert.error("Failed getting executions"); + } else { + if (responseJson.length > 0) { + setSelectedExecution(responseJson[0]); + setWorkflowExecutions(responseJson); + } else { + //alert.info("Couldn't find executions for the workflow") + setSelectedExecution({}); + setWorkflowExecutions([]); + } + } + }) + .catch((error) => { + setExecutionLoading(false); + alert.error(error.toString()); + }); + }; - return response.json() - }) - .then((responseJson) => { - getAvailableWorkflows() - }) - .catch(error => { - alert.error(error.toString()) - }); - } + const abortExecution = (workflowid, executionid) => { + alert.success("Aborting execution"); + fetch( + globalUrl + + "/api/v1/workflows/" + + workflowid + + "/executions/" + + executionid + + "/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 WORKFLOW EXECUTION :O!"); + } + //getWorkflowExecution(workflowid) - const getWorkflowMeta = (data) => { - let triggers = 0 - let schedules = 0 - let webhooks = 0 - let subflows = 0 - if (data.triggers !== undefined && data.triggers !== null && data.triggers.length > 0) { - triggers = data.triggers.length - for (let key in data.triggers) { + return response.json(); + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; - if (data.triggers[key].app_name === "Webhook") { - webhooks += 1 - //webhookImg = data.triggers[key].large_image - } else if (data.triggers[key].app_name === "Schedule") { - schedules += 1 - //scheduleImg = data.triggers[key].large_image - } else if (data.triggers[key].app_name === "Subflow") { - subflows += 1 - } - } - } + const executeWorkflow = (id) => { + alert.show("Executing workflow " + id); + setTrackingId(id); + fetch(globalUrl + "/api/v1/workflows/" + id + "/execute", { + 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!"); + } - return [triggers, schedules, webhooks, subflows] - } + return response.json(); + }) + .then((responseJson) => { + if (!responseJson.success) { + alert.error(responseJson.reason); + } + }) + .catch((error) => { + alert.error(error.toString()); + }); - // dropdown with copy etc I guess - const WorkflowPaper = (props) => { - const { data } = props; - const [open, setOpen] = React.useState(false); - const [anchorEl, setAnchorEl] = React.useState(null); + if (id === selectedWorkflow.id) { + sleep(2000).then(() => { + stop(); + start(); + }); + } + }; - var boxWidth = "2px" - if (selectedWorkflow.id === data.id) { - boxWidth = "4px" - } + function sleep(time) { + return new Promise((resolve) => setTimeout(resolve, time)); + } - var boxColor = "#FECC00" - if (data.is_valid) { - boxColor = "#86c142" - } + const exportAllWorkflows = () => { + for (var key in workflows) { + exportWorkflow(workflows[key]); + } + }; - if (!data.previously_saved) { - boxColor = "#f85a3e" - } + const exportWorkflow = (data) => { + console.log("export"); + let dataStr = JSON.stringify(data); - const menuClick = (event) => { - setOpen(!open) - setAnchorEl(event.currentTarget); - } + let dataUri = + "data:application/json;charset=utf-8," + encodeURIComponent(dataStr); + let exportFileDefaultName = data.name + ".json"; - var parsedName = data.name - if (parsedName !== undefined && parsedName !== null && parsedName.length > 25) { - parsedName = parsedName.slice(0,25)+".." - } + data["owner"] = ""; + for (var key in data.triggers) { + const trigger = data.triggers[key]; + if (trigger.app_name === "Shuffle Workflow") { + if (trigger.parameters.length > 2) { + trigger.parameters[2].value = ""; + } + } + if (trigger.status == "running") { + trigger.status = "stopped"; + } + } - const actions = data.actions !== null ? data.actions.length : 0 - const [triggers, schedules, webhooks, subflows] = getWorkflowMeta(data) + for (var key in data.actions) { + data.actions[key].authentication_id = ""; + } - return ( - - -
    - - - - {parsedName} - - - - - - - - {actions} - - - - - - - - {triggers} - - - - - - - - - - {subflows} - - - - {/* + //return + + data["org"] = []; + data["org_id"] = ""; + data.execution_org = { id: "" }; + console.log(data); + + let linkElement = document.createElement("a"); + linkElement.setAttribute("href", dataUri); + linkElement.setAttribute("download", exportFileDefaultName); + linkElement.click(); + }; + + const copyWorkflow = (data) => { + data = JSON.parse(JSON.stringify(data)); + alert.success("Copying workflow " + data.name); + console.log("data: ", data); + data.id = ""; + data.name = data.name + "_copy"; + //return + + fetch(globalUrl + "/api/v1/workflows", { + 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 workflows :O!"); + return; + } + return response.json(); + }) + .then((responseJson) => { + getAvailableWorkflows(); + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; + + const deleteWorkflow = (id) => { + alert.success("Deleted workflow " + id); + fetch(globalUrl + "/api/v1/workflows/" + 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 setting workflows :O!"); + } + + return response.json(); + }) + .then((responseJson) => { + getAvailableWorkflows(); + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; + + const getWorkflowMeta = (data) => { + let triggers = 0; + let schedules = 0; + let webhooks = 0; + let subflows = 0; + if ( + data.triggers !== undefined && + data.triggers !== null && + data.triggers.length > 0 + ) { + triggers = data.triggers.length; + for (let key in data.triggers) { + if (data.triggers[key].app_name === "Webhook") { + webhooks += 1; + //webhookImg = data.triggers[key].large_image + } else if (data.triggers[key].app_name === "Schedule") { + schedules += 1; + //scheduleImg = data.triggers[key].large_image + } else if (data.triggers[key].app_name === "Subflow") { + subflows += 1; + } + } + } + + return [triggers, schedules, webhooks, subflows]; + }; + + // dropdown with copy etc I guess + const WorkflowPaper = (props) => { + const { data } = props; + const [open, setOpen] = React.useState(false); + const [anchorEl, setAnchorEl] = React.useState(null); + + var boxWidth = "2px"; + if (selectedWorkflow.id === data.id) { + boxWidth = "4px"; + } + + var boxColor = "#FECC00"; + if (data.is_valid) { + boxColor = "#86c142"; + } + + if (!data.previously_saved) { + boxColor = "#f85a3e"; + } + + const menuClick = (event) => { + setOpen(!open); + setAnchorEl(event.currentTarget); + }; + + var parsedName = data.name; + if ( + parsedName !== undefined && + parsedName !== null && + parsedName.length > 25 + ) { + parsedName = parsedName.slice(0, 25) + ".."; + } + + const actions = data.actions !== null ? data.actions.length : 0; + const [triggers, schedules, webhooks, subflows] = getWorkflowMeta(data); + + return ( + + +
    + + + + {parsedName} + + + + + + + + {actions} + + + + + + + + {triggers} + + + + + + + + + + {subflows} + + + + {/* @@ -644,189 +743,290 @@ const MyView = (props) => { : null} */} - - - {data.tags !== undefined ? - data.tags.map((tag, index) => { - if (index >= 3) { - return null - } + + + {data.tags !== undefined + ? data.tags.map((tag, index) => { + if (index >= 3) { + return null; + } - return ( - - ) - }) - : null} - - - {data.actions !== undefined && data.actions !== null ? - - - - - - { - setOpen(false) - setAnchorEl(null) - }} - > - { - setModalOpen(true) - setEditingWorkflow(data) - setNewWorkflowName(data.name) - setNewWorkflowDescription(data.description) - if (data.tags !== undefined && data.tags !== null) { - setNewWorkflowTags(JSON.parse(JSON.stringify(data.tags))) - } - }} key={"change"}>{"Change details"} - { - copyWorkflow(data) - setOpen(false) - }} key={"copy"}>{"Copy"} - { - exportWorkflow(data) - setOpen(false) - }} key={"export"}>{"Export"} - { - setDeleteModalOpen(true) - setSelectedWorkflowId(data.id) - setOpen(false) - }} key={"delete"}>{"Delete"} + return ( + + ); + }) + : null} + + + {data.actions !== undefined && data.actions !== null ? ( + + + + + + { + setOpen(false); + setAnchorEl(null); + }} + > + { + setModalOpen(true); + setEditingWorkflow(data); + setNewWorkflowName(data.name); + setNewWorkflowDescription(data.description); + if (data.tags !== undefined && data.tags !== null) { + setNewWorkflowTags( + JSON.parse(JSON.stringify(data.tags)) + ); + } + }} + key={"change"} + > + {"Change details"} + + { + copyWorkflow(data); + setOpen(false); + }} + key={"copy"} + > + {"Copy"} + + { + exportWorkflow(data); + setOpen(false); + }} + key={"export"} + > + {"Export"} + + { + setDeleteModalOpen(true); + setSelectedWorkflowId(data.id); + setOpen(false); + }} + key={"delete"} + > + {"Delete"} + + + + + + + + + + + + ) : null} + + + ); + }; - - - - - - - - - - - : null} - - - ) - } + const executionPaper = (data) => { + var boxWidth = "2px"; + if (selectedExecution.execution_id === data.execution_id) { + boxWidth = "4px"; + } - const executionPaper = (data) => { - var boxWidth = "2px" - if (selectedExecution.execution_id === data.execution_id) { - boxWidth = "4px" - } + var boxColor = "orange"; + if ( + data.status === "ABORTED" || + data.status === "UNFINISHED" || + data.status === "FAILURE" + ) { + boxColor = "red"; + } else if (data.status === "FINISHED") { + boxColor = "green"; + } - var boxColor = "orange" - if (data.status === "ABORTED" || data.status === "UNFINISHED" || data.status === "FAILURE"){ - boxColor = "red" - } else if (data.status === "FINISHED") { - boxColor = "green" - } + var t = new Date(data.started_at * 1000); + if (data.workflow.actions === null || data.workflow.actions === undefined) { + return null; + } - var t = new Date(data.started_at*1000) - if (data.workflow.actions === null || data.workflow.actions === undefined ) { - return null - } + if (data.workflow.actions === null || data.workflow.actions === undefined) { + return null; + } - if (data.workflow.actions === null || data.workflow.actions === undefined) { - return null - } + var actions = data.workflow.actions.length; + if (data.results !== null) { + var results = data.results.length; + } - var actions = data.workflow.actions.length - if (data.results !== null) { - var results = data.results.length - } + return ( + { + setSelectedExecution(data); + }} + > +
    + + + +
    +

    + Status: {data.status} +

    + Actions: {results}/{actions} +
    +
    + +
    +
    +
    + + Started: {t.toISOString()} + +
    +
    +
    + + ); + }; - return ( - { - setSelectedExecution(data) - }}> -
    - - - -
    -

    Status: {data.status}

    - Actions: {results}/{actions} -
    -
    - -
    -
    -
    - - Started: {t.toISOString()} - -
    -
    -
    - - ) - } + const dividerColor = "rgb(225, 228, 232)"; - const dividerColor = "rgb(225, 228, 232)" + const resultPaperAppStyle = { + minHeight: "100px", + minWidth: "100%", + overflow: "hidden", + maxWidth: "100%", + marginTop: "5px", + color: "white", + backgroundColor: surfaceColor, + display: "flex", + }; - const resultPaperAppStyle = { - minHeight: "100px", - minWidth: "100%", - overflow: "hidden", - maxWidth: "100%", - marginTop: "5px", - color: "white", - backgroundColor: surfaceColor, - display: "flex", - } + function replaceAll(string, search, replace) { + return string.split(search).join(replace); + } - function replaceAll(string, search, replace) { - return string.split(search).join(replace); - } + const resultsPaper = (data) => { + var boxWidth = "2px"; + var boxColor = "orange"; + if ( + data.status === "ABORTED" || + data.status === "UNFINISHED" || + data.status === "FAILURE" + ) { + boxColor = "red"; + } else if (data.status === "FINISHED" || data.status === "SUCCESS") { + boxColor = "green"; + } else if (data.status === "SKIPPED" || data.status === "EXECUTING") { + boxColor = "yellow"; + } else { + boxColor = "green"; + } - const resultsPaper = (data) => { - var boxWidth = "2px" - var boxColor = "orange" - if (data.status === "ABORTED" || data.status === "UNFINISHED" || data.status === "FAILURE"){ - boxColor = "red" - } else if (data.status === "FINISHED" || data.status === "SUCCESS") { - boxColor = "green" - } else if (data.status === "SKIPPED" || data.status === "EXECUTING") { - boxColor = "yellow" - } else { - boxColor = "green" - } + var t = new Date(data.started_at * 1000); + var showResult = data.result.trim(); + const validate = validateJson(showResult); - var t = new Date(data.started_at*1000) - var showResult = data.result.trim() - const validate = validateJson(showResult) - - if (validate.valid) { - showResult = - } else { - // FIXME - have everything parsed as json, either just for frontend - // or in the backend? - /* + if (validate.valid) { + showResult = ( + + ); + } else { + // FIXME - have everything parsed as json, either just for frontend + // or in the backend? + /* const newdata = {"result": data.result} showResult = { name={"Results for "+data.action.name} /> */ - } + } - return ( - {}}> -
    -
    - - - -

    Name: {data.action.label}

    -
    - - App: {data.action.app_name}, Version: {data.action.app_version} - - - Action: {data.action.name}, Environment: {data.action.environment}, Status: {data.status} - -
    - - Started: {t.toISOString()} - -
    - -
    - - {showResult} - -
    -
    -
    -
    - ) - } + return ( + {}} + > +
    + + + +

    + Name: {data.action.label} +

    +
    + + App: {data.action.app_name}, Version: {data.action.app_version} + + + Action: {data.action.name}, Environment: {data.action.environment} + , Status: {data.status} + +
    + + Started: {t.toISOString()} + +
    + +
    + + {showResult} + +
    +
    +
    +
    + ); + }; - const resultsHandler = Object.getOwnPropertyNames(selectedExecution).length > 0 && selectedExecution.results !== null ? -
    - {selectedExecution.results.sort((a, b) => a.started_at - b.started_at).map((data, index) => { - return ( -
    - {resultsPaper(data)} -
    - ) - })} -
    - : -
    - No results yet -
    + const resultsHandler = + Object.getOwnPropertyNames(selectedExecution).length > 0 && + selectedExecution.results !== null ? ( +
    + {selectedExecution.results + .sort((a, b) => a.started_at - b.started_at) + .map((data, index) => { + return
    {resultsPaper(data)}
    ; + })} +
    + ) : ( +
    No results yet
    + ); - const resultsLength = Object.getOwnPropertyNames(selectedExecution).length > 0 && selectedExecution.results !== null ? selectedExecution.results.length : 0 + const resultsLength = + Object.getOwnPropertyNames(selectedExecution).length > 0 && + selectedExecution.results !== null + ? selectedExecution.results.length + : 0; - const ExecutionDetails = () => { - var starttime = new Date(selectedExecution.started_at*1000) - var endtime = new Date(selectedExecution.started_at*1000) + const ExecutionDetails = () => { + var starttime = new Date(selectedExecution.started_at * 1000); + var endtime = new Date(selectedExecution.started_at * 1000); - var parsedArgument = selectedExecution.execution_argument - if (selectedExecution.execution_argument !== undefined && selectedExecution.execution_argument.length > 0) { - parsedArgument = replaceAll(parsedArgument, " None", " \"None\""); - } + var parsedArgument = selectedExecution.execution_argument; + if ( + selectedExecution.execution_argument !== undefined && + selectedExecution.execution_argument.length > 0 + ) { + parsedArgument = replaceAll(parsedArgument, " None", ' "None"'); + } - var arg = null - if (selectedExecution.execution_argument !== undefined && selectedExecution.execution_argument.length > 0) { - var showResult = selectedExecution.execution_argument.trim() - const validate = validateJson(showResult) + var arg = null; + if ( + selectedExecution.execution_argument !== undefined && + selectedExecution.execution_argument.length > 0 + ) { + var showResult = selectedExecution.execution_argument.trim(); + const validate = validateJson(showResult); - arg = validate.valid ? - - : showResult - } + arg = validate.valid ? ( + + ) : ( + showResult + ); + } - var lastresult = null - if (selectedExecution.result !== undefined && selectedExecution.result.length > 0) { - var showResult = selectedExecution.result.trim() - const validate = validateJson(showResult) - lastresult = validate.valid ? - - : showResult - } + var lastresult = null; + if ( + selectedExecution.result !== undefined && + selectedExecution.result.length > 0 + ) { + var showResult = selectedExecution.result.trim(); + const validate = validateJson(showResult); + lastresult = validate.valid ? ( + + ) : ( + showResult + ); + } - /* + /*
    ID: {selectedExecution.execution_id}
    @@ -935,749 +1177,970 @@ const MyView = (props) => { Last node: {selectedExecution.workflow.actions.find(data => data.id === selectedExecution.last_node).actions[0].label}
    */ - if (Object.getOwnPropertyNames(selectedExecution).length > 0 && selectedExecution.workflow.actions !== null) { - return ( -
    -
    - Status: {selectedExecution.status} -
    -
    - Started: {starttime.toISOString()} -
    -
    - Finished: {endtime.toISOString()} -
    - {/* + if ( + Object.getOwnPropertyNames(selectedExecution).length > 0 && + selectedExecution.workflow.actions !== null + ) { + return ( +
    +
    + Status: {selectedExecution.status} +
    +
    + Started: {starttime.toISOString()} +
    +
    + Finished: {endtime.toISOString()} +
    + {/*
    Last Result: {lastresult}
    */} -
    - {arg} -
    - - {resultsHandler} -
    - ) - } +
    {arg}
    + + {resultsHandler} +
    + ); + } - return ( - executionLoading ? -
    - -
    - : -

    - There are no executiondetails yet. Click "execute" to run your first one. -

    - - ) - } + return executionLoading ? ( +
    + +
    + ) : ( +

    + There are no executiondetails yet. Click "execute" to run your first + one. +

    + ); + }; - const ExecutionsView = () => { - if (workflowExecutions.length > 0) { - const sortedWorkflows = workflowExecutions.sort((a, b) => a.started_at - b.started_at).reverse() + const ExecutionsView = () => { + if (workflowExecutions.length > 0) { + const sortedWorkflows = workflowExecutions + .sort((a, b) => a.started_at - b.started_at) + .reverse(); - return ( -
    - {sortedWorkflows.map(data => { - return ( - executionPaper(data) - ) - })} -
    - ) - } - return ( - executionLoading ? -
    - -
    - : -

    - Executions have been moved to the Workflow itself.
    Click here to see them -

    - ) - } + return ( +
    + {sortedWorkflows.map((data) => { + return executionPaper(data); + })} +
    + ); + } + return executionLoading ? ( +
    + +
    + ) : ( +

    + Executions have been moved to the Workflow itself.
    + + Click here to see them + +

    + ); + }; - // Can create and set workflows - const setNewWorkflow = (name, description, tags, editingWorkflow, redirect) => { + // Can create and set workflows + const setNewWorkflow = ( + name, + description, + tags, + editingWorkflow, + redirect + ) => { + var method = "POST"; + var extraData = ""; + var workflowdata = {}; - var method = "POST" - var extraData = "" - var workflowdata = {} + if (editingWorkflow.id !== undefined) { + console.log("Building original workflow"); + method = "PUT"; + extraData = "/" + editingWorkflow.id; + workflowdata = editingWorkflow; - if (editingWorkflow.id !== undefined) { - console.log("Building original workflow") - method = "PUT" - extraData = "/"+editingWorkflow.id - workflowdata = editingWorkflow + console.log("REMOVING OWNER"); + workflowdata["owner"] = ""; + // FIXME: Loop triggers and turn them off? + } - console.log("REMOVING OWNER") - workflowdata["owner"] = "" - // FIXME: Loop triggers and turn them off? - } + workflowdata["name"] = name; + workflowdata["description"] = description; + if (tags !== undefined) { + workflowdata["tags"] = tags; + } + //console.log(workflowdata) + //return - workflowdata["name"] = name - workflowdata["description"] = description - if (tags !== undefined) { - workflowdata["tags"] = tags - } - //console.log(workflowdata) - //return - - return fetch(globalUrl+"/api/v1/workflows"+extraData, { - method: method, - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - body: JSON.stringify(workflowdata), - credentials: "include", - }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for workflows :O!") - return - } - return response.json() - }) - .then((responseJson) => { - if (method === "POST" && redirect) { - window.location.pathname = "/workflows/"+responseJson["id"] - } else if (!redirect) { - // Update :) - getAvailableWorkflows() - } else { - alert.info("Successfully changed basic info for workflow") - } - - return responseJson + return fetch(globalUrl + "/api/v1/workflows" + extraData, { + method: method, + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(workflowdata), + credentials: "include", }) - .catch(error => { - alert.error(error.toString()) - }); - } + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for workflows :O!"); + return; + } + return response.json(); + }) + .then((responseJson) => { + if (method === "POST" && redirect) { + window.location.pathname = "/workflows/" + responseJson["id"]; + } else if (!redirect) { + // Update :) + getAvailableWorkflows(); + } else { + alert.info("Successfully changed basic info for workflow"); + } + return responseJson; + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; - const importFiles = (event) => { - console.log("Importing!") - const file = event.target.value - if (event.target.files.length > 0) { - for (var key in event.target.files) { - const file = event.target.files[key] - if (file.type !== "application/json") { - if (file.type !== undefined) { - alert.error("File has to contain valid json") - } + const importFiles = (event) => { + console.log("Importing!"); + const file = event.target.value; + if (event.target.files.length > 0) { + for (var key in event.target.files) { + const file = event.target.files[key]; + if (file.type !== "application/json") { + if (file.type !== undefined) { + alert.error("File has to contain valid json"); + } - continue - } + continue; + } - const reader = new FileReader() - // Waits for the read - reader.addEventListener('load', (event) => { - var data = reader.result - try { - data = JSON.parse(reader.result) - } catch (e) { - alert.error("Invalid JSON: "+e) - return - } + const reader = new FileReader(); + // Waits for the read + reader.addEventListener("load", (event) => { + var data = reader.result; + try { + data = JSON.parse(reader.result); + } catch (e) { + alert.error("Invalid JSON: " + e); + return; + } - // Initialize the workflow itself - const ret = setNewWorkflow(data.name, data.description, data.tags, {}, false) - .then((response) => { - if (response !== undefined) { - // SET THE FULL THING - data.id = response.id + // Initialize the workflow itself + const ret = setNewWorkflow( + data.name, + data.description, + data.tags, + {}, + false + ) + .then((response) => { + if (response !== undefined) { + // SET THE FULL THING + data.id = response.id; - // Actually create it - const ret = setNewWorkflow(data.name, data.description, data.tags, data, false) - .then((response) => { - if (response !== undefined) { - alert.success("Successfully imported "+data.name) - } - }) - } - }) - .catch(error => { - alert.error("Import error: "+error.toString()) - }); - }) + // Actually create it + const ret = setNewWorkflow( + data.name, + data.description, + data.tags, + data, + false + ).then((response) => { + if (response !== undefined) { + alert.success("Successfully imported " + data.name); + } + }); + } + }) + .catch((error) => { + alert.error("Import error: " + error.toString()); + }); + }); - // Actually reads - reader.readAsText(file) - } - } + // Actually reads + reader.readAsText(file); + } + } - setLoadWorkflowsModalOpen(false) - } + setLoadWorkflowsModalOpen(false); + }; - const modalView = modalOpen ? - {setModalOpen(false)}} - PaperProps={{ - style: { - backgroundColor: surfaceColor, - color: "white", - minWidth: "800px", - }, - }} - > - -
    - {editingWorkflow.id !== undefined ? "Editing" : "New"} workflow -
    - - - -
    -
    -
    - - - setNewWorkflowName(event.target.value)} - InputProps={{ - style:{ - color: "white", - }, - }} - color="primary" - placeholder="Name" - margin="dense" - defaultValue={newWorkflowName} - fullWidth - /> - setNewWorkflowDescription(event.target.value)} - InputProps={{ - style:{ - color: "white", - }, - }} - color="primary" - defaultValue={newWorkflowDescription} - placeholder="Description" - margin="dense" - fullWidth - /> - { - newWorkflowTags.push(chip) - setNewWorkflowTags(newWorkflowTags) - }} - onDelete={(chip, index) => { - newWorkflowTags.splice(index, 1) - setNewWorkflowTags(newWorkflowTags) - setUpdate("delete "+chip) - }} - /> - - - - + +
    +
    + + + + setNewWorkflowName(event.target.value)} + InputProps={{ + style: { + color: "white", + }, + }} + color="primary" + placeholder="Name" + margin="dense" + defaultValue={newWorkflowName} + fullWidth + /> + setNewWorkflowDescription(event.target.value)} + InputProps={{ + style: { + color: "white", + }, + }} + color="primary" + defaultValue={newWorkflowDescription} + placeholder="Description" + margin="dense" + fullWidth + /> + { + newWorkflowTags.push(chip); + setNewWorkflowTags(newWorkflowTags); + }} + onDelete={(chip, index) => { + newWorkflowTags.splice(index, 1); + setNewWorkflowTags(newWorkflowTags); + setUpdate("delete " + chip); + }} + /> + + + + - - -
    - : null + setModalOpen(false); + }} + color="primary" + > + Submit + + + + + ) : null; - - const viewSize = { - workflowView: 4, - executionsView: 3, - executionResults: 4, - } + const viewSize = { + workflowView: 4, + executionsView: 3, + executionResults: 4, + }; - const workflowViewStyle = { - flex: viewSize.workflowView, - marginLeft: "10px", - marginRight: "10px", - } + const workflowViewStyle = { + flex: viewSize.workflowView, + marginLeft: "10px", + marginRight: "10px", + }; - if (viewSize.workflowView === 0) { - workflowViewStyle.display = "none" - } + if (viewSize.workflowView === 0) { + workflowViewStyle.display = "none"; + } - const workflowButtons = - - {view === "grid" && ( - - - - )} - {view === "list" && ( - - - - )} - {workflows.length > 0 ? - - - - : null} - - - - upload = ref} onChange={importFiles} /> - {workflows.length > 0 ? - - - - : null} - - - - + const workflowButtons = ( + + {view === "grid" && ( + + + + )} + {view === "list" && ( + + + + )} + {workflows.length > 0 ? ( + + + + ) : null} + + + + (upload = ref)} + onChange={importFiles} + /> + {workflows.length > 0 ? ( + + + + ) : null} + + + + + ); - const useStyles = makeStyles((theme) => ({ - root: { - border: 0, - '& .MuiDataGrid-columnsContainer': { - backgroundColor: theme.palette.type === 'light' ? '#fafafa' : '#1d1d1d', - }, - '& .MuiDataGrid-iconSeparator': { - display: 'none', - }, - '& .MuiDataGrid-colCell, .MuiDataGrid-cell': { - borderRight: `1px solid ${ - theme.palette.type === 'light' ? 'white' : '#303030' - }`, - }, - '& .MuiDataGrid-columnsContainer, .MuiDataGrid-cell': { - borderBottom: `1px solid ${ - theme.palette.type === 'light' ? '#f0f0f0' : '#303030' - }`, - }, - '& .MuiDataGrid-cell': { - color: - theme.palette.type === 'light' - ? 'white' - : 'rgba(255,255,255,0.65)', - }, - '& .MuiPaginationItem-root, .MuiTablePagination-actions, .MuiTablePagination-caption': { - borderRadius: 0, - color: "white", - }, - }, - })); - const classes = useStyles(); + const useStyles = makeStyles((theme) => ({ + root: { + border: 0, + "& .MuiDataGrid-columnsContainer": { + backgroundColor: theme.palette.type === "light" ? "#fafafa" : "#1d1d1d", + }, + "& .MuiDataGrid-iconSeparator": { + display: "none", + }, + "& .MuiDataGrid-colCell, .MuiDataGrid-cell": { + borderRight: `1px solid ${ + theme.palette.type === "light" ? "white" : "#303030" + }`, + }, + "& .MuiDataGrid-columnsContainer, .MuiDataGrid-cell": { + borderBottom: `1px solid ${ + theme.palette.type === "light" ? "#f0f0f0" : "#303030" + }`, + }, + "& .MuiDataGrid-cell": { + color: + theme.palette.type === "light" ? "white" : "rgba(255,255,255,0.65)", + }, + "& .MuiPaginationItem-root, .MuiTablePagination-actions, .MuiTablePagination-caption": + { + borderRadius: 0, + color: "white", + }, + }, + })); + const classes = useStyles(); - const WorkflowGridView = () => { - let workflowData = ""; - if (workflows.length > 0) { - const columns = [ - { field: 'title', headerName: 'Title', width: 330, }, - { field: 'actions', headerName: 'Actions', width: 200, sortable: false, - disableClickEventBubbling: true, - renderCell: (params) => { - const data = params.row.record; - let [triggers, schedules, webhooks, subflows] = getWorkflowMeta(data); + const WorkflowGridView = () => { + let workflowData = ""; + if (workflows.length > 0) { + const columns = [ + { field: "title", headerName: "Title", width: 330 }, + { + field: "actions", + headerName: "Actions", + width: 200, + sortable: false, + disableClickEventBubbling: true, + renderCell: (params) => { + const data = params.row.record; + let [triggers, schedules, webhooks, subflows] = + getWorkflowMeta(data); - return - - - - - - - - executeWorkflow(data.id)} /> - - - - - {webhooks > 0 ? - - - - : null} - {schedules > 0 ? - - - - : null} - - } - }, - { field: 'tags', headerName: 'Tags', width: 390, sortable: false, - disableClickEventBubbling: true, - renderCell: (params) => { - const data = params.row.record; - return - {data.tags !== undefined ? - data.tags.map((tag, index) => { - if (index >= 3) { - return null - } + return ( + + + + + + + + + executeWorkflow(data.id)} + /> + + + + + {webhooks > 0 ? ( + + + + ) : null} + {schedules > 0 ? ( + + + + ) : null} + + ); + }, + }, + { + field: "tags", + headerName: "Tags", + width: 390, + sortable: false, + disableClickEventBubbling: true, + renderCell: (params) => { + const data = params.row.record; + return ( + + {data.tags !== undefined + ? data.tags.map((tag, index) => { + if (index >= 3) { + return null; + } - return ( - - ) - }) - : null} - - } - }, - ]; - let rows = []; - rows = workflows.map((data, index) => { - let obj = {"id":index+1, "title":data.name, "record":data,}; - return obj; - }); - workflowData = - } - return ( -
    - {workflowData} -
    - ); - } + return ( + + ); + }) + : null} +
    + ); + }, + }, + ]; + let rows = []; + rows = workflows.map((data, index) => { + let obj = { id: index + 1, title: data.name, record: data }; + return obj; + }); + workflowData = ( + + ); + } + return
    {workflowData}
    ; + }; - const WorkflowView = () => { - if (workflows.length === 0) { - return ( -
    - -
    -

    Welcome to Shuffle

    -
    -
    -

    - Shuffle is a flexible, easy to use, automation platform allowing users to integrate their services and devices freely. It's made to significantly reduce the amount of manual labor, and is focused on security applications. Click here to learn more. -

    -
    -
    - If you want to jump straight into it, click here to create your first workflow: -
    -
    - - - - ..OR - - {workflowButtons} - -
    -
    -
    - ) - } + const WorkflowView = () => { + if (workflows.length === 0) { + return ( +
    + +
    +

    Welcome to Shuffle

    +
    +
    +

    + Shuffle is a flexible, easy to use, automation platform + allowing users to integrate their services and devices freely. + It's made to significantly reduce the amount of manual labor, + and is focused on security applications.{" "} + + Click here to learn more. + +

    +
    +
    + If you want to jump straight into it, click here to create your + first workflow: +
    +
    + + + + ..OR + + {workflowButtons} + +
    +
    +
    + ); + } - return ( -
    -
    -
    -
    -

    Workflows

    -
    -
    + return ( +
    +
    +
    +
    +

    Workflows

    +
    +
    -
    -
    -
    -
    -
    -
    {workflows.length}
    -
    ACTIVE WORKFLOWS
    -
    -
    -
    -
    -
    -
    -
    -
    {workflows.length}
    -
    AVAILABE WORKFLOWS
    -
    -
    -
    -
    -
    -
    -
    -
    {workflows.length}
    -
    NOTIFICATIONS
    -
    -
    -
    -
    +
    +
    +
    +
    + +
    +
    +
    {workflows.length}
    +
    ACTIVE WORKFLOWS
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    {workflows.length}
    +
    AVAILABE WORKFLOWS
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    {workflows.length}
    +
    NOTIFICATIONS
    +
    +
    +
    +
    -
    -
    - This is your workflow view. Learn more about Workflows -
    -
    - {workflowButtons} -
    -
    -
    - {view === "grid" && ( - - {workflows.map((data, index) => { - return ( - - ) - })} - - )} - - {view === "list" && ( - - )} +
    +
    + + This is your workflow view.{" "} + + Learn more about Workflows + + +
    +
    {workflowButtons}
    +
    +
    + {view === "grid" && ( + + {workflows.map((data, index) => { + return ; + })} + + )} -
    -
    -
    - ) - } - - const importWorkflowsFromUrl = (url) => { - console.log("IMPORT WORKFLOWS FROM ", downloadUrl) + {view === "list" && } - const parsedData = { - "url": url, - "field_3": downloadBranch || 'master' - } +
    +
    +
    + ); + }; - if (field1.length > 0) { - parsedData["field_1"] = field1 - } + const importWorkflowsFromUrl = (url) => { + console.log("IMPORT WORKFLOWS FROM ", downloadUrl); - if (field2.length > 0) { - parsedData["field_2"] = field2 - } + const parsedData = { + url: url, + field_3: downloadBranch || "master", + }; - alert.success("Getting specific workflows from your URL.") - var cors = "cors" - fetch(globalUrl+"/api/v1/workflows/download_remote", { - method: "POST", - mode: "cors", - headers: { - 'Accept': 'application/json', - }, - body: JSON.stringify(parsedData), - credentials: "include", - }) - .then((response) => { - if (response.status === 200) { - alert.success("Successfully loaded workflows from "+downloadUrl) - getAvailableWorkflows() - } + if (field1.length > 0) { + parsedData["field_1"] = field1; + } - return response.json() - }) - .then((responseJson) => { - console.log("DATA: ", responseJson) - if (!responseJson.success) { - if (responseJson.reason !== undefined) { - alert.error("Failed loading: "+responseJson.reason) - } else { - alert.error("Failed loading") - } - } - }) - .catch(error => { - alert.error(error.toString()) - }) - } + if (field2.length > 0) { + parsedData["field_2"] = field2; + } - const handleGithubValidation = () => { - importWorkflowsFromUrl(downloadUrl) - setLoadWorkflowsModalOpen(false) - } + alert.success("Getting specific workflows from your URL."); + var cors = "cors"; + fetch(globalUrl + "/api/v1/workflows/download_remote", { + method: "POST", + mode: "cors", + headers: { + Accept: "application/json", + }, + body: JSON.stringify(parsedData), + credentials: "include", + }) + .then((response) => { + if (response.status === 200) { + alert.success("Successfully loaded workflows from " + downloadUrl); + getAvailableWorkflows(); + } - const workflowDownloadModalOpen = loadWorkflowsModalOpen ? - { - }} - PaperProps={{ - style: { - backgroundColor: surfaceColor, - color: "white", - minWidth: "800px", - minHeight: "320px", - }, - }} - > - -
    - Load workflows from github repo -
    - - - -
    -
    -
    - - Repository (supported: github, gitlab, bitbucket) - 0 ? userdata.active_org.defaults.workflow_download_repo : downloadUrl} - InputProps={{ - style:{ - color: "white", - height: "50px", - fontSize: "1em", - }, - }} - onChange={e => setDownloadUrl(e.target.value)} - placeholder="https://github.com/frikky/shuffle-apps" - fullWidth - /> + return response.json(); + }) + .then((responseJson) => { + console.log("DATA: ", responseJson); + if (!responseJson.success) { + if (responseJson.reason !== undefined) { + alert.error("Failed loading: " + responseJson.reason); + } else { + alert.error("Failed loading"); + } + } + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; - Branch (default value is "master"): -
    - 0 ? userdata.active_org.defaults.workflow_download_branch : downloadBranch} - InputProps={{ - style:{ - color: "white", - height: "50px", - fontSize: "1em", - }, - }} - onChange={e => setDownloadBranch(e.target.value)} - placeholder="master" - fullWidth - /> -
    + const handleGithubValidation = () => { + importWorkflowsFromUrl(downloadUrl); + setLoadWorkflowsModalOpen(false); + }; - Authentication (optional - private repos etc): -
    - setField1(e.target.value)} - type="username" - placeholder="Username / APIkey (optional)" - fullWidth - /> - setField2(e.target.value)} - type="password" - placeholder="Password (optional)" - fullWidth - /> -
    -
    - - - - -
    - : null + const workflowDownloadModalOpen = loadWorkflowsModalOpen ? ( + {}} + PaperProps={{ + style: { + backgroundColor: surfaceColor, + color: "white", + minWidth: "800px", + minHeight: "320px", + }, + }} + > + +
    + Load workflows from github repo +
    + + + +
    +
    +
    + + Repository (supported: github, gitlab, bitbucket) + 0 + ? userdata.active_org.defaults.workflow_download_repo + : downloadUrl + } + InputProps={{ + style: { + color: "white", + height: "50px", + fontSize: "1em", + }, + }} + onChange={(e) => setDownloadUrl(e.target.value)} + placeholder="https://github.com/frikky/shuffle-apps" + fullWidth + /> + + Branch (default value is "master"): + +
    + 0 + ? userdata.active_org.defaults.workflow_download_branch + : downloadBranch + } + InputProps={{ + style: { + color: "white", + height: "50px", + fontSize: "1em", + }, + }} + onChange={(e) => setDownloadBranch(e.target.value)} + placeholder="master" + fullWidth + /> +
    + + Authentication (optional - private repos etc): + +
    + setField1(e.target.value)} + type="username" + placeholder="Username / APIkey (optional)" + fullWidth + /> + setField2(e.target.value)} + type="password" + placeholder="Password (optional)" + fullWidth + /> +
    +
    + + + + +
    + ) : null; - const loadedCheck = isLoaded && isLoggedIn && workflowDone ? -
    - - {modalView} - {deleteModal} - {workflowDownloadModalOpen} -
    - : -
    - - - Loading Workflows - -
    + const loadedCheck = + isLoaded && isLoggedIn && workflowDone ? ( +
    + + {modalView} + {deleteModal} + {workflowDownloadModalOpen} +
    + ) : ( +
    + + Loading Workflows +
    + ); + // Maybe use gridview or something, idk + return
    {loadedCheck}
    ; +}; - // Maybe use gridview or something, idk - return ( -
    - {loadedCheck} -
    - ) -} - -export default MyView +export default MyView; diff --git a/frontend/src/views/Oauth2.jsx b/frontend/src/views/Oauth2.jsx index 3137d3d1..0f0e4d76 100644 --- a/frontend/src/views/Oauth2.jsx +++ b/frontend/src/views/Oauth2.jsx @@ -1,11 +1,7 @@ -import React, { } from 'react'; +import React from "react"; const Oauth2 = (props) => { - return ( -
    - tmp -
    - ) -} + return
    tmp
    ; +}; -export default Oauth2; +export default Oauth2; diff --git a/frontend/src/views/Post.jsx b/frontend/src/views/Post.jsx index 11d41646..a38f9d00 100644 --- a/frontend/src/views/Post.jsx +++ b/frontend/src/views/Post.jsx @@ -1,72 +1,61 @@ -import React from 'react'; +import React from "react"; const Body = { - maxWidth: '1000px', - minWidth: '768px', - margin: 'auto', - display: "flex", - heigth: "100%", - color: "white", - //textAlign: "center", + maxWidth: "1000px", + minWidth: "768px", + margin: "auto", + display: "flex", + heigth: "100%", + color: "white", + //textAlign: "center", }; const SideBar = { - maxWidth: "250px", - flex: "1", -} + maxWidth: "250px", + flex: "1", +}; const hrefStyle = { - color: "#385f71", - textDecoration: "none" -} + color: "#385f71", + textDecoration: "none", +}; const Post = (props) => { - const { currentPost, isLoaded } = props; + const { currentPost, isLoaded } = props; - const postData = -
    - -
    - {currentPost} -
    -
    + const postData = ( +
    + +
    {currentPost}
    +
    + ); - const loadedCheck = isLoaded ? -
    - {postData} -
    - : -
    -
    + const loadedCheck = isLoaded ?
    {postData}
    :
    ; - return ( -
    - {loadedCheck} -
    - ) -} + return
    {loadedCheck}
    ; +}; export default Post; diff --git a/frontend/src/views/PrivacyPolicy.jsx b/frontend/src/views/PrivacyPolicy.jsx index 52041010..8aae1aa6 100644 --- a/frontend/src/views/PrivacyPolicy.jsx +++ b/frontend/src/views/PrivacyPolicy.jsx @@ -1,122 +1,264 @@ -import React from 'react'; +import React from "react"; const PrivacyPolicy = () => { - return ( -
    -

    Privacy Policy

    - -

    Effective date: 17.08.2019

    - - -

    We operate the shuffler.io website.

    - -

    This page informs you of our policies regarding the collection, use, and disclosure of personal data when you use our service and the choices you have associated with that data.

    - -

    We use your data to provide and improve the service. By using the service, you agree to the collection and use of information in accordance with this policy. Unless otherwise defined in this Privacy Policy, terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, accessible from shuffler.io

    - - -

    Information Collection And Use

    - -

    We collect several different types of information for various purposes to provide and improve our service to you.

    - -

    Types of Data Collected

    - -

    Personal Data

    - -

    While using our service, we may ask you to provide us with certain personally identifiable information that can be used to contact or identify you ("Personal Data"). Personally identifiable information may include, but is not limited to:

    - -
      -
    • Cookies and Usage Data
    • -
    - -

    Usage Data

    - -

    We may also collect information how the service is accessed and used ("Usage Data"). This Usage Data may include information such as your computer's Internet Protocol address (e.g. IP address), browser type, browser version, the pages of our service that you visit, the time and date of your visit, the time spent on those pages, unique device identifiers and other diagnostic data.

    - -

    Tracking & Cookies Data

    -

    We use cookies and similar tracking technologies to track the activity on our service and hold certain information.

    -

    Cookies are files with small amount of data which may include an anonymous unique identifier. Cookies are sent to your browser from a website and stored on your device. Tracking technologies also used are beacons, tags, and scripts to collect and track information and to improve and analyze our service.

    -

    You can instruct your browser to refuse all cookies or to indicate when a cookie is being sent. However, if you do not accept cookies, you may not be able to use some portions of our service.

    -

    Examples of Cookies we use:

    -
      -
    • Session Cookies. We use Session Cookies to operate our service.
    • -
    • Preference Cookies. We use Preference Cookies to remember your preferences and various settings.
    • -
    • Security Cookies. We use Security Cookies for security purposes.
    • -
    - -

    Use of Data

    - -

    Shuffler uses the collected data for various purposes:

    -
      -
    • To provide and maintain the service
    • -
    • To notify you about changes to our service
    • -
    • To allow you to participate in interactive features of our service when you choose to do so
    • -
    • To provide customer care and support
    • -
    • To provide analysis or valuable information so that we can improve the service
    • -
    • To monitor the usage of the service
    • -
    • To detect, prevent and address technical issues
    • -
    - -

    Transfer Of Data

    -

    Your information, including Personal Data, may be transferred to — and maintained on — computers located outside of your state, province, country or other governmental jurisdiction where the data protection laws may differ than those from your jurisdiction.

    -

    If you are located outside Norway and choose to provide information to us, please note that we transfer the data, including Personal Data, to Norway and process it there.

    -

    Your consent to this Privacy Policy followed by your submission of such information represents your agreement to that transfer.

    -

    Shuffler will take all steps reasonably necessary to ensure that your data is treated securely and in accordance with this Privacy Policy and no transfer of your Personal Data will take place to an organization or a country unless there are adequate controls in place including the security of your data and other personal information.

    - -

    Disclosure Of Data

    - -

    Legal Requirements

    -

    Shuffler may disclose your Personal Data in the good faith belief that such action is necessary to:

    -
      -
    • To comply with a legal obligation
    • -
    • To protect and defend the rights or property of Shuffler
    • -
    • To prevent or investigate possible wrongdoing in connection with the service
    • -
    • To protect the personal safety of users of the service or the public
    • -
    • To protect against legal liability
    • -
    - -

    Security Of Data

    -

    The security of your data is important to us, but remember that no method of transmission over the Internet, or method of electronic storage is 100% secure. While we strive to use commercially acceptable means to protect your Personal Data, we cannot guarantee its absolute security.

    - -

    Service Providers

    -

    We may employ third party companies and individuals to facilitate our service ("service Providers"), to provide the service on our behalf, to perform service-related services or to assist us in analyzing how our service is used.

    -

    These third parties have access to your Personal Data only to perform these tasks on our behalf and are obligated not to disclose or use it for any other purpose.

    - -

    Analytics

    -

    We may use third-party service Providers to monitor and analyze the use of our service.

    -
      -
    • -

      Google Analytics

      -

      Google Analytics is a web analytics service offered by Google that tracks and reports website traffic. Google uses the data collected to track and monitor the use of our service. This data is shared with other Google services. Google may use the collected data to contextualize and personalize the ads of its own advertising network.

      -

      You can opt-out of having made your activity on the service available to Google Analytics by installing the Google Analytics opt-out browser add-on. The add-on prevents the Google Analytics JavaScript (ga.js, analytics.js, and dc.js) from sharing information with Google Analytics about visits activity.

      For more information on the privacy practices of Google, please visit the Google Privacy & Terms web page: https://policies.google.com/privacy?hl=en

      -
    • -
    - - -

    Links To Other Sites

    -

    Our service may contain links to other sites that are not operated by us. If you click on a third party link, you will be directed to that third party's site. We strongly advise you to review the Privacy Policy of every site you visit.

    -

    We have no control over and assume no responsibility for the content, privacy policies or practices of any third party sites or services.

    - - -

    Children's Privacy

    -

    Our service does not address anyone under the age of 18 ("Children").

    -

    We do not knowingly collect personally identifiable information from anyone under the age of 18. If you are a parent or guardian and you are aware that your Children has provided us with Personal Data, please contact us. If we become aware that we have collected Personal Data from children without verification of parental consent, we take steps to remove that information from our servers.

    - - -

    Changes To This Privacy Policy

    -

    We may update our Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page.

    -

    We will let you know via email and/or a prominent notice on our service, prior to the change becoming effective and update the "effective date" at the top of this Privacy Policy.

    -

    You are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when they are posted on this page.

    - - -

    Contact Us

    -

    If you have any questions about this Privacy Policy, please contact us:

    -
      -
    • By email: fredrik_9490@hotmail.com
    • - -
    -
    - ) -} + return ( +
    +

    Privacy Policy

    + +

    Effective date: 17.08.2019

    + +

    We operate the shuffler.io website.

    + +

    + This page informs you of our policies regarding the collection, use, and + disclosure of personal data when you use our service and the choices you + have associated with that data. +

    + +

    + We use your data to provide and improve the service. By using the + service, you agree to the collection and use of information in + accordance with this policy. Unless otherwise defined in this Privacy + Policy, terms used in this Privacy Policy have the same meanings as in + our Terms and Conditions, accessible from shuffler.io +

    + +

    Information Collection And Use

    + +

    + We collect several different types of information for various purposes + to provide and improve our service to you. +

    + +

    Types of Data Collected

    + +

    Personal Data

    + +

    + While using our service, we may ask you to provide us with certain + personally identifiable information that can be used to contact or + identify you ("Personal Data"). Personally identifiable information may + include, but is not limited to: +

    + +
      +
    • Cookies and Usage Data
    • +
    + +

    Usage Data

    + +

    + We may also collect information how the service is accessed and used + ("Usage Data"). This Usage Data may include information such as your + computer's Internet Protocol address (e.g. IP address), browser type, + browser version, the pages of our service that you visit, the time and + date of your visit, the time spent on those pages, unique device + identifiers and other diagnostic data. +

    + +

    Tracking & Cookies Data

    +

    + We use cookies and similar tracking technologies to track the activity + on our service and hold certain information. +

    +

    + Cookies are files with small amount of data which may include an + anonymous unique identifier. Cookies are sent to your browser from a + website and stored on your device. Tracking technologies also used are + beacons, tags, and scripts to collect and track information and to + improve and analyze our service. +

    +

    + You can instruct your browser to refuse all cookies or to indicate when + a cookie is being sent. However, if you do not accept cookies, you may + not be able to use some portions of our service. +

    +

    Examples of Cookies we use:

    +
      +
    • + Session Cookies. We use Session Cookies to operate + our service. +
    • +
    • + Preference Cookies. We use Preference Cookies to + remember your preferences and various settings. +
    • +
    • + Security Cookies. We use Security Cookies for + security purposes. +
    • +
    + +

    Use of Data

    + +

    Shuffler uses the collected data for various purposes:

    +
      +
    • To provide and maintain the service
    • +
    • To notify you about changes to our service
    • +
    • + To allow you to participate in interactive features of our service + when you choose to do so +
    • +
    • To provide customer care and support
    • +
    • + To provide analysis or valuable information so that we can improve the + service +
    • +
    • To monitor the usage of the service
    • +
    • To detect, prevent and address technical issues
    • +
    + +

    Transfer Of Data

    +

    + Your information, including Personal Data, may be transferred to — and + maintained on — computers located outside of your state, province, + country or other governmental jurisdiction where the data protection + laws may differ than those from your jurisdiction. +

    +

    + If you are located outside Norway and choose to provide information to + us, please note that we transfer the data, including Personal Data, to + Norway and process it there. +

    +

    + Your consent to this Privacy Policy followed by your submission of such + information represents your agreement to that transfer. +

    +

    + Shuffler will take all steps reasonably necessary to ensure that your + data is treated securely and in accordance with this Privacy Policy and + no transfer of your Personal Data will take place to an organization or + a country unless there are adequate controls in place including the + security of your data and other personal information. +

    + +

    Disclosure Of Data

    + +

    Legal Requirements

    +

    + Shuffler may disclose your Personal Data in the good faith belief that + such action is necessary to: +

    +
      +
    • To comply with a legal obligation
    • +
    • To protect and defend the rights or property of Shuffler
    • +
    • + To prevent or investigate possible wrongdoing in connection with the + service +
    • +
    • + To protect the personal safety of users of the service or the public +
    • +
    • To protect against legal liability
    • +
    + +

    Security Of Data

    +

    + The security of your data is important to us, but remember that no + method of transmission over the Internet, or method of electronic + storage is 100% secure. While we strive to use commercially acceptable + means to protect your Personal Data, we cannot guarantee its absolute + security. +

    + +

    Service Providers

    +

    + We may employ third party companies and individuals to facilitate our + service ("service Providers"), to provide the service on our behalf, to + perform service-related services or to assist us in analyzing how our + service is used. +

    +

    + These third parties have access to your Personal Data only to perform + these tasks on our behalf and are obligated not to disclose or use it + for any other purpose. +

    + +

    Analytics

    +

    + We may use third-party service Providers to monitor and analyze the use + of our service. +

    +
      +
    • +

      + Google Analytics +

      +

      + Google Analytics is a web analytics service offered by Google that + tracks and reports website traffic. Google uses the data collected + to track and monitor the use of our service. This data is shared + with other Google services. Google may use the collected data to + contextualize and personalize the ads of its own advertising + network. +

      +

      + You can opt-out of having made your activity on the service + available to Google Analytics by installing the Google Analytics + opt-out browser add-on. The add-on prevents the Google Analytics + JavaScript (ga.js, analytics.js, and dc.js) from sharing information + with Google Analytics about visits activity. +

      {" "} +

      + For more information on the privacy practices of Google, please + visit the Google Privacy & Terms web page:{" "} + + https://policies.google.com/privacy?hl=en + +

      +
    • +
    + +

    Links To Other Sites

    +

    + Our service may contain links to other sites that are not operated by + us. If you click on a third party link, you will be directed to that + third party's site. We strongly advise you to review the Privacy Policy + of every site you visit. +

    +

    + We have no control over and assume no responsibility for the content, + privacy policies or practices of any third party sites or services. +

    + +

    Children's Privacy

    +

    + Our service does not address anyone under the age of 18 ("Children"). +

    +

    + We do not knowingly collect personally identifiable information from + anyone under the age of 18. If you are a parent or guardian and you are + aware that your Children has provided us with Personal Data, please + contact us. If we become aware that we have collected Personal Data from + children without verification of parental consent, we take steps to + remove that information from our servers. +

    + +

    Changes To This Privacy Policy

    +

    + We may update our Privacy Policy from time to time. We will notify you + of any changes by posting the new Privacy Policy on this page. +

    +

    + We will let you know via email and/or a prominent notice on our service, + prior to the change becoming effective and update the "effective date" + at the top of this Privacy Policy. +

    +

    + You are advised to review this Privacy Policy periodically for any + changes. Changes to this Privacy Policy are effective when they are + posted on this page. +

    + +

    Contact Us

    +

    + If you have any questions about this Privacy Policy, please contact us: +

    +
      +
    • By email: fredrik_9490@hotmail.com
    • +
    +
    + ); +}; export default PrivacyPolicy; diff --git a/frontend/src/views/RegisterLink.jsx b/frontend/src/views/RegisterLink.jsx index a4d8456e..7fde36e1 100644 --- a/frontend/src/views/RegisterLink.jsx +++ b/frontend/src/views/RegisterLink.jsx @@ -1,13 +1,12 @@ -import React, {useState, useEffect} from 'react'; +import React, { useState, useEffect } from "react"; -import Paper from '@material-ui/core/Paper'; +import Paper from "@material-ui/core/Paper"; const bodyDivStyle = { - margin: "auto", - textAlign: "center", - width: "768px", -} - + margin: "auto", + textAlign: "center", + width: "768px", +}; //const tmpdata = { // "username": "frikky", @@ -23,71 +22,66 @@ const bodyDivStyle = { // FIXME - remove tmpdata // FIXME: Use isLoggedIn :) const Settings = (props) => { - const { globalUrl, isLoaded, surfaceColor, } = props; + const { globalUrl, isLoaded, surfaceColor } = props; - const [firstRequest, setFirstRequest] = useState(true); - const boxStyle = { - flex: "1", - marginLeft: "10px", - marginRight: "10px", - paddingLeft: "30px", - paddingRight: "30px", - paddingBottom: "30px", - paddingTop: "30px", - backgroundColor: surfaceColor, - color: "white", - display: "flex", - flexDirection: "column" - } + const [firstRequest, setFirstRequest] = useState(true); + const boxStyle = { + flex: "1", + marginLeft: "10px", + marginRight: "10px", + paddingLeft: "30px", + paddingRight: "30px", + paddingBottom: "30px", + paddingTop: "30px", + backgroundColor: surfaceColor, + color: "white", + display: "flex", + flexDirection: "column", + }; - const registerCall = () => { - const url = globalUrl+'/api/v1/register/'+props.match.params.key - fetch(url, { - method: 'GET', - credentials: 'include', - headers: { - 'Content-Type': 'application/json; charset=utf-8', - }, - }) - .then(response => - response.json().then(responseJson => { - console.log(responseJson) - }), - ) - .catch(error => { - console.log("SOMETHING WRONG") - }); - } + const registerCall = () => { + const url = globalUrl + "/api/v1/register/" + props.match.params.key; + fetch(url, { + method: "GET", + credentials: "include", + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }) + .then((response) => + response.json().then((responseJson) => { + console.log(responseJson); + }) + ) + .catch((error) => { + console.log("SOMETHING WRONG"); + }); + }; - // This should "always" have data - useEffect(() => { - if (firstRequest) { - setFirstRequest(false) - registerCall() - } - }) + // This should "always" have data + useEffect(() => { + if (firstRequest) { + setFirstRequest(false); + registerCall(); + } + }); - // Random names for type & autoComplete. Didn't research :^) - const landingpageData = -
    - -

    Registration verification

    -

    Thanks for verifying, redirecting you to our login!

    -
    -
    + // Random names for type & autoComplete. Didn't research :^) + const landingpageData = ( +
    + +

    Registration verification

    +

    Thanks for verifying, redirecting you to our login!

    +
    +
    + ); -const loadedCheck = isLoaded ? -
    - {landingpageData} -
    - : -
    -
    + const loadedCheck = isLoaded ? ( +
    {landingpageData}
    + ) : ( +
    + ); -return( -
    - {loadedCheck} -
    -) -} + return
    {loadedCheck}
    ; +}; export default Settings; diff --git a/frontend/src/views/RegisterPage.jsx b/frontend/src/views/RegisterPage.jsx index 7fea6d0b..34df7666 100644 --- a/frontend/src/views/RegisterPage.jsx +++ b/frontend/src/views/RegisterPage.jsx @@ -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. Please check your mail :)") - 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. Please check your mail :)"); + 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} -
    - Username -
    - -
    - Password -
    - -
    -
    - - - -
    - {loginInfo} -
    -
    - -
    -
    - ); -} + return ( + + {formtitle} +
    + Username +
    + +
    + Password +
    + +
    +
    + + + +
    + {loginInfo} +
    +
    + +
    +
    + ); +}; export default LoginDialog; diff --git a/frontend/src/views/Schedules.jsx b/frontend/src/views/Schedules.jsx index 89db7527..88c92251 100644 --- a/frontend/src/views/Schedules.jsx +++ b/frontend/src/views/Schedules.jsx @@ -1,220 +1,232 @@ -import React, { useEffect} from 'react'; +import React, { useEffect } from "react"; -import Paper from '@material-ui/core/Paper'; -import Grid from '@material-ui/core/Grid'; -import ButtonBase from '@material-ui/core/ButtonBase'; -import List from '@material-ui/core/List'; -import ListItem from '@material-ui/core/ListItem'; -import Button from '@material-ui/core/Button'; +import Paper from "@material-ui/core/Paper"; +import Grid from "@material-ui/core/Grid"; +import ButtonBase from "@material-ui/core/ButtonBase"; +import List from "@material-ui/core/List"; +import ListItem from "@material-ui/core/ListItem"; +import Button from "@material-ui/core/Button"; //import Breadcrumbs from '@material-ui/core/Breadcrumbs'; const Schedules = (props) => { - const { globalUrl } = props; + const { globalUrl } = props; - //const [schedules, setSchedules] = React.useState(scheduledata); - const [schedules, setSchedules] = React.useState({}); + //const [schedules, setSchedules] = React.useState(scheduledata); + const [schedules, setSchedules] = React.useState({}); - const getAvailableSchedules = () => { - fetch(globalUrl+"/api/v1/schedules", { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - }) - .then((response) => response.json()) - .then((responseJson) => { - setSchedules(responseJson) - }) - .catch(error => { - console.log(error) - }); - } + const getAvailableSchedules = () => { + fetch(globalUrl + "/api/v1/schedules", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + }) + .then((response) => response.json()) + .then((responseJson) => { + setSchedules(responseJson); + }) + .catch((error) => { + console.log(error); + }); + }; - // FIXME - add automated redirection, as empty apps look horrible currently - const newSchedule = () => { - fetch(globalUrl+"/api/v1/schedules/new", { - method: "POST", - headers: {"content-type": "application/json"}, - body: JSON.stringify(), - }) - .then((response) => response.json()) - .then((responseJson) => { - console.log(responseJson) - setSchedules({}) - }) - .catch(error => { - console.log(error) - }); - } + // FIXME - add automated redirection, as empty apps look horrible currently + const newSchedule = () => { + fetch(globalUrl + "/api/v1/schedules/new", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(), + }) + .then((response) => response.json()) + .then((responseJson) => { + console.log(responseJson); + setSchedules({}); + }) + .catch((error) => { + console.log(error); + }); + }; - const deleteSchedule = (id) => { - if (id === undefined) { - return - } + const deleteSchedule = (id) => { + if (id === undefined) { + return; + } - fetch(globalUrl+"/api/v1/schedules/"+id+"/delete", { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - }) - .then((response) => response.json()) - .then((responseJson) => { - setSchedules({}) - }) - .catch(error => { - console.log(error) - }); - } + fetch(globalUrl + "/api/v1/schedules/" + id + "/delete", { + method: "DELETE", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + }) + .then((response) => response.json()) + .then((responseJson) => { + setSchedules({}); + }) + .catch((error) => { + console.log(error); + }); + }; - // FIXME - use this? - //const getNewScheduleInfo = () => { - // fetch(globalUrl+"/api/v1/schedules", { - // method: 'GET', - // headers: { - // 'Content-Type': 'application/json', - // 'Accept': 'application/json', - // }, - // }) - // .then((response) => response.json()) - // .then((responseJson) => { - // setSchedules(responseJson) - // }) - // .catch(error => { - // console.log(error) - // }); - //} + // FIXME - use this? + //const getNewScheduleInfo = () => { + // fetch(globalUrl+"/api/v1/schedules", { + // method: 'GET', + // headers: { + // 'Content-Type': 'application/json', + // 'Accept': 'application/json', + // }, + // }) + // .then((response) => response.json()) + // .then((responseJson) => { + // setSchedules(responseJson) + // }) + // .catch(error => { + // console.log(error) + // }); + //} - useEffect(() => { - if (Object.getOwnPropertyNames(schedules).length <= 0) { - getAvailableSchedules() - } - }) + useEffect(() => { + if (Object.getOwnPropertyNames(schedules).length <= 0) { + getAvailableSchedules(); + } + }); + const bodyDivStyle = { + marginLeft: "20px", + marginRight: "20px", + width: "1350px", + minWidth: "1350px", + maxWidth: "1350px", + }; - const bodyDivStyle = { - marginLeft: "20px", - marginRight: "20px", - width: "1350px", - minWidth: "1350px", - maxWidth: "1350px", - } + const scheduleApp = (app) => { + console.log(app); + return ( + + + + + + + + + +
    +

    {app.name}

    +
    +
    {app.description}
    +
    + {app.action} +
    +
    +
    + ); + }; - const scheduleApp = (app) => { - console.log(app) - return( - - - - - - - - - -
    -

    {app.name}

    -
    -
    - {app.description} -
    -
    - - {app.action} - -
    -
    -
    - ) - } + const splitter = ( +
    + ); - const splitter =
    + const hrefStyle = { + color: "#385f71", + textDecoration: "none", + }; - const hrefStyle = { - color: "#385f71", - textDecoration: "none" - } + // FIXME - add Schedule modal + const schedulePaper = (schedule) => { + return ( +
    + +
    + {scheduleApp(schedule.appinfo.sourceapp)} +
    +
    ARROW
    +
    + {scheduleApp(schedule.appinfo.destinationapp)} +
    + {splitter} +
    + + + + + + + + + + +
    +
    +
    + ); + }; - // FIXME - add Schedule modal - const schedulePaper = (schedule) => { - return( -
    - -
    - {scheduleApp(schedule.appinfo.sourceapp)} -
    -
    - ARROW -
    -
    - {scheduleApp(schedule.appinfo.destinationapp)} -
    - {splitter} -
    - - - - - - - - - + console.log(schedules); + console.log(schedules); + console.log(schedules.schedules); + const schedulemap = + Object.getOwnPropertyNames(schedules).length > 0 && + schedules.schedules && + schedules.schedules.length > 0 ? ( +
    {schedules.schedules.map((data) => schedulePaper(data))}
    + ) : ( +
    + +
    + ); -
    -
    -
    -
    - ) - } + const scheduleView = + Object.getOwnPropertyNames(schedules).length > 0 ? ( +
    + + {schedulemap} +
    + ) : null; - console.log(schedules) - console.log(schedules) - console.log(schedules.schedules) - const schedulemap = Object.getOwnPropertyNames(schedules).length > 0 && schedules.schedules && schedules.schedules.length > 0 ? -
    - {schedules.schedules.map(data => ( - schedulePaper(data) - ))} -
    - : -
    - -
    + // Maybe use gridview or something, idk + return
    {scheduleView}
    ; +}; - const scheduleView = Object.getOwnPropertyNames(schedules).length > 0 ? -
    - - {schedulemap} -
    - : null - - // Maybe use gridview or something, idk - return ( -
    - {scheduleView} -
    - ) -} - -export default Schedules +export default Schedules; diff --git a/frontend/src/views/SetAuthentication.jsx b/frontend/src/views/SetAuthentication.jsx index 5d1ca617..388f165e 100644 --- a/frontend/src/views/SetAuthentication.jsx +++ b/frontend/src/views/SetAuthentication.jsx @@ -1,155 +1,170 @@ -import React, {useRef, useState, useEffect, useLayoutEffect} from 'react'; +import React, { useRef, useState, useEffect, useLayoutEffect } from "react"; -import { Typography, CircularProgress } from '@material-ui/core'; +import { Typography, CircularProgress } from "@material-ui/core"; const SetAuthentication = (props) => { const { globalUrl, isLoggedIn, isLoaded, userdata } = props; - const [firstRequest, setFirstRequest] = useState(true) - const [finished, setFinished] = useState(false) - const [response, setResponse] = useState("") - const [failed, setFailed] = useState(false) + const [firstRequest, setFirstRequest] = useState(true); + const [finished, setFinished] = useState(false); + const [response, setResponse] = useState(""); + const [failed, setFailed] = useState(false); - if (firstRequest) { - setFirstRequest(false) + if (firstRequest) { + setFirstRequest(false); - //code - //session_state - const urlSearchParams = new URLSearchParams(window.location.search); - const params = Object.fromEntries(urlSearchParams.entries()); + //code + //session_state + const urlSearchParams = new URLSearchParams(window.location.search); + const params = Object.fromEntries(urlSearchParams.entries()); - const authenticationStore = [] - var appAuthData = { - "label": "", - "app": { - "name": "", - "id": "", - "app_version": "", - }, - "fields": [], - "type": "oauth2", - } + const authenticationStore = []; + var appAuthData = { + label: "", + app: { + name: "", + id: "", + app_version: "", + }, + fields: [], + type: "oauth2", + }; - if (window !== undefined && window !== null) { - console.log(window.location) - appAuthData.fields.push({"key": "redirect_uri", "value": window.location.origin+window.location.pathname}) - } + if (window !== undefined && window !== null) { + console.log(window.location); + appAuthData.fields.push({ + key: "redirect_uri", + value: window.location.origin + window.location.pathname, + }); + } - if (params.code !== undefined && params.code !== null) { - appAuthData.fields.push({"key": "code", "value": params.code}) - } + if (params.code !== undefined && params.code !== null) { + appAuthData.fields.push({ key: "code", value: params.code }); + } - if (params.session_state !== undefined && params.session_state !== null) { - appAuthData.fields.push({"key": "session_state", "value": params.session_state}) - } + if (params.session_state !== undefined && params.session_state !== null) { + appAuthData.fields.push({ + key: "session_state", + value: params.session_state, + }); + } - if (params.state !== undefined && params.state !== null) { - const paramsplit = params.state.split("&") - console.log(paramsplit) - for (var key in paramsplit) { - const query = paramsplit[key].split("=") - console.log(query) + if (params.state !== undefined && params.state !== null) { + const paramsplit = params.state.split("&"); + console.log(paramsplit); + for (var key in paramsplit) { + const query = paramsplit[key].split("="); + console.log(query); - if (query.length !== 2) { - console.log("INVALID QUERY: ", query) - continue - } + if (query.length !== 2) { + console.log("INVALID QUERY: ", query); + continue; + } - if (query[0] === "workflow_id") { - appAuthData.reference_workflow = query[1] - } + if (query[0] === "workflow_id") { + appAuthData.reference_workflow = query[1]; + } - if (query[0] === "reference_action_id") { - //appAuthData.ReferenceWorkflow = query[1] - } + if (query[0] === "reference_action_id") { + //appAuthData.ReferenceWorkflow = query[1] + } - if (query[0] === "app_name") { - appAuthData.app.name = query[1] - appAuthData.label = "Oauth2 for "+query[1] - } + if (query[0] === "app_name") { + appAuthData.app.name = query[1]; + appAuthData.label = "Oauth2 for " + query[1]; + } - if (query[0] === "app_id") { - appAuthData.app.id = query[1] - } + if (query[0] === "app_id") { + appAuthData.app.id = query[1]; + } - if (query[0] === "app_version") { - appAuthData.app.app_version = query[1] - } + if (query[0] === "app_version") { + appAuthData.app.app_version = query[1]; + } - if (query[0] === "authentication_url") { - appAuthData.fields.push({"key": "authentication_url", "value": query[1]}) - } + if (query[0] === "authentication_url") { + appAuthData.fields.push({ + key: "authentication_url", + value: query[1], + }); + } - if (query[0] === "scope") { - appAuthData.fields.push({"key": "scope", "value": query[1]}) - } + if (query[0] === "scope") { + appAuthData.fields.push({ key: "scope", value: query[1] }); + } - if (query[0] === "client_id") { - appAuthData.fields.push({"key": "client_id", "value": query[1]}) - } + if (query[0] === "client_id") { + appAuthData.fields.push({ key: "client_id", value: query[1] }); + } - if (query[0] === "client_secret") { - appAuthData.fields.push({"key": "client_secret", "value": query[1]}) - } + if (query[0] === "client_secret") { + appAuthData.fields.push({ key: "client_secret", value: query[1] }); + } - if (query[0] === "oauth_url") { - appAuthData.fields.push({"key": "oauth_url", "value": query[1]}) - } + if (query[0] === "oauth_url") { + appAuthData.fields.push({ key: "oauth_url", value: query[1] }); + } - if (query[0] === "refresh_uri") { - appAuthData.fields.push({"key": "refresh_uri", "value": query[1]}) - } + if (query[0] === "refresh_uri") { + appAuthData.fields.push({ key: "refresh_uri", value: query[1] }); + } - if (query[0] === "refresh_url") { - appAuthData.fields.push({"key": "refresh_url", "value": query[1]}) - } - } - } + if (query[0] === "refresh_url") { + appAuthData.fields.push({ key: "refresh_url", value: query[1] }); + } + } + } - console.log(appAuthData) + console.log(appAuthData); - fetch(globalUrl+"/api/v1/apps/authentication", { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - credentials: "include", - body: JSON.stringify(appAuthData), + fetch(globalUrl + "/api/v1/apps/authentication", { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + body: JSON.stringify(appAuthData), }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for oauth2 authentication") - setFailed(true) - } + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for oauth2 authentication"); + setFailed(true); + } - return response.json() - }) - .then((responseJson) => { - //setUserSettings(responseJson) - console.log("Resp: ", responseJson) - setFinished(true) - setResponse(responseJson.reason) + return response.json(); + }) + .then((responseJson) => { + //setUserSettings(responseJson) + console.log("Resp: ", responseJson); + setFinished(true); + setResponse(responseJson.reason); - setTimeout(() => { - window.close() - }, 1000) - }) - .catch(error => { - console.log(error) - }); + setTimeout(() => { + window.close(); + }, 1000); + }) + .catch((error) => { + console.log(error); + }); + } - } - - return ( -
    - - {!finished ? : "DONE WITH AUTH - this will close soon!!"} -
    - {failed ? "Failed setup. Error: " : ""} {response} - -
    - ) -} + return ( +
    + + {!finished ? ( + + ) : ( + "DONE WITH AUTH - this will close soon!!" + )} +
    + {failed ? "Failed setup. Error: " : ""} {response} + +
    + ); +}; export default SetAuthentication; diff --git a/frontend/src/views/SetAuthenticationSSO.jsx b/frontend/src/views/SetAuthenticationSSO.jsx index 5fcd0f2b..92afe942 100644 --- a/frontend/src/views/SetAuthenticationSSO.jsx +++ b/frontend/src/views/SetAuthenticationSSO.jsx @@ -1,143 +1,158 @@ -import React, {useRef, useState, useEffect, useLayoutEffect} from 'react'; +import React, { useRef, useState, useEffect, useLayoutEffect } from "react"; -import { Typography, CircularProgress } from '@material-ui/core'; +import { Typography, CircularProgress } from "@material-ui/core"; const SetAuthentication = (props) => { const { globalUrl, isLoggedIn, isLoaded, userdata } = props; - const [firstRequest, setFirstRequest] = useState(true) - const [finished, setFinished] = useState(false) - const [response, setResponse] = useState("") - const [failed, setFailed] = useState(false) + const [firstRequest, setFirstRequest] = useState(true); + const [finished, setFinished] = useState(false); + const [response, setResponse] = useState(""); + const [failed, setFailed] = useState(false); - if (firstRequest) { - setFirstRequest(false) + if (firstRequest) { + setFirstRequest(false); - //code - //session_state - const urlSearchParams = new URLSearchParams(window.location.search); - const params = Object.fromEntries(urlSearchParams.entries()); + //code + //session_state + const urlSearchParams = new URLSearchParams(window.location.search); + const params = Object.fromEntries(urlSearchParams.entries()); - const authenticationStore = [] - var appAuthData = { - "label": "", - "app": { - "name": "", - "id": "", - "app_version": "", - }, - "fields": [], - "type": "oauth2", - } + const authenticationStore = []; + var appAuthData = { + label: "", + app: { + name: "", + id: "", + app_version: "", + }, + fields: [], + type: "oauth2", + }; - if (window !== undefined && window !== null) { - console.log(window.location) - appAuthData.fields.push({"key": "redirect_uri", "value": window.location.origin+window.location.pathname}) - } + if (window !== undefined && window !== null) { + console.log(window.location); + appAuthData.fields.push({ + key: "redirect_uri", + value: window.location.origin + window.location.pathname, + }); + } - if (params.code !== undefined && params.code !== null) { - appAuthData.fields.push({"key": "code", "value": params.code}) - } + if (params.code !== undefined && params.code !== null) { + appAuthData.fields.push({ key: "code", value: params.code }); + } - if (params.session_state !== undefined && params.session_state !== null) { - appAuthData.fields.push({"key": "session_state", "value": params.session_state}) - } + if (params.session_state !== undefined && params.session_state !== null) { + appAuthData.fields.push({ + key: "session_state", + value: params.session_state, + }); + } - if (params.state !== undefined && params.state !== null) { - const paramsplit = params.state.split("&") - console.log(paramsplit) - for (var key in paramsplit) { - const query = paramsplit[key].split("=") - console.log(query) + if (params.state !== undefined && params.state !== null) { + const paramsplit = params.state.split("&"); + console.log(paramsplit); + for (var key in paramsplit) { + const query = paramsplit[key].split("="); + console.log(query); - if (query.length !== 2) { - console.log("INVALID QUERY: ", query) - continue - } + if (query.length !== 2) { + console.log("INVALID QUERY: ", query); + continue; + } - if (query[0] === "workflow_id") { - appAuthData.reference_workflow = query[1] - } + if (query[0] === "workflow_id") { + appAuthData.reference_workflow = query[1]; + } - if (query[0] === "reference_action_id") { - //appAuthData.ReferenceWorkflow = query[1] - } + if (query[0] === "reference_action_id") { + //appAuthData.ReferenceWorkflow = query[1] + } - if (query[0] === "app_name") { - appAuthData.app.name = query[1] - appAuthData.label = "Oauth2 for "+query[1] - } + if (query[0] === "app_name") { + appAuthData.app.name = query[1]; + appAuthData.label = "Oauth2 for " + query[1]; + } - if (query[0] === "app_id") { - appAuthData.app.id = query[1] - } + if (query[0] === "app_id") { + appAuthData.app.id = query[1]; + } - if (query[0] === "app_version") { - appAuthData.app.app_version = query[1] - } + if (query[0] === "app_version") { + appAuthData.app.app_version = query[1]; + } - if (query[0] === "authentication_url") { - appAuthData.fields.push({"key": "authentication_url", "value": query[1]}) - } + if (query[0] === "authentication_url") { + appAuthData.fields.push({ + key: "authentication_url", + value: query[1], + }); + } - if (query[0] === "scope") { - appAuthData.fields.push({"key": "scope", "value": query[1]}) - } + if (query[0] === "scope") { + appAuthData.fields.push({ key: "scope", value: query[1] }); + } - if (query[0] === "client_id") { - appAuthData.fields.push({"key": "client_id", "value": query[1]}) - } + if (query[0] === "client_id") { + appAuthData.fields.push({ key: "client_id", value: query[1] }); + } - if (query[0] === "client_secret") { - appAuthData.fields.push({"key": "client_secret", "value": query[1]}) - } - } - } + if (query[0] === "client_secret") { + appAuthData.fields.push({ key: "client_secret", value: query[1] }); + } + } + } - console.log(appAuthData) + console.log(appAuthData); - fetch(globalUrl+"/api/v1/apps/authentication", { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - credentials: "include", - body: JSON.stringify(appAuthData), + fetch(globalUrl + "/api/v1/apps/authentication", { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + body: JSON.stringify(appAuthData), }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for oauth2 authentication") - setFailed(true) - } + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for oauth2 authentication"); + setFailed(true); + } - return response.json() - }) - .then((responseJson) => { - //setUserSettings(responseJson) - console.log("Resp: ", responseJson) - setFinished(true) - setResponse(responseJson.reason) + return response.json(); + }) + .then((responseJson) => { + //setUserSettings(responseJson) + console.log("Resp: ", responseJson); + setFinished(true); + setResponse(responseJson.reason); - setTimeout(() => { - window.close() - }, 1000) - }) - .catch(error => { - console.log(error) - }); + setTimeout(() => { + window.close(); + }, 1000); + }) + .catch((error) => { + console.log(error); + }); + } - } - - return ( -
    - - {!finished ? : "DONE WITH AUTH - this will close soon!!"} -
    - {failed ? "Failed setup. Error: " : ""} {response} - -
    - ) -} + return ( +
    + + {!finished ? ( + + ) : ( + "DONE WITH AUTH - this will close soon!!" + )} +
    + {failed ? "Failed setup. Error: " : ""} {response} + +
    + ); +}; export default SetAuthentication; diff --git a/frontend/src/views/SettingsPage.jsx b/frontend/src/views/SettingsPage.jsx index c7a4fb54..ae0fd9e7 100644 --- a/frontend/src/views/SettingsPage.jsx +++ b/frontend/src/views/SettingsPage.jsx @@ -1,50 +1,62 @@ -import React, {useState, useEffect} from 'react'; +import React, { useState, useEffect } from "react"; -import {Grid, Typography, Paper, Button, Divider, TextField} from '@material-ui/core'; -import {Link} from 'react-router-dom'; +import { + Grid, + Typography, + Paper, + Button, + Divider, + TextField, +} from "@material-ui/core"; +import { Link } from "react-router-dom"; import { useAlert } from "react-alert"; -import { useTheme } from '@material-ui/core/styles'; +import { useTheme } from "@material-ui/core/styles"; -import detectEthereumProvider from '@metamask/detect-provider'; +import detectEthereumProvider from "@metamask/detect-provider"; const Settings = (props) => { const { globalUrl, isLoaded, userdata, setUserData } = props; - const theme = useTheme(); - const alert = useAlert() + const theme = useTheme(); + const alert = useAlert(); - const [username, setUsername] = useState(""); - const [firstname, setFirstname] = useState(""); - const [lastname, setLastname] = useState(""); - const [title, setTitle] = useState(""); - const [companyname, setCompanyname] = useState(""); - const [email, setEmail] = useState(""); - const [phone, setPhone] = useState(""); - const [currentPassword, setCurrentPassword] = useState(""); - const [newPassword, setNewPassword] = useState(""); - const [newPassword2, setNewPassword2] = useState(""); - const [file, setFile] = React.useState("") - const [fileBase64, setFileBase64] = React.useState(userdata.image === undefined || userdata.image === null ? theme.palette.defaultImage : userdata.image) - const [loadedValidationWorkflows, setLoadedValidationWorkflows] = React.useState([]) - const [selfOwnedWorkflows, setSelfOwnedWorkflows] = React.useState([]) - const [loadedWorkflowCollections, setLoadedWorkflowCollections] = React.useState([]) + const [username, setUsername] = useState(""); + const [firstname, setFirstname] = useState(""); + const [lastname, setLastname] = useState(""); + const [title, setTitle] = useState(""); + const [companyname, setCompanyname] = useState(""); + const [email, setEmail] = useState(""); + const [phone, setPhone] = useState(""); + const [currentPassword, setCurrentPassword] = useState(""); + const [newPassword, setNewPassword] = useState(""); + const [newPassword2, setNewPassword2] = useState(""); + const [file, setFile] = React.useState(""); + const [fileBase64, setFileBase64] = React.useState( + userdata.image === undefined || userdata.image === null + ? theme.palette.defaultImage + : userdata.image + ); + const [loadedValidationWorkflows, setLoadedValidationWorkflows] = + React.useState([]); + const [selfOwnedWorkflows, setSelfOwnedWorkflows] = React.useState([]); + const [loadedWorkflowCollections, setLoadedWorkflowCollections] = + React.useState([]); - // Used for error messages etc - const [formMessage, ] = useState(""); - const [passwordFormMessage, setPasswordFormMessage] = useState(""); + // Used for error messages etc + const [formMessage] = useState(""); + const [passwordFormMessage, setPasswordFormMessage] = useState(""); - const [firstrequest, setFirstRequest] = useState(true) + const [firstrequest, setFirstRequest] = useState(true); - const [userSettings, setUserSettings] = useState({}) + const [userSettings, setUserSettings] = useState({}); - - /* + /* const [userdata.eth_info, setEthInfo] = useState(userdata.eth_info !== undefined && userdata.eth_info.account !== undefined && userdata.eth_info.account.length > 0 ? userdata.eth_info : { "account": "", "balance": "", }) */ - /* + /* console.log(userdata.eth_info) if (userdata.eth_info.account.length === 0 && userdata.eth_info !== undefined && userdata.eth_info.account !== undefined && userdata.eth_info.account.length > 0) { setEthInfo(userdata.eth_info) @@ -62,410 +74,470 @@ const Settings = (props) => { } */ - //Returns the value from a storage position at a given address. - const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" + //Returns the value from a storage position at a given address. + const isCloud = + window.location.host === "localhost:3002" || + window.location.host === "shuffler.io"; - const bodyDivStyle = { - margin: "auto", - textAlign: "center", - width: "1100px", - } - - const boxStyle = { - flex: "1", - color: "white", - position: "relative", - marginLeft: "10px", - marginRight: "10px", - paddingLeft: "30px", - paddingRight: "30px", - paddingBottom: "30px", - paddingTop: "30px", - backgroundColor: theme.palette.surfaceColor, - display: "flex", - flexDirection: "column" - } + const bodyDivStyle = { + margin: "auto", + textAlign: "center", + width: "1100px", + }; - const checkOwner = (data, userdata) => { - var currentOwner = false - if (data.owner.address === userdata.eth_info.account) { - currentOwner = true - } else { - if (data.top_ownerships !== undefined && data.top_ownerships !== null && data.top_ownerships.length === 1) { - for (var key in data.top_ownerships) { - if (data.top_ownerships[key].owner.address === userdata.eth_info.account) { - currentOwner = true - break - } - } - } - } + const boxStyle = { + flex: "1", + color: "white", + position: "relative", + marginLeft: "10px", + marginRight: "10px", + paddingLeft: "30px", + paddingRight: "30px", + paddingBottom: "30px", + paddingTop: "30px", + backgroundColor: theme.palette.surfaceColor, + display: "flex", + flexDirection: "column", + }; - return currentOwner - } - - const onPasswordChange = () => { - const data = {"username": userSettings.username, "currentpassword": currentPassword, "newpassword": newPassword, "newpassword2": newPassword2} - const url = globalUrl+'/api/v1/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) { - setPasswordFormMessage(responseJson["reason"]) - } else { - alert.success("Changed password!") - setPasswordFormMessage("") - } - }), - ) - .catch(error => { - setPasswordFormMessage("Something went wrong.") - }); - } + const checkOwner = (data, userdata) => { + var currentOwner = false; + if (data.owner.address === userdata.eth_info.account) { + currentOwner = true; + } else { + if ( + data.top_ownerships !== undefined && + data.top_ownerships !== null && + data.top_ownerships.length === 1 + ) { + for (var key in data.top_ownerships) { + if ( + data.top_ownerships[key].owner.address === userdata.eth_info.account + ) { + currentOwner = true; + break; + } + } + } + } - const loadWorkflowOwnership = () => { - fetch(globalUrl+"/api/v1/workflows/collections/untitled-collection-103712081", { - 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!") - } + return currentOwner; + }; - return response.json() - }) - .then((responseJson) => { - //console.log("Values: ", responseJson) - - // const [selfOwnedWorkflows, setSelfOwnedWorkflows] = React.useState([]) - if (responseJson !== undefined && responseJson !== null) { - const filteredOwnerships = responseJson.filter(data => checkOwner(data, userdata) === true) - if (filteredOwnerships !== undefined && filteredOwnerships !== null && filteredOwnerships.length > 0) { - setSelfOwnedWorkflows(filteredOwnerships) - } - - var collections = [] - for (var key in responseJson) { - var collectionname = responseJson[key].collection.name - if (!collections.includes(collectionname)) { - collections.push(collectionname) - } - } - - console.log(collections) - setLoadedWorkflowCollections(collections) - setLoadedValidationWorkflows(responseJson) - - setTimeout(() => { - window.scrollTo({ - top: document.body.scrollHeight, - left: 0, - behavior: 'smooth' - }) - }, 250) - - } + const onPasswordChange = () => { + const data = { + username: userSettings.username, + currentpassword: currentPassword, + newpassword: newPassword, + newpassword2: newPassword2, + }; + const url = globalUrl + "/api/v1/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", + }, }) - .catch(error => { - console.log(error) - }) - } + .then((response) => + response.json().then((responseJson) => { + if (responseJson["success"] === false) { + setPasswordFormMessage(responseJson["reason"]); + } else { + alert.success("Changed password!"); + setPasswordFormMessage(""); + } + }) + ) + .catch((error) => { + setPasswordFormMessage("Something went wrong."); + }); + }; - 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 WORKFLOW EXECUTION :O!") - } + const loadWorkflowOwnership = () => { + fetch( + globalUrl + "/api/v1/workflows/collections/untitled-collection-103712081", + { + 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!"); + } - return response.json() - }) - .then((responseJson) => { - setUserSettings(responseJson) - }) - .catch(error => { - console.log(error) - }); - } + return response.json(); + }) + .then((responseJson) => { + //console.log("Values: ", responseJson) - 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 WORKFLOW EXECUTION :O!") - } + // const [selfOwnedWorkflows, setSelfOwnedWorkflows] = React.useState([]) + if (responseJson !== undefined && responseJson !== null) { + const filteredOwnerships = responseJson.filter( + (data) => checkOwner(data, userdata) === true + ); + if ( + filteredOwnerships !== undefined && + filteredOwnerships !== null && + filteredOwnerships.length > 0 + ) { + setSelfOwnedWorkflows(filteredOwnerships); + } - return response.json() - }) - .then((responseJson) => { - setUserSettings(responseJson) - }) - .catch(error => { - console.log(error) - }); - } + var collections = []; + for (var key in responseJson) { + var collectionname = responseJson[key].collection.name; + if (!collections.includes(collectionname)) { + collections.push(collectionname); + } + } - // Gotta be a better way of doing this rofl - const setFields = () => { - if (userdata.username !== undefined) { - if (userdata.username.length > 0) { - setUsername(userdata.username) - } - //if (userdata.firstname.length > 0) { - // setFirstname(userdata.firstname) - //} - //if (userdata.lastname.length > 0) { - // setLastname(userdata.lastname) - //} - //if (userdata.title.length > 0) { - // setTitle(userdata.title) - //} - //if (userdata.companyname.length > 0) { - // setCompanyname(userdata.companyname) - //} - //if (userdata.phone.length > 0) { - // setPhone(userdata.phone) - //} - //if (userdata.email.length > 0) { - // setEmail(userdata.email) - //} - } - } + console.log(collections); + setLoadedWorkflowCollections(collections); + setLoadedValidationWorkflows(responseJson); - const registerProviders = (userdata) => { - // Register hooks here - detectEthereumProvider() - .then((provider) => { - if (provider) { + setTimeout(() => { + window.scrollTo({ + top: document.body.scrollHeight, + left: 0, + behavior: "smooth", + }); + }, 250); + } + }) + .catch((error) => { + console.log(error); + }); + }; - if (!provider.isMetaMask) { - alert.error("Only MetaMask is supported as of now.") - return - } + 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 WORKFLOW EXECUTION :O!"); + } - // Find the ethereum network - // Get the users' account(s) - //alert.info("Connecting to MetaMask") - //console.log("Connected: ", provider.isConnected()) + return response.json(); + }) + .then((responseJson) => { + setUserSettings(responseJson); + }) + .catch((error) => { + console.log(error); + }); + }; - if (!provider.isConnected()) { - alert.error("Metamask is not connected.") - return - } + 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 WORKFLOW EXECUTION :O!"); + } - provider.on('message', (event) => { - alert.info("Ethereum message: ", event) - }) + return response.json(); + }) + .then((responseJson) => { + setUserSettings(responseJson); + }) + .catch((error) => { + console.log(error); + }); + }; - provider.on('chainChanged', (chainId) => { - console.log("Changed chain to: ", chainId) + // Gotta be a better way of doing this rofl + const setFields = () => { + if (userdata.username !== undefined) { + if (userdata.username.length > 0) { + setUsername(userdata.username); + } + //if (userdata.firstname.length > 0) { + // setFirstname(userdata.firstname) + //} + //if (userdata.lastname.length > 0) { + // setLastname(userdata.lastname) + //} + //if (userdata.title.length > 0) { + // setTitle(userdata.title) + //} + //if (userdata.companyname.length > 0) { + // setCompanyname(userdata.companyname) + //} + //if (userdata.phone.length > 0) { + // setPhone(userdata.phone) + //} + //if (userdata.email.length > 0) { + // setEmail(userdata.email) + //} + } + }; - const method = "eth_getBalance" - const params = [ - userdata.eth_info.account, - "latest" - ] - provider.request({ - method: method, - params, - }) - .then((result) => { - console.log("Got result: ", result) - if (result !== undefined && result !== null) { - userdata.eth_info.balance = result - userdata.eth_info.parsed_balance = result/1000000000000000000 - console.log("INFO: ", userdata) - setUserData(userdata) - } else { - alert.error("Couldn't find balance: ", result) - } - }) - .catch((error) => { - // If the request fails, the Promise will reject with an error. - alert.error("Failed getting info from ethereum API: "+error) - }) - }) - } - }) - } + const registerProviders = (userdata) => { + // Register hooks here + detectEthereumProvider().then((provider) => { + if (provider) { + if (!provider.isMetaMask) { + alert.error("Only MetaMask is supported as of now."); + return; + } - // This should "always" have data - useEffect(() => { - if (firstrequest) { - setFirstRequest(false) - getSettings() - //registerProviders(userdata) - } + // Find the ethereum network + // Get the users' account(s) + //alert.info("Connecting to MetaMask") + //console.log("Connected: ", provider.isConnected()) - if (Object.getOwnPropertyNames(userdata).length > 0 && (username === "" && email === "")) { - setFields() - } - }) + if (!provider.isConnected()) { + alert.error("Metamask is not connected."); + return; + } - const ParsedWorkflowView = (props) => { - const { data } = props; + provider.on("message", (event) => { + alert.info("Ethereum message: ", event); + }); - var innerPaperStyle = { - backgroundColor: theme.palette.inputColor, - display: "flex", - flexDirection: "column", - padding: "0px 0px 12px 0px", - borderRadius: theme.palette.borderRadius, - } + provider.on("chainChanged", (chainId) => { + console.log("Changed chain to: ", chainId); - const currentOwner = checkOwner(data, userdata) - if (currentOwner === true) { - innerPaperStyle.border = "3px solid #f86a3e" - } - - return ( - - - {data.name} - - {data.collection.name} - - - {data.name} - - - - ) - } + const method = "eth_getBalance"; + const params = [userdata.eth_info.account, "latest"]; + provider + .request({ + method: method, + params, + }) + .then((result) => { + console.log("Got result: ", result); + if (result !== undefined && result !== null) { + userdata.eth_info.balance = result; + userdata.eth_info.parsed_balance = result / 1000000000000000000; + console.log("INFO: ", userdata); + setUserData(userdata); + } else { + alert.error("Couldn't find balance: ", result); + } + }) + .catch((error) => { + // If the request fails, the Promise will reject with an error. + alert.error("Failed getting info from ethereum API: " + error); + }); + }); + } + }); + }; - // Random names for type & autoComplete. Didn't research :^) - var imageData = file.length > 0 ? file : fileBase64 - imageData = imageData === undefined || imageData.length === 0 ? theme.palette.defaultImage : imageData - const imageInfo = - const landingpageData = -
    - - {imageInfo} -

    Settings

    -
    - setUsername(e.target.value)} - /> -
    -
    - setFirstname(e.target.value)} - /> - setLastname(e.target.value)} - /> -
    -

    APIKEY

    - What is the API key used for? - - - - {/* + // This should "always" have data + useEffect(() => { + if (firstrequest) { + setFirstRequest(false); + getSettings(); + //registerProviders(userdata) + } + + if ( + Object.getOwnPropertyNames(userdata).length > 0 && + username === "" && + email === "" + ) { + setFields(); + } + }); + + const ParsedWorkflowView = (props) => { + const { data } = props; + + var innerPaperStyle = { + backgroundColor: theme.palette.inputColor, + display: "flex", + flexDirection: "column", + padding: "0px 0px 12px 0px", + borderRadius: theme.palette.borderRadius, + }; + + const currentOwner = checkOwner(data, userdata); + if (currentOwner === true) { + innerPaperStyle.border = "3px solid #f86a3e"; + } + + return ( + + + {data.name} + + {data.collection.name} + + {data.name} + + + ); + }; + + // Random names for type & autoComplete. Didn't research :^) + var imageData = file.length > 0 ? file : fileBase64; + imageData = + imageData === undefined || imageData.length === 0 + ? theme.palette.defaultImage + : imageData; + const imageInfo = ( + + ); + const landingpageData = ( +
    + + {imageInfo} +

    Settings

    +
    + setUsername(e.target.value)} + /> +
    +
    + setFirstname(e.target.value)} + /> + setLastname(e.target.value)} + /> +
    +

    APIKEY

    + + What is the API key used for? + + + + + {/* {

    {formMessage}

    */} -

    Password

    -
    - setCurrentPassword(e.target.value)} - /> -
    -
    - setNewPassword(e.target.value)} - /> - setNewPassword2(e.target.value)} - /> -
    - -

    {passwordFormMessage}

    - -

    Platform Earnings

    -
    -
    -
    - {userdata.eth_info !== undefined && userdata.eth_info.account !== undefined && userdata.eth_info.account.length > 0 && userdata.eth_info.parsed_balance !== undefined ? -
    - - - - - - {/*window.ethereum.fromWei(userdata.eth_info.balance, "ether")*/} - {userdata.eth_info.parsed_balance.toFixed(4)} ETH - - -
    - : null} -
    -
    - - - Owned Workflows - - - {selfOwnedWorkflows.length} - - -
    -
    -
    - {userdata !== undefined && userdata.eth_info !== undefined && userdata.eth_info.account.length > 0 ? -
    - - Network: TBD - - - Address: {userdata.eth_info.account} - - {loadedWorkflowCollections.length > 0 ? - - Collections:  - {loadedWorkflowCollections.map((data, index) => { - var collectionname = data.toLowerCase() - collectionname = collectionname.replaceAll("#", "") - collectionname = collectionname.replaceAll(" ", "-") +

    Password

    +
    + setCurrentPassword(e.target.value)} + /> +
    +
    + setNewPassword(e.target.value)} + /> + setNewPassword2(e.target.value)} + /> +
    + +

    {passwordFormMessage}

    + +

    Platform Earnings

    +
    +
    +
    + {userdata.eth_info !== undefined && + userdata.eth_info.account !== undefined && + userdata.eth_info.account.length > 0 && + userdata.eth_info.parsed_balance !== undefined ? ( +
    + + + + + + {/*window.ethereum.fromWei(userdata.eth_info.balance, "ether")*/} + {userdata.eth_info.parsed_balance.toFixed(4)} ETH + + +
    + ) : null} +
    +
    + + Owned Workflows + + {selfOwnedWorkflows.length} + + +
    +
    +
    + {userdata !== undefined && + userdata.eth_info !== undefined && + userdata.eth_info.account.length > 0 ? ( +
    + Network: TBD + + Address: {userdata.eth_info.account} + + {loadedWorkflowCollections.length > 0 ? ( + + Collections:  + {loadedWorkflowCollections.map((data, index) => { + var collectionname = data.toLowerCase(); + collectionname = collectionname.replaceAll("#", ""); + collectionname = collectionname.replaceAll(" ", "-"); - return ( - - {data}  - - ) - })} - - : null} - +
    + ) : ( + + )} +
    +
    - }} - > - Validate ownership - -
    - : - - } -
    -
    + {loadedValidationWorkflows !== undefined && + loadedValidationWorkflows !== null + ? loadedValidationWorkflows.map((data, index) => { + return ( + + + + ); + }) + : null} +
    +
    + ); - {loadedValidationWorkflows !== undefined && loadedValidationWorkflows !== null ? - loadedValidationWorkflows.map((data, index) => { - return ( - - - - ) - }) - : null} -
    -
    - - - /* + /* 0x1 1 Ethereum Main Network (Mainnet) 0x3 3 Ropsten Test Network 0x4 4 Rinkeby Test Network 0x5 5 Goerli Test Network 0x2a 42 Kovan Test Network */ - const setUser = (userId, field, value) => { - const data = { "user_id": userId } - data[field] = value + 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!") - } + 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!"); + } - return response.json() - }) - .then((responseJson) => { - if (!responseJson.success && responseJson.reason !== undefined) { - alert.error("Failed updating user: " + responseJson.reason) - } - }) - .catch(error => { - console.log(error) - }); - } + return response.json(); + }) + .then((responseJson) => { + if (!responseJson.success && responseJson.reason !== undefined) { + alert.error("Failed updating user: " + responseJson.reason); + } + }) + .catch((error) => { + console.log(error); + }); + }; - const handleEthereumTokenCreation = async () => { - const provider = await detectEthereumProvider(); - if (!provider) { - console.log('Please install MetaMask!'); - alert.error("Please download the MetaMask browser extension to authenticate fully!") - return - } + const handleEthereumTokenCreation = async () => { + const provider = await detectEthereumProvider(); + if (!provider) { + console.log("Please install MetaMask!"); + alert.error( + "Please download the MetaMask browser extension to authenticate fully!" + ); + return; + } - - if (!provider.isMetaMask) { - alert.error("Only MetaMask is supported as of now.") - return - } + if (!provider.isMetaMask) { + alert.error("Only MetaMask is supported as of now."); + return; + } - if (!provider.isConnected()) { - alert.error("Metamask is not connected.") - return - } + if (!provider.isConnected()) { + alert.error("Metamask is not connected."); + return; + } - console.log("Should make a token") - } + console.log("Should make a token"); + }; - const handleEthereumConnection = async () => { - const provider = await detectEthereumProvider(); - if (!provider) { - console.log('Please install MetaMask!'); - alert.error("Please download the MetaMask browser extension to authenticate fully!") - return - } + const handleEthereumConnection = async () => { + const provider = await detectEthereumProvider(); + if (!provider) { + console.log("Please install MetaMask!"); + alert.error( + "Please download the MetaMask browser extension to authenticate fully!" + ); + return; + } - - if (!provider.isMetaMask) { - alert.error("Only MetaMask is supported as of now.") - return - } + if (!provider.isMetaMask) { + alert.error("Only MetaMask is supported as of now."); + return; + } - // Find the ethereum network - // Get the users' account(s) - //alert.info("Connecting to MetaMask") - //console.log("Connected: ", provider.isConnected()) + // Find the ethereum network + // Get the users' account(s) + //alert.info("Connecting to MetaMask") + //console.log("Connected: ", provider.isConnected()) - if (!provider.isConnected()) { - alert.error("Metamask is not connected.") - return - } + if (!provider.isConnected()) { + alert.error("Metamask is not connected."); + return; + } - provider.on('message', (event) => { - alert.info("Ethereum message: ", event) - }) + provider.on("message", (event) => { + alert.info("Ethereum message: ", event); + }); - /* + /* params: [ { from: '0xb60e8dd61c5d32be8058bb8eb970870f07233155', @@ -831,80 +955,78 @@ const Settings = (props) => { ] */ - // https://docs.metamask.io/guide/rpc-api.html - // Gets accounts - requires previous permissions - //const method = "eth_accounts" - //const params = [] - // - // Asks for permission, and gets the accounts - var method = "eth_requestAccounts" - var params = [] - provider.request({ - method: method, - params, - }) - .then((result) => { - if (result !== undefined && result !== null && result.length > 0) { - userdata.eth_info.account = result[0] + // https://docs.metamask.io/guide/rpc-api.html + // Gets accounts - requires previous permissions + //const method = "eth_accounts" + //const params = [] + // + // Asks for permission, and gets the accounts + var method = "eth_requestAccounts"; + var params = []; + provider + .request({ + method: method, + params, + }) + .then((result) => { + if (result !== undefined && result !== null && result.length > 0) { + userdata.eth_info.account = result[0]; - // Getting and setting balance for the current user - method = "eth_getBalance" - params = [ - userdata.eth_info.account, - "latest" - ] - provider.request({ - method: method, - params, - }) - .then((result) => { - if (result !== undefined && result !== null && result.length > 0) { - userdata.eth_info.balance = result - userdata.eth_info.parsed_balance = result/1000000000000000000 - console.log(userdata.eth_info) - setUserData(userdata.eth_info) + // Getting and setting balance for the current user + method = "eth_getBalance"; + params = [userdata.eth_info.account, "latest"]; + provider + .request({ + method: method, + params, + }) + .then((result) => { + if ( + result !== undefined && + result !== null && + result.length > 0 + ) { + userdata.eth_info.balance = result; + userdata.eth_info.parsed_balance = result / 1000000000000000000; + console.log(userdata.eth_info); + setUserData(userdata.eth_info); - // Updating - //if (userdata.eth_info !== userdata.userdata.eth_info) { - //} + // Updating + //if (userdata.eth_info !== userdata.userdata.eth_info) { + //} - setUser(userdata.id, "eth_info", userdata.eth_info) - userdata.userdata.eth_info = userdata.eth_info - } else { - alert.error("Couldn't find balance: ", result) - } - // The result varies by RPC method. - // For example, this method will return a transaction hash hexadecimal string on success. - }) - .catch((error) => { - // If the request fails, the Promise will reject with an error. - //setEthInfo(userdata.eth_info) - alert.error("Failed getting info from ethereum API: "+error) - }) - } else { - alert.error("Couldn't find any user: ", result) - } - }) - .catch((error) => { - // If the request fails, the Promise will reject with an error. - alert.error("Failed getting info from ethereum API: "+error) - }) + setUser(userdata.id, "eth_info", userdata.eth_info); + userdata.userdata.eth_info = userdata.eth_info; + } else { + alert.error("Couldn't find balance: ", result); + } + // The result varies by RPC method. + // For example, this method will return a transaction hash hexadecimal string on success. + }) + .catch((error) => { + // If the request fails, the Promise will reject with an error. + //setEthInfo(userdata.eth_info) + alert.error("Failed getting info from ethereum API: " + error); + }); + } else { + alert.error("Couldn't find any user: ", result); + } + }) + .catch((error) => { + // If the request fails, the Promise will reject with an error. + alert.error("Failed getting info from ethereum API: " + error); + }); - // Gets the users' balance in WEI (one quintilionth ETH) - } + // Gets the users' balance in WEI (one quintilionth ETH) + }; - const loadedCheck = isLoaded && !firstrequest ? -
    - {landingpageData} -
    - : -
    -
    + const loadedCheck = + isLoaded && !firstrequest ? ( +
    {landingpageData}
    + ) : ( +
    + ); - return( -
    - {loadedCheck} -
    - ) -} + return
    {loadedCheck}
    ; +}; export default Settings; diff --git a/frontend/src/views/Webhooks.jsx b/frontend/src/views/Webhooks.jsx index ac30a313..64a463b9 100644 --- a/frontend/src/views/Webhooks.jsx +++ b/frontend/src/views/Webhooks.jsx @@ -1,295 +1,316 @@ -import React, { useEffect} from 'react'; +import React, { useEffect } from "react"; -import Paper from '@material-ui/core/Paper'; -import Grid from '@material-ui/core/Grid'; -import ButtonBase from '@material-ui/core/ButtonBase'; -import Button from '@material-ui/core/Button'; -import List from '@material-ui/core/List'; -import ListItem from '@material-ui/core/ListItem'; -import TextField from '@material-ui/core/TextField'; -import Select from '@material-ui/core/Select'; -import MenuItem from '@material-ui/core/MenuItem'; +import Paper from "@material-ui/core/Paper"; +import Grid from "@material-ui/core/Grid"; +import ButtonBase from "@material-ui/core/ButtonBase"; +import Button from "@material-ui/core/Button"; +import List from "@material-ui/core/List"; +import ListItem from "@material-ui/core/ListItem"; +import TextField from "@material-ui/core/TextField"; +import Select from "@material-ui/core/Select"; +import MenuItem from "@material-ui/core/MenuItem"; -import Dialog from '@material-ui/core/Dialog'; -import DialogTitle from '@material-ui/core/DialogTitle'; -import DialogActions from '@material-ui/core/DialogActions'; -import DialogContent from '@material-ui/core/DialogContent'; +import Dialog from "@material-ui/core/Dialog"; +import DialogTitle from "@material-ui/core/DialogTitle"; +import DialogActions from "@material-ui/core/DialogActions"; +import DialogContent from "@material-ui/core/DialogContent"; -import WebhookImage from '../assets/img/webhook.png'; -import KafkaImage from '../assets/img/kafka.png'; +import WebhookImage from "../assets/img/webhook.png"; +import KafkaImage from "../assets/img/kafka.png"; const Webhooks = (props) => { - const { globalUrl, isLoaded } = props; - const validtypes = ["webhook"] + const { globalUrl, isLoaded } = props; + const validtypes = ["webhook"]; - //const [hooks, setSchedules] = React.useState(hookdata); - const [hooks, setHooks] = React.useState([]); - const [modalOpen, setModalOpen] = React.useState(false); - const [newHookName, setNewHookName] = React.useState(""); - const [newHookDescription, setNewHookDescription] = React.useState(""); - const [newHookType, setNewHookType] = React.useState(""); - const [firstrequest, setFirstrequest] = React.useState(true); - const [, setModalError] = React.useState(""); + //const [hooks, setSchedules] = React.useState(hookdata); + const [hooks, setHooks] = React.useState([]); + const [modalOpen, setModalOpen] = React.useState(false); + const [newHookName, setNewHookName] = React.useState(""); + const [newHookDescription, setNewHookDescription] = React.useState(""); + const [newHookType, setNewHookType] = React.useState(""); + const [firstrequest, setFirstrequest] = React.useState(true); + const [, setModalError] = React.useState(""); - useEffect(() => { - if (firstrequest) { - setFirstrequest(false) - getAvailableHooks() - } - }) + useEffect(() => { + if (firstrequest) { + setFirstrequest(false); + getAvailableHooks(); + } + }); - const newHook = () => { - if (newHookName.length === 0) { - setModalError("Missing name in modal") - return - } + const newHook = () => { + if (newHookName.length === 0) { + setModalError("Missing name in modal"); + return; + } - if (!validtypes.includes(newHookType)) { - setModalError(newHookType + " is not a valid type. Try this: "+validtypes) - } + if (!validtypes.includes(newHookType)) { + setModalError( + newHookType + " is not a valid type. Try this: " + validtypes + ); + } - fetch(globalUrl+"/api/v1/hooks/new", { - method: "POST", - headers: {"content-type": "application/json"}, - body: JSON.stringify({"name": newHookName, "description": newHookDescription, "type": newHookType}), - credentials: "include", - }) - .then((response) => response.json()) - .then((responseJson) => { - console.log(responseJson) - setHooks([]) - }) - .catch(error => { - console.log(error) - }); - } + fetch(globalUrl + "/api/v1/hooks/new", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + name: newHookName, + description: newHookDescription, + type: newHookType, + }), + credentials: "include", + }) + .then((response) => response.json()) + .then((responseJson) => { + console.log(responseJson); + setHooks([]); + }) + .catch((error) => { + console.log(error); + }); + }; - const getAvailableHooks = () => { - fetch(globalUrl+"/api/v1/hooks", { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - credentials: "include", - }) - .then((response) => response.json()) - .then((responseJson) => { - setHooks(responseJson) - }) - .catch(error => { - console.log(error) - // window.location.pathname = "/" - }); - } + const getAvailableHooks = () => { + fetch(globalUrl + "/api/v1/hooks", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => response.json()) + .then((responseJson) => { + setHooks(responseJson); + }) + .catch((error) => { + console.log(error); + // window.location.pathname = "/" + }); + }; - const deleteHook = (id) => { - if (id === undefined) { - return - } + const deleteHook = (id) => { + if (id === undefined) { + return; + } - fetch(globalUrl+"/api/v1/hooks/"+id+"/delete", { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - credentials: "include", - }) - .then((response) => response.json()) - .then((responseJson) => { - setHooks([]) - }) - .catch(error => { - console.log(error) - }); - } + fetch(globalUrl + "/api/v1/hooks/" + id + "/delete", { + method: "DELETE", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => response.json()) + .then((responseJson) => { + setHooks([]); + }) + .catch((error) => { + console.log(error); + }); + }; - const bodyDivStyle = { - marginLeft: "20px", - marginRight: "20px", - width: "1350px", - minWidth: "1350px", - maxWidth: "1350px", - } + const bodyDivStyle = { + marginLeft: "20px", + marginRight: "20px", + width: "1350px", + minWidth: "1350px", + maxWidth: "1350px", + }; - const hookApp = (app) => { + const hookApp = (app) => { + // Might be more options, but should be webhook or MQ + const appPicture = + app.type === "webhook" ? ( + webhook + ) : ( + MQ + ); - // Might be more options, but should be webhook or MQ - const appPicture = app.type === "webhook" ? - webhook - : - MQ + return ( + + + {appPicture} + + {splitter} + + + +
    +

    {app.info.name}

    +
    +
    Desc: {app.info.description}
    +
    Status: {app.status}
    +
    + {app.action} +
    +
    +
    + ); + }; - return( - - - - {appPicture} - - - {splitter} - - - -
    -

    {app.info.name}

    -
    -
    - Desc: {app.info.description} -
    -
    - Status: {app.status} -
    -
    - - {app.action} - -
    -
    -
    - ) - } + const splitter = ( +
    + ); - const splitter =
    + const hrefStyle = { + color: "#385f71", + textDecoration: "none", + }; - const hrefStyle = { - color: "#385f71", - textDecoration: "none" - } + // FIXME - add Schedule modal + const hookPaper = (hook) => { + return ( +
    + +
    {hookApp(hook)}
    + {splitter} +
    + + + + + + + + + + +
    +
    +
    + ); + }; - // FIXME - add Schedule modal - const hookPaper = (hook) => { - return( -
    - -
    - {hookApp(hook)} -
    - {splitter} -
    - - - - - - - - - - -
    -
    -
    - ) - } + const modalView = modalOpen ? ( + { + setModalOpen(false); + }} + > + Hook configuration + + { + setNewHookName(event.target.value); + }} + color="primary" + placeholder="Name" + margin="dense" + fullWidth + /> + { + setNewHookDescription(event.target.value); + }} + color="primary" + placeholder="Description" + margin="dense" + fullWidth + /> - const modalView = modalOpen ? - {setModalOpen(false)}} - > - Hook configuration - - {setNewHookName(event.target.value)}} - color="primary" - placeholder="Name" - margin="dense" - fullWidth - /> - {setNewHookDescription(event.target.value)}} - color="primary" - placeholder="Description" - margin="dense" - fullWidth - /> + + + + + + + + ) : null; - - - - - - - - : null + const hookmap = + hooks.length > 0 ? ( +
    {hooks.map((data) => hookPaper(data))}
    + ) : ( +
    + +
    + ); - const hookmap = hooks.length > 0 ? -
    - {hooks.map(data => ( - hookPaper(data) - ))} -
    - : -
    - -
    + const hookView = ( +
    + + {hookmap} +
    + ); - const hookView = -
    - - {hookmap} -
    + const loadedCheck = isLoaded ? ( +
    + {modalView} + {hookView} +
    + ) : ( +
    + ); - const loadedCheck = isLoaded ? -
    - {modalView} - {hookView} -
    - : -
    -
    + // Maybe use gridview or something, idk + return
    {loadedCheck}
    ; +}; - - // Maybe use gridview or something, idk - return ( -
    - {loadedCheck} -
    - ) -} - -export default Webhooks +export default Webhooks; diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx index a2db3a0e..a349dce8 100644 --- a/frontend/src/views/Workflows.jsx +++ b/frontend/src/views/Workflows.jsx @@ -1,1884 +1,2471 @@ -import React, { useEffect} from 'react'; -import { makeStyles } from '@material-ui/core/styles'; -import { useTheme } from '@material-ui/core/styles'; +import React, { useEffect } from "react"; +import { makeStyles } from "@material-ui/core/styles"; +import { useTheme } from "@material-ui/core/styles"; -import {Badge, Avatar, Grid, Paper, Tooltip, Divider, Button, TextField, FormControl, IconButton, Menu, MenuItem, FormControlLabel, Chip, Switch, Typography, Zoom, CircularProgress, Dialog, DialogTitle, DialogActions, DialogContent} from '@material-ui/core'; -import {GridOn as GridOnIcon, List as ListIcon, Close as CloseIcon, Compare as CompareIcon, Maximize as MaximizeIcon, Minimize as MinimizeIcon, AddCircle as AddCircleIcon, Toc as TocIcon, Send as SendIcon, Search as SearchIcon, FileCopy as FileCopyIcon, Delete as DeleteIcon, BubbleChart as BubbleChartIcon, Restore as RestoreIcon, Cached as CachedIcon, GetApp as GetAppIcon, Apps as AppsIcon, Edit as EditIcon, MoreVert as MoreVertIcon, PlayArrow as PlayArrowIcon, Add as AddIcon, Publish as PublishIcon, CloudUpload as CloudUploadIcon, CloudDownload as CloudDownloadIcon} from '@material-ui/icons'; +import { + Badge, + Avatar, + Grid, + Paper, + Tooltip, + Divider, + Button, + TextField, + FormControl, + IconButton, + Menu, + MenuItem, + FormControlLabel, + Chip, + Switch, + Typography, + Zoom, + CircularProgress, + Dialog, + DialogTitle, + DialogActions, + DialogContent, +} from "@material-ui/core"; +import { + GridOn as GridOnIcon, + List as ListIcon, + Close as CloseIcon, + Compare as CompareIcon, + Maximize as MaximizeIcon, + Minimize as MinimizeIcon, + AddCircle as AddCircleIcon, + Toc as TocIcon, + Send as SendIcon, + Search as SearchIcon, + FileCopy as FileCopyIcon, + Delete as DeleteIcon, + BubbleChart as BubbleChartIcon, + Restore as RestoreIcon, + Cached as CachedIcon, + GetApp as GetAppIcon, + Apps as AppsIcon, + Edit as EditIcon, + MoreVert as MoreVertIcon, + PlayArrow as PlayArrowIcon, + Add as AddIcon, + Publish as PublishIcon, + CloudUpload as CloudUploadIcon, + CloudDownload as CloudDownloadIcon, +} from "@material-ui/icons"; import NestedMenuItem from "material-ui-nested-menu-item"; //import {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'; //https://next.material-ui.com/components/material-icons/ -import {DataGrid, GridToolbar} from '@material-ui/data-grid'; +import { DataGrid, GridToolbar } from "@material-ui/data-grid"; //import JSONPretty from 'react-json-pretty'; //import JSONPrettyMon from 'react-json-pretty/dist/monikai' -import Dropzone from '../components/Dropzone'; +import Dropzone from "../components/Dropzone"; -import {Link} from 'react-router-dom'; +import { Link } from "react-router-dom"; import { useAlert } from "react-alert"; -import ChipInput from 'material-ui-chip-input' -import { v4 as uuidv4 } from 'uuid'; - - -const inputColor = "#383B40" -const surfaceColor = "#27292D" -const svgSize = 24 -const imagesize = 22 - +import ChipInput from "material-ui-chip-input"; +import { v4 as uuidv4 } from "uuid"; +const inputColor = "#383B40"; +const surfaceColor = "#27292D"; +const svgSize = 24; +const imagesize = 22; const useStyles = makeStyles((theme) => ({ - datagrid: { - border: 0, - '& .MuiDataGrid-columnsContainer': { - backgroundColor: theme.palette.type === 'light' ? '#fafafa' : theme.palette.inputColor, - }, - '& .MuiDataGrid-iconSeparator': { - display: 'none', - }, - '& .MuiDataGrid-colCell, .MuiDataGrid-cell': { - borderRight: `1px solid ${ - theme.palette.type === 'light' ? 'white' : '#303030' - }`, - }, - '& .MuiDataGrid-columnsContainer, .MuiDataGrid-cell': { - borderBottom: `1px solid ${ - theme.palette.type === 'light' ? '#f0f0f0' : '#303030' - }`, - }, - '& .MuiDataGrid-cell': { - color: - theme.palette.type === 'light' - ? 'white' - : 'rgba(255,255,255,0.65)', - }, - '& .MuiPaginationItem-root, .MuiTablePagination-actions, .MuiTablePagination-caption': { - borderRadius: 0, - color: "white", - }, - }, + datagrid: { + border: 0, + "& .MuiDataGrid-columnsContainer": { + backgroundColor: + theme.palette.type === "light" ? "#fafafa" : theme.palette.inputColor, + }, + "& .MuiDataGrid-iconSeparator": { + display: "none", + }, + "& .MuiDataGrid-colCell, .MuiDataGrid-cell": { + borderRight: `1px solid ${ + theme.palette.type === "light" ? "white" : "#303030" + }`, + }, + "& .MuiDataGrid-columnsContainer, .MuiDataGrid-cell": { + borderBottom: `1px solid ${ + theme.palette.type === "light" ? "#f0f0f0" : "#303030" + }`, + }, + "& .MuiDataGrid-cell": { + color: + theme.palette.type === "light" ? "white" : "rgba(255,255,255,0.65)", + }, + "& .MuiPaginationItem-root, .MuiTablePagination-actions, .MuiTablePagination-caption": + { + borderRadius: 0, + color: "white", + }, + }, })); - -// Takes an action in Shuffle and +// Takes an action in Shuffle and // Returns information about the icon, the color etc to be used // This can be used for actions of all types export const GetIconInfo = (action) => { - // Finds the icon based on the action. Should be verbs. - const iconList = [ - {"key": "cache_add", "values": ["set_cache"]}, - {"key": "cache_get", "values": ["get_cache"]}, - {"key": "filter", "values": ["filter", "route", "router",]}, - {"key": "merge", "values": ["join", "merge"]}, - {"key": "search", "values": ["search", "find", "locate", "index", "analyze", "anal", "match"]}, - {"key": "list", "values": ["list", "head", "options"]}, - {"key": "download", "values": ["capture", "get", "download", "return", "hello_world", "curl", "request", "export", "preview"]}, - {"key": "add", "values": ["add"]}, - {"key": "delete", "values": ["delete", "remove", "clear", "clean",]}, - {"key": "send", "values": ["send", "dispatch", "mail", "forward", "post", "submit", "mark", "set", "release", ]}, - {"key": "repeat", "values": ["repeat", "retry", "pause", "skip", "copy", "replicat", ]}, - {"key": "execute", "values": ["execute", "run", "play", "raise",]}, - {"key": "extract", "values": ["extract", "unpack", "decompress", "open"]}, - {"key": "inflate", "values": ["inflate", "pack", "compress",]}, - {"key": "edit", "values": ["modify", "update", "create", "edit", "put", "patch", "change", "replace", "conver", "map", "format", "escape", "describe", ]}, - {"key": "compare", "values": ["compare", "convert", "to", "filter", "translate", "parse"]}, - {"key": "close", "values": ["close", "stop", "cancel", "block", ]}, - ] + // Finds the icon based on the action. Should be verbs. + const iconList = [ + { key: "cache_add", values: ["set_cache"] }, + { key: "cache_get", values: ["get_cache"] }, + { key: "filter", values: ["filter", "route", "router"] }, + { key: "merge", values: ["join", "merge"] }, + { + key: "search", + values: ["search", "find", "locate", "index", "analyze", "anal", "match"], + }, + { key: "list", values: ["list", "head", "options"] }, + { + key: "download", + values: [ + "capture", + "get", + "download", + "return", + "hello_world", + "curl", + "request", + "export", + "preview", + ], + }, + { key: "add", values: ["add"] }, + { key: "delete", values: ["delete", "remove", "clear", "clean"] }, + { + key: "send", + values: [ + "send", + "dispatch", + "mail", + "forward", + "post", + "submit", + "mark", + "set", + "release", + ], + }, + { + key: "repeat", + values: ["repeat", "retry", "pause", "skip", "copy", "replicat"], + }, + { key: "execute", values: ["execute", "run", "play", "raise"] }, + { key: "extract", values: ["extract", "unpack", "decompress", "open"] }, + { key: "inflate", values: ["inflate", "pack", "compress"] }, + { + key: "edit", + values: [ + "modify", + "update", + "create", + "edit", + "put", + "patch", + "change", + "replace", + "conver", + "map", + "format", + "escape", + "describe", + ], + }, + { + key: "compare", + values: ["compare", "convert", "to", "filter", "translate", "parse"], + }, + { key: "close", values: ["close", "stop", "cancel", "block"] }, + ]; - var selectedKey = "" - if (action.name === undefined || action.name === null) { - } else { - const actionname = action.name.toLowerCase() - for (var key in iconList) { - //console.log(iconList[key], actionname) - const found = iconList[key].values.find(value => actionname.includes(value)) - if (found !== null && found !== undefined) { - selectedKey = iconList[key].key - break - } - } - } - - // Some of these are manually parsed or created instead of material ui - //M8 0C3.58 0 0 1.79 0 4C0 6.21 3.58 8 8 8C12.42 8 16 6.21 16 4C16 1.79 12.42 0 8 0ZM0 6V9C0 11.21 3.58 13 8 13C12.42 13 16 11.21 16 9V6C16 8.21 12.42 10 8 10C3.58 10 0 8.21 0 6ZM0 11V14C0 16.21 3.58 18 8 18C9.41 18 10.79 17.81 12 17.46V14.46C10.79 14.81 9.41 15 8 15C3.58 15 0 13.21 0 11ZM17 11V14H14V16H17V19H19V16H22V14H19V11 - //https://www.figma.com/file/uCfnMs5w6wnLx6ehPHEV74/Figma-Material-Design-System-v3_0?node-id=834%3A21 - //COLORS: https://www.pinterest.co.uk/pin/326299935499972946/ - const defaultColor = "#f76b1c" - const defaultGradient = ["#fad961", "#f76b1c"] - const parsedIcons = { - "cache_add": { - "icon": "M11 3C6.58 3 3 4.79 3 7C3 9.21 6.58 11 11 11C15.42 11 19 9.21 19 7C19 4.79 15.42 3 11 3ZM3 9V12C3 14.21 6.58 16 11 16C15.42 16 19 14.21 19 12V9C19 11.21 15.42 13 11 13C6.58 13 3 11.21 3 9ZM3 14V17C3 19.21 6.58 21 11 21C12.41 21 13.79 20.81 15 20.46V17.46C13.79 17.81 12.41 18 11 18C6.58 18 3 16.21 3 14ZM20 14V17H17V19H20V22H22V19H25V17H22V14", - "iconColor": "white", - "iconBackgroundColor": "#8acc3f", - "originalIcon": "", - "fillGradient": ["#8acc3f", "#459622"] - }, - "cache_get": { - "icon": "M12 2C7.58 2 4 3.79 4 6C4 8.06 7.13 9.74 11.15 9.96C12.45 8.7 14.19 8 16 8C16.8 8 17.59 8.14 18.34 8.41C19.37 7.74 20 6.91 20 6C20 3.79 16.42 2 12 2ZM4 8V11C4 12.68 6.08 14.11 9 14.71C9.06 13.7 9.32 12.72 9.77 11.82C6.44 11.34 4 9.82 4 8ZM15.93 9.94C14.75 9.95 13.53 10.4 12.46 11.46C8.21 15.71 13.71 22.5 18.75 19.17L23.29 23.71L24.71 22.29L20.17 17.75C22.66 13.97 19.47 9.93 15.93 9.94ZM15.9 12C17.47 11.95 19 13.16 19 15C19 15.7956 18.6839 16.5587 18.1213 17.1213C17.5587 17.6839 16.7956 18 16 18C13.33 18 12 14.77 13.88 12.88C14.47 12.29 15.19 12 15.9 12ZM4 13V16C4 18.05 7.09 19.72 11.06 19.95C10.17 19.07 9.54 17.95 9.22 16.74C6.18 16.17 4 14.72 4 13Z", - "iconColor": "white", - "iconBackgroundColor": "#8acc3f", - "originalIcon": "", - "fillGradient": ["#8acc3f", "#459622"] - }, - "repeat": { - "icon": "M19 8l-4 4h3c0 3.31-2.69 6-6 6-1.01 0-1.97-.25-2.8-.7l-1.46 1.46C8.97 19.54 10.43 20 12 20c4.42 0 8-3.58 8-8h3l-4-4zM6 12c0-3.31 2.69-6 6-6 1.01 0 1.97.25 2.8.7l1.46-1.46C15.03 4.46 13.57 4 12 4c-4.42 0-8 3.58-8 8H1l4 4 4-4H6z", - "iconColor": "white", - "iconBackgroundColor": defaultColor, - "originalIcon": , - }, - "add": { - "icon": "M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z", - "iconColor": "white", - "iconBackgroundColor": defaultColor, - "originalIcon": , - }, - "edit": { - "icon": "M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.9959.9959 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z", - "iconColor": "white", - "iconBackgroundColor": defaultColor, - "originalIcon": , - }, - "filter": { - "icon": "M4.25 5.61C6.27 8.2 10 13 10 13v6c0 .55.45 1 1 1h2c.55 0 1-.45 1-1v-6s3.72-4.8 5.74-7.39c.51-.66.04-1.61-.79-1.61H5.04c-.83 0-1.3.95-.79 1.61z", - "iconColor": "white", - "iconBackgroundColor": "#f5515f", - "originalIcon": "", - "fillGradient": ["#f5515f", "#a1051d"], - }, - "merge": { - "icon": "M17 20.41 18.41 19 15 15.59 13.59 17 17 20.41zM7.5 8H11v5.59L5.59 19 7 20.41l6-6V8h3.5L12 3.5 7.5 8z", - "iconColor": "white", - "iconBackgroundColor": "#f5515f", - "originalIcon": "", - "fillGradient": ["#f5515f", "#a1051d"], - }, - "compare": { - "icon": "M10 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h5v2h2V1h-2v2zm0 15H5l5-6v6zm9-15h-5v2h5v13l-5-6v9h5c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z", - "iconColor": "white", - "iconBackgroundColor": defaultColor, - "originalIcon": , - }, - "extract": { - "icon": "M3 3h18v2H3z", - "iconColor": "white", - "iconBackgroundColor": defaultColor, - "originalIcon": , - }, - "inflate": { - "icon": "M6 19h12v2H6z", - "iconColor": "white", - "iconBackgroundColor": defaultColor, - "originalIcon": , - }, - "list": { - "icon": "M3 9h14V7H3v2zm0 4h14v-2H3v2zm0 4h14v-2H3v2zm16 0h2v-2h-2v2zm0-10v2h2V7h-2zm0 6h2v-2h-2v2z", - "iconColor": "white", - "iconBackgroundColor": defaultColor, - "originalIcon": , - }, - "execute": { - "icon": "M8 5v14l11-7z", - "iconColor": "white", - "iconBackgroundColor": defaultColor, - "originalIcon": , - }, - "delete": { - "icon": "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z", - "iconColor": "white", - "iconBackgroundColor": "#03030e", - "originalIcon": , - "fillGradient": ["#03030e", "#205d66"] - }, - "close": { - "icon": "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z", - "iconColor": "white", - "iconBackgroundColor": "#03030e", - "originalIcon": , - "fillGradient": ["#03030e", "#205d66"] - }, - "send": { - "icon": "M2.01 21L23 12 2.01 3 2 10l15 2-15 2z", - "iconColor": "white", - "iconBackgroundColor": "#0373da", - "originalIcon": , - "fillGradient": ["#0bc8bf", "#0373da"] - }, - "download": { - "icon": "M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z", - "iconColor": "white", - "iconBackgroundColor": "#0373da", - "originalIcon": , - "fillGradient": ["#0bc8bf", "#0373da"] - }, - "search": { - "icon": "M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z", - "iconColor": "white", - "iconBackgroundColor": "green", - "originalIcon": , - } - } - - - var selectedItem = parsedIcons[selectedKey] - if (selectedItem === undefined || selectedItem === null) { - return { - "icon": "", - "iconColor": "", - "iconBackground": "black", - "originalIcon": "", - } - } - - if (selectedItem.fillGradient === undefined) { - selectedItem.fillGradient = defaultGradient - selectedItem.iconBackgroundColor = defaultColor - } - - if (selectedItem.icon === "" || selectedItem.icon === undefined) { - console.log(`MISSING PATH FOR ${selectedKey} (find in scope): `, selectedItem.originalIcon.type.type) - } - - if ((selectedItem.originalIcon === undefined || selectedItem.originalIcon === "") && (selectedItem.icon !== "" && selectedItem.icon !== undefined)) { - const svg_pin = - selectedItem.originalIcon = svg_pin - - } - - return selectedItem -} - - - -const chipStyle = { - backgroundColor: "#3d3f43", marginRight: 5, paddingLeft: 5, paddingRight: 5, height: 28, cursor: "pointer", borderColor: "#3d3f43", color: "white", -} - - - -export const validateJson = (showResult) => { - //showResult = showResult.split(" None").join(" \"None\"") - showResult = showResult.split(" False").join(" false") - showResult = showResult.split(" True").join(" true") - - var jsonvalid = true - try { - if (!showResult.includes("{") && !showResult.includes("[")) { - jsonvalid = false - } - } catch (e) { - showResult = showResult.split("'").join("\"") - - try { - if (!showResult.includes("{") && !showResult.includes("[")) { - jsonvalid = false - } - } catch (e) { - jsonvalid = false - } - } - - var result = showResult - try { - const result = jsonvalid ? JSON.parse(showResult) : showResult - } catch (e) { - //console.log("Failed parsing JSON even though its valid: ", e) - jsonvalid = false - } - - //console.log("VALID: ", jsonvalid, result) - return { - "valid": jsonvalid, - "result": result, - } -} - -const Workflows = (props) => { - const { globalUrl, isLoggedIn, isLoaded, userdata} = props; - document.title = "Shuffle - Workflows" - const theme = useTheme(); - const alert = useAlert() - const classes = useStyles(theme); - const imgSize = 60 - - const referenceUrl = globalUrl+"/api/v1/hooks/" - - var upload = "" - - const [workflows, setWorkflows] = React.useState([]); - const [filteredWorkflows, setFilteredWorkflows] = React.useState([]); - const [selectedWorkflow, setSelectedWorkflow] = React.useState({}); - const [firstrequest, setFirstrequest] = React.useState(true) - const [workflowDone, setWorkflowDone] = React.useState(false) - const [selectedWorkflowId, setSelectedWorkflowId] = React.useState("") - - const [field1, setField1] = React.useState("") - const [field2, setField2] = React.useState("") - const [downloadUrl, setDownloadUrl] = React.useState("https://github.com/frikky/shuffle-workflows") - const [downloadBranch, setDownloadBranch] = React.useState("master") - const [loadWorkflowsModalOpen, setLoadWorkflowsModalOpen] = React.useState(false) - const [exportModalOpen, setExportModalOpen] = React.useState(false) - const [exportData, setExportData] = React.useState(""); - - const [modalOpen, setModalOpen] = React.useState(false); - const [newWorkflowName, setNewWorkflowName] = React.useState(""); - const [newWorkflowDescription, setNewWorkflowDescription] = React.useState(""); - const [newWorkflowTags, setNewWorkflowTags] = React.useState([]); - - const [defaultReturnValue, setDefaultReturnValue] = React.useState(""); - - const [deleteModalOpen, setDeleteModalOpen] = React.useState(false); - const [publishModalOpen, setPublishModalOpen] = React.useState(false); - const [editingWorkflow, setEditingWorkflow] = React.useState({}) - const [importLoading, setImportLoading] = React.useState(false) - const [isDropzone, setIsDropzone] = React.useState(false); - const [view, setView] = React.useState("grid") - const [filters, setFilters] = React.useState([]) - const [submitLoading, setSubmitLoading] = React.useState(false) - const [actionImageList, setActionImageList] = React.useState([]) - - const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" - - const findWorkflow = (filters) => { - if (filters.length === 0) { - setFilteredWorkflows(workflows) - return - } - - var newWorkflows = [] - for (var workflowKey in workflows) { - const curWorkflow = workflows[workflowKey] - - var found = [false] - if (curWorkflow.tags === undefined || curWorkflow.tags === null) { - found = filters.map(filter => curWorkflow.name.toLowerCase().includes(filter)) - } else { - found = filters.map(filter => { - const newfilter = filter.toLowerCase() - if (filter === undefined) { - return false - } - - if (curWorkflow.name.toLowerCase().includes(filter.toLowerCase())) { - return true - } else if (curWorkflow.tags.includes(filter)) { - return true - } else if (curWorkflow.owner === filter) { - return true - } else if (curWorkflow.org_id === filter) { - return true - } else if (curWorkflow.actions !== null && curWorkflow.actions !== undefined) { - for (var key in curWorkflow.actions) { - const action = curWorkflow.actions[key] - if (action.app_name.toLowerCase() === newfilter || action.app_name.toLowerCase().includes(newfilter)) { - return true - } - } - } - - return false - }) - } - - if (found.every(v => v === true)) { - newWorkflows.push(curWorkflow) - continue - } - } - - if (newWorkflows.length !== workflows.length) { - setFilteredWorkflows(newWorkflows) - } - } - - const addFilter = (data) => { - if (data === null || data === undefined) { - return - } - - if (data.includes("<") && data.includes(">")) { - return - } - - if (filters.includes(data) || filters.includes(data.toLowerCase())) { - return - } - - filters.push(data.toLowerCase()) - setFilters(filters) - - findWorkflow(filters) - } - - const removeFilter = (index) => { - var newfilters = filters - - if (index < 0) { - console.log("Can't handle index: ", index) - return - } - - - newfilters.splice(index, 1) - - if (newfilters.length === 0) { - newfilters = [] - setFilters(newfilters) - } else { - setFilters(newfilters) - } - - findWorkflow(newfilters) - } - - const exportVerifyModal = exportModalOpen ? - { - setExportModalOpen(false) - setSelectedWorkflow({}) - }} - PaperProps={{ - style: { - backgroundColor: surfaceColor, - color: "white", - minWidth: 500, - padding: 30, - }, - }} - > - -
    - Want to auto-sanitize this workflow before exporting? -
    -
    - - - This will make potentially sensitive fields such as username, password, url etc. empty - - - - - -
    - : null - - const publishModal = publishModalOpen ? - { - setPublishModalOpen(false) - setSelectedWorkflow({}) - }} - PaperProps={{ - style: { - backgroundColor: surfaceColor, - color: "white", - minWidth: 500, - padding: 50, - }, - }} - > - -
    - Are you sure you want to PUBLISH this workflow? -
    -
    - -
    - - Before publishing, we will sanitize all inputs, remove references to you, randomize ID's and remove your authentication. - -
    - - -
    - -
    - : null - - const deleteModal = deleteModalOpen ? - { - setDeleteModalOpen(false) - setSelectedWorkflowId("") - }} - PaperProps={{ - style: { - backgroundColor: surfaceColor, - color: "white", - minWidth: 500, - }, - }} - > - -
    - Are you sure you want to delete this workflow?
    Other workflows relying on this one may stop working -
    - - - - - - -
    - : null - - 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 upload. Please wait while we validate the workflows") - - try { - reader.addEventListener('load', (e) => { - var data = e.target.result; - setIsDropzone(false) - try { - data = JSON.parse(reader.result) - } catch (e) { - alert.error("Invalid JSON: "+e) - return - } - - // Initialize the workflow itself - setNewWorkflow(data.name, data.description, data.tags, data.default_return_value, {}, false) - .then((response) => { - if (response !== undefined) { - // SET THE FULL THING - data.id = response.id - - // Actually create it - setNewWorkflow(data.name, data.description, data.tags, data.default_return_value, data, false) - .then((response) => { - if (response !== undefined) { - alert.success(`Successfully imported ${data.name}`) - } - }) - } - }) - .catch(error => { - alert.error("Import error: "+error.toString()) - }); - }) - } catch (e) { - console.log("Error in dropzone: ", e) - } - - reader.readAsText(files[0]); + var selectedKey = ""; + if (action.name === undefined || action.name === null) { + } else { + const actionname = action.name.toLowerCase(); + for (var key in iconList) { + //console.log(iconList[key], actionname) + const found = iconList[key].values.find((value) => + actionname.includes(value) + ); + if (found !== null && found !== undefined) { + selectedKey = iconList[key].key; + break; + } + } } - useEffect(() => { - if (isDropzone) { - setIsDropzone(false); - } + // Some of these are manually parsed or created instead of material ui + //M8 0C3.58 0 0 1.79 0 4C0 6.21 3.58 8 8 8C12.42 8 16 6.21 16 4C16 1.79 12.42 0 8 0ZM0 6V9C0 11.21 3.58 13 8 13C12.42 13 16 11.21 16 9V6C16 8.21 12.42 10 8 10C3.58 10 0 8.21 0 6ZM0 11V14C0 16.21 3.58 18 8 18C9.41 18 10.79 17.81 12 17.46V14.46C10.79 14.81 9.41 15 8 15C3.58 15 0 13.21 0 11ZM17 11V14H14V16H17V19H19V16H22V14H19V11 + //https://www.figma.com/file/uCfnMs5w6wnLx6ehPHEV74/Figma-Material-Design-System-v3_0?node-id=834%3A21 + //COLORS: https://www.pinterest.co.uk/pin/326299935499972946/ + const defaultColor = "#f76b1c"; + const defaultGradient = ["#fad961", "#f76b1c"]; + const parsedIcons = { + cache_add: { + icon: "M11 3C6.58 3 3 4.79 3 7C3 9.21 6.58 11 11 11C15.42 11 19 9.21 19 7C19 4.79 15.42 3 11 3ZM3 9V12C3 14.21 6.58 16 11 16C15.42 16 19 14.21 19 12V9C19 11.21 15.42 13 11 13C6.58 13 3 11.21 3 9ZM3 14V17C3 19.21 6.58 21 11 21C12.41 21 13.79 20.81 15 20.46V17.46C13.79 17.81 12.41 18 11 18C6.58 18 3 16.21 3 14ZM20 14V17H17V19H20V22H22V19H25V17H22V14", + iconColor: "white", + iconBackgroundColor: "#8acc3f", + originalIcon: "", + fillGradient: ["#8acc3f", "#459622"], + }, + cache_get: { + icon: "M12 2C7.58 2 4 3.79 4 6C4 8.06 7.13 9.74 11.15 9.96C12.45 8.7 14.19 8 16 8C16.8 8 17.59 8.14 18.34 8.41C19.37 7.74 20 6.91 20 6C20 3.79 16.42 2 12 2ZM4 8V11C4 12.68 6.08 14.11 9 14.71C9.06 13.7 9.32 12.72 9.77 11.82C6.44 11.34 4 9.82 4 8ZM15.93 9.94C14.75 9.95 13.53 10.4 12.46 11.46C8.21 15.71 13.71 22.5 18.75 19.17L23.29 23.71L24.71 22.29L20.17 17.75C22.66 13.97 19.47 9.93 15.93 9.94ZM15.9 12C17.47 11.95 19 13.16 19 15C19 15.7956 18.6839 16.5587 18.1213 17.1213C17.5587 17.6839 16.7956 18 16 18C13.33 18 12 14.77 13.88 12.88C14.47 12.29 15.19 12 15.9 12ZM4 13V16C4 18.05 7.09 19.72 11.06 19.95C10.17 19.07 9.54 17.95 9.22 16.74C6.18 16.17 4 14.72 4 13Z", + iconColor: "white", + iconBackgroundColor: "#8acc3f", + originalIcon: "", + fillGradient: ["#8acc3f", "#459622"], + }, + repeat: { + icon: "M19 8l-4 4h3c0 3.31-2.69 6-6 6-1.01 0-1.97-.25-2.8-.7l-1.46 1.46C8.97 19.54 10.43 20 12 20c4.42 0 8-3.58 8-8h3l-4-4zM6 12c0-3.31 2.69-6 6-6 1.01 0 1.97.25 2.8.7l1.46-1.46C15.03 4.46 13.57 4 12 4c-4.42 0-8 3.58-8 8H1l4 4 4-4H6z", + iconColor: "white", + iconBackgroundColor: defaultColor, + originalIcon: , + }, + add: { + icon: "M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z", + iconColor: "white", + iconBackgroundColor: defaultColor, + originalIcon: , + }, + edit: { + icon: "M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.9959.9959 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z", + iconColor: "white", + iconBackgroundColor: defaultColor, + originalIcon: , + }, + filter: { + icon: "M4.25 5.61C6.27 8.2 10 13 10 13v6c0 .55.45 1 1 1h2c.55 0 1-.45 1-1v-6s3.72-4.8 5.74-7.39c.51-.66.04-1.61-.79-1.61H5.04c-.83 0-1.3.95-.79 1.61z", + iconColor: "white", + iconBackgroundColor: "#f5515f", + originalIcon: "", + fillGradient: ["#f5515f", "#a1051d"], + }, + merge: { + icon: "M17 20.41 18.41 19 15 15.59 13.59 17 17 20.41zM7.5 8H11v5.59L5.59 19 7 20.41l6-6V8h3.5L12 3.5 7.5 8z", + iconColor: "white", + iconBackgroundColor: "#f5515f", + originalIcon: "", + fillGradient: ["#f5515f", "#a1051d"], + }, + compare: { + icon: "M10 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h5v2h2V1h-2v2zm0 15H5l5-6v6zm9-15h-5v2h5v13l-5-6v9h5c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z", + iconColor: "white", + iconBackgroundColor: defaultColor, + originalIcon: , + }, + extract: { + icon: "M3 3h18v2H3z", + iconColor: "white", + iconBackgroundColor: defaultColor, + originalIcon: , + }, + inflate: { + icon: "M6 19h12v2H6z", + iconColor: "white", + iconBackgroundColor: defaultColor, + originalIcon: , + }, + list: { + icon: "M3 9h14V7H3v2zm0 4h14v-2H3v2zm0 4h14v-2H3v2zm16 0h2v-2h-2v2zm0-10v2h2V7h-2zm0 6h2v-2h-2v2z", + iconColor: "white", + iconBackgroundColor: defaultColor, + originalIcon: , + }, + execute: { + icon: "M8 5v14l11-7z", + iconColor: "white", + iconBackgroundColor: defaultColor, + originalIcon: , + }, + delete: { + icon: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z", + iconColor: "white", + iconBackgroundColor: "#03030e", + originalIcon: , + fillGradient: ["#03030e", "#205d66"], + }, + close: { + icon: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z", + iconColor: "white", + iconBackgroundColor: "#03030e", + originalIcon: , + fillGradient: ["#03030e", "#205d66"], + }, + send: { + icon: "M2.01 21L23 12 2.01 3 2 10l15 2-15 2z", + iconColor: "white", + iconBackgroundColor: "#0373da", + originalIcon: , + fillGradient: ["#0bc8bf", "#0373da"], + }, + download: { + icon: "M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z", + iconColor: "white", + iconBackgroundColor: "#0373da", + originalIcon: , + fillGradient: ["#0bc8bf", "#0373da"], + }, + search: { + icon: "M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z", + iconColor: "white", + iconBackgroundColor: "green", + originalIcon: , + }, + }; + + var selectedItem = parsedIcons[selectedKey]; + if (selectedItem === undefined || selectedItem === null) { + return { + icon: "", + iconColor: "", + iconBackground: "black", + originalIcon: "", + }; + } + + if (selectedItem.fillGradient === undefined) { + selectedItem.fillGradient = defaultGradient; + selectedItem.iconBackgroundColor = defaultColor; + } + + if (selectedItem.icon === "" || selectedItem.icon === undefined) { + console.log( + `MISSING PATH FOR ${selectedKey} (find in scope): `, + selectedItem.originalIcon.type.type + ); + } + + if ( + (selectedItem.originalIcon === undefined || + selectedItem.originalIcon === "") && + selectedItem.icon !== "" && + selectedItem.icon !== undefined + ) { + const svg_pin = ( + + + + ); + selectedItem.originalIcon = svg_pin; + } + + return selectedItem; +}; + +const chipStyle = { + backgroundColor: "#3d3f43", + marginRight: 5, + paddingLeft: 5, + paddingRight: 5, + height: 28, + cursor: "pointer", + borderColor: "#3d3f43", + color: "white", +}; + +export const validateJson = (showResult) => { + //showResult = showResult.split(" None").join(" \"None\"") + showResult = showResult.split(" False").join(" false"); + showResult = showResult.split(" True").join(" true"); + + var jsonvalid = true; + try { + if (!showResult.includes("{") && !showResult.includes("[")) { + jsonvalid = false; + } + } catch (e) { + showResult = showResult.split("'").join('"'); + + try { + if (!showResult.includes("{") && !showResult.includes("[")) { + jsonvalid = false; + } + } catch (e) { + jsonvalid = false; + } + } + + var result = showResult; + try { + const result = jsonvalid ? JSON.parse(showResult) : showResult; + } catch (e) { + //console.log("Failed parsing JSON even though its valid: ", e) + jsonvalid = false; + } + + //console.log("VALID: ", jsonvalid, result) + return { + valid: jsonvalid, + result: result, + }; +}; + +const Workflows = (props) => { + const { globalUrl, isLoggedIn, isLoaded, userdata } = props; + document.title = "Shuffle - Workflows"; + const theme = useTheme(); + const alert = useAlert(); + const classes = useStyles(theme); + const imgSize = 60; + + const referenceUrl = globalUrl + "/api/v1/hooks/"; + + var upload = ""; + + const [workflows, setWorkflows] = React.useState([]); + const [filteredWorkflows, setFilteredWorkflows] = React.useState([]); + const [selectedWorkflow, setSelectedWorkflow] = React.useState({}); + const [firstrequest, setFirstrequest] = React.useState(true); + const [workflowDone, setWorkflowDone] = React.useState(false); + const [selectedWorkflowId, setSelectedWorkflowId] = React.useState(""); + + const [field1, setField1] = React.useState(""); + const [field2, setField2] = React.useState(""); + const [downloadUrl, setDownloadUrl] = React.useState( + "https://github.com/frikky/shuffle-workflows" + ); + const [downloadBranch, setDownloadBranch] = React.useState("master"); + const [loadWorkflowsModalOpen, setLoadWorkflowsModalOpen] = + React.useState(false); + const [exportModalOpen, setExportModalOpen] = React.useState(false); + const [exportData, setExportData] = React.useState(""); + + const [modalOpen, setModalOpen] = React.useState(false); + const [newWorkflowName, setNewWorkflowName] = React.useState(""); + const [newWorkflowDescription, setNewWorkflowDescription] = + React.useState(""); + const [newWorkflowTags, setNewWorkflowTags] = React.useState([]); + + const [defaultReturnValue, setDefaultReturnValue] = React.useState(""); + + const [deleteModalOpen, setDeleteModalOpen] = React.useState(false); + const [publishModalOpen, setPublishModalOpen] = React.useState(false); + const [editingWorkflow, setEditingWorkflow] = React.useState({}); + const [importLoading, setImportLoading] = React.useState(false); + const [isDropzone, setIsDropzone] = React.useState(false); + const [view, setView] = React.useState("grid"); + const [filters, setFilters] = React.useState([]); + const [submitLoading, setSubmitLoading] = React.useState(false); + const [actionImageList, setActionImageList] = React.useState([]); + + const isCloud = + window.location.host === "localhost:3002" || + window.location.host === "shuffler.io"; + + const findWorkflow = (filters) => { + if (filters.length === 0) { + setFilteredWorkflows(workflows); + return; + } + + var newWorkflows = []; + for (var workflowKey in workflows) { + const curWorkflow = workflows[workflowKey]; + + var found = [false]; + if (curWorkflow.tags === undefined || curWorkflow.tags === null) { + found = filters.map((filter) => + curWorkflow.name.toLowerCase().includes(filter) + ); + } else { + found = filters.map((filter) => { + const newfilter = filter.toLowerCase(); + if (filter === undefined) { + return false; + } + + if (curWorkflow.name.toLowerCase().includes(filter.toLowerCase())) { + return true; + } else if (curWorkflow.tags.includes(filter)) { + return true; + } else if (curWorkflow.owner === filter) { + return true; + } else if (curWorkflow.org_id === filter) { + return true; + } else if ( + curWorkflow.actions !== null && + curWorkflow.actions !== undefined + ) { + for (var key in curWorkflow.actions) { + const action = curWorkflow.actions[key]; + if ( + action.app_name.toLowerCase() === newfilter || + action.app_name.toLowerCase().includes(newfilter) + ) { + return true; + } + } + } + + return false; + }); + } + + if (found.every((v) => v === true)) { + newWorkflows.push(curWorkflow); + continue; + } + } + + if (newWorkflows.length !== workflows.length) { + setFilteredWorkflows(newWorkflows); + } + }; + + const addFilter = (data) => { + if (data === null || data === undefined) { + return; + } + + if (data.includes("<") && data.includes(">")) { + return; + } + + if (filters.includes(data) || filters.includes(data.toLowerCase())) { + return; + } + + filters.push(data.toLowerCase()); + setFilters(filters); + + findWorkflow(filters); + }; + + const removeFilter = (index) => { + var newfilters = filters; + + if (index < 0) { + console.log("Can't handle index: ", index); + return; + } + + newfilters.splice(index, 1); + + if (newfilters.length === 0) { + newfilters = []; + setFilters(newfilters); + } else { + setFilters(newfilters); + } + + findWorkflow(newfilters); + }; + + const exportVerifyModal = exportModalOpen ? ( + { + setExportModalOpen(false); + setSelectedWorkflow({}); + }} + PaperProps={{ + style: { + backgroundColor: surfaceColor, + color: "white", + minWidth: 500, + padding: 30, + }, + }} + > + +
    + Want to auto-sanitize this workflow before exporting? +
    +
    + + + This will make potentially sensitive fields such as username, + password, url etc. empty + + + + +
    + ) : null; + + const publishModal = publishModalOpen ? ( + { + setPublishModalOpen(false); + setSelectedWorkflow({}); + }} + PaperProps={{ + style: { + backgroundColor: surfaceColor, + color: "white", + minWidth: 500, + padding: 50, + }, + }} + > + +
    + Are you sure you want to PUBLISH this workflow? +
    +
    + +
    + + Before publishing, we will sanitize all inputs, remove references to + you, randomize ID's and remove your authentication. + +
    + + +
    +
    + ) : null; + + const deleteModal = deleteModalOpen ? ( + { + setDeleteModalOpen(false); + setSelectedWorkflowId(""); + }} + PaperProps={{ + style: { + backgroundColor: surfaceColor, + color: "white", + minWidth: 500, + }, + }} + > + +
    + Are you sure you want to delete this workflow?
    + Other workflows relying on this one may stop working +
    + + + + + +
    + ) : null; + + 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 upload. Please wait while we validate the workflows"); + + try { + reader.addEventListener("load", (e) => { + var data = e.target.result; + setIsDropzone(false); + try { + data = JSON.parse(reader.result); + } catch (e) { + alert.error("Invalid JSON: " + e); + return; + } + + // Initialize the workflow itself + setNewWorkflow( + data.name, + data.description, + data.tags, + data.default_return_value, + {}, + false + ) + .then((response) => { + if (response !== undefined) { + // SET THE FULL THING + data.id = response.id; + + // Actually create it + setNewWorkflow( + data.name, + data.description, + data.tags, + data.default_return_value, + data, + false + ).then((response) => { + if (response !== undefined) { + alert.success(`Successfully imported ${data.name}`); + } + }); + } + }) + .catch((error) => { + alert.error("Import error: " + error.toString()); + }); + }); + } catch (e) { + console.log("Error in dropzone: ", e); + } + + reader.readAsText(files[0]); + }; + + useEffect(() => { + if (isDropzone) { + setIsDropzone(false); + } }, [isDropzone]); - - const getAvailableWorkflows = () => { - fetch(globalUrl+"/api/v1/workflows", { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - credentials: "include", + const getAvailableWorkflows = () => { + 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!: ", response.status) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for workflows :O!: ", response.status); - if (isCloud) { - window.location.pathname = "/login" - } + if (isCloud) { + window.location.pathname = "/login"; + } - alert.info("Failed getting workflows.") - setWorkflowDone(true) + alert.info("Failed getting workflows."); + setWorkflowDone(true); - return - } - return response.json() - }) - .then((responseJson) => { + return; + } + return response.json(); + }) + .then((responseJson) => { + if (responseJson !== undefined) { + setWorkflows(responseJson); - if (responseJson !== undefined) { - setWorkflows(responseJson) + if (responseJson !== undefined) { + var actionnamelist = []; + var parsedactionlist = []; + for (var key in responseJson) { + for (var actionkey in responseJson[key].actions) { + const action = responseJson[key].actions[actionkey]; + //console.log("Action: ", action) + if (actionnamelist.includes(action.app_name)) { + continue; + } - if (responseJson !== undefined) { - var actionnamelist = [] - var parsedactionlist = [] - for (var key in responseJson) { - for (var actionkey in responseJson[key].actions) { - const action = responseJson[key].actions[actionkey] - //console.log("Action: ", action) - if (actionnamelist.includes(action.app_name)) { - continue - } + actionnamelist.push(action.app_name); + parsedactionlist.push(action); + } + } - actionnamelist.push(action.app_name) - parsedactionlist.push(action) - } - } + //console.log(parsedactionlist) + setActionImageList(parsedactionlist); + } - //console.log(parsedactionlist) - setActionImageList(parsedactionlist) - } + setFilteredWorkflows(responseJson); + setWorkflowDone(true); + } else { + if (isLoggedIn) { + alert.error("An error occurred while loading workflows"); + } - setFilteredWorkflows(responseJson) - setWorkflowDone(true) - } else { - if (isLoggedIn) { - alert.error("An error occurred while loading workflows") - } + return; + } + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; - return - } + // eslint-disable-next-line react-hooks/exhaustive-deps + useEffect(() => { + if (workflows.length <= 0 && firstrequest) { + const tmpView = localStorage.getItem("view"); + if (tmpView !== undefined && tmpView !== null) { + setView(tmpView); + } + setFirstrequest(false); + getAvailableWorkflows(); + } + }); - }) - .catch(error => { - alert.error(error.toString()) - }); - } + const viewStyle = { + color: "#ffffff", + width: "100%", + display: "flex", + minWidth: 1024, + maxWidth: 1024, + margin: "auto", + }; - // eslint-disable-next-line react-hooks/exhaustive-deps - useEffect(() => { - if (workflows.length <= 0 && firstrequest) { - const tmpView = localStorage.getItem('view'); - if (tmpView !== undefined && tmpView !== null) { - setView(tmpView) - } + const emptyWorkflowStyle = { + paddingTop: "200px", + width: 1024, + margin: "auto", + }; - setFirstrequest(false) - getAvailableWorkflows() - } - }) + const boxStyle = { + padding: "20px 20px 20px 20px", + width: "100%", + height: "250px", + color: "white", + backgroundColor: surfaceColor, + display: "flex", + flexDirection: "column", + }; - const viewStyle = { - color: "#ffffff", - width: "100%", - display: "flex", - minWidth: 1024, - maxWidth: 1024, - margin: "auto", - } + const paperAppContainer = { + display: "flex", + flexWrap: "wrap", + alignContent: "space-between", + marginTop: 5, + }; - const emptyWorkflowStyle = { - paddingTop: "200px", - width: 1024, - margin: "auto", - } + const paperAppStyle = { + minHeight: 130, + maxHeight: 130, + overflow: "hidden", + width: "100%", + color: "white", + backgroundColor: surfaceColor, + padding: "12px 12px 0px 15px", + borderRadius: 5, + display: "flex", + boxSizing: "border-box", + position: "relative", + }; - const boxStyle = { - padding: "20px 20px 20px 20px", - width: "100%", - height: "250px", - color: "white", - backgroundColor: surfaceColor, - display: "flex", - flexDirection: "column", - } + const gridContainer = { + height: "auto", + color: "white", + margin: "10px", + backgroundColor: surfaceColor, + }; + const workflowActionStyle = { + display: "flex", + width: 160, + height: 44, + justifyContent: "space-between", + }; - const paperAppContainer = { - display: "flex", - flexWrap: 'wrap', - alignContent: "space-between", - marginTop: 5, - } + const exportAllWorkflows = () => { + for (var key in workflows) { + exportWorkflow(workflows[key], false); + } + }; - const paperAppStyle = { - minHeight: 130, - maxHeight: 130, - overflow: "hidden", - width: "100%", - color: "white", - backgroundColor: surfaceColor, - padding: "12px 12px 0px 15px", - borderRadius: 5, - display: "flex", - boxSizing: "border-box", - position: "relative", - } + const deduplicateIds = (data) => { + if (data.triggers !== null && data.triggers !== undefined) { + for (var key in data.triggers) { + const trigger = data.triggers[key]; + if (trigger.app_name === "Shuffle Workflow") { + if (trigger.parameters.length > 2) { + trigger.parameters[2].value = ""; + } + } + if (trigger.status === "running") { + trigger.status = "stopped"; + } - const gridContainer = { - height: "auto", - color: "white", - margin: "10px", - backgroundColor: surfaceColor, - } + const newId = uuidv4(); + if (trigger.trigger_type === "WEBHOOK") { + if ( + trigger.parameters !== undefined && + trigger.parameters !== null && + trigger.parameters.length === 2 + ) { + trigger.parameters[0].value = + referenceUrl + "webhook_" + trigger.id; + trigger.parameters[1].value = "webhook_" + trigger.id; + } else if ( + trigger.parameters !== undefined && + trigger.parameters !== null && + trigger.parameters.length === 3 + ) { + trigger.parameters[0].value = + referenceUrl + "webhook_" + trigger.id; + trigger.parameters[1].value = "webhook_" + trigger.id; + // FIXME: Add auth here? + } else { + alert.info("Something is wrong with the webhook in the copy"); + } + } - const workflowActionStyle = { - display: "flex", - width: 160, - height: 44, - justifyContent: "space-between", - } + for (var branchkey in data.branches) { + const branch = data.branches[branchkey]; + if (branch.source_id === trigger.id) { + branch.source_id = newId; + } - const exportAllWorkflows = () => { - for (var key in workflows) { - exportWorkflow(workflows[key], false) - } - } + if (branch.destination_id === trigger.id) { + branch.destination_id = newId; + } + } - const deduplicateIds = (data) => { - if (data.triggers !== null && data.triggers !== undefined) { - for (var key in data.triggers) { - const trigger = data.triggers[key] - if (trigger.app_name === "Shuffle Workflow") { - if (trigger.parameters.length > 2) { - trigger.parameters[2].value = "" - } - } - - if (trigger.status === "running") { - trigger.status = "stopped" - } + trigger.environment = isCloud ? "cloud" : "Shuffle"; + trigger.id = newId; + } + } - const newId = uuidv4() - if (trigger.trigger_type === "WEBHOOK") { - if (trigger.parameters !== undefined && trigger.parameters !== null && trigger.parameters.length === 2) { - trigger.parameters[0].value = referenceUrl+"webhook_"+trigger.id - trigger.parameters[1].value = "webhook_"+trigger.id - } else if (trigger.parameters !== undefined && trigger.parameters !== null && trigger.parameters.length === 3) { - trigger.parameters[0].value = referenceUrl+"webhook_"+trigger.id - trigger.parameters[1].value = "webhook_"+trigger.id - // FIXME: Add auth here? - } else { - alert.info("Something is wrong with the webhook in the copy") - } - } + if (data.actions !== null && data.actions !== undefined) { + for (key in data.actions) { + data.actions[key].authentication_id = ""; - for (var branchkey in data.branches) { - const branch = data.branches[branchkey] - if (branch.source_id === trigger.id) { - branch.source_id = newId - } + for (var subkey in data.actions[key].parameters) { + const param = data.actions[key].parameters[subkey]; + if ( + param.name.includes("key") || + param.name.includes("user") || + param.name.includes("pass") || + param.name.includes("api") || + param.name.includes("auth") || + param.name.includes("secret") || + param.name.includes("domain") || + param.name.includes("url") || + param.name.includes("mail") + ) { + // FIXME: This may be a vuln if api-keys are generated that start with $ + if (param.value.startsWith("$")) { + console.log("Skipping field, as it's referencing a variable"); + } else { + param.value = ""; + param.is_valid = false; + } + } + } - if (branch.destination_id === trigger.id) { - branch.destination_id = newId - } - } + const newId = uuidv4(); + for (branchkey in data.branches) { + const branch = data.branches[branchkey]; + if (branch.source_id === data.actions[key].id) { + branch.source_id = newId; + } - trigger.environment = isCloud ? "cloud" : "Shuffle" - trigger.id = newId - } - } + if (branch.destination_id === data.actions[key].id) { + branch.destination_id = newId; + } + } - if (data.actions !== null && data.actions !== undefined) { - for (key in data.actions) { - data.actions[key].authentication_id = "" + if (data.actions[key].id === data.start) { + data.start = newId; + } - for (var subkey in data.actions[key].parameters) { - const param = data.actions[key].parameters[subkey] - if (param.name.includes("key") || param.name.includes("user") || param.name.includes("pass") || param.name.includes("api") || param.name.includes("auth") || param.name.includes("secret") || param.name.includes("domain") || param.name.includes("url") || param.name.includes("mail")) { - // FIXME: This may be a vuln if api-keys are generated that start with $ - if (param.value.startsWith("$")) { - console.log("Skipping field, as it's referencing a variable") - } else { - param.value = "" - param.is_valid = false - } - } - } + data.actions[key].environment = ""; + data.actions[key].id = newId; + } + } - const newId = uuidv4() - for ( branchkey in data.branches) { - const branch = data.branches[branchkey] - if (branch.source_id === data.actions[key].id) { - branch.source_id = newId - } + if ( + data.workflow_variables !== null && + data.workflow_variables !== undefined + ) { + for (key in data.workflow_variables) { + const param = data.workflow_variables[key]; + if ( + param.name.includes("key") || + param.name.includes("user") || + param.name.includes("pass") || + param.name.includes("api") || + param.name.includes("auth") || + param.name.includes("secret") || + param.name.includes("email") + ) { + param.value = ""; + param.is_valid = false; + } + } + } - if (branch.destination_id === data.actions[key].id) { - branch.destination_id = newId - } - } + return data; + }; - if (data.actions[key].id === data.start) { - data.start = newId - } + const sanitizeWorkflow = (data) => { + data = JSON.parse(JSON.stringify(data)); + data["owner"] = ""; + console.log("Sanitize start: ", data); + data = deduplicateIds(data); - data.actions[key].environment = "" - data.actions[key].id = newId - } - } + data["org"] = []; + data["org_id"] = ""; + data["execution_org"] = {}; - if (data.workflow_variables !== null && data.workflow_variables !== undefined) { - for (key in data.workflow_variables) { - const param = data.workflow_variables[key] - if (param.name.includes("key") || param.name.includes("user") || param.name.includes("pass") || param.name.includes("api") || param.name.includes("auth") || param.name.includes("secret") || param.name.includes("email")) { - param.value = "" - param.is_valid = false - } - } - } + // These are backwards.. True = saved before. Very confuse. + data["previously_saved"] = false; + data["first_save"] = false; + console.log("Sanitize end: ", data); - return data - } + return data; + }; - const sanitizeWorkflow = (data) => { - data = JSON.parse(JSON.stringify(data)) - data["owner"] = "" - console.log("Sanitize start: ", data) - data = deduplicateIds(data) + const exportWorkflow = (data, sanitize) => { + data = JSON.parse(JSON.stringify(data)); + let exportFileDefaultName = data.name + ".json"; - data["org"] = [] - data["org_id"] = "" - data["execution_org"] = {} + if (sanitize === true) { + data = sanitizeWorkflow(data); - // These are backwards.. True = saved before. Very confuse. - data["previously_saved"] = false - data["first_save"] = false - console.log("Sanitize end: ", data) + if (data.subflows !== null && data.subflows !== undefined) { + alert.info( + "Not exporting with subflows when sanitizing. Please manually export them." + ); + data.subflows = []; + } - return data - } + // for (var key in data.subflows) { + // if (data.sublof + // } + //} + } - const exportWorkflow = (data, sanitize) => { - data = JSON.parse(JSON.stringify(data)) - let exportFileDefaultName = data.name+'.json'; + // Add correct ID's for triggers + // Add mag - if (sanitize === true) { - data = sanitizeWorkflow(data) + 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(); + }; - if (data.subflows !== null && data.subflows !== undefined) { - alert.info("Not exporting with subflows when sanitizing. Please manually export them.") - data.subflows = [] - } + const publishWorkflow = (data) => { + data = JSON.parse(JSON.stringify(data)); + data = sanitizeWorkflow(data); + alert.info("Sanitizing and publishing " + data.name); - // for (var key in data.subflows) { - // if (data.sublof - // } - //} - } - - // Add correct ID's for triggers - // Add mag - - 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(); - } - - const publishWorkflow = (data) => { - data = JSON.parse(JSON.stringify(data)) - data = sanitizeWorkflow(data) - alert.info("Sanitizing and publishing "+data.name) - - // This ALWAYS talks to Shuffle cloud - fetch(globalUrl+"/api/v1/workflows/"+data.id+"/publish", { - 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 publish :O!") - } else { - if (isCloud) { - alert.success("Successfully published workflow") - } else { - alert.success("Successfully published workflow to https://shuffler.io") - } - } - - return response.json() - }) - .then((responseJson) => { - if (responseJson.reason !== undefined) { - alert.error("Failed publishing: ", responseJson.reason) - } - - getAvailableWorkflows() + // This ALWAYS talks to Shuffle cloud + fetch(globalUrl + "/api/v1/workflows/" + data.id + "/publish", { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(data), + credentials: "include", }) - .catch(error => { - alert.error(error.toString()) - }) - } + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for workflow publish :O!"); + } else { + if (isCloud) { + alert.success("Successfully published workflow"); + } else { + alert.success( + "Successfully published workflow to https://shuffler.io" + ); + } + } - const copyWorkflow = (data) => { - data = JSON.parse(JSON.stringify(data)) - alert.success("Copying workflow "+data.name) - data.id = "" - data.name = data.name+"_copy" - data = deduplicateIds(data) + return response.json(); + }) + .then((responseJson) => { + if (responseJson.reason !== undefined) { + alert.error("Failed publishing: ", responseJson.reason); + } + getAvailableWorkflows(); + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; - fetch(globalUrl+"/api/v1/workflows", { - 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 workflows :O!") - return - } - return response.json() - }) - .then(() => { - setTimeout(() => { - getAvailableWorkflows() - }, 1000) + const copyWorkflow = (data) => { + data = JSON.parse(JSON.stringify(data)); + alert.success("Copying workflow " + data.name); + data.id = ""; + data.name = data.name + "_copy"; + data = deduplicateIds(data); + + fetch(globalUrl + "/api/v1/workflows", { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(data), + credentials: "include", }) - .catch(error => { - alert.error(error.toString()) - }) - } + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for workflows :O!"); + return; + } + return response.json(); + }) + .then(() => { + setTimeout(() => { + getAvailableWorkflows(); + }, 1000); + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; + const deleteWorkflow = (id) => { + fetch(globalUrl + "/api/v1/workflows/" + 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 setting workflows :O!"); + alert.error("Failed deleting workflow"); + } else { + alert.success("Deleted workflow " + id); + } - const deleteWorkflow = (id) => { - fetch(globalUrl+"/api/v1/workflows/"+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 setting workflows :O!") - alert.error("Failed deleting workflow") - } else { - alert.success("Deleted workflow "+id) - } + return response.json(); + }) + .then(() => { + setTimeout(() => { + getAvailableWorkflows(); + }, 1000); + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; - return response.json() - }) - .then(() => { - setTimeout(() => { - getAvailableWorkflows() - }, 1000) - }) - .catch(error => { - alert.error(error.toString()) - }); - } + const handleChipClick = (e) => { + addFilter(e.target.innerHTML); + }; + const NewWorkflowPaper = () => { + const [hover, setHover] = React.useState(false); - const handleChipClick = (e) => { - addFilter(e.target.innerHTML) - } + const innerColor = "rgba(255,255,255,0.3)"; + const setupPaperStyle = { + minHeight: paperAppStyle.minHeight, + width: paperAppStyle.width, + color: innerColor, + padding: paperAppStyle.padding, + borderRadius: paperAppStyle.borderRadius, + display: "flex", + boxSizing: "border-box", + position: "relative", + border: `2px solid ${innerColor}`, + cursor: "pointer", + backgroundColor: hover ? "rgba(39,41,45,0.5)" : "rgba(39,41,45,1)", + }; + return ( + + setModalOpen(true)} + onMouseOver={() => { + setHover(true); + }} + onMouseOut={() => { + setHover(false); + }} + > + + + + + + + + ); + }; - const NewWorkflowPaper = () => { - const [hover, setHover] = React.useState(false) + const WorkflowPaper = (props) => { + const { data } = props; + const [open, setOpen] = React.useState(false); + const [anchorEl, setAnchorEl] = React.useState(null); - const innerColor = "rgba(255,255,255,0.3)" - const setupPaperStyle = { - minHeight: paperAppStyle.minHeight, - width: paperAppStyle.width, - color: innerColor, - padding: paperAppStyle.padding, - borderRadius: paperAppStyle.borderRadius, - display: "flex", - boxSizing: "border-box", - position: "relative", - border: `2px solid ${innerColor}`, - cursor: "pointer", - backgroundColor: hover ? "rgba(39,41,45,0.5)" : "rgba(39,41,45,1)", - } + var boxColor = "#FECC00"; + if (data.is_valid) { + boxColor = "#86c142"; + } - return( - - setModalOpen(true)} onMouseOver={() => {setHover(true)}} onMouseOut={() => {setHover(false)}}> - - - - - - - - ) - } + if (!data.previously_saved) { + boxColor = "#f85a3e"; + } - const WorkflowPaper = (props) => { - const { data } = props; - const [open, setOpen] = React.useState(false); - const [anchorEl, setAnchorEl] = React.useState(null); + const menuClick = (event) => { + setOpen(!open); + setAnchorEl(event.currentTarget); + }; + var parsedName = data.name; + if ( + parsedName !== undefined && + parsedName !== null && + parsedName.length > 20 + ) { + parsedName = parsedName.slice(0, 21) + ".."; + } + const actions = data.actions !== null ? data.actions.length : 0; + const [triggers, subflows] = getWorkflowMeta(data); - var boxColor = "#FECC00" - if (data.is_valid) { - boxColor = "#86c142" - } - - if (!data.previously_saved) { - boxColor = "#f85a3e" - } - - const menuClick = (event) => { - setOpen(!open) - setAnchorEl(event.currentTarget); - } - - var parsedName = data.name - if (parsedName !== undefined && parsedName !== null && parsedName.length > 20) { - parsedName = parsedName.slice(0,21)+".." - } - - const actions = data.actions !== null ? data.actions.length : 0 - const [triggers, subflows] = getWorkflowMeta(data) - - const workflowMenuButtons = { - setOpen(false) - setAnchorEl(null) - }} - > - { - setModalOpen(true) - setEditingWorkflow(data) - setNewWorkflowName(data.name) - setNewWorkflowDescription(data.description) - setDefaultReturnValue(data.default_return_value) - if (data.tags !== undefined && data.tags !== null) { - setNewWorkflowTags(JSON.parse(JSON.stringify(data.tags))) - } - }} key={"change"}> - - {"Change details"} - - { - setSelectedWorkflow(data) - setPublishModalOpen(true) - }} key={"publish"}> - - {"Publish Workflow"} - - { - copyWorkflow(data) - setOpen(false) - }} key={"duplicate"}> - - {"Duplicate Workflow"} - - {/*= 0} style={{backgroundColor: inputColor, color: "white"}} onClick={() => { + const workflowMenuButtons = ( + { + setOpen(false); + setAnchorEl(null); + }} + > + { + setModalOpen(true); + setEditingWorkflow(data); + setNewWorkflowName(data.name); + setNewWorkflowDescription(data.description); + setDefaultReturnValue(data.default_return_value); + if (data.tags !== undefined && data.tags !== null) { + setNewWorkflowTags(JSON.parse(JSON.stringify(data.tags))); + } + }} + key={"change"} + > + + {"Change details"} + + { + setSelectedWorkflow(data); + setPublishModalOpen(true); + }} + key={"publish"} + > + + {"Publish Workflow"} + + { + copyWorkflow(data); + setOpen(false); + }} + key={"duplicate"} + > + + {"Duplicate Workflow"} + + {/*= 0} style={{backgroundColor: inputColor, color: "white"}} onClick={() => { //copyWorkflow(data) //setOpen(false) }} key={"duplicate"}> {"Copy to Child Org"} */} - { - setExportModalOpen(true) + { + setExportModalOpen(true); - if (data.triggers !== null && data.triggers !== undefined) { - var newSubflows = [] - for (var key in data.triggers) { - const trigger = data.triggers[key] + if (data.triggers !== null && data.triggers !== undefined) { + var newSubflows = []; + for (var key in data.triggers) { + const trigger = data.triggers[key]; - if (trigger.parameters !== null && trigger.parameters !== undefined) { - for (var subkey in trigger.parameters) { - const param = trigger.parameters[subkey] - if (param.name === "workflow" && param.value !== data.id && !newSubflows.includes(param.value)) { - newSubflows.push(param.value) - } - } - } - } + if ( + trigger.parameters !== null && + trigger.parameters !== undefined + ) { + for (var subkey in trigger.parameters) { + const param = trigger.parameters[subkey]; + if ( + param.name === "workflow" && + param.value !== data.id && + !newSubflows.includes(param.value) + ) { + newSubflows.push(param.value); + } + } + } + } - var parsedworkflows = [] - for (var key in newSubflows) { - const foundWorkflow = workflows.find(workflow => workflow.id === newSubflows[key]) - if (foundWorkflow !== undefined && foundWorkflow !== null) { - parsedworkflows.push(foundWorkflow) - } - } - - if (parsedworkflows.length > 0) { - console.log("Appending subflows during export: ", parsedworkflows.length) - data.subflows = parsedworkflows - } - } + var parsedworkflows = []; + for (var key in newSubflows) { + const foundWorkflow = workflows.find( + (workflow) => workflow.id === newSubflows[key] + ); + if (foundWorkflow !== undefined && foundWorkflow !== null) { + parsedworkflows.push(foundWorkflow); + } + } - setExportData(data) - setOpen(false) - }} key={"export"}> - - {"Export Workflow"} - - { - setDeleteModalOpen(true) - setSelectedWorkflowId(data.id) - setOpen(false) - }} key={"delete"}> - - {"Delete Workflow"} - - + if (parsedworkflows.length > 0) { + console.log( + "Appending subflows during export: ", + parsedworkflows.length + ); + data.subflows = parsedworkflows; + } + } - var image = "" - var orgName = "" - var orgId = "" - if (userdata.orgs !== undefined) { - const foundOrg = userdata.orgs.find(org => org.id === data["org_id"]) - if (foundOrg !== undefined && foundOrg !== null) { - //position: "absolute", bottom: 5, right: -5, - const imageStyle = { - width: imagesize, - height: imagesize, - pointerEvents: "none", - marginLeft: data.creator_org !== undefined && data.creator_org.length > 0 ? 20 : 0, - borderRadius: 10, - border: foundOrg.id === userdata.active_org.id ? `3px solid ${boxColor}` : null, - cursor: "pointer", - marginRight: 10 } + setExportData(data); + setOpen(false); + }} + key={"export"} + > + + {"Export Workflow"} + + { + setDeleteModalOpen(true); + setSelectedWorkflowId(data.id); + setOpen(false); + }} + key={"delete"} + > + + {"Delete Workflow"} + + + ); - image = foundOrg.image === "" ? - {foundOrg.name} - : - {foundOrg.name} { - }}/> + var image = ""; + var orgName = ""; + var orgId = ""; + if (userdata.orgs !== undefined) { + const foundOrg = userdata.orgs.find((org) => org.id === data["org_id"]); + if (foundOrg !== undefined && foundOrg !== null) { + //position: "absolute", bottom: 5, right: -5, + const imageStyle = { + width: imagesize, + height: imagesize, + pointerEvents: "none", + marginLeft: + data.creator_org !== undefined && data.creator_org.length > 0 + ? 20 + : 0, + borderRadius: 10, + border: + foundOrg.id === userdata.active_org.id + ? `3px solid ${boxColor}` + : null, + cursor: "pointer", + marginRight: 10, + }; - orgName = foundOrg.name - orgId = foundOrg.id - } - } + image = + foundOrg.image === "" ? ( + {foundOrg.name} + ) : ( + {foundOrg.name} {}} + /> + ); - return ( - - -
    - - - -
    { - addFilter(orgId) - }}> - {image} -
    -
    - - - - {parsedName} - - - -
    - - - - - - {actions} - - - - - - - - {triggers} - - - - - { - if (subflows === 0) { - alert.info("No subflows for "+data.name) - return - } + orgName = foundOrg.name; + orgId = foundOrg.id; + } + } - var newWorkflows = [data] - for (var key in data.triggers) { - const trigger = data.triggers[key] - if (trigger.app_name !== "Shuffle Workflow") { - continue - } + return ( + + +
    + + + +
    { + addFilter(orgId); + }} + > + {image} +
    +
    + + + + {parsedName} + + + +
    + + + + + + {actions} + + + + + + + + {triggers} + + + + + { + if (subflows === 0) { + alert.info("No subflows for " + data.name); + return; + } - if (trigger.parameters !== undefined && trigger.parameters !== null && trigger.parameters.length > 0 && trigger.parameters[0].name === "workflow") { - const newWorkflow = workflows.find(item => item.id === trigger.parameters[0].value) - if (newWorkflow !== null && newWorkflow !== undefined) { - newWorkflows.push(newWorkflow) - continue - } - } - } + var newWorkflows = [data]; + for (var key in data.triggers) { + const trigger = data.triggers[key]; + if (trigger.app_name !== "Shuffle Workflow") { + continue; + } - setFilters(["Subflows of "+data.name]) - setFilteredWorkflows(newWorkflows) - }}> - - - - - {subflows} - - - - - - {data.tags !== undefined ? - data.tags.map((tag, index) => { - if (index >= 3) { - return null - } + if ( + trigger.parameters !== undefined && + trigger.parameters !== null && + trigger.parameters.length > 0 && + trigger.parameters[0].name === "workflow" + ) { + const newWorkflow = workflows.find( + (item) => item.id === trigger.parameters[0].value + ); + if (newWorkflow !== null && newWorkflow !== undefined) { + newWorkflows.push(newWorkflow); + continue; + } + } + } + setFilters(["Subflows of " + data.name]); + setFilteredWorkflows(newWorkflows); + }} + > + + + + + {subflows} + + + + + + {data.tags !== undefined + ? data.tags.map((tag, index) => { + if (index >= 3) { + return null; + } - return ( - - ) - }) - : null} - -
    - {data.actions !== undefined && data.actions !== null ? - - - - - - {workflowMenuButtons} - - - : null} - - - ) - } + return ( + + ); + }) + : null} + + + {data.actions !== undefined && data.actions !== null ? ( + + + + + + {workflowMenuButtons} + + + ) : null} + + + ); + }; + // Can create and set workflows + const setNewWorkflow = ( + name, + description, + tags, + defaultReturnValue, + editingWorkflow, + redirect + ) => { + var method = "POST"; + var extraData = ""; + var workflowdata = {}; + if (editingWorkflow.id !== undefined) { + console.log("Building original workflow"); + method = "PUT"; + extraData = "/" + editingWorkflow.id + "?skip_save=true"; + workflowdata = editingWorkflow; + console.log("REMOVING OWNER"); + workflowdata["owner"] = ""; + // FIXME: Loop triggers and turn them off? + } - // Can create and set workflows - const setNewWorkflow = (name, description, tags, defaultReturnValue, editingWorkflow, redirect) => { + workflowdata["name"] = name; + workflowdata["description"] = description; + if (tags !== undefined) { + workflowdata["tags"] = tags; + } - var method = "POST" - var extraData = "" - var workflowdata = {} + if (defaultReturnValue !== undefined) { + workflowdata["default_return_value"] = defaultReturnValue; + } - if (editingWorkflow.id !== undefined) { - console.log("Building original workflow") - method = "PUT" - extraData = "/"+editingWorkflow.id+"?skip_save=true" - workflowdata = editingWorkflow - - console.log("REMOVING OWNER") - workflowdata["owner"] = "" - // FIXME: Loop triggers and turn them off? - } - - workflowdata["name"] = name - workflowdata["description"] = description - if (tags !== undefined) { - workflowdata["tags"] = tags - } - - if (defaultReturnValue !== undefined) { - workflowdata["default_return_value"] = defaultReturnValue - } - - - - return fetch(globalUrl+"/api/v1/workflows"+extraData, { - method: method, - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - body: JSON.stringify(workflowdata), - credentials: "include", + return fetch(globalUrl + "/api/v1/workflows" + extraData, { + method: method, + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(workflowdata), + credentials: "include", }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for workflows :O!") - return - } - setSubmitLoading(false) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for workflows :O!"); + return; + } + setSubmitLoading(false); - return response.json() - }) - .then((responseJson) => { - if (method === "POST" && redirect) { - window.location.pathname = "/workflows/"+responseJson["id"] - setModalOpen(false) - } else if (!redirect) { - // Update :) - setTimeout(() => { - getAvailableWorkflows() - }, 1000) - setImportLoading(false) - setModalOpen(false) - } else { - alert.info("Successfully changed basic info for workflow") - setModalOpen(false) - } + return response.json(); + }) + .then((responseJson) => { + if (method === "POST" && redirect) { + window.location.pathname = "/workflows/" + responseJson["id"]; + setModalOpen(false); + } else if (!redirect) { + // Update :) + setTimeout(() => { + getAvailableWorkflows(); + }, 1000); + setImportLoading(false); + setModalOpen(false); + } else { + alert.info("Successfully changed basic info for workflow"); + setModalOpen(false); + } - return responseJson - }) - .catch(error => { - alert.error(error.toString()) - setImportLoading(false) - setModalOpen(false) - setSubmitLoading(false) - }); - } + return responseJson; + }) + .catch((error) => { + alert.error(error.toString()); + setImportLoading(false); + setModalOpen(false); + setSubmitLoading(false); + }); + }; + const importFiles = (event) => { + console.log("Importing!"); - const importFiles = (event) => { - console.log("Importing!") + setImportLoading(true); + if (event.target.files.length > 0) { + for (var key in event.target.files) { + const file = event.target.files[key]; + if (file.type !== "application/json") { + if (file.type !== undefined) { + alert.error("File has to contain valid json"); + } - setImportLoading(true) - if (event.target.files.length > 0) { - for (var key in event.target.files) { - const file = event.target.files[key] - if (file.type !== "application/json") { - if (file.type !== undefined) { - alert.error("File has to contain valid json") - } + continue; + } - continue - } + const reader = new FileReader(); + // Waits for the read + reader.addEventListener("load", (event) => { + var data = reader.result; + try { + data = JSON.parse(reader.result); + } catch (e) { + alert.error("Invalid JSON: " + e); + setImportLoading(false); + return; + } - const reader = new FileReader() - // Waits for the read - reader.addEventListener('load', (event) => { - var data = reader.result - try { - data = JSON.parse(reader.result) - } catch (e) { - alert.error("Invalid JSON: "+e) - setImportLoading(false) - return - } + // Initialize the workflow itself + setNewWorkflow( + data.name, + data.description, + data.tags, + data.default_return_value, + {}, + false + ) + .then((response) => { + if (response !== undefined) { + // SET THE FULL THING + data.id = response.id; + data.first_save = false; + data.previously_saved = false; + data.is_valid = false; - // Initialize the workflow itself - setNewWorkflow(data.name, data.description, data.tags, data.default_return_value, {}, false) - .then((response) => { - if (response !== undefined) { - // SET THE FULL THING - data.id = response.id - data.first_save = false - data.previously_saved = false - data.is_valid = false + // Actually create it + setNewWorkflow( + data.name, + data.description, + data.tags, + data.default_return_value, + data, + false + ).then((response) => { + if (response !== undefined) { + alert.success("Successfully imported " + data.name); + } + }); + } + }) + .catch((error) => { + alert.error("Import error: " + error.toString()); + }); + }); - // Actually create it - setNewWorkflow(data.name, data.description, data.tags, data.default_return_value, data, false) - .then((response) => { - if (response !== undefined) { - alert.success("Successfully imported "+data.name) - } - }) - } - }) - .catch(error => { - alert.error("Import error: "+error.toString()) - }); - }) + // Actually reads + reader.readAsText(file); + } + } - // Actually reads - reader.readAsText(file) - } - } + setLoadWorkflowsModalOpen(false); + }; - setLoadWorkflowsModalOpen(false) - } + const getWorkflowMeta = (data) => { + let triggers = 0; + let subflows = 0; + if ( + data.triggers !== undefined && + data.triggers !== null && + data.triggers.length > 0 + ) { + triggers = data.triggers.length; + for (let key in data.triggers) { + if (data.triggers[key].app_name === "Shuffle Workflow") { + subflows += 1; + } + } + } - const getWorkflowMeta = (data) => { - let triggers = 0 - let subflows = 0 - if (data.triggers !== undefined && data.triggers !== null && data.triggers.length > 0) { - triggers = data.triggers.length - for (let key in data.triggers) { - if (data.triggers[key].app_name === "Shuffle Workflow") { - subflows += 1 - } - } - } + return [triggers, subflows]; + }; - return [triggers, subflows] - } + const WorkflowListView = () => { + let workflowData = ""; + if (workflows.length > 0) { + const columns = [ + { + field: "image", + headerName: "Logo", + width: 50, + sortable: false, + renderCell: (params) => { + const data = params.row.record; - const WorkflowListView = () => { - let workflowData = ""; - if (workflows.length > 0) { - const columns = [ - { field: 'image', headerName: 'Logo', width: 50, sortable: false, renderCell: (params) => { - const data = params.row.record + var boxColor = "#FECC00"; + if (data.is_valid) { + boxColor = "#86c142"; + } - var boxColor = "#FECC00" - if (data.is_valid) { - boxColor = "#86c142" - } + if (!data.previously_saved) { + boxColor = "#f85a3e"; + } - if (!data.previously_saved) { - boxColor = "#f85a3e" - } + var image = ""; + if (userdata.orgs !== undefined) { + const foundOrg = userdata.orgs.find( + (org) => org.id === data["org_id"] + ); + if (foundOrg !== undefined && foundOrg !== null) { + //position: "absolute", bottom: 5, right: -5, + const imageStyle = { + width: imagesize + 7, + height: imagesize + 7, + pointerEvents: "none", + marginLeft: + data.creator_org !== undefined && + data.creator_org.length > 0 + ? 20 + : 0, + borderRadius: 10, + border: + foundOrg.id === userdata.active_org.id + ? `3px solid ${boxColor}` + : null, + cursor: "pointer", + marginTop: 5, + }; - var image = "" - if (userdata.orgs !== undefined) { - const foundOrg = userdata.orgs.find(org => org.id === data["org_id"]) - if (foundOrg !== undefined && foundOrg !== null) { - //position: "absolute", bottom: 5, right: -5, - const imageStyle = {width: imagesize+7, height: imagesize+7, pointerEvents: "none", marginLeft: data.creator_org !== undefined && data.creator_org.length > 0 ? 20 : 0, borderRadius: 10, border: foundOrg.id === userdata.active_org.id ? `3px solid ${boxColor}` : null, cursor: "pointer", marginTop: 5, } + // + image = + foundOrg.image === "" ? ( + {foundOrg.name} + ) : ( + {foundOrg.name} { + //setFilteredWorkflows(newWorkflows) + }} + /> + ); + } + } - // - image = foundOrg.image === "" ? - {foundOrg.name} - : - {foundOrg.name} { - //setFilteredWorkflows(newWorkflows) - }}/> + return
    {image}
    ; + }, + }, + { + field: "title", + headerName: "Title", + width: 330, + renderCell: (params) => { + const data = params.row.record; - } - } + return ( + + + {data.name} + + + ); + }, + }, - return ( -
    - {image} -
    - ) - }}, - { field: 'title', headerName: 'Title', width: 330, renderCell: (params) => { - const data = params.row.record + { + field: "options", + headerName: "Options", + width: 200, + sortable: false, + disableClickEventBubbling: true, + renderCell: (params) => { + const data = params.row.record; + const actions = data.actions !== null ? data.actions.length : 0; + let [triggers, subflows] = getWorkflowMeta(data); - return ( - - - - {data.name} - - - - ) - }}, + return ( + +
    + + + + + {actions} + + + + + + + + {triggers} + + + + + { + if (subflows === 0) { + alert.info("No subflows for " + data.name); + return; + } - { field: 'options', headerName: 'Options', width: 200, sortable: false, - disableClickEventBubbling: true, - renderCell: (params) => { - const data = params.row.record; - const actions = data.actions !== null ? data.actions.length : 0 - let [triggers, subflows] = getWorkflowMeta(data); + var newWorkflows = [data]; + for (var key in data.triggers) { + const trigger = data.triggers[key]; + if (trigger.app_name !== "Shuffle Workflow") { + continue; + } - return ( - -
    - - - - - {actions} - - - - - - - - {triggers} - - - - - { - if (subflows === 0) { - alert.info("No subflows for "+data.name) - return - } + if ( + trigger.parameters !== undefined && + trigger.parameters !== null && + trigger.parameters.length > 0 && + trigger.parameters[0].name === "workflow" + ) { + const newWorkflow = workflows.find( + (item) => item.id === trigger.parameters[0].value + ); + if ( + newWorkflow !== null && + newWorkflow !== undefined + ) { + newWorkflows.push(newWorkflow); + continue; + } + } + } - var newWorkflows = [data] - for (var key in data.triggers) { - const trigger = data.triggers[key] - if (trigger.app_name !== "Shuffle Workflow") { - continue - } + setFilters(["Subflows of " + data.name]); + setFilteredWorkflows(newWorkflows); + }} + > + + + + + {subflows} + + + +
    +
    + ); + }, + }, + { + field: "tags", + headerName: "Tags", + maxHeight: 15, + width: 300, + sortable: false, + disableClickEventBubbling: true, + renderCell: (params) => { + const data = params.row.record; + return ( + + {data.tags !== undefined + ? data.tags.map((tag, index) => { + if (index >= 3) { + return null; + } - if (trigger.parameters !== undefined && trigger.parameters !== null && trigger.parameters.length > 0 && trigger.parameters[0].name === "workflow") { - const newWorkflow = workflows.find(item => item.id === trigger.parameters[0].value) - if (newWorkflow !== null && newWorkflow !== undefined) { - newWorkflows.push(newWorkflow) - continue - } - } - } + return ( + + ); + }) + : null} + + ); + }, + }, + { + field: "", + headerName: "", + maxHeight: 15, + width: 100, + sortable: false, + disableClickEventBubbling: true, + renderCell: (params) => {}, + }, + ]; + let rows = []; + rows = filteredWorkflows.map((data, index) => { + let obj = { + id: index + 1, + title: data.name, + record: data, + }; - setFilters(["Subflows of "+data.name]) - setFilteredWorkflows(newWorkflows) - }}> - - - - - {subflows} - -
    + return obj; + }); + workflowData = ( + + ); + } + return
    {workflowData}
    ; + }; -
    -
    -
    - ) - } - }, - { field: 'tags', headerName: 'Tags', maxHeight: 15, width: 300, sortable: false, - disableClickEventBubbling: true, - renderCell: (params) => { - const data = params.row.record; - return ( - - {data.tags !== undefined ? - data.tags.map((tag, index) => { - if (index >= 3) { - return null - } + const modalView = modalOpen ? ( + { + setModalOpen(false); + }} + PaperProps={{ + style: { + backgroundColor: surfaceColor, + color: "white", + minWidth: "800px", + }, + }} + > + +
    + {editingWorkflow.id !== undefined ? "Editing" : "New"} workflow +
    + + + +
    +
    +
    + + + setNewWorkflowName(event.target.value)} + InputProps={{ + style: { + color: "white", + }, + }} + color="primary" + placeholder="Name" + margin="dense" + defaultValue={newWorkflowName} + autoFocus + fullWidth + /> + setNewWorkflowDescription(event.target.value)} + InputProps={{ + style: { + color: "white", + }, + }} + color="primary" + defaultValue={newWorkflowDescription} + placeholder="Description" + rows="3" + multiline + margin="dense" + fullWidth + /> + { + newWorkflowTags.push(chip); + setNewWorkflowTags(newWorkflowTags); + }} + onDelete={(chip, index) => { + newWorkflowTags.splice(index, 1); + setNewWorkflowTags(newWorkflowTags); + }} + /> - return ( - - ) - }) - : null} -
    - ) - } - }, - { field: '', headerName: '', maxHeight: 15, width: 100, sortable: false, - disableClickEventBubbling: true, - renderCell: (params) => { - } - } - ]; - let rows = []; - rows = filteredWorkflows.map((data, index) => { - let obj = { - "id":index+1, - "title":data.name, - "record":data, - } + setDefaultReturnValue(event.target.value)} + InputProps={{ + style: { + color: "white", + }, + }} + color="primary" + defaultValue={defaultReturnValue} + placeholder="Default return value (used for Subflows if the subflow fails)" + rows="3" + multiline + margin="dense" + fullWidth + /> + + + + + + + + ) : null; - const modalView = modalOpen ? - {setModalOpen(false)}} - PaperProps={{ - style: { - backgroundColor: surfaceColor, - color: "white", - minWidth: "800px", - }, - }} - > - -
    - {editingWorkflow.id !== undefined ? "Editing" : "New"} workflow -
    - - - -
    -
    -
    - - - setNewWorkflowName(event.target.value)} - InputProps={{ - style:{ - color: "white", - }, - }} - color="primary" - placeholder="Name" - margin="dense" - defaultValue={newWorkflowName} - autoFocus - fullWidth - /> - setNewWorkflowDescription(event.target.value)} - InputProps={{ - style:{ - color: "white", - }, - }} - color="primary" - defaultValue={newWorkflowDescription} - placeholder="Description" - rows="3" - multiline - margin="dense" - fullWidth - /> - { - newWorkflowTags.push(chip) - setNewWorkflowTags(newWorkflowTags) - }} - onDelete={(chip, index) => { - newWorkflowTags.splice(index, 1) - setNewWorkflowTags(newWorkflowTags) - }} - /> - - setDefaultReturnValue(event.target.value)} - InputProps={{ - style:{ - color: "white", - }, - }} - color="primary" - defaultValue={defaultReturnValue} - placeholder="Default return value (used for Subflows if the subflow fails)" - rows="3" - multiline - margin="dense" - fullWidth - /> - - - - - - - -
    - : null + if (viewSize.workflowView === 0) { + workflowViewStyle.display = "none"; + } - - const viewSize = { - workflowView: 4, - executionsView: 3, - executionResults: 4, - } + const workflowButtons = ( + + {view === "list" && ( + + + + )} + {view === "grid" && ( + + + + )} + + {importLoading ? ( + + ) : ( + + )} + + (upload = ref)} + onChange={importFiles} + /> + {workflows.length > 0 ? ( + + + + ) : null} + {isCloud ? null : ( + + + + )} + + ); - const workflowViewStyle = { - flex: viewSize.workflowView, - marginLeft: "10px", - marginRight: "10px", - } + const WorkflowView = () => { + if (workflows.length === 0) { + return ( +
    + +
    +

    Welcome to Shuffle

    +
    +
    +

    + Shuffle is a flexible, easy to use, automation platform + allowing users to integrate their services and devices freely. + It's made to significantly reduce the amount of manual labor, + and is focused on security applications.{" "} + + Click here to learn more. + +

    +
    +
    + If you want to jump straight into it, click here to create your + first workflow: +
    +
    + + + + ..OR + + {workflowButtons} + +
    +
    +
    + ); + } - if (viewSize.workflowView === 0) { - workflowViewStyle.display = "none" - } - - const workflowButtons = - - - {view === "list" && ( - - - - )} - {view === "grid" && ( - - - - )} - - {importLoading ? - - : - - } - - upload = ref} onChange={importFiles} /> - {workflows.length > 0 ? - - - - : null} - {isCloud ? null : - - - - } - - - const WorkflowView = () => { - if (workflows.length === 0) { - return ( -
    - -
    -

    Welcome to Shuffle

    -
    -
    -

    - Shuffle is a flexible, easy to use, automation platform allowing users to integrate their services and devices freely. It's made to significantly reduce the amount of manual labor, and is focused on security applications. Click here to learn more. -

    -
    -
    - If you want to jump straight into it, click here to create your first workflow: -
    -
    - - - - ..OR - - {workflowButtons} - -
    -
    -
    - ) - } - - return ( -
    -
    -
    -
    -

    Workflows

    -
    -
    - {/* + return ( +
    +
    +
    +
    +

    Workflows

    +
    +
    + {/*
    @@ -1910,7 +2497,7 @@ const Workflows = (props) => {
    */} - {/* + {/* chipRenderer={({ value, isFocused, isDisabled, handleClick, handleRequestDelete }, key) => { console.log("VALUE: ", value) @@ -1925,283 +2512,366 @@ const Workflows = (props) => { ) }} */} -
    - -
    - { - addFilter(chip) - }} - onDelete={(_, index) => { - removeFilter(index) - }} - /> -
    -
    - {workflowButtons} -
    -
    -
    - {actionImageList !== undefined && actionImageList !== null && actionImageList.length > 0 ? -
    - {actionImageList.map((data, index) => { - if (data.large_image === undefined || data.large_image === null || data.large_image.length === 0) { - return null - } +
    + +
    + { + addFilter(chip); + }} + onDelete={(_, index) => { + removeFilter(index); + }} + /> +
    +
    + {workflowButtons} +
    +
    +
    + {actionImageList !== undefined && + actionImageList !== null && + actionImageList.length > 0 ? ( +
    + {actionImageList.map((data, index) => { + if ( + data.large_image === undefined || + data.large_image === null || + data.large_image.length === 0 + ) { + return null; + } - if (data.app_name.toLowerCase() === "shuffle tools") { - data.large_image = theme.palette.defaultImage - } + if (data.app_name.toLowerCase() === "shuffle tools") { + data.large_image = theme.palette.defaultImage; + } - return ( - - { - console.log("FILTER: ", data) - addFilter(data.app_name) - }}> - - -
    - {data.app_name} -
    -
    -
    -
    -
    - ) - })} -
    - : null} - {view === "grid" ? - - - {filteredWorkflows.map((data, index) => { - return ( - - ) - })} - - : - - } + return ( + + { + console.log("FILTER: ", data); + addFilter(data.app_name); + }} + > + + +
    + {data.app_name} +
    +
    +
    +
    +
    + ); + })} +
    + ) : null} + {view === "grid" ? ( + + + {filteredWorkflows.map((data, index) => { + return ; + })} + + ) : ( + + )} -
    -
    -
    - ) - } +
    +
    +
    + ); + }; - const importWorkflowsFromUrl = (url) => { - console.log("IMPORT WORKFLOWS FROM ", downloadUrl) + const importWorkflowsFromUrl = (url) => { + console.log("IMPORT WORKFLOWS FROM ", downloadUrl); - const parsedData = { - "url": url, - "field_3": downloadBranch || 'master' - } + const parsedData = { + url: url, + field_3: downloadBranch || "master", + }; - if (field1.length > 0) { - parsedData["field_1"] = field1 - } + if (field1.length > 0) { + parsedData["field_1"] = field1; + } - if (field2.length > 0) { - parsedData["field_2"] = field2 - } + if (field2.length > 0) { + parsedData["field_2"] = field2; + } - alert.success("Getting specific workflows from your URL.") - fetch(globalUrl+"/api/v1/workflows/download_remote", { - method: "POST", - mode: "cors", - headers: { - 'Accept': 'application/json', - }, - body: JSON.stringify(parsedData), - credentials: "include", - }) - .then((response) => { - if (response.status === 200) { - alert.success("Successfully loaded workflows from "+downloadUrl) - setTimeout(() => { - getAvailableWorkflows() - }, 1000) - } + alert.success("Getting specific workflows from your URL."); + fetch(globalUrl + "/api/v1/workflows/download_remote", { + method: "POST", + mode: "cors", + headers: { + Accept: "application/json", + }, + body: JSON.stringify(parsedData), + credentials: "include", + }) + .then((response) => { + if (response.status === 200) { + alert.success("Successfully loaded workflows from " + downloadUrl); + setTimeout(() => { + getAvailableWorkflows(); + }, 1000); + } - return response.json() - }) - .then((responseJson) => { - if (!responseJson.success) { - if (responseJson.reason !== undefined) { - alert.error("Failed loading: "+responseJson.reason) - } else { - alert.error("Failed loading") - } - } - }) - .catch(error => { - alert.error(error.toString()) - }) - } + return response.json(); + }) + .then((responseJson) => { + if (!responseJson.success) { + if (responseJson.reason !== undefined) { + alert.error("Failed loading: " + responseJson.reason); + } else { + alert.error("Failed loading"); + } + } + }) + .catch((error) => { + alert.error(error.toString()); + }); + }; - const handleGithubValidation = () => { - importWorkflowsFromUrl(downloadUrl) - setLoadWorkflowsModalOpen(false) - } + const handleGithubValidation = () => { + importWorkflowsFromUrl(downloadUrl); + setLoadWorkflowsModalOpen(false); + }; - const workflowDownloadModalOpen = loadWorkflowsModalOpen ? - { - }} - PaperProps={{ - style: { - backgroundColor: surfaceColor, - color: "white", - minWidth: "800px", - minHeight: "320px", - }, - }} - > - -
    - Load workflows from github repo -
    - - - -
    -
    -
    - - Repository (supported: github, gitlab, bitbucket) - setDownloadUrl(e.target.value)} - placeholder="https://github.com/frikky/shuffle-apps" - fullWidth - /> + const workflowDownloadModalOpen = loadWorkflowsModalOpen ? ( + {}} + PaperProps={{ + style: { + backgroundColor: surfaceColor, + color: "white", + minWidth: "800px", + minHeight: "320px", + }, + }} + > + +
    + Load workflows from github repo +
    + + + +
    +
    +
    + + Repository (supported: github, gitlab, bitbucket) + setDownloadUrl(e.target.value)} + placeholder="https://github.com/frikky/shuffle-apps" + fullWidth + /> + + Branch (default value is "master"): + +
    + setDownloadBranch(e.target.value)} + placeholder="master" + fullWidth + /> +
    + + Authentication (optional - private repos etc): + +
    + setField1(e.target.value)} + type="username" + placeholder="Username / APIkey (optional)" + fullWidth + /> + setField2(e.target.value)} + type="password" + placeholder="Password (optional)" + fullWidth + /> +
    +
    + + + + +
    + ) : null; - Branch (default value is "master"): -
    - setDownloadBranch(e.target.value)} - placeholder="master" - fullWidth - /> -
    + const loadedCheck = + isLoaded && isLoggedIn && workflowDone ? ( +
    + 1366 ? 1366 : 1200, + margin: "auto", + padding: 20, + }} + onDrop={uploadFile} + > + + + {modalView} + {deleteModal} + {exportVerifyModal} + {publishModal} + {workflowDownloadModalOpen} +
    + ) : ( +
    + + Loading Workflows +
    + ); - Authentication (optional - private repos etc): -
    - setField1(e.target.value)} - type="username" - placeholder="Username / APIkey (optional)" - fullWidth - /> - setField2(e.target.value)} - type="password" - placeholder="Password (optional)" - fullWidth - /> -
    -
    - - - - -
    - : null + // Maybe use gridview or something, idk + return
    {loadedCheck}
    ; +}; - const loadedCheck = isLoaded && isLoggedIn && workflowDone ? -
    - 1366 ? 1366 : 1200, margin: "auto", padding: 20 }} onDrop={uploadFile}> - - - {modalView} - {deleteModal} - {exportVerifyModal} - {publishModal} - {workflowDownloadModalOpen} -
    - : -
    - - - Loading Workflows - -
    - - - // Maybe use gridview or something, idk - return ( -
    - {loadedCheck} -
    - ) -} - -export default Workflows +export default Workflows;