Another merge fix due
This commit is contained in:
@@ -0,0 +1,759 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useInterval } from "react-powerhooks";
|
||||
import { makeStyles, useTheme } from "@material-ui/core/styles";
|
||||
// nodejs library that concatenates classes
|
||||
import classNames from "classnames";
|
||||
import theme from '../theme.jsx';
|
||||
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 (
|
||||
<div style={{}}>
|
||||
<Typography variant="h6" style={{marginBotton: 15}}>
|
||||
{keys.title}
|
||||
</Typography>
|
||||
<AreaChart
|
||||
style={{marginTop: 15}}
|
||||
height={height}
|
||||
width={width}
|
||||
data={inputdata}
|
||||
series={
|
||||
<AreaSeries
|
||||
type="grouped"
|
||||
symbols={
|
||||
<PointSeries show={true} />
|
||||
}
|
||||
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={
|
||||
<TooltipArea
|
||||
color={"#000000"}
|
||||
style={{
|
||||
backgroundColor: "red",
|
||||
}}
|
||||
isRadial={true}
|
||||
onValueEnter={(event) => {
|
||||
if (hovered !== event.value.x) {
|
||||
//setHovered(event.value.x)
|
||||
}
|
||||
}}
|
||||
tooltip={
|
||||
<ChartTooltip
|
||||
followCursor={true}
|
||||
modifiers={{
|
||||
offset: '5px, 5px'
|
||||
}}
|
||||
content={(data, color) => {
|
||||
console.log("DATA: ", data)
|
||||
const name = data.metadata !== undefined && data.metadata.name !== undefined ? data.metadata.name : "No"
|
||||
|
||||
return (
|
||||
<div style={{borderRadius: theme.palette.borderRadius, backgroundColor: theme.palette.inputColor, border: "1px solid rgba(255,255,255,0.3)", color: "white", padding: 5, cursor: "pointer",}}>
|
||||
<Typography variant="body1">
|
||||
{name}
|
||||
</Typography>
|
||||
</div>
|
||||
)
|
||||
/*
|
||||
<TooltipTemplate
|
||||
color={"#ffffff"}
|
||||
value={{
|
||||
x: data.x,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
*/
|
||||
}
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const RadialChart = ({keys, setSelectedCategory}) => {
|
||||
const [hovered, setHovered] = useState("");
|
||||
|
||||
return (
|
||||
<div style={{cursor: "pointer",}} onClick={() => {
|
||||
console.log("Click: ", hovered)
|
||||
if (setSelectedCategory !== undefined) {
|
||||
setSelectedCategory(hovered)
|
||||
}
|
||||
}}>
|
||||
<RadialAreaChart
|
||||
id="workflow_categories"
|
||||
height={500}
|
||||
width={500}
|
||||
data={keys}
|
||||
axis={<RadialAxis type="category" />}
|
||||
series={
|
||||
<RadialAreaSeries
|
||||
interpolation="smooth"
|
||||
colorScheme={(colorInput) => {
|
||||
return '#f86a3e'
|
||||
}}
|
||||
animated={false}
|
||||
id="workflow_series_id"
|
||||
style={{cursor: "pointer",}}
|
||||
line={
|
||||
<RadialLine
|
||||
color={"#000000"}
|
||||
data={(data, color) => {
|
||||
console.log("INFO: ", data, color)
|
||||
return (
|
||||
null
|
||||
)
|
||||
}}
|
||||
/>
|
||||
}
|
||||
tooltip={
|
||||
<TooltipArea
|
||||
color={"#000000"}
|
||||
style={{
|
||||
backgroundColor: "red",
|
||||
}}
|
||||
isRadial={true}
|
||||
onValueEnter={(event) => {
|
||||
if (hovered !== event.value.x) {
|
||||
setHovered(event.value.x)
|
||||
}
|
||||
}}
|
||||
tooltip={
|
||||
<ChartTooltip
|
||||
followCursor={true}
|
||||
modifiers={{
|
||||
offset: '5px, 5px'
|
||||
}}
|
||||
content={(data, color) => {
|
||||
return (
|
||||
<div style={{borderRadius: theme.palette.borderRadius, backgroundColor: theme.palette.inputColor, border: "1px solid rgba(255,255,255,0.3)", color: "white", padding: 5, cursor: "pointer",}}>
|
||||
<Typography variant="body1">
|
||||
{data.x}
|
||||
</Typography>
|
||||
</div>
|
||||
)
|
||||
/*
|
||||
<TooltipTemplate
|
||||
color={"#ffffff"}
|
||||
value={{
|
||||
x: data.x,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
*/
|
||||
}
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
//axis={<RadialAxis type="category" />}
|
||||
}
|
||||
|
||||
// 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([]);
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
const foundWidget = widgetData.findIndex(data => data.title === widget)
|
||||
console.log("Found: ", foundWidget)
|
||||
if (foundWidget !== undefined && foundWidget !== null && foundWidget >= 0) {
|
||||
widgetData[foundWidget] = tmpdata
|
||||
} else {
|
||||
widgetData.push(tmpdata)
|
||||
}
|
||||
|
||||
console.log("Data: ", widgetData)
|
||||
setWidgetData(widgetData)
|
||||
}
|
||||
})
|
||||
.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)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getWidget("main", "Overall")
|
||||
getWidget("main", "Overall2")
|
||||
}, []);
|
||||
|
||||
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 ? (
|
||||
<div>
|
||||
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 (
|
||||
<div>
|
||||
{data}: {stats[data].total}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
const data = (
|
||||
<div className="content" style={{width: 1000, margin: "auto", paddingBottom: 200, textAlign: "center",}}>
|
||||
<div style={{width: 500, margin: "auto"}}>
|
||||
{keys.length > 0 ?
|
||||
<span>
|
||||
<RadialChart keys={keys} setSelectedCategory={setSelectedUsecaseCategory} />
|
||||
</span>
|
||||
: null}
|
||||
</div>
|
||||
|
||||
{widgetData === undefined || widgetData === null || widgetData === [] || widgetData.length === 0 ? null :
|
||||
<Draggable>
|
||||
<Paper style={{height: 350, width: 500, padding: "15px 15px 15px 15px", }}>
|
||||
<LineChartWrapper keys={widgetData[0]} height={280} width={470} />
|
||||
</Paper>
|
||||
</Draggable>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
||||
const dataWrapper = (
|
||||
<div style={{ maxWidth: 1366, margin: "auto" }}>{data}</div>
|
||||
);
|
||||
|
||||
return dataWrapper;
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
@@ -0,0 +1,86 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import ReactDOM from "react-dom"
|
||||
|
||||
import AppFramework from "../components/AppFramework.jsx";
|
||||
import { useAlert } from "react-alert";
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import theme from '../theme.jsx';
|
||||
|
||||
import {
|
||||
Button,
|
||||
} from "@material-ui/core";
|
||||
|
||||
const Framework = (props) => {
|
||||
const {globalUrl, isLoaded, isLoggedIn, showOptions, selectedOption, rolling, } = props;
|
||||
|
||||
const alert = useAlert()
|
||||
const [frameworkLoaded, setFrameworkLoaded] = useState(false)
|
||||
const [frameworkData, setFrameworkData] = useState()
|
||||
|
||||
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) {
|
||||
if (responseJson.reason !== undefined) {
|
||||
alert.error("Failed loading: " + responseJson.reason)
|
||||
} else {
|
||||
alert.error("Failed to load framework for your org.")
|
||||
}
|
||||
|
||||
setFrameworkLoaded(true)
|
||||
} else {
|
||||
ReactDOM.unstable_batchedUpdates(() => {
|
||||
setFrameworkData(responseJson)
|
||||
setFrameworkLoaded(true)
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
setFrameworkLoaded(true)
|
||||
alert.error(error.toString());
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getFramework()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{marginBottom: 25, marginTop: 25, width: 300, margin: "auto", textAlign: "center",}}>
|
||||
<Link style={{textDecoration: "none", }} to="/getting-started">
|
||||
<Button variant="outlined" color="secondary">
|
||||
Back to getting started
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
{frameworkLoaded === true && isLoaded ?
|
||||
<AppFramework
|
||||
frameworkData={frameworkData}
|
||||
selectedOption={"Draw"}
|
||||
showOptions={false}
|
||||
|
||||
isLoaded={isLoaded}
|
||||
isLoggedIn={isLoggedIn}
|
||||
globalUrl={globalUrl}
|
||||
/>
|
||||
: null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Framework;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,795 @@
|
||||
/* eslint-disable react/no-multi-comp */
|
||||
import React, {useState, useEffect} from 'react';
|
||||
import ReactDOM from "react-dom"
|
||||
|
||||
import { useInterval } from "react-powerhooks";
|
||||
import { makeStyles } from '@material-ui/styles';
|
||||
import { useNavigate, Link, useParams } from "react-router-dom";
|
||||
import {isMobile} from "react-device-detect";
|
||||
import theme from '../theme.jsx';
|
||||
import { validateJson, GetIconInfo } from "./Workflows.jsx";
|
||||
import { green, yellow } from "./AngularWorkflow.jsx";
|
||||
|
||||
import {
|
||||
Tooltip,
|
||||
IconButton,
|
||||
CircularProgress,
|
||||
TextField,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Paper,
|
||||
Typography,
|
||||
Divider,
|
||||
} from '@material-ui/core';
|
||||
|
||||
import {
|
||||
Preview as PreviewIcon,
|
||||
ContentCopy as ContentCopyIcon,
|
||||
} from '@mui/icons-material';
|
||||
|
||||
const hrefStyle = {
|
||||
color: "white",
|
||||
textDecoration: "none"
|
||||
}
|
||||
|
||||
const bodyDivStyle = {
|
||||
margin: "auto",
|
||||
marginTop: 100,
|
||||
width: isMobile? "100%":"500px",
|
||||
position: "relative",
|
||||
}
|
||||
|
||||
|
||||
const RunWorkflow = (defaultprops) => {
|
||||
const { globalUrl, isLoaded, isLoggedIn, setIsLoggedIn, setCookie, register, serverside } = defaultprops;
|
||||
|
||||
let navigate = useNavigate();
|
||||
const [message, setMessage] = useState("");
|
||||
const [workflow, setWorkflow] = React.useState({});
|
||||
const [executionRequest, setExecutionRequest] = React.useState({});
|
||||
const [executionArgument, setExecutionArgument] = useState("");
|
||||
const [executionLoading, setExecutionLoading] = useState(false);
|
||||
const [executionData, setExecutionData] = React.useState({});
|
||||
const [executionRunning, setExecutionRunning] = useState(false);
|
||||
const [workflowQuestion, setWorkflowQuestion] = useState("");
|
||||
const [selectedOrganization, setSelectedOrganization] = React.useState(undefined);
|
||||
const [apps, setApps] = React.useState([]);
|
||||
const [buttonClicked, setButtonClicked] = React.useState("");
|
||||
|
||||
const boxStyle = {
|
||||
color: "white",
|
||||
paddingLeft: "30px",
|
||||
paddingRight: "30px",
|
||||
paddingBottom: "30px",
|
||||
paddingTop: "30px",
|
||||
backgroundColor: theme.palette.surfaceColor,
|
||||
marginBottom: 150,
|
||||
}
|
||||
|
||||
const params = useParams();
|
||||
var props = JSON.parse(JSON.stringify(defaultprops))
|
||||
props.match = {}
|
||||
props.match.params = params
|
||||
|
||||
const defaultTitle = "Run Workflow"
|
||||
if (document != undefined && document.title != defaultTitle) {
|
||||
document.title = defaultTitle
|
||||
}
|
||||
|
||||
const parsedsearch = serverside === true ? "" : window.location.search
|
||||
if (serverside !== true) {
|
||||
const tmpMessage = new URLSearchParams(window.location.search).get("message")
|
||||
if (tmpMessage !== undefined && tmpMessage !== null && message !== tmpMessage) {
|
||||
setMessage(tmpMessage)
|
||||
}
|
||||
}
|
||||
|
||||
// Used to swap from login to register. True = login, false = register
|
||||
|
||||
// Error messages etc
|
||||
const [executionInfo, setExecutionInfo] = useState("");
|
||||
|
||||
const handleValidateForm = (executionArgument) => {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
const getApps = () => {
|
||||
fetch(globalUrl + "/api/v1/apps", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for apps :O!");
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
setApps(responseJson)
|
||||
})
|
||||
.catch(error => {
|
||||
console.log("App error: ", error);
|
||||
})
|
||||
}
|
||||
|
||||
const ShowExecutionResults = (props) => {
|
||||
const { executionData } = props;
|
||||
|
||||
if (executionData === undefined || executionData === null || executionData === {}) {
|
||||
return null
|
||||
}
|
||||
|
||||
const executionMargin = 20
|
||||
const defaultReturn =
|
||||
<div style={{marginTop: executionMargin, }}>
|
||||
<Typography variant="h6" style={{color: theme.palette.primaryColor}}>
|
||||
No results yet
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
if (executionData.results === undefined || executionData.results === null) {
|
||||
return defaultReturn
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{marginTop: executionMargin, }}>
|
||||
<Typography variant="h6" style={{color: theme.palette.primaryColor}}>
|
||||
Results
|
||||
</Typography>
|
||||
|
||||
{executionData.results.map((data, index) => {
|
||||
if (executionData.results.length !== 1 && (data.status === "SKIPPED")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// FIXME: The latter replace doens't really work if ' is used in a string
|
||||
var showResult = data.result.trim();
|
||||
const validate = validateJson(showResult);
|
||||
|
||||
const curapp = apps.find((a) => a.name === data.action.app_name && a.app_version === data.action.app_version);
|
||||
const imgsize = 50;
|
||||
const statusColor = data.status === "FINISHED" || data.status === "SUCCESS" ? green : data.status === "ABORTED" || data.status === "FAILURE" ? "red" : yellow;
|
||||
|
||||
var imgSrc = curapp === undefined ? "" : curapp.large_image;
|
||||
if (
|
||||
imgSrc.length === 0 &&
|
||||
workflow.actions !== undefined &&
|
||||
workflow.actions !== null
|
||||
) {
|
||||
// Look for the node in the workflow
|
||||
const action = workflow.actions.find(
|
||||
(action) => action.id === data.action.id
|
||||
);
|
||||
if (action !== undefined && action !== null) {
|
||||
imgSrc = action.large_image;
|
||||
}
|
||||
}
|
||||
|
||||
var actionimg =
|
||||
curapp === null ? null : (
|
||||
<img
|
||||
alt={data.action.app_name}
|
||||
src={imgSrc}
|
||||
style={{
|
||||
marginRight: 20,
|
||||
width: imgsize,
|
||||
height: imgsize,
|
||||
border: `2px solid ${statusColor}`,
|
||||
borderRadius:
|
||||
executionData.start === data.action.id ? 25 : 5,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
if (data.action.app_name === "shuffle-subflow") {
|
||||
//const parsedImage = triggers[2].large_image;
|
||||
//actionimg = (
|
||||
// <img
|
||||
// alt={"Shuffle Subflow"}
|
||||
// src={parsedImage}
|
||||
// style={{
|
||||
// marginRight: 20,
|
||||
// width: imgsize,
|
||||
// height: imgsize,
|
||||
// border: `2px solid ${statusColor}`,
|
||||
// borderRadius:
|
||||
// executionData.start === data.action.id ? 25 : 5,
|
||||
// }}
|
||||
// />
|
||||
//);
|
||||
} else if (data.action.app_name === "User Input") {
|
||||
//actionimg = (
|
||||
// <img
|
||||
// alt={"Shuffle Subflow"}
|
||||
// src={triggers[3].large_image}
|
||||
// style={{
|
||||
// marginRight: 20,
|
||||
// width: imgsize,
|
||||
// height: imgsize,
|
||||
// border: `2px solid ${statusColor}`,
|
||||
// borderRadius:
|
||||
// executionData.start === data.action.id ? 25 : 5,
|
||||
// }}
|
||||
// />
|
||||
//);
|
||||
}
|
||||
|
||||
if (validate.valid && typeof validate.result === "string") {
|
||||
validate.result = JSON.parse(validate.result);
|
||||
}
|
||||
|
||||
if (validate.valid && typeof validate.result === "object") {
|
||||
if (
|
||||
validate.result.result !== undefined &&
|
||||
validate.result.result !== null
|
||||
) {
|
||||
try {
|
||||
validate.result.result = JSON.parse(validate.result.result);
|
||||
} catch (e) {
|
||||
//console.log("ERROR PARSING: ", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var similarActionsView = null
|
||||
if (data.similar_actions !== undefined && data.similar_actions !== null) {
|
||||
var minimumMatch = 85
|
||||
var matching_executions = []
|
||||
if (data.similar_actions !== undefined && data.similar_actions !== null) {
|
||||
for (let [k,kval] in Object.entries(data.similar_actions)){
|
||||
if (data.similar_actions.hasOwnProperty(k)) {
|
||||
if (data.similar_actions[k].similarity > minimumMatch) {
|
||||
matching_executions.push(data.similar_actions[k].execution_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (matching_executions.length !== 0) {
|
||||
var parsed_url = matching_executions.join(",")
|
||||
|
||||
similarActionsView =
|
||||
<Tooltip
|
||||
color="primary"
|
||||
title="See executions with similar results (not identical)"
|
||||
placement="top"
|
||||
style={{ zIndex: 50000, marginLeft: 50, }}
|
||||
>
|
||||
<IconButton
|
||||
style={{
|
||||
marginTop: "auto",
|
||||
marginBottom: "auto",
|
||||
height: 30,
|
||||
paddingLeft: 0,
|
||||
width: 30,
|
||||
}}
|
||||
onClick={() => {
|
||||
navigate(`?execution_highlight=${parsed_url}`)
|
||||
}}
|
||||
>
|
||||
<PreviewIcon style={{ color: "rgba(255,255,255,0.5)" }} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
marginBottom: 20,
|
||||
border:
|
||||
data.action.sub_action === true
|
||||
? "1px solid rgba(255,255,255,0.3)"
|
||||
: "1px solid rgba(255,255,255, 0.3)",
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
padding: "15px 10px 10px 10px",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
onMouseOver={() => {
|
||||
}}
|
||||
onMouseOut={() => {
|
||||
}}
|
||||
>
|
||||
<div style={{ marginBottom: 5, display: "flex" }}>
|
||||
<Typography variant="body1">
|
||||
<b>Status </b>
|
||||
</Typography>
|
||||
<Typography variant="body1" color="textSecondary" style={{ marginRight: 15, }}>
|
||||
{data.status}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const onSubmit = (execution_id, authorization, answer) => {
|
||||
stop()
|
||||
setMessage("")
|
||||
setExecutionLoading(true)
|
||||
setExecutionData({})
|
||||
setExecutionInfo("")
|
||||
|
||||
var data = {
|
||||
"execution_argument": executionArgument
|
||||
}
|
||||
|
||||
var url = `${globalUrl}/api/v1/workflows/${props.match.params.key}/execute`
|
||||
var fetchBody = {
|
||||
mode: 'cors',
|
||||
credentials: 'include',
|
||||
crossDomain: true,
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
},
|
||||
}
|
||||
|
||||
if (answer !== undefined && execution_id !== undefined && authorization !== undefined) {
|
||||
url += `?reference_execution=${execution_id}&authorization=${authorization}&answer=${answer}`
|
||||
data = {}
|
||||
fetchBody.method = "GET"
|
||||
} else {
|
||||
fetchBody.method = "POST"
|
||||
fetchBody.body = JSON.stringify(data)
|
||||
}
|
||||
|
||||
fetch(url, fetchBody)
|
||||
.then((response) => {
|
||||
if (response.status !== 200 && response.status !== 201) {
|
||||
|
||||
if (answer !== undefined && execution_id !== undefined && authorization !== undefined) {
|
||||
setExecutionLoading(false)
|
||||
setExecutionRunning(true);
|
||||
setExecutionRequest({
|
||||
"execution_id": execution_id,
|
||||
"authorization": authorization,
|
||||
})
|
||||
|
||||
start();
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then(responseJson => {
|
||||
setExecutionLoading(false)
|
||||
if (responseJson["success"] === false) {
|
||||
console.log("Failed sending execution request")
|
||||
} else {
|
||||
console.log("Started execution")
|
||||
|
||||
if (answer !== undefined && answer !== null) {
|
||||
console.log("Skipping start")
|
||||
} else {
|
||||
setExecutionRunning(true);
|
||||
setExecutionRequest(responseJson)
|
||||
start();
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
//setExecutionInfo("Error in workflow startup: " + error)
|
||||
setExecutionLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
const getWorkflow = (workflow_id) => {
|
||||
fetch(globalUrl + "/api/v1/workflows/" + workflow_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 workflows :O!");
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
// Not sure why this is necessary.
|
||||
if (responseJson.isValid === undefined) {
|
||||
responseJson.isValid = true;
|
||||
}
|
||||
|
||||
if (responseJson.errors === undefined) {
|
||||
responseJson.errors = [];
|
||||
}
|
||||
|
||||
if (responseJson.actions === undefined || responseJson.actions === null) {
|
||||
responseJson.actions = [];
|
||||
}
|
||||
|
||||
if (responseJson.triggers === undefined || responseJson.triggers === null) {
|
||||
responseJson.triggers = [];
|
||||
}
|
||||
|
||||
handleGetOrg(responseJson.org_id)
|
||||
setWorkflow(responseJson);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("Get workflow error: ", error.toString());
|
||||
});
|
||||
};
|
||||
|
||||
const { start, stop } = useInterval({
|
||||
duration: 3000,
|
||||
startImmediate: true,
|
||||
callback: () => {
|
||||
fetchUpdates(executionRequest.execution_id, executionRequest.authorization)
|
||||
},
|
||||
});
|
||||
|
||||
const handleUpdateResults = (responseJson, executionRequest) => {
|
||||
if (responseJson === undefined || responseJson === null || responseJson.success === false) {
|
||||
return
|
||||
}
|
||||
|
||||
console.log("Got response: ", responseJson)
|
||||
|
||||
ReactDOM.unstable_batchedUpdates(() => {
|
||||
if (JSON.stringify(responseJson) !== JSON.stringify(executionData)) {
|
||||
// FIXME: If another is selected, don't edit..
|
||||
// Doesn't work because this is some async garbage
|
||||
if (executionData.execution_id === undefined || (responseJson.execution_id === executionData.execution_id && responseJson.results !== undefined && responseJson.results !== null)) {
|
||||
if (executionData.status !== responseJson.status || executionData.result !== responseJson.result || (executionData.results !== undefined && responseJson.results !== null && executionData.results.length !== responseJson.results.length)) {
|
||||
console.log("Updating data!")
|
||||
setExecutionData(responseJson)
|
||||
|
||||
for (var key in responseJson.results) {
|
||||
if (responseJson.results[key].status === "WAITING") {
|
||||
console.log("Found: ", responseJson.results[key])
|
||||
|
||||
const validate = validateJson(responseJson.results[key].result)
|
||||
console.log("Validate: ", validate)
|
||||
if (validate.valid && typeof validate.result === "string") {
|
||||
validate.result = JSON.parse(validate.result)
|
||||
}
|
||||
|
||||
console.log("Newresult: ", validate.result)
|
||||
if (validate.result["information"] !== undefined && validate.result["information"] !== null) {
|
||||
setWorkflowQuestion(validate.result["information"])
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log("NOT updating executiondata state.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (responseJson.status === "ABORTED" || responseJson.status === "STOPPED" || responseJson.status === "FAILURE" || responseJson.status === "WAITING") {
|
||||
stop();
|
||||
|
||||
if (executionRunning) {
|
||||
setExecutionRunning(false);
|
||||
}
|
||||
|
||||
//getWorkflowExecution(props.match.params.key, "");
|
||||
} else if (responseJson.status === "FINISHED") {
|
||||
setExecutionRunning(false)
|
||||
stop();
|
||||
//getWorkflowExecution(props.match.params.key, "");
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleGetOrg = (orgId, execution_id, authorization) => {
|
||||
if (orgId.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Just use this one?
|
||||
var url = execution_id !== undefined && authorization !== undefined ? `${globalUrl}/api/v1/orgs/${orgId}?reference_execution=${execution_id}&authorization=${authorization}` : `${globalUrl}/api/v1/orgs/${orgId}`;
|
||||
fetch(url, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status === 401) {
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (responseJson["success"] === false) {
|
||||
} else {
|
||||
if (responseJson.sync_features === undefined || responseJson.sync_features === null) {
|
||||
}
|
||||
|
||||
if (document != undefined && document.title != defaultTitle) {
|
||||
document.title = responseJson.name + " - " + defaultTitle
|
||||
}
|
||||
setSelectedOrganization(responseJson)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("Error getting org: ", error);
|
||||
});
|
||||
};
|
||||
|
||||
const fetchUpdates = (execution_id, authorization, getorg) => {
|
||||
const innerRequest = {
|
||||
"execution_id": execution_id,
|
||||
"authorization": authorization
|
||||
}
|
||||
|
||||
if (executionRequest.execution_id !== innerRequest.execution_id) {
|
||||
setExecutionRequest(innerRequest)
|
||||
}
|
||||
|
||||
if (execution_id === "" || authorization === "") {
|
||||
setExecutionLoading(false)
|
||||
setExecutionRunning(false)
|
||||
stop()
|
||||
return
|
||||
}
|
||||
|
||||
fetch(globalUrl + "/api/v1/streams/results", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
body: JSON.stringify(innerRequest),
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for stream results :O!");
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (getorg === true) {
|
||||
handleGetOrg(responseJson.org_id, execution_id, authorization)
|
||||
}
|
||||
|
||||
handleUpdateResults(responseJson, executionRequest);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("Execution result Error: ", error);
|
||||
});
|
||||
};
|
||||
|
||||
const answer = new URLSearchParams(window.location.search).get("answer")
|
||||
const execution_id = new URLSearchParams(window.location.search).get("reference_execution")
|
||||
const authorization = new URLSearchParams(window.location.search).get("authorization")
|
||||
useEffect(() => {
|
||||
getWorkflow(props.match.params.key)
|
||||
if (execution_id !== undefined && execution_id !== null && authorization !== undefined && authorization !== null) {
|
||||
console.log("Get execution: ", execution_id)
|
||||
fetchUpdates(execution_id, authorization, true)
|
||||
}
|
||||
|
||||
if (answer !== undefined && answer !== null) {
|
||||
console.log("Got answer: ", answer)
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
|
||||
const buttonBackground = "linear-gradient(to right, #f86a3e, #f34079)"
|
||||
const buttonStyle = {borderRadius: 25, height: 50, fontSize: 18, backgroundImage: handleValidateForm(executionArgument) || executionLoading ? buttonBackground : "grey", color: "white"}
|
||||
|
||||
console.log("execdata: ", executionData)
|
||||
const disabledButtons = message.length > 0 || executionData.status === "FINISHED" || executionData.status === "ABORTED"
|
||||
|
||||
const organization = selectedOrganization !== undefined && selectedOrganization !== null ? selectedOrganization.name : "Unknown"
|
||||
const contact = selectedOrganization !== undefined && selectedOrganization !== null && selectedOrganization.org !== undefined && selectedOrganization.org !== null? selectedOrganization.org : "support@shuffler.io"
|
||||
//const contact = selectedOrganization !== undefined && selectedOrganization !== null && selectedOrganization.contact !== undefined && selectedOrganization.contact !== null? selectedOrganization.contact : "support@shuffler.io"
|
||||
|
||||
const image = selectedOrganization !== undefined && selectedOrganization !== null && selectedOrganization.image !== undefined && selectedOrganization.image !== null && selectedOrganization.image !== "" ? selectedOrganization.image : theme.palette.defaultImage
|
||||
|
||||
console.log("IMG: ", image, "ORG: ", selectedOrganization)
|
||||
|
||||
if (!disabledButtons && answer !== undefined && answer !== null && organization !== "Unknown" && buttonClicked.length === 0) {
|
||||
console.log("Finding button!")
|
||||
// Find the button
|
||||
var buttonid = ""
|
||||
if (answer === "false") {
|
||||
buttonid = "abort_execution"
|
||||
} else if (answer === "true") {
|
||||
buttonid = "continue_execution"
|
||||
}
|
||||
|
||||
if (buttonid !== "") {
|
||||
const foundButton = document.getElementById(buttonid)
|
||||
console.log("Button: ", foundButton)
|
||||
if (foundButton !== undefined && foundButton !== null) {
|
||||
foundButton.click()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const basedata =
|
||||
<div style={bodyDivStyle}>
|
||||
<Paper style={boxStyle}>
|
||||
<form onSubmit={() => {onSubmit()}} style={{margin: "15px 15px 15px 15px"}}>
|
||||
|
||||
<img
|
||||
alt={workflow.name}
|
||||
src={image}
|
||||
style={{
|
||||
marginRight: 20,
|
||||
width: 100,
|
||||
height: 100,
|
||||
border: `2px solid ${green}`,
|
||||
borderRadius: 50,
|
||||
position: "absolute",
|
||||
top: -50,
|
||||
left: 200,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Typography variant="h6" style={{marginBottom: 10, marginTop: 50, textAlign: "center", }}>
|
||||
{organization}
|
||||
</Typography>
|
||||
<Typography variant="body1" color="textSecondary" style={{marginBottom: 15, marginTop: 0, textAlign: "center",}}>
|
||||
{contact}
|
||||
</Typography>
|
||||
<Divider style={{marginTop: 20, marginBottom: 20, }}/>
|
||||
<Typography color="textSecondary">{message}</Typography>
|
||||
|
||||
{answer !== undefined && answer !== null ? null :
|
||||
<Typography variant="h6" style={{marginBottom: 15, }}><b>Workflow: </b>{workflow.name}</Typography>
|
||||
}
|
||||
|
||||
{executionData !== undefined && executionData !== null && executionData !== {} && executionData.status !== undefined && (answer === undefined || answer === null) ?
|
||||
<div style={{ marginBottom: 5, display: "flex" }}>
|
||||
<Typography variant="body1">
|
||||
<b>Status </b>
|
||||
</Typography>
|
||||
<Typography variant="body1" color="textSecondary" style={{ marginRight: 15, }}>
|
||||
{executionData.status}
|
||||
</Typography>
|
||||
</div>
|
||||
: null}
|
||||
|
||||
{workflowQuestion.length > 0 ?
|
||||
<Typography variant="body1" style={{ marginBottom: 35, marginTop: 30, marginRight: 15, textAlign: "center", whiteSpace: "pre-line", }}>
|
||||
{workflowQuestion}
|
||||
</Typography>
|
||||
: null}
|
||||
|
||||
{answer !== undefined && answer !== null ? null :
|
||||
<span>
|
||||
Execution Argument
|
||||
<div style={{marginBottom: 5}}>
|
||||
<TextField
|
||||
color="primary"
|
||||
style={{backgroundColor: theme.palette.inputColor, marginTop: 5, }}
|
||||
multiLine
|
||||
maxRows={2}
|
||||
InputProps={{
|
||||
style:{
|
||||
height: "50px",
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
fullWidth={true}
|
||||
placeholder=""
|
||||
id="emailfield"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
onChange={(e) => {
|
||||
setExecutionArgument(e.target.value)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
}
|
||||
{executionRunning ?
|
||||
<span style={{width: 50, height: 50, margin: "auto", alignItems: "center", justifyContent: "center", textAlign: "center", }}>
|
||||
<CircularProgress style={{marginTop: 20, marginBottom: 20, marginLeft: 185, }}/>
|
||||
<Typography variant="body2" style={{margin: "auto", marginTop: 20, marginBottom: 20, textAlign: "center", alignItem: "center", }} color="textSecondary">
|
||||
Status: {executionData.status}
|
||||
</Typography>
|
||||
</span>
|
||||
|
||||
:
|
||||
answer !== undefined && answer !== null ?
|
||||
<span style={{marginTop: 20, }}>
|
||||
<Typography variant="body1" style={{textAlign: "center", marginTop: 30, marginBottom: 20, }}>
|
||||
{disabledButtons ? "Already answered. Nothing to do." : ""}
|
||||
</Typography>
|
||||
{disabledButtons ? null :
|
||||
<Typography variant="body2" color="textSecondary" style={{textAlign: "center", marginTop: 10, }}>
|
||||
What do you want to do?
|
||||
</Typography>
|
||||
}
|
||||
<div fullWidth style={{width: "100%", marginTop: 10, marginBottom: 10, display: "flex", }}>
|
||||
<Button fullWidth id="continue_execution" variant="contained" disabled={disabledButtons} color="primary" style={{border: answer === "true" ? "2px solid rgba(255,255,255,0.6)" : null, flex: 1,}} onClick={() => {
|
||||
onSubmit(execution_id, authorization, true)
|
||||
|
||||
setButtonClicked("FINISHED")
|
||||
setExecutionData({
|
||||
status: "FINISHED",
|
||||
})
|
||||
}}>Continue</Button>
|
||||
<Typography variant="body1" style={{marginLeft: 3, marginRight: 3, marginTop: 3, }}>
|
||||
or
|
||||
</Typography>
|
||||
<Button fullWidth id="abort_execution" variant="contained" color="primary" disabled={disabledButtons} style={{border: answer !== "true" ? "2px solid rgba(255,255,255,0.6)" : null, flex: 1, }} onClick={() => {
|
||||
onSubmit(execution_id, authorization, false)
|
||||
|
||||
setButtonClicked("ABORTED")
|
||||
setExecutionData({
|
||||
status: "ABORTED",
|
||||
})
|
||||
}}>Stop</Button>
|
||||
</div>
|
||||
</span>
|
||||
:
|
||||
<div style={{display: "flex", marginTop: "15px"}}>
|
||||
<Button variant="contained" type="submit" color="primary" fullWidth disabled={!handleValidateForm(executionArgument) || executionLoading}>
|
||||
{executionLoading ?
|
||||
<CircularProgress color="secondary" style={{color: "white",}} /> : "Run Workflow"}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
|
||||
{buttonClicked !== undefined && buttonClicked !== null && buttonClicked !== "finished" && buttonClicked.length > 0 ?
|
||||
<img id="finalize_gif" src="/images/finalize.gif" alt="finalize workflow animation" style={{width: 150, marginLeft: 125, borderRadius: theme.palette.borderRadius, }}
|
||||
onLoad={() => {
|
||||
console.log("Img loaded.")
|
||||
setTimeout(() => {
|
||||
console.log("Img closing.")
|
||||
setButtonClicked("finished")
|
||||
|
||||
}, 1250)
|
||||
|
||||
}}
|
||||
/>
|
||||
: null}
|
||||
|
||||
<div style={{marginTop: "10px"}}>
|
||||
{executionInfo}
|
||||
</div>
|
||||
|
||||
{answer !== undefined && answer !== null ? null :
|
||||
<ShowExecutionResults executionData={executionData} />
|
||||
}
|
||||
</form>
|
||||
</Paper>
|
||||
</div>
|
||||
|
||||
const loadedCheck = isLoaded ?
|
||||
<div>
|
||||
{basedata}
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
</div>
|
||||
|
||||
return (
|
||||
<div>
|
||||
{loadedCheck}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RunWorkflow;
|
||||
@@ -0,0 +1,190 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
|
||||
import theme from '../theme.jsx';
|
||||
import {isMobile} from "react-device-detect";
|
||||
import AppGrid from "../components/AppGrid.jsx"
|
||||
import WorkflowGrid from "../components/WorkflowGrid.jsx"
|
||||
import CreatorGrid from "../components/CreatorGrid.jsx"
|
||||
import DocsGrid from "../components/DocsGrid.jsx"
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import {
|
||||
Tabs,
|
||||
Tab,
|
||||
} from "@material-ui/core";
|
||||
|
||||
import {
|
||||
Apps as AppsIcon,
|
||||
Polymer as PolymerIcon,
|
||||
EmojiObjects as EmojiObjectsIcon,
|
||||
Description as DescriptionIcon,
|
||||
} from "@material-ui/icons";
|
||||
|
||||
|
||||
const bodyDivStyle = {
|
||||
margin: "auto",
|
||||
maxWidth: 1024,
|
||||
scrollX: "hidden",
|
||||
overflowX: "hidden",
|
||||
}
|
||||
|
||||
// Should be different if logged in :|
|
||||
const Search = (props) => {
|
||||
const { globalUrl, isLoaded, serverside, userdata, hidemargins, } = props;
|
||||
let navigate = useNavigate();
|
||||
|
||||
const [curTab, setCurTab] = useState(0);
|
||||
const iconStyle = { marginRight: 10 };
|
||||
|
||||
useEffect(() => {
|
||||
if (serverside !== true && window.location.search !== undefined && window.location.search !== null) {
|
||||
const urlSearchParams = new URLSearchParams(window.location.search)
|
||||
const params = Object.fromEntries(urlSearchParams.entries())
|
||||
const foundTab = params["tab"]
|
||||
if (foundTab !== null && foundTab !== undefined) {
|
||||
for (var key in Object.keys(views)) {
|
||||
const value = views[key]
|
||||
console.log(key, value)
|
||||
if (value === foundTab) {
|
||||
setConfig("", key)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
if (serverside === true) {
|
||||
return null
|
||||
}
|
||||
|
||||
const boxStyle = {
|
||||
color: "white",
|
||||
flex: "1",
|
||||
marginLeft: 10,
|
||||
marginRight: 10,
|
||||
paddingLeft: 30,
|
||||
paddingRight: 30,
|
||||
paddingBottom: 30,
|
||||
paddingTop: hidemargins === true ? 0 : 30,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
overflowX: "hidden",
|
||||
minHeight: 400,
|
||||
}
|
||||
|
||||
const views = {
|
||||
0: "apps",
|
||||
1: "workflows",
|
||||
2: "docs",
|
||||
3: "creators",
|
||||
}
|
||||
|
||||
const setConfig = (event, inputValue) => {
|
||||
const newValue = parseInt(inputValue)
|
||||
|
||||
setCurTab(newValue)
|
||||
if (newValue === 0) {
|
||||
document.title = "Shuffle - search - apps";
|
||||
} else if (newValue === 1) {
|
||||
document.title = "Shuffle - search - workflows";
|
||||
} else if (newValue === 2) {
|
||||
document.title = "Shuffle - search - documentation";
|
||||
} else if (newValue === 3) {
|
||||
document.title = "Shuffle - search - creators";
|
||||
} else {
|
||||
document.title = "Shuffle - search";
|
||||
}
|
||||
|
||||
|
||||
const urlSearchParams = new URLSearchParams(window.location.search)
|
||||
const params = Object.fromEntries(urlSearchParams.entries())
|
||||
const foundQuery = params["q"]
|
||||
var extraQ = ""
|
||||
if (foundQuery !== null && foundQuery !== undefined) {
|
||||
extraQ = "&q="+foundQuery
|
||||
}
|
||||
|
||||
|
||||
if ((serverside === false || serverside === undefined) && window.location.pathname.includes("/search")) {
|
||||
navigate(`/search?tab=${views[newValue]}`+extraQ)
|
||||
}
|
||||
}
|
||||
|
||||
if (isLoaded === false) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
// Random names for type & autoComplete. Didn't research :^)
|
||||
const landingpageDataBrowser =
|
||||
<div style={{paddingBottom: hidemargins === true ? 0 : 100, color: "white", }}>
|
||||
<div style={boxStyle}>
|
||||
<Tabs
|
||||
style={{width: 610, margin: "auto", marginTop: hidemargins === true ? 0 : 25, }}
|
||||
value={curTab}
|
||||
indicatorColor="primary"
|
||||
textColor="secondary"
|
||||
onChange={setConfig}
|
||||
aria-label="disabled tabs example"
|
||||
variant="scrollable"
|
||||
scrollButtons="auto"
|
||||
>
|
||||
<Tab
|
||||
label=<span>
|
||||
<AppsIcon style={iconStyle} /> Apps
|
||||
</span>
|
||||
/>
|
||||
<Tab
|
||||
label=<span>
|
||||
<PolymerIcon style={iconStyle} /> Workflows
|
||||
</span>
|
||||
/>
|
||||
<Tab
|
||||
label=<span>
|
||||
<DescriptionIcon style={iconStyle} /> Docs
|
||||
</span>
|
||||
/>
|
||||
<Tab
|
||||
label=<span>
|
||||
<EmojiObjectsIcon style={iconStyle} /> Creators
|
||||
</span>
|
||||
/>
|
||||
</Tabs>
|
||||
{curTab === 0 ?
|
||||
<AppGrid maxRows={3} showSuggestion={true} globalUrl={globalUrl} isMobile={isMobile} userdata={userdata} />
|
||||
:
|
||||
curTab === 1 ?
|
||||
window.location.pathname === "/search" ?
|
||||
<WorkflowGrid maxRows={3} showSuggestion={true} globalUrl={globalUrl} isMobile={isMobile} userdata={userdata} />
|
||||
:
|
||||
<WorkflowGrid maxRows={3} showSuggestion={true} globalUrl={globalUrl} isMobile={isMobile} userdata={userdata} />
|
||||
:
|
||||
curTab === 2 ?
|
||||
<DocsGrid maxRows={6} parsedXs={12} showSuggestion={true} globalUrl={globalUrl} isMobile={isMobile} userdata={userdata} />
|
||||
:
|
||||
curTab === 3 ?
|
||||
<CreatorGrid parsedXs={4} showSuggestion={true} globalUrl={globalUrl} isMobile={isMobile} userdata={userdata} />
|
||||
:
|
||||
null}
|
||||
</div>
|
||||
</div>
|
||||
//{/*alternativeView={true} />*/}
|
||||
|
||||
const loadedCheck = isLoaded ?
|
||||
<div>
|
||||
<div style={bodyDivStyle}>{landingpageDataBrowser}</div>
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
</div>
|
||||
|
||||
// #1f2023?
|
||||
return(
|
||||
<div style={{}}>
|
||||
{loadedCheck}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Search;
|
||||
@@ -0,0 +1,172 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
|
||||
import ReactGA from "react-ga4";
|
||||
import {
|
||||
Typography,
|
||||
CircularProgress,
|
||||
Button,
|
||||
} from "@material-ui/core";
|
||||
import theme from '../theme.jsx';
|
||||
import { useAlert } from "react-alert";
|
||||
|
||||
import AuthenticationOauth2 from "../components/Oauth2Auth.jsx";
|
||||
import AuthenticationWindow from "../components/AuthenticationWindow.jsx";
|
||||
import { base64_decode, appCategories } from "../views/AppCreator.jsx";
|
||||
|
||||
const SetAuthentication = (props) => {
|
||||
const { globalUrl, serverside } = props;
|
||||
|
||||
const [app, setApp] = useState({});
|
||||
const [isAppLoaded, setIsAppLoaded] = useState(false);
|
||||
const [loadFail, setLoadFail] = useState("");
|
||||
const [appAuthentication, setAppAuthentication] = React.useState([]);
|
||||
|
||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
||||
const alert = useAlert();
|
||||
|
||||
const parseIncomingOpenapiData = (data) => {
|
||||
if (data.app === undefined || data.app === null) {
|
||||
return
|
||||
}
|
||||
|
||||
// Should basically always be true if openapi exists too
|
||||
var parsedBaseapp = ""
|
||||
try {
|
||||
parsedBaseapp = base64_decode(data.app)
|
||||
} catch (e) {
|
||||
console.log("Failed JSON parsing: ", e)
|
||||
parsedBaseapp = data
|
||||
}
|
||||
|
||||
var parsedapp = JSON.parse(parsedBaseapp)
|
||||
parsedapp.name = parsedapp.name.replaceAll("_", " ");
|
||||
setApp(parsedapp);
|
||||
|
||||
document.title = parsedapp.name + " App Auth";
|
||||
|
||||
}
|
||||
|
||||
console.log("App: ", app)
|
||||
|
||||
const getApp = (appid) => {
|
||||
if (serverside === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`${globalUrl}/api/v1/apps/${appid}/config`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
if (isCloud) {
|
||||
ReactGA.event({
|
||||
category: "appauth",
|
||||
action: `app_not_found`,
|
||||
label: appid,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (isCloud) {
|
||||
ReactGA.event({
|
||||
category: "appauth",
|
||||
action: `app_found`,
|
||||
label: appid,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (responseJson.success === false || responseJson.success === undefined) {
|
||||
alert.error("Failed to get the app. Does it exist?")
|
||||
setIsAppLoaded(true)
|
||||
return;
|
||||
}
|
||||
|
||||
parseIncomingOpenapiData(responseJson);
|
||||
})
|
||||
.catch((error) => {
|
||||
alert.error("Error in app fetch: " + error.toString());
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Find the ID for the app from the "app_id" query
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const appid = urlParams.get("app_id");
|
||||
if (appid === null) {
|
||||
setLoadFail(
|
||||
<span>
|
||||
<Typography variant="h4">
|
||||
Failed to load the app. Please contact your provider or support@shuffler.io if this persists
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => window.location.reload()}
|
||||
>
|
||||
Reload Window
|
||||
</Button>
|
||||
</span>
|
||||
)
|
||||
} else {
|
||||
getApp(appid);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Handle:
|
||||
// 1. Check for org_id, authentication, and app keys in queries
|
||||
// 2. Load the app auth info from the orgs' apps
|
||||
// 3. Help them set info for the app
|
||||
// Make sure to test both private and public apps
|
||||
|
||||
const appname = app.name !== undefined ? app.name : "";
|
||||
|
||||
return (
|
||||
<div style={{width: 1000, margin: "auto", marginTop: 50, }}>
|
||||
{loadFail !== "" ?
|
||||
loadFail
|
||||
:
|
||||
<div>
|
||||
<Typography variant="h4" style={{marginBottom: 20,}}>
|
||||
Configure {appname} Authentication
|
||||
</Typography>
|
||||
{app.authentication === undefined || app.authentication === null || app.authentication.length === 0 ?
|
||||
null
|
||||
:
|
||||
app.authentication.type === "oauth2" ?
|
||||
<AuthenticationOauth2
|
||||
selectedApp={app}
|
||||
selectedAction={{
|
||||
"app_name": app.name,
|
||||
"app_id": app.id,
|
||||
"app_version": app.version,
|
||||
"large_image": app.large_image,
|
||||
}}
|
||||
authenticationType={app.authentication}
|
||||
isCloud={true}
|
||||
authButtonOnly={true}
|
||||
getAppAuthentication={undefined}
|
||||
/>
|
||||
:
|
||||
<AuthenticationWindow
|
||||
globalUrl={globalUrl}
|
||||
selectedApp={app}
|
||||
authFieldsOnly={true}
|
||||
getAppAuthentication={undefined}
|
||||
appAuthentication={appAuthentication}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
export default SetAuthentication;
|
||||
@@ -0,0 +1,544 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import ReactGA from 'react-ga4';
|
||||
import WelcomeForm2 from "../components/WelcomeForm2.jsx";
|
||||
import Stepper from "@material-ui/core/Stepper";
|
||||
import Step from "@material-ui/core/Step";
|
||||
import StepLabel from "@material-ui/core/StepLabel";
|
||||
import AppFramework from "../components/AppFramework.jsx";
|
||||
import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
|
||||
import {
|
||||
Grid,
|
||||
Container,
|
||||
Fade,
|
||||
Typography,
|
||||
Paper,
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
CardActionArea,
|
||||
} from '@mui/material';
|
||||
import theme from '../theme.jsx';
|
||||
import { useNavigate, Link } from "react-router-dom";
|
||||
import Drift from "react-driftjs";
|
||||
|
||||
const Welcome = (props) => {
|
||||
const { globalUrl, surfaceColor, newColor, mini, inputColor, userdata, isLoggedIn, isLoaded, serverside } = props;
|
||||
const [skipped, setSkipped] = React.useState(new Set());
|
||||
const [inputUsecase, setInputUsecase] = useState({});
|
||||
const [frameworkData, setFrameworkData] = useState(undefined);
|
||||
const [discoveryWrapper, setDiscoveryWrapper] = useState(undefined);
|
||||
const [activeStep, setActiveStep] = React.useState(1);
|
||||
const [apps, setApps] = React.useState([]);
|
||||
const [defaultSearch, setDefaultSearch] = React.useState("")
|
||||
const [selectionOpen, setSelectionOpen] = React.useState(false)
|
||||
const [showWelcome, setShowWelcome] = React.useState(false)
|
||||
const [usecases, setUsecases] = React.useState([]);
|
||||
const [workflows, setWorkflows] = React.useState([]);
|
||||
|
||||
let navigate = useNavigate();
|
||||
//if (serverside === false && isLoaded === true && isLoggedIn === false) {
|
||||
// console.log("Redirecting to login?")
|
||||
// console.log(window.location.pathname)
|
||||
// console.log(window.location)
|
||||
// navigate(`/login?view=${window.location.pathname}${window.location.search}`)
|
||||
//}
|
||||
|
||||
const isCloud =
|
||||
window.location.host === "localhost:3002" ||
|
||||
window.location.host === "shuffler.io";
|
||||
|
||||
const [steps, setSteps] = useState([
|
||||
"Help us get to know you",
|
||||
"Find your Apps",
|
||||
"Discover Usecases",
|
||||
])
|
||||
|
||||
|
||||
const handleKeysetting = (categorydata, workflows) => {
|
||||
//workflows[0].category = ["detect"]
|
||||
//workflows[0].usecase_ids = ["Correlate tickets"]
|
||||
|
||||
if (workflows !== undefined && workflows !== null) {
|
||||
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()) {
|
||||
//console.log("Got match: ", workflow.usecase_ids[usecasekey])
|
||||
|
||||
category.matches.push({
|
||||
"workflow": workflow.id,
|
||||
"category": subcategory.name,
|
||||
})
|
||||
subcategory.matches.push(workflow.id)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (subcategory.matches.length > 0) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newcategories.push(category)
|
||||
}
|
||||
|
||||
setUsecases(newcategories)
|
||||
} else {
|
||||
setUsecases(categorydata)
|
||||
}
|
||||
|
||||
setWorkflows(workflows)
|
||||
}
|
||||
|
||||
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) => {
|
||||
if (responseJson.success !== false) {
|
||||
handleKeysetting(responseJson, workflows)
|
||||
} else {
|
||||
//setWorkflows(workflows);
|
||||
//setWorkflowDone(true);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("Usecase error: " + 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) {
|
||||
console.log("Status not 200 for workflows :O!: ", response.status);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (responseJson !== undefined) {
|
||||
var newarray = []
|
||||
for (var key in responseJson) {
|
||||
const wf = responseJson[key]
|
||||
if (wf.public === true) {
|
||||
continue
|
||||
}
|
||||
|
||||
newarray.push(wf)
|
||||
}
|
||||
|
||||
// Workflows are set in here
|
||||
fetchUsecases(newarray)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("err in get workflows: ", error.toString());
|
||||
})
|
||||
}
|
||||
|
||||
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) {
|
||||
setFrameworkData({})
|
||||
|
||||
if (responseJson.reason !== undefined) {
|
||||
//alert.error("Failed loading: " + responseJson.reason)
|
||||
} else {
|
||||
//alert.error("Failed to load framework for your org.")
|
||||
}
|
||||
} else {
|
||||
setFrameworkData(responseJson)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("err in framework: ", error.toString());
|
||||
})
|
||||
}
|
||||
|
||||
const getApps = () => {
|
||||
fetch(globalUrl + "/api/v1/apps", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for apps :O!");
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
setApps(responseJson);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("App loading error: "+error.toString());
|
||||
});
|
||||
}
|
||||
|
||||
const usecaseButtons = [{
|
||||
"name": "Phishing",
|
||||
"usecase": "Email management",
|
||||
"color": "#C51152",
|
||||
}, {
|
||||
"name": "Enrichment",
|
||||
"usecase": "2. Enrich",
|
||||
"color": "#F4C20D",
|
||||
}, {
|
||||
"name": "Detection",
|
||||
"usecase": "3. Detect",
|
||||
"color": "#3CBA54",
|
||||
}, {
|
||||
"name": "Response",
|
||||
"usecase": "4. Respond",
|
||||
"color": "#4885ED",
|
||||
}]
|
||||
|
||||
const handleSetSearch = (input, orgupdate) => {
|
||||
if (input !== defaultSearch) {
|
||||
setDefaultSearch(input)
|
||||
setSelectionOpen(false)
|
||||
setTimeout(function(){
|
||||
setSelectionOpen(true)
|
||||
}, 150);
|
||||
|
||||
//if (userdata !== undefined && userdata.active_org !== undefined && userdata.active_org.id !== undefined) {
|
||||
// sendOrgUpdate("", "", userdata.active_org.id, orgupdate)
|
||||
//}
|
||||
} else {
|
||||
setDefaultSearch("")
|
||||
setSelectionOpen(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getFramework()
|
||||
getApps()
|
||||
getAvailableWorkflows()
|
||||
|
||||
if (
|
||||
window.location.search !== undefined &&
|
||||
window.location.search !== null
|
||||
) {
|
||||
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||
const params = Object.fromEntries(urlSearchParams.entries());
|
||||
const foundTab = params["tab"];
|
||||
if (foundTab !== null && foundTab !== undefined && !isNaN(foundTab)) {
|
||||
setShowWelcome(true)
|
||||
if (foundTab === 3 || foundTab === "3") {
|
||||
handleSetSearch(usecaseButtons[0].name, usecaseButtons[0].usecase)
|
||||
}
|
||||
|
||||
setActiveStep(foundTab-1)
|
||||
} else {
|
||||
//navigate(`/welcome?tab=1`)
|
||||
navigate(`/welcome?tab=2`)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const isStepSkipped = step => {
|
||||
return skipped.has(step)
|
||||
}
|
||||
|
||||
const paperObject = {
|
||||
flex: 1,
|
||||
padding: 0,
|
||||
textAlign: "center",
|
||||
maxWidth: 300,
|
||||
minWidth: 300,
|
||||
backgroundColor: theme.palette.surfaceColor,
|
||||
color: "white",
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
}
|
||||
|
||||
const actionObject = {
|
||||
padding: "35px",
|
||||
maxHeight: 300,
|
||||
minHeight: 300,
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
}
|
||||
|
||||
const imageStyle = {
|
||||
width: 150,
|
||||
// height: 150,
|
||||
// margin: "auto",
|
||||
// marginTop: 10,
|
||||
borderRadius: 75,
|
||||
objectFit: "scale-down",
|
||||
}
|
||||
const buttonStyle = {
|
||||
borderRadius: 8,
|
||||
height: 51,
|
||||
width: 464,
|
||||
fontSize: 16,
|
||||
background: "linear-gradient(89.83deg, #FF8444 0.13%, #F2643B 99.84%)",
|
||||
padding: "16px 24px",
|
||||
top: 75,
|
||||
margin: "auto",
|
||||
itemAlign: "center",
|
||||
}
|
||||
|
||||
const defaultImage = "/images/experienced.png"
|
||||
const experienced_image = userdata !== undefined && userdata !== null && userdata.active_org !== undefined && userdata.active_org.image !== undefined && userdata.active_org.image !== null && userdata.active_org.image !== "" ? userdata.active_org.image : defaultImage
|
||||
return (
|
||||
<div style={{width: 1000, margin: "auto", paddingBottom: 150, minHeight: 1500, marginTop: 50, }}>
|
||||
{/*
|
||||
<div style={{position: "fixed", bottom: 110, right: 110, display: "flex", }}>
|
||||
<img src="/images/Arrow.png" style={{width: 250, height: "100%",}} />
|
||||
</div>
|
||||
*/}
|
||||
{showWelcome === true ?
|
||||
<div>
|
||||
<div style={{minWidth: 500, maxWidth: 500, margin: "auto", marginTop: isCloud ? "auto" : 20, }}>
|
||||
<Stepper
|
||||
activeStep={activeStep}
|
||||
color="primary"
|
||||
style={{
|
||||
backgroundColor: theme.palette.platformColor,
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
padding: 12,
|
||||
border: "1px solid rgba(255,255,255,0.3)",
|
||||
maxWidth: 500,
|
||||
color: "white",
|
||||
}}
|
||||
>
|
||||
{steps.map((label, index) => {
|
||||
const stepProps = {}
|
||||
const labelProps = {}
|
||||
//if (isStepOptional(index)) {
|
||||
// labelProps.optional = "optional"
|
||||
//}
|
||||
|
||||
if (isStepSkipped(index)) {
|
||||
stepProps.completed = false;
|
||||
}
|
||||
|
||||
return (
|
||||
<Step key={label} {...stepProps} style={{maxWidth: 160, color: "white", }}>
|
||||
<StepLabel {...labelProps} style={{marginLeft: 10, color: "white",}}>
|
||||
{label}
|
||||
</StepLabel>
|
||||
</Step>
|
||||
)
|
||||
})}
|
||||
</Stepper>
|
||||
</div>
|
||||
<Grid container spacing={2} style={{ padding: 0, maxWidth: 1000, minWidth: 1000, margin: "auto", }}>
|
||||
<Grid item xs={window.location.href.includes("tab=2") ? 6 : 12}>
|
||||
<div>
|
||||
{/*
|
||||
<WelcomeForm
|
||||
userdata={userdata}
|
||||
globalUrl={globalUrl}
|
||||
discoveryWrapper={discoveryWrapper}
|
||||
setDiscoveryWrapper={setDiscoveryWrapper}
|
||||
/>
|
||||
*/}
|
||||
<WelcomeForm2
|
||||
userdata={userdata}
|
||||
globalUrl={globalUrl}
|
||||
discoveryWrapper={discoveryWrapper}
|
||||
setDiscoveryWrapper={setDiscoveryWrapper}
|
||||
appFramework={frameworkData}
|
||||
getFramework={getFramework}
|
||||
steps={steps}
|
||||
skipped={skipped}
|
||||
setSkipped={setSkipped}
|
||||
activeStep={activeStep}
|
||||
setActiveStep={setActiveStep}
|
||||
getApps={getApps}
|
||||
apps={apps}
|
||||
handleSetSearch={handleSetSearch}
|
||||
usecaseButtons={usecaseButtons}
|
||||
defaultSearch={defaultSearch}
|
||||
setDefaultSearch={setDefaultSearch}
|
||||
selectionOpen={selectionOpen}
|
||||
setSelectionOpen={setSelectionOpen}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
{frameworkData === undefined || window.location.href.includes("tab=1") || window.location.href.includes("tab=3") ? null :
|
||||
<div style={{marginTop: 25, }}>
|
||||
<Typography variant="h6" style={{textAlign: "center", marginBottom: 25, }}>
|
||||
App Framework
|
||||
</Typography>
|
||||
<Fade>
|
||||
<AppFramework
|
||||
inputUsecase={inputUsecase}
|
||||
frameworkData={frameworkData}
|
||||
setFrameworkData={setFrameworkData}
|
||||
selectedOption={"Draw"}
|
||||
showOptions={false}
|
||||
isLoaded={true}
|
||||
isLoggedIn={true}
|
||||
globalUrl={globalUrl}
|
||||
size={0.78}
|
||||
color={theme.palette.platformColor}
|
||||
discoveryWrapper={discoveryWrapper}
|
||||
setDiscoveryWrapper={setDiscoveryWrapper}
|
||||
apps={apps}
|
||||
inputUsecases={usecases}
|
||||
setInputUsecases={setUsecases}
|
||||
/>
|
||||
</Fade>
|
||||
</div>
|
||||
}
|
||||
</Grid>
|
||||
</div>
|
||||
:
|
||||
<Fade in={true}>
|
||||
<div style={{maxWidth: 700, margin: "auto", marginTop: 50, }}>
|
||||
{/*
|
||||
<div style={{display:"flex"}}>
|
||||
<ArrowBackIosIcon style={{color: "#9E9E9E",}} onClick={() => {
|
||||
navigate("/login")
|
||||
}}/>
|
||||
<Typography variant="body1" style={{color: "#9E9E9E",textAlign: "center", marginBottom: 50, paddingRight: "366px"}} onClick={() => {
|
||||
navigate("/login")
|
||||
}}>
|
||||
Back
|
||||
</Typography>
|
||||
</div>
|
||||
*/}
|
||||
<Typography variant="h4" style={{color: "#F1F1F1", textAlign: "center", marginTop: 50, }}>
|
||||
Help us get to know you
|
||||
</Typography>
|
||||
<Typography variant="body1" style={{color: "#9E9E9E", textAlign: "center", marginBottom: 50,}}>
|
||||
We will use this information to personalize your automation
|
||||
</Typography>
|
||||
<div style={{display: "flex", marginTop: 70, width: 700, margin: "auto",}}>
|
||||
<div style={{border: "1px solid #806BFF", borderRadius: theme.palette.borderRadius, }}>
|
||||
<Card style={paperObject} onClick={() => {
|
||||
if (isCloud) {
|
||||
ReactGA.event({
|
||||
category: "welcome",
|
||||
action: "click_welcome_continue",
|
||||
label: "",
|
||||
})
|
||||
} else {
|
||||
//setActiveStep(1)
|
||||
}
|
||||
|
||||
setShowWelcome(true)
|
||||
}}>
|
||||
<CardActionArea style={actionObject}>
|
||||
<img src="/images/welcome-to-shuffle.png" style={imageStyle} />
|
||||
<Typography variant="h4" style={{color: "#F1F1F1"}}>
|
||||
New to Shuffle
|
||||
</Typography>
|
||||
<Typography variant="body1" style={{marginTop: 10, color: "rgba(255,255,255,0.8)"}}>
|
||||
Let us guide you for an easier experience
|
||||
</Typography>
|
||||
</CardActionArea>
|
||||
</Card>
|
||||
</div>
|
||||
<div style={{marginLeft: 25, marginRight: 25, }}>
|
||||
{/* <Typography style={{marginTop: 200, }}>
|
||||
OR
|
||||
</Typography> */}
|
||||
</div>
|
||||
<Card style={paperObject} onClick={() => {
|
||||
if (isCloud) {
|
||||
ReactGA.event({
|
||||
category: "welcome",
|
||||
action: "click_getting_started",
|
||||
label: "",
|
||||
})
|
||||
}
|
||||
|
||||
navigate("/workflows?message=Skipped intro")
|
||||
}}>
|
||||
<CardActionArea style={actionObject}>
|
||||
<img src={experienced_image} style={{padding: experienced_image === defaultImage ? 38 : 10, objectFit: "scale-down", minHeight: experienced_image === defaultImage ? 40 : 70, maxHeight: experienced_image === defaultImage ? 40 : 70, bordeRadius: theme.palette.borderRadius, }} />
|
||||
<Typography variant="h4" style={{color: "#F1F1F1"}}>
|
||||
Experienced
|
||||
</Typography>
|
||||
<Typography variant="body1" style={{marginTop: 10, color: "rgba(255,255,255,0.8)"}}>
|
||||
Head to Shuffle right away
|
||||
</Typography>
|
||||
</CardActionArea>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/*
|
||||
<div style={{display: "flex", flexDirection: "row", }}>
|
||||
<Button variant="contained" type="submit" fullWidth style={buttonStyle} onClick={() => {
|
||||
navigate("/workflows?message=Skipped intro continue")
|
||||
}}>
|
||||
Continue
|
||||
</Button>
|
||||
</div>
|
||||
*/}
|
||||
|
||||
<div style={{margin: "auto", borderRadius: theme.palette.borderRadius, marginTop: 50, width: 200, overflow: "wrap", padding: 25, cursor: "pointer", }} onClick={() => {
|
||||
if (window.drift !== undefined) {
|
||||
window.drift.api.startInteraction({ interactionId: 340045 })
|
||||
} else {
|
||||
console.log("Couldn't find drift in window.drift and not .drift-open-chat with querySelector: ", window.drift)
|
||||
}
|
||||
}}>
|
||||
<Typography variant="body1" style={{margin: "auto", textAlign: "center"}}>
|
||||
Want a demo instead?
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</Fade>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Welcome;
|
||||
Reference in New Issue
Block a user