import React, { useEffect } from "react";
import { useInterval } from "react-powerhooks";
import Grid from "@material-ui/core/Grid";
import Paper from "@material-ui/core/Paper";
import Tooltip from "@material-ui/core/Tooltip";
import Divider from "@material-ui/core/Divider";
import Button from "@material-ui/core/Button";
import TextField from "@material-ui/core/TextField";
import FormControl from "@material-ui/core/FormControl";
import IconButton from "@material-ui/core/IconButton";
import Menu from "@material-ui/core/Menu";
import MenuItem from "@material-ui/core/MenuItem";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import Chip from "@material-ui/core/Chip";
import Switch from "@material-ui/core/Switch";
import Typography from "@material-ui/core/Typography";
import Zoom from "@material-ui/core/Zoom";
import CircularProgress from "@material-ui/core/CircularProgress";
import CachedIcon from "@material-ui/icons/Cached";
import GetAppIcon from "@material-ui/icons/GetApp";
import AppsIcon from "@material-ui/icons/Apps";
import EditIcon from "@material-ui/icons/Edit";
import MoreVertIcon from "@material-ui/icons/MoreVert";
import PlayArrowIcon from "@material-ui/icons/PlayArrow";
import AddIcon from "@material-ui/icons/Add";
import PublishIcon from "@material-ui/icons/Publish";
//import JSONPretty from 'react-json-pretty';
//import JSONPrettyMon from 'react-json-pretty/dist/monikai'
import ReactJson from "react-json-view";
import { Link } from "react-router-dom";
import { useAlert } from "react-alert";
import ChipInput from "material-ui-chip-input";
import Dialog from "@material-ui/core/Dialog";
import DialogTitle from "@material-ui/core/DialogTitle";
import DialogActions from "@material-ui/core/DialogActions";
import DialogContent from "@material-ui/core/DialogContent";
import CloudDownloadIcon from "@material-ui/icons/CloudDownload";
import BubbleChartIcon from "@material-ui/icons/BubbleChart";
import RestoreIcon from "@material-ui/icons/Restore";
import mobileImage from "../assets/img/mobile.svg";
import bagImage from "../assets/img/bag.svg";
import bookImage from "../assets/img/book.svg";
import {
DataGrid,
GridToolbarContainer,
GridDensitySelector,
GridToolbar,
} from "@mui/x-data-grid";
import { makeStyles } from "@material-ui/core/styles";
import ListIcon from "@material-ui/icons/List";
import GridOnIcon from "@material-ui/icons/GridOn";
const inputColor = "#383B40";
const surfaceColor = "#27292D";
const flexContainerStyle = {
display: "flex",
flexDirection: "row",
justifyContent: "left",
alignContent: "space-between",
};
const flexBoxStyle = {
height: 125,
borderRadius: 4,
boxSizing: "border-box",
letterSpacing: "0.4px",
color: "#D6791E",
margin: 10,
flex: 1,
};
//const activeWorkflowStyle = {backgroundColor: "#FFF5EE"}
//const notificationStyle = {backgroundColor: "#E5F9FF"}
//const activeWorkflowStyle = {backgroundColor: "#3d3f43"}
const availableWorkflowStyle = { backgroundColor: "#3d3f43" };
const notificationStyle = { backgroundColor: "#3d3f43" };
const activeWorkflowStyle = { backgroundColor: "#3d3f43" };
const flexContentStyle = {
display: "flex",
flexDirection: "row",
};
const iconStyle = {
width: "75px",
height: "75px",
padding: "20px",
};
const fontSize_16 = { fontSize: "16px" };
const counterStyle = { fontSize: "36px", fontWeight: "bold" };
const blockRightStyle = {
textAlign: "right",
padding: "20px 20px 0px 0px",
width: "100%",
};
export const validateJson = (showResult) => {
//showResult = showResult.split(" None").join(" \"None\"")
showResult = showResult.split(" False").join(" false");
showResult = showResult.split(" True").join(" true");
var jsonvalid = true;
try {
const tmp = String(JSON.parse(showResult));
if (!showResult.includes("{") && !showResult.includes("[")) {
jsonvalid = false;
}
} catch (e) {
showResult = showResult.split("'").join('"');
try {
const tmp = String(JSON.parse(showResult));
if (!showResult.includes("{") && !showResult.includes("[")) {
jsonvalid = false;
}
} catch (e) {
jsonvalid = false;
}
}
const result = jsonvalid ? JSON.parse(showResult) : showResult;
//console.log("VALID: ", jsonvalid, result)
return {
valid: jsonvalid,
result: result,
};
};
const MyView = (props) => {
const { globalUrl, isLoggedIn, isLoaded, userdata } = props;
document.title = "Shuffle - Workflows";
const alert = useAlert();
var upload = "";
const [file, setFile] = React.useState("");
const [workflows, setWorkflows] = React.useState([]);
const [selectedWorkflow, setSelectedWorkflow] = React.useState({});
const [selectedExecution, setSelectedExecution] = React.useState({});
const [workflowExecutions, setWorkflowExecutions] = React.useState([]);
const [firstrequest, setFirstrequest] = React.useState(true);
const [workflowDone, setWorkflowDone] = React.useState(false);
const [, setTrackingId] = React.useState("");
const [selectedWorkflowId, setSelectedWorkflowId] = React.useState("");
const [collapseJson, setCollapseJson] = React.useState(false);
const [field1, setField1] = React.useState("");
const [field2, setField2] = React.useState("");
const [downloadUrl, setDownloadUrl] = React.useState(
"https://github.com/frikky/shuffle-workflows"
);
const [downloadBranch, setDownloadBranch] = React.useState("master");
const [loadWorkflowsModalOpen, setLoadWorkflowsModalOpen] =
React.useState(false);
const [modalOpen, setModalOpen] = React.useState(false);
const [newWorkflowName, setNewWorkflowName] = React.useState("");
const [newWorkflowDescription, setNewWorkflowDescription] =
React.useState("");
const [newWorkflowTags, setNewWorkflowTags] = React.useState([]);
const [update, setUpdate] = React.useState("test");
const [deleteModalOpen, setDeleteModalOpen] = React.useState(false);
const [editingWorkflow, setEditingWorkflow] = React.useState({});
const [executionLoading, setExecutionLoading] = React.useState(false);
const [view, setView] = React.useState("grid");
const { start, stop } = useInterval({
duration: 5000,
startImmediate: false,
callback: () => {
//getWorkflowExecution(selectedWorkflow.id)
},
});
// DEBUG HERE
const handleClickLogout = () => {};
const deleteModal = deleteModalOpen ? (
) : null;
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!");
return;
}
return response.json();
})
.then((responseJson) => {
setSelectedExecution({});
setWorkflowExecutions([]);
if (responseJson !== undefined) {
setWorkflows(responseJson);
setWorkflowDone(true);
} else {
if (isLoggedIn) {
alert.error("An error occurred while loading workflows");
} else {
handleClickLogout();
}
return;
}
if (responseJson.length > 0) {
setSelectedWorkflow(responseJson[0]);
//getWorkflowExecution(responseJson[0].id)
}
})
.catch((error) => {
alert.error(error.toString());
});
};
useEffect(() => {
if (workflows.length <= 0 && firstrequest) {
setFirstrequest(false);
getAvailableWorkflows();
}
});
const viewStyle = {
color: "#ffffff",
width: "100%",
display: "flex",
minWidth: 1024,
maxWidth: 1024,
margin: "auto",
/*maxHeight: "90vh",*/
};
const emptyWorkflowStyle = {
paddingTop: "200px",
width: 1024,
margin: "auto",
};
const boxStyle = {
padding: "20px 20px 20px 20px",
width: "100%",
height: "250px",
color: "white",
backgroundColor: surfaceColor,
display: "flex",
flexDirection: "column",
};
const scrollStyle = {
marginTop: "10px",
overflow: "scroll",
height: "90%",
overflowX: "hidden",
overflowY: "auto",
};
const paperAppContainer = {
display: "flex",
flexWrap: "wrap",
alignContent: "space-between",
};
const paperAppStyle = {
minHeight: 130,
width: "100%",
color: "white",
backgroundColor: surfaceColor,
padding: "12px 12px 0px 15px",
borderRadius: 5,
display: "flex",
boxSizing: "border-box",
position: "relative",
};
const gridContainer = {
height: "auto",
color: "white",
margin: "10px",
backgroundColor: surfaceColor,
};
const workflowActionStyle = {
flex: "1",
display: "flex",
width: 150,
height: 44,
justifyContent: "space-between",
overflow: "hidden",
};
const getWorkflowExecution = (id) => {
setExecutionLoading(true);
fetch(globalUrl + "/api/v1/workflows/" + id + "/executions", {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
setExecutionLoading(false);
if (response.status !== 200) {
console.log("Status not 200 for WORKFLOW EXECUTION :O!");
}
return response.json();
})
.then((responseJson) => {
if (responseJson.success === false) {
alert.error("Failed getting executions");
} else {
if (responseJson.length > 0) {
setSelectedExecution(responseJson[0]);
setWorkflowExecutions(responseJson);
} else {
//alert.info("Couldn't find executions for the workflow")
setSelectedExecution({});
setWorkflowExecutions([]);
}
}
})
.catch((error) => {
setExecutionLoading(false);
alert.error(error.toString());
});
};
const abortExecution = (workflowid, executionid) => {
alert.success("Aborting execution");
fetch(
globalUrl +
"/api/v1/workflows/" +
workflowid +
"/executions/" +
executionid +
"/abort",
{
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
}
)
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for WORKFLOW EXECUTION :O!");
}
//getWorkflowExecution(workflowid)
return response.json();
})
.catch((error) => {
alert.error(error.toString());
});
};
const executeWorkflow = (id) => {
alert.show("Executing workflow " + id);
setTrackingId(id);
fetch(globalUrl + "/api/v1/workflows/" + id + "/execute", {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for WORKFLOW EXECUTION :O!");
}
return response.json();
})
.then((responseJson) => {
if (!responseJson.success) {
alert.error(responseJson.reason);
}
})
.catch((error) => {
alert.error(error.toString());
});
if (id === selectedWorkflow.id) {
sleep(2000).then(() => {
stop();
start();
});
}
};
function sleep(time) {
return new Promise((resolve) => setTimeout(resolve, time));
}
const exportAllWorkflows = () => {
for (var key in workflows) {
exportWorkflow(workflows[key]);
}
};
const exportWorkflow = (data) => {
console.log("export");
let dataStr = JSON.stringify(data);
let dataUri =
"data:application/json;charset=utf-8," + encodeURIComponent(dataStr);
let exportFileDefaultName = data.name + ".json";
data["owner"] = "";
for (var key in data.triggers) {
const trigger = data.triggers[key];
if (trigger.app_name === "Shuffle Workflow") {
if (trigger.parameters.length > 2) {
trigger.parameters[2].value = "";
}
}
if (trigger.status == "running") {
trigger.status = "stopped";
}
}
for (var key in data.actions) {
data.actions[key].authentication_id = "";
}
//return
data["org"] = [];
data["org_id"] = "";
data.execution_org = { id: "" };
console.log(data);
let linkElement = document.createElement("a");
linkElement.setAttribute("href", dataUri);
linkElement.setAttribute("download", exportFileDefaultName);
linkElement.click();
};
const copyWorkflow = (data) => {
data = JSON.parse(JSON.stringify(data));
alert.success("Copying workflow " + data.name);
console.log("data: ", data);
data.id = "";
data.name = data.name + "_copy";
//return
fetch(globalUrl + "/api/v1/workflows", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(data),
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for workflows :O!");
return;
}
return response.json();
})
.then((responseJson) => {
getAvailableWorkflows();
})
.catch((error) => {
alert.error(error.toString());
});
};
const deleteWorkflow = (id) => {
alert.success("Deleted workflow " + id);
fetch(globalUrl + "/api/v1/workflows/" + id, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for setting workflows :O!");
}
return response.json();
})
.then((responseJson) => {
getAvailableWorkflows();
})
.catch((error) => {
alert.error(error.toString());
});
};
const getWorkflowMeta = (data) => {
let triggers = 0;
let schedules = 0;
let webhooks = 0;
let subflows = 0;
if (
data.triggers !== undefined &&
data.triggers !== null &&
data.triggers.length > 0
) {
triggers = data.triggers.length;
for (let key in data.triggers) {
if (data.triggers[key].app_name === "Webhook") {
webhooks += 1;
//webhookImg = data.triggers[key].large_image
} else if (data.triggers[key].app_name === "Schedule") {
schedules += 1;
//scheduleImg = data.triggers[key].large_image
} else if (data.triggers[key].app_name === "Subflow") {
subflows += 1;
}
}
}
return [triggers, schedules, webhooks, subflows];
};
// dropdown with copy etc I guess
const WorkflowPaper = (props) => {
const { data } = props;
const [open, setOpen] = React.useState(false);
const [anchorEl, setAnchorEl] = React.useState(null);
var boxWidth = "2px";
if (selectedWorkflow.id === data.id) {
boxWidth = "4px";
}
var boxColor = "#FECC00";
if (data.is_valid) {
boxColor = "#86c142";
}
if (!data.previously_saved) {
boxColor = "#f85a3e";
}
const menuClick = (event) => {
setOpen(!open);
setAnchorEl(event.currentTarget);
};
var parsedName = data.name;
if (
parsedName !== undefined &&
parsedName !== null &&
parsedName.length > 25
) {
parsedName = parsedName.slice(0, 25) + "..";
}
const actions = data.actions !== null ? data.actions.length : 0;
const [triggers, schedules, webhooks, subflows] = getWorkflowMeta(data);
return (
Status: {data.status}
Actions: {results}/{actions}
Name: {data.action.label}
Shuffle is a flexible, easy to use, automation platform allowing users to integrate their services and devices freely. It's made to significantly reduce the amount of manual labor, and is focused on security applications.{" "} Click here to learn more.