Merge branch '1.3.0' of github.com:Shuffle/Shuffle into 1.3.0

This commit is contained in:
Aditya
2023-08-22 17:38:11 +05:30
67 changed files with 2958 additions and 11280 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ require (
github.com/gorilla/mux v1.8.0
github.com/h2non/filetype v1.1.3
github.com/satori/go.uuid v1.2.0
github.com/shuffle/shuffle-shared v0.4.19
github.com/shuffle/shuffle-shared v0.4.35
golang.org/x/crypto v0.9.0
google.golang.org/api v0.125.0
google.golang.org/appengine v1.6.7
+14 -3
View File
@@ -5841,6 +5841,8 @@ func handleAppZipUpload(resp http.ResponseWriter, request *http.Request) {
resp.Write([]byte("OK"))
}
func initHandlers() {
var err error
ctx := context.Background()
@@ -5890,6 +5892,7 @@ func initHandlers() {
r := mux.NewRouter()
r.HandleFunc("/api/v1/_ah/health", shuffle.HealthCheckHandler)
r.HandleFunc("/api/v1/health", shuffle.RunOpsHealthCheck).Methods("GET", "OPTIONS")
// Make user related locations
// Fix user changes with org
@@ -5998,7 +6001,7 @@ func initHandlers() {
// New for recommendations in Shuffle
r.HandleFunc("/api/v1/recommendations/get_actions", shuffle.HandleActionRecommendation).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/recommendations/modify", shuffle.HandleRecommendationAction).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/workflows/{key}/revisions", shuffle.GetWorkflowRevisions).Methods("GET", "OPTIONS")
// r.HandleFunc("/api/v1/workflows/{key}/revisions", shuffle.GetWorkflowRevisions).Methods("GET", "OPTIONS")
// Triggers
r.HandleFunc("/api/v1/hooks/new", shuffle.HandleNewHook).Methods("POST", "OPTIONS")
@@ -6054,9 +6057,16 @@ func initHandlers() {
r.HandleFunc("/api/v1/orgs/{orgId}/list_cache", shuffle.HandleListCacheKeys).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/get_cache", shuffle.HandleGetCacheKey).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/set_cache", shuffle.HandleSetCacheKey).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/cache/{cache_key}", shuffle.HandleDeleteCacheKey).Methods("DELETE", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/stats", shuffle.HandleGetStatistics).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/revisions", shuffle.GetWorkflowRevisions).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/cache", shuffle.HandleListCacheKeys).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/cache", shuffle.HandleSetCacheKey).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/cache/{cache_key}", shuffle.HandleDeleteCacheKey).Methods("DELETE", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/datastore", shuffle.HandleListCacheKeys).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/datastore", shuffle.HandleSetCacheKey).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/orgs/{orgId}/datastore/{cache_key}", shuffle.HandleDeleteCacheKey).Methods("DELETE", "OPTIONS")
// Docker orborus specific - downloads an image
r.HandleFunc("/api/v1/get_docker_image", getDockerImage).Methods("POST", "OPTIONS")
@@ -6098,6 +6108,7 @@ func initHandlers() {
// Had to move away from mux, which means Method is fucked up right now.
func main() {
initHandlers()
go shuffle.InitOpsWorkflow()
hostname, err := os.Hostname()
if err != nil {
hostname = "MISSING"
+1 -1
View File
@@ -1,7 +1,7 @@
version: '3'
services:
frontend:
image: ghcr.io/shuffle/shuffle-frontend:nightly
image: ghcr.io/shuffle/shuffle-frontend:latest
container_name: shuffle-frontend
hostname: shuffle-frontend
ports:
+7 -4
View File
@@ -13,8 +13,9 @@
"@mui/material": "^5.14.0",
"@mui/styles": "^5.14.0",
"@mui/x-data-grid": "^5.17.11",
"@uiw/codemirror-themes": "^4.21.7",
"@uiw/react-codemirror": "^4.21.7",
"@mui/x-date-pickers": "^6.11.1",
"@uiw/codemirror-themes": "^4.21.9",
"@uiw/react-codemirror": "^4.21.9",
"@use-it/interval": "^1.0.0",
"algoliasearch": "^4.13.1",
"calculate-size": "^1.1.1",
@@ -24,6 +25,7 @@
"cytoscape-edgehandles": "^3.6.0",
"cytoscape-node-html-label": "^1.1.5",
"d3": "^7.1.1",
"dayjs": "^1.11.9",
"dotenv": "^6.1.0",
"downshift": "^3.3.5",
"github-markdown-css": "^3.0.1",
@@ -36,11 +38,11 @@
"jss-nested": "^6.0.1",
"jss-props-sort": "^6.0.0",
"jss-vendor-prefixer": "^8.0.1",
"material-ui-chip-input": "^2.0.0-beta.2",
"md5-file": "^4.0.0",
"mdbreact": "^4.21.1",
"mime": "^3.0.0",
"moment": "^2.29.1",
"mui-chips-input": "^2.1.3",
"mui-nested-menu": "^3.2.1",
"process": "^0.11.10",
"react": "^18.2.0",
@@ -67,6 +69,7 @@
"react-router": "^6.14.1",
"react-router-dom": "^6.14.1",
"react-scripts": "^5.0.1",
"react-toastify": "^9.1.3",
"reaviz": "^14.9.4",
"remark-gfm": "^3.0.1",
"search-insights": "^2.2.1",
@@ -108,6 +111,6 @@
"react-16": "npm:react@16.13.1",
"react-dom-16": "npm:react-dom@16.13.1",
"react-error-overlay": "6.0.9",
"webpack": "^4.46.0"
"webpack": "^5.88.2"
}
}
+1 -1
View File
@@ -167,7 +167,7 @@ const App = (message, props) => {
const includedData =
<div
style={{
backgroundColor: "#1F2023",
backgroundColor: theme.palette.backgroundColor,
color: "rgba(255, 255, 255, 0.65)",
minHeight: "100vh",
}}
+21 -20
View File
@@ -1,10 +1,9 @@
import React, { useState, useEffect } from 'react';
import theme from '../theme.jsx';
import CytoscapeComponent from 'react-cytoscapejs';
import frameworkStyle from '../frameworkStyle.jsx';
import { v4 as uuidv4 } from "uuid";
import theme from '../theme.jsx';
import { useAlert } from "react-alert";
import AppSearch from '../components/Appsearch.jsx';
import PaperComponent from "../components/PaperComponent.jsx"
@@ -18,14 +17,15 @@ import {
Divider,
IconButton,
Badge,
CircularProgress,
CircularProgress,
Tooltip,
Dialog,
Chip,
Avatar,
Button
Button,
} from "@mui/material";
import {
Close as CloseIcon,
Delete as DeleteIcon,
@@ -33,10 +33,10 @@ import {
import * as edgehandles from "cytoscape-edgehandles";
import * as cytoscape from "cytoscape";
import { toast } from 'react-toastify';
cytoscape.use(edgehandles);
const svgSize = "40px"
const parsedDatatypeImages = {
"SIEM": encodeURI(`data:image/svg+xml;utf-8,<svg fill="rgb(248,90,62)" width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M6.93767 0C8.71083 0 10.4114 0.704386 11.6652 1.9582C12.919 3.21202 13.6234 4.91255 13.6234 6.68571C13.6234 8.34171 13.0165 9.864 12.0188 11.0366L12.2965 11.3143H13.1091L18.252 16.4571L16.7091 18L11.5662 12.8571V12.0446L11.2885 11.7669C10.116 12.7646 8.59367 13.3714 6.93767 13.3714C5.16451 13.3714 3.46397 12.667 2.21015 11.4132C0.956339 10.1594 0.251953 8.45888 0.251953 6.68571C0.251953 4.91255 0.956339 3.21202 2.21015 1.9582C3.46397 0.704386 5.16451 0 6.93767 0ZM6.93767 2.05714C4.36624 2.05714 2.3091 4.11429 2.3091 6.68571C2.3091 9.25714 4.36624 11.3143 6.93767 11.3143C9.5091 11.3143 11.5662 9.25714 11.5662 6.68571C11.5662 4.11429 9.5091 2.05714 6.93767 2.05714Z" /></svg>`),
@@ -520,7 +520,7 @@ const AppFramework = (props) => {
const scale = size === undefined ? 1 : size > 5 ? 3 : size
const alert = useAlert()
//const alert = useAlert()
const handleLoadNextSuggestion = (frameworkData) => {
@@ -783,16 +783,16 @@ const AppFramework = (props) => {
.then((responseJson) => {
if (responseJson.success === false) {
if (responseJson.reason !== undefined) {
alert.error("Failed updating: " + responseJson.reason)
toast("Failed updating: " + responseJson.reason)
} else {
alert.error("Failed to update framework for your org.")
toast("Failed to update framework for your org.")
}
} else {
alert.info("Updated usecase.")
toast("Updated usecase.")
}
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
//setFrameworkLoaded(true)
})
}
@@ -815,13 +815,13 @@ const AppFramework = (props) => {
})
.then((responseJson) => {
if (responseJson.success === false) {
alert.error("Failed to activate the app")
toast("Failed to activate the app")
} else {
//alert.success("App activated for your organization! Refresh the page to use the app.")
//toast("App activated for your organization! Refresh the page to use the app.")
}
})
.catch(error => {
//alert.error(error.toString())
//toast(error.toString())
console.log("Activate app error: ", error.toString())
});
}
@@ -851,9 +851,9 @@ const AppFramework = (props) => {
.then((responseJson) => {
if (responseJson.success === false) {
if (responseJson.reason !== undefined) {
alert.error("Failed updating: " + responseJson.reason)
toast("Failed updating: " + responseJson.reason)
} else {
alert.error("Failed to update framework for your org.")
toast("Failed to update framework for your org.")
}
}
@@ -862,7 +862,7 @@ const AppFramework = (props) => {
//setFrameworkData(responseJson)
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
//setFrameworkLoaded(true)
})
}
@@ -1856,6 +1856,7 @@ const AppFramework = (props) => {
//autounselectify={true}
var usecasediff = -100
const bgColor = color === undefined || color === null || color.length === 0 ? theme.palette.surfaceColor : color
console.log("Background: ", bgColor)
return (
<div style={{margin: "auto", backgroundColor: bgColor, position: "relative", }}>
@@ -1992,11 +1993,11 @@ const AppFramework = (props) => {
{
Object.getOwnPropertyNames(discoveryData).length > 0 ?
<Paper style={{width: 275, maxHeight: 400, overflow: "hidden", zIndex: 12500, padding: 25, paddingRight: 35, backgroundColor: theme.palette.surfaceColor, border: "1px solid rgba(255,255,255,0.2)", position: "absolute", top: -50, left: 50, }}>
<Paper style={{width: 300, maxHeight: 400, overflow: "hidden", zIndex: 12500, padding: 25, paddingRight: 25, backgroundColor: theme.palette.surfaceColor, border: "1px solid rgba(255,255,255,0.2)", position: "absolute", top: -50, left: 50, }}>
{paperTitle.length > 0 ?
<span>
<Typography variant="h6" style={{textAlign: "center"}}>
{paperTitle}
{paperTitle.replace("_", " ", -1)}
</Typography>
<Divider style={{marginTop: 5, marginBottom: 5 }} />
</span>
@@ -2126,7 +2127,7 @@ const AppFramework = (props) => {
?
<span>
<Typography variant="body2" color="textSecondary" style={{marginTop: 10}}>
Click an app below to select it
Search to find your app
</Typography>
</span>
:
@@ -2161,7 +2162,7 @@ const AppFramework = (props) => {
elements={elements}
minZoom={0.35}
maxZoom={2.00}
style={{width: 560*scale, height: 560*scale, backgroundColor: "transparent", margin: "auto",}}
style={{width: 560*scale, height: 560*scale, backgroundColor: theme.palette.backgroundColor, margin: "auto",}}
stylesheet={frameworkStyle}
boxSelectionEnabled={false}
panningEnabled={false}
+1 -1
View File
@@ -74,7 +74,7 @@ const AppGrid = props => {
.then(response => {
if (response.success === true) {
setFormMessage(response.reason)
//alert.info("Thanks for submitting!")
//toast("Thanks for submitting!")
} else {
setFormMessage(errorMessage)
}
+17 -43
View File
@@ -1,23 +1,34 @@
import React, { useState, useEffect } from 'react';
import theme from '../theme.jsx';
import ReactGA from 'react-ga4';
import theme from '../theme.jsx';
import {Link} from 'react-router-dom';
import { useAlert } from "react-alert";
import { Search as SearchIcon, CloudQueue as CloudQueueIcon, Code as CodeIcon } from '@mui/icons-material';
import { toast } from 'react-toastify';
//import algoliasearch from 'algoliasearch/lite';
import algoliasearch from 'algoliasearch';
import { InstantSearch, connectSearchBox, connectHits } from 'react-instantsearch-dom';
import { Grid, Paper, TextField, ButtonBase, InputAdornment, Typography, Button, Tooltip} from '@mui/material';
import {
Grid,
Paper,
TextField,
ButtonBase,
InputAdornment,
Typography,
Button,
Tooltip
} from '@mui/material';
import aa from 'search-insights'
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240")
const Appsearch = props => {
const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, userdata, cy, isCreatorPage, actionImageList, setActionImageList} = props
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
const alert = useAlert();
//const alert = useAlert();
const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows
const xs = parsedXs === undefined || parsedXs === null ? 12 : parsedXs
//const theme = useTheme();
//const [apps, setApps] = React.useState([]);
//const [filteredApps, setFilteredApps] = React.useState([]);
const [formMail, setFormMail] = React.useState("");
@@ -57,55 +68,18 @@ const Appsearch = props => {
if (response.status !== 200) {
console.log("Status not 200 for set creator :O!");
}
alert.success("Sucessfully updated specialzed app.")
toast("Sucessfully updated specialzed app.")
return response.json();
})
.then((responseJson) => {
if (!responseJson.success && responseJson.reason !== undefined) {
alert.error("Failed updating user: " + responseJson.reason);
toast("Failed updating user: " + responseJson.reason);
}
})
.catch((error) => {
console.log(error);
});
};
const submitContact = (email, message) => {
const data = {
"firstname": "",
"lastname": "",
"title": "",
"companyname": "",
"email": email,
"phone": "",
"message": message,
}
const errorMessage = "Something went wrong. Please contact frikky@shuffler.io directly."
fetch(globalUrl+"/api/v1/contact", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
.then(response => response.json())
.then(response => {
if (response.success === true) {
setFormMessage(response.reason)
//alert.info("Thanks for submitting!")
} else {
setFormMessage(errorMessage)
}
setFormMail("")
setMessage("")
})
.catch(error => {
setFormMessage(errorMessage)
console.log(error)
});
}
// value={currentRefinement}
const SearchBox = ({currentRefinement, refine, isSearchStalled} ) => {
@@ -1,7 +1,8 @@
import React, { useState, useEffect } from "react";
import theme from '../theme.jsx';
import { useAlert } from "react-alert";
import { toast } from 'react-toastify';
import {
Tooltip,
IconButton,
@@ -34,7 +35,7 @@ const AuthenticationItem = (props) => {
const [selectedAuthenticationModalOpen, setSelectedAuthenticationModalOpen] = React.useState(false);
const [authenticationFields, setAuthenticationFields] = React.useState([]);
const alert = useAlert();
//const alert = useAlert();
var bgColor = "#27292d";
if (index % 2 === 0) {
bgColor = "#1f2023";
@@ -63,7 +64,7 @@ const AuthenticationItem = (props) => {
}
const deleteAuthentication = (data) => {
alert.info("Deleting auth " + data.label);
toast("Deleting auth " + data.label);
// Just use this one?
const url = globalUrl + "/api/v1/apps/authentication/" + data.id;
@@ -79,13 +80,13 @@ const AuthenticationItem = (props) => {
response.json().then((responseJson) => {
console.log("RESP: ", responseJson);
if (responseJson["success"] === false) {
alert.error("Failed deleting auth");
toast("Failed deleting auth");
} else {
// Need to wait because query in ES is too fast
setTimeout(() => {
getAppAuthentication();
}, 1000);
//alert.success("Successfully deleted authentication!")
//toast("Successfully deleted authentication!")
}
})
)
@@ -115,9 +116,9 @@ const AuthenticationItem = (props) => {
.then((response) =>
response.json().then((responseJson) => {
if (responseJson["success"] === false) {
alert.error("Failed overwriting appauth in workflows");
toast("Failed overwriting appauth in workflows");
} else {
alert.success("Successfully updated auth everywhere!");
toast("Successfully updated auth everywhere!");
//setSelectedUserModalOpen(false);
setTimeout(() => {
getAppAuthentication();
@@ -126,7 +127,7 @@ const AuthenticationItem = (props) => {
})
)
.catch((error) => {
alert.error("Err: " + error.toString());
toast("Err: " + error.toString());
});
};
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from "react";
import theme from '../theme.jsx';
import { v4 as uuidv4 } from "uuid";
import { toast } from 'react-toastify';
import {
Button,
@@ -54,7 +54,7 @@ const AuthenticationData = (props) => {
})
.then((responseJson) => {
if (!responseJson.success) {
alert.error("Failed to set app auth: " + responseJson.reason);
toast("Failed to set app auth: " + responseJson.reason);
} else {
if (getAppAuthentication !== undefined) {
getAppAuthentication()
@@ -65,11 +65,11 @@ const AuthenticationData = (props) => {
}
// Needs a refresh with the new authentication..
//alert.success("Successfully saved new app auth")
//toast("Successfully saved new app auth")
}
})
.catch((error) => {
//alert.error(error.toString());
//toast(error.toString());
console.log("New auth error: ", error.toString());
});
}
@@ -146,7 +146,7 @@ const AuthenticationData = (props) => {
selectedApp.authentication.parameters[key].name
] = "false";
} else {
alert.info(
toast(
"Field " +
selectedApp.authentication.parameters[key].name +
" can't be empty"
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react";
import theme from '../theme.jsx';
import { v4 as uuidv4 } from "uuid";
import { useAlert } from "react-alert";
import { toast } from 'react-toastify';
import {
Divider,
@@ -44,7 +44,7 @@ const AuthenticationData = (props) => {
authFieldsOnly,
} = props
const alert = useAlert()
//const alert = useAlert()
let navigate = useNavigate();
const [submitSuccessful, setSubmitSuccessful] = useState(false)
const [authenticationOption, setAuthenticationOptions] = React.useState({
@@ -99,9 +99,9 @@ const AuthenticationData = (props) => {
.then((responseJson) => {
if (!responseJson.success) {
if (responseJson.reason === undefined) {
alert.error("Failed to set app auth. Are you logged in?")
toast("Failed to set app auth. Are you logged in?")
} else {
alert.error("Failed to set app auth: " + responseJson.reason);
toast("Failed to set app auth: " + responseJson.reason);
}
} else {
setSubmitSuccessful(true)
@@ -115,7 +115,7 @@ const AuthenticationData = (props) => {
}
})
.catch((error) => {
//alert.error(error.toString());
//toast(error.toString());
console.log("New auth error: ", error.toString());
});
};
@@ -175,7 +175,7 @@ const AuthenticationData = (props) => {
selectedApp.authentication.parameters[paramkey].name
] = "false";
} else {
alert.info(
toast(
"Field " +
selectedApp.authentication.parameters[paramkey].name +
" can't be empty"
+2 -2
View File
@@ -11,11 +11,11 @@ import {
Card,
} from "@mui/material";
import { useAlert } from "react-alert";
//import { useAlert
const Branding = (props) => {
const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, } = props;
const alert = useAlert();
//const alert = useAlert();
const [publishingInfo, setPublishingInfo] = useState("");
// Should enable / disable org branding
+13 -11
View File
@@ -1,5 +1,7 @@
import React, { useState, useEffect } from "react";
import theme from "../theme.jsx";
import { toast } from 'react-toastify';
import {
Tooltip,
Divider,
@@ -16,7 +18,7 @@ import {
DialogTitle,
DialogActions,
} from "@mui/material";
import { useAlert } from "react-alert";
//import { useAlert
import {
Edit as EditIcon,
@@ -72,7 +74,7 @@ const CacheView = (props) => {
const [dataValue, setDataValue] = React.useState({});
const [editCache, setEditCache] = React.useState(false);
const [show, setShow] = useState({});
const alert = useAlert();
//const alert = useAlert();
useEffect(() => {
listOrgCache(orgId);
console.log("orgid", orgId);
@@ -105,7 +107,7 @@ const CacheView = (props) => {
}
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
@@ -148,7 +150,7 @@ const CacheView = (props) => {
const deleteCache = (orgId, key) => {
alert.info("Attempting to delete Cache");
toast("Attempting to delete Cache");
fetch(globalUrl + `/api/v1/orgs/${orgId}/cache/${key}`, {
method: "DELETE",
headers: {
@@ -158,16 +160,16 @@ const CacheView = (props) => {
})
.then((response) => {
if (response.status === 200) {
alert.success("Successfully deleted Cache");
toast("Successfully deleted Cache");
setTimeout(() => {
listOrgCache(orgId);
}, 1000);
} else {
alert.error("Failed deleting Cache. Does it still exist?");
toast("Failed deleting Cache. Does it still exist?");
}
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
@@ -197,12 +199,12 @@ const CacheView = (props) => {
})
.then((responseJson) => {
setAddCache(responseJson);
alert.success("Cache Edited Successfully!");
toast("Cache Edited Successfully!");
listOrgCache(orgId);
setModalOpen(false);
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
@@ -231,12 +233,12 @@ const CacheView = (props) => {
})
.then((responseJson) => {
setAddCache(responseJson);
alert.success("New Cache Added Successfully!");
toast("New Cache Added Successfully!");
listOrgCache(orgId);
setModalOpen(false);
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
+12 -11
View File
@@ -1,5 +1,6 @@
import React, { useState, useEffect } from "react";
import { useInterval } from "react-powerhooks";
import { toast } from 'react-toastify';
import {
InputAdornment,
@@ -112,9 +113,9 @@ const ConfigureWorkflow = (props) => {
})
.then((response) => {
if (response.status === 200) {
//alert.success("Successfully GOT app "+appId)
//toast("Successfully GOT app "+appId)
} else {
alert.error("Failed getting app");
toast("Failed getting app");
}
return response.json();
@@ -128,7 +129,7 @@ const ConfigureWorkflow = (props) => {
}
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
@@ -566,7 +567,7 @@ const ConfigureWorkflow = (props) => {
.then((response) => {
if (response.status !== 200) {
//window.location.pathname = "/search"
//alert.error("Failed to find this app. Is it public?")
//toast("Failed to find this app. Is it public?")
}
return response.json();
@@ -574,16 +575,16 @@ const ConfigureWorkflow = (props) => {
.then((responseJson) => {
if (responseJson.success === false) {
if (responseJson.reason !== undefined) {
alert.error("Failed to activate the app: "+responseJson.reason);
toast("Failed to activate the app: "+responseJson.reason);
} else {
alert.error("Failed to activate the app");
toast("Failed to activate the app");
}
} else {
alert.success("App activated for your organization!");
toast("App activated for your organization!");
}
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
@@ -693,7 +694,7 @@ const ConfigureWorkflow = (props) => {
if (workflow.actions !== null) {
//console.log(workflow.actions)
alert.info("Setting action to version "+action.update_version)
toast("Setting action to version "+action.update_version)
for (let [key,keyval] in Object.entries(workflow.actions)) {
if (workflow.actions[key].app_name === action.app_name && workflow.actions[key].app_version === action.app_version) {
workflow.actions[key].app_version = action.update_version
@@ -851,7 +852,7 @@ const ConfigureWorkflow = (props) => {
console.log("NAVIGATOR: ", navigator);
const clipboard = navigator.clipboard;
if (clipboard === undefined) {
alert.error("Can only copy over HTTPS (port 3443)");
toast("Can only copy over HTTPS (port 3443)");
return;
}
@@ -864,7 +865,7 @@ const ConfigureWorkflow = (props) => {
/* Copy the text inside the text field */
document.execCommand("copy");
alert.success("Copied Webhook URL");
toast("Copied Webhook URL");
}
}}>
<Typography variant="body2" color="textSecondary">{webhook.description}</Typography>
+1 -1
View File
@@ -81,7 +81,7 @@ const CreatorGrid = props => {
.then(response => {
if (response.success === true) {
setFormMessage(response.reason)
//alert.info("Thanks for submitting!")
//toast("Thanks for submitting!")
} else {
setFormMessage(errorMessage)
}
+1 -1
View File
@@ -69,7 +69,7 @@ const DocsGrid = props => {
.then(response => {
if (response.success === true) {
setFormMessage(response.reason)
//alert.info("Thanks for submitting!")
//toast("Thanks for submitting!")
} else {
setFormMessage(errorMessage)
}
+205 -151
View File
@@ -1,8 +1,10 @@
import React, { useEffect, useContext } from "react";
import theme from '../theme.jsx';
import { isMobile } from "react-device-detect"
import ChipInput from "material-ui-chip-input";
import { MuiChipsInput } from "mui-chips-input";
import UsecaseSearch from "../components/UsecaseSearch.jsx"
import WorkflowGrid from "../components/WorkflowGrid.jsx"
import dayjs from 'dayjs';
import {
Badge,
@@ -25,6 +27,7 @@ import {
Typography,
Zoom,
CircularProgress,
Drawer,
Dialog,
DialogTitle,
DialogActions,
@@ -39,6 +42,13 @@ import {
} from "@mui/material";
import {
DatePicker,
LocalizationProvider,
} from '@mui/x-date-pickers'
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'
import {
ExpandLess as ExpandLessIcon,
ExpandMore as ExpandMoreIcon,
@@ -49,16 +59,19 @@ import {
const EditWorkflow = (props) => {
const { globalUrl, workflow, setWorkflow, modalOpen, setModalOpen, showUpload, usecases, setNewWorkflow, appFramework, isEditing, userdata, } = props
const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove
const [submitLoading, setSubmitLoading] = React.useState(false);
const [showMoreClicked, setShowMoreClicked] = React.useState(false);
const [innerWorkflow, setInnerWorkflow] = React.useState(workflow)
const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove
const [innerWorkflow, setInnerWorkflow] = React.useState(workflow)
const [newWorkflowTags, setNewWorkflowTags] = React.useState(workflow.tags !== undefined && workflow.tags !== null ? JSON.parse(JSON.stringify(workflow.tags)) : [])
const [description, setDescription] = React.useState(workflow.description !== undefined ? workflow.description : "")
const [selectedUsecases, setSelectedUsecases] = React.useState(workflow.usecase_ids !== undefined && workflow.usecase_ids !== null ? JSON.parse(JSON.stringify(workflow.usecase_ids)) : []);
const [foundWorkflowId, setFoundWorkflowId] = React.useState("")
const [name, setName] = React.useState(workflow.name !== undefined ? workflow.name : "")
const [description, setDescription] = React.useState(workflow.description !== undefined ? workflow.description : "")
const [dueDate, setDueDate] = React.useState(workflow.due_date !== undefined && workflow.due_date !== null && workflow.due_date !== 0 ? dayjs(workflow.due_date*1000) : dayjs().subtract(1, 'day'))
// Gets the generated workflow
const getGeneratedWorkflow = (workflow_id) => {
@@ -114,7 +127,7 @@ const EditWorkflow = (props) => {
}
})
.catch((error) => {
//alert.error(error.toString());
//toast(error.toString());
console.log("Get workflow error: ", error.toString());
})
}
@@ -136,67 +149,67 @@ const EditWorkflow = (props) => {
var total_count = 0
return (
<Dialog
<Drawer
open={modalOpen}
onClose={() => {
setModalOpen(false);
}}
PaperProps={{
style: {
backgroundColor: theme.palette.surfaceColor,
color: "white",
minWidth: isMobile ? "90%" : 550,
maxWidth: isMobile ? "90%" : 550,
minHeight: 400,
minWidth: isMobile ? "90%" : 650,
maxWidth: isMobile ? "90%" : 650,
minHeight: 400,
paddingTop: 25,
//minWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550,
//maxWidth: isMobile ? "90%" : newWorkflow === true ? 1000 : 550,
},
}}
>
<DialogTitle style={{padding: 30, paddingBottom: 0, zIndex: 1000,}}>
<div style={{display: "flex"}}>
<div style={{display: "flex"}}>
<div style={{flex: 1, color: "rgba(255,255,255,0.9)" }}>
<div style={{display: "flex"}}>
<Typography variant="h6" style={{flex: 9, }}>
{newWorkflow ? "New" : "Editing"} workflow
</Typography>
{newWorkflow === true ? null :
<div style={{ marginLeft: 5, flex: 1 }}>
<Tooltip title="Open Workflow Form for 'normal' users">
<a
rel="noopener noreferrer"
href={`/workflows/${workflow.id}/run`}
target="_blank"
style={{
textDecoration: "none",
color: "#f85a3e",
marginLeft: 5,
marginTop: 10,
}}
>
<OpenInNewIcon />
</a>
</Tooltip>
</div>
}
<div style={{display: "flex"}}>
<Typography variant="h6" style={{flex: 9, }}>
{newWorkflow ? "New" : "Editing"} workflow
</Typography>
{newWorkflow === true ? null :
<div style={{ marginLeft: 5, flex: 1 }}>
<Tooltip title="Open Workflow Form for 'normal' users">
<a
rel="noopener noreferrer"
href={`/workflows/${workflow.id}/run`}
target="_blank"
style={{
textDecoration: "none",
color: "#f85a3e",
marginLeft: 5,
marginTop: 10,
}}
>
<OpenInNewIcon />
</a>
</Tooltip>
</div>
<Typography variant="body2" color="textSecondary" style={{maxWidth: 440,}}>
Workflows can be built from scratch, or from templates. <a href="/usecases" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>Usecases</a> can help you discover next steps, and you can <a href="/search?tab=workflows" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>search</a> for them directly. <a href="/docs/workflows" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>Learn more</a>
</Typography>
{showUpload === true ?
<div style={{ float: "right" }}>
<Tooltip color="primary" title={"Import manually"} placement="top">
<Button
color="primary"
style={{}}
variant="text"
onClick={() => upload.click()}
>
<PublishIcon />
</Button>
</Tooltip>
</div>
: null}
}
</div>
<Typography variant="body2" color="textSecondary" style={{maxWidth: 440,}}>
Workflows can be built from scratch, or from templates. <a href="/usecases" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>Usecases</a> can help you discover next steps, and you can <a href="/search?tab=workflows" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>search</a> for them directly. <a href="/docs/workflows" rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", color: "#f86a3e" }}>Learn more</a>
</Typography>
{showUpload === true ?
<div style={{ float: "right" }}>
<Tooltip color="primary" title={"Import manually"} placement="top">
<Button
color="primary"
style={{}}
variant="text"
onClick={() => upload.click()}
>
<PublishIcon />
</Button>
</Tooltip>
</div>
: null}
</div>
{/*newWorkflow === true ?
<div style={{flex: 1, marginLeft: 45, }}>
@@ -211,12 +224,12 @@ const EditWorkflow = (props) => {
</div>
</DialogTitle>
<FormControl>
<DialogContent style={{paddingTop: 10, display: "flex", minHeight: 350, zIndex: 1001, }}>
<div style={{minWidth: newWorkflow ? 450 : 500, maxWidth: newWorkflow ? 450 : 500, }}>
<DialogContent style={{paddingTop: 10, display: "flex", minHeight: 300, zIndex: 1001, }}>
<div style={{minWidth: newWorkflow ? 500 : 550, maxWidth: newWorkflow ? 450 : 500, }}>
<TextField
onBlur={(event) => {
setName(event.target.value)
}}
onChange={(event) => {
setName(event.target.value)
}}
InputProps={{
style: {
color: "white",
@@ -231,27 +244,29 @@ const EditWorkflow = (props) => {
autoFocus
fullWidth
/>
<TextField
onBlur={(event) => {
setDescription(event.target.value)
}}
InputProps={{
style: {
color: "white",
},
}}
maxRows={4}
color="primary"
defaultValue={innerWorkflow.description}
placeholder="Description"
multiline
label="Description"
margin="dense"
fullWidth
/>
<div style={{display: "flex", }}>
<TextField
onBlur={(event) => {
setDescription(event.target.value)
}}
InputProps={{
style: {
color: "white",
},
}}
maxRows={4}
color="primary"
defaultValue={innerWorkflow.description}
placeholder="Description"
multiline
label="Description"
margin="dense"
fullWidth
/>
</div>
<div style={{display: "flex", marginTop: 10, }}>
<ChipInput
style={{ flex: 1, maxHeight: 40, marginTop: 12, overflow: "auto", }}
<MuiChipsInput
style={{ flex: 1, maxHeight: 40, }}
InputProps={{
style: {
color: "white",
@@ -261,13 +276,20 @@ const EditWorkflow = (props) => {
color="primary"
fullWidth
value={newWorkflowTags}
onChange={(chip) => {
console.log("Chip: ", chip)
//newWorkflowTags.push(chip);
setNewWorkflowTags(chip);
}}
onAdd={(chip) => {
newWorkflowTags.push(chip);
setNewWorkflowTags(newWorkflowTags);
}}
onDelete={(chip, index) => {
console.log("Deleting: ", chip, index)
newWorkflowTags.splice(index, 1);
setNewWorkflowTags(newWorkflowTags);
setUpdate(Math.random());
}}
/>
{usecases !== null && usecases !== undefined && usecases.length > 0 ?
@@ -328,26 +350,41 @@ const EditWorkflow = (props) => {
{showMoreClicked === true ?
<span style={{marginTop: 25, }}>
<div style={{display: "flex"}}>
<FormControl style={{marginTop: 15, }}>
<FormLabel id="demo-row-radio-buttons-group-label">Status</FormLabel>
<RadioGroup
row
aria-labelledby="demo-row-radio-buttons-group-label"
name="row-radio-buttons-group"
defaultValue={innerWorkflow.status}
onChange={(e) => {
console.log("Data: ", e.target.value)
innerWorkflow.workflow_type = e.target.value
setInnerWorkflow(innerWorkflow)
}}
>
<FormControlLabel value="test" control={<Radio />} label="Test" />
<FormControlLabel value="production" control={<Radio />} label="Production" />
<FormControl style={{marginTop: 15, }}>
<FormLabel id="demo-row-radio-buttons-group-label">Status</FormLabel>
<RadioGroup
row
aria-labelledby="demo-row-radio-buttons-group-label"
name="row-radio-buttons-group"
defaultValue={innerWorkflow.status}
onChange={(e) => {
console.log("Data: ", e.target.value)
innerWorkflow.workflow_type = e.target.value
setInnerWorkflow(innerWorkflow)
</RadioGroup>
</FormControl>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DatePicker
sx={{
marginTop: 3,
marginLeft: 3,
}}
>
<FormControlLabel value="test" control={<Radio />} label="Test" />
<FormControlLabel value="production" control={<Radio />} label="Production" />
</RadioGroup>
</FormControl>
value={dueDate}
label="Due Date"
format="YYYY-MM-DD"
onChange={(newValue) => {
setDueDate(newValue)
}}
/>
</LocalizationProvider>
</div>
<div />
<FormControl style={{marginTop: 15, }}>
@@ -430,38 +467,28 @@ const EditWorkflow = (props) => {
</span>
: null}
<Tooltip color="primary" title={"Add more details"} placement="top">
<IconButton
style={{ color: "white", margin: "auto", marginTop: 10, textAlign: "center", width: 50,}}
onClick={() => {
setShowMoreClicked(!showMoreClicked);
}}
>
{showMoreClicked ? <ExpandLessIcon /> : <ExpandMoreIcon/>}
</IconButton>
</Tooltip>
</div>
{/*newWorkflow === true ?
<div style={{marginLeft: 50, maxWidth: 400, minWidth: 400, position: "relative",}}>
<UsecaseSearch
globalUrl={globalUrl}
appFramework={appFramework}
defaultSearch={undefined}
apps={undefined}
setFoundWorkflowId={setFoundWorkflowId}
userdata={userdata}
/>
</div>
: null*/}
<IconButton
style={{ color: "white", margin: "auto", marginTop: 10, textAlign: "center", width: 50,}}
onClick={() => {
setShowMoreClicked(!showMoreClicked);
}}
>
{showMoreClicked ? <ExpandLessIcon /> : <ExpandMoreIcon/>}
</IconButton>
</Tooltip>
</div>
</DialogContent>
<DialogActions>
<DialogActions style={{paddingRight: 20, }}>
<Button
style={{}}
onClick={() => {
if (setNewWorkflow !== undefined) {
setWorkflow({})
}
if (setNewWorkflow !== undefined) {
setWorkflow({})
}
setModalOpen(false)
setModalOpen(false)
}}
color="primary"
>
@@ -470,46 +497,73 @@ const EditWorkflow = (props) => {
<Button
variant="contained"
style={{}}
disabled={name.length === 0}
disabled={name.length === 0 || submitLoading === true}
onClick={() => {
innerWorkflow.name = name
innerWorkflow.description = description
if (newWorkflowTags.length > 0) {
innerWorkflow.tags = newWorkflowTags
}
setSubmitLoading(true)
if (selectedUsecases.length > 0) {
innerWorkflow.usecase_ids = selectedUsecases
}
innerWorkflow.name = name
innerWorkflow.description = description
if (newWorkflowTags.length > 0) {
innerWorkflow.tags = newWorkflowTags
}
if (selectedUsecases.length > 0) {
innerWorkflow.usecase_ids = selectedUsecases
}
if (setNewWorkflow !== undefined) {
setNewWorkflow(
innerWorkflow.name,
innerWorkflow.description,
innerWorkflow.tags,
innerWorkflow.default_return_value,
innerWorkflow,
newWorkflow,
innerWorkflow.usecase_ids,
innerWorkflow.blogpost,
innerWorkflow.status,
)
setWorkflow({})
} else {
setWorkflow(innerWorkflow)
console.log("editing workflow: ", innerWorkflow)
}
setModalOpen(false)
if (dueDate > 0) {
innerWorkflow.due_date = new Date(`${dueDate["$y"]}-${dueDate["$M"]+1}-${dueDate["$D"]}`).getTime()/1000
}
if (setNewWorkflow !== undefined) {
setNewWorkflow(
innerWorkflow.name,
innerWorkflow.description,
innerWorkflow.tags,
innerWorkflow.default_return_value,
innerWorkflow,
newWorkflow,
innerWorkflow.usecase_ids,
innerWorkflow.blogpost,
innerWorkflow.status,
)
setWorkflow({})
} else {
setWorkflow(innerWorkflow)
console.log("editing workflow: ", innerWorkflow)
}
setSubmitLoading(true)
// If new workflow, don't close it
if (isEditing) {
setModalOpen(false)
}
}}
color="primary"
>
{submitLoading ? <CircularProgress color="secondary" /> : "Submit"}
</Button>
</DialogActions>
{newWorkflow === true && name.length > 5 ?
<div style={{marginLeft: 30, }}>
<WorkflowGrid
maxRows={1}
globalUrl={globalUrl}
showSuggestions={false}
isMobile={isMobile}
userdata={userdata}
inputsearch={name+description+newWorkflowTags.join(" ")}
parsedXs={6}
alternativeView={false}
onlyResults={true}
/>
</div>
: null}
</FormControl>
</Dialog>
</Drawer>
)
}
+19 -18
View File
@@ -1,4 +1,5 @@
import React, { useState, useEffect } from "react";
import { toast } from 'react-toastify';
import {
IconButton,
@@ -29,7 +30,7 @@ import {
Add as AddIcon,
} from "@mui/icons-material";
import { useAlert } from "react-alert";
//import { useAlert
import Dropzone from "../components/Dropzone.jsx";
import CodeEditor from "../components/ShuffleCodeEditor.jsx";
import theme from "../theme.jsx";
@@ -45,7 +46,7 @@ const Files = (props) => {
const [openEditor, setOpenEditor] = React.useState(false);
const [renderTextBox, setRenderTextBox] = React.useState(false);
const alert = useAlert();
//const alert = useAlert();
const allowedFileTypes = ["txt", "py", "yaml", "yml","json", "html", "js", "csv", "log"]
var upload = "";
@@ -113,7 +114,7 @@ const Files = (props) => {
}
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
@@ -139,12 +140,12 @@ const Files = (props) => {
})
.then((responseJson) => {
if (responseJson.success) {
alert.info("Successfully deleted file " + file.name);
toast("Successfully deleted file " + file.name);
} else if (
responseJson.reason !== undefined &&
responseJson.reason !== null
) {
alert.error("Failed to delete file: " + responseJson.reason);
toast("Failed to delete file: " + responseJson.reason);
}
setTimeout(() => {
getFiles();
@@ -153,7 +154,7 @@ const Files = (props) => {
console.log(responseJson);
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
@@ -178,7 +179,7 @@ const Files = (props) => {
// console.log("respdata type ->", typeof(respdata));
if (respdata.length === 0) {
alert.error("Failed getting file. Is it deleted?");
toast("Failed getting file. Is it deleted?");
return;
}
return respdata
@@ -189,7 +190,7 @@ const Files = (props) => {
//console.log("filecontent state ",fileContent);
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
@@ -210,7 +211,7 @@ const Files = (props) => {
})
.then((respdata) => {
if (respdata.length === 0) {
alert.error("Failed getting file. Is it deleted?");
toast("Failed getting file. Is it deleted?");
return;
}
@@ -249,7 +250,7 @@ const Files = (props) => {
//setSchedules(responseJson)
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
@@ -291,11 +292,11 @@ const Files = (props) => {
if (responseJson.success === true) {
handleFileUpload(responseJson.id, file);
} else {
alert.error("Failed to upload file ", filename);
toast("Failed to upload file ", filename);
}
})
.catch((error) => {
alert.error("Failed to upload file ", filename);
toast("Failed to upload file ", filename);
console.log(error.toString());
});
};
@@ -312,7 +313,7 @@ const Files = (props) => {
.then((response) => {
if (response.status !== 200 && response.status !== 201) {
console.log("Status not 200 for apps :O!");
alert.error("File was created, but failed to upload.");
toast("File was created, but failed to upload.");
return;
}
@@ -323,7 +324,7 @@ const Files = (props) => {
//setFiles(responseJson)
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
@@ -364,7 +365,7 @@ const Files = (props) => {
const files = isDropzone ? e.dataTransfer.files : e.target.files;
//const reader = new FileReader();
//alert.info("Starting fileupload")
//toast("Starting fileupload")
uploadFiles(files);
};
@@ -681,7 +682,7 @@ const Files = (props) => {
<ListItemText
primary=<span style={{ display:"inline"}}>
<Tooltip
title={`Edit File (${allowedFileTypes.join(", ")})`}
title={`Edit File (${allowedFileTypes.join(", ")}). Max size 2MB`}
style={{}}
aria-label={"Edit"}
>
@@ -742,7 +743,7 @@ const Files = (props) => {
) {
const clipboard = navigator.clipboard;
if (clipboard === undefined) {
alert.error(
toast(
"Can only copy over HTTPS (port 3443)"
);
return;
@@ -758,7 +759,7 @@ const Files = (props) => {
/* Copy the text inside the text field */
document.execCommand("copy");
alert.info(file.id + " copied to clipboard");
toast(file.id + " copied to clipboard");
}
}}
>
-54
View File
@@ -1,54 +0,0 @@
import React from "react";
//import List from '@material-ui/core/List';
//import ListItem from '@material-ui/core/ListItem';
//borderTop: "1px solid #385F71"
const FooterStyle = {
right: "0",
left: "0",
bottom: "0",
height: "130px",
backgroundColor: "rgba(15, 14, 31, 1)",
};
const FooterInfo = {
maxWidth: "1150px",
minWidth: "768px",
textAlign: "center",
margin: "auto",
};
const hrefStyle = {
color: "#bdbdbd",
textDecoration: "none",
};
const Footer = (props) => {
return (
<div style={FooterStyle}>
<div style={FooterInfo}>
<Box />
</div>
</div>
);
};
const Box = (props) => {
return (
<div style={{ display: "flex" }}>
<div style={{ flex: "1" }}>
<a style={hrefStyle} href="/about">
<h1>About</h1>
</a>
</div>
<div style={{ flex: "1" }}>
<a style={hrefStyle} href="/privacy-policy">
<h1>Privacy Policy</h1>
</a>
</div>
</div>
);
};
export default Footer;
+58 -25
View File
@@ -1,8 +1,9 @@
import React, {useState} from 'react';
import {BrowserView, MobileView} from "react-device-detect";
import { toast } from 'react-toastify';
import theme from '../theme.jsx';
import {BrowserView, MobileView} from "react-device-detect";
import {Link} from 'react-router-dom';
import { useNavigate, Link } from "react-router-dom";
import ReactGA from 'react-ga4';
import {
@@ -21,7 +22,7 @@ import {
IconButton,
Divider,
LinearProgress,
} from '@mui/material';
} from '@mui/material'
import {
MeetingRoom as MeetingRoomIcon,
@@ -29,16 +30,20 @@ import {
Settings as SettingsIcon,
Notifications as NotificationsIcon,
Home as HomeIcon,
Polymer as PolymerIcon,
Apps as AppsIcon,
Description as DescriptionIcon,
EmojiObjects as EmojiObjectsIcon,
Business as BusinessIcon,
Analytics as AnalyticsIcon,
Lightbulb as LightbulbIcon,
Polyline as PolylineIcon,
} from '@mui/icons-material';
import { useAlert } from "react-alert";
import {
Analytics as AnalyticsIcon,
Lightbulb as LightbulbIcon,
} from "@mui/icons-material";
//import { useAlert
import SearchField from '../components/Searchfield.jsx'
const hoverColor = "#f85a3e"
@@ -46,7 +51,8 @@ const hoverOutColor = "#e8eaf6"
const Header = props => {
const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, homePage, userdata, serverside, } = props;
const alert = useAlert()
//const theme = useTheme();
//const alert = useAlert()
const [HomeHoverColor, setHomeHoverColor] = useState(hoverOutColor);
@@ -57,6 +63,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
const [anchorEl, setAnchorEl] = React.useState(null);
const [anchorElAvatar, setAnchorElAvatar] = React.useState(null);
const [subAnchorEl, setSubAnchorEl] = React.useState(null);
let navigate = useNavigate();
const handleClick = (event) => {
@@ -95,7 +102,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
setNotifications([])
handleClose()
} else {
alert.error("Failed dismissing notifications. Please try again later.")
toast("Failed dismissing notifications. Please try again later.")
}
})
.catch(error => {
@@ -125,7 +132,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
console.log("NEW NOTIFICATIONS: ", newNotifications)
setNotifications(newNotifications)
} else {
alert.error("Failed dismissing notification. Please try again later.")
toast("Failed dismissing notification. Please try again later.")
}
})
.catch(error => {
@@ -400,9 +407,9 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
setTimeout(() => {
window.location.reload()
}, 2000)
alert.success("Successfully changed active organization - refreshing!")
toast("Successfully changed active organization - refreshing!")
} else {
alert.error("Failed changing org: ", responseJson.reason)
toast("Failed changing org: ", responseJson.reason)
}
})
.catch(error => {
@@ -514,6 +521,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
}
// Handle top bar or something
const defaultTop = 7
const loginTextBrowser = !isLoggedIn ?
<div style={{display: "flex", minWidth: 1250, maxWidth: 1250, margin: "auto", textAlign: "center",}}>
<div style={{display: "flex", flex: 1, }}>
@@ -664,7 +672,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
</div>
</div>
:
<div style={{display: "flex", backgroundColor: "#1f2023",}}>
<div style={{display: "flex", }}>
<div style={{minWidth: 1250, maxWidth: 1250, display: "flex", margin: "auto", }}>
<div style={{flex: 1, flexDirection: "row"}}>
<List style={{height: 56, marginTop: "auto", marginBottom: "auto", display: "flex", flexDirect: "row", alignItems: "baseline", maxWidth: 340, }} component="nav">
@@ -686,9 +694,9 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
<Link to="/workflows" style={hrefStyle}>
<div onMouseOver={handleSoarHover} onMouseOut={handleSoarHoverOut} style={{color: SoarHoverColor, cursor: "pointer", display: "flex"}}>
{/*
<PolymerIcon style={{marginRight: "5px"}} />
<PolylineIcon style={{marginRight: "5px"}} />
*/}
<Typography style={{marginTop: 5, marginRight: 8, }}>Workflows</Typography>
<Typography style={{marginTop: defaultTop, marginRight: 8, }}>Workflows</Typography>
</div>
</Link>
</ListItem>
@@ -698,17 +706,24 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
{/*
<AppsIcon style={{marginRight: "5px"}} />
*/}
<Typography style={{marginTop: 5, marginRight: 5, }}>Apps</Typography>
<Typography style={{marginTop: defaultTop, marginRight: 5, }}>Apps</Typography>
</div>
</Link>
</ListItem>
{/*
<ListItem style={{textAlign: "center"}}>
<Link to="/dashboard" style={hrefStyle}>
<div onMouseOver={handleDocsHover} onMouseOut={handleDocsHoverOut} style={{color: DocsHoverColor, cursor: "pointer"}}>Dashboard</div>
</Link>
</ListItem>
*/}
<ListItem style={listItemStyle}>
<Link to="/docs" style={hrefStyle}>
<div onMouseOver={handleDocsHover} onMouseOut={handleDocsHoverOut} style={{color: DocsHoverColor, cursor: "pointer", display: "flex"}}>
{/*
<DescriptionIcon style={{marginRight: "5px"}} />
*/}
<Typography style={{marginTop: 5,}}>Docs</Typography>
<Typography style={{marginTop: defaultTop,}}>Docs</Typography>
</div>
</Link>
</ListItem>
@@ -782,14 +797,13 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
</ListItem>
: null*/}
{userdata === undefined || userdata.orgs === undefined || userdata.orgs === null || userdata.orgs.length <= 1 ?
null
:
<span style={{paddingTop: 12, }}>
<Select
disableunderline
disableUnderline
SelectDisplayProps={{
style: {
maxWidth: 50,
@@ -883,13 +897,32 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
</span>
}
{/* Show on cloud, if not suborg and if not customer/pov/internal */}
{isCloud && (userdata.org_status === undefined || userdata.org_status === null || userdata.org_status.length === 0) ?
<ListItem style={{textAlign: "center", marginLeft: 0, marginRight: 7, marginTop: 3, }}>
<Link to ="/pricing?tab=cloud&highlight=true" style={hrefStyle}>
<Button variant="contained" color="primary" style={{textTransform: "none"}} onClick={() => {
ReactGA.event({
category: "header",
action: "pricing_upgrade_click",
label: "",
})
}}>
Upgrade
</Button>
</Link>
</ListItem>
: null}
{userdata === undefined || userdata.app_execution_limit === undefined || userdata.app_execution_usage === undefined || userdata.app_execution_usage < 1000 ?
null
:
<Tooltip title={`Amount of executions left: ${userdata.app_execution_usage} / ${userdata.app_execution_limit}. When the limit is reached, you can still use Shuffle normally, but your Workflow triggers may stop working. Reach out to support@shuffler.io to extend this limit.`}>
<div style={{maxHeight: 30, minHeight: 30, padding: 8, textAlign: "center", cursor: "pointer", borderRadius: theme.palette.borderRadius, marginRight: 10, marginTop: 12, backgroundColor: theme.palette.surfaceColor, minWidth: 60, maxWidth: 60, }} onClick={() => {
<div style={{maxHeight: 30, minHeight: 30, padding: 8, textAlign: "center", cursor: "pointer", borderRadius: theme.palette.borderRadius, marginRight: 10, marginTop: 12, backgroundColor: theme.palette.surfaceColor, minWidth: 60, maxWidth: 60, border: userdata.app_execution_usage/userdata.app_execution_limit >= 0.9 ? "#f86a3e" : null, }} onClick={() => {
console.log(userdata.appe_execution_usage/userdata.app_execution_limit)
if (window.drift !== undefined) {
window.drift.api.startInteraction({ interactionId: 326905 })
navigate("/pricing")
} else {
console.log("Couldn't find drift in window.drift and not .drift-open-chat with querySelector: ", window.drift)
}
@@ -999,16 +1032,16 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
const loadedCheck =
<div style={{minHeight: 68}}>
<BrowserView>
{loginTextBrowser}
{loginTextBrowser}
</BrowserView>
<MobileView>
{loginTextMobile}
{loginTextMobile}
</MobileView>
</div>
// <div style={{backgroundImage: "linear-gradient(-90deg,#342f78 0,#29255e 50%,#1b1947 100%"}}>
return (
<div style={{backgroundColor: props.color === "undefined" ? "inherit" : props.color}}>
{loadedCheck}
<div style={{backgroundColor: theme.palette.backgroundColor, }}>
{loadedCheck}
</div>
)
}
-176
View File
@@ -1,176 +0,0 @@
/* eslint-disable react/no-multi-comp */
import React, { useState } from "react";
import DialogTitle from "@material-ui/core/DialogTitle";
import Dialog from "@material-ui/core/Dialog";
import TextField from "@material-ui/core/TextField";
import Button from "@material-ui/core/Button";
const LoginDialog = (props) => {
const {
classes,
onClose,
open,
globalUrl,
isLoggedIn,
setIsLoggedIn,
...other
} = props;
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
//const [selectedValue, setSelectedValue] = useState(false);
// Used to swap from login to register. True = login, false = register
const [loginCheck, setLoginCheck] = useState(true);
// Error messages etc
const [loginInfo, setLoginInfo] = useState("");
const handleValidateForm = () => {
return username.length > 1 && password.length > 8;
};
const onSubmit = (e) => {
e.preventDefault();
// Just use this one?
var data =
'{"username": "' + username + '", "password": "' + password + '"}';
var baseurl = globalUrl;
if (loginCheck) {
var url = baseurl + "/login";
fetch(url, {
method: "POST",
body: data,
headers: {
"Content-Type": "application/json",
},
})
.then((response) =>
response.json().then((responseJson) => {
console.log(responseJson);
//console.log(e)
if (responseJson["success"] === false) {
setLoginInfo(responseJson["reason"]);
} else {
setLoginInfo("Successful login :)");
onClose();
setIsLoggedIn(true);
}
})
)
.catch((error) => {
setLoginInfo("Error in userdata");
});
} else {
url = baseurl + "/register";
fetch(url, {
method: "POST",
body: data,
headers: {
"Content-Type": "application/json",
},
})
.then((response) =>
response.json().then((responseJson) => {
if (responseJson["success"] === false) {
setLoginInfo(responseJson["reason"]);
} else {
setLoginInfo("Successful register :)");
onClose();
setIsLoggedIn(true);
}
})
)
.catch((error) => {
setLoginInfo("Error in userdata");
});
}
};
const onChangeUser = (e) => {
setUsername(e.target.value);
};
const onChangePass = (e) => {
setPassword(e.target.value);
};
const onClickRegister = () => {
setLoginCheck(!loginCheck);
};
//var loginChange = loginCheck ? (<div><p onClick={setLoginCheck(false)}>Want to register? Click here.</p></div>) : (<div><p onClick={setLoginCheck(true)}>Go back to login? Click here.</p></div>);
var formtitle = loginCheck ? <div>Login</div> : <div>Register</div>;
var formButton = loginCheck ? (
<div>Click to Register</div>
) : (
<div>Click to Login</div>
);
return (
<Dialog modal open={open} onClose={onClose} {...other}>
<DialogTitle>{formtitle}</DialogTitle>
<form onSubmit={onSubmit} style={{ margin: "15px 15px 15px 15px" }}>
Username
<div>
<TextField
required
id="standard-required"
autoComplete="username"
margin="normal"
variant="outlined"
onChange={onChangeUser}
/>
</div>
Password
<div>
<TextField
id="outlined-password-input"
type="password"
autoComplete="current-password"
margin="normal"
variant="outlined"
onChange={onChangePass}
/>
</div>
<div style={{ display: "flex", marginTop: "15px" }}>
<Button
color="secondary"
variant="contained"
type="submit"
style={{ flex: "1", marginRight: "5px" }}
disabled={!handleValidateForm()}
>
SUBMIT
</Button>
<Button
color="primary"
variant="contained"
type="button"
style={{ flex: "1" }}
onClick={onClose}
>
Cancel
</Button>
</div>
{loginInfo}
</form>
<div style={{ display: "flex" }}>
<Button
color="secondary"
variant="contained"
onClick={onClickRegister}
type="button"
style={{ flex: "1" }}
>
{formButton}
</Button>
</div>
</Dialog>
);
};
export default LoginDialog;
-213
View File
@@ -1,213 +0,0 @@
import React, { useState, useRef, useImperativeHandle } from "react";
import { makeStyles } from "@material-ui/core/styles";
import Menu, { MenuProps } from "@material-ui/core/Menu";
import MenuItem, { MenuItemProps } from "@material-ui/core/MenuItem";
import ArrowRight from "@material-ui/icons/ArrowRight";
import clsx from "clsx";
//<MenuItemProps, 'button'>
//export interface NestedMenuItemProps {
// /**
// * Open state of parent `<Menu />`, used to close decendent menus when the
// * root menu is closed.
// */
// parentMenuOpen: boolean;
// /**
// * Component for the container element.
// * @default 'div'
// */
// component: React.ElementType;
// /**
// * Effectively becomes the `children` prop passed to the `<MenuItem/>`
// * element.
// */
// label: React.ReactNode;
// /**
// * @default <ArrowRight />
// */
// rightIcon: React.ReactNode;
// /**
// * Props passed to container element.
// */
// ContainerProps: React.HTMLAttributes;
// //<HTMLElement> &React.RefAttributes<HTMLElement | null>
// /**
// * Props passed to sub `<Menu/>` element
// */
// MenuProps: Omit<MenuProps, 'children'>;
// /**
// * @see https://material-ui.com/api/list-item/
// */
// button: true;
//}
const TRANSPARENT = "rgba(0,0,0,0)";
const useMenuItemStyles = makeStyles((theme) => ({
root: (props: any) => ({
backgroundColor: props.open ? theme.palette.action.hover : TRANSPARENT,
}),
}));
/**
* Use as a drop-in replacement for `<MenuItem>` when you need to add cascading
* menu elements as children to this component.
*/
//const NestedMenuItem = React.forwardRef<NestedMenuItemProps>(
const NestedMenuItem = (props, ref) => {
console.log(props, ref);
//function NestedMenuItem(props, ref) {
const {
parentMenuOpen,
component = "div",
label,
rightIcon = <ArrowRight />,
children,
className,
tabIndex: tabIndexProp,
MenuProps = {},
ContainerProps: ContainerPropsProp = {},
...MenuItemProps
} = props;
const [isSubMenuOpen, setIsSubMenuOpen] = useState(false);
const { ref: containerRefProp, ...ContainerProps } = ContainerPropsProp;
const menuItemRef = useRef < HTMLLIElement > null;
useImperativeHandle(ref, () => menuItemRef.current);
const containerRef = useRef < HTMLDivElement > null;
useImperativeHandle(containerRefProp, () => containerRef.current);
const menuContainerRef = useRef < HTMLDivElement > null;
console.log(
"PAST THIS: ",
containerRefProp,
menuItemRef,
containerRef,
menuContainerRef,
ContainerProps
);
const handleMouseEnter = (event: React.MouseEvent<HTMLElement>) => {
setIsSubMenuOpen(true);
if (ContainerProps?.onMouseEnter) {
ContainerProps.onMouseEnter(event);
}
};
const handleMouseLeave = (event: React.MouseEvent<HTMLElement>) => {
setIsSubMenuOpen(false);
if (ContainerProps?.onMouseLeave) {
ContainerProps.onMouseLeave(event);
}
};
// Check if any immediate children are active
const isSubmenuFocused = () => {
const active = containerRef.current?.ownerDocument?.activeElement;
for (const child of menuContainerRef.current?.children ?? []) {
if (child === active) {
return true;
}
}
return false;
};
const handleFocus = (event: React.FocusEvent<HTMLElement>) => {
if (event.target === containerRef.current) {
setIsSubMenuOpen(true);
}
if (ContainerProps?.onFocus) {
ContainerProps.onFocus(event);
}
};
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === "Escape") {
return;
}
if (isSubmenuFocused()) {
event.stopPropagation();
}
const active = containerRef.current?.ownerDocument?.activeElement;
if (event.key === "ArrowLeft" && isSubmenuFocused()) {
containerRef.current?.focus();
}
if (
event.key === "ArrowRight" &&
event.target === containerRef.current &&
event.target === active
) {
console.log("MENU: ", menuContainerRef);
const firstChild = menuContainerRef.current.children[0];
console.log("FIRST: ", firstChild);
firstChild.focus();
}
};
const open = isSubMenuOpen && parentMenuOpen;
const menuItemClasses = useMenuItemStyles({ open });
// Root element must have a `tabIndex` attribute for keyboard navigation
let tabIndex;
if (!props.disabled) {
tabIndex = tabIndexProp !== undefined ? tabIndexProp : -1;
}
console.log("PAST 2! ", tabIndex);
return (
<div
{...ContainerProps}
ref={containerRef}
onFocus={handleFocus}
tabIndex={tabIndex}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onKeyDown={handleKeyDown}
>
<MenuItem
{...MenuItemProps}
className={clsx(menuItemClasses.root, className)}
ref={menuItemRef}
>
{label}
{rightIcon}
</MenuItem>
<Menu
// Set pointer events to 'none' to prevent the invisible Popover div
// from capturing events for clicks and hovers
style={{ pointerEvents: "none" }}
anchorEl={menuItemRef.current}
anchorOrigin={{
vertical: "top",
horizontal: "right",
}}
transformOrigin={{
vertical: "top",
horizontal: "left",
}}
open={open}
autoFocus={false}
disableAutoFocus
disableEnforceFocus
onClose={() => {
setIsSubMenuOpen(false);
}}
>
<div ref={menuContainerRef} style={{ pointerEvents: "auto" }}>
{children}
</div>
</Menu>
</div>
);
};
export default NestedMenuItem;
-202
View File
@@ -1,202 +0,0 @@
import React, {useState, useRef, useImperativeHandle} from 'react'
import {makeStyles} from '@material-ui/core/styles'
import Menu, {MenuProps} from '@material-ui/core/Menu'
import MenuItem, {MenuItemProps} from '@material-ui/core/MenuItem'
import ArrowRight from '@material-ui/icons/ArrowRight'
import clsx from 'clsx'
export interface NestedMenuItemProps extends Omit<MenuItemProps, 'button'> {
/**
* Open state of parent `<Menu />`, used to close decendent menus when the
* root menu is closed.
*/
parentMenuOpen: boolean
/**
* Component for the container element.
* @default 'div'
*/
component?: React.ElementType
/**
* Effectively becomes the `children` prop passed to the `<MenuItem/>`
* element.
*/
label?: React.ReactNode
/**
* @default <ArrowRight />
*/
rightIcon?: React.ReactNode
/**
* Props passed to container element.
*/
ContainerProps?: React.HTMLAttributes<HTMLElement> &
React.RefAttributes<HTMLElement | null>
/**
* Props passed to sub `<Menu/>` element
*/
MenuProps?: Omit<MenuProps, 'children'>
/**
* @see https://material-ui.com/api/list-item/
*/
button?: true | undefined
}
const TRANSPARENT = 'rgba(0,0,0,0)'
const useMenuItemStyles = makeStyles((theme) => ({
root: (props: any) => ({
backgroundColor: props.open ? theme.palette.action.hover : TRANSPARENT
})
}))
/**
* Use as a drop-in replacement for `<MenuItem>` when you need to add cascading
* menu elements as children to this component.
*/
const NestedMenuItem = React.forwardRef<
HTMLLIElement | null,
NestedMenuItemProps
>(function NestedMenuItem(props, ref) {
const {
parentMenuOpen,
component = 'div',
label,
rightIcon = <ArrowRight />,
children,
className,
tabIndex: tabIndexProp,
MenuProps = {},
ContainerProps: ContainerPropsProp = {},
...MenuItemProps
} = props
const {ref: containerRefProp, ...ContainerProps} = ContainerPropsProp
const menuItemRef = useRef<HTMLLIElement>(null)
useImperativeHandle(ref, () => menuItemRef.current)
const containerRef = useRef<HTMLDivElement>(null)
useImperativeHandle(containerRefProp, () => containerRef.current)
const menuContainerRef = useRef<HTMLDivElement>(null)
const [isSubMenuOpen, setIsSubMenuOpen] = useState(false)
const handleMouseEnter = (event: React.MouseEvent<HTMLElement>) => {
setIsSubMenuOpen(true)
if (ContainerProps?.onMouseEnter) {
ContainerProps.onMouseEnter(event)
}
}
const handleMouseLeave = (event: React.MouseEvent<HTMLElement>) => {
setIsSubMenuOpen(false)
if (ContainerProps?.onMouseLeave) {
ContainerProps.onMouseLeave(event)
}
}
// Check if any immediate children are active
const isSubmenuFocused = () => {
const active = containerRef.current?.ownerDocument?.activeElement
for (const child of menuContainerRef.current?.children ?? []) {
if (child === active) {
return true
}
}
return false
}
const handleFocus = (event: React.FocusEvent<HTMLElement>) => {
if (event.target === containerRef.current) {
setIsSubMenuOpen(true)
}
if (ContainerProps?.onFocus) {
ContainerProps.onFocus(event)
}
}
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === 'Escape') {
return
}
if (isSubmenuFocused()) {
event.stopPropagation()
}
const active = containerRef.current?.ownerDocument?.activeElement
if (event.key === 'ArrowLeft' && isSubmenuFocused()) {
containerRef.current?.focus()
}
if (
event.key === 'ArrowRight' &&
event.target === containerRef.current &&
event.target === active
) {
const firstChild = menuContainerRef.current?.children[0] as
| HTMLElement
| undefined
firstChild?.focus()
}
}
const open = isSubMenuOpen && parentMenuOpen
const menuItemClasses = useMenuItemStyles({open})
// Root element must have a `tabIndex` attribute for keyboard navigation
let tabIndex
if (!props.disabled) {
tabIndex = tabIndexProp !== undefined ? tabIndexProp : -1
}
return (
<div
{...ContainerProps}
ref={containerRef}
onFocus={handleFocus}
tabIndex={tabIndex}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onKeyDown={handleKeyDown}
>
<MenuItem
{...MenuItemProps}
className={clsx(menuItemClasses.root, className)}
ref={menuItemRef}
>
{label}
{rightIcon}
</MenuItem>
<Menu
// Set pointer events to 'none' to prevent the invisible Popover div
// from capturing events for clicks and hovers
style={{pointerEvents: 'none'}}
anchorEl={menuItemRef.current}
anchorOrigin={{
vertical: 'top',
horizontal: 'right'
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left'
}}
open={open}
autoFocus={false}
disableAutoFocus
disableEnforceFocus
onClose={() => {
setIsSubMenuOpen(false)
}}
>
<div ref={menuContainerRef} style={{pointerEvents: 'auto'}}>
{children}
</div>
</Menu>
</div>
)
})
export default NestedMenuItem
+106 -101
View File
@@ -1,101 +1,106 @@
import React, {useState} from 'react';
import theme from '../theme.jsx';
import {isMobile} from "react-device-detect";
import ReactGA from 'react-ga4';
import {TextField, Typography, Button} from '@material-ui/core';
const Newsletter = (props) => {
const { globalUrl, } = props;
const [email, setEmail] = useState("");
const [msg, setMsg] = useState("");
const [buttonActive, setButtonActive] = useState(true);
const buttonStyle = {minWidth: 300, borderRadius: 30, height: 60, width: 140, margin: isMobile ? "15px auto 15px auto" : "20px 20px 20px 10px", fontSize: 18,}
const newsletterSignup = (inemail) => {
if (inemail.length < 4) {
setMsg("Invalid email")
setButtonActive(true)
return
}
setButtonActive(false)
const data = {"email": inemail}
const url = globalUrl+'/api/v1/functions/newsletter_signup'
fetch(url, {
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
})
.then(response =>
response.json().then(responseJson => {
setButtonActive(true)
setMsg(responseJson["reason"])
if (responseJson["success"] === false) {
} else {
setEmail("")
}
}),
)
.catch(error => {
setMsg("Something went wrong: ", error.toString())
setButtonActive(true)
});
}
return (
<div style={{margin: "auto", color: "white", textAlign: "center",}}>
<Typography variant="h4" style={{marginTop: 35,}}>
Security Automation Newsletter
</Typography>
<Typography variant="h6" style={{color: "#7d7f82", marginTop: 20, }}>
Defensive security is 99% noise. Join us to sift through it.
</Typography>
<div style={{}}>
<TextField
style={{minWidth: isMobile ? "90%" : 450, backgroundColor: theme.palette.inputColor, marginTop: 20, borderRadius: 10, }}
InputProps={{
style:{
borderRadius: 10,
height: 60,
color: "white",
},
}}
color="primary"
value={email}
onChange={(e) => {
setEmail(e.target.value)
}}
placeholder="Your email"
id="standard-required"
margin="normal"
variant="outlined"
/>
</div>
<Button
variant="contained"
color="primary"
style={buttonStyle}
disabled={!buttonActive}
onClick={() => {
newsletterSignup(email)
ReactGA.event({
category: "newsletter",
action: `signup_click`,
label: "",
})
}}
>
Sign up
</Button>
<div/>
{msg}
</div>
)
}
export default Newsletter;
import React, {useState} from 'react';
import { useTheme } from '@mui/styles';
import {isMobile} from "react-device-detect";
import ReactGA from 'react-ga4';
import {
TextField,
Typography,
Button
} from '@mui/material';
const Newsletter = (props) => {
const { globalUrl, } = props;
const theme = useTheme();
const [email, setEmail] = useState("");
const [msg, setMsg] = useState("");
const [buttonActive, setButtonActive] = useState(true);
const buttonStyle = {minWidth: 300, borderRadius: 30, height: 60, width: 140, margin: isMobile ? "15px auto 15px auto" : "20px 20px 20px 10px", fontSize: 18,}
const newsletterSignup = (inemail) => {
if (inemail.length < 4) {
setMsg("Invalid email")
setButtonActive(true)
return
}
setButtonActive(false)
const data = {"email": inemail}
const url = globalUrl+'/api/v1/functions/newsletter_signup'
fetch(url, {
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
})
.then(response =>
response.json().then(responseJson => {
setButtonActive(true)
setMsg(responseJson["reason"])
if (responseJson["success"] === false) {
} else {
setEmail("")
}
}),
)
.catch(error => {
setMsg("Something went wrong: ", error.toString())
setButtonActive(true)
});
}
return (
<div style={{margin: "auto", color: "white", textAlign: "center",}}>
<Typography variant="h4" style={{marginTop: 35,}}>
Security Automation Newsletter
</Typography>
<Typography variant="h6" style={{color: "#7d7f82", marginTop: 20, }}>
Defensive security is 99% noise. Join us to sift through it.
</Typography>
<div style={{}}>
<TextField
style={{minWidth: isMobile ? "90%" : 450, backgroundColor: theme.palette.inputColor, marginTop: 20, borderRadius: 10, }}
InputProps={{
style:{
borderRadius: 10,
height: 60,
color: "white",
},
}}
color="primary"
value={email}
onChange={(e) => {
setEmail(e.target.value)
}}
placeholder="Your email"
id="standard-required"
margin="normal"
variant="outlined"
/>
</div>
<Button
variant="contained"
color="primary"
style={buttonStyle}
disabled={!buttonActive}
onClick={() => {
newsletterSignup(email)
ReactGA.event({
category: "newsletter",
action: `signup_click`,
label: "",
})
}}
>
Sign up
</Button>
<div/>
{msg}
</div>
)
}
export default Newsletter;
+19 -6
View File
@@ -1,7 +1,8 @@
import React, { useRef, useState, useEffect, useLayoutEffect } from "react";
import { toast } from 'react-toastify';
import { useParams, useNavigate, Link } from "react-router-dom";
import theme from '../theme.jsx';
import { useAlert } from "react-alert";
//import { useAlert
import { v4 as uuidv4 } from "uuid";
import {
@@ -36,8 +37,9 @@ import {
Breadcrumbs,
CircularProgress,
Switch,
Collapse,
Fade,
} from "@mui/material";
import {
LockOpen as LockOpenIcon,
SupervisorAccount as SupervisorAccountIcon,
@@ -70,6 +72,7 @@ const registeredApps = [
"todoist",
"microsoft_sentinel",
"microsoft_365_defender",
"google_chat",
"google_sheets",
"google_drive",
"google_disk",
@@ -97,7 +100,7 @@ const AuthenticationOauth2 = (props) => {
} = props;
let navigate = useNavigate();
const alert = useAlert()
//const alert = useAlert()
//const [update, setUpdate] = React.useState("|")
const [defaultConfigSet, setDefaultConfigSet] = React.useState(
@@ -260,6 +263,16 @@ const AuthenticationOauth2 = (props) => {
admin_consent,
"consent",
)
} else if (selectedApp.name.toLowerCase().includes("google_chat") || selectedApp.name.toLowerCase().includes("google_hangout")) {
handleOauth2Request(
"253565968129-6pij4g6ojim4gpum0h9m9u3bc357qsq7.apps.googleusercontent.com",
"",
"https://www.googleapis.com",
["https://www.googleapis.com/auth/chat.messages",],
admin_consent,
"consent",
)
} else if (selectedApp.name.toLowerCase().includes("jira_service_desk") || selectedApp.name.toLowerCase().includes("jira") || selectedApp.name.toLowerCase().includes("jira_service_management")) {
handleOauth2Request(
"AI02egeCQh1Zskm1QAJaaR6dzjR97V2F",
@@ -410,7 +423,7 @@ const AuthenticationOauth2 = (props) => {
//}
//while(open === true)
} catch (e) {
alert.error(
toast(
"Failed authentication - probably bad credentials. Try again"
);
setButtonClicked(false);
@@ -439,7 +452,7 @@ const AuthenticationOauth2 = (props) => {
console.log("NEW AUTH: ", authenticationOption);
if (authenticationOption.label.length === 0) {
authenticationOption.label = `Auth for ${selectedApp.name}`;
//alert.info("Label can't be empty")
//toast("Label can't be empty")
//return
}
@@ -467,7 +480,7 @@ const AuthenticationOauth2 = (props) => {
selectedApp.authentication.parameters[key].name
] = "false";
} else {
alert.info(
toast(
"Field " + selectedApp.authentication.parameters[key].name.replace("_basic", "", -1).replace("_", " ", -1) + " can't be empty"
);
+11 -14
View File
@@ -1,22 +1,21 @@
import React, { useEffect } from "react";
import theme from '../theme.jsx';
import theme from "../theme.jsx";
import { makeStyles } from "@mui/styles";
import { toast } from 'react-toastify';
import {
Tooltip,
Grid,
Button,
Grid,
Button,
TextField,
Typography,
Typography,
IconButton,
} from "@mui/material";
import { useAlert } from "react-alert";
import {
ExpandLess as ExpandLessIcon,
ExpandMore as ExpandMoreIcon,
Save as SaveIcon,
ExpandMore as ExpandMoreIcon,
Save as SaveIcon,
} from "@mui/icons-material";
const useStyles = makeStyles({
@@ -39,7 +38,7 @@ const OrgHeader = (props) => {
handleEditOrg,
} = props;
const alert = useAlert();
//const alert = useAlert();
const classes = useStyles();
var upload = "";
@@ -101,10 +100,9 @@ const OrgHeader = (props) => {
//console.log("USER: ", userdata)
const orgSaveButton = (
<Tooltip title="Save any unsaved data" placement="bottom">
<div>
<Button
style={{ width: 150, height: 55, flex: 1 }}
variant="contained"
variant="outlined"
color="primary"
disabled={
userdata === undefined || userdata === null || userdata.admin !== "true"
@@ -123,7 +121,6 @@ const OrgHeader = (props) => {
>
<SaveIcon />
</Button>
</div>
</Tooltip>
);
@@ -212,13 +209,13 @@ const OrgHeader = (props) => {
const invalid = ["#", ":", "."];
for (var key in invalid) {
if (e.target.value.includes(invalid[key])) {
alert.error("Can't use " + invalid[key] + " in name");
toast("Can't use " + invalid[key] + " in name");
return;
}
}
if (e.target.value.length > 100) {
alert.error("Choose a shorter name.");
toast("Choose a shorter name.");
return;
}
+225 -203
View File
@@ -1,4 +1,5 @@
import React, { useState, useEffect, useLayoutEffect } from "react";
import { toast } from 'react-toastify';
import { makeStyles, createStyles } from "@mui/styles";
import theme from '../theme.jsx';
@@ -6,9 +7,8 @@ import theme from '../theme.jsx';
import { validateJson, GetIconInfo } from "../views/Workflows.jsx";
import { GetParsedPaths } from "../views/Apps.jsx";
import { sortByKey } from "../views/AngularWorkflow.jsx";
//import NestedMenuItem from "material-ui-nested-menu-item-v5";
import { NestedMenuItem } from "mui-nested-menu";
import { useAlert } from "react-alert";
//import { useAlert
import {
ButtonGroup,
@@ -86,13 +86,12 @@ import {
Circle as CircleIcon,
SquareFoot as SquareFootIcon,
} from '@mui/icons-material';
//} from "@material-ui/icons";
//import CodeMirror from "@uiw/react-codemirror";
//import "codemirror/keymap/sublime";
//import "codemirror/theme/gruvbox-dark.css";
import ShuffleCodeEditor from "../components/ShuffleCodeEditor.jsx";
//import ShuffleCodeEditor from "../components/ShuffleCodeEditor.jsx";
const useStyles = makeStyles({
notchedOutline: {
@@ -113,7 +112,6 @@ const useStyles = makeStyles({
},
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",
},
@@ -158,28 +156,30 @@ const ParsedAction = (props) => {
authenticationType,
appAuthentication,
getAppAuthentication,
actionDelayChange,
getParents,
isCloud,
lastSaved,
setLastSaved,
setShowVideo,
toolsAppId,
aiSubmit,
//expansionModalOpen,
//setExpansionModalOpen,
actionDelayChange,
getParents,
isCloud,
lastSaved,
setLastSaved,
setShowVideo,
toolsAppId,
aiSubmit,
expansionModalOpen,
setExpansionModalOpen,
setEditorData,
setcodedata,
} = props;
const classes = useStyles();
const alert = useAlert()
const [expansionModalOpen, setExpansionModalOpen] = React.useState(false);
//const alert = useAlert()
const [hideBody, setHideBody] = React.useState(true);
const [activateHidingBodyButton, setActivateHidingBodyButton] = React.useState(false);
const [codedata, setcodedata] = React.useState("");
const [fieldCount, setFieldCount] = React.useState(0);
const [hiddenDescription, setHiddenDescription] = React.useState(true);
const [fieldCount, setFieldCount] = React.useState(0);
const [hiddenDescription, setHiddenDescription] = React.useState(true);
useEffect(() => {
@@ -228,9 +228,9 @@ const ParsedAction = (props) => {
})
.then((response) => {
if (response.status === 200) {
//alert.success("Successfully GOT app "+appId)
//toast("Successfully GOT app "+appId)
} else {
alert.error("Failed getting app");
toast("Failed getting app");
}
return response.json();
@@ -291,7 +291,7 @@ const ParsedAction = (props) => {
//foundparams.push(param.name)
}
} else {
alert.error("Couldn't find action " + selectedAction.name);
toast("Couldn't find action " + selectedAction.name);
}
selectedAction.errors = [];
@@ -307,7 +307,7 @@ const ParsedAction = (props) => {
}
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
@@ -1146,7 +1146,7 @@ const ParsedAction = (props) => {
// setNewSelectedAction({ target: { value: newValue.name } });
//}
}}
renderOption={(data) => {
renderOption={(props, data, state) => {
var newActionname = data.app_name;
if (
data.label !== undefined &&
@@ -1518,29 +1518,6 @@ const ParsedAction = (props) => {
const clickedFieldId = "rightside_field_" + count;
const shufflecode = fieldCount !== count ? null :
(
<ShuffleCodeEditor
isCloud={isCloud}
toolsAppId={toolsAppId}
fieldCount = {fieldCount}
setFieldCount = {setFieldCount}
actionlist = {actionlist}
changeActionParameterCodeMirror = {changeActionParameterCodeMirror}
codedata={codedata}
setcodedata={setcodedata}
expansionModalOpen={expansionModalOpen}
setExpansionModalOpen={setExpansionModalOpen}
globalUrl={globalUrl}
workflowExecutions={workflowExecutions}
getParents={getParents}
selectedAction={selectedAction}
parameterName={data.name}
aiSubmit={aiSubmit}
/>
)
//<TextareaAutosize
// <CodeMirror
//fullWidth
@@ -1590,38 +1567,47 @@ const ParsedAction = (props) => {
disableUnderline: true,
endAdornment: hideExtraTypes ? null : (
<InputAdornment position="end">
<ButtonGroup orientation={multiline ? "vertical" : "horizontal"}>
<Tooltip title="Expand window" placement="top">
<AspectRatioIcon
style={{ cursor: "pointer", margin: multiline ? 5 : 0 ,}}
onClick={(event) => {
event.preventDefault()
setFieldCount(count)
setcodedata(data.value)
setExpansionModalOpen(true)
}}
/>
</Tooltip>
<Tooltip title="Autocomplete text" placement="top">
<AddCircleOutlineIcon
style={{ cursor: "pointer", margin: multiline ? 5 : 0, }}
onClick={(event) => {
event.preventDefault()
<ButtonGroup orientation={multiline ? "vertical" : "horizontal"}>
<Tooltip title="Expand window" placement="top">
<AspectRatioIcon
style={{ cursor: "pointer", margin: multiline ? 5 : 0 ,}}
onClick={(event) => {
event.preventDefault()
setFieldCount(count)
setExpansionModalOpen(true)
// Get cursor position
// This makes it so we can put it in the right location?
setMenuPosition({
top: event.pageY + 10,
left: event.pageX + 10,
});
setShowDropdownNumber(count);
setShowDropdown(true);
setShowAutocomplete(true);
}}
/>
</Tooltip>
</ButtonGroup>
</InputAdornment>
//setcodedata(data.value)
setEditorData({
"name": data.name,
"value": data.value,
"field_number": count,
"actionlist": actionlist,
"field_id": clickedFieldId,
})
}}
/>
</Tooltip>
<Tooltip title="Autocomplete text" placement="top">
<AddCircleOutlineIcon
style={{ cursor: "pointer", margin: multiline ? 5 : 0, }}
onClick={(event) => {
event.preventDefault()
// Get cursor position
// This makes it so we can put it in the right location?
setMenuPosition({
top: event.pageY + 10,
left: event.pageX + 10,
});
setShowDropdownNumber(count);
setShowDropdown(true);
setShowAutocomplete(true);
}}
/>
</Tooltip>
</ButtonGroup>
</InputAdornment>
),
}}
multiline={data.name.startsWith("${") && data.name.endsWith("}") ? true : multiline}
@@ -1651,11 +1637,11 @@ const ParsedAction = (props) => {
*/
//console.log("Clicked field: ", clickedFieldId)
if (setScrollConfig !== undefined && scrollConfig !== null && scrollConfig !== undefined && scrollConfig.selected !== clickedFieldId) {
scrollConfig.selected = clickedFieldId
setScrollConfig(scrollConfig)
//console.log("Change field id!")
}
if (setScrollConfig !== undefined && scrollConfig !== null && scrollConfig !== undefined && scrollConfig.selected !== clickedFieldId) {
scrollConfig.selected = clickedFieldId
setScrollConfig(scrollConfig)
//console.log("Change field id!")
}
}}
id={clickedFieldId}
rows={data.name.startsWith("${") && data.name.endsWith("}") ? 2 : rows}
@@ -1696,10 +1682,10 @@ const ParsedAction = (props) => {
null : null
}
onBlur={(event) => {
baseHelperText = calculateHelpertext(event.target.value)
if (setLastSaved !== undefined) {
setLastSaved(false)
}
baseHelperText = calculateHelpertext(event.target.value)
if (setLastSaved !== undefined) {
setLastSaved(false)
}
}}
/>
);
@@ -2003,12 +1989,11 @@ const ParsedAction = (props) => {
datafield = (
<Select
MenuProps={{
disableScrollLock: true,
}}
MenuProps={{
disableScrollLock: true,
}}
SelectDisplayProps={{
style: {
marginLeft: 10,
},
}}
value={selectedActionParameters[count].value}
@@ -2568,7 +2553,7 @@ const ParsedAction = (props) => {
*/}
</div>
{datafield}
{shufflecode}
{/*shufflecode*/}
{showDropdown &&
showDropdownNumber === count &&
data.variant === "STATIC_VALUE" &&
@@ -2587,7 +2572,6 @@ const ParsedAction = (props) => {
labelId="action-autocompleter"
SelectDisplayProps={{
style: {
marginLeft: 10,
},
}}
onClose={() => {
@@ -2680,6 +2664,62 @@ const ParsedAction = (props) => {
return null;
};
const ActionSelectOption = (actionprops) => {
const { data, newActionname, newActiondescription, useIcon, extraDescription, } = actionprops;
const [hover, setHover] = React.useState(false);
console.log("Extra desc: ", extraDescription)
return (
<Tooltip
color="secondary"
title={newActiondescription}
placement="left"
>
<div style={{
cursor: "pointer",
padding: 8,
paddingLeft: 14,
paddingBottom: 4,
backgroundColor: hover ? theme.palette.surfaceColor : theme.palette.inputColor,
}} onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
onClick={() => {
//setSelectedAction(actionprops)
//setShowActionList(false)
//setUpdate(Math.random())
//
if (data !== undefined && data !== null) {
setNewSelectedAction({
target: {
value: data.name
}
});
}
}}
>
<div style={{ display: "flex", marginBottom: 0,}}>
<span
style={{
marginRight: 10,
marginTop: "auto",
marginBottom: 0,
}}
>
{useIcon}
</span>
<span style={{marginBottom: 0, marginTop: 3, }}>{newActionname}</span>
</div>
{extraDescription.length > 0 ?
<Typography variant="body2" color="textSecondary" style={{marginTop: 0, overflow: "hidden", whiteSpace: "nowrap", display: "block",}}>
{extraDescription}
</Typography>
: null}
</div>
</Tooltip>
)
}
//const CustomPopper = function (props) {
// const classes = useStyles()
// return <Popper {...props} className={classes.root} placement="bottom" />
@@ -2906,7 +2946,6 @@ const ParsedAction = (props) => {
}}
SelectDisplayProps={{
style: {
marginLeft: 10,
},
}}
>
@@ -2941,6 +2980,7 @@ const ParsedAction = (props) => {
<div style={{flex: 5}}>
<Typography style={{color: "rgba(255,255,255,0.7)"}}>Name</Typography>
<TextField
style={theme.palette.textFieldStyle}
InputProps={{
style: theme.palette.innerTextfieldStyle,
@@ -3227,8 +3267,7 @@ const ParsedAction = (props) => {
}
SelectDisplayProps={{
style: {
marginLeft: 10,
maxWidth: 250,
maxWidth: 250,
},
}}
fullWidth
@@ -3329,7 +3368,6 @@ const ParsedAction = (props) => {
}
SelectDisplayProps={{
style: {
marginLeft: 10,
},
}}
fullWidth
@@ -3375,9 +3413,9 @@ const ParsedAction = (props) => {
<div style={{ marginTop: "20px" }}>
<Typography>Execution variable (optional)</Typography>
<Select
MenuProps={{
disableScrollLock: true,
}}
MenuProps={{
disableScrollLock: true,
}}
value={
selectedAction.execution_variable !== undefined
&& selectedAction.execution_variable !== null
@@ -3389,7 +3427,6 @@ const ParsedAction = (props) => {
}
SelectDisplayProps={{
style: {
marginLeft: 10,
},
}}
fullWidth
@@ -3458,20 +3495,20 @@ const ParsedAction = (props) => {
autoHighlight
value={selectedAction}
classes={{ inputRoot: classes.inputRoot }}
groupBy={(option) => {
// Most popular
// Is categorized
// Uncategorized
return option.category_label !== undefined && option.category_label !== null && option.category_label.length > 0 ? "Most used" : "All Actions";
}}
renderGroup={(params) => {
return (
<li key={params.key}>
<Typography variant="body1" style={{textAlign: "center", marginLeft: 10, marginTop: 25, marginBottom: 10, }}>{params.group}</Typography>
<Typography variant="body2">{params.children}</Typography>
</li>
)
}}
groupBy={(option) => {
// Most popular
// Is categorized
// Uncategorized
return option.category_label !== undefined && option.category_label !== null && option.category_label.length > 0 ? "Most used" : "All Actions";
}}
renderGroup={(params) => {
return (
<li key={params.key}>
<Typography variant="body1" style={{textAlign: "center", marginLeft: 10, marginTop: 25, marginBottom: 10, }}>{params.group}</Typography>
<Typography variant="body2">{params.children}</Typography>
</li>
)
}}
options={selectedApp.actions === undefined || selectedApp.actions === null ? [] : selectedApp.actions.filter((a) => a.category_label !== undefined && a.category_label !== null && a.category_label.length > 0).concat(sortByKey(selectedApp.actions, "label"))}
ListboxProps={{
style: {
@@ -3491,8 +3528,6 @@ const ParsedAction = (props) => {
return null;
}
console.log("OPTION: ", option)
const newname = (
option.name.charAt(0).toUpperCase() + option.name.substring(1)
).replaceAll("_", " ");
@@ -3509,10 +3544,10 @@ const ParsedAction = (props) => {
// Workaround with event lol
if (newValue !== undefined && newValue !== null) {
setNewSelectedAction({
target: {
value: newValue.name
}
});
target: {
value: newValue.name
}
});
}
}}
renderOption={(props, data, state) => {
@@ -3560,100 +3595,87 @@ const ParsedAction = (props) => {
method = "CONNECT"
}
// FIXME: Should it require a base URL?
if (method.length > 0 && data.description !== undefined && data.description !== null && data.description.includes("http")) {
var extraUrl = ""
const descSplit = data.description.split("\n")
// Last line of descSplit
if (descSplit.length > 0) {
extraUrl = descSplit[descSplit.length-1]
}
// FIXME: Should it require a base URL?
if (method.length > 0 && data.description !== undefined && data.description !== null && data.description.includes("http")) {
var extraUrl = ""
const descSplit = data.description.split("\n")
// Last line of descSplit
if (descSplit.length > 0) {
extraUrl = descSplit[descSplit.length-1]
}
//for (let [line,lineval] in Object.entries(descSplit)) {
// if (descSplit[line].includes("http") && descSplit[line].includes("://")) {
// const urlsplit = descSplit[line].split("/")
// try {
// extraUrl = "/"+urlsplit.slice(3, urlsplit.length).join("/")
// } catch (e) {
// //console.log("Failed - running with -1")
// extraUrl = "/"+urlsplit.slice(3, urlsplit.length-1).join("/")
// }
//for (let [line,lineval] in Object.entries(descSplit)) {
// if (descSplit[line].includes("http") && descSplit[line].includes("://")) {
// const urlsplit = descSplit[line].split("/")
// try {
// extraUrl = "/"+urlsplit.slice(3, urlsplit.length).join("/")
// } catch (e) {
// //console.log("Failed - running with -1")
// extraUrl = "/"+urlsplit.slice(3, urlsplit.length-1).join("/")
// }
// //console.log("NO BASEURL TOO!! Why missing last one in certain scenarios (sevco)?", extraUrl, urlsplit, descSplit[line])
// //break
// }
//}
// //console.log("NO BASEURL TOO!! Why missing last one in certain scenarios (sevco)?", extraUrl, urlsplit, descSplit[line])
// //break
// }
//}
if (extraUrl.length > 0) {
if (extraUrl.includes(" ")) {
extraUrl = extraUrl.split(" ")[0]
}
if (extraUrl.length > 0) {
if (extraUrl.includes(" ")) {
extraUrl = extraUrl.split(" ")[0]
}
if (extraUrl.includes("#")) {
extraUrl = extraUrl.split("#")[0]
}
if (extraUrl.includes("#")) {
extraUrl = extraUrl.split("#")[0]
}
extraDescription = `${method} ${extraUrl}`
} else {
//console.log("No url found. Check again :)")
}
}
extraDescription = `${method} ${extraUrl}`
} else {
//console.log("No url found. Check again :)")
}
}
return (
<Tooltip
color="secondary"
title={newActiondescription}
placement="left"
>
<div>
<div style={{ display: "flex", marginBottom: 0,}}>
<span
style={{
marginRight: 10,
marginTop: "auto",
marginBottom: 0,
}}
>
{useIcon}
</span>
<span style={{marginBottom: 0, marginTop: 3, }}>{newActionname}</span>
</div>
{extraDescription.length > 0 ?
<Typography variant="body2" color="textSecondary" style={{marginTop: 0, overflow: "hidden", whiteSpace: "nowrap", display: "block",}}>
{extraDescription}
</Typography>
: null}
</div>
</Tooltip>
<ActionSelectOption
data={data}
newActiondescription={newActiondescription}
useIcon={useIcon}
newActionname={newActionname}
extraDescription={extraDescription}
/>
);
}}
renderInput={(params) => {
if (params.inputProps !== undefined && params.inputProps !== null && params.inputProps.value !== undefined && params.inputProps.value !== null) {
const prefixes = ["Post", "Put", "Patch"]
for (let [key,keyval] in Object.entries(prefixes)) {
if (params.inputProps.value.startsWith(prefixes[key])) {
params.inputProps.value = params.inputProps.value.replace(prefixes[key]+" ", "", -1)
if (params.inputProps.value.length > 1) {
params.inputProps.value = params.inputProps.value.charAt(0).toUpperCase()+params.inputProps.value.substring(1)
}
break
}
}
if (params.inputProps !== undefined && params.inputProps !== null && params.inputProps.value !== undefined && params.inputProps.value !== null) {
const prefixes = ["Post", "Put", "Patch"]
for (let [key,keyval] in Object.entries(prefixes)) {
if (params.inputProps.value.startsWith(prefixes[key])) {
params.inputProps.value = params.inputProps.value.replace(prefixes[key]+" ", "", -1)
if (params.inputProps.value.length > 1) {
params.inputProps.value = params.inputProps.value.charAt(0).toUpperCase()+params.inputProps.value.substring(1)
}
break
}
}
}
return (
<TextField
color="primary"
variant="body1"
style={{
backgroundColor: theme.palette.inputColor,
borderRadius: theme.palette.borderRadius,
}}
{...params}
label="Find Actions"
variant="outlined"
/>
<TextField
data-lpignore="true"
autocomplete="off"
dataLPIgnore="true"
color="primary"
id="checkbox-search"
variant="body1"
style={{
backgroundColor: theme.palette.inputColor,
borderRadius: theme.palette.borderRadius,
}}
{...params}
label="Find Actions"
variant="outlined"
/>
);
}}
/>
+4 -2
View File
@@ -12,10 +12,10 @@ import {
} from "@mui/material";
import Priority from "../components/Priority.jsx";
import { useAlert } from "react-alert";
//import { useAlert
const Priorities = (props) => {
const { globalUrl, userdata, serverside, billingInfo, stripeKey, checkLogin, } = props;
const { globalUrl, userdata, serverside, billingInfo, stripeKey, checkLogin, setAdminTab, setCurTab, } = props;
const [showDismissed, setShowDismissed] = React.useState(false);
const [showRead, setShowRead] = React.useState(false);
@@ -60,6 +60,8 @@ const Priorities = (props) => {
globalUrl={globalUrl}
priority={priority}
checkLogin={checkLogin}
setAdminTab={setAdminTab}
setCurTab={setCurTab}
/>
)
})
+31 -15
View File
@@ -1,5 +1,7 @@
import React, { useState, useEffect } from "react";
import { toast } from 'react-toastify';
import ReactGA from 'react-ga4';
import theme from "../theme.jsx";
import { useNavigate, Link } from "react-router-dom";
import {
@@ -16,12 +18,13 @@ import {
AutoFixHigh as AutoFixHighIcon,
ArrowForward as ArrowForwardIcon,
} from '@mui/icons-material';
import { useAlert } from "react-alert";
//import { useAlert
const Priority = (props) => {
const { globalUrl, userdata, serverside, priority, checkLogin, } = props;
const { globalUrl, userdata, serverside, priority, checkLogin, setAdminTab, setCurTab, } = props;
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
let navigate = useNavigate();
const changeRecommendation = (recommendation, action) => {
const data = {
@@ -54,20 +57,20 @@ const Priority = (props) => {
}
} else {
if (responseJson.success === false && responseJson.reason !== undefined) {
alert.error("Failed change recommendation: ", responseJson.reason)
toast("Failed change recommendation: ", responseJson.reason)
} else {
alert.error("Failed change recommendation");
toast("Failed change recommendation");
}
}
})
.catch((error) => {
alert.info("Failed dismissing alert. Please contact support@shuffler.io if this persists.");
toast("Failed dismissing alert. Please contact support@shuffler.io if this persists.");
});
}
return (
<div style={{border: priority.active === false ? "1px solid #000000" : priority.severity === 1 ? "1px solid #f85a3e" : "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, marginTop: 10, marginBottom: 10, padding: 15, textAlign: "center", height: 70, textAlign: "left", backgroundColor: theme.palette.surfaceColor, display: "flex", }}>
<div style={{border: priority.active === false ? "1px solid #000000" : priority.severity === 1 ? "1px solid #f85a3e" : "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, marginTop: 10, marginBottom: 10, padding: 15, textAlign: "center", height: 95, textAlign: "left", backgroundColor: theme.palette.surfaceColor, display: "flex", }}>
<div style={{flex: 2, overflow: "hidden",}}>
<span style={{display: "flex", }}>
{priority.type === "usecase" || priority.type == "apps" ? <AutoFixHighIcon style={{height: 19, width: 19, marginLeft: 3, marginRight: 10, }}/> : null}
@@ -100,17 +103,30 @@ const Priority = (props) => {
}
</div>
<div style={{flex: 1, display: "flex", marginLeft: 30, }}>
<Button style={{height: 50, borderRadius: 25, marginTop: 8, width: 175, marginRight: 10, color: priority.active === false ? "white" : "black", backgroundColor: priority.active === false ? theme.palette.inputColor : "white", }} variant="contained" color="secondary" onClick={() => {
/*
ReactGA.event({
category: "",
action: `partner_${partner.name}_click`,
label: "",
})
*/
<Button style={{height: 50, borderRadius: 25, marginTop: 8, width: 175, marginRight: 10, color: priority.active === false ? "white" : "black", backgroundColor: priority.active === false ? theme.palette.inputColor : "rgba(255,255,255,0.8)", }} variant="contained" color="secondary" onClick={() => {
if (isCloud) {
ReactGA.event({
category: "recommendation",
action: `click_${priority.name}`,
label: "",
})
}
navigate(priority.url)
if (setAdminTab !== undefined && setCurTab !== undefined) {
if (priority.description.toLowerCase().includes("notification workflow")) {
setCurTab(0)
setAdminTab(0)
}
if (priority.description.toLowerCase().includes("hybrid shuffle")) {
setCurTab(6)
}
}
}}>
explore
Explore
</Button>
{priority.active === true ?
<Button style={{borderRadius: 25, width: 100, height: 50, marginTop: 8, }} variant="text" color="secondary" onClick={() => {
-177
View File
@@ -1,177 +0,0 @@
import React, { useState } from "react";
import DialogTitle from "@material-ui/core/DialogTitle";
import Dialog from "@material-ui/core/Dialog";
import TextField from "@material-ui/core/TextField";
import Button from "@material-ui/core/Button";
import Divider from "@material-ui/core/Divider";
const SettingsDialog = (props) => {
const {
classes,
onClose,
settingsOpen,
settingsData,
globalUrl,
isLoggedIn,
setIsLoggedIn,
...other
} = props;
const [password1, setPassword1] = useState("");
const [password2, setPassword2] = useState("");
const [password3, setPassword3] = useState("");
const handleValidateForm = () => {
var passlength = 10;
if (
password1 === password2 &&
password1.length >= passlength &&
password3.length >= passlength
) {
return true;
}
return false;
};
const onChangePass1 = (e) => {
setPassword1(e.target.value);
};
const onChangePass2 = (e) => {
setPassword2(e.target.value);
};
const onChangePass3 = (e) => {
setPassword3(e.target.value);
};
const onSubmitPassReset = () => {
console.log("Should change password");
// Rofl, this can't possibly be typesafe
var data =
'{"password1": "' +
password1 +
'", "password2": "' +
password2 +
'", "password3": "' +
password3 +
'"}';
fetch(globalUrl + "/passwordreset", {
body: data,
method: "POST",
headers: {
"Content-Type": "application/json",
},
})
.then((response) => response.json())
.then((responseJson) => {
console.log(responseJson);
if (responseJson.status === true) {
console.log("SUCCESS");
}
})
.catch((error) => {
console.log(error);
});
};
//PaperProps={{style: {minWidth: "500px"}}
return (
<Dialog open={settingsOpen} onClose={() => onClose()} {...other}>
<DialogTitle>Settings</DialogTitle>
<Divider />
<div style={{ marginLeft: "15px", marginRight: "15px" }}>
<h3>Username</h3>
{settingsData.username}
</div>
<div
style={{
marginLeft: "15px",
marginRight: "15px",
marginBottom: "15px",
}}
>
<h3>ApiKey</h3>
<TextField
id="outlined-read-only-input"
defaultValue={settingsData.apikey}
value={settingsData.apikey}
style={{ width: 320 }}
InputProps={{
readOnly: true,
}}
variant="outlined"
/>
</div>
<Divider />
<form style={{ margin: "15px 15px 15px 15px" }}>
<h3>Change password</h3>
<div>
<TextField
id="standard-password-input"
label="Current password"
type="password"
name="password"
style={{ width: 320 }}
placeholder="********************************"
autoComplete="current-password"
margin="normal"
variant="outlined"
onChange={onChangePass1}
/>
</div>
<div>
<TextField
label="Confirm current password"
type="password"
placeholder="********************************"
name="password"
style={{ width: 320 }}
autoComplete="current-password"
margin="normal"
variant="outlined"
onChange={onChangePass2}
/>
</div>
<div>
<TextField
label="New password"
type="password"
name="password"
placeholder="********************************"
style={{ width: 320 }}
margin="normal"
variant="outlined"
onChange={onChangePass3}
/>
</div>
<div style={{ display: "flex", marginTop: "10px" }}>
<Button
color="secondary"
variant="contained"
onClick={onSubmitPassReset}
type="button"
style={{ flex: "1", marginRight: "5px" }}
disabled={!handleValidateForm()}
>
SUBMIT
</Button>
<Button
color="primary"
variant="contained"
type="button"
style={{ flex: "1" }}
onClick={onClose}
>
Cancel
</Button>
</div>
</form>
</Dialog>
);
};
export default SettingsDialog;
+76 -71
View File
@@ -1,4 +1,5 @@
import React, {useState, useEffect, useLayoutEffect} from 'react';
import { toast } from 'react-toastify';
import {
CircularProgress,
IconButton,
@@ -54,9 +55,10 @@ import {indentWithTab} from "@codemirror/commands"
import { padding, textAlign } from '@mui/system';
import data from '../frameworkStyle.jsx';
import { useNavigate, Link, useParams } from "react-router-dom";
import { tags as t } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes';
import { tags } from '@lezer/highlight';
const liquidFilters = [
{"name": "Size", "value": "size", "example": ""},
@@ -81,44 +83,39 @@ const pythonFilters = [
{"name": "Handle JSON", "value": `{% python %}\nimport json\njsondata = json.loads(r"""$nodename""")\n{% endpython %}`, "example": ``},
]
//const shuffleTheme = createTheme({
// theme: 'dark',
// settings: {
// background: '#282828',
// foreground: '#282828',
// caret: '#5d00ff',
// selection: '#036dd626',
// selectionMatch: '#036dd626',
// lineHighlight: '#8a91991a',
// gutterBackground: '#282828',
// gutterForeground: '#8a919966',
// fontSize: 18,
// borderRadius: theme.palette.borderRadius,
// border: `2px solid ${theme.palette.inputColor}`,
// },
// styles: [
// { tag: tags.comment, color: '#787b8099' },
// { tag: tags.variableName, color: '#0080ff' },
// { tag: [tags.string, tags.special(tags.brace)], color: '#5c6166' },
// { tag: tags.number, color: '#5c6166' },
// { tag: tags.bool, color: '#5c6166' },
// { tag: tags.null, color: '#5c6166' },
// { tag: tags.keyword, color: '#5c6166' },
// { tag: tags.operator, color: '#5c6166' },
// { tag: tags.className, color: '#5c6166' },
// { tag: tags.definition(tags.typeName), color: '#5c6166' },
// { tag: tags.typeName, color: '#5c6166' },
// { tag: tags.angleBracket, color: '#5c6166' },
// { tag: tags.tagName, color: '#5c6166' },
// { tag: tags.attributeName, color: '#5c6166' },
// ],
//});
const shuffleTheme = createTheme({
theme: 'dark',
settings: {
background: "rgba(40,40,40, 1)",
foreground: '#75baff',
caret: '#5d00ff',
selection: '#036dd626',
selectionMatch: '#036dd626',
lineHighlight: '#8a91991a',
gutterForeground: '#8a919966',
},
styles: [
{ tag: t.comment, color: '#787b8099' },
{ tag: t.variableName, color: '#0080ff' },
{ tag: [t.string, t.special(t.brace)], color: '#5c6166' },
{ tag: t.number, color: '#5c6166' },
{ tag: t.bool, color: '#5c6166' },
{ tag: t.null, color: '#5c6166' },
{ tag: t.keyword, color: '#5c6166' },
{ tag: t.operator, color: '#5c6166' },
{ tag: t.className, color: '#5c6166' },
{ tag: t.definition(t.typeName), color: '#5c6166' },
{ tag: t.typeName, color: '#5c6166' },
{ tag: t.angleBracket, color: '#5c6166' },
{ tag: t.tagName, color: '#5c6166' },
{ tag: t.attributeName, color: '#5c6166' },
],
});
const CodeEditor = (props) => {
const {
globalUrl,
fieldCount,
setFieldCount,
actionlist,
changeActionParameterCodeMirror,
expansionModalOpen,
@@ -132,6 +129,8 @@ const CodeEditor = (props) => {
selectedAction ,
workflowExecutions,
getParents,
fieldname,
} = props
const [localcodedata, setlocalcodedata] = React.useState(codedata === undefined || codedata === null || codedata.length === 0 ? "" : codedata);
@@ -140,7 +139,7 @@ const CodeEditor = (props) => {
const [validation, setValidation] = React.useState(false);
const [expOutput, setExpOutput] = React.useState(" ");
const [linewrap, setlinewrap] = React.useState(true);
const [codeTheme, setcodeTheme] = React.useState("gruvbox-dark");
//const [codeTheme, setcodeTheme] = React.useState("gruvbox-dark");
const [editorPopupOpen, setEditorPopupOpen] = React.useState(false);
const [currentCharacter, setCurrentCharacter] = React.useState(-1);
@@ -155,8 +154,8 @@ const CodeEditor = (props) => {
const [mainVariables, setMainVariables] = React.useState([]);
const [availableVariables, setAvailableVariables] = React.useState([]);
const [menuPosition, setMenuPosition] = useState(null);
const [showAutocomplete, setShowAutocomplete] = React.useState(false);
const [menuPosition, setMenuPosition] = useState(null);
const [showAutocomplete, setShowAutocomplete] = React.useState(false);
const [isAiLoading, setIsAiLoading] = React.useState(false);
@@ -863,12 +862,12 @@ const CodeEditor = (props) => {
var newResult = {}
if (responseJson.success === true && responseJson.result !== null && responseJson.result !== undefined && responseJson.result.length > 0) {
const result = responseJson.result.slice(0, 50)+"..."
//alert.info("SUCCESS: "+result)
//toast("SUCCESS: "+result)
const validate = validateJson(responseJson.result)
newResult = validate
} else if (responseJson.success === false && responseJson.reason !== undefined && responseJson.reason !== null) {
alert.error(responseJson.reason)
toast(responseJson.reason)
newResult = {"valid": false, "result": responseJson.reason}
} else if (responseJson.success === true) {
newResult = {"valid": false, "result": "Couldn't finish execution. Please fill all the required fields, and retry the execution."}
@@ -884,7 +883,7 @@ const CodeEditor = (props) => {
setExecuting(false)
})
.catch(error => {
//alert.error("Execution error: "+error.toString())
//toast("Execution error: "+error.toString())
console.log("error: ", error)
setExecuting(false)
})
@@ -919,7 +918,6 @@ const CodeEditor = (props) => {
maxHeight: isMobile ? "100%" : 720,
border: theme.palette.defaultBorder,
padding: isMobile ? "25px 10px 25px 10px" : 25,
backgroundColor: theme.palette.surfaceColor,
},
}}
>
@@ -1020,6 +1018,7 @@ const CodeEditor = (props) => {
aria-controls={liquidOpen ? 'basic-menu' : undefined}
aria-expanded={liquidOpen ? 'true' : undefined}
variant="outlined"
color="secondary"
style={{
textTransform: "none",
width: 100,
@@ -1055,6 +1054,7 @@ const CodeEditor = (props) => {
aria-controls={mathOpen ? 'basic-menu' : undefined}
aria-expanded={mathOpen ? 'true' : undefined}
variant="outlined"
color="secondary"
style={{
textTransform: "none",
width: 100,
@@ -1090,6 +1090,7 @@ const CodeEditor = (props) => {
aria-controls={pythonOpen ? 'basic-menu' : undefined}
aria-expanded={pythonOpen ? 'true' : undefined}
variant="outlined"
color="secondary"
style={{
textTransform: "none",
width: 100,
@@ -1125,6 +1126,7 @@ const CodeEditor = (props) => {
aria-controls={!!menuPosition ? 'basic-menu' : undefined}
aria-expanded={!!menuPosition ? 'true' : undefined}
variant="outlined"
color="secondary"
style={{
textTransform: "none",
width: 130,
@@ -1381,7 +1383,7 @@ const CodeEditor = (props) => {
position: "relative",
}}>
<CodeMirror
value = {localcodedata}
value={localcodedata}
height={isFileEditor ? 450 : 525}
width={isFileEditor ? 650 : 600}
style={{
@@ -1389,6 +1391,8 @@ const CodeEditor = (props) => {
wordBreak: "break-word",
marginTop: 0,
paddingTop: 0,
backgroundColor: "rgba(40,40,40,1)",
minHeight: 470,
}}
onCursorActivity = {(value) => {
// console.log(value.getCursor())
@@ -1398,27 +1402,22 @@ const CodeEditor = (props) => {
findIndex(value.getCursor().line, value.getCursor().ch)
highlight_variables(value)
}}
onChange={(value) => {
//console.log("Value: '", value.getValue(), "'")
onChange={(value, viewUpdate) => {
console.log("Value: ", value, viewUpdate)
setlocalcodedata(value)
expectedOutput(value)
setlocalcodedata(value.getValue())
expectedOutput(value.getValue())
if(value.display.input.prevInput.startsWith('$') || value.display.input.prevInput.endsWith('$')){
setEditorPopupOpen(true)
}
// console.log(findIndex(value.getValue()))
// highlight_variables(value)
//if(value.display.input.prevInput.startsWith('$') || value.display.input.prevInput.endsWith('$')){
// setEditorPopupOpen(true)
//}
}}
extensions={[indentWithTab]}
extensions={[]}//indentWithTab]}
theme={shuffleTheme}
options={{
styleSelectedText: true,
theme: codeTheme,
keyMap: 'sublime',
mode: validation === true ? "json" : "python",
lineWrapping: linewrap,
}}
/>
</span>
@@ -1664,34 +1663,29 @@ const CodeEditor = (props) => {
<div style={{display: 'flex',}}>
<button
<Button
style={{
color: "white",
background: "#383b49",
border: "none",
height: 35,
flex: 1,
marginLeft: 5,
marginTop: 5,
cursor: "pointer"
}}
variant="outlined"
color="secondary"
onClick={() => {
setExpansionModalOpen(false);
}}
>
Cancel
</button>
<button
</Button>
<Button
variant="contained"
color="primary"
style={{
color: "white",
background: "#f85a3e",
border: "none",
height: 35,
flex: 1,
marginLeft: 10,
marginTop: 5,
cursor: "pointer"
}}
onClick={(event) => {
// Take localcodedata through the Shuffle JSON parser just in case
@@ -1709,15 +1703,26 @@ const CodeEditor = (props) => {
runUpdateText(fixedcodedata);
setcodedata(fixedcodedata);
setExpansionModalOpen(false)
} else {
changeActionParameterCodeMirror(event, fieldCount, fixedcodedata)
} else if (changeActionParameterCodeMirror !== undefined) {
//changeActionParameterCodeMirror(event, fieldCount, fixedcodedata)
changeActionParameterCodeMirror(event, fieldCount, fixedcodedata, actionlist)
setExpansionModalOpen(false)
setcodedata(fixedcodedata)
}
// Check if fieldname is set, and try to find and inject the text
if (fieldname !== undefined && fieldname !== null && fieldname.length > 0) {
const foundfield = document.getElementById(fieldname)
if (foundfield !== undefined && foundfield !== null) {
foundfield.value = fixedcodedata
}
}
setExpansionModalOpen(false)
}}
>
Submit
</button>
</Button>
</div>
</Dialog>)
}
+21 -20
View File
@@ -1,7 +1,8 @@
import React, { useState, useEffect } from "react";
import { toast } from 'react-toastify';
import theme from '../theme.jsx';
import { useNavigate, Link } from "react-router-dom";
import { useAlert } from "react-alert";
//import { useAlert
import ConfigureWorkflow from "../components/ConfigureWorkflow.jsx";
import PaperComponent from "../components/PaperComponent.jsx"
import AuthenticationOauth2 from "../components/Oauth2Auth.jsx";
@@ -349,14 +350,14 @@ const UsecaseSearch = (props) => {
const [firstRequest, setFirstRequest] = React.useState(true);
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
const alert = useAlert()
//const alert = useAlert()
useEffect(() => {
// if (firstRequest !== true && workflow.id !== undefined && autotry === true && setUsecaseSearch !== undefined && authenticationModalOpen === false && configureWorkflowModalOpen === false) {
//
if (autotry === true && configureWorkflowModalOpen === false && workflow.id !== undefined && setUsecaseSearch !== undefined) {
console.log("Close it?")
alert.info("Workflow successfully added! Add more apps, and we will suggest more workflows")
toast("Workflow successfully added! Add more apps, and we will suggest more workflows")
if (setCloseWindow !== undefined) {
setCloseWindow(true)
@@ -793,7 +794,7 @@ const UsecaseSearch = (props) => {
console.log("Deleted workflow")
})
.catch((error) => {
//alert.error(error.toString());
//toast(error.toString());
console.log("Delete workflow error: ", error.toString());
})
}
@@ -822,7 +823,7 @@ const UsecaseSearch = (props) => {
}
})
.catch((error) => {
//alert.error(error.toString());
//toast(error.toString());
console.log("Get workflows error: ", error.toString());
})
}
@@ -894,9 +895,9 @@ const UsecaseSearch = (props) => {
.then((responseJson) => {
if (responseJson.success === false) {
if (responseJson.reason !== undefined) {
alert.error("Error setting workflow: ", responseJson.reason)
toast("Error setting workflow: ", responseJson.reason)
} else {
alert.error("Error setting workflow.")
toast("Error setting workflow.")
}
return
@@ -905,7 +906,7 @@ const UsecaseSearch = (props) => {
return responseJson;
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
}
@@ -930,7 +931,7 @@ const UsecaseSearch = (props) => {
if (responseJson.success === false) {
if (responseJson.reason !== null && responseJson.reason !== undefined) {
//alert.error(responseJson.reason)
//toast(responseJson.reason)
}
if (responseJson.source === "") {
@@ -1009,13 +1010,13 @@ const UsecaseSearch = (props) => {
responseJson.status,
).then((response) => {
if (response !== undefined) {
alert.success("Successfully generated " + responseJson.name);
toast("Successfully generated " + responseJson.name);
}
});
}
})
.catch((error) => {
alert.error("Generate error: " + error.toString());
toast("Generate error: " + error.toString());
})
@@ -1057,7 +1058,7 @@ const UsecaseSearch = (props) => {
.catch((error) => {
setIsUploading(false)
console.log("Merge err: ", error.toString())
//alert.error("Err: " + error.toString());
//toast("Err: " + error.toString());
});
}
@@ -1207,7 +1208,7 @@ const UsecaseSearch = (props) => {
}
if (changed) {
//alert.error("Errors were found. Click them to sort sort them out or go to the next usecase.")
//toast("Errors were found. Click them to sort sort them out or go to the next usecase.")
//setUpdate(Math.random())
//setIsUploading(false)
@@ -1268,13 +1269,13 @@ const UsecaseSearch = (props) => {
})
.then((responseJson) => {
if (responseJson.success === false) {
alert.error("Failed to activate the app")
toast("Failed to activate the app")
} else {
//alert.success("App activated for your organization! Refresh the page to use the app.")
//toast("App activated for your organization! Refresh the page to use the app.")
}
})
.catch(error => {
//alert.error(error.toString())
//toast(error.toString())
console.log("Activate app error: ", error.toString())
});
}
@@ -1304,9 +1305,9 @@ const UsecaseSearch = (props) => {
.then((responseJson) => {
if (responseJson.success === false) {
if (responseJson.reason !== undefined) {
alert.error("Failed updating default app: " + responseJson.reason)
toast("Failed updating default app: " + responseJson.reason)
} else {
alert.error("Failed to update framework for your org.")
toast("Failed to update framework for your org.")
}
} else {
@@ -1319,7 +1320,7 @@ const UsecaseSearch = (props) => {
//setFrameworkData(responseJson)
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
//setFrameworkLoaded(true)
})
}
@@ -1516,7 +1517,7 @@ const UsecaseSearch = (props) => {
return (
<div key={curindex} style={{display: "flex", maxHeight: 40, minHeight: 40, borderTop: "1px solid rgba(255,255,255,0.3)", }} onClick={() => {
if (subdata.disabled === true) {
//alert.info("Usecase not available yet.")
//toast("Usecase not available yet.")
return
}
+13 -13
View File
@@ -38,7 +38,7 @@ import {
Chip,
ButtonGroup,
} from "@mui/material";
import { useAlert } from "react-alert";
//import { useAlert
import { useNavigate, Link } from "react-router-dom";
import WorkflowSearch from '../components/Workflowsearch.jsx';
@@ -134,7 +134,7 @@ const WelcomeForm = (props) => {
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
const alert = useAlert();
//const alert = useAlert();
let navigate = useNavigate();
const onNodeSelect = (label) => {
@@ -262,16 +262,16 @@ const WelcomeForm = (props) => {
response.json().then((responseJson) => {
if (responseJson["success"] === false) {
console.log("Update user success")
//alert.error("Failed updating org: ", responseJson.reason);
//toast("Failed updating org: ", responseJson.reason);
} else {
console.log("Update success!")
//alert.success("Successfully edited org!");
//toast("Successfully edited org!");
}
})
)
.catch((error) => {
console.log("Update err: ", error.toString())
//alert.error("Err: " + error.toString());
//toast("Err: " + error.toString());
});
}
@@ -308,15 +308,15 @@ const WelcomeForm = (props) => {
response.json().then((responseJson) => {
if (responseJson["success"] === false) {
console.log("Update of org failed")
//alert.error("Failed updating org: ", responseJson.reason);
//toast("Failed updating org: ", responseJson.reason);
} else {
//alert.success("Successfully edited org!");
//toast("Successfully edited org!");
}
})
)
.catch((error) => {
console.log("Update err: ", error.toString())
//alert.error("Err: " + error.toString());
//toast("Err: " + error.toString());
});
}
@@ -615,7 +615,7 @@ const WelcomeForm = (props) => {
<Grid item xs={11} style={{marginTop: 25, }}>
{/*<FormLabel style={{ color: "#B9B9BA" }}>Find your integrations!</FormLabel>*/}
<div style={{display: "flex"}}>
<Button disabled={finishedApps.includes("CASES")} variant={defaultSearch === "CASES" ? "contained" : "outlined"} style={{
<Button disabled={finishedApps.includes("CASES")} variant={defaultSearch === "CASES" ? "contained" : "outlined"} color="secondary" style={{
flex: 1,
width: "100%",
padding: 25,
@@ -627,20 +627,20 @@ const WelcomeForm = (props) => {
</Button>
</div>
<div style={{display: "flex"}}>
<Button disabled={finishedApps.includes("SIEM")} variant={defaultSearch === "SIEM" ? "contained" : "outlined"} style={buttonStyle} startIcon={<SearchIcon />} onClick={(event) => { onNodeSelect("SIEM") }} >
<Button disabled={finishedApps.includes("SIEM")} variant={defaultSearch === "SIEM" ? "contained" : "outlined"} style={buttonStyle} startIcon={<SearchIcon />} color="secondary" onClick={(event) => { onNodeSelect("SIEM") }} >
SIEM
</Button>
<Button disabled={finishedApps.includes("EDR & AV") || finishedApps.includes("ERADICATION")} variant={defaultSearch === "Eradication" ? "contained" : "outlined"} style={buttonStyle} startIcon={<NewReleasesIcon />} onClick={(event) => { onNodeSelect("ERADICATION") }} >
<Button disabled={finishedApps.includes("EDR & AV") || finishedApps.includes("ERADICATION")} variant={defaultSearch === "Eradication" ? "contained" : "outlined"} style={buttonStyle} startIcon={<NewReleasesIcon />} color="secondary" onClick={(event) => { onNodeSelect("ERADICATION") }} >
Endpoint
</Button>
</div>
<div style={{display: "flex"}}>
<Button disabled={finishedApps.includes("INTEL")} variant={defaultSearch === "INTEL" ? "contained" : "outlined"} style={buttonStyle} startIcon={<ExtensionIcon />} onClick={(event) => { onNodeSelect("INTEL") }} >
<Button disabled={finishedApps.includes("INTEL")} variant={defaultSearch === "INTEL" ? "contained" : "outlined"} style={buttonStyle} startIcon={<ExtensionIcon />} color="secondary" onClick={(event) => { onNodeSelect("INTEL") }} >
Intel
</Button>
<Button disabled={finishedApps.includes("COMMS") || finishedApps.includes("EMAIL")} variant={defaultSearch === "EMAIL" ? "contained" : "outlined"} style={buttonStyle} startIcon={<EmailIcon />} onClick={(event) => { onNodeSelect("EMAIL") }} >
<Button disabled={finishedApps.includes("COMMS") || finishedApps.includes("EMAIL")} variant={defaultSearch === "EMAIL" ? "contained" : "outlined"} style={buttonStyle} startIcon={<EmailIcon />} color="secondary" onClick={(event) => { onNodeSelect("EMAIL") }} >
Email
</Button>
</div>
+57 -42
View File
@@ -25,12 +25,9 @@ import WorkflowPaperNew from "../components/WorkflowPaperNew.jsx"
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240")
const AppGrid = props => {
const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, alternativeView, } = props
const isCloud =
window.location.host === "localhost:3002" ||
window.location.host === "shuffler.io";
const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, alternativeView, onlyResults, inputsearch } = props
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows
const xs = parsedXs === undefined || parsedXs === null ? isMobile ? 6 : 4 : parsedXs
//const [apps, setApps] = React.useState([]);
@@ -38,7 +35,9 @@ const AppGrid = props => {
const [formMail, setFormMail] = React.useState("");
const [message, setMessage] = React.useState("");
const [formMessage, setFormMessage] = React.useState("");
const [usecases, setUsecases] = React.useState([]);
const [usecases, setUsecases] = React.useState([]);
const [localMessage, setLocalMessage] = React.useState("");
const buttonStyle = {borderRadius: 30, height: 50, width: 220, margin: isMobile ? "15px auto 15px auto" : 20, fontSize: 18,}
@@ -70,7 +69,7 @@ const AppGrid = props => {
.then(response => {
if (response.success === true) {
setFormMessage(response.reason)
//alert.info("Thanks for submitting!")
//toast("Thanks for submitting!")
} else {
setFormMessage(errorMessage)
}
@@ -153,21 +152,23 @@ const AppGrid = props => {
return response.json();
})
.then((responseJson) => {
if (responseJson.success !== false) {
console.log("Usecases: ", responseJson)
//handleKeysetting(responseJson, workflows)
}
if (responseJson.success !== false) {
console.log("Usecases: ", responseJson)
//handleKeysetting(responseJson, workflows)
}
})
.catch((error) => {
//alert.error("ERROR: " + error.toString());
//toast("ERROR: " + error.toString());
console.log("ERROR: " + error.toString());
});
};
useEffect(() => {
fetchUsecases()
}, [])
// value={currentRefinement}
const SearchBox = ({currentRefinement, refine, isSearchStalled} ) => {
useEffect(() => {
@@ -182,16 +183,23 @@ const AppGrid = props => {
}
}, [])
if (localMessage !== inputsearch && inputsearch !== undefined && inputsearch !== null && inputsearch.length > 0) {
console.log("In refinement: ", inputsearch)
//setLocalMessage(inputsearch)
refine(inputsearch)
} else if (onlyResults === true) {
// Don't return anything unless refinement works
return null
}
return (
<form noValidate action="" role="search">
{onlyResults !== true ?
<TextField
fullWidth
style={{backgroundColor: theme.palette.inputColor, borderRadius: borderRadius, margin: 10, width: "100%",}}
InputProps={{
style:{
color: "white",
fontSize: "1em",
height: 50,
},
startAdornment: (
<InputAdornment position="start">
@@ -210,8 +218,8 @@ const AppGrid = props => {
}}
limit={5}
/>
{/*isSearchStalled ? 'My search is stalled' : ''*/}
</form>
: null}
</form>
)
}
@@ -228,29 +236,35 @@ const AppGrid = props => {
var counted = 0
return (
<Grid container spacing={4} style={paperAppContainer}>
{hits.map((data, index) => {
workflowDelay += 50
<div>
{onlyResults === true && hits.length > 0 ?
<Typography variant="h6" style={{paddingBottom: 0, }}>
Relevant Workflows
</Typography>
: null}
<Grid container spacing={4} style={paperAppContainer}>
{hits.map((data, index) => {
workflowDelay += 50
if (counted === 12/xs*rowHandler) {
return null
}
if (counted === 12/xs*rowHandler) {
return null
}
counted += 1
counted += 1
return (
<Zoom key={index} in={true} style={{ transitionDelay: `${workflowDelay}ms` }}>
<Grid item xs={xs} style={{ padding: "12px 10px 12px 10px" }}>
return (
<Grid item xs={xs} style={{ padding: "12px 10px 12px 10px",}}>
{/*<Zoom key={index} in={true} style={{ transitionDelay: `${workflowDelay}ms` }}>*/}
{alternativeView === true ?
<WorkflowPaperNew key={index} data={data} />
:
<WorkflowPaper key={index} data={data} />
}
</Grid>
</Zoom>
)
})}
</Grid>
)
})}
</Grid>
</div>
)
}
@@ -331,11 +345,11 @@ const AppGrid = props => {
fullWidth={true}
placeholder="What apps do you want to see?"
type=""
id="standard-required"
id="standard-required"
margin="normal"
variant="outlined"
autoComplete="off"
onChange={e => setMessage(e.target.value)}
onChange={e => setMessage(e.target.value)}
/>
</div>
<Button
@@ -353,15 +367,16 @@ const AppGrid = props => {
</div>
: null
}
<span style={{position: "absolute", display: "flex", textAlign: "right", float: "right", right: 0, bottom: 120, }}>
<Typography variant="body2" color="textSecondary" style={{}}>
Search by
</Typography>
<a rel="noopener noreferrer" href="https://www.algolia.com/" target="_blank" style={{textDecoration: "none", color: "white"}}>
<img src={"/images/logo-algolia-nebula-blue-full.svg"} alt="Algolia logo" style={{height: 17, marginLeft: 5, marginTop: 3,}} />
</a>
</span>
{onlyResults === true ? null :
<span style={{position: "absolute", display: "flex", textAlign: "right", float: "right", right: 0, bottom: 120, }}>
<Typography variant="body2" color="textSecondary" style={{}}>
Search by
</Typography>
<a rel="noopener noreferrer" href="https://www.algolia.com/" target="_blank" style={{textDecoration: "none", color: "white"}}>
<img src={"/images/logo-algolia-nebula-blue-full.svg"} alt="Algolia logo" style={{height: 17, marginLeft: 5, marginTop: 3,}} />
</a>
</span>
}
</div>
)
}
-291
View File
@@ -1,291 +0,0 @@
import React, { useState, useEffect, useLayoutEffect } from "react";
import theme from '../theme';
import {
Chip,
Typography,
Paper,
Avatar,
Grid,
Tooltip,
} from "@material-ui/core";
import {
AvatarGroup,
} from "@mui/material"
import {
Restore as RestoreIcon,
Edit as EditIcon,
BubbleChart as BubbleChartIcon,
MoreVert as MoreVertIcon,
} from '@material-ui/icons';
import { useNavigate, Link, useParams } from "react-router-dom";
const workflowActionStyle = {
display: "flex",
width: 160,
height: 44,
justifyContent: "space-between",
}
const paperAppStyle = {
minHeight: 130,
maxHeight: 130,
overflow: "hidden",
width: "100%",
color: "white",
backgroundColor: theme.palette.surfaceColor,
padding: "12px 12px 0px 15px",
borderRadius: 5,
display: "flex",
boxSizing: "border-box",
position: "relative",
}
const chipStyle = {
backgroundColor: "#3d3f43",
marginRight: 5,
paddingLeft: 5,
paddingRight: 5,
height: 28,
cursor: "pointer",
borderColor: "#3d3f43",
color: "white",
}
const WorkflowPaper = (props) => {
const { data } = props;
let navigate = useNavigate();
const [open, setOpen] = React.useState(false);
const [anchorEl, setAnchorEl] = React.useState(null);
const appGroup = data.action_references === undefined || data.action_references === null ? [] : data.action_references
//console.log("Workflow: ", data)
var boxColor = "#86c142";
var parsedName = data.name;
if (
parsedName !== undefined &&
parsedName !== null &&
parsedName.length > 20
) {
parsedName = parsedName.slice(0, 21) + "..";
}
const imageStyle = {
width: 24,
height: 24,
marginRight: 10,
border: "1px solid rgba(255,255,255,0.3)",
}
var image = data.creator_info !== undefined && data.creator_info !== null && data.creator_info.image !== undefined && data.creator_info.image !== null && data.creator_info.image.length > 0 ? <Avatar alt={data.creator} src={data.creator_info.image} style={imageStyle}/> : <Avatar alt={"shuffle_image"} src={theme.palette.defaultImage} style={imageStyle}/>
const creatorname = data.creator_info !== undefined && data.creator_info !== null && data.creator_info.username !== undefined && data.creator_info.username !== null && data.creator_info.username.length > 0 ? data.creator_info.username : ""
var orgName = "";
var orgId = "";
if ((data.objectID === undefined || data.objectID === null) && data.id !== undefined && data.id !== null) {
data.objectID = data.id
}
//console.log("IMG: ", data)
var parsedUrl = `/workflows/${data.objectID}`
if (data.__queryID !== undefined && data.__queryID !== null) {
parsedUrl += `?queryID=${data.__queryID}`
}
return (
<div style={{width: "100%", position: "relative",}}>
<Paper square style={paperAppStyle}>
<div
style={{
position: "absolute",
bottom: 1,
left: 1,
height: 12,
width: 12,
backgroundColor: boxColor,
borderRadius: "0 100px 0 0",
}}
/>
<Grid
item
style={{ display: "flex", flexDirection: "column", width: "100%" }}
>
<Grid item style={{ display: "flex", maxHeight: 34 }}>
<Tooltip title={`${creatorname}`} placement="bottom">
<div
style={{ cursor: data.creator_info !== undefined ? "pointer" : "inherit" }}
onClick={() => {
if (data.creator_info !== undefined) {
navigate("/creators/"+data.creator_info.username)
}
}}
>
{image}
</div>
</Tooltip>
<Tooltip title={`Edit ${data.name}`} placement="bottom">
<Typography
variant="body1"
style={{
marginBottom: 0,
paddingBottom: 0,
maxHeight: 30,
flex: 10,
}}
>
<Link
to={parsedUrl}
style={{ textDecoration: "none", color: "inherit" }}
>
{parsedName}
</Link>
</Typography>
</Tooltip>
</Grid>
<Grid item style={workflowActionStyle}>
{appGroup.length > 0 ?
<div style={{display: "flex", marginTop: 8, }}>
<AvatarGroup max={4} style={{marginLeft: 5, maxHeight: 24,}}>
{appGroup.map((app, index) => {
return (
<div
key={index}
style={{
height: 24,
width: 24,
filter: "brightness(0.6)",
cursor: "pointer",
}}
onClick={() => {
navigate("/apps/"+app.id)
}}
>
<Tooltip color="primary" title={app.name} placement="bottom">
<Avatar alt={app.name} src={app.image_url} style={{width: 24, height: 24}}/>
</Tooltip>
</div>
)
})}
</AvatarGroup>
</div>
:
<Tooltip color="primary" title="Action amount" placement="bottom">
<span style={{ color: "#979797", display: "flex" }}>
<BubbleChartIcon
style={{ marginTop: "auto", marginBottom: "auto" }}
/>
<Typography
style={{
marginLeft: 5,
marginTop: "auto",
marginBottom: "auto",
}}
>
{data.actions === undefined || data.actions === null ? 1 : data.actions.length}
</Typography>
</span>
</Tooltip>
}
<Tooltip
color="primary"
title="Trigger amount"
placement="bottom"
>
<span
style={{ marginLeft: 15, color: "#979797", display: "flex" }}
>
<RestoreIcon
style={{
color: "#979797",
marginTop: "auto",
marginBottom: "auto",
}}
/>
<Typography
style={{
marginLeft: 5,
marginTop: "auto",
marginBottom: "auto",
}}
>
{data.triggers === undefined || data.triggers === null ? 1 : data.triggers.length}
</Typography>
</span>
</Tooltip>
<Tooltip color="primary" title="Subflows used" placement="bottom">
<span
style={{
marginLeft: 15,
display: "flex",
color: "#979797",
cursor: "pointer",
}}
onClick={() => {
}}
>
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
style={{
color: "#979797",
marginTop: "auto",
marginBottom: "auto",
}}
>
<path
d="M0 0H15V15H0V0ZM16 16H18V18H16V16ZM16 13H18V15H16V13ZM16 10H18V12H16V10ZM16 7H18V9H16V7ZM16 4H18V6H16V4ZM13 16H15V18H13V16ZM10 16H12V18H10V16ZM7 16H9V18H7V16ZM4 16H6V18H4V16Z"
fill="#979797"
/>
</svg>
<Typography
style={{
marginLeft: 5,
marginTop: "auto",
marginBottom: "auto",
}}
>
{0}
</Typography>
</span>
</Tooltip>
</Grid>
<Grid
item
style={{
justifyContent: "left",
overflow: "hidden",
marginTop: 5,
}}
>
{data.tags !== undefined && data.tags !== null
? data.tags.map((tag, index) => {
if (index >= 3) {
return null;
}
return (
<Chip
key={index}
style={chipStyle}
label={tag}
variant="outlined"
color="primary"
/>
);
})
: null}
</Grid>
</Grid>
</Paper>
</div>
)
}
export default WorkflowPaper
+1 -1
View File
@@ -53,7 +53,7 @@ const WorkflowSearch = props => {
.then(response => {
if (response.success === true) {
setFormMessage(response.reason)
//alert.info("Thanks for submitting!")
//toast("Thanks for submitting!")
} else {
setFormMessage(errorMessage)
}
+3 -1
View File
@@ -4,6 +4,7 @@ import { createTheme, adaptV4Theme } from "@mui/material/styles";
//const theme = createTheme({
const theme = createTheme(adaptV4Theme({
palette: {
theme: "dark",
main: "#F86743",
primary: {
main: "#F86743",
@@ -17,8 +18,8 @@ const theme = createTheme(adaptV4Theme({
secondary: "rgba(255,255,255,0.7)",
},
type: "dark",
surfaceColor: "#27292d",
inputColor: "#383B40",
surfaceColor: "#27292d",
platformColor: "#1c1c1d",
backgroundColor: "#1a1a1a",
borderRadius: 5,
@@ -34,6 +35,7 @@ const theme = createTheme(adaptV4Theme({
borderRadius: 5,
},
innerTextfieldStyle: {
// Removed since upgrading to mui 18
//color: "white",
//minHeight: 50,
//marginLeft: "5px",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+52 -43
View File
@@ -45,8 +45,9 @@ import {
import { v4 as uuidv4 } from "uuid";
import { Link, useParams } from "react-router-dom";
import YAML from "yaml";
import ChipInput from "material-ui-chip-input";
import { useAlert } from "react-alert";
import { MuiChipsInput } from "mui-chips-input";
//import { useAlert
import { ToastContainer, toast } from "react-toastify"
import words from "shellwords";
import AvatarEditor from "react-avatar-editor";
@@ -350,7 +351,7 @@ const getJsonObject = (properties) => {
const AppCreator = (defaultprops) => {
const { globalUrl, isLoaded } = defaultprops;
const classes = useStyles();
const alert = useAlert();
//const alert = useAlert();
const params = useParams();
var props = JSON.parse(JSON.stringify(defaultprops))
@@ -451,7 +452,7 @@ const AppCreator = (defaultprops) => {
})
.then((responseJson) => {
if (responseJson.success === false) {
alert.error("Failed to get the app");
toast("Failed to get the app");
setIsAppLoaded(true);
window.location.pathname = "/search";
} else {
@@ -459,7 +460,7 @@ const AppCreator = (defaultprops) => {
}
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
@@ -494,14 +495,14 @@ const AppCreator = (defaultprops) => {
.then((responseJson) => {
setIsAppLoaded(true);
if (!responseJson.success) {
alert.error("Failed to get app config. Do you have access?");
toast("Failed to get app config. Do you have access?");
} else {
parseIncomingOpenapiData(responseJson);
}
})
.catch((error) => {
console.log("Error: ", error.toString());
alert.error(error.toString());
toast(error.toString());
});
};
@@ -568,7 +569,7 @@ const AppCreator = (defaultprops) => {
}
if (data.openapi === null) {
alert.info("Failed to load OpenAPI for app. Please contact support if this persists.")
toast("Failed to load OpenAPI for app. Please contact support if this persists.")
setIsAppLoaded(true);
return
}
@@ -600,7 +601,7 @@ const AppCreator = (defaultprops) => {
}
if (!jsonvalid) {
alert.info("OpenAPI data is invalid.");
toast("OpenAPI data is invalid.");
return;
}
@@ -726,7 +727,7 @@ const AppCreator = (defaultprops) => {
for (let [method, methodvalue] of Object.entries(pathvalue)) {
if (methodvalue === null) {
alert.info("Skipped method (null)" + method);
toast("Skipped method (null)" + method);
continue;
}
@@ -734,7 +735,7 @@ const AppCreator = (defaultprops) => {
// Typical YAML issue
if (method !== "parameters") {
console.log("Invalid method: ", method, "data: ", methodvalue);
//alert.info("Skipped method (not allowed): " + method);
//toast("Skipped method (not allowed): " + method);
}
continue;
}
@@ -1596,7 +1597,7 @@ const AppCreator = (defaultprops) => {
setParameterLocation(value.in);
if (!apikeySelection.includes(value.in)) {
console.log("APIKEY SELECT: ", apikeySelection);
alert.error("Might be error in setting up API key authentication");
toast("Might be error in setting up API key authentication");
}
console.log("PARAM NAME: ", value.name);
@@ -1637,7 +1638,7 @@ const AppCreator = (defaultprops) => {
optionset = true
} else if (value.type === "oauth2" || key === "Oauth2" || key === "Oauth2c" || (key !== undefined && key !== null && key.toLowerCase().includes("oauth2"))) {
//alert.info("Can't handle Oauth2 auth yet.")
//toast("Can't handle Oauth2 auth yet.")
setAuthenticationOption("Oauth2");
setAuthenticationRequired(true);
optionset = true
@@ -1686,7 +1687,7 @@ const AppCreator = (defaultprops) => {
const scopekeysplit = scopekey.split("/");
if (scopekeysplit.length < 5) {
console.log("Skipping scope: ", scopekey);
alert.info("Skipping scope: " + scopekey);
toast("Skipping scope: " + scopekey);
continue;
}
@@ -1707,7 +1708,7 @@ const AppCreator = (defaultprops) => {
);
}
} else {
alert.error("Couldn't handle AUTH type: ", key);
toast("Couldn't handle AUTH type: ", key);
//newauth.push({
// "name": key,
// "type": value.in,
@@ -1716,7 +1717,7 @@ const AppCreator = (defaultprops) => {
}
}
} catch (e) {
alert.error("Failed to handle auth")
toast("Failed to handle auth")
console.log("Error: ", e)
}
@@ -1790,7 +1791,7 @@ const AppCreator = (defaultprops) => {
//const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"
if (newActions.length > 1000 && isCloud) {
alert.error("Cut down actions from " + newActions.length + " to 999 because of limit");
toast("Cut down actions from " + newActions.length + " to 999 because of limit");
newActions = newActions.slice(0, 999);
}
@@ -1812,7 +1813,7 @@ const AppCreator = (defaultprops) => {
// Saving the app that's been configured.
// Save SAVE app
const submitApp = () => {
alert.info("Uploading and building app " + name);
toast("Uploading and building app " + name);
setAppBuilding(true);
setErrorCode("");
@@ -1881,7 +1882,7 @@ const AppCreator = (defaultprops) => {
for (let actionkey in actions) {
var item = JSON.parse(JSON.stringify(actions[actionkey]))
if (item.errors.length > 0) {
alert.error("Saving with error in action " + item.name);
toast("Saving with error in action " + item.name);
}
if (item.name === undefined && item.description !== undefined) {
@@ -2107,7 +2108,7 @@ const AppCreator = (defaultprops) => {
// Bad code as it doesn't allow for "anything".
if (skipped) {
alert.info(
toast(
"Bad configuration of " +
item.name +
". Skipping because queries are invalid."
@@ -2358,7 +2359,7 @@ const AppCreator = (defaultprops) => {
if (authenticationOption === "API key") {
if (parameterName.length === 0) {
alert.error("A field name for the APIkey must be defined");
toast("A field name for the APIkey must be defined");
setAppBuilding(false);
return;
}
@@ -2424,7 +2425,7 @@ const AppCreator = (defaultprops) => {
const curauth = extraAuth[authkey];
if (curauth.name.toLowerCase() == "url") {
alert.error("Can't add extra auth with Name URL");
toast("Can't add extra auth with Name URL");
setAppBuilding(false);
return;
}
@@ -2459,10 +2460,10 @@ const AppCreator = (defaultprops) => {
if (!responseJson.success) {
if (responseJson.reason !== undefined) {
setErrorCode(responseJson.reason);
alert.error("Failed to verify: " + responseJson.reason);
toast("Failed to verify: " + responseJson.reason);
}
} else {
alert.success("Successfully uploaded openapi");
toast("Successfully uploaded openapi");
if (window.location.pathname.includes("/new")) {
if (responseJson.id !== undefined && responseJson.id !== null) {
window.location = `/apps/edit/${responseJson.id}`;
@@ -2473,7 +2474,7 @@ const AppCreator = (defaultprops) => {
.catch((error) => {
setAppBuilding(false);
setErrorCode(error.toString());
alert.error(error.toString());
toast(error.toString());
});
};
@@ -2805,7 +2806,7 @@ const AppCreator = (defaultprops) => {
!tmpstring.startsWith("http") &&
!tmpstring.startsWith("ftp")
) {
alert.error("Auth URL must start with http(s)://");
toast("Auth URL must start with http(s)://");
}
if (tmpstring.includes("?")) {
@@ -2854,7 +2855,7 @@ const AppCreator = (defaultprops) => {
!tmpstring.startsWith("http") &&
!tmpstring.startsWith("ftp")
) {
alert.error("Token URL must start with http(s)://");
toast("Token URL must start with http(s)://");
}
if (tmpstring.includes("?")) {
@@ -2900,7 +2901,7 @@ const AppCreator = (defaultprops) => {
!tmpstring.startsWith("http") &&
!tmpstring.startsWith("ftp")
) {
alert.error("Refresh URL must start with http(s)://");
toast("Refresh URL must start with http(s)://");
}
if (tmpstring.includes("?")) {
@@ -2925,7 +2926,7 @@ const AppCreator = (defaultprops) => {
>
Scopes for Oauth2
</Typography>
<ChipInput
<MuiChipsInput
style={{border: "2px solid #f86a3e", borderRadius: theme.palette.borderRadius,}}
required
InputProps={{
@@ -2939,6 +2940,10 @@ const AppCreator = (defaultprops) => {
color="primary"
fullWidth
value={oauth2Scopes}
onChange={(chips) => {
setOauth2Scopes(chips)
setUpdate(Math.random())
}}
onAdd={(chip) => {
oauth2Scopes.push(chip);
console.log(oauth2Scopes);
@@ -3787,7 +3792,7 @@ const AppCreator = (defaultprops) => {
value = keysplit[1].trim()
} else {
alert.error("Removed key: ", key)
toast("Removed key: ", key)
continue
}
}
@@ -4396,7 +4401,7 @@ const AppCreator = (defaultprops) => {
})}
</Select>
<h4>Tags</h4>
<ChipInput
<MuiChipsInput
style={{ marginTop: 10 }}
InputProps={{
style: {
@@ -4407,6 +4412,10 @@ const AppCreator = (defaultprops) => {
color="primary"
fullWidth
value={newWorkflowTags}
onChange={(chips) => {
setNewWorkflowTags(chips)
setUpdate("added "+chips)
}}
onAdd={(chip) => {
newWorkflowTags.push(chip);
setNewWorkflowTags(newWorkflowTags);
@@ -4618,11 +4627,11 @@ const AppCreator = (defaultprops) => {
setSelectedAction(selectedAction);
}
//alert.error("Failed getting authentications")
//toast("Failed getting authentications")
}
})
.catch((error) => {
alert.error("Auth loading error: " + error.toString());
toast("Auth loading error: " + error.toString());
});
};
@@ -4678,17 +4687,17 @@ const AppCreator = (defaultprops) => {
})
.then((responseJson) => {
if (!responseJson.success) {
alert.error("Failed to set app auth: " + responseJson.reason);
toast("Failed to set app auth: " + responseJson.reason);
} else {
getAppAuthentication(true);
setAuthenticationModalOpen(false);
// Needs a refresh with the new authentication..
//alert.success("Successfully saved new app auth")
//toast("Successfully saved new app auth")
}
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
@@ -4739,7 +4748,7 @@ const AppCreator = (defaultprops) => {
console.log("NEW AUTH: ", authenticationOption);
if (authenticationOption.label.length === 0) {
authenticationOption.label = `Auth for ${selectedApp.name}`;
//alert.info("Label can't be empty")
//toast("Label can't be empty")
//return
}
@@ -4749,7 +4758,7 @@ const AppCreator = (defaultprops) => {
selectedApp.authentication.parameters[key].name
].length === 0
) {
alert.info(
toast(
"Field " +
selectedApp.authentication.parameters[key].name +
" can't be empty"
@@ -5157,7 +5166,7 @@ const AppCreator = (defaultprops) => {
setFileBase64(canvasUrl);
}
} catch (e) {
alert.error("Failed to parse canvasurl!");
toast("Failed to parse canvasurl!");
}
};
@@ -5245,7 +5254,7 @@ const AppCreator = (defaultprops) => {
setOpenImageModal(false);
setDisableImageUpload(true);
} catch (e) {
alert.error("Failed to set image. Replace it if this persists.");
toast("Failed to set image. Replace it if this persists.");
}
}
};
@@ -5460,7 +5469,7 @@ const AppCreator = (defaultprops) => {
const invalid = ["#", ":", "."];
for (var key in invalid) {
if (e.target.value.includes(invalid[key])) {
alert.error("Can't use " + invalid[key] + " in name");
toast("Can't use " + invalid[key] + " in name");
setName(e.target.value.replaceAll(".", "").replaceAll("#", "").replaceAll(":", "").replaceAll(",", ""))
return;
@@ -5468,7 +5477,7 @@ const AppCreator = (defaultprops) => {
}
if (e.target.value.length > 29) {
alert.error("Choose a shorter name (max 29).");
toast("Choose a shorter name (max 29).");
setName(e.target.value.slice(0,28))
return;
}
@@ -5580,7 +5589,7 @@ const AppCreator = (defaultprops) => {
!tmpstring.startsWith("http") &&
!tmpstring.startsWith("ftp")
) {
alert.error("URL must start with http(s)://");
toast("URL must start with http(s)://");
}
if (tmpstring.includes("?")) {
-468
View File
@@ -1,468 +0,0 @@
import { Grid, Divider, List, ListItem, ListItemText } from "@mui/material";
import { experimentalStyled as styled } from '@mui/material/styles';
import Typography from "@material-ui/core/Typography";
import Paper from "@material-ui/core/Paper";
import Button from '@mui/material/Button';
import Box from '@material-ui/core/Box';
import React, { useState, useEffect } from "react";
import algoliasearch from "algoliasearch";
//import algoliarecommend from "algoliarecommend";
const searchClient = algoliasearch(
"JNSS5CFDZZ",
"db08e40265e2941b9a7d8f644b6e5240"
);
// https://www.algolia.com/doc/api-client/getting-started/install/
/*const algoliarecommend = require('@algolia/recommend');
const client = algoliarecommend(
"NSS5CFDZZ",
"db08e40265e2941b9a7d8f644b6e5240"
);*/
const Item = styled(Paper)(({ theme }) => ({
padding: theme.spacing(2),
border: "0.0625rem solid #b2b2b2",
borderRadius: "1.5625rem",
boxSizing: "content-box",
backgroundColor: "transparent",
width: "200px",
textAlign: 'center',
color: theme.palette.text.secondary,
marginTop: "20px",
marginBottom: "20px",
color: "textPrimary"
}));
const AppExplorer = (props) => {
const [algoliaResult, setAlgoliaResult] = useState("");
const runAlgoliaAppSearch = (query) => {
const index = searchClient.initIndex("appsearch");
index
.search(`${query}`)
.then(({ hits }) => {
setAlgoliaResult(hits);
})
.catch((err) => {
console.log(err);
});
};
useEffect(() => {
runAlgoliaAppSearch("wazuh")
}, [])
const brandApp = () => {
const index = searchClient.initIndex("appsearch");
const replicaIndex = searchClient.initIndex('appsearch');
replicaIndex.setSettings({
customRanking: [
"asc(time_edited)"
]
})
.then(({ hits }) => {
console.log(hits);
})
.catch((err) => {
console.log(err);
});
};
useEffect(() => {
brandApp()
}, [])
/*const trandingApp = () => {
const index = client.getTrendingGlobalItems([
{
indexName: "appsearch",
threshold: 60
},
])
.then(({ results }) => {
console.log(results);
})
.catch(err => {
console.log(err);
});
};
useEffect(() => {
trandingApp();
}, [])
*/
const SideBar = {
minWidth: 250,
borderRight: "1px solid rgba(255,255,255,0.3)",
left: 0,
position: "sticky",
minHeight: "90vh",
maxHeight: "90vh",
overflowX: "hidden",
overflowY: "auto",
zIndex: 1000,
color: "white"
};
const contentbar = {
padding: "40px",
};
const boxdata = {
paddingLeft: "30px"
}
const link = {
textDecoration: "none"
}
const catItems = (
<div style={SideBar}>
<List>
<ListItem >
<ListItemText>
<span><Typography variant="primary">Categories</Typography></span>
</ListItemText>
</ListItem>
<span></span>
<Divider />
<ListItem>
<ListItemText>
<Button variant="primary">
ASSETS
</Button>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<Button variant="primary">
CASES
</Button>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<Button variant="primary">
COMMS
</Button>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<Button variant="primary">
EDR & AV
</Button>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<Button variant="primary">
IAM
</Button>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<Button variant="primary">
INTEL
</Button>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<Button variant="primary">
NETWORK
</Button>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<Button variant="primary">
SIEM
</Button>
</ListItemText>
</ListItem>
</List>
</div>
)
return (
<div>
<div style={{ display: "flex" }}>
{catItems}
<div style={contentbar}>
<Grid>
<Grid item xl={8} style={{ "border": "20px" }}>
<Typography type="title" variant="h6">
Getting Started
</Typography>
<div style={{
paddingLeft: "50px",
}}>
</div>
</Grid>
</Grid>
<Box sx={{ flexGrow: 1 }}>
<Grid
container
spacing={{ xs: 1, md: 4 }}
columns={{ xs: 4, sm: 8, md: 12 }}
>
{Array.from(Array(algoliaResult.length)).map((_, index) => (
<Grid item xs={2} sm={4} md={4} key={index}>
<a href={algoliaResult[0]["objectID"]} style={link}>
<Item>
<div class="row">
<div class="column" style={{ float: "left" }}>
<img src={algoliaResult[0]["image_url"]} alt="shuffle" width="50px" />
</div>
<div class="column " style={boxdata}>
<div style={boxdata}>
<Typography align="left" variant="body1">
{algoliaResult[0]["name"]}
</Typography>
</div>
<div style={boxdata}>
<Typography align="left" variant="body2">
{algoliaResult[0]["description"].substring(0, 20)}
</Typography>
</div>
</div>
</div>
</Item>
</a>
</Grid>
))}
</Grid>
</Box>
<Grid item xl={8} style={{ "border": "20px" }}>
<Typography type="title" variant="h6">
Most Popular
</Typography>
<div style={{
paddingLeft: "50px",
}}>
</div>
</Grid>
<Box sx={{ flexGrow: 1 }}>
<Grid
container
spacing={{ xs: 1, md: 3 }}
columns={{ xs: 4, sm: 8, md: 12 }}
>
{Array.from(Array(3)).map((_, index) => (
<Grid item xs={2} sm={4} md={4} key={index}>
<a href="#" style={link}>
<Item>
<div class="row">
<div class="column" style={{ float: "left" }}>
<img src="/images/testing.png" alt="shuffle" width="50px" />
</div>
<div class="column " style={boxdata}>
<div style={boxdata}>
<Typography align="left" variant="body1">
App Name
</Typography>
</div>
<div style={boxdata}>
<Typography align="left" variant="body2">
Description
</Typography>
</div>
</div>
</div>
</Item>
</a>
</Grid>
))}
</Grid>
</Box>
<Grid item xl={8} style={{ "border": "20px" }}>
<Typography type="title" variant="h6">
Brand New
</Typography>
<div style={{
paddingLeft: "50px",
}}>
</div>
</Grid>
<Box sx={{ flexGrow: 1 }}>
<Grid
container
spacing={{ xs: 1, md: 3 }}
columns={{ xs: 4, sm: 8, md: 12 }}
>
{Array.from(Array(3)).map((_, index) => (
<Grid item xs={2} sm={4} md={4} key={index}>
<a href="#" style={link}>
<Item>
<div class="row">
<div class="column" style={{ float: "left" }}>
<img src="/images/testing.png" alt="shuffle" width="50px" />
</div>
<div class="column " style={boxdata}>
<div style={boxdata}>
<Typography align="left" variant="body1">
App Name
</Typography>
</div>
<div style={boxdata}>
<Typography align="left" variant="body2">
Description
</Typography>
</div>
</div>
</div>
</Item>
</a>
</Grid>
))}
</Grid>
</Box>
<Box sx={{ flexGrow: 1 }}>
<Grid
container
spacing={{ xs: 1, md: 3 }}
columns={{ xs: 4, sm: 8, md: 12 }}
>
{Array.from(Array(3)).map((_, index) => (
<Grid item xs={2} sm={4} md={4} key={index}>
<div className="row" >
<div className="column" style={{ float: "left", width: "33.33%", marginTop: "20px", marginBottom: "20px", }}>
<img src="/images/shuffle_logo.png" alt="shuffle" width="200px" />
</div>
</div>
</Grid>
))}
</Grid>
</Box>
<Grid item xl={8} style={{ "border": "20px" }}>
<Typography type="title" variant="h6">
Hybrid work
</Typography>
<div style={{
paddingLeft: "50px",
}}>
</div>
</Grid>
<Box sx={{ flexGrow: 1 }}>
<Grid
container
spacing={{ xs: 1, md: 3 }}
columns={{ xs: 4, sm: 8, md: 12 }}
>
{Array.from(Array(3)).map((_, index) => (
<Grid item xs={2} sm={4} md={4} key={index}>
<a href="#" style={link}>
<Item>
<div class="row">
<div class="column" style={{ float: "left" }}>
<img src="/images/testing.png" alt="shuffle" width="50px" />
</div>
<div class="column " style={boxdata}>
<div style={boxdata}>
<Typography align="left" variant="body1">
App Name
</Typography>
</div>
<div style={boxdata}>
<Typography align="left" variant="body2">
Description
</Typography>
</div>
</div>
</div>
</Item>
</a>
</Grid>
))}
</Grid>
</Box>
<div className="row" style={{ display: "flex" }}>
<div className="col" style={{ width: "40%", marginTop: "50px" }}>
<Typography variant="h6">Don't see it? Build it!</Typography>
<Typography variant="body2">Use our APIs to create an app that makes your working life better.And maybe even share it with the world.</Typography>
<a href="#" target="_blank" rel="nonref"
style={{
background: "#FF4500",
borderRadius: "3.125rem",
color: "#fff",
display: "block",
fontSize: ".9375rem",
fontWeight: "500",
height: "1rem",
letterSpacing: "-.02em",
lineHeight: ".875rem",
marginTop: "1.5rem",
padding: "1.3125rem 1.375rem",
textAlign: "center",
textDecoration: "none",
width: "8.5rem"
}}><span>visit developer portal</span></a>
</div>
<div className="col" style={{ float: "right" }}>
<div className="row" style={{ float: "left", marginLeft: "90px" }}>
<div className="column" style={{ float: "left", margin: "60px 10px 20px 30px" }}>
<img src="/images/demo1.png" alt="shuffle" width="90px" />
</div>
<div className="column" style={{ float: "left", margin: "60px 10px 20px 30px" }}>
<img src="/images/demo1.png" alt="shuffle" width="90px" />
</div>
<div className="column" style={{ float: "left", margin: "60px 10px 20px 30px" }}>
<img src="/images/demo1.png" alt="shuffle" width="90px" />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
export default AppExplorer;
-789
View File
@@ -1,789 +0,0 @@
import React from "react";
import { Grid, Container, Divider, CardMedia, List, ListItem, ListItemText } from "@mui/material";
import { makeStyles } from "@material-ui/core/styles";
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import Table from "@material-ui/core/Table";
import TableBody from "@material-ui/core/TableBody";
import TableCell from "@material-ui/core/TableCell";
import TableContainer from "@material-ui/core/TableContainer";
import TableHead from "@material-ui/core/TableHead";
import TableRow from "@material-ui/core/TableRow";
import Paper from "@material-ui/core/Paper";
import { LineChart, LineSeries, BarChart } from "reaviz";
import { Gridline, GridStripe } from "reaviz";
import { GridlineSeries } from "reaviz";
import InputLabel from '@material-ui/core/InputLabel';
import FormControl from '@material-ui/core/FormControl';
import Select from '@material-ui/core/Select';
import { styled, alpha } from '@mui/material/styles';
import AppBar from '@mui/material/AppBar';
import Box from '@mui/material/Box';
import Toolbar from '@mui/material/Toolbar';
import IconButton from '@mui/material/IconButton';
import Typography from '@mui/material/Typography';
import InputBase from '@mui/material/InputBase';
import Badge from '@mui/material/Badge';
import MenuItem from '@mui/material/MenuItem';
import Menu from '@mui/material/Menu';
import MenuIcon from '@mui/icons-material/Menu';
import SearchIcon from '@mui/icons-material/Search';
import AccountCircle from '@mui/icons-material/AccountCircle';
import MailIcon from '@mui/icons-material/Mail';
import NotificationsIcon from '@mui/icons-material/Notifications';
import MoreIcon from '@mui/icons-material/MoreVert';
import SearchField from "../components/Searchfield";
import { SpaRounded } from "@material-ui/icons";
import { isMobile } from "react-device-detect"
const data = [
{
key: new Date("11/29/2019"),
data: 10,
},
{
key: new Date("11/30/2019"),
data: 14,
},
{
key: new Date("12/01/2019"),
data: 5,
},
{
key: new Date("12/02/2019"),
data: 18,
},
];
const useStyles1 = makeStyles((theme) => ({
formControl: {
margin: theme.spacing(1),
minWidth: 120,
},
selectEmpty: {
marginTop: theme.spacing(2),
},
}));
const useStyles = makeStyles({
table: {
minWidth: 650,
},
root: {
minWidth: 275,
},
bullet: {
display: "inline-block",
margin: "0 2px",
transform: "scale(0.8)",
},
title: {
fontSize: 14,
},
pos: {
marginBottom: 12,
},
});
function createData(name, calories, fat, carbs, protein) {
return { name, calories, fat, carbs, protein };
}
const rows = [
createData("Frozen yoghurt", 159, 6.0, 24, 4.0),
createData("Ice cream sandwich", 237, 9.0, 37, 4.3),
createData("Eclair", 262, 16.0, 24, 6.0),
createData("Cupcake", 305, 3.7, 67, 4.3),
createData("Gingerbread", 356, 16.0, 49, 3.9),
];
const Search = styled('div')(({ theme }) => ({
position: 'relative',
borderRadius: theme.shape.borderRadius,
backgroundColor: alpha(theme.palette.common.white, 0.15),
'&:hover': {
backgroundColor: alpha(theme.palette.common.white, 0.25),
},
marginRight: theme.spacing(2),
marginLeft: 0,
width: '100%',
[theme.breakpoints.up('sm')]: {
marginLeft: theme.spacing(3),
width: 'auto',
},
}));
const SearchIconWrapper = styled('div')(({ theme }) => ({
padding: theme.spacing(0, 2),
height: '100%',
position: 'absolute',
pointerEvents: 'none',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}));
const StyledInputBase = styled(InputBase)(({ theme }) => ({
color: 'inherit',
'& .MuiInputBase-input': {
padding: theme.spacing(1, 1, 1, 0),
// vertical padding + font size from searchIcon
paddingLeft: `calc(1em + ${theme.spacing(4)})`,
transition: theme.transitions.create('width'),
width: '100%',
[theme.breakpoints.up('md')]: {
width: '20ch',
},
},
}));
function PrimarySearchAppBar() {
const [anchorEl, setAnchorEl] = React.useState(null);
const [mobileMoreAnchorEl, setMobileMoreAnchorEl] = React.useState(null);
const isMenuOpen = Boolean(anchorEl);
const isMobileMenuOpen = Boolean(mobileMoreAnchorEl);
const handleProfileMenuOpen = (event) => {
setAnchorEl(event.currentTarget);
};
const handleMobileMenuClose = () => {
setMobileMoreAnchorEl(null);
};
const handleMenuClose = () => {
setAnchorEl(null);
handleMobileMenuClose();
};
const handleMobileMenuOpen = (event) => {
setMobileMoreAnchorEl(event.currentTarget);
};
const menuId = 'primary-search-account-menu';
const renderMenu = (
<Menu
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
id={menuId}
keepMounted
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
open={isMenuOpen}
onClose={handleMenuClose}
>
<MenuItem onClick={handleMenuClose}>Profile</MenuItem>
<MenuItem onClick={handleMenuClose}>My account</MenuItem>
</Menu>
);
const mobileMenuId = 'primary-search-account-menu-mobile';
const renderMobileMenu = (
<Menu
anchorEl={mobileMoreAnchorEl}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
id={mobileMenuId}
keepMounted
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
open={isMobileMenuOpen}
onClose={handleMobileMenuClose}
>
<MenuItem>
<IconButton size="large" aria-label="show 4 new mails" color="inherit">
<Badge badgeContent={4} color="error">
<MailIcon />
</Badge>
</IconButton>
<p>Messages</p>
</MenuItem>
<MenuItem>
<IconButton
size="large"
aria-label="show 17 new notifications"
color="inherit"
>
<Badge badgeContent={17} color="error">
<NotificationsIcon />
</Badge>
</IconButton>
<p>Notifications</p>
</MenuItem>
<MenuItem onClick={handleProfileMenuOpen}>
<IconButton
size="large"
aria-label="account of current user"
aria-controls="primary-search-account-menu"
aria-haspopup="true"
color="inherit"
>
<AccountCircle />
</IconButton>
<p>Profile</p>
</MenuItem>
</Menu>
);
return (
<Box sx={{ flexGrow: 1 }}>
<AppBar position="fixed" style={{ backgroundColor: "black", boxShadow: "unset" }}>
<Toolbar>
<img src="/images/Shuffle_logo.png" style={{ height: "3rem", width: "3rem" }} alt="shuffle img" />
<SearchField />
{/* <Box sx={{ flexGrow: 1 }} /> */}
</Toolbar>
</AppBar>
{renderMobileMenu}
{renderMenu}
</Box>
);
}
const AppHub = () => {
const classes = useStyles();
const classes1 = useStyles1();
const [usecases, setUsecases] = React.useState([
{
"name": "1. Collect",
"color": "#c51152",
"list": [
{
"name": "Email management",
"priority": 100,
"type": "communication",
"items": {
"name": "Release a quarantined message",
"items": {}
},
"matches": []
},
{
"name": "EDR to ticket",
"priority": 100,
"type": "edr",
"items": {
"name": "Get host information",
"items": {}
},
"matches": []
},
{
"name": "SIEM to ticket",
"priority": 100,
"type": "siem",
"description": "Ensure tickets are forwarded to the correct destination. Alternatively add enrichment on it's way there.",
"video": "https://www.youtube.com/watch?v=FBISHA7V15c&t=197s&ab_channel=OpenSecure",
"blogpost": "https://medium.com/shuffle-automation/introducing-shuffle-an-open-source-soar-platform-part-1-58a529de7d12",
"reference_image": "/images/detectionframework.png",
"items": {},
"matches": []
},
{
"name": "2-way Ticket synchronization",
"priority": 90,
"items": {},
"matches": []
},
{
"name": "ChatOps",
"priority": 70,
"items": {},
"matches": []
},
{
"name": "Threat Intel received",
"priority": 50,
"items": {},
"matches": []
},
{
"name": "Assign tickets",
"priority": 30,
"items": {},
"matches": []
},
{
"name": "Firewall alerts",
"priority": 90,
"items": {
"name": "URL filtering",
"items": {}
},
"matches": []
},
{
"name": "IDS/IPS alerts",
"priority": 90,
"items": {
"name": "Manage policies",
"items": {}
},
"matches": []
},
{
"name": "Deduplicate information",
"priority": 70,
"items": {},
"matches": []
}
],
"matches": []
},
{
"name": "2. Enrich",
"color": "#f4c20d",
"list": [
{
"name": "Internal Enrichment",
"priority": 100,
"items": {
"name": "...",
"items": {}
},
"matches": []
},
{
"name": "External historical Enrichment",
"priority": 90,
"items": {
"name": "...",
"items": {}
},
"matches": []
},
{
"name": "Realtime",
"priority": 50,
"items": {
"name": "Analyze screenshots",
"items": {}
},
"matches": []
}
],
"matches": []
},
{
"name": "3. Detect",
"color": "#3cba54",
"list": [
{
"name": "Search SIEM (Sigma)",
"priority": 90,
"items": {
"name": "Endpoint",
"items": {}
},
"matches": []
},
{
"name": "Search EDR (OSQuery)",
"priority": 90,
"items": {},
"matches": []
},
{
"name": "Search emails (Sublime)",
"priority": 90,
"items": {
"name": "Check headers and IOCs",
"items": {}
},
"matches": []
},
{
"name": "Search IOCs (ioc-finder)",
"priority": 50,
"items": {},
"matches": []
},
{
"name": "Search files (Yara)",
"priority": 50,
"items": {},
"matches": []
},
{
"name": "Memory Analysis (Volatility)",
"priority": 50,
"items": {},
"matches": []
},
{
"name": "IDS & IPS (Snort/Surricata)",
"priority": 50,
"items": {},
"matches": []
},
{
"name": "Validate old tickets",
"priority": 50,
"items": {},
"matches": []
},
{
"name": "Honeypot access",
"priority": 50,
"items": {
"name": "...",
"items": {}
},
"matches": []
}
],
"matches": []
},
{
"name": "4. Respond",
"color": "#4885ed",
"list": [
{
"name": "Eradicate malware",
"priority": 90,
"items": {},
"matches": []
},
{
"name": "Quarantine host(s)",
"priority": 90,
"items": {},
"matches": []
},
{
"name": "Block IPs, URLs, Domains and Hashes",
"priority": 90,
"items": {},
"matches": []
},
{
"name": "Trigger scans",
"priority": 50,
"items": {},
"matches": []
},
{
"name": "Update indicators (FW, EDR, SIEM...)",
"priority": 50,
"items": {},
"matches": []
},
{
"name": "Autoblock activity when threat intel is received",
"priority": 50,
"items": {},
"matches": []
},
{
"name": "Lock/Delete/Reset account",
"priority": 50,
"items": {},
"matches": []
},
{
"name": "Lock vault",
"priority": 50,
"items": {},
"matches": []
},
{
"name": "Increase authentication",
"priority": 50,
"items": {},
"matches": []
},
{
"name": "Get policies from assets",
"priority": 50,
"items": {},
"matches": []
},
{
"name": "Run ansible scripts",
"priority": 50,
"items": {},
"matches": []
}
],
"matches": []
},
{
"name": "5. Verify",
"color": "#7f00ff",
"list": [
{
"name": "Discover vulnerabilities",
"priority": 80,
"items": {},
"matches": []
},
{
"name": "Discover assets",
"priority": 80,
"items": {},
"matches": []
},
{
"name": "Ensure policies are followed",
"priority": 80,
"items": {},
"matches": []
},
{
"name": "Find Inactive users",
"priority": 50,
"items": {},
"matches": []
},
{
"name": "Botnet tracker",
"priority": 50,
"items": {},
"matches": []
},
{
"name": "Ensure access rights match HR systems",
"priority": 50,
"items": {},
"matches": []
},
{
"name": "Ensure onboarding is followed",
"priority": 50,
"items": {},
"matches": []
},
{
"name": "Third party apps in SaaS",
"priority": 50,
"items": {},
"matches": []
},
{
"name": "Devices used for your cloud account",
"priority": 50,
"items": {},
"matches": []
},
{
"name": "Too much access in GCP/Azure/AWS/ other clouds",
"priority": 50,
"items": {},
"matches": []
},
{
"name": "Certificate validation",
"priority": 50,
"items": {},
"matches": []
},
{
"name": "Domain investigation with LetsEncrypt",
"priority": 50,
"items": {},
"matches": []
},
{
"name": "Monitor new DNS entries for domain with passive DNS",
"priority": 50,
"items": {},
"matches": []
},
{
"name": "Monitor and track password dumps",
"priority": 50,
"items": {},
"matches": []
},
{
"name": "Monitor for mentions of domain on darknet sites",
"priority": 50,
"items": {},
"matches": []
},
{
"name": "Reporting",
"priority": 50,
"items": {
"name": "Monthly reports",
"items": {
"name": "...",
"items": {}
}
},
"matches": []
}
],
"matches": []
}
]);
const SideBar = {
minWidth: 250,
maxWidth: 300,
borderRight: "1px solid rgba(255,255,255,0.3)",
left: 0,
position: "sticky",
minHeight: "90vh",
maxHeight: "90vh",
overflowX: "hidden",
overflowY: "auto",
zIndex: 1000,
color: "black"
};
const [age, setAge] = React.useState(0);
const handleChange = (event) => {
setAge(event.target.value);
};
return (
<div>
<Card>
<CardContent style={{ padding: 0 }}>
<div style={{
background: "url('/images/home-header-bg.png')", height: "450px", backgroundSize: "cover",
backgroundRepeat: "no-repeat",
backgroundPosition: "center",
position: "relative"
}}>
<div style={{ width: "95%", margin: "auto", position: "relative", height: "450px" }}>
<div>
<PrimarySearchAppBar />
</div>
<div style={{
position: "absolute",
bottom: "10%",
display: "flex",
alignItems: "flex-end",
justifyContent: "space-between",
width: "100%"
}}>
<div>
<img src="/images/Shuffle_logo.png" style={{ height: "4rem", width: "4rem" }} alt="shuffle img" />
<Typography type="title" variant="h1" color="#ef5d29">
SHUFFLE
</Typography>
</div>
<div>
<SearchField />
</div>
</div>
</div>
</div>
</CardContent>
</Card>
<div style={{ display: "flex" }}>
<div style={SideBar}>
<List>
<ListItem >
<ListItemText>
<span style={{ fontSize: "25px", fontFamily: "revert", fontWeight: "bold" }}>Categories</span>
</ListItemText>
</ListItem>
<span></span>
<Divider />
<ListItem>
<ListItemText>
<span style={{ fontSize: "25px", fontFamily: "revert" }}>Workflows</span>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<span style={{ fontSize: "25px", fontFamily: "revert" }}>Apps</span>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<span style={{ fontSize: "25px", fontFamily: "revert" }}>Docs</span>
</ListItemText>
</ListItem>
</List>
</div>
<div style={{ padding: "20px", width: "100%" }}>
<Typography type="title" variant="h2" color="black">
Workflow
</Typography>
<div style={{ width: "100%", minHeight: isMobile ? 0 : 71, maxHeight: isMobile ? 0 : 71, }}>
{!isMobile && usecases !== null && usecases !== undefined && usecases.length > 0 ?
<div style={{ display: "flex", }}>
<Grid container spacing={2}>
{usecases.map((usecase, index) => {
//console.log(usecase)
return (
<Grid item xs={4}>
<Paper
key={usecase.name}
style={{
flex: 1,
backgroundColor: "transparent",
marginRight: index === usecases.length - 1 ? 0 : 10,
cursor: "pointer",
overflow: "hidden",
padding: 10,
border: "0.0625rem solid #b2b2b2",
borderRadius: "1.5625rem",
boxSizing: "content-box",
cursor: "pointer",
height: "70px",
}}
onClick={() => {
console.log("clicked...")
}}
>
<a href={`/usecases?selected=${usecase.name}`} rel="noopener noreferrer" target="_blank" style={{ textDecoration: "none", }}>
<Typography variant="body1" color="textPrimary">
{usecase.name}
</Typography>
<Typography variant="body2" color="textSecondary">
In use: {usecase.matches.length}/{usecase.list.length}
</Typography>
</a>
</Paper>
</Grid>
)
})}
</Grid>
</div>
: null}
</div>
</div>
</div>
<Card>
<CardContent style={{ padding: 0 }}>
<Typography type="title" variant="h3" color="#ffffff" style={{
backgroundColor: "black", padding: "10px", height: "200px",
display: "flex",
justifyContent: "center",
alignItems: "center"
}}>
footer
</Typography>
</CardContent>
</Card>
</div>
);
};
export default AppHub;
-268
View File
@@ -1,268 +0,0 @@
import React, { useState, useEffect } from "react";
import { Grid, Container, Divider, CardMedia, List, ListItem, ListItemText } from "@mui/material";
import Typography from "@material-ui/core/Typography";
import theme from '../theme';
import {isMobile} from "react-device-detect";
import AppGrid1 from "../components/AppGrid1.jsx"
import WorkflowGrid from "../components/WorkflowGrid.jsx"
import CreatorGrid from "../components/CreatorGrid.jsx"
import DocsGrid from "../components/DocsGrid.jsx"
import Button from '@mui/material/Button';
import { useNavigate, Link } from "react-router-dom";
import {
Tabs,
Paper,
Tab,
} from "@material-ui/core";
import {
Business as BusinessIcon,
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 Appdemo = (props) => {
const { globalUrl, isLoaded, serverside, userdata, hidemargins, } = props;
const [appCategory,setAppCategory] = useState();
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 NoArguments_NoReturn = () => {
alert('Function Called...');
}
const SideBar = {
minWidth: 250,
maxWidth: 300,
borderRight: "1px solid rgba(255,255,255,0.3)",
left: 0,
position: "sticky",
minHeight: "90vh",
maxHeight: "90vh",
overflowX: "hidden",
overflowY: "auto",
zIndex: 1000,
color: "white"
};
const catItems = (
<div style={SideBar}>
<List>
<ListItem >
<ListItemText>
<span><Typography variant="primary">Categories</Typography></span>
</ListItemText>
</ListItem>
<span></span>
<Divider />
<ListItem>
<ListItemText>
<Button id="ASSETS" variant="primary" onClick={()=>{setAppCategory("assets")}}>
ASSETS
</Button>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<Button variant="primary" onClick={()=>{setAppCategory("cases")}}>
CASES
</Button>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<Button variant="primary" onClick={()=>{setAppCategory("comms")}}>
COMMS
</Button>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<Button variant="primary" onClick={()=>{setAppCategory("edr av")}}>
EDR & AV
</Button>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<Button variant="primary" onClick={()=>{setAppCategory("iam")}}>
IAM
</Button>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<Button variant="primary" onClick={()=>{setAppCategory("intel")}}>
INTEL
</Button>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<Button variant="primary" onClick={()=>{setAppCategory("network")}}>
NETWORK
</Button>
</ListItemText>
</ListItem>
<ListItem>
<ListItemText>
<Button variant="primary" onClick={()=>{setAppCategory("siem")}}>
SIEM
</Button>
</ListItemText>
</ListItem>
</List>
</div>
)
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", backgroundColor: theme.palette.surfacColor}}>
<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"
>
<Tab
label=<span>
<AppsIcon style={iconStyle} /> Apps
</span>
/>
</Tabs>
{curTab === 0 ?
<AppGrid1 maxRows={3} showSuggestion={true} globalUrl={globalUrl} isMobile={isMobile} userdata={userdata} searchValue={appCategory} key={appCategory} />
:
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={{backgroundColor: "#1f2023", display: "flex"}}>
{catItems}
{loadedCheck}
</div>
)
}
export default Appdemo;
+41 -46
View File
@@ -59,7 +59,8 @@ import algoliasearch from 'algoliasearch/lite';
import YAML from "yaml";
import { useNavigate, Link, useParams } from "react-router-dom";
import { useAlert } from "react-alert";
//import { useAlert
import { ToastContainer, toast } from "react-toastify"
import Dropzone from "../components/Dropzone.jsx";
const surfaceColor = "#27292D";
@@ -272,7 +273,7 @@ const Apps = (props) => {
//const [workflows, setWorkflows] = React.useState([]);
const baseRepository = "https://github.com/frikky/shuffle-apps";
const alert = useAlert();
//const alert = useAlert();
let navigate = useNavigate();
const [selectedApp, setSelectedApp] = React.useState({});
@@ -454,7 +455,7 @@ const Apps = (props) => {
//}, 5000)
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
setIsLoading(false);
});
};
@@ -462,7 +463,7 @@ const Apps = (props) => {
const downloadApp = (inputdata) => {
const id = inputdata.id;
alert.info("Downloading..");
toast("Downloading..");
fetch(globalUrl + "/api/v1/apps/" + id + "/config", {
method: "GET",
headers: {
@@ -480,7 +481,7 @@ const Apps = (props) => {
})
.then((responseJson) => {
if (!responseJson.success) {
alert.error("Failed to download file");
toast("Failed to download file");
} else {
console.log(responseJson);
const basedata = atob(responseJson.openapi);
@@ -538,7 +539,7 @@ const Apps = (props) => {
})
.catch((error) => {
console.log(error);
alert.error(error.toString());
toast(error.toString());
});
};
@@ -1152,7 +1153,7 @@ const Apps = (props) => {
<Select
value={sharingConfiguration}
onChange={(event) => {
alert.info("Changing sharing to " + event.target.value);
toast("Changing sharing to " + event.target.value);
setSharingConfiguration(event.target.value);
@@ -1479,7 +1480,7 @@ const Apps = (props) => {
try {
reader.readAsText(files[0]);
} catch (error) {
alert.error("Failed to read file");
toast("Failed to read file");
}
};
@@ -1578,9 +1579,9 @@ const Apps = (props) => {
})
.then((responseJson) => {
if (responseJson.success === false) {
alert.error("Failed to activate the app")
toast("Failed to activate the app")
} else {
alert.success("App activated for your organization! Refresh the page to use the app.")
toast("App activated for your organization! Refresh the page to use the app.")
if (refresh === true) {
getApps()
@@ -1588,7 +1589,7 @@ const Apps = (props) => {
}
})
.catch(error => {
//alert.error(error.toString())
//toast(error.toString())
console.log("Activate app error: ", error.toString())
});
}
@@ -1692,13 +1693,13 @@ const Apps = (props) => {
return (
<div style={{ textDecoration: "none", color: "white", }} onClick={(event) => {
//if (!isCloud) {
// alert.info("Since this is an on-prem instance. You will need to activate the app yourself. Opening link to download it in a new window.")
// toast("Since this is an on-prem instance. You will need to activate the app yourself. Opening link to download it in a new window.")
// setTimeout(() => {
// event.preventDefault()
// window.open(parsedUrl, '_blank')
// }, 2000)
//} else {
alert.info(`Activating ${name}`)
toast(`Activating ${name}`)
//}
console.log("CLICK: ", hit)
@@ -1867,12 +1868,6 @@ const Apps = (props) => {
style={{ backgroundColor: inputColor, borderRadius: 5 }}
InputProps={{
style: {
color: "white",
minHeight: "50px",
marginLeft: "5px",
maxWidth: "95%",
fontSize: "1em",
borderRadius: 5,
},
}}
disabled={
@@ -2016,7 +2011,7 @@ const Apps = (props) => {
parsedData["force_update"] = forceUpdate;
alert.success("Getting specific apps from your URL.");
toast("Getting specific apps from your URL.");
var cors = "cors";
fetch(globalUrl + "/api/v1/apps/get_existing", {
method: "POST",
@@ -2029,7 +2024,7 @@ const Apps = (props) => {
})
.then((response) => {
if (response.status === 200) {
alert.success("Loaded existing apps!");
toast("Loaded existing apps!");
}
//stop()
@@ -2040,12 +2035,12 @@ const Apps = (props) => {
.then((responseJson) => {
console.log("DATA: ", responseJson);
if (responseJson.reason !== undefined) {
alert.error("Failed loading: " + responseJson.reason);
toast("Failed loading: " + responseJson.reason);
}
})
.catch((error) => {
console.log("ERROR: ", error.toString());
//alert.error(error.toString());
//toast(error.toString());
//stop()
setIsLoading(false);
@@ -2055,7 +2050,7 @@ const Apps = (props) => {
// Locally hotloads app from folder
const hotloadApps = () => {
alert.info("Hotloading apps from location in .env");
toast("Hotloading apps from location in .env");
setIsLoading(true);
fetch(globalUrl + "/api/v1/apps/run_hotload", {
mode: "cors",
@@ -2067,7 +2062,7 @@ const Apps = (props) => {
.then((response) => {
setIsLoading(false);
if (response.status === 200) {
//alert.success("Hotloaded apps!")
//toast("Hotloaded apps!")
getApps();
}
@@ -2075,14 +2070,14 @@ const Apps = (props) => {
})
.then((responseJson) => {
if (responseJson.success === true) {
alert.info("Successfully finished hotload");
toast("Successfully finished hotload");
} else {
alert.error("Failed hotload: ", responseJson.reason);
toast("Failed hotload: ", responseJson.reason);
//(responseJson.reason !== undefined && responseJson.reason.length > 0) {
}
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
@@ -2106,7 +2101,7 @@ const Apps = (props) => {
});
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
@@ -2119,9 +2114,9 @@ const Apps = (props) => {
})
.then((response) => {
if (response.status === 200) {
//alert.success("Successfully GOT app "+appId)
//toast("Successfully GOT app "+appId)
} else {
alert.error("Failed getting app");
toast("Failed getting app");
}
return response.json();
@@ -2144,7 +2139,7 @@ const Apps = (props) => {
responseJson.loop_versions = selectedApp.loop_versions;
}
//alert.info("Should set app to selected")
//toast("Should set app to selected")
if (
responseJson.actions !== undefined &&
responseJson.actions !== null &&
@@ -2158,12 +2153,12 @@ const Apps = (props) => {
}
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
const deleteApp = (appId) => {
alert.info("Attempting to delete app");
toast("Attempting to delete app");
fetch(globalUrl + "/api/v1/apps/" + appId, {
method: "DELETE",
headers: {
@@ -2173,16 +2168,16 @@ const Apps = (props) => {
})
.then((response) => {
if (response.status === 200) {
alert.success("Successfully deleted app");
toast("Successfully deleted app");
setTimeout(() => {
getApps();
}, 1000);
} else {
alert.error("Failed deleting app. Does it still exist?");
toast("Failed deleting app. Does it still exist?");
}
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
@@ -2206,19 +2201,19 @@ const Apps = (props) => {
})
.then((responseJson) => {
//console.log(responseJson)
//alert.info(responseJson)
//toast(responseJson)
if (responseJson.success) {
alert.success("Successfully updated app configuration");
toast("Successfully updated app configuration");
} else {
if (responseJson.reason !== undefined && responseJson.reason !== null) {
alert.error("Error: "+responseJson.reason);
toast("Error: "+responseJson.reason);
} else {
alert.error("Error updating app configuration");
toast("Error updating app configuration");
}
}
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
@@ -2249,7 +2244,7 @@ const Apps = (props) => {
}
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
@@ -2287,7 +2282,7 @@ const Apps = (props) => {
validateOpenApi(responseJson);
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
setOpenApiError(error.toString());
});
};
@@ -2344,12 +2339,12 @@ const Apps = (props) => {
if (responseJson.reason !== undefined) {
setOpenApiError(responseJson.reason);
}
alert.error("An error occurred in the response");
toast("An error occurred in the response");
}
})
.catch((error) => {
setValidation(false);
alert.error(error.toString());
toast(error.toString());
setOpenApiError(error.toString());
});
};
-343
View File
@@ -1,343 +0,0 @@
import React, { useState } from 'react';
import { BrowserView, MobileView } from "react-device-detect";
import theme from '../theme.jsx';
import Paper from '@material-ui/core/Paper';
import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';
const bodyDivStyle = {
margin: "auto",
textAlign: "center",
width: "900px",
}
// Should be different if logged in :|
const Contact = (props) => {
const { globalUrl, isLoaded } = props;
const boxStyle = {
flex: "1",
marginLeft: "10px",
marginRight: "10px",
paddingLeft: "30px",
paddingRight: "30px",
paddingBottom: "30px",
paddingTop: "30px",
backgroundColor: theme.palette.surfaceColor,
display: "flex",
flexDirection: "column"
}
const bodyTextStyle = {
color: "#ffffff",
}
const [firstname, setFirstname] = useState("");
const [lastname, setLastname] = useState("");
const [title, setTitle] = useState("");
const [companyname, setCompanyname] = useState("");
const [email, setEmail] = useState("");
const [phone, setPhone] = useState("");
const [message, setMessage] = useState("");
const [formMessage, setFormMessage] = useState("");
const submitContact = () => {
const data = {
"firstname": firstname,
"lastname": lastname,
"title": title,
"companyname": companyname,
"email": email,
"phone": phone,
"message": message,
}
console.log(data)
fetch(globalUrl + "/api/v1/contact", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
.then(response => response.json())
.then(response => {
if (response.success === true) {
setFormMessage(response.message)
} else {
setFormMessage("Something went wrong. Please contact frikky@shuffler.io.")
}
console.log(response)
})
.catch(error => {
console.log(error)
});
}
// Random names for type & autoComplete. Didn't research :^)
const landingpageDataBrowser =
<div>
<div style={bodyTextStyle}>
<h3 style={{ color: "#f85a3e" }}>Contact us</h3>
<h2>Lets talk!</h2>
</div>
<div style={{ display: "flex" }}>
<Paper style={boxStyle}>
<h2>Contact Details</h2>
<div style={{ flex: "1", display: "flex", flexDirection: "row" }}>
<TextField
required
style={{ flex: "1", marginRight: "15px", backgroundColor: theme.palette.inputColor }}
InputProps={{
style: {
color: "white",
},
}}
color="primary"
fullWidth={true}
placeholder="First Name"
type="firstname"
id="standard-required"
autoComplete="firstname"
margin="normal"
variant="outlined"
onChange={e => setFirstname(e.target.value)}
/>
<TextField
style={{ flex: "1", marginLeft: "15px", backgroundColor: theme.palette.inputColor }}
InputProps={{
style: {
color: "white",
},
}}
color="primary"
fullWidth={true}
placeholder="Last Name"
type="lastname"
id="standard"
autoComplete="lastname"
margin="normal"
variant="outlined"
onChange={e => setLastname(e.target.value)}
/>
</div>
<div style={{ flex: "1", display: "flex", flexDirection: "row" }}>
<TextField
style={{ flex: "1", marginRight: "15px", backgroundColor: theme.palette.inputColor }}
InputProps={{
style: {
color: "white",
},
}}
color="primary"
fullWidth={true}
placeholder="Job Title"
type="jobtitle"
id="standard-required"
autoComplete="jobtitle"
margin="normal"
variant="outlined"
onChange={e => setTitle(e.target.value)}
/>
<TextField
style={{ flex: "1", marginLeft: "15px", backgroundColor: theme.palette.inputColor }}
InputProps={{
style: {
color: "white",
},
}}
color="primary"
fullWidth={true}
type="companyname"
placeholder="Company Name"
id="standard-required"
autoComplete="companyname"
margin="normal"
variant="outlined"
onChange={e => setCompanyname(e.target.value)}
/>
</div>
<div style={{ flex: "1", display: "flex", flexDirection: "row" }}>
<TextField
required
style={{ flex: "1", marginRight: "15px", backgroundColor: theme.palette.inputColor }}
InputProps={{
style: {
color: "white",
},
}}
color="primary"
fullWidth={true}
placeholder="Email"
type="email"
id="standard-required"
autoComplete="email"
margin="normal"
variant="outlined"
onChange={e => setEmail(e.target.value)}
/>
<TextField
style={{ flex: "1", marginLeft: "15px", backgroundColor: theme.palette.inputColor }}
InputProps={{
style: {
color: "white",
},
}}
color="primary"
fullWidth={true}
type="phone"
placeholder="Phone number"
id="standard-required"
autoComplete="phone"
margin="normal"
variant="outlined"
onChange={e => setPhone(e.target.value)}
/>
</div>
<div style={{ flex: 1 }}>
<h2>Message</h2>
</div>
<div style={{ flex: 4 }}>
<TextField
multiline
InputProps={{
style: {
color: "white",
},
}}
color="primary"
style={{ flex: "1", backgroundColor: theme.palette.inputColor }}
rows="6"
fullWidth={true}
placeholder="What can we help you with?"
id="filled-multiline-static"
margin="normal"
variant="outlined"
onChange={e => setMessage(e.target.value)}
/>
</div>
<Button
disabled={email.length <= 0 || message.length <= 0}
style={{ width: "100%", height: "60px", marginTop: "10px" }}
variant="contained"
color="primary"
onClick={submitContact}
>
Submit
</Button>
<h3>{formMessage}</h3>
</Paper>
</div>
</div>
const landingpageDataMobile =
<div style={{ paddingBottom: "50px" }}>
<div style={{ color: "white", textAlign: "center" }}>
<h3 style={{ color: "#f85a3e" }}>Contact us</h3>
<h2>Lets talk!</h2>
</div>
<div style={{ display: "flex" }}>
<Paper style={boxStyle}>
<h2>Contact Details</h2>
<div style={{ flex: "1", display: "flex", flexDirection: "row" }}>
<TextField
required
style={{ flex: "1", backgroundColor: theme.palette.inputColor }}
InputProps={{
style: {
color: "white",
},
}}
color="primary"
fullWidth={true}
placeholder="Name"
type="firstname"
id="standard-required"
autoComplete="firstname"
margin="normal"
variant="outlined"
onChange={e => setFirstname(e.target.value)}
/>
</div>
<div style={{ flex: "1", display: "flex", flexDirection: "row" }}>
<TextField
required
style={{ flex: "1", backgroundColor: theme.palette.inputColor }}
InputProps={{
style: {
color: "white",
},
}}
color="primary"
fullWidth={true}
placeholder="Email"
type="email"
id="standard-required"
autoComplete="email"
margin="normal"
variant="outlined"
onChange={e => setEmail(e.target.value)}
/>
</div>
<div style={{ flex: 1 }}>
<h2>Message</h2>
</div>
<div style={{ flex: 4 }}>
<TextField
multiline
style={{ flex: "1", backgroundColor: theme.palette.inputColor }}
InputProps={{
style: {
color: "white",
},
}}
color="primary"
rows="6"
fullWidth={true}
placeholder="What can we help you with?"
id="filled-multiline-static"
margin="normal"
variant="outlined"
onChange={e => setMessage(e.target.value)}
/>
</div>
<Button
disabled={email.length <= 0 || message.length <= 0}
style={{ width: "100%", height: "60px", marginTop: "10px" }}
variant="contained"
color="primary"
onClick={submitContact}
>
Submit
</Button>
<h3>{formMessage}</h3>
</Paper>
</div>
</div>
const loadedCheck = isLoaded ?
<div>
<BrowserView>
<div style={bodyDivStyle}>{landingpageDataBrowser}</div>
</BrowserView>
<MobileView>
{landingpageDataMobile}
</MobileView>
</div>
:
<div>
</div>
return (
<div>
{loadedCheck}
</div>
)
}
export default Contact;
+17 -17
View File
@@ -9,7 +9,8 @@ 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 { useAlert
import { ToastContainer, toast } from "react-toastify"
import {
Autocomplete,
@@ -83,7 +84,6 @@ const useStyles = makeStyles({
},
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",
},
@@ -156,7 +156,7 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
}, 100);
})
.catch((error) => {
//alert.error(error.toString());
//toast(error.toString());
setInputUsecase({})
setExpandedIndex(index)
setExpandedItem(subindex)
@@ -213,16 +213,16 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
.then((responseJson) => {
if (responseJson.success === false) {
if (responseJson.reason !== undefined) {
//alert.error("Failed updating: " + responseJson.reason)
//toast("Failed updating: " + responseJson.reason)
} else {
//alert.error("Failed to update framework for your org.")
//toast("Failed to update framework for your org.")
}
} else {
//alert.info("Updated usecase.")
//toast("Updated usecase.")
}
})
.catch((error) => {
//alert.error(error.toString());
//toast(error.toString());
//setFrameworkLoaded(true)
})
}
@@ -250,9 +250,9 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
.then((responseJson) => {
if (responseJson.success === false) {
if (responseJson.reason !== undefined) {
alert.error("Error updating workflow: ", responseJson.reason)
toast("Error updating workflow: ", responseJson.reason)
} else {
alert.error("Error updating workflow.")
toast("Error updating workflow.")
}
return
@@ -261,7 +261,7 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
return responseJson;
})
.catch((error) => {
alert.error("Problem setting workflow: ", error.toString());
toast("Problem setting workflow: ", error.toString());
});
};
@@ -1026,7 +1026,7 @@ const RadialChart = ({keys, setSelectedCategory}) => {
// What data do we fill in here? Idk
const Dashboard = (props) => {
const { globalUrl, isLoggedIn } = props;
const alert = useAlert();
//const alert = useAlert();
const [bigChartData, setBgChartData] = useState("data1");
const [dayAmount, setDayAmount] = useState(7);
const [firstRequest, setFirstRequest] = useState(true);
@@ -1118,16 +1118,16 @@ const Dashboard = (props) => {
.then((responseJson) => {
if (responseJson.success === false) {
if (responseJson.reason !== undefined) {
//alert.error("Failed loading: " + responseJson.reason)
//toast("Failed loading: " + responseJson.reason)
} else {
//alert.error("Failed to load framework for your org.")
//toast("Failed to load framework for your org.")
}
} else {
setFrameworkData(responseJson)
}
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
})
}
@@ -1158,7 +1158,7 @@ const Dashboard = (props) => {
})
.catch((error) => {
fetchUsecases()
//alert.error(error.toString());
//toast(error.toString());
});
}
@@ -1258,7 +1258,7 @@ const Dashboard = (props) => {
}
})
.catch((error) => {
//alert.error("ERROR: " + error.toString());
//toast("ERROR: " + error.toString());
console.log("ERROR: " + error.toString());
});
};
@@ -1292,7 +1292,7 @@ const Dashboard = (props) => {
setChangeme(stats_id);
})
.catch((error) => {
//alert.error("ERROR: " + error.toString());
//toast("ERROR: " + error.toString());
console.log("ERROR: " + error.toString());
});
};
+7 -7
View File
@@ -8,7 +8,8 @@ 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 { useAlert
import { ToastContainer, toast } from "react-toastify"
import Draggable from "react-draggable";
import {
@@ -93,7 +94,6 @@ const useStyles = makeStyles({
},
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",
},
@@ -333,7 +333,7 @@ const RadialChart = ({keys, setSelectedCategory}) => {
// What data do we fill in here? Idk
const Dashboard = (props) => {
const { globalUrl, isLoggedIn } = props;
const alert = useAlert();
//const alert = useAlert();
const [bigChartData, setBgChartData] = useState("data1");
const [dayAmount, setDayAmount] = useState(7);
const [firstRequest, setFirstRequest] = useState(true);
@@ -428,9 +428,9 @@ const Dashboard = (props) => {
console.log("Resp: ", responseJson)
if (responseJson.success === false) {
if (responseJson.reason !== undefined) {
//alert.error("Failed loading: " + responseJson.reason)
//toast("Failed loading: " + responseJson.reason)
} else {
//alert.error("Failed to load framework for your org.")
//toast("Failed to load framework for your org.")
}
} else {
var tmpdata = responseJson
@@ -453,7 +453,7 @@ const Dashboard = (props) => {
}
})
.catch((error) => {
//alert.error(error.toString());
//toast(error.toString());
})
}
@@ -505,7 +505,7 @@ const Dashboard = (props) => {
setChangeme(stats_id);
})
.catch((error) => {
//alert.error("ERROR: " + error.toString());
//toast("ERROR: " + error.toString());
console.log("ERROR: " + error.toString());
});
};
+10 -5
View File
@@ -436,7 +436,7 @@ const Docs = (defaultprops) => {
href={selectedMeta.link}
style={{ textDecoration: "none", color: "#f85a3e" }}
>
<Button style={{}} variant="outlined">
<Button style={{}} variant="outlined" color="secondary">
<EditIcon /> &nbsp;&nbsp;Edit
</Button>
</a>
@@ -792,17 +792,21 @@ const Docs = (defaultprops) => {
<div id="markdown_wrapper_outer" style={markdownStyle}>
<ReactMarkdown
components={{
link: OuterLink,
image: Img,
img: Img,
code: CodeHandler,
heading: Heading,
h1: Heading,
h2: Heading,
h3: Heading,
h4: Heading,
h5: Heading,
h6: Heading,
a: OuterLink,
}}
id="markdown_wrapper"
escapeHtml={false}
style={{
maxWidth: "100%", minWidth: "100%",
}}
remarkPlugins={[remarkGfm]}
>
{data}
</ReactMarkdown>
@@ -811,6 +815,7 @@ const Docs = (defaultprops) => {
</div>
</div>
);
// remarkPlugins={[remarkGfm]}
const mobileStyle = {
color: "white",
File diff suppressed because it is too large Load Diff
-393
View File
@@ -1,393 +0,0 @@
import React, { useState, useEffect } from "react";
import Button from "@material-ui/core/Button";
import Paper from "@material-ui/core/Paper";
import Divider from "@material-ui/core/Divider";
import Select from "@material-ui/core/Select";
import MenuItem from "@material-ui/core/MenuItem";
import WebhookImage from "../assets/img/webhook.png";
import KafkaImage from "../assets/img/kafka.png";
import EditWorkflow from "./EditWorkflow";
const EditWebhook = (props) => {
const { globalUrl, isLoaded } = props;
// FIXME
//const [webhookData, setWebhookData] = useState(webhooktest)
const [webhookData, setWebhookData] = useState({});
const [workflows, setWorkflows] = useState([]);
const [firstrequest, setFirstrequest] = React.useState(true);
const [selectedWorkflows, setSelectedWorkflows] = useState([]);
const getWorkflows = () => {
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 response.json();
})
.then((responseJson) => {
setWorkflows(responseJson);
})
.catch((error) => {
console.log(error);
});
};
const setWebhook = (inputdata) => {
console.log(inputdata);
fetch(globalUrl + "/api/v1/hooks/" + props.match.params.key, {
method: "PUT",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
body: JSON.stringify(inputdata),
})
.then((response) => response.json())
.then((responseJson) => {
console.log(responseJson);
})
.catch((error) => {
console.log(error);
});
};
const getCurrentWebhook = () => {
fetch(globalUrl + "/api/v1/hooks/" + props.match.params.key, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200!");
window.location.pathname = "webhooks";
}
return response.json();
})
.then((responseJson) => {
if (responseJson.actions === null) {
responseJson.actions = [];
}
if (responseJson.transforms === null) {
responseJson.transforms = [];
}
setWebhookData(responseJson);
})
.catch((error) => {
console.log(error);
//window.location.pathname = "webhooks"
});
};
useEffect(() => {
if (firstrequest) {
setFirstrequest(false);
getCurrentWebhook();
if (workflows.length <= 0) {
getWorkflows();
}
}
// After everything is loaded
if (
Object.getOwnPropertyNames(webhookData).length > 0 &&
webhookData.actions.length > 0 &&
workflows.length > 0 &&
selectedWorkflows.length === 0
) {
// Setting startup actions. making like this in case we want other actions
var tmpActionWorkflows = [];
for (var key in webhookData.actions) {
if (webhookData.actions[key].type === "workflow") {
tmpActionWorkflows.push(webhookData.actions[key]);
}
}
// Fix duplicates... Meh
var foundWorkflowIds = [];
var tmpWorkflows = [];
for (key in tmpActionWorkflows) {
if (foundWorkflowIds.includes(tmpActionWorkflows[key].id)) {
continue;
}
for (var subkey in workflows) {
if (tmpActionWorkflows[key].id === workflows[subkey]["id_"]) {
console.log(tmpActionWorkflows[key].id, workflows[subkey]["id_"]);
foundWorkflowIds.push(tmpActionWorkflows[key].id);
tmpWorkflows.push(workflows[subkey]);
break;
}
}
}
if (tmpWorkflows.length > 0) {
setSelectedWorkflows(tmpWorkflows);
}
}
});
const hookPicture =
Object.getOwnPropertyNames(webhookData).length > 0 &&
webhookData.type === "webhook" ? (
<img src={WebhookImage} alt="webhook" width="100px" height="100px" />
) : (
<img src={KafkaImage} alt="MQ" width="100px" height="100px" />
);
const executeHook = (action) => {
fetch(
globalUrl + "/api/v1/hooks/" + props.match.params.key + "/" + action,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
}
)
.then((response) => response.json())
.then((responseJson) => {
setWebhookData({});
})
.catch((error) => {
console.log(error);
});
};
const headerPaperStyle = {
display: "flex",
maxHeight: "800px",
minHeight: "800px",
margin: "10px 30px 10px 10px",
padding: "10px 5px 5px 5px",
flexDirection: "column",
};
// FIXME - add with counter to change the correct one (not just edit)
const addNewWorkflow = (event) => {
// Verify if it already exists in the array. Returns if it exists
for (var key in selectedWorkflows) {
var item = selectedWorkflows[key];
if (item["id_"] === event.target.value["id_"]) {
return;
}
}
// FIXME - make this possible for all accounts
if (selectedWorkflows.length === 0) {
console.log("ADD FIRST ITEM FOR SELECTEDWORKFLOWS");
console.log(event.target.value);
// Cleanup previous actions
var newActions = [];
if (webhookData.actions.length > 0) {
for (key in webhookData.actions) {
if (
webhookData.actions[key].type === "" ||
webhookData.actions[key].type === undefined
) {
continue;
}
newActions.push(webhookData.actions[key]);
}
}
// FIXME - how to stringify this better hurr
var formattedWorkflow = {
type: "workflow",
name: event.target.value.name,
id: event.target.value.id_,
field: "",
};
// FIXME: patch this n
newActions.push(formattedWorkflow);
console.log(newActions);
webhookData.actions = newActions;
setWebhook(webhookData);
}
var tmpSelectedWorkflows = [].concat(selectedWorkflows, [
event.target.value,
]);
setSelectedWorkflows(tmpSelectedWorkflows);
};
// FIXME
// Create a list with + button
// For each, choose the new workflow I wanna add
// Current: JUST ONE
const selectedWorkflowIds = selectedWorkflows.map((data) => {
return data["id_"];
});
const availableWorkflows = workflows.filter(
(data) => !selectedWorkflowIds.includes(data["id_"])
);
const WorkflowSelect = (counter) => {
if (selectedWorkflows[counter.counter] === undefined) {
return null;
}
console.log(selectedWorkflows[0]);
console.log(selectedWorkflows[0]);
console.log(selectedWorkflows[0]);
console.log(selectedWorkflows[counter.counter]);
console.log(selectedWorkflows[counter.counter].name);
return (
<div>
Workflow select:
<Select
value={selectedWorkflows[counter.counter].name}
onChange={(event) => {
addNewWorkflow(event, counter.counter);
}}
displayEmpty
name="workflow"
>
{availableWorkflows.map((data) => (
<MenuItem key={data.name} value={data} name={data.name}>
{data.name}
</MenuItem>
))}
</Select>
</div>
);
};
const extraWorkflow =
workflows.length > 0 && availableWorkflows.length > 0 ? (
<WorkflowSelect counter={selectedWorkflows.length} />
) : null;
const multiWorkflowSelect =
workflows.length > 0 && selectedWorkflows.length > 0 ? (
<div>
{selectedWorkflows.map((data, count) => (
<WorkflowSelect key={count} counter={count} />
))}
{extraWorkflow}
</div>
) : (
<WorkflowSelect counter={0} />
);
const headerInfo =
Object.getOwnPropertyNames(webhookData).length > 0 ? (
<div>
<Paper style={headerPaperStyle}>
<div style={{ display: "flex", flex: "1" }}>
<div style={{ flex: "1" }}>{hookPicture}</div>
<div
style={{ display: "flex", flexDirection: "column", flex: "5" }}
>
<div style={{ flex: "1" }}>
<h1>Name: {webhookData.info.name}</h1>
</div>
</div>
</div>
<div style={{ flex: "4" }}>
Description: {webhookData.info.description}
<div>Id: {webhookData.id}</div>
<div>Url: {webhookData.info.url}</div>
<div>Type: {webhookData.type}</div>
<div>Status: {webhookData.status}</div>
<div>
CHOOSE ACTIONS:
{multiWorkflowSelect}
</div>
</div>
<Divider />
<div style={{ flex: "1", display: "flex", flexDirection: "row" }}>
<div style={{ flex: "1" }}>
<Button
disabled={
webhookData.running === true && webhookData.name !== ""
}
onClick={() => {
executeHook("start");
}}
style={{
left: "50%",
top: "50%",
transform: "translate(-50%, -50%)",
}}
variant="outlined"
color="primary"
>
Start {webhookData.type}
</Button>
</div>
<div style={{ flex: "1" }}>
<Button
disabled={webhookData.running === false}
onClick={() => {
executeHook("stop");
}}
style={{
left: "50%",
top: "50%",
transform: "translate(-50%, -50%)",
}}
variant="outlined"
color="primary"
>
Stop {webhookData.type}
</Button>
</div>
</div>
</Paper>
</div>
) : null;
// FIXME - needs refresh every time you add a new workflow
const workflowdata =
Object.getOwnPropertyNames(webhookData).length > 0 &&
selectedWorkflows.length > 0 ? (
<EditWorkflow
globalUrl={globalUrl}
inputworkflows={selectedWorkflows}
inputname={webhookData.info.name}
inputtype={webhookData.type}
/>
) : null;
const loadedCheck = isLoaded ? (
<div style={{ display: "flex", backgroundColor: "#f7f7f7" }}>
<div style={{ flex: 1 }}>{workflowdata}</div>
<div style={{ flex: 1 }}>{headerInfo}</div>
</div>
) : (
<div></div>
);
// FIXME: Use this for testing
// <EditWorkflow globalUrl={globalUrl} inputname={"Helo?"} inputtype={"webhook"} /> : null
return <div>{loadedCheck}</div>;
};
export default EditWebhook;
-118
View File
@@ -1,118 +0,0 @@
/* eslint-disable react/no-multi-comp */
import React, { useState } from "react";
import TextField from "@material-ui/core/TextField";
import Button from "@material-ui/core/Button";
import Paper from "@material-ui/core/Paper";
const bodyDivStyle = {
margin: "auto",
marginTop: "100px",
width: "500px",
};
const ForgotPassword = (props) => {
const { globalUrl, isLoaded, isLoggedIn, surfaceColor, inputColor } = props;
const boxStyle = {
paddingLeft: "30px",
paddingRight: "30px",
paddingBottom: "30px",
paddingTop: "30px",
backgroundColor: surfaceColor,
};
const [username, setUsername] = useState("");
const [resetInfo, setResetInfo] = useState(
"You will receive an email with instructions shortly."
);
const handleValidateForm = () => {
return username.length > 3;
};
if (isLoggedIn === true) {
window.location.pathname = "/";
}
const onSubmit = (e) => {
e.preventDefault();
// FIXME - add some check here ROFL
// Just use this one?
var data = { username: username };
var baseurl = globalUrl;
var url = baseurl + "/api/v1/passwordresetmail";
fetch(url, {
method: "POST",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json; charset=utf-8",
},
})
.then((response) =>
response.json().then((responseJson) => {
if (responseJson["success"] === false) {
setResetInfo(responseJson["reason"]);
}
})
)
.catch((error) => {
setResetInfo("Error in userdata: " + error);
});
};
const onChangeUser = (e) => {
setUsername(e.target.value);
};
const data = (
<div style={bodyDivStyle}>
<Paper style={boxStyle}>
<form onSubmit={onSubmit} style={{ margin: "15px 15px 15px 15px" }}>
<h2>Password reset</h2>
<div>
<TextField
required
fullWidth={true}
color="primary"
style={{ backgroundColor: inputColor }}
InputProps={{
style: {
height: "50px",
color: "white",
fontSize: "1em",
},
}}
type="username"
placeholder="Username / Email"
id="standard-required"
autoComplete="username"
margin="normal"
variant="outlined"
onChange={onChangeUser}
/>
</div>
<div style={{ display: "flex", marginTop: "15px" }}>
<Button
color="primary"
variant="contained"
type="submit"
style={{ flex: "1", marginRight: "5px" }}
disabled={!handleValidateForm()}
>
SUBMIT
</Button>
</div>
<div style={{ marginTop: "20px" }}>{resetInfo}</div>
</form>
</Paper>
</div>
);
const loadedCheck = isLoaded ? <div>{data}</div> : <div></div>;
return <div>{loadedCheck}</div>;
};
export default ForgotPassword;
-136
View File
@@ -1,136 +0,0 @@
import React, { useState, useEffect } from "react";
import Paper from "@material-ui/core/Paper";
import Button from "@material-ui/core/Button";
import TextField from "@material-ui/core/TextField";
const bodyDivStyle = {
margin: "auto",
textAlign: "center",
width: "768px",
};
const boxStyle = {
flex: "1",
marginLeft: "10px",
marginRight: "10px",
paddingLeft: "30px",
paddingRight: "30px",
paddingBottom: "30px",
paddingTop: "30px",
backgroundColor: "#e8eaf6",
display: "flex",
flexDirection: "column",
};
//const tmpdata = {
// "username": "frikky",
// "firstname": "fred",
// "lastname": "ode",
// "title": "topkek",
// "companyname": "company here",
// "email": "your email pls",
// "phone": "PHONE!!",
//}
// FIXME - add fetch for data fields
// FIXME - remove tmpdata
// FIXME: Use isLoggedIn :)
const Settings = (props) => {
const { globalUrl, isLoaded } = props;
const [newPassword, setNewPassword] = useState("");
const [newPassword2, setNewPassword2] = useState("");
const [passwordFormMessage, setPasswordFormMessage] = useState("");
const onPasswordChange = () => {
const data = {
newpassword: newPassword,
newpassword2: newPassword2,
reference: props.match.params.key,
};
const url = globalUrl + "/api/v1/passwordreset";
fetch(url, {
mode: "cors",
method: "POST",
body: JSON.stringify(data),
credentials: "include",
crossDomain: true,
withCredentials: true,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
})
.then((response) =>
response.json().then((responseJson) => {
if (responseJson["success"] === false) {
setPasswordFormMessage(responseJson["reason"]);
}
})
)
.catch((error) => {
setPasswordFormMessage("Something went wrong.");
});
};
// This should "always" have data
useEffect(() => {});
// Random names for type & autoComplete. Didn't research :^)
const landingpageData = (
<div style={{ display: "flex", marginTop: "80px" }}>
<Paper style={boxStyle}>
<h2>Password Reset</h2>
<div style={{ flex: "1", display: "flex", flexDirection: "column" }}>
<TextField
required
style={{ flex: "1" }}
fullWidth={true}
placeholder="New password"
type="password"
id="standard-required"
autoComplete="password"
margin="normal"
variant="outlined"
onChange={(e) => setNewPassword(e.target.value)}
/>
<TextField
required
style={{ flex: "1" }}
fullWidth={true}
type="password"
placeholder="Repeat new password"
id="standard-required"
margin="normal"
variant="outlined"
onChange={(e) => setNewPassword2(e.target.value)}
/>
</div>
<Button
disabled={
newPassword.length < 10 ||
newPassword2.length < 10 ||
newPassword !== newPassword2
}
style={{ width: "100%", height: "60px", marginTop: "10px" }}
variant="contained"
color="primary"
onClick={() => onPasswordChange()}
>
Submit password change
</Button>
<h3>{passwordFormMessage}</h3>
</Paper>
</div>
);
const loadedCheck = isLoaded ? (
<div style={bodyDivStyle}>{landingpageData}</div>
) : (
<div></div>
);
return <div>{loadedCheck}</div>;
};
export default Settings;
+6 -5
View File
@@ -2,7 +2,8 @@ import React, { useEffect, useState } from 'react';
import ReactDOM from "react-dom"
import AppFramework from "../components/AppFramework.jsx";
import { useAlert } from "react-alert";
//import { useAlert
import { ToastContainer, toast } from "react-toastify"
import { Link, useParams } from "react-router-dom";
import theme from '../theme.jsx';
@@ -13,7 +14,7 @@ import {
const Framework = (props) => {
const {globalUrl, isLoaded, isLoggedIn, showOptions, selectedOption, rolling, } = props;
const alert = useAlert()
//const alert = useAlert()
const [frameworkLoaded, setFrameworkLoaded] = useState(false)
const [frameworkData, setFrameworkData] = useState()
@@ -36,9 +37,9 @@ const Framework = (props) => {
.then((responseJson) => {
if (responseJson.success === false) {
if (responseJson.reason !== undefined) {
alert.error("Failed loading: " + responseJson.reason)
toast("Failed loading: " + responseJson.reason)
} else {
alert.error("Failed to load framework for your org.")
toast("Failed to load framework for your org.")
}
setFrameworkLoaded(true)
@@ -51,7 +52,7 @@ const Framework = (props) => {
})
.catch((error) => {
setFrameworkLoaded(true)
alert.error(error.toString());
toast(error.toString());
})
}
+42 -43
View File
@@ -58,9 +58,7 @@ import {
CloudDownload as CloudDownloadIcon,
} from "@mui/icons-material";
//import {Search as SearchIcon, ArrowUpward as ArrowUpwardIcon, Visibility as VisibilityIcon, Done as DoneIcon, Close as CloseIcon, Error as ErrorIcon, FindReplace as FindreplaceIcon, ArrowLeft as ArrowLeftIcon, Cached as CachedIcon, DirectionsRun as DirectionsRunIcon, Add as AddIcon, Polymer as PolymerIcon, FormatListNumbered as FormatListNumberedIcon, Create as CreateIcon, PlayArrow as PlayArrowIcon, AspectRatio as AspectRatioIcon, MoreVert as MoreVertIcon, Apps as AppsIcon, Schedule as ScheduleIcon, FavoriteBorder as FavoriteBorderIcon, Pause as PauseIcon, Delete as DeleteIcon, AddCircleOutline as AddCircleOutlineIcon, Save as SaveIcon, KeyboardArrowLeft as KeyboardArrowLeftIcon, KeyboardArrowRight as KeyboardArrowRightIcon, ArrowBack as ArrowBackIcon, Settings as SettingsIcon, LockOpen as LockOpenIcon, ExpandMore as ExpandMoreIcon, VpnKey as VpnKeyIcon} from '@material-ui/icons';
//https://next.material-ui.com/components/material-icons/
import { DataGrid, GridToolbar } from "@mui/x-data-grid";
//import JSONPretty from 'react-json-pretty';
@@ -68,8 +66,9 @@ import { DataGrid, GridToolbar } from "@mui/x-data-grid";
import Dropzone from "../components/Dropzone.jsx";
import { useNavigate, Link, useParams } from "react-router-dom";
import { useAlert } from "react-alert";
import ChipInput from "material-ui-chip-input";
//import { useAlert
import { ToastContainer, toast } from "react-toastify"
import { MuiChipsInput } from "mui-chips-input";
import { v4 as uuidv4 } from "uuid";
const inputColor = "#383B40";
@@ -125,7 +124,7 @@ const GettingStarted = (props) => {
const { globalUrl, isLoggedIn, isLoaded, userdata } = props;
document.title = "Getting Started with Shuffle";
const alert = useAlert();
//const alert = useAlert();
const classes = useStyles(theme);
let navigate = useNavigate();
const imgSize = 60;
@@ -444,7 +443,7 @@ const GettingStarted = (props) => {
const files = isDropzone ? e.dataTransfer.files : e.target.files;
const reader = new FileReader();
alert.info("Starting upload. Please wait while we validate the workflows");
toast("Starting upload. Please wait while we validate the workflows");
try {
reader.addEventListener("load", (e) => {
@@ -453,7 +452,7 @@ const GettingStarted = (props) => {
try {
data = JSON.parse(reader.result);
} catch (e) {
alert.error("Invalid JSON: " + e);
toast("Invalid JSON: " + e);
return;
}
@@ -481,13 +480,13 @@ const GettingStarted = (props) => {
false
).then((response) => {
if (response !== undefined) {
alert.success(`Successfully imported ${data.name}`);
toast(`Successfully imported ${data.name}`);
}
});
}
})
.catch((error) => {
alert.error("Import error: " + error.toString());
toast("Import error: " + error.toString());
});
});
} catch (e) {
@@ -520,7 +519,7 @@ const GettingStarted = (props) => {
window.location.pathname = "/login";
}
alert.info("Failed getting workflows.");
toast("Failed getting workflows.");
setWorkflowDone(true);
return;
@@ -561,7 +560,7 @@ const GettingStarted = (props) => {
}, 100)
} else {
if (isLoggedIn) {
alert.error("An error occurred while loading workflows");
toast("An error occurred while loading workflows");
}
return;
@@ -570,7 +569,7 @@ const GettingStarted = (props) => {
.catch((error) => {
setVideoViewOpen(true)
alert.error(error.toString());
toast(error.toString());
});
};
@@ -687,7 +686,7 @@ const GettingStarted = (props) => {
trigger.parameters[1].value = "webhook_" + trigger.id;
// FIXME: Add auth here?
} else {
alert.info("Something is wrong with the webhook in the copy");
toast("Something is wrong with the webhook in the copy");
}
}
@@ -805,7 +804,7 @@ const GettingStarted = (props) => {
data = sanitizeWorkflow(data);
if (data.subflows !== null && data.subflows !== undefined) {
alert.info(
toast(
"Not exporting with subflows when sanitizing. Please manually export them."
);
data.subflows = [];
@@ -832,7 +831,7 @@ const GettingStarted = (props) => {
const publishWorkflow = (data) => {
data = JSON.parse(JSON.stringify(data));
data = sanitizeWorkflow(data);
alert.info("Sanitizing and publishing " + data.name);
toast("Sanitizing and publishing " + data.name);
// This ALWAYS talks to Shuffle cloud
fetch(globalUrl + "/api/v1/workflows/" + data.id + "/publish", {
@@ -849,9 +848,9 @@ const GettingStarted = (props) => {
console.log("Status not 200 for workflow publish :O!");
} else {
if (isCloud) {
alert.success("Successfully published workflow");
toast("Successfully published workflow");
} else {
alert.success(
toast(
"Successfully published workflow to https://shuffler.io"
);
}
@@ -861,19 +860,19 @@ const GettingStarted = (props) => {
})
.then((responseJson) => {
if (responseJson.reason !== undefined) {
alert.error("Failed publishing: ", responseJson.reason);
toast("Failed publishing: ", responseJson.reason);
}
getAvailableWorkflows();
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
const copyWorkflow = (data) => {
data = JSON.parse(JSON.stringify(data));
alert.success("Copying workflow " + data.name);
toast("Copying workflow " + data.name);
data.id = "";
data.name = data.name + "_copy";
data = deduplicateIds(data);
@@ -900,7 +899,7 @@ const GettingStarted = (props) => {
}, 1000);
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
@@ -916,9 +915,9 @@ const GettingStarted = (props) => {
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for setting workflows :O!");
alert.error("Failed deleting workflow. Do you have access?");
toast("Failed deleting workflow. Do you have access?");
} else {
alert.success("Deleted workflow " + id);
toast("Deleted workflow " + id);
}
return response.json();
@@ -929,7 +928,7 @@ const GettingStarted = (props) => {
}, 1000);
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
@@ -1274,7 +1273,7 @@ const GettingStarted = (props) => {
}}
onClick={() => {
if (subflows === 0) {
alert.info("No subflows for " + data.name);
toast("No subflows for " + data.name);
return;
}
@@ -1436,9 +1435,9 @@ const GettingStarted = (props) => {
.then((responseJson) => {
if (responseJson.success === false) {
if (responseJson.reason !== undefined) {
alert.error("Error setting workflow: ", responseJson.reason)
toast("Error setting workflow: ", responseJson.reason)
} else {
alert.error("Error setting workflow.")
toast("Error setting workflow.")
}
return
@@ -1455,14 +1454,14 @@ const GettingStarted = (props) => {
setImportLoading(false);
setModalOpen(false);
} else {
alert.info("Successfully changed basic info for workflow");
toast("Successfully changed basic info for workflow");
setModalOpen(false);
}
return responseJson;
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
setImportLoading(false);
setModalOpen(false);
setSubmitLoading(false);
@@ -1478,7 +1477,7 @@ const GettingStarted = (props) => {
const file = event.target.files[key];
if (file.type !== "application/json") {
if (file.type !== undefined) {
alert.error("File has to contain valid json");
toast("File has to contain valid json");
setImportLoading(false);
}
@@ -1492,7 +1491,7 @@ const GettingStarted = (props) => {
try {
data = JSON.parse(reader.result);
} catch (e) {
alert.error("Invalid JSON: " + e);
toast("Invalid JSON: " + e);
setImportLoading(false);
return;
}
@@ -1524,13 +1523,13 @@ const GettingStarted = (props) => {
false
).then((response) => {
if (response !== undefined) {
alert.success("Successfully imported " + data.name);
toast("Successfully imported " + data.name);
}
});
}
})
.catch((error) => {
alert.error("Import error: " + error.toString());
toast("Import error: " + error.toString());
});
});
@@ -1729,7 +1728,7 @@ const GettingStarted = (props) => {
}}
onClick={() => {
if (subflows === 0) {
alert.info("No subflows for " + data.name);
toast("No subflows for " + data.name);
return;
}
@@ -1930,7 +1929,7 @@ const GettingStarted = (props) => {
margin="dense"
fullWidth
/>
<ChipInput
<MuiChipsInput
style={{ marginTop: 10 }}
InputProps={{
style: {
@@ -2170,7 +2169,7 @@ const GettingStarted = (props) => {
})
return
} else {
alert.success("TBD: Coming in version 1.0.0");
toast("TBD: Coming in version 1.0.0");
}
const ele = document.getElementById("shuffle_search_field")
@@ -2181,7 +2180,7 @@ const GettingStarted = (props) => {
ele.style.borderWidth = "2px"
} else {
//alert.success("TBD: Coming in version 1.0.0");
//toast("TBD: Coming in version 1.0.0");
}
}}>
workflows made by other creators</span>!
@@ -2435,7 +2434,7 @@ const GettingStarted = (props) => {
</Typography>
</div>
<div style={{ flex: 1, float: "right" }}>
<ChipInput
<MuiChipsInput
style={{}}
InputProps={{
style: {
@@ -2601,7 +2600,7 @@ const GettingStarted = (props) => {
parsedData["field_2"] = field2;
}
alert.success("Getting specific workflows from your URL.");
toast("Getting specific workflows from your URL.");
fetch(globalUrl + "/api/v1/workflows/download_remote", {
method: "POST",
mode: "cors",
@@ -2613,7 +2612,7 @@ const GettingStarted = (props) => {
})
.then((response) => {
if (response.status === 200) {
alert.success("Successfully loaded workflows from " + downloadUrl);
toast("Successfully loaded workflows from " + downloadUrl);
setTimeout(() => {
getAvailableWorkflows();
}, 1000);
@@ -2624,14 +2623,14 @@ const GettingStarted = (props) => {
.then((responseJson) => {
if (!responseJson.success) {
if (responseJson.reason !== undefined) {
alert.error("Failed loading: " + responseJson.reason);
toast("Failed loading: " + responseJson.reason);
} else {
alert.error("Failed loading");
toast("Failed loading");
}
}
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
-221
View File
@@ -1,221 +0,0 @@
import React, { useEffect, useState } from "react";
import { Link, useParams } from "react-router-dom";
import theme from '../theme.jsx';
import Grid from "@material-ui/core/Grid";
import Card from "@material-ui/core/Card";
import CardActionArea from "@material-ui/core/CardActionArea";
import CardContent from "@material-ui/core/CardContent";
import CardHeader from "@material-ui/core/CardHeader";
import Typography from "@material-ui/core/Typography";
import Button from "@material-ui/core/Button";
const Workflows = (defaultprops) => {
const { globalUrl, isLoggedIn, isLoaded } = defaultprops;
const params = useParams();
var props = JSON.parse(JSON.stringify(defaultprops))
props.match = {}
props.match.params = params
const [curView, setCurView] = useState(0);
const [firstrequest, setFirstrequest] = useState(true);
const [selectedItems, setSelectedItems] = useState([]);
const viewdata1 = [
{
title: "General",
content: "Learn about our ticketing solutions",
subitems: [
{
name: "Search",
subtitle: "Search for anything, anywhere",
},
{
name: "Message",
subtitle: "Read and send messages",
},
{
name: "Parse emails",
subtitle: "what",
},
],
},
{
title: "Ticketing",
subitems: [
{
name: "Search",
subtitle: "Search for anything, anywhere",
},
{
name: "Message",
subtitle: "Read and send messages",
},
{
name: "Parse emails",
subtitle: "what",
},
],
},
{
title: "Threat intel",
subitems: [
{
name: "Search",
subtitle: "Search for anything, anywhere",
},
{
name: "Message",
subtitle: "Read and send messages",
},
{
name: "Parse emails",
subtitle: "what",
},
],
},
];
if (firstrequest) {
setFirstrequest(false);
if (props.match.params.key) {
console.log("PROPS: ", props.match.params.key);
const viewitem = viewdata1.find(
(item) =>
item.title.toLowerCase() === props.match.params.key.toLowerCase()
);
if (viewitem !== undefined && viewitem !== null) {
setCurView(1);
//setSelectedItem(viewitem)
}
}
}
const cardContentStyle = {
height: "100%",
width: "100%",
padding: 40,
};
const outerGridView = {
width: "100%",
marginTop: 15,
};
const paperStyle = {
height: 300,
color: "white",
backgroundColor: theme.palette.surfaceColor,
color: "white",
cursor: "pointer",
display: "flex",
textAlign: "center",
};
const HandleSelection = (data) => {
const [selected, setSelected] = useState(false);
var baseStyle = JSON.parse(JSON.stringify(paperStyle));
if (selected) {
baseStyle.backgroundColor = "white";
baseStyle.color = "black";
}
return (
<Grid
item
xs={4}
onClick={() => {
console.log(selectedItems);
if (selected) {
const index = selectedItems.findIndex(
(item) => item.title === data.title
);
if (index >= 0) {
selectedItems.splice(index, 1);
setSelectedItems(selectedItems);
}
} else {
selectedItems.push(data);
setSelectedItems(selectedItems);
}
setSelected(!selected);
//setCurView(1)
//setSelectedItem(data)
//window.location.pathname += "/"+data.title.toLowerCase()
}}
>
<Card style={baseStyle}>
<CardActionArea style={cardContentStyle}>
<CardContent>
<Typography variant="h4">{data.title}</Typography>
</CardContent>
</CardActionArea>
</Card>
</Grid>
);
};
const view1 =
curView === 0 ? (
<div>
<Typography variant="h4">What are you interested in?</Typography>
<Grid container style={outerGridView} spacing={3}>
{viewdata1.map((data) => {
return HandleSelection(data);
})}
</Grid>
{/*
<Button variant="contained" color="primary" style={{height: 50, width: 300, margin: "auto",}} onClick={() => {
setCurView(1)
}}>
Continue
</Button>
*/}
</div>
) : null;
const view2 =
curView === 1 ? (
<div>
<Typography variant="h4">Step 2.</Typography>
{/*
<Grid container style={outerGridView} spacing={3}>
{selectedItem.subitems === undefined ? null :
selectedItem.subitems.map(data => {
return (
<Grid item xs={4}>
<Card style={paperStyle}>
<CardActionArea style={cardContentStyle}>
<CardContent>
<Typography variant="h4">
{data.name}
</Typography>
<Typography variant="body1" style={{marginTop: 10}}>
{data.subtitle}
</Typography>
</CardContent>
</CardActionArea>
</Card>
</Grid>
)
})}
</Grid>
*/}
</div>
) : null;
const baseView = (
<div style={{ maxWidth: 1024, margin: "auto", paddingTop: 50 }}>
{view1}
{view2}
</div>
);
return <div>{baseView}</div>;
};
export default Workflows;
-207
View File
@@ -1,207 +0,0 @@
import React from "react";
import Paper from "@material-ui/core/Paper";
import Button from "@material-ui/core/Button";
import Divider from "@material-ui/core/Divider";
import { BrowserView, MobileView } from "react-device-detect";
import {
Schedule as ScheduleIcon,
Web as WebIcon,
AccountTree as AccountTreeIcon,
} from "@mui/icons-material";
const bodyDivStyle = {
margin: "auto",
marginTop: "75px",
textAlign: "center",
width: "1100px",
};
const surfaceColor = "#27292D";
const boxStyle = {
flex: "1",
marginLeft: "10px",
marginRight: "10px",
height: "400px",
//backgroundColor: "#e8eaf6",
backgroundColor: surfaceColor,
textAlign: "center",
display: "flex",
flexDirection: "column",
};
const bodyTextStyle = {
color: "#ffffff",
};
const hrefStyle = {
color: "black",
textDecoration: "none",
};
// Should be different if logged in :|
const LandingPage = (props) => {
const { isLoaded } = props;
const textColor = "#8899A6";
const iconColor = "#1DA1F2";
const iconSize = "8em";
const GridLayout = (header, description, link, icon) => {
return (
<Paper style={boxStyle}>
<a href={link} style={hrefStyle}>
<div style={{ flex: "1", color: "#FFFFFF" }}>
<h2>{header}</h2>
</div>
<Divider />
<div
style={{
flex: "3",
marginLeft: "10px",
marginRight: "10px",
marginTop: "10px",
color: textColor,
}}
>
{description}
</div>
<div style={{ margin: "auto" }}>{icon}</div>
<Divider style={{ marginTop: "20px", marginBottom: "20px" }} />
<div style={{ flex: "1", color: "#f85a3e" }}>
<div style={{}}>Learn more</div>
</div>
</a>
</Paper>
);
};
const listitems = [
GridLayout(
"Simple integrations",
"Easily use others' or create your own integration",
"/docs/apps",
<Web
style={{ fontSize: iconSize, marginTop: "20px", color: iconColor }}
/>
),
GridLayout(
"Workflows",
"Access the power of automation within minutes, whether its on premise or in the cloud",
"/docs/workflows",
<AccountTree
style={{ fontSize: iconSize, marginTop: "20px", color: iconColor }}
/>
),
GridLayout(
"Realtime actions",
"Beat the clock by leveraging our realtime triggers",
"/docs/triggers",
<ScheduleIcon
style={{ fontSize: iconSize, marginTop: "20px", color: iconColor }}
/>
),
];
// The actual landing page
// <img style={{width: "400px"}} alt={"logo"} src={Default}/>
const landingpageDataBrowser = (
<div>
<div style={bodyTextStyle}>
<h1>Shuffle</h1>
<h3 style={{ color: "#8899A6" }}>
A general automation solution for Infosec and IT Professionals
</h3>
</div>
<a href="/register" style={hrefStyle}>
<Button
style={{ width: "180px", height: "50px", borderRadius: "0px" }}
variant="outlined"
color="primary"
>
Try it out
</Button>
</a>
<a href="/contact" style={hrefStyle}>
<Button
style={{ width: "180px", height: "50px", borderRadius: "0px" }}
variant="contained"
color="primary"
>
Contact
</Button>
</a>
<div style={{ display: "flex", marginTop: "100px" }}>
{listitems.map((item) => {
return <div>{item}</div>;
})}
</div>
</div>
);
const landingpageDataMobile = (
<div>
<div
style={{
color: "white",
textAlign: "center",
marginLeft: "10px",
marginRight: "10px",
}}
>
<h1>Shuffle</h1>
<h3>A general automation solution for Infosec and IT Professionals</h3>
<a href="/contact" style={hrefStyle}>
<Button
style={{ width: "220px", height: "60px", borderRadius: "0px" }}
variant="contained"
color="primary"
>
Contact
</Button>
</a>
</div>
<div
style={{ display: "flex", flexDirection: "column", marginTop: "100px" }}
>
<div>{listitems[0]}</div>
<div style={{ marginTop: "20px" }}>{listitems[1]}</div>
<div style={{ marginTop: "20px", marginBottom: "30px" }}>
{listitems[2]}
</div>
<div
style={{
marginTop: "20px",
marginBottom: "30px",
textAlign: "center",
}}
>
<a href="/contact" style={hrefStyle}>
<Button
style={{ width: "220px", height: "60px", borderRadius: "0px" }}
variant="contained"
color="primary"
>
Contact
</Button>
</a>
</div>
</div>
</div>
);
// Reroute if the user is logged in
// const landingSite = isLoggedIn ? <Workflows globalUrl={globalUrl}{...props} /> : <div style={bodyDivStyle}>{landingpageData}</div>
const landingSite = <div style={bodyDivStyle}>{landingpageDataBrowser}</div>;
const loadedCheck = isLoaded ? (
<div>
<BrowserView>{landingSite}</BrowserView>
<MobileView>{landingpageDataMobile}</MobileView>
</div>
) : (
<div></div>
);
return <div>{loadedCheck}</div>;
};
export default LandingPage;
-531
View File
@@ -1,531 +0,0 @@
import React, { useState } from "react";
import Paper from "@material-ui/core/Paper";
import Card from "@material-ui/core/Card";
import CardActionArea from "@material-ui/core/CardActionArea";
import CardMedia from "@material-ui/core/CardMedia";
import CardContent from "@material-ui/core/CardContent";
import CardActions from "@material-ui/core/CardActions";
import Button from "@material-ui/core/Button";
import Divider from "@material-ui/core/Divider";
import Grid from "@material-ui/core/Grid";
import { BrowserView, MobileView } from "react-device-detect";
import {
Schedule as ScheduleIcon,
Web as WebIcon,
AccountTree as AccountTreeIcon,
Info as InfoIcon,
ArrowForward as ArrowForwardIcon,
Create as CreateIcon,
} from "@mui/icons-material";
const bodyDivStyle = {
margin: "auto",
};
const surfaceColor = "#27292D";
const boxStyle = {
flex: "1",
marginLeft: "10px",
marginRight: "10px",
height: "400px",
//backgroundColor: "#e8eaf6",
backgroundColor: surfaceColor,
textAlign: "center",
display: "flex",
flexDirection: "column",
};
const bodyTextStyle = {
color: "#ffffff",
};
const hrefStyle = {
color: "inherit",
textDecoration: "none",
};
// Should be different if logged in :|
const LandingPage = (props) => {
const { isLoaded } = props;
const textColor = "#8899A6";
const iconColor = "#1DA1F2";
const iconSize = "8em";
const GridLayout = (header, description, link, icon) => {
return (
<Paper style={boxStyle}>
<a href={link} style={hrefStyle}>
<div style={{ flex: "1", color: "#FFFFFF" }}>
<h2>{header}</h2>
</div>
<Divider />
<div
style={{
flex: "3",
marginLeft: "10px",
marginRight: "10px",
marginTop: "10px",
color: textColor,
}}
>
{description}
</div>
<div style={{ margin: "auto" }}>{icon}</div>
<Divider style={{ marginTop: "20px", marginBottom: "20px" }} />
<div style={{ flex: "1", color: "#f85a3e" }}>
<div style={{}}>Learn more</div>
</div>
</a>
</Paper>
);
};
const listitems = [
GridLayout(
"Simple integrations",
"Easily use others' or create your own integration",
"/docs/features",
<WebIcon
style={{ fontSize: iconSize, marginTop: "20px", color: iconColor }}
/>
),
GridLayout(
"Workflows",
"Access the power of automation within minutes, whether its on premise or in the cloud",
"/docs/features",
<AccountTreeIcon
style={{ fontSize: iconSize, marginTop: "20px", color: iconColor }}
/>
),
GridLayout(
"Realtime actions",
"Beat the clock by leveraging our realtime triggers",
"/docs/features",
<ScheduleIcon
style={{ fontSize: iconSize, marginTop: "20px", color: iconColor }}
/>
),
];
// The actual landing page
// <img style={{width: "400px"}} alt={"logo"} src={Default}/>
//We start by understanding your unique environment to help identify the right thing to automate.
const secondaryColor = "rgba(167,46,87,1)";
const primaryColor = "rgba(25, 35, 94, 1)";
const paperStyle = {
flex: 1,
backgroundColor: "inherit",
cursor: "pointer",
};
const secondaryItemList = [
{
primaryText: "No time to waste",
secondaryText:
"Bring all your applications into a single view, and make them all work together flawlessly!",
image: "/images/time.jpg",
},
{
primaryText: "Get a better overview",
secondaryText:
"Don't know what's happening? We'll help you track and act on your most valuable KPI's!",
image: "/images/overview.jpg",
},
{
primaryText: "Conquer your tasks",
secondaryText:
"Get access to powerful tools and pre-made workflows to help you crush your teams daily tasks!",
image: "/images/burnout.jpg",
},
];
const [image, setImage] = useState(secondaryItemList[0].image);
const landingpageDataBrowser = (
<div>
<div
style={{
backgroundImage: "url('/images/test.jpg')",
backgroundSize: "80% 100%",
backgroundRepeat: "no-repeat",
minHeight: "100vh",
maxHeight: 1024,
}}
>
<div
style={{
textAlign: "left",
paddingTop: 135,
maxWidth: 700,
paddingLeft: "50%",
color: secondaryColor,
display: "flex",
fontSize: 25,
}}
>
<div style={{ flex: 1 }}>
<a href="/docs/about" style={hrefStyle}>
<Grid container direction="row" alignItems="center">
<Grid item>
<InfoIcon />
</Grid>
<Grid item style={{ marginLeft: 5 }}>
About
</Grid>
</Grid>
</a>
</div>
<div style={{ flex: 1 }}>
<a href="/contact" style={hrefStyle}>
<Grid container direction="row" alignItems="center">
<Grid item>
<CreateIcon />
</Grid>
<Grid item style={{ marginLeft: 5 }}>
Get in touch
</Grid>
</Grid>
</a>
</div>
<div style={{ flex: 1 }}>
<a href="/login" style={hrefStyle}>
<Button
style={{
borderRadius: 25,
height: 50,
minWidth: 200,
backgroundColor: secondaryColor,
color: "white",
}}
variant="contained"
>
Try it out <ArrowForwardIcon />
</Button>
</a>
</div>
</div>
<div
style={
(bodyTextStyle,
{
textAlign: "left",
paddingTop: "8%",
paddingLeft: "28%",
maxWidth: 430,
})
}
>
<div style={{ fontSize: 25, color: "rgba(0,0,0,0.45)" }}>Shuffle</div>
<div style={{ fontSize: 50, color: "rgba(0,0,0,0.7)" }}>
INFORMATION <div style={{ color: secondaryColor }}>OVERLOAD</div>
</div>
<div
style={{
fontSize: 20,
color: "rgba(0, 0, 0, 0.45)",
marginTop: 20,
}}
>
Everyone run into the same fundamental operational problems. Mailbox
chaos, tickets getting out of hand and a constant feeling of being
overwhelmed. The good news?{" "}
<div style={{ color: secondaryColor, marginTop: 10 }}>
Shuffle solves them.
</div>
</div>
<a href="/docs/features" style={hrefStyle}>
<Button
style={{
borderRadius: 25,
height: 50,
marginTop: 50,
width: 200,
backgroundColor: secondaryColor,
color: "white",
}}
variant="contained"
>
Learn how
</Button>
</a>
</div>
</div>
<div
style={{
minHeight: 1024,
width: "100%",
backgroundImage: "linear-gradient(to bottom right, #19235e, #19235e)",
}}
>
<div
style={{
minHeight: 1000,
paddingTop: 150,
maxWidth: 1250,
margin: "auto",
}}
>
<div
style={{
color: "rgba(255,255,255,0.8",
fontSize: 60,
marginLeft: 25,
}}
>
<b>Automation is just the beginning </b>
</div>
<div style={{ marginTop: 40, display: "flex", flexDirection: "row" }}>
<div
style={{
flex: 8,
display: "flex",
flexDirection: "column",
fontSize: 40,
}}
>
{secondaryItemList.map((data, index) => {
const color =
image === data.image
? "rgba(255,255,255,1)"
: "rgba(255,255,255,0.4)";
return (
<div
style={{
borderRadius: 15,
padding: 25,
maxWidth: 600,
height: 150,
fontSize: 40,
color: color,
cursor: "pointer",
}}
onClick={() => setImage(data.image)}
>
{data.primaryText}
<div style={{ fontSize: 22, marginTop: 10 }}>
{data.secondaryText}
</div>
</div>
);
})}
</div>
<div style={{ flex: 1 }} />
<div style={{ flex: 10, height: "100%", width: "100%" }}>
<img
src={image}
style={{
borderRadius: 15,
minHeight: "100%",
minWidth: "100%",
maxWidth: "100%",
maxHeight: "100%",
}}
/>
</div>
</div>
</div>
<div
style={{
maxWidth: 1250,
paddingTop: 100,
paddingBottom: 100,
margin: "auto",
color: "rgba(255,255,255,0.8)",
}}
>
<Divider style={{ backgroundColor: "rgba(255,255,255,0.6)" }} />
<div
style={{
marginTop: 100,
fontSize: 40,
display: "flex",
marginLeft: 100,
marginRight: 100,
}}
>
<div style={{ flex: 3 }}>
Learn more about the benefits of Shuffle
</div>
<div style={{ flex: 1 }}>
<a href="/docs/features" style={hrefStyle}>
<Button
fullWidth
style={{
borderRadius: 25,
minHeight: 50,
minWidth: 200,
backgroundColor: secondaryColor,
color: "white",
}}
variant="contained"
>
See features
</Button>
</a>
</div>
</div>
</div>
</div>
<div
style={{
textAlign: "center",
maxWidth: 1100,
minHeight: 600,
paddingTop: 100,
margin: "auto",
color: "rgba(0,0,0,1)",
}}
>
<div style={{ fontSize: 50 }}>
<b>Focus on the work that matters to you</b>
</div>
<div
style={{ fontSize: 20, color: "rgba(0,0,0,0.7)", maxWidth: "100%" }}
>
Menial tasks, scattered content, constant copy pasting, waste of
talent - <b>there's a smarter way to work.</b>
</div>
<div style={{ display: "flex", marginTop: 50 }}>
<Card
onClick={() => {
window.location.pathname = "/docs/features";
}}
style={{ flex: 1, margin: 10, textAlign: "center" }}
>
<CardActionArea>
<CardMedia title="TEST" image="/images/time.jpg" />
<CardContent>
<h3>Premade playbooks</h3>
<p>Get your automation done with minimal effort</p>
</CardContent>
</CardActionArea>
<Button size="medium" color="green">
Learn more
</Button>
</Card>
<Card
onClick={() => {
window.location.pathname = "/docs/features";
}}
style={{ flex: 1, margin: 10, textAlign: "center" }}
>
<CardActionArea>
<CardMedia title="TEST" image="/images/time.jpg" />
<CardContent>
<h3>Open frameworks</h3>
<p>Mitre Att&ck, OpenAPI and more!</p>
</CardContent>
</CardActionArea>
<Button size="medium" color="green">
Learn more
</Button>
</Card>
<Card
onClick={() => {
window.location.pathname = "/docs/features";
}}
style={{ flex: 1, margin: 10, textAlign: "center" }}
>
<CardActionArea>
<CardMedia title="TEST" image="/images/time.jpg" />
<CardContent>
<h3>Hundreds of integrations</h3>
<p>Quickly integrate your software applications</p>
</CardContent>
</CardActionArea>
<Button size="medium" color="green">
Learn more
</Button>
</Card>
<Card
style={{ flex: 1, margin: 10, textAlign: "center" }}
onClick={() => {
window.location.pathname = "/docs/features";
}}
>
<CardActionArea>
<CardMedia title="TEST" image="images/time.jpg" />
<CardContent>
<h3>Automated compliance</h3>
<p>Stuck with compliance needs you can't meet?</p>
</CardContent>
</CardActionArea>
<Button size="medium" color="green">
Learn more
</Button>
</Card>
</div>
</div>
</div>
);
const landingpageDataMobile = (
<div style={{ backgroundColor: "#1F2023", paddingTop: 30 }}>
<div
style={{
color: "white",
textAlign: "center",
marginLeft: "10px",
marginRight: "10px",
}}
>
<h1>Shuffle</h1>
<h3>A general automation solution for Infosec and IT Professionals</h3>
<a href="/contact" style={hrefStyle}>
<Button
style={{ width: "220px", height: "60px", borderRadius: "0px" }}
variant="contained"
color="primary"
>
Contact
</Button>
</a>
</div>
<div
style={{ display: "flex", flexDirection: "column", marginTop: "100px" }}
>
<div>{listitems[0]}</div>
<div style={{ marginTop: "20px" }}>{listitems[1]}</div>
<div style={{ marginTop: "20px", marginBottom: "30px" }}>
{listitems[2]}
</div>
<div
style={{
marginTop: "20px",
marginBottom: "30px",
textAlign: "center",
}}
>
<a href="/contact" style={hrefStyle}>
<Button
style={{ width: "220px", height: "60px", borderRadius: "0px" }}
variant="contained"
color="primary"
>
Contact
</Button>
</a>
</div>
</div>
</div>
);
// Reroute if the user is logged in
// const landingSite = isLoggedIn ? <Workflows globalUrl={globalUrl}{...props} /> : <div style={bodyDivStyle}>{landingpageData}</div>
const landingSite = <div style={bodyDivStyle}>{landingpageDataBrowser}</div>;
const loadedCheck = isLoaded ? (
<div>
<BrowserView>{landingSite}</BrowserView>
<MobileView>{landingpageDataMobile}</MobileView>
</div>
) : (
<div></div>
);
return <div>{loadedCheck}</div>;
};
export default LandingPage;
File diff suppressed because it is too large Load Diff
-93
View File
@@ -1,93 +0,0 @@
import React, { useState, useEffect } from "react";
import { useParams } from "react-router-dom";
import Paper from "@material-ui/core/Paper";
const bodyDivStyle = {
margin: "auto",
textAlign: "center",
width: "768px",
};
//const tmpdata = {
// "username": "frikky",
// "firstname": "fred",
// "lastname": "ode",
// "title": "topkek",
// "companyname": "company here",
// "email": "your email pls",
// "phone": "PHONE!!",
//}
// FIXME - add fetch for data fields
// FIXME - remove tmpdata
// FIXME: Use isLoggedIn :)
const Settings = (defaultprops) => {
const { globalUrl, isLoaded, surfaceColor } = defaultprops;
const params = useParams();
var props = JSON.parse(JSON.stringify(defaultprops))
props.match = {}
props.match.params = params
const [firstRequest, setFirstRequest] = useState(true);
const boxStyle = {
flex: "1",
marginLeft: "10px",
marginRight: "10px",
paddingLeft: "30px",
paddingRight: "30px",
paddingBottom: "30px",
paddingTop: "30px",
backgroundColor: surfaceColor,
color: "white",
display: "flex",
flexDirection: "column",
};
const registerCall = () => {
const url = globalUrl + "/api/v1/register/" + props.match.params.key;
fetch(url, {
method: "GET",
credentials: "include",
headers: {
"Content-Type": "application/json; charset=utf-8",
},
})
.then((response) =>
response.json().then((responseJson) => {
console.log(responseJson);
})
)
.catch((error) => {
console.log("SOMETHING WRONG");
});
};
// This should "always" have data
useEffect(() => {
if (firstRequest) {
setFirstRequest(false);
registerCall();
}
});
// Random names for type & autoComplete. Didn't research :^)
const landingpageData = (
<div style={{ display: "flex", marginTop: "80px" }}>
<Paper style={boxStyle}>
<h2>Registration verification</h2>
<p>Thanks for verifying, redirecting you to our login!</p>
</Paper>
</div>
);
const loadedCheck = isLoaded ? (
<div style={bodyDivStyle}>{landingpageData}</div>
) : (
<div></div>
);
return <div>{loadedCheck}</div>;
};
export default Settings;
-176
View File
@@ -1,176 +0,0 @@
/* eslint-disable react/no-multi-comp */
import React, { useState } from "react";
import DialogTitle from "@material-ui/core/DialogTitle";
import Dialog from "@material-ui/core/Dialog";
import TextField from "@material-ui/core/TextField";
import Button from "@material-ui/core/Button";
const LoginDialog = (props) => {
const {
classes,
onClose,
open,
globalUrl,
isLoggedIn,
setIsLoggedIn,
...other
} = props;
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
//const [selectedValue, setSelectedValue] = useState(false);
// Used to swap from login to register. True = login, false = register
const [loginCheck, setLoginCheck] = useState(true);
// Error messages etc
const [loginInfo, setLoginInfo] = useState("");
const handleValidateForm = () => {
return username.length > 1 && password.length > 8;
};
const onSubmit = (e) => {
e.preventDefault();
// Just use this one?
var data =
'{"username": "' + username + '", "password": "' + password + '"}';
var baseurl = globalUrl;
if (loginCheck) {
var url = baseurl + "/login";
fetch(url, {
method: "POST",
body: data,
headers: {
"Content-Type": "application/json",
},
})
.then((response) =>
response.json().then((responseJson) => {
console.log(responseJson);
//console.log(e)
if (responseJson["success"] === false) {
setLoginInfo(responseJson["reason"]);
} else {
setLoginInfo("Successful login :)");
onClose();
setIsLoggedIn(true);
}
})
)
.catch((error) => {
setLoginInfo("Error in userdata");
});
} else {
url = baseurl + "/register";
fetch(url, {
method: "POST",
body: data,
headers: {
"Content-Type": "application/json",
},
})
.then((response) =>
response.json().then((responseJson) => {
if (responseJson["success"] === false) {
setLoginInfo(responseJson["reason"]);
} else {
setLoginInfo("Successful register. Please check your mail :)");
onClose();
setIsLoggedIn(true);
}
})
)
.catch((error) => {
setLoginInfo("Error in userdata");
});
}
};
const onChangeUser = (e) => {
setUsername(e.target.value);
};
const onChangePass = (e) => {
setPassword(e.target.value);
};
const onClickRegister = () => {
setLoginCheck(!loginCheck);
};
//var loginChange = loginCheck ? (<div><p onClick={setLoginCheck(false)}>Want to register? Click here.</p></div>) : (<div><p onClick={setLoginCheck(true)}>Go back to login? Click here.</p></div>);
var formtitle = loginCheck ? <div>Login</div> : <div>Register</div>;
var formButton = loginCheck ? (
<div>Click to Register</div>
) : (
<div>Click to Login</div>
);
return (
<Dialog modal open={open} onClose={onClose} {...other}>
<DialogTitle>{formtitle}</DialogTitle>
<form onSubmit={onSubmit} style={{ margin: "15px 15px 15px 15px" }}>
Username
<div>
<TextField
required
id="standard-required"
autoComplete="username"
margin="normal"
variant="outlined"
onChange={onChangeUser}
/>
</div>
Password
<div>
<TextField
id="outlined-password-input"
type="password"
autoComplete="current-password"
margin="normal"
variant="outlined"
onChange={onChangePass}
/>
</div>
<div style={{ display: "flex", marginTop: "15px" }}>
<Button
color="secondary"
variant="contained"
type="submit"
style={{ flex: "1", marginRight: "5px" }}
disabled={!handleValidateForm()}
>
SUBMIT
</Button>
<Button
color="primary"
variant="contained"
type="button"
style={{ flex: "1" }}
onClick={onClose}
>
Cancel
</Button>
</div>
{loginInfo}
</form>
<div style={{ display: "flex" }}>
<Button
color="secondary"
variant="contained"
onClick={onClickRegister}
type="button"
style={{ flex: "1" }}
>
{formButton}
</Button>
</div>
</Dialog>
);
};
export default LoginDialog;
-232
View File
@@ -1,232 +0,0 @@
import React, { useEffect } from "react";
import Paper from "@material-ui/core/Paper";
import Grid from "@material-ui/core/Grid";
import ButtonBase from "@material-ui/core/ButtonBase";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
import Button from "@material-ui/core/Button";
//import Breadcrumbs from '@material-ui/core/Breadcrumbs';
const Schedules = (props) => {
const { globalUrl } = props;
//const [schedules, setSchedules] = React.useState(scheduledata);
const [schedules, setSchedules] = React.useState({});
const getAvailableSchedules = () => {
fetch(globalUrl + "/api/v1/schedules", {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
})
.then((response) => response.json())
.then((responseJson) => {
setSchedules(responseJson);
})
.catch((error) => {
console.log(error);
});
};
// FIXME - add automated redirection, as empty apps look horrible currently
const newSchedule = () => {
fetch(globalUrl + "/api/v1/schedules/new", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(),
})
.then((response) => response.json())
.then((responseJson) => {
console.log(responseJson);
setSchedules({});
})
.catch((error) => {
console.log(error);
});
};
const deleteSchedule = (id) => {
if (id === undefined) {
return;
}
fetch(globalUrl + "/api/v1/schedules/" + id + "/delete", {
method: "DELETE",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
})
.then((response) => response.json())
.then((responseJson) => {
setSchedules({});
})
.catch((error) => {
console.log(error);
});
};
// FIXME - use this?
//const getNewScheduleInfo = () => {
// fetch(globalUrl+"/api/v1/schedules", {
// method: 'GET',
// headers: {
// 'Content-Type': 'application/json',
// 'Accept': 'application/json',
// },
// })
// .then((response) => response.json())
// .then((responseJson) => {
// setSchedules(responseJson)
// })
// .catch(error => {
// console.log(error)
// });
//}
useEffect(() => {
if (Object.getOwnPropertyNames(schedules).length <= 0) {
getAvailableSchedules();
}
});
const bodyDivStyle = {
marginLeft: "20px",
marginRight: "20px",
width: "1350px",
minWidth: "1350px",
maxWidth: "1350px",
};
const scheduleApp = (app) => {
console.log(app);
return (
<Grid container spacing={2} style={{ margin: "10px 10px 10px 10px" }}>
<Grid item>
<ButtonBase>
<img alt="" style={{ width: "100px", height: "100px" }} />
</ButtonBase>
</Grid>
<Grid item xs={12} sm container>
<Grid item xs container direction="column" spacing={2}>
<Grid item xs>
<div>
<h2>{app.name}</h2>
</div>
<div>{app.description}</div>
</Grid>
<Grid item>{app.action}</Grid>
</Grid>
</Grid>
</Grid>
);
};
const splitter = (
<div
style={{
width: "1px",
backgroundColor: "grey",
margin: "5px 5px 5px 5px",
}}
/>
);
const hrefStyle = {
color: "#385f71",
textDecoration: "none",
};
// FIXME - add Schedule modal
const schedulePaper = (schedule) => {
return (
<div>
<Paper
style={{
maxWidth: "1000px",
display: "flex",
padding: "10px 10px 10px 10px",
}}
>
<div style={{ flex: "5" }}>
{scheduleApp(schedule.appinfo.sourceapp)}
</div>
<div style={{ flex: "1", alignItems: "center" }}>ARROW</div>
<div style={{ flex: "5" }}>
{scheduleApp(schedule.appinfo.destinationapp)}
</div>
{splitter}
<div style={{ flex: "1" }}>
<List style={{ backgroundColor: "#ffffff" }}>
<ListItem style={{ flex: "1", textAlign: "center" }}>
<a href={"/schedules/" + schedule.id} style={hrefStyle}>
<Button disabled={false} color="primary">
Edit
</Button>
</a>
</ListItem>
<ListItem style={{ flex: "1", textAlign: "center" }}>
<Button
disabled={false}
onClick={() => {
deleteSchedule(schedule.id);
}}
color="primary"
>
Delete
</Button>
</ListItem>
</List>
</div>
</Paper>
</div>
);
};
console.log(schedules);
console.log(schedules);
console.log(schedules.schedules);
const schedulemap =
Object.getOwnPropertyNames(schedules).length > 0 &&
schedules.schedules &&
schedules.schedules.length > 0 ? (
<div>{schedules.schedules.map((data) => schedulePaper(data))}</div>
) : (
<div style={{ marginTop: "10%", marginLeft: "50%" }}>
<Button
disabled={false}
onClick={() => {
newSchedule();
}}
variant="outlined"
color="primary"
>
CREATE NEW SCHEDULE
</Button>
</div>
);
const scheduleView =
Object.getOwnPropertyNames(schedules).length > 0 ? (
<div style={bodyDivStyle}>
<Button
disabled={false}
onClick={() => {
newSchedule();
}}
color="primary"
>
New
</Button>
{schedulemap}
</div>
) : null;
// Maybe use gridview or something, idk
return <div>{scheduleView}</div>;
};
export default Schedules;
+11 -10
View File
@@ -10,11 +10,12 @@ import {
Divider,
TextField,
} from "@mui/material";
import { useAlert } from "react-alert";
//import { useAlert
import { ToastContainer, toast } from "react-toastify"
const Settings = (props) => {
const { globalUrl, isLoaded, userdata, setUserData } = props;
const alert = useAlert();
//const alert = useAlert();
let navigate = useNavigate();
const [username, setUsername] = useState("");
@@ -142,7 +143,7 @@ const Settings = (props) => {
if (responseJson["success"] === false) {
setPasswordFormMessage(responseJson["reason"]);
} else {
alert.success("Changed password!");
toast("Changed password!");
setPasswordFormMessage("");
}
})
@@ -294,22 +295,22 @@ const Settings = (props) => {
// detectEthereumProvider().then((provider) => {
// if (provider) {
// if (!provider.isMetaMask) {
// alert.error("Only MetaMask is supported as of now.");
// toast("Only MetaMask is supported as of now.");
// return;
// }
// // Find the ethereum network
// // Get the users' account(s)
// //alert.info("Connecting to MetaMask")
// //toast("Connecting to MetaMask")
// //console.log("Connected: ", provider.isConnected())
// if (!provider.isConnected()) {
// alert.error("Metamask is not connected.");
// toast("Metamask is not connected.");
// return;
// }
// provider.on("message", (event) => {
// alert.info("Ethereum message: ", event);
// toast("Ethereum message: ", event);
// });
// provider.on("chainChanged", (chainId) => {
@@ -330,12 +331,12 @@ const Settings = (props) => {
// console.log("INFO: ", userdata);
// setUserData(userdata);
// } else {
// alert.error("Couldn't find balance: ", result);
// toast("Couldn't find balance: ", result);
// }
// })
// .catch((error) => {
// // If the request fails, the Promise will reject with an error.
// alert.error("Failed getting info from ethereum API: " + error);
// toast("Failed getting info from ethereum API: " + error);
// });
// });
// }
@@ -858,7 +859,7 @@ const Settings = (props) => {
})
.then((responseJson) => {
if (!responseJson.success && responseJson.reason !== undefined) {
alert.error("Failed updating user: " + responseJson.reason);
toast("Failed updating user: " + responseJson.reason);
}
})
.catch((error) => {
-298
View File
@@ -1,298 +0,0 @@
import React from "react";
import { Grid, Container, Divider } from "@mui/material";
import { makeStyles } from "@mui/material/styles";
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import Typography from "@material-ui/core/Typography";
import Table from "@material-ui/core/Table";
import TableBody from "@material-ui/core/TableBody";
import TableCell from "@material-ui/core/TableCell";
import TableContainer from "@material-ui/core/TableContainer";
import TableHead from "@material-ui/core/TableHead";
import TableRow from "@material-ui/core/TableRow";
import Paper from "@material-ui/core/Paper";
import { LineChart, LineSeries, BarChart } from "reaviz";
import { GridStripe } from "reaviz";
//import { GridlineSeries } from "reaviz";
import InputLabel from '@material-ui/core/InputLabel';
import FormControl from '@material-ui/core/FormControl';
import Select from '@material-ui/core/Select';
import MenuItem from '@material-ui/core/MenuItem';
const data = [
{
key: new Date("11/29/2019"),
data: 10,
},
{
key: new Date("11/30/2019"),
data: 14,
},
{
key: new Date("12/01/2019"),
data: 5,
},
{
key: new Date("12/02/2019"),
data: 18,
},
];
const useStyles1 = makeStyles((theme) => ({
formControl: {
margin: theme.spacing(1),
minWidth: 120,
},
selectEmpty: {
marginTop: theme.spacing(2),
},
}));
const useStyles = makeStyles({
table: {
minWidth: 650,
},
root: {
minWidth: 275,
},
bullet: {
display: "inline-block",
margin: "0 2px",
transform: "scale(0.8)",
},
title: {
fontSize: 14,
},
pos: {
marginBottom: 12,
},
});
function createData(name, calories, fat, carbs, protein) {
return { name, calories, fat, carbs, protein };
}
const rows = [
createData("Frozen yoghurt", 159, 6.0, 24, 4.0),
createData("Ice cream sandwich", 237, 9.0, 37, 4.3),
createData("Eclair", 262, 16.0, 24, 6.0),
createData("Cupcake", 305, 3.7, 67, 4.3),
createData("Gingerbread", 356, 16.0, 49, 3.9),
];
const DashboardPage = () => {
const classes = useStyles();
const classes1 = useStyles1();
const [age, setAge] = React.useState(0);
const handleChange = (event) => {
setAge(event.target.value);
};
return (
<Container maxWidth="xl">
<Grid>
<Grid item xl={8} style={{"border":"20px"}}>
<center>
<Typography type="title" variant="h1" color="inherit">
Dashboard
</Typography>
<div style={{
"paddingLeft": "50px"
}}>
<FormControl className={classes1.formControl}>
<InputLabel id="demo-simple-select-label">Organization</InputLabel>
<Select
labelId="demo-simple-select-label"
onChange={handleChange}
>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
</FormControl>
</div>
</center>
</Grid>
<Divider />
</Grid>
<Grid
container
spacing={2}
style={{
maxWidth: "1250px",
margin: "auto auto 10px",
padding: "10px",
}}
>
<Grid item xs={4}>
<Card
className={classes.root}
style={{
color: "white",
backgroundColor: "RGB(31, 32, 36)",
height: "100px",
border: "2px solid rgb(197, 17, 82)",
}}
>
<CardContent>
<Typography
className={classes.title}
color="textSecondary"
gutterBottom
>
Total workflows executions
</Typography>
<Typography
variant="h1"
className={classes.pos}
color="textSecondary"
>
456
</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={4}>
<Card
className={classes.root}
style={{
color: "white",
backgroundColor: "RGB(31, 32, 36)",
height: "100px",
border: "2px solid rgb(244, 194, 13)",
}}
>
<CardContent>
<Typography
className={classes.title}
color="textSecondary"
gutterBottom
>
Total Apps executions
</Typography>
<Typography
variant="h1"
className={classes.pos}
color="textSecondary"
>
587
</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={4}>
<Card
className={classes.root}
style={{
color: "white",
backgroundColor: "RGB(31, 32, 36)",
height: "100px",
border: "2px solid rgb(72, 133, 237)",
}}
>
<CardContent>
<Typography
className={classes.title}
color="textSecondary"
gutterBottom
>
Total failed executions
</Typography>
<Typography
variant="h1"
className={classes.pos}
color="textSecondary"
>
999
</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
<Grid
container
spacing={3}
style={{
maxWidth: "1250px",
margin: "auto auto 10px",
color: "white",
backgroundColor: "rgb(39, 41, 45)",
padding: "20px",
}}
>
<Grid item md={6}>
<BarChart width={600} height={400} data={data} />
</Grid>
<Grid item md={6}>
<LineChart
width={600}
height={400}
data={data}
line={<GridStripe fill={"a#393c3e"} />}
series={<LineSeries symbols={null} />}
/>
</Grid>
</Grid>
<Grid
container
spacing={1}
style={{
maxWidth: "1250px",
margin: "auto auto 10px",
color: "white",
backgroundColor: "rgb(39, 41, 45)",
padding: "20px",
}}
>
<Grid item md={12}>
<TableContainer
component={Paper}
style={{
color: "white",
backgroundColor: "rgb(39, 41, 45) ",
}}
>
<Table
className={classes.table}
size="small"
aria-label="a dense table"
>
<TableHead>
<TableRow>
<TableCell>Dessert (100g serving)</TableCell>
<TableCell align="right">Calories</TableCell>
<TableCell align="right">Fat&nbsp;(g)</TableCell>
<TableCell align="right">Carbs&nbsp;(g)</TableCell>
<TableCell align="right">Protein&nbsp;(g)</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows.map((row) => (
<TableRow key={row.name}>
<TableCell component="th" scope="row">
{row.name}
</TableCell>
<TableCell align="right">{row.calories}</TableCell>
<TableCell align="right">{row.fat}</TableCell>
<TableCell align="right">{row.carbs}</TableCell>
<TableCell align="right">{row.protein}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Grid>
</Grid>
</Container>
);
};
export default DashboardPage;
+5 -4
View File
@@ -7,7 +7,8 @@ import {
Button,
} from "@mui/material";
import theme from '../theme.jsx';
import { useAlert } from "react-alert";
//import { useAlert
import { ToastContainer, toast } from "react-toastify"
import AuthenticationOauth2 from "../components/Oauth2Auth.jsx";
import AuthenticationWindow from "../components/AuthenticationWindow.jsx";
@@ -22,7 +23,7 @@ const SetAuthentication = (props) => {
const [appAuthentication, setAppAuthentication] = React.useState([]);
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
const alert = useAlert();
//const alert = useAlert();
const parseIncomingOpenapiData = (data) => {
if (data.app === undefined || data.app === null) {
@@ -84,7 +85,7 @@ const SetAuthentication = (props) => {
})
.then((responseJson) => {
if (responseJson.success === false || responseJson.success === undefined) {
alert.error("Failed to get the app. Does it exist?")
toast("Failed to get the app. Does it exist?")
setIsAppLoaded(true)
return;
}
@@ -92,7 +93,7 @@ const SetAuthentication = (props) => {
parseIncomingOpenapiData(responseJson);
})
.catch((error) => {
alert.error("Error in app fetch: " + error.toString());
toast("Error in app fetch: " + error.toString());
});
};
-316
View File
@@ -1,316 +0,0 @@
import React, { useEffect } from "react";
import Paper from "@material-ui/core/Paper";
import Grid from "@material-ui/core/Grid";
import ButtonBase from "@material-ui/core/ButtonBase";
import Button from "@material-ui/core/Button";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
import TextField from "@material-ui/core/TextField";
import Select from "@material-ui/core/Select";
import MenuItem from "@material-ui/core/MenuItem";
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 WebhookImage from "../assets/img/webhook.png";
import KafkaImage from "../assets/img/kafka.png";
const Webhooks = (props) => {
const { globalUrl, isLoaded } = props;
const validtypes = ["webhook"];
//const [hooks, setSchedules] = React.useState(hookdata);
const [hooks, setHooks] = React.useState([]);
const [modalOpen, setModalOpen] = React.useState(false);
const [newHookName, setNewHookName] = React.useState("");
const [newHookDescription, setNewHookDescription] = React.useState("");
const [newHookType, setNewHookType] = React.useState("");
const [firstrequest, setFirstrequest] = React.useState(true);
const [, setModalError] = React.useState("");
useEffect(() => {
if (firstrequest) {
setFirstrequest(false);
getAvailableHooks();
}
});
const newHook = () => {
if (newHookName.length === 0) {
setModalError("Missing name in modal");
return;
}
if (!validtypes.includes(newHookType)) {
setModalError(
newHookType + " is not a valid type. Try this: " + validtypes
);
}
fetch(globalUrl + "/api/v1/hooks/new", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
name: newHookName,
description: newHookDescription,
type: newHookType,
}),
credentials: "include",
})
.then((response) => response.json())
.then((responseJson) => {
console.log(responseJson);
setHooks([]);
})
.catch((error) => {
console.log(error);
});
};
const getAvailableHooks = () => {
fetch(globalUrl + "/api/v1/hooks", {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => response.json())
.then((responseJson) => {
setHooks(responseJson);
})
.catch((error) => {
console.log(error);
// window.location.pathname = "/"
});
};
const deleteHook = (id) => {
if (id === undefined) {
return;
}
fetch(globalUrl + "/api/v1/hooks/" + id + "/delete", {
method: "DELETE",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => response.json())
.then((responseJson) => {
setHooks([]);
})
.catch((error) => {
console.log(error);
});
};
const bodyDivStyle = {
marginLeft: "20px",
marginRight: "20px",
width: "1350px",
minWidth: "1350px",
maxWidth: "1350px",
};
const hookApp = (app) => {
// Might be more options, but should be webhook or MQ
const appPicture =
app.type === "webhook" ? (
<img src={WebhookImage} alt="webhook" width="100px" height="100px" />
) : (
<img src={KafkaImage} alt="MQ" width="100px" height="100px" />
);
return (
<Grid container spacing={2} style={{ margin: "10px 10px 10px 10px" }}>
<Grid item style={{ marginRight: "10px" }}>
<ButtonBase>{appPicture}</ButtonBase>
</Grid>
{splitter}
<Grid item xs={12} sm container style={{ marginLeft: "10px" }}>
<Grid item xs container direction="column" spacing={2}>
<Grid item xs>
<div>
<h2>{app.info.name}</h2>
</div>
<div>Desc: {app.info.description}</div>
<div>Status: {app.status}</div>
</Grid>
<Grid item>{app.action}</Grid>
</Grid>
</Grid>
</Grid>
);
};
const splitter = (
<div
style={{
width: "1px",
backgroundColor: "grey",
margin: "5px 5px 5px 5px",
}}
/>
);
const hrefStyle = {
color: "#385f71",
textDecoration: "none",
};
// FIXME - add Schedule modal
const hookPaper = (hook) => {
return (
<div>
<Paper
style={{
maxWidth: "500px",
display: "flex",
padding: "10px 10px 10px 10px",
marginTop: "10px",
}}
>
<div style={{ flex: "5" }}>{hookApp(hook)}</div>
{splitter}
<div style={{ flex: "1" }}>
<List style={{ backgroundColor: "#ffffff" }}>
<ListItem style={{ flex: "1", textAlign: "center" }}>
<a href={"/webhooks/" + hook.id} style={hrefStyle}>
<Button disabled={false} color="primary">
Edit
</Button>
</a>
</ListItem>
<ListItem style={{ flex: "1", textAlign: "center" }}>
<Button
disabled={false}
onClick={() => {
deleteHook(hook.id);
}}
color="primary"
>
Delete
</Button>
</ListItem>
</List>
</div>
</Paper>
</div>
);
};
const modalView = modalOpen ? (
<Dialog
modal
open={modalOpen}
onClose={() => {
setModalOpen(false);
}}
>
<DialogTitle>Hook configuration</DialogTitle>
<DialogContent>
<TextField
onChange={(event) => {
setNewHookName(event.target.value);
}}
color="primary"
placeholder="Name"
margin="dense"
fullWidth
/>
<TextField
onChange={(event) => {
setNewHookDescription(event.target.value);
}}
color="primary"
placeholder="Description"
margin="dense"
fullWidth
/>
<Select
value={newHookType}
onChange={(event) => {
setNewHookType(event.target.value);
}}
fullWidth="true"
>
{validtypes.map((data) => (
<MenuItem value={data}>{data}</MenuItem>
))}
</Select>
</DialogContent>
<DialogActions>
<Button onClick={() => setModalOpen(false)} color="primary">
Cancel
</Button>
<Button
disabled={
newHookName.length === 0 || !validtypes.includes(newHookType)
}
onClick={() => {
newHook();
setModalOpen(false);
}}
color="primary"
>
Submit
</Button>
</DialogActions>
</Dialog>
) : null;
const hookmap =
hooks.length > 0 ? (
<div>{hooks.map((data) => hookPaper(data))}</div>
) : (
<div style={{ marginTop: "10%", marginLeft: "50%" }}>
<Button
disabled={false}
onClick={() => {
setModalOpen(true);
}}
variant="outlined"
color="primary"
>
CREATE NEW HOOK
</Button>
</div>
);
const hookView = (
<div style={bodyDivStyle}>
<Button
disabled={false}
onClick={() => {
setModalOpen(true);
}}
color="primary"
>
New
</Button>
{hookmap}
</div>
);
const loadedCheck = isLoaded ? (
<div>
{modalView}
{hookView}
</div>
) : (
<div></div>
);
// Maybe use gridview or something, idk
return <div>{loadedCheck}</div>;
};
export default Webhooks;
+16 -14
View File
@@ -1,23 +1,25 @@
import React, { useState, useEffect } from 'react';
import ReactGA from 'react-ga4';
import WelcomeForm2 from "../components/WelcomeForm2.jsx";
import {
Stepper,
Step,
StepLabel,
} from '@mui/material';
import AppFramework from "../components/AppFramework.jsx";
import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
import {
ArrorForwardIos as ArrowForwardIosIcon,
} from '@mui/icons-material';
import {
Grid,
Container,
Collapse,
Fade,
Typography,
Paper,
Button,
Card,
CardContent,
CardActionArea,
Stepper,
Step,
StepLabel,
} from '@mui/material';
import theme from '../theme.jsx';
import { useNavigate, Link } from "react-router-dom";
@@ -193,9 +195,9 @@ const Welcome = (props) => {
setFrameworkData({})
if (responseJson.reason !== undefined) {
//alert.error("Failed loading: " + responseJson.reason)
//toast("Failed loading: " + responseJson.reason)
} else {
//alert.error("Failed to load framework for your org.")
//toast("Failed to load framework for your org.")
}
} else {
setFrameworkData(responseJson)
@@ -417,7 +419,7 @@ const Welcome = (props) => {
<Typography variant="h6" style={{textAlign: "center", marginBottom: 25, }}>
App Framework
</Typography>
<Collapse>
<Fade>
<AppFramework
inputUsecase={inputUsecase}
frameworkData={frameworkData}
@@ -428,20 +430,20 @@ const Welcome = (props) => {
isLoggedIn={true}
globalUrl={globalUrl}
size={0.78}
color={theme.palette.platformColor}
color={theme.palette.backgroundColor}
discoveryWrapper={discoveryWrapper}
setDiscoveryWrapper={setDiscoveryWrapper}
apps={apps}
inputUsecases={usecases}
setInputUsecases={setUsecases}
/>
</Collapse>
</Fade>
</div>
}
</Grid>
</div>
:
<Collapse in={true}>
<Fade in={true}>
<div style={{maxWidth: 700, margin: "auto", marginTop: 50, }}>
{/*
<div style={{display:"flex"}}>
@@ -537,7 +539,7 @@ const Welcome = (props) => {
</Typography>
</div>
</div>
</Collapse>
</Fade>
}
</div>
)
+340 -149
View File
@@ -3,12 +3,8 @@ import ReactDOM from "react-dom"
import { makeStyles } from "@mui/styles";
import { Navigate } from "react-router-dom";
//import { Redirect } from "react-router-dom";
import SecurityFramework from '../components/SecurityFramework.jsx';
import EditWorkflow from "../components/EditWorkflow.jsx"
//import { ShepherdTour, ShepherdTourContext } from 'react-shepherd'
import Priority from "../components/Priority.jsx";
import { isMobile } from "react-device-detect"
@@ -32,7 +28,6 @@ import {
MenuItem,
Chip,
Typography,
Zoom,
CircularProgress,
Dialog,
DialogTitle,
@@ -41,11 +36,10 @@ import {
Checkbox,
LinearProgress,
ListItemText,
} from "@mui/material"
import {
AvatarGroup,
} from "@mui/material"
Zoom,
} from "@mui/material";
import {
GridOn as GridOnIcon,
@@ -81,24 +75,17 @@ import {
ArrowRight as ArrowRightIcon,
} from "@mui/icons-material";
//import NestedMenuItem from "material-ui-nested-menu-item";
//import {Search as SearchIcon, ArrowUpward as ArrowUpwardIcon, Visibility as VisibilityIcon, Close as CloseIcon, Error as ErrorIcon, FindReplace as FindreplaceIcon, ArrowLeft as ArrowLeftIcon, Cached as CachedIcon, DirectionsRun as DirectionsRunIcon, Add as AddIcon, Polymer as PolymerIcon, FormatListNumbered as FormatListNumberedIcon, Create as CreateIcon, PlayArrow as PlayArrowIcon, AspectRatio as AspectRatioIcon, MoreVert as MoreVertIcon, Apps as AppsIcon, Schedule as ScheduleIcon, FavoriteBorder as FavoriteBorderIcon, Pause as PauseIcon, Delete as DeleteIcon, AddCircleOutline as AddCircleOutlineIcon, Save as SaveIcon, KeyboardArrowLeft as KeyboardArrowLeftIcon, KeyboardArrowRight as KeyboardArrowRightIcon, ArrowBack as ArrowBackIcon, Settings as SettingsIcon, LockOpen as LockOpenIcon, ExpandMore as ExpandMoreIcon, VpnKey as VpnKeyIcon} from '@material-ui/icons';
//https://next.material-ui.com/components/material-icons/
import { DataGrid, GridToolbar } from "@mui/x-data-grid";
//import JSONPretty from 'react-json-pretty';
//import JSONPrettyMon from 'react-json-pretty/dist/monikai'
import Dropzone from "../components/Dropzone.jsx";
import { useNavigate, Link } from "react-router-dom";
import { useAlert } from "react-alert";
import ChipInput from "material-ui-chip-input";
//import { useAlert
import { ToastContainer, toast } from "react-toastify"
import { MuiChipsInput } from "mui-chips-input";
import { v4 as uuidv4 } from "uuid";
import theme from "../theme.jsx";
const inputColor = "#383B40";
const surfaceColor = "#27292D";
const svgSize = 24;
const imagesize = 22;
@@ -409,16 +396,16 @@ export const validateJson = (showResult) => {
}
if (typeof showResult === "object" || typeof showResult === "array") {
return {
valid: true,
result: showResult,
}
return {
valid: true,
result: showResult,
}
}
if (showResult[0] === "\"") {
return {
valid: false,
result: showResult,
return {
valid: false,
result: showResult,
}
}
@@ -427,10 +414,10 @@ export const validateJson = (showResult) => {
if (!showResult.includes("{") && !showResult.includes("[")) {
jsonvalid = false
return {
valid: jsonvalid,
result: showResult,
};
return {
valid: jsonvalid,
result: showResult,
};
}
} catch (e) {
showResult = showResult.split("'").join('"');
@@ -543,7 +530,7 @@ const Workflows = (props) => {
document.title = "Shuffle - Workflows";
let navigate = useNavigate();
const alert = useAlert();
//const alert = useAlert();
const classes = useStyles(theme);
const imgSize = 60;
@@ -780,7 +767,7 @@ const Workflows = (props) => {
}}
PaperProps={{
style: {
backgroundColor: surfaceColor,
backgroundColor: theme.palette.surfaceColor,
color: "white",
minWidth: 500,
padding: 30,
@@ -835,7 +822,7 @@ const Workflows = (props) => {
}}
PaperProps={{
style: {
backgroundColor: surfaceColor,
backgroundColor: theme.palette.surfaceColor,
color: "white",
minWidth: 500,
padding: 50,
@@ -892,7 +879,7 @@ const Workflows = (props) => {
}}
PaperProps={{
style: {
backgroundColor: surfaceColor,
backgroundColor: theme.palette.surfaceColor,
color: "white",
minWidth: 500,
},
@@ -943,7 +930,7 @@ const Workflows = (props) => {
const files = isDropzone ? e.dataTransfer.files : e.target.files;
const reader = new FileReader();
alert.info("Starting upload. Please wait while we validate the workflows");
toast("Starting upload. Please wait while we validate the workflows");
try {
reader.addEventListener("load", (e) => {
@@ -952,7 +939,7 @@ const Workflows = (props) => {
try {
data = JSON.parse(reader.result);
} catch (e) {
alert.error("Invalid JSON: " + e);
toast("Invalid JSON: " + e);
return;
}
@@ -986,13 +973,13 @@ const Workflows = (props) => {
data.status
).then((response) => {
if (response !== undefined) {
alert.success(`Successfully imported ${data.name}`);
toast(`Successfully imported ${data.name}`);
}
});
}
})
.catch((error) => {
alert.error("Import error: " + error.toString());
toast("Import error: " + error.toString());
});
});
} catch (e) {
@@ -1030,9 +1017,9 @@ const Workflows = (props) => {
if (responseJson.success === false) {
setAppFramework({})
if (responseJson.reason !== undefined) {
//alert.error("Failed loading: " + responseJson.reason)
//toast("Failed loading: " + responseJson.reason)
} else {
//alert.error("Failed to load framework for your org.")
//toast("Failed to load framework for your org.")
}
} else {
setAppFramework(responseJson)
@@ -1060,7 +1047,7 @@ const Workflows = (props) => {
// navigate("/search?tab=workflows")
//}
alert.info("Failed getting workflows. Are you logged in?");
toast("Failed getting workflows. Are you logged in?");
return;
}
@@ -1125,14 +1112,14 @@ const Workflows = (props) => {
} else {
if (isLoggedIn) {
alert.error("An error occurred while loading workflows");
toast("An error occurred while loading workflows");
}
return;
}
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
@@ -1182,7 +1169,7 @@ const Workflows = (props) => {
setUsecases(newcategories)
} else {
setUsecases(categorydata)
setUsecases(categorydata)
}
}
@@ -1212,7 +1199,7 @@ const Workflows = (props) => {
}
})
.catch((error) => {
//alert.error("ERROR: " + error.toString());
//toast("ERROR: " + error.toString());
console.log("ERROR: " + error.toString());
setWorkflows(workflows);
setWorkflowDone(true);
@@ -1253,7 +1240,7 @@ const Workflows = (props) => {
width: "100%",
height: "250px",
color: "white",
backgroundColor: surfaceColor,
backgroundColor: theme.palette.surfaceColor,
display: "flex",
flexDirection: "column",
};
@@ -1271,19 +1258,19 @@ const Workflows = (props) => {
overflow: "hidden",
width: "100%",
color: "white",
backgroundColor: surfaceColor,
padding: "12px 12px 0px 15px",
borderRadius: 5,
display: "flex",
boxSizing: "border-box",
position: "relative",
padding: "12px 12px 0px 15px",
backgroundColor: theme.palette.surfaceColor,
};
const gridContainer = {
height: "auto",
color: "white",
margin: "10px",
backgroundColor: surfaceColor,
backgroundColor: theme.palette.surfaceColor,
};
const workflowActionStyle = {
@@ -1302,7 +1289,7 @@ const Workflows = (props) => {
}, i * 200);
}
alert.info(`exporting and keeping original for all ${allWorkflows.length} workflows`);
toast(`exporting and keeping original for all ${allWorkflows.length} workflows`);
};
const deduplicateIds = (data, skip_sanitize) => {
@@ -1343,7 +1330,7 @@ const Workflows = (props) => {
trigger.parameters[1].value = "webhook_" + trigger.id;
// FIXME: Add auth here?
} else {
alert.info("Something is wrong with the webhook in the copy");
toast("Something is wrong with the webhook in the copy");
}
}
@@ -1463,7 +1450,7 @@ const Workflows = (props) => {
data = sanitizeWorkflow(data);
if (data.subflows !== null && data.subflows !== undefined) {
alert.info(
toast(
"Not exporting with subflows when sanitizing. Please manually export them."
);
data.subflows = [];
@@ -1491,7 +1478,7 @@ const Workflows = (props) => {
const publishWorkflow = (data) => {
data = JSON.parse(JSON.stringify(data));
data = sanitizeWorkflow(data);
alert.info("Sanitizing and publishing " + data.name);
toast("Sanitizing and publishing " + data.name);
// This ALWAYS talks to Shuffle cloud
fetch(globalUrl + "/api/v1/workflows/" + data.id + "/publish", {
@@ -1508,9 +1495,9 @@ const Workflows = (props) => {
console.log("Status not 200 for workflow publish :O!");
} else {
if (isCloud) {
alert.success("Successfully published workflow");
toast("Successfully published workflow");
} else {
alert.success(
toast(
"Successfully published workflow to https://shuffler.io"
);
}
@@ -1520,20 +1507,20 @@ const Workflows = (props) => {
})
.then((responseJson) => {
if (responseJson.reason !== undefined) {
alert.error("Failed publishing: ", responseJson.reason);
toast("Failed publishing: ", responseJson.reason);
}
getAvailableWorkflows();
})
.catch((error) => {
alert.error("Failed publishing: is the workflow valid? Remember to save the workflow first.")
toast("Failed publishing: is the workflow valid? Remember to save the workflow first.")
console.log(error.toString());
});
};
const copyWorkflow = (data) => {
data = JSON.parse(JSON.stringify(data));
alert.success("Copying workflow " + data.name);
toast("Copying workflow " + data.name);
data.id = "";
data.name = data.name + "_copy";
data = deduplicateIds(data, true);
@@ -1560,7 +1547,7 @@ const Workflows = (props) => {
}, 1000);
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
@@ -1576,9 +1563,9 @@ const Workflows = (props) => {
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for setting workflows :O!");
alert.error("Failed deleting workflow. Do you have access?");
toast("Failed deleting workflow. Do you have access?");
} else {
alert.success("Deleted workflow " + id);
toast("Deleted workflow " + id);
}
return response.json();
@@ -1589,7 +1576,7 @@ const Workflows = (props) => {
}, 1000);
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
@@ -1606,6 +1593,7 @@ const Workflows = (props) => {
maxWidth: "100%",
minWidth: paperAppStyle.width,
color: innerColor,
padding: paperAppStyle.padding,
borderRadius: paperAppStyle.borderRadius,
display: "flex",
boxSizing: "border-box",
@@ -1613,12 +1601,10 @@ const Workflows = (props) => {
border: `2px solid ${innerColor}`,
cursor: "pointer",
backgroundColor: hover ? "rgba(39,41,45,0.5)" : "rgba(39,41,45,1)",
padding: paperAppStyle.padding,
};
return (
<Grid item xs={isMobile ? 12 : 4} style={{ }}>
<Grid item xs={isMobile ? 12 : 4} style={{ padding: "12px 10px 12px 10px" }}>
<Paper
square
style={setupPaperStyle}
@@ -1711,7 +1697,7 @@ const Workflows = (props) => {
}}
>
<MenuItem
style={{ backgroundColor: inputColor, color: "white" }}
style={{ backgroundColor: theme.palette.inputColor, color: "white" }}
onClick={(event) => {
event.stopPropagation()
ReactDOM.unstable_batchedUpdates(() => {
@@ -1735,7 +1721,7 @@ const Workflows = (props) => {
{"Edit details"}
</MenuItem>
<MenuItem
style={{ backgroundColor: inputColor, color: "white" }}
style={{ backgroundColor: theme.palette.inputColor, color: "white" }}
onClick={() => {
setSelectedWorkflow(data);
setPublishModalOpen(true);
@@ -1746,7 +1732,7 @@ const Workflows = (props) => {
{"Publish Workflow"}
</MenuItem>
<MenuItem
style={{ backgroundColor: inputColor, color: "white" }}
style={{ backgroundColor: theme.palette.inputColor, color: "white" }}
onClick={() => {
copyWorkflow(data);
setOpen(false);
@@ -1756,15 +1742,8 @@ const Workflows = (props) => {
<FileCopyIcon style={{ marginLeft: 0, marginRight: 8 }} />
{"Duplicate Workflow"}
</MenuItem>
{/*<NestedMenuItem disabled={userdata.orgs === undefined || userdata.orgs === null || userdata.orgs.length === 1 || userdata.orgs.length >= 0} style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
//copyWorkflow(data)
//setOpen(false)
}} key={"duplicate"}>
<FileCopyIcon style={{marginLeft: 0, marginRight: 8}}/>
{"Copy to Child Org"}
</NestedMenuItem>*/}
<MenuItem
style={{ backgroundColor: inputColor, color: "white" }}
style={{ backgroundColor: theme.palette.inputColor, color: "white" }}
onClick={() => {
setExportModalOpen(true);
@@ -1818,7 +1797,7 @@ const Workflows = (props) => {
{"Export Workflow"}
</MenuItem>
<MenuItem
style={{ backgroundColor: inputColor, color: "white" }}
style={{ backgroundColor: theme.palette.inputColor, color: "white" }}
onClick={() => {
setDeleteModalOpen(true);
setSelectedWorkflowId(data.id);
@@ -1910,7 +1889,7 @@ const Workflows = (props) => {
}
return (
<div style={{width: "100%", position: "relative",}}>
<div style={{width: "100%", position: "relative",}}>
<Paper square style={paperAppStyle}>
{selectedCategory !== "" ?
<Tooltip title={`Usecase Category: ${selectedCategory}`} placement="bottom">
@@ -2054,7 +2033,7 @@ const Workflows = (props) => {
}}
onClick={() => {
if (subflows === 0) {
alert.info("No subflows for " + data.name);
toast("No subflows for " + data.name);
return;
}
@@ -2229,9 +2208,9 @@ const Workflows = (props) => {
.then((responseJson) => {
if (responseJson.success === false) {
if (responseJson.reason !== undefined) {
alert.error("Error setting workflow: ", responseJson.reason)
toast("Error setting workflow: ", responseJson.reason)
} else {
alert.error("Error setting workflow.")
toast("Error setting workflow.")
}
return
@@ -2249,14 +2228,14 @@ const Workflows = (props) => {
setSubmitLoading(false)
setModalOpen(false);
} else {
//alert.info("Successfully changed basic info for workflow");
//toast("Successfully changed basic info for workflow");
setModalOpen(false);
}
return responseJson;
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
setSubmitLoading(false)
setModalOpen(false);
setSubmitLoading(false);
@@ -2273,7 +2252,7 @@ const Workflows = (props) => {
const file = event.target.files[key];
if (file.type !== "application/json") {
if (file.type !== undefined) {
alert.error("File has to contain valid json");
toast("File has to contain valid json");
setSubmitLoading(false)
}
@@ -2287,7 +2266,7 @@ const Workflows = (props) => {
try {
data = JSON.parse(reader.result);
} catch (e) {
alert.error("Invalid JSON: " + e);
toast("Invalid JSON: " + e);
setSubmitLoading(false)
return;
}
@@ -2327,13 +2306,13 @@ const Workflows = (props) => {
data.status,
).then((response) => {
if (response !== undefined) {
alert.success("Successfully imported " + data.name);
toast("Successfully imported " + data.name);
}
});
}
})
.catch((error) => {
alert.error("Import error: " + error.toString());
toast("Import error: " + error.toString());
});
});
@@ -2557,7 +2536,7 @@ const Workflows = (props) => {
}}
onClick={() => {
if (subflows === 0) {
alert.info("No subflows for " + data.name);
toast("No subflows for " + data.name);
return;
}
@@ -2705,7 +2684,7 @@ const Workflows = (props) => {
}}
PaperProps={{
style: {
backgroundColor: surfaceColor,
backgroundColor: theme.palette.surfaceColor,
color: "white",
minWidth: isMobile ? "90%" : "800px",
maxWidth: isMobile ? "90%" : "800px",
@@ -2761,7 +2740,7 @@ const Workflows = (props) => {
fullWidth
/>
<div style={{display: "flex", marginTop: 10, }}>
<ChipInput
<MuiChipsInput
style={{ flex: 1}}
InputProps={{
style: {
@@ -2823,7 +2802,7 @@ const Workflows = (props) => {
selectedUsecases.push(subcase.name)
}
setUpdate(Math.random());
setUpdate(Math.random());
setSelectedUsecases(selectedUsecases)
}}>
<Checkbox style={{color: selectedUsecases.includes(subcase.name) ? usecase.color : theme.palette.inputColor}} checked={selectedUsecases.includes(subcase.name)} />
@@ -3067,12 +3046,153 @@ const Workflows = (props) => {
</span>
);
// const tourOptions = {
// defaultStepOptions: {
// classes: "shadow-md bg-purple-dark",
// scrollTo: true
// },
// useModalOverlay: true,
// tourName: workflows,
// exitOnEsc: true,
// }
// //classes: "custom-class-name-1 custom-class-name-2",
// const newSteps = [
// {
// id: "intro",
// scrollTo: true,
// beforeShowPromise: function() {
// return new Promise(function(resolve) {
// setTimeout(function() {
// window.scrollTo(0, 0);
// resolve();
// }, 500);
// });
// },
// buttons: [
// {
// classes: "shepherd-button-primary",
// style: {
// backgroundColor: "red",
// color: "white",
// },
// text: "Next",
// type: "next"
// }
// ],
// highlightClass: "highlight",
// showCancelLink: true,
// text: [
// "React-Shepherd is a JavaScript library for guiding users through your React app."
// ],
// when: {
// show: () => {
// console.log("show step 1");
// },
// hide: () => {
// console.log("hide step 1");
// }
// }
// },
// {
// id: "second",
// attachTo: {
// element: "second-step",
// on: "top"
// },
// text: [
// "Yuk eksplorasi hasil Tes Minat Bakat-mu dan rekomendasi <b>Jurusan</b> dan Karier."
// ],
// buttons: [
// {
// classes: "btn btn-info",
// text: "Kembali",
// type: "back"
// },
// {
// classes: "btn btn-success",
// text: "Saya Mengerti",
// type: "cancel"
// }
// ],
// when: {
// show: () => {
// console.log("show stepp");
// },
// hide: () => {
// console.log("complete step");
// }
// },
// showCancelLink: false,
// scrollTo: true,
// modalOverlayOpeningPadding: 4,
// useModalOverlay: false,
// canClickTarget: false
// }
// ]
// function TourButton() {
// const tour = useContext(ShepherdTourContext);
// return (
// <Button variant="contained" color="primary" onClick={tour.start}>
// Start Tour
// </Button>
// );
// }
const WorkflowView = () => {
if (workflows.length === 0) {
// Not going there yet
//if ((userdata.tutorials !== undefined && userdata.tutorials !== null && !userdata.tutorials.includes("getting-started")) || userdata.tutorials === null) {
// return <Navigate to="/getting-started" replace />;
//}
// Not going there yet
//if ((userdata.tutorials !== undefined && userdata.tutorials !== null && !userdata.tutorials.includes("getting-started")) || userdata.tutorials === null) {
// return <Navigate to="/getting-started" replace />;
//}
//return (
// <div style={emptyWorkflowStyle}>
// <Paper style={boxStyle}>
// <div>
// <h2>Welcome to Shuffle</h2>
// </div>
// <div>
// <p>
// <b>Shuffle</b> 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.{" "}
// <a
// href="/docs/about"
// style={{ textDecoration: "none", color: "#f85a3e" }}
// >
// Click here to learn more.
// </a>
// </p>
// </div>
// <div>
// If you want to jump straight into it, click here to create your
// first workflow:
// </div>
// <div style={{ display: "flex" }}>
// <Button
// id="second-step"
// color="primary"
// style={{ marginTop: "20px" }}
// variant="outlined"
// onClick={() => setModalOpen(true)}
// >
// New workflow
// </Button>
// <span style={{ paddingTop: 20, display: "flex" }}>
// <Typography
// style={{ marginTop: 5, marginLeft: 30, marginRight: 15 }}
// >
// ..OR
// </Typography>
// {workflowButtons}
// </span>
// </div>
// </Paper>
// </div>
//)
}
var workflowDelay = -150
@@ -3084,40 +3204,104 @@ const Workflows = (props) => {
<div style={workflowViewStyle}>
<div style={{ display: "flex", marginTop: 25, }}>
<div style={{ flex: 1 }}>
<Typography variant="h1" style={{fontSize: 30}}>
Workflows
</Typography>
<Typography variant="h1" style={{fontSize: 30}}>
Workflows
</Typography>
</div>
{isMobile ? null :
<div style={{ display: "flex", margin: "0px 0px 20px 0px" }}>
<div style={{ flex: 1, float: "right" }}>
<ChipInput
style={{}}
InputProps={{
style: {
color: "white",
maxWidth: 275,
minWidth: 275,
},
}}
placeholder="Add Filter"
color="primary"
fullWidth
value={filters}
onAdd={(chip) => {
addFilter(chip);
}}
onDelete={(_, index) => {
removeFilter(index);
}}
/>
</div>
</div>
}
{/*
<div style={{ flex: 1 }}>
<Typography style={{ marginTop: 7, marginBottom: "auto" }}>
<a
rel="noopener noreferrer"
target="_blank"
href="https://shuffler.io/docs/workflows"
style={{ textDecoration: "none", color: "#f85a3e" }}
>
Learn more about Workflows
</a>
</Typography>
</div>
*/}
{isMobile ? null :
<div style={{ display: "flex", margin: "0px 0px 20px 0px" }}>
<div style={{ flex: 1, float: "right" }}>
<MuiChipsInput
style={{}}
InputProps={{
style: {
color: "white",
maxWidth: 275,
minWidth: 275,
},
}}
placeholder="Filter Workflows"
color="primary"
fullWidth
value={filters}
onChange={(chips) => {
setFilters(chips);
}}
onAdd={(chip) => {
addFilter(chip);
}}
onDelete={(_, index) => {
removeFilter(index);
}}
/>
</div>
</div>
}
<div style={{ flex: 1, textAlign: "right", }}>
{workflowButtons}
</div>
</div>
{/*
<div style={flexContainerStyle}>
<div style={{...flexBoxStyle, ...activeWorkflowStyle}}>
<div style={flexContentStyle}>
<div><img src={mobileImage} style={iconStyle} /></div>
<div style={ blockRightStyle }>
<div style={counterStyle}>{workflows.length}</div>
<div style={fontSize_16}>ACTIVE WORKFLOWS</div>
</div>
</div>
</div>
<div style={{...flexBoxStyle, ...availableWorkflowStyle}}>
<div style={flexContentStyle}>
<div><img src={bookImage} style={iconStyle} /></div>
<div style={ blockRightStyle }>
<div style={counterStyle}>{workflows.length}</div>
<div style={fontSize_16}>AVAILABE WORKFLOWS</div>
</div>
</div>
</div>
<div style={{...flexBoxStyle, ...notificationStyle}}>
<div style={flexContentStyle}>
<div><img src={bagImage} style={iconStyle} /></div>
<div style={ blockRightStyle }>
<div style={counterStyle}>{workflows.length}</div>
<div style={fontSize_16}>NOTIFICATIONS</div>
</div>
</div>
</div>
</div>
*/}
{/*
chipRenderer={({ value, isFocused, isDisabled, handleClick, handleRequestDelete }, key) => {
console.log("VALUE: ", value)
return (
<Chip
key={key}
style={chipStyle}
>
{value}
</Chip>
)
}}
*/}
<div style={{width: "100%", minHeight: isMobile ? 0 : 51, maxHeight: isMobile ? 0 : 51, marginTop: 10, }}>
{!isMobile && usecases !== null && usecases !== undefined && usecases.length > 0 ?
@@ -3125,6 +3309,7 @@ const Workflows = (props) => {
{usecases.map((usecase, index) => {
//console.log(usecase)
const percentDone = usecase.matches.length > 0 ? parseInt(usecase.matches.length/usecase.list.length*100) : 0
//console.log("Usecase Matches: ", usecase.matches, ", Percent: ", percentDone)
return (
<Paper
@@ -3166,7 +3351,10 @@ const Workflows = (props) => {
</div>
<div style={{ marginTop: 10, marginBottom: 10, }} />
{!isMobile && actionImageList !== undefined && actionImageList !== null && actionImageList.length > 0 ? (
{!isMobile &&
actionImageList !== undefined &&
actionImageList !== null &&
actionImageList.length > 0 ? (
<div
style={{
display: "flex",
@@ -3255,10 +3443,11 @@ const Workflows = (props) => {
return returnData
}
/*<Zoom key={index} in={true} style={{ transitionDelay: `${appDelay}ms` }}>*/
return (
<span>
{/*<Zoom key={index} in={true} style={{ transitionDelay: `${appDelay}ms` }}>*/}
{returnData}
{/*</Zoom>*/}
</span>
);
})}
@@ -3296,11 +3485,13 @@ const Workflows = (props) => {
/>
: null}
{/*<Zoom in={true} style={{ transitionDelay: `${workflowDelay}ms` }}>*/}
<div style={{marginTop: 15, marginBottom: 50, }}>
<div style={{marginTop: 30, marginBottom: 50, marginLeft: 30, }}>
{view === "grid" ? (
<Grid container spacing={filteredWorkflows.length === 0 ? 12 : filteredWorkflows.length === 1 ? 6 : 4} style={paperAppContainer}>
<NewWorkflowPaper />
{/*<Zoom in={true} style={{ transitionDelay: `${workflowDelay}ms` }}>*/}
<NewWorkflowPaper />
{/*</Zoom>*/}
{filteredWorkflows.map((data, index) => {
// Shouldn't be a part of this list
if (data.public === true) {
@@ -3311,20 +3502,20 @@ const Workflows = (props) => {
workflowDelay += 75
} else {
return (
<Zoom key={index} in={true} style={{ transitionDelay: `${workflowDelay}ms` }}>
<Grid key={index} item xs={isMobile ? 12 : 4} style={{}}>
<WorkflowPaper key={index} data={data} />
</Grid>
</Zoom>
<Grid key={index} item xs={isMobile ? 12 : 4} style={{ padding: "12px 10px 12px 10px" }}>
<WorkflowPaper key={index} data={data} />
</Grid>
)
}
return (
<Zoom key={index} in={true} style={{ transitionDelay: `${workflowDelay}ms` }}>
<Grid item xs={isMobile ? 12 : 4} style={{}}>
<span>
{/*<Zoom key={index} in={true} style={{ transitionDelay: `${workflowDelay}ms` }}>*/}
<Grid item xs={isMobile ? 12 : 4} style={{ padding: "12px 10px 12px 10px" }}>
<WorkflowPaper key={index} data={data} />
</Grid>
</Zoom>
{/*</Zoom>*/}
</span>
)
})}
</Grid>
@@ -3364,7 +3555,7 @@ const Workflows = (props) => {
parsedData["field_2"] = field2;
}
alert.success("Getting specific workflows from your URL.");
toast("Getting specific workflows from your URL.");
fetch(globalUrl + "/api/v1/workflows/download_remote", {
method: "POST",
mode: "cors",
@@ -3376,7 +3567,7 @@ const Workflows = (props) => {
})
.then((response) => {
if (response.status === 200) {
alert.success("Successfully loaded workflows from " + downloadUrl);
toast("Successfully loaded workflows from " + downloadUrl);
setTimeout(() => {
getAvailableWorkflows();
}, 1000);
@@ -3387,14 +3578,14 @@ const Workflows = (props) => {
.then((responseJson) => {
if (!responseJson.success) {
if (responseJson.reason !== undefined) {
alert.error("Failed loading: " + responseJson.reason);
toast("Failed loading: " + responseJson.reason);
} else {
alert.error("Failed loading");
toast("Failed loading");
}
}
})
.catch((error) => {
alert.error(error.toString());
toast(error.toString());
});
};
@@ -3409,7 +3600,7 @@ const Workflows = (props) => {
onClose={() => {}}
PaperProps={{
style: {
backgroundColor: surfaceColor,
backgroundColor: theme.palette.surfaceColor,
color: "white",
minWidth: "800px",
minHeight: "320px",
@@ -3436,7 +3627,7 @@ const Workflows = (props) => {
<DialogContent style={{ color: "rgba(255,255,255,0.65)" }}>
Repository (supported: github, gitlab, bitbucket)
<TextField
style={{ backgroundColor: inputColor }}
style={{ backgroundColor: theme.palette.inputColor }}
variant="outlined"
margin="normal"
defaultValue={downloadUrl}
@@ -3456,7 +3647,7 @@ const Workflows = (props) => {
</span>
<div style={{ display: "flex" }}>
<TextField
style={{ backgroundColor: inputColor }}
style={{ backgroundColor: theme.palette.inputColor }}
variant="outlined"
margin="normal"
defaultValue={downloadBranch}
@@ -3477,7 +3668,7 @@ const Workflows = (props) => {
</span>
<div style={{ display: "flex" }}>
<TextField
style={{ flex: 1, backgroundColor: inputColor }}
style={{ flex: 1, backgroundColor: theme.palette.inputColor }}
variant="outlined"
margin="normal"
InputProps={{
@@ -3493,7 +3684,7 @@ const Workflows = (props) => {
fullWidth
/>
<TextField
style={{ flex: 1, backgroundColor: inputColor }}
style={{ flex: 1, backgroundColor: theme.palette.inputColor }}
variant="outlined"
margin="normal"
InputProps={{
@@ -3643,7 +3834,7 @@ const Workflows = (props) => {
}}
PaperProps={{
style: {
backgroundColor: surfaceColor,
backgroundColor: theme.palette.surfaceColor,
color: "white",
minWidth: 560,
minHeight: 415,
@@ -3728,7 +3919,7 @@ const Workflows = (props) => {
setWorkflow={setEditingWorkflow}
modalOpen={modalOpen}
setModalOpen={setModalOpen}
usecases={usecases}
usecases={usecases}
setNewWorkflow={setNewWorkflow}
appFramework={appFramework}
isEditing={isEditing}