Binary file not shown.
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 1.1 KiB |
+19
-18
@@ -3,7 +3,7 @@ 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 { removeCookies, useCookies } from 'react-cookie';
|
||||
|
||||
import EditSchedule from "./views/EditSchedule";
|
||||
import Schedules from "./views/Schedules";
|
||||
@@ -93,23 +93,24 @@ const App = (message, props) => {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(responseJson => {
|
||||
if (responseJson.success === true) {
|
||||
//console.log(responseJson.success)
|
||||
setUserData(responseJson)
|
||||
setIsLoggedIn(true)
|
||||
.then(response => response.json())
|
||||
.then(responseJson => {
|
||||
if (responseJson.success === true) {
|
||||
//console.log(responseJson.success)
|
||||
setUserData(responseJson)
|
||||
setIsLoggedIn(true)
|
||||
console.log("Cookies: ", cookies)
|
||||
|
||||
// Updating cookie every request
|
||||
for (var key in responseJson["cookies"]) {
|
||||
setCookie(responseJson["cookies"][key].key, responseJson["cookies"][key].value, { path: "/" })
|
||||
}
|
||||
// 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)
|
||||
});
|
||||
}
|
||||
setIsLoaded(true)
|
||||
})
|
||||
.catch(error => {
|
||||
setIsLoaded(true)
|
||||
});
|
||||
}
|
||||
|
||||
// Dumb for content load (per now), but good for making the site not suddenly reload parts (ajax thingies)
|
||||
@@ -124,7 +125,7 @@ const App = (message, props) => {
|
||||
<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} userdata={userdata} {...props} />
|
||||
<Header cookies={cookies} removeCookie={removeCookie} isLoaded={isLoaded} globalUrl={globalUrl} setIsLoggedIn={setIsLoggedIn} isLoggedIn={isLoggedIn} userdata={userdata} {...props} />
|
||||
<Route exact path="/oauth2" render={props => <Oauth2 isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
|
||||
<Route exact path="/contact" render={props => <Contact isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
|
||||
<Route exact path="/login" render={props => <LoginPage isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} />
|
||||
@@ -140,7 +141,7 @@ const App = (message, props) => {
|
||||
<Route exact path="/apps/new" render={props => <AppCreator isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} {...props} />} />
|
||||
<Route exact path="/apps/edit/:appid" render={props => <AppCreator isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} {...props} />} />
|
||||
<Route exact path="/schedules/:key" render={props => <EditSchedule globalUrl={globalUrl} {...props} />} />
|
||||
<Route exact path="/workflows" render={props => <Workflows isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} cookies={cookies} {...props} />} />
|
||||
<Route exact path="/workflows" render={props => <Workflows cookies={cookies} removeCookie={removeCookie} isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} cookies={cookies} {...props} />} />
|
||||
<Route exact path="/workflows/:key" render={props => <AngularWorkflow userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} {...props} />} />
|
||||
<Route exact path="/docs/:key" render={props => <Docs isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
|
||||
<Route exact path="/docs" render={props => { window.location.pathname = "/docs/about" }} />
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import BackupIcon from '@material-ui/icons/Backup';
|
||||
|
||||
const dragOverStyle = {
|
||||
backgroundColor: 'rgba(0,0,0,0.8)',
|
||||
border: '5px dashed white',
|
||||
borderRadius: '8px',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
overflow: 'hidden',
|
||||
zIndex: 100,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
};
|
||||
|
||||
const Dropzone = ({ children, style, onDrop }) => {
|
||||
const dropzoneRef = useRef(null);
|
||||
const [dragging, setDragging] = useState(false);
|
||||
|
||||
let dragCounter = 0;
|
||||
|
||||
const handleDragOver = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const handleDragEnter = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
dragCounter++;
|
||||
if (e.dataTransfer.items && e.dataTransfer.items.length > 0)
|
||||
setDragging(true);
|
||||
};
|
||||
|
||||
const handleDragLeave = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
dragCounter--;
|
||||
if (dragCounter === 0) setDragging(false);
|
||||
};
|
||||
|
||||
const handleDrop = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setDragging(false);
|
||||
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
|
||||
onDrop(e);
|
||||
e.dataTransfer.clearData();
|
||||
dragCounter = 0;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!dropzoneRef.current) return;
|
||||
|
||||
dropzoneRef.current.addEventListener('dragover', handleDragOver);
|
||||
dropzoneRef.current.addEventListener('dragenter', handleDragEnter);
|
||||
dropzoneRef.current.addEventListener('dragleave', handleDragLeave);
|
||||
dropzoneRef.current.addEventListener('drop', handleDrop);
|
||||
|
||||
return () => {
|
||||
dropzoneRef.current.removeEventListener('dragover', handleDragOver);
|
||||
dropzoneRef.current.removeEventListener('dragenter', handleDragEnter);
|
||||
dropzoneRef.current.removeEventListener('dragleave', handleDragLeave);
|
||||
dropzoneRef.current.removeEventListener('drop', handleDrop);
|
||||
};
|
||||
}, [dropzoneRef]);
|
||||
|
||||
return (
|
||||
<div ref={dropzoneRef} style={{ position: 'relative', ...style }}>
|
||||
{dragging && (
|
||||
<div style={dragOverStyle}>
|
||||
<BackupIcon fontSize="large" />
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dropzone;
|
||||
@@ -19,7 +19,7 @@ const hoverColor = "#f85a3e"
|
||||
const hoverOutColor = "#e8eaf6"
|
||||
|
||||
const Header = props => {
|
||||
const { globalUrl, isLoggedIn, removeCookie, homePage, isLoaded, userdata } = props;
|
||||
const { globalUrl, isLoggedIn, removeCookie, homePage, isLoaded, userdata, cookies } = props;
|
||||
const theme = useTheme();
|
||||
|
||||
const [HomeHoverColor, setHomeHoverColor] = useState(hoverOutColor);
|
||||
@@ -35,27 +35,31 @@ const Header = props => {
|
||||
|
||||
// DEBUG HERE
|
||||
const handleClickLogout = () => {
|
||||
console.log("SHOULD LOG OUT")
|
||||
console.log(isLoggedIn)
|
||||
|
||||
console.log("COOKIES: ", cookies, "Remover: ", removeCookie)
|
||||
// Don't really care about the logout
|
||||
fetch(globalUrl+"/api/v1/logout", {
|
||||
fetch(globalUrl+"/api/v1/logout", {
|
||||
credentials: "include",
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
// Log out anyway
|
||||
console.log("Hey")
|
||||
//cookies.remove("session_token")
|
||||
//window.location.pathname = "/"
|
||||
console.log("Should've logged out")
|
||||
removeCookie("session_token", {path: "/"})
|
||||
window.location.pathname = "/"
|
||||
})
|
||||
removeCookie("session_token", {path: "/workflows"})
|
||||
window.location.reload()
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
});
|
||||
}
|
||||
console.log("Error in logout: ", error)
|
||||
removeCookie("session_token", {path: "/"})
|
||||
window.location.reload()
|
||||
//removeCookie("session_token", {path: "/"})
|
||||
})
|
||||
}
|
||||
|
||||
// Rofl this is weird
|
||||
const handleDocsHover = () => {
|
||||
@@ -158,6 +162,16 @@ const Header = props => {
|
||||
</div>
|
||||
</Link>
|
||||
</ListItem>
|
||||
{/*
|
||||
<ListItem style={{textAlign: "center"}}>
|
||||
<Link to="/pricing" style={hrefStyle}>
|
||||
<div onMouseOver={handleDocsHover} onMouseOut={handleDocsHoverOut} style={{color: DocsHoverColor, cursor: "pointer", display: "flex"}}>
|
||||
<DescriptionIcon style={{marginRight: "5px"}} />
|
||||
<span style={{marginTop: 2}}>Pricing</span>
|
||||
</div>
|
||||
</Link>
|
||||
</ListItem>
|
||||
*/}
|
||||
{/*
|
||||
<ListItem style={{textAlign: "center"}}>
|
||||
<Link to="/configurations" style={hrefStyle}>
|
||||
@@ -183,6 +197,19 @@ const Header = props => {
|
||||
color="primary"> Settings</Button>
|
||||
</Link>
|
||||
</ListItem>
|
||||
{/*
|
||||
<ListItem>
|
||||
<Link to="/contact" style={hrefStyle}>
|
||||
<Button
|
||||
style={{}}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
>
|
||||
Contact
|
||||
</Button>
|
||||
</Link>
|
||||
</ListItem>
|
||||
*/}
|
||||
{userdata === undefined || userdata.admin === undefined || userdata.admin === null || !userdata.admin ? null :
|
||||
<ListItem>
|
||||
<Link to="/admin" style={hrefStyle}>
|
||||
@@ -299,8 +326,8 @@ const Header = props => {
|
||||
</div>
|
||||
|
||||
// <Divider style={{height: "1px", width: "100%", backgroundColor: "rgb(91, 96, 100)"}}/>
|
||||
const loadedCheck = isLoaded ?
|
||||
<div>
|
||||
const loadedCheck =
|
||||
<div style={{minHeight: 68}}>
|
||||
<BrowserView>
|
||||
{loginTextBrowser}
|
||||
</BrowserView>
|
||||
@@ -308,10 +335,6 @@ const Header = props => {
|
||||
{loginTextMobile}
|
||||
</MobileView>
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
</div>
|
||||
|
||||
// <div style={{backgroundImage: "linear-gradient(-90deg,#342f78 0,#29255e 50%,#1b1947 100%"}}>
|
||||
return (
|
||||
<div>
|
||||
|
||||
+136
-27
@@ -3,24 +3,36 @@ import React, { useEffect} from 'react';
|
||||
import {Link} from 'react-router-dom';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import Switch from '@material-ui/core/Switch';
|
||||
import Select from '@material-ui/core/Select';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
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 Grid from '@material-ui/core/Grid';
|
||||
import List from '@material-ui/core/List';
|
||||
import ListItem from '@material-ui/core/ListItem';
|
||||
import ListItemText from '@material-ui/core/ListItemText';
|
||||
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
|
||||
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
|
||||
|
||||
import { useAlert } from "react-alert";
|
||||
|
||||
import { Dialog, DialogTitle, DialogActions, DialogContent } from '@material-ui/core';
|
||||
import { useTheme } from '@material-ui/core/styles';
|
||||
|
||||
import PolymerIcon from '@material-ui/icons/Polymer';
|
||||
import CheckCircleIcon from '@material-ui/icons/CheckCircle';
|
||||
import CloseIcon from '@material-ui/icons/Close';
|
||||
import AppsIcon from '@material-ui/icons/Apps';
|
||||
import ImageIcon from '@material-ui/icons/Image';
|
||||
import DeleteIcon from '@material-ui/icons/Delete';
|
||||
import CachedIcon from '@material-ui/icons/Cached';
|
||||
import AccessibilityNewIcon from '@material-ui/icons/AccessibilityNew';
|
||||
import LockIcon from '@material-ui/icons/Lock';
|
||||
@@ -46,6 +58,8 @@ const Admin = (props) => {
|
||||
const [curTab, setCurTab] = React.useState(0);
|
||||
const [users, setUsers] = React.useState([]);
|
||||
const [organizations, setOrganizations] = React.useState([]);
|
||||
const [orgSyncResponse, setOrgSyncResponse] = React.useState("");
|
||||
|
||||
const [environments, setEnvironments] = React.useState([]);
|
||||
const [authentication, setAuthentication] = React.useState([]);
|
||||
const [schedules, setSchedules] = React.useState([])
|
||||
@@ -182,10 +196,13 @@ const Admin = (props) => {
|
||||
});
|
||||
}
|
||||
|
||||
const enableCloudSync = (apikey, organization) => {
|
||||
const enableCloudSync = (apikey, organization, disableSync) => {
|
||||
setOrgSyncResponse("")
|
||||
|
||||
const data = {
|
||||
apikey: apikey,
|
||||
organization: organization,
|
||||
disable: disableSync,
|
||||
}
|
||||
|
||||
const url = globalUrl + '/api/v1/cloud/setup';
|
||||
@@ -200,21 +217,39 @@ const Admin = (props) => {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
},
|
||||
})
|
||||
.then(response =>
|
||||
response.json().then(responseJson => {
|
||||
setLoading(false)
|
||||
console.log(responseJson)
|
||||
if (responseJson["success"] === false) {
|
||||
alert.error("Failed setting up cloud sync")
|
||||
} else {
|
||||
alert.success("Set up cloud sync!")
|
||||
}
|
||||
}),
|
||||
)
|
||||
.catch(error => {
|
||||
setLoading(false)
|
||||
alert.error("Err: " + error.toString())
|
||||
});
|
||||
.then(response => {
|
||||
setLoading(false)
|
||||
if (response.status === 200) {
|
||||
console.log("Cloud sync success?")
|
||||
} else {
|
||||
console.log("Cloud sync fail?")
|
||||
}
|
||||
|
||||
return response.json()
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (!responseJson.success && responseJson.reason !== undefined) {
|
||||
setOrgSyncResponse(responseJson.reason)
|
||||
alert.error("Failed to handle sync: "+responseJson.reason)
|
||||
} else if (!responseJson.success) {
|
||||
alert.error("Failed to handle sync.")
|
||||
} else {
|
||||
getOrgs()
|
||||
if (disableSync) {
|
||||
alert.success("Successfully disabled sync!")
|
||||
} else {
|
||||
alert.success("Sync successfully set up!")
|
||||
}
|
||||
|
||||
selectedOrganization.cloud_sync = !selectedOrganization.cloud_sync
|
||||
setSelectedOrganization(selectedOrganization)
|
||||
setCloudSyncApikey("")
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
setLoading(false)
|
||||
alert.error("Err: " + error.toString())
|
||||
})
|
||||
}
|
||||
|
||||
const onPasswordChange = () => {
|
||||
@@ -362,9 +397,15 @@ const Admin = (props) => {
|
||||
for (var key in environments) {
|
||||
if (environments[key].Name == name) {
|
||||
if (environments[key].default) {
|
||||
alert.info("Can't delete the default environment")
|
||||
alert.error("Can't delete the default environment")
|
||||
return
|
||||
}
|
||||
|
||||
if (environments[key].type === "cloud") {
|
||||
alert.error("Can't delete the cloud environments")
|
||||
return
|
||||
}
|
||||
|
||||
environments[key].archived = true
|
||||
}
|
||||
|
||||
@@ -514,7 +555,7 @@ const Admin = (props) => {
|
||||
}
|
||||
|
||||
const getOrgs = () => {
|
||||
fetch(globalUrl + "/api/v1/getorgs", {
|
||||
fetch(globalUrl + "/api/v1/orgs", {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -780,6 +821,49 @@ const Admin = (props) => {
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
const GridItem = (props) => {
|
||||
const primary = props.data.primary
|
||||
const secondary = props.data.secondary
|
||||
const primaryIcon = props.data.icon
|
||||
const secondaryIcon = props.data.active ?
|
||||
<CheckCircleIcon style={{color: "green"}} />
|
||||
:
|
||||
<CloseIcon style={{color: "red"}} />
|
||||
|
||||
return (
|
||||
<Grid item xs={6}>
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar>
|
||||
{primaryIcon}
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={primary}
|
||||
secondary={secondary}
|
||||
/>
|
||||
{secondaryIcon}
|
||||
</ListItem>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
const itemColor = "black"
|
||||
var syncList = [
|
||||
{
|
||||
"primary": "Workflows",
|
||||
"secondary": "",
|
||||
"active": false,
|
||||
"icon": <PolymerIcon style={{color: itemColor}}/>,
|
||||
},
|
||||
{
|
||||
"primary": "Apps",
|
||||
"secondary": "",
|
||||
"active": false,
|
||||
"icon": <AppsIcon style={{color: itemColor}}/>,
|
||||
},
|
||||
]
|
||||
|
||||
const cloudSyncModal =
|
||||
<Dialog
|
||||
open={cloudSyncModalOpen}
|
||||
@@ -798,8 +882,6 @@ const Admin = (props) => {
|
||||
</span></DialogTitle>
|
||||
<DialogContent>
|
||||
What does <a href="https://shuffler.io/docs/hybrid#cloud_sync" target="_blank" style={{textDecoration: "none", color: "#f85a3e"}}>cloud sync</a> do?
|
||||
<div style={{marginTop: 5}}/>
|
||||
Cloud Apikey
|
||||
<div style={{display: "flex", marginBottom: 20, }}>
|
||||
<TextField
|
||||
color="primary"
|
||||
@@ -813,6 +895,7 @@ const Admin = (props) => {
|
||||
}}
|
||||
required
|
||||
fullWidth={true}
|
||||
disabled={selectedOrganization.cloud_sync}
|
||||
autoComplete="cloud apikey"
|
||||
id="apikey_field"
|
||||
margin="normal"
|
||||
@@ -822,17 +905,35 @@ const Admin = (props) => {
|
||||
setCloudSyncApikey(event.target.value)
|
||||
}}
|
||||
/>
|
||||
<Button disabled={cloudSyncApikey.length === 0 || loading} variant="contained" style={{ marginLeft: 15, height: 60, margin: "auto", borderRadius: "0px" }} onClick={() => {
|
||||
<Button disabled={(!selectedOrganization.cloud_sync && cloudSyncApikey.length === 0) || loading} variant="contained" style={{ marginLeft: 15, height: 60, margin: "auto", borderRadius: "0px" }} onClick={() => {
|
||||
setLoading(true)
|
||||
enableCloudSync(
|
||||
cloudSyncApikey,
|
||||
selectedOrganization,
|
||||
selectedOrganization.cloud_sync,
|
||||
)
|
||||
}} color="primary">
|
||||
Test sync
|
||||
{selectedOrganization.cloud_sync ?
|
||||
"Stop sync"
|
||||
:
|
||||
"Start sync"
|
||||
}
|
||||
</Button>
|
||||
</div>
|
||||
{orgSyncResponse.length > 0 ?
|
||||
<Typography style={{marginTop: 5, marginBottom: 10}}>
|
||||
Error: {orgSyncResponse}
|
||||
</Typography>
|
||||
: null
|
||||
}
|
||||
|
||||
<Grid container style={{width: "100%", marginBottom: 15, }}>
|
||||
{syncList.map((data, index) => {
|
||||
return (
|
||||
<GridItem key={index} data={data} />
|
||||
)
|
||||
})}
|
||||
</Grid>
|
||||
* New triggers (userinput, hotmail realtime)<div/>
|
||||
* Execute in the cloud rather than onprem<div/>
|
||||
* Apps can be built in the cloud<div/>
|
||||
@@ -1035,9 +1136,9 @@ const Admin = (props) => {
|
||||
}}
|
||||
>
|
||||
Edit user
|
||||
</Button>
|
||||
</Button>
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
</ListItem>
|
||||
)
|
||||
})}
|
||||
</List>
|
||||
@@ -1288,6 +1389,10 @@ const Admin = (props) => {
|
||||
primary="Orborus running (TBD)"
|
||||
style={{minWidth: 200, maxWidth: 200}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Type"
|
||||
style={{minWidth: 150, maxWidth: 150}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Default"
|
||||
style={{minWidth: 150, maxWidth: 150}}
|
||||
@@ -1301,7 +1406,7 @@ const Admin = (props) => {
|
||||
style={{minWidth: 150, maxWidth: 150}}
|
||||
/>
|
||||
</ListItem>
|
||||
{environments === undefined ? null : environments.map((environment, index)=> {
|
||||
{environments === undefined || environments === null ? null : environments.map((environment, index)=> {
|
||||
if (!showArchived && environment.archived) {
|
||||
return null
|
||||
}
|
||||
@@ -1321,6 +1426,10 @@ const Admin = (props) => {
|
||||
primary={"TBD"}
|
||||
style={{minWidth: 200, maxWidth: 200, overflow: "hidden"}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={environment.Type}
|
||||
style={{minWidth: 150, maxWidth: 150}}
|
||||
/>
|
||||
<ListItemText
|
||||
style={{minWidth: 150, maxWidth: 150, overflow: "hidden"}}
|
||||
primary={environment.default ? "true" : null}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -618,8 +618,7 @@ const AppCreator = (props) => {
|
||||
"id": props.match.params.appid,
|
||||
}
|
||||
|
||||
|
||||
if (basedata.info.contact !== undefined) {
|
||||
if (basedata.info !== undefined && basedata.info.contact !== undefined) {
|
||||
data.info["contact"] = basedata.info.contact
|
||||
} else if (contact === "") {
|
||||
data.info["contact"] = {
|
||||
@@ -701,7 +700,7 @@ const AppCreator = (props) => {
|
||||
data.paths[item.url][item.method.toLowerCase()].parameters.push(newitem)
|
||||
//console.log(queryitem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (item.paths.length > 0) {
|
||||
for (querykey in item.paths) {
|
||||
@@ -720,6 +719,30 @@ const AppCreator = (props) => {
|
||||
newitem.description = queryitem.description
|
||||
}
|
||||
|
||||
data.paths[item.url][item.method.toLowerCase()].parameters.push(newitem)
|
||||
//console.log(queryitem)
|
||||
}
|
||||
} else {
|
||||
// Always goes here if they didn't click anything :/
|
||||
const values = getCurrentPaths(item.url)
|
||||
const paths = values[0]
|
||||
|
||||
for (querykey in paths) {
|
||||
const queryitem = paths[querykey]
|
||||
newitem = {
|
||||
"in": "path",
|
||||
"name": queryitem,
|
||||
"description": "Generated by shuffler.io OpenAPI",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
},
|
||||
}
|
||||
|
||||
if (queryitem.description !== undefined) {
|
||||
newitem.description = queryitem.description
|
||||
}
|
||||
|
||||
data.paths[item.url][item.method.toLowerCase()].parameters.push(newitem)
|
||||
//console.log(queryitem)
|
||||
}
|
||||
@@ -1220,7 +1243,7 @@ const AppCreator = (props) => {
|
||||
return errormessage
|
||||
}
|
||||
|
||||
const UrlPathParameters = () => {
|
||||
const getCurrentPaths = (urlPath) => {
|
||||
var paths = []
|
||||
var queries = []
|
||||
|
||||
@@ -1296,7 +1319,16 @@ const AppCreator = (props) => {
|
||||
}
|
||||
}
|
||||
|
||||
return [paths, queries]
|
||||
}
|
||||
|
||||
const UrlPathParameters = () => {
|
||||
const values = getCurrentPaths(urlPath)
|
||||
const paths = values[0]
|
||||
const queries = values[1]
|
||||
|
||||
if (currentAction.paths !== paths && urlPath.length > 0) {
|
||||
console.log("IN PATHS SETTER: !", paths)
|
||||
setActionField("paths", paths)
|
||||
}
|
||||
|
||||
@@ -1563,6 +1595,8 @@ const AppCreator = (props) => {
|
||||
</Button>
|
||||
<Button color="primary" variant="outlined" style={{borderRadius: "0px"}} onClick={() => {
|
||||
console.log(urlPathQueries)
|
||||
console.log(urlPath)
|
||||
// value={urlPath}
|
||||
const errors = getActionErrors()
|
||||
addActionToView(errors)
|
||||
setActionsModalOpen(false)
|
||||
@@ -1653,7 +1687,7 @@ const AppCreator = (props) => {
|
||||
<div style={{color: "white"}}>
|
||||
<h2>Test</h2>
|
||||
Test an action to see whether it performs in an expected way.
|
||||
<Link target="_blank" to="https://shuffler.io/docs/apps#testing" style={{textDecoration: "none", color: "#f85a3e"}}> TBD: Click here to learn more about testing</Link>.
|
||||
<a target="_blank" href="https://shuffler.io/docs/apps#testing" style={{textDecoration: "none", color: "#f85a3e"}}> TBD: Click here to learn more about testing</a>.
|
||||
<div>
|
||||
Test :)
|
||||
</div>
|
||||
@@ -1704,7 +1738,7 @@ const AppCreator = (props) => {
|
||||
// <img src={file} id="logo" style={{width: "100%", height: "100%"}} />
|
||||
|
||||
const imageData = file.length > 0 ? file : fileBase64
|
||||
const imageInfo = <img src={imageData} alt="Click to upload an image (174x174)" id="logo" style={{maxWidth: 174, maxHeight: 174,}} />
|
||||
const imageInfo = <img src={imageData} alt="Click to upload an image (174x174)" id="logo" style={{maxWidth: 174, maxHeight: 174, minWidth: 174, minHeight: 174, objectFit: "contain",}} />
|
||||
|
||||
// Random names for type & autoComplete. Didn't research :^)
|
||||
const landingpageDataBrowser =
|
||||
@@ -1722,7 +1756,7 @@ const AppCreator = (props) => {
|
||||
</Breadcrumbs>
|
||||
<Paper style={boxStyle}>
|
||||
<h2 style={{marginBottom: "10px", color: "white"}}>General information</h2>
|
||||
<Link target="_blank" to="https://shuffler.io/docs/apps#create_openapi_app" style={{textDecoration: "none", color: "#f85a3e"}}>Click here to learn more about app creation</Link>
|
||||
<a target="_blank" href="https://shuffler.io/docs/apps#create_openapi_app" style={{textDecoration: "none", color: "#f85a3e"}}>Click here to learn more about app creation</a>
|
||||
<div style={{color: "white", flex: "1", display: "flex", flexDirection: "row"}}>
|
||||
<Tooltip title="Click to edit the app's image" placement="bottom">
|
||||
<div style={{flex: "1", margin: 10, border: "1px solid #f85a3e", cursor: "pointer", backgroundColor: inputColor, maxWidth: 174, maxHeight: 174}} onClick={() => {upload.click()}}>
|
||||
|
||||
+56
-29
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect} from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { useInterval } from 'react-powerhooks';
|
||||
|
||||
@@ -37,6 +37,7 @@ import DialogActions from '@material-ui/core/DialogActions';
|
||||
import DialogContent from '@material-ui/core/DialogContent';
|
||||
import CircularProgress from '@material-ui/core/CircularProgress';
|
||||
|
||||
import Dropzone from '../components/Dropzone';
|
||||
|
||||
const surfaceColor = "#27292D"
|
||||
const inputColor = "#383B40"
|
||||
@@ -134,6 +135,9 @@ const Apps = (props) => {
|
||||
const [cursearch, setCursearch] = React.useState("")
|
||||
const [sharingConfiguration, setSharingConfiguration] = React.useState("you")
|
||||
|
||||
const [isDropzone, setIsDropzone] = React.useState(false);
|
||||
const upload = React.useRef(null);
|
||||
|
||||
const { start, stop } = useInterval({
|
||||
duration: 5000,
|
||||
startImmediate: false,
|
||||
@@ -177,7 +181,6 @@ const Apps = (props) => {
|
||||
color: "#ffffff",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
margin: 20,
|
||||
}
|
||||
|
||||
const paperAppStyle = {
|
||||
@@ -733,7 +736,7 @@ const Apps = (props) => {
|
||||
- <a href="https://apis.guru/browse-apis/" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">OpenAPI directory</a>
|
||||
- <a href="https://editor.swagger.io/" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">OpenAPI Validator</a>
|
||||
<div/>
|
||||
Apps interact with eachother in workflows. They are created with the app creator, using OpenAPI specification or manually in python. The links above are references to OpenAPI tools and other app repositories. There's ten thousands of them.
|
||||
Apps interact with eachother in workflows. They are created with the app creator, using OpenAPI specification or manually in python. The links above are references to OpenAPI tools and other app repositories. There's thousands of them.
|
||||
<div/>
|
||||
<Divider style={{height: 1, backgroundColor: dividerColor, marginTop: 20, marginBottom: 20}} />
|
||||
<div style={{}}>
|
||||
@@ -790,8 +793,38 @@ const Apps = (props) => {
|
||||
//}
|
||||
}
|
||||
|
||||
const uploadFile = (e) => {
|
||||
const isDropzone = e.dataTransfer?.files.length > 0;
|
||||
const files = isDropzone ? e.dataTransfer.files : e.target.files;
|
||||
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.addEventListener('load', (e) => {
|
||||
const content = e.target.result;
|
||||
setOpenApiData(content);
|
||||
setIsDropzone(isDropzone);
|
||||
setOpenApiModal(true)
|
||||
})
|
||||
|
||||
reader.readAsText(files[0]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (openApiData.length > 0) {
|
||||
setOpenApiError('');
|
||||
validateOpenApi(openApiData);
|
||||
}
|
||||
}, [openApiData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (appValidation && isDropzone) {
|
||||
redirectOpenApi();
|
||||
setIsDropzone(false);
|
||||
}
|
||||
}, [appValidation, isDropzone]);
|
||||
|
||||
const appView = isLoggedIn ?
|
||||
<div style={{maxWidth: window.innerWidth > 1366 ? 1366 : 1200, margin: "auto",}}>
|
||||
<Dropzone style={{maxWidth: window.innerWidth > 1366 ? 1366 : 1200, margin: "auto", padding: 20 }} onDrop={uploadFile}>
|
||||
<div style={appViewStyle}>
|
||||
<div style={{flex: 1}}>
|
||||
<Breadcrumbs aria-label="breadcrumb" separator="›" style={{color: "white",}}>
|
||||
@@ -903,7 +936,7 @@ const Apps = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dropzone>
|
||||
:
|
||||
null
|
||||
|
||||
@@ -1174,7 +1207,7 @@ const Apps = (props) => {
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (responseJson.success) {
|
||||
setAppValidation(responseJson.id)
|
||||
setAppValidation(responseJson.id);
|
||||
} else {
|
||||
if (responseJson.reason !== undefined) {
|
||||
setOpenApiError(responseJson.reason)
|
||||
@@ -1327,7 +1360,7 @@ const Apps = (props) => {
|
||||
</Dialog>
|
||||
: null
|
||||
|
||||
const errorText = openApiError.length > 0 ? <div>Error: {openApiError}</div> : null
|
||||
const errorText = openApiError.length > 0 ? <div style={{marginTop: 10}}>Error: {openApiError}</div> : null
|
||||
const modalView = openApiModal ?
|
||||
<Dialog
|
||||
open={openApiModal}
|
||||
@@ -1376,28 +1409,22 @@ const Apps = (props) => {
|
||||
<div />
|
||||
https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v2.0/json/uber.json
|
||||
*/}
|
||||
Or paste the YAML or JSON specification
|
||||
<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 || appValidation.length > 0} color="primary" onClick={() => {
|
||||
setOpenApiError("")
|
||||
validateOpenApi(openApiData)
|
||||
}}>Validate OpenAPI</Button>
|
||||
}}
|
||||
onChange={e => setOpenApiData(e.target.value)}
|
||||
helperText={<span style={{color:"white", marginBottom: "2px",}}>Must point to a version 2 or 3 specification.</span>}
|
||||
placeholder="OpenAPI text"
|
||||
fullWidth
|
||||
/>
|
||||
<p>Or upload a YAML or JSON specification</p>
|
||||
<input
|
||||
hidden
|
||||
type="file"
|
||||
ref={upload}
|
||||
accept="application/JSON, text/yaml, text/x-yaml, application/x-yaml, application/vnd.yaml"
|
||||
multiple={false}
|
||||
onChange={uploadFile}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => upload.current.click()}
|
||||
>
|
||||
Upload
|
||||
</Button>
|
||||
{errorText}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
|
||||
@@ -160,10 +160,6 @@ const LoginDialog = props => {
|
||||
|
||||
//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>
|
||||
|
||||
console.log("THEME: ", theme.palette.surfaceColor)
|
||||
const basedata =
|
||||
<div style={bodyDivStyle}>
|
||||
<Paper style={{
|
||||
|
||||
@@ -109,13 +109,13 @@ const Settings = (props) => {
|
||||
|
||||
const getSettings = () => {
|
||||
fetch(globalUrl+"/api/v1/getsettings", {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
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!")
|
||||
@@ -137,24 +137,24 @@ const Settings = (props) => {
|
||||
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)
|
||||
}
|
||||
//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)
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ const Settings = (props) => {
|
||||
<div style={{display: "flex", marginTop: "80px"}}>
|
||||
<Paper style={boxStyle}>
|
||||
<h2>APIKEY</h2>
|
||||
<Link to="/docs/API#authentication" style={{textDecoration: "none", color: "#f85a3e"}}>What is the API key used for?</Link>
|
||||
<a target="_blank" href="/docs/API#authentication" style={{textDecoration: "none", color: "#f85a3e"}}>What is the API key used for?</a>
|
||||
<TextField
|
||||
style={{backgroundColor: theme.palette.inputColor, flex: "1"}}
|
||||
InputProps={{
|
||||
|
||||
@@ -33,7 +33,6 @@ import {Link} from 'react-router-dom';
|
||||
import { useAlert } from "react-alert";
|
||||
import ChipInput from 'material-ui-chip-input'
|
||||
|
||||
|
||||
import Dialog from '@material-ui/core/Dialog';
|
||||
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||
import DialogActions from '@material-ui/core/DialogActions';
|
||||
@@ -44,7 +43,7 @@ const inputColor = "#383B40"
|
||||
const surfaceColor = "#27292D"
|
||||
|
||||
const Workflows = (props) => {
|
||||
const { globalUrl, isLoggedIn, isLoaded, } = props;
|
||||
const { globalUrl, isLoggedIn, isLoaded, removeCookie, cookies} = props;
|
||||
document.title = "Shuffle - Workflows"
|
||||
|
||||
const alert = useAlert()
|
||||
@@ -83,6 +82,31 @@ const Workflows = (props) => {
|
||||
}
|
||||
})
|
||||
|
||||
// DEBUG HERE
|
||||
const handleClickLogout = () => {
|
||||
//console.log("Cookies: ", cookies)
|
||||
//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
|
||||
// removeCookie("session_token", {path: "/"})
|
||||
// //window.location = "/login"
|
||||
//})
|
||||
//.catch(error => {
|
||||
// console.log(error)
|
||||
// removeCookie("session_token", {path: "/"})
|
||||
//});
|
||||
}
|
||||
|
||||
const deleteModal = deleteModalOpen ?
|
||||
<Dialog
|
||||
open={deleteModalOpen}
|
||||
@@ -124,13 +148,13 @@ const Workflows = (props) => {
|
||||
|
||||
const getAvailableWorkflows = () => {
|
||||
fetch(globalUrl+"/api/v1/workflows", {
|
||||
method: 'GET',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for workflows :O!")
|
||||
@@ -149,7 +173,7 @@ const Workflows = (props) => {
|
||||
if (isLoggedIn) {
|
||||
alert.error("An error occurred while loading workflows")
|
||||
} else {
|
||||
window.location = "/login"
|
||||
handleClickLogout()
|
||||
}
|
||||
|
||||
return
|
||||
@@ -337,6 +361,9 @@ const Workflows = (props) => {
|
||||
}
|
||||
}
|
||||
|
||||
data.execution_org = {"id": ""}
|
||||
console.log(data)
|
||||
|
||||
let linkElement = document.createElement('a');
|
||||
linkElement.setAttribute('href', dataUri);
|
||||
linkElement.setAttribute('download', exportFileDefaultName);
|
||||
@@ -583,6 +610,10 @@ const Workflows = (props) => {
|
||||
return null
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user