Cloud sync for all edits in May->June
This commit is contained in:
@@ -23,7 +23,7 @@ require (
|
|||||||
github.com/gorilla/mux v1.8.1
|
github.com/gorilla/mux v1.8.1
|
||||||
github.com/h2non/filetype v1.1.3
|
github.com/h2non/filetype v1.1.3
|
||||||
github.com/satori/go.uuid v1.2.0
|
github.com/satori/go.uuid v1.2.0
|
||||||
github.com/shuffle/shuffle-shared v0.8.82
|
github.com/shuffle/shuffle-shared v0.8.84
|
||||||
golang.org/x/crypto v0.38.0
|
golang.org/x/crypto v0.38.0
|
||||||
google.golang.org/api v0.236.0
|
google.golang.org/api v0.236.0
|
||||||
google.golang.org/grpc v1.72.2
|
google.golang.org/grpc v1.72.2
|
||||||
|
|||||||
@@ -350,8 +350,8 @@ github.com/sendgrid/sendgrid-go v3.16.1+incompatible h1:zWhTmB0Y8XCDzeWIm2/BIt1G
|
|||||||
github.com/sendgrid/sendgrid-go v3.16.1+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
|
github.com/sendgrid/sendgrid-go v3.16.1+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
|
||||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||||
github.com/shuffle/shuffle-shared v0.8.82 h1:vwXG/NOZNoPMfbF14ZnygoY8+AI2QVsggWfgfpSbqzo=
|
github.com/shuffle/shuffle-shared v0.8.84 h1:ElIMQYjKBVOiadbiGkSzt/lPU5xqaQwRxQvk9wx/xYM=
|
||||||
github.com/shuffle/shuffle-shared v0.8.82/go.mod h1:RdfNxqCPI+zU4jQKy3E/p4Io2injm7LpSKQUCDHNtLk=
|
github.com/shuffle/shuffle-shared v0.8.84/go.mod h1:RdfNxqCPI+zU4jQKy3E/p4Io2injm7LpSKQUCDHNtLk=
|
||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
|
|||||||
+5
-5
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
frontend:
|
frontend:
|
||||||
image: ghcr.io/shuffle/shuffle-frontend:latest
|
image: ghcr.io/shuffle/shuffle-frontend:nightly
|
||||||
container_name: shuffle-frontend
|
container_name: shuffle-frontend
|
||||||
hostname: shuffle-frontend
|
hostname: shuffle-frontend
|
||||||
ports:
|
ports:
|
||||||
@@ -14,7 +14,7 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- backend
|
- backend
|
||||||
backend:
|
backend:
|
||||||
image: ghcr.io/shuffle/shuffle-backend:latest
|
image: ghcr.io/shuffle/shuffle-backend:nightly
|
||||||
container_name: shuffle-backend
|
container_name: shuffle-backend
|
||||||
hostname: ${BACKEND_HOSTNAME}
|
hostname: ${BACKEND_HOSTNAME}
|
||||||
# Here for debugging:
|
# Here for debugging:
|
||||||
@@ -33,7 +33,7 @@ services:
|
|||||||
- SHUFFLE_FILE_LOCATION=/shuffle-files
|
- SHUFFLE_FILE_LOCATION=/shuffle-files
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
orborus:
|
orborus:
|
||||||
image: ghcr.io/shuffle/shuffle-orborus:latest
|
image: ghcr.io/shuffle/shuffle-orborus:nightly
|
||||||
container_name: shuffle-orborus
|
container_name: shuffle-orborus
|
||||||
hostname: shuffle-orborus
|
hostname: shuffle-orborus
|
||||||
networks:
|
networks:
|
||||||
@@ -55,13 +55,13 @@ services:
|
|||||||
- SHUFFLE_STATS_DISABLED=true
|
- SHUFFLE_STATS_DISABLED=true
|
||||||
- SHUFFLE_LOGS_DISABLED=true
|
- SHUFFLE_LOGS_DISABLED=true
|
||||||
- SHUFFLE_SWARM_CONFIG=run
|
- SHUFFLE_SWARM_CONFIG=run
|
||||||
- SHUFFLE_WORKER_IMAGE=ghcr.io/shuffle/shuffle-worker:latest
|
- SHUFFLE_WORKER_IMAGE=ghcr.io/shuffle/shuffle-worker:nightly
|
||||||
env_file: .env
|
env_file: .env
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
security_opt:
|
security_opt:
|
||||||
- seccomp:unconfined
|
- seccomp:unconfined
|
||||||
opensearch:
|
opensearch:
|
||||||
image: opensearchproject/opensearch:2.19.1
|
image: opensearchproject/opensearch:3.0.0
|
||||||
hostname: shuffle-opensearch
|
hostname: shuffle-opensearch
|
||||||
container_name: shuffle-opensearch
|
container_name: shuffle-opensearch
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect, useContext, memo } from 'react';
|
import React, { useState, useEffect, useContext, memo } from 'react';
|
||||||
import { useNavigate, useLocation } from 'react-router-dom';
|
import { useNavigate, useLocation } from 'react-router-dom';
|
||||||
import OrganizationTab from '../components/OrganizationTab.jsx';
|
import OrganizationTab from '../components/OrganizationTab.jsx';
|
||||||
|
import PartnerTab from '../components/PartnerTab.jsx';
|
||||||
import UserManagmentTab from '../components/UserManagmentTab.jsx';
|
import UserManagmentTab from '../components/UserManagmentTab.jsx';
|
||||||
import CacheView from "../components/CacheView.jsx";
|
import CacheView from "../components/CacheView.jsx";
|
||||||
import Files from "../components/Files.jsx";
|
import Files from "../components/Files.jsx";
|
||||||
@@ -25,6 +26,24 @@ import { Index } from 'react-instantsearch-dom';
|
|||||||
import { Context } from '../context/ContextApi.jsx';
|
import { Context } from '../context/ContextApi.jsx';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
const PartnerIcon = ({ strokeColor, fillColor = 'transparent', width = 22, height = 22 }) => (
|
||||||
|
<svg
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
viewBox="0 0 18 16"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M14.5327 4.49465C14.0102 4.60513 13.5779 4.90684 13.2905 5.31071L12.382 1L1.7665 3.23791C1.24516 3.34838 0.911462 3.86034 1.02072 4.38181L3.25807 15L7.57601 14.0901C7.16749 13.8026 6.85992 13.3679 6.74948 12.8393C6.51672 11.7334 7.22331 10.6477 8.32892 10.4149C9.43453 10.1821 10.52 10.8889 10.7527 11.9947C10.8643 12.5221 10.7587 13.0448 10.501 13.4724L14.8189 12.5625L13.9104 8.25182C14.3368 8.50484 14.8533 8.60699 15.3759 8.49652C16.4815 8.2637 17.1893 7.17801 16.9553 6.07212C16.7225 4.96623 15.6371 4.25827 14.5315 4.49228L14.5327 4.49465Z"
|
||||||
|
fill={fillColor}
|
||||||
|
stroke={strokeColor}
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
const AdminNavBar = (props) => {
|
const AdminNavBar = (props) => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { globalUrl, userdata, isCloud,isOrgLoaded, isLoaded,removeCookie, handleStatusChange, selectedStatus, setSelectedStatus, handleEditOrg, serverside, notifications, handleGetOrg, orgId, checkLogin, setNotifications, stripeKey, setSelectedOrganization, selectedOrganization } = props;
|
const { globalUrl, userdata, isCloud,isOrgLoaded, isLoaded,removeCookie, handleStatusChange, selectedStatus, setSelectedStatus, handleEditOrg, serverside, notifications, handleGetOrg, orgId, checkLogin, setNotifications, stripeKey, setSelectedOrganization, selectedOrganization } = props;
|
||||||
@@ -37,17 +56,6 @@ const AdminNavBar = (props) => {
|
|||||||
const [visibleItems, setVisibleItems] = useState([]);
|
const [visibleItems, setVisibleItems] = useState([]);
|
||||||
const [isUserDataLoaded, setIsUserDataLoaded] = useState(false);
|
const [isUserDataLoaded, setIsUserDataLoaded] = useState(false);
|
||||||
|
|
||||||
const items = [
|
|
||||||
{ iconSrc: <BusinessIcon />, alt: "Organization Icon", text: "Organization", component: OrganizationTab, props: { isIntegrationPartner, isChildOrg, isGlobalUser, globalUrl,removeCookie, selectedStatus, isLoaded, setSelectedStatus, handleStatusChange, handleEditOrg, handleGetOrg, userdata, isCloud, serverside, notifications, checkLogin, setNotifications, stripeKey, setSelectedOrganization, selectedOrganization } },
|
|
||||||
{ iconSrc: <PermIdentityIcon />, alt: "Users Icon", text: "Users", component: UserManagmentTab, props: { globalUrl, userdata, serverside, isCloud, selectedOrganization, setSelectedOrganization, handleEditOrg } },
|
|
||||||
{ iconSrc: <HttpsOutlinedIcon />, alt: "App Auth Icon", text: "App_auth", component: AppAuthTab, props: { globalUrl, userdata, isCloud, selectedOrganization } },
|
|
||||||
{ iconSrc: <StorageOutlinedIcon />, alt: "Datastore Icon", text: "Datastore", component: CacheView, props: { globalUrl, userdata, selectedOrganization, serverside, isSelectedDataStore, orgId , isCloud} },
|
|
||||||
{ iconSrc: <InsertDriveFileOutlinedIcon />, alt: "Files Icon", text: "Files", component: Files, props: { isCloud, globalUrl, userdata, serverside, selectedOrganization, isSelectedFiles } },
|
|
||||||
{ iconSrc: <AccessTimeOutlinedIcon />, alt: "Trigger Icon", text: "Triggers", component: SchedulesTab, props: { globalUrl, userdata, isCloud, serverside } },
|
|
||||||
{ iconSrc: <FmdGoodOutlinedIcon />, alt: "Environments Icon", text: "Locations", component: EnvironmentTab, props: { globalUrl, userdata, isCloud, selectedOrganization } },
|
|
||||||
{ iconSrc: <GroupOutlinedIcon />, alt: "Tenants Icon", text: "Tenants", component: TenantsTab, props: {isCloud, globalUrl, userdata, serverside, selectedOrganization, setSelectedOrganization, checkLogin } }
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (userdata && userdata?.active_org?.id?.length > 0) {
|
if (userdata && userdata?.active_org?.id?.length > 0) {
|
||||||
@@ -119,7 +127,17 @@ const AdminNavBar = (props) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const queryParams = new URLSearchParams(location.search);
|
const queryParams = new URLSearchParams(location.search);
|
||||||
const tabName = queryParams.get('tab');
|
const tabName = queryParams.get('tab');
|
||||||
|
const partnerTab = queryParams.get('partner_tab');
|
||||||
|
|
||||||
|
// if(!isCloud){
|
||||||
|
// if(tabName === "partner" || partnerTab !== null) {
|
||||||
|
// setSelectedItem("Organization");
|
||||||
|
// navigate(`?tab=organization`, { replace: true });
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
if (partnerTab) {
|
||||||
|
setSelectedItem("Partner");
|
||||||
|
}
|
||||||
if (tabName === "environments") {
|
if (tabName === "environments") {
|
||||||
setSelectedItem("Locations");
|
setSelectedItem("Locations");
|
||||||
} else if (tabName === "suborgs") {
|
} else if (tabName === "suborgs") {
|
||||||
@@ -128,11 +146,24 @@ const AdminNavBar = (props) => {
|
|||||||
setSelectedItem("Datastore");
|
setSelectedItem("Datastore");
|
||||||
}else if (tabName) {
|
}else if (tabName) {
|
||||||
setSelectedItem(tabName.charAt(0).toUpperCase() + tabName.slice(1));
|
setSelectedItem(tabName.charAt(0).toUpperCase() + tabName.slice(1));
|
||||||
} else {
|
}else if (tabName === "partner") {
|
||||||
setSelectedItem("Organization");
|
setSelectedItem("Partner");
|
||||||
}
|
}
|
||||||
|
|
||||||
}, [location.search]);
|
}, [location.search]);
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{ iconSrc: <BusinessIcon />, alt: "Organization Icon", text: "Organization", component: OrganizationTab, props: { isIntegrationPartner, isChildOrg, isGlobalUser,globalUrl,removeCookie, selectedStatus, isLoaded, setSelectedStatus, handleStatusChange, handleEditOrg, handleGetOrg, userdata, isCloud, serverside, notifications, checkLogin, setNotifications, stripeKey, setSelectedOrganization, selectedOrganization } },
|
||||||
|
{ iconSrc: undefined, alt: "Partner Icon", text: "Partner", component: PartnerTab, props: { globalUrl,removeCookie, isLoaded, handleGetOrg, userdata, isCloud, serverside, checkLogin, setSelectedOrganization, selectedOrganization } },
|
||||||
|
{ iconSrc: <PermIdentityIcon />, alt: "Users Icon", text: "Users", component: UserManagmentTab, props: { globalUrl, userdata, serverside, isCloud, selectedOrganization, setSelectedOrganization, handleEditOrg } },
|
||||||
|
{ iconSrc: <HttpsOutlinedIcon />, alt: "App Auth Icon", text: "App_auth", component: AppAuthTab, props: { globalUrl, userdata, isCloud, selectedOrganization } },
|
||||||
|
{ iconSrc: <StorageOutlinedIcon />, alt: "Datastore Icon", text: "Datastore", component: CacheView, props: { globalUrl, userdata, selectedOrganization, serverside, isSelectedDataStore, orgId , isCloud} },
|
||||||
|
{ iconSrc: <InsertDriveFileOutlinedIcon />, alt: "Files Icon", text: "Files", component: Files, props: { isCloud, globalUrl, userdata, serverside, selectedOrganization, isSelectedFiles } },
|
||||||
|
{ iconSrc: <AccessTimeOutlinedIcon />, alt: "Trigger Icon", text: "Triggers", component: SchedulesTab, props: { globalUrl, userdata, isCloud, serverside } },
|
||||||
|
{ iconSrc: <FmdGoodOutlinedIcon />, alt: "Environments Icon", text: "Locations", component: EnvironmentTab, props: { globalUrl, userdata, isCloud, selectedOrganization } },
|
||||||
|
{ iconSrc: <GroupOutlinedIcon />, alt: "Tenants Icon", text: "Tenants", component: TenantsTab, props: {isCloud, globalUrl, userdata, serverside, selectedOrganization, setSelectedOrganization, checkLogin } }
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
const setConfig = (newValue) => {
|
const setConfig = (newValue) => {
|
||||||
setSelectedItem(newValue);
|
setSelectedItem(newValue);
|
||||||
if (newValue === "App Auth") {
|
if (newValue === "App Auth") {
|
||||||
@@ -217,7 +248,7 @@ const AdminNavBar = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return <ComponentToRender {...updatedProps} />;
|
return <ComponentToRender {...updatedProps} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultImage = "/images/logos/orange_logo.svg"
|
const defaultImage = "/images/logos/orange_logo.svg"
|
||||||
const imageData =
|
const imageData =
|
||||||
@@ -296,7 +327,7 @@ const AdminNavBar = (props) => {
|
|||||||
disabled={
|
disabled={
|
||||||
((item.text === "Users") || (item.text === "Files") || (item.text === "Triggers") || (item.text === "Locations")) && !(userdata?.support || userdata?.active_org?.role === "admin")
|
((item.text === "Users") || (item.text === "Files") || (item.text === "Triggers") || (item.text === "Locations")) && !(userdata?.support || userdata?.active_org?.role === "admin")
|
||||||
}
|
}
|
||||||
startIcon={item.iconSrc}
|
startIcon={item?.iconSrc === undefined ? <PartnerIcon strokeColor={selectedItem === item.text ? theme.palette.text.primary : theme.palette.text.secondary} /> : item?.iconSrc}
|
||||||
onClick={() => setConfig(item.text)}
|
onClick={() => setConfig(item.text)}
|
||||||
>
|
>
|
||||||
{item.text.replace(/_/g, " ")}
|
{item.text.replace(/_/g, " ")}
|
||||||
@@ -308,7 +339,10 @@ const AdminNavBar = (props) => {
|
|||||||
|
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
<Wrapper2>{renderComponent()}</Wrapper2>
|
|
||||||
|
<Wrapper2>
|
||||||
|
{renderComponent()}
|
||||||
|
</Wrapper2>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -424,7 +458,13 @@ const Loader = () => {
|
|||||||
const PaddingWrapper2 = memo(({ children }) => {
|
const PaddingWrapper2 = memo(({ children }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div div style={{marginBottom: 30, width: "75%" , maxWidth: 1200, height: "100%", boxSizing: 'border-box'}}>
|
<div style={{
|
||||||
|
marginBottom: 30,
|
||||||
|
width: "75%" ,
|
||||||
|
maxWidth: 1200,
|
||||||
|
height: "100%",
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
}}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
+1405
-388
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,205 @@
|
|||||||
|
import React, { useState, useEffect, useContext, memo } from "react";
|
||||||
|
|
||||||
|
import { Context } from "../context/ContextApi.jsx";
|
||||||
|
import { getTheme } from "../theme.jsx";
|
||||||
|
import { GetIconInfo } from "../views/Workflows2.jsx";
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
Typography,
|
||||||
|
Paper,
|
||||||
|
LinearProgress,
|
||||||
|
Grid,
|
||||||
|
Button,
|
||||||
|
} from '@mui/material';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Rocket as RocketIcon,
|
||||||
|
FilterAlt as FilterAltIcon,
|
||||||
|
} from '@mui/icons-material';
|
||||||
|
|
||||||
|
const CollectIngestModal = (props) => {
|
||||||
|
const { globalUrl, open, setOpen } = props;
|
||||||
|
|
||||||
|
const { themeMode, brandColor } = useContext(Context);
|
||||||
|
const theme = getTheme(themeMode, brandColor);
|
||||||
|
|
||||||
|
if (open === undefined || open === null) {
|
||||||
|
console.error("CollectIngestModal: 'open' prop is required.");
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setOpen === undefined || setOpen === null) {
|
||||||
|
console.error("CollectIngestModal: 'setOpen' prop is required.");
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const startIngestion = (appname, index) => {
|
||||||
|
console.log("APPNAME:", appname, "INDEX:", index)
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
"app_name": appname,
|
||||||
|
"label": appname,
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `${globalUrl}/api/v2/workflows/generate`
|
||||||
|
fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
console.log("Ingestion started successfully:", data);
|
||||||
|
toast.success(`Ingestion for ${appname} started successfully!`);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Error starting ingestion:", error);
|
||||||
|
toast.error(`Failed to start ingestion for ${appname}. Please try again.`);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const IngestItem = (props) => {
|
||||||
|
const { type, index } = props
|
||||||
|
|
||||||
|
const [hovering, setHovering] = useState(false);
|
||||||
|
const [isFinished, setIsFinished] = useState(false);
|
||||||
|
|
||||||
|
const appname = type
|
||||||
|
const ingestedAmount = 20
|
||||||
|
|
||||||
|
const iconDetails = GetIconInfo({
|
||||||
|
"app_name": appname,
|
||||||
|
"name": appname,
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
//<Grid item xs={hovering ? 12 : 5.9}
|
||||||
|
<Grid item xs={12}
|
||||||
|
style={{
|
||||||
|
minHeight: hovering ? 225 : 145,
|
||||||
|
maxHeight: hovering ? 225 : 145,
|
||||||
|
cursor: "pointer",
|
||||||
|
position: "relative",
|
||||||
|
transition: "all 0.3s ease-in-out",
|
||||||
|
|
||||||
|
borderRadius: theme.palette.borderRadius,
|
||||||
|
border: hovering ? `2px solid ${theme.palette.primary.main}` : isFinished ? `2px solid ${theme.palette.success.main}` : `2px solid ${theme.palette.secondary.main}`,
|
||||||
|
textAlign: "center",
|
||||||
|
marginBottom: 5,
|
||||||
|
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
onMouseEnter={() => setHovering(true)}
|
||||||
|
onMouseLeave={() => setHovering(false)}
|
||||||
|
>
|
||||||
|
<div style={{marginTop: 35, marginBottom: 35, }}>
|
||||||
|
{iconDetails?.originalIcon && (
|
||||||
|
iconDetails?.originalIcon
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Typography variant="h4" style={{marginTop: 10, }}>
|
||||||
|
|
||||||
|
{appname}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button variant="contained" onClick={() => {
|
||||||
|
toast.info("Starting ingest for relevant apps")
|
||||||
|
startIngestion(appname, index)
|
||||||
|
}}>
|
||||||
|
Start Ingestion
|
||||||
|
</Button>
|
||||||
|
{hovering ?
|
||||||
|
<div>
|
||||||
|
</div>
|
||||||
|
: null}
|
||||||
|
|
||||||
|
{isFinished ?
|
||||||
|
<div>
|
||||||
|
<Typography variant="body1" style={{
|
||||||
|
position: "absolute",
|
||||||
|
bottom: 10,
|
||||||
|
left: 10,
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
}}>
|
||||||
|
{ingestedAmount} / X
|
||||||
|
</Typography>
|
||||||
|
<LinearProgress
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
position: "absolute",
|
||||||
|
bottom: 0,
|
||||||
|
}}
|
||||||
|
variant="determinate"
|
||||||
|
fullWidth value={{ingestedAmount}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
: null}
|
||||||
|
</Grid>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
PaperProps={{
|
||||||
|
sx: {
|
||||||
|
borderRadius: theme?.palette?.DialogStyle?.borderRadius,
|
||||||
|
border: theme?.palette?.DialogStyle?.border,
|
||||||
|
minWidth: 500,
|
||||||
|
minHeight: 700,
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
open={open}
|
||||||
|
onClose={() => {
|
||||||
|
setOpen(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogTitle>
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent style={{ paddingLeft: "30px", paddingRight: '30px', backgroundColor: theme.palette.DialogStyle.backgroundColor, }}>
|
||||||
|
|
||||||
|
<Typography variant="h6" style={{ color: theme.palette.text.primary, marginBottom: 20 }}>
|
||||||
|
<FilterAltIcon style={{ verticalAlign: "middle", marginRight: 10 }} />
|
||||||
|
Collection and Ingestion
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Grid container>
|
||||||
|
<IngestItem type="Ingest Tickets" index={1} />
|
||||||
|
<IngestItem type="Enable Threat feeds" index={2} />
|
||||||
|
<IngestItem type="Track Assets" index={2} />
|
||||||
|
<IngestItem type="Enable Search" index={2} />
|
||||||
|
<IngestItem type="Enable Mitre Att&ck techniques" index={2} />
|
||||||
|
<IngestItem type="Enable Detection Rules" index={2} />
|
||||||
|
<IngestItem type="Ingest Logs" index={2} />
|
||||||
|
</Grid>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CollectIngestModal
|
||||||
@@ -142,7 +142,6 @@ const countries = [
|
|||||||
suggested: true,
|
suggested: true,
|
||||||
},
|
},
|
||||||
{ code: 'GA', label: 'Gabon', phone: '241' },
|
{ code: 'GA', label: 'Gabon', phone: '241' },
|
||||||
{ code: 'GB', label: 'United Kingdom', phone: '44' },
|
|
||||||
{ code: 'GD', label: 'Grenada', phone: '1-473' },
|
{ code: 'GD', label: 'Grenada', phone: '1-473' },
|
||||||
{ code: 'GE', label: 'Georgia', phone: '995' },
|
{ code: 'GE', label: 'Georgia', phone: '995' },
|
||||||
{ code: 'GF', label: 'French Guiana', phone: '594' },
|
{ code: 'GF', label: 'French Guiana', phone: '594' },
|
||||||
@@ -178,7 +177,6 @@ const countries = [
|
|||||||
{ code: 'IE', label: 'Ireland', phone: '353' },
|
{ code: 'IE', label: 'Ireland', phone: '353' },
|
||||||
{ code: 'IL', label: 'Israel', phone: '972' },
|
{ code: 'IL', label: 'Israel', phone: '972' },
|
||||||
{ code: 'IM', label: 'Isle of Man', phone: '44' },
|
{ code: 'IM', label: 'Isle of Man', phone: '44' },
|
||||||
{ code: 'IN', label: 'India', phone: '91' },
|
|
||||||
{
|
{
|
||||||
code: 'IO',
|
code: 'IO',
|
||||||
label: 'British Indian Ocean Territory',
|
label: 'British Indian Ocean Territory',
|
||||||
@@ -390,12 +388,6 @@ const countries = [
|
|||||||
},
|
},
|
||||||
{ code: 'UA', label: 'Ukraine', phone: '380' },
|
{ code: 'UA', label: 'Ukraine', phone: '380' },
|
||||||
{ code: 'UG', label: 'Uganda', phone: '256' },
|
{ code: 'UG', label: 'Uganda', phone: '256' },
|
||||||
{
|
|
||||||
code: 'US',
|
|
||||||
label: 'United States',
|
|
||||||
phone: '1',
|
|
||||||
suggested: true,
|
|
||||||
},
|
|
||||||
{ code: 'UY', label: 'Uruguay', phone: '598' },
|
{ code: 'UY', label: 'Uruguay', phone: '598' },
|
||||||
{ code: 'UZ', label: 'Uzbekistan', phone: '998' },
|
{ code: 'UZ', label: 'Uzbekistan', phone: '998' },
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -681,7 +681,7 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
|
|||||||
<Divider style={{ marginBottom: 10, }} />
|
<Divider style={{ marginBottom: 10, }} />
|
||||||
|
|
||||||
<Typography color="textSecondary" align="center" style={{ marginTop: 5, marginBottom: 5, fontSize: 18 }}>
|
<Typography color="textSecondary" align="center" style={{ marginTop: 5, marginBottom: 5, fontSize: 18 }}>
|
||||||
Version: 2.1.0-rc1
|
Version: 2.1.0-rc2
|
||||||
</Typography>
|
</Typography>
|
||||||
</Menu>
|
</Menu>
|
||||||
</span>
|
</span>
|
||||||
@@ -745,6 +745,8 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
|
|||||||
|
|
||||||
localStorage.setItem("globalUrl", "");
|
localStorage.setItem("globalUrl", "");
|
||||||
localStorage.setItem("getting_started_sidebar", "open");
|
localStorage.setItem("getting_started_sidebar", "open");
|
||||||
|
localStorage.removeItem("workflows");
|
||||||
|
localStorage.removeItem("apps");
|
||||||
|
|
||||||
fetch(`${globalUrl}/api/v1/orgs/${orgId}/change`, {
|
fetch(`${globalUrl}/api/v1/orgs/${orgId}/change`, {
|
||||||
mode: "cors",
|
mode: "cors",
|
||||||
@@ -913,7 +915,8 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
|
|||||||
"US": "us",
|
"US": "us",
|
||||||
"EU": "eu",
|
"EU": "eu",
|
||||||
"CA": "ca",
|
"CA": "ca",
|
||||||
"UK": "gb"
|
"UK": "gb",
|
||||||
|
"au": "au",
|
||||||
};
|
};
|
||||||
|
|
||||||
region = regionMapping[region_url] || "eu";
|
region = regionMapping[region_url] || "eu";
|
||||||
@@ -1465,7 +1468,7 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
|
|||||||
<Collapse in={openSecurityTab} timeout="auto" unmountOnExit >
|
<Collapse in={openSecurityTab} timeout="auto" unmountOnExit >
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
maxHeight: openSecurityTab && expandLeftNav ? 185 : 0,
|
maxHeight: openSecurityTab && expandLeftNav ? 250 : 0,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
transition: "max-height 0.3s ease, opacity 0.3s ease",
|
transition: "max-height 0.3s ease, opacity 0.3s ease",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@@ -1681,6 +1684,48 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
|
|||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<span style={{ display: "inline-block", width: "100%" }}>
|
||||||
|
<Button
|
||||||
|
component={Link}
|
||||||
|
to="/partners"
|
||||||
|
onClick={(event) => {
|
||||||
|
setCurrentOpenTab("partners");
|
||||||
|
localStorage.setItem("lastTabOpenByUser", "partners");
|
||||||
|
}}
|
||||||
|
variant="text"
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
height: 35,
|
||||||
|
color: themeMode === "dark" ? lightText : darkText,
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
textTransform: "none",
|
||||||
|
backgroundColor:
|
||||||
|
currentOpenTab === "partners"
|
||||||
|
? themeMode === "dark" ? darkHoverColor : lightHoverColor
|
||||||
|
: "transparent",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: themeMode === "dark" ? darkHoverColor : lightHoverColor,
|
||||||
|
},
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{ position: "relative", left: !expandLeftNav ? 10 : 0, marginRight: 10, fontSize: 18 }}>
|
||||||
|
•
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
display: expandLeftNav ? "inline" : "none",
|
||||||
|
opacity: expandLeftNav ? 1 : 0,
|
||||||
|
transition: "opacity 0.3s ease",
|
||||||
|
fontSize: 18,
|
||||||
|
color: themeMode === "dark" ? currentOpenTab === "locations" ? theme.palette.text.primary : lightText : darkText,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Partners
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
</span>
|
||||||
</>): null}
|
</>): null}
|
||||||
</Box>
|
</Box>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
|
|||||||
@@ -61,9 +61,9 @@ const menuData = {
|
|||||||
{
|
{
|
||||||
title: "Singul",
|
title: "Singul",
|
||||||
description:
|
description:
|
||||||
"Connect and run actions seamlessly between different platforms.",
|
"Connect your favorite services with a singul line of code.",
|
||||||
icon: "/images/logos/singul.svg",
|
icon: "/images/logos/singul.svg",
|
||||||
path: "https://singul-docs.gitbook.io/singul/getting-started",
|
path: "https://singul.io",
|
||||||
gaData: {
|
gaData: {
|
||||||
category: "navbar",
|
category: "navbar",
|
||||||
action: "products_click",
|
action: "products_click",
|
||||||
@@ -662,9 +662,9 @@ const MobileMenu = ({ anchorEl, handleClose, isLoggedIn, navigate, isCloud }) =>
|
|||||||
action: "partners_click",
|
action: "partners_click",
|
||||||
label: "go_to_partners",
|
label: "go_to_partners",
|
||||||
})
|
})
|
||||||
handleItemClick('/partners')
|
handleItemClick('/become-partner')
|
||||||
}else{
|
}else{
|
||||||
window.open("https://shuffler.io/partners", '_blank');
|
window.open("https://shuffler.io/become-partner", '_blank');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -1391,26 +1391,151 @@ const Navbar = (props) => {
|
|||||||
>
|
>
|
||||||
Pricing
|
Pricing
|
||||||
</Button>
|
</Button>
|
||||||
|
<Box
|
||||||
|
className="partners-container"
|
||||||
|
sx={{
|
||||||
|
position: "relative",
|
||||||
|
"&::after": {
|
||||||
|
// This creates an invisible connection between button and dropdown
|
||||||
|
content: '""',
|
||||||
|
position: "absolute",
|
||||||
|
top: "100%",
|
||||||
|
left: 0,
|
||||||
|
width: "100%",
|
||||||
|
height: "20px", // Buffer zone height
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
},
|
||||||
|
"&:hover .partners-dropdown": {
|
||||||
|
opacity: 1,
|
||||||
|
visibility: "visible",
|
||||||
|
pointerEvents: "auto",
|
||||||
|
transform: "translateY(0)",
|
||||||
|
transition: "opacity 0.3s ease, visibility 0s, transform 0.3s ease",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
component={Link}
|
component={Link}
|
||||||
to="/partners"
|
to="/become-partner"
|
||||||
sx={buttonStyles}
|
sx={{
|
||||||
|
...buttonStyles,
|
||||||
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if(isCloud) {
|
if(isCloud) {
|
||||||
navigate("/partners");
|
navigate("/become-partner");
|
||||||
ReactGA.event({
|
ReactGA.event({
|
||||||
category: "navbar",
|
category: "navbar",
|
||||||
action: "partners_click",
|
action: "partners_click",
|
||||||
label: "go_to_partners",
|
label: "go_to_become_partner",
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
window.open("https://shuffler.io/partners", '_blank');
|
window.open("https://shuffler.io/become-partner", '_blank');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Partners
|
Partners
|
||||||
</Button>
|
</Button>
|
||||||
|
<Box
|
||||||
|
className="partners-dropdown"
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
top: "140%",
|
||||||
|
left: 0,
|
||||||
|
backgroundColor: "#212121",
|
||||||
|
borderRadius: "4px",
|
||||||
|
boxShadow: "0px 5px 15px rgba(0, 0, 0, 0.2)",
|
||||||
|
width: "220px",
|
||||||
|
opacity: 0,
|
||||||
|
visibility: "hidden",
|
||||||
|
pointerEvents: "none",
|
||||||
|
transform: "translateY(-10px)",
|
||||||
|
transition: "opacity 0.3s ease, visibility 0s linear 0.3s, transform 0.3s ease",
|
||||||
|
zIndex: 10,
|
||||||
|
"&:hover": {
|
||||||
|
opacity: 1,
|
||||||
|
visibility: "visible",
|
||||||
|
pointerEvents: "auto",
|
||||||
|
transform: "translateY(0)",
|
||||||
|
},
|
||||||
|
// Add a pseudo-element to create a buffer zone above the dropdown
|
||||||
|
"&::before": {
|
||||||
|
content: '""',
|
||||||
|
position: "absolute",
|
||||||
|
top: "-20px", // Match the height of the buffer zone
|
||||||
|
left: 0,
|
||||||
|
width: "100%",
|
||||||
|
height: "20px",
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{}}>
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
sx={{
|
||||||
|
color: "white",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
textTransform: "none",
|
||||||
|
py: 2,
|
||||||
|
px: 2,
|
||||||
|
fontSize: "14px",
|
||||||
|
fontFamily: theme.typography.fontFamily,
|
||||||
|
borderRadius: "4px",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "rgba(255,255,255,0.05)"
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
if(isCloud) {
|
||||||
|
navigate("/become-partner");
|
||||||
|
ReactGA.event({
|
||||||
|
category: "navbar",
|
||||||
|
action: "partners_click",
|
||||||
|
label: "become_partner_click",
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
window.open("https://shuffler.io/become-partner", '_blank');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Become a partner
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
sx={{
|
||||||
|
color: "white",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
textTransform: "none",
|
||||||
|
fontSize: "14px",
|
||||||
|
fontFamily: theme.typography.fontFamily,
|
||||||
|
py: 2,
|
||||||
|
px: 2,
|
||||||
|
borderRadius: "4px",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "rgba(255,255,255,0.05)"
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
navigate("/partners");
|
||||||
|
if(isCloud) {
|
||||||
|
ReactGA.event({
|
||||||
|
category: "navbar",
|
||||||
|
action: "partners_click",
|
||||||
|
label: "discover_partners_click",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Discover partners
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { makeStyles, createStyles } from "@mui/styles";
|
|||||||
import {getTheme} from '../theme.jsx';
|
import {getTheme} from '../theme.jsx';
|
||||||
|
|
||||||
import { useNavigate, Link, useParams } from "react-router-dom";
|
import { useNavigate, Link, useParams } from "react-router-dom";
|
||||||
import { validateJson, GetIconInfo } from "../views/Workflows.jsx";
|
import { validateJson, GetIconInfo } from "../views/Workflows2.jsx";
|
||||||
import { GetParsedPaths } from "../views/Apps.jsx";
|
import { GetParsedPaths } from "../views/Apps.jsx";
|
||||||
import { sortByKey } from "../views/AngularWorkflow.jsx";
|
import { sortByKey } from "../views/AngularWorkflow.jsx";
|
||||||
import { NestedMenuItem } from "mui-nested-menu";
|
import { NestedMenuItem } from "mui-nested-menu";
|
||||||
@@ -825,7 +825,7 @@ const ParsedAction = (props) => {
|
|||||||
// Check if array, and if first item is object + success
|
// Check if array, and if first item is object + success
|
||||||
if (Array.isArray(valid.result) && valid.result.length > 0 && typeof valid.result[0] === "object") {
|
if (Array.isArray(valid.result) && valid.result.length > 0 && typeof valid.result[0] === "object") {
|
||||||
if (valid.result[0].success !== false) {
|
if (valid.result[0].success !== false) {
|
||||||
exampleData = valid.result[0]
|
exampleData = valid.result
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -910,14 +910,48 @@ const ParsedAction = (props) => {
|
|||||||
if (newActionList?.length > 0) {
|
if (newActionList?.length > 0) {
|
||||||
let appParentActions = parentActionList?.map(action => "$" + action.name.toLowerCase());
|
let appParentActions = parentActionList?.map(action => "$" + action.name.toLowerCase());
|
||||||
let notPresentAction = actions?.filter((action) => !appParentActions?.includes(action))
|
let notPresentAction = actions?.filter((action) => !appParentActions?.includes(action))
|
||||||
|
|
||||||
|
// Extract all variable references from paramvalue
|
||||||
|
// Examples of what it matches:
|
||||||
|
// - Simple variables: $test, $myVar, $x
|
||||||
|
// - Variables with underscores: $my_variable, $ok_ok, $testing_nice, $nice_try_man
|
||||||
|
// - Workflow data access patterns: $not_customer_copy.body.subject.#, $not_customer.body.subject.text
|
||||||
|
// - Deep nested properties: $email_action.response.items[0].sender.address
|
||||||
|
|
||||||
|
const variablePattern = /\$[a-zA-Z0-9_]+(?=\.|,|;|:|\s|"|'|`|\)|\]|\}|$)/g;
|
||||||
|
const foundVariables = paramvalue.match(variablePattern) || [];
|
||||||
|
|
||||||
notPresentAction?.forEach((action) => {
|
notPresentAction?.forEach((action) => {
|
||||||
action = action.replace(" ", "_");
|
action = action.replace(" ", "_");
|
||||||
if (paramvalue.includes(action)) {
|
|
||||||
|
// Check if the exact action is in the foundVariables list
|
||||||
|
if (foundVariables.includes(action)) {
|
||||||
errorVars.push(action);
|
errorVars.push(action);
|
||||||
// paramvalue = paramvalue.replace(action, "")
|
|
||||||
// paramvalue = paramvalue.replace(/^\s*[\r\n]/gm, "");
|
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
|
// Check for variables that don't exist in the workflow at all
|
||||||
|
foundVariables.forEach(variable => {
|
||||||
|
// Skip checking for $ followed by system vars as exec and shuffle_cache.
|
||||||
|
if (variable.match(/\$(exec|shuffle_cache)/)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Create list of all valid action variables from newActionList
|
||||||
|
const newActionNames = newActionList?.map(action => "$" + action.name.toLowerCase()) || [];
|
||||||
|
|
||||||
|
// Extract workflow and execution variables from newActionList
|
||||||
|
const workflowVars = newActionList
|
||||||
|
.filter(item => item.type === "workflow_variable" || item.type === "execution_variable")
|
||||||
|
.map(item => "$" + item.autocomplete.toLowerCase());
|
||||||
|
|
||||||
|
// If the variable doesn't exist in any workflow actions, parent actions, newActionList, or extracted variables, show error
|
||||||
|
if (!actions.includes(variable) &&
|
||||||
|
!appParentActions.includes(variable) &&
|
||||||
|
!newActionNames.includes(variable) &&
|
||||||
|
!workflowVars.includes(variable.toLowerCase())) {
|
||||||
|
errorVars.push(variable);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3085,9 +3119,6 @@ const ParsedAction = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var isAppSelected = false
|
var isAppSelected = false
|
||||||
|
|
||||||
console.log("App found: ", app.name, app.categories, appIndex, newactionname)
|
|
||||||
|
|
||||||
const paramIndex = selectedAction.parameters.findIndex((param) => param.name === "app_name")
|
const paramIndex = selectedAction.parameters.findIndex((param) => param.name === "app_name")
|
||||||
if (paramIndex > -1) {
|
if (paramIndex > -1) {
|
||||||
// Check the actual value and if it's the same
|
// Check the actual value and if it's the same
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { Box, Typography } from '@mui/material'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const PartnerApps = () => {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
minHeight: "500px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography>Partner Apps</Typography>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PartnerApps
|
||||||
@@ -0,0 +1,987 @@
|
|||||||
|
import React, { memo, useEffect, useState, useContext } from "react";
|
||||||
|
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { getTheme } from "../theme.jsx";
|
||||||
|
import { Context } from "../context/ContextApi.jsx";
|
||||||
|
import countries from "./Countries.jsx";
|
||||||
|
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
InputLabel,
|
||||||
|
Paper,
|
||||||
|
OutlinedInput,
|
||||||
|
Checkbox,
|
||||||
|
Card,
|
||||||
|
Tooltip,
|
||||||
|
FormControlLabel,
|
||||||
|
Chip,
|
||||||
|
Link,
|
||||||
|
Typography,
|
||||||
|
Switch,
|
||||||
|
Select,
|
||||||
|
MenuItem,
|
||||||
|
Divider,
|
||||||
|
ListItemText,
|
||||||
|
TextField,
|
||||||
|
Button,
|
||||||
|
Tabs,
|
||||||
|
Tab,
|
||||||
|
Grid,
|
||||||
|
Autocomplete,
|
||||||
|
Skeleton,
|
||||||
|
Box,
|
||||||
|
} from "@mui/material";
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
notchedOutline: {
|
||||||
|
borderColor: "#f85a3e !important",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const PartnerDetails = (props) => {
|
||||||
|
const {
|
||||||
|
userdata,
|
||||||
|
selectedOrganization,
|
||||||
|
setSelectedOrganization,
|
||||||
|
globalUrl,
|
||||||
|
isCloud,
|
||||||
|
adminTab,
|
||||||
|
isEditOrgTab,
|
||||||
|
partnerData,
|
||||||
|
loadingPartnerData,
|
||||||
|
setPartnerData,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const classes = useStyles();
|
||||||
|
const defaultBranch = "main";
|
||||||
|
const ITEM_HEIGHT = 48;
|
||||||
|
const ITEM_PADDING_TOP = 8;
|
||||||
|
const MenuProps = {
|
||||||
|
PaperProps: {
|
||||||
|
style: {
|
||||||
|
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
|
||||||
|
width: 400,
|
||||||
|
borderRadius: 10,
|
||||||
|
overflowY: "scroll",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
getContentAnchorEl: () => null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const expertiseOptions = [
|
||||||
|
"Cybersecurity",
|
||||||
|
"Cloud Security",
|
||||||
|
"Managed Detection and Response (MDR)",
|
||||||
|
"Incident Response",
|
||||||
|
"Penetration Testing",
|
||||||
|
"Compliance and Governance",
|
||||||
|
"Network Security",
|
||||||
|
"Endpoint Security",
|
||||||
|
"Identity and Access Management (IAM)",
|
||||||
|
"SIEM",
|
||||||
|
];
|
||||||
|
const servicesOptions = [
|
||||||
|
"Cybersecurity Consulting",
|
||||||
|
"Managed Security",
|
||||||
|
"Incident Response",
|
||||||
|
"Penetration Testing",
|
||||||
|
"Cloud Security",
|
||||||
|
"Managed Detection",
|
||||||
|
"Endpoint Security",
|
||||||
|
"Network Security",
|
||||||
|
"Identity Management",
|
||||||
|
"Compliance",
|
||||||
|
"Security Training",
|
||||||
|
"Security Management",
|
||||||
|
"Threat Intelligence",
|
||||||
|
"Web Security",
|
||||||
|
"Database Security",
|
||||||
|
"Disaster Recovery",
|
||||||
|
"IT Security Audit",
|
||||||
|
"Cybersecurity Training",
|
||||||
|
"Security Automation",
|
||||||
|
"AI Security",
|
||||||
|
"IoT Security",
|
||||||
|
"Cloud Security Broker",
|
||||||
|
"Security Service",
|
||||||
|
"Managed Security Provider",
|
||||||
|
"Cybersecurity Service",
|
||||||
|
]
|
||||||
|
|
||||||
|
const solutionsOptions = [
|
||||||
|
"EDR",
|
||||||
|
"MDR",
|
||||||
|
"XDR",
|
||||||
|
"Case Management",
|
||||||
|
"SIEM",
|
||||||
|
"Vulnerability Management",
|
||||||
|
"IPS",
|
||||||
|
"IDS",
|
||||||
|
"Threat Intelligence",
|
||||||
|
"IAM",
|
||||||
|
"Data Security",
|
||||||
|
"Incident Response",
|
||||||
|
"Cloud Security",
|
||||||
|
"Network Security",
|
||||||
|
]
|
||||||
|
|
||||||
|
const { themeMode } = useContext(Context);
|
||||||
|
const theme = getTheme(themeMode);
|
||||||
|
const [isDisabled, setIsDisabled] = useState(isCloud ? userdata?.active_org?.is_partner ? false : true : true);
|
||||||
|
const handleExpertiseChange = (event) => {
|
||||||
|
const { value } = event.target;
|
||||||
|
setPartnerData({
|
||||||
|
...partnerData,
|
||||||
|
expertise: value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsDisabled(isCloud ? userdata?.active_org?.is_partner ? false : true : true);
|
||||||
|
if(userdata?.support) {
|
||||||
|
setIsDisabled(false);
|
||||||
|
}
|
||||||
|
}, [userdata, isCloud]);
|
||||||
|
|
||||||
|
const handleServicesChange = (event) => {
|
||||||
|
const { value } = event.target;
|
||||||
|
setPartnerData({
|
||||||
|
...partnerData,
|
||||||
|
services: Array.isArray(value) ? value : [],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSolutionsChange = (event) => {
|
||||||
|
const { value } = event.target;
|
||||||
|
setPartnerData({
|
||||||
|
...partnerData,
|
||||||
|
solutions: Array.isArray(value) ? value : [],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const setSelectedRegion = (region) => {
|
||||||
|
// send a POST request to /api/v1/orgs/{org_id}/region with the region as the body
|
||||||
|
const regionMap = {
|
||||||
|
US: "us-west2",
|
||||||
|
EU: "europe-west2",
|
||||||
|
CA: "northamerica-northeast1",
|
||||||
|
UK: "europe-west2",
|
||||||
|
"EU-2": "europe-west3",
|
||||||
|
AUS: "australia-southeast1",
|
||||||
|
};
|
||||||
|
|
||||||
|
region = regionMap[region] || region;
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
dst_region: region,
|
||||||
|
};
|
||||||
|
|
||||||
|
toast.info(
|
||||||
|
"Changing region to " + region + "...This may take a few minutes."
|
||||||
|
);
|
||||||
|
|
||||||
|
fetch(`${globalUrl}/api/v1/orgs/${selectedOrganization.id}/change/region`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
credentials: "include",
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
timeOut: 1000,
|
||||||
|
}).then((response) => {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
response
|
||||||
|
.json()
|
||||||
|
.then((reason) => {
|
||||||
|
toast.error("Failed to change region: " + reason.reason);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
toast.error("Failed to change region");
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
toast("Region changed successfully! Reloading in 5 seconds..");
|
||||||
|
// Reload the page in 2 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload();
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return responseJson
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loadingPartnerData) {
|
||||||
|
return (
|
||||||
|
<div style={{ textAlign: "left", maxWidth: "95%" }}>
|
||||||
|
<Grid container spacing={3} style={{ textAlign: "left", marginTop: 5 }}>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<span>
|
||||||
|
<div>
|
||||||
|
<div style={{ flex: "3", color: "white" }}>
|
||||||
|
<div style={{ marginTop: 8, display: "flex" }} />
|
||||||
|
<div style={{ display: "flex" }}>
|
||||||
|
<div style={{ width: "100%", maxWidth: 434, marginRight: 10 }}>
|
||||||
|
<Typography variant="text" style={{ color: theme?.palette?.text?.primary }}>
|
||||||
|
Name
|
||||||
|
</Typography>
|
||||||
|
<Skeleton
|
||||||
|
variant="rounded"
|
||||||
|
height={35}
|
||||||
|
width="100%"
|
||||||
|
style={{
|
||||||
|
marginTop: 5,
|
||||||
|
borderRadius: 4
|
||||||
|
}}
|
||||||
|
animation="wave"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/* <div style={{ alignItems: "center" }}>
|
||||||
|
<div style={{ marginRight: "12px", color: theme?.palette?.text?.primary }}>
|
||||||
|
Expertise
|
||||||
|
</div>
|
||||||
|
<Skeleton
|
||||||
|
variant="rounded"
|
||||||
|
height={35}
|
||||||
|
width={220}
|
||||||
|
style={{
|
||||||
|
marginTop: 5,
|
||||||
|
borderRadius: 4
|
||||||
|
}}
|
||||||
|
animation="wave"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ alignItems: "center", marginLeft: 12 }}>
|
||||||
|
<div style={{ marginRight: "12px", color: theme?.palette?.text?.primary }}>
|
||||||
|
Services
|
||||||
|
</div>
|
||||||
|
<Skeleton
|
||||||
|
variant="rounded"
|
||||||
|
height={35}
|
||||||
|
width={220}
|
||||||
|
style={{
|
||||||
|
marginTop: 5,
|
||||||
|
borderRadius: 4
|
||||||
|
}}
|
||||||
|
animation="wave"
|
||||||
|
/>
|
||||||
|
</div> */}
|
||||||
|
<div style={{ alignItems: "center" }}>
|
||||||
|
<div style={{ marginRight: "12px", color: theme?.palette?.text?.primary }}>
|
||||||
|
Solutions
|
||||||
|
</div>
|
||||||
|
<Skeleton
|
||||||
|
variant="rounded"
|
||||||
|
height={35}
|
||||||
|
width={220}
|
||||||
|
style={{
|
||||||
|
marginTop: 5,
|
||||||
|
borderRadius: 4
|
||||||
|
}}
|
||||||
|
animation="wave"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginLeft: 13, fontSize: 16, color: "#9E9E9E" }}>
|
||||||
|
<Typography variant="text" style={{ color: theme?.palette?.text?.primary }}>
|
||||||
|
Region
|
||||||
|
</Typography>
|
||||||
|
<Skeleton
|
||||||
|
variant="rounded"
|
||||||
|
height={35}
|
||||||
|
width={190}
|
||||||
|
style={{
|
||||||
|
marginTop: 5,
|
||||||
|
borderRadius: 4
|
||||||
|
}}
|
||||||
|
animation="wave"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ alignItems: "center", marginLeft: 12 }}>
|
||||||
|
<div style={{ marginRight: "12px", color: theme?.palette?.text?.primary }}>
|
||||||
|
Country
|
||||||
|
</div>
|
||||||
|
<Skeleton
|
||||||
|
variant="rounded"
|
||||||
|
height={35}
|
||||||
|
width={220}
|
||||||
|
style={{
|
||||||
|
marginTop: 5,
|
||||||
|
borderRadius: 4
|
||||||
|
}}
|
||||||
|
animation="wave"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: "10px" }}>
|
||||||
|
<Typography variant="text" style={{ color: theme?.palette?.text?.primary }}>
|
||||||
|
Description
|
||||||
|
</Typography>
|
||||||
|
<Skeleton
|
||||||
|
variant="rounded"
|
||||||
|
height={89}
|
||||||
|
width="100%"
|
||||||
|
style={{
|
||||||
|
marginTop: 5,
|
||||||
|
borderRadius: 4
|
||||||
|
}}
|
||||||
|
animation="wave"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style={{ width: "100%", maxWidth: 500, marginRight: 10, marginTop: 10 }}>
|
||||||
|
<Typography variant="text" style={{ color: theme?.palette?.text?.primary }}>
|
||||||
|
Website URL
|
||||||
|
</Typography>
|
||||||
|
<Skeleton
|
||||||
|
variant="rounded"
|
||||||
|
height={35}
|
||||||
|
width="100%"
|
||||||
|
style={{
|
||||||
|
marginTop: 5,
|
||||||
|
borderRadius: 4
|
||||||
|
}}
|
||||||
|
animation="wave"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ width: "100%", maxWidth: 500, marginRight: 10, marginTop: 20 }}>
|
||||||
|
<Typography variant="text" style={{ color: theme?.palette?.text?.primary }}>
|
||||||
|
Article URL
|
||||||
|
</Typography>
|
||||||
|
<Skeleton
|
||||||
|
variant="rounded"
|
||||||
|
height={35}
|
||||||
|
width="100%"
|
||||||
|
style={{
|
||||||
|
marginTop: 5,
|
||||||
|
borderRadius: 4
|
||||||
|
}}
|
||||||
|
animation="wave"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ textAlign: "left", maxWidth: "95%" }}>
|
||||||
|
<Grid container spacing={3} style={{ textAlign: "left", marginTop: 5 }}>
|
||||||
|
<Grid item xs={12} style={{}}>
|
||||||
|
<span>
|
||||||
|
<div style={{}}>
|
||||||
|
<div style={{ flex: "3", color: "white" }}>
|
||||||
|
<div style={{ marginTop: 8, display: "flex" }} />
|
||||||
|
<div style={{ display: "flex" }}>
|
||||||
|
<div
|
||||||
|
style={{ width: "100%", maxWidth: 434, marginRight: 10 }}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="text"
|
||||||
|
style={{ color: theme.palette.text.primary }}
|
||||||
|
>
|
||||||
|
Name
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
required
|
||||||
|
disabled={isDisabled}
|
||||||
|
style={{
|
||||||
|
flex: "1",
|
||||||
|
display: "flex",
|
||||||
|
height: 35,
|
||||||
|
width: "100%",
|
||||||
|
maxWidth: 434,
|
||||||
|
marginTop: "5px",
|
||||||
|
marginRight: "15px",
|
||||||
|
color: theme.palette.textFieldStyle.color,
|
||||||
|
backgroundColor: isEditOrgTab
|
||||||
|
? theme.palette.textFieldStyle.backgroundColor
|
||||||
|
: theme.palette.inputColor,
|
||||||
|
cursor: isDisabled ? "not-allowed" : "pointer",
|
||||||
|
}}
|
||||||
|
fullWidth={true}
|
||||||
|
placeholder="Name"
|
||||||
|
type="name"
|
||||||
|
id="standard-required"
|
||||||
|
margin="normal"
|
||||||
|
variant="outlined"
|
||||||
|
value={partnerData?.name}
|
||||||
|
onBlur={() => {}}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e.target.value.length > 100) {
|
||||||
|
toast("Choose a shorter name.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPartnerData({
|
||||||
|
...partnerData,
|
||||||
|
name: e.target.value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
color="primary"
|
||||||
|
InputProps={{
|
||||||
|
style: {
|
||||||
|
color: theme.palette.textFieldStyle.color,
|
||||||
|
height: "35px",
|
||||||
|
fontSize: "1em",
|
||||||
|
borderRadius: 4,
|
||||||
|
backgroundColor:
|
||||||
|
theme.palette.textFieldStyle.backgroundColor,
|
||||||
|
},
|
||||||
|
classes: {
|
||||||
|
notchedOutline: isEditOrgTab
|
||||||
|
? null
|
||||||
|
: classes.notchedOutline,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/* <div style={{ alignItems: "center" }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginRight: "12px",
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Expertise
|
||||||
|
</div>
|
||||||
|
<FormControl style={{ width: 220, height: 35 }}>
|
||||||
|
<Select
|
||||||
|
style={{
|
||||||
|
minWidth: 220,
|
||||||
|
marginTop: 5,
|
||||||
|
maxWidth: 220,
|
||||||
|
height: 35,
|
||||||
|
borderRadius: 4,
|
||||||
|
color: theme.palette.textFieldStyle.color,
|
||||||
|
cursor: isDisabled ? "not-allowed" : "pointer",
|
||||||
|
}}
|
||||||
|
disabled={isDisabled}
|
||||||
|
id="multiselect-status"
|
||||||
|
multiple
|
||||||
|
value={partnerData?.expertise}
|
||||||
|
onChange={(event) => {
|
||||||
|
handleExpertiseChange(event);
|
||||||
|
}}
|
||||||
|
input={<OutlinedInput />}
|
||||||
|
renderValue={(selected) => selected.join(", ")}
|
||||||
|
MenuProps={MenuProps}
|
||||||
|
>
|
||||||
|
{expertiseOptions?.map((name) => (
|
||||||
|
<MenuItem key={name} value={name}>
|
||||||
|
<Checkbox
|
||||||
|
checked={partnerData?.expertise?.indexOf(name) > -1}
|
||||||
|
/>
|
||||||
|
<ListItemText primary={name} />
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
<div style={{ alignItems: "center", marginLeft: 12 }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginRight: "12px",
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Services
|
||||||
|
</div>
|
||||||
|
<FormControl style={{ width: 220, height: 35 }}>
|
||||||
|
<Select
|
||||||
|
style={{
|
||||||
|
minWidth: 220,
|
||||||
|
marginTop: 5,
|
||||||
|
maxWidth: 220,
|
||||||
|
height: 35,
|
||||||
|
borderRadius: 4,
|
||||||
|
color: theme.palette.textFieldStyle.color,
|
||||||
|
cursor: isDisabled ? "not-allowed" : "pointer",
|
||||||
|
}}
|
||||||
|
disabled={isDisabled}
|
||||||
|
id="multiselect-status"
|
||||||
|
multiple
|
||||||
|
value={partnerData?.services || []}
|
||||||
|
onChange={(event) => {
|
||||||
|
handleServicesChange(event);
|
||||||
|
}}
|
||||||
|
input={<OutlinedInput />}
|
||||||
|
renderValue={(selected) => selected.join(", ")}
|
||||||
|
MenuProps={MenuProps}
|
||||||
|
>
|
||||||
|
{servicesOptions?.map((name) => (
|
||||||
|
<MenuItem key={name} value={name}>
|
||||||
|
<Checkbox
|
||||||
|
checked={(partnerData?.services || []).indexOf(name) > -1}
|
||||||
|
/>
|
||||||
|
<ListItemText primary={name} />
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</div> */}
|
||||||
|
<div style={{ alignItems: "center" }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginRight: "12px",
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Solutions
|
||||||
|
</div>
|
||||||
|
<FormControl style={{ width: 190, height: 35 }}>
|
||||||
|
<Select
|
||||||
|
style={{
|
||||||
|
minWidth: 190,
|
||||||
|
marginTop: 5,
|
||||||
|
maxWidth: 190,
|
||||||
|
height: 35,
|
||||||
|
borderRadius: 4,
|
||||||
|
color: theme.palette.textFieldStyle.color,
|
||||||
|
cursor: isDisabled ? "not-allowed" : "pointer",
|
||||||
|
}}
|
||||||
|
disabled={isDisabled}
|
||||||
|
id="multiselect-status"
|
||||||
|
multiple
|
||||||
|
value={partnerData?.solutions || []}
|
||||||
|
onChange={(event) => {
|
||||||
|
handleSolutionsChange(event);
|
||||||
|
}}
|
||||||
|
input={<OutlinedInput />}
|
||||||
|
renderValue={(selected) => selected.join(", ")}
|
||||||
|
MenuProps={MenuProps}
|
||||||
|
>
|
||||||
|
{solutionsOptions?.map((name) => (
|
||||||
|
<MenuItem key={name} value={name}>
|
||||||
|
<Checkbox
|
||||||
|
checked={(partnerData?.solutions || []).indexOf(name) > -1}
|
||||||
|
/>
|
||||||
|
<ListItemText primary={name} />
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{ marginLeft: 13, fontSize: 16, color: "#9E9E9E" }}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="text"
|
||||||
|
style={{ color: theme.palette.text.primary }}
|
||||||
|
>
|
||||||
|
Region
|
||||||
|
</Typography>
|
||||||
|
<RegionChangeModal
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
selectedOrganization={selectedOrganization}
|
||||||
|
setSelectedRegion={setSelectedRegion}
|
||||||
|
partnerData={partnerData}
|
||||||
|
setPartnerData={setPartnerData}
|
||||||
|
userdata={userdata}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ alignItems: "flex-start", marginLeft: 13, display: "flex", flexDirection: "column" }}>
|
||||||
|
<Typography
|
||||||
|
variant="text"
|
||||||
|
style={{ color: theme.palette.text.primary }}
|
||||||
|
>
|
||||||
|
Country
|
||||||
|
</Typography>
|
||||||
|
<FormControl style={{ width: 210, height: 35 }}>
|
||||||
|
<Autocomplete
|
||||||
|
id="country-select"
|
||||||
|
value={countries.find(country => country.label === partnerData?.country)}
|
||||||
|
style={{
|
||||||
|
minWidth: 210,
|
||||||
|
marginTop: 5,
|
||||||
|
maxWidth: 210,
|
||||||
|
height: 35,
|
||||||
|
borderRadius: 4,
|
||||||
|
color: theme.palette.textFieldStyle.color,
|
||||||
|
cursor: isDisabled ? "not-allowed" : "pointer",
|
||||||
|
}}
|
||||||
|
disabled={isDisabled}
|
||||||
|
options={countries}
|
||||||
|
autoHighlight
|
||||||
|
onClear={() => {
|
||||||
|
setPartnerData({
|
||||||
|
...partnerData,
|
||||||
|
country: "",
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
getOptionLabel={(option) => option?.label || ""}
|
||||||
|
onChange={(event, newValue) => {
|
||||||
|
setPartnerData({
|
||||||
|
...partnerData,
|
||||||
|
country: newValue?.label,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
renderOption={(props, option) => (
|
||||||
|
<Box
|
||||||
|
component="li"
|
||||||
|
sx={{ "& > img": { mr: 2, flexShrink: 0 } }}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
loading="lazy"
|
||||||
|
width="20"
|
||||||
|
src={`https://flagcdn.com/48x36/${option.code.toLowerCase()}.png`}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
{option.label}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
renderInput={(params) => (
|
||||||
|
<TextField
|
||||||
|
{...params}
|
||||||
|
placeholder="Select country"
|
||||||
|
variant="outlined"
|
||||||
|
InputProps={{
|
||||||
|
...params.InputProps,
|
||||||
|
startAdornment: partnerData?.country ? (
|
||||||
|
<img
|
||||||
|
loading="lazy"
|
||||||
|
width="20"
|
||||||
|
src={`https://flagcdn.com/48x36/${countries.find(country => country.label === partnerData.country)?.code.toLowerCase()}.png`}
|
||||||
|
alt=""
|
||||||
|
style={{ marginRight: 8 }}
|
||||||
|
/>
|
||||||
|
) : null,
|
||||||
|
style: {
|
||||||
|
color: theme.palette.textFieldStyle.color,
|
||||||
|
height: "35px",
|
||||||
|
fontSize: "1em",
|
||||||
|
borderRadius: 4,
|
||||||
|
backgroundColor: theme.palette.textFieldStyle.backgroundColor,
|
||||||
|
},
|
||||||
|
classes: {
|
||||||
|
notchedOutline: isEditOrgTab ? null : classes.notchedOutline,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: "10px", }} />
|
||||||
|
<Typography
|
||||||
|
variant="text"
|
||||||
|
style={{ color: theme.palette.text.primary }}
|
||||||
|
>
|
||||||
|
Description
|
||||||
|
</Typography>
|
||||||
|
<div style={{ display: "flex" }}>
|
||||||
|
<TextField
|
||||||
|
required
|
||||||
|
disabled={isDisabled}
|
||||||
|
multiline
|
||||||
|
rows={3}
|
||||||
|
style={{
|
||||||
|
flex: "1",
|
||||||
|
marginTop: "5px",
|
||||||
|
color: theme.palette.textFieldStyle.color,
|
||||||
|
backgroundColor: isEditOrgTab
|
||||||
|
? theme.palette.textFieldStyle.backgroundColor
|
||||||
|
: theme.palette.inputColor,
|
||||||
|
height: 89,
|
||||||
|
borderRadius: 4,
|
||||||
|
cursor: isDisabled ? "not-allowed" : "pointer",
|
||||||
|
}}
|
||||||
|
fullWidth={true}
|
||||||
|
type="name"
|
||||||
|
id="outlined-with-placeholder"
|
||||||
|
margin="normal"
|
||||||
|
variant="outlined"
|
||||||
|
placeholder="A description for the organization"
|
||||||
|
value={partnerData?.description}
|
||||||
|
onBlur={(e) => {
|
||||||
|
setPartnerData({
|
||||||
|
...partnerData,
|
||||||
|
description: e.target.value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onChange={(e) => {
|
||||||
|
setPartnerData({
|
||||||
|
...partnerData,
|
||||||
|
description: e.target.value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
InputProps={{
|
||||||
|
classes: {
|
||||||
|
notchedOutline: isEditOrgTab
|
||||||
|
? null
|
||||||
|
: classes.notchedOutline,
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
color: theme.palette.textFieldStyle.color,
|
||||||
|
height: 89,
|
||||||
|
borderRadius: 4,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
style={{ width: "100%", maxWidth: 500, marginRight: 10, marginTop: 10 }}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="text"
|
||||||
|
style={{ color: theme.palette.text.primary }}
|
||||||
|
>
|
||||||
|
Website URL
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
required
|
||||||
|
disabled={isDisabled}
|
||||||
|
style={{
|
||||||
|
flex: "1",
|
||||||
|
display: "flex",
|
||||||
|
height: 35,
|
||||||
|
width: "100%",
|
||||||
|
maxWidth: 500,
|
||||||
|
marginTop: "5px",
|
||||||
|
marginRight: "15px",
|
||||||
|
color: theme.palette.textFieldStyle.color,
|
||||||
|
backgroundColor: isEditOrgTab
|
||||||
|
? theme.palette.textFieldStyle.backgroundColor
|
||||||
|
: theme.palette.inputColor,
|
||||||
|
cursor: isDisabled ? "not-allowed" : "pointer",
|
||||||
|
}}
|
||||||
|
fullWidth={true}
|
||||||
|
placeholder="https://www.example.com"
|
||||||
|
type="name"
|
||||||
|
id="standard-required"
|
||||||
|
margin="normal"
|
||||||
|
variant="outlined"
|
||||||
|
value={partnerData?.website_url}
|
||||||
|
onBlur={() => {}}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e.target.value.length > 100) {
|
||||||
|
toast("Choose a shorter website URL.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPartnerData({
|
||||||
|
...partnerData,
|
||||||
|
website_url: e.target.value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
color="primary"
|
||||||
|
InputProps={{
|
||||||
|
style: {
|
||||||
|
color: theme.palette.textFieldStyle.color,
|
||||||
|
height: "35px",
|
||||||
|
fontSize: "1em",
|
||||||
|
borderRadius: 4,
|
||||||
|
backgroundColor:
|
||||||
|
theme.palette.textFieldStyle.backgroundColor,
|
||||||
|
},
|
||||||
|
classes: {
|
||||||
|
notchedOutline: isEditOrgTab
|
||||||
|
? null
|
||||||
|
: classes.notchedOutline,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{ width: "100%", maxWidth: 500, marginRight: 10, marginTop: 20 }}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="text"
|
||||||
|
style={{ color: theme.palette.text.primary }}
|
||||||
|
>
|
||||||
|
Article URL
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
required
|
||||||
|
disabled={isDisabled}
|
||||||
|
style={{
|
||||||
|
flex: "1",
|
||||||
|
display: "flex",
|
||||||
|
height: 35,
|
||||||
|
width: "100%",
|
||||||
|
maxWidth: 500,
|
||||||
|
marginTop: "5px",
|
||||||
|
marginRight: "15px",
|
||||||
|
color: theme.palette.textFieldStyle.color,
|
||||||
|
backgroundColor: isEditOrgTab
|
||||||
|
? theme.palette.textFieldStyle.backgroundColor
|
||||||
|
: theme.palette.inputColor,
|
||||||
|
cursor: isDisabled ? "not-allowed" : "pointer",
|
||||||
|
}}
|
||||||
|
fullWidth={true}
|
||||||
|
placeholder="https://www.example.com"
|
||||||
|
type="name"
|
||||||
|
id="standard-required"
|
||||||
|
margin="normal"
|
||||||
|
variant="outlined"
|
||||||
|
value={partnerData?.article_url}
|
||||||
|
onBlur={() => {}}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e.target.value.length > 100) {
|
||||||
|
toast("Choose a shorter article URL.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPartnerData({
|
||||||
|
...partnerData,
|
||||||
|
article_url: e.target.value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
color="primary"
|
||||||
|
InputProps={{
|
||||||
|
style: {
|
||||||
|
color: theme.palette.textFieldStyle.color,
|
||||||
|
height: "35px",
|
||||||
|
fontSize: "1em",
|
||||||
|
borderRadius: 4,
|
||||||
|
backgroundColor:
|
||||||
|
theme.palette.textFieldStyle.backgroundColor,
|
||||||
|
},
|
||||||
|
classes: {
|
||||||
|
notchedOutline: isEditOrgTab
|
||||||
|
? null
|
||||||
|
: classes.notchedOutline,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PartnerDetails;
|
||||||
|
|
||||||
|
const RegionChangeModal = memo(
|
||||||
|
({
|
||||||
|
isDisabled,
|
||||||
|
selectedOrganization,
|
||||||
|
setSelectedRegion,
|
||||||
|
userdata,
|
||||||
|
partnerData,
|
||||||
|
setPartnerData,
|
||||||
|
}) => {
|
||||||
|
// Show from options: "us-west2", "europe-west2", "europe-west3", "northamerica-northeast1"
|
||||||
|
// var regions = ["us-west2", "europe-west2", "europe-west3", "northamerica-northeast1"]
|
||||||
|
|
||||||
|
const regionMapping = {
|
||||||
|
"Africa": "af",
|
||||||
|
"Asia Pacific (APAC)": "",
|
||||||
|
"Europe": "eu",
|
||||||
|
"Latin America": "la",
|
||||||
|
"Middle East": "me",
|
||||||
|
"North America": "na",
|
||||||
|
"Global": ""
|
||||||
|
};
|
||||||
|
|
||||||
|
//let regiontag = "UK";
|
||||||
|
let regiontag = partnerData?.region || "UK";
|
||||||
|
let regionCode = "gb";
|
||||||
|
|
||||||
|
const regionsplit = selectedOrganization?.region_url?.split(".");
|
||||||
|
|
||||||
|
if (regionsplit?.length > 2 && !regionsplit[0]?.includes("shuffler")) {
|
||||||
|
const namesplit = regionsplit[0]?.split("/");
|
||||||
|
regiontag = namesplit[namesplit.length - 1];
|
||||||
|
|
||||||
|
if (regiontag === "california") {
|
||||||
|
regiontag = "US";
|
||||||
|
regionCode = "us";
|
||||||
|
} else if (regiontag === "frankfurt") {
|
||||||
|
regiontag = "EU-2";
|
||||||
|
regionCode = "eu";
|
||||||
|
} else if (regiontag === "ca") {
|
||||||
|
regiontag = "CA";
|
||||||
|
regionCode = "ca";
|
||||||
|
} else if (regiontag === "austrailia") {
|
||||||
|
regiontag = "AUS";
|
||||||
|
regionCode = "au";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormControl
|
||||||
|
disabled={isDisabled}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
marginTop: 5,
|
||||||
|
alignItems: "center",
|
||||||
|
cursor: isDisabled ? "not-allowed" : "pointer",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* <InputLabel id="demo-simple-select-label">Region</InputLabel> */}
|
||||||
|
<Select
|
||||||
|
labelId="demo-simple-select-label"
|
||||||
|
id="demo-simple-select"
|
||||||
|
value={partnerData?.region || "Europe"}
|
||||||
|
style={{ minWidth: 190, height: 35, borderRadius: 4 }}
|
||||||
|
onChange={(e) => {
|
||||||
|
// if (userdata?.support) {
|
||||||
|
// setSelectedRegion(e.target.value)
|
||||||
|
// } else {
|
||||||
|
// handleSendChangeRegionMail(e.target.value)
|
||||||
|
// }
|
||||||
|
setPartnerData({
|
||||||
|
...partnerData,
|
||||||
|
region: e.target.value,
|
||||||
|
})
|
||||||
|
|
||||||
|
// setSelectedRegion(e.target.value)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Object.keys(regionMapping).map((region, index) => {
|
||||||
|
const regionImageCode = regionMapping[region];
|
||||||
|
// Set the default region if selectedOrganization.region is not set
|
||||||
|
if (selectedOrganization.region === undefined) {
|
||||||
|
selectedOrganization.region = "europe-west2";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (region === "AUS") {
|
||||||
|
region = "AUS (test)";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the current region matches the selected region
|
||||||
|
if (region === selectedOrganization.region) {
|
||||||
|
// If the region matches, set the MenuItem as selected
|
||||||
|
return (
|
||||||
|
<MenuItem value={region} key={index} disabled>
|
||||||
|
{/* show region image through cdn */}
|
||||||
|
{/* <img
|
||||||
|
src={`https://flagcdn.com/48x36/${regionImageCode}.png`}
|
||||||
|
alt={region}
|
||||||
|
style={{ marginRight: 10 }}
|
||||||
|
/> */}
|
||||||
|
{region}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<MenuItem sx={{ display: "flex" }} key={index} value={region}>
|
||||||
|
{/* <img
|
||||||
|
src={`https://flagcdn.com/48x36/${regionImageCode}.png`}
|
||||||
|
alt={region}
|
||||||
|
style={{ marginRight: 10, width: 20, height: 18 }}
|
||||||
|
/> */}
|
||||||
|
{region}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,391 @@
|
|||||||
|
import React, { useEffect, useState, useContext } from 'react';
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { Context } from '../context/ContextApi.jsx';
|
||||||
|
import { getTheme } from '../theme.jsx';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Typography,
|
||||||
|
Box,
|
||||||
|
IconButton,
|
||||||
|
Tooltip,
|
||||||
|
} from "@mui/material";
|
||||||
|
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
|
||||||
|
import { useNavigate } from 'react-router';
|
||||||
|
import PartnerHeader from '../components/PartnerHeader.jsx';
|
||||||
|
import PartnerDetails from '../components/PartnerDetails.jsx';
|
||||||
|
|
||||||
|
const PartnerSettings = (props) => {
|
||||||
|
const {
|
||||||
|
isCloud,
|
||||||
|
userdata,
|
||||||
|
globalUrl,
|
||||||
|
serverside,
|
||||||
|
loadingPartnerData,
|
||||||
|
selectedOrganization,
|
||||||
|
setSelectedOrganization,
|
||||||
|
handleGetOrg,
|
||||||
|
partnerData,
|
||||||
|
setPartnerData,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const [isPartner, setIsPartner] = React.useState(isCloud ? userdata?.active_org?.is_partner ? true : false : false);
|
||||||
|
const [partnerTypes, setPartnerTypes] = React.useState({});
|
||||||
|
const [isPublishing, setIsPublishing] = React.useState(false);
|
||||||
|
const [isToggling, setIsToggling] = React.useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsPartner(isCloud ? userdata?.active_org?.is_partner ? true : false : false);
|
||||||
|
if(userdata?.support){
|
||||||
|
setIsPartner(true);
|
||||||
|
}
|
||||||
|
}, [userdata, isCloud]);
|
||||||
|
// Partner Types handling : Getting from org status
|
||||||
|
useEffect(() => {
|
||||||
|
const partnerTypes = {};
|
||||||
|
userdata?.org_status.forEach(status => {
|
||||||
|
if (status.includes("_partner")) {
|
||||||
|
partnerTypes[status] = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setPartnerTypes(partnerTypes);
|
||||||
|
}, [selectedOrganization]);
|
||||||
|
// Partner Type Colors
|
||||||
|
const partnerTypeColors = {
|
||||||
|
"tech_partner": "#ff8544",
|
||||||
|
"distribution_partner": "#2BC07E",
|
||||||
|
"service_partner": "#a99cf9",
|
||||||
|
"integration_partner": "#fb47a0"
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSendUpdateRequest = () => {
|
||||||
|
toast("Your request has been sent to the support team. They will review your request and get back to you as soon as possible.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open partner page in new tab
|
||||||
|
const handleOpenPartnerPage = () => {
|
||||||
|
if (partnerData?.id) {
|
||||||
|
if(partnerData?.public){
|
||||||
|
window.open(`${window.location.origin}/partners/${partnerData.name.toLowerCase().replaceAll(" ", "_")}`)
|
||||||
|
}else{
|
||||||
|
window.open(`${window.location.origin}/partners/${partnerData?.id}`, "_blank");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toast.error("Partner ID not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update Partner Details
|
||||||
|
const handleUpdatePartnerDetails = () => {
|
||||||
|
// Validate required fields before publishing
|
||||||
|
const requiredStringFields = [
|
||||||
|
{ field: partnerData?.name, name: "Partner Name" },
|
||||||
|
{ field: partnerData?.description, name: "Description" },
|
||||||
|
{ field: selectedOrganization?.id, name: "Organization Id" },
|
||||||
|
{ field: partnerData?.image_url, name: "Logo Image" },
|
||||||
|
{ field: partnerData?.landscape_image_url, name: "Landscape Image" },
|
||||||
|
{ field: partnerData?.website_url, name: "Website URL" },
|
||||||
|
{ field: partnerData?.article_url, name: "Article URL" },
|
||||||
|
{ field: partnerData?.country, name: "Country" },
|
||||||
|
{ field: partnerData?.region, name: "Region" },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Required array fields (multi-select dropdowns)
|
||||||
|
const requiredArrayFields = [
|
||||||
|
{ field: partnerData?.solutions, name: "Solutions" },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Check if any required string fields are empty
|
||||||
|
const emptyStringFields = requiredStringFields.filter(item =>
|
||||||
|
!item.field || item.field.trim() === ""
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if any required array fields are empty
|
||||||
|
const emptyArrayFields = requiredArrayFields.filter(item =>
|
||||||
|
!item.field || !Array.isArray(item.field) || item.field.length === 0
|
||||||
|
);
|
||||||
|
|
||||||
|
// Combine all empty fields
|
||||||
|
const allEmptyFields = [...emptyStringFields, ...emptyArrayFields];
|
||||||
|
|
||||||
|
// If there are empty required fields, show error and return
|
||||||
|
if (allEmptyFields.length > 0) {
|
||||||
|
const missingFields = allEmptyFields.map(item => item.name).join(", ");
|
||||||
|
toast.error(`Please fill in all required fields: ${missingFields}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if at least one partner type is selected
|
||||||
|
if (Object.keys(partnerTypes).length === 0) {
|
||||||
|
toast.error("There should be at least one partner type");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsPublishing(true);
|
||||||
|
const url = globalUrl + "/api/v1/partners/" + userdata?.active_org?.id;
|
||||||
|
const data = {
|
||||||
|
id: partnerData.id?.trim() || null,
|
||||||
|
name: partnerData.name?.trim(),
|
||||||
|
org_id: selectedOrganization?.id?.trim(),
|
||||||
|
description: partnerData.description?.trim(),
|
||||||
|
website_url: partnerData.website_url?.trim(),
|
||||||
|
article_url: partnerData.article_url?.trim(),
|
||||||
|
partner_type: partnerTypes,
|
||||||
|
expertise: partnerData?.expertise || [],
|
||||||
|
services: partnerData?.services || [],
|
||||||
|
solutions: partnerData?.solutions || [],
|
||||||
|
country: partnerData?.country || "",
|
||||||
|
region: partnerData?.region || "",
|
||||||
|
image_url: partnerData?.image_url?.trim(),
|
||||||
|
landscape_image_url: partnerData?.landscape_image_url?.trim(),
|
||||||
|
public: partnerData?.public
|
||||||
|
}
|
||||||
|
fetch(url, {
|
||||||
|
mode: "cors",
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json; charset=utf-8",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
setIsPublishing(false);
|
||||||
|
if (response.status !== 200) {
|
||||||
|
toast.error("Failed to publish partner");
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
toast.success("Partner details successfully updated");
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setIsPublishing(false);
|
||||||
|
toast.error("Failed to update partner details: " + error?.message);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle Partner Publish Status
|
||||||
|
const handleTogglePublishStatus = () => {
|
||||||
|
setIsToggling(true);
|
||||||
|
const newPublishStatus = !partnerData?.public;
|
||||||
|
const url = globalUrl + "/api/v1/partners/" + userdata?.active_org?.id;
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
id: partnerData.id?.trim() || null,
|
||||||
|
name: partnerData.name?.trim(),
|
||||||
|
org_id: selectedOrganization?.id?.trim(),
|
||||||
|
description: partnerData.description?.trim(),
|
||||||
|
website_url: partnerData.website_url?.trim(),
|
||||||
|
article_url: partnerData.article_url?.trim(),
|
||||||
|
partner_type: partnerTypes,
|
||||||
|
usecases: partnerData?.usecases || [],
|
||||||
|
expertise: partnerData?.expertise || [],
|
||||||
|
services: partnerData?.services || [],
|
||||||
|
solutions: partnerData?.solutions || [],
|
||||||
|
country: partnerData?.country || "",
|
||||||
|
region: partnerData?.region || "",
|
||||||
|
image_url: partnerData?.image_url?.trim(),
|
||||||
|
landscape_image_url: partnerData?.landscape_image_url?.trim(),
|
||||||
|
public: newPublishStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
mode: "cors",
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json; charset=utf-8",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
setIsToggling(false);
|
||||||
|
if (response.status !== 200) {
|
||||||
|
toast.error("Failed to update publish status");
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
// Update local state
|
||||||
|
setPartnerData(prev => ({
|
||||||
|
...prev,
|
||||||
|
public: newPublishStatus
|
||||||
|
}));
|
||||||
|
toast.success(`Partner ${newPublishStatus ? 'published' : 'unpublished'} successfully`);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setIsToggling(false);
|
||||||
|
toast.error("Failed to update publish status: " + error?.message);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const { themeMode } = useContext(Context);
|
||||||
|
const theme = getTheme(themeMode);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ width: "100%", boxSizing: 'border-box', padding: "27px 10px 19px 27px", height:"100%", backgroundColor: theme.palette.platformColor, borderRadius: '16px', }}>
|
||||||
|
<div style={{ height: "100%", width: "100%", overflowX: 'hidden', scrollbarColor: theme.palette.scrollbarColorTransparent, scrollbarWidth: 'thin'}} >
|
||||||
|
<div style={{ marginBottom: 40 }}>
|
||||||
|
<div style={{display:"flex", alignItems:"center", justifyContent:"space-between"}}>
|
||||||
|
<div style={{width:'100%'}}>
|
||||||
|
<Box
|
||||||
|
sx={{display:"flex", alignItems:"flex-start", gap:2, justifyContent:"flex-start"
|
||||||
|
}}>
|
||||||
|
<Typography variant='h3' sx={{ marginBottom: "15px", marginTop: 0, }}>Configuration</Typography>
|
||||||
|
{Object?.entries(partnerTypes)?.map(([key, value]) => (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
borderRadius: "999px",
|
||||||
|
py: 1.2,
|
||||||
|
px: 2.5,
|
||||||
|
fontSize: "13px",
|
||||||
|
fontWeight: 500,
|
||||||
|
fontFamily: theme.typography.fontFamily,
|
||||||
|
color: "#fff",
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
border: `1.5px solid ${
|
||||||
|
partnerTypeColors[key]
|
||||||
|
}`,
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
textAlign: "center",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
color: partnerTypeColors[key],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{key.replace("_", " ").replace(/\b\w/g, char => char.toUpperCase())}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
width: "100%",
|
||||||
|
marginTop: 0.5,
|
||||||
|
}}>
|
||||||
|
<Typography variant="body2" style={{ color: theme.palette.text.secondary, fontSize: 16, maxWidth: "60%" }}>
|
||||||
|
Set up and manage partner details and information to be displayed on the Partners page.
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ display: "flex", gap: 2, alignItems: "center" }}>
|
||||||
|
{isPartner && (
|
||||||
|
<>
|
||||||
|
{/* View Partner Page Button */}
|
||||||
|
<Tooltip title="View Partner Page">
|
||||||
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
backgroundColor: theme.palette.action.hover,
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: theme.palette.action.selected,
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onClick={handleOpenPartnerPage}
|
||||||
|
disabled={!partnerData?.id}
|
||||||
|
>
|
||||||
|
<OpenInNewIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
{/* Update Details Button */}
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
fontSize: 16,
|
||||||
|
textTransform: 'capitalize',
|
||||||
|
boxShadow: "none",
|
||||||
|
px: 4,
|
||||||
|
}}
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
disabled={isPublishing || isToggling}
|
||||||
|
onClick={handleUpdatePartnerDetails}
|
||||||
|
>
|
||||||
|
{isPublishing ? "Updating..." : "Update Details"}
|
||||||
|
</Button>
|
||||||
|
{/* Toggle Publish/Unpublish Button */}
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
fontSize: 16,
|
||||||
|
textTransform: 'capitalize',
|
||||||
|
boxShadow: "none",
|
||||||
|
marginRight: 4,
|
||||||
|
px: 3,
|
||||||
|
backgroundColor: partnerData?.public ? "#FD4C62" : "#4caf50",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: partnerData?.public ? "#FD4C62" : "#4caf50"
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
variant="contained"
|
||||||
|
disabled={isToggling || isPublishing}
|
||||||
|
onClick={handleTogglePublishStatus}
|
||||||
|
>
|
||||||
|
{isToggling ? (partnerData?.public ? "Unpublishing..." : "Publishing...") : (partnerData?.public ? "Unpublish" : "Publish")}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isPartner && (
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
fontSize: 16,
|
||||||
|
textTransform: 'capitalize',
|
||||||
|
boxShadow: "none",
|
||||||
|
px: 5,
|
||||||
|
textDecoration: "none",
|
||||||
|
marginRight: 4
|
||||||
|
}}
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
onClick={() => {
|
||||||
|
if(!isCloud){
|
||||||
|
navigate("/become-partner")
|
||||||
|
}else{
|
||||||
|
window.open("https://shuffler.io/become-partner")
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Become a partner
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<PartnerHeader
|
||||||
|
isCloud={isCloud}
|
||||||
|
userdata={userdata}
|
||||||
|
isPublishing={isPublishing}
|
||||||
|
setSelectedOrganization={setSelectedOrganization}
|
||||||
|
globalUrl={globalUrl}
|
||||||
|
selectedOrganization={selectedOrganization}
|
||||||
|
isEditOrgTab={true}
|
||||||
|
loadingPartnerData={loadingPartnerData}
|
||||||
|
partnerData={partnerData}
|
||||||
|
setPartnerData={setPartnerData}
|
||||||
|
handleGetOrg={handleGetOrg}
|
||||||
|
/>
|
||||||
|
<PartnerDetails
|
||||||
|
isCloud={isCloud}
|
||||||
|
userdata={userdata}
|
||||||
|
partnerData={partnerData}
|
||||||
|
isPublishing={isPublishing}
|
||||||
|
loadingPartnerData={loadingPartnerData}
|
||||||
|
setPartnerData={setPartnerData}
|
||||||
|
setSelectedOrganization={setSelectedOrganization}
|
||||||
|
globalUrl={globalUrl}
|
||||||
|
selectedOrganization={selectedOrganization}
|
||||||
|
isEditOrgTab={true}
|
||||||
|
handleGetOrg={handleGetOrg}
|
||||||
|
serverside={serverside}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div >
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PartnerSettings;
|
||||||
@@ -0,0 +1,245 @@
|
|||||||
|
import React, { useEffect, useState, useCallback, useContext } from 'react';
|
||||||
|
import { useNavigate, useLocation } from "react-router-dom";
|
||||||
|
import Branding from "../components/Branding.jsx";
|
||||||
|
import { ToastContainer, toast } from "react-toastify";
|
||||||
|
import { Button } from '@mui/material';
|
||||||
|
import { getTheme } from '../theme.jsx';
|
||||||
|
import { Context } from '../context/ContextApi.jsx';
|
||||||
|
import PartnerSettings from '../components/PartnerSettings.jsx';
|
||||||
|
import PartnersUsecasesTab from '../components/PartnersUsecasesTab.jsx';
|
||||||
|
import PartnersApps from '../components/PartnerApps.jsx';
|
||||||
|
import PartnerArticles from '../components/PartnersArticles.jsx';
|
||||||
|
const PartnerTab = (props) => {
|
||||||
|
const location = useLocation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const {
|
||||||
|
userdata,
|
||||||
|
globalUrl,
|
||||||
|
serverside,
|
||||||
|
isCloud,
|
||||||
|
setSelectedOrganization,
|
||||||
|
selectedOrganization, handleGetOrg,
|
||||||
|
handleStatusChange,
|
||||||
|
isLoaded,
|
||||||
|
removeCookie
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
|
||||||
|
const [selectedTab, setSelectedTab] = useState('partner_settings');
|
||||||
|
const [loadingPartnerData, setLoadingPartnerData] = useState(false);
|
||||||
|
const [curIndex, setCurIndex] = React.useState(0);
|
||||||
|
const [partnerData, setPartnerData] = React.useState({
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
image_url: "",
|
||||||
|
landscape_image_url: "",
|
||||||
|
website_url: "",
|
||||||
|
article_url: "",
|
||||||
|
expertise: [],
|
||||||
|
usecases: [],
|
||||||
|
services: [],
|
||||||
|
solutions: [],
|
||||||
|
country: "",
|
||||||
|
region: "",
|
||||||
|
partner_type: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const tabsOnPartnerTab = [ 'Partner Settings', 'Usecases', 'Apps', 'AI Agents', 'Articles', 'Branding'];
|
||||||
|
|
||||||
|
const { themeMode } = useContext(Context);
|
||||||
|
const theme = getTheme(themeMode);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(!isCloud || !userdata?.active_org?.is_partner) {
|
||||||
|
// If the user is not a partner or if it's not a cloud environment do not make api call :)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoadingPartnerData(true);
|
||||||
|
const url = globalUrl + "/api/v1/partners/" + userdata?.active_org?.id;
|
||||||
|
fetch(url, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
toast("Failed to get partner data")
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
if(responseJson.success) {
|
||||||
|
setPartnerData(responseJson?.partner);
|
||||||
|
console.log("responseJson", responseJson)
|
||||||
|
setLoadingPartnerData(false);
|
||||||
|
}else{
|
||||||
|
setLoadingPartnerData(false);
|
||||||
|
toast(responseJson?.reason)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setLoadingPartnerData(false);
|
||||||
|
})
|
||||||
|
}, [globalUrl]);
|
||||||
|
|
||||||
|
// Used to auto select the tab based on the url : partner_tab
|
||||||
|
useEffect(() => {
|
||||||
|
const queryParams = new URLSearchParams(location.search);
|
||||||
|
const tabName = queryParams.get('partner_tab');
|
||||||
|
if (tabName) {
|
||||||
|
const decodedTabName = decodeURIComponent(tabName);
|
||||||
|
setSelectedTab(decodedTabName);
|
||||||
|
if (decodedTabName === 'partner_settings') {
|
||||||
|
setCurIndex(0);
|
||||||
|
} else if(decodedTabName === 'usecases'){
|
||||||
|
setCurIndex(1)
|
||||||
|
}else if (decodedTabName === 'apps'){
|
||||||
|
setCurIndex(2);
|
||||||
|
} else if (decodedTabName === 'aiagents') {
|
||||||
|
setCurIndex(3);
|
||||||
|
} else if (decodedTabName === 'articles') {
|
||||||
|
setCurIndex(4);
|
||||||
|
} else if (decodedTabName === 'branding') {
|
||||||
|
setCurIndex(5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [location.search]);
|
||||||
|
|
||||||
|
// Tab click on partner tab
|
||||||
|
const handleTabClick = (tabName) => {
|
||||||
|
const formattedTabName = tabName.toLowerCase().replace(/[\s&]+/g, '');
|
||||||
|
const encodedTabName = encodeURIComponent(formattedTabName);
|
||||||
|
setSelectedTab(formattedTabName);
|
||||||
|
document.title = `Shuffle - partner - ${formattedTabName}`;
|
||||||
|
navigate(`?partner_tab=${encodedTabName}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Rendering the content based on the selected tab
|
||||||
|
const renderContent = () => {
|
||||||
|
switch (selectedTab) {
|
||||||
|
case 'partner_settings':
|
||||||
|
return <PartnerSettings isCloud={isCloud} loadingPartnerData={loadingPartnerData} partnerData={partnerData} setPartnerData={setPartnerData} handleStatusChange={handleStatusChange} userdata={userdata} globalUrl={globalUrl} serverside={serverside} handleGetOrg={handleGetOrg} setSelectedOrganization={setSelectedOrganization} selectedOrganization={selectedOrganization} selectedTab={selectedTab} />;
|
||||||
|
case 'usecases':
|
||||||
|
return <PartnersUsecasesTab isCloud={isCloud} partnerData={partnerData} setPartnerData={setPartnerData} handleStatusChange={handleStatusChange} userdata={userdata} globalUrl={globalUrl} serverside={serverside} handleGetOrg={handleGetOrg} setSelectedOrganization={setSelectedOrganization} selectedOrganization={selectedOrganization} selectedTab={selectedTab} />;
|
||||||
|
case `apps`:
|
||||||
|
return <PartnersApps isCloud={isCloud} partnerData={partnerData} setPartnerData={setPartnerData} handleStatusChange={handleStatusChange} userdata={userdata} globalUrl={globalUrl} serverside={serverside} handleGetOrg={handleGetOrg} setSelectedOrganization={setSelectedOrganization} selectedOrganization={selectedOrganization} selectedTab={selectedTab} />;
|
||||||
|
case 'articles' :
|
||||||
|
return <PartnerArticles isCloud={isCloud} partnerData={partnerData} setPartnerData={setPartnerData} handleStatusChange={handleStatusChange} userdata={userdata} globalUrl={globalUrl} serverside={serverside} handleGetOrg={handleGetOrg} setSelectedOrganization={setSelectedOrganization} selectedOrganization={selectedOrganization} selectedTab={selectedTab} />;
|
||||||
|
case `ai_agents`:
|
||||||
|
return <PartnerArticles isCloud={isCloud} partnerData={partnerData} setPartnerData={setPartnerData} handleStatusChange={handleStatusChange} userdata={userdata} globalUrl={globalUrl} serverside={serverside} handleGetOrg={handleGetOrg} setSelectedOrganization={setSelectedOrganization} selectedOrganization={selectedOrganization} selectedTab={selectedTab} />;
|
||||||
|
case 'branding':
|
||||||
|
return <Branding
|
||||||
|
isCloud={isCloud}
|
||||||
|
userdata={userdata}
|
||||||
|
globalUrl={globalUrl}
|
||||||
|
handleGetOrg={handleGetOrg}
|
||||||
|
selectedOrganization={selectedOrganization}
|
||||||
|
clickedFromOrgTab={true}
|
||||||
|
setSelectedOrganization={setSelectedOrganization}
|
||||||
|
/>;
|
||||||
|
default:
|
||||||
|
return <PartnerSettings isCloud={isCloud} partnerData={partnerData} setPartnerData={setPartnerData} handleStatusChange={handleStatusChange} userdata={userdata} globalUrl={globalUrl} serverside={serverside} handleGetOrg={handleGetOrg} setSelectedOrganization={setSelectedOrganization} selectedOrganization={selectedOrganization} />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isTabDisabled = (tabName) => {
|
||||||
|
// If user is a support user, enable all tabs
|
||||||
|
if (userdata?.support) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For non-support users, apply the following restrictions:
|
||||||
|
|
||||||
|
// For onPrem only partner settings and branding are enabled
|
||||||
|
if (
|
||||||
|
!isCloud &&
|
||||||
|
(tabName === "Usecases" ||
|
||||||
|
tabName === "Apps" ||
|
||||||
|
tabName === "AI Agents" ||
|
||||||
|
tabName === "Articles")
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable Apps, Articles, and AI Agents for all non-support users
|
||||||
|
if (
|
||||||
|
tabName === "Apps" ||
|
||||||
|
tabName === "Articles" ||
|
||||||
|
tabName === "AI Agents"
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable Usecases tab for cloud users if not a partner or is in a sub-org
|
||||||
|
if (isCloud && tabName === "Usecases") {
|
||||||
|
if (
|
||||||
|
!userdata?.active_org?.is_partner ||
|
||||||
|
userdata?.active_org?.is_sub_org
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable Branding tab if not an integration partner
|
||||||
|
const isIntegrationPartner =
|
||||||
|
userdata &&
|
||||||
|
userdata?.org_status?.includes("integration_partner") &&
|
||||||
|
!userdata?.org_status?.includes("sub_org");
|
||||||
|
if (tabName === "Branding" && !isIntegrationPartner) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable the tab by default
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ height: "100%", width: "100%", color: theme.palette.platformColor, backgroundColor: theme.palette.platformColor, borderTopRightRadius: '8px', borderBottomRightRadius: 8, borderLeft: theme.palette.defaultBorder, boxSizing: 'border-box' }}>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-around', width: "100%", borderBottom: theme.palette.defaultBorder ,boxSizing: 'border-box' }}>
|
||||||
|
{tabsOnPartnerTab?.map((tabName, index) => (
|
||||||
|
<div style={{ pointerEvents: 'auto', width: '100%',}}>
|
||||||
|
<Button
|
||||||
|
key={tabName}
|
||||||
|
onClick={() => {
|
||||||
|
setCurIndex(index);
|
||||||
|
handleTabClick(index === 0 ? "partner_settings" : tabName.toLowerCase().replace(/[\s&]+/g, ''));
|
||||||
|
}}
|
||||||
|
disabled={isTabDisabled(tabName)}
|
||||||
|
variant="text"
|
||||||
|
sx={{
|
||||||
|
"&.MuiButton-root": {
|
||||||
|
padding: '28px 0',
|
||||||
|
borderBottom: index === curIndex ? '2px solid #FF8444' : 'none',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontWeight: index === curIndex ? 'bold' : 'normal',
|
||||||
|
color: index === curIndex ? "#FF8444" : theme.palette.text.primary,
|
||||||
|
textTransform: 'none',
|
||||||
|
fontSize: 16,
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
borderRadius: 0,
|
||||||
|
},
|
||||||
|
"&: hover": {
|
||||||
|
backgroundColor: theme.palette.hoverColor
|
||||||
|
},
|
||||||
|
"&.Mui-disabled": {
|
||||||
|
color: "#6F6F6F",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tabName}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'center', width: "100%", height: "100%", paddingBottom: 200, boxSizing:'border-box'}}>
|
||||||
|
{renderContent()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PartnerTab;
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { Box, Typography } from '@mui/material'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const PartnersArticles = () => {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
minHeight: "500px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography>Partner Articles</Typography>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PartnersArticles
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -89,49 +89,18 @@ const Priorities = memo((props) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
prepareNotificationAppList()
|
prepareNotificationAppList()
|
||||||
|
|
||||||
|
if (notifications === undefined || notifications === null || notifications.length === 0) {
|
||||||
|
getNotifications()
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
const getAppIDs = async (appList) => {
|
|
||||||
fetch(globalUrl + "/api/v1/apps", {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Accept: "application/json",
|
|
||||||
},
|
|
||||||
credentials: "include",
|
|
||||||
}).then((response) => {
|
|
||||||
if (response.status !== 200) {
|
|
||||||
toast("Failed getting app ids: ", response.reason);
|
|
||||||
console.log("Status not 200 for app ids :O!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
}).then((responseJson) => {
|
|
||||||
if (responseJson !== undefined) {
|
|
||||||
// console.log("App ids: ", responseJson)
|
|
||||||
// console.log("App list: ", appList)
|
|
||||||
const filteredApps = responseJson.filter(app => appList.includes(app.name));
|
|
||||||
const appDetails = filteredApps.map(app => ({ name: app.name, id: app.id }));
|
|
||||||
return appDetails
|
|
||||||
// console.log("App IDs: ", appDetails)
|
|
||||||
}
|
|
||||||
}).catch((error) => {
|
|
||||||
console.log("Error getting app ids: " + error);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// getting comms & cases app from app framework
|
// getting comms & cases app from app framework
|
||||||
var notificationAppList = [];
|
var notificationAppList = [];
|
||||||
// if (selectedOrganization.security_framework?.cases && selectedOrganization.security_framework?.cases.name.length > 0) {
|
|
||||||
// notificationAppList = notificationAppList.concat(selectedOrganization.security_framework?.cases);
|
|
||||||
// }
|
|
||||||
// if (selectedOrganization.security_framework?.communication && selectedOrganization.security_framework?.communication.name.length > 0) {
|
|
||||||
// notificationAppList = notificationAppList.concat(selectedOrganization.security_framework?.communication);
|
|
||||||
// }
|
|
||||||
|
|
||||||
const mergeAuthData = (result, responseJson) => {
|
const mergeAuthData = (result, responseJson) => {
|
||||||
// result is only getting: Jira AND discord.
|
// result is only getting: Jira AND discord.
|
||||||
|
|
||||||
@@ -146,7 +115,7 @@ const Priorities = memo((props) => {
|
|||||||
return updatedResult;
|
return updatedResult;
|
||||||
};
|
};
|
||||||
|
|
||||||
const prepareNotificationAppList = async () => {
|
const prepareNotificationAppList = () => {
|
||||||
// getting App ID,Authentication fields and saved auths for each app
|
// getting App ID,Authentication fields and saved auths for each app
|
||||||
var result = []
|
var result = []
|
||||||
fetch(globalUrl + "/api/v1/apps", {
|
fetch(globalUrl + "/api/v1/apps", {
|
||||||
@@ -187,18 +156,17 @@ const Priorities = memo((props) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return response.json();
|
return response.json();
|
||||||
}).then(async (responseJson) => {
|
}).then((responseJson) => {
|
||||||
if (!responseJson.success) {
|
if (!responseJson.success) {
|
||||||
console.log("Could not get app auth")
|
console.log("Could not get app auth")
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// console.log("responseJson of auth: ", responseJson.data)
|
// console.log("responseJson of auth: ", responseJson.data)
|
||||||
result = await mergeAuthData(appDetails, responseJson.data)
|
result = mergeAuthData(appDetails, responseJson.data)
|
||||||
// console.log("merged auth data: ", result)
|
// console.log("merged auth data: ", result)
|
||||||
// console.log("result", result)
|
// console.log("result", result)
|
||||||
|
|
||||||
result.map(item => {
|
result.map(item => {
|
||||||
console.log("Getting app config for: ", item.name);
|
|
||||||
fetch(globalUrl + `/api/v1/apps/${item.id}/config`, {
|
fetch(globalUrl + `/api/v1/apps/${item.id}/config`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -235,9 +203,9 @@ const Priorities = memo((props) => {
|
|||||||
setnotificationAppsDetails(newJSON);
|
setnotificationAppsDetails(newJSON);
|
||||||
console.log("notificationAppsDetails: ", newJSON)
|
console.log("notificationAppsDetails: ", newJSON)
|
||||||
}
|
}
|
||||||
}).then(async () => {
|
}).then(() => {
|
||||||
// removing this for now,
|
// removing this for now,
|
||||||
// await checkIfAlreadyGenerated(filteredApps, workflows);
|
// checkIfAlreadyGenerated(filteredApps, workflows);
|
||||||
|
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
console.log("Error getting app config: " + error);
|
console.log("Error getting app config: " + error);
|
||||||
@@ -280,143 +248,6 @@ const Priorities = memo((props) => {
|
|||||||
console.log("Apps: ", notificationAppsDetails);
|
console.log("Apps: ", notificationAppsDetails);
|
||||||
}, [notificationAppsDetails])
|
}, [notificationAppsDetails])
|
||||||
|
|
||||||
// const generateJiraNotificationWorkflow = async (app, appAuthId, projectId, issuetype) => {
|
|
||||||
// //currently only supports JIRA figure out a way to support more apps
|
|
||||||
|
|
||||||
// var appname = app.name;
|
|
||||||
// var appImage = app.large_image;
|
|
||||||
|
|
||||||
// var workflowName = `[GENERATED] ${appname} notification workflow`
|
|
||||||
// var workflowDescription = "Generated by Shuffle for sending info/error notifications."
|
|
||||||
// var data = {
|
|
||||||
// "name": workflowName,
|
|
||||||
// "description": workflowDescription,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fetch(globalUrl + "/api/v1/workflows", {
|
|
||||||
// method: "POST",
|
|
||||||
// headers: {
|
|
||||||
// "Content-Type": "application/json",
|
|
||||||
// Accept: "application/json",
|
|
||||||
// },
|
|
||||||
// body: JSON.stringify(data),
|
|
||||||
// credentials: "include",
|
|
||||||
// })
|
|
||||||
// .then((response) => {
|
|
||||||
// if (response.status !== 200) {
|
|
||||||
// toast("Failed setting notification workflow: ", response.reason);
|
|
||||||
// console.log("Status not 200 for workflows :O!");
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// return response.json();
|
|
||||||
// }).then((responseJson) => {
|
|
||||||
// if (responseJson !== undefined) {
|
|
||||||
// console.log("Notification workflow created successfully")
|
|
||||||
// var workflow_id = responseJson.id
|
|
||||||
// if (appname.toLowerCase() === "jira") {
|
|
||||||
// console.log("updating workflow for JIRA")
|
|
||||||
// var workflowBody = {
|
|
||||||
// "name": workflowName,
|
|
||||||
// "Description": workflowDescription,
|
|
||||||
// "id": workflow_id,
|
|
||||||
// "actions": [
|
|
||||||
// {
|
|
||||||
// "app_id": app.id,
|
|
||||||
// "app_name": "jira",
|
|
||||||
// "name": "post_create_issue",
|
|
||||||
// "authentication_id": appAuthId,
|
|
||||||
// "large_image": appImage,
|
|
||||||
// "isStartNode": true,
|
|
||||||
// "label": "create_issue",
|
|
||||||
// "app_version": "1.1.0",
|
|
||||||
// "parameters": [
|
|
||||||
// {
|
|
||||||
// "name": "body",
|
|
||||||
// "value": `{"fields": { "project": {"key": "${projectId}"},"summary": "$exec.title","issuetype": {"name": "${issuetype}"},"description": {"content": [{"content":[{"type":"text","text":"$exec.description"}],"type": "paragraph"}],"type": "doc","version": 1}}}`
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "name": "username_basic",
|
|
||||||
// "value": ""
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "name": "password_basic",
|
|
||||||
// "value": ""
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "name": "url",
|
|
||||||
// "value": ""
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "name": "headers",
|
|
||||||
// "value": "Content-type=application/json \nAccept=application/json"
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "name": "queries",
|
|
||||||
// "value": ""
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "name": "ssl_verify",
|
|
||||||
// "value": "False"
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// fetch(globalUrl + `/api/v1/workflows/${workflow_id}`, {
|
|
||||||
// method: "PUT",
|
|
||||||
// headers: {
|
|
||||||
// "Content-Type": "application/json",
|
|
||||||
// Accept: "application/json",
|
|
||||||
// },
|
|
||||||
// body: JSON.stringify(workflowBody),
|
|
||||||
// credentials: "include",
|
|
||||||
// })
|
|
||||||
// .then((response) => {
|
|
||||||
// if (response.status !== 200) {
|
|
||||||
// toast("Failed setting notification workflow: ", response.reason);
|
|
||||||
// console.log("Status not 200 for workflows :O!");
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// return response.json();
|
|
||||||
// }).then((responseJson) => {
|
|
||||||
// if (responseJson !== undefined) {
|
|
||||||
// handleEditOrg(
|
|
||||||
// selectedOrganization.name,
|
|
||||||
// selectedOrganization.description,
|
|
||||||
// selectedOrganization.id,
|
|
||||||
// selectedOrganization.image,
|
|
||||||
// {
|
|
||||||
// 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,
|
|
||||||
// notification_workflow: workflow_id,
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// 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,
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// console.log("Notification workflow updated successfully")
|
|
||||||
// toast("Notification workflow updated successfully")
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// }).catch((error) => {
|
|
||||||
// console.log("Error setting workflows: " + error);
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
const generateCommsNotificationWorkflow = async (app, sender, recepient) => {
|
const generateCommsNotificationWorkflow = async (app, sender, recepient) => {
|
||||||
var appname = app.name
|
var appname = app.name
|
||||||
var appImage = app.large_image
|
var appImage = app.large_image
|
||||||
@@ -984,12 +815,13 @@ print('"' + encoded + '"')
|
|||||||
<FormControl>
|
<FormControl>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
<div style={{ color: "rgba(255,255,255,0.9)" }}>
|
<div style={{ color: "rgba(255,255,255,0.9)" }}>
|
||||||
{`Configure ${selectedAppDetails.name} workflow`}
|
{`Configure ${selectedAppDetails?.name?.replaceAll("_", " ")} workflow`}
|
||||||
</div>
|
</div>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent style={{ color: "rgba(255,255,255,0.65)", overflowY: 'auto', maxHeight: '450px' }}>
|
<DialogContent style={{ color: "rgba(255,255,255,0.65)", overflowY: 'auto', maxHeight: '450px' }}>
|
||||||
<Divider style={{ marginBottom: '1em', backgroundColor: 'rgba(255,255,255,0.12)' }} />
|
<Divider style={{ marginBottom: '1em', backgroundColor: 'rgba(255,255,255,0.12)' }} />
|
||||||
{console.log("len Selected app details: ", selectedAppDetails)}
|
{console.log("len Selected app details: ", selectedAppDetails)}
|
||||||
|
|
||||||
{(selectedAppDetails.authentication_data || (selectedAppDetails.auth_config && selectedAppDetails.auth_config.required == false) || (selectedAppDetails.authentication && selectedAppDetails.authentication.required == false)) ?
|
{(selectedAppDetails.authentication_data || (selectedAppDetails.auth_config && selectedAppDetails.auth_config.required == false) || (selectedAppDetails.authentication && selectedAppDetails.authentication.required == false)) ?
|
||||||
<>
|
<>
|
||||||
<Box mb={2}>
|
<Box mb={2}>
|
||||||
@@ -1152,13 +984,15 @@ print('"' + encoded + '"')
|
|||||||
|
|
||||||
const renderChips = useCallback(() => {
|
const renderChips = useCallback(() => {
|
||||||
const appList = Object.values(notificationAppsDetails);
|
const appList = Object.values(notificationAppsDetails);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
<Stack direction="row" spacing={1}>
|
<Stack direction="row" spacing={1}>
|
||||||
|
|
||||||
{appList.map((app) => (
|
{appList.map((app) => (
|
||||||
<Chip
|
<Chip
|
||||||
key={app.id}
|
key={app.id}
|
||||||
label={app.name}
|
label={app?.name.replaceAll("_", " ")}
|
||||||
variant={generatedWorkflow.app_name === app.name ? "filled" : "outlined"}
|
variant={generatedWorkflow.app_name === app.name ? "filled" : "outlined"}
|
||||||
disabled={generatedWorkflow.app_name === app.name ? true : false}
|
disabled={generatedWorkflow.app_name === app.name ? true : false}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -1176,6 +1010,7 @@ print('"' + encoded + '"')
|
|||||||
avatar={<img src={app.large_image} alt={app.name} style={{ width: 24, height: 24, borderRadius: '50%' }} />}
|
avatar={<img src={app.large_image} alt={app.name} style={{ width: 24, height: 24, borderRadius: '50%' }} />}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
@@ -1198,7 +1033,7 @@ print('"' + encoded + '"')
|
|||||||
>
|
>
|
||||||
Set up app authentication
|
Set up app authentication
|
||||||
</Link>
|
</Link>
|
||||||
to unlock additional workflow options.
|
to show additional workflow options.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -1283,10 +1118,38 @@ print('"' + encoded + '"')
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getNotifications = () => {
|
||||||
|
fetch(`${globalUrl}/api/v1/notifications`, {
|
||||||
|
credentials: "include",
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(function (response) {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.log("Error in response");
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(function (responseJson) {
|
||||||
|
if (responseJson?.success !== false && responseJson?.notifications !== undefined && responseJson?.notifications !== null) {
|
||||||
|
setNotifications(responseJson.notifications || [])
|
||||||
|
} else {
|
||||||
|
toast("Failed loading notifications. Please try again later.");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log("error in notification loading: ", error);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
const clearNotifications = () => {
|
const clearNotifications = () => {
|
||||||
// Don't really care about the logout
|
// Don't really care about the logout
|
||||||
|
|
||||||
toast("Clearing notifications")
|
toast.info("Marking all notifications as read. This may take a while.")
|
||||||
fetch(`${globalUrl}/api/v1/notifications/clear`, {
|
fetch(`${globalUrl}/api/v1/notifications/clear`, {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ import AceEditor from "react-ace";
|
|||||||
import ace from "ace-builds";
|
import ace from "ace-builds";
|
||||||
import 'ace-builds/src-noconflict/mode-python';
|
import 'ace-builds/src-noconflict/mode-python';
|
||||||
import 'ace-builds/src-noconflict/mode-json';
|
import 'ace-builds/src-noconflict/mode-json';
|
||||||
|
import 'ace-builds/src-noconflict/mode-yaml';
|
||||||
//import 'ace-builds/src-noconflict/theme-twilight';
|
//import 'ace-builds/src-noconflict/theme-twilight';
|
||||||
//import 'ace-builds/src-noconflict/theme-solarized_dark';
|
//import 'ace-builds/src-noconflict/theme-solarized_dark';
|
||||||
import 'ace-builds/src-noconflict/theme-gruvbox';
|
import 'ace-builds/src-noconflict/theme-gruvbox';
|
||||||
@@ -119,6 +120,7 @@ const CodeEditor = (props) => {
|
|||||||
handleActionParamChange,
|
handleActionParamChange,
|
||||||
setcodedata,
|
setcodedata,
|
||||||
isFileEditor,
|
isFileEditor,
|
||||||
|
isWorkflowEditor,
|
||||||
runUpdateText,
|
runUpdateText,
|
||||||
toolsAppId,
|
toolsAppId,
|
||||||
parameterName,
|
parameterName,
|
||||||
@@ -143,11 +145,6 @@ const CodeEditor = (props) => {
|
|||||||
|
|
||||||
const [localcodedata, setlocalcodedata] = React.useState(codedata === undefined || codedata === null || codedata.length === 0 ? "" : codedata);
|
const [localcodedata, setlocalcodedata] = React.useState(codedata === undefined || codedata === null || codedata.length === 0 ? "" : codedata);
|
||||||
|
|
||||||
//const { setContainer } = useCodeMirror({
|
|
||||||
// container: editorRef.current,
|
|
||||||
// extensions,
|
|
||||||
// value: localcodedata,
|
|
||||||
//})
|
|
||||||
// const {codelang, setcodelang} = props
|
// const {codelang, setcodelang} = props
|
||||||
const {themeMode, supportEmail} = useContext(Context)
|
const {themeMode, supportEmail} = useContext(Context)
|
||||||
const theme = getTheme(themeMode)
|
const theme = getTheme(themeMode)
|
||||||
@@ -1529,13 +1526,15 @@ const CodeEditor = (props) => {
|
|||||||
// zIndex: 12501,
|
// zIndex: 12501,
|
||||||
pointerEvents: "auto",
|
pointerEvents: "auto",
|
||||||
color: theme.palette.DialogStyle.color,
|
color: theme.palette.DialogStyle.color,
|
||||||
minWidth: isMobile ? "100%" : isFileEditor ? "650px" : "80%",
|
minWidth: isMobile || isWorkflowEditor ? "100%" : isFileEditor ? "650px" : "80%",
|
||||||
maxWidth: isMobile ? "100%" : isFileEditor ? "650px" : "1100px",
|
maxWidth: isMobile || isWorkflowEditor ? "100%" : isFileEditor ? "650px" : "1100px",
|
||||||
minHeight: isMobile ? "100%" : "auto",
|
minHeight: isMobile || isWorkflowEditor ? "100%" : "auto",
|
||||||
maxHeight: isMobile ? "100%" : "700px",
|
maxHeight: isMobile || isWorkflowEditor ? "100%" : "700px",
|
||||||
border: "3px solid rgba(255,255,255,0.3)",
|
border: "3px solid rgba(255,255,255,0.3)",
|
||||||
padding: isMobile ? "25px 10px 25px 10px" : "25px",
|
padding: isMobile ? "25px 10px 25px 10px" : isWorkflowEditor ? "25px 10px 25px 200px" : "25px",
|
||||||
backgroundColor: themeMode === "dark" ? "black" : theme.palette.DialogStyle.backgroundColor,
|
backgroundColor: themeMode === "dark" ? "black" : theme.palette.DialogStyle.backgroundColor,
|
||||||
|
|
||||||
|
opacity: isWorkflowEditor ? 0.93 : 1,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -1719,7 +1718,7 @@ const CodeEditor = (props) => {
|
|||||||
Code Editor
|
Code Editor
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
*/}
|
*/}
|
||||||
{isFileEditor ? null :
|
{isFileEditor || isWorkflowEditor ? null :
|
||||||
<div style={{ display: "flex", maxHeight: 40, }}>
|
<div style={{ display: "flex", maxHeight: 40, }}>
|
||||||
<ButtonGroup style={{ borderRadius: theme.palette.borderRadius, }}>
|
<ButtonGroup style={{ borderRadius: theme.palette.borderRadius, }}>
|
||||||
{userdata !== undefined && userdata !== null && userdata.support === true ?
|
{userdata !== undefined && userdata !== null && userdata.support === true ?
|
||||||
@@ -2190,15 +2189,15 @@ const CodeEditor = (props) => {
|
|||||||
console.log("DROP: ", e)
|
console.log("DROP: ", e)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(availableVariables !== undefined && availableVariables !== null && availableVariables.length > 0) || isFileEditor ? (
|
{(availableVariables !== undefined && availableVariables !== null && availableVariables.length > 0) || isFileEditor || isWorkflowEditor ? (
|
||||||
<AceEditor
|
<AceEditor
|
||||||
id="shuffle-codeeditor"
|
id="shuffle-codeeditor"
|
||||||
name="shuffle-codeeditor"
|
name="shuffle-codeeditor"
|
||||||
value={localcodedata}
|
value={localcodedata}
|
||||||
mode={selectedAction === undefined ? "json" : selectedAction.name === "execute_python" ? "python" : selectedAction.name === "execute_bash" ? "bash" : "json"}
|
mode={isWorkflowEditor ? "yaml" : selectedAction === undefined ? "json" : selectedAction.name === "execute_python" ? "python" : selectedAction.name === "execute_bash" ? "bash" : "json"}
|
||||||
theme="gruvbox"
|
theme="gruvbox"
|
||||||
height={isFileEditor ? 450 : 550}
|
height={isFileEditor ? 450 : isWorkflowEditor ? "90vh" : 550}
|
||||||
width={isFileEditor ? 650 : "100%"}
|
width={isFileEditor ? 650 : isWorkflowEditor ? "90vw" : "100%"}
|
||||||
|
|
||||||
markers={markers}
|
markers={markers}
|
||||||
highlightActiveLine={false}
|
highlightActiveLine={false}
|
||||||
@@ -2259,7 +2258,7 @@ const CodeEditor = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isFileEditor ? null :
|
{isFileEditor || isWorkflowEditor ? null :
|
||||||
<div style={{
|
<div style={{
|
||||||
flex: sourceDataOpen ? 1.5 : 3,
|
flex: sourceDataOpen ? 1.5 : 3,
|
||||||
marginLeft: 5,
|
marginLeft: 5,
|
||||||
@@ -2524,7 +2523,7 @@ const CodeEditor = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div style={{ display: 'flex' }}>
|
<div style={{ display: 'flex', width: isWorkflowEditor ? "90%" : "100%", }}>
|
||||||
<Button
|
<Button
|
||||||
style={{
|
style={{
|
||||||
height: 35,
|
height: 35,
|
||||||
@@ -2547,6 +2546,7 @@ const CodeEditor = (props) => {
|
|||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
|
disabled={isWorkflowEditor}
|
||||||
style={{
|
style={{
|
||||||
height: 35,
|
height: 35,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@@ -2554,13 +2554,11 @@ const CodeEditor = (props) => {
|
|||||||
marginTop: 5,
|
marginTop: 5,
|
||||||
}}
|
}}
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
/*
|
if (isWorkflowEditor === true) {
|
||||||
const clickedFieldId = "rightside_field_" + fieldCount
|
setExpansionModalOpen(false)
|
||||||
const clickedField = document.getElementById(clickedFieldId)
|
navigate("")
|
||||||
if (clickedField !== undefined && clickedField !== null) {
|
return
|
||||||
clickedField.focus()
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
if (isFileEditor !== true) {
|
if (isFileEditor !== true) {
|
||||||
navigate("")
|
navigate("")
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ import {
|
|||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
|
IconButton,
|
||||||
|
Modal,
|
||||||
|
Checkbox,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -32,6 +35,8 @@ import {
|
|||||||
Business as BusinessIcon,
|
Business as BusinessIcon,
|
||||||
Flag,
|
Flag,
|
||||||
ArrowDropDown as ArrowDropDownIcon,
|
ArrowDropDown as ArrowDropDownIcon,
|
||||||
|
VisibilityOff,
|
||||||
|
Visibility,
|
||||||
|
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
|
|
||||||
@@ -66,7 +71,8 @@ const TenantsTab = memo((props) => {
|
|||||||
const itemColor = "black";
|
const itemColor = "black";
|
||||||
const { themeMode, supportEmail, brandColor } = useContext(Context);
|
const { themeMode, supportEmail, brandColor } = useContext(Context);
|
||||||
const theme = getTheme(themeMode, brandColor);
|
const theme = getTheme(themeMode, brandColor);
|
||||||
|
const [accountDeleteButtonClicked, setAccountDeleteButtonClicked] = useState(false);
|
||||||
|
const [selectedSuborg, setSelectedSuborg] = useState(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(parentOrg !== null && parentOrgFlag === null) {
|
if(parentOrg !== null && parentOrgFlag === null) {
|
||||||
let regiontag = "UK";
|
let regiontag = "UK";
|
||||||
@@ -594,6 +600,221 @@ const TenantsTab = memo((props) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DeleteAccountPopUp = () => {
|
||||||
|
const [userDeleteAccepted, setUserDeleteAccepted] = useState(false);
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
const [disabled, setDisabled] = useState(true);
|
||||||
|
const [open, setOpen] = useState(true);
|
||||||
|
const boxStyling = {
|
||||||
|
position: "relative",
|
||||||
|
top: "50%",
|
||||||
|
left: "50%",
|
||||||
|
transform: "translate(-50%, -50%)",
|
||||||
|
zIndex: "9999",
|
||||||
|
backgroundColor: theme.palette.backgroundColor,
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
padding: 20,
|
||||||
|
borderRadius: 5,
|
||||||
|
boxShadow: "0 0 10px rgba(0, 0, 0, 0.3)",
|
||||||
|
width: 430,
|
||||||
|
height: 430,
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeIconButtonStyling = {
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
border: "none",
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
position: 'relative',
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
cursor: "pointer",
|
||||||
|
left: "calc(100% - 30px)",
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePasswordVisibility = () => {
|
||||||
|
setShowPassword(!showPassword);
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttonStyle = {
|
||||||
|
marginTop: 20,
|
||||||
|
height: 50,
|
||||||
|
border: "none",
|
||||||
|
width: "100%",
|
||||||
|
fontSize: 16,
|
||||||
|
backgroundColor: disabled ? "gray" : "red",
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
cursor: disabled === false && "pointer",
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePasswordChange = (e) => {
|
||||||
|
setPassword(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCheckBoxEvent = () => {
|
||||||
|
setUserDeleteAccepted((prev) => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (password.length > 8 && userDeleteAccepted) {
|
||||||
|
setDisabled(false);
|
||||||
|
} else {
|
||||||
|
setDisabled(true);
|
||||||
|
}
|
||||||
|
}, [password, userDeleteAccepted]);
|
||||||
|
|
||||||
|
|
||||||
|
const handleDeleteAccount = () => {
|
||||||
|
const baseURL = globalUrl;
|
||||||
|
|
||||||
|
const url = `${baseURL}/api/v1/orgs/${selectedOrganization?.id}`;
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
suborg_id : selectedSuborg?.id,
|
||||||
|
password: password,
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
mode: "cors",
|
||||||
|
method: "DELETE",
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
credentials: "include",
|
||||||
|
crossDomain: true,
|
||||||
|
withCredentials: true,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (data.success) {
|
||||||
|
toast.success(
|
||||||
|
"Suborg deleted"
|
||||||
|
);
|
||||||
|
handleGetSubOrgs(userdata.active_org.id);
|
||||||
|
setAccountDeleteButtonClicked(false);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (data.reason) {
|
||||||
|
toast.error(data.reason);
|
||||||
|
}else {
|
||||||
|
toast.error("Failed to delete suborg. Please try again or contact support@shuffler.io for help.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(
|
||||||
|
"There was a problem with your fetch operation:",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open= {open} >
|
||||||
|
<div style={boxStyling}>
|
||||||
|
<IconButton
|
||||||
|
style={closeIconButtonStyling}
|
||||||
|
onClick={() => {
|
||||||
|
setAccountDeleteButtonClicked(false);
|
||||||
|
setSelectedSuborg(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
<h1 style={{textAlign:"center", margin : "0 0 20px 0"}}>Sub-Organization</h1>
|
||||||
|
{/* <div style={{paddingLeft: 15}} > */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
textAlign: "left",
|
||||||
|
// paddingLeft: 15
|
||||||
|
// marginLeft: 20
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<label>If you delete your suborg then:</label>
|
||||||
|
<ul style={{ textAlign: "left" }}>
|
||||||
|
<li>
|
||||||
|
<label>
|
||||||
|
Your suborg information will be removed from our Database
|
||||||
|
permanently.
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label>This action cannot be reversed.</label>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 10,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={userDeleteAccepted}
|
||||||
|
onChange={handleCheckBoxEvent}
|
||||||
|
size="medium"
|
||||||
|
/>
|
||||||
|
<label style={{ fontSize: "16px", color: theme.palette.text.primary }}>
|
||||||
|
I have read the above information and I agree to it completely
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: 20,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<label>Enter password to delete suborg {" " + selectedSuborg?.name}</label>
|
||||||
|
<TextField
|
||||||
|
style={{
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
flex: "1",
|
||||||
|
}}
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
value={password}
|
||||||
|
onChange={handlePasswordChange}
|
||||||
|
required
|
||||||
|
placeholder="Password"
|
||||||
|
id="standard-required"
|
||||||
|
autoComplete="off"
|
||||||
|
InputProps={{
|
||||||
|
style: {
|
||||||
|
color: theme.palette.textFieldStyle.color,
|
||||||
|
backgroundColor: theme.palette.textFieldStyle.backgroundColor,
|
||||||
|
height: "45px",
|
||||||
|
fontSize: "1em",
|
||||||
|
borderRadius: theme.palette.textFieldStyle.borderRadius,
|
||||||
|
},
|
||||||
|
endAdornment: (
|
||||||
|
<IconButton
|
||||||
|
onClick={handlePasswordVisibility}
|
||||||
|
style={{color:theme.palette.text.primary}}
|
||||||
|
>
|
||||||
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||||
|
</IconButton>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
style={buttonStyle}
|
||||||
|
disabled={disabled}
|
||||||
|
onClick={handleDeleteAccount}
|
||||||
|
>
|
||||||
|
Delete Suborg
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const modalView = (
|
const modalView = (
|
||||||
<Dialog
|
<Dialog
|
||||||
open={modalOpen}
|
open={modalOpen}
|
||||||
@@ -795,6 +1016,7 @@ const TenantsTab = memo((props) => {
|
|||||||
<div style={{ width: "100%", minHeight: 1100, boxSizing: 'border-box', padding: "27px 10px 19px 27px", height:"100%", backgroundColor: theme.palette.platformColor, borderTopRightRadius: '8px', borderBottomRightRadius: 8, borderLeft: theme.palette.defaultBorder,}}>
|
<div style={{ width: "100%", minHeight: 1100, boxSizing: 'border-box', padding: "27px 10px 19px 27px", height:"100%", backgroundColor: theme.palette.platformColor, borderTopRightRadius: '8px', borderBottomRightRadius: 8, borderLeft: theme.palette.defaultBorder,}}>
|
||||||
{modalView}
|
{modalView}
|
||||||
{cloudSyncModal}
|
{cloudSyncModal}
|
||||||
|
{accountDeleteButtonClicked && <DeleteAccountPopUp/>}
|
||||||
<div style={{height: "100%", maxHeight: 1700, overflowY: "auto",overflowX: 'hidden', scrollbarColor: theme.palette.scrollbarColorTransparent, scrollbarWidth: 'thin'}}>
|
<div style={{height: "100%", maxHeight: 1700, overflowY: "auto",overflowX: 'hidden', scrollbarColor: theme.palette.scrollbarColorTransparent, scrollbarWidth: 'thin'}}>
|
||||||
<div style={{ height: "100%", width: "calc(100% - 20px)", scrollbarColor: theme.palette.scrollbarColorTransparent, scrollbarWidth: 'thin' }}>
|
<div style={{ height: "100%", width: "calc(100% - 20px)", scrollbarColor: theme.palette.scrollbarColorTransparent, scrollbarWidth: 'thin' }}>
|
||||||
<div style={{ marginBottom: 20 }}>
|
<div style={{ marginBottom: 20 }}>
|
||||||
@@ -1340,6 +1562,7 @@ const TenantsTab = memo((props) => {
|
|||||||
|
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={
|
primary={
|
||||||
|
<>
|
||||||
<Tooltip title={data.id === userdata?.active_org?.id ? "You are already in this organization." : ""} disableInteractive>
|
<Tooltip title={data.id === userdata?.active_org?.id ? "You are already in this organization." : ""} disableInteractive>
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
@@ -1359,7 +1582,34 @@ const TenantsTab = memo((props) => {
|
|||||||
>
|
>
|
||||||
Change Active Org
|
Change Active Org
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
{selectedOrganization?.creator_org?.length > 0 ? null :
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
sx={{
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
textTransform: "none",
|
||||||
|
fontSize: "16px",
|
||||||
|
color: "red",
|
||||||
|
marginLeft: "5px",
|
||||||
|
borderColor: "red",
|
||||||
|
"&:hover": {
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
backgroundColor: "red",
|
||||||
|
},
|
||||||
|
|
||||||
|
}}
|
||||||
|
disabled={data?.id === userdata?.active_org?.id}
|
||||||
|
onClick={() => {
|
||||||
|
setAccountDeleteButtonClicked(true);
|
||||||
|
setSelectedSuborg(data);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Delete Org
|
||||||
|
</Button>}
|
||||||
|
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
style={{ display: "table-cell", verticalAlign: "middle" }}
|
style={{ display: "table-cell", verticalAlign: "middle" }}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1458,33 +1458,8 @@ const UserManagmentTab = memo((props) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const getRegionFlag = (region_url) => {
|
|
||||||
let regiontag = "UK";
|
|
||||||
let regionCode = "gb";
|
|
||||||
const regionsplit = region_url?.split(".");
|
|
||||||
if (regionsplit?.length > 2 && !regionsplit[0]?.includes("shuffler")) {
|
|
||||||
const namesplit = regionsplit[0]?.split("/");
|
|
||||||
regiontag = namesplit[namesplit?.length - 1];
|
|
||||||
|
|
||||||
if (regiontag === "california") {
|
const userRegion = data?.user_geo_info?.country?.iso_code
|
||||||
regiontag = "US";
|
|
||||||
regionCode = "us";
|
|
||||||
} else if (regiontag === "frankfurt") {
|
|
||||||
regiontag = "EU-2";
|
|
||||||
regionCode = "eu";
|
|
||||||
} else if (regiontag === "ca") {
|
|
||||||
regiontag = "CA";
|
|
||||||
regionCode = "ca";
|
|
||||||
}else if (regiontag === "au") {
|
|
||||||
regiontag = "AUS";
|
|
||||||
regionCode = "au"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return regionCode;
|
|
||||||
};
|
|
||||||
|
|
||||||
const userRegion = data?.user_geo_info?.country?.iso_code?.length > 0 ? data?.user_geo_info?.country?.iso_code : selectedOrganization?.region_url?.length > 0 ? getRegionFlag(selectedOrganization?.region_url) : "eu";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListItem key={index} style={{ backgroundColor: bgColor, display: 'table-row', borderBottomLeftRadius: users?.length - 1 === index ? 8 : 0, borderBottomRightRadius: users?.length - 1 === index ? 8 : 0 }}>
|
<ListItem key={index} style={{ backgroundColor: bgColor, display: 'table-row', borderBottomLeftRadius: users?.length - 1 === index ? 8 : 0, borderBottomRightRadius: users?.length - 1 === index ? 8 : 0 }}>
|
||||||
|
|||||||
@@ -181,6 +181,10 @@ export const getTheme = (themeMode, brandColor) =>
|
|||||||
cardBackgroundColor: themeMode === "dark" ? "#1e1e1e" : "#eaeaea",
|
cardBackgroundColor: themeMode === "dark" ? "#1e1e1e" : "#eaeaea",
|
||||||
cardHoverColor: themeMode === "dark" ? "#323232" : "#F0F0F0",
|
cardHoverColor: themeMode === "dark" ? "#323232" : "#F0F0F0",
|
||||||
hoverColor: themeMode === "dark" ? "#323232" : "#D6D6D6",
|
hoverColor: themeMode === "dark" ? "#323232" : "#D6D6D6",
|
||||||
|
usecaseCardColor: themeMode === "dark" ? "#2f2f2f" : "rgba(245, 245, 245, 1)",
|
||||||
|
usecaseCardHoverColor: themeMode === "dark" ? "#2F2F2F" : "rgba(245, 245, 245, 1)",
|
||||||
|
usecaseDialogFieldColor: themeMode === "dark" ? "#2B2B2B" : "#F5F5F5",
|
||||||
|
accentColor: themeMode === "dark" ? "#ff8544" : "#ff8544",
|
||||||
green: themeMode === "dark" ? "#5cc879" : "#008000",
|
green: themeMode === "dark" ? "#5cc879" : "#008000",
|
||||||
defaultBorder: themeMode === "dark" ? '1px solid #494949' : '1px solid #CCCCCC',
|
defaultBorder: themeMode === "dark" ? '1px solid #494949' : '1px solid #CCCCCC',
|
||||||
linkColor: brandColor === "#ff8544" ? "#f86a3e" : brandColor,
|
linkColor: brandColor === "#ff8544" ? "#f86a3e" : brandColor,
|
||||||
|
|||||||
@@ -287,8 +287,8 @@ const Admin2 = (props) => {
|
|||||||
|
|
||||||
|
|
||||||
const handleStatusChange = (event) => {
|
const handleStatusChange = (event) => {
|
||||||
const { value } = event.target;
|
const { value } = event.target
|
||||||
setSelectedStatus(value);
|
setSelectedStatus(value)
|
||||||
|
|
||||||
handleEditOrg(
|
handleEditOrg(
|
||||||
selectedOrganization?.name,
|
selectedOrganization?.name,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { getTheme } from "../theme.jsx";
|
|||||||
import { useInterval } from "react-powerhooks";
|
import { useInterval } from "react-powerhooks";
|
||||||
import { makeStyles, } from "@mui/styles";
|
import { makeStyles, } from "@mui/styles";
|
||||||
|
|
||||||
|
import YAML from "yaml";
|
||||||
import WorkflowTemplatePopup from "../components/WorkflowTemplatePopup.jsx"
|
import WorkflowTemplatePopup from "../components/WorkflowTemplatePopup.jsx"
|
||||||
import { v4 as uuidv4, v5 as uuidv5, validate as isUUID, } from "uuid";
|
import { v4 as uuidv4, v5 as uuidv5, validate as isUUID, } from "uuid";
|
||||||
import { useNavigate, Link, useParams } from "react-router-dom";
|
import { useNavigate, Link, useParams } from "react-router-dom";
|
||||||
@@ -132,6 +133,7 @@ import {
|
|||||||
ArrowForward as ArrowForwardIcon,
|
ArrowForward as ArrowForwardIcon,
|
||||||
OpenInFull as OpenInFullIcon,
|
OpenInFull as OpenInFullIcon,
|
||||||
Difference as DifferenceIcon,
|
Difference as DifferenceIcon,
|
||||||
|
DataObject as DataObjectIcon,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import SwapHorizIcon from '@mui/icons-material/SwapHoriz';
|
import SwapHorizIcon from '@mui/icons-material/SwapHoriz';
|
||||||
//import * as cytoscape from "cytoscape";
|
//import * as cytoscape from "cytoscape";
|
||||||
@@ -778,9 +780,9 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
"name": "action",
|
"name": "action",
|
||||||
"value": "list_tickets",
|
"value": "list_tickets",
|
||||||
"options": [
|
"options": [
|
||||||
|
"create_ticket",
|
||||||
"list_tickets",
|
"list_tickets",
|
||||||
"get_ticket",
|
"get_ticket",
|
||||||
"create_ticket",
|
|
||||||
"close_ticket",
|
"close_ticket",
|
||||||
"add_comment",
|
"add_comment",
|
||||||
"update_ticket",
|
"update_ticket",
|
||||||
@@ -794,16 +796,6 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
"required": false,
|
"required": false,
|
||||||
"multiline": true,
|
"multiline": true,
|
||||||
},
|
},
|
||||||
/*{
|
|
||||||
"name": "options",
|
|
||||||
"value": "deduplicate,enrich",
|
|
||||||
"required": false,
|
|
||||||
"multiselect": true,
|
|
||||||
"options": [
|
|
||||||
"deduplicate",
|
|
||||||
"enrich",
|
|
||||||
]
|
|
||||||
}*/
|
|
||||||
]
|
]
|
||||||
}, {
|
}, {
|
||||||
"name": "Communication",
|
"name": "Communication",
|
||||||
@@ -813,9 +805,9 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
"name": "action",
|
"name": "action",
|
||||||
"value": "list_messages",
|
"value": "list_messages",
|
||||||
"options": [
|
"options": [
|
||||||
|
"send_message",
|
||||||
"list_messages",
|
"list_messages",
|
||||||
"get_message",
|
"get_message",
|
||||||
"send_message",
|
|
||||||
"search_messages",
|
"search_messages",
|
||||||
"list_attachments",
|
"list_attachments",
|
||||||
"get_attachment",
|
"get_attachment",
|
||||||
@@ -861,7 +853,7 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
"label": "Assets",
|
"label": "Assets",
|
||||||
"parameters": [{
|
"parameters": [{
|
||||||
"name": "action",
|
"name": "action",
|
||||||
"value": "search_vulnerabilities",
|
"value": "list_assets",
|
||||||
"options": [
|
"options": [
|
||||||
"list_assets",
|
"list_assets",
|
||||||
"get_asset",
|
"get_asset",
|
||||||
@@ -915,8 +907,8 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
"value": "get_ioc",
|
"value": "get_ioc",
|
||||||
"options": [
|
"options": [
|
||||||
"get_ioc",
|
"get_ioc",
|
||||||
"search_ioc",
|
|
||||||
"create_ioc",
|
"create_ioc",
|
||||||
|
"search_ioc",
|
||||||
"update_ioc",
|
"update_ioc",
|
||||||
"delete_ioc"
|
"delete_ioc"
|
||||||
],
|
],
|
||||||
@@ -975,28 +967,9 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
"multiline": true,
|
"multiline": true,
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
]
|
]
|
||||||
}]
|
}]
|
||||||
|
|
||||||
/*
|
|
||||||
{
|
|
||||||
"name": "Email",
|
|
||||||
"label": "Email",
|
|
||||||
"parameters": [{
|
|
||||||
"name": "action",
|
|
||||||
"value": "list_email",
|
|
||||||
"options": [
|
|
||||||
"list_email",
|
|
||||||
"send_mail",
|
|
||||||
],
|
|
||||||
"required": true,
|
|
||||||
}],
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
*/
|
|
||||||
|
|
||||||
// For code editor
|
// For code editor
|
||||||
const [codeEditorModalOpen, setCodeEditorModalOpen] = React.useState(false);
|
const [codeEditorModalOpen, setCodeEditorModalOpen] = React.useState(false);
|
||||||
const [codedata, setcodedata] = React.useState("");
|
const [codedata, setcodedata] = React.useState("");
|
||||||
@@ -1213,6 +1186,7 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
if (workflow?.suborg_distribution !== undefined && workflow?.suborg_distribution !== null && workflow?.suborg_distribution.length > 0) {
|
if (workflow?.suborg_distribution !== undefined && workflow?.suborg_distribution !== null && workflow?.suborg_distribution.length > 0) {
|
||||||
getChildWorkflows(workflow.id)
|
getChildWorkflows(workflow.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
}, [workflow]);
|
}, [workflow]);
|
||||||
|
|
||||||
// Event for making sure app is correct
|
// Event for making sure app is correct
|
||||||
@@ -2506,9 +2480,11 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
console.log("Should redirect to register with redirect.")
|
console.log("Should redirect to register with redirect.")
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
toast("You may not have access to this workflow.")
|
// toast("You may not have access to this workflow.")
|
||||||
//window.location.href = `/register?view=/workflows/${props.match.params.key}&message=You need sign up to use workflows with Shuffle`
|
localStorage.setItem("redirectId", props.match.params.key)
|
||||||
window.location.href = `/workflows`
|
navigate("/register?view=workflows&message=You need sign up to use workflows with Shuffle");
|
||||||
|
// window.location.href = `/register?view=/workflows/${props.match.params.key}&message=You need sign up to use workflows with Shuffle`
|
||||||
|
// window.location.href = `/workflows`
|
||||||
}, 2500)
|
}, 2500)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -2878,9 +2854,9 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
setSavingState(0);
|
setSavingState(0);
|
||||||
console.log("Workflow failed loading: ", responseJson);
|
console.log("Workflow failed loading: ", responseJson);
|
||||||
if (responseJson.reason !== undefined && responseJson.reason !== null) {
|
if (responseJson.reason !== undefined && responseJson.reason !== null) {
|
||||||
toast("Failed to save: " + responseJson.reason);
|
toast.error("Failed to save: " + responseJson.reason);
|
||||||
} else {
|
} else {
|
||||||
toast(`Failed to save. Please contact your ${supportEmail} or your local admin if this is unexpected.`)
|
toast.error(`Failed to save. Please contact your ${supportEmail} or your local admin if this is unexpected.`)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setSavingState(1);
|
setSavingState(1);
|
||||||
@@ -4590,6 +4566,9 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
if(!isLoggedIn){
|
||||||
|
localStorage.setItem("redirectId", props.match.params.key);
|
||||||
|
}
|
||||||
window.location.pathname = "/workflows";
|
window.location.pathname = "/workflows";
|
||||||
}, 2500);
|
}, 2500);
|
||||||
|
|
||||||
@@ -4880,6 +4859,17 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
setOriginalWorkflow(responseJson)
|
setOriginalWorkflow(responseJson)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tmpUi = new URLSearchParams(cursearch).get("ui");
|
||||||
|
if (
|
||||||
|
tmpUi !== undefined &&
|
||||||
|
tmpUi !== null &&
|
||||||
|
tmpUi === "yaml"
|
||||||
|
) {
|
||||||
|
setTimeout(() => {
|
||||||
|
setupWorkflowYaml(responseJson)
|
||||||
|
}, 2500)
|
||||||
|
}
|
||||||
|
|
||||||
setWorkflow(responseJson);
|
setWorkflow(responseJson);
|
||||||
setWorkflowDone(true);
|
setWorkflowDone(true);
|
||||||
|
|
||||||
@@ -4898,6 +4888,9 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
|
|
||||||
setConfigureWorkflowModalOpen(true)
|
setConfigureWorkflowModalOpen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -9959,6 +9952,191 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const setupWorkflowYaml = (inputworkflow) => {
|
||||||
|
toast.warn("YAML exploring is an experimental feature - only visible to support users. The goal of this is to make it EASY to edit the workflow as YAML instead of just using the UI")
|
||||||
|
if (userdata?.support !== true) {
|
||||||
|
console.log("Not support: ", userdata)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should be getting the workflow based on actual nodes in the workflow
|
||||||
|
// as the goal is to have it be live
|
||||||
|
var copiedWorkflow = JSON.parse(JSON.stringify(inputworkflow))
|
||||||
|
|
||||||
|
const removeObjects = [
|
||||||
|
"execution_org",
|
||||||
|
"categories",
|
||||||
|
"example_argument",
|
||||||
|
"public",
|
||||||
|
"contact_info",
|
||||||
|
"published_id",
|
||||||
|
"revision_id",
|
||||||
|
"usecase_ids",
|
||||||
|
"input_questions",
|
||||||
|
"form_control",
|
||||||
|
"blogpost",
|
||||||
|
"video",
|
||||||
|
"status",
|
||||||
|
"generated",
|
||||||
|
"hidden",
|
||||||
|
"updated_by",
|
||||||
|
"validated",
|
||||||
|
"validation",
|
||||||
|
"childorg_workflow_ids",
|
||||||
|
"backup_config",
|
||||||
|
"auth_groups",
|
||||||
|
"isValid",
|
||||||
|
"workflow_as_code",
|
||||||
|
"image",
|
||||||
|
"sharing",
|
||||||
|
"owner",
|
||||||
|
"configuration",
|
||||||
|
"created",
|
||||||
|
"edited",
|
||||||
|
"last_runtime",
|
||||||
|
"due_date",
|
||||||
|
"is_valid",
|
||||||
|
"execution_environment",
|
||||||
|
"default_return_value",
|
||||||
|
"visual_branches",
|
||||||
|
"previously_saved",
|
||||||
|
"workflow_type",
|
||||||
|
"parentorg_workflow",
|
||||||
|
"suborg_distribution",
|
||||||
|
"id",
|
||||||
|
"comments",
|
||||||
|
"org_id",
|
||||||
|
|
||||||
|
// Failing test
|
||||||
|
"spalabi",
|
||||||
|
]
|
||||||
|
|
||||||
|
for (var i = 0; i < removeObjects.length; i++) {
|
||||||
|
delete copiedWorkflow[removeObjects[i]]
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeActionValues = [
|
||||||
|
"_id",
|
||||||
|
"id",
|
||||||
|
|
||||||
|
"large_image",
|
||||||
|
"description",
|
||||||
|
"is_valid",
|
||||||
|
"isStartNode",
|
||||||
|
"sharing",
|
||||||
|
"public",
|
||||||
|
"generated",
|
||||||
|
"execution_variable",
|
||||||
|
"position",
|
||||||
|
"category",
|
||||||
|
"reference_url",
|
||||||
|
"sub_action",
|
||||||
|
"run_magic_output",
|
||||||
|
"run_magic_input",
|
||||||
|
"category_label",
|
||||||
|
"suggestions",
|
||||||
|
"parent_controlled",
|
||||||
|
"source_workflow",
|
||||||
|
"source_executions",
|
||||||
|
"app_association",
|
||||||
|
"suggestion",
|
||||||
|
"small_image",
|
||||||
|
"long_description",
|
||||||
|
"tags",
|
||||||
|
"errors",
|
||||||
|
"source_executions",
|
||||||
|
"source_execution",
|
||||||
|
"required",
|
||||||
|
"example",
|
||||||
|
"type",
|
||||||
|
|
||||||
|
"test2",
|
||||||
|
]
|
||||||
|
|
||||||
|
const removeActionParamValues = [
|
||||||
|
"id",
|
||||||
|
"multiline",
|
||||||
|
"multiselect",
|
||||||
|
"options",
|
||||||
|
"action_field",
|
||||||
|
"variant",
|
||||||
|
"configuration",
|
||||||
|
"tags",
|
||||||
|
"schema",
|
||||||
|
"skip_multicheck",
|
||||||
|
"value_replace",
|
||||||
|
"unique_toggled",
|
||||||
|
"hidden",
|
||||||
|
"error",
|
||||||
|
"example",
|
||||||
|
|
||||||
|
"test3",
|
||||||
|
]
|
||||||
|
|
||||||
|
if (copiedWorkflow?.actions !== undefined && copiedWorkflow?.actions !== null && copiedWorkflow?.actions.length > 0) {
|
||||||
|
for (var key in copiedWorkflow.actions) {
|
||||||
|
for (var i = 0; i < removeActionValues.length; i++) {
|
||||||
|
delete copiedWorkflow.actions[key][removeActionValues[i]]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (copiedWorkflow.actions[key].parameters === undefined || copiedWorkflow.actions[key].parameters === null) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var j = 0; j < copiedWorkflow.actions[key].parameters.length; j++) {
|
||||||
|
for (var k = 0; k < removeActionParamValues.length; k++) {
|
||||||
|
delete copiedWorkflow.actions[key].parameters[j][removeActionParamValues[k]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (copiedWorkflow?.triggers !== undefined && copiedWorkflow?.triggers !== null && copiedWorkflow?.triggers.length > 0) {
|
||||||
|
for (var key in copiedWorkflow.triggers) {
|
||||||
|
for (var i = 0; i < removeActionValues.length; i++) {
|
||||||
|
delete copiedWorkflow.triggers[key][removeActionValues[i]]
|
||||||
|
}
|
||||||
|
|
||||||
|
delete copiedWorkflow.triggers[key]["app_name"]
|
||||||
|
delete copiedWorkflow.triggers[key]["app_version"]
|
||||||
|
delete copiedWorkflow.triggers[key]["name"]
|
||||||
|
delete copiedWorkflow.triggers[key]["priority"]
|
||||||
|
delete copiedWorkflow.triggers[key]["replacement_for_trigger"]
|
||||||
|
|
||||||
|
if (copiedWorkflow.triggers[key].parameters === undefined || copiedWorkflow.triggers[key].parameters === null) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var j = 0; j < copiedWorkflow.triggers[key].parameters.length; j++) {
|
||||||
|
for (var k = 0; k < removeActionParamValues.length; k++) {
|
||||||
|
delete copiedWorkflow.triggers[key].parameters[j][removeActionParamValues[k]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (copiedWorkflow?.workflow_variables === undefined || copiedWorkflow?.workflow_variables === null || copiedWorkflow?.workflow_variables.length === 0) {
|
||||||
|
delete copiedWorkflow.workflow_variables
|
||||||
|
}
|
||||||
|
|
||||||
|
if (copiedWorkflow?.branches === undefined || copiedWorkflow?.branches === null || copiedWorkflow?.branches.length === 0) {
|
||||||
|
delete copiedWorkflow.branches
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// YAML
|
||||||
|
const sampledata = YAML.stringify(copiedWorkflow)
|
||||||
|
|
||||||
|
navigate("?ui=yaml", { replace: true })
|
||||||
|
|
||||||
|
setCodeEditorModalOpen(true)
|
||||||
|
setEditorData({
|
||||||
|
"name": "workflow yaml",
|
||||||
|
"value": sampledata,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
//useEffect(() => {
|
//useEffect(() => {
|
||||||
if (firstrequest) {
|
if (firstrequest) {
|
||||||
@@ -10000,6 +10178,7 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
//navigate(`?execution_highlight=${parsed_url}`)
|
//navigate(`?execution_highlight=${parsed_url}`)
|
||||||
//props.history.push(curpath + newitem);
|
//props.history.push(curpath + newitem);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12136,10 +12315,8 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((app.id === "integration" || app.id === "shuffle_agent") && userdata.support !== true) {
|
if ((app.id === "integration" || app.id === "shuffle_agent") && userdata.support !== true) {
|
||||||
console.log("APPID: ", app.id, isCloud)
|
|
||||||
if (isCloud === false && app.id === "integration") {
|
if (isCloud === false && app.id === "integration") {
|
||||||
} else {
|
} else {
|
||||||
console.log("RETURNING", app.id)
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12572,7 +12749,7 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
// maxiter = max amount of parent nodes to loop
|
// maxiter = max amount of parent nodes to loop
|
||||||
// also handles breaks if there are issues
|
// also handles breaks if there are issues
|
||||||
var iterations = 0;
|
var iterations = 0;
|
||||||
var maxiter = 10;
|
var maxiter = 100;
|
||||||
while (true) {
|
while (true) {
|
||||||
for (let parentkey in allkeys) {
|
for (let parentkey in allkeys) {
|
||||||
var currentnode = cy.getElementById(allkeys[parentkey]);
|
var currentnode = cy.getElementById(allkeys[parentkey]);
|
||||||
@@ -18553,6 +18730,68 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
zoom: isSafari ? undefined : 0.9,
|
zoom: isSafari ? undefined : 0.9,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const changeOrg = () => {
|
||||||
|
localStorage.setItem("globalUrl", "");
|
||||||
|
localStorage.setItem("getting_started_sidebar", "open");
|
||||||
|
fetch(`${globalUrl}/api/v1/orgs/${workflow.org_id}/change`, {
|
||||||
|
mode: "cors",
|
||||||
|
credentials: "include",
|
||||||
|
crossDomain: true,
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({ "org_id": workflow.org_id }),
|
||||||
|
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) {
|
||||||
|
console.log("In here?")
|
||||||
|
if (responseJson.success === true) {
|
||||||
|
if (responseJson.region_url !== undefined && responseJson.region_url !== null && responseJson.region_url.length > 0) {
|
||||||
|
console.log("Region Change: ", responseJson.region_url);
|
||||||
|
localStorage.setItem("globalUrl", responseJson.region_url);
|
||||||
|
//globalUrl = responseJson.region_url
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseJson["reason"] === "SSO_REDIRECT") {
|
||||||
|
setTimeout(() => {
|
||||||
|
toast.info("Redirecting to SSO login page as SSO is required for this organization.")
|
||||||
|
window.location.href = responseJson["url"]
|
||||||
|
return
|
||||||
|
}, 2000)
|
||||||
|
} else {
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload();
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.info("Successfully changed active organisation - refreshing!");
|
||||||
|
} else {
|
||||||
|
if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason.length > 0) {
|
||||||
|
toast(responseJson.reason);
|
||||||
|
} else {
|
||||||
|
toast(`Failed changing org. Try again or contact ${supportEmail} if this persists.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log("error changing: ", error);
|
||||||
|
//removeCookie("session_token", {path: "/"})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const TopCytoscapeBar = (props) => {
|
const TopCytoscapeBar = (props) => {
|
||||||
const [hovered, setHovered] = useState(false)
|
const [hovered, setHovered] = useState(false)
|
||||||
|
|
||||||
@@ -18620,7 +18859,7 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!distributedFromParent ?
|
{!distributedFromParent || userdata?.support === true ?
|
||||||
isCorrectOrg ? null :
|
isCorrectOrg ? null :
|
||||||
<Typography variant="body2" style={{ marginLeft: 10, }}>
|
<Typography variant="body2" style={{ marginLeft: 10, }}>
|
||||||
<b>Warning</b>: <span
|
<b>Warning</b>: <span
|
||||||
@@ -18628,64 +18867,7 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
toast("Changing to correct organisation. Please wait a few seconds.")
|
toast("Changing to correct organisation. Please wait a few seconds.")
|
||||||
|
|
||||||
localStorage.setItem("globalUrl", "");
|
changeOrg()
|
||||||
localStorage.setItem("getting_started_sidebar", "open");
|
|
||||||
fetch(`${globalUrl}/api/v1/orgs/${workflow.org_id}/change`, {
|
|
||||||
mode: "cors",
|
|
||||||
credentials: "include",
|
|
||||||
crossDomain: true,
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({ "org_id": workflow.org_id }),
|
|
||||||
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) {
|
|
||||||
console.log("In here?")
|
|
||||||
if (responseJson.success === true) {
|
|
||||||
if (responseJson.region_url !== undefined && responseJson.region_url !== null && responseJson.region_url.length > 0) {
|
|
||||||
console.log("Region Change: ", responseJson.region_url);
|
|
||||||
localStorage.setItem("globalUrl", responseJson.region_url);
|
|
||||||
//globalUrl = responseJson.region_url
|
|
||||||
}
|
|
||||||
|
|
||||||
if (responseJson["reason"] === "SSO_REDIRECT") {
|
|
||||||
setTimeout(() => {
|
|
||||||
toast.info("Redirecting to SSO login page as SSO is required for this organization.")
|
|
||||||
window.location.href = responseJson["url"]
|
|
||||||
return
|
|
||||||
}, 2000)
|
|
||||||
} else {
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.reload();
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
toast("Successfully changed active organisation - refreshing!");
|
|
||||||
} else {
|
|
||||||
if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason.length > 0) {
|
|
||||||
toast(responseJson.reason);
|
|
||||||
} else {
|
|
||||||
toast(`Failed changing org. Try again or contact ${supportEmail} if this persists.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.log("error changing: ", error);
|
|
||||||
//removeCookie("session_token", {path: "/"})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -18707,6 +18889,11 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
<div>
|
<div>
|
||||||
{originalWorkflow?.parentorg_workflow !== undefined && originalWorkflow?.parentorg_workflow !== null && originalWorkflow?.parentorg_workflow.length > 0 || workflow?.parentorg_workflow !== undefined && workflow?.parentorg_workflow !== null && workflow?.parentorg_workflow.length > 0 ?
|
{originalWorkflow?.parentorg_workflow !== undefined && originalWorkflow?.parentorg_workflow !== null && originalWorkflow?.parentorg_workflow.length > 0 || workflow?.parentorg_workflow !== undefined && workflow?.parentorg_workflow !== null && workflow?.parentorg_workflow.length > 0 ?
|
||||||
|
|
||||||
|
null
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Disabled because redirects occurred anyway
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
color="secondary"
|
color="secondary"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
@@ -18717,17 +18904,20 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
marginLeft: 10,
|
marginLeft: 10,
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(`/workflows/${workflow.parentorg_workflow}`)
|
//changeOrg()
|
||||||
|
|
||||||
// Reload the page
|
// Reload the page
|
||||||
|
navigate(`/workflows/${workflow.parentorg_workflow}`)
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Go to parent org workflow
|
Go to parent org workflow
|
||||||
</Button>
|
</Button>
|
||||||
|
*/
|
||||||
|
|
||||||
: null}
|
: null}
|
||||||
|
|
||||||
{userdata !== undefined && userdata !== null && userdata.orgs !== undefined && userdata.orgs !== null && userdata.orgs.length > 1 && workflow?.id !== undefined && workflow?.id && workflow?.id?.length > 0 ?
|
{userdata !== undefined && userdata !== null && userdata.orgs !== undefined && userdata.orgs !== null && userdata.orgs.length > 1 && workflow?.id !== undefined && workflow?.id && workflow?.id?.length > 0 && userdata?.active_org?.creator_org?.length === 0 && userdata?.active_org?.id == workflow?.org_id ?
|
||||||
<Button
|
<Button
|
||||||
color="secondary"
|
color="secondary"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
@@ -19575,7 +19765,7 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
>
|
>
|
||||||
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title="Hide error messages. They will show up the next refresh."
|
title="Hides error messages this session."
|
||||||
placement="top"
|
placement="top"
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -20277,6 +20467,29 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
|
|
||||||
|
{userdata?.support === true ?
|
||||||
|
<Tooltip
|
||||||
|
color="secondary"
|
||||||
|
title="Show simplified YAML (support only)"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<Button
|
||||||
|
disabled={workflow.public}
|
||||||
|
color="secondary"
|
||||||
|
variant="text"
|
||||||
|
style={{ width: 65, height: buttonHeights, }}
|
||||||
|
onClick={() => {
|
||||||
|
setupWorkflowYaml(workflow)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DataObjectIcon />
|
||||||
|
</Button>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
: null}
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
|
||||||
|
|
||||||
@@ -20408,10 +20621,11 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
position,
|
position,
|
||||||
backgroundcolor: "#1f2023",
|
backgroundcolor: "#1f2023",
|
||||||
color: "#ffffff",
|
color: "#ffffff",
|
||||||
textHalign: "center",
|
|
||||||
textValign: "center",
|
textHalign: "right",
|
||||||
textMarginX: "0px",
|
textValign: "bottom",
|
||||||
textMarginY: "0px",
|
textMarginX: "-250px",
|
||||||
|
textMarginY: "-150px",
|
||||||
},
|
},
|
||||||
position,
|
position,
|
||||||
});
|
});
|
||||||
@@ -25509,6 +25723,7 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
|
|
||||||
|
|
||||||
setAiQueryModalOpen={setAiQueryModalOpen}
|
setAiQueryModalOpen={setAiQueryModalOpen}
|
||||||
|
isWorkflowEditor={editorData?.name === "workflow yaml"}
|
||||||
/>
|
/>
|
||||||
: null}
|
: null}
|
||||||
|
|
||||||
|
|||||||
@@ -745,7 +745,7 @@ const AppCreator = (defaultprops) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setBasedata(data);
|
setBasedata(data);
|
||||||
console.log("Info: ", data)
|
console.log("Loaded Info: ", data)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (data.info !== null && data.info !== undefined) {
|
if (data.info !== null && data.info !== undefined) {
|
||||||
@@ -759,7 +759,12 @@ const AppCreator = (defaultprops) => {
|
|||||||
if (data.info.title.length > 29) {
|
if (data.info.title.length > 29) {
|
||||||
setName(data.info.title.slice(0, 29));
|
setName(data.info.title.slice(0, 29));
|
||||||
} else {
|
} else {
|
||||||
setName(data.info.title);
|
// Check if fork=true in URL
|
||||||
|
if (window.location.search.includes("fork=true")) {
|
||||||
|
setName(data.info.title + " Fork");
|
||||||
|
} else {
|
||||||
|
setName(data.info.title)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4066,7 +4066,7 @@ const buttonBackground = "linear-gradient(to right, #f86a3e, #f34079)";
|
|||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
|
||||||
{isMobile ? null : (
|
{isMobile || app?.reference_org === userdata?.active_org?.id ? null : (
|
||||||
<Button
|
<Button
|
||||||
variant={userdata.active_apps !== undefined && userdata.active_apps !== null && userdata.active_apps.includes(appId) ? "outlined": "contained"}
|
variant={userdata.active_apps !== undefined && userdata.active_apps !== null && userdata.active_apps.includes(appId) ? "outlined": "contained"}
|
||||||
component="label"
|
component="label"
|
||||||
@@ -4142,7 +4142,7 @@ const buttonBackground = "linear-gradient(to right, #f86a3e, #f34079)";
|
|||||||
component="label"
|
component="label"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(`/apps/new?id=${appId}`)
|
navigate(`/apps/new?id=${appId}&fork=true`)
|
||||||
}}
|
}}
|
||||||
style={{ height: 40, marginTop: 5, marginLeft: 5, }}
|
style={{ height: 40, marginTop: 5, marginLeft: 5, }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl,
|
|||||||
whiteSpace: "nowrap",
|
whiteSpace: "nowrap",
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
}}>
|
}}>
|
||||||
{data.name.replace(/_/g, ' ').replace(/\b\w/g, char => char.toUpperCase())}
|
{data?.name?.replace(/_/g, ' ')?.replace(/\b\w/g, char => char?.toUpperCase())}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -165,7 +165,7 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl,
|
|||||||
marginLeft: 8,
|
marginLeft: 8,
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
}}>
|
}}>
|
||||||
{data.categories ? data.categories.join(", ") : "NA"}
|
{data?.categories ? data?.categories?.join(", ") : "NA"}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{
|
<div style={{
|
||||||
@@ -186,7 +186,7 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl,
|
|||||||
whiteSpace: "nowrap",
|
whiteSpace: "nowrap",
|
||||||
color: "rgba(158, 158, 158, 1)",
|
color: "rgba(158, 158, 158, 1)",
|
||||||
}}>
|
}}>
|
||||||
{data.generated !== true && data.tags && data.tags.slice(0, 2).map((tag, tagIndex) => (
|
{data?.generated !== true && data?.tags && data?.tags?.slice(0, 2).map((tag, tagIndex) => (
|
||||||
<span key={tagIndex}>
|
<span key={tagIndex}>
|
||||||
{tag}
|
{tag}
|
||||||
{tagIndex < data.tags.length - 1 ? ", " : ""}
|
{tagIndex < data.tags.length - 1 ? ", " : ""}
|
||||||
|
|||||||
@@ -447,6 +447,7 @@ const LoginPage = props => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isLoggedIn === true && serverside !== true) {
|
if (isLoggedIn === true && serverside !== true) {
|
||||||
|
setTimeout(() => {
|
||||||
const tmpView = new URLSearchParams(window.location.search).get("view");
|
const tmpView = new URLSearchParams(window.location.search).get("view");
|
||||||
if (tmpView !== undefined && tmpView !== null) {
|
if (tmpView !== undefined && tmpView !== null) {
|
||||||
let pathOnly = tmpView.split("?")[0];
|
let pathOnly = tmpView.split("?")[0];
|
||||||
@@ -455,12 +456,18 @@ const LoginPage = props => {
|
|||||||
if (pathOnly === "/pricing" || pathOnly === "admin") {
|
if (pathOnly === "/pricing" || pathOnly === "admin") {
|
||||||
window.location.replace(pathOnly + window.location.search);
|
window.location.replace(pathOnly + window.location.search);
|
||||||
} else {
|
} else {
|
||||||
|
if(localStorage.getItem("redirectId") !== null && localStorage.getItem("redirectId") !== undefined) {
|
||||||
|
const redirectId = localStorage.getItem("redirectId")
|
||||||
|
localStorage.removeItem("redirectId")
|
||||||
|
pathOnly = pathOnly + "/" + redirectId
|
||||||
|
}
|
||||||
window.location.replace(pathOnly);
|
window.location.replace(pathOnly);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.location.pathname = "/workflows"
|
window.location.pathname = "/workflows"
|
||||||
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkAdmin = () => {
|
const checkAdmin = () => {
|
||||||
@@ -548,12 +555,13 @@ const LoginPage = props => {
|
|||||||
|
|
||||||
setLoginLoading(false)
|
setLoginLoading(false)
|
||||||
|
|
||||||
console.log("Resp from backend: ", responseJson)
|
|
||||||
|
|
||||||
if (responseJson["success"] === false) {
|
if (responseJson["success"] === false) {
|
||||||
setLoginInfo(responseJson["reason"])
|
setLoginInfo(responseJson["reason"])
|
||||||
|
} else {
|
||||||
|
if (responseJson?.region_url !== undefined && responseJson?.region_url !== null && responseJson?.region_url !== "") {
|
||||||
|
toast.info("Set region to " + responseJson.region_url)
|
||||||
|
localStorage.setItem("globalUrl", responseJson.region_url)
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
|
|
||||||
if (responseJson["reason"] === "MFA_REDIRECT") {
|
if (responseJson["reason"] === "MFA_REDIRECT") {
|
||||||
setLoginInfo("Enter the 6-digit MFA code.")
|
setLoginInfo("Enter the 6-digit MFA code.")
|
||||||
@@ -569,7 +577,6 @@ const LoginPage = props => {
|
|||||||
//navigate(responseJson["url"])
|
//navigate(responseJson["url"])
|
||||||
window.location.href = responseJson["url"]
|
window.location.href = responseJson["url"]
|
||||||
return
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (responseJson["reason"] !== undefined && responseJson["reason"] !== null && responseJson["reason"].includes("error")) {
|
else if (responseJson["reason"] !== undefined && responseJson["reason"] !== null && responseJson["reason"].includes("error")) {
|
||||||
setLoginInfo(responseJson["reason"])
|
setLoginInfo(responseJson["reason"])
|
||||||
@@ -582,6 +589,7 @@ const LoginPage = props => {
|
|||||||
setCookie(responseJson["cookies"][key].key, responseJson["cookies"][key].value, { path: "/" })
|
setCookie(responseJson["cookies"][key].key, responseJson["cookies"][key].value, { path: "/" })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
const tmpView = new URLSearchParams(window.location.search).get("view");
|
const tmpView = new URLSearchParams(window.location.search).get("view");
|
||||||
if (tmpView !== undefined && tmpView !== null) {
|
if (tmpView !== undefined && tmpView !== null) {
|
||||||
let pathOnly = tmpView.split("?")[0];
|
let pathOnly = tmpView.split("?")[0];
|
||||||
@@ -590,18 +598,21 @@ const LoginPage = props => {
|
|||||||
if (pathOnly === "/pricing" || pathOnly === "admin") {
|
if (pathOnly === "/pricing" || pathOnly === "admin") {
|
||||||
window.location.replace(pathOnly + window.location.search);
|
window.location.replace(pathOnly + window.location.search);
|
||||||
} else {
|
} else {
|
||||||
|
if(localStorage.getItem("redirectId") !== null && localStorage.getItem("redirectId") !== undefined) {
|
||||||
|
const redirectId = localStorage.getItem("redirectId")
|
||||||
|
localStorage.removeItem("redirectId")
|
||||||
|
pathOnly = pathOnly + "/" + redirectId
|
||||||
|
}
|
||||||
window.location.replace(pathOnly);
|
window.location.replace(pathOnly);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("LOGIN DATA: ", responseJson)
|
|
||||||
if (responseJson.tutorials !== undefined && responseJson.tutorials !== null) {
|
if (responseJson.tutorials !== undefined && responseJson.tutorials !== null) {
|
||||||
// Find welcome in responseJson.tutorials under key name
|
// Find welcome in responseJson.tutorials under key name
|
||||||
const welcome = responseJson.tutorials.find(function (element) {
|
const welcome = responseJson.tutorials.find(function (element) {
|
||||||
return element.name === "welcome";
|
return element.name === "welcome";
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log("Welcome: ", welcome)
|
console.log("Welcome: ", welcome)
|
||||||
if (welcome === undefined || welcome === null) {
|
if (welcome === undefined || welcome === null) {
|
||||||
console.log("RUN login Welcome!!")
|
console.log("RUN login Welcome!!")
|
||||||
@@ -613,6 +624,7 @@ const LoginPage = props => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.location.pathname = "/workflows"
|
window.location.pathname = "/workflows"
|
||||||
|
}, 2000);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
@@ -641,11 +653,15 @@ const LoginPage = props => {
|
|||||||
//setLoginInfo("Successful register!")
|
//setLoginInfo("Successful register!")
|
||||||
//var newpath = "/login?message=Successfully signed up. You can now sign in."
|
//var newpath = "/login?message=Successfully signed up. You can now sign in."
|
||||||
//const tmpMessage = new URLSearchParams(window.location.search).get("message")
|
//const tmpMessage = new URLSearchParams(window.location.search).get("message")
|
||||||
setLoginInfo("Successful registration! Redirecting in 3 seconds...")
|
|
||||||
for (var key in responseJson["cookies"]) {
|
for (var key in responseJson["cookies"]) {
|
||||||
setCookie(responseJson["cookies"][key].key, responseJson["cookies"][key].value, { path: "/" })
|
setCookie(responseJson["cookies"][key].key, responseJson["cookies"][key].value, { path: "/" })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setLoginLoading(false)
|
||||||
|
setLoginInfo("Successful registration! Redirecting in 3 seconds...")
|
||||||
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.log("LOGIN DATA: ", responseJson)
|
console.log("LOGIN DATA: ", responseJson)
|
||||||
|
|
||||||
@@ -657,6 +673,11 @@ const LoginPage = props => {
|
|||||||
if (pathOnly === "/pricing" || pathOnly === "admin") {
|
if (pathOnly === "/pricing" || pathOnly === "admin") {
|
||||||
window.location.replace(pathOnly + window.location.search);
|
window.location.replace(pathOnly + window.location.search);
|
||||||
} else {
|
} else {
|
||||||
|
if(localStorage.getItem("redirectId") !== null && localStorage.getItem("redirectId") !== undefined) {
|
||||||
|
const redirectId = localStorage.getItem("redirectId")
|
||||||
|
localStorage.removeItem("redirectId")
|
||||||
|
pathOnly = pathOnly + "/" + redirectId
|
||||||
|
}
|
||||||
window.location.replace(pathOnly);
|
window.location.replace(pathOnly);
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -666,9 +687,8 @@ const LoginPage = props => {
|
|||||||
console.log("RUN Welcome!!")
|
console.log("RUN Welcome!!")
|
||||||
//window.location.pathname = "/welcome?tab=2"
|
//window.location.pathname = "/welcome?tab=2"
|
||||||
window.location.href = "/welcome"
|
window.location.href = "/welcome"
|
||||||
}, 1500);
|
}, 2000);
|
||||||
}
|
}
|
||||||
setLoginLoading(false)
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import React, { useEffect, useContext, memo, useState, useRef } from "react";
|
|||||||
import { useLocation, useNavigate, Link } from "react-router-dom";
|
import { useLocation, useNavigate, Link } from "react-router-dom";
|
||||||
import ReactDOM from "react-dom"
|
import ReactDOM from "react-dom"
|
||||||
import { getTheme } from "../theme.jsx";
|
import { getTheme } from "../theme.jsx";
|
||||||
|
|
||||||
// Material UI Icons
|
// Material UI Icons
|
||||||
import Add from '@mui/icons-material/Add';
|
import Add from '@mui/icons-material/Add';
|
||||||
import Search from '@mui/icons-material/Search';
|
import Search from '@mui/icons-material/Search';
|
||||||
@@ -12,6 +13,7 @@ import GridOnIcon from '@mui/icons-material/GridOn';
|
|||||||
import ListIcon from '@mui/icons-material/List';
|
import ListIcon from '@mui/icons-material/List';
|
||||||
import PublishIcon from '@mui/icons-material/Publish';
|
import PublishIcon from '@mui/icons-material/Publish';
|
||||||
import GetAppIcon from '@mui/icons-material/GetApp';
|
import GetAppIcon from '@mui/icons-material/GetApp';
|
||||||
|
|
||||||
// Material UI & Components
|
// Material UI & Components
|
||||||
import { makeStyles } from "@mui/styles";
|
import { makeStyles } from "@mui/styles";
|
||||||
import { Navigate } from "react-router-dom";
|
import { Navigate } from "react-router-dom";
|
||||||
@@ -92,6 +94,11 @@ import {
|
|||||||
Visibility as VisibilityIcon,
|
Visibility as VisibilityIcon,
|
||||||
EditNote as EditNoteIcon,
|
EditNote as EditNoteIcon,
|
||||||
ErrorOutline as ErrorOutlineIcon,
|
ErrorOutline as ErrorOutlineIcon,
|
||||||
|
Coronavirus as CoronavirusIcon,
|
||||||
|
Fingerprint as FingerprintIcon,
|
||||||
|
Psychology as PsychologyIcon,
|
||||||
|
Wifi as WifiIcon,
|
||||||
|
Devices as DevicesIcon,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
|
|
||||||
// Additional Components
|
// Additional Components
|
||||||
@@ -128,19 +135,20 @@ const imagesize = 22;
|
|||||||
export const GetIconInfo = (action) => {
|
export const GetIconInfo = (action) => {
|
||||||
// Finds the icon based on the action. Should be verbs.
|
// Finds the icon based on the action. Should be verbs.
|
||||||
const iconList = [
|
const iconList = [
|
||||||
{ key: "cases", values: ["cases"] },
|
{ key: "cases", values: ["cases", "ticket", "alert"] },
|
||||||
{ key: "cache_add", values: ["set_cache"] },
|
{ key: "cache_add", values: ["set_cache"] },
|
||||||
{ key: "cache_get", values: ["get_cache"] },
|
{ key: "cache_get", values: ["get_cache"] },
|
||||||
{ key: "filter", values: ["filter"] },
|
{ key: "filter", values: ["filter"] },
|
||||||
{ key: "merge", values: ["join", "merge", "route", "router"] },
|
{ key: "merge", values: ["join", "merge", "route", "router"] },
|
||||||
{
|
{
|
||||||
key: "search",
|
key: "search",
|
||||||
values: ["search", "find", "locate", "index", "analyze", "anal", "match", "check cache", "check", "verify", "validate"],
|
values: ["search", "find", "locate", "index", "analyze", "anal", "match", "check cache", "check", "verify", "validate", "siem", ],
|
||||||
},
|
},
|
||||||
{ key: "list", values: ["list", "head", "options"] },
|
{ key: "list", values: ["list", "head", "options"] },
|
||||||
{
|
{
|
||||||
key: "download",
|
key: "download",
|
||||||
values: [
|
values: [
|
||||||
|
"ingest",
|
||||||
"capture",
|
"capture",
|
||||||
"get",
|
"get",
|
||||||
"download",
|
"download",
|
||||||
@@ -154,20 +162,6 @@ export const GetIconInfo = (action) => {
|
|||||||
},
|
},
|
||||||
{ key: "add", values: ["add", "accept",] },
|
{ key: "add", values: ["add", "accept",] },
|
||||||
{ key: "delete", values: ["delete", "remove", "clear", "clean", "dismiss",] },
|
{ key: "delete", values: ["delete", "remove", "clear", "clean", "dismiss",] },
|
||||||
{
|
|
||||||
key: "send",
|
|
||||||
values: [
|
|
||||||
"send",
|
|
||||||
"dispatch",
|
|
||||||
"mail",
|
|
||||||
"forward",
|
|
||||||
"post",
|
|
||||||
"submit",
|
|
||||||
"mark",
|
|
||||||
"set",
|
|
||||||
"release",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: "repeat",
|
key: "repeat",
|
||||||
values: ["repeat", "retry", "pause", "skip", "copy", "replicat", "demo",],
|
values: ["repeat", "retry", "pause", "skip", "copy", "replicat", "demo",],
|
||||||
@@ -197,8 +191,29 @@ export const GetIconInfo = (action) => {
|
|||||||
key: "compare",
|
key: "compare",
|
||||||
values: ["compare", "convert", "to", "filter", "translate", "parse"],
|
values: ["compare", "convert", "to", "filter", "translate", "parse"],
|
||||||
},
|
},
|
||||||
|
{ key: "assets", values: ["cmdb", "assets", "asset", "cmdb", "inventory", "host", "hosts", "device", "devices"] },
|
||||||
{ key: "close", values: ["close", "stop", "cancel", "block"] },
|
{ key: "close", values: ["close", "stop", "cancel", "block"] },
|
||||||
{ key: "communication", values: ["communication", "comms", "email", "mail",] },
|
{ key: "communication", values: ["communication", "comms", "email", "mail",] },
|
||||||
|
{ key: "eradication", values: ["eradication", "edr", "xdr"] },
|
||||||
|
{ key: "iam", values: ["iam", "identity", "access", "auth", "authentication", "authorization", "oauth", "sso", "openid"] },
|
||||||
|
{ key: "intel", values: ["intel", "feed", "threat intel", "threat intelligence", "ti", "t.i.", "t.i", "ti.", "rule", "technique", "tactic", "techniques", "tactics", "ioc", "indicator",] },
|
||||||
|
{ key: "network", values: ["network", "net", "networking", "firewall", "proxy", "vpn", "sdwan", "sd-wan"] },
|
||||||
|
{
|
||||||
|
key: "send",
|
||||||
|
values: [
|
||||||
|
"send",
|
||||||
|
"dispatch",
|
||||||
|
"mail",
|
||||||
|
"forward",
|
||||||
|
"post",
|
||||||
|
"submit",
|
||||||
|
"mark",
|
||||||
|
"set",
|
||||||
|
"release",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
var selectedKey = ""
|
var selectedKey = ""
|
||||||
@@ -357,7 +372,45 @@ export const GetIconInfo = (action) => {
|
|||||||
iconBackgroundColor: "green",
|
iconBackgroundColor: "green",
|
||||||
originalIcon: <SearchIcon />,
|
originalIcon: <SearchIcon />,
|
||||||
},
|
},
|
||||||
};
|
eradication: {
|
||||||
|
icon: "",
|
||||||
|
iconColor: "white",
|
||||||
|
iconBackgroundColor: "green",
|
||||||
|
originalIcon: <CoronavirusIcon />,
|
||||||
|
},
|
||||||
|
iam: {
|
||||||
|
icon: "",
|
||||||
|
iconColor: "white",
|
||||||
|
iconBackgroundColor: "green",
|
||||||
|
originalIcon: <FingerprintIcon />,
|
||||||
|
},
|
||||||
|
intel: {
|
||||||
|
icon: "",
|
||||||
|
iconColor: "white",
|
||||||
|
iconBackgroundColor: "green",
|
||||||
|
originalIcon: <PsychologyIcon />,
|
||||||
|
},
|
||||||
|
network: {
|
||||||
|
icon: "",
|
||||||
|
iconColor: "white",
|
||||||
|
iconBackgroundColor: "green",
|
||||||
|
originalIcon: <WifiIcon />,
|
||||||
|
},
|
||||||
|
assets: {
|
||||||
|
icon: "",
|
||||||
|
iconColor: "white",
|
||||||
|
iconBackgroundColor: "green",
|
||||||
|
originalIcon: <DevicesIcon />,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
{ key: "eradication", values: ["eradication", "edr", "xdr"] },
|
||||||
|
{ key: "iam", values: ["iam", "identity", "access", "auth", "authentication", "authorization", "oauth", "sso", "openid"] },
|
||||||
|
{ key: "intel", values: ["intel", "threat intel", "threat intelligence", "ti", "t.i.", "t.i", "ti."] },
|
||||||
|
{ key: "network", values: ["network", "net", "networking", "firewall", "proxy", "vpn", "sdwan", "sd-wan"] },
|
||||||
|
{ key: "siem", values: ["siem", "security information and event management", "security information and event management", "security information and event management"] },
|
||||||
|
*/
|
||||||
|
|
||||||
var selectedItem = parsedIcons[selectedKey];
|
var selectedItem = parsedIcons[selectedKey];
|
||||||
if (selectedItem === undefined || selectedItem === null) {
|
if (selectedItem === undefined || selectedItem === null) {
|
||||||
@@ -374,11 +427,8 @@ export const GetIconInfo = (action) => {
|
|||||||
selectedItem.iconBackgroundColor = defaultColor;
|
selectedItem.iconBackgroundColor = defaultColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedItem.icon === "" || selectedItem.icon === undefined) {
|
if ((selectedItem.icon === "" || selectedItem.icon === undefined) && (selectedItem.originalIcon === undefined || selectedItem.originalIcon === "")) {
|
||||||
console.log(
|
console.log(`MISSING PATH FOR ${selectedKey} (find in scope): `, selectedItem.originalIcon.type.type)
|
||||||
`MISSING PATH FOR ${selectedKey} (find in scope): `,
|
|
||||||
selectedItem.originalIcon.type.type
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -753,6 +803,12 @@ const Workflows2 = (props) => {
|
|||||||
|
|
||||||
const handleTabChange = (event, newValue) => {
|
const handleTabChange = (event, newValue) => {
|
||||||
setCurrTab(newValue);
|
setCurrTab(newValue);
|
||||||
|
|
||||||
|
//if (view === "list" && currTab > 0) {
|
||||||
|
// setView("grid")
|
||||||
|
// setUpdate(Math.random())
|
||||||
|
//}
|
||||||
|
|
||||||
// Update URL query params based on tab index
|
// Update URL query params based on tab index
|
||||||
const tabMapping = {
|
const tabMapping = {
|
||||||
0: 'org_workflows',
|
0: 'org_workflows',
|
||||||
@@ -767,9 +823,6 @@ const Workflows2 = (props) => {
|
|||||||
navigate(`${location.pathname}?${queryParams.toString()}`);
|
navigate(`${location.pathname}?${queryParams.toString()}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleCreateWorkflow = () => {
|
const handleCreateWorkflow = () => {
|
||||||
setModalOpen(true)
|
setModalOpen(true)
|
||||||
setIsEditing(false)
|
setIsEditing(false)
|
||||||
@@ -1856,7 +1909,7 @@ const Workflows2 = (props) => {
|
|||||||
toast("Sanitizing and publishing " + data.name);
|
toast("Sanitizing and publishing " + data.name);
|
||||||
|
|
||||||
// This ALWAYS talks to Shuffle cloud
|
// This ALWAYS talks to Shuffle cloud
|
||||||
fetch(globalUrl + "/api/v1/workflows/" + data.id + "/publish", {
|
fetch(globalUrl + "/api/v1/workflows/" + data.id + "/publish" , {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -3414,6 +3467,8 @@ const Workflows2 = (props) => {
|
|||||||
return obj;
|
return obj;
|
||||||
})
|
})
|
||||||
|
|
||||||
|
console.log("ROWS: ", rows)
|
||||||
|
|
||||||
workflowData = (
|
workflowData = (
|
||||||
<DataGrid
|
<DataGrid
|
||||||
color="primary"
|
color="primary"
|
||||||
@@ -3851,6 +3906,8 @@ const Workflows2 = (props) => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
localStorage.setItem("view", "list");
|
localStorage.setItem("view", "list");
|
||||||
setView("list");
|
setView("list");
|
||||||
|
|
||||||
|
setCurrTab(0)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ListIcon />
|
<ListIcon />
|
||||||
@@ -4005,19 +4062,29 @@ const Workflows2 = (props) => {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currTab === 2) return;
|
if (filteredWorkflows.length > 0) {
|
||||||
if (userdata !== undefined && userdata !== null && filteredWorkflows.length > 0) {
|
|
||||||
var categoryWorkflows = []
|
var categoryWorkflows = []
|
||||||
if (currTab === 0) {
|
if (currTab === 0) {
|
||||||
categoryWorkflows = filteredWorkflows.filter(workflow => workflow?.org_id === userdata?.active_org?.id)
|
categoryWorkflows = filteredWorkflows.filter(workflow => workflow?.org_id === userdata?.active_org?.id)
|
||||||
|
|
||||||
|
if (categoryWorkflows.length === 0) {
|
||||||
|
setOrgWorkflows(filteredWorkflows)
|
||||||
|
} else {
|
||||||
setOrgWorkflows(categoryWorkflows)
|
setOrgWorkflows(categoryWorkflows)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else if (currTab === 1) {
|
else if (currTab === 1) {
|
||||||
categoryWorkflows = filteredWorkflows.filter(workflow => workflow?.org_id === userdata?.active_org?.id && workflow?.owner === userdata?.id)
|
categoryWorkflows = filteredWorkflows.filter(workflow => workflow?.org_id === userdata?.active_org?.id && workflow?.owner === userdata?.id)
|
||||||
setMyWorkflows(categoryWorkflows)
|
setMyWorkflows(categoryWorkflows)
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsLoadingWorkflow(false);
|
setIsLoadingWorkflow(false);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (currTab === 0 && workflows?.length > 0) {
|
||||||
|
setOrgWorkflows(workflows)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [currTab, workflows, userdata, filteredWorkflows, filters])
|
}, [currTab, workflows, userdata, filteredWorkflows, filters])
|
||||||
|
|
||||||
@@ -4607,13 +4674,17 @@ const Workflows2 = (props) => {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip title={view === "grid" ? "List view" : "Grid view"} placement="top">
|
<Tooltip title={view === "grid" ? "List view (Org Workflows only)" : "Grid view"} placement="top">
|
||||||
<IconButton
|
<IconButton
|
||||||
style={currTab === 2 ? iconButtonDisabledStyle : iconButtonStyle}
|
style={currTab === 2 ? iconButtonDisabledStyle : iconButtonStyle}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const newView = view === "grid" ? "list" : "grid";
|
const newView = view === "grid" ? "list" : "grid";
|
||||||
localStorage.setItem("workflowView", newView);
|
localStorage.setItem("workflowView", newView);
|
||||||
setView(newView);
|
setView(newView);
|
||||||
|
|
||||||
|
if (view === "grid") {
|
||||||
|
setCurrTab(0)
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
disabled={currTab === 2}
|
disabled={currTab === 2}
|
||||||
>
|
>
|
||||||
@@ -4699,7 +4770,12 @@ const Workflows2 = (props) => {
|
|||||||
paddingBottom: 40
|
paddingBottom: 40
|
||||||
}}>
|
}}>
|
||||||
|
|
||||||
{currTab === 0 && orgWorkflows.map((data, index) => {
|
{currTab !== 0 ? null :
|
||||||
|
orgWorkflows.length === 0 ?
|
||||||
|
<Typography variant="h6" style={{margin: "auto", minWidth: 500, }} >
|
||||||
|
No workflows found in this org with the Org ID filter. If this is an error, please click the "List View" button at the top to see ALL available workflows ({workflows.length}).
|
||||||
|
</Typography> :
|
||||||
|
orgWorkflows.map((data, index) => {
|
||||||
// Shouldn't be a part of this list
|
// Shouldn't be a part of this list
|
||||||
if (data.public === true) {
|
if (data.public === true) {
|
||||||
return null
|
return null
|
||||||
@@ -4742,7 +4818,12 @@ const Workflows2 = (props) => {
|
|||||||
})}
|
})}
|
||||||
|
|
||||||
{
|
{
|
||||||
currTab === 1 && myWorkflows.map((data, index) => {
|
currTab !== 1 ? null :
|
||||||
|
myWorkflows.length === 0 ?
|
||||||
|
<Typography variant="h6" style={{margin: "auto", width: 500, }} >
|
||||||
|
No workflows found in this org for your user.
|
||||||
|
</Typography> :
|
||||||
|
myWorkflows.map((data, index) => {
|
||||||
if (data.public === true) {
|
if (data.public === true) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -5123,7 +5204,7 @@ const Workflows2 = (props) => {
|
|||||||
|
|
||||||
//isLoaded && isLoggedIn && workflowDone ? (
|
//isLoaded && isLoggedIn && workflowDone ? (
|
||||||
const loadedCheck =
|
const loadedCheck =
|
||||||
workflowDone ? (
|
workflowDone && isLoaded ? (
|
||||||
<div>
|
<div>
|
||||||
{/*
|
{/*
|
||||||
<ShepherdTour steps={newSteps} tourOptions={tourOptions}>
|
<ShepherdTour steps={newSteps} tourOptions={tourOptions}>
|
||||||
|
|||||||
Reference in New Issue
Block a user