From 51e07f72718eef48b1daef352560f758e50b65c8 Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 10 May 2024 15:38:21 +0200 Subject: [PATCH] Added usecases --- frontend/src/views/Usecases.jsx | 1745 +++++++++++++++++++++++++++++++ 1 file changed, 1745 insertions(+) create mode 100644 frontend/src/views/Usecases.jsx diff --git a/frontend/src/views/Usecases.jsx b/frontend/src/views/Usecases.jsx new file mode 100644 index 00000000..34fe84c5 --- /dev/null +++ b/frontend/src/views/Usecases.jsx @@ -0,0 +1,1745 @@ +import React, { useState, useEffect } from "react" +import { useInterval } from "react-powerhooks" +import AppFramework from "../components/AppFramework.jsx" +import { makeStyles, } from "@mui/styles" +import classNames from "classnames" +import theme from '../theme.jsx' +import { useNavigate, Link, useParams } from "react-router-dom" +import WorkflowTemplatePopup from "../components/WorkflowTemplatePopup.jsx" +import { ToastContainer, toast } from "react-toastify" +import { parsedDatatypeImages } from "../components/AppFramework.jsx" +import { findSpecificApp } from "../components/AppFramework.jsx" + +import { + Autocomplete, + Tooltip, + TextField, + IconButton, + Button, + Typography, + Grid, + Paper, + Chip, + Checkbox, +} from "@mui/material"; + +import { + Close as CloseIcon, + DoneAll as DoneAllIcon, + Description as DescriptionIcon, + PlayArrow as PlayArrowIcon, + Edit as EditIcon, + CheckBox as CheckBoxIcon, + CheckBoxOutlineBlank as CheckBoxOutlineBlankIcon, + OpenInNew as OpenInNewIcon, +} from "@mui/icons-material"; + +import WorkflowPaper from "../components/WorkflowPaper.jsx" +import { removeParam } from "../views/AngularWorkflow.jsx" + +// core components +//import { +// chartExample1, +// chartExample2, +// chartExample3, +// chartExample4, +//} from "../charts.js"; + +import { + RadialBarChart, + RadialAreaChart, + RadialAxis, + StackedBarSeries, + TooltipArea, + ChartTooltip, + TooltipTemplate, + RadialAreaSeries, + RadialPointSeries, + RadialArea, + RadialLine, + TreeMap, + TreeMapSeries, + TreeMapLabel, + TreeMapRect, +} from 'reaviz'; + +const useStyles = makeStyles({ + notchedOutline: { + borderColor: "#f85a3e !important", + }, + root: { + "& .MuiAutocomplete-listbox": { + border: "2px solid #f85a3e", + color: "white", + fontSize: 18, + "& li:nth-child(even)": { + backgroundColor: "#CCC", + }, + "& li:nth-child(odd)": { + backgroundColor: "#FFF", + }, + }, + }, + inputRoot: { + color: "white", + "&:hover .MuiOutlinedInput-notchedOutline": { + borderColor: "#f86a3e", + }, + }, +}); + + + +const UsecaseListComponent = (props) => { + const { keys, userdata, isCloud, globalUrl, frameworkData, isLoggedIn, workflows, setWorkflows, getFramework, setFrameworkData, } = props + + + const [expandedIndex, setExpandedIndex] = useState(-1); + const [expandedItem, setExpandedItem] = useState(-1); + const [inputUsecase, setInputUsecase] = useState({}); + + const [prevSubcase, setPrevSubcase] = useState({}) + + const [editing, setEditing] = useState(false); + const [description, setDescription] = useState(""); + const [video, setVideo] = useState(""); + const [blogpost, setBlogpost] = useState(""); + const [workflowOutline, setWorkflowOutline] = useState(""); + + const [selectedWorkflows, setSelectedWorkflows] = useState([]) + const [firstLoad, setFirstLoad] = useState(true) + const [apps, setApps] = useState([]) + + + const classes = useStyles(); + let navigate = useNavigate(); + + const [mitreTags, setMitreTags] = useState([]); + + + const parseUsecase = (subcase) => { + const srcdata = findSpecificApp(frameworkData, subcase.type) + const dstdata = findSpecificApp(frameworkData, subcase.last) + + if (srcdata !== undefined && srcdata !== null) { + subcase.srcimg = srcdata.large_image + subcase.srcapp = srcdata.name + } + + if (dstdata !== undefined && dstdata !== null) { + subcase.dstimg = dstdata.large_image + subcase.dstapp = dstdata.name + } + + return subcase + } + + useEffect(() => { + //console.log("Frameworkdata changed. Use to set inputUsecase: ", frameworkData, prevSubcase) + if (frameworkData === undefined || prevSubcase === undefined) { + return + } + + var parsedUsecase = inputUsecase + const subcase = parseUsecase(prevSubcase) + + parsedUsecase.srcimg = subcase.srcimg + parsedUsecase.srcapp = subcase.srcapp + parsedUsecase.dstimg = subcase.dstimg + parsedUsecase.dstapp = subcase.dstapp + + setInputUsecase(parsedUsecase) + }, [frameworkData]) + + const loadApps = () => { + fetch(`${globalUrl}/api/v1/apps`, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + return response.json(); + }) + .then((responseJson) => { + if (responseJson === null) { + console.log("null-response from server") + const pretend_apps = [{ + "name": "TBD", + "app_name": "TBD", + "app_version": "TBD", + "description": "TBD", + "version": "TBD", + "large_image": "", + }] + + setApps(pretend_apps) + return + } + + if (responseJson.success === false) { + console.log("error loading apps: ", responseJson) + return + } + + setApps(responseJson); + }) + .catch((error) => { + console.log("App loading error: " + error.toString()); + }) + } + + useEffect(() => { + loadApps() + }, []) + + if (keys === undefined || keys === null || keys.length === 0) { + return null + } + + + + // Timeout 50ms to delay it slightly + const getUsecase = (subcase, index, subindex) => { + subcase = parseUsecase(subcase) + setPrevSubcase(subcase) + + fetch(`${globalUrl}/api/v1/workflows/usecases/${escape(subcase.name.replaceAll(" ", "_"))}`, { + 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) => { + var parsedUsecase = responseJson + + if (responseJson.success === false) { + parsedUsecase = subcase + } else { + parsedUsecase = responseJson + + parsedUsecase.srcimg = subcase.srcimg + parsedUsecase.srcapp = subcase.srcapp + parsedUsecase.dstimg = subcase.dstimg + parsedUsecase.dstapp = subcase.dstapp + } + + // Look for the type of app and fill in img1, srcapp... + setInputUsecase(parsedUsecase) + setExpandedIndex(index) + setExpandedItem(subindex) + + setTimeout(() => { + const found = document.getElementById("selected_box"); + if (found !== undefined && found !== null) { + //console.log("FOUND!!") + + //found.scrollTo({ + // top: 100, + // behavior: "smooth", + //}) + } else { + //console.log("NOT FOUND!!") + } + + setFirstLoad(true) + setSelectedWorkflows([]) + }, 100) + }) + .catch((error) => { + //toast(error.toString()); + setInputUsecase({}) + setExpandedIndex(index) + setExpandedItem(subindex) + + setFirstLoad(true) + setSelectedWorkflows([]) + }) + } + + const setUsecaseItem = (inputUsecase) => { + var parsedUsecase = inputUsecase + + if (blogpost !== inputUsecase.blogpost) { + inputUsecase.blogpost = blogpost + parsedUsecase.blogpost = blogpost + } + + if (video !== inputUsecase.video) { + inputUsecase.video = video + parsedUsecase.video = video + } + + if (description !== inputUsecase.description) { + inputUsecase.description = description + parsedUsecase.description = description + } + + if (mitreTags !== inputUsecase.mitre) { + inputUsecase.mitre = mitreTags + parsedUsecase.mitre = mitreTags + } + + if (workflowOutline !== inputUsecase.workflow_outline) { + inputUsecase.workflow_outline = workflowOutline + parsedUsecase.workflow_outline = workflowOutline + } + + fetch(globalUrl + "/api/v1/workflows/usecases", { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(parsedUsecase), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for framework!"); + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === false) { + if (responseJson.reason !== undefined) { + //toast("Failed updating: " + responseJson.reason) + } else { + //toast("Failed to update framework for your org.") + } + } else { + //toast("Updated usecase.") + } + }) + .catch((error) => { + //toast(error.toString()); + //setFrameworkLoaded(true) + }) + } + + const setWorkflow = (workflowdata) => { + const new_url = `${globalUrl}/api/v1/workflows/${workflowdata.id}` + + fetch(new_url, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(workflowdata), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for workflows :O!"); + return; + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === false) { + if (responseJson.reason !== undefined) { + toast("Error updating workflow: ", responseJson.reason) + } else { + toast("Error updating workflow.") + } + + return + } + + return responseJson; + }) + .catch((error) => { + toast("Problem setting workflow: ", error.toString()); + }); + }; + + return ( +
+ + Shuffle usecases + + + Usecases in Shuffle are divided into {keys.length} type{keys.length === 1 ? "" : "s"}. + + {keys.map((usecase, index) => { + return ( +
+ + {usecase.name} + + + {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("_", " ") + + return ( + { + if (fixedName === "increase authentication") { + getUsecase(subcase, index, subindex) + return + } + + //setSelectedWorkflows([]) + if (selectedItem) { + } else { + getUsecase(subcase, index, subindex) + navigate(`/usecases?selected_object=${fixedName}`) + } + }}> + { + }}> + {!selectedItem ? +
+ + {subcase.name} + + {finished ? + + { + }} + > + + + + : null} + {subcase.blogpost !== null && subcase.blogpost !== undefined && subcase.blogpost.length > 0 ? + + + { + }} + > + + + + + : null} + {subcase.video !== null && subcase.video !== undefined && subcase.video.length > 0 ? + + + { + }} + > + + + + + : null} +
+ : +
+ + {subcase.name} + +
+ {isLoggedIn === true ? + + { + setEditing(true) + if (subcase.description !== undefined && subcase.description !== null) { + setDescription(subcase.description) + } + + if (subcase.blogpost !== undefined && subcase.blogpost !== null) { + setBlogpost(subcase.blogpost) + } + + if (subcase.video !== undefined && subcase.video !== null) { + setVideo(subcase.video) + } + + if (subcase.mitre !== undefined && subcase.mitre !== null) { + setMitreTags(subcase.mitre) + } + + if (subcase.workflow_outline !== undefined && subcase.workflow_outline !== null) { + setWorkflowOutline(subcase.workflow_outline) + } else { + setWorkflowOutline("") + } + }} + > + + + + : null} + {subcase.blogpost !== null && subcase.blogpost !== undefined && subcase.blogpost.length > 0 ? + + + { + }} + > + + + + + : null} + {subcase.video !== null && subcase.video !== undefined && subcase.video.length > 0 ? + + + { + }} + > + + + + + : null} + + { + setExpandedItem(-1) + setExpandedIndex(-1) + setEditing(false) + setInputUsecase({}) + }} + > + + + +
+
+ {editing ? +
+ { + setDescription(event.target.value) + }} + id="descriptionEditng" + /> + { + setBlogpost(event.target.value) + }} + id="blogpostEditing" + /> + { + setVideo(event.target.value) + }} + id="videoEditing" + /> + { + setWorkflowOutline(event.target.value) + }} + id="workflowOutline" + tabIndex="-1" + /> + +
+ + +
+
+ : +
+ + {subcase.description} + + + {workflows !== undefined && workflows !== null && workflows.length > 0 ? + + Select relevant workflows + + : + + + Find workflows related to this usecase: + + + + + + + + + } + + {workflows !== undefined && workflows !== null && workflows.length > 0 ? + option.id === value.id} + getOptionLabel={(option) => { + + if ( + option === undefined || + option === null || + option.name === undefined || + option.name === null + ) { + return "No Workflow Selected"; + } + + const newname = (option.name.charAt(0).toUpperCase() + option.name.substring(1)).replaceAll("_", " "); + + return newname; + }} + fullWidth + style={{ + backgroundColor: theme.palette.inputColor, + height: 50, + borderRadius: theme.palette.borderRadius, + }} + onChange={(event, newValue) => { + //handleWorkflowSelectionUpdate({ target: { value: newValue} }) + //setSelectedWorkflows= + //var newvalue = [] + //for (var key in newValue) { + // if (newValue[key].id !== undefined) { + // newvalue.push(newValue[key].id) + // } + //} + + // Doing this way as you may want to remove some too + for (var key in workflows) { + if (!newValue.find(data => data.id === workflows[key].id)) { + // Check if it has the one in it + if (workflows[key]["usecase_ids"] !== undefined && workflows[key]["usecase_ids"] !== null && workflows[key]["usecase_ids"].includes(subcase.name)) { + const filtered = workflows[key]["usecase_ids"].filter(data => data !== subcase.name) + if (filtered !== undefined && filtered !== null) { + workflows[key]["usecase_ids"] = filtered + + setWorkflow(workflows[key]) + } + } + + continue + } + + if (workflows[key]["usecase_ids"] === undefined || workflows[key]["usecase_ids"] === null) { + workflows[key]["usecase_ids"] = [subcase.name] + setWorkflow(workflows[key]) + + } else if (!workflows[key]["usecase_ids"].includes(subcase.name)) { + workflows[key]["usecase_ids"].push(subcase.name) + setWorkflow(workflows[key]) + + } + } + + setWorkflows(workflows) + setSelectedWorkflows(newValue) + //setUpdate(Math.random()) + }} + renderOption={(props, data, state) => { + var newname = data.name + if (newname === undefined || newname === null) { + newname = "placeholder" + } + + if (newname.length > 2) { + newname = newname.charAt(0).toUpperCase() + newname.substring(1) + } + return ( +
  • + + {data.image !== undefined && data.image !== null && data.image.length > 0 ? + {newname} + : null} + + Choose {newname} + + + } placement="bottom"> + + } + checkedIcon={} + style={{ marginRight: 8 }} + checked={selectedWorkflows.find(wf => wf.id === data.id) !== undefined} + /> + {newname} + + +
  • + ) + }} + renderInput={(params) => { + return ( + + ); + }} + /> + : null} + + {}} + > + Try it out: + + {frameworkData !== undefined && frameworkData !== null && Object.keys(frameworkData).length > 0 ? + + : null} + + {/* + + + {subcase.extra_buttons !== undefined && subcase.extra_buttons !== null && subcase.extra_buttons.length > 0 ? +
    + {}}> + Examples + +
    + {subcase.extra_buttons.map((subdata, index) => { + var highlight = false + var baseTypeInfo = subcase.type !== undefined ? subcase.type : "communication" + if (frameworkData !== undefined && frameworkData !== null) { + if (frameworkData[baseTypeInfo] !== undefined && frameworkData[baseTypeInfo] !== null && subdata.app !== undefined && subdata.app !== null) { + if (frameworkData[baseTypeInfo].name !== undefined && frameworkData[baseTypeInfo].name.toLowerCase().replaceAll("_", " ") === subdata.app.toLowerCase().replaceAll("_", " ")) { + highlight = true + } + } + } + + var marginTop = 6 + if (subdata.name.includes(" ") && subdata.name.length > 10) { + marginTop = 0 + } + + return ( + +
    + + + {subdata.name} + +
    +
    + ) + })} +
    +
    + : null} + + */} +
    + } +
    + +
    +
    +
    + } +
    +
    + ) + })} +
    +
    + ) + })} +
    + ) +} + +const TreeChart = ({keys}) => { + const [hovered, setHovered] = useState(""); + + return ( +
    { + console.log("Click: ", hovered) + }}> + { + return info.color + }} + label={ + + } + rect={ + { + console.log("Click: ", event) + }} + /> + } + /> + } + /> +
    + ) + //axis={} +} + + +const RadialChart = ({keys, setSelectedCategory}) => { + const [hovered, setHovered] = useState(""); + + return ( +
    { + console.log("Click: ", hovered) + if (setSelectedCategory !== undefined) { + setSelectedCategory(hovered) + } + }}> + } + series={ + { + return '#f86a3e' + }} + animated={false} + id="workflow_series_id" + style={{cursor: "pointer",}} + line={ + { + console.log("INFO: ", data, color) + return ( + null + ) + }} + /> + } + tooltip={ + { + if (hovered !== event.value.x) { + setHovered(event.value.x) + } + }} + tooltip={ + { + return ( +
    + + {data.x} + +
    + ) + /* + + ) + */ + } + } + /> + } + /> + + } + /> + } + /> +
    + ) + //axis={} +} + +// 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 [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("increase authentication") + 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) + } + + console.log("CATEGORIES: ", newcategories) + + if (newcategories !== undefined && newcategories !== null && newcategories.length > 0) { + handleKeysetting(newcategories) + setUsecases(newcategories) + setSelectedUsecases(newcategories) + } 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 + + console.log("USECASES: ", usecases) + + const data = +
    +
    + {keys.length > 0 ? + + : null} +
    + + {usecases !== null && usecases !== undefined && usecases.length > 0 ? +
    + {usecases.map((usecase, index) => { + return ( + { + console.log("Clicked: ", usecase.name) + if (selectedUsecaseCategory === usecase.name) { + setSelectedUsecaseCategory("") + } else { + setSelectedUsecaseCategory(usecase.name) + } + //addFilter(usecase.name.slice(3,usecase.name.length)) + }} + variant="outlined" + color="primary" + /> + ) + })} +
    + : null} + + + + {treeKeys.length > 0 ? + + : null} + + {newdata} +
    + + const dataWrapper = +
    + {data} +
    + + + return dataWrapper +}; + +export default Dashboard;