diff --git a/.github/push_nightly.sh b/.github/push_nightly.sh
index 92d5320b..b3660193 100644
--- a/.github/push_nightly.sh
+++ b/.github/push_nightly.sh
@@ -2,40 +2,79 @@
# Done manually for now since GHCR isn't being pushed to easily with the current Github action CI. Nightly = Latest IF we run hotfixes on latest
### Pull latest from ghcr CI/CD
-#docker pull ghcr.io/shuffle/shuffle-app_sdk:nightly
-#docker pull ghcr.io/shuffle/shuffle-worker:nightly
-#docker pull ghcr.io/shuffle/shuffle-orborus:nightly
-#docker pull ghcr.io/shuffle/shuffle-frontend:nightly
+docker pull ghcr.io/shuffle/shuffle-app_sdk:nightly
+docker pull ghcr.io/shuffle/shuffle-worker:nightly
+docker pull ghcr.io/shuffle/shuffle-orborus:nightly
+docker pull ghcr.io/shuffle/shuffle-frontend:nightly
#docker pull ghcr.io/shuffle/shuffle-backend:nightly
+#
+### NIGHTLY releases
+docker tag ghcr.io/shuffle/shuffle-app_sdk:nightly ghcr.io/frikky/shuffle-app_sdk:nightly
+docker tag ghcr.io/shuffle/shuffle-worker:nightly ghcr.io/frikky/shuffle-worker:nightly
+docker tag ghcr.io/shuffle/shuffle-orborus:nightly ghcr.io/frikky/shuffle-orborus:nightly
+docker tag ghcr.io/shuffle/shuffle-frontend:nightly ghcr.io/frikky/shuffle-frontend:nightly
+docker tag ghcr.io/shuffle/shuffle-backend:nightly ghcr.io/frikky/shuffle-backend:nightly
+
+docker push ghcr.io/frikky/shuffle-app_sdk:nightly
+docker push ghcr.io/frikky/shuffle-worker:nightly
+docker push ghcr.io/frikky/shuffle-orborus:nightly
+docker push ghcr.io/frikky/shuffle-frontend:nightly
+docker push ghcr.io/frikky/shuffle-backend:nightly
### LATEST releases:
-#docker tag ghcr.io/shuffle/shuffle-app_sdk:nightly ghcr.io/shuffle/shuffle-app_sdk:latest
-#docker tag ghcr.io/shuffle/shuffle-worker:nightly ghcr.io/shuffle/shuffle-worker:latest
-#docker tag ghcr.io/shuffle/shuffle-orborus:nightly ghcr.io/shuffle/shuffle-orborus:latest
-#docker tag ghcr.io/shuffle/shuffle-frontend:nightly ghcr.io/shuffle/shuffle-frontend:latest
-#docker tag ghcr.io/shuffle/shuffle-backend:nightly ghcr.io/shuffle/shuffle-backend:latest
-#
-#docker push ghcr.io/shuffle/shuffle-app_sdk:latest
-#docker push ghcr.io/shuffle/shuffle-worker:latest
-#docker push ghcr.io/shuffle/shuffle-orborus:latest
-#docker push ghcr.io/shuffle/shuffle-frontend:latest
-#docker push ghcr.io/shuffle/shuffle-backend:latest
+## shuffle/shuffle
+docker tag ghcr.io/shuffle/shuffle-app_sdk:nightly ghcr.io/shuffle/shuffle-app_sdk:latest
+docker tag ghcr.io/shuffle/shuffle-worker:nightly ghcr.io/shuffle/shuffle-worker:latest
+docker tag ghcr.io/shuffle/shuffle-orborus:nightly ghcr.io/shuffle/shuffle-orborus:latest
+docker tag ghcr.io/shuffle/shuffle-frontend:nightly ghcr.io/shuffle/shuffle-frontend:latest
+docker tag ghcr.io/shuffle/shuffle-backend:nightly ghcr.io/shuffle/shuffle-backend:latest
+
+docker push ghcr.io/shuffle/shuffle-app_sdk:latest
+docker push ghcr.io/shuffle/shuffle-worker:latest
+docker push ghcr.io/shuffle/shuffle-orborus:latest
+docker push ghcr.io/shuffle/shuffle-frontend:latest
+docker push ghcr.io/shuffle/shuffle-backend:latest
+
+## frikky/shuffle
+docker tag ghcr.io/shuffle/shuffle-app_sdk:nightly ghcr.io/frikky/shuffle-app_sdk:latest
+docker tag ghcr.io/shuffle/shuffle-worker:nightly ghcr.io/frikky/shuffle-worker:latest
+docker tag ghcr.io/shuffle/shuffle-orborus:nightly ghcr.io/frikky/shuffle-orborus:latest
+docker tag ghcr.io/shuffle/shuffle-frontend:nightly ghcr.io/frikky/shuffle-frontend:latest
+docker tag ghcr.io/shuffle/shuffle-backend:nightly ghcr.io/frikky/shuffle-backend:latest
+
+docker push ghcr.io/frikky/shuffle-app_sdk:latest
+docker push ghcr.io/frikky/shuffle-worker:latest
+docker push ghcr.io/frikky/shuffle-orborus:latest
+docker push ghcr.io/frikky/shuffle-frontend:latest
+docker push ghcr.io/frikky/shuffle-backend:latest
### 1.1.0 releases:
-#docker tag ghcr.io/shuffle/shuffle-app_sdk:nightly ghcr.io/shuffle/shuffle-app_sdk:1.1.0
-#docker tag ghcr.io/shuffle/shuffle-worker:nightly ghcr.io/shuffle/shuffle-worker:1.1.0
-#docker tag ghcr.io/shuffle/shuffle-orborus:nightly ghcr.io/shuffle/shuffle-orborus:1.1.0
-#docker tag ghcr.io/shuffle/shuffle-frontend:nightly ghcr.io/shuffle/shuffle-frontend:1.1.0
-#docker tag ghcr.io/shuffle/shuffle-backend:nightly ghcr.io/shuffle/shuffle-backend:1.1.0
-#
-#docker push ghcr.io/shuffle/shuffle-app_sdk:1.1.0
-#docker push ghcr.io/shuffle/shuffle-worker:1.1.0
-#docker push ghcr.io/shuffle/shuffle-orborus:1.1.0
-#docker push ghcr.io/shuffle/shuffle-frontend:1.1.0
-#docker push ghcr.io/shuffle/shuffle-backend:1.1.0
+## shuffle/shuffle
+docker tag ghcr.io/shuffle/shuffle-app_sdk:nightly ghcr.io/shuffle/shuffle-app_sdk:1.1.0
+docker tag ghcr.io/shuffle/shuffle-worker:nightly ghcr.io/shuffle/shuffle-worker:1.1.0
+docker tag ghcr.io/shuffle/shuffle-orborus:nightly ghcr.io/shuffle/shuffle-orborus:1.1.0
+docker tag ghcr.io/shuffle/shuffle-frontend:nightly ghcr.io/shuffle/shuffle-frontend:1.1.0
+docker tag ghcr.io/shuffle/shuffle-backend:nightly ghcr.io/shuffle/shuffle-backend:1.1.0
+docker push ghcr.io/shuffle/shuffle-app_sdk:1.1.0
+docker push ghcr.io/shuffle/shuffle-worker:1.1.0
+docker push ghcr.io/shuffle/shuffle-orborus:1.1.0
+docker push ghcr.io/shuffle/shuffle-frontend:1.1.0
+docker push ghcr.io/shuffle/shuffle-backend:1.1.0
+## frikky/shuffle
+docker tag ghcr.io/shuffle/shuffle-app_sdk:nightly ghcr.io/frikky/shuffle-app_sdk:1.1.0
+docker tag ghcr.io/shuffle/shuffle-worker:nightly ghcr.io/frikky/shuffle-worker:1.1.0
+docker tag ghcr.io/shuffle/shuffle-orborus:nightly ghcr.io/frikky/shuffle-orborus:1.1.0
+docker tag ghcr.io/shuffle/shuffle-frontend:nightly ghcr.io/frikky/shuffle-frontend:1.1.0
+docker tag ghcr.io/shuffle/shuffle-backend:nightly ghcr.io/frikky/shuffle-backend:1.1.0
+
+docker push ghcr.io/frikky/shuffle-app_sdk:1.1.0
+docker push ghcr.io/frikky/shuffle-worker:1.1.0
+docker push ghcr.io/frikky/shuffle-orborus:1.1.0
+docker push ghcr.io/frikky/shuffle-frontend:1.1.0
+docker push ghcr.io/frikky/shuffle-backend:1.1.0
### Manage worker-scale upload (Requires auth)
# This is supposed to be unavailable, and only be downloadable by customers
diff --git a/frontend/src/components/Billing.jsx b/frontend/src/components/Billing.jsx
new file mode 100644
index 00000000..0394adad
--- /dev/null
+++ b/frontend/src/components/Billing.jsx
@@ -0,0 +1,317 @@
+import React, { useState, useEffect } from "react";
+import ReactGA from 'react-ga';
+
+import { useTheme } from "@material-ui/core/styles";
+import {
+ Paper,
+ Typography,
+ Divider,
+ Button,
+ Grid,
+ Card,
+} from "@material-ui/core";
+
+import { useAlert } from "react-alert";
+import { typecost, typecost_single, } from "../views/HandlePaymentNew.jsx";
+
+const Billing = (props) => {
+ const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, } = props;
+ console.log("Billing: ", billingInfo);
+ const theme = useTheme();
+ const alert = useAlert();
+
+ const stripe = typeof window === 'undefined' || window.location === undefined ? "" : props.stripeKey === undefined ? "" : window.Stripe ? window.Stripe(props.stripeKey) : ""
+ console.log("Stripe: ", stripe)
+
+ const paperStyle = {
+ padding: 20,
+ height: "100%",
+ width: "100%",
+ backgroundColor: theme.palette.surfaceColor,
+ border: "1px solid rgba(255,255,255,0.3)",
+ marginRight: 10,
+ }
+
+ const isCloud =
+ window.location.host === "localhost:3002" ||
+ window.location.host === "shuffler.io";
+
+ billingInfo.subscription = {
+ "active": true,
+ "name": "Pay as you go",
+ "price": typecost_single,
+ "currency": "USD",
+ "currency_text": "$",
+ "interval": "app run / month",
+ "description": "Pay as you go",
+ "features": [
+ "Includes 10.000 app run/month for free. ",
+ "Pay for what you use with no minimum commitment and cancel anytime.",
+ ],
+ "limit": 10000,
+ }
+
+
+ const handleStripeRedirect = () => {
+ //var priceItem = "price_1MRNF1DzMUgUjxHSfFTUb2Xh"
+ if (stripe == "") {
+ console.log("Stripe not loaded")
+ return
+ }
+
+ var priceItem = "price_1MROFrDzMUgUjxHShcSxgHO1"
+
+ const successUrl = `${window.location.origin}/admin?admin_tab=billing&payment=success`
+ const failUrl = `${window.location.origin}/admin?admin_tab=billing&payment=failure`
+ var checkoutObject = {
+ lineItems: [
+ {
+ price: priceItem,
+ quantity: 1
+ },
+ ],
+ mode: "subscription",
+ billingAddressCollection: "auto",
+ successUrl: successUrl,
+ cancelUrl: failUrl,
+ clientReferenceId: props.userdata.active_org.id,
+ }
+ //submitType: "donate",
+
+ stripe.redirectToCheckout(checkoutObject)
+ .then(function (result) {
+ console.log("SUCCESS STRIPE?: ", result)
+
+ ReactGA.event({
+ category: "pricing",
+ action: "add_card_success",
+ label: "",
+ })
+ })
+ .catch(function(error) {
+ console.error("STRIPE ERROR: ", error)
+
+ ReactGA.event({
+ category: "pricing",
+ action: "add_card_error",
+ label: "",
+ })
+ });
+ }
+
+ const cancelSubscriptions = (subscription_id) => {
+ const orgId = selectedOrganization.id;
+ const data = {
+ subscription_id: subscription_id,
+ action: "cancel",
+ org_id: selectedOrganization.id,
+ };
+
+ const url = globalUrl + `/api/v1/orgs/${orgId}/cancel`;
+ 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");
+ }
+
+ if (handleGetOrg != undefined) {
+ 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 SubscriptionObject = (props) => {
+ const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, subscription, } = props;
+
+ console.log("Sub: ", subscription)
+ var top_text = "Base Access"
+ if (subscription.limit === undefined && subscription.level !== undefined) {
+
+ subscription.name = "Enterprise"
+ subscription.currency_text = "$"
+ subscription.price = subscription.level*180
+ subscription.limit = subscription.level*100000
+ subscription.interval = subscription.recurrence
+ subscription.features = [
+ "Includes " + subscription.limit + " app runs/month. ",
+ "Multi-Tenancy and Region-Selection",
+ "And all other features from /pricing",
+ ]
+ }
+
+ if (subscription.name === "Enterprise" && subscription.active === true) {
+ top_text = "Current Plan"
+ }
+
+ return (
+
+
+
+ {top_text}
+
+
+
+
+
+ {subscription.name}
+
+
+
+ {subscription.currency_text}{subscription.price}
+
+
+ / {subscription.interval}
+
+
+
+ Features
+
+
+ {subscription.features !== undefined && subscription.features !== null ?
+ subscription.features.map((feature, index) => {
+ return (
+
+
+ {feature}
+
+
+ )
+ })
+ : null}
+
+
+ {/*subscription.name === "Pay as you go" && subscription.limit <= 10000 ?
+
+
+ You are not subscribed to any plan and are using the free plan with max 10,000 apps per month. Activate billing to de-activate this limit.
+
+ {
+ handleStripeRedirect()
+ }}
+ >
+ Activate Billing
+
+
+ : null*/}
+
+ )
+ }
+
+
+ return (
+
+
+ Billing
+
+
+ We use Stripe to manage subscriptions and do not store any of your billing information. You can manage your subscription and billing information below.
+
+
+ {billingInfo.subscription !== undefined && billingInfo.subscription !== null ?
+
+ : null}
+ {isCloud &&
+ selectedOrganization.subscriptions !== undefined &&
+ selectedOrganization.subscriptions !== null &&
+ selectedOrganization.subscriptions.length > 0 ?
+
+ selectedOrganization.subscriptions
+ .reverse()
+ .map((sub, index) => {
+ return (
+
+ )
+ })
+ : null}
+ {/*
+
+
+ Quantity : {sub.level}
+
+ Recurrence : {sub.recurrence}
+
+ {sub.active ? (
+
+
Started :{" "}
+ {new Date(sub.startdate * 1000).toISOString()}
+
+
{
+ cancelSubscriptions(sub.reference);
+ }}
+ >
+ Cancel subscription
+
+
+ ) : (
+
+
Cancelled :{" "}
+ {new Date(
+ sub.cancellationdate * 1000
+ ).toISOString()}
+
+
+ Status : Deactivated
+
+
+ )}
+
+
+ */}
+
+
+ )
+}
+
+export default Billing;
\ No newline at end of file
diff --git a/frontend/src/components/ConfigureWorkflow.jsx b/frontend/src/components/ConfigureWorkflow.jsx
index 0e5cc988..dc35d902 100644
--- a/frontend/src/components/ConfigureWorkflow.jsx
+++ b/frontend/src/components/ConfigureWorkflow.jsx
@@ -129,7 +129,7 @@ const ConfigureWorkflow = (props) => {
setFirstLoad(workflow.id)
const newactions = [];
- for (var key in workflow.actions) {
+ for (let [key, keyval] in workflow.actions.entries()) {
const action = workflow.actions[key];
var newaction = {
large_image: action.large_image,
@@ -175,7 +175,7 @@ const ConfigureWorkflow = (props) => {
) {
// Check if configuration is filled or not
var filled = true;
- for (var key in action.parameters) {
+ for (let [key,keyval] in action.parameters.entries()) {
if (action.parameters[key].configuration) {
//console.log("Found config: ", action.parameters[key])
if (
@@ -216,7 +216,7 @@ const ConfigureWorkflow = (props) => {
if (newaction.must_authenticate) {
var authenticationOptions = [];
- for (var key in appAuthentication) {
+ for (let [key,keyval] in appAuthentication.entries()) {
const auth = appAuthentication[key];
if (auth.app.name === app.name && auth.active) {
//console.log("Found auth: ", auth)
@@ -264,7 +264,8 @@ const ConfigureWorkflow = (props) => {
}
}
- for (var key in workflow.workflow_variables) {
+ if (workflow.workflow_variables !== undefined && workflow.workflow_variables !== null && workflow.workflow_variables.length !== 0) {
+ for (let [key,keyval] in workflow.workflow_variables.entries()) {
const variable = workflow.workflow_variables[key];
if (
variable.value === undefined ||
@@ -276,8 +277,10 @@ const ConfigureWorkflow = (props) => {
requiredVariables.push(variable);
}
}
+ }
- for (var key in workflow.triggers) {
+ if (workflow.triggers !== undefined && workflow.triggers !== null && workflow.triggers.length !== 0) {
+ for (let [key,keyval] in workflow.triggers.entries()) {
var trigger = workflow.triggers[key];
trigger.index = key;
@@ -299,7 +302,7 @@ const ConfigureWorkflow = (props) => {
}
]
- for (var subkey in tmpsteps) {
+ for (let [subkey,subkeyval] in tmpsteps.entries()) {
newactions[foundindex].steps.push(tmpsteps[subkey])
}
@@ -326,6 +329,7 @@ const ConfigureWorkflow = (props) => {
requiredTriggers.push(trigger);
}
+}
if (
requiredTriggers.length === 0 &&
@@ -342,11 +346,11 @@ const ConfigureWorkflow = (props) => {
if (appAuthentication.length !== previousAuth.length) {
var newactions = []
- for (var actionkey in requiredActions) {
+ for (let [actionkey, actionkeyval] in requiredActions.entries()) {
var newaction = requiredActions[actionkey];
const app = newaction.app;
- for (var key in appAuthentication) {
+ for (let [key,keyval] in appAuthentication.entries()) {
const auth = appAuthentication[key];
// Does this account for all the different ones of the same?
@@ -690,7 +694,7 @@ const ConfigureWorkflow = (props) => {
if (workflow.actions !== null) {
//console.log(workflow.actions)
alert.info("Setting action to version "+action.update_version)
- for (var key in workflow.actions) {
+ for (let [key,keyval] in workflow.actions.entries()) {
if (workflow.actions[key].app_name === action.app_name && workflow.actions[key].app_version === action.app_version) {
workflow.actions[key].app_version = action.update_version
diff --git a/frontend/src/components/Dropzone.js b/frontend/src/components/Dropzone.jsx
similarity index 100%
rename from frontend/src/components/Dropzone.js
rename to frontend/src/components/Dropzone.jsx
diff --git a/frontend/src/components/Newsletter.jsx b/frontend/src/components/Newsletter.jsx
new file mode 100644
index 00000000..d4d09dad
--- /dev/null
+++ b/frontend/src/components/Newsletter.jsx
@@ -0,0 +1,102 @@
+import React, {useState} from 'react';
+import { useTheme } from '@material-ui/core/styles';
+import {isMobile} from "react-device-detect";
+import ReactGA from 'react-ga';
+
+import {TextField, Typography, Button} from '@material-ui/core';
+
+const Newsletter = (props) => {
+ const { globalUrl, } = props;
+
+ const theme = useTheme();
+ const [email, setEmail] = useState("");
+ const [msg, setMsg] = useState("");
+ const [buttonActive, setButtonActive] = useState(true);
+ const buttonStyle = {minWidth: 300, borderRadius: 30, height: 60, width: 140, margin: isMobile ? "15px auto 15px auto" : "20px 20px 20px 10px", fontSize: 18,}
+
+ const newsletterSignup = (inemail) => {
+ if (inemail.length < 4) {
+ setMsg("Invalid email")
+ setButtonActive(true)
+ return
+ }
+
+ setButtonActive(false)
+ const data = {"email": inemail}
+ const url = globalUrl+'/api/v1/functions/newsletter_signup'
+ fetch(url, {
+ method: 'POST',
+ body: JSON.stringify(data),
+ headers: {
+ 'Content-Type': 'application/json; charset=utf-8',
+ },
+ })
+ .then(response =>
+ response.json().then(responseJson => {
+ setButtonActive(true)
+ setMsg(responseJson["reason"])
+ if (responseJson["success"] === false) {
+ } else {
+ setEmail("")
+ }
+ }),
+ )
+ .catch(error => {
+ setMsg("Something went wrong: ", error.toString())
+ setButtonActive(true)
+ });
+ }
+
+ return (
+
+
+ Security Automation Newsletter
+
+
+ Defensive security is 99% noise. Join us to sift through it.
+
+
+ {
+ setEmail(e.target.value)
+ }}
+ placeholder="Your email"
+ id="standard-required"
+ margin="normal"
+ variant="outlined"
+ />
+
+
{
+ newsletterSignup(email)
+ ReactGA.event({
+ category: "newsletter",
+ action: `signup_click`,
+ label: "",
+ })
+ }}
+ >
+ Sign up
+
+
+ {msg}
+
+ )
+}
+
+
+export default Newsletter;
\ No newline at end of file
diff --git a/frontend/src/components/OrgHeaderexpanded.jsx b/frontend/src/components/OrgHeaderexpanded.jsx
new file mode 100644
index 00000000..f2691e54
--- /dev/null
+++ b/frontend/src/components/OrgHeaderexpanded.jsx
@@ -0,0 +1,702 @@
+import React, { useEffect } from "react";
+
+import { makeStyles } from "@material-ui/styles";
+import { useTheme } from "@material-ui/core/styles";
+import { useAlert } from "react-alert";
+
+import {
+ FormControl,
+ InputLabel,
+ Paper,
+ OutlinedInput,
+ Checkbox,
+ Card,
+ Tooltip,
+ FormControlLabel,
+ Typography,
+ Switch,
+ Select,
+ MenuItem,
+ Divider,
+ TextField,
+ Button,
+ Tabs,
+ Tab,
+ Grid,
+} from "@material-ui/core";
+
+import IconButton from "@material-ui/core/IconButton";
+import ExpandLessIcon from "@material-ui/icons/ExpandLess";
+import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
+import SaveIcon from "@material-ui/icons/Save";
+
+const useStyles = makeStyles({
+ notchedOutline: {
+ borderColor: "#f85a3e !important",
+ },
+});
+
+const OrgHeaderexpanded = (props) => {
+ const {
+ userdata,
+ selectedOrganization,
+ setSelectedOrganization,
+ globalUrl,
+ isCloud,
+ adminTab,
+ } = props;
+
+ const theme = useTheme();
+ const alert = useAlert();
+ const classes = useStyles();
+ 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 [documentationReference, setDocumentationReference] = React.useState(
+ selectedOrganization.defaults === undefined
+ ? ""
+ : selectedOrganization.defaults.documentation_reference === undefined ||
+ selectedOrganization.defaults.documentation_reference.length === 0
+ ? ""
+ : selectedOrganization.defaults.documentation_reference
+ );
+ const [openidClientId, setOpenidClientId] = React.useState(
+ selectedOrganization.sso_config === undefined
+ ? ""
+ : selectedOrganization.sso_config.client_id === undefined ||
+ selectedOrganization.sso_config.client_id.length === 0
+ ? ""
+ : selectedOrganization.sso_config.client_id
+ );
+ const [openidClientSecret, setOpenidClientSecret] = React.useState(
+ selectedOrganization.sso_config === undefined
+ ? ""
+ : selectedOrganization.sso_config.client_secret === undefined ||
+ selectedOrganization.sso_config.client_secret.length === 0
+ ? ""
+ : selectedOrganization.sso_config.client_secret
+ );
+ const [openidAuthorization, setOpenidAuthorization] = React.useState(
+ selectedOrganization.sso_config === undefined
+ ? ""
+ : selectedOrganization.sso_config.openid_authorization === undefined ||
+ selectedOrganization.sso_config.openid_authorization.length === 0
+ ? ""
+ : selectedOrganization.sso_config.openid_authorization
+ );
+ const [openidToken, setOpenidToken] = React.useState(
+ selectedOrganization.sso_config === undefined
+ ? ""
+ : selectedOrganization.sso_config.openid_token === undefined ||
+ selectedOrganization.sso_config.openid_token.length === 0
+ ? ""
+ : selectedOrganization.sso_config.openid_token
+ )
+
+ const 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 orgSaveButton = (
+
+
+ handleEditOrg(
+ orgName,
+ orgDescription,
+ selectedOrganization.id,
+ selectedOrganization.image,
+ {
+ app_download_repo: appDownloadUrl,
+ app_download_branch: appDownloadBranch,
+ workflow_download_repo: workflowDownloadUrl,
+ workflow_download_branch: workflowDownloadBranch,
+ notification_workflow: notificationWorkflow,
+ documentation_reference: documentationReference,
+ },
+ {
+ sso_entrypoint: ssoEntrypoint,
+ sso_certificate: ssoCertificate,
+ client_id: openidClientId,
+ client_secret: openidClientSecret,
+ openid_authorization: openidAuthorization,
+ openid_token: openidToken,
+ }
+ )
+ }
+ >
+
+
+
+ );
+
+ return (
+
+
+
+
+ Notification Workflow ID
+ {
+ setNotificationWorkflow(e.target.value);
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+
+
+
+ Org Documentation reference
+ {
+ setDocumentationReference(e.target.value);
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+
+ {isCloud ? null :
+
+ OpenID connect
+
+
+
+ Client ID
+ 0
+ }
+ id="outlined-with-placeholder"
+ margin="normal"
+ variant="outlined"
+ placeholder="The OpenID client ID from the identity provider"
+ value={openidClientId}
+ onChange={(e) => {
+ setOpenidClientId(e.target.value);
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+
+
+
+ Client Secret (optional)
+ 0
+ }
+ id="outlined-with-placeholder"
+ margin="normal"
+ variant="outlined"
+ placeholder="The OpenID client secret - DONT use this if dealing with implicit auth / PKCE"
+ value={openidClientSecret}
+ onChange={(e) => {
+ setOpenidClientSecret(e.target.value);
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+
+
+
+
+
+ Authorization URL
+ {
+ setOpenidAuthorization(e.target.value)
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+
+
+
+ Token URL
+ {
+ setOpenidToken(e.target.value)
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+
+
+
+ }
+ {/*isCloud ? null : */}
+
+ SAML SSO (v1.1)
+
+
+
+ SSO Entrypoint (IdP)
+ 0
+ }
+ id="outlined-with-placeholder"
+ margin="normal"
+ variant="outlined"
+ placeholder="The entrypoint URL from your provider"
+ value={ssoEntrypoint}
+ onChange={(e) => {
+ setSsoEntrypoint(e.target.value);
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+
+
+
+ SSO Certificate (X509)
+ {
+ setSsoCertificate(e.target.value);
+ }}
+ InputProps={{
+ classes: {
+ notchedOutline: classes.notchedOutline,
+ },
+ style: {
+ color: "white",
+ },
+ }}
+ />
+
+
+
+ {isCloud ?
+
+ IdP URL for Shuffle: https://shuffler.io/api/v1/login_sso
+
+ : null}
+
+ {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",
+ },
+ }}
+ />
+
+
+ )}
+
+
+ {orgSaveButton}
+
+ {/*
+
+ {expanded ?
+
+ :
+
+ }
+
+ */}
+
+
+ )
+}
+
+export default OrgHeaderexpanded;
\ No newline at end of file
diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx
index b9ded7f8..ede8d900 100644
--- a/frontend/src/components/ParsedAction.jsx
+++ b/frontend/src/components/ParsedAction.jsx
@@ -277,7 +277,7 @@ const ParsedAction = (props) => {
console.log("FOUNDACTION: ", foundAction);
if (foundAction !== null && foundAction !== undefined) {
var foundparams = [];
- for (var paramkey in foundAction.parameters) {
+ for (let [paramkey,paramkeyval] in foundAction.parameters.entries()) {
const param = foundAction.parameters[paramkey];
const foundParam = selectedAction.parameters.find(
@@ -401,7 +401,7 @@ const ParsedAction = (props) => {
if (actionlist.length === 0) {
// FIXME: Have previous execution values in here
if (workflowExecutions.length > 0) {
- for (var key in workflowExecutions) {
+ for (let [key,keyval] in workflowExecutions.entries()) {
if (
workflowExecutions[key].execution_argument === undefined ||
workflowExecutions[key].execution_argument === null ||
@@ -451,7 +451,7 @@ const ParsedAction = (props) => {
workflow.workflow_variables !== undefined &&
workflow.workflow_variables.length > 0
) {
- for (var key in workflow.workflow_variables) {
+ for (let [key,keyval] in workflow.workflow_variables.entries()) {
const item = workflow.workflow_variables[key];
actionlist.push({
type: "workflow_variable",
@@ -470,7 +470,7 @@ const ParsedAction = (props) => {
workflow.execution_variables !== undefined &&
workflow.execution_variables.length > 0
) {
- for (var key in workflow.execution_variables) {
+ for (let [key,keyval] in workflow.execution_variables.entries()) {
const item = workflow.execution_variables[key];
actionlist.push({
type: "execution_variable",
@@ -488,7 +488,7 @@ const ParsedAction = (props) => {
var parents = getParents(selectedAction);
if (parents.length > 1) {
- for (var key in parents) {
+ for (let [key,keyval] in parents.entries()) {
const item = parents[key];
if (item.label === "Execution Argument") {
continue;
@@ -500,7 +500,7 @@ const ParsedAction = (props) => {
if (workflowExecutions.length > 0) {
// Look for the ID
const found = false;
- for (var key in workflowExecutions) {
+ for (let [key,keyval] in workflowExecutions.entries()) {
if (
workflowExecutions[key].results === undefined ||
workflowExecutions[key].results === null
@@ -566,7 +566,7 @@ const ParsedAction = (props) => {
if (found !== null && found !== undefined) {
var new_occurences = []
- for (var key in found) {
+ for (let [key,keyval] in found.entries()) {
if (found[key][0] !== "\\") {
new_occurences.push(found[key])
}
@@ -579,7 +579,7 @@ const ParsedAction = (props) => {
if (found !== null) {
try {
// When the found array is empty.
- for (var i = 0; i < found.length; i++) {
+ for (let i = 0; i < found.length; i++) {
const variableSplit = found[i].split(".#")
if ((variableSplit.length-1) > 1) {
//console.log("Larger than 1: ", variableSplit)
@@ -589,7 +589,7 @@ const ParsedAction = (props) => {
}
var foundSlice = false
- for (var j = 0; j < actionlist.length; j++) {
+ for (let j = 0; j < actionlist.length; j++) {
//console.log("ACTION: ", found[i], actionlist[j])
//console.log("ACTION :", found[i].split(".")[0].slice(1,).toLowerCase(), actionlist[j].autocomplete.toLowerCase())
if(found[i].split(".")[0].slice(1,).toLowerCase() == actionlist[j].autocomplete.toLowerCase()){
@@ -730,7 +730,7 @@ const ParsedAction = (props) => {
var curstring = "";
var record = false;
- for (var key in selectedActionParameters[count].value) {
+ for (let [key,keyval] in selectedActionParameters[count].value.entries()) {
const item = selectedActionParameters[count].value[key];
if (record) {
curstring += item;
@@ -911,7 +911,7 @@ const ParsedAction = (props) => {
var curstring = ""
var record = false
- for (var key in selectedActionParameters[count].value) {
+ for (let [key,keyval] in selectedActionParameters[count].value.entries()) {
const item = selectedActionParameters[count].value[key]
if (record) {
curstring += item
@@ -1187,7 +1187,7 @@ const ParsedAction = (props) => {
renderInput={(params) => {
if (params.inputProps !== undefined && params.inputProps !== null && params.inputProps.value !== undefined && params.inputProps.value !== null) {
const prefixes = ["Post", "Put", "Patch"]
- for (var key in prefixes) {
+ for (let [key,keyval] in prefixes.entries()) {
if (params.inputProps.value.startsWith(prefixes[key])) {
params.inputProps.value = params.inputProps.value.replace(prefixes[key]+" ", "", -1)
if (params.inputProps.value.length > 1) {
@@ -1426,7 +1426,7 @@ const ParsedAction = (props) => {
setHideBody(!hideBody);
- for (var key in selectedActionParameters) {
+ for (let [key,keyval] in selectedActionParameters.entries()) {
var currentItem = selectedActionParameters[key];
if (currentItem.name === "ssl_verify") {
@@ -1472,10 +1472,10 @@ const ParsedAction = (props) => {
openApiHelperText = "OpenAPI spec: fill the following fields.";
//console.log("SHOULD ADD TO selectedActionParameters!: ", found, selectedActionParameters)
var changed = false;
- for (var specKey in found) {
+ for (let [specKey,specKeyVal] in found.entries()) {
const tmpitem = found[specKey];
var skip = false;
- for (var innerkey in selectedActionParameters) {
+ for (let [innerkey,innerkeyval] in selectedActionParameters.entries()) {
if (selectedActionParameters[innerkey].name === tmpitem) {
skip = true;
break;
@@ -1707,7 +1707,7 @@ const ParsedAction = (props) => {
var foundnewline = false
var allValues = []
- for (var key in splitdata) {
+ for (let [key,keyval] in splitdata.entries()) {
const line = splitdata[key]
if (line === "") {
foundnewline = true
@@ -1777,7 +1777,7 @@ const ParsedAction = (props) => {
const tmpsplit = selectedActionParameters[count].value.split("\n")
var valsplit = []
var add_empty = false
- for (var key in tmpsplit) {
+ for (let [key,keyval] in tmpsplit.entries()) {
if (tmpsplit[key] === "") {
add_empty = true
continue
@@ -1792,7 +1792,7 @@ const ParsedAction = (props) => {
console.log("Split: ", valsplit)
var newarr = []
- for (var key in valsplit) {
+ for (let [key,keyval] in valsplit.entries()) {
var line = valsplit[key]
if (key == index) {
@@ -1832,7 +1832,7 @@ const ParsedAction = (props) => {
var tmpsplit = selectedActionParameters[count].value.split("\n")
var valsplit = []
var add_empty = false
- for (var key in tmpsplit) {
+ for (let [key,keyval] in tmpsplit.entries()) {
if (tmpsplit[key] === "") {
add_empty = true
continue
@@ -1847,7 +1847,7 @@ const ParsedAction = (props) => {
console.log("Split: ", valsplit)
var newarr = []
- for (var key in valsplit) {
+ for (let [key,keyval] in valsplit.entries()) {
var line = valsplit[key]
if (key == index) {
@@ -2094,7 +2094,7 @@ const ParsedAction = (props) => {
: "$" + values[0].autocomplete;
toComplete = toComplete.toLowerCase().replaceAll(" ", "_");
- for (var key in values) {
+ for (let [key,keyval] in values.entries()) {
if (key == 0 || values[key].autocomplete.length === 0) {
continue;
}
@@ -2205,7 +2205,7 @@ const ParsedAction = (props) => {
workflow.triggers !== null &&
workflow.triggers.length > 0
) {
- for (var key in workflow.triggers) {
+ for (let [key,keyval] in workflow.triggers.entries()) {
const item = workflow.triggers[key];
if (cy !== undefined) {
@@ -2727,7 +2727,7 @@ const ParsedAction = (props) => {
if (workflowExecutions.length > 0) {
// Look for the ID
const found = false;
- for (var key in workflowExecutions) {
+ for (let [key,keyval] in workflowExecutions.entries()) {
if (
workflowExecutions[key].results === undefined ||
workflowExecutions[key].results === null
@@ -2971,8 +2971,8 @@ const ParsedAction = (props) => {
//
// Should make it a function lol
if (workflow.branches !== undefined && workflow.branches !== null) {
- for (var key in workflow.branches) {
- for (var subkey in workflow.branches[key].conditions) {
+ for (let [key,keyval] in workflow.branches.entries()) {
+ for (let [subkey,subkeyval] in workflow.branches[key].conditions.entries()) {
const condition = workflow.branches[key].conditions[subkey]
const sourceparam = condition.source
const destinationparam = condition.destination
@@ -3080,12 +3080,12 @@ const ParsedAction = (props) => {
}
}
- for (var key in workflow.actions) {
+ for (let [key,keyval] in workflow.actions.entries()) {
if (workflow.actions[key].id === selectedAction.id) {
continue
}
- for (var subkey in workflow.actions[key].parameters) {
+ for (let [subkey, subkeyval] in workflow.actions[key].parameters.entries()) {
const param = workflow.actions[key].parameters[subkey];
if (!param.value.includes("$")) {
continue
@@ -3251,7 +3251,7 @@ const ParsedAction = (props) => {
selectedAction.selectedAuthentication = {};
selectedAction.authentication_id = "";
- for (var key in selectedAction.parameters) {
+ for (let [key,keyval] in selectedAction.parameters.entries()) {
//console.log(selectedAction.parameters[key])
if (selectedAction.parameters[key].configuration) {
selectedAction.parameters[key].value = "";
@@ -3568,7 +3568,7 @@ const ParsedAction = (props) => {
if (method.length > 0 && data.description !== undefined && data.description !== null && data.description.includes("http")) {
var extraUrl = ""
const descSplit = data.description.split("\n")
- for (var line in descSplit) {
+ for (let [line,lineval] in descSplit.entries()) {
if (descSplit[line].includes("http") && descSplit[line].includes("://")) {
const urlsplit = descSplit[line].split("/")
try {
@@ -3629,7 +3629,7 @@ const ParsedAction = (props) => {
renderInput={(params) => {
if (params.inputProps !== undefined && params.inputProps !== null && params.inputProps.value !== undefined && params.inputProps.value !== null) {
const prefixes = ["Post", "Put", "Patch"]
- for (var key in prefixes) {
+ for (let [key,keyval] in prefixes.entries()) {
if (params.inputProps.value.startsWith(prefixes[key])) {
params.inputProps.value = params.inputProps.value.replace(prefixes[key]+" ", "", -1)
if (params.inputProps.value.length > 1) {
diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx
index f21c9586..3e4097ab 100644
--- a/frontend/src/views/AngularWorkflow.jsx
+++ b/frontend/src/views/AngularWorkflow.jsx
@@ -134,7 +134,7 @@ import ParsedAction from "../components/ParsedAction.jsx";
import PaperComponent from "../components/PaperComponent.jsx"
import ExtraApps from "../components/ExtraApps.jsx"
import EditWorkflow from "../components/EditWorkflow.jsx"
-import AppStats from "../components/AppStats.jsx";
+// import AppStats from "../components/AppStats.jsx";
const surfaceColor = "#27292D";
const inputColor = "#383B40";
@@ -202,7 +202,7 @@ export function removeParam(key, sourceURL) {
if (queryString !== "") {
params_arr = queryString.split("&");
- for (var i = params_arr.length - 1; i >= 0; i -= 1) {
+ for (let i = params_arr.length - 1; i >= 0; i -= 1) {
param = params_arr[i].split("=")[0];
if (param === key) {
params_arr.splice(i, 1);
@@ -496,7 +496,7 @@ const AngularWorkflow = (defaultprops) => {
var baseSubflow = {}
const trigger = workflow.triggers[trigger_index];
if (trigger.parameters.length >= 3) {
- for (var key in trigger.parameters) {
+ for (let [key,keyval] in trigger.parameters.entries()) {
const param = trigger.parameters[key];
if (param.name === "workflow") {
@@ -877,13 +877,13 @@ const AngularWorkflow = (defaultprops) => {
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;
- }
+ if (responseJson.results !== null && responseJson.results.length > 0) {
+ for (let [key,keyval] in responseJson.results.entries()) {
+ 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");
@@ -950,32 +950,32 @@ const AngularWorkflow = (defaultprops) => {
}
}
- // 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);
- }
+ // FIXME - add outgoing nodes to executing
+ //const outgoingNodes = outgoingEdges.find().data().target
+ if (outgoingEdges.length > 0) {
+ for (let 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");
@@ -1028,13 +1028,13 @@ const AngularWorkflow = (defaultprops) => {
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");
- }
- }
+ var curelements = cy.elements();
+ for (let 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") {
@@ -1098,57 +1098,57 @@ const AngularWorkflow = (defaultprops) => {
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 cyelements = cy.elements();
+ var newActions = [];
+ var newTriggers = [];
+ var newBranches = [];
+ var newVBranches = [];
+ var newComments = [];
+ for (let cyelementsKey in cyelements) {
+ if (cyelements[cyelementsKey].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 type = cyelements[cyelementsKey].data()["type"];
+ if (type === undefined) {
+ if (
+ cyelements[cyelementsKey].data().source === undefined ||
+ cyelements[cyelementsKey].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,
- };
+ var parsedElement = {
+ id: cyelements[cyelementsKey].data().id,
+ source_id: cyelements[cyelementsKey].data().source,
+ destination_id: cyelements[cyelementsKey].data().target,
+ conditions: cyelements[cyelementsKey].data().conditions,
+ decorator: cyelements[cyelementsKey].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;
+ if (parsedElement.decorator) {
+ newVBranches.push(parsedElement);
+ } else {
+ newBranches.push(parsedElement);
+ }
+ } else {
+ if (type === "ACTION") {
+ const cyelement = cyelements[cyelementsKey].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();
- }
+ var curworkflowAction = useworkflow.actions.find(
+ (a) =>
+ a !== undefined &&
+ (a["id"] === elementid || a["_id"] === elementid)
+ );
+ if (curworkflowAction === undefined) {
+ curworkflowAction = cyelements[cyelementsKey].data();
+ }
- curworkflowAction.position = cyelements[key].position();
+ curworkflowAction.position = cyelements[cyelementsKey].position();
// workaround to fix some edgecases
if (
@@ -1158,27 +1158,27 @@ const AngularWorkflow = (defaultprops) => {
curworkflowAction.parameters = [];
}
- if (
- curworkflowAction.example === undefined ||
- curworkflowAction.example === "" ||
- curworkflowAction.example === null
- ) {
- if (cyelements[key].data().example !== undefined) {
- curworkflowAction.example = cyelements[key].data().example;
- }
- }
+ if (
+ curworkflowAction.example === undefined ||
+ curworkflowAction.example === "" ||
+ curworkflowAction.example === null
+ ) {
+ if (cyelements[cyelementsKey].data().example !== undefined) {
+ curworkflowAction.example = cyelements[cyelementsKey].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;
- }
+ // Cleans up OpenAPI items
+ var newparams = [];
+ for (let parametersKey in curworkflowAction.parameters) {
+ const thisitem = curworkflowAction.parameters[parametersKey];
+ if (thisitem.name.startsWith("${") && thisitem.name.endsWith("}")) {
+ continue;
+ }
newparams.push(thisitem);
}
@@ -1190,17 +1190,17 @@ const AngularWorkflow = (defaultprops) => {
useworkflow.triggers = [];
}
- var curworkflowTrigger = useworkflow.triggers.find(
- (a) => a.id === cyelements[key].data()["id"]
- );
- if (curworkflowTrigger === undefined) {
- curworkflowTrigger = cyelements[key].data();
- }
+ var curworkflowTrigger = useworkflow.triggers.find(
+ (a) => a.id === cyelements[cyelementsKey].data()["id"]
+ );
+ if (curworkflowTrigger === undefined) {
+ curworkflowTrigger = cyelements[cyelementsKey].data();
+ }
- curworkflowTrigger.position = cyelements[key].position();
- if (curworkflowTrigger.canConnect === false) {
- continue
- }
+ curworkflowTrigger.position = cyelements[cyelementsKey].position();
+ if (curworkflowTrigger.canConnect === false) {
+ continue
+ }
newTriggers.push(curworkflowTrigger);
} else if (type === "COMMENT") {
@@ -1208,17 +1208,17 @@ const AngularWorkflow = (defaultprops) => {
useworkflow.comments = [];
}
- var curworkflowComment = useworkflow.comments.find(
- (a) => a.id === cyelements[key].data()["id"]
- )
+ var curworkflowComment = useworkflow.comments.find(
+ (a) => a.id === cyelements[cyelementsKey].data()["id"]
+ )
- if (curworkflowComment === undefined) {
- curworkflowComment = cyelements[key].data();
- try {
- curworkflowComment.position.x = parseInt(curworkflowComment.position.x)
- } catch (e) {
- console.log("Failed to parse position Y of comment: ", curworkflowComment.position.x)
- }
+ if (curworkflowComment === undefined) {
+ curworkflowComment = cyelements[cyelementsKey].data();
+ try {
+ curworkflowComment.position.x = parseInt(curworkflowComment.position.x)
+ } catch (e) {
+ console.log("Failed to parse position Y of comment: ", curworkflowComment.position.x)
+ }
try {
curworkflowComment.position.y = parseInt(curworkflowComment.position.y)
@@ -1241,8 +1241,8 @@ const AngularWorkflow = (defaultprops) => {
curworkflowComment.width = 200
}
- curworkflowComment.position = cyelements[key].position();
- //console.log(curworkflowComment)
+ curworkflowComment.position = cyelements[cyelementsKey].position();
+ //console.log(curworkflowComment)
newComments.push(curworkflowComment);
} else {
@@ -1333,23 +1333,23 @@ const AngularWorkflow = (defaultprops) => {
workflow.isValid = true;
workflow.is_valid = true;
- const cyelements = cy.elements();
+ const cyelements = cy.elements();
+
+ for (let i = 0; i < cyelements.length; i++) {
+ //cyelements[i].removeStyle();
+ cyelements[i].data().is_valid = true;
+ cyelements[i].data().errors = [];
+ }
- for (var i = 0; i < cyelements.length; i++) {
- //cyelements[i].removeStyle();
- cyelements[i].data().is_valid = true;
- cyelements[i].data().errors = [];
- }
+ for (let [key,keyval] in workflow.actions.entries()) {
+ workflow.actions[key].is_valid = true;
+ workflow.actions[key].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]);
- }
+ for (let [key,keyval] in workflow.errors.entries()) {
+ //alert.info(workflow.errors[key]);
+ }
setWorkflow(workflow);
}
@@ -1373,7 +1373,7 @@ const AngularWorkflow = (defaultprops) => {
var firstnode = cy.getElementById(workflow.start);
if (firstnode.length === 0) {
var found = false;
- for (var key in workflow.actions) {
+ for (let [key,keyval] in workflow.actions.entries()) {
if (workflow.actions[key].isStartNode) {
console.log("Updating startnode");
workflow.start = workflow.actions[key].id;
@@ -1419,10 +1419,10 @@ const AngularWorkflow = (defaultprops) => {
setExecutionRequest({})
stop()
- var curelements = cy.elements();
- for (var i = 0; i < curelements.length; i++) {
- curelements[i].addClass("not-executing-highlight");
- }
+ var curelements = cy.elements();
+ for (let i = 0; i < curelements.length; i++) {
+ curelements[i].addClass("not-executing-highlight");
+ }
const data = { execution_argument: executionArgument, start: startNode };
fetch(
@@ -1452,14 +1452,14 @@ const AngularWorkflow = (defaultprops) => {
setExecutionRequestStarted(false);
stop();
- for (var i = 0; i < curelements.length; i++) {
- curelements[i].removeClass("not-executing-highlight");
- }
- return;
- } else {
- setExecutionRunning(true);
- setExecutionRequestStarted(false);
- }
+ for (let i = 0; i < curelements.length; i++) {
+ curelements[i].removeClass("not-executing-highlight");
+ }
+ return;
+ } else {
+ setExecutionRunning(true);
+ setExecutionRequestStarted(false);
+ }
if (
responseJson.execution_id === "" ||
@@ -1473,11 +1473,11 @@ const AngularWorkflow = (defaultprops) => {
setExecutionRequestStarted(false);
stop();
- for (var i = 0; i < curelements.length; i++) {
- curelements[i].removeClass("not-executing-highlight");
- }
- return;
- }
+ for (let i = 0; i < curelements.length; i++) {
+ curelements[i].removeClass("not-executing-highlight");
+ }
+ return;
+ }
setExecutionRequest({
execution_id: responseJson.execution_id,
@@ -1520,7 +1520,8 @@ const AngularWorkflow = (defaultprops) => {
.then((responseJson) => {
if (responseJson.success) {
var newauth = [];
- for (var key in responseJson.data) {
+ console.log("App auth: ", responseJson.data);
+ for (let [key,keyval] in responseJson.data.entries()) {
if (responseJson.data[key].defined === false) {
continue;
}
@@ -1546,11 +1547,11 @@ const AngularWorkflow = (defaultprops) => {
var tmpAuth = JSON.parse(JSON.stringify(responseJson.data));
var latest = 0;
- for (var key in tmpAuth) {
+ for (let [key,keyval] in tmpAuth.entries()) {
var item = tmpAuth[key];
const newfields = {};
- for (var filterkey in item.fields) {
+ for (let [filterkey, filterkeyval] in item.fields.entries()) {
newfields[item.fields[filterkey].key] =
item.fields[filterkey].value;
}
@@ -1564,7 +1565,7 @@ const AngularWorkflow = (defaultprops) => {
latest = item.edited;
selectedAction.selectedAuthentication = item;
- for (var key in workflow.actions) {
+ for (let [key,keyval] in workflow.actions.entries()) {
if (workflow.actions[key].app_name === selectedApp.name) {
workflow.actions[key].selectedAuthentication = item;
workflow.actions[key].authentication_id = item.id;
@@ -1813,28 +1814,28 @@ const AngularWorkflow = (defaultprops) => {
getUserProfile(responseJson.id, false)
}
- //{appGroup.map((data, index) => {
- //const [appGroup, setAppGroup] = React.useState([]);
- var appsFound = []
- for (var key in responseJson.actions) {
- const parsedAction = responseJson.actions[key]
- if (parsedAction.large_image === undefined || parsedAction.large_image === null || parsedAction.large_image === "") {
- continue
- }
- if (appsFound.findIndex(data => data.app_name === parsedAction.app_name) < 0) {
- appsFound.push(parsedAction)
- }
- }
+ //{appGroup.map((data, index) => {
+ //const [appGroup, setAppGroup] = React.useState([]);
+ var appsFound = []
+ for (let [key,keyval] in responseJson.actions.entries()) {
+ const parsedAction = responseJson.actions[key]
+ if (parsedAction.large_image === undefined || parsedAction.large_image === null || parsedAction.large_image === "") {
+ continue
+ }
+ if (appsFound.findIndex(data => data.app_name === parsedAction.app_name) < 0){
+ appsFound.push(parsedAction)
+ }
+ }
setAppGroup(appsFound)
- appsFound = []
- for (var key in responseJson.triggers) {
- const parsedAction = responseJson.triggers[key]
- if (appsFound.findIndex(data => data.app_name === parsedAction.app_name) < 0) {
- appsFound.push(parsedAction)
- }
- }
+ appsFound = []
+ for (let [key,keyval] in responseJson.triggers.entries()) {
+ const parsedAction = responseJson.triggers[key]
+ if (appsFound.findIndex(data => data.app_name === parsedAction.app_name) < 0){
+ appsFound.push(parsedAction)
+ }
+ }
setTriggerGroup(appsFound)
} else {
@@ -2236,10 +2237,11 @@ const AngularWorkflow = (defaultprops) => {
return;
}
- const connected = event.target.connectedEdges().jsons()
- for (var key in connected) {
- const edge = connected[key]
- //console.log("EDGE:", edge)
+ const connected = event.target.connectedEdges().jsons()
+ if (connected.length > 0 && connected !== undefined) {
+ for (let [key,keyval] in connected.entries()) {
+ const edge = connected[key]
+ //console.log("EDGE:", edge)
//const edge = edgeBase.json()
@@ -2251,11 +2253,12 @@ const AngularWorkflow = (defaultprops) => {
const edgeCurve = calculateEdgeCurve(sourcenode.position(), destinationnode.position())
const currentedge = cy.getElementById(edge.data.id)
- if (currentedge !== undefined && currentedge !== null) {
- currentedge.style('control-point-distance', edgeCurve.distance)
- currentedge.style('control-point-weight', edgeCurve.weight)
- }
- }
+ if (currentedge !== undefined && currentedge !== null) {
+ currentedge.style('control-point-distance', edgeCurve.distance)
+ currentedge.style('control-point-weight', edgeCurve.weight)
+ }
+ }
+ }
if (styledElements.length === 1) {
console.log(
@@ -2334,7 +2337,7 @@ const AngularWorkflow = (defaultprops) => {
) {
const allNodes = cy.nodes().jsons();
var found = false;
- for (var key in allNodes) {
+ for (let [key,keyval] in allNodes.entries()) {
const currentNode = allNodes[key];
if (
currentNode.data.attachedTo === nodedata.id &&
@@ -2401,8 +2404,8 @@ const AngularWorkflow = (defaultprops) => {
if (nodedata.app_name !== undefined) {
const allNodes = cy.nodes().jsons();
- for (var key in allNodes) {
- const currentNode = allNodes[key];
+ for (var key_ in allNodes) {
+ const currentNode = allNodes[key_];
if (currentNode.data.attachedTo === nodedata.id) {
cy.getElementById(currentNode.data.id).remove();
}
@@ -2484,8 +2487,8 @@ const AngularWorkflow = (defaultprops) => {
elementMouseIsOver.style.border = newBorder;
console.log("STYLED: ", styledElements);
- for (var key in styledElements) {
- const curElement = document.getElementById(styledElements[key]);
+ for (var styledElementsKey in styledElements) {
+ const curElement = document.getElementById(styledElements[styledElementsKey]);
if (curElement !== null && curElement !== undefined) {
curElement.style.border = curElement.style.original_border;
}
@@ -2497,8 +2500,8 @@ const AngularWorkflow = (defaultprops) => {
elementMouseIsOver.id === "cytoscape_view" ||
elementMouseIsOver.id === ""
) {
- for (var key in styledElements) {
- const curElement = document.getElementById(styledElements[key]);
+ for (var index in styledElements) {
+ const curElement = document.getElementById(styledElements[index]);
if (curElement !== null && curElement !== undefined) {
curElement.style.border = curElement.style.original_border;
}
@@ -2639,10 +2642,10 @@ const AngularWorkflow = (defaultprops) => {
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;
+ for (var sourceBranchesKey in sourcebranches) {
+ var newbranch = JSON.parse(JSON.stringify(sourcebranches[sourceBranchesKey]));
+ newbranch.id = uuidv4();
+ newbranch.source_id = newNodeData.id;
newbranch._id = newbranch.id;
newbranch.source = newbranch.source_id;
@@ -2653,12 +2656,12 @@ const AngularWorkflow = (defaultprops) => {
});
}
- for (var key in destinationbranches) {
- var newbranch = JSON.parse(
- JSON.stringify(destinationbranches[key])
- );
- newbranch.id = uuidv4();
- newbranch.destination_id = newNodeData.id;
+ for (var destinationBranchesKey in destinationbranches) {
+ var newbranch = JSON.parse(
+ JSON.stringify(destinationbranches[destinationBranchesKey])
+ );
+ newbranch.id = uuidv4();
+ newbranch.destination_id = newNodeData.id;
newbranch._id = newbranch.id;
newbranch.source = newbranch.source_id;
@@ -2731,25 +2734,25 @@ const AngularWorkflow = (defaultprops) => {
const parsedname = curaction.name.replaceAll(" ", "_").toLowerCase()
console.log("FIND AN ACTION AMONG THE APPS THAT MATCHES NAME: ", parsedname)
- curaction.matching_actions = []
- for (var key in newapps) {
- for (var subkey in newapps[key].actions) {
- const tmpaction = newapps[key].actions[subkey]
- if (tmpaction.name.replaceAll(" ", "_").toLowerCase() === parsedname) {
- console.log("MATCH!: ", newapps[key])
- curaction.matching_actions.push({
- "app_name": newapps[key].name,
- "app_version": newapps[key].app_version,
- "app_id": newapps[key].id,
- "action": tmpaction,
- "large_image": newapps[key].large_image,
- "app_index": key,
- "action_index": subkey,
- })
- }
- }
- }
- }
+ curaction.matching_actions = []
+ for (var newAppskey in newapps) {
+ for (let actionsSubkey in newapps[newAppskey].actions) {
+ const tmpaction = newapps[newAppskey].actions[actionsSubkey]
+ if (tmpaction.name.replaceAll(" ", "_").toLowerCase() === parsedname) {
+ console.log("MATCH!: ", newapps[newAppskey])
+ curaction.matching_actions.push({
+ "app_name": newapps[newAppskey].name,
+ "app_version": newapps[newAppskey].app_version,
+ "app_id": newapps[newAppskey].id,
+ "action": tmpaction,
+ "large_image": newapps[newAppskey].large_image,
+ "app_index": newAppskey,
+ "action_index": actionsSubkey,
+ })
+ }
+ }
+ }
+ }
if (!curapp || curapp === undefined) {
console.log("APPS - couldn't find it: ", newapps)
@@ -2804,13 +2807,13 @@ const AngularWorkflow = (defaultprops) => {
const tmpAuth = JSON.parse(JSON.stringify(newAppAuth));
//var tmpAuth = newAppAuth
- for (var key in tmpAuth) {
- var item = tmpAuth[key];
+ for (var tmpAuthKey in tmpAuth) {
+ var item = tmpAuth[tmpAuthKey];
- const newfields = {};
- for (var filterkey in item.fields) {
- newfields[item.fields[filterkey].key] = item.fields[filterkey].value;
- }
+ const newfields = {};
+ for (var fieldFilterKey in item.fields) {
+ newfields[item.fields[fieldFilterKey].key] = item.fields[fieldFilterKey].value;
+ }
item.fields = newfields;
if (item.app.name === curapp.name) {
@@ -2835,34 +2838,34 @@ const AngularWorkflow = (defaultprops) => {
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];
- }
- }
- } else {
- console.log("Should check APP if it has the same params as ACTION")
- for (var key in curapp.actions) {
- const tmpaction = curapp.actions[key]
- if (tmpaction.name === curaction.name) {
- console.log("Found action - needs change?", tmpaction)
- if (tmpaction.parameters !== undefined && tmpaction.parameters !== null && tmpaction.parameters.length > 0) {
- curaction.parameters = JSON.parse(JSON.stringify(tmpaction.parameters))
- }
- break
- }
- }
- }
+ if (
+ curaction.parameters !== undefined &&
+ curaction.parameters !== null &&
+ curaction.parameters.length > 0
+ ) {
+ for (var curActionParamKey in curaction.parameters) {
+ if (
+ curaction.parameters[curActionParamKey].options !== undefined &&
+ curaction.parameters[curActionParamKey].options !== null &&
+ curaction.parameters[curActionParamKey].options.length > 0 &&
+ curaction.parameters[curActionParamKey].value === ""
+ ) {
+ curaction.parameters[curActionParamKey].value = curaction.parameters[curActionParamKey].options[0];
+ }
+ }
+ } else {
+ console.log("Should check APP if it has the same params as ACTION")
+ for (var actionKey in curapp.actions) {
+ const tmpaction = curapp.actions[actionKey]
+ if (tmpaction.name === curaction.name) {
+ console.log("Found action - needs change?", tmpaction)
+ if (tmpaction.parameters !== undefined && tmpaction.parameters !== null && tmpaction.parameters.length > 0) {
+ curaction.parameters = JSON.parse(JSON.stringify(tmpaction.parameters))
+ }
+ break
+ }
+ }
+ }
console.log("ACTION CLICK: ", curaction)
setSelectedApp(curapp);
@@ -3000,7 +3003,7 @@ const AngularWorkflow = (defaultprops) => {
var exampledata = item.example === undefined ? "" : item.example;
if (workflowExecutions.length > 0) {
// Look for the ID
- for (var key in workflowExecutions) {
+ for (let [key,keyval] in workflowExecutions.entries()) {
if (
workflowExecutions[key].results === undefined ||
workflowExecutions[key].results === null
@@ -3147,7 +3150,7 @@ const AngularWorkflow = (defaultprops) => {
selectedkey = `.${key}`;
}
- for (var subitem in value) {
+ for (let [subitem,subitemval] in value.entries()) {
toreturn = GetParamMatch(
paramname,
value[subitem],
@@ -3198,8 +3201,8 @@ const AngularWorkflow = (defaultprops) => {
// 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];
+ for (var dstdataParamKey in dstdata.parameters) {
+ const param = dstdata.parameters[dstdataParamKey];
// Skip authentication params
if (param.configuration) {
continue
@@ -3213,17 +3216,17 @@ const AngularWorkflow = (defaultprops) => {
const foundresult = GetParamMatch(paramname, exampledata, "");
if (foundresult.length > 0) {
- console.log("FOUND ReS for field: ", dstdata.parameters[paramkey].name, foundresult)
- if (dstdata.parameters[paramkey].value.length === 0) {
- dstdata.parameters[paramkey].value = `$${parentlabel}${foundresult}`;
- dstdata.parameters[paramkey].autocompleted = true
+ console.log("FOUND ReS for field: ", dstdata.parameters[dstdataParamKey].name, foundresult)
+ if (dstdata.parameters[dstdataParamKey].value.length === 0) {
+ dstdata.parameters[dstdataParamKey].value = `$${parentlabel}${foundresult}`;
+ dstdata.parameters[dstdataParamKey].autocompleted = true
}
}
}
var parents = getParents(dstdata);
if (parents.length > 1) {
- for (var key in parents) {
+ for (let [key,keyval] in parents.entries()) {
const item = parents[key];
if (item.label === "Execution Argument") {
continue;
@@ -3234,7 +3237,7 @@ const AngularWorkflow = (defaultprops) => {
? ""
: item.label.toLowerCase().trim().replaceAll(" ", "_");
exampledata = GetExampleResult(item);
- for (var paramkey in dstdata.parameters) {
+ for (let [paramkey,paramkeyval] in dstdata.parameters.entries()) {
const param = dstdata.parameters[paramkey];
// Skip authentication params
if (param.configuration) {
@@ -3384,7 +3387,7 @@ const AngularWorkflow = (defaultprops) => {
// dest == dest && source == source
// backend: check all children? to stop recursion
var found = false;
- for (var key in workflow.branches) {
+ for (let [key,keyval] in workflow.branches.entries()) {
if (
workflow.branches[key].destination_id === edge.source &&
workflow.branches[key].source_id === edge.target
@@ -3519,7 +3522,7 @@ const AngularWorkflow = (defaultprops) => {
}
// Remove bad startnode
- for (var key in workflow.actions) {
+ for (let [key,keyval] in workflow.actions.entries()) {
const action = workflow.actions[key];
if (action.isStartNode && workflow.start !== action.id) {
action.isStartNode = false;
@@ -3583,7 +3586,7 @@ const AngularWorkflow = (defaultprops) => {
) {
var newparameters = [];
- for (var subkey in nodedata.parameters) {
+ for (let [subkey,subkeyval] in nodedata.parameters.entries()) {
var newparam = JSON.parse(
JSON.stringify(nodedata.parameters[subkey])
);
@@ -3676,11 +3679,11 @@ const AngularWorkflow = (defaultprops) => {
// Check if the source is trigger and can start
console.log("Removed: ", edge.data())
const allNodes = cy.nodes().jsons()
- for (var key in allNodes) {
- const curnode = allNodes[key]
- if (curnode.data.type !== "TRIGGER") {
- continue
- }
+ for (let [key,keyval] in allNodes.entries()) {
+ const curnode = allNodes[key]
+ if (curnode.data.type !== "TRIGGER") {
+ continue
+ }
if (curnode.data.id === edge.data("source")) {
console.log("Found matching trigger source: ", curnode)
@@ -3768,8 +3771,8 @@ const AngularWorkflow = (defaultprops) => {
if (data.app_name !== undefined) {
const allNodes = cy.nodes().jsons();
- for (var key in allNodes) {
- const currentNode = allNodes[key];
+ for (let allNodesKey in allNodes) {
+ const currentNode = allNodes[allNodesKey];
if (currentNode.data.attachedTo === data.id) {
cy.getElementById(currentNode.data.id).remove();
}
@@ -3948,7 +3951,7 @@ const AngularWorkflow = (defaultprops) => {
const parsedjson = JSON.parse(clipboard);
//console.log("Parsed: ", parsedjson)
- for (var key in parsedjson) {
+ for (let [key,keyval] in parsedjson.entries()) {
const item = parsedjson[key];
console.log("Adding: ", item);
item.data.id = uuidv4()
@@ -3996,7 +3999,7 @@ const AngularWorkflow = (defaultprops) => {
.then((responseJson) => {
var found = false;
var showEnvCnt = 0;
- for (var key in responseJson) {
+ for (let [key,keyval] in responseJson.entries()) {
if (responseJson[key].default && !found) {
setDefaultEnvironmentIndex(key);
found = true;
@@ -4012,7 +4015,7 @@ const AngularWorkflow = (defaultprops) => {
}
if (!found) {
- for (var key in responseJson) {
+ for (let [key,keyval] in responseJson.entries()) {
if (!responseJson[key].archived) {
setDefaultEnvironmentIndex(key);
break;
@@ -4051,11 +4054,18 @@ const AngularWorkflow = (defaultprops) => {
const animationDuration = 150;
const onNodeHoverOut = (event) => {
+ // console.log("Hover out: ", event.target.data());
+
const nodedata = event.target.data();
+ // console.log("nodedata", nodedata);
+ // console.log("nodedata.app_name: ", nodedata.app_name);
if (nodedata.app_name !== undefined) {
+
const allNodes = cy.nodes().jsons();
- for (var key in allNodes) {
- const currentNode = allNodes[key];
+ // console.log("allNodes: ", allNodes)
+ for (var nodekey in allNodes) {
+ const currentNode = allNodes[nodekey];
+ // console.log("Current node: ", currentNode);
if (
currentNode.data.isButton &&
currentNode.data.attachedTo !== nodedata.id
@@ -4332,8 +4342,9 @@ const AngularWorkflow = (defaultprops) => {
const allNodes = cy.nodes().jsons();
var found = false;
- for (var key in allNodes) {
- const currentNode = allNodes[key];
+ for (var _key in allNodes) {
+ const currentNode = allNodes[_key];
+ // console.log("CURRENT NODE: ", currentNode)
if (
(currentNode.data.isButton || currentNode.data.isSuggestion) &&
currentNode.data.attachedTo !== nodedata.id
@@ -4794,8 +4805,8 @@ const AngularWorkflow = (defaultprops) => {
// Verifies if a branch is valid and skips others
var newedges = [];
- for (var key in edges) {
- var item = edges[key];
+ for (var edgeKey in edges) {
+ var item = edges[edgeKey];
if (item.data === undefined) {
continue;
}
@@ -4819,6 +4830,7 @@ const AngularWorkflow = (defaultprops) => {
const removeNode = (nodeId) => {
const selectedNode = cy.getElementById(nodeId);
+ // console.log("selected node in removenode", selectedNode);
if (selectedNode.data() === undefined) {
console.log("No node to remove")
return;
@@ -5140,16 +5152,18 @@ const AngularWorkflow = (defaultprops) => {
return;
}
- var mappedStartnode = ""
- const alledges = cy.edges().jsons()
- for (var key in alledges) {
- const tmp = alledges[key]
- console.log("TMP: ", tmp, tmp.data.source)
- if (tmp.data.source === trigger.id) {
- mappedStartnode = tmp.data.target
- break
- }
- }
+ var mappedStartnode = ""
+ const alledges = cy.edges().jsons()
+ if (alledges !== undefined && alledges !== null && alledges.length > 0) {
+ for (let [key,keyval] in alledges.entries()) {
+ const tmp = alledges[key]
+ console.log("TMP: ", tmp, tmp.data.source)
+ if (tmp.data.source === trigger.id) {
+ mappedStartnode = tmp.data.target
+ break
+ }
+ }
+ }
alert.info("Creating schedule with name " + trigger.name);
const data = {
@@ -5920,11 +5934,11 @@ const AngularWorkflow = (defaultprops) => {
}
const tmpAuth = JSON.parse(JSON.stringify(appAuthentication));
- for (var key in tmpAuth) {
+ for (let [key,keyval] in tmpAuth.entries()) {
var item = tmpAuth[key];
const newfields = {};
- for (var filterkey in item.fields) {
+ for (let [filterkey,filterkeyval] in item.fields.entries()) {
newfields[item.fields[filterkey].key] = item.fields[filterkey].value;
}
@@ -5950,7 +5964,7 @@ const AngularWorkflow = (defaultprops) => {
authenticationOptions !== null &&
authenticationOptions.length > 0
) {
- for (var key in authenticationOptions) {
+ for (let [key,keyval] in authenticationOptions.entries()) {
const option = authenticationOptions[key];
if (option.active && newAppData.authentication_id === "") {
@@ -6362,7 +6376,7 @@ const AngularWorkflow = (defaultprops) => {
if (newApps.length === 0) {
const searchvalue = value.trim().toLowerCase();
newApps = allApps.filter((app) => {
- for (var key in app.actions) {
+ for (let [key,keyval] in app.actions.entries()) {
const inneraction = app.actions[key];
if (inneraction.name.toLowerCase().includes(searchvalue)) {
return true;
@@ -6727,7 +6741,7 @@ const AngularWorkflow = (defaultprops) => {
var highest = "";
const allitems = workflow.actions.concat(workflow.triggers);
- for (var key in allitems) {
+ for (let [key,keyval] in allitems.entries()) {
const item = allitems[key];
if (
item.app_name === appName &&
@@ -6785,15 +6799,15 @@ const AngularWorkflow = (defaultprops) => {
newSelectedAction.is_valid = true;
//console.log(newSelectedAction)
- // Simmple action swap autocompleter
- if (oldaction.parameters !== undefined && newSelectedAction.parameters !== undefined && oldaction.id === newSelectedAction.id) {
- var fileid_found = false
- for (var paramkey in oldaction.parameters) {
- const param = oldaction.parameters[paramkey];
-
- if (param.name === "file_id") {
- fileid_found = true
- }
+ // Simmple action swap autocompleter
+ if (oldaction.parameters !== undefined && newSelectedAction.parameters !== undefined && oldaction.id === newSelectedAction.id) {
+ var fileid_found = false
+ for (let [paramkey,paramkeyval] in oldaction.parameters.entries()) {
+ const param = oldaction.parameters[paramkey];
+
+ if (param.name === "file_id") {
+ fileid_found = true
+ }
if (param.value === null || param.value === undefined || param.value.length === 0) {
continue
@@ -6902,7 +6916,7 @@ const AngularWorkflow = (defaultprops) => {
// FIXME - should change icon-node (descriptor) as well
const allNodes = cy.nodes().jsons();
- for (var key in allNodes) {
+ for (let [key,keyval] in allNodes.entries()) {
const currentNode = allNodes[key];
if (
currentNode.data.attachedTo === oldaction.id &&
@@ -6994,7 +7008,7 @@ const AngularWorkflow = (defaultprops) => {
var iterations = 0;
var maxiter = 10;
while (true) {
- for (var key in allkeys) {
+ for (let [key,keyval] in allkeys.entries()) {
var currentnode = cy.getElementById(allkeys[key]);
if (currentnode === undefined || currentnode === null) {
continue;
@@ -7022,7 +7036,7 @@ const AngularWorkflow = (defaultprops) => {
continue;
}
- for (var i = 0; i < incomingEdges.length; i++) {
+ for (let i = 0; i < incomingEdges.length; i++) {
var tmp = incomingEdges[i];
if (tmp.data("decorator")) {
continue
@@ -7150,7 +7164,7 @@ const AngularWorkflow = (defaultprops) => {
workflow.triggers[selectedTriggerIndex].parameters[2].value.split(",");
console.log(splitItems);
if (splitItems.includes(value)) {
- for (var i = 0; i < splitItems.length; i++) {
+ for (let i = 0; i < splitItems.length; i++) {
if (splitItems[i] === value) {
splitItems.splice(i, 1);
}
@@ -7159,7 +7173,7 @@ const AngularWorkflow = (defaultprops) => {
splitItems.push(value);
}
- for (var i = 0; i < splitItems.length; i++) {
+ for (let i = 0; i < splitItems.length; i++) {
if (splitItems[i] === "") {
splitItems.splice(i, 1);
}
@@ -7241,7 +7255,7 @@ const AngularWorkflow = (defaultprops) => {
workflow.workflow_variables !== undefined &&
workflow.workflow_variables.length > 0
) {
- for (var key in workflow.workflow_variables) {
+ for (let [key,keyval] in workflow.workflow_variables.entries()) {
const item = workflow.workflow_variables[key];
actionlist.push({
type: "workflow_variable",
@@ -7260,7 +7274,7 @@ const AngularWorkflow = (defaultprops) => {
workflow.execution_variables !== undefined &&
workflow.execution_variables.length > 0
) {
- for (var key in workflow.execution_variables) {
+ for (let [key,keyval] in workflow.execution_variables.entries()) {
const item = workflow.execution_variables[key];
actionlist.push({
type: "execution_variable",
@@ -7276,7 +7290,7 @@ const AngularWorkflow = (defaultprops) => {
const destAction = cy.getElementById(selectedEdge.target);
var parents = getParents(destAction.data());
if (parents.length > 1) {
- for (var key in parents) {
+ for (let [key,keyval] in parents.entries()) {
const item = parents[key];
if (item.label === "Execution Argument") {
continue;
@@ -7452,7 +7466,7 @@ const AngularWorkflow = (defaultprops) => {
workflow.triggers !== null &&
workflow.triggers.length > 0
) {
- for (var key in workflow.triggers) {
+ for (let [key,keyval] in workflow.triggers.entries()) {
const item = workflow.triggers[key];
var node = cy.getElementById(item.id);
@@ -7469,8 +7483,10 @@ const AngularWorkflow = (defaultprops) => {
const handleActionHover = (inside, actionId) => {
var node = cy.getElementById(actionId);
+ console.log("Hovering over action: " + actionId)
if (node.length > 0) {
if (inside) {
+ console.log("Hovering over action: " + actionId)
node.addClass("shuffle-hover-highlight");
} else {
node.removeClass("shuffle-hover-highlight");
@@ -8796,7 +8812,7 @@ const AngularWorkflow = (defaultprops) => {
workflow.workflow_variables !== undefined &&
workflow.workflow_variables.length > 0
) {
- for (var key in workflow.workflow_variables) {
+ for (let [key,keyval] in workflow.workflow_variables.entries()) {
const item = workflow.workflow_variables[key];
actionlist.push({
type: "workflow_variable",
@@ -8815,7 +8831,7 @@ const AngularWorkflow = (defaultprops) => {
workflow.execution_variables !== undefined &&
workflow.execution_variables.length > 0
) {
- for (var key in workflow.execution_variables) {
+ for (let [key,keyval] in workflow.execution_variables.entries()) {
const item = workflow.execution_variables[key];
actionlist.push({
type: "execution_variable",
@@ -8830,7 +8846,7 @@ const AngularWorkflow = (defaultprops) => {
var parents = getParents(selectedTrigger);
if (parents.length > 1) {
- for (var key in parents) {
+ for (let [key,keyval] in parents.entries()) {
const item = parents[key];
if (item.label === "Execution Argument") {
continue;
@@ -8840,7 +8856,7 @@ const AngularWorkflow = (defaultprops) => {
// Find previous execution and their variables
if (workflowExecutions.length > 0) {
// Look for the ID
- for (var key in workflowExecutions) {
+ for (let [key,keyval] in workflowExecutions.entries()) {
if (
workflowExecutions[key].results === undefined ||
workflowExecutions[key].results === null
@@ -8969,7 +8985,7 @@ const AngularWorkflow = (defaultprops) => {
workflow.triggers !== null &&
workflow.triggers.length > 0
) {
- for (var key in workflow.triggers) {
+ for (let [key,keyval] in workflow.triggers.entries()) {
const item = workflow.triggers[key];
if (cy !== undefined) {
@@ -10553,8 +10569,8 @@ const AngularWorkflow = (defaultprops) => {
splitter
)
- console.log("Starting mail sub: ", workflow.triggers[selectedTriggerIndex].parameters[0].value, splitItem);
- for (var key in splitItem) {
+ console.log("Starting mail sub: ", workflow.triggers[selectedTriggerIndex].parameters[0].value, splitItem);
+ for (let [key,keyval] in splitItem.entries()) {
const item = splitItem[key];
const curfolder = triggerFolders.find((a) => a.displayName === item);
if (curfolder === undefined) {
@@ -12709,7 +12725,7 @@ const AngularWorkflow = (defaultprops) => {
}
to_be_copied = "$" + base_node_name.toLowerCase().replaceAll(" ", "_");
- for (var key in copy.namespace) {
+ for (let [key,keyval] in copy.namespace.entries()) {
if (copy.namespace[key].includes("Results for")) {
continue;
}
@@ -12984,7 +13000,7 @@ const AngularWorkflow = (defaultprops) => {
data.workflow.actions !== null
? data.workflow.actions.length
: 0;
- for (var key in data.workflow.triggers) {
+ for (let [key,keyval] in data.workflow.triggers.entries()) {
const trigger = data.workflow.triggers[key];
if (
(trigger.app_name === "User Input" &&
@@ -13030,17 +13046,17 @@ const AngularWorkflow = (defaultprops) => {
var checkStarted = false
if (isCloud && data.results !== undefined && data.results !== null && data.results.length > 0) {
- if (data.execution_argument !== undefined && data.execution_argument !== null && data.execution_argument.includes("too large")) {
- setExecutionData({});
- checkStarted = true
- start();
- setExecutionRunning(true);
- setExecutionRequestStarted(false);
- } else {
- for (var key in data.results) {
- if (data.results[key].status !== "SUCCESS") {
- continue
- }
+ if (data.execution_argument !== undefined && data.execution_argument !== null && data.execution_argument.includes("too large")) {
+ setExecutionData({});
+ checkStarted = true
+ start();
+ setExecutionRunning(true);
+ setExecutionRequestStarted(false);
+ } else {
+ for (let [key,keyval] in data.results.entries()) {
+ if (data.results[key].status !== "SUCCESS") {
+ continue
+ }
if (data.results[key].result.includes("too large")) {
setExecutionData({});
@@ -13516,17 +13532,17 @@ const AngularWorkflow = (defaultprops) => {
}
- var similarActionsView = null
- if (data.similar_actions !== undefined && data.similar_actions !== null) {
- var minimumMatch = 85
- var matching_executions = []
- for (var k in data.similar_actions) {
- if (data.similar_actions.hasOwnProperty(k)) {
- if (data.similar_actions[k].similarity > minimumMatch) {
- matching_executions.push(data.similar_actions[k].execution_id)
- }
- }
- }
+ var similarActionsView = null
+ if (data.similar_actions !== undefined && data.similar_actions !== null) {
+ var minimumMatch = 85
+ var matching_executions = []
+ for (let [k,kval] in data.similar_actions.entries()){
+ if (data.similar_actions.hasOwnProperty(k)) {
+ if (data.similar_actions[k].similarity > minimumMatch) {
+ matching_executions.push(data.similar_actions[k].execution_id)
+ }
+ }
+ }
if (matching_executions.length !== 0) {
var parsed_url = matching_executions.join(",")
@@ -13829,50 +13845,50 @@ const AngularWorkflow = (defaultprops) => {
var draggingDisabled = false;
const codePopoutModal = !codeModalOpen ? null : (
-
-
-
- {
- 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
- );
+
+
+
+ {
+ e.preventDefault();
+ for (let [key,keyval] in workflowExecutions.entries()) {
+ const execution = workflowExecutions[key];
+ const result = execution.results.find(
+ (data) =>
+ data.status === "SUCCESS" &&
+ data.action.id === selectedResult.action.id
+ );
if (result !== undefined) {
const oldstartnode = cy.getElementById(selectedResult.action.id);
@@ -13883,39 +13899,39 @@ const AngularWorkflow = (defaultprops) => {
}
}
- setSelectedResult(result);
- setUpdate(Math.random());
- break;
+ 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"
- );
+ {
+ e.preventDefault();
+ for (let [key,keyval] in workflowExecutions.entries()) {
+ 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) {
const oldstartnode = cy.getElementById(selectedResult.action.id);
@@ -14581,7 +14597,7 @@ const AngularWorkflow = (defaultprops) => {
authenticationOption.app.actions = [];
- for (var key in selectedApp.authentication.parameters) {
+ for (let [key,keyval] in selectedApp.authentication.parameters.entries()) {
if (
authenticationOption.fields[
selectedApp.authentication.parameters[key].name
@@ -14600,7 +14616,7 @@ const AngularWorkflow = (defaultprops) => {
// Automatically mapping fields that already exist (predefined).
// Warning if fields are NOT filled
- for (var key in selectedApp.authentication.parameters) {
+ for (let [key,keyval] in selectedApp.authentication.parameters.entries()) {
if (
authenticationOption.fields[
selectedApp.authentication.parameters[key].name
@@ -14649,7 +14665,7 @@ const AngularWorkflow = (defaultprops) => {
var newAuthOption = JSON.parse(JSON.stringify(authenticationOption));
var newFields = [];
- for (const key in newAuthOption.fields) {
+ for (let [key,keyval] in newAuthOption.fields.entries()) {
const value = newAuthOption.fields[key];
newFields.push({
key: key,
diff --git a/frontend/src/views/AppCreator.jsx b/frontend/src/views/AppCreator.jsx
index 81538b8f..a8da7482 100644
--- a/frontend/src/views/AppCreator.jsx
+++ b/frontend/src/views/AppCreator.jsx
@@ -456,7 +456,7 @@ const AppCreator = (defaultprops) => {
}
var newitem = data;
- for (var key in paramsplit) {
+ for (let [key,keyval] in paramsplit.entries()) {
var tmpparam = paramsplit[key];
if (tmpparam === "#") {
continue;
@@ -579,7 +579,7 @@ const AppCreator = (defaultprops) => {
console.log("Tags: ", data.tags)
if (data.tags !== undefined && data.tags.length > 0) {
var newtags = [];
- for (var key in data.tags) {
+ for (let [key,keyval] in data.tags.entries()) {
if (data.tags[key].name.length > 50) {
console.log(
"Skipping tag because it's too long: ",
@@ -685,7 +685,7 @@ const AppCreator = (defaultprops) => {
const pathsplit = path.split("/");
var categoryindex = -1;
// Stupid way of finding a category/grouping
- for (var key in pathsplit) {
+ for (let [key,keyval] in pathsplit.entries()) {
if (pathsplit[key].includes("_shuffle_replace_")) {
const regex = /_shuffle_replace_\d/i;
//console.log("NEW: ",
@@ -750,9 +750,9 @@ const AppCreator = (defaultprops) => {
}
//console.log("Data: ", data)
- for (var subkey in methodvalue["requestBody"]["content"][
+ for (let [subkey,subkeyval] in methodvalue["requestBody"]["content"][
"application/json"
- ]["schema"]["required"]) {
+ ]["schema"]["required"].entries()) {
const tmpitem =
methodvalue["requestBody"]["content"][
"application/json"
@@ -771,7 +771,7 @@ const AppCreator = (defaultprops) => {
);
var newbody = {};
// Can handle default, required, description and type
- for (var propkey in retRef.properties) {
+ for (let [propkey,propkeyval] in retRef.properties.entries()) {
console.log("replace: ", propkey)
const parsedkey = propkey.replaceAll(" ", "_").toLowerCase();
@@ -810,9 +810,9 @@ const AppCreator = (defaultprops) => {
tmpobject[prop] = `\$\{${prop}\}`;
}
- for (var subkey in methodvalue["requestBody"]["content"][
+ for (let [subkey,subkeyval] in methodvalue["requestBody"]["content"][
"application/xml"
- ]["schema"]["required"]) {
+ ]["schema"]["required"].entries()) {
const tmpitem =
methodvalue["requestBody"]["content"][
"application/xml"
@@ -917,7 +917,7 @@ const AppCreator = (defaultprops) => {
parameter["type"] === "object"
) {
var newbody = {};
- for (var propkey in parameter.properties) {
+ for (let [propkey,propkeyval] in parameter.properties.entries()) {
console.log("propkey2: ", propkey)
const parsedkey = propkey.replaceAll(" ", "_").toLowerCase();
if (parameter.properties[propkey].type === undefined) {
@@ -1046,7 +1046,7 @@ const AppCreator = (defaultprops) => {
parameter["type"] === "object"
) {
var newbody = {};
- for (var propkey in parameter.properties) {
+ for (let [propkey,propkeyval] in parameter.properties.entries()) {
console.log("propkey3: ", propkey)
const parsedkey = propkey.replaceAll(" ", "_").toLowerCase();
if (
@@ -1135,7 +1135,7 @@ const AppCreator = (defaultprops) => {
parameter["type"] === "object"
) {
var newbody = {};
- for (var propkey in parameter.properties) {
+ for (let [propkey,propkeyval] in parameter.properties.entries()) {
console.log("propkey4: ", propkey)
const parsedkey = propkey.replaceAll(" ", "_").toLowerCase();
if (
@@ -1219,7 +1219,7 @@ const AppCreator = (defaultprops) => {
parameter["type"] === "object"
) {
var newbody = {};
- for (var propkey in parameter.properties) {
+ for (let [propkey,keyval] in parameter.properties.entries()) {
console.log("propkey5: ", propkey)
const parsedkey = propkey
.replaceAll(" ", "_")
@@ -1287,7 +1287,7 @@ const AppCreator = (defaultprops) => {
}
}
- for (var key in methodvalue.parameters) {
+ for (let [key,keyval] in methodvalue.parameters.entries()) {
const parameter = handleGetRef(methodvalue.parameters[key], data);
if (parameter.in === "query") {
var tmpaction = {
@@ -1334,7 +1334,7 @@ const AppCreator = (defaultprops) => {
if (Object.getOwnPropertyNames(wordlist).length === 0) {
for (let [newpath, pathvalue] of Object.entries(data.paths)) {
const newpathsplit = newpath.split("/");
- for (var key in newpathsplit) {
+ for (let [key,keyval] in newpathsplit.entries()) {
const pathitem = newpathsplit[key].toLowerCase();
if (wordlist[pathitem] === undefined) {
wordlist[pathitem] = 1;
@@ -1351,7 +1351,7 @@ const AppCreator = (defaultprops) => {
const urlsplit = path.split("/");
if (urlsplit.length > 0) {
var curname = "";
- for (var key in urlsplit) {
+ for (let [key,keyval] in urlsplit.entries()) {
var subpath = urlsplit[key];
if (wordlist[subpath] > 2 || subpath.length < 1) {
continue;
@@ -1413,7 +1413,7 @@ const AppCreator = (defaultprops) => {
const regex = /{\w+}/g;
const found = firstUrl.match(regex);
if (found !== null) {
- for (var key in found) {
+ for (let [key,keyval] in found.entries()) {
const item = found[key].slice(1, found[key].length - 1);
const foundVar = data.servers[0].variables[item];
if (foundVar["default"] !== undefined) {
@@ -1683,7 +1683,7 @@ const AppCreator = (defaultprops) => {
if (newWorkflowTags.length > 0) {
var newtags = [];
- for (var key in newWorkflowTags) {
+ for (let [key,keyval] in newWorkflowTags.entries()) {
newtags.push({ name: newWorkflowTags[key] });
}
@@ -1696,7 +1696,7 @@ const AppCreator = (defaultprops) => {
// Handles actions
var handledPaths = []
- for (var key in actions) {
+ for (let [key,keyval] in actions.entries()) {
var item = JSON.parse(JSON.stringify(actions[key]))
if (item.errors.length > 0) {
alert.error("Saving with error in action " + item.name);
@@ -1712,7 +1712,7 @@ const AppCreator = (defaultprops) => {
if (handledPaths.includes(pathjoin)) {
// Max 100 of same lol
- for (var i = 0; i < 100; i++) {
+ for (let i = 0; i < 100; i++) {
item.url = item.url+"_shuffle_replace_"+i
pathjoin = item.url+"_"+item.method.toLowerCase()
@@ -1822,7 +1822,7 @@ const AppCreator = (defaultprops) => {
if (item.queries.length > 0) {
var skipped = false;
var querynames = []
- for (var querykey in item.queries) {
+ for (let [querykey,querykeyval] in item.queries.entries()) {
const queryitem = item.queries[querykey];
if (queryitem === undefined || queryitem === null || queryitem.name === undefined || queryitem.name === null || queryitem.name === "") {
@@ -1923,7 +1923,7 @@ const AppCreator = (defaultprops) => {
//data.paths[item.url][item.method.toLowerCase()].parameters.push(newitem)
if (item.paths.length > 0) {
- for (querykey in item.paths) {
+ for (let [querykey,querykeyval] in item.paths.entries()) {
const queryitem = item.paths[querykey];
if (queryitem.toLowerCase() == "url") {
@@ -1958,7 +1958,7 @@ const AppCreator = (defaultprops) => {
const values = getCurrentPaths(item.url);
const paths = values[0];
- for (querykey in paths) {
+ for (let [querykey,querykeyval] in paths.entries()) {
const queryitem = paths[querykey];
newitem = {
in: "path",
@@ -1996,7 +1996,7 @@ const AppCreator = (defaultprops) => {
// but it's the only way we can properly support e.g. GraphQL
// with good examples
var newbody = ""
- for (var key in item.body) {
+ for (let [key,keyval] in item.body.entries()) {
if (item.body[key] === "$") {
if (key > 0) {
//console.log("Found: ", item.body[key-1])
@@ -2118,9 +2118,9 @@ const AppCreator = (defaultprops) => {
const required = false;
const headersSplit = item.headers.split("\n");
- for (var key in headersSplit) {
+ for (let [key,keyval] in headersSplit.entries()) {
const header = headersSplit[key];
- var key = "";
+ // var key = "";
var value = "";
if (header.length > 0 && header.includes("= ")) {
const headersplit = header.split("= ");
diff --git a/frontend/src/views/Faq.jsx b/frontend/src/views/Faq.jsx
new file mode 100644
index 00000000..42d24261
--- /dev/null
+++ b/frontend/src/views/Faq.jsx
@@ -0,0 +1,258 @@
+import React, {useState} from 'react';
+import {isMobile} from "react-device-detect";
+import {Link} from 'react-router-dom';
+
+import {Divider, List, ListItem, ListItemText, Card, CardContent, Grid, Typography, Button, ButtonGroup, FormControl, Dialog, DialogTitle, DialogActions, DialogContent, Tooltip} from '@material-ui/core';
+import {ExpandMore as ExpandMoreIcon, ExpandLess as ExpandLessIcon} from '@material-ui/icons';
+
+const hrefStyle = {
+ textDecoration: "none",
+ color: "#f85a3e"
+}
+
+
+/*
+ * More questions:
+ * What happens with IPv6 vs IPv6?
+ * How long can contracts be?
+ * Any discount? 20% with 1 year+
+ * How can we pay? Manual or not
+ * How is support handled?
+ * How big is the team EXACTLY?
+ * What are requirements for everything?
+ * What level of support does Fredrik/Shuffle provide to paying customers with enterprise license agreements?
+ * What’s their guaranteed response time? 2 hours, 4 hours, next business day? Support 365/24/7, or just weekdays?
+ * How can customers submit support requests? Email, phone, and/or web?
+ * Is there a support team, or is Fredrik the only support person right now?
+ * What’s the annual Shuffle release schedule / frequency? One major release once a year with minor releases quarterly?
+ * The ability to run our own, private instance of Shuffle in a public or private cloud, as well as on virtualized or bare metal, standalone/isolated servers is very important.
+ * We were wondering how the shuffle environment handles a playbook in production(workflow editing and testing phase) vs. in operations (playbook/workflow is operational in a SOC).
+ * Is Shuffle capable of pushing notifications/messages to REDPro if a playbook is Active, Inactive or in Error so it’s general status can be understood via the Playbook Library.
+ * Will cloud webhooks behave any differently from on premise webhooks if we are hosting our own cloud.
+ * If we are hosting on our own cloud and the cloud is not connected to the open internet, will there a be a work around for delivering app updates.
+ * What other maintenance and troubleshooting considerations should we be aware of in an isolated cloud environment
+ * Do you have any documentation for putting workflows into a github.
+ */
+
+
+export const pricingFaq = [
+ {
+ "question": "What currency are your prices in?",
+ "answers": [
+ "They are in US Dollars.",
+ ],
+ },
+ {
+ "question": "Do you offer discounts or free trials?",
+ "answers": [
+ "We offer free trials, and may offer discounts and features for testing in certain scenarios.",
+ ],
+ },
+ {
+ "question": "What payment methods do you offer?",
+ "answers": [
+ "We accept credit cards, Apple Pay, Google Pay and any other payment Stripe supports.",
+ ],
+ },
+ {
+ "question": "How can I switch to annual billing?",
+ "answers": [
+ "Contact us at Contact page!",
+ ],
+ },
+ {
+ "question": "When does my membership get activated?",
+ "answers": [
+ "As soon as the payment is finished, you should see more features available in the Admin view.",
+ ],
+ },
+ {
+ "question": "How can I switch my plan?",
+ "answers": [
+ "Contact us at Contact page!",
+ ],
+ },
+ {
+ "question": "What happens after payment is finished?",
+ "answers": [
+ "We will automatically and immediately apply all the featuers to your organization.",
+ ],
+ },
+ {
+ "question": "How can I cancel my plan?",
+ "answers": [
+ "As an Admin of your organization, you can manage it from the Admin page.",
+ ],
+ },
+ {
+ "question": "What is your refund policy?",
+ "answers": [
+ "For monthly and yearly subscriptions, you have 48 hours after the transaction to request a refund. Note that we reserve the right to decline requests if we detect high activity on your account within this time." ,
+ ],
+ },
+ {
+ "question": "Do you offer support?",
+ "answers": [
+ "Yes! We offer priority support with an SLA to our enterprise customers, and will answer any questions directed our way on the Contact page otherwise." ,
+ ],
+ },
+ {
+ "question": "Can you help me automate my operations?",
+ "answers": [
+ "Yes! We offer support with setup, configuration, automation and app creation. This can be bought as an addition withour needing a subscription.",
+ ],
+ }
+]
+
+
+export const faqData = [
+ {
+ "question": "What is Niceable? What’s your mission?",
+ "answers": [
+ "Check out our cool About page!",
+ ],
+ },
+ {
+ "question": "How does it work?",
+ "answers": [
+ "We've got you covered! ",
+ ],
+ },
+ {
+ "question": "When will winners be announced?",
+ "answers": [
+ "When the prizedraw's 'ticket threshold' is reached, all contributors will receive an email notification about when the live announcement of the winners—the prize winner and the winning charity—will take place. In general, the live announcement happens within 48 hours of the email notification being sent."
+ ],
+ },
+ {
+ "question": "How much goes to charity?",
+ "answers": [
+ "All prizedraws are guaranteed to give the majority of user contributions—more than 50%—to the winning charity. Individual prizedraw hosts (ie, prize vendors) may choose to take a smaller amount for themselves and give a larger percentage to the winning charity. In any case, we are the only prizedraw hosting platform that guarantees that the majority goes to charity. It’s the right thing to do."
+ ],
+ },
+ {
+ "question": "How are winning charities selected?",
+ "answers": [
+ "Charities are selected through a voting process that happens separately for each prizedraw. The community of contributors for a given prizedraw use our voting system to determine the best destination for their crowdsourced contribution. The current vote distribution can be seen on each prizedraw page’s charity leaderboard.",
+ ],
+ },
+ {
+ "question": "How are the charitable options chosen?",
+ "answers": [
+ "All of the charities that users can vote for have been selected based on them receiving top ratings from the most respected “charity evaluator” organizations. These assessments focus on transparency and financial optimization as well as the nature of their mission and demonstrated impact of their activities. Ultimately, however, YOUR assessment matters most. So, discuss with our community and then decide for yourself!",
+ "If you’d like to recommend a charity or you are part of a charity that’s interested in being featured on our site, please let us know here (adam@niceable.co). ",
+ "In the future, additional charities will be added as options with the least voted for charities being replaced. That way, all of our charitable options will be ones that have been top rated by charity evaluator organizations and top vote getters from our wise and beloved Niceable users.",
+ "We are also working on adding lots of information and statistics about each charity to our site, something that our charitable partners are helping us with.",
+ ],
+ },
+ {
+ "question": "Can I “write-off” my contribution on my taxes?",
+ "answers": [
+ "That depends on where you live. We do not claim to be tax experts and do not offer any advice on such matters. Basically, in some places, you can. In others, you can't. Check with a licensed tax expert in your area.",
+ ],
+ },
+ {
+ "question": "Can I buy prizedraw prizes directly?",
+ "answers": [
+ "We encourage users to check out prizedraw hosts, many of whom promote our prizedraws and charitable partnerships through social media. They offer prizes because they want to support great charities and offer products and experiences to people who may not always have the money to buy their products directly. Making super nice(able) things accessible to you and everyone else is a major part of our mission and they help us do that.",
+ "The current constraints of capitalism are BS and we're out to change that. Thanks for being a hero! Our prizedraw hosts are reaching out to you and--unlike almost all other organizations--trust YOU to choose the most-worthy charity to support. So, we certainly encourage you to check out their other offerings. They are helping all of you make the impact that YOU want to make and may offer something super nice(able) that's also a perfect fit for you.",
+ ],
+ },
+ {
+ "question": "How do I enter a promocode?",
+ "answers": [
+ "If its your first time visiting us, you can do it in a Raffle on the right hand side. If you are already logged in, click the 'My account' button in the upper right corner of the screen. Then click the 'enter a promotional code, before submitting the code you have.",
+ "You should now have received more entries!",
+ ],
+ },
+ {
+ "question": "How do you select your vendors?",
+ "answers": [
+ "Currently, our #1 priority is learning more about YOU. What do our users want? What prizes, charities, site features and technology, support, etc.? Therefore, we are currently trying to maximize the diversity of our prizes to see what YOU value most. It’s about you, not us or our vendors.",
+ "Do you most value products and experiences that are ethically-produced? Crazy expensive? Mid-priced? Rare or one-of-a-kind? Created by independent vendors like artists and craftspeople? By everyday people offering services customized for you and you alone? Luxury brands? Houses? Vacations? Cutting-edge technology? Whatever you want, we’ll work hard to offer it. We believe that EVERYONE should be able to have super nice(able) things!",
+ "We are, however, limiting the number of active prizedraws that we have to ensure that these prizedraws fill up quickly, allowing prize winners and winning charities to enjoy their winnings sooner. In the future, we plan to offer many more prizedraws at one time.",
+ ],
+ },
+ {
+ "question": "Can I host a prizedraw so that I can make some money, support great charities, and reach new audiences?",
+ "answers": [
+ "Contact us here (adam@niceable.co)",
+ ],
+ },
+ {
+ "question": "Can I host a prizedraw and donate the prize (because I’m a super nice person)?",
+ "answers": [
+ " Contact us here (adam@niceable.co)",
+ ],
+ }
+ ]
+
+const Faq = (props) => {
+ const { theme } = props;
+
+ // Hahah, this is a hack fml
+ const HandleAnswer = (props) => {
+ const [answers, setAnswers] = useState("");
+ const current = props.current
+
+ const loadAnswers = () => {
+ if (answers === "") {
+ const data = current.answers.map(answer => {
+ return answer
+ })
+
+ setAnswers(data.join("
"))
+ } else {
+ setAnswers("")
+ }
+ }
+
+ const icon = answers === "" ? :
+
+
+ return (
+ loadAnswers()} style={{textAlign: "center"}}>
+ {icon}
+ {current.question} secondary= />
+
+ )
+
+ }
+
+ const width = isMobile ? "100%" : 1000
+ const FAQ =
+
+
+ Frequently asked questions
+
+
+ {pricingFaq.map((current) => {
+ return (
+
+
+
+
+ )
+ })}
+
+ {/*
+
+
+ Thanks for reading! Have a super nice(able) time entering prizedraws for amazing prizes, enjoying our awesome community, and making the impact that YOU want to make in the world!
+
+ */}
+
+
+ const landingpageData =
+
+ {FAQ}
+
+
+ return (
+
+ {landingpageData}
+
+ )
+}
+
+export default Faq;
\ No newline at end of file
diff --git a/frontend/src/views/HandlePaymentNew.jsx b/frontend/src/views/HandlePaymentNew.jsx
new file mode 100644
index 00000000..be41aa11
--- /dev/null
+++ b/frontend/src/views/HandlePaymentNew.jsx
@@ -0,0 +1,1548 @@
+import React, { useState, useEffect } from 'react';
+
+import ReactGA from 'react-ga';
+import { useNavigate, Link } from "react-router-dom";
+import {isMobile} from "react-device-detect";
+
+import {
+ Done as DoneIcon,
+ Clear as ClearIcon,
+ AddTask as AddTaskIcon,
+ } from '@mui/icons-material';
+
+import {
+ Slider,
+ Divider,
+ List,
+ ListItem,
+ ListItemText,
+ Card,
+ CardContent,
+ Grid,
+ Typography,
+ Button,
+ ButtonGroup,
+ FormControl,
+ Dialog,
+ DialogTitle,
+ DialogActions,
+ DialogContent,
+ Tooltip
+} from '@material-ui/core';
+import FAQ from "./Faq.jsx";
+import Newsletter from "../components/Newsletter.jsx";
+import Services from "./Services.jsx";
+
+export const typecost = 0.0018
+export const typecost_single = (typecost * 1.33).toFixed(4)
+
+// 1. Create 2-3 payment tiers (slider?)
+// 2. Create a way to show them anywhere
+//
+// Site references:
+// https://logz.io/pricing/
+// https://www.avanan.com/pricing
+const PaymentField = (props) => {
+ const { maxFields, theme, removeAdditions, isLoggedIn } = props
+
+ const isCloud =
+ window.location.host === "localhost:3002" ||
+ window.location.host === "shuffler.io";
+
+ // Multiple unused variables here
+ let navigate = useNavigate();
+ const parsedFields = maxFields === undefined ? 300 : maxFields
+ const [variant, setVariant] = useState(0)
+ const [shuffleVariant, setShuffleVariant] = useState(isCloud ? 0 : 1)
+ const [paymentType, setPaymentType] = useState(0)
+ const [modalOpen, setModalOpen] = useState(false)
+ const [showPricing, ] = useState(true)
+ const [currentPrice, setCurrentPrice] = useState(129)
+ const [isLoaded, setIsLoaded] = useState(false)
+ const [errorMessage, setErrorMessage] = useState("")
+
+ // Cloud
+ const [calculatedApps, setCalculatedApps] = useState(600)
+ const [calculatedCost, setCalculatedCost] = useState("$600")
+ const [selectedValue, setSelectedValue] = useState(100)
+
+ // Onprem
+ const [calculatedCores, setCalculatedCores] = useState(600)
+ const [onpremSelectedValue, setOnpremSelectedValue] = useState(8)
+
+ useEffect(() => {
+ console.log("New variant: ", shuffleVariant)
+
+ if (shuffleVariant === 1) {
+ setCalculatedCost("$600")
+ setSelectedValue(8)
+ } else {
+ setCalculatedCost("$180")
+ setSelectedValue(100)
+ }
+ }, [shuffleVariant])
+
+ if (typeof window === 'undefined' || window.location === undefined) {
+ return null
+ }
+
+
+ /*
+ const valuetext = (value, variant) => {
+ console.log("Valuetext: ", value, variant)
+ if (value === 32 || value === 1000) {
+ if (variant === 1) {
+ setCalculatedCores("Get A Quote")
+ setOnpremSelectedValue(value)
+ } else {
+ setCalculatedApps(`Get A Quote`)
+ setSelectedValue(value)
+ }
+ } else {
+ if (variant === 1) {
+ setCalculatedCores(`$${value*75}`)
+ setOnpremSelectedValue(value)
+ } else {
+ setCalculatedApps(`$${value*1000*0.0018}`)
+ setSelectedValue(value)
+ }
+ }
+ }
+ */
+
+
+ const handleChange = (event, newValue) => {
+ console.log("Event, value: ", event.target, newValue)
+
+ if (shuffleVariant === 1) {
+ setSelectedValue(newValue)
+ if (newValue === 32) {
+ setCalculatedCost(`Get A Quote`)
+ } else {
+ setCalculatedCost(`$${newValue*75}`)
+ }
+ } else {
+ setSelectedValue(newValue)
+ if (newValue === 1000) {
+ setCalculatedCost(`Get A Quote`)
+ } else {
+ setCalculatedCost(`$${newValue*1000*typecost}`)
+ }
+ }
+ }
+
+
+ // return `${value}`;
+ //}
+
+ if (!isLoaded) {
+ setIsLoaded(true)
+
+ const tmpsearch = typeof window === 'undefined' || window.location === undefined ? "" : window.location.search
+ const tmpVar = new URLSearchParams(tmpsearch).get("variant")
+ if (tmpVar !== undefined && tmpVar !== null && tmpVar < 3) {
+ setVariant(parseInt(tmpVar))
+ }
+
+ const tmpType = new URLSearchParams(tmpsearch).get("payment_type")
+ if (tmpType !== undefined && tmpType !== null && tmpType < 2) {
+ setPaymentType(parseInt(tmpType))
+ }
+
+ const modal = new URLSearchParams(tmpsearch).get("payment_modal")
+ if (modal !== undefined && modal !== null && modal === "open") {
+ setModalOpen(true)
+ }
+
+ const tmpView = new URLSearchParams(tmpsearch).get("view")
+ if (tmpView !== undefined && tmpView !== null && tmpView === "failure") {
+ setErrorMessage("Something went wrong with your payment. Please try again.")
+ }
+
+ const urlSearchParams = new URLSearchParams(window.location.search);
+ const params = Object.fromEntries(urlSearchParams.entries());
+ const foundTab = params["tab"];
+ if (foundTab !== null && foundTab !== undefined) {
+ if (foundTab === "onprem") {
+ //valuetext(8, 1)
+ setShuffleVariant(1)
+ //setCalculatedCores(`$${8*75}`)
+ }
+ }
+ }
+
+ const billingInfo = Billed anually or monthly at 1.2x the cost
+ //const skipFreemode = window.location.pathname.startsWith("/admin")
+ const skipFreemode = false
+ const maxwidth = isMobile ? "91%" : skipFreemode ? 1100 : 1200
+ const activeIcon =
+ const inActiveIcon =
+
+ // All triggers
+ const features = [
+
+ {
+ "name": "Users",
+ "basic": "No limit",
+ "community": "No limit",
+ "pro": "No limit",
+ "enterprise": "",
+ "active": true,
+ },
+ {
+ "name": "Apps",
+ "basic": "No limit",
+ "community": "No limit",
+ "pro": "No limit",
+ "enterprise": "",
+ "active": true,
+ },
+ {
+ "name": "Workflows",
+ "basic": "No limit",
+ "community": "No limit",
+ "pro": "No limit",
+ "enterprise": "",
+ "active": true,
+ },
+ {
+ "name": "Workflow App Runs",
+ "basic": "10.000 / month",
+ "community": "10.000 / month",
+ "pro": "Pay as you go",
+ "enterprise": "",
+ "active": true,
+ "onprem": false,
+ },
+ {
+ "name": "Workflow App Runs",
+ "basic": "No limit",
+ "community": "No limit",
+ "pro": "No limit",
+ "enterprise": "",
+ "active": true,
+ "cloud": false,
+ },
+ {
+ "name": "Shuffle Datastore (cache)",
+ "basic": "Max 1GB",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ "onprem": false,
+ },
+ {
+ "name": "Shuffle Datastore (cache)",
+ "basic": "Yes",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ "cloud": false,
+ },
+ {
+ "name": "File Storage",
+ "basic": "Max 1GB",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ "onprem": false,
+ },
+ {
+ "name": "File Storage",
+ "basic": "Yes",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ "cloud": false,
+ },
+ {
+ "name": "Multi-Tenant",
+ "basic": "No",
+ "community": "Yes",
+ "pro": "Add-on",
+ "enterprise": "",
+ "active": true,
+ "onprem": false,
+ },
+ {
+ "name": "Multi-Tenant",
+ "basic": "Yes",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ "cloud": false,
+ },
+ {
+ "name": "Region Control",
+ "basic": "No",
+ "community": "Yes",
+ "pro": "Add-on",
+ "enterprise": "",
+ "active": true,
+ "onprem": false,
+ },
+ {
+ "name": "Per-CPU-core support",
+ "basic": "0 / month",
+ "community": "0 / month",
+ "pro": "Pay as you go",
+ "enterprise": "8 / month",
+ "active": true,
+ "cloud": false,
+ },
+ {
+ "name": "Shuffle SMS alerting",
+ "basic": "30 / month",
+ "community": "Yes",
+ "pro": "300 / month",
+ "enterprise": "300 / month",
+ "active": true,
+ },
+ {
+ "name": "Shuffle Email alerting",
+ "basic": "100 / month",
+ "community": "Yes",
+ "pro": "10.000 / month",
+ "enterprise": "",
+ "active": true,
+ },
+ {
+ "name": "Priority Support",
+ "basic": "No",
+ "community": "No",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ "title": "Support & Success",
+ },
+ {
+ "name": "Maintenance & Updates",
+ "basic": "No",
+ "community": "",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ "cloud": false,
+ },
+ {
+ "name": "Documentation & Community Support",
+ "basic": "Yes",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ },
+ {
+ "name": "Email & Chat Support",
+ "basic": "support@shuffler.io",
+ "community": "No",
+ "pro": "Prioritized + Critical issue SLA",
+ "enterprise": "",
+ "active": true,
+ },
+ {
+ "name": "Personal onboarding",
+ "basic": "No",
+ "community": "",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ },
+ {
+ "name": "Shuffle Academy",
+ "basic": "Yes",
+ "community": "No",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": false,
+ },
+ {
+ "name": "Workflow editor",
+ "basic": "Yes",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ "title": "Basic features",
+ },
+ {
+ "name": "App editor",
+ "basic": "Yes",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ },
+ {
+ "name": "Private Apps",
+ "basic": "Yes",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ },
+ {
+ "name": "Default & Shared playbooks",
+ "basic": "Yes",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ },
+ {
+ "name": "Organization control",
+ "basic": "Yes",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ },
+ {
+ "name": "Autocomplete features",
+ "basic": "Yes",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ },
+ {
+ "name": "Hybrid Webhook trigger",
+ "basic": "Yes",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ },
+ {
+ "name": "Hybrid User Input trigger",
+ "basic": "Yes",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ },
+ {
+ "name": "Hybrid Email trigger",
+ "basic": "Yes",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ },
+ {
+ "name": "Hybrid Schedule",
+ "basic": "Yes",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ },
+ {
+ "name": "Failure Notifications",
+ "basic": "Yes",
+ "community": "No",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ },
+ {
+ "name": "Hybrid Executions",
+ "basic": "Yes",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ },
+ {
+ "name": "Use of Public Workflows",
+ "basic": "Yes",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ },
+ {
+ "name": "Multiple Environments",
+ "basic": "Yes",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ },
+
+ {
+ "name": "Shuffle creates integration",
+ "basic": "No",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ },
+ {
+ "name": "Uptime SLA",
+ "basic": "No",
+ "community": "No",
+ "pro": "99.9%",
+ "enterprise": "",
+ "active": true,
+ "onprem": false,
+ },
+ {
+ "name": "Automated backups",
+ "basic": "No",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ "onprem": false,
+ },
+ {
+ "name": "MSSP org overview",
+ "basic": "No",
+ "community": "No",
+ "pro": "Add-on",
+ "enterprise": "",
+ "active": true,
+ },
+ {
+ "name": "MSSP org control",
+ "basic": "No",
+ "community": "No",
+ "pro": "Add-on",
+ "enterprise": "",
+ "active": true,
+ },
+ {
+ "name": "Automatic Platform updates",
+ "basic": "Yes",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ "onprem": false,
+ },
+ {
+ "name": "Audit logging",
+ "basic": "No",
+ "community": "No",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": false,
+ },
+ {
+ "name": "2-factor authentication",
+ "basic": "Yes",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ "title": "Security & Development",
+ },
+ {
+ "name": "SAML / SSO",
+ "basic": "Yes",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ },
+ {
+ "name": "API-key management",
+ "basic": "yes",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": false,
+ },
+ {
+ "name": "Role-based access control (RBAC)",
+ "basic": "Yes",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": false,
+ },
+
+ {
+ "name": "Workflow recommendations",
+ "basic": "Yes",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ "title": "Additional Features",
+ },
+ {
+ "name": "Standardized app categories",
+ "basic": "Yes",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ },
+ {
+ "name": "App Framework",
+ "basic": "Yes",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ },
+ {
+ "name": "Hybrid search engine",
+ "basic": "Yes",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ },
+
+ {
+ "name": "Hybrid App syncronization",
+ "basic": "Yes",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": true,
+ },
+ {
+ "name": "Execution Retention",
+ "basic": "1 Month",
+ "community": "Yes",
+ "pro": "1 Year default",
+ "enterprise": "",
+ "active": true,
+ "onprem": false,
+ },
+ {
+ "name": "Detection Management",
+ "basic": "Yes",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": false,
+ },
+ {
+ "name": "Mitre Att&ck integrations",
+ "basic": "No",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": false,
+ },
+ {
+ "name": "Open Source account rollback",
+ "basic": "No",
+ "community": "Yes",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": false,
+ },
+ {
+ "name": "Data LOCATION control",
+ "basic": "No",
+ "community": "No",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": false,
+ },
+ {
+ "name": "Data RETENTION control",
+ "basic": "No",
+ "community": "No",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": false,
+ },
+ {
+ "name": "Shuffle IoC search",
+ "basic": "No",
+ "community": "No",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": false,
+ },
+ {
+ "name": "Controllable Reporting",
+ "basic": "No",
+ "community": "No",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": false,
+ },
+ {
+ "name": "Management dashboard",
+ "basic": "No",
+ "community": "No",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": false,
+ },
+ {
+ "name": "Risk based overview",
+ "basic": "No",
+ "community": "No",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": false,
+ },
+ {
+ "name": "Compliance dashboard",
+ "basic": "No",
+ "community": "No",
+ "pro": "Yes",
+ "enterprise": "",
+ "active": false,
+ },
+ {
+ "name": "App & Workflow Training",
+ "basic": "$4999 / 5 people",
+ "community": "No",
+ "pro": "$1499 / 5 people",
+ "enterprise": "",
+ "active": true,
+ "title": "Professional Services",
+ },
+ {
+ "name": "Developer Training",
+ "basic": "$4999 / 5 people",
+ "community": "No",
+ "pro": "$1499 / 5 people",
+ "enterprise": "",
+ "active": true,
+ },
+ {
+ "name": "Custom App Development",
+ "basic": "Contact us",
+ "community": "No",
+ "pro": "Contact us",
+ "enterprise": "",
+ "active": true,
+ },
+ {
+ "name": "Custom Workflow Development",
+ "basic": "Contact us",
+ "community": "No",
+ "pro": "Contact us",
+ "enterprise": "",
+ "active": true,
+ },
+ {
+ "name": "Shuffle Custom Modifications",
+ "basic": "Contact us",
+ "community": "No",
+ "pro": "Contact us",
+ "enterprise": "",
+ "active": true,
+ },
+
+ ]
+
+ const defaultTaskIcon =
+ const stripe = typeof window === 'undefined' || window.location === undefined ? "" : props.stripeKey === undefined ? "" : window.Stripe ? window.Stripe(props.stripeKey) : ""
+ const handleStripeRedirect = (payment_type, recurrence) => {
+ console.log("REDIRECT: ", payment_type, recurrence)
+
+ // FIXME: Proper redirect cycle here
+ if (props.userdata === undefined || props.userdata.username === undefined || props.userdata.active_org === undefined || props.userdata.active_org.id === undefined) {
+ console.log("User must sign in and have an organization first. Current: ", props.userdata)
+ // 1. Add query parameters: Yearly / monthly, Community / pro
+ navigate(`/register?view=pricing&variant=${variant}&payment_type=${paymentType}&payment_modal=open&message=You need to create a user to continue`)
+ return
+ } else {
+ console.log("Username is ", props.userdata.username)
+ }
+
+ //payment_type = community(0), pro(1)
+ //recurrence = yearly(0), monthly(1)
+ var priceItem = "price_1Hh8ecDzMUgUjxHSPEdeueyu"
+ var text = "enterprise_yearly_pay_click"
+ // recurrence = 0 = yearly
+ // recurrence = 1 = monthly
+ //
+ if (payment_type === 0) {
+ console.log("Handling payment type 0: hybrid")
+ priceItem = recurrence === 0 ? isCloud ? "price_1HhAOgDzMUgUjxHSmesUZkNU" : "price_1HhAOgDzMUgUjxHSmesUZkNU" : isCloud ? "price_1HhAOgDzMUgUjxHSfU8XzQ84" : "price_1HhAOgDzMUgUjxHSfU8XzQ84"
+
+ ReactGA.event({
+ category: "pricing",
+ action: `hybrid_pay_click`,
+ label: "",
+ })
+ } else if (payment_type === 1) {
+ console.log("Handling payment type 1: enterprise")
+ priceItem = recurrence === 0 ? isCloud ? "price_1HhAdrDzMUgUjxHSsIDOCYgm" : "price_1HhAdrDzMUgUjxHSsIDOCYgm" : isCloud ? "price_1HhAdrDzMUgUjxHS7Cu5vF95" : "price_1HhAdrDzMUgUjxHS7Cu5vF95"
+
+ if (recurrence === 1) {
+ text = "enterprise_monthly_pay_click"
+ }
+
+ ReactGA.event({
+ category: "pricing",
+ action: text,
+ label: "",
+ })
+ } else if (payment_type === 2) {
+ console.log("Handling payment type 2: basic")
+ priceItem = recurrence === 0 ? isCloud ? "price_1HnPmWDzMUgUjxHSzEHV5e6t" : "price_1HlvuPDzMUgUjxHS1pvtPONJ" : isCloud ? "price_1HnPmWDzMUgUjxHSGC3Yiact" : "price_1HlvuPDzMUgUjxHSrp7Ws8iu"
+
+ ReactGA.event({
+ category: "pricing",
+ action: `mssp_pay_click`,
+ label: "",
+ })
+ } else {
+ console.log(`No handler for redirect ${payment_type} yet`)
+ return
+ }
+
+ // Current URL + status = Success/fail
+ const successUrl = `${window.location.origin}/admin?payment=success`
+ const failUrl = `${window.location.origin}/pricing?view=failure&variant=${variant}&payment_type=${paymentType}`
+
+ var checkoutObject = {
+ lineItems: [
+ {price: priceItem, quantity: 1},
+ ],
+ mode: "subscription",
+ billingAddressCollection: "auto",
+ successUrl: successUrl,
+ cancelUrl: failUrl,
+ submitType: "donate",
+ clientReferenceId: props.userdata.active_org.id,
+ }
+
+ stripe.redirectToCheckout(checkoutObject)
+ .then(function (result) {
+ console.log("SUCCESS STRIPE?: ", result)
+
+ text += "_success"
+ ReactGA.event({
+ category: "pricing",
+ action: text,
+ label: "",
+ })
+ })
+ .catch(function(error) {
+ console.error("STRIPE ERROR: ", error)
+ text += "_fail"
+ ReactGA.event({
+ category: "pricing",
+ action: text,
+ label: "",
+ })
+ });
+
+ console.log("Done with payment!")
+ }
+
+ const buttonBackground = "linear-gradient(to right, #f86a3e, #f34079)"
+ const level1Button =
+ {
+ setMonthlyCost(0, paymentType)
+ setVariant(0)
+ setModalOpen(true)
+ ReactGA.event({
+ category: "pricing",
+ action: `hybrid_click`,
+ label: "",
+ })
+ }}>
+ Get hybrid access
+
+
+ const level2Button =
+ {
+ //setMonthlyCost(1, paymentType)
+ //setVariant(1)
+ //setModalOpen(true)
+ ReactGA.event({
+ category: "pricing",
+ action: `enterprise_click`,
+ label: "",
+ })
+
+ ReactGA.event({
+ category: "pricing",
+ action: `demo_click`,
+ label: "",
+ })
+
+ if (window.drift !== undefined) {
+ window.drift.api.startInteraction({ interactionId: 340045 })
+ }
+ }}>
+ Get a demo
+
+
+ const level3Button = skipFreemode ? null :
+ {
+
+ ReactGA.event({
+ category: "pricing",
+ action: `getting_started_click`,
+ label: "",
+ })
+
+ if (shuffleVariant === 0) {
+ navigate("/register?message=Get started for free")
+ } else {
+ window.location.href = "https://github.com/Shuffle/Shuffle/blob/master/.github/install-guide.md"
+ }
+
+ //setMonthlyCost(2, paymentType)
+ //setVariant(2)
+ //setModalOpen(true)
+ }}>
+ Start building!
+
+
+
+ const cardStyle = {
+ height: "100%",
+ width: "100%",
+ textAlign: "center",
+ backgroundColor: theme.palette.surfaceColor,
+ color: "white",
+ }
+
+
+ var indexskip = 0
+ const topRet =
+
+
Pricing
+ {/*
Find pricing, focused on shuffler.io and self-hosted */}
+ {/*
These prices are likely to change */}
+
+
+ {
+ event.preventDefault()
+
+ setShuffleVariant(0)
+ ReactGA.event({
+ category: "pricing",
+ action: `saas_normal_click`,
+ label: "",
+ })
+
+ navigate("/pricing?tab=cloud")
+ }}>Cloud
+ {
+ event.preventDefault()
+
+ setShuffleVariant(1)
+ navigate("/pricing?tab=onprem")
+
+ ReactGA.event({
+ category: "pricing",
+ action: `self_hosted_normal_click`,
+ label: "",
+ })
+
+ }}>Self-Hosted
+
+
+ {errorMessage.length > 0 ?
Error: {errorMessage} : null}
+
+
+ {skipFreemode ? null :
+
+
+
+ Free
+
+ {shuffleVariant === 0 ?
+ "shuffler.io / Cloud"
+ :
+ Open Source
+ }
+
+ {paymentType === 0 ? "Free" : "Free"}
+
+ {shuffleVariant === 0 ?
+ "Includes 10k App Executions. Refreshes every month."
+ :
+ "Unlimited use, self-hosted."
+ }
+
+
+
+
+ {defaultTaskIcon} Use any app
+
+
+
+ {defaultTaskIcon} Unlimited users
+
+
+
+ {defaultTaskIcon} Unlimited workflows
+
+
+
+
+ {defaultTaskIcon} {
+ if (window.drift !== undefined) {
+ window.drift.api.startInteraction({ interactionId: 340043, })
+ } else {
+ console.log("Couldn't find drift in window.drift and not .drift-open-chat with querySelector: ", window.drift)
+ }
+ }}>Free Support & Discord access
+
+
+
+ {level3Button}
+
+
+
+ }
+
+
+
+
+ {shuffleVariant === 1 ? "Scale" : "Enterprise"}
+
+ {shuffleVariant === 0 ?
+ "SaaS / Cloud"
+ :
+ "Open Source + Scale License"
+ }
+
+
+ {
+ if (calculatedCores === "Get A Quote") {
+ console.log("Clicked on get a quote")
+ if (window.drift !== undefined) {
+ window.drift.api.startInteraction({ interactionId: 340785 })
+ }
+ }
+ }}>{calculatedCost}
+ Per month for {shuffleVariant === 1 ? `${selectedValue} CPU cores` : `${selectedValue}k App Executions`}:
+
+
+ {
+ handleChange(event, newValue)
+ }}
+ marks
+ value={selectedValue}
+ step={shuffleVariant === 0 ? 100 : 4}
+ min={shuffleVariant === 0 ? 100 : 8}
+ max={shuffleVariant === 0 ? 1000 : 32}
+ valueLabelDisplay="auto"
+ />
+
+
+
+
+ {defaultTaskIcon} Priority Support
+
+
+
+ {defaultTaskIcon} {shuffleVariant === 0 ?
+ "Multi-Tenant"
+ :
+ "Scalable Orborus"
+ }
+
+
+ {shuffleVariant === 0 ?
+
+ {defaultTaskIcon} Multi-Region Tenants
+
+ :
+
+ {defaultTaskIcon} High Availability
+
+ }
+
+
+ {defaultTaskIcon} Help with Workflow and App development
+
+
+
+ {/*billingInfo*/}
+
+
+ {level2Button}
+ {shuffleVariant === 0 ?
+ {
+ if (isLoggedIn) {
+ console.log("Redirecting to Stripe!")
+
+ const priceItem = window.location.origin === "https://shuffler.io" ? "app_executions" : "price_1MROFrDzMUgUjxHShcSxgHO1"
+
+ const successUrl = `${window.location.origin}/admin?admin_tab=billing&payment=success`
+ const failUrl = `${window.location.origin}/pricing?admin_tab=billing&payment=failure`
+ var checkoutObject = {
+ lineItems: [
+ {
+ price: priceItem,
+ quantity: selectedValue/100,
+ },
+ ],
+ mode: "subscription",
+ billingAddressCollection: "auto",
+ successUrl: successUrl,
+ cancelUrl: failUrl,
+ clientReferenceId: props.userdata.active_org.id,
+ }
+
+ stripe.redirectToCheckout(checkoutObject)
+ .then(function (result) {
+ console.log("SUCCESS STRIPE?: ", result)
+
+ ReactGA.event({
+ category: "pricing",
+ action: "add_card_success",
+ label: "",
+ })
+ })
+ .catch(function(error) {
+ console.error("STRIPE ERROR: ", error)
+
+ ReactGA.event({
+ category: "pricing",
+ action: "add_card_error",
+ label: "",
+ })
+ });
+
+ } else {
+ console.log("Pay now")
+ navigate(`/register?view=pricing&message=You need to create a user to continue`)
+
+ //navigate("/login?redirect=/pricing")
+ //to={`/register?app_one=${app.name}&app_two=${secondaryApp.name}&message=You need to login first to connect ${app.name} and ${secondaryApp.name}`}
+ }
+ }}>
+ Get {selectedValue}k App Run Units
+
+ :
+ null}
+
+ {/*
+
+ {
+ if (window.drift !== undefined) {
+ window.drift.api.startInteraction({ interactionId: 340785 })
+ } else {
+ console.log("Couldn't find drift in window.drift and not .drift-open-chat with querySelector: ", window.drift)
+ }
+ }}>
+ Or get a quote
+
+
+ */}
+
+
+
+
+ {/*shuffleVariant === 1 ? null :
+
+
+
+ MSSP
+ Cross-Customer automation
+ ${paymentType === 0 ? 1999 : 2399}
+ Per month{paymentType === 0 ? ", billed yearly" : null}
+
+ - All previous tiers
+
+
+ - Extra Customer control
+
+
+ - Sub-organization access
+
+
+ - Build Shuffle into your product
+
+
+ {
+ ReactGA.event({
+ category: "pricing",
+ action: `quote_mssp_click`,
+ label: "",
+ })
+
+ if (window.drift !== undefined) {
+ window.drift.api.startInteraction({ interactionId: 340785 })
+ } else {
+ console.log("Couldn't find drift in window.drift and not .drift-open-chat with querySelector: ", window.drift)
+ }
+ }}>
+ Get a Quote
+
+
+
+
+ */}
+
+
+
+
+
+ {shuffleVariant === 0 ?
+ "- 100k Executions per month can handle about 500 Assets, and scales linearly."
+ :
+ "- 8 CPU-cores (default) can handle about 1500 Assets and scales linearly."
+ }
+
+ - All prices are in USD and exclude VAT
+
+ {/*
+
+ Shuffle is an Open Source project. Gives access to support, development and features not otherwise available. This applies to both Open Source & Cloud/SaaS. After the transaction is finished, you will immediately have full access to our support team, and you organization will automatically get upgraded resources assigned.
+
+ */}
+
+
+ Pay-as-you-go-pricing
+ Simple usage based pricing with no long-term commitments
+
+
+ Volume Discounts
+ Discounts trigger as your usage grows, so you always get a fair price.
+
+
+ Committed-use discounts
+ Get additional discounts for annual or multi-year commitments
+
+
+ {!showPricing ?
+ null :
+
+
Features ({shuffleVariant === 0 ? "Cloud" : "Self-Hosted"})
+
+
+
+ {isMobile ? "F" : "Free"}
+ style={{ textAlign: "left", flex: 2}}
+ />
+ {/*{isMobile ? "H" : "Hybrid"}
+ style={{ textAlign: "left", flex: 2}}
+ />*/}
+ {isMobile ? "E" : "Enterprise / Scale"}
+ style={{ textAlign: "left", flex: 2}}
+ />
+
+ {features.slice(0, parsedFields).map((data, index) => {
+
+ //const activeData = data.active ? activeIcon : data.basic === "No" || data.basic === false ? inActiveIcon : data.basic
+ const basicData = data.basic === "Yes" || data.basic === true ? activeIcon : data.basic === "No" || data.basic === false ? inActiveIcon : data.basic
+ const communityData = data.community === "Yes" || data.community === true ? activeIcon : data.community === "No" || data.community === false ? inActiveIcon : data.community
+ const proData = data.pro === "Yes" || data.pro === true ? activeIcon : data.pro === "No" || data.pro === false ? inActiveIcon : data.pro
+
+ if (shuffleVariant === 0 && data.cloud === false) {
+ indexskip += 1
+ return null
+ }
+
+ if (shuffleVariant === 1 && data.onprem === false) {
+ indexskip += 1
+ return null
+ }
+
+ const newindex = index-indexskip
+
+ return (
+
+ {data.title !== undefined ?
+
+ {data.title}
+
+ : null}
+
+ {data.active === true ?
+
+ :
+
+
+
+ }
+
+ {/*
+
+ */}
+
+
+
+ )
+ })}
+ {features.length > parsedFields ?
+
+ {
+ ReactGA.event({
+ category: "pricing",
+ action: `see_all_features_click`,
+ label: "",
+ })
+ }}>
+ See all features
+
+
+ : null
+ }
+ {/*isMobile ? null :
+
+ {level3Button}
+
+ {level1Button}
+
+ {level2Button}
+
+ */}
+ {/*
+
+
+
+
+
+ */}
+
+
+
+ }
+
+
Scalable models for MSSPs
+
+ Need support and automation help for diverse and scalable environments? Our Enterprise and MSSP offerings can help you whether Onprem or in our cloud.
+
+
+
+
+
+ Open Source
+ CPU-core-based
+ 8 cores included in sub
+ $75 /
+ CPU-core
+
+ Per month
+
+
+
+
+
+
+
+ Cloud
+ App-Execution based
+ Pay-as-you-go
+ ${typecost_single} /
+ app-runs
+ Per month
+
+
+
+
+
+
Cloud, Hybrid & Onprem
+
+ Our support model is built for both the Cloud and Onpremises version of Shuffle, and can be managed between both. Contact us for more info, or to get a quote from one of our verified resellers.
+
+
+
+
+ {removeAdditions === true ? null :
+
+
+
+
+
+ Got other questions?
+
+
+ If you got more questions about our pricing and plans, please contact us so we can help
+
+
+
+
+ }
+
+
+
+ const setMonthlyCost = (variant, paymentType) => {
+ setErrorMessage("")
+ if (variant === 0 && paymentType === 0) {
+ setCurrentPrice(129)
+ } else if (variant === 0 && paymentType === 1) {
+ setCurrentPrice(155)
+ } else if (variant === 1 && paymentType === 0) {
+ setCurrentPrice(999)
+ } else if (variant === 1 && paymentType === 1) {
+ setCurrentPrice(1199)
+ } else if (variant === 2 && paymentType === 0) {
+ setCurrentPrice(15)
+ } else if (variant === 2 && paymentType === 1) {
+ setCurrentPrice(18)
+ }
+ }
+
+ const modalView = modalOpen ?
+ {
+ setModalOpen(false)
+ ReactGA.event({
+ category: "pricing",
+ action: `close_window_outside_click`,
+ label: "",
+ })
+ }}
+ PaperProps={{
+ style: {
+ backgroundColor: "#1f2023",
+ color: "white",
+ minWidth: isMobile ? "100%" :500,
+ padding: 30,
+ },
+ }}
+ >
+
+ Shuffle payments
+
+
+ Choose recurrence
+
+
+ {
+ setPaymentType(0)
+ setMonthlyCost(variant, 0)
+ ReactGA.event({
+ category: "pricing",
+ action: `yearly_modal_click`,
+ label: "",
+ })
+ }}>Yearly
+ {
+ setPaymentType(1)
+ setMonthlyCost(variant, 1)
+ ReactGA.event({
+ category: "pricing",
+ action: `monthly_modal_click`,
+ label: "",
+ })
+ }}>Monthly
+
+
+
+
+
+ Your plan:
+
+
+ Shuffle {variant === 0 ? "Community" : variant === 2 ? "Free" : "Pro"} Edition
+
+
+
+
+ Monthly subtotal:
+
+
+ ${currentPrice}
+
+
+
+
+ Discount:
+
+
+ {paymentType === 0 ? "20%" : "0%"}
+
+
+
+
+ Beta opt-in:
+
+
+ Extra features
+
+
+
+
+
+ What you'll pay now:
+
+
+ ${paymentType === 0 ? currentPrice*12 : currentPrice}
+
+
+
+
+
+
+
+ {
+ handleStripeRedirect(variant, paymentType)
+ }}>
+ Pay now
+
+
+ Your plan will renew each {paymentType === 0 ? "year" : "month"}.
+
+ {/* */}
+
+
+ {
+ setModalOpen(false)
+ ReactGA.event({
+ category: "pricing",
+ action: `close_window_cancel_click`,
+ label: "",
+ })
+ }} color="primary">
+ Cancel
+
+
+
+
+ : null
+
+ return (
+
+ {topRet}
+ {modalView}
+
+ )
+}
+
+export default PaymentField;
\ No newline at end of file
diff --git a/frontend/src/views/Services.jsx b/frontend/src/views/Services.jsx
new file mode 100644
index 00000000..c8f8e7cc
--- /dev/null
+++ b/frontend/src/views/Services.jsx
@@ -0,0 +1,203 @@
+import React, {useState } from 'react';
+
+import ReactGA from 'react-ga';
+import { useNavigate, Link, useParams } from "react-router-dom";
+import {isMobile} from "react-device-detect";
+
+import {Done as DoneIcon, Clear as ClearIcon} from '@material-ui/icons';
+import {Paper, Divider, List, ListItem, ListItemText, Card, CardContent, Grid, Typography, Button, ButtonGroup, FormControl, Dialog, DialogTitle, DialogActions, DialogContent, Tooltip} from '@material-ui/core';
+import FAQ from "./Faq.jsx";
+import Newsletter from "../components/Newsletter.jsx";
+
+// 1. Create 2-3 payment tiers (slider?)
+// 2. Create a way to show them anywhere
+//
+// Site references:
+// https://logz.io/pricing/
+// https://www.avanan.com/pricing
+const PaymentField = (props) => {
+ const { maxFields, theme, removeAdditions } = props
+
+ const parsedFields = maxFields === undefined ? 300 : maxFields
+ const [variant, setVariant] = useState(0)
+ const [paymentType, setPaymentType] = useState(0)
+ const [modalOpen, setModalOpen] = useState(false)
+ const [showPricing, ] = useState(true)
+ const [currentPrice, setCurrentPrice] = useState(129)
+ const [isLoaded, setIsLoaded] = useState(false)
+ const [errorMessage, setErrorMessage] = useState("")
+ let navigate = useNavigate();
+
+ if (typeof window === 'undefined' || window.location === undefined) {
+ return null
+ }
+
+ if (!isLoaded) {
+ setIsLoaded(true)
+
+ const tmpsearch = typeof window === 'undefined' || window.location === undefined ? "" : window.location.search
+ const tmpVar = new URLSearchParams(tmpsearch).get("variant")
+ if (tmpVar !== undefined && tmpVar !== null && tmpVar < 3) {
+ setVariant(parseInt(tmpVar))
+ }
+
+ const tmpType = new URLSearchParams(tmpsearch).get("payment_type")
+ if (tmpType !== undefined && tmpType !== null && tmpType < 2) {
+ setPaymentType(parseInt(tmpType))
+ }
+
+ const modal = new URLSearchParams(tmpsearch).get("payment_modal")
+ if (modal !== undefined && modal !== null && modal === "open") {
+ setModalOpen(true)
+ }
+
+ const tmpView = new URLSearchParams(tmpsearch).get("view")
+ if (tmpView !== undefined && tmpView !== null && tmpView === "failure") {
+ setErrorMessage("Something went wrong with your payment. Please try again.")
+ }
+ }
+
+ const billingInfo = Billed anually or monthly at 1.2x the cost
+ const maxwidth = isMobile ? "91%" : 1024
+ const activeIcon =
+ const inActiveIcon =
+
+ const cardStyle = {
+ height: "100%",
+ width: "100%",
+ textAlign: "center",
+ backgroundColor: theme.palette.surfaceColor,
+ color: "white",
+ }
+
+ const maxWidth = isMobile ? "100%" : 1000
+ const margin = isMobile ? 15 : 50
+ const paperWidth = isMobile ? "100%" : maxWidth-margin*2
+ const listItemStyle = isMobile ? {textAlign: "center"} : {}
+ const topRet =
+
+
+
+ Our team is here to help you
+
+
+ We solve any problem that arises in a security operations center using YOUR tools. Here's how.
+
+
+
+
+ {navigate("/usecases")}} style={listItemStyle}>
+
+
+ {}} style={listItemStyle}>
+
+
+ {"https://discord.gg/B2CBzUm"}} style={listItemStyle}>
+
+
+
+
+
+
+ Accessibility first
+
+
+ Shuffle was built by and for security professionals. We aim to bring our unique toolbox to every operations center globally, enabling information sharing and collaboration at scale, whether in the cloud, or on-premises.
+
+
+
+
+
+
+ Automation Services
+
+
+ Our team of security and automation experts create workflows to automated your SOC end-to-end. In the case that apps are missing, we will create them for you and share with the community to everyones benefit.
+
+
+
+
+ {}} style={listItemStyle}>
+
+
+ {}} style={listItemStyle}>
+
+
+ {}} style={listItemStyle}>
+
+
+
+
+
+
+
+
+ {}} style={listItemStyle}>
+
+
+ {}} style={listItemStyle}>
+
+
+ {}} style={listItemStyle}>
+
+
+
+
+
+
+ Support and Maintenance
+
+
+ We recognize that support is a vital part of keeping operations stable and secure. That's why we're offering support of both the cloud platform and usage, as well as for the open source version of Shuffle.
+
+
+
+
+
+
+ Training
+
+
+ We teach you how to become a poweruser of Shuffle. It involves how to maintain, create, share and use it Shuffle, whether in a small operations center or as an MSSP.
+
+
+
+
+ {}} style={listItemStyle}>
+
+
+ {}} style={listItemStyle}>
+
+
+ {}} style={listItemStyle}>
+
+
+
+
+
+
+
+
+
+ return (
+
+ {topRet}
+ {removeAdditions === true ? null :
+
+
+
+
+ Got other questions?
+
+
+ If you got more questions about our pricing and plans, please contact us so we can help
+
+
+
+
+ }
+
+ )
+}
+
+export default PaymentField;
\ No newline at end of file