import React, { useEffect, useRef, useState, useContext, useCallback, useMemo } from "react"; import { ExpandLess as ExpandLessIcon, ExpandMore as ExpandMoreIcon, GridView as GridViewIcon, ShieldOutlined as ShieldOutlinedIcon, Add as AddIcon, BorderColor, Close as CloseIcon, ConstructionOutlined, } 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 { Settings as SettingsIcon } from "@mui/icons-material"; 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("/search")) || currentPath.includes("/search")) { 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 > { handleClose(); }} style={{fontSize: 18}} > Organization { handleClose(); }} style={{fontSize: 18}} > Account { handleClose(); }} style={{fontSize: 18}} > Notifications ({ notifications === undefined || notifications === null ? 0 : notifications?.filter((notification) => notification.read === false).length }) { handleClose(); }} style={{fontSize: 18}} > Use Cases { handleClose(); }} style={{fontSize: 18}} > About { handleClickLogout(); event.preventDefault(); handleClose(); }} >  Logout Version: 2.0.0-rc2 ); 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) => ({ id: org.id, name: org.name, image: org.image, region_url: getRegionTag(org.region_url), })) || [] ); setActiveOrgName(userdata?.active_org?.name || "Select Organization"); setSelectedOrg(userdata?.active_org?.name || "Select Organization"); },[orgOptions, activeOrgName, selectedOrg]); 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 modalView = ( { setSearchBarModalOpen(false); }} PaperProps={{ style: { color: "white", minWidth: 750, height: 785, borderRadius: 16, border: "1px solid var(--Container-Stroke, #494949)", background: "var(--Container, #000000)", boxShadow: "0px 16px 24px 8px rgba(0, 0, 0, 0.25)", zIndex: 13000, paddingTop: 20, }, }} > ); const getRegionFlag = (region_url) => { var region = "gb"; 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 (
{ if (window?.location?.pathname?.includes("/workflows/")) { setExpandLeftNav(false); } }} onMouseOver={() => { if (window?.location?.pathname?.includes("/workflows/")) { setExpandLeftNav(true); } } } > {modalView} Shuffle Logo {!leftSideBarOpenByClick && setExpandLeftNav(true);}} onMouseLeave={()=>{!leftSideBarOpenByClick && 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} {option.region_url} ) : null } {option.name} {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) => { return options.filter((option) => option.name .toLowerCase() .includes(params.inputValue.toLowerCase()) ); }} 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;