From 220f7308b6eae2e77d669768b494e099be6d83cc Mon Sep 17 00:00:00 2001 From: Frikky Date: Sun, 14 Nov 2021 23:36:13 +0100 Subject: [PATCH] Revert "Launch" --- 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 | 142 +- 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 | 7052 +++--- frontend/src/views/AdminSetup.jsx | 388 +- frontend/src/views/AngularWorkflow.jsx | 20199 +++++++--------- frontend/src/views/AppCreator.jsx | 8105 +++---- 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 | 4572 ++-- 63 files changed, 31323 insertions(+), 42417 deletions(-) diff --git a/frontend/README.md b/frontend/README.md index 2257fc4e..3bcf7832 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,5 +1,4 @@ # Certificate: - Creating a localhost certificate: ``` diff --git a/frontend/package.json b/frontend/package.json index f027f35a..aaa5e1c0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -93,7 +93,6 @@ "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 748b7a1a..2ece9bc4 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -1,15 +1,13 @@ - - - - - + + + + + Shuffle +
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index f1d6cecd..f1192ed3 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,559 +31,242 @@ 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 - ); - }); - } + // 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) + }) - // Register hooks here - provider.on("message", (event) => { - alert.info("Message from MetaMask: ", event); - }); + 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) + }) + }) + } + }) - provider.on("chainChanged", (chainId) => { - console.log("Changed chain to: ", chainId); + 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 + } - 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 - ); - }); - }); - } - }); + //console.log("USER: ", userInfo) + setUserData(userInfo) + 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; - } + }) + .catch(error => { + setIsLoaded(true) + }); + } - //console.log("USER: ", userInfo) - setUserData(userInfo); - setIsLoaded(true); - }) - .catch((error) => { - setIsLoaded(true); - }); - }; + // Dumb for content load (per now), but good for making the site not suddenly reload parts (ajax thingies) - // Dumb for content load (per now), but good for making the site not suddenly reload parts (ajax thingies) + const options = { + timeout: 9000, + position: positions.BOTTOM_LEFT, + }; - const options = { - timeout: 9000, - position: positions.BOTTOM_LEFT, - }; + const includedData = window.location.pathname === "/home" || window.location.pathname === "/features" ? +
+ } /> +
: +
+ +
+
+ } /> + } /> + } /> + {userdata.id !== undefined ? + } /> + : null} + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + { window.location.pathname = "/docs/about" }} /> + } /> + } /> + } /> + } /> + } /> +
- 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} - - - - - ); + //
+ // 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 c454c44b..82772dbb 100644 --- a/frontend/src/__test__/appdata.js +++ b/frontend/src/__test__/appdata.js @@ -1,1350 +1,3 @@ -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 706d41ce..7551141b 100644 --- a/frontend/src/__test__/environmentdata.js +++ b/frontend/src/__test__/environmentdata.js @@ -1,6 +1,3 @@ -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 73f5d5ae..49e05e90 100644 --- a/frontend/src/__test__/scheduledata.js +++ b/frontend/src/__test__/scheduledata.js @@ -1,29 +1,30 @@ 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 2142a3f5..1e3a8c55 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 d1df8ea8..2e2d0254 100644 --- a/frontend/src/__test__/workflowdata.js +++ b/frontend/src/__test__/workflowdata.js @@ -1,169 +1,3 @@ -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 56690e80..4751047f 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 340d03e2..67b54596 100644 --- a/frontend/src/components/AlertPopup.js +++ b/frontend/src/components/AlertPopup.js @@ -1,19 +1,26 @@ -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 4dfa06d0..66f4b1c7 100644 --- a/frontend/src/components/AlertTemplate.js +++ b/frontend/src/components/AlertTemplate.js @@ -1,48 +1,46 @@ -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 bb85a059..43b93d33 100644 --- a/frontend/src/components/ConfigureWorkflow.jsx +++ b/frontend/src/components/ConfigureWorkflow.jsx @@ -1,362 +1,267 @@ -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); - } - } - } + 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) { - for (var key in workflow.workflow_variables) { - const variable = workflow.workflow_variables[key]; - if ( - variable.value === undefined || - variable.value === undefined || - variable.value.length < 2 - ) { - variable.value = ""; - variable.index = key; - requiredVariables.push(variable); - } - } + if (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.triggers) { - var trigger = workflow.triggers[key]; - trigger.index = key; + for (var key in workflow.workflow_variables) { + const variable = workflow.workflow_variables[key] + if (variable.value === undefined || variable.value === undefined || variable.value.length < 2) { + variable.value = "" + variable.index = key + requiredVariables.push(variable) + } + } - if (trigger.status === "running") { - continue; - } + for (var key in workflow.triggers) { + var trigger = workflow.triggers[key] + trigger.index = key - if ( - trigger.trigger_type === "SUBFLOW" || - trigger.trigger_type === "USERINPUT" - ) { - continue; - } + if (trigger.status === "running") { + continue + } - requiredTriggers.push(trigger); - } + if (trigger.trigger_type === "SUBFLOW" || trigger.trigger_type === "USERINPUT") { + continue + } + + 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 6ea9f0de..621e74b5 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 482c45b7..aa68d486 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", + color: "#bdbdbd", + textDecoration: "none" +} + +const Footer = props => { + return ( +
+
+ +
+
+ ); }; -const Footer = (props) => { - return ( -
-
- -
-
- ); -}; - -const Box = (props) => { - return ( - - ); +const Box = props => { + return( + + ); }; export default Footer; diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js index 1abddaea..113ba374 100644 --- a/frontend/src/components/Header.js +++ b/frontend/src/components/Header.js @@ -1,310 +1,250 @@ -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 handleHelpHoverOut = () => { + setHelpHoverColor(hoverOutColor) + } + + const handleSoarHover = () => { + setSoarHoverColor(hoverColor) + } - const handleSoarHover = () => { - setSoarHoverColor(hoverColor); - }; + const handleSoarHoverOut = () => { + setSoarHoverColor(hoverOutColor) + } - const handleSoarHoverOut = () => { - setSoarHoverColor(hoverOutColor); - }; + const handleHomeHoverOut = () => { + setHomeHoverColor(hoverOutColor) + } - const handleHomeHoverOut = () => { - setHomeHoverColor(hoverOutColor); - }; + const handleLoginHover = () => { + setLoginHoverColor(hoverColor) + } - const handleLoginHover = () => { - setLoginHoverColor(hoverColor); - }; + const handleLoginHoverOut = () => { + setLoginHoverColor(hoverOutColor) + } - 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 notificationWidth = 350; - const NotificationItem = (props) => { - const { data } = props; + const chipStyle = { + backgroundColor: "#3d3f43", height: 30, marginRight: 5, paddingLeft: 5, paddingRight: 5, height: 28, cursor: "pointer", borderColor: "#3d3f43", color: "white", + } - return ( - - {/* + const notificationWidth = 350 + const NotificationItem = (props) => { + const {data} = props + + 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 +
+ +
+ {/*
@@ -573,236 +425,146 @@ 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 ( - - -
- {image} {data.name} -
-
-
- ); - })} - - )} -
-
- ); + return ( + - //console.log("USR: ", userdata.orgs) + +
+ {image} {data.name} +
+
+
+ ) + })} + + } +
+
- const loginTextMobile = !isLoggedIn ? ( -
- - - -
- - - - - -
- -
- - -
- About -
- -
-
-
- ) : ( -
-
- - - -
- Shuffle -
- -
- - -
- Workflows -
- -
- - -
- Apps -
- -
- {/* + //console.log("USR: ", userdata.orgs) + + const loginTextMobile = !isLoggedIn ? +
+ + + +
+ + + + + +
+ +
+ + +
+ About +
+ +
+
+
+ : +
+
+ + + +
Shuffle
+ +
+ + +
Workflows
+ +
+ + +
Apps
+ +
+ {/*
Configure
*/} -
-
-
- {avatarMenu} -
-
- ); +
+
+
+ {avatarMenu} +
+
- // - const loadedCheck = ( -
- {loginTextBrowser} - {loginTextMobile} -
- ); - //
- return ( -
- {loadedCheck} -
- ); -}; + // + const loadedCheck = +
+ + {loginTextBrowser} + + + {loginTextMobile} + +
+ //
+ return ( +
+ {loadedCheck} +
+ ) +} export default Header; diff --git a/frontend/src/components/LoginPopup.js b/frontend/src/components/LoginPopup.js index 1807ae9a..232b4976 100644 --- a/frontend/src/components/LoginPopup.js +++ b/frontend/src/components/LoginPopup.js @@ -1,176 +1,139 @@ /* 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 35d770a5..cabf1b24 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,100 +68,94 @@ 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; - console.log( - "PAST THIS: ", - containerRefProp, - menuItemRef, - containerRef, - menuContainerRef, - ContainerProps - ); + 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) 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 }); - - // Root element must have a `tabIndex` attribute for keyboard navigation - let tabIndex; - if (!props.disabled) { - tabIndex = tabIndexProp !== undefined ? tabIndexProp : -1; } - console.log("PAST 2! ", tabIndex); + const open = isSubMenuOpen && parentMenuOpen + const menuItemClasses = useMenuItemStyles({open}) + + // Root element must have a `tabIndex` attribute for keyboard navigation + let tabIndex + if (!props.disabled) { + tabIndex = tabIndexProp !== undefined ? tabIndexProp : -1 + } + + 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 634ccf67..2d9afa2d 100644 --- a/frontend/src/components/Oauth2Auth.jsx +++ b/frontend/src/components/Oauth2Auth.jsx @@ -1,332 +1,224 @@ -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& + //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 { - // FIXME: Awful, but works for prototyping - // How can we get a callback properly realtime? - // How can we properly try-catch without breaks on error? - try { - var newwin = window.open(url, "", "width=800,height=600"); - //console.log(newwin) + var 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!'); - var open = true; - const timer = setInterval(() => { - if (newwin.closed) { - setButtonClicked(false); - clearInterval(timer); - //alert('"Secure Payment" window closed!'); + getAppAuthentication(true, true) + } + }, 1000); + //do { + // setTimeout(() => { + // console.log(newwin) + // console.log("CLOSED", newwin.closed) + // if (newwin.closed) { - getAppAuthentication(true, true); - } - }, 1000); - //do { - // setTimeout(() => { - // console.log(newwin) - // console.log("CLOSED", newwin.closed) - // if (newwin.closed) { + // open = false + // } + // }, 1000) + //} + //while(open === true) + } catch (e) { + alert.error("Failed authentication - probably bad credentials. Try again") + setButtonClicked(false) + } - // open = false - // } - // }, 1000) - //} - //while(open === true) - } catch (e) { - alert.error( - "Failed authentication - probably bad credentials. Try again" - ); - setButtonClicked(false); - } + return + //do { + //} while ( + } - 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) - // + console.log("FIELDS: ", newFields) + newAuthOption.fields = newFields + setNewAppAuth(newAuthOption) + //appAuthentication.push(newAuthOption) + //setAppAuthentication(appAuthentication) + // + + //if (configureWorkflowModalOpen) { + // setSelectedAction({}) + //} + //setUpdate(authenticationOption.id) - //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); - }; - - if ( - authenticationOption.label === null || - authenticationOption.label === undefined - ) { - authenticationOption.label = selectedApp.name + " authentication"; + setSelectedScopes(typeof value === 'string' ? value.split(',') : value) } - //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 49897638..14e32552 100644 --- a/frontend/src/components/OrgHeader.jsx +++ b/frontend/src/components/OrgHeader.jsx @@ -1,627 +1,456 @@ -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 ? @@ -630,11 +459,13 @@ 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 97c5ac90..b6bcbd46 100644 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -1,115 +1,47 @@ -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 {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 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: { @@ -117,537 +49,374 @@ 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"); - } - return response.json(); - }) - .then((responseJson) => { - console.log("RESPONSE: ", responseJson); + 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 parsedapp = - responseJson.app !== undefined && responseJson.app !== null - ? JSON.parse(atob(responseJson.app)) - : {}; - console.log("PARSED: ", parsedapp); - //data = parsedapp.body === undefined ? parsedapp : parsedapp.body + return response.json() + }) + .then((responseJson) => { + console.log("RESPONSE: ", responseJson) - if ( - setApp && - parsedapp.actions !== undefined && - parsedapp.actions !== null - ) { - console.log("Inside first if"); - if ( - selectedApp.versions !== undefined && - selectedApp.versions !== null - ) { - parsedapp.versions = selectedApp.versions; - } + 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 ( - selectedApp.loop_versions !== undefined && - selectedApp.loop_versions !== null - ) { - parsedapp.loop_versions = selectedApp.loop_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 + } - // Find authentication, and if it works? - // If authentication has less OR more fields, it has to change - //console.log(selected + if (selectedApp.loop_versions !== undefined && selectedApp.loop_versions !== null) { + parsedapp.loop_versions = selectedApp.loop_versions + } - 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]; + // Find authentication, and if it works? + // If authentication has less OR more fields, it has to change + //console.log(selected - 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); - } + 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] - selectedAction.errors = []; - selectedAction.is_valid = true; + 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) + } - // Updating params for the new action - selectedAction.parameters = foundAction.parameters; - selectedAction.app_id = appId; - selectedAction.app_version = parsedapp.app_version; - setSelectedAction(selectedAction); - setSelectedApp(parsedapp); - } - }) - .catch((error) => { - alert.error(error.toString()); - }); - }; + selectedAction.errors = [] + selectedAction.is_valid = true - const defineStartnode = () => { - if (cy === undefined) { - return; - } + // Updating params for the new action + selectedAction.parameters = foundAction.parameters + selectedAction.app_id = appId + selectedAction.app_version = parsedapp.app_version - 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; - } - } + setSelectedAction(selectedAction) + setSelectedApp(parsedapp) + } + }) + .catch(error => { + alert.error(error.toString()) + }); + } - 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]); - } - } + const defineStartnode = () => { + if (cy === undefined) { + return + } - // 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 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 + } + } - setUpdate("start_node" + selectedAction.id); - workflow.start = selectedAction.id; - setWorkflow(workflow); - //setStartNode(selectedAction.id) - }; + 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]) + } + } - 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); + // 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()) + // } + // } + //} - useEffect(() => { - if ( - selectedActionParameters !== null && - selectedActionParameters.length === 0 - ) { - if ( - selectedAction.parameters !== null && - selectedAction.parameters.length > 0 - ) { - setSelectedActionParameters(selectedAction.parameters); - } - } + setUpdate("start_node"+selectedAction.id) + workflow.start = selectedAction.id + setWorkflow(workflow) + //setStartNode(selectedAction.id) + } - 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); - } + 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 (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, - }); - } - } + 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) + } - // 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: "", - }); - } - } + 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}) + } + } - // 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; - } + // 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 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; - } + // 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 foundResult = workflowExecutions[key].results.find( - (result) => result.action.id === item.id - ); - if (foundResult === undefined) { - 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 + } - 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 foundResult = workflowExecutions[key].results.find(result => result.action.id === item.id) + if (foundResult === undefined) { + continue + } - 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; - } - } + 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") - // Finds the FIRST json only - if (jsonvalid) { - exampledata = JSON.parse(foundResult.result); - break; - } - //else { - // console.log("Invalid JSON: ", foundResult.result) - //} - } - } + 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 + } + } - // 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); - } - } + // Finds the FIRST json only + if (jsonvalid) { + exampledata = JSON.parse(foundResult.result) + break + } + //else { + // console.log("Invalid JSON: ", foundResult.result) + //} + } + } - setActionlist(actionlist); - } - }); + // 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) + } + } - 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, - }, - ]; + setActionlist(actionlist) + } + }) - 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; - } + 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 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 IF: ", 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; - } - } + } 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 (event.target.value[event.target.value.length - 1] === "$") { - if (!showDropdown) { - setShowAutocomplete(false); - setShowDropdown(true); - setShowDropdownNumber(count); - } - } else { - if (showDropdown) { - setShowDropdown(false); - } - } + 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 + //} - // 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 - /* + if (paramcheck["value_replace"] === undefined) { + selectedActionParameters[count]["value_replace"] = paramcheck + selectedAction.parameters[count]["value_replace"] = paramcheck + } else { + selectedActionParameters[count]["value_replace"] = paramcheck["value_replace"] + selectedAction.parameters[count]["value_replace"] = paramcheck["value_replace"] + } + console.log("RESULT: ", selectedAction) + setSelectedAction(selectedAction) + //setUpdate(Math.random()) + return + } + } + + if (event.target.value[event.target.value.length-1] === "$") { + if (!showDropdown) { + setShowAutocomplete(false) + setShowDropdown(true) + setShowDropdownNumber(count) + } + } else { + if (showDropdown) { + setShowDropdown(false) + } + } + + // bad detection mechanism probably + if (event.target.value[event.target.value.length-1] === "." && actionlist.length > 0) { + console.log("GET THE LAST ARGUMENT FOR NODE!") + // THIS IS AN EXAMPLE OF SHOWING IT + /* const inputdata = {"data": "1.2.3.4", "dataType": "4.5.6.6"} setJsonList(GetParsedPaths(inputdata, "")) if (!showDropdown) { @@ -658,167 +427,141 @@ 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); - } 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.display.maxLine.text[event.display.maxLine.text.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 + } + } - // 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 - /* + if (event.display.maxLine.text[event.display.maxLine.text.length-1] === "$") { + if (!showDropdown) { + setShowAutocomplete(false) + setShowDropdown(true) + setShowDropdownNumber(count) + } + } else { + if (showDropdown) { + setShowDropdown(false) + } + } + + // bad detection mechanism probably + if (event.display.maxLine.text[event.display.maxLine.text.length-1] === "." && actionlist.length > 0) { + console.log("GET THE LAST ARGUMENT FOR NODE!") + // THIS IS AN EXAMPLE OF SHOWING IT + /* const inputdata = {"data": "1.2.3.4", "dataType": "4.5.6.6"} setJsonList(GetParsedPaths(inputdata, "")) if (!showDropdown) { @@ -829,1037 +572,803 @@ 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; - } - } + 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 - selectedAction.parameters = selectedActionParameters; + // This is a stupid workaround to make it refresh rofl + setSelectedAction({}) - // This is a stupid workaround to make it refresh rofl - setSelectedAction({}); + if (setSelectedTrigger !== undefined) { + setSelectedTrigger({}) + setSelectedApp({}) + setSelectedEdge({}) + } + // FIXME - check if startnode - if (setSelectedTrigger !== undefined) { - setSelectedTrigger({}); - setSelectedApp({}); - setSelectedEdge({}); - } - // FIXME - check if startnode + // Set value + setSelectedApp(selectedApp) + setSelectedAction(selectedAction) + setUpdate(Math.random()) + } - // 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" + } - // 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()) + // 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 + } - if (authWritten) { - return null; - } + authWritten = true + return ( + + Authentication fields are hidden + + ) + } - authWritten = true; - return ( - - Authentication fields are hidden - - ); - } + var staticcolor = "inherit" + var actioncolor = "inherit" + var varcolor = "inherit" + var multiline + if (data.multiline !== undefined && data.multiline !== null && data.multiline === true) { + multiline = true + } - var staticcolor = "inherit"; - var actioncolor = "inherit"; - var varcolor = "inherit"; - var multiline; - if ( - data.multiline !== undefined && - data.multiline !== null && - data.multiline === true - ) { - multiline = true; - } + if (data.value !== undefined && data.value !== null && data.value.startsWith("{") && data.value.endsWith("}")) { + multiline = true + } - if ( - data.value !== undefined && - data.value !== null && - data.value.startsWith("{") && - data.value.endsWith("}") - ) { - multiline = true; - } + var placeholder = "Static value" + if (data.example !== undefined && data.example !== null && data.example.length > 0) { + placeholder = data.example - var placeholder = "Static value"; - if ( - data.example !== undefined && - data.example !== null && - data.example.length > 0 - ) { - placeholder = data.example; + if (data.name === "url" && data.value.length === 0) { + data.value = data.example + } + } - if (data.name === "url" && data.value.length === 0) { - data.value = data.example; - } - } + if (data.name.startsWith("${") && data.name.endsWith("}")) { + const paramcheck = selectedAction.parameters.find(param => param.name === "body") + if (paramcheck !== undefined && paramcheck !== null) { + if (paramcheck["value_replace"] !== undefined && paramcheck["value_replace"] !== null) { + //console.log("IN THE VALUE REPLACE: ", paramcheck["value_replace"]) + const subparamindex = paramcheck["value_replace"].findIndex(param => param.key === data.name) + if (subparamindex !== -1) { + data.value = paramcheck["value_replace"][subparamindex]["value"] + } + } + } + } - if (data.name.startsWith("${") && data.name.endsWith("}")) { - const paramcheck = selectedAction.parameters.find( - (param) => param.name === "body" - ); - if (paramcheck !== undefined && paramcheck !== null) { - if ( - paramcheck["value_replace"] !== undefined && - paramcheck["value_replace"] !== null - ) { - //console.log("IN THE VALUE REPLACE: ", paramcheck["value_replace"]) - const subparamindex = paramcheck["value_replace"].findIndex( - (param) => param.key === data.name - ); - if (subparamindex !== -1) { - data.value = - paramcheck["value_replace"][subparamindex]["value"]; - } - } - } - } + var disabled = false + var rows = "5" + var openApiHelperText = "This is an OpenAPI specific field" + if (selectedApp.generated && data.name === "url" && data.required && data.configuration && hideExtraTypes) { + console.log("GENERATED WITH DATA: ", data) + return null + } - var disabled = false; - var rows = "5"; - var openApiHelperText = "This is an OpenAPI specific field"; - if ( - selectedApp.generated && - data.name === "url" && - data.required && - data.configuration && - hideExtraTypes - ) { - console.log("GENERATED WITH DATA: ", data); - return null; - } + if (selectedApp.generated && data.name === "headers") { + //console.log("HEADER: ", data) + //if (data.value.length === 0) { - if (selectedApp.generated && data.name === "headers") { - //console.log("HEADER: ", data) - //if (data.value.length === 0) { - //} - //setSelectedActionParameters(selectedActionParameters) - } + //} + //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} - - ); - } - )} - - ); - } else if (data.variant === "STATIC_VALUE") { - staticcolor = "#f85a3e"; - } + return ( + + {viewed_data} + + ) + })} + - if (data.field_active === false) { - return null; - } + } else if (data.variant === "STATIC_VALUE") { + staticcolor = "#f85a3e" + } - // Shows nested list of nodes > their JSON lists - const ActionlistWrapper = (props) => { - const handleMenuClose = () => { - setShowAutocomplete(false); + if (data.field_active === false) { + return null + } - if ( - !selectedActionParameters[count].value[ - selectedActionParameters[count].value.length - 1 - ] === "$" - ) { - setShowDropdown(false); - } + // Shows nested list of nodes > their JSON lists + const ActionlistWrapper = (props) => { - setUpdate(Math.random()); - setMenuPosition(null); - }; + const handleMenuClose = () => { + setShowAutocomplete(false) - const handleItemClick = (values) => { - if ( - values === undefined || - values === null || - values.length === 0 - ) { - return; - } + if (!selectedActionParameters[count].value[selectedActionParameters[count].value.length-1] === "$") { + setShowDropdown(false) + } - console.log("AUTOCOMPLETE1: ", values); + setUpdate(Math.random()) + setMenuPosition(null) + } - 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; - } + const handleItemClick = (values) => { + if (values === undefined || values === null || values.length === 0) { + return + } - toComplete += values[key].autocomplete; - } + console.log("AUTOCOMPLETE1: ", values) - // 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; - } - } + 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 + } - selectedActionParameters[count]["value_replace"] = - paramcheck; - selectedAction.parameters[count]["value_replace"] = - paramcheck; - setSelectedAction(selectedAction); - setUpdate(Math.random()); + toComplete += values[key].autocomplete + } - setShowDropdown(false); - setMenuPosition(null); - return; - } - } + // Handles the fields under OpenAPI body to be parsed. + if (data.name.startsWith("${") && data.name.endsWith("}")) { + console.log("INSIDE VALUE REPLACE: ", data.name, toComplete) + // PARAM FIX - Gonna use the ID field, even though it's a hack + const paramcheck = selectedAction.parameters.find(param => param.name === "body") + if (paramcheck !== undefined) { + if (paramcheck["value_replace"] === undefined || paramcheck["value_replace"] === null) { + paramcheck["value_replace"] = [{ + "key": data.name, + "value": toComplete, + }] - selectedActionParameters[count].value += toComplete; - selectedAction.parameters[count].value = - selectedActionParameters[count].value; - setSelectedAction(selectedAction); - setUpdate(Math.random()); + } else { + const subparamindex = paramcheck["value_replace"].findIndex(param => param.key === data.name) + if (subparamindex === -1) { + paramcheck["value_replace"].push({ + "key": data.name, + "value": toComplete, + }) + } else { + paramcheck["value_replace"][subparamindex]["value"] += toComplete + } + } - setShowDropdown(false); - setMenuPosition(null); - }; + + selectedActionParameters[count]["value_replace"] = paramcheck + selectedAction.parameters[count]["value_replace"] = paramcheck + setSelectedAction(selectedAction) + setUpdate(Math.random()) - const iconStyle = { - marginRight: 15, - }; + setShowDropdown(false) + setMenuPosition(null) + return + } + } - 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" ? ( - - ) : ( - - ); + selectedActionParameters[count].value += toComplete + selectedAction.parameters[count].value = selectedActionParameters[count].value + setSelectedAction(selectedAction) + setUpdate(Math.random()) - 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 = ""; - } - } + setShowDropdown(false) + setMenuPosition(null) + } - // 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 iconStyle = { + marginRight: 15, + } - 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"); - } - } - } - } - } - }; + return ( + { + handleMenuClose() + }} + open={!!menuPosition} + style={{ + color: "white", + marginTop: 2, + maxHeight: 650, + }} + > + {actionlist.map(innerdata => { + const icon = innerdata.type === "action" ? : innerdata.type === "workflow_variable" || innerdata.type === "execution_variable" ? : - const 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 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 handleMouseover = () => { - if (innerdata.type === "Execution Argument") { - handleExecArgumentHover(true); - } else if (innerdata.type === "action") { - handleActionHover(true, innerdata.id); - } - }; + // 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 handleMouseOut = () => { - if (innerdata.type === "Execution Argument") { - handleExecArgumentHover(false); - } else if (innerdata.type === "action") { - handleActionHover(false, 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') + } + } + } + } + } + } - var parsedPaths = []; - if (typeof innerdata.example === "object") { - parsedPaths = GetParsedPaths(innerdata.example, ""); - } + 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') + } + } + } + } - 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} -
-
-
- ); - })} - - ); - }; + 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 tmpitem = data.name.valueOf(); - if (data.name.startsWith("${") && data.name.endsWith("}")) { - tmpitem = tmpitem.slice(2, data.name.length - 1); - } + var parsedPaths = [] + if (typeof(innerdata.example) === "object") { + parsedPaths = GetParsedPaths(innerdata.example, "") + } - tmpitem = ( - tmpitem.charAt(0).toUpperCase() + tmpitem.substring(1) - ).replaceAll("_", " "); + 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} +
+
+
+ ) - if (tmpitem === "Username basic") { - tmpitem = "Username"; - } else if (tmpitem === "Password basic") { - tmpitem = "Password"; - } + })} + + : + handleMouseover()} onMouseOut={() => {handleMouseOut()}} + onClick={() => { + handleItemClick([innerdata]) + }} + > + +
+ {icon} {innerdata.name} +
+
+
+ + ) + })} + + ) + } - const description = - data.description === undefined ? "" : data.description; - const tooltipDescription = ( - - - - Required:{" "} - {data.required === true || data.configuration === true - ? "True" - : "False"} - - - - Example: {data.example} - - - - Description: {description} - - - ); - //var itemColor = "#f85a3e" - //if (!data.required) { - // itemColor = "#ffeb3b" - //} - { - /*
*/ - } - return ( -
- {hideBodyButton} -
- {data.configuration === true ? ( - - { - setAuthenticationModalOpen(true); - }} - /> - - ) : null} + var 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("_", " ") -
- - {tmpitem} - -
+ if (tmpitem === "Username basic") { + tmpitem = "Username" + } else if (tmpitem === "Password basic") { + tmpitem = "Password" + } - {/*selectedActionParameters[count].options !== undefined && selectedActionParameters[count].options !== null && selectedActionParameters[count].options.length > 0 ? null : + 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 :
{ @@ -1889,7 +1398,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 :
{}}> @@ -1913,262 +1422,158 @@ 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} -
- ); - })} -
- ); - } - return 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} - const expansionModal = ( - { - setExpansionModalOpen(false); - }} - PaperProps={{ - style: { - backgroundColor: theme.palette.surfaceColor, - color: "white", - minWidth: 600, - padding: 50, - }, - }} - > - - Workflow Variable - - Hello - - ); - //const CustomPopper = function (props) { - // const classes = useStyles() - // return - //} +
+ )})} +
+ ) + } + return null + } - 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 expansionModal = + { + setExpansionModalOpen(false) + }} + PaperProps={{ + style: { + backgroundColor: theme.palette.surfaceColor, + color: "white", + minWidth: 600, + padding: 50, + }, + }} + > + Workflow Variable + + Hello + + - var foundResult = workflowExecutions[key].results.find( - (result) => result.action.id === selectedAction.id - ); - if (foundResult === undefined || foundResult === null) { - continue; - } + //const CustomPopper = function (props) { + // const classes = useStyles() + // return + //} - setSelectedResult(foundResult); + 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 + } - if (setCodeModalOpen !== undefined) { - setCodeModalOpen(true); - } - break; - } - } - }} - > - - - - - { - setAuthenticationModalOpen(true); - }} - > - - - - - {}} - > - - - - - - -
-
-
- {/*selectedAction.id === workflow.start ? null : + 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 : */} - {selectedApp.versions !== null && - selectedApp.versions !== undefined && - selectedApp.versions.length > 1 ? ( - - ) : 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 - ); - } - } - } + {selectedApp.versions !== null && selectedApp.versions !== undefined && selectedApp.versions.length > 1 ? + + : 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) + } + } + } - setAuthenticationModalOpen(true); - }} - > - Authenticate{" "} - {selectedApp.name} - - - -
- ) : null} - {selectedAction.authentication !== undefined && - selectedAction.authentication !== null && - selectedAction.authentication.length > 0 ? ( -
- Authentication -
- + setAuthenticationModalOpen(true) + }}> + Authenticate {selectedApp.name} + + + +
+ : null} + {selectedAction.authentication !== undefined && selectedAction.authentication !== null && selectedAction.authentication.length > 0 ? +
+ + Authentication + +
+ + + {/*
-
- ) : null} + + { + setAuthenticationModalOpen(true) + }}> + + + +
+
+ : null} - {showEnvironment !== undefined && showEnvironment ? ( -
- Environment - - {data.Name} - - ); - })} - -
- ) : 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} - {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 } }); - } - }} - renderOption={(data) => { - var newActionname = data.name; - if ( - data.label !== undefined && - data.label !== null && - data.label.length > 0 - ) { - newActionname = data.label; - } + const newname = (option.name.charAt(0).toUpperCase()+option.name.substring(1)).replaceAll("_", " ") + return newname + }} + options={sortByKey(selectedApp.actions, "label")} + fullWidth + style={{backgroundColor: theme.palette.inputColor, height: 50, borderRadius: theme.palette.borderRadius,}} + onChange={(event, newValue) => { + // Workaround with event lol + if (newValue !== undefined && newValue !== null) { + setNewSelectedAction({"target": {"value": newValue.name}}) + } - const iconInfo = GetIconInfo({ name: data.name }); - const useIcon = iconInfo.originalIcon; + }} + renderOption={(data) => { + var newActionname = data.name + if (data.label !== undefined && data.label !== null && data.label.length > 0) { + newActionname = data.label + } - newActionname = ( - newActionname.charAt(0).toUpperCase() + - newActionname.substring(1) - ).replaceAll("_", " "); + const iconInfo = GetIconInfo({"name": data.name}) + const useIcon = iconInfo.originalIcon - return ( -
- - {useIcon} - - {newActionname} -
- ); - }} - renderInput={(params) => { - return ( - - ); - }} - /> - ) : null} + newActionname = (newActionname.charAt(0).toUpperCase()+newActionname.substring(1)).replaceAll("_", " ") - {/*setNewSelectedAction !== undefined ? + return ( +
+ {useIcon} + {newActionname} +
+ ) + }} + renderInput={(params) => { + return ( + + ) + }} + /> + + : null} + + {/*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 18c9815c..3a88dcf8 100644 --- a/frontend/src/components/RenderCytoscape.js +++ b/frontend/src/components/RenderCytoscape.js @@ -1,82 +1,79 @@ -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; - } - node.data.example = example; - return node; - }); + var example = "" + if (action.example !== undefined && action.example !== null && action.example.length > 0) { + example = action.example + } - const triggers = workflow.triggers.map((trigger) => { - const node = {}; - node.position = trigger.position; - node.data = trigger; + node.data.example = example + return node; + }) - node.data._id = trigger["id"]; - node.data.type = "TRIGGER"; + const triggers = workflow.triggers.map(trigger => { + const node = {} + node.position = trigger.position + node.data = trigger - return node; - }); + node.data._id = trigger["id"] + node.data.type = "TRIGGER" - // FIXME - tmp branch update - var insertedNodes = [].concat(actions, triggers); - const edges = workflow.branches.map((branch, index) => { - //workflow.branches[index].conditions = [{ + return node; + }) - const edge = {}; - var conditions = workflow.branches[index].conditions; - if (conditions === undefined || conditions === null) { - conditions = []; - } + // FIXME - tmp branch update + var insertedNodes = [].concat(actions, triggers) + const edges = workflow.branches.map((branch, index) => { + //workflow.branches[index].conditions = [{ - var label = ""; - if (conditions.length === 1) { - label = conditions.length + " condition"; - } else if (conditions.length > 1) { - label = conditions.length + " conditions"; - } + const edge = { }; + var conditions = workflow.branches[index].conditions + if (conditions === undefined || conditions === null) { + 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, - }; + var label = "" + if (conditions.length === 1) { + label = conditions.length+" condition" + } else if (conditions.length > 1) { + label = conditions.length+" conditions" + } - // This is an attempt at prettier edges. The numbers are weird to work with. - /* + 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. + /* //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) @@ -99,59 +96,51 @@ 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 00b8d09c..88d2d158 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 3eb38aa5..f493b1a5 100644 --- a/frontend/src/components/SettingsPopup.js +++ b/frontend/src/components/SettingsPopup.js @@ -1,177 +1,141 @@ -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 b26d4bad..71ce4bd5 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 7a9e5b2a..bbf07d1d 100644 --- a/frontend/src/css/nunito.css +++ b/frontend/src/css/nunito.css @@ -1,45 +1,40 @@ /* 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 491d9476..b0879d2e 100644 --- a/frontend/src/defaultCytoscapeStyle.js +++ b/frontend/src/defaultCytoscapeStyle.js @@ -1,391 +1,389 @@ -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]', @@ -399,4 +397,5 @@ const data = [ // }, //}, -export default data; + +export default data diff --git a/frontend/src/index.css b/frontend/src/index.css index da2c34e8..793c42e5 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 781cb802..5c4e400f 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -1,10 +1,13 @@ -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 5133f741..8859a0c6 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 c6b39a39..92371aad 100644 --- a/frontend/src/theme.js +++ b/frontend/src/theme.js @@ -1,66 +1,65 @@ -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 12447fb9..9c4044f9 100644 --- a/frontend/src/views/About.jsx +++ b/frontend/src/views/About.jsx @@ -1,74 +1,49 @@ -import React from "react"; +import React from 'react'; const hrefStyle = { - color: "#f85a3e", - textDecoration: "none", -}; + color: "#f85a3e", + textDecoration: "none" +} const 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. -

+ return ( +
+

About

-

- 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. -

+

+ 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. +

-

This site currently uses the following projects

- +

+ 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. +

-

Hopefully it is of use to some people :)

+

+ This site currently uses the following projects +

+ -

Thanks

-

Thanks to Andy for the initial frontend help :)

+

Hopefully it is of use to some people :)

-

Regards

-

- - @frikkylikeme - -

-
- ); -}; +

Thanks

+

+ Thanks to Andy for the initial frontend help :) +

+ +

Regards

+

+ @frikkylikeme +

+
+ ) +} export default About; diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index 46eb463e..20aa37ee 100644 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -1,208 +1,155 @@ -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", + 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({}) + // } + //} }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for apps :O!"); - } + .catch(error => { + alert.error(error.toString()) + }); + } - 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"], - }, - ]; - /* + const categories = [ + { + "name": "Ticketing", + "apps": [ + "TheHive", + "Service-Now", + "SecureWorks", + ], + "categories": ["tickets", "ticket", "ticketing"] + }, + ] + /* "SIEM", "Active Directory", "Firewalls", @@ -222,2588 +169,2178 @@ const Admin = (props) => { ] */ - const alert = useAlert(); + const alert = useAlert() - const deleteAuthentication = (data) => { - alert.info("Deleting auth " + data.label); + 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", - }, + // 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) }) - .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); - }) - .catch((error) => { - console.log(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) - - 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"; - } - - if (newValue === 6) { - console.log("Should get apps for categories."); - } - - //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); - - //console.log(newpath) - //window.location.pathame = newpath - - setModalUser({}); - }; - - if (firstRequest) { - setFirstRequest(false); - document.title = "Shuffle - admin"; - if (!isCloud) { - getUsers(); - } else { - getSettings(); - } - - if (props.match.params.key !== undefined) { - //const tmpitem = views[props.match.params.key] - setConfig("", props.match.params.key); - } - } - - if ( - selectedOrganization.id === undefined && - userdata !== undefined && - userdata.active_org !== undefined && - orgRequest - ) { - setOrgRequest(false); - handleGetOrg(userdata.active_org.id); - } - - const paperStyle = { - maxWidth: 1250, - margin: "auto", - color: "white", - backgroundColor: theme.palette.surfaceColor, - marginBottom: 10, - padding: 20, - }; - - const changeModalData = (field, value) => { - modalUser[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!"); - } else { - getUsers(); - } - - return response.json(); - }) - .then((responseJson) => { - if (!responseJson.success && responseJson.reason !== undefined) { - alert.error("Failed setting user: " + responseJson.reason); - } else { - alert.success("Set the user field " + field + " to " + value); - setSelectedUserModalOpen(false); - } - }) - .catch((error) => { - console.log(error); - }); - }; - - const generateApikey = (user) => { - const userId = user.id; - const data = { user_id: userId }; - - fetch(globalUrl + "/api/v1/generateapikey", { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(data), - credentials: "include", - }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for WORKFLOW EXECUTION :O!"); - } else { - getUsers(); - } - - return response.json(); - }) - .then((responseJson) => { - console.log("RESP: ", responseJson); - if (!responseJson.success && responseJson.reason !== undefined) { - alert.error("Failed getting new: " + responseJson.reason); - } else { - alert.success("Got new API key"); - } - }) - .catch((error) => { - console.log(error); - }); - }; - - const editAuthenticationModal = selectedAuthenticationModalOpen ? ( - { - setSelectedAuthenticationModalOpen(false); - }} - PaperProps={{ - style: { - backgroundColor: theme.palette.surfaceColor, - color: "white", - minWidth: "800px", - minHeight: "320px", - }, - }} - > - - - Edit authentication for {selectedAuthentication.app.name} ( - {selectedAuthentication.label}) - - - - {selectedAuthentication.fields.map((data, index) => { - //console.log("DATA: ", data, selectedAuthentication) - return ( -
- - {data.key} - - { - authenticationFields[index].value = e.target.value; - setAuthenticationFields(authenticationFields); - }} - /> -
- ); - })} -
- - - - -
- ) : 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 + + 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) @@ -2853,295 +2390,180 @@ 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); - }; - - const uploadFile = (e) => { - const isDropzone = - e.dataTransfer === undefined ? false : e.dataTransfer.files.length > 0; - const files = isDropzone ? e.dataTransfer.files : e.target.files; + 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 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; - 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; - } + 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 - if (environment.archived === undefined) { - getEnvironments(); - return null; - } + const environmentView = curTab === 5 ? +
+
+

