diff --git a/frontend/src/components/LeftSideBar.jsx b/frontend/src/components/LeftSideBar.jsx new file mode 100644 index 00000000..1d0de5d5 --- /dev/null +++ b/frontend/src/components/LeftSideBar.jsx @@ -0,0 +1,1546 @@ +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, +} 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]); + + + + 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("/usecases2")) || currentPath.includes("/usecases2")) { + 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); + setLeftSideBarOpenByClick(false); + } else { + setLeftSideBarOpenByClick(true); + 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(); + }} + > + Organization + + + + { + handleClose(); + }} + > + Account + + + + + + { + handleClose(); + }} + > + Notifications ({ + notifications === undefined || notifications === null ? 0 : + notifications?.filter((notification) => notification.read === false).length + }) + + + + { + handleClose(); + }} + > + Use Cases + + + + + + + { + handleClose(); + }} + > + About + + + { + handleClickLogout(); + event.preventDefault(); + handleClose(); + }} + > +  Logout + + + + + Version: 2.0.0-beta + + + + ); + + 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, + }; + + 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 = "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 = "fr"; + } + } + } + return regiontag; + }; + + 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, + }, + }; + + 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, + }, + }} + > + + + + + + + + ); + + return ( +
+ {modalView} + + + Shuffle Logo + + + + + + {!leftSideBarOpenByClick && setExpandLeftNav(true);}} onMouseLeave={()=>{!leftSideBarOpenByClick && setExpandLeftNav(false);setOpenAutocomplete(false);}}> + + {expandLeftNav ? ( + + + ), + disableUnderline: true, + }} + onClick={()=>{setSearchBarModalOpen(true)}} + onChange={()=> {setSearchBarModalOpen(true)}} + /> + + ):( + <> + + + + )} + + + + + + + + + { + setOpenautomateTab((prev) => !prev); + setOpenSecurityTab(false); + }} + sx={{ + marginLeft: 0.625, + }} + > + {openautomatetab ? ( + + ) : ( + + )} + + + + + + + + + + + + + + + + + + + + { + setOpenSecurityTab((prev) => !prev); + setOpenautomateTab(false); + }} + sx={{ + marginLeft: 0.625, + }} + > + {openSecurityTab ? ( + + ) : ( + + )} + + + + + + + + + + + + + + + + + + + + Recent Workflows + + + {recentworkflows?.slice(0, 2).map((workflow, index) => { + return ( + + ) + }) } + + + + + + 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); + }} + > + + {option.region_url} + + {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;