diff --git a/frontend/src/components/LicencePopup.jsx b/frontend/src/components/LicencePopup.jsx new file mode 100644 index 00000000..f2a1ef99 --- /dev/null +++ b/frontend/src/components/LicencePopup.jsx @@ -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 ( + +
+ setHovered(true)} + // onMouseLeave={() => setHovered(false)} + > + + + { + e.preventDefault(); + setSignatureOpen(false); + setTosChecked(false) + }} + > + + + + Read and Accept the EULA + + + { + setTosChecked(e.target.checked) + }} + inputProps={{ 'aria-label': 'primary checkbox' }} + /> + { + setTosChecked(!tosChecked) + }}> + Accept + + + 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. + + +
+ +
+
+
+ +
+ {top_text === "Base Cloud Access" && userdata.has_card_available === true ? + { + console.log("Clicked chip") + }} + variant="outlined" + color="primary" + /> + : null} + + {top_text} + + + {top_text === "Base Cloud Access" && userdata.has_card_available === false ? + + : null} + {isCloud && highlight === true && top_text !== "Base Cloud Access" ? + + { + setSignatureOpen(true) + }} + > + + + + : null} +
+ +
+ + {subscription.name} + + + {subscription.currency_text !== undefined ? +
+ + {subscription.currency_text}{subscription.price} + + + / {subscription.interval} + +
+ : null} + + + Features + +
    + {subscription.features !== undefined && subscription.features !== null ? + subscription.features.map((feature, index) => { + var parsedFeature = feature + if (feature.includes("Documentation: ")) { + parsedFeature = + + Documentation to get started + + } + + if (feature.includes("Worker License: ")) { + const fieldId = "webhook_uri_field_" + index + parsedFeature = + + + Use the {feature.split("Worker License: ")[0]} Worker + + { }} + InputProps={{ + endAdornment: + + { + 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" + > + + + + }} + fullWidth + /> + + } + + return ( +
  • + + {parsedFeature} + +
  • + ) + }) + : null} +
+ Billing email: {selectedOrganization.org} +
+ + + + +
+ {/* +
+ + Schedule Call Now +
+ */} + +
+
+ ) + } + + 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 = + const inActiveIcon = + const defaultTaskIcon = + + const buttonBackground = "linear-gradient(to right, #f86a3e, #f34079)" + const level1Button = + + + const level2Button = + + + const level3Button = skipFreemode ? null : + + + + 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 ( +
+ + + {isCloud && billingInfo.subscription !== undefined && billingInfo.subscription !== null ? + + : !isCloud ? + + + + + : null} + + {isCloud && + selectedOrganization.subscriptions !== undefined && + selectedOrganization.subscriptions !== null && + selectedOrganization.subscriptions.length > 0 ? + selectedOrganization.subscriptions + .reverse() + .map((sub, index) => { + return ( + + ) + }) + : null} + + + + {errorMessage.length > 0 ? Error: {errorMessage} : null} + +
+ + +
+ {shuffleVariant === 1 ? "Scale" : "Enterprise"} + + + {shuffleVariant === 0 ? + "SaaS / Cloud - Per Month" + : + "Open Source + Scale License" + } + + + { + + if (calculatedCores === "Get A Quote") { + console.log("Clicked on get a quote") + if (window.drift !== undefined) { + window.drift.api.startInteraction({ interactionId: 340785 }) + } + } + }}>{calculatedCost} + For {shuffleVariant === 1 ? `${selectedValue} CPU cores` : `${selectedValue}k App Runs`}: +
+ + { + handleChange(event, newValue) + }} + marks + value={selectedValue} + step={shuffleVariant === 0 ? 100 : 4} + min={shuffleVariant === 0 ? 100 : 8} + max={shuffleVariant === 0 ? 1000 : 32} + valueLabelDisplay="auto" + /> +
+ +
+
+ {defaultTaskIcon} + Priority Support +
+ +
+ {defaultTaskIcon} + + {shuffleVariant === 0 ? "Multi-Tenant" : "Scalable Orborus"} + +
+ +
+ {defaultTaskIcon} + + {shuffleVariant === 0 ? "Multi-Region Tenants" : "High Availability"} + +
+ +
+ {defaultTaskIcon} + Help with Workflow and App development +
+
+
+ + + + + + + + +
+ ) +} + +export default LicencePopup; diff --git a/frontend/src/components/NewHeader.jsx b/frontend/src/components/NewHeader.jsx index 7104f873..8df3fc0f 100644 --- a/frontend/src/components/NewHeader.jsx +++ b/frontend/src/components/NewHeader.jsx @@ -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 && ( +
+ )} + { + setModalOpen(false); + }} + PaperProps={{ + style: { + color: "white", + minWidth: 850, + minHeight: 370, + padding: 10, + backgroundColor: "rgba(0, 0, 0, 1)", + borderRadius: theme.palette.borderRadius, + }, + }} + > + + + Upgrade your plan + + { + if (isCloud) { + ReactGA.event({ + category: "header", + action: "close_Upgread_popup", + label: "", + })}; + setModalOpen(false); + }} + style={{ + marginLeft: "auto", + position: "absolute", + top: 20, + right: 20, + }} + > + + + +
+ +
+
+ + ); + // Handle top bar or something const defaultTop = -2 const loginTextBrowser = !isLoggedIn ? ( @@ -1537,11 +1615,12 @@ const Header = (props) => { {topbar} -
- {loginTextBrowser} -
- -
+
+ {loginTextBrowser} +
+ {modalView} + + : {loginTextMobile} }; diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index 13a9323d..beed4601 100755 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -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 && ( + { + setAppAuthenticationGroupModalOpen(false); + }} + PaperProps={{ + style: { + backgroundColor: theme.palette.surfaceColor, + color: "white", + minWidth: "1200px", + minHeight: "320px", + }, + }} + > + + App Authentication Groups + + + +
+ { + setAppAuthenticationGroupName(event.target.value); + }} + /> +
+
+ { + setAppAuthenticationGroupDescription(event.target.value); + }} + /> +
+ +
+ {/* Show a check box list of all app authentications to add to the auth group */} +
+ {authentication.map((data, index) => ( +
+ +
+ + { + handleAppAuthGroupCheckbox(data) + }} + name={data.label} + disabled={data.app.id in appsForAppAuthGroup} + /> +
+ + } + label={data.label} + /> +
+ ))} +
+ +
+ + +
+ +
+
+
+ )} + +