Environments

+ Decides what Orborus environment to execute an action in a workflow in. Learn more +
+ + + {setShowArchived(!showArchived)}} /> Show archived + + + + + + + + + + + + {environments === undefined || environments === null ? null : environments.map((environment, index)=> { + if (!showArchived && environment.archived) { + return null + } - var bgColor = "#27292d"; - if (index % 2 === 0) { - bgColor = "#1f2023"; - } + if (environment.archived === undefined) { + getEnvironments() + return null + } - return ( - - - - - - {environment.default ? null : ( - - )} - - -
- - {/* + } + + +
+ + {/**/} - {/**/} -
-
- - - - ); - })} - -
- ) : 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} -
-
-
- ); + - return ( -
- {modalView} - {cloudSyncModal} - {editUserModal} - {editAuthenticationModal} - {data} - -
- ); -}; + const iconStyle = {marginRight: 10} + const data = +
+ + + Organization/> + Users /> + App Authentication/> + Files /> + Schedules /> + {isCloud ? null : Environments/>} + {isCloud ? null : Organizations/>} + {/*window.location.protocol == "http:" && window.location.port === "3000" ? Hybrid/> : null*/} + {/*window.location.protocol === "http:" && window.location.port === "3000" ? Categories/> : null*/} + + +
+ {organizationView} + {authenticationView} + {usersView} + {environmentView} + {schedulesView} + {filesView} + {hybridTab} + {organizationsTab} + {appCategoryView} +
+
+
-export default Admin; + return ( +
+ {modalView} + {cloudSyncModal} + {editUserModal} + {editAuthenticationModal} + {data} + +
+ ) +} + +export default Admin diff --git a/frontend/src/views/AdminSetup.jsx b/frontend/src/views/AdminSetup.jsx index df2355bc..7f0e3a9a 100644 --- a/frontend/src/views/AdminSetup.jsx +++ b/frontend/src/views/AdminSetup.jsx @@ -1,233 +1,223 @@ /* 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"; + formtitle = "Create administrator account" + + const basedata = +
+ +
+

{formtitle}

+ Username +
+ +
+ Password +
+ +
+
+ + +
+
+ {loginInfo} +
+
+
+
- const basedata = ( -
- -
-

{formtitle}

- Username -
- -
- Password -
- -
-
- -
-
{loginInfo}
-
-
-
- ); + const loadedCheck = isLoaded ? +
+ {basedata} +
+ : +
+
- const loadedCheck = isLoaded ?
{basedata}
:
; - - return
{loadedCheck}
; -}; + return ( +
+ {loadedCheck} +
+ ) +} export default AdminAccount; diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index f582d3e4..0e3c8180 100644 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -1,99 +1,98 @@ -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 { - 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 {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 * 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); @@ -105,1464 +104,1301 @@ 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(); + 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) - 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 [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 [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 [history, setHistory] = React.useState([]) + const [historyIndex, setHistoryIndex] = React.useState(history.length) - const [history, setHistory] = React.useState([]); - const [historyIndex, setHistoryIndex] = React.useState(history.length); + 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 [appAuthentication, setAppAuthentication] = React.useState([]); - const [variablesModalOpen, setVariablesModalOpen] = React.useState(false); - const [executionVariablesModalOpen, setExecutionVariablesModalOpen] = - React.useState(false); - const [authenticationModalOpen, setAuthenticationModalOpen] = - React.useState(false); - const [conditionsModalOpen, setConditionsModalOpen] = React.useState(false); - const [newVariableName, setNewVariableName] = React.useState(""); - const [authenticationType, setAuthenticationType] = React.useState(""); - const [newVariableDescription, setNewVariableDescription] = - React.useState(""); - const [newVariableValue, setNewVariableValue] = React.useState(""); - const [workflowDone, setWorkflowDone] = React.useState(false); - const [authLoaded, setAuthLoaded] = React.useState(false); - const [localFirstrequest, setLocalFirstrequest] = React.useState(true); - const [requiresAuthentication, setRequiresAuthentication] = - React.useState(false); - const [rightSideBarOpen, setRightSideBarOpen] = React.useState(false); - const [showSkippedActions, setShowSkippedActions] = React.useState(false); - const [lastExecution, setLastExecution] = React.useState(""); - const [configureWorkflowModalOpen, setConfigureWorkflowModalOpen] = - React.useState(false); + const curpath = typeof window === 'undefined' || window.location === undefined ? "" : window.location.pathname - const curpath = - typeof window === "undefined" || window.location === undefined - ? "" - : window.location.pathname; + // 0 = normal, 1 = just done, 2 = normal + const [savingState, setSavingState] = React.useState(0) - // 0 = normal, 1 = just done, 2 = normal - const [savingState, setSavingState] = React.useState(0); + const [selectedResult, setSelectedResult] = React.useState({}) + const [codeModalOpen, setCodeModalOpen] = React.useState(false); - const [selectedResult, setSelectedResult] = React.useState({}); - const [codeModalOpen, setCodeModalOpen] = React.useState(false); + const [variableAnchorEl, setVariableAnchorEl] = React.useState(null) - const [variableAnchorEl, setVariableAnchorEl] = React.useState(null); + 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 [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({}); - // 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 [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 [apps, setApps] = React.useState([]); - const [filteredApps, setFilteredApps] = React.useState([]); - const [prioritizedApps, setPrioritizedApps] = React.useState([]); - const [firstrequest, setFirstrequest] = React.useState(true); + const [graphSetup, setGraphSetup] = React.useState(false); - const [environments, setEnvironments] = React.useState([]); - const [established, setEstablished] = React.useState(false); + const [selectedApp, setSelectedApp] = React.useState({}); + const [selectedAction, setSelectedAction] = React.useState({}); + const [selectedActionEnvironment, setSelectedActionEnvironment] = React.useState({}); - const [graphSetup, setGraphSetup] = React.useState(false); + const [executionRequest, setExecutionRequest] = React.useState({}) - const [selectedApp, setSelectedApp] = React.useState({}); - const [selectedAction, setSelectedAction] = React.useState({}); - const [selectedActionEnvironment, setSelectedActionEnvironment] = - React.useState({}); + const [executionRunning, setExecutionRunning] = React.useState(false) + const [executionModalOpen, setExecutionModalOpen] = React.useState(false) + const [executionModalView, setExecutionModalView] = React.useState(0) + const [executionData, setExecutionData] = React.useState({}) - const [executionRequest, setExecutionRequest] = React.useState({}); + const [lastSaved, setLastSaved] = React.useState(true) - const [executionRunning, setExecutionRunning] = React.useState(false); - const [executionModalOpen, setExecutionModalOpen] = React.useState(false); - const [executionModalView, setExecutionModalView] = React.useState(0); - const [executionData, setExecutionData] = React.useState({}); + // eslint-disable-next-line no-unused-vars + const [_, setUpdate] = useState(""); // Used for rendring, don't remove - const [lastSaved, setLastSaved] = React.useState(true); + const [workflowExecutions, setWorkflowExecutions] = React.useState([]); + const [defaultEnvironmentIndex, setDefaultEnvironmentIndex] = React.useState(0) - // eslint-disable-next-line no-unused-vars - const [_, setUpdate] = useState(""); // Used for rendring, don't remove + // 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 [workflowExecutions, setWorkflowExecutions] = React.useState([]); - const [defaultEnvironmentIndex, setDefaultEnvironmentIndex] = - React.useState(0); + useBeforeunload(() => { + if (!lastSaved) { + return unloadText + } + }) - // 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 [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() + } + }) - 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", + 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); - } - } - } - } - } - }) - .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", + console.log("PARAM: ", param) + if (param.name === "startnode" && param.value !== undefined && param.value !== null) { + setSubworkflowStartnode(param.value) + } + } + } + } + } }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for APIKEY gen :O!"); - } + .catch(error => { + alert.error(error.toString()) + }); + } - return response.json(); - }) - .then((responseJson) => { - setUserSettings(responseJson); - }) - .catch((error) => { - console.log(error); - }); - }; - const getSettings = () => { - fetch(globalUrl + "/api/v1/getsettings", { - method: "GET", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", + 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) }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for get settings :O!"); - } + .catch(error => { + console.log(error) + }); + } - return response.json(); - }) - .then((responseJson) => { - if ( - responseJson.apikey === undefined || - responseJson.apikey.length === 0 || - responseJson.apikey === null - ) { - generateApikey(); - } - setUserSettings(responseJson); - }) - .catch((error) => { - console.log(error); - }); - }; + const getSettings = () => { + fetch(globalUrl+"/api/v1/getsettings", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for get settings :O!") + } - 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", + return response.json() + }) + .then((responseJson) => { + if (responseJson.apikey === undefined || responseJson.apikey.length === 0 || responseJson.apikey === null) { + generateApikey() + } + setUserSettings(responseJson) }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for setting app auth :O!"); - } + .catch(error => { + console.log(error) + }); + } - return response.json(); - }) - .then((responseJson) => { - if (!responseJson.success) { - alert.error("Failed to set app auth: " + responseJson.reason); - } else { - getAppAuthentication(true, false); - 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, false) + setAuthenticationModalOpen(false) - const getWorkflowExecution = (id, execution_id) => { - fetch(globalUrl + "/api/v1/workflows/" + id + "/executions", { - method: "GET", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", + // 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 WORKFLOW EXECUTION :O!"); - } + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for apps :O!") + } - return response.json(); - }) - .then((responseJson) => { - if (responseJson.length > 0) { - // FIXME: Sort this by time + return response.json() + }) + .then((responseJson) => { + if (responseJson.success) { + var newauth = [] + for (var key in responseJson.data) { + if (responseJson.data[key].defined === false) { + continue + } - // - means it's opposite - const newkeys = sortByKey(responseJson, "-started_at"); - setWorkflowExecutions(newkeys); + newauth.push(responseJson.data[key]) + } + - 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 (cy !== undefined) { + console.log("NEW AUTH = reset cy's onnodeselect") - 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(); + // 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)) + } - setExecutionRequest({ - execution_id: execution.execution_id, - authorization: execution.authorization, - }); + setAppAuthentication(newauth) + setAuthLoaded(true) - const newitem = removeParam("execution_id", cursearch); - props.history.push(curpath + newitem); - } - } - } - }) - .catch((error) => { - alert.error(error.toString()); - }); - }; + if (updateAction === true) { + + if (selectedApp.authentication.required) { + // Setup auth here :) + var appUpdates = false + const authenticationOptions = [] - const fetchUpdates = () => { - fetch(globalUrl + "/api/v1/streams/results", { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(executionRequest), - credentials: "include", + var tmpAuth = JSON.parse(JSON.stringify(responseJson.data)) + var latest = 0 + for (var key in tmpAuth) { + var item = tmpAuth[key] + + const newfields = {} + for (var filterkey in item.fields) { + newfields[item.fields[filterkey].key] = item.fields[filterkey].value + } + + item.fields = newfields + if (item.app.name === selectedApp.name) { + authenticationOptions.push(item) + + // Always becoming the last one + if (item.edited > latest) { + latest = item.edited + selectedAction.selectedAuthentication = item + + for (var key in workflow.actions) { + if (workflow.actions[key].app_name === selectedApp.name) { + workflow.actions[key].selectedAuthentication = item + workflow.actions[key].authentication_id = item.id + appUpdates = true + } + } + } + } + } + + + selectedAction.authentication = authenticationOptions + if (selectedAction.selectedAuthentication === null || selectedAction.selectedAuthentication === undefined || selectedAction.selectedAuthentication.length === "") { + selectedAction.selectedAuthentication = {} + } + + + if (appUpdates === true) { + setAuthenticationModalOpen(false) + setSelectedAction(selectedAction) + setWorkflow(workflow) + saveWorkflow(workflow) + alert.info("Added and updated authentication!") + } else { + alert.error("Failed to find new authentication - did it work?") + } + } else { + alert.info("No authentication to update") + } + } + } else { + setAuthLoaded(true) + } + }) + .catch(error => { + setAuthLoaded(true) + alert.error("Auth loading error: "+error.toString()) + }) + } + + + const getApps = () => { + fetch(globalUrl+"/api/v1/apps", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for stream results :O!"); - stop(); - } + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for apps :O!") + } - return response.json(); - }) - .then((responseJson) => { - handleUpdateResults(responseJson, executionRequest); - }) - .catch((error) => { - console.log("Error: ", error); - 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) - 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", + 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))) + } }) - .then((response) => { - setSavingState(0); - if (response.status !== 200) { - console.log("Status not 200 for setting workflows :O!"); - } + .catch(error => { + alert.error(error.toString()) + }); + } + + const getWorkflow = (workflow_id, sourcenode) => { + console.log(`Getting workflow ${workflow_id} with append value ${sourcenode}`) - 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", + 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 apps :O!"); - } + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for workflows :O!") + window.location.pathname = "/workflows" + } - 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) => { + // Not sure why this is necessary. + if (responseJson.isValid === undefined) { + responseJson.isValid = true + } - newauth.push(responseJson.data[key]); - } + if (responseJson.errors === undefined) { + responseJson.errors = [] + } - if (cy !== undefined) { - console.log("NEW AUTH = reset cy's onnodeselect"); + if (responseJson.public) { + alert.info("This workflow is public. You will have to save it to make it your own!") + } - // 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)); - } + // Appends SUBFLOWS. Does NOT run during normal grabbing of workflows. + if (sourcenode.id !== undefined) { + console.log("WORKFLOW: ", responseJson) - setAppAuthentication(newauth); - setAuthLoaded(true); + var nodefound = false + const target = sourcenode.parameters.find(item => item.name === "startnode") + console.log(sourcenode.parameters) + console.log(target) + const target_id = target === undefined ? "" : target.value + const actions = responseJson.actions.map(action => { + const node = { + group: "nodes", + } - if (updateAction === true) { - if (selectedApp.authentication.required) { - // Setup auth here :) - var appUpdates = false; - const authenticationOptions = []; + // Set it dynamically? + node.position = { + x: sourcenode.position.x+action.position.x, + y: sourcenode.position.y+action.position.y, + } - var tmpAuth = JSON.parse(JSON.stringify(responseJson.data)); - var latest = 0; - for (var key in tmpAuth) { - var item = tmpAuth[key]; + node.data = action - const newfields = {}; - for (var filterkey in item.fields) { - newfields[item.fields[filterkey].key] = - item.fields[filterkey].value; - } + node.data._id = action["id"] + node.data.type = "ACTION" + node.data.source_workflow = responseJson.id + if (action.id === target_id) { + nodefound = true + } - item.fields = newfields; - if (item.app.name === selectedApp.name) { - authenticationOptions.push(item); + var example = "" + if (action.example !== undefined && action.example !== null && action.example.length > 0) { + example = action.example + } - // Always becoming the last one - if (item.edited > latest) { - latest = item.edited; - selectedAction.selectedAuthentication = item; + node.data.example = example + return node; + }) - 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 edges = responseJson.branches.map((branch, index) => { + const edge = { }; + var conditions = responseJson.branches[index].conditions + if (conditions === undefined || conditions === null) { + conditions = [] + } - selectedAction.authentication = authenticationOptions; - if ( - selectedAction.selectedAuthentication === null || - selectedAction.selectedAuthentication === undefined || - selectedAction.selectedAuthentication.length === "" - ) { - selectedAction.selectedAuthentication = {}; - } + var label = "" + if (conditions.length === 1) { + label = conditions.length+" condition" + } else if (conditions.length > 1) { + label = conditions.length+" conditions" + } - 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 sourceFound = actions.findIndex(action => action.data.id === branch.source_id) + if (sourceFound < 0) { + return null + } - const getApps = () => { - fetch(globalUrl + "/api/v1/apps", { - method: "GET", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", + 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) + } + } + } }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for apps :O!"); - } + .catch(error => { + alert.error(error.toString()) + }); + } - 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 onUnselect = (event) => { + console.time("UNSELECT") - 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 nodedata = event.target.data() + if (nodedata.app_name === undefined && nodedata.source === undefined) { + return + } + event.target.removeClass("selected") - const getWorkflow = (workflow_id, sourcenode) => { - console.log( - `Getting workflow ${workflow_id} with append value ${sourcenode}` - ); - fetch(globalUrl + "/api/v1/workflows/" + workflow_id, { - method: "GET", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for workflows :O!"); - window.location.pathname = "/workflows"; - } - - return response.json(); - }) - .then((responseJson) => { - // Not sure why this is necessary. - if (responseJson.isValid === undefined) { - responseJson.isValid = true; - } - - if (responseJson.errors === undefined) { - responseJson.errors = []; - } - - if (responseJson.public) { - alert.info( - "This workflow is public. You will have to save it to make it your own!" - ); - } - - // 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 - /* + // 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) @@ -1595,263 +1431,242 @@ 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({}); - }; - // Comparing locations between nodes and setting views - var styledElements = []; - var originalLocation = { - x: 0, - y: 0, - }; + setSelectedAction({}) + setSelectedTrigger({}) + } - 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; - } + // Comparing locations between nodes and setting views + var styledElements = [] + var originalLocation = { + x: 0, + y: 0, + } - const workflow_id = nodedata.parameters.find( - (param) => param.name === "workflow" - ); - if (workflow.id === workflow_id.valu) { - return; - } - cy.animation({ - zoom: 0, - center: { - eles: event.target, - }, - }) - .play() - .promise() - .then(() => { - console.log("DONE: ", workflow_id); - getWorkflow(workflow_id.value, nodedata); - cy.fit(null, 50); - }); - } - }; + 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 onNodeDragStop = (event, selectedAction) => { - const nodedata = event.target.data(); - if (nodedata.id === selectedAction.id) { - return; - } + const workflow_id = nodedata.parameters.find(param => param.name === "workflow") + if (workflow.id === workflow_id.valu) { + return + } - if (nodedata.finished === false) { - 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 (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); - } + } + } - originalLocation = { x: 0, y: 0 }; - } + const onNodeDragStop = (event, selectedAction) => { + const nodedata = event.target.data() + if (nodedata.id === selectedAction.id) { + 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 (nodedata.finished === false) { + return + } - selectedAction.parameters[idsplit[2]].value = newValue; - paramname = selectedAction.parameters[idsplit[2]].name; - idnumber = idsplit[2]; - } - } + 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) + } - if (idnumber >= 0 && paramname.length > 0) { - const exampledata = GetExampleResult(nodedata); - const parsedname = paramname - .toLowerCase() - .trim() - .replaceAll("_", " "); + originalLocation = {x: 0, y: 0} + } - const foundresult = GetParamMatch(parsedname, exampledata, ""); - if (foundresult.length > 0) { - console.log("FOUND RESULT: ", paramname, foundresult); - newValue = `${newValue}${foundresult}`; - } + 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] + } + } - selectedAction.parameters[idnumber].value = newValue; - } + if (idnumber >= 0 && paramname.length > 0) { + const exampledata = GetExampleResult(nodedata) + const parsedname = paramname.toLowerCase().trim().replaceAll("_", " ") - curElement.value = newValue; - } - } + const foundresult = GetParamMatch(parsedname, exampledata, "") + if (foundresult.length > 0) { + console.log("FOUND RESULT: ", paramname, foundresult) + newValue = `${newValue}${foundresult}` + } - 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; - } - } + selectedAction.parameters[idnumber].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); + curElement.value = newValue + } + } - 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, - }, - }; + 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 + } + } - cy.add(decoratorNode).unselectify(); - } else { - console.log("Node already exists - don't add descriptor node"); - } - } - originalLocation = { - x: 0, - y: 0, - }; - }; + // Readding the icon after moving the node + if (!found) { + const iconInfo = GetIconInfo(nodedata) + const svg_pin = `` + const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin) - const onNodeDrag = (event, selectedAction) => { - const nodedata = event.target.data(); - if (nodedata.finished === false) { - return; - } + 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, + }, + } - 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) - } + cy.add(decoratorNode).unselectify() + } else { + console.log("Node already exists - don't add descriptor node") + } + } + originalLocation = { + x: 0, + y: 0, + } + } - if (nodedata.id === selectedAction.id) { - return; - } + const onNodeDrag = (event, selectedAction) => { + const nodedata = event.target.data() + if (nodedata.finished === false) { + 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({ @@ -1867,709 +1682,588 @@ 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) @@ -2582,358 +2276,336 @@ const AngularWorkflow = (props) => { } } */ - } - } - // 1. Guess what the next node's action should be - // 2. Get result from previous nodes (if any) - // 3. TRY to automatically map them in based on synonyms - const newsource = cy.getElementById(edge.source); - const newdst = cy.getElementById(edge.target); - if ( - newsource !== undefined && - newsource !== null && - newdst !== undefined && - newdst !== null - ) { - const dstdata = RunAutocompleter(newdst.data()); - console.log("DST: ", dstdata); - } + + } + } - var newbranch = { - source_id: edge.source, - destination_id: edge.target, - id_: edge.id, - id: edge.id, - hasErrors: false, - decorator: false, - }; - if (!found) { - newbranch["hasErrors"] = false; + // 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) + } - workflow.branches.push(newbranch); - setWorkflow(workflow); - } + var newbranch = { + "source_id": edge.source, + "destination_id": edge.target, + "id_": edge.id, + "id": edge.id, + "hasErrors": false, + "decorator": false, + } - history.push({ - type: "edge", - action: "added", - data: edge, - }); - setHistory(history); - setHistoryIndex(history.length); - }; + if (!found) { + newbranch["hasErrors"] = false - 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; - } + workflow.branches.push(newbranch) + setWorkflow(workflow) + } - //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; - } + history.push({ + "type": "edge", + "action": "added", + "data": edge, + }) + setHistory(history) + setHistoryIndex(history.length) + } - // 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 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 + } - 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, - }; + //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 edgeToBeAdded = { - group: "edges", - data: newcybranch, - }; + // 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("SHOULD STITCH WITH STARTNODE"); - cy.add(edgeToBeAdded); - } + if (nodedata.type === "ACTION") { - if (nodedata.app_name === "Shuffle Tools") { - const iconInfo = GetIconInfo(nodedata); - const svg_pin = ``; - const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin); - nodedata.large_image = svgpin_Url; - nodedata.fillGradient = iconInfo.fillGradient; - nodedata.fillstyle = "solid"; - if ( - nodedata.fillGradient !== undefined && - nodedata.fillGradient !== null && - nodedata.fillGradient.length > 0 - ) { - nodedata.fillstyle = "linear-gradient"; - } else { - nodedata.iconBackground = iconInfo.iconBackgroundColor; - } - } - if ( - nodedata.parameters !== undefined && - nodedata.parameters !== null && - !nodedata.label.endsWith("_copy") - ) { - var newparameters = []; + 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, + } - for (var subkey in nodedata.parameters) { - var newparam = JSON.parse( - JSON.stringify(nodedata.parameters[subkey]) - ); - newparam.id = uuidv4(); - newparam.value = ""; - newparameters.push(newparam); - } + const edgeToBeAdded = { + group: "edges", + data: newcybranch, + } - nodedata.parameters = newparameters; - } + 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 === undefined || workflow.actions === null) { - workflow.actions = [nodedata]; - } else { - workflow.actions.push(nodedata); - } + 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) + } - setWorkflow(workflow); - } else if (nodedata.type === "TRIGGER") { - if (nodedata.is_valid === false) { - alert.info("This trigger is not available to you"); - node.remove(); - return; - } + nodedata.parameters = newparameters + } - if (workflow.triggers === undefined) { - workflow.triggers = [nodedata]; - } else { - workflow.triggers.push(nodedata); - } + if (workflow.actions === undefined || workflow.actions === null) { + workflow.actions = [nodedata] + } else { + workflow.actions.push(nodedata) + } - const newEdgeUuid = uuidv4(); - const newcybranch = { - source: nodedata.id, - target: workflow.start, - source_id: nodedata.id, - destination_id: workflow.start, - _id: newEdgeUuid, - id: newEdgeUuid, - hasErrors: false, - decorator: false, - }; + 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 edgeToBeAdded = { - group: "edges", - data: newcybranch, - }; + if (workflow.triggers === undefined) { + workflow.triggers = [nodedata] + } else { + workflow.triggers.push(nodedata) + } - if ( - nodedata.name !== "User Input" && - nodedata.name !== "Shuffle Workflow" - ) { - if ( - workflow.actions !== undefined && - workflow.actions !== null && - workflow.actions.length > 0 - ) { - cy.add(edgeToBeAdded); - } - } + 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, + } - setWorkflow(workflow); - } + const edgeToBeAdded = { + group: "edges", + data: newcybranch, + } - if (nodedata.app_name !== undefined) { - history.push({ - type: "node", - action: "added", - data: nodedata, - }); - setHistory(history); - setHistoryIndex(history.length); - } - }; + if (nodedata.name !== "User Input" && nodedata.name !== "Shuffle Workflow") { + if (workflow.actions !== undefined && workflow.actions !== null && workflow.actions.length > 0) { + cy.add(edgeToBeAdded) + } + } - const onEdgeRemoved = (event) => { - setLastSaved(false); - const edge = event.target; - workflow.branches = workflow.branches.filter( - (a) => a.id !== edge.data().id - ); - setWorkflow(workflow); - event.target.remove(); - // trigger as source check - const indexcheck = workflow.triggers.findIndex( - (data) => edge.data()["source"] === data.id - ); - if (indexcheck !== -1) { - console.log("Shouldnt remove edge from trigger"); - } + setWorkflow(workflow) + } - if (edge.data().source !== undefined) { - history.push({ - type: "edge", - action: "removed", - data: edge.data().source, - }); - setHistory(history); - setHistoryIndex(history.length); - } - }; - const onNodeRemoved = (event) => { - const node = event.target; - const data = node.data(); + if (nodedata.app_name !== undefined) { + history.push({ + "type": "node", + "action": "added", + "data": nodedata, + }) + setHistory(history) + setHistoryIndex(history.length) + } - if (data.finished === false) { - return; - } + } - workflow.actions = workflow.actions.filter((a) => a.id !== data.id); - workflow.triggers = workflow.triggers.filter((a) => a.id !== data.id); - if (workflow.start === data.id && workflow.actions.length > 0) { - // FIXME - should check branches connected to startnode, as picking random - // is just confusing - if (workflow.actions[0].id !== data.id) { - const ele = cy.getElementById(workflow.actions[0].id); - if (ele !== undefined && ele !== null) { - ele.data("isStartNode", true); - workflow.start = ele.id(); - } - } else { - if (workflow.actions.length > 1) { - const ele = cy.getElementById(workflow.actions[1].id); - if (ele !== undefined && ele !== null) { - ele.data("isStartNode", true); - workflow.start = ele.id(); - } - } - } - } + const onEdgeRemoved = (event) => { + setLastSaved(false) + const edge = event.target - 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(); - } - } + workflow.branches = workflow.branches.filter(a => a.id !== edge.data().id) + setWorkflow(workflow) + event.target.remove() - history.push({ - type: "node", - action: "removed", - data: data, - }); - setHistory(history); - setHistoryIndex(history.length); - } + // 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") - 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"); - } + if (edge.data().source !== undefined) { + history.push({ + "type": "edge", + "action": "removed", + "data": edge.data().source, + }) + setHistory(history) + setHistoryIndex(history.length) + } + } - 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; - } - } + const onNodeRemoved = (event) => { + const node = event.target + const data = node.data() - console.log("CTRL+C"); - if (cy !== undefined) { - var cydata = cy.$(":selected").jsons(); - if (cydata !== undefined && cydata !== null && cydata.length > 0) { - console.log(cydata); + if (data.finished === false) { + 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(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() - /* + 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() + /* const clipboard = navigator.clipboard if (clipboard === undefined || window === undefined || window === null) { alert.error("Can only use cliboard over HTTPS (port 3443)") @@ -2949,676 +2621,612 @@ const AngularWorkflow = (props) => { var jsonvalid = true var parsedjson = [] */ - } - break; - case 88: - if (event.ctrlKey) { - console.log("CTRL+X"); - } - break; - case 83: - break; - case 70: - break; - case 65: - // As a poweruser myself, I found myself hitting this a few - // too many times to just edit text. Need a better bind, which does NOT work while inside a field - break; - default: - break; - } + } + break; + case 88: + if (event.ctrlKey) { + console.log("CTRL+X") + } + break; + case 83: - //previouskey = event.keyCode - }; + 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; + } - 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; - } - } + //previouskey = event.keyCode + } - 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) + 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 + } + } - for (var key in parsedjson) { - const item = parsedjson[key]; - console.log("Adding: ", item); + 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) - cy.add({ - group: item.group, - data: item.data, - position: item.position, - }); - } - } catch (e) { - console.log("Error pasting: ", e); - //alert.info("Failed parsing clipboard: ", e) - } - }; + for (var key in parsedjson) { + const item = parsedjson[key] + console.log("Adding: ", item) - const registerKeys = () => { - document.addEventListener("keydown", handleKeyDown); - document.addEventListener("paste", handlePaste); - }; + 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 getEnvironments = () => { - fetch(globalUrl + "/api/v1/getenvironments", { - method: "GET", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", + 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", }) - .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) @@ -3641,4025 +3249,2843 @@ 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,/9j/4AAQSkZJRgABAQAAAQABAAD//gAfQ29tcHJlc3NlZCBieSBqcGVnLXJlY29tcHJlc3P/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCACuAK4DASIAAhEBAxEB/8QAHQABAQACAgMBAAAAAAAAAAAAAAgHCQEGAgMEBf/EAEQQAAEDAwIEAgUIBQsFAAAAAAABAgMEBhEFBwgSIUExUQkTInGRFBYyUmF0gbM2QlfB0RUYGSMzOENGcoOSlaGxtMP/xAAcAQEAAgIDAQAAAAAAAAAAAAAAAQYEBwIDBQj/xAAwEQABAwMCAgkEAgMAAAAAAAAAAQIDBAURBiESQQcTFBUiMVFhsXGBkaEl8DLB8f/aAAwDAQACEQMRAD8Aw0AD6lPlQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJjuFx5kgADxOOUzg5Y2yAAScQAAAAAAAAAAAAAAAAAAACFXBICdVwnVfsMtcPnD9qXEBqWr6fp1yw6O3Ro4JZ3yUyyvkSRzk6YVPqO+BUtuejw25pEYt03Xr2s+17bGKylY5PJUb1x7lRSs3PVtvtcqwSqquTkiFltulLhdIkmiREavNSAF9n6XT39D3UtLV1r/AFdBR1FS9fBII3yKn4NRTaTb3CJsHbbY0pLAoap8fg+tc+oVfejlwpkzRLPtu3WNi0PQdOoGo3lxS0zIUx5eymfipV6rpGiTaCFV+qlppejmoXeeVE+hqltzh83wurkfou2WtvikwjaiphbBGqL3y9UX/sfVudw9bkbP6BQ3DfdHp1FDXVXyaOKOq9dKjuXxVW+z+HY2zJGnL7TeqL44wST6RdyJt/a7c9f5a/8Amp0WrWtfc7jFTuaiNcuMeZkXXRlFa7fJPxKrmp5kCORWqqKqqqd1Bz4dFb4DKeRtXizuapOABheVHdndUXzJTCpknhAABxAAAAAAAAAAAAAA7ELtuShZHo3EVLiv3y+R6b+ZUl2ZTonmQp6Nz9Ib9+5ab+ZUl15x4qfP2r898zY9vhDf+il/hYvv8hzmtTLlROuOoRyKT9xB8XVq7LVvzZoNPTXLidGkjqRJ0ijp0d9H1j8KuV7NRMnSNoePbQbwuKntq/rcZb81c9I4KqKpdJCj+zXNe1HN/wBWMHnx2Ovmg7Q2NVb/AHkejJqG3xVHZXyeL9J9yuEVF6KSd6QSljr7Tsuhle+NlTccMLns+k1qtwqp8SroZWzRpIxUVq9Wqi5ynZSV+PrLbdsN6NVUS54VXHuRDssL1iuEbm7KmfhThqBjZLdIjt02+UPy4fR02M+NF+ftwR+bUbGqIvdEVUyp5/0c1jftCuH/AIRfwK3jlRWKuMNRVyufA6huZu5Y+02gya/eesRUcSIvqos5lnd2bGxOrlXw6GYzUt7lf1cUrlVeSf8ADBdpmxwRdZLG3CcycKr0eFhQU8ky7j68xI0VXOc2LlZjuuSK76tyjtK8NXtvT9cpdZp9OqnQRV1Pjlmb+HTKd8GetwN9d5+Ka4FsbbPSq/T9DmVyeopHcsj2edTInRjemcJ5Y7mC9xrFrts711SxdTqKeaq0qSNkzoM8nO+GOReXPX9dE690NlaYWujl6u4T5kVM8HNE9cmtNStoXR8dvg4WIuOL1OuAAvBSgAAAAAAAAAAAAOwHYhxKFlejd6XDfv3LTfzKkt7Vqtmn6XWahKvs00Eky48mtV37iIfRu9bhvz7lpv5lSW/qtC3UtLq9Pf0bUwyQr7nNVv7zQOrMd+TZ8sp8Ib70dxdxR8Pv8qQDwgWTQb2bx3TuTf1MzVkoJHVjIp2o6N9RNIqxOci/UjRERPAyFx37PW1DY1LuNoGmwadq1HWwUkz6aNI0likXDc48Fa7Cpjx7mOuFi9qLh+3rurbvcCdNMgrJPkXrp05WJNE5VifzfVexUan2neOOne609YtCi2ztnU4dU1CsrYa2o+Rv9Z6qKP2meHRVc7CYPdlbWJfIVhReqw3flw439vUr0XZEssrZsddl3n/lxZ2M/cLl3V167GWvrmpzrNVLS+olevi5Y3K3PwRDEHpE5J4bCtOale5k7NeR8b2plWubE5UVE79UQzFwyWZXWBsla9uam1W1cdGlRM1UxyvlVXq38Mohifj4Vfm/YaZxm54M479E6Hg22RjL7xsTLUc78YUstfHJLYeB+zla387GH9G4+7/0C0K239dtqlrblpnJDT18r0jRuVx/XReKvb5J4nzbccOW7nErcLNwt39ZrqPSZX80ctT/AG0rfq00K+zE1U/W8fIuSq2j2y1PWY7kr7F0Wo1OJyubVSUbFfzZ8VXHVftO2shbFGkcbURG9GoiYwnkh3S6gp4Gr3dAjHu83Lvj6GPBpuonx3jPxtTyRNvydW2/2tsva/QotBs3R4aKnjROZ6JmWVfN7/Fy+81p8WitTiLvlrGI1Frqdy+ar8jpzawv0fwNU3Fr/eNvn77T/wDpwHo6BkfLeHvkXKq1fP7Hn6/hZBao440wiO/0YkABuk0uAAAAAAAAAAAAB2A7EKShZXo3f0iv37lpv5lSXU5Ua1VXshCvo3lxcN+KvRFo9Nwv+5UfxLr6Hz7rHe8zInt8Ib/0UmLLCq+/yYL334U7M3tqY9amq59G1yNiM+X07GuSZvZJGL0djt4Kh0zaTgTs6wNaprhu7Xprnq6F/raeB1K2Cna/PsuVEVVcqdkVcZKmRUXwU5MBl6r44OzpIqN9D0n2Ggln7S9iK7+8j1xxIxMNROvZPMlbj5wmgWHlf8zw/wDhCrPDsSR6RKWansa1KmmlfFLFrrXskY5yOY5I1wrcdzv07GstziY3zVV/aKdWo3pFbJHJyx+lQrSOWJWIqOb4r3PLnj+u34mopOIDfVEwm712on2V6onwwc/zgd9v2v3d/wBQd/Asa9Htx38TfyVlOkShbssbsm3CSaNInO506Iqmqriwkjk4hr2kY7KuroUX7FSmjT9yfA/CfxAb6uarXbu3aqKmFRa9VT4YOkVlZWahVTV+o1UtTVVLvWTTSvfI+R31nOcviWbSmlamy1bqiocm6YTBWdUaqgvdK2CJqphc7npABsI18AAAAAAAAAAAAB17gDy3JTbcyZsbv7c+xGqalqFv6Rp9e3V2Qx1LKlHNwkSqreVU8+ZSl7c9IzpE6MZd23VdSKqojn0FW2drU88ORqr+BDnQFcuelrdc5VmmZ4l5opYrfqi4WyNIoX+FOSmze2uNzYHXeRKq6ZdFc7pyanSPi6+XMnMhljQNzbAuqNslv3jo1ejmo5EgrY3OwvTq3OU/FDTh08MZ95yzlY9JGqjHJ1RWZa5F+xWqilYqejindvTyqn13LPS9ItU3aeNFT2N2KSRuTmRyKnmSN6RWSJ+39rqyRq51rsqL/hqRxbu8269pub83txNfpY2Y5YVrFfF082uzk+/cPfvdHdXQ6S3761ul1Gnoan5TC9KRscueXHVW4Tp7jGtmiK223CKoV6OY1d/UybprajudBJT8Ctc5DHyomVTHxOMJ5Drjqqqvmq5U5NpmrTg5ABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//9k=", - 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 } @@ -7684,4240 +6110,2793 @@ const AngularWorkflow = (props) => { const trigger_header_auth = workflow.triggers[selectedTriggerIndex].parameters.length > 2 ? workflow.triggers[selectedTriggerIndex].parameters[2].value : "" */ - 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; + 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 diff --git a/frontend/src/views/AppCreator.jsx b/frontend/src/views/AppCreator.jsx index 6d068495..5554e204 100644 --- a/frontend/src/views/AppCreator.jsx +++ b/frontend/src/views/AppCreator.jsx @@ -1,3602 +1,2827 @@ -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 dividerStyle = { + marginBottom: "10px", + marginTop: "10px", + height: "1px", + width: "100%", + backgroundColor: "grey", +} + +const appIconStyle = { + marginLeft: "5px", +} -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; - } + try { + var args = rewrite(words.split(s)) + } catch (e) { + return s + } + + var out = { method: 'GET', header: {} } + var state = '' - var out = { method: "GET", header: {} }; - var state = ""; - - args.forEach(function (arg) { + 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 [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 [app, setApp] = useState({}) + const [appAuthentication, setAppAuthentication] = React.useState([]); + const [selectedAction, setSelectedAction] = useState({}) + const [authLoaded, setAuthLoaded] = useState(false) - 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 [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 isCloud = - window.location.host === "localhost:3002" || - window.location.host === "shuffler.io"; + 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], + }); - useEffect(() => { - if (firstrequest) { - setFirstrequest(false); - if (window.location.pathname.includes("apps/edit")) { - setIsEditing(true); - handleEditApp(); - } else { - checkQuery(); - } - } - }); + const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" - const handleEditApp = () => { - fetch(globalUrl + "/api/v1/apps/" + props.match.params.appid + "/config", { - method: "GET", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", + + 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", }) - .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", - }, - }} - /> -
-
{ - deletePathQuery(index); - }} - > - Delete -
-
- ); - })} - -
- ); + setUrlPathQueries(urlPathQueries) + }} + InputProps={{ + style:{ + color: "white", + }, + }} + /> - const loopActions = - actions.length === 0 ? null : ( -
- {filteredActions.slice(0, actionAmount).map((data, index) => { - var error = - data.errors.length > 0 ? ( - - - - ) : ( - - - - ); +
+
{deletePathQuery(index)}}> + Delete +
- 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 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); + const loopActions = actions.length === 0 ? + null + : +
+ {filteredActions.slice(0,actionAmount).map((data, index) => { + var error = data.errors.length > 0 ? + + + + : + + + + - if ( - data["body"] !== undefined && - data["body"] !== null && - data["body"].length > 0 - ) { - findBodyParams(data["body"]); - } + 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 (hasFile) { - setFileUploadEnabled(true); - } - }} - > -
- - - {hasFile ? ( - - ) : null}{" "} - {url} - {data.name} - -
-
- - {/* + 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} + + +
+
+
+ {/*
{testAction(index)}}> Test
*/} - -
{ - duplicateAction(index); - }} - > - -
-
- -
{ - deleteAction(index); - }} - > - -
-
-
- ); - })} -
- ); + +
{ + duplicateAction(index) + }}> + +
+
+ +
{deleteAction(index)}}> + +
+
+
+ ) + })} +
- const setActionField = (field, value) => { - currentAction[field] = value; - setCurrentAction(currentAction); + const setActionField = (field, value) => { + currentAction[field] = value + setCurrentAction(currentAction) - //setUrlPathQueries(currentAction.queries) - }; + //setUrlPathQueries(currentAction.queries) + } - const findBodyParams = (body) => { - const regex = /\${(\w+)}/g; - const found = body.match(regex); - if (found === null) { - setExtraBodyFields([]); - } else { - setExtraBodyFields(found); - } - }; + 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); + const HandleIndividualChip = (props) => { + const { chipData, index } = props; + const [chipRequired, setChipRequired] = useState(false) - return ( - - { - console.log("CLICK: ", chipData); - setChipRequired(!chipRequired); - }} - /> - - ); - }; + return ( + + { + console.log("CLICK: ", chipData) + setChipRequired(!chipRequired) + }} + /> + + ) + } - const SetExtraBodyField = (props) => { - const { extraBodyFields } = props; + const SetExtraBodyField = (props) => { + const { extraBodyFields } = props; - if (extraBodyFields === undefined || extraBodyFields === null) { - return null; - } + if (extraBodyFields === undefined || extraBodyFields === null) { + return null + } - //const parsedlist = extraBodyFields.join(", ") - //console.log("LIST: ", parsedlist) + //const parsedlist = extraBodyFields.join(", ") + //console.log("LIST: ", parsedlist) - return ( - - {extraBodyFields.map((data, index) => { - return ; - })} - - ); - }; + 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 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", + }, + }} + /> - 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", - }, - }} - /> -
- ); +
+
+
+ : null - const addActionToView = (errors) => { - currentAction.errors = errors; - currentAction.queries = urlPathQueries; - setUrlPathQueries([]); + 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 actionIndex = actions.findIndex( - (data) => data.name === currentAction.name - ); - if (actionIndex < 0) { - actions.push(currentAction); - } else { - actions[actionIndex] = currentAction; - } + const addActionToView = (errors) => { + currentAction.errors = errors + currentAction.queries = urlPathQueries + setUrlPathQueries([]) - if (actions.length > actionAmount) { - setActionAmount(actions.length); - } + const actionIndex = actions.findIndex(data => data.name === currentAction.name) + if (actionIndex < 0) { + actions.push(currentAction) + } else { + actions[actionIndex] = currentAction + } - setActions(actions); - setFilteredActions(actions); - }; - const getActionErrors = () => { - var errormessage = []; - if (currentAction.name === undefined || currentAction.name.length === 0) { - errormessage.push("Name can't be empty"); - } + if (actions.length > actionAmount) { + setActionAmount(actions.length) + } - // 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 /"); - } + setActions(actions) + setFilteredActions(actions) + } - const check = urlPathQueries.findIndex((data) => data.name.length === 0); - if (check >= 0) { - errormessage.push("All queries must have a value"); - } + const getActionErrors = () => { + var errormessage = [] + if (currentAction.name === undefined || currentAction.name.length === 0) { + errormessage.push("Name can't be empty") + } - return errormessage; - }; + // 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 getCurrentPaths = (urlPath) => { - var paths = []; - var queries = []; + const check = urlPathQueries.findIndex(data => data.name.length === 0) + if (check >= 0) { + errormessage.push("All queries must have a value") + } - if (urlPath.includes("{") && urlPath.includes("}")) { - var tmpWord = ""; - var record = false; + return errormessage + } - var query = false; - for (var key in urlPath) { - if (urlPath[key] === "?") { - query = true; - } + const getCurrentPaths = (urlPath) => { + var paths = [] + var queries = [] - if (urlPath[key] === "}") { - if (tmpWord === parameterName) { - tmpWord = ""; - record = false; - continue; - } else if (query) { - queries.push(tmpWord); - } else { - paths.push(tmpWord); - } + if (urlPath.includes("{") && urlPath.includes("}")) { + var tmpWord = "" + var record = false - tmpWord = ""; - record = false; - } + var query = false + for (var key in urlPath) { + if (urlPath[key] === "?") { + query = true + } - if (record) { - tmpWord += urlPath[key]; - } + if (urlPath[key] === "}") { + if (tmpWord === parameterName) { + tmpWord = "" + record = false + continue + } else if (query) { + queries.push(tmpWord) + } else { + paths.push(tmpWord) + } - //if (urlPath[key] === "{" && urlPath[key-1] === "/") { - if (urlPath[key] === "{") { - record = true; - } - } - } + tmpWord = "" + record = false + } - if (urlPath.includes("<") && urlPath.includes(">")) { - var tmpWord = ""; - var record = false; + if (record) { + tmpWord += urlPath[key] + } - var query = false; - for (var key in urlPath) { - if (urlPath[key] === "?") { - query = true; - } + //if (urlPath[key] === "{" && urlPath[key-1] === "/") { + if (urlPath[key] === "{") { + record = true + } + } + } - if (urlPath[key] === ">") { - if (tmpWord === parameterName) { - tmpWord = ""; - record = false; - continue; - } else if (query) { - queries.push(tmpWord); - } else { - paths.push(tmpWord); - } + if (urlPath.includes("<") && urlPath.includes(">")) { + var tmpWord = "" + var record = false - tmpWord = ""; - record = false; - } + var query = false + for (var key in urlPath) { + if (urlPath[key] === "?") { + query = true + } - if (record) { - tmpWord += urlPath[key]; - } + if (urlPath[key] === ">") { + if (tmpWord === parameterName) { + tmpWord = "" + record = false + continue + } else if (query) { + queries.push(tmpWord) + } else { + paths.push(tmpWord) + } - //if (urlPath[key] === "{" && urlPath[key-1] === "/") { - if (urlPath[key] === "<") { - record = true; - } - } - } + tmpWord = "" + record = false + } - return [paths, queries]; - }; + if (record) { + tmpWord += urlPath[key] + } - const UrlPathParameters = () => { - const values = getCurrentPaths(urlPath); - const paths = values[0]; - const queries = values[1]; + //if (urlPath[key] === "{" && urlPath[key-1] === "/") { + if (urlPath[key] === "<") { + record = true + } + } + } - if (currentAction.paths !== paths && urlPath.length > 0) { - //console.log("IN PATHS SETTER: !", paths) - setActionField("paths", paths); - } + return [paths, queries] + } - var tmpQueries = []; + const UrlPathParameters = () => { + const values = getCurrentPaths(urlPath) + const paths = values[0] + const queries = values[1] - // 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 }); - } - } + if (currentAction.paths !== paths && urlPath.length > 0) { + //console.log("IN PATHS SETTER: !", paths) + setActionField("paths", paths) + } - // FIXME: Frontend isn't updating.. - if ( - tmpQueries.length > 0 && - JSON.stringify(tmpQueries) !== JSON.stringify(urlPathQueries) - ) { - setUrlPathQueries(tmpQueries); - } + var tmpQueries = [] - return paths.length > 0 ? ( -
Required parameters: {paths.join(", ")}
- ) : null; - }; + // 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}) + } + } - 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(" ", " "); - } + // FIXME: Frontend isn't updating.. + if (tmpQueries.length > 0 && JSON.stringify(tmpQueries) !== JSON.stringify(urlPathQueries)) { + setUrlPathQueries(tmpQueries) + } - if (parsedurl.includes(" ")) { - parsedurl = parsedurl.replaceAll(" ", " "); - } + return paths.length > 0 ? +
+ Required parameters: {paths.join(", ")} +
+ : null + } - if (parsedurl.includes("[") && parsedurl.includes("]")) { - //console.log("REPLACE1") - parsedurl = parsedurl.replaceAll("[", "{"); - parsedurl = parsedurl.replaceAll("]", "}"); - } + 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.includes(">")) { - //console.log("REPLACE2") - parsedurl = parsedurl.replaceAll("<", "{"); - parsedurl = parsedurl.replaceAll(">", "}"); - } + if (parsedurl.includes(" ")) { + 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 (parsedurl.includes("[") && parsedurl.includes("]")) { + //console.log("REPLACE1") + parsedurl = parsedurl.replaceAll("[", "{") + parsedurl = parsedurl.replaceAll("]", "}") + } - if (tmp.length > 1) { - parsedurl = tmp[1].trim(); - setActionField("url", parsedurl); + if (parsedurl.includes("<") && parsedurl.includes(">")) { + //console.log("REPLACE2") + parsedurl = parsedurl.replaceAll("<", "{") + parsedurl = parsedurl.replaceAll(">", "}") + } - setCurrentActionMethod(tmp[0].toUpperCase()); - setActionField("method", tmp[0].toUpperCase()); - } + //console.log("URL2: ", parsedurl) + if (parsedurl.startsWith("PUT ") || parsedurl.startsWith("GET ") ||parsedurl.startsWith("POST ") || parsedurl.startsWith("DELETE ") ||parsedurl.startsWith("PATCH ") || parsedurl.startsWith("CONNECT ")) { - console.log("URL3: ", parsedurl); + const tmp = parsedurl.split(" ") - setUpdate(Math.random()); - } else if (parsedurl.startsWith("curl")) { - console.log("URL4: ", parsedurl); + if (tmp.length > 1) { + parsedurl = tmp[1].trim() + setActionField("url", 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()); - } + setCurrentActionMethod(tmp[0].toUpperCase()) + setActionField("method", tmp[0].toUpperCase()) + } + + console.log("URL3: ", parsedurl) - 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; - } + setUpdate(Math.random()) + } else if (parsedurl.startsWith("curl")) { + console.log("URL4: ", parsedurl) - if ( - key === "Authorization" && - authenticationOption === "Bearer auth" - ) { - continue; - } + 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()) + } - headers += key + "=" + value + "\n"; - } + 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 + } - setActionField("headers", headers); - } + if (key === "Authorization" && authenticationOption === "Bearer auth") { + continue + } - if (request.body !== undefined && request.body !== null) { - setActionField("body", request.body); - } + headers += key+"="+value+"\n" + } - // Parse URL - if (request.url !== undefined) { - parsedurl = request.url; - } - } + setActionField("headers", headers) + } - console.log("PARSED: ", parsedurl); - if (parsedurl !== undefined) { - if (parsedurl.includes("<") && parsedurl.includes(">")) { - parsedurl = parsedurl.split("<").join("{"); - parsedurl = parsedurl.split(">").join("}"); - } + if (request.body !== undefined && request.body !== null) { + setActionField("body", request.body) + } - if ( - parsedurl.startsWith("http") || - parsedurl.startsWith("ftp") - ) { - if ( - parsedurl !== undefined && - parsedurl.includes(parameterName) - ) { - // Remove <> etc. - // + // Parse URL + if (request.url !== undefined) { + parsedurl = request.url + } + } - console.log("IT HAS THE PARAM NAME!"); - const newurl = new URL(encodeURI(parsedurl)); - newurl.searchParams.delete(parameterName); - parsedurl = decodeURI(newurl.href); - } + console.log("PARSED: ", parsedurl) + if (parsedurl !== undefined) { + if (parsedurl.includes("<") && parsedurl.includes(">")) { + parsedurl = parsedurl.split("<").join("{") + parsedurl = parsedurl.split(">").join("}") + } - // Remove the base URL itself - if ( - parsedurl !== undefined && - baseUrl !== undefined && - baseUrl.length > 0 && - parsedurl.includes(baseUrl) - ) { - parsedurl = parsedurl.replace(baseUrl, ""); - } + 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) + } - // Check URL query && headers - //setActionField("url", parsedurl) - } - } - } + // Remove the base URL itself + if (parsedurl !== undefined && baseUrl !== undefined && baseUrl.length > 0 && parsedurl.includes(baseUrl)) { + parsedurl = parsedurl.replace(baseUrl, "") + } - 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} - - - - - - -
- ); + // Check URL query && headers + //setActionField("url", parsedurl) + } + } + } - const categories = [ - "Communication", - "Cases", - "SIEM", - "Assets", - "Intel", - "IAM", - "Network", - "Eradication", - "Other", - ]; + 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 + 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) + } + } - const found = selectedAction.parameters.find( - (param) => param.name === item.name - ); - if (found === null || found === undefined) { - selectedAction.parameters.push(item); - } - } + setSelectedAction(selectedAction) + } - setSelectedAction(selectedAction); - } + //alert.error("Failed getting authentications") + } + }) + .catch(error => { + alert.error("Auth loading error: "+error.toString()) + }) + } - //alert.error("Failed getting authentications") - } - }) - .catch((error) => { - alert.error("Auth loading error: " + error.toString()); - }); - }; + 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 + } - 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; - } + 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) + } + } + } - 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); - } - } - } + 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!") + } - 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) + setAuthenticationModalOpen(false) - return response.json(); - }) - .then((responseJson) => { - if (!responseJson.success) { - alert.error("Failed to set app auth: " + responseJson.reason); - } else { - getAppAuthentication(true); - setAuthenticationModalOpen(false); + // Needs a refresh with the new authentication.. + //alert.success("Successfully saved new app auth") + } + }) + .catch(error => { + alert.error(error.toString()) + }) + } - // Needs a refresh with the new authentication.. - //alert.success("Successfully saved new app auth") - } - }) - .catch((error) => { - alert.error(error.toString()); - }); - }; + const AuthenticationData = (props) => { + const selectedApp = props.app + console.log("APP: ", selectedApp) - const AuthenticationData = (props) => { - const selectedApp = props.app; - console.log("APP: ", selectedApp); + const [authenticationOption, setAuthenticationOptions] = React.useState({ + app: JSON.parse(JSON.stringify(selectedApp)), + fields: {}, + label: "", + usage: [{ + workflow_id: workflow.id, + }], + id: uuidv4(), + active: 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; - } + if (selectedApp.authentication.parameters === null || selectedApp.authentication.parameters === undefined || selectedApp.authentication.parameters.length === 0) { + return null + } - if ( - selectedApp.authentication.parameters === null || - selectedApp.authentication.parameters === undefined || - selectedApp.authentication.parameters.length === 0 - ) { - return null; - } + 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 - } + 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 + } + } - 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; - } - } + 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) + // + + setUpdate(authenticationOption.id) - 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"; - } + } - 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; - }} - /> -
- ); - })} - - - - - -
- ); - }; - }; + if (authenticationOption.label === null || authenticationOption.label === undefined) { + authenticationOption.label = selectedApp.name+" authentication" + } - const actionView = ( -
-
- {actionAmount > 0 && actionAmount < filteredActions.length ? ( - + + +
+ ) + } + } + + 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 (img) - 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 - ); + if (file !== "") { + const img = document.getElementById('logo') + var canvas = document.createElement('canvas') + canvas.width = 174 + canvas.height = 174 + var ctx = canvas.getContext('2d') - try { - const canvasUrl = canvas.toDataURL(); - if (canvasUrl !== fileBase64) { - //console.log("SET URL TO: ", canvasUrl) - setFileBase64(canvasUrl); - } - } catch (e) { - alert.error("Failed to parse canvasurl!"); - } - }; + 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 + ) - //console.log(img.width) - //console.log(img.width) - //canvas.width = img.width - //canvas.height = img.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!") + } + } - const [imageUploadError, setImageUploadError] = useState(""); - const [openImageModal, setOpenImageModal] = useState(""); - const [scale, setScale] = useState(1); - const [rotate, setRotatation] = useState(0); - const [disableImageUpload, setDisableImageUpload] = useState(true); + //console.log(img.width) + //console.log(img.width) + //canvas.width = img.width + //canvas.height = img.height + } - let imageData = fileBase64; - let croppedData = file.length > 0 ? file : fileBase64; + const [imageUploadError, setImageUploadError] = useState(""); + const [openImageModal, setOpenImageModal] = useState(""); + const [scale, setScale] = useState(1); + const [rotate, setRotatation] = useState(0); + const [disableImageUpload, setDisableImageUpload] = useState(true); - const imageInfo = ( - - ); + let imageData = fileBase64; + let croppedData = file.length > 0 ? file : fileBase64 - const alternateImg = ( - { - upload.click(); - }} - /> - ); + const imageInfo = - const zoomIn = () => { - console.log("ZOOOMING IN"); - setScale(scale + 0.1); - }; + const alternateImg = { + upload.click() + }}/> + + const zoomIn = () => { + console.log("ZOOOMING IN") + setScale(scale+0.1); + } - const zoomOut = () => { - setScale(scale - 0.1); - }; - const rotatation = () => { - setRotatation(rotate + 10); - }; + const zoomOut = () => { + setScale(scale-0.1); + } + const rotatation = () => { + setRotatation(rotate+10); + } - const onPositionChange = () => { - setDisableImageUpload(false); - }; + const onPositionChange = () => { + setDisableImageUpload(false); + } - const onCancelSaveAppIcon = () => { - setFile(""); - setOpenImageModal(false); - setImageUploadError(""); - }; + const onCancelSaveAppIcon = () => { + setFile(""); + setOpenImageModal(false) + setImageUploadError("") + } - let editor; - const setEditorRef = (imgEditor) => { - editor = imgEditor; - }; + let editor; + const setEditorRef = (imgEditor) => { editor = imgEditor; } - 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."); - } - } - }; + 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.") + } + } + } - const errorText = - imageUploadError.length > 0 ? ( -
Error: {imageUploadError}
- ) : null; - const imageUploadModalView = openImageModal ? ( - - - -
Upload App Icon
-
- {errorText} - - setRotatation(0)} - /> - - - - - - - - - - - - - - - - - - - - {/**/} -
-
- ) : null; + 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 - -
- -
{ - /* + // 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 ? imageInfo : alternateImg} - (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)
@@ -4823,61 +3737,46 @@ 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} -
- ) : ( -
- ); - return
{loadedCheck}
; -}; + const loadedCheck = isLoaded && isAppLoaded && !firstrequest ? +
+
{landingpageDataBrowser}
+ {newActionModal} +
+ : +
+
+ + return( +
+ {loadedCheck} +
+ ) +} export default AppCreator; diff --git a/frontend/src/views/Apps.jsx b/frontend/src/views/Apps.jsx index f413da97..b69836cc 100644 --- a/frontend/src/views/Apps.jsx +++ b/frontend/src/views/Apps.jsx @@ -1,2185 +1,1651 @@ -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, - }); - } - } + 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 +} - 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", + 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("") }) - .then((response) => { - setIsLoading(false); - if (response.status !== 200) { - console.log("Status not 200 for apps :O!"); + .catch(error => { + alert.error(error.toString()) + setIsLoading(false) + }); + } - if (isCloud) { - window.location.pathname = "/search"; - } - } + const downloadApp = (inputdata) => { + const id = inputdata.id - 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))) + 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 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); - } - } + 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) - //console.log(privateapps) - //console.log(valid) - //console.log(invalid) - //console.log(privateapps) - //privateapps.reverse() - privateapps.push(...valid); - privateapps.push(...invalid); + const newpaths = {} + if (inputdata["paths"] !== undefined) { + Object.keys(inputdata["paths"]).forEach(function(key) { + newpaths[key.split("?")[0]] = inputdata.paths[key] + }) + } - setApps(privateapps); - setCursearch(""); + inputdata.paths = newpaths + console.log("INPUT: ", inputdata) + var name = inputdata.info.title + name = name.replace(/ /g, "_", -1) + name = name.toLowerCase() - //handleSearchChange(event.target.value) - //setCursearch(event.target.value) - setFilteredApps(privateapps); - if (privateapps.length > 0) { - if (selectedApp.id === undefined || selectedApp.id === null) { - setSelectedApp(privateapps[0]); - } + delete inputdata.id + delete inputdata.editing - if ( - privateapps[0].actions !== null && - privateapps[0].actions.length > 0 - ) { - setSelectedAction(privateapps[0].actions[0]); - } else { - setSelectedAction({}); - } - } + const data = YAML.stringify(inputdata) + var blob = new Blob( [ data ], { + type: 'application/octet-stream' + }) - //runAppSearch("") - }) - .catch((error) => { - alert.error(error.toString()); - setIsLoading(false); - }); - }; + 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()) + }); + } - const downloadApp = (inputdata) => { - const id = inputdata.id; + // dropdown with copy etc I guess + const appPaper = (data) => { + if (data.name === "" && data.id === "") { + return null + } - 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 boxWidth = "2px" + if (selectedApp.id === data.id) { + boxWidth = "4px" + } - 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 boxColor = "orange" + if (data.is_valid) { + boxColor = "green" + } - const newpaths = {}; - if (inputdata["paths"] !== undefined) { - Object.keys(inputdata["paths"]).forEach(function (key) { - newpaths[key.split("?")[0]] = inputdata.paths[key]; - }); - } + if (!data.activated && data.generated) { + boxColor = "orange" + } - inputdata.paths = newpaths; - console.log("INPUT: ", inputdata); - var name = inputdata.info.title; - name = name.replace(/ /g, "_", -1); - name = name.toLowerCase(); + if (data.invalid) { + boxColor = "red" + } - delete inputdata.id; - delete inputdata.editing; + //
+ //
+ var imageline = data.large_image === undefined || data.large_image.length === 0 ? + {data.title} + : + {data.title} { + //console.log("IMG LOADED!: ", event.target) + }} /> - const data = YAML.stringify(inputdata); - var blob = new Blob([data], { - type: "application/octet-stream", - }); + var newAppname = data.name + if (newAppname === undefined) { + newAppname = "Undefined" + } else { + newAppname = newAppname.charAt(0).toUpperCase()+newAppname.substring(1) + newAppname = newAppname.replaceAll("_", " ") + } - 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 sharing = "public" + if (!data.sharing) { + sharing = "private" + } - // dropdown with copy etc I guess - const appPaper = (data) => { - if (data.name === "" && data.id === "") { - return null; - } + var valid = "true" + if (!data.valid) { + valid = "false" + } - var boxWidth = "2px"; - if (selectedApp.id === data.id) { - boxWidth = "4px"; - } + if (data.actions === null || data.actions.length === 0) { + valid = "false" + } - var boxColor = "orange"; - if (data.is_valid) { - boxColor = "green"; - } + var description = data.description + const maxDescLen = 60 + if (description.length > maxDescLen) { + description = data.description.slice(0, maxDescLen)+"..." + } - if (!data.activated && data.generated) { - boxColor = "orange"; - } + const version = data.app_version + return ( + { + if (selectedApp.id !== data.id) { + data.name = newAppname + setSelectedApp(data) - if (data.invalid) { - boxColor = "red"; - } + console.log(data) + if (data.actions !== undefined && data.actions !== null && data.actions.length > 0) { + setSelectedAction(data.actions[0]) + } else { + setSelectedAction({}) + } - //
- //
- var imageline = - data.large_image === undefined || data.large_image.length === 0 ? ( - {data.title} - ) : ( - {data.title} { - //console.log("IMG LOADED!: ", event.target) - }} - /> - ); + 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 newAppname = data.name; - if (newAppname === undefined) { - newAppname = "Undefined"; - } else { - newAppname = newAppname.charAt(0).toUpperCase() + newAppname.substring(1); - newAppname = newAppname.replaceAll("_", " "); - } + 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 sharing = "public"; - if (!data.sharing) { - sharing = "private"; - } + {data.activated && data.private_id !== undefined && data.private_id.length > 0 && data.generated ? + {downloadApp(data)}}> + + + + + : null} + + ) + } - var valid = "true"; - if (!data.valid) { - valid = "false"; - } + const dividerColor = "rgb(225, 228, 232)" + const uploadViewPaperStyle = { + minWidth: viewWidth, + maxWidth: viewWidth, + color: "white", + borderRadius: 5, + backgroundColor: surfaceColor, + //display: "flex", + marginBottom: 10, + overflow: "hidden", + } - if (data.actions === null || data.actions.length === 0) { - 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 = "" + } - var description = data.description; - const maxDescLen = 60; - if (description.length > maxDescLen) { - description = data.description.slice(0, maxDescLen) + "..."; - } + var description = selectedApp.description - const version = data.app_version; - return ( - { - if (selectedApp.id !== data.id) { - data.name = newAppname; - setSelectedApp(data); + const editUrl = "/apps/edit/"+selectedApp.id + const activateUrl = "/apps/new?id="+selectedApp.id - console.log(data); - if ( - data.actions !== undefined && - data.actions !== null && - data.actions.length > 0 - ) { - setSelectedAction(data.actions[0]); - } else { - setSelectedAction({}); - } + var downloadButton = 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; - } + var editButton = selectedApp.activated && selectedApp.private_id !== undefined && selectedApp.private_id.length > 0 && selectedApp.generated ? + + + + + : 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 activateButton = selectedApp.generated && !selectedApp.activated ? +
+ + + + + + +
+ : null - {data.activated && - data.private_id !== undefined && - data.private_id.length > 0 && - data.generated ? ( - { - downloadApp(data); - }} - > - - - - - ) : null} - - ); - }; + 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 dividerColor = "rgb(225, 228, 232)"; - const uploadViewPaperStyle = { - minWidth: viewWidth, - maxWidth: viewWidth, - color: "white", - borderRadius: 5, - backgroundColor: surfaceColor, - //display: "flex", - marginBottom: 10, - overflow: "hidden", - }; + var imageline = selectedApp.large_image === undefined || selectedApp.large_image.length === 0 ? + {selectedApp.title} + : + {selectedApp.title} - 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 = ""; - } + const GetAppExample = () => { + if (selectedAction.returns === undefined) { + return null + } - var description = selectedApp.description; + var showResult = selectedAction.returns.example + if (showResult === undefined || showResult === null || showResult.length === 0) { + return null + } - const editUrl = "/apps/edit/" + selectedApp.id; - const activateUrl = "/apps/new?id=" + selectedApp.id; + var jsonvalid = true + try { + const tmp = String(JSON.parse(showResult)) + if (!tmp.includes("{") && !tmp.includes("[")) { + jsonvalid = false + } + } catch (e) { + jsonvalid = false + } - var downloadButton = - selectedApp.activated && - selectedApp.private_id !== undefined && - selectedApp.private_id.length > 0 && - selectedApp.generated ? ( - - - - ) : null; - var editButton = - 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) - const activateButton = - selectedApp.generated && !selectedApp.activated ? ( -
- - - - - - -
- ) : null; + return ( +
+ {paths.map((data, index) => { + const circleSize = 10 + return ( + console.log(data.autocomplete)}> + {data.name} + + ) + })} +
+ ) + } - const deleteButton = - ((selectedApp.private_id !== undefined && - selectedApp.private_id.length > 0 && - selectedApp.generated) || - (selectedApp.downloaded !== undefined && - selectedApp.downloaded == true) || - !selectedApp.generated) && - activateButton === null ? ( - - - - ) : null; + return ( +
+ Example return
+ {selectedAction.returns.example} +
+ ) + } - var imageline = - selectedApp.large_image === undefined || - selectedApp.large_image.length === 0 ? ( - {selectedApp.title} - ) : ( - {selectedApp.title} - ); + const userRoles = [ + "you", + isCloud ? "public" : "everyone", + ] - const GetAppExample = () => { - if (selectedAction.returns === undefined) { - return null; - } + //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} - // FIXME: In here -> parse the values into a list or something - if (jsonvalid) { - const paths = GetParsedPaths(JSON.parse(showResult), ""); - console.log("PATHS: ", paths); + {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 + } - return ( -
- {paths.map((data, index) => { - const circleSize = 10; - return ( - console.log(data.autocomplete)} - > - {data.name} - - ); - })} -
- ); - } + return ( + + ) + })} +
+ : null} + {props.userdata !== undefined && props.userdata.id === selectedApp.owner ? +
+ {/*

ID: {selectedApp.id}

*/} + Sharing + { - 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); + + ) + })} + +
+ : 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 ? + - ) : null} - {isCloud ? ( - - - - - - ) : null} + + ) + })} + + : +
+ There are no actions defined for this app. +
+ } +
- {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; - } + {selectedAction.parameters !== undefined && selectedAction.parameters !== null ? +
+ Parameters + {selectedAction.parameters.map(data => { + var itemColor = "#f85a3e" + if (!data.required) { + itemColor = "#ffeb3b" + } - 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 handleSearchChange = (search) => { + if (apps === undefined || apps === null || apps.length === 0) { + return + } - {selectedAction.parameters !== undefined && - selectedAction.parameters !== null ? ( -
- Parameters - {selectedAction.parameters.map((data) => { - var itemColor = "#f85a3e"; - if (!data.required) { - itemColor = "#ffeb3b"; - } + 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) - 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; + //console.log(newapps) + setFilteredApps(newapps) + //if ((newapps.length === 0 || searchBackend) && !appSearchLoading) { - 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; + // //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 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", + 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) { + } }) - .then((response) => { - if (response.status === 200) { - alert.success("Loaded existing apps!"); - } + .catch(error => { + alert.error(error.toString()) + }); + } - //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()); + // 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); - }); - }; + var cors = "cors" + if (openApi.includes("= localhost")) { + cors = "no-cors" + } - // 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", + 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") + } }) - .then((response) => { - setIsLoading(false); - if (response.status === 200) { - //alert.success("Hotloaded apps!") - getApps(); - } + .catch(error => { + alert.error(error.toString()) + }); + } - 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()); - }); - }; + const runAppSearch = (searchterm) => { + const data = {"search": searchterm} - // 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", + 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) + } + } }) - .then((response) => { - response.text().then(function (text) { - validateOpenApi(text); - }); - }) - .catch((error) => { - alert.error(error.toString()); - }); - }; + .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", + 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) }) - .then((response) => { - if (response.status === 200) { - //alert.success("Successfully GOT app "+appId) - } else { - alert.error("Failed getting app"); - } + .catch(error => { + alert.error(error.toString()) + setOpenApiError(error.toString()) + }); + } - return response.json(); - }) - .then((responseJson) => { - console.log(responseJson); + const escapeApiData = (apidata) => { + //console.log(apidata) + try { + return JSON.stringify(JSON.parse(apidata)) + } catch(error) { + console.log("JSON DECODE ERROR - TRY YAML") + } - 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; - } + 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()) + } - //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()); - }); - }; + 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()); - }); - }; + // 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 updateAppField = (app_id, fieldname, fieldvalue) => { - const data = {}; - data[fieldname] = fieldvalue; + //console.log(newApidata) - console.log("DATA: ", data); + 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()) + }); + } - 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 redirectOpenApi = () => { + window.location.href = "/apps/new?id="+appValidation + } - const runAppSearch = (searchterm) => { - const data = { search: searchterm }; + const handleGithubValidation = (forceUpdate) => { + getSpecificApps(openApi, forceUpdate) + setLoadAppsModalOpen(false) + } - 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 deleteModal = deleteModalOpen ? + { + setDeleteModalOpen(false) + }} + PaperProps={{ + style: { + backgroundColor: surfaceColor, + color: "white", + minWidth: 500, + }, + }} + > + +
+ Are you sure?
Some workflows may stop working. +
+ + + + + + +
+ : null - const validateRemote = () => { - setValidation(true); + 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 + /> +
- 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(); - } + 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 - 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 - /> - {/* + 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 54932023..5a34382c 100644 --- a/frontend/src/views/Contact.jsx +++ b/frontend/src/views/Contact.jsx @@ -1,365 +1,345 @@ -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 { globalUrl, isLoaded } = props; + + const theme = useTheme(); - const theme = useTheme(); + 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 boxStyle = { - flex: "1", - marginLeft: "10px", - marginRight: "10px", - paddingLeft: "30px", - paddingRight: "30px", - paddingBottom: "30px", - paddingTop: "30px", - backgroundColor: theme.palette.surfaceColor, - display: "flex", - flexDirection: "column", - }; + const bodyTextStyle = { + color: "#ffffff", + } - const bodyTextStyle = { - color: "#ffffff", - }; + 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 [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 [formMessage, setFormMessage] = useState(""); - const [formMessage, setFormMessage] = useState(""); + const submitContact = () => { + const data = { + "firstname": firstname, + "lastname": lastname, + "title": title, + "companyname": companyname, + "email": email, + "phone": phone, + "message": message, + } + console.log(data) - const submitContact = () => { - const data = { - firstname: firstname, - lastname: lastname, - title: title, - companyname: companyname, - email: email, - phone: phone, - message: message, - }; - console.log(data); + 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) + }); + } - 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); - }); - }; + // 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}

+
+
+
- // 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 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 d0dc7754..3ffb560e 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,433 +34,430 @@ 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); + 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] - 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", + 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 + 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 :) - // Every time there's an update :) + // 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() - // 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() + // Index = what day are we on - // Index = what day are we on + // 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) - // 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 startdate = new Date() + startdate.setDate(-i) + startdate.setHours(0,0,0,0) - var startdate = new Date(); - startdate.setDate(-i); - startdate.setHours(0, 0, 0, 0); + var endtime = enddate.getTime()/1000 + var starttime = startdate.getTime()/1000 - var endtime = enddate.getTime() / 1000; - var starttime = startdate.getTime() / 1000; + 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( - "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; - } + //break + } + } + + newDayGraphLabels.push(i) + } - //break - } - } + console.log(newDayGraphLabels) + console.log(newDayGraphData) + } + } - newDayGraphLabels.push(i); - } + const newdata = Object.getOwnPropertyNames(stats).length > 0 ? +
+ Autoupdate every {autoUpdate/1000} seconds + {variables.map(data => { + if (stats[data] === undefined || stats[data] === null) { + return null + } - console.log(newDayGraphLabels); - console.log(newDayGraphData); - } - } + if (stats[data].total === undefined) { + return 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; - } + return ( +
+ {data}: {stats[data].total} +
+ ) + })} +
+ : null - if (stats[data].total === undefined) { - return null; - } + const data = +
+ {newdata} + + +
+ +
+ + + + + + +
Total Shipments
+ Workflows + + + + + + + + +
+
+ +
+ +
+
+
+ +
+ + + + +
Total Shipments
+ + {" "} + 763,215 + +
+ +
+ +
+
+
+ + + + +
Daily Sales
+ + {" "} + 3,500€ + +
+ +
+ +
+
+
+ + + + +
Completed Tasks
+ + 12,100K + +
+ +
+ +
+
+
+ +
+
- return ( -
- {data}: {stats[data].total} -
- ); - })} -
- ) : null; + const dataWrapper = +
+ {data} +
- const data = ( -
- {newdata} - - -
- -
- - - - - - -
Total Shipments
- Workflows - - - - - - - - -
-
- -
- -
-
-
- -
- - - - -
Total Shipments
- - 763,215 - -
- -
- -
-
-
- - - - -
Daily Sales
- - {" "} - 3,500€ - -
- -
- -
-
-
- - - - -
Completed Tasks
- - 12,100K - -
- -
- -
-
-
- -
-
- ); + return dataWrapper +} - const dataWrapper = ( -
{data}
- ); - - return dataWrapper; -}; - -export default Dashboard; +export default Dashboard diff --git a/frontend/src/views/Docs.jsx b/frontend/src/views/Docs.jsx index 6c3df78d..58ee19a3 100644 --- a/frontend/src/views/Docs.jsx +++ b/frontend/src/views/Docs.jsx @@ -1,453 +1,349 @@ -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); - } - } - } + 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 => {}); + } - setTocLines(innerTocLines); - } - } else { - setData("# Error\nThis page doesn't exist."); - } - }) - .catch((error) => {}); - }; + if (firstrequest) { + setFirstrequest(false) + if (!serverside) { + if (window.innerWidth < 768) { + setMobile(true) + } + } - if (firstrequest) { - setFirstrequest(false); - if (!serverside) { - if (window.innerWidth < 768) { - setMobile(true); - } - } + if (selectedDoc !== undefined) { + setData(selectedDoc.reason) + setList(selectedDoc.list) + setListLoaded(true) + } else { + if (!serverside) { + fetchDocList() + 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); - } - } - } + // 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) + } - // 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 parseElementScroll = () => { + const offset = 45 + var parent = document.getElementById("markdown_wrapper_outer") + if (parent !== null) { + //console.log("IN PARENT") + var elements = parent.getElementsByTagName('h2') - const parseElementScroll = () => { - const offset = 45; - var parent = document.getElementById("markdown_wrapper_outer"); - if (parent !== null) { - //console.log("IN PARENT") - var elements = parent.getElementsByTagName("h2"); + const name = window.location.hash.slice(1, window.location.hash.lenth).toLowerCase().split("%20").join(" ").split("_").join(" ").split("-").join(" ") - const name = window.location.hash - .slice(1, window.location.hash.lenth) - .toLowerCase() - .split("%20") - .join(" ") - .split("_") - .join(" ") - .split("-") - .join(" "); + //console.log(name) + var found = false + for (var key in elements) { + const element = elements[key] + if (element.innerHTML === undefined) { + continue + } - //console.log(name) - var found = false; - for (var 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) { - //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" - //}) - } - } + // 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 + } - // 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) { + 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) - // 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) + //console.log("NAME: ", name) + //console.log(document.body.innerHTML) + // parent = document.getElementById(parent); - //console.log("NAME: ", name) - //console.log(document.body.innerHTML) - // parent = document.getElementById(parent); + //var descendants = parent.getElementsByTagName(tagname); - //var descendants = parent.getElementsByTagName(tagname); + // this.scrollDiv.current.scrollIntoView({ behavior: 'smooth' }); - // this.scrollDiv.current.scrollIntoView({ behavior: 'smooth' }); + //$(".parent").find("h2:contains('Statistics')").parent(); + } - //$(".parent").find("h2:contains('Statistics')").parent(); - }; + if (serverside !== true && window.location.hash.length > 0) { + parseElementScroll() + } - if (serverside !== true && window.location.hash.length > 0) { - parseElementScroll(); - } + const markdownStyle = { + color: "rgba(255, 255, 255, 0.65)", + flex: "1", + maxWidth: mobile ? "100%" : 750, + overflow: "hidden", + paddingBottom: 200, + marginLeft: mobile ? 0 : 275, + } - const markdownStyle = { - color: "rgba(255, 255, 255, 0.65)", - flex: "1", - maxWidth: mobile ? "100%" : 750, - overflow: "hidden", - paddingBottom: 200, - marginLeft: mobile ? 0 : 275, - }; + function OuterLink(props) { + if (props.href.includes("http") || props.href.includes("mailto")) { + return {props.children} + } + return {props.children} + } - 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 Img(props) { - return {props.alt}; - } + function CodeHandler(props) { + return ( +
+				
+					{props.value}
+				
+			
+ ) + } - function CodeHandler(props) { - return ( -
-        {props.value}
-      
- ); - } + const Heading = (props) => { + const element = React.createElement(`h${props.level}`, {style: {marginTop: props.level === 1 ? 20 : 50}}, props.children) + const [hover, setHover] = useState(false) - const Heading = (props) => { - const element = React.createElement( - `h${props.level}`, - { style: { marginTop: props.level === 1 ? 20 : 50 } }, - props.children - ); - const [hover, setHover] = useState(false); + 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} + + + ) + })} +
+ } +
+
+ } - 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={() => { + 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'); @@ -455,208 +351,166 @@ 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)); - // } - // ); - //} - const postDataBrowser = - list === undefined || list === null ? null : ( -
-
- - - {list.map((data, index) => { - const item = data.name; - if (item === undefined) { - return null; - } + //function unicodeToChar(text) { + // return text.replace(/\\u[\dA-F]{4}/gi, + // function (match) { + // return String.fromCharCode(parseInt(match.replace(/\\u/g, ''), 16)); + // } + // ); + //} - 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 postDataBrowser = list === undefined || list === null ? null : +
    +
    + + + {list.map((data, index) => { + const item = data.name + if (item === undefined) { + return null + } - return ( - {}} - > - - - {data.text} - - - ); - })} -
    - ) : 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 mobileStyle = { - color: "white", - marginLeft: 25, - marginRight: 25, - paddingBottom: 50, - backgroundColor: "inherit", - display: "flex", - flexDirection: "column", - }; + return ( + {}}> + + - {data.text} + + + ) + })} +
    + : null} +
  • + ) + })} + + +
    +
    + +
    +
    - const postDataMobile = - list === undefined || list === null ? null : ( -
    -
    - - - {list.map((data, index) => { - const item = data.name; - if (item === undefined) { - return null; - } + const mobileStyle = { + color: "white", + marginLeft: 25, + marginRight: 25, + paddingBottom: 50, + backgroundColor: "inherit", + display: "flex", + flexDirection: "column", + } - const path = "/docs/" + item; - const newname = - item.charAt(0).toUpperCase() + - item.substring(1).split("_").join(" ").split("-").join(" "); - return ( - { - window.location.pathname = path; - }} - > - {newname} - - ); - })} - -
    -
    - -
    - - -
    - ); + const postDataMobile = list === undefined || list === null ? null : +
    +
    + + + {list.map((data, index) => { + const item = data.name + if (item === undefined) { + return null + } - //const imageModal = - // - // {imageModal} + const path = "/docs/"+item + const newname = item.charAt(0).toUpperCase()+item.substring(1).split("_").join(" ").split("-").join(" ") + return ( + {window.location.pathname = path}}>{newname} + ) + })} + +
    +
    + +
    + + - const loadedCheck = ( -
    - {postDataBrowser} - {postDataMobile} -
    - ); +
    + + + //const imageModal = + // + // {imageModal} + + + const loadedCheck = +
    + + {postDataBrowser} + + + {postDataMobile} + +
    + + return ( +
    + {loadedCheck} +
    + ) +} - return
    {loadedCheck}
    ; -}; export default Docs; diff --git a/frontend/src/views/EditSchedule.jsx b/frontend/src/views/EditSchedule.jsx index ffc3eb1c..c47453e8 100644 --- a/frontend/src/views/EditSchedule.jsx +++ b/frontend/src/views/EditSchedule.jsx @@ -1,1574 +1,1388 @@ -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 e1a5d516..9ec5879c 100644 --- a/frontend/src/views/EditWebhook.jsx +++ b/frontend/src/views/EditWebhook.jsx @@ -1,393 +1,366 @@ -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); - }) - .catch((error) => { - console.log(error); - }); - }; + 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 setWebhook = (inputdata) => { - console.log(inputdata); + }) + .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 setWebhook = (inputdata) => { + console.log(inputdata) - 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 = []; - } + 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) + }); + } - if (responseJson.transforms === null) { - responseJson.transforms = []; - } + 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 = [] + } - setWebhookData(responseJson); - }) - .catch((error) => { - console.log(error); - //window.location.pathname = "webhooks" - }); - }; + if (responseJson.transforms === null) { + responseJson.transforms = [] + } - useEffect(() => { - if (firstrequest) { - setFirstrequest(false); - getCurrentWebhook(); - if (workflows.length <= 0) { - getWorkflows(); - } - } + setWebhookData(responseJson) + }) + .catch(error => { + console.log(error) + //window.location.pathname = "webhooks" + }); + } - // 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]); - } - } + useEffect(() => { + if (firstrequest) { + setFirstrequest(false) + getCurrentWebhook() + if (workflows.length <= 0) { + getWorkflows() + } + } - // Fix duplicates... Meh - var foundWorkflowIds = []; - var tmpWorkflows = []; - for (key in tmpActionWorkflows) { - if (foundWorkflowIds.includes(tmpActionWorkflows[key].id)) { - continue; - } + // 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]) + } + } - 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; - } - } - } + // Fix duplicates... Meh + var foundWorkflowIds = [] + var tmpWorkflows = [] + for (key in tmpActionWorkflows) { + if (foundWorkflowIds.includes(tmpActionWorkflows[key].id)) { + continue + } - if (tmpWorkflows.length > 0) { - setSelectedWorkflows(tmpWorkflows); - } - } - }); + 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 + } + } + } - const hookPicture = - Object.getOwnPropertyNames(webhookData).length > 0 && - webhookData.type === "webhook" ? ( - webhook - ) : ( - MQ - ); + if (tmpWorkflows.length > 0) { + setSelectedWorkflows(tmpWorkflows) + } + } + }) - 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 headerPaperStyle = { - display: "flex", - maxHeight: "800px", - minHeight: "800px", - margin: "10px 30px 10px 10px", - padding: "10px 5px 5px 5px", - flexDirection: "column", - }; + const hookPicture = Object.getOwnPropertyNames(webhookData).length > 0 && webhookData.type === "webhook" ? + webhook + : + MQ - // 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 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 - make this possible for all accounts - if (selectedWorkflows.length === 0) { - console.log("ADD FIRST ITEM FOR SELECTEDWORKFLOWS"); - console.log(event.target.value); + const headerPaperStyle = { + display: "flex", + maxHeight: "800px", + minHeight: "800px", + margin: "10px 30px 10px 10px", + padding: "10px 5px 5px 5px", + flexDirection: "column", + } - // 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 - 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 + } + } - newActions.push(webhookData.actions[key]); - } - } + // FIXME - make this possible for all accounts + if (selectedWorkflows.length === 0) { + console.log("ADD FIRST ITEM FOR SELECTEDWORKFLOWS") + console.log(event.target.value) - // FIXME - how to stringify this better hurr - var formattedWorkflow = { - type: "workflow", - name: event.target.value.name, - id: event.target.value.id_, - field: "", - }; + // 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: patch this n - newActions.push(formattedWorkflow); - console.log(newActions); + newActions.push(webhookData.actions[key]) + } + } - webhookData.actions = newActions; - setWebhook(webhookData); - } + // FIXME - how to stringify this better hurr + var formattedWorkflow = { + "type": "workflow", + "name": event.target.value.name, + "id": event.target.value.id_, + "field": "", + } - var tmpSelectedWorkflows = [].concat(selectedWorkflows, [ - event.target.value, - ]); - setSelectedWorkflows(tmpSelectedWorkflows); - }; + // FIXME: patch this n + newActions.push(formattedWorkflow) + console.log(newActions) - // 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_"]) - ); + webhookData.actions = newActions + setWebhook(webhookData) + } - const WorkflowSelect = (counter) => { - if (selectedWorkflows[counter.counter] === undefined) { - return null; - } + var tmpSelectedWorkflows = [].concat(selectedWorkflows, [event.target.value]) + setSelectedWorkflows(tmpSelectedWorkflows) + } - 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: - -
    - ); - }; + // 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_"])) - const extraWorkflow = - workflows.length > 0 && availableWorkflows.length > 0 ? ( - - ) : null; + const WorkflowSelect = (counter) => { + if (selectedWorkflows[counter.counter] === undefined) { + return null + } - const multiWorkflowSelect = - workflows.length > 0 && selectedWorkflows.length > 0 ? ( -
    - {selectedWorkflows.map((data, count) => ( - - ))} - {extraWorkflow} -
    - ) : ( - - ); + 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 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 extraWorkflow = workflows.length > 0 && availableWorkflows.length > 0 ? + : null - // FIXME - needs refresh every time you add a new workflow - const workflowdata = - Object.getOwnPropertyNames(webhookData).length > 0 && - selectedWorkflows.length > 0 ? ( - - ) : null; + const multiWorkflowSelect = workflows.length > 0 && selectedWorkflows.length > 0 ? +
    + {selectedWorkflows.map((data, count) => ( + + ))} + {extraWorkflow} +
    + : - const loadedCheck = isLoaded ? ( -
    -
    {workflowdata}
    -
    {headerInfo}
    -
    - ) : ( -
    - ); + 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 - // FIXME: Use this for testing - // : null - return
    {loadedCheck}
    ; -}; + const loadedCheck = isLoaded ? +
    +
    + {workflowdata} +
    +
    + {headerInfo} +
    +
    + : +
    +
    + + // 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 29066fef..19ef4bfc 100644 --- a/frontend/src/views/EditWorkflow.jsx +++ b/frontend/src/views/EditWorkflow.jsx @@ -1,377 +1,364 @@ -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); - // 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", - }, - }, - ]); + const [elements, setElements] = useState([]) + const [loadedWorkflows, ] = useState(inputworkflows) - useEffect(() => { - if (elements.length === 0) { - setupGraph(); - } + // 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' + } + } +] +) - const cyDummy = cytoscape(); - if (!cyDummy.edgehandles) { - cytoscape.use(edgehandles); - } - }); - const setupGraph = () => { - // Convert our selection arrays to a string - //if (!this.loadedWorkflow.actions) { this.loadedWorkflow.actions = []; } + useEffect(() => { + if (elements.length === 0) { + setupGraph() + } - //setTimeout(() => { - // if (this.consoleArea && this.consoleArea.codeMirror) this.consoleArea.codeMirror.refresh(); - //}); + const cyDummy = cytoscape(); + if (!cyDummy.edgehandles) { cytoscape.use(edgehandles); } + }) - // Create the Cytoscape graph - // http://js.cytoscape.org/#style/labels + const setupGraph = () => { + // Convert our selection arrays to a string + //if (!this.loadedWorkflow.actions) { this.loadedWorkflow.actions = []; } - // Breaks stuff - //container: document.getElementById('cy'), + //setTimeout(() => { + // if (this.consoleArea && this.consoleArea.codeMirror) this.consoleArea.codeMirror.refresh(); + //}); - // 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; - }); - }); + // 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; + }); + }) - // 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 (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 (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; - } - } + setElements(tmpelements) + } - 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); + // Set some extra stuff? + var cy; + const cytmp = cytoscape() + cytmp.fit(null, 50); - if (shiftlength !== 0) { - const newelements = []; - for (key in tmpelements) { - // isNaN? - if ( - tmpelements[key].position === undefined || - tmpelements[key].position.isNaN - ) { - newelements.push(tmpelements); - continue; - } + return ( +
    + cy = cytmp} + elements={elements} + style={{width: '1000px', height: '1000px'}} + stylesheet={cystyle} + boxSelectionEnabled={false} + autounselectify={false} + wheelSensitivity={0.1} + />; +
    + ) +} - 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; +export default EditWorkflow; diff --git a/frontend/src/views/ForgotPassword.jsx b/frontend/src/views/ForgotPassword.jsx index e4c95013..0385579a 100644 --- a/frontend/src/views/ForgotPassword.jsx +++ b/frontend/src/views/ForgotPassword.jsx @@ -1,118 +1,122 @@ /* 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 boxStyle = { - paddingLeft: "30px", - paddingRight: "30px", - paddingBottom: "30px", - paddingTop: "30px", - backgroundColor: surfaceColor, - }; +const ForgotPassword = props => { + const { globalUrl, isLoaded, isLoggedIn, surfaceColor, inputColor } = props; - const [username, setUsername] = useState(""); - const [resetInfo, setResetInfo] = useState( - "You will receive an email with instructions shortly." - ); - const handleValidateForm = () => { - return username.length > 3; - }; + const boxStyle = { + paddingLeft: "30px", + paddingRight: "30px", + paddingBottom: "30px", + paddingTop: "30px", + backgroundColor: surfaceColor, + } - if (isLoggedIn === true) { - window.location.pathname = "/"; - } + const [username, setUsername] = useState("") + const [resetInfo, setResetInfo] = useState("You will receive an email with instructions shortly.") - const onSubmit = (e) => { - e.preventDefault(); - // FIXME - add some check here ROFL + const handleValidateForm = () => { + return username.length > 3 + } - // 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); - }); - }; + if (isLoggedIn === true) { + window.location.pathname = "/" + } - const onChangeUser = (e) => { - setUsername(e.target.value); - }; + const onSubmit = (e) => { + e.preventDefault() + // FIXME - add some check here ROFL - const data = ( -
    - -
    -

    Password reset

    -
    - -
    -
    - -
    -
    {resetInfo}
    -
    -
    -
    - ); + // 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 loadedCheck = isLoaded ?
    {data}
    :
    ; + const onChangeUser = (e) => { + setUsername(e.target.value) + } - return
    {loadedCheck}
    ; -}; + const data = +
    + +
    +

    Password reset

    +
    + +
    +
    + + +
    +
    + {resetInfo} +
    +
    +
    +
    + + const loadedCheck = isLoaded ? +
    + {data} +
    + : +
    +
    + + return ( +
    + {loadedCheck} +
    + ) +} export default ForgotPassword; diff --git a/frontend/src/views/ForgotPasswordLink.jsx b/frontend/src/views/ForgotPasswordLink.jsx index 8944deed..33a5b421 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,99 +38,97 @@ 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 c19459f6..9155753e 100644 --- a/frontend/src/views/HandlePayment.js +++ b/frontend/src/views/HandlePayment.js @@ -1,7 +1,9 @@ -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 f6c5a43d..5fb15287 100644 --- a/frontend/src/views/Introduction.jsx +++ b/frontend/src/views/Introduction.jsx @@ -1,182 +1,181 @@ -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 outerGridView = { - width: "100%", - marginTop: 15, - }; + const cardContentStyle = { + height: "100%", + width: "100%", + padding: 40, + } - const paperStyle = { - height: 300, - color: "white", - backgroundColor: theme.palette.surfaceColor, - color: "white", - cursor: "pointer", - display: "flex", - textAlign: "center", - }; + const outerGridView = { + width: "100%", + marginTop: 15, + } - const HandleSelection = (data) => { - const [selected, setSelected] = useState(false); + const paperStyle = { + height: 300, + color: "white", + backgroundColor: theme.palette.surfaceColor, + color: "white", + cursor: "pointer", + display: "flex", + textAlign: "center", + } - var baseStyle = JSON.parse(JSON.stringify(paperStyle)); - if (selected) { - baseStyle.backgroundColor = "white"; - baseStyle.color = "black"; - } + const HandleSelection = (data) => { + const [selected, setSelected] = useState(false); - 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); - } + var baseStyle = JSON.parse(JSON.stringify(paperStyle)) + if (selected) { + baseStyle.backgroundColor = "white" + baseStyle.color = "black" + } - setSelected(!selected); + 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) + } - //setCurView(1) - //setSelectedItem(data) - //window.location.pathname += "/"+data.title.toLowerCase() - }} - > - - - - {data.title} - - - - - ); - }; + setSelected(!selected) + + //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 => { @@ -199,17 +198,20 @@ 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 ed02060f..1dd2f912 100644 --- a/frontend/src/views/Landingpage.jsx +++ b/frontend/src/views/Landingpage.jsx @@ -1,205 +1,179 @@ -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}
    ; - const loadedCheck = isLoaded ? ( -
    - {landingSite} - {landingpageDataMobile} -
    - ) : ( -
    - ); + // Reroute if the user is logged in + // const landingSite = isLoggedIn ? :
    {landingpageData}
    + const landingSite =
    {landingpageDataBrowser}
    - return
    {loadedCheck}
    ; -}; + const loadedCheck = isLoaded ? +
    + + {landingSite} + + + {landingpageDataMobile} + +
    + : +
    +
    + + return( +
    + {loadedCheck} +
    + ) +} export default LandingPage; diff --git a/frontend/src/views/LandingpageLoggedin.jsx b/frontend/src/views/LandingpageLoggedin.jsx index c0abb59d..178c5930 100644 --- a/frontend/src/views/LandingpageLoggedin.jsx +++ b/frontend/src/views/LandingpageLoggedin.jsx @@ -1,16 +1,21 @@ -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 252d389c..4ff6f25e 100644 --- a/frontend/src/views/LandingpageNew.jsx +++ b/frontend/src/views/LandingpageNew.jsx @@ -1,529 +1,349 @@ -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 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 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 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 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]} +
    + +
    +
    - 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 2a51187e..6a6529e3 100644 --- a/frontend/src/views/LoginPage.jsx +++ b/frontend/src/views/LoginPage.jsx @@ -1,490 +1,391 @@ /* eslint-disable react/no-multi-comp */ -import React, { useState } from "react"; -import { makeStyles } from "@material-ui/styles"; -import { useInterval } from "react-powerhooks"; +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 { - 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 -
    - - 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} -
    - )} -
    -
    - ); + + + Are you sure Shuffle is installed correctly? + + + 1. Make sure shuffle-database folder has correct access:

    + sudo chown 1000:1000 -R shuffle-database +
    - const loadedCheck = isLoaded ?
    {basedata}
    :
    ; + + 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} +
    + } +
    +
    - return
    {loadedCheck}
    ; -}; + const loadedCheck = isLoaded ? +
    + {basedata} +
    + : +
    +
    + + return ( +
    + {loadedCheck} +
    + ) +} export default LoginDialog; diff --git a/frontend/src/views/MyView.jsx b/frontend/src/views/MyView.jsx index 3420a7d8..70927e69 100644 --- a/frontend/src/views/MyView.jsx +++ b/frontend/src/views/MyView.jsx @@ -1,735 +1,636 @@ -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 paperAppContainer = { - display: "flex", - flexWrap: "wrap", - alignContent: "space-between", - }; + const scrollStyle = { + marginTop: "10px", + overflow: "scroll", + height: "90%", + overflowX: "hidden", + overflowY: "auto", + } - 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 paperAppContainer = { + display: "flex", + flexWrap: 'wrap', + alignContent: "space-between", + } - const gridContainer = { - height: "auto", - color: "white", - margin: "10px", - backgroundColor: surfaceColor, - }; + 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 workflowActionStyle = { - flex: "1", - display: "flex", - width: 150, - height: 44, - justifyContent: "space-between", - overflow: "hidden", - }; + const gridContainer = { + height: "auto", + color: "white", + margin: "10px", + backgroundColor: surfaceColor, + } - const getWorkflowExecution = (id) => { - setExecutionLoading(true); - fetch(globalUrl + "/api/v1/workflows/" + id + "/executions", { - method: "GET", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", + 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() }) - .then((response) => { - setExecutionLoading(false); - if (response.status !== 200) { - console.log("Status not 200 for WORKFLOW EXECUTION :O!"); - } + .catch(error => { + alert.error(error.toString()) + }); + } - 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 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!") + } - 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() + }) + .then((responseJson) => { + getAvailableWorkflows() + }) + .catch(error => { + alert.error(error.toString()) + }); + } - return response.json(); - }) - .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) { - 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!"); - } + 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 response.json(); - }) - .then((responseJson) => { - if (!responseJson.success) { - alert.error(responseJson.reason); - } - }) - .catch((error) => { - alert.error(error.toString()); - }); + return [triggers, schedules, webhooks, subflows] + } - if (id === selectedWorkflow.id) { - sleep(2000).then(() => { - stop(); - start(); - }); - } - }; + // dropdown with copy etc I guess + const WorkflowPaper = (props) => { + const { data } = props; + const [open, setOpen] = React.useState(false); + const [anchorEl, setAnchorEl] = React.useState(null); - function sleep(time) { - return new Promise((resolve) => setTimeout(resolve, time)); - } + var boxWidth = "2px" + if (selectedWorkflow.id === data.id) { + boxWidth = "4px" + } - const exportAllWorkflows = () => { - for (var key in workflows) { - exportWorkflow(workflows[key]); - } - }; + var boxColor = "#FECC00" + if (data.is_valid) { + boxColor = "#86c142" + } - const exportWorkflow = (data) => { - console.log("export"); - let dataStr = JSON.stringify(data); + if (!data.previously_saved) { + boxColor = "#f85a3e" + } - let dataUri = - "data:application/json;charset=utf-8," + encodeURIComponent(dataStr); - let exportFileDefaultName = data.name + ".json"; + const menuClick = (event) => { + setOpen(!open) + setAnchorEl(event.currentTarget); + } - 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 = ""; - } - } + var parsedName = data.name + if (parsedName !== undefined && parsedName !== null && parsedName.length > 25) { + parsedName = parsedName.slice(0,25)+".." + } - if (trigger.status == "running") { - trigger.status = "stopped"; - } - } - for (var key in data.actions) { - data.actions[key].authentication_id = ""; - } + const actions = data.actions !== null ? data.actions.length : 0 + const [triggers, schedules, webhooks, subflows] = getWorkflowMeta(data) - //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} - - - - {/* + return ( + + +
    + + + + {parsedName} + + + + + + + + {actions} + + + + + + + + {triggers} + + + + + + + + + + {subflows} + + + + {/* @@ -743,290 +644,189 @@ 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"} - - - - - - - - - - - - ) : 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"} - const executionPaper = (data) => { - var boxWidth = "2px"; - if (selectedExecution.execution_id === data.execution_id) { - boxWidth = "4px"; - } + + + + + + + + + + + : null} + + + ) + } - var boxColor = "orange"; - if ( - data.status === "ABORTED" || - data.status === "UNFINISHED" || - data.status === "FAILURE" - ) { - boxColor = "red"; - } else if (data.status === "FINISHED") { - boxColor = "green"; - } + const executionPaper = (data) => { + var boxWidth = "2px" + if (selectedExecution.execution_id === data.execution_id) { + boxWidth = "4px" + } - var t = new Date(data.started_at * 1000); - if (data.workflow.actions === null || data.workflow.actions === undefined) { - return null; - } + var boxColor = "orange" + if (data.status === "ABORTED" || data.status === "UNFINISHED" || data.status === "FAILURE"){ + boxColor = "red" + } else if (data.status === "FINISHED") { + boxColor = "green" + } - 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 + } - var actions = data.workflow.actions.length; - if (data.results !== null) { - var results = data.results.length; - } + if (data.workflow.actions === null || data.workflow.actions === undefined) { + return null + } - return ( - { - setSelectedExecution(data); - }} - > -
    - - - -
    -

    - Status: {data.status} -

    - Actions: {results}/{actions} -
    -
    - -
    -
    -
    - - Started: {t.toISOString()} - -
    -
    -
    - - ); - }; + var actions = data.workflow.actions.length + if (data.results !== null) { + var results = data.results.length + } - const dividerColor = "rgb(225, 228, 232)"; + return ( + { + setSelectedExecution(data) + }}> +
    + + + +
    +

    Status: {data.status}

    + Actions: {results}/{actions} +
    +
    + +
    +
    +
    + + Started: {t.toISOString()} + +
    +
    +
    + + ) + } - const resultPaperAppStyle = { - minHeight: "100px", - minWidth: "100%", - overflow: "hidden", - maxWidth: "100%", - marginTop: "5px", - color: "white", - backgroundColor: surfaceColor, - display: "flex", - }; + const dividerColor = "rgb(225, 228, 232)" - function replaceAll(string, search, replace) { - return string.split(search).join(replace); - } + const resultPaperAppStyle = { + minHeight: "100px", + minWidth: "100%", + overflow: "hidden", + maxWidth: "100%", + marginTop: "5px", + color: "white", + backgroundColor: surfaceColor, + display: "flex", + } - 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"; - } + function replaceAll(string, search, replace) { + return string.split(search).join(replace); + } - var t = new Date(data.started_at * 1000); - var showResult = data.result.trim(); - const validate = validateJson(showResult); + 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" + } - if (validate.valid) { - showResult = ( - - ); - } else { - // FIXME - have everything parsed as json, either just for frontend - // or in the backend? - /* + 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? + /* 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}
    @@ -1177,970 +935,749 @@ 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 - ) => { - var method = "POST"; - var extraData = ""; - var workflowdata = {}; + // Can create and set workflows + const setNewWorkflow = (name, description, tags, editingWorkflow, redirect) => { - if (editingWorkflow.id !== undefined) { - console.log("Building original workflow"); - method = "PUT"; - extraData = "/" + editingWorkflow.id; - workflowdata = editingWorkflow; + var method = "POST" + var extraData = "" + var workflowdata = {} - console.log("REMOVING OWNER"); - workflowdata["owner"] = ""; - // FIXME: Loop triggers and turn them off? - } + if (editingWorkflow.id !== undefined) { + console.log("Building original workflow") + method = "PUT" + extraData = "/"+editingWorkflow.id + workflowdata = editingWorkflow - workflowdata["name"] = name; - workflowdata["description"] = description; - if (tags !== undefined) { - workflowdata["tags"] = tags; - } - //console.log(workflowdata) - //return + console.log("REMOVING OWNER") + workflowdata["owner"] = "" + // FIXME: Loop triggers and turn them off? + } - return fetch(globalUrl + "/api/v1/workflows" + extraData, { - method: method, - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(workflowdata), - credentials: "include", + 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 }) - .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"); - } + .catch(error => { + alert.error(error.toString()) + }); + } - 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 ; - })} - - )} +
    +
    + This is your workflow view. Learn more about Workflows +
    +
    + {workflowButtons} +
    +
    +
    + {view === "grid" && ( + + {workflows.map((data, index) => { + return ( + + ) + })} + + )} + + {view === "list" && ( + + )} - {view === "list" && } +
    +
    +
    + ) + } + + const importWorkflowsFromUrl = (url) => { + console.log("IMPORT WORKFLOWS FROM ", downloadUrl) -
    -
    -
    - ); - }; + const parsedData = { + "url": url, + "field_3": downloadBranch || 'master' + } - const importWorkflowsFromUrl = (url) => { - console.log("IMPORT WORKFLOWS FROM ", downloadUrl); + if (field1.length > 0) { + parsedData["field_1"] = field1 + } - const parsedData = { - url: url, - field_3: downloadBranch || "master", - }; + if (field2.length > 0) { + parsedData["field_2"] = field2 + } - if (field1.length > 0) { - parsedData["field_1"] = field1; - } + 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 (field2.length > 0) { - parsedData["field_2"] = field2; - } + 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()) + }) + } - 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 handleGithubValidation = () => { + importWorkflowsFromUrl(downloadUrl) + setLoadWorkflowsModalOpen(false) + } - 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()); - }); - }; + 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 + /> - const handleGithubValidation = () => { - importWorkflowsFromUrl(downloadUrl); - setLoadWorkflowsModalOpen(false); - }; + 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 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; + 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}
    ; -}; -export default MyView; + // Maybe use gridview or something, idk + return ( +
    + {loadedCheck} +
    + ) +} + +export default MyView diff --git a/frontend/src/views/Oauth2.jsx b/frontend/src/views/Oauth2.jsx index 0f0e4d76..3137d3d1 100644 --- a/frontend/src/views/Oauth2.jsx +++ b/frontend/src/views/Oauth2.jsx @@ -1,7 +1,11 @@ -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 a38f9d00..11d41646 100644 --- a/frontend/src/views/Post.jsx +++ b/frontend/src/views/Post.jsx @@ -1,61 +1,72 @@ -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 8aae1aa6..52041010 100644 --- a/frontend/src/views/PrivacyPolicy.jsx +++ b/frontend/src/views/PrivacyPolicy.jsx @@ -1,264 +1,122 @@ -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 7fde36e1..a4d8456e 100644 --- a/frontend/src/views/RegisterLink.jsx +++ b/frontend/src/views/RegisterLink.jsx @@ -1,12 +1,13 @@ -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", @@ -22,66 +23,71 @@ 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 34df7666..7fea6d0b 100644 --- a/frontend/src/views/RegisterPage.jsx +++ b/frontend/src/views/RegisterPage.jsx @@ -1,176 +1,139 @@ /* 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 88c92251..89db7527 100644 --- a/frontend/src/views/Schedules.jsx +++ b/frontend/src/views/Schedules.jsx @@ -1,232 +1,220 @@ -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 scheduleApp = (app) => { - console.log(app); - return ( - - - - - - - - - -
    -

    {app.name}

    -
    -
    {app.description}
    -
    - {app.action} -
    -
    -
    - ); - }; + const bodyDivStyle = { + marginLeft: "20px", + marginRight: "20px", + width: "1350px", + minWidth: "1350px", + maxWidth: "1350px", + } - const splitter = ( -
    - ); + const scheduleApp = (app) => { + console.log(app) + return( + + + + + + + + + +
    +

    {app.name}

    +
    +
    + {app.description} +
    +
    + + {app.action} + +
    +
    +
    + ) + } - const hrefStyle = { - color: "#385f71", - textDecoration: "none", - }; + const splitter =
    - // FIXME - add Schedule modal - const schedulePaper = (schedule) => { - return ( -
    - -
    - {scheduleApp(schedule.appinfo.sourceapp)} -
    -
    ARROW
    -
    - {scheduleApp(schedule.appinfo.destinationapp)} -
    - {splitter} -
    - - - - - - - - - - -
    -
    -
    - ); - }; + const hrefStyle = { + color: "#385f71", + textDecoration: "none" + } - 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))}
    - ) : ( -
    - -
    - ); + // FIXME - add Schedule modal + const schedulePaper = (schedule) => { + return( +
    + +
    + {scheduleApp(schedule.appinfo.sourceapp)} +
    +
    + ARROW +
    +
    + {scheduleApp(schedule.appinfo.destinationapp)} +
    + {splitter} +
    + + + + + + + + + - const scheduleView = - Object.getOwnPropertyNames(schedules).length > 0 ? ( -
    - - {schedulemap} -
    - ) : null; +
    +
    +
    +
    + ) + } - // Maybe use gridview or something, idk - return
    {scheduleView}
    ; -}; + 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) + ))} +
    + : +
    + +
    -export default Schedules; + const scheduleView = Object.getOwnPropertyNames(schedules).length > 0 ? +
    + + {schedulemap} +
    + : null + + // Maybe use gridview or something, idk + return ( +
    + {scheduleView} +
    + ) +} + +export default Schedules diff --git a/frontend/src/views/SetAuthentication.jsx b/frontend/src/views/SetAuthentication.jsx index 388f165e..5d1ca617 100644 --- a/frontend/src/views/SetAuthentication.jsx +++ b/frontend/src/views/SetAuthentication.jsx @@ -1,170 +1,155 @@ -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 92afe942..5fcd0f2b 100644 --- a/frontend/src/views/SetAuthenticationSSO.jsx +++ b/frontend/src/views/SetAuthenticationSSO.jsx @@ -1,158 +1,143 @@ -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 ae0fd9e7..c7a4fb54 100644 --- a/frontend/src/views/SettingsPage.jsx +++ b/frontend/src/views/SettingsPage.jsx @@ -1,62 +1,50 @@ -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) @@ -74,470 +62,410 @@ 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 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 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 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 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; - } - } - } - } + 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.") + }); + } - return currentOwner; - }; + 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!") + } - 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", - }, + 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) + + } }) - .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."); - }); - }; + .catch(error => { + console.log(error) + }) + } - 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!"); - } + 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!") + } - return response.json(); - }) - .then((responseJson) => { - //console.log("Values: ", responseJson) + return response.json() + }) + .then((responseJson) => { + setUserSettings(responseJson) + }) + .catch(error => { + console.log(error) + }); + } - // 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); - } + 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!") + } - var collections = []; - for (var key in responseJson) { - var collectionname = responseJson[key].collection.name; - if (!collections.includes(collectionname)) { - collections.push(collectionname); - } - } + return response.json() + }) + .then((responseJson) => { + setUserSettings(responseJson) + }) + .catch(error => { + console.log(error) + }); + } - console.log(collections); - setLoadedWorkflowCollections(collections); - setLoadedValidationWorkflows(responseJson); + // 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) + //} + } + } - setTimeout(() => { - window.scrollTo({ - top: document.body.scrollHeight, - left: 0, - behavior: "smooth", - }); - }, 250); - } - }) - .catch((error) => { - console.log(error); - }); - }; + const registerProviders = (userdata) => { + // Register hooks here + detectEthereumProvider() + .then((provider) => { + if (provider) { - 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!"); - } + if (!provider.isMetaMask) { + alert.error("Only MetaMask is supported as of now.") + return + } - return response.json(); - }) - .then((responseJson) => { - setUserSettings(responseJson); - }) - .catch((error) => { - console.log(error); - }); - }; + // Find the ethereum network + // Get the users' account(s) + //alert.info("Connecting to MetaMask") + //console.log("Connected: ", provider.isConnected()) - 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!"); - } + if (!provider.isConnected()) { + alert.error("Metamask is not connected.") + return + } - return response.json(); - }) - .then((responseJson) => { - setUserSettings(responseJson); - }) - .catch((error) => { - console.log(error); - }); - }; + provider.on('message', (event) => { + alert.info("Ethereum message: ", event) + }) - // 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) - //} - } - }; + provider.on('chainChanged', (chainId) => { + console.log("Changed chain to: ", chainId) - const registerProviders = (userdata) => { - // Register hooks here - detectEthereumProvider().then((provider) => { - if (provider) { - if (!provider.isMetaMask) { - alert.error("Only MetaMask is supported as of now."); - return; - } + 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) + }) + }) + } + }) + } - // Find the ethereum network - // Get the users' account(s) - //alert.info("Connecting to MetaMask") - //console.log("Connected: ", provider.isConnected()) + // This should "always" have data + useEffect(() => { + if (firstrequest) { + setFirstRequest(false) + getSettings() + //registerProviders(userdata) + } - if (!provider.isConnected()) { - alert.error("Metamask is not connected."); - return; - } + if (Object.getOwnPropertyNames(userdata).length > 0 && (username === "" && email === "")) { + setFields() + } + }) - provider.on("message", (event) => { - alert.info("Ethereum message: ", event); - }); + const ParsedWorkflowView = (props) => { + const { data } = props; - provider.on("chainChanged", (chainId) => { - console.log("Changed chain to: ", chainId); + var innerPaperStyle = { + backgroundColor: theme.palette.inputColor, + display: "flex", + flexDirection: "column", + padding: "0px 0px 12px 0px", + borderRadius: theme.palette.borderRadius, + } - 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 currentOwner = checkOwner(data, userdata) + if (currentOwner === true) { + innerPaperStyle.border = "3px solid #f86a3e" + } + + return ( + + + {data.name} + + {data.collection.name} + + + {data.name} + + + + ) + } - // 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? - - - - - {/* + // 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} - -
    - ) : ( - - )} -
    -
    + return ( + + {data}  + + ) + })} +
    + : null} +
    - ); + }} + > + Validate ownership + +
    + : + + } +
    +
    - /* + {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', @@ -955,78 +831,80 @@ 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 64a463b9..ac30a313 100644 --- a/frontend/src/views/Webhooks.jsx +++ b/frontend/src/views/Webhooks.jsx @@ -1,316 +1,295 @@ -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) => { - // Might be more options, but should be webhook or MQ - const appPicture = - app.type === "webhook" ? ( - webhook - ) : ( - MQ - ); + const hookApp = (app) => { - return ( - - - {appPicture} - - {splitter} - - - -
    -

    {app.info.name}

    -
    -
    Desc: {app.info.description}
    -
    Status: {app.status}
    -
    - {app.action} -
    -
    -
    - ); - }; + // Might be more options, but should be webhook or MQ + const appPicture = app.type === "webhook" ? + webhook + : + MQ - const splitter = ( -
    - ); + return( + + + + {appPicture} + + + {splitter} + + + +
    +

    {app.info.name}

    +
    +
    + Desc: {app.info.description} +
    +
    + Status: {app.status} +
    +
    + + {app.action} + +
    +
    +
    + ) + } - const hrefStyle = { - color: "#385f71", - textDecoration: "none", - }; + const splitter =
    - // FIXME - add Schedule modal - const hookPaper = (hook) => { - return ( -
    - -
    {hookApp(hook)}
    - {splitter} -
    - - - - - - - - - - -
    -
    -
    - ); - }; + const hrefStyle = { + color: "#385f71", + textDecoration: "none" + } - 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 - /> + // FIXME - add Schedule modal + const hookPaper = (hook) => { + return( +
    + +
    + {hookApp(hook)} +
    + {splitter} +
    + + + + + + + + + + +
    +
    +
    + ) + } - -
    - - - - -
    - ) : null; + 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 hookmap = - hooks.length > 0 ? ( -
    {hooks.map((data) => hookPaper(data))}
    - ) : ( -
    - -
    - ); + +
    + + + + +
    + : null - const hookView = ( -
    - - {hookmap} -
    - ); + const hookmap = hooks.length > 0 ? +
    + {hooks.map(data => ( + hookPaper(data) + ))} +
    + : +
    + +
    - const loadedCheck = isLoaded ? ( -
    - {modalView} - {hookView} -
    - ) : ( -
    - ); + const hookView = +
    + + {hookmap} +
    - // Maybe use gridview or something, idk - return
    {loadedCheck}
    ; -}; + const loadedCheck = isLoaded ? +
    + {modalView} + {hookView} +
    + : +
    +
    -export default Webhooks; + + // Maybe use gridview or something, idk + return ( +
    + {loadedCheck} +
    + ) +} + +export default Webhooks diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx index a349dce8..a2db3a0e 100644 --- a/frontend/src/views/Workflows.jsx +++ b/frontend/src/views/Workflows.jsx @@ -1,2471 +1,1884 @@ -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"; +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 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; - } - } - } + 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": , + } + } - // 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: "", - }; - } + 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.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.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 +} - 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", -}; + 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"); + //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('"'); + 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; - } - } + 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, - }; -}; + 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 { 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/"; + const referenceUrl = globalUrl+"/api/v1/hooks/" - var upload = ""; + 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 [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 [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 [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 [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 [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 isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" - const findWorkflow = (filters) => { - if (filters.length === 0) { - setFilteredWorkflows(workflows); - return; - } + const findWorkflow = (filters) => { + if (filters.length === 0) { + setFilteredWorkflows(workflows) + return + } - var newWorkflows = []; - for (var workflowKey in workflows) { - const curWorkflow = workflows[workflowKey]; + 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; - } + 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; - } - } - } + 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; - }); - } + return false + }) + } - if (found.every((v) => v === true)) { - newWorkflows.push(curWorkflow); - continue; - } - } + if (found.every(v => v === true)) { + newWorkflows.push(curWorkflow) + continue + } + } - if (newWorkflows.length !== workflows.length) { - setFilteredWorkflows(newWorkflows); - } - }; + if (newWorkflows.length !== workflows.length) { + setFilteredWorkflows(newWorkflows) + } + } - const addFilter = (data) => { - if (data === null || data === undefined) { - return; - } + const addFilter = (data) => { + if (data === null || data === undefined) { + return + } - if (data.includes("<") && data.includes(">")) { - return; - } + if (data.includes("<") && data.includes(">")) { + return + } - if (filters.includes(data) || filters.includes(data.toLowerCase())) { - return; - } + if (filters.includes(data) || filters.includes(data.toLowerCase())) { + return + } - filters.push(data.toLowerCase()); - setFilters(filters); + filters.push(data.toLowerCase()) + setFilters(filters) - findWorkflow(filters); - }; + findWorkflow(filters) + } - const removeFilter = (index) => { - var newfilters = filters; + const removeFilter = (index) => { + var newfilters = filters - if (index < 0) { - console.log("Can't handle index: ", index); - return; - } + 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); - } + newfilters.splice(index, 1) - findWorkflow(newfilters); - }; + if (newfilters.length === 0) { + newfilters = [] + setFilters(newfilters) + } else { + setFilters(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; + findWorkflow(newfilters) + } - 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 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 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 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 uploadFile = (e) => { - const isDropzone = - e.dataTransfer === undefined ? false : e.dataTransfer.files.length > 0; - const files = isDropzone ? e.dataTransfer.files : e.target.files; + 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"); + 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; - } + 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; + // 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); - } + // 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]); - }; + reader.readAsText(files[0]); + } - useEffect(() => { - if (isDropzone) { - setIsDropzone(false); - } + 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) => { - if (responseJson !== undefined) { - setWorkflows(responseJson); + return + } + return response.json() + }) + .then((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) { + setWorkflows(responseJson) - actionnamelist.push(action.app_name); - parsedactionlist.push(action); - } - } + 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 + } - //console.log(parsedactionlist) - setActionImageList(parsedactionlist); - } + actionnamelist.push(action.app_name) + parsedactionlist.push(action) + } + } - setFilteredWorkflows(responseJson); - setWorkflowDone(true); - } else { - if (isLoggedIn) { - alert.error("An error occurred while loading workflows"); - } + //console.log(parsedactionlist) + setActionImageList(parsedactionlist) + } - return; - } - }) - .catch((error) => { - alert.error(error.toString()); - }); - }; + setFilteredWorkflows(responseJson) + setWorkflowDone(true) + } else { + if (isLoggedIn) { + alert.error("An error occurred while loading workflows") + } - // 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); - } + return + } - setFirstrequest(false); - getAvailableWorkflows(); - } - }); - const viewStyle = { - color: "#ffffff", - width: "100%", - display: "flex", - minWidth: 1024, - maxWidth: 1024, - margin: "auto", - }; + }) + .catch(error => { + alert.error(error.toString()) + }); + } - const emptyWorkflowStyle = { - paddingTop: "200px", - width: 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 boxStyle = { - padding: "20px 20px 20px 20px", - width: "100%", - height: "250px", - color: "white", - backgroundColor: surfaceColor, - display: "flex", - flexDirection: "column", - }; + setFirstrequest(false) + getAvailableWorkflows() + } + }) - const paperAppContainer = { - display: "flex", - flexWrap: "wrap", - alignContent: "space-between", - marginTop: 5, - }; + const viewStyle = { + color: "#ffffff", + width: "100%", + display: "flex", + minWidth: 1024, + maxWidth: 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 emptyWorkflowStyle = { + paddingTop: "200px", + width: 1024, + margin: "auto", + } - const gridContainer = { - height: "auto", - color: "white", - margin: "10px", - backgroundColor: surfaceColor, - }; + const boxStyle = { + padding: "20px 20px 20px 20px", + width: "100%", + height: "250px", + color: "white", + backgroundColor: surfaceColor, + display: "flex", + flexDirection: "column", + } - const workflowActionStyle = { - display: "flex", - width: 160, - height: 44, - justifyContent: "space-between", - }; - const exportAllWorkflows = () => { - for (var key in workflows) { - exportWorkflow(workflows[key], false); - } - }; + const paperAppContainer = { + display: "flex", + flexWrap: 'wrap', + alignContent: "space-between", + marginTop: 5, + } - 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 = ""; - } - } + 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", + } - if (trigger.status === "running") { - trigger.status = "stopped"; - } - 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 gridContainer = { + height: "auto", + color: "white", + margin: "10px", + backgroundColor: surfaceColor, + } - for (var branchkey in data.branches) { - const branch = data.branches[branchkey]; - if (branch.source_id === trigger.id) { - branch.source_id = newId; - } + const workflowActionStyle = { + display: "flex", + width: 160, + height: 44, + justifyContent: "space-between", + } - if (branch.destination_id === trigger.id) { - branch.destination_id = newId; - } - } + const exportAllWorkflows = () => { + for (var key in workflows) { + exportWorkflow(workflows[key], false) + } + } - trigger.environment = isCloud ? "cloud" : "Shuffle"; - trigger.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" + } - if (data.actions !== null && data.actions !== undefined) { - for (key in data.actions) { - data.actions[key].authentication_id = ""; + 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") + } + } - 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; - } - } - } + for (var branchkey in data.branches) { + const branch = data.branches[branchkey] + if (branch.source_id === trigger.id) { + branch.source_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 (branch.destination_id === trigger.id) { + branch.destination_id = newId + } + } - if (branch.destination_id === data.actions[key].id) { - branch.destination_id = newId; - } - } + trigger.environment = isCloud ? "cloud" : "Shuffle" + trigger.id = newId + } + } - if (data.actions[key].id === data.start) { - data.start = newId; - } + if (data.actions !== null && data.actions !== undefined) { + for (key in data.actions) { + data.actions[key].authentication_id = "" - data.actions[key].environment = ""; - data.actions[key].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 ( - 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; - } - } - } + 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 + } - return data; - }; + if (branch.destination_id === data.actions[key].id) { + branch.destination_id = newId + } + } - const sanitizeWorkflow = (data) => { - data = JSON.parse(JSON.stringify(data)); - data["owner"] = ""; - console.log("Sanitize start: ", data); - data = deduplicateIds(data); + if (data.actions[key].id === data.start) { + data.start = newId + } - data["org"] = []; - data["org_id"] = ""; - data["execution_org"] = {}; + data.actions[key].environment = "" + data.actions[key].id = newId + } + } - // These are backwards.. True = saved before. Very confuse. - data["previously_saved"] = false; - data["first_save"] = false; - console.log("Sanitize end: ", data); + 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 + } + } + } - return data; - }; + return data + } - const exportWorkflow = (data, sanitize) => { - data = JSON.parse(JSON.stringify(data)); - let exportFileDefaultName = data.name + ".json"; + const sanitizeWorkflow = (data) => { + data = JSON.parse(JSON.stringify(data)) + data["owner"] = "" + console.log("Sanitize start: ", data) + data = deduplicateIds(data) - if (sanitize === true) { - data = sanitizeWorkflow(data); + data["org"] = [] + data["org_id"] = "" + data["execution_org"] = {} - if (data.subflows !== null && data.subflows !== undefined) { - alert.info( - "Not exporting with subflows when sanitizing. Please manually export them." - ); - data.subflows = []; - } + // These are backwards.. True = saved before. Very confuse. + data["previously_saved"] = false + data["first_save"] = false + console.log("Sanitize end: ", data) - // for (var key in data.subflows) { - // if (data.sublof - // } - //} - } + return data + } - // Add correct ID's for triggers - // Add mag + const exportWorkflow = (data, sanitize) => { + data = JSON.parse(JSON.stringify(data)) + let exportFileDefaultName = data.name+'.json'; - let dataStr = JSON.stringify(data); - let dataUri = - "data:application/json;charset=utf-8," + encodeURIComponent(dataStr); - let linkElement = document.createElement("a"); - linkElement.setAttribute("href", dataUri); - linkElement.setAttribute("download", exportFileDefaultName); - linkElement.click(); - }; + if (sanitize === true) { + data = sanitizeWorkflow(data) - const publishWorkflow = (data) => { - data = JSON.parse(JSON.stringify(data)); - data = sanitizeWorkflow(data); - alert.info("Sanitizing and publishing " + data.name); + if (data.subflows !== null && data.subflows !== undefined) { + alert.info("Not exporting with subflows when sanitizing. Please manually export them.") + data.subflows = [] + } - // 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", + // 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() }) - .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" - ); - } - } + .catch(error => { + alert.error(error.toString()) + }) + } - return response.json(); - }) - .then((responseJson) => { - if (responseJson.reason !== undefined) { - alert.error("Failed publishing: ", responseJson.reason); - } + 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) - getAvailableWorkflows(); - }) - .catch((error) => { - alert.error(error.toString()); - }); - }; - 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", + 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) }) - .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()); - }); - }; + .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); - } - 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 handleChipClick = (e) => { - addFilter(e.target.innerHTML); - }; + return response.json() + }) + .then(() => { + setTimeout(() => { + getAvailableWorkflows() + }, 1000) + }) + .catch(error => { + alert.error(error.toString()) + }); + } - const NewWorkflowPaper = () => { - const [hover, setHover] = React.useState(false); - 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)", - }; + const handleChipClick = (e) => { + addFilter(e.target.innerHTML) + } - return ( - - setModalOpen(true)} - onMouseOver={() => { - setHover(true); - }} - onMouseOut={() => { - setHover(false); - }} - > - - - - - - - - ); - }; - const WorkflowPaper = (props) => { - const { data } = props; - const [open, setOpen] = React.useState(false); - const [anchorEl, setAnchorEl] = React.useState(null); + const NewWorkflowPaper = () => { + const [hover, setHover] = React.useState(false) - var boxColor = "#FECC00"; - if (data.is_valid) { - boxColor = "#86c142"; - } + 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)", + } - if (!data.previously_saved) { - boxColor = "#f85a3e"; - } + return( + + setModalOpen(true)} onMouseOver={() => {setHover(true)}} onMouseOut={() => {setHover(false)}}> + + + + + + + + ) + } - const menuClick = (event) => { - setOpen(!open); - setAnchorEl(event.currentTarget); - }; + const WorkflowPaper = (props) => { + const { data } = props; + const [open, setOpen] = React.useState(false); + const [anchorEl, setAnchorEl] = React.useState(null); - 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={() => { + 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={() => { //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); - } - } + 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 + } + } - if (parsedworkflows.length > 0) { - console.log( - "Appending subflows during export: ", - parsedworkflows.length - ); - data.subflows = parsedworkflows; - } - } + setExportData(data) + setOpen(false) + }} key={"export"}> + + {"Export Workflow"} + + { + setDeleteModalOpen(true) + setSelectedWorkflowId(data.id) + setOpen(false) + }} key={"delete"}> + + {"Delete Workflow"} + + - setExportData(data); - setOpen(false); - }} - key={"export"} - > - - {"Export Workflow"} - - { - setDeleteModalOpen(true); - setSelectedWorkflowId(data.id); - setOpen(false); - }} - key={"delete"} - > - - {"Delete Workflow"} - - - ); + 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 } - 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, - }; + image = foundOrg.image === "" ? + {foundOrg.name} + : + {foundOrg.name} { + }}/> - image = - foundOrg.image === "" ? ( - {foundOrg.name} - ) : ( - {foundOrg.name} {}} - /> - ); + orgName = foundOrg.name + orgId = foundOrg.id + } + } - orgName = foundOrg.name; - orgId = foundOrg.id; - } - } + return ( + + +
    + + + +
    { + addFilter(orgId) + }}> + {image} +
    +
    + + + + {parsedName} + + + +
    + + + + + + {actions} + + + + + + + + {triggers} + + + + + { + if (subflows === 0) { + alert.info("No subflows for "+data.name) + return + } - return ( - - -
    - - - -
    { - addFilter(orgId); - }} - > - {image} -
    -
    - - - - {parsedName} - - - -
    - - - - - - {actions} - - - - - - - - {triggers} - - - - - { - if (subflows === 0) { - alert.info("No subflows for " + data.name); - return; - } + var newWorkflows = [data] + for (var key in data.triggers) { + const trigger = data.triggers[key] + if (trigger.app_name !== "Shuffle Workflow") { + continue + } - var newWorkflows = [data]; - for (var key in data.triggers) { - const trigger = data.triggers[key]; - if (trigger.app_name !== "Shuffle Workflow") { - continue; - } + 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 + } + } + } - 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 + } - 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? - } - workflowdata["name"] = name; - workflowdata["description"] = description; - if (tags !== undefined) { - workflowdata["tags"] = tags; - } + // Can create and set workflows + const setNewWorkflow = (name, description, tags, defaultReturnValue, editingWorkflow, redirect) => { - if (defaultReturnValue !== undefined) { - workflowdata["default_return_value"] = defaultReturnValue; - } + var method = "POST" + var extraData = "" + var workflowdata = {} - return fetch(globalUrl + "/api/v1/workflows" + extraData, { - method: method, - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(workflowdata), - credentials: "include", + 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", }) - .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!"); - 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"); - } + const importFiles = (event) => { + console.log("Importing!") - continue; - } + 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") + } - 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; - } + continue + } - // 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; + 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 + } - // 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()); - }); - }); + // 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 reads - reader.readAsText(file); - } - } + // 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()) + }); + }) - setLoadWorkflowsModalOpen(false); - }; + // Actually reads + reader.readAsText(file) + } + } - 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; - } - } - } + setLoadWorkflowsModalOpen(false) + } - return [triggers, subflows]; - }; + 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 WorkflowListView = () => { - let workflowData = ""; - if (workflows.length > 0) { - const columns = [ - { - field: "image", - headerName: "Logo", - width: 50, - sortable: false, - renderCell: (params) => { - const data = params.row.record; + return [triggers, subflows] + } - var boxColor = "#FECC00"; - if (data.is_valid) { - boxColor = "#86c142"; - } + 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 - if (!data.previously_saved) { - boxColor = "#f85a3e"; - } + var boxColor = "#FECC00" + if (data.is_valid) { + boxColor = "#86c142" + } - 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, - }; + if (!data.previously_saved) { + boxColor = "#f85a3e" + } - // - image = - foundOrg.image === "" ? ( - {foundOrg.name} - ) : ( - {foundOrg.name} { - //setFilteredWorkflows(newWorkflows) - }} - /> - ); - } - } + 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, } - return
    {image}
    ; - }, - }, - { - field: "title", - headerName: "Title", - width: 330, - renderCell: (params) => { - const data = params.row.record; + // + image = foundOrg.image === "" ? + {foundOrg.name} + : + {foundOrg.name} { + //setFilteredWorkflows(newWorkflows) + }}/> - return ( - - - {data.name} - - - ); - }, - }, + } + } - { - 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 ( +
    + {image} +
    + ) + }}, + { field: 'title', headerName: 'Title', width: 330, renderCell: (params) => { + const data = params.row.record - return ( - -
    - - - - - {actions} - - - - - - - - {triggers} - - - - - { - if (subflows === 0) { - alert.info("No subflows for " + data.name); - return; - } + return ( + + + + {data.name} + + + + ) + }}, - var newWorkflows = [data]; - for (var key in data.triggers) { - const trigger = data.triggers[key]; - if (trigger.app_name !== "Shuffle Workflow") { - continue; - } + { 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); - 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 ( + +
    + + + + + {actions} + + + + + + + + {triggers} + + + + + { + if (subflows === 0) { + alert.info("No subflows for "+data.name) + return + } - 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; - } + var newWorkflows = [data] + for (var key in data.triggers) { + const trigger = data.triggers[key] + if (trigger.app_name !== "Shuffle Workflow") { + 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, - }; + 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 obj; - }); - workflowData = ( - - ); - } - return
    {workflowData}
    ; - }; + setFilters(["Subflows of "+data.name]) + setFilteredWorkflows(newWorkflows) + }}> + + + + + {subflows} + +
    - 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); - }} - /> +
    +
    +
    + ) + } + }, + { 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 + } - 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; + return obj; + }) + workflowData = + + } + return ( +
    + {workflowData} +
    + ); + } - const viewSize = { - workflowView: 4, - executionsView: 3, - executionResults: 4, - }; + 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 - const workflowButtons = ( - - {view === "list" && ( - - - - )} - {view === "grid" && ( - - - - )} - - {importLoading ? ( - - ) : ( - - )} - - (upload = ref)} - onChange={importFiles} - /> - {workflows.length > 0 ? ( - - - - ) : null} - {isCloud ? null : ( - - - - )} - - ); + + const viewSize = { + workflowView: 4, + executionsView: 3, + executionResults: 4, + } - 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 workflowViewStyle = { + flex: viewSize.workflowView, + marginLeft: "10px", + marginRight: "10px", + } - return ( -
    -
    -
    -
    -

    Workflows

    -
    -
    - {/* + 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

    +
    +
    + {/*
    @@ -2497,7 +1910,7 @@ const Workflows = (props) => {
    */} - {/* + {/* chipRenderer={({ value, isFocused, isDisabled, handleClick, handleRequestDelete }, key) => { console.log("VALUE: ", value) @@ -2512,366 +1925,283 @@ 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 - /> - - 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; + 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 loadedCheck = - isLoaded && isLoggedIn && workflowDone ? ( -
    - 1366 ? 1366 : 1200, - margin: "auto", - padding: 20, - }} - onDrop={uploadFile} - > - - - {modalView} - {deleteModal} - {exportVerifyModal} - {publishModal} - {workflowDownloadModalOpen} -
    - ) : ( -
    - - Loading Workflows -
    - ); + Branch (default value is "master"): +
    + setDownloadBranch(e.target.value)} + placeholder="master" + fullWidth + /> +
    - // Maybe use gridview or something, idk - return
    {loadedCheck}
    ; -}; + 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 -export default Workflows; + 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