Added license popup properly
This commit is contained in:
@@ -0,0 +1,935 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import ReactGA from 'react-ga4';
|
||||
|
||||
import theme from "../theme.jsx";
|
||||
import { useTheme } from "@mui/styles";
|
||||
import countries from "../components/Countries.jsx";
|
||||
import {
|
||||
Box,
|
||||
Paper,
|
||||
Typography,
|
||||
Divider,
|
||||
Button,
|
||||
Grid,
|
||||
Card,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
TextField,
|
||||
InputAdornment,
|
||||
IconButton,
|
||||
Chip,
|
||||
Checkbox,
|
||||
Tooltip,
|
||||
Slider,
|
||||
DialogActions,
|
||||
CardContent,
|
||||
ButtonGroup,
|
||||
} from "@mui/material";
|
||||
|
||||
import { useNavigate, Link } from "react-router-dom";
|
||||
import { Autocomplete } from "@mui/material";
|
||||
import { toast } from "react-toastify"
|
||||
|
||||
import {
|
||||
Cached as CachedIcon,
|
||||
ContentCopy as ContentCopyIcon,
|
||||
Draw as DrawIcon,
|
||||
Close as CloseIcon,
|
||||
Done as DoneIcon,
|
||||
Clear as ClearIcon,
|
||||
AddTask as AddTaskIcon,
|
||||
} from "@mui/icons-material";
|
||||
|
||||
import { typecost, typecost_single, } from "../views/HandlePaymentNew.jsx";
|
||||
import { handlePayasyougo } from "../views/HandlePaymentNew.jsx"
|
||||
|
||||
const LicencePopup = (props) => {
|
||||
const { globalUrl, userdata, serverside, billingInfo, stripeKey, setModalOpen, isLoggedIn, isMobile } = props;
|
||||
//const alert = useAlert();
|
||||
let navigate = useNavigate();
|
||||
const isCloud = typeof window === 'undefined' || window === undefined || window.location === undefined ? true : window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
||||
|
||||
const [selectedDealModalOpen, setSelectedDealModalOpen] = React.useState(false);
|
||||
const [dealList, setDealList] = React.useState([]);
|
||||
const [dealName, setDealName] = React.useState("");
|
||||
const [dealAddress, setDealAddress] = React.useState("");
|
||||
const [dealType, setDealType] = React.useState("MSSP");
|
||||
const [selectedOrganization, setSelectedOrganization] = React.useState({});
|
||||
const [dealCountry, setDealCountry] = React.useState("United States");
|
||||
const [dealCurrency, setDealCurrency] = React.useState("USD");
|
||||
const [dealStatus, setDealStatus] = React.useState("initiated");
|
||||
const [dealValue, setDealValue] = React.useState("");
|
||||
const [dealDiscount, setDealDiscount] = React.useState("");
|
||||
const [dealerror, setDealerror] = React.useState("");
|
||||
const [variant, setVariant] = useState(0)
|
||||
const [shuffleVariant, setShuffleVariant] = useState(isCloud ? 0 : 1)
|
||||
|
||||
|
||||
// const parsedFields = maxFields === undefined ? 300 : maxFields
|
||||
const initialShuffleVariant = isCloud ? 0 : 1;
|
||||
const [paymentType, setPaymentType] = useState(0)
|
||||
const [currentPrice, setCurrentPrice] = useState(129)
|
||||
const [isLoaded, setIsLoaded] = useState(false)
|
||||
const [errorMessage, setErrorMessage] = useState("")
|
||||
const [highlight, setHighlight] = useState(false)
|
||||
|
||||
// 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)
|
||||
|
||||
const payasyougo = "Pay as you go"
|
||||
const stripe = typeof window === 'undefined' || window.location === undefined ? "" : props.stripeKey === undefined ? "" : window.Stripe ? window.Stripe(props.stripeKey) : ""
|
||||
|
||||
const paperStyle = {
|
||||
padding: 20,
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
height: "100%",
|
||||
}
|
||||
|
||||
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": [
|
||||
"Basic Support",
|
||||
"Limited App Runs (10.000)",
|
||||
],
|
||||
"limit": 10000,
|
||||
}
|
||||
|
||||
const sendSignatureRequest = (subscription) => {
|
||||
const url = `${globalUrl}/api/v1/orgs/${selectedOrganization.id}`;
|
||||
|
||||
fetch(url, {
|
||||
body: JSON.stringify({
|
||||
org_id: selectedOrganization.id,
|
||||
subscription: subscription,
|
||||
}),
|
||||
mode: "cors",
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
crossDomain: true,
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Error in response");
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
console.log("Response from signature request: ", responseJson);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("Error: ", error);
|
||||
})
|
||||
}
|
||||
|
||||
const SubscriptionObject = (props) => {
|
||||
const { globalUrl, index, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, subscription, highlight, } = props;
|
||||
|
||||
const [signatureOpen, setSignatureOpen] = React.useState(false);
|
||||
const [tosChecked, setTosChecked] = React.useState(subscription.eula_signed)
|
||||
const [hovered, setHovered] = React.useState(false)
|
||||
|
||||
var top_text = "Base Cloud Access"
|
||||
if (subscription.limit === undefined && subscription.level === undefined || subscription.level === null || subscription.level === 0) {
|
||||
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",
|
||||
]
|
||||
}
|
||||
|
||||
var newPaperstyle = JSON.parse(JSON.stringify(paperStyle))
|
||||
if (subscription.name === "Enterprise" && subscription.active === true) {
|
||||
top_text = "Current Plan"
|
||||
|
||||
newPaperstyle.border = "1px solid #f85a3e"
|
||||
}
|
||||
|
||||
var showSupport = false
|
||||
if (subscription.name.includes("default")) {
|
||||
top_text = "Custom Contract"
|
||||
newPaperstyle.border = "1px solid #f85a3e"
|
||||
showSupport = true
|
||||
}
|
||||
|
||||
if (subscription.name.includes("App Run Units")) {
|
||||
top_text = "Cloud Access"
|
||||
showSupport = true
|
||||
}
|
||||
|
||||
if (subscription.name.includes("Open Source")) {
|
||||
top_text = "Open Source"
|
||||
showSupport = true
|
||||
}
|
||||
|
||||
if (subscription.name.includes("Scale")) {
|
||||
top_text = "Scale access"
|
||||
}
|
||||
|
||||
if (highlight === true) {
|
||||
// Add an "Upgrade now" button
|
||||
// newPaperstyle.border = "1px solid #f85a3e"
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
style={{ borderRadius: theme.palette.borderRadius, }}
|
||||
placement="bottom"
|
||||
>
|
||||
<div style={{}}>
|
||||
<Paper
|
||||
style={newPaperstyle}
|
||||
// onMouseEnter={() => setHovered(true)}
|
||||
// onMouseLeave={() => setHovered(false)}
|
||||
>
|
||||
<Dialog
|
||||
open={signatureOpen}
|
||||
PaperProps={{
|
||||
style: {
|
||||
pointerEvents: "auto",
|
||||
color: "white",
|
||||
minWidth: 750,
|
||||
padding: 30,
|
||||
maxHeight: 700,
|
||||
overflowY: "auto",
|
||||
overflowX: "hidden",
|
||||
zIndex: 10012,
|
||||
// border: theme.palette.defaultBorder,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Tooltip
|
||||
title="Close window"
|
||||
placement="top"
|
||||
style={{ zIndex: 10011 }}
|
||||
>
|
||||
<IconButton
|
||||
style={{ zIndex: 5000, position: "absolute", top: 34, right: 34 }}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setSignatureOpen(false);
|
||||
setTosChecked(false)
|
||||
}}
|
||||
>
|
||||
<CloseIcon style={{ color: "white" }} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<DialogTitle id="form-dialog-title">Read and Accept the EULA</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
rows={17}
|
||||
multiline
|
||||
fullWidth
|
||||
InputProps={{
|
||||
readOnly: true,
|
||||
style: {
|
||||
fontSize: 14,
|
||||
color: "rgba(255, 255, 255, 0.6)",
|
||||
}
|
||||
}}
|
||||
value={subscription.eula}
|
||||
/>
|
||||
<Checkbox
|
||||
disabled={subscription.eula_signed}
|
||||
checked={tosChecked}
|
||||
onChange={(e) => {
|
||||
setTosChecked(e.target.checked)
|
||||
}}
|
||||
inputProps={{ 'aria-label': 'primary checkbox' }}
|
||||
/>
|
||||
<Typography variant="body1" style={{ display: "inline-block", marginLeft: 10, marginTop: 25, cursor: "pointer", }} onClick={() => {
|
||||
setTosChecked(!tosChecked)
|
||||
}}>
|
||||
Accept
|
||||
</Typography>
|
||||
<Typography variant="body2" style={{ display: "inline-block", marginLeft: 10, }} color="textSecondary">
|
||||
By clicking the “accept” button, you are signing the document, electronically agreeing that it has the same legal validity and effects as a handwritten signature, and that you have the competent authority to represent and sign on behalf an entity. Need support or have questions? Contact us at support@shuffler.io.
|
||||
</Typography>
|
||||
|
||||
<div style={{ display: "flex", marginTop: 25, }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
style={{ marginLeft: "auto", }}
|
||||
disabled={!tosChecked || subscription.eula_signed}
|
||||
onClick={() => {
|
||||
setSignatureOpen(false)
|
||||
subscription.eula_signed = true
|
||||
sendSignatureRequest(subscription)
|
||||
}}
|
||||
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<Button style={{ backgroundColor: '#2F2F2F', color: "white", textTransform: "capitalize", borderRadius: 200, boxShadow: 'none', width: 144, height: 40 }}>Current Plan</Button>
|
||||
<div style={{ display: "flex" }}>
|
||||
{top_text === "Base Cloud Access" && userdata.has_card_available === true ?
|
||||
<Chip
|
||||
style={{
|
||||
backgroundColor: "#f86a3e",
|
||||
paddingLeft: 5,
|
||||
paddingRight: 5,
|
||||
height: 28,
|
||||
cursor: "pointer",
|
||||
borderColor: "#3d3f43",
|
||||
color: "white",
|
||||
marginTop: 10,
|
||||
marginRight: 30,
|
||||
border: "1px solid rgba(255,255,255,0.3)",
|
||||
}}
|
||||
label={"Unlimited"}
|
||||
onClick={() => {
|
||||
console.log("Clicked chip")
|
||||
}}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
/>
|
||||
: null}
|
||||
<Typography variant="h6" style={{ marginTop: 10, marginBottom: 10, flex: 5, }}>
|
||||
{top_text}
|
||||
</Typography>
|
||||
|
||||
{top_text === "Base Cloud Access" && userdata.has_card_available === false ?
|
||||
<img
|
||||
src="/images/stripenew.png"
|
||||
style={{
|
||||
margin: "auto",
|
||||
width: 100,
|
||||
backgroundColor: "white",
|
||||
// borderRadius: theme.palette.borderRadius,
|
||||
}}
|
||||
/>
|
||||
: null}
|
||||
{isCloud && highlight === true && top_text !== "Base Cloud Access" ?
|
||||
<Tooltip
|
||||
title="Sign EULA"
|
||||
placement="top"
|
||||
style={{ zIndex: 10011 }}
|
||||
>
|
||||
<IconButton
|
||||
disabled={subscription.eula_signed}
|
||||
style={{ marginLeft: "auto", marginTop: 10, marginBottom: 10, flex: 1, }}
|
||||
onClick={() => {
|
||||
setSignatureOpen(true)
|
||||
}}
|
||||
>
|
||||
<DrawIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
: null}
|
||||
</div>
|
||||
<Divider />
|
||||
<div>
|
||||
<Typography variant="body1" style={{ marginTop: 20, }}>
|
||||
{subscription.name}
|
||||
</Typography>
|
||||
|
||||
{subscription.currency_text !== undefined ?
|
||||
<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>
|
||||
: null}
|
||||
|
||||
<Typography variant="body2" color="textSecondary" style={{ marginTop: 10, }}>
|
||||
Features
|
||||
</Typography>
|
||||
<ul>
|
||||
{subscription.features !== undefined && subscription.features !== null ?
|
||||
subscription.features.map((feature, index) => {
|
||||
var parsedFeature = feature
|
||||
if (feature.includes("Documentation: ")) {
|
||||
parsedFeature =
|
||||
<a
|
||||
href={feature.split("Documentation: ")[1]}
|
||||
target="_blank"
|
||||
style={{ textDecoration: "none", color: "#f85a3e", }}
|
||||
>
|
||||
Documentation to get started
|
||||
</a>
|
||||
}
|
||||
|
||||
if (feature.includes("Worker License: ")) {
|
||||
const fieldId = "webhook_uri_field_" + index
|
||||
parsedFeature =
|
||||
<span style={{ marginTop: 10, }}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
>
|
||||
Use the {feature.split("Worker License: ")[0]} Worker
|
||||
</Typography>
|
||||
<TextField
|
||||
value={feature.split("Worker License: ")[1]}
|
||||
style={{
|
||||
// backgroundColor: theme.palette.inputColor,
|
||||
// borderRadius: theme.palette.borderRadius,
|
||||
}}
|
||||
id={fieldId}
|
||||
onClick={() => { }}
|
||||
InputProps={{
|
||||
endAdornment:
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
aria-label="Copy webhook"
|
||||
onClick={() => {
|
||||
var copyText = document.getElementById(fieldId);
|
||||
if (copyText !== undefined && copyText !== null) {
|
||||
console.log("NAVIGATOR: ", navigator);
|
||||
const clipboard = navigator.clipboard;
|
||||
if (clipboard === undefined) {
|
||||
toast("Can only copy over HTTPS (port 3443)");
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.clipboard.writeText(copyText.value);
|
||||
copyText.select();
|
||||
copyText.setSelectionRange(
|
||||
0,
|
||||
99999
|
||||
); /* For mobile devices */
|
||||
|
||||
/* Copy the text inside the text field */
|
||||
document.execCommand("copy");
|
||||
toast("Copied Webhook URL");
|
||||
} else {
|
||||
console.log("Couldn't find webhook URI field: ", copyText);
|
||||
}
|
||||
}}
|
||||
edge="end"
|
||||
>
|
||||
<ContentCopyIcon />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
}}
|
||||
fullWidth
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={index}>
|
||||
<Typography variant="body2" color="textPrimary" style={{}}>
|
||||
{parsedFeature}
|
||||
</Typography>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
: null}
|
||||
</ul>
|
||||
Billing email: {selectedOrganization.org}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
style={{
|
||||
marginLeft: 115,
|
||||
marginTop: 50,
|
||||
borderRadius: 20,
|
||||
width: 120,
|
||||
color: "#FFFFFF",
|
||||
cursor: "pointer",
|
||||
textTransform: "capitalize",
|
||||
backgroundColor: "transparent",
|
||||
position: "relative", // Required for positioning tooltip
|
||||
}}
|
||||
title="Click to book a call"
|
||||
onClick={() => {
|
||||
if (isCloud) {
|
||||
ReactGA.event({
|
||||
category: "header",
|
||||
action: "bookcall_upgread_popup",
|
||||
label: "",
|
||||
})};
|
||||
window.open("https://drift.me/frikky", "_blank");
|
||||
// if (isLoggedIn) {
|
||||
// isLoggedInHandler()
|
||||
// } else {
|
||||
// navigate(`/register?view=pricing&message=You need to create a user to continue`)
|
||||
// }
|
||||
}}
|
||||
>
|
||||
Book a call
|
||||
<span
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "100%",
|
||||
left: "50%",
|
||||
transform: "translateX(-50%)",
|
||||
backgroundColor: "rgba(0, 0, 0, 0.8)",
|
||||
color: "#FFFFFF",
|
||||
borderRadius: "4px",
|
||||
padding: "4px 8px",
|
||||
fontSize: "14px",
|
||||
whiteSpace: "nowrap",
|
||||
opacity: 0,
|
||||
transition: "visibility 0s, opacity 0.1s linear",
|
||||
}}
|
||||
>
|
||||
Click to book a call
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
|
||||
</Paper>
|
||||
{/*
|
||||
<div style={{ paddingRight: 150, display: "flex", alignItems: "baseline" }}>
|
||||
|
||||
<Link to="https://drift.me/frikky/meeting" target="_blank" rel="noopener noreferrer" style={{ color: theme.palette.primary.main, marginLeft: 10 }}>Schedule Call Now</Link>
|
||||
</div>
|
||||
*/}
|
||||
|
||||
</div>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
console.log("New variant: ", shuffleVariant)
|
||||
|
||||
if (shuffleVariant === 1) {
|
||||
setCalculatedCost("$600")
|
||||
setSelectedValue(8)
|
||||
} else {
|
||||
setCalculatedCost("$540")
|
||||
setSelectedValue(300)
|
||||
}
|
||||
}, [shuffleVariant])
|
||||
|
||||
if (typeof window === 'undefined' || window.location === undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
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 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 < 300) {
|
||||
setCalculatedCost(`Pay as you go`)
|
||||
} else if (newValue === 1000) {
|
||||
setCalculatedCost(`Get A Quote`)
|
||||
} else {
|
||||
setCalculatedCost(`$${newValue * 1000 * typecost}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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") {
|
||||
setShuffleVariant(1);
|
||||
} else if (foundTab === "cloud") {
|
||||
setShuffleVariant(0);
|
||||
}
|
||||
}
|
||||
|
||||
const foundHighlight = params["highlight"];
|
||||
if (foundHighlight !== null && foundHighlight !== undefined) {
|
||||
setHighlight(true)
|
||||
}
|
||||
}
|
||||
|
||||
//const skipFreemode = window.location.pathname.startsWith("/admin")
|
||||
const skipFreemode = false
|
||||
const maxwidth = isMobile ? "91%" : skipFreemode ? 1100 : 1200
|
||||
const activeIcon = <DoneIcon style={{ color: "green" }} />
|
||||
const inActiveIcon = <ClearIcon style={{ color: "red" }} />
|
||||
const defaultTaskIcon = <AddTaskIcon style={{ marginRight: 10, marginTop: 5, }} />
|
||||
|
||||
const buttonBackground = "linear-gradient(to right, #f86a3e, #f34079)"
|
||||
const level1Button =
|
||||
<Button fullWidth variant="contained" color="primary" style={{ borderRadius: 25, height: 40, margin: "15px 0px 15px 0px", fontSize: 14, color: "white", backgroundImage: buttonBackground, }} onClick={() => {
|
||||
setMonthlyCost(0, paymentType)
|
||||
setVariant(0)
|
||||
setModalOpen(true)
|
||||
ReactGA.event({
|
||||
category: "pricing",
|
||||
action: `hybrid_click`,
|
||||
label: "",
|
||||
})
|
||||
}}>
|
||||
Get hybrid access
|
||||
</Button>
|
||||
|
||||
const level2Button =
|
||||
<Button fullWidth disabled={false} variant="outlined" color="primary" style={{ marginTop: shuffleVariant === 0 ? 20 : 45, borderRadius: 25, height: 40, fontSize: 14, color: isLoggedIn && shuffleVariant === 0 ? "black" : "white", backgroundImage: isLoggedIn && shuffleVariant === 0 ? "inherit" : buttonBackground, }} onClick={() => {
|
||||
|
||||
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
|
||||
</Button>
|
||||
|
||||
const level3Button = skipFreemode ? null :
|
||||
<Button fullWidth disabled={false} variant="contained" color="primary" style={{ borderRadius: 25, height: 40, margin: "15px 0px 15px 0px", fontSize: 14, color: "white", backgroundImage: buttonBackground, }} onClick={() => {
|
||||
|
||||
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"
|
||||
}
|
||||
}}>
|
||||
Start building!
|
||||
</Button>
|
||||
|
||||
|
||||
const cardStyle = {
|
||||
// height: "100%",
|
||||
// width: "100%",
|
||||
// textAlign: "center",
|
||||
color: "white",
|
||||
}
|
||||
|
||||
|
||||
const isLoggedInHandler = () => {
|
||||
if (calculatedCost === payasyougo) {
|
||||
handlePayasyougo(props.userdata)
|
||||
return
|
||||
}
|
||||
|
||||
const priceItem = window.location.origin === "https://shuffler.io" ?
|
||||
shuffleVariant === 0 ? "app_executions" : "cores"
|
||||
:
|
||||
shuffleVariant === 0 ? "price_1MROFrDzMUgUjxHShcSxgHO1" : "price_1NXjQqDzMUgUjxHSg690R4FP"
|
||||
|
||||
const successUrl = `${window.location.origin}/admin?admin_tab=billing&payment=success`
|
||||
const failUrl = `${window.location.origin}/pricing?admin_tab=billing&payment=failure`
|
||||
|
||||
console.log("Priceitem: ", priceItem, shuffleVariant)
|
||||
var checkoutObject = {
|
||||
lineItems: [
|
||||
{
|
||||
price: priceItem,
|
||||
quantity: shuffleVariant === 0 ? selectedValue / 100 : selectedValue,
|
||||
},
|
||||
],
|
||||
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: "",
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Grid container spacing={2} columns={16} style={{ flexDirection: "row", flexWrap: "nowrap", borderRadius: '16px', display: "flex" }}>
|
||||
<Grid item xs={8}>
|
||||
{isCloud && billingInfo.subscription !== undefined && billingInfo.subscription !== null ?
|
||||
<SubscriptionObject
|
||||
index={0}
|
||||
globalUrl={globalUrl}
|
||||
userdata={userdata}
|
||||
serverside={serverside}
|
||||
billingInfo={billingInfo}
|
||||
stripeKey={stripeKey}
|
||||
selectedOrganization={selectedOrganization}
|
||||
subscription={billingInfo.subscription}
|
||||
highlight={selectedOrganization.subscriptions === undefined || selectedOrganization.subscriptions === null || selectedOrganization.subscriptions.length === 0}
|
||||
/>
|
||||
: !isCloud ?
|
||||
<span style={{ display: "flex", }}>
|
||||
<SubscriptionObject
|
||||
index={0}
|
||||
globalUrl={globalUrl}
|
||||
userdata={userdata}
|
||||
serverside={false}
|
||||
billingInfo={undefined}
|
||||
selectedOrganization={selectedOrganization}
|
||||
subscription={{
|
||||
name: "Open Source",
|
||||
limit: 0,
|
||||
features: [
|
||||
"Unlimited app runs/month, but may be slow. Only limited by CPU.",
|
||||
"Multi-Tenancy",
|
||||
"Single-Sign-On",
|
||||
"Cloud Sync",
|
||||
],
|
||||
}}
|
||||
highlight={true}
|
||||
/>
|
||||
<SubscriptionObject
|
||||
index={1}
|
||||
globalUrl={globalUrl}
|
||||
userdata={userdata}
|
||||
serverside={false}
|
||||
billingInfo={undefined}
|
||||
selectedOrganization={selectedOrganization}
|
||||
subscription={{
|
||||
name: "Scale",
|
||||
limit: 0,
|
||||
features: [
|
||||
"All Open Source features",
|
||||
"Scale License. Runs faster, and across multiple servers.",
|
||||
"Priority Support",
|
||||
"Workflow & App development help",
|
||||
],
|
||||
}}
|
||||
highlight={false}
|
||||
/>
|
||||
</span>
|
||||
: null}
|
||||
|
||||
{isCloud &&
|
||||
selectedOrganization.subscriptions !== undefined &&
|
||||
selectedOrganization.subscriptions !== null &&
|
||||
selectedOrganization.subscriptions.length > 0 ?
|
||||
selectedOrganization.subscriptions
|
||||
.reverse()
|
||||
.map((sub, index) => {
|
||||
return (
|
||||
<SubscriptionObject
|
||||
index={index + 1}
|
||||
globalUrl={globalUrl}
|
||||
userdata={userdata}
|
||||
serverside={serverside}
|
||||
billingInfo={billingInfo}
|
||||
stripeKey={stripeKey}
|
||||
selectedOrganization={selectedOrganization}
|
||||
subscription={sub}
|
||||
highlight={true}
|
||||
/>
|
||||
)
|
||||
})
|
||||
: null}
|
||||
</Grid>
|
||||
<Grid item xs={8}>
|
||||
<Grid style={{}}>
|
||||
{errorMessage.length > 0 ? <Typography variant="h4">Error: {errorMessage}</Typography> : null}
|
||||
<Card style={{
|
||||
padding: 20,
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
border: "1px solid #f85a3e",
|
||||
}}>
|
||||
<div>
|
||||
<Button style={{ backgroundColor: 'rgba(255, 132, 68, 0.2)', color: "#FF8444", textTransform: "capitalize", borderRadius: 200, boxShadow: 'none', }}
|
||||
variant="contained"
|
||||
color="primary">Recommended </Button>
|
||||
|
||||
</div>
|
||||
<Typography variant="h6" style={{ marginTop: 10, marginBottom: 10, flex: 5, }}>{shuffleVariant === 1 ? "Scale" : "Enterprise"}</Typography>
|
||||
<Divider />
|
||||
<Typography variant="body1" color="textSecondary" style={{ marginTop: 20 }}>
|
||||
{shuffleVariant === 0 ?
|
||||
"SaaS / Cloud - Per Month"
|
||||
:
|
||||
"Open Source + Scale License"
|
||||
}
|
||||
</Typography>
|
||||
|
||||
<Typography style={{ minHeight: 46, cursor: calculatedCores === "Get A Quote" ? "pointer" : "inherit", }} onClick={() => {
|
||||
|
||||
if (calculatedCores === "Get A Quote") {
|
||||
console.log("Clicked on get a quote")
|
||||
if (window.drift !== undefined) {
|
||||
window.drift.api.startInteraction({ interactionId: 340785 })
|
||||
}
|
||||
}
|
||||
}}>{calculatedCost}</Typography>
|
||||
<Typography variant="body1" color="textSecondary" style={{}}>For {shuffleVariant === 1 ? `${selectedValue} CPU cores` : `${selectedValue}k App Runs`}: </Typography>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
|
||||
<Slider
|
||||
aria-label="Small steps"
|
||||
style={{ width: "80%", margin: "auto" }}
|
||||
onChange={(event, newValue) => {
|
||||
handleChange(event, newValue)
|
||||
}}
|
||||
marks
|
||||
value={selectedValue}
|
||||
step={shuffleVariant === 0 ? 100 : 4}
|
||||
min={shuffleVariant === 0 ? 100 : 8}
|
||||
max={shuffleVariant === 0 ? 1000 : 32}
|
||||
valueLabelDisplay="auto"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<span>{defaultTaskIcon}</span>
|
||||
<Typography style={{ fontSize: 14, marginLeft: 8 }}>Priority Support</Typography>
|
||||
</div>
|
||||
<Divider />
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<span>{defaultTaskIcon}</span>
|
||||
<Typography style={{ fontSize: 14, marginLeft: 8 }}>
|
||||
{shuffleVariant === 0 ? "Multi-Tenant" : "Scalable Orborus"}
|
||||
</Typography>
|
||||
</div>
|
||||
<Divider />
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<span>{defaultTaskIcon}</span>
|
||||
<Typography style={{ fontSize: 14, marginLeft: 8 }}>
|
||||
{shuffleVariant === 0 ? "Multi-Region Tenants" : "High Availability"}
|
||||
</Typography>
|
||||
</div>
|
||||
<Divider />
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<span>{defaultTaskIcon}</span>
|
||||
<Typography style={{ fontSize: 14, marginLeft: 8 }}>Help with Workflow and App development</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ marginTop: 20, }} />
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<DialogActions style={{ marginTop: 15 }} >
|
||||
<Button
|
||||
style={{ borderRadius: "0px", textTransform: "capitalize" }}
|
||||
onClick={() => {
|
||||
if (isCloud) {
|
||||
ReactGA.event({
|
||||
category: "header",
|
||||
action: "viewplan_upgread_popup",
|
||||
label: "",
|
||||
})};
|
||||
navigate("/pricing")
|
||||
setModalOpen(false)
|
||||
}}
|
||||
color="primary"
|
||||
>
|
||||
View all plans
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
style={{ borderRadius: 20, width: 120, textTransform: "capitalize" }}
|
||||
onClick={() => {
|
||||
if (isCloud) {
|
||||
ReactGA.event({
|
||||
category: "header",
|
||||
action: "upgread_clicks_popup",
|
||||
label: "",
|
||||
})};
|
||||
if (isLoggedIn) {
|
||||
isLoggedInHandler()
|
||||
} else {
|
||||
navigate(`/register?view=pricing&message=You need to create a user to continue`)
|
||||
}
|
||||
}}
|
||||
color="primary"
|
||||
>
|
||||
Upgrade
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LicencePopup;
|
||||
@@ -5,6 +5,7 @@ import { BrowserView, MobileView } from "react-device-detect";
|
||||
|
||||
import { useNavigate, Link } from "react-router-dom";
|
||||
import ReactGA from "react-ga4";
|
||||
import LicencePopup from "../components/LicencePopup.jsx";
|
||||
import SearchField from "../components/Searchfield.jsx";
|
||||
import {
|
||||
Paper,
|
||||
@@ -24,6 +25,8 @@ import {
|
||||
Divider,
|
||||
LinearProgress,
|
||||
AppBar,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
} from "@mui/material";
|
||||
|
||||
import {
|
||||
@@ -59,9 +62,8 @@ const Header = (props) => {
|
||||
userdata,
|
||||
isMobile,
|
||||
serverside,
|
||||
setModalOpen,
|
||||
|
||||
curpath,
|
||||
billingInfo
|
||||
} = props;
|
||||
|
||||
|
||||
@@ -71,11 +73,13 @@ const Header = (props) => {
|
||||
const [DocsHoverColor, setDocsHoverColor] = useState(hoverOutColor);
|
||||
const [HelpHoverColor, setHelpHoverColor] = useState(hoverOutColor);
|
||||
const [isHeader, setIsHeader] = React.useState(false);
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [anchorEl, setAnchorEl] = React.useState(null);
|
||||
const [anchorElAvatar, setAnchorElAvatar] = React.useState(null);
|
||||
const [subAnchorEl, setSubAnchorEl] = React.useState(null);
|
||||
const [upgradeHovered, setUpgradeHovered] = React.useState(false);
|
||||
const [showTopbar, setShowTopbar] = useState(false)
|
||||
const stripeKey = typeof window === 'undefined' || window.location === undefined ? "" : window.location.origin === "https://shuffler.io" ? "pk_live_XAxwE2Fp9DEbEcNYw4UKmyby00vIlIPPRp" : "pk_test_EdxgKfqmQGXY5JLjdBqtuhCw00BHbiKJDB"
|
||||
let navigate = useNavigate();
|
||||
|
||||
const handleClick = (event) => {
|
||||
@@ -679,6 +683,80 @@ const Header = (props) => {
|
||||
marginRight: 10,
|
||||
};
|
||||
|
||||
const modalView = (
|
||||
<>
|
||||
{modalOpen && (
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
||||
zIndex: 1299,
|
||||
backdropFilter: "blur(5px)",
|
||||
}}
|
||||
></div>
|
||||
)}
|
||||
<Dialog
|
||||
open={modalOpen}
|
||||
onClose={() => {
|
||||
setModalOpen(false);
|
||||
}}
|
||||
PaperProps={{
|
||||
style: {
|
||||
color: "white",
|
||||
minWidth: 850,
|
||||
minHeight: 370,
|
||||
padding: 10,
|
||||
backgroundColor: "rgba(0, 0, 0, 1)",
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogTitle style={{ display: "flex" }}>
|
||||
<span style={{ color: "white", fontSize: 24 }}>
|
||||
Upgrade your plan
|
||||
</span>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
if (isCloud) {
|
||||
ReactGA.event({
|
||||
category: "header",
|
||||
action: "close_Upgread_popup",
|
||||
label: "",
|
||||
})};
|
||||
setModalOpen(false);
|
||||
}}
|
||||
style={{
|
||||
marginLeft: "auto",
|
||||
position: "absolute",
|
||||
top: 20,
|
||||
right: 20,
|
||||
}}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</DialogTitle>
|
||||
<div style={{ paddingLeft: "30px", paddingRight: "30px" }}>
|
||||
<LicencePopup
|
||||
serverside={serverside}
|
||||
removeCookie={removeCookie}
|
||||
isLoaded={isLoaded}
|
||||
isLoggedIn={isLoggedIn}
|
||||
globalUrl={globalUrl}
|
||||
billingInfo={billingInfo}
|
||||
userdata={userdata}
|
||||
stripeKey={stripeKey}
|
||||
setModalOpen={setModalOpen}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
|
||||
// Handle top bar or something
|
||||
const defaultTop = -2
|
||||
const loginTextBrowser = !isLoggedIn ? (
|
||||
@@ -1537,11 +1615,12 @@ const Header = (props) => {
|
||||
|
||||
{topbar}
|
||||
|
||||
<div style={{ position: "sticky", top: 0, }}>
|
||||
{loginTextBrowser}
|
||||
</div>
|
||||
</AppBar>
|
||||
</div>
|
||||
<div style={{ position: "sticky", top: 0, }}>
|
||||
{loginTextBrowser}
|
||||
</div>
|
||||
{modalView}
|
||||
</AppBar>
|
||||
</div>
|
||||
:
|
||||
<MobileView>{loginTextMobile}</MobileView>
|
||||
};
|
||||
|
||||
@@ -164,6 +164,11 @@ const Admin = (props) => {
|
||||
const [selectedOrganization, setSelectedOrganization] = React.useState({});
|
||||
|
||||
//console.log("Selected: ", selectedOrganization)
|
||||
const [appAuthenticationGroupModalOpen , setAppAuthenticationGroupModalOpen] = React.useState(false);
|
||||
const [appsForAppAuthGroup, setAppsForAppAuthGroup] = React.useState([]);
|
||||
const [appAuthenticationGroupName, setAppAuthenticationGroupName] = React.useState("");
|
||||
const [appAuthenticationGroupDescription, setAppAuthenticationGroupDescription] = React.useState("");
|
||||
const [appAuthenticationGroups, setAppAuthenticationGroups] = React.useState([]);
|
||||
const [organizationFeatures, setOrganizationFeatures] = React.useState({});
|
||||
const [loginInfo, setLoginInfo] = React.useState("");
|
||||
const [curTab, setCurTab] = React.useState(0);
|
||||
@@ -426,6 +431,40 @@ const Admin = (props) => {
|
||||
});
|
||||
};
|
||||
|
||||
const createAppAuthenticationGroup = (name, description, appAuthIds) => {
|
||||
let app_auths = appAuthIds.map((appAuthId) => {
|
||||
return { id: appAuthId };
|
||||
});
|
||||
|
||||
fetch(globalUrl + "/api/v1/apps/authentication/group", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
body: JSON.stringify({
|
||||
label: name,
|
||||
description: description,
|
||||
app_auths: app_auths
|
||||
}),
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
throw new Error("Failed to create app authentication group");
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
// getAppAuthenticationGroups();
|
||||
toast("App authentication group created");
|
||||
})
|
||||
.catch((error) => {
|
||||
toast(error.toString());
|
||||
});
|
||||
};
|
||||
|
||||
const categories = [
|
||||
{
|
||||
name: "Ticketing",
|
||||
@@ -2050,6 +2089,33 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
});
|
||||
};
|
||||
|
||||
const getAppAuthenticationGroups = () => {
|
||||
fetch(globalUrl + "/api/v1/apps/authentication/group", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for apps :O!");
|
||||
return;
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (responseJson.success === true) {
|
||||
setAppAuthenticationGroups(responseJson.data);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
toast(error.toString());
|
||||
});
|
||||
};
|
||||
|
||||
const getAppAuthentication = () => {
|
||||
fetch(globalUrl + "/api/v1/apps/authentication", {
|
||||
method: "GET",
|
||||
@@ -2238,6 +2304,7 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
} else if (newValue === 2) {
|
||||
document.title = "Shuffle - admin - app authentication";
|
||||
getAppAuthentication();
|
||||
getAppAuthenticationGroups();
|
||||
} else if (newValue === 3) {
|
||||
document.title = "Shuffle - admin - Files";
|
||||
} else if (newValue === 4) {
|
||||
@@ -5095,8 +5162,145 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
setAuthenticationFields(newfields);
|
||||
};
|
||||
|
||||
|
||||
const handleAppAuthGroupCheckbox = (data) => {
|
||||
let appOrginal = data.app
|
||||
if (appsForAppAuthGroup.includes(appOrginal.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setAppsForAppAuthGroup([...appsForAppAuthGroup, data.id]);
|
||||
console.log("Apps for app auth group: ", appsForAppAuthGroup);
|
||||
};
|
||||
|
||||
const authenticationView =
|
||||
curTab === 2 ? (
|
||||
<>
|
||||
{/* (appAuthenticationGroupModalOpen : { */}
|
||||
{appAuthenticationGroupModalOpen && (
|
||||
<Dialog
|
||||
open={appAuthenticationGroupModalOpen}
|
||||
onClose={() => {
|
||||
setAppAuthenticationGroupModalOpen(false);
|
||||
}}
|
||||
PaperProps={{
|
||||
style: {
|
||||
backgroundColor: theme.palette.surfaceColor,
|
||||
color: "white",
|
||||
minWidth: "1200px",
|
||||
minHeight: "320px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogTitle>
|
||||
<span style={{ color: "white" }}>App Authentication Groups</span>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent>
|
||||
<div>
|
||||
<TextField
|
||||
color="primary"
|
||||
style={{ backgroundColor: theme.palette.inputColor }}
|
||||
autoFocus
|
||||
InputProps={{
|
||||
style: {
|
||||
height: "50px",
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
required
|
||||
fullWidth={true}
|
||||
placeholder="Name"
|
||||
id="namefield"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
onChange={(event) => {
|
||||
setAppAuthenticationGroupName(event.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<TextField
|
||||
color="primary"
|
||||
style={{ backgroundColor: theme.palette.inputColor }}
|
||||
autoFocus
|
||||
InputProps={{
|
||||
style: {
|
||||
height: "50px",
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
required
|
||||
fullWidth={true}
|
||||
placeholder="Description"
|
||||
id="descriptionfield"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
onChange={(event) => {
|
||||
setAppAuthenticationGroupDescription(event.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{/* Show a check box list of all app authentications to add to the auth group */}
|
||||
<div>
|
||||
{authentication.map((data, index) => (
|
||||
<div key={index}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Tooltip
|
||||
title={data.app.name}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '10px', marginLeft: '5px' }}>
|
||||
<img
|
||||
src={data.app.large_image ? data.app.large_image : '/images/no_image.png'}
|
||||
alt=""
|
||||
style={{ width: '50px', height: '50px', marginRight: '10px' }}
|
||||
/>
|
||||
<Checkbox
|
||||
checked={data.checked}
|
||||
onChange={(event) => {
|
||||
handleAppAuthGroupCheckbox(data)
|
||||
}}
|
||||
name={data.label}
|
||||
disabled={data.app.id in appsForAppAuthGroup}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
label={data.label}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<Button
|
||||
style={{}}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
createAppAuthenticationGroup(
|
||||
appAuthenticationGroupName,
|
||||
appAuthenticationGroupDescription,
|
||||
appsForAppAuthGroup
|
||||
);
|
||||
}}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
|
||||
|
||||
<div>
|
||||
<div style={{ marginTop: 20, marginBottom: 20 }}>
|
||||
<h2 style={{ display: "inline" }}>App Authentication</h2>
|
||||
@@ -5376,6 +5580,134 @@ If you're interested, please let me know a time that works for you, or set up a
|
||||
})}
|
||||
</List>
|
||||
</div>
|
||||
|
||||
{/* <div>
|
||||
<div style={{ marginTop: 20, marginBottom: 20 }}>
|
||||
<h2 style={{ display: "inline" }}>App Authentication Groups</h2>
|
||||
<span style={{ marginLeft: 25 }}>
|
||||
Groups of authentication options for subflows.{" "}
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="/docs/organizations#app_authentication_groups"
|
||||
style={{ textDecoration: "none", color: "#f85a3e" }}
|
||||
>
|
||||
Learn more about App Authentication Groups
|
||||
</a>
|
||||
</span>
|
||||
|
||||
<Divider
|
||||
style={{
|
||||
marginTop: 20,
|
||||
marginBottom: 20,
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
}}
|
||||
/>
|
||||
<List>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary="Label"
|
||||
style={{ minWidth: 150, maxWidth: 150 }}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Description"
|
||||
style={{ minWidth: 250, maxWidth: 250 }}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Apps"
|
||||
style={{ minWidth: 250, maxWidth: 250 }}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="CreatedAt"
|
||||
style={{ minWidth: 150, maxWidth: 150 }}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Actions"
|
||||
style={{ minWidth: 150, maxWidth: 150 }}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
{appAuthenticationGroups.map((data, index) => {
|
||||
var bgColor = "#27292d";
|
||||
if (index % 2 === 0) {
|
||||
bgColor = "#1f2023";
|
||||
}
|
||||
return (
|
||||
<ListItem key={index} style={{ backgroundColor: bgColor }}>
|
||||
<ListItemText
|
||||
primary={data.label}
|
||||
style={{ minWidth: 150, maxWidth: 150 }}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={data.description}
|
||||
style={{ minWidth: 250, maxWidth: 250 }}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={
|
||||
<div style={{ display: 'flex' }}>
|
||||
{data.app_auths.map((appAuth, index) => (
|
||||
<Tooltip
|
||||
title={appAuth.app.name}
|
||||
>
|
||||
<img
|
||||
key={index}
|
||||
src={appAuth.app.large_image}
|
||||
alt={appAuth.app.name}
|
||||
style={{ width: '24px', height: '24px', marginRight: '5px' }}
|
||||
/>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
style={{ minWidth: 250, maxWidth: 250 }}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={new Date(data.created * 1000).toLocaleDateString('en-GB')}
|
||||
style={{ minWidth: 150, maxWidth: 150 }}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={
|
||||
<div style={{ display: 'flex' }}>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
}}
|
||||
disabled={true}
|
||||
>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
// deleteAppAuthenticationGroup(data);
|
||||
}}
|
||||
disabled={true}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
}
|
||||
style={{ minWidth: 150, maxWidth: 150 }}
|
||||
/>
|
||||
|
||||
</ListItem>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</List>
|
||||
|
||||
<Button
|
||||
style={{ marginLeft: 10 }}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setAppAuthenticationGroupModalOpen(true);
|
||||
}}
|
||||
>
|
||||
Add Group
|
||||
</Button>
|
||||
|
||||
</div>
|
||||
</div> */}
|
||||
</>
|
||||
) : null;
|
||||
|
||||
const getLogs = async (ip, userId) => {
|
||||
|
||||
@@ -141,6 +141,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 { act } from "react";
|
||||
// import AppStats from "../components/AppStats.jsx";
|
||||
const noImage = "/public/no_image.png";
|
||||
|
||||
@@ -3034,6 +3035,48 @@ const AngularWorkflow = (defaultprops) => {
|
||||
}
|
||||
}
|
||||
|
||||
const [usedSubflowApps, setUsedSubflowApps] = React.useState([]);
|
||||
|
||||
const getWorkflowApps = (workflow_id) => {
|
||||
let apps = []
|
||||
|
||||
if (workflow_id === "") {
|
||||
console.log("workflow_id is empty");
|
||||
return {};
|
||||
}
|
||||
|
||||
fetch(`${globalUrl}/api/v1/workflows/${workflow_id}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for workflows :O!");
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
for (let index in responseJson.actions) {
|
||||
apps.push(responseJson.actions[index]);
|
||||
}
|
||||
|
||||
console.log("Setting used subflow apps: ", apps)
|
||||
setUsedSubflowApps(apps);
|
||||
|
||||
return apps
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("Get workflow apps error: ", error);
|
||||
});
|
||||
|
||||
return apps
|
||||
};
|
||||
|
||||
const getWorkflow = (workflow_id, sourcenode) => {
|
||||
fetch(`${globalUrl}/api/v1/workflows/${workflow_id}`, {
|
||||
method: "GET",
|
||||
@@ -3863,6 +3906,16 @@ const AngularWorkflow = (defaultprops) => {
|
||||
|
||||
//const data = JSON.parse(JSON.stringify(event.target.data()))
|
||||
const data = event.target.data()
|
||||
|
||||
console.log("NODE SELECT: ", data)
|
||||
|
||||
if (data.app_name === "Shuffle Workflow") {
|
||||
console.log("Shuffle Workflow selected")
|
||||
if (data.parameters[0].value !== undefined && data.parameters[0].value !== null && data.parameters[0].value.length > 0) {
|
||||
console.log("Get workflow apps calling")
|
||||
getWorkflowApps(data.parameters[0].value)
|
||||
}
|
||||
}
|
||||
|
||||
if (data.buttonType == "ACTIONSUGGESTION") {
|
||||
const attachedToId = data.attachedTo
|
||||
@@ -11374,6 +11427,193 @@ const AngularWorkflow = (defaultprops) => {
|
||||
|
||||
setWorkflow(workflow);
|
||||
}
|
||||
// Function to transform the data
|
||||
const transformAuthData = (authData) => {
|
||||
const transformedData = {};
|
||||
|
||||
let subflowId = workflow.triggers[selectedTriggerIndex].parameters[0].value;
|
||||
|
||||
// get the apps used in "find your workflow"
|
||||
if (subflowId === "" && subflowId === undefined && subflowId === null) {
|
||||
console.log("subflow is empty")
|
||||
return {};
|
||||
}
|
||||
|
||||
let workflowApps = usedSubflowApps;
|
||||
|
||||
if (workflowApps === undefined || workflowApps === null) {
|
||||
console.log("workflow apps is empty");
|
||||
return {};
|
||||
}
|
||||
|
||||
// get the app ids
|
||||
// let appIdsInWorkflow = [...new Set(workflowApps.map(app => app.app_id))];
|
||||
let appIdsInWorkflow = [];
|
||||
|
||||
Object.entries(workflowApps).forEach(([key, value]) => {
|
||||
console.log("VALUE: ", value)
|
||||
appIdsInWorkflow.push(value.app_id);
|
||||
})
|
||||
|
||||
appIdsInWorkflow = [...new Set(appIdsInWorkflow)];
|
||||
|
||||
console.log("appIdsInWorkflow: ", appIdsInWorkflow)
|
||||
|
||||
console.log("authData: ", authData, "workflowApps: ", workflowApps)
|
||||
|
||||
// loop through the authData and create transformedData which looks like:
|
||||
// appId: [auth1, auth2, ...]
|
||||
authData.forEach((auth) => {
|
||||
const { app } = auth;
|
||||
const appId = app.id;
|
||||
|
||||
// check if the app is used in the workflow
|
||||
if (appIdsInWorkflow.includes(appId)) {
|
||||
if (transformedData[appId] === undefined) {
|
||||
transformedData[appId] = [];
|
||||
}
|
||||
|
||||
transformedData[appId].push(auth);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
console.log("transformedData: ", transformedData)
|
||||
|
||||
return transformedData;
|
||||
|
||||
};
|
||||
|
||||
const AppAuthSelector = ({ appAuthData }) => {
|
||||
const [selectedAuth, setSelectedAuth] = useState("");
|
||||
const [transformedAuthData, setTransformedAuthData] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
setTransformedAuthData(transformAuthData(appAuthData));
|
||||
}, [appAuthData, selectedAuth]);
|
||||
|
||||
const handleShowingValue = (appName) => {
|
||||
let mappingWithName = {}
|
||||
let listWithValues = workflow.triggers[selectedTriggerIndex].parameters[5]?.value.split(";").filter(e => e).map(e => e.split("="))
|
||||
console.log("LIST WITH VALUES: ", listWithValues)
|
||||
for (let i = 0; i < listWithValues.length; i++) {
|
||||
mappingWithName[listWithValues[i][0]] = listWithValues[i][1]
|
||||
}
|
||||
|
||||
if (mappingWithName[appName] !== undefined) {
|
||||
return mappingWithName[appName];
|
||||
}
|
||||
|
||||
return "no-overrides";
|
||||
}
|
||||
|
||||
const handleSelectChange = (appName, appId, event) => {
|
||||
const authId = event.target.value || "no-override";
|
||||
|
||||
if (authId === "no-override") {
|
||||
// remove the override parameter
|
||||
let oldValue = workflow.triggers[selectedTriggerIndex].parameters[5].value;
|
||||
// replace from appName= to the next ;
|
||||
let newValue = oldValue.replace(new RegExp(appName + "=[^;]*;"), "");
|
||||
|
||||
workflow.triggers[selectedTriggerIndex].parameters[5].value = newValue
|
||||
setSelectedAuth("");
|
||||
return
|
||||
}
|
||||
|
||||
const auth = transformedAuthData[appId].find((auth) => auth.id === authId);
|
||||
|
||||
if (auth === undefined) {
|
||||
setSelectedAuth("");
|
||||
return;
|
||||
}
|
||||
|
||||
// // check if the trigger already has an override parameter
|
||||
// for (let i = 0; i < workflow.triggers[selectedTriggerIndex].parameters.length; i++) {
|
||||
// // if name includes the app id
|
||||
// if (workflow.triggers[selectedTriggerIndex].parameters[i].name.includes(appId + "_override")) {
|
||||
// // update the value
|
||||
// workflow.triggers[selectedTriggerIndex].parameters[i].value = auth.id;
|
||||
// setSelectedAuth(auth.id);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
if (workflow.triggers[selectedTriggerIndex].parameters[5] === undefined || workflow.triggers[selectedTriggerIndex].parameters[5] === null) {
|
||||
workflow.triggers[selectedTriggerIndex].parameters[5] = {
|
||||
name: "auth_override",
|
||||
value: "",
|
||||
};
|
||||
}
|
||||
|
||||
let authGroupValue = workflow.triggers[selectedTriggerIndex].parameters[5].value;
|
||||
|
||||
if (authGroupValue === undefined || authGroupValue === null || authGroupValue === "") {
|
||||
workflow.triggers[selectedTriggerIndex].parameters[5].value = appName + "=" + auth.id + ";";
|
||||
} else {
|
||||
// check if the app is already in the list
|
||||
if (authGroupValue.includes(appName)) {
|
||||
let oldValue = workflow.triggers[selectedTriggerIndex].parameters[5].value;
|
||||
let newValue = oldValue.replace(new RegExp(appName + "=[^;]*;"), appName + "=" + auth.id + ";");
|
||||
workflow.triggers[selectedTriggerIndex].parameters[5].value = newValue;
|
||||
} else {
|
||||
workflow.triggers[selectedTriggerIndex].parameters[5].value += appName + "=" + auth.id + ";";
|
||||
}
|
||||
}
|
||||
|
||||
// workflow.triggers[selectedTriggerIndex].parameters.push({
|
||||
// name: auth.label + "_" + auth.app.id + "_override",
|
||||
// value: auth.id,
|
||||
// });
|
||||
setSelectedAuth(auth.id);
|
||||
};
|
||||
|
||||
console.log("TRANSFORMED AUTH DATA: ", transformedAuthData);
|
||||
|
||||
return (
|
||||
<div className="auth-container" style={{ padding: '20px', backgroundColor: '#26292D', borderRadius: '8px' }}>
|
||||
{Object.entries(transformedAuthData).map(([appId, authList]) => (
|
||||
<div key={appId} className="auth-item" style={{ marginBottom: '20px' }}>
|
||||
<label className="auth-label" style={{ display: 'block', marginBottom: '10px', fontWeight: 'bold', color: '#E8E8E8' }}>
|
||||
Select Authentication for {authList[0].app.name}:
|
||||
</label>
|
||||
<select
|
||||
value={handleShowingValue(authList[0].app.name)}
|
||||
onChange={(e) => handleSelectChange(authList[0].app.name, authList[0].app.id, e)}
|
||||
className="auth-select"
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '10px',
|
||||
fontSize: '16px',
|
||||
borderRadius: '4px',
|
||||
border: '1px solid #555',
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
color: '#E8E8E8',
|
||||
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.2)',
|
||||
transition: 'border-color 0.2s, box-shadow 0.2s',
|
||||
}}
|
||||
onFocus={(e) => e.target.style.borderColor = '#007BFF'}
|
||||
onBlur={(e) => e.target.style.borderColor = '#555'}
|
||||
>
|
||||
<option value="no-override">No override</option>
|
||||
{authList.flatMap((auth) =>
|
||||
<option
|
||||
key={auth.id}
|
||||
value={auth.id}
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
fontSize: "1.2em",
|
||||
}}
|
||||
>
|
||||
{auth.label}
|
||||
</option>
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SubflowSidebar = () => {
|
||||
const [menuPosition, setMenuPosition] = useState(null);
|
||||
@@ -11860,6 +12100,10 @@ const AngularWorkflow = (defaultprops) => {
|
||||
name: "check_result",
|
||||
value: "false",
|
||||
};
|
||||
workflow.triggers[selectedTriggerIndex].parameters[5] = {
|
||||
name: "auth_override",
|
||||
value: "",
|
||||
};
|
||||
|
||||
/*
|
||||
// API-key has been replaced by auth key for the execution.
|
||||
@@ -11880,8 +12124,6 @@ const AngularWorkflow = (defaultprops) => {
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
|
||||
const handleSubflowStartnodeSelection = (e) => {
|
||||
setSubworkflowStartnode(e.target.value);
|
||||
|
||||
@@ -12203,8 +12445,8 @@ const AngularWorkflow = (defaultprops) => {
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
}}
|
||||
onChange={(event, newValue) => {
|
||||
setLastSaved(false)
|
||||
console.log("Found value: ", newValue)
|
||||
setLastSaved(false)
|
||||
console.log("Found value: ", newValue)
|
||||
|
||||
var parsedinput = { target: { value: newValue } }
|
||||
|
||||
@@ -12248,6 +12490,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
}}
|
||||
value={data}
|
||||
onClick={() => {
|
||||
getWorkflowApps(data.id);
|
||||
handleWorkflowSelectionUpdate({
|
||||
target: {
|
||||
value: data
|
||||
@@ -12329,7 +12572,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
}}
|
||||
onChange={(event, newValue) => {
|
||||
setLastSaved(false)
|
||||
setLastSaved(false)
|
||||
handleSubflowStartnodeSelection({ target: { value: newValue } })
|
||||
}}
|
||||
renderOption={(props, action, state) => {
|
||||
@@ -12477,6 +12720,24 @@ const AngularWorkflow = (defaultprops) => {
|
||||
*/}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<div className="app">
|
||||
<div style={{ display: "flex", marginTop: 10 }}>
|
||||
<div style={{ flex: "10" }}>
|
||||
<b>Auth Override</b>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: "flex", marginTop: 10 }}>
|
||||
<div style={{ flex: "10", marginLeft: 10 }}>
|
||||
<AppAuthSelector appAuthData={appAuthentication} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
---
|
||||
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
namespace: default
|
||||
name: pod-manager
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["get", "list", "create", "update", "delete"]
|
||||
- apiGroups: ["batch"]
|
||||
resources: ["jobs"]
|
||||
verbs: ["create", "get", "list", "watch", "delete"]
|
||||
|
||||
---
|
||||
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: pod-manager-binding
|
||||
namespace: default
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: default
|
||||
namespace: default
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: pod-manager
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
|
||||
---
|
||||
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
kompose.cmd: kompose convert -f docker-compose.yml
|
||||
kompose.version: 1.26.0 (40646f47)
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
io.kompose.service: orborus
|
||||
name: orborus
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
io.kompose.service: orborus
|
||||
strategy: {}
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
kompose.cmd: kompose convert -f docker-compose.yml
|
||||
kompose.version: 1.26.0 (40646f47)
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
io.kompose.network/shuffle: "true"
|
||||
io.kompose.service: orborus
|
||||
spec:
|
||||
containers:
|
||||
- env:
|
||||
- name: BASE_URL
|
||||
value: "https://shuffler.io"
|
||||
- name: SHUFFLE_SCALE_REPLICAS
|
||||
value: "7"
|
||||
- name: IS_KUBERNETES
|
||||
value: "true"
|
||||
- name: ENVIRONMENT_NAME
|
||||
value: "environment test"
|
||||
- name: ORG
|
||||
value: "9c938e5b-d812-40d9-92f0-93783f43ec0d"
|
||||
- name: AUTH
|
||||
value: "3663a270-bb3a-4678-a365-d879601a1a0c"
|
||||
|
||||
image: ghcr.io/shuffle/shuffle-orborus:nightly
|
||||
#imagePullPolicy: Never
|
||||
name: shuffle-orborus
|
||||
resources: {}
|
||||
hostname: shuffle-orborus
|
||||
restartPolicy: Always
|
||||
Reference in New Issue
Block a user