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)}
+ >
+
+
+
+ {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 && (
+
+ )}
+
+ >
+ );
+
// 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 && (
+
+ )}
+
+
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) => (
+
+
+
+ ))}
+
+ }
+ 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) => {
*/}
+
+
);
}
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