diff --git a/frontend/src/views/DashboardViews.jsx b/frontend/src/views/DashboardViews.jsx
new file mode 100644
index 00000000..ee4e1144
--- /dev/null
+++ b/frontend/src/views/DashboardViews.jsx
@@ -0,0 +1,833 @@
+import React, { useState, useEffect } from "react";
+import { useInterval } from "react-powerhooks";
+import DetectionFramework from "../components/DetectionFramework.jsx";
+import { makeStyles, useTheme } from "@material-ui/core/styles";
+// nodejs library that concatenates classes
+import classNames from "classnames";
+import theme from '../theme';
+import { useNavigate, Link, useParams } from "react-router-dom";
+
+// react plugin used to create charts
+//import { Line, Bar } from "react-chartjs-2";
+import { useAlert } from "react-alert";
+import Autocomplete from "@material-ui/lab/Autocomplete";
+import Draggable from "react-draggable";
+
+import {
+ Tooltip,
+ TextField,
+ IconButton,
+ Button,
+ Typography,
+ Grid,
+ Paper,
+ Chip,
+ Checkbox,
+} from "@material-ui/core";
+
+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 "@material-ui/icons";
+
+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,
+ Line,
+ LineChart,
+ LineSeries,
+ LinearYAxis,
+ LinearXAxis,
+ LinearYAxisTickSeries,
+ LinearXAxisTickSeries,
+ AreaChart,
+ AreaSeries,
+ PointSeries,
+} 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",
+ // This matches the specificity of the default styles at https://github.com/mui-org/material-ui/blob/v4.11.3/packages/material-ui-lab/src/Autocomplete/Autocomplete.js#L90
+ "&:hover .MuiOutlinedInput-notchedOutline": {
+ borderColor: "#f86a3e",
+ },
+ },
+});
+
+const inputdata = [
+ {
+ "key": "Threat Intel",
+ "value": 18,
+ "x": "2020-02-17T08:00:00.000Z",
+ "x0": "2020-02-17T08:00:00.000Z",
+ "x1": "2020-02-17T08:00:00.000Z",
+ "y": 18,
+ "y0": 0,
+ "y1": 18
+ },
+ {
+ "key": "Threat Intel",
+ "value": 3,
+ "x": "2020-02-21T08:00:00.000Z",
+ "x0": "2020-02-21T08:00:00.000Z",
+ "x1": "2020-02-21T08:00:00.000Z",
+ "y": 3,
+ "y0": 0,
+ "y1": 3
+ },
+ {
+ "key": "Threat Intel",
+ "value": 14,
+ "x": "2020-02-26T08:00:00.000Z",
+ "x0": "2020-02-26T08:00:00.000Z",
+ "x1": "2020-02-26T08:00:00.000Z",
+ "y": 14,
+ "y0": 0,
+ "y1": 14
+ },
+ {
+ "key": "Threat Intel",
+ "value": 18,
+ "x": "2020-02-29T08:00:00.000Z",
+ "x0": "2020-02-29T08:00:00.000Z",
+ "x1": "",
+ "y": 18,
+ "y0": 0,
+ "y1": 18
+ }
+]
+
+const LineChartWrapper = ({keys, height, width}) => {
+ const [hovered, setHovered] = useState("");
+
+ //console.log("Date: ", new Date("2019-11-14T08:00:00.000Z"))
+ console.log("Keys: ", keys)
+ var inputdata = keys.data
+
+ /*
+ const inputdata = [{
+ "key": "Intel",
+ "data": [
+ { key: new Date('11/22/2019'), data: 3, metadata: {color: "orange", "name": "Intel"}},
+ { key: new Date('11/24/2019'), data: 8, metadata: {color: "orange", "name": "Intel"}},
+ { key: new Date('11/29/2019'), data: 2, metadata: {color: "orange", "name": "Intel"}},
+ ]},
+ {
+ "key": "Popper",
+ "data": [
+ { key: new Date('11/24/2019'), data: 9, },
+ { key: new Date('11/29/2019'), data: 3, },
+ ]
+ }
+ ]
+ */
+
+ return (
+
+
+ {keys.title}
+
+
+ }
+ colorScheme={(colorInput) => {
+ var color = "cybertron"
+ if (colorInput !== undefined && colorInput.length > 0) {
+ color = colorInput[0].metadata !== undefined && colorInput[0].metadata.color !== undefined ? colorInput[0].metadata.color : color
+ }
+
+ return color
+ }}
+ tooltip={
+
{
+ if (hovered !== event.value.x) {
+ //setHovered(event.value.x)
+ }
+ }}
+ tooltip={
+ {
+ console.log("DATA: ", data)
+ const name = data.metadata !== undefined && data.metadata.name !== undefined ? data.metadata.name : "No"
+
+ return (
+
+
+ {name}
+
+
+ )
+ /*
+
+ )
+ */
+ }
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+
+ )
+}
+
+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 } = 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);
+
+ const [widgetData, setWidgetData] = 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 foundQuery2 = params["selected_object"]
+ if (foundQuery2 !== null && foundQuery2 !== undefined) {
+ console.log("Got selected_object: ", foundQuery2)
+
+ 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()
+ } else {
+ //console.log("Couldn't find item with name ", queryName)
+ }
+ }, 100);
+ }
+
+ }
+
+ useEffect(() => {
+ if (usecases.length > 0) {
+ console.log(usecases)
+ checkSelectedParams()
+ }
+ }, [usecases])
+
+ const getWidget = (dashboard, widget) => {
+ fetch(`${globalUrl}/api/v1/dashboards/${dashboard}/widgets/${widget}`, {
+ 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) => {
+ console.log("Resp: ", responseJson)
+ if (responseJson.success === false) {
+ if (responseJson.reason !== undefined) {
+ //alert.error("Failed loading: " + responseJson.reason)
+ } else {
+ //alert.error("Failed to load framework for your org.")
+ }
+ } else {
+ var tmpdata = responseJson
+ for (var key in tmpdata.data) {
+ for (var subkey in tmpdata.data[key].data) {
+ tmpdata.data[key].data[subkey].key = new Date(tmpdata.data[key].data[subkey].key)
+ }
+ }
+
+ console.log("VAl: ", tmpdata)
+
+ setWidgetData(tmpdata)
+ }
+ })
+ .catch((error) => {
+ //alert.error(error.toString());
+ })
+ }
+
+ document.title = "Shuffle - Dashboard";
+ 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) {
+ 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) => {
+ //alert.error("ERROR: " + error.toString());
+ console.log("ERROR: " + error.toString());
+ });
+ };
+
+ useEffect(() => {
+ getWidget("main", "tmp")
+ //fetchUsecases()
+ }, []);
+
+ 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) => {
+ //alert.error("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 = (
+
+
+ {keys.length > 0 ?
+
+
+
+ : null}
+
+
+ {widgetData === undefined ? null :
+
+
+
+
+
+ }
+
+ );
+
+ const dataWrapper = (
+ {data}
+ );
+
+ return dataWrapper;
+};
+
+export default Dashboard;