+65
-26
@@ -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
|
||||
|
||||
@@ -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 (
|
||||
<Paper style={paperStyle}>
|
||||
<div style={{display: "flex"}}>
|
||||
<Typography variant="h6" style={{ marginTop: 10, marginBottom: 10 }}>
|
||||
{top_text}
|
||||
</Typography>
|
||||
</div>
|
||||
<Divider />
|
||||
<div>
|
||||
<Typography variant="body1" style={{ marginTop: 20, }}>
|
||||
{subscription.name}
|
||||
</Typography>
|
||||
<div style={{display: "flex", }}>
|
||||
<Typography variant="h6" style={{ marginTop: 10, }}>
|
||||
{subscription.currency_text}{subscription.price}
|
||||
</Typography>
|
||||
<Typography variant="body1" color="textSecondary" style={{ marginLeft: 10, marginTop: 15, marginBottom: 10 }}>
|
||||
/ {subscription.interval}
|
||||
</Typography>
|
||||
</div>
|
||||
<Typography variant="body2" color="textSecondary" style={{ marginTop: 10, }}>
|
||||
Features
|
||||
</Typography>
|
||||
<ul>
|
||||
{subscription.features !== undefined && subscription.features !== null ?
|
||||
subscription.features.map((feature, index) => {
|
||||
return (
|
||||
<li>
|
||||
<Typography variant="body2" color="textPrimary" style={{ }}>
|
||||
{feature}
|
||||
</Typography>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
: null}
|
||||
</ul>
|
||||
</div>
|
||||
{/*subscription.name === "Pay as you go" && subscription.limit <= 10000 ?
|
||||
<span>
|
||||
<Typography variant="body2" color="textSecondary" style={{ marginTop: 20, marginBottom: 10 }}>
|
||||
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.
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
style={{ marginTop: 20, marginBottom: 10, }}
|
||||
onClick={() => {
|
||||
handleStripeRedirect()
|
||||
}}
|
||||
>
|
||||
Activate Billing
|
||||
</Button>
|
||||
</span>
|
||||
: null*/}
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Typography variant="h6" style={{ marginTop: 20, marginBottom: 10 }}>
|
||||
Billing
|
||||
</Typography>
|
||||
<Typography variant="body1" style={{ marginTop: 20, marginBottom: 10 }}>
|
||||
We use Stripe to manage subscriptions and do not store any of your billing information. You can manage your subscription and billing information below.
|
||||
</Typography>
|
||||
<div style={{display: "flex", maxWidth: 768, minWidth: 768, }}>
|
||||
{billingInfo.subscription !== undefined && billingInfo.subscription !== null ?
|
||||
<SubscriptionObject
|
||||
globalUrl={globalUrl}
|
||||
userdata={userdata}
|
||||
serverside={serverside}
|
||||
billingInfo={billingInfo}
|
||||
stripeKey={stripeKey}
|
||||
selectedOrganization={selectedOrganization}
|
||||
subscription={billingInfo.subscription}
|
||||
/>
|
||||
: null}
|
||||
{isCloud &&
|
||||
selectedOrganization.subscriptions !== undefined &&
|
||||
selectedOrganization.subscriptions !== null &&
|
||||
selectedOrganization.subscriptions.length > 0 ?
|
||||
|
||||
selectedOrganization.subscriptions
|
||||
.reverse()
|
||||
.map((sub, index) => {
|
||||
return (
|
||||
<SubscriptionObject
|
||||
globalUrl={globalUrl}
|
||||
userdata={userdata}
|
||||
serverside={serverside}
|
||||
billingInfo={billingInfo}
|
||||
stripeKey={stripeKey}
|
||||
selectedOrganization={selectedOrganization}
|
||||
subscription={sub}
|
||||
/>
|
||||
)
|
||||
})
|
||||
: null}
|
||||
{/*
|
||||
<Grid item key={index} xs={12/selectedOrganization.subscriptions.length}>
|
||||
<Card
|
||||
elevation={6}
|
||||
style={
|
||||
paperStyle
|
||||
}
|
||||
>
|
||||
<b>Quantity</b>: {sub.level}
|
||||
<div />
|
||||
<b>Recurrence</b>: {sub.recurrence}
|
||||
<div />
|
||||
{sub.active ? (
|
||||
<div>
|
||||
<b>Started</b>:{" "}
|
||||
{new Date(sub.startdate * 1000).toISOString()}
|
||||
<div />
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
style={{ marginTop: 15 }}
|
||||
onClick={() => {
|
||||
cancelSubscriptions(sub.reference);
|
||||
}}
|
||||
>
|
||||
Cancel subscription
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<b>Cancelled</b>:{" "}
|
||||
{new Date(
|
||||
sub.cancellationdate * 1000
|
||||
).toISOString()}
|
||||
<div />
|
||||
<Typography color="textSecondary">
|
||||
<b>Status</b>: Deactivated
|
||||
</Typography>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</Grid>
|
||||
*/}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Billing;
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<div style={{margin: "auto", color: "white", textAlign: "center",}}>
|
||||
<Typography variant="h4" style={{marginTop: 35,}}>
|
||||
Security Automation Newsletter
|
||||
</Typography>
|
||||
<Typography variant="h6" style={{color: "#7d7f82", marginTop: 20, }}>
|
||||
Defensive security is 99% noise. Join us to sift through it.
|
||||
</Typography>
|
||||
<div style={{}}>
|
||||
<TextField
|
||||
style={{minWidth: isMobile ? "90%" : 450, backgroundColor: theme.palette.inputColor, marginTop: 20, borderRadius: 10, }}
|
||||
InputProps={{
|
||||
style:{
|
||||
borderRadius: 10,
|
||||
height: 60,
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
color="primary"
|
||||
value={email}
|
||||
onChange={(e) => {
|
||||
setEmail(e.target.value)
|
||||
}}
|
||||
placeholder="Your email"
|
||||
id="standard-required"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
style={buttonStyle}
|
||||
disabled={!buttonActive}
|
||||
onClick={() => {
|
||||
newsletterSignup(email)
|
||||
ReactGA.event({
|
||||
category: "newsletter",
|
||||
action: `signup_click`,
|
||||
label: "",
|
||||
})
|
||||
}}
|
||||
>
|
||||
Sign up
|
||||
</Button>
|
||||
<div/>
|
||||
{msg}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default Newsletter;
|
||||
@@ -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 = (
|
||||
<Tooltip title="Save any unsaved data" placement="bottom">
|
||||
<Button
|
||||
style={{ width: 150, height: 55, flex: 1 }}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={
|
||||
userdata === undefined ||
|
||||
userdata === null ||
|
||||
userdata.admin !== "true"
|
||||
}
|
||||
onClick={() =>
|
||||
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,
|
||||
}
|
||||
)
|
||||
}
|
||||
>
|
||||
<SaveIcon />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
return (
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<Grid container spacing={3} style={{ textAlign: "left" }}>
|
||||
<Grid item xs={12} style={{}}>
|
||||
<span>
|
||||
<Typography>Notification Workflow ID</Typography>
|
||||
<TextField
|
||||
required
|
||||
style={{
|
||||
flex: "1",
|
||||
marginTop: "5px",
|
||||
marginRight: "15px",
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
}}
|
||||
fullWidth={true}
|
||||
type="name"
|
||||
id="outlined-with-placeholder"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
placeholder="ID of the workflow to receive notifications"
|
||||
value={notificationWorkflow}
|
||||
onChange={(e) => {
|
||||
setNotificationWorkflow(e.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: classes.notchedOutline,
|
||||
},
|
||||
style: {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</Grid>
|
||||
<Grid item xs={12} style={{}}>
|
||||
<span>
|
||||
<Typography>Org Documentation reference</Typography>
|
||||
<TextField
|
||||
required
|
||||
style={{
|
||||
flex: "1",
|
||||
marginTop: "5px",
|
||||
marginRight: "15px",
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
}}
|
||||
fullWidth={true}
|
||||
type="name"
|
||||
id="outlined-with-placeholder"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
placeholder="URL to an external reference for this implementation"
|
||||
value={documentationReference}
|
||||
onChange={(e) => {
|
||||
setDocumentationReference(e.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: classes.notchedOutline,
|
||||
},
|
||||
style: {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</Grid>
|
||||
{isCloud ? null :
|
||||
<Grid item xs={12} style={{marginTop: 50 }}>
|
||||
<Typography variant="h4" style={{textAlign: "center",}}>OpenID connect</Typography>
|
||||
<Grid container style={{marginTop: 10, }}>
|
||||
<Grid item xs={6} style={{}}>
|
||||
<span>
|
||||
<Typography>Client ID</Typography>
|
||||
<TextField
|
||||
required
|
||||
style={{
|
||||
flex: "1",
|
||||
marginTop: "5px",
|
||||
marginRight: "15px",
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
}}
|
||||
fullWidth={true}
|
||||
type="name"
|
||||
multiline={true}
|
||||
rows={2}
|
||||
disabled={
|
||||
selectedOrganization.manager_orgs !== undefined &&
|
||||
selectedOrganization.manager_orgs !== null &&
|
||||
selectedOrganization.manager_orgs.length > 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",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</Grid>
|
||||
<Grid item xs={6} style={{}}>
|
||||
<span>
|
||||
<Typography>Client Secret (optional)</Typography>
|
||||
<TextField
|
||||
required
|
||||
style={{
|
||||
flex: "1",
|
||||
marginTop: "5px",
|
||||
marginRight: "15px",
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
}}
|
||||
fullWidth={true}
|
||||
type="name"
|
||||
multiline={true}
|
||||
rows={2}
|
||||
disabled={
|
||||
selectedOrganization.manager_orgs !== undefined &&
|
||||
selectedOrganization.manager_orgs !== null &&
|
||||
selectedOrganization.manager_orgs.length > 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",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container style={{marginTop: 10, }}>
|
||||
<Grid item xs={6} style={{}}>
|
||||
<span>
|
||||
<Typography>Authorization URL</Typography>
|
||||
<TextField
|
||||
required
|
||||
style={{
|
||||
flex: "1",
|
||||
marginTop: "5px",
|
||||
marginRight: "15px",
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
}}
|
||||
fullWidth={true}
|
||||
type="name"
|
||||
id="outlined-with-placeholder"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
multiline={true}
|
||||
rows={2}
|
||||
placeholder="The OpenID authorization URL (usually ends with /authorize)"
|
||||
value={openidAuthorization}
|
||||
onChange={(e) => {
|
||||
setOpenidAuthorization(e.target.value)
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: classes.notchedOutline,
|
||||
},
|
||||
style: {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</Grid>
|
||||
<Grid item xs={6} style={{}}>
|
||||
<span>
|
||||
<Typography>Token URL</Typography>
|
||||
<TextField
|
||||
required
|
||||
style={{
|
||||
flex: "1",
|
||||
marginTop: "5px",
|
||||
marginRight: "15px",
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
}}
|
||||
fullWidth={true}
|
||||
type="name"
|
||||
id="outlined-with-placeholder"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
multiline={true}
|
||||
rows={2}
|
||||
placeholder="The OpenID token URL (usually ends with /token)"
|
||||
value={openidToken}
|
||||
onChange={(e) => {
|
||||
setOpenidToken(e.target.value)
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: classes.notchedOutline,
|
||||
},
|
||||
style: {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
}
|
||||
{/*isCloud ? null : */}
|
||||
<Grid item xs={12} style={{marginTop: 50,}}>
|
||||
<Typography variant="h4" style={{textAlign: "center",}}>SAML SSO (v1.1)</Typography>
|
||||
<Grid container style={{marginTop: 20, }}>
|
||||
<Grid item xs={6} style={{}}>
|
||||
<span>
|
||||
<Typography>SSO Entrypoint (IdP)</Typography>
|
||||
<TextField
|
||||
required
|
||||
style={{
|
||||
flex: "1",
|
||||
marginTop: "5px",
|
||||
marginRight: "15px",
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
}}
|
||||
fullWidth={true}
|
||||
type="name"
|
||||
multiline={true}
|
||||
rows={2}
|
||||
disabled={
|
||||
selectedOrganization.manager_orgs !== undefined &&
|
||||
selectedOrganization.manager_orgs !== null &&
|
||||
selectedOrganization.manager_orgs.length > 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",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</Grid>
|
||||
<Grid item xs={6} style={{}}>
|
||||
<span>
|
||||
<Typography>SSO Certificate (X509)</Typography>
|
||||
<TextField
|
||||
required
|
||||
style={{
|
||||
flex: "1",
|
||||
marginTop: "5px",
|
||||
marginRight: "15px",
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
}}
|
||||
fullWidth={true}
|
||||
type="name"
|
||||
id="outlined-with-placeholder"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
multiline={true}
|
||||
rows={2}
|
||||
placeholder="The X509 certificate to use"
|
||||
value={ssoCertificate}
|
||||
onChange={(e) => {
|
||||
setSsoCertificate(e.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: classes.notchedOutline,
|
||||
},
|
||||
style: {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</Grid>
|
||||
</Grid>
|
||||
{isCloud ?
|
||||
<Typography variant="body2" style={{textAlign: "left",}} color="textSecondary">
|
||||
IdP URL for Shuffle: https://shuffler.io/api/v1/login_sso
|
||||
</Typography>
|
||||
: null}
|
||||
</Grid>
|
||||
{isCloud ? null : (
|
||||
<Grid item xs={6} style={{}}>
|
||||
<span>
|
||||
<Typography>App Download URL</Typography>
|
||||
<TextField
|
||||
required
|
||||
style={{
|
||||
flex: "1",
|
||||
marginTop: "5px",
|
||||
marginRight: "15px",
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
}}
|
||||
fullWidth={true}
|
||||
type="name"
|
||||
id="outlined-with-placeholder"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
placeholder="A description for the organization"
|
||||
value={appDownloadUrl}
|
||||
onChange={(e) => {
|
||||
setAppDownloadUrl(e.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: classes.notchedOutline,
|
||||
},
|
||||
style: {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</Grid>
|
||||
)}
|
||||
{isCloud ? null : (
|
||||
<Grid item xs={6} style={{}}>
|
||||
<span>
|
||||
<Typography>App Download Branch</Typography>
|
||||
<TextField
|
||||
required
|
||||
style={{
|
||||
flex: "1",
|
||||
marginTop: "5px",
|
||||
marginRight: "15px",
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
}}
|
||||
fullWidth={true}
|
||||
type="name"
|
||||
id="outlined-with-placeholder"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
placeholder="A description for the organization"
|
||||
value={appDownloadBranch}
|
||||
onChange={(e) => {
|
||||
setAppDownloadBranch(e.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: classes.notchedOutline,
|
||||
},
|
||||
style: {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</Grid>
|
||||
)}
|
||||
{isCloud ? null : (
|
||||
<Grid item xs={6} style={{}}>
|
||||
<span>
|
||||
<Typography>Workflow Download URL</Typography>
|
||||
<TextField
|
||||
required
|
||||
style={{
|
||||
flex: "1",
|
||||
marginTop: "5px",
|
||||
marginRight: "15px",
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
}}
|
||||
fullWidth={true}
|
||||
type="name"
|
||||
id="outlined-with-placeholder"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
placeholder="A description for the organization"
|
||||
value={workflowDownloadUrl}
|
||||
onChange={(e) => {
|
||||
setWorkflowDownloadUrl(e.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: classes.notchedOutline,
|
||||
},
|
||||
style: {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</Grid>
|
||||
)}
|
||||
{isCloud ? null : (
|
||||
<Grid item xs={6} style={{}}>
|
||||
<span>
|
||||
<Typography>Workflow Download Branch</Typography>
|
||||
<TextField
|
||||
required
|
||||
style={{
|
||||
flex: "1",
|
||||
marginTop: "5px",
|
||||
marginRight: "15px",
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
}}
|
||||
fullWidth={true}
|
||||
type="name"
|
||||
id="outlined-with-placeholder"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
placeholder="A description for the organization"
|
||||
value={workflowDownloadBranch}
|
||||
onChange={(e) => {
|
||||
setWorkflowDownloadBranch(e.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: classes.notchedOutline,
|
||||
},
|
||||
style: {
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
<div style={{ margin: "auto", textalign: "center", marginTop: 15, marginBottom: 15, }}>
|
||||
{orgSaveButton}
|
||||
</div>
|
||||
{/*
|
||||
<span style={{textAlign: "center"}}>
|
||||
{expanded ?
|
||||
<ExpandLessIcon />
|
||||
:
|
||||
<ExpandMoreIcon />
|
||||
}
|
||||
</span>
|
||||
*/}
|
||||
</Grid>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default OrgHeaderexpanded;
|
||||
@@ -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) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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("= ");
|
||||
|
||||
@@ -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 <a href='/contact' style={hrefStyle}>Contact</a> 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 <a href='/contact' style={hrefStyle}>Contact</a> 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 <a href='/about' style={hrefStyle}>About</a> page!",
|
||||
],
|
||||
},
|
||||
{
|
||||
"question": "How does it work?",
|
||||
"answers": [
|
||||
"<a href='/' style={hrefStyle}>We've got you covered!</a>",
|
||||
],
|
||||
},
|
||||
{
|
||||
"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 <a href='mailto:adam@niceable.co' style={hrefStyle}>here (adam@niceable.co).</a>",
|
||||
"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": [
|
||||
"<a href='mailto:adam@niceable.co' style={hrefStyle}>Contact us here</Link> (adam@niceable.co)",
|
||||
],
|
||||
},
|
||||
{
|
||||
"question": "Can I host a prizedraw and donate the prize (because I’m a super nice person)?",
|
||||
"answers": [
|
||||
"<a href='mailto:adam@niceable.co' style={hrefStyle}>Contact us here</Link> (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("<div/>"))
|
||||
} else {
|
||||
setAnswers("")
|
||||
}
|
||||
}
|
||||
|
||||
const icon = answers === "" ? <ExpandMoreIcon /> : <ExpandLessIcon />
|
||||
|
||||
|
||||
return (
|
||||
<ListItem button onClick={() => loadAnswers()} style={{textAlign: "center"}}>
|
||||
<div style={{marginRight: 5, }}>{icon}</div>
|
||||
<ListItemText primary=<Typography variant="body1">{current.question}</Typography> secondary=<td style={{color: "rgba(255,255,255,0.8)"}} dangerouslySetInnerHTML={{__html: answers}} />/>
|
||||
</ListItem>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
const width = isMobile ? "100%" : 1000
|
||||
const FAQ =
|
||||
<div elevation={1} style={{padding: isMobile ? "20px 0px 10px 0px" : 100, textAlign: "center", color: "rgba(255,255,255,0.9)", minWidth: width, maxWidth: width, margin: "auto"}}>
|
||||
<Typography variant="h4" style={{textAlign: "center", marginBottom: 50, }}>
|
||||
Frequently asked questions
|
||||
</Typography>
|
||||
<Grid container spacing={4} style={{textAlign: "center", width: isMobile?"100%":"100%"}}>
|
||||
{pricingFaq.map((current) => {
|
||||
return (
|
||||
<Grid item xs={isMobile ? 12 : 6} key={current.question} style={{margin: "auto"}}>
|
||||
<HandleAnswer current={current} />
|
||||
<Divider style={{backgroundColor: "rgba(255,255,255,0.2)"}}/>
|
||||
</Grid>
|
||||
)
|
||||
})}
|
||||
</Grid>
|
||||
{/*
|
||||
<Divider />
|
||||
<Typography variant="body1" color="textPrimary" style={{textAlign: "left", marginTop: 15, marginBottom: 5}}>
|
||||
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!
|
||||
</Typography>
|
||||
*/}
|
||||
</div>
|
||||
|
||||
const landingpageData =
|
||||
<div style={{margin: "auto", maxWidth: isMobile ? "100%" : 2560, backgroundColor: theme.palette.inputColor}}>
|
||||
{FAQ}
|
||||
</div>
|
||||
|
||||
return (
|
||||
<div>
|
||||
{landingpageData}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Faq;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 = <Typography variant="body2" color="textSecondary" style={{marginTop: 30,}} >Billed anually or monthly at 1.2x the cost</Typography>
|
||||
const maxwidth = isMobile ? "91%" : 1024
|
||||
const activeIcon = <DoneIcon style={{color: "green"}} />
|
||||
const inActiveIcon = <ClearIcon style={{color: "red"}} />
|
||||
|
||||
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 =
|
||||
<div style={{maxWidth: maxWidth, minWidth: maxWidth, margin: "auto", textAlign: isMobile ? "center" : "left",}}>
|
||||
<div style={{margin: isMobile ? 20 : "0px 0px 80px 0px",}}>
|
||||
<Typography variant="h4" style={{marginTop: 100, textAlign: "center",}}>
|
||||
Our team is here to help you
|
||||
</Typography>
|
||||
<Typography variant="body1" style={{marginTop: 20, textAlign: "center",}}>
|
||||
We solve any problem that arises in a security operations center using YOUR tools. Here's how.
|
||||
</Typography>
|
||||
<Paper style={{maxWidth: paperWidth, minWidth: paperWidth, marginBottom: margin/2, marginTop: margin, backgroundColor: theme.palette.surfaceColor, color: "white", padding: isMobile ? 0 : margin, display: "flex", flexDirection: isMobile ? "column": "row", }}>
|
||||
<div style={{flex: 3, borderRight: "1px solid rgba(255,255,255,0.2)"}}>
|
||||
<List style={{marginRight: isMobile ? 0 : 40,}}>
|
||||
<ListItem button onClick={() => {navigate("/usecases")}} style={listItemStyle}>
|
||||
<ListItemText primary={"Free Usecases"} />
|
||||
</ListItem>
|
||||
<ListItem button onClick={() => {}} style={listItemStyle}>
|
||||
<ListItemText primary={"Hybrid integration"} />
|
||||
</ListItem>
|
||||
<ListItem button onClick={() => {"https://discord.gg/B2CBzUm"}} style={listItemStyle}>
|
||||
<ListItemText primary={"Open Source community"} />
|
||||
</ListItem>
|
||||
</List>
|
||||
</div>
|
||||
<div style={{flex: 6, marginLeft: isMobile ? margin : 40, marginRight: isMobile ? margin: 0, marginTop: isMobile ? 30 : 0}}>
|
||||
<Typography variant="h4" style={{}}>
|
||||
Accessibility first
|
||||
</Typography>
|
||||
<Typography variant="body1" style={{marginTop: 20,}}>
|
||||
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.
|
||||
</Typography>
|
||||
</div>
|
||||
</Paper>
|
||||
<Paper style={{maxWidth: paperWidth, minWidth: paperWidth, marginBottom: margin/2, marginTop: margin, backgroundColor: theme.palette.surfaceColor, color: "white", padding: isMobile ? 0 : margin, display: "flex", flexDirection: isMobile ? "column": "row",}}>
|
||||
<div style={{flex: 6, marginRight: 40, textAlign: "center",}}>
|
||||
<Typography variant="h4" style={{}}>
|
||||
Automation Services
|
||||
</Typography>
|
||||
<Typography variant="body1" style={{marginTop: 20,}}>
|
||||
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.
|
||||
</Typography>
|
||||
</div>
|
||||
<div style={{flex: 3, borderLeft: "1px solid rgba(255,255,255,0.2)"}}>
|
||||
<List style={{marginLeft: isMobile ? 0 : 40,}}>
|
||||
<ListItem button onClick={() => {}} style={listItemStyle}>
|
||||
<ListItemText primary={"Workflow Creation"} />
|
||||
</ListItem>
|
||||
<ListItem button onClick={() => {}} style={listItemStyle}>
|
||||
<ListItemText primary={"App Creation"} />
|
||||
</ListItem>
|
||||
<ListItem button onClick={() => {}} style={listItemStyle}>
|
||||
<ListItemText primary={"Custom Development"} />
|
||||
</ListItem>
|
||||
</List>
|
||||
</div>
|
||||
</Paper>
|
||||
<Paper style={{maxWidth: paperWidth, minWidth: paperWidth, marginBottom: margin/2, marginTop: margin, backgroundColor: theme.palette.surfaceColor, color: "white", padding: isMobile ? 0 : margin, display: "flex",flexDirection: isMobile ? "column": "row",}}>
|
||||
<div style={{flex: 3, borderRight: "1px solid rgba(255,255,255,0.2)"}}>
|
||||
<List style={{marginRight: 40,}}>
|
||||
<ListItem button onClick={() => {}} style={listItemStyle}>
|
||||
<ListItemText primary={"Quick response times"} />
|
||||
</ListItem>
|
||||
<ListItem button onClick={() => {}} style={listItemStyle}>
|
||||
<ListItemText primary={"Upgrades & configurations"} />
|
||||
</ListItem>
|
||||
<ListItem button onClick={() => {}} style={listItemStyle}>
|
||||
<ListItemText primary={"Business hour support"} />
|
||||
</ListItem>
|
||||
</List>
|
||||
</div>
|
||||
<div style={{flex: 6, marginLeft: isMobile ? 0 :40,}}>
|
||||
<Typography variant="h4" style={{}}>
|
||||
Support and Maintenance
|
||||
</Typography>
|
||||
<Typography variant="body1" color="textSecondary" style={{marginTop: 20,}}>
|
||||
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.
|
||||
</Typography>
|
||||
</div>
|
||||
</Paper>
|
||||
<Paper style={{maxWidth: paperWidth, minWidth: paperWidth, marginBottom: margin/2, marginTop: margin, backgroundColor: theme.palette.surfaceColor, color: "white", padding: isMobile ? 0 : margin, display: "flex",flexDirection: isMobile ? "column": "row",}}>
|
||||
<div style={{flex: 6, marginRight: 40,}}>
|
||||
<Typography variant="h4" style={{}}>
|
||||
Training
|
||||
</Typography>
|
||||
<Typography variant="body1" style={{marginTop: 20,}}>
|
||||
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.
|
||||
</Typography>
|
||||
</div>
|
||||
<div style={{flex: 3, borderLeft: "1px solid rgba(255,255,255,0.2)"}}>
|
||||
<List style={{marginLeft: isMobile ? 0 : 40,}}>
|
||||
<ListItem button onClick={() => {}} style={listItemStyle}>
|
||||
<ListItemText primary={"Administration"} />
|
||||
</ListItem>
|
||||
<ListItem button onClick={() => {}} style={listItemStyle}>
|
||||
<ListItemText primary={"Configuration"} />
|
||||
</ListItem>
|
||||
<ListItem button onClick={() => {}} style={listItemStyle}>
|
||||
<ListItemText primary={"Platform Development"} />
|
||||
</ListItem>
|
||||
</List>
|
||||
</div>
|
||||
</Paper>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
return (
|
||||
<div style={{paddingBottom: removeAdditions ? 0 : 100}}>
|
||||
{topRet}
|
||||
{removeAdditions === true ? null :
|
||||
<span>
|
||||
<FAQ theme={theme} type={"pricing"}/>
|
||||
<div style={{marginTop: 100, marginBottom: 100,}}>
|
||||
<Typography variant="h4">
|
||||
Got other questions?
|
||||
</Typography>
|
||||
<Typography variant="body1" color="textSecondary" style={{marginBottom: 200}}>
|
||||
If you got more questions about our pricing and plans, please <Link to="/contact" style={{textDecoration: "none", color: theme.palette.primary.main}}>contact us</Link> so we can help
|
||||
</Typography>
|
||||
<Newsletter />
|
||||
</div>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PaymentField;
|
||||
Reference in New Issue
Block a user