diff --git a/frontend/src/components/ApiExplorer.jsx b/frontend/src/components/ApiExplorer.jsx index f88702e5..c2dfaeb3 100644 --- a/frontend/src/components/ApiExplorer.jsx +++ b/frontend/src/components/ApiExplorer.jsx @@ -92,7 +92,7 @@ const RequestMethods = [ }, ]; -const ApiExplorer = memo(({ openapi, globalUrl, userdata, HandleApiExecution, selectedAppData, ConfigurationTab }) => { +const ApiExplorer = memo(({ openapi, globalUrl, userdata, HandleApiExecution, selectedAppData, ConfigurationTab, isLoggedIn, isLoaded }) => { const [actions, setActions] = useState([]); const [info, setInfo] = useState({}); const [serverurl, setServerUrl] = useState(""); @@ -1265,12 +1265,12 @@ const ApiExplorer = memo(({ openapi, globalUrl, userdata, HandleApiExecution, se style={{ display: "flex", flexDirection: 'row', - height: userdata?.support ? "auto" : "calc(100vh - 80px)", + height: (isLoggedIn && isLoaded) ? "auto" : "calc(100vh - 80px)", }} > - + - + ); @@ -1279,7 +1279,7 @@ const ApiExplorer = memo(({ openapi, globalUrl, userdata, HandleApiExecution, se export default ApiExplorer; -const ActionResponseAndRequest = memo(({ConfigurationTab, selectedAppData,actions, info, HandleApiExecution, userdata, filteredActions, setFilteredActions, serverurl, globalUrl, setSelectedActionIndex, ExampleBody, setExampleBody, selectedActionIndex}) => { +const ActionResponseAndRequest = memo(({ isLoggedIn, isLoaded, ConfigurationTab, selectedAppData,actions, info, HandleApiExecution, userdata, filteredActions, setFilteredActions, serverurl, globalUrl, setSelectedActionIndex, ExampleBody, setExampleBody, selectedActionIndex}) => { const [apiResponse, setApiResponse] = useState({}); const [isLoading, setIsLoading] = useState(false); const loadAction = 10; @@ -1366,7 +1366,7 @@ const ActionResponseAndRequest = memo(({ConfigurationTab, selectedAppData,action ))} - + )}) @@ -1381,6 +1381,8 @@ const ActionsList = memo(({ userdata, info, openapi, + isLoggedIn, + isLoaded }) => { const [searchQuery, setSearchQuery] = useState(""); @@ -1436,7 +1438,7 @@ const ActionsList = memo(({ } }; return ( -
+
{info?.title ? ( @@ -1487,7 +1489,7 @@ const ActionsList = memo(({ marginTop: 15, backgroundColor: "#1a1a1a", overflowY: "auto", - height: userdata?.support ? "calc(100vh - 190px)" : "calc(100vh - 260px)", + height: (isLoaded && isLoggedIn) ? "calc(100vh - 190px)" : "calc(100vh - 260px)", paddingRight: 5, }} > @@ -2776,7 +2778,7 @@ const Action = memo(( ); }) -const ActionResponse = memo(({ apiResponse, ExampleBody, userdata }) => { +const ActionResponse = memo(({ apiResponse, ExampleBody, isLoggedIn, isLoaded }) => { const [height, setHeight] = useState("14vh") const [responseTabIndex, setResponseTabIndex] = useState(0) const [oldResponse, setOldResponse] = useState(apiResponse) @@ -2897,7 +2899,7 @@ const ActionResponse = memo(({ apiResponse, ExampleBody, userdata }) => { }, []); return ( - +
{ /> )}) -const PaddingWrapper = memo(({ userdata, children }) => { +const PaddingWrapper = memo(({ isLoggedIn, isLoaded, children }) => { const { leftSideBarOpenByClick, windowWidth } = useContext(Context); return (
= 1920 ? "calc(100% - 630px)" : "calc(100% - 570px)" : windowWidth >= 1920 ? "calc(100vw - 460px)": "calc(100% - 410px)" @@ -3042,9 +3044,9 @@ const PaddingWrapper = memo(({ userdata, children }) => { ); }); -const ApiResponseWrapper = memo(({ children, userdata }) => { +const ApiResponseWrapper = memo(({ children, isLoaded, isLoggedIn }) => { return ( - + {children} ); diff --git a/frontend/src/components/AppModal.jsx b/frontend/src/components/AppModal.jsx index 17189887..4fcc5d07 100644 --- a/frontend/src/components/AppModal.jsx +++ b/frontend/src/components/AppModal.jsx @@ -1,4 +1,6 @@ import React, { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router'; + import { Dialog, DialogTitle, @@ -21,17 +23,22 @@ import LaunchIcon from '@mui/icons-material/Launch'; import CheckCircleIcon from '@mui/icons-material/CheckCircle'; import { CloudDownloadOutlined } from '@mui/icons-material'; import { findSpecificApp } from './AppFramework'; +import theme from "../theme"; +import YAML from 'yaml'; +import { toast } from 'react-toastify'; +import { Link } from 'react-router-dom'; -const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { +const AppModal = ({ open, onClose, app, globalUrl }) => { const [frameworkData, setFrameworkData] = useState({}) + const [userdata, setUserdata] = useState({}) const [usecases, setUsecases] = useState([]) const [workflows, setWorkflows] = useState([]) const [prevSubcase, setPrevSubcase] = useState({}) const [inputUsecase, setInputUsecase] = useState({}) const [latestUsecase, setLatestUsecase] = useState([]) const [foundAppUsecase, setFoundAppUsecase] = useState({}) - + const navigate = useNavigate(); const parseUsecase = (subcase) => { const srcdata = findSpecificApp(frameworkData, subcase.type) const dstdata = findSpecificApp(frameworkData, subcase.last) @@ -48,6 +55,25 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { return subcase } + useEffect(() => { + var baseurl = globalUrl; + fetch(baseurl + "/api/v1/me", { + credentials: "include", + headers: { + 'Content-Type': 'application/json', + }, + }) + .then(response => response.json()) + .then(responseJson => { + if (responseJson.success) { + setUserdata(responseJson) + } + }) + .catch(error => { + console.log("Failed login check: ", error); + }); + }, [app]); + const getFramework = () => { fetch(globalUrl + "/api/v1/apps/frameworkConfiguration", { @@ -266,6 +292,89 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { setFoundAppUsecase(foundSubcase); }, [latestUsecase]) + const downloadApp = (inputdata) => { + const id = inputdata.id; + + toast("Downloading.."); + fetch(globalUrl + "/api/v1/apps/" + id + "/config", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + window.location.pathname = "/apps"; + } + + return response.json(); + }) + .then((responseJson) => { + if (!responseJson.success) { + toast("Failed to download file"); + } else { + console.log(responseJson); + const basedata = atob(responseJson.openapi); + console.log("BASE: ", basedata); + var inputdata = JSON.parse(basedata); + console.log("POST INPUT: ", inputdata); + inputdata = JSON.parse(inputdata.body); + + const newpaths = {}; + if (inputdata["paths"] !== undefined) { + Object.keys(inputdata["paths"]).forEach(function (key) { + newpaths[key.split("?")[0]] = inputdata.paths[key]; + }); + } + + inputdata.paths = newpaths; + console.log("INPUT: ", inputdata); + var name = inputdata.info.title; + name = name.replace(/ /g, "_", -1); + name = name.toLowerCase(); + + delete inputdata.id; + delete inputdata.editing; + + const data = YAML.stringify(inputdata); + var blob = new Blob([data], { + type: "application/octet-stream", + }); + + var url = URL.createObjectURL(blob); + var link = document.createElement("a"); + link.setAttribute("href", url); + link.setAttribute("download", `${name}.yaml`); + var event = document.createEvent("MouseEvents"); + event.initMouseEvent( + "click", + true, + true, + window, + 1, + 0, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + null + ); + link.dispatchEvent(event); + //link.parentNode.removeChild(link) + } + }) + .catch((error) => { + console.log(error); + toast(error.toString()); + }); + }; + const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" || window.location.host === "localhost:3000" @@ -280,7 +389,8 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { newAppname = newAppname?.replaceAll("_", " "); } - var canEditApp = userdata.admin === "true" || userdata.id === app?.owner || app?.owner === "" || (userdata.admin === "true" && userdata.active_org.id === app?.reference_org) || !app?.generated + var canEditApp = userdata.admin === "true" || userdata?.id === app?.owner || app?.owner === "" || (userdata.admin === "true" && userdata.active_org.id === app?.reference_org) || !app?.generated + return ( @@ -294,7 +404,7 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { borderRadius: 2, border: "1px solid #494949", minWidth: '440px', - fontFamily: "Inter", + fontFamily: theme?.typography?.fontFamily, backgroundColor: "#212121", '& .MuiDialogContent-root': { backgroundColor: "#212121", @@ -303,10 +413,10 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { backgroundColor: "#212121", }, '& .MuiTypography-root': { - fontFamily: 'Inter, sans-serif', + fontFamily: theme?.typography?.fontFamily, }, '& .MuiButton-root': { - fontFamily: 'Inter, sans-serif', + fontFamily: theme?.typography?.fontFamily, }, } }} @@ -320,7 +430,7 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { pt: 2, pl: 3, pr: 2, - fontFamily: "Inter" + fontFamily: theme?.typography?.fontFamily }} > @@ -340,7 +450,7 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { -
+
{app?.name} { { isCloud && ( - { > - + ) }
@@ -391,23 +499,33 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => {
- + + {app?.activated && + app?.private_id !== undefined && + app?.private_id?.length > 0 && + app?.generated ? ( + ) : null} @@ -431,17 +561,17 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => {
- { > 20 - @@ -468,12 +598,12 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { paddingLeft: "10px", height: "100%", }}> - + {Array.isArray(app?.actions) ? app.actions.length : app?.actions} @@ -486,7 +616,7 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { paddingLeft: "10px", paddingTop: "5px" }}> -
+
{ app?.collection ? ( <> @@ -494,13 +624,25 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { app.collection - ) : "No collection yet" + ) : ( + + No collection yet + + ) }
@@ -517,7 +659,7 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { width: "100%" }}>
{ { foundAppUsecase === undefined ? ( - + ) : ( { /> ) } - { + { foundAppUsecase === undefined ? ( - + ) : ( {
-
+
@@ -611,4 +756,4 @@ const AppModal = ({ open, onClose, app, userdata, globalUrl }) => { ); }; -export default AppModal; \ No newline at end of file +export default AppModal; diff --git a/frontend/src/components/AppSelection.jsx b/frontend/src/components/AppSelection.jsx index 76d38d88..e9539eeb 100644 --- a/frontend/src/components/AppSelection.jsx +++ b/frontend/src/components/AppSelection.jsx @@ -465,7 +465,7 @@ const AppSelection = props => { style={{ fontSize: 16, color: "rgba(158, 158, 158, 1)", - fontFamily: "Inter", + fontFamily: theme?.typography?.fontFamily, }} > Your organization has no apps yet, select your starting apps here diff --git a/frontend/src/components/Billing.jsx b/frontend/src/components/Billing.jsx index f4fff846..c1ce38a3 100644 --- a/frontend/src/components/Billing.jsx +++ b/frontend/src/components/Billing.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useContext, memo, useMemo } from "react"; import ReactGA from 'react-ga4'; import theme from "../theme.jsx"; @@ -41,7 +41,8 @@ import { Delete, RestaurantRounded, Cloud, - CheckCircle + CheckCircle, + Padding, } from "@mui/icons-material"; //import { useAlert @@ -49,8 +50,9 @@ import { typecost, typecost_single, } from "../views/HandlePaymentNew.jsx"; import BillingStats from "./BillingStats.jsx"; import { handlePayasyougo } from "../views/HandlePaymentNew.jsx" import DeleteIcon from '@mui/icons-material/Delete'; +import { Context } from "../context/ContextApi.jsx"; -const Billing = (props) => { +const Billing = memo((props) => { const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, clickedFromOrgTab } = props; //const alert = useAlert(); let navigate = useNavigate(); @@ -72,7 +74,7 @@ const Billing = (props) => { const [alertThresholds, setAlertThresholds] = useState(selectedOrganization.Billing !== undefined && selectedOrganization.Billing.AlertThreshold !== undefined && selectedOrganization.Billing.AlertThreshold !== null ? selectedOrganization.Billing.AlertThreshold : [{ percentage: '', count: '', Email_send: false }]); const [currentIndex, setCurrentIndex] = useState(0); const [deleteAlertVerification, setDeleteAlertVerification] = useState(false); - + useEffect(() => { if (userdata.app_execution_limit !== undefined && userdata.app_execution_usage !== undefined) { const percentage = (userdata.app_execution_usage / userdata.app_execution_limit) * 100; @@ -81,18 +83,12 @@ const Billing = (props) => { } }, [userdata]); + const [BillingEmail, setBillingEmail] = useState(selectedOrganization?.Billing?.Email); - const [BillingEmail, setBillingEmail] = useState(selectedOrganization.Billing !== undefined && selectedOrganization.Billing.Email !== undefined && selectedOrganization.Billing.Email != null && selectedOrganization.Billing.Email.length > 0 ? selectedOrganization.Billing.Email : selectedOrganization.org); - - useState(() => { - // Set the billing email - setBillingEmail( - selectedOrganization.Billing !== undefined && - selectedOrganization.Billing.Email !== undefined && - selectedOrganization.Billing.Email.length > 0 - ? selectedOrganization.Billing.Email - : selectedOrganization.org - ); + useEffect(() => { + if (BillingEmail !== selectedOrganization?.Billing?.Email) { + setBillingEmail(selectedOrganization?.Billing?.Email); + } // Set and sort the alert thresholds const alertThresholds = selectedOrganization.Billing !== undefined && @@ -126,7 +122,6 @@ const Billing = (props) => { ]; const handleGetDeals = (orgId) => { - console.log("Get deals!"); if (orgId.length === 0) { toast( @@ -178,7 +173,7 @@ const Billing = (props) => { width: 340, height: 480, // width: "100%", - backgroundColor: theme.palette.platformColor, + backgroundColor: theme.palette.backgroundColor, borderRadius: theme.palette?.borderRadius * 2, border: "1px solid rgba(255,255,255,0.3)", marginRight: 10, @@ -384,7 +379,7 @@ const Billing = (props) => { } if (hovered) { - newPaperstyle.backgroundColor = theme.palette.surfaceColor + newPaperstyle.backgroundColor = "#2b2b2b" } const handleClickOpen = () => { @@ -412,7 +407,6 @@ const Billing = (props) => { const HandleChangeBillingEmail = (orgId) => { const email = newBillingEmail; const emailPattern = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/; - console.log("Pattern matches: ", emailPattern.test(email)); if (!emailPattern.test(email)) { toast("Please enter a valid email address"); return; @@ -445,7 +439,6 @@ const Billing = (props) => { } return response.json(); }).then((responseJson) => { - console.log("Got org:", responseJson); if (responseJson.success === true) { toast.success("Successfully updated billing email"); setBillingEmail(newBillingEmail); @@ -460,7 +453,7 @@ const Billing = (props) => { } return ( - setHovered(true)} onMouseLeave={() => setHovered(false)} @@ -726,7 +719,7 @@ const Billing = (props) => {
- Billing email: {BillingEmail} + {BillingEmail?.length > 0 ? `Billing email: ${BillingEmail}` : null} {userdata.has_card_available === true && ( : null} - +
) } const ConsultationManagement = (props) => { @@ -1035,12 +1027,12 @@ const Billing = (props) => { }) return ( - { color="primary" style={{ marginTop: userdata.support ? 0 : 10, - borderRadius: 25, + borderRadius: 8, height: 40, fontSize: 16, - color: "white", - backgroundColor: "#f86743", + color: "#1A1A1A", + backgroundColor: "#FF8544", textTransform: 'none', }} onClick={() => { @@ -1150,7 +1142,7 @@ const Billing = (props) => { > Buy - { setClickOnBuy(false) }}> + { setClickOnBuy(false) }} PaperProps={{style: {backgroundColor: "rgb(26, 26, 26)"}}}> You will be taken to Stripe to book professional service hours. You can adjust the number of hours on the left side of the Stripe page. @@ -1158,7 +1150,7 @@ const Billing = (props) => { - +
) } @@ -1308,13 +1300,13 @@ const Billing = (props) => { } return ( - { color="primary" style={{ marginTop: 10, - borderRadius: 25, + borderRadius: 8, height: 40, fontSize: 16, - color: "white", - textTransform: 'none' + color: "#1A1A1A", + textTransform: 'none', + backgroundColor: "#FF8544" }} onClick={() => { if (Cloud) { @@ -1397,10 +1390,12 @@ const Billing = (props) => { color="primary" style={{ marginTop: 10, - borderRadius: 25, + borderRadius: 8, height: 40, fontSize: 16, - textTransform: 'none' + textTransform: 'none', + color: "#FF8544", + backgroundColor: "#1a1a1a", }} onClick={() => { if (Cloud) { @@ -1425,6 +1420,7 @@ const Billing = (props) => { style: { width: 500, margin: 0, + backgroundColor: "rgb(26, 26, 26)", } }} > @@ -1464,7 +1460,7 @@ const Billing = (props) => {
- +
) } @@ -1892,13 +1888,15 @@ const Billing = (props) => { } }; - - const isChildOrg = userdata.active_org.creator_org !== "" && userdata.active_org.creator_org !== undefined && userdata.active_org.creator_org !== null + const isChildOrg = userdata?.active_org?.creator_org !== "" && userdata?.active_org?.creator_org !== undefined && userdata?.active_org?.creator_org !== null + return ( -
- {addDealModal} + +
+
+ {addDealModal} {clickedFromOrgTab ? -

Billing & Licensing

: + Billing & Licensing : Billing & Licensing } @@ -1942,11 +1940,11 @@ const Billing = (props) => { {isChildOrg ? - Billing is handled by your parent organisation. Reach out to support@shuffler.io if you have questions about this. + Licensing is handled by your parent organisation. Reach out to support@shuffler.io if you have questions about this. : null} -
+
{isCloud && billingInfo.subscription !== undefined && billingInfo.subscription !== null ? isChildOrg ? null : { ) : null*/} {!isChildOrg && isCloud && (
- + Professional Services - + We offer priority support through consultations and training to help you make the most of our product. If you have any questions, please reach out to us at support@shuffler.io.
@@ -2280,15 +2278,14 @@ const Billing = (props) => { )}
Manage Billing - + Manage your billing and licensing information below. When you reach the certain thresholds of your subscription limit, you will be notified by email. - Current Usage: + Current Usage: { marginBottom: 10, }} /> - + You have used {currentAppRunsInPercentage}% of total app execution limit or {userdata.app_execution_usage} app runs out of {userdata.app_execution_limit} app runs.
- + Set email alert thresholds for app runs - + You will be notified by email when you reach the {currentIndex !== -1 ? " " + getSafeValue(alertThresholds[currentIndex].percentage) + '%' + " " @@ -2320,8 +2317,8 @@ const Billing = (props) => { : " " + 0 + " "} app runs. - - Please note: Once your app runs reach the set alert threshold, all admins in the organization will receive an email notification. + + Please note: Once your app runs reach the set alert threshold, all admins in the organization will receive an email notification.
{alertThresholds.map((threshold, index) => ( @@ -2329,7 +2326,7 @@ const Billing = (props) => { { { Save +
Utilization & Stats @@ -2436,9 +2433,41 @@ const Billing = (props) => { globalUrl={globalUrl} selectedOrganization={selectedOrganization} userdata={userdata} - /> -
+ /> +
+ ) -} +}) -export default Billing; +export default memo(Billing); + +const PaddingWrapper = memo(({ clickedFromOrgTab, children }) => { + + const wrapperStyle = useMemo(() => ({ + width: clickedFromOrgTab + ? "100%" + : "auto", + padding: "27px 10px 19px 27px", + backgroundColor: '#212121', + borderRadius: '16px', + height: '100%', + boxSizing: 'border-box', + borderLeft: '1px solid #494949', + overflow: 'hidden', + maxHeight: "1700px", overflowY: "auto",scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin' + }), [clickedFromOrgTab]); + + return ( +
+ {children} +
+ ); + }); + + const Wrapper = memo(({ children, clickedFromOrgTab }) => { + return ( + + {children} + + ); + }); diff --git a/frontend/src/components/BillingStats.jsx b/frontend/src/components/BillingStats.jsx index 34405f29..8b5d4a1c 100644 --- a/frontend/src/components/BillingStats.jsx +++ b/frontend/src/components/BillingStats.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useContext, memo, useMemo } from 'react'; import theme from '../theme.jsx'; import classNames from "classnames"; @@ -37,6 +37,7 @@ import { } from 'reaviz'; import { typecost, typecost_single, } from "../views/HandlePaymentNew.jsx"; +import { Context } from '../context/ContextApi.jsx'; const LineChartWrapper = ({keys, inputname, height, width}) => { const [hovered, setHovered] = useState(""); @@ -82,8 +83,8 @@ const AppStats = (defaultprops) => { const [workflows, setWorkflows] = useState(inputWorkflows === undefined ? [] : inputWorkflows) const [resultRows, setResultRows] = useState([]) const [resultLoading, setResultLoading] = useState(true) - - const includedExecutions = selectedOrganization.sync_features.app_executions !== undefined ? selectedOrganization.sync_features.app_executions.limit : 0 + + const includedExecutions = selectedOrganization?.sync_features?.app_executions !== undefined ? selectedOrganization?.sync_features?.app_executions?.limit : 0 useEffect(() => { if (workflows === undefined || workflows === null || workflows.length === 0) { @@ -92,7 +93,6 @@ const AppStats = (defaultprops) => { }, []) - const getWorkflowStats = async (workflow, startTime, endTime) => { if (!userdata.support) { return workflow @@ -162,8 +162,6 @@ const AppStats = (defaultprops) => { const loadWorkflowStats = (foundWorkflows, startTime, endTime) => { if (!userdata.support) { - console.log("Not support") - return } @@ -488,8 +486,13 @@ const AppStats = (defaultprops) => { setApprunCosts(appcostRuns) } - const getStats = () => { - fetch(`${globalUrl}/api/v1/orgs/${selectedOrganization.id}/stats`, { + const getStats = (orgid) => { + + if (orgid === undefined || orgid === null) { + return + } + + fetch(`${globalUrl}/api/v1/orgs/${orgid}/stats`, { method: "GET", headers: { "Content-Type": "application/json", @@ -519,8 +522,10 @@ const AppStats = (defaultprops) => { } useEffect(() => { - getStats() - }, []) + if(selectedOrganization?.id?.length > 0) { + getStats(selectedOrganization.id) + } + }, [selectedOrganization]) const paperStyle = { textAlign: "center", @@ -639,7 +644,7 @@ const AppStats = (defaultprops) => { const data = (
- + All shown statistics are gathered from {
: null} + {clickedFromOrgTab? ( + +
+
+ } + /> +
+
+ } + /> +
+
+
+ ):(
{ />
+ )} +
diff --git a/frontend/src/components/Branding.jsx b/frontend/src/components/Branding.jsx index 39734084..e549d12e 100644 --- a/frontend/src/components/Branding.jsx +++ b/frontend/src/components/Branding.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useContext } from "react"; import ReactGA from 'react-ga4'; import theme from "../theme.jsx"; import { ToastContainer, toast } from "react-toastify" @@ -21,6 +21,7 @@ import { red, green, } from "../views/AngularWorkflow.jsx" +import { Context } from "../context/ContextApi.jsx"; //import { useAlert @@ -30,7 +31,8 @@ const Branding = (props) => { const [publishingInfo, setPublishingInfo] = useState(""); const [publishRequirements, setPublishRequirements] = useState([]) - + const { leftSideBarOpenByClick } = useContext(Context) + const handleEditOrg = (joinStatus) => { const data = { "org_id": selectedOrganization.id, @@ -115,48 +117,73 @@ const Branding = (props) => { const leadinfo = selectedOrganization.lead_info === undefined || selectedOrganization.lead_info === null || selectedOrganization.lead_info === "" ? "" : JSON.stringify(selectedOrganization.lead_info) const isPartner = leadinfo.includes("partner") - console.log("LEADINFO: ", leadinfo) - - console.log("SELECTEDORGANIZATION: ", selectedOrganization) return ( -
-

+
+
+
+ Partner Status & Branding -

- + + You can customize your organization's branding by uploading a logo, changing the color scheme and a lot more. - + {isPublished ? : } - {isPublished ? "Not Published" : "Published"} + {isPublished ? "Not Published" : "Published"}
- + {!isPartner ? : } - {!isPartner? "Not Officially Partnered" : "Officially Partnered"} + {!isPartner? "Not Officially Partnered" : "Officially Partnered"} - - - + {!isPublished ? ( + + + + ) : ( + + )} -

+ Partner Program -

-
+ +
- + By changing publishing settings, you agree to our Terms of Service, and acknowledge that your organization's non-sensitive data will be added as a creator account. None of your existing workflows, apps, or other stored data will be published. Any admin in your organization can manage the creator configuration. Becoming a creator organization IS reversible.
Support: support@shuffler.io {selectedOrganization.creator_id == "" ? @@ -167,9 +194,8 @@ const Branding = (props) => { null } -
+
+
) } diff --git a/frontend/src/components/CacheView.jsx b/frontend/src/components/CacheView.jsx index 59d541ba..4e8c6c24 100644 --- a/frontend/src/components/CacheView.jsx +++ b/frontend/src/components/CacheView.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useContext, memo } from "react"; import theme from "../theme.jsx"; import { toast } from 'react-toastify'; import ReactJson from "react-json-view-ssr"; @@ -19,6 +19,7 @@ import { Dialog, DialogTitle, DialogActions, + Skeleton, } from "@mui/material"; import { @@ -47,6 +48,7 @@ import { VisibilityOff as VisibilityOffIcon, } from "@mui/icons-material"; import { validateJson, } from "../views/Workflows.jsx"; +import { Context } from "../context/ContextApi.jsx"; const scrollStyle1 = { height: 100, @@ -65,7 +67,7 @@ const scrollStyle2 = { } -const CacheView = (props) => { +const CacheView = memo((props) => { const { globalUrl, userdata, serverside, orgId, isSelectedDataStore } = props; const [orgCache, setOrgCache] = React.useState(""); const [listCache, setListCache] = React.useState([]); @@ -78,11 +80,13 @@ const CacheView = (props) => { const [cacheCursor, setCacheCursor] = React.useState(""); const [dataValue, setDataValue] = React.useState({}); const [editCache, setEditCache] = React.useState(false); + const [cachedLoaded, setCachedLoaded] = React.useState(false); const [show, setShow] = useState({}); - useEffect(() => { - listOrgCache(orgId); - }, []); + if(orgId?.length >0){ + listOrgCache(orgId); + } + }, [orgId]); const listOrgCache = (orgId) => { fetch(globalUrl + `/api/v1/orgs/${orgId}/list_cache`, { @@ -104,6 +108,7 @@ const CacheView = (props) => { .then((responseJson) => { if (responseJson.success === true) { setListCache(responseJson.keys); + setCachedLoaded(true); } if (responseJson.cursor !== undefined && responseJson.cursor !== null && responseJson.cursor !== "") { @@ -232,6 +237,34 @@ const CacheView = (props) => { } } + const handleReactJsonClipboard = (copy) => { + const elementName = "copy_element_shuffle"; + let copyText = document.getElementById(elementName); + + if (copyText) { + if (copy.namespace && copy.name && copy.src) { + copy = copy.src; + } + + const clipboard = navigator.clipboard; + if (!clipboard) { + toast("Can only copy over HTTPS (port 3443)"); + return; + } + + let stringified = JSON.stringify(copy); + if (stringified.startsWith('"') && stringified.endsWith('"')) { + stringified = stringified.slice(1, -1); + } + + navigator.clipboard.writeText(stringified); + toast("Copied value to clipboard, NOT json path."); + } else { + console.log("Failed to copy from " + elementName + ": ", copyText); + } + }; + + const modalView = ( // console.log("key:", dataValue.key), //console.log("value:",dataValue.value), @@ -241,11 +274,23 @@ const CacheView = (props) => { setModalOpen(false); }} PaperProps={{ - style: { - backgroundColor: theme.palette.surfaceColor, - color: "white", + sx: { + borderRadius: theme?.palette?.DialogStyle?.borderRadius, + border: theme?.palette?.DialogStyle?.border, minWidth: "800px", minHeight: "320px", + fontFamily: theme?.typography?.fontFamily, + backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, + zIndex: 1000, + '& .MuiDialogContent-root': { + backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, + }, + '& .MuiDialogTitle-root': { + backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, + }, + '& .MuiDialogActions-root': { + backgroundColor: theme?.palette?.DialogStyle?.backgroundColor, + }, }, }} > @@ -254,7 +299,7 @@ const CacheView = (props) => { { editCache ? "Edit Cache" : "Add Cache" } -
+
Key { onChange={(e) => setKey(e.target.value)} />
-
+
Value - ({isValidJson.valid === true ? "Valid" : "Invalid"} JSON) @@ -320,7 +365,7 @@ const CacheView = (props) => {
+
- ); -} -export default CacheView; +}); + +export default memo(CacheView); diff --git a/frontend/src/components/EditWorkflow.jsx b/frontend/src/components/EditWorkflow.jsx index e26ae7c5..a7694562 100644 --- a/frontend/src/components/EditWorkflow.jsx +++ b/frontend/src/components/EditWorkflow.jsx @@ -1149,7 +1149,7 @@ const EditWorkflow = (props) => {
: null} - {/*!isEditing ? <> + {!isEditing ? <>
} @@ -1164,7 +1164,7 @@ const EditWorkflow = (props) => { />
- : null*/} + : null} {/* */} @@ -702,7 +711,7 @@ const Files = (props) => { }} /> - + {/*
*/} {fileCategories !== undefined && fileCategories !== null && fileCategories.length > 1 ? ( - File Category ) : null} -
+
{renderTextBox ? @@ -777,13 +788,14 @@ const Files = (props) => { : } @@ -824,323 +836,379 @@ const Files = (props) => { backgroundColor: theme.palette.inputColor, }} />} - - - - {/* - - */} - + + + {["Name", "Workflow", "Md5", "Status", "Filesize", "Actions"].map((header, index) => ( + - - - - - + /> + ))} - {files === undefined || files === null || files.length === 0 ? null : - files.map((file, index) => { - if (file.namespace === "") { - file.namespace = "default"; - } - - if (file.namespace !== selectedCategory) { - return null; - } - - var bgColor = isSelectedFiles ? "#212121":"#27292d"; - if (index % 2 === 0) { - bgColor = isSelectedFiles ? "#1A1A1A":"#1f2023"; - } - - const filenamesplit = file.filename.split(".") - const iseditable = file.filesize < 2000000 && file.status === "active" && allowedFileTypes.includes(filenamesplit[filenamesplit.length-1]) - - return ( - - {/* - ( + + {Array(6) + .fill() + .map((_, colIndex) => ( + + + + ))} + + )): + files.length === 0 ? ( +
+ + No files found + +
+ ):( + files?.map((file, index) => { + if (file.namespace === "") { + file.namespace = "default"; + } + + if (file.namespace !== selectedCategory) { + return null; + } + + var bgColor = isSelectedFiles ? "#212121":"#27292d"; + if (index % 2 === 0) { + bgColor = isSelectedFiles ? "#1A1A1A":"#1f2023"; + } + + const filenamesplit = file.filename.split(".") + const iseditable = file.filesize < 2000000 && file.status === "active" && allowedFileTypes.includes(filenamesplit[filenamesplit.length-1]) + return ( + - */} - - - - - : ( + > + {/* + + */} + + + + + : ( + + + + + + + + + + ) + } + style={{ + display: 'table-cell', + overflow: "hidden", + }} + /> + + {file.md5_sum} +
+ )} + primaryTypographyProps={{ + style:{ + display: 'table-cell', + marginLeft:isSelectedFiles? 15:null, + overflow: "hidden", + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + maxWidth: 200, + } + }} + /> + + + + + { + setOpenEditor(true) + setOpenFileId(file.id) + readFileData(file) + }} + > + edit icon + + + + {/* + + + { + // Open the file, without downloading it + window.open(`${globalUrl}/api/v1/files/${file.id}/content?type=text&authorization=${file.public_authorization}`, "_blank noreferrer noopener") + }} + > + + + + + */} + - { + downloadFile(file); }} - href={`/workflows/${file.workflow_id}`} - target="_blank" > - - - - + download icon + - ) - } - style={{ - minWidth: 100, - maxWidth: 100, - overflow: "hidden", - textAlign: isSelectedFiles?"center":null - }} - /> - - - - - - - { - setOpenEditor(true) - setOpenFileId(file.id) - readFileData(file) - }} - > - - - - - {/* - - - { - // Open the file, without downloading it - window.open(`${globalUrl}/api/v1/files/${file.id}/content?type=text&authorization=${file.public_authorization}`, "_blank noreferrer noopener") - }} - > - - - - - */} - - - { - downloadFile(file); - }} - > - - - - - - { - const elementName = "copy_element_shuffle"; - var copyText = - document.getElementById(elementName); - if ( - copyText !== null && - copyText !== undefined - ) { - const clipboard = navigator.clipboard; - if (clipboard === undefined) { - toast( - "Can only copy over HTTPS (port 3443)" - ); - return; - } - - navigator.clipboard.writeText(file.id); - copyText.select(); - copyText.setSelectionRange( - 0, - 99999 - ); /* For mobile devices */ - - /* Copy the text inside the text field */ - document.execCommand("copy"); - - toast(file.id + " copied to clipboard"); - } - }} + - - - - - { - deleteFile(file) + console.log("file is : ", file) + navigator.clipboard.writeText(file.id); + document.execCommand("copy"); + + toast(file.id + " copied to clipboard"); }} > - + copy icon - - - - style={{ - minWidth: 250, - maxWidth: 250, - // overflow: "hidden", - }} - /> - - ); - }) + + + + { + deleteFile(file) + }} + > + delete icon + + + + + style={{ + display: 'table-cell', + textAlign:'center' + // overflow: "hidden", + }} + /> + + ); + }) + ) } +
+
+
) -} +}) -export default Files; +export default memo(Files); + + +const DownloadFileIcon = memo(({ setLoadFileModalOpen, isSelectedFiles, }) => { + + const { leftSideBarOpenByClick } = useContext(Context) + + return( + + setLoadFileModalOpen(true)} + > + + + + ) +}) diff --git a/frontend/src/components/LeftSideBar.jsx b/frontend/src/components/LeftSideBar.jsx index 011ec9fe..934c14d9 100644 --- a/frontend/src/components/LeftSideBar.jsx +++ b/frontend/src/components/LeftSideBar.jsx @@ -7,6 +7,7 @@ import { Add as AddIcon, BorderColor, Close as CloseIcon, + ConstructionOutlined, } from "@mui/icons-material"; import SearchBox from "./SearchData.jsx"; import { @@ -85,7 +86,21 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { return orgOptions.find((option) => option.name === selectedOrg); }, [selectedOrg, orgOptions]); +//With this code it is opening search bar on google chrome search bar as well which is not required +useEffect(() => { + const handleKeyDown = (event) => { + if ((event.ctrlKey || event.metaKey) && event.key === "k") { + event.preventDefault(); + setSearchBarModalOpen((prev)=> !prev); + } + }; + window.addEventListener("keydown", handleKeyDown); + + return () => { + window.removeEventListener("keydown", handleKeyDown); + }; +}, [setSearchBarModalOpen]); const CustomPopper = (props) => { return ( @@ -141,7 +156,7 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { > {props.children} - + { }, }} > - + { handleClose(); @@ -466,7 +481,7 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { - + { handleClose(); @@ -478,7 +493,7 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { }) - + { handleClose(); @@ -641,7 +656,7 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { }; const getRegionTag = (region_url) => { - let regiontag = "eu"; + let regiontag = "UK"; if ( region_url !== undefined && region_url !== null && @@ -653,15 +668,25 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { regiontag = namesplit[namesplit.length - 1]; if (regiontag === "california") { - regiontag = "us"; + regiontag = "US"; } else if (regiontag === "frankfurt") { - regiontag = "fr"; + regiontag = "EU"; + } else if (regiontag === "ca"){ + regiontag = "CA"; } } } + return regiontag; }; + useEffect(() => { + + if(activeOrgName !== userdata?.active_org?.name){ + setActiveOrgName(userdata?.active_org?.name || "Select Organization"); + } + }, [userdata]); + const CheckOrgStates = useCallback(() => { setOrgOptions( userdata?.orgs?.map((org) => ({ @@ -723,6 +748,20 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { ); + const getRegionFlag = (region_url) => { + var region = "gb"; + const regionMapping = { + "US": "us", + "EU": "eu", + "CA": "ca", + "UK": "gb" + }; + + region = regionMapping[region_url] || "gb"; + + return `https://flagcdn.com/48x36/${region}.png`; + }; + return (
{ boxShadow: "0px 4px 12px rgba(0, 0, 0, 0.2)" , resize: 'both', - height: "calc(100vh - 32px)", + zoom: 0.8, + height: "calc((100vh - 32px)*1.2)", }} > {modalView} @@ -759,7 +799,7 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { { {expandLeftNav ? ( - ), - disableUnderline: true, - }} - onClick={()=>{setSearchBarModalOpen(true)}} - onChange={()=> {setSearchBarModalOpen(true)}} - /> + id="sidebar-search" + placeholder="Search" + sx={{ + width: "100%", + maxWidth: 228, + "& .MuiInputBase-root": { + height: 35, + padding: 0, + }, + "& .MuiOutlinedInput-root": { + cursor: "pointer", + width: 228, + "& fieldset": { + borderColor: "#494949", + }, + "&:hover fieldset": { + borderColor: "#ffffff", + display: "block", + }, + "&.Mui-focused fieldset": { + borderColor: "#A9A9A9", + }, + }, + "& input": { + padding: "8px 14px", + fontSize: "14px", + color: "#C8C8C8", + }, + backgroundColor: "transparent", + }} + variant="outlined" + InputProps={{ + startAdornment: ( + + ), + endAdornment: ( + + Ctrl/Cmd+K + + ), + disableUnderline: true, + }} + onClick={() => { + setSearchBarModalOpen(true); + }} + onChange={() => { + setSearchBarModalOpen(true); + }} + /> ):( <> @@ -891,10 +947,16 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { setCurrentOpenTab("automate"); localStorage.setItem("lastTabOpenByUser", "automate"); }} - sx={{ + variant="text" + style={{ ...ButtonStyle, backgroundColor: ((currentOpenTab === "automate" && currentPath.includes("/dashboards/automate"))|| (!expandLeftNav && (currentPath === "/workflows" || currentPath === "/usecases2" || currentPath.includes("/search"))))? "#2f2f2f": "transparent", - "&:hover": { backgroundColor: "#2f2f2f" }, + }} + onMouseOver={(event)=>{ + event.currentTarget.style.backgroundColor = "#2f2f2f"; + }} + onMouseOut={(event)=>{ + event.currentTarget.style.backgroundColor = ((currentOpenTab === "automate" && currentPath.includes("/dashboards/automate"))|| (!expandLeftNav && (currentPath === "/workflows" || currentPath === "/usecases2" || currentPath.includes("/search"))))? "#2f2f2f": "transparent"; }} > { setOpenautomateTab((prev) => !prev); setOpenSecurityTab(false); }} - sx={{ - marginLeft: 0.625, + style={{ + color: "#FFFFFF", + marginLeft: 0.625, + }} + onMouseOver={(event)=>{ + event.currentTarget.style.backgroundColor = "#2f2f2f"; + }} + onMouseOut={(event)=>{ + event.currentTarget.style.backgroundColor = "transparent"; }} > {openautomatetab ? ( @@ -952,23 +1021,28 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { flexDirection: "column", paddingLeft: 16, gap: 4, - marginTop: expandLeftNav ? 16 : 0, }} > - + - + - + { setOpenSecurityTab((prev) => !prev); setOpenautomateTab(false); }} - sx={{ + style={{ marginLeft: 0.625, + color: "#FFFFFF", + }} + onMouseOver={(event)=>{ + event.currentTarget.style.backgroundColor = "#2f2f2f"; + }} + onMouseOut={(event)=>{ + event.currentTarget.style.backgroundColor = "transparent"; }} > {openSecurityTab ? ( @@ -1144,14 +1244,12 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { display: "flex", flexDirection: "column", paddingLeft: 16, - gap: 4, - marginTop: expandLeftNav ? 16 : 0, }} disableRipple={expandLeftNav ? false : true} > { : "#6F6F6F", }} > - Detection + Forms - + { }, cursor: userdata?.support ? "pointer" : "not-allowed", }} - disabled={!userdata?.support} > • @@ -1248,26 +1345,81 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { : "#6F6F6F", }} > - Response + Datastore + + + + + + + - - -
+
*/}
diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 4f4bde7e..5749edfa 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -53,6 +53,7 @@ import { import { HelpOutline as HelpOutlineIcon, + OpenInFull as OpenInFullIcon, Description as DescriptionIcon, GetApp as GetAppIcon, Search as SearchIcon, @@ -2090,7 +2091,7 @@ const ParsedAction = (props) => { {data?.validation?.valid === true ? { : null } {data?.last_modified === true ? { > { setAuthenticationModalOpen(true); @@ -3452,36 +3453,10 @@ const ParsedAction = (props) => { disableUnderline: true, endAdornment: hideExtraTypes ? null : ( - - - { - event.preventDefault() - setFieldCount(count) - setExpansionModalOpen(true) - setActiveDialog("codeeditor") - //setcodedata(data.value) - var parsedvalue = data.value - if (parsedvalue === undefined || parsedvalue === null) { - parsedvalue = "" - } - - setEditorData({ - "name": data.name, - "value": parsedvalue, - "field_number": count, - "actionlist": actionlist, - "field_id": clickedFieldId, - - "example": selectedActionParameters[count].example, - }) - }} - /> - + { event.preventDefault() @@ -4355,6 +4330,34 @@ const ParsedAction = (props) => { {tmpitem} {selectedActionParameters[count].required || selectedActionParameters[count].configuration ? "*" : ""}
+ + + { + event.preventDefault() + setFieldCount(count) + setExpansionModalOpen(true) + setActiveDialog("codeeditor") + //setcodedata(data.value) + var parsedvalue = data.value + if (parsedvalue === undefined || parsedvalue === null) { + parsedvalue = "" + } + + setEditorData({ + "name": data.name, + "value": parsedvalue, + "field_number": count, + "actionlist": actionlist, + "field_id": clickedFieldId, + + "example": selectedActionParameters[count].example, + }) + }} + /> + +
{datafield} {/*shufflecode*/} diff --git a/frontend/src/components/Priorities.jsx b/frontend/src/components/Priorities.jsx index b65aef93..29078a6a 100644 --- a/frontend/src/components/Priorities.jsx +++ b/frontend/src/components/Priorities.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useContext, memo } from "react"; import { toast } from "react-toastify"; import theme from "../theme.jsx"; @@ -13,22 +13,25 @@ import { Card, Chip, Switch, + Skeleton, } from "@mui/material"; +import { Context } from "../context/ContextApi.jsx"; import { useNavigate, Link } from "react-router-dom"; import Priority from "../components/Priority.jsx"; +import { constrainMatrix } from "reaviz"; //import { useAlert -const Priorities = (props) => { +const Priorities = memo((props) => { const { globalUrl, userdata,clickedFromOrgTab, serverside, billingInfo, stripeKey, checkLogin, setAdminTab, setCurTab, notifications, setNotifications, } = props; + const [showDismissed, setShowDismissed] = React.useState(false); const [showRead, setShowRead] = React.useState(false); const [appFramework, setAppFramework] = React.useState({}); - const [selectedWorkflow, setSelectedWorkflow] = React.useState("NO HIGHLIGHT"); const [selectedExecutionId, setSelectedExecutionId] = React.useState("NO HIGHLIGHT"); const [highlightKMS, setHighlightKMS] = React.useState(false) - + let navigate = useNavigate(); useEffect(() => { getFramework() @@ -216,188 +219,16 @@ const Priorities = (props) => { const notificationWidth = "100%" const imagesize = 22 const boxColor = "#86c142" - const NotificationItem = (props) => { - const {data} = props - - var image = ""; - var orgName = ""; - var orgId = ""; - - - var highlighted = selectedExecutionId === "" && selectedWorkflow === "" ? false : data.reference_url === undefined || data.reference_url === null || data.reference_url.length === 0 ? false : data.reference_url.includes(selectedExecutionId) || data.reference_url.includes(selectedWorkflow) - - if (!highlighted && highlightKMS) { - if (data.title !== undefined && data.title !== null && data.title.toLowerCase().includes("kms")) { - highlighted = true - } else if (data.description !== undefined && data.description !== null && data.description.toLowerCase().includes("kms")) { - highlighted = true - } - - } - - if (userdata.orgs !== undefined) { - const foundOrg = userdata.orgs.find((org) => org.id === data["org_id"]); - if (foundOrg !== undefined && foundOrg !== null) { - //position: "absolute", bottom: 5, right: -5, - const imageStyle = { - width: imagesize, - height: imagesize, - pointerEvents: "none", - marginLeft: - data.creator_org !== undefined && data.creator_org.length > 0 - ? 20 - : 0, - borderRadius: 10, - border: - foundOrg.id === userdata.active_org.id - ? `3px solid ${boxColor}` - : null, - cursor: "pointer", - marginRight: 10, - }; - - image = - foundOrg.image === "" ? ( - {foundOrg.name} - ) : ( - {foundOrg.name} {}} - /> - ); - - orgName = foundOrg.name; - orgId = foundOrg.id; - } - } - - return ( - -
- {data.amount === 1 && data.read === false ? - - : null} - {data.ignored === true ? - - : null} - {data.read === false ? - - : - - } - - {data.title} - -
- - {data.image !== undefined && data.image !== null && data.image.length > 0 ? - {data.title} - : - null - } - - {data.description} - -
- - - {data.read === false ? ( - - ) : null} - - - - - - - First seen: {new Date(data.created_at * 1000).toISOString().slice(0, 19)} - - - Last seen: {new Date(data.updated_at * 1000).toISOString().slice(0, 19)} - - - Times seen: {data.amount} - -
-
- ); - } return ( -
-

Notifications ({ +
+
+
+ Notifications ({ notifications?.filter((notification) => showRead === true || notification.read === false).length - })

+ }) - + Notifications help you find potential problems with your workflows and apps.  { ) : null}
- {notifications === null || notifications === undefined || notifications.length === 0 ? null : -
- {notifications.map((notification, index) => { - if (showRead === false && notification.read === true) { - return null - } - - return ( - - ) - })} -
- } + {clickedFromOrgTab? null : } - -

Suggestions

- +

Suggestions

+ Suggestions are tasks identified by Shuffle to help you discover ways to protect your and customers' company.
These range from simple configurations in Shuffle to Usecases you may have missed. 
{ ) }) } - +
+
) -} +}) export default Priorities; + + +const NotificationItem = memo((props) => { + const {data, selectedExecutionId, selectedWorkflow, highlightKMS, userdata, imagesize, boxColor, clickedFromOrgTab, notificationWidth, dismissNotification} = props + + var image = ""; + var orgName = ""; + var orgId = ""; + + + var highlighted = selectedExecutionId === "" && selectedWorkflow === "" ? false : data.reference_url === undefined || data.reference_url === null || data.reference_url.length === 0 ? false : data.reference_url.includes(selectedExecutionId) || data.reference_url.includes(selectedWorkflow) + + if (!highlighted && highlightKMS) { + if (data.title !== undefined && data.title !== null && data.title.toLowerCase().includes("kms")) { + highlighted = true + } else if (data.description !== undefined && data.description !== null && data.description.toLowerCase().includes("kms")) { + highlighted = true + } + + } + + if (userdata.orgs !== undefined) { + const foundOrg = userdata.orgs.find((org) => org.id === data["org_id"]); + if (foundOrg !== undefined && foundOrg !== null) { + //position: "absolute", bottom: 5, right: -5, + const imageStyle = { + width: imagesize, + height: imagesize, + pointerEvents: "none", + marginLeft: + data.creator_org !== undefined && data.creator_org.length > 0 + ? 20 + : 0, + borderRadius: 10, + border: + foundOrg.id === userdata.active_org.id + ? `3px solid ${boxColor}` + : null, + cursor: "pointer", + marginRight: 10, + }; + + image = + foundOrg.image === "" ? ( + {foundOrg.name} + ) : ( + {foundOrg.name} {}} + /> + ); + + orgName = foundOrg.name; + orgId = foundOrg.id; + } + } + + return ( + +
+ {data.amount === 1 && data.read === false ? + + : null} + {data.ignored === true ? + + : null} + {data.read === false ? + + : + + } + + {data.title} + +
+ + {data.image !== undefined && data.image !== null && data.image.length > 0 ? + {data.title} + : + null + } + + {data.description} + +
+ + + {data.read === false ? ( + + ) : null} + + + + + + + First seen: {new Date(data.created_at * 1000).toISOString().slice(0, 19)} + + + + Last seen: {new Date(data.updated_at * 1000).toISOString().slice(0, 19)} + + + + Times seen: {data.amount} + +
+
+ ); +}) + + +const NotificationComponent = memo(({notifications, showRead, selectedExecutionId, selectedWorkflow, highlightKMS, userdata, imagesize, boxColor, clickedFromOrgTab, notificationWidth, dismissNotification}) => { + + return( +
+ {notifications === null || notifications === undefined || notifications?.length === 0 ? ( + null + ) : +
+ {notifications?.map((notification, index) => { + if (showRead === false && notification.read === true) { + return null + } + + return ( + + ) + })} +
+ } +
+ ) +}) + +// const PaddingWrapper = memo(({children, clickedFromOrgTab}) => { + +// const { leftSideBarOpenByClick } = useContext(Context) + +// return( +//
+// {children} +//
+// ) +// }) + +// const Wrapper = memo(({children, clickedFromOrgTab}) => { + +// return( +// +// {children} +// +// ) +// }) diff --git a/frontend/src/components/ShuffleCodeEditor1.jsx b/frontend/src/components/ShuffleCodeEditor1.jsx index 40d807bc..875637a1 100644 --- a/frontend/src/components/ShuffleCodeEditor1.jsx +++ b/frontend/src/components/ShuffleCodeEditor1.jsx @@ -1084,6 +1084,8 @@ const CodeEditor = (props) => { maxHeight: isMobile ? "100%" : 700, border: theme.palette.defaultBorder, padding: isMobile ? "25px 10px 25px 10px" : 25, + zoom: 0.8, + backgroundColor: "black", }, }} > diff --git a/frontend/src/defaultCytoscapeStyle.jsx b/frontend/src/defaultCytoscapeStyle.jsx index 135b68f5..d61f5809 100644 --- a/frontend/src/defaultCytoscapeStyle.jsx +++ b/frontend/src/defaultCytoscapeStyle.jsx @@ -382,7 +382,7 @@ const data = [ css: { "background-color": "#f85a3e", "border-color": "#f85a3e", - "border-width": "12px", + "border-width": "7px", "transition-property": "border-width", "transition-duration": "0.25s", label: "data(label)", diff --git a/frontend/src/theme.jsx b/frontend/src/theme.jsx index 22304fd0..68934746 100644 --- a/frontend/src/theme.jsx +++ b/frontend/src/theme.jsx @@ -50,6 +50,12 @@ const theme = createTheme(adaptV4Theme({ borderRadius: 5, height: 40, }, + DialogStyle: { + backgroundColor: "#212121", + borderRadius: 2, + boxShadow: "0px 0px 10px 0px rgba(0,0,0,0.75)", + border: "1px solid #494949", + }, innerTextfieldStyle: { height: 40, fontSize: 16, diff --git a/frontend/src/views/Admin2.jsx b/frontend/src/views/Admin2.jsx index e6ede6f1..222ea44b 100644 --- a/frontend/src/views/Admin2.jsx +++ b/frontend/src/views/Admin2.jsx @@ -4,13 +4,13 @@ import { toast } from "react-toastify"; const Admin2 = (props) => { // Destructure props if needed - const { userdata, globalUrl, isCloud, serverside, checkLogin, notifications, setNotifications, stripeKey } = props; + const { userdata, globalUrl, serverside, checkLogin, notifications, setNotifications, stripeKey, isLoaded, isLoggedIn} = props; const [selectedTab, setSelectedTab] = useState('editdetails'); const [selectedStatus, setSelectedStatus] = React.useState([]); const [selectedOrganization, setSelectedOrganization] = useState({}); const [organizationFeatures, setOrganizationFeatures] = useState({}); const [orgRequest, setOrgRequest] = React.useState(true); - + const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; const handleGetOrg = (orgId) => { if ( serverside !== true && @@ -51,8 +51,11 @@ const Admin2 = (props) => { .then((responseJson) => { if (responseJson["success"] === false) { toast( - "Failed getting your org. If this persists, please contact support.", + "Failed getting your org. If this persists, please contact support. Redirecting to workflows...", ); + setTimeout(() => { + window.location.href = "/workflows"; + }, 3000); } else { if ( responseJson.sync_features === undefined || @@ -151,6 +154,77 @@ const Admin2 = (props) => { }); }; + const urlSearchParams = new URLSearchParams(window.location.search); + const params = Object.fromEntries(urlSearchParams.entries()); + const foundOrgID = params["org_id"] + + useEffect(() => { + + if(foundOrgID !== null && foundOrgID !== undefined && userdata?.support && foundOrgID?.length > 0) { + handleClickChangeOrg(foundOrgID) + } + }, [foundOrgID]); + + const handleClickChangeOrg = (orgId) => { + // Don't really care about the logout + //name: org.name, + //orgId = "asd" + const data = { + org_id: orgId, + }; + + localStorage.setItem("globalUrl", ""); + localStorage.setItem("getting_started_sidebar", "open"); + + fetch(`${globalUrl}/api/v1/orgs/${orgId}/change`, { + mode: "cors", + credentials: "include", + crossDomain: true, + method: "POST", + body: JSON.stringify(data), + withCredentials: true, + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }) + .then(function (response) { + if (response.status !== 200) { + console.log("Error in response"); + } else { + localStorage.removeItem("apps") + localStorage.removeItem("workflows") + localStorage.removeItem("userinfo") + } + + return response.json(); + }) + .then(function (responseJson) { + if (responseJson.success === true) { + if (responseJson.region_url !== undefined && responseJson.region_url !== null && responseJson.region_url.length > 0) { + localStorage.setItem("globalUrl", responseJson.region_url) + //globalUrl = responseJson.region_url + } + + setTimeout(() => { + window.location.reload() + }, 3000); + toast("Successfully changed active organization - refreshing!"); + } else { + if (responseJson.reason !== undefined && responseJson.reason !== null) { + if (!responseJson.reason.includes("already")) { + toast("Failed changing org: " + responseJson.reason); + } + } else { + toast("Failed changing org") + } + } + }) + .catch((error) => { + console.log("error changing: ", error); + //removeCookie("session_token", {path: "/"}) + }); + }; + const handleEditOrg = ( name, description, @@ -159,6 +233,7 @@ const Admin2 = (props) => { defaults, sso_config, lead_info, + { mfa_required } = {} ) => { const data = { name: name, @@ -168,6 +243,7 @@ const Admin2 = (props) => { defaults: defaults, sso_config: sso_config, lead_info: lead_info, + mfa_required: mfa_required !== undefined ? mfa_required : selectedOrganization?.mfa_required, }; const url = globalUrl + `/api/v1/orgs/${selectedOrganization.id}`; @@ -202,21 +278,40 @@ const Admin2 = (props) => { }); }; - // useEffect(() => { - // handleGetOrg(); - // }, []); const handleStatusChange = (event) => { const { value } = event.target; setSelectedStatus(value); handleEditOrg( - "", - "", + selectedOrganization?.name, + selectedOrganization?.description, selectedOrganization.id, - "", - {}, - {}, + selectedOrganization?.image, + { + app_download_repo: selectedOrganization?.defaults?.app_download_repo, + app_download_branch: selectedOrganization?.defaults?.app_download_branch, + workflow_download_repo: selectedOrganization?.defaults?.workflow_download_repo, + workflow_download_branch: selectedOrganization?.defaults?.workflow_download_branch, + notification_workflow: selectedOrganization?.defaults?.notification_workflow, + documentation_reference: selectedOrganization?.defaults?.documentation_reference, + workflow_upload_repo: selectedOrganization?.defaults?.workflow_upload_repo, + workflow_upload_branch: selectedOrganization?.defaults?.workflow_upload_branch, + workflow_upload_username: selectedOrganization?.defaults?.workflow_upload_username, + workflow_upload_token: selectedOrganization?.defaults?.workflow_upload_token, + newsletter: !selectedOrganization?.defaults?.newsletter, + weekly_recommendations: selectedOrganization?.defaults?.weekly_recommendations, + }, + { + sso_entrypoint: selectedOrganization?.sso_config?.sso_entrypoint, + sso_certificate: selectedOrganization?.sso_config?.sso_certificate, + client_id: selectedOrganization?.sso_config?.client_id, + client_secret: selectedOrganization?.sso_config?.client_secret, + openid_authorization: selectedOrganization?.sso_config?.openid_authorization, + openid_token: selectedOrganization?.sso_config?.openid_token, + SSORequired: selectedOrganization?.sso_config?.SSORequired, + auto_provision: selectedOrganization?.sso_config?.auto_provision, + }, value.length === 0 ? ["none"] : value, ); }; @@ -228,14 +323,14 @@ const Admin2 = (props) => { orgRequest ) { const orgId = userdata.active_org.id - console.log("orgID", orgId) + setOrgRequest(false); handleGetOrg(orgId); } return ( -
- +
+
); }; diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 3bfece7c..f14fbdff 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -422,7 +422,7 @@ const AngularWorkflow = (defaultprops) => { const [subworkflow, setSubworkflow] = React.useState({}); const [subworkflowStartnode, setSubworkflowStartnode] = React.useState(""); const [leftViewOpen, setLeftViewOpen] = React.useState(isMobile ? false : true); - const [leftBarSize, setLeftBarSize] = React.useState(isMobile ? 0 : 255) + const [leftBarSize, setLeftBarSize] = React.useState(isMobile ? 0 : 235) const [creatorProfile, setCreatorProfile] = React.useState({}); const [usecases, setUsecases] = React.useState([]); const [files, setFiles] = React.useState({ @@ -3813,7 +3813,7 @@ const releaseToConnectLabel = "Release to Connect" }); } - cy.fit(null, 100); + cy.fit(null, 400); cy.on("add", "node", (e) => onNodeAdded(e)); cy.on("add", "edge", (e) => onEdgeAdded(e)); } else { @@ -4052,7 +4052,7 @@ const releaseToConnectLabel = "Release to Connect" .then(() => { console.log("DONE: ", workflow_id); getWorkflow(workflow_id.value, nodedata); - cy.fit(null, 50); + cy.fit(null, 300); }); } }; @@ -4180,7 +4180,7 @@ const releaseToConnectLabel = "Release to Connect" if (nodedata.app_name === "Webhook" || nodedata.app_name === "Schedule" || nodedata.app_name === "Gmail" || nodedata.app_name === "Office365") { if (!found) { - console.log("Find amount of executions for the specific nodetype: ", nodedata.app_name, "Executions: ", workflowExecutions) + //console.log("Find amount of executions for the specific nodetype: ", nodedata.app_name, "Executions: ", workflowExecutions) // Find how many executions it has var executions = 0 const matchingExecutions = workflowExecutions.filter((execution => execution.execution_source === nodedata.app_name.toLowerCase())) @@ -6121,7 +6121,7 @@ const releaseToConnectLabel = "Release to Connect" event.target.remove() //console.log("Found branch already!") - toast.error("Triggers can have exactly one target node") + toast.error("Triggers can point to exactly one start node") return @@ -7782,7 +7782,7 @@ const releaseToConnectLabel = "Release to Connect" const item = typeIds[idkey] if (item.data.id === nodedata.id) { //console.log("items: ", item.data.id, nodedata.id) - parsedStyle["border-width"] = "12px" + parsedStyle["border-width"] = "7px" break } } @@ -8391,7 +8391,7 @@ const releaseToConnectLabel = "Release to Connect" // Reset view for cytoscape if (cy !== undefined && cy !== null) { cy.add(insertedNodes); - cy.fit(null, 200); + cy.fit(null, 400); } else { setElements(insertedNodes); } @@ -8772,7 +8772,7 @@ const releaseToConnectLabel = "Release to Connect" } // preview: true, - cy.fit(null, 200); + cy.fit(null, 400) cy.on("boxselect", "node", (e) => { if (e.target.data("isButton") || e.target.data("isDescriptor") || e.target.data("isSuggestion")) { @@ -9380,7 +9380,9 @@ const releaseToConnectLabel = "Release to Connect" onChange={handleSetTab} aria-label="Left sidebar tab" orientation={isMobile ? "vertical" : "horizontal"} - style={{}} + style={{ + display: "flex", + }} > - {isMobile ? null : Variables} + {isMobile ? null : Vars} } style={tabStyle} @@ -9776,11 +9778,13 @@ const releaseToConnectLabel = "Release to Connect" const barHeight = bodyHeight - appBarSize - 50; const appScrollStyle = { overflow: "scroll", - maxHeight: isMobile ? bodyHeight - appBarSize * 4 : barHeight-300, - minHeight: isMobile ? bodyHeight - appBarSize * 4 : barHeight-300, + maxHeight: isMobile ? bodyHeight - appBarSize * 4 : barHeight-70, + minHeight: isMobile ? bodyHeight - appBarSize * 4 : barHeight-70, marginTop: 1, overflowY: "auto", overflowX: "hidden", + + zoom: 0.9, } const handleAppDrag = (e, app) => { @@ -10568,12 +10572,12 @@ const releaseToConnectLabel = "Release to Connect" var viewedApps = [] return (
-
+
{ @@ -16153,13 +16158,15 @@ const releaseToConnectLabel = "Release to Connect" position: "absolute", top: isMobile ? 30 : 25, - left: isMobile ? 20 : leftSideBarOpenByClick ? leftBarSize + 275 : leftBarSize+100, + left: isMobile ? 20 : leftSideBarOpenByClick ? leftBarSize + 275 : leftBarSize+135, transition: "left 0.3s ease", - maxWidth: 500, + zoom: 0.9, } const TopCytoscapeBar = (props) => { + const [hovered, setHovered] = useState(false) + if (workflow.public === true) { return null } @@ -16174,12 +16181,30 @@ const releaseToConnectLabel = "Release to Connect"
- {workflow.name} + cursor: "pointer", + display: "flex", + borderRadius: theme.palette.borderRadius, + border: hovered ? "1px solid rgba(255,255,255,0.3)" : "1px solid transparent", + paddingRight: 10, + paddingLeft: 10, + position: "relative", + }} + onMouseEnter={() => { + setHovered(true) + }} + onMouseLeave={() => { + setHovered(false) + }} + onClick={() => { + setEditWorkflowModalOpen(true) + setLastSaved(false) + }} + > + + {workflow.name} {workflowAsCode && ( @@ -16204,7 +16229,7 @@ const releaseToConnectLabel = "Release to Connect" {!distributedFromParent ? isCorrectOrg ? null : - + Warning: Change { @@ -16275,7 +16300,7 @@ const releaseToConnectLabel = "Release to Connect" >Active Organization to edit this Workflow. : - + Warning: This workflow is controlled by your parent org and may not be editable. } @@ -16285,7 +16310,7 @@ const releaseToConnectLabel = "Release to Connect" Select an Org @@ -16294,10 +16319,15 @@ const releaseToConnectLabel = "Release to Connect" pointerEvents: "auto", backgroundColor: theme.palette.inputColor, color: "white", - height: 50, maxWidth: 250, minWidth: 250, borderRadius: theme.palette?.borderRadius, + height: 40, + }} + InputProps={{ + style: { + height: 40, + } }} labelId="suborg-changer" value={workflow.org_id} @@ -16470,47 +16500,16 @@ const releaseToConnectLabel = "Release to Connect" } - -
-
+
{showEnvironment === true && environments.length > 1 && selectedActionEnvironment !== undefined && selectedActionEnvironment !== null && selectedActionEnvironment.Name !== undefined && selectedActionEnvironment.Name !== null ? - + - Execution Location + Location selected.length ? selected.join(', ') : 'All Categories'} > -
- - {userdata.priorities[0].name} - -
- - - {userdata.priorities[0].description} - - -
-
-
- - {/* - - */} -
-
- : null} + All Categories + {usecases.map((usecase, index) => { + if (usecase?.name === "5. Verify") { + return null; + } - {/* {foundPriority != null && workflows.length < 6 ? - - : null} */} + const percentDone = usecase.matches.length > 0 ? parseInt(usecase.matches.length / usecase.list.length * 100) : 0 + if (percentDone === 0) { + usecase = findMatches(usecase, workflows) + } -
- - Workflows - - -
- - - - - -
- -
- - - { - console.log("CHANGE: ", chips); - setFilters(chips); - findWorkflow(chips); - }} - style={{ flex: 0.9 }} - InputProps={{ - style: { - color: "white", - borderRadius: 8, - }, - }} - sx={{ - '& .MuiOutlinedInput-root': { - '& fieldset': { - borderColor: 'rgba(255, 255, 255, 0.2)', - }, - '&:hover fieldset': { - borderColor: 'rgba(255, 255, 255, 0.3)', - }, - '&.Mui-focused fieldset': { - borderColor: '#f85a3e', - }, - }, - '& .MuiChip-root': { - backgroundColor: 'rgba(255, 255, 255, 0.1)', - color: 'white', - '& .MuiChip-deleteIcon': { - color: 'rgba(255, 255, 255, 0.7)', - '&:hover': { - color: 'white', - }, - }, - }, - }} - /> - - - -
- - navigate("/workflows/debug")} - > - - - - - { - if (view === "grid") { - localStorage.setItem("workflowView", "list"); - setView("list"); + if (!filters.includes(usecase?.name.toLowerCase())) { + addFilter(usecase.name) } else { - localStorage.setItem("workflowView", "grid"); - setView("grid"); + removeFilter(filters.indexOf(usecase?.name.toLowerCase())) + } + }} + style={{ + padding: "12px 16px", + borderBottom: index === usecases.length - 2 ? "none" : "1px solid rgba(255,255,255,0.05)", + "&:hover": { + backgroundColor: "rgba(255,255,255,0.1)" } }} > - { - view === "list" ? : - } +
+ +
+ + {category} + + + {usecase?.matches.length}/{usecase?.list.length} + +
+
+ + ) + })} + + + +
+
+ + navigate("/workflows/debug")} + disabled={currTab === 2} + > + + + + { + const newView = view === "grid" ? "list" : "grid"; + localStorage.setItem("workflowView", newView); + setView(newView); + }} + disabled={currTab === 2} + > + {view === "grid" ? : } + + + upload.click()} + disabled={currTab === 2} > {submitLoading ? : } + { ref={(ref) => (upload = ref)} onChange={importFiles} /> - + + { - exportAllWorkflows(workflows); - }} + style={{ ...iconButtonStyle, cursor: "pointer" }} + disabled={isCloud || currTab === 2} + onClick={() => exportAllWorkflows(workflows)} >
- - -
-
- { - isLoadingWorkflow ? ( -
- -
- ) : ( - view === "grid" && currTab !== 2 ? ( - <> -
- - {filteredWorkflows.map((data, index) => { - // Shouldn't be a part of this list - if (data.public === true) { - return null - } - - if (firstLoad) { - workflowDelay += 75 - } else { - return - } - - return ( - - {/**/} - - {/**/} - - ) - })} -
- - ) : ( - currTab !== 2 && - ) - ) - } - - { - currTab === 2 && - ( - - - - - ) - } - -
+
+ { + isLoadingWorkflow ? ( + + ) : ( + view === "grid" && currTab !== 2 ? ( + <> +
- {/* {foundPriority != null && filteredWorkflows.length > 6 ? - - : null} */} + {filteredWorkflows.map((data, index) => { + // Shouldn't be a part of this list + if (data.public === true) { + return null + } + + if (firstLoad) { + workflowDelay += 75 + } else { + return + } + + return ( + + {/**/} + + {/**/} + + ) + })} +
+ + ) : ( + currTab !== 2 && + ) + ) + } + + { + currTab === 2 && + ( + + + + + ) + } + +
-
+ + + ); }); @@ -4758,7 +4756,7 @@ const Workflows2 = (props) => { ); // Maybe use gridview or something, idk - return
{loadedCheck}
; + return
{loadedCheck}
; };