App Authentication

@@ -5376,6 +5580,134 @@ If you're interested, please let me know a time that works for you, or set up a })}
+ + {/*
+
+

App Authentication Groups

+ + Groups of authentication options for subflows.{" "} + + Learn more about App Authentication Groups + + + + + + + + + + + + + + {appAuthenticationGroups.map((data, index) => { + var bgColor = "#27292d"; + if (index % 2 === 0) { + bgColor = "#1f2023"; + } + return ( + + + + + {data.app_auths.map((appAuth, index) => ( + + {appAuth.app.name} + + ))} +
+ } + style={{ minWidth: 250, maxWidth: 250 }} + /> + + + { + }} + disabled={true} + > + + + { + // deleteAppAuthenticationGroup(data); + }} + disabled={true} + > + + +
+ } + style={{ minWidth: 150, maxWidth: 150 }} + /> + + + ); + } + )} + + + + +
+ */} + ) : null; const getLogs = async (ip, userId) => { diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index e7767889..71c584ac 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -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 ( +
+ {Object.entries(transformedAuthData).map(([appId, authList]) => ( +
+ + +
+ ))} +
+ ); + }; 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) => { */} + +
+
+
+
+
+ Auth Override +
+
+ +
+
+ +
+
+
+
+
); } diff --git a/functions/kubernetes/orborus.yaml b/functions/kubernetes/orborus.yaml new file mode 100644 index 00000000..a94622ba --- /dev/null +++ b/functions/kubernetes/orborus.yaml @@ -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