+
+ Usecases
+
+
+ Choose a template tailored to your automation requirements, ready for immediate use.
+
+
+ {isLoggedIn === false ? null :
+
+
+
+ Your selected apps:
+
+
+
+
+
+ {parsedframework.map((app, index) => {
+ return (
+
+
+
+
+
+ )
+ })}
+
+
+
+
+
+ }
+
+ {/*userdata.priorities === null || userdata.priorities === undefined || userdata.priorities.length === 0 ? null :
+
+
+
+ Usecases based on your apps:
+
+
+
+
+ {userdata.priorities.map((priority, index) => {
+ if (priority.active === false) {
+ return null
+ }
+
+ if (priority.type !== "usecase") {
+ return null
+ }
+
+ if (usecaseCnt > 3) {
+ return null
+ }
+
+ usecaseCnt += 1
+
+ const parsedusecase = ParseUsecaseDesc(priority, frameworkData)
+ const newname = priority.name.slice(19, 100)
+ var parsedColor = priority.color === undefined ? "" : priority.color
+
+ // Search through usecases and find color
+ for (var usecasekey in keys) {
+ const usecase = keys[usecasekey]
+ for (var subcasekey in usecase.list) {
+ const subcase = usecase.list[subcasekey]
+ if (subcase.name.toLowerCase().includes(newname.toLowerCase())) {
+ parsedColor = usecase.color
+ break
+ }
+ }
+
+ if (parsedColor !== "") {
+ break
+ }
+ }
+
+ var srcname = parsedusecase[0] === "" ? "TBD" : parsedusecase[0].toUpperCase()
+ var destname = parsedusecase[2] === "" ? "TBD" : parsedusecase[2].toUpperCase()
+ if (parsedusecase[1] != undefined && parsedusecase[1] != null && parsedusecase[1].includes("svg") && parsedusecase[1].includes("248,90,62")) {
+ if (!srcname.includes(":default")) {
+ srcname += ":default"
+ }
+ }
+
+ if (parsedusecase[3] != undefined && parsedusecase[3] != null && parsedusecase[3].includes("svg") && parsedusecase[3].includes("248,90,62")) {
+ if (!destname.includes(":default")) {
+ destname += ":default"
+ }
+ }
+
+ return (
+
+
+
+ )
+ })}
+
+
+ */}
+
+
+ {keys.map((usecase, index) => {
+ return (
+
+
+ {index+1}. {usecase.name.slice(3, 100)}
+
+
+
+ {usecase.list.map((subcase, subindex) => {
+ const selectedItem = subindex === expandedItem && index === expandedIndex
+
+ if (subcase.matches === undefined || subcase.matches === null) {
+ subcase.matches = []
+ } else {
+ if (selectedItem && subcase.matches.length > 0 && selectedWorkflows.length === 0 && firstLoad === true) {
+ setFirstLoad(false)
+ setSelectedWorkflows(subcase.matches)
+
+ }
+ }
+
+ if (selectedItem && subcase.name !== undefined && inputUsecase.name !== undefined) {
+ if (subcase.name.toLowerCase().replaceAll(" ", "_") === inputUsecase.name.toLowerCase().replaceAll(" ", "_")) {
+ if (inputUsecase.description !== undefined && inputUsecase.description !== null) {
+ subcase.description = inputUsecase.description
+ }
+
+ if (inputUsecase.blogpost !== undefined && inputUsecase.blogpost !== null) {
+ subcase.blogpost = inputUsecase.blogpost
+ }
+
+ if (inputUsecase.video !== undefined && inputUsecase.video !== null) {
+ subcase.video = inputUsecase.video
+ }
+
+ if (inputUsecase.extra_buttons !== undefined && inputUsecase.extra_buttons !== null) {
+ subcase.extra_buttons = inputUsecase.extra_buttons
+ }
+
+ if (inputUsecase.workflow_outline !== undefined && inputUsecase.workflow_outline !== null) {
+ subcase.workflow_outline = inputUsecase.workflow_outline
+ }
+ }
+ }
+
+ const finished = subcase.matches.length > 0
+ const backgroundColor = theme.palette.surfaceColor
+ const itemBorder = `${selectedItem ? "3px" : expandedItem >= 0 ? "0px" : "1px"} solid ${usecase.color}`
+
+
+ const fixedName = subcase.name.toLowerCase().replace("_", " ")
+
+ //const parsedusecase = ParseUsecaseDesc(priority, frameworkData)
+ const newsubcase = parseUsecase(subcase, frameworkData)
+ var parsedUsecase = inputUsecase
+ parsedUsecase.srcimg = newsubcase.srcimg
+ parsedUsecase.srcapp = newsubcase.srcapp
+ parsedUsecase.dstimg = newsubcase.dstimg
+ parsedUsecase.dstapp = newsubcase.dstapp
+
+
+ var workflowBuilt = false
+ const newname = subcase.name.toLowerCase().replaceAll(" ", "_")
+ for (var workflowkey in workflows) {
+ const workflow = workflows[workflowkey]
+ if (workflow.usecase_ids === undefined || workflow.usecase_ids === null || workflow.usecase_ids.length === 0) {
+ continue
+ }
+
+ const newusecases = workflow.usecase_ids.map((usecase) => {
+ return usecase.toLowerCase().replaceAll("Suggested Usecase: ", "").replaceAll(" ", "_")
+ })
+
+ //console.log("WORKFLOW: ", newname, newusecases)
+ if (newusecases.includes(newname)) {
+ workflowBuilt = true
+ break
+ }
+ }
+
+ const usecaseDetails = parsedUsecase.name === undefined ? undefined : parsedUsecase.name.toLowerCase().replaceAll(" ", "_") === subcase.name.toLowerCase().replaceAll(" ", "_") ? parsedUsecase : undefined
+
+
+ return (
+ {
+ if (fixedName === "reporting") {
+ getUsecase(subcase, index, subindex)
+ return
+ }
+
+ //setSelectedWorkflows([])
+ if (selectedItem) {
+ } else {
+ getUsecase(subcase, index, subindex)
+ //navigate(`/usecases?selected_object=${fixedName}`)
+ }
+ }}>
+
+
+
+ )
+ })}
+
+
+ )
+ })}
+
+ )
+}
+
+// This is the start of a dashboard that can be used.
+// What data do we fill in here? Idk
+const Dashboard = (props) => {
+ const { globalUrl, isLoggedIn, userdata } = props;
+ //const alert = useAlert();
+ const { leftSideBarOpenByClick} = useContext(Context);
+ const [bigChartData, setBgChartData] = useState("data1");
+ const [dayAmount, setDayAmount] = useState(7);
+ const [firstRequest, setFirstRequest] = useState(true);
+ const [stats, setStats] = useState({});
+ const [changeme, setChangeme] = useState("");
+ const [statsRan, setStatsRan] = useState(false);
+ const [keys, setKeys] = useState([])
+ const [treeKeys, setTreeKeys] = useState([])
+
+ const [selectedUsecaseCategory, setSelectedUsecaseCategory] = useState("");
+ const [selectedUsecases, setSelectedUsecases] = useState([]);
+ const [usecases, setUsecases] = useState([])
+ const [workflows, setWorkflows] = useState([])
+ const [frameworkData, setFrameworkData] = useState(undefined);
+
+ let navigate = useNavigate();
+ const isCloud =
+ window.location.host === "localhost:3002" ||
+ window.location.host === "shuffler.io";
+
+
+ useEffect(() => {
+ if (selectedUsecaseCategory.length === 0) {
+ setSelectedUsecases(usecases)
+ } else {
+ const foundUsecase = usecases.find(data => data.name === selectedUsecaseCategory)
+ if (foundUsecase !== undefined && foundUsecase !== null) {
+ setSelectedUsecases([foundUsecase])
+ }
+ }
+ }, [selectedUsecaseCategory])
+
+ const checkSelectedParams = () => {
+ const urlSearchParams = new URLSearchParams(window.location.search)
+ const params = Object.fromEntries(urlSearchParams.entries())
+
+ const curpath = typeof window === "undefined" || window.location === undefined ? "" : window.location.pathname;
+ const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search;
+
+ const foundQuery = params["selected"]
+ if (foundQuery !== null && foundQuery !== undefined) {
+ setSelectedUsecaseCategory(foundQuery)
+
+ const newitem = removeParam("selected", cursearch);
+ navigate(curpath + newitem)
+ }
+
+ const baseItem = document.getElementById("reporting")
+ if (baseItem !== undefined && baseItem !== null) {
+ baseItem.click()
+
+ // Find close window button -> go to top
+ const foundButton = document.getElementById("close_selection")
+ if (foundButton !== undefined && foundButton !== null) {
+ foundButton.click()
+ }
+
+ // Scroll back to top
+ window.scrollTo(0, 0)
+ }
+
+ const foundQuery2 = params["selected_object"]
+ if (foundQuery2 !== null && foundQuery2 !== undefined) {
+ // Take a random object, quickly click it, then go to this one
+ // Something is weird with loading apps without it
+
+ const queryName = foundQuery2.toLowerCase().replaceAll("_", " ")
+ // Waiting a bit for it to render
+ setTimeout(() => {
+ const foundItem = document.getElementById(queryName)
+ if (foundItem !== undefined && foundItem !== null) {
+ foundItem.click()
+ // Scroll to it
+
+ setTimeout(() => {
+ foundItem.scrollIntoView({
+ behavior: "smooth",
+ block: "center",
+ inline: "center"
+ })
+ }, 100)
+ } else {
+ //console.log("Couldn't find item with name ", queryName)
+ }
+ }, 1000)
+ }
+
+ }
+
+ useEffect(() => {
+ if (usecases.length > 0) {
+ //console.log(usecases)
+ checkSelectedParams()
+ }
+ }, [usecases])
+
+ const getFramework = () => {
+ fetch(globalUrl + "/api/v1/apps/frameworkConfiguration", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for framework!");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ if (responseJson.success === false) {
+ const preparedData = {
+ "siem": findSpecificApp({}, "SIEM"),
+ "communication": findSpecificApp({}, "COMMUNICATION"),
+ "assets": findSpecificApp({}, "ASSETS"),
+ "cases": findSpecificApp({}, "CASES"),
+ "network": findSpecificApp({}, "NETWORK"),
+ "intel": findSpecificApp({}, "INTEL"),
+ "edr": findSpecificApp({}, "EDR"),
+ "iam": findSpecificApp({}, "IAM"),
+ "email": findSpecificApp({}, "EMAIL"),
+ }
+
+ console.log("Got error for framework! ", preparedData)
+ setFrameworkData(preparedData)
+ } else {
+ setFrameworkData(responseJson)
+ }
+ })
+ .catch((error) => {
+ toast(error.toString());
+ })
+ }
+
+
+ const getAvailableWorkflows = () => {
+ fetch(globalUrl + "/api/v1/workflows", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ fetchUsecases()
+ console.log("Status not 200 for workflows :O!: ", response.status);
+ return;
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ fetchUsecases(responseJson)
+
+ if (responseJson !== undefined) {
+ setWorkflows(responseJson);
+ }
+ })
+ .catch((error) => {
+ fetchUsecases()
+ //toast(error.toString());
+ });
+ }
+
+
+ document.title = "Shuffle - usecases";
+ var dayGraphLabels = [60, 80, 65, 130, 80, 105, 90, 130, 70, 115, 60, 130];
+ var dayGraphData = [60, 80, 65, 130, 80, 105, 90, 130, 70, 115, 60, 130];
+
+ const handleKeysetting = (categorydata) => {
+ var allCategories = []
+ var treeCategories = []
+ for (key in categorydata) {
+ const category = categorydata[key]
+ allCategories.push({"key": category.name, "data": category.list.length, "color": category.color})
+ treeCategories.push({"key": category.name, "data": 100, "color": category.color,})
+ for (var subkey in category.list) {
+ treeCategories.push({"key": category.list[subkey].name, "data": 20, "color": category.color})
+ }
+ }
+
+ setKeys(allCategories)
+ setTreeKeys(treeCategories)
+ }
+
+ const fetchUsecases = (workflows) => {
+ fetch(globalUrl + "/api/v1/workflows/usecases", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for usecases");
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ // Matching workflows with usecases
+ if (responseJson.success === false) {
+ return
+ }
+
+ if (workflows !== undefined && workflows !== null && workflows.length > 0) {
+ var categorydata = responseJson
+ var newcategories = []
+ for (var key in categorydata) {
+ var category = categorydata[key]
+ category.matches = []
+
+ for (var subcategorykey in category.list) {
+ var subcategory = category.list[subcategorykey]
+ subcategory.matches = []
+
+ for (var workflowkey in workflows) {
+ const workflow = workflows[workflowkey]
+
+ if (workflow.usecase_ids !== undefined && workflow.usecase_ids !== null) {
+ for (var usecasekey in workflow.usecase_ids) {
+ if (workflow.usecase_ids[usecasekey].toLowerCase() === subcategory.name.toLowerCase()) {
+
+ category.matches.push({
+ "workflow": workflow.id,
+ "category": subcategory.name,
+ })
+
+ subcategory.matches.push(workflow)
+ break
+ }
+ }
+ }
+
+ if (subcategory.matches.length > 0) {
+ break
+ }
+ }
+ }
+
+ newcategories.push(category)
+ }
+
+ if (newcategories !== undefined && newcategories !== null && newcategories.length > 0) {
+ handleKeysetting(newcategories)
+ setUsecases(newcategories)
+ setSelectedUsecases(newcategories)
+ } else {
+ handleKeysetting(responseJson)
+ setUsecases(responseJson)
+ setSelectedUsecases(responseJson)
+ }
+ } else {
+ handleKeysetting(responseJson)
+ setUsecases(responseJson)
+ setSelectedUsecases(responseJson)
+ }
+ })
+ .catch((error) => {
+ //toast("ERROR: " + error.toString());
+ console.log("ERROR: " + error.toString());
+ });
+ };
+
+ useEffect(() => {
+ getAvailableWorkflows()
+ getFramework()
+ }, []);
+
+ const fetchdata = (stats_id) => {
+ fetch(globalUrl + "/api/v1/stats/" + stats_id, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ credentials: "include",
+ })
+ .then((response) => {
+ if (response.status !== 200) {
+ console.log("Status not 200 for " + stats_id);
+ }
+
+ return response.json();
+ })
+ .then((responseJson) => {
+ stats[stats_id] = responseJson;
+ setStats(stats);
+ // Used to force updates
+ setChangeme(stats_id);
+ })
+ .catch((error) => {
+ //toast("ERROR: " + error.toString());
+ console.log("ERROR: " + error.toString());
+ });
+ };
+
+ let chart1_2_options = {
+ maintainAspectRatio: false,
+ legend: {
+ display: false,
+ },
+ tooltips: {
+ backgroundColor: "#f5f5f5",
+ titleFontColor: "#333",
+ bodyFontColor: "#666",
+ bodySpacing: 4,
+ xPadding: 12,
+ mode: "nearest",
+ intersect: 0,
+ position: "nearest",
+ },
+ responsive: true,
+ scales: {
+ yAxes: [
+ {
+ barPercentage: 1.6,
+ gridLines: {
+ drawBorder: false,
+ color: "rgba(29,140,248,0.0)",
+ zeroLineColor: "transparent",
+ },
+ ticks: {
+ suggestedMin: 60,
+ suggestedMax: 125,
+ padding: 20,
+ fontColor: "#9a9a9a",
+ },
+ },
+ ],
+ xAxes: [
+ {
+ barPercentage: 1.6,
+ gridLines: {
+ drawBorder: false,
+ color: "rgba(29,140,248,0.1)",
+ zeroLineColor: "transparent",
+ },
+ ticks: {
+ padding: 20,
+ fontColor: "#9a9a9a",
+ },
+ },
+ ],
+ },
+ };
+
+ const dayGraph = {
+ data: (canvas) => {
+ let ctx = canvas.getContext("2d");
+
+ let gradientStroke = ctx.createLinearGradient(0, 230, 0, 50);
+
+ gradientStroke.addColorStop(1, "rgba(29,140,248,0.2)");
+ gradientStroke.addColorStop(0.4, "rgba(29,140,248,0.0)");
+ gradientStroke.addColorStop(0, "rgba(29,140,248,0)"); //blue colors
+
+ return {
+ labels: dayGraphLabels,
+ datasets: [
+ {
+ label: "My First dataset",
+ fill: true,
+ backgroundColor: gradientStroke,
+ borderColor: "#1f8ef1",
+ borderWidth: 2,
+ borderDash: [],
+ borderDashOffset: 0.0,
+ pointBackgroundColor: "#1f8ef1",
+ pointBorderColor: "rgba(255,255,255,0)",
+ pointHoverBackgroundColor: "#1f8ef1",
+ pointBorderWidth: 20,
+ pointHoverRadius: 4,
+ pointHoverBorderWidth: 15,
+ pointRadius: 4,
+ data: dayGraphData,
+ },
+ ],
+ };
+ },
+ options: chart1_2_options,
+ };
+
+ // All these are currently tracked.
+ const variables = [
+ "backend_executions",
+ "workflow_executions",
+ "workflow_executions_aborted",
+ "workflow_executions_success",
+ "total_apps_created",
+ "total_apps_loaded",
+ "openapi_apps_created",
+ "total_apps_deleted",
+ "total_webhooks_ran",
+ "total_workflows",
+ "total_workflow_actions",
+ "total_workflow_triggers",
+ ];
+
+ const runUpdate = () => {
+ for (var key in variables) {
+ fetchdata(variables[key]);
+ }
+ };
+
+ // Refresh every 60 seconds
+ const autoUpdate = 60000;
+ const { start, stop } = useInterval({
+ duration: autoUpdate,
+ startImmediate: false,
+ callback: () => {
+ runUpdate();
+ },
+ });
+
+ if (firstRequest) {
+ setFirstRequest(false);
+ //start();
+ //runUpdate();
+ } else if (!statsRan) {
+ // FIXME: Run this under runUpdate schedule?
+ // 1. Fix labels in dayGraphy.data
+ // 2. Add data to the daygraph
+
+ // Every time there's an update :)
+
+ // This should probably be done in the backend.. bleh
+ if (
+ stats["workflow_executions"] !== undefined &&
+ stats["workflow_executions"] !== null &&
+ stats["workflow_executions"].data !== undefined
+ ) {
+ setStatsRan(true);
+ //console.log("NEW DATA?: ", stats)
+ console.log("SET WORKFLOW: ", stats["workflow_executions"]);
+ //var curday = startDate.getDate()
+
+ // Index = what day are we on
+
+ // 0 = today
+ var newDayGraphLabels = [];
+ var newDayGraphData = [];
+ for (var i = dayAmount; i > 0; i--) {
+ var enddate = new Date();
+ enddate.setDate(-i);
+ enddate.setHours(23, 59, 59, 999);
+
+ var startdate = new Date();
+ startdate.setDate(-i);
+ startdate.setHours(0, 0, 0, 0);
+
+ var endtime = enddate.getTime() / 1000;
+ var starttime = startdate.getTime() / 1000;
+
+ console.log(
+ "START: ",
+ starttime,
+ "END: ",
+ endtime,
+ "Data: ",
+ stats["workflow_executions"]
+ );
+ for (var key in stats["workflow_executions"].data) {
+ const item = stats["workflow_executions"]["data"][key];
+ console.log("ITEM: ", item.timestamp, endtime);
+ console.log(endtime - starttime);
+ if (
+ endtime - starttime > endtime - item.timestamp &&
+ endtime.timestamp >= 0
+ ) {
+ console.log("HIT? ");
+ }
+ console.log(item.timestamp - endtime);
+ //console.log(item.timestamp-endtime)
+ break;
+ if (item.timestamp > endtime && item.timestamp < starttime) {
+ if (newDayGraphData[i - 1] === undefined) {
+ newDayGraphData[i - 1] = 1;
+ } else {
+ newDayGraphData[i - 1] += 1;
+ }
+
+ //break
+ }
+ }
+
+ newDayGraphLabels.push(i);
+ }
+
+ console.log(newDayGraphLabels);
+ console.log(newDayGraphData);
+ }
+ }
+
+ const newdata =
+ Object.getOwnPropertyNames(stats).length > 0 ? (
+
+ Autoupdate every {autoUpdate / 1000} seconds
+ {variables.map((data) => {
+ if (stats[data] === undefined || stats[data] === null) {
+ return null;
+ }
+
+ if (stats[data].total === undefined) {
+ return null;
+ }
+
+ return (
+
+ {data}: {stats[data].total}
+
+ );
+ })}
+
+ ) : null
+
+ const data =
+