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/h2non/filetype v1.1.3
|
||||
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
|
||||
google.golang.org/api v0.236.0
|
||||
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/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/shuffle/shuffle-shared v0.8.82 h1:vwXG/NOZNoPMfbF14ZnygoY8+AI2QVsggWfgfpSbqzo=
|
||||
github.com/shuffle/shuffle-shared v0.8.82/go.mod h1:RdfNxqCPI+zU4jQKy3E/p4Io2injm7LpSKQUCDHNtLk=
|
||||
github.com/shuffle/shuffle-shared v0.8.84 h1:ElIMQYjKBVOiadbiGkSzt/lPU5xqaQwRxQvk9wx/xYM=
|
||||
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.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
frontend:
|
||||
image: ghcr.io/shuffle/shuffle-frontend:latest
|
||||
image: ghcr.io/shuffle/shuffle-frontend:nightly
|
||||
container_name: shuffle-frontend
|
||||
hostname: shuffle-frontend
|
||||
ports:
|
||||
@@ -14,7 +14,7 @@ services:
|
||||
depends_on:
|
||||
- backend
|
||||
backend:
|
||||
image: ghcr.io/shuffle/shuffle-backend:latest
|
||||
image: ghcr.io/shuffle/shuffle-backend:nightly
|
||||
container_name: shuffle-backend
|
||||
hostname: ${BACKEND_HOSTNAME}
|
||||
# Here for debugging:
|
||||
@@ -33,7 +33,7 @@ services:
|
||||
- SHUFFLE_FILE_LOCATION=/shuffle-files
|
||||
restart: unless-stopped
|
||||
orborus:
|
||||
image: ghcr.io/shuffle/shuffle-orborus:latest
|
||||
image: ghcr.io/shuffle/shuffle-orborus:nightly
|
||||
container_name: shuffle-orborus
|
||||
hostname: shuffle-orborus
|
||||
networks:
|
||||
@@ -55,13 +55,13 @@ services:
|
||||
- SHUFFLE_STATS_DISABLED=true
|
||||
- SHUFFLE_LOGS_DISABLED=true
|
||||
- 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
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
- seccomp:unconfined
|
||||
opensearch:
|
||||
image: opensearchproject/opensearch:2.19.1
|
||||
image: opensearchproject/opensearch:3.0.0
|
||||
hostname: shuffle-opensearch
|
||||
container_name: shuffle-opensearch
|
||||
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 { useNavigate, useLocation } from 'react-router-dom';
|
||||
import OrganizationTab from '../components/OrganizationTab.jsx';
|
||||
import PartnerTab from '../components/PartnerTab.jsx';
|
||||
import UserManagmentTab from '../components/UserManagmentTab.jsx';
|
||||
import CacheView from "../components/CacheView.jsx";
|
||||
import Files from "../components/Files.jsx";
|
||||
@@ -25,6 +26,24 @@ import { Index } from 'react-instantsearch-dom';
|
||||
import { Context } from '../context/ContextApi.jsx';
|
||||
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 location = useLocation();
|
||||
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 [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(() => {
|
||||
if (userdata && userdata?.active_org?.id?.length > 0) {
|
||||
@@ -119,7 +127,17 @@ const AdminNavBar = (props) => {
|
||||
useEffect(() => {
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
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") {
|
||||
setSelectedItem("Locations");
|
||||
} else if (tabName === "suborgs") {
|
||||
@@ -128,11 +146,24 @@ const AdminNavBar = (props) => {
|
||||
setSelectedItem("Datastore");
|
||||
}else if (tabName) {
|
||||
setSelectedItem(tabName.charAt(0).toUpperCase() + tabName.slice(1));
|
||||
} else {
|
||||
setSelectedItem("Organization");
|
||||
}else if (tabName === "partner") {
|
||||
setSelectedItem("Partner");
|
||||
}
|
||||
|
||||
}, [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) => {
|
||||
setSelectedItem(newValue);
|
||||
if (newValue === "App Auth") {
|
||||
@@ -208,16 +239,16 @@ const AdminNavBar = (props) => {
|
||||
const ComponentToRender = selectedItemData.component;
|
||||
const componentProps = selectedItemData.props;
|
||||
|
||||
const updatedProps = {
|
||||
...componentProps,
|
||||
notifications: notifications,
|
||||
setNotifications: setNotifications,
|
||||
userdata: userdata,
|
||||
selectedOrganization: selectedOrganization
|
||||
};
|
||||
const updatedProps = {
|
||||
...componentProps,
|
||||
notifications: notifications,
|
||||
setNotifications: setNotifications,
|
||||
userdata: userdata,
|
||||
selectedOrganization: selectedOrganization
|
||||
};
|
||||
|
||||
return <ComponentToRender {...updatedProps} />;
|
||||
};
|
||||
return <ComponentToRender {...updatedProps} />;
|
||||
};
|
||||
|
||||
const defaultImage = "/images/logos/orange_logo.svg"
|
||||
const imageData =
|
||||
@@ -296,7 +327,7 @@ const AdminNavBar = (props) => {
|
||||
disabled={
|
||||
((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)}
|
||||
>
|
||||
{item.text.replace(/_/g, " ")}
|
||||
@@ -308,7 +339,10 @@ const AdminNavBar = (props) => {
|
||||
|
||||
</nav>
|
||||
</div>
|
||||
<Wrapper2>{renderComponent()}</Wrapper2>
|
||||
|
||||
<Wrapper2>
|
||||
{renderComponent()}
|
||||
</Wrapper2>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
@@ -424,7 +458,13 @@ const Loader = () => {
|
||||
const PaddingWrapper2 = memo(({ children }) => {
|
||||
|
||||
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}
|
||||
</div>
|
||||
)
|
||||
|
||||
+1608
-591
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,
|
||||
},
|
||||
{ code: 'GA', label: 'Gabon', phone: '241' },
|
||||
{ code: 'GB', label: 'United Kingdom', phone: '44' },
|
||||
{ code: 'GD', label: 'Grenada', phone: '1-473' },
|
||||
{ code: 'GE', label: 'Georgia', phone: '995' },
|
||||
{ code: 'GF', label: 'French Guiana', phone: '594' },
|
||||
@@ -178,7 +177,6 @@ const countries = [
|
||||
{ code: 'IE', label: 'Ireland', phone: '353' },
|
||||
{ code: 'IL', label: 'Israel', phone: '972' },
|
||||
{ code: 'IM', label: 'Isle of Man', phone: '44' },
|
||||
{ code: 'IN', label: 'India', phone: '91' },
|
||||
{
|
||||
code: 'IO',
|
||||
label: 'British Indian Ocean Territory',
|
||||
@@ -390,12 +388,6 @@ const countries = [
|
||||
},
|
||||
{ code: 'UA', label: 'Ukraine', phone: '380' },
|
||||
{ code: 'UG', label: 'Uganda', phone: '256' },
|
||||
{
|
||||
code: 'US',
|
||||
label: 'United States',
|
||||
phone: '1',
|
||||
suggested: true,
|
||||
},
|
||||
{ code: 'UY', label: 'Uruguay', phone: '598' },
|
||||
{ code: 'UZ', label: 'Uzbekistan', phone: '998' },
|
||||
{
|
||||
|
||||
@@ -681,7 +681,7 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
|
||||
<Divider style={{ marginBottom: 10, }} />
|
||||
|
||||
<Typography color="textSecondary" align="center" style={{ marginTop: 5, marginBottom: 5, fontSize: 18 }}>
|
||||
Version: 2.1.0-rc1
|
||||
Version: 2.1.0-rc2
|
||||
</Typography>
|
||||
</Menu>
|
||||
</span>
|
||||
@@ -745,6 +745,8 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
|
||||
|
||||
localStorage.setItem("globalUrl", "");
|
||||
localStorage.setItem("getting_started_sidebar", "open");
|
||||
localStorage.removeItem("workflows");
|
||||
localStorage.removeItem("apps");
|
||||
|
||||
fetch(`${globalUrl}/api/v1/orgs/${orgId}/change`, {
|
||||
mode: "cors",
|
||||
@@ -913,7 +915,8 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
|
||||
"US": "us",
|
||||
"EU": "eu",
|
||||
"CA": "ca",
|
||||
"UK": "gb"
|
||||
"UK": "gb",
|
||||
"au": "au",
|
||||
};
|
||||
|
||||
region = regionMapping[region_url] || "eu";
|
||||
@@ -1465,7 +1468,7 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
|
||||
<Collapse in={openSecurityTab} timeout="auto" unmountOnExit >
|
||||
<Box
|
||||
style={{
|
||||
maxHeight: openSecurityTab && expandLeftNav ? 185 : 0,
|
||||
maxHeight: openSecurityTab && expandLeftNav ? 250 : 0,
|
||||
overflow: "hidden",
|
||||
transition: "max-height 0.3s ease, opacity 0.3s ease",
|
||||
display: "flex",
|
||||
@@ -1681,6 +1684,48 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
|
||||
</span>
|
||||
</Button>
|
||||
</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}
|
||||
</Box>
|
||||
</Collapse>
|
||||
|
||||
@@ -61,9 +61,9 @@ const menuData = {
|
||||
{
|
||||
title: "Singul",
|
||||
description:
|
||||
"Connect and run actions seamlessly between different platforms.",
|
||||
"Connect your favorite services with a singul line of code.",
|
||||
icon: "/images/logos/singul.svg",
|
||||
path: "https://singul-docs.gitbook.io/singul/getting-started",
|
||||
path: "https://singul.io",
|
||||
gaData: {
|
||||
category: "navbar",
|
||||
action: "products_click",
|
||||
@@ -662,9 +662,9 @@ const MobileMenu = ({ anchorEl, handleClose, isLoggedIn, navigate, isCloud }) =>
|
||||
action: "partners_click",
|
||||
label: "go_to_partners",
|
||||
})
|
||||
handleItemClick('/partners')
|
||||
handleItemClick('/become-partner')
|
||||
}else{
|
||||
window.open("https://shuffler.io/partners", '_blank');
|
||||
window.open("https://shuffler.io/become-partner", '_blank');
|
||||
return;
|
||||
}
|
||||
}}
|
||||
@@ -1391,26 +1391,151 @@ const Navbar = (props) => {
|
||||
>
|
||||
Pricing
|
||||
</Button>
|
||||
<Button
|
||||
component={Link}
|
||||
to="/partners"
|
||||
sx={buttonStyles}
|
||||
onClick={() => {
|
||||
if(isCloud) {
|
||||
navigate("/partners");
|
||||
ReactGA.event({
|
||||
category: "navbar",
|
||||
action: "partners_click",
|
||||
label: "go_to_partners",
|
||||
})
|
||||
} else {
|
||||
window.open("https://shuffler.io/partners", '_blank');
|
||||
return;
|
||||
}
|
||||
<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",
|
||||
},
|
||||
}}
|
||||
>
|
||||
Partners
|
||||
</Button>
|
||||
<Button
|
||||
component={Link}
|
||||
to="/become-partner"
|
||||
sx={{
|
||||
...buttonStyles,
|
||||
}}
|
||||
onClick={() => {
|
||||
if(isCloud) {
|
||||
navigate("/become-partner");
|
||||
ReactGA.event({
|
||||
category: "navbar",
|
||||
action: "partners_click",
|
||||
label: "go_to_become_partner",
|
||||
})
|
||||
} else {
|
||||
window.open("https://shuffler.io/become-partner", '_blank');
|
||||
return;
|
||||
}
|
||||
}}
|
||||
>
|
||||
Partners
|
||||
</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>
|
||||
);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { makeStyles, createStyles } from "@mui/styles";
|
||||
import {getTheme} from '../theme.jsx';
|
||||
|
||||
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 { sortByKey } from "../views/AngularWorkflow.jsx";
|
||||
import { NestedMenuItem } from "mui-nested-menu";
|
||||
@@ -825,7 +825,7 @@ const ParsedAction = (props) => {
|
||||
// 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 (valid.result[0].success !== false) {
|
||||
exampleData = valid.result[0]
|
||||
exampleData = valid.result
|
||||
break
|
||||
}
|
||||
} else {
|
||||
@@ -910,14 +910,48 @@ const ParsedAction = (props) => {
|
||||
if (newActionList?.length > 0) {
|
||||
let appParentActions = parentActionList?.map(action => "$" + action.name.toLowerCase());
|
||||
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) => {
|
||||
action = action.replace(" ", "_");
|
||||
if (paramvalue.includes(action)) {
|
||||
|
||||
// Check if the exact action is in the foundVariables list
|
||||
if (foundVariables.includes(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
|
||||
|
||||
console.log("App found: ", app.name, app.categories, appIndex, newactionname)
|
||||
|
||||
const paramIndex = selectedAction.parameters.findIndex((param) => param.name === "app_name")
|
||||
if (paramIndex > -1) {
|
||||
// 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(() => {
|
||||
prepareNotificationAppList()
|
||||
|
||||
if (notifications === undefined || notifications === null || notifications.length === 0) {
|
||||
getNotifications()
|
||||
}
|
||||
}, []);
|
||||
|
||||
let navigate = useNavigate();
|
||||
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
|
||||
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) => {
|
||||
// result is only getting: Jira AND discord.
|
||||
|
||||
@@ -146,7 +115,7 @@ const Priorities = memo((props) => {
|
||||
return updatedResult;
|
||||
};
|
||||
|
||||
const prepareNotificationAppList = async () => {
|
||||
const prepareNotificationAppList = () => {
|
||||
// getting App ID,Authentication fields and saved auths for each app
|
||||
var result = []
|
||||
fetch(globalUrl + "/api/v1/apps", {
|
||||
@@ -187,18 +156,17 @@ const Priorities = memo((props) => {
|
||||
return;
|
||||
}
|
||||
return response.json();
|
||||
}).then(async (responseJson) => {
|
||||
}).then((responseJson) => {
|
||||
if (!responseJson.success) {
|
||||
console.log("Could not get app auth")
|
||||
return;
|
||||
}
|
||||
// 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("result", result)
|
||||
|
||||
result.map(item => {
|
||||
console.log("Getting app config for: ", item.name);
|
||||
fetch(globalUrl + `/api/v1/apps/${item.id}/config`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
@@ -235,9 +203,9 @@ const Priorities = memo((props) => {
|
||||
setnotificationAppsDetails(newJSON);
|
||||
console.log("notificationAppsDetails: ", newJSON)
|
||||
}
|
||||
}).then(async () => {
|
||||
}).then(() => {
|
||||
// removing this for now,
|
||||
// await checkIfAlreadyGenerated(filteredApps, workflows);
|
||||
// checkIfAlreadyGenerated(filteredApps, workflows);
|
||||
|
||||
}).catch((error) => {
|
||||
console.log("Error getting app config: " + error);
|
||||
@@ -280,143 +248,6 @@ const Priorities = memo((props) => {
|
||||
console.log("Apps: ", 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) => {
|
||||
var appname = app.name
|
||||
var appImage = app.large_image
|
||||
@@ -984,12 +815,13 @@ print('"' + encoded + '"')
|
||||
<FormControl>
|
||||
<DialogTitle>
|
||||
<div style={{ color: "rgba(255,255,255,0.9)" }}>
|
||||
{`Configure ${selectedAppDetails.name} workflow`}
|
||||
{`Configure ${selectedAppDetails?.name?.replaceAll("_", " ")} workflow`}
|
||||
</div>
|
||||
</DialogTitle>
|
||||
<DialogContent style={{ color: "rgba(255,255,255,0.65)", overflowY: 'auto', maxHeight: '450px' }}>
|
||||
<Divider style={{ marginBottom: '1em', backgroundColor: 'rgba(255,255,255,0.12)' }} />
|
||||
{console.log("len Selected app details: ", selectedAppDetails)}
|
||||
|
||||
{(selectedAppDetails.authentication_data || (selectedAppDetails.auth_config && selectedAppDetails.auth_config.required == false) || (selectedAppDetails.authentication && selectedAppDetails.authentication.required == false)) ?
|
||||
<>
|
||||
<Box mb={2}>
|
||||
@@ -1152,13 +984,15 @@ print('"' + encoded + '"')
|
||||
|
||||
const renderChips = useCallback(() => {
|
||||
const appList = Object.values(notificationAppsDetails);
|
||||
|
||||
return (
|
||||
<Stack spacing={2}>
|
||||
<Stack direction="row" spacing={1}>
|
||||
|
||||
{appList.map((app) => (
|
||||
<Chip
|
||||
key={app.id}
|
||||
label={app.name}
|
||||
label={app?.name.replaceAll("_", " ")}
|
||||
variant={generatedWorkflow.app_name === app.name ? "filled" : "outlined"}
|
||||
disabled={generatedWorkflow.app_name === app.name ? true : false}
|
||||
onClick={() => {
|
||||
@@ -1176,6 +1010,7 @@ print('"' + encoded + '"')
|
||||
avatar={<img src={app.large_image} alt={app.name} style={{ width: 24, height: 24, borderRadius: '50%' }} />}
|
||||
/>
|
||||
))}
|
||||
|
||||
</Stack>
|
||||
|
||||
<Box sx={{
|
||||
@@ -1198,7 +1033,7 @@ print('"' + encoded + '"')
|
||||
>
|
||||
Set up app authentication
|
||||
</Link>
|
||||
to unlock additional workflow options.
|
||||
to show additional workflow options.
|
||||
</Typography>
|
||||
</Box>
|
||||
</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 = () => {
|
||||
// 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`, {
|
||||
credentials: "include",
|
||||
method: "GET",
|
||||
|
||||
@@ -64,6 +64,7 @@ import AceEditor from "react-ace";
|
||||
import ace from "ace-builds";
|
||||
import 'ace-builds/src-noconflict/mode-python';
|
||||
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-solarized_dark';
|
||||
import 'ace-builds/src-noconflict/theme-gruvbox';
|
||||
@@ -119,6 +120,7 @@ const CodeEditor = (props) => {
|
||||
handleActionParamChange,
|
||||
setcodedata,
|
||||
isFileEditor,
|
||||
isWorkflowEditor,
|
||||
runUpdateText,
|
||||
toolsAppId,
|
||||
parameterName,
|
||||
@@ -143,11 +145,6 @@ const CodeEditor = (props) => {
|
||||
|
||||
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 {themeMode, supportEmail} = useContext(Context)
|
||||
const theme = getTheme(themeMode)
|
||||
@@ -1529,13 +1526,15 @@ const CodeEditor = (props) => {
|
||||
// zIndex: 12501,
|
||||
pointerEvents: "auto",
|
||||
color: theme.palette.DialogStyle.color,
|
||||
minWidth: isMobile ? "100%" : isFileEditor ? "650px" : "80%",
|
||||
maxWidth: isMobile ? "100%" : isFileEditor ? "650px" : "1100px",
|
||||
minHeight: isMobile ? "100%" : "auto",
|
||||
maxHeight: isMobile ? "100%" : "700px",
|
||||
minWidth: isMobile || isWorkflowEditor ? "100%" : isFileEditor ? "650px" : "80%",
|
||||
maxWidth: isMobile || isWorkflowEditor ? "100%" : isFileEditor ? "650px" : "1100px",
|
||||
minHeight: isMobile || isWorkflowEditor ? "100%" : "auto",
|
||||
maxHeight: isMobile || isWorkflowEditor ? "100%" : "700px",
|
||||
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,
|
||||
|
||||
opacity: isWorkflowEditor ? 0.93 : 1,
|
||||
},
|
||||
}}
|
||||
>
|
||||
@@ -1719,7 +1718,7 @@ const CodeEditor = (props) => {
|
||||
Code Editor
|
||||
</DialogTitle>
|
||||
*/}
|
||||
{isFileEditor ? null :
|
||||
{isFileEditor || isWorkflowEditor ? null :
|
||||
<div style={{ display: "flex", maxHeight: 40, }}>
|
||||
<ButtonGroup style={{ borderRadius: theme.palette.borderRadius, }}>
|
||||
{userdata !== undefined && userdata !== null && userdata.support === true ?
|
||||
@@ -2190,15 +2189,15 @@ const CodeEditor = (props) => {
|
||||
console.log("DROP: ", e)
|
||||
}}
|
||||
>
|
||||
{(availableVariables !== undefined && availableVariables !== null && availableVariables.length > 0) || isFileEditor ? (
|
||||
{(availableVariables !== undefined && availableVariables !== null && availableVariables.length > 0) || isFileEditor || isWorkflowEditor ? (
|
||||
<AceEditor
|
||||
id="shuffle-codeeditor"
|
||||
name="shuffle-codeeditor"
|
||||
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"
|
||||
height={isFileEditor ? 450 : 550}
|
||||
width={isFileEditor ? 650 : "100%"}
|
||||
height={isFileEditor ? 450 : isWorkflowEditor ? "90vh" : 550}
|
||||
width={isFileEditor ? 650 : isWorkflowEditor ? "90vw" : "100%"}
|
||||
|
||||
markers={markers}
|
||||
highlightActiveLine={false}
|
||||
@@ -2259,7 +2258,7 @@ const CodeEditor = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isFileEditor ? null :
|
||||
{isFileEditor || isWorkflowEditor ? null :
|
||||
<div style={{
|
||||
flex: sourceDataOpen ? 1.5 : 3,
|
||||
marginLeft: 5,
|
||||
@@ -2524,7 +2523,7 @@ const CodeEditor = (props) => {
|
||||
</div>
|
||||
|
||||
|
||||
<div style={{ display: 'flex' }}>
|
||||
<div style={{ display: 'flex', width: isWorkflowEditor ? "90%" : "100%", }}>
|
||||
<Button
|
||||
style={{
|
||||
height: 35,
|
||||
@@ -2547,6 +2546,7 @@ const CodeEditor = (props) => {
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={isWorkflowEditor}
|
||||
style={{
|
||||
height: 35,
|
||||
flex: 1,
|
||||
@@ -2554,13 +2554,11 @@ const CodeEditor = (props) => {
|
||||
marginTop: 5,
|
||||
}}
|
||||
onClick={(event) => {
|
||||
/*
|
||||
const clickedFieldId = "rightside_field_" + fieldCount
|
||||
const clickedField = document.getElementById(clickedFieldId)
|
||||
if (clickedField !== undefined && clickedField !== null) {
|
||||
clickedField.focus()
|
||||
if (isWorkflowEditor === true) {
|
||||
setExpansionModalOpen(false)
|
||||
navigate("")
|
||||
return
|
||||
}
|
||||
*/
|
||||
|
||||
if (isFileEditor !== true) {
|
||||
navigate("")
|
||||
|
||||
@@ -21,6 +21,9 @@ import {
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
Skeleton,
|
||||
IconButton,
|
||||
Modal,
|
||||
Checkbox,
|
||||
} from "@mui/material";
|
||||
|
||||
import {
|
||||
@@ -32,6 +35,8 @@ import {
|
||||
Business as BusinessIcon,
|
||||
Flag,
|
||||
ArrowDropDown as ArrowDropDownIcon,
|
||||
VisibilityOff,
|
||||
Visibility,
|
||||
|
||||
} from "@mui/icons-material";
|
||||
|
||||
@@ -66,7 +71,8 @@ const TenantsTab = memo((props) => {
|
||||
const itemColor = "black";
|
||||
const { themeMode, supportEmail, brandColor } = useContext(Context);
|
||||
const theme = getTheme(themeMode, brandColor);
|
||||
|
||||
const [accountDeleteButtonClicked, setAccountDeleteButtonClicked] = useState(false);
|
||||
const [selectedSuborg, setSelectedSuborg] = useState(null);
|
||||
useEffect(() => {
|
||||
if(parentOrg !== null && parentOrgFlag === null) {
|
||||
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 = (
|
||||
<Dialog
|
||||
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,}}>
|
||||
{modalView}
|
||||
{cloudSyncModal}
|
||||
{accountDeleteButtonClicked && <DeleteAccountPopUp/>}
|
||||
<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={{ marginBottom: 20 }}>
|
||||
@@ -1340,7 +1562,8 @@ const TenantsTab = memo((props) => {
|
||||
|
||||
<ListItemText
|
||||
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
|
||||
color="primary"
|
||||
variant='outlined'
|
||||
@@ -1359,7 +1582,34 @@ const TenantsTab = memo((props) => {
|
||||
>
|
||||
Change Active Org
|
||||
</Button>
|
||||
|
||||
</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" }}
|
||||
/>
|
||||
|
||||
@@ -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") {
|
||||
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";
|
||||
const userRegion = data?.user_geo_info?.country?.iso_code
|
||||
|
||||
return (
|
||||
<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",
|
||||
cardHoverColor: themeMode === "dark" ? "#323232" : "#F0F0F0",
|
||||
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",
|
||||
defaultBorder: themeMode === "dark" ? '1px solid #494949' : '1px solid #CCCCCC',
|
||||
linkColor: brandColor === "#ff8544" ? "#f86a3e" : brandColor,
|
||||
|
||||
@@ -287,8 +287,8 @@ const Admin2 = (props) => {
|
||||
|
||||
|
||||
const handleStatusChange = (event) => {
|
||||
const { value } = event.target;
|
||||
setSelectedStatus(value);
|
||||
const { value } = event.target
|
||||
setSelectedStatus(value)
|
||||
|
||||
handleEditOrg(
|
||||
selectedOrganization?.name,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { getTheme } from "../theme.jsx";
|
||||
import { useInterval } from "react-powerhooks";
|
||||
import { makeStyles, } from "@mui/styles";
|
||||
|
||||
import YAML from "yaml";
|
||||
import WorkflowTemplatePopup from "../components/WorkflowTemplatePopup.jsx"
|
||||
import { v4 as uuidv4, v5 as uuidv5, validate as isUUID, } from "uuid";
|
||||
import { useNavigate, Link, useParams } from "react-router-dom";
|
||||
@@ -132,6 +133,7 @@ import {
|
||||
ArrowForward as ArrowForwardIcon,
|
||||
OpenInFull as OpenInFullIcon,
|
||||
Difference as DifferenceIcon,
|
||||
DataObject as DataObjectIcon,
|
||||
} from "@mui/icons-material";
|
||||
import SwapHorizIcon from '@mui/icons-material/SwapHoriz';
|
||||
//import * as cytoscape from "cytoscape";
|
||||
@@ -778,9 +780,9 @@ const AngularWorkflow = (defaultprops) => {
|
||||
"name": "action",
|
||||
"value": "list_tickets",
|
||||
"options": [
|
||||
"create_ticket",
|
||||
"list_tickets",
|
||||
"get_ticket",
|
||||
"create_ticket",
|
||||
"close_ticket",
|
||||
"add_comment",
|
||||
"update_ticket",
|
||||
@@ -794,16 +796,6 @@ const AngularWorkflow = (defaultprops) => {
|
||||
"required": false,
|
||||
"multiline": true,
|
||||
},
|
||||
/*{
|
||||
"name": "options",
|
||||
"value": "deduplicate,enrich",
|
||||
"required": false,
|
||||
"multiselect": true,
|
||||
"options": [
|
||||
"deduplicate",
|
||||
"enrich",
|
||||
]
|
||||
}*/
|
||||
]
|
||||
}, {
|
||||
"name": "Communication",
|
||||
@@ -813,9 +805,9 @@ const AngularWorkflow = (defaultprops) => {
|
||||
"name": "action",
|
||||
"value": "list_messages",
|
||||
"options": [
|
||||
"send_message",
|
||||
"list_messages",
|
||||
"get_message",
|
||||
"send_message",
|
||||
"search_messages",
|
||||
"list_attachments",
|
||||
"get_attachment",
|
||||
@@ -838,15 +830,15 @@ const AngularWorkflow = (defaultprops) => {
|
||||
"parameters": [{
|
||||
"name": "action",
|
||||
"value": "get_asset",
|
||||
"options": [
|
||||
"reset_password",
|
||||
"enable_user",
|
||||
"disable_user",
|
||||
"get_identity",
|
||||
"get_asset",
|
||||
"search_identity"
|
||||
],
|
||||
"required": true,
|
||||
"options": [
|
||||
"reset_password",
|
||||
"enable_user",
|
||||
"disable_user",
|
||||
"get_identity",
|
||||
"get_asset",
|
||||
"search_identity"
|
||||
],
|
||||
"required": true,
|
||||
},
|
||||
{
|
||||
"name": "fields",
|
||||
@@ -861,9 +853,9 @@ const AngularWorkflow = (defaultprops) => {
|
||||
"label": "Assets",
|
||||
"parameters": [{
|
||||
"name": "action",
|
||||
"value": "search_vulnerabilities",
|
||||
"value": "list_assets",
|
||||
"options": [
|
||||
"list_assets",
|
||||
"list_assets",
|
||||
"get_asset",
|
||||
"search_assets",
|
||||
"search_users",
|
||||
@@ -915,8 +907,8 @@ const AngularWorkflow = (defaultprops) => {
|
||||
"value": "get_ioc",
|
||||
"options": [
|
||||
"get_ioc",
|
||||
"search_ioc",
|
||||
"create_ioc",
|
||||
"search_ioc",
|
||||
"update_ioc",
|
||||
"delete_ioc"
|
||||
],
|
||||
@@ -975,28 +967,9 @@ const AngularWorkflow = (defaultprops) => {
|
||||
"multiline": true,
|
||||
}]
|
||||
},
|
||||
|
||||
|
||||
]
|
||||
}]
|
||||
|
||||
/*
|
||||
{
|
||||
"name": "Email",
|
||||
"label": "Email",
|
||||
"parameters": [{
|
||||
"name": "action",
|
||||
"value": "list_email",
|
||||
"options": [
|
||||
"list_email",
|
||||
"send_mail",
|
||||
],
|
||||
"required": true,
|
||||
}],
|
||||
}]
|
||||
}]
|
||||
*/
|
||||
|
||||
// For code editor
|
||||
const [codeEditorModalOpen, setCodeEditorModalOpen] = React.useState(false);
|
||||
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) {
|
||||
getChildWorkflows(workflow.id)
|
||||
}
|
||||
|
||||
}, [workflow]);
|
||||
|
||||
// Event for making sure app is correct
|
||||
@@ -2506,9 +2480,11 @@ const AngularWorkflow = (defaultprops) => {
|
||||
console.log("Should redirect to register with redirect.")
|
||||
|
||||
setTimeout(() => {
|
||||
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`
|
||||
window.location.href = `/workflows`
|
||||
// toast("You may not have access to this workflow.")
|
||||
localStorage.setItem("redirectId", props.match.params.key)
|
||||
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)
|
||||
|
||||
return
|
||||
@@ -2878,9 +2854,9 @@ const AngularWorkflow = (defaultprops) => {
|
||||
setSavingState(0);
|
||||
console.log("Workflow failed loading: ", responseJson);
|
||||
if (responseJson.reason !== undefined && responseJson.reason !== null) {
|
||||
toast("Failed to save: " + responseJson.reason);
|
||||
toast.error("Failed to save: " + responseJson.reason);
|
||||
} 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 {
|
||||
setSavingState(1);
|
||||
@@ -4590,6 +4566,9 @@ const AngularWorkflow = (defaultprops) => {
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if(!isLoggedIn){
|
||||
localStorage.setItem("redirectId", props.match.params.key);
|
||||
}
|
||||
window.location.pathname = "/workflows";
|
||||
}, 2500);
|
||||
|
||||
@@ -4880,6 +4859,17 @@ const AngularWorkflow = (defaultprops) => {
|
||||
setOriginalWorkflow(responseJson)
|
||||
}
|
||||
|
||||
const tmpUi = new URLSearchParams(cursearch).get("ui");
|
||||
if (
|
||||
tmpUi !== undefined &&
|
||||
tmpUi !== null &&
|
||||
tmpUi === "yaml"
|
||||
) {
|
||||
setTimeout(() => {
|
||||
setupWorkflowYaml(responseJson)
|
||||
}, 2500)
|
||||
}
|
||||
|
||||
setWorkflow(responseJson);
|
||||
setWorkflowDone(true);
|
||||
|
||||
@@ -4898,6 +4888,9 @@ const AngularWorkflow = (defaultprops) => {
|
||||
|
||||
setConfigureWorkflowModalOpen(true)
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
})
|
||||
.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
|
||||
//useEffect(() => {
|
||||
if (firstrequest) {
|
||||
@@ -10000,6 +10178,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
//navigate(`?execution_highlight=${parsed_url}`)
|
||||
//props.history.push(curpath + newitem);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -12136,10 +12315,8 @@ const AngularWorkflow = (defaultprops) => {
|
||||
}
|
||||
|
||||
if ((app.id === "integration" || app.id === "shuffle_agent") && userdata.support !== true) {
|
||||
console.log("APPID: ", app.id, isCloud)
|
||||
if (isCloud === false && app.id === "integration") {
|
||||
} else {
|
||||
console.log("RETURNING", app.id)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -12572,7 +12749,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
// maxiter = max amount of parent nodes to loop
|
||||
// also handles breaks if there are issues
|
||||
var iterations = 0;
|
||||
var maxiter = 10;
|
||||
var maxiter = 100;
|
||||
while (true) {
|
||||
for (let parentkey in allkeys) {
|
||||
var currentnode = cy.getElementById(allkeys[parentkey]);
|
||||
@@ -18553,6 +18730,68 @@ const AngularWorkflow = (defaultprops) => {
|
||||
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 [hovered, setHovered] = useState(false)
|
||||
|
||||
@@ -18620,72 +18859,15 @@ const AngularWorkflow = (defaultprops) => {
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{!distributedFromParent ?
|
||||
isCorrectOrg ? null :
|
||||
{!distributedFromParent || userdata?.support === true ?
|
||||
isCorrectOrg ? null :
|
||||
<Typography variant="body2" style={{ marginLeft: 10, }}>
|
||||
<b>Warning</b>: <span
|
||||
style={{ color: "#FF8544", cursor: "pointer", pointerEvents: "auto", }}
|
||||
onClick={() => {
|
||||
toast("Changing to correct organisation. Please wait a few seconds.")
|
||||
|
||||
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("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: "/"})
|
||||
})
|
||||
changeOrg()
|
||||
|
||||
|
||||
|
||||
@@ -18707,6 +18889,11 @@ const AngularWorkflow = (defaultprops) => {
|
||||
<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 ?
|
||||
|
||||
null
|
||||
|
||||
/*
|
||||
* Disabled because redirects occurred anyway
|
||||
|
||||
<Button
|
||||
color="secondary"
|
||||
variant="outlined"
|
||||
@@ -18717,17 +18904,20 @@ const AngularWorkflow = (defaultprops) => {
|
||||
marginLeft: 10,
|
||||
}}
|
||||
onClick={() => {
|
||||
navigate(`/workflows/${workflow.parentorg_workflow}`)
|
||||
//changeOrg()
|
||||
|
||||
// Reload the page
|
||||
navigate(`/workflows/${workflow.parentorg_workflow}`)
|
||||
window.location.reload()
|
||||
}}
|
||||
>
|
||||
Go to parent org workflow
|
||||
</Button>
|
||||
*/
|
||||
|
||||
: 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
|
||||
color="secondary"
|
||||
variant="outlined"
|
||||
@@ -19575,7 +19765,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
>
|
||||
|
||||
<Tooltip
|
||||
title="Hide error messages. They will show up the next refresh."
|
||||
title="Hides error messages this session."
|
||||
placement="top"
|
||||
>
|
||||
<IconButton
|
||||
@@ -19924,7 +20114,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
.catch((error) => {
|
||||
console.log("Dupe workflow for suborg error: ", error.toString())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const BottomCytoscapeBar = () => {
|
||||
if (workflow.id === undefined || workflow.id === null || (!workflow.public && apps.length === 0)) {
|
||||
@@ -20277,6 +20467,29 @@ const AngularWorkflow = (defaultprops) => {
|
||||
</Button>
|
||||
</span>
|
||||
</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>
|
||||
|
||||
|
||||
@@ -20408,10 +20621,11 @@ const AngularWorkflow = (defaultprops) => {
|
||||
position,
|
||||
backgroundcolor: "#1f2023",
|
||||
color: "#ffffff",
|
||||
textHalign: "center",
|
||||
textValign: "center",
|
||||
textMarginX: "0px",
|
||||
textMarginY: "0px",
|
||||
|
||||
textHalign: "right",
|
||||
textValign: "bottom",
|
||||
textMarginX: "-250px",
|
||||
textMarginY: "-150px",
|
||||
},
|
||||
position,
|
||||
});
|
||||
@@ -25509,6 +25723,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
|
||||
|
||||
setAiQueryModalOpen={setAiQueryModalOpen}
|
||||
isWorkflowEditor={editorData?.name === "workflow yaml"}
|
||||
/>
|
||||
: null}
|
||||
|
||||
|
||||
@@ -745,7 +745,7 @@ const AppCreator = (defaultprops) => {
|
||||
}
|
||||
|
||||
setBasedata(data);
|
||||
console.log("Info: ", data)
|
||||
console.log("Loaded Info: ", data)
|
||||
|
||||
try {
|
||||
if (data.info !== null && data.info !== undefined) {
|
||||
@@ -759,7 +759,12 @@ const AppCreator = (defaultprops) => {
|
||||
if (data.info.title.length > 29) {
|
||||
setName(data.info.title.slice(0, 29));
|
||||
} 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>
|
||||
}
|
||||
|
||||
{isMobile ? null : (
|
||||
{isMobile || app?.reference_org === userdata?.active_org?.id ? null : (
|
||||
<Button
|
||||
variant={userdata.active_apps !== undefined && userdata.active_apps !== null && userdata.active_apps.includes(appId) ? "outlined": "contained"}
|
||||
component="label"
|
||||
@@ -4142,7 +4142,7 @@ const buttonBackground = "linear-gradient(to right, #f86a3e, #f34079)";
|
||||
component="label"
|
||||
color="secondary"
|
||||
onClick={() => {
|
||||
navigate(`/apps/new?id=${appId}`)
|
||||
navigate(`/apps/new?id=${appId}&fork=true`)
|
||||
}}
|
||||
style={{ height: 40, marginTop: 5, marginLeft: 5, }}
|
||||
>
|
||||
|
||||
@@ -154,7 +154,7 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl,
|
||||
whiteSpace: "nowrap",
|
||||
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>
|
||||
@@ -165,7 +165,7 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl,
|
||||
marginLeft: 8,
|
||||
color: theme.palette.text.secondary,
|
||||
}}>
|
||||
{data.categories ? data.categories.join(", ") : "NA"}
|
||||
{data?.categories ? data?.categories?.join(", ") : "NA"}
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
@@ -186,7 +186,7 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl,
|
||||
whiteSpace: "nowrap",
|
||||
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}>
|
||||
{tag}
|
||||
{tagIndex < data.tags.length - 1 ? ", " : ""}
|
||||
|
||||
@@ -447,20 +447,27 @@ const LoginPage = props => {
|
||||
}
|
||||
|
||||
if (isLoggedIn === true && serverside !== true) {
|
||||
const tmpView = new URLSearchParams(window.location.search).get("view");
|
||||
if (tmpView !== undefined && tmpView !== null) {
|
||||
let pathOnly = tmpView.split("?")[0];
|
||||
if (!pathOnly.startsWith("/")) pathOnly = "/" + pathOnly;
|
||||
setTimeout(() => {
|
||||
const tmpView = new URLSearchParams(window.location.search).get("view");
|
||||
if (tmpView !== undefined && tmpView !== null) {
|
||||
let pathOnly = tmpView.split("?")[0];
|
||||
if (!pathOnly.startsWith("/")) pathOnly = "/" + pathOnly;
|
||||
|
||||
if (pathOnly === "/pricing" || pathOnly === "admin") {
|
||||
window.location.replace(pathOnly + window.location.search);
|
||||
} else {
|
||||
window.location.replace(pathOnly);
|
||||
if (pathOnly === "/pricing" || pathOnly === "admin") {
|
||||
window.location.replace(pathOnly + window.location.search);
|
||||
} 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);
|
||||
}
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.pathname = "/workflows"
|
||||
window.location.pathname = "/workflows"
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
const checkAdmin = () => {
|
||||
@@ -537,51 +544,52 @@ const LoginPage = props => {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for login:O!");
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for login:O!");
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
|
||||
setLoginLoading(false)
|
||||
|
||||
if (responseJson["success"] === false) {
|
||||
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)
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (responseJson["reason"] === "MFA_REDIRECT") {
|
||||
setLoginInfo("Enter the 6-digit MFA code.")
|
||||
setMFAField(true)
|
||||
return
|
||||
|
||||
setLoginLoading(false)
|
||||
|
||||
console.log("Resp from backend: ", responseJson)
|
||||
|
||||
if (responseJson["success"] === false) {
|
||||
}
|
||||
else if (responseJson["reason"] === "MFA_SETUP") {
|
||||
window.location.href = `/login/${responseJson.url}/mfa-setup`;
|
||||
return;
|
||||
}
|
||||
else if (responseJson["reason"] === "SSO_REDIRECT") {
|
||||
//navigate(responseJson["url"])
|
||||
window.location.href = responseJson["url"]
|
||||
return
|
||||
}
|
||||
else if (responseJson["reason"] !== undefined && responseJson["reason"] !== null && responseJson["reason"].includes("error")) {
|
||||
setLoginInfo(responseJson["reason"])
|
||||
return
|
||||
}
|
||||
else {
|
||||
|
||||
if (responseJson["reason"] === "MFA_REDIRECT") {
|
||||
setLoginInfo("Enter the 6-digit MFA code.")
|
||||
setMFAField(true)
|
||||
return
|
||||
|
||||
}
|
||||
else if (responseJson["reason"] === "MFA_SETUP") {
|
||||
window.location.href = `/login/${responseJson.url}/mfa-setup`;
|
||||
return;
|
||||
}
|
||||
else if (responseJson["reason"] === "SSO_REDIRECT") {
|
||||
//navigate(responseJson["url"])
|
||||
window.location.href = responseJson["url"]
|
||||
return
|
||||
|
||||
}
|
||||
else if (responseJson["reason"] !== undefined && responseJson["reason"] !== null && responseJson["reason"].includes("error")) {
|
||||
setLoginInfo(responseJson["reason"])
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
setLoginInfo("Successful login! Redirecting you in 3 seconds...")
|
||||
for (var key in responseJson["cookies"]) {
|
||||
setCookie(responseJson["cookies"][key].key, responseJson["cookies"][key].value, { path: "/" })
|
||||
}
|
||||
setLoginInfo("Successful login! Redirecting you in 3 seconds...")
|
||||
for (var key in responseJson["cookies"]) {
|
||||
setCookie(responseJson["cookies"][key].key, responseJson["cookies"][key].value, { path: "/" })
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
const tmpView = new URLSearchParams(window.location.search).get("view");
|
||||
if (tmpView !== undefined && tmpView !== null) {
|
||||
let pathOnly = tmpView.split("?")[0];
|
||||
@@ -590,18 +598,21 @@ const LoginPage = props => {
|
||||
if (pathOnly === "/pricing" || pathOnly === "admin") {
|
||||
window.location.replace(pathOnly + window.location.search);
|
||||
} 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);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("LOGIN DATA: ", responseJson)
|
||||
if (responseJson.tutorials !== undefined && responseJson.tutorials !== null) {
|
||||
// Find welcome in responseJson.tutorials under key name
|
||||
const welcome = responseJson.tutorials.find(function (element) {
|
||||
return element.name === "welcome";
|
||||
})
|
||||
|
||||
console.log("Welcome: ", welcome)
|
||||
if (welcome === undefined || welcome === null) {
|
||||
console.log("RUN login Welcome!!")
|
||||
@@ -613,12 +624,13 @@ const LoginPage = props => {
|
||||
}
|
||||
|
||||
window.location.pathname = "/workflows"
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
setLoginInfo("Error from login API: " + error)
|
||||
setLoginLoading(false)
|
||||
});
|
||||
}, 2000);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
setLoginInfo("Error from login API: " + error)
|
||||
setLoginLoading(false)
|
||||
});
|
||||
} else {
|
||||
url = baseurl + '/api/v1/register';
|
||||
fetch(url, {
|
||||
@@ -641,11 +653,15 @@ const LoginPage = props => {
|
||||
//setLoginInfo("Successful register!")
|
||||
//var newpath = "/login?message=Successfully signed up. You can now sign in."
|
||||
//const tmpMessage = new URLSearchParams(window.location.search).get("message")
|
||||
setLoginInfo("Successful registration! Redirecting in 3 seconds...")
|
||||
|
||||
for (var key in responseJson["cookies"]) {
|
||||
setCookie(responseJson["cookies"][key].key, responseJson["cookies"][key].value, { path: "/" })
|
||||
}
|
||||
|
||||
setLoginLoading(false)
|
||||
setLoginInfo("Successful registration! Redirecting in 3 seconds...")
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
console.log("LOGIN DATA: ", responseJson)
|
||||
|
||||
@@ -657,6 +673,11 @@ const LoginPage = props => {
|
||||
if (pathOnly === "/pricing" || pathOnly === "admin") {
|
||||
window.location.replace(pathOnly + window.location.search);
|
||||
} 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);
|
||||
}
|
||||
return
|
||||
@@ -666,9 +687,8 @@ const LoginPage = props => {
|
||||
console.log("RUN Welcome!!")
|
||||
//window.location.pathname = "/welcome?tab=2"
|
||||
window.location.href = "/welcome"
|
||||
}, 1500);
|
||||
}, 2000);
|
||||
}
|
||||
setLoginLoading(false)
|
||||
}),
|
||||
)
|
||||
.catch(error => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import React, { useEffect, useContext, memo, useState, useRef } from "react";
|
||||
import { useLocation, useNavigate, Link } from "react-router-dom";
|
||||
import ReactDOM from "react-dom"
|
||||
import { getTheme } from "../theme.jsx";
|
||||
|
||||
// Material UI Icons
|
||||
import Add from '@mui/icons-material/Add';
|
||||
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 PublishIcon from '@mui/icons-material/Publish';
|
||||
import GetAppIcon from '@mui/icons-material/GetApp';
|
||||
|
||||
// Material UI & Components
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { Navigate } from "react-router-dom";
|
||||
@@ -92,6 +94,11 @@ import {
|
||||
Visibility as VisibilityIcon,
|
||||
EditNote as EditNoteIcon,
|
||||
ErrorOutline as ErrorOutlineIcon,
|
||||
Coronavirus as CoronavirusIcon,
|
||||
Fingerprint as FingerprintIcon,
|
||||
Psychology as PsychologyIcon,
|
||||
Wifi as WifiIcon,
|
||||
Devices as DevicesIcon,
|
||||
} from "@mui/icons-material";
|
||||
|
||||
// Additional Components
|
||||
@@ -128,19 +135,20 @@ const imagesize = 22;
|
||||
export const GetIconInfo = (action) => {
|
||||
// Finds the icon based on the action. Should be verbs.
|
||||
const iconList = [
|
||||
{ key: "cases", values: ["cases"] },
|
||||
{ key: "cases", values: ["cases", "ticket", "alert"] },
|
||||
{ key: "cache_add", values: ["set_cache"] },
|
||||
{ key: "cache_get", values: ["get_cache"] },
|
||||
{ key: "filter", values: ["filter"] },
|
||||
{ key: "merge", values: ["join", "merge", "route", "router"] },
|
||||
{
|
||||
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: "download",
|
||||
values: [
|
||||
"ingest",
|
||||
"capture",
|
||||
"get",
|
||||
"download",
|
||||
@@ -154,20 +162,6 @@ export const GetIconInfo = (action) => {
|
||||
},
|
||||
{ key: "add", values: ["add", "accept",] },
|
||||
{ key: "delete", values: ["delete", "remove", "clear", "clean", "dismiss",] },
|
||||
{
|
||||
key: "send",
|
||||
values: [
|
||||
"send",
|
||||
"dispatch",
|
||||
"mail",
|
||||
"forward",
|
||||
"post",
|
||||
"submit",
|
||||
"mark",
|
||||
"set",
|
||||
"release",
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "repeat",
|
||||
values: ["repeat", "retry", "pause", "skip", "copy", "replicat", "demo",],
|
||||
@@ -197,8 +191,29 @@ export const GetIconInfo = (action) => {
|
||||
key: "compare",
|
||||
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: "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 = ""
|
||||
@@ -357,7 +372,45 @@ export const GetIconInfo = (action) => {
|
||||
iconBackgroundColor: "green",
|
||||
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];
|
||||
if (selectedItem === undefined || selectedItem === null) {
|
||||
@@ -374,11 +427,8 @@ export const GetIconInfo = (action) => {
|
||||
selectedItem.iconBackgroundColor = defaultColor;
|
||||
}
|
||||
|
||||
if (selectedItem.icon === "" || selectedItem.icon === undefined) {
|
||||
console.log(
|
||||
`MISSING PATH FOR ${selectedKey} (find in scope): `,
|
||||
selectedItem.originalIcon.type.type
|
||||
);
|
||||
if ((selectedItem.icon === "" || selectedItem.icon === undefined) && (selectedItem.originalIcon === undefined || selectedItem.originalIcon === "")) {
|
||||
console.log(`MISSING PATH FOR ${selectedKey} (find in scope): `, selectedItem.originalIcon.type.type)
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -753,6 +803,12 @@ const Workflows2 = (props) => {
|
||||
|
||||
const handleTabChange = (event, newValue) => {
|
||||
setCurrTab(newValue);
|
||||
|
||||
//if (view === "list" && currTab > 0) {
|
||||
// setView("grid")
|
||||
// setUpdate(Math.random())
|
||||
//}
|
||||
|
||||
// Update URL query params based on tab index
|
||||
const tabMapping = {
|
||||
0: 'org_workflows',
|
||||
@@ -767,9 +823,6 @@ const Workflows2 = (props) => {
|
||||
navigate(`${location.pathname}?${queryParams.toString()}`);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
const handleCreateWorkflow = () => {
|
||||
setModalOpen(true)
|
||||
setIsEditing(false)
|
||||
@@ -1854,9 +1907,9 @@ const Workflows2 = (props) => {
|
||||
data = JSON.parse(JSON.stringify(data));
|
||||
data = sanitizeWorkflow(data);
|
||||
toast("Sanitizing and publishing " + data.name);
|
||||
|
||||
|
||||
// This ALWAYS talks to Shuffle cloud
|
||||
fetch(globalUrl + "/api/v1/workflows/" + data.id + "/publish", {
|
||||
fetch(globalUrl + "/api/v1/workflows/" + data.id + "/publish" , {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -3414,6 +3467,8 @@ const Workflows2 = (props) => {
|
||||
return obj;
|
||||
})
|
||||
|
||||
console.log("ROWS: ", rows)
|
||||
|
||||
workflowData = (
|
||||
<DataGrid
|
||||
color="primary"
|
||||
@@ -3851,6 +3906,8 @@ const Workflows2 = (props) => {
|
||||
onClick={() => {
|
||||
localStorage.setItem("view", "list");
|
||||
setView("list");
|
||||
|
||||
setCurrTab(0)
|
||||
}}
|
||||
>
|
||||
<ListIcon />
|
||||
@@ -4005,20 +4062,30 @@ const Workflows2 = (props) => {
|
||||
// }
|
||||
|
||||
useEffect(() => {
|
||||
if (currTab === 2) return;
|
||||
if (userdata !== undefined && userdata !== null && filteredWorkflows.length > 0) {
|
||||
if (filteredWorkflows.length > 0) {
|
||||
|
||||
var categoryWorkflows = []
|
||||
if (currTab === 0) {
|
||||
categoryWorkflows = filteredWorkflows.filter(workflow => workflow?.org_id === userdata?.active_org?.id)
|
||||
setOrgWorkflows(categoryWorkflows)
|
||||
|
||||
if (categoryWorkflows.length === 0) {
|
||||
setOrgWorkflows(filteredWorkflows)
|
||||
} else {
|
||||
setOrgWorkflows(categoryWorkflows)
|
||||
}
|
||||
}
|
||||
else if (currTab === 1) {
|
||||
categoryWorkflows = filteredWorkflows.filter(workflow => workflow?.org_id === userdata?.active_org?.id && workflow?.owner === userdata?.id)
|
||||
setMyWorkflows(categoryWorkflows)
|
||||
}
|
||||
|
||||
setIsLoadingWorkflow(false);
|
||||
|
||||
}
|
||||
} else {
|
||||
if (currTab === 0 && workflows?.length > 0) {
|
||||
setOrgWorkflows(workflows)
|
||||
}
|
||||
}
|
||||
}, [currTab, workflows, userdata, filteredWorkflows, filters])
|
||||
|
||||
|
||||
@@ -4607,13 +4674,17 @@ const Workflows2 = (props) => {
|
||||
</IconButton>
|
||||
</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
|
||||
style={currTab === 2 ? iconButtonDisabledStyle : iconButtonStyle}
|
||||
onClick={() => {
|
||||
const newView = view === "grid" ? "list" : "grid";
|
||||
localStorage.setItem("workflowView", newView);
|
||||
setView(newView);
|
||||
|
||||
if (view === "grid") {
|
||||
setCurrTab(0)
|
||||
}
|
||||
}}
|
||||
disabled={currTab === 2}
|
||||
>
|
||||
@@ -4699,25 +4770,30 @@ const Workflows2 = (props) => {
|
||||
paddingBottom: 40
|
||||
}}>
|
||||
|
||||
{currTab === 0 && orgWorkflows.map((data, index) => {
|
||||
// Shouldn't be a part of this list
|
||||
if (data.public === true) {
|
||||
return null
|
||||
}
|
||||
{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
|
||||
if (data.public === true) {
|
||||
return null
|
||||
}
|
||||
|
||||
// if (firstLoad) {
|
||||
// workflowDelay += 75
|
||||
// } else {
|
||||
// return <WorkflowPaper key={index} data={data} />
|
||||
// }
|
||||
// if (firstLoad) {
|
||||
// workflowDelay += 75
|
||||
// } else {
|
||||
// return <WorkflowPaper key={index} data={data} />
|
||||
// }
|
||||
|
||||
return (
|
||||
<span key={index}>
|
||||
{/*<Zoom key={index} in={true} style={{ transitionDelay: `${workflowDelay}ms` }}>*/}
|
||||
<WorkflowPaper data={data} />
|
||||
{/*</Zoom>*/}
|
||||
</span>
|
||||
)
|
||||
return (
|
||||
<span key={index}>
|
||||
{/*<Zoom key={index} in={true} style={{ transitionDelay: `${workflowDelay}ms` }}>*/}
|
||||
<WorkflowPaper data={data} />
|
||||
{/*</Zoom>*/}
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
|
||||
{currTab === 3 && backupWorkflows.map((data, index) => {
|
||||
@@ -4742,24 +4818,29 @@ const Workflows2 = (props) => {
|
||||
})}
|
||||
|
||||
{
|
||||
currTab === 1 && myWorkflows.map((data, index) => {
|
||||
if (data.public === true) {
|
||||
return null
|
||||
}
|
||||
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) {
|
||||
return null
|
||||
}
|
||||
|
||||
// if (firstLoad) {
|
||||
// workflowDelay += 75
|
||||
// } else {
|
||||
// return <WorkflowPaper key={index} data={data} />
|
||||
// }
|
||||
// if (firstLoad) {
|
||||
// workflowDelay += 75
|
||||
// } else {
|
||||
// return <WorkflowPaper key={index} data={data} />
|
||||
// }
|
||||
|
||||
return (
|
||||
<span key={index}>
|
||||
{/*<Zoom key={index} in={true} style={{ transitionDelay: `${workflowDelay}ms` }}>*/}
|
||||
<WorkflowPaper data={data} />
|
||||
{/*</Zoom>*/}
|
||||
</span>
|
||||
)
|
||||
return (
|
||||
<span key={index}>
|
||||
{/*<Zoom key={index} in={true} style={{ transitionDelay: `${workflowDelay}ms` }}>*/}
|
||||
<WorkflowPaper data={data} />
|
||||
{/*</Zoom>*/}
|
||||
</span>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
@@ -5123,7 +5204,7 @@ const Workflows2 = (props) => {
|
||||
|
||||
//isLoaded && isLoggedIn && workflowDone ? (
|
||||
const loadedCheck =
|
||||
workflowDone ? (
|
||||
workflowDone && isLoaded ? (
|
||||
<div>
|
||||
{/*
|
||||
<ShepherdTour steps={newSteps} tourOptions={tourOptions}>
|
||||
|
||||
Reference in New Issue
Block a user