Initial open source commit
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
|
||||
const hrefStyle = {
|
||||
color: "#f85a3e",
|
||||
textDecoration: "none"
|
||||
}
|
||||
|
||||
const About = () => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>About</h1>
|
||||
|
||||
<p>
|
||||
Endao was started as a project in late 2018 as a free service to analyze APK (and soon IPA) files for vulnerabilities. The project was started after I,
|
||||
<a href="https://twitter.com/frikkylikeme" style={hrefStyle}>@frikkylikeme</a>
|
||||
, found multiple vulnerabilities in IoT devices based purely on their apps. As I wanted to learn more about these kind of vulnerabilities, I looked for solutions that work for my purpose, but didn't find any good, free and easy to use service - hence this site was born.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
My personal goal has and will always be to make the internet safer. As the IoT sphere grows, I want to be able to add ways of finding possible vulnerabilities fast to this website. This will hopefully include blogposts when I get around to it, as well as actual implementations. The vulnerability discovery field is in no way new, but I'll try my best to add whatever I can to it. As a disclaimer, I'm an "Ops" person, and I had never done frontend before creating this site. This is as much of a learning project within web development as it is in vulnerability discovery.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This site currently uses the following projects
|
||||
</p>
|
||||
<ul>
|
||||
<li><a style={hrefStyle} href="https://superanalyzer.rocks">SUPER Android Analyzer</a></li>
|
||||
<li><a style={hrefStyle} href="https://github.com/linkedin/qark">Qark</a></li>
|
||||
<li><a style={hrefStyle} href="https://virustotal.com">Virustotal</a> for malware checks in known APKs</li>
|
||||
<li>Some selfmade gibberish</li>
|
||||
</ul>
|
||||
|
||||
<p>Hopefully it is of use to some people :)</p>
|
||||
|
||||
<h3>Thanks</h3>
|
||||
<p>
|
||||
Thanks to Andy for the initial frontend help :)
|
||||
</p>
|
||||
|
||||
<h3>Regards</h3>
|
||||
<p>
|
||||
<a href="https://twitter.com/frikkylikeme" style={hrefStyle}>@frikkylikeme</a>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default About;
|
||||
@@ -0,0 +1,395 @@
|
||||
import React, { useEffect} from 'react';
|
||||
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import List from '@material-ui/core/List';
|
||||
import Divider from '@material-ui/core/Divider';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import ListItem from '@material-ui/core/ListItem';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Tabs from '@material-ui/core/Tabs';
|
||||
import Tab from '@material-ui/core/Tab';
|
||||
|
||||
|
||||
import { useAlert } from "react-alert";
|
||||
|
||||
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';
|
||||
|
||||
|
||||
const surfaceColor = "#27292D"
|
||||
const inputColor = "#383B40"
|
||||
const Admin = (props) => {
|
||||
const { globalUrl, } = props;
|
||||
const [firstRequest, setFirstRequest] = React.useState(true);
|
||||
const [modalUser, setModalUser] = React.useState({});
|
||||
const [modalOpen, setModalOpen] = React.useState(false);
|
||||
const [loginInfo, setLoginInfo] = React.useState("");
|
||||
const [curTab, setCurTab] = React.useState(0);
|
||||
const [users, setUsers] = React.useState([]);
|
||||
const [environments, setEnvironments] = React.useState([]);
|
||||
|
||||
const alert = useAlert()
|
||||
|
||||
const submitUser = (data) => {
|
||||
// FIXME - add some check here ROFL
|
||||
console.log("INPUT: ", data)
|
||||
|
||||
// Just use this one?
|
||||
var data = {"username": data.Username, "password": data.Password}
|
||||
var baseurl = globalUrl
|
||||
const url = baseurl+'/api/v1/register';
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
credentials: "include",
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then(response =>
|
||||
response.json().then(responseJson => {
|
||||
if (responseJson["success"] === false) {
|
||||
setLoginInfo("Error in input: "+responseJson.reason)
|
||||
} else {
|
||||
setLoginInfo("")
|
||||
setModalOpen(false)
|
||||
getUsers()
|
||||
}
|
||||
}),
|
||||
)
|
||||
.catch(error => {
|
||||
console.log("Error in userdata: ", error)
|
||||
});
|
||||
}
|
||||
|
||||
const deleteEnvironment = (name) => {
|
||||
// FIXME - add some check here ROFL
|
||||
var newEnv = []
|
||||
for (var key in environments) {
|
||||
if (environments[key].Name == name) {
|
||||
continue
|
||||
}
|
||||
|
||||
newEnv.push(environments[key])
|
||||
}
|
||||
|
||||
// Just use this one?
|
||||
const url = globalUrl+'/api/v1/setenvironments';
|
||||
fetch(url, {
|
||||
method: 'PUT',
|
||||
credentials: "include",
|
||||
body: JSON.stringify(newEnv),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then(response =>
|
||||
response.json().then(responseJson => {
|
||||
if (responseJson["success"] === false) {
|
||||
alert.error(responseJson.reason)
|
||||
} else {
|
||||
setLoginInfo("")
|
||||
setModalOpen(false)
|
||||
getEnvironments()
|
||||
}
|
||||
}),
|
||||
)
|
||||
//.catch(error => {
|
||||
// console.log("Error in userdata: ", error)
|
||||
//});
|
||||
}
|
||||
|
||||
const submitEnvironment = (data) => {
|
||||
// FIXME - add some check here ROFL
|
||||
environments.push({"name": data.environment, "type": "onprem"})
|
||||
|
||||
// Just use this one?
|
||||
var baseurl = globalUrl
|
||||
const url = baseurl+'/api/v1/setenvironments';
|
||||
fetch(url, {
|
||||
method: 'PUT',
|
||||
credentials: "include",
|
||||
body: JSON.stringify(environments),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then(response =>
|
||||
response.json().then(responseJson => {
|
||||
if (responseJson["success"] === false) {
|
||||
setLoginInfo("Error in input: "+responseJson.reason)
|
||||
} else {
|
||||
setLoginInfo("")
|
||||
setModalOpen(false)
|
||||
getEnvironments()
|
||||
}
|
||||
}),
|
||||
)
|
||||
.catch(error => {
|
||||
console.log("Error in userdata: ", error)
|
||||
});
|
||||
}
|
||||
|
||||
const getEnvironments = () => {
|
||||
fetch(globalUrl+"/api/v1/getenvironments", {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for apps :O!")
|
||||
return
|
||||
}
|
||||
|
||||
return response.json()
|
||||
})
|
||||
.then((responseJson) => {
|
||||
setEnvironments(responseJson)
|
||||
})
|
||||
.catch(error => {
|
||||
alert.error(error.toString())
|
||||
});
|
||||
}
|
||||
|
||||
const getUsers = () => {
|
||||
fetch(globalUrl+"/api/v1/getusers", {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for apps :O!")
|
||||
return
|
||||
}
|
||||
|
||||
return response.json()
|
||||
})
|
||||
.then((responseJson) => {
|
||||
setUsers(responseJson)
|
||||
})
|
||||
.catch(error => {
|
||||
alert.error(error.toString())
|
||||
});
|
||||
}
|
||||
|
||||
if (firstRequest) {
|
||||
setFirstRequest(false)
|
||||
getUsers()
|
||||
}
|
||||
|
||||
const paperStyle = {
|
||||
minWidth: "100%",
|
||||
maxWidth: "100%",
|
||||
color: "white",
|
||||
backgroundColor: surfaceColor,
|
||||
marginBottom: 10,
|
||||
padding: 20,
|
||||
}
|
||||
|
||||
const changeModalData = (field, value) => {
|
||||
modalUser[field] = value
|
||||
}
|
||||
|
||||
const modalView =
|
||||
<Dialog modal
|
||||
open={modalOpen}
|
||||
onClose={() => {setModalOpen(false)}}
|
||||
PaperProps={{
|
||||
style: {
|
||||
backgroundColor: surfaceColor,
|
||||
color: "white",
|
||||
minWidth: "800px",
|
||||
minHeight: "320px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogTitle><span style={{color: "white"}}>Add user</span></DialogTitle>
|
||||
<DialogContent>
|
||||
{curTab === 0 ?
|
||||
<div>
|
||||
Username
|
||||
<TextField
|
||||
color="primary"
|
||||
style={{backgroundColor: inputColor}}
|
||||
autoFocus
|
||||
InputProps={{
|
||||
style:{
|
||||
height: "50px",
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
required
|
||||
fullWidth={true}
|
||||
autoComplete="username"
|
||||
placeholder="username@example.com"
|
||||
id="emailfield"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
onChange={(event) => changeModalData("Username", event.target.value)}
|
||||
/>
|
||||
Password
|
||||
<TextField
|
||||
color="primary"
|
||||
style={{backgroundColor: inputColor}}
|
||||
InputProps={{
|
||||
style:{
|
||||
height: "50px",
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
required
|
||||
fullWidth={true}
|
||||
autoComplete="password"
|
||||
type="password"
|
||||
placeholder="********"
|
||||
id="pwfield"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
onChange={(event) => changeModalData("Password", event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
: curTab === 1 ?
|
||||
<div>
|
||||
Environment Name
|
||||
<TextField
|
||||
color="primary"
|
||||
style={{backgroundColor: inputColor}}
|
||||
autoFocus
|
||||
InputProps={{
|
||||
style:{
|
||||
height: "50px",
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
required
|
||||
fullWidth={true}
|
||||
placeholder="datacenter froglantern"
|
||||
id="environment_name"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
onChange={(event) => changeModalData("environment", event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
: null }
|
||||
{loginInfo}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button style={{borderRadius: "0px"}} onClick={() => setModalOpen(false)} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="contained" style={{borderRadius: "0px"}} onClick={() => {
|
||||
if (curTab === 0) {
|
||||
submitUser(modalUser)
|
||||
} else if (curTab === 1) {
|
||||
submitEnvironment(modalUser)
|
||||
}
|
||||
}} color="primary">
|
||||
Submit
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
const usersView = curTab === 0 ?
|
||||
<div>
|
||||
<h2>
|
||||
User management
|
||||
</h2>
|
||||
<Button
|
||||
style={{}}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => setModalOpen(true)}
|
||||
>
|
||||
Add user
|
||||
</Button>
|
||||
<Divider style={{marginTop: 20, marginBottom: 20, backgroundColor: inputColor}}/>
|
||||
<List>
|
||||
{users === undefined ? null : users.map(data => {
|
||||
console.log(data)
|
||||
return (
|
||||
<ListItem>
|
||||
{data.Username}
|
||||
</ListItem>
|
||||
)
|
||||
})}
|
||||
</List>
|
||||
</div>
|
||||
: null
|
||||
|
||||
const environmentView = curTab === 1 ?
|
||||
<div>
|
||||
<h2>
|
||||
Environments
|
||||
</h2>
|
||||
<Button
|
||||
style={{}}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => setModalOpen(true)}
|
||||
>
|
||||
Add environment
|
||||
</Button>
|
||||
<Divider style={{marginTop: 20, marginBottom: 20, backgroundColor: inputColor}}/>
|
||||
<List>
|
||||
{environments === undefined ? null : environments.map(environment => {
|
||||
return (
|
||||
<ListItem>
|
||||
<Button type="outlined" style={{borderRadius: "0px"}} onClick={() => deleteEnvironment(environment.Name)} color="primary">Delete</Button>
|
||||
- {environment.Name}
|
||||
</ListItem>
|
||||
)
|
||||
})}
|
||||
</List>
|
||||
</div>
|
||||
: null
|
||||
|
||||
const setConfig = (event, newValue) => {
|
||||
if (newValue === 1) {
|
||||
getEnvironments()
|
||||
}
|
||||
|
||||
setModalUser({})
|
||||
setCurTab(newValue)
|
||||
}
|
||||
|
||||
const data =
|
||||
<div style={{width: 1366, margin: "auto"}}>
|
||||
<Paper style={paperStyle}>
|
||||
<Tabs
|
||||
value={curTab}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
onChange={setConfig}
|
||||
aria-label="disabled tabs example"
|
||||
>
|
||||
<Tab label="Users" />
|
||||
<Tab label="Environments"/>
|
||||
</Tabs>
|
||||
<div style={{marginBottom: 10}}/>
|
||||
{usersView}
|
||||
{environmentView}
|
||||
</Paper>
|
||||
</div>
|
||||
|
||||
return (
|
||||
<div>
|
||||
{modalView}
|
||||
{data}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Admin
|
||||
@@ -0,0 +1,224 @@
|
||||
/* eslint-disable react/no-multi-comp */
|
||||
import React, {useState} from 'react';
|
||||
import { makeStyles } from '@material-ui/styles';
|
||||
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
|
||||
const hrefStyle = {
|
||||
color: "white",
|
||||
textDecoration: "none"
|
||||
}
|
||||
|
||||
const bodyDivStyle = {
|
||||
margin: "auto",
|
||||
marginTop: "100px",
|
||||
width: "500px",
|
||||
}
|
||||
|
||||
const surfaceColor = "#27292D"
|
||||
const inputColor = "#383B40"
|
||||
|
||||
const boxStyle = {
|
||||
paddingLeft: "30px",
|
||||
paddingRight: "30px",
|
||||
paddingBottom: "30px",
|
||||
paddingTop: "30px",
|
||||
backgroundColor: surfaceColor,
|
||||
}
|
||||
|
||||
const useStyles = makeStyles({
|
||||
notchedOutline: {
|
||||
borderColor: "#f85a3e !important"
|
||||
},
|
||||
});
|
||||
|
||||
const AdminAccount = props => {
|
||||
const { globalUrl, isLoaded, isLoggedIn, setIsLoggedIn, setCookie, } = props;
|
||||
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [firstRequest, setFirstRequest] = useState(true);
|
||||
|
||||
// Used to swap from login to register. True = login, false = register
|
||||
const register = true
|
||||
|
||||
const classes = useStyles();
|
||||
// Error messages etc
|
||||
const [loginInfo, setLoginInfo] = useState("");
|
||||
|
||||
const handleValidateForm = () => {
|
||||
return (username.length > 1 && password.length > 8);
|
||||
}
|
||||
|
||||
if (isLoggedIn === true) {
|
||||
window.location.pathname = "/workflows"
|
||||
}
|
||||
|
||||
const checkAdmin = () => {
|
||||
const url = globalUrl+'/api/v1/checkusers';
|
||||
fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then(response =>
|
||||
response.json().then(responseJson => {
|
||||
if (responseJson["success"] === false) {
|
||||
setLoginInfo(responseJson["reason"])
|
||||
} else {
|
||||
if (responseJson.reason === "redirect") {
|
||||
window.location.pathname = "/login"
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
.catch(error => {
|
||||
setLoginInfo("Error in userdata: ", error)
|
||||
})
|
||||
}
|
||||
|
||||
if (firstRequest) {
|
||||
setFirstRequest(false)
|
||||
checkAdmin()
|
||||
}
|
||||
|
||||
const onSubmit = (e) => {
|
||||
e.preventDefault()
|
||||
// FIXME - add some check here ROFL
|
||||
|
||||
// Just use this one?
|
||||
var data = {"username": username, "password": password}
|
||||
var baseurl = globalUrl
|
||||
const url = baseurl+'/api/v1/register';
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then(response =>
|
||||
response.json().then(responseJson => {
|
||||
if (responseJson["success"] === false) {
|
||||
setLoginInfo(responseJson["reason"])
|
||||
} else {
|
||||
setLoginInfo("Successful register :)")
|
||||
window.location.pathname = "/login"
|
||||
}
|
||||
}),
|
||||
)
|
||||
.catch(error => {
|
||||
setLoginInfo("Error in userdata: ", error)
|
||||
});
|
||||
}
|
||||
|
||||
const onChangeUser = (e) => {
|
||||
setUsername(e.target.value)
|
||||
}
|
||||
|
||||
const onChangePass = (e) => {
|
||||
setPassword(e.target.value)
|
||||
}
|
||||
|
||||
//const onClickRegister = () => {
|
||||
// if (props.location.pathname === "/login") {
|
||||
// window.location.pathname = "/register"
|
||||
// } else {
|
||||
// window.location.pathname = "/login"
|
||||
// }
|
||||
|
||||
// setLoginCheck(!register)
|
||||
//}
|
||||
|
||||
//var loginChange = register ? (<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 = register ? <div>Login</div> : <div>Register</div>
|
||||
|
||||
formtitle = "Create administrator account"
|
||||
|
||||
const basedata =
|
||||
<div style={bodyDivStyle}>
|
||||
<Paper style={boxStyle}>
|
||||
<form onSubmit={onSubmit} style={{margin: "15px 15px 15px 15px"}}>
|
||||
<h2>{formtitle}</h2>
|
||||
Username
|
||||
<div>
|
||||
<TextField
|
||||
color="primary"
|
||||
style={{backgroundColor: inputColor}}
|
||||
autoFocus
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: classes.notchedOutline,
|
||||
},
|
||||
style:{
|
||||
height: "50px",
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
required
|
||||
fullWidth={true}
|
||||
autoComplete="username"
|
||||
placeholder="username@example.com"
|
||||
id="emailfield"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
onChange={onChangeUser}
|
||||
/>
|
||||
</div>
|
||||
Password
|
||||
<div>
|
||||
<TextField
|
||||
color="primary"
|
||||
style={{backgroundColor: inputColor,}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: classes.notchedOutline,
|
||||
},
|
||||
style:{
|
||||
height: "50px",
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
required
|
||||
id="outlined-password-input"
|
||||
fullWidth={true}
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
placeholder="**********"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
onChange={onChangePass}
|
||||
/>
|
||||
</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: "10px"}}>
|
||||
{loginInfo}
|
||||
</div>
|
||||
</form>
|
||||
</Paper>
|
||||
</div>
|
||||
|
||||
const loadedCheck = isLoaded ?
|
||||
<div>
|
||||
{basedata}
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
</div>
|
||||
|
||||
return (
|
||||
<div>
|
||||
{loadedCheck}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AdminAccount;
|
||||
@@ -0,0 +1,26 @@
|
||||
import React, { useEffect} from 'react';
|
||||
|
||||
const Popup = (props) => {
|
||||
const { data } = props;
|
||||
|
||||
const popupStyle = {
|
||||
position: "fixed",
|
||||
width: "300px",
|
||||
height: "50px",
|
||||
backgroundColor: "black",
|
||||
color: "white",
|
||||
}
|
||||
|
||||
const popupData =
|
||||
<div>
|
||||
HEY
|
||||
</div>
|
||||
|
||||
return (
|
||||
<div>
|
||||
{popupData}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Popup
|
||||
@@ -0,0 +1,44 @@
|
||||
import React from 'react'
|
||||
import InfoIcon from './icons/InfoIcon'
|
||||
import SuccessIcon from './icons/SuccessIcon'
|
||||
import ErrorIcon from './icons/ErrorIcon'
|
||||
import CloseIcon from './icons/CloseIcon'
|
||||
|
||||
const alertStyle = {
|
||||
backgroundColor: '#151515',
|
||||
color: 'white',
|
||||
padding: '10px',
|
||||
textTransform: 'uppercase',
|
||||
borderRadius: '3px',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
boxShadow: '0px 2px 2px 2px rgba(0, 0, 0, 0.03)',
|
||||
fontFamily: 'Arial',
|
||||
width: '300px',
|
||||
boxSizing: 'border-box'
|
||||
}
|
||||
|
||||
const buttonStyle = {
|
||||
marginLeft: '20px',
|
||||
border: 'none',
|
||||
backgroundColor: 'transparent',
|
||||
cursor: 'pointer',
|
||||
color: '#FFFFFF'
|
||||
}
|
||||
|
||||
const AlertTemplate = ({ message, options, style, close }) => {
|
||||
return (
|
||||
<div style={{ ...alertStyle, ...style }}>
|
||||
{options.type === 'info' && <InfoIcon />}
|
||||
{options.type === 'success' && <SuccessIcon />}
|
||||
{options.type === 'error' && <ErrorIcon />}
|
||||
<span style={{ flex: 2 }}>{message}</span>
|
||||
<button onClick={close} style={buttonStyle}>
|
||||
<CloseIcon />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AlertTemplate
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,166 @@
|
||||
import React, {useState, useEffect} from 'react';
|
||||
|
||||
import {Route} from 'react-router';
|
||||
import {BrowserRouter} from 'react-router-dom';
|
||||
import { CookiesProvider } from 'react-cookie';
|
||||
import { useCookies } from 'react-cookie';
|
||||
|
||||
import EditSchedule from "./EditSchedule";
|
||||
import Schedules from "./Schedules";
|
||||
import Webhooks from "./Webhooks";
|
||||
import Workflows from "./Workflows";
|
||||
import EditWebhook from "./EditWebhook";
|
||||
import AngularWorkflow from "./AngularWorkflow";
|
||||
import ForgotPassword from "./ForgotPassword";
|
||||
import ForgotPasswordLink from "./ForgotPasswordLink";
|
||||
|
||||
import Header from './Header';
|
||||
import Apps from './Apps';
|
||||
import AppCreator from './AppCreator';
|
||||
import Contact from './Contact';
|
||||
import Oauth2 from './Oauth2';
|
||||
import About from "./About";
|
||||
import Post from "./Post";
|
||||
import Dashboard from "./Dashboard";
|
||||
import AdminSetup from "./AdminSetup";
|
||||
import Admin from "./Admin";
|
||||
import Docs from "./Docs";
|
||||
import RegisterLink from "./RegisterLink";
|
||||
import LandingPage from "./Landingpage";
|
||||
import LandingPageNew from "./LandingpageNew";
|
||||
import LoginPage from "./LoginPage";
|
||||
import SettingsPage from "./SettingsPage";
|
||||
|
||||
import MuiThemeProvider from '@material-ui/core/styles/MuiThemeProvider';
|
||||
import { createMuiTheme } from '@material-ui/core/styles';
|
||||
|
||||
import AlertTemplate from "react-alert-template-basic";
|
||||
import { positions, Provider } from "react-alert";
|
||||
|
||||
// Testing - localhost
|
||||
const globalUrl = "http://192.168.3.6:5001"
|
||||
|
||||
// Production - backend proxy forwarding in nginx
|
||||
//const globalUrl = window.location.origin
|
||||
|
||||
const surfaceColor = "#27292D"
|
||||
const inputColor = "#383B40"
|
||||
|
||||
const theme = createMuiTheme({
|
||||
palette: {
|
||||
primary: {
|
||||
main: "#f85a3e"
|
||||
},
|
||||
secondary: {
|
||||
main: '#e8eaf6',
|
||||
},
|
||||
},
|
||||
typography: {
|
||||
useNextVariants: true
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// FIXME - set client side cookies
|
||||
const App = (message, props) => {
|
||||
const [userdata, setUserData] = useState({});
|
||||
//const [homePage, ] = useState(true);
|
||||
const [cookies, setCookie, removeCookie] = useCookies([]);
|
||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||
const [dataset, setDataset] = useState(false);
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (dataset === false) {
|
||||
checkLogin()
|
||||
setDataset(true)
|
||||
initializeReactGA()
|
||||
}
|
||||
})
|
||||
|
||||
function initializeReactGA() {
|
||||
}
|
||||
|
||||
console.log(window.location)
|
||||
if (isLoaded && !isLoggedIn && (!window.location.pathname.startsWith("/login") && (!window.location.pathname.startsWith("/docs") && (!window.location.pathname.startsWith("/adminsetup"))))) {
|
||||
window.location = "login"
|
||||
}
|
||||
|
||||
const checkLogin = () => {
|
||||
var baseurl = globalUrl
|
||||
fetch(baseurl+"/api/v1/getinfo", {
|
||||
credentials: "include",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(responseJson => {
|
||||
if (responseJson.success === true) {
|
||||
setUserData(responseJson)
|
||||
setIsLoggedIn(true)
|
||||
|
||||
// Updating cookie every request
|
||||
for (var key in responseJson["cookies"]) {
|
||||
setCookie(responseJson["cookies"][key].key, responseJson["cookies"][key].value, {path: "/"})
|
||||
}
|
||||
}
|
||||
setIsLoaded(true)
|
||||
})
|
||||
.catch(error => {
|
||||
setIsLoaded(true)
|
||||
});
|
||||
}
|
||||
|
||||
// Dumb for content load (per now), but good for making the site not suddenly reload parts (ajax thingies)
|
||||
|
||||
const options = {
|
||||
timeout: 5000,
|
||||
position: positions.BOTTOM_CENTER
|
||||
};
|
||||
|
||||
const includedData = window.location.pathname === "/home" || window.location.pathname === "/features" ?
|
||||
<div>
|
||||
<Route exact path="/home" render={props => <LandingPageNew isLoaded={isLoaded} {...props} /> } />
|
||||
</div> :
|
||||
<div style={{backgroundColor: "#1F2023", color: "rgba(255, 255, 255, 0.65)", minHeight: "100vh"}}>
|
||||
<Header removeCookie={removeCookie} isLoaded={isLoaded} globalUrl={globalUrl} setIsLoggedIn={setIsLoggedIn} isLoggedIn={isLoggedIn} surfaceColor={surfaceColor} inputColor={inputColor}{...props} />
|
||||
<Route exact path="/oauth2" render={props => <Oauth2 isLoaded={isLoaded} globalUrl={globalUrl} surfaceColor={surfaceColor} inputColor={inputColor}{...props} /> } />
|
||||
<Route exact path="/contact" render={props => <Contact isLoaded={isLoaded} globalUrl={globalUrl} surfaceColor={surfaceColor} inputColor={inputColor} {...props} /> } />
|
||||
<Route exact path="/login" render={props => <LoginPage isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} surfaceColor={surfaceColor} inputColor={inputColor} {...props} /> } />
|
||||
<Route exact path="/admin" render={props => <Admin isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} surfaceColor={surfaceColor} inputColor={inputColor} {...props} /> } />
|
||||
<Route exact path="/settings" render={props => <SettingsPage isLoaded={isLoaded} userdata={userdata} globalUrl={globalUrl} surfaceColor={surfaceColor} inputColor={inputColor} {...props} /> } />
|
||||
<Route exact path="/AdminSetup" render={props => <AdminSetup isLoaded={isLoaded} userdata={userdata} globalUrl={globalUrl} surfaceColor={surfaceColor} inputColor={inputColor} {...props} /> } />
|
||||
<Route exact path="/webhooks" render={props => <Webhooks isLoaded={isLoaded} globalUrl={globalUrl} {...props} /> } />
|
||||
<Route exact path="/webhooks/:key" render={props => <EditWebhook isLoaded={isLoaded} globalUrl={globalUrl} surfaceColor={surfaceColor} inputColor={inputColor} {...props} /> } />
|
||||
<Route exact path="/schedules" render={props => <Schedules globalUrl={globalUrl} surfaceColor={surfaceColor} inputColor={inputColor} {...props} /> } />
|
||||
<Route exact path="/dashboard" render={props => <Dashboard isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} surfaceColor={surfaceColor} inputColor={inputColor} {...props} /> } />
|
||||
<Route exact path="/apps" render={props => <Apps isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} surfaceColor={surfaceColor} inputColor={inputColor} {...props} /> } />
|
||||
<Route exact path="/apps/new" render={props => <AppCreator isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} surfaceColor={surfaceColor} inputColor={inputColor} {...props} /> } />
|
||||
<Route exact path="/apps/edit/:appid" render={props => <AppCreator isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} surfaceColor={surfaceColor} inputColor={inputColor} {...props} /> } />
|
||||
<Route exact path="/schedules/:key" render={props => <EditSchedule globalUrl={globalUrl} surfaceColor={surfaceColor} inputColor={inputColor} {...props} /> } />
|
||||
<Route exact path="/workflows" render={props => <Workflows isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} cookies={cookies} surfaceColor={surfaceColor} inputColor={inputColor}{...props} /> } />
|
||||
<Route exact path="/workflows/:key" render={props => <AngularWorkflow globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} surfaceColor={surfaceColor} inputColor={inputColor}{...props} /> } />
|
||||
<Route exact path="/docs/:key" render={props => <Docs isLoaded={isLoaded} globalUrl={globalUrl} surfaceColor={surfaceColor} inputColor={inputColor}{...props} /> } />
|
||||
<Route exact path="/docs" render={props => {window.location.pathname = "/docs/about"}} />
|
||||
<Route exact path="/" render={props => {window.location.pathname = "/login"}} />
|
||||
</div>
|
||||
|
||||
// <div style={{backgroundColor: "rgba(21, 32, 43, 1)", color: "#fffff", minHeight: "100vh"}}>
|
||||
// backgroundColor: "#213243",
|
||||
// This is a mess hahahah
|
||||
return (
|
||||
<MuiThemeProvider theme={theme}>
|
||||
<CookiesProvider>
|
||||
<BrowserRouter>
|
||||
<Provider template={AlertTemplate} {...options}>
|
||||
{includedData}
|
||||
</Provider>
|
||||
</BrowserRouter>
|
||||
</CookiesProvider>
|
||||
</MuiThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,725 @@
|
||||
import React, { useEffect} from 'react';
|
||||
|
||||
import { useInterval } from 'react-powerhooks';
|
||||
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import Divider from '@material-ui/core/Divider';
|
||||
import ButtonBase from '@material-ui/core/ButtonBase';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import FormControl from '@material-ui/core/FormControl';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import YAML from 'yaml'
|
||||
import {Link} from 'react-router-dom';
|
||||
|
||||
import CloudDownload from '@material-ui/icons/CloudDownload';
|
||||
import { useAlert } from "react-alert";
|
||||
|
||||
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 CircularProgress from '@material-ui/core/CircularProgress';
|
||||
|
||||
|
||||
const surfaceColor = "#27292D"
|
||||
const inputColor = "#383B40"
|
||||
|
||||
const Apps = (props) => {
|
||||
const { globalUrl, isLoggedIn, isLoaded } = props;
|
||||
|
||||
//const [workflows, setWorkflows] = React.useState([]);
|
||||
const alert = useAlert()
|
||||
const [selectedApp, setSelectedApp] = React.useState({});
|
||||
const [firstrequest, setFirstrequest] = React.useState(true)
|
||||
const [apps, setApps] = React.useState([])
|
||||
const [filteredApps, setFilteredApps] = React.useState([])
|
||||
const [validation, setValidation] = React.useState(false)
|
||||
const [isLoading, setIsLoading] = React.useState(false)
|
||||
|
||||
const [openApi, setOpenApi] = React.useState("")
|
||||
const [openApiData, setOpenApiData] = React.useState("")
|
||||
const [appValidation, setAppValidation] = React.useState("")
|
||||
const [openApiModal, setOpenApiModal] = React.useState(false);
|
||||
const [openApiModalType, setOpenApiModalType] = React.useState("");
|
||||
const [openApiError, setOpenApiError] = React.useState("")
|
||||
const { start, stop } = useInterval({
|
||||
duration: 5000,
|
||||
startImmediate: false,
|
||||
callback: () => {
|
||||
getApps()
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (apps.length <= 0 && firstrequest) {
|
||||
document.title = "Shuffle - Apps"
|
||||
setFirstrequest(false)
|
||||
getApps()
|
||||
}
|
||||
})
|
||||
|
||||
const appViewStyle = {
|
||||
color: "#ffffff",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
}
|
||||
|
||||
const paperAppStyle = {
|
||||
minHeight: 130,
|
||||
maxHeight: 130,
|
||||
minWidth: "100%",
|
||||
maxWidth: "100%",
|
||||
color: "white",
|
||||
backgroundColor: surfaceColor,
|
||||
cursor: "pointer",
|
||||
display: "flex",
|
||||
}
|
||||
|
||||
const getApps = () => {
|
||||
fetch(globalUrl+"/api/v1/workflows/apps", {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for apps :O!")
|
||||
}
|
||||
|
||||
return response.json()
|
||||
})
|
||||
.then((responseJson) => {
|
||||
setApps(responseJson)
|
||||
setFilteredApps(responseJson)
|
||||
if (responseJson.length > 0) {
|
||||
setSelectedApp(responseJson[0])
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert.error(error.toString())
|
||||
});
|
||||
}
|
||||
|
||||
const downloadApp = (inputdata) => {
|
||||
const id = inputdata.id
|
||||
|
||||
alert.info("Preparing download.")
|
||||
fetch(globalUrl+"/api/v1/apps/"+id+"/config", {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
window.location.pathname = "/apps"
|
||||
}
|
||||
|
||||
return response.json()
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (!responseJson.success) {
|
||||
alert.error("Failed to download file")
|
||||
} else {
|
||||
const data = YAML.stringify(YAML.parse(responseJson.body))
|
||||
|
||||
var name = inputdata.name
|
||||
name = name.replace(/ /g, "_", -1)
|
||||
name = name.toLowerCase()
|
||||
|
||||
var blob = new Blob( [ data ], {
|
||||
type: 'application/octet-stream'
|
||||
})
|
||||
|
||||
var url = URL.createObjectURL( blob )
|
||||
var link = document.createElement( 'a' )
|
||||
link.setAttribute( 'href', url )
|
||||
link.setAttribute( 'download', `${name}.yaml` )
|
||||
var event = document.createEvent( 'MouseEvents' )
|
||||
event.initMouseEvent( 'click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null)
|
||||
link.dispatchEvent( event )
|
||||
//link.parentNode.removeChild(link)
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
alert.error(error.toString())
|
||||
});
|
||||
}
|
||||
|
||||
// dropdown with copy etc I guess
|
||||
const appPaper = (data) => {
|
||||
var boxWidth = "2px"
|
||||
if (selectedApp.id === data.id) {
|
||||
boxWidth = "4px"
|
||||
}
|
||||
|
||||
var boxColor = "orange"
|
||||
if (data.is_valid) {
|
||||
boxColor = "green"
|
||||
}
|
||||
|
||||
var imageline = data.large_image.length === 0 ?
|
||||
<img alt="" style={{width: 100}} />
|
||||
:
|
||||
<img alt="" src={data.large_image} style={{width: 100, height: 100, objectFit: "cover"}} />
|
||||
|
||||
// FIXME - add label to apps, as this might be slow with A LOT of apps
|
||||
var newAppname = data.name
|
||||
newAppname = newAppname.replace("_", " ")
|
||||
newAppname = newAppname.charAt(0).toUpperCase()+newAppname.substring(1)
|
||||
|
||||
var sharing = "public"
|
||||
if (!data.sharing) {
|
||||
sharing = "private"
|
||||
}
|
||||
|
||||
var valid = "true"
|
||||
if (!data.valid) {
|
||||
valid = "false"
|
||||
}
|
||||
|
||||
if (data.actions === null || data.actions.length === 0) {
|
||||
valid = "false"
|
||||
}
|
||||
|
||||
var description = data.description
|
||||
const maxDescLen = 60
|
||||
if (description.length > maxDescLen) {
|
||||
description = data.description.slice(0, maxDescLen)+"..."
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper square style={paperAppStyle} onClick={() => {
|
||||
if (selectedApp.id !== data.id) {
|
||||
setSelectedApp(data)
|
||||
}
|
||||
}}>
|
||||
<Grid container style={{margin: 10, flex: "10"}}>
|
||||
<ButtonBase>
|
||||
{imageline}
|
||||
</ButtonBase>
|
||||
<div style={{marginLeft: "10px", marginTop: "5px", marginBottom: "5px", width: boxWidth, backgroundColor: boxColor}}>
|
||||
</div>
|
||||
<Grid container style={{margin: "0px 10px 10px 10px", flex: "1"}}>
|
||||
<Grid style={{display: "flex", flexDirection: "column", width: "100%"}}>
|
||||
<Grid item style={{flex: "1"}}>
|
||||
<h3 style={{marginBottom: "0px"}}>{newAppname}</h3>
|
||||
</Grid>
|
||||
<div style={{display: "flex", flex: "1"}}>
|
||||
<Grid item style={{flex: "1", justifyContent: "center", overflow: "hidden"}}>
|
||||
{description}
|
||||
</Grid>
|
||||
</div>
|
||||
<Grid item style={{flex: "1", justifyContent: "center"}}>
|
||||
Sharing: {sharing}
|
||||
, Valid: {valid}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container style={{margin: "10px 10px 10px 10px", flex: "1"}} onClick={() => {downloadApp(data)}}>
|
||||
<Tooltip title={"Download"} style={{marginTop: "28px", width: "100%"}} aria-label={data.name}>
|
||||
<CloudDownload />
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
|
||||
const dividerColor = "rgb(225, 228, 232)"
|
||||
const uploadViewPaperStyle = {
|
||||
minWidth: "100%",
|
||||
maxWidth: "100%",
|
||||
color: "white",
|
||||
backgroundColor: surfaceColor,
|
||||
display: "flex",
|
||||
marginBottom: 10,
|
||||
}
|
||||
|
||||
//const handleFile = (event) =>{
|
||||
// const formData = new FormData();
|
||||
// formData.append('file', event.target.files[0]);
|
||||
|
||||
// fetch(globalUrl+"/api/v1/workflows/apps/validate", {
|
||||
// method: 'POST',
|
||||
// headers: {
|
||||
// 'Accept': 'application/json',
|
||||
// },
|
||||
// body: formData,
|
||||
// credentials: "include",
|
||||
// })
|
||||
// .then((response) => {
|
||||
// if (response.status !== 200) {
|
||||
// console.log("Status not 200 for apps :O!")
|
||||
// return
|
||||
// }
|
||||
// return response.json()
|
||||
// })
|
||||
// .then((responseJson) => {
|
||||
// console.log(responseJson)
|
||||
// })
|
||||
// .catch(error => {
|
||||
// alert.error(error.toString())
|
||||
// });
|
||||
//}
|
||||
|
||||
const UploadView = () => {
|
||||
//var imageline = selectedApp.large_image === undefined || selectedApp.large_image.length === 0 ?
|
||||
// <img alt="" style={{width: "80px"}} />
|
||||
// :
|
||||
// <img alt="PICTURE" src={selectedApp.large_image} style={{width: "80px", height: "80px"}} />
|
||||
// FIXME - add label to apps, as this might be slow with A LOT of apps
|
||||
var newAppname = selectedApp.name
|
||||
if (newAppname !== undefined && newAppname.length > 0) {
|
||||
newAppname = newAppname.replace("_", " ")
|
||||
newAppname = newAppname.charAt(0).toUpperCase()+newAppname.substring(1)
|
||||
} else {
|
||||
newAppname = ""
|
||||
}
|
||||
|
||||
var description = selectedApp.description
|
||||
|
||||
const url = "/apps/edit/"+selectedApp.id
|
||||
var editButton = selectedApp.private_id !== undefined && selectedApp.private_id.length > 0 && selectedApp.generated ?
|
||||
<Link to={url} style={{textDecoration: "none"}}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
component="label"
|
||||
color="primary"
|
||||
style={{marginTop: "10px"}}
|
||||
>
|
||||
Edit app
|
||||
</Button></Link> : null
|
||||
|
||||
|
||||
var deleteButton = (selectedApp.private_id !== undefined && selectedApp.private_id.length > 0 && selectedApp.generated) || (selectedApp.downloaded != undefined && selectedApp.downloaded == true) ?
|
||||
<Button
|
||||
variant="outlined"
|
||||
component="label"
|
||||
color="primary"
|
||||
style={{marginLeft: 5, marginTop: 10}}
|
||||
onClick={() => {
|
||||
deleteApp(selectedApp.id)
|
||||
}}
|
||||
>
|
||||
Delete app
|
||||
</Button> : null
|
||||
|
||||
|
||||
//fetch(globalUrl+"/api/v1/get_openapi/"+urlParams.get("id"), {
|
||||
var baseInfo = newAppname.length > 0 ?
|
||||
<div>
|
||||
<h2>{newAppname}</h2>
|
||||
<p>{description}</p>
|
||||
<p>{selectedApp.id}</p>
|
||||
<p>{selectedApp.privateId}</p>
|
||||
{editButton}
|
||||
{deleteButton}
|
||||
</div>
|
||||
:
|
||||
null
|
||||
|
||||
return(
|
||||
<div>
|
||||
<Paper square style={uploadViewPaperStyle}>
|
||||
<div style={{width: "100%", margin: 25}}>
|
||||
<h2>App creation</h2>
|
||||
<Link to="/docs/apps" style={{textDecoration: "none", color: "#f85a3e"}}>What are apps?</Link>
|
||||
- <a href="https://swagger.io/specification/" style={{textDecoration: "none", color: "#f85a3e"}}>OpenAPI specification</a>
|
||||
<div/>
|
||||
Apps are how you interact with workflows, and are used to execute workflows. They are created with the app creator, using OpenAPI specification or manually in python.
|
||||
<div/>
|
||||
<div style={{marginTop: 20}}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
component="label"
|
||||
color="primary"
|
||||
style={{marginRight: 10, }}
|
||||
onClick={() => {
|
||||
setOpenApiModal(true)
|
||||
}}
|
||||
>
|
||||
Create from OpenAPI
|
||||
</Button>
|
||||
<Link to="/apps/new" style={{textDecoration: "none", color: "#f85a3e"}}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
component="label"
|
||||
color="primary"
|
||||
style={{}}
|
||||
>
|
||||
Create from scratch
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</Paper>
|
||||
<Paper square style={uploadViewPaperStyle}>
|
||||
<div style={{width: "100%", margin: 25}}>
|
||||
{baseInfo}
|
||||
</div>
|
||||
</Paper>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const handleSearchChange = (event) => {
|
||||
const searchfield = event.target.value.toLowerCase()
|
||||
const newapps = apps.filter(data => data.name.toLowerCase().includes(searchfield) || data.description.toLowerCase().includes(searchfield))
|
||||
setFilteredApps(newapps)
|
||||
}
|
||||
|
||||
const appView = isLoggedIn ?
|
||||
<div style={{width: 1366, margin: "auto"}}>
|
||||
<div style={appViewStyle}>
|
||||
<div style={{flex: "1", marginLeft: "10px", marginRight: "10px"}}>
|
||||
<h2>Upload</h2>
|
||||
<div style={{marginTop: 20}}/>
|
||||
<UploadView />
|
||||
</div>
|
||||
<Divider style={{marginBottom: "10px", marginTop: "10px", height: "100%", width: "1px", backgroundColor: dividerColor}}/>
|
||||
<div style={{flex: 1, marginLeft: "10px", marginRight: "10px"}}>
|
||||
<div style={{display: "flex"}}>
|
||||
<div style={{flex: 10}}>
|
||||
<h2>Available integrations</h2>
|
||||
</div>
|
||||
{isLoading ? <CircularProgress style={{marginTop: 13, marginRight: 15}} /> : null}
|
||||
<Button
|
||||
variant="contained"
|
||||
component="label"
|
||||
color="primary"
|
||||
style={{maxHeight: 50, marginTop: 10}}
|
||||
onClick={() => {
|
||||
getExistingApps()
|
||||
}}
|
||||
>
|
||||
Load existing apps
|
||||
</Button>
|
||||
</div>
|
||||
<TextField
|
||||
style={{backgroundColor: inputColor}}
|
||||
InputProps={{
|
||||
style:{
|
||||
color: "white",
|
||||
minHeight: "50px",
|
||||
marginLeft: "5px",
|
||||
maxWidth: "95%",
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
fullWidth
|
||||
color="primary"
|
||||
placeholder={"Search apps"}
|
||||
onChange={(event) => {
|
||||
handleSearchChange(event)
|
||||
}}
|
||||
/>
|
||||
<div style={{marginTop: 15}}>
|
||||
{apps.length > 0 ?
|
||||
filteredApps.length > 0 ?
|
||||
<div style={{maxHeight: "80vh", overflowY: "scroll"}}>
|
||||
{filteredApps.map(app => {
|
||||
return (
|
||||
appPaper(app)
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
:
|
||||
<Paper square style={uploadViewPaperStyle}>
|
||||
<h4>
|
||||
Try a broader search term. E.g. "http" or "TheHive"
|
||||
</h4>
|
||||
</Paper>
|
||||
:
|
||||
<Paper square style={uploadViewPaperStyle}>
|
||||
<h4>
|
||||
No apps have been created, uploaded or downloaded yet. Click "Load existing apps" above to get the baseline. This may take a while as its building docker images.
|
||||
</h4>
|
||||
</Paper>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
<div style={{width: "600px", margin: "auto", color: "white", paddingBottom: "50px"}}>
|
||||
<h2>Available integrations</h2>
|
||||
<Divider style={{marginBottom: "10px", height: "1px", width: "100%", backgroundColor: dividerColor}}/>
|
||||
{apps.map(data => {
|
||||
return (
|
||||
appPaper(data)
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
// Gets the URL itself (hopefully this works in most cases?
|
||||
// Will then forward the data to an internal endpoint to validate the api
|
||||
const getExistingApps = () => {
|
||||
setValidation(true)
|
||||
|
||||
setIsLoading(true)
|
||||
start()
|
||||
|
||||
alert.success("Downloading and building apps. Feel free to move around meanwhile.")
|
||||
var cors = "cors"
|
||||
fetch(globalUrl+"/api/v1/apps/get_existing", {
|
||||
method: "GET",
|
||||
mode: "cors",
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
alert.success("Failed loading.")
|
||||
} else {
|
||||
response.text().then(function (text) {
|
||||
console.log("RETURN: ", text)
|
||||
alert.success("Loaded existing apps!")
|
||||
})
|
||||
}
|
||||
setIsLoading(false)
|
||||
stop()
|
||||
})
|
||||
.catch(error => {
|
||||
alert.error(error.toString())
|
||||
})
|
||||
}
|
||||
|
||||
// Gets the URL itself (hopefully this works in most cases?
|
||||
// Will then forward the data to an internal endpoint to validate the api
|
||||
const validateUrl = () => {
|
||||
setValidation(true)
|
||||
|
||||
var cors = "cors"
|
||||
if (openApi.includes("localhost")) {
|
||||
cors = "no-cors"
|
||||
}
|
||||
|
||||
fetch(openApi, {
|
||||
method: "GET",
|
||||
mode: "cors",
|
||||
})
|
||||
.then((response) => {
|
||||
response.text().then(function (text) {
|
||||
validateOpenApi(text)
|
||||
})
|
||||
})
|
||||
.catch(error => {
|
||||
alert.error(error.toString())
|
||||
});
|
||||
}
|
||||
|
||||
const deleteApp = (appId) => {
|
||||
alert.info("Attempting to delete app")
|
||||
fetch(globalUrl+"/api/v1/apps/"+appId, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
alert.success("Successfully deleted app")
|
||||
} else {
|
||||
alert.error("Failed deleting app")
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert.error(error.toString())
|
||||
});
|
||||
}
|
||||
|
||||
const validateRemote = () => {
|
||||
setValidation(true)
|
||||
|
||||
fetch(globalUrl+"/api/v1/get_openapi_uri", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(openApi),
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
return response.text()
|
||||
})
|
||||
.then((responseText) => {
|
||||
validateOpenApi(responseText)
|
||||
setValidation(false)
|
||||
})
|
||||
.catch(error => {
|
||||
alert.error(error.toString())
|
||||
});
|
||||
}
|
||||
|
||||
const escapeApiData = (apidata) => {
|
||||
console.log(apidata)
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(apidata))
|
||||
} catch(error) {
|
||||
console.log("JSON DECODE ERROR - TRY YAML")
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
return JSON.stringify(YAML.parse(apidata))
|
||||
} catch(error) {
|
||||
console.log("YAML DECODE ERROR - TRY SOMETHING ELSE?: "+error)
|
||||
setOpenApiError(error)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// Sends the data to backend, which should return a version 3 of the same API
|
||||
// If 200 - continue, otherwise, there's some issue somewhere
|
||||
const validateOpenApi = (openApidata) => {
|
||||
const newApidata = escapeApiData(openApidata)
|
||||
if (newApidata === "") {
|
||||
return
|
||||
}
|
||||
|
||||
fetch(globalUrl+"/api/v1/validate_openapi", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: newApidata,
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
return response.json()
|
||||
})
|
||||
.then((responseJson) => {
|
||||
setValidation(false)
|
||||
if (responseJson.success) {
|
||||
setAppValidation(responseJson.id)
|
||||
} else {
|
||||
if (responseJson.reason !== undefined) {
|
||||
setOpenApiError(responseJson.reason)
|
||||
}
|
||||
alert.error("An error occurred in the response")
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert.error(error.toString())
|
||||
});
|
||||
}
|
||||
|
||||
const redirectOpenApi = () => {
|
||||
window.location.href = "/apps/new?id="+appValidation
|
||||
}
|
||||
|
||||
const errorText = openApiError.length > 0 ? <div>Error: {openApiError}</div> : null
|
||||
const circularLoader = validation ? <CircularProgress color="primary" /> : null
|
||||
console.log(validation)
|
||||
const modalView = openApiModal ?
|
||||
<Dialog modal
|
||||
open={openApiModal}
|
||||
onClose={() => {setOpenApiModal(false)}}
|
||||
PaperProps={{
|
||||
style: {
|
||||
backgroundColor: surfaceColor,
|
||||
color: "white",
|
||||
minWidth: "800px",
|
||||
minHeight: "320px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<FormControl>
|
||||
<DialogTitle><div style={{color: "rgba(255,255,255,0.9)"}}>Create a new integration</div></DialogTitle>
|
||||
<DialogContent style={{color: "rgba(255,255,255,0.65)"}}>
|
||||
Paste in the URI for the OpenAPI
|
||||
<TextField
|
||||
style={{backgroundColor: inputColor}}
|
||||
variant="outlined"
|
||||
margin="normal"
|
||||
InputProps={{
|
||||
style:{
|
||||
color: "white",
|
||||
height: "50px",
|
||||
fontSize: "1em",
|
||||
},
|
||||
endAdornment: <Button style={{borderRadius: "0px", marginTop: "0px", height: "50px"}} variant="contained" disabled={openApi.length === 0} color="primary" onClick={() => {
|
||||
setOpenApiError("")
|
||||
validateRemote()
|
||||
}}>Validate</Button>
|
||||
}}
|
||||
onChange={e => setOpenApi(e.target.value)}
|
||||
helperText={<div style={{color:"white", marginBottom: "2px",}}>Must point to a version 2 or 3 specification.</div>}
|
||||
placeholder="OpenAPI URI"
|
||||
fullWidth
|
||||
/>
|
||||
<div style={{marginTop: "15px"}}/>
|
||||
Example:
|
||||
<div />
|
||||
https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v2.0/json/uber.json
|
||||
<h4>or paste the yaml/JSON directly below</h4>
|
||||
<TextField
|
||||
style={{backgroundColor: inputColor}}
|
||||
variant="outlined"
|
||||
multiline
|
||||
rows={6}
|
||||
margin="normal"
|
||||
InputProps={{
|
||||
style:{
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
},
|
||||
endAdornment: <Button style={{marginLeft: 10, borderRadius: "0px", marginTop: "0px"}} variant="contained" disabled={openApiData.length === 0} color="primary" onClick={() => {
|
||||
setOpenApiError("")
|
||||
validateOpenApi(openApiData)
|
||||
}}>Validate data</Button>
|
||||
}}
|
||||
onChange={e => setOpenApiData(e.target.value)}
|
||||
helperText={<div style={{color:"white", marginBottom: "2px",}}>Must point to a version 2 or 3 specification.</div>}
|
||||
placeholder="OpenAPI text"
|
||||
fullWidth
|
||||
/>
|
||||
{errorText}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{circularLoader}
|
||||
<Button style={{borderRadius: "0px"}} onClick={() => setOpenApiModal(false)} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button style={{borderRadius: "0px"}} disabled={appValidation.length === 0} onClick={() => {
|
||||
redirectOpenApi()
|
||||
}} color="primary">
|
||||
Submit
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</FormControl>
|
||||
</Dialog>
|
||||
: null
|
||||
|
||||
|
||||
const loadedCheck = isLoaded && !firstrequest ?
|
||||
<div>
|
||||
{appView}
|
||||
{modalView}
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
</div>
|
||||
|
||||
// Maybe use gridview or something, idk
|
||||
return (
|
||||
<div>
|
||||
{loadedCheck}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Apps
|
||||
@@ -0,0 +1,343 @@
|
||||
import React, {useState} from 'react';
|
||||
import {BrowserView, MobileView} from "react-device-detect";
|
||||
|
||||
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, surfaceColor, inputColor } = props;
|
||||
|
||||
const boxStyle = {
|
||||
flex: "1",
|
||||
marginLeft: "10px",
|
||||
marginRight: "10px",
|
||||
paddingLeft: "30px",
|
||||
paddingRight: "30px",
|
||||
paddingBottom: "30px",
|
||||
paddingTop: "30px",
|
||||
backgroundColor: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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;
|
||||
@@ -0,0 +1,211 @@
|
||||
import React, {useState} from 'react';
|
||||
// nodejs library that concatenates classes
|
||||
import classNames from "classnames";
|
||||
// react plugin used to create charts
|
||||
import { Line, Bar } from "react-chartjs-2";
|
||||
|
||||
// https://demos.creative-tim.com/black-dashboard-react/?ref=appseed#/admin/dashboard
|
||||
|
||||
// reactstrap components
|
||||
import {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Card,
|
||||
CardHeader,
|
||||
CardBody,
|
||||
CardTitle,
|
||||
DropdownToggle,
|
||||
DropdownMenu,
|
||||
DropdownItem,
|
||||
UncontrolledDropdown,
|
||||
Label,
|
||||
FormGroup,
|
||||
Input,
|
||||
Table,
|
||||
Row,
|
||||
Col,
|
||||
UncontrolledTooltip
|
||||
} from "reactstrap";
|
||||
|
||||
// core components
|
||||
import {
|
||||
chartExample1,
|
||||
chartExample2,
|
||||
chartExample3,
|
||||
chartExample4
|
||||
} from "./charts.js";
|
||||
|
||||
// This is the start of a dashboard that can be used.
|
||||
// What data do we fill in here? Idk
|
||||
const Dashboard = (props) => {
|
||||
const [bigChartData, setBgChartData] = useState("data1");
|
||||
|
||||
document.title = "Shuffle - dashboard"
|
||||
|
||||
const data =
|
||||
<div className="content">
|
||||
<Row>
|
||||
<Col xs="12">
|
||||
<Card className="card-chart">
|
||||
<CardHeader>
|
||||
<Row>
|
||||
<Col className="text-left" sm="6">
|
||||
<h5 className="card-category">Total Shipments</h5>
|
||||
<CardTitle tag="h2">Performance</CardTitle>
|
||||
</Col>
|
||||
<Col sm="6">
|
||||
<ButtonGroup
|
||||
className="btn-group-toggle float-right"
|
||||
data-toggle="buttons"
|
||||
>
|
||||
<Button
|
||||
tag="label"
|
||||
className={classNames("btn-simple", {
|
||||
active: bigChartData === "data1"
|
||||
})}
|
||||
color="info"
|
||||
id="0"
|
||||
size="sm"
|
||||
onClick={() => setBgChartData("data1")}
|
||||
>
|
||||
<input
|
||||
defaultChecked
|
||||
className="d-none"
|
||||
name="options"
|
||||
type="radio"
|
||||
/>
|
||||
<span className="d-none d-sm-block d-md-block d-lg-block d-xl-block">
|
||||
Accounts
|
||||
</span>
|
||||
<span className="d-block d-sm-none">
|
||||
<i className="tim-icons icon-single-02" />
|
||||
</span>
|
||||
</Button>
|
||||
<Button
|
||||
color="info"
|
||||
id="1"
|
||||
size="sm"
|
||||
tag="label"
|
||||
className={classNames("btn-simple", {
|
||||
active: bigChartData === "data2"
|
||||
})}
|
||||
onClick={() => setBgChartData("data2")}
|
||||
>
|
||||
<input
|
||||
className="d-none"
|
||||
name="options"
|
||||
type="radio"
|
||||
/>
|
||||
<span className="d-none d-sm-block d-md-block d-lg-block d-xl-block">
|
||||
Purchases
|
||||
</span>
|
||||
<span className="d-block d-sm-none">
|
||||
<i className="tim-icons icon-gift-2" />
|
||||
</span>
|
||||
</Button>
|
||||
<Button
|
||||
color="info"
|
||||
id="2"
|
||||
size="sm"
|
||||
tag="label"
|
||||
className={classNames("btn-simple", {
|
||||
active: bigChartData === "data3"
|
||||
})}
|
||||
onClick={() => setBgChartData("data3")}
|
||||
>
|
||||
<input
|
||||
className="d-none"
|
||||
name="options"
|
||||
type="radio"
|
||||
/>
|
||||
<span className="d-none d-sm-block d-md-block d-lg-block d-xl-block">
|
||||
Sessions
|
||||
</span>
|
||||
<span className="d-block d-sm-none">
|
||||
<i className="tim-icons icon-tap-02" />
|
||||
</span>
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<div className="chart-area">
|
||||
<Line
|
||||
data={chartExample1[bigChartData]}
|
||||
options={chartExample1.options}
|
||||
/>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col lg="4">
|
||||
<Card className="card-chart">
|
||||
<CardHeader>
|
||||
<h5 className="card-category">Total Shipments</h5>
|
||||
<CardTitle tag="h3">
|
||||
<i className="tim-icons icon-bell-55 text-info" />{" "}
|
||||
763,215
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<div className="chart-area">
|
||||
<Line
|
||||
data={chartExample2.data}
|
||||
options={chartExample2.options}
|
||||
/>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col lg="4">
|
||||
<Card className="card-chart">
|
||||
<CardHeader>
|
||||
<h5 className="card-category">Daily Sales</h5>
|
||||
<CardTitle tag="h3">
|
||||
<i className="tim-icons icon-delivery-fast text-primary" />{" "}
|
||||
3,500€
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<div className="chart-area">
|
||||
<Bar
|
||||
data={chartExample3.data}
|
||||
options={chartExample3.options}
|
||||
/>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col lg="4">
|
||||
<Card className="card-chart">
|
||||
<CardHeader>
|
||||
<h5 className="card-category">Completed Tasks</h5>
|
||||
<CardTitle tag="h3">
|
||||
<i className="tim-icons icon-send text-success" /> 12,100K
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<div className="chart-area">
|
||||
<Line
|
||||
data={chartExample4.data}
|
||||
options={chartExample4.options}
|
||||
/>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
const dataWrapper =
|
||||
<div style={{maxWidth: 1366, margin: "auto"}}>
|
||||
{data}
|
||||
</div>
|
||||
|
||||
return dataWrapper
|
||||
}
|
||||
|
||||
export default Dashboard;
|
||||
@@ -0,0 +1,226 @@
|
||||
import React, {useState, useEffect} from 'react';
|
||||
|
||||
import Divider from '@material-ui/core/Divider';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import {BrowserView, MobileView} from "react-device-detect";
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Menu from '@material-ui/core/Menu';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
|
||||
import {Link} from 'react-router-dom';
|
||||
|
||||
const Body = {
|
||||
maxWidth: '1000px',
|
||||
minWidth: '768px',
|
||||
margin: 'auto',
|
||||
display: "flex",
|
||||
heigth: "100%",
|
||||
color: "white",
|
||||
//textAlign: "center",
|
||||
};
|
||||
|
||||
const dividerColor = "rgb(225, 228, 232)"
|
||||
|
||||
const SideBar = {
|
||||
maxWidth: "250px",
|
||||
flex: "1",
|
||||
}
|
||||
|
||||
const hrefStyle = {
|
||||
color: "rgba(255, 255, 255, 0.40)",
|
||||
textDecoration: "none"
|
||||
}
|
||||
|
||||
const Docs = (props) => {
|
||||
const { isLoaded, globalUrl } = props;
|
||||
|
||||
const [data, setData] = useState("");
|
||||
const [firstrequest, setFirstrequest] = useState(true);
|
||||
const [list, setList] = useState([]);
|
||||
const [listLoaded, setListLoaded] = useState(false);
|
||||
const [anchorEl, setAnchorEl] = React.useState(null);
|
||||
|
||||
function handleClick(event) {
|
||||
setAnchorEl(event.currentTarget);
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
setAnchorEl(null);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (firstrequest) {
|
||||
setFirstrequest(false)
|
||||
fetchDocList()
|
||||
fetchDocs()
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
const fetchDocList = () => {
|
||||
fetch(globalUrl+"/api/v1/docs", {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((responseJson) => {
|
||||
if (responseJson.success) {
|
||||
setList(responseJson.list)
|
||||
} else {
|
||||
setList(["error"])
|
||||
}
|
||||
setListLoaded(true)
|
||||
})
|
||||
.catch(error => {});
|
||||
}
|
||||
|
||||
const fetchDocs = () => {
|
||||
fetch(globalUrl+"/api/v1/docs/"+props.match.params.key, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((responseJson) => {
|
||||
if (responseJson.success) {
|
||||
setData(responseJson.reason)
|
||||
} else {
|
||||
setData("# Error\nThis page doesn't exist.")
|
||||
}
|
||||
})
|
||||
.catch(error => {});
|
||||
}
|
||||
|
||||
const markdownStyle = {
|
||||
color: "rgba(255, 255, 255, 0.65)",
|
||||
flex: "1",
|
||||
}
|
||||
|
||||
function Link(props) {
|
||||
return <a href={props.href} style={{color: "#f85a3e", textDecoration: "none"}}>{props.children}</a>
|
||||
}
|
||||
|
||||
function Img(props) {
|
||||
return <img style={{maxWidth: "100%"}} alt={props.alt} src={props.src}/>
|
||||
}
|
||||
|
||||
//function unicodeToChar(text) {
|
||||
// return text.replace(/\\u[\dA-F]{4}/gi,
|
||||
// function (match) {
|
||||
// return String.fromCharCode(parseInt(match.replace(/\\u/g, ''), 16));
|
||||
// }
|
||||
// );
|
||||
//}
|
||||
|
||||
const postDataBrowser =
|
||||
<div style={Body}>
|
||||
<div style={SideBar}>
|
||||
<ul style={{listStyle: "none", paddingLeft: "0"}}>
|
||||
<li style={{marginTop: "10px"}}>
|
||||
<a style={hrefStyle} href="/">
|
||||
<h2>Home</h2>
|
||||
</a>
|
||||
</li>
|
||||
{list.map(item => {
|
||||
const path = "/docs/"+item
|
||||
const newname = item.charAt(0).toUpperCase()+item.substring(1)
|
||||
return (
|
||||
<li style={{marginTop: "10px"}}>
|
||||
<a style={hrefStyle} href={path}>
|
||||
<h2>{newname}</h2>
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
<div style={markdownStyle}>
|
||||
<ReactMarkdown
|
||||
escapeHtml={false}
|
||||
source={data}
|
||||
renderers={{link: Link, image: Img}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
const mobileStyle = {
|
||||
color: "white",
|
||||
marginLeft: "15px",
|
||||
marginRight: "15px",
|
||||
paddingBottom: "50px",
|
||||
backgroundColor: "inherit",
|
||||
}
|
||||
|
||||
const postDataMobile =
|
||||
<div style={mobileStyle}>
|
||||
<Button aria-controls="simple-menu" aria-haspopup="true" variant="outlined" color="primary" onClick={handleClick}>
|
||||
<div style={{color: "white"}}>
|
||||
More items
|
||||
</div>
|
||||
</Button>
|
||||
<Menu
|
||||
id="simple-menu"
|
||||
anchorEl={anchorEl}
|
||||
keepMounted
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={handleClose}
|
||||
>
|
||||
{list.map(item => {
|
||||
const path = "/docs/"+item
|
||||
const newname = item.charAt(0).toUpperCase()+item.substring(1)
|
||||
return (
|
||||
<MenuItem onClick={() => {window.location.pathname = path}}>{newname}</MenuItem>
|
||||
)
|
||||
})}
|
||||
</Menu>
|
||||
<div style={markdownStyle}>
|
||||
<ReactMarkdown
|
||||
escapeHtml={false}
|
||||
source={data}
|
||||
renderers={{link: Link, image: Img}}
|
||||
/>
|
||||
</div>
|
||||
<Divider style={{marginTop: "10px", marginBottom: "10px", backgroundColor: dividerColor}}/>
|
||||
<Button aria-controls="simple-menu" aria-haspopup="true" variant="outlined" color="primary" onClick={handleClick}>
|
||||
<div style={{color: "white"}}>
|
||||
More items
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
//const imageModal =
|
||||
// <Dialog modal
|
||||
// open={imageModalOpen}
|
||||
// </Dialog>
|
||||
// {imageModal}
|
||||
|
||||
|
||||
const loadedCheck = isLoaded && listLoaded ?
|
||||
<div>
|
||||
<BrowserView>
|
||||
{postDataBrowser}
|
||||
</BrowserView>
|
||||
<MobileView>
|
||||
{postDataMobile}
|
||||
</MobileView>
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
</div>
|
||||
|
||||
return (
|
||||
<div>
|
||||
{loadedCheck}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default Docs;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,366 @@
|
||||
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 everytime 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;
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
|
||||
const Flows = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>Flows</h1>
|
||||
|
||||
<p>
|
||||
Built to suit any organization
|
||||
</p>
|
||||
|
||||
<p>
|
||||
WAT
|
||||
</p>
|
||||
|
||||
<p>
|
||||
</p>
|
||||
|
||||
<p></p>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Flows;
|
||||
@@ -0,0 +1,54 @@
|
||||
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;
|
||||
@@ -0,0 +1,122 @@
|
||||
/* 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;
|
||||
@@ -0,0 +1,134 @@
|
||||
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;
|
||||
@@ -0,0 +1,295 @@
|
||||
import React, {useState} from 'react';
|
||||
import {BrowserView, MobileView} from "react-device-detect";
|
||||
|
||||
import {Link} from 'react-router-dom';
|
||||
|
||||
import List from '@material-ui/core/List';
|
||||
import ListItem from '@material-ui/core/ListItem';
|
||||
|
||||
import Button from '@material-ui/core/Button';
|
||||
import HomeIcon from '@material-ui/icons/Home';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
|
||||
const hoverColor = "#f85a3e"
|
||||
const hoverOutColor = "#e8eaf6"
|
||||
|
||||
const Header = props => {
|
||||
const { globalUrl, isLoggedIn, removeCookie, homePage, isLoaded } = props;
|
||||
|
||||
const [HomeHoverColor, setHomeHoverColor] = useState(hoverOutColor);
|
||||
const [SoarHoverColor, setSoarHoverColor] = useState(hoverOutColor);
|
||||
const [LoginHoverColor, setLoginHoverColor] = useState(hoverOutColor);
|
||||
const [DocsHoverColor, setDocsHoverColor] = useState(hoverOutColor);
|
||||
const [HelpHoverColor, setHelpHoverColor] = useState(hoverOutColor);
|
||||
|
||||
const hrefStyle = {
|
||||
color: hoverOutColor,
|
||||
textDecoration: "none",
|
||||
}
|
||||
|
||||
// DEBUG HERE
|
||||
const handleClickLogout = () => {
|
||||
console.log("SHOULD LOG OUT")
|
||||
console.log(isLoggedIn)
|
||||
|
||||
// Don't really care about the logout
|
||||
fetch(globalUrl+"/api/v1/logout", {
|
||||
credentials: "include",
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
// Log out anyway
|
||||
console.log("Hey")
|
||||
removeCookie("session_token", {path: "/"})
|
||||
window.location.pathname = "/"
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
});
|
||||
}
|
||||
|
||||
// Rofl this is weird
|
||||
const handleDocsHover = () => {
|
||||
setDocsHoverColor(hoverColor)
|
||||
}
|
||||
|
||||
const handleDocsHoverOut = () => {
|
||||
setDocsHoverColor(hoverOutColor)
|
||||
}
|
||||
|
||||
const handleHomeHover = () => {
|
||||
setHomeHoverColor(hoverColor)
|
||||
}
|
||||
|
||||
const handleHelpHover = () => {
|
||||
setHelpHoverColor(hoverColor)
|
||||
}
|
||||
|
||||
const handleHelpHoverOut = () => {
|
||||
setHelpHoverColor(hoverOutColor)
|
||||
}
|
||||
|
||||
const handleSoarHover = () => {
|
||||
setSoarHoverColor(hoverColor)
|
||||
}
|
||||
|
||||
const handleSoarHoverOut = () => {
|
||||
setSoarHoverColor(hoverOutColor)
|
||||
}
|
||||
|
||||
const handleHomeHoverOut = () => {
|
||||
setHomeHoverColor(hoverOutColor)
|
||||
}
|
||||
|
||||
const handleLoginHover = () => {
|
||||
setLoginHoverColor(hoverColor)
|
||||
}
|
||||
|
||||
const handleLoginHoverOut = () => {
|
||||
setLoginHoverColor(hoverOutColor)
|
||||
}
|
||||
|
||||
// Should be based on some path
|
||||
const logoCheck = !homePage ? null : null
|
||||
|
||||
|
||||
// Handle top bar or something
|
||||
const loginTextBrowser = !isLoggedIn ?
|
||||
<div style={{display: "flex"}}>
|
||||
<List style={{display: "flex", flexDirect: "row"}} component="nav">
|
||||
<ListItem style={{textAlign: "center", minWidth: "120px"}}>
|
||||
<Link to="/" style={hrefStyle}>
|
||||
<div onMouseOver={handleHomeHover} onMouseOut={handleHomeHoverOut} style={{color: HomeHoverColor, cursor: "pointer"}}>
|
||||
<Grid container direction="row" alignItems="center">
|
||||
<Grid item>
|
||||
<HomeIcon style={{marginTop: "3px", marginRight: "5px"}} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
Shuffle
|
||||
</Grid>
|
||||
</Grid>
|
||||
</div>
|
||||
</Link>
|
||||
</ListItem>
|
||||
<ListItem style={{textAlign: "center", marginLeft: "0px"}}>
|
||||
<Link to ="/docs/about" style={hrefStyle}>
|
||||
<div onMouseOver={handleSoarHover} onMouseOut={handleSoarHoverOut} style={{color: SoarHoverColor, cursor: "pointer"}}>
|
||||
About
|
||||
</div>
|
||||
</Link>
|
||||
</ListItem>
|
||||
</List>
|
||||
<div style={{flex: "7", display: "flex", flexDirection: "row-reverse"}}>
|
||||
<List style={{display: 'flex', flexDirection: 'row-reverse'}} component="nav">
|
||||
<ListItem style={{flex: "1", textAlign: "center"}}>
|
||||
<Link to="/login" style={hrefStyle}>
|
||||
<div onMouseOver={handleLoginHover} onMouseOut={handleLoginHoverOut} style={{color: LoginHoverColor, cursor: "pointer"}}>Login</div>
|
||||
</Link>
|
||||
</ListItem>
|
||||
</List>
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
<div style={{display: "flex"}}>
|
||||
<div style={{flex: "1", flexDirection: "row"}}>
|
||||
<List style={{display: "flex", flexDirect: "row", flex: "1"}} component="nav">
|
||||
<ListItem style={{textAlign: "center"}}>
|
||||
<Link to="/workflows" style={hrefStyle}>
|
||||
<div onMouseOver={handleSoarHover} onMouseOut={handleSoarHoverOut} style={{color: SoarHoverColor, cursor: "pointer"}}>Workflows</div>
|
||||
</Link>
|
||||
</ListItem>
|
||||
<ListItem style={{textAlign: "center"}}>
|
||||
<Link to="/apps" style={hrefStyle}>
|
||||
<div onMouseOver={handleHelpHover} onMouseOut={handleHelpHoverOut} style={{color: HelpHoverColor, cursor: "pointer"}}>Apps</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={{textAlign: "center"}}>
|
||||
<Link to="/docs/about" style={hrefStyle}>
|
||||
<div onMouseOver={handleDocsHover} onMouseOut={handleDocsHoverOut} style={{color: DocsHoverColor, cursor: "pointer"}}>Docs</div>
|
||||
</Link>
|
||||
</ListItem>
|
||||
{/*
|
||||
<ListItem style={{textAlign: "center"}}>
|
||||
<Link to="/configurations" style={hrefStyle}>
|
||||
<div onMouseOver={handleCredentialHover} onMouseOut={handleCredentialHoverOut} style={{color: CredentialHoverColor, cursor: "pointer"}}>Configure</div>
|
||||
</a>
|
||||
</ListItem>
|
||||
*/}
|
||||
</List>
|
||||
</div>
|
||||
<div style={{flex: "10", display: "flex", flexDirection: "row-reverse"}}>
|
||||
<List style={{display: 'flex', flexDirection: 'row-reverse'}} component="nav">
|
||||
<ListItem style={{flex: "1", textAlign: "center"}}>
|
||||
<div onMouseOver={handleLoginHover} onMouseOut={handleLoginHoverOut} onClick={handleClickLogout} style={{color: LoginHoverColor, cursor: "pointer"}}>
|
||||
Logout
|
||||
</div>
|
||||
</ListItem>
|
||||
{logoCheck}
|
||||
<ListItem style={{flex: "1", textAlign: "center"}}>
|
||||
<Link to="/settings" style={hrefStyle}>
|
||||
<Button
|
||||
style={{}}
|
||||
variant="outlined"
|
||||
color="primary"> Settings</Button>
|
||||
</Link>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Link to="/admin" style={hrefStyle}>
|
||||
<Button
|
||||
style={{}}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
>
|
||||
Admin
|
||||
</Button>
|
||||
</Link>
|
||||
</ListItem>
|
||||
</List>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
const loginTextMobile = !isLoggedIn ?
|
||||
<div style={{display: "flex"}}>
|
||||
<List style={{display: "flex", flexDirection: "row"}} component="nav">
|
||||
<ListItem style={{textAlign: "center"}}>
|
||||
<Link to="/" style={hrefStyle}>
|
||||
<div onMouseOver={handleHomeHover} onMouseOut={handleHomeHoverOut} style={{color: HomeHoverColor, cursor: "pointer"}}>
|
||||
<Grid container direction="row" alignItems="center">
|
||||
<Grid item>
|
||||
<HomeIcon style={{marginTop: "3px", marginRight: "5px"}} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</div>
|
||||
</Link>
|
||||
</ListItem>
|
||||
<ListItem style={{textAlign: "center"}}>
|
||||
<Link to="/docs/about" style={hrefStyle}>
|
||||
<div onMouseOver={handleSoarHover} onMouseOut={handleSoarHoverOut} style={{color: SoarHoverColor, cursor: "pointer"}}>
|
||||
About
|
||||
</div>
|
||||
</Link>
|
||||
</ListItem>
|
||||
</List>
|
||||
</div>
|
||||
:
|
||||
<div style={{display: "flex"}}>
|
||||
<div style={{flex: "1", flexDirection: "row"}}>
|
||||
<List style={{display: "flex", flexDirect: "row", flex: "1"}} component="nav">
|
||||
<ListItem style={{textAlign: "center"}}>
|
||||
<Link to="/" style={hrefStyle}>
|
||||
<div onMouseOver={handleHomeHover} onMouseOut={handleHomeHoverOut} style={{color: HomeHoverColor, cursor: "pointer"}}>Shuffle</div>
|
||||
</Link>
|
||||
</ListItem>
|
||||
<ListItem style={{textAlign: "center"}}>
|
||||
<Link to="/workflows" style={hrefStyle}>
|
||||
<div onMouseOver={handleSoarHover} onMouseOut={handleSoarHoverOut} style={{color: SoarHoverColor, cursor: "pointer"}}>Workflows</div>
|
||||
</Link>
|
||||
</ListItem>
|
||||
<ListItem style={{textAlign: "center"}}>
|
||||
<Link to="/apps" style={hrefStyle}>
|
||||
<div onMouseOver={handleHelpHover} onMouseOut={handleHelpHoverOut} style={{color: HelpHoverColor, cursor: "pointer"}}>Apps</div>
|
||||
</Link>
|
||||
</ListItem>
|
||||
{/*
|
||||
<ListItem style={{textAlign: "center"}}>
|
||||
<Link to="/configurations" style={hrefStyle}>
|
||||
<div onMouseOver={handleCredentialHover} onMouseOut={handleCredentialHoverOut} style={{color: CredentialHoverColor, cursor: "pointer"}}>Configure</div>
|
||||
</a>
|
||||
</ListItem>
|
||||
*/}
|
||||
</List>
|
||||
</div>
|
||||
<div style={{flex: "10", display: "flex", flexDirection: "row-reverse"}}>
|
||||
<List style={{display: 'flex', flexDirection: 'row-reverse'}} component="nav">
|
||||
<ListItem style={{flex: "1", textAlign: "center"}}>
|
||||
<div onMouseOver={handleLoginHover} onMouseOut={handleLoginHoverOut} onClick={handleClickLogout} style={{color: LoginHoverColor, cursor: "pointer"}}>
|
||||
Logout
|
||||
</div>
|
||||
</ListItem>
|
||||
{logoCheck}
|
||||
<ListItem style={{flex: "1", textAlign: "center"}}>
|
||||
<Link to="/settings" style={hrefStyle}>
|
||||
<Button
|
||||
style={{}}
|
||||
variant="contained"
|
||||
color="primary"> Settings</Button>
|
||||
</Link>
|
||||
</ListItem>
|
||||
<ListItem></ListItem>
|
||||
</List>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// <Divider style={{height: "1px", width: "100%", backgroundColor: "rgb(91, 96, 100)"}}/>
|
||||
const loadedCheck = isLoaded ?
|
||||
<div>
|
||||
<BrowserView>
|
||||
{loginTextBrowser}
|
||||
</BrowserView>
|
||||
<MobileView>
|
||||
{loginTextMobile}
|
||||
</MobileView>
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
</div>
|
||||
|
||||
// <div style={{backgroundImage: "linear-gradient(-90deg,#342f78 0,#29255e 50%,#1b1947 100%"}}>
|
||||
return (
|
||||
<div>
|
||||
{loadedCheck}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
|
||||
const Hooks = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>Hooks</h1>
|
||||
|
||||
<p>
|
||||
Built to suit any organization
|
||||
</p>
|
||||
|
||||
<p>
|
||||
WAT
|
||||
</p>
|
||||
|
||||
<p>
|
||||
</p>
|
||||
|
||||
<p></p>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Hooks;
|
||||
@@ -0,0 +1,179 @@
|
||||
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 ScheduleIcon from '@material-ui/icons/Schedule';
|
||||
import Web from '@material-ui/icons/Web';
|
||||
import AccountTree from '@material-ui/icons/AccountTree';
|
||||
|
||||
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;
|
||||
@@ -0,0 +1,21 @@
|
||||
import React, {} from 'react';
|
||||
|
||||
const bodyDivStyle = {
|
||||
transform: "translate(-50%, -50%)",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
position: "absolute",
|
||||
width: "500px",
|
||||
color: "white",
|
||||
}
|
||||
|
||||
// Should be different if logged in :|
|
||||
const LandingPageLoggedin = (props) => {
|
||||
|
||||
return(
|
||||
<div style={bodyDivStyle}>
|
||||
TMP landingpage when logged in
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default LandingPageLoggedin;
|
||||
@@ -0,0 +1,349 @@
|
||||
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 ScheduleIcon from '@material-ui/icons/Schedule';
|
||||
import Web from '@material-ui/icons/Web';
|
||||
import AccountTree from '@material-ui/icons/AccountTree';
|
||||
import InfoIcon from '@material-ui/icons/Info';
|
||||
import ArrowForwardIcon from '@material-ui/icons/ArrowForward';
|
||||
import CreateIcon from '@material-ui/icons/Create';
|
||||
|
||||
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", <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/features", <AccountTree 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;
|
||||
@@ -0,0 +1,255 @@
|
||||
/* eslint-disable react/no-multi-comp */
|
||||
import React, {useState} from 'react';
|
||||
import { makeStyles } from '@material-ui/styles';
|
||||
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
|
||||
const hrefStyle = {
|
||||
color: "white",
|
||||
textDecoration: "none"
|
||||
}
|
||||
|
||||
const bodyDivStyle = {
|
||||
margin: "auto",
|
||||
marginTop: "100px",
|
||||
width: "500px",
|
||||
}
|
||||
|
||||
const surfaceColor = "#27292D"
|
||||
const inputColor = "#383B40"
|
||||
|
||||
const boxStyle = {
|
||||
paddingLeft: "30px",
|
||||
paddingRight: "30px",
|
||||
paddingBottom: "30px",
|
||||
paddingTop: "30px",
|
||||
backgroundColor: surfaceColor,
|
||||
}
|
||||
|
||||
const useStyles = makeStyles({
|
||||
notchedOutline: {
|
||||
borderColor: "#f85a3e !important"
|
||||
},
|
||||
});
|
||||
|
||||
const LoginDialog = props => {
|
||||
const { globalUrl, isLoaded, isLoggedIn, setIsLoggedIn, setCookie, register } = props;
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [firstRequest, setFirstRequest] = useState(true);
|
||||
|
||||
// Used to swap from login to register. True = login, false = register
|
||||
|
||||
const classes = useStyles();
|
||||
// Error messages etc
|
||||
const [loginInfo, setLoginInfo] = useState("");
|
||||
|
||||
const handleValidateForm = () => {
|
||||
return (username.length > 1 && password.length > 8);
|
||||
}
|
||||
|
||||
if (isLoggedIn === true) {
|
||||
window.location.pathname = "/workflows"
|
||||
}
|
||||
|
||||
|
||||
const checkAdmin = () => {
|
||||
const url = globalUrl+'/api/v1/checkusers';
|
||||
fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then(response =>
|
||||
response.json().then(responseJson => {
|
||||
if (responseJson["success"] === false) {
|
||||
setLoginInfo(responseJson["reason"])
|
||||
} else {
|
||||
if (responseJson.reason === "stay") {
|
||||
window.location.pathname = "/adminsetup"
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
.catch(error => {
|
||||
setLoginInfo("Error in userdata: ", error)
|
||||
})
|
||||
}
|
||||
|
||||
if (firstRequest) {
|
||||
setFirstRequest(false)
|
||||
checkAdmin()
|
||||
}
|
||||
|
||||
const onSubmit = (e) => {
|
||||
e.preventDefault()
|
||||
// FIXME - add some check here ROFL
|
||||
|
||||
// Just use this one?
|
||||
var data = {"username": username, "password": password}
|
||||
var baseurl = globalUrl
|
||||
if (register) {
|
||||
var url = baseurl+'/api/v1/login';
|
||||
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) {
|
||||
setLoginInfo(responseJson["reason"])
|
||||
} else {
|
||||
setLoginInfo("Successful login, rerouting")
|
||||
for (var key in responseJson["cookies"]) {
|
||||
setCookie(responseJson["cookies"][key].key, responseJson["cookies"][key].value, {path: "/"})
|
||||
}
|
||||
|
||||
setIsLoggedIn(true)
|
||||
window.location.pathname = "/workflows"
|
||||
}
|
||||
}),
|
||||
)
|
||||
.catch(error => {
|
||||
setLoginInfo("Error in userdata: " + error)
|
||||
});
|
||||
} else {
|
||||
url = baseurl+'/api/v1/register';
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then(response =>
|
||||
response.json().then(responseJson => {
|
||||
if (responseJson["success"] === false) {
|
||||
setLoginInfo(responseJson["reason"])
|
||||
} else {
|
||||
setLoginInfo("Successful register :)")
|
||||
}
|
||||
}),
|
||||
)
|
||||
.catch(error => {
|
||||
setLoginInfo("Error in userdata: ", error)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const onChangeUser = (e) => {
|
||||
setUsername(e.target.value)
|
||||
}
|
||||
|
||||
const onChangePass = (e) => {
|
||||
setPassword(e.target.value)
|
||||
}
|
||||
|
||||
//const onClickRegister = () => {
|
||||
// if (props.location.pathname === "/login") {
|
||||
// window.location.pathname = "/register"
|
||||
// } else {
|
||||
// window.location.pathname = "/login"
|
||||
// }
|
||||
|
||||
// setLoginCheck(!register)
|
||||
//}
|
||||
|
||||
//var loginChange = register ? (<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 = register ? <div>Login</div> : <div>Register</div>
|
||||
|
||||
// <DialogTitle>{formtitle}</DialogTitle>
|
||||
|
||||
const basedata =
|
||||
<div style={bodyDivStyle}>
|
||||
<Paper style={boxStyle}>
|
||||
<form onSubmit={onSubmit} style={{margin: "15px 15px 15px 15px"}}>
|
||||
<h2>{formtitle}</h2>
|
||||
Username
|
||||
<div>
|
||||
<TextField
|
||||
color="primary"
|
||||
style={{backgroundColor: inputColor}}
|
||||
autoFocus
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: classes.notchedOutline,
|
||||
},
|
||||
style:{
|
||||
height: "50px",
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
required
|
||||
fullWidth={true}
|
||||
autoComplete="username"
|
||||
placeholder="username@example.com"
|
||||
id="emailfield"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
onChange={onChangeUser}
|
||||
/>
|
||||
</div>
|
||||
Password
|
||||
<div>
|
||||
<TextField
|
||||
color="primary"
|
||||
style={{backgroundColor: inputColor,}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: classes.notchedOutline,
|
||||
},
|
||||
style:{
|
||||
height: "50px",
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
required
|
||||
id="outlined-password-input"
|
||||
fullWidth={true}
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
placeholder="**********"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
onChange={onChangePass}
|
||||
/>
|
||||
</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: "10px"}}>
|
||||
{loginInfo}
|
||||
</div>
|
||||
</form>
|
||||
</Paper>
|
||||
</div>
|
||||
|
||||
const loadedCheck = isLoaded ?
|
||||
<div>
|
||||
{basedata}
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
</div>
|
||||
|
||||
return (
|
||||
<div>
|
||||
{loadedCheck}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LoginDialog;
|
||||
@@ -0,0 +1,139 @@
|
||||
/* 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;
|
||||
@@ -0,0 +1,11 @@
|
||||
import React, { } from 'react';
|
||||
|
||||
const Oauth2 = (props) => {
|
||||
return (
|
||||
<div>
|
||||
tmp
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Oauth2;
|
||||
@@ -0,0 +1,72 @@
|
||||
import React from 'react';
|
||||
|
||||
const Body = {
|
||||
maxWidth: '1000px',
|
||||
minWidth: '768px',
|
||||
margin: 'auto',
|
||||
display: "flex",
|
||||
heigth: "100%",
|
||||
color: "white",
|
||||
//textAlign: "center",
|
||||
};
|
||||
|
||||
const SideBar = {
|
||||
maxWidth: "250px",
|
||||
flex: "1",
|
||||
}
|
||||
|
||||
const hrefStyle = {
|
||||
color: "#385f71",
|
||||
textDecoration: "none"
|
||||
}
|
||||
|
||||
const Post = (props) => {
|
||||
const { currentPost, isLoaded } = props;
|
||||
|
||||
const postData =
|
||||
<div style={Body}>
|
||||
<div style={SideBar}>
|
||||
<ul style={{listStyle: "none", paddingLeft: "0"}}>
|
||||
<li style={{marginTop: "10px"}}>
|
||||
<a style={hrefStyle} href="/">
|
||||
<h2>Home</h2>
|
||||
</a>
|
||||
</li>
|
||||
<li style={{marginTop: "10px"}}>
|
||||
<a style={hrefStyle} href="/docs">
|
||||
<h2>Schedules</h2>
|
||||
</a>
|
||||
</li>
|
||||
<li style={{marginTop: "10px"}}>
|
||||
<a style={hrefStyle} href="/docs/about">
|
||||
<h2>About</h2>
|
||||
</a>
|
||||
</li>
|
||||
<li style={{marginTop: "10px"}}>
|
||||
<a style={hrefStyle} href="/docs/privacy-policy">
|
||||
<h2>Privacy Policy</h2>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div style={{flex: "1"}}>
|
||||
{currentPost}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
const loadedCheck = isLoaded ?
|
||||
<div>
|
||||
{postData}
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
</div>
|
||||
|
||||
return (
|
||||
<div>
|
||||
{loadedCheck}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Post;
|
||||
@@ -0,0 +1,122 @@
|
||||
import React from 'react';
|
||||
|
||||
const PrivacyPolicy = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>Privacy Policy</h1>
|
||||
|
||||
<p>Effective date: 17.08.2019</p>
|
||||
|
||||
|
||||
<p>We operate the shuffler.io website.</p>
|
||||
|
||||
<p>This page informs you of our policies regarding the collection, use, and disclosure of personal data when you use our service and the choices you have associated with that data.</p>
|
||||
|
||||
<p>We use your data to provide and improve the service. By using the service, you agree to the collection and use of information in accordance with this policy. Unless otherwise defined in this Privacy Policy, terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, accessible from shuffler.io</p>
|
||||
|
||||
|
||||
<h2>Information Collection And Use</h2>
|
||||
|
||||
<p>We collect several different types of information for various purposes to provide and improve our service to you.</p>
|
||||
|
||||
<h3>Types of Data Collected</h3>
|
||||
|
||||
<h4>Personal Data</h4>
|
||||
|
||||
<p>While using our service, we may ask you to provide us with certain personally identifiable information that can be used to contact or identify you ("Personal Data"). Personally identifiable information may include, but is not limited to:</p>
|
||||
|
||||
<ul>
|
||||
<li>Cookies and Usage Data</li>
|
||||
</ul>
|
||||
|
||||
<h4>Usage Data</h4>
|
||||
|
||||
<p>We may also collect information how the service is accessed and used ("Usage Data"). This Usage Data may include information such as your computer's Internet Protocol address (e.g. IP address), browser type, browser version, the pages of our service that you visit, the time and date of your visit, the time spent on those pages, unique device identifiers and other diagnostic data.</p>
|
||||
|
||||
<h4>Tracking & Cookies Data</h4>
|
||||
<p>We use cookies and similar tracking technologies to track the activity on our service and hold certain information.</p>
|
||||
<p>Cookies are files with small amount of data which may include an anonymous unique identifier. Cookies are sent to your browser from a website and stored on your device. Tracking technologies also used are beacons, tags, and scripts to collect and track information and to improve and analyze our service.</p>
|
||||
<p>You can instruct your browser to refuse all cookies or to indicate when a cookie is being sent. However, if you do not accept cookies, you may not be able to use some portions of our service.</p>
|
||||
<p>Examples of Cookies we use:</p>
|
||||
<ul>
|
||||
<li><strong>Session Cookies.</strong> We use Session Cookies to operate our service.</li>
|
||||
<li><strong>Preference Cookies.</strong> We use Preference Cookies to remember your preferences and various settings.</li>
|
||||
<li><strong>Security Cookies.</strong> We use Security Cookies for security purposes.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Use of Data</h2>
|
||||
|
||||
<p>Shuffler uses the collected data for various purposes:</p>
|
||||
<ul>
|
||||
<li>To provide and maintain the service</li>
|
||||
<li>To notify you about changes to our service</li>
|
||||
<li>To allow you to participate in interactive features of our service when you choose to do so</li>
|
||||
<li>To provide customer care and support</li>
|
||||
<li>To provide analysis or valuable information so that we can improve the service</li>
|
||||
<li>To monitor the usage of the service</li>
|
||||
<li>To detect, prevent and address technical issues</li>
|
||||
</ul>
|
||||
|
||||
<h2>Transfer Of Data</h2>
|
||||
<p>Your information, including Personal Data, may be transferred to — and maintained on — computers located outside of your state, province, country or other governmental jurisdiction where the data protection laws may differ than those from your jurisdiction.</p>
|
||||
<p>If you are located outside Norway and choose to provide information to us, please note that we transfer the data, including Personal Data, to Norway and process it there.</p>
|
||||
<p>Your consent to this Privacy Policy followed by your submission of such information represents your agreement to that transfer.</p>
|
||||
<p>Shuffler will take all steps reasonably necessary to ensure that your data is treated securely and in accordance with this Privacy Policy and no transfer of your Personal Data will take place to an organization or a country unless there are adequate controls in place including the security of your data and other personal information.</p>
|
||||
|
||||
<h2>Disclosure Of Data</h2>
|
||||
|
||||
<h3>Legal Requirements</h3>
|
||||
<p>Shuffler may disclose your Personal Data in the good faith belief that such action is necessary to:</p>
|
||||
<ul>
|
||||
<li>To comply with a legal obligation</li>
|
||||
<li>To protect and defend the rights or property of Shuffler</li>
|
||||
<li>To prevent or investigate possible wrongdoing in connection with the service</li>
|
||||
<li>To protect the personal safety of users of the service or the public</li>
|
||||
<li>To protect against legal liability</li>
|
||||
</ul>
|
||||
|
||||
<h2>Security Of Data</h2>
|
||||
<p>The security of your data is important to us, but remember that no method of transmission over the Internet, or method of electronic storage is 100% secure. While we strive to use commercially acceptable means to protect your Personal Data, we cannot guarantee its absolute security.</p>
|
||||
|
||||
<h2>Service Providers</h2>
|
||||
<p>We may employ third party companies and individuals to facilitate our service ("service Providers"), to provide the service on our behalf, to perform service-related services or to assist us in analyzing how our service is used.</p>
|
||||
<p>These third parties have access to your Personal Data only to perform these tasks on our behalf and are obligated not to disclose or use it for any other purpose.</p>
|
||||
|
||||
<h3>Analytics</h3>
|
||||
<p>We may use third-party service Providers to monitor and analyze the use of our service.</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p><strong>Google Analytics</strong></p>
|
||||
<p>Google Analytics is a web analytics service offered by Google that tracks and reports website traffic. Google uses the data collected to track and monitor the use of our service. This data is shared with other Google services. Google may use the collected data to contextualize and personalize the ads of its own advertising network.</p>
|
||||
<p>You can opt-out of having made your activity on the service available to Google Analytics by installing the Google Analytics opt-out browser add-on. The add-on prevents the Google Analytics JavaScript (ga.js, analytics.js, and dc.js) from sharing information with Google Analytics about visits activity.</p> <p>For more information on the privacy practices of Google, please visit the Google Privacy & Terms web page: <a href="https://policies.google.com/privacy?hl=en">https://policies.google.com/privacy?hl=en</a></p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<h2>Links To Other Sites</h2>
|
||||
<p>Our service may contain links to other sites that are not operated by us. If you click on a third party link, you will be directed to that third party's site. We strongly advise you to review the Privacy Policy of every site you visit.</p>
|
||||
<p>We have no control over and assume no responsibility for the content, privacy policies or practices of any third party sites or services.</p>
|
||||
|
||||
|
||||
<h2>Children's Privacy</h2>
|
||||
<p>Our service does not address anyone under the age of 18 ("Children").</p>
|
||||
<p>We do not knowingly collect personally identifiable information from anyone under the age of 18. If you are a parent or guardian and you are aware that your Children has provided us with Personal Data, please contact us. If we become aware that we have collected Personal Data from children without verification of parental consent, we take steps to remove that information from our servers.</p>
|
||||
|
||||
|
||||
<h2>Changes To This Privacy Policy</h2>
|
||||
<p>We may update our Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page.</p>
|
||||
<p>We will let you know via email and/or a prominent notice on our service, prior to the change becoming effective and update the "effective date" at the top of this Privacy Policy.</p>
|
||||
<p>You are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when they are posted on this page.</p>
|
||||
|
||||
|
||||
<h2>Contact Us</h2>
|
||||
<p>If you have any questions about this Privacy Policy, please contact us:</p>
|
||||
<ul>
|
||||
<li>By email: fredrik_9490@hotmail.com</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PrivacyPolicy;
|
||||
@@ -0,0 +1,93 @@
|
||||
import React, {useState, useEffect} from 'react';
|
||||
|
||||
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 = (props) => {
|
||||
const { globalUrl, isLoaded, surfaceColor, } = props;
|
||||
|
||||
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;
|
||||
@@ -0,0 +1,139 @@
|
||||
/* 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;
|
||||
@@ -0,0 +1,220 @@
|
||||
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
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
|
||||
const Schedules = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>Schedules</h1>
|
||||
|
||||
<p>
|
||||
Built to suit any organization
|
||||
</p>
|
||||
|
||||
<p>
|
||||
WAT
|
||||
</p>
|
||||
|
||||
<p>
|
||||
</p>
|
||||
|
||||
<p></p>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Schedules;
|
||||
@@ -0,0 +1,457 @@
|
||||
import React, {useState, useEffect} from 'react';
|
||||
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Divider from '@material-ui/core/Divider';
|
||||
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
|
||||
|
||||
//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, userdata, surfaceColor, inputColor } = props;
|
||||
|
||||
const [username, setUsername] = useState("");
|
||||
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 [currentPassword, setCurrentPassword] = useState("");
|
||||
const [newPassword, setNewPassword] = useState("");
|
||||
const [newPassword2, setNewPassword2] = useState("");
|
||||
|
||||
// Used for error messages etc
|
||||
const [formMessage, ] = useState("");
|
||||
const [passwordFormMessage, setPasswordFormMessage] = useState("");
|
||||
|
||||
const [firstrequest, setFirstRequest] = useState(true)
|
||||
|
||||
const [userInfo, ] = useState(userdata)
|
||||
const [userSettings, setUserSettings] = useState({})
|
||||
|
||||
const bodyDivStyle = {
|
||||
margin: "auto",
|
||||
textAlign: "center",
|
||||
width: "1100px",
|
||||
}
|
||||
|
||||
const boxStyle = {
|
||||
flex: "1",
|
||||
marginLeft: "10px",
|
||||
marginRight: "10px",
|
||||
paddingLeft: "30px",
|
||||
paddingRight: "30px",
|
||||
paddingBottom: "30px",
|
||||
paddingTop: "30px",
|
||||
backgroundColor: surfaceColor,
|
||||
display: "flex",
|
||||
flexDirection: "column"
|
||||
}
|
||||
|
||||
const onPasswordChange = () => {
|
||||
const data = {"currentpassword": currentPassword, "newpassword": newPassword, "newpassword2": newPassword2}
|
||||
const url = globalUrl+'/api/v1/passwordchange';
|
||||
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.")
|
||||
});
|
||||
}
|
||||
|
||||
const generateApikey = () => {
|
||||
fetch(globalUrl+"/api/v1/generateapikey", {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for WORKFLOW EXECUTION :O!")
|
||||
}
|
||||
|
||||
return response.json()
|
||||
})
|
||||
.then((responseJson) => {
|
||||
setUserSettings(responseJson)
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
});
|
||||
}
|
||||
|
||||
const getSettings = () => {
|
||||
fetch(globalUrl+"/api/v1/getsettings", {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for WORKFLOW EXECUTION :O!")
|
||||
}
|
||||
|
||||
return response.json()
|
||||
})
|
||||
.then((responseJson) => {
|
||||
console.log(responseJson)
|
||||
setUserSettings(responseJson)
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
});
|
||||
}
|
||||
|
||||
// Gotta be a better way of doing this rofl
|
||||
const setFields = () => {
|
||||
if (userInfo.username !== undefined) {
|
||||
if (userInfo.username.length > 0) {
|
||||
setUsername(userInfo.username)
|
||||
}
|
||||
if (userInfo.firstname.length > 0) {
|
||||
setFirstname(userInfo.firstname)
|
||||
}
|
||||
if (userInfo.lastname.length > 0) {
|
||||
setLastname(userInfo.lastname)
|
||||
}
|
||||
if (userInfo.title.length > 0) {
|
||||
setTitle(userInfo.title)
|
||||
}
|
||||
if (userInfo.companyname.length > 0) {
|
||||
setCompanyname(userInfo.companyname)
|
||||
}
|
||||
if (userInfo.phone.length > 0) {
|
||||
setPhone(userInfo.phone)
|
||||
}
|
||||
if (userInfo.email.length > 0) {
|
||||
setEmail(userInfo.email)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This should "always" have data
|
||||
useEffect(() => {
|
||||
if (firstrequest) {
|
||||
setFirstRequest(false)
|
||||
getSettings()
|
||||
}
|
||||
|
||||
if (Object.getOwnPropertyNames(userInfo).length > 0 && (username === "" && email === "")) {
|
||||
setFields()
|
||||
}
|
||||
})
|
||||
|
||||
// Random names for type & autoComplete. Didn't research :^)
|
||||
const landingpageData =
|
||||
<div style={{display: "flex", marginTop: "80px"}}>
|
||||
<Paper style={boxStyle}>
|
||||
<h2>APIKEY</h2>
|
||||
<TextField
|
||||
style={{backgroundColor: inputColor, flex: "1"}}
|
||||
InputProps={{
|
||||
style:{
|
||||
height: "50px",
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
color="primary"
|
||||
value={userSettings.apikey}
|
||||
required
|
||||
disabled
|
||||
fullWidth={true}
|
||||
placeholder="APIKEY"
|
||||
id="standard-required"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
/>
|
||||
<Button
|
||||
style={{width: "100%", height: "40px", marginTop: "10px"}}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => generateApikey()}
|
||||
>Re-Generate APIKEY</Button>
|
||||
<Divider style={{marginTop: "40px"}}/>
|
||||
<h2>Settings</h2>
|
||||
<div style={{flex: "1", display: "flex", flexDirection: "row"}}>
|
||||
<TextField
|
||||
style={{backgroundColor: inputColor, flex: "1"}}
|
||||
InputProps={{
|
||||
style:{
|
||||
height: "50px",
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
color="primary"
|
||||
required
|
||||
fullWidth={true}
|
||||
value={username}
|
||||
placeholder="Username"
|
||||
type="username"
|
||||
id="standard-required"
|
||||
autoComplete="username"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
onChange={e => setUsername(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div style={{flex: "1", display: "flex", flexDirection: "row"}}>
|
||||
<TextField
|
||||
style={{backgroundColor: inputColor, flex: "1", marginRight: "15px"}}
|
||||
InputProps={{
|
||||
style:{
|
||||
height: "50px",
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
color="primary"
|
||||
required
|
||||
fullWidth={true}
|
||||
value={firstname}
|
||||
placeholder="First Name"
|
||||
type="firstname"
|
||||
id="standard-required"
|
||||
autoComplete="firstname"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
onChange={e => setFirstname(e.target.value)}
|
||||
/>
|
||||
<TextField
|
||||
style={{backgroundColor: inputColor, flex: "1", marginLeft: "15px"}}
|
||||
InputProps={{
|
||||
style:{
|
||||
height: "50px",
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
color="primary"
|
||||
required
|
||||
value={lastname}
|
||||
fullWidth={true}
|
||||
placeholder="Last Name"
|
||||
type="lastname"
|
||||
id="standard-required"
|
||||
autoComplete="lastname"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
onChange={e => setLastname(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div style={{flex: "1", display: "flex", flexDirection: "row"}}>
|
||||
<TextField
|
||||
style={{backgroundColor: inputColor, flex: "1", marginRight: "15px",}}
|
||||
InputProps={{
|
||||
style:{
|
||||
height: "50px",
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
color="primary"
|
||||
required
|
||||
fullWidth={true}
|
||||
placeholder="Job Title"
|
||||
value={title}
|
||||
type="jobtitle"
|
||||
id="standard-required"
|
||||
autoComplete="jobtitle"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
onChange={e => setTitle(e.target.value)}
|
||||
/>
|
||||
<TextField
|
||||
style={{backgroundColor: inputColor, flex: "1", marginLeft: "15px"}}
|
||||
InputProps={{
|
||||
style:{
|
||||
height: "50px",
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
color="primary"
|
||||
required
|
||||
fullWidth={true}
|
||||
type="companyname"
|
||||
value={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
|
||||
style={{backgroundColor: inputColor, flex: "1", marginRight: "15px",}}
|
||||
InputProps={{
|
||||
style:{
|
||||
height: "50px",
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
color="primary"
|
||||
required
|
||||
fullWidth={true}
|
||||
placeholder="Email"
|
||||
type="email"
|
||||
value={email}
|
||||
id="standard-required"
|
||||
autoComplete="email"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
onChange={e => setEmail(e.target.value)}
|
||||
/>
|
||||
<TextField
|
||||
style={{backgroundColor: inputColor, flex: "1", marginLeft: "15px",}}
|
||||
InputProps={{
|
||||
style:{
|
||||
height: "50px",
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
color="primary"
|
||||
required
|
||||
fullWidth={true}
|
||||
type="phone"
|
||||
value={phone}
|
||||
placeholder="Phone number"
|
||||
id="standard-required"
|
||||
autoComplete="phone"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
onChange={e => setPhone(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
disabled={firstname.length <= 0 || lastname.length <= 0 || title.length <= 0 || companyname.length <= 0 || email.length <= 0 || phone.length <= 0}
|
||||
style={{width: "100%", height: "40px", marginTop: "10px"}}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => console.log("SUBMIT NORMAL INFO!!")}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
<h3>{formMessage}</h3>
|
||||
<Divider />
|
||||
<h2>Password</h2>
|
||||
<div style={{flex: "1", display: "flex", flexDirection: "row"}}>
|
||||
<TextField
|
||||
style={{backgroundColor: inputColor, flex: "1"}}
|
||||
InputProps={{
|
||||
style:{
|
||||
height: "50px",
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
color="primary"
|
||||
required
|
||||
fullWidth={true}
|
||||
placeholder="Current Password"
|
||||
type="password"
|
||||
id="standard-required"
|
||||
autoComplete="password"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
onChange={e => setCurrentPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div style={{flex: "1", display: "flex", flexDirection: "row"}}>
|
||||
<TextField
|
||||
style={{backgroundColor: inputColor, flex: "1", marginRight: "15px",}}
|
||||
InputProps={{
|
||||
style:{
|
||||
height: "50px",
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
color="primary"
|
||||
required
|
||||
fullWidth={true}
|
||||
placeholder="New password"
|
||||
type="password"
|
||||
id="standard-required"
|
||||
autoComplete="password"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
onChange={e => setNewPassword(e.target.value)}
|
||||
/>
|
||||
<TextField
|
||||
style={{backgroundColor: inputColor, flex: "1", marginLeft: "15px",}}
|
||||
InputProps={{
|
||||
style:{
|
||||
height: "50px",
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
color="primary"
|
||||
required
|
||||
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 || currentPassword.length < 10}
|
||||
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 && !firstrequest ?
|
||||
<div style={bodyDivStyle}>
|
||||
{landingpageData}
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
</div>
|
||||
|
||||
return(
|
||||
<div>
|
||||
{loadedCheck}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default Settings;
|
||||
@@ -0,0 +1,141 @@
|
||||
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;
|
||||
@@ -0,0 +1,295 @@
|
||||
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
|
||||
@@ -0,0 +1,831 @@
|
||||
import React, { useEffect} from 'react';
|
||||
import { useInterval } from 'react-powerhooks';
|
||||
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import Divider from '@material-ui/core/Divider';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import FormControl from '@material-ui/core/FormControl';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import Menu from '@material-ui/core/Menu';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
import MoreVertIcon from '@material-ui/icons/MoreVert';
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
import Switch from '@material-ui/core/Switch';
|
||||
|
||||
//import JSONPretty from 'react-json-pretty';
|
||||
//import JSONPrettyMon from 'react-json-pretty/dist/monikai'
|
||||
import ReactJson from 'react-json-view'
|
||||
|
||||
import { useAlert } from "react-alert";
|
||||
|
||||
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';
|
||||
const surfaceColor = "#27292D"
|
||||
|
||||
const Workflows = (props) => {
|
||||
const { globalUrl, isLoggedIn, isLoaded, } = props;
|
||||
document.title = "Shuffle - Workflows"
|
||||
|
||||
const alert = useAlert()
|
||||
|
||||
const [workflows, setWorkflows] = React.useState([]);
|
||||
const [selectedWorkflow, setSelectedWorkflow] = React.useState({});
|
||||
const [selectedExecution, setSelectedExecution] = React.useState({});
|
||||
const [workflowExecutions, setWorkflowExecutions] = React.useState([]);
|
||||
const [firstrequest, setFirstrequest] = React.useState(true)
|
||||
const [workflowDone, setWorkflowDone] = React.useState(false)
|
||||
const [, setTrackingId] = React.useState("")
|
||||
|
||||
const [collapseJson, setCollapseJson] = React.useState(false)
|
||||
|
||||
const [modalOpen, setModalOpen] = React.useState(false);
|
||||
const [newWorkflowName, setNewWorkflowname] = React.useState("");
|
||||
const [newWorkflowDescription, setNewWorkflowDescription] = React.useState("");
|
||||
const { start, stop } = useInterval({
|
||||
duration: 5000,
|
||||
startImmediate: false,
|
||||
callback: () => {
|
||||
getWorkflowExecution(selectedWorkflow.id)
|
||||
}
|
||||
});
|
||||
|
||||
const getAvailableWorkflows = () => {
|
||||
fetch(globalUrl+"/api/v1/workflows", {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for workflows :O!")
|
||||
return
|
||||
}
|
||||
return response.json()
|
||||
})
|
||||
.then((responseJson) => {
|
||||
console.log(responseJson)
|
||||
setSelectedExecution({})
|
||||
setWorkflowExecutions([])
|
||||
|
||||
if (responseJson !== undefined) {
|
||||
setWorkflows(responseJson)
|
||||
setWorkflowDone(true)
|
||||
} else {
|
||||
if (isLoggedIn) {
|
||||
alert.error("An error occurred while loading workflows")
|
||||
} else {
|
||||
window.location.pathname = "/login"
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (responseJson.length > 0){
|
||||
setSelectedWorkflow(responseJson[0])
|
||||
getWorkflowExecution(responseJson[0].id)
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert.error(error.toString())
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (workflows.length <= 0 && firstrequest) {
|
||||
setFirstrequest(false)
|
||||
getAvailableWorkflows()
|
||||
}
|
||||
})
|
||||
|
||||
const viewStyle = {
|
||||
color: "#ffffff",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
maxWidth: "1768px",
|
||||
margin: "auto",
|
||||
maxHeight: "90vh",
|
||||
}
|
||||
|
||||
const emptyWorkflowStyle = {
|
||||
paddingTop: "200px",
|
||||
width: "1024px",
|
||||
margin: "auto",
|
||||
}
|
||||
|
||||
const boxStyle = {
|
||||
padding: "20px 20px 20px 20px",
|
||||
width: "100%",
|
||||
height: "250px",
|
||||
color: "white",
|
||||
backgroundColor: surfaceColor,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}
|
||||
|
||||
|
||||
const scrollStyle = {
|
||||
marginTop: "10px",
|
||||
overflow: "scroll",
|
||||
height: "90%",
|
||||
overflowX: "auto",
|
||||
overflowY: "auto",
|
||||
}
|
||||
|
||||
const paperAppStyle = {
|
||||
minHeight: "100px",
|
||||
maxHeight: "100px",
|
||||
minWidth: "100%",
|
||||
maxWidth: "100%",
|
||||
marginTop: "5px",
|
||||
color: "white",
|
||||
backgroundColor: surfaceColor,
|
||||
cursor: "pointer",
|
||||
display: "flex",
|
||||
}
|
||||
|
||||
const getWorkflowExecution = (id) => {
|
||||
fetch(globalUrl+"/api/v1/workflows/"+id+"/executions", {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for WORKFLOW EXECUTION :O!")
|
||||
}
|
||||
|
||||
return response.json()
|
||||
})
|
||||
.then((responseJson) => {
|
||||
setWorkflowExecutions(responseJson)
|
||||
if (responseJson.length > 0 && Object.getOwnPropertyNames(selectedExecution).length === 0) {
|
||||
setSelectedExecution(responseJson[0])
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert.error(error.toString())
|
||||
});
|
||||
}
|
||||
|
||||
const abortExecution = (workflowid, executionid) => {
|
||||
alert.success("Aborting execution")
|
||||
fetch(globalUrl+"/api/v1/workflows/"+workflowid+"/executions/"+executionid+"/abort", {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for WORKFLOW EXECUTION :O!")
|
||||
}
|
||||
getWorkflowExecution(workflowid)
|
||||
|
||||
return response.json()
|
||||
})
|
||||
.catch(error => {
|
||||
alert.error(error.toString())
|
||||
});
|
||||
}
|
||||
|
||||
const executeWorkflow = (id) => {
|
||||
alert.show("Executing workflow "+id)
|
||||
setTrackingId(id)
|
||||
fetch(globalUrl+"/api/v1/workflows/"+id+"/execute", {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for WORKFLOW EXECUTION :O!")
|
||||
}
|
||||
|
||||
return response.json()
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (!responseJson.success) {
|
||||
alert.error(responseJson.reason)
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert.error(error.toString())
|
||||
});
|
||||
|
||||
if (id === selectedWorkflow.id) {
|
||||
sleep(2000).then(() => {
|
||||
stop()
|
||||
start()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function sleep (time) {
|
||||
return new Promise((resolve) => setTimeout(resolve, time));
|
||||
}
|
||||
|
||||
const exportWorkflow = (data) => {
|
||||
console.log("export")
|
||||
let dataStr = JSON.stringify(data)
|
||||
let dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
|
||||
let exportFileDefaultName = data.name+'.json';
|
||||
|
||||
let linkElement = document.createElement('a');
|
||||
linkElement.setAttribute('href', dataUri);
|
||||
linkElement.setAttribute('download', exportFileDefaultName);
|
||||
linkElement.click();
|
||||
}
|
||||
|
||||
const copyWorkflow = (data) => {
|
||||
alert.success("Copying workflow "+data.name)
|
||||
data.id = ""
|
||||
data.name = data.name+"_copy"
|
||||
|
||||
fetch(globalUrl+"/api/v1/workflows", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for workflows :O!")
|
||||
return
|
||||
}
|
||||
return response.json()
|
||||
})
|
||||
.then((responseJson) => {
|
||||
getAvailableWorkflows()
|
||||
})
|
||||
.catch(error => {
|
||||
alert.error(error.toString())
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const deleteWorkflow = (id) => {
|
||||
alert.success("Deleted workflow "+id)
|
||||
fetch(globalUrl+"/api/v1/workflows/"+id, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for setting workflows :O!")
|
||||
}
|
||||
|
||||
return response.json()
|
||||
})
|
||||
.then((responseJson) => {
|
||||
getAvailableWorkflows()
|
||||
})
|
||||
.catch(error => {
|
||||
alert.error(error.toString())
|
||||
});
|
||||
}
|
||||
|
||||
// dropdown with copy etc I guess
|
||||
const WorkflowPaper = (props) => {
|
||||
const { data } = props;
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [anchorEl, setAnchorEl] = React.useState(null);
|
||||
|
||||
var boxWidth = "2px"
|
||||
if (selectedWorkflow.id === data.id) {
|
||||
boxWidth = "4px"
|
||||
}
|
||||
|
||||
var boxColor = "orange"
|
||||
if (data.is_valid) {
|
||||
boxColor = "green"
|
||||
}
|
||||
|
||||
const menuClick = (event) => {
|
||||
setOpen(!open)
|
||||
setAnchorEl(event.currentTarget);
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper square style={paperAppStyle} onClick={() => {
|
||||
if (selectedWorkflow.id !== data.id) {
|
||||
setSelectedWorkflow(data)
|
||||
getWorkflowExecution(data.id)
|
||||
}
|
||||
}}>
|
||||
<div style={{marginLeft: "10px", marginTop: "5px", marginBottom: "5px", width: boxWidth, backgroundColor: boxColor}}>
|
||||
</div>
|
||||
<Grid container style={{margin: "10px 10px 10px 10px", flex: "1"}}>
|
||||
<Grid style={{display: "flex", flexDirection: "column", width: "100%"}}>
|
||||
<Grid item style={{flex: "1", display: "flex"}}>
|
||||
<div style={{flex: "10"}}>
|
||||
<h3 style={{marginBottom: "0px", marginTop: "10px"}}>{data.name}</h3>
|
||||
</div>
|
||||
<div style={{flex: "1"}}>
|
||||
<IconButton
|
||||
aria-label="more"
|
||||
aria-controls="long-menu"
|
||||
aria-haspopup="true"
|
||||
onClick={menuClick}
|
||||
>
|
||||
<MoreVertIcon />
|
||||
</IconButton>
|
||||
<Menu
|
||||
id="long-menu"
|
||||
anchorEl={anchorEl}
|
||||
keepMounted
|
||||
open={open}
|
||||
onClose={() => {
|
||||
setOpen(false)
|
||||
setAnchorEl(null)
|
||||
}}
|
||||
>
|
||||
|
||||
<MenuItem style={{backgroundColor: surfaceColor, color: "white"}} onClick={() => {
|
||||
copyWorkflow(data)
|
||||
setOpen(false)
|
||||
}} key={"copy"}>{"Copy"}</MenuItem>
|
||||
<MenuItem style={{backgroundColor: surfaceColor, color: "white"}} onClick={() => {
|
||||
exportWorkflow(data)
|
||||
setOpen(false)
|
||||
}} key={"export"}>{"Export"}</MenuItem>
|
||||
<MenuItem style={{backgroundColor: surfaceColor, color: "white"}} onClick={() => {
|
||||
deleteWorkflow(data.id)
|
||||
setOpen(false)
|
||||
}} key={"delete"}>{"Delete"}</MenuItem>
|
||||
|
||||
</Menu>
|
||||
</div>
|
||||
</Grid>
|
||||
<div style={{display: "flex", flex: "1"}}>
|
||||
<Grid item style={{flex: "1", justifyContent: "center"}}>
|
||||
<a href={"/workflows/"+data.id}>
|
||||
<Button style={{}} color="primary" variant="outlined" onClick={() => {}}>Edit</Button>
|
||||
</a>
|
||||
<Button style={{}} color="primary" variant="outlined" onClick={() => executeWorkflow(data.id)}>Execute</Button>
|
||||
</Grid>
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
|
||||
const executionPaper = (data) => {
|
||||
var boxWidth = "2px"
|
||||
if (selectedExecution.execution_id === data.execution_id) {
|
||||
boxWidth = "4px"
|
||||
}
|
||||
|
||||
var boxColor = "orange"
|
||||
if (data.status === "ABORTED" || data.status === "UNFINISHED" || data.status === "FAILURE"){
|
||||
boxColor = "red"
|
||||
} else if (data.status === "FINISHED") {
|
||||
boxColor = "green"
|
||||
}
|
||||
|
||||
var t = new Date(data.started_at*1000)
|
||||
if (data.workflow.actions === null || data.workflow.actions === undefined ) {
|
||||
return null
|
||||
}
|
||||
|
||||
var actions = data.workflow.actions.length
|
||||
if (data.results !== null) {
|
||||
var results = data.results.length
|
||||
}
|
||||
return (
|
||||
<Paper square style={paperAppStyle} onClick={() => {
|
||||
setSelectedExecution(data)
|
||||
}}>
|
||||
<div style={{marginLeft: "10px", marginTop: "5px", marginBottom: "5px", width: boxWidth, backgroundColor: boxColor}} />
|
||||
<Grid container style={{margin: "10px 10px 10px 10px", flex: "1"}}>
|
||||
<Grid style={{display: "flex", flexDirection: "column", width: "100%"}}>
|
||||
<Grid item style={{flex: "1", display: "flex"}}>
|
||||
<div style={{flex: "5"}}>
|
||||
<h3 style={{marginBottom: "0px", marginTop: "10px"}}><b>Status</b>: {data.status}</h3>
|
||||
Actions: {results}/{actions}
|
||||
</div>
|
||||
<div style={{flex: "1", marginTop: "10px"}}>
|
||||
<Button style={{}} color="primary" disabled={data.status === "FAILURE" || data.status === "ABORTED" || data.status === "FINISHED"} variant="outlined" onClick={() => abortExecution(data.workflow_id, data.execution_id)}>Abort</Button>
|
||||
</div>
|
||||
</Grid>
|
||||
<div style={{display: "flex", flex: "1"}}>
|
||||
<Grid item style={{flex: "10", justifyContent: "center"}}>
|
||||
Started: {t.toISOString()}
|
||||
</Grid>
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
|
||||
const dividerColor = "rgb(225, 228, 232)"
|
||||
|
||||
const resultPaperAppStyle = {
|
||||
minHeight: "100px",
|
||||
minWidth: "100%",
|
||||
overflow: "hidden",
|
||||
maxWidth: "100%",
|
||||
marginTop: "5px",
|
||||
color: "white",
|
||||
backgroundColor: surfaceColor,
|
||||
cursor: "pointer",
|
||||
display: "flex",
|
||||
}
|
||||
|
||||
function replaceAll(string, search, replace) {
|
||||
return string.split(search).join(replace);
|
||||
}
|
||||
|
||||
const resultsPaper = (data) => {
|
||||
var boxWidth = "2px"
|
||||
var boxColor = "orange"
|
||||
if (data.status === "ABORTED" || data.status === "UNFINISHED" || data.status === "FAILURE"){
|
||||
boxColor = "red"
|
||||
} else if (data.status === "FINISHED" || data.status === "SUCCESS") {
|
||||
boxColor = "green"
|
||||
} else if (data.status === "SKIPPED" || data.status === "EXECUTING") {
|
||||
boxColor = "yellow"
|
||||
} else {
|
||||
boxColor = "green"
|
||||
}
|
||||
|
||||
var t = new Date(data.started_at*1000)
|
||||
var showResult = data.result.trim()
|
||||
if (showResult.startsWith("{") && showResult.endsWith("}")) {
|
||||
//showResult = <JSONPretty
|
||||
// id="json-pretty"
|
||||
// theme={JSONPrettyMon}
|
||||
// data={showResult}/>
|
||||
|
||||
showResult = replaceAll(showResult, " None", " \"None\"");
|
||||
console.log(showResult)
|
||||
showResult = <ReactJson
|
||||
src={JSON.parse(showResult)}
|
||||
theme="solarized"
|
||||
collapsed={collapseJson}
|
||||
displayDataTypes={false}
|
||||
name={"Results for "+data.action.name}
|
||||
/>
|
||||
} else {
|
||||
// FIXME - have everything parsed as json, either just for frontend
|
||||
// or in the backend
|
||||
/*
|
||||
const newdata = {"result": data.result}
|
||||
showResult = <ReactJson
|
||||
src={JSON.parse(newdata)}
|
||||
theme="solarized"
|
||||
collapsed={collapseJson}
|
||||
displayDataTypes={false}
|
||||
name={"Results for "+data.action.name}
|
||||
/>
|
||||
*/
|
||||
}
|
||||
|
||||
console.log(data)
|
||||
return (
|
||||
<Paper square style={resultPaperAppStyle} onClick={() => {}}>
|
||||
<div style={{marginLeft: "10px", marginTop: "5px", marginBottom: "5px", marginRight: "5px", width: boxWidth, backgroundColor: boxColor}}>
|
||||
</div>
|
||||
<Grid container style={{margin: "10px 10px 10px 10px", flex: "1"}}>
|
||||
<Grid style={{display: "flex", flexDirection: "column", width: "100%"}}>
|
||||
<Grid item style={{flex: "1"}}>
|
||||
<h4 style={{marginBottom: "0px", marginTop: "10px"}}><b>Status</b>: {data.status}</h4>
|
||||
</Grid>
|
||||
<Grid item style={{flex: "1", justifyContent: "center"}}>
|
||||
App: {data.action.app_name}, Version: {data.action.app_version}
|
||||
</Grid>
|
||||
<Grid item style={{flex: "1", justifyContent: "center"}}>
|
||||
Action: {data.action.name}, Environment: {data.action.environment}
|
||||
</Grid>
|
||||
<div style={{display: "flex", flex: "1"}}>
|
||||
<Grid item style={{flex: "10", justifyContent: "center"}}>
|
||||
Started: {t.toISOString()}
|
||||
</Grid>
|
||||
</div>
|
||||
<Divider style={{marginBottom: "10px", marginTop: "10px", height: "1px", width: "100%", backgroundColor: dividerColor}}/>
|
||||
<div style={{display: "flex", flex: "1"}}>
|
||||
<Grid item style={{flex: "10", justifyContent: "center"}}>
|
||||
{showResult}
|
||||
</Grid>
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
|
||||
const resultsHandler = Object.getOwnPropertyNames(selectedExecution).length > 0 && selectedExecution.results !== null ?
|
||||
<div>
|
||||
{selectedExecution.results.sort((a, b) => a.started_at - b.started_at).map(data => {
|
||||
return (
|
||||
resultsPaper(data)
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
No results yet
|
||||
</div>
|
||||
|
||||
const resultsLength = Object.getOwnPropertyNames(selectedExecution).length > 0 && selectedExecution.results !== null ? selectedExecution.results.length : 0
|
||||
|
||||
const ExecutionDetails = () => {
|
||||
var starttime = new Date(selectedExecution.started_at*1000)
|
||||
var endtime = new Date(selectedExecution.started_at*1000)
|
||||
console.log(selectedExecution)
|
||||
|
||||
const arg = selectedExecution.execution_argument !== undefined && selectedExecution.execution_argument.length > 0 ?
|
||||
<div>
|
||||
Argument: {selectedExecution.execution_argument}
|
||||
</div>
|
||||
: null
|
||||
/*
|
||||
<div>
|
||||
ID: {selectedExecution.execution_id}
|
||||
</div>
|
||||
*/
|
||||
if (Object.getOwnPropertyNames(selectedExecution).length > 0 && selectedExecution.workflow.actions !== null) {
|
||||
return (
|
||||
<div >
|
||||
<div>
|
||||
Actions: {selectedExecution.workflow.actions.length}
|
||||
</div>
|
||||
<div>
|
||||
Results: {resultsLength}
|
||||
</div>
|
||||
<div>
|
||||
Status: {selectedExecution.status}
|
||||
</div>
|
||||
<div>
|
||||
Starttime: {starttime.toISOString()}
|
||||
</div>
|
||||
<div>
|
||||
Finished: {endtime.toISOString()}
|
||||
</div>
|
||||
<div>
|
||||
Result: {selectedExecution.result}
|
||||
</div>
|
||||
<div>
|
||||
Last node: {selectedExecution.last_node}
|
||||
</div>
|
||||
{arg}
|
||||
<Divider style={{marginBottom: "10px", marginTop: "10px", height: "1px", width: "100%", backgroundColor: dividerColor}}/>
|
||||
{resultsHandler}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<h4>
|
||||
There are no executiondetails yet. Click "execute" to run your first one.
|
||||
</h4>
|
||||
)
|
||||
}
|
||||
|
||||
const ExecutionsView = () => {
|
||||
if (workflowExecutions.length > 0) {
|
||||
const sortedWorkflows = workflowExecutions.sort((a, b) => a.started_at - b.started_at).reverse()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{sortedWorkflows.map(data => {
|
||||
return (
|
||||
executionPaper(data)
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<h4>
|
||||
There are no executions for this workflow yet
|
||||
</h4>
|
||||
)
|
||||
}
|
||||
|
||||
const setNewWorkflow = () => {
|
||||
if (newWorkflowName.length === 0) {
|
||||
return
|
||||
}
|
||||
var workflowdata = {
|
||||
"name": newWorkflowName,
|
||||
"description": newWorkflowDescription,
|
||||
}
|
||||
|
||||
fetch(globalUrl+"/api/v1/workflows", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(workflowdata),
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for workflows :O!")
|
||||
return
|
||||
}
|
||||
return response.json()
|
||||
})
|
||||
.then((responseJson) => {
|
||||
window.location.pathname = "/workflows/"+responseJson["id"]
|
||||
})
|
||||
.catch(error => {
|
||||
alert.error(error.toString())
|
||||
});
|
||||
}
|
||||
|
||||
const modalView = modalOpen ?
|
||||
<Dialog modal
|
||||
open={modalOpen}
|
||||
onClose={() => {setModalOpen(false)}}
|
||||
PaperProps={{
|
||||
style: {
|
||||
backgroundColor: surfaceColor,
|
||||
color: "white",
|
||||
minWidth: "800px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<FormControl>
|
||||
<DialogTitle><div style={{color: "white"}}>New workflow</div></DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
onBlur={(event) => setNewWorkflowname(event.target.value)}
|
||||
InputProps={{
|
||||
style:{
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
color="primary"
|
||||
placeholder="Name"
|
||||
margin="dense"
|
||||
fullWidth
|
||||
/>
|
||||
<TextField
|
||||
onBlur={(event) => setNewWorkflowDescription(event.target.value)}
|
||||
InputProps={{
|
||||
style:{
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
color="primary"
|
||||
placeholder="Description"
|
||||
margin="dense"
|
||||
fullWidth
|
||||
/>
|
||||
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button style={{}} onClick={() => setModalOpen(false)} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button style={{}} disabled={newWorkflowName.length === 0} onClick={() => {
|
||||
setNewWorkflow()
|
||||
setModalOpen(false)
|
||||
}} color="primary">
|
||||
Submit
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</FormControl>
|
||||
</Dialog>
|
||||
: null
|
||||
|
||||
|
||||
const viewSize = {
|
||||
workflowView: 1,
|
||||
executionsView: 1,
|
||||
executionResults: 2,
|
||||
}
|
||||
|
||||
const workflowViewStyle = {
|
||||
flex: viewSize.workflowView,
|
||||
marginLeft: "10px",
|
||||
marginRight: "10px",
|
||||
}
|
||||
|
||||
if (viewSize.workflowView === 0) {
|
||||
workflowViewStyle.display = "none"
|
||||
}
|
||||
|
||||
const workflowView = workflows.length > 0 ?
|
||||
<div style={viewStyle}>
|
||||
<div style={workflowViewStyle}>
|
||||
<div style={{display: "flex"}}>
|
||||
<div style={{flex: "4"}}>
|
||||
<h2>Workflows</h2>
|
||||
</div>
|
||||
<div style={{flex: "1", display: "flex", flexDirection: "row"}}>
|
||||
<div>
|
||||
<Button disabled={true} color="primary" style={{marginTop: "20px",}} variant="outlined" onClick={() => setModalOpen(true)}>Import</Button>
|
||||
</div>
|
||||
<div>
|
||||
<Button color="primary" style={{marginTop: "20px",}} variant="outlined" onClick={() => setModalOpen(true)}>New</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Divider style={{marginBottom: "10px", height: "1px", width: "100%", backgroundColor: dividerColor}}/>
|
||||
|
||||
<div style={scrollStyle}>
|
||||
{workflows.map(data => {
|
||||
return (
|
||||
<WorkflowPaper data={data} />
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{flex: viewSize.executionsView, marginLeft: "10px", marginRight: "10px"}}>
|
||||
<div style={{display: "flex"}}>
|
||||
<div style={{flex: "10"}}>
|
||||
<h2>Executions</h2>
|
||||
</div>
|
||||
<div style={{flex: "1"}}>
|
||||
<Button color="primary" style={{marginTop: "20px"}} variant="outlined" onClick={() => {alert.info("Refreshing executions"); getWorkflowExecution(selectedWorkflow.id)}}>Refresh</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Divider style={{marginBottom: "10px", height: "1px", width: "100%", backgroundColor: dividerColor}}/>
|
||||
<div style={scrollStyle}>
|
||||
<ExecutionsView />
|
||||
</div>
|
||||
</div>
|
||||
<div style={{flex: viewSize.executionResults, marginLeft: "10px", marginRight: "10px", minWidth: "33%"}}>
|
||||
<div style={{display: "flex"}}>
|
||||
<div style={{flex: "3"}}>
|
||||
<h2>Execution Timeline</h2>
|
||||
</div>
|
||||
<div style={{flex: "1"}}>
|
||||
<FormControlLabel
|
||||
style={{color: "white", marginBottom: "0px", marginTop: "10px"}}
|
||||
label=<div style={{color: "white"}}>Collapse results</div>
|
||||
control={<Switch checked={collapseJson} onChange={() => {setCollapseJson(!collapseJson)}} />}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Divider style={{marginBottom: "10px", height: "1px", width: "100%", backgroundColor: dividerColor}}/>
|
||||
<div style={scrollStyle}>
|
||||
<ExecutionDetails />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
<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 framework allowing users to integrate their services and devices to reduce the amount of manual labor required for those tasks. <a href="/docs/workflows" style={{textDecoration: "none", color: "#f85a3e"}}>Click here for more information.</a>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
If you want to jump straight into it, click the following button to create your first workflow:
|
||||
</div>
|
||||
<div>
|
||||
<Button color="primary" style={{marginTop: "20px",}} variant="outlined" onClick={() => setModalOpen(true)}>New workflow</Button>
|
||||
</div>
|
||||
</Paper>
|
||||
</div>
|
||||
|
||||
const loadedCheck = isLoaded && isLoggedIn && workflowDone ?
|
||||
<div>
|
||||
{workflowView}
|
||||
{modalView}
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
</div>
|
||||
|
||||
|
||||
// Maybe use gridview or something, idk
|
||||
return (
|
||||
<div>
|
||||
{loadedCheck}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Workflows
|
||||
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 10 KiB |
Executable
+3
@@ -0,0 +1,3 @@
|
||||
# resize: convert schedule.png -resize 100x100\> schedule100.png
|
||||
# base64: - cat picture.png | base64 -w 0
|
||||
# js insert: data:image/png;base64,<base64>
|
||||
@@ -0,0 +1,427 @@
|
||||
/*!
|
||||
|
||||
=========================================================
|
||||
* Black Dashboard React v1.1.0
|
||||
=========================================================
|
||||
|
||||
* Product Page: https://www.creative-tim.com/product/black-dashboard-react
|
||||
* Copyright 2020 Creative Tim (https://www.creative-tim.com)
|
||||
* Licensed under MIT (https://github.com/creativetimofficial/black-dashboard-react/blob/master/LICENSE.md)
|
||||
|
||||
* Coded by Creative Tim
|
||||
|
||||
=========================================================
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
*/
|
||||
// ##############################
|
||||
// // // Chart variables
|
||||
// #############################
|
||||
|
||||
// chartExample1 and chartExample2 options
|
||||
let chart1_2_options = {
|
||||
maintainAspectRatio: false,
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
tooltips: {
|
||||
backgroundColor: "#f5f5f5",
|
||||
titleFontColor: "#333",
|
||||
bodyFontColor: "#666",
|
||||
bodySpacing: 4,
|
||||
xPadding: 12,
|
||||
mode: "nearest",
|
||||
intersect: 0,
|
||||
position: "nearest"
|
||||
},
|
||||
responsive: true,
|
||||
scales: {
|
||||
yAxes: [
|
||||
{
|
||||
barPercentage: 1.6,
|
||||
gridLines: {
|
||||
drawBorder: false,
|
||||
color: "rgba(29,140,248,0.0)",
|
||||
zeroLineColor: "transparent"
|
||||
},
|
||||
ticks: {
|
||||
suggestedMin: 60,
|
||||
suggestedMax: 125,
|
||||
padding: 20,
|
||||
fontColor: "#9a9a9a"
|
||||
}
|
||||
}
|
||||
],
|
||||
xAxes: [
|
||||
{
|
||||
barPercentage: 1.6,
|
||||
gridLines: {
|
||||
drawBorder: false,
|
||||
color: "rgba(29,140,248,0.1)",
|
||||
zeroLineColor: "transparent"
|
||||
},
|
||||
ticks: {
|
||||
padding: 20,
|
||||
fontColor: "#9a9a9a"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
// #########################################
|
||||
// // // used inside src/views/Dashboard.js
|
||||
// #########################################
|
||||
let chartExample1 = {
|
||||
data1: canvas => {
|
||||
let ctx = canvas.getContext("2d");
|
||||
|
||||
let gradientStroke = ctx.createLinearGradient(0, 230, 0, 50);
|
||||
|
||||
gradientStroke.addColorStop(1, "rgba(29,140,248,0.2)");
|
||||
gradientStroke.addColorStop(0.4, "rgba(29,140,248,0.0)");
|
||||
gradientStroke.addColorStop(0, "rgba(29,140,248,0)"); //blue colors
|
||||
|
||||
return {
|
||||
labels: [
|
||||
"JAN",
|
||||
"FEB",
|
||||
"MAR",
|
||||
"APR",
|
||||
"MAY",
|
||||
"JUN",
|
||||
"JUL",
|
||||
"AUG",
|
||||
"SEP",
|
||||
"OCT",
|
||||
"NOV",
|
||||
"DEC"
|
||||
],
|
||||
datasets: [
|
||||
{
|
||||
label: "My First dataset",
|
||||
fill: true,
|
||||
backgroundColor: gradientStroke,
|
||||
borderColor: "#1f8ef1",
|
||||
borderWidth: 2,
|
||||
borderDash: [],
|
||||
borderDashOffset: 0.0,
|
||||
pointBackgroundColor: "#1f8ef1",
|
||||
pointBorderColor: "rgba(255,255,255,0)",
|
||||
pointHoverBackgroundColor: "#1f8ef1",
|
||||
pointBorderWidth: 20,
|
||||
pointHoverRadius: 4,
|
||||
pointHoverBorderWidth: 15,
|
||||
pointRadius: 4,
|
||||
data: [100, 70, 90, 70, 85, 60, 75, 60, 90, 80, 110, 100]
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
data2: canvas => {
|
||||
let ctx = canvas.getContext("2d");
|
||||
|
||||
let gradientStroke = ctx.createLinearGradient(0, 230, 0, 50);
|
||||
|
||||
gradientStroke.addColorStop(1, "rgba(29,140,248,0.2)");
|
||||
gradientStroke.addColorStop(0.4, "rgba(29,140,248,0.0)");
|
||||
gradientStroke.addColorStop(0, "rgba(29,140,248,0)"); //blue colors
|
||||
|
||||
return {
|
||||
labels: [
|
||||
"JAN",
|
||||
"FEB",
|
||||
"MAR",
|
||||
"APR",
|
||||
"MAY",
|
||||
"JUN",
|
||||
"JUL",
|
||||
"AUG",
|
||||
"SEP",
|
||||
"OCT",
|
||||
"NOV",
|
||||
"DEC"
|
||||
],
|
||||
datasets: [
|
||||
{
|
||||
label: "My First dataset",
|
||||
fill: true,
|
||||
backgroundColor: gradientStroke,
|
||||
borderColor: "#1f8ef1",
|
||||
borderWidth: 2,
|
||||
borderDash: [],
|
||||
borderDashOffset: 0.0,
|
||||
pointBackgroundColor: "#1f8ef1",
|
||||
pointBorderColor: "rgba(255,255,255,0)",
|
||||
pointHoverBackgroundColor: "#1f8ef1",
|
||||
pointBorderWidth: 20,
|
||||
pointHoverRadius: 4,
|
||||
pointHoverBorderWidth: 15,
|
||||
pointRadius: 4,
|
||||
data: [80, 120, 105, 110, 95, 105, 90, 100, 80, 95, 70, 120]
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
data3: canvas => {
|
||||
let ctx = canvas.getContext("2d");
|
||||
|
||||
let gradientStroke = ctx.createLinearGradient(0, 230, 0, 50);
|
||||
|
||||
gradientStroke.addColorStop(1, "rgba(29,140,248,0.2)");
|
||||
gradientStroke.addColorStop(0.4, "rgba(29,140,248,0.0)");
|
||||
gradientStroke.addColorStop(0, "rgba(29,140,248,0)"); //blue colors
|
||||
|
||||
return {
|
||||
labels: [
|
||||
"JAN",
|
||||
"FEB",
|
||||
"MAR",
|
||||
"APR",
|
||||
"MAY",
|
||||
"JUN",
|
||||
"JUL",
|
||||
"AUG",
|
||||
"SEP",
|
||||
"OCT",
|
||||
"NOV",
|
||||
"DEC"
|
||||
],
|
||||
datasets: [
|
||||
{
|
||||
label: "My First dataset",
|
||||
fill: true,
|
||||
backgroundColor: gradientStroke,
|
||||
borderColor: "#1f8ef1",
|
||||
borderWidth: 2,
|
||||
borderDash: [],
|
||||
borderDashOffset: 0.0,
|
||||
pointBackgroundColor: "#1f8ef1",
|
||||
pointBorderColor: "rgba(255,255,255,0)",
|
||||
pointHoverBackgroundColor: "#1f8ef1",
|
||||
pointBorderWidth: 20,
|
||||
pointHoverRadius: 4,
|
||||
pointHoverBorderWidth: 15,
|
||||
pointRadius: 4,
|
||||
data: [60, 80, 65, 130, 80, 105, 90, 130, 70, 115, 60, 130]
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
options: chart1_2_options
|
||||
};
|
||||
|
||||
// #########################################
|
||||
// // // used inside src/views/Dashboard.js
|
||||
// #########################################
|
||||
let chartExample2 = {
|
||||
data: canvas => {
|
||||
let ctx = canvas.getContext("2d");
|
||||
|
||||
let gradientStroke = ctx.createLinearGradient(0, 230, 0, 50);
|
||||
|
||||
gradientStroke.addColorStop(1, "rgba(29,140,248,0.2)");
|
||||
gradientStroke.addColorStop(0.4, "rgba(29,140,248,0.0)");
|
||||
gradientStroke.addColorStop(0, "rgba(29,140,248,0)"); //blue colors
|
||||
|
||||
return {
|
||||
labels: ["JUL", "AUG", "SEP", "OCT", "NOV", "DEC"],
|
||||
datasets: [
|
||||
{
|
||||
label: "Data",
|
||||
fill: true,
|
||||
backgroundColor: gradientStroke,
|
||||
borderColor: "#1f8ef1",
|
||||
borderWidth: 2,
|
||||
borderDash: [],
|
||||
borderDashOffset: 0.0,
|
||||
pointBackgroundColor: "#1f8ef1",
|
||||
pointBorderColor: "rgba(255,255,255,0)",
|
||||
pointHoverBackgroundColor: "#1f8ef1",
|
||||
pointBorderWidth: 20,
|
||||
pointHoverRadius: 4,
|
||||
pointHoverBorderWidth: 15,
|
||||
pointRadius: 4,
|
||||
data: [80, 100, 70, 80, 120, 80]
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
options: chart1_2_options
|
||||
};
|
||||
|
||||
// #########################################
|
||||
// // // used inside src/views/Dashboard.js
|
||||
// #########################################
|
||||
let chartExample3 = {
|
||||
data: canvas => {
|
||||
let ctx = canvas.getContext("2d");
|
||||
|
||||
let gradientStroke = ctx.createLinearGradient(0, 230, 0, 50);
|
||||
|
||||
gradientStroke.addColorStop(1, "rgba(72,72,176,0.1)");
|
||||
gradientStroke.addColorStop(0.4, "rgba(72,72,176,0.0)");
|
||||
gradientStroke.addColorStop(0, "rgba(119,52,169,0)"); //purple colors
|
||||
|
||||
return {
|
||||
labels: ["USA", "GER", "AUS", "UK", "RO", "BR"],
|
||||
datasets: [
|
||||
{
|
||||
label: "Countries",
|
||||
fill: true,
|
||||
backgroundColor: gradientStroke,
|
||||
hoverBackgroundColor: gradientStroke,
|
||||
borderColor: "#d048b6",
|
||||
borderWidth: 2,
|
||||
borderDash: [],
|
||||
borderDashOffset: 0.0,
|
||||
data: [53, 20, 10, 80, 100, 45]
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
options: {
|
||||
maintainAspectRatio: false,
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
tooltips: {
|
||||
backgroundColor: "#f5f5f5",
|
||||
titleFontColor: "#333",
|
||||
bodyFontColor: "#666",
|
||||
bodySpacing: 4,
|
||||
xPadding: 12,
|
||||
mode: "nearest",
|
||||
intersect: 0,
|
||||
position: "nearest"
|
||||
},
|
||||
responsive: true,
|
||||
scales: {
|
||||
yAxes: [
|
||||
{
|
||||
gridLines: {
|
||||
drawBorder: false,
|
||||
color: "rgba(225,78,202,0.1)",
|
||||
zeroLineColor: "transparent"
|
||||
},
|
||||
ticks: {
|
||||
suggestedMin: 60,
|
||||
suggestedMax: 120,
|
||||
padding: 20,
|
||||
fontColor: "#9e9e9e"
|
||||
}
|
||||
}
|
||||
],
|
||||
xAxes: [
|
||||
{
|
||||
gridLines: {
|
||||
drawBorder: false,
|
||||
color: "rgba(225,78,202,0.1)",
|
||||
zeroLineColor: "transparent"
|
||||
},
|
||||
ticks: {
|
||||
padding: 20,
|
||||
fontColor: "#9e9e9e"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// #########################################
|
||||
// // // used inside src/views/Dashboard.js
|
||||
// #########################################
|
||||
const chartExample4 = {
|
||||
data: canvas => {
|
||||
let ctx = canvas.getContext("2d");
|
||||
|
||||
let gradientStroke = ctx.createLinearGradient(0, 230, 0, 50);
|
||||
|
||||
gradientStroke.addColorStop(1, "rgba(66,134,121,0.15)");
|
||||
gradientStroke.addColorStop(0.4, "rgba(66,134,121,0.0)"); //green colors
|
||||
gradientStroke.addColorStop(0, "rgba(66,134,121,0)"); //green colors
|
||||
|
||||
return {
|
||||
labels: ["JUL", "AUG", "SEP", "OCT", "NOV"],
|
||||
datasets: [
|
||||
{
|
||||
label: "My First dataset",
|
||||
fill: true,
|
||||
backgroundColor: gradientStroke,
|
||||
borderColor: "#00d6b4",
|
||||
borderWidth: 2,
|
||||
borderDash: [],
|
||||
borderDashOffset: 0.0,
|
||||
pointBackgroundColor: "#00d6b4",
|
||||
pointBorderColor: "rgba(255,255,255,0)",
|
||||
pointHoverBackgroundColor: "#00d6b4",
|
||||
pointBorderWidth: 20,
|
||||
pointHoverRadius: 4,
|
||||
pointHoverBorderWidth: 15,
|
||||
pointRadius: 4,
|
||||
data: [90, 27, 60, 12, 80]
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
options: {
|
||||
maintainAspectRatio: false,
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
|
||||
tooltips: {
|
||||
backgroundColor: "#f5f5f5",
|
||||
titleFontColor: "#333",
|
||||
bodyFontColor: "#666",
|
||||
bodySpacing: 4,
|
||||
xPadding: 12,
|
||||
mode: "nearest",
|
||||
intersect: 0,
|
||||
position: "nearest"
|
||||
},
|
||||
responsive: true,
|
||||
scales: {
|
||||
yAxes: [
|
||||
{
|
||||
barPercentage: 1.6,
|
||||
gridLines: {
|
||||
drawBorder: false,
|
||||
color: "rgba(29,140,248,0.0)",
|
||||
zeroLineColor: "transparent"
|
||||
},
|
||||
ticks: {
|
||||
suggestedMin: 50,
|
||||
suggestedMax: 125,
|
||||
padding: 20,
|
||||
fontColor: "#9e9e9e"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
xAxes: [
|
||||
{
|
||||
barPercentage: 1.6,
|
||||
gridLines: {
|
||||
drawBorder: false,
|
||||
color: "rgba(0,242,195,0.1)",
|
||||
zeroLineColor: "transparent"
|
||||
},
|
||||
ticks: {
|
||||
padding: 20,
|
||||
fontColor: "#9e9e9e"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
chartExample1, // in src/views/Dashboard.js
|
||||
chartExample2, // in src/views/Dashboard.js
|
||||
chartExample3, // in src/views/Dashboard.js
|
||||
chartExample4 // in src/views/Dashboard.js
|
||||
};
|
||||
@@ -0,0 +1,235 @@
|
||||
const data = [{
|
||||
selector: 'node',
|
||||
css: {
|
||||
'label': 'data(label)',
|
||||
'text-valign': 'center',
|
||||
'font-family': 'Segoe UI, Tahoma, Geneva, Verdana, sans-serif, sans-serif',
|
||||
'font-weight': 'lighter',
|
||||
'margin-right': '10px',
|
||||
'font-size': '15px',
|
||||
'width': '80px',
|
||||
'height': '80px',
|
||||
'color': 'white',
|
||||
'padding': '10px',
|
||||
'margin': '5px',
|
||||
'border-width': '1px',
|
||||
'text-margin-x': '10px',
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: 'edge',
|
||||
css: {
|
||||
'target-arrow-shape': 'triangle',
|
||||
'target-arrow-color': 'yellow',
|
||||
'curve-style': 'unbundled-bezier',
|
||||
'label': 'data(label)',
|
||||
'text-margin-y': '-15px',
|
||||
"line-fill": "linear-gradient",
|
||||
"line-gradient-stop-colors": ["cyan", "yellow"],
|
||||
"line-gradient-stop-positions": ["0.0", "100"],
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: `node[type="ACTION"]`,
|
||||
css: {
|
||||
'shape': 'square',
|
||||
'background-color': '#213243',
|
||||
'border-color': '#81c784',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: `node[?small_image]`,
|
||||
css: {
|
||||
'background-image': 'data(small_image)',
|
||||
'text-halign': 'right',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: `node[?large_image]`,
|
||||
css: {
|
||||
'background-image': 'data(large_image)',
|
||||
'text-halign': 'right',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: `node[type="CONDITION"]`,
|
||||
css: {
|
||||
'shape': 'diamond',
|
||||
'border-color': '##FFEB3B',
|
||||
'padding': '30px'
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: `node[type="eventAction"]`,
|
||||
css: {
|
||||
'background-color': '#edbd21',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: `node[type="TRIGGER"]`,
|
||||
css: {
|
||||
'shape': 'octagon',
|
||||
'border-color': 'orange',
|
||||
'background-color': '#213243',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: `node[status="running"]`,
|
||||
css: {
|
||||
'border-color': '#81c784',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: `node[status="stopped"]`,
|
||||
css: {
|
||||
'border-color': 'orange',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: 'node[type="mq"]',
|
||||
css: {
|
||||
'background-color': '#edbd21',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: 'node[?isStartNode]',
|
||||
css: {
|
||||
'shape': 'ellipse',
|
||||
'border-color': '#80deea',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: 'node[?hasErrors]',
|
||||
css: {
|
||||
'color': '#991818',
|
||||
'font-style': 'italic',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: 'node:selected',
|
||||
css: {
|
||||
'background-color': '#77b0d0',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: '.success-highlight',
|
||||
css: {
|
||||
'background-color': '#399645',
|
||||
'transition-property': 'background-color',
|
||||
'transition-duration': '0.5s',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: '.failure-highlight',
|
||||
css: {
|
||||
'background-color': '#8e3530',
|
||||
'transition-property': 'background-color',
|
||||
'transition-duration': '0.5s',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: '.not-executing-highlight',
|
||||
css: {
|
||||
'background-color': 'grey',
|
||||
'border-color': 'grey',
|
||||
'transition-property': '#ffef47',
|
||||
'transition-duration': '0.25s',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: '.executing-highlight',
|
||||
css: {
|
||||
'background-color': '#ffef47',
|
||||
'border-color': '#ffef47',
|
||||
'transition-property': '#ffef47',
|
||||
'transition-duration': '0.25s',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: '.awaiting-data-highlight',
|
||||
css: {
|
||||
'background-color': '#f4ad42',
|
||||
'transition-property': 'background-color',
|
||||
'transition-duration': '0.5s',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: '$node > node',
|
||||
css: {
|
||||
'padding-top': '10px',
|
||||
'padding-left': '10px',
|
||||
'padding-bottom': '10px',
|
||||
'padding-right': '10px',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: 'edge.executing-highlight',
|
||||
css: {
|
||||
'width': '5px',
|
||||
'target-arrow-color': '#ffef47',
|
||||
'line-color': '#ffef47',
|
||||
'transition-property': 'line-color, width',
|
||||
'transition-duration': '0.25s',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: 'edge.success-highlight',
|
||||
css: {
|
||||
'width': '5px',
|
||||
'target-arrow-color': '#399645',
|
||||
'line-color': '#399645',
|
||||
'transition-property': 'line-color, width',
|
||||
'transition-duration': '0.5s',
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: 'edge[?hasErrors]',
|
||||
css: {
|
||||
'target-arrow-color': '#991818',
|
||||
'line-color': '#991818',
|
||||
'line-style': 'dashed'
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: '.eh-handle',
|
||||
style: {
|
||||
'background-color': '#337ab7',
|
||||
'width': '1px',
|
||||
'height': '1px',
|
||||
'shape': 'circle',
|
||||
'border-width': '1px',
|
||||
'border-color': 'black'
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: '.eh-source',
|
||||
style: {
|
||||
'border-width': '3',
|
||||
'border-color': '#337ab7'
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: '.eh-target',
|
||||
style: {
|
||||
'border-width': '3',
|
||||
'border-color': '#337ab7'
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: '.eh-preview, .eh-ghost-edge',
|
||||
style: {
|
||||
'background-color': '#337ab7',
|
||||
'line-color': '#337ab7',
|
||||
'target-arrow-color': '#337ab7',
|
||||
'source-arrow-color': '#337ab7'
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: 'edge:selected',
|
||||
css: {
|
||||
'target-arrow-color': '#f85a3e',
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
export default data
|
||||
@@ -0,0 +1,3 @@
|
||||
const data = [{"name": "cloud", "type": "cloud"}, {"name": "onprem", "type": "onprem"}]
|
||||
|
||||
export default data;
|
||||
@@ -0,0 +1,14 @@
|
||||
@import url('https://fonts.googleapis.com/css?family=Nunito+Sans');
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: "Nunito Sans", sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
|
||||
monospace;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import * as serviceWorker from './serviceWorker';
|
||||
|
||||
|
||||
ReactDOM.render(
|
||||
<App />
|
||||
, document.getElementById('root'));
|
||||
|
||||
// If you want your app to work offline and load faster, you can change
|
||||
// unregister() to register() below. Note this comes with some pitfalls.
|
||||
// Learn more about service workers: http://bit.ly/CRA-PWA
|
||||
serviceWorker.unregister();
|
||||
@@ -0,0 +1,30 @@
|
||||
const Data = {
|
||||
"src": {
|
||||
"name": "Get Tickets",
|
||||
"description": "Get tickets",
|
||||
"outputparameters": [{
|
||||
"name": "SymptomDescription",
|
||||
"schema": {"type": "string"}},
|
||||
{"name": "DetailedDescription",
|
||||
"schema": {"type": "string"}},
|
||||
{"name": "EventSource",
|
||||
"schema": {"type": "string"}
|
||||
}]
|
||||
},
|
||||
"dst": {
|
||||
"name": "Create alert",
|
||||
"description": "Create alert in TheHive",
|
||||
"inputparameters": [{
|
||||
"name": "title",
|
||||
"required": true,
|
||||
"schema": {"type": "string"}},
|
||||
{"name": "description",
|
||||
"required": true,
|
||||
"schema": {"type": "string"}},
|
||||
{"name": "source",
|
||||
"required": true,
|
||||
"schema": {"type": "string"}
|
||||
}]}
|
||||
};
|
||||
|
||||
export default Data;
|
||||
@@ -0,0 +1,127 @@
|
||||
// In production, we register a service worker to serve assets from local cache.
|
||||
|
||||
// This lets the app load faster on subsequent visits in production, and gives
|
||||
// it offline capabilities. However, it also means that developers (and users)
|
||||
// will only see deployed updates on the "N+1" visit to a page, since previously
|
||||
// cached resources are updated in the background.
|
||||
|
||||
// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
|
||||
// This link also includes instructions on opting out of this behavior.
|
||||
|
||||
const isLocalhost = Boolean(
|
||||
window.location.hostname === 'localhost' ||
|
||||
// [::1] is the IPv6 localhost address.
|
||||
window.location.hostname === '[::1]' ||
|
||||
// 127.0.0.1/8 is considered localhost for IPv4.
|
||||
window.location.hostname.match(
|
||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||
)
|
||||
);
|
||||
|
||||
export function register(config) {
|
||||
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||
// The URL constructor is available in all browsers that support SW.
|
||||
const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
|
||||
if (publicUrl.origin !== window.location.origin) {
|
||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||
// from what our page is served on. This might happen if a CDN is used to
|
||||
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||
|
||||
if (isLocalhost) {
|
||||
// This is running on localhost. Let's check if a service worker still exists or not.
|
||||
checkValidServiceWorker(swUrl, config);
|
||||
|
||||
// Add some additional logging to localhost, pointing developers to the
|
||||
// service worker/PWA documentation.
|
||||
navigator.serviceWorker.ready.then(() => {
|
||||
console.log(
|
||||
'This web app is being served cache-first by a service ' +
|
||||
'worker. To learn more, visit https://goo.gl/SC7cgQ'
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// Is not local host. Just register service worker
|
||||
registerValidSW(swUrl, config);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function registerValidSW(swUrl, config) {
|
||||
navigator.serviceWorker
|
||||
.register(swUrl)
|
||||
.then(registration => {
|
||||
registration.onupdatefound = () => {
|
||||
const installingWorker = registration.installing;
|
||||
installingWorker.onstatechange = () => {
|
||||
if (installingWorker.state === 'installed') {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
// At this point, the old content will have been purged and
|
||||
// the fresh content will have been added to the cache.
|
||||
// It's the perfect time to display a "New content is
|
||||
// available; please refresh." message in your web app.
|
||||
console.log('New content is available; please refresh.');
|
||||
|
||||
// Execute callback
|
||||
if (config.onUpdate) {
|
||||
config.onUpdate(registration);
|
||||
}
|
||||
} else {
|
||||
// At this point, everything has been precached.
|
||||
// It's the perfect time to display a
|
||||
// "Content is cached for offline use." message.
|
||||
console.log('Content is cached for offline use.');
|
||||
|
||||
// Execute callback
|
||||
if (config.onSuccess) {
|
||||
config.onSuccess(registration);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error during service worker registration:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function checkValidServiceWorker(swUrl, config) {
|
||||
// Check if the service worker can be found. If it can't reload the page.
|
||||
fetch(swUrl)
|
||||
.then(response => {
|
||||
// Ensure service worker exists, and that we really are getting a JS file.
|
||||
if (
|
||||
response.status === 404 ||
|
||||
response.headers.get('content-type').indexOf('javascript') === -1
|
||||
) {
|
||||
// No service worker found. Probably a different app. Reload the page.
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister().then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Service worker found. Proceed as normal.
|
||||
registerValidSW(swUrl, config);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
console.log(
|
||||
'No internet connection found. App is running in offline mode.'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function unregister() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
const data = {
|
||||
"id":"8ccf0bec1fde018771ab685d2a40bd52",
|
||||
"info":{
|
||||
"url":"",
|
||||
"name":"testing",
|
||||
"description":"wut"
|
||||
},
|
||||
"transforms":{},
|
||||
"actions": {},
|
||||
"type":"webhook",
|
||||
"status":"uninitialized",
|
||||
"running":false
|
||||
}
|
||||
|
||||
export default data;
|
||||
@@ -0,0 +1,3 @@
|
||||
const data = {"actions":[{"app_name":"hello_world","app_version":"1.0.0","errors":null,"id":"70574332-da82-cf17-c723-75fa7b8493c2","is_valid":true,"label":"hello_world","environment":"onprem","name":"hello_world","parameters":null,"position":{"x":353.7438792397648,"y":260.6717930890377},"priority":0},{"app_name":"hello_world","app_version":"1.0.0","errors":null,"id":"30522433-56ed-53c3-575d-766e282e1d3e","is_valid":true,"label":"random_number","environment":"cloud","name":"random_number","parameters":null,"position":{"x":458.30040774503794,"y":104.27580103487651},"priority":0},{"app_name":"hello_world","app_version":"1.0.0","errors":null,"id":"5b7ac5b5-9514-02b9-ebe0-998c0843b104","is_valid":false,"label":"hello_world_2","environment":"onprem","name":"hello_world","parameters":null,"position":{"x":414.7256019053981,"y":-140.46450482659628},"priority":0},{"app_name":"hello_world","app_version":"1.0.0","errors":null,"id":"7e6e7a19-4636-cebc-91c4-052a3769a18b","is_valid":true,"label":"hello_world_3","environment":"cloud","name":"hello_world","parameters":null,"position":{"x":83.59752786243806,"y":50.232317715020734},"priority":0},{"app_name":"hello_world","app_version":"1.0.0","errors":null,"id":"edbf927d-5a00-2405-28ed-47982cdf5110","is_valid":true,"label":"hello_world_4","environment":"cloud","name":"hello_world","parameters":null,"position":{"x":-147.30681300186404,"y":89.16690830150289},"priority":0},{"app_name":"hello_world","app_version":"1.0.0","errors":null,"id":"4844a855-1e2b-669d-fc72-5f398321ac5d","is_valid":false,"label":"hello_world_5","environment":"onprem","name":"hello_world","parameters":null,"position":{"x":130.24982593523967,"y":233.8325632286361},"priority":0},{"app_name":"hello_world","app_version":"1.0.0","errors":null,"id":"6d1d3f8a-1ac9-3db4-0e0f-2fe32e9d3c09","is_valid":true,"label":"hello_world_6","environment":"cloud","name":"hello_world","parameters":null,"position":{"x":83.551088005629,"y":-105.15867327274223},"priority":0},{"app_name":"hello_world","app_version":"1.0.0","errors":null,"id":"469d8c2b-52ac-e397-9a29-becccd04aed8","is_valid":true,"label":"hello_world_7","environment":"cloud","name":"hello_world","parameters":null,"position":{"x":314.4987657226086,"y":10.167183586257954},"priority":0}],"branches":[{"destination_id":"30522433-56ed-53c3-575d-766e282e1d3e","id":"4bcb9795-94e6-7d5f-2074-0d5b27784e0b","source_id":"70574332-da82-cf17-c723-75fa7b8493c2"},{"destination_id":"5b7ac5b5-9514-02b9-ebe0-998c0843b104","id":"fe0ab8e4-a535-61cd-3c09-8fd3d8e40769","source_id":"30522433-56ed-53c3-575d-766e282e1d3e"},{"destination_id":"469d8c2b-52ac-e397-9a29-becccd04aed8","id":"8b9ee9bc-b0ab-0bb6-af61-46d4594b2663","source_id":"30522433-56ed-53c3-575d-766e282e1d3e"},{"destination_id":"6d1d3f8a-1ac9-3db4-0e0f-2fe32e9d3c09","id":"c204d5ef-9cc1-d906-9988-86a624c57783","source_id":"469d8c2b-52ac-e397-9a29-becccd04aed8"},{"destination_id":"6d1d3f8a-1ac9-3db4-0e0f-2fe32e9d3c09","id":"1ffb3934-60ec-8f80-5cee-3ddc0a37fdb6","source_id":"5b7ac5b5-9514-02b9-ebe0-998c0843b104"},{"destination_id":"edbf927d-5a00-2405-28ed-47982cdf5110","id":"9c7fb048-9d0d-cb84-9ba0-be729af9b4d1","source_id":"6d1d3f8a-1ac9-3db4-0e0f-2fe32e9d3c09"},{"destination_id":"edbf927d-5a00-2405-28ed-47982cdf5110","id":"e3ab104e-fc8b-3af5-8daa-bfa57bcf9690","source_id":"7e6e7a19-4636-cebc-91c4-052a3769a18b"},{"destination_id":"7e6e7a19-4636-cebc-91c4-052a3769a18b","id":"b6626081-22dd-3af3-b899-480f60d886ca","source_id":"30522433-56ed-53c3-575d-766e282e1d3e"},{"destination_id":"4844a855-1e2b-669d-fc72-5f398321ac5d","id":"4275cf97-0447-bbda-0c80-ab20d389de1a","source_id":"edbf927d-5a00-2405-28ed-47982cdf5110"}],"conditions":[],"triggers":[],"transforms":[],"description":"asd","id":"2f299808-0f1b-4ae0-97fc-ac17483dfcf7","id":"2f299808-0f1b-4ae0-97fc-ac17483dfcf7","is_valid":true,"name":"test2","start":"70574332-da82-cf17-c723-75fa7b8493c2","owner":{"username":"","id":"","orgs":""},"execution_org":{"name":"","org":"","users":null,"id":""},"workflow_variables":null}
|
||||
|
||||
export default data;
|
||||
Reference in New Issue
Block a user