import React, { useEffect, useRef, useState, useContext, useCallback, useMemo, memo } from "react";
import {
ExpandLess as ExpandLessIcon,
ExpandMore as ExpandMoreIcon,
GridView as GridViewIcon,
ShieldOutlined as ShieldOutlinedIcon,
Add as AddIcon,
BorderColor,
Close as CloseIcon,
ConstructionOutlined as ConstructionOutlinedIcon,
Toc as TocIcon,
Settings as SettingsIcon
} from "@mui/icons-material";
import SearchBox from "./SearchData.jsx";
import {
Button,
Typography,
Autocomplete,
TextField,
Popper,
Avatar,
Menu,
IconButton,
MenuItem,
Divider,
Box,
Dialog,
DialogContent,
Fade,
Portal,
Collapse,
} from "@mui/material";
import theme from "../theme.jsx";
import RecentWorkflow from "../components/RecentWorkflow.jsx";
import { useNavigate } from "react-router";
import { Link } from "react-router-dom";
import {
Business as BusinessIcon,
Notifications as NotificationsIcon,
HelpOutline as HelpOutlineIcon,
MeetingRoom as MeetingRoomIcon,
Lightbulb as LightbulbIcon,
Search as SearchIcon
} from "@mui/icons-material";
import { toast } from "react-toastify";
import { Context } from "../context/ContextApi.jsx";
const ShuffleLogo = "/images/Shuffle_logo.png";
const detectionIcon = "/icons/detection.svg";
const documentationIcon = "/icons/documentation.svg";
const ExpandMoreAndLessIcon = "/icons/expandMoreIcon.svg";
const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
const navigate = useNavigate();
const {setLeftSideBarOpenByClick, leftSideBarOpenByClick, setSearchBarModalOpen, searchBarModalOpen} = useContext(Context);
const [expandLeftNav, setExpandLeftNav] = useState(false);
const [activeOrgName, setActiveOrgName] = useState(
userdata?.active_org?.name || "Select Organziation"
);
const [openAutocomplete, setOpenAutocomplete] = useState(false);
const [autocompleteValue, setAutocompleteValue] = useState("");
const [openautomatetab, setOpenautomateTab] = useState(false);
const [openSecurityTab, setOpenSecurityTab] = useState(false);
const [selectedOrg, setSelectedOrg] = useState(activeOrgName);
const [anchorElAvatar, setAnchorElAvatar] = React.useState(null);
const [anchorEl, setAnchorEl] = React.useState(null);
const [recentworkflows, setRecentworkflows] = useState(null);
const [usersWorkFlows, setUsersWorkFlows] = useState([]);
const [currentOpenTab, setCurrentOpenTab] = useState("");
const currentPath = window.location.pathname;
const [hoverOnAvatar, setHoverOnAvatar] = useState(false);
const [orgOptions, setOrgOptions] = useState(
userdata?.orgs?.map((org) => ({
id: org.id,
name: org.name,
image: org.image,
region_url: org.region_url,
})) || []
);
const userOrgs = React.useMemo(() => {
return orgOptions.find((option) => option.name === selectedOrg);
}, [selectedOrg, orgOptions]);
//With this code it is opening search bar on google chrome search bar as well which is not required
useEffect(() => {
const handleKeyDown = (event) => {
if ((event.ctrlKey || event.metaKey) && event.key === "k") {
event.preventDefault();
setSearchBarModalOpen((prev)=> !prev);
}
};
window.addEventListener("keydown", handleKeyDown);
return () => {
window.removeEventListener("keydown", handleKeyDown);
};
}, [setSearchBarModalOpen]);
const CustomPopper = (props) => {
return (
{props.children}
{
setSelectedOrg(userdata?.active_org?.name);
setOpenAutocomplete(false)
}}
>
Add suborg
);
};
const iconRef = useRef(null);
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const parsedAvatar =
userdata?.avatar !== undefined &&
userdata?.avatar !== null &&
userdata?.avatar.length > 0
? userdata?.avatar
: "";
const autocompleteRef = useRef(null);
const hoverOutColor = "#e8eaf6";
const handleClose = () => {
setAnchorEl(null);
setAnchorElAvatar(null);
};
const hrefStyle = {
color: hoverOutColor,
textDecoration: "none",
textTransform: "none",
fontStyle: "normal",
width: "100%",
fontSize: "16px",
};
const isCloud =
serverside === true || typeof window === "undefined"
? true
: window.location.host === "localhost:3002" ||
window.location.host === "shuffler.io" ||
window.location.host === "localhost:5002";
const UpdateTabStatus = useCallback(() => {
const lastTabOpenByUser = localStorage.getItem("lastTabOpenByUser");
if ((lastTabOpenByUser === "automate" && currentPath.includes("/dashboards/automate")) || currentPath.includes("/dashboards/automate")) {
setOpenautomateTab(true);
setCurrentOpenTab("automate");
setOpenSecurityTab(false);
} else if ((lastTabOpenByUser === "security" && currentPath.includes("/dashboards/security")) || currentPath.includes("/dashboards/security")) {
setOpenautomateTab(false);
setOpenSecurityTab(true);
setCurrentOpenTab("security");
} else if ((lastTabOpenByUser === "usecases" && currentPath.includes("/usecases")) || currentPath.includes("/usecases")) {
setOpenautomateTab(true);
setOpenSecurityTab(false);
setCurrentOpenTab("usecases");
} else if ((lastTabOpenByUser === "workflows" && currentPath === "/workflows") || currentPath === "/workflows") {
setOpenautomateTab(true);
setOpenSecurityTab(false);
setCurrentOpenTab("workflows");
} else if ((lastTabOpenByUser === "apps" && currentPath.includes("/apps")) || currentPath.includes("/apps")) {
setOpenautomateTab(true);
setOpenSecurityTab(false);
setCurrentOpenTab("apps");
} else if ((lastTabOpenByUser === "detection" && currentPath.includes("/detections")) || currentPath.includes("/detections")) {
setOpenautomateTab(false);
setOpenSecurityTab(true);
setCurrentOpenTab("detection");
} else if ((lastTabOpenByUser === "response" && currentPath.includes("/response")) || currentPath.includes("/response")) {
setOpenautomateTab(false);
setOpenSecurityTab(true);
setCurrentOpenTab("response");
} else if ((lastTabOpenByUser === "docs" || currentPath.includes("/docs")) || currentPath.includes("/docs")) {
setOpenautomateTab(false);
setOpenSecurityTab(false);
setCurrentOpenTab("docs");
} else {
setOpenautomateTab(true);
setOpenSecurityTab(false);
setCurrentOpenTab("");
}
},[currentPath]);
useEffect(() => {
UpdateTabStatus()
const expandLeftNav1 = localStorage.getItem("expandLeftNav")
if (expandLeftNav1 === "false") {
setLeftSideBarOpenByClick(false)
} else {
const currentLocation = window?.location?.pathname
if (currentLocation?.includes('/workflows/')) {
} else {
setLeftSideBarOpenByClick(true)
setExpandLeftNav(true)
}
}
}, [])
const getAvailableWorkflows = useCallback((amount) => {
var url = `${globalUrl}/api/v1/workflows`
if (amount !== undefined && amount !== null) {
url += `?top=${amount}`
}
fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
toast("Failed getting workflows. Are you logged in?");
return
}
return response.json();
})
.then((responseJson) => {
if (responseJson !== undefined) {
var newarray = []
for (var wfkey in responseJson) {
const wf = responseJson[wfkey]
if (wf.public === true || wf.hidden === true) {
continue
}
newarray.push(wf)
}
var actionnamelist = [];
var parsedactionlist = [];
for (var key in newarray) {
for (var actionkey in newarray[key].actions) {
const action = newarray[key].actions[actionkey];
if (actionnamelist.includes(action.app_name)) {
continue;
}
actionnamelist.push(action.app_name);
parsedactionlist.push(action);
}
}
if (newarray.length > 0) {
try {
localStorage.setItem("workflows", JSON.stringify(newarray))
} catch (e) {
console.log("Failed to set workflows in localstorage: ", e)
}
}
setTimeout(() => {
setUsersWorkFlows(newarray);
if (newarray.length === 0) {
setUsersWorkFlows([]);
}
}, 250)
}
})
.catch((error) => {
toast(error.toString());
});
}, [usersWorkFlows]);
useEffect(() => {
const fetchData = async () => {
if (recentworkflows === null || recentworkflows === undefined || recentworkflows?.length === 0) {
await delay(2000);
}
const storagewf = localStorage.getItem("workflows");
const storageWorkflows = JSON.parse(storagewf);
if (storageWorkflows) {
setUsersWorkFlows(storageWorkflows);
} else {
getAvailableWorkflows();
}
};
fetchData();
}, []);
useEffect(() => {
if (usersWorkFlows) {
const recentWorkflow = HandleGetUsersRecentWorkflows(usersWorkFlows);
setRecentworkflows(recentWorkflow);
}
}, [usersWorkFlows]);
const removeCookie = (name, path = "/") => {
document.cookie = `${name}=; path=${path}; expires=Thu, 01 Jan 1970 00:00:00 GMT;`;
};
const handleClickLogout = () => {
// Logout API call
fetch(`${globalUrl}/api/v1/logout`, {
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json",
},
})
.then((response) => {
if (response.status !== 200) {
toast.error("Failed to logout. Please try again.");
return;
}
// Remove cookies
removeCookie("session_token", { path: "/" });
removeCookie("__session", { path: "/" });
// Clear localStorage
localStorage.clear();
// Redirect to home immediately
window.location.href = "/";
})
.catch((error) => {
console.error("Logout error:", error);
});
};
const avatarMenu = (
{
setAnchorElAvatar(event.currentTarget);
}}
disableRipple
disableElevation
>
);
const getWorkflowAppgroup = (data) => {
if (!data.actions) {
return [];
}
let appsFound = [];
Object.keys(data.actions).forEach((key) => {
const parsedAction = data.actions[key];
if (!parsedAction.large_image) {
return;
}
if (
appsFound.findIndex((app) => app.app_name === parsedAction.app_name) < 0
) {
appsFound.push(parsedAction);
}
});
return appsFound;
};
const HandleGetUsersRecentWorkflows = useCallback(() => {
if (!usersWorkFlows) {
return [];
}
let groupedWorkflows = [];
usersWorkFlows.forEach((workflow) => {
const apps = getWorkflowAppgroup(workflow);
if (apps.length > 0) {
groupedWorkflows.push({
name: workflow.name,
id: workflow.id,
apps: apps,
});
}
});
return groupedWorkflows;
},[usersWorkFlows]);
const handleClickChangeOrg = (orgId) => {
toast.info("Changing active organization - please wait!");
const data = {
org_id: orgId,
};
if (userdata?.active_org?.id === orgId) {
return
}
localStorage.setItem("globalUrl", "");
localStorage.setItem("getting_started_sidebar", "open");
fetch(`${globalUrl}/api/v1/orgs/${orgId}/change`, {
mode: "cors",
credentials: "include",
crossDomain: true,
method: "POST",
body: JSON.stringify(data),
withCredentials: true,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
})
.then(function (response) {
if (response.status !== 200) {
console.log("Error in response");
} else {
localStorage.removeItem("apps");
localStorage.removeItem("workflows");
localStorage.removeItem("userinfo");
localStorage.removeItem("lastTabOpenByUser");
}
return response.json();
})
.then(function (responseJson) {
if (responseJson.success === true) {
if (
responseJson?.region_url !== undefined &&
responseJson?.region_url !== null &&
responseJson?.region_url.length > 0
) {
localStorage.setItem("globalUrl", responseJson.region_url);
}
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.success("Successfully changed active organization - 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 support@shuffler.io if this persists."
);
}
}
})
.catch((error) => {
console.log("error changing: ", error);
});
};
const getRegionTag = (region_url) => {
let regiontag = "UK";
//let regiontag = "EU";
if (
region_url !== undefined &&
region_url !== null &&
region_url.length > 0
) {
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";
} else if (regiontag === "frankfurt") {
regiontag = "EU-2";
} else if (regiontag === "ca"){
regiontag = "CA";
}
}
}
return regiontag;
};
useEffect(() => {
if(activeOrgName !== userdata?.active_org?.name){
setActiveOrgName(userdata?.active_org?.name || "Select Organization");
}
}, [userdata]);
const CheckOrgStates = useCallback(() => {
setOrgOptions(
userdata?.orgs?.map((org) => {
let skipOrg = false;
if (
org.creator_org !== undefined &&
org.creator_org !== null &&
org.creator_org.length > 0
) {
// Finds the parent org
for (let key in userdata.child_orgs) {
if (userdata.child_orgs[key].id === org.creator_org) {
skipOrg = true;
break;
}
}
if (skipOrg) {
return null; // Skip this org
}
}
return {
id: org.id,
name: org.name,
image: org.image,
region_url: getRegionTag(org.region_url),
margin_left:
org.creator_org !== undefined &&
org.creator_org !== null &&
org.creator_org.length > 0
? org.id === userdata.active_org.id
? 0
: 20
: 0,
};
}) || []
);
setActiveOrgName(userdata?.active_org?.name || "Select Organization");
setSelectedOrg(userdata?.active_org?.name || "Select Organization");
}, [userdata]);
useEffect(() => {
if (typeof userdata?.id === "string" && userdata?.id?.length > 0) {
CheckOrgStates();
setAutocompleteValue(userdata?.active_org?.name || "");
}
}, []);
const ButtonStyle = {
width: expandLeftNav ? "100%" : 30,
justifyContent: expandLeftNav ? "flex-start" : "center",
height: 35,
color: openautomatetab ? "#F1F1F1" : "#C8C8C8",
textTransform: "none",
"& .MuiButton-root ": {
width: expandLeftNav ? "100%" : 30,
padding: 0,
},
fontSize: 18
};
const getRegionFlag = (region_url) => {
var region = "UK";
const regionMapping = {
"US": "us",
"EU": "eu",
"CA": "ca",
"UK": "gb"
};
region = regionMapping[region_url] || "eu";
return `https://flagcdn.com/48x36/${region}.png`;
};
useEffect(() => {
if (window?.location?.pathname?.includes("/workflows/")) {
setExpandLeftNav(false);
}
}, [window?.location?.pathname]);
return (
{searchBarModalOpen ?
: null}
{
if(window?.location?.pathname?.includes("/workflows/")) {
setExpandLeftNav(true)
}
}}
onMouseLeave={()=>{
if(window?.location?.pathname?.includes("/workflows/")) {
setExpandLeftNav(false)
}
}}
>
{(!leftSideBarOpenByClick || window?.location?.pathname?.includes("/workflows/")) && setExpandLeftNav(true)}} onMouseLeave={()=>{(!leftSideBarOpenByClick || window?.location?.pathname?.includes("/workflows/")) && setExpandLeftNav(false);setOpenAutocomplete(false)}}>
{expandLeftNav ? (
),
endAdornment: (
Ctrl+K
),
disableUnderline: true,
}}
onClick={() => {
setSearchBarModalOpen(true);
}}
onChange={() => {
setSearchBarModalOpen(true);
}}
/>
):(
<>
>)}
{
setOpenautomateTab((prev) => !prev);
setOpenSecurityTab(false);
}}
style={{
color: "#FFFFFF",
marginLeft: 0.625,
}}
onMouseOver={(event)=>{
event.currentTarget.style.backgroundColor = "#2f2f2f";
}}
onMouseOut={(event)=>{
event.currentTarget.style.backgroundColor = "transparent";
}}
>
{openautomatetab ? (
) : (
)}
{
setOpenSecurityTab((prev) => !prev);
setOpenautomateTab(false);
}}
style={{
marginLeft: 0.625,
color: "#FFFFFF",
}}
onMouseOver={(event)=>{
event.currentTarget.style.backgroundColor = "#2f2f2f";
}}
onMouseOut={(event)=>{
event.currentTarget.style.backgroundColor = "transparent";
}}
>
{openSecurityTab ? (
) : (
)}
{recentworkflows?.length > 0 ?
Recent Workflows
{recentworkflows?.slice(0, 2).map((workflow, index) => {
return (
)
}) }
: null }
orgOptions, [orgOptions])}
getOptionLabel={(option) => option.name}
PopperComponent={CustomPopper}
open={openAutocomplete}
onOpen={() => setOpenAutocomplete(true)}
renderOption={(props, option, { selected, index }) => (
{
e.currentTarget.style.backgroundColor = "#444444";
}}
onMouseOut={(e) => {
e.currentTarget.style.backgroundColor = option.name === selectedOrg ? "#696969" : "transparent";
}}
onClick={(e) => {
if (option.id !== userdata?.active_org?.id) {
setSelectedOrg(option.name);
handleClickChangeOrg(option.id);
} else {
setSelectedOrg(userdata?.active_org?.name);
}
setOpenAutocomplete(false);
}}
>
{
isCloud ? (
{option.region_url}
) : null
}
{option.name}
)}
onChange={(event, newValue) => {
if (newValue) {
if (userdata?.active_org?.id !== newValue.id) {
setSelectedOrg(newValue.name);
handleClickChangeOrg(newValue.id);
} else {
setSelectedOrg(userdata?.active_org?.name);
}
setOpenAutocomplete(false);
}
}}
onInputChange={(event, newInputValue) => {
setAutocompleteValue(newInputValue);
}}
filterOptions={(options, params) => {
const normalize = (str) => str.toLowerCase().replace(/[\s-]+/g, "");
const input = normalize(params.inputValue);
return options.filter((option) =>
normalize(option.name).includes(input) ||
normalize(option.region_url).includes(input)
);
}}
value={userOrgs}
renderInput={(params) => (
{expandLeftNav ? (
) : (
setOpenAutocomplete((prev) => !prev)}
style={{
cursor: "pointer",
fontSize: "24px",
opacity: expandLeftNav ? 0 : 1,
transition: "opacity 0.3s ease",
}}
/>
)}
)}
/>
{event.currentTarget.style.backgroundColor = "#2f2f2f";setHoverOnAvatar(true)}}
onMouseLeave ={(event)=>{event.currentTarget.style.backgroundColor = "transparent";setHoverOnAvatar(false)}}
>
{expandLeftNav ? (
<>
>
) : (
<>
>
)}
);
};
export default LeftSideBar;
const ModalView = memo(({searchBarModalOpen, setSearchBarModalOpen, globalUrl, serverside, userdata}) => {
return (
(
)
)
});